diff options
author | Matthew Miller <matthew@millerti.me> | 2024-02-24 16:45:51 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-24 16:45:51 -0800 |
commit | 6eb5ac62e778c6c39c936cfce3309b9aa852021d (patch) | |
tree | 1d33f0a5ea58d74d0b41e6cc407ef604d76c399e /packages/server/src | |
parent | fe2245b9c1b7ada2099a6411c8ce2e6e6f18bbf9 (diff) | |
parent | b835ce41e1936765a49d7f7114116d78ddb67a1c (diff) |
Merge pull request #529 from MasterKale/fix/528-simplify-use-of-credential-id
fix/528-simplify-use-of-credential-id
Diffstat (limited to 'packages/server/src')
11 files changed, 102 insertions, 81 deletions
diff --git a/packages/server/src/authentication/generateAuthenticationOptions.test.ts b/packages/server/src/authentication/generateAuthenticationOptions.test.ts index 8d55580..49d668e 100644 --- a/packages/server/src/authentication/generateAuthenticationOptions.test.ts +++ b/packages/server/src/authentication/generateAuthenticationOptions.test.ts @@ -11,13 +11,11 @@ Deno.test('should generate credential request options suitable for sending via J const options = await generateAuthenticationOptions({ allowCredentials: [ { - id: isoUint8Array.fromASCIIString('1234'), - type: 'public-key', + id: '1234', transports: ['usb', 'nfc'], }, { - id: isoUint8Array.fromASCIIString('5678'), - type: 'public-key', + id: '5678', transports: ['internal'], }, ], @@ -30,12 +28,12 @@ Deno.test('should generate credential request options suitable for sending via J challenge: challengeString, allowCredentials: [ { - id: 'MTIzNA', + id: '1234', type: 'public-key', transports: ['usb', 'nfc'], }, { - id: 'NTY3OA', + id: '5678', type: 'public-key', transports: ['internal'], }, @@ -51,8 +49,8 @@ Deno.test('defaults to 60 seconds if no timeout is specified', async () => { const options = await generateAuthenticationOptions({ challenge: challengeBuffer, allowCredentials: [ - { id: isoUint8Array.fromASCIIString('1234'), type: 'public-key' }, - { id: isoUint8Array.fromASCIIString('5678'), type: 'public-key' }, + { id: '1234' }, + { id: '5678' }, ], }); @@ -63,8 +61,8 @@ Deno.test('should set userVerification to "preferred" if not specified', async ( const options = await generateAuthenticationOptions({ challenge: challengeBuffer, allowCredentials: [ - { id: isoUint8Array.fromASCIIString('1234'), type: 'public-key' }, - { id: isoUint8Array.fromASCIIString('5678'), type: 'public-key' }, + { id: '1234' }, + { id: '5678' }, ], }); @@ -94,8 +92,8 @@ Deno.test('should set userVerification if specified', async () => { const options = await generateAuthenticationOptions({ challenge: challengeBuffer, allowCredentials: [ - { id: isoUint8Array.fromASCIIString('1234'), type: 'public-key' }, - { id: isoUint8Array.fromASCIIString('5678'), type: 'public-key' }, + { id: '1234' }, + { id: '5678' }, ], userVerification: 'required', }); @@ -107,8 +105,8 @@ Deno.test('should set extensions if specified', async () => { const options = await generateAuthenticationOptions({ challenge: challengeBuffer, allowCredentials: [ - { id: isoUint8Array.fromASCIIString('1234'), type: 'public-key' }, - { id: isoUint8Array.fromASCIIString('5678'), type: 'public-key' }, + { id: '1234' }, + { id: '5678' }, ], extensions: { appid: 'simplewebauthn' }, }); @@ -117,19 +115,17 @@ Deno.test('should set extensions if specified', async () => { }); Deno.test('should generate a challenge if one is not provided', async () => { - const opts = { + // @ts-ignore 2345 + const options = await generateAuthenticationOptions({ allowCredentials: [ - { id: isoUint8Array.fromASCIIString('1234'), type: 'public-key' }, - { id: isoUint8Array.fromASCIIString('5678'), type: 'public-key' }, + { id: '1234' }, + { id: '5678' }, ], - }; - - // @ts-ignore 2345 - const options = await generateAuthenticationOptions(opts); + }); // Assert basic properties of the challenge assert(options.challenge.length >= 16); - assert(isoBase64URL.isBase64url(options.challenge)); + assert(isoBase64URL.isBase64URL(options.challenge)); }); Deno.test('should treat string challenges as UTF-8 strings', async () => { diff --git a/packages/server/src/authentication/generateAuthenticationOptions.ts b/packages/server/src/authentication/generateAuthenticationOptions.ts index b1c8166..44ed2b7 100644 --- a/packages/server/src/authentication/generateAuthenticationOptions.ts +++ b/packages/server/src/authentication/generateAuthenticationOptions.ts @@ -1,6 +1,7 @@ import type { AuthenticationExtensionsClientInputs, - PublicKeyCredentialDescriptorFuture, + AuthenticatorTransportFuture, + Base64URLString, PublicKeyCredentialRequestOptionsJSON, UserVerificationRequirement, } from '../deps.ts'; @@ -8,7 +9,10 @@ import { isoBase64URL, isoUint8Array } from '../helpers/iso/index.ts'; import { generateChallenge } from '../helpers/generateChallenge.ts'; export type GenerateAuthenticationOptionsOpts = { - allowCredentials?: PublicKeyCredentialDescriptorFuture[]; + allowCredentials?: { + id: Base64URLString; + transports?: AuthenticatorTransportFuture[]; + }[]; challenge?: string | Uint8Array; timeout?: number; userVerification?: UserVerificationRequirement; @@ -51,10 +55,17 @@ export async function generateAuthenticationOptions( return { challenge: isoBase64URL.fromBuffer(_challenge), - allowCredentials: allowCredentials?.map((cred) => ({ - ...cred, - id: isoBase64URL.fromBuffer(cred.id as Uint8Array), - })), + allowCredentials: allowCredentials?.map((cred) => { + if (!isoBase64URL.isBase64URL(cred.id)) { + throw new Error(`excludeCredential id "${cred.id}" is not a valid base64url string`); + } + + return { + ...cred, + id: isoBase64URL.trimPadding(cred.id), + type: 'public-key', + }; + }), timeout, userVerification, extensions, diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts index b150aff..844726f 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts @@ -267,9 +267,7 @@ Deno.test('should verify TPM assertion', { ignore: true }, async () => { expectedRPID: 'dev.dontneeda.pw', authenticator: { credentialPublicKey: isoBase64URL.toBuffer('BAEAAQ'), - credentialID: isoBase64URL.toBuffer( - 'YJ8FMM-AmcUt73XPX341WXWd7ypBMylGjjhu0g3VzME', - ), + credentialID: 'YJ8FMM-AmcUt73XPX341WXWd7ypBMylGjjhu0g3VzME', counter: 0, }, }); @@ -400,9 +398,8 @@ Deno.test('should pass verification if custom challenge verifier returns true', expectedOrigin: 'http://localhost:8000', expectedRPID: 'localhost', authenticator: { - credentialID: isoBase64URL.toBuffer( + credentialID: 'AaIBxnYfL2pDWJmIii6CYgHBruhVvFGHheWamphVioG_TnEXxKA9MW4FWnJh21zsbmRpRJso9i2JmAtWOtXfVd4oXTgYVusXwhWWsA', - ), credentialPublicKey: isoBase64URL.toBuffer( 'pQECAyYgASFYILTrxTUQv3X4DRM6L_pk65FSMebenhCx3RMsTKoBm-AxIlggEf3qk5552QLNSh1T1oQs7_2C2qysDwN4r4fCp52Hsqs', ), @@ -461,9 +458,8 @@ Deno.test('should pass verification if custom challenge verifier returns a Promi expectedOrigin: 'http://localhost:8000', expectedRPID: 'localhost', authenticator: { - credentialID: isoBase64URL.toBuffer( + credentialID: 'AaIBxnYfL2pDWJmIii6CYgHBruhVvFGHheWamphVioG_TnEXxKA9MW4FWnJh21zsbmRpRJso9i2JmAtWOtXfVd4oXTgYVusXwhWWsA', - ), credentialPublicKey: isoBase64URL.toBuffer( 'pQECAyYgASFYILTrxTUQv3X4DRM6L_pk65FSMebenhCx3RMsTKoBm-AxIlggEf3qk5552QLNSh1T1oQs7_2C2qysDwN4r4fCp52Hsqs', ), @@ -525,9 +521,8 @@ Deno.test('should return authenticator extension output', async () => { expectedRPID: 'try-webauthn.appspot.com', expectedChallenge: 'iZsVCztrDW7D2U_GHCIlYKLwV2bCsBTRqVQUnJXn9Tk', authenticator: { - credentialID: isoBase64URL.toBuffer( + credentialID: 'AaIBxnYfL2pDWJmIii6CYgHBruhVvFGHheWamphVioG_TnEXxKA9MW4FWnJh21zsbmRpRJso9i2JmAtWOtXfVd4oXTgYVusXwhWWsA', - ), credentialPublicKey: isoBase64URL.toBuffer( 'pQECAyYgASFYILTrxTUQv3X4DRM6L_pk65FSMebenhCx3RMsTKoBm-AxIlggEf3qk5552QLNSh1T1oQs7_2C2qysDwN4r4fCp52Hsqs', ), @@ -611,9 +606,8 @@ const authenticator: AuthenticatorDevice = { credentialPublicKey: isoBase64URL.toBuffer( 'pQECAyYgASFYIIheFp-u6GvFT2LNGovf3ZrT0iFVBsA_76rRysxRG9A1Ilgg8WGeA6hPmnab0HAViUYVRkwTNcN77QBf_RR0dv3lIvQ', ), - credentialID: isoBase64URL.toBuffer( + credentialID: 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew', - ), counter: 143, }; @@ -641,8 +635,7 @@ const authenticatorFirstTimeUsed: AuthenticatorDevice = { credentialPublicKey: isoBase64URL.toBuffer( 'pQECAyYgASFYIGmaxR4mBbukc2QhtW2ldhAAd555r-ljlGQN8MbcTnPPIlgg9CyUlE-0AB2fbzZbNgBvJuRa7r6o2jPphOmtyNPR_kY', ), - credentialID: isoBase64URL.toBuffer( + credentialID: 'wSisR0_4hlzw3Y1tj4uNwwifIhRa-ZxWJwWbnfror0pVK9qPdBPO5pW3gasPqn6wXHb0LNhXB_IrA1nFoSQJ9A', - ), counter: 0, }; diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts index c938598..c6430c0 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts @@ -1,6 +1,7 @@ import type { AuthenticationResponseJSON, AuthenticatorDevice, + Base64URLString, CredentialDeviceType, UserVerificationRequirement, } from '../deps.ts'; @@ -94,11 +95,15 @@ export async function verifyAuthenticationResponse( if (Array.isArray(expectedType)) { if (!expectedType.includes(type)) { const joinedExpectedType = expectedType.join(', '); - throw new Error(`Unexpected authentication response type "${type}", expected one of: ${joinedExpectedType}`); + throw new Error( + `Unexpected authentication response type "${type}", expected one of: ${joinedExpectedType}`, + ); } } else if (expectedType) { if (type !== expectedType) { - throw new Error(`Unexpected authentication response type "${type}", expected "${expectedType}"`); + throw new Error( + `Unexpected authentication response type "${type}", expected "${expectedType}"`, + ); } } else if (type !== 'webauthn.get') { throw new Error(`Unexpected authentication response type: ${type}`); @@ -133,13 +138,13 @@ export async function verifyAuthenticationResponse( } } - if (!isoBase64URL.isBase64url(assertionResponse.authenticatorData)) { + if (!isoBase64URL.isBase64URL(assertionResponse.authenticatorData)) { throw new Error( 'Credential response authenticatorData was not a base64url string', ); } - if (!isoBase64URL.isBase64url(assertionResponse.signature)) { + if (!isoBase64URL.isBase64URL(assertionResponse.signature)) { throw new Error('Credential response signature was not a base64url string'); } @@ -280,7 +285,7 @@ export async function verifyAuthenticationResponse( export type VerifiedAuthenticationResponse = { verified: boolean; authenticationInfo: { - credentialID: Uint8Array; + credentialID: Base64URLString; newCounter: number; userVerified: boolean; credentialDeviceType: CredentialDeviceType; diff --git a/packages/server/src/deps.ts b/packages/server/src/deps.ts index 3acef11..53b6247 100644 --- a/packages/server/src/deps.ts +++ b/packages/server/src/deps.ts @@ -5,12 +5,12 @@ export type { AuthenticationResponseJSON, AuthenticatorDevice, AuthenticatorSelectionCriteria, + AuthenticatorTransportFuture, Base64URLString, COSEAlgorithmIdentifier, CredentialDeviceType, Crypto, PublicKeyCredentialCreationOptionsJSON, - PublicKeyCredentialDescriptorFuture, PublicKeyCredentialParameters, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON, diff --git a/packages/server/src/helpers/convertCertBufferToPEM.ts b/packages/server/src/helpers/convertCertBufferToPEM.ts index d7cd4c0..6e7d5fc 100644 --- a/packages/server/src/helpers/convertCertBufferToPEM.ts +++ b/packages/server/src/helpers/convertCertBufferToPEM.ts @@ -13,7 +13,7 @@ export function convertCertBufferToPEM( * Get certBuffer to a base64 representation */ if (typeof certBuffer === 'string') { - if (isoBase64URL.isBase64url(certBuffer)) { + if (isoBase64URL.isBase64URL(certBuffer)) { b64cert = isoBase64URL.toBase64(certBuffer); } else if (isoBase64URL.isBase64(certBuffer)) { b64cert = certBuffer; diff --git a/packages/server/src/helpers/iso/isoBase64URL.ts b/packages/server/src/helpers/iso/isoBase64URL.ts index 5098b0c..99bf82a 100644 --- a/packages/server/src/helpers/iso/isoBase64URL.ts +++ b/packages/server/src/helpers/iso/isoBase64URL.ts @@ -1,4 +1,5 @@ import { base64 } from '../../deps.ts'; +import type { Base64URLString } from '../../deps.ts'; /** * Decode from a Base64URL-encoded string to an ArrayBuffer. Best used when converting a @@ -63,8 +64,15 @@ export function isBase64(input: string): boolean { /** * Confirm that the string is encoded into base64url, with support for optional padding */ -export function isBase64url(input: string): boolean { +export function isBase64URL(input: string): boolean { // Trim padding characters from the string if present - input = input.replace(/=/g, ''); + input = trimPadding(input); return base64.validate(input, true); } + +/** + * Remove optional padding from a base64url-encoded string + */ +export function trimPadding(input: Base64URLString): Base64URLString { + return input.replace(/=/g, ''); +} diff --git a/packages/server/src/registration/generateRegistrationOptions.test.ts b/packages/server/src/registration/generateRegistrationOptions.test.ts index fded674..6704d23 100644 --- a/packages/server/src/registration/generateRegistrationOptions.test.ts +++ b/packages/server/src/registration/generateRegistrationOptions.test.ts @@ -67,8 +67,7 @@ Deno.test('should map excluded credential IDs if specified', async () => { userName: 'usernameHere', excludeCredentials: [ { - id: isoUint8Array.fromASCIIString('someIDhere'), - type: 'public-key', + id: 'someIDhere', transports: ['usb', 'ble', 'nfc', 'internal'], }, ], @@ -78,7 +77,7 @@ Deno.test('should map excluded credential IDs if specified', async () => { options.excludeCredentials, [ { - id: 'c29tZUlEaGVyZQ', + id: 'someIDhere', type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'], }, diff --git a/packages/server/src/registration/generateRegistrationOptions.ts b/packages/server/src/registration/generateRegistrationOptions.ts index c894abb..2504751 100644 --- a/packages/server/src/registration/generateRegistrationOptions.ts +++ b/packages/server/src/registration/generateRegistrationOptions.ts @@ -2,9 +2,10 @@ import type { AttestationConveyancePreference, AuthenticationExtensionsClientInputs, AuthenticatorSelectionCriteria, + AuthenticatorTransportFuture, + Base64URLString, COSEAlgorithmIdentifier, PublicKeyCredentialCreationOptionsJSON, - PublicKeyCredentialDescriptorFuture, PublicKeyCredentialParameters, } from '../deps.ts'; import { generateChallenge } from '../helpers/generateChallenge.ts'; @@ -19,7 +20,10 @@ export type GenerateRegistrationOptionsOpts = { userDisplayName?: string; timeout?: number; attestationType?: AttestationConveyancePreference; - excludeCredentials?: PublicKeyCredentialDescriptorFuture[]; + excludeCredentials?: { + id: Base64URLString; + transports?: AuthenticatorTransportFuture[]; + }[]; authenticatorSelection?: AuthenticatorSelectionCriteria; extensions?: AuthenticationExtensionsClientInputs; supportedAlgorithmIDs?: COSEAlgorithmIdentifier[]; @@ -174,10 +178,17 @@ export async function generateRegistrationOptions( pubKeyCredParams, timeout, attestation: attestationType, - excludeCredentials: excludeCredentials.map((cred) => ({ - ...cred, - id: isoBase64URL.fromBuffer(cred.id as Uint8Array), - })), + excludeCredentials: excludeCredentials.map((cred) => { + if (!isoBase64URL.isBase64URL(cred.id)) { + throw new Error(`excludeCredential id "${cred.id}" is not a valid base64url string`); + } + + return { + ...cred, + id: isoBase64URL.trimPadding(cred.id), + type: 'public-key', + }; + }), authenticatorSelection, extensions: { ...extensions, diff --git a/packages/server/src/registration/verifyRegistrationResponse.test.ts b/packages/server/src/registration/verifyRegistrationResponse.test.ts index fbe7aed..89b4694 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.test.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.test.ts @@ -88,10 +88,7 @@ Deno.test('should verify Packed (EC2) attestation', async () => { ); assertEquals( verification.registrationInfo?.credentialID, - isoBase64URL.toBuffer( - 'AYThY1csINY4JrbHyGmqTl1nL_F1zjAF3hSAIngz8kAcjugmAMNVvxZRwqpEH-bNHHAIv291OX5ko9eDf_5mu3U' + - 'B2BvsScr2K-ppM4owOpGsqwg5tZglqqmxIm1Q', - ), + 'AYThY1csINY4JrbHyGmqTl1nL_F1zjAF3hSAIngz8kAcjugmAMNVvxZRwqpEH-bNHHAIv291OX5ko9eDf_5mu3UB2BvsScr2K-ppM4owOpGsqwg5tZglqqmxIm1Q', ); }); @@ -115,9 +112,7 @@ Deno.test('should verify Packed (X5C) attestation', async () => { ); assertEquals( verification.registrationInfo?.credentialID, - isoBase64URL.toBuffer( - '4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56Tlg', - ), + '4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56Tlg', ); }); @@ -140,9 +135,7 @@ Deno.test('should verify None attestation', async () => { ); assertEquals( verification.registrationInfo?.credentialID, - isoBase64URL.toBuffer( - 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY', - ), + 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY', ); assertEquals( verification.registrationInfo?.origin, @@ -182,7 +175,7 @@ Deno.test('should verify None attestation w/RSA public key', async () => { ); assertEquals( verification.registrationInfo?.credentialID, - isoBase64URL.toBuffer('kGXv4RJWLeXRw8Yf3T22K3Gq_GGeDv9OKYmAHLm0Ylo'), + 'kGXv4RJWLeXRw8Yf3T22K3Gq_GGeDv9OKYmAHLm0Ylo', ); assertEquals( verification.registrationInfo?.origin, @@ -227,10 +220,10 @@ Deno.test('should throw when response type is not expected value', async () => { expectedChallenge: attestationNoneChallenge, expectedOrigin: 'https://dev.dontneeda.pw', expectedRPID: 'dev.dontneeda.pw', - expectedType: 'something.get' + expectedType: 'something.get', }), Error, - 'registration response type', + 'registration response type', ); }); @@ -242,7 +235,7 @@ Deno.test('should throw when response type is not in list of expected types', as expectedChallenge: attestationNoneChallenge, expectedOrigin: 'https://dev.dontneeda.pw', expectedRPID: 'dev.dontneeda.pw', - expectedType: ['something.create', 'something.else.create'] + expectedType: ['something.create', 'something.else.create'], }), Error, 'registration response type', @@ -301,7 +294,7 @@ Deno.test('should validate when attestation type is not webauthn.create and expe expectedChallenge: challenge, expectedOrigin: origin, expectedRPID: 'dev.dontneeda.pw', - expectedType: 'webauthn.goodtype' + expectedType: 'webauthn.goodtype', }); assert(verification.verified); @@ -600,7 +593,7 @@ Deno.test('should validate TPM RSA response (SHA256)', async () => { ); assertEquals( verification.registrationInfo?.credentialID, - isoBase64URL.toBuffer('lGkWHPe88VpnNYgVBxzon_MRR9-gmgODveQ16uM_bPM'), + 'lGkWHPe88VpnNYgVBxzon_MRR9-gmgODveQ16uM_bPM', ); assertEquals( verification.registrationInfo?.origin, @@ -642,7 +635,7 @@ Deno.test('should validate TPM RSA response (SHA1)', async () => { ); assertEquals( verification.registrationInfo?.credentialID, - isoBase64URL.toBuffer('oELnad0f6-g2BtzEn_78iLNoubarlq0xFtOtAMXnflU'), + 'oELnad0f6-g2BtzEn_78iLNoubarlq0xFtOtAMXnflU', ); assertEquals( verification.registrationInfo?.origin, @@ -684,7 +677,7 @@ Deno.test('should validate Android-Key response', async () => { ); assertEquals( verification.registrationInfo?.credentialID, - isoBase64URL.toBuffer('PPa1spYTB680cQq5q6qBtFuPLLdG1FQ73EastkT8n0o'), + 'PPa1spYTB680cQq5q6qBtFuPLLdG1FQ73EastkT8n0o', ); assertEquals( verification.registrationInfo?.origin, diff --git a/packages/server/src/registration/verifyRegistrationResponse.ts b/packages/server/src/registration/verifyRegistrationResponse.ts index d2399e8..7851e20 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.ts @@ -1,4 +1,5 @@ import type { + Base64URLString, COSEAlgorithmIdentifier, CredentialDeviceType, RegistrationResponseJSON, @@ -95,11 +96,15 @@ export async function verifyRegistrationResponse( if (Array.isArray(expectedType)) { if (!expectedType.includes(type)) { const joinedExpectedType = expectedType.join(', '); - throw new Error(`Unexpected registration response type "${type}", expected one of: ${joinedExpectedType}`); + throw new Error( + `Unexpected registration response type "${type}", expected one of: ${joinedExpectedType}`, + ); } } else if (expectedType) { if (type !== expectedType) { - throw new Error(`Unexpected registration response type "${type}", expected "${expectedType}"`); + throw new Error( + `Unexpected registration response type "${type}", expected "${expectedType}"`, + ); } } else if (type !== 'webauthn.create') { throw new Error(`Unexpected registration response type: ${type}`); @@ -280,7 +285,7 @@ export async function verifyRegistrationResponse( fmt, counter, aaguid: convertAAGUIDToString(aaguid), - credentialID, + credentialID: isoBase64URL.fromBuffer(credentialID), credentialPublicKey, credentialType, attestationObject, @@ -328,7 +333,7 @@ export type VerifiedRegistrationResponse = { fmt: AttestationFormat; counter: number; aaguid: string; - credentialID: Uint8Array; + credentialID: Base64URLString; credentialPublicKey: Uint8Array; credentialType: 'public-key'; attestationObject: Uint8Array; |