Bug 878932, Parts 1-3: Add OCSP response parsing & validation to insanity::pkix, r=keeler, a=sledru
authorBrian Smith <brian@briansmith.org>
Sun, 16 Feb 2014 18:09:06 -0800
changeset 184042 4d6c9d219ee3b8c5503c39456ef42bebe2f6e364
parent 184041 4c7749cb2702f6d1fe282059fc069652252137eb
child 184043 5f62171194438532cdb2fc09626353de7c7a3786
push id462
push userraliiev@mozilla.com
push dateTue, 22 Apr 2014 00:22:30 +0000
treeherdermozilla-release@ac5db8c74ac0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, sledru
bugs878932
milestone29.0a2
Bug 878932, Parts 1-3: Add OCSP response parsing & validation to insanity::pkix, r=keeler, a=sledru
security/apps/AppSignatureVerification.cpp
security/apps/AppTrustDomain.cpp
security/apps/AppTrustDomain.h
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/certverifier/moz.build
security/insanity/include/insanity/pkix.h
security/insanity/include/insanity/pkixtypes.h
security/insanity/lib/pkixbuild.cpp
security/insanity/lib/pkixcheck.cpp
security/insanity/lib/pkixcheck.h
security/insanity/lib/pkixder.h
security/insanity/lib/pkixocsp.cpp
security/insanity/lib/pkixutil.h
--- a/security/apps/AppSignatureVerification.cpp
+++ b/security/apps/AppSignatureVerification.cpp
@@ -600,17 +600,17 @@ VerifySignature(AppTrustedRoot trustedRo
 
   // Verify certificate.
   AppTrustDomain trustDomain(nullptr); // TODO: null pinArg
   if (trustDomain.SetTrustedRoot(trustedRoot) != SECSuccess) {
     return MapSECStatus(SECFailure);
   }
   if (BuildCertChain(trustDomain, signerCert, PR_Now(), MustBeEndEntity,
                      KU_DIGITAL_SIGNATURE, SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
-                     builtChain) != SECSuccess) {
+                     nullptr, builtChain) != SECSuccess) {
     return MapSECStatus(SECFailure);
   }
 
   // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
   SECOidData* contentTypeOidData =
     SECOID_FindOID(&signedData->contentInfo.contentType);
   if (!contentTypeOidData) {
     return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
--- a/security/apps/AppTrustDomain.cpp
+++ b/security/apps/AppTrustDomain.cpp
@@ -173,9 +173,21 @@ AppTrustDomain::GetCertTrust(EndEntityOr
 
 SECStatus
 AppTrustDomain::VerifySignedData(const CERTSignedData* signedData,
                                   const CERTCertificate* cert)
 {
   return ::insanity::pkix::VerifySignedData(signedData, cert, mPinArg);
 }
 
+SECStatus
+AppTrustDomain::CheckRevocation(EndEntityOrCA,
+                                const CERTCertificate*,
+                                /*const*/ CERTCertificate*,
+                                PRTime time,
+                                /*optional*/ const SECItem*)
+{
+  // We don't currently do revocation checking. If we need to distrust an Apps
+  // certificate, we will use the active distrust mechanism.
+  return SECSuccess;
+}
+
 } }
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -24,17 +24,21 @@ public:
                          const CERTCertificate* candidateCert,
                  /*out*/ TrustLevel* trustLevel) MOZ_OVERRIDE;
   SECStatus FindPotentialIssuers(const SECItem* encodedIssuerName,
                                  PRTime time,
                          /*out*/ insanity::pkix::ScopedCERTCertList& results)
                                  MOZ_OVERRIDE;
   SECStatus VerifySignedData(const CERTSignedData* signedData,
                              const CERTCertificate* cert) MOZ_OVERRIDE;
-
+  SECStatus CheckRevocation(insanity::pkix::EndEntityOrCA endEntityOrCA,
+                            const CERTCertificate* cert,
+                            /*const*/ CERTCertificate* issuerCertToDup,
+                            PRTime time,
+                            /*optional*/ const SECItem* stapledOCSPresponse);
 private:
   void* mPinArg; // non-owning!
   insanity::pkix::ScopedCERTCertificate mTrustedRoot;
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm_AppsTrustDomain_h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -17,18 +17,18 @@
 #include "prerror.h"
 #include "sslerr.h"
 
 // ScopedXXX in this file are insanity::pkix::ScopedXXX, not
 // mozilla::ScopedXXX.
 using namespace insanity::pkix;
 using namespace mozilla::psm;
 
-#ifdef MOZ_LOGGING
-static PRLogModuleInfo* gCertVerifierLog = nullptr;
+#ifdef PR_LOGGING
+PRLogModuleInfo* gCertVerifierLog = nullptr;
 #endif
 
 namespace mozilla { namespace psm {
 
 const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
 const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
 
 CertVerifier::CertVerifier(implementation_config ic,
@@ -52,23 +52,24 @@ CertVerifier::CertVerifier(implementatio
 
 CertVerifier::~CertVerifier()
 {
 }
 
 void
 InitCertVerifierLog()
 {
-#ifdef MOZ_LOGGING
+#ifdef PR_LOGGING
   if (!gCertVerifierLog) {
     gCertVerifierLog = PR_NewLogModule("certverifier");
   }
 #endif
 }
 
+#if 0
 // Once we migrate to insanity::pkix or change the overridable error
 // logic this will become unnecesary.
 static SECStatus
 insertErrorIntoVerifyLog(CERTCertificate* cert, const PRErrorCode err,
                          CERTVerifyLog* verifyLog){
   CERTVerifyLogNode* node;
   node = (CERTVerifyLogNode *)PORT_ArenaAlloc(verifyLog->arena,
                                               sizeof(CERTVerifyLogNode));
@@ -89,16 +90,35 @@ insertErrorIntoVerifyLog(CERTCertificate
   verifyLog->head = node;
   if (!verifyLog->tail) {
     verifyLog->tail = node;
   }
   verifyLog->count++;
 
   return SECSuccess;
 }
+#endif
+
+SECStatus chainValidationCallback(void* state, const CERTCertList* certList,
+                                  PRBool* chainOK)
+{
+  *chainOK = PR_FALSE;
+
+  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("verifycert: Inside the Callback \n"));
+
+  // On sanity failure we fail closed.
+  if (!certList) {
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("verifycert: Short circuit, callback, "
+                                            "sanity check failed \n"));
+    PR_SetError(PR_INVALID_STATE_ERROR, 0);
+    return SECFailure;
+  }
+  *chainOK = PR_TRUE;
+  return SECSuccess;
+}
 
 static SECStatus
 ClassicVerifyCert(CERTCertificate* cert,
                   const SECCertificateUsage usage,
                   const PRTime time,
                   void* pinArg,
                   /*optional out*/ ScopedCERTCertList* validationChain,
                   /*optional out*/ CERTVerifyLog* verifyLog)
@@ -169,47 +189,48 @@ destroyCertListThatShouldNotExist(CERTCe
   }
 }
 #endif
 
 static SECStatus
 BuildCertChainForOneKeyUsage(TrustDomain& trustDomain, CERTCertificate* cert,
                              PRTime time, KeyUsages ku1, KeyUsages ku2,
                              KeyUsages ku3, SECOidTag eku,
+                             const SECItem* stapledOCSPResponse,
                              ScopedCERTCertList& builtChain)
 {
   PR_ASSERT(ku1);
   PR_ASSERT(ku2);
 
   SECStatus rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
-                                ku1, eku, builtChain);
+                                ku1, eku, stapledOCSPResponse, builtChain);
   if (rv != SECSuccess && ku2 &&
       PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
     rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
-                        ku2, eku, builtChain);
+                        ku2, eku, stapledOCSPResponse, builtChain);
     if (rv != SECSuccess && ku3 &&
         PR_GetError() == SEC_ERROR_INADEQUATE_KEY_USAGE) {
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
-                          ku3, eku, builtChain);
+                          ku3, eku, stapledOCSPResponse, builtChain);
       if (rv != SECSuccess) {
         PR_SetError(SEC_ERROR_INADEQUATE_KEY_USAGE, 0);
       }
     }
   }
   return rv;
 }
 
 SECStatus
 CertVerifier::InsanityVerifyCert(
                    CERTCertificate* cert,
-      /*optional*/ const SECItem* /*TODO: stapledOCSPResponse*/,
                    const SECCertificateUsage usage,
                    const PRTime time,
                    void* pinArg,
                    const Flags flags,
+      /*optional*/ const SECItem* stapledOCSPResponse,
   /*optional out*/ insanity::pkix::ScopedCERTCertList* validationChain)
 {
   PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Top of InsanityVerifyCert\n"));
   SECStatus rv;
 
   // TODO(bug 970750): anyExtendedKeyUsage
   // TODO: encipherOnly/decipherOnly
   // S/MIME Key Usage: http://tools.ietf.org/html/rfc3850#section-4.4.2
@@ -223,78 +244,78 @@ CertVerifier::InsanityVerifyCert(
     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, mOCSPDownloadEnabled,
                                        mOCSPStrict, pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
                           KU_DIGITAL_SIGNATURE,
                           SEC_OID_EXT_KEY_USAGE_CLIENT_AUTH,
-                          builtChain);
+                          stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageSSLServer: {
       // TODO: When verifying a certificate in an SSL handshake, we should
       // restrict the acceptable key usage based on the key exchange method
       // chosen by the server.
       NSSCertDBTrustDomain trustDomain(trustSSL, mOCSPDownloadEnabled,
                                        mOCSPStrict, 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,
-                                        builtChain);
+                                        stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, mOCSPDownloadEnabled,
                                        mOCSPStrict, pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeCA,
                           KU_KEY_CERT_SIGN,
                           SEC_OID_EXT_KEY_USAGE_SERVER_AUTH,
-                          builtChain);
+                          stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, mOCSPDownloadEnabled,
                                        mOCSPStrict, pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
                           KU_DIGITAL_SIGNATURE,
                           SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
-                          builtChain);
+                          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, mOCSPDownloadEnabled,
                                        mOCSPStrict, pinArg);
       rv = BuildCertChainForOneKeyUsage(trustDomain, cert, time,
                                         KU_KEY_ENCIPHERMENT, // RSA
                                         KU_KEY_AGREEMENT, // ECDH/DH
                                         0,
                                         SEC_OID_EXT_KEY_USAGE_EMAIL_PROTECT,
-                                        builtChain);
+                                        stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning,
                                        mOCSPDownloadEnabled, mOCSPStrict,
                                        pinArg);
       rv = BuildCertChain(trustDomain, cert, time, MustBeEndEntity,
                           KU_DIGITAL_SIGNATURE,
                           SEC_OID_EXT_KEY_USAGE_CODE_SIGN,
-                          builtChain);
+                          stapledOCSPResponse, builtChain);
       break;
     }
 
     case certificateUsageVerifyCA:
     case certificateUsageStatusResponder: {
       // XXX This is a pretty useless way to verify a certificate. It is used
       // by the implementation of window.crypto.importCertificates and in the
       // certificate viewer UI. Because we don't know what trust bit is
@@ -310,28 +331,28 @@ CertVerifier::InsanityVerifyCert(
         endEntityOrCA = MustBeEndEntity;
         keyUsage = KU_DIGITAL_SIGNATURE;
         eku = SEC_OID_OCSP_RESPONDER;
       }
 
       NSSCertDBTrustDomain sslTrust(trustSSL,
                                     mOCSPDownloadEnabled, mOCSPStrict, pinArg);
       rv = BuildCertChain(sslTrust, cert, time, endEntityOrCA,
-                          keyUsage, eku, builtChain);
+                          keyUsage, eku, stapledOCSPResponse, builtChain);
       if (rv == SECFailure && PR_GetError() == SEC_ERROR_UNKNOWN_ISSUER) {
         NSSCertDBTrustDomain emailTrust(trustEmail, mOCSPDownloadEnabled,
                                         mOCSPStrict, pinArg);
         rv = BuildCertChain(emailTrust, cert, time, endEntityOrCA, keyUsage,
-                            eku, builtChain);
+                            eku, stapledOCSPResponse, builtChain);
         if (rv == SECFailure && SEC_ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
                                                   mOCSPDownloadEnabled,
                                                   mOCSPStrict, pinArg);
           rv = BuildCertChain(objectSigningTrust, cert, time, endEntityOrCA,
-                              keyUsage, eku, builtChain);
+                              keyUsage, eku, stapledOCSPResponse, builtChain);
         }
       }
 
       break;
     }
 
     default:
       PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
@@ -565,18 +586,18 @@ CertVerifier::VerifyCert(CERTCertificate
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
 #else
     PR_SetError(PR_INVALID_STATE_ERROR, 0);
 #endif
     return SECFailure;
   }
 
   if (mImplementation == insanity) {
-    return InsanityVerifyCert(cert, stapledOCSPResponse, usage, time,
-                              pinArg, flags, validationChain);
+    return InsanityVerifyCert(cert, usage, time, pinArg, flags,
+                              stapledOCSPResponse, validationChain);
   }
 
   if (mImplementation == classic) {
     // XXX: we do not care about the localOnly flag (currently) as the
     // caller that wants localOnly should disable and reenable the fetching.
     return ClassicVerifyCert(cert, usage, time, pinArg, validationChain,
                              verifyLog);
   }
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -73,20 +73,20 @@ public:
   const bool mCRLDownloadEnabled;
 #endif
   const bool mOCSPDownloadEnabled;
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
 
 private:
   SECStatus InsanityVerifyCert(CERTCertificate* cert,
-      /*optional*/ const SECItem* stapledOCSPResponse,
       const SECCertificateUsage usage,
       const PRTime time,
       void* pinArg,
       const Flags flags,
+      /*optional*/ const SECItem* stapledOCSPResponse,
       /*optional out*/ insanity::pkix::ScopedCERTCertList* validationChain);
 };
 
 void InitCertVerifierLog();
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__CertVerifier_h
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -33,22 +33,22 @@ namespace {
 
 inline void PORT_Free_string(char* str) { PORT_Free(str); }
 
 typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule;
 
 } // unnamed namespace
 
 NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
-                                           bool /*ocspDownloadEnabled*/,
-                                           bool /*ocspStrict*/,
+                                           bool ocspDownloadEnabled,
+                                           bool ocspStrict,
                                            void* pinArg)
   : mCertDBTrustType(certDBTrustType)
-//  , mOCSPDownloadEnabled(ocspDownloadEnabled)
-//  , mOCSPStrict(ocspStrict)
+  , mOCSPDownloadEnabled(ocspDownloadEnabled)
+  , mOCSPStrict(ocspStrict)
   , mPinArg(pinArg)
 {
 }
 
 SECStatus
 NSSCertDBTrustDomain::FindPotentialIssuers(
   const SECItem* encodedIssuerName, PRTime time,
   /*out*/ insanity::pkix::ScopedCERTCertList& results)
@@ -119,16 +119,128 @@ NSSCertDBTrustDomain::GetCertTrust(EndEn
 
 SECStatus
 NSSCertDBTrustDomain::VerifySignedData(const CERTSignedData* signedData,
                                        const CERTCertificate* cert)
 {
   return ::insanity::pkix::VerifySignedData(signedData, cert, mPinArg);
 }
 
+SECStatus
+NSSCertDBTrustDomain::CheckRevocation(
+  insanity::pkix::EndEntityOrCA endEntityOrCA,
+  const CERTCertificate* cert,
+  /*const*/ CERTCertificate* issuerCert,
+  PRTime time,
+  /*optional*/ const SECItem* stapledOCSPResponse)
+{
+  // Actively distrusted certificates will have already been blocked by
+  // GetCertTrust.
+
+  // TODO: need to verify that IsRevoked isn't called for trust anchors AND
+  // that that fact is documented in insanity.
+
+  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+         ("NSSCertDBTrustDomain: Top of CheckRevocation\n"));
+
+  PORT_Assert(cert);
+  PORT_Assert(issuerCert);
+  if (!cert || !issuerCert) {
+    PORT_SetError(SEC_ERROR_INVALID_ARGS);
+    return SECFailure;
+  }
+
+  // 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);
+    if (rv == SECSuccess) {
+      return rv;
+    }
+    if (PR_GetError() != SEC_ERROR_OCSP_OLD_RESPONSE) {
+      return rv;
+    }
+  }
+
+  // TODO(bug 921885): We need to change this when we add EV support.
+
+  // TODO: when !mOCSPDownloadEnabled, we still need to handle the fallback for
+  // expired responses. But, if/when we disable OCSP fetching by default, it
+  // would be ambiguous whether !mOCSPDownloadEnabled means "I want the default"
+  // or "I really never want you to ever fetch OCSP."
+  if (mOCSPDownloadEnabled) {
+    // We don't do OCSP fetching for intermediates.
+    if (endEntityOrCA == MustBeCA) {
+      PR_ASSERT(!stapledOCSPResponse);
+      return SECSuccess;
+    }
+
+    ScopedPtr<char, PORT_Free_string>
+      url(CERT_GetOCSPAuthorityInfoAccessLocation(cert));
+
+    // 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.
+    if (!url) {
+      if (stapledOCSPResponse) {
+        PR_SetError(SEC_ERROR_OCSP_OLD_RESPONSE, 0);
+        return SECFailure;
+      }
+      return SECSuccess;
+    }
+
+    ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+    if (!arena) {
+      return SECFailure;
+    }
+
+    const SECItem* request
+      = CreateEncodedOCSPRequest(arena.get(), cert, issuerCert);
+    if (!request) {
+      return SECFailure;
+    }
+
+    const SECItem* response(CERT_PostOCSPRequest(arena.get(), url.get(),
+                                                 request));
+    if (!response) {
+      if (mOCSPStrict) {
+        return SECFailure;
+      }
+      // Soft fail -> success :(
+    } else {
+      SECStatus rv = VerifyEncodedOCSPResponse(*this, cert, issuerCert, time,
+                                               response);
+      if (rv == SECSuccess) {
+        return SECSuccess;
+      }
+      PRErrorCode error = PR_GetError();
+      switch (error) {
+        case SEC_ERROR_OCSP_UNKNOWN_CERT:
+        case SEC_ERROR_REVOKED_CERTIFICATE:
+          return SECFailure;
+        default:
+          if (mOCSPStrict) {
+            return SECFailure;
+          }
+          break; // Soft fail -> success :(
+      }
+    }
+  }
+
+  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+         ("NSSCertDBTrustDomain: end of CheckRevocation"));
+
+  return SECSuccess;
+}
+
 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
@@ -58,18 +58,24 @@ public:
 
   virtual SECStatus GetCertTrust(insanity::pkix::EndEntityOrCA endEntityOrCA,
                                  const CERTCertificate* candidateCert,
                          /*out*/ TrustLevel* trustLevel);
 
   virtual SECStatus VerifySignedData(const CERTSignedData* signedData,
                                      const CERTCertificate* cert);
 
+  virtual SECStatus CheckRevocation(insanity::pkix::EndEntityOrCA endEntityOrCA,
+                                    const CERTCertificate* cert,
+                          /*const*/ CERTCertificate* issuerCert,
+                                    PRTime time,
+                       /*optional*/ const SECItem* stapledOCSPResponse);
+
 private:
   const SECTrustType mCertDBTrustType;
-//  const bool mOCSPDownloadEnabled;
-//  const bool mOCSPStrict;
+  const bool mOCSPDownloadEnabled;
+  const bool mOCSPStrict;
   void* mPinArg; // non-owning!
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__NSSCertDBTrustDomain_h
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -4,16 +4,17 @@
 # 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 += [
     '../insanity/lib/pkixbuild.cpp',
     '../insanity/lib/pkixcheck.cpp',
     '../insanity/lib/pkixder.cpp',
     '../insanity/lib/pkixkey.cpp',
+    '../insanity/lib/pkixocsp.cpp',
     'CertVerifier.cpp',
     'NSSCertDBTrustDomain.cpp',
 ]
 
 if not CONFIG['NSS_NO_LIBPKIX']:
     UNIFIED_SOURCES += [
         'ExtendedValidation.cpp',
     ]
--- a/security/insanity/include/insanity/pkix.h
+++ b/security/insanity/include/insanity/pkix.h
@@ -24,19 +24,31 @@
 namespace insanity { namespace pkix {
 
 SECStatus BuildCertChain(TrustDomain& trustDomain,
                          CERTCertificate* cert,
                          PRTime time,
                          EndEntityOrCA endEntityOrCA,
             /*optional*/ KeyUsages requiredKeyUsagesIfPresent,
             /*optional*/ SECOidTag requiredEKUIfPresent,
+            /*optional*/ const SECItem* stapledOCSPResponse,
                  /*out*/ ScopedCERTCertList& results);
 
 // Verify the given signed data using the public key of the given certificate.
 // (EC)DSA parameter inheritance is not supported.
 SECStatus VerifySignedData(const CERTSignedData* sd,
                            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);
+
+SECStatus VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
+                                    const CERTCertificate* cert,
+                                    CERTCertificate* issuerCert,
+                                    PRTime time,
+                                    const SECItem* encodedResponse);
+
 } } // namespace insanity::pkix
 
 #endif // insanity_pkix__pkix_h
--- a/security/insanity/include/insanity/pkixtypes.h
+++ b/security/insanity/include/insanity/pkixtypes.h
@@ -74,16 +74,24 @@ public:
   // has all the public key information needed--i.e. it should ensure that the
   // certificate is not trying to use EC(DSA) parameter inheritance.
   //
   // Most implementations of this function should probably forward the call
   // directly to insanity::pkix::VerifySignedData.
   virtual SECStatus VerifySignedData(const CERTSignedData* signedData,
                                      const CERTCertificate* cert) = 0;
 
+  // issuerCertToDup is only non-const so CERT_DupCertificate can be called on
+  // it.
+  virtual SECStatus CheckRevocation(EndEntityOrCA endEntityOrCA,
+                                    const CERTCertificate* cert,
+                          /*const*/ CERTCertificate* issuerCertToDup,
+                                    PRTime time,
+                       /*optional*/ const SECItem* stapledOCSPresponse) = 0;
+
 protected:
   TrustDomain() { }
 
 private:
   TrustDomain(const TrustDomain&) /* = delete */;
   void operator=(const TrustDomain&) /* = delete */;
 };
 
--- a/security/insanity/lib/pkixbuild.cpp
+++ b/security/insanity/lib/pkixbuild.cpp
@@ -91,27 +91,29 @@ BackCert::Init()
 }
 
 static Result BuildForward(TrustDomain& trustDomain,
                            BackCert& subject,
                            PRTime time,
                            EndEntityOrCA endEntityOrCA,
                            KeyUsages requiredKeyUsagesIfPresent,
                            SECOidTag requiredEKUIfPresent,
+                           /*optional*/ const SECItem* stapledOCSPResponse,
                            unsigned int subCACount,
                            /*out*/ ScopedCERTCertList& results);
 
 // The code that executes in the inner loop of BuildForward
 static Result
 BuildForwardInner(TrustDomain& trustDomain,
                   BackCert& subject,
                   PRTime time,
                   EndEntityOrCA endEntityOrCA,
                   SECOidTag requiredEKUIfPresent,
                   CERTCertificate* potentialIssuerCertToDup,
+                  /*optional*/ const SECItem* stapledOCSPResponse,
                   unsigned int subCACount,
                   ScopedCERTCertList& results)
 {
   PORT_Assert(potentialIssuerCertToDup);
 
   BackCert potentialIssuer(potentialIssuerCertToDup, &subject,
                            BackCert::ExcludeCN);
   Result rv = potentialIssuer.Init();
@@ -131,36 +133,30 @@ BuildForwardInner(TrustDomain& trustDoma
     if (SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derPublicKey,
                               &prev->GetNSSCert()->derPublicKey) &&
         SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derSubject,
                               &prev->GetNSSCert()->derSubject)) {
       return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); // XXX: error code
     }
   }
 
-  rv = CheckTimes(potentialIssuer.GetNSSCert(), time);
-  if (rv != Success) {
-    return rv;
-  }
-
   rv = CheckNameConstraints(potentialIssuer);
   if (rv != Success) {
     return rv;
   }
 
   unsigned int newSubCACount = subCACount;
   if (endEntityOrCA == MustBeCA) {
     newSubCACount = subCACount + 1;
   } else {
     PR_ASSERT(newSubCACount == 0);
   }
-
   rv = BuildForward(trustDomain, potentialIssuer, time, MustBeCA,
                     KU_KEY_CERT_SIGN, requiredEKUIfPresent,
-                    newSubCACount, results);
+                    nullptr, newSubCACount, results);
   if (rv != Success) {
     return rv;
   }
 
   if (trustDomain.VerifySignedData(&subject.GetNSSCert()->signatureWrap,
                                    potentialIssuer.GetNSSCert()) != SECSuccess) {
     return MapSECStatus(SECFailure);
   }
@@ -171,46 +167,36 @@ BuildForwardInner(TrustDomain& trustDoma
 // Caller must check for expiration before calling this function
 static Result
 BuildForward(TrustDomain& trustDomain,
              BackCert& subject,
              PRTime time,
              EndEntityOrCA endEntityOrCA,
              KeyUsages requiredKeyUsagesIfPresent,
              SECOidTag requiredEKUIfPresent,
+             /*optional*/ const SECItem* stapledOCSPResponse,
              unsigned int subCACount,
              /*out*/ ScopedCERTCertList& results)
 {
   // Avoid stack overflows and poor performance by limiting cert length.
   // XXX: 6 is not enough for chains.sh anypolicywithlevel.cfg tests
   static const size_t MAX_DEPTH = 8;
   if (subCACount >= MAX_DEPTH - 1) {
     return RecoverableError;
   }
 
+  Result rv;
+
   TrustDomain::TrustLevel trustLevel;
-  Result rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
-                                                    subject.GetNSSCert(),
-                                                    &trustLevel));
-  if (rv != Success) {
-    return rv;
-  }
-  if (trustLevel == TrustDomain::ActivelyDistrusted) {
-    return Fail(RecoverableError, SEC_ERROR_UNTRUSTED_CERT);
-  }
-  if (trustLevel != TrustDomain::TrustAnchor &&
-      trustLevel != TrustDomain::InheritsTrust) {
-    // The TrustDomain returned a trust level that we weren't expecting.
-    return Fail(FatalError, PR_INVALID_STATE_ERROR);
-  }
 
-  rv = CheckExtensions(subject, endEntityOrCA,
-                       trustLevel == TrustDomain::TrustAnchor,
-                       requiredKeyUsagesIfPresent, requiredEKUIfPresent,
-                       subCACount);
+  rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
+                                        endEntityOrCA,
+                                        requiredKeyUsagesIfPresent,
+                                        requiredEKUIfPresent, subCACount,
+                                        &trustLevel);
   if (rv != Success) {
     return rv;
   }
 
   if (trustLevel == TrustDomain::TrustAnchor) {
     // End of the recursion. Create the result list and add the trust anchor to
     // it.
     results = CERT_NewCertList();
@@ -232,18 +218,27 @@ BuildForward(TrustDomain& trustDomain,
   if (!candidates) {
     return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER);
   }
 
   for (CERTCertListNode* n = CERT_LIST_HEAD(candidates);
        !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) {
     rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA,
                            requiredEKUIfPresent,
-                           n->cert, subCACount, results);
+                           n->cert, stapledOCSPResponse, subCACount,
+                           results);
     if (rv == Success) {
+      SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA,
+                                                  subject.GetNSSCert(),
+                                                  n->cert, time,
+                                                  stapledOCSPResponse);
+      if (srv != SECSuccess) {
+        return MapSECStatus(SECFailure);
+      }
+
       // We found a trusted issuer. At this point, we know the cert is valid
       return subject.PrependNSSCertToList(results.get());
     }
     if (rv != RecoverableError) {
       return rv;
     }
   }
 
@@ -252,16 +247,17 @@ BuildForward(TrustDomain& trustDomain,
 
 SECStatus
 BuildCertChain(TrustDomain& trustDomain,
                CERTCertificate* certToDup,
                PRTime time,
                EndEntityOrCA endEntityOrCA,
                /*optional*/ KeyUsages requiredKeyUsagesIfPresent,
                /*optional*/ SECOidTag requiredEKUIfPresent,
+               /*optional*/ const SECItem* stapledOCSPResponse,
                /*out*/ ScopedCERTCertList& results)
 {
   PORT_Assert(certToDup);
 
   if (!certToDup) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
   }
@@ -280,29 +276,22 @@ BuildCertChain(TrustDomain& trustDomain,
   BackCert cert(certToDup, nullptr, cnOptions);
   Result rv = cert.Init();
   if (rv != Success) {
     return SECFailure;
   }
 
   rv = BuildForward(trustDomain, cert, time, endEntityOrCA,
                     requiredKeyUsagesIfPresent, requiredEKUIfPresent,
-                    0, results);
+                    stapledOCSPResponse, 0, results);
   if (rv != Success) {
     results = nullptr;
     return SECFailure;
   }
 
-  // Build the cert chain even if the cert is expired, because we would
-  // rather report the untrusted issuer error than the expired error.
-  if (CheckTimes(cert.GetNSSCert(), time) != Success) {
-    PR_SetError(SEC_ERROR_EXPIRED_CERTIFICATE, 0);
-    return SECFailure;
-  }
-
   return SECSuccess;
 }
 
 PLArenaPool*
 BackCert::GetArena()
 {
   if (!arena) {
     arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
--- a/security/insanity/lib/pkixcheck.cpp
+++ b/security/insanity/lib/pkixcheck.cpp
@@ -313,36 +313,65 @@ CheckExtendedKeyUsage(EndEntityOrCA endE
     // we can't require the EKU be explicitly included for CA certificates.
     PR_SetError(SEC_ERROR_INADEQUATE_CERT_TYPE, 0);
     return RecoverableError;
   }
 
   return Success;
 }
 
-// Checks extensions that apply to both EE and intermediate certs,
-// except for AIA, CRL, and AKI/SKI, which are handled elsewhere.
 Result
-CheckExtensions(BackCert& cert,
-                EndEntityOrCA endEntityOrCA,
-                bool isTrustAnchor,
-                KeyUsages requiredKeyUsagesIfPresent,
-                SECOidTag requiredEKUIfPresent,
-                unsigned int subCACount)
+CheckIssuerIndependentProperties(TrustDomain& trustDomain,
+                                 BackCert& cert,
+                                 PRTime time,
+                                 EndEntityOrCA endEntityOrCA,
+                                 KeyUsages requiredKeyUsagesIfPresent,
+                                 SECOidTag requiredEKUIfPresent,
+                                 unsigned int subCACount,
+                /*optional out*/ TrustDomain::TrustLevel* trustLevelOut)
 {
+  Result rv;
+
+  TrustDomain::TrustLevel trustLevel;
+  rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA,
+                                             cert.GetNSSCert(),
+                                             &trustLevel));
+  if (rv != Success) {
+    return rv;
+  }
+  if (trustLevel == TrustDomain::ActivelyDistrusted) {
+    PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
+    return RecoverableError;
+  }
+  if (trustLevel != TrustDomain::TrustAnchor &&
+      trustLevel != TrustDomain::InheritsTrust) {
+    // The TrustDomain returned a trust level that we weren't expecting.
+    PORT_SetError(PR_INVALID_STATE_ERROR);
+    return FatalError;
+  }
+  if (trustLevelOut) {
+    *trustLevelOut = trustLevel;
+  }
+
+  bool isTrustAnchor = endEntityOrCA == MustBeCA &&
+                       trustLevel == TrustDomain::TrustAnchor;
+
+  rv = CheckTimes(cert.GetNSSCert(), time);
+  if (rv != Success) {
+    return rv;
+  }
+
   // 4.2.1.1. Authority Key Identifier dealt with as part of path building
   // 4.2.1.2. Subject Key Identifier dealt with as part of path building
 
   PLArenaPool* arena = cert.GetArena();
   if (!arena) {
     return FatalError;
   }
 
-  Result rv;
-
   // 4.2.1.3. Key Usage
 
   rv = CheckKeyUsage(endEntityOrCA, isTrustAnchor, cert.encodedKeyUsage,
                      requiredKeyUsagesIfPresent, arena);
   if (rv != Success) {
     return rv;
   }
 
--- a/security/insanity/lib/pkixcheck.h
+++ b/security/insanity/lib/pkixcheck.h
@@ -18,22 +18,23 @@
 #ifndef insanity__pkixcheck_h
 #define insanity__pkixcheck_h
 
 #include "pkixutil.h"
 #include "certt.h"
 
 namespace insanity { namespace pkix {
 
-Result CheckTimes(const CERTCertificate* cert, PRTime time);
-
-Result CheckExtensions(BackCert& certExt,
-                       EndEntityOrCA endEntityOrCA,
-                       bool isTrustAnchor,
-                       KeyUsages requiredKeyUsagesIfPresent,
-                       SECOidTag requiredEKUIfPresent,
-                       unsigned int depth);
+Result CheckIssuerIndependentProperties(
+          TrustDomain& trustDomain,
+          BackCert& cert,
+          PRTime time,
+          EndEntityOrCA endEntityOrCA,
+          KeyUsages requiredKeyUsagesIfPresent,
+          SECOidTag requiredEKUIfPresent,
+          unsigned int subCACount,
+          /*optional out*/ TrustDomain::TrustLevel* trustLevel = nullptr);
 
 Result CheckNameConstraints(BackCert& cert);
 
 } } // namespace insanity::pkix
 
 #endif // insanity__pkixcheck_h
--- a/security/insanity/lib/pkixder.h
+++ b/security/insanity/lib/pkixder.h
@@ -213,16 +213,23 @@ ExpectTagAndLength(Input& input, uint8_t
 
   return Success;
 }
 
 Result
 ExpectTagAndGetLength(Input& input, uint8_t expectedTag, uint16_t& length);
 
 inline Result
+ExpectTagAndIgnoreLength(Input& input, uint8_t expectedTag)
+{
+  uint16_t ignored;
+  return ExpectTagAndGetLength(input, expectedTag, ignored);
+}
+
+inline Result
 End(Input& input)
 {
   if (!input.AtEnd()) {
     return Fail(SEC_ERROR_BAD_DER);
   }
 
   return Success;
 }
new file mode 100644
--- /dev/null
+++ b/security/insanity/lib/pkixocsp.cpp
@@ -0,0 +1,949 @@
+/* -*- 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 <limits>
+
+#include "insanity/bind.h"
+#include "insanity/pkix.h"
+#include "pkixcheck.h"
+#include "pkixder.h"
+
+#include "hasht.h"
+#include "pk11pub.h"
+#include "secder.h"
+
+#ifdef _MSC_VER
+// C4480: nonstandard extension used: specifying underlying type for enum
+#define ENUM_CLASS  __pragma(warning(disable: 4480)) enum
+#else
+#define ENUM_CLASS enum class
+#endif
+
+// TODO: use typed/qualified typedefs everywhere?
+// TODO: When should we return SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE?
+
+namespace insanity { namespace pkix {
+
+static const PRTime ONE_DAY
+  = INT64_C(24) * INT64_C(60) * INT64_C(60) * PR_USEC_PER_SEC;
+static const PRTime SLOP = ONE_DAY;
+
+// These values correspond to the tag values in the ASN.1 CertStatus
+ENUM_CLASS CertStatus : uint8_t {
+  Good = der::CONTEXT_SPECIFIC | 0,
+  Revoked = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+  Unknown = der::CONTEXT_SPECIFIC | 2
+};
+
+class Context
+{
+public:
+  Context(TrustDomain& trustDomain,
+          const CERTCertificate& cert,
+          CERTCertificate& issuerCert,
+          PRTime time)
+    : trustDomain(trustDomain)
+    , cert(cert)
+    , issuerCert(issuerCert)
+    , time(time)
+    , certStatus(CertStatus::Unknown)
+  {
+  }
+
+  TrustDomain& trustDomain;
+  const CERTCertificate& cert;
+  CERTCertificate& issuerCert;
+  const PRTime time;
+  CertStatus certStatus;
+
+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.
+static Result
+CheckOCSPResponseSignerCert(TrustDomain& trustDomain,
+                            CERTCertificate& potentialSigner,
+                            const CERTCertificate& issuerCert, PRTime time)
+{
+  Result rv;
+
+  BackCert cert(&potentialSigner, nullptr, BackCert::ExcludeCN);
+  rv = cert.Init();
+  if (rv != Success) {
+    return rv;
+  }
+
+  // We don't need to do a complete verification of the signer (i.e. we don't
+  // have to call BuildCertChain to verify the entire chain) because we
+  // already know that the issuerCert is valid, since revocation checking is
+  // done from the root to the parent after we've built a complete chain that
+  // we know is otherwise valid. Rather, we just need to do a one-step
+  // validation from potentialSigner to issuerCert.
+  //
+  // It seems reasonable to require the KU_DIGITAL_SIGNATURE key usage on the
+  // OCSP responder certificate if the OCSP responder certificate has a
+  // key usage extension. However, according to bug 240456, some OCSP responder
+  // certificates may have only the nonRepudiation bit set. Also, the OCSP
+  // specification (RFC 6960) does not mandate any particular key usage to be
+  // asserted for OCSP responde signers. Oddly, the CABForum Baseline
+  // Requirements v.1.1.5 do say "If the Root CA Private Key is used for
+  // signing OCSP responses, then the digitalSignature bit MUST be set."
+  //
+  // Note that CheckIssuerIndependentProperties processes
+  // SEC_OID_OCSP_RESPONDER in the way that the OCSP specification requires us
+  // to--in particular, it doesn't allow SEC_OID_OCSP_RESPONDER to be implied
+  // by a missing EKU extension, unlike other EKUs.
+  //
+  // TODO(bug 926261): If we're validating for a policy then the policy OID we
+  // are validating for should be passed to CheckIssuerIndependentProperties.
+  rv = CheckIssuerIndependentProperties(trustDomain, cert, time,
+                                        MustBeEndEntity, 0,
+                                        SEC_OID_OCSP_RESPONDER, 0);
+  if (rv != Success) {
+    return rv;
+  }
+
+  // It is possible that there exists a certificate with the same key as the
+  // issuer but with a different name, so we need to compare names
+  // TODO: needs test
+  if (!SECITEM_ItemsAreEqual(&cert.GetNSSCert()->derIssuer,
+                             &issuerCert.derSubject) &&
+      CERT_CompareName(&cert.GetNSSCert()->issuer,
+                       &issuerCert.subject) != SECEqual) {
+    return Fail(RecoverableError, SEC_ERROR_OCSP_RESPONDER_CERT_INVALID);
+  }
+
+  // TODO(bug 926260): check name constraints
+
+  if (trustDomain.VerifySignedData(&potentialSigner.signatureWrap,
+                                   &issuerCert) != SECSuccess) {
+    return MapSECStatus(SECFailure);
+  }
+
+  // TODO: check for revocation of the OCSP responder certificate unless no-check
+  // or the caller forcing no-check. To properly support the no-check policy, we'd
+  // need to enforce policy constraints from the issuerChain.
+
+  return Success;
+}
+
+//typedef enum {
+//    ocspResponderID_byName = 1,
+//    ocspResponderID_byKey = 2
+//} ResponderIDType;
+
+ENUM_CLASS ResponderIDType : uint8_t
+{
+  byName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 1,
+  byKey = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 2
+};
+
+static inline der::Result OCSPResponse(der::Input&, Context&);
+static inline der::Result ResponseBytes(der::Input&, Context&);
+static inline der::Result BasicResponse(der::Input&, Context&);
+static inline der::Result ResponseData(
+                              der::Input& tbsResponseData, Context& context,
+                              const CERTSignedData& signedResponseData,
+                              /*const*/ SECItem* certs, size_t numCerts);
+static inline der::Result SingleResponse(der::Input& input,
+                                          Context& context);
+static inline der::Result CheckExtensionsForCriticality(der::Input&);
+static inline der::Result CertID(der::Input& input,
+                                  const Context& context,
+                                  /*out*/ bool& match);
+static der::Result MatchIssuerKey(const SECItem& issuerKeyHash,
+                                   const CERTCertificate& issuer,
+                                   /*out*/ bool& match);
+
+// RFC 6960 section 4.2.2.2: The OCSP responder must either be the issuer of
+// the cert or it must be a delegated OCSP response signing cert directly
+// issued by the issuer. If the OCSP responder is a delegated OCSP response
+// signer, then its certificate is (probably) embedded within the OCSP
+// response and we'll need to verify that it is a valid certificate that chains
+// *directly* to issuerCert.
+static CERTCertificate*
+GetOCSPSignerCertificate(TrustDomain& trustDomain,
+                         ResponderIDType responderIDType,
+                         const SECItem& responderIDItem,
+                         const SECItem* certs, size_t numCerts,
+                         CERTCertificate& issuerCert, PRTime time)
+{
+  bool isIssuer = true;
+  size_t i = 0;
+  for (;;) {
+    ScopedCERTCertificate potentialSigner;
+    if (isIssuer) {
+      potentialSigner = CERT_DupCertificate(&issuerCert);
+    } else if (i < numCerts) {
+      potentialSigner = CERT_NewTempCertificate(
+                          CERT_GetDefaultCertDB(),
+                          /*TODO*/const_cast<SECItem*>(&certs[i]), nullptr,
+                          false, false);
+      if (!potentialSigner) {
+        return nullptr;
+      }
+      ++i;
+    } else {
+      PR_SetError(SEC_ERROR_OCSP_INVALID_SIGNING_CERT, 0);
+      return nullptr;
+    }
+
+    bool match;
+    switch (responderIDType) {
+      case ResponderIDType::byName:
+        // The CA is very likely to have encoded the name in the OCSP response
+        // exactly the same as the name is encoded in the signing certificate.
+        // Consequently, most of the time we will avoid parsing the name
+        // completely. We're assuming here that the signer's subject name is
+        // correctly formatted.
+        // TODO: need test for exact name
+        // TODO: need test for non-exact name match
+        match = SECITEM_ItemsAreEqual(&responderIDItem,
+                                      &potentialSigner->derSubject);
+        if (!match) {
+          ScopedPLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+          if (!arena) {
+            return nullptr;
+          }
+          CERTName name;
+          if (SEC_QuickDERDecodeItem(arena.get(), &name,
+                                     SEC_ASN1_GET(CERT_NameTemplate),
+                                     &responderIDItem) != SECSuccess) {
+            return nullptr;
+          }
+          match = CERT_CompareName(&name, &potentialSigner->subject) == SECEqual;
+        }
+        break;
+
+      case ResponderIDType::byKey:
+      {
+        der::Input responderID;
+        if (responderID.Init(responderIDItem.data, responderIDItem.len)
+              != der::Success) {
+          return nullptr;
+        }
+        SECItem issuerKeyHash;
+        if (der::Skip(responderID, der::OCTET_STRING, issuerKeyHash) != der::Success) {
+          return nullptr;
+        }
+        if (MatchIssuerKey(issuerKeyHash, *potentialSigner.get(), match)
+              != der::Success) {
+          return nullptr;
+        }
+        break;
+      }
+
+      default:
+        PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0);
+        return nullptr;
+    }
+
+    if (match && !isIssuer) {
+      Result rv = CheckOCSPResponseSignerCert(trustDomain,
+                                              *potentialSigner.get(),
+                                              issuerCert, time);
+      if (rv == RecoverableError) {
+        match = false;
+      } else if (rv != Success) {
+        return nullptr;
+      }
+    }
+
+    if (match) {
+      return potentialSigner.release();
+    }
+
+    isIssuer = false;
+  }
+}
+
+static SECStatus
+VerifySignature(Context& context, ResponderIDType responderIDType,
+                const SECItem& responderID, const SECItem* certs,
+                size_t numCerts, const CERTSignedData& signedResponseData)
+{
+  ScopedCERTCertificate signer(
+    GetOCSPSignerCertificate(context.trustDomain, responderIDType, responderID,
+                             certs, numCerts, context.issuerCert,
+                             context.time));
+  if (!signer) {
+    return SECFailure;
+  }
+
+  if (context.trustDomain.VerifySignedData(&signedResponseData, signer.get())
+        != SECSuccess) {
+    if (PR_GetError() == SEC_ERROR_BAD_SIGNATURE) {
+      PR_SetError(SEC_ERROR_OCSP_BAD_SIGNATURE, 0);
+    }
+    return SECFailure;
+  }
+
+  return SECSuccess;
+}
+
+SECStatus
+VerifyEncodedOCSPResponse(TrustDomain& trustDomain,
+                          const CERTCertificate* cert,
+                          CERTCertificate* issuerCert, PRTime time,
+                          const SECItem* encodedResponse)
+{
+  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);
+
+  if (der::Nested(input, der::SEQUENCE,
+                  bind(OCSPResponse, _1, ref(context))) != der::Success) {
+    return SECFailure;
+  }
+
+  if (der::End(input) != der::Success) {
+    return SECFailure;
+  }
+
+  switch (context.certStatus) {
+    case CertStatus::Good:
+      return SECSuccess;
+    case CertStatus::Revoked:
+      PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0);
+      return SECFailure;
+    case CertStatus::Unknown:
+      PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
+      return SECFailure;
+  }
+
+  PR_NOT_REACHED("unknown CertStatus");
+  PR_SetError(SEC_ERROR_OCSP_UNKNOWN_CERT, 0);
+  return SECFailure;
+}
+
+// OCSPResponse ::= SEQUENCE {
+//       responseStatus         OCSPResponseStatus,
+//       responseBytes          [0] EXPLICIT ResponseBytes OPTIONAL }
+//
+static inline der::Result
+OCSPResponse(der::Input& input, Context& context)
+{
+  // OCSPResponseStatus ::= ENUMERATED {
+  //     successful            (0),  -- Response has valid confirmations
+  //     malformedRequest      (1),  -- Illegal confirmation request
+  //     internalError         (2),  -- Internal error in issuer
+  //     tryLater              (3),  -- Try again later
+  //                                 -- (4) is not used
+  //     sigRequired           (5),  -- Must sign the request
+  //     unauthorized          (6)   -- Request unauthorized
+  // }
+  uint8_t responseStatus;
+
+  if (der::Enumerated(input, responseStatus) != der::Success) {
+    return der::Failure;
+  }
+  switch (responseStatus) {
+    case 0: break; // successful
+    case 1: return der::Fail(SEC_ERROR_OCSP_MALFORMED_REQUEST);
+    case 2: return der::Fail(SEC_ERROR_OCSP_SERVER_ERROR);
+    case 3: return der::Fail(SEC_ERROR_OCSP_TRY_SERVER_LATER);
+    case 5: return der::Fail(SEC_ERROR_OCSP_REQUEST_NEEDS_SIG);
+    case 6: return der::Fail(SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST);
+    default: return der::Fail(SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS);
+  }
+
+  return der::Nested(input, der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
+                     der::SEQUENCE, bind(ResponseBytes, _1, ref(context)));
+}
+
+// ResponseBytes ::=       SEQUENCE {
+//     responseType   OBJECT IDENTIFIER,
+//     response       OCTET STRING }
+static inline der::Result
+ResponseBytes(der::Input& input, Context& context)
+{
+  static const uint8_t id_pkix_ocsp_basic[] = {
+    0x2B, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x01, 0x01
+  };
+
+  if (der::OID(input, id_pkix_ocsp_basic) != der::Success) {
+    return der::Failure;
+  }
+
+  return der::Nested(input, der::OCTET_STRING, der::SEQUENCE,
+                     bind(BasicResponse, _1, ref(context)));
+}
+
+// BasicOCSPResponse       ::= SEQUENCE {
+//    tbsResponseData      ResponseData,
+//    signatureAlgorithm   AlgorithmIdentifier,
+//    signature            BIT STRING,
+//    certs            [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL }
+der::Result
+BasicResponse(der::Input& input, Context& context)
+{
+  der::Input::Mark mark(input.GetMark());
+
+  uint16_t length;
+  if (der::ExpectTagAndGetLength(input, der::SEQUENCE, length)
+        != der::Success) {
+    return der::Failure;
+  }
+
+  // The signature covers the entire DER encoding of tbsResponseData, including
+  // the beginning tag and length. However, when we're parsing tbsResponseData,
+  // we want to strip off the tag and length because we don't need it after
+  // we've confirmed it's there and figured out what length it is.
+
+  der::Input tbsResponseData;
+
+  if (input.Skip(length, tbsResponseData) != der::Success) {
+    return der::Failure;
+  }
+
+  CERTSignedData signedData;
+
+  input.GetSECItem(siBuffer, mark, signedData.data);
+
+  if (der::Nested(input, der::SEQUENCE,
+                  bind(der::AlgorithmIdentifier, _1,
+                       ref(signedData.signatureAlgorithm))) != der::Success) {
+    return der::Failure;
+  }
+
+  if (der::Skip(input, der::BIT_STRING, signedData.signature) != der::Success) {
+    return der::Failure;
+  }
+  if (signedData.signature.len == 0) {
+    return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE);
+  }
+  unsigned int unusedBitsAtEnd = signedData.signature.data[0];
+  // XXX: Really the constraint should be that unusedBitsAtEnd must be less
+  // than 7. But, we suspect there are no valid OCSP response signatures with
+  // non-zero unused bits. It seems like NSS assumes this in various places, so
+  // we enforce it. If we find compatibility issues, we'll know we're wrong.
+  if (unusedBitsAtEnd != 0) {
+    return der::Fail(SEC_ERROR_OCSP_BAD_SIGNATURE);
+  }
+  ++signedData.signature.data;
+  --signedData.signature.len;
+  signedData.signature.len = (signedData.signature.len << 3); // Bytes to bits
+
+  // Parse certificates, if any
+
+  SECItem certs[8];
+  size_t numCerts = 0;
+
+  if (!input.AtEnd()) {
+    // We ignore the lengths of the wrappers because we'll detect bad lengths
+    // during parsing--too short and we'll run out of input for parsing a cert,
+    // and too long and we'll have leftover data that won't parse as a cert.
+
+    // [0] wrapper
+    if (der::ExpectTagAndIgnoreLength(
+          input, der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0)
+        != der::Success) {
+      return der::Failure;
+    }
+
+    // SEQUENCE wrapper
+    if (der::ExpectTagAndIgnoreLength(input, der::SEQUENCE) != der::Success) {
+      return der::Failure;
+    }
+
+    // sequence of certificates
+    while (!input.AtEnd()) {
+      if (numCerts == PR_ARRAY_SIZE(certs)) {
+        return der::Fail(SEC_ERROR_BAD_DER);
+      }
+
+      // Unwrap the SEQUENCE that contains the certificate, which is itself a
+      // SEQUENCE.
+      der::Input::Mark mark(input.GetMark());
+      if (der::Skip(input, der::SEQUENCE) != der::Success) {
+        return der::Failure;
+      }
+
+      input.GetSECItem(siBuffer, mark, certs[numCerts]);
+      ++numCerts;
+    }
+  }
+
+  return ResponseData(tbsResponseData, context, signedData, certs, numCerts);
+}
+
+// ResponseData ::= SEQUENCE {
+//    version             [0] EXPLICIT Version DEFAULT v1,
+//    responderID             ResponderID,
+//    producedAt              GeneralizedTime,
+//    responses               SEQUENCE OF SingleResponse,
+//    responseExtensions  [1] EXPLICIT Extensions OPTIONAL }
+static inline der::Result
+ResponseData(der::Input& input, Context& context,
+             const CERTSignedData& signedResponseData,
+             /*const*/ SECItem* certs, size_t numCerts)
+{
+  uint8_t version;
+  if (der::OptionalVersion(input, version) != der::Success) {
+    return der::Failure;
+  }
+  if (version != der::v1) {
+    // TODO: more specific error code for bad version?
+    return der::Fail(SEC_ERROR_BAD_DER);
+  }
+
+  // ResponderID ::= CHOICE {
+  //    byName              [1] Name,
+  //    byKey               [2] KeyHash }
+  SECItem responderID;
+  uint16_t responderIDLength;
+  ResponderIDType responderIDType
+    = input.Peek(static_cast<uint8_t>(ResponderIDType::byName))
+    ? ResponderIDType::byName
+    : ResponderIDType::byKey;
+  if (ExpectTagAndGetLength(input, static_cast<uint8_t>(responderIDType),
+                            responderIDLength) != der::Success) {
+    return der::Failure;
+  }
+  // TODO: responderID probably needs to have another level of ASN1 tag/length
+  // checked and stripped.
+  if (input.Skip(responderIDLength, responderID) != der::Success) {
+    return der::Failure;
+  }
+
+  // This is the soonest we can verify the signature. We verify the signature
+  // right away to follow the principal of minimizing the processing of data
+  // before verifying its signature.
+  if (VerifySignature(context, responderIDType, responderID, certs, numCerts,
+                      signedResponseData) != SECSuccess) {
+    return der::Failure;
+  }
+
+  // TODO: Do we even need to parse this? Should we just skip it?
+  PRTime producedAt;
+  if (der::GeneralizedTime(input, producedAt) != der::Success) {
+    return der::Failure;
+  }
+
+  // We don't accept an empty sequence of responses. In practice, a legit OCSP
+  // responder will never return an empty response, and handling the case of an
+  // empty response makes things unnecessarily complicated.
+  if (der::NestedOf(input, der::SEQUENCE, der::SEQUENCE,
+                    der::MustNotBeEmpty,
+                    bind(SingleResponse, _1, ref(context))) != der::Success) {
+    return der::Failure;
+  }
+
+  if (!input.AtEnd()) {
+    if (CheckExtensionsForCriticality(input) != der::Success) {
+      return der::Failure;
+    }
+  }
+
+  return der::Success;
+}
+
+// SingleResponse ::= SEQUENCE {
+//    certID                       CertID,
+//    certStatus                   CertStatus,
+//    thisUpdate                   GeneralizedTime,
+//    nextUpdate           [0]     EXPLICIT GeneralizedTime OPTIONAL,
+//    singleExtensions     [1]     EXPLICIT Extensions{{re-ocsp-crl |
+//                                              re-ocsp-archive-cutoff |
+//                                              CrlEntryExtensions, ...}
+//                                              } OPTIONAL }
+static inline der::Result
+SingleResponse(der::Input& input, Context& context)
+{
+  bool match = false;
+  if (der::Nested(input, der::SEQUENCE,
+                  bind(CertID, _1, cref(context), ref(match)))
+        != der::Success) {
+    return der::Failure;
+  }
+
+  if (!match) {
+    return der::Success;
+  }
+
+  // CertStatus ::= CHOICE {
+  //     good        [0]     IMPLICIT NULL,
+  //     revoked     [1]     IMPLICIT RevokedInfo,
+  //     unknown     [2]     IMPLICIT UnknownInfo }
+  //
+  // In the event of multiple SingleResponses for a cert that have conflicting
+  // statuses, we use the following precedence rules:
+  //
+  // * revoked overrides good and unknown
+  // * good overrides unknown
+  if (input.Peek(static_cast<uint8_t>(CertStatus::Good))) {
+    if (ExpectTagAndLength(input, static_cast<uint8_t>(CertStatus::Good), 0)
+          != der::Success) {
+      return der::Failure;
+    }
+    if (context.certStatus != CertStatus::Revoked) {
+      context.certStatus = CertStatus::Good;
+    }
+  } else if (input.Peek(static_cast<uint8_t>(CertStatus::Revoked))) {
+    // We don't need any info from the RevokedInfo structure, so we don't even
+    // parse it. TODO: We should mention issues like this in the explanation of
+    // why we treat invalid OCSP responses equivalently to revoked for OCSP
+    // stapling.
+    if (der::Skip(input, static_cast<uint8_t>(CertStatus::Revoked))
+          != der::Success) {
+      return der::Failure;
+    }
+    context.certStatus = CertStatus::Revoked;
+  } else if (ExpectTagAndLength(input,
+                                static_cast<uint8_t>(CertStatus::Unknown),
+                                0) != der::Success) {
+    return der::Failure;
+  }
+
+  // http://tools.ietf.org/html/rfc6960#section-3.2
+  // 5. The time at which the status being indicated is known to be
+  //    correct (thisUpdate) is sufficiently recent;
+  // 6. When available, the time at or before which newer information will
+  //    be available about the status of the certificate (nextUpdate) is
+  //    greater than the current time.
+
+  // We won't accept any OCSP responses that are more than 10 days old, even if
+  // the nextUpdate time is further in the future.
+  static const PRTime OLDEST_ACCEPTABLE = INT64_C(10) * ONE_DAY;
+
+  PRTime thisUpdate;
+  if (der::GeneralizedTime(input, thisUpdate) != der::Success) {
+    return der::Failure;
+  }
+
+  if (thisUpdate > context.time + SLOP) {
+    return der::Fail(SEC_ERROR_OCSP_FUTURE_RESPONSE);
+  }
+
+  PRTime notAfter;
+  static const uint8_t NEXT_UPDATE_TAG =
+    der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0;
+  if (input.Peek(NEXT_UPDATE_TAG)) {
+    PRTime nextUpdate;
+    if (der::Nested(input, NEXT_UPDATE_TAG,
+                    bind(der::GeneralizedTime, _1, ref(nextUpdate)))
+          != der::Success) {
+      return der::Failure;
+    }
+
+    if (nextUpdate < thisUpdate) {
+      return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
+    }
+    if (nextUpdate - thisUpdate <= OLDEST_ACCEPTABLE) {
+      notAfter = nextUpdate;
+    } else {
+      notAfter = thisUpdate + OLDEST_ACCEPTABLE;
+    }
+  } else {
+    // NSS requires all OCSP responses without a nextUpdate to be recent.
+    // Match that stricter behavior.
+    notAfter = thisUpdate + ONE_DAY;
+  }
+
+  if (context.time < SLOP) { // prevent underflow
+    return der::Fail(SEC_ERROR_INVALID_ARGS);
+  }
+  if (context.time - SLOP > notAfter) {
+    return der::Fail(SEC_ERROR_OCSP_OLD_RESPONSE);
+  }
+
+
+  if (!input.AtEnd()) {
+    if (CheckExtensionsForCriticality(input) != der::Success) {
+      return der::Failure;
+    }
+  }
+
+  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 }
+static inline der::Result
+CertID(der::Input& input, const Context& context, /*out*/ bool& match)
+{
+  match = false;
+
+  SECAlgorithmID hashAlgorithm;
+  if (der::Nested(input, der::SEQUENCE,
+                  bind(der::AlgorithmIdentifier, _1, ref(hashAlgorithm)))
+         != der::Success) {
+    return der::Failure;
+  }
+
+  SECItem issuerNameHash;
+  if (der::Skip(input, der::OCTET_STRING, issuerNameHash) != der::Success) {
+    return der::Failure;
+  }
+
+  SECItem issuerKeyHash;
+  if (der::Skip(input, der::OCTET_STRING, issuerKeyHash) != der::Success) {
+    return der::Failure;
+  }
+
+  SECItem serialNumber;
+  if (der::CertificateSerialNumber(input, serialNumber) != der::Success) {
+    return der::Failure;
+  }
+
+  const CERTCertificate& cert = context.cert;
+  const CERTCertificate& issuerCert = context.issuerCert;
+
+  if (!SECITEM_ItemsAreEqual(&serialNumber, &cert.serialNumber)) {
+    return der::Success;
+  }
+
+  // TODO: support SHA-2 hashes.
+
+  SECOidTag hashAlg = SECOID_GetAlgorithmTag(&hashAlgorithm);
+  if (hashAlg != SEC_OID_SHA1) {
+    return der::Success;
+  }
+
+  if (issuerNameHash.len != SHA1_LENGTH) {
+    return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
+  }
+
+  // From http://tools.ietf.org/html/rfc6960#section-4.1.1:
+  // "The hash shall be calculated over the DER encoding of the
+  // issuer's name field in the certificate being checked."
+  uint8_t hashBuf[SHA1_LENGTH];
+  if (PK11_HashBuf(SEC_OID_SHA1, hashBuf, cert.derIssuer.data,
+                   cert.derIssuer.len) != SECSuccess) {
+    return der::Failure;
+  }
+  if (memcmp(hashBuf, issuerNameHash.data, issuerNameHash.len)) {
+    return der::Success;
+  }
+
+  return MatchIssuerKey(issuerKeyHash, issuerCert, match);
+}
+
+// From http://tools.ietf.org/html/rfc6960#section-4.1.1:
+// "The hash shall be calculated over the value (excluding tag and length) of
+// the subject public key field in the issuer's certificate."
+static der::Result
+MatchIssuerKey(const SECItem& issuerKeyHash, const CERTCertificate& issuer,
+               /*out*/ bool& match)
+{
+  if (issuerKeyHash.len != SHA1_LENGTH)  {
+    return der::Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE);
+  }
+
+  // TODO(bug 966856): support SHA-2 hashes
+
+  // Copy just the length and data pointer (nothing needs to be freed) of the
+  // subject public key so we can convert the length from bits to bytes, which
+  // is what the digest function expects.
+  SECItem spk = issuer.subjectPublicKeyInfo.subjectPublicKey;
+  DER_ConvertBitString(&spk);
+
+  static uint8_t hashBuf[SHA1_LENGTH];
+  if (PK11_HashBuf(SEC_OID_SHA1, hashBuf, spk.data, spk.len) != SECSuccess) {
+    return der::Failure;
+  }
+
+  match = !memcmp(hashBuf, issuerKeyHash.data, issuerKeyHash.len);
+  return der::Success;
+}
+
+// Extension  ::=  SEQUENCE  {
+//      extnID      OBJECT IDENTIFIER,
+//      critical    BOOLEAN DEFAULT FALSE,
+//      extnValue   OCTET STRING
+//      }
+static der::Result
+CheckExtensionForCriticality(der::Input& input)
+{
+  uint16_t toSkip;
+  if (ExpectTagAndGetLength(input, der::OIDTag, toSkip) != der::Success) {
+    return der::Failure;
+  }
+
+  // TODO: maybe we should check the syntax of the OID value
+  if (input.Skip(toSkip) != der::Success) {
+    return der::Failure;
+  }
+
+  // The only valid explicit encoding of the value is TRUE, so don't even
+  // bother parsing it, since we're going to fail either way.
+  if (input.Peek(der::BOOLEAN)) {
+    return der::Fail(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION);
+  }
+
+  if (ExpectTagAndGetLength(input, der::OCTET_STRING, toSkip)
+        != der::Success) {
+    return der::Failure;
+  }
+  return input.Skip(toSkip);
+}
+
+static der::Result
+CheckExtensionsForCriticality(der::Input& input)
+{
+  return der::NestedOf(input, der::SEQUENCE | 1, der::SEQUENCE,
+                       der::MustNotBeEmpty, CheckExtensionForCriticality);
+}
+
+//   1. The certificate identified in a received response corresponds to
+//      the certificate that was identified in the corresponding request;
+//   2. The signature on the response is valid;
+//   3. The identity of the signer matches the intended recipient of the
+//      request;
+//   4. The signer is currently authorized to provide a response for the
+//      certificate in question;
+//   5. The time at which the status being indicated is known to be
+//      correct (thisUpdate) is sufficiently recent;
+//   6. When available, the time at or before which newer information will
+//      be available about the status of the certificate (nextUpdate) is
+//      greater than the current time.
+//
+//   Responses whose nextUpdate value is earlier than
+//   the local system time value SHOULD be considered unreliable.
+//   Responses whose thisUpdate time is later than the local system time
+//   SHOULD be considered unreliable.
+//
+//   If nextUpdate is not set, the responder is indicating that newer
+//   revocation information is available all the time.
+//
+// http://tools.ietf.org/html/rfc5019#section-4
+
+SECItem*
+CreateEncodedOCSPRequest(PLArenaPool* arena,
+                         const CERTCertificate* cert,
+                         const CERTCertificate* issuerCert)
+{
+  if (!arena || !cert || !issuerCert) {
+    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+    return nullptr;
+  }
+
+  // We do not add any extensions to the request.
+
+  // RFC 6960 says "An OCSP client MAY wish to specify the kinds of response
+  // types it understands. To do so, it SHOULD use an extension with the OID
+  // id-pkix-ocsp-response." This use of MAY and SHOULD is unclear. MSIE11
+  // on Windows 8.1 does not include any extensions, whereas NSS has always
+  // included the id-pkix-ocsp-response extension. Avoiding the sending the
+  // extension is better for OCSP GET because it makes the request smaller,
+  // and thus more likely to fit within the 255 byte limit for OCSP GET that
+  // is specified in RFC 5019 Section 5.
+
+  // Bug 966856: Add the id-pkix-ocsp-pref-sig-algs extension.
+
+  // Since we don't know whether the OCSP responder supports anything other
+  // than SHA-1, we have no choice but to use SHA-1 for issuerNameHash and
+  // issuerKeyHash.
+  static const uint8_t hashAlgorithm[11] = {
+    0x30, 0x09,                               // SEQUENCE
+    0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, //   OBJECT IDENTIFIER id-sha1
+    0x05, 0x00,                               //   NULL
+  };
+  static const uint8_t hashLen = SHA1_LENGTH;
+
+  static const unsigned int totalLenWithoutSerialNumberData
+    = 2                             // OCSPRequest
+    + 2                             //   tbsRequest
+    + 2                             //     requestList
+    + 2                             //       Request
+    + 2                             //         reqCert (CertID)
+    + PR_ARRAY_SIZE(hashAlgorithm)  //           hashAlgorithm
+    + 2 + hashLen                   //           issuerNameHash
+    + 2 + hashLen                   //           issuerKeyHash
+    + 2;                            //           serialNumber (header)
+
+  // The only way we could have a request this large is if the serialNumber was
+  // ridiculously and unreasonably large. RFC 5280 says "Conforming CAs MUST
+  // NOT use serialNumber values longer than 20 octets." With this restriction,
+  // we allow for some amount of non-conformance with that requirement while
+  // still ensuring we can encode the length values in the ASN.1 TLV structures
+  // in a single byte.
+  if (issuerCert->serialNumber.len > 127u - totalLenWithoutSerialNumberData) {
+    PR_SetError(SEC_ERROR_BAD_DATA, 0);
+    return nullptr;
+  }
+
+  uint8_t totalLen = static_cast<uint8_t>(totalLenWithoutSerialNumberData +
+    cert->serialNumber.len);
+
+  SECItem* encodedRequest = SECITEM_AllocItem(arena, nullptr, totalLen);
+  if (!encodedRequest) {
+    return nullptr;
+  }
+
+  uint8_t* d = encodedRequest->data;
+  *d++ = 0x30; *d++ = totalLen - 2;  // OCSPRequest (SEQUENCE)
+  *d++ = 0x30; *d++ = totalLen - 4;  //   tbsRequest (SEQUENCE)
+  *d++ = 0x30; *d++ = totalLen - 6;  //     requestList (SEQUENCE OF)
+  *d++ = 0x30; *d++ = totalLen - 8;  //       Request (SEQUENCE)
+  *d++ = 0x30; *d++ = totalLen - 10; //         reqCert (CertID SEQUENCE)
+
+  // reqCert.hashAlgorithm
+  for (size_t i = 0; i < PR_ARRAY_SIZE(hashAlgorithm); ++i) {
+    *d++ = hashAlgorithm[i];
+  }
+
+  // reqCert.issuerNameHash (OCTET STRING)
+  *d++ = 0x04;
+  *d++ = hashLen;
+  if (PK11_HashBuf(SEC_OID_SHA1, d, issuerCert->derSubject.data,
+                   issuerCert->derSubject.len) != SECSuccess) {
+    return nullptr;
+  }
+  d += hashLen;
+
+  // reqCert.issuerKeyHash (OCTET STRING)
+  *d++ = 0x04;
+  *d++ = hashLen;
+  SECItem key = issuerCert->subjectPublicKeyInfo.subjectPublicKey;
+  DER_ConvertBitString(&key);
+  if (PK11_HashBuf(SEC_OID_SHA1, d, key.data, key.len) != SECSuccess) {
+    return nullptr;
+  }
+  d += hashLen;
+
+  // reqCert.serialNumber (INTEGER)
+  *d++ = 0x02; // INTEGER
+  *d++ = static_cast<uint8_t>(cert->serialNumber.len);
+  for (size_t i = 0; i < cert->serialNumber.len; ++i) {
+    *d++ = cert->serialNumber.data[i];
+  }
+
+  PR_ASSERT(d == encodedRequest->data + totalLen);
+
+  return encodedRequest;
+}
+
+} } // namespace insanity::pkix
--- a/security/insanity/lib/pkixutil.h
+++ b/security/insanity/lib/pkixutil.h
@@ -104,17 +104,22 @@ public:
 
   const SECItem* encodedBasicConstraints;
   const SECItem* encodedExtendedKeyUsage;
   const SECItem* encodedKeyUsage;
   const SECItem* encodedNameConstraints;
 
   BackCert* const childCert;
 
-  const CERTCertificate* GetNSSCert() const { return nssCert; }
+  // Only non-const so that we can pass this to TrustDomain::IsRevoked,
+  // which only takes a non-const pointer because VerifyEncodedOCSPResponse
+  // requires it, and that is only because the implementation of
+  // VerifyEncodedOCSPResponse does a CERT_DupCertificate. TODO: get rid
+  // of that CERT_DupCertificate so that we can make all these things const.
+  /*const*/ CERTCertificate* GetNSSCert() const { return nssCert; }
 
   // Returns the names that should be considered when evaluating name
   // constraints. The list is constructed lazily and cached. The result is a
   // weak reference; do not try to free it, and do not hold long-lived
   // references to it.
   Result GetConstrainedNames(/*out*/ const CERTGeneralName** result);
 
   // This is the only place where we should be dealing with non-const