diff options
Diffstat (limited to 'dom/webauthn/CTAPHIDTokenManager.cpp')
-rw-r--r-- | dom/webauthn/CTAPHIDTokenManager.cpp | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/dom/webauthn/CTAPHIDTokenManager.cpp b/dom/webauthn/CTAPHIDTokenManager.cpp new file mode 100644 index 0000000000..7739d3deb0 --- /dev/null +++ b/dom/webauthn/CTAPHIDTokenManager.cpp @@ -0,0 +1,638 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "WebAuthnCoseIdentifiers.h" +#include "mozilla/dom/CTAPHIDTokenManager.h" +#include "mozilla/dom/U2FHIDTokenManager.h" +#include "mozilla/dom/WebAuthnUtil.h" +#include "mozilla/dom/WebAuthnCBORUtil.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/StaticMutex.h" +#include <iostream> +namespace mozilla::dom { + +static StaticMutex gCTAPMutex; +static CTAPHIDTokenManager* gCTAPInstance; +static nsIThread* gPCTAPBackgroundThread; + +static void ctap1_register_callback(uint64_t aTransactionId, + rust_u2f_result* aResult) { + UniquePtr<CTAPResult> rv = MakeUnique<CTAPResult>(aTransactionId, aResult); + + StaticMutexAutoLock lock(gCTAPMutex); + if (!gCTAPInstance || NS_WARN_IF(!gPCTAPBackgroundThread)) { + return; + } + + nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<CTAPResult>&&>( + "CTAPHIDTokenManager::HandleRegisterResult", gCTAPInstance, + &CTAPHIDTokenManager::HandleRegisterResult, std::move(rv))); + + MOZ_ALWAYS_SUCCEEDS( + gPCTAPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); +} + +static void ctap2_register_callback(uint64_t aTransactionId, + rust_ctap2_register_result* aResult) { + UniquePtr<CTAPResult> rv = MakeUnique<CTAPResult>(aTransactionId, aResult); + + StaticMutexAutoLock lock(gCTAPMutex); + if (!gCTAPInstance || NS_WARN_IF(!gPCTAPBackgroundThread)) { + return; + } + + nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<CTAPResult>&&>( + "CTAPHIDTokenManager::HandleRegisterResult", gCTAPInstance, + &CTAPHIDTokenManager::HandleRegisterResult, std::move(rv))); + + MOZ_ALWAYS_SUCCEEDS( + gPCTAPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); +} + +static void ctap1_sign_callback(uint64_t aTransactionId, + rust_u2f_result* aResult) { + UniquePtr<CTAPResult> rv = MakeUnique<CTAPResult>(aTransactionId, aResult); + + StaticMutexAutoLock lock(gCTAPMutex); + if (!gCTAPInstance || NS_WARN_IF(!gPCTAPBackgroundThread)) { + return; + } + + nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<CTAPResult>&&>( + "CTAPHIDTokenManager::HandleSignResult", gCTAPInstance, + &CTAPHIDTokenManager::HandleSignResult, std::move(rv))); + + MOZ_ALWAYS_SUCCEEDS( + gPCTAPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); +} + +static void ctap2_sign_callback(uint64_t aTransactionId, + rust_ctap2_sign_result* aResult) { + UniquePtr<CTAPResult> rv = MakeUnique<CTAPResult>(aTransactionId, aResult); + + StaticMutexAutoLock lock(gCTAPMutex); + if (!gCTAPInstance || NS_WARN_IF(!gPCTAPBackgroundThread)) { + return; + } + + nsCOMPtr<nsIRunnable> r(NewRunnableMethod<UniquePtr<CTAPResult>&&>( + "CTAPHIDTokenManager::HandleSignResult", gCTAPInstance, + &CTAPHIDTokenManager::HandleSignResult, std::move(rv))); + + MOZ_ALWAYS_SUCCEEDS( + gPCTAPBackgroundThread->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); +} + +CTAPHIDTokenManager::CTAPHIDTokenManager() { + StaticMutexAutoLock lock(gCTAPMutex); + mozilla::ipc::AssertIsOnBackgroundThread(); + MOZ_ASSERT(XRE_IsParentProcess()); + MOZ_ASSERT(!gCTAPInstance); + + mCTAPManager = rust_ctap2_mgr_new(); + gPCTAPBackgroundThread = NS_GetCurrentThread(); + MOZ_ASSERT(gPCTAPBackgroundThread, "This should never be null!"); + gCTAPInstance = this; +} + +void CTAPHIDTokenManager::Drop() { + { + StaticMutexAutoLock lock(gCTAPMutex); + mozilla::ipc::AssertIsOnBackgroundThread(); + + mRegisterPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + mSignPromise.RejectIfExists(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + + gCTAPInstance = nullptr; + } + + // Release gCTAPMutex before we call CTAPManager::drop(). It will wait + // for the work queue thread to join, and that requires the + // u2f_{register,sign}_callback to lock and return. + rust_ctap2_mgr_free(mCTAPManager); + mCTAPManager = nullptr; + + // Reset transaction ID so that queued runnables exit early. + mTransaction.reset(); +} + +// A CTAP Register operation causes a new key pair to be generated by the token. +// The token then returns the public key of the key pair, and a handle to the +// private key, which is a fancy way of saying "key wrapped private key", as +// well as the generated attestation certificate and a signature using that +// certificate's private key. +// Requests can be either CTAP1 or CTAP2, those will be packaged differently +// and handed over to the Rust lib. +RefPtr<U2FRegisterPromise> CTAPHIDTokenManager::Register( + const WebAuthnMakeCredentialInfo& aInfo, bool aForceNoneAttestation, + void status_callback(rust_ctap2_status_update_res*)) { + mozilla::ipc::AssertIsOnBackgroundThread(); + + uint64_t registerFlags = 0; + bool is_ctap2_request = false; + const uint8_t* user_id = nullptr; + size_t user_id_len = 0; + nsCString user_name; + + if (aInfo.Extra().isSome()) { + const auto& extra = aInfo.Extra().ref(); + const WebAuthnAuthenticatorSelection& sel = extra.AuthenticatorSelection(); + + UserVerificationRequirement userVerificationRequirement = + sel.userVerificationRequirement(); + + bool requireUserVerification = + userVerificationRequirement == UserVerificationRequirement::Required; + + bool requirePlatformAttachment = false; + if (sel.authenticatorAttachment().isSome()) { + const AuthenticatorAttachment authenticatorAttachment = + sel.authenticatorAttachment().value(); + if (authenticatorAttachment == AuthenticatorAttachment::Platform) { + requirePlatformAttachment = true; + } + } + + // Set flags for credential creation. + if (sel.requireResidentKey()) { + registerFlags |= U2F_FLAG_REQUIRE_RESIDENT_KEY; + } + if (requireUserVerification) { + registerFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION; + } + if (requirePlatformAttachment) { + registerFlags |= U2F_FLAG_REQUIRE_PLATFORM_ATTACHMENT; + } + + nsTArray<CoseAlg> coseAlgos; + for (const auto& coseAlg : extra.coseAlgs()) { + switch (static_cast<CoseAlgorithmIdentifier>(coseAlg.alg())) { + case CoseAlgorithmIdentifier::ES256: + coseAlgos.AppendElement(coseAlg); + break; + default: + continue; + } + } + + // Only if no algorithms were specified, default to the only CTAP 1 / U2F + // protocol-supported algorithm. Ultimately this logic must move into + // u2f-hid-rs in a fashion that doesn't break the tests. + if (extra.coseAlgs().IsEmpty()) { + coseAlgos.AppendElement( + static_cast<int32_t>(CoseAlgorithmIdentifier::ES256)); + } + + // If there are no acceptable/supported algorithms, reject the promise. + if (coseAlgos.IsEmpty()) { + return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, + __func__); + } + + user_id_len = extra.User().Id().Length(); + user_id = extra.User().Id().Elements(); + user_name = NS_ConvertUTF16toUTF8(extra.User().DisplayName()); + is_ctap2_request = true; + } + + CryptoBuffer rpIdHash, clientDataHash; + NS_ConvertUTF16toUTF8 rpId(aInfo.RpId()); + nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash, + clientDataHash); + if (NS_WARN_IF(NS_FAILED(rv))) { + return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, + __func__); + } + + ClearPromises(); + mTransaction.reset(); + + const int32_t pub_cred_params = (int32_t) + CoseAlgorithmIdentifier::ES256; // Currently the only supported one + uint64_t tid; + if (is_ctap2_request) { + AuthenticatorArgsUser user = {user_id, user_id_len, user_name.get()}; + AuthenticatorArgsPubCred pub_cred = {&pub_cred_params, 1}; + AuthenticatorArgsChallenge challenge = {aInfo.Challenge().Elements(), + aInfo.Challenge().Length()}; + AuthenticatorArgsOptions options = { + static_cast<bool>(registerFlags & U2F_FLAG_REQUIRE_RESIDENT_KEY), + static_cast<bool>(registerFlags & U2F_FLAG_REQUIRE_USER_VERIFICATION), + true, // user presence + aForceNoneAttestation}; + tid = rust_ctap2_mgr_register( + mCTAPManager, (uint64_t)aInfo.TimeoutMS(), ctap2_register_callback, + status_callback, challenge, rpId.get(), + NS_ConvertUTF16toUTF8(aInfo.Origin()).get(), user, pub_cred, + Ctap2PubKeyCredentialDescriptor(aInfo.ExcludeList()).Get(), options, + nullptr); + } else { + tid = rust_u2f_mgr_register( + mCTAPManager, registerFlags, (uint64_t)aInfo.TimeoutMS(), + ctap1_register_callback, clientDataHash.Elements(), + clientDataHash.Length(), rpIdHash.Elements(), rpIdHash.Length(), + U2FKeyHandles(aInfo.ExcludeList()).Get()); + } + if (tid == 0) { + return U2FRegisterPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, + __func__); + } + + mTransaction = Some(Transaction( + tid, rpIdHash, Nothing(), aInfo.ClientDataJSON(), aForceNoneAttestation)); + + return mRegisterPromise.Ensure(__func__); +} + +// Signing into a webpage. Again, depending on if the request is CTAP1 or +// CTAP2, it will be packaged differently and passed to the Rust lib. +RefPtr<U2FSignPromise> CTAPHIDTokenManager::Sign( + const WebAuthnGetAssertionInfo& aInfo, + void status_callback(rust_ctap2_status_update_res*)) { + mozilla::ipc::AssertIsOnBackgroundThread(); + + bool is_ctap2_request = false; + CryptoBuffer rpIdHash, clientDataHash; + NS_ConvertUTF16toUTF8 rpId(aInfo.RpId()); + nsresult rv = BuildTransactionHashes(rpId, aInfo.ClientDataJSON(), rpIdHash, + clientDataHash); + if (NS_WARN_IF(NS_FAILED(rv))) { + return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + } + + uint64_t signFlags = 0; + nsTArray<nsTArray<uint8_t>> appIds; + appIds.AppendElement(rpIdHash.InfallibleClone()); + + Maybe<nsTArray<uint8_t>> appIdHashExt = Nothing(); + + if (aInfo.Extra().isSome()) { + const auto& extra = aInfo.Extra().ref(); + + UserVerificationRequirement userVerificationReq = + extra.userVerificationRequirement(); + + // Set flags for credential requests. + if (userVerificationReq == UserVerificationRequirement::Required) { + signFlags |= U2F_FLAG_REQUIRE_USER_VERIFICATION; + } + + // Process extensions. + for (const WebAuthnExtension& ext : extra.Extensions()) { + if (ext.type() == WebAuthnExtension::TWebAuthnExtensionAppId) { + appIdHashExt = Some(ext.get_WebAuthnExtensionAppId().AppId().Clone()); + appIds.AppendElement(appIdHashExt->Clone()); + } + } + + is_ctap2_request = true; + } + + ClearPromises(); + mTransaction.reset(); + uint64_t tid; + if (is_ctap2_request) { + AuthenticatorArgsChallenge challenge = {aInfo.Challenge().Elements(), + aInfo.Challenge().Length()}; + AuthenticatorArgsOptions options = { + false, // resident key, not used when signing + static_cast<bool>(signFlags & U2F_FLAG_REQUIRE_USER_VERIFICATION), + true, // user presence + }; + tid = rust_ctap2_mgr_sign( + mCTAPManager, (uint64_t)aInfo.TimeoutMS(), ctap2_sign_callback, + status_callback, challenge, rpId.get(), + NS_ConvertUTF16toUTF8(aInfo.Origin()).get(), + Ctap2PubKeyCredentialDescriptor(aInfo.AllowList()).Get(), options, + nullptr); + } else { + tid = rust_u2f_mgr_sign( + mCTAPManager, signFlags, (uint64_t)aInfo.TimeoutMS(), + ctap1_sign_callback, clientDataHash.Elements(), clientDataHash.Length(), + U2FAppIds(appIds).Get(), U2FKeyHandles(aInfo.AllowList()).Get()); + } + if (tid == 0) { + return U2FSignPromise::CreateAndReject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + } + + mTransaction = Some(Transaction(tid, std::move(rpIdHash), appIdHashExt, + aInfo.ClientDataJSON())); + + return mSignPromise.Ensure(__func__); +} + +void CTAPHIDTokenManager::Cancel() { + mozilla::ipc::AssertIsOnBackgroundThread(); + + ClearPromises(); + rust_u2f_mgr_cancel(mCTAPManager); + mTransaction.reset(); +} + +void CTAPHIDTokenManager::HandleRegisterResult( + UniquePtr<CTAPResult>&& aResult) { + mozilla::ipc::AssertIsOnBackgroundThread(); + + if (mTransaction.isNothing() || + aResult->GetTransactionId() != mTransaction.ref().mId) { + return; + } + + MOZ_ASSERT(!mRegisterPromise.IsEmpty()); + + if (aResult->IsError()) { + mRegisterPromise.Reject(aResult->GetError(), __func__); + return; + } + + if (aResult->IsCtap2()) { + HandleRegisterResultCtap2(std::move(aResult)); + } else { + HandleRegisterResultCtap1(std::move(aResult)); + } +} + +void CTAPHIDTokenManager::HandleRegisterResultCtap1( + UniquePtr<CTAPResult>&& aResult) { + CryptoBuffer regData; + CryptoBuffer pubKeyBuf; + CryptoBuffer keyHandle; + CryptoBuffer attestationCertBuf; + CryptoBuffer signatureBuf; + + nsTArray<uint8_t> registration; + if (!aResult->CopyRegistration(registration)) { + mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + // Decompose the U2F registration packet + + regData.Assign(registration); + + // Only handles attestation cert chains of length=1. + nsresult rv = U2FDecomposeRegistrationResponse( + regData, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf); + if (NS_WARN_IF(NS_FAILED(rv))) { + mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + CryptoBuffer rpIdHashBuf; + if (!rpIdHashBuf.Assign(mTransaction.ref().mRpIdHash)) { + mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + CryptoBuffer attObj; + rv = AssembleAttestationObject( + rpIdHashBuf, pubKeyBuf, keyHandle, attestationCertBuf, signatureBuf, + mTransaction.ref().mForceNoneAttestation, attObj); + if (NS_FAILED(rv)) { + mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + nsTArray<WebAuthnExtensionResult> extensions; + WebAuthnMakeCredentialResult result(mTransaction.ref().mClientDataJSON, + attObj, keyHandle, regData, extensions); + mRegisterPromise.Resolve(std::move(result), __func__); +} + +void CTAPHIDTokenManager::HandleRegisterResultCtap2( + UniquePtr<CTAPResult>&& aResult) { + CryptoBuffer attObj; + + nsTArray<uint8_t> attestation; + if (!aResult->Ctap2CopyAttestation(attestation)) { + mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + if (!attObj.Assign(attestation)) { + mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + // We would have a copy of the client data stored inside mTransaction, + // but we need the one from authenticator-rs, as that data is part of + // the signed payload. If we reorder the JSON-values (e.g. by sorting the + // members alphabetically, as the codegen from IDL does, so we can't use + // that), that would break the signature and lead to a failed authentication + // on the server. So we make sure to take exactly the client data that + // authenticator-rs sent to the token. + nsCString clientData; + if (!aResult->CopyClientDataStr(clientData)) { + mRegisterPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + // Dummy-values. Not used with CTAP2. + nsTArray<WebAuthnExtensionResult> extensions; + CryptoBuffer regData, keyHandle; + WebAuthnMakeCredentialResult result(clientData, attObj, keyHandle, regData, + extensions); + mRegisterPromise.Resolve(std::move(result), __func__); +} + +void CTAPHIDTokenManager::HandleSignResult(UniquePtr<CTAPResult>&& aResult) { + mozilla::ipc::AssertIsOnBackgroundThread(); + + if (mTransaction.isNothing() || + aResult->GetTransactionId() != mTransaction.ref().mId) { + return; + } + + MOZ_ASSERT(!mSignPromise.IsEmpty()); + + if (aResult->IsError()) { + mSignPromise.Reject(aResult->GetError(), __func__); + return; + } + + if (aResult->IsCtap2()) { + HandleSignResultCtap2(std::move(aResult)); + } else { + HandleSignResultCtap1(std::move(aResult)); + } +} + +void CTAPHIDTokenManager::HandleSignResultCtap1( + UniquePtr<CTAPResult>&& aResult) { + nsTArray<uint8_t> hashChosenByAuthenticator; + if (!aResult->CopyAppId(hashChosenByAuthenticator)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + nsTArray<uint8_t> keyHandle; + if (!aResult->CopyKeyHandle(keyHandle)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + nsTArray<uint8_t> signature; + if (!aResult->CopySignature(signature)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + CryptoBuffer rawSignatureBuf; + if (!rawSignatureBuf.Assign(signature)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + nsTArray<WebAuthnExtensionResult> extensions; + + if (mTransaction.ref().mAppIdHash.isSome()) { + bool usedAppId = + (hashChosenByAuthenticator == mTransaction.ref().mAppIdHash.ref()); + extensions.AppendElement(WebAuthnExtensionResultAppId(usedAppId)); + } + + CryptoBuffer signatureBuf; + CryptoBuffer counterBuf; + uint8_t flags = 0; + nsresult rv = U2FDecomposeSignResponse(rawSignatureBuf, flags, counterBuf, + signatureBuf); + if (NS_WARN_IF(NS_FAILED(rv))) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + CryptoBuffer chosenAppIdBuf; + if (!chosenAppIdBuf.Assign(hashChosenByAuthenticator)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + // Preserve the two LSBs of the flags byte, UP and RFU1. + // See <https://github.com/fido-alliance/fido-2-specs/pull/519> + flags &= 0b11; + + CryptoBuffer emptyAttestationData; + CryptoBuffer authenticatorData; + rv = AssembleAuthenticatorData(chosenAppIdBuf, flags, counterBuf, + emptyAttestationData, authenticatorData); + if (NS_WARN_IF(NS_FAILED(rv))) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + + nsTArray<uint8_t> userHandle; + + WebAuthnGetAssertionResult result(mTransaction.ref().mClientDataJSON, + keyHandle, signatureBuf, authenticatorData, + extensions, rawSignatureBuf, userHandle); + nsTArray<WebAuthnGetAssertionResultWrapper> results = { + {result, mozilla::Nothing()}}; + mSignPromise.Resolve(std::move(results), __func__); +} + +void CTAPHIDTokenManager::HandleSignResultCtap2( + UniquePtr<CTAPResult>&& aResult) { + // Have choice here. For discoverable creds, the token + // can return multiple assertions. The user has to choose + // into which account we should sign in. We are getting + // all of them from auth-rs, let the user select one and send + // that back to the server + size_t num_of_results; + if (!aResult->Ctap2GetNumberOfSignAssertions(num_of_results)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return; + } + nsTArray<WebAuthnGetAssertionResultWrapper> results; + for (size_t idx = 0; idx < num_of_results; ++idx) { + auto assertion = HandleSelectedSignResultCtap2( + std::forward<UniquePtr<CTAPResult>>(aResult), idx); + if (assertion.isNothing()) { + return; + } + results.AppendElement(assertion.extract()); + } + if (results.IsEmpty()) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + } else { + mSignPromise.Resolve(std::move(results), __func__); + } +} + +mozilla::Maybe<WebAuthnGetAssertionResultWrapper> +CTAPHIDTokenManager::HandleSelectedSignResultCtap2( + UniquePtr<CTAPResult>&& aResult, size_t index) { + nsTArray<uint8_t> signature; + if (!aResult->Ctap2CopySignature(signature, index)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return mozilla::Nothing(); + } + + CryptoBuffer signatureBuf; + if (!signatureBuf.Assign(signature)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return mozilla::Nothing(); + } + + nsTArray<uint8_t> cred; + CryptoBuffer pubKeyCred; + if (aResult->Ctap2HasPubKeyCredential(index)) { + if (!aResult->Ctap2CopyPubKeyCredential(cred, index)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return mozilla::Nothing(); + } + if (!pubKeyCred.Assign(cred)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return mozilla::Nothing(); + } + } + + nsTArray<uint8_t> auth; + if (!aResult->Ctap2CopyAuthData(auth, index)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return mozilla::Nothing(); + } + + CryptoBuffer authData; + if (!authData.Assign(auth)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return mozilla::Nothing(); + } + + nsTArray<uint8_t> userID; + if (aResult->HasUserId(index)) { + if (!aResult->Ctap2CopyUserId(userID, + index)) { // Misusing AppId for User-handle + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return mozilla::Nothing(); + } + } + + // We would have a copy of the client data stored inside mTransaction, + // but we need the one from authenticator-rs, as that data is part of + // the signed payload. If we reorder the JSON-values (e.g. by sorting the + // members alphabetically, as the codegen from IDL does, so we can't use + // that), that would break the signature and lead to a failed authentication + // on the server. So we make sure to take exactly the client data that + // authenticator-rs sent to the token. + nsCString clientData; + if (!aResult->CopyClientDataStr(clientData)) { + mSignPromise.Reject(NS_ERROR_DOM_UNKNOWN_ERR, __func__); + return mozilla::Nothing(); + } + + nsTArray<WebAuthnExtensionResult> extensions; + WebAuthnGetAssertionResult assertion(clientData, pubKeyCred, signatureBuf, + authData, extensions, signature, userID); + mozilla::Maybe<nsCString> username; + nsCString name; + if (aResult->CopyUserName(name, index)) { + username = Some(name); + } + + WebAuthnGetAssertionResultWrapper result = {assertion, username}; + return mozilla::Some(result); +} + +} // namespace mozilla::dom |