Bug 1245527 - Rewrite U2F.cpp to use U2FTokenManager r?keeler r?ttaubert draft
authorJ.C. Jones <jjones@mozilla.com>
Mon, 11 Sep 2017 12:56:59 -0700
changeset 663073 8c0fa3b4675d355a651531290e72e7e3444d2fcf
parent 663072 9455755e101b0a6cffe0c22f0eec184f8e653ea1
child 663074 ba6f1aa7211876f7cada66a6b6e42f1320353568
push id79302
push userbmo:jjones@mozilla.com
push dateTue, 12 Sep 2017 14:54:04 +0000
reviewerskeeler, ttaubert
bugs1245527, 1328830
milestone57.0a1
Bug 1245527 - Rewrite U2F.cpp to use U2FTokenManager r?keeler r?ttaubert - This patch reworks the U2F module to asynchronously call U2FManager, which in turn handles constructing and managing the U2FTokenManager via IPC. - Add U2FTransaction{Parent,Child} implementations to mirror similar ones for WebAuthn - Rewrite all tests to compensate for U2F executing asynchronously now. - Used async tasks, used the manifest parameters for scheme, and generally made these cleaner. - The mochitest "pref =" functionality from Bug 1328830 doesn't support Android yet, causing breakage on Android. Rework the tests to go back to the old way of using iframes to test U2F. NOTE TO REVIEWERS: Since this is huge, I recommend the following: keeler - please review U2F.cpp/h, the tests, and the security-prefs.js. Most of the U2F logic is still in U2F.cpp like before, but there's been some reworking of how it is called. ttaubert - please review U2FManager, the Transaction classes, build changes, and the changes to nsGlobalWindow. All of these should be very similar to the WebAuthn code it's patterned off. MozReview-Commit-ID: C1ZN2ch66Rm
dom/base/nsGlobalWindow.cpp
dom/u2f/U2F.cpp
dom/u2f/U2F.h
dom/u2f/U2FAuthenticator.h
dom/u2f/U2FManager.cpp
dom/u2f/U2FManager.h
dom/u2f/U2FTransactionChild.cpp
dom/u2f/U2FTransactionChild.h
dom/u2f/U2FTransactionParent.cpp
dom/u2f/U2FTransactionParent.h
dom/u2f/U2FUtil.h
dom/u2f/moz.build
dom/u2f/tests/frame_appid_facet.html
dom/u2f/tests/frame_appid_facet_insecure.html
dom/u2f/tests/frame_appid_facet_subdomain.html
dom/u2f/tests/frame_multiple_keys.html
dom/u2f/tests/frame_no_token.html
dom/u2f/tests/frame_register.html
dom/u2f/tests/frame_register_sign.html
dom/u2f/tests/frame_utils.js
dom/u2f/tests/mochitest.ini
dom/u2f/tests/test_appid_facet.html
dom/u2f/tests/test_appid_facet_insecure.html
dom/u2f/tests/test_appid_facet_subdomain.html
dom/u2f/tests/test_multiple_keys.html
dom/u2f/tests/test_no_token.html
dom/u2f/tests/test_register.html
dom/u2f/tests/test_register_sign.html
dom/u2f/tests/test_util_methods.html
dom/u2f/tests/u2futil.js
dom/webauthn/WebAuthnManager.cpp
security/manager/ssl/security-prefs.js
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -5397,18 +5397,18 @@ nsGlobalWindow::GetCrypto(ErrorResult& a
 }
 
 mozilla::dom::U2F*
 nsGlobalWindow::GetU2f(ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
   if (!mU2F) {
-    RefPtr<U2F> u2f = new U2F();
-    u2f->Init(AsInner(), aError);
+    RefPtr<U2F> u2f = new U2F(AsInner());
+    u2f->Init(aError);
     if (NS_WARN_IF(aError.Failed())) {
       return nullptr;
     }
 
     mU2F = u2f;
   }
   return mU2F;
 }
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -1,1069 +1,389 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "hasht.h"
-#include "mozilla/dom/CallbackFunction.h"
-#include "mozilla/dom/ContentChild.h"
-#include "mozilla/dom/CryptoBuffer.h"
-#include "mozilla/dom/NSSU2FTokenRemote.h"
 #include "mozilla/dom/U2F.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/dom/WebCryptoCommon.h"
 #include "nsContentUtils.h"
-#include "nsINSSU2FToken.h"
 #include "nsNetCID.h"
-#include "nsNSSComponent.h"
-#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
 #include "nsURLParsers.h"
-#include "nsXPCOMCIDInternal.h"
-#include "pk11pub.h"
-
-using mozilla::dom::ContentChild;
+#include "U2FManager.h"
 
 namespace mozilla {
 namespace dom {
 
-#define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f_enable_softtoken"
-#define PREF_U2F_USBTOKEN_ENABLED  "security.webauth.u2f_enable_usbtoken"
+static mozilla::LazyLogModule gU2FLog("u2fmanager");
 
-NS_NAMED_LITERAL_CSTRING(kPoolName, "WebAuth_U2F-IO");
 NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment");
 NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion");
+NS_NAMED_LITERAL_STRING(kRequiredU2FVersion, "U2F_V2");
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
 
-static mozilla::LazyLogModule gU2FLog("u2f");
+static uint32_t
+AdjustedTimeoutMillis(const Optional<Nullable<int32_t>>& opt_aSeconds)
+{
+  uint32_t adjustedTimeoutMillis = 30000u;
+  if (opt_aSeconds.WasPassed() && !opt_aSeconds.Value().IsNull()) {
+    adjustedTimeoutMillis = opt_aSeconds.Value().Value() * 1000u;
+    adjustedTimeoutMillis = std::max(15000u, adjustedTimeoutMillis);
+    adjustedTimeoutMillis = std::min(120000u, adjustedTimeoutMillis);
+  }
+  return adjustedTimeoutMillis;
+}
 
 static nsresult
 AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
-                   const nsAString& aChallenge, CryptoBuffer& aClientData)
+                   const nsAString& aChallenge,
+                   /* out */ nsString& aClientData)
 {
   MOZ_ASSERT(NS_IsMainThread());
   U2FClientData clientDataObject;
   clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
   clientDataObject.mChallenge.Construct(aChallenge);
   clientDataObject.mOrigin.Construct(aOrigin);
 
-  nsAutoString json;
-  if (NS_WARN_IF(!clientDataObject.ToJSON(json))) {
+  if (NS_WARN_IF(!clientDataObject.ToJSON(aClientData))) {
     return NS_ERROR_FAILURE;
   }
 
-  if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
   return NS_OK;
 }
 
-U2FStatus::U2FStatus()
-  : mCount(0)
-  , mIsStopped(false)
-  , mReentrantMonitor("U2FStatus")
-{}
-
-U2FStatus::~U2FStatus()
-{}
-
-void
-U2FStatus::WaitGroupAdd()
+static void
+RegisteredKeysToScopedCredentialList(const nsAString& aAppId,
+  const nsTArray<RegisteredKey>& aKeys,
+  nsTArray<WebAuthnScopedCredentialDescriptor>& aList)
 {
-  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  for (const RegisteredKey& key : aKeys) {
+    // Check for required attributes
+    if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed() ||
+        key.mVersion.Value() != kRequiredU2FVersion) {
+      continue;
+    }
 
-  mCount += 1;
-  MOZ_LOG(gU2FLog, LogLevel::Debug,
-          ("U2FStatus::WaitGroupAdd, now %d", mCount));
-}
+    // If this key's mAppId doesn't match the invocation, we can't handle it.
+    if (key.mAppId.WasPassed() && !key.mAppId.Value().Equals(aAppId)) {
+      continue;
+    }
 
-void
-U2FStatus::WaitGroupDone()
-{
-  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    CryptoBuffer keyHandle;
+    nsresult rv = keyHandle.FromJwkBase64(key.mKeyHandle.Value());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
 
-  MOZ_ASSERT(mCount > 0);
-  mCount -= 1;
-  MOZ_LOG(gU2FLog, LogLevel::Debug,
-          ("U2FStatus::WaitGroupDone, now %d", mCount));
-  if (mCount == 0) {
-    mReentrantMonitor.NotifyAll();
+    WebAuthnScopedCredentialDescriptor c;
+    c.id() = keyHandle;
+    aList.AppendElement(c);
   }
 }
 
-void
-U2FStatus::WaitGroupWait()
-{
-  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  MOZ_LOG(gU2FLog, LogLevel::Debug,
-          ("U2FStatus::WaitGroupWait, now %d", mCount));
-
-  while (mCount > 0) {
-    mReentrantMonitor.Wait();
-  }
-
-  MOZ_ASSERT(mCount == 0);
-  MOZ_LOG(gU2FLog, LogLevel::Debug,
-          ("U2FStatus::Wait completed, now count=%d stopped=%d", mCount,
-           mIsStopped));
-}
-
-void
-U2FStatus::Stop(const ErrorCode aErrorCode)
-{
-  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  MOZ_ASSERT(!mIsStopped);
-  mIsStopped = true;
-  mErrorCode = aErrorCode;
-
-  // TODO: Let WaitGroupWait exit early upon a Stop. Requires consideration of
-  // threads calling IsStopped() followed by WaitGroupDone(). Right now, Stop
-  // prompts work tasks to end early, but it could also prompt an immediate
-  // "Go ahead" to the thread waiting at WaitGroupWait.
-}
-
-void
-U2FStatus::Stop(const ErrorCode aErrorCode, const nsAString& aResponse)
-{
-  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  Stop(aErrorCode);
-  mResponse = aResponse;
-}
-
-bool
-U2FStatus::IsStopped()
-{
-  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  return mIsStopped;
-}
-
-ErrorCode
-U2FStatus::GetErrorCode()
-{
-  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  MOZ_ASSERT(mIsStopped);
-  return mErrorCode;
-}
-
-nsString
-U2FStatus::GetResponse()
-{
-  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  MOZ_ASSERT(mIsStopped);
-  return mResponse;
-}
-
-U2FTask::U2FTask(const nsAString& aOrigin,
-                 const nsAString& aAppId,
-                 const Authenticator& aAuthenticator,
-                 nsISerialEventTarget* aEventTarget)
-  : Runnable("dom::U2FTask")
-  , mOrigin(aOrigin)
-  , mAppId(aAppId)
-  , mAuthenticator(aAuthenticator)
-  , mEventTarget(aEventTarget)
-{}
-
-U2FTask::~U2FTask()
-{}
-
-RefPtr<U2FPromise>
-U2FTask::Execute()
-{
-  RefPtr<U2FPromise> p = mPromise.Ensure(__func__);
-
-  nsCOMPtr<nsIRunnable> r(this);
-
-  // TODO: Use a thread pool here, but we have to solve the PContentChild issues
-  // of being in a worker thread.
-  mEventTarget->Dispatch(r.forget());
-  return p;
-}
-
-U2FPrepTask::U2FPrepTask(const Authenticator& aAuthenticator,
-                         nsISerialEventTarget* aEventTarget)
-  : Runnable("dom::U2FPrepTask")
-  , mAuthenticator(aAuthenticator)
-  , mEventTarget(aEventTarget)
-{}
-
-U2FPrepTask::~U2FPrepTask()
-{}
-
-RefPtr<U2FPrepPromise>
-U2FPrepTask::Execute()
-{
-  RefPtr<U2FPrepPromise> p = mPromise.Ensure(__func__);
-
-  nsCOMPtr<nsIRunnable> r(this);
-
-  // TODO: Use a thread pool here, but we have to solve the PContentChild issues
-  // of being in a worker thread.
-  mEventTarget->Dispatch(r.forget());
-  return p;
-}
-
-U2FIsRegisteredTask::U2FIsRegisteredTask(const Authenticator& aAuthenticator,
-                                         const LocalRegisteredKey& aRegisteredKey,
-                                         const CryptoBuffer& aAppParam,
-                                         nsISerialEventTarget* aEventTarget)
-  : U2FPrepTask(aAuthenticator, aEventTarget)
-  , mRegisteredKey(aRegisteredKey)
-  , mAppParam(aAppParam)
-{}
-
-U2FIsRegisteredTask::~U2FIsRegisteredTask()
-{}
-
-NS_IMETHODIMP
-U2FIsRegisteredTask::Run()
-{
-  bool isCompatible = false;
-  nsresult rv = mAuthenticator->IsCompatibleVersion(mRegisteredKey.mVersion,
-                                                    &isCompatible);
-  if (NS_FAILED(rv)) {
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!isCompatible) {
-    mPromise.Reject(ErrorCode::BAD_REQUEST, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  // Decode the key handle
-  CryptoBuffer keyHandle;
-  rv = keyHandle.FromJwkBase64(mRegisteredKey.mKeyHandle);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mPromise.Reject(ErrorCode::BAD_REQUEST, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  // We ignore mTransports, as it is intended to be used for sorting the
-  // available devices by preference, but is not an exclusion factor.
-
-  bool isRegistered = false;
-  rv = mAuthenticator->IsRegistered(keyHandle.Elements(), keyHandle.Length(),
-                                    mAppParam.Elements(), mAppParam.Length(),
-                                    &isRegistered);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  if (isRegistered) {
-    mPromise.Reject(ErrorCode::DEVICE_INELIGIBLE, __func__);
-    return NS_OK;
-  }
-
-  mPromise.Resolve(mAuthenticator, __func__);
-  return NS_OK;
-}
-
-U2FRegisterTask::U2FRegisterTask(const nsAString& aOrigin,
-                                 const nsAString& aAppId,
-                                 const Authenticator& aAuthenticator,
-                                 const CryptoBuffer& aAppParam,
-                                 const CryptoBuffer& aChallengeParam,
-                                 const LocalRegisterRequest& aRegisterEntry,
-                                 nsISerialEventTarget* aEventTarget)
-  : U2FTask(aOrigin, aAppId, aAuthenticator, aEventTarget)
-  , mAppParam(aAppParam)
-  , mChallengeParam(aChallengeParam)
-  , mRegisterEntry(aRegisterEntry)
-{}
-
-U2FRegisterTask::~U2FRegisterTask()
-{}
-
-NS_IMETHODIMP
-U2FRegisterTask::Run()
+static ErrorCode
+EvaluateAppID(const nsString& aOrigin, /* in/out */ nsString& aAppId)
 {
-  bool isCompatible = false;
-  nsresult rv = mAuthenticator->IsCompatibleVersion(mRegisterEntry.mVersion,
-                                                    &isCompatible);
-  if (NS_FAILED(rv)) {
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!isCompatible) {
-    mPromise.Reject(ErrorCode::BAD_REQUEST, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  uint8_t* buffer;
-  uint32_t bufferlen;
-  rv = mAuthenticator->Register(mAppParam.Elements(),
-                                mAppParam.Length(),
-                                mChallengeParam.Elements(),
-                                mChallengeParam.Length(),
-                                &buffer, &bufferlen);
-  if (NS_WARN_IF(NS_FAILED(rv)))  {
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  MOZ_ASSERT(buffer);
-  CryptoBuffer regData;
-  if (NS_WARN_IF(!regData.Assign(buffer, bufferlen))) {
-    free(buffer);
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  free(buffer);
-
-  // Assemble a response object to return
-  nsString clientDataBase64;
-  nsString registrationDataBase64;
-  nsresult rvClientData = mRegisterEntry.mClientData.ToJwkBase64(clientDataBase64);
-  nsresult rvRegistrationData = regData.ToJwkBase64(registrationDataBase64);
-
-  if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
-      NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  RegisterResponse response;
-  response.mClientData.Construct(clientDataBase64);
-  response.mRegistrationData.Construct(registrationDataBase64);
-  response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
-  nsString responseStr;
-  if (NS_WARN_IF(!response.ToJSON(responseStr))) {
-    return NS_ERROR_FAILURE;
-  }
-  mPromise.Resolve(responseStr, __func__);
-  return NS_OK;
-}
-
-U2FSignTask::U2FSignTask(const nsAString& aOrigin,
-                         const nsAString& aAppId,
-                         const nsAString& aVersion,
-                         const Authenticator& aAuthenticator,
-                         const CryptoBuffer& aAppParam,
-                         const CryptoBuffer& aChallengeParam,
-                         const CryptoBuffer& aClientData,
-                         const CryptoBuffer& aKeyHandle,
-                         nsISerialEventTarget* aEventTarget)
-  : U2FTask(aOrigin, aAppId, aAuthenticator, aEventTarget)
-  , mVersion(aVersion)
-  , mAppParam(aAppParam)
-  , mChallengeParam(aChallengeParam)
-  , mClientData(aClientData)
-  , mKeyHandle(aKeyHandle)
-{}
-
-U2FSignTask::~U2FSignTask()
-{}
-
-NS_IMETHODIMP
-U2FSignTask::Run()
-{
-  bool isCompatible = false;
-  nsresult rv = mAuthenticator->IsCompatibleVersion(mVersion, &isCompatible);
-  if (NS_FAILED(rv)) {
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!isCompatible) {
-    mPromise.Reject(ErrorCode::BAD_REQUEST, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  bool isRegistered = false;
-  rv = mAuthenticator->IsRegistered(mKeyHandle.Elements(), mKeyHandle.Length(),
-                                    mAppParam.Elements(), mAppParam.Length(),
-                                    &isRegistered);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  if (!isRegistered) {
-    mPromise.Reject(ErrorCode::DEVICE_INELIGIBLE, __func__);
-    return NS_OK;
-  }
-
-  CryptoBuffer signatureData;
-  uint8_t* buffer;
-  uint32_t bufferlen;
-  rv = mAuthenticator->Sign(mAppParam.Elements(), mAppParam.Length(),
-                            mChallengeParam.Elements(), mChallengeParam.Length(),
-                            mKeyHandle.Elements(), mKeyHandle.Length(),
-                            &buffer, &bufferlen);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  MOZ_ASSERT(buffer);
-  if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
-    free(buffer);
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  free(buffer);
-
-  // Assemble a response object to return
-  nsString clientDataBase64;
-  nsString signatureDataBase64;
-  nsString keyHandleBase64;
-  nsresult rvClientData = mClientData.ToJwkBase64(clientDataBase64);
-  nsresult rvSignatureData = signatureData.ToJwkBase64(signatureDataBase64);
-  nsresult rvKeyHandle = mKeyHandle.ToJwkBase64(keyHandleBase64);
-  if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
-      NS_WARN_IF(NS_FAILED(rvSignatureData) ||
-      NS_WARN_IF(NS_FAILED(rvKeyHandle)))) {
-    mPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
-    return NS_ERROR_FAILURE;
-  }
-
-  SignResponse response;
-  response.mKeyHandle.Construct(keyHandleBase64);
-  response.mClientData.Construct(clientDataBase64);
-  response.mSignatureData.Construct(signatureDataBase64);
-  response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
-  nsString responseStr;
-  if (NS_WARN_IF(!response.ToJSON(responseStr))) {
-    return NS_ERROR_FAILURE;
-  }
-  mPromise.Resolve(responseStr, __func__);
-  return NS_OK;
-}
-
-U2FRunnable::U2FRunnable(const nsAString& aOrigin, const nsAString& aAppId,
-                         nsISerialEventTarget* aEventTarget)
-  : Runnable("dom::U2FRunnable")
-  , mOrigin(aOrigin)
-  , mAppId(aAppId)
-  , mEventTarget(aEventTarget)
-{}
-
-U2FRunnable::~U2FRunnable()
-{}
-
-// EvaluateAppIDAndRunTask determines whether the supplied FIDO AppID is valid for
-// the current FacetID, e.g., the current origin.
-// See https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-appid-and-facets.html
-// for a description of the algorithm.
-ErrorCode
-U2FRunnable::EvaluateAppID()
-{
-  nsCOMPtr<nsIURLParser> urlParser =
-      do_GetService(NS_STDURLPARSER_CONTRACTID);
-
-  MOZ_ASSERT(urlParser);
-
-  uint32_t facetSchemePos;
-  int32_t facetSchemeLen;
-  uint32_t facetAuthPos;
-  int32_t facetAuthLen;
   // Facet is the specification's way of referring to the web origin.
-  nsAutoCString facetUrl = NS_ConvertUTF16toUTF8(mOrigin);
-  nsresult rv = urlParser->ParseURL(facetUrl.get(), mOrigin.Length(),
-                                    &facetSchemePos, &facetSchemeLen,
-                                    &facetAuthPos, &facetAuthLen,
-                                    nullptr, nullptr);      // ignore path
-  if (NS_WARN_IF(NS_FAILED(rv))) {
+  nsAutoCString facetString = NS_ConvertUTF16toUTF8(aOrigin);
+  nsCOMPtr<nsIURI> facetUri;
+  if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
     return ErrorCode::BAD_REQUEST;
   }
 
-  nsAutoCString facetScheme(Substring(facetUrl, facetSchemePos, facetSchemeLen));
-  nsAutoCString facetAuth(Substring(facetUrl, facetAuthPos, facetAuthLen));
-
-  uint32_t appIdSchemePos;
-  int32_t appIdSchemeLen;
-  uint32_t appIdAuthPos;
-  int32_t appIdAuthLen;
-  // AppID is user-supplied. It's quite possible for this parse to fail.
-  nsAutoCString appIdUrl = NS_ConvertUTF16toUTF8(mAppId);
-  rv = urlParser->ParseURL(appIdUrl.get(), mAppId.Length(),
-                           &appIdSchemePos, &appIdSchemeLen,
-                           &appIdAuthPos, &appIdAuthLen,
-                           nullptr, nullptr);      // ignore path
-  if (NS_FAILED(rv)) {
-    return ErrorCode::BAD_REQUEST;
-  }
-
-  nsAutoCString appIdScheme(Substring(appIdUrl, appIdSchemePos, appIdSchemeLen));
-  nsAutoCString appIdAuth(Substring(appIdUrl, appIdAuthPos, appIdAuthLen));
-
   // If the facetId (origin) is not HTTPS, reject
-  if (!facetScheme.LowerCaseEqualsLiteral("https")) {
+  bool facetIsHttps = false;
+  if (NS_FAILED(facetUri->SchemeIs("https", &facetIsHttps)) || !facetIsHttps) {
     return ErrorCode::BAD_REQUEST;
   }
 
   // If the appId is empty or null, overwrite it with the facetId and accept
-  if (mAppId.IsEmpty() || mAppId.EqualsLiteral("null")) {
-    mAppId.Assign(mOrigin);
+  if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
+    aAppId.Assign(aOrigin);
     return ErrorCode::OK;
   }
 
+  // AppID is user-supplied. It's quite possible for this parse to fail.
+  nsAutoCString appIdString = NS_ConvertUTF16toUTF8(aAppId);
+  nsCOMPtr<nsIURI> appIdUri;
+  if (NS_FAILED(NS_NewURI(getter_AddRefs(appIdUri), appIdString))) {
+    return ErrorCode::BAD_REQUEST;
+  }
+
   // if the appId URL is not HTTPS, reject.
-  if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
+  bool appIdIsHttps = false;
+  if (NS_FAILED(appIdUri->SchemeIs("https", &appIdIsHttps)) || !appIdIsHttps) {
     return ErrorCode::BAD_REQUEST;
   }
 
-  // If the facetId and the appId auths match, accept
-  if (facetAuth == appIdAuth) {
+  // If the facetId and the appId hosts match, accept
+  nsAutoCString facetHost;
+  if (NS_FAILED(facetUri->GetHost(facetHost))) {
+    return ErrorCode::BAD_REQUEST;
+  }
+  nsAutoCString appIdHost;
+  if (NS_FAILED(appIdUri->GetHost(appIdHost))) {
+    return ErrorCode::BAD_REQUEST;
+  }
+  if (facetHost.Equals(appIdHost)) {
     return ErrorCode::OK;
   }
 
   // TODO(Bug 1244959) Implement the remaining algorithm.
   return ErrorCode::BAD_REQUEST;
 }
 
-U2FRegisterRunnable::U2FRegisterRunnable(const nsAString& aOrigin,
-                                         const nsAString& aAppId,
-                                         const Sequence<RegisterRequest>& aRegisterRequests,
-                                         const Sequence<RegisteredKey>& aRegisteredKeys,
-                                         const Sequence<Authenticator>& aAuthenticators,
-                                         U2FRegisterCallback* aCallback,
-                                         nsISerialEventTarget* aEventTarget)
-  : U2FRunnable(aOrigin, aAppId, aEventTarget)
-  , mAuthenticators(aAuthenticators)
-  // U2FRegisterCallback does not support threadsafe refcounting, and must be
-  // used and destroyed on main.
-  , mCallback(new nsMainThreadPtrHolder<U2FRegisterCallback>(
-      "U2FRegisterRunnable::mCallback", aCallback))
+template<typename T, typename C>
+static void
+ExecuteCallback(T& aResp, Maybe<nsMainThreadPtrHandle<C>>& aCb)
 {
   MOZ_ASSERT(NS_IsMainThread());
-
-  // The WebIDL dictionary types RegisterRequest and RegisteredKey cannot
-  // be copied to this thread, so store them serialized.
-  for (const RegisterRequest& req : aRegisterRequests) {
-    // Check for required attributes
-    if (!req.mChallenge.WasPassed() || !req.mVersion.WasPassed()) {
-      continue;
-    }
-
-    LocalRegisterRequest localReq;
-    localReq.mVersion = req.mVersion.Value();
-    localReq.mChallenge = req.mChallenge.Value();
-
-    nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment,
-                                     localReq.mChallenge, localReq.mClientData);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      continue;
-    }
-
-    mRegisterRequests.AppendElement(localReq);
+  if (aCb.isNothing()) {
+    return;
   }
 
-  for (const RegisteredKey& key : aRegisteredKeys) {
-    // Check for required attributes
-    if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed()) {
-      continue;
-    }
+  ErrorResult error;
+  aCb.ref()->Call(aResp, error);
+  NS_WARNING_ASSERTION(!error.Failed(), "dom::U2F::Promise callback failed");
+  error.SuppressException(); // Useful exceptions already emitted
 
-    LocalRegisteredKey localKey;
-    localKey.mVersion = key.mVersion.Value();
-    localKey.mKeyHandle = key.mKeyHandle.Value();
-    if (key.mAppId.WasPassed()) {
-      localKey.mAppId.SetValue(key.mAppId.Value());
-    }
-
-    mRegisteredKeys.AppendElement(localKey);
-  }
+  aCb.reset();
+  MOZ_ASSERT(aCb.isNothing());
 }
 
-U2FRegisterRunnable::~U2FRegisterRunnable()
+U2F::U2F(nsPIDOMWindowInner* aParent)
+  : mParent(aParent)
 {
-  nsNSSShutDownPreventionLock locker;
-
-  if (isAlreadyShutDown()) {
-    return;
-  }
-  shutdown(ShutdownCalledFrom::Object);
 }
 
-void
-U2FRegisterRunnable::SetTimeout(const int32_t aTimeoutMillis)
+U2F::~U2F()
 {
-  opt_mTimeoutSeconds.SetValue(aTimeoutMillis);
+  mPromiseHolder.DisconnectIfExists();
+  mRegisterCallback.reset();
+  mSignCallback.reset();
 }
 
 void
-U2FRegisterRunnable::SendResponse(const RegisterResponse& aResponse)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  ErrorResult rv;
-  mCallback->Call(aResponse, rv);
-  NS_WARNING_ASSERTION(!rv.Failed(), "callback failed");
-  // Useful exceptions already got reported.
-  rv.SuppressException();
-}
-
-NS_IMETHODIMP
-U2FRegisterRunnable::Run()
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Create a Status object to keep track of when we're done
-  RefPtr<U2FStatus> status = new U2FStatus();
-
-  // Evaluate the AppID
-  ErrorCode appIdResult = EvaluateAppID();
-  if (appIdResult != ErrorCode::OK) {
-    status->Stop(appIdResult);
-  }
-
-  // Produce the AppParam from the current AppID
-  nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
-  CryptoBuffer appParam;
-  if (!appParam.SetLength(SHA256_LENGTH, fallible)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  // Note: This could use nsICryptoHash to avoid having to interact with NSS
-  // directly.
-  SECStatus srv;
-  srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
-                     reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
-                     cAppId.Length());
-  if (srv != SECSuccess) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // First, we must determine if any of the RegisteredKeys are already
-  // registered, e.g., in the whitelist.
-  for (LocalRegisteredKey key : mRegisteredKeys) {
-    nsTArray<RefPtr<U2FPrepPromise>> prepPromiseList;
-    for (const Authenticator& token : mAuthenticators) {
-      RefPtr<U2FIsRegisteredTask> compTask =
-        new U2FIsRegisteredTask(token, key, appParam, mEventTarget);
-      prepPromiseList.AppendElement(compTask->Execute());
-    }
-
-    // Treat each call to Promise::All as a work unit, as it completes together
-    status->WaitGroupAdd();
-
-    U2FPrepPromise::All(mEventTarget, prepPromiseList)
-    ->Then(mEventTarget, __func__,
-      [&status] (const nsTArray<Authenticator>& aTokens) {
-        MOZ_LOG(gU2FLog, LogLevel::Debug,
-                ("ALL: None of the RegisteredKeys were recognized. n=%zu",
-                 aTokens.Length()));
-
-        status->WaitGroupDone();
-      },
-      [&status] (ErrorCode aErrorCode) {
-        status->Stop(aErrorCode);
-        status->WaitGroupDone();
-    });
-  }
-
-  // Wait for all the IsRegistered tasks to complete
-  status->WaitGroupWait();
-
-  // Check to see whether we're supposed to stop, because one of the keys was
-  // recognized.
-  if (status->IsStopped()) {
-    status->WaitGroupAdd();
-    mEventTarget->Dispatch(NS_NewRunnableFunction(
-      "dom::U2FRegisterRunnable::Run",
-      [&status, this] () {
-        RegisterResponse response;
-        response.mErrorCode.Construct(
-            static_cast<uint32_t>(status->GetErrorCode()));
-        SendResponse(response);
-        status->WaitGroupDone();
-      }));
-
-    // Don't exit until the main thread runnable completes
-    status->WaitGroupWait();
-    return NS_OK;
-  }
-
-  // Now proceed to actually register a new key.
-  for (LocalRegisterRequest req : mRegisterRequests) {
-    // Hash the ClientData into the ChallengeParam
-    CryptoBuffer challengeParam;
-    if (!challengeParam.SetLength(SHA256_LENGTH, fallible)) {
-      continue;
-    }
-
-    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
-                       req.mClientData.Elements(), req.mClientData.Length());
-    if (srv != SECSuccess) {
-      continue;
-    }
-
-    for (const Authenticator& token : mAuthenticators) {
-      RefPtr<U2FRegisterTask> registerTask = new U2FRegisterTask(mOrigin, mAppId,
-                                                                 token, appParam,
-                                                                 challengeParam,
-                                                                 req,
-                                                                 mEventTarget);
-      status->WaitGroupAdd();
-
-      registerTask->Execute()->Then(mEventTarget, __func__,
-        [&status] (nsString aResponse) {
-          if (!status->IsStopped()) {
-            status->Stop(ErrorCode::OK, aResponse);
-          }
-          status->WaitGroupDone();
-        },
-        [&status] (ErrorCode aErrorCode) {
-          // Ignore the failing error code, as we only want the first success.
-          // U2F devices don't provide much for error codes anyway, so if
-          // they all fail we'll return DEVICE_INELIGIBLE.
-          status->WaitGroupDone();
-     });
-    }
-  }
-
-  // Wait until the first key is successfuly generated
-  status->WaitGroupWait();
-
-  // If none of the tasks completed, then nothing could satisfy.
-  if (!status->IsStopped()) {
-    status->Stop(ErrorCode::BAD_REQUEST);
-  }
-
-  // Transmit back to the JS engine from the Main Thread
-  status->WaitGroupAdd();
-  mEventTarget->Dispatch(NS_NewRunnableFunction(
-    "dom::U2FRegisterRunnable::Run",
-    [&status, this] () {
-      RegisterResponse response;
-      if (status->GetErrorCode() == ErrorCode::OK) {
-        response.Init(status->GetResponse());
-      } else {
-        response.mErrorCode.Construct(
-            static_cast<uint32_t>(status->GetErrorCode()));
-      }
-      SendResponse(response);
-      status->WaitGroupDone();
-    }));
-
-  // TODO: Add timeouts, Bug 1301793
-  status->WaitGroupWait();
-  return NS_OK;
-}
-
-U2FSignRunnable::U2FSignRunnable(const nsAString& aOrigin,
-                                 const nsAString& aAppId,
-                                 const nsAString& aChallenge,
-                                 const Sequence<RegisteredKey>& aRegisteredKeys,
-                                 const Sequence<Authenticator>& aAuthenticators,
-                                 U2FSignCallback* aCallback,
-                                 nsISerialEventTarget* aEventTarget)
-  : U2FRunnable(aOrigin, aAppId, aEventTarget)
-  , mAuthenticators(aAuthenticators)
-  // U2FSignCallback does not support threadsafe refcounting, and must be used
-  // and destroyed on main.
-  , mCallback(new nsMainThreadPtrHolder<U2FSignCallback>(
-      "U2FSignRunnable::mCallback", aCallback))
+U2F::Init(ErrorResult& aRv)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // Convert WebIDL objects to generic structs to pass between threads
-  for (const RegisteredKey& key : aRegisteredKeys) {
-    // Check for required attributes
-    if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed()) {
-      continue;
-    }
-
-    LocalRegisteredKey localKey;
-    localKey.mVersion = key.mVersion.Value();
-    localKey.mKeyHandle = key.mKeyHandle.Value();
-    if (key.mAppId.WasPassed()) {
-      localKey.mAppId.SetValue(key.mAppId.Value());
-    }
-
-    mRegisteredKeys.AppendElement(localKey);
-  }
-
-  // Assemble a clientData object
-  nsresult rv = AssembleClientData(aOrigin, kGetAssertion, aChallenge,
-                                   mClientData);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    MOZ_LOG(gU2FLog, LogLevel::Warning,
-            ("Failed to AssembleClientData for the U2FSignRunnable."));
-    return;
-  }
-}
-
-U2FSignRunnable::~U2FSignRunnable()
-{
-  nsNSSShutDownPreventionLock locker;
-
-  if (isAlreadyShutDown()) {
-    return;
-  }
-  shutdown(ShutdownCalledFrom::Object);
-}
-
-void
-U2FSignRunnable::SetTimeout(const int32_t aTimeoutMillis)
-{
-  opt_mTimeoutSeconds.SetValue(aTimeoutMillis);
-}
-
-void
-U2FSignRunnable::SendResponse(const SignResponse& aResponse)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  ErrorResult rv;
-  mCallback->Call(aResponse, rv);
-  NS_WARNING_ASSERTION(!rv.Failed(), "callback failed");
-  // Useful exceptions already got reported.
-  rv.SuppressException();
-}
-
-NS_IMETHODIMP
-U2FSignRunnable::Run()
-{
-  MOZ_ASSERT(!NS_IsMainThread());
-
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Create a Status object to keep track of when we're done
-  RefPtr<U2FStatus> status = new U2FStatus();
-
-  // Evaluate the AppID
-  ErrorCode appIdResult = EvaluateAppID();
-  if (appIdResult != ErrorCode::OK) {
-    status->Stop(appIdResult);
-  }
-
-  // Hash the AppID and the ClientData into the AppParam and ChallengeParam
-  nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
-  CryptoBuffer appParam;
-  CryptoBuffer challengeParam;
-  if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
-      !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  SECStatus srv;
-  srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
-                     reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
-                     cAppId.Length());
-  if (srv != SECSuccess) {
-    return NS_ERROR_FAILURE;
-  }
-
-  srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
-                     mClientData.Elements(), mClientData.Length());
-  if (srv != SECSuccess) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Search the signing requests for one a token can fulfill
-  for (LocalRegisteredKey key : mRegisteredKeys) {
-    // Do not permit an individual RegisteredKey to assert a different AppID
-    if (!key.mAppId.IsNull() && mAppId != key.mAppId.Value()) {
-      continue;
-    }
-
-    // Decode the key handle
-    CryptoBuffer keyHandle;
-    nsresult rv = keyHandle.FromJwkBase64(key.mKeyHandle);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      continue;
-    }
-
-    // We ignore mTransports, as it is intended to be used for sorting the
-    // available devices by preference, but is not an exclusion factor.
-
-    for (const Authenticator& token : mAuthenticators) {
-      RefPtr<U2FSignTask> signTask = new U2FSignTask(mOrigin, mAppId,
-                                                     key.mVersion, token,
-                                                     appParam, challengeParam,
-                                                     mClientData, keyHandle,
-                                                     mEventTarget);
-      status->WaitGroupAdd();
-
-      signTask->Execute()->Then(mEventTarget, __func__,
-        [&status] (nsString aResponse) {
-          if (!status->IsStopped()) {
-            status->Stop(ErrorCode::OK, aResponse);
-          }
-          status->WaitGroupDone();
-        },
-        [&status] (ErrorCode aErrorCode) {
-          // Ignore the failing error code, as we only want the first success.
-          // U2F devices don't provide much for error codes anyway, so if
-          // they all fail we'll return DEVICE_INELIGIBLE.
-          status->WaitGroupDone();
-      });
-    }
-  }
-
-  // Wait for the authenticators to finish
-  status->WaitGroupWait();
-
-  // If none of the tasks completed, then nothing could satisfy.
-  if (!status->IsStopped()) {
-    status->Stop(ErrorCode::DEVICE_INELIGIBLE);
-  }
-
-  // Transmit back to the JS engine from the Main Thread
-  status->WaitGroupAdd();
-  mEventTarget->Dispatch(NS_NewRunnableFunction(
-    "dom::U2FSignRunnable::Run",
-    [&status, this] () {
-      SignResponse response;
-      if (status->GetErrorCode() == ErrorCode::OK) {
-        response.Init(status->GetResponse());
-      } else {
-        response.mErrorCode.Construct(
-          static_cast<uint32_t>(status->GetErrorCode()));
-      }
-      SendResponse(response);
-      status->WaitGroupDone();
-    }));
-
-  // TODO: Add timeouts, Bug 1301793
-  status->WaitGroupWait();
-  return NS_OK;
-}
-
-U2F::U2F()
-  : mInitialized(false)
-{}
-
-U2F::~U2F()
-{
-  nsNSSShutDownPreventionLock locker;
-
-  if (isAlreadyShutDown()) {
-    return;
-  }
-  shutdown(ShutdownCalledFrom::Object);
-}
-
-/* virtual */ JSObject*
-U2F::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return U2FBinding::Wrap(aCx, this, aGivenProto);
-}
-
-void
-U2F::Init(nsPIDOMWindowInner* aParent, ErrorResult& aRv)
-{
-  MOZ_ASSERT(!mInitialized);
-  MOZ_ASSERT(!mParent);
-  mParent = do_QueryInterface(aParent);
   MOZ_ASSERT(mParent);
+  MOZ_ASSERT(!mEventTarget);
 
   nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
   MOZ_ASSERT(doc);
+  if (!doc) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
 
   nsIPrincipal* principal = doc->NodePrincipal();
   aRv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (NS_WARN_IF(mOrigin.IsEmpty())) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
-  if (!EnsureNSSInitializedChromeOrContent()) {
-    MOZ_LOG(gU2FLog, LogLevel::Debug,
-            ("Failed to get NSS context for U2F"));
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  // This only functions in e10s mode
-  if (XRE_IsParentProcess()) {
-    MOZ_LOG(gU2FLog, LogLevel::Debug,
-            ("Is non-e10s Process, U2F not available"));
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
+  mEventTarget = doc->EventTargetFor(TaskCategory::Other);
+  MOZ_ASSERT(mEventTarget);
+}
 
-  // Monolithically insert compatible nsIU2FToken objects into mAuthenticators.
-  // In future functionality expansions, this is where we could add a dynamic
-  // add/remove interface.
-  if (Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED)) {
-    if (!mAuthenticators.AppendElement(new NSSU2FTokenRemote(),
-                                       mozilla::fallible)) {
-      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-      return;
-    }
-  }
-
-  mEventTarget = doc->EventTargetFor(TaskCategory::Other);
-
-  mInitialized = true;
+/* virtual */ JSObject*
+U2F::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return U2FBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
 U2F::Register(const nsAString& aAppId,
               const Sequence<RegisterRequest>& aRegisterRequests,
               const Sequence<RegisteredKey>& aRegisteredKeys,
               U2FRegisterCallback& aCallback,
               const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
               ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mInitialized) {
-    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+  RefPtr<U2FManager> mgr = U2FManager::GetOrCreate();
+  MOZ_ASSERT(mgr);
+  if (!mgr || mRegisterCallback.isSome()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  MOZ_ASSERT(!mPromiseHolder.Exists());
+  MOZ_ASSERT(mRegisterCallback.isNothing());
+  mRegisterCallback = Some(nsMainThreadPtrHandle<U2FRegisterCallback>(
+                        new nsMainThreadPtrHolder<U2FRegisterCallback>(
+                            "U2F::Register::callback", &aCallback)));
+
+  uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
+
+  // Evaluate the AppID
+  nsString adjustedAppId;
+  adjustedAppId.Assign(aAppId);
+  ErrorCode appIdResult = EvaluateAppID(mOrigin, adjustedAppId);
+  if (appIdResult != ErrorCode::OK) {
+    RegisterResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
+    ExecuteCallback(response, mRegisterCallback);
     return;
   }
 
-  RefPtr<SharedThreadPool> pool = SharedThreadPool::Get(kPoolName);
-  RefPtr<U2FRegisterRunnable> task = new U2FRegisterRunnable(mOrigin, aAppId,
-                                                             aRegisterRequests,
-                                                             aRegisteredKeys,
-                                                             mAuthenticators,
-                                                             &aCallback,
-                                                             mEventTarget);
-  pool->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+  // Produce the AppParam from the current AppID
+  nsCString cAppId = NS_ConvertUTF16toUTF8(adjustedAppId);
+
+  nsAutoString clientDataJSON;
+
+  // Pick the first valid RegisterRequest; we can only work with one.
+  for (const RegisterRequest& req : aRegisterRequests) {
+    if (!req.mChallenge.WasPassed() || !req.mVersion.WasPassed() ||
+        req.mVersion.Value() != kRequiredU2FVersion) {
+      continue;
+    }
+
+    nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment,
+                                     req.mChallenge.Value(), clientDataJSON);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      continue;
+    }
+  }
+
+  // Did we not get a valid RegisterRequest? Abort.
+  if (clientDataJSON.IsEmpty()) {
+    RegisterResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
+    ExecuteCallback(response, mRegisterCallback);
+    return;
+  }
+
+  // Build the exclusion list, if any
+  nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
+  RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
+                                       excludeList);
+
+  auto& localReqHolder = mPromiseHolder;
+  auto& localCb = mRegisterCallback;
+  RefPtr<U2FPromise> p = mgr->Register(mParent, cAppId,
+                                       NS_ConvertUTF16toUTF8(clientDataJSON),
+                                       adjustedTimeoutMillis, excludeList);
+  p->Then(mEventTarget, "dom::U2F::Register::Promise::Resolve",
+          [&localCb, &localReqHolder](nsString aResponse) {
+              MOZ_LOG(gU2FLog, LogLevel::Debug,
+                      ("dom::U2F::Register::Promise::Resolve, response was %s",
+                        NS_ConvertUTF16toUTF8(aResponse).get()));
+              RegisterResponse response;
+              response.Init(aResponse);
+
+              ExecuteCallback(response, localCb);
+              localReqHolder.Complete();
+          },
+          [&localCb, &localReqHolder](ErrorCode aErrorCode) {
+              MOZ_LOG(gU2FLog, LogLevel::Debug,
+                      ("dom::U2F::Register::Promise::Reject, response was %d",
+                        static_cast<uint32_t>(aErrorCode)));
+              RegisterResponse response;
+              response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
+
+              ExecuteCallback(response, localCb);
+              localReqHolder.Complete();
+          })
+  ->Track(mPromiseHolder);
 }
 
 void
 U2F::Sign(const nsAString& aAppId,
           const nsAString& aChallenge,
           const Sequence<RegisteredKey>& aRegisteredKeys,
           U2FSignCallback& aCallback,
           const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
           ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (!mInitialized) {
-    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
+  RefPtr<U2FManager> mgr = U2FManager::GetOrCreate();
+  MOZ_ASSERT(mgr);
+  if (!mgr || mSignCallback.isSome()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  MOZ_ASSERT(!mPromiseHolder.Exists());
+  MOZ_ASSERT(mSignCallback.isNothing());
+  mSignCallback = Some(nsMainThreadPtrHandle<U2FSignCallback>(
+                    new nsMainThreadPtrHolder<U2FSignCallback>(
+                        "U2F::Sign::callback", &aCallback)));
+
+  uint32_t adjustedTimeoutMillis = AdjustedTimeoutMillis(opt_aTimeoutSeconds);
+
+  // Evaluate the AppID
+  nsString adjustedAppId;
+  adjustedAppId.Assign(aAppId);
+  ErrorCode appIdResult = EvaluateAppID(mOrigin, adjustedAppId);
+  if (appIdResult != ErrorCode::OK) {
+    SignResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(appIdResult));
+    ExecuteCallback(response, mSignCallback);
     return;
   }
 
-  RefPtr<SharedThreadPool> pool = SharedThreadPool::Get(kPoolName);
-  RefPtr<U2FSignRunnable> task = new U2FSignRunnable(mOrigin, aAppId, aChallenge,
-                                                     aRegisteredKeys,
-                                                     mAuthenticators, &aCallback,
-                                                     mEventTarget);
-  pool->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+  // Produce the AppParam from the current AppID
+  nsCString cAppId = NS_ConvertUTF16toUTF8(adjustedAppId);
+
+  nsAutoString clientDataJSON;
+  nsresult rv = AssembleClientData(mOrigin, kGetAssertion, aChallenge,
+                                   clientDataJSON);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    SignResponse response;
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::BAD_REQUEST));
+    ExecuteCallback(response, mSignCallback);
+    return;
+  }
+
+  // Build the key list, if any
+  nsTArray<WebAuthnScopedCredentialDescriptor> permittedList;
+  RegisteredKeysToScopedCredentialList(adjustedAppId, aRegisteredKeys,
+                                       permittedList);
+  auto& localReqHolder = mPromiseHolder;
+  auto& localCb = mSignCallback;
+  RefPtr<U2FPromise> p = mgr->Sign(mParent, cAppId,
+                                   NS_ConvertUTF16toUTF8(clientDataJSON),
+                                   adjustedTimeoutMillis, permittedList);
+  p->Then(mEventTarget, "dom::U2F::Sign::Promise::Resolve",
+          [&localCb, &localReqHolder](nsString aResponse) {
+              MOZ_LOG(gU2FLog, LogLevel::Debug,
+                      ("dom::U2F::Sign::Promise::Resolve, response was %s",
+                        NS_ConvertUTF16toUTF8(aResponse).get()));
+              SignResponse response;
+              response.Init(aResponse);
+
+              ExecuteCallback(response, localCb);
+              localReqHolder.Complete();
+          },
+          [&localCb, &localReqHolder](ErrorCode aErrorCode) {
+              MOZ_LOG(gU2FLog, LogLevel::Debug,
+                      ("dom::U2F::Sign::Promise::Reject, response was %d",
+                        static_cast<uint32_t>(aErrorCode)));
+              SignResponse response;
+              response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
+
+              ExecuteCallback(response, localCb);
+              localReqHolder.Complete();
+          })
+  ->Track(mPromiseHolder);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -5,298 +5,55 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_U2F_h
 #define mozilla_dom_U2F_h
 
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/BindingDeclarations.h"
-#include "mozilla/dom/CryptoBuffer.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/U2FBinding.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/MozPromise.h"
-#include "mozilla/ReentrantMonitor.h"
-#include "mozilla/SharedThreadPool.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsIU2FToken.h"
-#include "nsNSSShutDown.h"
 #include "nsPIDOMWindow.h"
 #include "nsProxyRelease.h"
 #include "nsWrapperCache.h"
-
 #include "U2FAuthenticator.h"
 
 class nsISerialEventTarget;
 
 namespace mozilla {
 namespace dom {
 
 class U2FRegisterCallback;
 class U2FSignCallback;
 
 // Defined in U2FBinding.h by the U2F.webidl; their use requires a JSContext.
 struct RegisterRequest;
 struct RegisteredKey;
 
-// These structs are analogs to the WebIDL versions, but can be used on worker
-// threads which lack a JSContext.
-struct LocalRegisterRequest
-{
-  nsString mChallenge;
-  nsString mVersion;
-  CryptoBuffer mClientData;
-};
-
-struct LocalRegisteredKey
-{
-  nsString mKeyHandle;
-  nsString mVersion;
-  Nullable<nsString> mAppId;
-  // TODO: Support transport preferences
-  // Nullable<nsTArray<Transport>> mTransports;
-};
-
-typedef MozPromise<nsString, ErrorCode, false> U2FPromise;
-typedef MozPromise<Authenticator, ErrorCode, false> U2FPrepPromise;
-
-// U2FPrepTasks return lists of Authenticators that are OK to
-// proceed; they're useful for culling incompatible Authenticators.
-// Currently, only IsRegistered is supported.
-class U2FPrepTask : public Runnable
-{
-public:
-  explicit U2FPrepTask(const Authenticator& aAuthenticator,
-                       nsISerialEventTarget* aEventTarget);
-
-  RefPtr<U2FPrepPromise> Execute();
-
-protected:
-  virtual ~U2FPrepTask();
-
-  Authenticator mAuthenticator;
-  MozPromiseHolder<U2FPrepPromise> mPromise;
-  const nsCOMPtr<nsISerialEventTarget> mEventTarget;
-};
-
-// Determine whether the provided Authenticator already knows
-// of the provided Registered Key.
-class U2FIsRegisteredTask final : public U2FPrepTask
-{
-public:
-  U2FIsRegisteredTask(const Authenticator& aAuthenticator,
-                      const LocalRegisteredKey& aRegisteredKey,
-                      const CryptoBuffer& aAppParam,
-                      nsISerialEventTarget* aEventTarget);
-
-  NS_DECL_NSIRUNNABLE
-private:
-  ~U2FIsRegisteredTask();
-
-  LocalRegisteredKey mRegisteredKey;
-  CryptoBuffer mAppParam;
-};
-
-class U2FTask : public Runnable
-{
-public:
-  U2FTask(const nsAString& aOrigin,
-          const nsAString& aAppId,
-          const Authenticator& aAuthenticator,
-          nsISerialEventTarget* aEventTarget);
-
-  RefPtr<U2FPromise> Execute();
-
-  nsString mOrigin;
-  nsString mAppId;
-  Authenticator mAuthenticator;
-  const nsCOMPtr<nsISerialEventTarget> mEventTarget;
-
-protected:
-  virtual ~U2FTask();
-
-  MozPromiseHolder<U2FPromise> mPromise;
-};
-
-// Use the provided Authenticator to Register a new scoped credential
-// for the provided application.
-class U2FRegisterTask final : public U2FTask
-{
-public:
-  U2FRegisterTask(const nsAString& aOrigin,
-                  const nsAString& aAppId,
-                  const Authenticator& aAuthenticator,
-                  const CryptoBuffer& aAppParam,
-                  const CryptoBuffer& aChallengeParam,
-                  const LocalRegisterRequest& aRegisterEntry,
-                  nsISerialEventTarget* aEventTarget);
-
-  NS_DECL_NSIRUNNABLE
-private:
-  ~U2FRegisterTask();
-
-  CryptoBuffer mAppParam;
-  CryptoBuffer mChallengeParam;
-  LocalRegisterRequest mRegisterEntry;
-};
-
-// Generate an assertion using the provided Authenticator for the given origin
-// and provided application to attest to ownership of a valid scoped credential.
-class U2FSignTask final : public U2FTask
-{
-public:
-  U2FSignTask(const nsAString& aOrigin,
-              const nsAString& aAppId,
-              const nsAString& aVersion,
-              const Authenticator& aAuthenticator,
-              const CryptoBuffer& aAppParam,
-              const CryptoBuffer& aChallengeParam,
-              const CryptoBuffer& aClientData,
-              const CryptoBuffer& aKeyHandle,
-              nsISerialEventTarget* aEventTarget);
-
-  NS_DECL_NSIRUNNABLE
-private:
-  ~U2FSignTask();
-
-  nsString mVersion;
-  CryptoBuffer mAppParam;
-  CryptoBuffer mChallengeParam;
-  CryptoBuffer mClientData;
-  CryptoBuffer mKeyHandle;
-};
-
-// Mediate inter-thread communication for multiple authenticators being queried
-// in concert. Operates as a cyclic buffer with a stop-work method.
-class U2FStatus
-{
-public:
-  U2FStatus();
-  U2FStatus(const U2FStatus&) = delete;
-
-  void WaitGroupAdd();
-  void WaitGroupDone();
-  void WaitGroupWait();
-
-  void Stop(const ErrorCode aErrorCode);
-  void Stop(const ErrorCode aErrorCode, const nsAString& aResponse);
-  bool IsStopped();
-  ErrorCode GetErrorCode();
-  nsString GetResponse();
-
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(U2FStatus)
-
-private:
-  ~U2FStatus();
-
-  uint16_t mCount;
-  bool mIsStopped;
-  nsString mResponse;
-  MOZ_INIT_OUTSIDE_CTOR ErrorCode mErrorCode;
-  ReentrantMonitor mReentrantMonitor;
-};
-
-// U2FRunnables run to completion, performing a single U2F operation such as
-// registering, or signing.
-class U2FRunnable : public Runnable
-                  , public nsNSSShutDownObject
-{
-public:
-  U2FRunnable(const nsAString& aOrigin, const nsAString& aAppId,
-              nsISerialEventTarget* aEventTarget);
-
-  // No NSS resources to release.
-  virtual
-  void virtualDestroyNSSReference() override {};
-
-protected:
-  virtual ~U2FRunnable();
-  ErrorCode EvaluateAppID();
-
-  nsString mOrigin;
-  nsString mAppId;
-  const nsCOMPtr<nsISerialEventTarget> mEventTarget;
-};
-
-// This U2FRunnable completes a single application-requested U2F Register
-// operation.
-class U2FRegisterRunnable : public U2FRunnable
-{
-public:
-  U2FRegisterRunnable(const nsAString& aOrigin,
-                      const nsAString& aAppId,
-                      const Sequence<RegisterRequest>& aRegisterRequests,
-                      const Sequence<RegisteredKey>& aRegisteredKeys,
-                      const Sequence<Authenticator>& aAuthenticators,
-                      U2FRegisterCallback* aCallback,
-                      nsISerialEventTarget* aEventTarget);
-
-  void SendResponse(const RegisterResponse& aResponse);
-  void SetTimeout(const int32_t aTimeoutMillis);
-
-  NS_DECL_NSIRUNNABLE
-
-private:
-  ~U2FRegisterRunnable();
-
-  nsTArray<LocalRegisterRequest> mRegisterRequests;
-  nsTArray<LocalRegisteredKey> mRegisteredKeys;
-  nsTArray<Authenticator> mAuthenticators;
-  nsMainThreadPtrHandle<U2FRegisterCallback> mCallback;
-  Nullable<int32_t> opt_mTimeoutSeconds;
-};
-
-// This U2FRunnable completes a single application-requested U2F Sign operation.
-class U2FSignRunnable : public U2FRunnable
-{
-public:
-  U2FSignRunnable(const nsAString& aOrigin,
-                  const nsAString& aAppId,
-                  const nsAString& aChallenge,
-                  const Sequence<RegisteredKey>& aRegisteredKeys,
-                  const Sequence<Authenticator>& aAuthenticators,
-                  U2FSignCallback* aCallback,
-                  nsISerialEventTarget* aEventTarget);
-
-  void SendResponse(const SignResponse& aResponse);
-  void SetTimeout(const int32_t aTimeoutMillis);
-
-  NS_DECL_NSIRUNNABLE
-
-private:
-  ~U2FSignRunnable();
-
-  nsString mChallenge;
-  CryptoBuffer mClientData;
-  nsTArray<LocalRegisteredKey> mRegisteredKeys;
-  nsTArray<Authenticator> mAuthenticators;
-  nsMainThreadPtrHandle<U2FSignCallback> mCallback;
-  Nullable<int32_t> opt_mTimeoutSeconds;
-};
-
 // The U2F Class is used by the JS engine to initiate U2F operations.
 class U2F final : public nsISupports
                 , public nsWrapperCache
-                , public nsNSSShutDownObject
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(U2F)
 
-  U2F();
+  explicit U2F(nsPIDOMWindowInner* aParent);
 
   nsPIDOMWindowInner*
   GetParentObject() const
   {
     return mParent;
   }
 
   void
-  Init(nsPIDOMWindowInner* aParent, ErrorResult& aRv);
+  Init(ErrorResult& aRv);
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
   Register(const nsAString& aAppId,
            const Sequence<RegisterRequest>& aRegisterRequests,
            const Sequence<RegisteredKey>& aRegisteredKeys,
@@ -307,26 +64,23 @@ public:
   void
   Sign(const nsAString& aAppId,
        const nsAString& aChallenge,
        const Sequence<RegisteredKey>& aRegisteredKeys,
        U2FSignCallback& aCallback,
        const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
        ErrorResult& aRv);
 
-  // No NSS resources to release.
-  virtual
-  void virtualDestroyNSSReference() override {};
-
 private:
+  nsString mOrigin;
   nsCOMPtr<nsPIDOMWindowInner> mParent;
-  nsString mOrigin;
-  Sequence<Authenticator> mAuthenticators;
-  bool mInitialized;
   nsCOMPtr<nsISerialEventTarget> mEventTarget;
+  Maybe<nsMainThreadPtrHandle<U2FRegisterCallback>> mRegisterCallback;
+  Maybe<nsMainThreadPtrHandle<U2FSignCallback>> mSignCallback;
+  MozPromiseRequestHolder<U2FPromise> mPromiseHolder;
 
   ~U2F();
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_U2F_h
--- a/dom/u2f/U2FAuthenticator.h
+++ b/dom/u2f/U2FAuthenticator.h
@@ -2,31 +2,31 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_U2FAuthenticator_h
 #define mozilla_dom_U2FAuthenticator_h
 
-#include "nsIU2FToken.h"
+#include "mozilla/MozPromise.h"
 
 namespace mozilla {
 namespace dom {
 
- // These enumerations are defined in the FIDO U2F Javascript API under the
+// These enumerations are defined in the FIDO U2F Javascript API under the
 // interface "ErrorCode" as constant integers, and thus in the U2F.webidl file.
 // Any changes to these must occur in both locations.
 enum class ErrorCode {
   OK = 0,
   OTHER_ERROR = 1,
   BAD_REQUEST = 2,
   CONFIGURATION_UNSUPPORTED = 3,
   DEVICE_INELIGIBLE = 4,
   TIMEOUT = 5
 };
 
-typedef nsCOMPtr<nsIU2FToken> Authenticator;
+typedef MozPromise<nsString, ErrorCode, false> U2FPromise;
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_U2FAuthenticator_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/U2FManager.cpp
@@ -0,0 +1,505 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "hasht.h"
+#include "nsICryptoHash.h"
+#include "nsNetCID.h"
+#include "U2FManager.h"
+#include "U2FTransactionChild.h"
+#include "U2FUtil.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PWebAuthnTransaction.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ipc/BackgroundChild.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+/***********************************************************************
+ * Statics
+ **********************************************************************/
+
+namespace {
+StaticRefPtr<U2FManager> gU2FManager;
+static mozilla::LazyLogModule gU2FManagerLog("u2fmanager");
+}
+
+NS_NAMED_LITERAL_STRING(kVisibilityChange, "visibilitychange");
+
+NS_IMPL_ISUPPORTS(U2FManager, nsIIPCBackgroundChildCreateCallback,
+                  nsIDOMEventListener);
+
+/***********************************************************************
+ * Utility Functions
+ **********************************************************************/
+
+static void
+ListenForVisibilityEvents(nsPIDOMWindowInner* aParent,
+                          U2FManager* aListener)
+{
+  MOZ_ASSERT(aParent);
+  MOZ_ASSERT(aListener);
+
+  nsCOMPtr<nsIDocument> doc = aParent->GetExtantDoc();
+  if (NS_WARN_IF(!doc)) {
+    return;
+  }
+
+  nsresult rv = doc->AddSystemEventListener(kVisibilityChange, aListener,
+                                            /* use capture */ true,
+                                            /* wants untrusted */ false);
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+static void
+StopListeningForVisibilityEvents(nsPIDOMWindowInner* aParent,
+                                 U2FManager* aListener)
+{
+  MOZ_ASSERT(aParent);
+  MOZ_ASSERT(aListener);
+
+  nsCOMPtr<nsIDocument> doc = aParent->GetExtantDoc();
+  if (NS_WARN_IF(!doc)) {
+    return;
+  }
+
+  nsresult rv = doc->RemoveSystemEventListener(kVisibilityChange, aListener,
+                                               /* use capture */ true);
+  Unused << NS_WARN_IF(NS_FAILED(rv));
+}
+
+static ErrorCode
+ConvertNSResultToErrorCode(const nsresult& aError)
+{
+  if (aError == NS_ERROR_DOM_TIMEOUT_ERR) {
+    return ErrorCode::TIMEOUT;
+  }
+  /* Emitted by U2F{Soft,HID}TokenManager when we really mean ineligible */
+  if (aError == NS_ERROR_DOM_NOT_ALLOWED_ERR) {
+    return ErrorCode::DEVICE_INELIGIBLE;
+  }
+  return ErrorCode::OTHER_ERROR;
+}
+
+/***********************************************************************
+ * U2FManager Implementation
+ **********************************************************************/
+
+U2FManager::U2FManager()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+}
+
+void
+U2FManager::MaybeClearTransaction()
+{
+  mClientData.reset();
+  mInfo.reset();
+  mTransactionPromise.RejectIfExists(ErrorCode::OTHER_ERROR, __func__);
+  if (mCurrentParent) {
+    StopListeningForVisibilityEvents(mCurrentParent, this);
+    mCurrentParent = nullptr;
+  }
+
+  if (mChild) {
+    RefPtr<U2FTransactionChild> c;
+    mChild.swap(c);
+    c->Send__delete__(c);
+  }
+}
+
+U2FManager::~U2FManager()
+{
+  MaybeClearTransaction();
+}
+
+RefPtr<U2FManager::BackgroundActorPromise>
+U2FManager::GetOrCreateBackgroundActor()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  PBackgroundChild *actor = BackgroundChild::GetForCurrentThread();
+  RefPtr<U2FManager::BackgroundActorPromise> promise =
+    mPBackgroundCreationPromise.Ensure(__func__);
+
+  if (actor) {
+    ActorCreated(actor);
+  } else {
+    bool ok = BackgroundChild::GetOrCreateForCurrentThread(this);
+    if (NS_WARN_IF(!ok)) {
+      ActorFailed();
+    }
+  }
+
+  return promise;
+}
+
+//static
+U2FManager*
+U2FManager::GetOrCreate()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (gU2FManager) {
+    return gU2FManager;
+  }
+
+  gU2FManager = new U2FManager();
+  ClearOnShutdown(&gU2FManager);
+  return gU2FManager;
+}
+
+//static
+U2FManager*
+U2FManager::Get()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  return gU2FManager;
+}
+
+nsresult
+U2FManager::PopulateTransactionInfo(const nsCString& aRpId,
+                      const nsCString& aClientDataJSON,
+                      const uint32_t& aTimeoutMillis,
+                      const nsTArray<WebAuthnScopedCredentialDescriptor>& aList)
+{
+  MOZ_ASSERT(mInfo.isNothing());
+
+  nsresult srv;
+  nsCOMPtr<nsICryptoHash> hashService =
+    do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
+  if (NS_FAILED(srv)) {
+    return srv;
+  }
+
+  CryptoBuffer rpIdHash;
+  if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  srv = HashCString(hashService, aRpId, rpIdHash);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  CryptoBuffer clientDataHash;
+  if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  srv = HashCString(hashService, aClientDataJSON, clientDataHash);
+  if (NS_WARN_IF(NS_FAILED(srv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (MOZ_LOG_TEST(gU2FLog, LogLevel::Debug)) {
+    nsString base64;
+    Unused << NS_WARN_IF(NS_FAILED(rpIdHash.ToJwkBase64(base64)));
+
+    MOZ_LOG(gU2FLog, LogLevel::Debug,
+            ("dom::U2FManager::RpID: %s", aRpId.get()));
+
+    MOZ_LOG(gU2FLog, LogLevel::Debug,
+            ("dom::U2FManager::Rp ID Hash (base64): %s",
+              NS_ConvertUTF16toUTF8(base64).get()));
+
+    Unused << NS_WARN_IF(NS_FAILED(clientDataHash.ToJwkBase64(base64)));
+
+    MOZ_LOG(gU2FLog, LogLevel::Debug,
+            ("dom::U2FManager::Client Data JSON: %s", aClientDataJSON.get()));
+
+    MOZ_LOG(gU2FLog, LogLevel::Debug,
+            ("dom::U2FManager::Client Data Hash (base64): %s",
+              NS_ConvertUTF16toUTF8(base64).get()));
+  }
+
+  // Always blank for U2F
+  nsTArray<WebAuthnExtension> extensions;
+
+  WebAuthnTransactionInfo info(rpIdHash,
+                               clientDataHash,
+                               aTimeoutMillis,
+                               aList,
+                               extensions);
+  mInfo = Some(info);
+  return NS_OK;
+}
+
+
+already_AddRefed<U2FPromise>
+U2FManager::Register(nsPIDOMWindowInner* aParent, const nsCString& aRpId,
+               const nsCString& aClientDataJSON,
+               const uint32_t& aTimeoutMillis,
+               const nsTArray<WebAuthnScopedCredentialDescriptor>& aExcludeList)
+{
+  MOZ_ASSERT(aParent);
+
+  MaybeClearTransaction();
+
+  if (NS_FAILED(PopulateTransactionInfo(aRpId, aClientDataJSON, aTimeoutMillis,
+                                        aExcludeList))) {
+    return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
+  }
+
+  RefPtr<MozPromise<nsresult, nsresult, false>> p = GetOrCreateBackgroundActor();
+  p->Then(GetMainThreadSerialEventTarget(), __func__,
+          []() {
+            U2FManager* mgr = U2FManager::Get();
+            if (!mgr) {
+              return;
+            }
+            mgr->StartRegister();
+          },
+          []() {
+            // This case can't actually happen, we'll have crashed if the child
+            // failed to create.
+          });
+
+  // Only store off the promise if we've succeeded in sending the IPC event.
+  RefPtr<U2FPromise> promise = mTransactionPromise.Ensure(__func__);
+  mClientData = Some(aClientDataJSON);
+  mCurrentParent = aParent;
+  ListenForVisibilityEvents(aParent, this);
+  return promise.forget();
+}
+
+already_AddRefed<U2FPromise>
+U2FManager::Sign(nsPIDOMWindowInner* aParent,
+                 const nsCString& aRpId,
+                 const nsCString& aClientDataJSON,
+                 const uint32_t& aTimeoutMillis,
+                 const nsTArray<WebAuthnScopedCredentialDescriptor>& aAllowList)
+{
+  MOZ_ASSERT(aParent);
+
+  MaybeClearTransaction();
+
+  if (NS_FAILED(PopulateTransactionInfo(aRpId, aClientDataJSON, aTimeoutMillis,
+                                        aAllowList))) {
+    return U2FPromise::CreateAndReject(ErrorCode::OTHER_ERROR, __func__).forget();
+  }
+
+  RefPtr<MozPromise<nsresult, nsresult, false>> p = GetOrCreateBackgroundActor();
+  p->Then(GetMainThreadSerialEventTarget(), __func__,
+          []() {
+            U2FManager* mgr = U2FManager::Get();
+            if (!mgr) {
+              return;
+            }
+            mgr->StartSign();
+          },
+          []() {
+            // This case can't actually happen, we'll have crashed if the child
+            // failed to create.
+          });
+
+  // Only store off the promise if we've succeeded in sending the IPC event.
+  RefPtr<U2FPromise> promise = mTransactionPromise.Ensure(__func__);
+  mClientData = Some(aClientDataJSON);
+  mCurrentParent = aParent;
+  ListenForVisibilityEvents(aParent, this);
+  return promise.forget();
+}
+
+void
+U2FManager::StartRegister() {
+  if (mChild) {
+    mChild->SendRequestRegister(mInfo.ref());
+  }
+}
+
+void
+U2FManager::StartSign() {
+  if (mChild) {
+    mChild->SendRequestSign(mInfo.ref());
+  }
+}
+
+void
+U2FManager::StartCancel() {
+  if (mChild) {
+    mChild->SendRequestCancel();
+  }
+}
+
+void
+U2FManager::FinishRegister(nsTArray<uint8_t>& aRegBuffer)
+{
+  MOZ_ASSERT(!mTransactionPromise.IsEmpty());
+  MOZ_ASSERT(mInfo.isSome());
+
+  CryptoBuffer clientDataBuf;
+  if (NS_WARN_IF(!clientDataBuf.Assign(mClientData.ref()))) {
+    mTransactionPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
+    return;
+  }
+
+  CryptoBuffer regBuf;
+  if (NS_WARN_IF(!regBuf.Assign(aRegBuffer))) {
+    mTransactionPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
+    return;
+  }
+
+  nsString clientDataBase64;
+  nsString registrationDataBase64;
+  nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
+  nsresult rvRegistrationData = regBuf.ToJwkBase64(registrationDataBase64);
+
+  if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+      NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
+    mTransactionPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
+    return;
+  }
+
+  // Assemble a response object to return
+  RegisterResponse response;
+  response.mClientData.Construct(clientDataBase64);
+  response.mRegistrationData.Construct(registrationDataBase64);
+  response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+  nsString responseStr;
+  if (NS_WARN_IF(!response.ToJSON(responseStr))) {
+    mTransactionPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
+    return;
+  }
+
+  mTransactionPromise.Resolve(responseStr, __func__);
+  MaybeClearTransaction();
+}
+
+void
+U2FManager::FinishSign(nsTArray<uint8_t>& aCredentialId,
+                       nsTArray<uint8_t>& aSigBuffer)
+{
+  MOZ_ASSERT(!mTransactionPromise.IsEmpty());
+  MOZ_ASSERT(mInfo.isSome());
+
+  CryptoBuffer clientDataBuf;
+  if (NS_WARN_IF(!clientDataBuf.Assign(mClientData.ref()))) {
+    mTransactionPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
+    return;
+  }
+
+  CryptoBuffer credBuf;
+  if (NS_WARN_IF(!credBuf.Assign(aCredentialId))) {
+    mTransactionPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
+    return;
+  }
+
+  CryptoBuffer sigBuf;
+  if (NS_WARN_IF(!sigBuf.Assign(aSigBuffer))) {
+    mTransactionPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
+    return;
+  }
+
+  // Assemble a response object to return
+  nsString clientDataBase64;
+  nsString signatureDataBase64;
+  nsString keyHandleBase64;
+  nsresult rvClientData = clientDataBuf.ToJwkBase64(clientDataBase64);
+  nsresult rvSignatureData = sigBuf.ToJwkBase64(signatureDataBase64);
+  nsresult rvKeyHandle = credBuf.ToJwkBase64(keyHandleBase64);
+  if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+      NS_WARN_IF(NS_FAILED(rvSignatureData) ||
+      NS_WARN_IF(NS_FAILED(rvKeyHandle)))) {
+    mTransactionPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
+    return;
+  }
+
+  SignResponse response;
+  response.mKeyHandle.Construct(keyHandleBase64);
+  response.mClientData.Construct(clientDataBase64);
+  response.mSignatureData.Construct(signatureDataBase64);
+  response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+  nsString responseStr;
+  if (NS_WARN_IF(!response.ToJSON(responseStr))) {
+    mTransactionPromise.Reject(ErrorCode::OTHER_ERROR, __func__);
+    return;
+  }
+
+  mTransactionPromise.Resolve(responseStr, __func__);
+  MaybeClearTransaction();
+}
+
+void
+U2FManager::Cancel(const nsresult& aError)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ErrorCode code = ConvertNSResultToErrorCode(aError);
+
+  if (!mTransactionPromise.IsEmpty()) {
+    mTransactionPromise.RejectIfExists(code, __func__);
+  }
+
+  MaybeClearTransaction();
+}
+
+NS_IMETHODIMP
+U2FManager::HandleEvent(nsIDOMEvent* aEvent)
+{
+  MOZ_ASSERT(aEvent);
+
+  nsAutoString type;
+  aEvent->GetType(type);
+  if (!type.Equals(kVisibilityChange)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIDocument> doc =
+    do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
+  MOZ_ASSERT(doc);
+
+  if (doc && doc->Hidden()) {
+    MOZ_LOG(gU2FManagerLog, LogLevel::Debug,
+            ("Visibility change: U2F window is hidden, cancelling job."));
+
+    StartCancel();
+    Cancel(NS_ERROR_DOM_TIMEOUT_ERR);
+  }
+
+  return NS_OK;
+}
+
+void
+U2FManager::ActorCreated(PBackgroundChild* aActor)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aActor);
+
+  if (mChild) {
+    return;
+  }
+
+  RefPtr<U2FTransactionChild> mgr(new U2FTransactionChild());
+  PWebAuthnTransactionChild* constructedMgr =
+    aActor->SendPWebAuthnTransactionConstructor(mgr);
+
+  if (NS_WARN_IF(!constructedMgr)) {
+    ActorFailed();
+    return;
+  }
+  MOZ_ASSERT(constructedMgr == mgr);
+  mChild = mgr.forget();
+  mPBackgroundCreationPromise.Resolve(NS_OK, __func__);
+}
+
+void
+U2FManager::ActorDestroyed()
+{
+  mChild = nullptr;
+}
+
+void
+U2FManager::ActorFailed()
+{
+  MOZ_CRASH("We shouldn't be here!");
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/U2FManager.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_U2FManager_h
+#define mozilla_dom_U2FManager_h
+
+#include "U2FAuthenticator.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/PWebAuthnTransaction.h"
+#include "nsIDOMEventListener.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
+
+/*
+ * Content process manager for the U2F protocol. Created on calls to the
+ * U2F DOM object, this manager handles establishing IPC channels
+ * for U2F transactions, as well as keeping track of MozPromise objects
+ * representing transactions in flight.
+ *
+ * The U2F spec (http://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915.zip)
+ * allows for two different types of transactions: registration and signing.
+ * When either of these is requested via the DOM API, the following steps are
+ * executed in the U2FManager:
+ *
+ * - Validation of the request. Return a failed promise to the caller if request
+ *   does not have correct parameters.
+ *
+ * - If request is valid, open a new IPC channel for running the transaction. If
+ *   another transaction is already running in this content process, cancel it.
+ *   Return a pending promise to the caller.
+ *
+ * - Send transaction information to parent process (by running the Start*
+ *   functions of U2FManager). Assuming another transaction is currently in
+ *   flight in another content process, parent will handle canceling it.
+ *
+ * - On return of successful transaction information from parent process, turn
+ *   information into DOM object format required by spec, and resolve promise
+ *   (by running the Finish* functions of U2FManager). On cancellation request
+ *   from parent, reject promise with corresponding error code. Either
+ *   outcome will also close the IPC channel.
+ *
+ */
+
+namespace mozilla {
+namespace dom {
+
+class ArrayBufferViewOrArrayBuffer;
+class OwningArrayBufferViewOrArrayBuffer;
+class Promise;
+class U2FTransactionChild;
+class U2FTransactionInfo;
+
+class U2FManager final : public nsIIPCBackgroundChildCreateCallback
+                       , public nsIDOMEventListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
+  static U2FManager* GetOrCreate();
+  static U2FManager* Get();
+
+  void FinishRegister(nsTArray<uint8_t>& aRegBuffer);
+  void FinishSign(nsTArray<uint8_t>& aCredentialId,
+                  nsTArray<uint8_t>& aSigBuffer);
+  void Cancel(const nsresult& aError);
+
+  already_AddRefed<U2FPromise> Register(nsPIDOMWindowInner* aParent,
+              const nsCString& aRpId,
+              const nsCString& aClientDataJSON,
+              const uint32_t& aTimeoutMillis,
+              const nsTArray<WebAuthnScopedCredentialDescriptor>& aExcludeList);
+  already_AddRefed<U2FPromise> Sign(nsPIDOMWindowInner* aParent,
+              const nsCString& aRpId,
+              const nsCString& aClientDataJSON,
+              const uint32_t& aTimeoutMillis,
+              const nsTArray<WebAuthnScopedCredentialDescriptor>& aKeyList);
+
+  void StartRegister();
+  void StartSign();
+  void StartCancel();
+
+  // nsIIPCbackgroundChildCreateCallback methods
+  void ActorCreated(PBackgroundChild* aActor) override;
+  void ActorFailed() override;
+  void ActorDestroyed();
+private:
+  U2FManager();
+  virtual ~U2FManager();
+
+  void MaybeClearTransaction();
+  nsresult PopulateTransactionInfo(const nsCString& aRpId,
+                    const nsCString& aClientDataJSON,
+                    const uint32_t& aTimeoutMillis,
+                    const nsTArray<WebAuthnScopedCredentialDescriptor>& aList);
+
+  typedef MozPromise<nsresult, nsresult, false> BackgroundActorPromise;
+
+  RefPtr<BackgroundActorPromise> GetOrCreateBackgroundActor();
+
+  // Promise representing transaction status.
+  MozPromiseHolder<U2FPromise> mTransactionPromise;
+
+  // IPC Channel for the current transaction.
+  RefPtr<U2FTransactionChild> mChild;
+
+  // Parent of the context we're currently running the transaction in.
+  nsCOMPtr<nsPIDOMWindowInner> mCurrentParent;
+
+  // Client data, stored on successful transaction creation, so that it can be
+  // used to assemble reply objects.
+  Maybe<nsCString> mClientData;
+
+  // Holds the parameters of the current transaction, as we need them both
+  // before the transaction request is sent, and on successful return.
+  Maybe<WebAuthnTransactionInfo> mInfo;
+
+  // Promise for dealing with PBackground Actor creation.
+  MozPromiseHolder<BackgroundActorPromise> mPBackgroundCreationPromise;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_U2FManager_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/U2FTransactionChild.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "U2FTransactionChild.h"
+
+namespace mozilla {
+namespace dom {
+
+U2FTransactionChild::U2FTransactionChild()
+{
+  // Retain a reference so the task object isn't deleted without IPDL's
+  // knowledge. The reference will be released by
+  // mozilla::ipc::BackgroundChildImpl::DeallocPWebAuthnTransactionChild.
+  NS_ADDREF_THIS();
+}
+
+mozilla::ipc::IPCResult
+U2FTransactionChild::RecvConfirmRegister(nsTArray<uint8_t>&& aRegBuffer)
+{
+  RefPtr<U2FManager> mgr = U2FManager::Get();
+  MOZ_ASSERT(mgr);
+  mgr->FinishRegister(aRegBuffer);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+U2FTransactionChild::RecvConfirmSign(nsTArray<uint8_t>&& aCredentialId,
+                                     nsTArray<uint8_t>&& aBuffer)
+{
+  RefPtr<U2FManager> mgr = U2FManager::Get();
+  MOZ_ASSERT(mgr);
+  mgr->FinishSign(aCredentialId, aBuffer);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+U2FTransactionChild::RecvCancel(const nsresult& aError)
+{
+  RefPtr<U2FManager> mgr = U2FManager::Get();
+  MOZ_ASSERT(mgr);
+  mgr->Cancel(aError);
+  return IPC_OK();
+}
+
+void
+U2FTransactionChild::ActorDestroy(ActorDestroyReason why)
+{
+  RefPtr<U2FManager> mgr = U2FManager::Get();
+  // This could happen after the U2FManager has been shut down.
+  if (mgr) {
+    mgr->ActorDestroyed();
+  }
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/U2FTransactionChild.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_U2FTransactionChild_h
+#define mozilla_dom_U2FTransactionChild_h
+
+#include "mozilla/dom/PWebAuthnTransactionChild.h"
+
+/*
+ * Child process IPC implementation for U2F API. Receives results of U2F
+ * transactions from the parent process, and sends them to the U2FManager
+ * to either cancel the transaction, or be formatted and relayed to content.
+ */
+
+namespace mozilla {
+namespace dom {
+
+class U2FTransactionChild final : public PWebAuthnTransactionChild
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(U2FTransactionChild);
+  U2FTransactionChild();
+  mozilla::ipc::IPCResult RecvConfirmRegister(nsTArray<uint8_t>&& aRegBuffer) override;
+  mozilla::ipc::IPCResult RecvConfirmSign(nsTArray<uint8_t>&& aCredentialId,
+                                          nsTArray<uint8_t>&& aBuffer) override;
+  mozilla::ipc::IPCResult RecvCancel(const nsresult& aError) override;
+  void ActorDestroy(ActorDestroyReason why) override;
+private:
+  ~U2FTransactionChild() = default;
+};
+
+}
+}
+
+#endif //mozilla_dom_U2FTransactionChild_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/U2FTransactionParent.cpp
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/U2FTokenManager.h"
+#include "U2FTransactionParent.h"
+
+namespace mozilla {
+namespace dom {
+
+mozilla::ipc::IPCResult
+U2FTransactionParent::RecvRequestRegister(const WebAuthnTransactionInfo& aTransactionInfo)
+{
+  U2FTokenManager* mgr = U2FTokenManager::Get();
+  mgr->Register(this, aTransactionInfo);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+U2FTransactionParent::RecvRequestSign(const WebAuthnTransactionInfo& aTransactionInfo)
+{
+  U2FTokenManager* mgr = U2FTokenManager::Get();
+  mgr->Sign(this, aTransactionInfo);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+U2FTransactionParent::RecvRequestCancel()
+{
+  U2FTokenManager* mgr = U2FTokenManager::Get();
+  mgr->Cancel(this);
+  return IPC_OK();
+}
+
+void
+U2FTransactionParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  U2FTokenManager* mgr = U2FTokenManager::Get();
+  mgr->MaybeClearTransaction(this);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/u2f/U2FTransactionParent.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_U2FTransactionParent_h
+#define mozilla_dom_U2FTransactionParent_h
+
+#include "mozilla/dom/PWebAuthnTransactionParent.h"
+
+/*
+ * Parent process IPC implementation for WebAuthn and U2F API. Receives
+ * authentication data to be either registered or signed by a key, passes
+ * information to U2FTokenManager.
+ */
+
+namespace mozilla {
+namespace dom {
+
+class U2FTransactionParent final : public PWebAuthnTransactionParent
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(U2FTransactionParent);
+  U2FTransactionParent() = default;
+  virtual mozilla::ipc::IPCResult
+  RecvRequestRegister(const WebAuthnTransactionInfo& aTransactionInfo) override;
+  virtual mozilla::ipc::IPCResult
+  RecvRequestSign(const WebAuthnTransactionInfo& aTransactionInfo) override;
+  virtual mozilla::ipc::IPCResult RecvRequestCancel() override;
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+private:
+  ~U2FTransactionParent() = default;
+};
+
+}
+}
+
+#endif //mozilla_dom_U2FTransactionParent_h
new file mode 100644
--- /dev/null
+++ b/dom/u2f/U2FUtil.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_U2FUtil_h
+#define mozilla_dom_U2FUtil_h
+
+namespace mozilla {
+namespace dom {
+
+static nsresult
+HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
+            /* out */ CryptoBuffer& aOut)
+{
+  MOZ_ASSERT(aHashService);
+
+  nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = aHashService->Update(
+    reinterpret_cast<const uint8_t*>(aIn.BeginReading()), aIn.Length());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoCString fullHash;
+  // Passing false below means we will get a binary result rather than a
+  // base64-encoded string.
+  rv = aHashService->Finish(false, fullHash);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  aOut.Assign(fullHash);
+  return rv;
+}
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_U2FUtil_h
+
--- a/dom/u2f/moz.build
+++ b/dom/u2f/moz.build
@@ -5,27 +5,32 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
 
 EXPORTS.mozilla.dom += [
     'U2F.h',
     'U2FAuthenticator.h',
+    'U2FUtil.h',
 ]
 
 UNIFIED_SOURCES += [
     'U2F.cpp',
+    'U2FManager.cpp',
+    'U2FTransactionChild.cpp',
+    'U2FTransactionParent.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/crypto',
+    '/dom/webauthn',
     '/security/manager/ssl',
     '/security/pkix/include',
     '/security/pkix/lib',
 ]
 
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
--- a/dom/u2f/tests/frame_appid_facet.html
+++ b/dom/u2f/tests/frame_appid_facet.html
@@ -1,63 +1,81 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
 </head>
 <body>
-<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
+<p>AppID / Facet checks</p>
 <script class="testbody" type="text/javascript">
 "use strict";
 
-local_is(window.location.origin, "https://example.com", "Is loaded correctly");
+async function doTests() {
+  let version = "U2F_V2";
+  let challenge = new Uint8Array(16);
+  window.crypto.getRandomValues(challenge);
 
-var version = "U2F_V2";
-var challenge = new Uint8Array(16);
+  local_is(window.location.origin, "https://example.com", "Is loaded correctly");
 
-local_expectThisManyTests(5);
+  // Ensure the SpecialPowers push worked properly
+  local_isnot(window.u2f, undefined, "U2F API endpoint must exist");
 
-u2f.register(null, [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_is(res.errorCode, 0, "Null AppID should work.");
-  local_completeTest();
-});
+  await promiseU2FRegister(null, [{
+      version: version,
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+      local_is(res.errorCode, 0, "Null AppID should work.");
+  });
 
-u2f.register("", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_is(res.errorCode, 0, "Empty AppID should work.");
-  local_completeTest();
-});
+  await promiseU2FRegister("", [{
+      version: version,
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+      local_is(res.errorCode, 0, "Empty AppID should work.");
+  });
+
+  // Test: Correct TLD, but incorrect scheme
+  await promiseU2FRegister("http://example.com/appId", [{
+      version: version,
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+      local_isnot(res.errorCode, 0, "HTTP scheme is disallowed");
+  });
 
-// Test: Correct TLD, but incorrect scheme
-u2f.register("http://example.com/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_isnot(res.errorCode, 0, "HTTP scheme is disallowed");
-  local_completeTest();
-});
+  // Test: Correct TLD, and also HTTPS
+  await promiseU2FRegister("https://example.com/appId", [{
+      version: version,
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+      local_is(res.errorCode, 0, "HTTPS origin for example.com should work");
+  });
+
+  // Test: Sub-domain
+  await promiseU2FRegister("https://test2.example.com/appId", [{
+      version: version,
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+      local_is(res.errorCode, 2, "HTTPS origin for test2.example.com shouldn't work");
+  });
 
-// Test: Correct TLD, and also HTTPS
-u2f.register("https://example.com/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_is(res.errorCode, 0, "HTTPS origin for example.com should work");
-  local_completeTest();
-});
+  // Test: Sub-sub-domain
+  await promiseU2FRegister("https://sub.test2.example.com/appId", [{
+      version: version,
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+      local_is(res.errorCode, 2, "HTTPS origin for sub.test2.example.com shouldn't work");
+  });
 
-// Test: Dynamic origin
-u2f.register(window.location.origin + "/otherAppId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_is(res.errorCode, 0, "Direct window origin should work");
-  local_completeTest();
-});
+  // Test: Dynamic origin
+  await promiseU2FRegister(window.location.origin + "/otherAppId", [{
+      version: version,
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+      local_is(res.errorCode, 0, "Direct window origin should work");
+  });
+  local_finished();
+};
 
+doTests();
 </script>
 </body>
 </html>
--- a/dom/u2f/tests/frame_appid_facet_insecure.html
+++ b/dom/u2f/tests/frame_appid_facet_insecure.html
@@ -1,60 +1,62 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
 </head>
 <body>
-<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
+<p>Insecure AppID / FacetID behavior check</p>
 <script class="testbody" type="text/javascript">
 "use strict";
 
-local_is(window.location.origin, "http://mochi.test:8888", "Is loaded correctly");
+local_setParentOrigin("http://mochi.test:8888");
 
-var version = "U2F_V2";
-var challenge = new Uint8Array(16);
+async function doTests() {
+  var version = "U2F_V2";
+  var challenge = new Uint8Array(16);
 
-local_expectThisManyTests(5);
+  local_is(window.location.origin, "http://test2.example.com", "Is loaded correctly");
 
-u2f.register(null, [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_isnot(res.errorCode, 0, "Insecure origin disallowed for null AppID");
-  local_completeTest();
-});
+  await promiseU2FRegister(null, [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_isnot(res.errorCode, 0, "Insecure origin disallowed for null AppID");
+  });
 
-u2f.register("", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_isnot(res.errorCode, 0, "Insecure origin disallowed for empty AppID");
-  local_completeTest();
-});
+  await promiseU2FRegister("", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_isnot(res.errorCode, 0, "Insecure origin disallowed for empty AppID");
+  });
 
-u2f.register("http://example.com/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTP AppID");
-  local_completeTest();
-});
+  await promiseU2FRegister("http://example.com/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTP AppID");
+  });
 
-u2f.register("https://example.com/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTPS AppID from HTTP origin");
-  local_completeTest();
-});
+  await promiseU2FRegister("https://example.com/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTPS AppID from HTTP origin");
+  });
 
-u2f.register(window.location.origin + "/otherAppId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTP origin");
-  local_completeTest();
-});
+  await promiseU2FRegister(window.location.origin + "/otherAppId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTP origin");
+  });
+
+  local_finished();
+};
+
+doTests();
 
 </script>
 </body>
-</html>
+</html>
\ No newline at end of file
--- a/dom/u2f/tests/frame_appid_facet_subdomain.html
+++ b/dom/u2f/tests/frame_appid_facet_subdomain.html
@@ -1,56 +1,57 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
 </head>
 <body>
-<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
+<p>AppID / FacetID behavior check for subdomain processing</p>
 <script class="testbody" type="text/javascript">
 "use strict";
 
-var version = "U2F_V2";
-var challenge = new Uint8Array(16);
+async function doTests() {
+  var version = "U2F_V2";
+  var challenge = new Uint8Array(16);
 
-local_is(window.location.origin, "https://test1.example.com", "Is loaded correctly");
-
-local_expectThisManyTests(4);
+  local_is(window.location.origin, "https://test1.example.com", "Is loaded correctly");
 
-// same domain check
-u2f.register("https://test1.example.com/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_is(res.errorCode, 0, "AppID should work from a different path of this domain");
-  local_completeTest();
-});
+  // same domain check
+  await promiseU2FRegister("https://test1.example.com/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_is(res.errorCode, 0, "AppID should work from a different path of this domain");
+  });
 
-// same domain check, but wrong scheme
-u2f.register("http://test1.example.com/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_isnot(res.errorCode, 0, "AppID should not work when using a different scheme");
-  local_completeTest();
-});
+  // same domain check, but wrong scheme
+  await promiseU2FRegister("http://test1.example.com/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_isnot(res.errorCode, 0, "AppID should not work when using a different scheme");
+  });
 
-// eTLD+1 subdomain check
-u2f.register("https://example.com/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_isnot(res.errorCode, 0, "AppID should not work from another subdomain in this registered domain");
-  local_completeTest();
-});
+  // eTLD+1 subdomain check
+  await promiseU2FRegister("https://example.com/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_isnot(res.errorCode, 0, "AppID should not work from another subdomain in this registered domain");
+  });
 
-// other domain check
-u2f.register("https://mochi.test:8888/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_isnot(res.errorCode, 0, "AppID should not work from other domains");
-  local_completeTest();
-});
+  // other domain check
+  await promiseU2FRegister("https://mochi.test:8888/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_isnot(res.errorCode, 0, "AppID should not work from other domains");
+  });
+
+  local_finished();
+};
+
+doTests();
 
 </script>
 </body>
 </html>
--- a/dom/u2f/tests/frame_multiple_keys.html
+++ b/dom/u2f/tests/frame_multiple_keys.html
@@ -1,120 +1,111 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
+  <script type="text/javascript" src="frame_utils.js"></script>
   <script type="text/javascript" src="u2futil.js"></script>
 </head>
 <body>
-
+<p>Test for multiple simultaneous key</p>
 <script class="testbody" type="text/javascript">
 "use strict";
 
 function keyHandleFromRegResponse(aRegResponse) {
   // Parse the response data from the U2F token
-  var registrationData = base64ToBytesUrlSafe(aRegResponse.registrationData);
+  let registrationData = base64ToBytesUrlSafe(aRegResponse.registrationData);
   local_is(registrationData[0], 0x05, "Reserved byte is correct")
 
-  var keyHandleLength = registrationData[66];
-  var keyHandleBytes = registrationData.slice(67, 67 + keyHandleLength);
+  let keyHandleLength = registrationData[66];
+  let keyHandleBytes = registrationData.slice(67, 67 + keyHandleLength);
 
   return {
     version: "U2F_V2",
     keyHandle: bytesToBase64UrlSafe(keyHandleBytes),
   };
 }
 
-local_expectThisManyTests(1);
-
-// Ensure the SpecialPowers push worked properly
-local_isnot(window.u2f, undefined, "U2F API endpoint must exist");
-
-var challenge = new Uint8Array(16);
+let challenge = new Uint8Array(16);
 window.crypto.getRandomValues(challenge);
 
-var regRequest = {
+let regRequest = {
   version: "U2F_V2",
   challenge: bytesToBase64UrlSafe(challenge),
 };
 
-var testState = {
+let testState = {
   key1: null,
   key2: null,
 }
 
-// Get two valid keys and present them
-window.u2f.register(window.location.origin, [regRequest], [], function(aRegResponse) {
-  testState.key1 = keyHandleFromRegResponse(aRegResponse);
-  registerSecondKey();
-});
-
-// Get the second key...
-// It's OK to repeat the regRequest; not material for this test
-function registerSecondKey() {
-  window.u2f.register(window.location.origin, [regRequest], [], function(aRegResponse) {
-    testState.key2 = keyHandleFromRegResponse(aRegResponse);
-
-    registerWithInvalidAndValidKey();
-  });
-}
-
-function registerWithInvalidAndValidKey() {
-  window.u2f.register(window.location.origin, [regRequest],
-                      [invalidKey, testState.key1], function(aRegResponse) {
-    // Expect a failure response since key1 is already registered
-    local_is(aRegResponse.errorCode, 4, "The register should have skipped since there was a valid key");
-
-    testSignSingleKey();
-  });
-}
-
-// It should also work with just one key
-function testSignSingleKey() {
-  window.u2f.sign(window.location.origin, bytesToBase64UrlSafe(challenge),
-                  [testState.key1], function(aSignResponse) {
-    local_is(aSignResponse.errorCode, 0, "The signing did not error with one key");
-    local_isnot(aSignResponse.clientData, undefined, "The signing provided clientData with one key");
-
-    testSignDual();
-  });
-}
-
-function testSignDual() {
-  // It's OK to sign with either one
-  window.u2f.sign(window.location.origin, bytesToBase64UrlSafe(challenge),
-                  [testState.key1, testState.key2], function(aSignResponse) {
-    local_is(aSignResponse.errorCode, 0, "The signing did not error with two keys");
-    local_isnot(aSignResponse.clientData, undefined, "The signing provided clientData with two keys");
-
-    testSignWithInvalidKey();
-  });
-}
-
 // Just a key that came from a random profile; syntactically valid but not
 // unwrappable.
-var invalidKey = {
+let invalidKey = {
   "version": "U2F_V2",
   "keyHandle": "rQdreHgHrmKfsnGPAElEP9yfTx6eq2eU3_Y8n0RRsGKML0DY2d1_a8_-sOtxDr3"
 };
 
-function testSignWithInvalidKey() {
-  window.u2f.sign(window.location.origin, bytesToBase64UrlSafe(challenge),
-                  [invalidKey, testState.key2], function(aSignResponse) {
+async function doTests() {
+  // Ensure the SpecialPowers push worked properly
+  local_isnot(window.u2f, undefined, "U2F API endpoint must exist");
+
+  // Get two valid keys and present them
+  await promiseU2FRegister(window.location.origin, [regRequest], [], function(aRegResponse) {
+    testState.key1 = keyHandleFromRegResponse(aRegResponse);
+  });
+
+  // Get the second key...
+  // It's OK to repeat the regRequest; not material for this test
+  await promiseU2FRegister(window.location.origin, [regRequest], [], function(aRegResponse) {
+    testState.key2 = keyHandleFromRegResponse(aRegResponse);
+  });
+
+  await promiseU2FRegister(window.location.origin, [regRequest],
+                           [invalidKey], function(aRegResponse) {
+    // The invalid key shouldn't match anything, so we should register OK here, too
+    local_is(aRegResponse.errorCode, 0, "The register should have gone through with the invalid key");
+  });
+
+
+  await promiseU2FRegister(window.location.origin, [regRequest],
+                           [invalidKey, testState.key1], function(aRegResponse) {
+    // Expect a failure response since key1 is already registered
+    local_is(aRegResponse.errorCode, 4, "The register should have skipped since there was a valid key");
+  });
+
+  await promiseU2FSign(window.location.origin, bytesToBase64UrlSafe(challenge),
+                  [testState.key1], function(aSignResponse) {
+    local_is(aSignResponse.errorCode, 0, "The signing did not error with one key");
+    local_isnot(aSignResponse.clientData, undefined, "The signing provided clientData with one key");
+  });
+
+  // It's OK to sign with either one
+  await promiseU2FSign(window.location.origin, bytesToBase64UrlSafe(challenge),
+                  [testState.key1, testState.key2], function(aSignResponse) {
+    local_is(aSignResponse.errorCode, 0, "The signing did not error with two keys");
+    local_isnot(aSignResponse.clientData, undefined, "The signing provided clientData with two keys");
+  });
+
+  await promiseU2FSign(window.location.origin, bytesToBase64UrlSafe(challenge),
+                    [invalidKey, testState.key2], function(aSignResponse) {
     local_is(aSignResponse.errorCode, 0, "The signing did not error when given an invalid key");
     local_isnot(aSignResponse.clientData, undefined, "The signing provided clientData even when given an invalid key");
+  });
 
-    testSignWithInvalidKeyReverse();
-  });
-}
-
-function testSignWithInvalidKeyReverse() {
-  window.u2f.sign(window.location.origin, bytesToBase64UrlSafe(challenge),
-                  [testState.key2, invalidKey], function(aSignResponse) {
+  await promiseU2FSign(window.location.origin, bytesToBase64UrlSafe(challenge),
+                    [testState.key2, invalidKey], function(aSignResponse) {
     local_is(aSignResponse.errorCode, 0, "The signing did not error when given an invalid key");
     local_isnot(aSignResponse.clientData, undefined, "The signing provided clientData even when given an invalid key");
+  });
 
-    local_completeTest();
+  await promiseU2FSign(window.location.origin, bytesToBase64UrlSafe(challenge),
+                    [invalidKey], function(aSignResponse) {
+    local_is(aSignResponse.errorCode, 4, "The signing couldn't complete with this invalid key");
+    local_is(aSignResponse.clientData, undefined, "The signing shouldn't provide clientData when there's no valid key");
   });
-}
-</script>
+
+  local_finished();
+};
 
+doTests();
+</script>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/dom/u2f/tests/frame_no_token.html
+++ b/dom/u2f/tests/frame_no_token.html
@@ -1,30 +1,31 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Test for FIDO Universal Second Factor No Token</title>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
 </head>
 <body>
+<p>No token check (because of how prefs work)</p>
+<script class="testbody" type="text/javascript">
+"use strict";
 
-<script class="testbody" type="text/javascript">
+async function doTests() {
+  var challenge = new Uint8Array(16);
+  window.crypto.getRandomValues(challenge);
 
-var challenge = new Uint8Array(16);
-window.crypto.getRandomValues(challenge);
+  var regRequest = {
+    version: "U2F_V2",
+    challenge: bytesToBase64UrlSafe(challenge),
+  };
 
-var regRequest = {
-  version: "U2F_V2",
-  challenge: bytesToBase64UrlSafe(challenge),
+  await promiseU2FRegister(window.location.origin, [regRequest], [], function (res) {
+    local_isnot(res.errorCode, 0, "The registration should be rejected.");
+  })
+
+  local_finished();
 };
 
-local_expectThisManyTests(1);
-
-u2f.register(window.location.origin, [regRequest], [], function (res) {
-  local_isnot(res.errorCode, 0, "The registration should be rejected.");
-  local_completeTest();
-});
-
+doTests();
 </script>
-
 </body>
 </html>
-
--- a/dom/u2f/tests/frame_register.html
+++ b/dom/u2f/tests/frame_register.html
@@ -1,81 +1,77 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
 </head>
 <body>
-<p>Test for Register behavior for FIDO Universal Second Factor</p>
+<p>Register behavior</p>
 <script class="testbody" type="text/javascript">
 "use strict";
-
 var version = "U2F_V2";
 var challenge = new Uint8Array(16);
 
-local_is(window.location.origin, "https://example.com", "Is loaded correctly");
-
-local_expectThisManyTests(7);
-
-// basic check
-u2f.register("https://example.com/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_is(res.errorCode, 0, "AppID should work from the domain");
-  local_completeTest();
-});
-
-u2f.register("https://example.net/appId", [{
-  version: version,
-  challenge: bytesToBase64UrlSafe(challenge),
-}], [], function(res){
-  local_is(res.errorCode, 2, "AppID should not work from other domains");
-  local_completeTest();
-});
-
-u2f.register("", [], [], function(res){
-  local_is(res.errorCode, 2, "Empty register requests");
-  local_completeTest();
-});
+async function doTests() {
+  local_is(window.location.origin, "https://example.com", "Is loaded correctly");
 
-local_doesThrow(function(){
-  u2f.register("", null, [], null);
-}, "Non-array register requests");
-
-local_doesThrow(function(){
-  u2f.register("", [], null, null);
-}, "Non-array sign requests");
-
-local_doesThrow(function(){
-  u2f.register("", null, null, null);
-}, "Non-array for both arguments");
+  // basic check
+  await promiseU2FRegister("https://example.com/appId", [{
+    version: version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_is(res.errorCode, 0, "AppID should work from the domain");
+  });
 
-u2f.register("", [{}], [], function(res){
-  local_is(res.errorCode, 2, "Empty request");
-  local_completeTest();
-});
-
-u2f.register("https://example.net/appId", [{
+  await promiseU2FRegister("https://example.net/appId", [{
     version: version,
-  }], [], function(res){
-    local_is(res.errorCode, 2, "Missing challenge");
-    local_completeTest();
-});
-
-u2f.register("https://example.net/appId", [{
     challenge: bytesToBase64UrlSafe(challenge),
   }], [], function(res){
-   local_is(res.errorCode, 2, "Missing version");
-   local_completeTest();
-});
+    local_is(res.errorCode, 2, "AppID should not work from other domains");
+  });
+
+  await promiseU2FRegister("", [], [], function(res){
+    local_is(res.errorCode, 2, "Empty register requests");
+  });
+
+  local_doesThrow(function(){
+    u2f.register("", null, [], null);
+  }, "Non-array register requests");
+
+  local_doesThrow(function(){
+    u2f.register("", [], null, null);
+  }, "Non-array sign requests");
+
+  local_doesThrow(function(){
+    u2f.register("", null, null, null);
+  }, "Non-array for both arguments");
+
+  await promiseU2FRegister("", [{}], [], function(res){
+    local_is(res.errorCode, 2, "Empty request");
+  });
 
-u2f.register("https://example.net/appId", [{
-    version: "a_version_00",
-    challenge: bytesToBase64UrlSafe(challenge),
-  }], [], function(res){
-    local_is(res.errorCode, 2, "Invalid version");
-    local_completeTest();
-});
+  await promiseU2FRegister("https://example.net/appId", [{
+      version: version,
+    }], [], function(res){
+      local_is(res.errorCode, 2, "Missing challenge");
+  });
+
+  await promiseU2FRegister("https://example.net/appId", [{
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+     local_is(res.errorCode, 2, "Missing version");
+  });
 
+  await promiseU2FRegister("https://example.net/appId", [{
+      version: "a_version_00",
+      challenge: bytesToBase64UrlSafe(challenge),
+    }], [], function(res){
+      local_is(res.errorCode, 2, "Invalid version");
+  });
+
+  local_finished();
+};
+
+doTests();
 </script>
 </body>
 </html>
--- a/dom/u2f/tests/frame_register_sign.html
+++ b/dom/u2f/tests/frame_register_sign.html
@@ -1,22 +1,26 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
+  <script type="text/javascript" src="frame_utils.js"></script>
   <script type="text/javascript" src="u2futil.js"></script>
+
+  <script type="text/javascript" src="pkijs/asn1.js"></script>
   <script type="text/javascript" src="pkijs/common.js"></script>
-  <script type="text/javascript" src="pkijs/asn1.js"></script>
   <script type="text/javascript" src="pkijs/x509_schema.js"></script>
   <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
 </head>
 <body>
-<p>Register and Sign Test for FIDO Universal Second Factor</p>
+<p>Register and sign behavior</p>
 <script class="testbody" type="text/javascript">
 "use strict";
 
+var version = "U2F_V2";
+var challenge = new Uint8Array(16);
 var state = {
   // Raw messages
   regRequest: null,
   regResponse: null,
 
   regKey: null,
   signChallenge: null,
   signResponse: null,
@@ -25,177 +29,157 @@ var state = {
   publicKey: null,
   keyHandle: null,
 
   // Constants
   version: "U2F_V2",
   appId: window.location.origin,
 };
 
-SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
-                                   ["security.webauth.u2f_enable_softtoken", true]]},
-function() {
+async function doTests() {
+  local_is(window.location.origin, "https://example.com", "Is loaded correctly");
+
   local_isnot(window.u2f, undefined, "U2F API endpoint must exist");
   local_isnot(window.u2f.register, undefined, "U2F Register API endpoint must exist");
   local_isnot(window.u2f.sign, undefined, "U2F Sign API endpoint must exist");
 
-  testRegistering();
-
-  function testRegistering() {
-    var challenge = new Uint8Array(16);
-    window.crypto.getRandomValues(challenge);
-
-    state.regRequest = {
-      version: state.version,
-      challenge: bytesToBase64UrlSafe(challenge),
-    };
-
-    u2f.register(state.appId, [state.regRequest], [], function(regResponse) {
-      state.regResponse = regResponse;
+  var challenge = new Uint8Array(16);
+  window.crypto.getRandomValues(challenge);
 
-      local_is(regResponse.errorCode, 0, "The registration did not error");
-      local_isnot(regResponse.registrationData, undefined, "The registration did not provide registration data");
-      if (regResponse.errorCode > 0) {
-        local_finished();
-        return;
-      }
+  state.regRequest = {
+    version: state.version,
+    challenge: bytesToBase64UrlSafe(challenge),
+  };
 
-      // Parse the response data from the U2F token
-      var registrationData = base64ToBytesUrlSafe(regResponse.registrationData);
-      local_is(registrationData[0], 0x05, "Reserved byte is correct")
-
-      state.publicKeyBytes = registrationData.slice(1, 66);
-      var keyHandleLength = registrationData[66];
-      state.keyHandleBytes = registrationData.slice(67, 67 + keyHandleLength);
-      state.keyHandle = bytesToBase64UrlSafe(state.keyHandleBytes);
-      state.attestation = registrationData.slice(67 + keyHandleLength);
+  await SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
+                                           ["security.webauth.webauthn_enable_softtoken", true],
+                                           ["security.webauth.webauthn_enable_usbtoken", false]]});
 
-      local_is(state.attestation[0], 0x30, "Attestation Certificate has correct starting byte");
-      var asn1 = org.pkijs.fromBER(state.attestation.buffer);
-      console.log(asn1);
-      state.attestationCert = new org.pkijs.simpl.CERT({ schema: asn1.result });
-      console.log(state.attestationCert);
-      state.attestationSig = state.attestation.slice(asn1.offset);
-      local_is(state.attestationCert.subject.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Subject");
-      local_is(state.attestationCert.issuer.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Issuer");
-      local_is(state.attestationCert.notAfter.value - state.attestationCert.notBefore.value, 1000*60*60*48, "Valid 48 hours (in millis)");
-
-      // Verify that the clientData from the U2F token makes sense
-      var clientDataJSON = "";
-      base64ToBytesUrlSafe(regResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
-      var clientData = JSON.parse(clientDataJSON);
-      local_is(clientData.typ, "navigator.id.finishEnrollment", "Register - Data type matches");
-      local_is(clientData.challenge, state.regRequest.challenge, "Register - Challenge matches");
-      local_is(clientData.origin, window.location.origin, "Register - Origins are the same");
+  // Ensure the SpecialPowers push worked properly
+  local_isnot(window.u2f, undefined, "U2F API endpoint must exist");
 
-      // Verify the signature from the attestation certificate
-      deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
-      .then(function(params){
-        state.appParam = params.appParam;
-        state.challengeParam = params.challengeParam;
-        return state.attestationCert.getPublicKey();
-      }).then(function(attestationPublicKey) {
-        var signedData = assembleRegistrationSignedData(state.appParam, state.challengeParam, state.keyHandleBytes, state.publicKeyBytes);
-        return verifySignature(attestationPublicKey, signedData, state.attestationSig);
-      }).then(function(verified) {
-        console.log("No error verifying signature");
-        local_ok(verified, "Attestation Certificate signature verified")
-         // Import the public key of the U2F token into WebCrypto
-        return importPublicKey(state.publicKeyBytes)
-      }).then(function(key) {
-        state.publicKey = key;
-        local_ok(true, "Imported public key")
+  await promiseU2FRegister(state.appId, [state.regRequest], [], function(regResponse) {
+      state.regResponse = regResponse;
+  });
 
-        // Ensure the attestation certificate is properly self-signed
-        return state.attestationCert.verify()
-      }).then(function(){
-        local_ok(true, "Attestation Certificate verification successful");
-
-        // Continue test
-        testReRegister()
-      }).catch(function(err){
-        console.log(err);
-        local_ok(false, "Attestation Certificate verification failed");
-        local_finished();
-      });
-    });
+  local_is(state.regResponse.errorCode, 0, "The registration did not error");
+  local_isnot(state.regResponse.registrationData, undefined, "The registration did not provide registration data");
+  if (state.regResponse.errorCode > 0) {
+    return;
   }
 
-  function testReRegister() {
-    state.regKey = {
-      version: state.version,
-      keyHandle: state.keyHandle,
-    };
+  // Parse the response data from the U2F token
+  var registrationData = base64ToBytesUrlSafe(state.regResponse.registrationData);
+  local_is(registrationData[0], 0x05, "Reserved byte is correct")
+
+  state.publicKeyBytes = registrationData.slice(1, 66);
+  var keyHandleLength = registrationData[66];
+  state.keyHandleBytes = registrationData.slice(67, 67 + keyHandleLength);
+  state.keyHandle = bytesToBase64UrlSafe(state.keyHandleBytes);
+  state.attestation = registrationData.slice(67 + keyHandleLength);
+
+  local_is(state.attestation[0], 0x30, "Attestation Certificate has correct starting byte");
+  var asn1 = org.pkijs.fromBER(state.attestation.buffer);
+  console.log(asn1);
+  state.attestationCert = new org.pkijs.simpl.CERT({ schema: asn1.result });
+  console.log(state.attestationCert);
+  state.attestationSig = state.attestation.slice(asn1.offset);
+  local_is(state.attestationCert.subject.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Subject");
+  local_is(state.attestationCert.issuer.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Issuer");
+  local_is(state.attestationCert.notAfter.value - state.attestationCert.notBefore.value, 1000*60*60*48, "Valid 48 hours (in millis)");
+
+  // Verify that the clientData from the U2F token makes sense
+  var clientDataJSON = "";
+  base64ToBytesUrlSafe(state.regResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
+  var clientData = JSON.parse(clientDataJSON);
+  local_is(clientData.typ, "navigator.id.finishEnrollment", "Register - Data type matches");
+  local_is(clientData.challenge, state.regRequest.challenge, "Register - Challenge matches");
+  local_is(clientData.origin, window.location.origin, "Register - Origins are the same");
 
-    // Test that we don't re-register if we provide regKey as an
-    // "already known" key handle. The U2F module should recognize regKey
-    // as being usable and, thus, give back errorCode 4.
-    u2f.register(state.appId, [state.regRequest], [state.regKey], function(regResponse) {
-      // Since we attempted to register with state.regKey as a known key, expect
-      // ineligible (=4).
-      local_is(regResponse.errorCode, 4, "The re-registration should show device ineligible");
-      local_is(regResponse.registrationData, undefined, "The re-registration did not provide registration data");
+  // Verify the signature from the attestation certificate
+  await deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
+  .then(function(params){
+    state.appParam = params.appParam;
+    state.challengeParam = params.challengeParam;
+    return state.attestationCert.getPublicKey();
+  }).then(function(attestationPublicKey) {
+    var signedData = assembleRegistrationSignedData(state.appParam, state.challengeParam, state.keyHandleBytes, state.publicKeyBytes);
+    return verifySignature(attestationPublicKey, signedData, state.attestationSig);
+  }).then(function(verified) {
+    local_ok(verified, "Attestation Certificate signature verified")
+     // Import the public key of the U2F token into WebCrypto
+    return importPublicKey(state.publicKeyBytes)
+  }).then(function(key) {
+    state.publicKey = key;
+    local_ok(true, "Imported public key")
+
+    // Ensure the attestation certificate is properly self-signed
+    return state.attestationCert.verify()
+  }).then(function(verified) {
+    local_ok(verified, "Register attestation signature verified")
+  });
 
-      // Continue test
-      testSigning();
-    });
+  state.regKey = {
+    version: state.version,
+    keyHandle: state.keyHandle,
+  };
+
+  // Test that we don't re-register if we provide regKey as an
+  // "already known" key handle. The U2F module should recognize regKey
+  // as being usable and, thus, give back errorCode 4.
+  await promiseU2FRegister(state.appId, [state.regRequest], [state.regKey], function(regResponse) {
+    // Since we attempted to register with state.regKey as a known key, expect
+    // ineligible (=4).
+    local_is(regResponse.errorCode, 4, "The re-registration should show device ineligible");
+    local_is(regResponse.registrationData, undefined, "The re-registration did not provide registration data");
+  });
+
+  window.crypto.getRandomValues(challenge);
+  state.signChallenge = bytesToBase64UrlSafe(challenge);
+
+  // Now try to sign the signature challenge
+  await promiseU2FSign(state.appId, state.signChallenge, [state.regKey], function(signResponse) {
+    state.signResponse = signResponse;
+  });
+
+  // Make sure this signature op worked, bailing early if it failed.
+  local_is(state.signResponse.errorCode, 0, "The signing did not error");
+  local_isnot(state.signResponse.clientData, undefined, "The signing did provide client data");
+
+  if (state.signResponse.errorCode > 0) {
+    return;
   }
 
-  function testSigning() {
-    var challenge = new Uint8Array(16);
-    window.crypto.getRandomValues(challenge);
-    state.signChallenge = bytesToBase64UrlSafe(challenge);
-
-    // Now try to sign the signature challenge
-    u2f.sign(state.appId, state.signChallenge, [state.regKey], function(signResponse) {
-      state.signResponse = signResponse;
-
-      // Make sure this signature op worked, bailing early if it failed.
-      local_is(signResponse.errorCode, 0, "The signing did not error");
-      local_isnot(signResponse.clientData, undefined, "The signing did not provide client data");
+  // Decode the clientData that was returned from the module
+  var clientDataJSON = "";
+  base64ToBytesUrlSafe(state.signResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
+  var clientData = JSON.parse(clientDataJSON);
+  local_is(clientData.typ, "navigator.id.getAssertion", "Sign - Data type matches");
+  local_is(clientData.challenge, state.signChallenge, "Sign - Challenge matches");
+  local_is(clientData.origin, window.location.origin, "Sign - Origins are the same");
 
-      if (signResponse.errorCode > 0) {
-        local_finished();
-        return;
-      }
-
-      // Decode the clientData that was returned from the module
-      var clientDataJSON = "";
-      base64ToBytesUrlSafe(signResponse.clientData).map(x => clientDataJSON += String.fromCharCode(x));
-      var clientData = JSON.parse(clientDataJSON);
-      local_is(clientData.typ, "navigator.id.getAssertion", "Sign - Data type matches");
-      local_is(clientData.challenge, state.signChallenge, "Sign - Challenge matches");
-      local_is(clientData.origin, window.location.origin, "Sign - Origins are the same");
+  // Parse the signature data
+  var signatureData = base64ToBytesUrlSafe(state.signResponse.signatureData);
+  if (signatureData[0] != 0x01) {
+    throw "User presence byte not set";
+  }
+  var presenceAndCounter = signatureData.slice(0,5);
+  var signatureValue = signatureData.slice(5);
 
-      // Parse the signature data
-      var signatureData = base64ToBytesUrlSafe(signResponse.signatureData);
-      if (signatureData[0] != 0x01) {
-        throw "User presence byte not set";
-      }
-      var presenceAndCounter = signatureData.slice(0,5);
-      var signatureValue = signatureData.slice(5);
+  // Assemble the signed data and verify the signature
+  await deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
+  .then(function(params){
+    return assembleSignedData(params.appParam, presenceAndCounter, params.challengeParam);
+  })
+  .then(function(signedData) {
+    return verifySignature(state.publicKey, signedData, signatureValue);
+  })
+  .then(function(verified) {
+    local_ok(verified, "Signing signature verified")
+  });
 
-      // Assemble the signed data and verify the signature
-      deriveAppAndChallengeParam(state.appId, string2buffer(clientDataJSON))
-      .then(function(params){
-        return assembleSignedData(params.appParam, presenceAndCounter, params.challengeParam);
-      })
-      .then(function(signedData) {
-        return verifySignature(state.publicKey, signedData, signatureValue);
-      })
-      .then(function(verified) {
-        console.log("No error verifying signing signature");
-        local_ok(verified, "Signing signature verified")
+  local_finished();
+};
 
-        local_finished();
-      })
-      .catch(function(err) {
-        console.log(err);
-        local_ok(false, "Signing signature invalid");
-        local_finished();
-      });
-    });
-  }
-});
-
+doTests();
 </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/frame_utils.js
@@ -0,0 +1,52 @@
+// Utilities to help talk between the frame_ iframe documents and the parent
+// tests.
+
+var _parentOrigin = "https://example.com/";
+
+function local_setParentOrigin(aOrigin) {
+  _parentOrigin = aOrigin;
+}
+
+function handleEventMessage(event) {
+  if ("test" in event.data) {
+    let summary = event.data.test + ": " + event.data.msg;
+    ok(event.data.status, summary);
+  } else if ("done" in event.data) {
+    SimpleTest.finish();
+  } else {
+    ok(false, "Unexpected message in the test harness: " + event.data)
+  }
+}
+
+function local_is(value, expected, message) {
+  if (value === expected) {
+    local_ok(true, message);
+  } else {
+    local_ok(false, message + " unexpectedly: " + value + " !== " + expected);
+  }
+}
+
+function local_isnot(value, expected, message) {
+  if (value !== expected) {
+    local_ok(true, message);
+  } else {
+    local_ok(false, message + " unexpectedly: " + value + " === " + expected);
+  }
+}
+
+function local_ok(expression, message) {
+  let body = {"test": this.location.pathname, "status":expression, "msg": message}
+  parent.postMessage(body, _parentOrigin);
+}
+
+function local_doesThrow(fn, name) {
+  let gotException = false;
+  try {
+    fn();
+  } catch (ex) { gotException = true; }
+  local_ok(gotException, name);
+};
+
+function local_finished() {
+  parent.postMessage({"done":true}, _parentOrigin);
+}
--- a/dom/u2f/tests/mochitest.ini
+++ b/dom/u2f/tests/mochitest.ini
@@ -1,32 +1,30 @@
 [DEFAULT]
 support-files =
   frame_appid_facet.html
   frame_appid_facet_insecure.html
   frame_appid_facet_subdomain.html
+  frame_multiple_keys.html
   frame_no_token.html
   frame_register.html
   frame_register_sign.html
-  frame_multiple_keys.html
+  frame_utils.js
   pkijs/asn1.js
   pkijs/common.js
   pkijs/x509_schema.js
   pkijs/x509_simpl.js
   u2futil.js
 
+scheme = https
+
 # Feature does not function without e10s (Disabled in Bug 1297552)
-[test_util_methods.html]
-skip-if = !e10s
-[test_no_token.html]
-skip-if = !e10s
-[test_register.html]
-skip-if = !e10s
-[test_register_sign.html]
 skip-if = !e10s
+
+[test_util_methods.html]
+[test_no_token.html]
+[test_register.html]
+[test_register_sign.html]
 [test_appid_facet.html]
-skip-if = !e10s
 [test_appid_facet_insecure.html]
-skip-if = !e10s
+scheme = http
 [test_appid_facet_subdomain.html]
-skip-if = !e10s
-[test_multiple_keys.html] # Disabled in bug 1334388
-skip-if = true
\ No newline at end of file
+[test_multiple_keys.html]
--- a/dom/u2f/tests/test_appid_facet.html
+++ b/dom/u2f/tests/test_appid_facet.html
@@ -1,34 +1,44 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
+  <title>FIDO U2F: AppID / FacetID behavior</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
+  <script type="text/javascript" src="pkijs/asn1.js"></script>
+  <script type="text/javascript" src="pkijs/common.js"></script>
+  <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+  <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
+<h1>FIDO U2F: AppID / FacetID behavior</h1>
+<ul>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681 (initial implementation)</a></li>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245527">Mozilla Bug 1245527 (hardware rewrite)</a></li>
+</ul>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
+"use strict";
 
 SimpleTest.waitForExplicitFinish();
 
+// listen for messages from the test harness
+window.addEventListener("message", handleEventMessage);
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
-                                   ["security.webauth.u2f_enable_softtoken", true],
-                                   ["security.webauth.u2f_enable_usbtoken", false]]},
-function() {
-  // listen for messages from the test harness
-  window.addEventListener("message", handleEventMessage);
-  document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_appid_facet.html";
-});
+                                   ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
+  function(){
+    document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_appid_facet.html";
+  });
 
 </script>
-
 </body>
 </html>
--- a/dom/u2f/tests/test_appid_facet_insecure.html
+++ b/dom/u2f/tests/test_appid_facet_insecure.html
@@ -1,34 +1,44 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
+  <title>FIDO U2F: Insecure AppID / FacetID behavior</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
+  <script type="text/javascript" src="pkijs/asn1.js"></script>
+  <script type="text/javascript" src="pkijs/common.js"></script>
+  <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+  <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
+<h1>FIDO U2F: Insecure AppID / FacetID behavior</h1>
+<ul>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681 (initial implementation)</a></li>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245527">Mozilla Bug 1245527 (hardware rewrite)</a></li>
+</ul>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
+"use strict";
 
 SimpleTest.waitForExplicitFinish();
 
+// listen for messages from the test harness
+window.addEventListener("message", handleEventMessage);
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
-                                   ["security.webauth.u2f_enable_softtoken", true],
-                                   ["security.webauth.u2f_enable_usbtoken", false]]},
-function() {
-  // listen for messages from the test harness
-  window.addEventListener("message", handleEventMessage);
-  document.getElementById('testing_frame').src = "http://mochi.test:8888/tests/dom/u2f/tests/frame_appid_facet_insecure.html";
-});
+                                   ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
+  function(){
+    document.getElementById('testing_frame').src = "http://test2.example.com/tests/dom/u2f/tests/frame_appid_facet_insecure.html";
+  });
 
 </script>
-
 </body>
 </html>
--- a/dom/u2f/tests/test_appid_facet_subdomain.html
+++ b/dom/u2f/tests/test_appid_facet_subdomain.html
@@ -1,34 +1,45 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
+  <title>FIDO U2F: Subdomain AppID / FacetID behavior</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
+  <script type="text/javascript" src="pkijs/asn1.js"></script>
+  <script type="text/javascript" src="pkijs/common.js"></script>
+  <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+  <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
+<h1>FIDO U2F: Subdomain AppID / FacetID behavior</h1>
+<ul>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681 (initial implementation)</a></li>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245527">Mozilla Bug 1245527 (hardware rewrite)</a></li>
+</ul>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
+"use strict";
 
 SimpleTest.waitForExplicitFinish();
 
+// listen for messages from the test harness
+window.addEventListener("message", handleEventMessage);
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
-                                   ["security.webauth.u2f_enable_softtoken", true],
-                                   ["security.webauth.u2f_enable_usbtoken", false]]},
-function() {
-  // listen for messages from the test harness
-  window.addEventListener("message", handleEventMessage);
-  document.getElementById('testing_frame').src = "https://test1.example.com/tests/dom/u2f/tests/frame_appid_facet_subdomain.html";
-});
+                                   ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
+  function(){
+    document.getElementById('testing_frame').src = "https://test1.example.com/tests/dom/u2f/tests/frame_appid_facet_subdomain.html";
+  });
 
 </script>
 
 </body>
 </html>
--- a/dom/u2f/tests/test_multiple_keys.html
+++ b/dom/u2f/tests/test_multiple_keys.html
@@ -1,39 +1,46 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Test for Multiple Keys for FIDO U2F</title>
+  <title>FIDO U2F: Multiple Keys</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
+  <script type="text/javascript" src="pkijs/asn1.js"></script>
+  <script type="text/javascript" src="pkijs/common.js"></script>
+  <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+  <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-
-<h1>Test Multiple Keys for FIDO U2F</h1>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1333592">Mozilla Bug 1333592</a>
-
-<script class="testbody" type="text/javascript">
-"use strict";
-
- /** Test for XBL scope behavior. **/
-SimpleTest.waitForExplicitFinish();
-
-// Embed the real test. It will take care of everything else.
-//
-// This is necessary since the U2F object on window is hidden behind a preference
-// and window won't pick up changes by pref without a reload.
-SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
-                                   ["security.webauth.u2f_enable_softtoken", true],
-                                   ["security.webauth.u2f_enable_usbtoken", false]]},
-function() {
-  // listen for messages from the test harness
-  window.addEventListener("message", handleEventMessage);
-  document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_multiple_keys.html";
-});
-</script>
+<h1>FIDO U2F: Multiple Keys</h1>
+<ul>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681 (initial implementation)</a></li>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245527">Mozilla Bug 1245527 (hardware rewrite)</a></li>
+</ul>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
+<pre id="log"></pre>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+// listen for messages from the test harness
+window.addEventListener("message", handleEventMessage);
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
+                                   ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
+  function(){
+    document.getElementById('testing_frame').src = "https://test2.example.com/tests/dom/u2f/tests/frame_multiple_keys.html";
+  });
+
+
+</script>
+
 </body>
 </html>
--- a/dom/u2f/tests/test_no_token.html
+++ b/dom/u2f/tests/test_no_token.html
@@ -1,32 +1,45 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Test for FIDO Universal Second Factor No Token</title>
+  <title>FIDO U2F: No Token</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
+  <script type="text/javascript" src="pkijs/asn1.js"></script>
+  <script type="text/javascript" src="pkijs/common.js"></script>
+  <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+  <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
+<h1>FIDO U2F: No Token</h1>
+<ul>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681 (initial implementation)</a></li>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245527">Mozilla Bug 1245527 (hardware rewrite)</a></li>
+</ul>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
+<pre id="log"></pre>
+
 <script class="testbody" type="text/javascript">
+"use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
-                                   ["security.webauth.u2f_enable_softtoken", false],
-                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+                                   ["security.webauth.webauthn_enable_softtoken", false],
+                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
   // listen for messages from the test harness
   window.addEventListener("message", handleEventMessage);
-  document.getElementById('testing_frame').src = 'frame_no_token.html';
+  document.getElementById('testing_frame').src = "https://test1.example.com/tests/dom/u2f/tests/frame_no_token.html";
 });
 
 </script>
 
 </body>
 </html>
--- a/dom/u2f/tests/test_register.html
+++ b/dom/u2f/tests/test_register.html
@@ -1,34 +1,44 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Test for Register behavior for FIDO Universal Second Factor</title>
+  <title>FIDO U2F: Register behavior</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
+  <script type="text/javascript" src="pkijs/asn1.js"></script>
+  <script type="text/javascript" src="pkijs/common.js"></script>
+  <script type="text/javascript" src="pkijs/x509_schema.js"></script>
+  <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
+<h1>FIDO U2F: Register behavior</h1>
+<ul>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681 (initial implementation)</a></li>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245527">Mozilla Bug 1245527 (hardware rewrite)</a></li>
+</ul>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
+"use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
-                                   ["security.webauth.u2f_enable_softtoken", true],
-                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+                                   ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
   // listen for messages from the test harness
   window.addEventListener("message", handleEventMessage);
   document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_register.html";
 });
-
 </script>
 
 </body>
 </html>
--- a/dom/u2f/tests/test_register_sign.html
+++ b/dom/u2f/tests/test_register_sign.html
@@ -1,34 +1,40 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Register and Sign Test for FIDO Universal Second Factor</title>
+  <title>FIDO U2F: Sign and Register behavior</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script src="u2futil.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="frame_utils.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
+<h1>FIDO U2F: Sign and Register behavior</h1>
+<ul>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681 (initial implementation)</a></li>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245527">Mozilla Bug 1245527 (hardware rewrite)</a></li>
+</ul>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
+"use strict";
 
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
-                                   ["security.webauth.u2f_enable_softtoken", true],
-                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+                                   ["security.webauth.webauthn_enable_softtoken", true],
+                                   ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
   // listen for messages from the test harness
   window.addEventListener("message", handleEventMessage);
-  document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_appid_facet.html";
+  document.getElementById('testing_frame').src = "https://example.com/tests/dom/u2f/tests/frame_register_sign.html";
 });
-
 </script>
 
 </body>
 </html>
--- a/dom/u2f/tests/test_util_methods.html
+++ b/dom/u2f/tests/test_util_methods.html
@@ -1,65 +1,50 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Test for Utility Methods for other FIDO Universal Second Factor tests</title>
+  <title>FIDO U2F: Validate Test Utilities</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/dom/u2f/tests/u2futil.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="u2futil.js"></script>
   <script type="text/javascript" src="pkijs/common.js"></script>
   <script type="text/javascript" src="pkijs/asn1.js"></script>
   <script type="text/javascript" src="pkijs/x509_schema.js"></script>
   <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
+<h1>FIDO U2F: Validate Test Utilities</h1>
+<ul>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681 (initial implementation)</a></li>
+  <li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1245527">Mozilla Bug 1245527 (hardware rewrite)</a></li>
+</ul>
 
-SimpleTest.waitForExplicitFinish();
+<pre id="log"></pre>
 
-SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
-                                   ["security.webauth.u2f_enable_softtoken", true],
-                                   ["security.webauth.u2f_enable_usbtoken", false]]},
-function() {
+<script class="testbody" type="text/javascript">
+"use strict";
+
+add_task(async function() {
   // Example from:
   // https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-u2f-raw-message-formats.html
   //
   // Run this example from the console to check that the u2futil methods work
   var pubKey = hexDecode("04d368f1b665bade3c33a20f1e429c7750d5033660c019119d29aa4ba7abc04aa7c80a46bbe11ca8cb5674d74f31f8a903f6bad105fb6ab74aefef4db8b0025e1d");
   var appId = "https://gstatic.com/securitykey/a/example.com";
   var clientData = string2buffer('{"typ":"navigator.id.getAssertion","challenge":"opsXqUifDriAAmWclinfbS0e-USY0CgyJHe_Otd7z8o","cid_pubkey":{"kty":"EC","crv":"P-256","x":"HzQwlfXX7Q4S5MtCCnZUNBw3RMzPO9tOyWjBqRl4tJ8","y":"XVguGFLIZx1fXg3wNqfdbn75hi4-_7-BxhMljw42Ht4"},"origin":"http://example.com"}');
   var presenceAndCounter = hexDecode("0100000001");
   var signature = hexDecode("304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f");
 
-  // Import the key
-  // Assemble the client data
-  // Verify
-  Promise.all([
-    importPublicKey(pubKey),
-    deriveAppAndChallengeParam(appId, clientData)
-  ])
-  .then(function(results) {
-    var importedKey = results[0];
-    var params = results[1];
-    var signedData = new Uint8Array(assembleSignedData(params.appParam, presenceAndCounter, params.challengeParam));
-    return verifySignature(importedKey, signedData, signature);
-  })
+  var importedKey = await importPublicKey(pubKey);
+  var params = await deriveAppAndChallengeParam(appId, clientData);
+  var signedData = new Uint8Array(assembleSignedData(params.appParam, presenceAndCounter, params.challengeParam));
+
+  await verifySignature(importedKey, signedData, signature)
   .then(function(verified) {
-    console.log("verified:", verified);
-    ok(true, "Utility methods work")
-    SimpleTest.finish();
-  })
-  .catch(function(err) {
-    console.log("error:", err);
-    ok(false, "Utility methods failed")
-    SimpleTest.finish();
+    ok(verified, "Utility methods work")
   });
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/u2f/tests/u2futil.js
+++ b/dom/u2f/tests/u2futil.js
@@ -1,88 +1,34 @@
-// Used by local_addTest() / local_completeTest()
-var _countCompletions = 0;
-var _expectedCompletions = 0;
-var _parentOrigin = "http://mochi.test:8888";
-
-function local_setParentOrigin(aOrigin) {
-  _parentOrigin = aOrigin;
+function promiseU2FRegister(aAppId, aChallenges, aExcludedKeys, aFunc) {
+  return new Promise(function(resolve, reject) {
+      u2f.register(aAppId, aChallenges, aExcludedKeys, function(res) {
+        aFunc(res);
+        resolve(res);
+      });
+  });
 }
 
-function handleEventMessage(event) {
-  if ("test" in event.data) {
-    let summary = event.data.test + ": " + event.data.msg;
-    log(event.data.status + ": " + summary);
-    ok(event.data.status, summary);
-  } else if ("done" in event.data) {
-    SimpleTest.finish();
-  } else {
-    ok(false, "Unexpected message in the test harness: " + event.data)
-  }
+function promiseU2FSign(aAppId, aChallenge, aAllowedKeys, aFunc) {
+  return new Promise(function(resolve, reject) {
+      u2f.sign(aAppId, aChallenge, aAllowedKeys, function(res) {
+        aFunc(res);
+        resolve(res);
+      });
+  });
 }
 
 function log(msg) {
   console.log(msg)
   let logBox = document.getElementById("log");
   if (logBox) {
     logBox.textContent += "\n" + msg;
   }
 }
 
-function local_is(value, expected, message) {
-  if (value === expected) {
-    local_ok(true, message);
-  } else {
-    local_ok(false, message + " unexpectedly: " + value + " !== " + expected);
-  }
-}
-
-function local_isnot(value, expected, message) {
-  if (value !== expected) {
-    local_ok(true, message);
-  } else {
-    local_ok(false, message + " unexpectedly: " + value + " === " + expected);
-  }
-}
-
-function local_ok(expression, message) {
-  let body = {"test": this.location.pathname, "status":expression, "msg": message}
-  parent.postMessage(body, _parentOrigin);
-}
-
-function local_doesThrow(fn, name) {
-  let gotException = false;
-  try {
-    fn();
-  } catch (ex) { gotException = true; }
-  local_ok(gotException, name);
-};
-
-function local_expectThisManyTests(count) {
-  if (_expectedCompletions > 0) {
-    local_ok(false, "Error: local_expectThisManyTests should only be called once.");
-  }
-  _expectedCompletions = count;
-}
-
-function local_completeTest() {
-  _countCompletions += 1
-  if (_countCompletions == _expectedCompletions) {
-    log("All tests completed.")
-    local_finished();
-  }
-  if (_countCompletions > _expectedCompletions) {
-    local_ok(false, "Error: local_completeTest called more than local_addTest.");
-  }
-}
-
-function local_finished() {
-  parent.postMessage({"done":true}, _parentOrigin);
-}
-
 function string2buffer(str) {
   return (new Uint8Array(str.length)).map((x, i) => str.charCodeAt(i));
 }
 
 function buffer2string(buf) {
   let str = "";
   buf.map(x => str += String.fromCharCode(x));
   return str;
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -6,24 +6,25 @@
 
 #include "hasht.h"
 #include "nsICryptoHash.h"
 #include "nsNetCID.h"
 #include "nsThreadUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/dom/AuthenticatorAttestationResponse.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PWebAuthnTransaction.h"
+#include "mozilla/dom/U2FUtil.h"
 #include "mozilla/dom/WebAuthnCBORUtil.h"
 #include "mozilla/dom/WebAuthnManager.h"
+#include "mozilla/dom/WebAuthnTransactionChild.h"
 #include "mozilla/dom/WebAuthnUtil.h"
-#include "mozilla/dom/PWebAuthnTransaction.h"
-#include "mozilla/dom/WebAuthnTransactionChild.h"
 #include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/PBackgroundChild.h"
-#include "mozilla/ipc/BackgroundChild.h"
 
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 
 /***********************************************************************
  * Protocol Constants
@@ -70,45 +71,16 @@ GetAlgorithmName(const OOS& aAlgorithm,
   } else {
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
 
   return NS_OK;
 }
 
 static nsresult
-HashCString(nsICryptoHash* aHashService, const nsACString& aIn,
-            /* out */ CryptoBuffer& aOut)
-{
-  MOZ_ASSERT(aHashService);
-
-  nsresult rv = aHashService->Init(nsICryptoHash::SHA256);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  rv = aHashService->Update(
-    reinterpret_cast<const uint8_t*>(aIn.BeginReading()),aIn.Length());
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  nsAutoCString fullHash;
-  // Passing false below means we will get a binary result rather than a
-  // base64-encoded string.
-  rv = aHashService->Finish(false, fullHash);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  aOut.Assign(fullHash);
-  return rv;
-}
-
-static nsresult
 AssembleClientData(const nsAString& aOrigin, const CryptoBuffer& aChallenge,
                    /* out */ nsACString& aJsonOut)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsString challengeBase64;
   nsresult rv = aChallenge.ToJwkBase64(challengeBase64);
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/security/manager/ssl/security-prefs.js
+++ b/security/manager/ssl/security-prefs.js
@@ -101,19 +101,16 @@ pref("security.pki.netscape_step_up_poli
 #endif
 
 // Configures Certificate Transparency support mode:
 // 0: Fully disabled.
 // 1: Only collect telemetry. CT qualification checks are not performed.
 pref("security.pki.certificate_transparency.mode", 0);
 
 pref("security.webauth.u2f", false);
-pref("security.webauth.u2f_enable_softtoken", false);
-pref("security.webauth.u2f_enable_usbtoken", false);
-
 pref("security.webauth.webauthn", false);
 pref("security.webauth.webauthn_enable_softtoken", false);
 pref("security.webauth.webauthn_enable_usbtoken", false);
 
 pref("security.ssl.errorReporting.enabled", true);
 pref("security.ssl.errorReporting.url", "https://incoming.telemetry.mozilla.org/submit/sslreports/");
 pref("security.ssl.errorReporting.automatic", false);