Bug 1264472 - Use nsRunnables in FIDO U2F. r=keeler
authorJ.C. Jones <jjones@mozilla.com>
Mon, 18 Apr 2016 14:49:07 -0700
changeset 294717 3aa298c8b91daa97b490f5faf14658be18c88f18
parent 294716 fbad795be936e54fdc6f5135ccab4f7bd238b798
child 294718 801cac365dd931fd04d02fcae3dc5bc0c966f323
push id30208
push usercbook@mozilla.com
push dateMon, 25 Apr 2016 09:55:37 +0000
treeherdermozilla-central@1c6385ae1fe7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler
bugs1264472, 1244959
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1264472 - Use nsRunnables in FIDO U2F. r=keeler - Move the AppID/FacetID algorithm into its own (potentially reentrant) method to facilitate Bug 1244959 - Change the Register and Sign operations to be Runnables so that in the future they can be executed after (future) remote fetches - Clean up error handling - Remove unnecessary remote-load Facet test files; we'll re-add some form of them when the remote load algorithm is completed MozReview-Commit-ID: 4K1q6ovzhgf
dom/u2f/U2F.cpp
dom/u2f/U2F.h
dom/u2f/tests/facet/facetList-good
dom/u2f/tests/facet/facetList-good^headers^
dom/u2f/tests/facet/facetList-invalid_format
dom/u2f/tests/facet/facetList-invalid_format^headers^
dom/u2f/tests/facet/facetList-no_overlap
dom/u2f/tests/facet/facetList-no_overlap^headers^
dom/u2f/tests/facet/facetList.txt
dom/u2f/tests/mochitest.ini
dom/u2f/tests/test_frame.html
dom/u2f/tests/test_frame_appid_facet_remoteload.html
dom/u2f/tests/test_frame_register_sign.html
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -6,59 +6,639 @@
 
 #include "hasht.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/CryptoBuffer.h"
 #include "mozilla/dom/U2F.h"
 #include "mozilla/dom/U2FBinding.h"
 #include "mozilla/Preferences.h"
 #include "nsContentUtils.h"
-#include "nsIEffectiveTLDService.h"
 #include "nsNetCID.h"
 #include "nsNSSComponent.h"
 #include "nsURLParsers.h"
 #include "pk11pub.h"
 
 using mozilla::dom::ContentChild;
 
 namespace mozilla {
 namespace dom {
 
-// 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
-};
-
 #define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f_enable_softtoken"
 #define PREF_U2F_USBTOKEN_ENABLED  "security.webauth.u2f_enable_usbtoken"
 
-const nsString U2F::FinishEnrollment =
-  NS_LITERAL_STRING("navigator.id.finishEnrollment");
-
-const nsString U2F::GetAssertion =
-  NS_LITERAL_STRING("navigator.id.getAssertion");
+NS_NAMED_LITERAL_STRING(kFinishEnrollment, "navigator.id.finishEnrollment");
+NS_NAMED_LITERAL_STRING(kGetAssertion, "navigator.id.getAssertion");
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(U2F)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(U2F)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(U2F)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(U2F, mParent)
 
-static mozilla::LazyLogModule gU2FLog("fido_u2f");
+static mozilla::LazyLogModule gU2FLog("webauth_u2f");
+
+template <class CB, class Rsp>
+void
+SendError(CB* aCallback, ErrorCode aErrorCode)
+{
+  Rsp response;
+  response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
+
+  ErrorResult rv;
+  aCallback->Call(response, rv);
+  NS_WARN_IF(rv.Failed());
+  // Useful exceptions already got reported.
+  rv.SuppressException();
+}
+
+static nsresult
+AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
+                   const nsAString& aChallenge, CryptoBuffer& aClientData)
+{
+  ClientData clientDataObject;
+  clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
+  clientDataObject.mChallenge.Construct(aChallenge);
+  clientDataObject.mOrigin.Construct(aOrigin);
+
+  nsAutoString json;
+  if (NS_WARN_IF(!clientDataObject.ToJSON(json))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (NS_WARN_IF(!aClientData.Assign(NS_ConvertUTF16toUTF8(json)))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+static nsresult
+NSSTokenIsCompatible(nsINSSU2FToken* aNSSToken, const nsString& aVersionString,
+                     bool* aIsCompatible)
+{
+  MOZ_ASSERT(aIsCompatible);
+
+  if (XRE_IsParentProcess()) {
+    MOZ_ASSERT(aNSSToken);
+    return aNSSToken->IsCompatibleVersion(aVersionString, aIsCompatible);
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenIsCompatibleVersion(aVersionString, aIsCompatible)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+static nsresult
+NSSTokenIsRegistered(nsINSSU2FToken* aNSSToken, CryptoBuffer& aKeyHandle,
+                     bool* aIsRegistered)
+{
+  MOZ_ASSERT(aIsRegistered);
+
+  if (XRE_IsParentProcess()) {
+    MOZ_ASSERT(aNSSToken);
+    return aNSSToken->IsRegistered(aKeyHandle.Elements(), aKeyHandle.Length(),
+                                   aIsRegistered);
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenIsRegistered(aKeyHandle, aIsRegistered)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+static nsresult
+NSSTokenSign(nsINSSU2FToken* aNSSToken, CryptoBuffer& aKeyHandle,
+             CryptoBuffer& aApplication, CryptoBuffer& aChallenge,
+             CryptoBuffer& aSignatureData)
+{
+  if (XRE_IsParentProcess()) {
+    MOZ_ASSERT(aNSSToken);
+    uint8_t* buffer;
+    uint32_t bufferlen;
+    nsresult rv = aNSSToken->Sign(aApplication.Elements(), aApplication.Length(),
+                                  aChallenge.Elements(), aChallenge.Length(),
+                                  aKeyHandle.Elements(), aKeyHandle.Length(),
+                                  &buffer, &bufferlen);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    MOZ_ASSERT(buffer);
+    aSignatureData.Assign(buffer, bufferlen);
+    free(buffer);
+    return NS_OK;
+  }
+
+  nsTArray<uint8_t> signatureBuffer;
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenSign(aApplication, aChallenge, aKeyHandle,
+                               &signatureBuffer)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aSignatureData.Assign(signatureBuffer);
+  return NS_OK;
+}
+
+static nsresult
+NSSTokenRegister(nsINSSU2FToken* aNSSToken, CryptoBuffer& aApplication,
+                 CryptoBuffer& aChallenge, CryptoBuffer& aRegistrationData)
+{
+  if (XRE_IsParentProcess()) {
+    MOZ_ASSERT(aNSSToken);
+    uint8_t* buffer;
+    uint32_t bufferlen;
+    nsresult rv;
+    rv = aNSSToken->Register(aApplication.Elements(), aApplication.Length(),
+                             aChallenge.Elements(), aChallenge.Length(),
+                             &buffer, &bufferlen);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    MOZ_ASSERT(buffer);
+    aRegistrationData.Assign(buffer, bufferlen);
+    free(buffer);
+    return NS_OK;
+  }
+
+  nsTArray<uint8_t> registrationBuffer;
+  ContentChild* cc = ContentChild::GetSingleton();
+  MOZ_ASSERT(cc);
+  if (!cc->SendNSSU2FTokenRegister(aApplication, aChallenge,
+                                   &registrationBuffer)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aRegistrationData.Assign(registrationBuffer);
+  return NS_OK;
+}
+
+U2FTask::U2FTask(const nsAString& aOrigin, const nsAString& aAppId)
+  : mOrigin(aOrigin)
+  , mAppId(aAppId)
+{}
+
+U2FTask::~U2FTask()
+{}
+
+U2FRegisterTask::U2FRegisterTask(const nsAString& aOrigin,
+                                 const nsAString& aAppId,
+                                 const Sequence<RegisterRequest>& aRegisterRequests,
+                                 const Sequence<RegisteredKey>& aRegisteredKeys,
+                                 U2FRegisterCallback* aCallback,
+                                 const nsCOMPtr<nsINSSU2FToken>& aNSSToken)
+  : U2FTask(aOrigin, aAppId)
+  , mRegisterRequests(aRegisterRequests)
+  , mRegisteredKeys(aRegisteredKeys)
+  , mCallback(aCallback)
+  , mNSSToken(aNSSToken)
+{}
+
+U2FRegisterTask::~U2FRegisterTask()
+{
+  nsNSSShutDownPreventionLock locker;
+
+  if (isAlreadyShutDown()) {
+    return;
+  }
+  shutdown(calledFromObject);
+}
+
+void
+U2FRegisterTask::ReturnError(ErrorCode aCode)
+{
+  SendError<U2FRegisterCallback, RegisterResponse>(mCallback.get(), aCode);
+}
+
+NS_IMETHODIMP
+U2FRegisterTask::Run()
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    ReturnError(ErrorCode::OTHER_ERROR);
+    return NS_ERROR_FAILURE;
+  }
+
+  // TODO: Implement USB Tokens in Bug 1245527
+  const bool softTokenEnabled =
+    Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
+
+  for (size_t i = 0; i < mRegisteredKeys.Length(); ++i) {
+    RegisteredKey request(mRegisteredKeys[i]);
+
+    // Check for required attributes
+    if (!(request.mKeyHandle.WasPassed() &&
+          request.mVersion.WasPassed())) {
+      continue;
+    }
+
+    // Do not permit an individual RegisteredKey to assert a different AppID
+    if (request.mAppId.WasPassed() && mAppId != request.mAppId.Value()) {
+      continue;
+    }
+
+    // Decode the key handle
+    CryptoBuffer keyHandle;
+    nsresult rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReturnError(ErrorCode::BAD_REQUEST);
+      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 isCompatible = false;
+    bool isRegistered = false;
+
+    // Determine if the provided keyHandle is registered at any device. If so,
+    // then we'll return DEVICE_INELIGIBLE to signify we're already registered.
+    if (softTokenEnabled) {
+      rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(),
+                                &isCompatible);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      rv = NSSTokenIsRegistered(mNSSToken, keyHandle, &isRegistered);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      if (isCompatible && isRegistered) {
+        ReturnError(ErrorCode::DEVICE_INELIGIBLE);
+        return NS_OK;
+      }
+    }
+  }
+
+  // Search the requests in order for the first some token can fulfill
+  for (size_t i = 0; i < mRegisterRequests.Length(); ++i) {
+    RegisterRequest request(mRegisterRequests[i]);
+
+    // Check for equired attributes
+    if (!(request.mVersion.WasPassed() &&
+        request.mChallenge.WasPassed())) {
+      continue;
+    }
+
+    CryptoBuffer clientData;
+    nsresult rv = AssembleClientData(mOrigin, kFinishEnrollment,
+                                     request.mChallenge.Value(),
+                                     clientData);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Hash the AppID and the ClientData into the AppParam and ChallengeParam
+    SECStatus srv;
+    nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
+    CryptoBuffer appParam;
+    CryptoBuffer challengeParam;
+    if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
+        !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
+                       reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
+                       cAppId.Length());
+    if (srv != SECSuccess) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
+                       clientData.Elements(), clientData.Length());
+    if (srv != SECSuccess) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Get the registration data from the token
+    CryptoBuffer regData;
+    bool registerSuccess = false;
+    bool isCompatible = false;
+
+    if (!registerSuccess && softTokenEnabled) {
+      rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(),
+                                &isCompatible);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      if (isCompatible) {
+        rv = NSSTokenRegister(mNSSToken, appParam, challengeParam, regData);
+        if (NS_FAILED(rv)) {
+          ReturnError(ErrorCode::OTHER_ERROR);
+          return NS_ERROR_FAILURE;
+        }
+        registerSuccess = true;
+      }
+    }
+
+    if (!registerSuccess) {
+      // Try another request
+      continue;
+    }
+
+    // Assemble a response object to return
+    nsString clientDataBase64, registrationDataBase64;
+    nsresult rvClientData =
+      clientData.ToJwkBase64(clientDataBase64);
+    nsresult rvRegistrationData =
+      regData.ToJwkBase64(registrationDataBase64);
+    if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+        NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    RegisterResponse response;
+    response.mClientData.Construct(clientDataBase64);
+    response.mRegistrationData.Construct(registrationDataBase64);
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+    ErrorResult result;
+    mCallback->Call(response, result);
+    NS_WARN_IF(result.Failed());
+    // Useful exceptions already got reported.
+    result.SuppressException();
+    return NS_OK;
+  }
+
+  // Nothing could satisfy
+  ReturnError(ErrorCode::BAD_REQUEST);
+  return NS_ERROR_FAILURE;
+}
+
+U2FSignTask::U2FSignTask(const nsAString& aOrigin,
+                         const nsAString& aAppId,
+                         const nsAString& aChallenge,
+                         const Sequence<RegisteredKey>& aRegisteredKeys,
+                         U2FSignCallback* aCallback,
+                         const nsCOMPtr<nsINSSU2FToken>& aNSSToken)
+  : U2FTask(aOrigin, aAppId)
+  , mChallenge(aChallenge)
+  , mRegisteredKeys(aRegisteredKeys)
+  , mCallback(aCallback)
+  , mNSSToken(aNSSToken)
+{}
+
+U2FSignTask::~U2FSignTask()
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    return;
+  }
+  shutdown(calledFromObject);
+}
+
+void
+U2FSignTask::ReturnError(ErrorCode aCode)
+{
+  SendError<U2FSignCallback, SignResponse>(mCallback.get(), aCode);
+}
+
+NS_IMETHODIMP
+U2FSignTask::Run()
+{
+  nsNSSShutDownPreventionLock locker;
+  if (isAlreadyShutDown()) {
+    ReturnError(ErrorCode::OTHER_ERROR);
+    return NS_ERROR_FAILURE;
+  }
+
+  // TODO: Implement USB Tokens in Bug 1245527
+  const bool softTokenEnabled =
+    Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
+
+  // Search the requests for one a token can fulfill
+  for (size_t i = 0; i < mRegisteredKeys.Length(); i += 1) {
+    RegisteredKey request(mRegisteredKeys[i]);
+
+    // Check for required attributes
+    if (!(request.mVersion.WasPassed() &&
+          request.mKeyHandle.WasPassed())) {
+      continue;
+    }
+
+    // Do not permit an individual RegisteredKey to assert a different AppID
+    if (request.mAppId.WasPassed() && mAppId != request.mAppId.Value()) {
+      continue;
+    }
+
+    // Assemble a clientData object
+    CryptoBuffer clientData;
+    nsresult rv = AssembleClientData(mOrigin, kGetAssertion, mChallenge,
+                                     clientData);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Hash the AppID and the ClientData into the AppParam and ChallengeParam
+    SECStatus srv;
+    nsCString cAppId = NS_ConvertUTF16toUTF8(mAppId);
+    CryptoBuffer appParam;
+    CryptoBuffer challengeParam;
+    if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
+        !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
+                       reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
+                       cAppId.Length());
+    if (srv != SECSuccess) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
+                       clientData.Elements(), clientData.Length());
+    if (srv != SECSuccess) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Decode the key handle
+    CryptoBuffer keyHandle;
+    rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+
+    // Get the signature from the token
+    CryptoBuffer signatureData;
+    bool signSuccess = false;
+
+    // We ignore mTransports, as it is intended to be used for sorting the
+    // available devices by preference, but is not an exclusion factor.
+
+    if (!signSuccess && softTokenEnabled) {
+      bool isCompatible = false;
+      bool isRegistered = false;
+
+      rv = NSSTokenIsCompatible(mNSSToken, request.mVersion.Value(),
+                                &isCompatible);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      rv = NSSTokenIsRegistered(mNSSToken, keyHandle, &isRegistered);
+      if (NS_FAILED(rv)) {
+        ReturnError(ErrorCode::OTHER_ERROR);
+        return NS_ERROR_FAILURE;
+      }
+
+      if (isCompatible && isRegistered) {
+        rv = NSSTokenSign(mNSSToken, keyHandle, appParam, challengeParam,
+                          signatureData);
+        if (NS_FAILED(rv)) {
+          ReturnError(ErrorCode::OTHER_ERROR);
+          return NS_ERROR_FAILURE;
+        }
+        signSuccess = true;
+      }
+    }
+
+    if (!signSuccess) {
+      // Try another request
+      continue;
+    }
+
+    // Assemble a response object to return
+    nsString clientDataBase64, signatureDataBase64;
+    nsresult rvClientData =
+      clientData.ToJwkBase64(clientDataBase64);
+    nsresult rvSignatureData =
+      signatureData.ToJwkBase64(signatureDataBase64);
+    if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
+        NS_WARN_IF(NS_FAILED(rvSignatureData))) {
+      ReturnError(ErrorCode::OTHER_ERROR);
+      return NS_ERROR_FAILURE;
+    }
+    SignResponse response;
+    response.mKeyHandle.Construct(request.mKeyHandle.Value());
+    response.mClientData.Construct(clientDataBase64);
+    response.mSignatureData.Construct(signatureDataBase64);
+    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
+
+    ErrorResult result;
+    mCallback->Call(response, result);
+    NS_WARN_IF(result.Failed());
+    // Useful exceptions already got reported.
+    result.SuppressException();
+    return NS_OK;
+  }
+
+  // Nothing could satisfy
+  ReturnError(ErrorCode::DEVICE_INELIGIBLE);
+  return NS_ERROR_FAILURE;
+}
+
+// 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.
+static void
+EvaluateAppIDAndRunTask(U2FTask* aTask)
+{
+  MOZ_ASSERT(aTask);
+
+  nsCOMPtr<nsIURLParser> urlParser =
+      do_GetService(NS_STDURLPARSER_CONTRACTID);
+
+  MOZ_ASSERT(urlParser);
+
+  uint32_t facetSchemePos;
+  int32_t facetSchemeLen;
+  uint32_t facetAuthPos;
+  int32_t facetAuthLen;
+  // Facet is the specification's way of referring to the web origin.
+  nsAutoCString facetUrl = NS_ConvertUTF16toUTF8(aTask->mOrigin);
+  nsresult rv = urlParser->ParseURL(facetUrl.get(), aTask->mOrigin.Length(),
+                                    &facetSchemePos, &facetSchemeLen,
+                                    &facetAuthPos, &facetAuthLen,
+                                    nullptr, nullptr);      // ignore path
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aTask->ReturnError(ErrorCode::BAD_REQUEST);
+    return;
+  }
+
+  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(aTask->mAppId);
+  rv = urlParser->ParseURL(appIdUrl.get(), aTask->mAppId.Length(),
+                           &appIdSchemePos, &appIdSchemeLen,
+                           &appIdAuthPos, &appIdAuthLen,
+                           nullptr, nullptr);      // ignore path
+  if (NS_FAILED(rv)) {
+    aTask->ReturnError(ErrorCode::BAD_REQUEST);
+    return;
+  }
+
+  nsAutoCString appIdScheme(Substring(appIdUrl, appIdSchemePos, appIdSchemeLen));
+  nsAutoCString appIdAuth(Substring(appIdUrl, appIdAuthPos, appIdAuthLen));
+
+  // If the facetId (origin) is not HTTPS, reject
+  if (!facetScheme.LowerCaseEqualsLiteral("https")) {
+    aTask->ReturnError(ErrorCode::BAD_REQUEST);
+    return;
+  }
+
+  // If the appId is empty or null, overwrite it with the facetId and accept
+  if (aTask->mAppId.IsEmpty() || aTask->mAppId.EqualsLiteral("null")) {
+    aTask->mAppId.Assign(aTask->mOrigin);
+    aTask->Run();
+    return;
+  }
+
+  // if the appId URL is not HTTPS, reject.
+  if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
+    aTask->ReturnError(ErrorCode::BAD_REQUEST);
+    return;
+  }
+
+  // If the facetId and the appId auths match, accept
+  if (facetAuth == appIdAuth) {
+    aTask->Run();
+    return;
+  }
+
+  // TODO(Bug 1244959) Implement the remaining algorithm.
+  aTask->ReturnError(ErrorCode::BAD_REQUEST);
+  return;
+}
 
 U2F::U2F()
 {}
 
 U2F::~U2F()
 {
   nsNSSShutDownPreventionLock locker;
 
@@ -86,16 +666,17 @@ U2F::Init(nsPIDOMWindowInner* aParent, E
 
   nsIPrincipal* principal = doc->NodePrincipal();
   aRv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 
   if (NS_WARN_IF(mOrigin.IsEmpty())) {
+    aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   if (!EnsureNSSInitializedChromeOrContent()) {
     MOZ_LOG(gU2FLog, LogLevel::Debug, ("Failed to get NSS context for U2F"));
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
@@ -109,607 +690,42 @@ U2F::Init(nsPIDOMWindowInner* aParent, E
   }
 
   aRv = mUSBToken.Init();
   if (NS_WARN_IF(aRv.Failed())) {
     return;
   }
 }
 
-nsresult
-U2F::NSSTokenIsCompatible(const nsString& aVersionString, bool* aIsCompatible)
-{
-  MOZ_ASSERT(aIsCompatible);
-
-  if (XRE_IsParentProcess()) {
-    MOZ_ASSERT(mNSSToken);
-    return mNSSToken->IsCompatibleVersion(aVersionString, aIsCompatible);
-  }
-
-  ContentChild* cc = ContentChild::GetSingleton();
-  MOZ_ASSERT(cc);
-  if (!cc->SendNSSU2FTokenIsCompatibleVersion(aVersionString, aIsCompatible)) {
-    return NS_ERROR_FAILURE;
-  }
-  return NS_OK;
-}
-
-nsresult
-U2F::NSSTokenIsRegistered(CryptoBuffer& aKeyHandle, bool* aIsRegistered)
-{
-  MOZ_ASSERT(aIsRegistered);
-
-  if (XRE_IsParentProcess()) {
-    MOZ_ASSERT(mNSSToken);
-    return mNSSToken->IsRegistered(aKeyHandle.Elements(), aKeyHandle.Length(),
-                                   aIsRegistered);
-  }
-
-  ContentChild* cc = ContentChild::GetSingleton();
-  MOZ_ASSERT(cc);
-  if (!cc->SendNSSU2FTokenIsRegistered(aKeyHandle, aIsRegistered)) {
-    return NS_ERROR_FAILURE;
-  }
-  return NS_OK;
-}
-
-nsresult
-U2F::NSSTokenRegister(CryptoBuffer& aApplication, CryptoBuffer& aChallenge,
-                      CryptoBuffer& aRegistrationData)
-{
-  if (XRE_IsParentProcess()) {
-    MOZ_ASSERT(mNSSToken);
-    uint8_t* buffer;
-    uint32_t bufferlen;
-    nsresult rv;
-    rv = mNSSToken->Register(aApplication.Elements(), aApplication.Length(),
-                             aChallenge.Elements(), aChallenge.Length(),
-                             &buffer, &bufferlen);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    MOZ_ASSERT(buffer);
-    aRegistrationData.Assign(buffer, bufferlen);
-    free(buffer);
-    return NS_OK;
-  }
-
-  nsTArray<uint8_t> registrationBuffer;
-  ContentChild* cc = ContentChild::GetSingleton();
-  MOZ_ASSERT(cc);
-  if (!cc->SendNSSU2FTokenRegister(aApplication, aChallenge,
-                                   &registrationBuffer)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  aRegistrationData.Assign(registrationBuffer);
-  return NS_OK;
-}
-
-nsresult
-U2F::NSSTokenSign(CryptoBuffer& aKeyHandle, CryptoBuffer& aApplication,
-                  CryptoBuffer& aChallenge, CryptoBuffer& aSignatureData)
-{
-  if (XRE_IsParentProcess()) {
-    MOZ_ASSERT(mNSSToken);
-    uint8_t* buffer;
-    uint32_t bufferlen;
-    nsresult rv = mNSSToken->Sign(aApplication.Elements(), aApplication.Length(),
-                                  aChallenge.Elements(), aChallenge.Length(),
-                                  aKeyHandle.Elements(), aKeyHandle.Length(),
-                                  &buffer, &bufferlen);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    MOZ_ASSERT(buffer);
-    aSignatureData.Assign(buffer, bufferlen);
-    free(buffer);
-    return NS_OK;
-  }
-
-  nsTArray<uint8_t> signatureBuffer;
-  ContentChild* cc = ContentChild::GetSingleton();
-  MOZ_ASSERT(cc);
-  if (!cc->SendNSSU2FTokenSign(aApplication, aChallenge, aKeyHandle,
-                               &signatureBuffer)) {
-    return NS_ERROR_FAILURE;
-  }
-
-  aSignatureData.Assign(signatureBuffer);
-  return NS_OK;
-}
-
-nsresult
-U2F::AssembleClientData(const nsAString& aTyp,
-                        const nsAString& aChallenge,
-                        CryptoBuffer& aClientData) const
-{
-  ClientData clientDataObject;
-  clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
-  clientDataObject.mChallenge.Construct(aChallenge);
-  clientDataObject.mOrigin.Construct(mOrigin);
-
-  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_FAILURE;
-  }
-
-  return NS_OK;
-}
-
-bool
-U2F::ValidAppID(/* in/out */ nsString& aAppId) const
-{
-  nsCOMPtr<nsIURLParser> urlParser =
-      do_GetService(NS_STDURLPARSER_CONTRACTID);
-  nsCOMPtr<nsIEffectiveTLDService> tldService =
-      do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
-
-  MOZ_ASSERT(urlParser);
-  MOZ_ASSERT(tldService);
-
-  uint32_t facetSchemePos;
-  int32_t facetSchemeLen;
-  uint32_t facetAuthPos;
-  int32_t facetAuthLen;
-  // Facet is the specification's way of referring to the web origin.
-  nsAutoCString facetUrl = NS_ConvertUTF16toUTF8(mOrigin);
-  nsresult rv = urlParser->ParseURL(facetUrl.get(), mOrigin.Length(),
-                                    &facetSchemePos, &facetSchemeLen,
-                                    &facetAuthPos, &facetAuthLen,
-                                    nullptr, nullptr);      // ignore path
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return false;
-  }
-
-  nsAutoCString facetScheme(Substring(facetUrl, facetSchemePos, facetSchemeLen));
-  nsAutoCString facetAuth(Substring(facetUrl, facetAuthPos, facetAuthLen));
-
-  uint32_t appIdSchemePos;
-  int32_t appIdSchemeLen;
-  uint32_t appIdAuthPos;
-  int32_t appIdAuthLen;
-  nsAutoCString appIdUrl = NS_ConvertUTF16toUTF8(aAppId);
-  rv = urlParser->ParseURL(appIdUrl.get(), aAppId.Length(),
-                           &appIdSchemePos, &appIdSchemeLen,
-                           &appIdAuthPos, &appIdAuthLen,
-                           nullptr, nullptr);      // ignore path
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return false;
-  }
-
-  nsAutoCString appIdScheme(Substring(appIdUrl, appIdSchemePos, appIdSchemeLen));
-  nsAutoCString appIdAuth(Substring(appIdUrl, appIdAuthPos, appIdAuthLen));
-
-  // If the facetId (origin) is not HTTPS, reject
-  if (!facetScheme.LowerCaseEqualsLiteral("https")) {
-    return false;
-  }
-
-  // If the appId is empty or null, overwrite it with the facetId and accept
-  if (aAppId.IsEmpty() || aAppId.EqualsLiteral("null")) {
-    aAppId.Assign(mOrigin);
-    return true;
-  }
-
-  // if the appId URL is not HTTPS, reject.
-  if (!appIdScheme.LowerCaseEqualsLiteral("https")) {
-    return false;
-  }
-
-  // If the facetId and the appId auths match, accept
-  if (facetAuth == appIdAuth) {
-    return true;
-  }
-
-  // TODO(Bug 1244959) Implement the remaining algorithm.
-  return false;
-}
-
-template <class CB, class Rsp>
-void
-SendError(CB& aCallback, ErrorCode aErrorCode)
-{
-  Rsp response;
-  response.mErrorCode.Construct(static_cast<uint32_t>(aErrorCode));
-
-  ErrorResult rv;
-  aCallback.Call(response, rv);
-  NS_WARN_IF(rv.Failed());
-  // Useful exceptions already got reported.
-  rv.SuppressException();
-}
-
 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)
 {
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                     ErrorCode::OTHER_ERROR);
-    return;
-  }
-
-  const bool softTokenEnabled =
-    Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
-
-  const bool usbTokenEnabled =
-    Preferences::GetBool(PREF_U2F_USBTOKEN_ENABLED);
-
-  nsAutoString appId(aAppId);
-
-  // Verify the global appId first.
-  if (!ValidAppID(appId)) {
-    SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                     ErrorCode::BAD_REQUEST);
-    return;
-  }
-
-  for (size_t i = 0; i < aRegisteredKeys.Length(); ++i) {
-    RegisteredKey request(aRegisteredKeys[i]);
-
-    // Check for required attributes
-    if (!(request.mKeyHandle.WasPassed() &&
-          request.mVersion.WasPassed())) {
-      continue;
-    }
-
-    // Verify the appId for this Registered Key, if set
-    if (request.mAppId.WasPassed() &&
-        !ValidAppID(request.mAppId.Value())) {
-      continue;
-    }
-
-    // Decode the key handle
-    CryptoBuffer keyHandle;
-    nsresult rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::BAD_REQUEST);
-      return;
-    }
-
-    // We ignore mTransports, as it is intended to be used for sorting the
-    // available devices by preference, but is not an exclusion factor.
-
-    bool isCompatible = false;
-    bool isRegistered = false;
-
-    // Determine if the provided keyHandle is registered at any device. If so,
-    // then we'll return DEVICE_INELIGIBLE to signify we're already registered.
-    if (usbTokenEnabled &&
-        mUSBToken.IsCompatibleVersion(request.mVersion.Value()) &&
-        mUSBToken.IsRegistered(keyHandle)) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                  ErrorCode::DEVICE_INELIGIBLE);
-      return;
-    }
-    if (softTokenEnabled) {
-      rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
-      if (NS_FAILED(rv)) {
-        SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                         ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      rv = NSSTokenIsRegistered(keyHandle, &isRegistered);
-      if (NS_FAILED(rv)) {
-        SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                         ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      if (isCompatible && isRegistered) {
-        SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                         ErrorCode::DEVICE_INELIGIBLE);
-        return;
-      }
-    }
-  }
-
-  // Search the requests in order for the first some token can fulfill
-  for (size_t i = 0; i < aRegisterRequests.Length(); ++i) {
-    RegisterRequest request(aRegisterRequests[i]);
-
-    // Check for equired attributes
-    if (!(request.mVersion.WasPassed() &&
-        request.mChallenge.WasPassed())) {
-      continue;
-    }
+  RefPtr<U2FRegisterTask> registerTask = new U2FRegisterTask(mOrigin, aAppId,
+                                                             aRegisterRequests,
+                                                             aRegisteredKeys,
+                                                             &aCallback,
+                                                             mNSSToken);
 
-    CryptoBuffer clientData;
-    nsresult rv = AssembleClientData(FinishEnrollment,
-                                     request.mChallenge.Value(),
-                                     clientData);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    // Hash the AppID and the ClientData into the AppParam and ChallengeParam
-    SECStatus srv;
-    nsCString cAppId = NS_ConvertUTF16toUTF8(appId);
-    CryptoBuffer appParam;
-    CryptoBuffer challengeParam;
-    if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
-        !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
-                       reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
-                       cAppId.Length());
-    if (srv != SECSuccess) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
-                       clientData.Elements(), clientData.Length());
-    if (srv != SECSuccess) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    // Get the registration data from the token
-    CryptoBuffer registrationData;
-    bool registerSuccess = false;
-    bool isCompatible = false;
-    if (usbTokenEnabled) {
-      // TODO: Implement in Bug 1245527
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    if (!registerSuccess && softTokenEnabled) {
-      rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
-      if (NS_FAILED(rv)) {
-        SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                        ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      if (isCompatible) {
-        rv = NSSTokenRegister(appParam, challengeParam, registrationData);
-        if (NS_FAILED(rv)) {
-          SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                        ErrorCode::OTHER_ERROR);
-          return;
-        }
-        registerSuccess = true;
-      }
-    }
-
-    if (!registerSuccess) {
-      // Try another request
-      continue;
-    }
-
-    // Assemble a response object to return
-    nsString clientDataBase64, registrationDataBase64;
-    nsresult rvClientData =
-      clientData.ToJwkBase64(clientDataBase64);
-    nsresult rvRegistrationData =
-      registrationData.ToJwkBase64(registrationDataBase64);
-    if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
-        NS_WARN_IF(NS_FAILED(rvRegistrationData))) {
-      SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                       ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    RegisterResponse response;
-    response.mClientData.Construct(clientDataBase64);
-    response.mRegistrationData.Construct(registrationDataBase64);
-    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
-    ErrorResult result;
-    aCallback.Call(response, result);
-    NS_WARN_IF(result.Failed());
-    // Useful exceptions already got reported.
-    result.SuppressException();
-    return;
-  }
-
-  // Nothing could satisfy
-  SendError<U2FRegisterCallback, RegisterResponse>(aCallback,
-                                                   ErrorCode::BAD_REQUEST);
-  return;
+  EvaluateAppIDAndRunTask(registerTask);
 }
 
 void
 U2F::Sign(const nsAString& aAppId,
           const nsAString& aChallenge,
           const Sequence<RegisteredKey>& aRegisteredKeys,
           U2FSignCallback& aCallback,
           const Optional<Nullable<int32_t>>& opt_aTimeoutSeconds,
           ErrorResult& aRv)
 {
-  nsNSSShutDownPreventionLock locker;
-  if (isAlreadyShutDown()) {
-    SendError<U2FSignCallback, SignResponse>(aCallback,
-                                             ErrorCode::OTHER_ERROR);
-    return;
-  }
-
-  const bool softTokenEnabled =
-    Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED);
-
-  const bool usbTokenEnabled =
-    Preferences::GetBool(PREF_U2F_USBTOKEN_ENABLED);
-
-  nsAutoString appId(aAppId);
-
-  // Verify the global appId first.
-  if (!ValidAppID(appId)) {
-    SendError<U2FSignCallback, SignResponse>(aCallback,
-                                             ErrorCode::BAD_REQUEST);
-    return;
-  }
-
-  // Search the requests for one a token can fulfill
-  for (size_t i = 0; i < aRegisteredKeys.Length(); i += 1) {
-    RegisteredKey request(aRegisteredKeys[i]);
-
-    // Check for required attributes
-    if (!(request.mVersion.WasPassed() &&
-          request.mKeyHandle.WasPassed())) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      continue;
-    }
-
-    // Allow an individual RegisteredKey to assert a different AppID
-    nsAutoString regKeyAppId(appId);
-    if (request.mAppId.WasPassed()) {
-      regKeyAppId.Assign(request.mAppId.Value());
-      if (!ValidAppID(regKeyAppId)) {
-        continue;
-      }
-    }
-
-    // Assemble a clientData object
-    CryptoBuffer clientData;
-    nsresult rv = AssembleClientData(GetAssertion, aChallenge, clientData);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    // Hash the AppID and the ClientData into the AppParam and ChallengeParam
-    SECStatus srv;
-    nsCString cAppId = NS_ConvertUTF16toUTF8(regKeyAppId);
-    CryptoBuffer appParam;
-    CryptoBuffer challengeParam;
-    if (!appParam.SetLength(SHA256_LENGTH, fallible) ||
-        !challengeParam.SetLength(SHA256_LENGTH, fallible)) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    srv = PK11_HashBuf(SEC_OID_SHA256, appParam.Elements(),
-                       reinterpret_cast<const uint8_t*>(cAppId.BeginReading()),
-                       cAppId.Length());
-    if (srv != SECSuccess) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    srv = PK11_HashBuf(SEC_OID_SHA256, challengeParam.Elements(),
-                       clientData.Elements(), clientData.Length());
-    if (srv != SECSuccess) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
+  RefPtr<U2FSignTask> signTask = new U2FSignTask(mOrigin, aAppId, aChallenge,
+                                                 aRegisteredKeys, &aCallback,
+                                                 mNSSToken);
 
-    // Decode the key handle
-    CryptoBuffer keyHandle;
-    rv = keyHandle.FromJwkBase64(request.mKeyHandle.Value());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    // Get the signature from the token
-    CryptoBuffer signatureData;
-    bool signSuccess = false;
-
-    // We ignore mTransports, as it is intended to be used for sorting the
-    // available devices by preference, but is not an exclusion factor.
-
-    if (usbTokenEnabled &&
-        mUSBToken.IsCompatibleVersion(request.mVersion.Value())) {
-      // TODO: Implement in Bug 1245527
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-
-    if (!signSuccess && softTokenEnabled) {
-      bool isCompatible = false;
-      bool isRegistered = false;
-
-      rv = NSSTokenIsCompatible(request.mVersion.Value(), &isCompatible);
-      if (NS_FAILED(rv)) {
-        SendError<U2FSignCallback, SignResponse>(aCallback,
-                                                 ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      rv = NSSTokenIsRegistered(keyHandle, &isRegistered);
-      if (NS_FAILED(rv)) {
-        SendError<U2FSignCallback, SignResponse>(aCallback,
-                                                 ErrorCode::OTHER_ERROR);
-        return;
-      }
-
-      if (isCompatible && isRegistered) {
-        rv = NSSTokenSign(keyHandle, appParam, challengeParam, signatureData);
-        if (NS_FAILED(rv)) {
-          SendError<U2FSignCallback, SignResponse>(aCallback,
-                                                   ErrorCode::OTHER_ERROR);
-          return;
-        }
-        signSuccess = true;
-      }
-    }
-
-    if (!signSuccess) {
-      // Try another request
-      continue;
-    }
-
-    // Assemble a response object to return
-    nsString clientDataBase64, signatureDataBase64;
-    nsresult rvClientData =
-      clientData.ToJwkBase64(clientDataBase64);
-    nsresult rvSignatureData =
-      signatureData.ToJwkBase64(signatureDataBase64);
-    if (NS_WARN_IF(NS_FAILED(rvClientData)) ||
-        NS_WARN_IF(NS_FAILED(rvSignatureData))) {
-      SendError<U2FSignCallback, SignResponse>(aCallback,
-                                               ErrorCode::OTHER_ERROR);
-      return;
-    }
-    SignResponse response;
-    response.mKeyHandle.Construct(request.mKeyHandle.Value());
-    response.mClientData.Construct(clientDataBase64);
-    response.mSignatureData.Construct(signatureDataBase64);
-    response.mErrorCode.Construct(static_cast<uint32_t>(ErrorCode::OK));
-
-    ErrorResult result;
-    aCallback.Call(response, result);
-    NS_WARN_IF(result.Failed());
-    // Useful exceptions already got reported.
-    result.SuppressException();
-    return;
-  }
-
-  // Nothing could satisfy
-  SendError<U2FSignCallback, SignResponse>(aCallback,
-                                           ErrorCode::DEVICE_INELIGIBLE);
-  return;
+  EvaluateAppIDAndRunTask(signTask);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/u2f/U2F.h
+++ b/dom/u2f/U2F.h
@@ -29,16 +29,98 @@ class U2FRegisterCallback;
 class U2FSignCallback;
 
 } // namespace dom
 } // namespace mozilla
 
 namespace mozilla {
 namespace dom {
 
+// 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
+};
+
+class U2FTask : public nsRunnable
+{
+public:
+  U2FTask(const nsAString& aOrigin,
+          const nsAString& aAppId);
+
+  nsString mOrigin;
+  nsString mAppId;
+
+  virtual
+  void ReturnError(ErrorCode code) = 0;
+
+protected:
+  virtual ~U2FTask();
+};
+
+class U2FRegisterTask final : public nsNSSShutDownObject,
+                              public U2FTask
+{
+public:
+  U2FRegisterTask(const nsAString& aOrigin,
+                  const nsAString& aAppId,
+                  const Sequence<RegisterRequest>& aRegisterRequests,
+                  const Sequence<RegisteredKey>& aRegisteredKeys,
+                  U2FRegisterCallback* aCallback,
+                  const nsCOMPtr<nsINSSU2FToken>& aNSSToken);
+
+  // No NSS resources to release.
+  virtual
+  void virtualDestroyNSSReference() override {};
+
+  void ReturnError(ErrorCode code) override;
+
+  NS_DECL_NSIRUNNABLE
+private:
+  ~U2FRegisterTask();
+
+  Sequence<RegisterRequest> mRegisterRequests;
+  Sequence<RegisteredKey> mRegisteredKeys;
+  RefPtr<U2FRegisterCallback> mCallback;
+  nsCOMPtr<nsINSSU2FToken> mNSSToken;
+};
+
+class U2FSignTask final : public nsNSSShutDownObject,
+                          public U2FTask
+{
+public:
+  U2FSignTask(const nsAString& aOrigin,
+              const nsAString& aAppId,
+              const nsAString& aChallenge,
+              const Sequence<RegisteredKey>& aRegisteredKeys,
+              U2FSignCallback* aCallback,
+              const nsCOMPtr<nsINSSU2FToken>& aNSSToken);
+
+  // No NSS resources to release.
+  virtual
+  void virtualDestroyNSSReference() override {};
+
+  void ReturnError(ErrorCode code) override;
+
+  NS_DECL_NSIRUNNABLE
+private:
+  ~U2FSignTask();
+
+  nsString mChallenge;
+  Sequence<RegisteredKey> mRegisteredKeys;
+  RefPtr<U2FSignCallback> mCallback;
+  nsCOMPtr<nsINSSU2FToken> mNSSToken;
+};
+
 class U2F final : public nsISupports,
                   public nsWrapperCache,
                   public nsNSSShutDownObject
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(U2F)
 
@@ -77,45 +159,15 @@ public:
   void virtualDestroyNSSReference() override {};
 
 private:
   nsCOMPtr<nsPIDOMWindowInner> mParent;
   nsString mOrigin;
   USBToken mUSBToken;
   nsCOMPtr<nsINSSU2FToken> mNSSToken;
 
-  static const nsString FinishEnrollment;
-  static const nsString GetAssertion;
-
   ~U2F();
-
-  nsresult
-  AssembleClientData(const nsAString& aTyp,
-                     const nsAString& aChallenge,
-                     CryptoBuffer& aClientData) const;
-
-  // ValidAppID determines whether the supplied FIDO AppID is valid for
-  // the current FacetID, e.g., the current origin. If the supplied
-  // aAppId param is null or empty, it will be filled in per the algorithm.
-  // See https://fidoalliance.org/specs/fido-u2f-v1.0-nfc-bt-amendment-20150514/fido-appid-and-facets.html
-  // for a description of the algorithm.
-  bool
-  ValidAppID(/* in/out */ nsString& aAppId) const;
-
-  nsresult
-  NSSTokenIsCompatible(const nsString& versionString, bool* isCompatible);
-
-  nsresult
-  NSSTokenIsRegistered(CryptoBuffer& keyHandle, bool* isRegistered);
-
-  nsresult
-  NSSTokenRegister(CryptoBuffer& application, CryptoBuffer& challenge,
-                   CryptoBuffer& registrationData);
-
-  nsresult
-  NSSTokenSign(CryptoBuffer& keyHandle, CryptoBuffer& application,
-               CryptoBuffer& challenge, CryptoBuffer& signatureData);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_U2F_h
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-good
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "trustedFacets" : [{
-    "version": { "major": 1, "minor" : 0 },
-    "ids": [
-     "https://fido.example.com"
-    ]
-  }]
-}
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-good^headers^
+++ /dev/null
@@ -1,1 +0,0 @@
-Content-Type: application/fido.trusted-apps+json
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-invalid_format
+++ /dev/null
@@ -1,6 +0,0 @@
-# This file isn't actually JSON, so it shouldn't successfully parse.
-{
-  "trustedFacets" : [{
-    "version": { "major": 1, "minor" : 0 },
-  },{}]
-}
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-invalid_format^headers^
+++ /dev/null
@@ -1,1 +0,0 @@
-Content-Type: application/fido.trusted-apps+json
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-no_overlap
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "trustedFacets" : [{
-    "version": { "major": 1, "minor" : 0 },
-    "ids": [
-     "https://example.net",
-     "http://www.example.com"
-    ]
-  }]
-}
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList-no_overlap^headers^
+++ /dev/null
@@ -1,1 +0,0 @@
-Content-Type: application/fido.trusted-apps+json
\ No newline at end of file
deleted file mode 100644
--- a/dom/u2f/tests/facet/facetList.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "trustedFacets" : [{
-    "version": { "major": 1, "minor" : 0 },
-    "ids": [
-     "https://fido.example.com"
-    ]
-  }]
-}
\ No newline at end of file
--- a/dom/u2f/tests/mochitest.ini
+++ b/dom/u2f/tests/mochitest.ini
@@ -1,25 +1,17 @@
 [DEFAULT]
 support-files =
   frame_no_token.html
   u2futil.js
   test_frame_appid_facet.html
   test_frame_register.html
   test_frame_register_sign.html
-  test_frame_appid_facet_remoteload.html
   test_frame_appid_facet_insecure.html
   test_frame_appid_facet_subdomain.html
-  facet/facetList.txt
-  facet/facetList-good
-  facet/facetList-good^headers^
-  facet/facetList-no_overlap
-  facet/facetList-no_overlap^headers^
-  facet/facetList-invalid_format
-  facet/facetList-invalid_format^headers^
   pkijs/common.js
   pkijs/asn1.js
   pkijs/x509_schema.js
   pkijs/x509_simpl.js
 
 [test_util_methods.html]
 [test_no_token.html]
 [test_frame.html]
--- a/dom/u2f/tests/test_frame.html
+++ b/dom/u2f/tests/test_frame.html
@@ -21,17 +21,16 @@
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.u2f", true],
                                    ["security.webauth.u2f_enable_softtoken", true]]},
 function() {
   var testList = [
     "https://example.com/tests/dom/u2f/tests/test_frame_register.html",
     "https://example.com/tests/dom/u2f/tests/test_frame_register_sign.html",
     "http://mochi.test:8888/tests/dom/u2f/tests/test_frame_appid_facet_insecure.html",
     "https://example.com/tests/dom/u2f/tests/test_frame_appid_facet.html",
-    "https://example.com/tests/dom/u2f/tests/test_frame_appid_facet_remoteload.html",
     "https://test1.example.com/tests/dom/u2f/tests/test_frame_appid_facet_subdomain.html"
   ];
 
   function log(msg) {
     document.getElementById("log").textContent += "\n" + msg;
   }
 
   function nextTest() {
deleted file mode 100644
--- a/dom/u2f/tests/test_frame_appid_facet_remoteload.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<head>
-  <script src="u2futil.js"></script>
-</head>
-<body>
-<p>Test for Remote AppId Load 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");
-
-// TODO: Must support remote loads of AppID manifests first.
-//
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList.txt", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 2, "Should not permit this AppId contentType");
-// });
-
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetListMissing", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 2, "Should not permit with a missing AppID list");
-// });
-
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList-good", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 0, "The AppId should permit example.com");
-// });
-
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList-no_overlap", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 2, "Should not permit with a missing AppID list");
-// });
-
-// u2f.register("https://test1.example.com/dom/u2f/tests/facet/facetList-invalid_format", [{
-//   version: version,
-//   challenge: bytesToBase64UrlSafe(challenge),
-// }], [], function(res){
-//   local_is(res.errorCode, 2, "Should not fail gracefully on invalid formatted facet lists");
-// });
-
-local_finished();
-
-</script>
-</body>
-</html>
--- a/dom/u2f/tests/test_frame_register_sign.html
+++ b/dom/u2f/tests/test_frame_register_sign.html
@@ -77,19 +77,19 @@ function() {
       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", "Data type matches");
-      local_is(clientData.challenge, state.regRequest.challenge, "Register challenge matches");
-      local_is(clientData.origin, window.location.origin, "Origins are the same");
+      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) {
@@ -156,19 +156,19 @@ function() {
         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", "Data type matches");
-      local_is(clientData.challenge, state.signChallenge, "Sign challenge matches");
-      local_is(clientData.origin, window.location.origin, "Origins are the same");
+      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);