fune/dom/webauthn/tests/browser/browser_fido_appid_extension.js
Kris Maglione 0b1a146519 Bug 1596918: Part 4c - Fix callers which depend on document lifecycle changes. r=mccr8
ContentTask tasks have a different lifetime than SpecialPowers tasks, with the
former being tied to the lifetime of a message manager and the latter tied to
the lifetime of a window global. That means that existing ContentTask callers
which expect to be able to register load listeners before the creation of a
window global, or which expect to persist after a page has navigated, won't
work as SpecialPowers tasks.

Since those sorts of tasks are not really resilient in the face of Fission,
they should really be written to work differently, but this patch mostly just
reverts them to using ContentTask for the time being.

Differential Revision: https://phabricator.services.mozilla.com/D53744

--HG--
extra : moz-landing-system : lando
2019-12-13 20:36:36 +00:00

196 lines
5.7 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";
const TEST_URL = "https://example.com/";
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`
);
};
}
let expectNotSupportedError = expectError("NotSupported");
let expectInvalidStateError = expectError("InvalidState");
let expectSecurityError = expectError("Security");
/* eslint-disable no-shadow */
function promiseU2FRegister(tab, app_id) {
let challenge = crypto.getRandomValues(new Uint8Array(16));
challenge = bytesToBase64UrlSafe(challenge);
return SpecialPowers.spawn(
tab.linkedBrowser,
[[app_id, challenge]],
function([app_id, challenge]) {
return new Promise(resolve => {
content.u2f.register(
app_id,
[{ version: "U2F_V2", challenge }],
[],
resolve
);
});
}
).then(res => {
is(res.errorCode, 0, "u2f.register() succeeded");
let data = base64ToBytesUrlSafe(res.registrationData);
is(data[0], 0x05, "Reserved byte is correct");
return data.slice(67, 67 + data[66]);
});
}
function promiseWebAuthnRegister(tab, appid) {
return ContentTask.spawn(tab.linkedBrowser, appid, appid => {
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", icon: "none" },
user: {
id: new Uint8Array(),
name: "none",
icon: "none",
displayName: "none",
},
pubKeyCredParams,
extensions: { appid },
challenge,
};
return content.navigator.credentials
.create({ publicKey })
.then(res => res.rawId);
});
}
function promiseWebAuthnSign(tab, key_handle, extensions = {}) {
return ContentTask.spawn(
tab.linkedBrowser,
[key_handle, extensions],
([key_handle, extensions]) => {
let challenge = 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(credential => {
return {
authenticatorData: credential.response.authenticatorData,
clientDataJSON: credential.response.clientDataJSON,
extensions: credential.getClientExtensionResults(),
};
});
}
);
}
/* eslint-enable no-shadow */
add_task(function test_setup() {
Services.prefs.setBoolPref("security.webauth.u2f", true);
Services.prefs.setBoolPref("security.webauth.webauthn", true);
Services.prefs.setBoolPref(
"security.webauth.webauthn_enable_android_fido2",
false
);
Services.prefs.setBoolPref(
"security.webauth.webauthn_enable_softtoken",
true
);
Services.prefs.setBoolPref(
"security.webauth.webauthn_enable_usbtoken",
false
);
});
add_task(async function test_appid() {
// Open a new tab.
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
// Get a keyHandle for a FIDO AppId.
let appid = "https://example.com/appId";
let keyHandle = await promiseU2FRegister(tab, appid);
// The FIDO AppId extension can't be used for MakeCredential.
await promiseWebAuthnRegister(tab, appid)
.then(arrivingHereIsBad)
.catch(expectNotSupportedError);
// Using the keyHandle shouldn't work without the FIDO AppId extension.
// This will be an invalid state, because the softtoken will consent without
// having the correct "RP ID" via the FIDO extension.
await promiseWebAuthnSign(tab, keyHandle)
.then(arrivingHereIsBad)
.catch(expectInvalidStateError);
// Invalid app IDs (for the current origin) must be rejected.
await promiseWebAuthnSign(tab, keyHandle, {
appid: "https://bogus.com/appId",
})
.then(arrivingHereIsBad)
.catch(expectSecurityError);
// Non-matching app IDs must be rejected. Even when the user/softtoken
// consents, leading to an invalid state.
await promiseWebAuthnSign(tab, keyHandle, { appid: appid + "2" })
.then(arrivingHereIsBad)
.catch(expectInvalidStateError);
let rpId = new TextEncoder("utf-8").encode(appid);
let rpIdHash = await crypto.subtle.digest("SHA-256", rpId);
// Succeed with the right fallback rpId.
await promiseWebAuthnSign(tab, keyHandle, { appid }).then(
({ authenticatorData, clientDataJSON, extensions }) => {
is(extensions.appid, true, "appid extension was acted upon");
// Check that the correct rpIdHash is returned.
let rpIdHashSign = authenticatorData.slice(0, 32);
ok(memcmp(rpIdHash, rpIdHashSign), "rpIdHash is correct");
let clientData = JSON.parse(buffer2string(clientDataJSON));
is(clientData.clientExtensions.appid, appid, "appid extension sent");
}
);
// Close tab.
BrowserTestUtils.removeTab(tab);
});
add_task(function test_cleanup() {
Services.prefs.clearUserPref("security.webauth.u2f");
Services.prefs.clearUserPref("security.webauth.webauthn");
Services.prefs.clearUserPref("security.webauth.webauthn_enable_softtoken");
Services.prefs.clearUserPref("security.webauth.webauthn_enable_usbtoken");
});