diff options
Diffstat (limited to 'packages/server/src')
-rw-r--r-- | packages/server/src/assertion/parseAssertionAuthData.ts | 28 | ||||
-rw-r--r-- | packages/server/src/assertion/verifyAssertionResponse.test.ts | 47 | ||||
-rw-r--r-- | packages/server/src/assertion/verifyAssertionResponse.ts | 25 | ||||
-rw-r--r-- | packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts | 28 | ||||
-rw-r--r-- | packages/server/src/attestation/verifications/verifyFIDOU2F.ts | 9 | ||||
-rw-r--r-- | packages/server/src/attestation/verifications/verifyNone.ts | 13 | ||||
-rw-r--r-- | packages/server/src/attestation/verifications/verifyPacked.ts | 9 | ||||
-rw-r--r-- | packages/server/src/helpers/parseAuthenticatorData.ts (renamed from packages/server/src/attestation/parseAttestationAuthData.ts) | 4 |
8 files changed, 77 insertions, 86 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; diff --git a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts index 6f5365a..5705065 100644 --- a/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts +++ b/packages/server/src/attestation/verifications/verifyAndroidSafetyNet.ts @@ -11,8 +11,7 @@ import toHash from "@helpers/toHash"; import verifySignature from '@helpers/verifySignature'; import convertCOSEtoPKCS from '@helpers/convertCOSEtoPKCS'; import getCertificateInfo from '@helpers/getCertificateInfo'; - -import parseAttestationAuthData from '../parseAttestationAuthData'; +import parseAuthenticatorData from '@helpers/parseAuthenticatorData'; /** @@ -23,6 +22,20 @@ export default function verifyAttestationAndroidSafetyNet( base64ClientDataJSON: string, ): VerifiedAttestation { const { attStmt, authData, fmt } = attestationObject; + const authDataStruct = parseAuthenticatorData(authData); + const { counter, credentialID, COSEPublicKey, flags } = authDataStruct; + + if (!flags.up) { + throw new Error('User was not present for attestation (None)'); + } + + if (!COSEPublicKey) { + throw new Error('No public key was provided by authenticator (SafetyNet)'); + } + + if (!credentialID) { + throw new Error('No credential ID was provided by authenticator (SafetyNet)'); + } if (!attStmt.response) { throw new Error('No response was included in attStmt by authenticator (SafetyNet)'); @@ -107,19 +120,8 @@ export default function verifyAttestationAndroidSafetyNet( if (toReturn.verified) { - const authDataStruct = parseAttestationAuthData(authData); - const { counter, credentialID, COSEPublicKey, flags } = authDataStruct; - toReturn.userVerified = flags.uv; - if (!COSEPublicKey) { - throw new Error('No public key was provided by authenticator (SafetyNet)'); - } - - if (!credentialID) { - throw new Error('No credential ID was provided by authenticator (SafetyNet)'); - } - const publicKey = convertCOSEtoPKCS(COSEPublicKey); toReturn.authenticatorInfo = { diff --git a/packages/server/src/attestation/verifications/verifyFIDOU2F.ts b/packages/server/src/attestation/verifications/verifyFIDOU2F.ts index 75e664f..6768abc 100644 --- a/packages/server/src/attestation/verifications/verifyFIDOU2F.ts +++ b/packages/server/src/attestation/verifications/verifyFIDOU2F.ts @@ -1,12 +1,11 @@ import base64url from 'base64url'; -import { AttestationObject, VerifiedAttestation, U2F_USER_PRESENTED } from '@webauthntine/typescript-types'; +import { AttestationObject, VerifiedAttestation } from '@webauthntine/typescript-types'; import toHash from '@helpers/toHash'; import convertCOSEtoPKCS from '@helpers/convertCOSEtoPKCS'; import convertASN1toPEM from '@helpers/convertASN1toPEM'; import verifySignature from '@helpers/verifySignature'; - -import parseAttestationAuthData from '../parseAttestationAuthData'; +import parseAuthenticatorData from '@helpers/parseAuthenticatorData'; /** @@ -18,7 +17,7 @@ export default function verifyAttestationFIDOU2F( ): VerifiedAttestation { const { fmt, authData, attStmt } = attestationObject; - const authDataStruct = parseAttestationAuthData(authData); + const authDataStruct = parseAuthenticatorData(authData); const { flags, COSEPublicKey, @@ -27,7 +26,7 @@ export default function verifyAttestationFIDOU2F( counter, } = authDataStruct; - if (!(flags.flagsInt & U2F_USER_PRESENTED)) { + if (!(flags.up)) { throw new Error('User was NOT present during authentication (FIDOU2F)'); } diff --git a/packages/server/src/attestation/verifications/verifyNone.ts b/packages/server/src/attestation/verifications/verifyNone.ts index 4f967d1..470a10a 100644 --- a/packages/server/src/attestation/verifications/verifyNone.ts +++ b/packages/server/src/attestation/verifications/verifyNone.ts @@ -2,8 +2,8 @@ import base64url from 'base64url'; import { AttestationObject, VerifiedAttestation } from "@webauthntine/typescript-types"; import convertCOSEtoPKCS from "@helpers/convertCOSEtoPKCS"; +import parseAuthenticatorData from '@helpers/parseAuthenticatorData'; -import parseAttestationAuthData from '../parseAttestationAuthData'; /** @@ -15,7 +15,7 @@ export default function verifyAttestationNone( attestationObject: AttestationObject, ): VerifiedAttestation { const { fmt, authData } = attestationObject; - const authDataStruct = parseAttestationAuthData(authData); + const authDataStruct = parseAuthenticatorData(authData); const { credentialID, @@ -24,6 +24,10 @@ export default function verifyAttestationNone( flags, } = authDataStruct; + if (!flags.up) { + throw new Error('User was not present for attestation (None)'); + } + if (!COSEPublicKey) { throw new Error('No public key was provided by authenticator (None)'); } @@ -32,11 +36,6 @@ export default function verifyAttestationNone( throw new Error('No credential ID was provided by authenticator (None)'); } - // Make sure the (U)ser (P)resent for the attestation - if (!flags.up) { - throw new Error('User was not present for attestation (None)'); - } - const publicKey = convertCOSEtoPKCS(COSEPublicKey); const toReturn: VerifiedAttestation = { diff --git a/packages/server/src/attestation/verifications/verifyPacked.ts b/packages/server/src/attestation/verifications/verifyPacked.ts index 98b4e66..a40385a 100644 --- a/packages/server/src/attestation/verifications/verifyPacked.ts +++ b/packages/server/src/attestation/verifications/verifyPacked.ts @@ -9,8 +9,7 @@ import toHash from "@helpers/toHash"; import convertASN1toPEM from '@helpers/convertASN1toPEM'; import getCertificateInfo from '@helpers/getCertificateInfo'; import verifySignature from '@helpers/verifySignature'; - -import parseAttestationAuthData from '../parseAttestationAuthData'; +import parseAuthenticatorData from '@helpers/parseAuthenticatorData'; /** @@ -22,10 +21,14 @@ export default function verifyAttestationPacked(attestationObject: AttestationOb const { fmt, authData, attStmt } = attestationObject; const { sig, x5c, ecdaaKeyId } = attStmt; - const authDataStruct = parseAttestationAuthData(authData); + const authDataStruct = parseAuthenticatorData(authData); const { COSEPublicKey, counter, credentialID, flags } = authDataStruct; + if (!flags.up) { + throw new Error('User was not present for attestation (Packed)'); + } + if (!COSEPublicKey) { throw new Error('No public key was provided by authenticator (Packed)'); } diff --git a/packages/server/src/attestation/parseAttestationAuthData.ts b/packages/server/src/helpers/parseAuthenticatorData.ts index b51af5f..a3dd868 100644 --- a/packages/server/src/attestation/parseAttestationAuthData.ts +++ b/packages/server/src/helpers/parseAuthenticatorData.ts @@ -1,9 +1,9 @@ -import { ParsedAttestationAuthData } from "@webauthntine/typescript-types"; +import { ParsedAuthenticatorData } from "@webauthntine/typescript-types"; /** * Make sense of the authData buffer contained in an Attestation */ -export default function parseAttestationAuthData(authData: Buffer): ParsedAttestationAuthData { +export default function parseAuthenticatorData(authData: Buffer): ParsedAuthenticatorData { let intBuffer = authData; const rpIdHash = intBuffer.slice(0, 32); |