Bug 1547877 - enable configuration of new cert storage implementation r=keeler
authorMyk Melez <myk@mykzilla.org>
Thu, 02 May 2019 23:02:13 +0000
changeset 531193 d7b02bc7cf44b2eba303a009a5b2e836d0345b69
parent 531192 20280c382db03c171f90a5c4c8b8ed669df8b9b6
child 531210 083106d8fc7407c880a3a044c83d4e15e5961063
child 531211 6c00dab7137aabe0a86b9d64f567383dd2b81635
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs1547877
milestone68.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 1547877 - enable configuration of new cert storage implementation r=keeler Differential Revision: https://phabricator.services.mozilla.com/D29306
netwerk/base/nsNetUtil.cpp
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/manager/ssl/CSTrustDomain.cpp
security/manager/ssl/CSTrustDomain.h
security/manager/ssl/CertBlocklist.cpp
security/manager/ssl/CertBlocklist.h
security/manager/ssl/DataStorage.cpp
security/manager/ssl/NSSErrorsService.h
security/manager/ssl/components.conf
security/manager/ssl/moz.build
security/manager/ssl/nsICertBlocklist.idl
security/manager/ssl/nsNSSModule.cpp
security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
security/manager/ssl/tests/unit/test_cert_storage.js
security/manager/ssl/tests/unit/test_cert_storage_direct.js
security/manager/ssl/tests/unit/test_cert_storage_prefs.js
security/manager/ssl/tests/unit/test_intermediate_preloads.js
services/common/blocklist-clients.js
toolkit/library/gtest/rust/Cargo.toml
toolkit/library/rust/Cargo.toml
toolkit/library/rust/gkrust-features.mozbuild
toolkit/library/rust/shared/Cargo.toml
toolkit/library/rust/shared/lib.rs
toolkit/modules/AppConstants.jsm
toolkit/moz.configure
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -76,17 +76,21 @@
 #include "mozilla/dom/nsMixedContentBlocker.h"
 #include "mozilla/dom/BlobURLProtocolHandler.h"
 #include "mozilla/net/HttpBaseChannel.h"
 #include "nsIScriptError.h"
 #include "nsISiteSecurityService.h"
 #include "nsHttpHandler.h"
 #include "nsNSSComponent.h"
 #include "nsIRedirectHistoryEntry.h"
-#include "nsICertStorage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsICertStorage.h"
+#else
+#  include "nsICertBlocklist.h"
+#endif
 #include "nsICertOverrideService.h"
 #include "nsQueryObject.h"
 #include "mozIThirdPartyUtil.h"
 #include "../mime/nsMIMEHeaderParamImpl.h"
 #include "nsStandardURL.h"
 #include "nsChromeProtocolHandler.h"
 #include "nsJSProtocolHandler.h"
 #include "nsDataHandler.h"
@@ -2560,17 +2564,21 @@ nsresult NS_GetFilenameFromDisposition(n
 }
 
 void net_EnsurePSMInit() {
   nsresult rv;
   nsCOMPtr<nsISupports> psm = do_GetService(PSM_COMPONENT_CONTRACTID, &rv);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   nsCOMPtr<nsISupports> sss = do_GetService(NS_SSSERVICE_CONTRACTID);
+#ifdef MOZ_NEW_CERT_STORAGE
   nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTSTORAGE_CONTRACTID);
+#else
+  nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTBLOCKLIST_CONTRACTID);
+#endif
   nsCOMPtr<nsISupports> cos = do_GetService(NS_CERTOVERRIDE_CONTRACTID);
 }
 
 bool NS_IsAboutBlank(nsIURI* uri) {
   // GetSpec can be expensive for some URIs, so check the scheme first.
   bool isAbout = false;
   if (NS_FAILED(uri->SchemeIs("about", &isAbout)) || !isAbout) {
     return false;
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -9,17 +9,19 @@
 #include <stdint.h>
 
 #include "ExtendedValidation.h"
 #include "NSSErrorsService.h"
 #include "OCSPVerificationTrustDomain.h"
 #include "PublicKeyPinningService.h"
 #include "cert.h"
 #include "certdb.h"
-#include "cert_storage/src/cert_storage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "cert_storage/src/cert_storage.h"
+#endif
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Services.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "nsCRTGlue.h"
@@ -83,26 +85,31 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
       mDistrustedCAPolicy(distrustedCAPolicy),
       mSawDistrustedCAByPolicyError(false),
       mOriginAttributes(originAttributes),
       mThirdPartyRootInputs(thirdPartyRootInputs),
       mThirdPartyIntermediateInputs(thirdPartyIntermediateInputs),
       mBuiltChain(builtChain),
       mPinningTelemetryInfo(pinningTelemetryInfo),
       mHostname(hostname),
+#ifdef MOZ_NEW_CERT_STORAGE
       mCertStorage(do_GetService(NS_CERT_STORAGE_CID)),
+#else
+      mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)),
+#endif
       mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED),
       mSCTListFromCertificate(),
       mSCTListFromOCSPStapling() {}
 
 Result NSSCertDBTrustDomain::FindIssuer(Input encodedIssuerName,
                                         IssuerChecker& checker, Time) {
   Vector<Input> rootCandidates;
   Vector<Input> intermediateCandidates;
 
+#ifdef MOZ_NEW_CERT_STORAGE
   if (!mCertStorage) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
   nsTArray<uint8_t> subject;
   if (!subject.AppendElements(encodedIssuerName.UnsafeGetData(),
                               encodedIssuerName.GetLength())) {
     return Result::FATAL_ERROR_NO_MEMORY;
   }
@@ -117,16 +124,17 @@ Result NSSCertDBTrustDomain::FindIssuer(
     if (rv != Success) {
       continue;  // probably too big
     }
     // Currently we're only expecting intermediate certificates in cert storage.
     if (!intermediateCandidates.append(certDER)) {
       return Result::FATAL_ERROR_NO_MEMORY;
     }
   }
+#endif
 
   SECItem encodedIssuerNameItem = UnsafeMapInputToSECItem(encodedIssuerName);
 
   // NSS seems not to differentiate between "no potential issuers found" and
   // "there was an error trying to retrieve the potential issuers." We assume
   // there was no error if CERT_CreateSubjectCertList returns nullptr.
   UniqueCERTCertList candidates(CERT_CreateSubjectCertList(
       nullptr, CERT_GetDefaultCertDB(), &encodedIssuerNameItem, 0, false));
@@ -211,44 +219,69 @@ Result NSSCertDBTrustDomain::GetCertTrus
   SECItem candidateCertDERSECItem = UnsafeMapInputToSECItem(candidateCertDER);
   UniqueCERTCertificate candidateCert(CERT_NewTempCertificate(
       CERT_GetDefaultCertDB(), &candidateCertDERSECItem, nullptr, false, true));
   if (!candidateCert) {
     return MapPRErrorCodeToResult(PR_GetError());
   }
 
   // Check the certificate against the OneCRL cert blocklist
+#ifdef MOZ_NEW_CERT_STORAGE
   if (!mCertStorage) {
+#else
+  if (!mCertBlocklist) {
+#endif
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   // The certificate blocklist currently only applies to TLS server
   // certificates.
   if (mCertDBTrustType == trustSSL) {
+#ifdef MOZ_NEW_CERT_STORAGE
     int16_t revocationState;
 
     nsTArray<uint8_t> issuerBytes;
     nsTArray<uint8_t> serialBytes;
     nsTArray<uint8_t> subjectBytes;
     nsTArray<uint8_t> pubKeyBytes;
 
     nsresult nsrv = BuildRevocationCheckArrays(
         candidateCert, issuerBytes, serialBytes, subjectBytes, pubKeyBytes);
+#else
+    bool isCertRevoked;
+
+    nsAutoCString encIssuer;
+    nsAutoCString encSerial;
+    nsAutoCString encSubject;
+    nsAutoCString encPubKey;
+
+    nsresult nsrv = BuildRevocationCheckStrings(
+        candidateCert.get(), encIssuer, encSerial, encSubject, encPubKey);
+#endif
 
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
 
+#ifdef MOZ_NEW_CERT_STORAGE
     nsrv = mCertStorage->GetRevocationState(
         issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState);
+#else
+    nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject,
+                                         encPubKey, &isCertRevoked);
+#endif
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
 
+#ifdef MOZ_NEW_CERT_STORAGE
     if (revocationState == nsICertStorage::STATE_ENFORCE) {
+#else
+    if (isCertRevoked) {
+#endif
       MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
               ("NSSCertDBTrustDomain: certificate is in blocklist"));
       return Result::ERROR_REVOKED_CERTIFICATE;
     }
   }
 
   // This may be a third-party root.
   for (const auto& thirdPartyRootInput : mThirdPartyRootInputs) {
@@ -492,17 +525,21 @@ Result NSSCertDBTrustDomain::CheckRevoca
   }
   // At this point, if and only if cachedErrorResult is Success, there was no
   // cached response.
   MOZ_ASSERT((!cachedResponsePresent && cachedResponseResult == Success) ||
              (cachedResponsePresent && cachedResponseResult != Success));
 
   // If we have a fresh OneCRL Blocklist we can skip OCSP for CA certs
   bool blocklistIsFresh;
+#ifdef MOZ_NEW_CERT_STORAGE
   nsresult nsrv = mCertStorage->IsBlocklistFresh(&blocklistIsFresh);
+#else
+  nsresult nsrv = mCertBlocklist->IsBlocklistFresh(&blocklistIsFresh);
+#endif
   if (NS_FAILED(nsrv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   // TODO: We still need to handle the fallback for invalid stapled responses.
   // But, if/when we disable OCSP fetching by default, it would be ambiguous
   // whether security.OCSP.enable==0 means "I want the default" or "I really
   // never want you to ever fetch OCSP."
@@ -1268,16 +1305,17 @@ nsresult DefaultServerNicknameForCert(co
     if (!conflict) {
       return NS_OK;
     }
   }
 
   return NS_ERROR_FAILURE;
 }
 
+#ifdef MOZ_NEW_CERT_STORAGE
 nsresult BuildRevocationCheckArrays(const UniqueCERTCertificate& cert,
                                     /*out*/ nsTArray<uint8_t>& issuerBytes,
                                     /*out*/ nsTArray<uint8_t>& serialBytes,
                                     /*out*/ nsTArray<uint8_t>& subjectBytes,
                                     /*out*/ nsTArray<uint8_t>& pubKeyBytes) {
   issuerBytes.Clear();
   if (!issuerBytes.AppendElements(
           BitwiseCast<char*, uint8_t*>(cert->derIssuer.data),
@@ -1299,16 +1337,55 @@ nsresult BuildRevocationCheckArrays(cons
   pubKeyBytes.Clear();
   if (!pubKeyBytes.AppendElements(
           BitwiseCast<char*, uint8_t*>(cert->derPublicKey.data),
           cert->derPublicKey.len)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
+#else
+nsresult BuildRevocationCheckStrings(const CERTCertificate* cert,
+                                     /*out*/ nsCString& encIssuer,
+                                     /*out*/ nsCString& encSerial,
+                                     /*out*/ nsCString& encSubject,
+                                     /*out*/ nsCString& encPubKey) {
+  // Convert issuer, serial, subject and pubKey data to Base64 encoded DER
+  nsDependentCSubstring issuerString(
+      BitwiseCast<char*, uint8_t*>(cert->derIssuer.data), cert->derIssuer.len);
+  nsDependentCSubstring serialString(
+      BitwiseCast<char*, uint8_t*>(cert->serialNumber.data),
+      cert->serialNumber.len);
+  nsDependentCSubstring subjectString(
+      BitwiseCast<char*, uint8_t*>(cert->derSubject.data),
+      cert->derSubject.len);
+  nsDependentCSubstring pubKeyString(
+      BitwiseCast<char*, uint8_t*>(cert->derPublicKey.data),
+      cert->derPublicKey.len);
+
+  nsresult rv = Base64Encode(issuerString, encIssuer);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Encode(serialString, encSerial);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Encode(subjectString, encSubject);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Encode(pubKeyString, encPubKey);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+#endif
 
 /**
  * Given a list of certificates representing a verified certificate path from an
  * end-entity certificate to a trust anchor, imports the intermediate
  * certificates into the permanent certificate database. This is an attempt to
  * cope with misconfigured servers that don't include the appropriate
  * intermediate certificates in the TLS handshake.
  *
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -6,17 +6,21 @@
 
 #ifndef NSSCertDBTrustDomain_h
 #define NSSCertDBTrustDomain_h
 
 #include "CertVerifier.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/TimeStamp.h"
-#include "nsICertStorage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsICertStorage.h"
+#else
+#  include "nsICertBlocklist.h"
+#endif
 #include "nsString.h"
 #include "mozpkix/pkixtypes.h"
 #include "secmodt.h"
 
 namespace mozilla {
 namespace psm {
 
 enum class ValidityCheckingMode {
@@ -54,16 +58,17 @@ void DisableMD5();
  */
 bool LoadLoadableRoots(const nsCString& dir);
 
 void UnloadLoadableRoots();
 
 nsresult DefaultServerNicknameForCert(const CERTCertificate* cert,
                                       /*out*/ nsCString& nickname);
 
+#ifdef MOZ_NEW_CERT_STORAGE
 /**
  * Build nsTArray<uint8_t>s out of the issuer, serial, subject and public key
  * data from the supplied certificate for use in revocation checks.
  *
  * @param cert
  *        The CERTCertificate* from which to extract the data.
  * @param out encIssuer
  *        The array to populate with issuer data.
@@ -77,16 +82,41 @@ nsresult DefaultServerNicknameForCert(co
  *        NS_OK, unless there's a memory allocation problem, in which case
  *        NS_ERROR_OUT_OF_MEMORY.
  */
 nsresult BuildRevocationCheckArrays(const UniqueCERTCertificate& cert,
                                     /*out*/ nsTArray<uint8_t>& issuerBytes,
                                     /*out*/ nsTArray<uint8_t>& serialBytes,
                                     /*out*/ nsTArray<uint8_t>& subjectBytes,
                                     /*out*/ nsTArray<uint8_t>& pubKeyBytes);
+#else
+/**
+ * Build strings of base64 encoded issuer, serial, subject and public key data
+ * from the supplied certificate for use in revocation checks.
+ *
+ * @param cert
+ *        The CERTCertificate* from which to extract the data.
+ * @param out encIssuer
+ *        The string to populate with base64 encoded issuer data.
+ * @param out encSerial
+ *        The string to populate with base64 encoded serial number data.
+ * @param out encSubject
+ *        The string to populate with base64 encoded subject data.
+ * @param out encPubKey
+ *        The string to populate with base64 encoded public key data.
+ * @return
+ *        NS_OK, unless there's a Base64 encoding problem, in which case
+ *        NS_ERROR_FAILURE.
+ */
+nsresult BuildRevocationCheckStrings(const CERTCertificate* cert,
+                                     /*out*/ nsCString& encIssuer,
+                                     /*out*/ nsCString& encSerial,
+                                     /*out*/ nsCString& encSubject,
+                                     /*out*/ nsCString& encPubKey);
+#endif
 
 void SaveIntermediateCerts(const UniqueCERTCertList& certList);
 
 class NSSCertDBTrustDomain : public mozilla::pkix::TrustDomain {
  public:
   typedef mozilla::pkix::Result Result;
 
   enum OCSPFetching {
@@ -227,17 +257,21 @@ class NSSCertDBTrustDomain : public mozi
   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
+#ifdef MOZ_NEW_CERT_STORAGE
   nsCOMPtr<nsICertStorage> mCertStorage;
+#else
+  nsCOMPtr<nsICertBlocklist> mCertBlocklist;
+#endif
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
   // Certificate Transparency data extracted during certificate verification
   UniqueSECItem mSCTListFromCertificate;
   UniqueSECItem mSCTListFromOCSPStapling;
 };
 
 }  // namespace psm
 }  // namespace mozilla
--- a/security/manager/ssl/CSTrustDomain.cpp
+++ b/security/manager/ssl/CSTrustDomain.cpp
@@ -1,19 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* 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/. */
 
-#include "cert_storage/src/cert_storage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "cert_storage/src/cert_storage.h"
+#endif
 #include "CSTrustDomain.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Preferences.h"
-#include "nsDirectoryServiceUtils.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsDirectoryServiceUtils.h"
+#endif
 #include "nsNSSCertificate.h"
 #include "nsNSSComponent.h"
 #include "NSSCertDBTrustDomain.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "mozpkix/pkixnss.h"
 
 using namespace mozilla::pkix;
@@ -21,17 +25,21 @@ using namespace mozilla::pkix;
 namespace mozilla {
 namespace psm {
 
 static LazyLogModule gTrustDomainPRLog("CSTrustDomain");
 #define CSTrust_LOG(args) MOZ_LOG(gTrustDomainPRLog, LogLevel::Debug, args)
 
 CSTrustDomain::CSTrustDomain(UniqueCERTCertList& certChain)
     : mCertChain(certChain),
+#ifdef MOZ_NEW_CERT_STORAGE
       mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)) {}
+#else
+      mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)) {}
+#endif
 
 Result CSTrustDomain::GetCertTrust(EndEntityOrCA endEntityOrCA,
                                    const CertPolicyId& policy,
                                    Input candidateCertDER,
                                    /*out*/ TrustLevel& trustLevel) {
   MOZ_ASSERT(policy.IsAnyPolicy());
   if (!policy.IsAnyPolicy()) {
     return Result::FATAL_ERROR_INVALID_ARGS;
@@ -39,35 +47,55 @@ Result CSTrustDomain::GetCertTrust(EndEn
 
   SECItem candidateCertDERSECItem = UnsafeMapInputToSECItem(candidateCertDER);
   UniqueCERTCertificate candidateCert(CERT_NewTempCertificate(
       CERT_GetDefaultCertDB(), &candidateCertDERSECItem, nullptr, false, true));
   if (!candidateCert) {
     return MapPRErrorCodeToResult(PR_GetError());
   }
 
+#ifdef MOZ_NEW_CERT_STORAGE
   nsTArray<uint8_t> issuerBytes;
   nsTArray<uint8_t> serialBytes;
   nsTArray<uint8_t> subjectBytes;
   nsTArray<uint8_t> pubKeyBytes;
 
   nsresult nsrv = BuildRevocationCheckArrays(
       candidateCert, issuerBytes, serialBytes, subjectBytes, pubKeyBytes);
+#else
+  nsAutoCString encIssuer;
+  nsAutoCString encSerial;
+  nsAutoCString encSubject;
+  nsAutoCString encPubKey;
+
+  nsresult nsrv = BuildRevocationCheckStrings(candidateCert.get(), encIssuer,
+                                              encSerial, encSubject, encPubKey);
+#endif
   if (NS_FAILED(nsrv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
+#ifdef MOZ_NEW_CERT_STORAGE
   int16_t revocationState;
   nsrv = mCertBlocklist->GetRevocationState(
       issuerBytes, serialBytes, subjectBytes, pubKeyBytes, &revocationState);
+#else
+  bool isCertRevoked;
+  nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject,
+                                       encPubKey, &isCertRevoked);
+#endif
   if (NS_FAILED(nsrv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
+#ifdef MOZ_NEW_CERT_STORAGE
   if (revocationState == nsICertStorage::STATE_ENFORCE) {
+#else
+  if (isCertRevoked) {
+#endif
     CSTrust_LOG(("CSTrustDomain: certificate is revoked\n"));
     return Result::ERROR_REVOKED_CERTIFICATE;
   }
 
   // Is this cert our built-in content signing root?
   bool isRoot = false;
   nsCOMPtr<nsINSSComponent> component(do_GetService(PSM_COMPONENT_CONTRACTID));
   if (!component) {
--- a/security/manager/ssl/CSTrustDomain.h
+++ b/security/manager/ssl/CSTrustDomain.h
@@ -6,17 +6,21 @@
 
 #ifndef CSTrustDomain_h
 #define CSTrustDomain_h
 
 #include "mozpkix/pkixtypes.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/UniquePtr.h"
 #include "nsDebug.h"
-#include "nsICertStorage.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsICertStorage.h"
+#else
+#  include "nsICertBlocklist.h"
+#endif
 #include "nsIX509CertDB.h"
 #include "ScopedNSSTypes.h"
 
 namespace mozilla {
 namespace psm {
 
 class CSTrustDomain final : public mozilla::pkix::TrustDomain {
  public:
@@ -68,15 +72,19 @@ class CSTrustDomain final : public mozil
       mozilla::pkix::Input extensionData) override;
   virtual Result DigestBuf(mozilla::pkix::Input item,
                            mozilla::pkix::DigestAlgorithm digestAlg,
                            /*out*/ uint8_t* digestBuf,
                            size_t digestBufLen) override;
 
  private:
   /*out*/ UniqueCERTCertList& mCertChain;
+#ifdef MOZ_NEW_CERT_STORAGE
   nsCOMPtr<nsICertStorage> mCertBlocklist;
+#else
+  nsCOMPtr<nsICertBlocklist> mCertBlocklist;
+#endif
 };
 
 }  // namespace psm
 }  // namespace mozilla
 
 #endif  // CSTrustDomain_h
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/CertBlocklist.cpp
@@ -0,0 +1,627 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "CertBlocklist.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Casting.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDependentString.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsICryptoHash.h"
+#include "nsIFileStreams.h"
+#include "nsILineInputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsIX509Cert.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsPromiseFlatString.h"
+#include "nsTHashtable.h"
+#include "nsThreadUtils.h"
+#include "mozpkix/Input.h"
+#include "prtime.h"
+
+NS_IMPL_ISUPPORTS(CertBlocklist, nsICertBlocklist)
+
+using namespace mozilla;
+using namespace mozilla::pkix;
+
+#define PREF_BACKGROUND_UPDATE_TIMER \
+  "app.update.lastUpdateTime.blocklist-background-update-timer"
+#define PREF_BLOCKLIST_ONECRL_CHECKED \
+  "services.settings.security.onecrl.checked"
+#define PREF_MAX_STALENESS_IN_SECONDS \
+  "security.onecrl.maximum_staleness_in_seconds"
+
+static LazyLogModule gCertBlockPRLog("CertBlock");
+
+uint32_t CertBlocklist::sLastBlocklistUpdate = 0U;
+uint32_t CertBlocklist::sMaxStaleness = 0U;
+
+CertBlocklistItem::CertBlocklistItem(const uint8_t* DNData, size_t DNLength,
+                                     const uint8_t* otherData,
+                                     size_t otherLength,
+                                     CertBlocklistItemMechanism itemMechanism)
+    : mIsCurrent(false), mItemMechanism(itemMechanism) {
+  mDNData = new uint8_t[DNLength];
+  memcpy(mDNData, DNData, DNLength);
+  mDNLength = DNLength;
+
+  mOtherData = new uint8_t[otherLength];
+  memcpy(mOtherData, otherData, otherLength);
+  mOtherLength = otherLength;
+}
+
+CertBlocklistItem::CertBlocklistItem(const CertBlocklistItem& aItem) {
+  mDNLength = aItem.mDNLength;
+  mDNData = new uint8_t[mDNLength];
+  memcpy(mDNData, aItem.mDNData, mDNLength);
+
+  mOtherLength = aItem.mOtherLength;
+  mOtherData = new uint8_t[mOtherLength];
+  memcpy(mOtherData, aItem.mOtherData, mOtherLength);
+
+  mItemMechanism = aItem.mItemMechanism;
+
+  mIsCurrent = aItem.mIsCurrent;
+}
+
+CertBlocklistItem::~CertBlocklistItem() {
+  delete[] mDNData;
+  delete[] mOtherData;
+}
+
+nsresult CertBlocklistItem::ToBase64(nsACString& b64DNOut,
+                                     nsACString& b64OtherOut) {
+  nsDependentCSubstring DNString(BitwiseCast<char*, uint8_t*>(mDNData),
+                                 mDNLength);
+  nsDependentCSubstring otherString(BitwiseCast<char*, uint8_t*>(mOtherData),
+                                    mOtherLength);
+  nsresult rv = Base64Encode(DNString, b64DNOut);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Encode(otherString, b64OtherOut);
+  return rv;
+}
+
+bool CertBlocklistItem::operator==(const CertBlocklistItem& aItem) const {
+  if (aItem.mItemMechanism != mItemMechanism) {
+    return false;
+  }
+  if (aItem.mDNLength != mDNLength || aItem.mOtherLength != mOtherLength) {
+    return false;
+  }
+  return memcmp(aItem.mDNData, mDNData, mDNLength) == 0 &&
+         memcmp(aItem.mOtherData, mOtherData, mOtherLength) == 0;
+}
+
+uint32_t CertBlocklistItem::Hash() const {
+  uint32_t hash;
+  // there's no requirement for a serial to be as large as the size of the hash
+  // key; if it's smaller, fall back to the first octet (otherwise, the last
+  // four)
+  if (mItemMechanism == BlockByIssuerAndSerial &&
+      mOtherLength >= sizeof(hash)) {
+    memcpy(&hash, mOtherData + mOtherLength - sizeof(hash), sizeof(hash));
+  } else {
+    hash = *mOtherData;
+  }
+  return hash;
+}
+
+CertBlocklist::CertBlocklist()
+    : mMutex("CertBlocklist::mMutex"),
+      mModified(false),
+      mBackingFileIsInitialized(false),
+      mBackingFile(nullptr) {}
+
+CertBlocklist::~CertBlocklist() {
+  Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged,
+                                  PREF_MAX_STALENESS_IN_SECONDS, this);
+  Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged,
+                                  PREF_BLOCKLIST_ONECRL_CHECKED, this);
+}
+
+nsresult CertBlocklist::Init() {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, ("CertBlocklist::Init"));
+
+  // Init must be on main thread for getting the profile directory
+  if (!NS_IsMainThread()) {
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+            ("CertBlocklist::Init - called off main thread"));
+    return NS_ERROR_NOT_SAME_THREAD;
+  }
+
+  // Register preference callbacks
+  nsresult rv = Preferences::RegisterCallbackAndCall(
+      CertBlocklist::PreferenceChanged, PREF_MAX_STALENESS_IN_SECONDS, this);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Preferences::RegisterCallbackAndCall(
+      CertBlocklist::PreferenceChanged, PREF_BLOCKLIST_ONECRL_CHECKED, this);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Get the profile directory
+  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                              getter_AddRefs(mBackingFile));
+  if (NS_FAILED(rv) || !mBackingFile) {
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+            ("CertBlocklist::Init - couldn't get profile dir"));
+    // Since we're returning NS_OK here, set mBackingFile to a safe value.
+    // (We need initialization to succeed and CertBlocklist to be in a
+    // well-defined state if the profile directory doesn't exist.)
+    mBackingFile = nullptr;
+    return NS_OK;
+  }
+  rv = mBackingFile->Append(NS_LITERAL_STRING("revocations.txt"));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsAutoCString path;
+  rv = mBackingFile->GetPersistentDescriptor(path);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::Init certList path: %s", path.get()));
+
+  return NS_OK;
+}
+
+nsresult CertBlocklist::EnsureBackingFileInitialized(MutexAutoLock& lock) {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::EnsureBackingFileInitialized"));
+  if (mBackingFileIsInitialized || !mBackingFile) {
+    return NS_OK;
+  }
+
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::EnsureBackingFileInitialized - not initialized"));
+
+  bool exists = false;
+  nsresult rv = mBackingFile->Exists(&exists);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!exists) {
+    MOZ_LOG(
+        gCertBlockPRLog, LogLevel::Warning,
+        ("CertBlocklist::EnsureBackingFileInitialized no revocations file"));
+    return NS_OK;
+  }
+
+  // Load the revocations file into the cert blocklist
+  nsCOMPtr<nsIFileInputStream> fileStream(
+      do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = fileStream->Init(mBackingFile, -1, -1, false);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsILineInputStream> lineStream(do_QueryInterface(fileStream, &rv));
+  nsAutoCString line;
+  nsAutoCString DN;
+  nsAutoCString other;
+  CertBlocklistItemMechanism mechanism;
+  // read in the revocations file. The file format is as follows: each line
+  // contains a comment, base64 encoded DER for a DN, base64 encoded DER for a
+  // serial number or a Base64 encoded SHA256 hash of a public key. Comment
+  // lines start with '#', serial number lines, ' ' (a space), public key hashes
+  // with '\t' (a tab) and anything else is assumed to be a DN.
+  bool more = true;
+  do {
+    rv = lineStream->ReadLine(line, &more);
+    if (NS_FAILED(rv)) {
+      break;
+    }
+    // ignore comments and empty lines
+    if (line.IsEmpty() || line.First() == '#') {
+      continue;
+    }
+    if (line.First() != ' ' && line.First() != '\t') {
+      DN = line;
+      continue;
+    }
+    other = line;
+    if (line.First() == ' ') {
+      mechanism = BlockByIssuerAndSerial;
+    } else {
+      mechanism = BlockBySubjectAndPubKey;
+    }
+    other.Trim(" \t", true, false, false);
+    // Serial numbers and public key hashes 'belong' to the last DN line seen;
+    // if no DN has been seen, the serial number or public key hash is ignored.
+    if (DN.IsEmpty() || other.IsEmpty()) {
+      continue;
+    }
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+            ("CertBlocklist::EnsureBackingFileInitialized adding: %s %s",
+             DN.get(), other.get()));
+
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+            ("CertBlocklist::EnsureBackingFileInitialized - pre-decode"));
+
+    rv = AddRevokedCertInternal(DN, other, mechanism, CertOldFromLocalCache,
+                                lock);
+
+    if (NS_FAILED(rv)) {
+      // we warn here, rather than abandoning, since we need to
+      // ensure that as many items as possible are read
+      MOZ_LOG(
+          gCertBlockPRLog, LogLevel::Warning,
+          ("CertBlocklist::EnsureBackingFileInitialized adding revoked cert "
+           "failed"));
+    }
+  } while (more);
+  mBackingFileIsInitialized = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CertBlocklist::RevokeCertBySubjectAndPubKey(const nsACString& aSubject,
+                                            const nsACString& aPubKeyHash) {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::RevokeCertBySubjectAndPubKey - subject is: %s and "
+           "pubKeyHash: %s",
+           PromiseFlatCString(aSubject).get(),
+           PromiseFlatCString(aPubKeyHash).get()));
+  MutexAutoLock lock(mMutex);
+
+  return AddRevokedCertInternal(aSubject, aPubKeyHash, BlockBySubjectAndPubKey,
+                                CertNewFromBlocklist, lock);
+}
+
+NS_IMETHODIMP
+CertBlocklist::RevokeCertByIssuerAndSerial(const nsACString& aIssuer,
+                                           const nsACString& aSerialNumber) {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::RevokeCertByIssuerAndSerial - issuer is: %s and "
+           "serial: %s",
+           PromiseFlatCString(aIssuer).get(),
+           PromiseFlatCString(aSerialNumber).get()));
+  MutexAutoLock lock(mMutex);
+
+  return AddRevokedCertInternal(aIssuer, aSerialNumber, BlockByIssuerAndSerial,
+                                CertNewFromBlocklist, lock);
+}
+
+nsresult CertBlocklist::AddRevokedCertInternal(
+    const nsACString& aEncodedDN, const nsACString& aEncodedOther,
+    CertBlocklistItemMechanism aMechanism, CertBlocklistItemState aItemState,
+    MutexAutoLock& /*proofOfLock*/) {
+  nsCString decodedDN;
+  nsCString decodedOther;
+
+  nsresult rv = Base64Decode(aEncodedDN, decodedDN);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Decode(aEncodedOther, decodedOther);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  CertBlocklistItem item(
+      BitwiseCast<const uint8_t*, const char*>(decodedDN.get()),
+      decodedDN.Length(),
+      BitwiseCast<const uint8_t*, const char*>(decodedOther.get()),
+      decodedOther.Length(), aMechanism);
+
+  if (aItemState == CertNewFromBlocklist) {
+    // We want SaveEntries to be a no-op if no new entries are added.
+    nsGenericHashKey<CertBlocklistItem>* entry = mBlocklist.GetEntry(item);
+    if (!entry) {
+      mModified = true;
+    } else {
+      // Ensure that any existing item is replaced by a fresh one so we can
+      // use mIsCurrent to decide which entries to write out.
+      mBlocklist.RemoveEntry(entry);
+    }
+    item.mIsCurrent = true;
+  }
+  mBlocklist.PutEntry(item);
+
+  return NS_OK;
+}
+
+// Write a line for a given string in the output stream
+nsresult WriteLine(nsIOutputStream* outputStream, const nsACString& string) {
+  nsAutoCString line(string);
+  line.Append('\n');
+
+  const char* data = line.get();
+  uint32_t length = line.Length();
+  nsresult rv = NS_OK;
+  while (NS_SUCCEEDED(rv) && length) {
+    uint32_t bytesWritten = 0;
+    rv = outputStream->Write(data, length, &bytesWritten);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+    // if no data is written, something is wrong
+    if (!bytesWritten) {
+      return NS_ERROR_FAILURE;
+    }
+    length -= bytesWritten;
+    data += bytesWritten;
+  }
+  return rv;
+}
+
+// void saveEntries();
+// Store the blockist in a text file containing base64 encoded issuers and
+// serial numbers.
+//
+// Each item is stored on a separate line; each issuer is followed by its
+// revoked serial numbers, indented by one space.
+//
+// lines starting with a # character are ignored
+NS_IMETHODIMP
+CertBlocklist::SaveEntries() {
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
+          ("CertBlocklist::SaveEntries - not initialized"));
+  MutexAutoLock lock(mMutex);
+  if (!mModified) {
+    return NS_OK;
+  }
+
+  nsresult rv = EnsureBackingFileInitialized(lock);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  if (!mBackingFile) {
+    // We allow this to succeed with no profile directory for tests
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+            ("CertBlocklist::SaveEntries no file in profile to write to"));
+    return NS_OK;
+  }
+
+  // Data needed for writing blocklist items out to the revocations file
+  IssuerTable issuerTable;
+  BlocklistStringSet issuers;
+  nsCOMPtr<nsIOutputStream> outputStream;
+
+  rv = NS_NewAtomicFileOutputStream(getter_AddRefs(outputStream), mBackingFile,
+                                    -1, -1, 0);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = WriteLine(outputStream,
+                 NS_LITERAL_CSTRING("# Auto generated contents. Do not edit."));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // Sort blocklist items into lists of serials for each issuer
+  for (auto iter = mBlocklist.Iter(); !iter.Done(); iter.Next()) {
+    CertBlocklistItem item = iter.Get()->GetKey();
+    if (!item.mIsCurrent) {
+      continue;
+    }
+
+    nsAutoCString encDN;
+    nsAutoCString encOther;
+
+    nsresult rv = item.ToBase64(encDN, encOther);
+    if (NS_FAILED(rv)) {
+      MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+              ("CertBlocklist::SaveEntries writing revocation data failed"));
+      return NS_ERROR_FAILURE;
+    }
+
+    // If it's a subject / public key block, write it straight out
+    if (item.mItemMechanism == BlockBySubjectAndPubKey) {
+      WriteLine(outputStream, encDN);
+      WriteLine(outputStream, NS_LITERAL_CSTRING("\t") + encOther);
+      continue;
+    }
+
+    // Otherwise, we have to group entries by issuer
+    issuers.PutEntry(encDN);
+    BlocklistStringSet* issuerSet = issuerTable.Get(encDN);
+    if (!issuerSet) {
+      issuerSet = new BlocklistStringSet();
+      issuerTable.Put(encDN, issuerSet);
+    }
+    issuerSet->PutEntry(encOther);
+  }
+
+  for (auto iter = issuers.Iter(); !iter.Done(); iter.Next()) {
+    nsCStringHashKey* hashKey = iter.Get();
+    nsAutoPtr<BlocklistStringSet> issuerSet;
+    issuerTable.Remove(hashKey->GetKey(), &issuerSet);
+
+    nsresult rv = WriteLine(outputStream, hashKey->GetKey());
+    if (NS_FAILED(rv)) {
+      break;
+    }
+
+    // Write serial data to the output stream
+    for (auto iter = issuerSet->Iter(); !iter.Done(); iter.Next()) {
+      nsresult rv = WriteLine(outputStream,
+                              NS_LITERAL_CSTRING(" ") + iter.Get()->GetKey());
+      if (NS_FAILED(rv)) {
+        MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+                ("CertBlocklist::SaveEntries writing revocation data failed"));
+        return NS_ERROR_FAILURE;
+      }
+    }
+  }
+
+  nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outputStream);
+  MOZ_ASSERT(safeStream, "expected a safe output stream!");
+  if (!safeStream) {
+    return NS_ERROR_FAILURE;
+  }
+  rv = safeStream->Finish();
+  if (NS_FAILED(rv)) {
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+            ("CertBlocklist::SaveEntries saving revocation data failed"));
+    return rv;
+  }
+  mModified = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CertBlocklist::IsCertRevoked(const nsACString& aIssuerString,
+                             const nsACString& aSerialNumberString,
+                             const nsACString& aSubjectString,
+                             const nsACString& aPubKeyString, bool* _retval) {
+  MutexAutoLock lock(mMutex);
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Warning, ("CertBlocklist::IsCertRevoked"));
+
+  nsCString decodedIssuer;
+  nsCString decodedSerial;
+  nsCString decodedSubject;
+  nsCString decodedPubKey;
+
+  nsresult rv = Base64Decode(aIssuerString, decodedIssuer);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Decode(aSerialNumberString, decodedSerial);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Decode(aSubjectString, decodedSubject);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  rv = Base64Decode(aPubKeyString, decodedPubKey);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = EnsureBackingFileInitialized(lock);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  CertBlocklistItem issuerSerial(
+      BitwiseCast<const uint8_t*, const char*>(decodedIssuer.get()),
+      decodedIssuer.Length(),
+      BitwiseCast<const uint8_t*, const char*>(decodedSerial.get()),
+      decodedSerial.Length(), BlockByIssuerAndSerial);
+
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+          ("CertBlocklist::IsCertRevoked issuer %s - serial %s",
+           PromiseFlatCString(aIssuerString).get(),
+           PromiseFlatCString(aSerialNumberString).get()));
+
+  *_retval = mBlocklist.Contains(issuerSerial);
+
+  if (*_retval) {
+    MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+            ("certblocklist::IsCertRevoked found by issuer / serial"));
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsICryptoHash> crypto;
+  crypto = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
+
+  rv = crypto->Init(nsICryptoHash::SHA256);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = crypto->Update(
+      BitwiseCast<const uint8_t*, const char*>(decodedPubKey.get()),
+      decodedPubKey.Length());
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCString hashString;
+  rv = crypto->Finish(false, hashString);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  CertBlocklistItem subjectPubKey(
+      BitwiseCast<const uint8_t*, const char*>(decodedSubject.get()),
+      decodedSubject.Length(),
+      BitwiseCast<const uint8_t*, const char*>(hashString.get()),
+      hashString.Length(), BlockBySubjectAndPubKey);
+
+  nsCString encodedHash;
+  rv = Base64Encode(hashString, encodedHash);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  MOZ_LOG(
+      gCertBlockPRLog, LogLevel::Warning,
+      ("CertBlocklist::IsCertRevoked subject %s - pubKeyHash %s (pubKey %s)",
+       PromiseFlatCString(aSubjectString).get(),
+       PromiseFlatCString(encodedHash).get(),
+       PromiseFlatCString(aPubKeyString).get()));
+  *_retval = mBlocklist.Contains(subjectPubKey);
+
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+          ("CertBlocklist::IsCertRevoked by subject / pubkey? %s",
+           *_retval ? "true" : "false"));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CertBlocklist::IsBlocklistFresh(bool* _retval) {
+  MutexAutoLock lock(mMutex);
+  *_retval = false;
+
+  uint32_t now = uint32_t(PR_Now() / PR_USEC_PER_SEC);
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+          ("CertBlocklist::IsBlocklistFresh ? lastUpdate is %i",
+           sLastBlocklistUpdate));
+
+  if (now > sLastBlocklistUpdate) {
+    int64_t interval = now - sLastBlocklistUpdate;
+    MOZ_LOG(
+        gCertBlockPRLog, LogLevel::Warning,
+        ("CertBlocklist::IsBlocklistFresh we're after the last BlocklistUpdate "
+         "interval is %" PRId64 ", staleness %u",
+         interval, sMaxStaleness));
+    *_retval = sMaxStaleness > interval;
+  }
+  MOZ_LOG(
+      gCertBlockPRLog, LogLevel::Warning,
+      ("CertBlocklist::IsBlocklistFresh ? %s", *_retval ? "true" : "false"));
+  return NS_OK;
+}
+
+/* static */
+void CertBlocklist::PreferenceChanged(const char* aPref,
+                                      CertBlocklist* aBlocklist)
+
+{
+  MutexAutoLock lock(aBlocklist->mMutex);
+
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+          ("CertBlocklist::PreferenceChanged %s changed", aPref));
+  if (strcmp(aPref, PREF_BLOCKLIST_ONECRL_CHECKED) == 0) {
+    sLastBlocklistUpdate =
+        Preferences::GetUint(PREF_BLOCKLIST_ONECRL_CHECKED, uint32_t(0));
+  } else if (strcmp(aPref, PREF_MAX_STALENESS_IN_SECONDS) == 0) {
+    sMaxStaleness =
+        Preferences::GetUint(PREF_MAX_STALENESS_IN_SECONDS, uint32_t(0));
+  }
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/CertBlocklist.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 CertBlocklist_h
+#define CertBlocklist_h
+
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsClassHashtable.h"
+#include "nsICertBlocklist.h"
+#include "nsIOutputStream.h"
+#include "nsIX509CertDB.h"
+#include "nsString.h"
+#include "nsTHashtable.h"
+#include "mozpkix/Input.h"
+
+#define NS_CERT_BLOCKLIST_CID                        \
+  {                                                  \
+    0x11aefd53, 0x2fbb, 0x4c92, {                    \
+      0xa0, 0xc1, 0x05, 0x32, 0x12, 0xae, 0x42, 0xd0 \
+    }                                                \
+  }
+
+enum CertBlocklistItemMechanism {
+  BlockByIssuerAndSerial,
+  BlockBySubjectAndPubKey
+};
+
+enum CertBlocklistItemState { CertNewFromBlocklist, CertOldFromLocalCache };
+
+class CertBlocklistItem {
+ public:
+  CertBlocklistItem(const uint8_t* DNData, size_t DNLength,
+                    const uint8_t* otherData, size_t otherLength,
+                    CertBlocklistItemMechanism itemMechanism);
+  CertBlocklistItem(const CertBlocklistItem& aItem);
+  ~CertBlocklistItem();
+  nsresult ToBase64(nsACString& b64IssuerOut, nsACString& b64SerialOut);
+  bool operator==(const CertBlocklistItem& aItem) const;
+  uint32_t Hash() const;
+  bool mIsCurrent;
+  CertBlocklistItemMechanism mItemMechanism;
+
+ private:
+  size_t mDNLength;
+  uint8_t* mDNData;
+  size_t mOtherLength;
+  uint8_t* mOtherData;
+};
+
+typedef nsGenericHashKey<CertBlocklistItem> BlocklistItemKey;
+typedef nsTHashtable<BlocklistItemKey> BlocklistTable;
+typedef nsTHashtable<nsCStringHashKey> BlocklistStringSet;
+typedef nsClassHashtable<nsCStringHashKey, BlocklistStringSet> IssuerTable;
+
+class CertBlocklist : public nsICertBlocklist {
+ public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSICERTBLOCKLIST
+  CertBlocklist();
+  nsresult Init();
+
+ private:
+  BlocklistTable mBlocklist;
+  nsresult AddRevokedCertInternal(const nsACString& aEncodedDN,
+                                  const nsACString& aEncodedOther,
+                                  CertBlocklistItemMechanism aMechanism,
+                                  CertBlocklistItemState aItemState,
+                                  mozilla::MutexAutoLock& /*proofOfLock*/);
+  mozilla::Mutex mMutex;
+  bool mModified;
+  bool mBackingFileIsInitialized;
+  // call EnsureBackingFileInitialized before operations that read or
+  // modify CertBlocklist data
+  nsresult EnsureBackingFileInitialized(mozilla::MutexAutoLock& lock);
+  nsCOMPtr<nsIFile> mBackingFile;
+
+ protected:
+  static void PreferenceChanged(const char* aPref, CertBlocklist* aBlocklist);
+  static uint32_t sLastBlocklistUpdate;
+  static uint32_t sLastKintoUpdate;
+  static uint32_t sMaxStaleness;
+  static bool sUseAMO;
+  virtual ~CertBlocklist();
+};
+
+#endif  // CertBlocklist_h
--- a/security/manager/ssl/DataStorage.cpp
+++ b/security/manager/ssl/DataStorage.cpp
@@ -13,17 +13,19 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
-#include "nsIFileStreams.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsIFileStreams.h"
+#endif
 #include "nsIMemoryReporter.h"
 #include "nsIObserverService.h"
 #include "nsITimer.h"
 #include "nsIThread.h"
 #include "nsNetUtil.h"
 #include "nsPrintfCString.h"
 #include "nsStreamUtils.h"
 #include "nsThreadUtils.h"
--- a/security/manager/ssl/NSSErrorsService.h
+++ b/security/manager/ssl/NSSErrorsService.h
@@ -3,18 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef NSSErrorsService_h
 #define NSSErrorsService_h
 
 #include "nsINSSErrorsService.h"
 #include "mozilla/Attributes.h"
 #include "nsCOMPtr.h"
-#include "nsILineInputStream.h"
-#include "nsISafeOutputStream.h"
+#ifdef MOZ_NEW_CERT_STORAGE
+#  include "nsILineInputStream.h"
+#  include "nsISafeOutputStream.h"
+#endif
 #include "nsIStringBundle.h"
 #include "prerror.h"
 
 namespace mozilla {
 namespace psm {
 
 class NSSErrorsService final : public nsINSSErrorsService {
   NS_DECL_ISUPPORTS
--- a/security/manager/ssl/components.conf
+++ b/security/manager/ssl/components.conf
@@ -152,22 +152,16 @@ Classes = [
     },
     {
         'cid': '{16955eee-6c48-4152-9309-c42a465138a1}',
         'contract_ids': ['@mozilla.org/ssservice;1'],
         'type': 'nsSiteSecurityService',
         'legacy_constructor': 'mozilla::psm::NSSConstructor<nsSiteSecurityService>',
     },
     {
-        'cid': '{16e5c837-f877-4e23-9c64-eddf905e30e6}',
-        'contract_ids': ['@mozilla.org/security/certstorage;1'],
-        'headers': ['/security/manager/ssl/cert_storage/src/cert_storage.h'],
-        'legacy_constructor': 'construct_cert_storage',
-    },
-    {
         'cid': '{57972956-5718-42d2-8070-b3fc72212eaf}',
         'contract_ids': ['@mozilla.org/security/oskeystore;1'],
         'type': 'OSKeyStore',
         'legacy_constructor': 'mozilla::psm::NSSConstructor<OSKeyStore>',
     },
     {
         'cid': '{4fe082ae-6ff0-4b41-b24f-eaa664f6e46a}',
         'contract_ids': ['@mozilla.org/security/osreauthenticator;1'],
@@ -180,8 +174,27 @@ if defined('MOZ_XUL'):
     Classes += [
         {
             'cid': '{4ea60761-31d6-491d-9e34-4b53a26c416c}',
             'contract_ids': ['@mozilla.org/security/nsCertTree;1'],
             'type': 'nsCertTree',
             'legacy_constructor': 'mozilla::psm::NSSConstructor<nsCertTree>',
         },
     ]
+
+if defined('MOZ_NEW_CERT_STORAGE'):
+    Classes += [
+        {
+            'cid': '{16e5c837-f877-4e23-9c64-eddf905e30e6}',
+            'contract_ids': ['@mozilla.org/security/certstorage;1'],
+            'headers': ['/security/manager/ssl/cert_storage/src/cert_storage.h'],
+            'legacy_constructor': 'construct_cert_storage',
+        },
+    ]
+else:
+    Classes += [
+        {
+            'cid': '{11aefd53-2fbb-4c92-a0c1-053212ae42d0}',
+            'contract_ids': ['@mozilla.org/security/certblocklist;1'],
+            'type': 'CertBlocklist',
+            'legacy_constructor': 'mozilla::psm::NSSConstructor<CertBlocklist>',
+        },
+    ]
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -8,17 +8,16 @@ TEST_DIRS += [ 'tests' ]
 
 XPIDL_SOURCES += [
     'nsIASN1Object.idl',
     'nsIASN1PrintableItem.idl',
     'nsIASN1Sequence.idl',
     'nsIBadCertListener2.idl',
     'nsICertificateDialogs.idl',
     'nsICertOverrideService.idl',
-    'nsICertStorage.idl',
     'nsIClientAuthDialogs.idl',
     'nsIContentSignatureVerifier.idl',
     'nsICryptoHash.idl',
     'nsICryptoHMAC.idl',
     'nsIGenKeypairInfoDlg.idl',
     'nsIKeygenThread.idl',
     'nsIKeyModule.idl',
     'nsILocalCertService.idl',
@@ -89,17 +88,16 @@ EXPORTS.mozilla.psm += [
     'PSMContentListener.h',
 ]
 
 EXPORTS.ipc += [
     'DataStorageIPCUtils.h',
 ]
 
 UNIFIED_SOURCES += [
-    'cert_storage/src/cert_storage.cpp',
     'ContentSignatureVerifier.cpp',
     'CryptoTask.cpp',
     'CSTrustDomain.cpp',
     'DataStorage.cpp',
     'EnterpriseRoots.cpp',
     'LocalCertService.cpp',
     'nsCertOverrideService.cpp',
     'nsClientAuthRemember.cpp',
@@ -178,16 +176,31 @@ if CONFIG['MOZ_XUL']:
     UNIFIED_SOURCES += [
         'nsCertTree.cpp',
     ]
 
 UNIFIED_SOURCES += [
     'md4.c',
 ]
 
+if CONFIG['MOZ_NEW_CERT_STORAGE']:
+    XPIDL_SOURCES += [
+        'nsICertStorage.idl',
+    ]
+    UNIFIED_SOURCES += [
+        'cert_storage/src/cert_storage.cpp',
+    ]
+else:
+    XPIDL_SOURCES += [
+        'nsICertBlocklist.idl',
+    ]
+    UNIFIED_SOURCES += [
+        'CertBlocklist.cpp',
+    ]
+
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/crypto',
     '/security/certverifier',
 ]
 
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/nsICertBlocklist.idl
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIX509Cert;
+
+%{C++
+#define NS_CERTBLOCKLIST_CONTRACTID "@mozilla.org/security/certblocklist;1"
+%}
+
+/**
+ * Represents a service to add certificates as explicitly blocked/distrusted.
+ */
+[scriptable, uuid(e0654480-f433-11e4-b939-0800200c9a66)]
+interface nsICertBlocklist : nsISupports {
+  /**
+   * Add details of a revoked certificate :
+   * issuer name (base-64 encoded DER) and serial number (base-64 encoded DER).
+   */
+  [must_use]
+  void revokeCertByIssuerAndSerial(in ACString issuer,
+                                   in ACString serialNumber);
+
+  /**
+   * Add details of a revoked certificate :
+   * subject name (base-64 encoded DER) and hash of public key (base-64 encoded
+   * sha-256 hash of the public key).
+   */
+  [must_use]
+  void revokeCertBySubjectAndPubKey(in ACString subject,
+                                    in ACString pubKeyHash);
+
+  /**
+   * Persist (fresh) blocklist entries to the profile (if a profile directory is
+   * available). Note: calling this will result in synchronous I/O.
+   */
+  [must_use]
+  void saveEntries();
+
+  /**
+   * Check if a certificate is blocked.
+   * issuer - issuer name, DER, Base64 encoded
+   * serial - serial number, DER, BAse64 encoded
+   * subject - subject name, DER, Base64 encoded
+   * pubkey - public key, DER, Base64 encoded
+   */
+  [must_use]
+  boolean isCertRevoked(in ACString issuer,
+                        in ACString serial,
+                        in ACString subject,
+                        in ACString pubkey);
+
+   /**
+    * Check that the blocklist data is current. Specifically, that the current
+    * time is no more than security.onecrl.maximum_staleness_in_seconds seconds
+    * after the last blocklist update (as stored in the
+    * app.update.lastUpdateTime.blocklist-background-update-timer pref)
+    */
+  [must_use]
+  boolean isBlocklistFresh();
+};
--- a/security/manager/ssl/nsNSSModule.cpp
+++ b/security/manager/ssl/nsNSSModule.cpp
@@ -1,16 +1,19 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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/. */
 
 #include "nsNSSModule.h"
 
+#ifndef MOZ_NEW_CERT_STORAGE
+#  include "CertBlocklist.h"
+#endif
 #include "ContentSignatureVerifier.h"
 #include "NSSErrorsService.h"
 #include "OSKeyStore.h"
 #include "OSReauthenticator.h"
 #include "PKCS11ModuleDB.h"
 #include "PSMContentListener.h"
 #include "SecretDecoderRing.h"
 #include "TransportSecurityInfo.h"
@@ -142,16 +145,20 @@ IMPL(nsKeyObject, nullptr, ProcessRestri
 IMPL(nsKeyObjectFactory, nullptr, ProcessRestriction::AnyProcess)
 IMPL(ContentSignatureVerifier, nullptr)
 IMPL(nsCertOverrideService, &nsCertOverrideService::Init,
      ProcessRestriction::ParentProcessOnly, ThreadRestriction::MainThreadOnly)
 IMPL(nsRandomGenerator, nullptr, ProcessRestriction::AnyProcess)
 IMPL(TransportSecurityInfo, nullptr, ProcessRestriction::AnyProcess)
 IMPL(nsSiteSecurityService, &nsSiteSecurityService::Init,
      ProcessRestriction::AnyProcess, ThreadRestriction::MainThreadOnly)
+#ifndef MOZ_NEW_CERT_STORAGE
+IMPL(CertBlocklist, &CertBlocklist::Init, ProcessRestriction::ParentProcessOnly,
+     ThreadRestriction::MainThreadOnly)
+#endif
 IMPL(OSKeyStore, nullptr, ProcessRestriction::ParentProcessOnly,
      ThreadRestriction::MainThreadOnly)
 IMPL(OSReauthenticator, nullptr, ProcessRestriction::ParentProcessOnly,
      ThreadRestriction::MainThreadOnly)
 #undef IMPL
 
 }  // namespace psm
 }  // namespace mozilla
--- a/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
+++ b/security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // Repeatedly opens the certificate viewer dialog with various certificates and
 // determines that the viewer correctly identifies either what usages those
 // certificates are valid for or what errors prevented the certificates from
 // being verified.
 
+var { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 var { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 add_task(async function testCAandTitle() {
   let cert = await readCertificate("ca.pem", "CTu,CTu,CTu");
   let win = await displayCertificate(cert);
   checkUsages(win, [{id: "verify-ssl-ca"}]);
   checkDetailsPane(win, ["ca"]);
 
@@ -95,26 +96,34 @@ add_task(async function testUntrusted() 
   checkDetailsPane(eeWin, ["ee-from-untrusted-ca"]);
   await BrowserTestUtils.closeWindow(eeWin);
 });
 
 add_task(async function testRevoked() {
   // Note that there's currently no way to un-do this. This should only be a
   // problem if another test re-uses a certificate with this same key (perhaps
   // likely) and subject (less likely).
-  let certBlocklist = Cc["@mozilla.org/security/certstorage;1"]
-                        .getService(Ci.nsICertStorage);
-  let result = await new Promise((resolve) =>
-    certBlocklist.setRevocations([{
-      QueryInterface: ChromeUtils.generateQI([Ci.nsISubjectAndPubKeyRevocationState]),
-      subject: "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
-      pubKey: "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=", // hash of the shared key
-      state: Ci.nsICertStorage.STATE_ENFORCE, // yes, we want this to be revoked
-    }], resolve));
-  Assert.equal(result, Cr.NS_OK, "setting revocation state should succeed");
+  if (AppConstants.MOZ_NEW_CERT_STORAGE) {
+    let certBlocklist = Cc["@mozilla.org/security/certstorage;1"]
+                          .getService(Ci.nsICertStorage);
+    let result = await new Promise((resolve) =>
+      certBlocklist.setRevocations([{
+        QueryInterface: ChromeUtils.generateQI([Ci.nsISubjectAndPubKeyRevocationState]),
+        subject: "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
+        pubKey: "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=", // hash of the shared key
+        state: Ci.nsICertStorage.STATE_ENFORCE, // yes, we want this to be revoked
+      }], resolve));
+    Assert.equal(result, Cr.NS_OK, "setting revocation state should succeed");
+  } else {
+    let certBlocklist = Cc["@mozilla.org/security/certblocklist;1"]
+                          .getService(Ci.nsICertBlocklist);
+    certBlocklist.revokeCertBySubjectAndPubKey(
+      "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
+      "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="); // hash of the shared key
+  }
   let cert = await readCertificate("revoked.pem", ",,");
   let win = await displayCertificate(cert);
   // As of bug 1312827, OneCRL only applies to TLS web server certificates, so
   // this certificate will actually verify successfully for every end-entity
   // usage except TLS web server.
   checkUsages(win, [{id: "verify-email-recip"}, {id: "verify-email-signer"}, {id: "verify-ssl-client"}]);
   checkDetailsPane(win, ["ca", "revoked"]);
   await BrowserTestUtils.closeWindow(win);
--- a/security/manager/ssl/tests/unit/test_cert_storage.js
+++ b/security/manager/ssl/tests/unit/test_cert_storage.js
@@ -190,24 +190,100 @@ function fetch_blocklist() {
   Services.prefs.setCharPref("services.settings.server",
                              `http://localhost:${port}/v1`);
 
   BlocklistClients.initialize();
 
   return RemoteSettings.pollChanges();
 }
 
+function* generate_revocations_txt_lines() {
+  let profile = do_get_profile();
+  let revocations = profile.clone();
+  revocations.append("revocations.txt");
+  ok(revocations.exists(), "the revocations file should exist");
+  let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
+                      .createInstance(Ci.nsIFileInputStream);
+  inputStream.init(revocations, -1, -1, 0);
+  inputStream.QueryInterface(Ci.nsILineInputStream);
+  let hasmore = false;
+  do {
+    let line = {};
+    hasmore = inputStream.readLine(line);
+    yield line.value;
+  } while (hasmore);
+}
+
+// Check that revocations.txt contains, in any order, the lines
+// ("top-level lines") that are the keys in |expected|, each followed
+// immediately by the lines ("sublines") in expected[topLevelLine]
+// (again, in any order).
+function check_revocations_txt_contents(expected) {
+  let lineGenerator = generate_revocations_txt_lines();
+  let firstLine = lineGenerator.next();
+  equal(firstLine.done, false,
+        "first line of revocations.txt should be present");
+  equal(firstLine.value, "# Auto generated contents. Do not edit.",
+        "first line of revocations.txt");
+  let line = lineGenerator.next();
+  let topLevelFound = {};
+  while (true) {
+    if (line.done) {
+      break;
+    }
+
+    ok(line.value in expected,
+       `${line.value} should be an expected top-level line in revocations.txt`);
+    ok(!(line.value in topLevelFound),
+       `should not have seen ${line.value} before in revocations.txt`);
+    topLevelFound[line.value] = true;
+    let topLevelLine = line.value;
+
+    let sublines = expected[line.value];
+    let subFound = {};
+    while (true) {
+      line = lineGenerator.next();
+      if (line.done || !(line.value in sublines)) {
+        break;
+      }
+      ok(!(line.value in subFound),
+         `should not have seen ${line.value} before in revocations.txt`);
+      subFound[line.value] = true;
+    }
+    for (let subline in sublines) {
+      ok(subFound[subline],
+         `should have found ${subline} below ${topLevelLine} in revocations.txt`);
+    }
+  }
+  for (let topLevelLine in expected) {
+    ok(topLevelFound[topLevelLine],
+       `should have found ${topLevelLine} in revocations.txt`);
+  }
+}
+
 function run_test() {
   // import the certificates we need
   load_cert("test-ca", "CTu,CTu,CTu");
   load_cert("test-int", ",,");
   load_cert("other-test-ca", "CTu,CTu,CTu");
 
-  let certList = Cc["@mozilla.org/security/certstorage;1"]
-                  .getService(Ci.nsICertStorage);
+  let certList = AppConstants.MOZ_NEW_CERT_STORAGE ?
+    Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage) :
+    Cc["@mozilla.org/security/certblocklist;1"].getService(Ci.nsICertBlocklist);
+
+  let expected = { "MCIxIDAeBgNVBAMMF0Fub3RoZXIgVGVzdCBFbmQtZW50aXR5":
+                     { "\tVCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=": true },
+                   "MBgxFjAUBgNVBAMMDU90aGVyIHRlc3QgQ0E=":
+                     { " Rym6o+VN9xgZXT/QLrvN/nv1ZN4=": true},
+                   "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=":
+                     { " a0X7/7DlTaedpgrIJg25iBPOkIM=": true},
+                   "MBwxGjAYBgNVBAMMEVRlc3QgSW50ZXJtZWRpYXRl":
+                     { " Tg==": true,
+                       " Hw==": true },
+                 };
 
   add_task(async function() {
     // check some existing items in revocations.txt are blocked.
     // This test corresponds to:
     // issuer: MBIxEDAOBgNVBAMMB1Rlc3QgQ0E= (CN=Test CA)
     // serial: Kg== (42)
     let file = "test_onecrl/ee-revoked-by-revocations-txt.pem";
     await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
@@ -267,16 +343,22 @@ function run_test() {
     await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
     file = "test_onecrl/another-ee-revoked-by-revocations-txt-serial-2.pem";
     await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
 
     // The cert revoked by subject and pubkeyhash should still be revoked.
     file = "test_onecrl/ee-revoked-by-subject-and-pubkey.pem";
     await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
 
+    if (!AppConstants.MOZ_NEW_CERT_STORAGE) {
+      // Check the blocklist entry has been persisted properly to the backing
+      // file
+      check_revocations_txt_contents(expected);
+    }
+
     // Check the blocklisted intermediate now causes a failure
     file = "test_onecrl/test-int-ee.pem";
     await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
     await verify_non_tls_usage_succeeds(file);
 
     // Check the ee with the blocklisted root also causes a failure
     file = "bad_certs/other-issuer-ee.pem";
     await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
@@ -289,16 +371,38 @@ function run_test() {
 
     // Check a non-blocklisted chain still validates OK
     file = "bad_certs/default-ee.pem";
     await verify_cert(file, PRErrorCodeSuccess);
 
     // Check a bad cert is still bad (unknown issuer)
     file = "bad_certs/unknownissuer.pem";
     await verify_cert(file, SEC_ERROR_UNKNOWN_ISSUER);
+
+    if (!AppConstants.MOZ_NEW_CERT_STORAGE) {
+      // check that save with no further update is a no-op
+      let lastModified = gRevocations.lastModifiedTime;
+      // add an already existing entry
+      certList.revokeCertByIssuerAndSerial("MBwxGjAYBgNVBAMMEVRlc3QgSW50ZXJtZWRpYXRl",
+                                           "Hw==");
+      certList.saveEntries();
+      let newModified = gRevocations.lastModifiedTime;
+      equal(lastModified, newModified,
+            "saveEntries with no modifications should not update the backing file");
+    }
   });
 
-  add_task(async function() {
+  add_test({
+    skip_if: () => AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, function() {
+    // Check the blocklist entry has not changed
+    check_revocations_txt_contents(expected);
+    run_next_test();
+  });
+
+  add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function() {
     ok(certList.isBlocklistFresh(), "Blocklist should be fresh.");
   });
 
   run_next_test();
 }
--- a/security/manager/ssl/tests/unit/test_cert_storage_direct.js
+++ b/security/manager/ssl/tests/unit/test_cert_storage_direct.js
@@ -4,17 +4,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // This file consists of unit tests for cert_storage (whereas test_cert_storage.js is more of an
 // integration test).
 
 do_get_profile();
 
-var certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
+if (AppConstants.MOZ_NEW_CERT_STORAGE) {
+  this.certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
+}
 
 async function addCertBySubject(cert, subject) {
   let result = await new Promise((resolve) => {
     certStorage.addCertBySubject(btoa(cert), btoa(subject), Ci.nsICertStorage.TRUST_INHERIT,
                                  resolve);
   });
   Assert.equal(result, Cr.NS_OK, "addCertBySubject should succeed");
 }
@@ -41,17 +43,19 @@ function arrayToString(a) {
   }
   return s;
 }
 
 function getLongString(uniquePart, length) {
   return String(uniquePart).padStart(length, "0");
 }
 
-add_task(async function test_common_subject() {
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_common_subject() {
   await addCertBySubject("some certificate bytes 1", "some common subject");
   await addCertBySubject("some certificate bytes 2", "some common subject");
   await addCertBySubject("some certificate bytes 3", "some common subject");
   let storedCerts = certStorage.findCertsBySubject(stringToArray("some common subject"));
   let storedCertsAsStrings = storedCerts.map(arrayToString);
   let expectedCerts = ["some certificate bytes 1", "some certificate bytes 2",
                        "some certificate bytes 3"];
   Assert.deepEqual(storedCertsAsStrings.sort(), expectedCerts.sort(), "should find expected certs");
@@ -63,34 +67,38 @@ add_task(async function test_common_subj
                    "should still find expected certs");
 
   let storedOtherCerts = certStorage.findCertsBySubject(stringToArray("some other subject"));
   let storedOtherCertsAsStrings = storedOtherCerts.map(arrayToString);
   let expectedOtherCerts = ["some other certificate bytes"];
   Assert.deepEqual(storedOtherCertsAsStrings, expectedOtherCerts, "should have other certificate");
 });
 
-add_task(async function test_many_entries() {
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_many_entries() {
   const NUM_CERTS = 500;
   const CERT_LENGTH = 3000;
   const SUBJECT_LENGTH = 40;
   for (let i = 0; i < NUM_CERTS; i++) {
     await addCertBySubject(getLongString(i, CERT_LENGTH), getLongString(i, SUBJECT_LENGTH));
   }
   for (let i = 0; i < NUM_CERTS; i++) {
     let subject = stringToArray(getLongString(i, SUBJECT_LENGTH));
     let storedCerts = certStorage.findCertsBySubject(subject);
     Assert.equal(storedCerts.length, 1, "should have 1 certificate (lots of data test)");
     let storedCertAsString = arrayToString(storedCerts[0]);
     Assert.equal(storedCertAsString, getLongString(i, CERT_LENGTH),
                  "certificate should be as expected (lots of data test)");
   }
 });
 
-add_task(async function test_removal() {
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_removal() {
   // As long as cert_storage is given valid base64, attempting to delete some nonexistent
   // certificate will "succeed" (it'll do nothing).
   await removeCertByHash(btoa("thishashisthewrongsize"));
 
   await addCertBySubject("removal certificate bytes 1", "common subject to remove");
   await addCertBySubject("removal certificate bytes 2", "common subject to remove");
   await addCertBySubject("removal certificate bytes 3", "common subject to remove");
 
--- a/security/manager/ssl/tests/unit/test_cert_storage_prefs.js
+++ b/security/manager/ssl/tests/unit/test_cert_storage_prefs.js
@@ -2,16 +2,20 @@
 /* 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/. */
 "use strict";
 
 // Tests that cert_storage properly handles its preference values.
 
 function run_test() {
+  if (!AppConstants.MOZ_NEW_CERT_STORAGE) {
+    return;
+  }
+
   let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(Ci.nsICertStorage);
   // Since none of our prefs start with values, looking them up will fail. cert_storage should use
   // safe fallbacks.
   ok(!certStorage.isBlocklistFresh(), "checking blocklist freshness shouldn't crash");
   ok(!certStorage.isWhitelistFresh(), "checking whitelist freshness shouldn't crash");
   ok(!certStorage.isEnrollmentFresh(), "checking enrollment freshness shouldn't crash");
 
   // If we set nonsensical values, cert_storage should still use safe fallbacks.
--- a/security/manager/ssl/tests/unit/test_intermediate_preloads.js
+++ b/security/manager/ssl/tests/unit/test_intermediate_preloads.js
@@ -2,22 +2,26 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 // 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/.
 
 "use strict";
 do_get_profile(); // must be called before getting nsIX509CertDB
 
-const {RemoteSecuritySettings} = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm");
 const {RemoteSettings} = ChromeUtils.import("resource://services-settings/remote-settings.js");
 const {TestUtils} = ChromeUtils.import("resource://testing-common/TestUtils.jsm");
 const {TelemetryTestUtils} = ChromeUtils.import("resource://testing-common/TelemetryTestUtils.jsm");
 
-let remoteSecSetting = new RemoteSecuritySettings();
+let remoteSecSetting;
+if (AppConstants.MOZ_NEW_CERT_STORAGE) {
+  const {RemoteSecuritySettings} = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm");
+  remoteSecSetting = new RemoteSecuritySettings();
+}
+
 let server;
 
 let intermediate1Data;
 let intermediate2Data;
 
 const INTERMEDIATES_DL_PER_POLL_PREF     = "security.remote_settings.intermediates.downloads_per_poll";
 const INTERMEDIATES_ENABLED_PREF         = "security.remote_settings.intermediates.enabled";
 
@@ -208,17 +212,19 @@ function setupKintoPreloadServer(certGen
 
     response.setStatusLine(null, 404, `Identifier ${identifier} Not Found`);
     if (options.attachmentCB) {
       options.attachmentCB(identifier, false);
     }
   });
 }
 
-add_task(async function test_preload_empty() {
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_preload_empty() {
   Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
 
   let countDownloadAttempts = 0;
   setupKintoPreloadServer(
     cyclingIteratorGenerator([]),
     found => { countDownloadAttempts++; }
   );
 
@@ -235,31 +241,35 @@ add_task(async function test_preload_emp
 
   equal(countDownloadAttempts, 0, "There should have been no downloads");
 
   // check that ee cert 1 is unknown
   await checkCertErrorGeneric(certDB, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
                               certificateUsageSSLServer);
 });
 
-add_task(async function test_preload_disabled() {
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_preload_disabled() {
   Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, false);
 
   let countDownloadAttempts = 0;
   setupKintoPreloadServer(
     cyclingIteratorGenerator([intermediate1Data]),
     {attachmentCB: (identifier, attachmentFound) => { countDownloadAttempts++; }}
   );
 
   equal(await syncAndPromiseUpdate(), "disabled", "Preloading update should not have run");
 
   equal(countDownloadAttempts, 0, "There should have been no downloads");
 });
 
-add_task(async function test_preload_invalid_hash() {
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_preload_invalid_hash() {
   Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
   const invalidHash = "6e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d";
 
   let countDownloadAttempts = 0;
   setupKintoPreloadServer(
     cyclingIteratorGenerator([intermediate1Data]),
     {
       attachmentCB: (identifier, attachmentFound) => { countDownloadAttempts++; },
@@ -289,17 +299,19 @@ add_task(async function test_preload_inv
   let ee_cert = constructCertFromFile("test_intermediate_preloads/ee.pem");
   notEqual(ee_cert, null, "EE cert should have successfully loaded");
 
   // We should still have a missing intermediate.
   await checkCertErrorGeneric(certDB, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
                               certificateUsageSSLServer);
 });
 
-add_task(async function test_preload_invalid_length() {
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_preload_invalid_length() {
   Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
 
   let countDownloadAttempts = 0;
   setupKintoPreloadServer(
     cyclingIteratorGenerator([intermediate1Data]),
     {
       attachmentCB: (identifier, attachmentFound) => { countDownloadAttempts++; },
       lengthFunc: data => 42,
@@ -328,17 +340,19 @@ add_task(async function test_preload_inv
   let ee_cert = constructCertFromFile("test_intermediate_preloads/ee.pem");
   notEqual(ee_cert, null, "EE cert should have successfully loaded");
 
   // We should still have a missing intermediate.
   await checkCertErrorGeneric(certDB, ee_cert, SEC_ERROR_UNKNOWN_ISSUER,
                               certificateUsageSSLServer);
 });
 
-add_task(async function test_preload_basic() {
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_preload_basic() {
   Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
   Services.prefs.setIntPref(INTERMEDIATES_DL_PER_POLL_PREF, 100);
 
   let countDownloadAttempts = 0;
   setupKintoPreloadServer(
     cyclingIteratorGenerator([intermediate1Data, intermediate2Data]),
     {attachmentCB: (identifier, attachmentFound) => { countDownloadAttempts++; }}
   );
@@ -374,17 +388,19 @@ add_task(async function test_preload_bas
 
   // check that ee cert 2 does not verify - since we don't know the issuer of
   // this certificate
   await checkCertErrorGeneric(certDB, ee_cert_2, SEC_ERROR_UNKNOWN_ISSUER,
                               certificateUsageSSLServer);
 });
 
 
-add_task(async function test_preload_200() {
+add_task({
+    skip_if: () => !AppConstants.MOZ_NEW_CERT_STORAGE,
+  }, async function test_preload_200() {
   Services.prefs.setBoolPref(INTERMEDIATES_ENABLED_PREF, true);
   Services.prefs.setIntPref(INTERMEDIATES_DL_PER_POLL_PREF, 100);
 
   let countDownloadedAttachments = 0;
   let countMissingAttachments = 0;
   setupKintoPreloadServer(
     cyclingIteratorGenerator([intermediate1Data, intermediate2Data], 200),
     {
--- a/services/common/blocklist-clients.js
+++ b/services/common/blocklist-clients.js
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var EXPORTED_SYMBOLS = [
   "initialize",
 ];
 
+const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const { RemoteSecuritySettings } = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm");
 const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 ChromeUtils.defineModuleGetter(this, "RemoteSettings", "resource://services-settings/remote-settings.js");
 ChromeUtils.defineModuleGetter(this, "jexlFilterFunc", "resource://services-settings/remote-settings.js");
 
 const PREF_SECURITY_SETTINGS_ONECRL_BUCKET     = "services.settings.security.onecrl.bucket";
 const PREF_SECURITY_SETTINGS_ONECRL_COLLECTION = "services.settings.security.onecrl.collection";
 const PREF_SECURITY_SETTINGS_ONECRL_SIGNER     = "services.settings.security.onecrl.signer";
@@ -68,49 +68,70 @@ function setRevocations(certStorage, rev
   );
 }
 
 /**
  * Revoke the appropriate certificates based on the records from the blocklist.
  *
  * @param {Object} data   Current records in the local db.
  */
-async function updateCertBlocklist({ data: { created, updated, deleted } }) {
-  const certList = Cc["@mozilla.org/security/certstorage;1"]
-                     .getService(Ci.nsICertStorage);
-  let items = [];
+const updateCertBlocklist = AppConstants.MOZ_NEW_CERT_STORAGE ?
+  async function({ data: { created, updated, deleted } }) {
+    const certList = Cc["@mozilla.org/security/certstorage;1"]
+                       .getService(Ci.nsICertStorage);
+    let items = [];
 
-  for (let item of deleted) {
-    if (item.issuerName && item.serialNumber) {
-      items.push(new IssuerAndSerialRevocationState(item.issuerName,
-        item.serialNumber, Ci.nsICertStorage.STATE_UNSET));
-    } else if (item.subject && item.pubKeyHash) {
-      items.push(new SubjectAndPubKeyRevocationState(item.subject,
-        item.pubKeyHash, Ci.nsICertStorage.STATE_UNSET));
+    for (let item of deleted) {
+      if (item.issuerName && item.serialNumber) {
+        items.push(new IssuerAndSerialRevocationState(item.issuerName,
+          item.serialNumber, Ci.nsICertStorage.STATE_UNSET));
+      } else if (item.subject && item.pubKeyHash) {
+        items.push(new SubjectAndPubKeyRevocationState(item.subject,
+          item.pubKeyHash, Ci.nsICertStorage.STATE_UNSET));
+      }
     }
-  }
 
-  const toAdd = created.concat(updated.map(u => u.new));
+    const toAdd = created.concat(updated.map(u => u.new));
 
-  for (let item of toAdd) {
-    if (item.issuerName && item.serialNumber) {
-      items.push(new IssuerAndSerialRevocationState(item.issuerName,
-        item.serialNumber, Ci.nsICertStorage.STATE_ENFORCE));
-    } else if (item.subject && item.pubKeyHash) {
-      items.push(new SubjectAndPubKeyRevocationState(item.subject,
-        item.pubKeyHash, Ci.nsICertStorage.STATE_ENFORCE));
+    for (let item of toAdd) {
+      if (item.issuerName && item.serialNumber) {
+        items.push(new IssuerAndSerialRevocationState(item.issuerName,
+          item.serialNumber, Ci.nsICertStorage.STATE_ENFORCE));
+      } else if (item.subject && item.pubKeyHash) {
+        items.push(new SubjectAndPubKeyRevocationState(item.subject,
+          item.pubKeyHash, Ci.nsICertStorage.STATE_ENFORCE));
+      }
+    }
+
+    try {
+      await setRevocations(certList, items);
+    } catch (e) {
+      Cu.reportError(e);
     }
-  }
-
-  try {
-    await setRevocations(certList, items);
-  } catch (e) {
-    Cu.reportError(e);
-  }
-}
+  } : async function({ data: { current: records } }) {
+    const certList = Cc["@mozilla.org/security/certblocklist;1"]
+                       .getService(Ci.nsICertBlocklist);
+    for (let item of records) {
+      try {
+        if (item.issuerName && item.serialNumber) {
+          certList.revokeCertByIssuerAndSerial(item.issuerName,
+                                              item.serialNumber);
+        } else if (item.subject && item.pubKeyHash) {
+          certList.revokeCertBySubjectAndPubKey(item.subject,
+                                                item.pubKeyHash);
+        }
+      } catch (e) {
+        // prevent errors relating to individual blocklist entries from
+        // causing sync to fail. We will accumulate telemetry on these failures in
+        // bug 1254099.
+        Cu.reportError(e);
+      }
+    }
+    certList.saveEntries();
+  };
 
 /**
  * Modify the appropriate security pins based on records from the remote
  * collection.
  *
  * @param {Object} data   Current records in the local db.
  */
 async function updatePinningList({ data: { current: records } }) {
@@ -251,21 +272,33 @@ function initialize() {
 
   PinningBlocklistClient = RemoteSettings(Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_COLLECTION), {
     bucketNamePref: PREF_BLOCKLIST_PINNING_BUCKET,
     lastCheckTimePref: PREF_BLOCKLIST_PINNING_CHECKED_SECONDS,
     signerName: Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_SIGNER),
   });
   PinningBlocklistClient.on("sync", updatePinningList);
 
-  // In Bug 1526018 this will move into its own service, as it's not quite like
-  // the others.
-  RemoteSecuritySettingsClient = new RemoteSecuritySettings();
+  if (AppConstants.MOZ_NEW_CERT_STORAGE) {
+    const { RemoteSecuritySettings } = ChromeUtils.import("resource://gre/modules/psm/RemoteSecuritySettings.jsm");
+
+    // In Bug 1526018 this will move into its own service, as it's not quite like
+    // the others.
+    RemoteSecuritySettingsClient = new RemoteSecuritySettings();
+
+    return {
+      OneCRLBlocklistClient,
+      AddonBlocklistClient,
+      PluginBlocklistClient,
+      GfxBlocklistClient,
+      PinningBlocklistClient,
+      RemoteSecuritySettingsClient,
+    };
+  }
 
   return {
     OneCRLBlocklistClient,
     AddonBlocklistClient,
     PluginBlocklistClient,
     GfxBlocklistClient,
     PinningBlocklistClient,
-    RemoteSecuritySettingsClient,
   };
 }
--- a/toolkit/library/gtest/rust/Cargo.toml
+++ b/toolkit/library/gtest/rust/Cargo.toml
@@ -19,16 +19,17 @@ spidermonkey_rust = ["gkrust-shared/spid
 cranelift_x86 = ["gkrust-shared/cranelift_x86"]
 cranelift_arm32 = ["gkrust-shared/cranelift_arm32"]
 cranelift_arm64 = ["gkrust-shared/cranelift_arm64"]
 cranelift_none = ["gkrust-shared/cranelift_none"]
 gecko_profiler = ["gkrust-shared/gecko_profiler"]
 gecko_profiler_parse_elf = ["gkrust-shared/gecko_profiler_parse_elf"]
 bitsdownload = ["gkrust-shared/bitsdownload"]
 new_xulstore = ["gkrust-shared/new_xulstore"]
+new_cert_storage = ["gkrust-shared/new_cert_storage"]
 
 [dependencies]
 bench-collections-gtest = { path = "../../../../xpcom/rust/gtest/bench-collections" }
 mp4parse-gtest = { path = "../../../../dom/media/gtest" }
 nsstring-gtest = { path = "../../../../xpcom/rust/gtest/nsstring" }
 xpcom-gtest = { path = "../../../../xpcom/rust/gtest/xpcom" }
 gkrust-shared = { path = "../../rust/shared" }
 
--- a/toolkit/library/rust/Cargo.toml
+++ b/toolkit/library/rust/Cargo.toml
@@ -20,16 +20,17 @@ spidermonkey_rust = ["gkrust-shared/spid
 cranelift_x86 = ["gkrust-shared/cranelift_x86"]
 cranelift_arm32 = ["gkrust-shared/cranelift_arm32"]
 cranelift_arm64 = ["gkrust-shared/cranelift_arm64"]
 cranelift_none = ["gkrust-shared/cranelift_none"]
 gecko_profiler = ["gkrust-shared/gecko_profiler"]
 gecko_profiler_parse_elf = ["gkrust-shared/gecko_profiler_parse_elf"]
 bitsdownload = ["gkrust-shared/bitsdownload"]
 new_xulstore = ["gkrust-shared/new_xulstore"]
+new_cert_storage = ["gkrust-shared/new_cert_storage"]
 
 [dependencies]
 gkrust-shared = { path = "shared" }
 mozilla-central-workspace-hack = { path = "../../../build/workspace-hack" }
 
 [dev-dependencies]
 stylo_tests = { path = "../../../servo/ports/geckolib/tests/" }
 
--- a/toolkit/library/rust/gkrust-features.mozbuild
+++ b/toolkit/library/rust/gkrust-features.mozbuild
@@ -48,8 +48,11 @@ if CONFIG['MOZ_GECKO_PROFILER']:
 if CONFIG['MOZ_GECKO_PROFILER_PARSE_ELF']:
     gkrust_features += ['gecko_profiler_parse_elf']
 
 if CONFIG['MOZ_BITS_DOWNLOAD']:
     gkrust_features += ['bitsdownload']
 
 if CONFIG['MOZ_NEW_XULSTORE']:
     gkrust_features += ['new_xulstore']
+
+if CONFIG['MOZ_NEW_CERT_STORAGE']:
+    gkrust_features += ['new_cert_storage']
--- a/toolkit/library/rust/shared/Cargo.toml
+++ b/toolkit/library/rust/shared/Cargo.toml
@@ -29,17 +29,17 @@ gkrust_utils = { path = "../../../../xpc
 rsdparsa_capi = { path = "../../../../media/webrtc/signaling/src/sdp/rsdparsa_capi" }
 xulstore = { path = "../../../components/xulstore", optional = true }
 # We have these to enforce common feature sets for said crates.
 log = {version = "0.4", features = ["release_max_level_info"]}
 env_logger = {version = "0.5", default-features = false} # disable `regex` to reduce code size
 cose-c = { version = "0.1.5" }
 jsrust_shared = { path = "../../../../js/src/rust/shared", optional = true }
 arrayvec = "0.4"
-cert_storage = { path = "../../../../security/manager/ssl/cert_storage" }
+cert_storage = { path = "../../../../security/manager/ssl/cert_storage", optional = true }
 bitsdownload = { path = "../../../components/bitsdownload", optional = true }
 storage = { path = "../../../../storage/rust" }
 bookmark_sync = { path = "../../../components/places/bookmark_sync", optional = true }
 
 [build-dependencies]
 rustc_version = "0.2"
 
 [features]
@@ -57,16 +57,17 @@ moz_places = ["bookmark_sync"]
 spidermonkey_rust = ["jsrust_shared"]
 cranelift_x86 = ["jsrust_shared/cranelift_x86"]
 cranelift_arm32 = ["jsrust_shared/cranelift_arm32"]
 cranelift_arm64 = ["jsrust_shared/cranelift_arm64"]
 cranelift_none = ["jsrust_shared/cranelift_none"]
 gecko_profiler = ["profiler_helper"]
 gecko_profiler_parse_elf = ["profiler_helper/parse_elf"]
 new_xulstore = ["xulstore"]
+new_cert_storage = ["cert_storage"]
 
 [lib]
 path = "lib.rs"
 test = false
 doctest = false
 bench = false
 doc = false
 plugin = false
--- a/toolkit/library/rust/shared/lib.rs
+++ b/toolkit/library/rust/shared/lib.rs
@@ -26,16 +26,17 @@ extern crate encoding_glue;
 #[cfg(feature = "cubeb-remoting")]
 extern crate audioipc_client;
 #[cfg(feature = "cubeb-remoting")]
 extern crate audioipc_server;
 extern crate env_logger;
 extern crate u2fhid;
 extern crate gkrust_utils;
 extern crate log;
+#[cfg(feature = "new_cert_storage")]
 extern crate cert_storage;
 extern crate cosec;
 extern crate rsdparsa_capi;
 #[cfg(feature = "new_xulstore")]
 extern crate xulstore;
 #[cfg(feature = "spidermonkey_rust")]
 extern crate jsrust_shared;
 #[cfg(feature = "bitsdownload")]
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -359,9 +359,16 @@ this.AppConstants = Object.freeze({
 #endif
 
   MOZ_NEW_NOTIFICATION_STORE:
 #ifdef MOZ_NEW_NOTIFICATION_STORE
     true,
 #else
     false,
 #endif
+
+  MOZ_NEW_CERT_STORAGE:
+#ifdef MOZ_NEW_CERT_STORAGE
+    true,
+#else
+    false,
+#endif
 });
--- a/toolkit/moz.configure
+++ b/toolkit/moz.configure
@@ -1745,8 +1745,20 @@ set_define('MOZ_NEW_XULSTORE', True, whe
 
 @depends(milestone)
 def new_notification_store(milestone):
     if milestone.is_nightly:
         return True
 
 set_config('MOZ_NEW_NOTIFICATION_STORE', True, when=new_notification_store)
 set_define('MOZ_NEW_NOTIFICATION_STORE', True, when=new_notification_store)
+
+
+# new Cert Storage implementation
+# ==============================================================
+
+@depends(milestone)
+def new_cert_storage(milestone):
+    if milestone.is_nightly:
+        return True
+
+set_config('MOZ_NEW_CERT_STORAGE', True, when=new_cert_storage)
+set_define('MOZ_NEW_CERT_STORAGE', True, when=new_cert_storage)