Bug 991815 - Part 1/2 - Allow intermediate OCSP responses up to 1 year old. r=keeler
authorCamilo Viecco <cviecco@mozilla.com>
Fri, 30 May 2014 16:12:36 -0700
changeset 198439 71b7b1f1e87bf233c4d08dd20e04372b1b751549
parent 198438 15838e0f0578d20c4a114a9e038172014029adaa
child 198440 d4fd9141e5ab755c27aae40b7876a0018dac75bb
push idunknown
push userunknown
push dateunknown
reviewerskeeler
bugs991815
milestone32.0a1
Bug 991815 - Part 1/2 - Allow intermediate OCSP responses up to 1 year old. r=keeler
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/pkix/include/pkix/pkix.h
security/pkix/lib/pkixocsp.cpp
--- 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