--- a/dom/moz.build
+++ b/dom/moz.build
@@ -81,16 +81,17 @@ DIRS += [
'ipc',
'workers',
'audiochannel',
'broadcastchannel',
'messagechannel',
'promise',
'smil',
'url',
+ 'webauthn',
'webidl',
'xbl',
'xml',
'xslt',
'xul',
'manifest',
'vr',
'u2f',
--- a/dom/u2f/U2F.cpp
+++ b/dom/u2f/U2F.cpp
@@ -37,17 +37,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
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 gWebauthLog("webauth_u2f");
+static mozilla::LazyLogModule gU2FLog("u2f");
static nsresult
AssembleClientData(const nsAString& aOrigin, const nsAString& aTyp,
const nsAString& aChallenge, CryptoBuffer& aClientData)
{
MOZ_ASSERT(NS_IsMainThread());
U2FClientData clientDataObject;
clientDataObject.mTyp.Construct(aTyp); // "Typ" from the U2F specification
@@ -76,47 +76,47 @@ U2FStatus::~U2FStatus()
{}
void
U2FStatus::WaitGroupAdd()
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mCount += 1;
- MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ MOZ_LOG(gU2FLog, LogLevel::Debug,
("U2FStatus::WaitGroupAdd, now %d", mCount));
}
void
U2FStatus::WaitGroupDone()
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
MOZ_ASSERT(mCount > 0);
mCount -= 1;
- MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ MOZ_LOG(gU2FLog, LogLevel::Debug,
("U2FStatus::WaitGroupDone, now %d", mCount));
if (mCount == 0) {
mReentrantMonitor.NotifyAll();
}
}
void
U2FStatus::WaitGroupWait()
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
- MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ MOZ_LOG(gU2FLog, LogLevel::Debug,
("U2FStatus::WaitGroupWait, now %d", mCount));
while (mCount > 0) {
mReentrantMonitor.Wait();
}
MOZ_ASSERT(mCount == 0);
- MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ MOZ_LOG(gU2FLog, LogLevel::Debug,
("U2FStatus::Wait completed, now count=%d stopped=%d", mCount,
mIsStopped));
}
void
U2FStatus::Stop(const ErrorCode aErrorCode)
{
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
@@ -622,17 +622,17 @@ U2FRegisterRunnable::Run()
}
// Treat each call to Promise::All as a work unit, as it completes together
status->WaitGroupAdd();
U2FPrepPromise::All(AbstractThread::MainThread(), prepPromiseList)
->Then(AbstractThread::MainThread(), __func__,
[status] (const nsTArray<Authenticator>& aTokens) {
- MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ MOZ_LOG(gU2FLog, LogLevel::Debug,
("ALL: None of the RegisteredKeys were recognized. n=%d",
aTokens.Length()));
status->WaitGroupDone();
},
[status] (ErrorCode aErrorCode) {
status->Stop(aErrorCode);
status->WaitGroupDone();
@@ -779,17 +779,17 @@ U2FSignRunnable::U2FSignRunnable(const n
mRegisteredKeys.AppendElement(localKey);
}
// Assemble a clientData object
nsresult rv = AssembleClientData(aOrigin, kGetAssertion, aChallenge,
mClientData);
if (NS_WARN_IF(NS_FAILED(rv))) {
- MOZ_LOG(gWebauthLog, LogLevel::Warning,
+ MOZ_LOG(gU2FLog, LogLevel::Warning,
("Failed to AssembleClientData for the U2FSignRunnable."));
return;
}
}
U2FSignRunnable::~U2FSignRunnable()
{
nsNSSShutDownPreventionLock locker;
@@ -971,25 +971,25 @@ U2F::Init(nsPIDOMWindowInner* aParent, E
}
if (NS_WARN_IF(mOrigin.IsEmpty())) {
aRv.Throw(NS_ERROR_FAILURE);
return;
}
if (!EnsureNSSInitializedChromeOrContent()) {
- MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ 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(gWebauthLog, LogLevel::Debug,
+ MOZ_LOG(gU2FLog, LogLevel::Debug,
("Is non-e10s Process, U2F not available"));
aRv.Throw(NS_ERROR_FAILURE);
return;
}
// Monolithically insert compatible nsIU2FToken objects into mAuthenticators.
// In future functionality expansions, this is where we could add a dynamic
// add/remove interface.
deleted file mode 100644
--- a/dom/u2f/WebAuthentication.cpp
+++ /dev/null
@@ -1,1054 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "mozilla/dom/WebAuthentication.h"
-#include "mozilla/dom/WebAuthnAssertion.h"
-#include "mozilla/dom/WebAuthnAttestation.h"
-
-#include "mozilla/dom/Promise.h"
-#include "nsICryptoHash.h"
-#include "pkix/Input.h"
-#include "pkixutil.h"
-
-namespace mozilla {
-namespace dom {
-
-extern mozilla::LazyLogModule gWebauthLog; // defined in U2F.cpp
-
-// Only needed for refcounted objects.
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthentication, mParent)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthentication)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthentication)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthentication)
- NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
- NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-template<class OOS>
-static nsresult
-GetAlgorithmName(JSContext* aCx, const OOS& aAlgorithm,
- /* out */ nsString& aName)
-{
- MOZ_ASSERT(aAlgorithm.IsString()); // TODO: remove assertion when we coerce.
-
- if (aAlgorithm.IsString()) {
- // If string, then treat as algorithm name
- aName.Assign(aAlgorithm.GetAsString());
- } else {
- // TODO: Coerce to string and extract name. See WebCryptoTask.cpp
- }
-
- if (!NormalizeToken(aName, aName)) {
- 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))) {
- return NS_ERROR_FAILURE;
- }
-
- WebAuthnClientData clientDataObject;
- clientDataObject.mOrigin.Assign(aOrigin);
- clientDataObject.mHashAlg.SetAsString().Assign(NS_LITERAL_STRING("S256"));
- clientDataObject.mChallenge.Assign(challengeBase64);
-
- nsAutoString temp;
- if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
- return NS_ERROR_FAILURE;
- }
-
- aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
- return NS_OK;
-}
-
-static nsresult
-ScopedCredentialGetData(const ScopedCredentialDescriptor& aSCD,
- /* out */ uint8_t** aBuf, /* out */ uint32_t* aBufLen)
-{
- MOZ_ASSERT(aBuf);
- MOZ_ASSERT(aBufLen);
-
- if (aSCD.mId.IsArrayBufferView()) {
- const ArrayBufferView& view = aSCD.mId.GetAsArrayBufferView();
- view.ComputeLengthAndData();
- *aBuf = view.Data();
- *aBufLen = view.Length();
- } else if (aSCD.mId.IsArrayBuffer()) {
- const ArrayBuffer& buffer = aSCD.mId.GetAsArrayBuffer();
- buffer.ComputeLengthAndData();
- *aBuf = buffer.Data();
- *aBufLen = buffer.Length();
- } else {
- MOZ_ASSERT(false);
- return NS_ERROR_FAILURE;
- }
-
- return NS_OK;
-}
-
-static nsresult
-ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
- uint32_t aLen)
-{
- if (aSrc.EnsureLength(aLen) != pkix::Success) {
- return NS_ERROR_DOM_UNKNOWN_ERR;
- }
-
- aDest.ClearAndRetainStorage();
-
- for (uint32_t offset = 0; offset < aLen; ++offset) {
- uint8_t b;
- if (aSrc.Read(b) != pkix::Success) {
- return NS_ERROR_DOM_UNKNOWN_ERR;
- }
- if (!aDest.AppendElement(b, mozilla::fallible)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- }
-
- return NS_OK;
-}
-
-static nsresult
-U2FAssembleAuthenticatorData(/* out */ CryptoBuffer& aAuthenticatorData,
- const CryptoBuffer& aRpIdHash,
- const CryptoBuffer& aSignatureData)
-{
- // The AuthenticatorData for U2F devices is the concatenation of the
- // RP ID with the output of the U2F Sign operation.
- if (aRpIdHash.Length() != 32) {
- return NS_ERROR_INVALID_ARG;
- }
-
- if (!aAuthenticatorData.AppendElements(aRpIdHash, mozilla::fallible)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
-
- if (!aAuthenticatorData.AppendElements(aSignatureData, mozilla::fallible)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
-
- return NS_OK;
-}
-
-static nsresult
-U2FDecomposeRegistrationResponse(const CryptoBuffer& aResponse,
- /* out */ CryptoBuffer& aPubKeyBuf,
- /* out */ CryptoBuffer& aKeyHandleBuf,
- /* out */ CryptoBuffer& aAttestationCertBuf,
- /* out */ CryptoBuffer& aSignatureBuf)
-{
- // U2F v1.1 Format via
- // http://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html
- //
- // Bytes Value
- // 1 0x05
- // 65 public key
- // 1 key handle length
- // * key handle
- // ASN.1 attestation certificate
- // * attestation signature
-
- pkix::Input u2fResponse;
- u2fResponse.Init(aResponse.Elements(), aResponse.Length());
-
- pkix::Reader input(u2fResponse);
-
- uint8_t b;
- if (input.Read(b) != pkix::Success) {
- return NS_ERROR_DOM_UNKNOWN_ERR;
- }
- if (b != 0x05) {
- return NS_ERROR_DOM_UNKNOWN_ERR;
- }
-
- nsresult rv = ReadToCryptoBuffer(input, aPubKeyBuf, 65);
- if (NS_FAILED(rv)) {
- return rv;
- }
-
- uint8_t handleLen;
- if (input.Read(handleLen) != pkix::Success) {
- return NS_ERROR_DOM_UNKNOWN_ERR;
- }
-
- rv = ReadToCryptoBuffer(input, aKeyHandleBuf, handleLen);
- if (NS_FAILED(rv)) {
- return rv;
- }
-
- // We have to parse the ASN.1 SEQUENCE on the outside to determine the cert's
- // length.
- pkix::Input cert;
- if (pkix::der::ExpectTagAndGetValue(input, pkix::der::SEQUENCE, cert)
- != pkix::Success) {
- return NS_ERROR_DOM_UNKNOWN_ERR;
- }
-
- pkix::Reader certInput(cert);
- rv = ReadToCryptoBuffer(certInput, aAttestationCertBuf, cert.GetLength());
- if (NS_FAILED(rv)) {
- return rv;
- }
-
- // The remainder of u2fResponse is the signature
- pkix::Input u2fSig;
- input.SkipToEnd(u2fSig);
- pkix::Reader sigInput(u2fSig);
- rv = ReadToCryptoBuffer(sigInput, aSignatureBuf, u2fSig.GetLength());
- if (NS_FAILED(rv)) {
- return rv;
- }
-
- return NS_OK;
-}
-
-WebAuthentication::WebAuthentication(nsPIDOMWindowInner* aParent)
- : mInitialized(false)
-{
- mParent = do_QueryInterface(aParent);
- MOZ_ASSERT(mParent);
-}
-
-WebAuthentication::~WebAuthentication()
-{}
-
-nsresult
-WebAuthentication::InitLazily()
-{
- if (mInitialized) {
- return NS_OK;
- }
-
- MOZ_ASSERT(mParent);
- if (!mParent) {
- return NS_ERROR_FAILURE;
- }
-
- nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
- MOZ_ASSERT(doc);
-
- nsIPrincipal* principal = doc->NodePrincipal();
- nsresult rv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return NS_ERROR_FAILURE;
- }
-
- if (NS_WARN_IF(mOrigin.IsEmpty())) {
- return NS_ERROR_FAILURE;
- }
-
- // This only functions in e10s mode
- // TODO: Remove in Bug 1323339
- if (XRE_IsParentProcess()) {
- MOZ_LOG(gWebauthLog, LogLevel::Debug,
- ("Is non-e10s Process, WebAuthn not available"));
- return NS_ERROR_FAILURE;
- }
-
- if (Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED)) {
- if (!mAuthenticators.AppendElement(new NSSU2FTokenRemote(),
- mozilla::fallible)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
- }
-
- mInitialized = true;
- return NS_OK;
-}
-
-JSObject*
-WebAuthentication::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
- return WebAuthenticationBinding::Wrap(aCx, this, aGivenProto);
-}
-
-// NOTE: This method represents a theoretical way to use a U2F-compliant token
-// to produce the result of the WebAuthn MakeCredential method. The exact
-// mapping of U2F data fields to WebAuthn data fields is still a matter of
-// ongoing discussion, and this should not be taken as anything but a point-in-
-// time possibility.
-void
-WebAuthentication::U2FAuthMakeCredential(
- const RefPtr<CredentialRequest>& aRequest,
- const Authenticator& aToken, CryptoBuffer& aRpIdHash,
- const nsACString& aClientData, CryptoBuffer& aClientDataHash,
- const Account& aAccount,
- const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
- const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
- const WebAuthnExtensions& aExtensions)
-{
- MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthMakeCredential"));
- aRequest->AddActiveToken(__func__);
-
- // 5.1.1 When this operation is invoked, the authenticator must perform the
- // following procedure:
-
- // 5.1.1.a Check if all the supplied parameters are syntactically well-
- // formed and of the correct length. If not, return an error code equivalent
- // to UnknownError and terminate the operation.
-
- if ((aRpIdHash.Length() != SHA256_LENGTH) ||
- (aClientDataHash.Length() != SHA256_LENGTH)) {
- aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
- return;
- }
-
- // 5.1.1.b Check if at least one of the specified combinations of
- // ScopedCredentialType and cryptographic parameters is supported. If not,
- // return an error code equivalent to NotSupportedError and terminate the
- // operation.
-
- bool isValidCombination = false;
-
- for (size_t a = 0; a < aNormalizedParams.Length(); ++a) {
- if (aNormalizedParams[a].mType == ScopedCredentialType::ScopedCred &&
- aNormalizedParams[a].mAlgorithm.IsString() &&
- aNormalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
- WEBCRYPTO_NAMED_CURVE_P256)) {
- isValidCombination = true;
- break;
- }
- }
- if (!isValidCombination) {
- aRequest->SetFailure(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
- return;
- }
-
- // 5.1.1.c Check if a credential matching any of the supplied
- // ScopedCredential identifiers is present on this authenticator. If so,
- // return an error code equivalent to NotAllowedError and terminate the
- // operation.
-
- if (aExcludeList.WasPassed()) {
- const Sequence<ScopedCredentialDescriptor>& list = aExcludeList.Value();
-
- for (const ScopedCredentialDescriptor& scd : list) {
- bool isRegistered = false;
-
- uint8_t *data;
- uint32_t len;
-
- // data is owned by the Descriptor, do don't free it here.
- if (NS_FAILED(ScopedCredentialGetData(scd, &data, &len))) {
- aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
- return;
- }
-
- nsresult rv = aToken->IsRegistered(data, len, &isRegistered);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aRequest->SetFailure(rv);
- return;
- }
-
- if (isRegistered) {
- aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
- return;
- }
- }
- }
-
- // 5.1.1.d Prompt the user for consent to create a new credential. The
- // prompt for obtaining this consent is shown by the authenticator if it has
- // its own output capability, or by the user agent otherwise. If the user
- // denies consent, return an error code equivalent to NotAllowedError and
- // terminate the operation.
-
- // 5.1.1.d Once user consent has been obtained, generate a new credential
- // object
-
- // 5.1.1.e If any error occurred while creating the new credential object,
- // return an error code equivalent to UnknownError and terminate the
- // operation.
-
- // 5.1.1.f Process all the supported extensions requested by the client, and
- // generate an attestation statement. If no authority key is available to
- // sign such an attestation statement, then the authenticator performs self
- // attestation of the credential with its own private key. For more details
- // on attestation, see §5.3 Credential Attestation Statements.
-
- // No extensions are supported
-
- // 4.1.1.11 While issuedRequests is not empty, perform the following actions
- // depending upon the adjustedTimeout timer and responses from the
- // authenticators:
-
- // 4.1.1.11.a If the adjustedTimeout timer expires, then for each entry in
- // issuedRequests invoke the authenticatorCancel operation on that
- // authenticator and remove its entry from the list.
-
- uint8_t* buffer;
- uint32_t bufferlen;
-
- nsresult rv = aToken->Register(aRpIdHash.Elements(), aRpIdHash.Length(),
- aClientDataHash.Elements(),
- aClientDataHash.Length(), &buffer, &bufferlen);
-
- // 4.1.1.11.b If any authenticator returns a status indicating that the user
- // cancelled the operation, delete that authenticator’s entry from
- // issuedRequests. For each remaining entry in issuedRequests invoke the
- // authenticatorCancel operation on that authenticator and remove its entry
- // from the list.
-
- // 4.1.1.11.c If any authenticator returns an error status, delete the
- // corresponding entry from issuedRequests.
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
- return;
- }
-
- MOZ_ASSERT(buffer);
- CryptoBuffer regData;
- if (NS_WARN_IF(!regData.Assign(buffer, bufferlen))) {
- free(buffer);
- aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
- free(buffer);
-
- // Decompose the U2F registration packet
- CryptoBuffer pubKeyBuf;
- CryptoBuffer keyHandleBuf;
- CryptoBuffer attestationCertBuf;
- CryptoBuffer signatureBuf;
-
- rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
- attestationCertBuf, signatureBuf);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aRequest->SetFailure(rv);
- return;
- }
-
- // Sign the aClientDataHash explicitly to get the format needed for
- // the AuthenticatorData parameter of WebAuthnAttestation. This might
- // be temporary while the spec settles down how to incorporate U2F.
- rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
- aClientDataHash.Elements(), aClientDataHash.Length(),
- keyHandleBuf.Elements(), keyHandleBuf.Length(), &buffer,
- &bufferlen);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aRequest->SetFailure(rv);
- return;
- }
-
- MOZ_ASSERT(buffer);
- CryptoBuffer signatureData;
- if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
- free(buffer);
- aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
- free(buffer);
-
- CryptoBuffer clientDataBuf;
- if (!clientDataBuf.Assign(aClientData)) {
- aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- CryptoBuffer authenticatorDataBuf;
- rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
- signatureData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aRequest->SetFailure(rv);
- return;
- }
-
- // 4.1.1.11.d If any authenticator indicates success:
-
- // 4.1.1.11.d.1 Remove this authenticator’s entry from issuedRequests.
-
- // 4.1.1.11.d.2 Create a new ScopedCredentialInfo object named value and
- // populate its fields with the values returned from the authenticator as well
- // as the clientDataJSON computed earlier.
-
- RefPtr<ScopedCredential> credential = new ScopedCredential(this);
- credential->SetType(ScopedCredentialType::ScopedCred);
- credential->SetId(keyHandleBuf);
-
- RefPtr<WebAuthnAttestation> attestation = new WebAuthnAttestation(this);
- attestation->SetFormat(NS_LITERAL_STRING("u2f"));
- attestation->SetClientData(clientDataBuf);
- attestation->SetAuthenticatorData(authenticatorDataBuf);
- attestation->SetAttestation(regData);
-
- CredentialPtr info = new ScopedCredentialInfo(this);
- info->SetCredential(credential);
- info->SetAttestation(attestation);
-
- // 4.1.1.11.d.3 For each remaining entry in issuedRequests invoke the
- // authenticatorCancel operation on that authenticator and remove its entry
- // from the list.
-
- // 4.1.1.11.d.4 Resolve promise with value and terminate this algorithm.
- aRequest->SetSuccess(info);
-}
-
-// NOTE: This method represents a theoretical way to use a U2F-compliant token
-// to produce the result of the WebAuthn GetAssertion method. The exact mapping
-// of U2F data fields to WebAuthn data fields is still a matter of ongoing
-// discussion, and this should not be taken as anything but a point-in- time
-// possibility.
-void
-WebAuthentication::U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
- const Authenticator& aToken, CryptoBuffer& aRpIdHash,
- const nsACString& aClientData, CryptoBuffer& aClientDataHash,
- nsTArray<CryptoBuffer>& aAllowList,
- const WebAuthnExtensions& aExtensions)
-{
- MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthGetAssertion"));
-
- // 4.1.2.7.e Add an entry to issuedRequests, corresponding to this request.
- aRequest->AddActiveToken(__func__);
-
- // 4.1.2.8 While issuedRequests is not empty, perform the following actions
- // depending upon the adjustedTimeout timer and responses from the
- // authenticators:
-
- // 4.1.2.8.a If the timer for adjustedTimeout expires, then for each entry
- // in issuedRequests invoke the authenticatorCancel operation on that
- // authenticator and remove its entry from the list.
-
- for (CryptoBuffer& allowedCredential : aAllowList) {
- bool isRegistered = false;
- nsresult rv = aToken->IsRegistered(allowedCredential.Elements(),
- allowedCredential.Length(),
- &isRegistered);
-
- // 4.1.2.8.b If any authenticator returns a status indicating that the user
- // cancelled the operation, delete that authenticator’s entry from
- // issuedRequests. For each remaining entry in issuedRequests invoke the
- // authenticatorCancel operation on that authenticator, and remove its entry
- // from the list.
-
- // 4.1.2.8.c If any authenticator returns an error status, delete the
- // corresponding entry from issuedRequests.
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aRequest->SetFailure(rv);
- return;
- }
-
- if (!isRegistered) {
- continue;
- }
-
- // Sign
- uint8_t* buffer;
- uint32_t bufferlen;
- rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
- aClientDataHash.Elements(), aClientDataHash.Length(),
- allowedCredential.Elements(), allowedCredential.Length(),
- &buffer, &bufferlen);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aRequest->SetFailure(rv);
- return;
- }
-
- MOZ_ASSERT(buffer);
- CryptoBuffer signatureData;
- if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
- free(buffer);
- aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
- free(buffer);
-
- // 4.1.2.8.d If any authenticator returns success:
-
- // 4.1.2.8.d.1 Remove this authenticator’s entry from issuedRequests.
-
- // 4.1.2.8.d.2 Create a new WebAuthnAssertion object named value and
- // populate its fields with the values returned from the authenticator as
- // well as the clientDataJSON computed earlier.
-
- CryptoBuffer clientDataBuf;
- if (!clientDataBuf.Assign(aClientData)) {
- aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
- return;
- }
-
- CryptoBuffer authenticatorDataBuf;
- rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
- signatureData);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- aRequest->SetFailure(rv);
- return;
- }
-
- RefPtr<ScopedCredential> credential = new ScopedCredential(this);
- credential->SetType(ScopedCredentialType::ScopedCred);
- credential->SetId(allowedCredential);
-
- AssertionPtr assertion = new WebAuthnAssertion(this);
- assertion->SetCredential(credential);
- assertion->SetClientData(clientDataBuf);
- assertion->SetAuthenticatorData(authenticatorDataBuf);
- assertion->SetSignature(signatureData);
-
- // 4.1.2.8.d.3 For each remaining entry in issuedRequests invoke the
- // authenticatorCancel operation on that authenticator and remove its entry
- // from the list.
-
- // 4.1.2.8.d.4 Resolve promise with value and terminate this algorithm.
- aRequest->SetSuccess(assertion);
- return;
- }
-
- // 4.1.2.9 Reject promise with a DOMException whose name is "NotAllowedError",
- // and terminate this algorithm.
- aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
-}
-
-nsresult
-WebAuthentication::RelaxSameOrigin(const nsAString& aInputRpId,
- /* out */ nsACString& aRelaxedRpId)
-{
- MOZ_ASSERT(mParent);
- nsCOMPtr<nsIDocument> document = mParent->GetDoc();
- if (!document || !document->IsHTMLDocument()) {
- return NS_ERROR_FAILURE;
- }
-
- // TODO: Bug 1329764: Invoke the Relax Algorithm, once properly defined
- aRelaxedRpId.Assign(NS_ConvertUTF16toUTF8(aInputRpId));
- return NS_OK;
-}
-
-already_AddRefed<Promise>
-WebAuthentication::MakeCredential(JSContext* aCx, const Account& aAccount,
- const Sequence<ScopedCredentialParameters>& aCryptoParameters,
- const ArrayBufferViewOrArrayBuffer& aChallenge,
- const ScopedCredentialOptions& aOptions)
-{
- MOZ_ASSERT(mParent);
- nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
- if (!global) {
- return nullptr;
- }
-
- ErrorResult rv;
- RefPtr<Promise> promise = Promise::Create(global, rv);
-
- nsresult initRv = InitLazily();
- if (NS_FAILED(initRv)) {
- promise->MaybeReject(initRv);
- return promise.forget();
- }
-
- // 4.1.1.1 If timeoutSeconds was specified, check if its value lies within a
- // reasonable range as defined by the platform and if not, correct it to the
- // closest value lying within that range.
-
- double adjustedTimeout = 30.0;
- if (aOptions.mTimeoutSeconds.WasPassed()) {
- adjustedTimeout = aOptions.mTimeoutSeconds.Value();
- adjustedTimeout = std::max(15.0, adjustedTimeout);
- adjustedTimeout = std::min(120.0, adjustedTimeout);
- }
-
- // 4.1.1.2 Let promise be a new Promise. Return promise and start a timer for
- // adjustedTimeout seconds.
-
- RefPtr<CredentialRequest> requestMonitor = new CredentialRequest();
- requestMonitor->SetDeadline(TimeDuration::FromSeconds(adjustedTimeout));
-
- if (mOrigin.EqualsLiteral("null")) {
- // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
- // DOMException whose name is "NotAllowedError", and terminate this
- // algorithm
- MOZ_LOG(gWebauthLog, LogLevel::Debug, ("Rejecting due to opaque origin"));
- promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
- return promise.forget();
- }
-
- nsCString rpId;
- if (!aOptions.mRpId.WasPassed()) {
- // 4.1.1.3.a If rpId is not specified, then set rpId to callerOrigin, and
- // rpIdHash to the SHA-256 hash of rpId.
- rpId.Assign(NS_ConvertUTF16toUTF8(mOrigin));
- } else {
- // 4.1.1.3.b If rpId is specified, then invoke the procedure used for
- // relaxing the same-origin restriction by setting the document.domain
- // attribute, using rpId as the given value but without changing the current
- // document’s domain. If no errors are thrown, set rpId to the value of host
- // as computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
- // Otherwise, reject promise with a DOMException whose name is
- // "SecurityError", and terminate this algorithm.
-
- if (NS_FAILED(RelaxSameOrigin(aOptions.mRpId.Value(), rpId))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
- }
-
- CryptoBuffer rpIdHash;
- if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
- promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
- return promise.forget();
- }
-
- nsresult srv;
- nsCOMPtr<nsICryptoHash> hashService =
- do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
- if (NS_WARN_IF(NS_FAILED(srv))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- srv = HashCString(hashService, rpId, rpIdHash);
- if (NS_WARN_IF(NS_FAILED(srv))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- // 4.1.1.4 Process each element of cryptoParameters using the following steps,
- // to produce a new sequence normalizedParameters.
- nsTArray<ScopedCredentialParameters> normalizedParams;
- for (size_t a = 0; a < aCryptoParameters.Length(); ++a) {
- // 4.1.1.4.a Let current be the currently selected element of
- // cryptoParameters.
-
- // 4.1.1.4.b If current.type does not contain a ScopedCredentialType
- // supported by this implementation, then stop processing current and move
- // on to the next element in cryptoParameters.
- if (aCryptoParameters[a].mType != ScopedCredentialType::ScopedCred) {
- continue;
- }
-
- // 4.1.1.4.c Let normalizedAlgorithm be the result of normalizing an
- // algorithm using the procedure defined in [WebCryptoAPI], with alg set to
- // current.algorithm and op set to 'generateKey'. If an error occurs during
- // this procedure, then stop processing current and move on to the next
- // element in cryptoParameters.
-
- nsString algName;
- if (NS_FAILED(GetAlgorithmName(aCx, aCryptoParameters[a].mAlgorithm,
- algName))) {
- continue;
- }
-
- // 4.1.1.4.d Add a new object of type ScopedCredentialParameters to
- // normalizedParameters, with type set to current.type and algorithm set to
- // normalizedAlgorithm.
- ScopedCredentialParameters normalizedObj;
- normalizedObj.mType = aCryptoParameters[a].mType;
- normalizedObj.mAlgorithm.SetAsString().Assign(algName);
-
- if (!normalizedParams.AppendElement(normalizedObj, mozilla::fallible)){
- promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
- return promise.forget();
- }
- }
-
- // 4.1.1.5 If normalizedAlgorithm is empty and cryptoParameters was not empty,
- // cancel the timer started in step 2, reject promise with a DOMException
- // whose name is "NotSupportedError", and terminate this algorithm.
- if (normalizedParams.IsEmpty() && !aCryptoParameters.IsEmpty()) {
- promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
- return promise.forget();
- }
-
- // 4.1.1.6 If excludeList is undefined, set it to the empty list.
-
- // 4.1.1.7 If extensions was specified, process any extensions supported by
- // this client platform, to produce the extension data that needs to be sent
- // to the authenticator. If an error is encountered while processing an
- // extension, skip that extension and do not produce any extension data for
- // it. Call the result of this processing clientExtensions.
-
- // Currently no extensions are supported
-
- // 4.1.1.8 Use attestationChallenge, callerOrigin and rpId, along with the
- // token binding key associated with callerOrigin (if any), to create a
- // ClientData structure representing this request. Choose a hash algorithm for
- // hashAlg and compute the clientDataJSON and clientDataHash.
-
- CryptoBuffer challenge;
- if (!challenge.Assign(aChallenge)) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- nsAutoCString clientDataJSON;
- srv = AssembleClientData(mOrigin, challenge, clientDataJSON);
- if (NS_WARN_IF(NS_FAILED(srv))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- CryptoBuffer clientDataHash;
- if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- srv = HashCString(hashService, clientDataJSON, clientDataHash);
- if (NS_WARN_IF(NS_FAILED(srv))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- // 4.1.1.9 Initialize issuedRequests to an empty list.
- RefPtr<CredentialPromise> monitorPromise = requestMonitor->Ensure();
-
- // 4.1.1.10 For each authenticator currently available on this platform:
- // asynchronously invoke the authenticatorMakeCredential operation on that
- // authenticator with rpIdHash, clientDataHash, accountInformation,
- // normalizedParameters, excludeList and clientExtensions as parameters. Add a
- // corresponding entry to issuedRequests.
- for (Authenticator u2ftoken : mAuthenticators) {
- // 4.1.1.10.a For each credential C in excludeList that has a non-empty
- // transports list, optionally use only the specified transports to test for
- // the existence of C.
- U2FAuthMakeCredential(requestMonitor, u2ftoken, rpIdHash, clientDataJSON,
- clientDataHash, aAccount, normalizedParams,
- aOptions.mExcludeList, aOptions.mExtensions);
- }
-
- requestMonitor->CompleteTask();
-
- monitorPromise->Then(AbstractThread::MainThread(), __func__,
- [promise] (CredentialPtr aInfo) {
- promise->MaybeResolve(aInfo);
- },
- [promise] (nsresult aErrorCode) {
- promise->MaybeReject(aErrorCode);
- });
-
- return promise.forget();
-}
-
-already_AddRefed<Promise>
-WebAuthentication::GetAssertion(const ArrayBufferViewOrArrayBuffer& aChallenge,
- const AssertionOptions& aOptions)
-{
- MOZ_ASSERT(mParent);
- nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
- if (!global) {
- return nullptr;
- }
-
- // 4.1.2.1 If timeoutSeconds was specified, check if its value lies within a
- // reasonable range as defined by the platform and if not, correct it to the
- // closest value lying within that range.
-
- double adjustedTimeout = 30.0;
- if (aOptions.mTimeoutSeconds.WasPassed()) {
- adjustedTimeout = aOptions.mTimeoutSeconds.Value();
- adjustedTimeout = std::max(15.0, adjustedTimeout);
- adjustedTimeout = std::min(120.0, adjustedTimeout);
- }
-
- // 4.1.2.2 Let promise be a new Promise. Return promise and start a timer for
- // adjustedTimeout seconds.
-
- RefPtr<AssertionRequest> requestMonitor = new AssertionRequest();
- requestMonitor->SetDeadline(TimeDuration::FromSeconds(adjustedTimeout));
-
- ErrorResult rv;
- RefPtr<Promise> promise = Promise::Create(global, rv);
-
- nsresult initRv = InitLazily();
- if (NS_FAILED(initRv)) {
- promise->MaybeReject(initRv);
- return promise.forget();
- }
-
- if (mOrigin.EqualsLiteral("null")) {
- // 4.1.2.3 If callerOrigin is an opaque origin, reject promise with a
- // DOMException whose name is "NotAllowedError", and terminate this algorithm
- promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
- return promise.forget();
- }
-
- nsCString rpId;
- if (!aOptions.mRpId.WasPassed()) {
- // 4.1.2.3.a If rpId is not specified, then set rpId to callerOrigin, and
- // rpIdHash to the SHA-256 hash of rpId.
- rpId.Assign(NS_ConvertUTF16toUTF8(mOrigin));
- } else {
- // 4.1.2.3.b If rpId is specified, then invoke the procedure used for
- // relaxing the same-origin restriction by setting the document.domain
- // attribute, using rpId as the given value but without changing the current
- // document’s domain. If no errors are thrown, set rpId to the value of host
- // as computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
- // Otherwise, reject promise with a DOMException whose name is
- // "SecurityError", and terminate this algorithm.
-
- if (NS_FAILED(RelaxSameOrigin(aOptions.mRpId.Value(), rpId))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
- }
-
- CryptoBuffer rpIdHash;
- if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- nsresult srv;
- nsCOMPtr<nsICryptoHash> hashService =
- do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
- if (NS_WARN_IF(NS_FAILED(srv))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- srv = HashCString(hashService, rpId, rpIdHash);
- if (NS_WARN_IF(NS_FAILED(srv))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- // 4.1.2.4 If extensions was specified, process any extensions supported by
- // this client platform, to produce the extension data that needs to be sent
- // to the authenticator. If an error is encountered while processing an
- // extension, skip that extension and do not produce any extension data for
- // it. Call the result of this processing clientExtensions.
-
- // TODO
-
- // 4.1.2.5 Use assertionChallenge, callerOrigin and rpId, along with the token
- // binding key associated with callerOrigin (if any), to create a ClientData
- // structure representing this request. Choose a hash algorithm for hashAlg
- // and compute the clientDataJSON and clientDataHash.
- CryptoBuffer challenge;
- if (!challenge.Assign(aChallenge)) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- nsAutoCString clientDataJSON;
- srv = AssembleClientData(mOrigin, challenge, clientDataJSON);
- if (NS_WARN_IF(NS_FAILED(srv))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- CryptoBuffer clientDataHash;
- if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- srv = HashCString(hashService, clientDataJSON, clientDataHash);
- if (NS_WARN_IF(NS_FAILED(srv))) {
- promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
- return promise.forget();
- }
-
- // Note: we only support U2F-style authentication for now, so we effectively
- // require an AllowList.
- if (!aOptions.mAllowList.WasPassed()) {
- promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
- return promise.forget();
- }
-
- const Sequence<ScopedCredentialDescriptor>& allowList =
- aOptions.mAllowList.Value();
-
- // 4.1.2.6 Initialize issuedRequests to an empty list.
- RefPtr<AssertionPromise> monitorPromise = requestMonitor->Ensure();
-
- // 4.1.2.7 For each authenticator currently available on this platform,
- // perform the following steps:
- for(Authenticator u2ftoken : mAuthenticators) {
- // 4.1.2.7.a If allowList is undefined or empty, let credentialList be an
- // empty list. Otherwise, execute a platform-specific procedure to determine
- // which, if any, credentials listed in allowList might be present on this
- // authenticator, and set credentialList to this filtered list. If no such
- // filtering is possible, set credentialList to an empty list.
-
- nsTArray<CryptoBuffer> credentialList;
-
- for (const ScopedCredentialDescriptor& scd : allowList) {
- CryptoBuffer buf;
- if (NS_WARN_IF(!buf.Assign(scd.mId))) {
- continue;
- }
-
- // 4.1.2.7.b For each credential C within the credentialList that has a
- // non- empty transports list, optionally use only the specified
- // transports to get assertions using credential C.
-
- // TODO: Filter using Transport
- if (!credentialList.AppendElement(buf, mozilla::fallible)) {
- requestMonitor->CancelNow();
- promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
- return promise.forget();
- }
- }
-
- // 4.1.2.7.c If the above filtering process concludes that none of the
- // credentials on allowList can possibly be on this authenticator, do not
- // perform any of the following steps for this authenticator, and proceed to
- // the next authenticator (if any).
- if (credentialList.IsEmpty()) {
- continue;
- }
-
- // 4.1.2.7.d Asynchronously invoke the authenticatorGetAssertion operation
- // on this authenticator with rpIdHash, clientDataHash, credentialList, and
- // clientExtensions as parameters.
- U2FAuthGetAssertion(requestMonitor, u2ftoken, rpIdHash, clientDataJSON,
- clientDataHash, credentialList, aOptions.mExtensions);
- }
-
- requestMonitor->CompleteTask();
-
- monitorPromise->Then(AbstractThread::MainThread(), __func__,
- [promise] (AssertionPtr aAssertion) {
- promise->MaybeResolve(aAssertion);
- },
- [promise] (nsresult aErrorCode) {
- promise->MaybeReject(aErrorCode);
- });
-
- return promise.forget();
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/u2f/WebAuthentication.h
+++ /dev/null
@@ -1,113 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_WebAuthentication_h
-#define mozilla_dom_WebAuthentication_h
-
-#include "js/TypeDecls.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/dom/BindingDeclarations.h"
-#include "mozilla/dom/DOMException.h"
-#include "mozilla/dom/WebAuthenticationBinding.h"
-#include "mozilla/dom/WebCryptoCommon.h"
-#include "mozilla/ErrorResult.h"
-#include "mozilla/MozPromise.h"
-#include "mozilla/ReentrantMonitor.h"
-#include "mozilla/SharedThreadPool.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsWrapperCache.h"
-
-#include "U2FAuthenticator.h"
-#include "WebAuthnRequest.h"
-
-namespace mozilla {
-namespace dom {
-
-struct Account;
-class ArrayBufferViewOrArrayBuffer;
-struct AssertionOptions;
-class OwningArrayBufferViewOrArrayBuffer;
-struct ScopedCredentialOptions;
-struct ScopedCredentialParameters;
-
-} // namespace dom
-} // namespace mozilla
-
-namespace mozilla {
-namespace dom {
-
-typedef RefPtr<ScopedCredentialInfo> CredentialPtr;
-typedef RefPtr<WebAuthnAssertion> AssertionPtr;
-typedef WebAuthnRequest<CredentialPtr> CredentialRequest;
-typedef WebAuthnRequest<AssertionPtr> AssertionRequest;
-typedef MozPromise<CredentialPtr, nsresult, false> CredentialPromise;
-typedef MozPromise<AssertionPtr, nsresult, false> AssertionPromise;
-
-class WebAuthentication final : public nsISupports
- , public nsWrapperCache
-{
-public:
- NS_DECL_CYCLE_COLLECTING_ISUPPORTS
- NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthentication)
-
-public:
- explicit WebAuthentication(nsPIDOMWindowInner* aParent);
-
-protected:
- ~WebAuthentication();
-
-public:
- nsPIDOMWindowInner*
- GetParentObject() const
- {
- return mParent;
- }
-
- virtual JSObject*
- WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
-
- already_AddRefed<Promise>
- MakeCredential(JSContext* aCx, const Account& accountInformation,
- const Sequence<ScopedCredentialParameters>& cryptoParameters,
- const ArrayBufferViewOrArrayBuffer& attestationChallenge,
- const ScopedCredentialOptions& options);
-
- already_AddRefed<Promise>
- GetAssertion(const ArrayBufferViewOrArrayBuffer& assertionChallenge,
- const AssertionOptions& options);
-
-private:
- nsresult
- InitLazily();
-
- void
- U2FAuthMakeCredential(const RefPtr<CredentialRequest>& aRequest,
- const Authenticator& aToken, CryptoBuffer& aRpIdHash,
- const nsACString& aClientData, CryptoBuffer& aClientDataHash,
- const Account& aAccount,
- const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
- const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
- const WebAuthnExtensions& aExtensions);
- void
- U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
- const Authenticator& aToken, CryptoBuffer& aRpIdHash,
- const nsACString& aClientData, CryptoBuffer& aClientDataHash,
- nsTArray<CryptoBuffer>& aAllowList,
- const WebAuthnExtensions& aExtensions);
-
- nsresult
- RelaxSameOrigin(const nsAString& aInputRpId, nsACString& aRelaxedRpId);
-
- nsCOMPtr<nsPIDOMWindowInner> mParent;
- nsString mOrigin;
- Sequence<Authenticator> mAuthenticators;
- bool mInitialized;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_WebAuthentication_h
deleted file mode 100644
--- a/dom/u2f/WebAuthnRequest.h
+++ /dev/null
@@ -1,137 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_WebAuthnAsync_h
-#define mozilla_dom_WebAuthnAsync_h
-
-#include "mozilla/MozPromise.h"
-#include "mozilla/ReentrantMonitor.h"
-#include "mozilla/SharedThreadPool.h"
-#include "mozilla/TimeStamp.h"
-
-namespace mozilla {
-namespace dom {
-
-extern mozilla::LazyLogModule gWebauthLog; // defined in U2F.cpp
-
-// WebAuthnRequest tracks the completion of a single WebAuthn request that
-// may run on multiple kinds of authenticators, and be subject to a deadline.
-template<class Success>
-class WebAuthnRequest {
-public:
- WebAuthnRequest()
- : mCancelled(false)
- , mSuccess(false)
- , mCountTokens(0)
- , mTokensFailed(0)
- , mReentrantMonitor("WebAuthnRequest")
- {}
-
- void AddActiveToken(const char* aCallSite)
- {
- MOZ_LOG(gWebauthLog, LogLevel::Debug,
- ("WebAuthnRequest is tracking a new token, called from [%s]",
- aCallSite));
- ReentrantMonitorAutoEnter mon(mReentrantMonitor);
- MOZ_ASSERT(!IsComplete());
- mCountTokens += 1;
- }
-
- bool IsComplete()
- {
- ReentrantMonitorAutoEnter mon(mReentrantMonitor);
- return mCancelled || mSuccess ||
- (mCountTokens > 0 && mTokensFailed == mCountTokens);
- }
-
- void CancelNow()
- {
- ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-
- // It's possible for a race to cause CancelNow to get called after
- // a success or a cancel. We only complete once.
- if (IsComplete()) {
- return;
- }
-
- mCancelled = true;
- mPromise.Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
- }
-
- void SetFailure(nsresult aError)
- {
- ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-
- // It's possible for a race to cause SetFailure to get called after
- // a success or a cancel. We only complete once.
- if (IsComplete()) {
- return;
- }
-
- mTokensFailed += 1;
- MOZ_ASSERT(mTokensFailed <= mCountTokens);
-
- if (mTokensFailed == mCountTokens) {
- // Provide the final error as being indicitive of the whole set.
- mPromise.Reject(aError, __func__);
- }
- }
-
- void SetSuccess(const Success& aResult)
- {
- ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-
- // It's possible for a race to cause multiple calls to SetSuccess
- // in succession. We will only select the earliest.
- if (IsComplete()) {
- return;
- }
-
- mSuccess = true;
- mPromise.Resolve(aResult, __func__);
- }
-
- void SetDeadline(TimeDuration aDeadline)
- {
- ReentrantMonitorAutoEnter mon(mReentrantMonitor);
- MOZ_ASSERT(!IsComplete());
- // TODO: Monitor the deadline and stop with a timeout error if it expires.
- }
-
- RefPtr<MozPromise<Success, nsresult, false>> Ensure()
- {
- ReentrantMonitorAutoEnter mon(mReentrantMonitor);
- MOZ_ASSERT(!IsComplete());
- return mPromise.Ensure(__func__);
- }
-
- void CompleteTask()
- {
- ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-
- if (mCountTokens == 0) {
- // Special case for there being no tasks to complete
- mPromise.Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
- }
- }
-
- NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebAuthnRequest)
-
-private:
- ~WebAuthnRequest() {};
-
- bool mCancelled;
- bool mSuccess;
- int mCountTokens;
- int mTokensFailed;
- ReentrantMonitor mReentrantMonitor;
- MozPromiseHolder<MozPromise<Success, nsresult, false>> mPromise;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_WebAuthnAsync_h
--- a/dom/u2f/moz.build
+++ b/dom/u2f/moz.build
@@ -1,36 +1,23 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
EXPORTS.mozilla.dom += [
- 'NSSU2FTokenRemote.h',
- 'ScopedCredential.h',
- 'ScopedCredentialInfo.h',
'U2F.h',
'U2FAuthenticator.h',
'USBToken.h',
- 'WebAuthentication.h',
- 'WebAuthnAssertion.h',
- 'WebAuthnAttestation.h',
- 'WebAuthnRequest.h',
]
UNIFIED_SOURCES += [
- 'NSSU2FTokenRemote.cpp',
- 'ScopedCredential.cpp',
- 'ScopedCredentialInfo.cpp',
'U2F.cpp',
'USBToken.cpp',
- 'WebAuthentication.cpp',
- 'WebAuthnAssertion.cpp',
- 'WebAuthnAttestation.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
'/dom/base',
--- a/dom/u2f/tests/mochitest.ini
+++ b/dom/u2f/tests/mochitest.ini
@@ -22,23 +22,8 @@ skip-if = !e10s
[test_register_sign.html]
skip-if = !e10s
[test_appid_facet.html]
skip-if = !e10s
[test_appid_facet_insecure.html]
skip-if = !e10s
[test_appid_facet_subdomain.html]
skip-if = !e10s
-[test_webauthn_loopback.html]
-skip-if = !e10s
-scheme = https
-[test_webauthn_no_token.html]
-skip-if = !e10s
-scheme = https
-[test_webauthn_make_credential.html]
-skip-if = !e10s
-scheme = https
-[test_webauthn_get_assertion.html]
-skip-if = !e10s
-scheme = https
-[test_webauthn_sameorigin.html]
-skip-if = !e10s
-scheme = https
\ No newline at end of file
rename from dom/u2f/NSSU2FTokenRemote.cpp
rename to dom/webauthn/NSSU2FTokenRemote.cpp
rename from dom/u2f/NSSU2FTokenRemote.h
rename to dom/webauthn/NSSU2FTokenRemote.h
rename from dom/u2f/ScopedCredential.cpp
rename to dom/webauthn/ScopedCredential.cpp
rename from dom/u2f/ScopedCredential.h
rename to dom/webauthn/ScopedCredential.h
rename from dom/u2f/ScopedCredentialInfo.cpp
rename to dom/webauthn/ScopedCredentialInfo.cpp
rename from dom/u2f/ScopedCredentialInfo.h
rename to dom/webauthn/ScopedCredentialInfo.h
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/WebAuthentication.cpp
@@ -0,0 +1,1057 @@
+/* -*- 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/WebAuthentication.h"
+#include "mozilla/dom/WebAuthnAssertion.h"
+#include "mozilla/dom/WebAuthnAttestation.h"
+
+#include "mozilla/dom/Promise.h"
+#include "nsICryptoHash.h"
+#include "pkix/Input.h"
+#include "pkixutil.h"
+
+#define PREF_U2F_SOFTTOKEN_ENABLED "security.webauth.u2f_enable_softtoken"
+#define PREF_U2F_USBTOKEN_ENABLED "security.webauth.u2f_enable_usbtoken"
+
+namespace mozilla {
+namespace dom {
+
+static mozilla::LazyLogModule gWebauthLog("webauthn");
+
+// Only needed for refcounted objects.
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthentication, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthentication)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthentication)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthentication)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+template<class OOS>
+static nsresult
+GetAlgorithmName(JSContext* aCx, const OOS& aAlgorithm,
+ /* out */ nsString& aName)
+{
+ MOZ_ASSERT(aAlgorithm.IsString()); // TODO: remove assertion when we coerce.
+
+ if (aAlgorithm.IsString()) {
+ // If string, then treat as algorithm name
+ aName.Assign(aAlgorithm.GetAsString());
+ } else {
+ // TODO: Coerce to string and extract name. See WebCryptoTask.cpp
+ }
+
+ if (!NormalizeToken(aName, aName)) {
+ 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))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WebAuthnClientData clientDataObject;
+ clientDataObject.mOrigin.Assign(aOrigin);
+ clientDataObject.mHashAlg.SetAsString().Assign(NS_LITERAL_STRING("S256"));
+ clientDataObject.mChallenge.Assign(challengeBase64);
+
+ nsAutoString temp;
+ if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
+ return NS_OK;
+}
+
+static nsresult
+ScopedCredentialGetData(const ScopedCredentialDescriptor& aSCD,
+ /* out */ uint8_t** aBuf, /* out */ uint32_t* aBufLen)
+{
+ MOZ_ASSERT(aBuf);
+ MOZ_ASSERT(aBufLen);
+
+ if (aSCD.mId.IsArrayBufferView()) {
+ const ArrayBufferView& view = aSCD.mId.GetAsArrayBufferView();
+ view.ComputeLengthAndData();
+ *aBuf = view.Data();
+ *aBufLen = view.Length();
+ } else if (aSCD.mId.IsArrayBuffer()) {
+ const ArrayBuffer& buffer = aSCD.mId.GetAsArrayBuffer();
+ buffer.ComputeLengthAndData();
+ *aBuf = buffer.Data();
+ *aBufLen = buffer.Length();
+ } else {
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+ReadToCryptoBuffer(pkix::Reader& aSrc, /* out */ CryptoBuffer& aDest,
+ uint32_t aLen)
+{
+ if (aSrc.EnsureLength(aLen) != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ aDest.ClearAndRetainStorage();
+
+ for (uint32_t offset = 0; offset < aLen; ++offset) {
+ uint8_t b;
+ if (aSrc.Read(b) != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ if (!aDest.AppendElement(b, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+U2FAssembleAuthenticatorData(/* out */ CryptoBuffer& aAuthenticatorData,
+ const CryptoBuffer& aRpIdHash,
+ const CryptoBuffer& aSignatureData)
+{
+ // The AuthenticatorData for U2F devices is the concatenation of the
+ // RP ID with the output of the U2F Sign operation.
+ if (aRpIdHash.Length() != 32) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!aAuthenticatorData.AppendElements(aRpIdHash, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!aAuthenticatorData.AppendElements(aSignatureData, mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+U2FDecomposeRegistrationResponse(const CryptoBuffer& aResponse,
+ /* out */ CryptoBuffer& aPubKeyBuf,
+ /* out */ CryptoBuffer& aKeyHandleBuf,
+ /* out */ CryptoBuffer& aAttestationCertBuf,
+ /* out */ CryptoBuffer& aSignatureBuf)
+{
+ // U2F v1.1 Format via
+ // http://fidoalliance.org/specs/fido-u2f-v1.1-id-20160915/fido-u2f-raw-message-formats-v1.1-id-20160915.html
+ //
+ // Bytes Value
+ // 1 0x05
+ // 65 public key
+ // 1 key handle length
+ // * key handle
+ // ASN.1 attestation certificate
+ // * attestation signature
+
+ pkix::Input u2fResponse;
+ u2fResponse.Init(aResponse.Elements(), aResponse.Length());
+
+ pkix::Reader input(u2fResponse);
+
+ uint8_t b;
+ if (input.Read(b) != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ if (b != 0x05) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ nsresult rv = ReadToCryptoBuffer(input, aPubKeyBuf, 65);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ uint8_t handleLen;
+ if (input.Read(handleLen) != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ rv = ReadToCryptoBuffer(input, aKeyHandleBuf, handleLen);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // We have to parse the ASN.1 SEQUENCE on the outside to determine the cert's
+ // length.
+ pkix::Input cert;
+ if (pkix::der::ExpectTagAndGetValue(input, pkix::der::SEQUENCE, cert)
+ != pkix::Success) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ pkix::Reader certInput(cert);
+ rv = ReadToCryptoBuffer(certInput, aAttestationCertBuf, cert.GetLength());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // The remainder of u2fResponse is the signature
+ pkix::Input u2fSig;
+ input.SkipToEnd(u2fSig);
+ pkix::Reader sigInput(u2fSig);
+ rv = ReadToCryptoBuffer(sigInput, aSignatureBuf, u2fSig.GetLength());
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+WebAuthentication::WebAuthentication(nsPIDOMWindowInner* aParent)
+ : mInitialized(false)
+{
+ mParent = do_QueryInterface(aParent);
+ MOZ_ASSERT(mParent);
+}
+
+WebAuthentication::~WebAuthentication()
+{}
+
+nsresult
+WebAuthentication::InitLazily()
+{
+ if (mInitialized) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mParent);
+ if (!mParent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocument> doc = mParent->GetDoc();
+ MOZ_ASSERT(doc);
+
+ nsIPrincipal* principal = doc->NodePrincipal();
+ nsresult rv = nsContentUtils::GetUTFOrigin(principal, mOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(mOrigin.IsEmpty())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // This only functions in e10s mode
+ // TODO: Remove in Bug 1323339
+ if (XRE_IsParentProcess()) {
+ MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ ("Is non-e10s Process, WebAuthn not available"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (Preferences::GetBool(PREF_U2F_SOFTTOKEN_ENABLED)) {
+ if (!mAuthenticators.AppendElement(new NSSU2FTokenRemote(),
+ mozilla::fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+JSObject*
+WebAuthentication::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return WebAuthenticationBinding::Wrap(aCx, this, aGivenProto);
+}
+
+// NOTE: This method represents a theoretical way to use a U2F-compliant token
+// to produce the result of the WebAuthn MakeCredential method. The exact
+// mapping of U2F data fields to WebAuthn data fields is still a matter of
+// ongoing discussion, and this should not be taken as anything but a point-in-
+// time possibility.
+void
+WebAuthentication::U2FAuthMakeCredential(
+ const RefPtr<CredentialRequest>& aRequest,
+ const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+ const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+ const Account& aAccount,
+ const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
+ const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
+ const WebAuthnExtensions& aExtensions)
+{
+ MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthMakeCredential"));
+ aRequest->AddActiveToken(__func__);
+
+ // 5.1.1 When this operation is invoked, the authenticator must perform the
+ // following procedure:
+
+ // 5.1.1.a Check if all the supplied parameters are syntactically well-
+ // formed and of the correct length. If not, return an error code equivalent
+ // to UnknownError and terminate the operation.
+
+ if ((aRpIdHash.Length() != SHA256_LENGTH) ||
+ (aClientDataHash.Length() != SHA256_LENGTH)) {
+ aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ // 5.1.1.b Check if at least one of the specified combinations of
+ // ScopedCredentialType and cryptographic parameters is supported. If not,
+ // return an error code equivalent to NotSupportedError and terminate the
+ // operation.
+
+ bool isValidCombination = false;
+
+ for (size_t a = 0; a < aNormalizedParams.Length(); ++a) {
+ if (aNormalizedParams[a].mType == ScopedCredentialType::ScopedCred &&
+ aNormalizedParams[a].mAlgorithm.IsString() &&
+ aNormalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
+ WEBCRYPTO_NAMED_CURVE_P256)) {
+ isValidCombination = true;
+ break;
+ }
+ }
+ if (!isValidCombination) {
+ aRequest->SetFailure(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return;
+ }
+
+ // 5.1.1.c Check if a credential matching any of the supplied
+ // ScopedCredential identifiers is present on this authenticator. If so,
+ // return an error code equivalent to NotAllowedError and terminate the
+ // operation.
+
+ if (aExcludeList.WasPassed()) {
+ const Sequence<ScopedCredentialDescriptor>& list = aExcludeList.Value();
+
+ for (const ScopedCredentialDescriptor& scd : list) {
+ bool isRegistered = false;
+
+ uint8_t *data;
+ uint32_t len;
+
+ // data is owned by the Descriptor, do don't free it here.
+ if (NS_FAILED(ScopedCredentialGetData(scd, &data, &len))) {
+ aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ nsresult rv = aToken->IsRegistered(data, len, &isRegistered);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ if (isRegistered) {
+ aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return;
+ }
+ }
+ }
+
+ // 5.1.1.d Prompt the user for consent to create a new credential. The
+ // prompt for obtaining this consent is shown by the authenticator if it has
+ // its own output capability, or by the user agent otherwise. If the user
+ // denies consent, return an error code equivalent to NotAllowedError and
+ // terminate the operation.
+
+ // 5.1.1.d Once user consent has been obtained, generate a new credential
+ // object
+
+ // 5.1.1.e If any error occurred while creating the new credential object,
+ // return an error code equivalent to UnknownError and terminate the
+ // operation.
+
+ // 5.1.1.f Process all the supported extensions requested by the client, and
+ // generate an attestation statement. If no authority key is available to
+ // sign such an attestation statement, then the authenticator performs self
+ // attestation of the credential with its own private key. For more details
+ // on attestation, see §5.3 Credential Attestation Statements.
+
+ // No extensions are supported
+
+ // 4.1.1.11 While issuedRequests is not empty, perform the following actions
+ // depending upon the adjustedTimeout timer and responses from the
+ // authenticators:
+
+ // 4.1.1.11.a If the adjustedTimeout timer expires, then for each entry in
+ // issuedRequests invoke the authenticatorCancel operation on that
+ // authenticator and remove its entry from the list.
+
+ uint8_t* buffer;
+ uint32_t bufferlen;
+
+ nsresult rv = aToken->Register(aRpIdHash.Elements(), aRpIdHash.Length(),
+ aClientDataHash.Elements(),
+ aClientDataHash.Length(), &buffer, &bufferlen);
+
+ // 4.1.1.11.b If any authenticator returns a status indicating that the user
+ // cancelled the operation, delete that authenticator’s entry from
+ // issuedRequests. For each remaining entry in issuedRequests invoke the
+ // authenticatorCancel operation on that authenticator and remove its entry
+ // from the list.
+
+ // 4.1.1.11.c If any authenticator returns an error status, delete the
+ // corresponding entry from issuedRequests.
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(NS_ERROR_DOM_UNKNOWN_ERR);
+ return;
+ }
+
+ MOZ_ASSERT(buffer);
+ CryptoBuffer regData;
+ if (NS_WARN_IF(!regData.Assign(buffer, bufferlen))) {
+ free(buffer);
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ free(buffer);
+
+ // Decompose the U2F registration packet
+ CryptoBuffer pubKeyBuf;
+ CryptoBuffer keyHandleBuf;
+ CryptoBuffer attestationCertBuf;
+ CryptoBuffer signatureBuf;
+
+ rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf,
+ attestationCertBuf, signatureBuf);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ // Sign the aClientDataHash explicitly to get the format needed for
+ // the AuthenticatorData parameter of WebAuthnAttestation. This might
+ // be temporary while the spec settles down how to incorporate U2F.
+ rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
+ aClientDataHash.Elements(), aClientDataHash.Length(),
+ keyHandleBuf.Elements(), keyHandleBuf.Length(), &buffer,
+ &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ MOZ_ASSERT(buffer);
+ CryptoBuffer signatureData;
+ if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
+ free(buffer);
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ free(buffer);
+
+ CryptoBuffer clientDataBuf;
+ if (!clientDataBuf.Assign(aClientData)) {
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ CryptoBuffer authenticatorDataBuf;
+ rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
+ signatureData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ // 4.1.1.11.d If any authenticator indicates success:
+
+ // 4.1.1.11.d.1 Remove this authenticator’s entry from issuedRequests.
+
+ // 4.1.1.11.d.2 Create a new ScopedCredentialInfo object named value and
+ // populate its fields with the values returned from the authenticator as well
+ // as the clientDataJSON computed earlier.
+
+ RefPtr<ScopedCredential> credential = new ScopedCredential(this);
+ credential->SetType(ScopedCredentialType::ScopedCred);
+ credential->SetId(keyHandleBuf);
+
+ RefPtr<WebAuthnAttestation> attestation = new WebAuthnAttestation(this);
+ attestation->SetFormat(NS_LITERAL_STRING("u2f"));
+ attestation->SetClientData(clientDataBuf);
+ attestation->SetAuthenticatorData(authenticatorDataBuf);
+ attestation->SetAttestation(regData);
+
+ CredentialPtr info = new ScopedCredentialInfo(this);
+ info->SetCredential(credential);
+ info->SetAttestation(attestation);
+
+ // 4.1.1.11.d.3 For each remaining entry in issuedRequests invoke the
+ // authenticatorCancel operation on that authenticator and remove its entry
+ // from the list.
+
+ // 4.1.1.11.d.4 Resolve promise with value and terminate this algorithm.
+ aRequest->SetSuccess(info);
+}
+
+// NOTE: This method represents a theoretical way to use a U2F-compliant token
+// to produce the result of the WebAuthn GetAssertion method. The exact mapping
+// of U2F data fields to WebAuthn data fields is still a matter of ongoing
+// discussion, and this should not be taken as anything but a point-in- time
+// possibility.
+void
+WebAuthentication::U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
+ const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+ const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+ nsTArray<CryptoBuffer>& aAllowList,
+ const WebAuthnExtensions& aExtensions)
+{
+ MOZ_LOG(gWebauthLog, LogLevel::Debug, ("U2FAuthGetAssertion"));
+
+ // 4.1.2.7.e Add an entry to issuedRequests, corresponding to this request.
+ aRequest->AddActiveToken(__func__);
+
+ // 4.1.2.8 While issuedRequests is not empty, perform the following actions
+ // depending upon the adjustedTimeout timer and responses from the
+ // authenticators:
+
+ // 4.1.2.8.a If the timer for adjustedTimeout expires, then for each entry
+ // in issuedRequests invoke the authenticatorCancel operation on that
+ // authenticator and remove its entry from the list.
+
+ for (CryptoBuffer& allowedCredential : aAllowList) {
+ bool isRegistered = false;
+ nsresult rv = aToken->IsRegistered(allowedCredential.Elements(),
+ allowedCredential.Length(),
+ &isRegistered);
+
+ // 4.1.2.8.b If any authenticator returns a status indicating that the user
+ // cancelled the operation, delete that authenticator’s entry from
+ // issuedRequests. For each remaining entry in issuedRequests invoke the
+ // authenticatorCancel operation on that authenticator, and remove its entry
+ // from the list.
+
+ // 4.1.2.8.c If any authenticator returns an error status, delete the
+ // corresponding entry from issuedRequests.
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ if (!isRegistered) {
+ continue;
+ }
+
+ // Sign
+ uint8_t* buffer;
+ uint32_t bufferlen;
+ rv = aToken->Sign(aRpIdHash.Elements(), aRpIdHash.Length(),
+ aClientDataHash.Elements(), aClientDataHash.Length(),
+ allowedCredential.Elements(), allowedCredential.Length(),
+ &buffer, &bufferlen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ MOZ_ASSERT(buffer);
+ CryptoBuffer signatureData;
+ if (NS_WARN_IF(!signatureData.Assign(buffer, bufferlen))) {
+ free(buffer);
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ free(buffer);
+
+ // 4.1.2.8.d If any authenticator returns success:
+
+ // 4.1.2.8.d.1 Remove this authenticator’s entry from issuedRequests.
+
+ // 4.1.2.8.d.2 Create a new WebAuthnAssertion object named value and
+ // populate its fields with the values returned from the authenticator as
+ // well as the clientDataJSON computed earlier.
+
+ CryptoBuffer clientDataBuf;
+ if (!clientDataBuf.Assign(aClientData)) {
+ aRequest->SetFailure(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ CryptoBuffer authenticatorDataBuf;
+ rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, aRpIdHash,
+ signatureData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->SetFailure(rv);
+ return;
+ }
+
+ RefPtr<ScopedCredential> credential = new ScopedCredential(this);
+ credential->SetType(ScopedCredentialType::ScopedCred);
+ credential->SetId(allowedCredential);
+
+ AssertionPtr assertion = new WebAuthnAssertion(this);
+ assertion->SetCredential(credential);
+ assertion->SetClientData(clientDataBuf);
+ assertion->SetAuthenticatorData(authenticatorDataBuf);
+ assertion->SetSignature(signatureData);
+
+ // 4.1.2.8.d.3 For each remaining entry in issuedRequests invoke the
+ // authenticatorCancel operation on that authenticator and remove its entry
+ // from the list.
+
+ // 4.1.2.8.d.4 Resolve promise with value and terminate this algorithm.
+ aRequest->SetSuccess(assertion);
+ return;
+ }
+
+ // 4.1.2.9 Reject promise with a DOMException whose name is "NotAllowedError",
+ // and terminate this algorithm.
+ aRequest->SetFailure(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+}
+
+nsresult
+WebAuthentication::RelaxSameOrigin(const nsAString& aInputRpId,
+ /* out */ nsACString& aRelaxedRpId)
+{
+ MOZ_ASSERT(mParent);
+ nsCOMPtr<nsIDocument> document = mParent->GetDoc();
+ if (!document || !document->IsHTMLDocument()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // TODO: Bug 1329764: Invoke the Relax Algorithm, once properly defined
+ aRelaxedRpId.Assign(NS_ConvertUTF16toUTF8(aInputRpId));
+ return NS_OK;
+}
+
+already_AddRefed<Promise>
+WebAuthentication::MakeCredential(JSContext* aCx, const Account& aAccount,
+ const Sequence<ScopedCredentialParameters>& aCryptoParameters,
+ const ArrayBufferViewOrArrayBuffer& aChallenge,
+ const ScopedCredentialOptions& aOptions)
+{
+ MOZ_ASSERT(mParent);
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ return nullptr;
+ }
+
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(global, rv);
+
+ nsresult initRv = InitLazily();
+ if (NS_FAILED(initRv)) {
+ promise->MaybeReject(initRv);
+ return promise.forget();
+ }
+
+ // 4.1.1.1 If timeoutSeconds was specified, check if its value lies within a
+ // reasonable range as defined by the platform and if not, correct it to the
+ // closest value lying within that range.
+
+ double adjustedTimeout = 30.0;
+ if (aOptions.mTimeoutSeconds.WasPassed()) {
+ adjustedTimeout = aOptions.mTimeoutSeconds.Value();
+ adjustedTimeout = std::max(15.0, adjustedTimeout);
+ adjustedTimeout = std::min(120.0, adjustedTimeout);
+ }
+
+ // 4.1.1.2 Let promise be a new Promise. Return promise and start a timer for
+ // adjustedTimeout seconds.
+
+ RefPtr<CredentialRequest> requestMonitor = new CredentialRequest();
+ requestMonitor->SetDeadline(TimeDuration::FromSeconds(adjustedTimeout));
+
+ if (mOrigin.EqualsLiteral("null")) {
+ // 4.1.1.3 If callerOrigin is an opaque origin, reject promise with a
+ // DOMException whose name is "NotAllowedError", and terminate this
+ // algorithm
+ MOZ_LOG(gWebauthLog, LogLevel::Debug, ("Rejecting due to opaque origin"));
+ promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return promise.forget();
+ }
+
+ nsCString rpId;
+ if (!aOptions.mRpId.WasPassed()) {
+ // 4.1.1.3.a If rpId is not specified, then set rpId to callerOrigin, and
+ // rpIdHash to the SHA-256 hash of rpId.
+ rpId.Assign(NS_ConvertUTF16toUTF8(mOrigin));
+ } else {
+ // 4.1.1.3.b If rpId is specified, then invoke the procedure used for
+ // relaxing the same-origin restriction by setting the document.domain
+ // attribute, using rpId as the given value but without changing the current
+ // document’s domain. If no errors are thrown, set rpId to the value of host
+ // as computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
+ // Otherwise, reject promise with a DOMException whose name is
+ // "SecurityError", and terminate this algorithm.
+
+ if (NS_FAILED(RelaxSameOrigin(aOptions.mRpId.Value(), rpId))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+ }
+
+ CryptoBuffer rpIdHash;
+ if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
+ promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return promise.forget();
+ }
+
+ nsresult srv;
+ nsCOMPtr<nsICryptoHash> hashService =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ srv = HashCString(hashService, rpId, rpIdHash);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // 4.1.1.4 Process each element of cryptoParameters using the following steps,
+ // to produce a new sequence normalizedParameters.
+ nsTArray<ScopedCredentialParameters> normalizedParams;
+ for (size_t a = 0; a < aCryptoParameters.Length(); ++a) {
+ // 4.1.1.4.a Let current be the currently selected element of
+ // cryptoParameters.
+
+ // 4.1.1.4.b If current.type does not contain a ScopedCredentialType
+ // supported by this implementation, then stop processing current and move
+ // on to the next element in cryptoParameters.
+ if (aCryptoParameters[a].mType != ScopedCredentialType::ScopedCred) {
+ continue;
+ }
+
+ // 4.1.1.4.c Let normalizedAlgorithm be the result of normalizing an
+ // algorithm using the procedure defined in [WebCryptoAPI], with alg set to
+ // current.algorithm and op set to 'generateKey'. If an error occurs during
+ // this procedure, then stop processing current and move on to the next
+ // element in cryptoParameters.
+
+ nsString algName;
+ if (NS_FAILED(GetAlgorithmName(aCx, aCryptoParameters[a].mAlgorithm,
+ algName))) {
+ continue;
+ }
+
+ // 4.1.1.4.d Add a new object of type ScopedCredentialParameters to
+ // normalizedParameters, with type set to current.type and algorithm set to
+ // normalizedAlgorithm.
+ ScopedCredentialParameters normalizedObj;
+ normalizedObj.mType = aCryptoParameters[a].mType;
+ normalizedObj.mAlgorithm.SetAsString().Assign(algName);
+
+ if (!normalizedParams.AppendElement(normalizedObj, mozilla::fallible)){
+ promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return promise.forget();
+ }
+ }
+
+ // 4.1.1.5 If normalizedAlgorithm is empty and cryptoParameters was not empty,
+ // cancel the timer started in step 2, reject promise with a DOMException
+ // whose name is "NotSupportedError", and terminate this algorithm.
+ if (normalizedParams.IsEmpty() && !aCryptoParameters.IsEmpty()) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return promise.forget();
+ }
+
+ // 4.1.1.6 If excludeList is undefined, set it to the empty list.
+
+ // 4.1.1.7 If extensions was specified, process any extensions supported by
+ // this client platform, to produce the extension data that needs to be sent
+ // to the authenticator. If an error is encountered while processing an
+ // extension, skip that extension and do not produce any extension data for
+ // it. Call the result of this processing clientExtensions.
+
+ // Currently no extensions are supported
+
+ // 4.1.1.8 Use attestationChallenge, callerOrigin and rpId, along with the
+ // token binding key associated with callerOrigin (if any), to create a
+ // ClientData structure representing this request. Choose a hash algorithm for
+ // hashAlg and compute the clientDataJSON and clientDataHash.
+
+ CryptoBuffer challenge;
+ if (!challenge.Assign(aChallenge)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsAutoCString clientDataJSON;
+ srv = AssembleClientData(mOrigin, challenge, clientDataJSON);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ CryptoBuffer clientDataHash;
+ if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ srv = HashCString(hashService, clientDataJSON, clientDataHash);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // 4.1.1.9 Initialize issuedRequests to an empty list.
+ RefPtr<CredentialPromise> monitorPromise = requestMonitor->Ensure();
+
+ // 4.1.1.10 For each authenticator currently available on this platform:
+ // asynchronously invoke the authenticatorMakeCredential operation on that
+ // authenticator with rpIdHash, clientDataHash, accountInformation,
+ // normalizedParameters, excludeList and clientExtensions as parameters. Add a
+ // corresponding entry to issuedRequests.
+ for (Authenticator u2ftoken : mAuthenticators) {
+ // 4.1.1.10.a For each credential C in excludeList that has a non-empty
+ // transports list, optionally use only the specified transports to test for
+ // the existence of C.
+ U2FAuthMakeCredential(requestMonitor, u2ftoken, rpIdHash, clientDataJSON,
+ clientDataHash, aAccount, normalizedParams,
+ aOptions.mExcludeList, aOptions.mExtensions);
+ }
+
+ requestMonitor->CompleteTask();
+
+ monitorPromise->Then(AbstractThread::MainThread(), __func__,
+ [promise] (CredentialPtr aInfo) {
+ promise->MaybeResolve(aInfo);
+ },
+ [promise] (nsresult aErrorCode) {
+ promise->MaybeReject(aErrorCode);
+ });
+
+ return promise.forget();
+}
+
+already_AddRefed<Promise>
+WebAuthentication::GetAssertion(const ArrayBufferViewOrArrayBuffer& aChallenge,
+ const AssertionOptions& aOptions)
+{
+ MOZ_ASSERT(mParent);
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ return nullptr;
+ }
+
+ // 4.1.2.1 If timeoutSeconds was specified, check if its value lies within a
+ // reasonable range as defined by the platform and if not, correct it to the
+ // closest value lying within that range.
+
+ double adjustedTimeout = 30.0;
+ if (aOptions.mTimeoutSeconds.WasPassed()) {
+ adjustedTimeout = aOptions.mTimeoutSeconds.Value();
+ adjustedTimeout = std::max(15.0, adjustedTimeout);
+ adjustedTimeout = std::min(120.0, adjustedTimeout);
+ }
+
+ // 4.1.2.2 Let promise be a new Promise. Return promise and start a timer for
+ // adjustedTimeout seconds.
+
+ RefPtr<AssertionRequest> requestMonitor = new AssertionRequest();
+ requestMonitor->SetDeadline(TimeDuration::FromSeconds(adjustedTimeout));
+
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(global, rv);
+
+ nsresult initRv = InitLazily();
+ if (NS_FAILED(initRv)) {
+ promise->MaybeReject(initRv);
+ return promise.forget();
+ }
+
+ if (mOrigin.EqualsLiteral("null")) {
+ // 4.1.2.3 If callerOrigin is an opaque origin, reject promise with a
+ // DOMException whose name is "NotAllowedError", and terminate this algorithm
+ promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return promise.forget();
+ }
+
+ nsCString rpId;
+ if (!aOptions.mRpId.WasPassed()) {
+ // 4.1.2.3.a If rpId is not specified, then set rpId to callerOrigin, and
+ // rpIdHash to the SHA-256 hash of rpId.
+ rpId.Assign(NS_ConvertUTF16toUTF8(mOrigin));
+ } else {
+ // 4.1.2.3.b If rpId is specified, then invoke the procedure used for
+ // relaxing the same-origin restriction by setting the document.domain
+ // attribute, using rpId as the given value but without changing the current
+ // document’s domain. If no errors are thrown, set rpId to the value of host
+ // as computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
+ // Otherwise, reject promise with a DOMException whose name is
+ // "SecurityError", and terminate this algorithm.
+
+ if (NS_FAILED(RelaxSameOrigin(aOptions.mRpId.Value(), rpId))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+ }
+
+ CryptoBuffer rpIdHash;
+ if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsresult srv;
+ nsCOMPtr<nsICryptoHash> hashService =
+ do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &srv);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ srv = HashCString(hashService, rpId, rpIdHash);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // 4.1.2.4 If extensions was specified, process any extensions supported by
+ // this client platform, to produce the extension data that needs to be sent
+ // to the authenticator. If an error is encountered while processing an
+ // extension, skip that extension and do not produce any extension data for
+ // it. Call the result of this processing clientExtensions.
+
+ // TODO
+
+ // 4.1.2.5 Use assertionChallenge, callerOrigin and rpId, along with the token
+ // binding key associated with callerOrigin (if any), to create a ClientData
+ // structure representing this request. Choose a hash algorithm for hashAlg
+ // and compute the clientDataJSON and clientDataHash.
+ CryptoBuffer challenge;
+ if (!challenge.Assign(aChallenge)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ nsAutoCString clientDataJSON;
+ srv = AssembleClientData(mOrigin, challenge, clientDataJSON);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ CryptoBuffer clientDataHash;
+ if (!clientDataHash.SetLength(SHA256_LENGTH, fallible)) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ srv = HashCString(hashService, clientDataJSON, clientDataHash);
+ if (NS_WARN_IF(NS_FAILED(srv))) {
+ promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
+ return promise.forget();
+ }
+
+ // Note: we only support U2F-style authentication for now, so we effectively
+ // require an AllowList.
+ if (!aOptions.mAllowList.WasPassed()) {
+ promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
+ return promise.forget();
+ }
+
+ const Sequence<ScopedCredentialDescriptor>& allowList =
+ aOptions.mAllowList.Value();
+
+ // 4.1.2.6 Initialize issuedRequests to an empty list.
+ RefPtr<AssertionPromise> monitorPromise = requestMonitor->Ensure();
+
+ // 4.1.2.7 For each authenticator currently available on this platform,
+ // perform the following steps:
+ for(Authenticator u2ftoken : mAuthenticators) {
+ // 4.1.2.7.a If allowList is undefined or empty, let credentialList be an
+ // empty list. Otherwise, execute a platform-specific procedure to determine
+ // which, if any, credentials listed in allowList might be present on this
+ // authenticator, and set credentialList to this filtered list. If no such
+ // filtering is possible, set credentialList to an empty list.
+
+ nsTArray<CryptoBuffer> credentialList;
+
+ for (const ScopedCredentialDescriptor& scd : allowList) {
+ CryptoBuffer buf;
+ if (NS_WARN_IF(!buf.Assign(scd.mId))) {
+ continue;
+ }
+
+ // 4.1.2.7.b For each credential C within the credentialList that has a
+ // non- empty transports list, optionally use only the specified
+ // transports to get assertions using credential C.
+
+ // TODO: Filter using Transport
+ if (!credentialList.AppendElement(buf, mozilla::fallible)) {
+ requestMonitor->CancelNow();
+ promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
+ return promise.forget();
+ }
+ }
+
+ // 4.1.2.7.c If the above filtering process concludes that none of the
+ // credentials on allowList can possibly be on this authenticator, do not
+ // perform any of the following steps for this authenticator, and proceed to
+ // the next authenticator (if any).
+ if (credentialList.IsEmpty()) {
+ continue;
+ }
+
+ // 4.1.2.7.d Asynchronously invoke the authenticatorGetAssertion operation
+ // on this authenticator with rpIdHash, clientDataHash, credentialList, and
+ // clientExtensions as parameters.
+ U2FAuthGetAssertion(requestMonitor, u2ftoken, rpIdHash, clientDataJSON,
+ clientDataHash, credentialList, aOptions.mExtensions);
+ }
+
+ requestMonitor->CompleteTask();
+
+ monitorPromise->Then(AbstractThread::MainThread(), __func__,
+ [promise] (AssertionPtr aAssertion) {
+ promise->MaybeResolve(aAssertion);
+ },
+ [promise] (nsresult aErrorCode) {
+ promise->MaybeReject(aErrorCode);
+ });
+
+ return promise.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/WebAuthentication.h
@@ -0,0 +1,115 @@
+/* -*- 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_WebAuthentication_h
+#define mozilla_dom_WebAuthentication_h
+
+#include "hasht.h"
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/SharedThreadPool.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsNetCID.h"
+#include "nsWrapperCache.h"
+
+#include "U2FAuthenticator.h"
+#include "WebAuthnRequest.h"
+
+namespace mozilla {
+namespace dom {
+
+struct Account;
+class ArrayBufferViewOrArrayBuffer;
+struct AssertionOptions;
+class OwningArrayBufferViewOrArrayBuffer;
+struct ScopedCredentialOptions;
+struct ScopedCredentialParameters;
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+namespace dom {
+
+typedef RefPtr<ScopedCredentialInfo> CredentialPtr;
+typedef RefPtr<WebAuthnAssertion> AssertionPtr;
+typedef WebAuthnRequest<CredentialPtr> CredentialRequest;
+typedef WebAuthnRequest<AssertionPtr> AssertionRequest;
+typedef MozPromise<CredentialPtr, nsresult, false> CredentialPromise;
+typedef MozPromise<AssertionPtr, nsresult, false> AssertionPromise;
+
+class WebAuthentication final : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthentication)
+
+public:
+ explicit WebAuthentication(nsPIDOMWindowInner* aParent);
+
+protected:
+ ~WebAuthentication();
+
+public:
+ nsPIDOMWindowInner*
+ GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<Promise>
+ MakeCredential(JSContext* aCx, const Account& accountInformation,
+ const Sequence<ScopedCredentialParameters>& cryptoParameters,
+ const ArrayBufferViewOrArrayBuffer& attestationChallenge,
+ const ScopedCredentialOptions& options);
+
+ already_AddRefed<Promise>
+ GetAssertion(const ArrayBufferViewOrArrayBuffer& assertionChallenge,
+ const AssertionOptions& options);
+
+private:
+ nsresult
+ InitLazily();
+
+ void
+ U2FAuthMakeCredential(const RefPtr<CredentialRequest>& aRequest,
+ const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+ const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+ const Account& aAccount,
+ const nsTArray<ScopedCredentialParameters>& aNormalizedParams,
+ const Optional<Sequence<ScopedCredentialDescriptor>>& aExcludeList,
+ const WebAuthnExtensions& aExtensions);
+ void
+ U2FAuthGetAssertion(const RefPtr<AssertionRequest>& aRequest,
+ const Authenticator& aToken, CryptoBuffer& aRpIdHash,
+ const nsACString& aClientData, CryptoBuffer& aClientDataHash,
+ nsTArray<CryptoBuffer>& aAllowList,
+ const WebAuthnExtensions& aExtensions);
+
+ nsresult
+ RelaxSameOrigin(const nsAString& aInputRpId, nsACString& aRelaxedRpId);
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+ nsString mOrigin;
+ Sequence<Authenticator> mAuthenticators;
+ bool mInitialized;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthentication_h
rename from dom/u2f/WebAuthnAssertion.cpp
rename to dom/webauthn/WebAuthnAssertion.cpp
rename from dom/u2f/WebAuthnAssertion.h
rename to dom/webauthn/WebAuthnAssertion.h
rename from dom/u2f/WebAuthnAttestation.cpp
rename to dom/webauthn/WebAuthnAttestation.cpp
rename from dom/u2f/WebAuthnAttestation.h
rename to dom/webauthn/WebAuthnAttestation.h
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/WebAuthnRequest.h
@@ -0,0 +1,137 @@
+/* -*- 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_WebAuthnAsync_h
+#define mozilla_dom_WebAuthnAsync_h
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+namespace dom {
+
+//extern mozilla::LazyLogModule gWebauthLog; // defined in U2F.cpp
+
+// WebAuthnRequest tracks the completion of a single WebAuthn request that
+// may run on multiple kinds of authenticators, and be subject to a deadline.
+template<class Success>
+class WebAuthnRequest {
+public:
+ WebAuthnRequest()
+ : mCancelled(false)
+ , mSuccess(false)
+ , mCountTokens(0)
+ , mTokensFailed(0)
+ , mReentrantMonitor("WebAuthnRequest")
+ {}
+
+ void AddActiveToken(const char* aCallSite)
+ {
+ // MOZ_LOG(gWebauthLog, LogLevel::Debug,
+ // ("WebAuthnRequest is tracking a new token, called from [%s]",
+ // aCallSite));
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ MOZ_ASSERT(!IsComplete());
+ mCountTokens += 1;
+ }
+
+ bool IsComplete()
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ return mCancelled || mSuccess ||
+ (mCountTokens > 0 && mTokensFailed == mCountTokens);
+ }
+
+ void CancelNow()
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // It's possible for a race to cause CancelNow to get called after
+ // a success or a cancel. We only complete once.
+ if (IsComplete()) {
+ return;
+ }
+
+ mCancelled = true;
+ mPromise.Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
+ }
+
+ void SetFailure(nsresult aError)
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // It's possible for a race to cause SetFailure to get called after
+ // a success or a cancel. We only complete once.
+ if (IsComplete()) {
+ return;
+ }
+
+ mTokensFailed += 1;
+ MOZ_ASSERT(mTokensFailed <= mCountTokens);
+
+ if (mTokensFailed == mCountTokens) {
+ // Provide the final error as being indicitive of the whole set.
+ mPromise.Reject(aError, __func__);
+ }
+ }
+
+ void SetSuccess(const Success& aResult)
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ // It's possible for a race to cause multiple calls to SetSuccess
+ // in succession. We will only select the earliest.
+ if (IsComplete()) {
+ return;
+ }
+
+ mSuccess = true;
+ mPromise.Resolve(aResult, __func__);
+ }
+
+ void SetDeadline(TimeDuration aDeadline)
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ MOZ_ASSERT(!IsComplete());
+ // TODO: Monitor the deadline and stop with a timeout error if it expires.
+ }
+
+ RefPtr<MozPromise<Success, nsresult, false>> Ensure()
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+ MOZ_ASSERT(!IsComplete());
+ return mPromise.Ensure(__func__);
+ }
+
+ void CompleteTask()
+ {
+ ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+ if (mCountTokens == 0) {
+ // Special case for there being no tasks to complete
+ mPromise.Reject(NS_ERROR_DOM_NOT_ALLOWED_ERR, __func__);
+ }
+ }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebAuthnRequest)
+
+private:
+ ~WebAuthnRequest() {};
+
+ bool mCancelled;
+ bool mSuccess;
+ int mCountTokens;
+ int mTokensFailed;
+ ReentrantMonitor mReentrantMonitor;
+ MozPromiseHolder<MozPromise<Success, nsresult, false>> mPromise;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_WebAuthnAsync_h
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/moz.build
@@ -0,0 +1,38 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ 'NSSU2FTokenRemote.h',
+ 'ScopedCredential.h',
+ 'ScopedCredentialInfo.h',
+ 'WebAuthentication.h',
+ 'WebAuthnAssertion.h',
+ 'WebAuthnAttestation.h',
+ 'WebAuthnRequest.h',
+]
+
+UNIFIED_SOURCES += [
+ 'NSSU2FTokenRemote.cpp',
+ 'ScopedCredential.cpp',
+ 'ScopedCredentialInfo.cpp',
+ 'WebAuthentication.cpp',
+ 'WebAuthnAssertion.cpp',
+ 'WebAuthnAttestation.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/dom/crypto',
+ '/security/manager/ssl',
+ '/security/pkix/include',
+ '/security/pkix/lib',
+]
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/tests/mochitest.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+[test_webauthn_loopback.html]
+skip-if = !e10s
+scheme = https
+[test_webauthn_no_token.html]
+skip-if = !e10s
+scheme = https
+[test_webauthn_make_credential.html]
+skip-if = !e10s
+scheme = https
+[test_webauthn_get_assertion.html]
+skip-if = !e10s
+scheme = https
+[test_webauthn_sameorigin.html]
+skip-if = !e10s
+scheme = https
\ No newline at end of file
rename from dom/u2f/tests/test_webauthn_get_assertion.html
rename to dom/webauthn/tests/test_webauthn_get_assertion.html
rename from dom/u2f/tests/test_webauthn_loopback.html
rename to dom/webauthn/tests/test_webauthn_loopback.html
rename from dom/u2f/tests/test_webauthn_make_credential.html
rename to dom/webauthn/tests/test_webauthn_make_credential.html
rename from dom/u2f/tests/test_webauthn_no_token.html
rename to dom/webauthn/tests/test_webauthn_no_token.html
rename from dom/u2f/tests/test_webauthn_sameorigin.html
rename to dom/webauthn/tests/test_webauthn_sameorigin.html