Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -535,15 +535,19 @@ class Fido2Lib {
* @param {String} expected.challenge The base64url encoded challenge that was sent to the client, as generated by [assertionOptions]{@link Fido2Lib#assertionOptions}
* @param {String} expected.origin The expected origin that the authenticator has signed over. For example, "https://localhost:8443" or "https://webauthn.org"
* @param {String} expected.factor Which factor is expected for the assertion. Valid values are "first", "second", or "either".
* If "first", this requires that the authenticator performed user verification (e.g. - biometric authentication, PIN authentication, etc.).
* If "first", this requires that the authenticator performed user presence (UP). User verification (UV) is only
* checked if `expected.userVerification` is set to `"required"`.
* If "second", this requires that the authenticator performed user presence (e.g. - user pressed a button).
* If "either", then either "first" or "second" is acceptable
* @param {String} [expected.userVerification] The expected user verification requirement. Valid values are "required", "preferred", or "discouraged".
* If "required", the authenticator must have performed user verification and the UV flag is checked.
* @return {Promise<Fido2AttestationResult>} Returns a Promise that resolves to a {@link Fido2AttestationResult}
* @throws {Error} If parsing or validation fails
*/
async attestationResult(res, expected) {
expected.flags = factorToFlags(expected.factor, ["AT"]);
expected.flags = factorToFlags(expected.factor, ["AT"], expected.userVerification);
delete expected.factor;
delete expected.userVerification;
return await Fido2AttestationResult.create(res, expected);
Comment on lines 547 to 551
}

Expand All @@ -562,9 +566,12 @@ class Fido2Lib {
* @param {String} expected.challenge The base64url encoded challenge that was sent to the client, as generated by [assertionOptions]{@link Fido2Lib#assertionOptions}
* @param {String} expected.origin The expected origin that the authenticator has signed over. For example, "https://localhost:8443" or "https://webauthn.org"
* @param {String} expected.factor Which factor is expected for the assertion. Valid values are "first", "second", or "either".
* If "first", this requires that the authenticator performed user verification (e.g. - biometric authentication, PIN authentication, etc.).
* If "first", this requires that the authenticator performed user presence (UP). User verification (UV) is only
* checked if `expected.userVerification` is set to `"required"`.
* If "second", this requires that the authenticator performed user presence (e.g. - user pressed a button).
* If "either", then either "first" or "second" is acceptable
* @param {String} [expected.userVerification] The expected user verification requirement. Valid values are "required", "preferred", or "discouraged".
* If "required", the authenticator must have performed user verification and the UV flag is checked.
* @param {String} expected.publicKey A PEM encoded public key that will be used to validate the assertion response signature.
* This is the public key that was returned for this user during [attestationResult]{@link Fido2Lib#attestationResult}
* @param {Number} expected.prevCounter The previous value of the signature counter for this authenticator.
Expand All @@ -574,8 +581,9 @@ class Fido2Lib {
*/
// deno-lint-ignore require-await
async assertionResult(res, expected) {
expected.flags = factorToFlags(expected.factor, []);
expected.flags = factorToFlags(expected.factor, [], expected.userVerification);
delete expected.factor;
delete expected.userVerification;
return Fido2AssertionResult.create(res, expected);
Comment on lines 583 to 587
}

Expand Down Expand Up @@ -811,21 +819,23 @@ function setOpt(obj, prop, val) {
}

/**
* @param {string} expectedFactor - "first" | "second" | "either"
* Appends expected flags to an array based on factor string and optional userVerification requirement.
* See {@link https://www.w3.org/TR/webauthn-3/#authdata-flags Flags Docs on W3}
*
* @param {Array<string>} flags array of flag strings
* @param {string} expectedFactor - "first" | "second" | "either"
* @param {Array<string>} flags - array of flag strings to append to
* @param {string} [userVerification] - "required" | "preferred" | "discouraged".
* When "required", the UV flag is added for factor "first" per WebAuthn spec step 16.
*/


function factorToFlags(expectedFactor, flags) {
// var flags = ["AT"];
function factorToFlags(expectedFactor, flags, userVerification) {
flags = flags || [];

switch (expectedFactor) {
case "first":
flags.push("UP");
flags.push("UV");
if (userVerification === "required") {
flags.push("UV");
}
break;
case "second":
flags.push("UP");
Expand Down
94 changes: 94 additions & 0 deletions test/main.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,40 @@ describe("Fido2Lib", function() {
});

it("catches bad requests");

it("validates attestation with factor first and no userVerification does not require UV", async function() {
const expectations = {
challenge: "33EHav-jZ1v9qwH783aU-j0ARx6r5o-YHh-wd7C6jPbd7Wh6ytbIZosIIACehwf9-s6hXhySHO-HHUjEwZS29w",
origin: "https://localhost:8443",
factor: "first",
};

const res = await serv.attestationResult(
h.lib.makeCredentialAttestationNoneResponse,
expectations,
);

assert.instanceOf(res, Fido2AttestationResult);
return res;
});

it("rejects attestation with factor first and userVerification required when UV not set", async function() {
const expectations = {
challenge: "33EHav-jZ1v9qwH783aU-j0ARx6r5o-YHh-wd7C6jPbd7Wh6ytbIZosIIACehwf9-s6hXhySHO-HHUjEwZS29w",
origin: "https://localhost:8443",
factor: "first",
userVerification: "required",
};

return assert.isRejected(
serv.attestationResult(
h.lib.makeCredentialAttestationNoneResponse,
expectations,
),
Error,
"expected flag was not set: UV",
);
});
});

describe("assertionOptions", function() {
Expand Down Expand Up @@ -754,6 +788,66 @@ describe("Fido2Lib", function() {
return res;
});
});

it("valid assertion with factor first and no userVerification does not require UV", function() {
const expectations = {
challenge: "eaTyUNnyPDDdK8SNEgTEUvz1Q8dylkjjTimYd5X7QAo-F8_Z1lsJi3BilUpFZHkICNDWY8r9ivnTgW7-XZC3qQ",
origin: "https://localhost:8443",
factor: "first",
publicKey: h.lib.assnPublicKey,
prevCounter: 362,
userHandle: null,
};

return serv.assertionResult(h.lib.assertionResponse, expectations)
.then((res) => {
assert.instanceOf(res, Fido2AssertionResult);
return res;
});
});

it("valid assertion with factor first and userVerification required", function() {
const expectations = {
challenge: "g_Pu32bpluktxugNNBLX-ZO5N9ub0D50bJERbKiU2GWON3md0rR9CaQYdPHdCgo-dpi1-9gbJJvmCuHDnh04Rg",
origin: "https://mighty-fireant-84.loca.lt",
factor: "first",
userVerification: "required",
publicKey: "-----BEGIN PUBLIC KEY-----\n" +
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0dBhdNNvh2NkaNstlFhrBhi9yrjP\n" +
"0qPqZvRRnf3zQiN9zDwJ9ZXoyO4dhKz3OIhMBJG6F+muH35fEsWBZI6dhg==\n" +
"-----END PUBLIC KEY-----\n",
prevCounter: 0,
userHandle: null,
};

let assertionResponse = {
rawId: coerceToArrayBuffer("7S8aQSSxqPkztahKbgw36Mr_-hE", "rawId"),
response: {
authenticatorData: coerceToArrayBuffer("YS67HU8UTNyqQ5f-EVzitWw5paVnpyhQli2ahN6PS6UFAAAAAA", "authenticatorData"),
clientDataJSON: coerceToArrayBuffer("eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiZ19QdTMyYnBsdWt0eHVnTk5CTFgtWk81Tjl1YjBENTBiSkVSYktpVTJHV09OM21kMHJSOUNhUVlkUEhkQ2dvLWRwaTEtOWdiSkp2bUN1SERuaDA0UmciLCJvcmlnaW4iOiJodHRwczovL21pZ2h0eS1maXJlYW50LTg0LmxvY2EubHQifQ", "clientDataJSON"),
signature: coerceToArrayBuffer("MEQCIEhIhQBglBn1iGMDgF4WFDG7ISJHD1C1Q60drTaijjV2AiBOnQleadMnzcMJ0EBpwoP8zr2V5lBuKvpNfJrcbC1T4w", "signature"),
},
};

return serv.assertionResult(assertionResponse, expectations).then((res) => {
assert.instanceOf(res, Fido2AssertionResult);
return res;
});
});

it("rejects assertion with factor first and userVerification required when UV not set", function() {
const expectations = {
challenge: "eaTyUNnyPDDdK8SNEgTEUvz1Q8dylkjjTimYd5X7QAo-F8_Z1lsJi3BilUpFZHkICNDWY8r9ivnTgW7-XZC3qQ",
origin: "https://localhost:8443",
factor: "first",
userVerification: "required",
publicKey: h.lib.assnPublicKey,
prevCounter: 362,
userHandle: null,
};

return assert.isRejected(serv.assertionResult(h.lib.assertionResponse, expectations), Error, "expected flag was not set: UV");
});
});

describe("addAttestationFormat", function() {
Expand Down
2 changes: 2 additions & 0 deletions types/main.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ declare module "fido2-lib" {
origin: string;
challenge: string;
factor: Factor;
userVerification?: UserVerification;
}

interface Fido2AttestationResult {
Expand Down Expand Up @@ -153,6 +154,7 @@ declare module "fido2-lib" {
publicKey: string;
prevCounter: number;
userHandle: string | null;
userVerification?: UserVerification;
allowCredentials?: PublicKeyCredentialDescriptor[];
}

Expand Down