diff options
author | Erwin Pe <erwin.pe@mongodb.com> | 2021-12-06 19:33:59 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-12-06 19:50:05 +0000 |
commit | 7fe2b3080aa2d9e31e57aa2d556db8c4ec10663d (patch) | |
tree | 273c9666c0b2448647a0f1ef864d72414375e91e | |
parent | d4b800c330d8c713d26b0828fe39e046bac5ba03 (diff) | |
download | mongo-7fe2b3080aa2d9e31e57aa2d556db8c4ec10663d.tar.gz |
SERVER-60310 Skip irrelevant certificates when validating OCSP responses
(cherry picked from commit c56dfd0e1963c76c97a9d9d19ac7cef390f82066)
-rw-r--r-- | jstests/ocsp/lib/mock_ocsp.js | 16 | ||||
-rw-r--r-- | jstests/ocsp/lib/ocsp_mock.py | 9 | ||||
-rw-r--r-- | jstests/ocsp/ocsp_ignore_irrelevant.js | 184 | ||||
-rw-r--r-- | src/mongo/util/net/ssl_manager_openssl.cpp | 202 | ||||
-rw-r--r-- | src/third_party/mock_ocsp_responder/mock_ocsp_responder.py | 56 |
5 files changed, 398 insertions, 69 deletions
diff --git a/jstests/ocsp/lib/mock_ocsp.js b/jstests/ocsp/lib/mock_ocsp.js index 1896b2943c0..95e7e5713cf 100644 --- a/jstests/ocsp/lib/mock_ocsp.js +++ b/jstests/ocsp/lib/mock_ocsp.js @@ -40,7 +40,11 @@ class MockOCSPServer { * @param {number} next_update_secs * @param {object} responder_certificate_set */ - constructor(fault_type, next_update_secs, responder_certificate_set = OCSP_DELEGATE_RESPONDER) { + constructor(fault_type, + next_update_secs, + responder_certificate_set = OCSP_DELEGATE_RESPONDER, + include_extraneous_status = false, + issuer_hash_algorithm = "") { this.python = "python3"; this.fault_type = fault_type; @@ -57,6 +61,8 @@ class MockOCSPServer { // responder in the certificates. this.port = 8100; this.next_update_secs = next_update_secs; + this.include_extraneous_status = include_extraneous_status; + this.issuer_hash_algorithm = issuer_hash_algorithm; } start() { @@ -79,6 +85,14 @@ class MockOCSPServer { args.push("--next_update_seconds=" + this.next_update_secs); } + if (this.include_extraneous_status) { + args.push("--include_extraneous_status"); + } + + if (this.issuer_hash_algorithm) { + args.push("--issuer_hash_algorithm=" + this.issuer_hash_algorithm); + } + clearRawMongoProgramOutput(); this.pid = _startMongoProgram({args: args}); diff --git a/jstests/ocsp/lib/ocsp_mock.py b/jstests/ocsp/lib/ocsp_mock.py index 78a5313efe1..1f458e375dd 100644 --- a/jstests/ocsp/lib/ocsp_mock.py +++ b/jstests/ocsp/lib/ocsp_mock.py @@ -32,12 +32,19 @@ def main(): parser.add_argument('--next_update_seconds', type=int, default=32400, help="Specify how long the OCSP response should be valid for") + parser.add_argument('--include_extraneous_status', action='store_true', help="Include status of extraneous certificates in the response") + + parser.add_argument('--issuer_hash_algorithm', type=str, default='sha1', help="Algorithm to use when hashing issuer name and key") + args = parser.parse_args() if args.verbose: logging.basicConfig(level=logging.DEBUG) print('Initializing OCSP Responder') - mock_ocsp_responder.init_responder(issuer_cert=args.ca_file, responder_cert=args.ocsp_responder_cert, responder_key=args.ocsp_responder_key, fault=args.fault, next_update_seconds=args.next_update_seconds) + mock_ocsp_responder.init_responder(issuer_cert=args.ca_file, responder_cert=args.ocsp_responder_cert, + responder_key=args.ocsp_responder_key, fault=args.fault, next_update_seconds=args.next_update_seconds, + include_extraneous_status=args.include_extraneous_status, + issuer_hash_algorithm=args.issuer_hash_algorithm) mock_ocsp_responder.init(port=args.port, debug=args.verbose, host=args.bind_ip) diff --git a/jstests/ocsp/ocsp_ignore_irrelevant.js b/jstests/ocsp/ocsp_ignore_irrelevant.js new file mode 100644 index 00000000000..7a0850e4977 --- /dev/null +++ b/jstests/ocsp/ocsp_ignore_irrelevant.js @@ -0,0 +1,184 @@ +// Check that OCSP ignores statuses of irrelevant certificates in the response +// @tags: [requires_http_client] + +load("jstests/ocsp/lib/mock_ocsp.js"); + +(function() { +"use strict"; + +if (determineSSLProvider() === "apple") { + return; +} + +const INCLUDE_EXTRA_STATUS = true; + +function clientConnect(mongod, cafile) { + const exitCode = runMongoProgram("mongo", + "--host", + "localhost", + "--port", + mongod.port, + "--tls", + "--tlsCAFile", + OCSP_CA_PEM, + "--tlsCertificateKeyFile", + OCSP_CLIENT_CERT, + "--tlsAllowInvalidHostnames", + "--verbose", + 1, + "--eval", + ";"); + return exitCode; +} + +/** + * Tests OCSP status verification in the client-side ignores the statuses + * of irrelevant certificates. No stapling is performed server-side. + */ +function testClient(serverCert, caCert, responderCertPair, issuerDigest) { + jsTestLog("Running client test with params: " + JSON.stringify(arguments)); + + clearOCSPCache(); + + let mock_ocsp = + new MockOCSPServer("", 1, responderCertPair, INCLUDE_EXTRA_STATUS, issuerDigest); + + let ocsp_options = { + sslMode: "requireSSL", + sslPEMKeyFile: serverCert, + sslCAFile: caCert, + sslAllowInvalidHostnames: "", + setParameter: { + "failpoint.disableStapling": "{'mode':'alwaysOn'}", + "ocspEnabled": "true", + }, + waitForConnect: false, + }; + + const conn = MongoRunner.runMongod(ocsp_options); + waitForServer(conn); + + jsTestLog( + "Testing client can connect if OCSP response has extraneous statuses and the matching CertID is Good"); + mock_ocsp.start(); + + assert.eq(clientConnect(conn), 0); + + mock_ocsp.stop(); + + jsTestLog( + "Testing client can't connect if OCSP response has extraneous statuses and the matching CertID is Revoked"); + mock_ocsp = + new MockOCSPServer(FAULT_REVOKED, 1, responderCertPair, INCLUDE_EXTRA_STATUS, issuerDigest); + mock_ocsp.start(); + + assert.neq(clientConnect(conn), 0); + + MongoRunner.stopMongod(conn); + + // The mongoRunner spawns a new Mongo Object 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(); +} + +/** + * Tests OCSP status verification in the server-side (for stapling) ignores + * the statuses of irrelevant certificates. The server certificate must have + * the status_request TLS Feature (aka MustStaple) extension for the assertions + * in this test to work. + */ +function testStapling(serverCert, caCert, responderCertPair, issuerDigest) { + jsTestLog("Running stapling test with params: " + JSON.stringify(arguments)); + + clearOCSPCache(); + + let mock_ocsp = + new MockOCSPServer("", 32400, responderCertPair, INCLUDE_EXTRA_STATUS, issuerDigest); + + let ocsp_options = { + sslMode: "requireSSL", + sslPEMKeyFile: serverCert, + sslCAFile: caCert, + sslAllowInvalidHostnames: "", + setParameter: { + "ocspEnabled": "true", + }, + waitForConnect: false, + }; + + let conn = null; + + jsTestLog( + "Testing server staples a Good status if OCSP response has extraneous statuses and the matching CertID is Good"); + mock_ocsp.start(); + + conn = MongoRunner.runMongod(ocsp_options); + waitForServer(conn); + + assert.eq(clientConnect(conn), 0); + + MongoRunner.stopMongod(conn); + sleep(1000); + mock_ocsp.stop(); + + jsTestLog( + "Testing server staples a revoked status if OCSP response has extraneous statuses and the matching CertID is Revoked"); + Object.extend(ocsp_options, {waitForConnect: false}); + mock_ocsp = new MockOCSPServer( + FAULT_REVOKED, 32400, responderCertPair, INCLUDE_EXTRA_STATUS, issuerDigest); + mock_ocsp.start(); + + conn = MongoRunner.runMongod(ocsp_options); + waitForServer(conn); + + clearRawMongoProgramOutput(); + assert.neq(clientConnect(conn), 0); + + assert.soon(function() { + return rawMongoProgramOutput().search("OCSPCertificateStatusRevoked") !== -1; + }); + + MongoRunner.stopMongod(conn); + + // The mongoRunner spawns a new Mongo Object 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(); +} + +let digests = ['sha1']; +if (determineSSLProvider() !== "windows") { + // windows can't handle issuer names & keys hashed + // using sha256, so this is only tested on openssl. + digests.push('sha256'); +} + +for (const digest of digests) { + testClient(OCSP_SERVER_CERT, OCSP_CA_PEM, OCSP_DELEGATE_RESPONDER, digest); + testClient(OCSP_SERVER_CERT, OCSP_CA_PEM, OCSP_CA_RESPONDER, digest); + + if (determineSSLProvider() === "windows") { + continue; + } + + testClient(OCSP_SERVER_SIGNED_BY_INTERMEDIATE_CA_PEM, + OCSP_INTERMEDIATE_CA_WITH_ROOT_PEM, + OCSP_INTERMEDIATE_RESPONDER, + digest); + testClient(OCSP_SERVER_AND_INTERMEDIATE_APPENDED_PEM, + OCSP_CA_PEM, + OCSP_INTERMEDIATE_RESPONDER, + digest); +} + +if (!supportsStapling()) { + return; +} + +for (const digest of digests) { + testStapling(OCSP_SERVER_MUSTSTAPLE_CERT, OCSP_CA_PEM, OCSP_DELEGATE_RESPONDER, digest); +} +}()); diff --git a/src/mongo/util/net/ssl_manager_openssl.cpp b/src/mongo/util/net/ssl_manager_openssl.cpp index b222f01954a..3e3cb3252f3 100644 --- a/src/mongo/util/net/ssl_manager_openssl.cpp +++ b/src/mongo/util/net/ssl_manager_openssl.cpp @@ -465,15 +465,14 @@ struct X509_OBJECTFree { using UniqueX509Object = std::unique_ptr<X509_OBJECT, X509_OBJECTFree>; -StatusWith<UniqueCertId> getCertIdForCert(SSL_CTX* context, - X509* cert, - STACK_OF(X509) * intermediateCerts) { +StatusWith<UniqueX509> getIssuerCertForCert(SSL_CTX* context, + X509* cert, + STACK_OF(X509) * intermediateCerts) { // First search the intermediate certificates for the issuer. for (int i = 0; i < sk_X509_num(intermediateCerts); i++) { if (X509_NAME_cmp(X509_get_issuer_name(cert), X509_get_subject_name(sk_X509_value(intermediateCerts, i))) == 0) { - return UniqueCertId( - OCSP_cert_to_id(nullptr, cert, sk_X509_value(intermediateCerts, i))); + return UniqueX509(X509_dup(sk_X509_value(intermediateCerts, i))); } } @@ -494,7 +493,11 @@ StatusWith<UniqueCertId> getCertIdForCert(SSL_CTX* context, if (obj == nullptr) { return getSSLFailure("Could not get X509 Object from store."); } - return UniqueCertId(OCSP_cert_to_id(nullptr, cert, X509_OBJECT_get0_X509(obj.get()))); + return UniqueX509(X509_dup(X509_OBJECT_get0_X509(obj.get()))); +} + +UniqueCertId getCertIdForCert(X509* cert, X509* issuerCert) { + return UniqueCertId(OCSP_cert_to_id(nullptr, cert, issuerCert)); } struct OCSPCertIDCompareLess { @@ -521,6 +524,7 @@ struct OCSPValidationContext { std::map<std::string, OCSPRequestAndIDs> ocspRequestMap; OCSPCertIDSet uniqueCertIds; std::vector<std::string> leafResponders; + UniqueX509 issuerCert; }; constexpr Milliseconds kOCSPUnknownStatusRefreshRate = Minutes(5); @@ -621,11 +625,10 @@ struct OCSPCacheKey { * to the ocspRequestMap. See comment in extractOcspUris for details. */ StatusWith<std::vector<std::string>> addOCSPUrlToMap( - SSL_CTX* context, X509* cert, + X509* issuerCert, std::map<std::string, OCSPRequestAndIDs>& ocspRequestMap, - OCSPCertIDSet& uniqueCertIds, - STACK_OF(X509) * intermediateCerts) { + OCSPCertIDSet& uniqueCertIds) { UniqueOpenSSLStringStack aiaOCSP(X509_get1_ocsp(cert)); std::vector<std::string> responders; @@ -657,22 +660,12 @@ StatusWith<std::vector<std::string>> addOCSPUrlToMap( HostAndPort hostAndPort(str::stream() << host << ":" << port); - auto swCertId = getCertIdForCert(context, cert, intermediateCerts); - if (!swCertId.isOK()) { - return swCertId.getStatus(); - } - - UniqueCertId certID = std::move(swCertId.getValue()); + UniqueCertId certID = getCertIdForCert(cert, issuerCert); if (!certID) { return getSSLFailure("Could not get certificate ID for Map."); } - swCertId = getCertIdForCert(context, cert, intermediateCerts); - if (!swCertId.isOK()) { - return swCertId.getStatus(); - } - - UniqueCertId certIDForArray = std::move(swCertId.getValue()); + UniqueCertId certIDForArray = getCertIdForCert(cert, issuerCert); if (certIDForArray == nullptr) { return getSSLFailure("Could not get certificate ID for Array."); } @@ -688,12 +681,7 @@ StatusWith<std::vector<std::string>> addOCSPUrlToMap( mapIter->second.certIDs.insert(std::move(certIDForArray)); } - auto swCertId = getCertIdForCert(context, cert, intermediateCerts); - if (!swCertId.isOK()) { - return swCertId.getStatus(); - } - - UniqueCertId certIDForSet = std::move(swCertId.getValue()); + UniqueCertId certIDForSet = getCertIdForCert(cert, issuerCert); if (!certIDForSet) { return getSSLFailure("Could not get certificate ID for Set."); } @@ -739,13 +727,61 @@ Future<UniqueOCSPResponse> retrieveOCSPResponse(const std::string& host, }); } +bool certIdsMatch(const UniqueCertId& reqCertId, const UniqueCertId& rspCertId, X509* issuerCert) { + + if (!(OCSP_id_cmp(rspCertId.get(), reqCertId.get()))) { + // all CertID fields match + return true; + } + + ASN1_OBJECT* rspHashAlgo = nullptr; + ASN1_INTEGER* rspSerial = nullptr; + ASN1_INTEGER* reqSerial = nullptr; + + if (!(OCSP_id_get0_info(nullptr, nullptr, nullptr, &reqSerial, reqCertId.get()))) { + LOGV2_DEBUG(6031001, 3, "Failed to obtain field values from OCSP request CertID"); + return false; + } + + if (!(OCSP_id_get0_info(nullptr, &rspHashAlgo, nullptr, &rspSerial, rspCertId.get()))) { + LOGV2_DEBUG(6031002, 3, "Failed to obtain field values from OCSP response CertID"); + return false; + } + + if (ASN1_INTEGER_cmp(rspSerial, reqSerial)) { + // serial numbers don't match + return false; + } + + // It is possible that the issuer hashes did not match because + // the hash algorithm used is different, so we have to re-hash the + // requested issuer name & key using the algorithm in the response, and + // do another comparison. + auto md = EVP_get_digestbyobj(rspHashAlgo); + if (!md) { + // unrecognized digest algorithm, skip + LOGV2_DEBUG(6031000, 3, "Received an OCSP CertID with an unknown hash algorithm"); + return false; + } + + auto newReqCertId = UniqueCertId(OCSP_cert_id_new( + md, X509_get_subject_name(issuerCert), X509_get0_pubkey_bitstr(issuerCert), reqSerial)); + + // Compare just the issuer hashes here since the serial numbers are known to be the same. + if (!(OCSP_id_issuer_cmp(newReqCertId.get(), rspCertId.get()))) { + // issuers matched + return true; + } + return false; +} + /** * This function iterates over the basic response object from the OCSP response object * and returns a set of Certificate IDs that are there in the response and a date object * which represents the time when the Response needs to be refreshed. */ StatusWith<std::pair<OCSPCertIDSet, boost::optional<Date_t>>> iterateResponse( - OCSP_BASICRESP* basicResp, STACK_OF(X509) * intermediateCerts) { + OCSP_BASICRESP* basicResp, X509* issuerCert, const OCSPCertIDSet& certIdsInRequest) { boost::optional<Date_t> earliestNextUpdate = boost::none; OCSPCertIDSet certIdsInResponse; @@ -759,9 +795,22 @@ StatusWith<std::pair<OCSPCertIDSet, boost::optional<Date_t>>> iterateResponse( return getSSLFailure("OCSP Basic Response invalid: Missing response."); } - certIdsInResponse.emplace( + auto certId = UniqueCertId( OCSP_CERTID_dup(const_cast<OCSP_CERTID*>(OCSP_SINGLERESP_get0_id(singleResp)))); + // This is O(n), but certIdsInRequest is expected to only contain just + // one CertID in it anyways, so this should be no big deal + if (std::none_of(certIdsInRequest.begin(), + certIdsInRequest.end(), + [&certId, &issuerCert](const UniqueCertId& reqCertId) { + return certIdsMatch(reqCertId, certId, issuerCert); + })) { + // skip irrelevant certificates + continue; + } + + certIdsInResponse.insert(std::move(certId)); + int reason; ASN1_GENERALIZEDTIME *revtime, *thisupd, *nextupd; @@ -784,6 +833,12 @@ StatusWith<std::pair<OCSPCertIDSet, boost::optional<Date_t>>> iterateResponse( } } + if (certIdsInResponse.empty()) { + return getSSLFailure( + "OCSP Basic Response is insufficient: No single responses matched any of the expected " + "cert IDs"); + } + if (earliestNextUpdate < Date_t::now()) { return getSSLFailure("OCSP Basic Response is invalid: Response is expired."); } @@ -797,7 +852,12 @@ StatusWith<std::pair<OCSPCertIDSet, boost::optional<Date_t>>> iterateResponse( * earliest expiration date on the OCSPResponse. */ StatusWith<std::pair<OCSPCertIDSet, boost::optional<Date_t>>> parseAndValidateOCSPResponse( - SSL_CTX* context, X509_STORE* ca, OCSP_RESPONSE* response, STACK_OF(X509) * intermediateCerts) { + SSL_CTX* context, + X509_STORE* ca, + OCSP_RESPONSE* response, + STACK_OF(X509) * intermediateCerts, + X509* issuerCert, + const OCSPCertIDSet& certIdsInRequest) { // Read the overall status of the OCSP response int responseStatus = OCSP_response_status(response); switch (responseStatus) { @@ -834,46 +894,56 @@ StatusWith<std::pair<OCSPCertIDSet, boost::optional<Date_t>>> parseAndValidateOC return getSSLFailure("Failed to verify signature from OCSP response."); } - return iterateResponse(basicResponse.get(), intermediateCerts); + return iterateResponse(basicResponse.get(), issuerCert, certIdsInRequest); } Future<OCSPFetchResponse> dispatchOCSPRequests(SSL_CTX* context, std::shared_ptr<X509_STORE> ca, std::shared_ptr<STACK_OF(X509)> intermediateCerts, - OCSPValidationContext& ocspContext, + OCSPValidationContext ocspContext, OCSPPurpose purpose) { - auto& [ocspRequestMap, _, leafResponders] = ocspContext; + auto& [ocspRequestMap, _, leafResponders, issuer] = ocspContext; struct OCSPCompletionState { OCSPCompletionState(int numRequests_, Promise<OCSPFetchResponse> promise_, - std::shared_ptr<STACK_OF(X509)> intermediateCerts_) + std::shared_ptr<STACK_OF(X509)> intermediateCerts_, + OCSPValidationContext ocspContext_) : finishLine(numRequests_), promise(std::move(promise_)), - intermediateCerts(std::move(intermediateCerts_)) {} + intermediateCerts(std::move(intermediateCerts_)), + ocspContext(std::move(ocspContext_)) {} StrongWeakFinishLine finishLine; Promise<OCSPFetchResponse> promise; std::shared_ptr<STACK_OF(X509)> intermediateCerts; + OCSPValidationContext ocspContext; }; std::vector<Future<UniqueOCSPResponse>> futureResponses{}; + std::vector<OCSPCertIDSet*> requestedCertIDSets{}; for (auto host : leafResponders) { auto& ocspRequestAndIDs = ocspRequestMap[host]; Future<UniqueOCSPResponse> futureResponse = retrieveOCSPResponse(host, ocspRequestAndIDs, purpose); futureResponses.push_back(std::move(futureResponse)); + requestedCertIDSets.push_back(&ocspRequestAndIDs.certIDs); }; auto pf = makePromiseFuture<OCSPFetchResponse>(); - auto state = std::make_shared<OCSPCompletionState>( - futureResponses.size(), std::move(pf.promise), std::move(intermediateCerts)); + auto state = std::make_shared<OCSPCompletionState>(futureResponses.size(), + std::move(pf.promise), + std::move(intermediateCerts), + std::move(ocspContext)); for (size_t i = 0; i < futureResponses.size(); i++) { auto futureResponse = std::move(futureResponses[i]); + auto requestedCertIDs = requestedCertIDSets[i]; + std::move(futureResponse) - .getAsync([context, ca, state](StatusWith<UniqueOCSPResponse> swResponse) mutable { + .getAsync([context, ca, state, requestedCertIDs]( + StatusWith<UniqueOCSPResponse> swResponse) mutable { if (!swResponse.isOK()) { if (state->finishLine.arriveWeakly()) { state->promise.setError( @@ -883,8 +953,13 @@ Future<OCSPFetchResponse> dispatchOCSPRequests(SSL_CTX* context, return; } - auto swCertIDSetAndDuration = parseAndValidateOCSPResponse( - context, ca.get(), swResponse.getValue().get(), state->intermediateCerts.get()); + auto swCertIDSetAndDuration = + parseAndValidateOCSPResponse(context, + ca.get(), + swResponse.getValue().get(), + state->intermediateCerts.get(), + state->ocspContext.issuerCert.get(), + *requestedCertIDs); if (swCertIDSetAndDuration.isOK() || swCertIDSetAndDuration.getStatus() == @@ -931,8 +1006,13 @@ StatusWith<OCSPValidationContext> extractOcspUris(SSL_CTX* context, std::map<std::string, OCSPRequestAndIDs> ocspRequestMap; OCSPCertIDSet uniqueCertIds; + auto swIssuerCert = getIssuerCertForCert(context, peerCert, intermediateCerts); + if (!swIssuerCert.isOK()) { + return swIssuerCert.getStatus(); + } + auto swLeafResponders = - addOCSPUrlToMap(context, peerCert, ocspRequestMap, uniqueCertIds, intermediateCerts); + addOCSPUrlToMap(peerCert, swIssuerCert.getValue().get(), ocspRequestMap, uniqueCertIds); if (!swLeafResponders.isOK()) { return swLeafResponders.getStatus(); } @@ -942,8 +1022,10 @@ StatusWith<OCSPValidationContext> extractOcspUris(SSL_CTX* context, return getSSLFailure("Certificate has no OCSP Responders"); } - return OCSPValidationContext{ - std::move(ocspRequestMap), std::move(uniqueCertIds), std::move(leafResponders)}; + return OCSPValidationContext{std::move(ocspRequestMap), + std::move(uniqueCertIds), + std::move(leafResponders), + std::move(swIssuerCert.getValue())}; } class OCSPCache : public ReadThroughCache<OCSPCacheKey, OCSPFetchResponse> { @@ -980,12 +1062,10 @@ private: return boost::none; } - auto ocspContext = std::move(swOCSPContext.getValue()); - auto swResponse = dispatchOCSPRequests(key.context, nullptr, key.intermediateCerts, - ocspContext, + std::move(swOCSPContext.getValue()), OCSPPurpose::kClientVerify) .getNoThrow(); if (!swResponse.isOK()) { @@ -1630,24 +1710,27 @@ StatusWith<bool> verifyStapledResponse(SSL* conn, X509* peerCert, OCSP_RESPONSE* // OCSP checks. AIA stands for the Authority Information Access x509 extension. ERR_clear_error(); auto intermediateCerts = SSLgetVerifiedChain(conn); - OCSPCertIDSet emptyCertIDSet{}; auto context = SSL_get_SSL_CTX(conn); - auto swCertId = getCertIdForCert(context, peerCert, intermediateCerts.get()); - if (!swCertId.isOK()) { - return swCertId.getStatus(); + auto swIssuerCert = getIssuerCertForCert(context, peerCert, intermediateCerts.get()); + if (!swIssuerCert.isOK()) { + return swIssuerCert.getStatus(); } - auto swCertIDSetAndDuration = - parseAndValidateOCSPResponse(context, nullptr, response, intermediateCerts.get()); + auto issuerCert = std::move(swIssuerCert.getValue()); + + auto certId = getCertIdForCert(peerCert, issuerCert.get()); + OCSPCertIDSet requestedCertIds; + requestedCertIds.insert(std::move(certId)); + + auto swCertIDSetAndDuration = parseAndValidateOCSPResponse( + context, nullptr, response, intermediateCerts.get(), issuerCert.get(), requestedCertIds); if (swCertIDSetAndDuration.getStatus() == ErrorCodes::OCSPCertificateStatusRevoked) { return swCertIDSetAndDuration.getStatus(); } - if (swCertIDSetAndDuration.isOK() && - swCertIDSetAndDuration.getValue().first.find(swCertId.getValue()) != - swCertIDSetAndDuration.getValue().first.end()) { + if (swCertIDSetAndDuration.isOK()) { return true; } @@ -2025,10 +2108,11 @@ Future<Milliseconds> OCSPFetcher::fetchAndStaple(Promise<void>* promise) { return swOCSPContext.getStatus(); } - auto ocspContext = std::move(swOCSPContext.getValue()); - - return dispatchOCSPRequests( - _context, _ca, std::move(intermediateCerts), ocspContext, OCSPPurpose::kStaple) + return dispatchOCSPRequests(_context, + _ca, + std::move(intermediateCerts), + std::move(swOCSPContext.getValue()), + OCSPPurpose::kStaple) .onCompletion( [this, promise](StatusWith<OCSPFetchResponse> swResponse) mutable -> Milliseconds { LOGV2_INFO(577165, "OCSP fetch/staple completion"); diff --git a/src/third_party/mock_ocsp_responder/mock_ocsp_responder.py b/src/third_party/mock_ocsp_responder/mock_ocsp_responder.py index 236b14519df..551127dcfb8 100644 --- a/src/third_party/mock_ocsp_responder/mock_ocsp_responder.py +++ b/src/third_party/mock_ocsp_responder/mock_ocsp_responder.py @@ -128,6 +128,7 @@ class OCSPResponseBuilder(object): _revocation_date = None _certificate_issuer = None _hash_algo = None + _issuer_hash_algo = None _key_hash_algo = None _nonce = None _this_update = None @@ -172,6 +173,7 @@ class OCSPResponseBuilder(object): self._certificate_status_list = certificate_status_list self._revocation_date = revocation_date + self._issuer_hash_algo = 'sha1' self._key_hash_algo = 'sha1' self._hash_algo = 'sha256' self._response_data_extensions = {} @@ -237,6 +239,23 @@ class OCSPResponseBuilder(object): self._next_update = value + @_writer + def issuer_hash_algo(self, value): + """ + String name of the hash algorithm used for hashing the issuer name and + issuer public key. + """ + + if not isinstance(value, str): + raise TypeError(_pretty_message( + ''' + issuer_hash_algo must be an instance of str, not %s + ''', + _type_name(value) + )) + + self._issuer_hash_algo = value + def build(self, responder_private_key=None, responder_certificate=None): """ Validates the request information, constructs the ASN.1 structure and @@ -365,10 +384,10 @@ class OCSPResponseBuilder(object): response = { 'cert_id': { 'hash_algorithm': { - 'algorithm': self._key_hash_algo + 'algorithm': self._issuer_hash_algo }, - 'issuer_name_hash': getattr(issuer.subject, self._key_hash_algo), - 'issuer_key_hash': getattr(issuer.public_key, self._key_hash_algo), + 'issuer_name_hash': getattr(issuer.subject, self._issuer_hash_algo), + 'issuer_key_hash': getattr(issuer.public_key, self._issuer_hash_algo), 'serial_number': serial, }, 'cert_status': cert_status, @@ -452,7 +471,8 @@ app = Flask(__name__) class OCSPResponder: def __init__(self, issuer_cert: str, responder_cert: str, responder_key: str, - fault: str, next_update_seconds: int): + fault: str, next_update_seconds: int, + include_extraneous_status: bool, issuer_hash_algorithm: str): """ Create a new OCSPResponder instance. @@ -468,7 +488,8 @@ class OCSPResponder: will return the corresponding certificate as a string. :param next_update_seconds: The ``nextUpdate`` value that will be written into the response. Default: 9 hours. - + :param include_extraneous_status: Include status of irrelevant certs in the response. + :param issuer_hash_algorithm: Algorithm to use when hashing the issuer name & key. """ # Certs and keys self._issuer_cert = asymmetric.load_certificate(issuer_cert) @@ -480,6 +501,10 @@ class OCSPResponder: self._fault = fault + self._include_extraneous_status = include_extraneous_status + + self._issuer_hash_algorithm = issuer_hash_algorithm + def _fail(self, status: ResponseStatus) -> OCSPResponse: builder = OCSPResponseBuilder(response_status=status.value) return builder.build() @@ -522,7 +547,14 @@ class OCSPResponder: logger.exception('Could not determine certificate status: %s', e) return self._fail(ResponseStatus.internal_error) - certificate_status_list = [(serial, certificate_status.value)] + if self._include_extraneous_status: + revocation_date = datetime(2018, 1, 1, 1, 00, 00, 00, timezone.utc) + certificate_status_list = [ (serial+3, CertificateStatus.good.value), + (serial+2, CertificateStatus.unknown.value), + (serial+1, CertificateStatus.revoked.value), + (serial, certificate_status.value) ] + else: + certificate_status_list = [(serial, certificate_status.value)] # Build the response builder = OCSPResponseBuilder(**{ @@ -563,6 +595,10 @@ class OCSPResponder: # Set certificate issuer builder.certificate_issuer = self._issuer_cert + # Set the issuer hash algorithm + if self._issuer_hash_algorithm: + builder.issuer_hash_algo = self._issuer_hash_algorithm + # Set next update date now = datetime.now(timezone.utc) builder.next_update = (now + timedelta(seconds=self._next_update_seconds)).replace(microsecond=0) @@ -579,9 +615,13 @@ class OCSPResponder: responder = None -def init_responder(issuer_cert: str, responder_cert: str, responder_key: str, fault: str, next_update_seconds: int): +def init_responder(issuer_cert: str, responder_cert: str, responder_key: str, fault: str, + next_update_seconds: int, include_extraneous_status: bool, + issuer_hash_algorithm: str): global responder - responder = OCSPResponder(issuer_cert=issuer_cert, responder_cert=responder_cert, responder_key=responder_key, fault=fault, next_update_seconds=next_update_seconds) + responder = OCSPResponder(issuer_cert=issuer_cert, responder_cert=responder_cert, responder_key=responder_key, + fault=fault, next_update_seconds=next_update_seconds, + include_extraneous_status=include_extraneous_status, issuer_hash_algorithm=issuer_hash_algorithm) def init(port=8080, debug=False, host=None): logger.info('Launching %sserver on port %d', 'debug' if debug else '', port) |