diff options
author | Corentin Mors <corentin@dashlane.com> | 2023-07-20 09:30:10 +0200 |
---|---|---|
committer | Corentin Mors <corentin@dashlane.com> | 2023-07-20 16:43:01 +0200 |
commit | 57f58b87892fe01ba62f78be1b9ac219decd854c (patch) | |
tree | 518533a748005bebe93f7d7d477ce61b655680ba /packages/server/src | |
parent | 79f89b85ba19429c2f4974bc012071aa6f542c55 (diff) |
Add matched RPID to verify response
Diffstat (limited to 'packages/server/src')
5 files changed, 41 insertions, 6 deletions
diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts index 5547224..c996991 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts @@ -45,6 +45,7 @@ test('should return authenticator info after verification', async () => { expect(verification.authenticationInfo.newCounter).toEqual(144); expect(verification.authenticationInfo.credentialID).toEqual(authenticator.credentialID); expect(verification.authenticationInfo?.origin).toEqual(assertionOrigin); + expect(verification.authenticationInfo?.rpID).toEqual('dev.dontneeda.pw'); }); test('should throw when response challenge is not expected value', async () => { @@ -226,6 +227,7 @@ test('should support multiple possible origins', async () => { expect(verification.verified).toEqual(true); expect(verification.authenticationInfo?.origin).toEqual(assertionOrigin); + expect(verification.authenticationInfo?.rpID).toEqual('dev.dontneeda.pw'); }); test('should throw an error if origin not in list of expected origins', async () => { @@ -375,6 +377,7 @@ test('should return credential backup info', async () => { expect(verification.authenticationInfo?.credentialDeviceType).toEqual('singleDevice'); expect(verification.authenticationInfo?.credentialBackedUp).toEqual(false); expect(verification.authenticationInfo?.origin).toEqual(assertionOrigin); + expect(verification.authenticationInfo?.rpID).toEqual('dev.dontneeda.pw'); }); /** diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts index bfc5bf5..c9f23ca 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts @@ -154,7 +154,7 @@ export async function verifyAuthenticationResponse( expectedRPIDs = expectedRPID; } - await matchExpectedRPID(rpIdHash, expectedRPIDs); + const matchedRPID = await matchExpectedRPID(rpIdHash, expectedRPIDs); if (advancedFIDOConfig !== undefined) { const { userVerification: fidoUserVerification } = advancedFIDOConfig; @@ -216,6 +216,7 @@ export async function verifyAuthenticationResponse( credentialBackedUp, authenticatorExtensionResults: extensionsData, origin: clientDataJSON.origin, + rpID: matchedRPID, }, }; @@ -238,6 +239,7 @@ export async function verifyAuthenticationResponse( * backed up. Always `false` for single-device credentials. **Should be kept in a DB for later * reference!** * @param authenticationInfo.origin The origin of the website that the authentication occurred on + * @param authenticationInfo.rpID The RP ID that the authentication occurred on * @param authenticationInfo?.authenticatorExtensionResults The authenticator extensions returned * by the browser */ @@ -250,6 +252,7 @@ export type VerifiedAuthenticationResponse = { credentialDeviceType: CredentialDeviceType; credentialBackedUp: boolean; origin: string; + rpID: string; authenticatorExtensionResults?: AuthenticationExtensionsAuthenticatorOutputs; }; }; diff --git a/packages/server/src/helpers/matchExpectedRPID.ts b/packages/server/src/helpers/matchExpectedRPID.ts index be49fc2..c08c223 100644 --- a/packages/server/src/helpers/matchExpectedRPID.ts +++ b/packages/server/src/helpers/matchExpectedRPID.ts @@ -2,19 +2,22 @@ import { toHash } from './toHash'; import { isoUint8Array } from './iso'; /** - * Go through each expected RP ID and try to find one that matches. Raises an Error if no + * Go through each expected RP ID and try to find one that matches. Returns the unhashed RP ID + * that matched the hash in the response. + * + * Raises an `UnexpectedRPIDHash` error if no match is found */ export async function matchExpectedRPID( rpIDHash: Uint8Array, expectedRPIDs: string[], -): Promise<void> { +): Promise<string> { try { - await Promise.any( + const matchedRPID = await Promise.any<string>( expectedRPIDs.map(expected => { return new Promise((resolve, reject) => { toHash(isoUint8Array.fromASCIIString(expected)).then(expectedRPIDHash => { if (isoUint8Array.areEqual(rpIDHash, expectedRPIDHash)) { - resolve(true); + resolve(expected); } else { reject(); } @@ -22,6 +25,8 @@ export async function matchExpectedRPID( }); }), ); + + return matchedRPID; } catch (err) { const _err = err as Error; diff --git a/packages/server/src/registration/verifyRegistrationResponse.test.ts b/packages/server/src/registration/verifyRegistrationResponse.test.ts index 2b973e5..e6acd2a 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.test.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.test.ts @@ -70,6 +70,7 @@ test('should verify FIDO U2F attestation', async () => { isoBase64URL.toBuffer(attestationFIDOU2F.response.attestationObject), ); expect(verification.registrationInfo?.origin).toEqual('https://dev.dontneeda.pw'); + expect(verification.registrationInfo?.rpID).toEqual('dev.dontneeda.pw'); }); test('should verify Packed (EC2) attestation', async () => { @@ -177,6 +178,7 @@ test('should verify None attestation w/RSA public key', async () => { isoBase64URL.toBuffer('kGXv4RJWLeXRw8Yf3T22K3Gq_GGeDv9OKYmAHLm0Ylo'), ); expect(verification.registrationInfo?.origin).toEqual('https://dev.dontneeda.pw'); + expect(verification.registrationInfo?.rpID).toEqual('dev.dontneeda.pw'); }); test('should throw when response challenge is not expected value', async () => { @@ -419,6 +421,7 @@ test('should validate TPM RSA response (SHA256)', async () => { isoBase64URL.toBuffer('lGkWHPe88VpnNYgVBxzon_MRR9-gmgODveQ16uM_bPM'), ); expect(verification.registrationInfo?.origin).toEqual('https://dev.dontneeda.pw'); + expect(verification.registrationInfo?.rpID).toEqual('dev.dontneeda.pw'); }); test('should validate TPM RSA response (SHA1)', async () => { @@ -455,6 +458,7 @@ test('should validate TPM RSA response (SHA1)', async () => { isoBase64URL.toBuffer('oELnad0f6-g2BtzEn_78iLNoubarlq0xFtOtAMXnflU'), ); expect(verification.registrationInfo?.origin).toEqual('https://dev.dontneeda.pw'); + expect(verification.registrationInfo?.rpID).toEqual('dev.dontneeda.pw'); }); test('should validate Android-Key response', async () => { @@ -491,6 +495,7 @@ test('should validate Android-Key response', async () => { isoBase64URL.toBuffer('PPa1spYTB680cQq5q6qBtFuPLLdG1FQ73EastkT8n0o'), ); expect(verification.registrationInfo?.origin).toEqual('https://dev.dontneeda.pw'); + expect(verification.registrationInfo?.rpID).toEqual('dev.dontneeda.pw'); }); test('should support multiple possible origins', async () => { @@ -503,6 +508,20 @@ test('should support multiple possible origins', async () => { expect(verification.verified).toBe(true); expect(verification.registrationInfo?.origin).toEqual('https://dev.dontneeda.pw'); + expect(verification.registrationInfo?.rpID).toEqual('dev.dontneeda.pw'); +}); + +test('should not set RPID in registrationInfo when not expected', async () => { + const verification = await verifyRegistrationResponse({ + response: attestationNone, + expectedChallenge: attestationNoneChallenge, + expectedOrigin: 'https://dev.dontneeda.pw', + expectedRPID: undefined, + }); + + expect(verification.verified).toBe(true); + expect(verification.registrationInfo?.origin).toEqual('https://dev.dontneeda.pw'); + expect(verification.registrationInfo?.rpID).toBeUndefined(); }); test('should throw an error if origin not in list of expected origins', async () => { diff --git a/packages/server/src/registration/verifyRegistrationResponse.ts b/packages/server/src/registration/verifyRegistrationResponse.ts index 5a52f6a..d33cdea 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.ts @@ -141,6 +141,7 @@ export async function verifyRegistrationResponse( parsedAuthData; // Make sure the response's RP ID is ours + let matchedRPID: string | undefined; if (expectedRPID) { let expectedRPIDs: string[] = []; if (typeof expectedRPID === 'string') { @@ -149,7 +150,7 @@ export async function verifyRegistrationResponse( expectedRPIDs = expectedRPID; } - await matchExpectedRPID(rpIdHash, expectedRPIDs); + matchedRPID = await matchExpectedRPID(rpIdHash, expectedRPIDs); } // Make sure someone was physically present @@ -247,6 +248,7 @@ export async function verifyRegistrationResponse( credentialDeviceType, credentialBackedUp, origin: clientDataJSON.origin, + rpID: matchedRPID, authenticatorExtensionResults: extensionsData, }; } @@ -275,6 +277,8 @@ export async function verifyRegistrationResponse( * backed up. Always `false` for single-device credentials. **Should be kept in a DB for later * reference!** * @param registrationInfo.origin The origin of the website that the registration occurred on + * @param registrationInfo?.rpID The RP ID that the registration occurred on, if one or more were + * specified in the registration options * @param registrationInfo?.authenticatorExtensionResults The authenticator extensions returned * by the browser */ @@ -292,6 +296,7 @@ export type VerifiedRegistrationResponse = { credentialDeviceType: CredentialDeviceType; credentialBackedUp: boolean; origin: string; + rpID?: string; authenticatorExtensionResults?: AuthenticationExtensionsAuthenticatorOutputs; }; }; |