Bug 991815 - Part 1/2 - Allow intermediate OCSP responses up to 1 year old. r=keeler
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -180,24 +180,34 @@ NSSCertDBTrustDomain::CheckRevocation(
PORT_Assert(cert);
PORT_Assert(issuerCert);
if (!cert || !issuerCert) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
+ // Bug 991815: The BR allow OCSP for intermediates to be up to one year old.
+ // Since this affects EV there is no reason why DV should be more strict
+ // so all intermediatates are allowed to have OCSP responses up to one year
+ // old.
+ uint16_t maxOCSPLifetimeInDays = 10;
+ if (endEntityOrCA == EndEntityOrCA::MustBeCA) {
+ maxOCSPLifetimeInDays = 365;
+ }
+
// If we have a stapled OCSP response then the verification of that response
// 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.
if (stapledOCSPResponse) {
PR_ASSERT(endEntityOrCA == EndEntityOrCA::MustBeEndEntity);
SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert,
time,
+ maxOCSPLifetimeInDays,
stapledOCSPResponse,
ResponseWasStapled);
if (rv == SECSuccess) {
// stapled OCSP response present and good
Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1);
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("NSSCertDBTrustDomain: stapled OCSP response: good"));
return rv;
@@ -371,16 +381,17 @@ NSSCertDBTrustDomain::CheckRevocation(
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("NSSCertDBTrustDomain: returning SECSuccess after "
"OCSP request failure"));
return SECSuccess; // Soft fail -> success :(
}
SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time,
+ maxOCSPLifetimeInDays,
response,
ResponseIsFromNetwork);
if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) {
PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse"));
return rv;
}
@@ -394,23 +405,24 @@ NSSCertDBTrustDomain::CheckRevocation(
("NSSCertDBTrustDomain: end of CheckRevocation"));
return SECSuccess;
}
SECStatus
NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse(
const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
- const SECItem* encodedResponse, EncodedResponseSource responseSource)
+ uint16_t maxLifetimeInDays, const SECItem* encodedResponse,
+ EncodedResponseSource responseSource)
{
PRTime thisUpdate = 0;
PRTime validThrough = 0;
SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
- encodedResponse, &thisUpdate,
- &validThrough);
+ maxLifetimeInDays, encodedResponse,
+ &thisUpdate, &validThrough);
PRErrorCode error = (rv == SECSuccess ? 0 : PR_GetError());
// 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
// repeatedly requesting a response from a failing server).
if (rv != SECSuccess && error != SEC_ERROR_REVOKED_CERTIFICATE &&
error != SEC_ERROR_OCSP_UNKNOWN_CERT) {
validThrough = time + ServerFailureDelay;
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -85,17 +85,18 @@ public:
private:
enum EncodedResponseSource {
ResponseIsFromNetwork = 1,
ResponseWasStapled = 2
};
static const PRTime ServerFailureDelay = 5 * 60 * PR_USEC_PER_SEC;
SECStatus VerifyAndMaybeCacheEncodedOCSPResponse(
const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
- const SECItem* encodedResponse, EncodedResponseSource responseSource);
+ uint16_t maxLifetimeInDays, const SECItem* encodedResponse,
+ EncodedResponseSource responseSource);
const SECTrustType mCertDBTrustType;
const OCSPFetching mOCSPFetching;
OCSPCache& mOCSPCache; // non-owning!
void* mPinArg; // non-owning!
const CertVerifier::ocsp_get_config mOCSPGetConfig;
CERTChainVerifyCallback* mCheckChainCallback; // non-owning!
};
--- a/security/pkix/include/pkix/pkix.h
+++ b/security/pkix/include/pkix/pkix.h
@@ -116,15 +116,16 @@ SECItem* CreateEncodedOCSPRequest(PLAren
// trustworthy. If the response is not trustworthy, thisUpdate will be 0.
// Similarly, the optional parameter validThrough will be the time through
// which the encoded response is considered trustworthy (that is, if a response had a
// thisUpdate time of validThrough, it would be considered trustworthy).
SECStatus VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
const CERTCertificate* cert,
CERTCertificate* issuerCert,
PRTime time,
+ uint16_t maxLifetimeInDays,
const SECItem* encodedResponse,
/* optional out */ PRTime* thisUpdate,
/* optional out */ PRTime* validThrough);
} } // namespace mozilla::pkix
#endif // mozilla_pkix__pkix_h
--- a/security/pkix/lib/pkixocsp.cpp
+++ b/security/pkix/lib/pkixocsp.cpp
@@ -51,38 +51,41 @@ MOZILLA_PKIX_ENUM_CLASS CertStatus : uin
class Context
{
public:
Context(TrustDomain& trustDomain,
const CERTCertificate& cert,
CERTCertificate& issuerCert,
PRTime time,
+ uint16_t maxLifetimeInDays,
PRTime* thisUpdate,
PRTime* validThrough)
: trustDomain(trustDomain)
, cert(cert)
, issuerCert(issuerCert)
, time(time)
+ , maxLifetimeInDays(maxLifetimeInDays)
, certStatus(CertStatus::Unknown)
, thisUpdate(thisUpdate)
, validThrough(validThrough)
{
if (thisUpdate) {
*thisUpdate = 0;
}
if (validThrough) {
*validThrough = 0;
}
}
TrustDomain& trustDomain;
const CERTCertificate& cert;
CERTCertificate& issuerCert;
const PRTime time;
+ const uint16_t maxLifetimeInDays;
CertStatus certStatus;
PRTime* thisUpdate;
PRTime* validThrough;
private:
Context(const Context&); // delete
void operator=(const Context&); // delete
};
@@ -332,16 +335,17 @@ SetErrorToMalformedResponseOnBadDERError
PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0);
}
}
SECStatus
VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
const CERTCertificate* cert,
CERTCertificate* issuerCert, PRTime time,
+ uint16_t maxOCSPLifetimeInDays,
const SECItem* encodedResponse,
PRTime* thisUpdate,
PRTime* validThrough)
{
PR_ASSERT(cert);
PR_ASSERT(issuerCert);
// TODO: PR_Assert(pinArg)
PR_ASSERT(encodedResponse);
@@ -350,19 +354,18 @@ VerifyEncodedOCSPResponse(TrustDomain& t
return SECFailure;
}
der::Input input;
if (input.Init(encodedResponse->data, encodedResponse->len) != der::Success) {
SetErrorToMalformedResponseOnBadDERError();
return SECFailure;
}
-
- Context context(trustDomain, *cert, *issuerCert, time, thisUpdate,
- validThrough);
+ Context context(trustDomain, *cert, *issuerCert, time, maxOCSPLifetimeInDays,
+ thisUpdate, validThrough);
if (der::Nested(input, der::SEQUENCE,
bind(OCSPResponse, _1, ref(context))) != der::Success) {
SetErrorToMalformedResponseOnBadDERError();
return SECFailure;
}
if (der::End(input) != der::Success) {
@@ -667,19 +670,18 @@ SingleResponse(der::Input& input, Contex
// http://tools.ietf.org/html/rfc6960#section-3.2
// 5. The time at which the status being indicated is known to be
// correct (thisUpdate) is sufficiently recent;
// 6. When available, the time at or before which newer information will
// be available about the status of the certificate (nextUpdate) is
// greater than the current time.
- // We won't accept any OCSP responses that are more than 10 days old, even if
- // the nextUpdate time is further in the future.
- static const PRTime OLDEST_ACCEPTABLE = INT64_C(10) * ONE_DAY;
+ const PRTime maxLifetime =
+ context.maxLifetimeInDays * ONE_DAY;
PRTime thisUpdate;
if (der::GeneralizedTime(input, thisUpdate) != der::Success) {
return der::Failure;
}
if (thisUpdate > context.time + SLOP) {
return der::Fail(SEC_ERROR_OCSP_FUTURE_RESPONSE);
@@ -694,20 +696,20 @@ SingleResponse(der::Input& input, Contex
bind(der::GeneralizedTime, _1, ref(nextUpdate)))
!= der::Success) {
return der::Failure;
}
if (nextUpdate < thisUpdate) {
return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
}
- if (nextUpdate - thisUpdate <= OLDEST_ACCEPTABLE) {
+ if (nextUpdate - thisUpdate <= maxLifetime) {
notAfter = nextUpdate;
} else {
- notAfter = thisUpdate + OLDEST_ACCEPTABLE;
+ notAfter = thisUpdate + maxLifetime;
}
} else {
// NSS requires all OCSP responses without a nextUpdate to be recent.
// Match that stricter behavior.
notAfter = thisUpdate + ONE_DAY;
}
if (context.time < SLOP) { // prevent underflow