From 421171f9722068fdf6b4eb1ff3c70ac42c210b34 Mon Sep 17 00:00:00 2001 From: Nina Satragno Date: Thu, 2 Jan 2020 17:38:55 -0500 Subject: [PATCH] feat(WebAuthn): add an option for user verification Allow configuring the user verification requirement. This is implemented as a boolean that is translated into either UserVerificationRequirement "required" or "discouraged". Do not default to "preferred" since its behaviour is confusing at best. Fixes #21 --- README.md | 6 ++++-- src/Webauthn.js | 27 ++++++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5df506d..a49f40a 100644 --- a/README.md +++ b/README.md @@ -123,11 +123,13 @@ user. - An object mapping, where the key is the name of a property from the registration request to be included in the user object and the value is the name of that property on the user object. -- `[store = MemoryAdapter]` - The storage interface for user objects. Defaults +- `[store = MemoryAdapter]` - the storage interface for user objects. Defaults to an object in memory (for testing only). - `[attestation = 'none']` - the [attestation conveyance preference]( https://w3c.github.io/webauthn/#enum-attestation-convey). Setting this to anything other than `'none'` will require attestation and validate it. +- `[requireUserVerification = false]` - whether to require [user verification]( +https://w3c.github.io/webauthn/#user-verification). - `[credentialEndpoint = '/register']` - the path of the credential attestation challenge endpoint. - `[assertionEndpoint = '/login']` - the path of the challenge assertion @@ -135,7 +137,7 @@ endpoint. - `[challengeEndpoint = '/response']` - the path of the challenge response endpoint. - `[logoutEndpoint = '/logout']` - the path of the logout endpoint. -- `[enableLogging = true]` - Enable or disable logging to stdout. +- `[enableLogging = true]` - enable or disable logging to stdout. **`webauthn.initialize()`** diff --git a/src/Webauthn.js b/src/Webauthn.js index bc54e25..8b7d26d 100644 --- a/src/Webauthn.js +++ b/src/Webauthn.js @@ -38,6 +38,7 @@ class Webauthn { logoutEndpoint: '/logout', enableLogging: true, attestation: Dictionaries.AttestationConveyancePreference.NONE, + requireUserVerification: false, }, options) const attestationOptions = Object.values(Dictionaries.AttestationConveyancePreference) @@ -117,6 +118,9 @@ class Webauthn { if (this.config.enableLogging) console.log('STORED') const attestation = new AttestationChallengeBuilder(this) + .setUserVerification(this.config.requireUserVerification ? + Dictionaries.UserVerificationRequirement.REQUIRED + : Dictionaries.UserVerificationRequirement.DISCOURAGED) .setUserInfo(user) .setAttestationType(this.config.attestation) // .setAuthenticator() // Forces TPM @@ -260,7 +264,7 @@ class Webauthn { } } else if (response.authenticatorData !== undefined) { - result = Webauthn.verifyAuthenticatorAssertionResponse(response, user.authenticator, this.config.enableLogging) + result = this.verifyAuthenticatorAssertionResponse(response, user.authenticator) if (result.verified) { if (result.counter <= user.authenticator.counter) @@ -358,8 +362,13 @@ class Webauthn { const authrDataStruct = Webauthn.parseMakeCredAuthData(ctapMakeCredResp.authData); if (this.config.enableLogging) console.log('AUTHR_DATA_STRUCT', authrDataStruct) - if (!(authrDataStruct.flags & 0x01)) // U2F_USER_PRESENTED - throw new Error('User was NOT presented durring authentication!'); + if (!(authrDataStruct.flags & 0x01)) // U2F_USER_PRESENT + throw new Error('User was not present during authentication!'); + + if (this.config.requireUserVerification && !(authrDataStruct.flags & 0x04)) {// U2F_USER_VERIFIED + throw new Error('User was not verified during authentication!') + } + const publicKey = Webauthn.COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey) @@ -474,16 +483,20 @@ class Webauthn { return response } - static verifyAuthenticatorAssertionResponse (webauthnResponse, authr, enableLogging = false) { + verifyAuthenticatorAssertionResponse (webauthnResponse, authr) { const authenticatorData = base64url.toBuffer(webauthnResponse.authenticatorData) const response = { 'verified': false } if (['fido-u2f'].includes(authr.fmt)) { const authrDataStruct = Webauthn.parseGetAssertAuthData(authenticatorData) - if (enableLogging) console.log('AUTH_DATA', authrDataStruct) + if (this.config.enableLogging) console.log('AUTH_DATA', authrDataStruct) + + if (!(authrDataStruct.flags & 0x01)) {// U2F_USER_PRESENT + throw new Error('User was not present during authentication!') + } - if (!(authrDataStruct.flags & 0x01)) {// U2F_USER_PRESENTED - throw new Error('User was not presented durring authentication!') + if (this.config.requireUserVerification && !(authrDataStruct.flags & 0x04)) {// U2F_USER_VERIFIED + throw new Error('User was not verified during authentication!') } const clientDataHash = Webauthn.hash(base64url.toBuffer(webauthnResponse.clientDataJSON))