diff options
author | Matthew Miller <matthew@millerti.me> | 2020-06-07 15:09:57 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-06-07 15:09:57 -0700 |
commit | 992a56a10fab7f651a936bcc65642664b9cd86bc (patch) | |
tree | 8a0bf34253858057e35a3aa996b911a97751e3af /packages/server/src/assertion/verifyAssertionResponse.test.ts | |
parent | b4c1bae58a11f7651dd44b7cfc7ba210ef09a605 (diff) | |
parent | c172c6afd507d8a690c8716bb37d551b9e99379a (diff) |
Merge pull request #24 from MasterKale/feature/improved-verification
feature/improved-verification
Diffstat (limited to 'packages/server/src/assertion/verifyAssertionResponse.test.ts')
-rw-r--r-- | packages/server/src/assertion/verifyAssertionResponse.test.ts | 177 |
1 files changed, 136 insertions, 41 deletions
diff --git a/packages/server/src/assertion/verifyAssertionResponse.test.ts b/packages/server/src/assertion/verifyAssertionResponse.test.ts index 9da06ce..20b6e0e 100644 --- a/packages/server/src/assertion/verifyAssertionResponse.test.ts +++ b/packages/server/src/assertion/verifyAssertionResponse.test.ts @@ -1,7 +1,9 @@ +import base64url from 'base64url'; import verifyAssertionResponse from './verifyAssertionResponse'; import * as decodeClientDataJSON from '../helpers/decodeClientDataJSON'; import * as parseAuthenticatorData from '../helpers/parseAuthenticatorData'; +import toHash from '../helpers/toHash'; let mockDecodeClientData: jest.SpyInstance; let mockParseAuthData: jest.SpyInstance; @@ -17,60 +19,51 @@ afterEach(() => { }); test('should verify an assertion response', () => { - const verification = verifyAssertionResponse( - assertionResponse, - assertionChallenge, - assertionOrigin, - authenticator, - ); - - expect(verification.verified).toEqual(true); -}); - -test('should verify an assertion response if origin does not start with https', () => { - const verification = verifyAssertionResponse( - assertionResponse, - assertionChallenge, - 'dev.dontneeda.pw', - authenticator, - ); + const verification = verifyAssertionResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + }); expect(verification.verified).toEqual(true); }); test('should return authenticator info after verification', () => { - const verification = verifyAssertionResponse( - assertionResponse, - assertionChallenge, - assertionOrigin, - authenticator, - ); + const verification = verifyAssertionResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + }); expect(verification.authenticatorInfo.counter).toEqual(144); - expect(verification.authenticatorInfo.base64CredentialID).toEqual( - authenticator.credentialID, - ); + expect(verification.authenticatorInfo.base64CredentialID).toEqual(authenticator.credentialID); }); test('should throw when response challenge is not expected value', () => { expect(() => { - verifyAssertionResponse( - assertionResponse, - 'shouldhavebeenthisvalue', - 'https://different.address', - authenticator, - ); + verifyAssertionResponse({ + credential: assertionResponse, + expectedChallenge: 'shouldhavebeenthisvalue', + expectedOrigin: 'https://different.address', + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + }); }).toThrow(/assertion challenge/i); }); test('should throw when response origin is not expected value', () => { expect(() => { - verifyAssertionResponse( - assertionResponse, - assertionChallenge, - 'https://different.address', - authenticator, - ); + verifyAssertionResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: 'https://different.address', + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + }); }).toThrow(/assertion origin/i); }); @@ -83,17 +76,30 @@ test('should throw when assertion type is not webauthn.create', () => { }); expect(() => { - verifyAssertionResponse(assertionResponse, assertionChallenge, assertionOrigin, authenticator); + verifyAssertionResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + }); }).toThrow(/assertion type/i); }); test('should throw error if user was not present', () => { mockParseAuthData.mockReturnValue({ + rpIdHash: toHash(Buffer.from('dev.dontneeda.pw', 'ascii')), flags: 0, }); expect(() => { - verifyAssertionResponse(assertionResponse, assertionChallenge, assertionOrigin, authenticator); + verifyAssertionResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + }); }).toThrow(/not present/i); }); @@ -106,10 +112,74 @@ test('should throw error if previous counter value is not less than in response' }; expect(() => { - verifyAssertionResponse(assertionResponse, assertionChallenge, assertionOrigin, badDevice); + verifyAssertionResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: badDevice, + }); }).toThrow(/counter value/i); }); +test('should throw error if assertion RP ID is unexpected value', () => { + mockParseAuthData.mockReturnValue({ + rpIdHash: toHash(Buffer.from('bad.url', 'ascii')), + flags: 0, + }); + + expect(() => { + verifyAssertionResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + }); + }).toThrow(/rp id/i); +}); + +test('should not compare counters if both are 0', () => { + const verification = verifyAssertionResponse({ + credential: assertionFirstTimeUsedResponse, + expectedChallenge: assertionFirstTimeUsedChallenge, + expectedOrigin: assertionFirstTimeUsedOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticatorFirstTimeUsed, + }); + + expect(verification.verified).toEqual(true); +}); + +test('should throw an error if user verification is required but user was not verified', () => { + const actualData = parseAuthenticatorData.default( + base64url.toBuffer(assertionResponse.response.authenticatorData), + ); + + mockParseAuthData.mockReturnValue({ + ...actualData, + flags: { + up: true, + uv: false, + }, + }); + + expect(() => { + verifyAssertionResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + requireUserVerification: true, + }); + }).toThrow(/user could not be verified/i); +}); + +/** + * Assertion examples below + */ + const assertionResponse = { id: 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Pxg6jo_o0hYiew', rawId: '', @@ -134,5 +204,30 @@ const authenticator = { 'BIheFp-u6GvFT2LNGovf3ZrT0iFVBsA_76rRysxRG9A18WGeA6hPmnab0HAViUYVRkwTNcN77QBf_' + 'RR0dv3lIvQ', credentialID: 'KEbWNCc7NgaYnUyrNeFGX9_3Y-8oJ3KwzjnaiD1d1LVTxR7v3CaKfCz2Vy_g_MHSh7yJ8yL0Px' + 'g6jo_o0hYiew', + counter: 143, +}; + +/** + * Represented a device that's being used on the website for the first time + */ +const assertionFirstTimeUsedResponse = { + id: 'wSisR0_4hlzw3Y1tj4uNwwifIhRa-ZxWJwWbnfror0pVK9qPdBPO5pW3gasPqn6wXHb0LNhXB_IrA1nFoSQJ9A', + rawId: 'wSisR0_4hlzw3Y1tj4uNwwifIhRa-ZxWJwWbnfror0pVK9qPdBPO5pW3gasPqn6wXHb0LNhXB_IrA1nFoSQJ9A', + response: { + authenticatorData: 'PdxHEOnAiLIp26idVjIguzn3Ipr_RlsKZWsa-5qK-KABAAAAAA', + clientDataJSON: + 'eyJjaGFsbGVuZ2UiOiJkRzkwWVd4c2VWVnVhWEYxWlZaaGJIVmxSWFpsY25sQmMzTmxjblJwYjI0IiwiY2xpZW50RXh0ZW5zaW9ucyI6e30sImhhc2hBbGdvcml0aG0iOiJTSEEtMjU2Iiwib3JpZ2luIjoiaHR0cHM6Ly9kZXYuZG9udG5lZWRhLnB3IiwidHlwZSI6IndlYmF1dGhuLmdldCJ9', + signature: + 'MEQCIBu6M-DGzu1O8iocGHEj0UaAZm0HmxTeRIE6-nS3_CPjAiBDsmIzy5sacYwwzgpXqfwRt_2vl5yiQZ_OAqWJQBGVsQ', + }, + type: 'public-key', +}; +const assertionFirstTimeUsedChallenge = 'totallyUniqueValueEveryAssertion'; +const assertionFirstTimeUsedOrigin = 'https://dev.dontneeda.pw'; +const authenticatorFirstTimeUsed = { + publicKey: + 'BGmaxR4mBbukc2QhtW2ldhAAd555r-ljlGQN8MbcTnPP9CyUlE-0AB2fbzZbNgBvJuRa7r6o2jPphOmtyNPR_kY', + credentialID: + 'wSisR0_4hlzw3Y1tj4uNwwifIhRa-ZxWJwWbnfror0pVK9qPdBPO5pW3gasPqn6wXHb0LNhXB_IrA1nFoSQJ9A', counter: 0, }; |