Skip to main content
Module

x/fido2/test/main.test.js

A node.js library for performing FIDO 2.0 / WebAuthn server functionality
Latest
File
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055
// Testing libimport * as chai from "chai";import * as chaiAsPromised from "chai-as-promised";
// Helpersimport * as h from "./helpers/fido2-helpers.js";import { Stub } from "./helpers/stub.js";import { packedSelfAttestationResponse } from "./fixtures/packedSelfAttestationData.js";

// Test subjectimport { arrayBufferEquals, abToBuf, androidSafetyNetAttestation, appleAttestation, appendBuffer, coerceToArrayBuffer, Fido2AssertionResult, Fido2AttestationResult, fidoU2fAttestation, MdsCollection, MdsEntry, noneAttestation, packedAttestation, tools, tpmAttestation, Fido2Lib} from "../lib/main.js";chai.use(chaiAsPromised.default);const { assert } = chai;
// Test subjectfunction restoreAttestationFormats() { // add 'none' attestation format Fido2Lib.addAttestationFormat( noneAttestation.name, noneAttestation.parseFn, noneAttestation.validateFn, ); // add 'u2f' attestation format Fido2Lib.addAttestationFormat( fidoU2fAttestation.name, fidoU2fAttestation.parseFn, fidoU2fAttestation.validateFn, ); // add 'packed' attestation format Fido2Lib.addAttestationFormat( packedAttestation.name, packedAttestation.parseFn, packedAttestation.validateFn ); // add 'tpm' attestation format Fido2Lib.addAttestationFormat( tpmAttestation.name, tpmAttestation.parseFn, tpmAttestation.validateFn ); // add 'android-safetynet' attestation format Fido2Lib.addAttestationFormat( androidSafetyNetAttestation.name, androidSafetyNetAttestation.parseFn, androidSafetyNetAttestation.validateFn ); // add 'apple' attestation format Fido2Lib.addAttestationFormat( appleAttestation.name, appleAttestation.parseFn, appleAttestation.validateFn );}
describe("Fido2Lib", function() { it("can create FIDO server object", function() { const fs = new Fido2Lib(); assert.instanceOf(fs, Fido2Lib); assert.isFunction(fs.attestationOptions); assert.isFunction(fs.attestationResult); assert.isFunction(fs.assertionOptions); assert.isFunction(fs.assertionResult); });
describe("config", function() { it("can config timeout", function() { const fs = new Fido2Lib({ timeout: 42, }); assert.strictEqual(fs.config.timeout, 42); });
it("can config zero timeout", function() { const fs = new Fido2Lib({ timeout: 0, }); assert.strictEqual(fs.config.timeout, 0); });
it("has default timeout", function() { const fs = new Fido2Lib(); assert.strictEqual(fs.config.timeout, 60000); });
it("throws on bad timeout", function() { assert.throws(function() { new Fido2Lib({ timeout: "foo", }); }, TypeError, "expected timeout to be number, got: foo"); });
it("throws on NaN timeout", function() { assert.throws(function() { new Fido2Lib({ timeout: NaN, }); }, RangeError, "timeout should be zero or positive integer"); });
it("throws on floating point timeout", function() { assert.throws(function() { new Fido2Lib({ timeout: 3.14, }); }, RangeError, "timeout should be zero or positive integer"); });
it("throws on negative timeout", function() { assert.throws(function() { new Fido2Lib({ timeout: -1, }); }, RangeError, "timeout should be zero or positive integer"); });
it("can config rpId", function() { const fs = new Fido2Lib({ rpId: "example.com", }); assert.strictEqual(fs.config.rpId, "example.com"); });
it("throws on bad rpId", function() { assert.throws(function() { new Fido2Lib({ rpId: -1, }); }, TypeError, "expected rpId to be string, got: -1"); });
it("can config rpName", function() { const fs = new Fido2Lib({ rpName: "ACME", }); assert.strictEqual(fs.config.rpName, "ACME"); });
it("has default rpName", function() { const fs = new Fido2Lib(); assert.strictEqual(fs.config.rpName, "Anonymous Service"); });
it("throws on bad rpName", function() { assert.throws(function() { new Fido2Lib({ rpName: -1, }); }, TypeError, "expected rpName to be string, got: -1"); });
it("can config rpIcon", function() { const fs = new Fido2Lib({ rpIcon: "https://example.com/foo.png", }); assert.strictEqual(fs.config.rpIcon, "https://example.com/foo.png"); });
it("throws on bad rpIcon", function() { assert.throws(function() { new Fido2Lib({ rpIcon: -1, }); }, TypeError, "expected rpIcon to be string, got: -1"); });
it("can config challengeSize", function() { const fs = new Fido2Lib({ challengeSize: 128, }); assert.strictEqual(fs.config.challengeSize, 128); });
it("has default challengeSize", function() { const fs = new Fido2Lib(); assert.strictEqual(fs.config.challengeSize, 64); });
it("throws if challengeSize too small", function() { assert.throws(function() { new Fido2Lib({ challengeSize: 31, }); }, RangeError, "challenge size too small"); });
it("throws on bad challengeSize", function() { assert.throws(function() { new Fido2Lib({ challengeSize: "foo", }); }, TypeError, "expected challengeSize to be number, got: foo"); });
it("can config direct attestation", function() { const fs = new Fido2Lib({ attestation: "direct", }); assert.strictEqual(fs.config.attestation, "direct"); });
it("can config indirect attestation", function() { const fs = new Fido2Lib({ attestation: "indirect", }); assert.strictEqual(fs.config.attestation, "indirect"); });
it("can config none attestation", function() { const fs = new Fido2Lib({ attestation: "none", }); assert.strictEqual(fs.config.attestation, "none"); });
it("can config defautl attestation", function() { const fs = new Fido2Lib(); assert.strictEqual(fs.config.attestation, "direct"); });
it("throws on bad attestation string", function() { assert.throws(function() { new Fido2Lib({ attestation: "foo", }); }, TypeError, "expected attestation to be 'direct', 'indirect', or 'none', got: foo"); });
it("throws on bad attestation type", function() { assert.throws(function() { new Fido2Lib({ attestation: -1, }); }, TypeError, "expected attestation to be 'direct', 'indirect', or 'none', got: -1"); });
it("can config authenticatorAttachment to platform", function() { const fs = new Fido2Lib({ authenticatorAttachment: "platform", }); assert.strictEqual(fs.config.authenticatorAttachment, "platform"); });
it("can config authenticatorAttachment to cross-platform", function() { const fs = new Fido2Lib({ authenticatorAttachment: "cross-platform", }); assert.strictEqual(fs.config.authenticatorAttachment, "cross-platform"); });
it("throws if authenticatorAttachment isn't platform or cross-platform", function() { assert.throws(function() { new Fido2Lib({ authenticatorAttachment: "bob", }); }, TypeError, "expected authenticatorAttachment to be 'platform', or 'cross-platform', got: bob"); });
it("can config authenticatorRequireResidentKey to false", function() { let fs = new Fido2Lib({ authenticatorRequireResidentKey: false, }); assert.strictEqual(fs.config.authenticatorRequireResidentKey, false); });
it("can config authenticatorRequireResidentKey to true", function() { const fs = new Fido2Lib({ authenticatorRequireResidentKey: true, }); assert.strictEqual(fs.config.authenticatorRequireResidentKey, true); });
it("throws if authenticatorRequireResidentKey is non-boolean", function() { assert.throws(function() { new Fido2Lib({ authenticatorRequireResidentKey: 0, }); }, TypeError, "expected authenticatorRequireResidentKey to be boolean, got: 0"); });
it("can config authenticatorUserVerification to discouraged", function() { let fs = new Fido2Lib({ authenticatorUserVerification: "discouraged", }); assert.strictEqual(fs.config.authenticatorUserVerification, "discouraged"); });
it("can config authenticatorUserVerification to preferred", function() { const fs = new Fido2Lib({ authenticatorUserVerification: "preferred", }); assert.strictEqual( fs.config.authenticatorUserVerification, "preferred", ); });
it("can config authenticatorUserVerification to required", function() { const fs = new Fido2Lib({ authenticatorUserVerification: "required", }); assert.strictEqual(fs.config.authenticatorUserVerification, "required"); });
it("throws if authenticatorUserVerification is not required, preferred, or discouraged", function() { assert.throws(function() { new Fido2Lib({ authenticatorUserVerification: "bob", }); }, TypeError, "expected authenticatorUserVerification to be 'required', 'preferred', or 'discouraged', got: bob"); });
it("can config cryptoParams order", function() { const fs = new Fido2Lib({ cryptoParams: [-257, -7], }); assert.deepEqual(fs.config.cryptoParams, [-257, -7]); });
it("can config cryptoParams value", function() { const fs = new Fido2Lib({ cryptoParams: [-8], }); assert.deepEqual(fs.config.cryptoParams, [-8]); });
it("can config cryptoParams value", function() { const fs = new Fido2Lib(); assert.deepEqual(fs.config.cryptoParams, [-7, -257]); });
it("throws on bad cryptoParams", function() { assert.throws(function() { new Fido2Lib({ cryptoParams: "bob", }); }, TypeError, "expected cryptoParams to be Array, got: bob"); });
it("throws on bad value inside cryptoParams", function() { assert.throws(function() { new Fido2Lib({ cryptoParams: [-7, "bob", -257], }); }, TypeError, "expected cryptoParam to be number, got: bob"); });
it("throws on empty cryptoParams", function() { assert.throws(function() { new Fido2Lib({ cryptoParams: [], }); }, TypeError, "cryptoParams must have at least one element"); }); });
describe("attestationOptions", function() { let serv; beforeEach(function() { serv = new Fido2Lib(); });
it("returns options", function() { return serv.attestationOptions().then((opts) => { assert.isObject(opts); }); });
it("returns a challenge", function() { return serv.attestationOptions().then((opts) => { assert.instanceOf(opts.challenge, ArrayBuffer); assert.strictEqual(opts.challenge.byteLength, 64); }); });
it("returns a timeout", function() { return serv.attestationOptions().then((opts) => { assert.isNumber(opts.timeout); assert.strictEqual(opts.timeout, 60000); }); });
it("picks up values from constructors options", function() { serv = new Fido2Lib({ timeout: 42, rpId: "example.com", rpName: "ACME", rpIcon: "https://example.com/logo.png", challengeSize: 128, attestation: "none", cryptoParams: [-8, -9], authenticatorAttachment: "platform", authenticatorRequireResidentKey: false, authenticatorUserVerification: "required", });
return serv.attestationOptions().then((opts) => { assert.isObject(opts); assert.isNumber(opts.timeout); assert.strictEqual(opts.timeout, 42); assert.isObject(opts.rp); assert.isString(opts.rp.id); assert.strictEqual(opts.rp.id, "example.com"); assert.isString(opts.rp.name); assert.strictEqual(opts.rp.name, "ACME"); assert.isString(opts.rp.icon); assert.strictEqual(opts.rp.icon, "https://example.com/logo.png"); assert.instanceOf(opts.challenge, ArrayBuffer); assert.strictEqual(opts.challenge.byteLength, 128); assert.isArray(opts.pubKeyCredParams); assert.strictEqual(opts.pubKeyCredParams.length, 2); assert.deepEqual(opts.pubKeyCredParams, [ { type: "public-key", alg: -8, }, { type: "public-key", alg: -9, }, ]); assert.isNumber(opts.timeout); assert.strictEqual(opts.timeout, 42); assert.isObject(opts.authenticatorSelection); assert.isString(opts.authenticatorSelection.authenticatorAttachment); assert.strictEqual(opts.authenticatorSelection.authenticatorAttachment, "platform"); assert.isBoolean(opts.authenticatorSelection.requireResidentKey); assert.strictEqual(opts.authenticatorSelection.requireResidentKey, false); assert.isString(opts.authenticatorSelection.userVerification); assert.strictEqual(opts.authenticatorSelection.userVerification, "required"); assert.isString(opts.attestation); assert.strictEqual(opts.attestation, "none"); }); });
it("accepts extraData and returns rawChallenge", async function() { const extraData = new Uint8Array([0x1, 0x2, 0x3, 0x4]).buffer; const opts = await serv.attestationOptions({ extraData, });
const challenge = opts.challenge; const calculatedChallenge = await tools.hashDigest( appendBuffer(opts.rawChallenge, extraData), ); assert.isTrue(arrayBufferEquals(challenge, calculatedChallenge), "extraData hashes match"); }); });
describe("attestationResult", function() { let serv; beforeEach(function() { serv = new Fido2Lib(); });
it("validates a credential request with 'none' attestation", async function() { const expectations = { challenge: "33EHav-jZ1v9qwH783aU-j0ARx6r5o-YHh-wd7C6jPbd7Wh6ytbIZosIIACehwf9-s6hXhySHO-HHUjEwZS29w", origin: "https://localhost:8443", factor: "either", };
const res = await serv.attestationResult( h.lib.makeCredentialAttestationNoneResponse, expectations, );
assert.instanceOf(res, Fido2AttestationResult); return res; });
it("validates a credential request with 'u2f' attestation");
it("validates a packed credential that has self attestation", async function() { const expectations = { challenge: "zBNZ9XmBj4cu7xxYI_uSJauAj89yOTZX1xEqKxhQydhYCTdoKB0k8bzs3llRrBxQlNn3WyRovWvYAXmuIiswLQ", origin: "http://localhost:3000", factor: "either", };
const parsedPackedSelfAttestationResponse = { ...packedSelfAttestationResponse, id: tools.base64.toArrayBuffer(packedSelfAttestationResponse.id), rawId: tools.base64.toArrayBuffer(packedSelfAttestationResponse.rawId), response: { attestationObject: tools.base64.toArrayBuffer(packedSelfAttestationResponse.response.attestationObject), clientDataJSON: tools.base64.toArrayBuffer(packedSelfAttestationResponse.response.clientDataJSON), }, };
const res = await serv.attestationResult( parsedPackedSelfAttestationResponse, expectations, );
assert.instanceOf(res, Fido2AttestationResult); return res; });
it("validates a credential request with 'android-safetynet' attestation", function() { const serv = new Fido2Lib(); const expectations = { challenge: "NrRzgRhGy5Y0NlKNhEAqs4ZFVgNGtN49ZyCTOfLk8G1EPY3vnN3zasIZynlCAyUOLdB3-AALfy1XG2MiVps_Vw", origin: "https://contubernio.tic.udc.es", factor: "second", };
const makeCredentialAttestationSafetyNetResponse = { rawId: coerceToArrayBuffer("AcaOtf577JrxNa9lHZ9g1Npx2YgKhU0w-F_fFkzbOZNZRmh4_S4NFXBBOH75Jf5NS76jK9vcuRiamDIn63Jxxw0","rawId"), response: { attestationObject: coerceToArrayBuffer( "o2NmbXRxYW5kcm9pZC1zYWZldHluZXRnYXR0U3RtdKJjdmVyaTIwMTgxNzAxN2hyZXNwb25zZVkU3mV5SmhiR2NpT2lKU1V6STFOaUlzSW5nMVl5STZXeUpOU1VsR2EzcERRMEpJZFdkQmQwbENRV2RKVWtGT1kxTnJhbVJ6Tlc0MkswTkJRVUZCUVVGd1lUQmpkMFJSV1VwTGIxcEphSFpqVGtGUlJVeENVVUYzVVdwRlRFMUJhMGRCTVZWRlFtaE5RMVpXVFhoSWFrRmpRbWRPVmtKQmIxUkdWV1IyWWpKa2MxcFRRbFZqYmxaNlpFTkNWRnBZU2pKaFYwNXNZM3BGVkUxQ1JVZEJNVlZGUVhoTlMxSXhVbFJKUlU1Q1NVUkdVRTFVUVdWR2R6QjVUVVJCZUUxVVRYaE5WRkY0VGtSc1lVWjNNSGxOVkVGNFRWUkZlRTFVVVhoT1JHeGhUVWQzZUVONlFVcENaMDVXUWtGWlZFRnNWbFJOVWsxM1JWRlpSRlpSVVVsRmQzQkVXVmQ0Y0ZwdE9YbGliV3hvVFZKWmQwWkJXVVJXVVZGSVJYY3hUbUl6Vm5Wa1IwWndZbWxDVjJGWFZqTk5VazEzUlZGWlJGWlJVVXRGZDNCSVlqSTVibUpIVldkVVJYaEVUVkp6ZDBkUldVUldVVkZFUlhoS2FHUklVbXhqTTFGMVdWYzFhMk50T1hCYVF6VnFZakl3ZDJkbl"+ "JXbE5RVEJIUTFOeFIxTkpZak5FVVVWQ1FWRlZRVUUwU1VKRWQwRjNaMmRGUzBGdlNVSkJVVU5YUlhKQ1VWUkhXa2RPTVdsYVlrNDVaV2hTWjJsbVYwSjRjV2t5VUdSbmVIY3dNMUEzVkhsS1dtWk5lR3B3TlV3M2FqRkhUbVZRU3pWSWVtUnlWVzlKWkRGNVEwbDVRazE1ZUhGbllYcHhaM1J3V0RWWGNITllWelJXWmsxb1NtSk9NVmt3T1hGNmNYQTJTa1FyTWxCYVpHOVVWVEZyUmxKQlRWZG1UQzlWZFZwMGF6ZHdiVkpZWjBkdE5XcExSSEphT1U1NFpUQTBkazFaVVhJNE9FNXhkMWN2YTJaYU1XZFVUMDVKVlZRd1YzTk1WQzgwTlRJeVFsSlhlR1ozZUdNelVVVXhLMVJMVjJ0TVEzSjJaV3MyVjJ4SmNYbGhRelV5VnpkTlJGSTRUWEJHWldKNWJWTkxWSFozWmsxU2QzbExVVXhVTUROVlREUjJkRFE0ZVVWak9ITndOM2RVUVVoTkwxZEVaemhSYjNSaGNtWTRUMEpJYTI1dldqa3lXR2wyYVdGV05uUlJjV2hTVDBoRFptZHRia05ZYVhobVZ6QjNSVmhEZG5GcFRGUmlVWFJWWWt4elV5ODRTVkowWkZocmNGRkNPVUZuVFVKQlFVZHFaMmRLV1UxSlNVTldSRUZQUW1kT1ZraFJPRUpCWmpo"+ "RlFrRk5RMEpoUVhkRmQxbEVWbEl3YkVKQmQzZERaMWxKUzNkWlFrSlJWVWhCZDBWM1JFRlpSRlpTTUZSQlVVZ3ZRa0ZKZDBGRVFXUkNaMDVXU0ZFMFJVWm5VVlUyUkVoQ2QzTkJkbUkxTTJjdlF6QTNjSEpVZG5aM1RsRlJURmwzU0hkWlJGWlNNR3BDUW1kM1JtOUJWVzFPU0RSaWFFUnllal"+ "YyYzFsS09GbHJRblZuTmpNd1NpOVRjM2RhUVZsSlMzZFpRa0pSVlVoQlVVVkZWMFJDVjAxRFkwZERRM05IUVZGVlJrSjZRVUpvYUhSdlpFaFNkMDlwT0haaU1rNTZZME0xZDJFeWEzVmFNamwyV25rNWJtUklUWGhpZWtWM1MzZFpTVXQzV1VKQ1VWVklUVUZMUjBneWFEQmtTRUUyVEhrNWQyRXlhM1ZhTWpsMlduazVibU16U1hsTU1HUlZWWHBHVUUxVE5XcGpibEYzU0ZGWlJGWlNNRkpDUWxsM1JrbEpVMWxZVWpCYVdFNHdURzFHZFZwSVNuWmhWMUYxV1RJNWRFMURSVWRCTVZWa1NVRlJZVTFDWjNkRFFWbEhXalJGVFVGUlNVTk5RWGRIUTJselIwRlJVVUl4Ym10RFFsRk5kMHgzV1VSV1VqQm1Ra05uZDBwcVFXdHZRMHRuU1VsWlpXRklVakJqUkc5MlRESk9lV0pETlhkaE1tdDFXakk1ZGxwNU9VaFdSazE0VkhwRmRWa3pTbk5OU1VsQ1FrRlpTMHQzV1VKQ1FVaFhaVkZKUlVGblUwSTVVVk5DT0dkRWQwRklZMEU1YkhsVlREbEdNMDFEU1ZWV1FtZEpUVXBTVjJwMVRrNUZlR3Q2ZGprNFRVeDVRVXg2UlRkNFdrOU5RVUZCUm5adWRYa3dXbmRCUVVKQlRVRlRSRUpIUVdsRlFUZGxMe"+ "kJaVW5VemQwRkdiVmRJTWpkTk1uWmlWbU5hTDIxeWNDczBjbVpaWXk4MVNWQktNamxHTm1kRFNWRkRia3REUTBGaFkxWk9aVmxhT0VORFpsbGtSM0JDTWtkelNIaDFUVTlJYTJFdlR6UXhhbGRsUml0NlowSXhRVVZUVlZwVE5uYzNjeloyZUVWQlNESkxhaXRMVFVSaE5XOUxLekpOYzNoMFZDOVVUVFZoTVhSdlIyOUJRVUZDWWpVM2MzUktUVUZCUVZGRVFVVlpkMUpCU1dkRldHSnBiMUJpU25BNWNVTXdSR295TlRoRVJrZFRVazFCVlN0YVFqRkZhVlpGWW1KaUx6UlZkazVGUTBsQ2FFaHJRblF4T0haU2JqbDZSSFo1Y21aNGVYVmtZMGhVVDFOc00yZFVZVmxCTHpkNVZDOUNhVWcwVFVFd1IwTlRjVWRUU1dJelJGRkZRa04zVlVGQk5FbENRVkZFU1VGalVVSnNiV1E0VFVWblRHUnljbkpOWWtKVVEzWndUVmh6ZERVcmQzZ3lSR3htWVdwS1RrcFZVRFJxV1VacVdWVlJPVUl6V0RSRk1ucG1ORGx1V0ROQmVYVmFSbmhCY1U5U2JtSnFMelZxYTFrM1lUaHhUVW93YWpFNWVrWlBRaXR4WlhKNFpXTXdibWh0T0dkWmJFeGlVVzAyYzB0Wk4xQXdaWGhtY2pkSWRVc3pUV3RRTVhCbFl6RTBk"+ "MFpGVldGSGNVUjNWV0pIWjJ3dmIybDZNemhHV0VORkswTlhPRVV4VVVGRlZXWjJZbEZRVkZsaVMzaFphaXQwUTA1c2MzTXdZbFJUYjB3eVdqSmtMMm96UW5CTU0wMUdkekI1ZUZOTEwxVlVjWGxyVEhJeVFTOU5aR2hLVVcxNGFTdEhLMDFMVWxOelVYSTJNa0Z1V21GMU9YRTJXVVp2YVNz"+ "NVFVVklLMEUwT0ZoMFNYbHphRXg1UTFSVk0waDBLMkZMYjJoSGJuaEJOWFZzTVZoU2JYRndPRWgyWTBGME16bFFPVFZHV2tkR1NtVXdkWFpzZVdwUGQwRjZXSFZOZFRkTksxQlhVbU1pTENKTlNVbEZVMnBEUTBGNlMyZEJkMGxDUVdkSlRrRmxUekJ0Y1VkT2FYRnRRa3BYYkZGMVJFRk9RbWRyY1docmFVYzVkekJDUVZGelJrRkVRazFOVTBGM1NHZFpSRlpSVVV4RmVHUklZa2M1YVZsWGVGUmhWMlIxU1VaS2RtSXpVV2RSTUVWblRGTkNVMDFxUlZSTlFrVkhRVEZWUlVOb1RVdFNNbmgyV1cxR2MxVXliRzVpYWtWVVRVSkZSMEV4VlVWQmVFMUxVako0ZGxsdFJuTlZNbXh1WW1wQlpVWjNNSGhPZWtFeVRWUlZkMDFFUVhkT1JFcGhSbmN3ZVUxVVJYbE5WRlYzVFVSQmQwNUVTbUZOUlVsNFEzcEJTa0puVGxaQ1FWbFVRV3hXVkUxU05IZElRVmxFVmxGUlMwVjRWa2hpTWpsdVlrZFZaMVpJU2pGak0xRm5WVEpXZVdSdGJHcGFXRTE0UlhwQlVrSm5UbFpDUVUxVVEydGtWVlY1UWtSUlUwRjRWSHBGZDJkblJXbE5RVEJIUTFOeFIxTkpZak5FVVVWQ1FWRlZRVUUwU1VKRWQwRjNaMmRGUzB"+ "GdlNVSkJVVVJSUjAwNVJqRkpkazR3TlhwclVVODVLM1JPTVhCSlVuWktlbnA1VDFSSVZ6VkVla1ZhYUVReVpWQkRiblpWUVRCUmF6STRSbWRKUTJaTGNVTTVSV3R6UXpSVU1tWlhRbGxyTDJwRFprTXpVak5XV2sxa1V5OWtUalJhUzBORlVGcFNja0Y2UkhOcFMxVkVlbEp5YlVKQ1NqVjNkV1JuZW01a1NVMVpZMHhsTDFKSFIwWnNOWGxQUkVsTFoycEZkaTlUU2tndlZVd3JaRVZoYkhST01URkNiWE5MSzJWUmJVMUdLeXRCWTNoSFRtaHlOVGx4VFM4NWFXdzNNVWt5WkU0NFJrZG1ZMlJrZDNWaFpXbzBZbGhvY0RCTVkxRkNZbXA0VFdOSk4wcFFNR0ZOTTFRMFNTdEVjMkY0YlV0R2MySnFlbUZVVGtNNWRYcHdSbXhuVDBsbk4zSlNNalY0YjNsdVZYaDJPSFpPYld0eE4zcGtVRWRJV0d0NFYxazNiMGM1YWl0S2ExSjVRa0ZDYXpkWWNrcG1iM1ZqUWxwRmNVWktTbE5RYXpkWVFUQk1TMWN3V1RONk5XOTZNa1F3WXpGMFNrdDNTRUZuVFVKQlFVZHFaMmRGZWsxSlNVSk1la0ZQUW1kT1ZraFJPRUpCWmpoRlFrRk5RMEZaV1hkSVVWbEVWbEl3YkVKQ1dYZEdRVmxKUzNkWlFrSlJWVWhCZD"+ "BWSFEwTnpSMEZSVlVaQ2QwMURUVUpKUjBFeFZXUkZkMFZDTDNkUlNVMUJXVUpCWmpoRFFWRkJkMGhSV1VSV1VqQlBRa0paUlVaS2FsSXJSelJSTmpncllqZEhRMlpIU2tGaWIwOTBPVU5tTUhKTlFqaEhRVEZWWkVsM1VWbE5RbUZCUmtwMmFVSXhaRzVJUWpkQllXZGlaVmRpVTJGTVpDOW"+ "pSMWxaZFUxRVZVZERRM05IUVZGVlJrSjNSVUpDUTJ0M1NucEJiRUpuWjNKQ1owVkdRbEZqZDBGWldWcGhTRkl3WTBSdmRrd3lPV3BqTTBGMVkwZDBjRXh0WkhaaU1tTjJXak5PZVUxcVFYbENaMDVXU0ZJNFJVdDZRWEJOUTJWblNtRkJhbWhwUm05a1NGSjNUMms0ZGxrelNuTk1ia0p5WVZNMWJtSXlPVzVNTW1SNlkycEpkbG96VG5sTmFUVnFZMjEzZDFCM1dVUldVakJuUWtSbmQwNXFRVEJDWjFwdVoxRjNRa0ZuU1hkTGFrRnZRbWRuY2tKblJVWkNVV05EUVZKWlkyRklVakJqU0UwMlRIazVkMkV5YTNWYU1qbDJXbms1ZVZwWVFuWmpNbXd3WWpOS05VeDZRVTVDWjJ0eGFHdHBSemwzTUVKQlVYTkdRVUZQUTBGUlJVRkhiMEVyVG01dU56aDVObkJTYW1RNVdHeFJWMDVoTjBoVVoybGFMM0l6VWs1SGEyMVZiVmxJVUZGeE5sTmpkR2s1VUVWaGFuWjNVbFF5YVZkVVNGRnlNREptWlhOeFQzRkNXVEpGVkZWM1oxcFJLMnhzZEc5T1JuWm9jMDg1ZEhaQ1EwOUpZWHB3YzNkWFF6bGhTamw0YW5VMGRGZEVVVWc0VGxaVk5sbGFXaTlZZEdWRVUwZFZPVmw2U25GUWFsazRjVE5OUkhoeWVtM"+ "XhaWEJDUTJZMWJ6aHRkeTkzU2pSaE1rYzJlSHBWY2paR1lqWlVPRTFqUkU4eU1sQk1Va3cyZFROTk5GUjZjek5CTWsweGFqWmllV3RLV1drNGQxZEpVbVJCZGt0TVYxcDFMMkY0UWxaaWVsbHRjVzEzYTIwMWVreFRSRmMxYmtsQlNtSkZURU5SUTFwM1RVZzFOblF5UkhaeGIyWjRjelpDUW1ORFJrbGFWVk53ZUhVMmVEWjBaREJXTjFOMlNrTkRiM05wY2xOdFNXRjBhaTg1WkZOVFZrUlJhV0psZERoeEx6ZFZTelIyTkZwVlRqZ3dZWFJ1V25veGVXYzlQU0pkZlEuZXlKdWIyNWpaU0k2SW05R2VVNWtTVzQwU204M1ZsbFVkRFJrUkVnMlYxRjZkalZ3TjFac1kwZzRhV3RHVmtoTE1EUmxjWE05SWl3aWRHbHRaWE4wWVcxd1RYTWlPakUxT1RFME16TTRNRGMxTmpRc0ltRndhMUJoWTJ0aFoyVk9ZVzFsSWpvaVkyOXRMbWR2YjJkc1pTNWhibVJ5YjJsa0xtZHRjeUlzSW1Gd2EwUnBaMlZ6ZEZOb1lUSTFOaUk2SWl0clVrSk1WM1ZuVm5wNE1sbFVjVEk1VGtneVJWSnVSRzlITlVOMlRrUnRNR2N5ZGsxUWNVUlFZbFU5SWl3aVkzUnpVSEp2Wm1sc1pVMWhkR05vSWpwMGNuVmxMQ0poY0d0"+ "RFpYSjBhV1pwWTJGMFpVUnBaMlZ6ZEZOb1lUSTFOaUk2V3lJNFVERnpWekJGVUVwamMyeDNOMVY2VW5OcFdFdzJOSGNyVHpVd1JXUXJVa0pKUTNSaGVURm5NalJOUFNKZExDSmlZWE5wWTBsdWRHVm5jbWwwZVNJNmRISjFaU3dpWlhaaGJIVmhkR2x2YmxSNWNHVWlPaUpDUVZOSlF5Sjku"+ "S3pKWWJYUEJUSUFvamFNbThqQ1hjR3E3ZU9COWxCMnV1LUlrMmNxU29HYzBYNnlTSUNVRlotNmJSbjAtcnZDS25zazlBN0Y0bkt0UUl0dDBsaFFPUnlLOFFuUUNINnFCYlM1NjQ4akt5cFNQXzNTaEdmbWhuRk1PWWU5UlpONDA0Vi0zWl8xR3BaeElvRjZ4VWZRR2UwSUU3UVd6TEcyN3daSlFDZWwwRzZtMXU0d2JBcWllN0dBRXV0Z0tTc0dxcXdYb3NldEZCQnFzUS1mbUk3Y0lEb3pVc01pbkw0ZmE1djkwQWsyZHFLSlFyclZqRWczdzJKYTNjdmo1X0dWMFpzUlVrdDlQY2E3TnBPaGUtejE4a0xCUGpkYlhJNlZLVW1QSjNrY2xmcVZtY1VvRGk4cXVVMW1wVlJ1VTRIdy1QU2VpMEtqUDVVYTI4UTZxZEs2U2pBaGF1dGhEYXRhWMXZV8JswNgrrG46LjoHvBP-XVNbLFy4fMYY3an12FPexUUAAAAAuT_ZYfLmRi-xIoIAIkfeeABBAcaOtf577JrxNa9lHZ9g1Npx2YgKhU0w-F_fFkzbOZNZRmh4_S4NFXBBOH75Jf5NS76jK9vcuRiamDIn63Jxxw2lAQIDJiABIVggKCVQt7mNWFnqmPUTz5n6zHuR1TMvb8RmmH0E3ILATSciWCCs8K3giniEnyLwfll7C8e1g1PPckAN8JXnGWUfHuGVEA", "attestationObject" ), clientDataJSON: coerceToArrayBuffer("eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiTnJSemdSaEd5NVkwTmxLTmhFQXFzNFpGVmdOR3RONDlaeUNUT2ZMazhHMUVQWTN2bk4zemFzSVp5bmxDQXlVT0xkQjMtQUFMZnkxWEcyTWlWcHNfVnciLCJvcmlnaW4iOiJodHRwczpcL1wvY29udHViZXJuaW8udGljLnVkYy5lcyIsImFuZHJvaWRQYWNrYWdlTmFtZSI6ImNvbS5hbmRyb2lkLmNocm9tZSJ9","clientDataJSON"), }, };
return serv.attestationResult( makeCredentialAttestationSafetyNetResponse, expectations, ).then((res) => { assert.instanceOf(res, Fido2AttestationResult); return res; }); });
it("validates a credential request with 'apple' attestation", function() { const serv = new Fido2Lib(); const expectations = { challenge: "AkSfM/TFowAdgrwV1NQ1JyTOHLuIlukj6skT5GPVDR3RsrB4eCa2JcSVd+ltFCf1XX3hl9p8NQbh3rurROnx0Q==", origin: "http://localhost:3000", factor: "either", };
const makeCredentialAttestationSafetyNetResponse = { rawId: coerceToArrayBuffer("nYpcVrc+OjVgCMS9laIxkRR64nk=", "rawId"), response: { attestationObject: coerceToArrayBuffer( "o2NmbXRlYXBwbGVnYXR0U3RtdKFjeDVjglkCSDCCAkQwggHJoAMCAQICBgGAxyervjAKBggqhkjOPQQDAjBIMRwwGgYDVQQDDBNBcHBsZSBXZWJBdXRobiBDQSAxMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIyMDUxNDA5NTgyN1oXDTIyMDUxNzA5NTgyN1owgZExSTBHBgNVBAMMQGNlZDAwODVmNDUxMzEyODY0Njg3OThkNTIyOWFhODdhOThhNzYwNDQ2NzA0MWQ1YmFkODhlZDk3OTE0NzQ0MjQxGjAYBgNVBAsMEUFBQSBDZXJ0aWZpY2F0aW9uMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0FQqXwZPjTK8bPlw3NA14XeYC5OWxmwQ2CgrSfYtW36rdiNzfRExutXjpzhM/R/yhN3fFyYxUbe9wVww6Gn4lqNVMFMwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCBPAwMwYJKoZIhvdjZAgCBCYwJKEiBCCZNn3+lBmJWg6L9fm41ptdlsARxiVBVZoDzD5WKWitqTAKBggqhkjOPQQDAgNpADBmAjEAnjE6AVjiVRlOqzw3Xj4ySG/+X9RhfI2uhD3oXRbDKrhQnIm5npuC/zy15UeW+mv6AjEA9IYWcstULcXBlL0o65cCeyZ6fysXjBMkbc7cubP/wMvX9/mZtvyC5OHTLIGyJhhGWQI4MIICNDCCAbqgAwIBAgIQViVTlcen+0Dr4ijYJghTtjAKBggqhkjOPQQDAzBLMR8wHQYDVQQDDBZBcHBsZSBXZWJBdXRobiBSb290IENBMRMwEQYDVQQKDApBcHBsZSBJbmMuMRMwEQYDVQQIDApDYWxpZm9ybmlhMB4XDTIwMDMxODE4MzgwMVoXDTMwMDMxMzAwMDAwMFowSDEcMBoGA1UEAwwTQXBwbGUgV2ViQXV0aG4gQ0EgMTETMBEGA1UECgwKQXBwbGUgSW5jLjETMBEGA1UECAwKQ2FsaWZvcm5pYTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIMuhy8mFJGBAiW59fzWu2N4tfVfP8sEW8c1mTR1/VSQRN+b/hkhF2XGmh3aBQs41FCDQBpDT7JNES1Ww+HPv8uYkf7AaWCBvvlsvHfIjd2vRqWu4d1RW1r6q5O+nAsmkaNmMGQwEgYDVR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBQm12TZxXjCWmfRp95rEtAbY/HG1zAdBgNVHQ4EFgQU666CxP+hrFtR1M8kYQUAvmO9d4gwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMQDdixo0gaX62du052V7hB4UTCe3W4dqQYbCsUdXUDNyJ+/lVEV+9kiVDGMuXEg+cMECMCyKYETcIB/P5ZvDTSkwwUh4Udlg7Wp18etKyr44zSW4l9DIBb7wx/eLB6VxxugOB2hhdXRoRGF0YViYSZYN5YgOjGh0NBcPZHZgW4/krrmihjLHmVzzuoMdl2NFAAAAAPJKjnDQ0/gsKTcyUjzE3loAFJ2KXFa3Pjo1YAjEvZWiMZEUeuJ5pQECAyYgASFYINBUKl8GT40yvGz5cNzQNeF3mAuTlsZsENgoK0n2LVt+Ilggq3Yjc30RMbrV46c4TP0f8oTd3xcmMVG3vcFcMOhp+JY=", "attestationObject" ), clientDataJSON: coerceToArrayBuffer( "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQWtTZk1fVEZvd0FkZ3J3VjFOUTFKeVRPSEx1SWx1a2o2c2tUNUdQVkRSM1JzckI0ZUNhMkpjU1ZkLWx0RkNmMVhYM2hsOXA4TlFiaDNydXJST254MFEiLCJvcmlnaW4iOiJodHRwOi8vbG9jYWxob3N0OjMwMDAifQ==", "clientDataJSON" ), }, };
return serv .attestationResult( makeCredentialAttestationSafetyNetResponse, expectations ) .then((res) => { assert.instanceOf(res, Fido2AttestationResult); return res; }); });
it("catches bad requests"); });
describe("assertionOptions", function() { let serv; beforeEach(function() { serv = new Fido2Lib(); });
it("returns a challenge", function() { return serv.assertionOptions().then((chal) => { assert.isNumber(chal.timeout); assert.strictEqual(chal.timeout, 60000); assert.instanceOf(chal.challenge, ArrayBuffer); assert.strictEqual(chal.challenge.byteLength, 64); }); });
it("picks up values from constructors options", function() { serv = new Fido2Lib({ timeout: 42, rpId: "example.com", rpName: "ACME", rpIcon: "https://example.com/logo.png", challengeSize: 128, attestation: "none", cryptoParams: [-8, -9], authenticatorAttachment: "platform", authenticatorRequireResidentKey: false, authenticatorUserVerification: "required", });
return serv.assertionOptions().then((opts) => { assert.isObject(opts); assert.isNumber(opts.timeout); assert.strictEqual(opts.timeout, 42); assert.isString(opts.rpId); assert.strictEqual(opts.rpId, "example.com"); assert.instanceOf(opts.challenge, ArrayBuffer); assert.strictEqual(opts.challenge.byteLength, 128); assert.isString(opts.userVerification); assert.strictEqual(opts.userVerification, "required"); }); });
it("accepts extraData and returns rawChallenge", async function() { const extraData = new Uint8Array([0x1, 0x2, 0x3, 0x4]).buffer; const opts = await serv.assertionOptions({ extraData, });
const challenge = opts.challenge; const calculatedChallenge = await tools.hashDigest( appendBuffer(opts.rawChallenge, extraData), ); assert.isTrue(arrayBufferEquals(challenge, calculatedChallenge), "extraData hashes match"); }); });
describe("assertionResult", function() { let serv; beforeEach(function() { serv = new Fido2Lib(); });
it("valid an assertion", function() { const expectations = { challenge: "eaTyUNnyPDDdK8SNEgTEUvz1Q8dylkjjTimYd5X7QAo-F8_Z1lsJi3BilUpFZHkICNDWY8r9ivnTgW7-XZC3qQ", origin: "https://localhost:8443", factor: "either", 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 without userHandle", function() { const expectations = { challenge: "eaTyUNnyPDDdK8SNEgTEUvz1Q8dylkjjTimYd5X7QAo-F8_Z1lsJi3BilUpFZHkICNDWY8r9ivnTgW7-XZC3qQ", origin: "https://localhost:8443", factor: "either", publicKey: h.lib.assnPublicKey, prevCounter: 362, userHandle: null, };
const assertionResponse = { rawId: h.lib.assertionResponse.rawId, response: { clientDataJSON: h.lib.assertionResponse.response.clientDataJSON, authenticatorData: h.lib.assertionResponse.response.authenticatorData, signature: h.lib.assertionResponse.response.signature, }, };
return serv.assertionResult(assertionResponse, expectations).then( (res) => { assert.instanceOf(res, Fido2AssertionResult); return res; }, ); });
it("valid assertion with null userHandle", function() { const expectations = { challenge: "eaTyUNnyPDDdK8SNEgTEUvz1Q8dylkjjTimYd5X7QAo-F8_Z1lsJi3BilUpFZHkICNDWY8r9ivnTgW7-XZC3qQ", origin: "https://localhost:8443", factor: "either", publicKey: h.lib.assnPublicKey, prevCounter: 362, userHandle: null, };
const assertionResponse = { rawId: h.lib.assertionResponse.rawId, response: { clientDataJSON: h.lib.assertionResponse.response.clientDataJSON, authenticatorData: h.lib.assertionResponse.response.authenticatorData, signature: h.lib.assertionResponse.response.signature, userHandle: null, }, };
return serv.assertionResult(assertionResponse, expectations).then( (res) => { assert.instanceOf(res, Fido2AssertionResult); return res; }, ); });
it("valid assertion without counter supported", function() { const expectations = { challenge: "g_Pu32bpluktxugNNBLX-ZO5N9ub0D50bJERbKiU2GWON3md0rR9CaQYdPHdCgo-dpi1-9gbJJvmCuHDnh04Rg", origin: "https://mighty-fireant-84.loca.lt", factor: "first", 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; }); }); });
describe("addAttestationFormat", function() { afterEach(function() { Fido2Lib.deleteAllAttestationFormats(); });
afterEach(function() { restoreAttestationFormats(); });
it("adds to map on success", function() { const serv = new Fido2Lib(); assert.instanceOf(serv.attestationMap, Map); const prevSize = serv.attestationMap.size; const ret = Fido2Lib.addAttestationFormat("foo", function() {}, function() {}); assert.isTrue(ret); assert.strictEqual(serv.attestationMap.size, prevSize + 1); assert.isTrue(serv.attestationMap.has("foo")); const newFmt = serv.attestationMap.get("foo"); assert.isObject(newFmt); assert.strictEqual(Object.keys(newFmt).length, 2); assert.isFunction(newFmt.parseFn); assert.isFunction(newFmt.validateFn); });
it("throws on bad fmt", function() { assert.throws(() => { Fido2Lib.addAttestationFormat({}, function() {}, function() {}); }, TypeError, "expected 'fmt' to be string, got: object"); });
it("throws on duplicate fmt", function() { Fido2Lib.addAttestationFormat("foo", function() {}, function() {}); assert.throws(() => { Fido2Lib.addAttestationFormat("foo", function() {}, function() {}); }, Error, "can't add format: 'foo' already exists"); });
it("throws on bad parseFn", function() { assert.throws(() => { Fido2Lib.addAttestationFormat("foo", [], function() {}); }, TypeError, "expected 'parseFn' to be string, got: object"); });
it("throws on bad validateFn", function() { assert.throws(() => { Fido2Lib.addAttestationFormat("foo", function() {}, "blah"); }, TypeError, "expected 'validateFn' to be string, got: string"); }); }); describe("parseAttestation", function() { let parseStub; let validateStub; beforeEach(function() { parseStub = new Stub(); validateStub = new Stub(); Fido2Lib.addAttestationFormat("foo", parseStub.stub(), parseStub.stub()); });
afterEach(function() { Fido2Lib.deleteAllAttestationFormats(); });
afterEach(function() { restoreAttestationFormats(); });
it("returns Map on success", function() { const arg = new Map([ ["test", "yup"], ]); parseStub.return(arg); const ret = Fido2Lib.parseAttestation("foo", arg); assert.instanceOf(ret, Map); assert.strictEqual(parseStub.callCount, 1); assert.isTrue(parseStub.calledWith.includes(arg)); });
it("success when returning empty map", function() { const arg = new Map(); parseStub.return(arg); const ret = Fido2Lib.parseAttestation("foo", arg); assert.instanceOf(ret, Map); assert.strictEqual(parseStub.callCount, 1); assert.isTrue(parseStub.calledWith.includes(arg)); });
it("throws if parseFn doesn't return Map", function() { assert.throws(() => { Fido2Lib.parseAttestation("foo", { test: "yup" }); }, Error, "foo parseFn did not return a Map"); });
it("throws on non-string format", function() { assert.throws(() => { Fido2Lib.parseAttestation({}, { test: "yup" }); }, TypeError, "expected 'fmt' to be string, got: object"); });
it("throws on missing format", function() { assert.throws(() => { Fido2Lib.parseAttestation(); }, TypeError, "expected 'fmt' to be string, got: undefined"); });
it("throws on missing data", function() { assert.throws(() => { Fido2Lib.parseAttestation("foo"); }, TypeError, "expected 'attStmt' to be object, got: undefined"); }); });
describe("validateAttestation", function() { let parseStub; let validateStub; let fakeRequest; beforeEach(function() { parseStub = new Stub(); validateStub = new Stub(); Fido2Lib.addAttestationFormat("foo", parseStub.stub(), validateStub.stub()); fakeRequest = { authnrData: new Map([ ["fmt", "foo"], ]), }; });
afterEach(function() { Fido2Lib.deleteAllAttestationFormats(); });
afterEach(function() { restoreAttestationFormats(); });
// ToDo: Where does this check for a map? it("returns Map on success", async function() { validateStub.return(true); const ret = await Fido2Lib.validateAttestation.call(fakeRequest); assert.isTrue(ret); assert.strictEqual(validateStub.callCount, 1); });
it("throws if validateFn doesn't return true", async function() { return assert.isRejected(Fido2Lib.validateAttestation.call(fakeRequest), Error, "foo validateFn did not return 'true'"); });
it("throws on non-string format", function() { fakeRequest.authnrData.set("fmt", {}); return assert.isRejected(Fido2Lib.validateAttestation.call(fakeRequest), TypeError, "expected 'fmt' to be string, got: object"); });
it("throws on missing format", function() { fakeRequest.authnrData.clear(); return assert.isRejected(Fido2Lib.validateAttestation.call(fakeRequest), TypeError, "expected 'fmt' to be string, got: undefined"); }); });
describe("createMdsCollection", function() { it("throws if no name provided", function() { assert.throws(function() { Fido2Lib.createMdsCollection(); }, Error, "expected 'collectionName' to be non-empty string, got: undefined"); });
it("returns a MdsCollection", function() { const mc = Fido2Lib.createMdsCollection("test"); assert.instanceOf(mc, MdsCollection); }); });
describe("addMdsCollection", function() { afterEach(function() { Fido2Lib.clearMdsCollections(); });
it("throws if argument isn't a MdsCollection", function() { return assert.isRejected( Fido2Lib.addMdsCollection(), Error, "expected 'mdsCollection' to be instance of MdsCollection, got: undefined", ); });
it("sets the current global MDS collection", async function() { const mc = Fido2Lib.createMdsCollection("test"); await mc.addToc(h.mds.mds2TocJwt); mc.addEntry(h.mds.mds2UafEntry); assert.strictEqual(mc.entryList.size, 0); await Fido2Lib.addMdsCollection(mc); assert.strictEqual(mc.entryList.size, 1); });
it("can add multiple collections", async function() { const mc1 = Fido2Lib.createMdsCollection("fido-mds-1"); await mc1.addToc(h.mds.mds1TocJwt); mc1.addEntry(h.mds.mds1UafEntry); assert.strictEqual(mc1.entryList.size, 0); await Fido2Lib.addMdsCollection(mc1); assert.strictEqual(mc1.entryList.size, 1);
const mc2 = Fido2Lib.createMdsCollection("fido-mds-2"); await mc2.addToc(h.mds.mds2TocJwt); mc2.addEntry(h.mds.mds2UafEntry); assert.strictEqual(mc2.entryList.size, 0); await Fido2Lib.addMdsCollection(mc2); assert.strictEqual(mc2.entryList.size, 1); }); });
describe("findMdsEntry", function() { afterEach(function() { Fido2Lib.clearMdsCollections(); });
it("throws if a global MDS collection hasn't been set", function() { assert.throws(function() { Fido2Lib.findMdsEntry("4e4e#4005"); }, Error, "must set MDS collection before attempting to find an MDS entry"); });
it("finds a UAF MDS entry in the global collection", async function() { const mc = Fido2Lib.createMdsCollection("test"); await mc.addToc(h.mds.mds2TocJwt); mc.addEntry(h.mds.mds2UafEntry); await Fido2Lib.addMdsCollection(mc);
const entryList = Fido2Lib.findMdsEntry("4e4e#4005"); assert.isArray(entryList); assert.strictEqual(entryList.length, 1); const entry = entryList[0]; assert.instanceOf(entry, MdsEntry); assert.strictEqual(entry.aaid, "4e4e#4005"); });
it("finds a UAF MDS entry in the global collection", async function() { const mc = Fido2Lib.createMdsCollection("test"); await mc.addToc(h.mds.mds1TocJwt); mc.addEntry(h.mds.mds1U2fEntry); await Fido2Lib.addMdsCollection(mc);
const entryList = Fido2Lib.findMdsEntry( "923881fe2f214ee465484371aeb72e97f5a58e0a", ); assert.isArray(entryList); assert.strictEqual(entryList.length, 1); const entry = entryList[0]; assert.strictEqual(entry.protocolFamily, "u2f"); assert.deepEqual(entry.attestationCertificateKeyIdentifiers, ["923881fe2f214ee465484371aeb72e97f5a58e0a"]); assert.strictEqual(entry.description, "Feitian BioPass FIDO Security Key"); });
it("throws if id isn't specified", async function() { const mc = Fido2Lib.createMdsCollection("test"); await mc.addToc(h.mds.mds2TocJwt); mc.addEntry(h.mds.mds2UafEntry); await Fido2Lib.addMdsCollection(mc);
assert.throws(function() { Fido2Lib.findMdsEntry(); }, Error, "expected 'id' to be String, got: undefined"); });
it("can find multiple entries", async function() { // Add UAF 4e4e#4005 from FIDO MDS 1 const mc1 = Fido2Lib.createMdsCollection("fido-mds1-toc"); await mc1.addToc(h.mds.mds1TocJwt); mc1.addEntry(h.mds.mds1UafEntry4e4e4005); await Fido2Lib.addMdsCollection(mc1);
// Add UAF 4e4e#4005 from FIDO MDS 2 const mc2 = Fido2Lib.createMdsCollection("fido-mds2-toc"); await mc2.addToc(h.mds.mds2TocJwt); mc2.addEntry(h.mds.mds2UafEntry); await Fido2Lib.addMdsCollection(mc2);
const entryList = Fido2Lib.findMdsEntry("4e4e#4005"); assert.isArray(entryList); assert.strictEqual(entryList.length, 2);
// first entry from MDS1 const entry1 = entryList[0]; assert.strictEqual(entry1.aaid, "4e4e#4005"); assert.isObject(entry1.collection); assert.strictEqual(entry1.collection.name, "fido-mds1-toc"); assert.isUndefined(entry1.legalHeader);
// second entry from MDS2 const entry2 = entryList[1]; assert.strictEqual(entry2.aaid, "4e4e#4005"); assert.isObject(entry2.collection); assert.strictEqual(entry2.collection.name, "fido-mds2-toc"); assert.isString(entry2.legalHeader); // distinguishing characteristic of MDS2 }); });});