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