summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/server/src/authentication/verifyAuthenticationResponse.test.ts34
-rw-r--r--packages/server/src/authentication/verifyAuthenticationResponse.ts14
-rw-r--r--packages/server/src/registration/verifyRegistrationResponse.test.ts58
-rw-r--r--packages/server/src/registration/verifyRegistrationResponse.ts14
4 files changed, 118 insertions, 2 deletions
diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts
index 822bdd9..b150aff 100644
--- a/packages/server/src/authentication/verifyAuthenticationResponse.test.ts
+++ b/packages/server/src/authentication/verifyAuthenticationResponse.test.ts
@@ -335,6 +335,40 @@ Deno.test('should throw an error if RP ID not in list of possible RP IDs', async
);
});
+Deno.test('should throw an error if type not the expected type', async () => {
+ await assertRejects(
+ () =>
+ verifyAuthenticationResponse({
+ response: assertionResponse,
+ expectedChallenge: assertionChallenge,
+ expectedOrigin: assertionOrigin,
+ // assertionResponse contains webauthn.get, this should produce an error
+ expectedType: 'payment.get',
+ expectedRPID: 'localhost',
+ authenticator: authenticator,
+ }),
+ Error,
+ 'Unexpected authentication response type',
+ );
+});
+
+Deno.test('should throw an error if type not in list of expected types', async () => {
+ await assertRejects(
+ () =>
+ verifyAuthenticationResponse({
+ response: assertionResponse,
+ expectedChallenge: assertionChallenge,
+ expectedOrigin: assertionOrigin,
+ // assertionResponse contains webauthn.get, this should produce an error
+ expectedType: ['payment.get', 'something.get'],
+ expectedRPID: 'localhost',
+ authenticator: authenticator,
+ }),
+ Error,
+ 'Unexpected authentication response type',
+ );
+});
+
Deno.test('should pass verification if custom challenge verifier returns true', async () => {
const verification = await verifyAuthenticationResponse({
response: {
diff --git a/packages/server/src/authentication/verifyAuthenticationResponse.ts b/packages/server/src/authentication/verifyAuthenticationResponse.ts
index 41370a0..c938598 100644
--- a/packages/server/src/authentication/verifyAuthenticationResponse.ts
+++ b/packages/server/src/authentication/verifyAuthenticationResponse.ts
@@ -18,6 +18,7 @@ export type VerifyAuthenticationResponseOpts = {
expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
expectedOrigin: string | string[];
expectedRPID: string | string[];
+ expectedType?: string | string[];
authenticator: AuthenticatorDevice;
requireUserVerification?: boolean;
advancedFIDOConfig?: {
@@ -35,6 +36,7 @@ export type VerifyAuthenticationResponseOpts = {
* `generateAuthenticationOptions()`
* @param expectedOrigin Website URL (or array of URLs) that the registration should have occurred on
* @param expectedRPID RP ID (or array of IDs) that was specified in the registration options
+ * @param expectedType (Optional) The response type expected ('webauthn.get')
* @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID
* @param requireUserVerification (Optional) Enforce user verification by the authenticator
* (via PIN, fingerprint, etc...)
@@ -52,6 +54,7 @@ export async function verifyAuthenticationResponse(
expectedChallenge,
expectedOrigin,
expectedRPID,
+ expectedType,
authenticator,
requireUserVerification = true,
advancedFIDOConfig,
@@ -88,7 +91,16 @@ export async function verifyAuthenticationResponse(
const { type, origin, challenge, tokenBinding } = clientDataJSON;
// Make sure we're handling an authentication
- if (type !== 'webauthn.get') {
+ if (Array.isArray(expectedType)) {
+ if (!expectedType.includes(type)) {
+ const joinedExpectedType = expectedType.join(', ');
+ throw new Error(`Unexpected authentication response type "${type}", expected one of: ${joinedExpectedType}`);
+ }
+ } else if (expectedType) {
+ if (type !== expectedType) {
+ throw new Error(`Unexpected authentication response type "${type}", expected "${expectedType}"`);
+ }
+ } else if (type !== 'webauthn.get') {
throw new Error(`Unexpected authentication response type: ${type}`);
}
diff --git a/packages/server/src/registration/verifyRegistrationResponse.test.ts b/packages/server/src/registration/verifyRegistrationResponse.test.ts
index 59dbd13..fbe7aed 100644
--- a/packages/server/src/registration/verifyRegistrationResponse.test.ts
+++ b/packages/server/src/registration/verifyRegistrationResponse.test.ts
@@ -219,6 +219,36 @@ Deno.test('should throw when response origin is not expected value', async () =>
);
});
+Deno.test('should throw when response type is not expected value', async () => {
+ await assertRejects(
+ () =>
+ verifyRegistrationResponse({
+ response: attestationNone,
+ expectedChallenge: attestationNoneChallenge,
+ expectedOrigin: 'https://dev.dontneeda.pw',
+ expectedRPID: 'dev.dontneeda.pw',
+ expectedType: 'something.get'
+ }),
+ Error,
+ 'registration response type',
+ );
+});
+
+Deno.test('should throw when response type is not in list of expected types', async () => {
+ await assertRejects(
+ () =>
+ verifyRegistrationResponse({
+ response: attestationNone,
+ expectedChallenge: attestationNoneChallenge,
+ expectedOrigin: 'https://dev.dontneeda.pw',
+ expectedRPID: 'dev.dontneeda.pw',
+ expectedType: ['something.create', 'something.else.create']
+ }),
+ Error,
+ 'registration response type',
+ );
+});
+
Deno.test('should throw when attestation type is not webauthn.create', async () => {
const origin = 'https://dev.dontneeda.pw';
const challenge = attestationNoneChallenge;
@@ -250,6 +280,34 @@ Deno.test('should throw when attestation type is not webauthn.create', async ()
mockDecodeClientData.restore();
});
+Deno.test('should validate when attestation type is not webauthn.create and expected type provided', async () => {
+ const origin = 'https://dev.dontneeda.pw';
+ const challenge = attestationNoneChallenge;
+
+ const mockDecodeClientData = stub(
+ _decodeClientDataJSONInternals,
+ 'stubThis',
+ returnsNext([
+ {
+ origin,
+ type: 'webauthn.goodtype',
+ challenge: attestationNoneChallenge,
+ },
+ ]),
+ );
+
+ const verification = await verifyRegistrationResponse({
+ response: attestationNone,
+ expectedChallenge: challenge,
+ expectedOrigin: origin,
+ expectedRPID: 'dev.dontneeda.pw',
+ expectedType: 'webauthn.goodtype'
+ });
+ assert(verification.verified);
+
+ mockDecodeClientData.restore();
+});
+
Deno.test('should throw if an unexpected attestation format is specified', async () => {
const realAtteObj = decodeAttestationObject(
isoBase64URL.toBuffer(attestationNone.response.attestationObject),
diff --git a/packages/server/src/registration/verifyRegistrationResponse.ts b/packages/server/src/registration/verifyRegistrationResponse.ts
index 154d31a..d2399e8 100644
--- a/packages/server/src/registration/verifyRegistrationResponse.ts
+++ b/packages/server/src/registration/verifyRegistrationResponse.ts
@@ -33,6 +33,7 @@ export type VerifyRegistrationResponseOpts = {
expectedChallenge: string | ((challenge: string) => boolean | Promise<boolean>);
expectedOrigin: string | string[];
expectedRPID?: string | string[];
+ expectedType?: string | string[];
requireUserVerification?: boolean;
supportedAlgorithmIDs?: COSEAlgorithmIdentifier[];
};
@@ -47,6 +48,7 @@ export type VerifyRegistrationResponseOpts = {
* `generateRegistrationOptions()`
* @param expectedOrigin Website URL (or array of URLs) that the registration should have occurred on
* @param expectedRPID RP ID (or array of IDs) that was specified in the registration options
+ * @param expectedType (Optional) The response type expected ('webauthn.create')
* @param requireUserVerification (Optional) Enforce user verification by the authenticator
* (via PIN, fingerprint, etc...)
* @param supportedAlgorithmIDs Array of numeric COSE algorithm identifiers supported for
@@ -60,6 +62,7 @@ export async function verifyRegistrationResponse(
expectedChallenge,
expectedOrigin,
expectedRPID,
+ expectedType,
requireUserVerification = true,
supportedAlgorithmIDs = supportedCOSEAlgorithmIdentifiers,
} = options;
@@ -89,7 +92,16 @@ export async function verifyRegistrationResponse(
const { type, origin, challenge, tokenBinding } = clientDataJSON;
// Make sure we're handling an registration
- if (type !== 'webauthn.create') {
+ if (Array.isArray(expectedType)) {
+ if (!expectedType.includes(type)) {
+ const joinedExpectedType = expectedType.join(', ');
+ throw new Error(`Unexpected registration response type "${type}", expected one of: ${joinedExpectedType}`);
+ }
+ } else if (expectedType) {
+ if (type !== expectedType) {
+ throw new Error(`Unexpected registration response type "${type}", expected "${expectedType}"`);
+ }
+ } else if (type !== 'webauthn.create') {
throw new Error(`Unexpected registration response type: ${type}`);
}