@metamask-previews/passkey-controller 4.0.0-preview-2b92c5a1c → 4.0.0-preview-429f77222

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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Changed
11
+
12
+ - Use `@metamask/utils` SHA-256, SHA-384, and SHA-512 digests for asynchronous WebAuthn verification and RP ID matching, while retaining Noble SHA-256 for synchronous HKDF ([#10188](https://github.com/MetaMask/core/pull/10188))
13
+
10
14
  ## [4.0.0]
11
15
 
12
16
  ### Changed
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,UAAU,GACf,UAAU,CAEZ;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,UAAU,GACd;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CASpC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,UAAU,GACd,MAAM,CAMR"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAYA;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,UAAU,EACf,IAAI,EAAE,UAAU,GACf,UAAU,CAIZ;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,UAAU,GACd;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CASpC;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,UAAU,GACd,MAAM,CAMR"}
@@ -2,7 +2,7 @@ import { bytesToBase64, base64ToBytes } from '@metamask/utils';
2
2
  import { gcm } from '@noble/ciphers/aes';
3
3
  import { randomBytes } from '@noble/ciphers/webcrypto';
4
4
  import { hkdf } from '@noble/hashes/hkdf';
5
- import { sha256 } from '@noble/hashes/sha2';
5
+ import { sha256 as nobleSha256 } from '@noble/hashes/sha2';
6
6
  import { bytesToBase64URL } from './encoding.js';
7
7
  const PASSKEY_HKDF_INFO = 'metamask:passkey:encryption-key:v1';
8
8
  const AES_GCM_IV_LENGTH = 12;
@@ -24,7 +24,9 @@ export function randomBytesToBase64URL(byteLength) {
24
24
  * @returns 32-byte derived encryption key.
25
25
  */
26
26
  export function deriveEncryptionKey(ikm, salt) {
27
- return hkdf(sha256, ikm, salt, PASSKEY_HKDF_INFO, 32);
27
+ // @noble/hashes/hkdf accepts only a synchronous hash callback, so this
28
+ // synchronous HKDF path intentionally retains Noble SHA-256.
29
+ return hkdf(nobleSha256, ikm, salt, PASSKEY_HKDF_INFO, 32);
28
30
  }
29
31
  /**
30
32
  * Encrypts plaintext with an AES-256-GCM key.
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,iBAAiB,GAAG,oCAAoC,CAAC;AAE/D,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACvD,OAAO,gBAAgB,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAe,EACf,IAAgB;IAEhB,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,SAAiB,EACjB,GAAe;IAEf,MAAM,EAAE,GAAG,WAAW,CAAC,iBAAiB,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,MAAM,eAAe,GAAG,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtD,OAAO;QACL,UAAU,EAAE,aAAa,CAAC,eAAe,CAAC;QAC1C,EAAE,EAAE,aAAa,CAAC,EAAE,CAAC;KACtB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAkB,EAClB,EAAU,EACV,GAAe;IAEf,MAAM,eAAe,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAE7D,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAC7C,CAAC","sourcesContent":["import { bytesToBase64, base64ToBytes } from '@metamask/utils';\nimport { gcm } from '@noble/ciphers/aes';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 } from '@noble/hashes/sha2';\n\nimport { bytesToBase64URL } from './encoding.js';\n\nconst PASSKEY_HKDF_INFO = 'metamask:passkey:encryption-key:v1';\n\nconst AES_GCM_IV_LENGTH = 12;\n\n/**\n * Generates random bytes and returns them as a base64url string (no padding).\n *\n * @param byteLength - Number of bytes to generate (e.g. WebAuthn challenge length).\n * @returns Base64url-encoded random bytes.\n */\nexport function randomBytesToBase64URL(byteLength: number): string {\n return bytesToBase64URL(randomBytes(byteLength));\n}\n\n/**\n * Derives an AES-256 encryption key from input key material and a credential ID\n * using HKDF-SHA256.\n *\n * @param ikm - Input key material (e.g. PRF output or userHandle).\n * @param salt - HKDF salt.\n * @returns 32-byte derived encryption key.\n */\nexport function deriveEncryptionKey(\n ikm: Uint8Array,\n salt: Uint8Array,\n): Uint8Array {\n return hkdf(sha256, ikm, salt, PASSKEY_HKDF_INFO, 32);\n}\n\n/**\n * Encrypts plaintext with an AES-256-GCM key.\n *\n * @param plaintext - UTF-8 string to encrypt.\n * @param key - 32-byte AES-256 key from {@link deriveEncryptionKey}.\n * @returns Base64-encoded ciphertext and IV.\n */\nexport function encryptWithKey(\n plaintext: string,\n key: Uint8Array,\n): { ciphertext: string; iv: string } {\n const iv = randomBytes(AES_GCM_IV_LENGTH);\n const encoded = new TextEncoder().encode(plaintext);\n const ciphertextBytes = gcm(key, iv).encrypt(encoded);\n\n return {\n ciphertext: bytesToBase64(ciphertextBytes),\n iv: bytesToBase64(iv),\n };\n}\n\n/**\n * Decrypts AES-256-GCM ciphertext with the given key.\n *\n * @param ciphertext - Base64-encoded ciphertext.\n * @param iv - Base64-encoded initialization vector.\n * @param key - 32-byte AES-256 key from {@link deriveEncryptionKey}.\n * @returns Decrypted UTF-8 string.\n */\nexport function decryptWithKey(\n ciphertext: string,\n iv: string,\n key: Uint8Array,\n): string {\n const ciphertextBytes = base64ToBytes(ciphertext);\n const ivBytes = base64ToBytes(iv);\n const plaintext = gcm(key, ivBytes).decrypt(ciphertextBytes);\n\n return new TextDecoder().decode(plaintext);\n}\n"]}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/utils/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,iBAAiB,GAAG,oCAAoC,CAAC;AAE/D,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,UAAkB;IACvD,OAAO,gBAAgB,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAe,EACf,IAAgB;IAEhB,uEAAuE;IACvE,6DAA6D;IAC7D,OAAO,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,SAAiB,EACjB,GAAe;IAEf,MAAM,EAAE,GAAG,WAAW,CAAC,iBAAiB,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,MAAM,eAAe,GAAG,GAAG,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAEtD,OAAO;QACL,UAAU,EAAE,aAAa,CAAC,eAAe,CAAC;QAC1C,EAAE,EAAE,aAAa,CAAC,EAAE,CAAC;KACtB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAkB,EAClB,EAAU,EACV,GAAe;IAEf,MAAM,eAAe,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAE7D,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAC7C,CAAC","sourcesContent":["import { bytesToBase64, base64ToBytes } from '@metamask/utils';\nimport { gcm } from '@noble/ciphers/aes';\nimport { randomBytes } from '@noble/ciphers/webcrypto';\nimport { hkdf } from '@noble/hashes/hkdf';\nimport { sha256 as nobleSha256 } from '@noble/hashes/sha2';\n\nimport { bytesToBase64URL } from './encoding.js';\n\nconst PASSKEY_HKDF_INFO = 'metamask:passkey:encryption-key:v1';\n\nconst AES_GCM_IV_LENGTH = 12;\n\n/**\n * Generates random bytes and returns them as a base64url string (no padding).\n *\n * @param byteLength - Number of bytes to generate (e.g. WebAuthn challenge length).\n * @returns Base64url-encoded random bytes.\n */\nexport function randomBytesToBase64URL(byteLength: number): string {\n return bytesToBase64URL(randomBytes(byteLength));\n}\n\n/**\n * Derives an AES-256 encryption key from input key material and a credential ID\n * using HKDF-SHA256.\n *\n * @param ikm - Input key material (e.g. PRF output or userHandle).\n * @param salt - HKDF salt.\n * @returns 32-byte derived encryption key.\n */\nexport function deriveEncryptionKey(\n ikm: Uint8Array,\n salt: Uint8Array,\n): Uint8Array {\n // @noble/hashes/hkdf accepts only a synchronous hash callback, so this\n // synchronous HKDF path intentionally retains Noble SHA-256.\n return hkdf(nobleSha256, ikm, salt, PASSKEY_HKDF_INFO, 32);\n}\n\n/**\n * Encrypts plaintext with an AES-256-GCM key.\n *\n * @param plaintext - UTF-8 string to encrypt.\n * @param key - 32-byte AES-256 key from {@link deriveEncryptionKey}.\n * @returns Base64-encoded ciphertext and IV.\n */\nexport function encryptWithKey(\n plaintext: string,\n key: Uint8Array,\n): { ciphertext: string; iv: string } {\n const iv = randomBytes(AES_GCM_IV_LENGTH);\n const encoded = new TextEncoder().encode(plaintext);\n const ciphertextBytes = gcm(key, iv).encrypt(encoded);\n\n return {\n ciphertext: bytesToBase64(ciphertextBytes),\n iv: bytesToBase64(iv),\n };\n}\n\n/**\n * Decrypts AES-256-GCM ciphertext with the given key.\n *\n * @param ciphertext - Base64-encoded ciphertext.\n * @param iv - Base64-encoded initialization vector.\n * @param key - 32-byte AES-256 key from {@link deriveEncryptionKey}.\n * @returns Decrypted UTF-8 string.\n */\nexport function decryptWithKey(\n ciphertext: string,\n iv: string,\n key: Uint8Array,\n): string {\n const ciphertextBytes = base64ToBytes(ciphertext);\n const ivBytes = base64ToBytes(iv);\n const plaintext = gcm(key, ivBytes).decrypt(ciphertextBytes);\n\n return new TextDecoder().decode(plaintext);\n}\n"]}
@@ -4,8 +4,8 @@
4
4
  *
5
5
  * @param rpIdHash - The rpIdHash from authenticatorData (32 bytes).
6
6
  * @param expectedRPIDs - One or more RP ID strings to check against.
7
- * @returns The matching RP ID string.
7
+ * @returns A promise for the matching RP ID string.
8
8
  * @throws If no expected RP ID matches.
9
9
  */
10
- export declare function matchExpectedRPID(rpIdHash: Uint8Array, expectedRPIDs: string[]): string;
10
+ export declare function matchExpectedRPID(rpIdHash: Uint8Array, expectedRPIDs: string[]): Promise<string>;
11
11
  //# sourceMappingURL=match-expected-rp-id.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"match-expected-rp-id.d.ts","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
+ {"version":3,"file":"match-expected-rp-id.d.ts","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,UAAU,EACpB,aAAa,EAAE,MAAM,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CAQjB"}
@@ -1,5 +1,4 @@
1
- import { areUint8ArraysEqual } from '@metamask/utils';
2
- import { sha256 } from '@noble/hashes/sha2';
1
+ import { areUint8ArraysEqual, sha256 } from '@metamask/utils';
3
2
  import { bytesToHex } from '../utils/encoding.js';
4
3
  /**
5
4
  * Verify that an authenticator data rpIdHash matches one of the expected
@@ -7,12 +6,12 @@ import { bytesToHex } from '../utils/encoding.js';
7
6
  *
8
7
  * @param rpIdHash - The rpIdHash from authenticatorData (32 bytes).
9
8
  * @param expectedRPIDs - One or more RP ID strings to check against.
10
- * @returns The matching RP ID string.
9
+ * @returns A promise for the matching RP ID string.
11
10
  * @throws If no expected RP ID matches.
12
11
  */
13
- export function matchExpectedRPID(rpIdHash, expectedRPIDs) {
12
+ export async function matchExpectedRPID(rpIdHash, expectedRPIDs) {
14
13
  for (const rpID of expectedRPIDs) {
15
- const expectedHash = sha256(new TextEncoder().encode(rpID));
14
+ const expectedHash = await sha256(new TextEncoder().encode(rpID));
16
15
  if (areUint8ArraysEqual(rpIdHash, expectedHash)) {
17
16
  return rpID;
18
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"match-expected-rp-id.js","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD;;;;;;;;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.js';\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
+ {"version":3,"file":"match-expected-rp-id.js","sourceRoot":"","sources":["../../src/webauthn/match-expected-rp-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAoB,EACpB,aAAuB;IAEvB,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAClE,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, sha256 } from '@metamask/utils';\n\nimport { bytesToHex } from '../utils/encoding.js';\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 A promise for the matching RP ID string.\n * @throws If no expected RP ID matches.\n */\nexport async function matchExpectedRPID(\n rpIdHash: Uint8Array,\n expectedRPIDs: string[],\n): Promise<string> {\n for (const rpID of expectedRPIDs) {\n const expectedHash = await 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":"verify-authentication-response.d.ts","sourceRoot":"","sources":["../../src/webauthn/verify-authentication-response.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAMhE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAGhE,MAAM,MAAM,8BAA8B,GACtC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,kBAAkB,CAAC,EAAE,KAAK,CAAA;CAAE,GAC/C;IACE,QAAQ,EAAE,IAAI,CAAC;IACf,kBAAkB,EAAE;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,OAAO,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,uFAAuF;QACvF,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,4BAA4B,CAAC,IAAI,EAAE;IACvD,QAAQ,EAAE,6BAA6B,CAAC;IACxC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAClC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE;QACV,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,EAAE,UAAU,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,4BAA4B,EAAE,CAAC;KAC7C,CAAC;IACF,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,GAAG,OAAO,CAAC,8BAA8B,CAAC,CA+I1C"}
1
+ {"version":3,"file":"verify-authentication-response.d.ts","sourceRoot":"","sources":["../../src/webauthn/verify-authentication-response.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAMhE,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAGhE,MAAM,MAAM,8BAA8B,GACtC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,kBAAkB,CAAC,EAAE,KAAK,CAAA;CAAE,GAC/C;IACE,QAAQ,EAAE,IAAI,CAAC;IACf,kBAAkB,EAAE;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,OAAO,CAAC;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,uFAAuF;QACvF,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,4BAA4B,CAAC,IAAI,EAAE;IACvD,QAAQ,EAAE,6BAA6B,CAAC;IACxC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAClC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE;QACV,EAAE,EAAE,MAAM,CAAC;QACX,SAAS,EAAE,UAAU,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,4BAA4B,EAAE,CAAC;KAC7C,CAAC;IACF,uBAAuB,CAAC,EAAE,OAAO,CAAC;CACnC,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAiJ1C"}
@@ -1,6 +1,5 @@
1
1
  import { decodePartialCBOR } from '@levischuck/tiny-cbor';
2
- import { concatBytes } from '@metamask/utils';
3
- import { sha256 } from '@noble/hashes/sha2';
2
+ import { concatBytes, sha256 } from '@metamask/utils';
4
3
  import { base64URLToBytes } from '../utils/encoding.js';
5
4
  import { decodeClientDataJSON } from './decode-client-data-json.js';
6
5
  import { matchExpectedRPID } from './match-expected-rp-id.js';
@@ -90,7 +89,10 @@ export async function verifyAuthenticationResponse(opts) {
90
89
  const authDataBuffer = base64URLToBytes(assertionResponse.authenticatorData);
91
90
  const parsedAuthData = parseAuthenticatorData(authDataBuffer);
92
91
  const { rpIdHash, flags, counter } = parsedAuthData;
93
- const matchedRPID = expectedRPIDs.length > 0 ? matchExpectedRPID(rpIdHash, expectedRPIDs) : '';
92
+ let matchedRPID = '';
93
+ if (expectedRPIDs.length > 0) {
94
+ matchedRPID = await matchExpectedRPID(rpIdHash, expectedRPIDs);
95
+ }
94
96
  // WebAuthn only requires the user presence flag be true
95
97
  if (!flags.up) {
96
98
  throw new Error('User not present during authentication');
@@ -99,7 +101,7 @@ export async function verifyAuthenticationResponse(opts) {
99
101
  if (requireUserVerification && !flags.uv) {
100
102
  throw new Error('User verification required, but user could not be verified');
101
103
  }
102
- const clientDataHash = sha256(base64URLToBytes(assertionResponse.clientDataJSON));
104
+ const clientDataHash = await sha256(base64URLToBytes(assertionResponse.clientDataJSON));
103
105
  const signatureBase = concatBytes([authDataBuffer, clientDataHash]);
104
106
  const signature = base64URLToBytes(assertionResponse.signature);
105
107
  const cosePublicKey = decodePartialCBOR(new Uint8Array(credential.publicKey), 0)[0];
@@ -1 +1 @@
1
- {"version":3,"file":"verify-authentication-response.js","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.js';\nimport { base64URLToBytes } from '../utils/encoding.js';\nimport { decodeClientDataJSON } from './decode-client-data-json.js';\nimport { matchExpectedRPID } from './match-expected-rp-id.js';\nimport { parseAuthenticatorData } from './parse-authenticator-data.js';\nimport type { ParsedAuthenticatorData } from './types.js';\nimport type { PasskeyAuthenticationResponse } from './types.js';\nimport { verifySignature } from './verify-signature.js';\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 /** Matched RP ID, or `\"\"` when `expectedRPIDs` is empty (RP ID hash check skipped). */\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.expectedRPIDs - Relying Party ID strings to match against `rpIdHash`.\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 expectedRPIDs: 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 expectedRPIDs,\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 const matchedRPID =\n expectedRPIDs.length > 0 ? matchExpectedRPID(rpIdHash, expectedRPIDs) : '';\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"]}
1
+ {"version":3,"file":"verify-authentication-response.js","sourceRoot":"","sources":["../../src/webauthn/verify-authentication-response.ts"],"names":[],"mappings":"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","sourcesContent":["import { decodePartialCBOR } from '@levischuck/tiny-cbor';\nimport { concatBytes, sha256 } from '@metamask/utils';\n\nimport type { AuthenticatorTransportFuture } from '../types.js';\nimport { base64URLToBytes } from '../utils/encoding.js';\nimport { decodeClientDataJSON } from './decode-client-data-json.js';\nimport { matchExpectedRPID } from './match-expected-rp-id.js';\nimport { parseAuthenticatorData } from './parse-authenticator-data.js';\nimport type { ParsedAuthenticatorData } from './types.js';\nimport type { PasskeyAuthenticationResponse } from './types.js';\nimport { verifySignature } from './verify-signature.js';\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 /** Matched RP ID, or `\"\"` when `expectedRPIDs` is empty (RP ID hash check skipped). */\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.expectedRPIDs - Relying Party ID strings to match against `rpIdHash`.\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 expectedRPIDs: 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 expectedRPIDs,\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 let matchedRPID = '';\n if (expectedRPIDs.length > 0) {\n matchedRPID = await matchExpectedRPID(rpIdHash, expectedRPIDs);\n }\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 = await 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"]}
@@ -1 +1 @@
1
- {"version":3,"file":"verify-registration-response.d.ts","sourceRoot":"","sources":["../../src/webauthn/verify-registration-response.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAWhE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAG9D,MAAM,MAAM,4BAA4B,GACpC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,gBAAgB,CAAC,EAAE,KAAK,CAAA;CAAE,GAC7C;IACE,QAAQ,EAAE,IAAI,CAAC;IACf,gBAAgB,EAAE;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,UAAU,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,4BAA4B,EAAE,CAAC;QAC5C,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;QAC1B,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;CACH,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,QAAQ,EAAE,2BAA2B,CAAC;IACtC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAClC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CAClC,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAkLxC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iCAAiC,CAC/C,oBAAoB,EAAE,2BAA2B,GAChD,MAAM,GAAG,SAAS,CAMpB"}
1
+ {"version":3,"file":"verify-registration-response.d.ts","sourceRoot":"","sources":["../../src/webauthn/verify-registration-response.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAWhE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,YAAY,CAAC;AAG9D,MAAM,MAAM,4BAA4B,GACpC;IAAE,QAAQ,EAAE,KAAK,CAAC;IAAC,gBAAgB,CAAC,EAAE,KAAK,CAAA;CAAE,GAC7C;IACE,QAAQ,EAAE,IAAI,CAAC;IACf,gBAAgB,EAAE;QAChB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,UAAU,CAAC;QACtB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,4BAA4B,EAAE,CAAC;QAC5C,MAAM,EAAE,MAAM,CAAC;QACf,iBAAiB,EAAE,MAAM,CAAC;QAC1B,YAAY,EAAE,OAAO,CAAC;KACvB,CAAC;CACH,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE;IACrD,QAAQ,EAAE,2BAA2B,CAAC;IACtC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAClC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CAClC,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAkLxC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iCAAiC,CAC/C,oBAAoB,EAAE,2BAA2B,GAChD,MAAM,GAAG,SAAS,CAMpB"}
@@ -1,6 +1,5 @@
1
1
  import { decodePartialCBOR } from '@levischuck/tiny-cbor';
2
- import { concatBytes } from '@metamask/utils';
3
- import { sha256 } from '@noble/hashes/sha2';
2
+ import { concatBytes, sha256 } from '@metamask/utils';
4
3
  import { base64URLToBytes, bytesToBase64URL, bytesToHex, } from '../utils/encoding.js';
5
4
  import { COSEALG, COSEKEYS } from './constants.js';
6
5
  import { decodeAttestationObject } from './decode-attestation-object.js';
@@ -90,7 +89,7 @@ export async function verifyRegistrationResponse(opts) {
90
89
  const parsedAuthData = parseAuthenticatorData(authData);
91
90
  const { rpIdHash, flags, counter, credentialID, credentialPublicKey, aaguid, } = parsedAuthData;
92
91
  if (expectedRPIDs.length > 0) {
93
- matchExpectedRPID(rpIdHash, expectedRPIDs);
92
+ await matchExpectedRPID(rpIdHash, expectedRPIDs);
94
93
  }
95
94
  // Make sure someone was physically present
96
95
  if (!flags.up) {
@@ -226,7 +225,7 @@ async function verifyPackedAttestation(attStmt, authData, clientDataJSONB64url,
226
225
  if (attStmtAlg !== credAlg) {
227
226
  throw new Error(`Packed attestation alg ${attStmtAlg} does not match credential alg ${credAlg}`);
228
227
  }
229
- const clientDataHash = sha256(base64URLToBytes(clientDataJSONB64url));
228
+ const clientDataHash = await sha256(base64URLToBytes(clientDataJSONB64url));
230
229
  const signatureBase = concatBytes([authData, clientDataHash]);
231
230
  return verifySignature({
232
231
  cosePublicKey,
@@ -1 +1 @@
1
- {"version":3,"file":"verify-registration-response.js","sourceRoot":"","sources":["../../src/webauthn/verify-registration-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.js';\nimport {\n base64URLToBytes,\n bytesToBase64URL,\n bytesToHex,\n} from '../utils/encoding.js';\nimport { COSEALG, COSEKEYS } from './constants.js';\nimport { decodeAttestationObject } from './decode-attestation-object.js';\nimport { decodeClientDataJSON } from './decode-client-data-json.js';\nimport { matchExpectedRPID } from './match-expected-rp-id.js';\nimport { parseAuthenticatorData } from './parse-authenticator-data.js';\nimport type { PasskeyRegistrationResponse } from './types.js';\nimport { verifySignature } from './verify-signature.js';\n\nexport type VerifiedRegistrationResponse =\n | { verified: false; registrationInfo?: never }\n | {\n verified: true;\n registrationInfo: {\n credentialId: string;\n publicKey: Uint8Array;\n counter: number;\n transports?: AuthenticatorTransportFuture[];\n aaguid: string;\n attestationFormat: string;\n userVerified: boolean;\n };\n };\n\n/**\n * Verifies a WebAuthn registration (attestation) response per\n * W3C WebAuthn Level 3 §7.1.\n *\n * Performs the following checks in order:\n * 1. Credential ID presence and base64url consistency (`id === rawId`), and\n * that `id` matches the credential id inside parsed authenticator data.\n * 2. Credential type is `\"public-key\"`.\n * 3. `clientDataJSON` -- type is `\"webauthn.create\"`, challenge and origin\n * match the expected values.\n * 4. Attestation object -- CBOR-decodes and parses `authData` to verify\n * the RP ID hash, user-presence flag, optional user-verification flag,\n * and the attested credential public key algorithm.\n * 5. Attestation statement -- supports `\"none\"` (no signature) and\n * `\"packed\"` self-attestation (signature verified against the\n * credential's own public key).\n *\n * @param opts - Verification options.\n * @param opts.response - The `PublicKeyCredential` result from\n * `navigator.credentials.create()`, serialized as JSON.\n * @param opts.expectedChallenge - The base64url challenge that was passed\n * to the authenticator (must match `clientDataJSON.challenge`).\n * @param opts.expectedOrigin - One or more acceptable values for\n * `clientDataJSON.origin` (WebAuthn). Extension and HTTPS contexts differ by scheme.\n * @param opts.expectedRPIDs - Relying Party ID strings. The authenticator's\n * `rpIdHash` must equal `SHA-256(rpID)` for at least one entry.\n * @param opts.requireUserVerification - When `true`, verification fails\n * if the UV flag is not set. Defaults to `false`.\n * @param opts.supportedAlgorithmIDs - COSE algorithm identifiers accepted\n * for the credential public key. Defaults to EdDSA, ES256, and RS256.\n * @returns On success, `{ verified: true, registrationInfo }` with the\n * parsed credential ID, public key, counter, AAGUID, and transport\n * hints. On failure, `{ verified: false }`.\n */\nexport async function verifyRegistrationResponse(opts: {\n response: PasskeyRegistrationResponse;\n expectedChallenge: string;\n expectedOrigin: string | string[];\n expectedRPIDs: string[];\n requireUserVerification?: boolean;\n supportedAlgorithmIDs?: number[];\n}): Promise<VerifiedRegistrationResponse> {\n const {\n response,\n expectedChallenge,\n expectedOrigin,\n expectedRPIDs,\n requireUserVerification = false,\n supportedAlgorithmIDs = [COSEALG.EdDSA, COSEALG.ES256, COSEALG.RS256],\n } = opts;\n\n const {\n id,\n rawId,\n type: credentialType,\n response: attestationResponse,\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 const clientDataJSON = decodeClientDataJSON(\n attestationResponse.clientDataJSON,\n );\n const { type, challenge, origin, tokenBinding } = clientDataJSON;\n\n // Make sure we're handling an registration\n if (type !== 'webauthn.create') {\n throw new Error(`Unexpected registration 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 registration 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 registration response origin \"${origin}\", expected one of: ${expectedOrigins.join(', ')}`,\n );\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(\n `Unexpected tokenBinding.status value of \"${tokenBinding.status}\"`,\n );\n }\n }\n\n const attestationObjectBytes = base64URLToBytes(\n attestationResponse.attestationObject,\n );\n const decodedAttObj = decodeAttestationObject(attestationObjectBytes);\n const fmt = decodedAttObj.get('fmt');\n const authData = decodedAttObj.get('authData');\n const attStmt = decodedAttObj.get('attStmt');\n\n const parsedAuthData = parseAuthenticatorData(authData);\n const {\n rpIdHash,\n flags,\n counter,\n credentialID,\n credentialPublicKey,\n aaguid,\n } = parsedAuthData;\n\n if (expectedRPIDs.length > 0) {\n matchExpectedRPID(rpIdHash, expectedRPIDs);\n }\n\n // Make sure someone was physically present\n if (!flags.up) {\n throw new Error('User presence was required, but user was not present');\n }\n\n // Enforce user verification if specified\n if (requireUserVerification && !flags.uv) {\n throw new Error(\n 'User verification was required, but user could not be verified',\n );\n }\n\n if (!credentialID) {\n throw new Error('No credential ID was provided by authenticator');\n }\n\n const attestedCredentialId = bytesToBase64URL(credentialID);\n if (id !== attestedCredentialId) {\n throw new Error(\n 'Credential id does not match the credential id in authenticator data',\n );\n }\n\n if (!credentialPublicKey) {\n throw new Error('No public key was provided by authenticator');\n }\n if (!aaguid) {\n throw new Error('No AAGUID was present during registration');\n }\n\n const decodedPublicKey = decodePartialCBOR(\n new Uint8Array(credentialPublicKey),\n 0,\n )[0] as Map<number, number | Uint8Array>;\n const alg = decodedPublicKey.get(COSEKEYS.Alg);\n\n if (typeof alg !== 'number') {\n throw new Error('Credential public key was missing numeric alg');\n }\n\n // Make sure the key algorithm is one we specified within the registration options\n if (!supportedAlgorithmIDs.includes(alg)) {\n throw new Error(\n `Unexpected public key alg \"${alg}\", expected one of \"${supportedAlgorithmIDs.join(', ')}\"`,\n );\n }\n\n let verified = false;\n if (fmt === 'none') {\n if (attStmt.size > 0) {\n throw new Error('None attestation had unexpected attestation statement');\n }\n verified = true;\n } else if (fmt === 'packed') {\n verified = await verifyPackedAttestation(\n attStmt,\n authData,\n attestationResponse.clientDataJSON,\n decodedPublicKey,\n );\n } else {\n throw new Error(`Unsupported attestation format: ${fmt}`);\n }\n\n if (!verified) {\n return { verified: false };\n }\n\n return {\n verified: true,\n registrationInfo: {\n credentialId: attestedCredentialId,\n publicKey: credentialPublicKey,\n counter,\n transports:\n attestationResponse.transports as AuthenticatorTransportFuture[],\n aaguid: formatAAGUID(aaguid),\n attestationFormat: fmt,\n userVerified: flags.uv,\n },\n };\n}\n\n/**\n * Reads the authenticator AAGUID out of a registration response by decoding its\n * attestation object and parsing the attested credential data within\n * `authData`.\n *\n * The AAGUID identifies the authenticator model (e.g. iCloud Keychain, Google\n * Password Manager, a hardware key), so it is useful for telemetry or for\n * showing the user where their passkey lives.\n *\n * Unlike {@link verifyRegistrationResponse}, this reads the response as-is\n * without verifying it, so treat the value as untrusted until enrollment\n * completes; the verified AAGUID is persisted on\n * `passkeyRecord.credential.aaguid`. Note also that many authenticators\n * deliberately report an all-zero AAGUID.\n *\n * @param registrationResponse - Result of `navigator.credentials.create()`.\n * @returns The AAGUID as a dashed UUID string, or `undefined` if the\n * authenticator data carries no attested credential data.\n * @throws If the attestation object or its authenticator data is malformed.\n */\nexport function getAAGUIDFromRegistrationResponse(\n registrationResponse: PasskeyRegistrationResponse,\n): string | undefined {\n const attestationObject = decodeAttestationObject(\n base64URLToBytes(registrationResponse.response.attestationObject),\n );\n const { aaguid } = parseAuthenticatorData(attestationObject.get('authData'));\n return aaguid ? formatAAGUID(aaguid) : undefined;\n}\n\n/**\n * Format the raw 16-byte AAGUID from attested credential data as a dashed\n * UUID string (8-4-4-4-12).\n *\n * @param aaguid - Raw AAGUID bytes.\n * @returns The AAGUID in canonical UUID form.\n */\nfunction formatAAGUID(aaguid: Uint8Array): string {\n const aaguidHex = bytesToHex(aaguid);\n return [\n aaguidHex.slice(0, 8),\n aaguidHex.slice(8, 12),\n aaguidHex.slice(12, 16),\n aaguidHex.slice(16, 20),\n aaguidHex.slice(20),\n ].join('-');\n}\n\n/**\n * Verify packed self-attestation per WebAuthn §8.2: no x5c certificate\n * chain, signature over `authData || SHA-256(clientDataJSON)` verified\n * with the credential's own public key, and `alg` in the attestation\n * statement must match the credential key's algorithm.\n *\n * @param attStmt - The attestation statement map from the attestation\n * object.\n * @param attStmt.get - Accessor to retrieve statement fields by key.\n * @param attStmt.size - Number of entries in the statement.\n * @param authData - Raw authenticator data bytes.\n * @param clientDataJSONB64url - Base64url-encoded clientDataJSON.\n * @param cosePublicKey - Decoded COSE public key map from authenticator\n * data.\n * @returns Whether the packed attestation signature is valid.\n */\nasync function verifyPackedAttestation(\n attStmt: { get(key: string): unknown; size: number },\n authData: Uint8Array,\n clientDataJSONB64url: string,\n cosePublicKey: Map<number, number | Uint8Array>,\n): Promise<boolean> {\n const attStmtAlg = attStmt.get('alg') as number | undefined;\n const signature = attStmt.get('sig') as Uint8Array | undefined;\n const x5c = attStmt.get('x5c') as Uint8Array[] | undefined;\n\n if (typeof attStmtAlg !== 'number') {\n throw new Error('Packed attestation statement missing alg');\n }\n\n if (!signature) {\n throw new Error('Packed attestation missing signature');\n }\n\n if (x5c && x5c.length > 0) {\n throw new Error(\n 'Packed attestation with certificate chain (x5c) is not supported; only self-attestation is accepted',\n );\n }\n\n const credAlg = cosePublicKey.get(COSEKEYS.Alg) as number;\n if (attStmtAlg !== credAlg) {\n throw new Error(\n `Packed attestation alg ${attStmtAlg} does not match credential alg ${credAlg}`,\n );\n }\n\n const clientDataHash = sha256(base64URLToBytes(clientDataJSONB64url));\n const signatureBase = concatBytes([authData, clientDataHash]);\n\n return verifySignature({\n cosePublicKey,\n signature,\n data: signatureBase,\n });\n}\n"]}
1
+ {"version":3,"file":"verify-registration-response.js","sourceRoot":"","sources":["../../src/webauthn/verify-registration-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EACL,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,GACX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAiBxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,IAOhD;IACC,MAAM,EACJ,QAAQ,EACR,iBAAiB,EACjB,cAAc,EACd,aAAa,EACb,uBAAuB,GAAG,KAAK,EAC/B,qBAAqB,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,GACtE,GAAG,IAAI,CAAC;IAET,MAAM,EACJ,EAAE,EACF,KAAK,EACL,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,mBAAmB,GAC9B,GAAG,QAAQ,CAAC;IAEb,oCAAoC;IACpC,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC3C,CAAC;IAED,iCAAiC;IACjC,IAAI,EAAE,KAAK,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IAED,0CAA0C;IAC1C,IAAI,cAAc,KAAK,YAAY,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,8BAA8B,MAAM,CAAC,cAAc,CAAC,yBAAyB,CAC9E,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,oBAAoB,CACzC,mBAAmB,CAAC,cAAc,CACnC,CAAC;IACF,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,cAAc,CAAC;IAEjE,2CAA2C;IAC3C,IAAI,IAAI,KAAK,iBAAiB,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,0CAA0C,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,sDAAsD;IACtD,IAAI,SAAS,KAAK,iBAAiB,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,+CAA+C,SAAS,gBAAgB,iBAAiB,GAAG,CAC7F,CAAC;IACJ,CAAC;IAED,oCAAoC;IACpC,MAAM,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC;QACnD,CAAC,CAAC,cAAc;QAChB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IACrB,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,4CAA4C,MAAM,uBAAuB,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACtG,CAAC;IACJ,CAAC;IAED,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QAED,IACE,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,eAAe,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,MAAM,CAAC,EACxE,CAAC;YACD,MAAM,IAAI,KAAK,CACb,4CAA4C,YAAY,CAAC,MAAM,GAAG,CACnE,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,sBAAsB,GAAG,gBAAgB,CAC7C,mBAAmB,CAAC,iBAAiB,CACtC,CAAC;IACF,MAAM,aAAa,GAAG,uBAAuB,CAAC,sBAAsB,CAAC,CAAC;IACtE,MAAM,GAAG,GAAG,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAE7C,MAAM,cAAc,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAC;IACxD,MAAM,EACJ,QAAQ,EACR,KAAK,EACL,OAAO,EACP,YAAY,EACZ,mBAAmB,EACnB,MAAM,GACP,GAAG,cAAc,CAAC;IAEnB,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,iBAAiB,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;IACnD,CAAC;IAED,2CAA2C;IAC3C,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IAED,yCAAyC;IACzC,IAAI,uBAAuB,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,gEAAgE,CACjE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,oBAAoB,GAAG,gBAAgB,CAAC,YAAY,CAAC,CAAC;IAC5D,IAAI,EAAE,KAAK,oBAAoB,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,mBAAmB,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,gBAAgB,GAAG,iBAAiB,CACxC,IAAI,UAAU,CAAC,mBAAmB,CAAC,EACnC,CAAC,CACF,CAAC,CAAC,CAAqC,CAAC;IACzC,MAAM,GAAG,GAAG,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAE/C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,kFAAkF;IAClF,IAAI,CAAC,qBAAqB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CACb,8BAA8B,GAAG,uBAAuB,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC5F,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QACnB,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;QAC3E,CAAC;QACD,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;SAAM,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,QAAQ,GAAG,MAAM,uBAAuB,CACtC,OAAO,EACP,QAAQ,EACR,mBAAmB,CAAC,cAAc,EAClC,gBAAgB,CACjB,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,mCAAmC,GAAG,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,IAAI;QACd,gBAAgB,EAAE;YAChB,YAAY,EAAE,oBAAoB;YAClC,SAAS,EAAE,mBAAmB;YAC9B,OAAO;YACP,UAAU,EACR,mBAAmB,CAAC,UAA4C;YAClE,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC;YAC5B,iBAAiB,EAAE,GAAG;YACtB,YAAY,EAAE,KAAK,CAAC,EAAE;SACvB;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,iCAAiC,CAC/C,oBAAiD;IAEjD,MAAM,iBAAiB,GAAG,uBAAuB,CAC/C,gBAAgB,CAAC,oBAAoB,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAClE,CAAC;IACF,MAAM,EAAE,MAAM,EAAE,GAAG,sBAAsB,CAAC,iBAAiB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC;IAC7E,OAAO,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACnD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,MAAkB;IACtC,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IACrC,OAAO;QACL,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QACrB,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QACtB,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;QACvB,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC;QACvB,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;KACpB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,KAAK,UAAU,uBAAuB,CACpC,OAAoD,EACpD,QAAoB,EACpB,oBAA4B,EAC5B,aAA+C;IAE/C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAuB,CAAC;IAC5D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAA2B,CAAC;IAC/D,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAA6B,CAAC;IAE3D,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,qGAAqG,CACtG,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAW,CAAC;IAC1D,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,0BAA0B,UAAU,kCAAkC,OAAO,EAAE,CAChF,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAC5E,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC;IAE9D,OAAO,eAAe,CAAC;QACrB,aAAa;QACb,SAAS;QACT,IAAI,EAAE,aAAa;KACpB,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { decodePartialCBOR } from '@levischuck/tiny-cbor';\nimport { concatBytes, sha256 } from '@metamask/utils';\n\nimport type { AuthenticatorTransportFuture } from '../types.js';\nimport {\n base64URLToBytes,\n bytesToBase64URL,\n bytesToHex,\n} from '../utils/encoding.js';\nimport { COSEALG, COSEKEYS } from './constants.js';\nimport { decodeAttestationObject } from './decode-attestation-object.js';\nimport { decodeClientDataJSON } from './decode-client-data-json.js';\nimport { matchExpectedRPID } from './match-expected-rp-id.js';\nimport { parseAuthenticatorData } from './parse-authenticator-data.js';\nimport type { PasskeyRegistrationResponse } from './types.js';\nimport { verifySignature } from './verify-signature.js';\n\nexport type VerifiedRegistrationResponse =\n | { verified: false; registrationInfo?: never }\n | {\n verified: true;\n registrationInfo: {\n credentialId: string;\n publicKey: Uint8Array;\n counter: number;\n transports?: AuthenticatorTransportFuture[];\n aaguid: string;\n attestationFormat: string;\n userVerified: boolean;\n };\n };\n\n/**\n * Verifies a WebAuthn registration (attestation) response per\n * W3C WebAuthn Level 3 §7.1.\n *\n * Performs the following checks in order:\n * 1. Credential ID presence and base64url consistency (`id === rawId`), and\n * that `id` matches the credential id inside parsed authenticator data.\n * 2. Credential type is `\"public-key\"`.\n * 3. `clientDataJSON` -- type is `\"webauthn.create\"`, challenge and origin\n * match the expected values.\n * 4. Attestation object -- CBOR-decodes and parses `authData` to verify\n * the RP ID hash, user-presence flag, optional user-verification flag,\n * and the attested credential public key algorithm.\n * 5. Attestation statement -- supports `\"none\"` (no signature) and\n * `\"packed\"` self-attestation (signature verified against the\n * credential's own public key).\n *\n * @param opts - Verification options.\n * @param opts.response - The `PublicKeyCredential` result from\n * `navigator.credentials.create()`, serialized as JSON.\n * @param opts.expectedChallenge - The base64url challenge that was passed\n * to the authenticator (must match `clientDataJSON.challenge`).\n * @param opts.expectedOrigin - One or more acceptable values for\n * `clientDataJSON.origin` (WebAuthn). Extension and HTTPS contexts differ by scheme.\n * @param opts.expectedRPIDs - Relying Party ID strings. The authenticator's\n * `rpIdHash` must equal `SHA-256(rpID)` for at least one entry.\n * @param opts.requireUserVerification - When `true`, verification fails\n * if the UV flag is not set. Defaults to `false`.\n * @param opts.supportedAlgorithmIDs - COSE algorithm identifiers accepted\n * for the credential public key. Defaults to EdDSA, ES256, and RS256.\n * @returns On success, `{ verified: true, registrationInfo }` with the\n * parsed credential ID, public key, counter, AAGUID, and transport\n * hints. On failure, `{ verified: false }`.\n */\nexport async function verifyRegistrationResponse(opts: {\n response: PasskeyRegistrationResponse;\n expectedChallenge: string;\n expectedOrigin: string | string[];\n expectedRPIDs: string[];\n requireUserVerification?: boolean;\n supportedAlgorithmIDs?: number[];\n}): Promise<VerifiedRegistrationResponse> {\n const {\n response,\n expectedChallenge,\n expectedOrigin,\n expectedRPIDs,\n requireUserVerification = false,\n supportedAlgorithmIDs = [COSEALG.EdDSA, COSEALG.ES256, COSEALG.RS256],\n } = opts;\n\n const {\n id,\n rawId,\n type: credentialType,\n response: attestationResponse,\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 const clientDataJSON = decodeClientDataJSON(\n attestationResponse.clientDataJSON,\n );\n const { type, challenge, origin, tokenBinding } = clientDataJSON;\n\n // Make sure we're handling an registration\n if (type !== 'webauthn.create') {\n throw new Error(`Unexpected registration 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 registration 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 registration response origin \"${origin}\", expected one of: ${expectedOrigins.join(', ')}`,\n );\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(\n `Unexpected tokenBinding.status value of \"${tokenBinding.status}\"`,\n );\n }\n }\n\n const attestationObjectBytes = base64URLToBytes(\n attestationResponse.attestationObject,\n );\n const decodedAttObj = decodeAttestationObject(attestationObjectBytes);\n const fmt = decodedAttObj.get('fmt');\n const authData = decodedAttObj.get('authData');\n const attStmt = decodedAttObj.get('attStmt');\n\n const parsedAuthData = parseAuthenticatorData(authData);\n const {\n rpIdHash,\n flags,\n counter,\n credentialID,\n credentialPublicKey,\n aaguid,\n } = parsedAuthData;\n\n if (expectedRPIDs.length > 0) {\n await matchExpectedRPID(rpIdHash, expectedRPIDs);\n }\n\n // Make sure someone was physically present\n if (!flags.up) {\n throw new Error('User presence was required, but user was not present');\n }\n\n // Enforce user verification if specified\n if (requireUserVerification && !flags.uv) {\n throw new Error(\n 'User verification was required, but user could not be verified',\n );\n }\n\n if (!credentialID) {\n throw new Error('No credential ID was provided by authenticator');\n }\n\n const attestedCredentialId = bytesToBase64URL(credentialID);\n if (id !== attestedCredentialId) {\n throw new Error(\n 'Credential id does not match the credential id in authenticator data',\n );\n }\n\n if (!credentialPublicKey) {\n throw new Error('No public key was provided by authenticator');\n }\n if (!aaguid) {\n throw new Error('No AAGUID was present during registration');\n }\n\n const decodedPublicKey = decodePartialCBOR(\n new Uint8Array(credentialPublicKey),\n 0,\n )[0] as Map<number, number | Uint8Array>;\n const alg = decodedPublicKey.get(COSEKEYS.Alg);\n\n if (typeof alg !== 'number') {\n throw new Error('Credential public key was missing numeric alg');\n }\n\n // Make sure the key algorithm is one we specified within the registration options\n if (!supportedAlgorithmIDs.includes(alg)) {\n throw new Error(\n `Unexpected public key alg \"${alg}\", expected one of \"${supportedAlgorithmIDs.join(', ')}\"`,\n );\n }\n\n let verified = false;\n if (fmt === 'none') {\n if (attStmt.size > 0) {\n throw new Error('None attestation had unexpected attestation statement');\n }\n verified = true;\n } else if (fmt === 'packed') {\n verified = await verifyPackedAttestation(\n attStmt,\n authData,\n attestationResponse.clientDataJSON,\n decodedPublicKey,\n );\n } else {\n throw new Error(`Unsupported attestation format: ${fmt}`);\n }\n\n if (!verified) {\n return { verified: false };\n }\n\n return {\n verified: true,\n registrationInfo: {\n credentialId: attestedCredentialId,\n publicKey: credentialPublicKey,\n counter,\n transports:\n attestationResponse.transports as AuthenticatorTransportFuture[],\n aaguid: formatAAGUID(aaguid),\n attestationFormat: fmt,\n userVerified: flags.uv,\n },\n };\n}\n\n/**\n * Reads the authenticator AAGUID out of a registration response by decoding its\n * attestation object and parsing the attested credential data within\n * `authData`.\n *\n * The AAGUID identifies the authenticator model (e.g. iCloud Keychain, Google\n * Password Manager, a hardware key), so it is useful for telemetry or for\n * showing the user where their passkey lives.\n *\n * Unlike {@link verifyRegistrationResponse}, this reads the response as-is\n * without verifying it, so treat the value as untrusted until enrollment\n * completes; the verified AAGUID is persisted on\n * `passkeyRecord.credential.aaguid`. Note also that many authenticators\n * deliberately report an all-zero AAGUID.\n *\n * @param registrationResponse - Result of `navigator.credentials.create()`.\n * @returns The AAGUID as a dashed UUID string, or `undefined` if the\n * authenticator data carries no attested credential data.\n * @throws If the attestation object or its authenticator data is malformed.\n */\nexport function getAAGUIDFromRegistrationResponse(\n registrationResponse: PasskeyRegistrationResponse,\n): string | undefined {\n const attestationObject = decodeAttestationObject(\n base64URLToBytes(registrationResponse.response.attestationObject),\n );\n const { aaguid } = parseAuthenticatorData(attestationObject.get('authData'));\n return aaguid ? formatAAGUID(aaguid) : undefined;\n}\n\n/**\n * Format the raw 16-byte AAGUID from attested credential data as a dashed\n * UUID string (8-4-4-4-12).\n *\n * @param aaguid - Raw AAGUID bytes.\n * @returns The AAGUID in canonical UUID form.\n */\nfunction formatAAGUID(aaguid: Uint8Array): string {\n const aaguidHex = bytesToHex(aaguid);\n return [\n aaguidHex.slice(0, 8),\n aaguidHex.slice(8, 12),\n aaguidHex.slice(12, 16),\n aaguidHex.slice(16, 20),\n aaguidHex.slice(20),\n ].join('-');\n}\n\n/**\n * Verify packed self-attestation per WebAuthn §8.2: no x5c certificate\n * chain, signature over `authData || SHA-256(clientDataJSON)` verified\n * with the credential's own public key, and `alg` in the attestation\n * statement must match the credential key's algorithm.\n *\n * @param attStmt - The attestation statement map from the attestation\n * object.\n * @param attStmt.get - Accessor to retrieve statement fields by key.\n * @param attStmt.size - Number of entries in the statement.\n * @param authData - Raw authenticator data bytes.\n * @param clientDataJSONB64url - Base64url-encoded clientDataJSON.\n * @param cosePublicKey - Decoded COSE public key map from authenticator\n * data.\n * @returns Whether the packed attestation signature is valid.\n */\nasync function verifyPackedAttestation(\n attStmt: { get(key: string): unknown; size: number },\n authData: Uint8Array,\n clientDataJSONB64url: string,\n cosePublicKey: Map<number, number | Uint8Array>,\n): Promise<boolean> {\n const attStmtAlg = attStmt.get('alg') as number | undefined;\n const signature = attStmt.get('sig') as Uint8Array | undefined;\n const x5c = attStmt.get('x5c') as Uint8Array[] | undefined;\n\n if (typeof attStmtAlg !== 'number') {\n throw new Error('Packed attestation statement missing alg');\n }\n\n if (!signature) {\n throw new Error('Packed attestation missing signature');\n }\n\n if (x5c && x5c.length > 0) {\n throw new Error(\n 'Packed attestation with certificate chain (x5c) is not supported; only self-attestation is accepted',\n );\n }\n\n const credAlg = cosePublicKey.get(COSEKEYS.Alg) as number;\n if (attStmtAlg !== credAlg) {\n throw new Error(\n `Packed attestation alg ${attStmtAlg} does not match credential alg ${credAlg}`,\n );\n }\n\n const clientDataHash = await sha256(base64URLToBytes(clientDataJSONB64url));\n const signatureBase = concatBytes([authData, clientDataHash]);\n\n return verifySignature({\n cosePublicKey,\n signature,\n data: signatureBase,\n });\n}\n"]}
@@ -3,8 +3,9 @@ type COSEPublicKey = Map<number, number | Uint8Array>;
3
3
  * Verify a WebAuthn signature using the appropriate algorithm based on
4
4
  * the COSE key type.
5
5
  *
6
- * Uses @noble/curves for EC2 and OKP (synchronous, audited, handles DER
7
- * natively). Falls back to Web Crypto API for RSA.
6
+ * Uses the async `@metamask/utils` SHA digests with @noble/curves for EC2.
7
+ * Ed25519 verification also remains on @noble/curves, and RSA falls back to
8
+ * the Web Crypto API.
8
9
  *
9
10
  * @param opts - Options object.
10
11
  * @param opts.cosePublicKey - COSE-encoded public key as a Map.
@@ -1 +1 @@
1
- {"version":3,"file":"verify-signature.d.ts","sourceRoot":"","sources":["../../src/webauthn/verify-signature.ts"],"names":[],"mappings":"AAQA,KAAK,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC;AAsLtD;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,UAAU,CAAC;IACtB,IAAI,EAAE,UAAU,CAAC;CAClB,GAAG,OAAO,CAAC,OAAO,CAAC,CAcnB"}
1
+ {"version":3,"file":"verify-signature.d.ts","sourceRoot":"","sources":["../../src/webauthn/verify-signature.ts"],"names":[],"mappings":"AAOA,KAAK,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC;AAuLtD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,aAAa,EAAE,aAAa,CAAC;IAC7B,SAAS,EAAE,UAAU,CAAC;IACtB,IAAI,EAAE,UAAU,CAAC;CAClB,GAAG,OAAO,CAAC,OAAO,CAAC,CAcnB"}
@@ -1,7 +1,6 @@
1
- import { concatBytes } from '@metamask/utils';
1
+ import { concatBytes, sha256, sha384, sha512 } from '@metamask/utils';
2
2
  import { ed25519 } from '@noble/curves/ed25519';
3
3
  import { p256, p384, p521 } from '@noble/curves/nist';
4
- import { sha256, sha384, sha512 } from '@noble/hashes/sha2';
5
4
  import { bytesToBase64URL } from '../utils/encoding.js';
6
5
  import { COSEALG, COSECRV, COSEKEYS, COSEKTY } from './constants.js';
7
6
  /**
@@ -20,15 +19,16 @@ function getKeyType(cosePublicKey) {
20
19
  /**
21
20
  * Verify an EC2 (P-256, P-384, P-521) signature using @noble/curves.
22
21
  *
23
- * ECDSA requires the data to be hashed with the curve-appropriate
24
- * algorithm before verification: SHA-256 for P-256 and SHA-384 for P-384.
22
+ * ECDSA requires the data to be hashed with the curve-appropriate algorithm
23
+ * before verification. Hashing uses the async `@metamask/utils` digests while
24
+ * signature verification remains synchronous in `@noble/curves`.
25
25
  *
26
26
  * @param cosePublicKey - COSE-encoded EC2 public key.
27
27
  * @param signature - DER-encoded ECDSA signature.
28
28
  * @param data - Data that was signed.
29
- * @returns Whether the signature is valid.
29
+ * @returns A promise for whether the signature is valid.
30
30
  */
31
- function verifyEC2(cosePublicKey, signature, data) {
31
+ async function verifyEC2(cosePublicKey, signature, data) {
32
32
  const alg = cosePublicKey.get(COSEKEYS.Alg);
33
33
  const crv = cosePublicKey.get(COSEKEYS.Crv);
34
34
  const xCoord = cosePublicKey.get(COSEKEYS.X);
@@ -42,11 +42,11 @@ function verifyEC2(cosePublicKey, signature, data) {
42
42
  const uncompressed = concatBytes([new Uint8Array([0x04]), xCoord, yCoord]);
43
43
  switch (crv) {
44
44
  case COSECRV.P256:
45
- return p256.verify(signature, sha256(data), uncompressed);
45
+ return p256.verify(signature, await sha256(data), uncompressed);
46
46
  case COSECRV.P384:
47
- return p384.verify(signature, sha384(data), uncompressed);
47
+ return p384.verify(signature, await sha384(data), uncompressed);
48
48
  case COSECRV.P521:
49
- return p521.verify(signature, sha512(data), uncompressed);
49
+ return p521.verify(signature, await sha512(data), uncompressed);
50
50
  default:
51
51
  throw new Error(`Unsupported EC2 curve: ${crv}`);
52
52
  }
@@ -146,8 +146,9 @@ async function verifyRSA(cosePublicKey, signature, data) {
146
146
  * Verify a WebAuthn signature using the appropriate algorithm based on
147
147
  * the COSE key type.
148
148
  *
149
- * Uses @noble/curves for EC2 and OKP (synchronous, audited, handles DER
150
- * natively). Falls back to Web Crypto API for RSA.
149
+ * Uses the async `@metamask/utils` SHA digests with @noble/curves for EC2.
150
+ * Ed25519 verification also remains on @noble/curves, and RSA falls back to
151
+ * the Web Crypto API.
151
152
  *
152
153
  * @param opts - Options object.
153
154
  * @param opts.cosePublicKey - COSE-encoded public key as a Map.
@@ -1 +1 @@
1
- {"version":3,"file":"verify-signature.js","sourceRoot":"","sources":["../../src/webauthn/verify-signature.ts"],"names":[],"mappings":"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","sourcesContent":["import { concatBytes } from '@metamask/utils';\nimport { ed25519 } from '@noble/curves/ed25519';\nimport { p256, p384, p521 } from '@noble/curves/nist';\nimport { sha256, sha384, sha512 } from '@noble/hashes/sha2';\n\nimport { bytesToBase64URL } from '../utils/encoding.js';\nimport { COSEALG, COSECRV, COSEKEYS, COSEKTY } from './constants.js';\n\ntype COSEPublicKey = Map<number, number | Uint8Array>;\n\n/**\n * Get the key type from a COSE public key map.\n *\n * @param cosePublicKey - COSE public key map.\n * @returns The COSEKTY value.\n */\nfunction getKeyType(cosePublicKey: COSEPublicKey): number {\n const kty = cosePublicKey.get(COSEKEYS.Kty);\n if (typeof kty !== 'number') {\n throw new Error('COSE public key missing kty');\n }\n return kty;\n}\n\n/**\n * Verify an EC2 (P-256, P-384, P-521) signature using @noble/curves.\n *\n * ECDSA requires the data to be hashed with the curve-appropriate\n * algorithm before verification: SHA-256 for P-256 and SHA-384 for P-384.\n *\n * @param cosePublicKey - COSE-encoded EC2 public key.\n * @param signature - DER-encoded ECDSA signature.\n * @param data - Data that was signed.\n * @returns Whether the signature is valid.\n */\nfunction verifyEC2(\n cosePublicKey: COSEPublicKey,\n signature: Uint8Array,\n data: Uint8Array,\n): boolean {\n const alg = cosePublicKey.get(COSEKEYS.Alg);\n const crv = cosePublicKey.get(COSEKEYS.Crv) as number;\n const xCoord = cosePublicKey.get(COSEKEYS.X) as Uint8Array;\n const yCoord = cosePublicKey.get(COSEKEYS.Y) as Uint8Array;\n\n if (typeof alg !== 'number') {\n throw new Error('EC2 public key missing alg');\n }\n\n if (!xCoord || !yCoord) {\n throw new Error('EC2 public key missing x or y coordinate');\n }\n\n const uncompressed = concatBytes([new Uint8Array([0x04]), xCoord, yCoord]);\n\n switch (crv) {\n case COSECRV.P256:\n return p256.verify(signature, sha256(data), uncompressed);\n case COSECRV.P384:\n return p384.verify(signature, sha384(data), uncompressed);\n case COSECRV.P521:\n return p521.verify(signature, sha512(data), uncompressed);\n default:\n throw new Error(`Unsupported EC2 curve: ${crv}`);\n }\n}\n\n/**\n * Verify an OKP (Ed25519) signature using @noble/curves.\n *\n * @param cosePublicKey - COSE-encoded OKP public key.\n * @param signature - Raw Ed25519 signature (64 bytes).\n * @param data - Data that was signed.\n * @returns Whether the signature is valid.\n */\nfunction verifyOKP(\n cosePublicKey: COSEPublicKey,\n signature: Uint8Array,\n data: Uint8Array,\n): boolean {\n const alg = cosePublicKey.get(COSEKEYS.Alg);\n const crv = cosePublicKey.get(COSEKEYS.Crv);\n const xCoord = cosePublicKey.get(COSEKEYS.X) as Uint8Array;\n\n if (alg !== COSEALG.EdDSA) {\n throw new Error(`Unexpected OKP algorithm: ${String(alg)}`);\n }\n\n if (crv !== COSECRV.ED25519) {\n throw new Error(`Unsupported OKP curve: ${String(crv)}`);\n }\n\n if (!xCoord) {\n throw new Error('OKP public key missing x coordinate');\n }\n\n return ed25519.verify(signature, data, xCoord);\n}\n\n/**\n * Verify an RSA signature using Web Crypto API.\n *\n * @param cosePublicKey - COSE-encoded RSA public key.\n * @param signature - RSA PKCS#1 v1.5 signature.\n * @param data - Data that was signed.\n * @returns Whether the signature is valid.\n */\nasync function verifyRSA(\n cosePublicKey: COSEPublicKey,\n signature: Uint8Array,\n data: Uint8Array,\n): Promise<boolean> {\n const alg = cosePublicKey.get(COSEKEYS.Alg);\n const modulus = cosePublicKey.get(COSEKEYS.N) as Uint8Array;\n const exponent = cosePublicKey.get(COSEKEYS.E) as Uint8Array;\n\n if (typeof alg !== 'number') {\n throw new Error('RSA public key missing alg');\n }\n\n if (!modulus || !exponent) {\n throw new Error('RSA public key missing n or e');\n }\n\n let keyAlgorithmName: 'RSASSA-PKCS1-v1_5' | 'RSA-PSS';\n let hashAlg: string;\n let saltLength: number | undefined;\n switch (alg) {\n case COSEALG.RS1:\n keyAlgorithmName = 'RSASSA-PKCS1-v1_5';\n hashAlg = 'SHA-1';\n break;\n case COSEALG.RS256:\n keyAlgorithmName = 'RSASSA-PKCS1-v1_5';\n hashAlg = 'SHA-256';\n break;\n case COSEALG.RS384:\n keyAlgorithmName = 'RSASSA-PKCS1-v1_5';\n hashAlg = 'SHA-384';\n break;\n case COSEALG.RS512:\n keyAlgorithmName = 'RSASSA-PKCS1-v1_5';\n hashAlg = 'SHA-512';\n break;\n case COSEALG.PS256:\n keyAlgorithmName = 'RSA-PSS';\n hashAlg = 'SHA-256';\n saltLength = 32;\n break;\n case COSEALG.PS384:\n keyAlgorithmName = 'RSA-PSS';\n hashAlg = 'SHA-384';\n saltLength = 48;\n break;\n case COSEALG.PS512:\n keyAlgorithmName = 'RSA-PSS';\n hashAlg = 'SHA-512';\n saltLength = 64;\n break;\n default:\n throw new Error(`Unsupported RSA algorithm: ${alg}`);\n }\n\n const key = await globalThis.crypto.subtle.importKey(\n 'jwk',\n {\n kty: 'RSA',\n n: bytesToBase64URL(modulus),\n e: bytesToBase64URL(exponent),\n },\n { name: keyAlgorithmName, hash: { name: hashAlg } },\n false,\n ['verify'],\n );\n\n const verifyAlgorithm =\n keyAlgorithmName === 'RSA-PSS'\n ? { name: 'RSA-PSS', saltLength: saltLength as number }\n : 'RSASSA-PKCS1-v1_5';\n\n const signatureBytes = Uint8Array.from(signature);\n const dataBytes = Uint8Array.from(data);\n return globalThis.crypto.subtle.verify(\n verifyAlgorithm,\n key,\n signatureBytes,\n dataBytes,\n );\n}\n\n/**\n * Verify a WebAuthn signature using the appropriate algorithm based on\n * the COSE key type.\n *\n * Uses @noble/curves for EC2 and OKP (synchronous, audited, handles DER\n * natively). Falls back to Web Crypto API for RSA.\n *\n * @param opts - Options object.\n * @param opts.cosePublicKey - COSE-encoded public key as a Map.\n * @param opts.signature - The signature bytes.\n * @param opts.data - The data that was signed.\n * @returns Whether the signature is valid.\n */\nexport async function verifySignature(opts: {\n cosePublicKey: COSEPublicKey;\n signature: Uint8Array;\n data: Uint8Array;\n}): Promise<boolean> {\n const { cosePublicKey, signature, data } = opts;\n const kty = getKeyType(cosePublicKey);\n\n switch (kty) {\n case COSEKTY.EC2:\n return verifyEC2(cosePublicKey, signature, data);\n case COSEKTY.OKP:\n return verifyOKP(cosePublicKey, signature, data);\n case COSEKTY.RSA:\n return verifyRSA(cosePublicKey, signature, data);\n default:\n throw new Error(`Unsupported COSE key type: ${kty}`);\n }\n}\n"]}
1
+ {"version":3,"file":"verify-signature.js","sourceRoot":"","sources":["../../src/webauthn/verify-signature.ts"],"names":[],"mappings":"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","sourcesContent":["import { concatBytes, sha256, sha384, sha512 } from '@metamask/utils';\nimport { ed25519 } from '@noble/curves/ed25519';\nimport { p256, p384, p521 } from '@noble/curves/nist';\n\nimport { bytesToBase64URL } from '../utils/encoding.js';\nimport { COSEALG, COSECRV, COSEKEYS, COSEKTY } from './constants.js';\n\ntype COSEPublicKey = Map<number, number | Uint8Array>;\n\n/**\n * Get the key type from a COSE public key map.\n *\n * @param cosePublicKey - COSE public key map.\n * @returns The COSEKTY value.\n */\nfunction getKeyType(cosePublicKey: COSEPublicKey): number {\n const kty = cosePublicKey.get(COSEKEYS.Kty);\n if (typeof kty !== 'number') {\n throw new Error('COSE public key missing kty');\n }\n return kty;\n}\n\n/**\n * Verify an EC2 (P-256, P-384, P-521) signature using @noble/curves.\n *\n * ECDSA requires the data to be hashed with the curve-appropriate algorithm\n * before verification. Hashing uses the async `@metamask/utils` digests while\n * signature verification remains synchronous in `@noble/curves`.\n *\n * @param cosePublicKey - COSE-encoded EC2 public key.\n * @param signature - DER-encoded ECDSA signature.\n * @param data - Data that was signed.\n * @returns A promise for whether the signature is valid.\n */\nasync function verifyEC2(\n cosePublicKey: COSEPublicKey,\n signature: Uint8Array,\n data: Uint8Array,\n): Promise<boolean> {\n const alg = cosePublicKey.get(COSEKEYS.Alg);\n const crv = cosePublicKey.get(COSEKEYS.Crv) as number;\n const xCoord = cosePublicKey.get(COSEKEYS.X) as Uint8Array;\n const yCoord = cosePublicKey.get(COSEKEYS.Y) as Uint8Array;\n\n if (typeof alg !== 'number') {\n throw new Error('EC2 public key missing alg');\n }\n\n if (!xCoord || !yCoord) {\n throw new Error('EC2 public key missing x or y coordinate');\n }\n\n const uncompressed = concatBytes([new Uint8Array([0x04]), xCoord, yCoord]);\n\n switch (crv) {\n case COSECRV.P256:\n return p256.verify(signature, await sha256(data), uncompressed);\n case COSECRV.P384:\n return p384.verify(signature, await sha384(data), uncompressed);\n case COSECRV.P521:\n return p521.verify(signature, await sha512(data), uncompressed);\n default:\n throw new Error(`Unsupported EC2 curve: ${crv}`);\n }\n}\n\n/**\n * Verify an OKP (Ed25519) signature using @noble/curves.\n *\n * @param cosePublicKey - COSE-encoded OKP public key.\n * @param signature - Raw Ed25519 signature (64 bytes).\n * @param data - Data that was signed.\n * @returns Whether the signature is valid.\n */\nfunction verifyOKP(\n cosePublicKey: COSEPublicKey,\n signature: Uint8Array,\n data: Uint8Array,\n): boolean {\n const alg = cosePublicKey.get(COSEKEYS.Alg);\n const crv = cosePublicKey.get(COSEKEYS.Crv);\n const xCoord = cosePublicKey.get(COSEKEYS.X) as Uint8Array;\n\n if (alg !== COSEALG.EdDSA) {\n throw new Error(`Unexpected OKP algorithm: ${String(alg)}`);\n }\n\n if (crv !== COSECRV.ED25519) {\n throw new Error(`Unsupported OKP curve: ${String(crv)}`);\n }\n\n if (!xCoord) {\n throw new Error('OKP public key missing x coordinate');\n }\n\n return ed25519.verify(signature, data, xCoord);\n}\n\n/**\n * Verify an RSA signature using Web Crypto API.\n *\n * @param cosePublicKey - COSE-encoded RSA public key.\n * @param signature - RSA PKCS#1 v1.5 signature.\n * @param data - Data that was signed.\n * @returns Whether the signature is valid.\n */\nasync function verifyRSA(\n cosePublicKey: COSEPublicKey,\n signature: Uint8Array,\n data: Uint8Array,\n): Promise<boolean> {\n const alg = cosePublicKey.get(COSEKEYS.Alg);\n const modulus = cosePublicKey.get(COSEKEYS.N) as Uint8Array;\n const exponent = cosePublicKey.get(COSEKEYS.E) as Uint8Array;\n\n if (typeof alg !== 'number') {\n throw new Error('RSA public key missing alg');\n }\n\n if (!modulus || !exponent) {\n throw new Error('RSA public key missing n or e');\n }\n\n let keyAlgorithmName: 'RSASSA-PKCS1-v1_5' | 'RSA-PSS';\n let hashAlg: string;\n let saltLength: number | undefined;\n switch (alg) {\n case COSEALG.RS1:\n keyAlgorithmName = 'RSASSA-PKCS1-v1_5';\n hashAlg = 'SHA-1';\n break;\n case COSEALG.RS256:\n keyAlgorithmName = 'RSASSA-PKCS1-v1_5';\n hashAlg = 'SHA-256';\n break;\n case COSEALG.RS384:\n keyAlgorithmName = 'RSASSA-PKCS1-v1_5';\n hashAlg = 'SHA-384';\n break;\n case COSEALG.RS512:\n keyAlgorithmName = 'RSASSA-PKCS1-v1_5';\n hashAlg = 'SHA-512';\n break;\n case COSEALG.PS256:\n keyAlgorithmName = 'RSA-PSS';\n hashAlg = 'SHA-256';\n saltLength = 32;\n break;\n case COSEALG.PS384:\n keyAlgorithmName = 'RSA-PSS';\n hashAlg = 'SHA-384';\n saltLength = 48;\n break;\n case COSEALG.PS512:\n keyAlgorithmName = 'RSA-PSS';\n hashAlg = 'SHA-512';\n saltLength = 64;\n break;\n default:\n throw new Error(`Unsupported RSA algorithm: ${alg}`);\n }\n\n const key = await globalThis.crypto.subtle.importKey(\n 'jwk',\n {\n kty: 'RSA',\n n: bytesToBase64URL(modulus),\n e: bytesToBase64URL(exponent),\n },\n { name: keyAlgorithmName, hash: { name: hashAlg } },\n false,\n ['verify'],\n );\n\n const verifyAlgorithm =\n keyAlgorithmName === 'RSA-PSS'\n ? { name: 'RSA-PSS', saltLength: saltLength as number }\n : 'RSASSA-PKCS1-v1_5';\n\n const signatureBytes = Uint8Array.from(signature);\n const dataBytes = Uint8Array.from(data);\n return globalThis.crypto.subtle.verify(\n verifyAlgorithm,\n key,\n signatureBytes,\n dataBytes,\n );\n}\n\n/**\n * Verify a WebAuthn signature using the appropriate algorithm based on\n * the COSE key type.\n *\n * Uses the async `@metamask/utils` SHA digests with @noble/curves for EC2.\n * Ed25519 verification also remains on @noble/curves, and RSA falls back to\n * the Web Crypto API.\n *\n * @param opts - Options object.\n * @param opts.cosePublicKey - COSE-encoded public key as a Map.\n * @param opts.signature - The signature bytes.\n * @param opts.data - The data that was signed.\n * @returns Whether the signature is valid.\n */\nexport async function verifySignature(opts: {\n cosePublicKey: COSEPublicKey;\n signature: Uint8Array;\n data: Uint8Array;\n}): Promise<boolean> {\n const { cosePublicKey, signature, data } = opts;\n const kty = getKeyType(cosePublicKey);\n\n switch (kty) {\n case COSEKTY.EC2:\n return verifyEC2(cosePublicKey, signature, data);\n case COSEKTY.OKP:\n return verifyOKP(cosePublicKey, signature, data);\n case COSEKTY.RSA:\n return verifyRSA(cosePublicKey, signature, data);\n default:\n throw new Error(`Unsupported COSE key type: ${kty}`);\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/passkey-controller",
3
- "version": "4.0.0-preview-2b92c5a1c",
3
+ "version": "4.0.0-preview-429f77222",
4
4
  "description": "Controller and utilities for passkey-based wallet unlock",
5
5
  "keywords": [
6
6
  "Ethereum",