259 lines
6.1 KiB
JavaScript
259 lines
6.1 KiB
JavaScript
/* 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 */
|