/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; let exports = this; const scripts = [ "pkijs/common.js", "pkijs/asn1.js", "pkijs/x509_schema.js", "pkijs/x509_simpl.js", "browser/cbor.js", "browser/u2futil.js", ]; for (let script of scripts) { Services.scriptloader.loadSubScript( `chrome://mochitests/content/browser/dom/webauthn/tests/${script}`, this ); } function add_virtual_authenticator(autoremove = true) { let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService( Ci.nsIWebAuthnService ); let id = webauthnService.addVirtualAuthenticator( "ctap2_1", "internal", true, true, true, true ); if (autoremove) { registerCleanupFunction(() => { webauthnService.removeVirtualAuthenticator(id); }); } return id; } async function addCredential(authenticatorId, rpId) { let keyPair = await crypto.subtle.generateKey( { name: "ECDSA", namedCurve: "P-256", }, true, ["sign"] ); let credId = new Uint8Array(32); crypto.getRandomValues(credId); credId = bytesToBase64UrlSafe(credId); let privateKey = await crypto.subtle .exportKey("pkcs8", keyPair.privateKey) .then(privateKey => bytesToBase64UrlSafe(privateKey)); let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService( Ci.nsIWebAuthnService ); webauthnService.addCredential( authenticatorId, credId, true, // resident key rpId, privateKey, "VGVzdCBVc2Vy", // "Test User" 0 // sign count ); return credId; } async function removeCredential(authenticatorId, credId) { let webauthnService = Cc["@mozilla.org/webauthn/service;1"].getService( Ci.nsIWebAuthnService ); webauthnService.removeCredential(authenticatorId, credId); } function memcmp(x, y) { let xb = new Uint8Array(x); let yb = new Uint8Array(y); if (x.byteLength != y.byteLength) { return false; } for (let i = 0; i < xb.byteLength; ++i) { if (xb[i] != yb[i]) { return false; } } return true; } function arrivingHereIsBad(aResult) { ok(false, "Bad result! Received a: " + aResult); } function expectError(aType) { let expected = `${aType}Error`; return function (aResult) { is( aResult.slice(0, expected.length), expected, `Expecting a ${aType}Error` ); }; } /* eslint-disable no-shadow */ function promiseWebAuthnMakeCredential( tab, attestation = "none", residentKey = "discouraged", extensions = {} ) { return ContentTask.spawn( tab.linkedBrowser, [attestation, residentKey, extensions], ([attestation, residentKey, extensions]) => { const cose_alg_ECDSA_w_SHA256 = -7; let challenge = content.crypto.getRandomValues(new Uint8Array(16)); let pubKeyCredParams = [ { type: "public-key", alg: cose_alg_ECDSA_w_SHA256, }, ]; let publicKey = { rp: { id: content.document.domain, name: "none" }, user: { id: new Uint8Array(), name: "none", displayName: "none", }, pubKeyCredParams, authenticatorSelection: { authenticatorAttachment: "cross-platform", residentKey, }, extensions, attestation, challenge, }; return content.navigator.credentials .create({ publicKey }) .then(credential => { return { clientDataJSON: credential.response.clientDataJSON, attObj: credential.response.attestationObject, rawId: credential.rawId, }; }); } ); } function promiseWebAuthnGetAssertion(tab, key_handle = null, extensions = {}) { return ContentTask.spawn( tab.linkedBrowser, [key_handle, extensions], ([key_handle, extensions]) => { let challenge = content.crypto.getRandomValues(new Uint8Array(16)); if (key_handle == null) { key_handle = content.crypto.getRandomValues(new Uint8Array(16)); } let credential = { id: key_handle, type: "public-key", transports: ["usb"], }; let publicKey = { challenge, extensions, rpId: content.document.domain, allowCredentials: [credential], }; return content.navigator.credentials .get({ publicKey }) .then(assertion => { return { authenticatorData: assertion.response.authenticatorData, clientDataJSON: assertion.response.clientDataJSON, extensions: assertion.getClientExtensionResults(), signature: assertion.response.signature, }; }); } ); } function promiseWebAuthnGetAssertionDiscoverable( tab, mediation = "optional", extensions = {} ) { return ContentTask.spawn( tab.linkedBrowser, [extensions, mediation], ([extensions, mediation]) => { let challenge = content.crypto.getRandomValues(new Uint8Array(16)); let publicKey = { challenge, extensions, rpId: content.document.domain, allowCredentials: [], }; return content.navigator.credentials.get({ publicKey, mediation }); } ); } function checkRpIdHash(rpIdHash, hostname) { return crypto.subtle .digest("SHA-256", string2buffer(hostname)) .then(calculatedRpIdHash => { let calcHashStr = bytesToBase64UrlSafe( new Uint8Array(calculatedRpIdHash) ); let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(rpIdHash)); if (calcHashStr != providedHashStr) { throw new Error("Calculated RP ID hash doesn't match."); } }); } function promiseNotification(id) { return new Promise(resolve => { PopupNotifications.panel.addEventListener("popupshown", function shown() { let notification = PopupNotifications.getNotification(id); if (notification) { ok(true, `${id} prompt visible`); PopupNotifications.panel.removeEventListener("popupshown", shown); resolve(); } }); }); } /* eslint-enable no-shadow */