bug 1534600 - make nsIContentSignatureVerifier asynchronous r=KevinJacobs,mythmon,glasserc
authorDana Keeler <dkeeler@mozilla.com>
Fri, 03 May 2019 21:21:58 +0000
changeset 472568 c7a5a7b9cd50eff1be7591a5c49ee1164d800349
parent 472567 dda3ed67b6bbe085c9b39dc56950e16683d17b7c
child 472569 7aae4f23a5a189ffda85ab44342669b235c91c38
child 472602 7f0dd201e0593edb157189e0952f8def5e387f11
push id35967
push userccoroiu@mozilla.com
push dateSun, 05 May 2019 20:06:39 +0000
treeherdermozilla-central@7aae4f23a5a1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersKevinJacobs, mythmon, glasserc
bugs1534600
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 1534600 - make nsIContentSignatureVerifier asynchronous r=KevinJacobs,mythmon,glasserc Differential Revision: https://phabricator.services.mozilla.com/D29763
security/manager/ssl/ContentSignatureVerifier.cpp
security/manager/ssl/ContentSignatureVerifier.h
security/manager/ssl/nsIContentSignatureVerifier.idl
security/manager/ssl/tests/unit/test_content_signing.js
services/common/tests/unit/test_blocklist_signatures.js
services/settings/RemoteSettingsClient.jsm
toolkit/components/normandy/lib/NormandyApi.jsm
--- a/security/manager/ssl/ContentSignatureVerifier.cpp
+++ b/security/manager/ssl/ContentSignatureVerifier.cpp
@@ -2,79 +2,148 @@
 /* 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 "ContentSignatureVerifier.h"
 
 #include "BRNameMatchingPolicy.h"
+#include "CryptoTask.h"
 #include "ScopedNSSTypes.h"
 #include "SharedCertVerifier.h"
 #include "cryptohi.h"
 #include "keyhi.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Unused.h"
+#include "mozilla/dom/Promise.h"
 #include "nsCOMPtr.h"
 #include "nsPromiseFlatString.h"
 #include "nsSecurityHeaderParser.h"
 #include "nsWhitespaceTokenizer.h"
 #include "mozpkix/pkix.h"
 #include "mozpkix/pkixtypes.h"
 #include "secerr.h"
 
 NS_IMPL_ISUPPORTS(ContentSignatureVerifier, nsIContentSignatureVerifier)
 
 using namespace mozilla;
 using namespace mozilla::pkix;
 using namespace mozilla::psm;
+using dom::Promise;
 
 static LazyLogModule gCSVerifierPRLog("ContentSignatureVerifier");
 #define CSVerifier_LOG(args) MOZ_LOG(gCSVerifierPRLog, LogLevel::Debug, args)
 
 // Content-Signature prefix
 const unsigned char kPREFIX[] = {'C', 'o', 'n', 't', 'e', 'n', 't',
                                  '-', 'S', 'i', 'g', 'n', 'a', 't',
                                  'u', 'r', 'e', ':', 0};
 
+class VerifyContentSignatureTask : public CryptoTask {
+ public:
+  VerifyContentSignatureTask(const nsACString& aData,
+                             const nsACString& aCSHeader,
+                             const nsACString& aCertChain,
+                             const nsACString& aHostname,
+                             RefPtr<Promise>& aPromise)
+      : mData(aData),
+        mCSHeader(aCSHeader),
+        mCertChain(aCertChain),
+        mHostname(aHostname),
+        mSignatureVerified(false),
+        mPromise(aPromise) {}
+
+ private:
+  virtual nsresult CalculateResult() override;
+  virtual void CallCallback(nsresult rv) override;
+
+  nsCString mData;
+  nsCString mCSHeader;
+  nsCString mCertChain;
+  nsCString mHostname;
+  bool mSignatureVerified;
+  RefPtr<Promise> mPromise;
+};
+
 NS_IMETHODIMP
-ContentSignatureVerifier::VerifyContentSignature(const nsACString& aData,
-                                                 const nsACString& aCSHeader,
-                                                 const nsACString& aCertChain,
-                                                 const nsACString& aHostname,
-                                                 bool* _retval) {
-  NS_ENSURE_ARG(_retval);
-  *_retval = false;
+ContentSignatureVerifier::AsyncVerifyContentSignature(
+    const nsACString& aData, const nsACString& aCSHeader,
+    const nsACString& aCertChain, const nsACString& aHostname, JSContext* aCx,
+    Promise** aPromise) {
+  NS_ENSURE_ARG_POINTER(aCx);
+
+  nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+  if (NS_WARN_IF(!globalObject)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  ErrorResult result;
+  RefPtr<Promise> promise = Promise::Create(globalObject, result);
+  if (NS_WARN_IF(result.Failed())) {
+    return result.StealNSResult();
+  }
 
+  RefPtr<VerifyContentSignatureTask> task(new VerifyContentSignatureTask(
+      aData, aCSHeader, aCertChain, aHostname, promise));
+  nsresult rv = task->Dispatch("ContentSig");
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  promise.forget(aPromise);
+  return NS_OK;
+}
+
+static nsresult VerifyContentSignatureInternal(
+    const nsACString& aData, const nsACString& aCSHeader,
+    const nsACString& aCertChain, const nsACString& aHostname,
+    /* out */
+    mozilla::Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS&
+        aErrorLabel,
+    /* out */ nsACString& aCertFingerprint, /* out */ uint32_t& aErrorValue);
+static nsresult ParseContentSignatureHeader(
+    const nsACString& aContentSignatureHeader,
+    /* out */ nsCString& aSignature);
+
+nsresult VerifyContentSignatureTask::CalculateResult() {
   // 3 is the default, non-specific, "something failed" error.
   Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS errorLabel =
       Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err3;
   nsAutoCString certFingerprint;
   uint32_t errorValue = 3;
   nsresult rv =
-      VerifyContentSignatureInternal(aData, aCSHeader, aCertChain, aHostname,
+      VerifyContentSignatureInternal(mData, mCSHeader, mCertChain, mHostname,
                                      errorLabel, certFingerprint, errorValue);
   if (NS_FAILED(rv)) {
     CSVerifier_LOG(("CSVerifier: Signature verification failed"));
     if (certFingerprint.Length() > 0) {
       Telemetry::AccumulateCategoricalKeyed(certFingerprint, errorLabel);
     }
     Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, errorValue);
     if (rv == NS_ERROR_INVALID_SIGNATURE) {
       return NS_OK;
     }
     return rv;
   }
 
-  *_retval = true;
+  mSignatureVerified = true;
   Accumulate(Telemetry::CONTENT_SIGNATURE_VERIFICATION_STATUS, 0);
 
   return NS_OK;
 }
 
+void VerifyContentSignatureTask::CallCallback(nsresult rv) {
+  if (NS_FAILED(rv)) {
+    mPromise->MaybeReject(rv);
+  } else {
+    mPromise->MaybeResolve(mSignatureVerified);
+  }
+}
+
 bool IsNewLine(char16_t c) { return c == '\n' || c == '\r'; }
 
 nsresult ReadChainIntoCertList(const nsACString& aCertChain,
                                CERTCertList* aCertList) {
   bool inBlock = false;
   bool certFound = false;
 
   const nsCString header = NS_LITERAL_CSTRING("-----BEGIN CERTIFICATE-----");
@@ -133,17 +202,17 @@ nsresult ReadChainIntoCertList(const nsA
 
 // Given data to verify, a content signature header value, a string representing
 // a list of PEM-encoded certificates, and a hostname to validate the
 // certificates against, this function attempts to validate the certificate
 // chain, extract the signature from the header, and verify the data using the
 // key in the end-entity certificate from the chain. Returns NS_OK if everything
 // is satisfactory and a failing nsresult otherwise. The output parameters are
 // filled with telemetry data to report in the case of failures.
-nsresult ContentSignatureVerifier::VerifyContentSignatureInternal(
+static nsresult VerifyContentSignatureInternal(
     const nsACString& aData, const nsACString& aCSHeader,
     const nsACString& aCertChain, const nsACString& aHostname,
     /* out */
     Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS& aErrorLabel,
     /* out */ nsACString& aCertFingerprint,
     /* out */ uint32_t& aErrorValue) {
   UniqueCERTCertList certCertList(CERT_NewCertList());
   if (!certCertList) {
@@ -311,17 +380,17 @@ nsresult ContentSignatureVerifier::Verif
     aErrorLabel = Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS::err1;
     aErrorValue = 1;
     return NS_ERROR_INVALID_SIGNATURE;
   }
 
   return NS_OK;
 }
 
-nsresult ContentSignatureVerifier::ParseContentSignatureHeader(
+static nsresult ParseContentSignatureHeader(
     const nsACString& aContentSignatureHeader,
     /* out */ nsCString& aSignature) {
   // We only support p384 ecdsa.
   NS_NAMED_LITERAL_CSTRING(signature_var, "p384ecdsa");
 
   aSignature.Truncate();
 
   const nsCString& flatHeader = PromiseFlatCString(aContentSignatureHeader);
--- a/security/manager/ssl/ContentSignatureVerifier.h
+++ b/security/manager/ssl/ContentSignatureVerifier.h
@@ -22,22 +22,11 @@
 
 class ContentSignatureVerifier final : public nsIContentSignatureVerifier {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSICONTENTSIGNATUREVERIFIER
 
  private:
   ~ContentSignatureVerifier() = default;
-
-  nsresult VerifyContentSignatureInternal(
-      const nsACString& aData, const nsACString& aCSHeader,
-      const nsACString& aCertChain, const nsACString& aHostname,
-      /* out */
-      mozilla::Telemetry::LABELS_CONTENT_SIGNATURE_VERIFICATION_ERRORS&
-          aErrorLabel,
-      /* out */ nsACString& aCertFingerprint, /* out */ uint32_t& aErrorValue);
-  nsresult ParseContentSignatureHeader(
-      const nsACString& aContentSignatureHeader,
-      /* out */ nsCString& aSignature);
 };
 
 #endif  // ContentSignatureVerifier_h
--- a/security/manager/ssl/nsIContentSignatureVerifier.idl
+++ b/security/manager/ssl/nsIContentSignatureVerifier.idl
@@ -22,17 +22,18 @@ interface nsIContentSignatureVerifier : 
    *
    * @param aData                   The data to be tested.
    * @param aContentSignatureHeader The content-signature header,
    *                                url-safe base64 encoded.
    * @param aCertificateChain       The certificate chain to use for verification.
    *                                PEM encoded string.
    * @param aHostname               The hostname for which the end entity must
                                     be valid.
-   * @returns true if the signature matches the data and aCertificateChain is
-   *          valid within aContext, false if not.
+   * @returns Promise that resolves with the value true if the signature
+   *          matches the data and aCertificateChain is valid within aContext,
+   *          and false if not. Rejects if another error occurred.
    */
-  [must_use]
-  boolean verifyContentSignature(in ACString aData,
-                                 in ACString aContentSignatureHeader,
-                                 in ACString aCertificateChain,
-                                 in ACString aHostname);
+  [implicit_jscontext, must_use]
+  Promise asyncVerifyContentSignature(in ACString aData,
+                                      in ACString aContentSignatureHeader,
+                                      in ACString aCertificateChain,
+                                      in ACString aHostname);
 };
--- a/security/manager/ssl/tests/unit/test_content_signing.js
+++ b/security/manager/ssl/tests/unit/test_content_signing.js
@@ -58,17 +58,17 @@ function check_telemetry(expected_index,
     equal(VERIFICATION_HISTOGRAM.snapshot().values[i] || 0, expected_value,
       "count " + i + ": " + VERIFICATION_HISTOGRAM.snapshot().values[i] +
       " expected " + expected_value);
   }
   VERIFICATION_HISTOGRAM.clear();
   ERROR_HISTOGRAM.clear();
 }
 
-function run_test() {
+add_task(async function run_test() {
   // set up some data
   const DATA = readFile(do_get_file(TEST_DATA_DIR + "test.txt"));
   const GOOD_SIGNATURE = "p384ecdsa=" +
       readFile(do_get_file(TEST_DATA_DIR + "test.txt.signature"))
       .trim();
 
   const BAD_SIGNATURE = "p384ecdsa=WqRXFQ7tnlVufpg7A-ZavXvWd2Zln0o4woHBy26C2r" +
                         "UWM4GJke4pE8ecHiXoi-7KnZXty6Pe3s4o3yAIyKDP9jUC52Ek1G" +
@@ -92,123 +92,123 @@ function run_test() {
   let notValidYetOneCRLChain = loadChain(TEST_DATA_DIR + "content_signing",
                                          ["onecrl_ee_not_valid_yet", "int",
                                           "root"]);
 
   // Check signature verification works without error before the root is set
   VERIFICATION_HISTOGRAM.clear();
   let chain1 = oneCRLChain.join("\n");
   let verifier = getSignatureVerifier();
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME),
      "Before the root is set, signatures should fail to verify but not throw.");
   // Check for generic chain building error.
   check_telemetry(6, 1, getCertHash("content_signing_onecrl_ee"));
 
   setRoot(TEST_DATA_DIR + "content_signing_root.pem");
 
   // Check good signatures from good certificates with the correct SAN
-  ok(verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME),
+  ok(await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME),
      "A OneCRL signature should verify with the OneCRL chain");
   let chain2 = remoteNewTabChain.join("\n");
-  ok(verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain2,
-                                     ABOUT_NEWTAB_NAME),
+  ok(await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chain2,
+                                                ABOUT_NEWTAB_NAME),
      "A newtab signature should verify with the newtab chain");
   // Check for valid signature
   check_telemetry(0, 2, getCertHash("content_signing_remote_newtab_ee"));
 
   // Check a bad signature when a good chain is provided
   chain1 = oneCRLChain.join("\n");
-  ok(!verifier.verifyContentSignature(DATA, BAD_SIGNATURE, chain1, ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, BAD_SIGNATURE, chain1, ONECRL_NAME),
      "A bad signature should not verify");
   // Check for invalid signature
   check_telemetry(1, 1, getCertHash("content_signing_onecrl_ee"));
 
   // Check a good signature from cert with good SAN but a different key than the
   // one used to create the signature
   let badKeyChain = oneCRLBadKeyChain.join("\n");
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, badKeyChain,
-                                      ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, badKeyChain,
+                                                 ONECRL_NAME),
      "A signature should not verify if the signing key is wrong");
   // Check for wrong key in cert.
   check_telemetry(9, 1, getCertHash("content_signing_onecrl_wrong_key_ee"));
 
   // Check a good signature from cert with good SAN but a different key than the
   // one used to create the signature (this time, an RSA key)
   let rsaKeyChain = oneCRLBadKeyChain.join("\n");
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, rsaKeyChain,
-                                      ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, rsaKeyChain,
+                                                 ONECRL_NAME),
      "A signature should not verify if the signing key is wrong (RSA)");
   // Check for wrong key in cert.
   check_telemetry(9, 1, getCertHash("content_signing_onecrl_wrong_key_ee"));
 
   // Check a good signature from cert with good SAN but with chain missing root
   let missingRoot = [oneCRLChain[0], oneCRLChain[1]].join("\n");
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, missingRoot,
-                                      ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, missingRoot,
+                                                 ONECRL_NAME),
      "A signature should not verify if the chain is incomplete (missing root)");
   // Check for generic chain building error.
   check_telemetry(6, 1, getCertHash("content_signing_onecrl_ee"));
 
   // Check a good signature from cert with good SAN but with no path to root
   let missingInt = [oneCRLChain[0], oneCRLChain[2]].join("\n");
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, missingInt,
-                                      ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, missingInt,
+                                                 ONECRL_NAME),
      "A signature should not verify if the chain is incomplete (missing int)");
   // Check for generic chain building error.
   check_telemetry(6, 1, getCertHash("content_signing_onecrl_ee"));
 
   // Check good signatures from good certificates with the wrong SANs
   chain1 = oneCRLChain.join("\n");
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
-                                      ABOUT_NEWTAB_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
+                                                 ABOUT_NEWTAB_NAME),
      "A OneCRL signature should not verify if we require the newtab SAN");
   // Check for invalid EE cert.
   check_telemetry(7, 1, getCertHash("content_signing_onecrl_ee"));
 
   chain2 = remoteNewTabChain.join("\n");
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain2,
-                                      ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chain2,
+                                                 ONECRL_NAME),
      "A newtab signature should not verify if we require the OneCRL SAN");
   // Check for invalid EE cert.
   check_telemetry(7, 1, getCertHash("content_signing_remote_newtab_ee"));
 
   // Check good signatures with good chains with some other invalid names
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ""),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chain1, ""),
      "A signature should not verify if the SANs do not match an empty name");
   // Check for invalid EE cert.
   check_telemetry(7, 1, getCertHash("content_signing_onecrl_ee"));
 
   // Test expired certificate.
   let chainExpired = expiredOneCRLChain.join("\n");
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chainExpired, ""),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chainExpired, ""),
      "A signature should not verify if the signing certificate is expired");
   // Check for expired cert.
   check_telemetry(4, 1, getCertHash("content_signing_onecrl_ee_expired"));
 
   // Test not valid yet certificate.
   let chainNotValidYet = notValidYetOneCRLChain.join("\n");
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chainNotValidYet, ""),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chainNotValidYet, ""),
      "A signature should not verify if the signing certificate is not valid yet");
   // Check for not yet valid cert.
   check_telemetry(5, 1, getCertHash("content_signing_onecrl_ee_not_valid_yet"));
 
   let relatedName = "subdomain." + ONECRL_NAME;
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
-                                      relatedName),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
+                                                 relatedName),
      "A signature should not verify if the SANs do not match a related name");
 
   let randomName = "\xb1\x9bU\x1c\xae\xaa3\x19H\xdb\xed\xa1\xa1\xe0\x81\xfb" +
                    "\xb2\x8f\x1cP\xe5\x8b\x9c\xc2s\xd3\x1f\x8e\xbbN";
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1, randomName),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chain1, randomName),
      "A signature should not verify if the SANs do not match a random name");
 
   // check good signatures with chains that have strange or missing SANs
   chain1 = noSANChain.join("\n");
-  ok(!verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
-                                      ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, chain1,
+                                                 ONECRL_NAME),
      "A signature should not verify if the SANs do not match a supplied name");
 
   // Check malformed signature data
   chain1 = oneCRLChain.join("\n");
   let bad_signatures = [
     // wrong length
     "p384ecdsa=WqRXFQ7tnlVufpg7A-ZavXvWd2Zln0o4woHBy26C2rUWM4GJke4pE8ecHiXoi-" +
     "7KnZXty6Pe3s4o3yAIyKDP9jUC52Ek1Gq25j_X703nP5rk5gM1qz5Fe-qCWakPPl6L==",
@@ -221,19 +221,18 @@ function run_test() {
     // actually sha256 with RSA
     "p384ecdsa=XS_jiQsS5qlzQyUKaA1nAnQn_OvxhvDfKybflB8Xe5gNH1wNmPGK1qN-jpeTfK" +
     "6ob3l3gCTXrsMnOXMeht0kPP3wLfVgXbuuO135pQnsv0c-ltRMWLe56Cm4S4Z6E7WWKLPWaj" +
     "jhAcG5dZxjffP9g7tuPP4lTUJztyc4d1z_zQZakEG7R0vN7P5_CaX9MiMzP4R7nC3H4Ba6yi" +
     "yjlGvsZwJ_C5zDQzWWs95czUbMzbDScEZ_7AWnidw91jZn-fUK3xLb6m-Zb_b4GAqZ-vnXIf" +
     "LpLB1Nzal42BQZn7i4rhAldYdcVvy7rOMlsTUb5Zz6vpVW9LCT9lMJ7Sq1xbU-0g==",
     ];
   for (let badSig of bad_signatures) {
-    throws(() => {
-      verifier.verifyContentSignature(DATA, badSig, chain1, ONECRL_NAME);
-    }, /NS_ERROR/, `Bad or malformed signature "${badSig}" should be rejected`);
+    await Assert.rejects(verifier.asyncVerifyContentSignature(DATA, badSig, chain1, ONECRL_NAME),
+                         /NS_ERROR/, `Bad or malformed signature "${badSig}" should be rejected`);
   }
 
   // Check malformed and missing certificate chain data
   let chainSuffix = [oneCRLChain[1], oneCRLChain[2]].join("\n");
   let badChains = [
     // no data
     "",
     // completely wrong data
@@ -254,22 +253,25 @@ function run_test() {
   for (let badSection of badSections) {
     // ensure we test each bad section on its own...
     badChains.push(badSection);
     // ... and as part of a chain with good certificates
     badChains.push(badSection + "\n" + chainSuffix);
   }
 
   for (let badChain of badChains) {
-    throws(() => {
-      verifier.verifyContentSignature(DATA, GOOD_SIGNATURE, badChain,
-                                      ONECRL_NAME);
-    }, /NS_ERROR/, `Bad chain data starting "${badChain.substring(0, 80)}" ` +
-                   "should be rejected");
+    await Assert.rejects(verifier.asyncVerifyContentSignature(DATA, GOOD_SIGNATURE, badChain,
+                                                              ONECRL_NAME),
+                         /NS_ERROR/,
+                         `Bad chain data starting "${badChain.substring(0, 80)}" ` +
+                         "should be rejected");
   }
 
-  ok(!verifier.verifyContentSignature(DATA + "appended data", GOOD_SIGNATURE, chain1, ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA + "appended data", GOOD_SIGNATURE, chain1,
+                                                 ONECRL_NAME),
      "A good signature should not verify if the data is tampered with (append)");
-  ok(!verifier.verifyContentSignature("prefixed data" + DATA, GOOD_SIGNATURE, chain1, ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature("prefixed data" + DATA, GOOD_SIGNATURE, chain1,
+                                                 ONECRL_NAME),
      "A good signature should not verify if the data is tampered with (prefix)");
-  ok(!verifier.verifyContentSignature(DATA.replace(/e/g, "i"), GOOD_SIGNATURE, chain1, ONECRL_NAME),
+  ok(!await verifier.asyncVerifyContentSignature(DATA.replace(/e/g, "i"), GOOD_SIGNATURE, chain1,
+                                                 ONECRL_NAME),
      "A good signature should not verify if the data is tampered with (modify)");
-}
+});
--- a/services/common/tests/unit/test_blocklist_signatures.js
+++ b/services/common/tests/unit/test_blocklist_signatures.js
@@ -131,26 +131,24 @@ add_task(async function test_check_signa
   // First, perform a signature verification with known data and signature
   // to ensure things are working correctly
   let verifier = Cc["@mozilla.org/security/contentsignatureverifier;1"]
                    .createInstance(Ci.nsIContentSignatureVerifier);
 
   const emptyData = "[]";
   const emptySignature = "p384ecdsa=zbugm2FDitsHwk5-IWsas1PpWwY29f0Fg5ZHeqD8fzep7AVl2vfcaHA7LdmCZ28qZLOioGKvco3qT117Q4-HlqFTJM7COHzxGyU2MMJ0ZTnhJrPOC1fP3cVQjU1PTWi9";
   const name = "onecrl.content-signature.mozilla.org";
-  ok(verifier.verifyContentSignature(emptyData, emptySignature,
-                                     getCertChain(), name));
-
-  verifier = Cc["@mozilla.org/security/contentsignatureverifier;1"]
-               .createInstance(Ci.nsIContentSignatureVerifier);
+  ok(await verifier.asyncVerifyContentSignature(emptyData, emptySignature,
+                                                getCertChain(), name));
 
   const collectionData = '[{"details":{"bug":"https://bugzilla.mozilla.org/show_bug.cgi?id=1155145","created":"2016-01-18T14:43:37Z","name":"GlobalSign certs","who":".","why":"."},"enabled":true,"id":"97fbf7c4-3ef2-f54f-0029-1ba6540c63ea","issuerName":"MHExKDAmBgNVBAMTH0dsb2JhbFNpZ24gUm9vdFNpZ24gUGFydG5lcnMgQ0ExHTAbBgNVBAsTFFJvb3RTaWduIFBhcnRuZXJzIENBMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMQswCQYDVQQGEwJCRQ==","last_modified":2000,"serialNumber":"BAAAAAABA/A35EU="},{"details":{"bug":"https://bugzilla.mozilla.org/show_bug.cgi?id=1155145","created":"2016-01-18T14:48:11Z","name":"GlobalSign certs","who":".","why":"."},"enabled":true,"id":"e3bd531e-1ee4-7407-27ce-6fdc9cecbbdc","issuerName":"MIGBMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTElMCMGA1UECxMcUHJpbWFyeSBPYmplY3QgUHVibGlzaGluZyBDQTEwMC4GA1UEAxMnR2xvYmFsU2lnbiBQcmltYXJ5IE9iamVjdCBQdWJsaXNoaW5nIENB","last_modified":3000,"serialNumber":"BAAAAAABI54PryQ="}]';
   const collectionSignature = "p384ecdsa=f4pA2tYM5jQgWY6YUmhUwQiBLj6QO5sHLD_5MqLePz95qv-7cNCuQoZnPQwxoptDtW8hcWH3kLb0quR7SB-r82gkpR9POVofsnWJRA-ETb0BcIz6VvI3pDT49ZLlNg3p";
 
-  ok(verifier.verifyContentSignature(collectionData, collectionSignature, getCertChain(), name));
+  ok(await verifier.asyncVerifyContentSignature(collectionData, collectionSignature,
+                                                getCertChain(), name));
 
   // set up prefs so the kinto updater talks to the test server
   Services.prefs.setCharPref(PREF_SETTINGS_SERVER,
     `http://localhost:${server.identity.primaryPort}/v1`);
 
   // These are records we'll use in the test collections
   const RECORD1 = {
     details: {
--- a/services/settings/RemoteSettingsClient.jsm
+++ b/services/settings/RemoteSettingsClient.jsm
@@ -448,20 +448,20 @@ class RemoteSettingsClient extends Event
       localRecords = data.map(r => kintoCollection.cleanLocalFields(r));
     }
 
     const serialized = await RemoteSettingsWorker.canonicalStringify(localRecords,
                                                                      remoteRecords,
                                                                      timestamp);
     const verifier = Cc["@mozilla.org/security/contentsignatureverifier;1"]
       .createInstance(Ci.nsIContentSignatureVerifier);
-    if (!verifier.verifyContentSignature(serialized,
-                                         "p384ecdsa=" + signature,
-                                         certChain,
-                                         this.signerName)) {
+    if (!await verifier.asyncVerifyContentSignature(serialized,
+                                                    "p384ecdsa=" + signature,
+                                                    certChain,
+                                                    this.signerName)) {
       throw new RemoteSettingsClient.InvalidSignatureError(`${bucket}/${collection}`);
     }
   }
 
   /**
    * Fetch the whole list of records from the server, verify the signature again
    * and then compute a synchronization result as if the diff-based sync happened.
    * And eventually, wipe out the local data.
--- a/toolkit/components/normandy/lib/NormandyApi.jsm
+++ b/toolkit/components/normandy/lib/NormandyApi.jsm
@@ -129,17 +129,17 @@ var NormandyApi = {
 
     const serialized = typeof data == "string" ? data : CanonicalJSON.stringify(data);
 
     const verifier = Cc["@mozilla.org/security/contentsignatureverifier;1"]
       .createInstance(Ci.nsIContentSignatureVerifier);
 
     let valid;
     try {
-      valid = verifier.verifyContentSignature(
+      valid = await verifier.asyncVerifyContentSignature(
         serialized,
         builtSignature,
         certChain,
         "normandy.content-signature.mozilla.org"
       );
     } catch (err) {
       throw new NormandyApi.InvalidSignatureError(`${type} signature validation failed: ${err}`);
     }