summaryrefslogtreecommitdiffhomepage
path: root/packages/server/src/assertion/verifyAssertionResponse.ts
diff options
context:
space:
mode:
authorMatthew Miller <matthew@millerti.me>2020-06-07 15:09:57 -0700
committerGitHub <noreply@github.com>2020-06-07 15:09:57 -0700
commit992a56a10fab7f651a936bcc65642664b9cd86bc (patch)
tree8a0bf34253858057e35a3aa996b911a97751e3af /packages/server/src/assertion/verifyAssertionResponse.ts
parentb4c1bae58a11f7651dd44b7cfc7ba210ef09a605 (diff)
parentc172c6afd507d8a690c8716bb37d551b9e99379a (diff)
Merge pull request #24 from MasterKale/feature/improved-verification
feature/improved-verification
Diffstat (limited to 'packages/server/src/assertion/verifyAssertionResponse.ts')
-rw-r--r--packages/server/src/assertion/verifyAssertionResponse.ts81
1 files changed, 51 insertions, 30 deletions
diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts
index 7d13271..0029796 100644
--- a/packages/server/src/assertion/verifyAssertionResponse.ts
+++ b/packages/server/src/assertion/verifyAssertionResponse.ts
@@ -1,8 +1,5 @@
import base64url from 'base64url';
-import {
- AssertionCredentialJSON,
- AuthenticatorDevice,
-} from '@simplewebauthn/typescript-types';
+import { AssertionCredentialJSON, AuthenticatorDevice } from '@simplewebauthn/typescript-types';
import decodeClientDataJSON from '../helpers/decodeClientDataJSON';
import toHash from '../helpers/toHash';
@@ -10,27 +7,46 @@ import convertASN1toPEM from '../helpers/convertASN1toPEM';
import verifySignature from '../helpers/verifySignature';
import parseAuthenticatorData from '../helpers/parseAuthenticatorData';
+type Options = {
+ credential: AssertionCredentialJSON;
+ expectedChallenge: string;
+ expectedOrigin: string;
+ expectedRPID: string;
+ authenticator: AuthenticatorDevice;
+ requireUserVerification?: boolean;
+};
+
/**
* Verify that the user has legitimately completed the login process
*
- * @param response Authenticator assertion response with base64url-encoded values
+ * **Options:**
+ *
+ * @param credential Authenticator credential returned by browser's `startAssertion()`
* @param expectedChallenge The random value provided to generateAssertionOptions for the
* authenticator to sign
- * @param expectedOrigin Expected URL of website assertion should have occurred on
+ * @param expectedOrigin Website URL that the attestation should have occurred on
+ * @param expectedRPID RP ID that was specified in the attestation options
+ * @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID
+ * @param requireUserVerification (Optional) Enforce user verification by the authenticator
+ * (via PIN, fingerprint, etc...)
*/
-export default function verifyAssertionResponse(
- credential: AssertionCredentialJSON,
- expectedChallenge: string,
- expectedOrigin: string,
- authenticator: AuthenticatorDevice,
-): VerifiedAssertion {
+export default function verifyAssertionResponse(options: Options): VerifiedAssertion {
+ const {
+ credential,
+ expectedChallenge,
+ expectedOrigin,
+ expectedRPID,
+ authenticator,
+ requireUserVerification = false,
+ } = options;
const { response } = credential;
const clientDataJSON = decodeClientDataJSON(response.clientDataJSON);
const { type, origin, challenge } = clientDataJSON;
- if (!expectedOrigin.startsWith('https://')) {
- expectedOrigin = `https://${expectedOrigin}`;
+ // Make sure we're handling an assertion
+ if (type !== 'webauthn.get') {
+ throw new Error(`Unexpected assertion type: ${type}`);
}
if (challenge !== expectedChallenge) {
@@ -44,20 +60,33 @@ export default function verifyAssertionResponse(
throw new Error(`Unexpected assertion origin "${origin}", expected "${expectedOrigin}"`);
}
- // Make sure we're handling an assertion
- if (type !== 'webauthn.get') {
- throw new Error(`Unexpected assertion type: ${type}`);
- }
-
const authDataBuffer = base64url.toBuffer(response.authenticatorData);
- const authDataStruct = parseAuthenticatorData(authDataBuffer);
- const { flags, counter } = authDataStruct;
+ const parsedAuthData = parseAuthenticatorData(authDataBuffer);
+ const { rpIdHash, flags, counter } = parsedAuthData;
+
+ // Make sure the response's RP ID is ours
+ const expectedRPIDHash = toHash(Buffer.from(expectedRPID, 'ascii'));
+ if (!rpIdHash.equals(expectedRPIDHash)) {
+ throw new Error(`Unexpected RP ID hash`);
+ }
+ // Make sure someone was physically present
if (!flags.up) {
throw new Error('User not present during assertion');
}
- if (counter <= authenticator.counter) {
+ // Enforce user verification if specified
+ if (requireUserVerification && !flags.uv) {
+ throw new Error('User verification required, but user could not be verified');
+ }
+
+ const clientDataHash = toHash(base64url.toBuffer(response.clientDataJSON));
+ const signatureBase = Buffer.concat([authDataBuffer, clientDataHash]);
+
+ const publicKey = convertASN1toPEM(base64url.toBuffer(authenticator.publicKey));
+ const signature = base64url.toBuffer(response.signature);
+
+ if ((counter > 0 || authenticator.counter > 0) && 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
// used for this client. If this happens, then someone's somehow increased the counter
@@ -67,14 +96,6 @@ export default function verifyAssertionResponse(
);
}
- const { rpIdHash, flagsBuf, counterBuf } = authDataStruct;
-
- const clientDataHash = toHash(base64url.toBuffer(response.clientDataJSON));
- const signatureBase = Buffer.concat([rpIdHash, flagsBuf, counterBuf, clientDataHash]);
-
- const publicKey = convertASN1toPEM(base64url.toBuffer(authenticator.publicKey));
- const signature = base64url.toBuffer(response.signature);
-
const toReturn = {
verified: verifySignature(signature, signatureBase, publicKey),
authenticatorInfo: {