optimizations, certificate verification, code cleanup

This commit is contained in:
Günter Obiltschnig 2014-10-09 20:54:07 +02:00
parent 5b0fa2e06a
commit 80952e11d1
10 changed files with 596 additions and 397 deletions

View File

@ -52,54 +52,22 @@ public:
ulVersion = SECBUFFER_VERSION;
}
AutoSecBufferDesc(const AutoSecBufferDesc& desc):
/// Creates a AutoSecBufferDesc from another buffer, resets the other buffer!
_pSec(desc._pSec),
_autoRelease(desc._autoRelease)
{
poco_check_ptr (_pSec);
poco_static_assert (numBufs > 0);
for (int i = 0; i < numBufs; ++i)
{
_buffers[i].pvBuffer = desc._buffers[i].pvBuffer;
_buffers[i].cbBuffer = desc._buffers[i].cbBuffer;
_buffers[i].BufferType = desc._buffers[i].BufferType;
}
cBuffers = numBufs;
pBuffers = _buffers;
ulVersion = SECBUFFER_VERSION;
// steal the buffers from the original one
const_cast<AutoSecBufferDesc*>(&desc)->initBuffers();
}
AutoSecBufferDesc& operator = (const AutoSecBufferDesc& desc)
{
if (&desc != this)
{
_pSec = desc._pSec;
_autoRelease = desc._autoRelease;
for (int i = 0; i < numBufs; ++i)
{
_buffers[i].pvBuffer = desc._buffers[i].pvBuffer;
_buffers[i].cbBuffer = desc._buffers[i].cbBuffer;
_buffers[i].BufferType = desc._buffers[i].BufferType;
}
cBuffers = numBufs;
pBuffers = _buffers;
ulVersion = desc.ulVersion;
// steal the buffers from the original one
const_cast<AutoSecBufferDesc*>(&desc)->initBuffers();
}
return *this;
}
~AutoSecBufferDesc()
/// Destroys the AutoSecBufferDesc
{
release();
}
void reset(bool autoRelease)
{
release();
_autoRelease = autoRelease;
initBuffers();
cBuffers = numBufs;
pBuffers = _buffers;
ulVersion = SECBUFFER_VERSION;
}
void release()
{
if (_autoRelease)
@ -157,6 +125,9 @@ public:
}
private:
AutoSecBufferDesc(const AutoSecBufferDesc& desc);
AutoSecBufferDesc& operator = (const AutoSecBufferDesc& desc);
void release(int idx, bool force)
{
if (force && _buffers[idx].pvBuffer)

View File

@ -93,12 +93,9 @@ public:
///
/// Client: Same as VERIFY_RELAXED.
VERIFY_ONCE = 3
/// Server: Only request a client certificate on the initial
/// TLS/SSL handshake. Do not ask for a client certificate
/// again in case of a renegotiation.
///
/// Client: Same as VERIFY_RELAXED.
VERIFY_ONCE = 1
/// Same as VERIFY_RELAXED (provided for interface compatibility with
/// the OpenSSL implementation.
};
enum Options
@ -160,6 +157,16 @@ public:
bool sessionCacheEnabled() const;
/// Returns true iff the session cache is enabled.
void enableExtendedCertificateVerification(bool flag = true);
/// Enable or disable the automatic post-connection
/// extended certificate verification.
///
/// See X509Certificate::verify() for more information.
bool extendedCertificateVerificationEnabled() const;
/// Returns true iff automatic extended certificate
/// verification is enabled.
int options() const;
/// Returns the options flags.
@ -202,6 +209,7 @@ private:
Usage _usage;
Context::VerificationMode _mode;
int _options;
bool _extendedCertificateVerification;
std::string _certNameOrPath;
std::string _certStoreName;
HCERTSTORE _hMemCertStore;
@ -245,9 +253,16 @@ inline bool Context::isForServerUse() const
}
inline bool Context::extendedCertificateVerificationEnabled() const
{
return _extendedCertificateVerification;
}
inline bool Context::sessionCacheEnabled() const
{
return false;
return true;
/// Session cache is always enabled with Schannel.
}

View File

@ -25,7 +25,6 @@
#include "Poco/Net/Context.h"
#include "Poco/Net/AutoSecBufferDesc.h"
#include "Poco/Net/X509Certificate.h"
#include "Poco/SharedPtr.h"
#include "Poco/Buffer.h"
#include <winsock2.h>
#include <windows.h>
@ -46,32 +45,11 @@ class NetSSL_Win_API SecureSocketImpl
/// The SocketImpl for SecureStreamSocket.
{
public:
enum
{
IO_BUFFER_SIZE = 65536,
TIMEOUT_MILLISECS = 200
};
enum Mode
{
MODE_CLIENT,
MODE_SERVER
};
enum State
{
ST_INITIAL = 0,
ST_CONNECTING,
ST_CLIENTHANDSHAKESTART,
ST_CLIENTHANDSHAKECONDREAD,
ST_CLIENTHANDSHAKEINCOMPLETE,
ST_CLIENTHANDSHAKEOK,
ST_CLIENTHANDSHAKEEXTERROR,
ST_CLIENTHANDSHAKECONTINUE,
ST_VERIFY,
ST_DONE,
ST_ERROR
};
SecureSocketImpl(Poco::AutoPtr<SocketImpl> pSocketImpl, Context::Ptr pContext);
/// Creates the SecureSocketImpl.
@ -160,8 +138,14 @@ public:
const std::string& getPeerHostName() const;
/// Returns the peer host name.
State getState() const;
/// Returns the state of the socket
void verifyPeerCertificate();
/// Performs post-connect (or post-accept) peer certificate validation,
/// using the peer host name set with setPeerHostName(), or the peer's
/// IP address string if no peer host name has been set.
void verifyPeerCertificate(const std::string& hostName);
/// Performs post-connect (or post-accept) peer certificate validation
/// using the given peer host name.
Context::Ptr context() const;
/// Returns the Context.
@ -172,23 +156,40 @@ public:
poco_socket_t sockfd();
/// Returns the underlying socket descriptor.
int available() const;
/// Returns the number of bytes available in the buffer.
protected:
enum
{
IO_BUFFER_SIZE = 32768,
TIMEOUT_MILLISECS = 200
};
enum State
{
ST_INITIAL = 0,
ST_CONNECTING,
ST_CLIENTHANDSHAKESTART,
ST_CLIENTHANDSHAKECONDREAD,
ST_CLIENTHANDSHAKEINCOMPLETE,
ST_CLIENTHANDSHAKEOK,
ST_CLIENTHANDSHAKEEXTERROR,
ST_CLIENTHANDSHAKECONTINUE,
ST_VERIFY,
ST_DONE,
ST_ERROR
};
int sendRawBytes(const void* buffer, int length, int flags = 0);
/// Sends the data in clearText
int receiveRawBytes(void* buffer, int length, int flags = 0);
/// Receives raw data from the socket and stores it
/// in buffer. Up to length bytes are received.
///
/// Returns the number of bytes received.
void clientConnectVerify();
void sendInitialTokenOutBuffer();
void performServerHandshake();
bool serverHandshakeLoop(PCtxtHandle phContext, PCredHandle phCred, bool requireClientAuth, bool doInitialRead, bool newContext);
void clientVerifyCertificate(PCCERT_CONTEXT pServerCert, const std::string& serverName, DWORD dwCertFlags);
void clientVerifyCertificate(const std::string& hostName);
void verifyCertificateChainClient(PCCERT_CONTEXT pServerCert, PCCERT_CHAIN_CONTEXT pChainContext);
void serverVerifyCertificate(PCCERT_CONTEXT pPeerCert, DWORD dwCertFlags);
void serverVerifyCertificate();
LONG serverDisconnect(PCredHandle phCreds, CtxtHandle* phContext);
LONG clientDisconnect(PCredHandle phCreds, CtxtHandle* phContext);
bool loadSecurityLibrary();
@ -201,8 +202,8 @@ protected:
void performInitialClientHandshake();
SECURITY_STATUS performClientHandshakeLoop();
void performClientHandshakeLoopIncompleteMessage();
void performClientHandshakeLoopCondRead();
void performClientHandshakeLoopRead();
void performClientHandshakeLoopCondReceive();
void performClientHandshakeLoopReceive();
void performClientHandshakeLoopOK();
void performClientHandshakeLoopInit();
void performClientHandshakeExtraBuffer();
@ -219,7 +220,9 @@ protected:
void completeHandshake();
static int lastError();
void stateMachine();
State getState() const;
void setState(State st);
static bool isLocalHost(const std::string& hostName);
private:
SecureSocketImpl(const SecureSocketImpl&);
@ -231,34 +234,30 @@ private:
std::string _peerHostName;
bool _useMachineStore;
bool _clientAuthRequired;
PCCERT_CONTEXT _pServerCertificate;
SecurityFunctionTableW& _securityFunctions;
PCCERT_CONTEXT _pOwnCertificate;
PCCERT_CONTEXT _pPeerCertificate;
CredHandle _hCreds;
CtxtHandle _hContext;
DWORD _clientFlags;
DWORD _serverFlags;
DWORD _contextFlags;
SecurityFunctionTableW& _securityFunctions;
BYTE* _pReceiveBuffer;
DWORD _receiveBufferSize;
BYTE* _pIOBuffer;
DWORD _ioBufferOffset;
Poco::Buffer<BYTE> _overflowBuffer;
Poco::Buffer<BYTE> _sendBuffer;
Poco::Buffer<BYTE> _recvBuffer;
DWORD _recvBufferOffset;
DWORD _ioBufferSize;
SecPkgContext_StreamSizes _streamSizes;
AutoSecBufferDesc<1> _outSecBuffer;
AutoSecBufferDesc<2> _inSecBuffer;
Poco::SharedPtr<Poco::Buffer<BYTE> > _pSendBuffer;
SecBuffer _extraSecBuffer;
bool _doReadFirst;
DWORD _bytesRead;
SECURITY_STATUS _securityStatus;
State _state;
DWORD _outFlags;
std::string _hostName;
bool _needData;
bool _needHandshake;
friend class SecureStreamSocketImpl;

View File

@ -123,6 +123,25 @@ public:
/// Returns true if verification against the issuer certificate
/// was successful, false otherwise.
bool verify(const std::string& hostName) const;
/// Verifies the validity of the certificate against the host name.
///
/// For this check to be successful, the certificate must contain
/// a domain name that matches the domain name
/// of the host.
///
/// Returns true if verification succeeded, or false otherwise.
static bool verify(const Poco::Net::X509Certificate& cert, const std::string& hostName);
/// Verifies the validity of the certificate against the host name.
///
/// For this check to be successful, the certificate must contain
/// a domain name that matches the domain name
/// of the host.
///
/// Returns true if verification succeeded, or false otherwise.
const PCCERT_CONTEXT system() const;
/// Returns the underlying WinCrypt certificate.
@ -139,6 +158,9 @@ protected:
void importPEMCertificate(const char* pBuffer, std::size_t size);
void importDERCertificate(const char* pBuffer, std::size_t size);
static bool containsWildcards(const std::string& commonName);
static bool matchWildcard(const std::string& alias, const std::string& hostName);
private:
enum
{

View File

@ -48,6 +48,7 @@ Context::Context(Usage usage,
_usage(usage),
_mode(verMode),
_options(options),
_extendedCertificateVerification(true),
_certNameOrPath(certNameOrPath),
_certStoreName(certStore),
_hMemCertStore(0),
@ -126,6 +127,12 @@ void Context::init()
}
void Context::enableExtendedCertificateVerification(bool flag)
{
_extendedCertificateVerification = flag;
}
void Context::addTrustedCert(const Poco::Net::X509Certificate& cert)
{
Poco::FastMutex::ScopedLock lock(_mutex);
@ -278,7 +285,7 @@ void Context::acquireSchannelCredentials(CredHandle& credHandle) const
if (isForServerUse())
{
if (_mode == Context::VERIFY_STRICT)
if (_mode >= Context::VERIFY_STRICT)
schannelCred.dwFlags |= SCH_CRED_NO_SYSTEM_MAPPER;
if (_mode == Context::VERIFY_NONE)
@ -286,13 +293,16 @@ void Context::acquireSchannelCredentials(CredHandle& credHandle) const
}
else
{
if (_mode == Context::VERIFY_STRICT)
if (_mode >= Context::VERIFY_STRICT)
schannelCred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
else
schannelCred.dwFlags |= SCH_CRED_USE_DEFAULT_CREDS;
if (_mode == Context::VERIFY_NONE)
schannelCred.dwFlags |= SCH_CRED_MANUAL_CRED_VALIDATION | SCH_CRED_NO_SERVERNAME_CHECK;
if (!_extendedCertificateVerification)
schannelCred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
}
#if defined(SCH_USE_STRONG_CRYPTO)

File diff suppressed because it is too large Load Diff

View File

@ -142,7 +142,7 @@ void SecureStreamSocketImpl::sendUrgent(unsigned char data)
int SecureStreamSocketImpl::available()
{
return 0; // TODO _impl.available();
return _impl.available();
}
@ -198,22 +198,19 @@ bool SecureStreamSocketImpl::getLazyHandshake() const
void SecureStreamSocketImpl::verifyPeerCertificate()
{
// TODO
// _impl.verifyPeerCertificate();
_impl.verifyPeerCertificate();
}
void SecureStreamSocketImpl::verifyPeerCertificate(const std::string& hostName)
{
// TODO
// _impl.verifyPeerCertificate(hostName);
_impl.verifyPeerCertificate(hostName);
}
int SecureStreamSocketImpl::completeHandshake()
{
// TODO
// return _impl.completeHandshake();
_impl.completeHandshake();
return 0;
}

View File

@ -27,7 +27,8 @@
#include "Poco/Buffer.h"
#include "Poco/UnicodeConverter.h"
#include "Poco/Format.h"
#include <sstream>
#include "Poco/RegularExpression.h"
#include "Poco/Net/DNS.h"
namespace Poco {
@ -142,8 +143,53 @@ std::string X509Certificate::subjectName(NID nid) const
void X509Certificate::extractNames(std::string& cmnName, std::set<std::string>& domainNames) const
{
domainNames.clear();
// TODO: extract subject alternative names
cmnName = commonName();
PCERT_EXTENSION pExt = _pCert->pCertInfo->rgExtension;
for (int i = 0; i < _pCert->pCertInfo->cExtension; i++, pExt++)
{
if (std::strcmp(pExt->pszObjId, szOID_SUBJECT_ALT_NAME2) == 0)
{
DWORD flags(0);
#if defined(CRYPT_DECODE_ENABLE_PUNYCODE_FLAG)
flags |= CRYPT_DECODE_ENABLE_PUNYCODE_FLAG;
#endif
Poco::Buffer<char> buffer(256);
DWORD bufferSize = buffer.sizeBytes();
BOOL rc = CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
pExt->pszObjId,
pExt->Value.pbData,
pExt->Value.cbData,
flags,
0,
buffer.begin(),
&bufferSize);
if (!rc && GetLastError() == ERROR_MORE_DATA)
{
buffer.resize(bufferSize);
rc = CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
pExt->pszObjId,
pExt->Value.pbData,
pExt->Value.cbData,
flags,
0,
buffer.begin(),
&bufferSize);
}
if (rc)
{
PCERT_ALT_NAME_INFO pNameInfo = reinterpret_cast<PCERT_ALT_NAME_INFO>(buffer.begin());
for (int i = 0; i < pNameInfo->cAltEntry; i++)
{
std::wstring waltName(pNameInfo->rgAltEntry->pwszDNSName);
std::string altName;
Poco::UnicodeConverter::toUTF8(waltName, altName);
domainNames.insert(altName);
}
}
}
}
if (!cmnName.empty() && domainNames.empty())
{
domainNames.insert(cmnName);
@ -167,32 +213,69 @@ Poco::DateTime X509Certificate::expiresOn() const
bool X509Certificate::issuedBy(const X509Certificate& issuerCertificate) const
{
HCERTSTORE hCertStore = CertOpenSystemStoreW(NULL, L"CA");
if (!hCertStore) throw Poco::SystemException("Cannot open CA store");
// TODO
try
class CertStoreHandle
{
PCCERT_CONTEXT pIssuer = 0;
do
public:
CertStoreHandle(HCERTSTORE hStore):
_hStore(hStore)
{
DWORD flags = CERT_STORE_REVOCATION_FLAG | CERT_STORE_SIGNATURE_FLAG | CERT_STORE_TIME_VALIDITY_FLAG;
pIssuer = CertGetIssuerCertificateFromStore(hCertStore, _pCert, 0, &flags);
if (pIssuer)
{
X509Certificate issuer(pIssuer);
if (flags & CERT_STORE_NO_CRL_FLAG)
flags &= ~(CERT_STORE_NO_CRL_FLAG | CERT_STORE_REVOCATION_FLAG);
if (flags)
break;
}
else break;
}
while (pIssuer);
}
catch (...)
}
~CertStoreHandle()
{
if (_hStore) CertCloseStore(_hStore, 0);
}
HCERTSTORE handle() const
{
return _hStore;
}
private:
HCERTSTORE _hStore;
};
CertStoreHandle caStore(CertOpenSystemStoreW(0, L"CA"));
CertStoreHandle rootStore(CertOpenSystemStoreW(0, L"ROOT"));
bool result = false;
PCCERT_CONTEXT pIssuer;
PCCERT_CONTEXT pIssued = _pCert;
do
{
}
return false;
DWORD flags = CERT_STORE_REVOCATION_FLAG | CERT_STORE_SIGNATURE_FLAG | CERT_STORE_TIME_VALIDITY_FLAG;
pIssuer = 0;
if (caStore.handle())
{
pIssuer = CertGetIssuerCertificateFromStore(caStore.handle(), pIssued, 0, &flags);
}
if (!pIssuer && rootStore.handle())
{
pIssuer = CertGetIssuerCertificateFromStore(rootStore.handle(), pIssued, 0, &flags);
}
if (pIssuer)
{
X509Certificate issuer(pIssuer);
if (flags & CERT_STORE_NO_CRL_FLAG)
flags &= ~(CERT_STORE_NO_CRL_FLAG | CERT_STORE_REVOCATION_FLAG);
if (flags)
break;
if (CertCompareCertificate(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, issuerCertificate.system()->pCertInfo, issuer.system()->pCertInfo))
{
result = true;
break;
}
if (CertCompareCertificate(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pIssuer->pCertInfo, pIssued->pCertInfo))
{
// reached self-signed certificate
break;
}
}
else break;
}
while (pIssuer);
return result;
}
@ -321,4 +404,86 @@ void X509Certificate::importDERCertificate(const char* pBuffer, std::size_t size
}
bool X509Certificate::verify(const std::string& hostName) const
{
return verify(*this, hostName);
}
bool X509Certificate::verify(const Poco::Net::X509Certificate& certificate, const std::string& hostName)
{
std::string commonName;
std::set<std::string> dnsNames;
certificate.extractNames(commonName, dnsNames);
if (!commonName.empty()) dnsNames.insert(commonName);
bool ok = (dnsNames.find(hostName) != dnsNames.end());
if (!ok)
{
for (std::set<std::string>::const_iterator it = dnsNames.begin(); !ok && it != dnsNames.end(); ++it)
{
try
{
// two cases: strData contains wildcards or not
if (containsWildcards(*it))
{
// a compare by IPAddress is not possible with wildcards
// only allow compare by name
ok = matchWildcard(*it, hostName);
}
else
{
// it depends on hostName if we compare by IP or by alias
IPAddress ip;
if (IPAddress::tryParse(hostName, ip))
{
// compare by IP
const HostEntry& heData = DNS::resolve(*it);
const HostEntry::AddressList& addr = heData.addresses();
HostEntry::AddressList::const_iterator it = addr.begin();
HostEntry::AddressList::const_iterator itEnd = addr.end();
for (; it != itEnd && !ok; ++it)
{
ok = (*it == ip);
}
}
else
{
ok = Poco::icompare(*it, hostName) == 0;
}
}
}
catch (NoAddressFoundException&)
{
}
catch (HostNotFoundException&)
{
}
}
}
return ok;
}
bool X509Certificate::containsWildcards(const std::string& commonName)
{
return (commonName.find('*') != std::string::npos || commonName.find('?') != std::string::npos);
}
bool X509Certificate::matchWildcard(const std::string& wildcard, const std::string& hostName)
{
// fix wildcards
std::string wildcardExpr("^");
wildcardExpr += Poco::replace(wildcard, ".", "\\.");
Poco::replaceInPlace(wildcardExpr, "*", ".*");
Poco::replaceInPlace(wildcardExpr, "..*", ".*");
Poco::replaceInPlace(wildcardExpr, "?", ".?");
Poco::replaceInPlace(wildcardExpr, "..?", ".?");
wildcardExpr += "$";
Poco::RegularExpression expr(wildcardExpr, Poco::RegularExpression::RE_CASELESS);
return expr.match(hostName);
}
} } // namespace Poco::Net

View File

@ -293,13 +293,21 @@ void HTTPSClientSessionTest::testInterop()
HTTPRequest request(HTTPRequest::HTTP_GET, "/public/poco/NetSSL.txt");
s.sendRequest(request);
Poco::Net::X509Certificate cert = s.serverCertificate();
HTTPResponse response;
std::istream& rs = s.receiveResponse(response);
std::ostringstream ostr;
StreamCopier::copyStream(rs, ostr);
std::string str(ostr.str());
assert (str == "This is a test file for NetSSL.\n");
assert (cert.commonName() == "secure.appinf.com" || cert.commonName() == "*.appinf.com");
std::string commonName;
std::set<std::string> domainNames;
cert.extractNames(commonName, domainNames);
assert (commonName == "secure.appinf.com" || commonName == "*.appinf.com");
assert (domainNames.find("appinf.com") != domainNames.end()
|| domainNames.find("*.appinf.com") != domainNames.end());
}

View File

@ -204,7 +204,8 @@ void HTTPSServerTest::testIdentityRequestKeepAlive()
assert (response.getContentLength() == body.size());
assert (response.getContentType() == "text/plain");
assert (!response.getKeepAlive());
assert (rbody == body);}
assert (rbody == body);
}
void HTTPSServerTest::testChunkedRequestKeepAlive()