diff options
author | Matthew Miller <matthew@millerti.me> | 2023-07-27 08:42:05 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-27 08:42:05 -0700 |
commit | f30a195ab1c5b7476fb8056e6ac25a13a7546452 (patch) | |
tree | c2779d080e51bea1463effd581752cd245b9940a /packages/server/src | |
parent | 5731c7d0427323bbd78df195295c28eb39f822ba (diff) | |
parent | 22ca0cd6dab119a28794dbaa33d650f97ab107a1 (diff) |
Merge pull request #415 from Mikescops/feature/add-origin-authentication-registration-info
Add origin and rpID in authentication and registration info
Diffstat (limited to 'packages/server/src')
5 files changed, 54 insertions, 6 deletions
diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts index 30eb9d1..5a760e4 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts @@ -44,6 +44,8 @@ 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 () => { @@ -224,6 +226,7 @@ test('should support multiple possible origins', async () => { }); expect(verification.verified).toEqual(true); + expect(verification.authenticationInfo?.origin).toEqual(assertionOrigin); }); test('should throw an error if origin not in list of expected origins', async () => { @@ -249,6 +252,7 @@ test('should support multiple possible RP IDs', async () => { }); expect(verification.verified).toEqual(true); + expect(verification.authenticationInfo?.rpID).toEqual('dev.dontneeda.pw'); }); test('should throw an error if RP ID not in list of possible RP IDs', async () => { diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts index d95bca5..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; @@ -215,6 +215,8 @@ export async function verifyAuthenticationResponse( credentialDeviceType, credentialBackedUp, authenticatorExtensionResults: extensionsData, + origin: clientDataJSON.origin, + rpID: matchedRPID, }, }; @@ -236,6 +238,8 @@ export async function verifyAuthenticationResponse( * @param authenticationInfo.credentialBackedUp Whether or not the multi-device credential has been * 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 */ @@ -247,6 +251,8 @@ export type VerifiedAuthenticationResponse = { userVerified: boolean; 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 9fd8a96..7f89857 100644 --- a/packages/server/src/registration/verifyRegistrationResponse.test.ts +++ b/packages/server/src/registration/verifyRegistrationResponse.test.ts @@ -69,6 +69,8 @@ test('should verify FIDO U2F attestation', async () => { expect(verification.registrationInfo?.attestationObject).toEqual( 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 () => { @@ -140,6 +142,7 @@ test('should verify None attestation', async () => { 'AdKXJEch1aV5Wo7bj7qLHskVY4OoNaj9qu8TPdJ7kSAgUeRxWNngXlcNIGt4gexZGKVGcqZpqqWordXb_he1izY', ), ); + expect(verification.registrationInfo?.origin).toEqual('https://dev.dontneeda.pw'); }); test('should verify None attestation w/RSA public key', async () => { @@ -174,6 +177,8 @@ test('should verify None attestation w/RSA public key', async () => { expect(verification.registrationInfo?.credentialID).toEqual( 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 () => { @@ -415,6 +420,8 @@ test('should validate TPM RSA response (SHA256)', async () => { expect(verification.registrationInfo?.credentialID).toEqual( 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 () => { @@ -450,6 +457,8 @@ test('should validate TPM RSA response (SHA1)', async () => { expect(verification.registrationInfo?.credentialID).toEqual( 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 () => { @@ -485,6 +494,8 @@ test('should validate Android-Key response', async () => { expect(verification.registrationInfo?.credentialID).toEqual( 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 () => { @@ -496,6 +507,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?.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 2546813..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 @@ -246,6 +247,8 @@ export async function verifyRegistrationResponse( userVerified: flags.uv, credentialDeviceType, credentialBackedUp, + origin: clientDataJSON.origin, + rpID: matchedRPID, authenticatorExtensionResults: extensionsData, }; } @@ -273,6 +276,9 @@ export async function verifyRegistrationResponse( * @param registrationInfo.credentialBackedUp Whether or not the multi-device credential has been * 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 */ @@ -289,6 +295,8 @@ export type VerifiedRegistrationResponse = { userVerified: boolean; credentialDeviceType: CredentialDeviceType; credentialBackedUp: boolean; + origin: string; + rpID?: string; authenticatorExtensionResults?: AuthenticationExtensionsAuthenticatorOutputs; }; }; |