fune/dom/webauthn/CTAPHIDTokenManager.cpp
Cristian Tuns 450e37a837 Backed out 8 changesets (bug 1773760) for causing mochitest failures and build bustages on Android CLOSED TREE
Backed out changeset d21043c53a8e (bug 1773760)
Backed out changeset d874fa9f72da (bug 1773760)
Backed out changeset 3d270430ac9a (bug 1773760)
Backed out changeset d644ed6a5b97 (bug 1773760)
Backed out changeset 0276c01b26db (bug 1773760)
Backed out changeset 5b5584197d5f (bug 1773760)
Backed out changeset 7c89625a2b88 (bug 1773760)
Backed out changeset 8507daa63430 (bug 1773760)
2023-01-25 18:09:18 -05:00

638 lines
22 KiB
C++

/* -*- 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