summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorErwin Pe <erwin.pe@mongodb.com>2021-12-06 19:33:59 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-12-06 19:50:05 +0000
commit7fe2b3080aa2d9e31e57aa2d556db8c4ec10663d (patch)
tree273c9666c0b2448647a0f1ef864d72414375e91e
parentd4b800c330d8c713d26b0828fe39e046bac5ba03 (diff)
downloadmongo-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.js16
-rw-r--r--jstests/ocsp/lib/ocsp_mock.py9
-rw-r--r--jstests/ocsp/ocsp_ignore_irrelevant.js184
-rw-r--r--src/mongo/util/net/ssl_manager_openssl.cpp202
-rw-r--r--src/third_party/mock_ocsp_responder/mock_ocsp_responder.py56
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)