diff --git a/lib/response.js b/lib/response.js index ec38718a..0e9e0a58 100644 --- a/lib/response.js +++ b/lib/response.js @@ -48,6 +48,7 @@ class Fido2Result { await this.validateRawAuthnrData(); await this.validateRpIdHash(); await this.validateFlags(); + await this.validateExtensions(); } async create(req, exp) { diff --git a/lib/validator.js b/lib/validator.js index 66c1bb39..c012580c 100644 --- a/lib/validator.js +++ b/lib/validator.js @@ -613,6 +613,27 @@ async function validatePublicKey() { return true; } +function validateExtensions() { + const extensions = this.authnrData.get("webAuthnExtensions"); + const shouldHaveExtensions = this.authnrData.get("flags").has("ED"); + + if (shouldHaveExtensions) { + if (Array.isArray(extensions) && + extensions.every(item => typeof item === "object") + ) { + this.audit.journal.add("webAuthnExtensions"); + } else { + throw new Error("webAuthnExtensions aren't valid"); + } + } else { + if (extensions !== undefined) { + throw new Error("unexpected webAuthnExtensions found"); + } + } + + return true; +} + async function validateUserHandle() { let userHandle = this.authnrData.get("userHandle"); @@ -706,6 +727,7 @@ function attach(o) { validateAaguid, validateCredId, validatePublicKey, + validateExtensions, validateFlags, validateUserHandle, validateCounter, diff --git a/package.json b/package.json index e9a42c26..4c93e08c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fido2-lib", - "version": "3.3.1", + "version": "3.3.2", "description": "A library for performing FIDO 2.0 / WebAuthn functionality", "type": "module", "main": "dist/main.cjs", diff --git a/test/validator.test.js b/test/validator.test.js index 585f40a8..9a2e9ffc 100644 --- a/test/validator.test.js +++ b/test/validator.test.js @@ -722,6 +722,34 @@ describe("attestation validation", function() { }); }); + describe("validateExtensions", function() { + // original test data does not contain extensions + it("returns true on validation without extensions", async function() { + const ret = attResp.validateExtensions(); + assert.isTrue(ret); + assert.isFalse(attResp.audit.journal.has("webAuthnExtensions")); + }); + + it("returns true on validation with extensions", async function() { + attResp.authnrData.get("flags").add("ED"); + attResp.authnrData.set("webAuthnExtensions", [{ credProtect: 1 }]); + const ret = attResp.validateExtensions(); + assert.isTrue(ret); + assert.isTrue(attResp.audit.journal.has("webAuthnExtensions")); + }); + + it("throws on invalid extensions", async function() { + attResp.authnrData.get("flags").add("ED"); + attResp.authnrData.set("webAuthnExtensions", [42]); + assert.throws(() => attResp.validateExtensions(), Error, "webAuthnExtensions aren't valid"); + }); + + it("throws on unexpected extensions", async function() { + attResp.authnrData.set("webAuthnExtensions", [{ credProtect: 1 }]); + assert.throws(() => attResp.validateExtensions(), Error, "unexpected webAuthnExtensions found"); + }); + }); + describe("validateTokenBinding", function() { it("returns true if tokenBinding is undefined", async function() { const ret = await attResp.validateTokenBinding(); @@ -828,6 +856,7 @@ describe("attestation validation", function() { await attResp.validateAaguid(); await attResp.validateCredId(); await attResp.validatePublicKey(); + await attResp.validateExtensions(); await attResp.validateFlags(); await attResp.validateInitialCounter();