bug 915932 - cache OCSP responses when using insanity::pkix r=cviecco r=briansmith
authorDavid Keeler <dkeeler@mozilla.com>
Wed, 12 Mar 2014 13:08:48 -0700
changeset 191444 840df518d026f9f7b9bf896fb7ef8d0b3e9fb9da
parent 191443 599d7b0af8936efe00aee3f1292d0664fd9f97b3
child 191445 20e8191247fd97395056ee1e4d7e48e1ba806c40
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscviecco, briansmith
bugs915932
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 915932 - cache OCSP responses when using insanity::pkix r=cviecco r=briansmith
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/certverifier/OCSPCache.cpp
security/certverifier/OCSPCache.h
security/certverifier/moz.build
security/insanity/include/insanity/pkix.h
security/insanity/lib/pkixocsp.cpp
security/manager/ssl/public/nsIX509CertDB.idl
security/manager/ssl/src/SSLServerCertVerification.cpp
security/manager/ssl/src/nsNSSCertificateDB.cpp
security/manager/ssl/src/nsNSSComponent.cpp
security/manager/ssl/tests/gtest/OCSPCacheTest.cpp
security/manager/ssl/tests/gtest/moz.build
security/manager/ssl/tests/unit/head_psm.js
security/manager/ssl/tests/unit/test_ev_certs.js
security/manager/ssl/tests/unit/test_ocsp_caching.js
security/manager/ssl/tests/unit/test_ocsp_required.js
security/manager/ssl/tests/unit/test_ocsp_stapling.js
security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -267,17 +267,18 @@ CertVerifier::InsanityVerifyCert(
   // TODO(bug 915931): Pass in stapled OCSP response in all calls to
   //                   BuildCertChain.
 
   insanity::pkix::ScopedCERTCertList builtChain;
   switch (usage) {
     case certificateUsageSSLClient: {
       // XXX: We don't really have a trust bit for SSL client authentication so
       // just use trustEmail as it is the closest alternative.
-      NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, pinArg);
+      NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
+                                       pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
                           KU_DIGITAL_SIGNATURE,
                           SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH,
                           SEC_OID_X509_ANY_POLICY,
                           stapledOCSPResponse, builtChain);
       break;
     }
 
@@ -291,17 +292,17 @@ CertVerifier::InsanityVerifyCert(
       SECOidTag evPolicy = SEC_OID_UNKNOWN;
       rv = GetFirstEVPolicy(cert, evPolicy);
       if (rv == SECSuccess && evPolicy != SEC_OID_UNKNOWN) {
         NSSCertDBTrustDomain
           trustDomain(trustSSL,
                       ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP
                         ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
                         : NSSCertDBTrustDomain::FetchOCSPForEV,
-                      pinArg);
+                      mOCSPCache, pinArg);
         rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                           KU_DIGITAL_SIGNATURE, // ECDHE/DHE
                                           KU_KEY_ENCIPHERMENT, // RSA
                                           KU_KEY_AGREEMENT, // ECDH/DH
                                           SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
                                           evPolicy, stapledOCSPResponse,
                                           builtChain);
         if (rv == SECSuccess) {
@@ -316,65 +317,69 @@ CertVerifier::InsanityVerifyCert(
 
       if (flags & FLAG_MUST_BE_EV) {
         PR_SetError(SEC_ERROR_POLICY_VALIDATION_FAILED, 0);
         rv = SECFailure;
         break;
       }
 
       // Now try non-EV.
-      NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, pinArg);
+      NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
+                                       pinArg);
       rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                         KU_DIGITAL_SIGNATURE, // ECDHE/DHE
                                         KU_KEY_ENCIPHERMENT, // RSA
                                         KU_KEY_AGREEMENT, // ECDH/DH
                                         SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
                                         SEC_OID_X509_ANY_POLICY,
                                         stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageSSLCA: {
-      NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, pinArg);
+      NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
+                                       pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeCA,
                           KU_KEY_CERT_SIGN,
                           SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
                           SEC_OID_X509_ANY_POLICY,
                           stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageEmailSigner: {
-      NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, pinArg);
+      NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
+                                       pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
                           KU_DIGITAL_SIGNATURE,
                           SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
                           SEC_OID_X509_ANY_POLICY,
                           stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageEmailRecipient: {
       // TODO: The higher level S/MIME processing should pass in which key
       // usage it is trying to verify for, and base its algorithm choices
       // based on the result of the verification(s).
-      NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, pinArg);
+      NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
+                                       pinArg);
       rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                         KU_KEY_ENCIPHERMENT, // RSA
                                         KU_KEY_AGREEMENT, // ECDH/DH
                                         0,
                                         SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
                                         SEC_OID_X509_ANY_POLICY,
                                         stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching,
-                                       pinArg);
+                                       mOCSPCache, pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
                           KU_DIGITAL_SIGNATURE,
                           SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
                           SEC_OID_X509_ANY_POLICY,
                           stapledOCSPResponse, builtChain);
       break;
     }
 
@@ -392,28 +397,31 @@ CertVerifier::InsanityVerifyCert(
         keyUsage = KU_KEY_CERT_SIGN;
         eku = SEC_OID_UNKNOWN;
       } else {
         endEntityOrCA = MustBeEndEntity;
         keyUsage = KU_DIGITAL_SIGNATURE;
         eku = SEC_OID_OCSP_RESPONDER;
       }
 
-      NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, pinArg);
+      NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, mOCSPCache,
+                                    pinArg);
       rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA,
                           keyUsage, eku, SEC_OID_X509_ANY_POLICY,
                           stapledOCSPResponse, builtChain);
       if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) {
-        NSSCertDBTrustDomain emailTrust(trustEmail, ocspFetching, pinArg);
+        NSSCertDBTrustDomain emailTrust(trustEmail, ocspFetching, mOCSPCache,
+                                        pinArg);
         rv = BuildCertChain(emailTrust, cert, time, endEntityOrCA, keyUsage,
                             eku, SEC_OID_X509_ANY_POLICY,
                             stapledOCSPResponse, builtChain);
         if (rv == SECFailure && SEC_ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
-                                                  ocspFetching, pinArg);
+                                                  ocspFetching, mOCSPCache,
+                                                  pinArg);
           rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA,
                               keyUsage, eku, SEC_OID_X509_ANY_POLICY,
                               stapledOCSPResponse, builtChain);
         }
       }
 
       break;
     }
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_psm__CertVerifier_h
 #define mozilla_psm__CertVerifier_h
 
 #include "insanity/pkixtypes.h"
+#include "OCSPCache.h"
 
 namespace mozilla { namespace psm {
 
 class CertVerifier
 {
 public:
   typedef unsigned int Flags;
   // XXX: FLAG_LOCAL_ONLY is ignored in the classic verification case
@@ -62,16 +63,18 @@ public:
   CertVerifier(implementation_config ic,
 #ifndef NSS_NO_LIBPKIX
                missing_cert_download_config ac, crl_download_config cdc,
 #endif
                ocsp_download_config odc, ocsp_strict_config osc,
                ocsp_get_config ogc);
   ~CertVerifier();
 
+  void ClearOCSPCache() { mOCSPCache.Clear(); }
+
   const implementation_config mImplementation;
 #ifndef NSS_NO_LIBPKIX
   const bool mMissingCertDownloadEnabled;
   const bool mCRLDownloadEnabled;
 #endif
   const bool mOCSPDownloadEnabled;
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
@@ -80,14 +83,16 @@ private:
   SECStatus InsanityVerifyCert(CERTCertificate* cert,
       const SECCertificateUsage usage,
       const PRTime time,
       void* pinArg,
       const Flags flags,
       /*optional*/ const SECItem* stapledOCSPResponse,
       /*optional out*/ insanity::pkix::ScopedCERTCertList* validationChain,
       /*optional out*/ SECOidTag* evOidPolicy);
+
+  OCSPCache mOCSPCache;
 };
 
 void InitCertVerifierLog();
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__CertVerifier_h
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -4,18 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "NSSCertDBTrustDomain.h"
 
 #include <stdint.h>
 
 #include "ExtendedValidation.h"
+#include "certdb.h"
 #include "insanity/pkix.h"
-#include "certdb.h"
 #include "nss.h"
 #include "ocsp.h"
 #include "pk11pub.h"
 #include "prerror.h"
 #include "prmem.h"
 #include "prprf.h"
 #include "secerr.h"
 #include "secmod.h"
@@ -25,29 +25,31 @@ using namespace insanity::pkix;
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gCertVerifierLog;
 #endif
 
 namespace mozilla { namespace psm {
 
 const char BUILTIN_ROOTS_MODULE_DEFAULT_NAME[] = "Builtin Roots Module";
 
-namespace {
+void PORT_Free_string(char* str) { PORT_Free(str); }
 
-inline void PORT_Free_string(char* str) { PORT_Free(str); }
+namespace {
 
 typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule;
 
 } // unnamed namespace
 
 NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
                                            OCSPFetching ocspFetching,
+                                           OCSPCache& ocspCache,
                                            void* pinArg)
   : mCertDBTrustType(certDBTrustType)
   , mOCSPFetching(ocspFetching)
+  , mOCSPCache(ocspCache)
   , mPinArg(pinArg)
 {
 }
 
 SECStatus
 NSSCertDBTrustDomain::FindPotentialIssuers(
   const SECItem* encodedIssuerName, PRTime time,
   /*out*/ insanity::pkix::ScopedCERTCertList& results)
@@ -156,54 +158,116 @@ NSSCertDBTrustDomain::CheckRevocation(
   }
 
   // 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 == MustBeEndEntity);
-    SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
-                                             stapledOCSPResponse);
+    SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert,
+                                                          time,
+                                                          stapledOCSPResponse);
     if (rv == SECSuccess) {
+      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+             ("NSSCertDBTrustDomain: stapled OCSP response: good"));
       return rv;
     }
     if (PR_GetError() != SEC_ERROR_OCSP_OLD_RESPONSE) {
+      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+             ("NSSCertDBTrustDomain: stapled OCSP response: failure"));
       return rv;
     }
+  } else {
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+           ("NSSCertDBTrustDomain: no stapled OCSP response"));
   }
 
-  // TODO(bug 915932): Need to change this when we add the OCSP cache.
+  PRErrorCode cachedResponseErrorCode = 0;
+  PRTime cachedResponseValidThrough = 0;
+  bool cachedResponsePresent = mOCSPCache.Get(cert, issuerCert,
+                                              cachedResponseErrorCode,
+                                              cachedResponseValidThrough);
+  if (cachedResponsePresent) {
+    if (cachedResponseErrorCode == 0 && cachedResponseValidThrough >= time) {
+      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+             ("NSSCertDBTrustDomain: cached OCSP response: good"));
+      return SECSuccess;
+    }
+    // If we have a cached revoked response, use it.
+    if (cachedResponseErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
+      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+             ("NSSCertDBTrustDomain: cached OCSP response: revoked"));
+      PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0);
+      return SECFailure;
+    }
+    // The cached response may indicate an unknown certificate or it may be
+    // expired. Don't return with either of these statuses yet - we may be
+    // able to fetch a more recent one.
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+           ("NSSCertDBTrustDomain: cached OCSP response: error %ld valid "
+           "until %lld", cachedResponseErrorCode, cachedResponseValidThrough));
+    // When a good cached response has expired, it is more convenient
+    // to convert that to an error code and just deal with
+    // cachedResponseErrorCode from here on out.
+    if (cachedResponseErrorCode == 0 && cachedResponseValidThrough < time) {
+      cachedResponseErrorCode = SEC_ERROR_OCSP_OLD_RESPONSE;
+    }
+  } else {
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+           ("NSSCertDBTrustDomain: no cached OCSP response"));
+  }
+  // At this point, if and only if cachedErrorResponseCode is 0, there was no
+  // cached response.
+  PR_ASSERT((!cachedResponsePresent && cachedResponseErrorCode == 0) ||
+            (cachedResponsePresent && cachedResponseErrorCode != 0));
 
   // TODO: We still need to handle the fallback for expired responses. But,
   // if/when we disable OCSP fetching by default, it would be ambiguous whether
   // security.OCSP.enable==0 means "I want the default" or "I really never want
   // you to ever fetch OCSP."
 
   if ((mOCSPFetching == NeverFetchOCSP) ||
       (endEntityOrCA == MustBeCA && (mOCSPFetching == FetchOCSPForDVHardFail ||
                                      mOCSPFetching == FetchOCSPForDVSoftFail))) {
+    // We're not going to be doing any fetching, so if there was a cached
+    // "unknown" response, say so.
+    if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
+      PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
+      return SECFailure;
+    }
+    // If we're doing hard-fail, we want to know if we have a cached response
+    // that has expired.
+    if (mOCSPFetching == FetchOCSPForDVHardFail &&
+        cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
+      PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
+      return SECFailure;
+    }
+
     return SECSuccess;
   }
 
   if (mOCSPFetching == LocalOnlyOCSPForEV) {
-    PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
+    PR_SetError(cachedResponseErrorCode != 0 ? cachedResponseErrorCode
+                                             : SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
     return SECFailure;
   }
 
   ScopedPtr<char, PORT_Free_string>
     url(CERT_GetOCSPAuthorityInfoAccessLocation(cert));
 
   if (!url) {
-    if (stapledOCSPResponse) {
-      PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
+    if (mOCSPFetching == FetchOCSPForEV ||
+        cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
+      PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
       return SECFailure;
     }
-    if (mOCSPFetching == FetchOCSPForEV) {
-      PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
+    if (stapledOCSPResponse ||
+        cachedResponseErrorCode == SEC_ERROR_OCSP_OLD_RESPONSE) {
+      PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 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,
     // interpretation of "strict" revocation checking in the face of a
     // certificate that lacks an OCSP responder URI.
     return SECSuccess;
@@ -224,25 +288,32 @@ NSSCertDBTrustDomain::CheckRevocation(
                                                request));
   if (!response) {
     if (mOCSPFetching != FetchOCSPForDVSoftFail) {
       PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
              ("NSSCertDBTrustDomain: returning SECFailure after "
               "CERT_PostOCSPRequest failure"));
       return SECFailure;
     }
+    if (cachedResponseErrorCode == SEC_ERROR_OCSP_UNKNOWN_CERT) {
+      PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+             ("NSSCertDBTrustDomain: returning SECFailure from cached "
+              "response after CERT_PostOCSPRequest failure"));
+      PR_SetError(cachedResponseErrorCode, 0);
+      return SECFailure;
+    }
 
     PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
            ("NSSCertDBTrustDomain: returning SECSuccess after "
             "CERT_PostOCSPRequest failure"));
     return SECSuccess; // Soft fail -> success :(
   }
 
-  SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
-                                           response);
+  SECStatus rv = VerifyAndMaybeCacheEncodedOCSPResponse(cert, issuerCert, time,
+                                                        response);
   if (rv == SECSuccess || mOCSPFetching != FetchOCSPForDVSoftFail) {
     PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
       ("NSSCertDBTrustDomain: returning after VerifyEncodedOCSPResponse"));
     return rv;
   }
 
   PRErrorCode error = PR_GetError();
   if (error == SEC_ERROR_OCSP_UNKNOWN_CERT ||
@@ -251,16 +322,41 @@ NSSCertDBTrustDomain::CheckRevocation(
   }
 
   PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
          ("NSSCertDBTrustDomain: end of CheckRevocation"));
 
   return SECSuccess;
 }
 
+SECStatus
+NSSCertDBTrustDomain::VerifyAndMaybeCacheEncodedOCSPResponse(
+  const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
+  const SECItem* encodedResponse)
+{
+  PRTime thisUpdate = 0;
+  PRTime validThrough = 0;
+  SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
+                                           encodedResponse, &thisUpdate,
+                                           &validThrough);
+  PRErrorCode error = (rv == SECSuccess ? 0 : PR_GetError());
+  if (rv == SECSuccess || error == SEC_ERROR_REVOKED_CERTIFICATE ||
+      error == SEC_ERROR_OCSP_UNKNOWN_CERT) {
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+           ("NSSCertDBTrustDomain: caching OCSP response"));
+    if (mOCSPCache.Put(cert, issuerCert, error, thisUpdate, validThrough)
+          != SECSuccess) {
+      return SECFailure;
+    }
+    // The call to Put may have un-set the error. Re-set it.
+    PR_SetError(error, 0);
+  }
+  return rv;
+}
+
 namespace {
 
 static char*
 nss_addEscape(const char* string, char quote)
 {
   char* newString = 0;
   int escapes = 0, size = 0;
   const char* src;
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -14,16 +14,18 @@
 namespace mozilla { namespace psm {
 
 SECStatus InitializeNSS(const char* dir, bool readOnly);
 
 void DisableMD5();
 
 extern const char BUILTIN_ROOTS_MODULE_DEFAULT_NAME[];
 
+void PORT_Free_string(char* str);
+
 // The dir parameter is the path to the directory containing the NSS builtin
 // roots module. Usually this is the same as the path to the other NSS shared
 // libraries. If it is null then the (library) path will be searched.
 //
 // The modNameUTF8 parameter should usually be
 // BUILTIN_ROOTS_MODULE_DEFAULT_NAME.
 SECStatus LoadLoadableRoots(/*optional*/ const char* dir,
                             const char* modNameUTF8);
@@ -50,17 +52,17 @@ public:
   enum OCSPFetching {
     NeverFetchOCSP = 0,
     FetchOCSPForDVSoftFail = 1,
     FetchOCSPForDVHardFail = 2,
     FetchOCSPForEV = 3,
     LocalOnlyOCSPForEV = 4,
   };
   NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching,
-                       void* pinArg);
+                       OCSPCache& ocspCache, void* pinArg);
 
   virtual SECStatus FindPotentialIssuers(
                         const SECItem* encodedIssuerName,
                         PRTime time,
                 /*out*/ insanity::pkix::ScopedCERTCertList& results);
 
   virtual SECStatus GetCertTrust(insanity::pkix::EndEntityOrCA endEntityOrCA,
                                  SECOidTag policy,
@@ -72,16 +74,21 @@ public:
 
   virtual SECStatus CheckRevocation(insanity::pkix::EndEntityOrCA endEntityOrCA,
                                     const CERTCertificate* cert,
                           /*const*/ CERTCertificate* issuerCert,
                                     PRTime time,
                        /*optional*/ const SECItem* stapledOCSPResponse);
 
 private:
+  SECStatus VerifyAndMaybeCacheEncodedOCSPResponse(
+    const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
+    const SECItem* encodedResponse);
+
   const SECTrustType mCertDBTrustType;
   const OCSPFetching mOCSPFetching;
+  OCSPCache& mOCSPCache; // non-owning!
   void* mPinArg; // non-owning!
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__NSSCertDBTrustDomain_h
new file mode 100644
--- /dev/null
+++ b/security/certverifier/OCSPCache.cpp
@@ -0,0 +1,289 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Copyright 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "OCSPCache.h"
+
+#include "NSSCertDBTrustDomain.h"
+#include "pk11pub.h"
+#include "secerr.h"
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* gCertVerifierLog;
+#endif
+
+namespace mozilla { namespace psm {
+
+void
+Insanity_PK11_DestroyContext_true(PK11Context* context)
+{
+  PK11_DestroyContext(context, true);
+}
+
+typedef insanity::pkix::ScopedPtr<PK11Context,
+                                  Insanity_PK11_DestroyContext_true>
+                                  ScopedPK11Context;
+
+// Let derIssuer be the DER encoding of the issuer of aCert.
+// Let derPublicKey be the DER encoding of the public key of aIssuerCert.
+// Let serialNumber be the bytes of the serial number of aCert.
+// The value calculated is SHA384(derIssuer || derPublicKey || serialNumber).
+// Because the DER encodings include the length of the data encoded,
+// there do not exist A(derIssuerA, derPublicKeyA, serialNumberA) and
+// B(derIssuerB, derPublicKeyB, serialNumberB) such that the concatenation of
+// each triplet results in the same string of bytes but where each part in A is
+// not equal to its counterpart in B. This is important because as a result it
+// is computationally infeasible to find collisions that would subvert this
+// cache (given that SHA384 is a cryptographically-secure hash function).
+static SECStatus
+CertIDHash(SHA384Buffer& buf, const CERTCertificate* aCert,
+       const CERTCertificate* aIssuerCert)
+{
+  ScopedPK11Context context(PK11_CreateDigestContext(SEC_OID_SHA384));
+  if (!context) {
+    return SECFailure;
+  }
+  SECStatus rv = PK11_DigestBegin(context.get());
+  if (rv != SECSuccess) {
+    return rv;
+  }
+  rv = PK11_DigestOp(context.get(), aCert->derIssuer.data,
+                     aCert->derIssuer.len);
+  if (rv != SECSuccess) {
+    return rv;
+  }
+  rv = PK11_DigestOp(context.get(), aIssuerCert->derPublicKey.data,
+                     aIssuerCert->derPublicKey.len);
+  if (rv != SECSuccess) {
+    return rv;
+  }
+  rv = PK11_DigestOp(context.get(), aCert->serialNumber.data,
+                     aCert->serialNumber.len);
+  if (rv != SECSuccess) {
+    return rv;
+  }
+  uint32_t outLen = 0;
+  rv = PK11_DigestFinal(context.get(), buf, &outLen, SHA384_LENGTH);
+  if (outLen != SHA384_LENGTH) {
+    return SECFailure;
+  }
+  return rv;
+}
+
+SECStatus
+OCSPCache::Entry::Init(const CERTCertificate* aCert,
+                       const CERTCertificate* aIssuerCert,
+                       PRErrorCode aErrorCode,
+                       PRTime aThisUpdate,
+                       PRTime aValidThrough)
+{
+  mErrorCode = aErrorCode;
+  mThisUpdate = aThisUpdate;
+  mValidThrough = aValidThrough;
+  return CertIDHash(mIDHash, aCert, aIssuerCert);
+}
+
+OCSPCache::OCSPCache()
+  : mMutex("OCSPCache-mutex")
+{
+}
+
+OCSPCache::~OCSPCache()
+{
+  Clear();
+}
+
+// Returns -1 if no entry is found for the given (cert, issuer) pair.
+int32_t
+OCSPCache::FindInternal(const CERTCertificate* aCert,
+                        const CERTCertificate* aIssuerCert,
+                        const MutexAutoLock& /* aProofOfLock */)
+{
+  if (mEntries.length() == 0) {
+    return -1;
+  }
+
+  SHA384Buffer idHash;
+  SECStatus rv = CertIDHash(idHash, aCert, aIssuerCert);
+  if (rv != SECSuccess) {
+    return -1;
+  }
+
+  // mEntries is sorted with the most-recently-used entry at the end.
+  // Thus, searching from the end will often be fastest.
+  for (int32_t i = mEntries.length() - 1; i >= 0; i--) {
+    if (memcmp(mEntries[i]->mIDHash, idHash, SHA384_LENGTH) == 0) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+void
+OCSPCache::LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
+                        const CERTCertificate* aIssuerCert)
+{
+#ifdef PR_LOGGING
+  if (PR_LOG_TEST(gCertVerifierLog, PR_LOG_DEBUG)) {
+    insanity::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
+      cn(CERT_GetCommonName(&aCert->subject));
+    insanity::pkix::ScopedPtr<char, mozilla::psm::PORT_Free_string>
+      cnIssuer(CERT_GetCommonName(&aIssuerCert->subject));
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, (aMessage, cn.get(), cnIssuer.get()));
+  }
+#endif
+}
+
+void
+OCSPCache::MakeMostRecentlyUsed(size_t aIndex,
+                                const MutexAutoLock& /* aProofOfLock */)
+{
+  Entry* entry = mEntries[aIndex];
+  // Since mEntries is sorted with the most-recently-used entry at the end,
+  // aIndex is likely to be near the end, so this is likely to be fast.
+  mEntries.erase(mEntries.begin() + aIndex);
+  mEntries.append(entry);
+}
+
+bool
+OCSPCache::Get(const CERTCertificate* aCert,
+               const CERTCertificate* aIssuerCert,
+               PRErrorCode& aErrorCode,
+               PRTime& aValidThrough)
+{
+  PR_ASSERT(aCert);
+  PR_ASSERT(aIssuerCert);
+
+  MutexAutoLock lock(mMutex);
+
+  int32_t index = FindInternal(aCert, aIssuerCert, lock);
+  if (index < 0) {
+    LogWithCerts("OCSPCache::Get(%s, %s) not in cache", aCert, aIssuerCert);
+    return false;
+  }
+  LogWithCerts("OCSPCache::Get(%s, %s) in cache", aCert, aIssuerCert);
+  aErrorCode = mEntries[index]->mErrorCode;
+  aValidThrough = mEntries[index]->mValidThrough;
+  MakeMostRecentlyUsed(index, lock);
+  return true;
+}
+
+SECStatus
+OCSPCache::Put(const CERTCertificate* aCert,
+               const CERTCertificate* aIssuerCert,
+               PRErrorCode aErrorCode,
+               PRTime aThisUpdate,
+               PRTime aValidThrough)
+{
+  PR_ASSERT(aCert);
+  PR_ASSERT(aIssuerCert);
+
+  MutexAutoLock lock(mMutex);
+
+  int32_t index = FindInternal(aCert, aIssuerCert, lock);
+
+  if (index >= 0) {
+    // Never replace an entry indicating a revoked certificate.
+    if (mEntries[index]->mErrorCode == SEC_ERROR_REVOKED_CERTIFICATE) {
+      LogWithCerts("OCSPCache::Put(%s, %s) already in cache as revoked - "
+                   "not replacing", aCert, aIssuerCert);
+      MakeMostRecentlyUsed(index, lock);
+      return SECSuccess;
+    }
+
+    // Never replace a newer entry with an older one unless the older entry
+    // indicates a revoked certificate, which we want to remember.
+    if (mEntries[index]->mThisUpdate > aThisUpdate &&
+        aErrorCode != SEC_ERROR_REVOKED_CERTIFICATE) {
+      LogWithCerts("OCSPCache::Put(%s, %s) already in cache with more recent "
+                   "validity - not replacing", aCert, aIssuerCert);
+      MakeMostRecentlyUsed(index, lock);
+      return SECSuccess;
+    }
+
+    LogWithCerts("OCSPCache::Put(%s, %s) already in cache - replacing",
+                 aCert, aIssuerCert);
+    mEntries[index]->mErrorCode = aErrorCode;
+    mEntries[index]->mThisUpdate = aThisUpdate;
+    mEntries[index]->mValidThrough = aValidThrough;
+    MakeMostRecentlyUsed(index, lock);
+    return SECSuccess;
+  }
+
+  if (mEntries.length() == MaxEntries) {
+    LogWithCerts("OCSPCache::Put(%s, %s) too full - evicting an entry", aCert,
+                 aIssuerCert);
+    for (Entry** toEvict = mEntries.begin(); toEvict != mEntries.end();
+         toEvict++) {
+      // Never evict an entry that indicates a revoked or unknokwn certificate,
+      // because revoked responses are more security-critical to remember.
+      if ((*toEvict)->mErrorCode != SEC_ERROR_REVOKED_CERTIFICATE &&
+          (*toEvict)->mErrorCode != SEC_ERROR_OCSP_UNKNOWN_CERT) {
+        delete *toEvict;
+        mEntries.erase(toEvict);
+        break;
+      }
+    }
+    // Well, we tried, but apparently everything is revoked or unknown.
+    // We don't want to remove a cached revoked or unknown response. If we're
+    // trying to insert a good response, we can just return "successfully"
+    // without doing so. This means we'll lose some speed, but it's not a
+    // security issue. If we're trying to insert a revoked or unknown response,
+    // we can't. We should return with an error that causes the current
+    // verification to fail.
+    if (mEntries.length() == MaxEntries) {
+      if (aErrorCode != 0) {
+        PR_SetError(aErrorCode, 0);
+        return SECFailure;
+      }
+      return SECSuccess;
+    }
+  }
+
+  Entry* newEntry = new Entry();
+  // Normally we don't have to do this in Gecko, because OOM is fatal.
+  // However, if we want to embed this in another project, OOM might not
+  // be fatal, so handle this case.
+  if (!newEntry) {
+    PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+    return SECFailure;
+  }
+  SECStatus rv = newEntry->Init(aCert, aIssuerCert, aErrorCode, aThisUpdate,
+                                aValidThrough);
+  if (rv != SECSuccess) {
+    return rv;
+  }
+  mEntries.append(newEntry);
+  LogWithCerts("OCSPCache::Put(%s, %s) added to cache", aCert, aIssuerCert);
+  return SECSuccess;
+}
+
+void
+OCSPCache::Clear()
+{
+  MutexAutoLock lock(mMutex);
+  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("OCSPCache::Clear: clearing cache"));
+  // First go through and delete the memory being pointed to by the pointers
+  // in the vector.
+  for (Entry** entry = mEntries.begin(); entry < mEntries.end();
+       entry++) {
+    delete *entry;
+  }
+  // Then remove the pointers themselves.
+  mEntries.clearAndFree();
+}
+
+} } // namespace mozilla::psm
new file mode 100644
--- /dev/null
+++ b/security/certverifier/OCSPCache.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* Copyright 2013 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef mozilla_psm_OCSPCache_h
+#define mozilla_psm_OCSPCache_h
+
+#include "certt.h"
+#include "hasht.h"
+#include "insanity/pkixtypes.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Vector.h"
+#include "prerror.h"
+
+namespace mozilla { namespace psm {
+
+// make SHA384Buffer be of type "array of uint8_t of length SHA384_LENGTH"
+typedef uint8_t SHA384Buffer[SHA384_LENGTH];
+
+// OCSPCache can store and retrieve OCSP response verification results. Each
+// result is keyed on the certificate that purportedly corresponds to it (where
+// certificates are distinguished based on serial number, issuer, and
+// issuer public key, much like in an encoded OCSP response itself). A maximum
+// of 1024 distinct entries can be stored.
+// OCSPCache is thread-safe.
+class OCSPCache
+{
+public:
+  OCSPCache();
+  ~OCSPCache();
+
+  // Returns true if the status of the given certificate (issued by the given
+  // issuer) is in the cache, and false otherwise.
+  // If it is in the cache, returns by reference the error code of the cached
+  // status and the time through which the status is considered trustworthy.
+  bool Get(const CERTCertificate* aCert, const CERTCertificate* aIssuerCert,
+           /* out */ PRErrorCode& aErrorCode, /* out */ PRTime& aValidThrough);
+
+  // Caches the status of the given certificate (issued by the given issuer).
+  // The status is considered trustworthy through the given time.
+  // A status with an error code of SEC_ERROR_REVOKED_CERTIFICATE will not
+  // be replaced or evicted.
+  // A status with an error code of SEC_ERROR_OCSP_UNKNOWN_CERT will not
+  // be evicted when the cache is full.
+  // A status with a more recent thisUpdate will not be replaced with a
+  // status with a less recent thisUpdate unless the less recent status
+  // indicates the certificate is revoked.
+  SECStatus Put(const CERTCertificate* aCert,
+                const CERTCertificate* aIssuerCert,
+                PRErrorCode aErrorCode,
+                PRTime aThisUpdate,
+                PRTime aValidThrough);
+
+  // Removes everything from the cache.
+  void Clear();
+
+private:
+  class Entry
+  {
+  public:
+    SECStatus Init(const CERTCertificate* aCert,
+                   const CERTCertificate* aIssuerCert,
+                   PRErrorCode aErrorCode, PRTime aThisUpdate,
+                   PRTime aValidThrough);
+
+    PRErrorCode mErrorCode;
+    PRTime mThisUpdate;
+    PRTime mValidThrough;
+    // The SHA-384 hash of the concatenation of the DER encodings of the
+    // issuer name and issuer key, followed by the serial number.
+    // See the documentation for CertIDHash in OCSPCache.cpp.
+    SHA384Buffer mIDHash;
+  };
+
+  int32_t FindInternal(const CERTCertificate* aCert,
+                       const CERTCertificate* aIssuerCert,
+                       const MutexAutoLock& aProofOfLock);
+  void MakeMostRecentlyUsed(size_t aIndex, const MutexAutoLock& aProofOfLock);
+  void LogWithCerts(const char* aMessage, const CERTCertificate* aCert,
+                    const CERTCertificate* aIssuerCert);
+
+  Mutex mMutex;
+  static const size_t MaxEntries = 1024;
+  // Sorted with the most-recently-used entry at the end.
+  // Using 256 here reserves as much possible inline storage as the vector
+  // implementation will give us. 1024 bytes is the maximum it allows,
+  // which results in 256 Entry pointers or 128 Entry pointers, depending
+  // on the size of a pointer.
+  Vector<Entry*, 256> mEntries;
+};
+
+} } // namespace mozilla::psm
+
+#endif // mozilla_psm_OCSPCache_h
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 UNIFIED_SOURCES += [
     'CertVerifier.cpp',
     'NSSCertDBTrustDomain.cpp',
+    'OCSPCache.cpp',
 ]
 
 if not CONFIG['NSS_NO_EV_CERTS']:
     UNIFIED_SOURCES += [
         'ExtendedValidation.cpp',
     ]
 
 LOCAL_INCLUDES += [
--- a/security/insanity/include/insanity/pkix.h
+++ b/security/insanity/include/insanity/pkix.h
@@ -98,17 +98,26 @@ SECStatus VerifySignedData(const CERTSig
                            const CERTCertificate* cert,
                            void* pkcs11PinArg);
 
 // The return value, if non-null, is owned by the arena and MUST NOT be freed.
 SECItem* CreateEncodedOCSPRequest(PLArenaPool* arena,
                                   const CERTCertificate* cert,
                                   const CERTCertificate* issuerCert);
 
+// 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
+// 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,
-                                    const SECItem* encodedResponse);
+                                    const SECItem* encodedResponse,
+                 /* optional out */ PRTime* thisUpdate,
+                 /* optional out */ PRTime* validThrough);
 
 } } // namespace insanity::pkix
 
 #endif // insanity_pkix__pkix_h
--- a/security/insanity/lib/pkixocsp.cpp
+++ b/security/insanity/lib/pkixocsp.cpp
@@ -50,30 +50,42 @@ ENUM_CLASS CertStatus : uint8_t {
 };
 
 class Context
 {
 public:
   Context(TrustDomain& trustDomain,
           const CERTCertificate& cert,
           CERTCertificate& issuerCert,
-          PRTime time)
+          PRTime time,
+          PRTime* thisUpdate,
+          PRTime* validThrough)
     : trustDomain(trustDomain)
     , cert(cert)
     , issuerCert(issuerCert)
     , time(time)
     , 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;
   CertStatus certStatus;
+  PRTime* thisUpdate;
+  PRTime* validThrough;
 
 private:
   Context(const Context&); // delete
   void operator=(const Context&); // delete
 };
 
 // Verify that potentialSigner is a valid delegated OCSP response signing cert
 // according to RFC 6960 section 4.2.2.2.
@@ -298,33 +310,36 @@ VerifySignature(Context& context, Respon
 
   return SECSuccess;
 }
 
 SECStatus
 VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
                           const CERTCertificate* cert,
                           CERTCertificate* issuerCert, PRTime time,
-                          const SECItem* encodedResponse)
+                          const SECItem* encodedResponse,
+                          PRTime* thisUpdate,
+                          PRTime* validThrough)
 {
   PR_ASSERT(cert);
   PR_ASSERT(issuerCert);
   // TODO: PR_Assert(pinArg)
   PR_ASSERT(encodedResponse);
   if (!cert || !issuerCert || !encodedResponse || !encodedResponse->data) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
   }
 
   der::Input input;
   if (input.Init(encodedResponse->data, encodedResponse->len) != der::Success) {
     return SECFailure;
   }
 
-  Context context(trustDomain, *cert, *issuerCert, time);
+  Context context(trustDomain, *cert, *issuerCert, time, thisUpdate,
+                  validThrough);
 
   if (der::Nested(input, der::SEQUENCE,
                   bind(OCSPResponse, _1, ref(context))) != der::Success) {
     return SECFailure;
   }
 
   if (der::End(input) != der::Success) {
     return SECFailure;
@@ -679,16 +694,23 @@ SingleResponse(der::Input& input, Contex
 
 
   if (!input.AtEnd()) {
     if (CheckExtensionsForCriticality(input) != der::Success) {
       return der::Failure;
     }
   }
 
+  if (context.thisUpdate) {
+    *context.thisUpdate = thisUpdate;
+  }
+  if (context.validThrough) {
+    *context.validThrough = notAfter;
+  }
+
   return der::Success;
 }
 
 // CertID          ::=     SEQUENCE {
 //        hashAlgorithm       AlgorithmIdentifier,
 //        issuerNameHash      OCTET STRING, -- Hash of issuer's DN
 //        issuerKeyHash       OCTET STRING, -- Hash of issuer's public key
 //        serialNumber        CertificateSerialNumber }
--- a/security/manager/ssl/public/nsIX509CertDB.idl
+++ b/security/manager/ssl/public/nsIX509CertDB.idl
@@ -16,17 +16,17 @@ interface nsIRecentBadCerts;
 interface nsIX509CertList;
 
 %{C++
 #define NS_X509CERTDB_CONTRACTID "@mozilla.org/security/x509certdb;1"
 %}
 
 typedef uint32_t AppTrustedRoot;
 
-[scriptable, function, uuid(e12aec59-7153-4e84-9376-2e851311b7a3)]
+[scriptable, function, uuid(0927baea-622d-4e41-a76d-255af426e7fb)]
 interface nsIOpenSignedAppFileCallback : nsISupports
 {
   void openSignedAppFileFinished(in nsresult rv,
                                  in nsIZipReader aZipReader,
                                  in nsIX509Cert3 aSignerCert);
 };
 
 /**
@@ -353,9 +353,12 @@ interface nsIX509CertDB : nsISupports {
    */
   int32_t /*PRErrorCode*/
     verifyCertNow(in nsIX509Cert aCert,
                   in int64_t /*SECCertificateUsage*/ aUsage,
                   in uint32_t aFlags,
                   out nsIX509CertList verifiedChain,
                   out bool aHasEVPolicy);
 
+  // Clears the OCSP cache for the current certificate verification
+  // implementation.
+  void clearOCSPCache();
 };
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -904,61 +904,63 @@ AuthCertificate(CertVerifier& certVerifi
 {
   MOZ_ASSERT(infoObject);
   MOZ_ASSERT(cert);
 
   SECStatus rv;
 
   // TODO: Remove this after we switch to insanity::pkix as the
   // only option
-  if (stapledOCSPResponse) {
-    CERTCertDBHandle* handle = CERT_GetDefaultCertDB();
-    rv = CERT_CacheOCSPResponseFromSideChannel(handle, cert, PR_Now(),
-                                               stapledOCSPResponse,
-                                               infoObject);
-    if (rv != SECSuccess) {
-      // Due to buggy servers that will staple expired OCSP responses
-      // (see for example http://trac.nginx.org/nginx/ticket/425),
-      // don't terminate the connection if the stapled response is expired.
-      // We will fall back to fetching revocation information.
-      PRErrorCode ocspErrorCode = PR_GetError();
-      if (ocspErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE) {
-        // stapled OCSP response present but invalid for some reason
-        Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4);
-        return rv;
+  if (certVerifier.mImplementation == CertVerifier::classic) {
+    if (stapledOCSPResponse) {
+      CERTCertDBHandle* handle = CERT_GetDefaultCertDB();
+      rv = CERT_CacheOCSPResponseFromSideChannel(handle, cert, PR_Now(),
+                                                 stapledOCSPResponse,
+                                                 infoObject);
+      if (rv != SECSuccess) {
+        // Due to buggy servers that will staple expired OCSP responses
+        // (see for example http://trac.nginx.org/nginx/ticket/425),
+        // don't terminate the connection if the stapled response is expired.
+        // We will fall back to fetching revocation information.
+        PRErrorCode ocspErrorCode = PR_GetError();
+        if (ocspErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE) {
+          // stapled OCSP response present but invalid for some reason
+          Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4);
+          return rv;
+        } else {
+          // stapled OCSP response present but expired
+          Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3);
+        }
       } else {
-        // stapled OCSP response present but expired
-        Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3);
+        // stapled OCSP response present and good
+        Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1);
       }
     } else {
-      // stapled OCSP response present and good
-      Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1);
-    }
-  } else {
-    // no stapled OCSP response
-    Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 2);
+      // no stapled OCSP response
+      Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 2);
 
-    uint32_t reasonsForNotFetching = 0;
+      uint32_t reasonsForNotFetching = 0;
 
-    char* ocspURI = CERT_GetOCSPAuthorityInfoAccessLocation(cert);
-    if (!ocspURI) {
-      reasonsForNotFetching |= 1; // invalid/missing OCSP URI
-    } else {
-      if (std::strncmp(ocspURI, "http://", 7)) { // approximation
+      char* ocspURI = CERT_GetOCSPAuthorityInfoAccessLocation(cert);
+      if (!ocspURI) {
         reasonsForNotFetching |= 1; // invalid/missing OCSP URI
+      } else {
+        if (std::strncmp(ocspURI, "http://", 7)) { // approximation
+          reasonsForNotFetching |= 1; // invalid/missing OCSP URI
+        }
+        PORT_Free(ocspURI);
       }
-      PORT_Free(ocspURI);
-    }
 
-    if (!certVerifier.mOCSPDownloadEnabled) {
-      reasonsForNotFetching |= 2;
+      if (!certVerifier.mOCSPDownloadEnabled) {
+        reasonsForNotFetching |= 2;
+      }
+
+      Telemetry::Accumulate(Telemetry::SSL_OCSP_MAY_FETCH,
+                            reasonsForNotFetching);
     }
-
-    Telemetry::Accumulate(Telemetry::SSL_OCSP_MAY_FETCH,
-                          reasonsForNotFetching);
   }
 
   // We want to avoid storing any intermediate cert information when browsing
   // in private, transient contexts.
   bool saveIntermediates =
     !(providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE);
 
   insanity::pkix::ScopedCERTCertList certList;
--- a/security/manager/ssl/src/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/src/nsNSSCertificateDB.cpp
@@ -1795,8 +1795,30 @@ nsNSSCertificateDB::VerifyCertNow(nsIX50
     NS_ENSURE_TRUE(evOidPolicy == SEC_OID_UNKNOWN, NS_ERROR_FAILURE);
     NS_ENSURE_TRUE(error != 0, NS_ERROR_FAILURE);
     *_retval = error;
   }
   nssCertList.forget(verifiedChain);
 
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsNSSCertificateDB::ClearOCSPCache()
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+  NS_ENSURE_TRUE(certVerifier, NS_ERROR_FAILURE);
+  if (certVerifier->mImplementation == CertVerifier::insanity) {
+    certVerifier->ClearOCSPCache();
+  } else {
+    SECStatus srv = CERT_ClearOCSPCache();
+    if (srv != SECSuccess) {
+      return MapSECStatus(srv);
+    }
+  }
+
+  return NS_OK;
+}
--- a/security/manager/ssl/src/nsNSSComponent.cpp
+++ b/security/manager/ssl/src/nsNSSComponent.cpp
@@ -992,16 +992,28 @@ void nsNSSComponent::setValidationOption
 #ifndef NSS_NO_LIBPKIX
       aiaDownloadEnabled ?
         CertVerifier::missing_cert_download_on : CertVerifier::missing_cert_download_off,
       crlDownloading ?
         CertVerifier::crl_download_allowed : CertVerifier::crl_local_only,
 #endif
       odc, osc, ogc);
 
+  // insanity::pkix has its own OCSP cache, so disable the NSS cache
+  // if appropriate.
+  if (certVerifierImplementation == CertVerifier::insanity) {
+    // Using -1 disables the cache. The other arguments are the default
+    // values and aren't exposed by the API.
+    CERT_OCSPCacheSettings(-1, 1*60*60L, 24*60*60L);
+  } else {
+    // Using 1000 enables the cache with the default size of 1000. Again,
+    // these values are not exposed by the API.
+    CERT_OCSPCacheSettings(1000, 1*60*60L, 24*60*60L);
+  }
+
   CERT_ClearOCSPCache();
 }
 
 // Enable the TLS versions given in the prefs, defaulting to SSL 3.0 (min
 // version) and TLS 1.2 (max version) when the prefs aren't set or set to
 // invalid values.
 nsresult
 nsNSSComponent::setEnabledTLSVersions()
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/gtest/OCSPCacheTest.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CertVerifier.h"
+#include "OCSPCache.h"
+#include "nss.h"
+#include "prprf.h"
+#include "secerr.h"
+
+#include "gtest/gtest.h"
+
+const int MaxCacheEntries = 1024;
+
+class OCSPCacheTest : public ::testing::Test
+{
+  protected:
+    static void SetUpTestCase()
+    {
+      NSS_NoDB_Init(nullptr);
+      mozilla::psm::InitCertVerifierLog();
+    }
+
+    mozilla::psm::OCSPCache cache;
+};
+
+// Makes a fake certificate with just the fields we need for testing here.
+// (And those values are almost entirely bogus.)
+// stackCert should be stack-allocated memory.
+static void
+MakeFakeCert(CERTCertificate* stackCert, const char* subjectValue,
+             const char* issuerValue, const char* serialNumberValue,
+             const char* publicKeyValue)
+{
+  stackCert->derSubject.data = (unsigned char*)subjectValue;
+  stackCert->derSubject.len = strlen(subjectValue);
+  stackCert->derIssuer.data = (unsigned char*)issuerValue;
+  stackCert->derIssuer.len = strlen(issuerValue);
+  stackCert->serialNumber.data = (unsigned char*)serialNumberValue;
+  stackCert->serialNumber.len = strlen(serialNumberValue);
+  stackCert->derPublicKey.data = (unsigned char*)publicKeyValue;
+  stackCert->derPublicKey.len = strlen(publicKeyValue);
+  CERTName *subject = CERT_AsciiToName(subjectValue); // TODO: this will leak...
+  ASSERT_TRUE(subject);
+  stackCert->subject.arena = subject->arena;
+  stackCert->subject.rdns = subject->rdns;
+}
+
+static void
+PutAndGet(mozilla::psm::OCSPCache& cache, CERTCertificate* subject,
+          CERTCertificate* issuer,
+          PRErrorCode error, PRTime time)
+{
+  // The first time is thisUpdate. The second is validUntil.
+  // The caller is expecting the validUntil returned with Get
+  // to be equal to the passed-in time. Since these values will
+  // be different in practice, make thisUpdate less than validUntil.
+  ASSERT_TRUE(time >= 10);
+  SECStatus rv = cache.Put(subject, issuer, error, time - 10, time);
+  ASSERT_TRUE(rv == SECSuccess);
+  PRErrorCode errorOut;
+  PRTime timeOut;
+  ASSERT_TRUE(cache.Get(subject, issuer, errorOut, timeOut));
+  ASSERT_TRUE(error == errorOut && time == timeOut);
+}
+
+TEST_F(OCSPCacheTest, TestPutAndGet)
+{
+  SCOPED_TRACE("");
+  CERTCertificate subject;
+  MakeFakeCert(&subject, "CN=subject1", "CN=issuer1", "001", "key001");
+  CERTCertificate issuer;
+  MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000");
+  PutAndGet(cache, &subject, &issuer, 0, PR_Now());
+  PRErrorCode errorOut;
+  PRTime timeOut;
+  ASSERT_FALSE(cache.Get(&issuer, &issuer, errorOut, timeOut));
+}
+
+TEST_F(OCSPCacheTest, TestVariousGets)
+{
+  SCOPED_TRACE("");
+  CERTCertificate issuer;
+  MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000");
+  PRTime timeIn = PR_Now();
+  for (int i = 0; i < MaxCacheEntries; i++) {
+    CERTCertificate subject;
+    char subjectBuf[64];
+    PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i);
+    char serialBuf[8];
+    PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i);
+    MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000");
+    PutAndGet(cache, &subject, &issuer, 0, timeIn + i);
+  }
+  CERTCertificate subject;
+  // This will be at the end of the list in the cache
+  PRErrorCode errorOut;
+  PRTime timeOut;
+  MakeFakeCert(&subject, "CN=subject0000", "CN=issuer1", "0000", "key000");
+  ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut));
+  ASSERT_TRUE(errorOut == 0 && timeOut == timeIn);
+  // Once we access it, it goes to the front
+  ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut));
+  ASSERT_TRUE(errorOut == 0 && timeOut == timeIn);
+  MakeFakeCert(&subject, "CN=subject0512", "CN=issuer1", "0512", "key000");
+  // This will be in the middle
+  ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut));
+  ASSERT_TRUE(errorOut == 0 && timeOut == timeIn + 512);
+  ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut));
+  ASSERT_TRUE(errorOut == 0 && timeOut == timeIn + 512);
+  // We've never seen this certificate
+  MakeFakeCert(&subject, "CN=subject1111", "CN=issuer1", "1111", "key000");
+  ASSERT_FALSE(cache.Get(&subject, &issuer, errorOut, timeOut));
+}
+
+TEST_F(OCSPCacheTest, TestEviction)
+{
+  SCOPED_TRACE("");
+  CERTCertificate issuer;
+  PRTime timeIn = PR_Now();
+  MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000");
+  // By putting more distinct entries in the cache than it can hold,
+  // we cause the least recently used entry to be evicted.
+  for (int i = 0; i < MaxCacheEntries + 1; i++) {
+    CERTCertificate subject;
+    char subjectBuf[64];
+    PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i);
+    char serialBuf[8];
+    PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i);
+    MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000");
+    PutAndGet(cache, &subject, &issuer, 0, timeIn + i);
+  }
+  CERTCertificate evictedSubject;
+  MakeFakeCert(&evictedSubject, "CN=subject0000", "CN=issuer1", "0000", "key000");
+  PRErrorCode errorOut;
+  PRTime timeOut;
+  ASSERT_FALSE(cache.Get(&evictedSubject, &issuer, errorOut, timeOut));
+}
+
+TEST_F(OCSPCacheTest, TestNoEvictionForRevokedResponses)
+{
+  SCOPED_TRACE("");
+  CERTCertificate issuer;
+  MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000");
+  CERTCertificate notEvictedSubject;
+  MakeFakeCert(&notEvictedSubject, "CN=subject0000", "CN=issuer1", "0000", "key000");
+  PRTime timeIn = PR_Now();
+  PutAndGet(cache, &notEvictedSubject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, timeIn);
+  // By putting more distinct entries in the cache than it can hold,
+  // we cause the least recently used entry that isn't revoked to be evicted.
+  for (int i = 1; i < MaxCacheEntries + 1; i++) {
+    CERTCertificate subject;
+    char subjectBuf[64];
+    PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i);
+    char serialBuf[8];
+    PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i);
+    MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000");
+    PutAndGet(cache, &subject, &issuer, 0, timeIn + i);
+  }
+  PRErrorCode errorOut;
+  PRTime timeOut;
+  ASSERT_TRUE(cache.Get(&notEvictedSubject, &issuer, errorOut, timeOut));
+  ASSERT_TRUE(errorOut == SEC_ERROR_REVOKED_CERTIFICATE && timeOut == timeIn);
+  CERTCertificate evictedSubject;
+  MakeFakeCert(&evictedSubject, "CN=subject0001", "CN=issuer1", "0001", "key000");
+  ASSERT_FALSE(cache.Get(&evictedSubject, &issuer, errorOut, timeOut));
+}
+
+TEST_F(OCSPCacheTest, TestEverythingIsRevoked)
+{
+  SCOPED_TRACE("");
+  CERTCertificate issuer;
+  MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000");
+  PRTime timeIn = PR_Now();
+  // Fill up the cache with revoked responses.
+  for (int i = 0; i < MaxCacheEntries; i++) {
+    CERTCertificate subject;
+    char subjectBuf[64];
+    PR_snprintf(subjectBuf, sizeof(subjectBuf), "CN=subject%04d", i);
+    char serialBuf[8];
+    PR_snprintf(serialBuf, sizeof(serialBuf), "%04d", i);
+    MakeFakeCert(&subject, subjectBuf, "CN=issuer1", serialBuf, "key000");
+    PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, timeIn + i);
+  }
+  CERTCertificate goodSubject;
+  MakeFakeCert(&goodSubject, "CN=subject1025", "CN=issuer1", "1025", "key000");
+  // This will "succeed", allowing verification to continue. However,
+  // nothing was actually put in the cache.
+  SECStatus result = cache.Put(&goodSubject, &issuer, 0, timeIn + 1025 - 50,
+                               timeIn + 1025);
+  ASSERT_TRUE(result == SECSuccess);
+  PRErrorCode errorOut;
+  PRTime timeOut;
+  ASSERT_FALSE(cache.Get(&goodSubject, &issuer, errorOut, timeOut));
+
+  CERTCertificate revokedSubject;
+  MakeFakeCert(&revokedSubject, "CN=subject1026", "CN=issuer1", "1026", "key000");
+  // This will fail, causing verification to fail.
+  result = cache.Put(&revokedSubject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE,
+                     timeIn + 1026 - 50, timeIn + 1026);
+  PRErrorCode error = PR_GetError();
+  ASSERT_TRUE(result == SECFailure);
+  ASSERT_TRUE(error == SEC_ERROR_REVOKED_CERTIFICATE);
+}
+
+TEST_F(OCSPCacheTest, VariousIssuers)
+{
+  SCOPED_TRACE("");
+  CERTCertificate issuer1;
+  MakeFakeCert(&issuer1, "CN=issuer1", "CN=issuer1", "000", "key000");
+  CERTCertificate issuer2;
+  MakeFakeCert(&issuer2, "CN=issuer2", "CN=issuer2", "000", "key001");
+  CERTCertificate issuer3;
+  // Note: same CN as issuer1
+  MakeFakeCert(&issuer3, "CN=issuer1", "CN=issuer3", "000", "key003");
+  CERTCertificate subject;
+  MakeFakeCert(&subject, "CN=subject", "CN=issuer1", "001", "key002");
+  PRTime timeIn = PR_Now();
+  PutAndGet(cache, &subject, &issuer1, 0, timeIn);
+  PRErrorCode errorOut;
+  PRTime timeOut;
+  ASSERT_TRUE(cache.Get(&subject, &issuer1, errorOut, timeOut));
+  ASSERT_TRUE(errorOut == 0 && timeOut == timeIn);
+  ASSERT_FALSE(cache.Get(&subject, &issuer2, errorOut, timeOut));
+  ASSERT_FALSE(cache.Get(&subject, &issuer3, errorOut, timeOut));
+}
+
+TEST_F(OCSPCacheTest, Times)
+{
+  SCOPED_TRACE("");
+  CERTCertificate subject;
+  MakeFakeCert(&subject, "CN=subject1", "CN=issuer1", "001", "key001");
+  CERTCertificate issuer;
+  MakeFakeCert(&issuer, "CN=issuer1", "CN=issuer1", "000", "key000");
+  PutAndGet(cache, &subject, &issuer, SEC_ERROR_OCSP_UNKNOWN_CERT, 100);
+  PutAndGet(cache, &subject, &issuer, 0, 200);
+  // This should not override the more recent entry.
+  SECStatus rv = cache.Put(&subject, &issuer, SEC_ERROR_OCSP_UNKNOWN_CERT, 100, 100);
+  ASSERT_TRUE(rv == SECSuccess);
+  PRErrorCode errorOut;
+  PRTime timeOut;
+  ASSERT_TRUE(cache.Get(&subject, &issuer, errorOut, timeOut));
+  // Here we see the more recent time.
+  ASSERT_TRUE(errorOut == 0 && timeOut == 200);
+
+  // SEC_ERROR_REVOKED_CERTIFICATE overrides everything
+  PutAndGet(cache, &subject, &issuer, SEC_ERROR_REVOKED_CERTIFICATE, 50);
+}
--- a/security/manager/ssl/tests/gtest/moz.build
+++ b/security/manager/ssl/tests/gtest/moz.build
@@ -4,21 +4,19 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 LIBRARY_NAME = 'ssltest'
 
 LIBXUL_LIBRARY = True
 
 SOURCES += [
-	'TLSIntoleranceTest.cpp',
+    'OCSPCacheTest.cpp',
+    'TLSIntoleranceTest.cpp',
 ]
 
 LOCAL_INCLUDES += [
+    '../../../../certverifier',
     '../../../../insanity/include',
+    '/security/manager/ssl/src',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
-
-LOCAL_INCLUDES += [
-    '/security/manager/ssl/src',
-]
-
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -17,16 +17,17 @@ let gIsWindows = ("@mozilla.org/windows-
 
 const isDebugBuild = Cc["@mozilla.org/xpcom/debug;1"]
                        .getService(Ci.nsIDebug2).isDebugBuild;
 
 const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
 const SSL_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE;
 
 // Sort in numerical order
+const SEC_ERROR_BAD_DER                                 = SEC_ERROR_BASE +   9;
 const SEC_ERROR_EXPIRED_CERTIFICATE                     = SEC_ERROR_BASE +  11;
 const SEC_ERROR_REVOKED_CERTIFICATE                     = SEC_ERROR_BASE +  12; // -8180
 const SEC_ERROR_UNKNOWN_ISSUER                          = SEC_ERROR_BASE +  13;
 const SEC_ERROR_BAD_DATABASE                            = SEC_ERROR_BASE +  18;
 const SEC_ERROR_UNTRUSTED_ISSUER                        = SEC_ERROR_BASE +  20; // -8172
 const SEC_ERROR_UNTRUSTED_CERT                          = SEC_ERROR_BASE +  21; // -8171
 const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE              = SEC_ERROR_BASE +  30; // -8162
 const SEC_ERROR_EXTENSION_NOT_FOUND                     = SEC_ERROR_BASE +  35; // -8157
@@ -113,21 +114,19 @@ function _getLibraryFunctionWithNoArgume
   }
 
   let SECStatus = ctypes.int;
   let func = nsslib.declare(functionName, ctypes.default_abi, SECStatus);
   return func;
 }
 
 function clearOCSPCache() {
-  let CERT_ClearOCSPCache =
-    _getLibraryFunctionWithNoArguments("CERT_ClearOCSPCache", "nss3");
-  if (CERT_ClearOCSPCache() != 0) {
-    throw "Failed to clear OCSP cache";
-  }
+  let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+                 .getService(Ci.nsIX509CertDB);
+  certdb.clearOCSPCache();
 }
 
 function clearSessionCache() {
   let SSL_ClearSessionCache = null;
   try {
     SSL_ClearSessionCache =
       _getLibraryFunctionWithNoArguments("SSL_ClearSessionCache", "ssl3");
   } catch (e) {
--- a/security/manager/ssl/tests/unit/test_ev_certs.js
+++ b/security/manager/ssl/tests/unit/test_ev_certs.js
@@ -218,22 +218,20 @@ function add_tests_in_mode(useInsanity)
       let cert = certdb.findCertByNickname(null, "ev-valid");
       let hasEVPolicy = {};
       let verifiedChain = {};
       let flags = Ci.nsIX509CertDB.FLAG_LOCAL_ONLY |
                   Ci.nsIX509CertDB.FLAG_MUST_BE_EV;
 
       let error = certdb.verifyCertNow(cert, certificateUsageSSLServer,
                                        flags, verifiedChain, hasEVPolicy);
-      // XXX(bug 915932): Without an OCSP cache, local-only validation of EV
-      //                  certs will always fail due to lack of an OCSP.
-      do_check_eq(hasEVPolicy.value, isDebugBuild && !useInsanity);
+      do_check_eq(hasEVPolicy.value, isDebugBuild);
       do_check_eq(error,
-                  useInsanity ? SEC_ERROR_POLICY_VALIDATION_FAILED
-                              : (isDebugBuild ? 0
+                  isDebugBuild ? 0
+                               : (useInsanity ? SEC_ERROR_POLICY_VALIDATION_FAILED
                                               : SEC_ERROR_EXTENSION_NOT_FOUND));
       failingOcspResponder.stop(run_next_test);
     });
   });
 }
 
 // bug 950240: add FLAG_MUST_BE_EV to CertVerifier::VerifyCert
 // to prevent spurious OCSP requests that race with OCSP stapling.
--- a/security/manager/ssl/tests/unit/test_ocsp_caching.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_caching.js
@@ -35,16 +35,30 @@ function run_test() {
     let goodResponse = ocspResponses[0];
 
     response.setStatusLine(request.httpVersion, 200, "OK");
     response.setHeader("Content-Type", "application/ocsp-response");
     response.bodyOutputStream.write(goodResponse, goodResponse.length);
   });
   ocspResponder.start(8080);
 
+  add_tests_in_mode(true);
+  add_tests_in_mode(false);
+
+  add_test(function() { ocspResponder.stop(run_next_test); });
+  run_next_test();
+}
+
+function add_tests_in_mode(useInsanity) {
+  add_test(function () {
+    Services.prefs.setBoolPref("security.use_insanity_verification",
+                               useInsanity);
+    run_next_test();
+  });
+
   // This test assumes that OCSPStaplingServer uses the same cert for
   // ocsp-stapling-unknown.example.com and ocsp-stapling-none.example.com.
 
   // Get an Unknown response for the *.exmaple.com cert and put it in the
   // OCSP cache.
   add_connection_test("ocsp-stapling-unknown.example.com",
                       getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNKNOWN_CERT),
                       clearSessionCache);
@@ -88,26 +102,28 @@ function run_test() {
   add_test(function() { clearOCSPCache(); gFetchCount = 0; run_next_test(); });
 
   // A failure to retrieve an OCSP response will result in an error entry being
   // added to the cache.
   add_connection_test("ocsp-stapling-none.example.com", Cr.NS_OK,
                       clearSessionCache);
   add_test(function() { do_check_eq(gFetchCount, 1); run_next_test(); });
 
-  // The error entry will prevent a fetch from happening for a while.
-  add_connection_test("ocsp-stapling-none.example.com", Cr.NS_OK,
-                      clearSessionCache);
-  add_test(function() { do_check_eq(gFetchCount, 1); run_next_test(); });
+  // TODO(bug 977865): implement this for insanity
+  if (!useInsanity) {
+    // The error entry will prevent a fetch from happening for a while.
+    add_connection_test("ocsp-stapling-none.example.com", Cr.NS_OK,
+                        clearSessionCache);
+    add_test(function() { do_check_eq(gFetchCount, 1); run_next_test(); });
+  }
 
   // The error entry must not prevent a stapled OCSP response from being
   // honored.
   add_connection_test("ocsp-stapling-revoked.example.com",
                       getXPCOMStatusFromNSS(SEC_ERROR_REVOKED_CERTIFICATE),
                       clearSessionCache);
   add_test(function() { do_check_eq(gFetchCount, 1); run_next_test(); });
 
   //---------------------------------------------------------------------------
 
-  add_test(function() { ocspResponder.stop(run_next_test); });
-
-  run_next_test();
+  // Reset state
+  add_test(function() { clearOCSPCache(); gFetchCount = 0; run_next_test(); });
 }
--- a/security/manager/ssl/tests/unit/test_ocsp_required.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_required.js
@@ -48,15 +48,15 @@ function add_tests_in_mode(useInsanity)
     run_next_test();
   });
 
   add_connection_test("ocsp-stapling-none.example.com",
                       getXPCOMStatusFromNSS(SEC_ERROR_OCSP_BAD_SIGNATURE));
   add_connection_test("ocsp-stapling-none.example.com",
                       getXPCOMStatusFromNSS(SEC_ERROR_OCSP_BAD_SIGNATURE));
   add_test(function () {
-    // XXX(bug 915932): special case for insanity::pkix due to the temporary
-    // lack of an OCSP cache.
+    // TODO(bug 977865): insanity::pkix keeps requesting responses from
+    // failing responders
     do_check_eq(gOCSPRequestCount, useInsanity ? 2 : 1);
     gOCSPRequestCount = 0;
     run_next_test();
   });
 }
--- a/security/manager/ssl/tests/unit/test_ocsp_stapling.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_stapling.js
@@ -47,17 +47,19 @@ function add_tests_in_mode(useInsanity, 
   // The following error codes are defined in security/nss/lib/util/SECerrs.h
 
   add_ocsp_test("ocsp-stapling-good.example.com", Cr.NS_OK, true);
 
   add_ocsp_test("ocsp-stapling-revoked.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_REVOKED_CERTIFICATE), true);
 
   // SEC_ERROR_OCSP_INVALID_SIGNING_CERT vs SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE
-  // depends on whether the CA that signed the response is a trusted CA.
+  // depends on whether the CA that signed the response is a trusted CA
+  // (but only with the classic implementation - insanity::pkix always
+  // results in the error SEC_ERROR_OCSP_INVALID_SIGNING_CERT).
 
   // This stapled response is from a CA that is untrusted and did not issue
   // the server's certificate.
   add_test(function() {
     certDB.setCertTrust(otherTestCA, Ci.nsIX509Cert.CA_CERT,
                         Ci.nsIX509CertDB.UNTRUSTED);
     run_next_test();
   });
@@ -88,46 +90,52 @@ function add_tests_in_mode(useInsanity, 
                 getXPCOMStatusFromNSS(SEC_ERROR_OCSP_TRY_SERVER_LATER), true);
   add_ocsp_test("ocsp-stapling-needssig.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_OCSP_REQUEST_NEEDS_SIG), true);
   add_ocsp_test("ocsp-stapling-unauthorized.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST),
                 true);
   add_ocsp_test("ocsp-stapling-unknown.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNKNOWN_CERT), true);
+  // TODO(bug 977870): this should not result in SEC_ERROR_BAD_DER
   add_ocsp_test("ocsp-stapling-good-other.example.com",
-                getXPCOMStatusFromNSS(SEC_ERROR_OCSP_UNKNOWN_CERT), true);
+                getXPCOMStatusFromNSS(
+                  useInsanity ? SEC_ERROR_BAD_DER
+                              : SEC_ERROR_OCSP_UNKNOWN_CERT), true);
   // If the server doesn't staple an OCSP response, we continue as normal
   // (this means that even though stapling is enabled, we expect an OCSP
   // request).
   add_connection_test("ocsp-stapling-none.example.com", Cr.NS_OK,
     function() {
       gExpectOCSPRequest = true;
       clearOCSPCache();
       clearSessionCache();
       Services.prefs.setBoolPref("security.ssl.enable_ocsp_stapling", true);
     }
   );
+  // TODO(bug 977870): this should not result in SEC_ERROR_BAD_DER
   add_ocsp_test("ocsp-stapling-empty.example.com",
-                getXPCOMStatusFromNSS(SEC_ERROR_OCSP_MALFORMED_RESPONSE), true);
+                getXPCOMStatusFromNSS(
+                  useInsanity ? SEC_ERROR_BAD_DER
+                              : SEC_ERROR_OCSP_MALFORMED_RESPONSE), true);
   // ocsp-stapling-expired.example.com and
   // ocsp-stapling-expired-fresh-ca.example.com are handled in
   // test_ocsp_stapling_expired.js
 }
 
 function check_ocsp_stapling_telemetry() {
   let histogram = Cc["@mozilla.org/base/telemetry;1"]
                     .getService(Ci.nsITelemetry)
                     .getHistogramById("SSL_OCSP_STAPLING")
                     .snapshot();
-  do_check_eq(histogram.counts[0], 2 * 0); // histogram bucket 0 is unused
-  do_check_eq(histogram.counts[1], 2 * 1); // 1 connection with a good response
-  do_check_eq(histogram.counts[2], 2 * 14); // 14 connections with no stapled resp.
-  do_check_eq(histogram.counts[3], 2 * 0); // 0 connections with an expired response
-  do_check_eq(histogram.counts[4], 2 * 11); // 11 connections with bad responses
+  do_check_eq(histogram.counts[0], 0); // histogram bucket 0 is unused
+  do_check_eq(histogram.counts[1], 1); // 1 connection with a good response
+  do_check_eq(histogram.counts[2], 14); // 14 connections with no stapled resp.
+  do_check_eq(histogram.counts[3], 0); // 0 connections with an expired response
+  do_check_eq(histogram.counts[4], 11); // 11 connections with bad responses
   run_next_test();
 }
 
 function run_test() {
   do_get_profile();
 
   let certDB = Cc["@mozilla.org/security/x509certdb;1"]
                   .getService(Ci.nsIX509CertDB);
--- a/security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_stapling_expired.js
@@ -102,15 +102,15 @@ function add_tests_in_mode(useInsanity)
                 ocspResponseUnknown);
 }
 
 function check_ocsp_stapling_telemetry() {
   let histogram = Cc["@mozilla.org/base/telemetry;1"]
                     .getService(Ci.nsITelemetry)
                     .getHistogramById("SSL_OCSP_STAPLING")
                     .snapshot();
-  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 * 9); // 8 connections with an expired response
-  do_check_eq(histogram.counts[4], 2 * 0); // 0 connections with bad responses
+  do_check_eq(histogram.counts[0], 0); // histogram bucket 0 is unused
+  do_check_eq(histogram.counts[1], 0); // 0 connections with a good response
+  do_check_eq(histogram.counts[2], 0); // 0 connections with no stapled resp.
+  do_check_eq(histogram.counts[3], 9); // 9 connections with an expired response
+  do_check_eq(histogram.counts[4], 0); // 0 connections with bad responses
   run_next_test();
 }