Bug 1518300 - Refactor CryptoUtils and add JWK/JWE methods to jwcrypto. r=rfkelly,tjr
authorEdouard Oger <eoger@fastmail.com>
Fri, 18 Jan 2019 20:58:42 +0000
changeset 514460 7befa4aa3c7f8d3e153845363b5e19a99bfe16ef
parent 514459 db283d60bcf1b16b3812cdd57a88e1eada225f81
child 514461 5433228bc01803d5040ea8c6d51465490c76f0ec
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrfkelly, tjr
bugs1518300
milestone66.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 1518300 - Refactor CryptoUtils and add JWK/JWE methods to jwcrypto. r=rfkelly,tjr Differential Revision: https://phabricator.services.mozilla.com/D15868
services/common/hawkrequest.js
services/common/rest.js
services/common/tests/unit/test_hawkclient.js
services/common/tests/unit/test_hawkrequest.js
services/common/tests/unit/test_tokenauthenticatedrequest.js
services/common/utils.js
services/crypto/modules/jwcrypto.jsm
services/crypto/modules/utils.js
services/crypto/tests/unit/test_jwcrypto.js
services/crypto/tests/unit/test_utils_hawk.js
services/crypto/tests/unit/test_utils_hkdfExpand.js
services/crypto/tests/unit/test_utils_httpmac.js
services/crypto/tests/unit/test_utils_pbkdf2.js
services/crypto/tests/unit/test_utils_sha1.js
services/crypto/tests/unit/xpcshell.ini
services/fxaccounts/Credentials.jsm
services/fxaccounts/FxAccounts.jsm
services/fxaccounts/FxAccountsClient.jsm
services/fxaccounts/tests/xpcshell/test_accounts.js
services/fxaccounts/tests/xpcshell/test_credentials.js
services/sync/modules/browserid_identity.js
services/sync/modules/util.js
services/sync/tests/unit/test_keys.js
toolkit/components/extensions/ExtensionStorageSync.jsm
toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_crypto.js
--- a/services/common/hawkrequest.js
+++ b/services/common/hawkrequest.js
@@ -74,17 +74,17 @@ HAWKAuthenticatedRESTRequest.prototype =
     if (this.credentials) {
       let options = {
         now: this.now,
         localtimeOffsetMsec: this.localtimeOffsetMsec,
         credentials: this.credentials,
         payload: data && JSON.stringify(data) || "",
         contentType,
       };
-      let header = CryptoUtils.computeHAWK(this.uri, method, options);
+      let header = await CryptoUtils.computeHAWK(this.uri, method, options);
       this.setHeader("Authorization", header.field);
     }
 
     for (let header in this.extraHeaders) {
       this.setHeader(header, this.extraHeaders[header]);
     }
 
     this.setHeader("Content-Type", contentType);
@@ -108,32 +108,27 @@ HAWKAuthenticatedRESTRequest.prototype =
   *        A context for the credentials. A protocol version will be prepended
   *        to the context, see Credentials.keyWord for more information.
   * @param size
   *        The size in bytes of the expected derived buffer,
   *        defaults to 3 * 32.
   * @return credentials
   *        Returns an object:
   *        {
-  *          algorithm: sha256
   *          id: the Hawk id (from the first 32 bytes derived)
   *          key: the Hawk key (from bytes 32 to 64)
   *          extra: size - 64 extra bytes (if size > 64)
   *        }
   */
-function deriveHawkCredentials(tokenHex,
-                                                            context,
-                                                            size = 96,
-                                                            hexKey = false) {
+async function deriveHawkCredentials(tokenHex, context, size = 96) {
   let token = CommonUtils.hexToBytes(tokenHex);
-  let out = CryptoUtils.hkdf(token, undefined, Credentials.keyWord(context), size);
+  let out = await CryptoUtils.hkdfLegacy(token, undefined, Credentials.keyWord(context), size);
 
   let result = {
-    algorithm: "sha256",
-    key: hexKey ? CommonUtils.bytesAsHex(out.slice(32, 64)) : out.slice(32, 64),
+    key: out.slice(32, 64),
     id: CommonUtils.bytesAsHex(out.slice(0, 32)),
   };
   if (size > 64) {
     result.extra = out.slice(64);
   }
 
   return result;
 }
--- a/services/common/rest.js
+++ b/services/common/rest.js
@@ -653,17 +653,17 @@ function TokenAuthenticatedRESTRequest(u
   RESTRequest.call(this, uri);
   this.authToken = authToken;
   this.extra = extra || {};
 }
 TokenAuthenticatedRESTRequest.prototype = {
   __proto__: RESTRequest.prototype,
 
   async dispatch(method, data) {
-    let sig = CryptoUtils.computeHTTPMACSHA1(
+    let sig = await CryptoUtils.computeHTTPMACSHA1(
       this.authToken.id, this.authToken.key, method, this.uri, this.extra
     );
 
     this.setHeader("Authorization", sig.getHeader());
 
     return super.dispatch(method, data);
   },
 };
--- a/services/common/tests/unit/test_hawkclient.js
+++ b/services/common/tests/unit/test_hawkclient.js
@@ -464,26 +464,16 @@ add_task(async function test_401_then_50
   } catch (err) {
     Assert.equal(err.code, 500);
   }
   Assert.equal(attempts, 2);
 
   await promiseStopServer(server);
 });
 
-add_task(async function throw_if_not_json_body() {
-  let client = new HawkClient("https://example.com");
-  try {
-    await client.request("/bogus", "GET", {}, "I am not json");
-    do_throw("Expected an error");
-  } catch (err) {
-    Assert.ok(!!err.message);
-  }
-});
-
 // End of tests.
 // Utility functions follow
 
 function getTimestampDelta(authHeader, now = Date.now()) {
   let tsMS = new Date(
       parseInt(/ts="(\d+)"/.exec(authHeader)[1], 10) * SECOND_MS);
   return Math.abs(tsMS - now);
 }
--- a/services/common/tests/unit/test_hawkrequest.js
+++ b/services/common/tests/unit/test_hawkrequest.js
@@ -192,21 +192,18 @@ add_task(async function test_hawk_langua
   let response = await request.post({});
 
   Assert.equal(200, response.status);
   Services.prefs.resetUserPrefs();
 
   await promiseStopServer(server);
 });
 
-add_task(function test_deriveHawkCredentials() {
-  let credentials = deriveHawkCredentials(
-    SESSION_KEYS.sessionToken, "sessionToken");
-
-  Assert.equal(credentials.algorithm, "sha256");
+add_task(async function test_deriveHawkCredentials() {
+  let credentials = await deriveHawkCredentials(SESSION_KEYS.sessionToken, "sessionToken");
   Assert.equal(credentials.id, SESSION_KEYS.tokenID);
   Assert.equal(CommonUtils.bytesAsHex(credentials.key), SESSION_KEYS.reqHMACkey);
 });
 
 // turn formatted test vectors into normal hex strings
 function h(hexStr) {
   return hexStr.replace(/\s+/g, "");
 }
--- a/services/common/tests/unit/test_tokenauthenticatedrequest.js
+++ b/services/common/tests/unit/test_tokenauthenticatedrequest.js
@@ -17,32 +17,32 @@ add_task(async function test_authenticat
   let message = "Great Success!";
 
   // TODO: We use a preset key here, but use getTokenFromBrowserIDAssertion()
   // from TokenServerClient to get a real one when possible. (Bug 745800)
   let id = "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x";
   let key = "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=";
   let method = "GET";
 
-  let nonce = btoa(CryptoUtils.generateRandomBytes(16));
+  let nonce = btoa(CryptoUtils.generateRandomBytesLegacy(16));
   let ts = Math.floor(Date.now() / 1000);
   let extra = {ts, nonce};
 
   let auth;
 
   let server = httpd_setup({"/foo": function(request, response) {
       Assert.ok(request.hasHeader("Authorization"));
       Assert.equal(auth, request.getHeader("Authorization"));
 
       response.setStatusLine(request.httpVersion, 200, "OK");
       response.bodyOutputStream.write(message, message.length);
     },
   });
   let uri = CommonUtils.makeURI(server.baseURI + "/foo");
-  let sig = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri, extra);
+  let sig = await CryptoUtils.computeHTTPMACSHA1(id, key, method, uri, extra);
   auth = sig.getHeader();
 
   let req = new TokenAuthenticatedRESTRequest(uri, {id, key}, extra);
   await req.get();
 
   Assert.equal(message, req.response.body);
 
   await promiseStopServer(server);
--- a/services/common/utils.js
+++ b/services/common/utils.js
@@ -192,16 +192,38 @@ var CommonUtils = {
   byteArrayToString: function byteArrayToString(bytes) {
     return bytes.map(byte => String.fromCharCode(byte)).join("");
   },
 
   stringToByteArray: function stringToByteArray(bytesString) {
     return Array.prototype.slice.call(bytesString).map(c => c.charCodeAt(0));
   },
 
+  // A lot of Util methods work with byte strings instead of ArrayBuffers.
+  // A patch should address this problem, but in the meantime let's provide
+  // helpers method to convert byte strings to Uint8Array.
+  byteStringToArrayBuffer(byteString) {
+    if (byteString === undefined) {
+      return new Uint8Array();
+    }
+    const bytes = new Uint8Array(byteString.length);
+    for (let i = 0; i < byteString.length; ++i) {
+      bytes[i] = byteString.charCodeAt(i) & 0xff;
+    }
+    return bytes;
+  },
+
+  arrayBufferToByteString(buffer) {
+    return CommonUtils.byteArrayToString([...buffer]);
+  },
+
+  bufferToHex(buffer) {
+    return Array.prototype.map.call(buffer, (x) => ("00" + x.toString(16)).slice(-2)).join("");
+  },
+
   bytesAsHex: function bytesAsHex(bytes) {
     let s = "";
     for (let i = 0, len = bytes.length; i < len; i++) {
       let c = (bytes[i].charCodeAt(0) & 0xff).toString(16);
       if (c.length == 1) {
         c = "0" + c;
       }
       s += c;
@@ -220,16 +242,21 @@ var CommonUtils = {
   hexToBytes: function hexToBytes(str) {
     let bytes = [];
     for (let i = 0; i < str.length - 1; i += 2) {
       bytes.push(parseInt(str.substr(i, 2), 16));
     }
     return String.fromCharCode.apply(String, bytes);
   },
 
+  hexToArrayBuffer(str) {
+    const octString = CommonUtils.hexToBytes(str);
+    return CommonUtils.byteStringToArrayBuffer(octString);
+  },
+
   hexAsString: function hexAsString(hex) {
     return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex));
   },
 
   /**
    * Base32 encode (RFC 4648) a string
    */
   encodeBase32: function encodeBase32(bytes) {
--- a/services/crypto/modules/jwcrypto.jsm
+++ b/services/crypto/modules/jwcrypto.jsm
@@ -1,137 +1,205 @@
-/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
-/* vim: set ft=javascript 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/. */
 
 "use strict";
 
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/Log.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "IdentityCryptoService",
                                    "@mozilla.org/identity/crypto-service;1",
                                    "nsIIdentityCryptoService");
+XPCOMUtils.defineLazyGlobalGetters(this, ["crypto"]);
 
-var EXPORTED_SYMBOLS = ["jwcrypto"];
+const EXPORTED_SYMBOLS = ["jwcrypto"];
 
 const PREF_LOG_LEVEL = "services.crypto.jwcrypto.log.level";
-
 XPCOMUtils.defineLazyGetter(this, "log", function() {
-  let log = Log.repository.getLogger("Services.Crypto.jwcrypto");
+  const log = Log.repository.getLogger("Services.Crypto.jwcrypto");
   // Default log level is "Error", but consumers can change this with the pref
   // "services.crypto.jwcrypto.log.level".
   log.level = Log.Level.Error;
-  let appender = new Log.DumpAppender();
+  const appender = new Log.DumpAppender();
   log.addAppender(appender);
   try {
-    let level =
+    const level =
       Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
       && Services.prefs.getCharPref(PREF_LOG_LEVEL);
     log.level = Log.Level[level] || Log.Level.Error;
   } catch (e) {
     log.error(e);
   }
 
   return log;
 });
 
-const ALGORITHMS = { RS256: "RS256", DS160: "DS160" };
-const DURATION_MS = 1000 * 60 * 2; // 2 minutes default assertion lifetime
-
-function generateKeyPair(aAlgorithmName, aCallback) {
-  log.debug("Generate key pair; alg = " + aAlgorithmName);
-
-  IdentityCryptoService.generateKeyPair(aAlgorithmName, function(rv, aKeyPair) {
-    if (!Components.isSuccessCode(rv)) {
-      return aCallback("key generation failed");
-    }
-
-    var publicKey;
+const ASSERTION_DEFAULT_DURATION_MS = 1000 * 60 * 2; // 2 minutes default assertion lifetime
+const ECDH_PARAMS = {
+  name: "ECDH",
+  namedCurve: "P-256",
+};
+const AES_PARAMS = {
+  name: "AES-GCM",
+  length: 256,
+};
+const AES_TAG_LEN = 128;
+const AES_GCM_IV_SIZE = 12;
+const UTF8_ENCODER = new TextEncoder();
+const UTF8_DECODER = new TextDecoder();
 
-    switch (aKeyPair.keyType) {
-     case ALGORITHMS.RS256:
-      publicKey = {
-        algorithm: "RS",
-        exponent:  aKeyPair.hexRSAPublicKeyExponent,
-        modulus:   aKeyPair.hexRSAPublicKeyModulus,
-      };
-      break;
-
-     case ALGORITHMS.DS160:
-      publicKey = {
-        algorithm: "DS",
-        y: aKeyPair.hexDSAPublicValue,
-        p: aKeyPair.hexDSAPrime,
-        q: aKeyPair.hexDSASubPrime,
-        g: aKeyPair.hexDSAGenerator,
-      };
-      break;
-
-    default:
-      return aCallback("unknown key type");
-    }
-
-    let keyWrapper = {
-      serializedPublicKey: JSON.stringify(publicKey),
-      _kp: aKeyPair,
-    };
+class JWCrypto {
+  /**
+   * Encrypts the given data into a JWE using AES-256-GCM content encryption.
+   *
+   * This function implements a very small subset of the JWE encryption standard
+   * from https://tools.ietf.org/html/rfc7516. The only supported content encryption
+   * algorithm is enc="A256GCM" [1] and the only supported key encryption algorithm
+   * is alg="ECDH-ES" [2].
+   * The IV is generated randomly: if you are using long-lived keys you might be
+   * exposing yourself to a birthday attack. Please consult your nearest cryptographer.
+   *
+   * @param {Object} key Peer Public JWK.
+   * @param {ArrayBuffer} data
+   *
+   * [1] https://tools.ietf.org/html/rfc7518#section-5.3
+   * [2] https://tools.ietf.org/html/rfc7518#section-4.6
+   *
+   * @returns {Promise<String>}
+   */
+  async generateJWE(key, data) {
+    // Generate an ephemeral key to use just for this encryption.
+    const epk = await crypto.subtle.generateKey(ECDH_PARAMS, true, ["deriveKey"]);
+    const peerPublicKey = await crypto.subtle.importKey("jwk", key, ECDH_PARAMS, false, ["deriveKey"]);
+    return this._generateJWE(epk, peerPublicKey, data);
+  }
 
-    return aCallback(null, keyWrapper);
-  });
-}
-
-function sign(aPayload, aKeypair, aCallback) {
-  aKeypair._kp.sign(aPayload, function(rv, signature) {
-    if (!Components.isSuccessCode(rv)) {
-      log.error("signer.sign failed");
-      return aCallback("Sign failed");
-    }
-    log.debug("signer.sign: success");
-    return aCallback(null, signature);
-  });
-}
-
-function jwcryptoClass() {
-}
+  async _generateJWE(epk, peerPublicKey, data) {
+    let iv = crypto.getRandomValues(new Uint8Array(AES_GCM_IV_SIZE));
+    const ownPublicJWK = await crypto.subtle.exportKey("jwk", epk.publicKey);
+    delete ownPublicJWK.key_ops;
+    // Do ECDH agreement to get the content encryption key.
+    const contentKey = await deriveECDHSharedAESKey(epk.privateKey, peerPublicKey, ["encrypt"]);
+    let header = {alg: "ECDH-ES", enc: "A256GCM", epk: ownPublicJWK};
+    // Yes, additionalData is the byte representation of the base64 representation of the stringified header.
+    const additionalData = UTF8_ENCODER.encode(ChromeUtils.base64URLEncode(UTF8_ENCODER.encode(JSON.stringify(header)), {pad: false}));
+    const encrypted = await crypto.subtle.encrypt({
+        name: "AES-GCM",
+        iv,
+        additionalData,
+        tagLength: AES_TAG_LEN,
+      },
+      contentKey,
+      data,
+    );
+    const tagIdx = encrypted.byteLength - ((AES_TAG_LEN + 7) >> 3);
+    let ciphertext = encrypted.slice(0, tagIdx);
+    let tag = encrypted.slice(tagIdx);
+    // JWE serialization.
+    header = UTF8_ENCODER.encode(JSON.stringify(header));
+    header = ChromeUtils.base64URLEncode(header, {pad: false});
+    tag = ChromeUtils.base64URLEncode(tag, {pad: false});
+    ciphertext = ChromeUtils.base64URLEncode(ciphertext, {pad: false});
+    iv = ChromeUtils.base64URLEncode(iv, {pad: false});
+    return `${header}..${iv}.${ciphertext}.${tag}`; // No CEK
+  }
 
-jwcryptoClass.prototype = {
-  /*
-   * Determine the expiration of the assertion.  Returns expiry date
-   * in milliseconds as integer.
+  /**
+   * Decrypts the given JWE using AES-256-GCM content encryption into a byte array.
+   * This function does the opposite of `JWCrypto.generateJWE`.
+   * The only supported content encryption algorithm is enc="A256GCM" [1]
+   * and the only supported key encryption algorithm is alg="ECDH-ES" [2].
    *
-   * @param localtimeOffsetMsec (optional)
-   *        The number of milliseconds that must be added to the local clock
-   *        for it to agree with the server.  For example, if the local clock
-   *        if two minutes fast, localtimeOffsetMsec would be -120000
+   * @param {"ECDH-ES"} algorithm
+   * @param {CryptoKey} key Local private key
+   *
+   * [1] https://tools.ietf.org/html/rfc7518#section-5.3
+   * [2] https://tools.ietf.org/html/rfc7518#section-4.6
    *
-   * @param now (options)
-   *        Current date in milliseconds.  Useful for mocking clock
-   *        skew in testing.
+   * @returns {Promise<Uint8Array>}
    */
-  getExpiration(duration = DURATION_MS, localtimeOffsetMsec = 0, now = Date.now()) {
-    return now + localtimeOffsetMsec + duration;
-  },
+  async decryptJWE(jwe, key) {
+    let [header, cek, iv, ciphertext, authTag] = jwe.split(".");
+    const additionalData = UTF8_ENCODER.encode(header);
+    header = JSON.parse(UTF8_DECODER.decode(ChromeUtils.base64URLDecode(header, {padding: "reject"})));
+    if (cek.length > 0 || header.enc !== "A256GCM" || header.alg !== "ECDH-ES") {
+      throw new Error("Unknown algorithm.");
+    }
+    if ("apu" in header || "apv" in header) {
+      throw new Error("apu and apv header values are not supported.");
+    }
+    const peerPublicKey = await crypto.subtle.importKey("jwk", header.epk, ECDH_PARAMS, false, ["deriveKey"]);
+    // Do ECDH agreement to get the content encryption key.
+    const contentKey = await deriveECDHSharedAESKey(key, peerPublicKey, ["decrypt"]);
+    iv = ChromeUtils.base64URLDecode(iv, {padding: "reject"});
+    ciphertext = new Uint8Array(ChromeUtils.base64URLDecode(ciphertext, {padding: "reject"}));
+    authTag = new Uint8Array(ChromeUtils.base64URLDecode(authTag, {padding: "reject"}));
+    const bundle = new Uint8Array([...ciphertext, ...authTag]);
 
-  isCertValid(aCert, aCallback) {
-    // XXX check expiration, bug 769850
-    aCallback(true);
-  },
+    const decrypted = await crypto.subtle.decrypt({
+        name: "AES-GCM",
+        iv,
+        tagLength: AES_TAG_LEN,
+        additionalData,
+      },
+      contentKey,
+      bundle
+    );
+    return new Uint8Array(decrypted);
+  }
 
   generateKeyPair(aAlgorithmName, aCallback) {
     log.debug("generating");
-    generateKeyPair(aAlgorithmName, aCallback);
-  },
+    log.debug("Generate key pair; alg = " + aAlgorithmName);
+
+    IdentityCryptoService.generateKeyPair(aAlgorithmName, (rv, aKeyPair) => {
+      if (!Components.isSuccessCode(rv)) {
+        return aCallback("key generation failed");
+      }
+
+      let publicKey;
+
+      switch (aKeyPair.keyType) {
+       case "RS256":
+        publicKey = {
+          algorithm: "RS",
+          exponent:  aKeyPair.hexRSAPublicKeyExponent,
+          modulus:   aKeyPair.hexRSAPublicKeyModulus,
+        };
+        break;
 
-  /*
+       case "DS160":
+        publicKey = {
+          algorithm: "DS",
+          y: aKeyPair.hexDSAPublicValue,
+          p: aKeyPair.hexDSAPrime,
+          q: aKeyPair.hexDSASubPrime,
+          g: aKeyPair.hexDSAGenerator,
+        };
+        break;
+
+      default:
+        return aCallback("unknown key type");
+      }
+
+      const keyWrapper = {
+        serializedPublicKey: JSON.stringify(publicKey),
+        _kp: aKeyPair,
+      };
+
+      return aCallback(null, keyWrapper);
+    });
+  }
+
+  /**
    * Generate an assertion and return it through the provided callback.
    *
    * @param aCert
    *        Identity certificate
    *
    * @param aKeyPair
    *        KeyPair object
    *
@@ -158,34 +226,73 @@ jwcryptoClass.prototype = {
   generateAssertion(aCert, aKeyPair, aAudience, aOptions, aCallback) {
     if (typeof aOptions == "function") {
       aCallback = aOptions;
       aOptions = { };
     }
 
     // for now, we hack the algorithm name
     // XXX bug 769851
-    var header = {"alg": "DS128"};
-    var headerBytes = IdentityCryptoService.base64UrlEncode(
+    const header = {"alg": "DS128"};
+    const headerBytes = IdentityCryptoService.base64UrlEncode(
                           JSON.stringify(header));
 
-    var payload = {
-      exp: this.getExpiration(
-               aOptions.duration, aOptions.localtimeOffsetMsec, aOptions.now),
+
+    function getExpiration(duration = ASSERTION_DEFAULT_DURATION_MS, localtimeOffsetMsec = 0, now = Date.now()) {
+      return now + localtimeOffsetMsec + duration;
+    }
+
+    const payload = {
+      exp: getExpiration(aOptions.duration, aOptions.localtimeOffsetMsec, aOptions.now),
       aud: aAudience,
     };
-    var payloadBytes = IdentityCryptoService.base64UrlEncode(
+    const payloadBytes = IdentityCryptoService.base64UrlEncode(
                           JSON.stringify(payload));
 
     log.debug("payload", { payload, payloadBytes });
-    sign(headerBytes + "." + payloadBytes, aKeyPair, function(err, signature) {
-      if (err)
-        return aCallback(err);
-
-      var signedAssertion = headerBytes + "." + payloadBytes + "." + signature;
-      return aCallback(null, aCert + "~" + signedAssertion);
+    const message = headerBytes + "." + payloadBytes;
+    aKeyPair._kp.sign(message, (rv, signature) => {
+      if (!Components.isSuccessCode(rv)) {
+        log.error("signer.sign failed");
+        aCallback("Sign failed");
+        return;
+      }
+      log.debug("signer.sign: success");
+      const signedAssertion = message + "." + signature;
+      aCallback(null, aCert + "~" + signedAssertion);
     });
-  },
+  }
+}
 
-};
+/**
+ * Do an ECDH agreement between a public and private key,
+ * returning the derived encryption key as specced by
+ * JWA RFC.
+ * The raw ECDH secret is derived into a key using
+ * Concat KDF, as defined in Section 5.8.1 of [NIST.800-56A].
+ * @param {CryptoKey} privateKey
+ * @param {CryptoKey} publicKey
+ * @param {String[]} keyUsages See `SubtleCrypto.deriveKey` 5th paramater documentation.
+ * @returns {Promise<CryptoKey>}
+ */
+async function deriveECDHSharedAESKey(privateKey, publicKey, keyUsages) {
+  const params = {...ECDH_PARAMS, ...{public: publicKey}};
+  const sharedKey = await crypto.subtle.deriveKey(params, privateKey, AES_PARAMS, true, keyUsages);
+  // This is the NIST Concat KDF specialized to a specific set of parameters,
+  // which basically turn it into a single application of SHA256.
+  // The details are from the JWA RFC.
+  let sharedKeyBytes = await crypto.subtle.exportKey("raw", sharedKey);
+  sharedKeyBytes = new Uint8Array(sharedKeyBytes);
+  const info = [
+    "\x00\x00\x00\x07A256GCM", // 7-byte algorithm identifier
+    "\x00\x00\x00\x00",  // empty PartyUInfo
+    "\x00\x00\x00\x00",  // empty PartyVInfo
+    "\x00\x00\x01\x00",  // keylen == 256
+  ].join("");
+  const pkcs = `\x00\x00\x00\x01${String.fromCharCode.apply(null, sharedKeyBytes)}${info}`;
+  const pkcsBuf = Uint8Array.from(Array.prototype.map.call(pkcs, (c) => c.charCodeAt(0)));
+  const derivedKeyBytes = await crypto.subtle.digest({
+    name: "SHA-256",
+  }, pkcsBuf);
+  return crypto.subtle.importKey("raw", derivedKeyBytes, AES_PARAMS, false, keyUsages);
+}
 
-var jwcrypto = new jwcryptoClass();
-this.jwcrypto.ALGORITHMS = ALGORITHMS;
+const jwcrypto = new JWCrypto();
--- a/services/crypto/modules/utils.js
+++ b/services/crypto/modules/utils.js
@@ -2,67 +2,82 @@
  * 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/. */
 
 var EXPORTED_SYMBOLS = ["CryptoUtils"];
 
 ChromeUtils.import("resource://services-common/observers.js");
 ChromeUtils.import("resource://services-common/utils.js");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyGlobalGetters(this, ["crypto"]);
 
+XPCOMUtils.defineLazyGetter(this, "textEncoder",
+  function() { return new TextEncoder(); }
+);
+
+/**
+ * A number of `Legacy` suffixed functions are exposed by CryptoUtils.
+ * They work with octet strings, which were used before Javascript
+ * got ArrayBuffer and friends.
+ */
 var CryptoUtils = {
-  xor: function xor(a, b) {
+  xor(a, b) {
     let bytes = [];
 
     if (a.length != b.length) {
       throw new Error("can't xor unequal length strings: " + a.length + " vs " + b.length);
     }
 
     for (let i = 0; i < a.length; i++) {
       bytes[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
     }
 
     return String.fromCharCode.apply(String, bytes);
   },
 
   /**
    * Generate a string of random bytes.
+   * @returns {String} Octet string
    */
-  generateRandomBytes: function generateRandomBytes(length) {
-    let rng = Cc["@mozilla.org/security/random-generator;1"]
-                .createInstance(Ci.nsIRandomGenerator);
-    let bytes = rng.generateRandomBytes(length);
-    return CommonUtils.byteArrayToString(bytes);
+  generateRandomBytesLegacy(length) {
+    let bytes = CryptoUtils.generateRandomBytes(length);
+    return CommonUtils.arrayBufferToByteString(bytes);
+  },
+
+  generateRandomBytes(length) {
+    return crypto.getRandomValues(new Uint8Array(length));
   },
 
   /**
    * UTF8-encode a message and hash it with the given hasher. Returns a
    * string containing bytes. The hasher is reset if it's an HMAC hasher.
    */
-  digestUTF8: function digestUTF8(message, hasher) {
+  digestUTF8(message, hasher) {
     let data = this._utf8Converter.convertToByteArray(message, {});
     hasher.update(data, data.length);
     let result = hasher.finish(false);
     if (hasher instanceof Ci.nsICryptoHMAC) {
       hasher.reset();
     }
     return result;
   },
 
   /**
-   * Treat the given message as a bytes string and hash it with the given
-   * hasher. Returns a string containing bytes. The hasher is reset if it's
-   * an HMAC hasher.
+   * Treat the given message as a bytes string (if necessary) and hash it with
+   * the given hasher. Returns a string containing bytes.
+   * The hasher is reset if it's an HMAC hasher.
    */
-  digestBytes: function digestBytes(message, hasher) {
-    // No UTF-8 encoding for you, sunshine.
-    let bytes = new Uint8Array(message.length);
-    for (let i = 0; i < message.length; ++i) {
-      bytes[i] = message.charCodeAt(i) & 0xff;
+  digestBytes(bytes, hasher) {
+    if (typeof bytes == "string" || bytes instanceof String) {
+      bytes = CommonUtils.byteStringToArrayBuffer(bytes);
     }
+    return CryptoUtils.digestBytesArray(bytes, hasher);
+  },
+
+  digestBytesArray(bytes, hasher) {
     hasher.update(bytes, bytes.length);
     let result = hasher.finish(false);
     if (hasher instanceof Ci.nsICryptoHMAC) {
       hasher.reset();
     }
     return result;
   },
 
@@ -72,46 +87,16 @@ var CryptoUtils = {
    * with a single hasher, but eventually you must extract the result
    * yourself.
    */
   updateUTF8(message, hasher) {
     let bytes = this._utf8Converter.convertToByteArray(message, {});
     hasher.update(bytes, bytes.length);
   },
 
-  /**
-   * UTF-8 encode a message and perform a SHA-1 over it.
-   *
-   * @param message
-   *        (string) Buffer to perform operation on. Should be a JS string.
-   *                 It is possible to pass in a string representing an array
-   *                 of bytes. But, you probably don't want to UTF-8 encode
-   *                 such data and thus should not be using this function.
-   *
-   * @return string
-   *         Raw bytes constituting SHA-1 hash. Value is a JS string. Each
-   *         character is the byte value for that offset. Returned string
-   *         always has .length == 20.
-   */
-  UTF8AndSHA1: function UTF8AndSHA1(message) {
-    let hasher = Cc["@mozilla.org/security/hash;1"]
-                 .createInstance(Ci.nsICryptoHash);
-    hasher.init(hasher.SHA1);
-
-    return CryptoUtils.digestUTF8(message, hasher);
-  },
-
-  sha1: function sha1(message) {
-    return CommonUtils.bytesAsHex(CryptoUtils.UTF8AndSHA1(message));
-  },
-
-  sha1Base32: function sha1Base32(message) {
-    return CommonUtils.encodeBase32(CryptoUtils.UTF8AndSHA1(message));
-  },
-
   sha256(message) {
     let hasher = Cc["@mozilla.org/security/hash;1"]
                  .createInstance(Ci.nsICryptoHash);
     hasher.init(hasher.SHA256);
     return CommonUtils.bytesAsHex(CryptoUtils.digestUTF8(message, hasher));
   },
 
   sha256Base64(message) {
@@ -136,131 +121,94 @@ var CryptoUtils = {
   makeHMACHasher: function makeHMACHasher(type, key) {
     let hasher = Cc["@mozilla.org/security/hmac;1"]
                    .createInstance(Ci.nsICryptoHMAC);
     hasher.init(type, key);
     return hasher;
   },
 
   /**
-   * HMAC-based Key Derivation (RFC 5869).
+   * @param {string} alg Hash algorithm (common values are SHA-1 or SHA-256)
+   * @param {string} key Key as an octet string.
+   * @param {string} data Data as an octet string.
    */
-  hkdf: function hkdf(ikm, xts, info, len) {
-    let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
-                                       CryptoUtils.makeHMACKey(xts));
-    let prk = CryptoUtils.digestBytes(ikm, h);
-    return CryptoUtils.hkdfExpand(prk, info, len);
+  async hmacLegacy(alg, key, data) {
+    if (!key || !key.length) {
+      key = "\0";
+    }
+    data = CommonUtils.byteStringToArrayBuffer(data);
+    key = CommonUtils.byteStringToArrayBuffer(key);
+    const result = await CryptoUtils.hmac(alg, key, data);
+    return CommonUtils.arrayBufferToByteString(result);
   },
 
   /**
-   * HMAC-based Key Derivation Step 2 according to RFC 5869.
+   * @param {string} ikm IKM as an octet string.
+   * @param {string} salt Salt as an Hex string.
+   * @param {string} info Info as a regular string.
+   * @param {Number} len Desired output length in bytes.
    */
-  hkdfExpand: function hkdfExpand(prk, info, len) {
-    const BLOCKSIZE = 256 / 8;
-    let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
-                                       CryptoUtils.makeHMACKey(prk));
-    let T = "";
-    let Tn = "";
-    let iterations = Math.ceil(len / BLOCKSIZE);
-    for (let i = 0; i < iterations; i++) {
-      Tn = CryptoUtils.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
-      T += Tn;
-    }
-    return T.slice(0, len);
+  async hkdfLegacy(ikm, xts, info, len) {
+    ikm = CommonUtils.byteStringToArrayBuffer(ikm);
+    xts = CommonUtils.byteStringToArrayBuffer(xts);
+    info = textEncoder.encode(info);
+    const okm = await CryptoUtils.hkdf(ikm, xts, info, len);
+    return CommonUtils.arrayBufferToByteString(okm);
   },
 
   /**
-   * PBKDF2 implementation in Javascript.
-   *
-   * The arguments to this function correspond to items in
-   * PKCS #5, v2.0 pp. 9-10
-   *
-   * P: the passphrase, an octet string:              e.g., "secret phrase"
-   * S: the salt, an octet string:                    e.g., "DNXPzPpiwn"
-   * c: the number of iterations, a positive integer: e.g., 4096
-   * dkLen: the length in octets of the destination
-   *        key, a positive integer:                  e.g., 16
-   * hmacAlg: The algorithm to use for hmac
-   * hmacLen: The hmac length
-   *
-   * The default value of 20 for hmacLen is appropriate for SHA1.  For SHA256,
-   * hmacLen should be 32.
-   *
-   * The output is an octet string of length dkLen, which you
-   * can encode as you wish.
+   * @param {String} alg Hash algorithm (common values are SHA-1 or SHA-256)
+   * @param {ArrayBuffer} key
+   * @param {ArrayBuffer} data
+   * @param {Number} len Desired output length in bytes.
+   * @returns {Uint8Array}
    */
-  pbkdf2Generate: function pbkdf2Generate(P, S, c, dkLen,
-                       hmacAlg = Ci.nsICryptoHMAC.SHA1, hmacLen = 20) {
-
-    // We don't have a default in the algo itself, as NSS does.
-    if (!dkLen) {
-      throw new Error("dkLen should be defined");
-    }
-
-    function F(S, c, i, h) {
-
-      function XOR(a, b, isA) {
-        if (a.length != b.length) {
-          return false;
-        }
-
-        let val = [];
-        for (let i = 0; i < a.length; i++) {
-          if (isA) {
-            val[i] = a[i] ^ b[i];
-          } else {
-            val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
-          }
-        }
+  async hmac(alg, key, data) {
+    const hmacKey = await crypto.subtle.importKey("raw", key, {name: "HMAC", hash: alg}, false, ["sign"]);
+    const result = await crypto.subtle.sign("HMAC", hmacKey, data);
+    return new Uint8Array(result);
+  },
 
-        return val;
-      }
-
-      let ret;
-      let U = [];
-
-      /* Encode i into 4 octets: _INT */
-      let I = [];
-      I[0] = String.fromCharCode((i >> 24) & 0xff);
-      I[1] = String.fromCharCode((i >> 16) & 0xff);
-      I[2] = String.fromCharCode((i >> 8) & 0xff);
-      I[3] = String.fromCharCode(i & 0xff);
-
-      U[0] = CryptoUtils.digestBytes(S + I.join(""), h);
-      for (let j = 1; j < c; j++) {
-        U[j] = CryptoUtils.digestBytes(U[j - 1], h);
-      }
-
-      ret = U[0];
-      for (let j = 1; j < c; j++) {
-        ret = CommonUtils.byteArrayToString(XOR(ret, U[j]));
-      }
+  /**
+   * @param {ArrayBuffer} ikm
+   * @param {ArrayBuffer} salt
+   * @param {ArrayBuffer} info
+   * @param {Number} len Desired output length in bytes.
+   * @returns {Uint8Array}
+   */
+  async hkdf(ikm, salt, info, len) {
+    const key = await crypto.subtle.importKey("raw", ikm, {name: "HKDF"}, false, ["deriveBits"]);
+    const okm = await crypto.subtle.deriveBits({
+        name: "HKDF",
+        hash: "SHA-256",
+        salt,
+        info,
+    }, key, len * 8);
+    return new Uint8Array(okm);
+  },
 
-      return ret;
-    }
-
-    let l = Math.ceil(dkLen / hmacLen);
-    let r = dkLen - ((l - 1) * hmacLen);
-
-    // Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
-    let h = CryptoUtils.makeHMACHasher(hmacAlg,
-                                       CryptoUtils.makeHMACKey(P));
-
-    let T = [];
-    for (let i = 0; i < l;) {
-      T[i] = F(S, c, ++i, h);
-    }
-
-    let ret = "";
-    for (let i = 0; i < l - 1;) {
-      ret += T[i++];
-    }
-    ret += T[l - 1].substr(0, r);
-
-    return ret;
+  /**
+   * PBKDF2 password stretching with SHA-256 hmac.
+   *
+   * @param {string} passphrase Passphrase as an octet string.
+   * @param {string} salt Salt as an octet string.
+   * @param {string} iterations Number of iterations, a positive integer.
+   * @param {string} len Desired output length in bytes.
+   */
+  async pbkdf2Generate(passphrase, salt, iterations, len) {
+    passphrase = CommonUtils.byteStringToArrayBuffer(passphrase);
+    salt = CommonUtils.byteStringToArrayBuffer(salt);
+    const key = await crypto.subtle.importKey("raw", passphrase, {name: "PBKDF2"}, false, ["deriveBits"]);
+    const output = await crypto.subtle.deriveBits({
+        name: "PBKDF2",
+        hash: "SHA-256",
+        salt,
+        iterations,
+    }, key, len * 8);
+    return CommonUtils.arrayBufferToByteString(new Uint8Array(output));
   },
 
   /**
    * Compute the HTTP MAC SHA-1 for an HTTP request.
    *
    * @param  identifier
    *         (string) MAC Key Identifier.
    * @param  key
@@ -290,25 +238,24 @@ var CryptoUtils = {
    *           hostname - (string) HTTP hostname used (derived from arguments).
    *           port - (string) HTTP port number used (derived from arguments).
    *           mac - (string) Raw HMAC digest bytes.
    *           getHeader - (function) Call to obtain the string Authorization
    *             header value for this invocation.
    *           nonce - (string) Nonce value used.
    *           ts - (number) Integer seconds since Unix epoch that was used.
    */
-  computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
-                                                  uri, extra) {
+  async computeHTTPMACSHA1(identifier, key, method, uri, extra) {
     let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
     let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
 
     // We are allowed to use more than the Base64 alphabet if we want.
     let nonce = (extra && extra.nonce)
                 ? extra.nonce
-                : btoa(CryptoUtils.generateRandomBytes(nonce_bytes));
+                : btoa(CryptoUtils.generateRandomBytesLegacy(nonce_bytes));
 
     let host = uri.asciiHost;
     let port;
     let usedMethod = method.toUpperCase();
 
     if (uri.port != -1) {
       port = uri.port;
     } else if (uri.scheme == "http") {
@@ -324,19 +271,17 @@ var CryptoUtils = {
     let requestString = ts.toString(10) + "\n" +
                         nonce + "\n" +
                         usedMethod + "\n" +
                         uri.pathQueryRef + "\n" +
                         host + "\n" +
                         port + "\n" +
                         ext + "\n";
 
-    let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
-                                            CryptoUtils.makeHMACKey(key));
-    let mac = CryptoUtils.digestBytes(requestString, hasher);
+    const mac = await CryptoUtils.hmacLegacy("SHA-1", key, requestString);
 
     function getHeader() {
       return CryptoUtils.getHTTPMACSHA1Header(this.identifier, this.ts,
                                               this.nonce, this.mac, this.ext);
     }
 
     return {
       identifier,
@@ -401,17 +346,16 @@ var CryptoUtils = {
    * @param  method
    *         (string) HTTP request method.
    * @param  options
    *         (object) extra parameters (all but "credentials" are optional):
    *           credentials - (object, mandatory) HAWK credentials object.
    *             All three keys are required:
    *             id - (string) key identifier
    *             key - (string) raw key bytes
-   *             algorithm - (string) which hash to use: "sha1" or "sha256"
    *           ext - (string) application-specific data, included in MAC
    *           localtimeOffsetMsec - (number) local clock offset (vs server)
    *           payload - (string) payload to include in hash, containing the
    *                     HTTP request body. If not provided, the HAWK hash
    *                     will not cover the request body, and the server
    *                     should not check it either. This will be UTF-8
    *                     encoded into bytes before hashing. This function
    *                     cannot handle arbitrary binary data, sorry (the
@@ -427,103 +371,83 @@ var CryptoUtils = {
    *           hash - (base64 string) pre-calculated payload hash. If
    *                  provided, "payload" is ignored.
    *           ts - (number) pre-calculated timestamp, secs since epoch
    *           now - (number) current time, ms-since-epoch, for tests
    *           nonce - (string) pre-calculated nonce. Should only be defined
    *                   for testing as this function will generate a
    *                   cryptographically secure random one if not defined.
    * @returns
-   *         (object) Contains results of operation. The object has the
+   *         Promise<Object> Contains results of operation. The object has the
    *         following keys:
    *           field - (string) HAWK header, to use in Authorization: header
    *           artifacts - (object) other generated values:
    *             ts - (number) timestamp, in seconds since epoch
    *             nonce - (string)
    *             method - (string)
    *             resource - (string) path plus querystring
    *             host - (string)
    *             port - (number)
    *             hash - (string) payload hash (base64)
    *             ext - (string) app-specific data
    *             MAC - (string) request MAC (base64)
    */
-  computeHAWK(uri, method, options) {
+  async computeHAWK(uri, method, options) {
     let credentials = options.credentials;
     let ts = options.ts || Math.floor(((options.now || Date.now()) +
                                        (options.localtimeOffsetMsec || 0))
                                       / 1000);
-
-    let hash_algo, hmac_algo;
-    if (credentials.algorithm == "sha1") {
-      hash_algo = Ci.nsICryptoHash.SHA1;
-      hmac_algo = Ci.nsICryptoHMAC.SHA1;
-    } else if (credentials.algorithm == "sha256") {
-      hash_algo = Ci.nsICryptoHash.SHA256;
-      hmac_algo = Ci.nsICryptoHMAC.SHA256;
-    } else {
-      throw new Error("Unsupported algorithm: " + credentials.algorithm);
-    }
-
     let port;
     if (uri.port != -1) {
       port = uri.port;
     } else if (uri.scheme == "http") {
       port = 80;
     } else if (uri.scheme == "https") {
       port = 443;
     } else {
       throw new Error("Unsupported URI scheme: " + uri.scheme);
     }
 
     let artifacts = {
       ts,
-      nonce: options.nonce || btoa(CryptoUtils.generateRandomBytes(8)),
+      nonce: options.nonce || btoa(CryptoUtils.generateRandomBytesLegacy(8)),
       method: method.toUpperCase(),
       resource: uri.pathQueryRef, // This includes both path and search/queryarg.
       host: uri.asciiHost.toLowerCase(), // This includes punycoding.
       port: port.toString(10),
       hash: options.hash,
       ext: options.ext,
     };
 
     let contentType = CryptoUtils.stripHeaderAttributes(options.contentType);
 
     if (!artifacts.hash && options.hasOwnProperty("payload")
         && options.payload) {
-      let hasher = Cc["@mozilla.org/security/hash;1"]
-                     .createInstance(Ci.nsICryptoHash);
-      hasher.init(hash_algo);
-      CryptoUtils.updateUTF8("hawk.1.payload\n", hasher);
-      CryptoUtils.updateUTF8(contentType + "\n", hasher);
-      CryptoUtils.updateUTF8(options.payload, hasher);
-      CryptoUtils.updateUTF8("\n", hasher);
-      let hash = hasher.finish(false);
+      const buffer = textEncoder.encode(`hawk.1.payload\n${contentType}\n${options.payload}\n`);
+      const hash = await crypto.subtle.digest("SHA-256", buffer);
       // HAWK specifies this .hash to use +/ (not _-) and include the
       // trailing "==" padding.
-      let hash_b64 = btoa(hash);
-      artifacts.hash = hash_b64;
+      artifacts.hash = ChromeUtils.base64URLEncode(hash, {pad: true}).replace(/-/g, "+").replace(/_/g, "/");
     }
 
     let requestString = ("hawk.1.header\n" +
                          artifacts.ts.toString(10) + "\n" +
                          artifacts.nonce + "\n" +
                          artifacts.method + "\n" +
                          artifacts.resource + "\n" +
                          artifacts.host + "\n" +
                          artifacts.port + "\n" +
                          (artifacts.hash || "") + "\n");
     if (artifacts.ext) {
       requestString += artifacts.ext.replace("\\", "\\\\").replace("\n", "\\n");
     }
     requestString += "\n";
 
-    let hasher = CryptoUtils.makeHMACHasher(hmac_algo,
-                                            CryptoUtils.makeHMACKey(credentials.key));
-    artifacts.mac = btoa(CryptoUtils.digestBytes(requestString, hasher));
+    const hash = await CryptoUtils.hmacLegacy("SHA-256", credentials.key, requestString);
+    artifacts.mac = btoa(hash);
     // The output MAC uses "+" and "/", and padded== .
 
     function escape(attribute) {
       // This is used for "x=y" attributes inside HTTP headers.
       return attribute.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
     }
     let header = ('Hawk id="' + credentials.id + '", ' +
                   'ts="' + artifacts.ts + '", ' +
--- a/services/crypto/tests/unit/test_jwcrypto.js
+++ b/services/crypto/tests/unit/test_jwcrypto.js
@@ -6,231 +6,180 @@
 ChromeUtils.defineModuleGetter(this, "jwcrypto",
                                "resource://services-crypto/jwcrypto.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "CryptoService",
                                    "@mozilla.org/identity/crypto-service;1",
                                    "nsIIdentityCryptoService");
 
+Cu.importGlobalProperties(["crypto"]);
+
 const RP_ORIGIN = "http://123done.org";
 const INTERNAL_ORIGIN = "browserid://";
 
 const SECOND_MS = 1000;
 const MINUTE_MS = SECOND_MS * 60;
 const HOUR_MS = MINUTE_MS * 60;
 
 // Enable logging from jwcrypto.jsm.
 Services.prefs.setCharPref("services.crypto.jwcrypto.log.level", "Debug");
 
-function test_sanity() {
-  do_test_pending();
-
-  jwcrypto.generateKeyPair("DS160", function(err, kp) {
-    Assert.equal(null, err);
-
-    do_test_finished();
-    run_next_test();
-  });
-}
-
-function test_generate() {
-  do_test_pending();
-  jwcrypto.generateKeyPair("DS160", function(err, kp) {
-    Assert.equal(null, err);
-    Assert.notEqual(kp, null);
-
-    do_test_finished();
-    run_next_test();
-  });
-}
-
-function test_get_assertion() {
-  do_test_pending();
-
-  jwcrypto.generateKeyPair(
-    "DS160",
-    function(err, kp) {
-      jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN, (err2, backedAssertion) => {
-        Assert.equal(null, err2);
-
-        Assert.equal(backedAssertion.split("~").length, 2);
-        Assert.equal(backedAssertion.split(".").length, 3);
-
-        do_test_finished();
-        run_next_test();
+function promisify(fn) {
+  return (...args) => {
+    return new Promise((res, rej) => {
+      fn(...args, (err, result) => {
+        err ? rej(err) : res(result);
       });
     });
+  };
 }
+const generateKeyPair = promisify(jwcrypto.generateKeyPair);
+const generateAssertion = promisify(jwcrypto.generateAssertion);
 
-function test_rsa() {
-  do_test_pending();
-  function checkRSA(err, kpo) {
-    Assert.notEqual(kpo, undefined);
-    info(kpo.serializedPublicKey);
-    let pk = JSON.parse(kpo.serializedPublicKey);
-    Assert.equal(pk.algorithm, "RS");
-/* TODO
-    do_check_neq(kpo.sign, null);
-    do_check_eq(typeof kpo.sign, "function");
-    do_check_neq(kpo.userID, null);
-    do_check_neq(kpo.url, null);
-    do_check_eq(kpo.url, INTERNAL_ORIGIN);
-    do_check_neq(kpo.exponent, null);
-    do_check_neq(kpo.modulus, null);
+add_task(async function test_jwe_roundtrip_ecdh_es_encryption() {
+  const data = crypto.getRandomValues(new Uint8Array(123));
+  const localEpk = await crypto.subtle.generateKey({
+    name: "ECDH",
+    namedCurve: "P-256",
+  }, true, ["deriveKey"]);
+  const remoteEpk = await crypto.subtle.generateKey({
+    name: "ECDH",
+    namedCurve: "P-256",
+  }, true, ["deriveKey"]);
+  const jwe = await jwcrypto._generateJWE(localEpk, remoteEpk.publicKey, data);
+  const decryptedJWE = await jwcrypto.decryptJWE(jwe, remoteEpk.privateKey);
+  Assert.deepEqual(data, decryptedJWE);
+});
 
-    // TODO: should sign be async?
-    let sig = kpo.sign("This is a message to sign");
+add_task(async function test_sanity() {
+  // Shouldn't reject.
+  await generateKeyPair("DS160");
+});
 
-    do_check_neq(sig, null);
-    do_check_eq(typeof sig, "string");
-    do_check_true(sig.length > 1);
-*/
-    do_test_finished();
-    run_next_test();
-  }
+add_task(async function test_generate() {
+  let kp = await generateKeyPair("DS160");
+  Assert.notEqual(kp, null);
+});
 
-  jwcrypto.generateKeyPair("RS256", checkRSA);
-}
+add_task(async function test_get_assertion() {
+  let kp = await generateKeyPair("DS160");
+  let backedAssertion = await generateAssertion("fake-cert", kp, RP_ORIGIN);
+  Assert.equal(backedAssertion.split("~").length, 2);
+  Assert.equal(backedAssertion.split(".").length, 3);
+});
 
-function test_dsa() {
-  do_test_pending();
-  function checkDSA(err, kpo) {
-    Assert.notEqual(kpo, undefined);
-    info(kpo.serializedPublicKey);
-    let pk = JSON.parse(kpo.serializedPublicKey);
-    Assert.equal(pk.algorithm, "DS");
-/* TODO
-    do_check_neq(kpo.sign, null);
-    do_check_eq(typeof kpo.sign, "function");
-    do_check_neq(kpo.userID, null);
-    do_check_neq(kpo.url, null);
-    do_check_eq(kpo.url, INTERNAL_ORIGIN);
-    do_check_neq(kpo.generator, null);
-    do_check_neq(kpo.prime, null);
-    do_check_neq(kpo.subPrime, null);
-    do_check_neq(kpo.publicValue, null);
+add_task(async function test_rsa() {
+  let kpo = await generateKeyPair("RS256");
+  Assert.notEqual(kpo, undefined);
+  info(kpo.serializedPublicKey);
+  let pk = JSON.parse(kpo.serializedPublicKey);
+  Assert.equal(pk.algorithm, "RS");
+  /* TODO
+  do_check_neq(kpo.sign, null);
+  do_check_eq(typeof kpo.sign, "function");
+  do_check_neq(kpo.userID, null);
+  do_check_neq(kpo.url, null);
+  do_check_eq(kpo.url, INTERNAL_ORIGIN);
+  do_check_neq(kpo.exponent, null);
+  do_check_neq(kpo.modulus, null);
+
+  // TODO: should sign be async?
+  let sig = kpo.sign("This is a message to sign");
+
+  do_check_neq(sig, null);
+  do_check_eq(typeof sig, "string");
+  do_check_true(sig.length > 1);
+  */
+});
 
-    let sig = kpo.sign("This is a message to sign");
+add_task(async function test_dsa() {
+  let kpo = await generateKeyPair("DS160");
+  info(kpo.serializedPublicKey);
+  let pk = JSON.parse(kpo.serializedPublicKey);
+  Assert.equal(pk.algorithm, "DS");
+  /* TODO
+  do_check_neq(kpo.sign, null);
+  do_check_eq(typeof kpo.sign, "function");
+  do_check_neq(kpo.userID, null);
+  do_check_neq(kpo.url, null);
+  do_check_eq(kpo.url, INTERNAL_ORIGIN);
+  do_check_neq(kpo.generator, null);
+  do_check_neq(kpo.prime, null);
+  do_check_neq(kpo.subPrime, null);
+  do_check_neq(kpo.publicValue, null);
 
-    do_check_neq(sig, null);
-    do_check_eq(typeof sig, "string");
-    do_check_true(sig.length > 1);
-*/
-    do_test_finished();
-    run_next_test();
-  }
+  let sig = kpo.sign("This is a message to sign");
 
-  jwcrypto.generateKeyPair("DS160", checkDSA);
-}
+  do_check_neq(sig, null);
+  do_check_eq(typeof sig, "string");
+  do_check_true(sig.length > 1);
+  */
+});
 
-function test_get_assertion_with_offset() {
-  do_test_pending();
-
-
+add_task(async function test_get_assertion_with_offset() {
   // Use an arbitrary date in the past to ensure we don't accidentally pass
   // this test with current dates, missing offsets, etc.
   let serverMsec = Date.parse("Tue Oct 31 2000 00:00:00 GMT-0800");
 
   // local clock skew
   // clock is 12 hours fast; -12 hours offset must be applied
   let localtimeOffsetMsec = -1 * 12 * HOUR_MS;
   let localMsec = serverMsec - localtimeOffsetMsec;
 
-  jwcrypto.generateKeyPair(
-    "DS160",
-    function(err, kp) {
-      jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN,
-        { duration: MINUTE_MS,
-          localtimeOffsetMsec,
-          now: localMsec},
-          function(err2, backedAssertion) {
-            Assert.equal(null, err2);
-
-            // properly formed
-            let cert;
-            let assertion;
-            [cert, assertion] = backedAssertion.split("~");
-
-            Assert.equal(cert, "fake-cert");
-            Assert.equal(assertion.split(".").length, 3);
-
-            let components = extractComponents(assertion);
-
-            // Expiry is within two minutes, corrected for skew
-            let exp = parseInt(components.payload.exp, 10);
-            Assert.ok(exp - serverMsec === MINUTE_MS);
-
-            do_test_finished();
-            run_next_test();
-          }
-      );
+  let kp = await generateKeyPair("DS160");
+  let backedAssertion = await generateAssertion("fake-cert", kp, RP_ORIGIN,
+    {
+      duration: MINUTE_MS,
+      localtimeOffsetMsec,
+      now: localMsec,
     }
   );
-}
-
-function test_assertion_lifetime() {
-  do_test_pending();
-
-  jwcrypto.generateKeyPair(
-    "DS160",
-    function(err, kp) {
-      jwcrypto.generateAssertion("fake-cert", kp, RP_ORIGIN,
-        {duration: MINUTE_MS},
-        function(err2, backedAssertion) {
-          Assert.equal(null, err2);
+  // properly formed
+  let cert;
+  let assertion;
+  [cert, assertion] = backedAssertion.split("~");
 
-          // properly formed
-          let cert;
-          let assertion;
-          [cert, assertion] = backedAssertion.split("~");
+  Assert.equal(cert, "fake-cert");
+  Assert.equal(assertion.split(".").length, 3);
+
+  let components = extractComponents(assertion);
 
-          Assert.equal(cert, "fake-cert");
-          Assert.equal(assertion.split(".").length, 3);
-
-          let components = extractComponents(assertion);
-
-          // Expiry is within one minute, as we specified above
-          let exp = parseInt(components.payload.exp, 10);
-          Assert.ok(Math.abs(Date.now() - exp) > 50 * SECOND_MS);
-          Assert.ok(Math.abs(Date.now() - exp) <= MINUTE_MS);
+  // Expiry is within two minutes, corrected for skew
+  let exp = parseInt(components.payload.exp, 10);
+  Assert.ok(exp - serverMsec === MINUTE_MS);
+});
 
-          do_test_finished();
-          run_next_test();
-        }
-      );
-    }
-  );
-}
+add_task(async function test_assertion_lifetime() {
+  let kp = await generateKeyPair("DS160");
+  let backedAssertion = await generateAssertion("fake-cert", kp, RP_ORIGIN, {duration: MINUTE_MS});
+  // properly formed
+  let cert;
+  let assertion;
+  [cert, assertion] = backedAssertion.split("~");
 
-function test_audience_encoding_bug972582() {
-  let audience = "i-like-pie.com";
+  Assert.equal(cert, "fake-cert");
+  Assert.equal(assertion.split(".").length, 3);
+
+  let components = extractComponents(assertion);
 
-  jwcrypto.generateKeyPair(
-    "DS160",
-    function(err, kp) {
-      Assert.equal(null, err);
-      jwcrypto.generateAssertion("fake-cert", kp, audience,
-        function(err2, backedAssertion) {
-          Assert.equal(null, err2);
+  // Expiry is within one minute, as we specified above
+  let exp = parseInt(components.payload.exp, 10);
+  Assert.ok(Math.abs(Date.now() - exp) > 50 * SECOND_MS);
+  Assert.ok(Math.abs(Date.now() - exp) <= MINUTE_MS);
+});
 
-          let [/* cert */, assertion] = backedAssertion.split("~");
-          let components = extractComponents(assertion);
-          Assert.equal(components.payload.aud, audience);
-
-          do_test_finished();
-          run_next_test();
-        }
-      );
-    }
-  );
-}
-
-// End of tests
-// Helper function follow
+add_task(async function test_audience_encoding_bug972582() {
+  let audience = "i-like-pie.com";
+  let kp = await generateKeyPair("DS160");
+  let backedAssertion = await generateAssertion("fake-cert", kp, audience);
+  let [/* cert */, assertion] = backedAssertion.split("~");
+  let components = extractComponents(assertion);
+  Assert.equal(components.payload.aud, audience);
+});
 
 function extractComponents(signedObject) {
   if (typeof(signedObject) != "string") {
     throw new Error("malformed signature " + typeof(signedObject));
   }
 
   let parts = signedObject.split(".");
   if (parts.length != 3) {
@@ -254,21 +203,8 @@ function extractComponents(signedObject)
   }
 
   return {header,
           payload,
           headerSegment,
           payloadSegment,
           cryptoSegment};
 }
-
-var TESTS = [
-  test_sanity,
-  test_generate,
-  test_get_assertion,
-  test_get_assertion_with_offset,
-  test_assertion_lifetime,
-  test_audience_encoding_bug972582,
-];
-
-TESTS = TESTS.concat([test_rsa, test_dsa]);
-
-TESTS.forEach(f => add_test(f));
--- a/services/crypto/tests/unit/test_utils_hawk.js
+++ b/services/crypto/tests/unit/test_utils_hawk.js
@@ -6,94 +6,60 @@ ChromeUtils.import("resource://services-
 ChromeUtils.import("resource://services-crypto/utils.js");
 
 function run_test() {
   initTestLogging();
 
   run_next_test();
 }
 
-add_test(function test_hawk() {
+add_task(async function test_hawk() {
   let compute = CryptoUtils.computeHAWK;
 
-  // vectors copied from the HAWK (node.js) tests
-  let credentials_sha1 = {
-    id: "123456",
-    key: "2983d45yun89q",
-    algorithm: "sha1",
-  };
-
   let method = "POST";
   let ts = 1353809207;
   let nonce = "Ygvqdz";
-  let result;
 
-  let uri_http = CommonUtils.makeURI("http://example.net/somewhere/over/the/rainbow");
-  let sha1_opts = { credentials: credentials_sha1,
-                    ext: "Bazinga!",
-                    ts,
-                    nonce,
-                    payload: "something to write about",
-                  };
-  result = compute(uri_http, method, sha1_opts);
-
-  // The HAWK spec uses non-urlsafe base64 (+/) for its output MAC string.
-  Assert.equal(result.field,
-               'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
-               'hash="bsvY3IfUllw6V5rvk4tStEvpBhE=", ext="Bazinga!", ' +
-               'mac="qbf1ZPG/r/e06F4ht+T77LXi5vw="'
-             );
-  Assert.equal(result.artifacts.ts, ts);
-  Assert.equal(result.artifacts.nonce, nonce);
-  Assert.equal(result.artifacts.method, method);
-  Assert.equal(result.artifacts.resource, "/somewhere/over/the/rainbow");
-  Assert.equal(result.artifacts.host, "example.net");
-  Assert.equal(result.artifacts.port, 80);
-  // artifacts.hash is the *payload* hash, not the overall request MAC.
-  Assert.equal(result.artifacts.hash, "bsvY3IfUllw6V5rvk4tStEvpBhE=");
-  Assert.equal(result.artifacts.ext, "Bazinga!");
-
-  let credentials_sha256 = {
+  let credentials = {
     id: "123456",
     key: "2983d45yun89q",
-    algorithm: "sha256",
   };
 
   let uri_https = CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow");
-  let sha256_opts = { credentials: credentials_sha256,
+  let opts = { credentials,
                       ext: "Bazinga!",
                       ts,
                       nonce,
                       payload: "something to write about",
                       contentType: "text/plain",
                     };
 
-  result = compute(uri_https, method, sha256_opts);
+  let result = await compute(uri_https, method, opts);
   Assert.equal(result.field,
                'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
                'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
                'ext="Bazinga!", ' +
                'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
              );
   Assert.equal(result.artifacts.ts, ts);
   Assert.equal(result.artifacts.nonce, nonce);
   Assert.equal(result.artifacts.method, method);
   Assert.equal(result.artifacts.resource, "/somewhere/over/the/rainbow");
   Assert.equal(result.artifacts.host, "example.net");
   Assert.equal(result.artifacts.port, 443);
   Assert.equal(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
   Assert.equal(result.artifacts.ext, "Bazinga!");
 
-  let sha256_opts_noext = { credentials: credentials_sha256,
+  let opts_noext = { credentials,
                             ts,
                             nonce,
                             payload: "something to write about",
                             contentType: "text/plain",
                           };
-  result = compute(uri_https, method, sha256_opts_noext);
+  result = await compute(uri_https, method, opts_noext);
   Assert.equal(result.field,
                'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
                'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
                'mac="HTgtd0jPI6E4izx8e4OHdO36q00xFCU0FolNq3RiCYs="'
              );
   Assert.equal(result.artifacts.ts, ts);
   Assert.equal(result.artifacts.nonce, nonce);
   Assert.equal(result.artifacts.method, method);
@@ -103,193 +69,191 @@ add_test(function test_hawk() {
   Assert.equal(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
 
   /* Leaving optional fields out should work, although of course then we can't
    * assert much about the resulting hashes. The resulting header should look
    * roughly like:
    * Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U="
    */
 
-  result = compute(uri_https, method, { credentials: credentials_sha256 });
+  result = await compute(uri_https, method, { credentials });
   let fields = result.field.split(" ");
   Assert.equal(fields[0], "Hawk");
   Assert.equal(fields[1], 'id="123456",'); // from creds.id
   Assert.ok(fields[2].startsWith('ts="'));
   /* The HAWK spec calls for seconds-since-epoch, not ms-since-epoch.
    * Warning: this test will fail in the year 33658, and for time travellers
    * who journey earlier than 2001. Please plan accordingly. */
   Assert.ok(result.artifacts.ts > 1000 * 1000 * 1000);
   Assert.ok(result.artifacts.ts < 1000 * 1000 * 1000 * 1000);
   Assert.ok(fields[3].startsWith('nonce="'));
   Assert.equal(fields[3].length, ('nonce="12345678901=",').length);
   Assert.equal(result.artifacts.nonce.length, ("12345678901=").length);
 
-  let result2 = compute(uri_https, method, { credentials: credentials_sha256 });
+  let result2 = await compute(uri_https, method, { credentials });
   Assert.notEqual(result.artifacts.nonce, result2.artifacts.nonce);
 
   /* Using an upper-case URI hostname shouldn't affect the hash. */
 
   let uri_https_upper = CommonUtils.makeURI("https://EXAMPLE.NET/somewhere/over/the/rainbow");
-  result = compute(uri_https_upper, method, sha256_opts);
+  result = await compute(uri_https_upper, method, opts);
   Assert.equal(result.field,
                'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
                'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
                'ext="Bazinga!", ' +
                'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
              );
 
   /* Using a lower-case method name shouldn't affect the hash. */
-  result = compute(uri_https_upper, method.toLowerCase(), sha256_opts);
+  result = await compute(uri_https_upper, method.toLowerCase(), opts);
   Assert.equal(result.field,
                'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
                'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
                'ext="Bazinga!", ' +
                'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
              );
 
   /* The localtimeOffsetMsec field should be honored. HAWK uses this to
    * compensate for clock skew between client and server: if the request is
    * rejected with a timestamp out-of-range error, the error includes the
    * server's time, and the client computes its clock offset and tries again.
    * Clients can remember this offset for a while.
    */
 
-  result = compute(uri_https, method, { credentials: credentials_sha256,
+  result = await compute(uri_https, method, { credentials,
                                         now: 1378848968650,
                                       });
   Assert.equal(result.artifacts.ts, 1378848968);
 
-  result = compute(uri_https, method, { credentials: credentials_sha256,
+  result = await compute(uri_https, method, { credentials,
                                         now: 1378848968650,
                                         localtimeOffsetMsec: 1000 * 1000,
                                       });
   Assert.equal(result.artifacts.ts, 1378848968 + 1000);
 
   /* Search/query-args in URIs should be included in the hash. */
   let makeURI = CommonUtils.makeURI;
-  result = compute(makeURI("http://example.net/path"), method, sha256_opts);
+  result = await compute(makeURI("http://example.net/path"), method, opts);
   Assert.equal(result.artifacts.resource, "/path");
   Assert.equal(result.artifacts.mac, "WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI=");
 
-  result = compute(makeURI("http://example.net/path/"), method, sha256_opts);
+  result = await compute(makeURI("http://example.net/path/"), method, opts);
   Assert.equal(result.artifacts.resource, "/path/");
   Assert.equal(result.artifacts.mac, "xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw=");
 
-  result = compute(makeURI("http://example.net/path?query=search"), method, sha256_opts);
+  result = await compute(makeURI("http://example.net/path?query=search"), method, opts);
   Assert.equal(result.artifacts.resource, "/path?query=search");
   Assert.equal(result.artifacts.mac, "C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho=");
 
   /* Test handling of the payload, which is supposed to be a bytestring
   (String with codepoints from U+0000 to U+00FF, pre-encoded). */
 
-  result = compute(makeURI("http://example.net/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://example.net/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                    });
   Assert.equal(result.artifacts.hash, undefined);
   Assert.equal(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
 
   // Empty payload changes nothing.
-  result = compute(makeURI("http://example.net/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://example.net/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                      payload: null,
                    });
   Assert.equal(result.artifacts.hash, undefined);
   Assert.equal(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
 
-  result = compute(makeURI("http://example.net/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://example.net/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                      payload: "hello",
                    });
   Assert.equal(result.artifacts.hash, "uZJnFj0XVBA6Rs1hEvdIDf8NraM0qRNXdFbR3NEQbVA=");
   Assert.equal(result.artifacts.mac, "pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM=");
 
   // update, utf-8 payload
-  result = compute(makeURI("http://example.net/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://example.net/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                      payload: "andré@example.org", // non-ASCII
                    });
   Assert.equal(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
   Assert.equal(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
 
   /* If "hash" is provided, "payload" is ignored. */
-  result = compute(makeURI("http://example.net/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://example.net/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                      hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=",
                      payload: "something else",
                    });
   Assert.equal(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
   Assert.equal(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
 
   // the payload "hash" is also non-urlsafe base64 (+/)
-  result = compute(makeURI("http://example.net/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://example.net/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                      payload: "something else",
                    });
   Assert.equal(result.artifacts.hash, "lERFXr/IKOaAoYw+eBseDUSwmqZTX0uKZpcWLxsdzt8=");
   Assert.equal(result.artifacts.mac, "jiZuhsac35oD7IdcblhFncBr8tJFHcwWLr8NIYWr9PQ=");
 
   /* Test non-ascii hostname. HAWK (via the node.js "url" module) punycodes
    * "ëxample.net" into "xn--xample-ova.net" before hashing. I still think
    * punycode was a bad joke that got out of the lab and into a spec.
    */
 
-  result = compute(makeURI("http://ëxample.net/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://ëxample.net/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                    });
   Assert.equal(result.artifacts.mac, "pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0=");
   Assert.equal(result.artifacts.host, "xn--xample-ova.net");
 
-  result = compute(makeURI("http://example.net/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://example.net/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                      ext: "backslash=\\ quote=\" EOF",
                    });
   Assert.equal(result.artifacts.mac, "BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc=");
   Assert.equal(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="');
 
-  result = compute(makeURI("http://example.net:1234/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://example.net:1234/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                    });
   Assert.equal(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
   Assert.equal(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
 
   /* HAWK (the node.js library) uses a URL parser which stores the "port"
    * field as a string, but makeURI() gives us an integer. So we'll diverge
    * on ports with a leading zero. This test vector would fail on the node.js
    * library (HAWK-1.1.1), where they get a MAC of
    * "T+GcAsDO8GRHIvZLeepSvXLwDlFJugcZroAy9+uAtcw=". I think HAWK should be
    * updated to do what we do here, so port="01234" should get the same hash
    * as port="1234".
    */
-  result = compute(makeURI("http://example.net:01234/path"), method,
-                   { credentials: credentials_sha256,
+  result = await compute(makeURI("http://example.net:01234/path"), method,
+                   { credentials,
                      ts: 1353809207,
                      nonce: "Ygvqdz",
                    });
   Assert.equal(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
   Assert.equal(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
-
-  run_next_test();
 });
 
 
 add_test(function test_strip_header_attributes() {
   let strip = CryptoUtils.stripHeaderAttributes;
 
   Assert.equal(strip(undefined), "");
   Assert.equal(strip("text/plain"), "text/plain");
deleted file mode 100644
--- a/services/crypto/tests/unit/test_utils_hkdfExpand.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-ChromeUtils.import("resource://services-common/utils.js");
-ChromeUtils.import("resource://services-crypto/utils.js");
-
-// Test vectors from RFC 5869
-
-// Test case 1
-
-var tc1 = {
-   IKM:  "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
-   salt: "000102030405060708090a0b0c",
-   info: "f0f1f2f3f4f5f6f7f8f9",
-   L:    42,
-   PRK:  "077709362c2e32df0ddc3f0dc47bba63" +
-         "90b6c73bb50f9c3122ec844ad7c2b3e5",
-   OKM:  "3cb25f25faacd57a90434f64d0362f2a" +
-         "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
-         "34007208d5b887185865",
-};
-
-// Test case 2
-
-var tc2 = {
-   IKM:  "000102030405060708090a0b0c0d0e0f" +
-         "101112131415161718191a1b1c1d1e1f" +
-         "202122232425262728292a2b2c2d2e2f" +
-         "303132333435363738393a3b3c3d3e3f" +
-         "404142434445464748494a4b4c4d4e4f",
-   salt: "606162636465666768696a6b6c6d6e6f" +
-         "707172737475767778797a7b7c7d7e7f" +
-         "808182838485868788898a8b8c8d8e8f" +
-         "909192939495969798999a9b9c9d9e9f" +
-         "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
-   info: "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" +
-         "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
-         "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" +
-         "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
-         "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
-   L:    82,
-   PRK:  "06a6b88c5853361a06104c9ceb35b45c" +
-         "ef760014904671014a193f40c15fc244",
-   OKM:  "b11e398dc80327a1c8e7f78c596a4934" +
-         "4f012eda2d4efad8a050cc4c19afa97c" +
-         "59045a99cac7827271cb41c65e590e09" +
-         "da3275600c2f09b8367793a9aca3db71" +
-         "cc30c58179ec3e87c14c01d5c1f3434f" +
-         "1d87",
-};
-
-// Test case 3
-
-var tc3 = {
-   IKM:  "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
-   salt: "",
-   info: "",
-   L:    42,
-   PRK:  "19ef24a32c717b167f33a91d6f648bdf" +
-         "96596776afdb6377ac434c1c293ccb04",
-   OKM:  "8da4e775a563c18f715f802a063c5a31" +
-         "b8a11f5c5ee1879ec3454e5f3c738d2d" +
-         "9d201395faa4b61a96c8",
-};
-
-function sha256HMAC(message, key) {
-  let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
-  return CryptoUtils.digestBytes(message, h);
-}
-
-function _hexToString(hex) {
-  let ret = "";
-  if (hex.length % 2 != 0) {
-    return false;
-  }
-
-  for (let i = 0; i < hex.length; i += 2) {
-    let cur = hex[i] + hex[i + 1];
-    ret += String.fromCharCode(parseInt(cur, 16));
-  }
-  return ret;
-}
-
-function extract_hex(salt, ikm) {
-  salt = _hexToString(salt);
-  ikm = _hexToString(ikm);
-  return CommonUtils.bytesAsHex(sha256HMAC(ikm, CryptoUtils.makeHMACKey(salt)));
-}
-
-function expand_hex(prk, info, len) {
-  prk = _hexToString(prk);
-  info = _hexToString(info);
-  return CommonUtils.bytesAsHex(CryptoUtils.hkdfExpand(prk, info, len));
-}
-
-function hkdf_hex(ikm, salt, info, len) {
-  ikm = _hexToString(ikm);
-  if (salt)
-    salt = _hexToString(salt);
-  info = _hexToString(info);
-  return CommonUtils.bytesAsHex(CryptoUtils.hkdf(ikm, salt, info, len));
-}
-
-// In bug 1437416 we thought we supplied a default for the salt but
-// actually ended up calling the platform c++ code with undefined as the
-// salt - which still ended up doing the right thing. Let's try and
-// codify that behaviour.
-let hkdf_tc1 = {
-  ikm: "foo",
-  info: "bar",
-  salt: undefined,
-  len: 64,
-  // As all inputs are known, we can pre-calculate the expected result:
-  // >>> tokenlib.utils.HKDF("foo", None, "bar", 64).encode("hex")
-  // 'f037f3ab189f485d0d93249f432def681a0305e39ef85f810e2f0b74d2078861fbd34318934b49de822c6148c8bb0785613e4b01176b47634e25eecd5e94ff3b'
-  result: "f037f3ab189f485d0d93249f432def681a0305e39ef85f810e2f0b74d2078861fbd34318934b49de822c6148c8bb0785613e4b01176b47634e25eecd5e94ff3b",
-};
-
-// same inputs, but this time with the default salt explicitly defined.
-// should give the same result.
-let hkdf_tc2 = {
-  ikm: "foo",
-  info: "bar",
-  salt: String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
-  len: 64,
-  result: hkdf_tc1.result,
-};
-
-function run_test() {
-  _("Verifying Test Case 1");
-  Assert.equal(extract_hex(tc1.salt, tc1.IKM), tc1.PRK);
-  Assert.equal(expand_hex(tc1.PRK, tc1.info, tc1.L), tc1.OKM);
-  Assert.equal(hkdf_hex(tc1.IKM, tc1.salt, tc1.info, tc1.L), tc1.OKM);
-
-  _("Verifying Test Case 2");
-  Assert.equal(extract_hex(tc2.salt, tc2.IKM), tc2.PRK);
-  Assert.equal(expand_hex(tc2.PRK, tc2.info, tc2.L), tc2.OKM);
-  Assert.equal(hkdf_hex(tc2.IKM, tc2.salt, tc2.info, tc2.L), tc2.OKM);
-
-  _("Verifying Test Case 3");
-  Assert.equal(extract_hex(tc3.salt, tc3.IKM), tc3.PRK);
-  Assert.equal(expand_hex(tc3.PRK, tc3.info, tc3.L), tc3.OKM);
-  Assert.equal(hkdf_hex(tc3.IKM, tc3.salt, tc3.info, tc3.L), tc3.OKM);
-  Assert.equal(hkdf_hex(tc3.IKM, undefined, tc3.info, tc3.L), tc3.OKM);
-
-  _("Verifying hkdf semantics");
-  for (let tc of [hkdf_tc1, hkdf_tc2]) {
-    let result = CommonUtils.bytesAsHex(CryptoUtils.hkdf(tc.ikm, tc.salt, tc.info, tc.len));
-    Assert.equal(result, tc.result);
-  }
-}
--- a/services/crypto/tests/unit/test_utils_httpmac.js
+++ b/services/crypto/tests/unit/test_utils_httpmac.js
@@ -1,69 +1,64 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://services-common/utils.js");
 ChromeUtils.import("resource://services-crypto/utils.js");
 
-function run_test() {
+add_test(function setup() {
   initTestLogging();
-
   run_next_test();
-}
+});
 
-add_test(function test_sha1() {
+add_task(async function test_sha1() {
   _("Ensure HTTP MAC SHA1 generation works as expected.");
 
   let id = "vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7";
   let key = "b8u1cc5iiio5o319og7hh8faf2gi5ym4aq0zwf112cv1287an65fudu5zj7zo7dz";
   let ts = 1329181221;
   let method = "GET";
   let nonce = "wGX71";
   let uri = CommonUtils.makeURI("http://10.250.2.176/alias/");
 
-  let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
+  let result = await CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
                                               {ts, nonce});
 
   Assert.equal(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
 
   Assert.equal(result.getHeader(),
                'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
                'ts="1329181221", nonce="wGX71", mac="jzh5chjQc2zFEvLbyHnPdX11Yck="');
 
   let ext = "EXTRA DATA; foo,bar=1";
 
-  result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
+  result = await CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
                                               {ts, nonce, ext});
   Assert.equal(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
   Assert.equal(result.getHeader(),
                'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
                'ts="1329181221", nonce="wGX71", mac="bNf4Fnt5k6DnhmyipLPkuZroH68=", ' +
                'ext="EXTRA DATA; foo,bar=1"');
-
-  run_next_test();
 });
 
-add_test(function test_nonce_length() {
+add_task(async function test_nonce_length() {
   _("Ensure custom nonce lengths are honoured.");
 
   function get_mac(length) {
     let uri = CommonUtils.makeURI("http://example.com/");
     return CryptoUtils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
       nonce_bytes: length,
     });
   }
 
-  let result = get_mac(12);
+  let result = await get_mac(12);
   Assert.equal(12, atob(result.nonce).length);
 
-  result = get_mac(2);
+  result = await get_mac(2);
   Assert.equal(2, atob(result.nonce).length);
 
-  result = get_mac(0);
+  result = await get_mac(0);
   Assert.equal(8, atob(result.nonce).length);
 
-  result = get_mac(-1);
+  result = await get_mac(-1);
   Assert.equal(8, atob(result.nonce).length);
-
-  run_next_test();
 });
deleted file mode 100644
--- a/services/crypto/tests/unit/test_utils_pbkdf2.js
+++ /dev/null
@@ -1,156 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-ChromeUtils.import("resource://services-crypto/utils.js");
-ChromeUtils.import("resource://services-common/utils.js");
-
-var {bytesAsHex: b2h} = CommonUtils;
-
-add_task(function test_pbkdf2() {
-  let symmKey16 = CryptoUtils.pbkdf2Generate("secret phrase", "DNXPzPpiwn", 4096, 16);
-  Assert.equal(symmKey16.length, 16);
-  Assert.equal(btoa(symmKey16), "d2zG0d2cBfXnRwMUGyMwyg==");
-  Assert.equal(CommonUtils.encodeBase32(symmKey16), "O5WMNUO5TQC7LZ2HAMKBWIZQZI======");
-  let symmKey32 = CryptoUtils.pbkdf2Generate("passphrase", "salt", 4096, 32);
-  Assert.equal(symmKey32.length, 32);
-});
-
-// http://tools.ietf.org/html/rfc6070
-// PBKDF2 HMAC-SHA1 Test Vectors
-add_task(function test_pbkdf2_hmac_sha1() {
-  let pbkdf2 = CryptoUtils.pbkdf2Generate;
-  let vectors = [
-    {P: "password",                    // (8 octets)
-     S: "salt",                        // (4 octets)
-     c: 1,
-     dkLen: 20,
-     DK: h("0c 60 c8 0f 96 1f 0e 71" +
-           "f3 a9 b5 24 af 60 12 06" +
-           "2f e0 37 a6"),             // (20 octets)
-    },
-
-    {P: "password",                    // (8 octets)
-     S: "salt",                        // (4 octets)
-     c: 2,
-     dkLen: 20,
-     DK: h("ea 6c 01 4d c7 2d 6f 8c" +
-           "cd 1e d9 2a ce 1d 41 f0" +
-           "d8 de 89 57"),             // (20 octets)
-    },
-
-    {P: "password",                    // (8 octets)
-     S: "salt",                        // (4 octets)
-     c: 4096,
-     dkLen: 20,
-     DK: h("4b 00 79 01 b7 65 48 9a" +
-           "be ad 49 d9 26 f7 21 d0" +
-           "65 a4 29 c1"),             // (20 octets)
-    },
-
-    // XXX Uncomment the following test after Bug 968567 lands
-    //
-    // XXX As it stands, I estimate that the CryptoUtils implementation will
-    // take approximately 16 hours in my 2.3GHz MacBook to perform this many
-    // rounds.
-    //
-    // {P: "password",                     // (8 octets)
-    //  S: "salt"                          // (4 octets)
-    //  c: 16777216,
-    //  dkLen = 20,
-    //  DK: h("ee fe 3d 61 cd 4d a4 e4"+
-    //        "e9 94 5b 3d 6b a2 15 8c"+
-    //        "26 34 e9 84"),             // (20 octets)
-    // },
-
-    {P: "passwordPASSWORDpassword",    // (24 octets)
-     S: "saltSALTsaltSALTsaltSALTsaltSALTsalt", // (36 octets)
-     c: 4096,
-     dkLen: 25,
-     DK: h("3d 2e ec 4f e4 1c 84 9b" +
-           "80 c8 d8 36 62 c0 e4 4a" +
-           "8b 29 1a 96 4c f2 f0 70" +
-           "38"),                      // (25 octets)
-
-    },
-
-    {P: "pass\0word",                  // (9 octets)
-     S: "sa\0lt",                      // (5 octets)
-     c: 4096,
-     dkLen: 16,
-     DK: h("56 fa 6a a7 55 48 09 9d" +
-           "cc 37 d7 f0 34 25 e0 c3"), // (16 octets)
-    },
-  ];
-
-  for (let v of vectors) {
-    Assert.equal(v.DK, b2h(pbkdf2(v.P, v.S, v.c, v.dkLen)));
-  }
-});
-
-// I can't find any normative ietf test vectors for pbkdf2 hmac-sha256.
-// The following vectors are derived with the same inputs as above (the sha1
-// test).  Results verified by users here:
-// https://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors
-add_task(function test_pbkdf2_hmac_sha256() {
-  let pbkdf2 = CryptoUtils.pbkdf2Generate;
-  let vectors = [
-    {P: "password",                    // (8 octets)
-     S: "salt",                        // (4 octets)
-     c: 1,
-     dkLen: 32,
-     DK: h("12 0f b6 cf fc f8 b3 2c" +
-           "43 e7 22 52 56 c4 f8 37" +
-           "a8 65 48 c9 2c cc 35 48" +
-           "08 05 98 7c b7 0b e1 7b"), // (32 octets)
-    },
-
-    {P: "password",                    // (8 octets)
-     S: "salt",                        // (4 octets)
-     c: 2,
-     dkLen: 32,
-     DK: h("ae 4d 0c 95 af 6b 46 d3" +
-           "2d 0a df f9 28 f0 6d d0" +
-           "2a 30 3f 8e f3 c2 51 df" +
-           "d6 e2 d8 5a 95 47 4c 43"), // (32 octets)
-    },
-
-    {P: "password",                    // (8 octets)
-     S: "salt",                        // (4 octets)
-     c: 4096,
-     dkLen: 32,
-     DK: h("c5 e4 78 d5 92 88 c8 41" +
-           "aa 53 0d b6 84 5c 4c 8d" +
-           "96 28 93 a0 01 ce 4e 11" +
-           "a4 96 38 73 aa 98 13 4a"), // (32 octets)
-    },
-
-    {P: "passwordPASSWORDpassword",    // (24 octets)
-     S: "saltSALTsaltSALTsaltSALTsaltSALTsalt", // (36 octets)
-     c: 4096,
-     dkLen: 40,
-     DK: h("34 8c 89 db cb d3 2b 2f" +
-           "32 d8 14 b8 11 6e 84 cf" +
-           "2b 17 34 7e bc 18 00 18" +
-           "1c 4e 2a 1f b8 dd 53 e1" +
-           "c6 35 51 8c 7d ac 47 e9"), // (40 octets)
-    },
-
-    {P: "pass\0word",                  // (9 octets)
-     S: "sa\0lt",                      // (5 octets)
-     c: 4096,
-     dkLen: 16,
-     DK: h("89 b6 9d 05 16 f8 29 89" +
-           "3c 69 62 26 65 0a 86 87"), // (16 octets)
-    },
-  ];
-
-  for (let v of vectors) {
-    Assert.equal(v.DK,
-        b2h(pbkdf2(v.P, v.S, v.c, v.dkLen, Ci.nsICryptoHMAC.SHA256, 32)));
-  }
-});
-
-// turn formatted test vectors into normal hex strings
-function h(hexStr) {
-  return hexStr.replace(/\s+/g, "");
-}
deleted file mode 100644
--- a/services/crypto/tests/unit/test_utils_sha1.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-_("Make sure sha1 digests works with various messages");
-
-ChromeUtils.import("resource://services-crypto/utils.js");
-
-function run_test() {
-  let mes1 = "hello";
-  let mes2 = "world";
-
-  let dig0 = CryptoUtils.UTF8AndSHA1(mes1);
-  Assert.equal(dig0,
-               "\xaa\xf4\xc6\x1d\xdc\xc5\xe8\xa2\xda\xbe\xde\x0f\x3b\x48\x2c\xd9\xae\xa9\x43\x4d");
-
-  _("Make sure right sha1 digests are generated");
-  let dig1 = CryptoUtils.sha1(mes1);
-  Assert.equal(dig1, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
-  let dig2 = CryptoUtils.sha1(mes2);
-  Assert.equal(dig2, "7c211433f02071597741e6ff5a8ea34789abbf43");
-  let dig12 = CryptoUtils.sha1(mes1 + mes2);
-  Assert.equal(dig12, "6adfb183a4a2c94a2f92dab5ade762a47889a5a1");
-  let dig21 = CryptoUtils.sha1(mes2 + mes1);
-  Assert.equal(dig21, "5715790a892990382d98858c4aa38d0617151575");
-
-  _("Repeated sha1s shouldn't change the digest");
-  Assert.equal(CryptoUtils.sha1(mes1), dig1);
-  Assert.equal(CryptoUtils.sha1(mes2), dig2);
-  Assert.equal(CryptoUtils.sha1(mes1 + mes2), dig12);
-  Assert.equal(CryptoUtils.sha1(mes2 + mes1), dig21);
-
-  _("Nested sha1 should work just fine");
-  let nest1 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes1)))));
-  Assert.equal(nest1, "23f340d0cff31e299158b3181b6bcc7e8c7f985a");
-  let nest2 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes2)))));
-  Assert.equal(nest2, "1f6453867e3fb9876ae429918a64cdb8dc5ff2d0");
-}
--- a/services/crypto/tests/unit/xpcshell.ini
+++ b/services/crypto/tests/unit/xpcshell.ini
@@ -11,12 +11,9 @@ support-files =
 # Bug 676977: test hangs consistently on Android
 skip-if = os == "android"
 [test_crypto_service.js]
 skip-if = (os == "android" || appname == 'thunderbird')
 [test_jwcrypto.js]
 skip-if = (os == "android" || appname == 'thunderbird')
 
 [test_utils_hawk.js]
-[test_utils_hkdfExpand.js]
 [test_utils_httpmac.js]
-[test_utils_pbkdf2.js]
-[test_utils_sha1.js]
--- a/services/fxaccounts/Credentials.jsm
+++ b/services/fxaccounts/Credentials.jsm
@@ -18,18 +18,16 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://services-crypto/utils.js");
 ChromeUtils.import("resource://services-common/utils.js");
 
 const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/";
 const PBKDF2_ROUNDS = 1000;
 const STRETCHED_PW_LENGTH_BYTES = 32;
 const HKDF_SALT = CommonUtils.hexToBytes("00");
 const HKDF_LENGTH = 32;
-const HMAC_ALGORITHM = Ci.nsICryptoHMAC.SHA256;
-const HMAC_LENGTH = 32;
 
 // loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
 // "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
 // default.
 const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
 try {
   this.LOG_LEVEL =
     Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
@@ -47,18 +45,16 @@ var Credentials = Object.freeze({
    * Make constants accessible to tests
    */
   constants: {
     PROTOCOL_VERSION,
     PBKDF2_ROUNDS,
     STRETCHED_PW_LENGTH_BYTES,
     HKDF_SALT,
     HKDF_LENGTH,
-    HMAC_ALGORITHM,
-    HMAC_LENGTH,
   },
 
   /**
    * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
    *
    * keyWord derivation for use as a salt.
    *
    *
@@ -91,38 +87,36 @@ var Credentials = Object.freeze({
   },
 
   setup(emailInput, passwordInput, options = {}) {
     return new Promise(resolve => {
       log.debug("setup credentials for " + emailInput);
 
       let hkdfSalt = options.hkdfSalt || HKDF_SALT;
       let hkdfLength = options.hkdfLength || HKDF_LENGTH;
-      let hmacLength = options.hmacLength || HMAC_LENGTH;
-      let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
       let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
       let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;
 
       let result = {};
 
       let password = CommonUtils.encodeUTF8(passwordInput);
       let salt = this.keyWordExtended("quickStretch", emailInput);
 
-      let runnable = () => {
+      let runnable = async () => {
         let start = Date.now();
-        let quickStretchedPW = CryptoUtils.pbkdf2Generate(
-            password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);
+        let quickStretchedPW = await CryptoUtils.pbkdf2Generate(
+            password, salt, pbkdf2Rounds, stretchedPWLength);
 
         result.quickStretchedPW = quickStretchedPW;
 
         result.authPW =
-          CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);
+          await CryptoUtils.hkdfLegacy(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);
 
         result.unwrapBKey =
-          CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);
+          await CryptoUtils.hkdfLegacy(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);
 
         log.debug("Credentials set up after " + (Date.now() - start) + " ms");
         resolve(result);
       };
 
       Services.tm.dispatchToMainThread(runnable);
       log.debug("Dispatched thread for credentials setup crypto work");
     });
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -966,17 +966,17 @@ FxAccountsInternal.prototype = {
       if (!userData) {
         throw new Error("Can't get keys; User is not signed in");
       }
       if (userData.kB) { // Bug 1426306 - Migrate from kB to derived keys.
         log.info("Migrating kB to derived keys.");
         const {uid, kB} = userData;
         await this.updateUserAccountData({
           uid,
-          ...this._deriveKeys(uid, CommonUtils.hexToBytes(kB)),
+          ...(await this._deriveKeys(uid, CommonUtils.hexToBytes(kB))),
           kA: null, // Remove kA and kB from storage.
           kB: null,
         });
         userData = await this.getUserAccountData();
       }
       if (DERIVED_KEYS_NAMES.every(k => userData[k])) {
         return currentState.resolve(userData);
       }
@@ -1033,17 +1033,17 @@ FxAccountsInternal.prototype = {
     // so that we don't risk getting into a weird state.
     let kBbytes = CryptoUtils.xor(CommonUtils.hexToBytes(data.unwrapBKey),
                                   wrapKB);
 
     if (logPII) {
       log.debug("kBbytes: " + kBbytes);
     }
     let updateData = {
-      ...this._deriveKeys(data.uid, kBbytes),
+      ...(await this._deriveKeys(data.uid, kBbytes)),
       keyFetchToken: null, // null values cause the item to be removed.
       unwrapBKey: null,
     };
 
     log.debug("Keys Obtained:" +
               DERIVED_KEYS_NAMES.map(k => `${k}=${!!updateData[k]}`).join(", "));
     if (logPII) {
       log.debug("Keys Obtained:" +
@@ -1057,42 +1057,42 @@ FxAccountsInternal.prototype = {
     await this.notifyObservers(ONVERIFIED_NOTIFICATION);
     // Some parts of the device registration depend on the Sync keys being available,
     // so let's re-trigger it now that we have them.
     await this.updateDeviceRegistration();
     data = await currentState.getUserAccountData();
     return currentState.resolve(data);
   },
 
-  _deriveKeys(uid, kBbytes) {
+  async _deriveKeys(uid, kBbytes) {
     return {
-      kSync: CommonUtils.bytesAsHex(this._deriveSyncKey(kBbytes)),
+      kSync: CommonUtils.bytesAsHex((await this._deriveSyncKey(kBbytes))),
       kXCS: CommonUtils.bytesAsHex(this._deriveXClientState(kBbytes)),
-      kExtSync: CommonUtils.bytesAsHex(this._deriveWebExtSyncStoreKey(kBbytes)),
+      kExtSync: CommonUtils.bytesAsHex((await this._deriveWebExtSyncStoreKey(kBbytes))),
       kExtKbHash: CommonUtils.bytesAsHex(this._deriveWebExtKbHash(uid, kBbytes)),
     };
   },
 
   /**
    * Derive the Sync Key given the byte string kB.
    *
    * @returns HKDF(kB, undefined, "identity.mozilla.com/picl/v1/oldsync", 64)
    */
   _deriveSyncKey(kBbytes) {
-    return CryptoUtils.hkdf(kBbytes, undefined,
+    return CryptoUtils.hkdfLegacy(kBbytes, undefined,
                             "identity.mozilla.com/picl/v1/oldsync", 2 * 32);
   },
 
   /**
    * Derive the WebExtensions Sync Storage Key given the byte string kB.
    *
    * @returns HKDF(kB, undefined, "identity.mozilla.com/picl/v1/chrome.storage.sync", 64)
    */
   _deriveWebExtSyncStoreKey(kBbytes) {
-    return CryptoUtils.hkdf(kBbytes, undefined,
+    return CryptoUtils.hkdfLegacy(kBbytes, undefined,
                             "identity.mozilla.com/picl/v1/chrome.storage.sync",
                             2 * 32);
   },
 
   /**
    * Derive the WebExtensions kbHash given the byte string kB.
    *
    * @returns SHA256(uid + kB)
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ b/services/fxaccounts/FxAccountsClient.jsm
@@ -182,19 +182,19 @@ this.FxAccountsClient.prototype = {
   /**
    * Check the status of a session given a session token
    *
    * @param sessionTokenHex
    *        The session token encoded in hex
    * @return Promise
    *        Resolves with a boolean indicating if the session is still valid
    */
-  sessionStatus(sessionTokenHex) {
-    return this._request("/session/status", "GET",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken")).then(
+  async sessionStatus(sessionTokenHex) {
+    const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
+    return this._request("/session/status", "GET", credentials).then(
         () => Promise.resolve(true),
         error => {
           if (isInvalidTokenError(error)) {
             return Promise.resolve(false);
           }
           throw error;
         }
       );
@@ -203,98 +203,97 @@ this.FxAccountsClient.prototype = {
   /**
    * Destroy the current session with the Firefox Account API server and its
    * associated device.
    *
    * @param sessionTokenHex
    *        The session token encoded in hex
    * @return Promise
    */
-  signOut(sessionTokenHex, options = {}) {
+  async signOut(sessionTokenHex, options = {}) {
+    const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
     let path = "/session/destroy";
     if (options.service) {
       path += "?service=" + encodeURIComponent(options.service);
     }
-    return this._request(path, "POST",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
+    return this._request(path, "POST", credentials);
   },
 
   /**
    * Check the verification status of the user's FxA email address
    *
    * @param sessionTokenHex
    *        The current session token encoded in hex
    * @return Promise
    */
-  recoveryEmailStatus(sessionTokenHex, options = {}) {
+  async recoveryEmailStatus(sessionTokenHex, options = {}) {
+    const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
     let path = "/recovery_email/status";
     if (options.reason) {
       path += "?reason=" + encodeURIComponent(options.reason);
     }
 
-    return this._request(path, "GET",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
+    return this._request(path, "GET", credentials);
   },
 
   /**
    * Resend the verification email for the user
    *
    * @param sessionTokenHex
    *        The current token encoded in hex
    * @return Promise
    */
-  resendVerificationEmail(sessionTokenHex) {
-    return this._request("/recovery_email/resend_code", "POST",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
+  async resendVerificationEmail(sessionTokenHex) {
+    const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
+    return this._request("/recovery_email/resend_code", "POST", credentials);
   },
 
   /**
    * Retrieve encryption keys
    *
    * @param keyFetchTokenHex
    *        A one-time use key fetch token encoded in hex
    * @return Promise
    *        Returns a promise that resolves to an object:
    *        {
    *          kA: an encryption key for recevorable data (bytes)
    *          wrapKB: an encryption key that requires knowledge of the
    *                  user's password (bytes)
    *        }
    */
-  accountKeys(keyFetchTokenHex) {
-    let creds = deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
+  async accountKeys(keyFetchTokenHex) {
+    let creds = await deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
     let keyRequestKey = creds.extra.slice(0, 32);
-    let morecreds = CryptoUtils.hkdf(keyRequestKey, undefined,
+    let morecreds = await CryptoUtils.hkdfLegacy(keyRequestKey, undefined,
                                      Credentials.keyWord("account/keys"), 3 * 32);
     let respHMACKey = morecreds.slice(0, 32);
     let respXORKey = morecreds.slice(32, 96);
 
-    return this._request("/account/keys", "GET", creds).then(resp => {
-      if (!resp.bundle) {
-        throw new Error("failed to retrieve keys");
-      }
+    const resp = await this._request("/account/keys", "GET", creds);
+    if (!resp.bundle) {
+      throw new Error("failed to retrieve keys");
+    }
 
-      let bundle = CommonUtils.hexToBytes(resp.bundle);
-      let mac = bundle.slice(-32);
+    let bundle = CommonUtils.hexToBytes(resp.bundle);
+    let mac = bundle.slice(-32);
 
-      let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
-        CryptoUtils.makeHMACKey(respHMACKey));
+    let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
+      CryptoUtils.makeHMACKey(respHMACKey));
 
-      let bundleMAC = CryptoUtils.digestBytes(bundle.slice(0, -32), hasher);
-      if (mac !== bundleMAC) {
-        throw new Error("error unbundling encryption keys");
-      }
+    let bundleMAC = CryptoUtils.digestBytes(bundle.slice(0, -32), hasher);
+    if (mac !== bundleMAC) {
+      throw new Error("error unbundling encryption keys");
+    }
 
-      let keyAWrapB = CryptoUtils.xor(respXORKey, bundle.slice(0, 64));
+    let keyAWrapB = CryptoUtils.xor(respXORKey, bundle.slice(0, 64));
 
-      return {
-        kA: keyAWrapB.slice(0, 32),
-        wrapKB: keyAWrapB.slice(32),
-      };
-    });
+    return {
+      kA: keyAWrapB.slice(0, 32),
+      wrapKB: keyAWrapB.slice(32),
+    };
   },
 
   /**
    * Sends a public key to the FxA API server and returns a signed certificate
    *
    * @param sessionTokenHex
    *        The current session token encoded in hex
    * @param serializedPublicKey
@@ -303,18 +302,18 @@ this.FxAccountsClient.prototype = {
    *        The lifetime of the certificate
    * @return Promise
    *        Returns a promise that resolves to the signed certificate.
    *        The certificate can be used to generate a Persona assertion.
    * @throws a new Error
    *         wrapping any of these HTTP code/errno pairs:
    *           https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-12
    */
-  signCertificate(sessionTokenHex, serializedPublicKey, lifetime) {
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
+  async signCertificate(sessionTokenHex, serializedPublicKey, lifetime) {
+    let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
 
     let body = { publicKey: serializedPublicKey,
                  duration: lifetime };
     return Promise.resolve()
       .then(_ => this._request("/certificate/sign", "POST", creds, body))
       .then(resp => resp.cert,
             err => {
               log.error("HAWK.signCertificate error: " + JSON.stringify(err));
@@ -392,20 +391,20 @@ this.FxAccountsClient.prototype = {
    *         Resolves to an object:
    *         {
    *           id: Device identifier
    *           createdAt: Creation time (milliseconds since epoch)
    *           name: Name of device
    *           type: Type of device (mobile|desktop)
    *         }
    */
-  registerDevice(sessionTokenHex, name, type, options = {}) {
+  async registerDevice(sessionTokenHex, name, type, options = {}) {
     let path = "/account/device";
 
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
+    let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
     let body = { name, type };
 
     if (options.pushCallback) {
       body.pushCallback = options.pushCallback;
     }
     if (options.pushPublicKey && options.pushAuthKey) {
       body.pushPublicKey = options.pushPublicKey;
       body.pushAuthKey = options.pushAuthKey;
@@ -427,73 +426,73 @@ this.FxAccountsClient.prototype = {
    * @param  excludedIds
    *         Devices to exclude when sending to all devices (deviceIds must be null).
    * @param  payload
    *         Data to send with the message
    * @return Promise
    *         Resolves to an empty object:
    *         {}
    */
-  notifyDevices(sessionTokenHex, deviceIds, excludedIds, payload, TTL = 0) {
+  async notifyDevices(sessionTokenHex, deviceIds, excludedIds, payload, TTL = 0) {
+    const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
     if (deviceIds && excludedIds) {
       throw new Error("You cannot specify excluded devices if deviceIds is set.");
     }
     const body = {
       to: deviceIds || "all",
       payload,
       TTL,
     };
     if (excludedIds) {
       body.excluded = excludedIds;
     }
-    return this._request("/account/devices/notify", "POST",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"), body);
+    return this._request("/account/devices/notify", "POST", credentials, body);
   },
 
   /**
    * Retrieves pending commands for our device.
    *
    * @method getCommands
    * @param  sessionTokenHex - Session token obtained from signIn
    * @param  [index] - If specified, only messages received after the one who
    *                   had that index will be retrieved.
    * @param  [limit] - Maximum number of messages to retrieve.
    */
-  getCommands(sessionTokenHex, {index, limit}) {
+  async getCommands(sessionTokenHex, {index, limit}) {
+    const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
     const params = new URLSearchParams();
     if (index != undefined) {
       params.set("index", index);
     }
     if (limit != undefined) {
       params.set("limit", limit);
     }
     const path = `/account/device/commands?${params.toString()}`;
-    return this._request(path, "GET",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"));
+    return this._request(path, "GET", credentials);
   },
 
   /**
    * Invokes a command on another device.
    *
    * @method invokeCommand
    * @param  sessionTokenHex - Session token obtained from signIn
    * @param  command - Name of the command to invoke
    * @param  target - Recipient device ID.
    * @param  payload
    * @return Promise
    *         Resolves to the request's response, (which should be an empty object)
    */
-  invokeCommand(sessionTokenHex, command, target, payload) {
+  async invokeCommand(sessionTokenHex, command, target, payload) {
+    const credentials = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
     const body = {
       command,
       target,
       payload,
     };
-    return this._request("/account/devices/invoke_command", "POST",
-      deriveHawkCredentials(sessionTokenHex, "sessionToken"), body);
+    return this._request("/account/devices/invoke_command", "POST", credentials, body);
   },
 
   /**
    * Update the session or name for an existing device
    *
    * @method updateDevice
    * @param  sessionTokenHex
    *         Session token obtained from signIn
@@ -513,20 +512,20 @@ this.FxAccountsClient.prototype = {
    *         `pushAuthKey` push auth secret (URLSafe Base64 string)
    * @return Promise
    *         Resolves to an object:
    *         {
    *           id: Device identifier
    *           name: Device name
    *         }
    */
-  updateDevice(sessionTokenHex, id, name, options = {}) {
+  async updateDevice(sessionTokenHex, id, name, options = {}) {
     let path = "/account/device";
 
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
+    let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
     let body = { id, name };
     if (options.pushCallback) {
       body.pushCallback = options.pushCallback;
     }
     if (options.pushPublicKey && options.pushAuthKey) {
       body.pushPublicKey = options.pushPublicKey;
       body.pushAuthKey = options.pushAuthKey;
     }
@@ -549,19 +548,19 @@ this.FxAccountsClient.prototype = {
    *             isCurrentDevice: Boolean indicating whether the item
    *                              represents the current device
    *             name: Device name
    *             type: Device type (mobile|desktop)
    *           },
    *           ...
    *         ]
    */
-  getDeviceList(sessionTokenHex) {
+  async getDeviceList(sessionTokenHex) {
     let path = "/account/devices";
-    let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
+    let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
 
     return this._request(path, "GET", creds, {});
   },
 
   _clearBackoff() {
       this.backoffError = null;
   },
 
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts.js
@@ -1441,31 +1441,30 @@ add_task(async function test_checkVerifi
 
   await fxa.checkVerificationStatus();
 
   user = await fxa.internal.getUserAccountData();
   Assert.equal(user.email, alice.email);
   Assert.equal(user.sessionToken, null);
 });
 
-add_test(function test_deriveKeys() {
+add_task(async function test_deriveKeys() {
   let account = MakeFxAccounts();
   let kBhex = "fd5c747806c07ce0b9d69dcfea144663e630b65ec4963596a22f24910d7dd15d";
   let kB = CommonUtils.hexToBytes(kBhex);
   const uid = "1ad7f502-4cc7-4ec1-a209-071fd2fae348";
 
-  const {kSync, kXCS, kExtSync, kExtKbHash} = account.internal._deriveKeys(uid, kB);
+  const {kSync, kXCS, kExtSync, kExtKbHash} = await account.internal._deriveKeys(uid, kB);
 
   Assert.equal(kSync, "ad501a50561be52b008878b2e0d8a73357778a712255f7722f497b5d4df14b05" +
                       "dc06afb836e1521e882f521eb34691d172337accdbf6e2a5b968b05a7bbb9885");
   Assert.equal(kXCS, "6ae94683571c7a7c54dab4700aa3995f");
   Assert.equal(kExtSync, "f5ccd9cfdefd9b1ac4d02c56964f59239d8dfa1ca326e63696982765c1352cdc" +
                          "5d78a5a9c633a6d25edfea0a6c221a3480332a49fd866f311c2e3508ddd07395");
   Assert.equal(kExtKbHash, "6192f1cc7dce95334455ba135fa1d8fca8f70e8f594ae318528de06f24ed0273");
-  run_next_test();
 });
 
 /*
  * End of tests.
  * Utility functions follow.
  */
 
 function expandHex(two_hex) {
--- a/services/fxaccounts/tests/xpcshell/test_credentials.js
+++ b/services/fxaccounts/tests/xpcshell/test_credentials.js
@@ -28,44 +28,44 @@ var vectors = {
 };
 
 // A simple test suite with no utf8 encoding madness.
 add_task(async function test_onepw_setup_credentials() {
   let email = "francine@example.org";
   let password = CommonUtils.encodeUTF8("i like pie");
 
   let pbkdf2 = CryptoUtils.pbkdf2Generate;
-  let hkdf = CryptoUtils.hkdf;
+  let hkdf = CryptoUtils.hkdfLegacy;
 
   // quickStretch the email
   let saltyEmail = Credentials.keyWordExtended("quickStretch", email);
 
   Assert.equal(b2h(saltyEmail), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a6672616e63696e65406578616d706c652e6f7267");
 
   let pbkdf2Rounds = 1000;
   let pbkdf2Len = 32;
 
-  let quickStretchedPW = pbkdf2(password, saltyEmail, pbkdf2Rounds, pbkdf2Len, Ci.nsICryptoHMAC.SHA256, 32);
+  let quickStretchedPW = await pbkdf2(password, saltyEmail, pbkdf2Rounds, pbkdf2Len);
   let quickStretchedActual = "6b88094c1c73bbf133223f300d101ed70837af48d9d2c1b6e7d38804b20cdde4";
   Assert.equal(b2h(quickStretchedPW), quickStretchedActual);
 
   // obtain hkdf info
   let authKeyInfo = Credentials.keyWord("authPW");
   Assert.equal(b2h(authKeyInfo), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f617574685057");
 
   // derive auth password
   let hkdfSalt = h2b("00");
   let hkdfLen = 32;
-  let authPW = hkdf(quickStretchedPW, hkdfSalt, authKeyInfo, hkdfLen);
+  let authPW = await hkdf(quickStretchedPW, hkdfSalt, authKeyInfo, hkdfLen);
 
   Assert.equal(b2h(authPW), "4b8dec7f48e7852658163601ff766124c312f9392af6c3d4e1a247eb439be342");
 
   // derive unwrap key
   let unwrapKeyInfo = Credentials.keyWord("unwrapBkey");
-  let unwrapKey = hkdf(quickStretchedPW, hkdfSalt, unwrapKeyInfo, hkdfLen);
+  let unwrapKey = await hkdf(quickStretchedPW, hkdfSalt, unwrapKeyInfo, hkdfLen);
 
   Assert.equal(b2h(unwrapKey), "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9");
 });
 
 add_task(async function test_client_stretch_kdf() {
   let expected = vectors["client stretch-KDF"];
 
   let email = h2s(expected.email);
@@ -74,18 +74,16 @@ add_task(async function test_client_stre
   // Intermediate value from sjcl implementation in fxa-js-client
   // The key thing is the c3a9 sequence in "andré"
   let salt = Credentials.keyWordExtended("quickStretch", email);
   Assert.equal(b2h(salt), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a616e6472c3a9406578616d706c652e6f7267");
 
   let options = {
     stretchedPassLength: 32,
     pbkdf2Rounds: 1000,
-    hmacAlgorithm: Ci.nsICryptoHMAC.SHA256,
-    hmacLength: 32,
     hkdfSalt: h2b("00"),
     hkdfLength: 32,
   };
 
   let results = await Credentials.setup(email, password, options);
 
   Assert.equal(expected.quickStretchedPW, b2h(results.quickStretchedPW),
       "quickStretchedPW is wrong");
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -596,31 +596,30 @@ this.BrowserIDManager.prototype = {
       await this._ensureValidToken();
     } catch (ex) {
       this._log.error("Failed to fetch a token for authentication", ex);
       return null;
     }
     if (!this._token) {
       return null;
     }
-    let credentials = {algorithm: "sha256",
-                       id: this._token.id,
+    let credentials = {id: this._token.id,
                        key: this._token.key,
                       };
     method = method || httpObject.method;
 
     // Get the local clock offset from the Firefox Accounts server.  This should
     // be close to the offset from the storage server.
     let options = {
       now: this._now(),
       localtimeOffsetMsec: this._localtimeOffsetMsec,
       credentials,
     };
 
-    let headerValue = CryptoUtils.computeHAWK(httpObject.uri, method, options);
+    let headerValue = await CryptoUtils.computeHAWK(httpObject.uri, method, options);
     return {headers: {authorization: headerValue.field}};
   },
 
   /**
    * Determine the cluster for the current user and update state.
    * Returns true if a new cluster URL was found and it is different from
    * the existing cluster URL, false otherwise.
    */
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -50,22 +50,20 @@ class HMACMismatch extends Error {
   }
 }
 
 /*
  * Utility functions
  */
 var Utils = {
   // Aliases from CryptoUtils.
-  generateRandomBytes: CryptoUtils.generateRandomBytes,
+  generateRandomBytesLegacy: CryptoUtils.generateRandomBytesLegacy,
   computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
   digestUTF8: CryptoUtils.digestUTF8,
   digestBytes: CryptoUtils.digestBytes,
-  sha1: CryptoUtils.sha1,
-  sha1Base32: CryptoUtils.sha1Base32,
   sha256: CryptoUtils.sha256,
   makeHMACKey: CryptoUtils.makeHMACKey,
   makeHMACHasher: CryptoUtils.makeHMACHasher,
   hkdfExpand: CryptoUtils.hkdfExpand,
   pbkdf2Generate: CryptoUtils.pbkdf2Generate,
   getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,
 
   /**
@@ -187,17 +185,17 @@ var Utils = {
     };
   },
 
   /**
    * GUIDs are 9 random bytes encoded with base64url (RFC 4648).
    * That makes them 12 characters long with 72 bits of entropy.
    */
   makeGUID: function makeGUID() {
-    return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
+    return CommonUtils.encodeBase64URL(Utils.generateRandomBytesLegacy(9));
   },
 
   _base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
   checkGUID: function checkGUID(guid) {
     return !!guid && this._base64url_regex.test(guid);
   },
 
   /**
--- a/services/sync/tests/unit/test_keys.js
+++ b/services/sync/tests/unit/test_keys.js
@@ -60,17 +60,17 @@ add_test(function test_set_invalid_value
     thrown = true;
     Assert.equal(ex.message.indexOf("Encryption key can only be set to"), 0);
   } finally {
     Assert.ok(thrown);
     thrown = false;
   }
 
   try {
-    bundle.hmacKey = Utils.generateRandomBytes(15);
+    bundle.hmacKey = Utils.generateRandomBytesLegacy(15);
   } catch (ex) {
     thrown = true;
     Assert.equal(ex.message.indexOf("HMAC key must be at least 128"), 0);
   } finally {
     Assert.ok(thrown);
     thrown = false;
   }
 
@@ -90,17 +90,17 @@ add_test(function test_set_invalid_value
     thrown = true;
     Assert.equal(ex.message.indexOf("HMAC key can only be set to"), 0);
   } finally {
     Assert.ok(thrown);
     thrown = false;
   }
 
   try {
-    bundle.hmacKey = Utils.generateRandomBytes(15);
+    bundle.hmacKey = Utils.generateRandomBytesLegacy(15);
   } catch (ex) {
     thrown = true;
     Assert.equal(ex.message.indexOf("HMAC key must be at least 128"), 0);
   } finally {
     Assert.ok(thrown);
     thrown = false;
   }
 
--- a/toolkit/components/extensions/ExtensionStorageSync.jsm
+++ b/toolkit/components/extensions/ExtensionStorageSync.jsm
@@ -429,17 +429,17 @@ class CryptoCollection {
 
   /**
    * Generate a new salt for use in hashing extension and record
    * IDs.
    *
    * @returns {string} A base64-encoded string of the salt
    */
   getNewSalt() {
-    return btoa(CryptoUtils.generateRandomBytes(STORAGE_SYNC_CRYPTO_SALT_LENGTH_BYTES));
+    return btoa(CryptoUtils.generateRandomBytesLegacy(STORAGE_SYNC_CRYPTO_SALT_LENGTH_BYTES));
   }
 
   /**
    * Retrieve the keyring record from the crypto collection.
    *
    * You can use this if you want to check metadata on the keyring
    * record rather than use the keyring itself.
    *
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_crypto.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_crypto.js
@@ -52,22 +52,25 @@ class StaticKeyEncryptionRemoteTransform
     this.keyBundle = keyBundle;
   }
 
   getKeys() {
     return Promise.resolve(this.keyBundle);
   }
 }
 const BORING_KB = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
-const STRETCHED_KEY = CryptoUtils.hkdf(BORING_KB, undefined, `testing storage.sync encryption`, 2 * 32);
-const KEY_BUNDLE = {
-  sha256HMACHasher: Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, Utils.makeHMACKey(STRETCHED_KEY.slice(0, 32))),
-  encryptionKeyB64: btoa(STRETCHED_KEY.slice(32, 64)),
-};
-const transformer = new StaticKeyEncryptionRemoteTransformer(KEY_BUNDLE);
+let transformer;
+add_task(async function setup() {
+  const STRETCHED_KEY = await CryptoUtils.hkdfLegacy(BORING_KB, undefined, `testing storage.sync encryption`, 2 * 32);
+  const KEY_BUNDLE = {
+    sha256HMACHasher: Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, Utils.makeHMACKey(STRETCHED_KEY.slice(0, 32))),
+    encryptionKeyB64: btoa(STRETCHED_KEY.slice(32, 64)),
+  };
+  transformer = new StaticKeyEncryptionRemoteTransformer(KEY_BUNDLE);
+});
 
 add_task(async function test_encryption_transformer_roundtrip() {
   const POSSIBLE_DATAS = [
     "string",
     2,          // number
     [1, 2, 3],  // array
     {key: "value"}, // object
   ];