@metamask-previews/passkey-controller 0.0.0-preview-4c0846313 → 0.0.0-preview-dc38a2562

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@ var _PasskeyController_instances, _PasskeyController_ceremonyManager, _PasskeyCo
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.PasskeyController = exports.passkeyControllerSelectors = exports.getDefaultPasskeyControllerState = void 0;
16
16
  const base_controller_1 = require("@metamask/base-controller");
17
+ const utils_1 = require("@metamask/utils");
17
18
  const webcrypto_1 = require("@noble/ciphers/webcrypto");
18
19
  const ceremony_manager_1 = require("./ceremony-manager.cjs");
19
20
  const constants_1 = require("./constants.cjs");
@@ -242,6 +243,7 @@ class PasskeyController extends base_controller_1.BaseController {
242
243
  publicKey: (0, encoding_1.bytesToBase64URL)(registrationInfo.publicKey),
243
244
  counter: registrationInfo.counter,
244
245
  transports: registrationInfo.transports,
246
+ aaguid: registrationInfo.aaguid,
245
247
  },
246
248
  encryptedVaultKey: { ciphertext, iv },
247
249
  keyDerivation,
@@ -336,7 +338,7 @@ class PasskeyController extends base_controller_1.BaseController {
336
338
  }
337
339
  // check if vault key matches
338
340
  const { oldVaultKey, newVaultKey } = params;
339
- if (decryptedVaultKey !== oldVaultKey) {
341
+ if (!(0, utils_1.areUint8ArraysEqual)((0, utils_1.stringToBytes)(decryptedVaultKey), (0, utils_1.stringToBytes)(oldVaultKey))) {
340
342
  log('Passkey renewal rejected: decrypted vault key does not match oldVaultKey');
341
343
  throw new errors_1.PasskeyControllerError(constants_1.PasskeyControllerErrorMessage.VaultKeyMismatch, { code: constants_1.PasskeyControllerErrorCode.VaultKeyMismatch });
342
344
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PasskeyController.cjs","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\n\nimport { WEBAUTHN_TIMEOUT_MS, CeremonyManager } from './ceremony-manager';\nimport {\n controllerName,\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nimport { PasskeyControllerError } from './errors';\nimport {\n deriveKeyFromAuthenticationResponse,\n deriveKeyFromRegistrationResponse,\n} from './key-derivation';\nimport { createModuleLogger, projectLogger } from './logger';\nimport type { PasskeyRecord } from './types';\nimport { decryptWithKey, encryptWithKey } from './utils/crypto';\nimport { base64URLToBytes, bytesToBase64URL } from './utils/encoding';\nimport { COSEALG } from './webauthn/constants';\nimport { decodeClientDataJSON } from './webauthn/decode-client-data-json';\nimport type {\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n} from './webauthn/types';\nimport { verifyAuthenticationResponse } from './webauthn/verify-authentication-response';\nimport { verifyRegistrationResponse } from './webauthn/verify-registration-response';\n\nexport type PasskeyControllerState = {\n passkeyRecord: PasskeyRecord | null;\n};\n\nexport type PasskeyControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n PasskeyControllerState\n>;\n\n/**\n * Actions exposed by {@link PasskeyController} on its messenger.\n *\n * Only `:getState` is exposed. Derived enrollment status is available via\n * {@link passkeyControllerSelectors.selectIsPasskeyEnrolled}, and lifecycle\n * methods ({@link PasskeyController.generateRegistrationOptions},\n * {@link PasskeyController.protectVaultKeyWithPasskey}, etc.) accept or\n * return non-`Json` runtime values (WebAuthn `PublicKeyCredential` objects\n * and the vault key string), so they require a direct controller reference.\n */\nexport type PasskeyControllerActions = PasskeyControllerGetStateAction;\n\nexport type PasskeyControllerStateChangedEvent = ControllerStateChangedEvent<\n typeof controllerName,\n PasskeyControllerState\n>;\n\nexport type PasskeyControllerEvents = PasskeyControllerStateChangedEvent;\n\nexport type PasskeyControllerMessenger = Messenger<\n typeof controllerName,\n PasskeyControllerActions,\n PasskeyControllerEvents\n>;\n\n/**\n * Returns the default (empty) state for {@link PasskeyController}.\n *\n * @returns A fresh state object with no enrolled passkey.\n */\nexport function getDefaultPasskeyControllerState(): PasskeyControllerState {\n return { passkeyRecord: null };\n}\n\nconst passkeyControllerMetadata = {\n passkeyRecord: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<PasskeyControllerState>;\n\nconst log = createModuleLogger(projectLogger, controllerName);\n\n/**\n * Selectors for {@link PasskeyControllerState}.\n *\n * Use these instead of dedicated getter methods on the controller, so that\n * derived values can be consumed from Redux selectors and other places that\n * only have access to a state object.\n */\nexport const passkeyControllerSelectors = {\n selectIsPasskeyEnrolled: (state: PasskeyControllerState): boolean =>\n state.passkeyRecord !== null,\n};\n\n/**\n * Passkey-based protection for the vault encryption key (WebAuthn).\n *\n * Uses PRF-backed derivation when available; otherwise uses the credential\n * `userHandle`.\n */\nexport class PasskeyController extends BaseController<\n typeof controllerName,\n PasskeyControllerState,\n PasskeyControllerMessenger\n> {\n readonly #ceremonyManager = new CeremonyManager();\n\n readonly #rpID: string;\n\n readonly #rpName: string;\n\n readonly #expectedOrigin: string | string[];\n\n readonly #userName: string;\n\n readonly #userDisplayName: string;\n\n /**\n * Constructs a new {@link PasskeyController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - Initial state. Missing properties are filled in with\n * defaults from {@link getDefaultPasskeyControllerState}.\n * @param args.rpID - WebAuthn Relying Party ID (typically the eTLD+1 of the\n * client origin, or `localhost` in dev).\n * @param args.rpName - Human-readable Relying Party name shown by the OS\n * passkey UI.\n * @param args.expectedOrigin - One or more acceptable origins for the\n * `clientDataJSON.origin` check (e.g. `chrome-extension://...`).\n * @param args.userName - Optional `user.name` shown by the OS passkey UI.\n * Defaults to `rpName` so client builds (Stable, Flask, etc.) can\n * differentiate without changes here.\n * @param args.userDisplayName - Optional `user.displayName` shown by the OS\n * passkey UI. Defaults to `rpName`.\n */\n constructor({\n messenger,\n state = {},\n rpID,\n rpName,\n expectedOrigin,\n userName,\n userDisplayName,\n }: {\n messenger: PasskeyControllerMessenger;\n state?: Partial<PasskeyControllerState>;\n rpID: string;\n rpName: string;\n expectedOrigin: string | string[];\n userName?: string;\n userDisplayName?: string;\n }) {\n super({\n messenger,\n metadata: passkeyControllerMetadata,\n name: controllerName,\n state: { ...getDefaultPasskeyControllerState(), ...state },\n });\n\n this.#rpID = rpID;\n this.#rpName = rpName;\n this.#expectedOrigin = expectedOrigin;\n this.#userName = userName ?? rpName;\n this.#userDisplayName = userDisplayName ?? rpName;\n }\n\n #requireEnrolled(): PasskeyRecord {\n const record = this.state.passkeyRecord;\n if (!record) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n return record;\n }\n\n #getChallengeFromClientData(clientDataJSON: string): string {\n return decodeClientDataJSON(clientDataJSON).challenge;\n }\n\n /**\n * Checks if the passkey is enrolled.\n *\n * @returns Whether the passkey is enrolled.\n */\n isPasskeyEnrolled(): boolean {\n return passkeyControllerSelectors.selectIsPasskeyEnrolled(this.state);\n }\n\n /**\n * Registration options for enrolling a passkey.\n *\n * Call before {@link protectVaultKeyWithPasskey}.\n *\n * @param creationOptionsConfig - Optional configuration.\n * @param creationOptionsConfig.prfAvailable - Omit PRF when `false`. Default `true`.\n * @returns Options for `navigator.credentials.create()`.\n */\n generateRegistrationOptions(creationOptionsConfig?: {\n prfAvailable?: boolean;\n }): PasskeyRegistrationOptions {\n const includePrf = creationOptionsConfig?.prfAvailable !== false;\n const prfSalt = includePrf\n ? bytesToBase64URL(randomBytes(32).slice())\n : undefined;\n const userHandle = bytesToBase64URL(randomBytes(64).slice());\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (prfSalt) {\n extensions.prf = { eval: { first: prfSalt } };\n }\n\n const options: PasskeyRegistrationOptions = {\n rp: { name: this.#rpName, id: this.#rpID },\n user: {\n id: userHandle,\n name: this.#userName,\n displayName: this.#userDisplayName,\n },\n challenge,\n pubKeyCredParams: [\n { alg: COSEALG.EdDSA, type: 'public-key' },\n { alg: COSEALG.ES256, type: 'public-key' },\n { alg: COSEALG.RS256, type: 'public-key' },\n ],\n timeout: WEBAUTHN_TIMEOUT_MS,\n authenticatorSelection: {\n userVerification: 'preferred',\n authenticatorAttachment: 'platform',\n residentKey: 'preferred',\n },\n hints: ['client-device', 'hybrid'],\n attestation: 'none',\n ...(Object.keys(extensions).length > 0 ? { extensions } : {}),\n };\n\n this.#ceremonyManager.saveRegistrationCeremony(challenge, {\n userHandle,\n prfSalt: prfSalt ?? '',\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * WebAuthn request options for authenticating with the enrolled passkey.\n *\n * Call before {@link retrieveVaultKeyWithPasskey},\n * {@link verifyPasskeyAuthentication}, or {@link renewVaultKeyProtection}.\n *\n * @returns Options for `navigator.credentials.get()`.\n */\n generateAuthenticationOptions(): PasskeyAuthenticationOptions {\n const record = this.#requireEnrolled();\n\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (record.keyDerivation.method === 'prf') {\n extensions.prf = { eval: { first: record.keyDerivation.prfSalt } };\n }\n\n const options: PasskeyAuthenticationOptions = {\n challenge,\n rpId: this.#rpID,\n allowCredentials: [\n {\n id: record.credential.id,\n type: 'public-key',\n transports: record.credential.transports,\n },\n ],\n userVerification: 'preferred',\n hints: ['client-device', 'hybrid'],\n timeout: WEBAUTHN_TIMEOUT_MS,\n extensions,\n };\n\n this.#ceremonyManager.saveAuthenticationCeremony(challenge, {\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * Completes enrollment and binds the vault key to the new passkey.\n *\n * @param params - Protection parameters.\n * @param params.registrationResponse - Credential from `navigator.credentials.create()`.\n * @param params.vaultKey - Vault encryption key to protect.\n */\n async protectVaultKeyWithPasskey(params: {\n registrationResponse: PasskeyRegistrationResponse;\n vaultKey: string;\n }): Promise<void> {\n const { registrationResponse, vaultKey } = params;\n\n // get challenge\n const challenge = this.#getChallengeFromClientData(\n registrationResponse.response.clientDataJSON,\n );\n const registrationCeremony =\n this.#ceremonyManager.getRegistrationCeremony(challenge);\n if (!registrationCeremony) {\n log('No active passkey registration ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoRegistrationCeremony,\n { code: PasskeyControllerErrorCode.NoRegistrationCeremony },\n );\n }\n\n try {\n // verify registration response\n const { verified, registrationInfo } = await verifyRegistrationResponse({\n response: registrationResponse,\n expectedChallenge: registrationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n requireUserVerification: false,\n }).catch((error) => {\n log('Error verifying passkey registration response', error);\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.RegistrationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!verified || !registrationInfo) {\n log(\n 'Passkey registration verification returned unverified or missing registration info',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n { code: PasskeyControllerErrorCode.RegistrationVerificationFailed },\n );\n }\n\n // derive key\n const { encKey, keyDerivation } = deriveKeyFromRegistrationResponse(\n registrationResponse,\n registrationCeremony,\n registrationInfo.credentialId,\n );\n\n // encrypt vault key\n const { ciphertext, iv } = encryptWithKey(vaultKey, encKey);\n\n // persist passkey record\n this.update((state) => {\n state.passkeyRecord = {\n credential: {\n id: registrationInfo.credentialId,\n publicKey: bytesToBase64URL(registrationInfo.publicKey),\n counter: registrationInfo.counter,\n transports: registrationInfo.transports,\n },\n encryptedVaultKey: { ciphertext, iv },\n keyDerivation,\n };\n });\n } finally {\n this.#ceremonyManager.deleteRegistrationCeremony(challenge);\n }\n }\n\n /**\n * Returns the decrypted vault encryption key from the passkey authentication\n * response.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns The vault encryption key.\n */\n async retrieveVaultKeyWithPasskey(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<string> {\n // verify authentication response\n await this.#verifyAuthenticationResponse(authenticationResponse);\n\n // derive key (#verifyAuthenticationResponse guarantees enrolled)\n const passkeyRecord = this.#requireEnrolled();\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let vaultKey: string;\n try {\n vaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (cause) {\n log(\n 'Error decrypting vault key with passkey',\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: cause instanceof Error ? cause : new Error(String(cause)),\n },\n );\n }\n\n return vaultKey;\n }\n\n /**\n * Returns whether passkey authentication succeeds for this credential (same\n * work as {@link retrieveVaultKeyWithPasskey} without exposing the vault key).\n *\n * Returns `false` only when the failure is a {@link PasskeyControllerError}\n * with a defined `code`. Unexpected errors (e.g. malformed `clientDataJSON`,\n * internal bugs) are rethrown.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns `true` if authentication succeeds, otherwise `false`.\n */\n async verifyPasskeyAuthentication(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<boolean> {\n try {\n await this.retrieveVaultKeyWithPasskey(authenticationResponse);\n return true;\n } catch (error: unknown) {\n if (error instanceof PasskeyControllerError && error.code !== undefined) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Updates the vault encryption key for the same passkey (e.g. after a password change).\n *\n * Caller MUST first verify the assertion via {@link verifyPasskeyAuthentication}\n * or {@link retrieveVaultKeyWithPasskey}. This method does not re-verify\n * because the ceremony is single-use (deleted on verify) and the signature\n * counter is advanced (replay would be rejected). Authentication here is\n * enforced by the prior verification plus the `oldVaultKey` match below.\n *\n * @param params - Renewal parameters.\n * @param params.authenticationResponse - Credential from `navigator.credentials.get()`,\n * already verified by the caller.\n * @param params.oldVaultKey - Expected current vault key.\n * @param params.newVaultKey - New vault key to protect.\n */\n async renewVaultKeyProtection(params: {\n authenticationResponse: PasskeyAuthenticationResponse;\n oldVaultKey: string;\n newVaultKey: string;\n }): Promise<void> {\n const { authenticationResponse } = params;\n const passkeyRecord = this.#requireEnrolled();\n\n // derive key\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let decryptedVaultKey: string;\n try {\n decryptedVaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (error) {\n log(\n 'Error decrypting vault key during passkey vault key renewal',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n }\n\n // check if vault key matches\n const { oldVaultKey, newVaultKey } = params;\n if (decryptedVaultKey !== oldVaultKey) {\n log(\n 'Passkey renewal rejected: decrypted vault key does not match oldVaultKey',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyMismatch,\n { code: PasskeyControllerErrorCode.VaultKeyMismatch },\n );\n }\n\n // encrypt new vault key\n const { ciphertext, iv } = encryptWithKey(newVaultKey, encKey);\n\n // persist passkey record (mutate current state only for vault key material)\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n state.passkeyRecord.encryptedVaultKey = { ciphertext, iv };\n });\n }\n\n /**\n * Unenrolls the passkey, removing the protected vault key material.\n */\n removePasskey(): void {\n this.update(() => getDefaultPasskeyControllerState());\n this.#ceremonyManager.clear();\n }\n\n /**\n * Resets state and clears in-flight registration/authentication ceremonies.\n */\n clearState(): void {\n this.removePasskey();\n }\n\n /**\n * Releases all in-flight ceremony state and tears down the messenger.\n */\n destroy(): void {\n this.#ceremonyManager.clear();\n super.destroy();\n }\n\n /**\n * Verifies an authentication response for the enrolled passkey.\n *\n * @param authenticationResponse - Authentication result JSON.\n */\n async #verifyAuthenticationResponse(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<void> {\n let challenge: string | undefined;\n try {\n // get challenge\n challenge = this.#getChallengeFromClientData(\n authenticationResponse.response.clientDataJSON,\n );\n\n // get passkey record\n const record = this.#requireEnrolled();\n\n // get authentication ceremony\n const authenticationCeremony =\n this.#ceremonyManager.getAuthenticationCeremony(challenge);\n if (!authenticationCeremony) {\n log('No active passkey authentication ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoAuthenticationCeremony,\n { code: PasskeyControllerErrorCode.NoAuthenticationCeremony },\n );\n }\n\n // verify authentication response\n const result = await verifyAuthenticationResponse({\n response: authenticationResponse,\n expectedChallenge: authenticationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n credential: {\n id: record.credential.id,\n publicKey: base64URLToBytes(record.credential.publicKey),\n counter: record.credential.counter,\n transports: record.credential.transports,\n },\n // UV optional for device compatibility; vault key remains password-gated.\n requireUserVerification: false,\n }).catch((error) => {\n log(\n 'Error verifying passkey authentication response',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!result.verified) {\n log('Passkey authentication verification returned unverified');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n },\n );\n }\n\n // persist passkey record with updated counter without clobbering concurrent updates\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n { code: PasskeyControllerErrorCode.NotEnrolled },\n );\n }\n const latest = state.passkeyRecord;\n latest.credential.counter = Math.max(\n result.authenticationInfo.newCounter,\n latest.credential.counter,\n );\n });\n } finally {\n if (challenge) {\n this.#ceremonyManager.deleteAuthenticationCeremony(challenge);\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"PasskeyController.cjs","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { areUint8ArraysEqual, stringToBytes } from '@metamask/utils';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\n\nimport { WEBAUTHN_TIMEOUT_MS, CeremonyManager } from './ceremony-manager';\nimport {\n controllerName,\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nimport { PasskeyControllerError } from './errors';\nimport {\n deriveKeyFromAuthenticationResponse,\n deriveKeyFromRegistrationResponse,\n} from './key-derivation';\nimport { createModuleLogger, projectLogger } from './logger';\nimport type { PasskeyRecord } from './types';\nimport { decryptWithKey, encryptWithKey } from './utils/crypto';\nimport { base64URLToBytes, bytesToBase64URL } from './utils/encoding';\nimport { COSEALG } from './webauthn/constants';\nimport { decodeClientDataJSON } from './webauthn/decode-client-data-json';\nimport type {\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n} from './webauthn/types';\nimport { verifyAuthenticationResponse } from './webauthn/verify-authentication-response';\nimport { verifyRegistrationResponse } from './webauthn/verify-registration-response';\n\nexport type PasskeyControllerState = {\n passkeyRecord: PasskeyRecord | null;\n};\n\nexport type PasskeyControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n PasskeyControllerState\n>;\n\n/**\n * Actions exposed by {@link PasskeyController} on its messenger.\n *\n * Only `:getState` is exposed. Derived enrollment status is available via\n * {@link passkeyControllerSelectors.selectIsPasskeyEnrolled}, and lifecycle\n * methods ({@link PasskeyController.generateRegistrationOptions},\n * {@link PasskeyController.protectVaultKeyWithPasskey}, etc.) accept or\n * return non-`Json` runtime values (WebAuthn `PublicKeyCredential` objects\n * and the vault key string), so they require a direct controller reference.\n */\nexport type PasskeyControllerActions = PasskeyControllerGetStateAction;\n\nexport type PasskeyControllerStateChangedEvent = ControllerStateChangedEvent<\n typeof controllerName,\n PasskeyControllerState\n>;\n\nexport type PasskeyControllerEvents = PasskeyControllerStateChangedEvent;\n\nexport type PasskeyControllerMessenger = Messenger<\n typeof controllerName,\n PasskeyControllerActions,\n PasskeyControllerEvents\n>;\n\n/**\n * Returns the default (empty) state for {@link PasskeyController}.\n *\n * @returns A fresh state object with no enrolled passkey.\n */\nexport function getDefaultPasskeyControllerState(): PasskeyControllerState {\n return { passkeyRecord: null };\n}\n\nconst passkeyControllerMetadata = {\n passkeyRecord: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<PasskeyControllerState>;\n\nconst log = createModuleLogger(projectLogger, controllerName);\n\n/**\n * Selectors for {@link PasskeyControllerState}.\n *\n * Use these instead of dedicated getter methods on the controller, so that\n * derived values can be consumed from Redux selectors and other places that\n * only have access to a state object.\n */\nexport const passkeyControllerSelectors = {\n selectIsPasskeyEnrolled: (state: PasskeyControllerState): boolean =>\n state.passkeyRecord !== null,\n};\n\n/**\n * Passkey-based protection for the vault encryption key (WebAuthn).\n *\n * Uses PRF-backed derivation when available; otherwise uses the credential\n * `userHandle`.\n */\nexport class PasskeyController extends BaseController<\n typeof controllerName,\n PasskeyControllerState,\n PasskeyControllerMessenger\n> {\n readonly #ceremonyManager = new CeremonyManager();\n\n readonly #rpID: string;\n\n readonly #rpName: string;\n\n readonly #expectedOrigin: string | string[];\n\n readonly #userName: string;\n\n readonly #userDisplayName: string;\n\n /**\n * Constructs a new {@link PasskeyController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - Initial state. Missing properties are filled in with\n * defaults from {@link getDefaultPasskeyControllerState}.\n * @param args.rpID - WebAuthn Relying Party ID (typically the eTLD+1 of the\n * client origin, or `localhost` in dev).\n * @param args.rpName - Human-readable Relying Party name shown by the OS\n * passkey UI.\n * @param args.expectedOrigin - One or more acceptable origins for the\n * `clientDataJSON.origin` check (e.g. `chrome-extension://...`).\n * @param args.userName - Optional `user.name` shown by the OS passkey UI.\n * Defaults to `rpName` so client builds (Stable, Flask, etc.) can\n * differentiate without changes here.\n * @param args.userDisplayName - Optional `user.displayName` shown by the OS\n * passkey UI. Defaults to `rpName`.\n */\n constructor({\n messenger,\n state = {},\n rpID,\n rpName,\n expectedOrigin,\n userName,\n userDisplayName,\n }: {\n messenger: PasskeyControllerMessenger;\n state?: Partial<PasskeyControllerState>;\n rpID: string;\n rpName: string;\n expectedOrigin: string | string[];\n userName?: string;\n userDisplayName?: string;\n }) {\n super({\n messenger,\n metadata: passkeyControllerMetadata,\n name: controllerName,\n state: { ...getDefaultPasskeyControllerState(), ...state },\n });\n\n this.#rpID = rpID;\n this.#rpName = rpName;\n this.#expectedOrigin = expectedOrigin;\n this.#userName = userName ?? rpName;\n this.#userDisplayName = userDisplayName ?? rpName;\n }\n\n #requireEnrolled(): PasskeyRecord {\n const record = this.state.passkeyRecord;\n if (!record) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n return record;\n }\n\n #getChallengeFromClientData(clientDataJSON: string): string {\n return decodeClientDataJSON(clientDataJSON).challenge;\n }\n\n /**\n * Checks if the passkey is enrolled.\n *\n * @returns Whether the passkey is enrolled.\n */\n isPasskeyEnrolled(): boolean {\n return passkeyControllerSelectors.selectIsPasskeyEnrolled(this.state);\n }\n\n /**\n * Registration options for enrolling a passkey.\n *\n * Call before {@link protectVaultKeyWithPasskey}.\n *\n * @param creationOptionsConfig - Optional configuration.\n * @param creationOptionsConfig.prfAvailable - Omit PRF when `false`. Default `true`.\n * @returns Options for `navigator.credentials.create()`.\n */\n generateRegistrationOptions(creationOptionsConfig?: {\n prfAvailable?: boolean;\n }): PasskeyRegistrationOptions {\n const includePrf = creationOptionsConfig?.prfAvailable !== false;\n const prfSalt = includePrf\n ? bytesToBase64URL(randomBytes(32).slice())\n : undefined;\n const userHandle = bytesToBase64URL(randomBytes(64).slice());\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (prfSalt) {\n extensions.prf = { eval: { first: prfSalt } };\n }\n\n const options: PasskeyRegistrationOptions = {\n rp: { name: this.#rpName, id: this.#rpID },\n user: {\n id: userHandle,\n name: this.#userName,\n displayName: this.#userDisplayName,\n },\n challenge,\n pubKeyCredParams: [\n { alg: COSEALG.EdDSA, type: 'public-key' },\n { alg: COSEALG.ES256, type: 'public-key' },\n { alg: COSEALG.RS256, type: 'public-key' },\n ],\n timeout: WEBAUTHN_TIMEOUT_MS,\n authenticatorSelection: {\n userVerification: 'preferred',\n authenticatorAttachment: 'platform',\n residentKey: 'preferred',\n },\n hints: ['client-device', 'hybrid'],\n attestation: 'none',\n ...(Object.keys(extensions).length > 0 ? { extensions } : {}),\n };\n\n this.#ceremonyManager.saveRegistrationCeremony(challenge, {\n userHandle,\n prfSalt: prfSalt ?? '',\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * WebAuthn request options for authenticating with the enrolled passkey.\n *\n * Call before {@link retrieveVaultKeyWithPasskey},\n * {@link verifyPasskeyAuthentication}, or {@link renewVaultKeyProtection}.\n *\n * @returns Options for `navigator.credentials.get()`.\n */\n generateAuthenticationOptions(): PasskeyAuthenticationOptions {\n const record = this.#requireEnrolled();\n\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (record.keyDerivation.method === 'prf') {\n extensions.prf = { eval: { first: record.keyDerivation.prfSalt } };\n }\n\n const options: PasskeyAuthenticationOptions = {\n challenge,\n rpId: this.#rpID,\n allowCredentials: [\n {\n id: record.credential.id,\n type: 'public-key',\n transports: record.credential.transports,\n },\n ],\n userVerification: 'preferred',\n hints: ['client-device', 'hybrid'],\n timeout: WEBAUTHN_TIMEOUT_MS,\n extensions,\n };\n\n this.#ceremonyManager.saveAuthenticationCeremony(challenge, {\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * Completes enrollment and binds the vault key to the new passkey.\n *\n * @param params - Protection parameters.\n * @param params.registrationResponse - Credential from `navigator.credentials.create()`.\n * @param params.vaultKey - Vault encryption key to protect.\n */\n async protectVaultKeyWithPasskey(params: {\n registrationResponse: PasskeyRegistrationResponse;\n vaultKey: string;\n }): Promise<void> {\n const { registrationResponse, vaultKey } = params;\n\n // get challenge\n const challenge = this.#getChallengeFromClientData(\n registrationResponse.response.clientDataJSON,\n );\n const registrationCeremony =\n this.#ceremonyManager.getRegistrationCeremony(challenge);\n if (!registrationCeremony) {\n log('No active passkey registration ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoRegistrationCeremony,\n { code: PasskeyControllerErrorCode.NoRegistrationCeremony },\n );\n }\n\n try {\n // verify registration response\n const { verified, registrationInfo } = await verifyRegistrationResponse({\n response: registrationResponse,\n expectedChallenge: registrationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n requireUserVerification: false,\n }).catch((error) => {\n log('Error verifying passkey registration response', error);\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.RegistrationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!verified || !registrationInfo) {\n log(\n 'Passkey registration verification returned unverified or missing registration info',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n { code: PasskeyControllerErrorCode.RegistrationVerificationFailed },\n );\n }\n\n // derive key\n const { encKey, keyDerivation } = deriveKeyFromRegistrationResponse(\n registrationResponse,\n registrationCeremony,\n registrationInfo.credentialId,\n );\n\n // encrypt vault key\n const { ciphertext, iv } = encryptWithKey(vaultKey, encKey);\n\n // persist passkey record\n this.update((state) => {\n state.passkeyRecord = {\n credential: {\n id: registrationInfo.credentialId,\n publicKey: bytesToBase64URL(registrationInfo.publicKey),\n counter: registrationInfo.counter,\n transports: registrationInfo.transports,\n aaguid: registrationInfo.aaguid,\n },\n encryptedVaultKey: { ciphertext, iv },\n keyDerivation,\n };\n });\n } finally {\n this.#ceremonyManager.deleteRegistrationCeremony(challenge);\n }\n }\n\n /**\n * Returns the decrypted vault encryption key from the passkey authentication\n * response.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns The vault encryption key.\n */\n async retrieveVaultKeyWithPasskey(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<string> {\n // verify authentication response\n await this.#verifyAuthenticationResponse(authenticationResponse);\n\n // derive key (#verifyAuthenticationResponse guarantees enrolled)\n const passkeyRecord = this.#requireEnrolled();\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let vaultKey: string;\n try {\n vaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (cause) {\n log(\n 'Error decrypting vault key with passkey',\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: cause instanceof Error ? cause : new Error(String(cause)),\n },\n );\n }\n\n return vaultKey;\n }\n\n /**\n * Returns whether passkey authentication succeeds for this credential (same\n * work as {@link retrieveVaultKeyWithPasskey} without exposing the vault key).\n *\n * Returns `false` only when the failure is a {@link PasskeyControllerError}\n * with a defined `code`. Unexpected errors (e.g. malformed `clientDataJSON`,\n * internal bugs) are rethrown.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns `true` if authentication succeeds, otherwise `false`.\n */\n async verifyPasskeyAuthentication(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<boolean> {\n try {\n await this.retrieveVaultKeyWithPasskey(authenticationResponse);\n return true;\n } catch (error: unknown) {\n if (error instanceof PasskeyControllerError && error.code !== undefined) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Updates the vault encryption key for the same passkey (e.g. after a password change).\n *\n * Caller MUST first verify the assertion via {@link verifyPasskeyAuthentication}\n * or {@link retrieveVaultKeyWithPasskey}. This method does not re-verify\n * because the ceremony is single-use (deleted on verify) and the signature\n * counter is advanced (replay would be rejected). Authentication here is\n * enforced by the prior verification plus the `oldVaultKey` match below.\n *\n * @param params - Renewal parameters.\n * @param params.authenticationResponse - Credential from `navigator.credentials.get()`,\n * already verified by the caller.\n * @param params.oldVaultKey - Expected current vault key.\n * @param params.newVaultKey - New vault key to protect.\n */\n async renewVaultKeyProtection(params: {\n authenticationResponse: PasskeyAuthenticationResponse;\n oldVaultKey: string;\n newVaultKey: string;\n }): Promise<void> {\n const { authenticationResponse } = params;\n const passkeyRecord = this.#requireEnrolled();\n\n // derive key\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let decryptedVaultKey: string;\n try {\n decryptedVaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (error) {\n log(\n 'Error decrypting vault key during passkey vault key renewal',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n }\n\n // check if vault key matches\n const { oldVaultKey, newVaultKey } = params;\n if (\n !areUint8ArraysEqual(\n stringToBytes(decryptedVaultKey),\n stringToBytes(oldVaultKey),\n )\n ) {\n log(\n 'Passkey renewal rejected: decrypted vault key does not match oldVaultKey',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyMismatch,\n { code: PasskeyControllerErrorCode.VaultKeyMismatch },\n );\n }\n\n // encrypt new vault key\n const { ciphertext, iv } = encryptWithKey(newVaultKey, encKey);\n\n // persist passkey record (mutate current state only for vault key material)\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n state.passkeyRecord.encryptedVaultKey = { ciphertext, iv };\n });\n }\n\n /**\n * Unenrolls the passkey, removing the protected vault key material.\n */\n removePasskey(): void {\n this.update(() => getDefaultPasskeyControllerState());\n this.#ceremonyManager.clear();\n }\n\n /**\n * Resets state and clears in-flight registration/authentication ceremonies.\n */\n clearState(): void {\n this.removePasskey();\n }\n\n /**\n * Releases all in-flight ceremony state and tears down the messenger.\n */\n destroy(): void {\n this.#ceremonyManager.clear();\n super.destroy();\n }\n\n /**\n * Verifies an authentication response for the enrolled passkey.\n *\n * @param authenticationResponse - Authentication result JSON.\n */\n async #verifyAuthenticationResponse(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<void> {\n let challenge: string | undefined;\n try {\n // get challenge\n challenge = this.#getChallengeFromClientData(\n authenticationResponse.response.clientDataJSON,\n );\n\n // get passkey record\n const record = this.#requireEnrolled();\n\n // get authentication ceremony\n const authenticationCeremony =\n this.#ceremonyManager.getAuthenticationCeremony(challenge);\n if (!authenticationCeremony) {\n log('No active passkey authentication ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoAuthenticationCeremony,\n { code: PasskeyControllerErrorCode.NoAuthenticationCeremony },\n );\n }\n\n // verify authentication response\n const result = await verifyAuthenticationResponse({\n response: authenticationResponse,\n expectedChallenge: authenticationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n credential: {\n id: record.credential.id,\n publicKey: base64URLToBytes(record.credential.publicKey),\n counter: record.credential.counter,\n transports: record.credential.transports,\n },\n // UV optional for device compatibility; vault key remains password-gated.\n requireUserVerification: false,\n }).catch((error) => {\n log(\n 'Error verifying passkey authentication response',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!result.verified) {\n log('Passkey authentication verification returned unverified');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n },\n );\n }\n\n // persist passkey record with updated counter without clobbering concurrent updates\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n { code: PasskeyControllerErrorCode.NotEnrolled },\n );\n }\n const latest = state.passkeyRecord;\n latest.credential.counter = Math.max(\n result.authenticationInfo.newCounter,\n latest.credential.counter,\n );\n });\n } finally {\n if (challenge) {\n this.#ceremonyManager.deleteAuthenticationCeremony(challenge);\n }\n }\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"PasskeyController.d.cts","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAIrD,OAAO,EACL,cAAc,EAGf,wBAAoB;AAOrB,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAgB;AAK7C,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,0BAA0B,EAC1B,2BAA2B,EAC5B,6BAAyB;AAI1B,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,wBAAwB,CACpE,OAAO,cAAc,EACrB,sBAAsB,CACvB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,wBAAwB,GAAG,+BAA+B,CAAC;AAEvE,MAAM,MAAM,kCAAkC,GAAG,2BAA2B,CAC1E,OAAO,cAAc,EACrB,sBAAsB,CACvB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,kCAAkC,CAAC;AAEzE,MAAM,MAAM,0BAA0B,GAAG,SAAS,CAChD,OAAO,cAAc,EACrB,wBAAwB,EACxB,uBAAuB,CACxB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gCAAgC,IAAI,sBAAsB,CAEzE;AAaD;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B;qCACJ,sBAAsB,KAAG,OAAO;CAElE,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,cAAc,CACnD,OAAO,cAAc,EACrB,sBAAsB,EACtB,0BAA0B,CAC3B;;IAaC;;;;;;;;;;;;;;;;;;OAkBG;gBACS,EACV,SAAS,EACT,KAAU,EACV,IAAI,EACJ,MAAM,EACN,cAAc,EACd,QAAQ,EACR,eAAe,GAChB,EAAE;QACD,SAAS,EAAE,0BAA0B,CAAC;QACtC,KAAK,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;QACxC,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B;IAgCD;;;;OAIG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;;;;;;;OAQG;IACH,2BAA2B,CAAC,qBAAqB,CAAC,EAAE;QAClD,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,0BAA0B;IA+C9B;;;;;;;OAOG;IACH,6BAA6B,IAAI,4BAA4B;IAkC7D;;;;;;OAMG;IACG,0BAA0B,CAAC,MAAM,EAAE;QACvC,oBAAoB,EAAE,2BAA2B,CAAC;QAClD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyEjB;;;;;;OAMG;IACG,2BAA2B,CAC/B,sBAAsB,EAAE,6BAA6B,GACpD,OAAO,CAAC,MAAM,CAAC;IAoClB;;;;;;;;;;OAUG;IACG,2BAA2B,CAC/B,sBAAsB,EAAE,6BAA6B,GACpD,OAAO,CAAC,OAAO,CAAC;IAYnB;;;;;;;;;;;;;;OAcG;IACG,uBAAuB,CAAC,MAAM,EAAE;QACpC,sBAAsB,EAAE,6BAA6B,CAAC;QACtD,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6DjB;;OAEG;IACH,aAAa,IAAI,IAAI;IAKrB;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,OAAO,IAAI,IAAI;CA2FhB"}
1
+ {"version":3,"file":"PasskeyController.d.cts","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAKrD,OAAO,EACL,cAAc,EAGf,wBAAoB;AAOrB,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAgB;AAK7C,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,0BAA0B,EAC1B,2BAA2B,EAC5B,6BAAyB;AAI1B,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,wBAAwB,CACpE,OAAO,cAAc,EACrB,sBAAsB,CACvB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,wBAAwB,GAAG,+BAA+B,CAAC;AAEvE,MAAM,MAAM,kCAAkC,GAAG,2BAA2B,CAC1E,OAAO,cAAc,EACrB,sBAAsB,CACvB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,kCAAkC,CAAC;AAEzE,MAAM,MAAM,0BAA0B,GAAG,SAAS,CAChD,OAAO,cAAc,EACrB,wBAAwB,EACxB,uBAAuB,CACxB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gCAAgC,IAAI,sBAAsB,CAEzE;AAaD;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B;qCACJ,sBAAsB,KAAG,OAAO;CAElE,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,cAAc,CACnD,OAAO,cAAc,EACrB,sBAAsB,EACtB,0BAA0B,CAC3B;;IAaC;;;;;;;;;;;;;;;;;;OAkBG;gBACS,EACV,SAAS,EACT,KAAU,EACV,IAAI,EACJ,MAAM,EACN,cAAc,EACd,QAAQ,EACR,eAAe,GAChB,EAAE;QACD,SAAS,EAAE,0BAA0B,CAAC;QACtC,KAAK,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;QACxC,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B;IAgCD;;;;OAIG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;;;;;;;OAQG;IACH,2BAA2B,CAAC,qBAAqB,CAAC,EAAE;QAClD,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,0BAA0B;IA+C9B;;;;;;;OAOG;IACH,6BAA6B,IAAI,4BAA4B;IAkC7D;;;;;;OAMG;IACG,0BAA0B,CAAC,MAAM,EAAE;QACvC,oBAAoB,EAAE,2BAA2B,CAAC;QAClD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0EjB;;;;;;OAMG;IACG,2BAA2B,CAC/B,sBAAsB,EAAE,6BAA6B,GACpD,OAAO,CAAC,MAAM,CAAC;IAoClB;;;;;;;;;;OAUG;IACG,2BAA2B,CAC/B,sBAAsB,EAAE,6BAA6B,GACpD,OAAO,CAAC,OAAO,CAAC;IAYnB;;;;;;;;;;;;;;OAcG;IACG,uBAAuB,CAAC,MAAM,EAAE;QACpC,sBAAsB,EAAE,6BAA6B,CAAC;QACtD,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkEjB;;OAEG;IACH,aAAa,IAAI,IAAI;IAKrB;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,OAAO,IAAI,IAAI;CA2FhB"}
@@ -1 +1 @@
1
- {"version":3,"file":"PasskeyController.d.mts","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAIrD,OAAO,EACL,cAAc,EAGf,wBAAoB;AAOrB,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAgB;AAK7C,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,0BAA0B,EAC1B,2BAA2B,EAC5B,6BAAyB;AAI1B,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,wBAAwB,CACpE,OAAO,cAAc,EACrB,sBAAsB,CACvB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,wBAAwB,GAAG,+BAA+B,CAAC;AAEvE,MAAM,MAAM,kCAAkC,GAAG,2BAA2B,CAC1E,OAAO,cAAc,EACrB,sBAAsB,CACvB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,kCAAkC,CAAC;AAEzE,MAAM,MAAM,0BAA0B,GAAG,SAAS,CAChD,OAAO,cAAc,EACrB,wBAAwB,EACxB,uBAAuB,CACxB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gCAAgC,IAAI,sBAAsB,CAEzE;AAaD;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B;qCACJ,sBAAsB,KAAG,OAAO;CAElE,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,cAAc,CACnD,OAAO,cAAc,EACrB,sBAAsB,EACtB,0BAA0B,CAC3B;;IAaC;;;;;;;;;;;;;;;;;;OAkBG;gBACS,EACV,SAAS,EACT,KAAU,EACV,IAAI,EACJ,MAAM,EACN,cAAc,EACd,QAAQ,EACR,eAAe,GAChB,EAAE;QACD,SAAS,EAAE,0BAA0B,CAAC;QACtC,KAAK,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;QACxC,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B;IAgCD;;;;OAIG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;;;;;;;OAQG;IACH,2BAA2B,CAAC,qBAAqB,CAAC,EAAE;QAClD,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,0BAA0B;IA+C9B;;;;;;;OAOG;IACH,6BAA6B,IAAI,4BAA4B;IAkC7D;;;;;;OAMG;IACG,0BAA0B,CAAC,MAAM,EAAE;QACvC,oBAAoB,EAAE,2BAA2B,CAAC;QAClD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyEjB;;;;;;OAMG;IACG,2BAA2B,CAC/B,sBAAsB,EAAE,6BAA6B,GACpD,OAAO,CAAC,MAAM,CAAC;IAoClB;;;;;;;;;;OAUG;IACG,2BAA2B,CAC/B,sBAAsB,EAAE,6BAA6B,GACpD,OAAO,CAAC,OAAO,CAAC;IAYnB;;;;;;;;;;;;;;OAcG;IACG,uBAAuB,CAAC,MAAM,EAAE;QACpC,sBAAsB,EAAE,6BAA6B,CAAC;QACtD,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IA6DjB;;OAEG;IACH,aAAa,IAAI,IAAI;IAKrB;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,OAAO,IAAI,IAAI;CA2FhB"}
1
+ {"version":3,"file":"PasskeyController.d.mts","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,2BAA2B,EAE5B,kCAAkC;AACnC,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,4BAA4B;AAKrD,OAAO,EACL,cAAc,EAGf,wBAAoB;AAOrB,OAAO,KAAK,EAAE,aAAa,EAAE,oBAAgB;AAK7C,OAAO,KAAK,EACV,4BAA4B,EAC5B,6BAA6B,EAC7B,0BAA0B,EAC1B,2BAA2B,EAC5B,6BAAyB;AAI1B,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,aAAa,GAAG,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG,wBAAwB,CACpE,OAAO,cAAc,EACrB,sBAAsB,CACvB,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,MAAM,wBAAwB,GAAG,+BAA+B,CAAC;AAEvE,MAAM,MAAM,kCAAkC,GAAG,2BAA2B,CAC1E,OAAO,cAAc,EACrB,sBAAsB,CACvB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,kCAAkC,CAAC;AAEzE,MAAM,MAAM,0BAA0B,GAAG,SAAS,CAChD,OAAO,cAAc,EACrB,wBAAwB,EACxB,uBAAuB,CACxB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,gCAAgC,IAAI,sBAAsB,CAEzE;AAaD;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B;qCACJ,sBAAsB,KAAG,OAAO;CAElE,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,cAAc,CACnD,OAAO,cAAc,EACrB,sBAAsB,EACtB,0BAA0B,CAC3B;;IAaC;;;;;;;;;;;;;;;;;;OAkBG;gBACS,EACV,SAAS,EACT,KAAU,EACV,IAAI,EACJ,MAAM,EACN,cAAc,EACd,QAAQ,EACR,eAAe,GAChB,EAAE;QACD,SAAS,EAAE,0BAA0B,CAAC;QACtC,KAAK,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;QACxC,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;QAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B;IAgCD;;;;OAIG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;;;;;;;OAQG;IACH,2BAA2B,CAAC,qBAAqB,CAAC,EAAE;QAClD,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,GAAG,0BAA0B;IA+C9B;;;;;;;OAOG;IACH,6BAA6B,IAAI,4BAA4B;IAkC7D;;;;;;OAMG;IACG,0BAA0B,CAAC,MAAM,EAAE;QACvC,oBAAoB,EAAE,2BAA2B,CAAC;QAClD,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0EjB;;;;;;OAMG;IACG,2BAA2B,CAC/B,sBAAsB,EAAE,6BAA6B,GACpD,OAAO,CAAC,MAAM,CAAC;IAoClB;;;;;;;;;;OAUG;IACG,2BAA2B,CAC/B,sBAAsB,EAAE,6BAA6B,GACpD,OAAO,CAAC,OAAO,CAAC;IAYnB;;;;;;;;;;;;;;OAcG;IACG,uBAAuB,CAAC,MAAM,EAAE;QACpC,sBAAsB,EAAE,6BAA6B,CAAC;QACtD,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkEjB;;OAEG;IACH,aAAa,IAAI,IAAI;IAKrB;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,OAAO,IAAI,IAAI;CA2FhB"}
@@ -11,6 +11,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
11
11
  };
12
12
  var _PasskeyController_instances, _PasskeyController_ceremonyManager, _PasskeyController_rpID, _PasskeyController_rpName, _PasskeyController_expectedOrigin, _PasskeyController_userName, _PasskeyController_userDisplayName, _PasskeyController_requireEnrolled, _PasskeyController_getChallengeFromClientData, _PasskeyController_verifyAuthenticationResponse;
13
13
  import { BaseController } from "@metamask/base-controller";
14
+ import { areUint8ArraysEqual, stringToBytes } from "@metamask/utils";
14
15
  import { randomBytes } from "@noble/ciphers/webcrypto";
15
16
  import { WEBAUTHN_TIMEOUT_MS, CeremonyManager } from "./ceremony-manager.mjs";
16
17
  import { controllerName, PasskeyControllerErrorCode, PasskeyControllerErrorMessage } from "./constants.mjs";
@@ -238,6 +239,7 @@ export class PasskeyController extends BaseController {
238
239
  publicKey: bytesToBase64URL(registrationInfo.publicKey),
239
240
  counter: registrationInfo.counter,
240
241
  transports: registrationInfo.transports,
242
+ aaguid: registrationInfo.aaguid,
241
243
  },
242
244
  encryptedVaultKey: { ciphertext, iv },
243
245
  keyDerivation,
@@ -332,7 +334,7 @@ export class PasskeyController extends BaseController {
332
334
  }
333
335
  // check if vault key matches
334
336
  const { oldVaultKey, newVaultKey } = params;
335
- if (decryptedVaultKey !== oldVaultKey) {
337
+ if (!areUint8ArraysEqual(stringToBytes(decryptedVaultKey), stringToBytes(oldVaultKey))) {
336
338
  log('Passkey renewal rejected: decrypted vault key does not match oldVaultKey');
337
339
  throw new PasskeyControllerError(PasskeyControllerErrorMessage.VaultKeyMismatch, { code: PasskeyControllerErrorCode.VaultKeyMismatch });
338
340
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PasskeyController.mjs","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":";;;;;;;;;;;;AAKA,OAAO,EAAE,cAAc,EAAE,kCAAkC;AAE3D,OAAO,EAAE,WAAW,EAAE,iCAAiC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,+BAA2B;AAC1E,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,6BAA6B,EAC9B,wBAAoB;AACrB,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAClD,OAAO,EACL,mCAAmC,EACnC,iCAAiC,EAClC,6BAAyB;AAC1B,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,qBAAiB;AAE7D,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,2BAAuB;AAChE,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,6BAAyB;AACtE,OAAO,EAAE,OAAO,EAAE,iCAA6B;AAC/C,OAAO,EAAE,oBAAoB,EAAE,+CAA2C;AAO1E,OAAO,EAAE,4BAA4B,EAAE,sDAAkD;AACzF,OAAO,EAAE,0BAA0B,EAAE,oDAAgD;AAoCrF;;;;GAIG;AACH,MAAM,UAAU,gCAAgC;IAC9C,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC;AAED,MAAM,yBAAyB,GAAG;IAChC,aAAa,EAAE;QACb,OAAO,EAAE,IAAI;QACb,sBAAsB,EAAE,KAAK;QAC7B,kBAAkB,EAAE,KAAK;QACzB,QAAQ,EAAE,IAAI;KACf;CAC8C,CAAC;AAElD,MAAM,GAAG,GAAG,kBAAkB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;AAE9D;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG;IACxC,uBAAuB,EAAE,CAAC,KAA6B,EAAW,EAAE,CAClE,KAAK,CAAC,aAAa,KAAK,IAAI;CAC/B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,OAAO,iBAAkB,SAAQ,cAItC;IAaC;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,EACV,SAAS,EACT,KAAK,GAAG,EAAE,EACV,IAAI,EACJ,MAAM,EACN,cAAc,EACd,QAAQ,EACR,eAAe,GAShB;QACC,KAAK,CAAC;YACJ,SAAS;YACT,QAAQ,EAAE,yBAAyB;YACnC,IAAI,EAAE,cAAc;YACpB,KAAK,EAAE,EAAE,GAAG,gCAAgC,EAAE,EAAE,GAAG,KAAK,EAAE;SAC3D,CAAC,CAAC;;QArDI,6CAAmB,IAAI,eAAe,EAAE,EAAC;QAEzC,0CAAc;QAEd,4CAAgB;QAEhB,oDAAmC;QAEnC,8CAAkB;QAElB,qDAAyB;QA6ChC,uBAAA,IAAI,2BAAS,IAAI,MAAA,CAAC;QAClB,uBAAA,IAAI,6BAAW,MAAM,MAAA,CAAC;QACtB,uBAAA,IAAI,qCAAmB,cAAc,MAAA,CAAC;QACtC,uBAAA,IAAI,+BAAa,QAAQ,IAAI,MAAM,MAAA,CAAC;QACpC,uBAAA,IAAI,sCAAoB,eAAe,IAAI,MAAM,MAAA,CAAC;IACpD,CAAC;IAmBD;;;;OAIG;IACH,iBAAiB;QACf,OAAO,0BAA0B,CAAC,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxE,CAAC;IAED;;;;;;;;OAQG;IACH,2BAA2B,CAAC,qBAE3B;QACC,MAAM,UAAU,GAAG,qBAAqB,EAAE,YAAY,KAAK,KAAK,CAAC;QACjE,MAAM,OAAO,GAAG,UAAU;YACxB,CAAC,CAAC,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;YAC3C,CAAC,CAAC,SAAS,CAAC;QACd,MAAM,UAAU,GAAG,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7D,MAAM,SAAS,GAAG,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAE5D,MAAM,UAAU,GAA4B,EAAE,CAAC;QAC/C,IAAI,OAAO,EAAE,CAAC;YACZ,UAAU,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;QAChD,CAAC;QAED,MAAM,OAAO,GAA+B;YAC1C,EAAE,EAAE,EAAE,IAAI,EAAE,uBAAA,IAAI,iCAAQ,EAAE,EAAE,EAAE,uBAAA,IAAI,+BAAM,EAAE;YAC1C,IAAI,EAAE;gBACJ,EAAE,EAAE,UAAU;gBACd,IAAI,EAAE,uBAAA,IAAI,mCAAU;gBACpB,WAAW,EAAE,uBAAA,IAAI,0CAAiB;aACnC;YACD,SAAS;YACT,gBAAgB,EAAE;gBAChB,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE;gBAC1C,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE;gBAC1C,EAAE,GAAG,EAAE,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE;aAC3C;YACD,OAAO,EAAE,mBAAmB;YAC5B,sBAAsB,EAAE;gBACtB,gBAAgB,EAAE,WAAW;gBAC7B,uBAAuB,EAAE,UAAU;gBACnC,WAAW,EAAE,WAAW;aACzB;YACD,KAAK,EAAE,CAAC,eAAe,EAAE,QAAQ,CAAC;YAClC,WAAW,EAAE,MAAM;YACnB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC;QAEF,uBAAA,IAAI,0CAAiB,CAAC,wBAAwB,CAAC,SAAS,EAAE;YACxD,UAAU;YACV,OAAO,EAAE,OAAO,IAAI,EAAE;YACtB,SAAS;YACT,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;;OAOG;IACH,6BAA6B;QAC3B,MAAM,MAAM,GAAG,uBAAA,IAAI,wEAAiB,MAArB,IAAI,CAAmB,CAAC;QAEvC,MAAM,SAAS,GAAG,gBAAgB,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAE5D,MAAM,UAAU,GAA4B,EAAE,CAAC;QAC/C,IAAI,MAAM,CAAC,aAAa,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC1C,UAAU,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC;QACrE,CAAC;QAED,MAAM,OAAO,GAAiC;YAC5C,SAAS;YACT,IAAI,EAAE,uBAAA,IAAI,+BAAM;YAChB,gBAAgB,EAAE;gBAChB;oBACE,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE;oBACxB,IAAI,EAAE,YAAY;oBAClB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,UAAU;iBACzC;aACF;YACD,gBAAgB,EAAE,WAAW;YAC7B,KAAK,EAAE,CAAC,eAAe,EAAE,QAAQ,CAAC;YAClC,OAAO,EAAE,mBAAmB;YAC5B,UAAU;SACX,CAAC;QAEF,uBAAA,IAAI,0CAAiB,CAAC,0BAA0B,CAAC,SAAS,EAAE;YAC1D,SAAS;YACT,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;QAEH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,0BAA0B,CAAC,MAGhC;QACC,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;QAElD,gBAAgB;QAChB,MAAM,SAAS,GAAG,uBAAA,IAAI,mFAA4B,MAAhC,IAAI,EACpB,oBAAoB,CAAC,QAAQ,CAAC,cAAc,CAC7C,CAAC;QACF,MAAM,oBAAoB,GACxB,uBAAA,IAAI,0CAAiB,CAAC,uBAAuB,CAAC,SAAS,CAAC,CAAC;QAC3D,IAAI,CAAC,oBAAoB,EAAE,CAAC;YAC1B,GAAG,CAAC,uDAAuD,CAAC,CAAC;YAC7D,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,sBAAsB,EACpD,EAAE,IAAI,EAAE,0BAA0B,CAAC,sBAAsB,EAAE,CAC5D,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,+BAA+B;YAC/B,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,GAAG,MAAM,0BAA0B,CAAC;gBACtE,QAAQ,EAAE,oBAAoB;gBAC9B,iBAAiB,EAAE,oBAAoB,CAAC,SAAS;gBACjD,cAAc,EAAE,uBAAA,IAAI,yCAAgB;gBACpC,YAAY,EAAE,uBAAA,IAAI,+BAAM;gBACxB,uBAAuB,EAAE,KAAK;aAC/B,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;gBACjB,GAAG,CAAC,+CAA+C,EAAE,KAAK,CAAC,CAAC;gBAC5D,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,8BAA8B,EAC5D;oBACE,IAAI,EAAE,0BAA0B,CAAC,8BAA8B;oBAC/D,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;iBACjE,CACF,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACnC,GAAG,CACD,oFAAoF,CACrF,CAAC;gBACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,8BAA8B,EAC5D,EAAE,IAAI,EAAE,0BAA0B,CAAC,8BAA8B,EAAE,CACpE,CAAC;YACJ,CAAC;YAED,aAAa;YACb,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,iCAAiC,CACjE,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,CAAC,YAAY,CAC9B,CAAC;YAEF,oBAAoB;YACpB,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAE5D,yBAAyB;YACzB,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;gBACpB,KAAK,CAAC,aAAa,GAAG;oBACpB,UAAU,EAAE;wBACV,EAAE,EAAE,gBAAgB,CAAC,YAAY;wBACjC,SAAS,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,SAAS,CAAC;wBACvD,OAAO,EAAE,gBAAgB,CAAC,OAAO;wBACjC,UAAU,EAAE,gBAAgB,CAAC,UAAU;qBACxC;oBACD,iBAAiB,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE;oBACrC,aAAa;iBACd,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,uBAAA,IAAI,0CAAiB,CAAC,0BAA0B,CAAC,SAAS,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,2BAA2B,CAC/B,sBAAqD;QAErD,iCAAiC;QACjC,MAAM,uBAAA,IAAI,qFAA8B,MAAlC,IAAI,EAA+B,sBAAsB,CAAC,CAAC;QAEjE,iEAAiE;QACjE,MAAM,aAAa,GAAG,uBAAA,IAAI,wEAAiB,MAArB,IAAI,CAAmB,CAAC;QAC9C,MAAM,MAAM,GAAG,mCAAmC,CAChD,sBAAsB,EACtB,aAAa,CACd,CAAC;QAEF,oBAAoB;QACpB,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,cAAc,CACvB,aAAa,CAAC,iBAAiB,CAAC,UAAU,EAC1C,aAAa,CAAC,iBAAiB,CAAC,EAAE,EAClC,MAAM,CACP,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CACD,yCAAyC,EACzC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,wBAAwB,EACtD;gBACE,IAAI,EAAE,0BAA0B,CAAC,wBAAwB;gBACzD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;aACjE,CACF,CAAC;QACJ,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;;;;;;;;OAUG;IACH,KAAK,CAAC,2BAA2B,CAC/B,sBAAqD;QAErD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,2BAA2B,CAAC,sBAAsB,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IAAI,KAAK,YAAY,sBAAsB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBACxE,OAAO,KAAK,CAAC;YACf,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,uBAAuB,CAAC,MAI7B;QACC,MAAM,EAAE,sBAAsB,EAAE,GAAG,MAAM,CAAC;QAC1C,MAAM,aAAa,GAAG,uBAAA,IAAI,wEAAiB,MAArB,IAAI,CAAmB,CAAC;QAE9C,aAAa;QACb,MAAM,MAAM,GAAG,mCAAmC,CAChD,sBAAsB,EACtB,aAAa,CACd,CAAC;QAEF,oBAAoB;QACpB,IAAI,iBAAyB,CAAC;QAC9B,IAAI,CAAC;YACH,iBAAiB,GAAG,cAAc,CAChC,aAAa,CAAC,iBAAiB,CAAC,UAAU,EAC1C,aAAa,CAAC,iBAAiB,CAAC,EAAE,EAClC,MAAM,CACP,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,GAAG,CACD,6DAA6D,EAC7D,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,wBAAwB,EACtD;gBACE,IAAI,EAAE,0BAA0B,CAAC,wBAAwB;gBACzD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;aACjE,CACF,CAAC;QACJ,CAAC;QAED,6BAA6B;QAC7B,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;QAC5C,IAAI,iBAAiB,KAAK,WAAW,EAAE,CAAC;YACtC,GAAG,CACD,0EAA0E,CAC3E,CAAC;YACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,gBAAgB,EAC9C,EAAE,IAAI,EAAE,0BAA0B,CAAC,gBAAgB,EAAE,CACtD,CAAC;QACJ,CAAC;QAED,wBAAwB;QACxB,MAAM,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAE/D,4EAA4E;QAC5E,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;gBACzB,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,WAAW,EACzC;oBACE,IAAI,EAAE,0BAA0B,CAAC,WAAW;iBAC7C,CACF,CAAC;YACJ,CAAC;YACD,KAAK,CAAC,aAAa,CAAC,iBAAiB,GAAG,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC;QAC7D,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,aAAa;QACX,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,gCAAgC,EAAE,CAAC,CAAC;QACtD,uBAAA,IAAI,0CAAiB,CAAC,KAAK,EAAE,CAAC;IAChC,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAED;;OAEG;IACH,OAAO;QACL,uBAAA,IAAI,0CAAiB,CAAC,KAAK,EAAE,CAAC;QAC9B,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;CAwFF;;IAndG,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,WAAW,EACzC;YACE,IAAI,EAAE,0BAA0B,CAAC,WAAW;SAC7C,CACF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC,yGAE2B,cAAsB;IAChD,OAAO,oBAAoB,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AA+WD;;;;GAIG;AACH,KAAK,0DACH,sBAAqD;IAErD,IAAI,SAA6B,CAAC;IAClC,IAAI,CAAC;QACH,gBAAgB;QAChB,SAAS,GAAG,uBAAA,IAAI,mFAA4B,MAAhC,IAAI,EACd,sBAAsB,CAAC,QAAQ,CAAC,cAAc,CAC/C,CAAC;QAEF,qBAAqB;QACrB,MAAM,MAAM,GAAG,uBAAA,IAAI,wEAAiB,MAArB,IAAI,CAAmB,CAAC;QAEvC,8BAA8B;QAC9B,MAAM,sBAAsB,GAC1B,uBAAA,IAAI,0CAAiB,CAAC,yBAAyB,CAAC,SAAS,CAAC,CAAC;QAC7D,IAAI,CAAC,sBAAsB,EAAE,CAAC;YAC5B,GAAG,CAAC,yDAAyD,CAAC,CAAC;YAC/D,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,wBAAwB,EACtD,EAAE,IAAI,EAAE,0BAA0B,CAAC,wBAAwB,EAAE,CAC9D,CAAC;QACJ,CAAC;QAED,iCAAiC;QACjC,MAAM,MAAM,GAAG,MAAM,4BAA4B,CAAC;YAChD,QAAQ,EAAE,sBAAsB;YAChC,iBAAiB,EAAE,sBAAsB,CAAC,SAAS;YACnD,cAAc,EAAE,uBAAA,IAAI,yCAAgB;YACpC,YAAY,EAAE,uBAAA,IAAI,+BAAM;YACxB,UAAU,EAAE;gBACV,EAAE,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE;gBACxB,SAAS,EAAE,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;gBACxD,OAAO,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO;gBAClC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,UAAU;aACzC;YACD,0EAA0E;YAC1E,uBAAuB,EAAE,KAAK;SAC/B,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YACjB,GAAG,CACD,iDAAiD,EACjD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAC1D,CAAC;YACF,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,gCAAgC,EAC9D;gBACE,IAAI,EAAE,0BAA0B,CAAC,gCAAgC;gBACjE,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;aACjE,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,GAAG,CAAC,yDAAyD,CAAC,CAAC;YAC/D,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,gCAAgC,EAC9D;gBACE,IAAI,EAAE,0BAA0B,CAAC,gCAAgC;aAClE,CACF,CAAC;QACJ,CAAC;QAED,oFAAoF;QACpF,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;YACpB,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;gBACzB,MAAM,IAAI,sBAAsB,CAC9B,6BAA6B,CAAC,WAAW,EACzC,EAAE,IAAI,EAAE,0BAA0B,CAAC,WAAW,EAAE,CACjD,CAAC;YACJ,CAAC;YACD,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC;YACnC,MAAM,CAAC,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAClC,MAAM,CAAC,kBAAkB,CAAC,UAAU,EACpC,MAAM,CAAC,UAAU,CAAC,OAAO,CAC1B,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,IAAI,SAAS,EAAE,CAAC;YACd,uBAAA,IAAI,0CAAiB,CAAC,4BAA4B,CAAC,SAAS,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\n\nimport { WEBAUTHN_TIMEOUT_MS, CeremonyManager } from './ceremony-manager';\nimport {\n controllerName,\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nimport { PasskeyControllerError } from './errors';\nimport {\n deriveKeyFromAuthenticationResponse,\n deriveKeyFromRegistrationResponse,\n} from './key-derivation';\nimport { createModuleLogger, projectLogger } from './logger';\nimport type { PasskeyRecord } from './types';\nimport { decryptWithKey, encryptWithKey } from './utils/crypto';\nimport { base64URLToBytes, bytesToBase64URL } from './utils/encoding';\nimport { COSEALG } from './webauthn/constants';\nimport { decodeClientDataJSON } from './webauthn/decode-client-data-json';\nimport type {\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n} from './webauthn/types';\nimport { verifyAuthenticationResponse } from './webauthn/verify-authentication-response';\nimport { verifyRegistrationResponse } from './webauthn/verify-registration-response';\n\nexport type PasskeyControllerState = {\n passkeyRecord: PasskeyRecord | null;\n};\n\nexport type PasskeyControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n PasskeyControllerState\n>;\n\n/**\n * Actions exposed by {@link PasskeyController} on its messenger.\n *\n * Only `:getState` is exposed. Derived enrollment status is available via\n * {@link passkeyControllerSelectors.selectIsPasskeyEnrolled}, and lifecycle\n * methods ({@link PasskeyController.generateRegistrationOptions},\n * {@link PasskeyController.protectVaultKeyWithPasskey}, etc.) accept or\n * return non-`Json` runtime values (WebAuthn `PublicKeyCredential` objects\n * and the vault key string), so they require a direct controller reference.\n */\nexport type PasskeyControllerActions = PasskeyControllerGetStateAction;\n\nexport type PasskeyControllerStateChangedEvent = ControllerStateChangedEvent<\n typeof controllerName,\n PasskeyControllerState\n>;\n\nexport type PasskeyControllerEvents = PasskeyControllerStateChangedEvent;\n\nexport type PasskeyControllerMessenger = Messenger<\n typeof controllerName,\n PasskeyControllerActions,\n PasskeyControllerEvents\n>;\n\n/**\n * Returns the default (empty) state for {@link PasskeyController}.\n *\n * @returns A fresh state object with no enrolled passkey.\n */\nexport function getDefaultPasskeyControllerState(): PasskeyControllerState {\n return { passkeyRecord: null };\n}\n\nconst passkeyControllerMetadata = {\n passkeyRecord: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<PasskeyControllerState>;\n\nconst log = createModuleLogger(projectLogger, controllerName);\n\n/**\n * Selectors for {@link PasskeyControllerState}.\n *\n * Use these instead of dedicated getter methods on the controller, so that\n * derived values can be consumed from Redux selectors and other places that\n * only have access to a state object.\n */\nexport const passkeyControllerSelectors = {\n selectIsPasskeyEnrolled: (state: PasskeyControllerState): boolean =>\n state.passkeyRecord !== null,\n};\n\n/**\n * Passkey-based protection for the vault encryption key (WebAuthn).\n *\n * Uses PRF-backed derivation when available; otherwise uses the credential\n * `userHandle`.\n */\nexport class PasskeyController extends BaseController<\n typeof controllerName,\n PasskeyControllerState,\n PasskeyControllerMessenger\n> {\n readonly #ceremonyManager = new CeremonyManager();\n\n readonly #rpID: string;\n\n readonly #rpName: string;\n\n readonly #expectedOrigin: string | string[];\n\n readonly #userName: string;\n\n readonly #userDisplayName: string;\n\n /**\n * Constructs a new {@link PasskeyController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - Initial state. Missing properties are filled in with\n * defaults from {@link getDefaultPasskeyControllerState}.\n * @param args.rpID - WebAuthn Relying Party ID (typically the eTLD+1 of the\n * client origin, or `localhost` in dev).\n * @param args.rpName - Human-readable Relying Party name shown by the OS\n * passkey UI.\n * @param args.expectedOrigin - One or more acceptable origins for the\n * `clientDataJSON.origin` check (e.g. `chrome-extension://...`).\n * @param args.userName - Optional `user.name` shown by the OS passkey UI.\n * Defaults to `rpName` so client builds (Stable, Flask, etc.) can\n * differentiate without changes here.\n * @param args.userDisplayName - Optional `user.displayName` shown by the OS\n * passkey UI. Defaults to `rpName`.\n */\n constructor({\n messenger,\n state = {},\n rpID,\n rpName,\n expectedOrigin,\n userName,\n userDisplayName,\n }: {\n messenger: PasskeyControllerMessenger;\n state?: Partial<PasskeyControllerState>;\n rpID: string;\n rpName: string;\n expectedOrigin: string | string[];\n userName?: string;\n userDisplayName?: string;\n }) {\n super({\n messenger,\n metadata: passkeyControllerMetadata,\n name: controllerName,\n state: { ...getDefaultPasskeyControllerState(), ...state },\n });\n\n this.#rpID = rpID;\n this.#rpName = rpName;\n this.#expectedOrigin = expectedOrigin;\n this.#userName = userName ?? rpName;\n this.#userDisplayName = userDisplayName ?? rpName;\n }\n\n #requireEnrolled(): PasskeyRecord {\n const record = this.state.passkeyRecord;\n if (!record) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n return record;\n }\n\n #getChallengeFromClientData(clientDataJSON: string): string {\n return decodeClientDataJSON(clientDataJSON).challenge;\n }\n\n /**\n * Checks if the passkey is enrolled.\n *\n * @returns Whether the passkey is enrolled.\n */\n isPasskeyEnrolled(): boolean {\n return passkeyControllerSelectors.selectIsPasskeyEnrolled(this.state);\n }\n\n /**\n * Registration options for enrolling a passkey.\n *\n * Call before {@link protectVaultKeyWithPasskey}.\n *\n * @param creationOptionsConfig - Optional configuration.\n * @param creationOptionsConfig.prfAvailable - Omit PRF when `false`. Default `true`.\n * @returns Options for `navigator.credentials.create()`.\n */\n generateRegistrationOptions(creationOptionsConfig?: {\n prfAvailable?: boolean;\n }): PasskeyRegistrationOptions {\n const includePrf = creationOptionsConfig?.prfAvailable !== false;\n const prfSalt = includePrf\n ? bytesToBase64URL(randomBytes(32).slice())\n : undefined;\n const userHandle = bytesToBase64URL(randomBytes(64).slice());\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (prfSalt) {\n extensions.prf = { eval: { first: prfSalt } };\n }\n\n const options: PasskeyRegistrationOptions = {\n rp: { name: this.#rpName, id: this.#rpID },\n user: {\n id: userHandle,\n name: this.#userName,\n displayName: this.#userDisplayName,\n },\n challenge,\n pubKeyCredParams: [\n { alg: COSEALG.EdDSA, type: 'public-key' },\n { alg: COSEALG.ES256, type: 'public-key' },\n { alg: COSEALG.RS256, type: 'public-key' },\n ],\n timeout: WEBAUTHN_TIMEOUT_MS,\n authenticatorSelection: {\n userVerification: 'preferred',\n authenticatorAttachment: 'platform',\n residentKey: 'preferred',\n },\n hints: ['client-device', 'hybrid'],\n attestation: 'none',\n ...(Object.keys(extensions).length > 0 ? { extensions } : {}),\n };\n\n this.#ceremonyManager.saveRegistrationCeremony(challenge, {\n userHandle,\n prfSalt: prfSalt ?? '',\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * WebAuthn request options for authenticating with the enrolled passkey.\n *\n * Call before {@link retrieveVaultKeyWithPasskey},\n * {@link verifyPasskeyAuthentication}, or {@link renewVaultKeyProtection}.\n *\n * @returns Options for `navigator.credentials.get()`.\n */\n generateAuthenticationOptions(): PasskeyAuthenticationOptions {\n const record = this.#requireEnrolled();\n\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (record.keyDerivation.method === 'prf') {\n extensions.prf = { eval: { first: record.keyDerivation.prfSalt } };\n }\n\n const options: PasskeyAuthenticationOptions = {\n challenge,\n rpId: this.#rpID,\n allowCredentials: [\n {\n id: record.credential.id,\n type: 'public-key',\n transports: record.credential.transports,\n },\n ],\n userVerification: 'preferred',\n hints: ['client-device', 'hybrid'],\n timeout: WEBAUTHN_TIMEOUT_MS,\n extensions,\n };\n\n this.#ceremonyManager.saveAuthenticationCeremony(challenge, {\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * Completes enrollment and binds the vault key to the new passkey.\n *\n * @param params - Protection parameters.\n * @param params.registrationResponse - Credential from `navigator.credentials.create()`.\n * @param params.vaultKey - Vault encryption key to protect.\n */\n async protectVaultKeyWithPasskey(params: {\n registrationResponse: PasskeyRegistrationResponse;\n vaultKey: string;\n }): Promise<void> {\n const { registrationResponse, vaultKey } = params;\n\n // get challenge\n const challenge = this.#getChallengeFromClientData(\n registrationResponse.response.clientDataJSON,\n );\n const registrationCeremony =\n this.#ceremonyManager.getRegistrationCeremony(challenge);\n if (!registrationCeremony) {\n log('No active passkey registration ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoRegistrationCeremony,\n { code: PasskeyControllerErrorCode.NoRegistrationCeremony },\n );\n }\n\n try {\n // verify registration response\n const { verified, registrationInfo } = await verifyRegistrationResponse({\n response: registrationResponse,\n expectedChallenge: registrationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n requireUserVerification: false,\n }).catch((error) => {\n log('Error verifying passkey registration response', error);\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.RegistrationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!verified || !registrationInfo) {\n log(\n 'Passkey registration verification returned unverified or missing registration info',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n { code: PasskeyControllerErrorCode.RegistrationVerificationFailed },\n );\n }\n\n // derive key\n const { encKey, keyDerivation } = deriveKeyFromRegistrationResponse(\n registrationResponse,\n registrationCeremony,\n registrationInfo.credentialId,\n );\n\n // encrypt vault key\n const { ciphertext, iv } = encryptWithKey(vaultKey, encKey);\n\n // persist passkey record\n this.update((state) => {\n state.passkeyRecord = {\n credential: {\n id: registrationInfo.credentialId,\n publicKey: bytesToBase64URL(registrationInfo.publicKey),\n counter: registrationInfo.counter,\n transports: registrationInfo.transports,\n },\n encryptedVaultKey: { ciphertext, iv },\n keyDerivation,\n };\n });\n } finally {\n this.#ceremonyManager.deleteRegistrationCeremony(challenge);\n }\n }\n\n /**\n * Returns the decrypted vault encryption key from the passkey authentication\n * response.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns The vault encryption key.\n */\n async retrieveVaultKeyWithPasskey(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<string> {\n // verify authentication response\n await this.#verifyAuthenticationResponse(authenticationResponse);\n\n // derive key (#verifyAuthenticationResponse guarantees enrolled)\n const passkeyRecord = this.#requireEnrolled();\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let vaultKey: string;\n try {\n vaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (cause) {\n log(\n 'Error decrypting vault key with passkey',\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: cause instanceof Error ? cause : new Error(String(cause)),\n },\n );\n }\n\n return vaultKey;\n }\n\n /**\n * Returns whether passkey authentication succeeds for this credential (same\n * work as {@link retrieveVaultKeyWithPasskey} without exposing the vault key).\n *\n * Returns `false` only when the failure is a {@link PasskeyControllerError}\n * with a defined `code`. Unexpected errors (e.g. malformed `clientDataJSON`,\n * internal bugs) are rethrown.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns `true` if authentication succeeds, otherwise `false`.\n */\n async verifyPasskeyAuthentication(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<boolean> {\n try {\n await this.retrieveVaultKeyWithPasskey(authenticationResponse);\n return true;\n } catch (error: unknown) {\n if (error instanceof PasskeyControllerError && error.code !== undefined) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Updates the vault encryption key for the same passkey (e.g. after a password change).\n *\n * Caller MUST first verify the assertion via {@link verifyPasskeyAuthentication}\n * or {@link retrieveVaultKeyWithPasskey}. This method does not re-verify\n * because the ceremony is single-use (deleted on verify) and the signature\n * counter is advanced (replay would be rejected). Authentication here is\n * enforced by the prior verification plus the `oldVaultKey` match below.\n *\n * @param params - Renewal parameters.\n * @param params.authenticationResponse - Credential from `navigator.credentials.get()`,\n * already verified by the caller.\n * @param params.oldVaultKey - Expected current vault key.\n * @param params.newVaultKey - New vault key to protect.\n */\n async renewVaultKeyProtection(params: {\n authenticationResponse: PasskeyAuthenticationResponse;\n oldVaultKey: string;\n newVaultKey: string;\n }): Promise<void> {\n const { authenticationResponse } = params;\n const passkeyRecord = this.#requireEnrolled();\n\n // derive key\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let decryptedVaultKey: string;\n try {\n decryptedVaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (error) {\n log(\n 'Error decrypting vault key during passkey vault key renewal',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n }\n\n // check if vault key matches\n const { oldVaultKey, newVaultKey } = params;\n if (decryptedVaultKey !== oldVaultKey) {\n log(\n 'Passkey renewal rejected: decrypted vault key does not match oldVaultKey',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyMismatch,\n { code: PasskeyControllerErrorCode.VaultKeyMismatch },\n );\n }\n\n // encrypt new vault key\n const { ciphertext, iv } = encryptWithKey(newVaultKey, encKey);\n\n // persist passkey record (mutate current state only for vault key material)\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n state.passkeyRecord.encryptedVaultKey = { ciphertext, iv };\n });\n }\n\n /**\n * Unenrolls the passkey, removing the protected vault key material.\n */\n removePasskey(): void {\n this.update(() => getDefaultPasskeyControllerState());\n this.#ceremonyManager.clear();\n }\n\n /**\n * Resets state and clears in-flight registration/authentication ceremonies.\n */\n clearState(): void {\n this.removePasskey();\n }\n\n /**\n * Releases all in-flight ceremony state and tears down the messenger.\n */\n destroy(): void {\n this.#ceremonyManager.clear();\n super.destroy();\n }\n\n /**\n * Verifies an authentication response for the enrolled passkey.\n *\n * @param authenticationResponse - Authentication result JSON.\n */\n async #verifyAuthenticationResponse(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<void> {\n let challenge: string | undefined;\n try {\n // get challenge\n challenge = this.#getChallengeFromClientData(\n authenticationResponse.response.clientDataJSON,\n );\n\n // get passkey record\n const record = this.#requireEnrolled();\n\n // get authentication ceremony\n const authenticationCeremony =\n this.#ceremonyManager.getAuthenticationCeremony(challenge);\n if (!authenticationCeremony) {\n log('No active passkey authentication ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoAuthenticationCeremony,\n { code: PasskeyControllerErrorCode.NoAuthenticationCeremony },\n );\n }\n\n // verify authentication response\n const result = await verifyAuthenticationResponse({\n response: authenticationResponse,\n expectedChallenge: authenticationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n credential: {\n id: record.credential.id,\n publicKey: base64URLToBytes(record.credential.publicKey),\n counter: record.credential.counter,\n transports: record.credential.transports,\n },\n // UV optional for device compatibility; vault key remains password-gated.\n requireUserVerification: false,\n }).catch((error) => {\n log(\n 'Error verifying passkey authentication response',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!result.verified) {\n log('Passkey authentication verification returned unverified');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n },\n );\n }\n\n // persist passkey record with updated counter without clobbering concurrent updates\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n { code: PasskeyControllerErrorCode.NotEnrolled },\n );\n }\n const latest = state.passkeyRecord;\n latest.credential.counter = Math.max(\n result.authenticationInfo.newCounter,\n latest.credential.counter,\n );\n });\n } finally {\n if (challenge) {\n this.#ceremonyManager.deleteAuthenticationCeremony(challenge);\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"PasskeyController.mjs","sourceRoot":"","sources":["../src/PasskeyController.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ControllerGetStateAction,\n ControllerStateChangedEvent,\n StateMetadata,\n} from '@metamask/base-controller';\nimport { BaseController } from '@metamask/base-controller';\nimport type { Messenger } from '@metamask/messenger';\nimport { areUint8ArraysEqual, stringToBytes } from '@metamask/utils';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\n\nimport { WEBAUTHN_TIMEOUT_MS, CeremonyManager } from './ceremony-manager';\nimport {\n controllerName,\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nimport { PasskeyControllerError } from './errors';\nimport {\n deriveKeyFromAuthenticationResponse,\n deriveKeyFromRegistrationResponse,\n} from './key-derivation';\nimport { createModuleLogger, projectLogger } from './logger';\nimport type { PasskeyRecord } from './types';\nimport { decryptWithKey, encryptWithKey } from './utils/crypto';\nimport { base64URLToBytes, bytesToBase64URL } from './utils/encoding';\nimport { COSEALG } from './webauthn/constants';\nimport { decodeClientDataJSON } from './webauthn/decode-client-data-json';\nimport type {\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n} from './webauthn/types';\nimport { verifyAuthenticationResponse } from './webauthn/verify-authentication-response';\nimport { verifyRegistrationResponse } from './webauthn/verify-registration-response';\n\nexport type PasskeyControllerState = {\n passkeyRecord: PasskeyRecord | null;\n};\n\nexport type PasskeyControllerGetStateAction = ControllerGetStateAction<\n typeof controllerName,\n PasskeyControllerState\n>;\n\n/**\n * Actions exposed by {@link PasskeyController} on its messenger.\n *\n * Only `:getState` is exposed. Derived enrollment status is available via\n * {@link passkeyControllerSelectors.selectIsPasskeyEnrolled}, and lifecycle\n * methods ({@link PasskeyController.generateRegistrationOptions},\n * {@link PasskeyController.protectVaultKeyWithPasskey}, etc.) accept or\n * return non-`Json` runtime values (WebAuthn `PublicKeyCredential` objects\n * and the vault key string), so they require a direct controller reference.\n */\nexport type PasskeyControllerActions = PasskeyControllerGetStateAction;\n\nexport type PasskeyControllerStateChangedEvent = ControllerStateChangedEvent<\n typeof controllerName,\n PasskeyControllerState\n>;\n\nexport type PasskeyControllerEvents = PasskeyControllerStateChangedEvent;\n\nexport type PasskeyControllerMessenger = Messenger<\n typeof controllerName,\n PasskeyControllerActions,\n PasskeyControllerEvents\n>;\n\n/**\n * Returns the default (empty) state for {@link PasskeyController}.\n *\n * @returns A fresh state object with no enrolled passkey.\n */\nexport function getDefaultPasskeyControllerState(): PasskeyControllerState {\n return { passkeyRecord: null };\n}\n\nconst passkeyControllerMetadata = {\n passkeyRecord: {\n persist: true,\n includeInDebugSnapshot: false,\n includeInStateLogs: false,\n usedInUi: true,\n },\n} satisfies StateMetadata<PasskeyControllerState>;\n\nconst log = createModuleLogger(projectLogger, controllerName);\n\n/**\n * Selectors for {@link PasskeyControllerState}.\n *\n * Use these instead of dedicated getter methods on the controller, so that\n * derived values can be consumed from Redux selectors and other places that\n * only have access to a state object.\n */\nexport const passkeyControllerSelectors = {\n selectIsPasskeyEnrolled: (state: PasskeyControllerState): boolean =>\n state.passkeyRecord !== null,\n};\n\n/**\n * Passkey-based protection for the vault encryption key (WebAuthn).\n *\n * Uses PRF-backed derivation when available; otherwise uses the credential\n * `userHandle`.\n */\nexport class PasskeyController extends BaseController<\n typeof controllerName,\n PasskeyControllerState,\n PasskeyControllerMessenger\n> {\n readonly #ceremonyManager = new CeremonyManager();\n\n readonly #rpID: string;\n\n readonly #rpName: string;\n\n readonly #expectedOrigin: string | string[];\n\n readonly #userName: string;\n\n readonly #userDisplayName: string;\n\n /**\n * Constructs a new {@link PasskeyController}.\n *\n * @param args - The constructor arguments.\n * @param args.messenger - The messenger suited for this controller.\n * @param args.state - Initial state. Missing properties are filled in with\n * defaults from {@link getDefaultPasskeyControllerState}.\n * @param args.rpID - WebAuthn Relying Party ID (typically the eTLD+1 of the\n * client origin, or `localhost` in dev).\n * @param args.rpName - Human-readable Relying Party name shown by the OS\n * passkey UI.\n * @param args.expectedOrigin - One or more acceptable origins for the\n * `clientDataJSON.origin` check (e.g. `chrome-extension://...`).\n * @param args.userName - Optional `user.name` shown by the OS passkey UI.\n * Defaults to `rpName` so client builds (Stable, Flask, etc.) can\n * differentiate without changes here.\n * @param args.userDisplayName - Optional `user.displayName` shown by the OS\n * passkey UI. Defaults to `rpName`.\n */\n constructor({\n messenger,\n state = {},\n rpID,\n rpName,\n expectedOrigin,\n userName,\n userDisplayName,\n }: {\n messenger: PasskeyControllerMessenger;\n state?: Partial<PasskeyControllerState>;\n rpID: string;\n rpName: string;\n expectedOrigin: string | string[];\n userName?: string;\n userDisplayName?: string;\n }) {\n super({\n messenger,\n metadata: passkeyControllerMetadata,\n name: controllerName,\n state: { ...getDefaultPasskeyControllerState(), ...state },\n });\n\n this.#rpID = rpID;\n this.#rpName = rpName;\n this.#expectedOrigin = expectedOrigin;\n this.#userName = userName ?? rpName;\n this.#userDisplayName = userDisplayName ?? rpName;\n }\n\n #requireEnrolled(): PasskeyRecord {\n const record = this.state.passkeyRecord;\n if (!record) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n return record;\n }\n\n #getChallengeFromClientData(clientDataJSON: string): string {\n return decodeClientDataJSON(clientDataJSON).challenge;\n }\n\n /**\n * Checks if the passkey is enrolled.\n *\n * @returns Whether the passkey is enrolled.\n */\n isPasskeyEnrolled(): boolean {\n return passkeyControllerSelectors.selectIsPasskeyEnrolled(this.state);\n }\n\n /**\n * Registration options for enrolling a passkey.\n *\n * Call before {@link protectVaultKeyWithPasskey}.\n *\n * @param creationOptionsConfig - Optional configuration.\n * @param creationOptionsConfig.prfAvailable - Omit PRF when `false`. Default `true`.\n * @returns Options for `navigator.credentials.create()`.\n */\n generateRegistrationOptions(creationOptionsConfig?: {\n prfAvailable?: boolean;\n }): PasskeyRegistrationOptions {\n const includePrf = creationOptionsConfig?.prfAvailable !== false;\n const prfSalt = includePrf\n ? bytesToBase64URL(randomBytes(32).slice())\n : undefined;\n const userHandle = bytesToBase64URL(randomBytes(64).slice());\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (prfSalt) {\n extensions.prf = { eval: { first: prfSalt } };\n }\n\n const options: PasskeyRegistrationOptions = {\n rp: { name: this.#rpName, id: this.#rpID },\n user: {\n id: userHandle,\n name: this.#userName,\n displayName: this.#userDisplayName,\n },\n challenge,\n pubKeyCredParams: [\n { alg: COSEALG.EdDSA, type: 'public-key' },\n { alg: COSEALG.ES256, type: 'public-key' },\n { alg: COSEALG.RS256, type: 'public-key' },\n ],\n timeout: WEBAUTHN_TIMEOUT_MS,\n authenticatorSelection: {\n userVerification: 'preferred',\n authenticatorAttachment: 'platform',\n residentKey: 'preferred',\n },\n hints: ['client-device', 'hybrid'],\n attestation: 'none',\n ...(Object.keys(extensions).length > 0 ? { extensions } : {}),\n };\n\n this.#ceremonyManager.saveRegistrationCeremony(challenge, {\n userHandle,\n prfSalt: prfSalt ?? '',\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * WebAuthn request options for authenticating with the enrolled passkey.\n *\n * Call before {@link retrieveVaultKeyWithPasskey},\n * {@link verifyPasskeyAuthentication}, or {@link renewVaultKeyProtection}.\n *\n * @returns Options for `navigator.credentials.get()`.\n */\n generateAuthenticationOptions(): PasskeyAuthenticationOptions {\n const record = this.#requireEnrolled();\n\n const challenge = bytesToBase64URL(randomBytes(32).slice());\n\n const extensions: Record<string, unknown> = {};\n if (record.keyDerivation.method === 'prf') {\n extensions.prf = { eval: { first: record.keyDerivation.prfSalt } };\n }\n\n const options: PasskeyAuthenticationOptions = {\n challenge,\n rpId: this.#rpID,\n allowCredentials: [\n {\n id: record.credential.id,\n type: 'public-key',\n transports: record.credential.transports,\n },\n ],\n userVerification: 'preferred',\n hints: ['client-device', 'hybrid'],\n timeout: WEBAUTHN_TIMEOUT_MS,\n extensions,\n };\n\n this.#ceremonyManager.saveAuthenticationCeremony(challenge, {\n challenge,\n createdAt: Date.now(),\n });\n\n return options;\n }\n\n /**\n * Completes enrollment and binds the vault key to the new passkey.\n *\n * @param params - Protection parameters.\n * @param params.registrationResponse - Credential from `navigator.credentials.create()`.\n * @param params.vaultKey - Vault encryption key to protect.\n */\n async protectVaultKeyWithPasskey(params: {\n registrationResponse: PasskeyRegistrationResponse;\n vaultKey: string;\n }): Promise<void> {\n const { registrationResponse, vaultKey } = params;\n\n // get challenge\n const challenge = this.#getChallengeFromClientData(\n registrationResponse.response.clientDataJSON,\n );\n const registrationCeremony =\n this.#ceremonyManager.getRegistrationCeremony(challenge);\n if (!registrationCeremony) {\n log('No active passkey registration ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoRegistrationCeremony,\n { code: PasskeyControllerErrorCode.NoRegistrationCeremony },\n );\n }\n\n try {\n // verify registration response\n const { verified, registrationInfo } = await verifyRegistrationResponse({\n response: registrationResponse,\n expectedChallenge: registrationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n requireUserVerification: false,\n }).catch((error) => {\n log('Error verifying passkey registration response', error);\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.RegistrationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!verified || !registrationInfo) {\n log(\n 'Passkey registration verification returned unverified or missing registration info',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.RegistrationVerificationFailed,\n { code: PasskeyControllerErrorCode.RegistrationVerificationFailed },\n );\n }\n\n // derive key\n const { encKey, keyDerivation } = deriveKeyFromRegistrationResponse(\n registrationResponse,\n registrationCeremony,\n registrationInfo.credentialId,\n );\n\n // encrypt vault key\n const { ciphertext, iv } = encryptWithKey(vaultKey, encKey);\n\n // persist passkey record\n this.update((state) => {\n state.passkeyRecord = {\n credential: {\n id: registrationInfo.credentialId,\n publicKey: bytesToBase64URL(registrationInfo.publicKey),\n counter: registrationInfo.counter,\n transports: registrationInfo.transports,\n aaguid: registrationInfo.aaguid,\n },\n encryptedVaultKey: { ciphertext, iv },\n keyDerivation,\n };\n });\n } finally {\n this.#ceremonyManager.deleteRegistrationCeremony(challenge);\n }\n }\n\n /**\n * Returns the decrypted vault encryption key from the passkey authentication\n * response.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns The vault encryption key.\n */\n async retrieveVaultKeyWithPasskey(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<string> {\n // verify authentication response\n await this.#verifyAuthenticationResponse(authenticationResponse);\n\n // derive key (#verifyAuthenticationResponse guarantees enrolled)\n const passkeyRecord = this.#requireEnrolled();\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let vaultKey: string;\n try {\n vaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (cause) {\n log(\n 'Error decrypting vault key with passkey',\n cause instanceof Error ? cause : new Error(String(cause)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: cause instanceof Error ? cause : new Error(String(cause)),\n },\n );\n }\n\n return vaultKey;\n }\n\n /**\n * Returns whether passkey authentication succeeds for this credential (same\n * work as {@link retrieveVaultKeyWithPasskey} without exposing the vault key).\n *\n * Returns `false` only when the failure is a {@link PasskeyControllerError}\n * with a defined `code`. Unexpected errors (e.g. malformed `clientDataJSON`,\n * internal bugs) are rethrown.\n *\n * @param authenticationResponse - Credential from `navigator.credentials.get()`.\n * @returns `true` if authentication succeeds, otherwise `false`.\n */\n async verifyPasskeyAuthentication(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<boolean> {\n try {\n await this.retrieveVaultKeyWithPasskey(authenticationResponse);\n return true;\n } catch (error: unknown) {\n if (error instanceof PasskeyControllerError && error.code !== undefined) {\n return false;\n }\n throw error;\n }\n }\n\n /**\n * Updates the vault encryption key for the same passkey (e.g. after a password change).\n *\n * Caller MUST first verify the assertion via {@link verifyPasskeyAuthentication}\n * or {@link retrieveVaultKeyWithPasskey}. This method does not re-verify\n * because the ceremony is single-use (deleted on verify) and the signature\n * counter is advanced (replay would be rejected). Authentication here is\n * enforced by the prior verification plus the `oldVaultKey` match below.\n *\n * @param params - Renewal parameters.\n * @param params.authenticationResponse - Credential from `navigator.credentials.get()`,\n * already verified by the caller.\n * @param params.oldVaultKey - Expected current vault key.\n * @param params.newVaultKey - New vault key to protect.\n */\n async renewVaultKeyProtection(params: {\n authenticationResponse: PasskeyAuthenticationResponse;\n oldVaultKey: string;\n newVaultKey: string;\n }): Promise<void> {\n const { authenticationResponse } = params;\n const passkeyRecord = this.#requireEnrolled();\n\n // derive key\n const encKey = deriveKeyFromAuthenticationResponse(\n authenticationResponse,\n passkeyRecord,\n );\n\n // decrypt vault key\n let decryptedVaultKey: string;\n try {\n decryptedVaultKey = decryptWithKey(\n passkeyRecord.encryptedVaultKey.ciphertext,\n passkeyRecord.encryptedVaultKey.iv,\n encKey,\n );\n } catch (error) {\n log(\n 'Error decrypting vault key during passkey vault key renewal',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyDecryptionFailed,\n {\n code: PasskeyControllerErrorCode.VaultKeyDecryptionFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n }\n\n // check if vault key matches\n const { oldVaultKey, newVaultKey } = params;\n if (\n !areUint8ArraysEqual(\n stringToBytes(decryptedVaultKey),\n stringToBytes(oldVaultKey),\n )\n ) {\n log(\n 'Passkey renewal rejected: decrypted vault key does not match oldVaultKey',\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.VaultKeyMismatch,\n { code: PasskeyControllerErrorCode.VaultKeyMismatch },\n );\n }\n\n // encrypt new vault key\n const { ciphertext, iv } = encryptWithKey(newVaultKey, encKey);\n\n // persist passkey record (mutate current state only for vault key material)\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n {\n code: PasskeyControllerErrorCode.NotEnrolled,\n },\n );\n }\n state.passkeyRecord.encryptedVaultKey = { ciphertext, iv };\n });\n }\n\n /**\n * Unenrolls the passkey, removing the protected vault key material.\n */\n removePasskey(): void {\n this.update(() => getDefaultPasskeyControllerState());\n this.#ceremonyManager.clear();\n }\n\n /**\n * Resets state and clears in-flight registration/authentication ceremonies.\n */\n clearState(): void {\n this.removePasskey();\n }\n\n /**\n * Releases all in-flight ceremony state and tears down the messenger.\n */\n destroy(): void {\n this.#ceremonyManager.clear();\n super.destroy();\n }\n\n /**\n * Verifies an authentication response for the enrolled passkey.\n *\n * @param authenticationResponse - Authentication result JSON.\n */\n async #verifyAuthenticationResponse(\n authenticationResponse: PasskeyAuthenticationResponse,\n ): Promise<void> {\n let challenge: string | undefined;\n try {\n // get challenge\n challenge = this.#getChallengeFromClientData(\n authenticationResponse.response.clientDataJSON,\n );\n\n // get passkey record\n const record = this.#requireEnrolled();\n\n // get authentication ceremony\n const authenticationCeremony =\n this.#ceremonyManager.getAuthenticationCeremony(challenge);\n if (!authenticationCeremony) {\n log('No active passkey authentication ceremony for challenge');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NoAuthenticationCeremony,\n { code: PasskeyControllerErrorCode.NoAuthenticationCeremony },\n );\n }\n\n // verify authentication response\n const result = await verifyAuthenticationResponse({\n response: authenticationResponse,\n expectedChallenge: authenticationCeremony.challenge,\n expectedOrigin: this.#expectedOrigin,\n expectedRPID: this.#rpID,\n credential: {\n id: record.credential.id,\n publicKey: base64URLToBytes(record.credential.publicKey),\n counter: record.credential.counter,\n transports: record.credential.transports,\n },\n // UV optional for device compatibility; vault key remains password-gated.\n requireUserVerification: false,\n }).catch((error) => {\n log(\n 'Error verifying passkey authentication response',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n cause: error instanceof Error ? error : new Error(String(error)),\n },\n );\n });\n if (!result.verified) {\n log('Passkey authentication verification returned unverified');\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.AuthenticationVerificationFailed,\n {\n code: PasskeyControllerErrorCode.AuthenticationVerificationFailed,\n },\n );\n }\n\n // persist passkey record with updated counter without clobbering concurrent updates\n this.update((state) => {\n if (!state.passkeyRecord) {\n throw new PasskeyControllerError(\n PasskeyControllerErrorMessage.NotEnrolled,\n { code: PasskeyControllerErrorCode.NotEnrolled },\n );\n }\n const latest = state.passkeyRecord;\n latest.credential.counter = Math.max(\n result.authenticationInfo.newCounter,\n latest.credential.counter,\n );\n });\n } finally {\n if (challenge) {\n this.#ceremonyManager.deleteAuthenticationCeremony(challenge);\n }\n }\n }\n}\n"]}
package/dist/index.cjs CHANGED
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MAX_CONCURRENT_PASSKEY_CEREMONIES = exports.CEREMONY_MAX_AGE_MS = exports.CEREMONY_TTL_SLACK_MS = exports.WEBAUTHN_TIMEOUT_MS = exports.passkeyControllerSelectors = exports.getDefaultPasskeyControllerState = exports.PasskeyController = exports.PasskeyControllerError = exports.PasskeyControllerErrorMessage = exports.PasskeyControllerErrorCode = exports.controllerName = void 0;
3
+ exports.passkeyControllerSelectors = exports.getDefaultPasskeyControllerState = exports.PasskeyController = exports.PasskeyControllerError = exports.PasskeyControllerErrorMessage = exports.PasskeyControllerErrorCode = void 0;
4
4
  var constants_1 = require("./constants.cjs");
5
- Object.defineProperty(exports, "controllerName", { enumerable: true, get: function () { return constants_1.controllerName; } });
6
5
  Object.defineProperty(exports, "PasskeyControllerErrorCode", { enumerable: true, get: function () { return constants_1.PasskeyControllerErrorCode; } });
7
6
  Object.defineProperty(exports, "PasskeyControllerErrorMessage", { enumerable: true, get: function () { return constants_1.PasskeyControllerErrorMessage; } });
8
7
  var errors_1 = require("./errors.cjs");
@@ -11,9 +10,4 @@ var PasskeyController_1 = require("./PasskeyController.cjs");
11
10
  Object.defineProperty(exports, "PasskeyController", { enumerable: true, get: function () { return PasskeyController_1.PasskeyController; } });
12
11
  Object.defineProperty(exports, "getDefaultPasskeyControllerState", { enumerable: true, get: function () { return PasskeyController_1.getDefaultPasskeyControllerState; } });
13
12
  Object.defineProperty(exports, "passkeyControllerSelectors", { enumerable: true, get: function () { return PasskeyController_1.passkeyControllerSelectors; } });
14
- var ceremony_manager_1 = require("./ceremony-manager.cjs");
15
- Object.defineProperty(exports, "WEBAUTHN_TIMEOUT_MS", { enumerable: true, get: function () { return ceremony_manager_1.WEBAUTHN_TIMEOUT_MS; } });
16
- Object.defineProperty(exports, "CEREMONY_TTL_SLACK_MS", { enumerable: true, get: function () { return ceremony_manager_1.CEREMONY_TTL_SLACK_MS; } });
17
- Object.defineProperty(exports, "CEREMONY_MAX_AGE_MS", { enumerable: true, get: function () { return ceremony_manager_1.CEREMONY_MAX_AGE_MS; } });
18
- Object.defineProperty(exports, "MAX_CONCURRENT_PASSKEY_CEREMONIES", { enumerable: true, get: function () { return ceremony_manager_1.MAX_CONCURRENT_PASSKEY_CEREMONIES; } });
19
13
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6CAIqB;AAHnB,2GAAA,cAAc,OAAA;AACd,uHAAA,0BAA0B,OAAA;AAC1B,0HAAA,6BAA6B,OAAA;AAE/B,uCAAkD;AAAzC,gHAAA,sBAAsB,OAAA;AAE/B,6DAI6B;AAH3B,sHAAA,iBAAiB,OAAA;AACjB,qIAAA,gCAAgC,OAAA;AAChC,+HAAA,0BAA0B,OAAA;AAE5B,2DAK4B;AAJ1B,uHAAA,mBAAmB,OAAA;AACnB,yHAAA,qBAAqB,OAAA;AACrB,uHAAA,mBAAmB,OAAA;AACnB,qIAAA,iCAAiC,OAAA","sourcesContent":["export {\n controllerName,\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nexport { PasskeyControllerError } from './errors';\nexport type { PasskeyControllerErrorOptions } from './errors';\nexport {\n PasskeyController,\n getDefaultPasskeyControllerState,\n passkeyControllerSelectors,\n} from './PasskeyController';\nexport {\n WEBAUTHN_TIMEOUT_MS,\n CEREMONY_TTL_SLACK_MS,\n CEREMONY_MAX_AGE_MS,\n MAX_CONCURRENT_PASSKEY_CEREMONIES,\n} from './ceremony-manager';\nexport type {\n PasskeyControllerState,\n PasskeyControllerMessenger,\n PasskeyControllerGetStateAction,\n PasskeyControllerActions,\n PasskeyControllerStateChangedEvent,\n PasskeyControllerEvents,\n} from './PasskeyController';\nexport type {\n EncryptedVaultKey,\n PasskeyCredentialInfo,\n PasskeyDerivationMethod,\n PasskeyKeyDerivation,\n PasskeyRecord,\n PrfEvalExtension,\n PrfClientExtensionResults,\n} from './types';\nexport type {\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n PublicKeyCredentialHint,\n} from './webauthn/types';\n"]}
1
+ {"version":3,"file":"index.cjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,6CAGqB;AAFnB,uHAAA,0BAA0B,OAAA;AAC1B,0HAAA,6BAA6B,OAAA;AAE/B,uCAAkD;AAAzC,gHAAA,sBAAsB,OAAA;AAC/B,6DAI6B;AAH3B,sHAAA,iBAAiB,OAAA;AACjB,qIAAA,gCAAgC,OAAA;AAChC,+HAAA,0BAA0B,OAAA","sourcesContent":["export {\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nexport { PasskeyControllerError } from './errors';\nexport {\n PasskeyController,\n getDefaultPasskeyControllerState,\n passkeyControllerSelectors,\n} from './PasskeyController';\nexport type {\n PasskeyControllerState,\n PasskeyControllerMessenger,\n PasskeyControllerGetStateAction,\n PasskeyControllerActions,\n PasskeyControllerStateChangedEvent,\n PasskeyControllerEvents,\n} from './PasskeyController';\nexport type {\n PasskeyCredentialInfo,\n PasskeyDerivationMethod,\n PasskeyKeyDerivation,\n PasskeyRecord,\n PrfEvalExtension,\n PrfClientExtensionResults,\n} from './types';\nexport type {\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n} from './webauthn/types';\n"]}
package/dist/index.d.cts CHANGED
@@ -1,9 +1,7 @@
1
- export { controllerName, PasskeyControllerErrorCode, PasskeyControllerErrorMessage, } from "./constants.cjs";
1
+ export { PasskeyControllerErrorCode, PasskeyControllerErrorMessage, } from "./constants.cjs";
2
2
  export { PasskeyControllerError } from "./errors.cjs";
3
- export type { PasskeyControllerErrorOptions } from "./errors.cjs";
4
3
  export { PasskeyController, getDefaultPasskeyControllerState, passkeyControllerSelectors, } from "./PasskeyController.cjs";
5
- export { WEBAUTHN_TIMEOUT_MS, CEREMONY_TTL_SLACK_MS, CEREMONY_MAX_AGE_MS, MAX_CONCURRENT_PASSKEY_CEREMONIES, } from "./ceremony-manager.cjs";
6
4
  export type { PasskeyControllerState, PasskeyControllerMessenger, PasskeyControllerGetStateAction, PasskeyControllerActions, PasskeyControllerStateChangedEvent, PasskeyControllerEvents, } from "./PasskeyController.cjs";
7
- export type { EncryptedVaultKey, PasskeyCredentialInfo, PasskeyDerivationMethod, PasskeyKeyDerivation, PasskeyRecord, PrfEvalExtension, PrfClientExtensionResults, } from "./types.cjs";
8
- export type { PasskeyRegistrationOptions, PasskeyRegistrationResponse, PasskeyAuthenticationOptions, PasskeyAuthenticationResponse, PublicKeyCredentialHint, } from "./webauthn/types.cjs";
5
+ export type { PasskeyCredentialInfo, PasskeyDerivationMethod, PasskeyKeyDerivation, PasskeyRecord, PrfEvalExtension, PrfClientExtensionResults, } from "./types.cjs";
6
+ export type { PasskeyRegistrationOptions, PasskeyRegistrationResponse, PasskeyAuthenticationOptions, PasskeyAuthenticationResponse, } from "./webauthn/types.cjs";
9
7
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,6BAA6B,GAC9B,wBAAoB;AACrB,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAClD,YAAY,EAAE,6BAA6B,EAAE,qBAAiB;AAC9D,OAAO,EACL,iBAAiB,EACjB,gCAAgC,EAChC,0BAA0B,GAC3B,gCAA4B;AAC7B,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,iCAAiC,GAClC,+BAA2B;AAC5B,YAAY,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,+BAA+B,EAC/B,wBAAwB,EACxB,kCAAkC,EAClC,uBAAuB,GACxB,gCAA4B;AAC7B,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,aAAa,EACb,gBAAgB,EAChB,yBAAyB,GAC1B,oBAAgB;AACjB,YAAY,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC7B,uBAAuB,GACxB,6BAAyB"}
1
+ {"version":3,"file":"index.d.cts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,GAC9B,wBAAoB;AACrB,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAClD,OAAO,EACL,iBAAiB,EACjB,gCAAgC,EAChC,0BAA0B,GAC3B,gCAA4B;AAC7B,YAAY,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,+BAA+B,EAC/B,wBAAwB,EACxB,kCAAkC,EAClC,uBAAuB,GACxB,gCAA4B;AAC7B,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,aAAa,EACb,gBAAgB,EAChB,yBAAyB,GAC1B,oBAAgB;AACjB,YAAY,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,GAC9B,6BAAyB"}
package/dist/index.d.mts CHANGED
@@ -1,9 +1,7 @@
1
- export { controllerName, PasskeyControllerErrorCode, PasskeyControllerErrorMessage, } from "./constants.mjs";
1
+ export { PasskeyControllerErrorCode, PasskeyControllerErrorMessage, } from "./constants.mjs";
2
2
  export { PasskeyControllerError } from "./errors.mjs";
3
- export type { PasskeyControllerErrorOptions } from "./errors.mjs";
4
3
  export { PasskeyController, getDefaultPasskeyControllerState, passkeyControllerSelectors, } from "./PasskeyController.mjs";
5
- export { WEBAUTHN_TIMEOUT_MS, CEREMONY_TTL_SLACK_MS, CEREMONY_MAX_AGE_MS, MAX_CONCURRENT_PASSKEY_CEREMONIES, } from "./ceremony-manager.mjs";
6
4
  export type { PasskeyControllerState, PasskeyControllerMessenger, PasskeyControllerGetStateAction, PasskeyControllerActions, PasskeyControllerStateChangedEvent, PasskeyControllerEvents, } from "./PasskeyController.mjs";
7
- export type { EncryptedVaultKey, PasskeyCredentialInfo, PasskeyDerivationMethod, PasskeyKeyDerivation, PasskeyRecord, PrfEvalExtension, PrfClientExtensionResults, } from "./types.mjs";
8
- export type { PasskeyRegistrationOptions, PasskeyRegistrationResponse, PasskeyAuthenticationOptions, PasskeyAuthenticationResponse, PublicKeyCredentialHint, } from "./webauthn/types.mjs";
5
+ export type { PasskeyCredentialInfo, PasskeyDerivationMethod, PasskeyKeyDerivation, PasskeyRecord, PrfEvalExtension, PrfClientExtensionResults, } from "./types.mjs";
6
+ export type { PasskeyRegistrationOptions, PasskeyRegistrationResponse, PasskeyAuthenticationOptions, PasskeyAuthenticationResponse, } from "./webauthn/types.mjs";
9
7
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,6BAA6B,GAC9B,wBAAoB;AACrB,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAClD,YAAY,EAAE,6BAA6B,EAAE,qBAAiB;AAC9D,OAAO,EACL,iBAAiB,EACjB,gCAAgC,EAChC,0BAA0B,GAC3B,gCAA4B;AAC7B,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,iCAAiC,GAClC,+BAA2B;AAC5B,YAAY,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,+BAA+B,EAC/B,wBAAwB,EACxB,kCAAkC,EAClC,uBAAuB,GACxB,gCAA4B;AAC7B,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,aAAa,EACb,gBAAgB,EAChB,yBAAyB,GAC1B,oBAAgB;AACjB,YAAY,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,EAC7B,uBAAuB,GACxB,6BAAyB"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,GAC9B,wBAAoB;AACrB,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAClD,OAAO,EACL,iBAAiB,EACjB,gCAAgC,EAChC,0BAA0B,GAC3B,gCAA4B;AAC7B,YAAY,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,+BAA+B,EAC/B,wBAAwB,EACxB,kCAAkC,EAClC,uBAAuB,GACxB,gCAA4B;AAC7B,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,aAAa,EACb,gBAAgB,EAChB,yBAAyB,GAC1B,oBAAgB;AACjB,YAAY,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,4BAA4B,EAC5B,6BAA6B,GAC9B,6BAAyB"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,4 @@
1
- export { controllerName, PasskeyControllerErrorCode, PasskeyControllerErrorMessage } from "./constants.mjs";
1
+ export { PasskeyControllerErrorCode, PasskeyControllerErrorMessage } from "./constants.mjs";
2
2
  export { PasskeyControllerError } from "./errors.mjs";
3
3
  export { PasskeyController, getDefaultPasskeyControllerState, passkeyControllerSelectors } from "./PasskeyController.mjs";
4
- export { WEBAUTHN_TIMEOUT_MS, CEREMONY_TTL_SLACK_MS, CEREMONY_MAX_AGE_MS, MAX_CONCURRENT_PASSKEY_CEREMONIES } from "./ceremony-manager.mjs";
5
4
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,0BAA0B,EAC1B,6BAA6B,EAC9B,wBAAoB;AACrB,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAElD,OAAO,EACL,iBAAiB,EACjB,gCAAgC,EAChC,0BAA0B,EAC3B,gCAA4B;AAC7B,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,iCAAiC,EAClC,+BAA2B","sourcesContent":["export {\n controllerName,\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nexport { PasskeyControllerError } from './errors';\nexport type { PasskeyControllerErrorOptions } from './errors';\nexport {\n PasskeyController,\n getDefaultPasskeyControllerState,\n passkeyControllerSelectors,\n} from './PasskeyController';\nexport {\n WEBAUTHN_TIMEOUT_MS,\n CEREMONY_TTL_SLACK_MS,\n CEREMONY_MAX_AGE_MS,\n MAX_CONCURRENT_PASSKEY_CEREMONIES,\n} from './ceremony-manager';\nexport type {\n PasskeyControllerState,\n PasskeyControllerMessenger,\n PasskeyControllerGetStateAction,\n PasskeyControllerActions,\n PasskeyControllerStateChangedEvent,\n PasskeyControllerEvents,\n} from './PasskeyController';\nexport type {\n EncryptedVaultKey,\n PasskeyCredentialInfo,\n PasskeyDerivationMethod,\n PasskeyKeyDerivation,\n PasskeyRecord,\n PrfEvalExtension,\n PrfClientExtensionResults,\n} from './types';\nexport type {\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n PublicKeyCredentialHint,\n} from './webauthn/types';\n"]}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,6BAA6B,EAC9B,wBAAoB;AACrB,OAAO,EAAE,sBAAsB,EAAE,qBAAiB;AAClD,OAAO,EACL,iBAAiB,EACjB,gCAAgC,EAChC,0BAA0B,EAC3B,gCAA4B","sourcesContent":["export {\n PasskeyControllerErrorCode,\n PasskeyControllerErrorMessage,\n} from './constants';\nexport { PasskeyControllerError } from './errors';\nexport {\n PasskeyController,\n getDefaultPasskeyControllerState,\n passkeyControllerSelectors,\n} from './PasskeyController';\nexport type {\n PasskeyControllerState,\n PasskeyControllerMessenger,\n PasskeyControllerGetStateAction,\n PasskeyControllerActions,\n PasskeyControllerStateChangedEvent,\n PasskeyControllerEvents,\n} from './PasskeyController';\nexport type {\n PasskeyCredentialInfo,\n PasskeyDerivationMethod,\n PasskeyKeyDerivation,\n PasskeyRecord,\n PrfEvalExtension,\n PrfClientExtensionResults,\n} from './types';\nexport type {\n PasskeyRegistrationOptions,\n PasskeyRegistrationResponse,\n PasskeyAuthenticationOptions,\n PasskeyAuthenticationResponse,\n} from './webauthn/types';\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.cjs","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Base64String = string;\n\nexport type Base64URLString = string;\n\nexport type AuthenticatorTransportFuture =\n | 'ble'\n | 'cable'\n | 'hybrid'\n | 'internal'\n | 'nfc'\n | 'smart-card'\n | 'usb';\n\n/**\n * WebAuthn credential metadata used to identify the passkey and verify\n * subsequent assertions.\n */\nexport type PasskeyCredentialInfo = {\n /** WebAuthn credential ID (base64url). */\n id: Base64URLString;\n /** COSE-encoded credential public key (base64url) used to verify assertions. */\n publicKey: Base64URLString;\n /** Authenticator signature counter for replay/clone detection. */\n counter: number;\n /** Authenticator transports hint for `allowCredentials`. */\n transports?: AuthenticatorTransportFuture[];\n};\n\n/**\n * Vault key wrapped under the passkey-derived AES-256-GCM key.\n */\nexport type EncryptedVaultKey = {\n /** Base64-encoded AES-256-GCM ciphertext of the vault key. */\n ciphertext: Base64String;\n /** Base64-encoded AES-GCM IV used during encryption. */\n iv: Base64String;\n};\n\n/**\n * Parameters needed to reproduce the AES-256 wrapping key at unlock time.\n *\n * Encoded as a discriminated union so PRF-only fields (e.g. `prfSalt`) can\n * only exist on the PRF branch, removing the \"optional but actually\n * required\" footgun.\n */\nexport type PasskeyKeyDerivation =\n | {\n method: 'prf';\n /**\n * PRF salt sent in `get()` extension options to reproduce the same PRF\n * output that was generated at registration.\n */\n prfSalt: Base64URLString;\n }\n | { method: 'userHandle' };\n\n/** Discriminator value for {@link PasskeyKeyDerivation}. */\nexport type PasskeyDerivationMethod = PasskeyKeyDerivation['method'];\n\nexport type PasskeyRecord = {\n /** WebAuthn credential metadata used for assertion verification & re-discovery. */\n credential: PasskeyCredentialInfo;\n /** Vault key wrapped under the passkey-derived key. */\n encryptedVaultKey: EncryptedVaultKey;\n /** How the wrapping key is reconstructed at unlock time. */\n keyDerivation: PasskeyKeyDerivation;\n};\n\n/**\n * In-memory state for one **in-flight** WebAuthn **registration** ceremony\n * (from `create()` options until `protectVaultKeyWithPasskey` completes). This is\n * not a user login session; it is keyed by challenge and distinct from the full\n * spec ceremony (which includes the authenticator round-trip).\n */\nexport type PasskeyRegistrationCeremony = {\n userHandle: Base64URLString;\n prfSalt: Base64URLString;\n challenge: Base64URLString;\n /** When this ceremony was started (ms since epoch); used for TTL pruning. */\n createdAt: number;\n};\n\n/**\n * In-memory state for one **in-flight** WebAuthn **authentication** ceremony\n * (`get()` options until the assertion is verified). Not a user login session.\n */\nexport type PasskeyAuthenticationCeremony = {\n challenge: Base64URLString;\n /** When this ceremony was started (ms since epoch); used for TTL pruning. */\n createdAt: number;\n};\n\n/**\n * PRF extension types not covered by DOM typings.\n */\nexport type PrfEvalExtension = {\n eval: {\n first: Base64URLString;\n };\n};\n\nexport type PrfClientExtensionResults = {\n prf?: {\n enabled?: boolean;\n results?: { first?: Base64URLString };\n };\n};\n"]}
1
+ {"version":3,"file":"types.cjs","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Base64String = string;\n\nexport type Base64URLString = string;\n\nexport type AuthenticatorTransportFuture =\n | 'ble'\n | 'cable'\n | 'hybrid'\n | 'internal'\n | 'nfc'\n | 'smart-card'\n | 'usb';\n\n/**\n * WebAuthn credential metadata used to identify the passkey and verify\n * subsequent assertions.\n */\nexport type PasskeyCredentialInfo = {\n /** WebAuthn credential ID (base64url). */\n id: Base64URLString;\n /** COSE-encoded credential public key (base64url) used to verify assertions. */\n publicKey: Base64URLString;\n /** Authenticator signature counter for replay/clone detection. */\n counter: number;\n /** Authenticator transports hint for `allowCredentials`. */\n transports?: AuthenticatorTransportFuture[];\n /** Authenticator AAGUID captured from attested credential data at registration. */\n aaguid: string;\n};\n\n/**\n * Vault key wrapped under the passkey-derived AES-256-GCM key.\n */\nexport type EncryptedVaultKey = {\n /** Base64-encoded AES-256-GCM ciphertext of the vault key. */\n ciphertext: Base64String;\n /** Base64-encoded AES-GCM IV used during encryption. */\n iv: Base64String;\n};\n\n/**\n * Parameters needed to reproduce the AES-256 wrapping key at unlock time.\n *\n * Encoded as a discriminated union so PRF-only fields (e.g. `prfSalt`) can\n * only exist on the PRF branch, removing the \"optional but actually\n * required\" footgun.\n */\nexport type PasskeyKeyDerivation =\n | {\n method: 'prf';\n /**\n * PRF salt sent in `get()` extension options to reproduce the same PRF\n * output that was generated at registration.\n */\n prfSalt: Base64URLString;\n }\n | { method: 'userHandle' };\n\n/** Discriminator value for {@link PasskeyKeyDerivation}. */\nexport type PasskeyDerivationMethod = PasskeyKeyDerivation['method'];\n\nexport type PasskeyRecord = {\n /** WebAuthn credential metadata used for assertion verification & re-discovery. */\n credential: PasskeyCredentialInfo;\n /** Vault key wrapped under the passkey-derived key. */\n encryptedVaultKey: EncryptedVaultKey;\n /** How the wrapping key is reconstructed at unlock time. */\n keyDerivation: PasskeyKeyDerivation;\n};\n\n/**\n * In-memory state for one **in-flight** WebAuthn **registration** ceremony\n * (from `create()` options until `protectVaultKeyWithPasskey` completes). This is\n * not a user login session; it is keyed by challenge and distinct from the full\n * spec ceremony (which includes the authenticator round-trip).\n */\nexport type PasskeyRegistrationCeremony = {\n userHandle: Base64URLString;\n prfSalt: Base64URLString;\n challenge: Base64URLString;\n /** When this ceremony was started (ms since epoch); used for TTL pruning. */\n createdAt: number;\n};\n\n/**\n * In-memory state for one **in-flight** WebAuthn **authentication** ceremony\n * (`get()` options until the assertion is verified). Not a user login session.\n */\nexport type PasskeyAuthenticationCeremony = {\n challenge: Base64URLString;\n /** When this ceremony was started (ms since epoch); used for TTL pruning. */\n createdAt: number;\n};\n\n/**\n * PRF extension types not covered by DOM typings.\n */\nexport type PrfEvalExtension = {\n eval: {\n first: Base64URLString;\n };\n};\n\nexport type PrfClientExtensionResults = {\n prf?: {\n enabled?: boolean;\n results?: { first?: Base64URLString };\n };\n};\n"]}
package/dist/types.d.cts CHANGED
@@ -14,6 +14,8 @@ export type PasskeyCredentialInfo = {
14
14
  counter: number;
15
15
  /** Authenticator transports hint for `allowCredentials`. */
16
16
  transports?: AuthenticatorTransportFuture[];
17
+ /** Authenticator AAGUID captured from attested credential data at registration. */
18
+ aaguid: string;
17
19
  };
18
20
  /**
19
21
  * Vault key wrapped under the passkey-derived AES-256-GCM key.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,MAAM,MAAM,4BAA4B,GACpC,KAAK,GACL,OAAO,GACP,QAAQ,GACR,UAAU,GACV,KAAK,GACL,YAAY,GACZ,KAAK,CAAC;AAEV;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,0CAA0C;IAC1C,EAAE,EAAE,eAAe,CAAC;IACpB,gFAAgF;IAChF,SAAS,EAAE,eAAe,CAAC;IAC3B,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,4BAA4B,EAAE,CAAC;CAC7C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8DAA8D;IAC9D,UAAU,EAAE,YAAY,CAAC;IACzB,wDAAwD;IACxD,EAAE,EAAE,YAAY,CAAC;CAClB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE,MAAM,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,OAAO,EAAE,eAAe,CAAC;CAC1B,GACD;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC;AAE7B,4DAA4D;AAC5D,MAAM,MAAM,uBAAuB,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;AAErE,MAAM,MAAM,aAAa,GAAG;IAC1B,mFAAmF;IACnF,UAAU,EAAE,qBAAqB,CAAC;IAClC,uDAAuD;IACvD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,4DAA4D;IAC5D,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,eAAe,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,EAAE,eAAe,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE;QACJ,KAAK,EAAE,eAAe,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,CAAC,EAAE;QACJ,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,eAAe,CAAA;SAAE,CAAC;KACvC,CAAC;CACH,CAAC"}
1
+ {"version":3,"file":"types.d.cts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,MAAM,MAAM,4BAA4B,GACpC,KAAK,GACL,OAAO,GACP,QAAQ,GACR,UAAU,GACV,KAAK,GACL,YAAY,GACZ,KAAK,CAAC;AAEV;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,0CAA0C;IAC1C,EAAE,EAAE,eAAe,CAAC;IACpB,gFAAgF;IAChF,SAAS,EAAE,eAAe,CAAC;IAC3B,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,4BAA4B,EAAE,CAAC;IAC5C,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8DAA8D;IAC9D,UAAU,EAAE,YAAY,CAAC;IACzB,wDAAwD;IACxD,EAAE,EAAE,YAAY,CAAC;CAClB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE,MAAM,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,OAAO,EAAE,eAAe,CAAC;CAC1B,GACD;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC;AAE7B,4DAA4D;AAC5D,MAAM,MAAM,uBAAuB,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;AAErE,MAAM,MAAM,aAAa,GAAG;IAC1B,mFAAmF;IACnF,UAAU,EAAE,qBAAqB,CAAC;IAClC,uDAAuD;IACvD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,4DAA4D;IAC5D,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,eAAe,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,EAAE,eAAe,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE;QACJ,KAAK,EAAE,eAAe,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,CAAC,EAAE;QACJ,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,eAAe,CAAA;SAAE,CAAC;KACvC,CAAC;CACH,CAAC"}
package/dist/types.d.mts CHANGED
@@ -14,6 +14,8 @@ export type PasskeyCredentialInfo = {
14
14
  counter: number;
15
15
  /** Authenticator transports hint for `allowCredentials`. */
16
16
  transports?: AuthenticatorTransportFuture[];
17
+ /** Authenticator AAGUID captured from attested credential data at registration. */
18
+ aaguid: string;
17
19
  };
18
20
  /**
19
21
  * Vault key wrapped under the passkey-derived AES-256-GCM key.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,MAAM,MAAM,4BAA4B,GACpC,KAAK,GACL,OAAO,GACP,QAAQ,GACR,UAAU,GACV,KAAK,GACL,YAAY,GACZ,KAAK,CAAC;AAEV;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,0CAA0C;IAC1C,EAAE,EAAE,eAAe,CAAC;IACpB,gFAAgF;IAChF,SAAS,EAAE,eAAe,CAAC;IAC3B,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,4BAA4B,EAAE,CAAC;CAC7C,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8DAA8D;IAC9D,UAAU,EAAE,YAAY,CAAC;IACzB,wDAAwD;IACxD,EAAE,EAAE,YAAY,CAAC;CAClB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE,MAAM,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,OAAO,EAAE,eAAe,CAAC;CAC1B,GACD;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC;AAE7B,4DAA4D;AAC5D,MAAM,MAAM,uBAAuB,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;AAErE,MAAM,MAAM,aAAa,GAAG;IAC1B,mFAAmF;IACnF,UAAU,EAAE,qBAAqB,CAAC;IAClC,uDAAuD;IACvD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,4DAA4D;IAC5D,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,eAAe,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,EAAE,eAAe,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE;QACJ,KAAK,EAAE,eAAe,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,CAAC,EAAE;QACJ,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,eAAe,CAAA;SAAE,CAAC;KACvC,CAAC;CACH,CAAC"}
1
+ {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,MAAM,MAAM,4BAA4B,GACpC,KAAK,GACL,OAAO,GACP,QAAQ,GACR,UAAU,GACV,KAAK,GACL,YAAY,GACZ,KAAK,CAAC;AAEV;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,0CAA0C;IAC1C,EAAE,EAAE,eAAe,CAAC;IACpB,gFAAgF;IAChF,SAAS,EAAE,eAAe,CAAC;IAC3B,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,4BAA4B,EAAE,CAAC;IAC5C,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,8DAA8D;IAC9D,UAAU,EAAE,YAAY,CAAC;IACzB,wDAAwD;IACxD,EAAE,EAAE,YAAY,CAAC;CAClB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAC5B;IACE,MAAM,EAAE,KAAK,CAAC;IACd;;;OAGG;IACH,OAAO,EAAE,eAAe,CAAC;CAC1B,GACD;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,CAAC;AAE7B,4DAA4D;AAC5D,MAAM,MAAM,uBAAuB,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC;AAErE,MAAM,MAAM,aAAa,GAAG;IAC1B,mFAAmF;IACnF,UAAU,EAAE,qBAAqB,CAAC;IAClC,uDAAuD;IACvD,iBAAiB,EAAE,iBAAiB,CAAC;IACrC,4DAA4D;IAC5D,aAAa,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,UAAU,EAAE,eAAe,CAAC;IAC5B,OAAO,EAAE,eAAe,CAAC;IACzB,SAAS,EAAE,eAAe,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,SAAS,EAAE,eAAe,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE;QACJ,KAAK,EAAE,eAAe,CAAC;KACxB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,GAAG,CAAC,EAAE;QACJ,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE;YAAE,KAAK,CAAC,EAAE,eAAe,CAAA;SAAE,CAAC;KACvC,CAAC;CACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Base64String = string;\n\nexport type Base64URLString = string;\n\nexport type AuthenticatorTransportFuture =\n | 'ble'\n | 'cable'\n | 'hybrid'\n | 'internal'\n | 'nfc'\n | 'smart-card'\n | 'usb';\n\n/**\n * WebAuthn credential metadata used to identify the passkey and verify\n * subsequent assertions.\n */\nexport type PasskeyCredentialInfo = {\n /** WebAuthn credential ID (base64url). */\n id: Base64URLString;\n /** COSE-encoded credential public key (base64url) used to verify assertions. */\n publicKey: Base64URLString;\n /** Authenticator signature counter for replay/clone detection. */\n counter: number;\n /** Authenticator transports hint for `allowCredentials`. */\n transports?: AuthenticatorTransportFuture[];\n};\n\n/**\n * Vault key wrapped under the passkey-derived AES-256-GCM key.\n */\nexport type EncryptedVaultKey = {\n /** Base64-encoded AES-256-GCM ciphertext of the vault key. */\n ciphertext: Base64String;\n /** Base64-encoded AES-GCM IV used during encryption. */\n iv: Base64String;\n};\n\n/**\n * Parameters needed to reproduce the AES-256 wrapping key at unlock time.\n *\n * Encoded as a discriminated union so PRF-only fields (e.g. `prfSalt`) can\n * only exist on the PRF branch, removing the \"optional but actually\n * required\" footgun.\n */\nexport type PasskeyKeyDerivation =\n | {\n method: 'prf';\n /**\n * PRF salt sent in `get()` extension options to reproduce the same PRF\n * output that was generated at registration.\n */\n prfSalt: Base64URLString;\n }\n | { method: 'userHandle' };\n\n/** Discriminator value for {@link PasskeyKeyDerivation}. */\nexport type PasskeyDerivationMethod = PasskeyKeyDerivation['method'];\n\nexport type PasskeyRecord = {\n /** WebAuthn credential metadata used for assertion verification & re-discovery. */\n credential: PasskeyCredentialInfo;\n /** Vault key wrapped under the passkey-derived key. */\n encryptedVaultKey: EncryptedVaultKey;\n /** How the wrapping key is reconstructed at unlock time. */\n keyDerivation: PasskeyKeyDerivation;\n};\n\n/**\n * In-memory state for one **in-flight** WebAuthn **registration** ceremony\n * (from `create()` options until `protectVaultKeyWithPasskey` completes). This is\n * not a user login session; it is keyed by challenge and distinct from the full\n * spec ceremony (which includes the authenticator round-trip).\n */\nexport type PasskeyRegistrationCeremony = {\n userHandle: Base64URLString;\n prfSalt: Base64URLString;\n challenge: Base64URLString;\n /** When this ceremony was started (ms since epoch); used for TTL pruning. */\n createdAt: number;\n};\n\n/**\n * In-memory state for one **in-flight** WebAuthn **authentication** ceremony\n * (`get()` options until the assertion is verified). Not a user login session.\n */\nexport type PasskeyAuthenticationCeremony = {\n challenge: Base64URLString;\n /** When this ceremony was started (ms since epoch); used for TTL pruning. */\n createdAt: number;\n};\n\n/**\n * PRF extension types not covered by DOM typings.\n */\nexport type PrfEvalExtension = {\n eval: {\n first: Base64URLString;\n };\n};\n\nexport type PrfClientExtensionResults = {\n prf?: {\n enabled?: boolean;\n results?: { first?: Base64URLString };\n };\n};\n"]}
1
+ {"version":3,"file":"types.mjs","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type Base64String = string;\n\nexport type Base64URLString = string;\n\nexport type AuthenticatorTransportFuture =\n | 'ble'\n | 'cable'\n | 'hybrid'\n | 'internal'\n | 'nfc'\n | 'smart-card'\n | 'usb';\n\n/**\n * WebAuthn credential metadata used to identify the passkey and verify\n * subsequent assertions.\n */\nexport type PasskeyCredentialInfo = {\n /** WebAuthn credential ID (base64url). */\n id: Base64URLString;\n /** COSE-encoded credential public key (base64url) used to verify assertions. */\n publicKey: Base64URLString;\n /** Authenticator signature counter for replay/clone detection. */\n counter: number;\n /** Authenticator transports hint for `allowCredentials`. */\n transports?: AuthenticatorTransportFuture[];\n /** Authenticator AAGUID captured from attested credential data at registration. */\n aaguid: string;\n};\n\n/**\n * Vault key wrapped under the passkey-derived AES-256-GCM key.\n */\nexport type EncryptedVaultKey = {\n /** Base64-encoded AES-256-GCM ciphertext of the vault key. */\n ciphertext: Base64String;\n /** Base64-encoded AES-GCM IV used during encryption. */\n iv: Base64String;\n};\n\n/**\n * Parameters needed to reproduce the AES-256 wrapping key at unlock time.\n *\n * Encoded as a discriminated union so PRF-only fields (e.g. `prfSalt`) can\n * only exist on the PRF branch, removing the \"optional but actually\n * required\" footgun.\n */\nexport type PasskeyKeyDerivation =\n | {\n method: 'prf';\n /**\n * PRF salt sent in `get()` extension options to reproduce the same PRF\n * output that was generated at registration.\n */\n prfSalt: Base64URLString;\n }\n | { method: 'userHandle' };\n\n/** Discriminator value for {@link PasskeyKeyDerivation}. */\nexport type PasskeyDerivationMethod = PasskeyKeyDerivation['method'];\n\nexport type PasskeyRecord = {\n /** WebAuthn credential metadata used for assertion verification & re-discovery. */\n credential: PasskeyCredentialInfo;\n /** Vault key wrapped under the passkey-derived key. */\n encryptedVaultKey: EncryptedVaultKey;\n /** How the wrapping key is reconstructed at unlock time. */\n keyDerivation: PasskeyKeyDerivation;\n};\n\n/**\n * In-memory state for one **in-flight** WebAuthn **registration** ceremony\n * (from `create()` options until `protectVaultKeyWithPasskey` completes). This is\n * not a user login session; it is keyed by challenge and distinct from the full\n * spec ceremony (which includes the authenticator round-trip).\n */\nexport type PasskeyRegistrationCeremony = {\n userHandle: Base64URLString;\n prfSalt: Base64URLString;\n challenge: Base64URLString;\n /** When this ceremony was started (ms since epoch); used for TTL pruning. */\n createdAt: number;\n};\n\n/**\n * In-memory state for one **in-flight** WebAuthn **authentication** ceremony\n * (`get()` options until the assertion is verified). Not a user login session.\n */\nexport type PasskeyAuthenticationCeremony = {\n challenge: Base64URLString;\n /** When this ceremony was started (ms since epoch); used for TTL pruning. */\n createdAt: number;\n};\n\n/**\n * PRF extension types not covered by DOM typings.\n */\nexport type PrfEvalExtension = {\n eval: {\n first: Base64URLString;\n };\n};\n\nexport type PrfClientExtensionResults = {\n prf?: {\n enabled?: boolean;\n results?: { first?: Base64URLString };\n };\n};\n"]}
@@ -1,26 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.matchExpectedRPID = void 0;
4
+ const utils_1 = require("@metamask/utils");
4
5
  const sha2_1 = require("@noble/hashes/sha2");
5
6
  const encoding_1 = require("../utils/encoding.cjs");
6
- /**
7
- * Compare two Uint8Arrays for equality in constant time.
8
- *
9
- * @param first - First array.
10
- * @param second - Second array.
11
- * @returns Whether the two arrays are equal.
12
- */
13
- function areEqual(first, second) {
14
- if (first.length !== second.length) {
15
- return false;
16
- }
17
- let diff = 0;
18
- for (let i = 0; i < first.length; i++) {
19
- // eslint-disable-next-line no-bitwise
20
- diff |= first[i] ^ second[i];
21
- }
22
- return diff === 0;
23
- }
24
7
  /**
25
8
  * Verify that an authenticator data rpIdHash matches one of the expected
26
9
  * RP IDs by SHA-256 hashing each candidate and comparing.
@@ -33,7 +16,7 @@ function areEqual(first, second) {
33
16
  function matchExpectedRPID(rpIdHash, expectedRPIDs) {
34
17
  for (const rpID of expectedRPIDs) {
35
18
  const expectedHash = (0, sha2_1.sha256)(new TextEncoder().encode(rpID));
36
- if (areEqual(rpIdHash, expectedHash)) {
19
+ if ((0, utils_1.areUint8ArraysEqual)(rpIdHash, expectedHash)) {
37
20
  return rpID;
38
21
  }
39
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"match-expected-rp-id.cjs","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":";;;AAAA,6CAA4C;AAE5C,oDAA+C;AAE/C;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,KAAiB,EAAE,MAAkB;IACrD,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,sCAAsC;QACtC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,iBAAiB,CAC/B,QAAoB,EACpB,aAAuB;IAEvB,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,YAAY,GAAG,IAAA,aAAM,EAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,IAAI,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAA,qBAAU,EAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC7E,CAAC;AAXD,8CAWC","sourcesContent":["import { sha256 } from '@noble/hashes/sha2';\n\nimport { bytesToHex } from '../utils/encoding';\n\n/**\n * Compare two Uint8Arrays for equality in constant time.\n *\n * @param first - First array.\n * @param second - Second array.\n * @returns Whether the two arrays are equal.\n */\nfunction areEqual(first: Uint8Array, second: Uint8Array): boolean {\n if (first.length !== second.length) {\n return false;\n }\n let diff = 0;\n for (let i = 0; i < first.length; i++) {\n // eslint-disable-next-line no-bitwise\n diff |= first[i] ^ second[i];\n }\n return diff === 0;\n}\n\n/**\n * Verify that an authenticator data rpIdHash matches one of the expected\n * RP IDs by SHA-256 hashing each candidate and comparing.\n *\n * @param rpIdHash - The rpIdHash from authenticatorData (32 bytes).\n * @param expectedRPIDs - One or more RP ID strings to check against.\n * @returns The matching RP ID string.\n * @throws If no expected RP ID matches.\n */\nexport function matchExpectedRPID(\n rpIdHash: Uint8Array,\n expectedRPIDs: string[],\n): string {\n for (const rpID of expectedRPIDs) {\n const expectedHash = sha256(new TextEncoder().encode(rpID));\n if (areEqual(rpIdHash, expectedHash)) {\n return rpID;\n }\n }\n throw new Error(`Unexpected RP ID hash: received ${bytesToHex(rpIdHash)}`);\n}\n"]}
1
+ {"version":3,"file":"match-expected-rp-id.cjs","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":";;;AAAA,2CAAsD;AACtD,6CAA4C;AAE5C,oDAA+C;AAE/C;;;;;;;;GAQG;AACH,SAAgB,iBAAiB,CAC/B,QAAoB,EACpB,aAAuB;IAEvB,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,YAAY,GAAG,IAAA,aAAM,EAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,IAAI,IAAA,2BAAmB,EAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,mCAAmC,IAAA,qBAAU,EAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC7E,CAAC;AAXD,8CAWC","sourcesContent":["import { areUint8ArraysEqual } from '@metamask/utils';\nimport { sha256 } from '@noble/hashes/sha2';\n\nimport { bytesToHex } from '../utils/encoding';\n\n/**\n * Verify that an authenticator data rpIdHash matches one of the expected\n * RP IDs by SHA-256 hashing each candidate and comparing.\n *\n * @param rpIdHash - The rpIdHash from authenticatorData (32 bytes).\n * @param expectedRPIDs - One or more RP ID strings to check against.\n * @returns The matching RP ID string.\n * @throws If no expected RP ID matches.\n */\nexport function matchExpectedRPID(\n rpIdHash: Uint8Array,\n expectedRPIDs: string[],\n): string {\n for (const rpID of expectedRPIDs) {\n const expectedHash = sha256(new TextEncoder().encode(rpID));\n if (areUint8ArraysEqual(rpIdHash, expectedHash)) {\n return rpID;\n }\n }\n throw new Error(`Unexpected RP ID hash: received ${bytesToHex(rpIdHash)}`);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"match-expected-rp-id.d.cts","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":"AAuBA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,UAAU,EACpB,aAAa,EAAE,MAAM,EAAE,GACtB,MAAM,CAQR"}
1
+ {"version":3,"file":"match-expected-rp-id.d.cts","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,UAAU,EACpB,aAAa,EAAE,MAAM,EAAE,GACtB,MAAM,CAQR"}
@@ -1 +1 @@
1
- {"version":3,"file":"match-expected-rp-id.d.mts","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":"AAuBA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,UAAU,EACpB,aAAa,EAAE,MAAM,EAAE,GACtB,MAAM,CAQR"}
1
+ {"version":3,"file":"match-expected-rp-id.d.mts","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,UAAU,EACpB,aAAa,EAAE,MAAM,EAAE,GACtB,MAAM,CAQR"}
@@ -1,23 +1,6 @@
1
+ import { areUint8ArraysEqual } from "@metamask/utils";
1
2
  import { sha256 } from "@noble/hashes/sha2";
2
3
  import { bytesToHex } from "../utils/encoding.mjs";
3
- /**
4
- * Compare two Uint8Arrays for equality in constant time.
5
- *
6
- * @param first - First array.
7
- * @param second - Second array.
8
- * @returns Whether the two arrays are equal.
9
- */
10
- function areEqual(first, second) {
11
- if (first.length !== second.length) {
12
- return false;
13
- }
14
- let diff = 0;
15
- for (let i = 0; i < first.length; i++) {
16
- // eslint-disable-next-line no-bitwise
17
- diff |= first[i] ^ second[i];
18
- }
19
- return diff === 0;
20
- }
21
4
  /**
22
5
  * Verify that an authenticator data rpIdHash matches one of the expected
23
6
  * RP IDs by SHA-256 hashing each candidate and comparing.
@@ -30,7 +13,7 @@ function areEqual(first, second) {
30
13
  export function matchExpectedRPID(rpIdHash, expectedRPIDs) {
31
14
  for (const rpID of expectedRPIDs) {
32
15
  const expectedHash = sha256(new TextEncoder().encode(rpID));
33
- if (areEqual(rpIdHash, expectedHash)) {
16
+ if (areUint8ArraysEqual(rpIdHash, expectedHash)) {
34
17
  return rpID;
35
18
  }
36
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"match-expected-rp-id.mjs","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,2BAA2B;AAE5C,OAAO,EAAE,UAAU,EAAE,8BAA0B;AAE/C;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,KAAiB,EAAE,MAAkB;IACrD,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,sCAAsC;QACtC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAoB,EACpB,aAAuB;IAEvB,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,IAAI,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,mCAAmC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["import { sha256 } from '@noble/hashes/sha2';\n\nimport { bytesToHex } from '../utils/encoding';\n\n/**\n * Compare two Uint8Arrays for equality in constant time.\n *\n * @param first - First array.\n * @param second - Second array.\n * @returns Whether the two arrays are equal.\n */\nfunction areEqual(first: Uint8Array, second: Uint8Array): boolean {\n if (first.length !== second.length) {\n return false;\n }\n let diff = 0;\n for (let i = 0; i < first.length; i++) {\n // eslint-disable-next-line no-bitwise\n diff |= first[i] ^ second[i];\n }\n return diff === 0;\n}\n\n/**\n * Verify that an authenticator data rpIdHash matches one of the expected\n * RP IDs by SHA-256 hashing each candidate and comparing.\n *\n * @param rpIdHash - The rpIdHash from authenticatorData (32 bytes).\n * @param expectedRPIDs - One or more RP ID strings to check against.\n * @returns The matching RP ID string.\n * @throws If no expected RP ID matches.\n */\nexport function matchExpectedRPID(\n rpIdHash: Uint8Array,\n expectedRPIDs: string[],\n): string {\n for (const rpID of expectedRPIDs) {\n const expectedHash = sha256(new TextEncoder().encode(rpID));\n if (areEqual(rpIdHash, expectedHash)) {\n return rpID;\n }\n }\n throw new Error(`Unexpected RP ID hash: received ${bytesToHex(rpIdHash)}`);\n}\n"]}
1
+ {"version":3,"file":"match-expected-rp-id.mjs","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,wBAAwB;AACtD,OAAO,EAAE,MAAM,EAAE,2BAA2B;AAE5C,OAAO,EAAE,UAAU,EAAE,8BAA0B;AAE/C;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAoB,EACpB,aAAuB;IAEvB,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,IAAI,mBAAmB,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,mCAAmC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC7E,CAAC","sourcesContent":["import { areUint8ArraysEqual } from '@metamask/utils';\nimport { sha256 } from '@noble/hashes/sha2';\n\nimport { bytesToHex } from '../utils/encoding';\n\n/**\n * Verify that an authenticator data rpIdHash matches one of the expected\n * RP IDs by SHA-256 hashing each candidate and comparing.\n *\n * @param rpIdHash - The rpIdHash from authenticatorData (32 bytes).\n * @param expectedRPIDs - One or more RP ID strings to check against.\n * @returns The matching RP ID string.\n * @throws If no expected RP ID matches.\n */\nexport function matchExpectedRPID(\n rpIdHash: Uint8Array,\n expectedRPIDs: string[],\n): string {\n for (const rpID of expectedRPIDs) {\n const expectedHash = sha256(new TextEncoder().encode(rpID));\n if (areUint8ArraysEqual(rpIdHash, expectedHash)) {\n return rpID;\n }\n }\n throw new Error(`Unexpected RP ID hash: received ${bytesToHex(rpIdHash)}`);\n}\n"]}
@@ -117,7 +117,7 @@ async function verifyAuthenticationResponse(opts) {
117
117
  }
118
118
  if ((counter > 0 || credential.counter > 0) &&
119
119
  counter <= credential.counter) {
120
- throw new Error(`Response counter value ${counter} was lower than expected ${credential.counter}`);
120
+ throw new Error(`Response counter value ${counter} must be greater than stored counter ${credential.counter}`);
121
121
  }
122
122
  return {
123
123
  verified: true,
@@ -1 +1 @@
1
- {"version":3,"file":"verify-authentication-response.cjs","sourceRoot":"","sources":["../../src/webauthn/verify-authentication-response.ts"],"names":[],"mappings":"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","sourcesContent":["import { decodePartialCBOR } from '@levischuck/tiny-cbor';\nimport { concatBytes } from '@metamask/utils';\nimport { sha256 } from '@noble/hashes/sha2';\n\nimport type { AuthenticatorTransportFuture } from '../types';\nimport { base64URLToBytes } from '../utils/encoding';\nimport { decodeClientDataJSON } from './decode-client-data-json';\nimport { matchExpectedRPID } from './match-expected-rp-id';\nimport { parseAuthenticatorData } from './parse-authenticator-data';\nimport type { ParsedAuthenticatorData } from './types';\nimport type { PasskeyAuthenticationResponse } from './types';\nimport { verifySignature } from './verify-signature';\n\nexport type VerifiedAuthenticationResponse =\n | { verified: false; authenticationInfo?: never }\n | {\n verified: true;\n authenticationInfo: {\n credentialId: string;\n newCounter: number;\n userVerified: boolean;\n origin: string;\n rpID: string;\n };\n };\n\n/**\n * Verifies a WebAuthn authentication (assertion) response per\n * W3C WebAuthn Level 3 §7.2.\n *\n * Performs the following checks in order:\n * 1. Credential ID presence, base64url consistency, and type.\n * 2. `clientDataJSON` -- type is `\"webauthn.get\"`, challenge and origin\n * match.\n * 3. `authenticatorData` -- RP ID hash matches, user-presence flag is\n * set, and optional user-verification flag is checked.\n * 4. Signature verification -- `signature` is verified over\n * `authData || SHA-256(clientDataJSON)` using the stored credential\n * public key (COSE-encoded).\n * 5. Counter monotonicity -- if either the stored or returned counter\n * is non-zero, the new counter must exceed the stored value.\n *\n * @param opts - Verification options.\n * @param opts.response - The `PublicKeyCredential` result from\n * `navigator.credentials.get()`, serialized as JSON.\n * @param opts.expectedChallenge - The base64url challenge that was issued\n * for this ceremony.\n * @param opts.expectedOrigin - One or more acceptable origins.\n * @param opts.expectedRPID - The Relying Party ID domain.\n * @param opts.credential - The stored credential record to verify against.\n * @param opts.credential.id - The credential ID (base64url).\n * @param opts.credential.publicKey - The COSE-encoded public key bytes\n * persisted during registration.\n * @param opts.credential.counter - The last known signature counter value.\n * @param opts.credential.transports - Optional authenticator transports.\n * @param opts.requireUserVerification - When `true`, verification fails\n * if the UV flag is not set. Defaults to `false`.\n * @returns Verification result containing `verified` status and parsed\n * authentication info (new counter, origin, RP ID).\n */\nexport async function verifyAuthenticationResponse(opts: {\n response: PasskeyAuthenticationResponse;\n expectedChallenge: string;\n expectedOrigin: string | string[];\n expectedRPID: string;\n credential: {\n id: string;\n publicKey: Uint8Array;\n counter: number;\n transports?: AuthenticatorTransportFuture[];\n };\n requireUserVerification?: boolean;\n}): Promise<VerifiedAuthenticationResponse> {\n const {\n response,\n expectedChallenge,\n expectedOrigin,\n expectedRPID,\n credential,\n requireUserVerification = false,\n } = opts;\n\n const {\n id,\n rawId,\n type: credentialType,\n response: assertionResponse,\n } = response;\n\n // Ensure credential specified an ID\n if (!id) {\n throw new Error('Missing credential ID');\n }\n\n // Ensure ID is base64url-encoded\n if (id !== rawId) {\n throw new Error('Credential ID was not base64url-encoded');\n }\n\n // Make sure credential type is public-key\n if (credentialType !== 'public-key') {\n throw new Error(\n `Unexpected credential type ${String(credentialType)}, expected \"public-key\"`,\n );\n }\n\n if (typeof assertionResponse?.clientDataJSON !== 'string') {\n throw new Error('Credential response clientDataJSON was not a string');\n }\n\n const clientDataJSON = decodeClientDataJSON(assertionResponse.clientDataJSON);\n const { type, challenge, origin, tokenBinding } = clientDataJSON;\n\n // Make sure we're handling an authentication\n if (type !== 'webauthn.get') {\n throw new Error(`Unexpected authentication response type: ${type}`);\n }\n\n // Ensure the device provided the challenge we gave it\n if (challenge !== expectedChallenge) {\n throw new Error(\n `Unexpected authentication response challenge \"${challenge}\", expected \"${expectedChallenge}\"`,\n );\n }\n\n // Check that the origin is our site\n const expectedOrigins = Array.isArray(expectedOrigin)\n ? expectedOrigin\n : [expectedOrigin];\n if (!expectedOrigins.includes(origin)) {\n throw new Error(\n `Unexpected authentication response origin \"${origin}\", expected one of: ${expectedOrigins.join(', ')}`,\n );\n }\n\n if (\n assertionResponse.userHandle &&\n typeof assertionResponse.userHandle !== 'string'\n ) {\n throw new Error('Credential response userHandle was not a string');\n }\n\n if (tokenBinding) {\n if (typeof tokenBinding !== 'object') {\n throw new Error('ClientDataJSON tokenBinding was not an object');\n }\n\n if (\n !['present', 'supported', 'not-supported'].includes(tokenBinding.status)\n ) {\n throw new Error(`Unexpected tokenBinding status ${tokenBinding.status}`);\n }\n }\n\n const authDataBuffer = base64URLToBytes(assertionResponse.authenticatorData);\n const parsedAuthData: ParsedAuthenticatorData =\n parseAuthenticatorData(authDataBuffer);\n const { rpIdHash, flags, counter } = parsedAuthData;\n\n // Make sure the response's RP ID is ours\n const matchedRPID = matchExpectedRPID(rpIdHash, [expectedRPID]);\n\n // WebAuthn only requires the user presence flag be true\n if (!flags.up) {\n throw new Error('User not present during authentication');\n }\n\n // Enforce user verification if required\n if (requireUserVerification && !flags.uv) {\n throw new Error(\n 'User verification required, but user could not be verified',\n );\n }\n\n const clientDataHash = sha256(\n base64URLToBytes(assertionResponse.clientDataJSON),\n );\n const signatureBase = concatBytes([authDataBuffer, clientDataHash]);\n\n const signature = base64URLToBytes(assertionResponse.signature);\n\n const cosePublicKey = decodePartialCBOR(\n new Uint8Array(credential.publicKey),\n 0,\n )[0] as Map<number, number | Uint8Array>;\n\n const verified = await verifySignature({\n cosePublicKey,\n signature,\n data: signatureBase,\n });\n\n if (!verified) {\n return { verified: false };\n }\n\n if (\n (counter > 0 || credential.counter > 0) &&\n counter <= credential.counter\n ) {\n throw new Error(\n `Response counter value ${counter} was lower than expected ${credential.counter}`,\n );\n }\n\n return {\n verified: true,\n authenticationInfo: {\n credentialId: credential.id,\n newCounter: counter,\n userVerified: flags.uv,\n origin: clientDataJSON.origin,\n rpID: matchedRPID,\n },\n };\n}\n"]}
1
+ {"version":3,"file":"verify-authentication-response.cjs","sourceRoot":"","sources":["../../src/webauthn/verify-authentication-response.ts"],"names":[],"mappings":"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","sourcesContent":["import { decodePartialCBOR } from '@levischuck/tiny-cbor';\nimport { concatBytes } from '@metamask/utils';\nimport { sha256 } from '@noble/hashes/sha2';\n\nimport type { AuthenticatorTransportFuture } from '../types';\nimport { base64URLToBytes } from '../utils/encoding';\nimport { decodeClientDataJSON } from './decode-client-data-json';\nimport { matchExpectedRPID } from './match-expected-rp-id';\nimport { parseAuthenticatorData } from './parse-authenticator-data';\nimport type { ParsedAuthenticatorData } from './types';\nimport type { PasskeyAuthenticationResponse } from './types';\nimport { verifySignature } from './verify-signature';\n\nexport type VerifiedAuthenticationResponse =\n | { verified: false; authenticationInfo?: never }\n | {\n verified: true;\n authenticationInfo: {\n credentialId: string;\n newCounter: number;\n userVerified: boolean;\n origin: string;\n rpID: string;\n };\n };\n\n/**\n * Verifies a WebAuthn authentication (assertion) response per\n * W3C WebAuthn Level 3 §7.2.\n *\n * Performs the following checks in order:\n * 1. Credential ID presence, base64url consistency, and type.\n * 2. `clientDataJSON` -- type is `\"webauthn.get\"`, challenge and origin\n * match.\n * 3. `authenticatorData` -- RP ID hash matches, user-presence flag is\n * set, and optional user-verification flag is checked.\n * 4. Signature verification -- `signature` is verified over\n * `authData || SHA-256(clientDataJSON)` using the stored credential\n * public key (COSE-encoded).\n * 5. Counter monotonicity -- if either the stored or returned counter\n * is non-zero, the new counter must exceed the stored value.\n *\n * @param opts - Verification options.\n * @param opts.response - The `PublicKeyCredential` result from\n * `navigator.credentials.get()`, serialized as JSON.\n * @param opts.expectedChallenge - The base64url challenge that was issued\n * for this ceremony.\n * @param opts.expectedOrigin - One or more acceptable origins.\n * @param opts.expectedRPID - The Relying Party ID domain.\n * @param opts.credential - The stored credential record to verify against.\n * @param opts.credential.id - The credential ID (base64url).\n * @param opts.credential.publicKey - The COSE-encoded public key bytes\n * persisted during registration.\n * @param opts.credential.counter - The last known signature counter value.\n * @param opts.credential.transports - Optional authenticator transports.\n * @param opts.requireUserVerification - When `true`, verification fails\n * if the UV flag is not set. Defaults to `false`.\n * @returns Verification result containing `verified` status and parsed\n * authentication info (new counter, origin, RP ID).\n */\nexport async function verifyAuthenticationResponse(opts: {\n response: PasskeyAuthenticationResponse;\n expectedChallenge: string;\n expectedOrigin: string | string[];\n expectedRPID: string;\n credential: {\n id: string;\n publicKey: Uint8Array;\n counter: number;\n transports?: AuthenticatorTransportFuture[];\n };\n requireUserVerification?: boolean;\n}): Promise<VerifiedAuthenticationResponse> {\n const {\n response,\n expectedChallenge,\n expectedOrigin,\n expectedRPID,\n credential,\n requireUserVerification = false,\n } = opts;\n\n const {\n id,\n rawId,\n type: credentialType,\n response: assertionResponse,\n } = response;\n\n // Ensure credential specified an ID\n if (!id) {\n throw new Error('Missing credential ID');\n }\n\n // Ensure ID is base64url-encoded\n if (id !== rawId) {\n throw new Error('Credential ID was not base64url-encoded');\n }\n\n // Make sure credential type is public-key\n if (credentialType !== 'public-key') {\n throw new Error(\n `Unexpected credential type ${String(credentialType)}, expected \"public-key\"`,\n );\n }\n\n if (typeof assertionResponse?.clientDataJSON !== 'string') {\n throw new Error('Credential response clientDataJSON was not a string');\n }\n\n const clientDataJSON = decodeClientDataJSON(assertionResponse.clientDataJSON);\n const { type, challenge, origin, tokenBinding } = clientDataJSON;\n\n // Make sure we're handling an authentication\n if (type !== 'webauthn.get') {\n throw new Error(`Unexpected authentication response type: ${type}`);\n }\n\n // Ensure the device provided the challenge we gave it\n if (challenge !== expectedChallenge) {\n throw new Error(\n `Unexpected authentication response challenge \"${challenge}\", expected \"${expectedChallenge}\"`,\n );\n }\n\n // Check that the origin is our site\n const expectedOrigins = Array.isArray(expectedOrigin)\n ? expectedOrigin\n : [expectedOrigin];\n if (!expectedOrigins.includes(origin)) {\n throw new Error(\n `Unexpected authentication response origin \"${origin}\", expected one of: ${expectedOrigins.join(', ')}`,\n );\n }\n\n if (\n assertionResponse.userHandle &&\n typeof assertionResponse.userHandle !== 'string'\n ) {\n throw new Error('Credential response userHandle was not a string');\n }\n\n if (tokenBinding) {\n if (typeof tokenBinding !== 'object') {\n throw new Error('ClientDataJSON tokenBinding was not an object');\n }\n\n if (\n !['present', 'supported', 'not-supported'].includes(tokenBinding.status)\n ) {\n throw new Error(`Unexpected tokenBinding status ${tokenBinding.status}`);\n }\n }\n\n const authDataBuffer = base64URLToBytes(assertionResponse.authenticatorData);\n const parsedAuthData: ParsedAuthenticatorData =\n parseAuthenticatorData(authDataBuffer);\n const { rpIdHash, flags, counter } = parsedAuthData;\n\n // Make sure the response's RP ID is ours\n const matchedRPID = matchExpectedRPID(rpIdHash, [expectedRPID]);\n\n // WebAuthn only requires the user presence flag be true\n if (!flags.up) {\n throw new Error('User not present during authentication');\n }\n\n // Enforce user verification if required\n if (requireUserVerification && !flags.uv) {\n throw new Error(\n 'User verification required, but user could not be verified',\n );\n }\n\n const clientDataHash = sha256(\n base64URLToBytes(assertionResponse.clientDataJSON),\n );\n const signatureBase = concatBytes([authDataBuffer, clientDataHash]);\n\n const signature = base64URLToBytes(assertionResponse.signature);\n\n const cosePublicKey = decodePartialCBOR(\n new Uint8Array(credential.publicKey),\n 0,\n )[0] as Map<number, number | Uint8Array>;\n\n const verified = await verifySignature({\n cosePublicKey,\n signature,\n data: signatureBase,\n });\n\n if (!verified) {\n return { verified: false };\n }\n\n if (\n (counter > 0 || credential.counter > 0) &&\n counter <= credential.counter\n ) {\n throw new Error(\n `Response counter value ${counter} must be greater than stored counter ${credential.counter}`,\n );\n }\n\n return {\n verified: true,\n authenticationInfo: {\n credentialId: credential.id,\n newCounter: counter,\n userVerified: flags.uv,\n origin: clientDataJSON.origin,\n rpID: matchedRPID,\n },\n };\n}\n"]}
@@ -114,7 +114,7 @@ export async function verifyAuthenticationResponse(opts) {
114
114
  }
115
115
  if ((counter > 0 || credential.counter > 0) &&
116
116
  counter <= credential.counter) {
117
- throw new Error(`Response counter value ${counter} was lower than expected ${credential.counter}`);
117
+ throw new Error(`Response counter value ${counter} must be greater than stored counter ${credential.counter}`);
118
118
  }
119
119
  return {
120
120
  verified: true,
@@ -1 +1 @@
1
- {"version":3,"file":"verify-authentication-response.mjs","sourceRoot":"","sources":["../../src/webauthn/verify-authentication-response.ts"],"names":[],"mappings":"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","sourcesContent":["import { decodePartialCBOR } from '@levischuck/tiny-cbor';\nimport { concatBytes } from '@metamask/utils';\nimport { sha256 } from '@noble/hashes/sha2';\n\nimport type { AuthenticatorTransportFuture } from '../types';\nimport { base64URLToBytes } from '../utils/encoding';\nimport { decodeClientDataJSON } from './decode-client-data-json';\nimport { matchExpectedRPID } from './match-expected-rp-id';\nimport { parseAuthenticatorData } from './parse-authenticator-data';\nimport type { ParsedAuthenticatorData } from './types';\nimport type { PasskeyAuthenticationResponse } from './types';\nimport { verifySignature } from './verify-signature';\n\nexport type VerifiedAuthenticationResponse =\n | { verified: false; authenticationInfo?: never }\n | {\n verified: true;\n authenticationInfo: {\n credentialId: string;\n newCounter: number;\n userVerified: boolean;\n origin: string;\n rpID: string;\n };\n };\n\n/**\n * Verifies a WebAuthn authentication (assertion) response per\n * W3C WebAuthn Level 3 §7.2.\n *\n * Performs the following checks in order:\n * 1. Credential ID presence, base64url consistency, and type.\n * 2. `clientDataJSON` -- type is `\"webauthn.get\"`, challenge and origin\n * match.\n * 3. `authenticatorData` -- RP ID hash matches, user-presence flag is\n * set, and optional user-verification flag is checked.\n * 4. Signature verification -- `signature` is verified over\n * `authData || SHA-256(clientDataJSON)` using the stored credential\n * public key (COSE-encoded).\n * 5. Counter monotonicity -- if either the stored or returned counter\n * is non-zero, the new counter must exceed the stored value.\n *\n * @param opts - Verification options.\n * @param opts.response - The `PublicKeyCredential` result from\n * `navigator.credentials.get()`, serialized as JSON.\n * @param opts.expectedChallenge - The base64url challenge that was issued\n * for this ceremony.\n * @param opts.expectedOrigin - One or more acceptable origins.\n * @param opts.expectedRPID - The Relying Party ID domain.\n * @param opts.credential - The stored credential record to verify against.\n * @param opts.credential.id - The credential ID (base64url).\n * @param opts.credential.publicKey - The COSE-encoded public key bytes\n * persisted during registration.\n * @param opts.credential.counter - The last known signature counter value.\n * @param opts.credential.transports - Optional authenticator transports.\n * @param opts.requireUserVerification - When `true`, verification fails\n * if the UV flag is not set. Defaults to `false`.\n * @returns Verification result containing `verified` status and parsed\n * authentication info (new counter, origin, RP ID).\n */\nexport async function verifyAuthenticationResponse(opts: {\n response: PasskeyAuthenticationResponse;\n expectedChallenge: string;\n expectedOrigin: string | string[];\n expectedRPID: string;\n credential: {\n id: string;\n publicKey: Uint8Array;\n counter: number;\n transports?: AuthenticatorTransportFuture[];\n };\n requireUserVerification?: boolean;\n}): Promise<VerifiedAuthenticationResponse> {\n const {\n response,\n expectedChallenge,\n expectedOrigin,\n expectedRPID,\n credential,\n requireUserVerification = false,\n } = opts;\n\n const {\n id,\n rawId,\n type: credentialType,\n response: assertionResponse,\n } = response;\n\n // Ensure credential specified an ID\n if (!id) {\n throw new Error('Missing credential ID');\n }\n\n // Ensure ID is base64url-encoded\n if (id !== rawId) {\n throw new Error('Credential ID was not base64url-encoded');\n }\n\n // Make sure credential type is public-key\n if (credentialType !== 'public-key') {\n throw new Error(\n `Unexpected credential type ${String(credentialType)}, expected \"public-key\"`,\n );\n }\n\n if (typeof assertionResponse?.clientDataJSON !== 'string') {\n throw new Error('Credential response clientDataJSON was not a string');\n }\n\n const clientDataJSON = decodeClientDataJSON(assertionResponse.clientDataJSON);\n const { type, challenge, origin, tokenBinding } = clientDataJSON;\n\n // Make sure we're handling an authentication\n if (type !== 'webauthn.get') {\n throw new Error(`Unexpected authentication response type: ${type}`);\n }\n\n // Ensure the device provided the challenge we gave it\n if (challenge !== expectedChallenge) {\n throw new Error(\n `Unexpected authentication response challenge \"${challenge}\", expected \"${expectedChallenge}\"`,\n );\n }\n\n // Check that the origin is our site\n const expectedOrigins = Array.isArray(expectedOrigin)\n ? expectedOrigin\n : [expectedOrigin];\n if (!expectedOrigins.includes(origin)) {\n throw new Error(\n `Unexpected authentication response origin \"${origin}\", expected one of: ${expectedOrigins.join(', ')}`,\n );\n }\n\n if (\n assertionResponse.userHandle &&\n typeof assertionResponse.userHandle !== 'string'\n ) {\n throw new Error('Credential response userHandle was not a string');\n }\n\n if (tokenBinding) {\n if (typeof tokenBinding !== 'object') {\n throw new Error('ClientDataJSON tokenBinding was not an object');\n }\n\n if (\n !['present', 'supported', 'not-supported'].includes(tokenBinding.status)\n ) {\n throw new Error(`Unexpected tokenBinding status ${tokenBinding.status}`);\n }\n }\n\n const authDataBuffer = base64URLToBytes(assertionResponse.authenticatorData);\n const parsedAuthData: ParsedAuthenticatorData =\n parseAuthenticatorData(authDataBuffer);\n const { rpIdHash, flags, counter } = parsedAuthData;\n\n // Make sure the response's RP ID is ours\n const matchedRPID = matchExpectedRPID(rpIdHash, [expectedRPID]);\n\n // WebAuthn only requires the user presence flag be true\n if (!flags.up) {\n throw new Error('User not present during authentication');\n }\n\n // Enforce user verification if required\n if (requireUserVerification && !flags.uv) {\n throw new Error(\n 'User verification required, but user could not be verified',\n );\n }\n\n const clientDataHash = sha256(\n base64URLToBytes(assertionResponse.clientDataJSON),\n );\n const signatureBase = concatBytes([authDataBuffer, clientDataHash]);\n\n const signature = base64URLToBytes(assertionResponse.signature);\n\n const cosePublicKey = decodePartialCBOR(\n new Uint8Array(credential.publicKey),\n 0,\n )[0] as Map<number, number | Uint8Array>;\n\n const verified = await verifySignature({\n cosePublicKey,\n signature,\n data: signatureBase,\n });\n\n if (!verified) {\n return { verified: false };\n }\n\n if (\n (counter > 0 || credential.counter > 0) &&\n counter <= credential.counter\n ) {\n throw new Error(\n `Response counter value ${counter} was lower than expected ${credential.counter}`,\n );\n }\n\n return {\n verified: true,\n authenticationInfo: {\n credentialId: credential.id,\n newCounter: counter,\n userVerified: flags.uv,\n origin: clientDataJSON.origin,\n rpID: matchedRPID,\n },\n };\n}\n"]}
1
+ {"version":3,"file":"verify-authentication-response.mjs","sourceRoot":"","sources":["../../src/webauthn/verify-authentication-response.ts"],"names":[],"mappings":"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","sourcesContent":["import { decodePartialCBOR } from '@levischuck/tiny-cbor';\nimport { concatBytes } from '@metamask/utils';\nimport { sha256 } from '@noble/hashes/sha2';\n\nimport type { AuthenticatorTransportFuture } from '../types';\nimport { base64URLToBytes } from '../utils/encoding';\nimport { decodeClientDataJSON } from './decode-client-data-json';\nimport { matchExpectedRPID } from './match-expected-rp-id';\nimport { parseAuthenticatorData } from './parse-authenticator-data';\nimport type { ParsedAuthenticatorData } from './types';\nimport type { PasskeyAuthenticationResponse } from './types';\nimport { verifySignature } from './verify-signature';\n\nexport type VerifiedAuthenticationResponse =\n | { verified: false; authenticationInfo?: never }\n | {\n verified: true;\n authenticationInfo: {\n credentialId: string;\n newCounter: number;\n userVerified: boolean;\n origin: string;\n rpID: string;\n };\n };\n\n/**\n * Verifies a WebAuthn authentication (assertion) response per\n * W3C WebAuthn Level 3 §7.2.\n *\n * Performs the following checks in order:\n * 1. Credential ID presence, base64url consistency, and type.\n * 2. `clientDataJSON` -- type is `\"webauthn.get\"`, challenge and origin\n * match.\n * 3. `authenticatorData` -- RP ID hash matches, user-presence flag is\n * set, and optional user-verification flag is checked.\n * 4. Signature verification -- `signature` is verified over\n * `authData || SHA-256(clientDataJSON)` using the stored credential\n * public key (COSE-encoded).\n * 5. Counter monotonicity -- if either the stored or returned counter\n * is non-zero, the new counter must exceed the stored value.\n *\n * @param opts - Verification options.\n * @param opts.response - The `PublicKeyCredential` result from\n * `navigator.credentials.get()`, serialized as JSON.\n * @param opts.expectedChallenge - The base64url challenge that was issued\n * for this ceremony.\n * @param opts.expectedOrigin - One or more acceptable origins.\n * @param opts.expectedRPID - The Relying Party ID domain.\n * @param opts.credential - The stored credential record to verify against.\n * @param opts.credential.id - The credential ID (base64url).\n * @param opts.credential.publicKey - The COSE-encoded public key bytes\n * persisted during registration.\n * @param opts.credential.counter - The last known signature counter value.\n * @param opts.credential.transports - Optional authenticator transports.\n * @param opts.requireUserVerification - When `true`, verification fails\n * if the UV flag is not set. Defaults to `false`.\n * @returns Verification result containing `verified` status and parsed\n * authentication info (new counter, origin, RP ID).\n */\nexport async function verifyAuthenticationResponse(opts: {\n response: PasskeyAuthenticationResponse;\n expectedChallenge: string;\n expectedOrigin: string | string[];\n expectedRPID: string;\n credential: {\n id: string;\n publicKey: Uint8Array;\n counter: number;\n transports?: AuthenticatorTransportFuture[];\n };\n requireUserVerification?: boolean;\n}): Promise<VerifiedAuthenticationResponse> {\n const {\n response,\n expectedChallenge,\n expectedOrigin,\n expectedRPID,\n credential,\n requireUserVerification = false,\n } = opts;\n\n const {\n id,\n rawId,\n type: credentialType,\n response: assertionResponse,\n } = response;\n\n // Ensure credential specified an ID\n if (!id) {\n throw new Error('Missing credential ID');\n }\n\n // Ensure ID is base64url-encoded\n if (id !== rawId) {\n throw new Error('Credential ID was not base64url-encoded');\n }\n\n // Make sure credential type is public-key\n if (credentialType !== 'public-key') {\n throw new Error(\n `Unexpected credential type ${String(credentialType)}, expected \"public-key\"`,\n );\n }\n\n if (typeof assertionResponse?.clientDataJSON !== 'string') {\n throw new Error('Credential response clientDataJSON was not a string');\n }\n\n const clientDataJSON = decodeClientDataJSON(assertionResponse.clientDataJSON);\n const { type, challenge, origin, tokenBinding } = clientDataJSON;\n\n // Make sure we're handling an authentication\n if (type !== 'webauthn.get') {\n throw new Error(`Unexpected authentication response type: ${type}`);\n }\n\n // Ensure the device provided the challenge we gave it\n if (challenge !== expectedChallenge) {\n throw new Error(\n `Unexpected authentication response challenge \"${challenge}\", expected \"${expectedChallenge}\"`,\n );\n }\n\n // Check that the origin is our site\n const expectedOrigins = Array.isArray(expectedOrigin)\n ? expectedOrigin\n : [expectedOrigin];\n if (!expectedOrigins.includes(origin)) {\n throw new Error(\n `Unexpected authentication response origin \"${origin}\", expected one of: ${expectedOrigins.join(', ')}`,\n );\n }\n\n if (\n assertionResponse.userHandle &&\n typeof assertionResponse.userHandle !== 'string'\n ) {\n throw new Error('Credential response userHandle was not a string');\n }\n\n if (tokenBinding) {\n if (typeof tokenBinding !== 'object') {\n throw new Error('ClientDataJSON tokenBinding was not an object');\n }\n\n if (\n !['present', 'supported', 'not-supported'].includes(tokenBinding.status)\n ) {\n throw new Error(`Unexpected tokenBinding status ${tokenBinding.status}`);\n }\n }\n\n const authDataBuffer = base64URLToBytes(assertionResponse.authenticatorData);\n const parsedAuthData: ParsedAuthenticatorData =\n parseAuthenticatorData(authDataBuffer);\n const { rpIdHash, flags, counter } = parsedAuthData;\n\n // Make sure the response's RP ID is ours\n const matchedRPID = matchExpectedRPID(rpIdHash, [expectedRPID]);\n\n // WebAuthn only requires the user presence flag be true\n if (!flags.up) {\n throw new Error('User not present during authentication');\n }\n\n // Enforce user verification if required\n if (requireUserVerification && !flags.uv) {\n throw new Error(\n 'User verification required, but user could not be verified',\n );\n }\n\n const clientDataHash = sha256(\n base64URLToBytes(assertionResponse.clientDataJSON),\n );\n const signatureBase = concatBytes([authDataBuffer, clientDataHash]);\n\n const signature = base64URLToBytes(assertionResponse.signature);\n\n const cosePublicKey = decodePartialCBOR(\n new Uint8Array(credential.publicKey),\n 0,\n )[0] as Map<number, number | Uint8Array>;\n\n const verified = await verifySignature({\n cosePublicKey,\n signature,\n data: signatureBase,\n });\n\n if (!verified) {\n return { verified: false };\n }\n\n if (\n (counter > 0 || credential.counter > 0) &&\n counter <= credential.counter\n ) {\n throw new Error(\n `Response counter value ${counter} must be greater than stored counter ${credential.counter}`,\n );\n }\n\n return {\n verified: true,\n authenticationInfo: {\n credentialId: credential.id,\n newCounter: counter,\n userVerified: flags.uv,\n origin: clientDataJSON.origin,\n rpID: matchedRPID,\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/passkey-controller",
3
- "version": "0.0.0-preview-4c0846313",
3
+ "version": "0.0.0-preview-dc38a2562",
4
4
  "description": "Controller and utilities for passkey-based wallet unlock",
5
5
  "keywords": [
6
6
  "Ethereum",