diff options
-rw-r--r-- | BuildTools/SCons/SConscript.boot | 4 | ||||
-rw-r--r-- | Swift/SConscript | 2 | ||||
-rw-r--r-- | Swiften/Client/ClientError.h | 2 | ||||
-rw-r--r-- | Swiften/Client/CoreClient.cpp | 6 | ||||
-rw-r--r-- | Swiften/TLS/CertificateVerificationError.h | 2 | ||||
-rw-r--r-- | Swiften/TLS/Schannel/SchannelCertificate.h | 7 | ||||
-rw-r--r-- | Swiften/TLS/Schannel/SchannelContext.cpp | 143 | ||||
-rw-r--r-- | Swiften/TLS/Schannel/SchannelContext.h | 4 | ||||
-rw-r--r-- | Swiften/TLS/Schannel/SchannelUtil.h | 135 |
9 files changed, 287 insertions, 18 deletions
diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot index 188184c..dc8a8a5 100644 --- a/BuildTools/SCons/SConscript.boot +++ b/BuildTools/SCons/SConscript.boot @@ -49,18 +49,19 @@ vars.Add(PathVariable("sqlite_libdir", "SQLite library location", None, PathVari vars.Add("sqlite_libname", "SQLite library name", "libsqlite3" if os.name == "nt" else "sqlite3") vars.Add(PathVariable("avahi_includedir", "Avahi headers location", None, PathVariable.PathAccept)) vars.Add(PathVariable("avahi_libdir", "Avahi library location", None, PathVariable.PathAccept)) vars.Add(PathVariable("qt", "Qt location", "", PathVariable.PathAccept)) vars.Add(PathVariable("docbook_xml", "DocBook XML", None, PathVariable.PathAccept)) vars.Add(PathVariable("docbook_xsl", "DocBook XSL", None, PathVariable.PathAccept)) vars.Add(BoolVariable("build_examples", "Build example programs", "yes")) vars.Add(BoolVariable("enable_variants", "Build in a separate dir under build/, depending on compile flags", "no")) vars.Add(BoolVariable("experimental", "Build experimental features", "no")) +vars.Add(BoolVariable("set_iterator_debug_level", "Set _ITERATOR_DEBUG_LEVEL=0", "yes")) ################################################################################ # Set up default build & configure environment ################################################################################ env = Environment(CPPPATH = ["#"], ENV = { 'PATH' : os.environ['PATH'], 'LD_LIBRARY_PATH' : os.environ.get("LD_LIBRARY_PATH", ""), }, variables = vars) @@ -128,19 +129,20 @@ if env["optimize"] : env.Append(CCFLAGS = ["-O2"]) if env["target"] == "xcode" and os.environ["CONFIGURATION"] == "Release" : env.Append(CCFLAGS = ["-Os"]) if env["debug"] : if env["PLATFORM"] == "win32" : env.Append(CCFLAGS = ["/Zi", "/MDd"]) env.Append(LINKFLAGS = ["/DEBUG"]) - env.Append(CPPDEFINES = ["_ITERATOR_DEBUG_LEVEL=0"]) + if env["set_iterator_debug_level"] : + env.Append(CPPDEFINES = ["_ITERATOR_DEBUG_LEVEL=0"]) else : env.Append(CCFLAGS = ["-g"]) elif env["PLATFORM"] == "win32" : env.Append(CCFLAGS = ["/MD"]) if env.get("universal", 0) : assert(env["PLATFORM"] == "darwin") env.Append(CCFLAGS = [ "-isysroot", "/Developer/SDKs/MacOSX10.4u.sdk", diff --git a/Swift/SConscript b/Swift/SConscript index b66058b..49aa985 100644 --- a/Swift/SConscript +++ b/Swift/SConscript @@ -5,12 +5,12 @@ Import("env") SConscript("Controllers/SConscript") if env["SCONS_STAGE"] == "build" : if not GetOption("help") and not env.get("HAVE_OPENSSL", 0) and not env.get("HAVE_SCHANNEL", 0) : print "Error: Swift requires OpenSSL support, and OpenSSL was not found." if "Swift" in env["PROJECTS"] : env["PROJECTS"].remove("Swift") elif not GetOption("help") and not env.get("HAVE_QT", 0) : print "Error: Swift requires Qt. Not building Swift." - env["PROJECTS"].remove("Swift") +# env["PROJECTS"].remove("Swift") elif env["target"] == "native": SConscript("QtUI/SConscript") diff --git a/Swiften/Client/ClientError.h b/Swiften/Client/ClientError.h index baf1b0a..2f2d2af 100644 --- a/Swiften/Client/ClientError.h +++ b/Swiften/Client/ClientError.h @@ -34,18 +34,20 @@ namespace Swift { CertificateNotYetValidError, CertificateSelfSignedError, CertificateRejectedError, CertificateUntrustedError, InvalidCertificatePurposeError, CertificatePathLengthExceededError, InvalidCertificateSignatureError, InvalidCAError, InvalidServerIdentityError, + RevokedError, + RevocationCheckFailedError }; ClientError(Type type = UnknownError) : type_(type) {} Type getType() const { return type_; } private: Type type_; }; diff --git a/Swiften/Client/CoreClient.cpp b/Swiften/Client/CoreClient.cpp index f7e3b21..14481c6 100644 --- a/Swiften/Client/CoreClient.cpp +++ b/Swiften/Client/CoreClient.cpp @@ -265,18 +265,24 @@ void CoreClient::handleSessionFinished(boost::shared_ptr<Error> error) { case CertificateVerificationError::InvalidSignature: clientError = ClientError(ClientError::InvalidCertificateSignatureError); break; case CertificateVerificationError::InvalidCA: clientError = ClientError(ClientError::InvalidCAError); break; case CertificateVerificationError::InvalidServerIdentity: clientError = ClientError(ClientError::InvalidServerIdentityError); break; + case CertificateVerificationError::Revoked: + clientError = ClientError(ClientError::RevokedError); + break; + case CertificateVerificationError::RevocationCheckFailed: + clientError = ClientError(ClientError::RevocationCheckFailedError); + break; } } actualError = boost::optional<ClientError>(clientError); } onDisconnected(actualError); } void CoreClient::handleNeedCredentials() { assert(session_); diff --git a/Swiften/TLS/CertificateVerificationError.h b/Swiften/TLS/CertificateVerificationError.h index 22e6eaf..b17f5df 100644 --- a/Swiften/TLS/CertificateVerificationError.h +++ b/Swiften/TLS/CertificateVerificationError.h @@ -20,18 +20,20 @@ namespace Swift { NotYetValid, SelfSigned, Rejected, Untrusted, InvalidPurpose, PathLengthExceeded, InvalidSignature, InvalidCA, InvalidServerIdentity, + Revoked, + RevocationCheckFailed }; CertificateVerificationError(Type type = UnknownError) : type(type) {} Type getType() const { return type; } private: diff --git a/Swiften/TLS/Schannel/SchannelCertificate.h b/Swiften/TLS/Schannel/SchannelCertificate.h index f531cff..395d3ec 100644 --- a/Swiften/TLS/Schannel/SchannelCertificate.h +++ b/Swiften/TLS/Schannel/SchannelCertificate.h @@ -42,20 +42,25 @@ namespace Swift { return m_dnsNames; } std::vector<std::string> getXMPPAddresses() const { return m_xmppAddresses; } - ByteArray toDER() const; + ScopedCertContext getCertContext() const + { + return m_cert; + } + ByteArray toDER() const; + private: void parse(); std::string wstrToStr(const std::wstring& wstr); void addSRVName(const std::string& name) { m_srvNames.push_back(name); } 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 diff --git a/Swiften/TLS/Schannel/SchannelContext.h b/Swiften/TLS/Schannel/SchannelContext.h index 7726c41..70b0694 100644 --- a/Swiften/TLS/Schannel/SchannelContext.h +++ b/Swiften/TLS/Schannel/SchannelContext.h @@ -45,39 +45,41 @@ namespace Swift virtual Certificate::ref getPeerCertificate() const; virtual CertificateVerificationError::ref getPeerCertificateVerificationError() const; virtual ByteArray getFinishMessage() const; private: void determineStreamSizes(); void continueHandshake(const SafeByteArray& data); void indicateError(); + void handleCertError(SECURITY_STATUS status) ; void sendDataOnNetwork(const void* pData, size_t dataSize); void forwardDataToApplication(const void* pData, size_t dataSize); void decryptAndProcessData(const SafeByteArray& data); void encryptAndSendData(const SafeByteArray& data); void appendNewData(const SafeByteArray& data); + SECURITY_STATUS validateServerCertificate(); private: enum SchannelState { Start, Connecting, Connected, Error }; SchannelState m_state; - CertificateVerificationError m_verificationError; + boost::optional<CertificateVerificationError> m_verificationError; ULONG m_secContext; ScopedCredHandle m_credHandle; ScopedCtxtHandle m_ctxtHandle; DWORD m_ctxtFlags; SecPkgContext_StreamSizes m_streamSizes; std::vector<char> m_receivedData; diff --git a/Swiften/TLS/Schannel/SchannelUtil.h b/Swiften/TLS/Schannel/SchannelUtil.h index 0a54f16..4f73aac 100644 --- a/Swiften/TLS/Schannel/SchannelUtil.h +++ b/Swiften/TLS/Schannel/SchannelUtil.h @@ -240,19 +240,19 @@ namespace Swift { } explicit ScopedCertContext(PCCERT_CONTEXT pCert) : m_pHandle( new HandleContext(pCert) ) { } // Copy constructor - explicit ScopedCertContext(const ScopedCertContext& rhs) + ScopedCertContext(const ScopedCertContext& rhs) { m_pHandle = rhs.m_pHandle; } ~ScopedCertContext() { m_pHandle.reset(); } @@ -261,34 +261,165 @@ namespace Swift FreeContext(); return &m_pHandle->m_pCertCtxt; } operator PCCERT_CONTEXT() const { return m_pHandle->m_pCertCtxt; } + PCCERT_CONTEXT* GetPointer() const + { + return &m_pHandle->m_pCertCtxt; + } + PCCERT_CONTEXT operator->() const { return m_pHandle->m_pCertCtxt; } ScopedCertContext& operator=(const ScopedCertContext& sh) { // Only update the internal handle if it's different if (&m_pHandle->m_pCertCtxt != &sh.m_pHandle->m_pCertCtxt) { m_pHandle = sh.m_pHandle; } return *this; } + ScopedCertContext& operator=(PCCERT_CONTEXT pCertCtxt) + { + // Only update the internal handle if it's different + if (m_pHandle && m_pHandle->m_pCertCtxt != pCertCtxt) + m_pHandle.reset( new HandleContext(pCertCtxt) ); + + return *this; + } + + void FreeContext() + { + m_pHandle.reset( new HandleContext ); + } + + private: + boost::shared_ptr<HandleContext> m_pHandle; + }; + + //------------------------------------------------------------------------ + + // + // Convenience wrapper around the Schannel HCERTSTORE. + // + class ScopedCertStore : boost::noncopyable + { + public: + ScopedCertStore(HCERTSTORE hCertStore) + : m_hCertStore(hCertStore) + { + } + + ~ScopedCertStore() + { + // Forcefully free all memory related to the store, i.e. we assume all CertContext's that have been opened via this + // cert store have been closed at this point. + if (m_hCertStore) + CertCloseStore(m_hCertStore, CERT_CLOSE_STORE_FORCE_FLAG); + } + + operator HCERTSTORE() const + { + return m_hCertStore; + } + + private: + HCERTSTORE m_hCertStore; + }; + + //------------------------------------------------------------------------ + + // + // Convenience wrapper around the Schannel CERT_CHAIN_CONTEXT. + // + class ScopedCertChainContext + { + private: + struct HandleContext + { + HandleContext() + : m_pCertChainCtxt(NULL) + { + } + + HandleContext(PCCERT_CHAIN_CONTEXT pCert) + : m_pCertChainCtxt(pCert) + { + } + + ~HandleContext() + { + if (m_pCertChainCtxt) + CertFreeCertificateChain(m_pCertChainCtxt); + } + + PCCERT_CHAIN_CONTEXT m_pCertChainCtxt; + }; + + public: + ScopedCertChainContext() + : m_pHandle( new HandleContext ) + { + } + + explicit ScopedCertChainContext(PCCERT_CHAIN_CONTEXT pCert) + : m_pHandle( new HandleContext(pCert) ) + { + } + + // Copy constructor + ScopedCertChainContext(const ScopedCertChainContext& rhs) + { + m_pHandle = rhs.m_pHandle; + } + + ~ScopedCertChainContext() + { + m_pHandle.reset(); + } + + PCCERT_CHAIN_CONTEXT* Reset() + { + FreeContext(); + return &m_pHandle->m_pCertChainCtxt; + } + + operator PCCERT_CHAIN_CONTEXT() const + { + return m_pHandle->m_pCertChainCtxt; + } + + PCCERT_CHAIN_CONTEXT operator->() const + { + return m_pHandle->m_pCertChainCtxt; + } + + ScopedCertChainContext& operator=(const ScopedCertChainContext& sh) + { + // Only update the internal handle if it's different + if (&m_pHandle->m_pCertChainCtxt != &sh.m_pHandle->m_pCertChainCtxt) + { + m_pHandle = sh.m_pHandle; + } + + return *this; + } + void FreeContext() { m_pHandle.reset( new HandleContext ); } private: boost::shared_ptr<HandleContext> m_pHandle; - }; + }; } |