diff options
Diffstat (limited to 'packages/server/src/assertion')
3 files changed, 44 insertions, 56 deletions
diff --git a/packages/server/src/assertion/parseAssertionAuthData.ts b/packages/server/src/assertion/parseAssertionAuthData.ts deleted file mode 100644 index bdd636a..0000000 --- a/packages/server/src/assertion/parseAssertionAuthData.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ParsedAssertionAuthData } from "@webauthntine/typescript-types"; - -/** - * Make sense of the authData buffer contained in an Assertion - */ -export default function parseAssertionAuthData(authData: Buffer): ParsedAssertionAuthData { - let intBuffer = authData; - - const rpIdHash = intBuffer.slice(0, 32); - intBuffer = intBuffer.slice(32); - - const flagsBuf = intBuffer.slice(0, 1); - intBuffer = intBuffer.slice(1); - - const flags = flagsBuf[0]; - const counterBuf = intBuffer.slice(0, 4); - intBuffer = intBuffer.slice(4); - - const counter = counterBuf.readUInt32BE(0); - - return { - rpIdHash, - flagsBuf, - flags, - counter, - counterBuf, - }; -} diff --git a/packages/server/src/assertion/verifyAssertionResponse.test.ts b/packages/server/src/assertion/verifyAssertionResponse.test.ts index 9e5b083..99e87d2 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.test.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.test.ts @@ -1,14 +1,14 @@ import verifyAssertionResponse from './verifyAssertionResponse'; import * as decodeClientDataJSON from '../helpers/decodeClientDataJSON'; -import * as parseAssertionAuthData from './parseAssertionAuthData'; +import * as parseAuthenticatorData from '../helpers/parseAuthenticatorData'; let mockDecodeClientData: jest.SpyInstance; let mockParseAuthData: jest.SpyInstance; beforeEach(() => { mockDecodeClientData = jest.spyOn(decodeClientDataJSON, 'default'); - mockParseAuthData = jest.spyOn(parseAssertionAuthData, 'default'); + mockParseAuthData = jest.spyOn(parseAuthenticatorData, 'default'); }); afterEach(() => { @@ -19,13 +19,24 @@ afterEach(() => { test('should verify an assertion response', () => { const verification = verifyAssertionResponse( assertionResponse, - 'https://dev.dontneeda.pw', + assertionOrigin, authenticator, ); expect(verification.verified).toEqual(true); }); +test('should return authenticator info after verification', () => { + const verification = verifyAssertionResponse( + assertionResponse, + assertionOrigin, + authenticator, + ); + + expect(verification.authenticatorInfo.counter).toEqual(144); + expect(verification.authenticatorInfo.base64CredentialID).toEqual(authenticator.base64CredentialID); +}); + test('should throw when response origin is not expected value', () => { expect(() => { verifyAssertionResponse( @@ -68,18 +79,18 @@ test('should throw error if user was not present', () => { test('should throw error if previous counter value is not less than in response', () => { // This'll match the `counter` value in `assertionResponse`, simulating a potential replay attack - const badCounter = 135; + const badCounter = 144; const badDevice = { ...authenticator, counter: badCounter, }; expect(() => { - verifyAssertionResponse( + console.log(verifyAssertionResponse( assertionResponse, assertionOrigin, badDevice, - ); + )); }).toThrow(); }); @@ -93,19 +104,21 @@ test('should throw error if previous counter value is not less than in response' * } */ const assertionResponse = { - base64AuthenticatorData: 'PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAhw', - base64ClientDataJSON: 'eyJjaGFsbGVuZ2UiOiJXRzVRU21RM1oyOTROR2gyTVROUk56WnViVmhMTlZZMWMwOHRP' + - 'V3BLVG5JIiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoi' + - 'aHR0cHM6Ly9kZXYuZG9udG5lZWRhLnB3IiwidHlwZSI6IndlYmF1dGhuLmdldCJ9', - base64Signature: 'MEQCIHZYFY3LsKzI0T9XRwEACl7YsYZysZ2HUw3q9f7tlq3wAiBNbyBbQMNM56P6Z00tBEZ6v' + - 'II4f9Al-p4pZw7OBpSaog', + base64CredentialID: 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Px' + + 'g6jo_o0hYiew', + base64AuthenticatorData: 'PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAkA==', + base64ClientDataJSON: 'eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sVWFXMWwiLCJj' + + 'bGllbnRFeHRlbnNpb25zIjp7fSwiaGFzaEFsZ29yaXRobSI6IlNIQS0yNTYiLCJvcmlnaW4iOiJodHRwczovL2Rldi5k' + + 'b250bmVlZGEucHciLCJ0eXBlIjoid2ViYXV0aG4uZ2V0In0=', + base64Signature: 'MEUCIQDYXBOpCWSWq2Ll4558GJKD2RoWg958lvJSB_GdeokxogIgWuEVQ7ee6AswQY0OsuQ6y8Ks6' + + 'jhd45bDx92wjXKs900=' }; const assertionOrigin = 'https://dev.dontneeda.pw'; const authenticator = { - base64PublicKey: 'BBMQEnZRfg4ASys9kfGUj99Xlsa028wqYJZw8xuGahPQJWN3K9D9DajLxzKlY7uf_ulA5D6gh' + - 'UJ9hrouDX84S_I', - base64CredentialID: 'wJZRtQbYjKlpiRnzet7yyVizdsj_oUhi11kFbKyO0hc5gIg-4xeaTC9YC9y9sfow6gO3jE' + - 'MoONBKNX4SmSclmQ', - counter: 134, + base64PublicKey: 'BIheFp-u6GvFT2LNGovf3ZrT0iFVBsA_76rRysxRG9A18WGeA6hPmnab0HAViUYVRkwTNcN77QBf_' + + 'RR0dv3lIvQ', + base64CredentialID: 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Px' + + 'g6jo_o0hYiew', + counter: 0, }; diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts index fb668f4..a3b631b 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.ts @@ -1,17 +1,16 @@ import base64url from 'base64url'; import { AuthenticatorAssertionResponseJSON, - U2F_USER_PRESENTED, AuthenticatorDevice, VerifiedAssertion, } from "@webauthntine/typescript-types"; import decodeClientDataJSON from "@helpers/decodeClientDataJSON"; -import parseAssertionAuthData from './parseAssertionAuthData'; import toHash from '@helpers/toHash'; import convertASN1toPEM from '@helpers/convertASN1toPEM'; import verifySignature from '@helpers/verifySignature'; +import parseAuthenticatorData from '@helpers/parseAuthenticatorData'; /** * Verify that the user has legitimately completed the login process @@ -40,19 +39,13 @@ export default function verifyAssertionResponse( } const authDataBuffer = base64url.toBuffer(base64AuthenticatorData); - const authData = parseAssertionAuthData(authDataBuffer); + const authDataStruct = parseAuthenticatorData(authDataBuffer); + const { credentialID, flags, counter } = authDataStruct; - if (!(authData.flags & U2F_USER_PRESENTED)) { + if (!(flags.up)) { throw new Error('User was NOT present during assertion!'); } - const { - rpIdHash, - flagsBuf, - counterBuf, - counter, - } = authData; - if (counter <= authenticator.counter) { // Error out when the counter in the DB is greater than or equal to the counter in the // dataStruct. It's related to how the authenticator maintains the number of times its been @@ -63,6 +56,12 @@ export default function verifyAssertionResponse( ); } + const { + rpIdHash, + flagsBuf, + counterBuf, + } = authDataStruct; + const clientDataHash = toHash(base64url.toBuffer(base64ClientDataJSON)); const signatureBase = Buffer.concat([ rpIdHash, @@ -76,6 +75,10 @@ export default function verifyAssertionResponse( const toReturn = { verified: verifySignature(signature, signatureBase, publicKey), + authenticatorInfo: { + counter, + base64CredentialID: response.base64CredentialID, + }, }; return toReturn; |