summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMatthew Miller <matthew@millerti.me>2021-01-21 14:07:41 -0800
committerGitHub <noreply@github.com>2021-01-21 14:07:41 -0800
commitc7bce757ebb64dfe856e62326b2045761b70ea48 (patch)
tree9ba5e07bb8e6aeb072d63287d86ada37b45a2d74
parent9ad5f7d214a707af9c3f9d6d078323e41ce1a017 (diff)
parent5fa62a7f182028ddb25d4ae450756e2f290efbb7 (diff)
Merge pull request #91 from MasterKale/feature/multiple-origins
feature/multiple-origins
-rw-r--r--package.json4
-rw-r--r--packages/server/src/assertion/verifyAssertionResponse.test.ts48
-rw-r--r--packages/server/src/assertion/verifyAssertionResponse.ts38
-rw-r--r--packages/server/src/attestation/verifyAttestationResponse.test.ts44
-rw-r--r--packages/server/src/attestation/verifyAttestationResponse.ts38
5 files changed, 152 insertions, 20 deletions
diff --git a/package.json b/package.json
index 6d13b34..d013745 100644
--- a/package.json
+++ b/package.json
@@ -22,14 +22,14 @@
"@typescript-eslint/parser": "^3.10.1",
"eslint": "^7.8.1",
"husky": "^4.3.0",
- "jest": "^25.1.0",
+ "jest": "^26.6.3",
"jest-environment-jsdom": "^26.3.0",
"lerna": "^3.22.1",
"lint-staged": "^10.3.0",
"prettier": "^2.1.1",
"rimraf": "^3.0.2",
"semver": "^7.3.2",
- "ts-jest": "^25.5.1",
+ "ts-jest": "^26.4.4",
"ts-morph": "^9.0.0",
"ts-node": "^8.10.2",
"ttypescript": "^1.5.12",
diff --git a/packages/server/src/assertion/verifyAssertionResponse.test.ts b/packages/server/src/assertion/verifyAssertionResponse.test.ts
index 3f97959..4e4b64f 100644
--- a/packages/server/src/assertion/verifyAssertionResponse.test.ts
+++ b/packages/server/src/assertion/verifyAssertionResponse.test.ts
@@ -207,6 +207,54 @@ test.skip('should verify TPM assertion', () => {
expect(verification.verified).toEqual(true);
});
+test('should support multiple possible origins', () => {
+ const verification = verifyAssertionResponse({
+ credential: assertionResponse,
+ expectedChallenge: assertionChallenge,
+ expectedOrigin: ['https://simplewebauthn.dev', assertionOrigin],
+ expectedRPID: 'dev.dontneeda.pw',
+ authenticator: authenticator,
+ });
+
+ expect(verification.verified).toEqual(true);
+});
+
+test('should throw an error if origin not in list of expected origins', async () => {
+ expect(() => {
+ verifyAssertionResponse({
+ credential: assertionResponse,
+ expectedChallenge: assertionChallenge,
+ expectedOrigin: ['https://simplewebauthn.dev', 'https://fizz.buzz'],
+ expectedRPID: 'dev.dontneeda.pw',
+ authenticator: authenticator,
+ });
+ }).toThrow(/unexpected assertion origin/i);
+});
+
+test('should support multiple possible RP IDs', async () => {
+ const verification = verifyAssertionResponse({
+ credential: assertionResponse,
+ expectedChallenge: assertionChallenge,
+ expectedOrigin: assertionOrigin,
+ expectedRPID: ['dev.dontneeda.pw', 'simplewebauthn.dev'],
+ authenticator: authenticator,
+ });
+
+ expect(verification.verified).toEqual(true);
+});
+
+test('should throw an error if RP ID not in list of possible RP IDs', async () => {
+ expect(() => {
+ verifyAssertionResponse({
+ credential: assertionResponse,
+ expectedChallenge: assertionChallenge,
+ expectedOrigin: assertionOrigin,
+ expectedRPID: ['simplewebauthn.dev'],
+ authenticator: authenticator,
+ });
+ }).toThrow(/unexpected rp id/i);
+});
+
/**
* Assertion examples below
*/
diff --git a/packages/server/src/assertion/verifyAssertionResponse.ts b/packages/server/src/assertion/verifyAssertionResponse.ts
index 993b7e9..0c76fae 100644
--- a/packages/server/src/assertion/verifyAssertionResponse.ts
+++ b/packages/server/src/assertion/verifyAssertionResponse.ts
@@ -15,8 +15,8 @@ import isBase64URLString from '../helpers/isBase64URLString';
type Options = {
credential: AssertionCredentialJSON;
expectedChallenge: string;
- expectedOrigin: string;
- expectedRPID: string;
+ expectedOrigin: string | string[];
+ expectedRPID: string | string[];
authenticator: AuthenticatorDevice;
fidoUserVerification?: UserVerificationRequirement;
};
@@ -29,8 +29,8 @@ type Options = {
* @param credential Authenticator credential returned by browser's `startAssertion()`
* @param expectedChallenge The base64url-encoded `options.challenge` returned by
* `generateAssertionOptions()`
- * @param expectedOrigin Website URL that the attestation should have occurred on
- * @param expectedRPID RP ID that was specified in the attestation options
+ * @param expectedOrigin Website URL (or array of URLs) that the attestation should have occurred on
+ * @param expectedRPID RP ID (or array of IDs) that was specified in the attestation options
* @param authenticator An internal {@link AuthenticatorDevice} matching the credential's ID
* @param fidoUserVerification (Optional) The value specified for `userVerification` when calling
* `generateAssertionOptions()`. Activates FIDO-specific user presence and verification checks.
@@ -87,8 +87,16 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser
}
// Check that the origin is our site
- if (origin !== expectedOrigin) {
- throw new Error(`Unexpected assertion origin "${origin}", expected "${expectedOrigin}"`);
+ if (Array.isArray(expectedOrigin)) {
+ if (!expectedOrigin.includes(origin)) {
+ throw new Error(
+ `Unexpected assertion origin "${origin}", expected one of: ${expectedOrigin.join(', ')}`,
+ );
+ }
+ } else {
+ if (origin !== expectedOrigin) {
+ throw new Error(`Unexpected assertion origin "${origin}", expected "${expectedOrigin}"`);
+ }
}
if (!isBase64URLString(response.authenticatorData)) {
@@ -118,9 +126,21 @@ export default function verifyAssertionResponse(options: Options): VerifiedAsser
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`);
+ if (typeof expectedRPID === 'string') {
+ const expectedRPIDHash = toHash(Buffer.from(expectedRPID, 'ascii'));
+ if (!rpIdHash.equals(expectedRPIDHash)) {
+ throw new Error(`Unexpected RP ID hash`);
+ }
+ } else {
+ // Go through each expected RP ID and try to find one that matches
+ const foundMatch = expectedRPID.some(expected => {
+ const expectedRPIDHash = toHash(Buffer.from(expected, 'ascii'));
+ return rpIdHash.equals(expectedRPIDHash);
+ });
+
+ if (!foundMatch) {
+ throw new Error(`Unexpected RP ID hash`);
+ }
}
// Enforce user verification if required
diff --git a/packages/server/src/attestation/verifyAttestationResponse.test.ts b/packages/server/src/attestation/verifyAttestationResponse.test.ts
index 99bf653..a6e0814 100644
--- a/packages/server/src/attestation/verifyAttestationResponse.test.ts
+++ b/packages/server/src/attestation/verifyAttestationResponse.test.ts
@@ -438,6 +438,50 @@ test('should validate Android-Key response', async () => {
);
});
+test('should support multiple possible origins', async () => {
+ const verification = await verifyAttestationResponse({
+ credential: attestationNone,
+ expectedChallenge: attestationNoneChallenge,
+ expectedOrigin: ['https://dev.dontneeda.pw', 'https://different.address'],
+ expectedRPID: 'dev.dontneeda.pw',
+ });
+
+ expect(verification.verified).toBe(true);
+});
+
+test('should throw an error if origin not in list of expected origins', async () => {
+ await expect(
+ verifyAttestationResponse({
+ credential: attestationNone,
+ expectedChallenge: attestationNoneChallenge,
+ expectedOrigin: ['https://different.address'],
+ expectedRPID: 'dev.dontneeda.pw',
+ }),
+ ).rejects.toThrow(/unexpected attestation origin/i);
+});
+
+test('should support multiple possible RP IDs', async () => {
+ const verification = await verifyAttestationResponse({
+ credential: attestationNone,
+ expectedChallenge: attestationNoneChallenge,
+ expectedOrigin: 'https://dev.dontneeda.pw',
+ expectedRPID: ['dev.dontneeda.pw', 'simplewebauthn.dev'],
+ });
+
+ expect(verification.verified).toBe(true);
+});
+
+test('should throw an error if RP ID not in list of possible RP IDs', async () => {
+ await expect(
+ verifyAttestationResponse({
+ credential: attestationNone,
+ expectedChallenge: attestationNoneChallenge,
+ expectedOrigin: 'https://dev.dontneeda.pw',
+ expectedRPID: ['simplewebauthn.dev'],
+ }),
+ ).rejects.toThrow(/unexpected rp id/i);
+});
+
/**
* Various Attestations Below
*/
diff --git a/packages/server/src/attestation/verifyAttestationResponse.ts b/packages/server/src/attestation/verifyAttestationResponse.ts
index 8a213f0..3940ea6 100644
--- a/packages/server/src/attestation/verifyAttestationResponse.ts
+++ b/packages/server/src/attestation/verifyAttestationResponse.ts
@@ -22,8 +22,8 @@ import verifyApple from './verifications/verifyApple';
type Options = {
credential: AttestationCredentialJSON;
expectedChallenge: string;
- expectedOrigin: string;
- expectedRPID?: string;
+ expectedOrigin: string | string[];
+ expectedRPID?: string | string[];
requireUserVerification?: boolean;
supportedAlgorithmIDs?: COSEAlgorithmIdentifier[];
};
@@ -36,8 +36,8 @@ type Options = {
* @param credential Authenticator credential returned by browser's `startAttestation()`
* @param expectedChallenge The base64url-encoded `options.challenge` returned by
* `generateAttestationOptions()`
- * @param expectedOrigin Website URL that the attestation should have occurred on
- * @param expectedRPID RP ID that was specified in the attestation options
+ * @param expectedOrigin Website URL (or array of URLs) that the attestation should have occurred on
+ * @param expectedRPID RP ID (or array of IDs) that was specified in the attestation options
* @param requireUserVerification (Optional) Enforce user verification by the authenticator
* (via PIN, fingerprint, etc...)
* @param supportedAlgorithmIDs Array of numeric COSE algorithm identifiers supported for
@@ -88,8 +88,16 @@ export default async function verifyAttestationResponse(
}
// Check that the origin is our site
- if (origin !== expectedOrigin) {
- throw new Error(`Unexpected attestation origin "${origin}", expected "${expectedOrigin}"`);
+ if (Array.isArray(expectedOrigin)) {
+ if (!expectedOrigin.includes(origin)) {
+ throw new Error(
+ `Unexpected attestation origin "${origin}", expected one of: ${expectedOrigin.join(', ')}`,
+ );
+ }
+ } else {
+ if (origin !== expectedOrigin) {
+ throw new Error(`Unexpected attestation origin "${origin}", expected "${expectedOrigin}"`);
+ }
}
if (tokenBinding) {
@@ -110,9 +118,21 @@ export default async function verifyAttestationResponse(
// Make sure the response's RP ID is ours
if (expectedRPID) {
- const expectedRPIDHash = toHash(Buffer.from(expectedRPID, 'ascii'));
- if (!rpIdHash.equals(expectedRPIDHash)) {
- throw new Error(`Unexpected RP ID hash`);
+ if (typeof expectedRPID === 'string') {
+ const expectedRPIDHash = toHash(Buffer.from(expectedRPID, 'ascii'));
+ if (!rpIdHash.equals(expectedRPIDHash)) {
+ throw new Error(`Unexpected RP ID hash`);
+ }
+ } else {
+ // Go through each expected RP ID and try to find one that matches
+ const foundMatch = expectedRPID.some(expected => {
+ const expectedRPIDHash = toHash(Buffer.from(expected, 'ascii'));
+ return rpIdHash.equals(expectedRPIDHash);
+ });
+
+ if (!foundMatch) {
+ throw new Error(`Unexpected RP ID hash`);
+ }
}
}