diff options
author | Matthew Miller <matthew@millerti.me> | 2020-07-05 17:13:31 -0700 |
---|---|---|
committer | Matthew Miller <matthew@millerti.me> | 2020-07-05 17:13:31 -0700 |
commit | af287ffa3d2f740f5005ab00ffa5d1557b4bb9f7 (patch) | |
tree | 8b6b99db410a0d1761b10301c48024a35f95b023 /packages/server/src/assertion/verifyAssertionResponse.ts | |
parent | 817c41586d88e5731ca3d2575c65e3eca95b3e06 (diff) |
Add various checks to assertion verifier
Diffstat (limited to 'packages/server/src/assertion/verifyAssertionResponse.ts')
-rw-r--r-- | packages/server/src/assertion/verifyAssertionResponse.ts | 51 |
1 files changed, 49 insertions, 2 deletions
diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts index 889c577..d0993f7 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.ts @@ -6,6 +6,7 @@ import toHash from '../helpers/toHash'; import convertASN1toPEM from '../helpers/convertASN1toPEM'; import verifySignature from '../helpers/verifySignature'; import parseAuthenticatorData from '../helpers/parseAuthenticatorData'; +import isBase64URLString from '../helpers/isBase64URLString'; type Options = { credential: AssertionCredentialJSON; @@ -39,10 +40,34 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser authenticator, requireUserVerification = false, } = options; - const { response } = credential; + const { id, rawId, type: credentialType, response } = credential; + + // Ensure credential specified an ID + if (!id) { + throw new Error('Missing credential ID'); + } + + // Ensure ID is base64url-encoded + if (id !== rawId) { + throw new Error('Credential ID was not base64url-encoded'); + } + + // Make sure credential type is public-key + if (credentialType !== 'public-key') { + throw new Error(`Unexpected credential type ${credentialType}, expected "public-key"`); + } + + if (!response) { + throw new Error('Credential missing response'); + } + + if (typeof response?.clientDataJSON !== 'string') { + throw new Error('Credential response clientDataJSON was not a string'); + } + const clientDataJSON = decodeClientDataJSON(response.clientDataJSON); - const { type, origin, challenge } = clientDataJSON; + const { type, origin, challenge, tokenBinding } = clientDataJSON; // Make sure we're handling an assertion if (type !== 'webauthn.get') { @@ -62,6 +87,28 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser throw new Error(`Unexpected assertion origin "${origin}", expected "${expectedOrigin}"`); } + if (!isBase64URLString(response.authenticatorData)) { + throw new Error('Credential response authenticatorData was not a base64url string'); + } + + if (!isBase64URLString(response.signature)) { + throw new Error('Credential response signature was not a base64url string'); + } + + if (typeof response.userHandle !== 'string') { + throw new Error('Credential response userHandle was not a string'); + } + + if (tokenBinding) { + if (typeof tokenBinding !== 'object') { + throw new Error('ClientDataJSON tokenBinding was not an object'); + } + + if (['present', 'supported', 'notSupported'].indexOf(tokenBinding.status) < 0) { + throw new Error(`Unexpected tokenBinding status ${tokenBinding.status}`); + } + } + const authDataBuffer = base64url.toBuffer(response.authenticatorData); const parsedAuthData = parseAuthenticatorData(authDataBuffer); const { rpIdHash, flags, counter } = parsedAuthData; |