diff options
author | Matthew Miller <matthew@millerti.me> | 2021-02-05 09:12:23 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-05 09:12:23 -0800 |
commit | 432ff68bab6b11f6ccbf549d8be397fd71da29f9 (patch) | |
tree | 9dc17be10efa4545a0b5a5a86236cf34405d133f | |
parent | 70c1360339d0e63dafd4a04fa1824d9453f0a802 (diff) | |
parent | 897291381dd45a94daf322cf533626fe7e235349 (diff) |
Merge pull request #97 from MasterKale/feature/better-passwordless-usernameless
feature/better-passwordless-usernameless
15 files changed, 251 insertions, 163 deletions
diff --git a/package.json b/package.json index d013745..f1b578f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "ts-morph": "^9.0.0", "ts-node": "^8.10.2", "ttypescript": "^1.5.12", - "typedoc": "^0.20.0-beta.8", + "typedoc": "^0.20.20", "typescript": "^4.0.5", "typescript-transform-paths": "^1.1.15" }, diff --git a/packages/server/src/assertion/generateAssertionOptions.test.ts b/packages/server/src/assertion/generateAssertionOptions.test.ts index 24253bf..0208d9d 100644 --- a/packages/server/src/assertion/generateAssertionOptions.test.ts +++ b/packages/server/src/assertion/generateAssertionOptions.test.ts @@ -8,12 +8,12 @@ test('should generate credential request options suitable for sending via JSON', const options = generateAssertionOptions({ allowCredentials: [ { - id: Buffer.from('1234', 'ascii').toString('base64'), + id: Buffer.from('1234', 'ascii'), type: 'public-key', transports: ['usb', 'nfc'], }, { - id: Buffer.from('5678', 'ascii').toString('base64'), + id: Buffer.from('5678', 'ascii'), type: 'public-key', transports: ['internal'], }, @@ -27,12 +27,12 @@ test('should generate credential request options suitable for sending via JSON', challenge: 'dG90YWxseXJhbmRvbXZhbHVl', allowCredentials: [ { - id: 'MTIzNA==', + id: 'MTIzNA', type: 'public-key', transports: ['usb', 'nfc'], }, { - id: 'NTY3OA==', + id: 'NTY3OA', type: 'public-key', transports: ['internal'], }, @@ -45,8 +45,8 @@ test('defaults to 60 seconds if no timeout is specified', () => { const options = generateAssertionOptions({ challenge: 'totallyrandomvalue', allowCredentials: [ - { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' }, - { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' }, + { id: Buffer.from('1234', 'ascii'), type: 'public-key' }, + { id: Buffer.from('5678', 'ascii'), type: 'public-key' }, ], }); @@ -57,8 +57,8 @@ test('should not set userVerification if not specified', () => { const options = generateAssertionOptions({ challenge: 'totallyrandomvalue', allowCredentials: [ - { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' }, - { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' }, + { id: Buffer.from('1234', 'ascii'), type: 'public-key' }, + { id: Buffer.from('5678', 'ascii'), type: 'public-key' }, ], }); @@ -88,8 +88,8 @@ test('should set userVerification if specified', () => { const options = generateAssertionOptions({ challenge: 'totallyrandomvalue', allowCredentials: [ - { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' }, - { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' }, + { id: Buffer.from('1234', 'ascii'), type: 'public-key' }, + { id: Buffer.from('5678', 'ascii'), type: 'public-key' }, ], userVerification: 'required', }); @@ -101,8 +101,8 @@ test('should set extensions if specified', () => { const options = generateAssertionOptions({ challenge: 'totallyrandomvalue', allowCredentials: [ - { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' }, - { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' }, + { id: Buffer.from('1234', 'ascii'), type: 'public-key' }, + { id: Buffer.from('5678', 'ascii'), type: 'public-key' }, ], extensions: { appid: 'simplewebauthn' }, }); @@ -115,8 +115,8 @@ test('should set extensions if specified', () => { test('should generate a challenge if one is not provided', () => { const opts = { allowCredentials: [ - { id: Buffer.from('1234', 'ascii').toString('base64'), type: 'public-key' }, - { id: Buffer.from('5678', 'ascii').toString('base64'), type: 'public-key' }, + { id: Buffer.from('1234', 'ascii'), type: 'public-key' }, + { id: Buffer.from('5678', 'ascii'), type: 'public-key' }, ], }; diff --git a/packages/server/src/assertion/generateAssertionOptions.ts b/packages/server/src/assertion/generateAssertionOptions.ts index 370dbc8..19bce91 100644 --- a/packages/server/src/assertion/generateAssertionOptions.ts +++ b/packages/server/src/assertion/generateAssertionOptions.ts @@ -1,7 +1,7 @@ import type { AuthenticationExtensionsClientInputs, PublicKeyCredentialRequestOptionsJSON, - PublicKeyCredentialDescriptorJSON, + PublicKeyCredentialDescriptor, UserVerificationRequirement, } from '@simplewebauthn/typescript-types'; import base64url from 'base64url'; @@ -9,7 +9,7 @@ import base64url from 'base64url'; import generateChallenge from '../helpers/generateChallenge'; type Options = { - allowCredentials?: PublicKeyCredentialDescriptorJSON[]; + allowCredentials?: PublicKeyCredentialDescriptor[]; challenge?: string | Buffer; timeout?: number; userVerification?: UserVerificationRequirement; @@ -44,7 +44,10 @@ export default function generateAssertionOptions( return { challenge: base64url.encode(challenge), - allowCredentials, + allowCredentials: allowCredentials?.map(cred => ({ + ...cred, + id: base64url.encode(cred.id as Buffer), + })), timeout, userVerification, extensions, diff --git a/packages/server/src/assertion/verifyAssertionResponse.test.ts b/packages/server/src/assertion/verifyAssertionResponse.test.ts index 4e4b64f..1708f77 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.test.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.test.ts @@ -4,6 +4,7 @@ import verifyAssertionResponse from './verifyAssertionResponse'; import * as decodeClientDataJSON from '../helpers/decodeClientDataJSON'; import * as parseAuthenticatorData from '../helpers/parseAuthenticatorData'; import toHash from '../helpers/toHash'; +import { AuthenticatorDevice } from '@simplewebauthn/typescript-types'; let mockDecodeClientData: jest.SpyInstance; let mockParseAuthData: jest.SpyInstance; @@ -39,8 +40,8 @@ test('should return authenticator info after verification', () => { authenticator: authenticator, }); - expect(verification.authenticatorInfo.counter).toEqual(144); - expect(verification.authenticatorInfo.base64CredentialID).toEqual(authenticator.credentialID); + expect(verification.assertionInfo.newCounter).toEqual(144); + expect(verification.assertionInfo.credentialID).toEqual(authenticator.credentialID); }); test('should throw when response challenge is not expected value', () => { @@ -198,8 +199,8 @@ test.skip('should verify TPM assertion', () => { expectedOrigin: assertionOrigin, expectedRPID: 'dev.dontneeda.pw', authenticator: { - publicKey: 'BAEAAQ', - credentialID: 'YJ8FMM-AmcUt73XPX341WXWd7ypBMylGjjhu0g3VzME', + credentialPublicKey: base64url.toBuffer('BAEAAQ'), + credentialID: base64url.toBuffer('YJ8FMM-AmcUt73XPX341WXWd7ypBMylGjjhu0g3VzME'), counter: 0, }, }); @@ -278,11 +279,13 @@ const assertionResponse = { const assertionChallenge = base64url.encode('totallyUniqueValueEveryTime'); const assertionOrigin = 'https://dev.dontneeda.pw'; -const authenticator = { - publicKey: +const authenticator: AuthenticatorDevice = { + credentialPublicKey: base64url.toBuffer( 'pQECAyYgASFYIIheFp-u6GvFT2LNGovf3ZrT0iFVBsA_76rRysxRG9A1Ilgg8WGeA6hPmnab0HAViUYVRkwTNcN77QBf_RR0dv3lIvQ', - credentialID: + ), + credentialID: base64url.toBuffer( 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew', + ), counter: 143, }; @@ -303,10 +306,12 @@ const assertionFirstTimeUsedResponse = { }; const assertionFirstTimeUsedChallenge = base64url.encode('totallyUniqueValueEveryAssertion'); const assertionFirstTimeUsedOrigin = 'https://dev.dontneeda.pw'; -const authenticatorFirstTimeUsed = { - publicKey: +const authenticatorFirstTimeUsed: AuthenticatorDevice = { + credentialPublicKey: base64url.toBuffer( 'pQECAyYgASFYIGmaxR4mBbukc2QhtW2ldhAAd555r-ljlGQN8MbcTnPPIlgg9CyUlE-0AB2fbzZbNgBvJuRa7r6o2jPphOmtyNPR_kY', - credentialID: + ), + credentialID: base64url.toBuffer( 'wSisR0_4hlzw3Y1tj4uNwwifIhRa-ZxWJwWbnfror0pVK9qPdBPO5pW3gasPqn6wXHb0LNhXB_IrA1nFoSQJ9A', + ), counter: 0, }; diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts index 0c76fae..2c85e6e 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.ts @@ -163,7 +163,7 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser const clientDataHash = toHash(base64url.toBuffer(response.clientDataJSON)); const signatureBase = Buffer.concat([authDataBuffer, clientDataHash]); - const publicKey = convertPublicKeyToPEM(authenticator.publicKey); + const publicKey = convertPublicKeyToPEM(authenticator.credentialPublicKey); const signature = base64url.toBuffer(response.signature); if ((counter > 0 || authenticator.counter > 0) && counter <= authenticator.counter) { @@ -178,9 +178,9 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser const toReturn = { verified: verifySignature(signature, signatureBase, publicKey), - authenticatorInfo: { - counter, - base64CredentialID: credential.id, + assertionInfo: { + newCounter: counter, + credentialID: authenticator.credentialID, }, }; @@ -191,16 +191,17 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser * Result of assertion verification * * @param verified If the assertion response could be verified - * @param authenticatorInfo.base64CredentialID The ID of the authenticator used during assertion. + * @param assertionInfo.credentialID The ID of the authenticator used during assertion. * Should be used to identify which DB authenticator entry needs its `counter` updated to the value * below - * @param authenticatorInfo.counter The number of times the authenticator identified above reported - * it has been used. **Should be kept in a DB for later reference to help prevent replay attacks!** + * @param assertionInfo.newCounter The number of times the authenticator identified above + * reported it has been used. **Should be kept in a DB for later reference to help prevent replay + * attacks!** */ export type VerifiedAssertion = { verified: boolean; - authenticatorInfo: { - counter: number; - base64CredentialID: string; + assertionInfo: { + credentialID: Buffer; + newCounter: number; }; }; diff --git a/packages/server/src/attestation/generateAttestationOptions.test.ts b/packages/server/src/attestation/generateAttestationOptions.test.ts index 902aae6..eb7dcd7 100644 --- a/packages/server/src/attestation/generateAttestationOptions.test.ts +++ b/packages/server/src/attestation/generateAttestationOptions.test.ts @@ -62,13 +62,17 @@ test('should map excluded credential IDs if specified', () => { userID: '1234', userName: 'usernameHere', excludeCredentials: [ - { id: 'someIDhere', type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'] }, + { + id: Buffer.from('someIDhere', 'ascii'), + type: 'public-key', + transports: ['usb', 'ble', 'nfc', 'internal'], + }, ], }); expect(options.excludeCredentials).toEqual([ { - id: 'someIDhere', + id: 'c29tZUlEaGVyZQ', type: 'public-key', transports: ['usb', 'ble', 'nfc', 'internal'], }, diff --git a/packages/server/src/attestation/generateAttestationOptions.ts b/packages/server/src/attestation/generateAttestationOptions.ts index 2c0f85b..a523cd1 100644 --- a/packages/server/src/attestation/generateAttestationOptions.ts +++ b/packages/server/src/attestation/generateAttestationOptions.ts @@ -4,7 +4,7 @@ import type { AuthenticatorSelectionCriteria, COSEAlgorithmIdentifier, PublicKeyCredentialCreationOptionsJSON, - PublicKeyCredentialDescriptorJSON, + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, } from '@simplewebauthn/typescript-types'; import base64url from 'base64url'; @@ -20,7 +20,7 @@ type Options = { userDisplayName?: string; timeout?: number; attestationType?: AttestationConveyancePreference; - excludeCredentials?: PublicKeyCredentialDescriptorJSON[]; + excludeCredentials?: PublicKeyCredentialDescriptor[]; authenticatorSelection?: AuthenticatorSelectionCriteria; extensions?: AuthenticationExtensionsClientInputs; supportedAlgorithmIDs?: COSEAlgorithmIdentifier[]; @@ -145,7 +145,10 @@ export default function generateAttestationOptions( pubKeyCredParams, timeout, attestation: attestationType, - excludeCredentials, + excludeCredentials: excludeCredentials.map(cred => ({ + ...cred, + id: base64url.encode(cred.id as Buffer), + })), authenticatorSelection, extensions, }; diff --git a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.test.ts b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.test.ts index d6536b9..ea16b1d 100644 --- a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.test.ts +++ b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.test.ts @@ -15,7 +15,7 @@ let aaguid: Buffer; beforeEach(() => { const { attestationObject, clientDataJSON } = attestationAndroidSafetyNet.response; - const decodedAttestationObject = decodeAttestationObject(attestationObject); + const decodedAttestationObject = decodeAttestationObject(base64url.toBuffer(attestationObject)); authData = decodedAttestationObject.authData; attStmt = decodedAttestationObject.attStmt; diff --git a/packages/server/src/attestation/verifyAttestationResponse.test.ts b/packages/server/src/attestation/verifyAttestationResponse.test.ts index a6e0814..dc75538 100644 --- a/packages/server/src/attestation/verifyAttestationResponse.test.ts +++ b/packages/server/src/attestation/verifyAttestationResponse.test.ts @@ -42,13 +42,23 @@ test('should verify FIDO U2F attestation', async () => { }); expect(verification.verified).toEqual(true); - expect(verification.authenticatorInfo?.fmt).toEqual('fido-u2f'); - expect(verification.authenticatorInfo?.counter).toEqual(0); - expect(verification.authenticatorInfo?.base64PublicKey).toEqual( - 'pQECAyYgASFYIMiRyw5pUoMhBjCrcQND6lJPaRHA0f-XWcKBb5ZwWk1eIlggFJu6aan4o7epl6qa9n9T-6KsIMvZE2PcTnLj8rN58is', + expect(verification.attestationInfo?.fmt).toEqual('fido-u2f'); + expect(verification.attestationInfo?.counter).toEqual(0); + expect(verification.attestationInfo?.credentialPublicKey).toEqual( + base64url.toBuffer( + 'pQECAyYgASFYIMiRyw5pUoMhBjCrcQND6lJPaRHA0f-XWcKBb5ZwWk1eIlggFJu6aan4o7epl6qa9n9T-6KsIMvZE2PcTnLj8rN58is', + ), ); - expect(verification.authenticatorInfo?.base64CredentialID).toEqual( - 'VHzbxaYaJu2P8m1Y2iHn2gRNHrgK0iYbn9E978L3Qi7Q-chFeicIHwYCRophz5lth2nCgEVKcgWirxlgidgbUQ', + expect(verification.attestationInfo?.credentialID).toEqual( + base64url.toBuffer( + 'VHzbxaYaJu2P8m1Y2iHn2gRNHrgK0iYbn9E978L3Qi7Q-chFeicIHwYCRophz5lth2nCgEVKcgWirxlgidgbUQ', + ), + ); + expect(verification.attestationInfo?.aaguid).toEqual('00000000-0000-0000-0000-000000000000'); + expect(verification.attestationInfo?.credentialType).toEqual('public-key'); + expect(verification.attestationInfo?.userVerified).toEqual(false); + expect(verification.attestationInfo?.attestationObject).toEqual( + base64url.toBuffer(attestationFIDOU2F.response.attestationObject), ); }); @@ -61,14 +71,18 @@ test('should verify Packed (EC2) attestation', async () => { }); expect(verification.verified).toEqual(true); - expect(verification.authenticatorInfo?.fmt).toEqual('packed'); - expect(verification.authenticatorInfo?.counter).toEqual(1589874425); - expect(verification.authenticatorInfo?.base64PublicKey).toEqual( - 'pQECAyYgASFYIEoxVVqK-oIGmqoDEyO4KjmMx5R2HeMM4LQQXh8sE01PIlggtzuuoMN5fWnAIuuXdlfshOGu1k3ApBUtDJ8eKiuo_6c', + expect(verification.attestationInfo?.fmt).toEqual('packed'); + expect(verification.attestationInfo?.counter).toEqual(1589874425); + expect(verification.attestationInfo?.credentialPublicKey).toEqual( + base64url.toBuffer( + 'pQECAyYgASFYIEoxVVqK-oIGmqoDEyO4KjmMx5R2HeMM4LQQXh8sE01PIlggtzuuoMN5fWnAIuuXdlfshOGu1k3ApBUtDJ8eKiuo_6c', + ), ); - expect(verification.authenticatorInfo?.base64CredentialID).toEqual( - 'AYThY1csINY4JrbHyGmqTl1nL_F1zjAF3hSAIngz8kAcjugmAMNVvxZRwqpEH-bNHHAIv291OX5ko9eDf_5mu3U' + - 'B2BvsScr2K-ppM4owOpGsqwg5tZglqqmxIm1Q', + expect(verification.attestationInfo?.credentialID).toEqual( + base64url.toBuffer( + 'AYThY1csINY4JrbHyGmqTl1nL_F1zjAF3hSAIngz8kAcjugmAMNVvxZRwqpEH-bNHHAIv291OX5ko9eDf_5mu3U' + + 'B2BvsScr2K-ppM4owOpGsqwg5tZglqqmxIm1Q', + ), ); }); @@ -81,13 +95,17 @@ test('should verify Packed (X5C) attestation', async () => { }); expect(verification.verified).toEqual(true); - expect(verification.authenticatorInfo?.fmt).toEqual('packed'); - expect(verification.authenticatorInfo?.counter).toEqual(28); - expect(verification.authenticatorInfo?.base64PublicKey).toEqual( - 'pQECAyYgASFYIGwlsYCNyRb4AD9cyTw6cH5VS-uzflmmO1UldGGe9eIaIlggvadzKD8p6wKLjgYfxRxldjCMGRV0YyM13osWbKIPrF8', + expect(verification.attestationInfo?.fmt).toEqual('packed'); + expect(verification.attestationInfo?.counter).toEqual(28); + expect(verification.attestationInfo?.credentialPublicKey).toEqual( + base64url.toBuffer( + 'pQECAyYgASFYIGwlsYCNyRb4AD9cyTw6cH5VS-uzflmmO1UldGGe9eIaIlggvadzKD8p6wKLjgYfxRxldjCMGRV0YyM13osWbKIPrF8', + ), ); - expect(verification.authenticatorInfo?.base64CredentialID).toEqual( - '4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56Tlg', + expect(verification.attestationInfo?.credentialID).toEqual( + base64url.toBuffer( + '4rrvMciHCkdLQ2HghazIp1sMc8TmV8W8RgoX-x8tqV_1AmlqWACqUK8mBGLandr-htduQKPzgb2yWxOFV56Tlg', + ), ); }); @@ -100,13 +118,17 @@ test('should verify None attestation', async () => { }); expect(verification.verified).toEqual(true); - expect(verification.authenticatorInfo?.fmt).toEqual('none'); - expect(verification.authenticatorInfo?.counter).toEqual(0); - expect(verification.authenticatorInfo?.base64PublicKey).toEqual( - 'pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow', + expect(verification.attestationInfo?.fmt).toEqual('none'); + expect(verification.attestationInfo?.counter).toEqual(0); + expect(verification.attestationInfo?.credentialPublicKey).toEqual( + base64url.toBuffer( + 'pQECAyYgASFYID5PQTZQQg6haZFQWFzqfAOyQ_ENsMH8xxQ4GRiNPsqrIlggU8IVUOV8qpgk_Jh-OTaLuZL52KdX1fTht07X4DiQPow', + ), ); - expect(verification.authenticatorInfo?.base64CredentialID).toEqual( - 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY', + expect(verification.attestationInfo?.credentialID).toEqual( + base64url.toBuffer( + 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY', + ), ); }); @@ -130,13 +152,15 @@ test('should verify None attestation w/RSA public key', async () => { }); expect(verification.verified).toEqual(true); - expect(verification.authenticatorInfo?.fmt).toEqual('none'); - expect(verification.authenticatorInfo?.counter).toEqual(0); - expect(verification.authenticatorInfo?.base64PublicKey).toEqual( - 'pAEDAzkBACBZAQDxfpXrj0ba_AH30JJ_-W7BHSOPugOD8aEDdNBKc1gjB9AmV3FPl2aL0fwiOMKtM_byI24qXb2FzcyjC7HUVkHRtzkAQnahXckI4wY_01koaY6iwXuIE3Ya0Zjs2iZyz6u4G_abGnWdObqa_kHxc3CHR7Xy5MDkAkKyX6TqU0tgHZcEhDd_Lb5ONJDwg4wvKlZBtZYElfMuZ6lonoRZ7qR_81rGkDZyFaxp6RlyvzEbo4ijeIaHQylqCz-oFm03ifZMOfRHYuF4uTjJDRH-g4BW1f3rdi7DTHk1hJnIw1IyL_VFIQ9NifkAguYjNCySCUNpYli2eMrPhAu5dYJFFjINIUMBAAE', + expect(verification.attestationInfo?.fmt).toEqual('none'); + expect(verification.attestationInfo?.counter).toEqual(0); + expect(verification.attestationInfo?.credentialPublicKey).toEqual( + base64url.toBuffer( + 'pAEDAzkBACBZAQDxfpXrj0ba_AH30JJ_-W7BHSOPugOD8aEDdNBKc1gjB9AmV3FPl2aL0fwiOMKtM_byI24qXb2FzcyjC7HUVkHRtzkAQnahXckI4wY_01koaY6iwXuIE3Ya0Zjs2iZyz6u4G_abGnWdObqa_kHxc3CHR7Xy5MDkAkKyX6TqU0tgHZcEhDd_Lb5ONJDwg4wvKlZBtZYElfMuZ6lonoRZ7qR_81rGkDZyFaxp6RlyvzEbo4ijeIaHQylqCz-oFm03ifZMOfRHYuF4uTjJDRH-g4BW1f3rdi7DTHk1hJnIw1IyL_VFIQ9NifkAguYjNCySCUNpYli2eMrPhAu5dYJFFjINIUMBAAE', + ), ); - expect(verification.authenticatorInfo?.base64CredentialID).toEqual( - 'kGXv4RJWLeXRw8Yf3T22K3Gq_GGeDv9OKYmAHLm0Ylo', + expect(verification.attestationInfo?.credentialID).toEqual( + base64url.toBuffer('kGXv4RJWLeXRw8Yf3T22K3Gq_GGeDv9OKYmAHLm0Ylo'), ); }); @@ -186,7 +210,9 @@ test('should throw when attestation type is not webauthn.create', async () => { test('should throw if an unexpected attestation format is specified', async () => { const fmt = 'fizzbuzz'; - const realAtteObj = decodeAttestationObject.default(attestationNone.response.attestationObject); + const realAtteObj = decodeAttestationObject.default( + base64url.toBuffer(attestationNone.response.attestationObject), + ); mockDecodeAttestation.mockReturnValue({ ...realAtteObj, @@ -205,7 +231,9 @@ test('should throw if an unexpected attestation format is specified', async () = }); test('should throw error if assertion RP ID is unexpected value', async () => { - const { authData } = decodeAttestationObject.default(attestationNone.response.attestationObject); + const { authData } = decodeAttestationObject.default( + base64url.toBuffer(attestationNone.response.attestationObject), + ); const actualAuthData = parseAuthenticatorData.default(authData); mockParseAuthData.mockReturnValue({ @@ -325,7 +353,7 @@ test('should not include authenticator info if not verified', async () => { }); expect(verification.verified).toBe(false); - expect(verification.authenticatorInfo).toBeUndefined(); + expect(verification.attestationInfo).toBeUndefined(); }); test('should throw an error if user verification is required but user was not verified', async () => { @@ -368,13 +396,15 @@ test('should validate TPM RSA response (SHA256)', async () => { }); expect(verification.verified).toEqual(true); - expect(verification.authenticatorInfo?.fmt).toEqual('tpm'); - expect(verification.authenticatorInfo?.counter).toEqual(30); - expect(verification.authenticatorInfo?.base64PublicKey).toEqual( - 'pAEDAzkBACBZAQCtxzw59Wsl8xWP97wPTu2TSDlushwshL8GedHAHO1R62m3nNy21hCLJlQabfLepRUQ_v9mq3PCmV81tBSqtRGU5_YlK0R2yeu756SnT39c6hKC3PBPt_xdjL_ccz4H_73DunfB63QZOtdeAsswV7WPLqMARofuM-LQ_LHnNguCypDcxhADuUqQtogfwZsknTVIPxzGcfqnQ7ERF9D9AOWIQ8YjOsTi_B2zS8SOySKIFUGwwYcPG7DiCE-QJcI-fpydRDnEq6UxbkYgB7XK4BlmPKlwuXkBDX9egl_Ma4B7W2WJvYbKevu6Z8Kc5y-OITpNVDYKbBK3qKyh4yIUpB1NIUMBAAE', + expect(verification.attestationInfo?.fmt).toEqual('tpm'); + expect(verification.attestationInfo?.counter).toEqual(30); + expect(verification.attestationInfo?.credentialPublicKey).toEqual( + base64url.toBuffer( + 'pAEDAzkBACBZAQCtxzw59Wsl8xWP97wPTu2TSDlushwshL8GedHAHO1R62m3nNy21hCLJlQabfLepRUQ_v9mq3PCmV81tBSqtRGU5_YlK0R2yeu756SnT39c6hKC3PBPt_xdjL_ccz4H_73DunfB63QZOtdeAsswV7WPLqMARofuM-LQ_LHnNguCypDcxhADuUqQtogfwZsknTVIPxzGcfqnQ7ERF9D9AOWIQ8YjOsTi_B2zS8SOySKIFUGwwYcPG7DiCE-QJcI-fpydRDnEq6UxbkYgB7XK4BlmPKlwuXkBDX9egl_Ma4B7W2WJvYbKevu6Z8Kc5y-OITpNVDYKbBK3qKyh4yIUpB1NIUMBAAE', + ), ); - expect(verification.authenticatorInfo?.base64CredentialID).toEqual( - 'lGkWHPe88VpnNYgVBxzon_MRR9-gmgODveQ16uM_bPM', + expect(verification.attestationInfo?.credentialID).toEqual( + base64url.toBuffer('lGkWHPe88VpnNYgVBxzon_MRR9-gmgODveQ16uM_bPM'), ); }); @@ -398,13 +428,15 @@ test('should validate TPM RSA response (SHA1)', async () => { }); expect(verification.verified).toEqual(true); - expect(verification.authenticatorInfo?.fmt).toEqual('tpm'); - expect(verification.authenticatorInfo?.counter).toEqual(97); - expect(verification.authenticatorInfo?.base64PublicKey).toEqual( - 'pAEDAzn__iBZAQCzl_wD24PZ5z-po2FrwoQVdd13got_CkL8p4B_NvJBC5OwAYKDilii_wj-0CA8ManbpSInx9Tdnz6t91OhudwUT0-W_BHSLK_MqFcjZWrR5LYVmVpz1EgH3DrOTra4AlogEq2D2CYktPrPe7joE-oT3vAYXK8vzQDLRyaxI_Z1qS4KLlLCdajW8PGpw1YRjMDw6s69GZU8mXkgNPMCUh1TZ1bnCvJTO9fnmLjDjqdQGRU4bWo8tFjCL8g1-2WD_2n0-twt6n-Uox5VnR1dQJG4awMlanBCkGGpOb3WBDQ8K10YJJ2evPhJKGJahBvu2Dxmq6pLCAXCv0ma3EHj-PmDIUMBAAE', + expect(verification.attestationInfo?.fmt).toEqual('tpm'); + expect(verification.attestationInfo?.counter).toEqual(97); + expect(verification.attestationInfo?.credentialPublicKey).toEqual( + base64url.toBuffer( + 'pAEDAzn__iBZAQCzl_wD24PZ5z-po2FrwoQVdd13got_CkL8p4B_NvJBC5OwAYKDilii_wj-0CA8ManbpSInx9Tdnz6t91OhudwUT0-W_BHSLK_MqFcjZWrR5LYVmVpz1EgH3DrOTra4AlogEq2D2CYktPrPe7joE-oT3vAYXK8vzQDLRyaxI_Z1qS4KLlLCdajW8PGpw1YRjMDw6s69GZU8mXkgNPMCUh1TZ1bnCvJTO9fnmLjDjqdQGRU4bWo8tFjCL8g1-2WD_2n0-twt6n-Uox5VnR1dQJG4awMlanBCkGGpOb3WBDQ8K10YJJ2evPhJKGJahBvu2Dxmq6pLCAXCv0ma3EHj-PmDIUMBAAE', + ), ); - expect(verification.authenticatorInfo?.base64CredentialID).toEqual( - 'oELnad0f6-g2BtzEn_78iLNoubarlq0xFtOtAMXnflU', + expect(verification.attestationInfo?.credentialID).toEqual( + base64url.toBuffer('oELnad0f6-g2BtzEn_78iLNoubarlq0xFtOtAMXnflU'), ); }); @@ -428,13 +460,15 @@ test('should validate Android-Key response', async () => { }); expect(verification.verified).toEqual(true); - expect(verification.authenticatorInfo?.fmt).toEqual('android-key'); - expect(verification.authenticatorInfo?.counter).toEqual(108); - expect(verification.authenticatorInfo?.base64PublicKey).toEqual( - 'pQECAyYgASFYIEjCq7woGNN_42rbaqMgJvz0nuKTWNRrR29lMX3J239oIlgg6IcAXqPJPIjSrClHDAmbJv_EShYhYq0R9-G3k744n7Y', + expect(verification.attestationInfo?.fmt).toEqual('android-key'); + expect(verification.attestationInfo?.counter).toEqual(108); + expect(verification.attestationInfo?.credentialPublicKey).toEqual( + base64url.toBuffer( + 'pQECAyYgASFYIEjCq7woGNN_42rbaqMgJvz0nuKTWNRrR29lMX3J239oIlgg6IcAXqPJPIjSrClHDAmbJv_EShYhYq0R9-G3k744n7Y', + ), ); - expect(verification.authenticatorInfo?.base64CredentialID).toEqual( - 'PPa1spYTB680cQq5q6qBtFuPLLdG1FQ73EastkT8n0o', + expect(verification.attestationInfo?.credentialID).toEqual( + base64url.toBuffer('PPa1spYTB680cQq5q6qBtFuPLLdG1FQ73EastkT8n0o'), ); }); diff --git a/packages/server/src/attestation/verifyAttestationResponse.ts b/packages/server/src/attestation/verifyAttestationResponse.ts index 3940ea6..023c8f2 100644 --- a/packages/server/src/attestation/verifyAttestationResponse.ts +++ b/packages/server/src/attestation/verifyAttestationResponse.ts @@ -4,12 +4,13 @@ import { COSEAlgorithmIdentifier, } from '@simplewebauthn/typescript-types'; -import decodeAttestationObject, { ATTESTATION_FORMATS } from '../helpers/decodeAttestationObject'; +import decodeAttestationObject, { ATTESTATION_FORMAT } from '../helpers/decodeAttestationObject'; import decodeClientDataJSON from '../helpers/decodeClientDataJSON'; import parseAuthenticatorData from '../helpers/parseAuthenticatorData'; import toHash from '../helpers/toHash'; import decodeCredentialPublicKey from '../helpers/decodeCredentialPublicKey'; import { COSEKEYS } from '../helpers/convertCOSEtoPKCS'; +import convertAAGUIDToString from '../helpers/convertAAGUIDToString'; import { supportedCOSEAlgorithmIdentifiers } from './generateAttestationOptions'; import verifyFIDOU2F from './verifications/verifyFIDOU2F'; @@ -110,8 +111,9 @@ export default async function verifyAttestationResponse( } } - const attestationObject = decodeAttestationObject(response.attestationObject); - const { fmt, authData, attStmt } = attestationObject; + const attestationObject = base64url.toBuffer(response.attestationObject); + const decodedAttestationObject = decodeAttestationObject(attestationObject); + const { fmt, authData, attStmt } = decodedAttestationObject; const parsedAuthData = parseAuthenticatorData(authData); const { aaguid, rpIdHash, flags, credentialID, counter, credentialPublicKey } = parsedAuthData; @@ -177,7 +179,7 @@ export default async function verifyAttestationResponse( * Verification can only be performed when attestation = 'direct' */ let verified = false; - if (fmt === ATTESTATION_FORMATS.FIDO_U2F) { + if (fmt === ATTESTATION_FORMAT.FIDO_U2F) { verified = verifyFIDOU2F({ attStmt, clientDataHash, @@ -186,7 +188,7 @@ export default async function verifyAttestationResponse( rpIdHash, aaguid, }); - } else if (fmt === ATTESTATION_FORMATS.PACKED) { + } else if (fmt === ATTESTATION_FORMAT.PACKED) { verified = await verifyPacked({ attStmt, authData, @@ -194,14 +196,14 @@ export default async function verifyAttestationResponse( credentialPublicKey, aaguid, }); - } else if (fmt === ATTESTATION_FORMATS.ANDROID_SAFETYNET) { + } else if (fmt === ATTESTATION_FORMAT.ANDROID_SAFETYNET) { verified = await verifyAndroidSafetynet({ attStmt, authData, clientDataHash, aaguid, }); - } else if (fmt === ATTESTATION_FORMATS.ANDROID_KEY) { + } else if (fmt === ATTESTATION_FORMAT.ANDROID_KEY) { verified = await verifyAndroidKey({ attStmt, authData, @@ -209,7 +211,7 @@ export default async function verifyAttestationResponse( credentialPublicKey, aaguid, }); - } else if (fmt === ATTESTATION_FORMATS.TPM) { + } else if (fmt === ATTESTATION_FORMAT.TPM) { verified = await verifyTPM({ aaguid, attStmt, @@ -217,14 +219,14 @@ export default async function verifyAttestationResponse( credentialPublicKey, clientDataHash, }); - } else if (fmt === ATTESTATION_FORMATS.APPLE) { + } else if (fmt === ATTESTATION_FORMAT.APPLE) { verified = await verifyApple({ attStmt, authData, clientDataHash, credentialPublicKey, }); - } else if (fmt === ATTESTATION_FORMATS.NONE) { + } else if (fmt === ATTESTATION_FORMAT.NONE) { if (Object.keys(attStmt).length > 0) { throw new Error('None attestation had unexpected attestation statement'); } @@ -236,17 +238,18 @@ export default async function verifyAttestationResponse( const toReturn: VerifiedAttestation = { verified, - userVerified: flags.uv, }; if (toReturn.verified) { - toReturn.userVerified = flags.uv; - - toReturn.authenticatorInfo = { + toReturn.attestationInfo = { fmt, counter, - base64PublicKey: base64url.encode(credentialPublicKey), - base64CredentialID: base64url.encode(credentialID), + aaguid: convertAAGUIDToString(aaguid), + credentialPublicKey, + credentialID, + credentialType, + userVerified: flags.uv, + attestationObject, }; } @@ -257,23 +260,28 @@ export default async function verifyAttestationResponse( * Result of attestation verification * * @param verified If the assertion response could be verified - * @param userVerified Whether the user was uniquely identified during attestation - * @param authenticatorInfo.fmt Type of attestation - * @param authenticatorInfo.counter The number of times the authenticator reported it has been used. + * @param attestationInfo.fmt Type of attestation + * @param attestationInfo.counter The number of times the authenticator reported it has been used. * Should be kept in a DB for later reference to help prevent replay attacks - * @param authenticatorInfo.base64PublicKey Base64URL-encoded ArrayBuffer containing the - * authenticator's public key. **Should be kept in a DB for later reference!** - * @param authenticatorInfo.base64CredentialID Base64URL-encoded ArrayBuffer containing the - * authenticator's credential ID for the public key above. **Should be kept in a DB for later - * reference!** + * @param attestationInfo.aaguid Authenticator's Attestation GUID indicating the type of the + * authenticator + * @param attestationInfo.credentialPublicKey The credential's public key + * @param attestationInfo.credentialID The credential's credential ID for the public key above + * @param attestationInfo.credentialType The type of the credential returned by the browser + * @param attestationInfo.userVerified Whether the user was uniquely identified during attestation + * @param attestationInfo.attestationObject The raw `response.attestationObject` Buffer returned by + * the authenticator */ export type VerifiedAttestation = { verified: boolean; - userVerified: boolean; - authenticatorInfo?: { - fmt: ATTESTATION_FORMATS; + attestationInfo?: { + fmt: ATTESTATION_FORMAT; counter: number; - base64PublicKey: string; - base64CredentialID: string; + aaguid: string; + credentialPublicKey: Buffer; + credentialID: Buffer; + credentialType: string; + userVerified: boolean; + attestationObject: Buffer; }; }; diff --git a/packages/server/src/helpers/convertPublicKeyToPEM.ts b/packages/server/src/helpers/convertPublicKeyToPEM.ts index 834f96c..9be1e0a 100644 --- a/packages/server/src/helpers/convertPublicKeyToPEM.ts +++ b/packages/server/src/helpers/convertPublicKeyToPEM.ts @@ -1,15 +1,12 @@ import cbor from 'cbor'; import jwkToPem from 'jwk-to-pem'; -import base64url from 'base64url'; import { COSEKEYS, COSEKTY, COSECRV } from './convertCOSEtoPKCS'; -export default function convertPublicKeyToPEM(publicKey: string): string { - const publicKeyBuffer = base64url.toBuffer(publicKey); - +export default function convertPublicKeyToPEM(publicKey: Buffer): string { let struct; try { - struct = cbor.decodeAllSync(publicKeyBuffer)[0]; + struct = cbor.decodeAllSync(publicKey)[0]; } catch (err) { throw new Error(`Error decoding public key while converting to PEM: ${err.message}`); } diff --git a/packages/server/src/helpers/decodeAttestationObject.test.ts b/packages/server/src/helpers/decodeAttestationObject.test.ts index 2f88f2a..6ba29e9 100644 --- a/packages/server/src/helpers/decodeAttestationObject.test.ts +++ b/packages/server/src/helpers/decodeAttestationObject.test.ts @@ -2,10 +2,13 @@ import decodeAttestationObject from './decodeAttestationObject'; test('should decode base64url-encoded indirect attestationObject', () => { const decoded = decodeAttestationObject( - 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEAbElFazplpnc037DORGDZNjDq86cN9vm6' + - '+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQKmPuEwByQJ3e89TccUSrCGDkNWquhevjLLn/' + - 'KNZZaxQQ0steueoG2g12dvnUNbiso8kVJDyLa+6UiA34eniujWlAQIDJiABIVggiUk8wN2j' + - '+3fkKI7KSiLBkKzs3FfhPZxHgHPnGLvOY/YiWCBv7+XyTqArnMVtQ947/8Xk8fnVCdLMRWJGM1VbNevVcQ==', + Buffer.from( + 'o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjEAbElFazplpnc037DORGDZNjDq86cN9vm6' + + '+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQKmPuEwByQJ3e89TccUSrCGDkNWquhevjLLn/' + + 'KNZZaxQQ0steueoG2g12dvnUNbiso8kVJDyLa+6UiA34eniujWlAQIDJiABIVggiUk8wN2j' + + '+3fkKI7KSiLBkKzs3FfhPZxHgHPnGLvOY/YiWCBv7+XyTqArnMVtQ947/8Xk8fnVCdLMRWJGM1VbNevVcQ==', + 'base64', + ), ); expect(decoded.fmt).toEqual('none'); @@ -15,21 +18,24 @@ test('should decode base64url-encoded indirect attestationObject', () => { test('should decode base64url-encoded direct attestationObject', () => { const decoded = decodeAttestationObject( - 'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAK40WxA0t7py7AjEXvwGwTlmqlvrOk' + - 's5g9lf+9zXzRiVAiEA3bv60xyXveKDOusYzniD7CDSostCet9PYK7FLdnTdZNjeDVjgVkCwTCCAr0wggGloAMCAQICBCrn' + - 'YmMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMT' + - 'QwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNV' + - 'BAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1YmljbyBVMkYgRUUgU2VyaWFsIDcxOTgwNzA3NT' + - 'BZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCoDhl5gQ9meEf8QqiVUV4S/Ca+Oax47MhcpIW9VEhqM2RDTmd3HaL3+SnvH' + - '49q8YubSRp/1Z1uP+okMynSGnj+jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBgu' + - 'UcAgEBBAQDAgQwMCEGCysGAQQBguUcAQEEBBIEEG1Eupv27C5JuTAMj+kgy3MwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B' + - 'AQsFAAOCAQEAclfQPNzD4RVphJDW+A75W1MHI3PZ5kcyYysR3Nx3iuxr1ZJtB+F7nFQweI3jL05HtFh2/4xVIgKb6Th4eV' + - 'cjMecncBaCinEbOcdP1sEli9Hk2eVm1XB5A0faUjXAPw/+QLFCjgXG6ReZ5HVUcWkB7riLsFeJNYitiKrTDXFPLy+sNtVN' + - 'utcQnFsCerDKuM81TvEAigkIbKCGlq8M/NvBg5j83wIxbCYiyV7mIr3RwApHieShzLdJo1S6XydgQjC+/64G5r8C+8AVvN' + - 'FR3zXXCpio5C3KRIj88HEEIYjf6h1fdLfqeIsq+cUUqbq5T+c4nNoZUZCysTB9v5EY4akp+GhhdXRoRGF0YVjEAbElFazp' + - 'lpnc037DORGDZNjDq86cN9vm6+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQGFYevaR71ptU5YtXOSnVzPQTsGgK+' + - 'gLiBKnqPWBmZXNRvjISqlLxiwApzlrfkTc3lEMYMatjeACCnsijOkNEGOlAQIDJiABIVggdWLG6UvGyHFw/k/bv6/k6z/L' + - 'LgSO5KXzXw2EcUxkEX8iWCBeaVLz/cbyoKvRIg/q+q7tan0VN+i3WR0BOBCcuNP7yw==', + Buffer.from( + 'o2NmbXRoZmlkby11MmZnYXR0U3RtdKJjc2lnWEgwRgIhAK40WxA0t7py7AjEXvwGwTlmqlvrOk' + + 's5g9lf+9zXzRiVAiEA3bv60xyXveKDOusYzniD7CDSostCet9PYK7FLdnTdZNjeDVjgVkCwTCCAr0wggGloAMCAQICBCrn' + + 'YmMwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMT' + + 'QwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNV' + + 'BAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1YmljbyBVMkYgRUUgU2VyaWFsIDcxOTgwNzA3NT' + + 'BZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCoDhl5gQ9meEf8QqiVUV4S/Ca+Oax47MhcpIW9VEhqM2RDTmd3HaL3+SnvH' + + '49q8YubSRp/1Z1uP+okMynSGnj+jbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS4xMBMGCysGAQQBgu' + + 'UcAgEBBAQDAgQwMCEGCysGAQQBguUcAQEEBBIEEG1Eupv27C5JuTAMj+kgy3MwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B' + + 'AQsFAAOCAQEAclfQPNzD4RVphJDW+A75W1MHI3PZ5kcyYysR3Nx3iuxr1ZJtB+F7nFQweI3jL05HtFh2/4xVIgKb6Th4eV' + + 'cjMecncBaCinEbOcdP1sEli9Hk2eVm1XB5A0faUjXAPw/+QLFCjgXG6ReZ5HVUcWkB7riLsFeJNYitiKrTDXFPLy+sNtVN' + + 'utcQnFsCerDKuM81TvEAigkIbKCGlq8M/NvBg5j83wIxbCYiyV7mIr3RwApHieShzLdJo1S6XydgQjC+/64G5r8C+8AVvN' + + 'FR3zXXCpio5C3KRIj88HEEIYjf6h1fdLfqeIsq+cUUqbq5T+c4nNoZUZCysTB9v5EY4akp+GhhdXRoRGF0YVjEAbElFazp' + + 'lpnc037DORGDZNjDq86cN9vm6+APoAM20wtBAAAAAAAAAAAAAAAAAAAAAAAAAAAAQGFYevaR71ptU5YtXOSnVzPQTsGgK+' + + 'gLiBKnqPWBmZXNRvjISqlLxiwApzlrfkTc3lEMYMatjeACCnsijOkNEGOlAQIDJiABIVggdWLG6UvGyHFw/k/bv6/k6z/L' + + 'LgSO5KXzXw2EcUxkEX8iWCBeaVLz/cbyoKvRIg/q+q7tan0VN+i3WR0BOBCcuNP7yw==', + 'base64', + ), ); expect(decoded.fmt).toEqual('fido-u2f'); diff --git a/packages/server/src/helpers/decodeAttestationObject.ts b/packages/server/src/helpers/decodeAttestationObject.ts index 09d95fb..362e8a0 100644 --- a/packages/server/src/helpers/decodeAttestationObject.ts +++ b/packages/server/src/helpers/decodeAttestationObject.ts @@ -6,15 +6,12 @@ import cbor from 'cbor'; * * @param base64AttestationObject Base64URL-encoded Attestation Object */ -export default function decodeAttestationObject( - base64AttestationObject: string, -): AttestationObject { - const toBuffer = base64url.toBuffer(base64AttestationObject); - const toCBOR: AttestationObject = cbor.decodeAllSync(toBuffer)[0]; +export default function decodeAttestationObject(attestationObject: Buffer): AttestationObject { + const toCBOR: AttestationObject = cbor.decodeAllSync(attestationObject)[0]; return toCBOR; } -export enum ATTESTATION_FORMATS { +export enum ATTESTATION_FORMAT { FIDO_U2F = 'fido-u2f', PACKED = 'packed', ANDROID_SAFETYNET = 'android-safetynet', @@ -25,7 +22,7 @@ export enum ATTESTATION_FORMATS { } export type AttestationObject = { - fmt: ATTESTATION_FORMATS; + fmt: ATTESTATION_FORMAT; attStmt: AttestationStatement; authData: Buffer; }; diff --git a/packages/server/src/metadata/metadataService.ts b/packages/server/src/metadata/metadataService.ts index 56163e8..dc48f28 100644 --- a/packages/server/src/metadata/metadataService.ts +++ b/packages/server/src/metadata/metadataService.ts @@ -8,6 +8,8 @@ import toHash from '../helpers/toHash'; import validateCertificatePath from '../helpers/validateCertificatePath'; import convertX509CertToPEM from '../helpers/convertX509CertToPEM'; import convertAAGUIDToString from '../helpers/convertAAGUIDToString'; +// TODO: Re-enable this once we figure out logging +// import { log } from '../helpers/logging'; import parseJWT from './parseJWT'; @@ -63,7 +65,7 @@ class MetadataService { const { mdsServers, statements } = opts; - this.state = SERVICE_STATE.REFRESHING; + this.setState(SERVICE_STATE.REFRESHING); // If metadata statements are provided, load them into the cache first if (statements?.length) { @@ -86,6 +88,9 @@ class MetadataService { // If MDS servers are provided, then process them and add their statements to the cache if (mdsServers?.length) { + // TODO: Re-enable this once we figure out logging + // const currentCacheCount = Object.keys(this.statementCache).length; + for (const server of mdsServers) { try { await this.downloadTOC({ @@ -98,11 +103,18 @@ class MetadataService { }); } catch (err) { // Notify of the error and move on + // TODO: Re-enable this once we figure out logging + // log('warning', `Could not download TOC from ${server.url}:`, err); } } + + // TODO: Re-enable this once we figure out logging + // const newCacheCount = Object.keys(this.statementCache).length; + // const cacheDiff = newCacheCount - currentCacheCount; + // log('info', `Downloaded ${cacheDiff} statements from ${mdsServers.length} metadata servers`); } - this.state = SERVICE_STATE.READY; + this.setState(SERVICE_STATE.READY); } /** @@ -142,10 +154,10 @@ class MetadataService { const now = new Date(); if (now > mds.nextUpdate) { try { - this.state = SERVICE_STATE.REFRESHING; + this.setState(SERVICE_STATE.REFRESHING); await this.downloadTOC(mds); } finally { - this.state = SERVICE_STATE.READY; + this.setState(SERVICE_STATE.READY); } } } @@ -309,6 +321,24 @@ class MetadataService { return readyPromise; } + + /** + * Report service status on change + */ + private setState(newState: SERVICE_STATE) { + this.state = newState; + + if (newState === SERVICE_STATE.DISABLED) { + // TODO: Re-enable this once we figure out logging + // log('MetadataService is DISABLED'); + } else if (newState === SERVICE_STATE.REFRESHING) { + // TODO: Re-enable this once we figure out logging + // log('MetadataService is REFRESHING'); + } else if (newState === SERVICE_STATE.READY) { + // TODO: Re-enable this once we figure out logging + // log('MetadataService is READY'); + } + } } const metadataService = new MetadataService(); diff --git a/packages/typescript-types/src/index.ts b/packages/typescript-types/src/index.ts index c950398..8ba2297 100644 --- a/packages/typescript-types/src/index.ts +++ b/packages/typescript-types/src/index.ts @@ -112,9 +112,9 @@ export interface AuthenticatorAssertionResponseJSON * A WebAuthn-compatible device and the information needed to verify assertions by it */ export type AuthenticatorDevice = { - publicKey: Base64URLString; - credentialID: Base64URLString; - // Number of times this device is expected to have been used + credentialPublicKey: Buffer; + credentialID: Buffer; + // Number of times this authenticator is expected to have been used counter: number; // From browser's `startAttestation()` -> AttestationCredentialJSON.transports (API L2 and up) transports?: AuthenticatorTransport[]; |