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 181127 affd460bc3d7ee6d8a6347bd7ae7faa4c7dc1ecd
parent 181126 d411b847239193691525f8451ecb8275c5260a62
child 181128 ab6da1212288078c906efa56182464c3cbcce436
push id6596
push useremorley@mozilla.com
push dateThu, 01 May 2014 15:25:19 +0000
treeherderfx-team@a06b2ea13c72 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs744204
milestone32.0a1
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;