/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "hasht.h" #include "nsICryptoHash.h" #include "nsNetCID.h" #include "nsThreadUtils.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/AuthenticatorAttestationResponse.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PWebAuthnTransaction.h" #include "mozilla/dom/U2FUtil.h" #include "mozilla/dom/WebAuthnCBORUtil.h" #include "mozilla/dom/WebAuthnManager.h" #include "mozilla/dom/WebAuthnTransactionChild.h" #include "mozilla/dom/WebAuthnUtil.h" #include "mozilla/dom/WebCryptoCommon.h" #include "mozilla/ipc/BackgroundChild.h" #include "mozilla/ipc/PBackgroundChild.h" using namespace mozilla::ipc; namespace mozilla { namespace dom { /*********************************************************************** * Protocol Constants **********************************************************************/ const uint8_t FLAG_TUP = 0x01; // Test of User Presence required const uint8_t FLAG_AT = 0x40; // Authenticator Data is provided /*********************************************************************** * Statics **********************************************************************/ namespace { StaticRefPtr gWebAuthnManager; static mozilla::LazyLogModule gWebAuthnManagerLog("webauthnmanager"); } NS_NAMED_LITERAL_STRING(kVisibilityChange, "visibilitychange"); NS_IMPL_ISUPPORTS(WebAuthnManager, nsIIPCBackgroundChildCreateCallback, nsIDOMEventListener); /*********************************************************************** * Utility Functions **********************************************************************/ template static nsresult GetAlgorithmName(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 } // Only ES256 is currently supported if (NORMALIZED_EQUALS(aName, JWK_ALG_ECDSA_P_256)) { aName.AssignLiteral(JWK_ALG_ECDSA_P_256); } else { return NS_ERROR_DOM_SYNTAX_ERR; } return NS_OK; } 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; } CollectedClientData clientDataObject; clientDataObject.mChallenge.Assign(challengeBase64); clientDataObject.mOrigin.Assign(aOrigin); clientDataObject.mHashAlg.Assign(NS_LITERAL_STRING("SHA-256")); nsAutoString temp; if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) { return NS_ERROR_FAILURE; } aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp)); return NS_OK; } nsresult GetOrigin(nsPIDOMWindowInner* aParent, /*out*/ nsAString& aOrigin, /*out*/ nsACString& aHost) { MOZ_ASSERT(aParent); nsCOMPtr doc = aParent->GetDoc(); MOZ_ASSERT(doc); nsCOMPtr principal = doc->NodePrincipal(); nsresult rv = nsContentUtils::GetUTFOrigin(principal, aOrigin); if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(aOrigin.IsEmpty())) { return NS_ERROR_FAILURE; } if (aOrigin.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(gWebAuthnManagerLog, LogLevel::Debug, ("Rejecting due to opaque origin")); return NS_ERROR_DOM_NOT_ALLOWED_ERR; } nsCOMPtr originUri; if (NS_FAILED(principal->GetURI(getter_AddRefs(originUri)))) { return NS_ERROR_FAILURE; } if (NS_FAILED(originUri->GetAsciiHost(aHost))) { return NS_ERROR_FAILURE; } return NS_OK; } nsresult RelaxSameOrigin(nsPIDOMWindowInner* aParent, const nsAString& aInputRpId, /* out */ nsACString& aRelaxedRpId) { MOZ_ASSERT(aParent); nsCOMPtr doc = aParent->GetDoc(); MOZ_ASSERT(doc); nsCOMPtr principal = doc->NodePrincipal(); nsCOMPtr uri; if (NS_FAILED(principal->GetURI(getter_AddRefs(uri)))) { return NS_ERROR_FAILURE; } nsAutoCString originHost; if (NS_FAILED(uri->GetAsciiHost(originHost))) { return NS_ERROR_FAILURE; } nsCOMPtr document = aParent->GetDoc(); if (!document || !document->IsHTMLDocument()) { return NS_ERROR_FAILURE; } nsHTMLDocument* html = document->AsHTMLDocument(); if (NS_WARN_IF(!html)) { return NS_ERROR_FAILURE; } if (!html->IsRegistrableDomainSuffixOfOrEqualTo(aInputRpId, originHost)) { return NS_ERROR_DOM_SECURITY_ERR; } aRelaxedRpId.Assign(NS_ConvertUTF16toUTF8(aInputRpId)); return NS_OK; } static void ListenForVisibilityEvents(nsPIDOMWindowInner* aParent, WebAuthnManager* aListener) { MOZ_ASSERT(aParent); MOZ_ASSERT(aListener); nsCOMPtr doc = aParent->GetExtantDoc(); if (NS_WARN_IF(!doc)) { return; } nsresult rv = doc->AddSystemEventListener(kVisibilityChange, aListener, /* use capture */ true, /* wants untrusted */ false); Unused << NS_WARN_IF(NS_FAILED(rv)); } static void StopListeningForVisibilityEvents(nsPIDOMWindowInner* aParent, WebAuthnManager* aListener) { MOZ_ASSERT(aParent); MOZ_ASSERT(aListener); nsCOMPtr doc = aParent->GetExtantDoc(); if (NS_WARN_IF(!doc)) { return; } nsresult rv = doc->RemoveSystemEventListener(kVisibilityChange, aListener, /* use capture */ true); Unused << NS_WARN_IF(NS_FAILED(rv)); } /*********************************************************************** * WebAuthnManager Implementation **********************************************************************/ WebAuthnManager::WebAuthnManager() { MOZ_ASSERT(NS_IsMainThread()); } void WebAuthnManager::MaybeClearTransaction() { mClientData.reset(); mInfo.reset(); mTransactionPromise = nullptr; if (mCurrentParent) { StopListeningForVisibilityEvents(mCurrentParent, this); mCurrentParent = nullptr; } if (mChild) { RefPtr c; mChild.swap(c); c->Send__delete__(c); } } WebAuthnManager::~WebAuthnManager() { MaybeClearTransaction(); } RefPtr WebAuthnManager::GetOrCreateBackgroundActor() { MOZ_ASSERT(NS_IsMainThread()); PBackgroundChild *actor = BackgroundChild::GetForCurrentThread(); RefPtr promise = mPBackgroundCreationPromise.Ensure(__func__); if (actor) { ActorCreated(actor); } else { bool ok = BackgroundChild::GetOrCreateForCurrentThread(this); if (NS_WARN_IF(!ok)) { ActorFailed(); } } return promise; } //static WebAuthnManager* WebAuthnManager::GetOrCreate() { MOZ_ASSERT(NS_IsMainThread()); if (gWebAuthnManager) { return gWebAuthnManager; } gWebAuthnManager = new WebAuthnManager(); ClearOnShutdown(&gWebAuthnManager); return gWebAuthnManager; } //static WebAuthnManager* WebAuthnManager::Get() { MOZ_ASSERT(NS_IsMainThread()); return gWebAuthnManager; } already_AddRefed WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent, const MakeCredentialOptions& aOptions) { MOZ_ASSERT(aParent); MaybeClearTransaction(); nsCOMPtr global = do_QueryInterface(aParent); ErrorResult rv; RefPtr promise = Promise::Create(global, rv); if(rv.Failed()) { return nullptr; } nsString origin; nsCString rpId; rv = GetOrigin(aParent, origin, rpId); if (NS_WARN_IF(rv.Failed())) { promise->MaybeReject(rv); return promise.forget(); } // 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. uint32_t adjustedTimeout = 30000; if (aOptions.mTimeout.WasPassed()) { adjustedTimeout = aOptions.mTimeout.Value(); adjustedTimeout = std::max(15000u, adjustedTimeout); adjustedTimeout = std::min(120000u, adjustedTimeout); } if (aOptions.mRp.mId.WasPassed()) { // 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(aParent, aOptions.mRp.mId.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 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(); } // Process each element of cryptoParameters using the following steps, to // produce a new sequence normalizedParameters. nsTArray normalizedParams; for (size_t a = 0; a < aOptions.mParameters.Length(); ++a) { // Let current be the currently selected element of // cryptoParameters. // If current.type does not contain a PublicKeyCredentialType // supported by this implementation, then stop processing current and move // on to the next element in cryptoParameters. if (aOptions.mParameters[a].mType != PublicKeyCredentialType::Public_key) { continue; } // 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(aOptions.mParameters[a].mAlgorithm, algName))) { continue; } // Add a new object of type PublicKeyCredentialParameters to // normalizedParameters, with type set to current.type and algorithm set to // normalizedAlgorithm. PublicKeyCredentialParameters normalizedObj; normalizedObj.mType = aOptions.mParameters[a].mType; normalizedObj.mAlgorithm.SetAsString().Assign(algName); if (!normalizedParams.AppendElement(normalizedObj, mozilla::fallible)){ promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY); return promise.forget(); } } // 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() && !aOptions.mParameters.IsEmpty()) { promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return promise.forget(); } // TODO: The following check should not be here. This is checking for // parameters specific to the soft key, and should be put in the soft key // manager in the parent process. Still need to serialize // PublicKeyCredentialParameters first. // Check if at least one of the specified combinations of // PublicKeyCredentialParameters 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 < normalizedParams.Length(); ++a) { if (normalizedParams[a].mType == PublicKeyCredentialType::Public_key && normalizedParams[a].mAlgorithm.IsString() && normalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral( JWK_ALG_ECDSA_P_256)) { isValidCombination = true; break; } } if (!isValidCombination) { promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return promise.forget(); } // If excludeList is undefined, set it to the empty list. // // 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 // // 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(aOptions.mChallenge)) { promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); return promise.forget(); } nsAutoCString clientDataJSON; srv = AssembleClientData(origin, 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(); } nsTArray excludeList; if (aOptions.mExcludeList.WasPassed()) { for (const auto& s: aOptions.mExcludeList.Value()) { WebAuthnScopedCredentialDescriptor c; CryptoBuffer cb; cb.Assign(s.mId); c.id() = cb; excludeList.AppendElement(c); } } // TODO: Add extension list building nsTArray extensions; WebAuthnTransactionInfo info(rpIdHash, clientDataHash, adjustedTimeout, excludeList, extensions); RefPtr> p = GetOrCreateBackgroundActor(); p->Then(GetMainThreadSerialEventTarget(), __func__, []() { WebAuthnManager* mgr = WebAuthnManager::Get(); if (!mgr) { return; } mgr->StartRegister(); }, []() { // This case can't actually happen, we'll have crashed if the child // failed to create. }); mTransactionPromise = promise; mClientData = Some(clientDataJSON); mCurrentParent = aParent; mInfo = Some(info); ListenForVisibilityEvents(aParent, this); return promise.forget(); } void WebAuthnManager::StartRegister() { if (mChild) { mChild->SendRequestRegister(mInfo.ref()); } } void WebAuthnManager::StartSign() { if (mChild) { mChild->SendRequestSign(mInfo.ref()); } } void WebAuthnManager::StartCancel() { if (mChild) { mChild->SendRequestCancel(); } } already_AddRefed WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent, const PublicKeyCredentialRequestOptions& aOptions) { MOZ_ASSERT(aParent); MaybeClearTransaction(); nsCOMPtr global = do_QueryInterface(aParent); ErrorResult rv; RefPtr promise = Promise::Create(global, rv); if(rv.Failed()) { return nullptr; } nsString origin; nsCString rpId; rv = GetOrigin(aParent, origin, rpId); if (NS_WARN_IF(rv.Failed())) { promise->MaybeReject(rv); return promise.forget(); } // 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. uint32_t adjustedTimeout = 30000; if (aOptions.mTimeout.WasPassed()) { adjustedTimeout = aOptions.mTimeout.Value(); adjustedTimeout = std::max(15000u, adjustedTimeout); adjustedTimeout = std::min(120000u, adjustedTimeout); } if (aOptions.mRpId.WasPassed()) { // 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(aParent, 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 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(); } // 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(aOptions.mChallenge)) { promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR); return promise.forget(); } nsAutoCString clientDataJSON; srv = AssembleClientData(origin, 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.Length() < 1) { promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR); return promise.forget(); } nsTArray allowList; for (const auto& s: aOptions.mAllowList) { WebAuthnScopedCredentialDescriptor c; CryptoBuffer cb; cb.Assign(s.mId); c.id() = cb; allowList.AppendElement(c); } // TODO: Add extension list building // 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. nsTArray extensions; WebAuthnTransactionInfo info(rpIdHash, clientDataHash, adjustedTimeout, allowList, extensions); RefPtr> p = GetOrCreateBackgroundActor(); p->Then(GetMainThreadSerialEventTarget(), __func__, []() { WebAuthnManager* mgr = WebAuthnManager::Get(); if (!mgr) { return; } mgr->StartSign(); }, []() { // This case can't actually happen, we'll have crashed if the child // failed to create. }); // Only store off the promise if we've succeeded in sending the IPC event. mTransactionPromise = promise; mClientData = Some(clientDataJSON); mCurrentParent = aParent; mInfo = Some(info); ListenForVisibilityEvents(aParent, this); return promise.forget(); } void WebAuthnManager::FinishMakeCredential(nsTArray& aRegBuffer) { MOZ_ASSERT(mTransactionPromise); MOZ_ASSERT(mInfo.isSome()); CryptoBuffer regData; if (NS_WARN_IF(!regData.Assign(aRegBuffer.Elements(), aRegBuffer.Length()))) { Cancel(NS_ERROR_OUT_OF_MEMORY); return; } mozilla::dom::CryptoBuffer aaguidBuf; if (NS_WARN_IF(!aaguidBuf.SetCapacity(16, mozilla::fallible))) { Cancel(NS_ERROR_OUT_OF_MEMORY); return; } // TODO: Adjust the AAGUID from all zeroes in Bug 1381575 (if needed) // See https://github.com/w3c/webauthn/issues/506 for (int i=0; i<16; i++) { aaguidBuf.AppendElement(0x00, mozilla::fallible); } // Decompose the U2F registration packet CryptoBuffer pubKeyBuf; CryptoBuffer keyHandleBuf; CryptoBuffer attestationCertBuf; CryptoBuffer signatureBuf; // Only handles attestation cert chains of length=1. nsresult rv = U2FDecomposeRegistrationResponse(regData, pubKeyBuf, keyHandleBuf, attestationCertBuf, signatureBuf); if (NS_WARN_IF(NS_FAILED(rv))) { Cancel(rv); return; } MOZ_ASSERT(keyHandleBuf.Length() <= 0xFFFF); nsAutoString keyHandleBase64Url; rv = keyHandleBuf.ToJwkBase64(keyHandleBase64Url); if (NS_WARN_IF(NS_FAILED(rv))) { Cancel(rv); return; } CryptoBuffer clientDataBuf; if (!clientDataBuf.Assign(mClientData.ref())) { Cancel(NS_ERROR_OUT_OF_MEMORY); return; } CryptoBuffer rpIdHashBuf; if (!rpIdHashBuf.Assign(mInfo.ref().RpIdHash())) { Cancel(NS_ERROR_OUT_OF_MEMORY); return; } // Construct the public key object CryptoBuffer pubKeyObj; rv = CBOREncodePublicKeyObj(pubKeyBuf, pubKeyObj); if (NS_FAILED(rv)) { Cancel(rv); return; } // During create credential, counter is always 0 for U2F // See https://github.com/w3c/webauthn/issues/507 mozilla::dom::CryptoBuffer counterBuf; if (NS_WARN_IF(!counterBuf.SetCapacity(4, mozilla::fallible))) { Cancel(NS_ERROR_OUT_OF_MEMORY); return; } counterBuf.AppendElement(0x00, mozilla::fallible); counterBuf.AppendElement(0x00, mozilla::fallible); counterBuf.AppendElement(0x00, mozilla::fallible); counterBuf.AppendElement(0x00, mozilla::fallible); // Construct the Attestation Data, which slots into the end of the // Authentication Data buffer. CryptoBuffer attDataBuf; rv = AssembleAttestationData(aaguidBuf, keyHandleBuf, pubKeyObj, attDataBuf); if (NS_FAILED(rv)) { Cancel(rv); return; } mozilla::dom::CryptoBuffer authDataBuf; rv = AssembleAuthenticatorData(rpIdHashBuf, FLAG_TUP, counterBuf, attDataBuf, authDataBuf); if (NS_FAILED(rv)) { Cancel(rv); return; } // The Authentication Data buffer gets CBOR-encoded with the Cert and // Signature to build the Attestation Object. CryptoBuffer attObj; rv = CBOREncodeAttestationObj(authDataBuf, attestationCertBuf, signatureBuf, attObj); if (NS_FAILED(rv)) { Cancel(rv); return; } // Create a new PublicKeyCredential object and populate its fields with the // values returned from the authenticator as well as the clientDataJSON // computed earlier. RefPtr attestation = new AuthenticatorAttestationResponse(mCurrentParent); attestation->SetClientDataJSON(clientDataBuf); attestation->SetAttestationObject(attObj); RefPtr credential = new PublicKeyCredential(mCurrentParent); credential->SetId(keyHandleBase64Url); credential->SetType(NS_LITERAL_STRING("public-key")); credential->SetRawId(keyHandleBuf); credential->SetResponse(attestation); mTransactionPromise->MaybeResolve(credential); MaybeClearTransaction(); } void WebAuthnManager::FinishGetAssertion(nsTArray& aCredentialId, nsTArray& aSigBuffer) { MOZ_ASSERT(mTransactionPromise); MOZ_ASSERT(mInfo.isSome()); CryptoBuffer tokenSignatureData; if (NS_WARN_IF(!tokenSignatureData.Assign(aSigBuffer.Elements(), aSigBuffer.Length()))) { Cancel(NS_ERROR_OUT_OF_MEMORY); return; } CryptoBuffer clientDataBuf; if (!clientDataBuf.Assign(mClientData.ref())) { Cancel(NS_ERROR_OUT_OF_MEMORY); return; } CryptoBuffer rpIdHashBuf; if (!rpIdHashBuf.Assign(mInfo.ref().RpIdHash())) { Cancel(NS_ERROR_OUT_OF_MEMORY); return; } CryptoBuffer signatureBuf; CryptoBuffer counterBuf; uint8_t flags = 0; nsresult rv = U2FDecomposeSignResponse(tokenSignatureData, flags, counterBuf, signatureBuf); if (NS_WARN_IF(NS_FAILED(rv))) { Cancel(rv); return; } CryptoBuffer attestationDataBuf; CryptoBuffer authenticatorDataBuf; rv = AssembleAuthenticatorData(rpIdHashBuf, FLAG_TUP, counterBuf, /* deliberately empty */ attestationDataBuf, authenticatorDataBuf); if (NS_WARN_IF(NS_FAILED(rv))) { Cancel(rv); return; } CryptoBuffer credentialBuf; if (!credentialBuf.Assign(aCredentialId)) { Cancel(NS_ERROR_OUT_OF_MEMORY); return; } nsAutoString credentialBase64Url; rv = credentialBuf.ToJwkBase64(credentialBase64Url); if (NS_WARN_IF(NS_FAILED(rv))) { Cancel(rv); return; } // If any authenticator returns success: // Create a new PublicKeyCredential object named value and populate its fields // with the values returned from the authenticator as well as the // clientDataJSON computed earlier. RefPtr assertion = new AuthenticatorAssertionResponse(mCurrentParent); assertion->SetClientDataJSON(clientDataBuf); assertion->SetAuthenticatorData(authenticatorDataBuf); assertion->SetSignature(signatureBuf); RefPtr credential = new PublicKeyCredential(mCurrentParent); credential->SetId(credentialBase64Url); credential->SetType(NS_LITERAL_STRING("public-key")); credential->SetRawId(credentialBuf); credential->SetResponse(assertion); mTransactionPromise->MaybeResolve(credential); MaybeClearTransaction(); } void WebAuthnManager::Cancel(const nsresult& aError) { MOZ_ASSERT(NS_IsMainThread()); if (mTransactionPromise) { mTransactionPromise->MaybeReject(aError); } MaybeClearTransaction(); } NS_IMETHODIMP WebAuthnManager::HandleEvent(nsIDOMEvent* aEvent) { MOZ_ASSERT(aEvent); nsAutoString type; aEvent->GetType(type); if (!type.Equals(kVisibilityChange)) { return NS_ERROR_FAILURE; } nsCOMPtr doc = do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget()); MOZ_ASSERT(doc); if (doc && doc->Hidden()) { MOZ_LOG(gWebAuthnManagerLog, LogLevel::Debug, ("Visibility change: WebAuthn window is hidden, cancelling job.")); StartCancel(); Cancel(NS_ERROR_ABORT); } return NS_OK; } void WebAuthnManager::ActorCreated(PBackgroundChild* aActor) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aActor); if (mChild) { return; } RefPtr mgr(new WebAuthnTransactionChild()); PWebAuthnTransactionChild* constructedMgr = aActor->SendPWebAuthnTransactionConstructor(mgr); if (NS_WARN_IF(!constructedMgr)) { ActorFailed(); return; } MOZ_ASSERT(constructedMgr == mgr); mChild = mgr.forget(); mPBackgroundCreationPromise.Resolve(NS_OK, __func__); } void WebAuthnManager::ActorDestroyed() { mChild = nullptr; } void WebAuthnManager::ActorFailed() { MOZ_CRASH("We shouldn't be here!"); } } }