Bug 744204 - Allow Certificate key pinning Part 2 - Certverifier Interface. r=keeler
authorCamilo Viecco <cviecco@mozilla.com>
Wed, 05 Feb 2014 14:49:10 -0800
changeset 201419 affd460bc3d7ee6d8a6347bd7ae7faa4c7dc1ecd
parent 201418 d411b847239193691525f8451ecb8275c5260a62
child 201420 ab6da1212288078c906efa56182464c3cbcce436
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs744204
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 744204 - Allow Certificate key pinning Part 2 - Certverifier Interface. r=keeler
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/manager/ssl/src/SSLServerCertVerification.cpp
security/manager/ssl/src/SharedCertVerifier.h
security/manager/ssl/src/nsCMS.cpp
security/manager/ssl/src/nsNSSCertificate.cpp
security/manager/ssl/src/nsNSSCertificateDB.cpp
security/manager/ssl/src/nsNSSComponent.cpp
security/manager/ssl/src/nsUsageArrayHelper.cpp
security/pkix/include/pkix/pkixtypes.h
security/pkix/lib/pkixbuild.cpp
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -30,16 +30,18 @@ public:
                                  MOZ_OVERRIDE;
   SECStatus VerifySignedData(const CERTSignedData* signedData,
                              const CERTCertificate* cert) MOZ_OVERRIDE;
   SECStatus CheckRevocation(mozilla::pkix::EndEntityOrCA endEntityOrCA,
                             const CERTCertificate* cert,
                             /*const*/ CERTCertificate* issuerCertToDup,
                             PRTime time,
                             /*optional*/ const SECItem* stapledOCSPresponse);
+  SECStatus IsChainValid(const CERTCertList* certChain) { return SECSuccess; }
+
 private:
   void* mPinArg; // non-owning!
   mozilla::pkix::ScopedCERTCertificate mTrustedRoot;
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm_AppsTrustDomain_h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -6,19 +6,21 @@
 
 #include "CertVerifier.h"
 
 #include <stdint.h>
 
 #include "pkix/pkix.h"
 #include "ExtendedValidation.h"
 #include "NSSCertDBTrustDomain.h"
+#include "PublicKeyPinningService.h"
 #include "cert.h"
 #include "ocsp.h"
 #include "secerr.h"
+#include "pk11pub.h"
 #include "prerror.h"
 #include "sslerr.h"
 
 // ScopedXXX in this file are mozilla::pkix::ScopedXXX, not
 // mozilla::ScopedXXX.
 using namespace mozilla::pkix;
 using namespace mozilla::psm;
 
@@ -33,25 +35,27 @@ const CertVerifier::Flags CertVerifier::
 
 CertVerifier::CertVerifier(implementation_config ic,
 #ifndef NSS_NO_LIBPKIX
                            missing_cert_download_config mcdc,
                            crl_download_config cdc,
 #endif
                            ocsp_download_config odc,
                            ocsp_strict_config osc,
-                           ocsp_get_config ogc)
+                           ocsp_get_config ogc,
+                           pinning_enforcement_config pel)
   : mImplementation(ic)
 #ifndef NSS_NO_LIBPKIX
   , mMissingCertDownloadEnabled(mcdc == missing_cert_download_on)
   , mCRLDownloadEnabled(cdc == crl_download_allowed)
 #endif
   , mOCSPDownloadEnabled(odc == ocsp_on)
   , mOCSPStrict(osc == ocsp_strict)
   , mOCSPGETEnabled(ogc == ocsp_get_enabled)
+  , mPinningEnforcementLevel(pel)
 {
 }
 
 CertVerifier::~CertVerifier()
 {
 }
 
 void
@@ -59,17 +63,16 @@ InitCertVerifierLog()
 {
 #ifdef PR_LOGGING
   if (!gCertVerifierLog) {
     gCertVerifierLog = PR_NewLogModule("certverifier");
   }
 #endif
 }
 
-#if 0
 // Once we migrate to mozilla::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));
@@ -90,96 +93,197 @@ insertErrorIntoVerifyLog(CERTCertificate
   verifyLog->head = node;
   if (!verifyLog->tail) {
     verifyLog->tail = node;
   }
   verifyLog->count++;
 
   return SECSuccess;
 }
-#endif
+
+SECStatus
+IsCertBuiltInRoot(CERTCertificate* cert, bool& result) {
+  result = false;
+  ScopedPtr<PK11SlotList, PK11_FreeSlotList> slots;
+  slots = PK11_GetAllSlotsForCert(cert, nullptr);
+  if (!slots) {
+    if (PORT_GetError() == SEC_ERROR_NO_TOKEN) {
+      // no list
+      return SECSuccess;
+    }
+    return SECFailure;
+  }
+  for (PK11SlotListElement* le = slots->head; le; le = le->next) {
+    char* token = PK11_GetTokenName(le->slot);
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+           ("BuiltInRoot? subject=%s token=%s",cert->subjectName, token));
+    if (strcmp("Builtin Object Token", token) == 0) {
+      result = true;
+      return SECSuccess;
+    }
+  }
+  return SECSuccess;
+}
+
+struct ChainValidationCallbackState
+{
+  const char* hostname;
+  const CertVerifier::pinning_enforcement_config pinningEnforcementLevel;
+  const SECCertificateUsage usage;
+  const PRTime time;
+};
 
 SECStatus chainValidationCallback(void* state, const CERTCertList* certList,
                                   PRBool* chainOK)
 {
+  ChainValidationCallbackState* callbackState =
+    reinterpret_cast<ChainValidationCallbackState*>(state);
+
   *chainOK = PR_FALSE;
 
-  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("verifycert: Inside the Callback \n"));
+  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_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+           ("verifycert: Short circuit, callback, sanity check failed \n"));
+    PR_SetError(PR_INVALID_STATE_ERROR, 0);
+    return SECFailure;
+  }
+  if (!callbackState) {
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+           ("verifycert: Short circuit, callback, no state! \n"));
     PR_SetError(PR_INVALID_STATE_ERROR, 0);
     return SECFailure;
   }
-  *chainOK = PR_TRUE;
+
+  if (callbackState->usage != certificateUsageSSLServer ||
+      callbackState->pinningEnforcementLevel == CertVerifier::pinningDisabled) {
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+           ("verifycert: Callback shortcut pel=%d \n",
+            callbackState->pinningEnforcementLevel));
+    *chainOK = PR_TRUE;
+    return SECSuccess;
+  }
+
+  for (CERTCertListNode* node = CERT_LIST_HEAD(certList);
+       !CERT_LIST_END(node, certList);
+       node = CERT_LIST_NEXT(node)) {
+    CERTCertificate* currentCert = node->cert;
+    if (CERT_LIST_END(CERT_LIST_NEXT(node), certList)) {
+      bool isBuiltInRoot = false;
+      SECStatus srv = IsCertBuiltInRoot(currentCert, isBuiltInRoot);
+      if (srv != SECSuccess) {
+        PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Is BuiltInRoot failure"));
+        return srv;
+      }
+      // If desired, the user can enable "allow user CA MITM mode", in which
+      // case key pinning is not enforced for certificates that chain to trust
+      // anchors that are not in Mozilla's root program
+      if (!isBuiltInRoot &&
+          (callbackState->pinningEnforcementLevel ==
+             CertVerifier::pinningAllowUserCAMITM)) {
+        *chainOK = PR_TRUE;
+        return SECSuccess;
+      }
+    }
+  }
+
+  *chainOK = PublicKeyPinningService::
+               ChainHasValidPins(certList, callbackState->hostname,
+                                 callbackState->time);
+
   return SECSuccess;
 }
 
 static SECStatus
 ClassicVerifyCert(CERTCertificate* cert,
                   const SECCertificateUsage usage,
                   const PRTime time,
                   void* pinArg,
+                  ChainValidationCallbackState* callbackState,
                   /*optional out*/ ScopedCERTCertList* validationChain,
                   /*optional out*/ CERTVerifyLog* verifyLog)
 {
   SECStatus rv;
   SECCertUsage enumUsage;
-  if (validationChain) {
-    switch(usage){
-      case  certificateUsageSSLClient:
-        enumUsage = certUsageSSLClient;
-        break;
-      case  certificateUsageSSLServer:
-        enumUsage = certUsageSSLServer;
-        break;
-      case certificateUsageSSLCA:
-        enumUsage = certUsageSSLCA;
-        break;
-      case certificateUsageEmailSigner:
-        enumUsage = certUsageEmailSigner;
-        break;
-      case certificateUsageEmailRecipient:
-        enumUsage = certUsageEmailRecipient;
-        break;
-      case certificateUsageObjectSigner:
-        enumUsage = certUsageObjectSigner;
-        break;
-      case certificateUsageVerifyCA:
-        enumUsage = certUsageVerifyCA;
-        break;
-      case certificateUsageStatusResponder:
-        enumUsage = certUsageStatusResponder;
-        break;
-      default:
-        PR_NOT_REACHED("unexpected usage");
-        PORT_SetError(SEC_ERROR_INVALID_ARGS);
-        return SECFailure;
-    }
+  switch (usage) {
+    case certificateUsageSSLClient:
+      enumUsage = certUsageSSLClient;
+      break;
+    case certificateUsageSSLServer:
+      enumUsage = certUsageSSLServer;
+      break;
+    case certificateUsageSSLCA:
+      enumUsage = certUsageSSLCA;
+      break;
+    case certificateUsageEmailSigner:
+      enumUsage = certUsageEmailSigner;
+      break;
+    case certificateUsageEmailRecipient:
+      enumUsage = certUsageEmailRecipient;
+      break;
+    case certificateUsageObjectSigner:
+      enumUsage = certUsageObjectSigner;
+      break;
+    case certificateUsageVerifyCA:
+      enumUsage = certUsageVerifyCA;
+      break;
+    case certificateUsageStatusResponder:
+      enumUsage = certUsageStatusResponder;
+      break;
+    default:
+      PR_NOT_REACHED("unexpected usage");
+      PORT_SetError(SEC_ERROR_INVALID_ARGS);
+      return SECFailure;
   }
   if (usage == certificateUsageSSLServer) {
     // SSL server cert verification has always used CERT_VerifyCert, so we
     // continue to use it for SSL cert verification to minimize the risk of
     // there being any differnce in results between CERT_VerifyCert and
     // CERT_VerifyCertificate.
     rv = CERT_VerifyCert(CERT_GetDefaultCertDB(), cert, true,
                          certUsageSSLServer, time, pinArg, verifyLog);
   } else {
     rv = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), cert, true,
                                 usage, time, pinArg, verifyLog, nullptr);
   }
-  if (rv == SECSuccess && validationChain) {
-    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("VerifyCert: getting chain in 'classic' \n"));
-    *validationChain = CERT_GetCertChainFromCert(cert, time, enumUsage);
-    if (!*validationChain) {
-      rv = SECFailure;
+
+  if (rv == SECSuccess &&
+      (validationChain || usage == certificateUsageSSLServer)) {
+    PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+           ("VerifyCert: getting chain in 'classic' \n"));
+    ScopedCERTCertList certChain(CERT_GetCertChainFromCert(cert, time,
+                                                           enumUsage));
+    if (!certChain) {
+      return SECFailure;
+    }
+    if (usage == certificateUsageSSLServer) {
+      PRBool chainOK = PR_FALSE;
+      SECStatus srv = chainValidationCallback(callbackState, certChain.get(),
+                                              &chainOK);
+      if (srv != SECSuccess) {
+        return srv;
+      }
+      if (chainOK != PR_TRUE) {
+        if (verifyLog) {
+          insertErrorIntoVerifyLog(cert,
+                                   SEC_ERROR_APPLICATION_CALLBACK_ERROR,
+                                   verifyLog);
+        }
+        PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0); // same as libpkix
+        return SECFailure;
+      }
+    }
+    if (rv == SECSuccess && validationChain) {
+      *validationChain = certChain.release();
     }
   }
+
   return rv;
 }
 
 #ifndef NSS_NO_LIBPKIX
 static void
 destroyCertListThatShouldNotExist(CERTCertList** certChain)
 {
   PR_ASSERT(certChain);
@@ -227,16 +331,17 @@ BuildCertChainForOneKeyUsage(TrustDomain
 
 SECStatus
 CertVerifier::MozillaPKIXVerifyCert(
                    CERTCertificate* cert,
                    const SECCertificateUsage usage,
                    const PRTime time,
                    void* pinArg,
                    const Flags flags,
+                   ChainValidationCallbackState* callbackState,
       /*optional*/ const SECItem* stapledOCSPResponse,
   /*optional out*/ mozilla::pkix::ScopedCERTCertList* validationChain,
   /*optional out*/ SECOidTag* evOidPolicy)
 {
   PR_LOG(gCertVerifierLog, PR_LOG_DEBUG, ("Top of MozillaPKIXVerifyCert\n"));
 
   PR_ASSERT(cert);
   PR_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV));
@@ -249,16 +354,20 @@ CertVerifier::MozillaPKIXVerifyCert(
   }
 
   if (!cert ||
       (usage != certificateUsageSSLServer && (flags & FLAG_MUST_BE_EV))) {
     PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
     return SECFailure;
   }
 
+  CERTChainVerifyCallback callbackContainer;
+  callbackContainer.isChainValid = chainValidationCallback;
+  callbackContainer.isChainValidArg = callbackState;
+
   NSSCertDBTrustDomain::OCSPFetching ocspFetching
     = !mOCSPDownloadEnabled ||
       (flags & FLAG_LOCAL_ONLY) ? NSSCertDBTrustDomain::NeverFetchOCSP
     : !mOCSPStrict              ? NSSCertDBTrustDomain::FetchOCSPForDVSoftFail
                                 : NSSCertDBTrustDomain::FetchOCSPForDVHardFail;
 
   SECStatus rv;
 
@@ -295,17 +404,17 @@ CertVerifier::MozillaPKIXVerifyCert(
       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,
-                      mOCSPCache, pinArg);
+                      mOCSPCache, pinArg, &callbackContainer);
         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) {
@@ -321,17 +430,17 @@ CertVerifier::MozillaPKIXVerifyCert(
       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, mOCSPCache,
-                                       pinArg);
+                                       pinArg, &callbackContainer);
       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;
@@ -438,29 +547,35 @@ CertVerifier::MozillaPKIXVerifyCert(
     *validationChain = builtChain.release();
   }
 
   return rv;
 }
 
 SECStatus
 CertVerifier::VerifyCert(CERTCertificate* cert,
-            /*optional*/ const SECItem* stapledOCSPResponse,
                          const SECCertificateUsage usage,
                          const PRTime time,
                          void* pinArg,
+                         const char* hostname,
                          const Flags flags,
+                         /*optional in*/ const SECItem* stapledOCSPResponse,
                          /*optional out*/ ScopedCERTCertList* validationChain,
                          /*optional out*/ SECOidTag* evOidPolicy,
                          /*optional out*/ CERTVerifyLog* verifyLog)
 {
+  ChainValidationCallbackState callbackState = { hostname,
+                                                 mPinningEnforcementLevel,
+                                                 usage,
+                                                 time };
+
   if (mImplementation == mozillapkix) {
     return MozillaPKIXVerifyCert(cert, usage, time, pinArg, flags,
-                                 stapledOCSPResponse, validationChain,
-                                 evOidPolicy);
+                                 &callbackState, stapledOCSPResponse,
+                                 validationChain, evOidPolicy);
   }
 
   if (!cert)
   {
     PR_NOT_REACHED("Invalid arguments to CertVerifier::VerifyCert");
     PORT_SetError(SEC_ERROR_INVALID_ARGS);
     return SECFailure;
   }
@@ -576,17 +691,17 @@ CertVerifier::VerifyCert(CERTCertificate
   cvin[1].value.pointer.revocation = &rev;
   cvin[2].type = cert_pi_date;
   cvin[2].value.scalar.time = time;
   i = 3;
 
   CERTChainVerifyCallback callbackContainer;
   if (usage == certificateUsageSSLServer) {
     callbackContainer.isChainValid = chainValidationCallback;
-    callbackContainer.isChainValidArg = nullptr;
+    callbackContainer.isChainValidArg = &callbackState;
     cvin[i].type = cert_pi_chainVerifyCallback;
     cvin[i].value.pointer.chainVerifyCallback = &callbackContainer;
     ++i;
   }
 
   const size_t evParamLocation = i;
 
   if (evPolicy != SEC_OID_UNKNOWN) {
@@ -680,18 +795,18 @@ CertVerifier::VerifyCert(CERTCertificate
     PR_SetError(PR_INVALID_STATE_ERROR, 0);
 #endif
     return SECFailure;
   }
 
   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);
+    return ClassicVerifyCert(cert, usage, time, pinArg, &callbackState,
+                             validationChain, verifyLog);
   }
 
 #ifdef NSS_NO_LIBPKIX
   PR_NOT_REACHED("libpkix implementation chosen but not even compiled in");
   PR_SetError(PR_INVALID_STATE_ERROR, 0);
   return SECFailure;
 #else
   PR_ASSERT(mImplementation == libpkix);
@@ -821,19 +936,19 @@ CertVerifier::VerifySSLServerCert(CERTCe
   if (!hostname || !hostname[0]) {
     PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
     return SECFailure;
   }
 
   // CreateCertErrorRunnable assumes that CERT_VerifyCertName is only called
   // if VerifyCert succeeded.
   ScopedCERTCertList validationChain;
-  SECStatus rv = VerifyCert(peerCert, stapledOCSPResponse,
-                            certificateUsageSSLServer, time,
-                            pinarg, 0, &validationChain, evOidPolicy);
+  SECStatus rv = VerifyCert(peerCert, certificateUsageSSLServer, time, pinarg,
+                            hostname, 0, stapledOCSPResponse, &validationChain,
+                            evOidPolicy, nullptr);
   if (rv != SECSuccess) {
     return rv;
   }
 
   rv = CERT_VerifyCertName(peerCert, hostname);
   if (rv != SECSuccess) {
     return rv;
   }
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -7,33 +7,36 @@
 #ifndef mozilla_psm__CertVerifier_h
 #define mozilla_psm__CertVerifier_h
 
 #include "pkix/pkixtypes.h"
 #include "OCSPCache.h"
 
 namespace mozilla { namespace psm {
 
+struct ChainValidationCallbackState;
+
 class CertVerifier
 {
 public:
   typedef unsigned int Flags;
   // XXX: FLAG_LOCAL_ONLY is ignored in the classic verification case
   static const Flags FLAG_LOCAL_ONLY;
   // Don't perform fallback DV validation on EV validation failure.
   static const Flags FLAG_MUST_BE_EV;
 
   // *evOidPolicy == SEC_OID_UNKNOWN means the cert is NOT EV
   // Only one usage per verification is supported.
   SECStatus VerifyCert(CERTCertificate* cert,
-          /*optional*/ const SECItem* stapledOCSPResponse,
                        const SECCertificateUsage usage,
                        const PRTime time,
                        void* pinArg,
+                       const char* hostname,
                        const Flags flags = 0,
+       /*optional in*/ const SECItem* stapledOCSPResponse = nullptr,
       /*optional out*/ mozilla::pkix::ScopedCERTCertList* validationChain = nullptr,
       /*optional out*/ SECOidTag* evOidPolicy = nullptr ,
       /*optional out*/ CERTVerifyLog* verifyLog = nullptr);
 
   SECStatus VerifySSLServerCert(
                     CERTCertificate* peerCert,
        /*optional*/ const SECItem* stapledOCSPResponse,
                     PRTime time,
@@ -47,49 +50,58 @@ public:
   enum implementation_config {
     classic = 0,
 #ifndef NSS_NO_LIBPKIX
     libpkix = 1,
 #endif
     mozillapkix = 2
   };
 
+  enum pinning_enforcement_config {
+    pinningDisabled = 0,
+    pinningAllowUserCAMITM = 1,
+    pinningStrict = 2
+  };
+
   enum missing_cert_download_config { missing_cert_download_off = 0, missing_cert_download_on };
   enum crl_download_config { crl_local_only = 0, crl_download_allowed };
   enum ocsp_download_config { ocsp_off = 0, ocsp_on };
   enum ocsp_strict_config { ocsp_relaxed = 0, ocsp_strict };
   enum ocsp_get_config { ocsp_get_disabled = 0, ocsp_get_enabled = 1 };
 
   bool IsOCSPDownloadEnabled() const { return mOCSPDownloadEnabled; }
 
   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);
+               ocsp_get_config ogc,
+               pinning_enforcement_config pinningEnforcementLevel);
   ~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;
+  const pinning_enforcement_config mPinningEnforcementLevel;
 
 private:
   SECStatus MozillaPKIXVerifyCert(CERTCertificate* cert,
       const SECCertificateUsage usage,
       const PRTime time,
       void* pinArg,
       const Flags flags,
+      ChainValidationCallbackState* callbackState,
       /*optional*/ const SECItem* stapledOCSPResponse,
       /*optional out*/ mozilla::pkix::ScopedCERTCertList* validationChain,
       /*optional out*/ SECOidTag* evOidPolicy);
 
   OCSPCache mOCSPCache;
 };
 
 void InitCertVerifierLog();
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -37,21 +37,23 @@ namespace {
 
 typedef ScopedPtr<SECMODModule, SECMOD_DestroyModule> ScopedSECMODModule;
 
 } // unnamed namespace
 
 NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
                                            OCSPFetching ocspFetching,
                                            OCSPCache& ocspCache,
-                                           void* pinArg)
+                                           void* pinArg,
+                                           CERTChainVerifyCallback* checkChainCallback)
   : mCertDBTrustType(certDBTrustType)
   , mOCSPFetching(ocspFetching)
   , mOCSPCache(ocspCache)
   , mPinArg(pinArg)
+  , mCheckChainCallback(checkChainCallback)
 {
 }
 
 SECStatus
 NSSCertDBTrustDomain::FindPotentialIssuers(
   const SECItem* encodedIssuerName, PRTime time,
   /*out*/ mozilla::pkix::ScopedCERTCertList& results)
 {
@@ -400,16 +402,47 @@ NSSCertDBTrustDomain::VerifyAndMaybeCach
   // If the verification failed, re-set to that original error
   // (the call to Put may have un-set it).
   if (rv != SECSuccess) {
     PR_SetError(error, 0);
   }
   return rv;
 }
 
+SECStatus
+NSSCertDBTrustDomain::IsChainValid(const CERTCertList* certChain) {
+  SECStatus rv = SECFailure;
+
+  PR_LOG(gCertVerifierLog, PR_LOG_DEBUG,
+      ("NSSCertDBTrustDomain: Top of IsChainValid mCheckCallback=%p",
+       mCheckChainCallback));
+
+  if (!mCheckChainCallback) {
+    return SECSuccess;
+  }
+  if (!mCheckChainCallback->isChainValid) {
+    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+    return SECFailure;
+  }
+  PRBool chainOK;
+  rv = (mCheckChainCallback->isChainValid)(mCheckChainCallback->isChainValidArg,
+                                           certChain, &chainOK);
+  if (rv != SECSuccess) {
+    return rv;
+  }
+  // rv = SECSuccess only implies successful call, now is time
+  // to check the chain check status
+  // we should only return success if the chain is valid
+  if (chainOK) {
+    return SECSuccess;
+  }
+  PR_SetError(SEC_ERROR_APPLICATION_CALLBACK_ERROR, 0);
+  return SECFailure;
+}
+
 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
@@ -52,17 +52,18 @@ public:
   enum OCSPFetching {
     NeverFetchOCSP = 0,
     FetchOCSPForDVSoftFail = 1,
     FetchOCSPForDVHardFail = 2,
     FetchOCSPForEV = 3,
     LocalOnlyOCSPForEV = 4,
   };
   NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching,
-                       OCSPCache& ocspCache, void* pinArg);
+                       OCSPCache& ocspCache, void* pinArg,
+                       CERTChainVerifyCallback* checkChainCallback = nullptr);
 
   virtual SECStatus FindPotentialIssuers(
                         const SECItem* encodedIssuerName,
                         PRTime time,
                 /*out*/ mozilla::pkix::ScopedCERTCertList& results);
 
   virtual SECStatus GetCertTrust(mozilla::pkix::EndEntityOrCA endEntityOrCA,
                                  SECOidTag policy,
@@ -73,27 +74,30 @@ public:
                                      const CERTCertificate* cert);
 
   virtual SECStatus CheckRevocation(mozilla::pkix::EndEntityOrCA endEntityOrCA,
                                     const CERTCertificate* cert,
                           /*const*/ CERTCertificate* issuerCert,
                                     PRTime time,
                        /*optional*/ const SECItem* stapledOCSPResponse);
 
+  virtual SECStatus IsChainValid(const CERTCertList* certChain);
+
 private:
   enum EncodedResponseSource {
     ResponseIsFromNetwork = 1,
     ResponseWasStapled = 2
   };
   static const PRTime ServerFailureDelay = 5 * 60 * PR_USEC_PER_SEC;
   SECStatus VerifyAndMaybeCacheEncodedOCSPResponse(
     const CERTCertificate* cert, CERTCertificate* issuerCert, PRTime time,
     const SECItem* encodedResponse, EncodedResponseSource responseSource);
 
   const SECTrustType mCertDBTrustType;
   const OCSPFetching mOCSPFetching;
   OCSPCache& mOCSPCache; // non-owning!
   void* mPinArg; // non-owning!
+  CERTChainVerifyCallback* mCheckChainCallback; // non-owning!
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__NSSCertDBTrustDomain_h
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -11,16 +11,17 @@ UNIFIED_SOURCES += [
 ]
 
 if not CONFIG['NSS_NO_EV_CERTS']:
     UNIFIED_SOURCES += [
         'ExtendedValidation.cpp',
     ]
 
 LOCAL_INCLUDES += [
+    '../manager/boot/src',
     '../pkix/include',
 ]
 
 DIRS += [
     '../pkix',
 ]
 
 FAIL_ON_WARNINGS = True
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -629,18 +629,19 @@ NSSDetermineCertOverrideErrors(CertVerif
 
   // We ignore the result code of the cert verification (i.e. VerifyCert's rv)
   // Either it is a failure, which is expected, and we'll process the
   //                         verify log below.
   // Or it is a success, then a domain mismatch is the only
   //                     possible failure.
   // XXX TODO: convert to VerifySSLServerCert
   // XXX TODO: get rid of error log
-  certVerifier.VerifyCert(cert, stapledOCSPResponse, certificateUsageSSLServer,
-                          now, infoObject, 0, nullptr, nullptr, verify_log);
+  certVerifier.VerifyCert(cert, certificateUsageSSLServer,
+                          now, infoObject, infoObject->GetHostNameRaw(),
+                          0, stapledOCSPResponse, nullptr, nullptr, verify_log);
 
   // Check the name field against the desired hostname.
   if (CERT_VerifyCertName(cert, infoObject->GetHostNameRaw()) != SECSuccess) {
     collectedErrors |= nsICertOverrideService::ERROR_MISMATCH;
     errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN;
   }
 
   CERTVerifyLogNode* i_node;
--- a/security/manager/ssl/src/SharedCertVerifier.h
+++ b/security/manager/ssl/src/SharedCertVerifier.h
@@ -19,21 +19,23 @@ protected:
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedCertVerifier)
 
   SharedCertVerifier(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)
+                     ocsp_get_config ogc,
+                     pinning_enforcement_config pinningEnforcementLevel)
     : mozilla::psm::CertVerifier(ic,
 #ifndef NSS_NO_LIBPKIX
                                  ac, cdc,
 #endif
-                                 odc, osc, ogc)
+                                 odc, osc, ogc,
+                                 pinningEnforcementLevel)
   {
   }
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm__SharedCertVerifier_h
--- a/security/manager/ssl/src/nsCMS.cpp
+++ b/security/manager/ssl/src/nsCMS.cpp
@@ -259,19 +259,20 @@ nsresult nsCMSMessage::CommonVerifySigna
 
   // See bug 324474. We want to make sure the signing cert is 
   // still valid at the current time.
 
   certVerifier = GetDefaultCertVerifier();
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
 
   {
-    SECStatus srv = certVerifier->VerifyCert(si->cert, nullptr,
+    SECStatus srv = certVerifier->VerifyCert(si->cert,
                                              certificateUsageEmailSigner,
-                                             PR_Now(), nullptr /*XXX pinarg*/);
+                                             PR_Now(), nullptr /*XXX pinarg*/,
+                                             nullptr /*hostname*/);
     if (srv != SECSuccess) {
       PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
              ("nsCMSMessage::CommonVerifySignature - signing cert not trusted now\n"));
       rv = NS_ERROR_CMS_VERIFY_UNTRUSTED;
       goto loser;
     }
   }
 
--- a/security/manager/ssl/src/nsNSSCertificate.cpp
+++ b/security/manager/ssl/src/nsNSSCertificate.cpp
@@ -824,20 +824,22 @@ nsNSSCertificate::GetChain(nsIArray** _r
   PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("Getting chain for \"%s\"\n", mCert->nickname));
 
   ::mozilla::pkix::ScopedCERTCertList nssChain;
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
 
   // We want to test all usages, but we start with server because most of the
   // time Firefox users care about server certs.
-  certVerifier->VerifyCert(mCert.get(), nullptr,
+  certVerifier->VerifyCert(mCert.get(),
                            certificateUsageSSLServer, PR_Now(),
                            nullptr, /*XXX fixme*/
+                           nullptr, /* hostname */
                            CertVerifier::FLAG_LOCAL_ONLY,
+                           nullptr, /* stapledOCSPResponse */
                            &nssChain);
   // This is the whitelist of all non-SSLServer usages that are supported by
   // verifycert.
   const int otherUsagesToTest = certificateUsageSSLClient |
                                 certificateUsageSSLCA |
                                 certificateUsageEmailSigner |
                                 certificateUsageEmailRecipient |
                                 certificateUsageObjectSigner |
@@ -846,20 +848,22 @@ nsNSSCertificate::GetChain(nsIArray** _r
        usage < certificateUsageAnyCA && !nssChain;
        usage = usage << 1) {
     if ((usage & otherUsagesToTest) == 0) {
       continue;
     }
     PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
            ("pipnss: PKIX attempting chain(%d) for '%s'\n",
             usage, mCert->nickname));
-    certVerifier->VerifyCert(mCert.get(), nullptr,
+    certVerifier->VerifyCert(mCert.get(),
                              usage, PR_Now(),
                              nullptr, /*XXX fixme*/
+                             nullptr, /*hostname*/
                              CertVerifier::FLAG_LOCAL_ONLY,
+                             nullptr, /* stapledOCSPResponse */
                              &nssChain);
   }
 
   if (!nssChain) {
     // There is not verified path for the chain, howeever we still want to 
     // present to the user as much of a possible chain as possible, in the case
     // where there was a problem with the cert or the issuers.
     PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
@@ -1462,20 +1466,21 @@ nsNSSCertificate::hasValidEVOidTag(SECOi
     certVerifier(mozilla::psm::GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
 
   validEV = false;
   resultOidTag = SEC_OID_UNKNOWN;
 
   uint32_t flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY |
     mozilla::psm::CertVerifier::FLAG_MUST_BE_EV;
-  SECStatus rv = certVerifier->VerifyCert(mCert.get(), nullptr,
+  SECStatus rv = certVerifier->VerifyCert(mCert.get(),
     certificateUsageSSLServer, PR_Now(),
     nullptr /* XXX pinarg */,
-    flags, nullptr, &resultOidTag);
+    nullptr /* hostname */,
+    flags, nullptr /* stapledOCSPResponse */ , nullptr, &resultOidTag);
 
   if (rv != SECSuccess) {
     resultOidTag = SEC_OID_UNKNOWN;
   }
   if (resultOidTag != SEC_OID_UNKNOWN) {
     validEV = true;
   }
   return NS_OK;
--- a/security/manager/ssl/src/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/src/nsNSSCertificateDB.cpp
@@ -629,19 +629,20 @@ nsNSSCertificateDB::ImportEmailCertifica
        node = CERT_LIST_NEXT(node)) {
 
     if (!node->cert) {
       continue;
     }
 
     mozilla::pkix::ScopedCERTCertList certChain;
 
-    SECStatus rv = certVerifier->VerifyCert(node->cert, nullptr,
+    SECStatus rv = certVerifier->VerifyCert(node->cert,
                                             certificateUsageEmailRecipient,
-                                            now, ctx, 0, &certChain);
+                                            now, ctx, nullptr, 0,
+                                            nullptr, &certChain);
 
     if (rv != SECSuccess) {
       nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(node->cert);
       DisplayCertificateAlert(ctx, "NotImportingUnverifiedCert", certToShow, locker);
       continue;
     }
     rv = ImportCertsIntoPermanentStorage(certChain, certUsageEmailRecipient,
                                          false);
@@ -796,19 +797,20 @@ nsNSSCertificateDB::ImportValidCACertsIn
    * valid chains, if yes, then import.
    */
   CERTCertListNode *node;
 
   for (node = CERT_LIST_HEAD(certList);
        !CERT_LIST_END(node,certList);
        node = CERT_LIST_NEXT(node)) {
     mozilla::pkix::ScopedCERTCertList certChain;
-    SECStatus rv = certVerifier->VerifyCert(node->cert, nullptr,
+    SECStatus rv = certVerifier->VerifyCert(node->cert,
                                             certificateUsageVerifyCA,
-                                            PR_Now(), ctx, 0, &certChain);
+                                            PR_Now(), ctx, nullptr, 0, nullptr,
+                                            &certChain);
     if (rv != SECSuccess) {
       nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(node->cert);
       DisplayCertificateAlert(ctx, "NotImportingUnverifiedCert", certToShow, proofOfLock);
       continue;
     }
 
     rv = ImportCertsIntoPermanentStorage(certChain, certUsageAnyCA, true);
     if (rv != SECSuccess) {
@@ -1376,19 +1378,20 @@ nsNSSCertificateDB::FindCertByEmailAddre
     return NS_ERROR_FAILURE; // no certs found
 
   CERTCertListNode *node;
   // search for a valid certificate
   for (node = CERT_LIST_HEAD(certlist);
        !CERT_LIST_END(node, certlist);
        node = CERT_LIST_NEXT(node)) {
 
-    SECStatus srv = certVerifier->VerifyCert(node->cert, nullptr,
+    SECStatus srv = certVerifier->VerifyCert(node->cert,
                                              certificateUsageEmailRecipient,
-                                             PR_Now(), nullptr /*XXX pinarg*/);
+                                             PR_Now(), nullptr /*XXX pinarg*/,
+                                             nullptr /*hostname*/);
     if (srv == SECSuccess) {
       break;
     }
   }
 
   if (CERT_LIST_END(node, certlist)) {
     // no valid cert found
     return NS_ERROR_FAILURE;
@@ -1767,20 +1770,22 @@ nsNSSCertificateDB::VerifyCertNow(nsIX50
 
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_FAILURE);
 
   mozilla::pkix::ScopedCERTCertList resultChain;
   SECOidTag evOidPolicy;
   SECStatus srv;
 
-  srv = certVerifier->VerifyCert(nssCert, nullptr,
+  srv = certVerifier->VerifyCert(nssCert,
                                  aUsage, PR_Now(),
                                  nullptr, // Assume no context
+                                 nullptr, // hostname
                                  aFlags,
+                                 nullptr, // stapledOCSPResponse
                                  &resultChain,
                                  &evOidPolicy);
 
   PRErrorCode error = PR_GetError();
 
   nsCOMPtr<nsIX509CertList> nssCertList;
   // This adopts the list
   nssCertList = new nsNSSCertList(resultChain, locker);
--- a/security/manager/ssl/src/nsNSSComponent.cpp
+++ b/security/manager/ssl/src/nsNSSComponent.cpp
@@ -989,30 +989,48 @@ void nsNSSComponent::setValidationOption
     } else if (certVerifierImplementation == CertVerifier::libpkix) {
       Telemetry::Accumulate(Telemetry::CERT_VALIDATION_LIBRARY, 2);
 #endif
     } else if (certVerifierImplementation == CertVerifier::mozillapkix) {
       Telemetry::Accumulate(Telemetry::CERT_VALIDATION_LIBRARY, 3);
     }
   }
 
+  CertVerifier::pinning_enforcement_config
+    pinningEnforcementLevel = CertVerifier::pinningAllowUserCAMITM;
+  int prefPinningEnforcementLevel = Preferences::GetInt("security.cert_pinning.enforcement_level",
+                                                         pinningEnforcementLevel);
+  switch (prefPinningEnforcementLevel) {
+    case 0:
+      pinningEnforcementLevel = CertVerifier::pinningDisabled;
+      break;
+    case 1:
+      pinningEnforcementLevel = CertVerifier::pinningAllowUserCAMITM;
+      break;
+    case 2:
+      pinningEnforcementLevel = CertVerifier::pinningStrict;
+      break;
+    default:
+      pinningEnforcementLevel = CertVerifier::pinningAllowUserCAMITM;
+  }
+
   CertVerifier::ocsp_download_config odc;
   CertVerifier::ocsp_strict_config osc;
   CertVerifier::ocsp_get_config ogc;
 
   SetClassicOCSPBehaviorFromPrefs(&odc, &osc, &ogc, lock);
   mDefaultCertVerifier = new SharedCertVerifier(
       certVerifierImplementation,
 #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);
+      odc, osc, ogc, pinningEnforcementLevel);
 
   // mozilla::pkix has its own OCSP cache, so disable the NSS cache
   // if appropriate.
   if (certVerifierImplementation == CertVerifier::mozillapkix) {
     // 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 {
@@ -1625,17 +1643,18 @@ nsNSSComponent::Observe(nsISupports* aSu
     } else if (prefName.Equals("security.OCSP.enabled")
                || prefName.Equals("security.CRL_download.enabled")
                || prefName.Equals("security.fresh_revocation_info.require")
                || prefName.Equals("security.missing_cert_download.enabled")
                || prefName.Equals("security.OCSP.require")
                || prefName.Equals("security.OCSP.GET.enabled")
                || prefName.Equals("security.ssl.enable_ocsp_stapling")
                || prefName.Equals("security.use_mozillapkix_verification")
-               || prefName.Equals("security.use_libpkix_verification")) {
+               || prefName.Equals("security.use_libpkix_verification")
+               || prefName.Equals("security.cert_pinning.enforcement_level")) {
       MutexAutoLock lock(mutex);
       setValidationOptions(false, lock);
     } else if (prefName.Equals("network.ntlm.send-lm-response")) {
       bool sendLM = Preferences::GetBool("network.ntlm.send-lm-response",
                                          SEND_LM_DEFAULT);
       nsNTLMAuthModule::SetSendLM(sendLM);
       clearSessionCache = false;
     } else {
--- a/security/manager/ssl/src/nsUsageArrayHelper.cpp
+++ b/security/manager/ssl/src/nsUsageArrayHelper.cpp
@@ -100,18 +100,19 @@ nsUsageArrayHelper::check(uint32_t previ
     break;
   case certificateUsageStatusResponder:
     typestr = "VerifyStatusResponder";
     break;
   default:
     MOZ_CRASH("unknown cert usage passed to check()");
   }
 
-  SECStatus rv = certVerifier->VerifyCert(mCert, nullptr, aCertUsage,
-                         time, nullptr /*XXX:wincx*/, flags);
+  SECStatus rv = certVerifier->VerifyCert(mCert, aCertUsage, time,
+                                          nullptr /*XXX:wincx*/,
+                                          nullptr /*hostname*/, flags);
 
   if (rv == SECSuccess) {
     typestr.Append(suffix);
     nsAutoString verifyDesc;
     m_rv = nssComponent->GetPIPNSSBundleString(typestr.get(), verifyDesc);
     if (NS_SUCCEEDED(m_rv)) {
       outUsages[aCounter++] = ToNewUnicode(verifyDesc);
     }
--- a/security/pkix/include/pkix/pkixtypes.h
+++ b/security/pkix/include/pkix/pkixtypes.h
@@ -95,16 +95,21 @@ public:
   // 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;
 
+  // Called as soon as we think we have a valid chain but before revocation
+  // checks are done. Called to compute additional chain level checks, by the
+  // TrustDomain.
+  virtual SECStatus IsChainValid(const CERTCertList* certChain) = 0;
+
 protected:
   TrustDomain() { }
 
 private:
   TrustDomain(const TrustDomain&) /* = delete */;
   void operator=(const TrustDomain&) /* = delete */;
 };
 
--- a/security/pkix/lib/pkixbuild.cpp
+++ b/security/pkix/lib/pkixbuild.cpp
@@ -219,16 +219,40 @@ BuildForward(TrustDomain& trustDomain,
         trustLevel != TrustDomain::TrustAnchor) {
       deferredEndEntityError = PR_GetError();
     } else {
       return rv;
     }
   }
 
   if (trustLevel == TrustDomain::TrustAnchor) {
+    ScopedCERTCertList certChain(CERT_NewCertList());
+    if (!certChain) {
+      PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+      return MapSECStatus(SECFailure);
+    }
+
+    rv = subject.PrependNSSCertToList(certChain.get());
+    if (rv != Success) {
+      return rv;
+    }
+    BackCert* child = subject.childCert;
+    while (child) {
+      rv = child->PrependNSSCertToList(certChain.get());
+      if (rv != Success) {
+        return rv;
+      }
+      child = child->childCert;
+    }
+
+    SECStatus srv = trustDomain.IsChainValid(certChain.get());
+    if (srv != SECSuccess) {
+      return MapSECStatus(srv);
+    }
+
     // End of the recursion. Create the result list and add the trust anchor to
     // it.
     results = CERT_NewCertList();
     if (!results) {
       return FatalError;
     }
     rv = subject.PrependNSSCertToList(results.get());
     return rv;