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 173276 840df518d026f9f7b9bf896fb7ef8d0b3e9fb9da
parent 173275 599d7b0af8936efe00aee3f1292d0664fd9f97b3
child 173277 20e8191247fd97395056ee1e4d7e48e1ba806c40
push id5649
push userkwierso@gmail.com
push dateThu, 13 Mar 2014 04:23:46 +0000
treeherderfx-team@9b1812a7852e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscviecco, briansmith
bugs915932
milestone30.0a1
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();
 }