diff options
Diffstat (limited to 'packages/server/src')
-rw-r--r-- | packages/server/src/authentication/verifyAuthenticationResponse.test.ts | 109 | ||||
-rw-r--r-- | packages/server/src/authentication/verifyAuthenticationResponse.ts | 45 |
2 files changed, 147 insertions, 7 deletions
diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts index ecd3c24..8a5b2fa 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts @@ -372,6 +372,115 @@ test('should return credential backup info', async () => { expect(verification.authenticationInfo?.credentialBackedUp).toEqual(false); }); +test('[FIDO Conformance] should verify if user verification is required and user was verified but not present', () => { + const actualData = esmParseAuthenticatorData.parseAuthenticatorData( + base64url.toBuffer(assertionResponse.response.authenticatorData), + ); + + mockParseAuthData.mockReturnValue({ + ...actualData, + flags: { + up: false, + uv: true, + }, + }); + + const verification = verifyAuthenticationResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + advancedFIDOConfig: { + userVerification: 'required', + } + }); + + expect(verification.verified).toEqual(true); +}); + +test('[FIDO Conformance] should verify if user verification is preferred and user was not verified or present', () => { + const actualData = esmParseAuthenticatorData.parseAuthenticatorData( + base64url.toBuffer(assertionResponse.response.authenticatorData), + ); + + mockParseAuthData.mockReturnValue({ + ...actualData, + flags: { + up: false, + uv: false, + }, + }); + + const verification = verifyAuthenticationResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + requireUserVerification: false, + advancedFIDOConfig: { + userVerification: 'preferred', + }, + }); + + expect(verification.verified).toEqual(true); +}); + +test('[FIDO Conformance] should verify if user verification is discouraged and user was verified but not present', () => { + const actualData = esmParseAuthenticatorData.parseAuthenticatorData( + base64url.toBuffer(assertionResponse.response.authenticatorData), + ); + + mockParseAuthData.mockReturnValue({ + ...actualData, + flags: { + up: false, + uv: true, + }, + }); + + const verification = verifyAuthenticationResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + advancedFIDOConfig: { + userVerification: 'discouraged', + }, + }); + + expect(verification.verified).toEqual(true); +}); + +test('[FIDO Conformance] should verify if user verification is discouraged and user was not verified or present', () => { + const actualData = esmParseAuthenticatorData.parseAuthenticatorData( + base64url.toBuffer(assertionResponse.response.authenticatorData), + ); + + mockParseAuthData.mockReturnValue({ + ...actualData, + flags: { + up: false, + uv: false, + }, + }); + + const verification = verifyAuthenticationResponse({ + credential: assertionResponse, + expectedChallenge: assertionChallenge, + expectedOrigin: assertionOrigin, + expectedRPID: 'dev.dontneeda.pw', + authenticator: authenticator, + advancedFIDOConfig: { + userVerification: 'discouraged', + }, + }); + + expect(verification.verified).toEqual(true); +}); + /** * Assertion examples below */ diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts index e3dc770..f6a437e 100644 --- a/packages/server/src/authentication/verifyAuthenticationResponse.ts +++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts @@ -3,6 +3,7 @@ import { AuthenticationCredentialJSON, AuthenticatorDevice, CredentialDeviceType, + UserVerificationRequirement, } from '@simplewebauthn/typescript-types'; import { decodeClientDataJSON } from '../helpers/decodeClientDataJSON'; @@ -21,6 +22,9 @@ export type VerifyAuthenticationResponseOpts = { expectedRPID: string | string[]; authenticator: AuthenticatorDevice; requireUserVerification?: boolean; + advancedFIDOConfig?: { + userVerification?: UserVerificationRequirement, + }, }; /** @@ -36,6 +40,11 @@ export type VerifyAuthenticationResponseOpts = { * @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID * @param requireUserVerification (Optional) Enforce user verification by the authenticator * (via PIN, fingerprint, etc...) + * @param advancedFIDOConfig (Optional) Options for satisfying more stringent FIDO RP feature + * requirements + * @param advancedFIDOConfig.userVerification (Optional) Enable alternative rules for evaluating the + * User Presence and User Verified flags in authenticator data: UV (and UP) flags are optional + * unless this value is `"required"` */ export function verifyAuthenticationResponse( options: VerifyAuthenticationResponseOpts, @@ -47,6 +56,7 @@ export function verifyAuthenticationResponse( expectedRPID, authenticator, requireUserVerification, + advancedFIDOConfig, } = options; const { id, rawId, type: credentialType, response } = credential; @@ -155,14 +165,35 @@ export function verifyAuthenticationResponse( } } - // WebAuthn only requires the user presence flag be true - if (!flags.up) { - throw new Error('User not present during authentication'); - } + if (advancedFIDOConfig !== undefined) { + const { + userVerification: fidoUserVerification, + } = advancedFIDOConfig; + + /** + * Use FIDO Conformance-defined rules for verifying UP and UV flags + */ + if (fidoUserVerification === 'required') { + // Require `flags.uv` be true (implies `flags.up` is true) + if (!flags.uv) { + throw new Error('User verification required, but user could not be verified'); + } + } else if (fidoUserVerification === 'preferred' || fidoUserVerification === 'discouraged') { + // Ignore `flags.uv` + } + } else { + /** + * Use WebAuthn spec-defined rules for verifying UP and UV flags + */ + // WebAuthn only requires the user presence flag be true + if (!flags.up) { + throw new Error('User not present during authentication'); + } - // Enforce user verification if required - if (requireUserVerification && !flags.uv) { - throw new Error('User verification required, but user could not be verified'); + // Enforce user verification if required + if (requireUserVerification && !flags.uv) { + throw new Error('User verification required, but user could not be verified'); + } } const clientDataHash = toHash(base64url.toBuffer(response.clientDataJSON)); |