Bug 1713602 - Use NSS only on the socket thread in NSSCertDBTrustDomain::IsChainValid r=keeler
authorR. Martinho Fernandes <bugs@rmf.io>
Sat, 14 Aug 2021 02:11:30 +0000
changeset 588882 14c0d6cfcf6710f4d631d69344becb7f074edcb3
parent 588881 17252dafd099c9ac8d34ed68762ccb8921b18ba7
child 588883 c88594abf8442eda66bddc6dec89161773c832a8
push id148064
push userdkeeler@mozilla.com
push dateSat, 14 Aug 2021 02:13:55 +0000
treeherderautoland@14c0d6cfcf67 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs1713602
milestone93.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 1713602 - Use NSS only on the socket thread in NSSCertDBTrustDomain::IsChainValid r=keeler Differential Revision: https://phabricator.services.mozilla.com/D116879
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/certverifier/TrustOverrideUtils.h
security/certverifier/tests/gtest/TrustOverrideTest.cpp
security/ct/CTDiversityPolicy.cpp
security/ct/CTDiversityPolicy.h
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/TransportSecurityInfo.cpp
security/manager/ssl/TransportSecurityInfo.h
security/manager/ssl/nsNSSCallbacks.cpp
security/manager/ssl/nsNSSCertificate.cpp
security/manager/ssl/nsNSSCertificateDB.cpp
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -126,30 +126,28 @@ CertVerifier::CertVerifier(OcspDownloadC
         Unused << mThirdPartyIntermediateInputs.append(input);
       }
     }
   }
 }
 
 CertVerifier::~CertVerifier() = default;
 
-Result IsCertChainRootBuiltInRoot(const UniqueCERTCertList& chain,
+Result IsCertChainRootBuiltInRoot(const nsTArray<nsTArray<uint8_t>>& chain,
                                   bool& result) {
-  if (!chain || CERT_LIST_EMPTY(chain)) {
+  if (chain.IsEmpty()) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
-  CERTCertListNode* rootNode = CERT_LIST_TAIL(chain);
-  if (!rootNode) {
-    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  const nsTArray<uint8_t>& rootBytes = chain.LastElement();
+  Input rootInput;
+  Result rv = rootInput.Init(rootBytes.Elements(), rootBytes.Length());
+  if (rv != Result::Success) {
+    return rv;
   }
-  CERTCertificate* root = rootNode->cert;
-  if (!root) {
-    return Result::FATAL_ERROR_LIBRARY_FAILURE;
-  }
-  return IsCertBuiltInRoot(root, result);
+  return IsCertBuiltInRoot(rootInput, result);
 }
 
 Result IsDelegatedCredentialAcceptable(const DelegatedCredentialInfo& dcInfo) {
   bool isEcdsa = dcInfo.scheme == ssl_sig_ecdsa_secp256r1_sha256 ||
                  dcInfo.scheme == ssl_sig_ecdsa_secp384r1_sha384 ||
                  dcInfo.scheme == ssl_sig_ecdsa_secp521r1_sha512;
 
   // Firefox currently does not advertise any RSA schemes for use
@@ -162,28 +160,36 @@ Result IsDelegatedCredentialAcceptable(c
 
   return Result::Success;
 }
 
 // The term "builtin root" traditionally refers to a root CA certificate that
 // has been added to the NSS trust store, because it has been approved
 // for inclusion according to the Mozilla CA policy, and might be accepted
 // by Mozilla applications as an issuer for certificates seen on the public web.
-Result IsCertBuiltInRoot(CERTCertificate* cert, bool& result) {
+Result IsCertBuiltInRoot(Input certInput, bool& result) {
   if (NS_FAILED(BlockUntilLoadableCertsLoaded())) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
+  CERTCertDBHandle* certDB(CERT_GetDefaultCertDB());
+  SECItem certDER(UnsafeMapInputToSECItem(certInput));
+  UniqueCERTCertificate cert(
+      CERT_NewTempCertificate(certDB, &certDER, nullptr, false, true));
+  if (!cert) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+
   result = false;
 #ifdef DEBUG
   nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
   if (!component) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
-  nsresult rv = component->IsCertTestBuiltInRoot(cert, &result);
+  nsresult rv = component->IsCertTestBuiltInRoot(cert.get(), &result);
   if (NS_FAILED(rv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
   if (result) {
     return Success;
   }
 #endif  // DEBUG
   AutoSECMODListReadLock lock;
@@ -200,17 +206,18 @@ Result IsCertBuiltInRoot(CERTCertificate
       // the builtin roots, but which also contains additional CA certificates,
       // such as CAs trusted in a local deployment.
       // We want to be able to distinguish between these two categories,
       // because a CA, which may issue certificates for the public web,
       // is expected to comply with additional requirements.
       // If the certificate has attribute CKA_NSS_MOZILLA_CA_POLICY set to true,
       // then we treat it as a "builtin root".
       if (PK11_IsPresent(slot) && PK11_HasRootCerts(slot)) {
-        CK_OBJECT_HANDLE handle = PK11_FindCertInSlot(slot, cert, nullptr);
+        CK_OBJECT_HANDLE handle =
+            PK11_FindCertInSlot(slot, cert.get(), nullptr);
         if (handle != CK_INVALID_HANDLE &&
             PK11_HasAttributeSet(slot, handle, CKA_NSS_MOZILLA_CA_POLICY,
                                  false)) {
           // Attribute was found, and is set to true
           result = true;
           break;
         }
       }
@@ -276,30 +283,30 @@ void CertVerifier::LoadKnownCTLogs() {
     mCTVerifier->AddLog(std::move(logVerifier));
   }
   // TBD: Initialize mCTDiversityPolicy with the CA dependency map
   // of the known CT logs operators.
   mCTDiversityPolicy = MakeUnique<CTDiversityPolicy>();
 }
 
 Result CertVerifier::VerifyCertificateTransparencyPolicy(
-    NSSCertDBTrustDomain& trustDomain, const UniqueCERTCertList& builtChain,
-    Input sctsFromTLS, Time time,
+    NSSCertDBTrustDomain& trustDomain,
+    const nsTArray<nsTArray<uint8_t>>& builtChain, Input sctsFromTLS, Time time,
     /*optional out*/ CertificateTransparencyInfo* ctInfo) {
   if (ctInfo) {
     ctInfo->Reset();
   }
   if (mCTMode == CertificateTransparencyMode::Disabled) {
     return Success;
   }
   if (ctInfo) {
     ctInfo->enabled = true;
   }
 
-  if (!builtChain || CERT_LIST_EMPTY(builtChain)) {
+  if (builtChain.IsEmpty()) {
     return Result::FATAL_ERROR_INVALID_ARGS;
   }
 
   Input embeddedSCTs = trustDomain.GetSCTListFromCertificate();
   if (embeddedSCTs.GetLength() > 0) {
     MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
             ("Got embedded SCT data of length %zu\n",
              static_cast<size_t>(embeddedSCTs.GetLength())));
@@ -311,66 +318,68 @@ Result CertVerifier::VerifyCertificateTr
              static_cast<size_t>(sctsFromOCSP.GetLength())));
   }
   if (sctsFromTLS.GetLength() > 0) {
     MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
             ("Got TLS SCT data of length %zu\n",
              static_cast<size_t>(sctsFromTLS.GetLength())));
   }
 
-  CERTCertListNode* endEntityNode = CERT_LIST_HEAD(builtChain);
-  if (!endEntityNode || CERT_LIST_END(endEntityNode, builtChain)) {
-    return Result::FATAL_ERROR_INVALID_ARGS;
-  }
-  CERTCertListNode* issuerNode = CERT_LIST_NEXT(endEntityNode);
-  if (!issuerNode || CERT_LIST_END(issuerNode, builtChain)) {
+  if (builtChain.Length() == 1) {
     // Issuer certificate is required for SCT verification.
     // If we've arrived here, we probably have a "trust chain" with only one
     // certificate (i.e. a self-signed end-entity that has been set as a trust
     // anchor either by a third party modifying our trust DB or via the
     // enterprise roots feature). If this is the case, certificate transparency
     // information will probably not be present, and it certainly won't verify
     // correctly. To simplify things, we return an empty CTVerifyResult and a
     // "not enough SCTs" CTPolicyCompliance result.
     if (ctInfo) {
       CTVerifyResult emptyResult;
       ctInfo->verifyResult = std::move(emptyResult);
       ctInfo->policyCompliance = CTPolicyCompliance::NotEnoughScts;
     }
     return Success;
   }
 
-  CERTCertificate* endEntity = endEntityNode->cert;
-  CERTCertificate* issuer = issuerNode->cert;
-  if (!endEntity || !issuer) {
-    return Result::FATAL_ERROR_INVALID_ARGS;
-  }
-
-  if (endEntity->subjectName) {
-    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
-            ("Verifying CT Policy compliance of subject %s\n",
-             endEntity->subjectName));
-  }
-
-  Input endEntityDER;
+  const nsTArray<uint8_t>& endEntityBytes = builtChain.ElementAt(0);
+  Input endEntityInput;
   Result rv =
-      endEntityDER.Init(endEntity->derCert.data, endEntity->derCert.len);
+      endEntityInput.Init(endEntityBytes.Elements(), endEntityBytes.Length());
   if (rv != Success) {
     return rv;
   }
 
-  Input issuerPublicKeyDER;
-  rv = issuerPublicKeyDER.Init(issuer->derPublicKey.data,
-                               issuer->derPublicKey.len);
+  const nsTArray<uint8_t>& issuerBytes = builtChain.ElementAt(1);
+  Input issuerInput;
+  rv = issuerInput.Init(issuerBytes.Elements(), issuerBytes.Length());
+  if (rv != Success) {
+    return rv;
+  }
+  BackCert issuerBackCert(issuerInput, EndEntityOrCA::MustBeCA, nullptr);
+  rv = issuerBackCert.Init();
   if (rv != Success) {
     return rv;
   }
+  Input issuerPublicKeyInput = issuerBackCert.GetSubjectPublicKeyInfo();
+
+  SECItem endEntityDERItem = UnsafeMapInputToSECItem(endEntityInput);
+  UniqueCERTCertificate endEntityCert(CERT_NewTempCertificate(
+      CERT_GetDefaultCertDB(), &endEntityDERItem, nullptr, false, true));
+  if (!endEntityCert) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  if (endEntityCert->subjectName) {
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("Verifying CT Policy compliance of subject %s\n",
+             endEntityCert->subjectName));
+  }
 
   CTVerifyResult result;
-  rv = mCTVerifier->Verify(endEntityDER, issuerPublicKeyDER, embeddedSCTs,
+  rv = mCTVerifier->Verify(endEntityInput, issuerPublicKeyInput, embeddedSCTs,
                            sctsFromOCSP, sctsFromTLS, time, result);
   if (rv != Success) {
     MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
             ("SCT verification failed with fatal error %" PRId32 "\n",
              static_cast<uint32_t>(rv)));
     return rv;
   }
 
@@ -409,30 +418,31 @@ Result CertVerifier::VerifyCertificateTr
          "invalidSignature=%zu invalidTimestamp=%zu "
          "decodingErrors=%zu\n",
          validCount, unknownLogCount, disqualifiedLogCount,
          invalidSignatureCount, invalidTimestampCount, result.decodingErrors));
   }
 
   PRTime notBefore;
   PRTime notAfter;
-  if (CERT_GetCertTimes(endEntity, &notBefore, &notAfter) != SECSuccess) {
+  if (CERT_GetCertTimes(endEntityCert.get(), &notBefore, &notAfter) !=
+      SECSuccess) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
   size_t lifetimeInMonths;
   rv = GetCertLifetimeInFullMonths(notBefore, notAfter, lifetimeInMonths);
   if (rv != Success) {
     return rv;
   }
 
   CTLogOperatorList allOperators;
   GetCTLogOperatorsFromVerifiedSCTList(result.verifiedScts, allOperators);
 
   CTLogOperatorList dependentOperators;
-  rv = mCTDiversityPolicy->GetDependentOperators(builtChain.get(), allOperators,
+  rv = mCTDiversityPolicy->GetDependentOperators(builtChain, allOperators,
                                                  dependentOperators);
   if (rv != Success) {
     return rv;
   }
 
   CTPolicyEnforcer ctPolicyEnforcer;
   CTPolicyCompliance ctPolicyCompliance;
   ctPolicyEnforcer.CheckCompliance(result.verifiedScts, lifetimeInMonths,
@@ -461,17 +471,17 @@ bool CertVerifier::SHA1ModeMoreRestricti
       MOZ_ASSERT(false, "unexpected SHA1Mode type");
       return true;
   }
 }
 
 Result CertVerifier::VerifyCert(
     CERTCertificate* cert, SECCertificateUsage usage, Time time, void* pinArg,
     const char* hostname,
-    /*out*/ UniqueCERTCertList& builtChain,
+    /*out*/ nsTArray<nsTArray<uint8_t>>& builtChain,
     /*optional*/ const Flags flags,
     /*optional*/ const Maybe<nsTArray<nsTArray<uint8_t>>>& extraCertificates,
     /*optional*/ const Maybe<nsTArray<uint8_t>>& stapledOCSPResponseArg,
     /*optional*/ const Maybe<nsTArray<uint8_t>>& sctsFromTLS,
     /*optional*/ const OriginAttributes& originAttributes,
     /*optional out*/ EVStatus* evStatus,
     /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
     /*optional out*/ KeySizeStatus* keySizeStatus,
@@ -883,17 +893,17 @@ static bool CertIsSelfSigned(const Uniqu
   rv = VerifySignedData(trustDomain, backCert.GetSignedData(),
                         backCert.GetSubjectPublicKeyInfo());
   return rv == Success;
 }
 
 Result CertVerifier::VerifySSLServerCert(
     const UniqueCERTCertificate& peerCert, Time time,
     /*optional*/ void* pinarg, const nsACString& hostname,
-    /*out*/ UniqueCERTCertList& builtChain,
+    /*out*/ nsTArray<nsTArray<uint8_t>>& builtChain,
     /*optional*/ Flags flags,
     /*optional*/ const Maybe<nsTArray<nsTArray<uint8_t>>>& extraCertificates,
     /*optional*/ const Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
     /*optional*/ const Maybe<nsTArray<uint8_t>>& sctsFromTLS,
     /*optional*/ const Maybe<DelegatedCredentialInfo>& dcInfo,
     /*optional*/ const OriginAttributes& originAttributes,
     /*optional out*/ EVStatus* evStatus,
     /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -154,17 +154,17 @@ class CertVerifier {
     OCSP_STAPLING_INVALID = 4,
   };
 
   // *evOidPolicy == SEC_OID_UNKNOWN means the cert is NOT EV
   // Only one usage per verification is supported.
   mozilla::pkix::Result VerifyCert(
       CERTCertificate* cert, SECCertificateUsage usage,
       mozilla::pkix::Time time, void* pinArg, const char* hostname,
-      /*out*/ UniqueCERTCertList& builtChain, Flags flags = 0,
+      /*out*/ nsTArray<nsTArray<uint8_t>>& builtChain, Flags flags = 0,
       /*optional in*/
       const Maybe<nsTArray<nsTArray<uint8_t>>>& extraCertificates = Nothing(),
       /*optional in*/ const Maybe<nsTArray<uint8_t>>& stapledOCSPResponseArg =
           Nothing(),
       /*optional in*/ const Maybe<nsTArray<uint8_t>>& sctsFromTLS = Nothing(),
       /*optional in*/ const OriginAttributes& originAttributes =
           OriginAttributes(),
       /*optional out*/ EVStatus* evStatus = nullptr,
@@ -172,17 +172,17 @@ class CertVerifier {
       /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
       /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
       /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
       /*optional out*/ CertificateTransparencyInfo* ctInfo = nullptr);
 
   mozilla::pkix::Result VerifySSLServerCert(
       const UniqueCERTCertificate& peerCert, mozilla::pkix::Time time,
       void* pinarg, const nsACString& hostname,
-      /*out*/ UniqueCERTCertList& builtChain,
+      /*out*/ nsTArray<nsTArray<uint8_t>>& builtChain,
       /*optional*/ Flags flags = 0,
       /*optional*/ const Maybe<nsTArray<nsTArray<uint8_t>>>& extraCertificates =
           Nothing(),
       /*optional*/ const Maybe<nsTArray<uint8_t>>& stapledOCSPResponse =
           Nothing(),
       /*optional*/ const Maybe<nsTArray<uint8_t>>& sctsFromTLS = Nothing(),
       /*optional*/ const Maybe<DelegatedCredentialInfo>& dcInfo = Nothing(),
       /*optional*/ const OriginAttributes& originAttributes =
@@ -251,28 +251,29 @@ class CertVerifier {
 
   // We only have a forward declarations of these classes (see above)
   // so we must allocate dynamically.
   UniquePtr<mozilla::ct::MultiLogCTVerifier> mCTVerifier;
   UniquePtr<mozilla::ct::CTDiversityPolicy> mCTDiversityPolicy;
 
   void LoadKnownCTLogs();
   mozilla::pkix::Result VerifyCertificateTransparencyPolicy(
-      NSSCertDBTrustDomain& trustDomain, const UniqueCERTCertList& builtChain,
+      NSSCertDBTrustDomain& trustDomain,
+      const nsTArray<nsTArray<uint8_t>>& builtChain,
       mozilla::pkix::Input sctsFromTLS, mozilla::pkix::Time time,
       /*optional out*/ CertificateTransparencyInfo* ctInfo);
 
   // Returns true if the configured SHA1 mode is more restrictive than the given
   // mode. SHA1Mode::Forbidden is more restrictive than any other mode except
   // Forbidden. Next is ImportedRoot, then ImportedRootOrBefore2016, then
   // Allowed. (A mode is never more restrictive than itself.)
   bool SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode);
 };
 
-mozilla::pkix::Result IsCertBuiltInRoot(CERTCertificate* cert, bool& result);
+mozilla::pkix::Result IsCertBuiltInRoot(pkix::Input certInput, bool& result);
 mozilla::pkix::Result CertListContainsExpectedKeys(const CERTCertList* certList,
                                                    const char* hostname,
                                                    mozilla::pkix::Time time);
 
 }  // namespace psm
 }  // namespace mozilla
 
 #endif  // CertVerifier_h
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -17,24 +17,26 @@
 #include "cert.h"
 #include "cert_storage/src/cert_storage.h"
 #include "certdb.h"
 #include "mozilla/AppShutdown.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Services.h"
+#include "mozilla/SyncRunnable.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "mozpkix/Result.h"
 #include "mozpkix/pkix.h"
 #include "mozpkix/pkixnss.h"
 #include "mozpkix/pkixutil.h"
 #include "nsCRTGlue.h"
 #include "nsIObserverService.h"
+#include "nsNetCID.h"
 #include "nsNSSCertHelper.h"
 #include "nsNSSCertificate.h"
 #include "nsNSSCertificateDB.h"
 #include "nsPrintfCString.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nss.h"
 #include "pk11pub.h"
@@ -68,17 +70,17 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
     TimeDuration ocspTimeoutHard, uint32_t certShortLifetimeInDays,
     unsigned int minRSABits, ValidityCheckingMode validityCheckingMode,
     CertVerifier::SHA1Mode sha1Mode, NetscapeStepUpPolicy netscapeStepUpPolicy,
     CRLiteMode crliteMode, uint64_t crliteCTMergeDelaySeconds,
     const OriginAttributes& originAttributes,
     const Vector<Input>& thirdPartyRootInputs,
     const Vector<Input>& thirdPartyIntermediateInputs,
     const Maybe<nsTArray<nsTArray<uint8_t>>>& extraCertificates,
-    /*out*/ UniqueCERTCertList& builtChain,
+    /*out*/ nsTArray<nsTArray<uint8_t>>& builtChain,
     /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo,
     /*optional*/ const char* hostname)
     : mCertDBTrustType(certDBTrustType),
       mOCSPFetching(ocspFetching),
       mOCSPCache(ocspCache),
       mPinArg(pinArg),
       mOCSPTimeoutSoft(ocspTimeoutSoft),
       mOCSPTimeoutHard(ocspTimeoutHard),
@@ -1086,120 +1088,147 @@ SECStatus GetCertDistrustAfterValue(cons
   return DER_DecodeTimeChoice(&distrustTime, distrustItem);
 }
 
 SECStatus GetCertNotBeforeValue(const CERTCertificate* cert,
                                 PRTime& distrustTime) {
   return DER_DecodeTimeChoice(&distrustTime, &cert->validity.notBefore);
 }
 
-nsresult isDistrustedCertificateChain(const UniqueCERTCertList& certList,
-                                      const SECTrustType certDBTrustType,
-                                      bool& isDistrusted) {
+nsresult isDistrustedCertificateChain(
+    const nsTArray<nsTArray<uint8_t>>& certArray,
+    const SECTrustType certDBTrustType, bool& isDistrusted) {
+  if (certArray.Length() == 0) {
+    return NS_ERROR_FAILURE;
+  }
+
   // Set the default result to be distrusted.
   isDistrusted = true;
 
   // There is no distrust to set if the certDBTrustType is not SSL or Email.
   if (certDBTrustType != trustSSL && certDBTrustType != trustEmail) {
     isDistrusted = false;
     return NS_OK;
   }
 
-  // Allocate objects and retreive the root and end-entity certificates.
-  const CERTCertificate* certRoot = CERT_LIST_TAIL(certList)->cert;
-  const CERTCertificate* certLeaf = CERT_LIST_HEAD(certList)->cert;
+  SECStatus runnableRV = SECFailure;
 
-  // Set isDistrusted to false if there is no distrust for the root.
-  if (!certRoot->distrust) {
-    isDistrusted = false;
-    return NS_OK;
-  }
+  RefPtr<Runnable> isDistrustedChainTask =
+      NS_NewRunnableFunction("isDistrustedCertificateChain", [&]() {
+        // Allocate objects and retreive the root and end-entity certificates.
+        CERTCertDBHandle* certDB(CERT_GetDefaultCertDB());
+        const nsTArray<uint8_t>& certRootDER = certArray.LastElement();
+        SECItem certRootDERItem = {
+            siBuffer, const_cast<unsigned char*>(certRootDER.Elements()),
+            AssertedCast<unsigned int>(certRootDER.Length())};
+        UniqueCERTCertificate certRoot(CERT_NewTempCertificate(
+            certDB, &certRootDERItem, nullptr, false, true));
+        if (!certRoot) {
+          runnableRV = SECFailure;
+          return;
+        }
+        const nsTArray<uint8_t>& certLeafDER = certArray.ElementAt(0);
+        SECItem certLeafDERItem = {
+            siBuffer, const_cast<unsigned char*>(certLeafDER.Elements()),
+            AssertedCast<unsigned int>(certLeafDER.Length())};
+        UniqueCERTCertificate certLeaf(CERT_NewTempCertificate(
+            certDB, &certLeafDERItem, nullptr, false, true));
+        if (!certLeaf) {
+          runnableRV = SECFailure;
+          return;
+        }
+
+        // Set isDistrusted to false if there is no distrust for the root.
+        if (!certRoot->distrust) {
+          isDistrusted = false;
+          runnableRV = SECSuccess;
+          return;
+        }
 
-  // Create a pointer to refer to the selected distrust struct.
-  SECItem* distrustPtr = nullptr;
-  if (certDBTrustType == trustSSL) {
-    distrustPtr = &certRoot->distrust->serverDistrustAfter;
-  }
-  if (certDBTrustType == trustEmail) {
-    distrustPtr = &certRoot->distrust->emailDistrustAfter;
-  }
+        // Create a pointer to refer to the selected distrust struct.
+        SECItem* distrustPtr = nullptr;
+        if (certDBTrustType == trustSSL) {
+          distrustPtr = &certRoot->distrust->serverDistrustAfter;
+        }
+        if (certDBTrustType == trustEmail) {
+          distrustPtr = &certRoot->distrust->emailDistrustAfter;
+        }
+
+        // Get validity for the current end-entity certificate
+        // and get the distrust field for the root certificate.
+        PRTime certRootDistrustAfter;
+        PRTime certLeafNotBefore;
 
-  // Get validity for the current end-entity certificate
-  // and get the distrust field for the root certificate.
-  PRTime certRootDistrustAfter;
-  PRTime certLeafNotBefore;
+        runnableRV =
+            GetCertDistrustAfterValue(distrustPtr, certRootDistrustAfter);
+        if (runnableRV != SECSuccess) {
+          return;
+        }
+
+        runnableRV = GetCertNotBeforeValue(certLeaf.get(), certLeafNotBefore);
+        if (runnableRV != SECSuccess) {
+          return;
+        }
 
-  SECStatus rv = GetCertDistrustAfterValue(distrustPtr, certRootDistrustAfter);
-  if (rv != SECSuccess) {
+        // Compare the validity of the end-entity certificate with
+        // the distrust value of the root.
+        if (certLeafNotBefore <= certRootDistrustAfter) {
+          isDistrusted = false;
+        }
+
+        runnableRV = SECSuccess;
+      });
+  nsCOMPtr<nsIEventTarget> socketThread(
+      do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
+  if (!socketThread) {
     return NS_ERROR_FAILURE;
   }
-
-  rv = GetCertNotBeforeValue(certLeaf, certLeafNotBefore);
-  if (rv != SECSuccess) {
+  nsresult rv =
+      SyncRunnable::DispatchToThread(socketThread, isDistrustedChainTask);
+  if (NS_FAILED(rv) || runnableRV != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
-
-  // Compare the validity of the end-entity certificate with
-  // the distrust value of the root.
-  if (certLeafNotBefore <= certRootDistrustAfter) {
-    isDistrusted = false;
-  }
-
   return NS_OK;
 }
 
-Result NSSCertDBTrustDomain::IsChainValid(const DERArray& certArray, Time time,
+Result NSSCertDBTrustDomain::IsChainValid(const DERArray& reversedDERArray,
+                                          Time time,
                                           const CertPolicyId& requiredPolicy) {
   MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
           ("NSSCertDBTrustDomain: IsChainValid"));
 
-  UniqueCERTCertList certList;
-  SECStatus srv =
-      ConstructCERTCertListFromReversedDERArray(certArray, certList);
-  if (srv != SECSuccess) {
-    return MapPRErrorCodeToResult(PR_GetError());
+  size_t numCerts = reversedDERArray.GetLength();
+  if (numCerts < 1) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
-  if (CERT_LIST_EMPTY(certList)) {
-    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  nsTArray<nsTArray<uint8_t>> certArray;
+  for (size_t i = numCerts; i > 0; --i) {
+    const Input* derInput = reversedDERArray.GetDER(i - 1);
+    certArray.EmplaceBack(derInput->UnsafeGetData(), derInput->GetLength());
   }
 
-  // Modernization in-progress: Keep certList as a CERTCertList for storage into
-  // the mBuiltChain variable at the end.
-  nsTArray<RefPtr<nsIX509Cert>> nssCertList;
-  nsresult nsrv = nsNSSCertificateDB::ConstructCertArrayFromUniqueCertList(
-      certList, nssCertList);
+  bool isBuiltInRoot = false;
 
-  if (NS_FAILED(nsrv)) {
-    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  const nsTArray<uint8_t>& rootBytes = certArray.LastElement();
+  Input rootInput;
+  Result rv = rootInput.Init(rootBytes.Elements(), rootBytes.Length());
+  if (rv != Success) {
+    return rv;
   }
-  nsCOMPtr<nsIX509Cert> rootCert;
-  nsrv = nsNSSCertificate::GetRootCertificate(nssCertList, rootCert);
-  if (NS_FAILED(nsrv)) {
-    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  rv = IsCertBuiltInRoot(rootInput, isBuiltInRoot);
+  if (rv != Result::Success) {
+    return rv;
   }
-  UniqueCERTCertificate root(rootCert->GetCert());
-  if (!root) {
-    return Result::FATAL_ERROR_LIBRARY_FAILURE;
-  }
-  bool isBuiltInRoot = false;
-  nsrv = rootCert->GetIsBuiltInRoot(&isBuiltInRoot);
-  if (NS_FAILED(nsrv)) {
-    return Result::FATAL_ERROR_LIBRARY_FAILURE;
-  }
+  nsresult nsrv;
   // If mHostname isn't set, we're not verifying in the context of a TLS
   // handshake, so don't verify key pinning in those cases.
   if (mHostname) {
     nsTArray<Span<const uint8_t>> derCertSpanList;
-    size_t numCerts = certArray.GetLength();
-    for (size_t i = numCerts; i > 0; --i) {
-      const Input* der = certArray.GetDER(i - 1);
-      if (!der) {
-        return Result::FATAL_ERROR_LIBRARY_FAILURE;
-      }
-      derCertSpanList.EmplaceBack(der->UnsafeGetData(), der->GetLength());
+    for (const auto& certDER : certArray) {
+      derCertSpanList.EmplaceBack(certDER.Elements(), certDER.Length());
     }
 
     bool chainHasValidPins;
     nsrv = PublicKeyPinningService::ChainHasValidPins(
         derCertSpanList, mHostname, time, isBuiltInRoot, chainHasValidPins,
         mPinningTelemetryInfo);
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
@@ -1209,56 +1238,64 @@ Result NSSCertDBTrustDomain::IsChainVali
     }
   }
 
   // Check that the childs' certificate NotBefore date is anterior to
   // the NotAfter value of the parent when the root is a builtin.
   if (isBuiltInRoot) {
     bool isDistrusted;
     nsrv =
-        isDistrustedCertificateChain(certList, mCertDBTrustType, isDistrusted);
+        isDistrustedCertificateChain(certArray, mCertDBTrustType, isDistrusted);
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
     if (isDistrusted) {
       return Result::ERROR_UNTRUSTED_ISSUER;
     }
   }
 
   // See bug 1434300. If the root is a Symantec root, see if we distrust this
   // path. Since we already have the root available, we can check that cheaply
   // here before proceeding with the rest of the algorithm.
 
   // This algorithm only applies if we are verifying in the context of a TLS
   // handshake. To determine this, we check mHostname: If it isn't set, this is
   // not TLS, so don't run the algorithm.
-  nsTArray<uint8_t> rootCertDER(root.get()->derCert.data,
-                                root.get()->derCert.len);
+  const nsTArray<uint8_t>& rootCertDER = certArray.LastElement();
   if (mHostname && CertDNIsInList(rootCertDER, RootSymantecDNs)) {
-    nsTArray<nsTArray<uint8_t>> intCerts;
-
-    nsrv = nsNSSCertificate::GetIntermediatesAsDER(nssCertList, intCerts);
-    if (NS_FAILED(nsrv)) {
+    if (numCerts <= 1) {
       // This chain is supposed to be complete, so this is an error.
       return Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED;
     }
+    nsTArray<Input> intCerts;
+
+    for (size_t i = 1; i < certArray.Length() - 1; ++i) {
+      const nsTArray<uint8_t>& certBytes = certArray.ElementAt(i);
+      Input certInput;
+      rv = certInput.Init(certBytes.Elements(), certBytes.Length());
+      if (rv != Success) {
+        return Result::FATAL_ERROR_LIBRARY_FAILURE;
+      }
+
+      intCerts.EmplaceBack(certInput);
+    }
 
     bool isDistrusted = false;
     nsrv = CheckForSymantecDistrust(intCerts, RootAppleAndGoogleSPKIs,
                                     isDistrusted);
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
     if (isDistrusted) {
       mSawDistrustedCAByPolicyError = true;
       return Result::ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED;
     }
   }
 
-  mBuiltChain = std::move(certList);
+  mBuiltChain = std::move(certArray);
 
   return Success;
 }
 
 Result NSSCertDBTrustDomain::CheckSignatureDigestAlgorithm(
     DigestAlgorithm aAlg, EndEntityOrCA endEntityOrCA, Time notBefore) {
   // (new Date("2016-01-01T00:00:00Z")).getTime() / 1000
   static const Time JANUARY_FIRST_2016 = TimeFromEpochInSeconds(1451606400);
@@ -1694,17 +1731,16 @@ void SaveIntermediateCerts(const nsTArra
     // enterprise root temporarily imported via the child mode or enterprise
     // root features. We don't want to import these because they're intended to
     // be temporary (and because importing them happens to reset their trust
     // settings, which breaks these features).
     index++;
     if (index == 1 || index == certList.Length()) {
       continue;
     }
-
     SECItem certDERItem = {siBuffer,
                            const_cast<unsigned char*>(certDER.Elements()),
                            AssertedCast<unsigned int>(certDER.Length())};
     UniqueCERTCertificate certHandle(CERT_NewTempCertificate(
         CERT_GetDefaultCertDB(), &certDERItem, nullptr, false, true));
     if (!certHandle) {
       continue;
     }
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -131,17 +131,17 @@ class NSSCertDBTrustDomain : public mozi
       unsigned int minRSABits, ValidityCheckingMode validityCheckingMode,
       CertVerifier::SHA1Mode sha1Mode,
       NetscapeStepUpPolicy netscapeStepUpPolicy, CRLiteMode crliteMode,
       uint64_t crliteCTMergeDelaySeconds,
       const OriginAttributes& originAttributes,
       const Vector<mozilla::pkix::Input>& thirdPartyRootInputs,
       const Vector<mozilla::pkix::Input>& thirdPartyIntermediateInputs,
       const Maybe<nsTArray<nsTArray<uint8_t>>>& extraCertificates,
-      /*out*/ UniqueCERTCertList& builtChain,
+      /*out*/ nsTArray<nsTArray<uint8_t>>& builtChain,
       /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
       /*optional*/ const char* hostname = nullptr);
 
   virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName,
                             IssuerChecker& checker,
                             mozilla::pkix::Time time) override;
 
   virtual Result GetCertTrust(
@@ -253,17 +253,17 @@ class NSSCertDBTrustDomain : public mozi
   CRLiteMode mCRLiteMode;
   uint64_t mCRLiteCTMergeDelaySeconds;
   bool mSawDistrustedCAByPolicyError;
   const OriginAttributes& mOriginAttributes;
   const Vector<mozilla::pkix::Input>& mThirdPartyRootInputs;  // non-owning
   const Vector<mozilla::pkix::Input>&
       mThirdPartyIntermediateInputs;                             // non-owning
   const Maybe<nsTArray<nsTArray<uint8_t>>>& mExtraCertificates;  // non-owning
-  UniqueCERTCertList& mBuiltChain;                               // non-owning
+  nsTArray<nsTArray<uint8_t>>& mBuiltChain;                      // non-owning
   PinningTelemetryInfo* mPinningTelemetryInfo;
   const char* mHostname;  // non-owning - only used for pinning checks
   nsCOMPtr<nsICertStorage> mCertStorage;
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
   // Certificate Transparency data extracted during certificate verification
   UniqueSECItem mSCTListFromCertificate;
   UniqueSECItem mSCTListFromOCSPStapling;
 
--- a/security/certverifier/TrustOverrideUtils.h
+++ b/security/certverifier/TrustOverrideUtils.h
@@ -50,29 +50,23 @@ static bool CertDNIsInList(const nsTArra
     if (InputsAreEqual(subject, dnInput)) {
       return true;
     }
   }
   return false;
 }
 
 template <size_t T>
-static bool CertSPKIIsInList(const nsTArray<uint8_t>& aCert,
+static bool CertSPKIIsInList(Input aCertInput,
                              const DataAndLength (&aSpkiList)[T]) {
-  Input certInput;
-  mozilla::pkix::Result rv = certInput.Init(aCert.Elements(), aCert.Length());
-  if (rv != Success) {
-    return false;
-  }
-
   // we don't use the certificate for path building, so this parameter doesn't
   // matter
   EndEntityOrCA notUsedForPaths = EndEntityOrCA::MustBeEndEntity;
-  BackCert cert(certInput, notUsedForPaths, nullptr);
-  rv = cert.Init();
+  BackCert cert(aCertInput, notUsedForPaths, nullptr);
+  mozilla::pkix::Result rv = cert.Init();
   if (rv != Success) {
     return false;
   }
 
   Input publicKey(cert.GetSubjectPublicKeyInfo());
 
   for (auto& spki : aSpkiList) {
     Input spkiInput;
@@ -130,20 +124,19 @@ static bool CertMatchesStaticData(const 
 // This accepts a pre-segmented certificate chain (e.g. SegmentCertificateChain)
 // as |intCerts|, and pre-assumes that the root has been identified
 // as being affected (this is to avoid duplicate Segment operations in the
 // NSSCertDBTrustDomain). Each of the |intCerts| is evaluated against a
 // |allowlist| of SPKI entries, and if a match is found, then this returns
 // "not distrusted." Otherwise, due to the precondition holding, the chain is
 // "distrusted."
 template <size_t T>
-static nsresult CheckForSymantecDistrust(
-    const nsTArray<nsTArray<uint8_t>>& intCerts,
-    const DataAndLength (&allowlist)[T],
-    /* out */ bool& isDistrusted) {
+static nsresult CheckForSymantecDistrust(const nsTArray<Input>& intCerts,
+                                         const DataAndLength (&allowlist)[T],
+                                         /* out */ bool& isDistrusted) {
   // PRECONDITION: The rootCert is already verified as being one of the
   // affected Symantec roots
 
   isDistrusted = true;
 
   for (const auto& cert : intCerts) {
     if (CertSPKIIsInList(cert, allowlist)) {
       isDistrusted = false;
--- a/security/certverifier/tests/gtest/TrustOverrideTest.cpp
+++ b/security/certverifier/tests/gtest/TrustOverrideTest.cpp
@@ -210,17 +210,23 @@ TEST_F(psm_TrustOverrideTest, CheckCertD
 
   EXPECT_TRUE(CertDNIsInList(caArray, OverrideCaDNs))
       << "CA should be in the DN list";
   EXPECT_FALSE(CertDNIsInList(intermediateArray, OverrideCaDNs))
       << "Int should not be in the DN list";
 }
 
 TEST_F(psm_TrustOverrideTest, CheckCertSPKIIsInList) {
-  nsTArray<uint8_t> caArray(kOverrideCaDer, sizeof(kOverrideCaDer));
-  nsTArray<uint8_t> intermediateArray(kOverrideCaIntermediateDer,
-                                      sizeof(kOverrideCaIntermediateDer));
+  mozilla::pkix::Input caInput;
+  mozilla::pkix::Result rv =
+      caInput.Init(kOverrideCaDer, sizeof(kOverrideCaDer));
+  ASSERT_TRUE(rv == Success);
 
-  EXPECT_TRUE(CertSPKIIsInList(caArray, OverrideCaSPKIs))
+  mozilla::pkix::Input intermediateInput;
+  rv = intermediateInput.Init(kOverrideCaIntermediateDer,
+                              sizeof(kOverrideCaIntermediateDer));
+  ASSERT_TRUE(rv == Success);
+
+  EXPECT_TRUE(CertSPKIIsInList(caInput, OverrideCaSPKIs))
       << "CA should be in the SPKI list";
-  EXPECT_FALSE(CertSPKIIsInList(intermediateArray, OverrideCaSPKIs))
+  EXPECT_FALSE(CertSPKIIsInList(intermediateInput, OverrideCaSPKIs))
       << "Int should not be in the SPKI list";
 }
--- a/security/ct/CTDiversityPolicy.cpp
+++ b/security/ct/CTDiversityPolicy.cpp
@@ -25,15 +25,15 @@ void GetCTLogOperatorsFromVerifiedSCTLis
     }
     if (!alreadyAdded) {
       operators.push_back(sctLogOperatorId);
     }
   }
 }
 
 Result CTDiversityPolicy::GetDependentOperators(
-    const CERTCertList* builtChain, const CTLogOperatorList& operators,
-    CTLogOperatorList& dependentOperators) {
+    const nsTArray<nsTArray<uint8_t>>& builtChain,
+    const CTLogOperatorList& operators, CTLogOperatorList& dependentOperators) {
   return Success;
 }
 
 }  // namespace ct
 }  // namespace mozilla
--- a/security/ct/CTDiversityPolicy.h
+++ b/security/ct/CTDiversityPolicy.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CTDiversityPolicy_h
 #define CTDiversityPolicy_h
 
 #include "CTLog.h"
 #include "CTVerifyResult.h"
 #include "certt.h"
+#include "nsTArray.h"
 #include "mozpkix/Result.h"
 
 namespace mozilla {
 namespace ct {
 
 // Retuns the list of unique CT log operator IDs appearing in the provided
 // list of verified SCTs.
 void GetCTLogOperatorsFromVerifiedSCTList(const VerifiedSCTList& list,
@@ -25,17 +26,18 @@ void GetCTLogOperatorsFromVerifiedSCTLis
 // See CTPolicyEnforcer.h for more details.
 class CTDiversityPolicy {
  public:
   // Given a certificate chain and a set of CT log operators,
   // returns the subset of log operators that are dependent on the CA
   // issuing the certificate (as defined by the CT Policy).
   //
   // NOTE: TBD, PENDING FINALIZATION OF MOZILLA CT POLICY.
-  pkix::Result GetDependentOperators(const CERTCertList* builtChain,
-                                     const CTLogOperatorList& operators,
-                                     CTLogOperatorList& dependentOperators);
+  pkix::Result GetDependentOperators(
+      const nsTArray<nsTArray<uint8_t>>& builtChain,
+      const CTLogOperatorList& operators,
+      CTLogOperatorList& dependentOperators);
 };
 
 }  // namespace ct
 }  // namespace mozilla
 
 #endif  // CTDiversityPolicy_h
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -538,17 +538,22 @@ void GatherEKUTelemetry(const UniqueCERT
 
   // Only log telemetry if the root CA is built-in
   CERTCertificate* rootCert = rootNode->cert;
   MOZ_ASSERT(rootCert);
   if (!rootCert) {
     return;
   }
   bool isBuiltIn = false;
-  Result rv = IsCertBuiltInRoot(rootCert, isBuiltIn);
+  Input rootInput;
+  Result rv = rootInput.Init(rootCert->derCert.data, rootCert->derCert.len);
+  if (rv != Result::Success) {
+    return;
+  }
+  rv = IsCertBuiltInRoot(rootInput, isBuiltIn);
   if (rv != Success || !isBuiltIn) {
     return;
   }
 
   // Find the EKU extension, if present
   bool foundEKU = false;
   SECOidTag oidTag;
   CERTCertExtension* ekuExtension = nullptr;
@@ -754,18 +759,38 @@ void GatherCertificateTransparencyTeleme
 // This function collects telemetry about certs. It will be called on one of
 // CertVerificationThread. When the socket process is used this will be called
 // on the parent process.
 static void CollectCertTelemetry(
     mozilla::pkix::Result aCertVerificationResult, EVStatus aEVStatus,
     CertVerifier::OCSPStaplingStatus aOcspStaplingStatus,
     KeySizeStatus aKeySizeStatus, SHA1ModeResult aSha1ModeResult,
     const PinningTelemetryInfo& aPinningTelemetryInfo,
-    const UniqueCERTCertList& aBuiltCertChain,
+    const nsTArray<nsTArray<uint8_t>>& aBuiltCertChain,
     const CertificateTransparencyInfo& aCertificateTransparencyInfo) {
+  UniqueCERTCertList builtCertChainList(CERT_NewCertList());
+  if (!builtCertChainList) {
+    return;
+  }
+  CERTCertDBHandle* certDB(CERT_GetDefaultCertDB());
+  for (const auto& certBytes : aBuiltCertChain) {
+    SECItem certDERItem = {siBuffer, const_cast<uint8_t*>(certBytes.Elements()),
+                           AssertedCast<unsigned int>(certBytes.Length())};
+    UniqueCERTCertificate cert(
+        CERT_NewTempCertificate(certDB, &certDERItem, nullptr, false, true));
+    if (!cert) {
+      return;
+    }
+    if (CERT_AddCertToListTail(builtCertChainList.get(), cert.get()) !=
+        SECSuccess) {
+      return;
+    }
+    Unused << cert.release();  // cert is now owned by certList.
+  }
+
   uint32_t evStatus = (aCertVerificationResult != Success) ? 0  // 0 = Failure
                       : (aEVStatus != EVStatus::EV)        ? 1  // 1 = DV
                                                            : 2;        // 2 = EV
   Telemetry::Accumulate(Telemetry::CERT_EV_STATUS, evStatus);
 
   if (aOcspStaplingStatus != CertVerifier::OCSP_STAPLING_NEVER_CHECKED) {
     Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, aOcspStaplingStatus);
   }
@@ -788,18 +813,18 @@ static void CollectCertTelemetry(
   if (aPinningTelemetryInfo.accumulateResult) {
     MOZ_ASSERT(aPinningTelemetryInfo.certPinningResultHistogram.isSome());
     Telemetry::Accumulate(
         aPinningTelemetryInfo.certPinningResultHistogram.value(),
         aPinningTelemetryInfo.certPinningResultBucket);
   }
 
   if (aCertVerificationResult == Success) {
-    GatherSuccessfulValidationTelemetry(aBuiltCertChain);
-    GatherCertificateTransparencyTelemetry(aBuiltCertChain,
+    GatherSuccessfulValidationTelemetry(builtCertChainList);
+    GatherCertificateTransparencyTelemetry(builtCertChainList,
                                            aEVStatus == EVStatus::EV,
                                            aCertificateTransparencyInfo);
   }
 }
 
 static void AuthCertificateSetResults(
     TransportSecurityInfo* aInfoObject, nsNSSCertificate* aCert,
     nsTArray<nsTArray<uint8_t>>&& aBuiltCertChain,
@@ -834,17 +859,17 @@ Result AuthCertificate(
     CertVerifier& certVerifier, void* aPinArg,
     const UniqueCERTCertificate& cert,
     const nsTArray<nsTArray<uint8_t>>& peerCertChain,
     const nsACString& aHostName, const OriginAttributes& aOriginAttributes,
     const Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
     const Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
     const Maybe<DelegatedCredentialInfo>& dcInfo, uint32_t providerFlags,
     Time time, uint32_t certVerifierFlags,
-    /*out*/ UniqueCERTCertList& builtCertChain,
+    /*out*/ nsTArray<nsTArray<uint8_t>>& builtCertChain,
     /*out*/ EVStatus& evStatus,
     /*out*/ CertificateTransparencyInfo& certificateTransparencyInfo,
     /*out*/ bool& aIsCertChainRootBuiltInRoot) {
   MOZ_ASSERT(cert);
 
   CertVerifier::OCSPStaplingStatus ocspStaplingStatus =
       CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
   KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
@@ -986,16 +1011,28 @@ PRErrorCode AuthCertificateParseResults(
   return errorCodeTrust      ? errorCodeTrust
          : errorCodeMismatch ? errorCodeMismatch
          : errorCodeTime     ? errorCodeTime
                              : aDefaultErrorCodeToReport;
 }
 
 }  // unnamed namespace
 
+static nsTArray<nsTArray<uint8_t>> CreateCertBytesArray(
+    const UniqueCERTCertList& aCertChain) {
+  nsTArray<nsTArray<uint8_t>> certsBytes;
+  for (CERTCertListNode* n = CERT_LIST_HEAD(aCertChain);
+       !CERT_LIST_END(n, aCertChain); n = CERT_LIST_NEXT(n)) {
+    nsTArray<uint8_t> certBytes;
+    certBytes.AppendElements(n->cert->derCert.data, n->cert->derCert.len);
+    certsBytes.AppendElement(std::move(certBytes));
+  }
+  return certsBytes;
+}
+
 /*static*/
 SECStatus SSLServerCertVerificationJob::Dispatch(
     uint64_t addrForLogging, void* aPinArg,
     const UniqueCERTCertificate& serverCert,
     nsTArray<nsTArray<uint8_t>>&& peerCertChain, const nsACString& aHostName,
     int32_t aPort, const OriginAttributes& aOriginAttributes,
     Maybe<nsTArray<uint8_t>>& stapledOCSPResponse,
     Maybe<nsTArray<uint8_t>>& sctsFromTLSExtension,
@@ -1048,36 +1085,33 @@ SSLServerCertVerificationJob::Run() {
 
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   if (!certVerifier) {
     PR_SetError(SEC_ERROR_NOT_INITIALIZED, 0);
     return NS_OK;
   }
 
   TimeStamp jobStartTime = TimeStamp::Now();
-  UniqueCERTCertList builtCertChain;
   EVStatus evStatus;
   CertificateTransparencyInfo certificateTransparencyInfo;
   bool isCertChainRootBuiltInRoot = false;
+  nsTArray<nsTArray<uint8_t>> certBytesArray;
   Result rv = AuthCertificate(
       *certVerifier, mPinArg, mCert, mPeerCertChain, mHostName,
       mOriginAttributes, mStapledOCSPResponse, mSCTsFromTLSExtension, mDCInfo,
-      mProviderFlags, mTime, mCertVerifierFlags, builtCertChain, evStatus,
+      mProviderFlags, mTime, mCertVerifierFlags, certBytesArray, evStatus,
       certificateTransparencyInfo, isCertChainRootBuiltInRoot);
 
   RefPtr<nsNSSCertificate> nsc = nsNSSCertificate::Create(mCert.get());
-  nsTArray<nsTArray<uint8_t>> certBytesArray;
   if (rv == Success) {
     Telemetry::AccumulateTimeDelta(
         Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_MOZILLAPKIX, jobStartTime,
         TimeStamp::Now());
     Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
 
-    certBytesArray =
-        TransportSecurityInfo::CreateCertBytesArray(builtCertChain);
     mResultTask->Dispatch(
         nsc, std::move(certBytesArray), std::move(mPeerCertChain),
         TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
             certificateTransparencyInfo),
         evStatus, true, 0, 0, isCertChainRootBuiltInRoot, mProviderFlags);
     return NS_OK;
   }
 
@@ -1197,17 +1231,17 @@ SECStatus AuthCertificateHook(void* arg,
 
   UniqueCERTCertList peerCertChain(SSL_PeerCertificateChain(fd));
   if (!peerCertChain) {
     PR_SetError(PR_INVALID_STATE_ERROR, 0);
     return SECFailure;
   }
 
   nsTArray<nsTArray<uint8_t>> peerCertsBytes =
-      TransportSecurityInfo::CreateCertBytesArray(peerCertChain);
+      CreateCertBytesArray(peerCertChain);
 
   // SSL_PeerStapledOCSPResponses will never return a non-empty response if
   // OCSP stapling wasn't enabled because libssl wouldn't have let the server
   // return a stapled OCSP response.
   // We don't own these pointers.
   const SECItemArray* csa = SSL_PeerStapledOCSPResponses(fd);
   Maybe<nsTArray<uint8_t>> stapledOCSPResponse;
   // we currently only support single stapled responses
--- a/security/manager/ssl/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/TransportSecurityInfo.cpp
@@ -1148,29 +1148,16 @@ uint16_t TransportSecurityInfo::ConvertC
     case CTPolicyCompliance::Unknown:
     default:
       MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
   }
 
   return nsITransportSecurityInfo::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
 }
 
-// static
-nsTArray<nsTArray<uint8_t>> TransportSecurityInfo::CreateCertBytesArray(
-    const UniqueCERTCertList& aCertChain) {
-  nsTArray<nsTArray<uint8_t>> certsBytes;
-  for (CERTCertListNode* n = CERT_LIST_HEAD(aCertChain);
-       !CERT_LIST_END(n, aCertChain); n = CERT_LIST_NEXT(n)) {
-    nsTArray<uint8_t> certBytes;
-    certBytes.AppendElements(n->cert->derCert.data, n->cert->derCert.len);
-    certsBytes.AppendElement(std::move(certBytes));
-  }
-  return certsBytes;
-}
-
 NS_IMETHODIMP
 TransportSecurityInfo::GetIsDomainMismatch(bool* aIsDomainMismatch) {
   NS_ENSURE_ARG_POINTER(aIsDomainMismatch);
   *aIsDomainMismatch = mHaveCertErrorBits && mIsDomainMismatch;
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/security/manager/ssl/TransportSecurityInfo.h
+++ b/security/manager/ssl/TransportSecurityInfo.h
@@ -86,19 +86,16 @@ class TransportSecurityInfo : public nsI
   bool HasServerCert() {
     MutexAutoLock lock(mMutex);
     return mServerCert != nullptr;
   }
 
   static uint16_t ConvertCertificateTransparencyInfoToStatus(
       const mozilla::psm::CertificateTransparencyInfo& info);
 
-  static nsTArray<nsTArray<uint8_t>> CreateCertBytesArray(
-      const UniqueCERTCertList& aCertChain);
-
   // Use errorCode == 0 to indicate success;
   virtual void SetCertVerificationResult(PRErrorCode errorCode){};
 
   void SetCertificateTransparencyStatus(
       uint16_t aCertificateTransparencyStatus) {
     MutexAutoLock lock(mMutex);
     mCertificateTransparencyStatus = aCertificateTransparencyStatus;
   }
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -1093,21 +1093,21 @@ static void RebuildVerifiedCertificateIn
   int flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY;
   if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
       !infoObject->SharedState().IsOCSPMustStapleEnabled()) {
     flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
   }
 
   EVStatus evStatus;
   CertificateTransparencyInfo certificateTransparencyInfo;
-  UniqueCERTCertList builtChain;
+  nsTArray<nsTArray<uint8_t>> certBytesArray;
   bool isBuiltCertChainRootBuiltInRoot = false;
   mozilla::pkix::Result rv = certVerifier->VerifySSLServerCert(
       cert, mozilla::pkix::Now(), infoObject, infoObject->GetHostName(),
-      builtChain, flags, maybePeerCertsBytes, stapledOCSPResponse,
+      certBytesArray, flags, maybePeerCertsBytes, stapledOCSPResponse,
       sctsFromTLSExtension, Nothing(), infoObject->GetOriginAttributes(),
       &evStatus,
       nullptr,  // OCSP stapling telemetry
       nullptr,  // key size telemetry
       nullptr,  // SHA-1 telemetry
       nullptr,  // pinning telemetry
       &certificateTransparencyInfo, &isBuiltCertChainRootBuiltInRoot);
 
@@ -1127,18 +1127,16 @@ static void RebuildVerifiedCertificateIn
     infoObject->SetServerCert(nssc, EVStatus::NotEV);
   }
 
   if (rv == Success) {
     uint16_t status =
         TransportSecurityInfo::ConvertCertificateTransparencyInfoToStatus(
             certificateTransparencyInfo);
     infoObject->SetCertificateTransparencyStatus(status);
-    nsTArray<nsTArray<uint8_t>> certBytesArray =
-        TransportSecurityInfo::CreateCertBytesArray(builtChain);
     infoObject->SetSucceededCertChain(std::move(certBytesArray));
     infoObject->SetIsBuiltCertChainRootBuiltInRoot(
         isBuiltCertChainRootBuiltInRoot);
   }
 }
 
 void HandshakeCallback(PRFileDesc* fd, void* client_data) {
   SECStatus rv;
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -146,17 +146,22 @@ nsresult nsNSSCertificate::GetCertType(u
   *aCertType = mCertType;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetIsBuiltInRoot(bool* aIsBuiltInRoot) {
   NS_ENSURE_ARG(aIsBuiltInRoot);
 
-  pkix::Result rv = IsCertBuiltInRoot(mCert.get(), *aIsBuiltInRoot);
+  pkix::Input certInput;
+  pkix::Result rv = certInput.Init(mCert->derCert.data, mCert->derCert.len);
+  if (rv != pkix::Result::Success) {
+    return NS_ERROR_FAILURE;
+  }
+  rv = IsCertBuiltInRoot(certInput, *aIsBuiltInRoot);
   if (rv != pkix::Result::Success) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsNSSCertificate::GetDbKey(nsACString& aDbKey) {
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -1257,17 +1257,17 @@ nsresult VerifyCertAtTime(nsIX509Cert* a
   UniqueCERTCertificate nssCert(aCert->GetCert());
   if (!nssCert) {
     return NS_ERROR_INVALID_ARG;
   }
 
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   NS_ENSURE_TRUE(certVerifier, NS_ERROR_FAILURE);
 
-  UniqueCERTCertList resultChain;
+  nsTArray<nsTArray<uint8_t>> resultChain;
   EVStatus evStatus;
   mozilla::pkix::Result result;
 
   if (!aHostname.IsVoid() && aUsage == certificateUsageSSLServer) {
     result =
         certVerifier->VerifySSLServerCert(nssCert, aTime,
                                           nullptr,  // Assume no context
                                           aHostname, resultChain, aFlags,
@@ -1284,21 +1284,24 @@ nsresult VerifyCertAtTime(nsIX509Cert* a
         aHostname.IsVoid() ? nullptr : flatHostname.get(), resultChain, aFlags,
         Nothing(),  // extraCertificates
         Nothing(),  // stapledOCSPResponse
         Nothing(),  // sctsFromTLSExtension
         OriginAttributes(), &evStatus);
   }
 
   if (result == mozilla::pkix::Success) {
-    nsresult rv = nsNSSCertificateDB::ConstructCertArrayFromUniqueCertList(
-        resultChain, aVerifiedChain);
-
-    if (NS_FAILED(rv)) {
-      return rv;
+    for (const auto& certDER : resultChain) {
+      RefPtr<nsIX509Cert> cert = nsNSSCertificate::ConstructFromDER(
+          const_cast<char*>(reinterpret_cast<const char*>(certDER.Elements())),
+          static_cast<int>(certDER.Length()));
+      if (!cert) {
+        return NS_ERROR_FAILURE;
+      }
+      aVerifiedChain.AppendElement(cert);
     }
 
     if (evStatus == EVStatus::EV) {
       *aHasEVPolicy = true;
     }
   }
 
   *_retval = mozilla::pkix::MapResultToPRErrorCode(result);