From 380e545c9019a854011fbe150414a73468e8e9a3 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 31 Mar 2023 10:26:19 +0200 Subject: [PATCH] [EC-598] feat: make everything compile again --- .../browser/src/background/main.background.ts | 15 +- .../src/background/runtime.background.ts | 4 +- apps/browser/src/browser/webauthn-utils.ts | 41 +- .../browser-fido2-user-interface.service.ts | 28 +- .../src/webauthn/content/messaging/message.ts | 18 +- .../src/webauthn/content/page-script.ts | 6 +- .../vault/models/request/cipher.request.ts | 6 +- ...fido2-authenticator.service.abstraction.ts | 2 +- .../fido2-client.service.abstraction.ts | 2 +- .../abstractions/fido2.service.abstraction.ts | 193 ++-- .../services/fido2-authenticator.service.ts | 3 +- .../src/webauthn/services/fido2.service.ts | 939 +++++++++--------- 12 files changed, 657 insertions(+), 600 deletions(-) diff --git a/apps/browser/src/background/main.background.ts b/apps/browser/src/background/main.background.ts index 364871e1d7..4707b6f2cb 100644 --- a/apps/browser/src/background/main.background.ts +++ b/apps/browser/src/background/main.background.ts @@ -82,9 +82,11 @@ import { CipherService } from "@bitwarden/common/vault/services/cipher.service"; import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service"; import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; import { SyncService } from "@bitwarden/common/vault/services/sync/sync.service"; +import { Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction } from "@bitwarden/common/webauthn/abstractions/fido2-authenticator.service.abstraction"; +import { Fido2ClientService as Fido2ClientServiceAbstraction } from "@bitwarden/common/webauthn/abstractions/fido2-client.service.abstraction"; import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction } from "@bitwarden/common/webauthn/abstractions/fido2-user-interface.service.abstraction"; -import { Fido2Service as Fido2ServiceAbstraction } from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction"; -import { Fido2Service } from "@bitwarden/common/webauthn/services/fido2.service"; +import { Fido2AuthenticatorService } from "@bitwarden/common/webauthn/services/fido2-authenticator.service"; +import { Fido2ClientService } from "@bitwarden/common/webauthn/services/fido2-client.service"; import ContextMenusBackground from "../autofill/background/context-menus.background"; import NotificationBackground from "../autofill/background/notification.background"; @@ -178,7 +180,8 @@ export default class MainBackground { userVerificationApiService: UserVerificationApiServiceAbstraction; syncNotifierService: SyncNotifierServiceAbstraction; fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction; - fido2Service: Fido2ServiceAbstraction; + fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction; + fido2ClientService: Fido2ClientServiceAbstraction; avatarUpdateService: AvatarUpdateServiceAbstraction; mainContextMenuHandler: MainContextMenuHandler; cipherContextMenuHandler: CipherContextMenuHandler; @@ -481,7 +484,11 @@ export default class MainBackground { ); this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.popupUtilsService); - this.fido2Service = new Fido2Service(this.fido2UserInterfaceService, this.cipherService); + this.fido2AuthenticatorService = new Fido2AuthenticatorService( + this.cipherService, + this.fido2UserInterfaceService + ); + this.fido2ClientService = new Fido2ClientService(this.fido2AuthenticatorService); const systemUtilsServiceReloadCallback = () => { const forceWindowReload = diff --git a/apps/browser/src/background/runtime.background.ts b/apps/browser/src/background/runtime.background.ts index 9a90712ee0..0ad9d9d2b5 100644 --- a/apps/browser/src/background/runtime.background.ts +++ b/apps/browser/src/background/runtime.background.ts @@ -223,11 +223,11 @@ export default class RuntimeBackground { this.abortControllers.get(msg.abortedRequestId)?.abort(); break; case "fido2RegisterCredentialRequest": - return await this.main.fido2Service + return await this.main.fido2ClientService .createCredential(msg.data, this.createAbortController(msg.requestId)) .finally(() => this.abortControllers.delete(msg.requestId)); case "fido2GetCredentialRequest": - return await this.main.fido2Service + return await this.main.fido2ClientService .assertCredential(msg.data, this.createAbortController(msg.requestId)) .finally(() => this.abortControllers.delete(msg.requestId)); } diff --git a/apps/browser/src/browser/webauthn-utils.ts b/apps/browser/src/browser/webauthn-utils.ts index a4ad7a1495..80b301a799 100644 --- a/apps/browser/src/browser/webauthn-utils.ts +++ b/apps/browser/src/browser/webauthn-utils.ts @@ -1,16 +1,16 @@ -import { Fido2Utils } from "@bitwarden/common/webauthn/abstractions/fido2-utils"; import { - CredentialAssertParams, - CredentialAssertResult, - CredentialRegistrationParams, - CredentialRegistrationResult, -} from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction"; + CreateCredentialParams, + CreateCredentialResult, + AssertCredentialParams, + AssertCredentialResult, +} from "@bitwarden/common/webauthn/abstractions/fido2-client.service.abstraction"; +import { Fido2Utils } from "@bitwarden/common/webauthn/abstractions/fido2-utils"; class BitAuthenticatorAttestationResponse implements AuthenticatorAttestationResponse { clientDataJSON: ArrayBuffer; attestationObject: ArrayBuffer; - constructor(private result: CredentialRegistrationResult) { + constructor(private result: CreateCredentialResult) { this.clientDataJSON = Fido2Utils.stringToBuffer(result.clientDataJSON); this.attestationObject = Fido2Utils.stringToBuffer(result.attestationObject); } @@ -35,8 +35,9 @@ class BitAuthenticatorAttestationResponse implements AuthenticatorAttestationRes export class WebauthnUtils { static mapCredentialCreationOptions( options: CredentialCreationOptions, - origin: string - ): CredentialRegistrationParams { + origin: string, + sameOriginWithAncestors: boolean + ): CreateCredentialParams { const keyOptions = options.publicKey; if (keyOptions == undefined) { @@ -55,15 +56,12 @@ export class WebauthnUtils { excludeCredentials: keyOptions.excludeCredentials?.map((credential) => ({ id: Fido2Utils.bufferToString(credential.id), transports: credential.transports, + type: credential.type, })), - extensions: { - appid: keyOptions.extensions?.appid, - appidExclude: keyOptions.extensions?.appidExclude, - credProps: keyOptions.extensions?.credProps, - uvm: keyOptions.extensions?.uvm, - }, + extensions: undefined, // extensions not currently supported pubKeyCredParams: keyOptions.pubKeyCredParams.map((params) => ({ alg: params.alg, + type: params.type, })), rp: { id: keyOptions.rp.id, @@ -74,12 +72,11 @@ export class WebauthnUtils { displayName: keyOptions.user.displayName, }, timeout: keyOptions.timeout, + sameOriginWithAncestors, }; } - static mapCredentialRegistrationResult( - result: CredentialRegistrationResult - ): PublicKeyCredential { + static mapCredentialRegistrationResult(result: CreateCredentialResult): PublicKeyCredential { return { id: result.credentialId, rawId: Fido2Utils.stringToBuffer(result.credentialId), @@ -92,8 +89,9 @@ export class WebauthnUtils { static mapCredentialRequestOptions( options: CredentialRequestOptions, - origin: string - ): CredentialAssertParams { + origin: string, + sameOriginWithAncestors: boolean + ): AssertCredentialParams { const keyOptions = options.publicKey; if (keyOptions == undefined) { @@ -108,10 +106,11 @@ export class WebauthnUtils { rpId: keyOptions.rpId, userVerification: keyOptions.userVerification, timeout: keyOptions.timeout, + sameOriginWithAncestors, }; } - static mapCredentialAssertResult(result: CredentialAssertResult): PublicKeyCredential { + static mapCredentialAssertResult(result: AssertCredentialResult): PublicKeyCredential { return { id: result.credentialId, rawId: Fido2Utils.stringToBuffer(result.credentialId), diff --git a/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts b/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts index b0e1beb519..e30fc4cb33 100644 --- a/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts +++ b/apps/browser/src/services/fido2/browser-fido2-user-interface.service.ts @@ -5,13 +5,24 @@ import { Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction, NewCredentialParams, } from "@bitwarden/common/webauthn/abstractions/fido2-user-interface.service.abstraction"; -import { RequestAbortedError } from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction"; import { BrowserApi } from "../../browser/browserApi"; import { PopupUtilsService } from "../../popup/services/popup-utils.service"; const BrowserFido2MessageName = "BrowserFido2UserInterfaceServiceMessage"; +export class Fido2Error extends Error { + constructor(message: string, readonly fallbackRequested = false) { + super(message); + } +} + +export class RequestAbortedError extends Fido2Error { + constructor(fallbackRequested = false) { + super("Fido2 request was aborted", fallbackRequested); + } +} + export type BrowserFido2Message = { requestId: string } & ( | { type: "PickCredentialRequest"; @@ -198,6 +209,21 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi return false; } + async confirmNewNonDiscoverableCredential( + params: NewCredentialParams, + abortController?: AbortController + ): Promise { + return null; + } + + async informExcludedCredential( + existingCipherIds: string[], + newCredential: NewCredentialParams, + abortController?: AbortController + ): Promise { + // Not Implemented + } + private setAbortTimeout(abortController: AbortController) { return setTimeout(() => abortController.abort()); } diff --git a/apps/browser/src/webauthn/content/messaging/message.ts b/apps/browser/src/webauthn/content/messaging/message.ts index 3fb93c6a62..bcab2f12a3 100644 --- a/apps/browser/src/webauthn/content/messaging/message.ts +++ b/apps/browser/src/webauthn/content/messaging/message.ts @@ -1,9 +1,9 @@ import { - CredentialAssertParams, - CredentialAssertResult, - CredentialRegistrationParams, - CredentialRegistrationResult, -} from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction"; + CreateCredentialParams, + CreateCredentialResult, + AssertCredentialParams, + AssertCredentialResult, +} from "@bitwarden/common/webauthn/abstractions/fido2-client.service.abstraction"; export enum MessageType { CredentialCreationRequest, @@ -17,22 +17,22 @@ export enum MessageType { export type CredentialCreationRequest = { type: MessageType.CredentialCreationRequest; - data: CredentialRegistrationParams; + data: CreateCredentialParams; }; export type CredentialCreationResponse = { type: MessageType.CredentialCreationResponse; - result?: CredentialRegistrationResult; + result?: CreateCredentialResult; }; export type CredentialGetRequest = { type: MessageType.CredentialGetRequest; - data: CredentialAssertParams; + data: AssertCredentialParams; }; export type CredentialGetResponse = { type: MessageType.CredentialGetResponse; - result?: CredentialAssertResult; + result?: AssertCredentialResult; }; export type AbortRequest = { diff --git a/apps/browser/src/webauthn/content/page-script.ts b/apps/browser/src/webauthn/content/page-script.ts index e6e2f07812..eda3d15d48 100644 --- a/apps/browser/src/webauthn/content/page-script.ts +++ b/apps/browser/src/webauthn/content/page-script.ts @@ -22,7 +22,8 @@ navigator.credentials.create = async ( const response = await messenger.request( { type: MessageType.CredentialCreationRequest, - data: WebauthnUtils.mapCredentialCreationOptions(options, window.location.origin), + // TODO: Fix sameOriginWithAncestors! + data: WebauthnUtils.mapCredentialCreationOptions(options, window.location.origin, true), }, abortController ); @@ -49,7 +50,8 @@ navigator.credentials.get = async ( const response = await messenger.request( { type: MessageType.CredentialGetRequest, - data: WebauthnUtils.mapCredentialRequestOptions(options, window.location.origin), + // TODO: Fix sameOriginWithAncestors! + data: WebauthnUtils.mapCredentialRequestOptions(options, window.location.origin, true), }, abortController ); diff --git a/libs/common/src/vault/models/request/cipher.request.ts b/libs/common/src/vault/models/request/cipher.request.ts index dc0e14ea70..67230ea9bc 100644 --- a/libs/common/src/vault/models/request/cipher.request.ts +++ b/libs/common/src/vault/models/request/cipher.request.ts @@ -127,7 +127,11 @@ export class CipherRequest { this.fido2Key = new Fido2KeyApi(); this.fido2Key.keyType = cipher.fido2Key.keyType != null - ? (cipher.fido2Key.keyType.encryptedString as "ECDSA") + ? (cipher.fido2Key.keyType.encryptedString as "public-key") + : null; + this.fido2Key.keyAlgorithm = + cipher.fido2Key.keyAlgorithm != null + ? (cipher.fido2Key.keyAlgorithm.encryptedString as "ECDSA") : null; this.fido2Key.keyCurve = cipher.fido2Key.keyCurve != null diff --git a/libs/common/src/webauthn/abstractions/fido2-authenticator.service.abstraction.ts b/libs/common/src/webauthn/abstractions/fido2-authenticator.service.abstraction.ts index c6726f3eee..fc5f4a184c 100644 --- a/libs/common/src/webauthn/abstractions/fido2-authenticator.service.abstraction.ts +++ b/libs/common/src/webauthn/abstractions/fido2-authenticator.service.abstraction.ts @@ -39,7 +39,7 @@ export class Fido2AutenticatorError extends Error { export interface PublicKeyCredentialDescriptor { id: BufferSource; - transports?: ("ble" | "internal" | "nfc" | "usb")[]; + transports?: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; type: "public-key"; } diff --git a/libs/common/src/webauthn/abstractions/fido2-client.service.abstraction.ts b/libs/common/src/webauthn/abstractions/fido2-client.service.abstraction.ts index 6402cd4b77..c76f5439a1 100644 --- a/libs/common/src/webauthn/abstractions/fido2-client.service.abstraction.ts +++ b/libs/common/src/webauthn/abstractions/fido2-client.service.abstraction.ts @@ -24,7 +24,7 @@ export interface CreateCredentialParams { challenge: string; // b64 encoded excludeCredentials?: { id: string; // b64 encoded - transports?: ("ble" | "internal" | "nfc" | "usb")[]; + transports?: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[]; type: "public-key"; }[]; extensions?: { diff --git a/libs/common/src/webauthn/abstractions/fido2.service.abstraction.ts b/libs/common/src/webauthn/abstractions/fido2.service.abstraction.ts index 6044ff3f94..e0fa676a8c 100644 --- a/libs/common/src/webauthn/abstractions/fido2.service.abstraction.ts +++ b/libs/common/src/webauthn/abstractions/fido2.service.abstraction.ts @@ -1,101 +1,110 @@ -export type UserVerification = "discouraged" | "preferred" | "required"; +/** + * + * REMOVE BEFORE MERGE + * + * This is the old version of our FIDO2 client which was built according to spec. + * It left here for reference purposes until we no longer need it. + * + */ -export interface CredentialRegistrationParams { - origin: string; - attestation?: "direct" | "enterprise" | "indirect" | "none"; - authenticatorSelection?: { - // authenticatorAttachment?: AuthenticatorAttachment; // not used - requireResidentKey?: boolean; - residentKey?: "discouraged" | "preferred" | "required"; - userVerification?: UserVerification; - }; - challenge: string; // b64 encoded - excludeCredentials?: { - id: string; // b64 encoded - transports?: ("ble" | "internal" | "nfc" | "usb")[]; - // type: "public-key"; // not used - }[]; - extensions?: { - appid?: string; - appidExclude?: string; - credProps?: boolean; - uvm?: boolean; - }; - pubKeyCredParams: { - alg: number; - // type: "public-key"; // not used - }[]; - rp: { - id?: string; - name: string; - }; - user: { - id: string; // b64 encoded - displayName: string; - }; - timeout: number; -} +// export type UserVerification = "discouraged" | "preferred" | "required"; -export interface CredentialRegistrationResult { - credentialId: string; - clientDataJSON: string; - attestationObject: string; - authData: string; - publicKeyAlgorithm: number; - transports: string[]; -} +// export interface CredentialRegistrationParams { +// origin: string; +// attestation?: "direct" | "enterprise" | "indirect" | "none"; +// authenticatorSelection?: { +// // authenticatorAttachment?: AuthenticatorAttachment; // not used +// requireResidentKey?: boolean; +// residentKey?: "discouraged" | "preferred" | "required"; +// userVerification?: UserVerification; +// }; +// challenge: string; // b64 encoded +// excludeCredentials?: { +// id: string; // b64 encoded +// transports?: ("ble" | "internal" | "nfc" | "usb")[]; +// // type: "public-key"; // not used +// }[]; +// extensions?: { +// appid?: string; +// appidExclude?: string; +// credProps?: boolean; +// uvm?: boolean; +// }; +// pubKeyCredParams: { +// alg: number; +// // type: "public-key"; // not used +// }[]; +// rp: { +// id?: string; +// name: string; +// }; +// user: { +// id: string; // b64 encoded +// displayName: string; +// }; +// timeout: number; +// } -export interface CredentialAssertParams { - allowedCredentialIds: string[]; - rpId: string; - origin: string; - challenge: string; - userVerification?: UserVerification; - timeout: number; -} +// export interface CredentialRegistrationResult { +// credentialId: string; +// clientDataJSON: string; +// attestationObject: string; +// authData: string; +// publicKeyAlgorithm: number; +// transports: string[]; +// } -export interface CredentialAssertResult { - credentialId: string; - clientDataJSON: string; - authenticatorData: string; - signature: string; - userHandle: string; -} +// export interface CredentialAssertParams { +// allowedCredentialIds: string[]; +// rpId: string; +// origin: string; +// challenge: string; +// userVerification?: UserVerification; +// timeout: number; +// } -export class Fido2Error extends Error { - constructor(message: string, readonly fallbackRequested = false) { - super(message); - } -} +// export interface CredentialAssertResult { +// credentialId: string; +// clientDataJSON: string; +// authenticatorData: string; +// signature: string; +// userHandle: string; +// } -export class RequestAbortedError extends Fido2Error { - constructor(fallbackRequested = false) { - super("Fido2 request was aborted", fallbackRequested); - } -} +// export class Fido2Error extends Error { +// constructor(message: string, readonly fallbackRequested = false) { +// super(message); +// } +// } -export class NoCredentialFoundError extends Fido2Error { - constructor() { - super("No valid credential found", true); - } -} +// export class RequestAbortedError extends Fido2Error { +// constructor(fallbackRequested = false) { +// super("Fido2 request was aborted", fallbackRequested); +// } +// } -export class OriginMismatchError extends Fido2Error { - constructor() { - super( - "Authentication requests must originate from the same source that created the credential.", - false - ); - } -} +// export class NoCredentialFoundError extends Fido2Error { +// constructor() { +// super("No valid credential found", true); +// } +// } -export abstract class Fido2Service { - createCredential: ( - params: CredentialRegistrationParams, - abortController?: AbortController - ) => Promise; - assertCredential: ( - params: CredentialAssertParams, - abortController?: AbortController - ) => Promise; -} +// export class OriginMismatchError extends Fido2Error { +// constructor() { +// super( +// "Authentication requests must originate from the same source that created the credential.", +// false +// ); +// } +// } + +// export abstract class Fido2Service { +// createCredential: ( +// params: CredentialRegistrationParams, +// abortController?: AbortController +// ) => Promise; +// assertCredential: ( +// params: CredentialAssertParams, +// abortController?: AbortController +// ) => Promise; +// } diff --git a/libs/common/src/webauthn/services/fido2-authenticator.service.ts b/libs/common/src/webauthn/services/fido2-authenticator.service.ts index 6df532982a..1817c1c645 100644 --- a/libs/common/src/webauthn/services/fido2-authenticator.service.ts +++ b/libs/common/src/webauthn/services/fido2-authenticator.service.ts @@ -1,9 +1,9 @@ import { CBOR } from "cbor-redux"; import { Utils } from "../../misc/utils"; +import { CipherService } from "../../vault/abstractions/cipher.service"; import { CipherType } from "../../vault/enums/cipher-type"; import { CipherView } from "../../vault/models/view/cipher.view"; -import { CipherService } from "../../vault/services/cipher.service"; import { Fido2AlgorithmIdentifier, Fido2AutenticatorError, @@ -13,6 +13,7 @@ import { Fido2AuthenticatorMakeCredentialResult, Fido2AuthenticatorMakeCredentialsParams, Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction, + PublicKeyCredentialDescriptor, } from "../abstractions/fido2-authenticator.service.abstraction"; import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction"; import { Fido2Utils } from "../abstractions/fido2-utils"; diff --git a/libs/common/src/webauthn/services/fido2.service.ts b/libs/common/src/webauthn/services/fido2.service.ts index 776ed05df1..f1236cddfc 100644 --- a/libs/common/src/webauthn/services/fido2.service.ts +++ b/libs/common/src/webauthn/services/fido2.service.ts @@ -1,465 +1,474 @@ -import { CBOR } from "cbor-redux"; - -import { Utils } from "../../misc/utils"; -import { CipherService } from "../../vault/abstractions/cipher.service"; -import { CipherType } from "../../vault/enums/cipher-type"; -import { Cipher } from "../../vault/models/domain/cipher"; -import { CipherView } from "../../vault/models/view/cipher.view"; -import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction"; -import { Fido2Utils } from "../abstractions/fido2-utils"; -import { - CredentialAssertParams, - CredentialAssertResult, - CredentialRegistrationParams, - CredentialRegistrationResult, - Fido2Service as Fido2ServiceAbstraction, - NoCredentialFoundError, - UserVerification, -} from "../abstractions/fido2.service.abstraction"; -import { Fido2KeyView } from "../models/view/fido2-key.view"; - -import { CredentialId } from "./credential-id"; -import { joseToDer } from "./ecdsa-utils"; - -// We support self-signing, but Google won't accept it. -// TODO: Look into supporting self-signed packed format. -const STANDARD_ATTESTATION_FORMAT: "none" | "packed" = "none"; -const TIMEOUTS = { - NO_VERIFICATION: { - DEFAULT: 120000, - MIN: 30000, - MAX: 180000, - }, - WITH_VERIFICATION: { - DEFAULT: 300000, - MIN: 30000, - MAX: 600000, - }, -}; - -interface BitCredential { - credentialId: CredentialId; - keyType: "ECDSA"; - keyCurve: "P-256"; - keyValue: CryptoKey; - rpId: string; - rpName: string; - userHandle: Uint8Array; - userName: string; - origin: string; -} - -const KeyUsages: KeyUsage[] = ["sign"]; - -export class Fido2Service implements Fido2ServiceAbstraction { - constructor( - private fido2UserInterfaceService: Fido2UserInterfaceService, - private cipherService: CipherService - ) {} - - async createCredential( - params: CredentialRegistrationParams, - abortController = new AbortController() - ): Promise { - // Comment: Timeouts could potentially be implemented using decorators. - // But since I try to use decorators a little as possible and only - // for the most generic solutions, I'm gonne leave this as is untill peer review. - const timeout = setAbortTimeout( - abortController, - params.authenticatorSelection.userVerification, - params.timeout - ); - - const presence = await this.fido2UserInterfaceService.confirmNewCredential( - { - credentialName: params.rp.name, - userName: params.user.displayName, - }, - abortController - ); - - const attestationFormat = STANDARD_ATTESTATION_FORMAT; - const encoder = new TextEncoder(); - - const clientData = encoder.encode( - JSON.stringify({ - type: "webauthn.create", - challenge: params.challenge, - origin: params.origin, - crossOrigin: false, - }) - ); - const keyPair = await crypto.subtle.generateKey( - { - name: "ECDSA", - namedCurve: "P-256", - }, - true, - KeyUsages - ); - - const credentialId = await this.saveCredential({ - keyType: "ECDSA", - keyCurve: "P-256", - keyValue: keyPair.privateKey, - origin: params.origin, - rpId: params.rp.id, - rpName: params.rp.name, - userHandle: Fido2Utils.stringToBuffer(params.user.id), - userName: params.user.displayName, - }); - - const authData = await generateAuthData({ - rpId: params.rp.id, - credentialId, - userPresence: presence, - userVerification: true, // TODO: Change to false - keyPair, - }); - - const asn1Der_signature = await generateSignature({ - authData, - clientData, - privateKey: keyPair.privateKey, - }); - - const attestationObject = new Uint8Array( - CBOR.encode({ - fmt: attestationFormat, - attStmt: - attestationFormat === "packed" - ? { - alg: -7, - sig: asn1Der_signature, - } - : {}, - authData, - }) - ); - - clearTimeout(timeout); - - return { - credentialId: Fido2Utils.bufferToString(credentialId.raw), - clientDataJSON: Fido2Utils.bufferToString(clientData), - attestationObject: Fido2Utils.bufferToString(attestationObject), - authData: Fido2Utils.bufferToString(authData), - publicKeyAlgorithm: -7, - transports: ["nfc", "usb"], - }; - } - - async assertCredential( - params: CredentialAssertParams, - abortController = new AbortController() - ): Promise { - const timeout = setAbortTimeout(abortController, params.userVerification, params.timeout); - let credential: BitCredential | undefined; - - if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) { - // We're looking for regular non-resident keys - credential = await this.getCredential(params.allowedCredentialIds); - - if (credential === undefined) { - throw new NoCredentialFoundError(); - } - - // TODO: Google doesn't work with this. Look into how we're supposed to check this - // if (credential.origin !== params.origin) { - // throw new OriginMismatchError(); - // } - - await this.fido2UserInterfaceService.confirmCredential( - credential.credentialId.encoded, - abortController - ); - } else { - // We're looking for a resident key - const credentials = await this.getCredentialsByRp(params.rpId); - - if (credentials.length === 0) { - throw new NoCredentialFoundError(); - } - - const pickedId = await this.fido2UserInterfaceService.pickCredential( - credentials.map((c) => c.credentialId.encoded), - abortController - ); - credential = credentials.find((c) => c.credentialId.encoded === pickedId); - } - - const encoder = new TextEncoder(); - const clientData = encoder.encode( - JSON.stringify({ - type: "webauthn.get", - challenge: params.challenge, - origin: params.origin, - }) - ); - - const authData = await generateAuthData({ - credentialId: credential.credentialId, - rpId: params.rpId, - userPresence: true, - userVerification: true, // TODO: Change to false! - }); - - const signature = await generateSignature({ - authData, - clientData, - privateKey: credential.keyValue, - }); - - clearTimeout(timeout); - - return { - credentialId: credential.credentialId.encoded, - clientDataJSON: Fido2Utils.bufferToString(clientData), - authenticatorData: Fido2Utils.bufferToString(authData), - signature: Fido2Utils.bufferToString(signature), - userHandle: Fido2Utils.bufferToString(credential.userHandle), - }; - } - - private async getCredential(allowedCredentialIds: string[]): Promise { - let cipher: Cipher | undefined; - for (const allowedCredential of allowedCredentialIds) { - cipher = await this.cipherService.get(allowedCredential); - - if (cipher?.deletedDate != undefined) { - cipher = undefined; - } - - if (cipher != undefined) { - break; - } - } - - if (cipher == undefined) { - return undefined; - } - - const cipherView = await cipher.decrypt(); - return await mapCipherViewToBitCredential(cipherView); - } - - private async saveCredential( - credential: Omit - ): Promise { - const pcks8Key = await crypto.subtle.exportKey("pkcs8", credential.keyValue); - - const view = new CipherView(); - view.type = CipherType.Fido2Key; - view.name = credential.rpName; - - view.fido2Key = new Fido2KeyView(); - view.fido2Key.origin = credential.origin; - view.fido2Key.keyType = credential.keyType; - view.fido2Key.keyCurve = credential.keyCurve; - view.fido2Key.keyValue = Fido2Utils.bufferToString(pcks8Key); - view.fido2Key.rpId = credential.rpId; - view.fido2Key.rpName = credential.rpName; - view.fido2Key.userHandle = Fido2Utils.bufferToString(credential.userHandle); - view.fido2Key.userName = credential.userName; - view.fido2Key.origin = credential.origin; - - const cipher = await this.cipherService.encrypt(view); - await this.cipherService.createWithServer(cipher); - - // TODO: Cipher service modifies supplied object, we might wanna change that. - return new CredentialId(cipher.id); - } - - private async getCredentialsByRp(rpId: string): Promise { - const allCipherViews = await this.cipherService.getAllDecrypted(); - const cipherViews = allCipherViews.filter( - (cv) => !cv.isDeleted && cv.type === CipherType.Fido2Key && cv.fido2Key?.rpId === rpId - ); - - return await Promise.all(cipherViews.map((view) => mapCipherViewToBitCredential(view))); - } -} - -interface AuthDataParams { - rpId: string; - credentialId: CredentialId; - userPresence: boolean; - userVerification: boolean; - keyPair?: CryptoKeyPair; -} - -async function mapCipherViewToBitCredential(cipherView: CipherView): Promise { - const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.keyValue); - const privateKey = await crypto.subtle.importKey( - "pkcs8", - keyBuffer, - { - name: cipherView.fido2Key.keyType, - namedCurve: cipherView.fido2Key.keyCurve, - }, - true, - KeyUsages - ); - - return { - credentialId: new CredentialId(cipherView.id), - keyType: cipherView.fido2Key.keyType, - keyCurve: cipherView.fido2Key.keyCurve, - keyValue: privateKey, - rpId: cipherView.fido2Key.rpId, - rpName: cipherView.fido2Key.rpName, - userHandle: Fido2Utils.stringToBuffer(cipherView.fido2Key.userHandle), - userName: cipherView.fido2Key.userName, - origin: cipherView.fido2Key.origin, - }; -} - -async function generateAuthData(params: AuthDataParams) { - const encoder = new TextEncoder(); - - const authData: Array = []; - - const rpIdHash = new Uint8Array( - await crypto.subtle.digest({ name: "SHA-256" }, encoder.encode(params.rpId)) - ); - authData.push(...rpIdHash); - - const flags = authDataFlags({ - extensionData: false, - attestationData: params.keyPair !== undefined, - userVerification: params.userVerification, - userPresence: params.userPresence, - }); - authData.push(flags); - - // add 4 bytes of counter - we use time in epoch seconds as monotonic counter - // TODO: Consider changing this to a cryptographically safe random number - const now = new Date().getTime() / 1000; - authData.push( - ((now & 0xff000000) >> 24) & 0xff, - ((now & 0x00ff0000) >> 16) & 0xff, - ((now & 0x0000ff00) >> 8) & 0xff, - now & 0x000000ff - ); - - // attestedCredentialData - const attestedCredentialData: Array = []; - - // Use 0 because we're self-signing at the moment - const aaguid = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - attestedCredentialData.push(...aaguid); - - // credentialIdLength (2 bytes) and credential Id - const rawId = params.credentialId.raw; - const credentialIdLength = [(rawId.length - (rawId.length & 0xff)) / 256, rawId.length & 0xff]; - attestedCredentialData.push(...credentialIdLength); - attestedCredentialData.push(...rawId); - - if (params.keyPair) { - const publicKeyJwk = await crypto.subtle.exportKey("jwk", params.keyPair.publicKey); - // COSE format of the EC256 key - const keyX = Utils.fromUrlB64ToArray(publicKeyJwk.x); - const keyY = Utils.fromUrlB64ToArray(publicKeyJwk.y); - - // const credPublicKeyCOSE = { - // "1": 2, // kty - // "3": -7, // alg - // "-1": 1, // crv - // "-2": keyX, - // "-3": keyY, - // }; - // const coseBytes = new Uint8Array(cbor.encode(credPublicKeyCOSE)); - - // Can't get `cbor-redux` to encode in CTAP2 canonical CBOR. So we do it manually: - const coseBytes = new Uint8Array(77); - coseBytes.set([0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20], 0); - coseBytes.set(keyX, 10); - coseBytes.set([0x22, 0x58, 0x20], 10 + 32); - coseBytes.set(keyY, 10 + 32 + 3); - - // credential public key - convert to array from CBOR encoded COSE key - attestedCredentialData.push(...coseBytes); - - authData.push(...attestedCredentialData); - } - - return new Uint8Array(authData); -} - -interface SignatureParams { - authData: Uint8Array; - clientData: Uint8Array; - privateKey: CryptoKey; -} - -async function generateSignature(params: SignatureParams) { - const clientDataHash = await crypto.subtle.digest({ name: "SHA-256" }, params.clientData); - const sigBase = new Uint8Array([...params.authData, ...new Uint8Array(clientDataHash)]); - const p1336_signature = new Uint8Array( - await window.crypto.subtle.sign( - { - name: "ECDSA", - hash: { name: "SHA-256" }, - }, - params.privateKey, - sigBase - ) - ); - - const asn1Der_signature = joseToDer(p1336_signature, "ES256"); - - return asn1Der_signature; -} - -interface Flags { - extensionData: boolean; - attestationData: boolean; - userVerification: boolean; - userPresence: boolean; -} - -function authDataFlags(options: Flags): number { - let flags = 0; - - if (options.extensionData) { - flags |= 0b1000000; - } - - if (options.attestationData) { - flags |= 0b01000000; - } - - if (options.userVerification) { - flags |= 0b00000100; - } - - if (options.userPresence) { - flags |= 0b00000001; - } - - return flags; -} - -function setAbortTimeout( - abortController: AbortController, - userVerification: UserVerification, - timeout?: number -): number { - let clampedTimeout: number; - - if (userVerification === "discouraged") { - timeout = timeout ?? TIMEOUTS.NO_VERIFICATION.DEFAULT; - clampedTimeout = Math.max( - TIMEOUTS.NO_VERIFICATION.MIN, - Math.min(timeout, TIMEOUTS.NO_VERIFICATION.MAX) - ); - } else { - timeout = timeout ?? TIMEOUTS.WITH_VERIFICATION.DEFAULT; - clampedTimeout = Math.max( - TIMEOUTS.WITH_VERIFICATION.MIN, - Math.min(timeout, TIMEOUTS.WITH_VERIFICATION.MAX) - ); - } - - return window.setTimeout(() => abortController.abort(), clampedTimeout); -} +/** + * + * REMOVE BEFORE MERGE + * + * This is the old version of our FIDO2 client which was built according to spec. + * It left here for reference purposes until we no longer need it. + * + */ + +// import { CBOR } from "cbor-redux"; + +// import { Utils } from "../../misc/utils"; +// import { CipherService } from "../../vault/abstractions/cipher.service"; +// import { CipherType } from "../../vault/enums/cipher-type"; +// import { Cipher } from "../../vault/models/domain/cipher"; +// import { CipherView } from "../../vault/models/view/cipher.view"; +// import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction"; +// import { Fido2Utils } from "../abstractions/fido2-utils"; +// import { +// CredentialAssertParams, +// CredentialAssertResult, +// CredentialRegistrationParams, +// CredentialRegistrationResult, +// Fido2Service as Fido2ServiceAbstraction, +// NoCredentialFoundError, +// UserVerification, +// } from "../abstractions/fido2.service.abstraction"; +// import { Fido2KeyView } from "../models/view/fido2-key.view"; + +// import { CredentialId } from "./credential-id"; +// import { joseToDer } from "./ecdsa-utils"; + +// // We support self-signing, but Google won't accept it. +// // TODO: Look into supporting self-signed packed format. +// const STANDARD_ATTESTATION_FORMAT: "none" | "packed" = "none"; +// const TIMEOUTS = { +// NO_VERIFICATION: { +// DEFAULT: 120000, +// MIN: 30000, +// MAX: 180000, +// }, +// WITH_VERIFICATION: { +// DEFAULT: 300000, +// MIN: 30000, +// MAX: 600000, +// }, +// }; + +// interface BitCredential { +// credentialId: CredentialId; +// keyType: "ECDSA"; +// keyCurve: "P-256"; +// keyValue: CryptoKey; +// rpId: string; +// rpName: string; +// userHandle: Uint8Array; +// userName: string; +// origin: string; +// } + +// const KeyUsages: KeyUsage[] = ["sign"]; + +// export class Fido2Service implements Fido2ServiceAbstraction { +// constructor( +// private fido2UserInterfaceService: Fido2UserInterfaceService, +// private cipherService: CipherService +// ) {} + +// async createCredential( +// params: CredentialRegistrationParams, +// abortController = new AbortController() +// ): Promise { +// // Comment: Timeouts could potentially be implemented using decorators. +// // But since I try to use decorators a little as possible and only +// // for the most generic solutions, I'm gonne leave this as is untill peer review. +// const timeout = setAbortTimeout( +// abortController, +// params.authenticatorSelection.userVerification, +// params.timeout +// ); + +// const presence = await this.fido2UserInterfaceService.confirmNewCredential( +// { +// credentialName: params.rp.name, +// userName: params.user.displayName, +// }, +// abortController +// ); + +// const attestationFormat = STANDARD_ATTESTATION_FORMAT; +// const encoder = new TextEncoder(); + +// const clientData = encoder.encode( +// JSON.stringify({ +// type: "webauthn.create", +// challenge: params.challenge, +// origin: params.origin, +// crossOrigin: false, +// }) +// ); +// const keyPair = await crypto.subtle.generateKey( +// { +// name: "ECDSA", +// namedCurve: "P-256", +// }, +// true, +// KeyUsages +// ); + +// const credentialId = await this.saveCredential({ +// keyType: "ECDSA", +// keyCurve: "P-256", +// keyValue: keyPair.privateKey, +// origin: params.origin, +// rpId: params.rp.id, +// rpName: params.rp.name, +// userHandle: Fido2Utils.stringToBuffer(params.user.id), +// userName: params.user.displayName, +// }); + +// const authData = await generateAuthData({ +// rpId: params.rp.id, +// credentialId, +// userPresence: presence, +// userVerification: true, // TODO: Change to false +// keyPair, +// }); + +// const asn1Der_signature = await generateSignature({ +// authData, +// clientData, +// privateKey: keyPair.privateKey, +// }); + +// const attestationObject = new Uint8Array( +// CBOR.encode({ +// fmt: attestationFormat, +// attStmt: +// attestationFormat === "packed" +// ? { +// alg: -7, +// sig: asn1Der_signature, +// } +// : {}, +// authData, +// }) +// ); + +// clearTimeout(timeout); + +// return { +// credentialId: Fido2Utils.bufferToString(credentialId.raw), +// clientDataJSON: Fido2Utils.bufferToString(clientData), +// attestationObject: Fido2Utils.bufferToString(attestationObject), +// authData: Fido2Utils.bufferToString(authData), +// publicKeyAlgorithm: -7, +// transports: ["nfc", "usb"], +// }; +// } + +// async assertCredential( +// params: CredentialAssertParams, +// abortController = new AbortController() +// ): Promise { +// const timeout = setAbortTimeout(abortController, params.userVerification, params.timeout); +// let credential: BitCredential | undefined; + +// if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) { +// // We're looking for regular non-resident keys +// credential = await this.getCredential(params.allowedCredentialIds); + +// if (credential === undefined) { +// throw new NoCredentialFoundError(); +// } + +// // TODO: Google doesn't work with this. Look into how we're supposed to check this +// // if (credential.origin !== params.origin) { +// // throw new OriginMismatchError(); +// // } + +// await this.fido2UserInterfaceService.confirmCredential( +// credential.credentialId.encoded, +// abortController +// ); +// } else { +// // We're looking for a resident key +// const credentials = await this.getCredentialsByRp(params.rpId); + +// if (credentials.length === 0) { +// throw new NoCredentialFoundError(); +// } + +// const pickedId = await this.fido2UserInterfaceService.pickCredential( +// credentials.map((c) => c.credentialId.encoded), +// abortController +// ); +// credential = credentials.find((c) => c.credentialId.encoded === pickedId); +// } + +// const encoder = new TextEncoder(); +// const clientData = encoder.encode( +// JSON.stringify({ +// type: "webauthn.get", +// challenge: params.challenge, +// origin: params.origin, +// }) +// ); + +// const authData = await generateAuthData({ +// credentialId: credential.credentialId, +// rpId: params.rpId, +// userPresence: true, +// userVerification: true, // TODO: Change to false! +// }); + +// const signature = await generateSignature({ +// authData, +// clientData, +// privateKey: credential.keyValue, +// }); + +// clearTimeout(timeout); + +// return { +// credentialId: credential.credentialId.encoded, +// clientDataJSON: Fido2Utils.bufferToString(clientData), +// authenticatorData: Fido2Utils.bufferToString(authData), +// signature: Fido2Utils.bufferToString(signature), +// userHandle: Fido2Utils.bufferToString(credential.userHandle), +// }; +// } + +// private async getCredential(allowedCredentialIds: string[]): Promise { +// let cipher: Cipher | undefined; +// for (const allowedCredential of allowedCredentialIds) { +// cipher = await this.cipherService.get(allowedCredential); + +// if (cipher?.deletedDate != undefined) { +// cipher = undefined; +// } + +// if (cipher != undefined) { +// break; +// } +// } + +// if (cipher == undefined) { +// return undefined; +// } + +// const cipherView = await cipher.decrypt(); +// return await mapCipherViewToBitCredential(cipherView); +// } + +// private async saveCredential( +// credential: Omit +// ): Promise { +// const pcks8Key = await crypto.subtle.exportKey("pkcs8", credential.keyValue); + +// const view = new CipherView(); +// view.type = CipherType.Fido2Key; +// view.name = credential.rpName; + +// view.fido2Key = new Fido2KeyView(); +// view.fido2Key.origin = credential.origin; +// view.fido2Key.keyType = credential.keyType; +// view.fido2Key.keyCurve = credential.keyCurve; +// view.fido2Key.keyValue = Fido2Utils.bufferToString(pcks8Key); +// view.fido2Key.rpId = credential.rpId; +// view.fido2Key.rpName = credential.rpName; +// view.fido2Key.userHandle = Fido2Utils.bufferToString(credential.userHandle); +// view.fido2Key.userName = credential.userName; +// view.fido2Key.origin = credential.origin; + +// const cipher = await this.cipherService.encrypt(view); +// await this.cipherService.createWithServer(cipher); + +// // TODO: Cipher service modifies supplied object, we might wanna change that. +// return new CredentialId(cipher.id); +// } + +// private async getCredentialsByRp(rpId: string): Promise { +// const allCipherViews = await this.cipherService.getAllDecrypted(); +// const cipherViews = allCipherViews.filter( +// (cv) => !cv.isDeleted && cv.type === CipherType.Fido2Key && cv.fido2Key?.rpId === rpId +// ); + +// return await Promise.all(cipherViews.map((view) => mapCipherViewToBitCredential(view))); +// } +// } + +// interface AuthDataParams { +// rpId: string; +// credentialId: CredentialId; +// userPresence: boolean; +// userVerification: boolean; +// keyPair?: CryptoKeyPair; +// } + +// async function mapCipherViewToBitCredential(cipherView: CipherView): Promise { +// const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.keyValue); +// const privateKey = await crypto.subtle.importKey( +// "pkcs8", +// keyBuffer, +// { +// name: cipherView.fido2Key.keyType, +// namedCurve: cipherView.fido2Key.keyCurve, +// }, +// true, +// KeyUsages +// ); + +// return { +// credentialId: new CredentialId(cipherView.id), +// keyType: cipherView.fido2Key.keyType, +// keyCurve: cipherView.fido2Key.keyCurve, +// keyValue: privateKey, +// rpId: cipherView.fido2Key.rpId, +// rpName: cipherView.fido2Key.rpName, +// userHandle: Fido2Utils.stringToBuffer(cipherView.fido2Key.userHandle), +// userName: cipherView.fido2Key.userName, +// origin: cipherView.fido2Key.origin, +// }; +// } + +// async function generateAuthData(params: AuthDataParams) { +// const encoder = new TextEncoder(); + +// const authData: Array = []; + +// const rpIdHash = new Uint8Array( +// await crypto.subtle.digest({ name: "SHA-256" }, encoder.encode(params.rpId)) +// ); +// authData.push(...rpIdHash); + +// const flags = authDataFlags({ +// extensionData: false, +// attestationData: params.keyPair !== undefined, +// userVerification: params.userVerification, +// userPresence: params.userPresence, +// }); +// authData.push(flags); + +// // add 4 bytes of counter - we use time in epoch seconds as monotonic counter +// // TODO: Consider changing this to a cryptographically safe random number +// const now = new Date().getTime() / 1000; +// authData.push( +// ((now & 0xff000000) >> 24) & 0xff, +// ((now & 0x00ff0000) >> 16) & 0xff, +// ((now & 0x0000ff00) >> 8) & 0xff, +// now & 0x000000ff +// ); + +// // attestedCredentialData +// const attestedCredentialData: Array = []; + +// // Use 0 because we're self-signing at the moment +// const aaguid = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; +// attestedCredentialData.push(...aaguid); + +// // credentialIdLength (2 bytes) and credential Id +// const rawId = params.credentialId.raw; +// const credentialIdLength = [(rawId.length - (rawId.length & 0xff)) / 256, rawId.length & 0xff]; +// attestedCredentialData.push(...credentialIdLength); +// attestedCredentialData.push(...rawId); + +// if (params.keyPair) { +// const publicKeyJwk = await crypto.subtle.exportKey("jwk", params.keyPair.publicKey); +// // COSE format of the EC256 key +// const keyX = Utils.fromUrlB64ToArray(publicKeyJwk.x); +// const keyY = Utils.fromUrlB64ToArray(publicKeyJwk.y); + +// // const credPublicKeyCOSE = { +// // "1": 2, // kty +// // "3": -7, // alg +// // "-1": 1, // crv +// // "-2": keyX, +// // "-3": keyY, +// // }; +// // const coseBytes = new Uint8Array(cbor.encode(credPublicKeyCOSE)); + +// // Can't get `cbor-redux` to encode in CTAP2 canonical CBOR. So we do it manually: +// const coseBytes = new Uint8Array(77); +// coseBytes.set([0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20], 0); +// coseBytes.set(keyX, 10); +// coseBytes.set([0x22, 0x58, 0x20], 10 + 32); +// coseBytes.set(keyY, 10 + 32 + 3); + +// // credential public key - convert to array from CBOR encoded COSE key +// attestedCredentialData.push(...coseBytes); + +// authData.push(...attestedCredentialData); +// } + +// return new Uint8Array(authData); +// } + +// interface SignatureParams { +// authData: Uint8Array; +// clientData: Uint8Array; +// privateKey: CryptoKey; +// } + +// async function generateSignature(params: SignatureParams) { +// const clientDataHash = await crypto.subtle.digest({ name: "SHA-256" }, params.clientData); +// const sigBase = new Uint8Array([...params.authData, ...new Uint8Array(clientDataHash)]); +// const p1336_signature = new Uint8Array( +// await window.crypto.subtle.sign( +// { +// name: "ECDSA", +// hash: { name: "SHA-256" }, +// }, +// params.privateKey, +// sigBase +// ) +// ); + +// const asn1Der_signature = joseToDer(p1336_signature, "ES256"); + +// return asn1Der_signature; +// } + +// interface Flags { +// extensionData: boolean; +// attestationData: boolean; +// userVerification: boolean; +// userPresence: boolean; +// } + +// function authDataFlags(options: Flags): number { +// let flags = 0; + +// if (options.extensionData) { +// flags |= 0b1000000; +// } + +// if (options.attestationData) { +// flags |= 0b01000000; +// } + +// if (options.userVerification) { +// flags |= 0b00000100; +// } + +// if (options.userPresence) { +// flags |= 0b00000001; +// } + +// return flags; +// } + +// function setAbortTimeout( +// abortController: AbortController, +// userVerification: UserVerification, +// timeout?: number +// ): number { +// let clampedTimeout: number; + +// if (userVerification === "discouraged") { +// timeout = timeout ?? TIMEOUTS.NO_VERIFICATION.DEFAULT; +// clampedTimeout = Math.max( +// TIMEOUTS.NO_VERIFICATION.MIN, +// Math.min(timeout, TIMEOUTS.NO_VERIFICATION.MAX) +// ); +// } else { +// timeout = timeout ?? TIMEOUTS.WITH_VERIFICATION.DEFAULT; +// clampedTimeout = Math.max( +// TIMEOUTS.WITH_VERIFICATION.MIN, +// Math.min(timeout, TIMEOUTS.WITH_VERIFICATION.MAX) +// ); +// } + +// return window.setTimeout(() => abortController.abort(), clampedTimeout); +// }