Skip to main content
Module

x/fido2/test/main.test.js

A node.js library for performing FIDO 2.0 / WebAuthn server functionality
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
// Testing libimport * as chai from "chai";import * as chaiAsPromised from "chai-as-promised";
// Helpersimport * as h from "./helpers/fido2-helpers.js";import * as sinon from "sinon";import { packedSelfAttestationResponse } from "./fixtures/packedSelfAttestationData.js";
// Test subjectimport { arrayBufferEquals, abToBuf, androidSafetyNetAttestation, 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 );}
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("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, // userHandle: h.lib.assertionResponse.response.userHandle }, };
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 = sinon.stub(); validateStub = sinon.stub(); Fido2Lib.addAttestationFormat("foo", parseStub, validateStub); });
afterEach(function() { Fido2Lib.deleteAllAttestationFormats(); });
afterEach(function() { restoreAttestationFormats(); });
it("returns Map on success", function() { const arg = new Map([ ["test", "yup"], ]); parseStub.onCall(0).returns(arg); const ret = Fido2Lib.parseAttestation("foo", arg); assert.instanceOf(ret, Map); assert.strictEqual(parseStub.callCount, 1); assert.isTrue(parseStub.calledWith(arg)); });
it("success when returning empty map", function() { const arg = new Map(); parseStub.onCall(0).returns(arg); const ret = Fido2Lib.parseAttestation("foo", arg); assert.instanceOf(ret, Map); assert.strictEqual(parseStub.callCount, 1); assert.isTrue(parseStub.calledWith(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 = sinon.stub(); validateStub = sinon.stub(); Fido2Lib.addAttestationFormat("foo", parseStub, validateStub); 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.onCall(0).returns(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() { 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 }); });});