1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 08:43:33 +00:00

[EC-598] feat: make everything compile again

This commit is contained in:
Andreas Coroiu
2023-03-31 10:26:19 +02:00
parent 25ebbec0eb
commit 380e545c90
12 changed files with 657 additions and 600 deletions

View File

@@ -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 { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service"; import { SyncNotifierService } from "@bitwarden/common/vault/services/sync/sync-notifier.service";
import { SyncService } from "@bitwarden/common/vault/services/sync/sync.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 { 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 { Fido2AuthenticatorService } from "@bitwarden/common/webauthn/services/fido2-authenticator.service";
import { Fido2Service } from "@bitwarden/common/webauthn/services/fido2.service"; import { Fido2ClientService } from "@bitwarden/common/webauthn/services/fido2-client.service";
import ContextMenusBackground from "../autofill/background/context-menus.background"; import ContextMenusBackground from "../autofill/background/context-menus.background";
import NotificationBackground from "../autofill/background/notification.background"; import NotificationBackground from "../autofill/background/notification.background";
@@ -178,7 +180,8 @@ export default class MainBackground {
userVerificationApiService: UserVerificationApiServiceAbstraction; userVerificationApiService: UserVerificationApiServiceAbstraction;
syncNotifierService: SyncNotifierServiceAbstraction; syncNotifierService: SyncNotifierServiceAbstraction;
fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction; fido2UserInterfaceService: Fido2UserInterfaceServiceAbstraction;
fido2Service: Fido2ServiceAbstraction; fido2AuthenticatorService: Fido2AuthenticatorServiceAbstraction;
fido2ClientService: Fido2ClientServiceAbstraction;
avatarUpdateService: AvatarUpdateServiceAbstraction; avatarUpdateService: AvatarUpdateServiceAbstraction;
mainContextMenuHandler: MainContextMenuHandler; mainContextMenuHandler: MainContextMenuHandler;
cipherContextMenuHandler: CipherContextMenuHandler; cipherContextMenuHandler: CipherContextMenuHandler;
@@ -481,7 +484,11 @@ export default class MainBackground {
); );
this.fido2UserInterfaceService = new BrowserFido2UserInterfaceService(this.popupUtilsService); 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 systemUtilsServiceReloadCallback = () => {
const forceWindowReload = const forceWindowReload =

View File

@@ -223,11 +223,11 @@ export default class RuntimeBackground {
this.abortControllers.get(msg.abortedRequestId)?.abort(); this.abortControllers.get(msg.abortedRequestId)?.abort();
break; break;
case "fido2RegisterCredentialRequest": case "fido2RegisterCredentialRequest":
return await this.main.fido2Service return await this.main.fido2ClientService
.createCredential(msg.data, this.createAbortController(msg.requestId)) .createCredential(msg.data, this.createAbortController(msg.requestId))
.finally(() => this.abortControllers.delete(msg.requestId)); .finally(() => this.abortControllers.delete(msg.requestId));
case "fido2GetCredentialRequest": case "fido2GetCredentialRequest":
return await this.main.fido2Service return await this.main.fido2ClientService
.assertCredential(msg.data, this.createAbortController(msg.requestId)) .assertCredential(msg.data, this.createAbortController(msg.requestId))
.finally(() => this.abortControllers.delete(msg.requestId)); .finally(() => this.abortControllers.delete(msg.requestId));
} }

View File

@@ -1,16 +1,16 @@
import { Fido2Utils } from "@bitwarden/common/webauthn/abstractions/fido2-utils";
import { import {
CredentialAssertParams, CreateCredentialParams,
CredentialAssertResult, CreateCredentialResult,
CredentialRegistrationParams, AssertCredentialParams,
CredentialRegistrationResult, AssertCredentialResult,
} from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction"; } from "@bitwarden/common/webauthn/abstractions/fido2-client.service.abstraction";
import { Fido2Utils } from "@bitwarden/common/webauthn/abstractions/fido2-utils";
class BitAuthenticatorAttestationResponse implements AuthenticatorAttestationResponse { class BitAuthenticatorAttestationResponse implements AuthenticatorAttestationResponse {
clientDataJSON: ArrayBuffer; clientDataJSON: ArrayBuffer;
attestationObject: ArrayBuffer; attestationObject: ArrayBuffer;
constructor(private result: CredentialRegistrationResult) { constructor(private result: CreateCredentialResult) {
this.clientDataJSON = Fido2Utils.stringToBuffer(result.clientDataJSON); this.clientDataJSON = Fido2Utils.stringToBuffer(result.clientDataJSON);
this.attestationObject = Fido2Utils.stringToBuffer(result.attestationObject); this.attestationObject = Fido2Utils.stringToBuffer(result.attestationObject);
} }
@@ -35,8 +35,9 @@ class BitAuthenticatorAttestationResponse implements AuthenticatorAttestationRes
export class WebauthnUtils { export class WebauthnUtils {
static mapCredentialCreationOptions( static mapCredentialCreationOptions(
options: CredentialCreationOptions, options: CredentialCreationOptions,
origin: string origin: string,
): CredentialRegistrationParams { sameOriginWithAncestors: boolean
): CreateCredentialParams {
const keyOptions = options.publicKey; const keyOptions = options.publicKey;
if (keyOptions == undefined) { if (keyOptions == undefined) {
@@ -55,15 +56,12 @@ export class WebauthnUtils {
excludeCredentials: keyOptions.excludeCredentials?.map((credential) => ({ excludeCredentials: keyOptions.excludeCredentials?.map((credential) => ({
id: Fido2Utils.bufferToString(credential.id), id: Fido2Utils.bufferToString(credential.id),
transports: credential.transports, transports: credential.transports,
type: credential.type,
})), })),
extensions: { extensions: undefined, // extensions not currently supported
appid: keyOptions.extensions?.appid,
appidExclude: keyOptions.extensions?.appidExclude,
credProps: keyOptions.extensions?.credProps,
uvm: keyOptions.extensions?.uvm,
},
pubKeyCredParams: keyOptions.pubKeyCredParams.map((params) => ({ pubKeyCredParams: keyOptions.pubKeyCredParams.map((params) => ({
alg: params.alg, alg: params.alg,
type: params.type,
})), })),
rp: { rp: {
id: keyOptions.rp.id, id: keyOptions.rp.id,
@@ -74,12 +72,11 @@ export class WebauthnUtils {
displayName: keyOptions.user.displayName, displayName: keyOptions.user.displayName,
}, },
timeout: keyOptions.timeout, timeout: keyOptions.timeout,
sameOriginWithAncestors,
}; };
} }
static mapCredentialRegistrationResult( static mapCredentialRegistrationResult(result: CreateCredentialResult): PublicKeyCredential {
result: CredentialRegistrationResult
): PublicKeyCredential {
return { return {
id: result.credentialId, id: result.credentialId,
rawId: Fido2Utils.stringToBuffer(result.credentialId), rawId: Fido2Utils.stringToBuffer(result.credentialId),
@@ -92,8 +89,9 @@ export class WebauthnUtils {
static mapCredentialRequestOptions( static mapCredentialRequestOptions(
options: CredentialRequestOptions, options: CredentialRequestOptions,
origin: string origin: string,
): CredentialAssertParams { sameOriginWithAncestors: boolean
): AssertCredentialParams {
const keyOptions = options.publicKey; const keyOptions = options.publicKey;
if (keyOptions == undefined) { if (keyOptions == undefined) {
@@ -108,10 +106,11 @@ export class WebauthnUtils {
rpId: keyOptions.rpId, rpId: keyOptions.rpId,
userVerification: keyOptions.userVerification, userVerification: keyOptions.userVerification,
timeout: keyOptions.timeout, timeout: keyOptions.timeout,
sameOriginWithAncestors,
}; };
} }
static mapCredentialAssertResult(result: CredentialAssertResult): PublicKeyCredential { static mapCredentialAssertResult(result: AssertCredentialResult): PublicKeyCredential {
return { return {
id: result.credentialId, id: result.credentialId,
rawId: Fido2Utils.stringToBuffer(result.credentialId), rawId: Fido2Utils.stringToBuffer(result.credentialId),

View File

@@ -5,13 +5,24 @@ import {
Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction, Fido2UserInterfaceService as Fido2UserInterfaceServiceAbstraction,
NewCredentialParams, NewCredentialParams,
} from "@bitwarden/common/webauthn/abstractions/fido2-user-interface.service.abstraction"; } 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 { BrowserApi } from "../../browser/browserApi";
import { PopupUtilsService } from "../../popup/services/popup-utils.service"; import { PopupUtilsService } from "../../popup/services/popup-utils.service";
const BrowserFido2MessageName = "BrowserFido2UserInterfaceServiceMessage"; 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 } & ( export type BrowserFido2Message = { requestId: string } & (
| { | {
type: "PickCredentialRequest"; type: "PickCredentialRequest";
@@ -198,6 +209,21 @@ export class BrowserFido2UserInterfaceService implements Fido2UserInterfaceServi
return false; return false;
} }
async confirmNewNonDiscoverableCredential(
params: NewCredentialParams,
abortController?: AbortController
): Promise<string> {
return null;
}
async informExcludedCredential(
existingCipherIds: string[],
newCredential: NewCredentialParams,
abortController?: AbortController
): Promise<void> {
// Not Implemented
}
private setAbortTimeout(abortController: AbortController) { private setAbortTimeout(abortController: AbortController) {
return setTimeout(() => abortController.abort()); return setTimeout(() => abortController.abort());
} }

View File

@@ -1,9 +1,9 @@
import { import {
CredentialAssertParams, CreateCredentialParams,
CredentialAssertResult, CreateCredentialResult,
CredentialRegistrationParams, AssertCredentialParams,
CredentialRegistrationResult, AssertCredentialResult,
} from "@bitwarden/common/webauthn/abstractions/fido2.service.abstraction"; } from "@bitwarden/common/webauthn/abstractions/fido2-client.service.abstraction";
export enum MessageType { export enum MessageType {
CredentialCreationRequest, CredentialCreationRequest,
@@ -17,22 +17,22 @@ export enum MessageType {
export type CredentialCreationRequest = { export type CredentialCreationRequest = {
type: MessageType.CredentialCreationRequest; type: MessageType.CredentialCreationRequest;
data: CredentialRegistrationParams; data: CreateCredentialParams;
}; };
export type CredentialCreationResponse = { export type CredentialCreationResponse = {
type: MessageType.CredentialCreationResponse; type: MessageType.CredentialCreationResponse;
result?: CredentialRegistrationResult; result?: CreateCredentialResult;
}; };
export type CredentialGetRequest = { export type CredentialGetRequest = {
type: MessageType.CredentialGetRequest; type: MessageType.CredentialGetRequest;
data: CredentialAssertParams; data: AssertCredentialParams;
}; };
export type CredentialGetResponse = { export type CredentialGetResponse = {
type: MessageType.CredentialGetResponse; type: MessageType.CredentialGetResponse;
result?: CredentialAssertResult; result?: AssertCredentialResult;
}; };
export type AbortRequest = { export type AbortRequest = {

View File

@@ -22,7 +22,8 @@ navigator.credentials.create = async (
const response = await messenger.request( const response = await messenger.request(
{ {
type: MessageType.CredentialCreationRequest, type: MessageType.CredentialCreationRequest,
data: WebauthnUtils.mapCredentialCreationOptions(options, window.location.origin), // TODO: Fix sameOriginWithAncestors!
data: WebauthnUtils.mapCredentialCreationOptions(options, window.location.origin, true),
}, },
abortController abortController
); );
@@ -49,7 +50,8 @@ navigator.credentials.get = async (
const response = await messenger.request( const response = await messenger.request(
{ {
type: MessageType.CredentialGetRequest, type: MessageType.CredentialGetRequest,
data: WebauthnUtils.mapCredentialRequestOptions(options, window.location.origin), // TODO: Fix sameOriginWithAncestors!
data: WebauthnUtils.mapCredentialRequestOptions(options, window.location.origin, true),
}, },
abortController abortController
); );

View File

@@ -127,7 +127,11 @@ export class CipherRequest {
this.fido2Key = new Fido2KeyApi(); this.fido2Key = new Fido2KeyApi();
this.fido2Key.keyType = this.fido2Key.keyType =
cipher.fido2Key.keyType != null 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; : null;
this.fido2Key.keyCurve = this.fido2Key.keyCurve =
cipher.fido2Key.keyCurve != null cipher.fido2Key.keyCurve != null

View File

@@ -39,7 +39,7 @@ export class Fido2AutenticatorError extends Error {
export interface PublicKeyCredentialDescriptor { export interface PublicKeyCredentialDescriptor {
id: BufferSource; id: BufferSource;
transports?: ("ble" | "internal" | "nfc" | "usb")[]; transports?: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[];
type: "public-key"; type: "public-key";
} }

View File

@@ -24,7 +24,7 @@ export interface CreateCredentialParams {
challenge: string; // b64 encoded challenge: string; // b64 encoded
excludeCredentials?: { excludeCredentials?: {
id: string; // b64 encoded id: string; // b64 encoded
transports?: ("ble" | "internal" | "nfc" | "usb")[]; transports?: ("ble" | "hybrid" | "internal" | "nfc" | "usb")[];
type: "public-key"; type: "public-key";
}[]; }[];
extensions?: { extensions?: {

View File

@@ -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 { // export type UserVerification = "discouraged" | "preferred" | "required";
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 CredentialRegistrationResult { // export interface CredentialRegistrationParams {
credentialId: string; // origin: string;
clientDataJSON: string; // attestation?: "direct" | "enterprise" | "indirect" | "none";
attestationObject: string; // authenticatorSelection?: {
authData: string; // // authenticatorAttachment?: AuthenticatorAttachment; // not used
publicKeyAlgorithm: number; // requireResidentKey?: boolean;
transports: string[]; // 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 { // export interface CredentialRegistrationResult {
allowedCredentialIds: string[]; // credentialId: string;
rpId: string; // clientDataJSON: string;
origin: string; // attestationObject: string;
challenge: string; // authData: string;
userVerification?: UserVerification; // publicKeyAlgorithm: number;
timeout: number; // transports: string[];
} // }
export interface CredentialAssertResult { // export interface CredentialAssertParams {
credentialId: string; // allowedCredentialIds: string[];
clientDataJSON: string; // rpId: string;
authenticatorData: string; // origin: string;
signature: string; // challenge: string;
userHandle: string; // userVerification?: UserVerification;
} // timeout: number;
// }
export class Fido2Error extends Error { // export interface CredentialAssertResult {
constructor(message: string, readonly fallbackRequested = false) { // credentialId: string;
super(message); // clientDataJSON: string;
} // authenticatorData: string;
} // signature: string;
// userHandle: string;
// }
export class RequestAbortedError extends Fido2Error { // export class Fido2Error extends Error {
constructor(fallbackRequested = false) { // constructor(message: string, readonly fallbackRequested = false) {
super("Fido2 request was aborted", fallbackRequested); // super(message);
} // }
} // }
export class NoCredentialFoundError extends Fido2Error { // export class RequestAbortedError extends Fido2Error {
constructor() { // constructor(fallbackRequested = false) {
super("No valid credential found", true); // super("Fido2 request was aborted", fallbackRequested);
} // }
} // }
export class OriginMismatchError extends Fido2Error { // export class NoCredentialFoundError extends Fido2Error {
constructor() { // constructor() {
super( // super("No valid credential found", true);
"Authentication requests must originate from the same source that created the credential.", // }
false // }
);
}
}
export abstract class Fido2Service { // export class OriginMismatchError extends Fido2Error {
createCredential: ( // constructor() {
params: CredentialRegistrationParams, // super(
abortController?: AbortController // "Authentication requests must originate from the same source that created the credential.",
) => Promise<CredentialRegistrationResult>; // false
assertCredential: ( // );
params: CredentialAssertParams, // }
abortController?: AbortController // }
) => Promise<CredentialAssertResult>;
} // export abstract class Fido2Service {
// createCredential: (
// params: CredentialRegistrationParams,
// abortController?: AbortController
// ) => Promise<CredentialRegistrationResult>;
// assertCredential: (
// params: CredentialAssertParams,
// abortController?: AbortController
// ) => Promise<CredentialAssertResult>;
// }

View File

@@ -1,9 +1,9 @@
import { CBOR } from "cbor-redux"; import { CBOR } from "cbor-redux";
import { Utils } from "../../misc/utils"; import { Utils } from "../../misc/utils";
import { CipherService } from "../../vault/abstractions/cipher.service";
import { CipherType } from "../../vault/enums/cipher-type"; import { CipherType } from "../../vault/enums/cipher-type";
import { CipherView } from "../../vault/models/view/cipher.view"; import { CipherView } from "../../vault/models/view/cipher.view";
import { CipherService } from "../../vault/services/cipher.service";
import { import {
Fido2AlgorithmIdentifier, Fido2AlgorithmIdentifier,
Fido2AutenticatorError, Fido2AutenticatorError,
@@ -13,6 +13,7 @@ import {
Fido2AuthenticatorMakeCredentialResult, Fido2AuthenticatorMakeCredentialResult,
Fido2AuthenticatorMakeCredentialsParams, Fido2AuthenticatorMakeCredentialsParams,
Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction, Fido2AuthenticatorService as Fido2AuthenticatorServiceAbstraction,
PublicKeyCredentialDescriptor,
} from "../abstractions/fido2-authenticator.service.abstraction"; } from "../abstractions/fido2-authenticator.service.abstraction";
import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction"; import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction";
import { Fido2Utils } from "../abstractions/fido2-utils"; import { Fido2Utils } from "../abstractions/fido2-utils";

View File

@@ -1,465 +1,474 @@
import { CBOR } from "cbor-redux"; /**
*
import { Utils } from "../../misc/utils"; * REMOVE BEFORE MERGE
import { CipherService } from "../../vault/abstractions/cipher.service"; *
import { CipherType } from "../../vault/enums/cipher-type"; * This is the old version of our FIDO2 client which was built according to spec.
import { Cipher } from "../../vault/models/domain/cipher"; * It left here for reference purposes until we no longer need it.
import { CipherView } from "../../vault/models/view/cipher.view"; *
import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction"; */
import { Fido2Utils } from "../abstractions/fido2-utils";
import { // import { CBOR } from "cbor-redux";
CredentialAssertParams,
CredentialAssertResult, // import { Utils } from "../../misc/utils";
CredentialRegistrationParams, // import { CipherService } from "../../vault/abstractions/cipher.service";
CredentialRegistrationResult, // import { CipherType } from "../../vault/enums/cipher-type";
Fido2Service as Fido2ServiceAbstraction, // import { Cipher } from "../../vault/models/domain/cipher";
NoCredentialFoundError, // import { CipherView } from "../../vault/models/view/cipher.view";
UserVerification, // import { Fido2UserInterfaceService } from "../abstractions/fido2-user-interface.service.abstraction";
} from "../abstractions/fido2.service.abstraction"; // import { Fido2Utils } from "../abstractions/fido2-utils";
import { Fido2KeyView } from "../models/view/fido2-key.view"; // import {
// CredentialAssertParams,
import { CredentialId } from "./credential-id"; // CredentialAssertResult,
import { joseToDer } from "./ecdsa-utils"; // CredentialRegistrationParams,
// CredentialRegistrationResult,
// We support self-signing, but Google won't accept it. // Fido2Service as Fido2ServiceAbstraction,
// TODO: Look into supporting self-signed packed format. // NoCredentialFoundError,
const STANDARD_ATTESTATION_FORMAT: "none" | "packed" = "none"; // UserVerification,
const TIMEOUTS = { // } from "../abstractions/fido2.service.abstraction";
NO_VERIFICATION: { // import { Fido2KeyView } from "../models/view/fido2-key.view";
DEFAULT: 120000,
MIN: 30000, // import { CredentialId } from "./credential-id";
MAX: 180000, // import { joseToDer } from "./ecdsa-utils";
},
WITH_VERIFICATION: { // // We support self-signing, but Google won't accept it.
DEFAULT: 300000, // // TODO: Look into supporting self-signed packed format.
MIN: 30000, // const STANDARD_ATTESTATION_FORMAT: "none" | "packed" = "none";
MAX: 600000, // const TIMEOUTS = {
}, // NO_VERIFICATION: {
}; // DEFAULT: 120000,
// MIN: 30000,
interface BitCredential { // MAX: 180000,
credentialId: CredentialId; // },
keyType: "ECDSA"; // WITH_VERIFICATION: {
keyCurve: "P-256"; // DEFAULT: 300000,
keyValue: CryptoKey; // MIN: 30000,
rpId: string; // MAX: 600000,
rpName: string; // },
userHandle: Uint8Array; // };
userName: string;
origin: string; // interface BitCredential {
} // credentialId: CredentialId;
// keyType: "ECDSA";
const KeyUsages: KeyUsage[] = ["sign"]; // keyCurve: "P-256";
// keyValue: CryptoKey;
export class Fido2Service implements Fido2ServiceAbstraction { // rpId: string;
constructor( // rpName: string;
private fido2UserInterfaceService: Fido2UserInterfaceService, // userHandle: Uint8Array;
private cipherService: CipherService // userName: string;
) {} // origin: string;
// }
async createCredential(
params: CredentialRegistrationParams, // const KeyUsages: KeyUsage[] = ["sign"];
abortController = new AbortController()
): Promise<CredentialRegistrationResult> { // export class Fido2Service implements Fido2ServiceAbstraction {
// Comment: Timeouts could potentially be implemented using decorators. // constructor(
// But since I try to use decorators a little as possible and only // private fido2UserInterfaceService: Fido2UserInterfaceService,
// for the most generic solutions, I'm gonne leave this as is untill peer review. // private cipherService: CipherService
const timeout = setAbortTimeout( // ) {}
abortController,
params.authenticatorSelection.userVerification, // async createCredential(
params.timeout // params: CredentialRegistrationParams,
); // abortController = new AbortController()
// ): Promise<CredentialRegistrationResult> {
const presence = await this.fido2UserInterfaceService.confirmNewCredential( // // Comment: Timeouts could potentially be implemented using decorators.
{ // // But since I try to use decorators a little as possible and only
credentialName: params.rp.name, // // for the most generic solutions, I'm gonne leave this as is untill peer review.
userName: params.user.displayName, // const timeout = setAbortTimeout(
}, // abortController,
abortController // params.authenticatorSelection.userVerification,
); // params.timeout
// );
const attestationFormat = STANDARD_ATTESTATION_FORMAT;
const encoder = new TextEncoder(); // const presence = await this.fido2UserInterfaceService.confirmNewCredential(
// {
const clientData = encoder.encode( // credentialName: params.rp.name,
JSON.stringify({ // userName: params.user.displayName,
type: "webauthn.create", // },
challenge: params.challenge, // abortController
origin: params.origin, // );
crossOrigin: false,
}) // const attestationFormat = STANDARD_ATTESTATION_FORMAT;
); // const encoder = new TextEncoder();
const keyPair = await crypto.subtle.generateKey(
{ // const clientData = encoder.encode(
name: "ECDSA", // JSON.stringify({
namedCurve: "P-256", // type: "webauthn.create",
}, // challenge: params.challenge,
true, // origin: params.origin,
KeyUsages // crossOrigin: false,
); // })
// );
const credentialId = await this.saveCredential({ // const keyPair = await crypto.subtle.generateKey(
keyType: "ECDSA", // {
keyCurve: "P-256", // name: "ECDSA",
keyValue: keyPair.privateKey, // namedCurve: "P-256",
origin: params.origin, // },
rpId: params.rp.id, // true,
rpName: params.rp.name, // KeyUsages
userHandle: Fido2Utils.stringToBuffer(params.user.id), // );
userName: params.user.displayName,
}); // const credentialId = await this.saveCredential({
// keyType: "ECDSA",
const authData = await generateAuthData({ // keyCurve: "P-256",
rpId: params.rp.id, // keyValue: keyPair.privateKey,
credentialId, // origin: params.origin,
userPresence: presence, // rpId: params.rp.id,
userVerification: true, // TODO: Change to false // rpName: params.rp.name,
keyPair, // userHandle: Fido2Utils.stringToBuffer(params.user.id),
}); // userName: params.user.displayName,
// });
const asn1Der_signature = await generateSignature({
authData, // const authData = await generateAuthData({
clientData, // rpId: params.rp.id,
privateKey: keyPair.privateKey, // credentialId,
}); // userPresence: presence,
// userVerification: true, // TODO: Change to false
const attestationObject = new Uint8Array( // keyPair,
CBOR.encode({ // });
fmt: attestationFormat,
attStmt: // const asn1Der_signature = await generateSignature({
attestationFormat === "packed" // authData,
? { // clientData,
alg: -7, // privateKey: keyPair.privateKey,
sig: asn1Der_signature, // });
}
: {}, // const attestationObject = new Uint8Array(
authData, // CBOR.encode({
}) // fmt: attestationFormat,
); // attStmt:
// attestationFormat === "packed"
clearTimeout(timeout); // ? {
// alg: -7,
return { // sig: asn1Der_signature,
credentialId: Fido2Utils.bufferToString(credentialId.raw), // }
clientDataJSON: Fido2Utils.bufferToString(clientData), // : {},
attestationObject: Fido2Utils.bufferToString(attestationObject), // authData,
authData: Fido2Utils.bufferToString(authData), // })
publicKeyAlgorithm: -7, // );
transports: ["nfc", "usb"],
}; // clearTimeout(timeout);
}
// return {
async assertCredential( // credentialId: Fido2Utils.bufferToString(credentialId.raw),
params: CredentialAssertParams, // clientDataJSON: Fido2Utils.bufferToString(clientData),
abortController = new AbortController() // attestationObject: Fido2Utils.bufferToString(attestationObject),
): Promise<CredentialAssertResult> { // authData: Fido2Utils.bufferToString(authData),
const timeout = setAbortTimeout(abortController, params.userVerification, params.timeout); // publicKeyAlgorithm: -7,
let credential: BitCredential | undefined; // transports: ["nfc", "usb"],
// };
if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) { // }
// We're looking for regular non-resident keys
credential = await this.getCredential(params.allowedCredentialIds); // async assertCredential(
// params: CredentialAssertParams,
if (credential === undefined) { // abortController = new AbortController()
throw new NoCredentialFoundError(); // ): Promise<CredentialAssertResult> {
} // const timeout = setAbortTimeout(abortController, params.userVerification, params.timeout);
// let credential: BitCredential | undefined;
// TODO: Google doesn't work with this. Look into how we're supposed to check this
// if (credential.origin !== params.origin) { // if (params.allowedCredentialIds && params.allowedCredentialIds.length > 0) {
// throw new OriginMismatchError(); // // We're looking for regular non-resident keys
// } // credential = await this.getCredential(params.allowedCredentialIds);
await this.fido2UserInterfaceService.confirmCredential( // if (credential === undefined) {
credential.credentialId.encoded, // throw new NoCredentialFoundError();
abortController // }
);
} else { // // TODO: Google doesn't work with this. Look into how we're supposed to check this
// We're looking for a resident key // // if (credential.origin !== params.origin) {
const credentials = await this.getCredentialsByRp(params.rpId); // // throw new OriginMismatchError();
// // }
if (credentials.length === 0) {
throw new NoCredentialFoundError(); // await this.fido2UserInterfaceService.confirmCredential(
} // credential.credentialId.encoded,
// abortController
const pickedId = await this.fido2UserInterfaceService.pickCredential( // );
credentials.map((c) => c.credentialId.encoded), // } else {
abortController // // We're looking for a resident key
); // const credentials = await this.getCredentialsByRp(params.rpId);
credential = credentials.find((c) => c.credentialId.encoded === pickedId);
} // if (credentials.length === 0) {
// throw new NoCredentialFoundError();
const encoder = new TextEncoder(); // }
const clientData = encoder.encode(
JSON.stringify({ // const pickedId = await this.fido2UserInterfaceService.pickCredential(
type: "webauthn.get", // credentials.map((c) => c.credentialId.encoded),
challenge: params.challenge, // abortController
origin: params.origin, // );
}) // credential = credentials.find((c) => c.credentialId.encoded === pickedId);
); // }
const authData = await generateAuthData({ // const encoder = new TextEncoder();
credentialId: credential.credentialId, // const clientData = encoder.encode(
rpId: params.rpId, // JSON.stringify({
userPresence: true, // type: "webauthn.get",
userVerification: true, // TODO: Change to false! // challenge: params.challenge,
}); // origin: params.origin,
// })
const signature = await generateSignature({ // );
authData,
clientData, // const authData = await generateAuthData({
privateKey: credential.keyValue, // credentialId: credential.credentialId,
}); // rpId: params.rpId,
// userPresence: true,
clearTimeout(timeout); // userVerification: true, // TODO: Change to false!
// });
return {
credentialId: credential.credentialId.encoded, // const signature = await generateSignature({
clientDataJSON: Fido2Utils.bufferToString(clientData), // authData,
authenticatorData: Fido2Utils.bufferToString(authData), // clientData,
signature: Fido2Utils.bufferToString(signature), // privateKey: credential.keyValue,
userHandle: Fido2Utils.bufferToString(credential.userHandle), // });
};
} // clearTimeout(timeout);
private async getCredential(allowedCredentialIds: string[]): Promise<BitCredential | undefined> { // return {
let cipher: Cipher | undefined; // credentialId: credential.credentialId.encoded,
for (const allowedCredential of allowedCredentialIds) { // clientDataJSON: Fido2Utils.bufferToString(clientData),
cipher = await this.cipherService.get(allowedCredential); // authenticatorData: Fido2Utils.bufferToString(authData),
// signature: Fido2Utils.bufferToString(signature),
if (cipher?.deletedDate != undefined) { // userHandle: Fido2Utils.bufferToString(credential.userHandle),
cipher = undefined; // };
} // }
if (cipher != undefined) { // private async getCredential(allowedCredentialIds: string[]): Promise<BitCredential | undefined> {
break; // let cipher: Cipher | undefined;
} // for (const allowedCredential of allowedCredentialIds) {
} // cipher = await this.cipherService.get(allowedCredential);
if (cipher == undefined) { // if (cipher?.deletedDate != undefined) {
return undefined; // cipher = undefined;
} // }
const cipherView = await cipher.decrypt(); // if (cipher != undefined) {
return await mapCipherViewToBitCredential(cipherView); // break;
} // }
// }
private async saveCredential(
credential: Omit<BitCredential, "credentialId"> // if (cipher == undefined) {
): Promise<CredentialId> { // return undefined;
const pcks8Key = await crypto.subtle.exportKey("pkcs8", credential.keyValue); // }
const view = new CipherView(); // const cipherView = await cipher.decrypt();
view.type = CipherType.Fido2Key; // return await mapCipherViewToBitCredential(cipherView);
view.name = credential.rpName; // }
view.fido2Key = new Fido2KeyView(); // private async saveCredential(
view.fido2Key.origin = credential.origin; // credential: Omit<BitCredential, "credentialId">
view.fido2Key.keyType = credential.keyType; // ): Promise<CredentialId> {
view.fido2Key.keyCurve = credential.keyCurve; // const pcks8Key = await crypto.subtle.exportKey("pkcs8", credential.keyValue);
view.fido2Key.keyValue = Fido2Utils.bufferToString(pcks8Key);
view.fido2Key.rpId = credential.rpId; // const view = new CipherView();
view.fido2Key.rpName = credential.rpName; // view.type = CipherType.Fido2Key;
view.fido2Key.userHandle = Fido2Utils.bufferToString(credential.userHandle); // view.name = credential.rpName;
view.fido2Key.userName = credential.userName;
view.fido2Key.origin = credential.origin; // view.fido2Key = new Fido2KeyView();
// view.fido2Key.origin = credential.origin;
const cipher = await this.cipherService.encrypt(view); // view.fido2Key.keyType = credential.keyType;
await this.cipherService.createWithServer(cipher); // view.fido2Key.keyCurve = credential.keyCurve;
// view.fido2Key.keyValue = Fido2Utils.bufferToString(pcks8Key);
// TODO: Cipher service modifies supplied object, we might wanna change that. // view.fido2Key.rpId = credential.rpId;
return new CredentialId(cipher.id); // view.fido2Key.rpName = credential.rpName;
} // view.fido2Key.userHandle = Fido2Utils.bufferToString(credential.userHandle);
// view.fido2Key.userName = credential.userName;
private async getCredentialsByRp(rpId: string): Promise<BitCredential[]> { // view.fido2Key.origin = credential.origin;
const allCipherViews = await this.cipherService.getAllDecrypted();
const cipherViews = allCipherViews.filter( // const cipher = await this.cipherService.encrypt(view);
(cv) => !cv.isDeleted && cv.type === CipherType.Fido2Key && cv.fido2Key?.rpId === rpId // await this.cipherService.createWithServer(cipher);
);
// // TODO: Cipher service modifies supplied object, we might wanna change that.
return await Promise.all(cipherViews.map((view) => mapCipherViewToBitCredential(view))); // return new CredentialId(cipher.id);
} // }
}
// private async getCredentialsByRp(rpId: string): Promise<BitCredential[]> {
interface AuthDataParams { // const allCipherViews = await this.cipherService.getAllDecrypted();
rpId: string; // const cipherViews = allCipherViews.filter(
credentialId: CredentialId; // (cv) => !cv.isDeleted && cv.type === CipherType.Fido2Key && cv.fido2Key?.rpId === rpId
userPresence: boolean; // );
userVerification: boolean;
keyPair?: CryptoKeyPair; // return await Promise.all(cipherViews.map((view) => mapCipherViewToBitCredential(view)));
} // }
// }
async function mapCipherViewToBitCredential(cipherView: CipherView): Promise<BitCredential> {
const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.keyValue); // interface AuthDataParams {
const privateKey = await crypto.subtle.importKey( // rpId: string;
"pkcs8", // credentialId: CredentialId;
keyBuffer, // userPresence: boolean;
{ // userVerification: boolean;
name: cipherView.fido2Key.keyType, // keyPair?: CryptoKeyPair;
namedCurve: cipherView.fido2Key.keyCurve, // }
},
true, // async function mapCipherViewToBitCredential(cipherView: CipherView): Promise<BitCredential> {
KeyUsages // const keyBuffer = Fido2Utils.stringToBuffer(cipherView.fido2Key.keyValue);
); // const privateKey = await crypto.subtle.importKey(
// "pkcs8",
return { // keyBuffer,
credentialId: new CredentialId(cipherView.id), // {
keyType: cipherView.fido2Key.keyType, // name: cipherView.fido2Key.keyType,
keyCurve: cipherView.fido2Key.keyCurve, // namedCurve: cipherView.fido2Key.keyCurve,
keyValue: privateKey, // },
rpId: cipherView.fido2Key.rpId, // true,
rpName: cipherView.fido2Key.rpName, // KeyUsages
userHandle: Fido2Utils.stringToBuffer(cipherView.fido2Key.userHandle), // );
userName: cipherView.fido2Key.userName,
origin: cipherView.fido2Key.origin, // return {
}; // credentialId: new CredentialId(cipherView.id),
} // keyType: cipherView.fido2Key.keyType,
// keyCurve: cipherView.fido2Key.keyCurve,
async function generateAuthData(params: AuthDataParams) { // keyValue: privateKey,
const encoder = new TextEncoder(); // rpId: cipherView.fido2Key.rpId,
// rpName: cipherView.fido2Key.rpName,
const authData: Array<number> = []; // userHandle: Fido2Utils.stringToBuffer(cipherView.fido2Key.userHandle),
// userName: cipherView.fido2Key.userName,
const rpIdHash = new Uint8Array( // origin: cipherView.fido2Key.origin,
await crypto.subtle.digest({ name: "SHA-256" }, encoder.encode(params.rpId)) // };
); // }
authData.push(...rpIdHash);
// async function generateAuthData(params: AuthDataParams) {
const flags = authDataFlags({ // const encoder = new TextEncoder();
extensionData: false,
attestationData: params.keyPair !== undefined, // const authData: Array<number> = [];
userVerification: params.userVerification,
userPresence: params.userPresence, // const rpIdHash = new Uint8Array(
}); // await crypto.subtle.digest({ name: "SHA-256" }, encoder.encode(params.rpId))
authData.push(flags); // );
// authData.push(...rpIdHash);
// 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 flags = authDataFlags({
const now = new Date().getTime() / 1000; // extensionData: false,
authData.push( // attestationData: params.keyPair !== undefined,
((now & 0xff000000) >> 24) & 0xff, // userVerification: params.userVerification,
((now & 0x00ff0000) >> 16) & 0xff, // userPresence: params.userPresence,
((now & 0x0000ff00) >> 8) & 0xff, // });
now & 0x000000ff // authData.push(flags);
);
// // add 4 bytes of counter - we use time in epoch seconds as monotonic counter
// attestedCredentialData // // TODO: Consider changing this to a cryptographically safe random number
const attestedCredentialData: Array<number> = []; // const now = new Date().getTime() / 1000;
// authData.push(
// Use 0 because we're self-signing at the moment // ((now & 0xff000000) >> 24) & 0xff,
const aaguid = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // ((now & 0x00ff0000) >> 16) & 0xff,
attestedCredentialData.push(...aaguid); // ((now & 0x0000ff00) >> 8) & 0xff,
// now & 0x000000ff
// credentialIdLength (2 bytes) and credential Id // );
const rawId = params.credentialId.raw;
const credentialIdLength = [(rawId.length - (rawId.length & 0xff)) / 256, rawId.length & 0xff]; // // attestedCredentialData
attestedCredentialData.push(...credentialIdLength); // const attestedCredentialData: Array<number> = [];
attestedCredentialData.push(...rawId);
// // Use 0 because we're self-signing at the moment
if (params.keyPair) { // const aaguid = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
const publicKeyJwk = await crypto.subtle.exportKey("jwk", params.keyPair.publicKey); // attestedCredentialData.push(...aaguid);
// COSE format of the EC256 key
const keyX = Utils.fromUrlB64ToArray(publicKeyJwk.x); // // credentialIdLength (2 bytes) and credential Id
const keyY = Utils.fromUrlB64ToArray(publicKeyJwk.y); // const rawId = params.credentialId.raw;
// const credentialIdLength = [(rawId.length - (rawId.length & 0xff)) / 256, rawId.length & 0xff];
// const credPublicKeyCOSE = { // attestedCredentialData.push(...credentialIdLength);
// "1": 2, // kty // attestedCredentialData.push(...rawId);
// "3": -7, // alg
// "-1": 1, // crv // if (params.keyPair) {
// "-2": keyX, // const publicKeyJwk = await crypto.subtle.exportKey("jwk", params.keyPair.publicKey);
// "-3": keyY, // // COSE format of the EC256 key
// }; // const keyX = Utils.fromUrlB64ToArray(publicKeyJwk.x);
// const coseBytes = new Uint8Array(cbor.encode(credPublicKeyCOSE)); // const keyY = Utils.fromUrlB64ToArray(publicKeyJwk.y);
// Can't get `cbor-redux` to encode in CTAP2 canonical CBOR. So we do it manually: // // const credPublicKeyCOSE = {
const coseBytes = new Uint8Array(77); // // "1": 2, // kty
coseBytes.set([0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20], 0); // // "3": -7, // alg
coseBytes.set(keyX, 10); // // "-1": 1, // crv
coseBytes.set([0x22, 0x58, 0x20], 10 + 32); // // "-2": keyX,
coseBytes.set(keyY, 10 + 32 + 3); // // "-3": keyY,
// // };
// credential public key - convert to array from CBOR encoded COSE key // // const coseBytes = new Uint8Array(cbor.encode(credPublicKeyCOSE));
attestedCredentialData.push(...coseBytes);
// // Can't get `cbor-redux` to encode in CTAP2 canonical CBOR. So we do it manually:
authData.push(...attestedCredentialData); // const coseBytes = new Uint8Array(77);
} // coseBytes.set([0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58, 0x20], 0);
// coseBytes.set(keyX, 10);
return new Uint8Array(authData); // coseBytes.set([0x22, 0x58, 0x20], 10 + 32);
} // coseBytes.set(keyY, 10 + 32 + 3);
interface SignatureParams { // // credential public key - convert to array from CBOR encoded COSE key
authData: Uint8Array; // attestedCredentialData.push(...coseBytes);
clientData: Uint8Array;
privateKey: CryptoKey; // authData.push(...attestedCredentialData);
} // }
async function generateSignature(params: SignatureParams) { // return new Uint8Array(authData);
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( // interface SignatureParams {
await window.crypto.subtle.sign( // authData: Uint8Array;
{ // clientData: Uint8Array;
name: "ECDSA", // privateKey: CryptoKey;
hash: { name: "SHA-256" }, // }
},
params.privateKey, // async function generateSignature(params: SignatureParams) {
sigBase // 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(
const asn1Der_signature = joseToDer(p1336_signature, "ES256"); // {
// name: "ECDSA",
return asn1Der_signature; // hash: { name: "SHA-256" },
} // },
// params.privateKey,
interface Flags { // sigBase
extensionData: boolean; // )
attestationData: boolean; // );
userVerification: boolean;
userPresence: boolean; // const asn1Der_signature = joseToDer(p1336_signature, "ES256");
}
// return asn1Der_signature;
function authDataFlags(options: Flags): number { // }
let flags = 0;
// interface Flags {
if (options.extensionData) { // extensionData: boolean;
flags |= 0b1000000; // attestationData: boolean;
} // userVerification: boolean;
// userPresence: boolean;
if (options.attestationData) { // }
flags |= 0b01000000;
} // function authDataFlags(options: Flags): number {
// let flags = 0;
if (options.userVerification) {
flags |= 0b00000100; // if (options.extensionData) {
} // flags |= 0b1000000;
// }
if (options.userPresence) {
flags |= 0b00000001; // if (options.attestationData) {
} // flags |= 0b01000000;
// }
return flags;
} // if (options.userVerification) {
// flags |= 0b00000100;
function setAbortTimeout( // }
abortController: AbortController,
userVerification: UserVerification, // if (options.userPresence) {
timeout?: number // flags |= 0b00000001;
): number { // }
let clampedTimeout: number;
// return flags;
if (userVerification === "discouraged") { // }
timeout = timeout ?? TIMEOUTS.NO_VERIFICATION.DEFAULT;
clampedTimeout = Math.max( // function setAbortTimeout(
TIMEOUTS.NO_VERIFICATION.MIN, // abortController: AbortController,
Math.min(timeout, TIMEOUTS.NO_VERIFICATION.MAX) // userVerification: UserVerification,
); // timeout?: number
} else { // ): number {
timeout = timeout ?? TIMEOUTS.WITH_VERIFICATION.DEFAULT; // let clampedTimeout: number;
clampedTimeout = Math.max(
TIMEOUTS.WITH_VERIFICATION.MIN, // if (userVerification === "discouraged") {
Math.min(timeout, TIMEOUTS.WITH_VERIFICATION.MAX) // timeout = timeout ?? TIMEOUTS.NO_VERIFICATION.DEFAULT;
); // clampedTimeout = Math.max(
} // TIMEOUTS.NO_VERIFICATION.MIN,
// Math.min(timeout, TIMEOUTS.NO_VERIFICATION.MAX)
return window.setTimeout(() => abortController.abort(), clampedTimeout); // );
} // } 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);
// }