Backed out 3 changesets (bug 1245527) for ASan browser-chrome leaks and Android mochitest bustage
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 09 Sep 2017 00:09:21 -0700
changeset 429381 c3eea543172cde237ab6dafae9611539bf34f951
parent 429380 d59f7e89c0884cb430520b6bb136cc880fd3375d
child 429382 c71b01e993510268bab7d60154b2f80692fd507d
child 429404 bd62e8a3194213d0ef806a83f5c888b72b663cd0
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1245527
milestone57.0a1
backs out8ee1f7aebd6266c897a642dd9aafd8ba682f420c
e6a5de8d12467ae51e70ebd445900c2032e673e6
be63e73426b4d878946778c1f543c37a9c6ed1ce
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 3 changesets (bug 1245527) for ASan browser-chrome leaks and Android mochitest bustage Backed out changeset 8ee1f7aebd62 (bug 1245527) Backed out changeset e6a5de8d1246 (bug 1245527) Backed out changeset be63e73426b4 (bug 1245527) MozReview-Commit-ID: AU22LgPh9iB
dom/base/nsGlobalWindow.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
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/NSSU2FTokenRemote.cpp
dom/webauthn/NSSU2FTokenRemote.h
dom/webauthn/U2FTokenManager.cpp
dom/webauthn/U2FTokenManager.h
dom/webauthn/WebAuthnManager.cpp
dom/webauthn/moz.build
ipc/ipdl/sync-messages.ini
security/manager/ssl/moz.build
security/manager/ssl/nsINSSU2FToken.idl
security/manager/ssl/nsIU2FToken.idl
security/manager/ssl/nsNSSModule.cpp
security/manager/ssl/nsNSSU2FToken.cpp
security/manager/ssl/nsNSSU2FToken.h
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(AsInner());
-    u2f->Init(aError);
+    RefPtr<U2F> u2f = new U2F();
+    u2f->Init(AsInner(), aError);
     if (NS_WARN_IF(aError.Failed())) {
       return nullptr;
     }
 
     mU2F = u2f;
   }
   return mU2F;
 }
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -127,16 +127,17 @@
 #include "nsIFormProcessor.h"
 #include "nsIGfxInfo.h"
 #include "nsIIdleService.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIMemoryInfoDumper.h"
 #include "nsIMemoryReporter.h"
 #include "nsIMozBrowserFrame.h"
 #include "nsIMutable.h"
+#include "nsINSSU2FToken.h"
 #include "nsIObserverService.h"
 #include "nsIParentChannel.h"
 #include "nsIPresShell.h"
 #include "nsIRemoteWindowContext.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsISiteSecurityService.h"
 #include "nsISpellChecker.h"
@@ -3545,16 +3546,115 @@ ContentParent::RecvSetURITitle(const URI
   nsCOMPtr<IHistory> history = services::GetHistoryService();
   if (history) {
     history->SetURITitle(ourURI, title);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentParent::RecvNSSU2FTokenIsCompatibleVersion(const nsString& aVersion,
+                                                  bool* aIsCompatible)
+{
+  MOZ_ASSERT(aIsCompatible);
+
+  nsCOMPtr<nsINSSU2FToken> nssToken(do_GetService(NS_NSSU2FTOKEN_CONTRACTID));
+  if (NS_WARN_IF(!nssToken)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  nsresult rv = nssToken->IsCompatibleVersion(aVersion, aIsCompatible);
+  if (NS_FAILED(rv)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ContentParent::RecvNSSU2FTokenIsRegistered(nsTArray<uint8_t>&& aKeyHandle,
+                                           nsTArray<uint8_t>&& aApplication,
+                                           bool* aIsValidKeyHandle)
+{
+  MOZ_ASSERT(aIsValidKeyHandle);
+
+  nsCOMPtr<nsINSSU2FToken> nssToken(do_GetService(NS_NSSU2FTOKEN_CONTRACTID));
+  if (NS_WARN_IF(!nssToken)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  nsresult rv = nssToken->IsRegistered(aKeyHandle.Elements(), aKeyHandle.Length(),
+                                       aApplication.Elements(), aApplication.Length(),
+                                       aIsValidKeyHandle);
+  if (NS_FAILED(rv)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ContentParent::RecvNSSU2FTokenRegister(nsTArray<uint8_t>&& aApplication,
+                                       nsTArray<uint8_t>&& aChallenge,
+                                       nsTArray<uint8_t>* aRegistration)
+{
+  MOZ_ASSERT(aRegistration);
+
+  nsCOMPtr<nsINSSU2FToken> nssToken(do_GetService(NS_NSSU2FTOKEN_CONTRACTID));
+  if (NS_WARN_IF(!nssToken)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  uint8_t* buffer;
+  uint32_t bufferlen;
+  nsresult rv = nssToken->Register(aApplication.Elements(), aApplication.Length(),
+                                   aChallenge.Elements(), aChallenge.Length(),
+                                   &buffer, &bufferlen);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  MOZ_ASSERT(buffer);
+  aRegistration->ReplaceElementsAt(0, aRegistration->Length(), buffer, bufferlen);
+  free(buffer);
+  if (NS_FAILED(rv)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ContentParent::RecvNSSU2FTokenSign(nsTArray<uint8_t>&& aApplication,
+                                   nsTArray<uint8_t>&& aChallenge,
+                                   nsTArray<uint8_t>&& aKeyHandle,
+                                   nsTArray<uint8_t>* aSignature)
+{
+  MOZ_ASSERT(aSignature);
+
+  nsCOMPtr<nsINSSU2FToken> nssToken(do_GetService(NS_NSSU2FTOKEN_CONTRACTID));
+  if (NS_WARN_IF(!nssToken)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  uint8_t* buffer;
+  uint32_t bufferlen;
+  nsresult rv = nssToken->Sign(aApplication.Elements(), aApplication.Length(),
+                               aChallenge.Elements(), aChallenge.Length(),
+                               aKeyHandle.Elements(), aKeyHandle.Length(),
+                               &buffer, &bufferlen);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  MOZ_ASSERT(buffer);
+  aSignature->ReplaceElementsAt(0, aSignature->Length(), buffer, bufferlen);
+  free(buffer);
+  if (NS_FAILED(rv)) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentParent::RecvIsSecureURI(const uint32_t& aType,
                                const URIParams& aURI,
                                const uint32_t& aFlags,
                                const OriginAttributes& aOriginAttributes,
                                bool* aIsSecureURI)
 {
   nsCOMPtr<nsISiteSecurityService> sss(do_GetService(NS_SSSERVICE_CONTRACTID));
   if (!sss) {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -855,16 +855,32 @@ private:
 
   virtual PIPCBlobInputStreamParent*
   AllocPIPCBlobInputStreamParent(const nsID& aID,
                                  const uint64_t& aSize) override;
 
   virtual bool
   DeallocPIPCBlobInputStreamParent(PIPCBlobInputStreamParent* aActor) override;
 
+  virtual mozilla::ipc::IPCResult RecvNSSU2FTokenIsCompatibleVersion(const nsString& aVersion,
+                                                                     bool* aIsCompatible) override;
+
+  virtual mozilla::ipc::IPCResult RecvNSSU2FTokenIsRegistered(nsTArray<uint8_t>&& aKeyHandle,
+                                                              nsTArray<uint8_t>&& aApplication,
+                                                              bool* aIsValidKeyHandle) override;
+
+  virtual mozilla::ipc::IPCResult RecvNSSU2FTokenRegister(nsTArray<uint8_t>&& aApplication,
+                                                          nsTArray<uint8_t>&& aChallenge,
+                                                          nsTArray<uint8_t>* aRegistration) override;
+
+  virtual mozilla::ipc::IPCResult RecvNSSU2FTokenSign(nsTArray<uint8_t>&& aApplication,
+                                                      nsTArray<uint8_t>&& aChallenge,
+                                                      nsTArray<uint8_t>&& aKeyHandle,
+                                                      nsTArray<uint8_t>* aSignature) override;
+
   virtual mozilla::ipc::IPCResult RecvIsSecureURI(const uint32_t& aType, const URIParams& aURI,
                                                   const uint32_t& aFlags,
                                                   const OriginAttributes& aOriginAttributes,
                                                   bool* aIsSecureURI) override;
 
   virtual mozilla::ipc::IPCResult RecvAccumulateMixedContentHSTS(const URIParams& aURI,
                                                                  const bool& aActive,
                                                                  const bool& aHSTSPriming,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -697,16 +697,61 @@ parent:
         returns (nsresult rv, Endpoint<PPluginModuleParent> aEndpoint);
 
     async PJavaScript();
 
     async PRemoteSpellcheckEngine();
 
     async InitCrashReporter(Shmem shmem, NativeThreadId tid);
 
+    /**
+     * Is this token compatible with the provided version?
+     *
+     * |version| The offered version to test
+     * Returns |True| if the offered version is compatible
+     */
+    sync NSSU2FTokenIsCompatibleVersion(nsString version)
+        returns (bool result);
+
+    /**
+     * Return whether the provided KeyHandle belongs to this Token
+     *
+     * |keyHandle| Key Handle to evaluate.
+     * |application| The FIDO Application data that is associated with this key.
+     * Returns |True| if the Key Handle is ours.
+     */
+    sync NSSU2FTokenIsRegistered(uint8_t[] keyHandle, uint8_t[] application)
+        returns (bool isValidKeyHandle);
+
+    /**
+     * Generates a public/private keypair for the provided application
+     * and challenge, returning the pubkey, challenge response, and
+     * key handle in the registration data.
+     *
+     * |application| The FIDO Application data to associate with the key.
+     * |challenge| The Challenge to satisfy in the response.
+     * |registration| An array containing the pubkey, challenge response,
+     *                     and key handle.
+     */
+    sync NSSU2FTokenRegister(uint8_t[] application, uint8_t[] challenge)
+        returns (uint8_t[] registration);
+
+    /**
+     * Creates a signature over the "param" arguments using the private key
+     * provided in the key handle argument.
+     *
+     * |application| The FIDO Application data to associate with the key.
+     * |challenge| The Challenge to satisfy in the response.
+     * |keyHandle| The Key Handle opaque object to use.
+     * |signature| The resulting signature.
+     */
+    sync NSSU2FTokenSign(uint8_t[] application, uint8_t[] challenge,
+                         uint8_t[] keyHandle)
+        returns (uint8_t[] signature);
+
     sync IsSecureURI(uint32_t aType, URIParams aURI, uint32_t aFlags,
                      OriginAttributes aOriginAttributes)
         returns (bool isSecureURI);
 
     async AccumulateMixedContentHSTS(URIParams aURI, bool aActive, bool aHasHSTSPriming,
                                      OriginAttributes aOriginAttributes);
 
     nested(inside_cpow) async PHal();
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -1,389 +1,1069 @@
 /* -*- 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/dom/WebCryptoCommon.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ReentrantMonitor.h"
 #include "nsContentUtils.h"
+#include "nsINSSU2FToken.h"
 #include "nsNetCID.h"
-#include "nsNetUtil.h"
+#include "nsNSSComponent.h"
+#include "nsThreadUtils.h"
 #include "nsURLParsers.h"
-#include "U2FManager.h"
+#include "nsXPCOMCIDInternal.h"
+#include "pk11pub.h"
+
+using mozilla::dom::ContentChild;
 
 namespace mozilla {
 namespace dom {
 
-static mozilla::LazyLogModule gU2FLog("u2fmanager");
+#define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f_enable_softtoken"
+#define PREF_U2F_USBTOKEN_ENABLED  "security.webauth.u2f_enable_usbtoken"
 
+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 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 mozilla::LazyLogModule gU2FLog("u2f");
 
 static nsresult
 AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
-                   const nsAString& aChallenge,
-                   /* out */ nsString& aClientData)
+                   const nsAString& aChallenge, CryptoBuffer& aClientData)
 {
   MOZ_ASSERT(NS_IsMainThread());
   U2FClientData clientDataObject;
   clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
   clientDataObject.mChallenge.Construct(aChallenge);
   clientDataObject.mOrigin.Construct(aOrigin);
 
-  if (NS_WARN_IF(!clientDataObject.ToJSON(aClientData))) {
+  nsAutoString json;
+  if (NS_WARN_IF(!clientDataObject.ToJSON(json))) {
     return NS_ERROR_FAILURE;
   }
 
+  if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
   return NS_OK;
 }
 
-static void
-RegisteredKeysToScopedCredentialList(const nsAString& aAppId,
-  const nsTArray<RegisteredKey>& aKeys,
-  nsTArray<WebAuthnScopedCredentialDescriptor>& aList)
+U2FStatus::U2FStatus()
+  : mCount(0)
+  , mIsStopped(false)
+  , mReentrantMonitor("U2FStatus")
+{}
+
+U2FStatus::~U2FStatus()
+{}
+
+void
+U2FStatus::WaitGroupAdd()
 {
-  for (const RegisteredKey& key : aKeys) {
-    // Check for required attributes
-    if (!key.mVersion.WasPassed() || !key.mKeyHandle.WasPassed() ||
-        key.mVersion.Value() != kRequiredU2FVersion) {
-      continue;
-    }
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
-    // 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;
-    }
+  mCount += 1;
+  MOZ_LOG(gU2FLog, LogLevel::Debug,
+          ("U2FStatus::WaitGroupAdd, now %d", mCount));
+}
 
-    CryptoBuffer keyHandle;
-    nsresult rv = keyHandle.FromJwkBase64(key.mKeyHandle.Value());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      continue;
-    }
+void
+U2FStatus::WaitGroupDone()
+{
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
 
-    WebAuthnScopedCredentialDescriptor c;
-    c.id() = keyHandle;
-    aList.AppendElement(c);
+  MOZ_ASSERT(mCount > 0);
+  mCount -= 1;
+  MOZ_LOG(gU2FLog, LogLevel::Debug,
+          ("U2FStatus::WaitGroupDone, now %d", mCount));
+  if (mCount == 0) {
+    mReentrantMonitor.NotifyAll();
   }
 }
 
-static ErrorCode
-EvaluateAppID(const nsString& aOrigin, /* in/out */ nsString& aAppId)
+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()
+{
+  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 facetString = NS_ConvertUTF16toUTF8(aOrigin);
-  nsCOMPtr<nsIURI> facetUri;
-  if (NS_FAILED(NS_NewURI(getter_AddRefs(facetUri), facetString))) {
+  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))) {
     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
-  bool facetIsHttps = false;
-  if (NS_FAILED(facetUri->SchemeIs("https", &facetIsHttps)) || !facetIsHttps) {
+  if (!facetScheme.LowerCaseEqualsLiteral("https")) {
     return ErrorCode::BAD_REQUEST;
   }
 
   // If the appId is empty or null, overwrite it with the facetId and accept
-  if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
-    aAppId.Assign(aOrigin);
+  if (mAppId.IsEmpty() || mAppId.EqualsLiteral("null")) {
+    mAppId.Assign(mOrigin);
     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.
-  bool appIdIsHttps = false;
-  if (NS_FAILED(appIdUri->SchemeIs("https", &appIdIsHttps)) || !appIdIsHttps) {
+  if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
     return ErrorCode::BAD_REQUEST;
   }
 
-  // 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)) {
+  // If the facetId and the appId auths match, accept
+  if (facetAuth == appIdAuth) {
     return ErrorCode::OK;
   }
 
   // TODO(Bug 1244959) Implement the remaining algorithm.
   return ErrorCode::BAD_REQUEST;
 }
 
-template<typename T, typename C>
-static void
-ExecuteCallback(T& aResp, Maybe<nsMainThreadPtrHandle<C>>& aCb)
+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))
+{
+  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);
+  }
+
+  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);
+  }
+}
+
+U2FRegisterRunnable::~U2FRegisterRunnable()
+{
+  nsNSSShutDownPreventionLock locker;
+
+  if (isAlreadyShutDown()) {
+    return;
+  }
+  shutdown(ShutdownCalledFrom::Object);
+}
+
+void
+U2FRegisterRunnable::SetTimeout(const int32_t aTimeoutMillis)
+{
+  opt_mTimeoutSeconds.SetValue(aTimeoutMillis);
+}
+
+void
+U2FRegisterRunnable::SendResponse(const RegisterResponse& aResponse)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (aCb.isNothing()) {
-    return;
+
+  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();
+     });
+    }
   }
 
-  ErrorResult error;
-  aCb.ref()->Call(aResp, error);
-  NS_WARNING_ASSERTION(!error.Failed(), "dom::U2F::Promise callback failed");
-  error.SuppressException(); // Useful exceptions already emitted
+  // 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))
+{
+  MOZ_ASSERT(NS_IsMainThread());
 
-  aCb.reset();
-  MOZ_ASSERT(aCb.isNothing());
+  // 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();
 }
 
-U2F::U2F(nsPIDOMWindowInner* aParent)
-  : mParent(aParent)
+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()
 {
-  mPromiseHolder.DisconnectIfExists();
-  mRegisterCallback.reset();
-  mSignCallback.reset();
+  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(ErrorResult& aRv)
+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;
   }
 
-  mEventTarget = doc->EventTargetFor(TaskCategory::Other);
-  MOZ_ASSERT(mEventTarget);
-}
+  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;
+  }
 
-/* virtual */ JSObject*
-U2F::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return U2FBinding::Wrap(aCx, this, aGivenProto);
+  // 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;
 }
 
 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());
 
-  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);
+  if (!mInitialized) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
   }
 
-  // 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);
+  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);
 }
 
 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());
 
-  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);
+  if (!mInitialized) {
+    aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
   }
 
-  // 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);
+  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);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -5,55 +5,298 @@
  * 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)
 
-  explicit U2F(nsPIDOMWindowInner* aParent);
+  U2F();
 
   nsPIDOMWindowInner*
   GetParentObject() const
   {
     return mParent;
   }
 
   void
-  Init(ErrorResult& aRv);
+  Init(nsPIDOMWindowInner* aParent, 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,
@@ -64,23 +307,26 @@ 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:
+  nsCOMPtr<nsPIDOMWindowInner> mParent;
   nsString mOrigin;
-  nsCOMPtr<nsPIDOMWindowInner> mParent;
+  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 "mozilla/MozPromise.h"
+#include "nsIU2FToken.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 MozPromise<nsString, ErrorCode, false> U2FPromise;
+typedef nsCOMPtr<nsIU2FToken> Authenticator;
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_U2FAuthenticator_h
deleted file mode 100644
--- a/dom/u2f/U2FManager.cpp
+++ /dev/null
@@ -1,505 +0,0 @@
-/* -*- 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
deleted file mode 100644
--- a/dom/u2f/U2FManager.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/* -*- 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
deleted file mode 100644
--- a/dom/u2f/U2FTransactionChild.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-/* -*- 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
deleted file mode 100644
--- a/dom/u2f/U2FTransactionChild.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- 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
deleted file mode 100644
--- a/dom/u2f/U2FTransactionParent.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -*- 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
deleted file mode 100644
--- a/dom/u2f/U2FTransactionParent.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* -*- 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
deleted file mode 100644
--- a/dom/u2f/U2FUtil.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/* -*- 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,32 +5,27 @@
 # 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']
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/frame_appid_facet.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <script src="u2futil.js"></script>
+</head>
+<body>
+<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+local_is(window.location.origin, "https://example.com", "Is loaded correctly");
+
+var version = "U2F_V2";
+var challenge = new Uint8Array(16);
+
+local_expectThisManyTests(5);
+
+u2f.register(null, [{
+  version: version,
+  challenge: bytesToBase64UrlSafe(challenge),
+}], [], function(res){
+  local_is(res.errorCode, 0, "Null AppID should work.");
+  local_completeTest();
+});
+
+u2f.register("", [{
+  version: version,
+  challenge: bytesToBase64UrlSafe(challenge),
+}], [], function(res){
+  local_is(res.errorCode, 0, "Empty AppID should work.");
+  local_completeTest();
+});
+
+// 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
+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: 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();
+});
+
+</script>
+</body>
+</html>
--- a/dom/u2f/tests/frame_appid_facet_insecure.html
+++ b/dom/u2f/tests/frame_appid_facet_insecure.html
@@ -1,62 +1,60 @@
 <!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 src="u2futil.js"></script>
 </head>
 <body>
-<p>Insecure AppID / FacetID behavior check</p>
+<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
 <script class="testbody" type="text/javascript">
 "use strict";
 
-local_setParentOrigin("http://mochi.test:8888");
+local_is(window.location.origin, "http://mochi.test:8888", "Is loaded correctly");
 
-async function doTests() {
-  var version = "U2F_V2";
-  var challenge = new Uint8Array(16);
+var version = "U2F_V2";
+var challenge = new Uint8Array(16);
 
-  local_is(window.location.origin, "http://test2.example.com", "Is loaded correctly");
+local_expectThisManyTests(5);
 
-  await promiseU2FRegister(null, [{
-    version: version,
-    challenge: bytesToBase64UrlSafe(challenge),
-  }], [], function(res){
-    local_isnot(res.errorCode, 0, "Insecure origin disallowed for null AppID");
-  });
+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("", [{
-    version: version,
-    challenge: bytesToBase64UrlSafe(challenge),
-  }], [], function(res){
-    local_isnot(res.errorCode, 0, "Insecure origin disallowed for empty 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("http://example.com/appId", [{
-    version: version,
-    challenge: bytesToBase64UrlSafe(challenge),
-  }], [], function(res){
-    local_isnot(res.errorCode, 0, "Insecure origin disallowed for HTTP 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("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("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(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();
+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();
+});
 
 </script>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/dom/u2f/tests/frame_appid_facet_subdomain.html
+++ b/dom/u2f/tests/frame_appid_facet_subdomain.html
@@ -1,57 +1,56 @@
 <!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 src="u2futil.js"></script>
 </head>
 <body>
-<p>AppID / FacetID behavior check</p>
+<p>Test for AppID / FacetID behavior for FIDO Universal Second Factor</p>
 <script class="testbody" type="text/javascript">
 "use strict";
 
-async function doTests() {
-  var version = "U2F_V2";
-  var challenge = new Uint8Array(16);
+var version = "U2F_V2";
+var challenge = new Uint8Array(16);
 
-  local_is(window.location.origin, "https://test1.example.com", "Is loaded correctly");
+local_is(window.location.origin, "https://test1.example.com", "Is loaded correctly");
+
+local_expectThisManyTests(4);
 
-  // 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
+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, 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");
-  });
+// 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();
+});
 
-  // 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");
-  });
+// 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();
+});
 
-  // 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();
+// 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();
+});
 
 </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/frame_multiple_keys.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <script type="text/javascript" src="u2futil.js"></script>
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+"use strict";
+
+function keyHandleFromRegResponse(aRegResponse) {
+  // Parse the response data from the U2F token
+  var registrationData = base64ToBytesUrlSafe(aRegResponse.registrationData);
+  local_is(registrationData[0], 0x05, "Reserved byte is correct")
+
+  var keyHandleLength = registrationData[66];
+  var 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);
+window.crypto.getRandomValues(challenge);
+
+var regRequest = {
+  version: "U2F_V2",
+  challenge: bytesToBase64UrlSafe(challenge),
+};
+
+var 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 = {
+  "version": "U2F_V2",
+  "keyHandle": "rQdreHgHrmKfsnGPAElEP9yfTx6eq2eU3_Y8n0RRsGKML0DY2d1_a8_-sOtxDr3"
+};
+
+function testSignWithInvalidKey() {
+  window.u2f.sign(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) {
+    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();
+  });
+}
+</script>
+
+</body>
+</html>
\ No newline at end of file
--- a/dom/u2f/tests/frame_no_token.html
+++ b/dom/u2f/tests/frame_no_token.html
@@ -1,31 +1,30 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <script type="text/javascript" src="frame_utils.js"></script>
-  <script type="text/javascript" src="u2futil.js"></script>
+  <title>Test for FIDO Universal Second Factor No Token</title>
+  <script src="u2futil.js"></script>
 </head>
 <body>
-<p>No token check (because of how prefs work)</p>
+
 <script class="testbody" type="text/javascript">
-"use strict";
-
-async function doTests() {
-  var challenge = new Uint8Array(16);
-  window.crypto.getRandomValues(challenge);
 
-  var regRequest = {
-    version: "U2F_V2",
-    challenge: bytesToBase64UrlSafe(challenge),
-  };
+var challenge = new Uint8Array(16);
+window.crypto.getRandomValues(challenge);
 
-  await promiseU2FRegister(window.location.origin, [regRequest], [], function (res) {
-    local_isnot(res.errorCode, 0, "The registration should be rejected.");
-  })
-
-  local_finished();
+var regRequest = {
+  version: "U2F_V2",
+  challenge: bytesToBase64UrlSafe(challenge),
 };
 
-doTests();
+local_expectThisManyTests(1);
+
+u2f.register(window.location.origin, [regRequest], [], function (res) {
+  local_isnot(res.errorCode, 0, "The registration should be rejected.");
+  local_completeTest();
+});
+
 </script>
+
 </body>
 </html>
+
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/frame_register.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <script src="u2futil.js"></script>
+</head>
+<body>
+<p>Test for Register behavior for FIDO Universal Second Factor</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();
+});
+
+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");
+
+u2f.register("", [{}], [], function(res){
+  local_is(res.errorCode, 2, "Empty request");
+  local_completeTest();
+});
+
+u2f.register("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();
+});
+
+u2f.register("https://example.net/appId", [{
+    version: "a_version_00",
+    challenge: bytesToBase64UrlSafe(challenge),
+  }], [], function(res){
+    local_is(res.errorCode, 2, "Invalid version");
+    local_completeTest();
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/u2f/tests/frame_register_sign.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+  <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>
+</head>
+<body>
+<p>Register and Sign Test for FIDO Universal Second Factor</p>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+var state = {
+  // Raw messages
+  regRequest: null,
+  regResponse: null,
+
+  regKey: null,
+  signChallenge: null,
+  signResponse: null,
+
+  // Parsed values
+  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() {
+  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;
+
+      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;
+      }
+
+      // 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);
+
+      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");
+
+      // 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")
+
+        // 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();
+      });
+    });
+  }
+
+  function testReRegister() {
+    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.
+    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");
+
+      // Continue test
+      testSigning();
+    });
+  }
+
+  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");
+
+      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(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
+      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();
+      })
+      .catch(function(err) {
+        console.log(err);
+        local_ok(false, "Signing signature invalid");
+        local_finished();
+      });
+    });
+  }
+});
+
+</script>
+</body>
+</html>
deleted file mode 100644
--- a/dom/u2f/tests/frame_utils.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// 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,31 +1,32 @@
 [DEFAULT]
 support-files =
+  frame_appid_facet.html
   frame_appid_facet_insecure.html
   frame_appid_facet_subdomain.html
   frame_no_token.html
-  frame_utils.js
+  frame_register.html
+  frame_register_sign.html
+  frame_multiple_keys.html
   pkijs/asn1.js
   pkijs/common.js
   pkijs/x509_schema.js
   pkijs/x509_simpl.js
   u2futil.js
 
-prefs =
-    security.webauth.u2f=true
-    security.webauth.webauthn_enable_softtoken=true
-    security.webauth.webauthn_enable_usbtoken=false
-
-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]
-scheme = http
+skip-if = !e10s
 [test_appid_facet_subdomain.html]
-[test_multiple_keys.html]
+skip-if = !e10s
+[test_multiple_keys.html] # Disabled in bug 1334388
+skip-if = true
\ No newline at end of file
--- a/dom/u2f/tests/test_appid_facet.html
+++ b/dom/u2f/tests/test_appid_facet.html
@@ -1,68 +1,34 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>FIDO U2F: AppID / FacetID behavior</title>
+  <title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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>
+  <script src="u2futil.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
+
+<div id="framediv">
+  <iframe id="testing_frame"></iframe>
+</div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
-"use strict";
 
-var version = "U2F_V2";
-var challenge = new Uint8Array(16);
-
-add_task(async function(){
-  await promiseU2FRegister(null, [{
-      version: version,
-      challenge: bytesToBase64UrlSafe(challenge),
-    }], [], function(res){
-      is(res.errorCode, 0, "Null AppID should work.");
-  });
-
-  await promiseU2FRegister("", [{
-      version: version,
-      challenge: bytesToBase64UrlSafe(challenge),
-    }], [], function(res){
-      is(res.errorCode, 0, "Empty AppID should work.");
-  });
+SimpleTest.waitForExplicitFinish();
 
-  await promiseU2FRegister("http://example.com/appId", [{
-      version: version,
-      challenge: bytesToBase64UrlSafe(challenge),
-    }], [], function(res){
-      isnot(res.errorCode, 0, "HTTP scheme is disallowed");
-  });
-
-  await promiseU2FRegister("https://example.com/appId", [{
-      version: version,
-      challenge: bytesToBase64UrlSafe(challenge),
-    }], [], function(res){
-      is(res.errorCode, 0, "HTTPS origin for example.com should work");
-  });
-
-  await promiseU2FRegister(window.location.origin + "/otherAppId", [{
-      version: version,
-      challenge: bytesToBase64UrlSafe(challenge),
-    }], [], function(res){
-      is(res.errorCode, 0, "Direct window origin should work");
-  });
-})
+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";
+});
 
 </script>
+
 </body>
 </html>
--- a/dom/u2f/tests/test_appid_facet_insecure.html
+++ b/dom/u2f/tests/test_appid_facet_insecure.html
@@ -1,37 +1,34 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>FIDO U2F: Insecure AppID / FacetID behavior</title>
+  <title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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/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>
+  <script src="u2futil.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-// listen for messages from the test harness
-window.addEventListener("message", handleEventMessage);
-document.getElementById('testing_frame').src = "http://test2.example.com/tests/dom/u2f/tests/frame_appid_facet_insecure.html";
+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";
+});
+
 </script>
+
 </body>
 </html>
--- a/dom/u2f/tests/test_appid_facet_subdomain.html
+++ b/dom/u2f/tests/test_appid_facet_subdomain.html
@@ -1,39 +1,34 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>FIDO U2F: Subdomain AppID / FacetID behavior</title>
+  <title>Test for AppID / FacetID behavior for FIDO Universal Second Factor</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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/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>
+  <script src="u2futil.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-// 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";
+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";
+});
 
 </script>
 
 </body>
 </html>
--- a/dom/u2f/tests/test_multiple_keys.html
+++ b/dom/u2f/tests/test_multiple_keys.html
@@ -1,124 +1,39 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>FIDO U2F: Multiple Keys</title>
+  <title>Test for Multiple Keys for FIDO U2F</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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>
+  <script src="u2futil.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<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>
 
-<pre id="log"></pre>
+<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";
 
-function keyHandleFromRegResponse(aRegResponse) {
-  // Parse the response data from the U2F token
-  var registrationData = base64ToBytesUrlSafe(aRegResponse.registrationData);
-  is(registrationData[0], 0x05, "Reserved byte is correct")
-
-  var keyHandleLength = registrationData[66];
-  var keyHandleBytes = registrationData.slice(67, 67 + keyHandleLength);
-
-  return {
-    version: "U2F_V2",
-    keyHandle: bytesToBase64UrlSafe(keyHandleBytes),
-  };
-}
-
-// Ensure the SpecialPowers push worked properly
-add_task(async function(){
-  isnot(window.u2f, undefined, "U2F API endpoint must exist");
-});
-
-var challenge = new Uint8Array(16);
-window.crypto.getRandomValues(challenge);
-
-var regRequest = {
-  version: "U2F_V2",
-  challenge: bytesToBase64UrlSafe(challenge),
-};
-
-var testState = {
-  key1: null,
-  key2: null,
-}
-
-// Just a key that came from a random profile; syntactically valid but not
-// unwrappable.
-let invalidKey = {
-  "version": "U2F_V2",
-  "keyHandle": "rQdreHgHrmKfsnGPAElEP9yfTx6eq2eU3_Y8n0RRsGKML0DY2d1_a8_-sOtxDr3"
-};
-
-add_task(async function(){
-  // Get two valid keys and present them
-  await promiseU2FRegister(window.location.origin, [regRequest], [], function(aRegResponse) {
-    testState.key1 = keyHandleFromRegResponse(aRegResponse);
-  });
+ /** Test for XBL scope behavior. **/
+SimpleTest.waitForExplicitFinish();
 
-  // 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
-    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
-    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) {
-    is(aSignResponse.errorCode, 0, "The signing did not error with one key");
-    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) {
-    is(aSignResponse.errorCode, 0, "The signing did not error with two keys");
-    isnot(aSignResponse.clientData, undefined, "The signing provided clientData with two keys");
-  });
-
-  await promiseU2FSign(window.location.origin, bytesToBase64UrlSafe(challenge),
-                    [invalidKey, testState.key2], function(aSignResponse) {
-    is(aSignResponse.errorCode, 0, "The signing did not error when given an invalid key");
-    isnot(aSignResponse.clientData, undefined, "The signing provided clientData even when given an invalid key");
-  });
-
-  await promiseU2FSign(window.location.origin, bytesToBase64UrlSafe(challenge),
-                    [testState.key2, invalidKey], function(aSignResponse) {
-    is(aSignResponse.errorCode, 0, "The signing did not error when given an invalid key");
-    isnot(aSignResponse.clientData, undefined, "The signing provided clientData even when given an invalid key");
-  });
-
-  await promiseU2FSign(window.location.origin, bytesToBase64UrlSafe(challenge),
-                    [invalidKey], function(aSignResponse) {
-    is(aSignResponse.errorCode, 4, "The signing couldn't complete with this invalid key");
-    is(aSignResponse.clientData, undefined, "The signing shouldn't provide clientData when there's no valid key");
-  });
+// 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>
 
+<div id="framediv">
+  <iframe id="testing_frame"></iframe>
+</div>
+
 </body>
 </html>
--- a/dom/u2f/tests/test_no_token.html
+++ b/dom/u2f/tests/test_no_token.html
@@ -1,45 +1,32 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>FIDO U2F: No Token</title>
+  <title>Test for FIDO Universal Second Factor No Token</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.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>
-
-  <script type="text/javascript" src="frame_utils.js"></script>
-  <script type="text/javascript" src="u2futil.js"></script>
-
+  <script src="u2futil.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<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>
-
-<pre id="log"></pre>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
 
 <div id="framediv">
   <iframe id="testing_frame"></iframe>
 </div>
 
 <script class="testbody" type="text/javascript">
-"use strict";
 
 SimpleTest.waitForExplicitFinish();
 
-SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn_enable_softtoken", false]]},
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
+                                   ["security.webauth.u2f_enable_softtoken", false],
+                                   ["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_no_token.html";
+  document.getElementById('testing_frame').src = 'frame_no_token.html';
 });
 
 </script>
 
 </body>
 </html>
--- a/dom/u2f/tests/test_register.html
+++ b/dom/u2f/tests/test_register.html
@@ -1,92 +1,34 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>FIDO U2F: Register behavior</title>
+  <title>Test for Register behavior for FIDO Universal Second Factor</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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>
+  <script src="u2futil.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
+
+<div id="framediv">
+  <iframe id="testing_frame"></iframe>
+</div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
-"use strict";
+
+SimpleTest.waitForExplicitFinish();
 
-var version = "U2F_V2";
-var challenge = new Uint8Array(16);
-
-add_task(async function(){
-  is(window.location.origin, "https://example.com", "Is loaded correctly");
+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_register.html";
 });
 
-add_task(async function(){
-  // basic check
-  await promiseU2FRegister("https://example.com/appId", [{
-    version: version,
-    challenge: bytesToBase64UrlSafe(challenge),
-  }], [], function(res){
-    is(res.errorCode, 0, "AppID should work from the domain");
-  });
-
-  await promiseU2FRegister("https://example.net/appId", [{
-    version: version,
-    challenge: bytesToBase64UrlSafe(challenge),
-  }], [], function(res){
-    is(res.errorCode, 2, "AppID should not work from other domains");
-  });
-
-  await promiseU2FRegister("", [], [], function(res){
-    is(res.errorCode, 2, "Empty register requests");
-  });
-
-  SimpleTest.doesThrow(function(){
-    u2f.register("", null, [], null);
-  }, "Non-array register requests");
-
-  SimpleTest.doesThrow(function(){
-    u2f.register("", [], null, null);
-  }, "Non-array sign requests");
-
-  SimpleTest.doesThrow(function(){
-    u2f.register("", null, null, null);
-  }, "Non-array for both arguments");
-
-  await promiseU2FRegister("", [{}], [], function(res){
-    is(res.errorCode, 2, "Empty request");
-  });
-
-  await promiseU2FRegister("https://example.net/appId", [{
-      version: version,
-    }], [], function(res){
-      is(res.errorCode, 2, "Missing challenge");
-  });
-
-  await promiseU2FRegister("https://example.net/appId", [{
-      challenge: bytesToBase64UrlSafe(challenge),
-    }], [], function(res){
-     is(res.errorCode, 2, "Missing version");
-  });
-
-  await promiseU2FRegister("https://example.net/appId", [{
-      version: "a_version_00",
-      challenge: bytesToBase64UrlSafe(challenge),
-    }], [], function(res){
-      is(res.errorCode, 2, "Invalid version");
-  });
-
-});
 </script>
 
 </body>
 </html>
--- a/dom/u2f/tests/test_register_sign.html
+++ b/dom/u2f/tests/test_register_sign.html
@@ -1,188 +1,34 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>FIDO U2F: Sign and Register behavior</title>
+  <title>Register and Sign Test for FIDO Universal Second Factor</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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>
+  <script src="u2futil.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
-<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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1231681">Mozilla Bug 1231681</a>
+
+<div id="framediv">
+  <iframe id="testing_frame"></iframe>
+</div>
 
 <pre id="log"></pre>
 
 <script class="testbody" type="text/javascript">
-"use strict";
+
+SimpleTest.waitForExplicitFinish();
 
-var version = "U2F_V2";
-var challenge = new Uint8Array(16);
-
-add_task(async function(){
-  is(window.location.origin, "https://example.com", "Is loaded correctly");
+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";
 });
 
-var state = {
-  // Raw messages
-  regRequest: null,
-  regResponse: null,
-
-  regKey: null,
-  signChallenge: null,
-  signResponse: null,
-
-  // Parsed values
-  publicKey: null,
-  keyHandle: null,
-
-  // Constants
-  version: "U2F_V2",
-  appId: window.location.origin,
-};
-
-add_task(async function(){
-  isnot(window.u2f, undefined, "U2F API endpoint must exist");
-  isnot(window.u2f.register, undefined, "U2F Register API endpoint must exist");
-  isnot(window.u2f.sign, undefined, "U2F Sign API endpoint must exist");
-
-  var challenge = new Uint8Array(16);
-  window.crypto.getRandomValues(challenge);
-
-  state.regRequest = {
-    version: state.version,
-    challenge: bytesToBase64UrlSafe(challenge),
-  };
-
-  await promiseU2FRegister(state.appId, [state.regRequest], [], function(regResponse) {
-      state.regResponse = regResponse;
-  });
-
-  is(state.regResponse.errorCode, 0, "The registration did not error");
-  isnot(state.regResponse.registrationData, undefined, "The registration did not provide registration data");
-  if (state.regResponse.errorCode > 0) {
-    return;
-  }
-
-  // Parse the response data from the U2F token
-  var registrationData = base64ToBytesUrlSafe(state.regResponse.registrationData);
-  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);
-
-  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);
-  is(state.attestationCert.subject.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Subject");
-  is(state.attestationCert.issuer.types_and_values[0].value.value_block.value, "Firefox U2F Soft Token", "Expected Issuer");
-  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);
-  is(clientData.typ, "navigator.id.finishEnrollment", "Register - Data type matches");
-  is(clientData.challenge, state.regRequest.challenge, "Register - Challenge matches");
-  is(clientData.origin, window.location.origin, "Register - Origins are the same");
-
-  // 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) {
-    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;
-    ok(true, "Imported public key")
-
-    // Ensure the attestation certificate is properly self-signed
-    return state.attestationCert.verify()
-  }).then(function(verified) {
-    ok(verified, "Register attestation signature verified")
-  });
-
-  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).
-    is(regResponse.errorCode, 4, "The re-registration should show device ineligible");
-    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.
-  is(state.signResponse.errorCode, 0, "The signing did not error");
-  isnot(state.signResponse.clientData, undefined, "The signing did provide client data");
-
-  if (state.signResponse.errorCode > 0) {
-    return;
-  }
-
-  // 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);
-  is(clientData.typ, "navigator.id.getAssertion", "Sign - Data type matches");
-  is(clientData.challenge, state.signChallenge, "Sign - Challenge matches");
-  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);
-
-  // 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) {
-    ok(verified, "Signing signature verified")
-  });
-});
 </script>
 
 </body>
 </html>
--- a/dom/u2f/tests/test_util_methods.html
+++ b/dom/u2f/tests/test_util_methods.html
@@ -1,50 +1,65 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>FIDO U2F: Validate Test Utilities</title>
+  <title>Test for Utility Methods for other FIDO Universal Second Factor tests</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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="/tests/dom/u2f/tests/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>
-<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>
+<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">
 
-<pre id="log"></pre>
+SimpleTest.waitForExplicitFinish();
 
-<script class="testbody" type="text/javascript">
-"use strict";
-
-add_task(async function() {
+SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
+                                   ["security.webauth.u2f_enable_softtoken", true],
+                                   ["security.webauth.u2f_enable_usbtoken", false]]},
+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");
 
-  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)
+  // 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);
+  })
   .then(function(verified) {
-    ok(verified, "Utility methods work")
+    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();
   });
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/u2f/tests/u2futil.js
+++ b/dom/u2f/tests/u2futil.js
@@ -1,34 +1,88 @@
-function promiseU2FRegister(aAppId, aChallenges, aExcludedKeys, aFunc) {
-  return new Promise(function(resolve, reject) {
-      u2f.register(aAppId, aChallenges, aExcludedKeys, function(res){
-        aFunc(res);
-        resolve(res);
-      });
-  });
+// 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 promiseU2FSign(aAppId, aChallenge, aAllowedKeys, aFunc) {
-  return new Promise(function(resolve, reject) {
-      u2f.sign(aAppId, aChallenge, aAllowedKeys, 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 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;
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/NSSU2FTokenRemote.cpp
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ContentChild.h"
+
+#include "NSSU2FTokenRemote.h"
+
+using mozilla::dom::ContentChild;
+
+NS_IMPL_ISUPPORTS(NSSU2FTokenRemote, nsIU2FToken)
+
+static mozilla::LazyLogModule gWebauthLog("webauth_u2f");
+
+NSSU2FTokenRemote::NSSU2FTokenRemote()
+{}
+
+NSSU2FTokenRemote::~NSSU2FTokenRemote()
+{}
+
+NS_IMETHODIMP
+NSSU2FTokenRemote::IsCompatibleVersion(const nsAString& aVersionString,
+                                       bool* aIsCompatible)
+{
+  NS_ENSURE_ARG_POINTER(aIsCompatible);
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenIsCompatibleVersion(
+        nsString(aVersionString), aIsCompatible)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NSSU2FTokenRemote::IsRegistered(uint8_t* aKeyHandle, uint32_t aKeyHandleLen,
+                                uint8_t* aAppParam, uint32_t aAppParamLen,
+                                bool* aIsRegistered)
+{
+  NS_ENSURE_ARG_POINTER(aKeyHandle);
+  NS_ENSURE_ARG_POINTER(aAppParam);
+  NS_ENSURE_ARG_POINTER(aIsRegistered);
+
+  nsTArray<uint8_t> keyHandle;
+  if (!keyHandle.ReplaceElementsAt(0, keyHandle.Length(), aKeyHandle,
+                                   aKeyHandleLen)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  nsTArray<uint8_t> appParam;
+  if (!appParam.ReplaceElementsAt(0, appParam.Length(), aAppParam,
+                                  aAppParamLen)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenIsRegistered(keyHandle, appParam, aIsRegistered)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NSSU2FTokenRemote::Register(uint8_t* aApplication,
+                            uint32_t aApplicationLen,
+                            uint8_t* aChallenge,
+                            uint32_t aChallengeLen,
+                            uint8_t** aRegistration,
+                            uint32_t* aRegistrationLen)
+{
+  NS_ENSURE_ARG_POINTER(aApplication);
+  NS_ENSURE_ARG_POINTER(aChallenge);
+  NS_ENSURE_ARG_POINTER(aRegistration);
+  NS_ENSURE_ARG_POINTER(aRegistrationLen);
+
+  nsTArray<uint8_t> application;
+  if (!application.ReplaceElementsAt(0, application.Length(), aApplication,
+                                     aApplicationLen)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  nsTArray<uint8_t> challenge;
+  if (!challenge.ReplaceElementsAt(0, challenge.Length(), aChallenge,
+                                   aChallengeLen)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  nsTArray<uint8_t> registrationBuffer;
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenRegister(application, challenge,
+                                   &registrationBuffer)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  size_t dataLen = registrationBuffer.Length();
+  uint8_t* tmp = reinterpret_cast<uint8_t*>(moz_xmalloc(dataLen));
+  if (NS_WARN_IF(!tmp)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  memcpy(tmp, registrationBuffer.Elements(), dataLen);
+  *aRegistration = tmp;
+  *aRegistrationLen = dataLen;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+NSSU2FTokenRemote::Sign(uint8_t* aApplication, uint32_t aApplicationLen,
+                        uint8_t* aChallenge, uint32_t aChallengeLen,
+                        uint8_t* aKeyHandle, uint32_t aKeyHandleLen,
+                        uint8_t** aSignature, uint32_t* aSignatureLen)
+{
+  NS_ENSURE_ARG_POINTER(aApplication);
+  NS_ENSURE_ARG_POINTER(aChallenge);
+  NS_ENSURE_ARG_POINTER(aKeyHandle);
+  NS_ENSURE_ARG_POINTER(aSignature);
+  NS_ENSURE_ARG_POINTER(aSignatureLen);
+
+  nsTArray<uint8_t> application;
+  if (!application.ReplaceElementsAt(0, application.Length(), aApplication,
+                                     aApplicationLen)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  nsTArray<uint8_t> challenge;
+  if (!challenge.ReplaceElementsAt(0, challenge.Length(), aChallenge,
+                                   aChallengeLen)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  nsTArray<uint8_t> keyHandle;
+  if (!keyHandle.ReplaceElementsAt(0, keyHandle.Length(), aKeyHandle,
+                                   aKeyHandleLen)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  nsTArray<uint8_t> signatureBuffer;
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenSign(application, challenge, keyHandle,
+                               &signatureBuffer)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  size_t dataLen = signatureBuffer.Length();
+  uint8_t* tmp = reinterpret_cast<uint8_t*>(moz_xmalloc(dataLen));
+  if (NS_WARN_IF(!tmp)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  memcpy(tmp, signatureBuffer.Elements(), dataLen);
+  *aSignature = tmp;
+  *aSignatureLen = dataLen;
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/NSSU2FTokenRemote.h
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSSU2FTokenRemote_h
+#define NSSU2FTokenRemote_h
+
+#include "nsIU2FToken.h"
+
+class NSSU2FTokenRemote : public nsIU2FToken
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIU2FTOKEN
+
+  NSSU2FTokenRemote();
+
+private:
+  virtual ~NSSU2FTokenRemote();
+};
+
+#endif // NSSU2FTokenRemote_h
--- a/dom/webauthn/U2FTokenManager.cpp
+++ b/dom/webauthn/U2FTokenManager.cpp
@@ -3,17 +3,17 @@
 /* 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 "mozilla/dom/U2FTokenTransport.h"
 #include "mozilla/dom/U2FHIDTokenManager.h"
 #include "mozilla/dom/U2FSoftTokenManager.h"
-#include "mozilla/dom/PWebAuthnTransactionParent.h"
+#include "mozilla/dom/WebAuthnTransactionParent.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/dom/WebAuthnUtil.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Unused.h"
 #include "hasht.h"
 #include "nsICryptoHash.h"
 #include "pkix/Input.h"
 #include "pkixutil.h"
@@ -160,17 +160,17 @@ U2FTokenManager::Get()
 void
 U2FTokenManager::AbortTransaction(const nsresult& aError)
 {
   Unused << mTransactionParent->SendCancel(aError);
   ClearTransaction();
 }
 
 void
-U2FTokenManager::MaybeClearTransaction(PWebAuthnTransactionParent* aParent)
+U2FTokenManager::MaybeClearTransaction(WebAuthnTransactionParent* aParent)
 {
   // Only clear if we've been requested to do so by our current transaction
   // parent.
   if (mTransactionParent == aParent) {
     ClearTransaction();
   }
 }
 
@@ -213,17 +213,17 @@ U2FTokenManager::GetTokenManagerImpl()
   }
 
   // TODO Use WebAuthnRequest to aggregate results from all transports,
   //      once we have multiple HW transport types.
   return new U2FHIDTokenManager();
 }
 
 void
-U2FTokenManager::Register(PWebAuthnTransactionParent* aTransactionParent,
+U2FTokenManager::Register(WebAuthnTransactionParent* aTransactionParent,
                           const WebAuthnTransactionInfo& aTransactionInfo)
 {
   MOZ_LOG(gU2FTokenManagerLog, LogLevel::Debug, ("U2FAuthRegister"));
 
   ClearTransaction();
   mTransactionParent = aTransactionParent;
   mTokenManagerImpl = GetTokenManagerImpl();
 
@@ -295,17 +295,17 @@ U2FTokenManager::MaybeAbortRegister(uint
     return;
   }
 
   mRegisterPromise.Complete();
   AbortTransaction(aError);
 }
 
 void
-U2FTokenManager::Sign(PWebAuthnTransactionParent* aTransactionParent,
+U2FTokenManager::Sign(WebAuthnTransactionParent* aTransactionParent,
                       const WebAuthnTransactionInfo& aTransactionInfo)
 {
   MOZ_LOG(gU2FTokenManagerLog, LogLevel::Debug, ("U2FAuthSign"));
 
   ClearTransaction();
   mTransactionParent = aTransactionParent;
   mTokenManagerImpl = GetTokenManagerImpl();
 
@@ -374,17 +374,17 @@ U2FTokenManager::MaybeAbortSign(uint64_t
     return;
   }
 
   mSignPromise.Complete();
   AbortTransaction(aError);
 }
 
 void
-U2FTokenManager::Cancel(PWebAuthnTransactionParent* aParent)
+U2FTokenManager::Cancel(WebAuthnTransactionParent* aParent)
 {
   if (mTransactionParent != aParent) {
     return;
   }
 
   mTokenManagerImpl->Cancel();
   ClearTransaction();
 }
--- a/dom/webauthn/U2FTokenManager.h
+++ b/dom/webauthn/U2FTokenManager.h
@@ -31,38 +31,38 @@ public:
   enum TransactionType
   {
     RegisterTransaction = 0,
     SignTransaction,
     NumTransactionTypes
   };
   NS_INLINE_DECL_REFCOUNTING(U2FTokenManager)
   static U2FTokenManager* Get();
-  void Register(PWebAuthnTransactionParent* aTransactionParent,
+  void Register(WebAuthnTransactionParent* aTransactionParent,
                 const WebAuthnTransactionInfo& aTransactionInfo);
-  void Sign(PWebAuthnTransactionParent* aTransactionParent,
+  void Sign(WebAuthnTransactionParent* aTransactionParent,
             const WebAuthnTransactionInfo& aTransactionInfo);
-  void Cancel(PWebAuthnTransactionParent* aTransactionParent);
-  void MaybeClearTransaction(PWebAuthnTransactionParent* aParent);
+  void Cancel(WebAuthnTransactionParent* aTransactionParent);
+  void MaybeClearTransaction(WebAuthnTransactionParent* aParent);
   static void Initialize();
 private:
   U2FTokenManager();
   ~U2FTokenManager();
   RefPtr<U2FTokenTransport> GetTokenManagerImpl();
   void AbortTransaction(const nsresult& aError);
   void ClearTransaction();
   void MaybeConfirmRegister(uint64_t aTransactionId,
                             U2FRegisterResult& aResult);
   void MaybeAbortRegister(uint64_t aTransactionId, const nsresult& aError);
   void MaybeConfirmSign(uint64_t aTransactionId, U2FSignResult& aResult);
   void MaybeAbortSign(uint64_t aTransactionId, const nsresult& aError);
   // Using a raw pointer here, as the lifetime of the IPC object is managed by
   // the PBackground protocol code. This means we cannot be left holding an
   // invalid IPC protocol object after the transaction is finished.
-  PWebAuthnTransactionParent* mTransactionParent;
+  WebAuthnTransactionParent* mTransactionParent;
   RefPtr<U2FTokenTransport> mTokenManagerImpl;
   MozPromiseRequestHolder<U2FRegisterPromise> mRegisterPromise;
   MozPromiseRequestHolder<U2FSignPromise> mSignPromise;
   // Guards the asynchronous promise resolution of token manager impls.
   // We don't need to protect this with a lock as it will only be modified
   // and checked on the PBackground thread in the parent process.
   uint64_t mTransactionId;
 };
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -7,25 +7,24 @@
 #include "hasht.h"
 #include "nsICryptoHash.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h" // Used by WD-05 compat support (Remove in Bug 1381126)
 #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/WebAuthnUtil.h"
+#include "mozilla/dom/PWebAuthnTransaction.h"
 #include "mozilla/dom/WebAuthnTransactionChild.h"
-#include "mozilla/dom/WebAuthnUtil.h"
 #include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
-#include "mozilla/ipc/PBackgroundChild.h"
 
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 
 /***********************************************************************
  * Protocol Constants
@@ -72,16 +71,45 @@ 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/dom/webauthn/moz.build
+++ b/dom/webauthn/moz.build
@@ -10,16 +10,17 @@ with Files("**"):
 IPDL_SOURCES += [
     'PWebAuthnTransaction.ipdl'
 ]
 
 EXPORTS.mozilla.dom += [
     'AuthenticatorAssertionResponse.h',
     'AuthenticatorAttestationResponse.h',
     'AuthenticatorResponse.h',
+    'NSSU2FTokenRemote.h',
     'PublicKeyCredential.h',
     'U2FHIDTokenManager.h',
     'U2FSoftTokenManager.h',
     'U2FTokenManager.h',
     'U2FTokenTransport.h',
     'WebAuthnCBORUtil.h',
     'WebAuthnManager.h',
     'WebAuthnRequest.h',
@@ -29,16 +30,17 @@ EXPORTS.mozilla.dom += [
 ]
 
 UNIFIED_SOURCES += [
     'AuthenticatorAssertionResponse.cpp',
     'AuthenticatorAttestationResponse.cpp',
     'AuthenticatorResponse.cpp',
     'cbor-cpp/src/encoder.cpp',
     'cbor-cpp/src/output_dynamic.cpp',
+    'NSSU2FTokenRemote.cpp',
     'PublicKeyCredential.cpp',
     'U2FHIDTokenManager.cpp',
     'U2FSoftTokenManager.cpp',
     'U2FTokenManager.cpp',
     'WebAuthnCBORUtil.cpp',
     'WebAuthnManager.cpp',
     'WebAuthnTransactionChild.cpp',
     'WebAuthnTransactionParent.cpp',
--- a/ipc/ipdl/sync-messages.ini
+++ b/ipc/ipdl/sync-messages.ini
@@ -851,16 +851,24 @@ description =
 [PContent::CreateChildProcess]
 description =
 [PContent::BridgeToChildProcess]
 description =
 [PContent::LoadPlugin]
 description =
 [PContent::ConnectPluginBridge]
 description =
+[PContent::NSSU2FTokenIsCompatibleVersion]
+description =
+[PContent::NSSU2FTokenIsRegistered]
+description =
+[PContent::NSSU2FTokenRegister]
+description =
+[PContent::NSSU2FTokenSign]
+description =
 [PContent::IsSecureURI]
 description =
 [PContent::PURLClassifier]
 description =
 [PContent::ClassifyLocal]
 description =
 [PContent::GetGfxVars]
 description =
--- a/security/manager/ssl/moz.build
+++ b/security/manager/ssl/moz.build
@@ -20,30 +20,32 @@ XPIDL_SOURCES += [
     'nsICryptoHash.idl',
     'nsICryptoHMAC.idl',
     'nsIDataSignatureVerifier.idl',
     'nsIGenKeypairInfoDlg.idl',
     'nsIKeygenThread.idl',
     'nsIKeyModule.idl',
     'nsILocalCertService.idl',
     'nsINSSErrorsService.idl',
+    'nsINSSU2FToken.idl',
     'nsINSSVersion.idl',
     'nsIPK11Token.idl',
     'nsIPK11TokenDB.idl',
     'nsIPKCS11Module.idl',
     'nsIPKCS11ModuleDB.idl',
     'nsIPKCS11Slot.idl',
     'nsIProtectedAuthThread.idl',
     'nsISecretDecoderRing.idl',
     'nsISecurityUITelemetry.idl',
     'nsISiteSecurityService.idl',
     'nsISSLStatus.idl',
     'nsISSLStatusProvider.idl',
     'nsITokenDialogs.idl',
     'nsITokenPasswordDialogs.idl',
+    'nsIU2FToken.idl',
     'nsIX509Cert.idl',
     'nsIX509CertDB.idl',
     'nsIX509CertList.idl',
     'nsIX509CertValidity.idl',
 ]
 
 if CONFIG['MOZ_XUL']:
     XPIDL_SOURCES += [
@@ -62,16 +64,17 @@ TESTING_JS_MODULES.psm += [
 EXPORTS += [
     'CryptoTask.h',
     'nsClientAuthRemember.h',
     'nsNSSCallbacks.h',
     'nsNSSCertificate.h',
     'nsNSSComponent.h',
     'nsNSSHelper.h',
     'nsNSSShutDown.h',
+    'nsNSSU2FToken.h',
     'nsRandomGenerator.h',
     'nsSecurityHeaderParser.h',
     'NSSErrorsService.h',
     'ScopedNSSTypes.h',
     'SharedCertVerifier.h',
 ]
 
 EXPORTS.mozilla += [
@@ -110,16 +113,17 @@ UNIFIED_SOURCES += [
     'nsNSSCertificateDB.cpp',
     'nsNSSCertTrust.cpp',
     'nsNSSCertValidity.cpp',
     'nsNSSComponent.cpp',
     'nsNSSErrors.cpp',
     'nsNSSIOLayer.cpp',
     'nsNSSModule.cpp',
     'nsNSSShutDown.cpp',
+    'nsNSSU2FToken.cpp',
     'nsNSSVersion.cpp',
     'nsNTLMAuthModule.cpp',
     'nsPK11TokenDB.cpp',
     'nsPKCS11Slot.cpp',
     'nsPKCS12Blob.cpp',
     'nsProtectedAuthThread.cpp',
     'nsRandomGenerator.cpp',
     'nsSecureBrowserUIImpl.cpp',
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/nsINSSU2FToken.idl
@@ -0,0 +1,16 @@
+/* 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 "nsIU2FToken.idl"
+
+/**
+ * Interface used to interact with the NSS-backed software U2F Token
+ */
+[scriptable, uuid(d9104a00-140b-4f86-a4b0-4998878ef4e6 )]
+interface nsINSSU2FToken : nsIU2FToken {
+};
+
+%{C++
+#define NS_NSSU2FTOKEN_CONTRACTID  "@mozilla.org/dom/u2f/nss-u2f-token;1"
+%}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/nsIU2FToken.idl
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+
+/**
+ * Interface used to interact with U2F Token devices
+ */
+[scriptable, uuid(5778242f-1f42-47a2-b514-fa1adde2d904)]
+interface nsIU2FToken : nsISupports {
+  /**
+   * Is this token compatible with the provided version?
+   *
+   * @param version The offered version to test
+   * @return True if the offered version is compatible
+   */
+  [must_use]
+  void isCompatibleVersion(in AString version, [retval] out boolean result);
+
+  /**
+   * Return whether the provided KeyHandle belongs to this Token
+   *
+   * @param keyHandle Key Handle to evaluate.
+   * @param application The FIDO Application data to associate with the key.
+   * @return True if the Key Handle is ours.
+   */
+  [must_use]
+  void isRegistered([array, size_is(keyHandleLen)] in octet keyHandle,
+                    in uint32_t keyHandleLen,
+                    [array, size_is(applicationLen)] in octet application,
+                    in uint32_t applicationLen,
+                    [retval] out boolean result);
+
+  /**
+   * Generates a public/private keypair for the provided application
+   * and challenge, returning the pubkey, challenge response, and
+   * key handle in the registration data.
+   *
+   * @param application The FIDO Application data to associate with the key.
+   * @param challenge The Challenge to satisfy in the response.
+   * @param registration An array containing the pubkey, challenge response,
+   *                     and key handle.
+   */
+  [must_use]
+  void register([array, size_is(applicationLen)] in octet application,
+                in uint32_t applicationLen,
+                [array, size_is(challengeLen)] in octet challenge,
+                in uint32_t challengeLen,
+                [array, size_is(registrationLen)] out octet registration,
+                out uint32_t registrationLen);
+
+  /**
+   * Creates a signature over the "param" arguments using the private key
+   * provided in the key handle argument.
+   *
+   * @param application The FIDO Application data to associate with the key.
+   * @param challenge The Challenge to satisfy in the response.
+   * @param keyHandle The Key Handle opaque object to use.
+   * @param signature The resulting signature.
+   */
+  [must_use]
+  void sign([array, size_is(applicationLen)] in octet application,
+            in uint32_t applicationLen,
+            [array, size_is(challengeLen)] in octet challenge,
+            in uint32_t challengeLen,
+            [array, size_is(keyHandleLen)] in octet keyHandle,
+            in uint32_t keyHandleLen,
+            [array, size_is(signatureLen)] out octet signature,
+            out uint32_t signatureLen);
+};
--- a/security/manager/ssl/nsNSSModule.cpp
+++ b/security/manager/ssl/nsNSSModule.cpp
@@ -17,16 +17,17 @@
 #include "nsCryptoHash.h"
 #include "nsDataSignatureVerifier.h"
 #include "nsICategoryManager.h"
 #include "nsKeyModule.h"
 #include "nsKeygenHandler.h"
 #include "nsNSSCertificate.h"
 #include "nsNSSCertificateDB.h"
 #include "nsNSSComponent.h"
+#include "nsNSSU2FToken.h"
 #include "nsNSSVersion.h"
 #include "nsNTLMAuthModule.h"
 #include "nsNetCID.h"
 #include "nsPK11TokenDB.h"
 #include "nsPKCS11Slot.h"
 #include "nsRandomGenerator.h"
 #include "nsSSLSocketProvider.h"
 #include "nsSSLStatus.h"
@@ -149,16 +150,17 @@ NS_DEFINE_NAMED_CID(NS_CRYPTO_HASH_CID);
 NS_DEFINE_NAMED_CID(NS_CRYPTO_HMAC_CID);
 NS_DEFINE_NAMED_CID(NS_NTLMAUTHMODULE_CID);
 NS_DEFINE_NAMED_CID(NS_KEYMODULEOBJECT_CID);
 NS_DEFINE_NAMED_CID(NS_KEYMODULEOBJECTFACTORY_CID);
 NS_DEFINE_NAMED_CID(NS_DATASIGNATUREVERIFIER_CID);
 NS_DEFINE_NAMED_CID(NS_CONTENTSIGNATUREVERIFIER_CID);
 NS_DEFINE_NAMED_CID(NS_CERTOVERRIDE_CID);
 NS_DEFINE_NAMED_CID(NS_RANDOMGENERATOR_CID);
+NS_DEFINE_NAMED_CID(NS_NSSU2FTOKEN_CID);
 NS_DEFINE_NAMED_CID(NS_SSLSTATUS_CID);
 NS_DEFINE_NAMED_CID(TRANSPORTSECURITYINFO_CID);
 NS_DEFINE_NAMED_CID(NS_NSSERRORSSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_NSSVERSION_CID);
 NS_DEFINE_NAMED_CID(NS_SECURE_BROWSER_UI_CID);
 NS_DEFINE_NAMED_CID(NS_SITE_SECURITY_SERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_CERT_BLOCKLIST_CID);
 
@@ -197,16 +199,18 @@ static const mozilla::Module::CIDEntry k
   { &kNS_CONTENTSIGNATUREVERIFIER_CID, false, nullptr,
     Constructor<ContentSignatureVerifier> },
   { &kNS_CERTOVERRIDE_CID, false, nullptr,
     Constructor<nsCertOverrideService, &nsCertOverrideService::Init,
                 ProcessRestriction::ParentProcessOnly,
                 ThreadRestriction::MainThreadOnly> },
   { &kNS_RANDOMGENERATOR_CID, false, nullptr,
     Constructor<nsRandomGenerator, nullptr, ProcessRestriction::AnyProcess> },
+  { &kNS_NSSU2FTOKEN_CID, false, nullptr,
+    Constructor<nsNSSU2FToken, &nsNSSU2FToken::Init> },
   { &kNS_SSLSTATUS_CID, false, nullptr,
     Constructor<nsSSLStatus, nullptr, ProcessRestriction::AnyProcess> },
   { &kTRANSPORTSECURITYINFO_CID, false, nullptr,
     Constructor<TransportSecurityInfo, nullptr,
                 ProcessRestriction::AnyProcess> },
   { &kNS_NSSERRORSSERVICE_CID, false, nullptr, NSSErrorsServiceConstructor },
   { &kNS_NSSVERSION_CID, false, nullptr, nsNSSVersionConstructor },
   { &kNS_SECURE_BROWSER_UI_CID, false, nullptr, nsSecureBrowserUIImplConstructor },
@@ -242,16 +246,17 @@ static const mozilla::Module::ContractID
   { "@mozilla.org/uriloader/psm-external-content-listener;1", &kNS_PSMCONTENTLISTEN_CID },
   { NS_NTLMAUTHMODULE_CONTRACTID, &kNS_NTLMAUTHMODULE_CID },
   { NS_KEYMODULEOBJECT_CONTRACTID, &kNS_KEYMODULEOBJECT_CID },
   { NS_KEYMODULEOBJECTFACTORY_CONTRACTID, &kNS_KEYMODULEOBJECTFACTORY_CID },
   { NS_DATASIGNATUREVERIFIER_CONTRACTID, &kNS_DATASIGNATUREVERIFIER_CID },
   { NS_CONTENTSIGNATUREVERIFIER_CONTRACTID, &kNS_CONTENTSIGNATUREVERIFIER_CID },
   { NS_CERTOVERRIDE_CONTRACTID, &kNS_CERTOVERRIDE_CID },
   { NS_RANDOMGENERATOR_CONTRACTID, &kNS_RANDOMGENERATOR_CID },
+  { NS_NSSU2FTOKEN_CONTRACTID, &kNS_NSSU2FTOKEN_CID },
   { NS_SECURE_BROWSER_UI_CONTRACTID, &kNS_SECURE_BROWSER_UI_CID },
   { NS_SSSERVICE_CONTRACTID, &kNS_SITE_SECURITY_SERVICE_CID },
   { NS_CERTBLOCKLIST_CONTRACTID, &kNS_CERT_BLOCKLIST_CID },
   { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kNSSCategories[] = {
   { NS_CONTENT_LISTENER_CATEGORYMANAGER_ENTRY, "application/x-x509-ca-cert", "@mozilla.org/uriloader/psm-external-content-listener;1" },
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/nsNSSU2FToken.cpp
@@ -0,0 +1,876 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNSSU2FToken.h"
+
+#include "CryptoBuffer.h"
+#include "mozilla/Base64.h"
+#include "mozilla/Casting.h"
+#include "nsNSSComponent.h"
+#include "pk11pub.h"
+#include "prerror.h"
+#include "secerr.h"
+#include "WebCryptoCommon.h"
+
+using namespace mozilla;
+using mozilla::dom::CreateECParamsForCurve;
+
+NS_IMPL_ISUPPORTS(nsNSSU2FToken, nsIU2FToken, nsINSSU2FToken)
+
+// Not named "security.webauth.u2f_softtoken_counter" because setting that
+// name causes the window.u2f object to disappear until preferences get
+// reloaded, as its' pref is a substring!
+#define PREF_U2F_NSSTOKEN_COUNTER "security.webauth.softtoken_counter"
+
+const nsCString nsNSSU2FToken::mSecretNickname =
+  NS_LITERAL_CSTRING("U2F_NSSTOKEN");
+const nsString nsNSSU2FToken::mVersion =
+  NS_LITERAL_STRING("U2F_V2");
+const char* kAttestCertSubjectName = "CN=Firefox U2F Soft Token";
+
+// This U2F-compatible soft token uses FIDO U2F-compatible ECDSA keypairs
+// on the SEC_OID_SECG_EC_SECP256R1 curve. When asked to Register, it will
+// generate and return a new keypair KP, where the private component is wrapped
+// using AES-KW with the 128-bit mWrappingKey to make an opaque "key handle".
+// In other words, Register yields { KP_pub, AES-KW(KP_priv, key=mWrappingKey) }
+//
+// The value mWrappingKey is long-lived; it is persisted as part of the NSS DB
+// for the current profile. The attestation certificates that are produced are
+// ephemeral to counteract profiling. They have little use for a soft-token
+// at any rate, but are required by the specification.
+
+const uint32_t kParamLen = 32;
+const uint32_t kPublicKeyLen = 65;
+const uint32_t kWrappedKeyBufLen = 256;
+const uint32_t kWrappingKeyByteLen = 128/8;
+const uint32_t kSaltByteLen = 64/8;
+const uint32_t kVersion1KeyHandleLen = 162;
+NS_NAMED_LITERAL_STRING(kEcAlgorithm, WEBCRYPTO_NAMED_CURVE_P256);
+
+const PRTime kOneDay = PRTime(PR_USEC_PER_SEC)
+                     * PRTime(60)  // sec
+                     * PRTime(60)  // min
+                     * PRTime(24); // hours
+const PRTime kExpirationSlack = kOneDay; // Pre-date for clock skew
+const PRTime kExpirationLife = kOneDay;
+
+enum SoftTokenHandle {
+  Version1 = 0,
+};
+
+static mozilla::LazyLogModule gNSSTokenLog("webauth_u2f");
+
+nsNSSU2FToken::nsNSSU2FToken()
+  : mInitialized(false)
+{}
+
+nsNSSU2FToken::~nsNSSU2FToken()
+{
+  nsNSSShutDownPreventionLock locker;
+
+  if (isAlreadyShutDown()) {
+    return;
+  }
+
+  destructorSafeDestroyNSSReference();
+  shutdown(ShutdownCalledFrom::Object);
+}
+
+void
+nsNSSU2FToken::virtualDestroyNSSReference()
+{
+  destructorSafeDestroyNSSReference();
+}
+
+void
+nsNSSU2FToken::destructorSafeDestroyNSSReference()
+{
+  mWrappingKey = nullptr;
+}
+
+/**
+ * Gets the first key with the given nickname from the given slot. Any other
+ * keys found are not returned.
+ * PK11_GetNextSymKey() should not be called on the returned key.
+ *
+ * @param aSlot Slot to search.
+ * @param aNickname Nickname the key should have.
+ * @return The first key found. nullptr if no key could be found.
+ */
+static UniquePK11SymKey
+GetSymKeyByNickname(const UniquePK11SlotInfo& aSlot,
+                    const nsCString& aNickname,
+                    const nsNSSShutDownPreventionLock&)
+{
+  MOZ_ASSERT(aSlot);
+  if (!aSlot) {
+    return nullptr;
+  }
+
+  MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
+          ("Searching for a symmetric key named %s", aNickname.get()));
+
+  UniquePK11SymKey keyListHead(
+    PK11_ListFixedKeysInSlot(aSlot.get(), const_cast<char*>(aNickname.get()),
+                             /* wincx */ nullptr));
+  if (!keyListHead) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key not found."));
+    return nullptr;
+  }
+
+  // Sanity check PK11_ListFixedKeysInSlot() only returns keys with the correct
+  // nickname.
+  MOZ_ASSERT(aNickname ==
+               UniquePORTString(PK11_GetSymKeyNickname(keyListHead.get())).get());
+  MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("Symmetric key found!"));
+
+  // Free any remaining keys in the key list.
+  UniquePK11SymKey freeKey(PK11_GetNextSymKey(keyListHead.get()));
+  while (freeKey) {
+    freeKey = UniquePK11SymKey(PK11_GetNextSymKey(freeKey.get()));
+  }
+
+  return keyListHead;
+}
+
+static nsresult
+GenEcKeypair(const UniquePK11SlotInfo& aSlot,
+     /*out*/ UniqueSECKEYPrivateKey& aPrivKey,
+     /*out*/ UniqueSECKEYPublicKey& aPubKey,
+             const nsNSSShutDownPreventionLock&)
+{
+  MOZ_ASSERT(aSlot);
+  if (!aSlot) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+  if (!arena) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // Set the curve parameters; keyParams belongs to the arena memory space
+  SECItem* keyParams = CreateECParamsForCurve(kEcAlgorithm, arena.get());
+  if (!keyParams) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // Generate a key pair
+  CK_MECHANISM_TYPE mechanism = CKM_EC_KEY_PAIR_GEN;
+
+  SECKEYPublicKey* pubKeyRaw;
+  aPrivKey = UniqueSECKEYPrivateKey(
+    PK11_GenerateKeyPair(aSlot.get(), mechanism, keyParams, &pubKeyRaw,
+                         /* ephemeral */ false, false,
+                         /* wincx */ nullptr));
+  aPubKey = UniqueSECKEYPublicKey(pubKeyRaw);
+  pubKeyRaw = nullptr;
+  if (!aPrivKey.get() || !aPubKey.get()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Check that the public key has the correct length
+  if (aPubKey->u.ec.publicValue.len != kPublicKeyLen) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsNSSU2FToken::GetOrCreateWrappingKey(const UniquePK11SlotInfo& aSlot,
+                                      const nsNSSShutDownPreventionLock& locker)
+{
+  MOZ_ASSERT(aSlot);
+  if (!aSlot) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // Search for an existing wrapping key. If we find it,
+  // store it for later and mark ourselves initialized.
+  mWrappingKey = GetSymKeyByNickname(aSlot, mSecretNickname, locker);
+  if (mWrappingKey) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("U2F Soft Token Key found."));
+    mInitialized = true;
+    return NS_OK;
+  }
+
+  MOZ_LOG(gNSSTokenLog, LogLevel::Info,
+          ("No keys found. Generating new U2F Soft Token wrapping key."));
+
+  // We did not find an existing wrapping key, so we generate one in the
+  // persistent database (e.g, Token).
+  mWrappingKey = UniquePK11SymKey(
+    PK11_TokenKeyGenWithFlags(aSlot.get(), CKM_AES_KEY_GEN,
+                              /* default params */ nullptr,
+                              kWrappingKeyByteLen,
+                              /* empty keyid */ nullptr,
+                              /* flags */ CKF_WRAP | CKF_UNWRAP,
+                              /* attributes */ PK11_ATTR_TOKEN |
+                                               PK11_ATTR_PRIVATE,
+                              /* wincx */ nullptr));
+
+  if (!mWrappingKey) {
+      MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+              ("Failed to store wrapping key, NSS error #%d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+
+  SECStatus srv = PK11_SetSymKeyNickname(mWrappingKey.get(),
+                                         mSecretNickname.get());
+  if (srv != SECSuccess) {
+      MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+              ("Failed to set nickname, NSS error #%d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+
+  MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
+          ("Key stored, nickname set to %s.", mSecretNickname.get()));
+
+  Preferences::SetUint(PREF_U2F_NSSTOKEN_COUNTER, 0);
+  return NS_OK;
+}
+
+static nsresult
+GetAttestationCertificate(const UniquePK11SlotInfo& aSlot,
+                  /*out*/ UniqueSECKEYPrivateKey& aAttestPrivKey,
+                  /*out*/ UniqueCERTCertificate& aAttestCert,
+                          const nsNSSShutDownPreventionLock& locker)
+{
+  MOZ_ASSERT(aSlot);
+  if (!aSlot) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  UniqueSECKEYPublicKey pubKey;
+
+  // Construct an ephemeral keypair for this Attestation Certificate
+  nsresult rv = GenEcKeypair(aSlot, aAttestPrivKey, pubKey, locker);
+  if (NS_FAILED(rv) || !aAttestPrivKey || !pubKey) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to gen keypair, NSS error #%d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+
+  // Construct the Attestation Certificate itself
+  UniqueCERTName subjectName(CERT_AsciiToName(kAttestCertSubjectName));
+  if (!subjectName) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to set subject name, NSS error #%d", PORT_GetError()));
+      return NS_ERROR_FAILURE;
+  }
+
+  UniqueCERTSubjectPublicKeyInfo spki(
+    SECKEY_CreateSubjectPublicKeyInfo(pubKey.get()));
+  if (!spki) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to set SPKI, NSS error #%d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+
+  UniqueCERTCertificateRequest certreq(
+    CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr));
+  if (!certreq) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to gen CSR, NSS error #%d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+
+  PRTime now = PR_Now();
+  PRTime notBefore = now - kExpirationSlack;
+  PRTime notAfter = now + kExpirationLife;
+
+  UniqueCERTValidity validity(CERT_CreateValidity(notBefore, notAfter));
+  if (!validity) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to gen validity, NSS error #%d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+
+  unsigned long serial;
+  unsigned char* serialBytes =
+    mozilla::BitwiseCast<unsigned char*, unsigned long*>(&serial);
+  SECStatus srv = PK11_GenerateRandomOnSlot(aSlot.get(), serialBytes,
+                                            sizeof(serial));
+  if (srv != SECSuccess) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to gen serial, NSS error #%d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+  // Ensure that the most significant bit isn't set (which would
+  // indicate a negative number, which isn't valid for serial
+  // numbers).
+  serialBytes[0] &= 0x7f;
+  // Also ensure that the least significant bit on the most
+  // significant byte is set (to prevent a leading zero byte,
+  // which also wouldn't be valid).
+  serialBytes[0] |= 0x01;
+
+  aAttestCert = UniqueCERTCertificate(
+    CERT_CreateCertificate(serial, subjectName.get(), validity.get(),
+                           certreq.get()));
+  if (!aAttestCert) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to gen certificate, NSS error #%d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+
+  PLArenaPool* arena = aAttestCert->arena;
+
+  srv = SECOID_SetAlgorithmID(arena, &aAttestCert->signature,
+                              SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE,
+                              /* wincx */ nullptr);
+  if (srv != SECSuccess) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Set version to X509v3.
+  *(aAttestCert->version.data) = SEC_CERTIFICATE_VERSION_3;
+  aAttestCert->version.len = 1;
+
+  SECItem innerDER = { siBuffer, nullptr, 0 };
+  if (!SEC_ASN1EncodeItem(arena, &innerDER, aAttestCert.get(),
+                          SEC_ASN1_GET(CERT_CertificateTemplate))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  SECItem* signedCert = PORT_ArenaZNew(arena, SECItem);
+  if (!signedCert) {
+    return NS_ERROR_FAILURE;
+  }
+
+  srv = SEC_DerSignData(arena, signedCert, innerDER.data, innerDER.len,
+                        aAttestPrivKey.get(),
+                        SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
+  if (srv != SECSuccess) {
+    return NS_ERROR_FAILURE;
+  }
+  aAttestCert->derCert = *signedCert;
+
+  MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
+          ("U2F Soft Token attestation certificate generated."));
+  return NS_OK;
+}
+
+// Set up the context for the soft U2F Token. This is called by NSS
+// initialization.
+nsresult
+nsNSSU2FToken::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!mInitialized);
+  if (mInitialized) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  UniquePK11SlotInfo slot(PK11_GetInternalKeySlot());
+  MOZ_ASSERT(slot.get());
+
+  // Search for an existing wrapping key, or create one.
+  nsresult rv = GetOrCreateWrappingKey(slot, locker);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mInitialized = true;
+  MOZ_LOG(gNSSTokenLog, LogLevel::Debug, ("U2F Soft Token initialized."));
+  return NS_OK;
+}
+
+// Convert a Private Key object into an opaque key handle, using AES Key Wrap
+// with the long-lived aPersistentKey mixed with aAppParam to convert aPrivKey.
+// The key handle's format is version || saltLen || salt || wrappedPrivateKey
+static UniqueSECItem
+KeyHandleFromPrivateKey(const UniquePK11SlotInfo& aSlot,
+                        const UniquePK11SymKey& aPersistentKey,
+                        uint8_t* aAppParam, uint32_t aAppParamLen,
+                        const UniqueSECKEYPrivateKey& aPrivKey,
+                        const nsNSSShutDownPreventionLock&)
+{
+  MOZ_ASSERT(aSlot);
+  MOZ_ASSERT(aPersistentKey);
+  MOZ_ASSERT(aAppParam);
+  MOZ_ASSERT(aPrivKey);
+  if (!aSlot || !aPersistentKey || !aPrivKey || !aAppParam) {
+    return nullptr;
+  }
+
+  // Generate a random salt
+  uint8_t saltParam[kSaltByteLen];
+  SECStatus srv = PK11_GenerateRandomOnSlot(aSlot.get(), saltParam,
+                                            sizeof(saltParam));
+  if (srv != SECSuccess) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to generate a salt, NSS error #%d", PORT_GetError()));
+    return nullptr;
+  }
+
+  // Prepare the HKDF (https://tools.ietf.org/html/rfc5869)
+  CK_NSS_HKDFParams hkdfParams = { true, saltParam, sizeof(saltParam),
+                                   true, aAppParam, aAppParamLen };
+  SECItem kdfParams = { siBuffer, (unsigned char*)&hkdfParams,
+                        sizeof(hkdfParams) };
+
+  // Derive a wrapping key from aPersistentKey, the salt, and the aAppParam.
+  // CKM_AES_KEY_GEN and CKA_WRAP are key type and usage attributes of the
+  // derived symmetric key and don't matter because we ignore them anyway.
+  UniquePK11SymKey wrapKey(PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256,
+                                      &kdfParams, CKM_AES_KEY_GEN, CKA_WRAP,
+                                      kWrappingKeyByteLen));
+  if (!wrapKey.get()) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to derive a wrapping key, NSS error #%d", PORT_GetError()));
+    return nullptr;
+  }
+
+  UniqueSECItem wrappedKey(SECITEM_AllocItem(/* default arena */ nullptr,
+                                             /* no buffer */ nullptr,
+                                             kWrappedKeyBufLen));
+  if (!wrappedKey) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
+    return nullptr;
+  }
+
+  UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD,
+                                       /* default IV */ nullptr ));
+
+  srv = PK11_WrapPrivKey(aSlot.get(), wrapKey.get(), aPrivKey.get(),
+                         CKM_NSS_AES_KEY_WRAP_PAD, param.get(), wrappedKey.get(),
+                         /* wincx */ nullptr);
+  if (srv != SECSuccess) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to wrap U2F key, NSS error #%d", PORT_GetError()));
+    return nullptr;
+  }
+
+  // Concatenate the salt and the wrapped Private Key together
+  mozilla::dom::CryptoBuffer keyHandleBuf;
+  if (!keyHandleBuf.SetCapacity(wrappedKey.get()->len + sizeof(saltParam) + 2,
+                                 mozilla::fallible)) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
+    return nullptr;
+  }
+
+  // It's OK to ignore the return values here because we're writing into
+  // pre-allocated space
+  keyHandleBuf.AppendElement(SoftTokenHandle::Version1, mozilla::fallible);
+  keyHandleBuf.AppendElement(sizeof(saltParam), mozilla::fallible);
+  keyHandleBuf.AppendElements(saltParam, sizeof(saltParam), mozilla::fallible);
+  keyHandleBuf.AppendSECItem(wrappedKey.get());
+
+  UniqueSECItem keyHandle(SECITEM_AllocItem(nullptr, nullptr, 0));
+  if (!keyHandle) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
+    return nullptr;
+  }
+
+  if (!keyHandleBuf.ToSECItem(/* default arena */ nullptr, keyHandle.get())) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Failed to allocate memory"));
+    return nullptr;
+  }
+  return keyHandle;
+}
+
+// Convert an opaque key handle aKeyHandle back into a Private Key object, using
+// the long-lived aPersistentKey mixed with aAppParam and the AES Key Wrap
+// algorithm.
+static UniqueSECKEYPrivateKey
+PrivateKeyFromKeyHandle(const UniquePK11SlotInfo& aSlot,
+                        const UniquePK11SymKey& aPersistentKey,
+                        uint8_t* aKeyHandle, uint32_t aKeyHandleLen,
+                        uint8_t* aAppParam, uint32_t aAppParamLen,
+                        const nsNSSShutDownPreventionLock&)
+{
+  MOZ_ASSERT(aSlot);
+  MOZ_ASSERT(aPersistentKey);
+  MOZ_ASSERT(aKeyHandle);
+  MOZ_ASSERT(aAppParam);
+  MOZ_ASSERT(aAppParamLen == SHA256_LENGTH);
+  if (!aSlot || !aPersistentKey || !aKeyHandle || !aAppParam ||
+      aAppParamLen != SHA256_LENGTH) {
+    return nullptr;
+  }
+
+  // As we only support one key format ourselves (right now), fail early if
+  // we aren't that length
+  if (aKeyHandleLen != kVersion1KeyHandleLen) {
+    return nullptr;
+  }
+
+  if (aKeyHandle[0] != SoftTokenHandle::Version1) {
+    // Unrecognized version
+    return nullptr;
+  }
+
+  uint8_t saltLen = aKeyHandle[1];
+  uint8_t* saltPtr = aKeyHandle + 2;
+  if (saltLen != kSaltByteLen) {
+    return nullptr;
+  }
+
+  // Prepare the HKDF (https://tools.ietf.org/html/rfc5869)
+  CK_NSS_HKDFParams hkdfParams = { true, saltPtr, saltLen,
+                                   true, aAppParam, aAppParamLen };
+  SECItem kdfParams = { siBuffer, (unsigned char*)&hkdfParams,
+                        sizeof(hkdfParams) };
+
+  // Derive a wrapping key from aPersistentKey, the salt, and the aAppParam.
+  // CKM_AES_KEY_GEN and CKA_WRAP are key type and usage attributes of the
+  // derived symmetric key and don't matter because we ignore them anyway.
+  UniquePK11SymKey wrapKey(PK11_Derive(aPersistentKey.get(), CKM_NSS_HKDF_SHA256,
+                                       &kdfParams, CKM_AES_KEY_GEN, CKA_WRAP,
+                                       kWrappingKeyByteLen));
+  if (!wrapKey.get()) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Failed to derive a wrapping key, NSS error #%d", PORT_GetError()));
+    return nullptr;
+  }
+
+  uint8_t wrappedLen = aKeyHandleLen - saltLen - 2;
+  uint8_t* wrappedPtr = aKeyHandle + saltLen + 2;
+
+  ScopedAutoSECItem wrappedKeyItem(wrappedLen);
+  memcpy(wrappedKeyItem.data, wrappedPtr, wrappedKeyItem.len);
+
+  ScopedAutoSECItem pubKey(kPublicKeyLen);
+
+  UniqueSECItem param(PK11_ParamFromIV(CKM_NSS_AES_KEY_WRAP_PAD,
+                                       /* default IV */ nullptr ));
+
+  CK_ATTRIBUTE_TYPE usages[] = { CKA_SIGN };
+  int usageCount = 1;
+
+  UniqueSECKEYPrivateKey unwrappedKey(
+    PK11_UnwrapPrivKey(aSlot.get(), wrapKey.get(), CKM_NSS_AES_KEY_WRAP_PAD,
+                       param.get(), &wrappedKeyItem,
+                       /* no nickname */ nullptr,
+                       /* discard pubkey */ &pubKey,
+                       /* not permanent */ false,
+                       /* non-exportable */ true,
+                       CKK_EC, usages, usageCount,
+                       /* wincx */ nullptr));
+  if (!unwrappedKey) {
+    // Not our key.
+    MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
+            ("Could not unwrap key handle, NSS Error #%d", PORT_GetError()));
+    return nullptr;
+  }
+
+  return unwrappedKey;
+}
+
+// Return whether the provided version is supported by this token.
+NS_IMETHODIMP
+nsNSSU2FToken::IsCompatibleVersion(const nsAString& aVersion, bool* aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+  MOZ_ASSERT(mInitialized);
+  *aResult = (mVersion == aVersion);
+  return NS_OK;
+}
+
+// IsRegistered determines if the provided key handle is usable by this token.
+NS_IMETHODIMP
+nsNSSU2FToken::IsRegistered(uint8_t* aKeyHandle, uint32_t aKeyHandleLen,
+                            uint8_t* aAppParam, uint32_t aAppParamLen,
+                            bool* aResult)
+{
+  NS_ENSURE_ARG_POINTER(aKeyHandle);
+  NS_ENSURE_ARG_POINTER(aAppParam);
+  NS_ENSURE_ARG_POINTER(aResult);
+
+  if (!NS_IsMainThread()) {
+    NS_ERROR("nsNSSU2FToken::IsRegistered called off the main thread");
+    return NS_ERROR_NOT_SAME_THREAD;
+  }
+
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  MOZ_ASSERT(mInitialized);
+  if (!mInitialized) {
+    return NS_ERROR_FAILURE;
+  }
+
+  UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+  MOZ_ASSERT(slot.get());
+
+  // Decode the key handle
+  UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(slot, mWrappingKey,
+                                                           aKeyHandle,
+                                                           aKeyHandleLen,
+                                                           aAppParam,
+                                                           aAppParamLen,
+                                                           locker);
+  *aResult = (privKey.get() != nullptr);
+  return NS_OK;
+}
+
+// A U2F Register operation causes a new key pair to be generated by the token.
+// The token then returns the public key of the key pair, and a handle to the
+// private key, which is a fancy way of saying "key wrapped private key", as
+// well as the generated attestation certificate and a signature using that
+// certificate's private key.
+//
+// The KeyHandleFromPrivateKey and PrivateKeyFromKeyHandle methods perform
+// the actual key wrap/unwrap operations.
+//
+// The format of the return registration data is as follows:
+//
+// Bytes  Value
+// 1      0x05
+// 65     public key
+// 1      key handle length
+// *      key handle
+// ASN.1  attestation certificate
+// *      attestation signature
+//
+NS_IMETHODIMP
+nsNSSU2FToken::Register(uint8_t* aApplication,
+                        uint32_t aApplicationLen,
+                        uint8_t* aChallenge,
+                        uint32_t aChallengeLen,
+                        uint8_t** aRegistration,
+                        uint32_t* aRegistrationLen)
+{
+  NS_ENSURE_ARG_POINTER(aApplication);
+  NS_ENSURE_ARG_POINTER(aChallenge);
+  NS_ENSURE_ARG_POINTER(aRegistration);
+  NS_ENSURE_ARG_POINTER(aRegistrationLen);
+
+  if (!NS_IsMainThread()) {
+    NS_ERROR("nsNSSU2FToken::Register called off the main thread");
+    return NS_ERROR_NOT_SAME_THREAD;
+  }
+
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  MOZ_ASSERT(mInitialized);
+  if (!mInitialized) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  // We should already have a wrapping key
+  MOZ_ASSERT(mWrappingKey);
+
+  UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+  MOZ_ASSERT(slot.get());
+
+  // Construct a one-time-use Attestation Certificate
+  UniqueSECKEYPrivateKey attestPrivKey;
+  UniqueCERTCertificate attestCert;
+  nsresult rv = GetAttestationCertificate(slot, attestPrivKey, attestCert,
+                                          locker);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
+  MOZ_ASSERT(attestCert);
+  MOZ_ASSERT(attestPrivKey);
+
+  // Generate a new keypair; the private will be wrapped into a Key Handle
+  UniqueSECKEYPrivateKey privKey;
+  UniqueSECKEYPublicKey pubKey;
+  rv = GenEcKeypair(slot, privKey, pubKey, locker);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // The key handle will be the result of keywrap(privKey, key=mWrappingKey)
+  UniqueSECItem keyHandleItem = KeyHandleFromPrivateKey(slot, mWrappingKey,
+                                                        aApplication,
+                                                        aApplicationLen,
+                                                        privKey, locker);
+  if (!keyHandleItem.get()) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Sign the challenge using the Attestation privkey (from attestCert)
+  mozilla::dom::CryptoBuffer signedDataBuf;
+  if (!signedDataBuf.SetCapacity(1 + aApplicationLen + aChallengeLen +
+                                 keyHandleItem->len + kPublicKeyLen,
+                                 mozilla::fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // It's OK to ignore the return values here because we're writing into
+  // pre-allocated space
+  signedDataBuf.AppendElement(0x00, mozilla::fallible);
+  signedDataBuf.AppendElements(aApplication, aApplicationLen, mozilla::fallible);
+  signedDataBuf.AppendElements(aChallenge, aChallengeLen, mozilla::fallible);
+  signedDataBuf.AppendSECItem(keyHandleItem.get());
+  signedDataBuf.AppendSECItem(pubKey->u.ec.publicValue);
+
+  ScopedAutoSECItem signatureItem;
+  SECStatus srv = SEC_SignData(&signatureItem, signedDataBuf.Elements(),
+                               signedDataBuf.Length(), attestPrivKey.get(),
+                               SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
+  if (srv != SECSuccess) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Signature failure: %d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+
+  // Serialize the registration data
+  mozilla::dom::CryptoBuffer registrationBuf;
+  if (!registrationBuf.SetCapacity(1 + kPublicKeyLen + 1 + keyHandleItem->len +
+                                   attestCert.get()->derCert.len +
+                                   signatureItem.len, mozilla::fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  registrationBuf.AppendElement(0x05, mozilla::fallible);
+  registrationBuf.AppendSECItem(pubKey->u.ec.publicValue);
+  registrationBuf.AppendElement(keyHandleItem->len, mozilla::fallible);
+  registrationBuf.AppendSECItem(keyHandleItem.get());
+  registrationBuf.AppendSECItem(attestCert.get()->derCert);
+  registrationBuf.AppendSECItem(signatureItem);
+  if (!registrationBuf.ToNewUnsignedBuffer(aRegistration, aRegistrationLen)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+// A U2F Sign operation creates a signature over the "param" arguments (plus
+// some other stuff) using the private key indicated in the key handle argument.
+//
+// The format of the signed data is as follows:
+//
+//  32    Application parameter
+//  1     User presence (0x01)
+//  4     Counter
+//  32    Challenge parameter
+//
+// The format of the signature data is as follows:
+//
+//  1     User presence
+//  4     Counter
+//  *     Signature
+//
+NS_IMETHODIMP
+nsNSSU2FToken::Sign(uint8_t* aApplication, uint32_t aApplicationLen,
+                    uint8_t* aChallenge, uint32_t aChallengeLen,
+                    uint8_t* aKeyHandle, uint32_t aKeyHandleLen,
+                    uint8_t** aSignature, uint32_t* aSignatureLen)
+{
+  NS_ENSURE_ARG_POINTER(aApplication);
+  NS_ENSURE_ARG_POINTER(aChallenge);
+  NS_ENSURE_ARG_POINTER(aKeyHandle);
+  NS_ENSURE_ARG_POINTER(aKeyHandleLen);
+  NS_ENSURE_ARG_POINTER(aSignature);
+  NS_ENSURE_ARG_POINTER(aSignatureLen);
+
+  if (!NS_IsMainThread()) {
+    NS_ERROR("nsNSSU2FToken::Sign called off the main thread");
+    return NS_ERROR_NOT_SAME_THREAD;
+  }
+
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  MOZ_ASSERT(mInitialized);
+  if (!mInitialized) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  MOZ_ASSERT(mWrappingKey);
+
+  UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+  MOZ_ASSERT(slot.get());
+
+  if ((aChallengeLen != kParamLen) || (aApplicationLen != kParamLen)) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Parameter lengths are wrong! challenge=%d app=%d expected=%d",
+            aChallengeLen, aApplicationLen, kParamLen));
+
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+
+  // Decode the key handle
+  UniqueSECKEYPrivateKey privKey = PrivateKeyFromKeyHandle(slot, mWrappingKey,
+                                                           aKeyHandle,
+                                                           aKeyHandleLen,
+                                                           aApplication,
+                                                           aApplicationLen,
+                                                           locker);
+  if (!privKey.get()) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning, ("Couldn't get the priv key!"));
+    return NS_ERROR_FAILURE;
+  }
+
+  // Increment the counter and turn it into a SECItem
+  uint32_t counter = Preferences::GetUint(PREF_U2F_NSSTOKEN_COUNTER) + 1;
+  Preferences::SetUint(PREF_U2F_NSSTOKEN_COUNTER, counter);
+  ScopedAutoSECItem counterItem(4);
+  counterItem.data[0] = (counter >> 24) & 0xFF;
+  counterItem.data[1] = (counter >> 16) & 0xFF;
+  counterItem.data[2] = (counter >>  8) & 0xFF;
+  counterItem.data[3] = (counter >>  0) & 0xFF;
+
+  // Compute the signature
+  mozilla::dom::CryptoBuffer signedDataBuf;
+  if (!signedDataBuf.SetCapacity(1 + 4 + (2 * kParamLen), mozilla::fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // It's OK to ignore the return values here because we're writing into
+  // pre-allocated space
+  signedDataBuf.AppendElements(aApplication, aApplicationLen, mozilla::fallible);
+  signedDataBuf.AppendElement(0x01, mozilla::fallible);
+  signedDataBuf.AppendSECItem(counterItem);
+  signedDataBuf.AppendElements(aChallenge, aChallengeLen, mozilla::fallible);
+
+  if (MOZ_LOG_TEST(gNSSTokenLog, LogLevel::Debug)) {
+    nsAutoCString base64;
+    nsresult rv = Base64URLEncode(signedDataBuf.Length(), signedDataBuf.Elements(),
+                                  Base64URLEncodePaddingPolicy::Omit, base64);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return NS_ERROR_FAILURE;
+    }
+
+    MOZ_LOG(gNSSTokenLog, LogLevel::Debug,
+            ("U2F Token signing bytes (base64): %s", base64.get()));
+  }
+
+  ScopedAutoSECItem signatureItem;
+  SECStatus srv = SEC_SignData(&signatureItem, signedDataBuf.Elements(),
+                               signedDataBuf.Length(), privKey.get(),
+                               SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
+  if (srv != SECSuccess) {
+    MOZ_LOG(gNSSTokenLog, LogLevel::Warning,
+            ("Signature failure: %d", PORT_GetError()));
+    return NS_ERROR_FAILURE;
+  }
+
+  // Assemble the signature data into a buffer for return
+  mozilla::dom::CryptoBuffer signatureBuf;
+  if (!signatureBuf.SetCapacity(1 + counterItem.len + signatureItem.len,
+                                mozilla::fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  // It's OK to ignore the return values here because we're writing into
+  // pre-allocated space
+  signatureBuf.AppendElement(0x01, mozilla::fallible);
+  signatureBuf.AppendSECItem(counterItem);
+  signatureBuf.AppendSECItem(signatureItem);
+
+  if (!signatureBuf.ToNewUnsignedBuffer(aSignature, aSignatureLen)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/nsNSSU2FToken.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsNSSU2FToken_h
+#define nsNSSU2FToken_h
+
+#include "nsINSSU2FToken.h"
+
+#include "nsNSSShutDown.h"
+#include "ScopedNSSTypes.h"
+
+#define NS_NSSU2FTOKEN_CID \
+  {0x79f95a6c, 0xd0f7, 0x4d7d, {0xae, 0xaa, 0xcd, 0x0a, 0x04, 0xb6, 0x50, 0x89}}
+
+class nsNSSU2FToken : public nsINSSU2FToken,
+                      public nsNSSShutDownObject
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIU2FTOKEN
+  NS_DECL_NSINSSU2FTOKEN
+
+  nsNSSU2FToken();
+
+  // For nsNSSShutDownObject
+  virtual void virtualDestroyNSSReference() override;
+  void destructorSafeDestroyNSSReference();
+
+  // Initializes the token and constructs and persists keys, if needed. Asserts
+  // that it is only called by the main thread.
+  nsresult Init();
+
+private:
+  bool mInitialized;
+  mozilla::UniquePK11SymKey mWrappingKey;
+
+  static const nsCString mSecretNickname;
+  static const nsString mVersion;
+
+  ~nsNSSU2FToken();
+  nsresult GetOrCreateWrappingKey(const mozilla::UniquePK11SlotInfo& aSlot,
+                                  const nsNSSShutDownPreventionLock&);
+};
+
+#endif // nsNSSU2FToken_h
--- a/security/manager/ssl/security-prefs.js
+++ b/security/manager/ssl/security-prefs.js
@@ -101,16 +101,19 @@ 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);