diff options
-rw-r--r-- | jstests/ocsp/ocsp_basic.js | 8 | ||||
-rw-r--r-- | jstests/ocsp/ocsp_stapling.js | 8 | ||||
-rw-r--r-- | src/mongo/base/error_codes.yml | 1 | ||||
-rw-r--r-- | src/mongo/db/db.cpp | 3 | ||||
-rw-r--r-- | src/mongo/s/server.cpp | 3 | ||||
-rw-r--r-- | src/mongo/shell/dbshell.cpp | 5 | ||||
-rw-r--r-- | src/mongo/transport/session_asio.h | 25 | ||||
-rw-r--r-- | src/mongo/util/net/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/util/net/http_client_curl.cpp | 10 | ||||
-rw-r--r-- | src/mongo/util/net/ocsp/ocsp_manager.cpp | 84 | ||||
-rw-r--r-- | src/mongo/util/net/ocsp/ocsp_manager.h | 26 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager.h | 2 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_apple.cpp | 26 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_openssl.cpp | 251 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_windows.cpp | 36 |
15 files changed, 321 insertions, 169 deletions
diff --git a/jstests/ocsp/ocsp_basic.js b/jstests/ocsp/ocsp_basic.js index 2253429c105..f3b946e235b 100644 --- a/jstests/ocsp/ocsp_basic.js +++ b/jstests/ocsp/ocsp_basic.js @@ -39,6 +39,12 @@ assert.throws(() => { new Mongo(conn.host); }); -mock_ocsp.stop(); MongoRunner.stopMongod(conn); + +// The mongoRunner spawns a shell to validate the collections which races +// with the shutdown logic of the mock_ocsp responder on some platforms. +// We need this sleep to make sure that the threads don't interfere with +// each other. +sleep(1000); +mock_ocsp.stop(); }());
\ No newline at end of file diff --git a/jstests/ocsp/ocsp_stapling.js b/jstests/ocsp/ocsp_stapling.js index 8486894fa90..207c873d11a 100644 --- a/jstests/ocsp/ocsp_stapling.js +++ b/jstests/ocsp/ocsp_stapling.js @@ -70,6 +70,12 @@ assert.throws(() => { new Mongo(conn.host); }); -mock_ocsp.stop(); MongoRunner.stopMongod(conn); + +// The mongoRunner spawns a shell to validate the collections which races +// with the shutdown logic of the mock_ocsp responder on some platforms. +// We need this sleep to make sure that the threads don't interfere with +// each other. +sleep(1000); +mock_ocsp.stop(); }());
\ No newline at end of file diff --git a/src/mongo/base/error_codes.yml b/src/mongo/base/error_codes.yml index 7644749e989..4938c9a3f99 100644 --- a/src/mongo/base/error_codes.yml +++ b/src/mongo/base/error_codes.yml @@ -336,6 +336,7 @@ error_codes: - {code: 299,name: OCSPCertificateStatusRevoked} - {code: 300,name: RangeDeletionAbandonedBecauseCollectionWithUUIDDoesNotExist} - {code: 301,name: DataCorruptionDetected} + - {code: 302,name: OCSPCertificateStatusUnknown} # Error codes 4000-8999 are reserved. diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp index 97f2580e389..14bed1ae035 100644 --- a/src/mongo/db/db.cpp +++ b/src/mongo/db/db.cpp @@ -170,6 +170,7 @@ #include "mongo/util/fast_clock_source_factory.h" #include "mongo/util/latch_analyzer.h" #include "mongo/util/log.h" +#include "mongo/util/net/ocsp/ocsp_manager.h" #include "mongo/util/net/socket_utils.h" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/ntservice.h" @@ -314,6 +315,8 @@ ExitCode _initAndListen(int listenPort) { VersionInfoInterface::instance().logTargetMinOS(); #endif + OCSPManager::get()->startThreadPool(); + logProcessDetails(); serviceContext->setServiceEntryPoint(std::make_unique<ServiceEntryPointMongod>(serviceContext)); diff --git a/src/mongo/s/server.cpp b/src/mongo/s/server.cpp index faa5ccf183b..84f9a779fb0 100644 --- a/src/mongo/s/server.cpp +++ b/src/mongo/s/server.cpp @@ -104,6 +104,7 @@ #include "mongo/util/fast_clock_source_factory.h" #include "mongo/util/latch_analyzer.h" #include "mongo/util/log.h" +#include "mongo/util/net/ocsp/ocsp_manager.h" #include "mongo/util/net/socket_exception.h" #include "mongo/util/net/socket_utils.h" #include "mongo/util/net/ssl_manager.h" @@ -511,6 +512,8 @@ ExitCode runMongosServer(ServiceContext* serviceContext) { initWireSpec(); + OCSPManager::get()->startThreadPool(); + serviceContext->setServiceEntryPoint(std::make_unique<ServiceEntryPointMongos>(serviceContext)); auto tl = diff --git a/src/mongo/shell/dbshell.cpp b/src/mongo/shell/dbshell.cpp index a106a040a26..88699025c56 100644 --- a/src/mongo/shell/dbshell.cpp +++ b/src/mongo/shell/dbshell.cpp @@ -78,6 +78,7 @@ #include "mongo/util/exit.h" #include "mongo/util/file.h" #include "mongo/util/log.h" +#include "mongo/util/net/ocsp/ocsp_manager.h" #include "mongo/util/net/ssl_options.h" #include "mongo/util/password.h" #include "mongo/util/quick_exit.h" @@ -752,6 +753,9 @@ int _main(int argc, char* argv[], char** envp) { setGlobalServiceContext(ServiceContext::make()); // TODO This should use a TransportLayerManager or TransportLayerFactory auto serviceContext = getGlobalServiceContext(); + + OCSPManager::get()->startThreadPool(); + transport::TransportLayerASIO::Options opts; opts.enableIPv6 = shellGlobalParams.enableIPv6; opts.mode = transport::TransportLayerASIO::Options::kEgress; @@ -804,7 +808,6 @@ int _main(int argc, char* argv[], char** envp) { boost::log::core::get()->add_sink(std::move(consoleSink)); } - // Get the URL passed to the shell std::string& cmdlineURI = shellGlobalParams.url; diff --git a/src/mongo/transport/session_asio.h b/src/mongo/transport/session_asio.h index f8c43554c07..687b1022662 100644 --- a/src/mongo/transport/session_asio.h +++ b/src/mongo/transport/session_asio.h @@ -258,9 +258,12 @@ protected: return doHandshake().then([this, target] { _ranHandshake = true; - SSLPeerInfo::forSession(shared_from_this()) = - uassertStatusOK(getSSLManager()->parseAndValidatePeerCertificate( - _sslSocket->native_handle(), _sslSocket->get_sni(), target.host(), target)); + return getSSLManager() + ->parseAndValidatePeerCertificate( + _sslSocket->native_handle(), _sslSocket->get_sni(), target.host(), target) + .then([this](SSLPeerInfo info) { + SSLPeerInfo::forSession(shared_from_this()) = info; + }); }); } @@ -634,13 +637,17 @@ private: } }; return doHandshake().then([this](size_t size) { - auto& sslPeerInfo = SSLPeerInfo::forSession(shared_from_this()); - - if (sslPeerInfo.subjectName.empty()) { - sslPeerInfo = uassertStatusOK(getSSLManager()->parseAndValidatePeerCertificate( - _sslSocket->native_handle(), _sslSocket->get_sni(), "", _remote)); + if (SSLPeerInfo::forSession(shared_from_this()).subjectName.empty()) { + return getSSLManager() + ->parseAndValidatePeerCertificate( + _sslSocket->native_handle(), _sslSocket->get_sni(), "", _remote) + .then([this](SSLPeerInfo info) -> bool { + SSLPeerInfo::forSession(shared_from_this()) = info; + return true; + }); } - return true; + + return Future<bool>::makeReady(true); }); } else if (_tl->_sslMode() == SSLParams::SSLMode_requireSSL) { uasserted(ErrorCodes::SSLHandshakeFailed, diff --git a/src/mongo/util/net/SConscript b/src/mongo/util/net/SConscript index c06b8cb0225..0f545358feb 100644 --- a/src/mongo/util/net/SConscript +++ b/src/mongo/util/net/SConscript @@ -121,6 +121,8 @@ if not get_option('ssl') == 'off': ], LIBDEPS=[ '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/db/service_context', + '$BUILD_DIR/mongo/util/concurrency/thread_pool', ], LIBDEPS_PRIVATE=[ 'http_client', diff --git a/src/mongo/util/net/http_client_curl.cpp b/src/mongo/util/net/http_client_curl.cpp index 20ca65442b3..bddb5fb910f 100644 --- a/src/mongo/util/net/http_client_curl.cpp +++ b/src/mongo/util/net/http_client_curl.cpp @@ -131,17 +131,21 @@ private: } static void _lockShare(CURL*, curl_lock_data, curl_lock_access, void* ctx) { - reinterpret_cast<Mutex*>(ctx)->lock(); + reinterpret_cast<stdx::recursive_mutex*>(ctx)->lock(); } static void _unlockShare(CURL*, curl_lock_data, void* ctx) { - reinterpret_cast<Mutex*>(ctx)->unlock(); + reinterpret_cast<stdx::recursive_mutex*>(ctx)->unlock(); } private: bool _initialized = false; CURLSH* _share = nullptr; - Mutex _shareMutex = MONGO_MAKE_LATCH("CurlLibraryManager::_shareMutex"); + + // A recursive mutex here is needed because CURL needs to lock this multiple times depending + // on the "internal CURL type" of the object that CURL is sending. Using a normal mutex + // causes the CURL system to deadlock. + stdx::recursive_mutex _shareMutex{}; } curlLibraryManager; /** diff --git a/src/mongo/util/net/ocsp/ocsp_manager.cpp b/src/mongo/util/net/ocsp/ocsp_manager.cpp index 1b3bf98115e..11ebf085bde 100644 --- a/src/mongo/util/net/ocsp/ocsp_manager.cpp +++ b/src/mongo/util/net/ocsp/ocsp_manager.cpp @@ -29,27 +29,83 @@ #include "mongo/platform/basic.h" -#include "mongo/util/net/http_client.h" +#include "mongo/db/client.h" +#include "mongo/executor/network_interface_factory.h" #include "mongo/util/net/ocsp/ocsp_manager.h" namespace mongo { -StatusWith<std::vector<uint8_t>> ocspRequestStatus(ConstDataRange data, StringData responderURI) { - auto client = HttpClient::create(); - if (!client) { - return Status(ErrorCodes::InternalErrorNotSupported, "HTTP Client not supported"); +namespace { + +auto makeTaskExecutor() { + ThreadPool::Options tpOptions; + tpOptions.poolName = "OCSPManagerHTTP"; + tpOptions.maxThreads = 10; + tpOptions.onCreateThread = [](const std::string& threadName) { + Client::initThread(threadName.c_str()); + }; + return std::make_unique<ThreadPool>(tpOptions); +} + +} // namespace + +OCSPManager::OCSPManager() { + _pool = makeTaskExecutor(); + + _client = HttpClient::create(); + if (!_client) { + return; + } + + _client->allowInsecureHTTP(true); + _client->setTimeout(kOCSPRequestTimeoutSeconds); + _client->setHeaders({"Content-Type: application/ocsp-request"}); +} + +void OCSPManager::startThreadPool() { + if (_pool) { + _pool->startup(); } - client->allowInsecureHTTP(true); - client->setTimeout(kOCSPRequestTimeoutSeconds); - client->setHeaders({"Content-Type: application/ocsp-request"}); - auto dataBuilder = client->post("http://" + responderURI, data); - if (dataBuilder.size() == 0) { - return Status(ErrorCodes::SSLHandshakeFailed, "OCSP Validation Failed"); +} + +/** + * Constructs the HTTP client and sends the OCSP request to the responder. + * Returns a vector of bytes to be constructed into a OCSP response. + */ +Future<std::vector<uint8_t>> OCSPManager::requestStatus(std::vector<uint8_t> data, + StringData responderURI) { + if (!this->_client) { + return Future<std::vector<uint8_t>>::makeReady( + Status(ErrorCodes::InternalErrorNotSupported, "HTTP Client not supported")); } - auto blobSize = dataBuilder.size(); - auto blobData = dataBuilder.release(); - return std::vector<uint8_t>(blobData.get(), blobData.get() + blobSize); + auto pf = makePromiseFuture<DataBuilder>(); + std::string uri("http://" + responderURI); + + _pool->schedule( + [this, promise = std::move(pf.promise), uri = std::move(uri), data = std::move(data)]( + auto status) mutable { + if (!status.isOK()) { + return; + } + try { + auto result = this->_client->post(uri, data); + promise.emplaceValue(std::move(result)); + } catch (...) { + promise.setError(exceptionToStatus()); + } + }); + + return std::move(pf.future).then( + [](DataBuilder dataBuilder) mutable -> Future<std::vector<uint8_t>> { + if (dataBuilder.size() == 0) { + return Status(ErrorCodes::SSLHandshakeFailed, "Failed to acquire OCSP Response."); + } + + auto blobSize = dataBuilder.size(); + auto blobData = dataBuilder.release(); + return std::vector<uint8_t>(blobData.get(), blobData.get() + blobSize); + }); } } // namespace mongo
\ No newline at end of file diff --git a/src/mongo/util/net/ocsp/ocsp_manager.h b/src/mongo/util/net/ocsp/ocsp_manager.h index 5d2a3d1a9a5..e392b5e0a12 100644 --- a/src/mongo/util/net/ocsp/ocsp_manager.h +++ b/src/mongo/util/net/ocsp/ocsp_manager.h @@ -30,17 +30,33 @@ #include "mongo/base/data_range.h" +#include "mongo/util/concurrency/thread_pool.h" #include "mongo/util/duration.h" +#include "mongo/util/future.h" #include "mongo/util/net/hostandport.h" +#include "mongo/util/net/http_client.h" namespace mongo { constexpr Seconds kOCSPRequestTimeoutSeconds(5); +class OCSPManager { -/** - * Constructs the HTTP client and sends the OCSP request to the responder. - * Returns a vector of bytes to be constructed into a OCSP response. - */ -StatusWith<std::vector<uint8_t>> ocspRequestStatus(ConstDataRange data, StringData responderURI); +public: + OCSPManager(); + + static OCSPManager* get() { + static OCSPManager manager = OCSPManager(); + + return &manager; + }; + + Future<std::vector<uint8_t>> requestStatus(std::vector<uint8_t> data, StringData responderURI); + + void startThreadPool(); + +private: + std::unique_ptr<HttpClient> _client; + std::unique_ptr<ThreadPool> _pool; +}; } // namespace mongo
\ No newline at end of file diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h index 8da19482cbb..499cdd85b7e 100644 --- a/src/mongo/util/net/ssl_manager.h +++ b/src/mongo/util/net/ssl_manager.h @@ -259,7 +259,7 @@ public: * X509 authorization will be returned in `roles`. * Further, the SNI Name will be captured into the `sni` value, when available. */ - virtual StatusWith<SSLPeerInfo> parseAndValidatePeerCertificate( + virtual Future<SSLPeerInfo> parseAndValidatePeerCertificate( SSLConnectionType ssl, boost::optional<std::string> sni, const std::string& remoteHost, diff --git a/src/mongo/util/net/ssl_manager_apple.cpp b/src/mongo/util/net/ssl_manager_apple.cpp index eda69e8b9a7..970db6c9d3b 100644 --- a/src/mongo/util/net/ssl_manager_apple.cpp +++ b/src/mongo/util/net/ssl_manager_apple.cpp @@ -1196,11 +1196,10 @@ public: const std::string& remoteHost, const HostAndPort& hostForLogging) final; - StatusWith<SSLPeerInfo> parseAndValidatePeerCertificate( - ::SSLContextRef conn, - boost::optional<std::string> sniName, - const std::string& remoteHost, - const HostAndPort& hostForLogging) final; + Future<SSLPeerInfo> parseAndValidatePeerCertificate(::SSLContextRef conn, + boost::optional<std::string> sniName, + const std::string& remoteHost, + const HostAndPort& hostForLogging) final; const SSLConfiguration& getSSLConfiguration() const final { return _sslConfiguration; @@ -1394,7 +1393,7 @@ SSLPeerInfo SSLManagerApple::parseAndValidatePeerCertificateDeprecated( auto ssl = checked_cast<const SSLConnectionApple*>(conn)->get(); auto swPeerSubjectName = - parseAndValidatePeerCertificate(ssl, boost::none, remoteHost, hostForLogging); + parseAndValidatePeerCertificate(ssl, boost::none, remoteHost, hostForLogging).getNoThrow(); // We can't use uassertStatusOK here because we need to throw a NetworkException. if (!swPeerSubjectName.isOK()) { throwSocketError(SocketErrorKind::CONNECT_ERROR, swPeerSubjectName.getStatus().reason()); @@ -1420,7 +1419,7 @@ StatusWith<TLSVersion> mapTLSVersion(SSLContextRef ssl) { } -StatusWith<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( +Future<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( ::SSLContextRef ssl, boost::optional<std::string> sniName, const std::string& remoteHost, @@ -1430,7 +1429,7 @@ StatusWith<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( // Record TLS version stats auto tlsVersionStatus = mapTLSVersion(ssl); if (!tlsVersionStatus.isOK()) { - return tlsVersionStatus.getStatus(); + return Future<SSLPeerInfo>::makeReady(tlsVersionStatus.getStatus()); } recordTLSVersion(tlsVersionStatus.getValue(), hostForLogging); @@ -1443,14 +1442,14 @@ StatusWith<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( * so that the validation path runs anyway. */ if (!_sslConfiguration.hasCA && isSSLServer) { - return SSLPeerInfo(sniName); + return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(sniName)); } - const auto badCert = [&](StringData msg, bool warn = false) -> StatusWith<SSLPeerInfo> { + const auto badCert = [&](StringData msg, bool warn = false) -> Future<SSLPeerInfo> { constexpr StringData prefix = "SSL peer certificate validation failed: "_sd; if (warn) { warning() << prefix << msg; - return SSLPeerInfo(sniName); + return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(sniName)); } else { std::string m = str::stream() << prefix << msg << "; connection rejected"; error() << m; @@ -1573,7 +1572,8 @@ StatusWith<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( if (!swPeerCertificateRoles.isOK()) { return swPeerCertificateRoles.getStatus(); } - return SSLPeerInfo(peerSubjectName, sniName, std::move(swPeerCertificateRoles.getValue())); + return Future<SSLPeerInfo>::makeReady( + SSLPeerInfo(peerSubjectName, sniName, std::move(swPeerCertificateRoles.getValue()))); } // If this is an SSL client context (on a MongoDB server or client) @@ -1642,7 +1642,7 @@ StatusWith<SSLPeerInfo> SSLManagerApple::parseAndValidatePeerCertificate( } } - return SSLPeerInfo(peerSubjectName); + return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(peerSubjectName)); } int SSLManagerApple::SSL_read(SSLConnectionInterface* conn, void* buf, int num) { diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp index 70f7a751497..702ae32b4c3 100644 --- a/src/mongo/util/net/ssl_manager_openssl.cpp +++ b/src/mongo/util/net/ssl_manager_openssl.cpp @@ -65,6 +65,7 @@ #include "mongo/util/net/ssl_types.h" #include "mongo/util/scopeguard.h" #include "mongo/util/str.h" +#include "mongo/util/strong_weak_finish_line.h" #include "mongo/util/text.h" #include <arpa/inet.h> @@ -386,11 +387,10 @@ public: const std::string& remoteHost, const HostAndPort& hostForLogging) final; - StatusWith<SSLPeerInfo> parseAndValidatePeerCertificate( - SSL* conn, - boost::optional<std::string> sni, - const std::string& remoteHost, - const HostAndPort& hostForLogging) final; + Future<SSLPeerInfo> parseAndValidatePeerCertificate(SSL* conn, + boost::optional<std::string> sni, + const std::string& remoteHost, + const HostAndPort& hostForLogging) final; const SSLConfiguration& getSSLConfiguration() const final { return _sslConfiguration; @@ -1000,8 +1000,8 @@ StatusWith<OCSPValidationContext> extractOcspUris(SSL_CTX* context, std::move(ocspRequestMap), std::move(uniqueCertIds), std::move(leafResponders)}; } -StatusWith<UniqueOCSPResponse> retrieveOCSPResponse(const std::string& host, - OCSPRequestAndIDs& ocspRequestAndIDs) { +Future<UniqueOCSPResponse> retrieveOCSPResponse(const std::string& host, + OCSPRequestAndIDs& ocspRequestAndIDs) { auto& [ocspReq, certIDs] = ocspRequestAndIDs; // Decompose the OCSP request into a DER encoded OCSP request @@ -1018,20 +1018,21 @@ StatusWith<UniqueOCSPResponse> retrieveOCSPResponse(const std::string& host, } // Query the OCSP responder - auto responseData = ocspRequestStatus(buffer, host); - if (!responseData.isOK()) { - return responseData.getStatus(); - } - std::vector<uint8_t> respDataVector(std::move(responseData.getValue())); - const uint8_t* respDataPtr = respDataVector.data(); + return OCSPManager::get() + ->requestStatus(buffer, host) + .then([](std::vector<uint8_t> responseData) mutable -> StatusWith<UniqueOCSPResponse> { + const uint8_t* respDataPtr = responseData.data(); - // Convert the Response back to a OpenSSL known format - UniqueOCSPResponse response(d2i_OCSP_RESPONSE(nullptr, &respDataPtr, respDataVector.size())); + // Convert the Response back to a OpenSSL known format + UniqueOCSPResponse response( + d2i_OCSP_RESPONSE(nullptr, &respDataPtr, responseData.size())); - if (response == nullptr) { - return getSSLFailure("Could not retrieve OCSP Response."); - } - return std::move(response); + if (response == nullptr) { + return getSSLFailure("Could not retrieve OCSP Response."); + } + + return std::move(response); + }); } /** @@ -1136,9 +1137,17 @@ int ocspServerCallback(SSL* ssl, void* arg) { stdx::lock_guard<mongo::Mutex> guard(sharedResponseMutex); auto response = static_cast<std::shared_ptr<OCSP_RESPONSE>*>(arg); + if (!response) { + return SSL_TLSEXT_ERR_NOACK; + } + unsigned char* ocspResponseBuffer = NULL; int length = i2d_OCSP_RESPONSE(response->get(), &ocspResponseBuffer); + if (length == 0) { + return SSL_TLSEXT_ERR_NOACK; + } + SSL_set_tlsext_status_ocsp_resp(ssl, ocspResponseBuffer, length); } @@ -1178,11 +1187,79 @@ StatusWith<bool> verifyStapledResponse(SSL* conn, X509* peerCert, OCSP_RESPONSE* return false; } -// This function returns early if there is an issue processing the request in the beginning. -// If there is an issue when processing the responses, the function will just continue to the -// next certificate. If there is a revoked certificate in the chain, the function will fail. -// Otherwise, the function will error if it cannot validate the peer certificate. -Status verifyPeerCertWithOCSP(SSL* conn, X509* peerCert) { +Future<std::pair<Status, UniqueOCSPResponse>> dispatchRequests( + SSL_CTX* context, + UniqueVerifiedChainPolyfill intermediateCerts, + OCSPValidationContext& ocspContext) { + auto& [ocspRequestMap, _, leafResponders] = ocspContext; + + struct OCSPCompletionState { + OCSPCompletionState(int numRequests_, + Promise<std::pair<Status, UniqueOCSPResponse>> promise_, + UniqueVerifiedChainPolyfill intermediateCerts_) + : finishLine(numRequests_), + promise(std::move(promise_)), + intermediateCerts(std::move(intermediateCerts_)) {} + + StrongWeakFinishLine finishLine; + Promise<std::pair<Status, UniqueOCSPResponse>> promise; + std::shared_ptr<STACK_OF(X509)> intermediateCerts; + }; + + std::vector<Future<UniqueOCSPResponse>> futureResponses{}; + + for (auto host : leafResponders) { + auto& ocspRequestAndIDs = ocspRequestMap[host]; + Future<UniqueOCSPResponse> futureResponse = retrieveOCSPResponse(host, ocspRequestAndIDs); + futureResponses.push_back(std::move(futureResponse)); + } + + auto pf = makePromiseFuture<std::pair<Status, UniqueOCSPResponse>>(); + auto state = std::make_shared<OCSPCompletionState>( + futureResponses.size(), std::move(pf.promise), std::move(intermediateCerts)); + + for (size_t i = 0; i < futureResponses.size(); i++) { + auto futureResponse = std::move(futureResponses[i]); + std::move(futureResponse) + .getAsync([context, state](StatusWith<UniqueOCSPResponse> swResponse) mutable { + if (!swResponse.isOK()) { + if (state->finishLine.arriveWeakly()) { + state->promise.setError( + Status(ErrorCodes::OCSPCertificateStatusUnknown, + "Could not obtain status information of certificates.")); + } + return; + } + + auto swCertIDSet = validateResponse( + context, swResponse.getValue().get(), state->intermediateCerts.get()); + + if (swCertIDSet.isOK() || + swCertIDSet.getStatus() == ErrorCodes::OCSPCertificateStatusRevoked) { + if (state->finishLine.arriveStrongly()) { + state->promise.emplaceValue(swCertIDSet.getStatus(), + std::move(swResponse.getValue())); + return; + } + } else { + if (state->finishLine.arriveWeakly()) { + state->promise.setError( + Status(ErrorCodes::OCSPCertificateStatusUnknown, + "Could not obtain status information of certificates.")); + return; + } + } + }); + } + + return std::move(pf.future); +} + +// This function returns a future with a not OK status if there is an issue processing the +// request in the beginning. If there is an issue when processing the responses, the function +// will just continue to the next certificate. If there is a revoked certificate in the chain, +// the future will resolve to a not OK status. +Future<void> verifyPeerCertWithOCSP(SSL* conn, X509* peerCert) { UniqueOpenSSLStringStack aiaOCSP(X509_get1_ocsp(peerCert)); if (!aiaOCSP) { return Status::OK(); @@ -1192,53 +1269,24 @@ Status verifyPeerCertWithOCSP(SSL* conn, X509* peerCert) { ERR_clear_error(); auto intermediateCerts = SSLgetVerifiedChain(conn); - auto swOCSPContext = extractOcspUris(SSL_get_SSL_CTX(conn), peerCert, intermediateCerts.get()); + auto context = SSL_get_SSL_CTX(conn); + + auto swOCSPContext = extractOcspUris(context, peerCert, intermediateCerts.get()); if (!swOCSPContext.isOK()) { return swOCSPContext.getStatus(); } - auto& [ocspRequestMap, uniqueCertIds, leafResponders] = swOCSPContext.getValue(); - - for (auto& [host, ocspRequestAndIDs] : ocspRequestMap) { - auto swResponse = retrieveOCSPResponse(host, ocspRequestAndIDs); - if (swResponse.getStatus().code() == ErrorCodes::InternalErrorNotSupported) { - warning() << "Could not perform OCSP validation: " << swResponse.getStatus(); - return Status::OK(); - } else if (!swResponse.isOK()) { - continue; - } - - auto& response = swResponse.getValue(); - - auto swCertIDSet = - validateResponse(SSL_get_SSL_CTX(conn), response.get(), intermediateCerts.get()); - - // The only error that will fail is an OCSPCertificateStatusRevoked. Even if a - // response has an unknown error, we don't want to discredit all the responses - // because of that one issue. The main thing we are looking for is status - // information on the peer certificate as described below. - if (swCertIDSet.getStatus() == ErrorCodes::OCSPCertificateStatusRevoked) { - return swCertIDSet.getStatus(); - } - - if (swCertIDSet.isOK()) { - for (auto& certId : swCertIDSet.getValue()) { - uniqueCertIds.erase(certId); - } - } - } - - auto swCertId = getCertIdForCert(SSL_get_SSL_CTX(conn), peerCert); - if (!swCertId.isOK()) { - return swCertId.getStatus(); - } + auto ocspContext = std::move(swOCSPContext.getValue()); - // If we can get status information on the peer certificate, everything is good to go. - if (uniqueCertIds.find(swCertId.getValue()) != uniqueCertIds.end()) { - return getSSLFailure("OCSP Validation Error: Could not validate the peer certificate."); - } + return dispatchRequests(context, std::move(intermediateCerts), ocspContext) + .onCompletion( + [context](StatusWith<std::pair<Status, UniqueOCSPResponse>> swResponse) -> Status { + if (!swResponse.isOK()) { + return Status::OK(); + } - return Status::OK(); + return swResponse.getValue().first; + }); } // The definition of the callbacks @@ -1300,7 +1348,7 @@ int ocspClientCallback(SSL* ssl, void* arg) { * the chain using a CRL. If no CRL is provided then the shell should reach out to the OCSP * responders itself and verify the status of the peer certificate. */ -Status ocspClientVerification(SSL* ssl) { +Future<void> ocspClientVerification(SSL* ssl) { UniqueX509 peerCert(SSL_get_peer_certificate(ssl)); const unsigned char* response_ptr = NULL; @@ -1340,47 +1388,37 @@ Status stapleOCSPResponse(SSL_CTX* context) { return Status::OK(); } - STACK_OF(X509) * intermediateCerts; + STACK_OF(X509) * intermediateCertsPtr; - if (SSL_CTX_get0_chain_certs(context, &intermediateCerts) == 0) { + if (SSL_CTX_get0_chain_certs(context, &intermediateCertsPtr) == 0) { return getSSLFailure("Could not get chain for SSL Context."); } - auto swOCSPContext = extractOcspUris(context, cert, intermediateCerts); + UniqueVerifiedChainPolyfill intermediateCerts(intermediateCertsPtr); + + auto swOCSPContext = extractOcspUris(context, cert, intermediateCerts.get()); if (!swOCSPContext.isOK()) { + warning() << "Could not staple OCSP response to outgoing certificate."; return swOCSPContext.getStatus(); } - auto& [ocspRequestMap, _, leafResponders] = swOCSPContext.getValue(); + auto ocspContext = std::move(swOCSPContext.getValue()); - for (auto host : leafResponders) { - auto& ocspRequestAndIDs = ocspRequestMap[host]; - auto swResponse = retrieveOCSPResponse(host, ocspRequestAndIDs); - if (!swResponse.isOK()) { - if (swResponse.getStatus() == ErrorCodes::InternalErrorNotSupported) { - warning() << "Could not perform OCSP validation: " << swResponse.getStatus(); - return Status::OK(); + dispatchRequests(context, std::move(intermediateCerts), ocspContext) + .getAsync([context](StatusWith<std::pair<Status, UniqueOCSPResponse>> swResponse) { + if (!swResponse.isOK()) { + warning() << "Could not staple OCSP response to outgoing certificate."; + return; } - continue; - } - - auto status = validateResponse(context, swResponse.getValue().get(), intermediateCerts); - - // If the certificate status is neither OK nor revoked, then we can get the - // status of the certificate from the next responder. If all are indeterminate, - // we can put the onus on the client to retrieve the response. - if (status.isOK() || status == ErrorCodes::OCSPCertificateStatusRevoked) { stdx::lock_guard<mongo::Mutex> guard(sharedResponseMutex); sharedResponseForServer = - std::shared_ptr<OCSP_RESPONSE>(std::move(swResponse.getValue())); - SSL_CTX_set_tlsext_status_cb(context, ocspServerCallback); - SSL_CTX_set_tlsext_status_arg(context, &sharedResponseForServer); - return Status::OK(); - } - } + std::shared_ptr<OCSP_RESPONSE>(std::move(swResponse.getValue().second)); + }); + + SSL_CTX_set_tlsext_status_cb(context, ocspServerCallback); + SSL_CTX_set_tlsext_status_arg(context, &sharedResponseForServer); - warning() << "Could not staple OCSP response to outgoing certificate."; return Status::OK(); } #endif @@ -1472,7 +1510,6 @@ Status SSLManagerOpenSSL::initSSLContext(SSL_CTX* context, } if (sslOCSPEnabled) { - #if OPENSSL_VERSION_NUMBER >= 0x10002000L if (direction == SSLManagerInterface::ConnectionDirection::kIncoming) { auto resp = stapleOCSPResponse(context); @@ -1949,11 +1986,12 @@ Status _validatePeerRoles(const stdx::unordered_set<RoleName>& embeddedRoles, SS } } // namespace -StatusWith<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate( +Future<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate( SSL* conn, boost::optional<std::string> sni, const std::string& remoteHost, const HostAndPort& hostForLogging) { + auto tlsVersionStatus = mapTLSVersion(conn); if (!tlsVersionStatus.isOK()) { return tlsVersionStatus.getStatus(); @@ -1997,11 +2035,9 @@ StatusWith<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate( } } + Future<void> ocspFuture; if (sslOCSPEnabled) { - auto status = ocspClientVerification(conn); - if (!status.isOK()) { - return status; - } + ocspFuture = ocspClientVerification(conn); } // TODO: check optional cipher restriction, using cert. @@ -2010,7 +2046,7 @@ StatusWith<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate( StatusWith<stdx::unordered_set<RoleName>> swPeerCertificateRoles = _parsePeerRoles(peerCert); if (!swPeerCertificateRoles.isOK()) { - return swPeerCertificateRoles.getStatus(); + return Future<SSLPeerInfo>::makeReady(swPeerCertificateRoles.getStatus()); } if (auto status = _validatePeerRoles(swPeerCertificateRoles.getValue(), conn); !status.isOK()) { @@ -2039,7 +2075,13 @@ StatusWith<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate( warning() << "Client connecting with server's own TLS certificate"; } - return SSLPeerInfo(peerSubject, sni, std::move(swPeerCertificateRoles.getValue())); + // void futures are default constructed as ready futures. + return std::move(ocspFuture) + .then([peerSubject, + sni, + peerCertificateRoles = std::move(swPeerCertificateRoles.getValue())] { + return SSLPeerInfo(peerSubject, sni, peerCertificateRoles); + }); } // If this is an SSL client context (on a MongoDB server or client) @@ -2139,11 +2181,11 @@ StatusWith<SSLPeerInfo> SSLManagerOpenSSL::parseAndValidatePeerCertificate( warning() << msg; } else { error() << msg; - return Status(ErrorCodes::SSLHandshakeFailed, msg); + return Future<SSLPeerInfo>::makeReady(Status(ErrorCodes::SSLHandshakeFailed, msg)); } } - return SSLPeerInfo(peerSubject); + return std::move(ocspFuture).then([this, peerSubject]() { return SSLPeerInfo(peerSubject); }); } @@ -2154,7 +2196,8 @@ SSLPeerInfo SSLManagerOpenSSL::parseAndValidatePeerCertificateDeprecated( const SSLConnectionOpenSSL* conn = checked_cast<const SSLConnectionOpenSSL*>(connInterface); auto swPeerSubjectName = - parseAndValidatePeerCertificate(conn->ssl, boost::none, remoteHost, hostForLogging); + parseAndValidatePeerCertificate(conn->ssl, boost::none, remoteHost, hostForLogging) + .getNoThrow(); // We can't use uassertStatusOK here because we need to throw a NetworkException. if (!swPeerSubjectName.isOK()) { throwSocketError(SocketErrorKind::CONNECT_ERROR, swPeerSubjectName.getStatus().reason()); diff --git a/src/mongo/util/net/ssl_manager_windows.cpp b/src/mongo/util/net/ssl_manager_windows.cpp index 48e21eeae20..4c9bcb356c8 100644 --- a/src/mongo/util/net/ssl_manager_windows.cpp +++ b/src/mongo/util/net/ssl_manager_windows.cpp @@ -273,11 +273,10 @@ public: const std::string& remoteHost, const HostAndPort& hostForLogging) final; - StatusWith<SSLPeerInfo> parseAndValidatePeerCertificate( - PCtxtHandle ssl, - boost::optional<std::string> sni, - const std::string& remoteHost, - const HostAndPort& hostForLogging) final; + Future<SSLPeerInfo> parseAndValidatePeerCertificate(PCtxtHandle ssl, + boost::optional<std::string> sni, + const std::string& remoteHost, + const HostAndPort& hostForLogging) final; const SSLConfiguration& getSSLConfiguration() const final { @@ -1601,17 +1600,19 @@ SSLPeerInfo SSLManagerWindows::parseAndValidatePeerCertificateDeprecated( const SSLConnectionInterface* conn, const std::string& remoteHost, const HostAndPort& hostForLogging) { - auto swPeerSubjectName = parseAndValidatePeerCertificate( - const_cast<SSLConnectionWindows*>(static_cast<const SSLConnectionWindows*>(conn)) - ->_engine.native_handle(), - boost::none, - remoteHost, - hostForLogging); + + auto swPeerSubjectName = + parseAndValidatePeerCertificate( + const_cast<SSLConnectionWindows*>(static_cast<const SSLConnectionWindows*>(conn)) + ->_engine.native_handle(), + boost::none, + remoteHost, + hostForLogging) + .getNoThrow(); // We can't use uassertStatusOK here because we need to throw a SocketException. if (!swPeerSubjectName.isOK()) { throwSocketError(SocketErrorKind::CONNECT_ERROR, swPeerSubjectName.getStatus().reason()); } - return swPeerSubjectName.getValue(); } @@ -1859,7 +1860,7 @@ StatusWith<TLSVersion> mapTLSVersion(PCtxtHandle ssl) { } } -StatusWith<SSLPeerInfo> SSLManagerWindows::parseAndValidatePeerCertificate( +Future<SSLPeerInfo> SSLManagerWindows::parseAndValidatePeerCertificate( PCtxtHandle ssl, boost::optional<std::string> sni, const std::string& remoteHost, @@ -1876,7 +1877,7 @@ StatusWith<SSLPeerInfo> SSLManagerWindows::parseAndValidatePeerCertificate( recordTLSVersion(tlsVersionStatus.getValue(), hostForLogging); if (!_sslConfiguration.hasCA && isSSLServer) - return SSLPeerInfo(sni); + return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(sni)); SECURITY_STATUS ss = QueryContextAttributes(ssl, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &cert); @@ -1928,7 +1929,7 @@ StatusWith<SSLPeerInfo> SSLManagerWindows::parseAndValidatePeerCertificate( } if (peerSubjectName.empty()) { - return SSLPeerInfo(sni); + return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(sni)); } LOG(2) << "Accepted TLS connection from peer: " << peerSubjectName; @@ -1945,9 +1946,10 @@ StatusWith<SSLPeerInfo> SSLManagerWindows::parseAndValidatePeerCertificate( return swPeerCertificateRoles.getStatus(); } - return SSLPeerInfo(peerSubjectName, sni, std::move(swPeerCertificateRoles.getValue())); + return Future<SSLPeerInfo>::makeReady( + SSLPeerInfo(peerSubjectName, sni, std::move(swPeerCertificateRoles.getValue()))); } else { - return SSLPeerInfo(peerSubjectName); + return Future<SSLPeerInfo>::makeReady(SSLPeerInfo(peerSubjectName)); } } |