Source code
Revision control
Copy as Markdown
Other Tools
/* 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,
"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(
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 */