Backed out 2 changesets (bug 1429796) for failing xperf on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Wed, 20 Mar 2019 00:03:49 +0200
changeset 465128 91403c24fee3d5df8fa978710785807b409d7af8
parent 465127 b7e589bb49e3b7719d6ebe1c109cb43d7ad7c48e
child 465135 26fafa29a3eb557f9e41ecd9e6821052475055ce
push id80915
push userapavel@mozilla.com
push dateTue, 19 Mar 2019 22:04:18 +0000
treeherderautoland@91403c24fee3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1429796
milestone68.0a1
backs outb0d08863f7a5fc08a3c0709b5e7151d80ae18261
1bd54f8dfd9e877154a8a22992008ef74910fe37
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
Backed out 2 changesets (bug 1429796) for failing xperf on a CLOSED TREE Backed out changeset b0d08863f7a5 (bug 1429796) Backed out changeset 1bd54f8dfd9e (bug 1429796)
Cargo.lock
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/ContentSignatureVerifier.h
security/manager/ssl/DataStorage.cpp
security/manager/ssl/NSSErrorsService.h
security/manager/ssl/cert_storage/Cargo.toml
security/manager/ssl/cert_storage/src/cert_storage.cpp
security/manager/ssl/cert_storage/src/cert_storage.h
security/manager/ssl/cert_storage/src/lib.rs
security/manager/ssl/components.conf
security/manager/ssl/moz.build
security/manager/ssl/nsICertBlocklist.idl
security/manager/ssl/nsICertStorage.idl
security/manager/ssl/nsNSSModule.cpp
security/manager/ssl/tests/mochitest/browser/browser_certViewer.js
security/manager/ssl/tests/unit/test_cert_blocklist.js
security/manager/ssl/tests/unit/test_cert_storage.js
security/manager/ssl/tests/unit/xpcshell.ini
services/common/blocklist-clients.js
toolkit/library/rust/shared/Cargo.toml
toolkit/library/rust/shared/lib.rs
xpcom/build/Services.py
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -372,30 +372,16 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "cc"
 version = "1.0.23"
 source = "git+https://github.com/glandium/cc-rs?branch=1.0.23-clang-cl-aarch64#2aa71628b1261b5515bd8668afca591669ba195d"
 
 [[package]]
-name = "cert_storage"
-version = "0.0.1"
-dependencies = [
- "base64 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "nserror 0.1.0",
- "nsstring 0.1.0",
- "rkv 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "style 0.0.1",
- "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
- "xpcom 0.1.0",
-]
-
-[[package]]
 name = "cexpr"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1136,17 +1122,16 @@ dependencies = [
 
 [[package]]
 name = "gkrust-shared"
 version = "0.1.0"
 dependencies = [
  "arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "audioipc-client 0.4.0",
  "audioipc-server 0.2.3",
- "cert_storage 0.0.1",
  "cose-c 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "cubeb-pulse 0.2.0",
  "cubeb-sys 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding_c 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding_glue 0.1.0",
  "env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "geckoservo 0.0.1",
  "gkrust_utils 0.1.0",
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -75,17 +75,17 @@
 #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"
+#include "nsICertBlocklist.h"
 #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"
@@ -2567,17 +2567,17 @@ 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);
-  nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTSTORAGE_CONTRACTID);
+  nsCOMPtr<nsISupports> cbl = do_GetService(NS_CERTBLOCKLIST_CONTRACTID);
   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,16 @@
 #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"
 #include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "nsCRTGlue.h"
 #include "nsNSSCertHelper.h"
@@ -81,17 +80,17 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
       mDistrustedCAPolicy(distrustedCAPolicy),
       mSawDistrustedCAByPolicyError(false),
       mOriginAttributes(originAttributes),
       mThirdPartyRootInputs(thirdPartyRootInputs),
       mThirdPartyIntermediateInputs(thirdPartyIntermediateInputs),
       mBuiltChain(builtChain),
       mPinningTelemetryInfo(pinningTelemetryInfo),
       mHostname(hostname),
-      mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)),
+      mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)),
       mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED),
       mSCTListFromCertificate(),
       mSCTListFromOCSPStapling() {}
 
 Result NSSCertDBTrustDomain::FindIssuer(Input encodedIssuerName,
                                         IssuerChecker& checker, Time) {
   Vector<Input> rootCandidates;
   Vector<Input> intermediateCandidates;
@@ -191,37 +190,37 @@ Result NSSCertDBTrustDomain::GetCertTrus
   // Check the certificate against the OneCRL cert blocklist
   if (!mCertBlocklist) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
   // The certificate blocklist currently only applies to TLS server
   // certificates.
   if (mCertDBTrustType == trustSSL) {
-    int16_t revocationState;
+    bool isCertRevoked;
 
     nsAutoCString encIssuer;
     nsAutoCString encSerial;
     nsAutoCString encSubject;
     nsAutoCString encPubKey;
 
     nsresult nsrv = BuildRevocationCheckStrings(
         candidateCert.get(), encIssuer, encSerial, encSubject, encPubKey);
 
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
 
-    nsrv = mCertBlocklist->GetRevocationState(encIssuer, encSerial, encSubject,
-                                              encPubKey, &revocationState);
+    nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject,
+                                         encPubKey, &isCertRevoked);
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
 
-    if (revocationState == nsICertStorage::STATE_ENFORCE) {
+    if (isCertRevoked) {
       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) {
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -6,17 +6,17 @@
 
 #ifndef NSSCertDBTrustDomain_h
 #define NSSCertDBTrustDomain_h
 
 #include "CertVerifier.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/TimeStamp.h"
-#include "nsICertStorage.h"
+#include "nsICertBlocklist.h"
 #include "nsString.h"
 #include "mozpkix/pkixtypes.h"
 #include "secmodt.h"
 
 namespace mozilla {
 namespace psm {
 
 enum class ValidityCheckingMode {
@@ -218,17 +218,17 @@ 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
-  nsCOMPtr<nsICertStorage> mCertBlocklist;
+  nsCOMPtr<nsICertBlocklist> mCertBlocklist;
   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,17 @@
 /* -*- 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"
 #include "CSTrustDomain.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Preferences.h"
-#include "nsDirectoryServiceUtils.h"
 #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 +19,17 @@ 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),
-      mCertBlocklist(do_GetService(NS_CERT_STORAGE_CID)) {}
+      mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID)) {}
 
 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;
@@ -50,24 +48,24 @@ Result CSTrustDomain::GetCertTrust(EndEn
   nsAutoCString encPubKey;
 
   nsresult nsrv = BuildRevocationCheckStrings(candidateCert.get(), encIssuer,
                                               encSerial, encSubject, encPubKey);
   if (NS_FAILED(nsrv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
-  int16_t revocationState;
-  nsrv = mCertBlocklist->GetRevocationState(encIssuer, encSerial, encSubject,
-                                            encPubKey, &revocationState);
+  bool isCertRevoked;
+  nsrv = mCertBlocklist->IsCertRevoked(encIssuer, encSerial, encSubject,
+                                       encPubKey, &isCertRevoked);
   if (NS_FAILED(nsrv)) {
     return Result::FATAL_ERROR_LIBRARY_FAILURE;
   }
 
-  if (revocationState == nsICertStorage::STATE_ENFORCE) {
+  if (isCertRevoked) {
     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,17 @@
 
 #ifndef CSTrustDomain_h
 #define CSTrustDomain_h
 
 #include "mozpkix/pkixtypes.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/UniquePtr.h"
 #include "nsDebug.h"
-#include "nsICertStorage.h"
+#include "nsICertBlocklist.h"
 #include "nsIX509CertDB.h"
 #include "ScopedNSSTypes.h"
 
 namespace mozilla {
 namespace psm {
 
 class CSTrustDomain final : public mozilla::pkix::TrustDomain {
  public:
@@ -68,15 +68,15 @@ 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;
-  nsCOMPtr<nsICertStorage> mCertBlocklist;
+  nsCOMPtr<nsICertBlocklist> mCertBlocklist;
 };
 
 }  // namespace psm
 }  // namespace mozilla
 
 #endif  // CSTrustDomain_h
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/CertBlocklist.cpp
@@ -0,0 +1,626 @@
+/* -*- 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.blocklist.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/ContentSignatureVerifier.h
+++ b/security/manager/ssl/ContentSignatureVerifier.h
@@ -4,20 +4,18 @@
  * 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 ContentSignatureVerifier_h
 #define ContentSignatureVerifier_h
 
 #include "cert.h"
 #include "CSTrustDomain.h"
-#include "nsDirectoryServiceUtils.h"
 #include "nsIContentSignatureVerifier.h"
 #include "nsIStreamListener.h"
-#include "nsNetUtil.h"
 #include "nsString.h"
 #include "ScopedNSSTypes.h"
 
 // 45a5fe2f-c350-4b86-962d-02d5aaaa955a
 #define NS_CONTENTSIGNATUREVERIFIER_CID              \
   {                                                  \
     0x45a5fe2f, 0xc350, 0x4b86, {                    \
       0x96, 0x2d, 0x02, 0xd5, 0xaa, 0xaa, 0x95, 0x5a \
--- a/security/manager/ssl/DataStorage.cpp
+++ b/security/manager/ssl/DataStorage.cpp
@@ -13,17 +13,16 @@
 #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"
 #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,16 @@
  * 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"
 #include "nsIStringBundle.h"
 #include "prerror.h"
 
 namespace mozilla {
 namespace psm {
 
 class NSSErrorsService final : public nsINSSErrorsService {
   NS_DECL_ISUPPORTS
deleted file mode 100644
--- a/security/manager/ssl/cert_storage/Cargo.toml
+++ /dev/null
@@ -1,14 +0,0 @@
-[package]
-name = "cert_storage"
-version = "0.0.1"
-authors = ["Dana Keeler <dkeeler@mozilla.com>", "Mark Goodwin <mgoodwin@mozilla.com"]
-
-[dependencies]
-base64 = "0.10"
-nserror = { path = "../../../../xpcom/rust/nserror" }
-nsstring = { path = "../../../../xpcom/rust/nsstring" }
-rkv = "0.9.2"
-sha2 = "^0.7"
-style = { path = "../../../../servo/components/style" }
-time = "0.1"
-xpcom = { path = "../../../../xpcom/rust/xpcom" }
\ No newline at end of file
deleted file mode 100644
--- a/security/manager/ssl/cert_storage/src/cert_storage.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-/* -*- 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 "cert_storage.h"
-
-nsresult construct_cert_storage(nsISupports* outer, REFNSIID iid,
-                                void** result) {
-  // Forward to the main thread synchronously.
-  nsCOMPtr<nsIThread> mainThread;
-  nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  mozilla::SyncRunnable::DispatchToThread(
-      mainThread, new mozilla::SyncRunnable(
-                      NS_NewRunnableFunction("psm::Constructor", [&]() {
-                        rv = cert_storage_constructor(outer, iid, result);
-                      })));
-  return rv;
-}
\ No newline at end of file
deleted file mode 100644
--- a/security/manager/ssl/cert_storage/src/cert_storage.h
+++ /dev/null
@@ -1,29 +0,0 @@
-/* -*- 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 _cert_storage_h_
-#define _cert_storage_h_
-
-#include "nsISupportsUtils.h"  // for nsresult, etc.
-#include "mozilla/SyncRunnable.h"
-
-// {16e5c837-f877-4e23-9c64-eddf905e30e6}
-#define NS_CERT_STORAGE_CID                          \
-  {                                                  \
-    0x16e5c837, 0xf877, 0x4e23, {                    \
-      0x9c, 0x64, 0xed, 0xdf, 0x90, 0x5e, 0x30, 0xe6 \
-    }                                                \
-  }
-
-extern "C" {
-nsresult cert_storage_constructor(nsISupports* outer, REFNSIID iid,
-                                  void** result);
-};
-
-nsresult construct_cert_storage(nsISupports* outer, REFNSIID iid,
-                                void** result);
-
-#endif  // _cert_storage_h_
\ No newline at end of file
deleted file mode 100644
--- a/security/manager/ssl/cert_storage/src/lib.rs
+++ /dev/null
@@ -1,732 +0,0 @@
-/* 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/. */
-
-extern crate base64;
-extern crate nserror;
-extern crate nsstring;
-extern crate rkv;
-extern crate sha2;
-extern crate time;
-#[macro_use]
-extern crate xpcom;
-extern crate style;
-
-use nsstring::{nsACString, nsAString, nsCStr, nsCString, nsString};
-use sha2::{Digest, Sha256};
-use std::collections::HashMap;
-use std::ffi::{CStr, CString};
-use std::fmt::Display;
-use std::fs::{create_dir_all, remove_file, File};
-use std::io::{BufRead, BufReader};
-use std::os::raw::c_char;
-use std::path::PathBuf;
-use std::slice;
-use std::str;
-use std::sync::RwLock;
-use std::time::{Duration, SystemTime};
-use style::gecko_bindings::structs::nsresult;
-use xpcom::interfaces::{nsICertStorage, nsIFile, nsIObserver, nsIPrefBranch, nsISupports};
-use xpcom::{nsIID, GetterAddrefs, RefPtr, XpCom};
-
-use rkv::{Rkv, SingleStore, StoreOptions, Value};
-
-const PREFIX_REV_IS: &str = "is";
-const PREFIX_REV_SPK: &str = "spk";
-const PREFIX_CRLITE: &str = "crlite";
-const PREFIX_WL: &str = "wl";
-
-fn make_key(prefix: &str, part_a: &[u8], part_b: &[u8]) -> Vec<u8> {
-    let mut key = prefix.as_bytes().to_owned();
-    key.extend_from_slice(part_a);
-    key.extend_from_slice(part_b);
-    key
-}
-
-#[allow(non_camel_case_types, non_snake_case)]
-
-/// `SecurityStateError` is a type to represent errors in accessing or
-/// modifying security state.
-#[derive(Debug)]
-pub struct SecurityStateError {
-    message: String,
-}
-
-impl<T: Display> From<T> for SecurityStateError {
-    /// Creates a new instance of `SecurityStateError` from something that
-    /// implements the `Display` trait.
-    fn from(err: T) -> SecurityStateError {
-        SecurityStateError {
-            message: format!("{}", err),
-        }
-    }
-}
-
-/// `SecurityState`
-pub struct SecurityState {
-    env: Rkv,
-    store: SingleStore,
-    int_prefs: HashMap<String, i32>,
-}
-
-impl SecurityState {
-    pub fn new(profile_path: PathBuf) -> Result<SecurityState, SecurityStateError> {
-        let mut store_path = profile_path.clone();
-        store_path.push("security_state");
-
-        create_dir_all(store_path.as_path())?;
-        let env = Rkv::new(store_path.as_path())?;
-        let mut options = StoreOptions::create();
-        options.create = true;
-        let store = env.open_single("cert_storage", options)?;
-        let mut ss = SecurityState {
-            env: env,
-            store: store,
-            int_prefs: HashMap::new(),
-        };
-
-        let mut revocations_path = profile_path;
-        revocations_path.push("revocations.txt");
-
-        // if the profile has a revocations.txt, migrate it and remove the file
-        if revocations_path.exists() {
-            ss.migrate(&revocations_path)?;
-            remove_file(revocations_path)?;
-        }
-        Ok(ss)
-    }
-
-    fn migrate(&mut self, revocations_path: &PathBuf) -> Result<(), SecurityStateError> {
-        let f = File::open(revocations_path)?;
-        let file = BufReader::new(f);
-
-        // Add the data from revocations.txt
-        let mut dn: Option<Vec<u8>> = None;
-        for line in file.lines() {
-            let l = match line.map_err(|_| SecurityStateError::from("io error reading line data")) {
-                Ok(data) => data,
-                Err(e) => return Err(e),
-            };
-            if l.len() == 0 || l.starts_with("#") {
-                continue;
-            }
-            let leading_char = match l.chars().next() {
-                Some(c) => c,
-                None => {
-                    return Err(SecurityStateError::from(
-                        "couldn't get char from non-empty str?",
-                    ));
-                }
-            };
-            // In future, we can maybe log migration failures. For now, ignore decoding and storage
-            // errors and attempt to continue.
-            // Check if we have a new DN
-            if leading_char != '\t' && leading_char != ' ' {
-                if let Ok(decoded_dn) = base64::decode(&l) {
-                    dn = Some(decoded_dn);
-                }
-                continue;
-            }
-            let l_sans_prefix = match base64::decode(&l[1..]) {
-                Ok(decoded) => decoded,
-                Err(_) => continue,
-            };
-            if let Some(name) = &dn {
-                if leading_char == '\t' {
-                    let _ = self.set_revocation_by_subject_and_pub_key(
-                        name,
-                        &l_sans_prefix,
-                        nsICertStorage::STATE_ENFORCE as i16,
-                    );
-                } else {
-                    let _ = self.set_revocation_by_issuer_and_serial(
-                        name,
-                        &l_sans_prefix,
-                        nsICertStorage::STATE_ENFORCE as i16,
-                    );
-                }
-            }
-        }
-
-        Ok(())
-    }
-
-    fn write_entry(&mut self, key: &[u8], value: i16) -> Result<(), SecurityStateError> {
-        let mut writer = self.env.write()?;
-        self.store
-            .put(&mut writer, key, &Value::I64(value as i64))?;
-        writer.commit()?;
-        Ok(())
-    }
-
-    fn read_entry(&self, key: &[u8]) -> Result<Option<i16>, SecurityStateError> {
-        let reader = self.env.read()?;
-        match self.store.get(&reader, key) {
-            Ok(Some(Value::I64(i)))
-                if i <= (std::i16::MAX as i64) && i >= (std::i16::MIN as i64) =>
-            {
-                Ok(Some(i as i16))
-            }
-            Ok(None) => Ok(None),
-            _ => Err(SecurityStateError::from(
-                "There was a problem getting the value",
-            )),
-        }
-    }
-
-    pub fn set_revocation_by_issuer_and_serial(
-        &mut self,
-        issuer: &[u8],
-        serial: &[u8],
-        state: i16,
-    ) -> Result<(), SecurityStateError> {
-        self.write_entry(&make_key(PREFIX_REV_IS, issuer, serial), state)
-    }
-
-    pub fn set_revocation_by_subject_and_pub_key(
-        &mut self,
-        subject: &[u8],
-        pub_key_hash: &[u8],
-        state: i16,
-    ) -> Result<(), SecurityStateError> {
-        self.write_entry(&make_key(PREFIX_REV_SPK, subject, pub_key_hash), state)
-    }
-
-    pub fn set_enrollment(
-        &mut self,
-        issuer: &[u8],
-        serial: &[u8],
-        state: i16,
-    ) -> Result<(), SecurityStateError> {
-        self.write_entry(&make_key(PREFIX_CRLITE, issuer, serial), state)
-    }
-
-    pub fn set_whitelist(
-        &mut self,
-        issuer: &[u8],
-        serial: &[u8],
-        state: i16,
-    ) -> Result<(), SecurityStateError> {
-        self.write_entry(&make_key(PREFIX_WL, issuer, serial), state)
-    }
-
-    pub fn get_revocation_state(
-        &self,
-        issuer: &[u8],
-        serial: &[u8],
-        subject: &[u8],
-        pub_key: &[u8],
-    ) -> Result<i16, SecurityStateError> {
-        let mut digest = Sha256::default();
-        digest.input(pub_key);
-        let pub_key_hash = digest.result();
-
-        let subject_pubkey = make_key(PREFIX_REV_SPK, subject, &pub_key_hash);
-        let issuer_serial = make_key(PREFIX_REV_IS, issuer, serial);
-
-        let st: i16 = match self.read_entry(&issuer_serial) {
-            Ok(Some(value)) => value,
-            Ok(None) => nsICertStorage::STATE_UNSET as i16,
-            Err(_) => {
-                return Err(SecurityStateError::from(
-                    "problem reading revocation state (from issuer / serial)",
-                ));
-            }
-        };
-
-        if st != nsICertStorage::STATE_UNSET as i16 {
-            return Ok(st);
-        }
-
-        match self.read_entry(&subject_pubkey) {
-            Ok(Some(value)) => Ok(value),
-            Ok(None) => Ok(nsICertStorage::STATE_UNSET as i16),
-            Err(_) => {
-                return Err(SecurityStateError::from(
-                    "problem reading revocation state (from subject / pubkey)",
-                ));
-            }
-        }
-    }
-
-    pub fn get_enrollment_state(
-        &self,
-        issuer: &[u8],
-        serial: &[u8],
-    ) -> Result<i16, SecurityStateError> {
-        let issuer_serial = make_key(PREFIX_CRLITE, issuer, serial);
-        match self.read_entry(&issuer_serial) {
-            Ok(Some(value)) => Ok(value),
-            Ok(None) => Ok(nsICertStorage::STATE_UNSET as i16),
-            Err(_) => return Err(SecurityStateError::from("problem reading enrollment state")),
-        }
-    }
-
-    pub fn get_whitelist_state(
-        &self,
-        issuer: &[u8],
-        serial: &[u8],
-    ) -> Result<i16, SecurityStateError> {
-        let issuer_serial = make_key(PREFIX_WL, issuer, serial);
-        match self.read_entry(&issuer_serial) {
-            Ok(Some(value)) => Ok(value),
-            Ok(None) => Ok(nsICertStorage::STATE_UNSET as i16),
-            Err(_) => Err(SecurityStateError::from("problem reading whitelist state")),
-        }
-    }
-
-    pub fn is_data_fresh(
-        &self,
-        update_pref: &str,
-        allowed_staleness: &str,
-    ) -> Result<bool, SecurityStateError> {
-        let checked = match self.int_prefs.get(update_pref) {
-            Some(ch) => *ch,
-            None => 0,
-        };
-        let staleness_seconds = match self.int_prefs.get(allowed_staleness) {
-            Some(st) => *st,
-            None => 0,
-        };
-
-        let update = SystemTime::UNIX_EPOCH + Duration::new(checked as u64, 0);
-        let staleness = Duration::new(staleness_seconds as u64, 0);
-
-        Ok(match SystemTime::now().duration_since(update) {
-            Ok(duration) => duration <= staleness,
-            Err(_) => false,
-        })
-    }
-
-    pub fn is_blocklist_fresh(&self) -> Result<bool, SecurityStateError> {
-        self.is_data_fresh(
-            "services.blocklist.onecrl.checked",
-            "security.onecrl.maximum_staleness_in_seconds",
-        )
-    }
-
-    pub fn is_whitelist_fresh(&self) -> Result<bool, SecurityStateError> {
-        self.is_data_fresh(
-            "services.blocklist.intermediates.checked",
-            "security.onecrl.maximum_staleness_in_seconds",
-        )
-    }
-
-    pub fn is_enrollment_fresh(&self) -> Result<bool, SecurityStateError> {
-        self.is_data_fresh(
-            "services.blocklist.crlite.checked",
-            "security.onecrl.maximum_staleness_in_seconds",
-        )
-    }
-
-    pub fn pref_seen(&mut self, name: &str, value: i32) {
-        self.int_prefs.insert(name.to_owned(), value);
-    }
-}
-
-fn get_path_from_directory_service(key: &str) -> Result<PathBuf, SecurityStateError> {
-    let directory_service = match xpcom::services::get_DirectoryService() {
-        Some(ds) => ds,
-        _ => return Err(SecurityStateError::from("None")),
-    };
-
-    let cs_key = CString::new(key)?;
-    let mut requested_dir = GetterAddrefs::<nsIFile>::new();
-
-    unsafe {
-        (*directory_service)
-            .Get(
-                (&cs_key).as_ptr(),
-                &nsIFile::IID as *const nsIID,
-                requested_dir.void_ptr(),
-            )
-            .to_result()
-            .map_err(|res| SecurityStateError {
-                message: (*res.error_name()).as_str_unchecked().to_owned(),
-            })
-    }?;
-
-    let dir_path = match requested_dir.refptr() {
-        None => return Err(SecurityStateError::from("directory service failure")),
-        Some(refptr) => refptr,
-    };
-
-    let mut path = nsString::new();
-
-    unsafe {
-        (*dir_path)
-            .GetPath(&mut path as &mut nsAString)
-            // For reasons that aren't clear to me, NsresultExt does not
-            // implement std::error::Error (or Debug / Display). This map_err
-            // hack is a way to get an error with a useful message.
-            .to_result()
-            .map_err(|res| SecurityStateError {
-                message: (*res.error_name()).as_str_unchecked().to_owned(),
-            })?;
-    }
-
-    Ok(PathBuf::from(format!("{}", path)))
-}
-
-fn do_construct_cert_storage(
-    _outer: *const nsISupports,
-    iid: *const xpcom::nsIID,
-    result: *mut *mut xpcom::reexports::libc::c_void,
-) -> Result<(), SecurityStateError> {
-    let path_buf = match get_path_from_directory_service("ProfD") {
-        Ok(path) => path,
-        Err(_) => match get_path_from_directory_service("TmpD") {
-            Ok(path) => path,
-            Err(e) => return Err(e),
-        },
-    };
-
-    let cert_storage = CertStorage::allocate(InitCertStorage {
-        security_state: RwLock::new(SecurityState::new(path_buf)?),
-    });
-
-    unsafe {
-        cert_storage
-            .QueryInterface(iid, result)
-            // As above; greasy hack because NsresultExt
-            .to_result()
-            .map_err(|res| SecurityStateError {
-                message: (*res.error_name()).as_str_unchecked().to_owned(),
-            })?;
-
-        return cert_storage.setup_prefs();
-    };
-}
-
-fn read_int_pref(name: &str) -> Result<i32, SecurityStateError> {
-    let pref_service = match xpcom::services::get_PreferencesService() {
-        Some(ps) => ps,
-        _ => {
-            return Err(SecurityStateError::from(
-                "could not get preferences service",
-            ));
-        }
-    };
-
-    let prefs: RefPtr<nsIPrefBranch> = match (*pref_service).query_interface() {
-        Some(pb) => pb,
-        _ => return Err(SecurityStateError::from("could not QI to nsIPrefBranch")),
-    };
-    let pref_name = match CString::new(name) {
-        Ok(n) => n,
-        _ => return Err(SecurityStateError::from("could not build pref name string")),
-    };
-
-    let mut pref_value: i32 = -1;
-
-    // We can't use GetIntPrefWithDefault because optional_argc is not
-    // supported. No matter, we can just check for failure and ignore
-    // any NS_ERROR_UNEXPECTED result.
-    let res = unsafe { (*prefs).GetIntPref((&pref_name).as_ptr(), (&mut pref_value) as *mut i32) };
-    if !res.succeeded() {
-        match res.0 {
-            r if r == nsresult::NS_ERROR_UNEXPECTED as u32 => (),
-            _ => return Err(SecurityStateError::from("could not read pref")),
-        }
-    }
-    Ok(pref_value)
-}
-
-#[no_mangle]
-pub extern "C" fn cert_storage_constructor(
-    outer: *const nsISupports,
-    iid: *const xpcom::nsIID,
-    result: *mut *mut xpcom::reexports::libc::c_void,
-) -> nserror::nsresult {
-    if !outer.is_null() {
-        return nserror::NS_ERROR_NO_AGGREGATION;
-    }
-
-    match do_construct_cert_storage(outer, iid, result) {
-        Ok(_) => nserror::NS_OK,
-        Err(_) => {
-            // In future: log something so we know what went wrong?
-            nserror::NS_ERROR_FAILURE
-        }
-    }
-}
-
-macro_rules! try_ns {
-    ($e:expr) => {
-        match $e {
-            Ok(value) => value,
-            Err(_) => return nserror::NS_ERROR_FAILURE,
-        }
-    };
-}
-
-#[derive(xpcom)]
-#[xpimplements(nsICertStorage, nsIObserver)]
-#[refcnt = "atomic"]
-struct InitCertStorage {
-    security_state: RwLock<SecurityState>,
-}
-
-#[allow(non_snake_case)]
-impl CertStorage {
-    unsafe fn setup_prefs(&self) -> Result<(), SecurityStateError> {
-        let int_prefs = [
-            "services.blocklist.onecrl.checked",
-            "services.blocklist.intermediates.checked",
-            "services.blocklist.crlite.checked",
-            "security.onecrl.maximum_staleness_in_seconds",
-        ];
-
-        // Fetch add observers for relevant prefs
-        let pref_service = xpcom::services::get_PreferencesService().unwrap();
-        let prefs: RefPtr<nsIPrefBranch> = match (*pref_service).query_interface() {
-            Some(pb) => pb,
-            _ => return Err(SecurityStateError::from("could not QI to nsIPrefBranch")),
-        };
-
-        for pref in int_prefs.into_iter() {
-            let pref_nscstr = &nsCStr::from(pref.to_owned()) as &nsACString;
-            let rv = (*prefs).AddObserverImpl(pref_nscstr, self.coerce::<nsIObserver>(), false);
-            match read_int_pref(pref) {
-                Ok(up) => {
-                    let mut ss = match self.security_state.write() {
-                        Err(_) => return Err(SecurityStateError::from("could not get write lock")),
-                        Ok(write_guard) => write_guard,
-                    };
-                    ss.pref_seen(pref, up)
-                }
-                Err(_) => return Err(SecurityStateError::from("could not read pref")),
-            };
-            assert!(rv.succeeded());
-        }
-
-        Ok(())
-    }
-
-    unsafe fn SetRevocationByIssuerAndSerial(
-        &self,
-        issuer: *const nsACString,
-        serial: *const nsACString,
-        state: i16,
-    ) -> nserror::nsresult {
-        if issuer.is_null() || serial.is_null() {
-            return nserror::NS_ERROR_FAILURE;
-        }
-        let issuer_decoded = try_ns!(base64::decode(&*issuer));
-        let serial_decoded = try_ns!(base64::decode(&*serial));
-        let mut ss = try_ns!(self.security_state.write());
-        match ss.set_revocation_by_issuer_and_serial(&issuer_decoded, &serial_decoded, state) {
-            Ok(_) => nserror::NS_OK,
-            _ => nserror::NS_ERROR_FAILURE,
-        }
-    }
-
-    unsafe fn SetRevocationBySubjectAndPubKey(
-        &self,
-        subject: *const nsACString,
-        pub_key_base64: *const nsACString,
-        state: i16,
-    ) -> nserror::nsresult {
-        if subject.is_null() || pub_key_base64.is_null() {
-            return nserror::NS_ERROR_FAILURE;
-        }
-        let subject_decoded = try_ns!(base64::decode(&*subject));
-        let pub_key_decoded = try_ns!(base64::decode(&*pub_key_base64));
-        let mut ss = try_ns!(self.security_state.write());
-        match ss.set_revocation_by_subject_and_pub_key(&subject_decoded, &pub_key_decoded, state) {
-            Ok(_) => nserror::NS_OK,
-            _ => nserror::NS_ERROR_FAILURE,
-        }
-    }
-
-    unsafe fn SetEnrollment(
-        &self,
-        issuer: *const nsACString,
-        serial: *const nsACString,
-        state: i16,
-    ) -> nserror::nsresult {
-        if issuer.is_null() || serial.is_null() {
-            return nserror::NS_ERROR_FAILURE;
-        }
-        let issuer_decoded = try_ns!(base64::decode(&*issuer));
-        let serial_decoded = try_ns!(base64::decode(&*serial));
-        let mut ss = try_ns!(self.security_state.write());
-        match ss.set_enrollment(&issuer_decoded, &serial_decoded, state) {
-            Ok(_) => nserror::NS_OK,
-            _ => nserror::NS_ERROR_FAILURE,
-        }
-    }
-
-    unsafe fn SetWhitelist(
-        &self,
-        issuer: *const nsACString,
-        serial: *const nsACString,
-        state: i16,
-    ) -> nserror::nsresult {
-        if issuer.is_null() || serial.is_null() {
-            return nserror::NS_ERROR_FAILURE;
-        }
-        let issuer_decoded = try_ns!(base64::decode(&*issuer));
-        let serial_decoded = try_ns!(base64::decode(&*serial));
-        let mut ss = try_ns!(self.security_state.write());
-        match ss.set_whitelist(&issuer_decoded, &serial_decoded, state) {
-            Ok(_) => nserror::NS_OK,
-            _ => nserror::NS_ERROR_FAILURE,
-        }
-    }
-
-    unsafe fn GetRevocationState(
-        &self,
-        issuer: *const nsACString,
-        serial: *const nsACString,
-        subject: *const nsACString,
-        pub_key_base64: *const nsACString,
-        state: *mut i16,
-    ) -> nserror::nsresult {
-        if issuer.is_null() || serial.is_null() || subject.is_null() || pub_key_base64.is_null() {
-            return nserror::NS_ERROR_FAILURE;
-        }
-        // TODO (bug 1535752): If we're calling this function when we already have binary data (e.g.
-        // in a TrustDomain::GetCertTrust callback), we should be able to pass in the binary data
-        // directly. See also bug 1535486.
-        let issuer_decoded = try_ns!(base64::decode(&*issuer));
-        let serial_decoded = try_ns!(base64::decode(&*serial));
-        let subject_decoded = try_ns!(base64::decode(&*subject));
-        let pub_key_decoded = try_ns!(base64::decode(&*pub_key_base64));
-        let ss = try_ns!(self.security_state.read());
-        *state = nsICertStorage::STATE_UNSET as i16;
-        match ss.get_revocation_state(
-            &issuer_decoded,
-            &serial_decoded,
-            &subject_decoded,
-            &pub_key_decoded,
-        ) {
-            Ok(st) => {
-                *state = st;
-                nserror::NS_OK
-            }
-            _ => nserror::NS_ERROR_FAILURE,
-        }
-    }
-
-    unsafe fn GetEnrollmentState(
-        &self,
-        issuer: *const nsACString,
-        serial: *const nsACString,
-        state: *mut i16,
-    ) -> nserror::nsresult {
-        if issuer.is_null() || serial.is_null() {
-            return nserror::NS_ERROR_FAILURE;
-        }
-        let issuer_decoded = try_ns!(base64::decode(&*issuer));
-        let serial_decoded = try_ns!(base64::decode(&*serial));
-        let ss = try_ns!(self.security_state.read());
-        *state = nsICertStorage::STATE_UNSET as i16;
-        match ss.get_enrollment_state(&issuer_decoded, &serial_decoded) {
-            Ok(st) => {
-                *state = st;
-                nserror::NS_OK
-            }
-            _ => nserror::NS_ERROR_FAILURE,
-        }
-    }
-
-    unsafe fn GetWhitelistState(
-        &self,
-        issuer: *const nsACString,
-        serial: *const nsACString,
-        state: *mut i16,
-    ) -> nserror::nsresult {
-        if issuer.is_null() || serial.is_null() {
-            return nserror::NS_ERROR_FAILURE;
-        }
-        let issuer_decoded = try_ns!(base64::decode(&*issuer));
-        let serial_decoded = try_ns!(base64::decode(&*serial));
-        let ss = try_ns!(self.security_state.read());
-        *state = nsICertStorage::STATE_UNSET as i16;
-        match ss.get_whitelist_state(&issuer_decoded, &serial_decoded) {
-            Ok(st) => {
-                *state = st;
-                nserror::NS_OK
-            }
-            _ => nserror::NS_ERROR_FAILURE,
-        }
-    }
-
-    unsafe fn IsBlocklistFresh(&self, fresh: *mut bool) -> nserror::nsresult {
-        *fresh = false;
-        let ss = try_ns!(self.security_state.read());
-        *fresh = match ss.is_blocklist_fresh() {
-            Ok(is_fresh) => is_fresh,
-            Err(_) => false,
-        };
-
-        nserror::NS_OK
-    }
-
-    unsafe fn IsWhitelistFresh(&self, fresh: *mut bool) -> nserror::nsresult {
-        *fresh = false;
-        let ss = try_ns!(self.security_state.read());
-        *fresh = match ss.is_whitelist_fresh() {
-            Ok(is_fresh) => is_fresh,
-            Err(_) => false,
-        };
-
-        nserror::NS_OK
-    }
-
-    unsafe fn IsEnrollmentFresh(&self, fresh: *mut bool) -> nserror::nsresult {
-        *fresh = false;
-        let ss = try_ns!(self.security_state.read());
-        *fresh = match ss.is_enrollment_fresh() {
-            Ok(is_fresh) => is_fresh,
-            Err(_) => false,
-        };
-
-        nserror::NS_OK
-    }
-
-    unsafe fn Observe(
-        &self,
-        subject: *const nsISupports,
-        topic: *const c_char,
-        pref_name: *const i16,
-    ) -> nserror::nsresult {
-        match CStr::from_ptr(topic).to_str() {
-            Ok("nsPref:changed") => {
-                let mut pref_value: i32 = 0;
-
-                let prefs: RefPtr<nsIPrefBranch> = match (*subject).query_interface() {
-                    Some(pb) => pb,
-                    _ => return nserror::NS_ERROR_FAILURE,
-                };
-
-                // Convert our wstring pref_name to a cstring (via nsCString's
-                // utf16 to utf8 conversion)
-                let mut len: usize = 0;
-                while (*(pref_name.offset(len as isize))) != 0 {
-                    len += 1;
-                }
-                let name_slice = slice::from_raw_parts(pref_name as *const u16, len);
-                let mut name_string = nsCString::new();
-                name_string.assign_utf16_to_utf8(name_slice);
-
-                let pref_name = match CString::new(name_string.as_str_unchecked()) {
-                    Ok(n) => n,
-                    _ => return nserror::NS_ERROR_FAILURE,
-                };
-
-                let res = prefs.GetIntPref((&pref_name).as_ptr(), (&mut pref_value) as *mut i32);
-
-                if !res.succeeded() {
-                    return res;
-                }
-
-                let mut ss = try_ns!(self.security_state.write());
-                ss.pref_seen(name_string.as_str_unchecked(), pref_value);
-            }
-            _ => (),
-        }
-        nserror::NS_OK
-    }
-}
--- a/security/manager/ssl/components.conf
+++ b/security/manager/ssl/components.conf
@@ -152,20 +152,20 @@ 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': '{11aefd53-2fbb-4c92-a0c1-053212ae42d0}',
+        'contract_ids': ['@mozilla.org/security/certblocklist;1'],
+        'type': 'CertBlocklist',
+        'legacy_constructor': 'mozilla::psm::NSSConstructor<CertBlocklist>',
     },
     {
         'cid': '{57972956-5718-42d2-8070-b3fc72212eaf}',
         'contract_ids': ['@mozilla.org/security/oskeystore;1'],
         'type': 'OSKeyStore',
         'legacy_constructor': 'mozilla::psm::NSSConstructor<OSKeyStore>',
     },
     {
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -6,19 +6,19 @@
 
 TEST_DIRS += [ 'tests' ]
 
 XPIDL_SOURCES += [
     'nsIASN1Object.idl',
     'nsIASN1PrintableItem.idl',
     'nsIASN1Sequence.idl',
     'nsIBadCertListener2.idl',
+    'nsICertBlocklist.idl',
     'nsICertificateDialogs.idl',
     'nsICertOverrideService.idl',
-    'nsICertStorage.idl',
     'nsIClientAuthDialogs.idl',
     'nsIContentSignatureVerifier.idl',
     'nsICryptoHash.idl',
     'nsICryptoHMAC.idl',
     'nsIGenKeypairInfoDlg.idl',
     'nsIKeygenThread.idl',
     'nsIKeyModule.idl',
     'nsILocalCertService.idl',
@@ -94,17 +94,17 @@ EXPORTS.mozilla.psm += [
     'PSMContentListener.h',
 ]
 
 EXPORTS.ipc += [
     'DataStorageIPCUtils.h',
 ]
 
 UNIFIED_SOURCES += [
-    'cert_storage/src/cert_storage.cpp',
+    'CertBlocklist.cpp',
     'ContentSignatureVerifier.cpp',
     'CryptoTask.cpp',
     'CSTrustDomain.cpp',
     'DataStorage.cpp',
     'EnterpriseRoots.cpp',
     'LocalCertService.cpp',
     'nsCertOverrideService.cpp',
     'nsClientAuthRemember.cpp',
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();
+};
deleted file mode 100644
--- a/security/manager/ssl/nsICertStorage.idl
+++ /dev/null
@@ -1,116 +0,0 @@
-/* -*- 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"
-
-%{C++
-#define NS_CERTSTORAGE_CONTRACTID "@mozilla.org/security/certstorage;1"
-%}
-
-[scriptable, uuid(327100a7-3401-45ef-b160-bf880f1016fd)]
-interface nsICertStorage : nsISupports {
-  const short STATE_UNSET = 0;
-  const short STATE_ENFORCE = 1;
-
-  /**
-   * Set the revocation state of a certificate by issuer and serial number:
-   * issuer name (base-64 encoded DER) and serial number (base-64 encoded DER).
-   */
-  [must_use]
-  void setRevocationByIssuerAndSerial(in ACString issuer,
-                                      in ACString serialNumber,
-                                      in short state);
-
-  /**
-   * Set the revocation state of a certificate by subject and public key hash:
-   * subject name (base-64 encoded DER) and hash of public key (base-64 encoded
-   * sha-256 hash of the public key).
-   * state (short) is STATE_ENFORCE for revoked certs, STATE_UNSET otherwise.
-   */
-  [must_use]
-  void setRevocationBySubjectAndPubKey(in ACString subject,
-                                       in ACString pubKeyHash,
-                                       in short state);
-
-  /**
-   * Set the whitelist state of an intermediate certificate by issuer and
-   * serial number:
-   * issuer name (base-64 encoded DER) and serial number (base-64 encoded DER).
-   * state (short) is STATE_ENFORCE for whitelisted certs, STATE_UNSET otherwise.
-   */
-  [must_use]
-  void setWhitelist(in ACString issuer,
-                    in ACString serialNumber,
-                    in short state);
-
-  /**
-   * Set the CRLite enrollment state of a certificate by issuer and serial
-   * number:
-   * issuer name (base-64 encoded DER) and serial number (base-64 encoded DER).
-   * state (short) is STATE_ENFORCE for enrolled certs, STATE_UNSET otherwise.
-   */
-  [must_use]
-  void setEnrollment(in ACString issuer,
-                    in ACString serialNumber,
-                    in short state);
-
-  /**
-   * Get the revocation state of a certificate.
-   * 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]
-  short getRevocationState(in ACString issuer,
-                           in ACString serial,
-                           in ACString subject,
-                           in ACString pubkey);
-
-  /**
-   * Get the CRLite enrollment status of a certificate.
-   * issuer - issuer name, DER, Base64 encoded
-   * serial - serial number, DER, Base64 encoded
-   */
-  [must_use]
-  short getEnrollmentState(in ACString issuer,
-                           in ACString serial);
-
-  /**
-   * Get the whitelist status of an intermediate certificate.
-   * issuer - issuer name, DER, Base64 encoded
-   * serial - serial number, DER, Base64 encoded
-   */
-  [must_use]
-  short getWhitelistState(in ACString issuer,
-                          in ACString serial);
-
-   /**
-    * 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
-    * services.blocklist.onecrl.checked pref)
-    */
-  [must_use]
-  boolean isBlocklistFresh();
-
-   /**
-    * Check that the whitelist data is current. Specifically, that the current
-    * time is no more than security.onecrl.maximum_staleness_in_seconds seconds
-    * after the last whitelist update (as stored in the
-    * services.blocklist.intermediates.checked pref)
-    */
-  [must_use]
-  boolean isWhitelistFresh();
-
-   /**
-    * Check that the CRLite enrollment data is current. Specifically, that the current
-    * time is no more than security.onecrl.maximum_staleness_in_seconds seconds
-    * after the last crlite enrollment update (as stored in the
-    * services.blocklist.crlite.checked pref)
-    */
-  [must_use]
-  boolean isEnrollmentFresh();
-};
--- a/security/manager/ssl/nsNSSModule.cpp
+++ b/security/manager/ssl/nsNSSModule.cpp
@@ -1,20 +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"
 
+#include "CertBlocklist.h"
 #include "ContentSignatureVerifier.h"
 #include "NSSErrorsService.h"
-#include "OSKeyStore.h"
-#include "OSReauthenticator.h"
 #include "PKCS11ModuleDB.h"
 #include "PSMContentListener.h"
 #include "SecretDecoderRing.h"
 #include "TransportSecurityInfo.h"
 #include "mozilla/MacroArgs.h"
 #include "mozilla/ModuleUtils.h"
 #include "mozilla/SyncRunnable.h"
 #include "nsCURILoader.h"
@@ -29,16 +28,18 @@
 #include "nsNSSVersion.h"
 #include "nsNetCID.h"
 #include "nsPK11TokenDB.h"
 #include "nsPKCS11Slot.h"
 #include "nsRandomGenerator.h"
 #include "nsSecureBrowserUIImpl.h"
 #include "nsSiteSecurityService.h"
 #include "nsXULAppAPI.h"
+#include "OSKeyStore.h"
+#include "OSReauthenticator.h"
 
 #ifdef MOZ_XUL
 #  include "nsCertTree.h"
 #endif
 
 namespace mozilla {
 namespace psm {
 
@@ -142,16 +143,18 @@ 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)
+IMPL(CertBlocklist, &CertBlocklist::Init, ProcessRestriction::ParentProcessOnly,
+     ThreadRestriction::MainThreadOnly)
 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
@@ -95,22 +95,21 @@ 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);
-  certBlocklist.setRevocationBySubjectAndPubKey(
+  let certBlocklist = Cc["@mozilla.org/security/certblocklist;1"]
+                        .getService(Ci.nsICertBlocklist);
+  certBlocklist.revokeCertBySubjectAndPubKey(
     "MBIxEDAOBgNVBAMMB3Jldm9rZWQ=", // CN=revoked
-    "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=", // hash of the shared key
-    Ci.nsICertStorage.STATE_ENFORCE); // yes, we want this to be 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);
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_cert_blocklist.js
@@ -0,0 +1,396 @@
+/* -*- 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";
+
+// This test checks a number of things:
+// * it ensures that data loaded from revocations.txt on startup is present
+// * it ensures that data served from OneCRL are persisted correctly
+// * it ensures that items in the CertBlocklist are seen as revoked by the
+//   cert verifier
+// * it does a sanity check to ensure other cert verifier behavior is
+//   unmodified
+
+const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+const { RemoteSettings } = ChromeUtils.import("resource://services-settings/remote-settings.js");
+const BlocklistClients = ChromeUtils.import("resource://services-common/blocklist-clients.js", null);
+
+// First, we need to setup appInfo for the blocklist service to work
+var id = "xpcshell@tests.mozilla.org";
+var appName = "XPCShell";
+var version = "1";
+var platformVersion = "1.9.2";
+ChromeUtils.import("resource://testing-common/AppInfo.jsm", this);
+/* global updateAppInfo:false */ // Imported via AppInfo.jsm.
+updateAppInfo({
+  name: appName,
+  ID: id,
+  version,
+  platformVersion: platformVersion ? platformVersion : "1.0",
+  crashReporter: true,
+});
+
+// we need to ensure we setup revocation data before certDB, or we'll start with
+// no revocation.txt in the profile
+var gProfile = do_get_profile();
+
+var gRevocations = gProfile.clone();
+gRevocations.append("revocations.txt");
+if (!gRevocations.exists()) {
+  let existing = do_get_file("test_onecrl/sample_revocations.txt", false);
+  existing.copyTo(gProfile, "revocations.txt");
+}
+
+var certDB = Cc["@mozilla.org/security/x509certdb;1"]
+               .getService(Ci.nsIX509CertDB);
+
+// set up a test server to serve the kinto views.
+var testserver = new HttpServer();
+
+
+const kintoHelloViewJSON = `{"settings":{"batch_max_requests":25}}`;
+const kintoChangesJSON = `{
+  "data": [
+    {
+      "host": "firefox.settings.services.mozilla.com",
+      "id": "3ace9d8e-00b5-a353-7fd5-1f081ff482ba",
+      "last_modified": 100000000000000000001,
+      "bucket": "blocklists",
+      "collection": "certificates"
+    }
+  ]
+}`;
+const certMetadataJSON = `{"data": {}}`;
+const certBlocklistJSON = `{
+  "data": [` +
+  // test with some bad data ...
+  ` {
+      "id": "1",
+      "last_modified": 100000000000000000001,
+      "issuerName": "Some nonsense in issuer",
+      "serialNumber": "AkHVNA=="
+    },
+    {
+      "id": "2",
+      "last_modified": 100000000000000000002,
+      "issuerName": "MA0xCzAJBgNVBAMMAmNh",
+      "serialNumber": "some nonsense in serial"
+    },
+    {
+      "id": "3",
+      "last_modified": 100000000000000000003,
+      "issuerName": "and serial",
+      "serialNumber": "some nonsense in both issuer"
+    },` +
+  // some mixed
+  // In these case, the issuer name and the valid serialNumber correspond
+  // to test-int.pem in bad_certs/
+  ` {
+      "id": "4",
+      "last_modified": 100000000000000000004,
+      "issuerName": "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=",
+      "serialNumber": "oops! more nonsense."
+    },` +
+  ` {
+      "id": "5",
+      "last_modified": 100000000000000000004,
+      "issuerName": "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=",
+      "serialNumber": "a0X7/7DlTaedpgrIJg25iBPOkIM="
+    },` +
+  // ... and some good
+  // In this case, the issuer name and the valid serialNumber correspond
+  // to other-test-ca.pem in bad_certs/ (for testing root revocation)
+  ` {
+      "id": "6",
+      "last_modified": 100000000000000000005,
+      "issuerName": "MBgxFjAUBgNVBAMMDU90aGVyIHRlc3QgQ0E=",
+      "serialNumber": "Rym6o+VN9xgZXT/QLrvN/nv1ZN4="
+    },` +
+  // These items correspond to an entry in sample_revocations.txt where:
+  // isser name is "another imaginary issuer" base-64 encoded, and
+  // serialNumbers are:
+  // "serial2." base-64 encoded, and
+  // "another serial." base-64 encoded
+  // We need this to ensure that existing items are retained if they're
+  // also in the blocklist
+  ` {
+      "id": "7",
+      "last_modified": 100000000000000000006,
+      "issuerName": "YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy",
+      "serialNumber": "c2VyaWFsMi4="
+    },` +
+  ` {
+      "id": "8",
+      "last_modified": 100000000000000000006,
+      "issuerName": "YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy",
+      "serialNumber": "YW5vdGhlciBzZXJpYWwu"
+    },` +
+  // This item revokes same-issuer-ee.pem by subject and pubKeyHash.
+  ` {
+      "id": "9",
+      "last_modified": 100000000000000000007,
+      "subject": "MCIxIDAeBgNVBAMMF0Fub3RoZXIgVGVzdCBFbmQtZW50aXR5",
+      "pubKeyHash": "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="
+    }
+  ]
+}`;
+
+function serveResponse(body) {
+  return (req, response) => {
+    response.setHeader("Content-Type", "application/json; charset=UTF-8");
+    response.setStatusLine(null, 200, "OK");
+    response.write(body);
+  };
+}
+
+testserver.registerPathHandler("/v1/",
+                               serveResponse(kintoHelloViewJSON));
+testserver.registerPathHandler("/v1/buckets/monitor/collections/changes/records",
+                               serveResponse(kintoChangesJSON));
+testserver.registerPathHandler("/v1/buckets/blocklists/collections/certificates",
+                               serveResponse(certMetadataJSON));
+testserver.registerPathHandler("/v1/buckets/blocklists/collections/certificates/records",
+                               serveResponse(certBlocklistJSON));
+
+// start the test server
+testserver.start(-1);
+var port = testserver.identity.primaryPort;
+
+// Setup the addonManager
+var addonManager = Cc["@mozilla.org/addons/integration;1"]
+                     .getService(Ci.nsIObserver)
+                     .QueryInterface(Ci.nsITimerCallback);
+addonManager.observe(null, "addons-startup", null);
+
+function verify_cert(file, expectedError) {
+  let ee = constructCertFromFile(file);
+  return checkCertErrorGeneric(certDB, ee, expectedError,
+                               certificateUsageSSLServer);
+}
+
+// The certificate blocklist currently only applies to TLS server certificates.
+async function verify_non_tls_usage_succeeds(file) {
+  let ee = constructCertFromFile(file);
+  await checkCertErrorGeneric(certDB, ee, PRErrorCodeSuccess,
+                              certificateUsageSSLClient);
+  await checkCertErrorGeneric(certDB, ee, PRErrorCodeSuccess,
+                              certificateUsageEmailSigner);
+  await checkCertErrorGeneric(certDB, ee, PRErrorCodeSuccess,
+                              certificateUsageEmailRecipient);
+}
+
+function load_cert(cert, trust) {
+  let file = "bad_certs/" + cert + ".pem";
+  addCertFromFile(certDB, file, trust);
+}
+
+function test_is_revoked(certList, issuerString, serialString, subjectString,
+                         pubKeyString) {
+  return certList.isCertRevoked(btoa(issuerString), btoa(serialString),
+                                btoa(subjectString), btoa(pubKeyString));
+}
+
+function fetch_blocklist() {
+  Services.prefs.setBoolPref("services.settings.load_dump", false);
+  Services.prefs.setBoolPref("services.settings.verify_signature", false);
+  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/certblocklist;1"]
+                  .getService(Ci.nsICertBlocklist);
+
+  let expected = { "MCIxIDAeBgNVBAMMF0Fub3RoZXIgVGVzdCBFbmQtZW50aXR5":
+                     { "\tVCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=": true },
+                   "MBgxFjAUBgNVBAMMDU90aGVyIHRlc3QgQ0E=":
+                     { " Rym6o+VN9xgZXT/QLrvN/nv1ZN4=": true},
+                   "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=":
+                     { " a0X7/7DlTaedpgrIJg25iBPOkIM=": true},
+                   "YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy":
+                     { " YW5vdGhlciBzZXJpYWwu": true,
+                       " c2VyaWFsMi4=": true },
+                 };
+
+  add_task(async function() {
+    // check some existing items in revocations.txt are blocked. Since the
+    // CertBlocklistItems don't know about the data they contain, we can use
+    // arbitrary data (not necessarily DER) to test if items are revoked or not.
+    // This test corresponds to:
+    // issuer: c29tZSBpbWFnaW5hcnkgaXNzdWVy
+    // serial: c2VyaWFsLg==
+    ok(test_is_revoked(certList, "some imaginary issuer", "serial."),
+      "issuer / serial pair should be blocked");
+
+    // This test corresponds to:
+    // issuer: YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy
+    // serial: c2VyaWFsLg==
+    ok(test_is_revoked(certList, "another imaginary issuer", "serial."),
+      "issuer / serial pair should be blocked");
+
+    // And this test corresponds to:
+    // issuer: YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy
+    // serial: c2VyaWFsMi4=
+    // (we test this issuer twice to ensure we can read multiple serials)
+    ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
+      "issuer / serial pair should be blocked");
+
+    // Soon we'll load a blocklist which revokes test-int.pem, which issued
+    // test-int-ee.pem.
+    // Check the cert validates before we load the blocklist
+    let file = "test_onecrl/test-int-ee.pem";
+    await verify_cert(file, PRErrorCodeSuccess);
+
+    // The blocklist also revokes other-test-ca.pem, which issued
+    // other-ca-ee.pem. Check the cert validates before we load the blocklist
+    file = "bad_certs/other-issuer-ee.pem";
+    await verify_cert(file, PRErrorCodeSuccess);
+
+    // The blocklist will revoke same-issuer-ee.pem via subject / pubKeyHash.
+    // Check the cert validates before we load the blocklist
+    file = "test_onecrl/same-issuer-ee.pem";
+    await verify_cert(file, PRErrorCodeSuccess);
+  });
+
+  // blocklist load is async so we must use add_test from here
+  add_task(fetch_blocklist);
+
+  add_task(async function() {
+    // The blocklist will be loaded now. Let's check the data is sane.
+    // In particular, we should still have the revoked issuer / serial pair
+    // that was in both revocations.txt and the blocklist.
+    ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
+      "issuer / serial pair should be blocked");
+
+    // Check that both serials in the certItem with multiple serials were read
+    // properly
+    ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
+       "issuer / serial pair should be blocked");
+    ok(test_is_revoked(certList, "another imaginary issuer", "another serial."),
+       "issuer / serial pair should be blocked");
+
+    // test a subject / pubKey revocation
+    ok(test_is_revoked(certList, "nonsense", "more nonsense",
+                       "some imaginary subject", "some imaginary pubkey"),
+       "issuer / serial pair should be blocked");
+
+    // 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
+    let 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);
+    await verify_non_tls_usage_succeeds(file);
+
+    // Check the ee blocked by subject / pubKey causes a failure
+    file = "test_onecrl/same-issuer-ee.pem";
+    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
+    await verify_non_tls_usage_succeeds(file);
+
+    // 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);
+
+    // check that save with no further update is a no-op
+    let lastModified = gRevocations.lastModifiedTime;
+    // add an already existing entry
+    certList.revokeCertByIssuerAndSerial("YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy",
+                                         "c2VyaWFsMi4=");
+    certList.saveEntries();
+    let newModified = gRevocations.lastModifiedTime;
+    equal(lastModified, newModified,
+          "saveEntries with no modifications should not update the backing file");
+  });
+
+  add_test(function() {
+    // Check the blocklist entry has not changed
+    check_revocations_txt_contents(expected);
+    run_next_test();
+  });
+
+  run_next_test();
+}
deleted file mode 100644
--- a/security/manager/ssl/tests/unit/test_cert_storage.js
+++ /dev/null
@@ -1,305 +0,0 @@
-/* -*- 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";
-
-// This test checks a number of things:
-// * it ensures that data loaded from revocations.txt on startup is present
-// * it ensures that data served from OneCRL are persisted correctly
-// * it ensures that items in the CertBlocklist are seen as revoked by the
-//   cert verifier
-// * it does a sanity check to ensure other cert verifier behavior is
-//   unmodified
-
-const { setTimeout } = ChromeUtils.import("resource://gre/modules/Timer.jsm", {});
-const { RemoteSettings } = ChromeUtils.import("resource://services-settings/remote-settings.js", {});
-const BlocklistClients = ChromeUtils.import("resource://services-common/blocklist-clients.js", {});
-
-// First, we need to setup appInfo for the blocklist service to work
-var id = "xpcshell@tests.mozilla.org";
-var appName = "XPCShell";
-var version = "1";
-var platformVersion = "1.9.2";
-ChromeUtils.import("resource://testing-common/AppInfo.jsm", this);
-/* global updateAppInfo:false */ // Imported via AppInfo.jsm.
-updateAppInfo({
-  name: appName,
-  ID: id,
-  version,
-  platformVersion: platformVersion ? platformVersion : "1.0",
-  crashReporter: true,
-});
-
-// we need to ensure we setup revocation data before certDB, or we'll start with
-// no revocation.txt in the profile
-var gProfile = do_get_profile();
-
-var gRevocations = gProfile.clone();
-gRevocations.append("revocations.txt");
-if (!gRevocations.exists()) {
-  let existing = do_get_file("test_onecrl/sample_revocations.txt", false);
-  existing.copyTo(gProfile, "revocations.txt");
-}
-
-var certDB = Cc["@mozilla.org/security/x509certdb;1"]
-               .getService(Ci.nsIX509CertDB);
-
-// set up a test server to serve the kinto views.
-var testserver = new HttpServer();
-
-
-const kintoHelloViewJSON = `{"settings":{"batch_max_requests":25}}`;
-const kintoChangesJSON = `{
-  "data": [
-    {
-      "host": "firefox.settings.services.mozilla.com",
-      "id": "3ace9d8e-00b5-a353-7fd5-1f081ff482ba",
-      "last_modified": 100000000000000000001,
-      "bucket": "blocklists",
-      "collection": "certificates"
-    }
-  ]
-}`;
-const certMetadataJSON = `{"data": {}}`;
-const certBlocklistJSON = `{
-  "data": [` +
-  // test with some bad data ...
-  ` {
-      "id": "1",
-      "last_modified": 100000000000000000001,
-      "issuerName": "Some nonsense in issuer",
-      "serialNumber": "AkHVNA=="
-    },
-    {
-      "id": "2",
-      "last_modified": 100000000000000000002,
-      "issuerName": "MA0xCzAJBgNVBAMMAmNh",
-      "serialNumber": "some nonsense in serial"
-    },
-    {
-      "id": "3",
-      "last_modified": 100000000000000000003,
-      "issuerName": "and serial",
-      "serialNumber": "some nonsense in both issuer"
-    },` +
-  // some mixed
-  // In these case, the issuer name and the valid serialNumber correspond
-  // to test-int.pem in bad_certs/
-  ` {
-      "id": "4",
-      "last_modified": 100000000000000000004,
-      "issuerName": "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=",
-      "serialNumber": "oops! more nonsense."
-    },` +
-  ` {
-      "id": "5",
-      "last_modified": 100000000000000000004,
-      "issuerName": "MBIxEDAOBgNVBAMMB1Rlc3QgQ0E=",
-      "serialNumber": "a0X7/7DlTaedpgrIJg25iBPOkIM="
-    },` +
-  // ... and some good
-  // In this case, the issuer name and the valid serialNumber correspond
-  // to other-test-ca.pem in bad_certs/ (for testing root revocation)
-  ` {
-      "id": "6",
-      "last_modified": 100000000000000000005,
-      "issuerName": "MBgxFjAUBgNVBAMMDU90aGVyIHRlc3QgQ0E=",
-      "serialNumber": "Rym6o+VN9xgZXT/QLrvN/nv1ZN4="
-    },` +
-  // These items correspond to an entry in sample_revocations.txt where:
-  // isser name is "another imaginary issuer" base-64 encoded, and
-  // serialNumbers are:
-  // "serial2." base-64 encoded, and
-  // "another serial." base-64 encoded
-  // We need this to ensure that existing items are retained if they're
-  // also in the blocklist
-  ` {
-      "id": "7",
-      "last_modified": 100000000000000000006,
-      "issuerName": "YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy",
-      "serialNumber": "c2VyaWFsMi4="
-    },` +
-  ` {
-      "id": "8",
-      "last_modified": 100000000000000000006,
-      "issuerName": "YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy",
-      "serialNumber": "YW5vdGhlciBzZXJpYWwu"
-    },` +
-  // This item revokes same-issuer-ee.pem by subject and pubKeyHash.
-  ` {
-      "id": "9",
-      "last_modified": 100000000000000000007,
-      "subject": "MCIxIDAeBgNVBAMMF0Fub3RoZXIgVGVzdCBFbmQtZW50aXR5",
-      "pubKeyHash": "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8="
-    }
-  ]
-}`;
-
-function serveResponse(body) {
-  return (req, response) => {
-    response.setHeader("Content-Type", "application/json; charset=UTF-8");
-    response.setStatusLine(null, 200, "OK");
-    response.write(body);
-  };
-}
-
-testserver.registerPathHandler("/v1/",
-                               serveResponse(kintoHelloViewJSON));
-testserver.registerPathHandler("/v1/buckets/monitor/collections/changes/records",
-                               serveResponse(kintoChangesJSON));
-testserver.registerPathHandler("/v1/buckets/blocklists/collections/certificates",
-                               serveResponse(certMetadataJSON));
-testserver.registerPathHandler("/v1/buckets/blocklists/collections/certificates/records",
-                               serveResponse(certBlocklistJSON));
-
-// start the test server
-testserver.start(-1);
-var port = testserver.identity.primaryPort;
-
-// Setup the addonManager
-var addonManager = Cc["@mozilla.org/addons/integration;1"]
-                     .getService(Ci.nsIObserver)
-                     .QueryInterface(Ci.nsITimerCallback);
-addonManager.observe(null, "addons-startup", null);
-
-function verify_cert(file, expectedError) {
-  let ee = constructCertFromFile(file);
-  return checkCertErrorGeneric(certDB, ee, expectedError,
-                               certificateUsageSSLServer);
-}
-
-// The certificate blocklist currently only applies to TLS server certificates.
-async function verify_non_tls_usage_succeeds(file) {
-  let ee = constructCertFromFile(file);
-  await checkCertErrorGeneric(certDB, ee, PRErrorCodeSuccess,
-                              certificateUsageSSLClient);
-  await checkCertErrorGeneric(certDB, ee, PRErrorCodeSuccess,
-                              certificateUsageEmailSigner);
-  await checkCertErrorGeneric(certDB, ee, PRErrorCodeSuccess,
-                              certificateUsageEmailRecipient);
-}
-
-function load_cert(cert, trust) {
-  let file = "bad_certs/" + cert + ".pem";
-  addCertFromFile(certDB, file, trust);
-}
-
-function test_is_revoked(certList, issuerString, serialString, subjectString,
-                         pubKeyString) {
-  return certList.getRevocationState(btoa(issuerString), btoa(serialString),
-                                     btoa(subjectString), btoa(pubKeyString)) == Ci.nsICertStorage.STATE_ENFORCE;
-}
-
-function fetch_blocklist() {
-  Services.prefs.setBoolPref("services.settings.load_dump", false);
-  Services.prefs.setBoolPref("services.settings.verify_signature", false);
-  Services.prefs.setCharPref("services.settings.server",
-                             `http://localhost:${port}/v1`);
-
-  BlocklistClients.initialize();
-
-  return RemoteSettings.pollChanges();
-}
-
-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);
-
-  add_task(async function() {
-    // check some existing items in revocations.txt are blocked. Since the
-    // CertBlocklistItems don't know about the data they contain, we can use
-    // arbitrary data (not necessarily DER) to test if items are revoked or not.
-    // This test corresponds to:
-    // issuer: c29tZSBpbWFnaW5hcnkgaXNzdWVy
-    // serial: c2VyaWFsLg==
-    ok(test_is_revoked(certList, "some imaginary issuer", "serial."),
-      "issuer / serial pair should be blocked");
-
-    // This test corresponds to:
-    // issuer: YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy
-    // serial: c2VyaWFsLg==
-    ok(test_is_revoked(certList, "another imaginary issuer", "serial."),
-      "issuer / serial pair should be blocked");
-
-    // And this test corresponds to:
-    // issuer: YW5vdGhlciBpbWFnaW5hcnkgaXNzdWVy
-    // serial: c2VyaWFsMi4=
-    // (we test this issuer twice to ensure we can read multiple serials)
-    ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
-      "issuer / serial pair should be blocked");
-
-    // Soon we'll load a blocklist which revokes test-int.pem, which issued
-    // test-int-ee.pem.
-    // Check the cert validates before we load the blocklist
-    let file = "test_onecrl/test-int-ee.pem";
-    await verify_cert(file, PRErrorCodeSuccess);
-
-    // The blocklist also revokes other-test-ca.pem, which issued
-    // other-ca-ee.pem. Check the cert validates before we load the blocklist
-    file = "bad_certs/other-issuer-ee.pem";
-    await verify_cert(file, PRErrorCodeSuccess);
-
-    // The blocklist will revoke same-issuer-ee.pem via subject / pubKeyHash.
-    // Check the cert validates before we load the blocklist
-    file = "test_onecrl/same-issuer-ee.pem";
-    await verify_cert(file, PRErrorCodeSuccess);
-  });
-
-  // blocklist load is async so we must use add_test from here
-  add_task(fetch_blocklist);
-
-  add_task(async function() {
-    // The blocklist will be loaded now. Let's check the data is sane.
-    // In particular, we should still have the revoked issuer / serial pair
-    // that was in both revocations.txt and the blocklist.
-    ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
-      "issuer / serial pair should be blocked");
-
-    // Check that both serials in the certItem with multiple serials were read
-    // properly
-    ok(test_is_revoked(certList, "another imaginary issuer", "serial2."),
-       "issuer / serial pair should be blocked");
-    ok(test_is_revoked(certList, "another imaginary issuer", "another serial."),
-       "issuer / serial pair should be blocked");
-
-    // test a subject / pubKey revocation
-    ok(test_is_revoked(certList, "nonsense", "more nonsense",
-                       "some imaginary subject", "some imaginary pubkey"),
-       "issuer / serial pair should be blocked");
-
-    // Check the blocklisted intermediate now causes a failure
-    let 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);
-    await verify_non_tls_usage_succeeds(file);
-
-    // Check the ee blocked by subject / pubKey causes a failure
-    file = "test_onecrl/same-issuer-ee.pem";
-    await verify_cert(file, SEC_ERROR_REVOKED_CERTIFICATE);
-    await verify_non_tls_usage_succeeds(file);
-
-    // 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);
-  });
-
-  add_task(async function() {
-    ok(certList.isBlocklistFresh(), "Blocklist should be fresh.");
-  });
-
-  run_next_test();
-}
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -41,17 +41,17 @@ support-files =
 
 [test_add_preexisting_cert.js]
 [test_baseline_requirements_subject_common_name.js]
 [test_broken_fips.js]
 # FIPS has never been a thing on Android, so the workaround doesn't
 # exist on that platform.
 # FIPS still works on Linux/Windows, so this test doesn't make any sense there.
 skip-if = os != 'mac'
-[test_cert_storage.js]
+[test_cert_blocklist.js]
 tags = addons psm blocklist
 [test_cert_chains.js]
 run-sequentially = hardcoded ports
 [test_cert_dbKey.js]
 [test_cert_eku.js]
 [test_cert_embedded_null.js]
 [test_cert_expiration_canary.js]
 run-if = nightly_build
--- a/services/common/blocklist-clients.js
+++ b/services/common/blocklist-clients.js
@@ -34,51 +34,36 @@ const PREF_BLOCKLIST_GFX_COLLECTION     
 const PREF_BLOCKLIST_GFX_CHECKED_SECONDS     = "services.blocklist.gfx.checked";
 const PREF_BLOCKLIST_GFX_SIGNER              = "services.blocklist.gfx.signer";
 
 /**
  * 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);
-  for (let item of deleted) {
-    if (item.issuerName && item.serialNumber) {
-      certList.setRevocationByIssuerAndSerial(item.issuerName,
-                                              item.serialNumber,
-                                              Ci.nsICertStorage.STATE_UNSET);
-      } else if (item.subject && item.pubKeyHash) {
-        certList.setRevocationBySubjectAndPubKey(item.subject,
-                                                 item.pubKeyHash,
-                                                 Ci.nsICertStorage.STATE_UNSET);
-      }
-  }
-
-  const toAdd = created.concat(updated.map(u => u.new));
-
-  for (let item of toAdd) {
+async function updateCertBlocklist({ 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.setRevocationByIssuerAndSerial(item.issuerName,
-                                                item.serialNumber,
-                                                Ci.nsICertStorage.STATE_ENFORCE);
+        certList.revokeCertByIssuerAndSerial(item.issuerName,
+                                            item.serialNumber);
       } else if (item.subject && item.pubKeyHash) {
-        certList.setRevocationBySubjectAndPubKey(item.subject,
-                                                 item.pubKeyHash,
-                                                 Ci.nsICertStorage.STATE_ENFORCE);
+        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.
  */
--- a/toolkit/library/rust/shared/Cargo.toml
+++ b/toolkit/library/rust/shared/Cargo.toml
@@ -27,17 +27,16 @@ u2fhid = { path = "../../../../dom/webau
 gkrust_utils = { path = "../../../../xpcom/rust/gkrust_utils" }
 rsdparsa_capi = { path = "../../../../media/webrtc/signaling/src/sdp/rsdparsa_capi" }
 # 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" }
 
 [build-dependencies]
 rustc_version = "0.2"
 
 [features]
 default = []
 bindgen = ["geckoservo/bindgen"]
 servo = ["geckoservo"]
--- a/toolkit/library/rust/shared/lib.rs
+++ b/toolkit/library/rust/shared/lib.rs
@@ -26,17 +26,16 @@ 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;
-extern crate cert_storage;
 extern crate cosec;
 extern crate rsdparsa_capi;
 #[cfg(feature = "spidermonkey_rust")]
 extern crate jsrust_shared;
 
 extern crate arrayvec;
 
 use std::boxed::Box;
--- a/xpcom/build/Services.py
+++ b/xpcom/build/Services.py
@@ -9,28 +9,24 @@ def service(name, iface, contractid):
 
 
 service('ChromeRegistryService', 'nsIChromeRegistry',
         "@mozilla.org/chrome/chrome-registry;1")
 service('ToolkitChromeRegistryService', 'nsIToolkitChromeRegistry',
         "@mozilla.org/chrome/chrome-registry;1")
 service('XULChromeRegistryService', 'nsIXULChromeRegistry',
         "@mozilla.org/chrome/chrome-registry;1")
-service('DirectoryService', 'nsIProperties',
-        "@mozilla.org/file/directory_service;1"),
 service('IOService', 'nsIIOService',
         "@mozilla.org/network/io-service;1")
 service('ObserverService', 'nsIObserverService',
         "@mozilla.org/observer-service;1")
 service('StringBundleService', 'nsIStringBundleService',
         "@mozilla.org/intl/stringbundle;1")
 service('PermissionManager', 'nsIPermissionManager',
         "@mozilla.org/permissionmanager;1")
-service('PreferencesService', 'nsIPrefService',
-        "@mozilla.org/preferences-service;1")
 service('ServiceWorkerManager', 'nsIServiceWorkerManager',
         "@mozilla.org/serviceworkers/manager;1")
 service('AsyncShutdown', 'nsIAsyncShutdownService',
         "@mozilla.org/async-shutdown-service;1")
 service('UUIDGenerator', 'nsIUUIDGenerator',
         "@mozilla.org/uuid-generator;1")
 service('GfxInfo', 'nsIGfxInfo',
         "@mozilla.org/gfx/info;1")
@@ -66,17 +62,16 @@ CPP_INCLUDES = """
 #include "nsIIOService.h"
 #include "nsIDirectoryService.h"
 #include "nsIChromeRegistry.h"
 #include "nsIStringBundle.h"
 #include "nsIToolkitChromeRegistry.h"
 #include "IHistory.h"
 #include "nsIXPConnect.h"
 #include "nsIPermissionManager.h"
-#include "nsIPrefService.h"
 #include "nsIServiceWorkerManager.h"
 #include "nsICacheStorageService.h"
 #include "nsIStreamTransportService.h"
 #include "nsISocketTransportService.h"
 #include "nsIURIClassifier.h"
 #include "nsIHttpActivityObserver.h"
 #include "nsIAsyncShutdown.h"
 #include "nsIUUIDGenerator.h"