Module
x/fido2/test/parseExpectations.test.js
A node.js library for performing FIDO 2.0 / WebAuthn server functionality
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483// Testing libimport * as chai from "chai";import * as chaiAsPromised from "chai-as-promised";
// Helpers
import { arrayBufferEquals, coerceToArrayBuffer } from "../lib/main.js";
// Test subjectimport { parseExpectations } from "../lib/main.js";chai.use(chaiAsPromised.default);const { assert } = chai;
describe("parseExpectations", function() { it("returns Map on good expectations", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg", }; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 2); assert.strictEqual(ret.get("origin"), exp.origin); assert.strictEqual(ret.get("challenge"), exp.challenge); });
it("doesn't add extra items to Map", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg", foo: "bar", beer: true, }; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 2); assert.strictEqual(ret.get("origin"), exp.origin); assert.strictEqual(ret.get("challenge"), exp.challenge); });
it("throws on invalid origin", function() { const exp = { origin: "asdf", challenge: "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg", }; assert.throws(() => { parseExpectations(exp); }, TypeError, "Invalid URL"); });
it("throws if expected origin is https:443", function() { const exp = { origin: "https://webauthn.bin.coffee:443", challenge: "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg", }; assert.throws(() => { parseExpectations(exp); }, Error, "origin was malformatted"); });
it("throws if expected rpId is invalid type", function() { const exp = { rpId: 23, origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg", }; assert.throws( () => { parseExpectations(exp); }, Error, "expected 'rpId' should be string, got number", ); });
it("throws if expected rpId is invalid", function() { const exp = { rpId: "foobar", origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg", }; assert.throws( () => { parseExpectations(exp); }, Error, "rpId is not a valid eTLD+1", ); });
it("sets rpId properly on successful parsing", function() { const exp = { rpId: "google.com", origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg", }; const ret = parseExpectations(exp); assert.strictEqual(ret.get("rpId"), "google.com"); assert.strictEqual(ret.size, 3); });
it("coerces Array challenge to base64url", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: [ 0xe0, 0x14, 0xb5, 0x60, 0x92, 0x91, 0x09, 0xe0, 0x95, 0xa0, 0xb7, 0x5f, 0x1b, 0xf6, 0xfa, 0xe8, 0x1b, 0x92, 0x43, 0xe2, 0x36, 0x9f, 0x7e, 0x16, 0xb0, 0xb1, 0x6f, 0xcb, 0xad, 0x9f, 0xa4, 0x85, 0x45, 0x8e, 0xb9, 0xb7, 0xdb, 0xfb, 0x45, 0x45, 0x08, 0xb1, 0x5f, 0xd5, 0x3c, 0x10, 0x15, 0x53, 0xae, 0x24, 0xe4, 0xad, 0xe0, 0x29, 0xfb, 0x59, 0xc3, 0xbd, 0x86, 0xe8, 0x44, 0xaf, 0x56, 0x16, ], }; const base64UrlChallenge = "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg"; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 2); assert.strictEqual(ret.get("origin"), exp.origin); assert.strictEqual(ret.get("challenge"), base64UrlChallenge); });
it("coerces Uint8Array challenge to base64url", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: Uint8Array.from([ 0xe0, 0x14, 0xb5, 0x60, 0x92, 0x91, 0x09, 0xe0, 0x95, 0xa0, 0xb7, 0x5f, 0x1b, 0xf6, 0xfa, 0xe8, 0x1b, 0x92, 0x43, 0xe2, 0x36, 0x9f, 0x7e, 0x16, 0xb0, 0xb1, 0x6f, 0xcb, 0xad, 0x9f, 0xa4, 0x85, 0x45, 0x8e, 0xb9, 0xb7, 0xdb, 0xfb, 0x45, 0x45, 0x08, 0xb1, 0x5f, 0xd5, 0x3c, 0x10, 0x15, 0x53, 0xae, 0x24, 0xe4, 0xad, 0xe0, 0x29, 0xfb, 0x59, 0xc3, 0xbd, 0x86, 0xe8, 0x44, 0xaf, 0x56, 0x16, ]), }; const base64UrlChallenge = "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg"; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 2); assert.strictEqual(ret.get("origin"), exp.origin); assert.strictEqual(ret.get("challenge"), base64UrlChallenge); });
it("coerces ArrayBuffer challenge to base64url", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: Uint8Array.from([ 0xe0, 0x14, 0xb5, 0x60, 0x92, 0x91, 0x09, 0xe0, 0x95, 0xa0, 0xb7, 0x5f, 0x1b, 0xf6, 0xfa, 0xe8, 0x1b, 0x92, 0x43, 0xe2, 0x36, 0x9f, 0x7e, 0x16, 0xb0, 0xb1, 0x6f, 0xcb, 0xad, 0x9f, 0xa4, 0x85, 0x45, 0x8e, 0xb9, 0xb7, 0xdb, 0xfb, 0x45, 0x45, 0x08, 0xb1, 0x5f, 0xd5, 0x3c, 0x10, 0x15, 0x53, 0xae, 0x24, 0xe4, 0xad, 0xe0, 0x29, 0xfb, 0x59, 0xc3, 0xbd, 0x86, 0xe8, 0x44, 0xaf, 0x56, 0x16, ]).buffer, }; const base64UrlChallenge = "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg"; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 2); assert.strictEqual(ret.get("origin"), exp.origin); assert.strictEqual(ret.get("challenge"), base64UrlChallenge); });
it("coerces base64 challenge to base64url", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", }; const base64UrlChallenge = "4BS1YJKRCeCVoLdfG_b66BuSQ-I2n34WsLFvy62fpIVFjrm32_tFRQixX9U8EBVTriTkreAp-1nDvYboRK9WFg"; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 2); assert.strictEqual(ret.get("origin"), exp.origin); assert.strictEqual(ret.get("challenge"), base64UrlChallenge); });
it("empty expectations object returns empty map", function() { const exp = {}; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 0); });
it("adds flags to map when they exist", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", flags: new Set(["UP", "AT"]), }; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 3); const flags = ret.get("flags"); assert.instanceOf(flags, Set); assert.strictEqual(flags.size, 2); assert.isTrue(flags.has("UP"), "flags has UP"); assert.isTrue(flags.has("AT"), "flags has AT"); });
it("converts Array of flags to Set", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", flags: ["UP", "AT"], }; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 3); const flags = ret.get("flags"); assert.instanceOf(flags, Set); assert.strictEqual(flags.size, 2); assert.isTrue(flags.has("UP"), "flags has UP"); assert.isTrue(flags.has("AT"), "flags has AT"); });
it("throws if flags is something other than Array or Set", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", flags: "foo", }; assert.throws(() => { parseExpectations(exp); }, TypeError, "expected flags to be an Array or a Set, got: string"); });
it("adds prevCount to map when it exists", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", prevCounter: 666, }; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 3); const prevCounter = ret.get("prevCounter"); assert.isNumber(prevCounter); assert.strictEqual(prevCounter, 666); });
it("adds prevCount to map when it's zero", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", prevCounter: 0, }; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 3); const prevCounter = ret.get("prevCounter"); assert.isNumber(prevCounter); assert.strictEqual(prevCounter, 0); });
it("throws when prevCount is not a number", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", prevCounter: "string", }; assert.throws(() => { parseExpectations(exp); }, TypeError, "expected 'prevCounter' should be Number, got string"); });
it("adds publicKey to map when it exists", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", publicKey: "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERez9aO2wBAWO54MuGbEqSdWahSnG\n" + "MAg35BCNkaE3j8Q+O/ZhhKqTeIKm7El70EG6ejt4sg1ZaoQ5ELg8k3ywTg==\n" + "-----END PUBLIC KEY-----\n", }; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 3); const publicKey = ret.get("publicKey"); assert.isString(publicKey); assert.strictEqual( publicKey, "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERez9aO2wBAWO54MuGbEqSdWahSnG\n" + "MAg35BCNkaE3j8Q+O/ZhhKqTeIKm7El70EG6ejt4sg1ZaoQ5ELg8k3ywTg==\n" + "-----END PUBLIC KEY-----\n", ); });
it("throws when publicKey is not a string", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", publicKey: {}, }; assert.throws(() => { parseExpectations(exp); }, TypeError, "expected 'publicKey' should be String, got object"); });
it("adds userHandle to map when it exists", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", prevCounter: 0, userHandle: "YWs", };
const ret = parseExpectations(exp); let userHandle = ret.get("userHandle"); assert.isString(userHandle); assert.strictEqual(userHandle.length, 3); userHandle = coerceToArrayBuffer(userHandle, "userHandle"); let expectedUserHandle = new Uint8Array([ 0x61, 0x6B, ]).buffer;
assert.isTrue(arrayBufferEquals(userHandle, expectedUserHandle), "userHandle has correct value"); });
it("adds userHandle to map when null", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", prevCounter: 0, userHandle: null, };
const ret = parseExpectations(exp); const userHandle = ret.get("userHandle"); assert.isNull(userHandle); });
it("adds userHandle to map when empty string", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", prevCounter: 0, userHandle: "", };
const ret = parseExpectations(exp); const userHandle = ret.get("userHandle"); assert.isString(userHandle); assert.strictEqual(userHandle.length, 0); assert.strictEqual(userHandle, ""); });
it("adds allowCredentials to map when it exists", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", allowCredentials: [{ id: "dGVzdA==", transports: ["usb"], type: "public-key" }], };
const ret = parseExpectations(exp); const allowCredentials = ret.get("allowCredentials"); assert.isArray(allowCredentials); assert.strictEqual(allowCredentials.length, 1); allowCredentials[0].id = coerceToArrayBuffer(allowCredentials[0].id, "allowCredentials.id"); const expectedallowCredentialsId = new Uint8Array([ 0x74, 0x65, 0x73, 0x74, ]).buffer; assert.isTrue( arrayBufferEquals(allowCredentials[0].id, expectedallowCredentialsId), "allowCredentials has correct value", ); });
it("adds allowCredentials to map when null", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", prevCounter: 0, allowCredentials: null, };
const ret = parseExpectations(exp); const allowCredentials = ret.get("allowCredentials"); assert.isNull(allowCredentials); });
it("works when allowCredentials is undefined", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", prevCounter: 0, };
const ret = parseExpectations(exp); const allowCredentials = ret.get("allowCredentials"); assert.isUndefined(allowCredentials); });
it("works when userHandle is undefined", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", prevCounter: 0, };
const ret = parseExpectations(exp); const userHandle = ret.get("userHandle"); assert.isUndefined(userHandle); });
it("throws when allowCredentials is not a array", function() { const exp = { origin: "https://webauthn.bin.coffee", challenge: "4BS1YJKRCeCVoLdfG/b66BuSQ+I2n34WsLFvy62fpIVFjrm32/tFRQixX9U8EBVTriTkreAp+1nDvYboRK9WFg", publicKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESe3kuy9dZzFYR/uw+exFJxKLt6E+\n3Sp0RamB8J63CxYnbRhv6SF6MwQx/LNHJHw7rrN2xioEu88ArEDdk0jHAQ==\n-----END PUBLIC KEY-----\n", allowCredentials: {}, }; assert.throws(() => { parseExpectations(exp); }, TypeError, "expected 'allowCredentials' to be null or array, got object"); });
it("works with typical attestation expectations", function() { const exp = { challenge: "HcsOvH431SaLt1hc7mpkqohMaub+oTO5ao/hzJOkUwQEdTWDhOYTdp4ejQcOCsIYdB64c1fkeqiblg6EkygpUA==", origin: "https://localhost:8443", publicKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESe3kuy9dZzFYR/uw+exFJxKLt6E+\n3Sp0RamB8J63CxYnbRhv6SF6MwQx/LNHJHw7rrN2xioEu88ArEDdk0jHAQ==\n-----END PUBLIC KEY-----\n", prevCounter: 0, flags: ["UP-or-UV"], }; const ret = parseExpectations(exp); assert.instanceOf(ret, Map); assert.strictEqual(ret.size, 5); });});