summaryrefslogtreecommitdiffstats
path: root/dom/webauthn/CTAPHIDTokenManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webauthn/CTAPHIDTokenManager.cpp')
-rw-r--r--dom/webauthn/CTAPHIDTokenManager.cpp638
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