зеркало из https://github.com/mozilla/gecko-dev.git
bug 997509 - heed expired Revoked or Unknown OCSP responses r=briansmith
This commit is contained in:
Родитель
7ce0e02be8
Коммит
c13f6d39c7
|
@ -212,13 +212,18 @@ NSSCertDBTrustDomain::CheckRevocation(
|
|||
// determines the result unless the OCSP response is expired. We make an
|
||||
// exception for expired responses because some servers, nginx in particular,
|
||||
// are known to serve expired responses due to bugs.
|
||||
// We keep track of the result of verifying the stapled response but don't
|
||||
// immediately return failure if the response has expired.
|
||||
PRErrorCode stapledOCSPResponseErrorCode = 0;
|
||||
if (stapledOCSPResponse) {
|
||||
PR_ASSERT(endEntityOrCA == EndEntityOrCA::MustBeEndEntity);
|
||||
bool expired;
|
||||
SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert,
|
||||
time,
|
||||
maxOCSPLifetimeInDays,
|
||||
stapledOCSPResponse,
|
||||
ResponseWasStapled);
|
||||
ResponseWasStapled,
|
||||
expired);
|
||||
if (rv == SECSuccess) {
|
||||
// stapled OCSP response present and good
|
||||
Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1);
|
||||
|
@ -226,17 +231,19 @@ NSSCertDBTrustDomain::CheckRevocation(
|
|||
("NSSCertDBTrustDomain: stapled OCSP response: good"));
|
||||
return rv;
|
||||
}
|
||||
if (PR_GetError() != SEC_ERROR_OCSP_OLD_RESPONSE) {
|
||||
stapledOCSPResponseErrorCode = PR_GetError();
|
||||
if (stapledOCSPResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE ||
|
||||
expired) {
|
||||
// stapled OCSP response present but expired
|
||||
Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3);
|
||||
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
|
||||
("NSSCertDBTrustDomain: expired stapled OCSP response"));
|
||||
} else {
|
||||
// stapled OCSP response present but invalid for some reason
|
||||
Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4);
|
||||
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
|
||||
("NSSCertDBTrustDomain: stapled OCSP response: failure"));
|
||||
return rv;
|
||||
} else {
|
||||
// stapled OCSP response present but expired
|
||||
Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3);
|
||||
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
|
||||
("NSSCertDBTrustDomain: expired stapled OCSP response"));
|
||||
}
|
||||
} else {
|
||||
// no stapled OCSP response
|
||||
|
@ -334,11 +341,14 @@ NSSCertDBTrustDomain::CheckRevocation(
|
|||
PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
if (stapledOCSPResponse ||
|
||||
cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
|
||||
if (cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
|
||||
PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
if (stapledOCSPResponseErrorCode != 0) {
|
||||
PR_SetError(stapledOCSPResponseErrorCode, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
// Nothing to do if we don't have an OCSP responder URI for the cert; just
|
||||
// assume it is good. Note that this is the confusing, but intended,
|
||||
|
@ -392,11 +402,11 @@ NSSCertDBTrustDomain::CheckRevocation(
|
|||
PR_SetError(cachedResponseErrorCode, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
if (stapledOCSPResponse) {
|
||||
if (stapledOCSPResponseErrorCode != 0) {
|
||||
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
|
||||
("NSSCertDBTrustDomain: returning SECFailure from expired "
|
||||
"stapled response after OCSP request failure"));
|
||||
PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
|
||||
PR_SetError(stapledOCSPResponseErrorCode, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
|
@ -406,10 +416,15 @@ NSSCertDBTrustDomain::CheckRevocation(
|
|||
return SECSuccess; // Soft fail -> success :(
|
||||
}
|
||||
|
||||
// If the response from the network has expired but indicates a revoked
|
||||
// or unknown certificate, PR_GetError() will return the appropriate error.
|
||||
// We actually ignore expired here.
|
||||
bool expired;
|
||||
SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time,
|
||||
maxOCSPLifetimeInDays,
|
||||
response,
|
||||
ResponseIsFromNetwork);
|
||||
ResponseIsFromNetwork,
|
||||
expired);
|
||||
if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) {
|
||||
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
|
||||
("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse"));
|
||||
|
@ -422,11 +437,11 @@ NSSCertDBTrustDomain::CheckRevocation(
|
|||
return rv;
|
||||
}
|
||||
|
||||
if (stapledOCSPResponse) {
|
||||
if (stapledOCSPResponseErrorCode != 0) {
|
||||
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
|
||||
("NSSCertDBTrustDomain: returning SECFailure from expired stapled "
|
||||
"response after OCSP request verification failure"));
|
||||
PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
|
||||
PR_SetError(stapledOCSPResponseErrorCode, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
|
||||
|
@ -440,14 +455,20 @@ SECStatus
|
|||
NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse(
|
||||
const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
|
||||
uint16_t maxLifetimeInDays, const SECItem* encodedResponse,
|
||||
EncodedResponseSource responseSource)
|
||||
EncodedResponseSource responseSource, /*out*/ bool& expired)
|
||||
{
|
||||
PRTime thisUpdate = 0;
|
||||
PRTime validThrough = 0;
|
||||
SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
|
||||
maxLifetimeInDays, encodedResponse,
|
||||
&thisUpdate, &validThrough);
|
||||
expired, &thisUpdate, &validThrough);
|
||||
PRErrorCode error = (rv == SECSuccess ? 0 : PR_GetError());
|
||||
// If a response was stapled and expired, we don't want to cache it. Return
|
||||
// early to simplify the logic here.
|
||||
if (responseSource == ResponseWasStapled && expired) {
|
||||
PR_ASSERT(rv != SECSuccess);
|
||||
return rv;
|
||||
}
|
||||
// validThrough is only trustworthy if the response successfully verifies
|
||||
// or it indicates a revoked or unknown certificate.
|
||||
// If this isn't the case, store an indication of failure (to prevent
|
||||
|
|
|
@ -91,7 +91,7 @@ private:
|
|||
SECStatus VerifyAndMaybeCacheEncodedOCSPResponse(
|
||||
const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
|
||||
uint16_t maxLifetimeInDays, const SECItem* encodedResponse,
|
||||
EncodedResponseSource responseSource);
|
||||
EncodedResponseSource responseSource, /*out*/ bool& expired);
|
||||
|
||||
const SECTrustType mCertDBTrustType;
|
||||
const OCSPFetching mOCSPFetching;
|
||||
|
|
|
@ -135,6 +135,34 @@ function add_tests_in_mode(useMozillaPKIX)
|
|||
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNKNOWN_CERT),
|
||||
ocspResponseUnknown);
|
||||
|
||||
if (useMozillaPKIX) {
|
||||
// If the response is expired but indicates Revoked or Unknown and a
|
||||
// newer status can't be fetched, the Revoked or Unknown status will
|
||||
// be returned.
|
||||
add_ocsp_test("ocsp-stapling-revoked-old.example.com",
|
||||
getXPCOMStatusFromNSS(SEC_ERROR_REVOKED_CERTIFICATE),
|
||||
null);
|
||||
add_ocsp_test("ocsp-stapling-unknown-old.example.com",
|
||||
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNKNOWN_CERT),
|
||||
null);
|
||||
// If the response is expired but indicates Revoked or Unknown and
|
||||
// a newer status can be fetched and successfully verified, this
|
||||
// should result in a successful certificate verification.
|
||||
add_ocsp_test("ocsp-stapling-revoked-old.example.com", Cr.NS_OK,
|
||||
ocspResponseGood);
|
||||
add_ocsp_test("ocsp-stapling-unknown-old.example.com", Cr.NS_OK,
|
||||
ocspResponseGood);
|
||||
// If a newer status can be fetched but it fails to verify, the
|
||||
// Revoked or Unknown status of the expired stapled response
|
||||
// should be returned.
|
||||
add_ocsp_test("ocsp-stapling-revoked-old.example.com",
|
||||
getXPCOMStatusFromNSS(SEC_ERROR_REVOKED_CERTIFICATE),
|
||||
expiredOCSPResponseGood);
|
||||
add_ocsp_test("ocsp-stapling-unknown-old.example.com",
|
||||
getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNKNOWN_CERT),
|
||||
expiredOCSPResponseGood);
|
||||
}
|
||||
|
||||
if (useMozillaPKIX) {
|
||||
// These tests are verifying that an valid but very old response
|
||||
// is rejected as a valid stapled response, requiring a fetch
|
||||
|
@ -158,8 +186,8 @@ function check_ocsp_stapling_telemetry() {
|
|||
do_check_eq(histogram.counts[0], 2 * 0); // histogram bucket 0 is unused
|
||||
do_check_eq(histogram.counts[1], 2 * 0); // 0 connections with a good response
|
||||
do_check_eq(histogram.counts[2], 2 * 0); // 0 connections with no stapled resp.
|
||||
do_check_eq(histogram.counts[3], 2 * 12 + 3); // 12 connections with an expired response
|
||||
// +3 more mozilla::pkix-only expired responses
|
||||
do_check_eq(histogram.counts[3], 2 * 12 + 9); // 12 connections with an expired response
|
||||
// +9 more mozilla::pkix-only expired responses
|
||||
do_check_eq(histogram.counts[4], 2 * 0); // 0 connections with bad responses
|
||||
run_next_test();
|
||||
}
|
||||
|
|
|
@ -23,7 +23,9 @@ const OCSPHost sOCSPHosts[] =
|
|||
{
|
||||
{ "ocsp-stapling-good.example.com", ORTGood, nullptr },
|
||||
{ "ocsp-stapling-revoked.example.com", ORTRevoked, nullptr },
|
||||
{ "ocsp-stapling-revoked-old.example.com", ORTRevokedOld, nullptr },
|
||||
{ "ocsp-stapling-unknown.example.com", ORTUnknown, nullptr },
|
||||
{ "ocsp-stapling-unknown-old.example.com", ORTUnknownOld, nullptr },
|
||||
{ "ocsp-stapling-good-other.example.com", ORTGoodOtherCert, "ocspOtherEndEntity" },
|
||||
{ "ocsp-stapling-good-other-ca.example.com", ORTGoodOtherCA, "otherCA" },
|
||||
{ "ocsp-stapling-expired.example.com", ORTExpired, nullptr },
|
||||
|
|
|
@ -109,7 +109,8 @@ GetOCSPResponseForType(OCSPResponseType aORT, CERTCertificate *aCert,
|
|||
if (aORT == ORTSkipResponseBytes) {
|
||||
context.skipResponseBytes = true;
|
||||
}
|
||||
if (aORT == ORTExpired || aORT == ORTExpiredFreshCA) {
|
||||
if (aORT == ORTExpired || aORT == ORTExpiredFreshCA ||
|
||||
aORT == ORTRevokedOld || aORT == ORTUnknownOld) {
|
||||
context.thisUpdate = oldNow;
|
||||
context.nextUpdate = oldNow + 10 * PR_USEC_PER_SEC;
|
||||
}
|
||||
|
@ -119,10 +120,10 @@ GetOCSPResponseForType(OCSPResponseType aORT, CERTCertificate *aCert,
|
|||
if (aORT == ORTAncientAlmostExpired) {
|
||||
context.thisUpdate = now - (640 * oneDay);
|
||||
}
|
||||
if (aORT == ORTRevoked) {
|
||||
if (aORT == ORTRevoked || aORT == ORTRevokedOld) {
|
||||
context.certStatus = 1;
|
||||
}
|
||||
if (aORT == ORTUnknown) {
|
||||
if (aORT == ORTUnknown || aORT == ORTUnknownOld) {
|
||||
context.certStatus = 2;
|
||||
}
|
||||
if (aORT == ORTBadSignature) {
|
||||
|
|
|
@ -16,7 +16,9 @@ enum OCSPResponseType
|
|||
ORTNull = 0,
|
||||
ORTGood, // the certificate is good
|
||||
ORTRevoked, // the certificate has been revoked
|
||||
ORTRevokedOld, // same, but the response is old
|
||||
ORTUnknown, // the responder doesn't know if the cert is good
|
||||
ORTUnknownOld, // same, but the response is old
|
||||
ORTGoodOtherCert, // the response references a different certificate
|
||||
ORTGoodOtherCA, // the wrong CA has signed the response
|
||||
ORTExpired, // the signature on the response has expired
|
||||
|
|
|
@ -109,6 +109,10 @@ SECItem* CreateEncodedOCSPRequest(PLArenaPool* arena,
|
|||
const CERTCertificate* cert,
|
||||
const CERTCertificate* issuerCert);
|
||||
|
||||
// The out parameter expired will be true if the response has expired. If the
|
||||
// response also indicates a revoked or unknown certificate, that error
|
||||
// will be returned by PR_GetError(). Otherwise, SEC_ERROR_OCSP_OLD_RESPONSE
|
||||
// will be returned by PR_GetError() for an expired response.
|
||||
// The optional parameter thisUpdate will be the thisUpdate value of
|
||||
// the encoded response if it is considered trustworthy. Only
|
||||
// good, unknown, or revoked responses that verify correctly are considered
|
||||
|
@ -122,6 +126,7 @@ SECStatus VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
|
|||
PRTime time,
|
||||
uint16_t maxLifetimeInDays,
|
||||
const SECItem* encodedResponse,
|
||||
/* out */ bool& expired,
|
||||
/* optional out */ PRTime* thisUpdate,
|
||||
/* optional out */ PRTime* validThrough);
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ public:
|
|||
, certStatus(CertStatus::Unknown)
|
||||
, thisUpdate(thisUpdate)
|
||||
, validThrough(validThrough)
|
||||
, expired(false)
|
||||
{
|
||||
if (thisUpdate) {
|
||||
*thisUpdate = 0;
|
||||
|
@ -87,6 +88,7 @@ public:
|
|||
CertStatus certStatus;
|
||||
PRTime* thisUpdate;
|
||||
PRTime* validThrough;
|
||||
bool expired;
|
||||
|
||||
private:
|
||||
Context(const Context&); // delete
|
||||
|
@ -319,6 +321,7 @@ VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
|
|||
CERTCertificate* issuerCert, PRTime time,
|
||||
uint16_t maxOCSPLifetimeInDays,
|
||||
const SECItem* encodedResponse,
|
||||
bool& expired,
|
||||
PRTime* thisUpdate,
|
||||
PRTime* validThrough)
|
||||
{
|
||||
|
@ -331,6 +334,9 @@ VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
|
|||
return SECFailure;
|
||||
}
|
||||
|
||||
// Always initialize this to something reasonable.
|
||||
expired = false;
|
||||
|
||||
der::Input input;
|
||||
if (input.Init(encodedResponse->data, encodedResponse->len) != der::Success) {
|
||||
SetErrorToMalformedResponseOnBadDERError();
|
||||
|
@ -351,8 +357,14 @@ VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
|
|||
return SECFailure;
|
||||
}
|
||||
|
||||
expired = context.expired;
|
||||
|
||||
switch (context.certStatus) {
|
||||
case CertStatus::Good:
|
||||
if (expired) {
|
||||
PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
|
||||
return SECFailure;
|
||||
}
|
||||
return SECSuccess;
|
||||
case CertStatus::Revoked:
|
||||
PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0);
|
||||
|
@ -658,8 +670,9 @@ SingleResponse(der::Input& input, Context& context)
|
|||
if (context.time < SLOP) { // prevent underflow
|
||||
return der::Fail(SEC_ERROR_INVALID_ARGS);
|
||||
}
|
||||
|
||||
if (context.time - SLOP > notAfter) {
|
||||
return der::Fail(SEC_ERROR_OCSP_OLD_RESPONSE);
|
||||
context.expired = true;
|
||||
}
|
||||
|
||||
if (!input.AtEnd()) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче