Bug 1264472 - Use nsRunnables in FIDO U2F r?keeler draft
authorJ.C. Jones <jjones@mozilla.com>
Mon, 18 Apr 2016 14:49:07 -0700
changeset 354454 2f7f2f966f313e5e1720a2d0aa10218819d9b3c9
parent 352736 d53b301a14e1dfa05324072a7159796a4f5e24c7
child 519010 e5cd5a8f8236bf68ee3bc99e92448bd881714818
push id16085
push userjjones@mozilla.com
push dateWed, 20 Apr 2016 23:36:31 +0000
reviewerskeeler
bugs1264472, 1244959
milestone48.0a1
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);