diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2018-03-16 12:03:10 -0400 |
---|---|---|
committer | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2018-03-22 13:53:46 -0400 |
commit | 585b952d3adbb73860eec575b1913429c482de38 (patch) | |
tree | 31a8d1bc004ef5efd9de8354dc276ff14d8bae03 | |
parent | 9eba3a0ed37787fc69c4744432c1db70167bc80a (diff) | |
download | mongo-585b952d3adbb73860eec575b1913429c482de38.tar.gz |
SERVER-22411 SChannel Certificate Validation and Fixes
-rw-r--r-- | jstests/ssl/repl_ssl_noca.js | 5 | ||||
-rw-r--r-- | jstests/ssl/ssl_with_system_ca.js | 5 | ||||
-rw-r--r-- | jstests/sslSpecial/SERVER-26369.js | 1 | ||||
-rw-r--r-- | src/mongo/platform/windows_basic.h | 2 | ||||
-rw-r--r-- | src/mongo/util/errno_util.cpp | 2 | ||||
-rw-r--r-- | src/mongo/util/net/ssl/detail/impl/engine_schannel.ipp | 7 | ||||
-rw-r--r-- | src/mongo/util/net/ssl/detail/impl/schannel.ipp | 18 | ||||
-rw-r--r-- | src/mongo/util/net/ssl/detail/schannel.hpp | 6 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_windows.cpp | 421 |
9 files changed, 445 insertions, 22 deletions
diff --git a/jstests/ssl/repl_ssl_noca.js b/jstests/ssl/repl_ssl_noca.js index dedae46d62a..256f56f5ffe 100644 --- a/jstests/ssl/repl_ssl_noca.js +++ b/jstests/ssl/repl_ssl_noca.js @@ -1,8 +1,13 @@ (function() { 'use strict'; if (_isWindows()) { + // OpenSSL backed imports Root CA and intermediate CA runProgram( "certutil.exe", "-addstore", "-user", "-f", "CA", "jstests\\libs\\trusted-ca.pem"); + + // SChannel backed follows Windows rules and only trusts the Root store in Local Machine and + // Current User. + runProgram("certutil.exe", "-addstore", "-f", "Root", "jstests\\libs\\trusted-ca.pem"); } var replTest = new ReplSetTest({ diff --git a/jstests/ssl/ssl_with_system_ca.js b/jstests/ssl/ssl_with_system_ca.js index 494177f6c3d..812ebef43c6 100644 --- a/jstests/ssl/ssl_with_system_ca.js +++ b/jstests/ssl/ssl_with_system_ca.js @@ -11,8 +11,13 @@ const HOST_TYPE = getBuildInfo().buildEnvironment.target_os; if (HOST_TYPE == "windows") { + // OpenSSL backed imports Root CA and intermediate CA runProgram( "certutil.exe", "-addstore", "-user", "-f", "CA", "jstests\\libs\\trusted-ca.pem"); + + // SChannel backed follows Windows rules and only trusts the Root store in Local Machine and + // Current User. + runProgram("certutil.exe", "-addstore", "-f", "Root", "jstests\\libs\\trusted-ca.pem"); } var testWithCerts = function(serverPem) { diff --git a/jstests/sslSpecial/SERVER-26369.js b/jstests/sslSpecial/SERVER-26369.js index e2852189822..737159a9183 100644 --- a/jstests/sslSpecial/SERVER-26369.js +++ b/jstests/sslSpecial/SERVER-26369.js @@ -1,3 +1,4 @@ +// Validate the shardsrvr does not crash when enabling SSL with encrypted PEM for a cluster // Checking UUID consistency involves talking to a shard node, which in this test is shutdown TestData.skipCheckingUUIDsConsistentAcrossCluster = true; diff --git a/src/mongo/platform/windows_basic.h b/src/mongo/platform/windows_basic.h index defe7b68b09..25693a541f0 100644 --- a/src/mongo/platform/windows_basic.h +++ b/src/mongo/platform/windows_basic.h @@ -95,6 +95,8 @@ #include <sspi.h> +#define CERT_CHAIN_PARA_HAS_EXTRA_FIELDS + #include <schannel.h> #undef WIN32_NO_STATUS diff --git a/src/mongo/util/errno_util.cpp b/src/mongo/util/errno_util.cpp index 9fa7902bfc1..fca401aa129 100644 --- a/src/mongo/util/errno_util.cpp +++ b/src/mongo/util/errno_util.cpp @@ -48,7 +48,7 @@ const int kBuflen = 256; // strerror strings in non-English locales can be larg std::string errnoWithDescription(int errNumber) { #if defined(_WIN32) - if (errNumber < 0) + if (errNumber == -1) errNumber = GetLastError(); #else if (errNumber < 0) diff --git a/src/mongo/util/net/ssl/detail/impl/engine_schannel.ipp b/src/mongo/util/net/ssl/detail/impl/engine_schannel.ipp index a2d5d8c21f5..2fb526296fc 100644 --- a/src/mongo/util/net/ssl/detail/impl/engine_schannel.ipp +++ b/src/mongo/util/net/ssl/detail/impl/engine_schannel.ipp @@ -44,15 +44,16 @@ namespace detail { engine::engine(SCHANNEL_CRED* context) : _pCred(context), - _hcxt{0, 0}, - _hcred{0, 0}, _inBuffer(kDefaultBufferSize), _outBuffer(kDefaultBufferSize), _extraBuffer(kDefaultBufferSize), _handshakeManager( &_hcxt, &_hcred, _serverName, &_inBuffer, &_outBuffer, &_extraBuffer, _pCred), _readManager(&_hcxt, &_hcred, &_inBuffer, &_extraBuffer), - _writeManager(&_hcxt, &_outBuffer) {} + _writeManager(&_hcxt, &_outBuffer) { + SecInvalidateHandle(&_hcxt); + SecInvalidateHandle(&_hcred); +} engine::~engine() { DeleteSecurityContext(&_hcxt); diff --git a/src/mongo/util/net/ssl/detail/impl/schannel.ipp b/src/mongo/util/net/ssl/detail/impl/schannel.ipp index 14da4977b6d..d0741260dfc 100644 --- a/src/mongo/util/net/ssl/detail/impl/schannel.ipp +++ b/src/mongo/util/net/ssl/detail/impl/schannel.ipp @@ -33,6 +33,7 @@ #include <memory> #include "asio/detail/assert.hpp" +#include "mongo/util/assert_util.h" namespace asio { namespace ssl { @@ -66,7 +67,7 @@ ssl_want SSLHandshakeManager::nextHandshake(asio::error_code& ec, HandshakeState return ssl_want::want_nothing; } - want = doServerHandshake(true, ec, pHandshakeState); + want = doServerHandshake(ec, pHandshakeState); if (ec) { return want; } @@ -92,7 +93,7 @@ ssl_want SSLHandshakeManager::nextHandshake(asio::error_code& ec, HandshakeState ssl_want want; if (_mode == HandshakeMode::Server) { - want = doServerHandshake(false, ec, pHandshakeState); + want = doServerHandshake(ec, pHandshakeState); } else { want = doClientHandshake(ec); } @@ -254,8 +255,7 @@ ssl_want SSLHandshakeManager::startShutdown(asio::error_code& ec) { return ssl_want::want_nothing; } -ssl_want SSLHandshakeManager::doServerHandshake(bool newConversation, - asio::error_code& ec, +ssl_want SSLHandshakeManager::doServerHandshake(asio::error_code& ec, HandshakeState* pHandshakeState) { TimeStamp lifetime; @@ -294,7 +294,7 @@ ssl_want SSLHandshakeManager::doServerHandshake(bool newConversation, ULONG retAttribs = 0; SECURITY_STATUS ss = AcceptSecurityContext(_phcred, - newConversation ? NULL : _phctxt, + SecIsValidHandle(_phctxt) ? _phctxt : NULL, &inputBufferDesc, attribs, 0, @@ -320,7 +320,10 @@ ssl_want SSLHandshakeManager::doServerHandshake(bool newConversation, return ssl_want::want_nothing; } - invariant(attribs == retAttribs); + + // ASC_RET_EXTENDED_ERROR is not support on Windows 7/Windows 2008 R2. + // ASC_RET_MUTUAL_AUTH is not set since we do our own certificate validation later. + invariant(attribs == (retAttribs | ASC_RET_EXTENDED_ERROR | ASC_RET_MUTUAL_AUTH)); if (inputBuffers[1].BufferType == SECBUFFER_EXTRA) { _pExtraEncryptedBuffer->reset(); @@ -456,7 +459,8 @@ ssl_want SSLHandshakeManager::doClientHandshake(asio::error_code& ec) { return ssl_want::want_nothing; } - invariant(sspiFlags == retAttribs); + // ASC_RET_EXTENDED_ERROR is not support on Windows 7/Windows 2008 R2 + invariant(sspiFlags == (retAttribs | ASC_RET_EXTENDED_ERROR)); if (_pInBuffer->size()) { // Locate (optional) extra buffer diff --git a/src/mongo/util/net/ssl/detail/schannel.hpp b/src/mongo/util/net/ssl/detail/schannel.hpp index 4cf00271f0e..2f65e0c2355 100644 --- a/src/mongo/util/net/ssl/detail/schannel.hpp +++ b/src/mongo/util/net/ssl/detail/schannel.hpp @@ -319,7 +319,7 @@ private: DWORD getServerFlags() { return ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY | - ASC_REQ_EXTENDED_ERROR | ASC_REQ_STREAM; + ASC_REQ_EXTENDED_ERROR | ASC_REQ_STREAM | ASC_REQ_MUTUAL_AUTH; } DWORD getClientFlags() { @@ -347,9 +347,7 @@ private: ssl_want startShutdown(asio::error_code& ec); - ssl_want doServerHandshake(bool newConversation, - asio::error_code& ec, - HandshakeState* pHandshakeState); + ssl_want doServerHandshake(asio::error_code& ec, HandshakeState* pHandshakeState); ssl_want doClientHandshake(asio::error_code& ec); diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp index ee4ec4313ad..beb3c7ccaab 100644 --- a/src/mongo/util/net/ssl_manager_windows.cpp +++ b/src/mongo/util/net/ssl_manager_windows.cpp @@ -49,6 +49,7 @@ #include "mongo/util/concurrency/mutex.h" #include "mongo/util/debug_util.h" #include "mongo/util/exit.h" +#include "mongo/util/hex.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/net/private/ssl_expiration.h" @@ -93,6 +94,21 @@ struct CryptCRLFree { using UniqueCRL = std::unique_ptr<const CRL_CONTEXT, CryptCRLFree>; + +/** +* Free a Certificate Chain Context +*/ +struct CryptCertChainFree { + void operator()(const CERT_CHAIN_CONTEXT* p) noexcept { + if (p) { + ::CertFreeCertificateChain(p); + } + } +}; + +using UniqueCertChain = std::unique_ptr<const CERT_CHAIN_CONTEXT, CryptCertChainFree>; + + /** * A simple generic class to manage Windows handle like things. Behaves similiar to std::unique_ptr. * @@ -182,13 +198,74 @@ struct CertStoreFree { using UniqueCertStore = AutoHandle<HCERTSTORE, CertStoreFree>; /** +* Free a HCERTCHAINENGINE Handle +*/ +struct CertChainEngineFree { + void operator()(HCERTCHAINENGINE const p) noexcept { + if (p) { + ::CertFreeCertificateChainEngine(p); + } + } +}; + +using UniqueCertChainEngine = AutoHandle<HCERTCHAINENGINE, CertChainEngineFree>; + +/** * The lifetime of a private key of a certificate loaded from a PEM is bound to the CryptContext's * lifetime * so we treat the certificate and cryptcontext as a pair. */ using UniqueCertificateWithPrivateKey = std::tuple<UniqueCertificate, UniqueCryptProvider>; -} // namespace +// MongoDB wants RFC 2253 (LDAP) formatted DN names for auth purposes +std::string getCertificateSubjectName(PCCERT_CONTEXT cert) { + DWORD needed = + CertNameToStrW(cert->dwCertEncodingType, + &(cert->pCertInfo->Subject), + CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG | CERT_NAME_STR_REVERSE_FLAG, + NULL, + 0); + uassert( + 50753, str::stream() << "CertNameToStr size query failed with: " << needed, needed != 0); + + auto nameBuf = std::make_unique<wchar_t[]>(needed); + DWORD cbConverted = + CertNameToStrW(cert->dwCertEncodingType, + &(cert->pCertInfo->Subject), + CERT_X500_NAME_STR | CERT_NAME_STR_CRLF_FLAG | CERT_NAME_STR_REVERSE_FLAG, + nameBuf.get(), + needed); + uassert(50754, + str::stream() << "CertNameToStr retrieval failed with unexpected return: " + << cbConverted, + needed == cbConverted); + + // Windows converts the names as RFC 1799 (x.509) instead of RFC 2253 (LDAP) + std::wstring str(nameBuf.get()); + + // Windows uses "S" instead of "ST" for stateOrProvinceName (2.5.4.8) OID so we massage the + // string here. + boost::replace_all(str, L"\r\nS=", L",ST="); + boost::replace_all(str, L"\r\n", L","); + + return toUtf8String(str.c_str()); +} + +StatusWith<stdx::unordered_set<RoleName>> parsePeerRoles(PCCERT_CONTEXT cert) { + PCERT_EXTENSION extension = CertFindExtension(mongodbRolesOID.identifier.c_str(), + cert->pCertInfo->cExtension, + cert->pCertInfo->rgExtension); + + stdx::unordered_set<RoleName> roles; + + if (!extension) { + return roles; + } + + return parsePeerRoles( + ConstDataRange(reinterpret_cast<char*>(extension->Value.pbData), + reinterpret_cast<char*>(extension->Value.pbData) + extension->Value.cbData)); +} /** * Manage state for a SSL Connection. Used by the Socket class. @@ -250,6 +327,12 @@ private: void _handshake(SSLConnectionWindows* conn, bool client); + Status _validateCertificate(PCCERT_CONTEXT cert, + std::string* subjectName, + Date_t* serverCertificateExpirationDate); + + Status _initChainEngines(bool hasCAFile); + private: bool _weakValidation; bool _allowInvalidCertificates; @@ -265,10 +348,14 @@ private: std::array<PCCERT_CONTEXT, 1> _serverCertificates; UniqueCertStore _certStore; -}; -// Global variable indicating if this is a server or a client instance -bool isSSLServer = false; + std::array<HCERTSTORE, 1> _additionalCertStores; + CERT_CHAIN_ENGINE_CONFIG _chainEngineConfigMachine; + UniqueCertChainEngine _chainEngineMachine; + + CERT_CHAIN_ENGINE_CONFIG _chainEngineConfigUser; + UniqueCertChainEngine _chainEngineUser; +}; MONGO_INITIALIZER(SSLManager)(InitializerContext*) { stdx::lock_guard<SimpleMutex> lck(sslManagerMtx); @@ -285,6 +372,8 @@ SSLConnectionWindows::SSLConnectionWindows(SCHANNEL_CRED* cred, int len) : _cred(cred), socket(sock), _engine(_cred) { + // TODO: SNI: _engine.set_server_name(undotted.c_str()); + _tempBuffer.resize(17 * 1024); if (len > 0) { @@ -294,6 +383,11 @@ SSLConnectionWindows::SSLConnectionWindows(SCHANNEL_CRED* cred, SSLConnectionWindows::~SSLConnectionWindows() {} +} // namespace + +// Global variable indicating if this is a server or a client instance +bool isSSLServer = false; + std::unique_ptr<SSLManagerInterface> SSLManagerInterface::create(const SSLParams& params, bool isServer) { return stdx::make_unique<SSLManagerWindows>(params, isServer); @@ -306,6 +400,8 @@ SSLManagerInterface* getSSLManager() { return NULL; } +namespace { + SSLManagerWindows::SSLManagerWindows(const SSLParams& params, bool isServer) : _weakValidation(params.sslWeakCertificateValidation), _allowInvalidCertificates(params.sslAllowInvalidCertificates), @@ -324,14 +420,75 @@ SSLManagerWindows::SSLManagerWindows(const SSLParams& params, bool isServer) uassertStatusOK(initSSLContext(&_clientCred, params, ConnectionDirection::kOutgoing)); - // TODO: validate client certificate + // Certificates may not have been loaded. This typically occurs in unit tests. + if (_clientCertificates[0] != nullptr) { + uassertStatusOK(_validateCertificate( + _clientCertificates[0], &_sslConfiguration.clientSubjectName, NULL)); + } // SSL server specific initialization if (isServer) { uassertStatusOK(initSSLContext(&_serverCred, params, ConnectionDirection::kIncoming)); - // TODO: validate server certificate + if (_serverCertificates[0] != nullptr) { + uassertStatusOK( + _validateCertificate(_serverCertificates[0], + &_sslConfiguration.serverSubjectName, + &_sslConfiguration.serverCertificateExpirationDate)); + } + + // Monitor the server certificate's expiration + static CertificateExpirationMonitor task = + CertificateExpirationMonitor(_sslConfiguration.serverCertificateExpirationDate); } + + uassertStatusOK(_initChainEngines(!params.sslCAFile.empty())); +} + +StatusWith<UniqueCertChainEngine> initChainEngine(CERT_CHAIN_ENGINE_CONFIG* chainEngineConfig, + HCERTSTORE certStore, + DWORD flags) { + memset(chainEngineConfig, 0, sizeof(*chainEngineConfig)); + chainEngineConfig->cbSize = sizeof(*chainEngineConfig); + + // If the user specified a CA file, then we need to restrict our trusted roots to this store. + // This means that the CA file overrules the Windows cert store. + if (certStore) { + chainEngineConfig->hExclusiveRoot = certStore; + } + chainEngineConfig->dwFlags = flags; + + HCERTCHAINENGINE chainEngine; + BOOL ret = CertCreateCertificateChainEngine(chainEngineConfig, &chainEngine); + if (!ret) { + DWORD gle = GetLastError(); + return Status(ErrorCodes::InvalidSSLConfiguration, + str::stream() << "CertCreateCertificateChainEngine failed: " + << errnoWithDescription(gle)); + } + + return chainEngine; +} + +Status SSLManagerWindows::_initChainEngines(bool hasCAFile) { + auto swMachine = + initChainEngine(&_chainEngineConfigMachine, _certStore, CERT_CHAIN_USE_LOCAL_MACHINE_STORE); + + if (!swMachine.isOK()) { + return swMachine.getStatus(); + } + + _chainEngineMachine = std::move(swMachine.getValue()); + + auto swUser = initChainEngine(&_chainEngineConfigUser, _certStore, 0); + + if (!swUser.isOK()) { + return swUser.getStatus(); + } + + _chainEngineUser = std::move(swUser.getValue()); + + return Status::OK(); } int SSLManagerWindows::SSL_read(SSLConnectionInterface* connInterface, void* buf, int num) { @@ -1059,6 +1216,43 @@ void SSLManagerWindows::_handshake(SSLConnectionWindows* conn, bool client) { } } +unsigned long long FiletimeToULL(FILETIME ft) { + return *reinterpret_cast<unsigned long long*>(&ft); +} + + +unsigned long long FiletimeToEpocMillis(FILETIME ft) { + constexpr auto kOneHundredNanosecondsSinceEpoch = 116444736000000000LL; + + uint64_t ns100 = ((static_cast<int64_t>(ft.dwHighDateTime) << 32) + ft.dwLowDateTime) - + kOneHundredNanosecondsSinceEpoch; + return ns100 / 1000; +} + +Status SSLManagerWindows::_validateCertificate(PCCERT_CONTEXT cert, + std::string* subjectName, + Date_t* serverCertificateExpirationDate) { + + *subjectName = getCertificateSubjectName(cert); + + if (serverCertificateExpirationDate != nullptr) { + FILETIME currentTime; + GetSystemTimeAsFileTime(¤tTime); + unsigned long long currentTimeLong = FiletimeToULL(currentTime); + + if ((FiletimeToULL(cert->pCertInfo->NotBefore) > currentTimeLong) || + (currentTimeLong > FiletimeToULL(cert->pCertInfo->NotAfter))) { + severe() << "The provided SSL certificate is expired or not yet valid."; + fassertFailedNoTrace(50755); + } + + *serverCertificateExpirationDate = + Date_t::fromMillisSinceEpoch(FiletimeToEpocMillis(cert->pCertInfo->NotAfter)); + } + + return Status::OK(); +} + SSLPeerInfo SSLManagerWindows::parseAndValidatePeerCertificateDeprecated( const SSLConnectionInterface* conn, const std::string& remoteHost) { auto swPeerSubjectName = parseAndValidatePeerCertificate( @@ -1073,10 +1267,223 @@ SSLPeerInfo SSLManagerWindows::parseAndValidatePeerCertificateDeprecated( return swPeerSubjectName.getValue().get_value_or(SSLPeerInfo()); } +// Get a list of subject alternative names to assist the user in diagnosing certificate verification +// errors. +StatusWith<std::vector<std::string>> getSubjectAlternativeNames(PCCERT_CONTEXT cert) { + + std::vector<std::string> names; + PCERT_EXTENSION extension = CertFindExtension( + szOID_SUBJECT_ALT_NAME2, cert->pCertInfo->cExtension, cert->pCertInfo->rgExtension); + + if (extension == nullptr) { + return names; + } + + auto swBlob = + decodeObject(szOID_SUBJECT_ALT_NAME2, extension->Value.pbData, extension->Value.cbData); + + if (!swBlob.isOK()) { + return swBlob.getStatus(); + } + + CERT_ALT_NAME_INFO* altNames = reinterpret_cast<CERT_ALT_NAME_INFO*>(swBlob.getValue().data()); + for (size_t i = 0; i < altNames->cAltEntry; i++) { + if (altNames->rgAltEntry[i].dwAltNameChoice == CERT_ALT_NAME_DNS_NAME) { + names.push_back(toUtf8String(altNames->rgAltEntry[i].pwszDNSName)); + } + } + + return names; +} + +Status validatePeerCertificate(const std::string& remoteHost, + PCCERT_CONTEXT cert, + HCERTCHAINENGINE certChainEngine, + bool allowInvalidCertificates, + bool allowInvalidHostnames) { + CERT_CHAIN_PARA certChainPara; + memset(&certChainPara, 0, sizeof(certChainPara)); + certChainPara.cbSize = sizeof(CERT_CHAIN_PARA); + + // szOID_PKIX_KP_SERVER_AUTH ("1.3.6.1.5.5.7.3.1") - means the certificate can be used for + // server authentication + LPSTR usage[] = { + const_cast<LPSTR>(szOID_PKIX_KP_SERVER_AUTH), + }; + + // If remoteHost is empty, then this is running on the server side, and we want to verify the + // client cert + if (remoteHost.empty()) { + certChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; + certChainPara.RequestedUsage.Usage.cUsageIdentifier = _countof(usage); + certChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = usage; + } + + PCCERT_CHAIN_CONTEXT chainContext; + BOOL ret = CertGetCertificateChain(certChainEngine, + cert, + NULL, + NULL, + &certChainPara, + CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT, + NULL, + &chainContext); + if (!ret) { + DWORD gle = GetLastError(); + return Status(ErrorCodes::InvalidSSLConfiguration, + str::stream() << "CertGetCertificateChain failed: " + << errnoWithDescription(gle)); + } + + UniqueCertChain certChainHolder(chainContext); + + SSL_EXTRA_CERT_CHAIN_POLICY_PARA sslCertChainPolicy; + memset(&sslCertChainPolicy, 0, sizeof(sslCertChainPolicy)); + sslCertChainPolicy.cbSize = sizeof(sslCertChainPolicy); + + std::wstring serverName; + + // If remoteHost is empty, then this is running on the server side, and we want to verify the + // client cert + if (remoteHost.empty()) { + sslCertChainPolicy.dwAuthType = AUTHTYPE_CLIENT; + } else { + serverName = toNativeString(remoteHost.c_str()); + sslCertChainPolicy.pwszServerName = const_cast<wchar_t*>(serverName.c_str()); + sslCertChainPolicy.dwAuthType = AUTHTYPE_SERVER; + } + + CERT_CHAIN_POLICY_PARA chain_policy_para; + memset(&chain_policy_para, 0, sizeof(chain_policy_para)); + chain_policy_para.cbSize = sizeof(chain_policy_para); + chain_policy_para.pvExtraPolicyPara = &sslCertChainPolicy; + + CERT_CHAIN_POLICY_STATUS certChainPolicyStatus; + memset(&certChainPolicyStatus, 0, sizeof(certChainPolicyStatus)); + certChainPolicyStatus.cbSize = sizeof(certChainPolicyStatus); + + ret = CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, certChainHolder.get(), &chain_policy_para, &certChainPolicyStatus); + + // This means something really went wrong, this should not happen. + if (!ret) { + DWORD gle = GetLastError(); + return Status(ErrorCodes::InvalidSSLConfiguration, + str::stream() << "CertVerifyCertificateChainPolicy failed: " + << errnoWithDescription(gle)); + } + + // This means the certificate chain is not valid. + // Ignore CRYPT_E_NO_REVOCATION_CHECK since most CAs lack revocation information especially test + // certificates + if (certChainPolicyStatus.dwError != S_OK && + certChainPolicyStatus.dwError != CRYPT_E_NO_REVOCATION_CHECK) { + if (certChainPolicyStatus.dwError == CERT_E_CN_NO_MATCH || allowInvalidCertificates) { + + // Give the user a hint why the certificate validation failed. + StringBuilder certificateNames; + auto swAltNames = getSubjectAlternativeNames(cert); + if (swAltNames.isOK() && !swAltNames.getValue().empty()) { + for (auto& name : swAltNames.getValue()) { + certificateNames << name << " "; + } + }; + + certificateNames << ", Subject Name: " << getCertificateSubjectName(cert); + + str::stream msg; + msg << "The server certificate does not match the host name. Hostname: " << remoteHost + << " does not match " << certificateNames.str(); + + if (allowInvalidCertificates) { + warning() << "SSL peer certificate validation failed (" + << integerToHex(certChainPolicyStatus.dwError) + << "): " << errnoWithDescription(certChainPolicyStatus.dwError); + warning() << msg.ss.str(); + return Status::OK(); + } else if (allowInvalidHostnames) { + warning() << msg.ss.str(); + return Status::OK(); + } else { + return Status(ErrorCodes::SSLHandshakeFailed, msg); + } + } else { + str::stream msg; + msg << "SSL peer certificate validation failed: (" + << integerToHex(certChainPolicyStatus.dwError) << ")" + << errnoWithDescription(certChainPolicyStatus.dwError); + error() << msg.ss.str(); + return Status(ErrorCodes::SSLHandshakeFailed, msg); + } + } + + return Status::OK(); +} + StatusWith<boost::optional<SSLPeerInfo>> SSLManagerWindows::parseAndValidatePeerCertificate( PCtxtHandle ssl, const std::string& remoteHost) { + PCCERT_CONTEXT cert; + if (!_sslConfiguration.hasCA && isSSLServer) + return {boost::none}; + + SECURITY_STATUS ss = QueryContextAttributes(ssl, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &cert); - return {boost::none}; + if (ss == SEC_E_NO_CREDENTIALS) { // no certificate presented by peer + if (_weakValidation) { + warning() << "no SSL certificate provided by peer"; + } else { + auto msg = "no SSL certificate provided by peer; connection rejected"; + error() << msg; + return Status(ErrorCodes::SSLHandshakeFailed, msg); + } + + return {boost::none}; + } + + // Check for unexpected errors + if (ss != SEC_E_OK) { + return Status(ErrorCodes::SSLHandshakeFailed, + str::stream() << "QueryContextAttributes failed with" << ss); + } + + UniqueCertificate certHolder(cert); + + // Validate against the local machine store first since it is easier to manage programmatically. + Status validateCertMachine = validatePeerCertificate(remoteHost, + certHolder.get(), + _chainEngineMachine, + _allowInvalidCertificates, + _allowInvalidHostnames); + if (!validateCertMachine.isOK()) { + // Validate against the current user store since this is easier for unprivileged users to + // manage. + Status validateCertUser = validatePeerCertificate(remoteHost, + certHolder.get(), + _chainEngineUser, + _allowInvalidCertificates, + _allowInvalidHostnames); + if (!validateCertUser.isOK()) { + // Return the local machine status + return validateCertMachine; + } + } + + std::string peerSubjectName = getCertificateSubjectName(cert); + LOG(2) << "Accepted TLS connection from peer: " << peerSubjectName; + + // On the server side, parse the certificate for roles + if (remoteHost.empty()) { + StatusWith<stdx::unordered_set<RoleName>> swPeerCertificateRoles = parsePeerRoles(cert); + if (!swPeerCertificateRoles.isOK()) { + return swPeerCertificateRoles.getStatus(); + } + + return boost::make_optional( + SSLPeerInfo(peerSubjectName, std::move(swPeerCertificateRoles.getValue()))); + } else { + return boost::make_optional(SSLPeerInfo(peerSubjectName, stdx::unordered_set<RoleName>())); + } } +} // namespace } // namespace mongo |