summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'Swiften/TLS/Schannel/SchannelContext.cpp')
-rw-r--r--Swiften/TLS/Schannel/SchannelContext.cpp143
1 files changed, 131 insertions, 12 deletions
diff --git a/Swiften/TLS/Schannel/SchannelContext.cpp b/Swiften/TLS/Schannel/SchannelContext.cpp
index b2fea65..9be1ded 100644
--- a/Swiften/TLS/Schannel/SchannelContext.cpp
+++ b/Swiften/TLS/Schannel/SchannelContext.cpp
@@ -1,27 +1,27 @@
/*
* Copyright (c) 2011 Soren Dreijer
* Licensed under the simplified BSD license.
* See Documentation/Licenses/BSD-simplified.txt for more information.
*/
-#include <Swiften/TLS/Schannel/SchannelContext.h>
-#include <Swiften/TLS/Schannel/SchannelCertificate.h>
+#include "Swiften/TLS/Schannel/SchannelContext.h"
+#include "Swiften/TLS/Schannel/SchannelCertificate.h"
#include <Swiften/TLS/CAPICertificate.h>
+#include <WinHTTP.h> // For SECURITY_FLAG_IGNORE_CERT_CN_INVALID
namespace Swift {
//------------------------------------------------------------------------
SchannelContext::SchannelContext()
: m_state(Start)
, m_secContext(0)
-, m_verificationError(CertificateVerificationError::UnknownError)
, m_my_cert_store(NULL)
, m_cert_store_name("MY")
, m_cert_name()
{
m_ctxtFlags = ISC_REQ_ALLOCATE_MEMORY |
ISC_REQ_CONFIDENTIALITY |
ISC_REQ_EXTENDED_ERROR |
ISC_REQ_INTEGRITY |
ISC_REQ_REPLAY_DETECT |
@@ -44,19 +44,19 @@ SchannelContext::~SchannelContext()
void SchannelContext::determineStreamSizes()
{
QueryContextAttributes(m_ctxtHandle, SECPKG_ATTR_STREAM_SIZES, &m_streamSizes);
}
//------------------------------------------------------------------------
void SchannelContext::connect()
{
- PCCERT_CONTEXT pCertContext = NULL;
+ ScopedCertContext pCertContext;
m_state = Connecting;
// If a user name is specified, then attempt to find a client
// certificate. Otherwise, just create a NULL credential.
if (!m_cert_name.empty())
{
if (m_my_cert_store == NULL)
{
@@ -80,24 +80,24 @@ void SchannelContext::connect()
// We use an empty list for client certificates
PCCERT_CONTEXT clientCerts[1] = {0};
SCHANNEL_CRED sc = {0};
sc.dwVersion = SCHANNEL_CRED_VERSION;
/////SSL3?
sc.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT | SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT;
- sc.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION | SCH_CRED_REVOCATION_CHECK_CHAIN;
+ sc.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION;
if (pCertContext)
{
sc.cCreds = 1;
- sc.paCred = &pCertContext;
+ sc.paCred = pCertContext.GetPointer();
sc.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS;
}
else
{
sc.cCreds = 0; // Let Crypto API find the appropriate certificate for us
sc.paCred = clientCerts;
sc.dwFlags |= SCH_CRED_USE_DEFAULT_CREDS;
}
@@ -108,22 +108,19 @@ void SchannelContext::connect()
NULL,
UNISP_NAME,
SECPKG_CRED_OUTBOUND,
NULL,
&sc,
NULL,
NULL,
m_credHandle.Reset(),
NULL);
-
- // cleanup: Free the certificate context. Schannel has already made its own copy.
- if (pCertContext) CertFreeCertificateContext(pCertContext);
-
+
if (status != SEC_E_OK)
{
// We failed to obtain the credentials handle
indicateError();
return;
}
SecBuffer outBuffers[2];
@@ -158,36 +155,109 @@ void SchannelContext::connect()
0,
m_ctxtHandle.Reset(),
&outBufferDesc,
&m_secContext,
NULL);
if (status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED)
{
// We failed to initialize the security context
+ handleCertError(status);
indicateError();
return;
}
// Start the handshake
sendDataOnNetwork(outBuffers[0].pvBuffer, outBuffers[0].cbBuffer);
if (status == SEC_E_OK)
{
+ status = validateServerCertificate();
+ if (status != SEC_E_OK)
+ handleCertError(status);
+
m_state = Connected;
determineStreamSizes();
onConnected();
}
}
//------------------------------------------------------------------------
+SECURITY_STATUS SchannelContext::validateServerCertificate()
+{
+ SchannelCertificate::ref pServerCert = boost::dynamic_pointer_cast<SchannelCertificate>( getPeerCertificate() );
+ if (!pServerCert)
+ return SEC_E_WRONG_PRINCIPAL;
+
+ const LPSTR usage[] =
+ {
+ szOID_PKIX_KP_SERVER_AUTH,
+ szOID_SERVER_GATED_CRYPTO,
+ szOID_SGC_NETSCAPE
+ };
+
+ CERT_CHAIN_PARA chainParams = {0};
+ chainParams.cbSize = sizeof(chainParams);
+ chainParams.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR;
+ chainParams.RequestedUsage.Usage.cUsageIdentifier = ARRAYSIZE(usage);
+ chainParams.RequestedUsage.Usage.rgpszUsageIdentifier = const_cast<LPSTR*>(usage);
+
+ DWORD chainFlags = CERT_CHAIN_CACHE_END_CERT | CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT;
+
+ ScopedCertChainContext pChainContext;
+
+ BOOL success = CertGetCertificateChain(
+ NULL, // Use the chain engine for the current user (assumes a user is logged in)
+ pServerCert->getCertContext(),
+ NULL,
+ NULL,
+ &chainParams,
+ chainFlags,
+ NULL,
+ pChainContext.Reset());
+
+ if (!success)
+ return GetLastError();
+
+ SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslChainPolicy = {0};
+ sslChainPolicy.cbSize = sizeof(sslChainPolicy);
+ sslChainPolicy.dwAuthType = AUTHTYPE_SERVER;
+ sslChainPolicy.fdwChecks = SECURITY_FLAG_IGNORE_CERT_CN_INVALID; // Swiften checks the server name for us. Is this the correct way to disable server name checking?
+ sslChainPolicy.pwszServerName = NULL;
+
+ CERT_CHAIN_POLICY_PARA certChainPolicy = {0};
+ certChainPolicy.cbSize = sizeof(certChainPolicy);
+ certChainPolicy.dwFlags = CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG; // Swiften checks the server name for us. Is this the correct way to disable server name checking?
+ certChainPolicy.pvExtraPolicyPara = &sslChainPolicy;
+
+ CERT_CHAIN_POLICY_STATUS certChainPolicyStatus = {0};
+ certChainPolicyStatus.cbSize = sizeof(certChainPolicyStatus);
+
+ // Verify the chain
+ if (!CertVerifyCertificateChainPolicy(
+ CERT_CHAIN_POLICY_SSL,
+ pChainContext,
+ &certChainPolicy,
+ &certChainPolicyStatus))
+ {
+ return GetLastError();
+ }
+
+ if (certChainPolicyStatus.dwError != S_OK)
+ return certChainPolicyStatus.dwError;
+
+ return S_OK;
+}
+
+//------------------------------------------------------------------------
+
void SchannelContext::appendNewData(const SafeByteArray& data)
{
size_t originalSize = m_receivedData.size();
m_receivedData.resize( originalSize + data.size() );
memcpy( &m_receivedData[0] + originalSize, &data[0], data.size() );
}
//------------------------------------------------------------------------
@@ -264,41 +334,87 @@ void SchannelContext::continueHandshake(const SafeByteArray& data)
if (pExtraBuffer->BufferType == SECBUFFER_EXTRA)
m_receivedData.erase(m_receivedData.begin(), m_receivedData.end() - pExtraBuffer->cbBuffer);
else
m_receivedData.clear();
break;
}
else if (status == SEC_E_OK)
{
+ status = validateServerCertificate();
+ if (status != SEC_E_OK)
+ handleCertError(status);
+
SecBuffer* pExtraBuffer = &inBuffers[1];
if (pExtraBuffer && pExtraBuffer->cbBuffer > 0)
m_receivedData.erase(m_receivedData.begin(), m_receivedData.end() - pExtraBuffer->cbBuffer);
else
m_receivedData.clear();
m_state = Connected;
determineStreamSizes();
onConnected();
}
else
{
// We failed to initialize the security context
+ handleCertError(status);
indicateError();
return;
}
}
}
//------------------------------------------------------------------------
+void SchannelContext::handleCertError(SECURITY_STATUS status)
+{
+ if (status == SEC_E_UNTRUSTED_ROOT ||
+ status == CERT_E_UNTRUSTEDROOT ||
+ status == CRYPT_E_ISSUER_SERIALNUMBER ||
+ status == CRYPT_E_SIGNER_NOT_FOUND ||
+ status == CRYPT_E_NO_TRUSTED_SIGNER)
+ {
+ m_verificationError = CertificateVerificationError::Untrusted;
+ }
+ else if (status == SEC_E_CERT_EXPIRED ||
+ status == CERT_E_EXPIRED)
+ {
+ m_verificationError = CertificateVerificationError::Expired;
+ }
+ else if (status == CRYPT_E_SELF_SIGNED)
+ {
+ m_verificationError = CertificateVerificationError::SelfSigned;
+ }
+ else if (status == CRYPT_E_HASH_VALUE ||
+ status == TRUST_E_CERT_SIGNATURE)
+ {
+ m_verificationError = CertificateVerificationError::InvalidSignature;
+ }
+ else if (status == CRYPT_E_REVOKED)
+ {
+ m_verificationError = CertificateVerificationError::Revoked;
+ }
+ else if (status == CRYPT_E_NO_REVOCATION_CHECK ||
+ status == CRYPT_E_REVOCATION_OFFLINE)
+ {
+ m_verificationError = CertificateVerificationError::RevocationCheckFailed;
+ }
+ else
+ {
+ m_verificationError = CertificateVerificationError::UnknownError;
+ }
+}
+
+//------------------------------------------------------------------------
+
void SchannelContext::sendDataOnNetwork(const void* pData, size_t dataSize)
{
if (dataSize > 0 && pData)
{
SafeByteArray byteArray(dataSize);
memcpy(&byteArray[0], pData, dataSize);
onDataForNetwork(byteArray);
}
@@ -443,18 +559,21 @@ void SchannelContext::decryptAndProcessData(const SafeByteArray& data)
m_receivedData.erase(m_receivedData.begin(), m_receivedData.begin() + inData);
}
}
}
//------------------------------------------------------------------------
void SchannelContext::encryptAndSendData(const SafeByteArray& data)
{
+ if (m_streamSizes.cbMaximumMessage == 0)
+ return;
+
SecBuffer outBuffers[4] = {0};
// Calculate the largest required size of the send buffer
size_t messageBufferSize = (data.size() > m_streamSizes.cbMaximumMessage)
? m_streamSizes.cbMaximumMessage
: data.size();
// Allocate a packet for the encrypted data
SafeByteArray sendBuffer;
@@ -538,20 +657,20 @@ Certificate::ref SchannelContext::getPeerCertificate() const
return pCertificate;
}
//------------------------------------------------------------------------
CertificateVerificationError::ref SchannelContext::getPeerCertificateVerificationError() const
{
boost::shared_ptr<CertificateVerificationError> pCertError;
- if (m_state == Error)
- pCertError.reset( new CertificateVerificationError(m_verificationError) );
+ if (m_verificationError)
+ pCertError.reset( new CertificateVerificationError(*m_verificationError) );
return pCertError;
}
//------------------------------------------------------------------------
ByteArray SchannelContext::getFinishMessage() const
{
// TODO: Implement