bug 1473573 - import intermediate certificates as well as roots r=jcj
authorDana Keeler <dkeeler@mozilla.com>
Tue, 12 Feb 2019 18:23:25 +0000
changeset 458776 c8e523ac7349
parent 458775 204a7782dc0b
child 458777 c405d8906d7c
push id35548
push useropoprus@mozilla.com
push dateWed, 13 Feb 2019 09:48:26 +0000
treeherdermozilla-central@93e37c529818 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjcj
bugs1473573
milestone67.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 1473573 - import intermediate certificates as well as roots r=jcj Differential Revision: https://phabricator.services.mozilla.com/D18630
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/manager/ssl/EnterpriseRoots.cpp
security/manager/ssl/EnterpriseRoots.h
security/manager/ssl/SharedCertVerifier.h
security/manager/ssl/moz.build
security/manager/ssl/nsINSSComponent.idl
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/nsNSSComponent.h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -84,42 +84,47 @@ CertVerifier::CertVerifier(OcspDownloadC
                            mozilla::TimeDuration ocspTimeoutSoft,
                            mozilla::TimeDuration ocspTimeoutHard,
                            uint32_t certShortLifetimeInDays,
                            PinningMode pinningMode, SHA1Mode sha1Mode,
                            BRNameMatchingPolicy::Mode nameMatchingMode,
                            NetscapeStepUpPolicy netscapeStepUpPolicy,
                            CertificateTransparencyMode ctMode,
                            DistrustedCAPolicy distrustedCAPolicy,
-                           const Vector<Vector<uint8_t>>& thirdPartyRoots)
+                           const Vector<EnterpriseCert>& thirdPartyCerts)
     : mOCSPDownloadConfig(odc),
       mOCSPStrict(osc == ocspStrict),
       mOCSPTimeoutSoft(ocspTimeoutSoft),
       mOCSPTimeoutHard(ocspTimeoutHard),
       mCertShortLifetimeInDays(certShortLifetimeInDays),
       mPinningMode(pinningMode),
       mSHA1Mode(sha1Mode),
       mNameMatchingMode(nameMatchingMode),
       mNetscapeStepUpPolicy(netscapeStepUpPolicy),
       mCTMode(ctMode),
       mDistrustedCAPolicy(distrustedCAPolicy) {
   LoadKnownCTLogs();
-  for (const auto& root : thirdPartyRoots) {
-    Vector<uint8_t> rootCopy;
-    if (rootCopy.append(root.begin(), root.end())) {
-      // Best-effort. If we run out of memory, users might see untrusted issuer
-      // errors, but the browser will probably crash before then.
-      Unused << mThirdPartyRoots.append(std::move(rootCopy));
+  for (const auto& root : thirdPartyCerts) {
+    EnterpriseCert rootCopy;
+    // Best-effort. If we run out of memory, users might see untrusted issuer
+    // errors, but the browser will probably crash before then.
+    if (NS_SUCCEEDED(rootCopy.Init(root))) {
+      Unused << mThirdPartyCerts.append(std::move(rootCopy));
     }
   }
-  for (const auto& root : mThirdPartyRoots) {
-    Input rootInput;
-    if (rootInput.Init(root.begin(), root.length()) == Success) {
-      // Best effort again.
-      Unused << mThirdPartyRootInputs.append(rootInput);
+  for (const auto& root : mThirdPartyCerts) {
+    Input input;
+    if (root.GetInput(input) == Success) {
+      // mThirdPartyCerts consists of roots and intermediates.
+      if (root.GetIsRoot()) {
+        // Best effort again.
+        Unused << mThirdPartyRootInputs.append(input);
+      } else {
+        Unused << mThirdPartyIntermediateInputs.append(input);
+      }
     }
   }
 }
 
 CertVerifier::~CertVerifier() {}
 
 Result IsCertChainRootBuiltInRoot(const UniqueCERTCertList& chain,
                                   bool& result) {
@@ -523,17 +528,17 @@ Result CertVerifier::VerifyCert(
       // XXX: We don't really have a trust bit for SSL client authentication so
       // just use trustEmail as it is the closest alternative.
       NSSCertDBTrustDomain trustDomain(
           trustEmail, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
           mOCSPTimeoutHard, mCertShortLifetimeInDays, pinningDisabled,
           MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff,
           SHA1Mode::Allowed, NetscapeStepUpPolicy::NeverMatch,
           mDistrustedCAPolicy, originAttributes, mThirdPartyRootInputs,
-          builtChain, nullptr, nullptr);
+          mThirdPartyIntermediateInputs, builtChain, nullptr, nullptr);
       rv = BuildCertChain(
           trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity,
           KeyUsage::digitalSignature, KeyPurposeId::id_kp_clientAuth,
           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
     case certificateUsageSSLServer: {
@@ -596,17 +601,18 @@ Result CertVerifier::VerifyCert(
         }
 
         NSSCertDBTrustDomain trustDomain(
             trustSSL, evOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
             mOCSPTimeoutHard, mCertShortLifetimeInDays, mPinningMode,
             MIN_RSA_BITS, ValidityCheckingMode::CheckForEV,
             sha1ModeConfigurations[i], mNetscapeStepUpPolicy,
             mDistrustedCAPolicy, originAttributes, mThirdPartyRootInputs,
-            builtChain, pinningTelemetryInfo, hostname);
+            mThirdPartyIntermediateInputs, builtChain, pinningTelemetryInfo,
+            hostname);
         rv = BuildCertChainForOneKeyUsage(
             trustDomain, certDER, time,
             KeyUsage::digitalSignature,  // (EC)DHE
             KeyUsage::keyEncipherment,   // RSA
             KeyUsage::keyAgreement,      // (EC)DH
             KeyPurposeId::id_kp_serverAuth, evPolicy, stapledOCSPResponse,
             ocspStaplingStatus);
         if (rv == Success &&
@@ -677,18 +683,18 @@ Result CertVerifier::VerifyCert(
           }
 
           NSSCertDBTrustDomain trustDomain(
               trustSSL, defaultOCSPFetching, mOCSPCache, pinArg,
               mOCSPTimeoutSoft, mOCSPTimeoutHard, mCertShortLifetimeInDays,
               mPinningMode, keySizeOptions[i],
               ValidityCheckingMode::CheckingOff, sha1ModeConfigurations[j],
               mNetscapeStepUpPolicy, mDistrustedCAPolicy, originAttributes,
-              mThirdPartyRootInputs, builtChain, pinningTelemetryInfo,
-              hostname);
+              mThirdPartyRootInputs, mThirdPartyIntermediateInputs, builtChain,
+              pinningTelemetryInfo, hostname);
           rv = BuildCertChainForOneKeyUsage(
               trustDomain, certDER, time,
               KeyUsage::digitalSignature,  //(EC)DHE
               KeyUsage::keyEncipherment,   // RSA
               KeyUsage::keyAgreement,      //(EC)DH
               KeyPurposeId::id_kp_serverAuth, CertPolicyId::anyPolicy,
               stapledOCSPResponse, ocspStaplingStatus);
           if (rv != Success && !IsFatalError(rv) &&
@@ -746,32 +752,32 @@ Result CertVerifier::VerifyCert(
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(
           trustSSL, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
           mOCSPTimeoutHard, mCertShortLifetimeInDays, pinningDisabled,
           MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff,
           SHA1Mode::Allowed, mNetscapeStepUpPolicy, mDistrustedCAPolicy,
-          originAttributes, mThirdPartyRootInputs, builtChain, nullptr,
-          nullptr);
+          originAttributes, mThirdPartyRootInputs,
+          mThirdPartyIntermediateInputs, builtChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time, EndEntityOrCA::MustBeCA,
                           KeyUsage::keyCertSign, KeyPurposeId::id_kp_serverAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(
           trustEmail, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
           mOCSPTimeoutHard, mCertShortLifetimeInDays, pinningDisabled,
           MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff,
           SHA1Mode::Allowed, NetscapeStepUpPolicy::NeverMatch,
           mDistrustedCAPolicy, originAttributes, mThirdPartyRootInputs,
-          builtChain, nullptr, nullptr);
+          mThirdPartyIntermediateInputs, builtChain, nullptr, nullptr);
       rv = BuildCertChain(
           trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity,
           KeyUsage::digitalSignature, KeyPurposeId::id_kp_emailProtection,
           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(
             trustDomain, certDER, time, EndEntityOrCA::MustBeEndEntity,
             KeyUsage::nonRepudiation, KeyPurposeId::id_kp_emailProtection,
@@ -785,17 +791,17 @@ Result CertVerifier::VerifyCert(
       // usage it is trying to verify for, and base its algorithm choices
       // based on the result of the verification(s).
       NSSCertDBTrustDomain trustDomain(
           trustEmail, defaultOCSPFetching, mOCSPCache, pinArg, mOCSPTimeoutSoft,
           mOCSPTimeoutHard, mCertShortLifetimeInDays, pinningDisabled,
           MIN_RSA_BITS_WEAK, ValidityCheckingMode::CheckingOff,
           SHA1Mode::Allowed, NetscapeStepUpPolicy::NeverMatch,
           mDistrustedCAPolicy, originAttributes, mThirdPartyRootInputs,
-          builtChain, nullptr, nullptr);
+          mThirdPartyIntermediateInputs, builtChain, nullptr, nullptr);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::keyEncipherment,  // RSA
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
                             EndEntityOrCA::MustBeEndEntity,
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CertVerifier_h
 #define CertVerifier_h
 
 #include "BRNameMatchingPolicy.h"
 #include "CTPolicyEnforcer.h"
 #include "CTVerifyResult.h"
+#include "EnterpriseRoots.h"
 #include "OCSPCache.h"
 #include "RootCertificateTelemetryUtils.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 #include "nsString.h"
 #include "mozpkix/pkixtypes.h"
@@ -202,17 +203,17 @@ class CertVerifier {
   CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                mozilla::TimeDuration ocspTimeoutSoft,
                mozilla::TimeDuration ocspTimeoutHard,
                uint32_t certShortLifetimeInDays, PinningMode pinningMode,
                SHA1Mode sha1Mode, BRNameMatchingPolicy::Mode nameMatchingMode,
                NetscapeStepUpPolicy netscapeStepUpPolicy,
                CertificateTransparencyMode ctMode,
                DistrustedCAPolicy distrustedCAPolicy,
-               const Vector<Vector<uint8_t>>& thirdPartyRoots);
+               const Vector<EnterpriseCert>& thirdPartyCerts);
   ~CertVerifier();
 
   void ClearOCSPCache() { mOCSPCache.Clear(); }
 
   const OcspDownloadConfig mOCSPDownloadConfig;
   const bool mOCSPStrict;
   const mozilla::TimeDuration mOCSPTimeoutSoft;
   const mozilla::TimeDuration mOCSPTimeoutHard;
@@ -222,20 +223,22 @@ class CertVerifier {
   const BRNameMatchingPolicy::Mode mNameMatchingMode;
   const NetscapeStepUpPolicy mNetscapeStepUpPolicy;
   const CertificateTransparencyMode mCTMode;
   const DistrustedCAPolicy mDistrustedCAPolicy;
 
  private:
   OCSPCache mOCSPCache;
   // We keep a copy of the bytes of each third party root to own.
-  Vector<Vector<uint8_t>> mThirdPartyRoots;
+  Vector<EnterpriseCert> mThirdPartyCerts;
   // This is a reusable, precomputed list of Inputs corresponding to each root
-  // in mThirdPartyRoots that wasn't too long to make an Input out of.
+  // in mThirdPartyCerts that wasn't too long to make an Input out of.
   Vector<mozilla::pkix::Input> mThirdPartyRootInputs;
+  // Similarly, but with intermediates.
+  Vector<mozilla::pkix::Input> mThirdPartyIntermediateInputs;
 
   // 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(
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -56,16 +56,17 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
     /*optional but shouldn't be*/ void* pinArg, TimeDuration ocspTimeoutSoft,
     TimeDuration ocspTimeoutHard, uint32_t certShortLifetimeInDays,
     CertVerifier::PinningMode pinningMode, unsigned int minRSABits,
     ValidityCheckingMode validityCheckingMode, CertVerifier::SHA1Mode sha1Mode,
     NetscapeStepUpPolicy netscapeStepUpPolicy,
     DistrustedCAPolicy distrustedCAPolicy,
     const OriginAttributes& originAttributes,
     const Vector<Input>& thirdPartyRootInputs,
+    const Vector<Input>& thirdPartyIntermediateInputs,
     /*out*/ UniqueCERTCertList& builtChain,
     /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo,
     /*optional*/ const char* hostname)
     : mCertDBTrustType(certDBTrustType),
       mOCSPFetching(ocspFetching),
       mOCSPCache(ocspCache),
       mPinArg(pinArg),
       mOCSPTimeoutSoft(ocspTimeoutSoft),
@@ -75,16 +76,17 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
       mMinRSABits(minRSABits),
       mValidityCheckingMode(validityCheckingMode),
       mSHA1Mode(sha1Mode),
       mNetscapeStepUpPolicy(netscapeStepUpPolicy),
       mDistrustedCAPolicy(distrustedCAPolicy),
       mSawDistrustedCAByPolicyError(false),
       mOriginAttributes(originAttributes),
       mThirdPartyRootInputs(thirdPartyRootInputs),
+      mThirdPartyIntermediateInputs(thirdPartyIntermediateInputs),
       mBuiltChain(builtChain),
       mPinningTelemetryInfo(pinningTelemetryInfo),
       mHostname(hostname),
       mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)),
       mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED),
       mSCTListFromCertificate(),
       mSCTListFromOCSPStapling() {}
 
@@ -121,16 +123,23 @@ Result NSSCertDBTrustDomain::FindIssuer(
   }
 
   for (const auto& thirdPartyRootInput : mThirdPartyRootInputs) {
     if (!rootCandidates.append(thirdPartyRootInput)) {
       return Result::FATAL_ERROR_NO_MEMORY;
     }
   }
 
+  for (const auto& thirdPartyIntermediateInput :
+       mThirdPartyIntermediateInputs) {
+    if (!intermediateCandidates.append(thirdPartyIntermediateInput)) {
+      return Result::FATAL_ERROR_NO_MEMORY;
+    }
+  }
+
   // Handle imposed name constraints, if any.
   ScopedAutoSECItem nameConstraints;
   Input nameConstraintsInput;
   Input* nameConstraintsInputPtr = nullptr;
   SECStatus srv =
       CERT_GetImposedNameConstraints(&encodedIssuerNameItem, &nameConstraints);
   if (srv == SECSuccess) {
     if (nameConstraintsInput.Init(nameConstraints.data, nameConstraints.len) !=
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -103,16 +103,17 @@ class NSSCertDBTrustDomain : public mozi
       mozilla::TimeDuration ocspTimeoutHard, uint32_t certShortLifetimeInDays,
       CertVerifier::PinningMode pinningMode, unsigned int minRSABits,
       ValidityCheckingMode validityCheckingMode,
       CertVerifier::SHA1Mode sha1Mode,
       NetscapeStepUpPolicy netscapeStepUpPolicy,
       DistrustedCAPolicy distrustedCAPolicy,
       const OriginAttributes& originAttributes,
       const Vector<mozilla::pkix::Input>& thirdPartyRootInputs,
+      const Vector<mozilla::pkix::Input>& thirdPartyIntermediateInputs,
       /*out*/ UniqueCERTCertList& builtChain,
       /*optional*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
       /*optional*/ const char* hostname = nullptr);
 
   virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName,
                             IssuerChecker& checker,
                             mozilla::pkix::Time time) override;
 
@@ -212,16 +213,18 @@ class NSSCertDBTrustDomain : public mozi
   const unsigned int mMinRSABits;
   ValidityCheckingMode mValidityCheckingMode;
   CertVerifier::SHA1Mode mSHA1Mode;
   NetscapeStepUpPolicy mNetscapeStepUpPolicy;
   DistrustedCAPolicy mDistrustedCAPolicy;
   bool mSawDistrustedCAByPolicyError;
   const OriginAttributes& mOriginAttributes;
   const Vector<mozilla::pkix::Input>& mThirdPartyRootInputs;  // non-owning
+  const Vector<mozilla::pkix::Input>&
+      mThirdPartyIntermediateInputs;                          // non-owning
   UniqueCERTCertList& mBuiltChain;                            // non-owning
   PinningTelemetryInfo* mPinningTelemetryInfo;
   const char* mHostname;  // non-owning - only used for pinning checks
   nsCOMPtr<nsICertBlocklist> mCertBlocklist;
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
   // Certificate Transparency data extracted during certificate verification
   UniqueSECItem mSCTListFromCertificate;
   UniqueSECItem mSCTListFromOCSPStapling;
--- a/security/manager/ssl/EnterpriseRoots.cpp
+++ b/security/manager/ssl/EnterpriseRoots.cpp
@@ -4,40 +4,72 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "EnterpriseRoots.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Unused.h"
+#include "mozpkix/Result.h"
 
 #ifdef XP_MACOSX
 #  include <Security/Security.h>
 #  include "KeychainSecret.h"  // for ScopedCFType
 #endif                         // XP_MACOSX
 
 extern LazyLogModule gPIPNSSLog;
 
 using namespace mozilla;
 
+nsresult EnterpriseCert::Init(const uint8_t* data, size_t len, bool isRoot) {
+  mDER.clear();
+  if (!mDER.append(data, len)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  mIsRoot = isRoot;
+
+  return NS_OK;
+}
+
+nsresult EnterpriseCert::Init(const EnterpriseCert& orig) {
+  return Init(orig.mDER.begin(), orig.mDER.length(), orig.mIsRoot);
+}
+
+nsresult EnterpriseCert::CopyBytes(nsTArray<uint8_t>& dest) const {
+  dest.Clear();
+  if (!dest.AppendElements(mDER.begin(), mDER.length())) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+pkix::Result EnterpriseCert::GetInput(pkix::Input& input) const {
+  return input.Init(mDER.begin(), mDER.length());
+}
+
+bool EnterpriseCert::GetIsRoot() const { return mIsRoot; }
+
 #ifdef XP_WIN
-const wchar_t* kWindowsDefaultRootStoreName = L"ROOT";
+const wchar_t* kWindowsDefaultRootStoreNames[] = {L"ROOT", L"CA"};
 
 // Helper function to determine if the OS considers the given certificate to be
 // a trust anchor for TLS server auth certificates. This is to be used in the
 // context of importing what are presumed to be root certificates from the OS.
 // If this function returns true but it turns out that the given certificate is
 // in some way unsuitable to issue certificates, mozilla::pkix will never build
 // a valid chain that includes the certificate, so importing it even if it
 // isn't a valid CA poses no risk.
-static bool CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate) {
+static void CertIsTrustAnchorForTLSServerAuth(PCCERT_CONTEXT certificate,
+                                              bool& isTrusted, bool& isRoot) {
+  isTrusted = false;
+  isRoot = false;
   MOZ_ASSERT(certificate);
   if (!certificate) {
-    return false;
+    return;
   }
 
   PCCERT_CHAIN_CONTEXT pChainContext = nullptr;
   CERT_ENHKEY_USAGE enhkeyUsage;
   memset(&enhkeyUsage, 0, sizeof(CERT_ENHKEY_USAGE));
   LPCSTR identifiers[] = {
       "1.3.6.1.5.5.7.3.1",  // id-kp-serverAuth
   };
@@ -51,30 +83,29 @@ static bool CertIsTrustAnchorForTLSServe
   CERT_CHAIN_PARA chainPara;
   memset(&chainPara, 0, sizeof(CERT_CHAIN_PARA));
   chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
   chainPara.RequestedUsage = certUsage;
 
   if (!CertGetCertificateChain(nullptr, certificate, nullptr, nullptr,
                                &chainPara, 0, nullptr, &pChainContext)) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("CertGetCertificateChain failed"));
-    return false;
+    return;
   }
-  bool trusted =
-      pChainContext->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR;
-  bool isRoot = pChainContext->cChain == 1;
+  isTrusted = pChainContext->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR;
+  if (isTrusted && pChainContext->cChain > 0) {
+    // The so-called "final chain" is what we're after:
+    // https://docs.microsoft.com/en-us/windows/desktop/api/wincrypt/ns-wincrypt-_cert_chain_context
+    CERT_SIMPLE_CHAIN* finalChain =
+        pChainContext->rgpChain[pChainContext->cChain - 1];
+    // This is a root if the final chain consists of only one certificate (i.e.
+    // this one).
+    isRoot = finalChain->cElement == 1;
+  }
   CertFreeCertificateChain(pChainContext);
-  if (trusted && isRoot) {
-    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-            ("certificate is trust anchor for TLS server auth"));
-    return true;
-  }
-  MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-          ("certificate not trust anchor for TLS server auth"));
-  return false;
 }
 
 // Because HCERTSTORE is just a typedef void*, we can't use any of the nice
 // scoped or unique pointer templates. To elaborate, any attempt would
 // instantiate those templates with T = void. When T gets used in the context
 // of T&, this results in void&, which isn't legal.
 class ScopedCertStore final {
  public:
@@ -95,18 +126,18 @@ class ScopedCertStore final {
 // Supported flags are:
 //   CERT_SYSTEM_STORE_LOCAL_MACHINE
 //     (for HKLM\SOFTWARE\Microsoft\SystemCertificates)
 //   CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
 //     (for
 //     HKLM\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\Certificates)
 //   CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE
 //     (for HKLM\SOFTWARE\Microsoft\EnterpriseCertificates\Root\Certificates)
-static void GatherEnterpriseRootsForLocation(DWORD locationFlag,
-                                             Vector<Vector<uint8_t>>& roots) {
+static void GatherEnterpriseCertsForLocation(DWORD locationFlag,
+                                             Vector<EnterpriseCert>& certs) {
   MOZ_ASSERT(locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE ||
                  locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY ||
                  locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
              "unexpected locationFlag for GatherEnterpriseRootsForLocation");
   if (!(locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE ||
         locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY ||
         locationFlag == CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE)) {
     return;
@@ -114,62 +145,65 @@ static void GatherEnterpriseRootsForLoca
 
   DWORD flags =
       locationFlag | CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG;
   // The certificate store being opened should consist only of certificates
   // added by a user or administrator and not any certificates that are part
   // of Microsoft's root store program.
   // The 3rd parameter to CertOpenStore should be NULL according to
   // https://msdn.microsoft.com/en-us/library/windows/desktop/aa376559%28v=vs.85%29.aspx
-  ScopedCertStore enterpriseRootStore(
-      CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_W, 0, NULL, flags,
-                    kWindowsDefaultRootStoreName));
-  if (!enterpriseRootStore.get()) {
-    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-            ("failed to open enterprise root store"));
-    return;
-  }
-  PCCERT_CONTEXT certificate = nullptr;
-  uint32_t numImported = 0;
-  while ((certificate = CertFindCertificateInStore(
-              enterpriseRootStore.get(), X509_ASN_ENCODING, 0, CERT_FIND_ANY,
-              nullptr, certificate))) {
-    if (!CertIsTrustAnchorForTLSServerAuth(certificate)) {
+  for (auto name : kWindowsDefaultRootStoreNames) {
+    ScopedCertStore enterpriseRootStore(
+        CertOpenStore(CERT_STORE_PROV_SYSTEM_REGISTRY_W, 0, NULL, flags, name));
+    if (!enterpriseRootStore.get()) {
       MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-              ("skipping cert not trust anchor for TLS server auth"));
+              ("failed to open enterprise root store"));
       continue;
     }
-    Vector<uint8_t> certBytes;
-    if (!certBytes.append(certificate->pbCertEncoded,
-                          certificate->cbCertEncoded)) {
-      MOZ_LOG(gPIPNSSLog, LogLevel::Verbose,
-              ("couldn't copy cert bytes (out of memory?)"));
-      continue;
+    PCCERT_CONTEXT certificate = nullptr;
+    uint32_t numImported = 0;
+    while ((certificate = CertFindCertificateInStore(
+                enterpriseRootStore.get(), X509_ASN_ENCODING, 0, CERT_FIND_ANY,
+                nullptr, certificate))) {
+      bool isTrusted;
+      bool isRoot;
+      CertIsTrustAnchorForTLSServerAuth(certificate, isTrusted, isRoot);
+      if (!isTrusted) {
+        MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+                ("skipping cert not trusted for TLS server auth"));
+        continue;
+      }
+      EnterpriseCert enterpriseCert;
+      if (NS_FAILED(enterpriseCert.Init(certificate->pbCertEncoded,
+                                        certificate->cbCertEncoded, isRoot))) {
+        // Best-effort. We probably ran out of memory.
+        continue;
+      }
+      if (!certs.append(std::move(enterpriseCert))) {
+        // Best-effort again.
+        continue;
+      }
+      numImported++;
     }
-    if (!roots.append(std::move(certBytes))) {
-      MOZ_LOG(gPIPNSSLog, LogLevel::Verbose,
-              ("couldn't append cert bytes to roots list (out of memory?)"));
-      continue;
-    }
-    numImported++;
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+            ("imported %u certs from %S", numImported, name));
   }
-  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u roots", numImported));
 }
 
-static void GatherEnterpriseRootsWindows(Vector<Vector<uint8_t>>& roots) {
-  GatherEnterpriseRootsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE, roots);
-  GatherEnterpriseRootsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
-                                   roots);
-  GatherEnterpriseRootsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
-                                   roots);
+static void GatherEnterpriseCertsWindows(Vector<EnterpriseCert>& certs) {
+  GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE, certs);
+  GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY,
+                                   certs);
+  GatherEnterpriseCertsForLocation(CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
+                                   certs);
 }
 #endif  // XP_WIN
 
 #ifdef XP_MACOSX
-OSStatus GatherEnterpriseRootsOSX(Vector<Vector<uint8_t>>& roots) {
+OSStatus GatherEnterpriseCertsMacOS(Vector<EnterpriseCert>& certs) {
   // The following builds a search dictionary corresponding to:
   // { class: "certificate",
   //   match limit: "match all",
   //   policy: "SSL (TLS)",
   //   only include trusted certificates: true }
   // This operates on items that have been added to the keychain and thus gives
   // us all 3rd party certificates that have been trusted for SSL (TLS), which
   // is what we want (thus we don't import built-in root certificates that ship
@@ -199,41 +233,40 @@ OSStatus GatherEnterpriseRootsOSX(Vector
   CFIndex count = CFArrayGetCount(arr.get());
   uint32_t numImported = 0;
   for (CFIndex i = 0; i < count; i++) {
     const CFTypeRef c = CFArrayGetValueAtIndex(arr.get(), i);
     // Because we asked for certificates, each CFTypeRef in the array is really
     // a SecCertificateRef.
     const SecCertificateRef s = (const SecCertificateRef)c;
     ScopedCFType<CFDataRef> der(SecCertificateCopyData(s));
-    const unsigned char* ptr = CFDataGetBytePtr(der.get());
-    unsigned int len = CFDataGetLength(der.get());
-    Vector<uint8_t> certBytes;
-    if (!certBytes.append(ptr, len)) {
-      MOZ_LOG(gPIPNSSLog, LogLevel::Verbose,
-              ("couldn't copy cert bytes (out of memory?)"));
+    EnterpriseCert enterpriseCert;
+    // TODO(BUG 1526004) currently we treat all 3rd party certificates as trust
+    // anchors on MacOS. This will be addressed in a follow-up bug.
+    if (NS_FAILED(enterpriseCert.Init(CFDataGetBytePtr(der.get()),
+                                      CFDataGetLength(der.get()), true))) {
+      // Best-effort. We probably ran out of memory.
       continue;
     }
-    if (!roots.append(std::move(certBytes))) {
-      MOZ_LOG(gPIPNSSLog, LogLevel::Verbose,
-              ("couldn't append cert bytes to roots list (out of memory?)"));
+    if (!certs.append(std::move(enterpriseCert))) {
+      // Best-effort again.
       continue;
     }
     numImported++;
   }
-  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u roots", numImported));
+  MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("imported %u certs", numImported));
   return errSecSuccess;
 }
 #endif  // XP_MACOSX
 
-nsresult GatherEnterpriseRoots(Vector<Vector<uint8_t>>& roots) {
-  roots.clear();
+nsresult GatherEnterpriseCerts(Vector<EnterpriseCert>& certs) {
+  certs.clear();
 #ifdef XP_WIN
-  GatherEnterpriseRootsWindows(roots);
+  GatherEnterpriseCertsWindows(certs);
 #endif  // XP_WIN
 #ifdef XP_MACOSX
-  OSStatus rv = GatherEnterpriseRootsOSX(roots);
+  OSStatus rv = GatherEnterpriseCertsMacOS(certs);
   if (rv != errSecSuccess) {
     return NS_ERROR_FAILURE;
   }
 #endif  // XP_MACOSX
   return NS_OK;
 }
--- a/security/manager/ssl/EnterpriseRoots.h
+++ b/security/manager/ssl/EnterpriseRoots.h
@@ -3,13 +3,32 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef EnterpriseRoots_h
 #define EnterpriseRoots_h
 
 #include "mozilla/Vector.h"
+#include "mozpkix/Input.h"
+#include "mozpkix/Result.h"
+#include "nsTArray.h"
 
-nsresult GatherEnterpriseRoots(
-    mozilla::Vector<mozilla::Vector<uint8_t>>& roots);
+class EnterpriseCert {
+ public:
+  EnterpriseCert() : mIsRoot(false) {}
+
+  nsresult Init(const uint8_t* data, size_t len, bool isRoot);
+  // Like a copy constructor but able to return a result.
+  nsresult Init(const EnterpriseCert& orig);
+
+  nsresult CopyBytes(nsTArray<uint8_t>& dest) const;
+  mozilla::pkix::Result GetInput(mozilla::pkix::Input& input) const;
+  bool GetIsRoot() const;
+
+ private:
+  mozilla::Vector<uint8_t> mDER;
+  bool mIsRoot;
+};
+
+nsresult GatherEnterpriseCerts(mozilla::Vector<EnterpriseCert>& certs);
 
 #endif  // EnterpriseRoots_h
--- a/security/manager/ssl/SharedCertVerifier.h
+++ b/security/manager/ssl/SharedCertVerifier.h
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef SharedCertVerifier_h
 #define SharedCertVerifier_h
 
 #include "CertVerifier.h"
+#include "EnterpriseRoots.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/TimeStamp.h"
 
 namespace mozilla {
 namespace psm {
 
 class SharedCertVerifier : public mozilla::psm::CertVerifier {
  protected:
@@ -23,19 +24,19 @@ class SharedCertVerifier : public mozill
                      mozilla::TimeDuration ocspSoftTimeout,
                      mozilla::TimeDuration ocspHardTimeout,
                      uint32_t certShortLifetimeInDays, PinningMode pinningMode,
                      SHA1Mode sha1Mode,
                      BRNameMatchingPolicy::Mode nameMatchingMode,
                      NetscapeStepUpPolicy netscapeStepUpPolicy,
                      CertificateTransparencyMode ctMode,
                      DistrustedCAPolicy distrustedCAPolicy,
-                     const Vector<Vector<uint8_t>>& thirdPartyRoots)
+                     const Vector<EnterpriseCert>& thirdPartyCerts)
       : mozilla::psm::CertVerifier(
             odc, osc, ocspSoftTimeout, ocspHardTimeout, certShortLifetimeInDays,
             pinningMode, sha1Mode, nameMatchingMode, netscapeStepUpPolicy,
-            ctMode, distrustedCAPolicy, thirdPartyRoots) {}
+            ctMode, distrustedCAPolicy, thirdPartyCerts) {}
 };
 
 }  // namespace psm
 }  // namespace mozilla
 
 #endif  // SharedCertVerifier_h
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -63,16 +63,17 @@ TESTING_JS_MODULES.psm += [
 ]
 
 EXTRA_JS_MODULES.psm += [
     'RemoteSecuritySettings.jsm',
 ]
 
 EXPORTS += [
     'CryptoTask.h',
+    'EnterpriseRoots.h',
     'nsClientAuthRemember.h',
     'nsNSSCallbacks.h',
     'nsNSSCertificate.h',
     'nsNSSComponent.h',
     'nsNSSHelper.h',
     'nsRandomGenerator.h',
     'nsSecurityHeaderParser.h',
     'NSSErrorsService.h',
--- a/security/manager/ssl/nsINSSComponent.idl
+++ b/security/manager/ssl/nsINSSComponent.idl
@@ -50,16 +50,21 @@ interface nsINSSComponent : nsISupports 
    * certificates (i.e. root certificates gleaned from the OS certificate
    * store). Returns an empty array otherwise.
    * Currently this is only implemented on Windows and MacOS X, so this
    * function returns an empty array on all other platforms.
    */
   Array<Array<octet> > getEnterpriseRoots();
 
   /**
+   * Similarly, but for intermediate certificates.
+   */
+  Array<Array<octet> > getEnterpriseIntermediates();
+
+  /**
    * For performance reasons, the builtin roots module is loaded on a background
    * thread. When any code that depends on the builtin roots module runs, it
    * must first wait for the module to be loaded.
    */
   [noscript] void blockUntilLoadableRootsLoaded();
 
   /**
    * In theory a token on a PKCS#11 module can be inserted or removed at any
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -477,17 +477,17 @@ bool nsNSSComponent::ShouldEnableEnterpr
 
 void nsNSSComponent::UnloadEnterpriseRoots() {
   MOZ_ASSERT(NS_IsMainThread());
   if (!NS_IsMainThread()) {
     return;
   }
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("UnloadEnterpriseRoots"));
   MutexAutoLock lock(mMutex);
-  mEnterpriseRoots.clear();
+  mEnterpriseCerts.clear();
   setValidationOptions(false, lock);
 }
 
 static const char* kEnterpriseRootModePref =
     "security.enterprise_roots.enabled";
 
 void nsNSSComponent::MaybeImportEnterpriseRoots() {
   MOZ_ASSERT(NS_IsMainThread());
@@ -505,44 +505,59 @@ void nsNSSComponent::MaybeImportEnterpri
   }
   if (importEnterpriseRoots) {
     ImportEnterpriseRoots();
   }
 }
 
 void nsNSSComponent::ImportEnterpriseRoots() {
   MutexAutoLock lock(mMutex);
-  nsresult rv = GatherEnterpriseRoots(mEnterpriseRoots);
+  nsresult rv = GatherEnterpriseCerts(mEnterpriseCerts);
   if (NS_FAILED(rv)) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("failed gathering enterprise roots"));
   }
 }
 
-NS_IMETHODIMP
-nsNSSComponent::GetEnterpriseRoots(
-    nsTArray<nsTArray<uint8_t>>& enterpriseRoots) {
+nsresult nsNSSComponent::CommonGetEnterpriseCerts(
+    nsTArray<nsTArray<uint8_t>>& enterpriseCerts, bool getRoots) {
   MutexAutoLock nsNSSComponentLock(mMutex);
   MOZ_ASSERT(NS_IsMainThread());
   if (!NS_IsMainThread()) {
     return NS_ERROR_NOT_SAME_THREAD;
   }
 
-  enterpriseRoots.Clear();
-  for (const auto& root : mEnterpriseRoots) {
-    nsTArray<uint8_t> rootCopy;
-    if (!rootCopy.AppendElements(root.begin(), root.length())) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-    if (!enterpriseRoots.AppendElement(std::move(rootCopy))) {
-      return NS_ERROR_OUT_OF_MEMORY;
+  enterpriseCerts.Clear();
+  for (const auto& cert : mEnterpriseCerts) {
+    nsTArray<uint8_t> certCopy;
+    // mEnterpriseCerts includes both roots and intermediates.
+    if (cert.GetIsRoot() == getRoots) {
+      nsresult rv = cert.CopyBytes(certCopy);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+      if (!enterpriseCerts.AppendElement(std::move(certCopy))) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
     }
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsNSSComponent::GetEnterpriseRoots(
+    nsTArray<nsTArray<uint8_t>>& enterpriseRoots) {
+  return CommonGetEnterpriseCerts(enterpriseRoots, true);
+}
+
+NS_IMETHODIMP
+nsNSSComponent::GetEnterpriseIntermediates(
+    nsTArray<nsTArray<uint8_t>>& enterpriseIntermediates) {
+  return CommonGetEnterpriseCerts(enterpriseIntermediates, false);
+}
+
 class LoadLoadableRootsTask final : public Runnable {
  public:
   LoadLoadableRootsTask(nsNSSComponent* nssComponent,
                         bool importEnterpriseRoots, uint32_t familySafetyMode,
                         Vector<nsCString>&& possibleLoadableRootsLocations)
       : Runnable("LoadLoadableRootsTask"),
         mNSSComponent(nssComponent),
         mImportEnterpriseRoots(importEnterpriseRoots),
@@ -1194,17 +1209,17 @@ void nsNSSComponent::setValidationOption
   TimeDuration hardTimeout;
 
   GetRevocationBehaviorFromPrefs(&odc, &osc, &certShortLifetimeInDays,
                                  softTimeout, hardTimeout, proofOfLock);
 
   mDefaultCertVerifier = new SharedCertVerifier(
       odc, osc, softTimeout, hardTimeout, certShortLifetimeInDays, pinningMode,
       sha1Mode, nameMatchingMode, netscapeStepUpPolicy, ctMode,
-      distrustedCAPolicy, mEnterpriseRoots);
+      distrustedCAPolicy, mEnterpriseCerts);
 }
 
 void nsNSSComponent::UpdateCertVerifierWithEnterpriseRoots() {
   MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mDefaultCertVerifier);
   if (NS_WARN_IF(!mDefaultCertVerifier)) {
     return;
   }
@@ -1213,17 +1228,17 @@ void nsNSSComponent::UpdateCertVerifierW
   mDefaultCertVerifier = new SharedCertVerifier(
       oldCertVerifier->mOCSPDownloadConfig,
       oldCertVerifier->mOCSPStrict ? CertVerifier::ocspStrict
                                    : CertVerifier::ocspRelaxed,
       oldCertVerifier->mOCSPTimeoutSoft, oldCertVerifier->mOCSPTimeoutHard,
       oldCertVerifier->mCertShortLifetimeInDays, oldCertVerifier->mPinningMode,
       oldCertVerifier->mSHA1Mode, oldCertVerifier->mNameMatchingMode,
       oldCertVerifier->mNetscapeStepUpPolicy, oldCertVerifier->mCTMode,
-      oldCertVerifier->mDistrustedCAPolicy, mEnterpriseRoots);
+      oldCertVerifier->mDistrustedCAPolicy, mEnterpriseCerts);
 }
 
 // Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and
 // TLS 1.2 (max) when the prefs aren't set or set to invalid values.
 nsresult nsNSSComponent::setEnabledTLSVersions() {
   // keep these values in sync with security-prefs.js
   // 1 means TLS 1.0, 2 means TLS 1.1, etc.
   static const uint32_t PSM_DEFAULT_MIN_TLS_VERSION = 1;
--- a/security/manager/ssl/nsNSSComponent.h
+++ b/security/manager/ssl/nsNSSComponent.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef _nsNSSComponent_h_
 #define _nsNSSComponent_h_
 
 #include "nsINSSComponent.h"
 
+#include "EnterpriseRoots.h"
 #include "ScopedNSSTypes.h"
 #include "SharedCertVerifier.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
@@ -81,16 +82,18 @@ class nsNSSComponent final : public nsIN
                             const mozilla::MutexAutoLock& proofOfLock);
   void UpdateCertVerifierWithEnterpriseRoots();
   nsresult setEnabledTLSVersions();
   nsresult RegisterObservers();
 
   void MaybeImportEnterpriseRoots();
   void ImportEnterpriseRoots();
   void UnloadEnterpriseRoots();
+  nsresult CommonGetEnterpriseCerts(
+      nsTArray<nsTArray<uint8_t>>& enterpriseCerts, bool getRoots);
 
   bool ShouldEnableEnterpriseRootsForFamilySafety(uint32_t familySafetyMode);
 
   // mLoadableRootsLoadedMonitor protects mLoadableRootsLoaded.
   mozilla::Monitor mLoadableRootsLoadedMonitor;
   bool mLoadableRootsLoaded;
   nsresult mLoadableRootsLoadedResult;
 
@@ -101,17 +104,17 @@ class nsNSSComponent final : public nsIN
 
 #ifdef DEBUG
   nsString mTestBuiltInRootHash;
 #endif
   nsString mContentSigningRootHash;
   RefPtr<mozilla::psm::SharedCertVerifier> mDefaultCertVerifier;
   nsString mMitmCanaryIssuer;
   bool mMitmDetecionEnabled;
-  mozilla::Vector<mozilla::Vector<uint8_t>> mEnterpriseRoots;
+  mozilla::Vector<EnterpriseCert> mEnterpriseCerts;
 
   // The following members are accessed only on the main thread:
   static int mInstanceCount;
   // If InitializeNSS succeeds, then we have dispatched an event to load the
   // loadable roots module on a background thread. We must wait for it to
   // complete before attempting to unload the module again in ShutdownNSS. If we
   // never dispatched the event, then we can't wait for it to complete (because
   // it will never complete) so we use this boolean to keep track of if we