Bug 1854618 - Implement about:webauthn page to display authenticator info (r=jschanck,desktop-theme-reviewers,bolsson)

Differential Revision: https://phabricator.services.mozilla.com/D188973
This commit is contained in:
M. Sirringhaus 2023-10-31 21:34:34 +00:00
Родитель b2c34cc36b
Коммит f4b1edc284
19 изменённых файлов: 852 добавлений и 15 удалений

Просмотреть файл

@ -263,6 +263,11 @@ var allowlist = [
file: "resource://gre/localization/en-US/toolkit/about/aboutThirdParty.ftl",
platforms: ["linux", "macosx"],
},
// Bug 1854618 - referenced by aboutWebauthn.html which is only for Linux and Mac
{
file: "resource://gre/localization/en-US/toolkit/about/aboutWebauthn.ftl",
platforms: ["win", "android"],
},
// Bug 1973834 - referenced by aboutWindowsMessages.html which is only for Windows
{
file: "resource://gre/localization/en-US/toolkit/about/aboutWindowsMessages.ftl",

Просмотреть файл

@ -131,6 +131,10 @@ static const RedirEntry kRedirMap[] = {
nsIAboutModule::IS_SECURE_CHROME_UI},
{"mozilla", "chrome://global/content/mozilla.html",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT},
#if !defined(ANDROID) && !defined(XP_WIN)
{"webauthn", "chrome://global/content/aboutWebauthn.html",
nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::IS_SECURE_CHROME_UI},
#endif
{"neterror", "chrome://global/content/aboutNetError.xhtml",
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |

Просмотреть файл

@ -44,6 +44,8 @@ if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] == 'windows':
about_pages.append('windows-messages')
if not defined('MOZ_GLEAN_ANDROID'):
about_pages.append('glean')
if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] != 'android' and buildconfig.substs['MOZ_WIDGET_TOOLKIT'] != 'windows':
about_pages.append('webauthn')
Headers = ['/docshell/build/nsDocShellModule.h']

Просмотреть файл

@ -369,5 +369,8 @@ AndroidWebAuthnService::SetUserVerified(uint64_t authenticatorId,
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -160,4 +160,12 @@ WebAuthnService::SetUserVerified(uint64_t authenticatorId,
return mPlatformService->SetUserVerified(authenticatorId, isUserVerified);
}
NS_IMETHODIMP
WebAuthnService::Listen() {
if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) {
return mTestService->Listen();
}
return mPlatformService->Listen();
}
} // namespace mozilla::dom

Просмотреть файл

@ -0,0 +1,49 @@
/* 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 super::*;
use authenticator::{InteractiveRequest, InteractiveUpdate};
pub(crate) type InteractiveManagementReceiver = Option<Sender<InteractiveRequest>>;
pub(crate) fn send_about_prompt(prompt: &BrowserPromptType) -> Result<(), nsresult> {
let json = nsString::from(&serde_json::to_string(&prompt).unwrap_or_default());
notify_observers(PromptTarget::AboutPage, json)
}
pub(crate) fn interactive_status_callback(
status_rx: Receiver<StatusUpdate>,
transaction: Arc<Mutex<Option<TransactionState>>>, /* Shared with an AuthrsTransport */
) -> Result<(), nsresult> {
loop {
match status_rx.recv() {
Ok(StatusUpdate::InteractiveManagement(InteractiveUpdate::StartManagement((
tx,
auth_info,
)))) => {
let mut guard = transaction.lock().unwrap();
let Some(transaction) = guard.as_mut() else {
warn!("STATUS: received status update after end of transaction.");
break;
};
transaction.interactive_receiver.replace(tx);
let prompt = BrowserPromptType::SelectedDevice { auth_info };
send_about_prompt(&prompt)?;
}
Ok(StatusUpdate::SelectDeviceNotice) => {
info!("STATUS: Please select a device by touching one of them.");
let prompt = BrowserPromptType::SelectDevice;
send_about_prompt(&prompt)?;
}
Ok(_) => {
// currently not handled
continue;
}
Err(RecvError) => {
info!("STATUS: end");
break;
}
}
}
Ok(())
}

Просмотреть файл

@ -20,7 +20,8 @@ use authenticator::{
},
errors::AuthenticatorError,
statecallback::StateCallback,
Pin, RegisterResult, SignResult, StateMachine, StatusPinUv, StatusUpdate,
AuthenticatorInfo, ManageResult, Pin, RegisterResult, SignResult, StateMachine, StatusPinUv,
StatusUpdate,
};
use base64::Engine;
use cstr::cstr;
@ -44,7 +45,8 @@ use xpcom::interfaces::{
nsIWebAuthnSignPromise, nsIWebAuthnSignResult,
};
use xpcom::{xpcom_method, RefPtr};
mod about_webauthn_controller;
use about_webauthn_controller::*;
mod test_token;
use test_token::TestTokenManager;
@ -77,6 +79,9 @@ enum BrowserPromptType<'a> {
SelectDevice,
UvBlocked,
PinRequired,
SelectedDevice {
auth_info: Option<AuthenticatorInfo>,
},
PinInvalid {
retries: Option<u8>,
},
@ -87,6 +92,14 @@ enum BrowserPromptType<'a> {
SelectSignResult {
entities: &'a [PublicKeyCredentialUserEntity],
},
ListenSuccess,
ListenError,
}
#[derive(Debug)]
enum PromptTarget {
Browser,
AboutPage,
}
#[derive(Serialize)]
@ -98,13 +111,29 @@ struct BrowserPromptMessage<'a> {
browsing_context_id: Option<u64>,
}
fn notify_observers(prompt_target: PromptTarget, json: nsString) -> Result<(), nsresult> {
let main_thread = get_main_thread()?;
let target = match prompt_target {
PromptTarget::Browser => cstr!("webauthn-prompt"),
PromptTarget::AboutPage => cstr!("about-webauthn-prompt"),
};
RunnableBuilder::new("AuthrsService::send_prompt", move || {
if let Ok(obs_svc) = xpcom::components::Observer::service::<nsIObserverService>() {
unsafe {
obs_svc.NotifyObservers(std::ptr::null(), target.as_ptr(), json.as_ptr());
}
}
})
.dispatch(main_thread.coerce())
}
fn send_prompt(
prompt: BrowserPromptType,
tid: u64,
origin: Option<&str>,
browsing_context_id: Option<u64>,
) -> Result<(), nsresult> {
let main_thread = get_main_thread()?;
let mut json = nsString::new();
write!(
json,
@ -117,18 +146,7 @@ fn send_prompt(
})
)
.or(Err(NS_ERROR_FAILURE))?;
RunnableBuilder::new("AuthrsService::send_prompt", move || {
if let Ok(obs_svc) = xpcom::components::Observer::service::<nsIObserverService>() {
unsafe {
obs_svc.NotifyObservers(
std::ptr::null(),
cstr!("webauthn-prompt").as_ptr(),
json.as_ptr(),
);
}
}
})
.dispatch(main_thread.coerce())
notify_observers(PromptTarget::Browser, json)
}
fn cancel_prompts(tid: u64) -> Result<(), nsresult> {
@ -499,6 +517,7 @@ impl SignPromise {
#[derive(Clone)]
enum TransactionPromise {
Listen,
Register(RegisterPromise),
Sign(SignPromise),
}
@ -506,6 +525,7 @@ enum TransactionPromise {
impl TransactionPromise {
fn reject(&self, err: nsresult) -> Result<(), nsresult> {
match self {
TransactionPromise::Listen => Ok(()),
TransactionPromise::Register(promise) => promise.resolve_or_reject(Err(err)),
TransactionPromise::Sign(promise) => promise.resolve_or_reject(Err(err)),
}
@ -525,6 +545,7 @@ struct TransactionState {
promise: TransactionPromise,
pin_receiver: PinReceiver,
selection_receiver: SelectionReceiver,
interactive_receiver: InteractiveManagementReceiver,
}
// AuthrsService provides an nsIWebAuthnService built on top of authenticator-rs.
@ -717,6 +738,7 @@ impl AuthrsService {
browsing_context_id,
pending_args: Some(TransactionArgs::Register(timeout_ms as u64, info)),
promise: TransactionPromise::Register(promise),
interactive_receiver: None,
pin_receiver: None,
selection_receiver: None,
});
@ -752,6 +774,10 @@ impl AuthrsService {
let Some(TransactionArgs::Register(timeout_ms, info)) = state.pending_args.take() else {
return Err(NS_ERROR_FAILURE);
};
// We have to drop the guard here, as there _may_ still be another operation
// ongoing and `register()` below will try to cancel it. This will call the state
// callback of that operation, which in turn may try to access `transaction`, deadlocking.
drop(guard);
let (status_tx, status_rx) = channel::<StatusUpdate>();
let status_transaction = self.transaction.clone();
@ -803,6 +829,7 @@ impl AuthrsService {
let _ = cancel_prompts(tid);
}
let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
*guard = None;
}),
);
@ -935,6 +962,7 @@ impl AuthrsService {
let _ = cancel_prompts(tid);
}
let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
*guard = None;
}),
);
@ -966,6 +994,7 @@ impl AuthrsService {
browsing_context_id,
pending_args: None,
promise: TransactionPromise::Sign(promise),
interactive_receiver: None,
pin_receiver: None,
selection_receiver: None,
});
@ -1143,6 +1172,81 @@ impl AuthrsService {
self.test_token_manager
.set_user_verified(authenticator_id, is_user_verified)
}
xpcom_method!(listen => Listen());
pub(crate) fn listen(&self) -> Result<(), nsresult> {
// For now, we don't support softtokens
if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
return Ok(());
}
{
let mut guard = self.transaction.lock().unwrap();
if guard.as_ref().is_some() {
// ignore listen() and continue with ongoing transaction
return Ok(());
}
*guard = Some(TransactionState {
tid: 0,
browsing_context_id: 0,
pending_args: None,
promise: TransactionPromise::Listen,
interactive_receiver: None,
pin_receiver: None,
selection_receiver: None,
});
}
let callback_transaction = self.transaction.clone();
let state_callback = StateCallback::<Result<ManageResult, AuthenticatorError>>::new(
Box::new(move |result| {
let mut guard = callback_transaction.lock().unwrap();
let Some(state) = guard.as_mut() else {
return;
};
match state.promise {
TransactionPromise::Listen => (),
_ => return,
}
*guard = None;
let msg = match result {
Ok(_) => BrowserPromptType::ListenSuccess,
Err(_) => BrowserPromptType::ListenError,
};
let _ = send_about_prompt(&msg);
}),
);
// Calling `manage()` within the lock, to avoid race conditions
// where we might check listen_blocked, see that it's false,
// continue along, but in parallel `make_credential()` aborts the
// interactive process shortly after, setting listen_blocked to true,
// then accessing usb_token_manager afterwards and at the same time
// we do it here, causing a runtime crash for trying to mut-borrow it twice.
let (status_tx, status_rx) = channel::<StatusUpdate>();
let status_transaction = self.transaction.clone();
RunnableBuilder::new(
"AuthrsTransport::AboutWebauthn::StatusReceiver",
move || {
let _ = interactive_status_callback(status_rx, status_transaction);
},
)
.may_block(true)
.dispatch_background_task()?;
if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
self.usb_token_manager.lock().unwrap().manage(
60 * 1000 * 1000,
status_tx,
state_callback,
);
} else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
// We don't yet support softtoken
} else {
// Silently accept request, if all webauthn-options are disabled.
// Used for testing.
}
Ok(())
}
}
#[no_mangle]

Просмотреть файл

@ -90,4 +90,7 @@ interface nsIWebAuthnService : nsISupports
// Sets the "isUserVerified" bit on a virtual authenticator. See
// https://w3c.github.io/webauthn/#sctn-automation-set-user-verified
void setUserVerified(in uint64_t authenticatorId, in bool isUserVerified);
// about:webauthn-specific functions
void listen();
};

Просмотреть файл

@ -0,0 +1,61 @@
/* 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/. */
@import url("chrome://global/skin/in-content/common.css");
html {
height: 100%;
}
body {
display: flex;
align-items: stretch;
height: 100%;
}
label {
display: block;
}
#info-text-div {
padding: 20px;
}
#ctap-listen-div {
padding-top: 15px;
}
.category {
cursor: pointer;
/* Center category names */
display: flex;
align-items: center;
}
.disabled-category {
pointer-events: none;
}
@media (max-width: 830px){
#categories > .category {
padding-inline-start: 5px;
margin-inline-start: 0;
}
}
#main-content {
flex: 1;
}
.token-info-flex-box {
display: flex;
}
.token-info-flex-child {
flex: 1;
}
.token-info-flex-child#authenticator-options {
margin-inline-end: 2px;
}

Просмотреть файл

@ -0,0 +1,56 @@
<!DOCTYPE html>
<!-- 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/. -->
<html>
<head>
<meta
http-equiv="Content-Security-Policy"
content="default-src chrome:; style-src chrome:; object-src 'none'"
/>
<meta charset="utf-8" />
<title data-l10n-id="about-webauthn-page-title"></title>
<link rel="stylesheet" href="chrome://global/content/aboutWebauthn.css" />
<script src="chrome://global/content/aboutWebauthn.js"></script>
<link rel="localization" href="toolkit/about/aboutWebauthn.ftl" />
</head>
<body id="body">
<div id="info-text-div">
<label
id="info-text-field"
data-l10n-id="about-webauthn-text-connect-device"
></label>
</div>
<div hidden id="categories">
<div id="info-tab-button" class="category" selected="true">
<span
class="tablinks"
data-l10n-id="about-webauthn-info-section-title"
></span>
</div>
</div>
<div hidden id="main-content">
<div hidden class="tabcontent token-info-section" id="token-info-section">
<h2
class="categoryTitle"
data-l10n-id="about-webauthn-info-section-title"
></h2>
<div id="ctap2-token-info" class="token-info-flex-box" display="flex">
<div id="ctap2-token-info-options" class="token-info-flex-child">
<h3 data-l10n-id="about-webauthn-options-subsection-title"></h3>
<table id="authenticator-options"></table>
</div>
<div id="ctap2-token-info-info" class="token-info-flex-child">
<h3 data-l10n-id="about-webauthn-info-subsection-title"></h3>
<table id="authenticator-info"></table>
</div>
</div>
</div>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,142 @@
/* 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 AboutWebauthnService = null;
var AboutWebauthnManagerJS = {
_topic: "about-webauthn-prompt",
_initialized: false,
_l10n: null,
init() {
if (this._initialized) {
return;
}
this._l10n = new Localization(["toolkit/about/aboutWebauthn.ftl"], true);
Services.obs.addObserver(this, this._topic);
this._initialized = true;
reset_page();
},
uninit() {
Services.obs.removeObserver(this, this._topic);
},
observe(aSubject, aTopic, aData) {
let data = JSON.parse(aData);
// We have token
if (data.type == "selected-device") {
this._curr_data = data.auth_info;
document.getElementById("token-info-section").style.display = "block";
this.show_ui_based_on_authenticator_info(data);
} else if (data.type == "select-device") {
set_info_text("about-webauthn-text-select-device");
} else if (data.type == "listen-success" || data.type == "listen-error") {
reset_page();
AboutWebauthnService.listen();
}
},
show_authenticator_options(options, element, l10n_base) {
let table = document.getElementById(element);
var empty_table = document.createElement("table");
empty_table.id = element;
table.parentNode.replaceChild(empty_table, table);
table = document.getElementById(element);
for (let key in options) {
if (key == "options") {
continue;
}
// Create an empty <tr> element and add it to the 1st position of the table:
var row = table.insertRow(0);
// Insert new cells (<td> elements) at the 1st and 2nd position of the "new" <tr> element:
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
// Add some text to the new cells:
let key_text = this._l10n.formatValueSync(
l10n_base + "-" + key.toLowerCase().replace(/_/g, "-")
);
var key_node = document.createTextNode(key_text);
cell1.appendChild(key_node);
var raw_value = JSON.stringify(options[key]);
var value = raw_value;
if (["true", "false", "null"].includes(raw_value)) {
value = this._l10n.formatValueSync(l10n_base + "-" + raw_value);
}
var value_node = document.createTextNode(value);
cell2.appendChild(value_node);
}
},
show_ui_based_on_authenticator_info(data) {
// Hide the "Please plug in a token"-message
document.getElementById("info-text-div").hidden = true;
// Show options, based on what the token supports
if (data.auth_info) {
document.getElementById("main-content").hidden = false;
document.getElementById("categories").hidden = false;
this.show_authenticator_options(
data.auth_info.options,
"authenticator-options",
"about-webauthn-auth-option"
);
this.show_authenticator_options(
data.auth_info,
"authenticator-info",
"about-webauthn-auth-info"
);
} else {
// Currently auth-rs doesn't send this, because it filters out ctap2-devices.
// U2F / CTAP1 tokens can't be managed
set_info_text("about-webauthn-text-non-ctap2-device");
}
},
};
function set_info_text(l10nId) {
document.getElementById("info-text-div").hidden = false;
let field = document.getElementById("info-text-field");
field.setAttribute("data-l10n-id", l10nId);
}
function reset_page() {
// Hide all main sections
document.getElementById("main-content").hidden = true;
document.getElementById("categories").hidden = true;
// Only display the "please connect a device" - text
set_info_text("about-webauthn-text-connect-device");
}
async function onLoad() {
AboutWebauthnManagerJS.init();
try {
AboutWebauthnService.listen();
} catch (ex) {
set_info_text("about-webauthn-text-not-available");
AboutWebauthnManagerJS.uninit();
}
}
try {
AboutWebauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
Ci.nsIWebAuthnService
);
document.addEventListener("DOMContentLoaded", onLoad);
window.addEventListener("beforeunload", event => {
AboutWebauthnManagerJS.uninit();
if (AboutWebauthnService) {
AboutWebauthnService.cancel(0);
}
});
} catch (ex) {
// Do nothing if we fail to create a singleton instance,
// showing the default no-module message.
console.error(ex);
}

Просмотреть файл

@ -0,0 +1,8 @@
# 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/.
toolkit.jar:
content/global/aboutWebauthn.css (content/aboutWebauthn.css)
content/global/aboutWebauthn.html (content/aboutWebauthn.html)
content/global/aboutWebauthn.js (content/aboutWebauthn.js)

Просмотреть файл

@ -0,0 +1,13 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
with Files("**"):
BUG_COMPONENT = ("Core", "DOM: Web Authentication")
if CONFIG["MOZ_WIDGET_TOOLKIT"] not in ("windows", "android"):
BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"]
JAR_MANIFESTS += ["jar.mn"]

Просмотреть файл

@ -0,0 +1,11 @@
[DEFAULT]
head = head.js
prefs =
security.webauth.webauthn=true
security.webauth.webauthn_enable_softtoken=false
security.webauth.webauthn_enable_android_fido2=false
security.webauth.webauthn_enable_usbtoken=false
security.webauthn.ctap2=true
[browser_aboutwebauthn_info.js]
[browser_aboutwebauthn_no_token.js]

Просмотреть файл

@ -0,0 +1,218 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var doc, tab;
add_setup(async function () {
info("Starting about:webauthn");
tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: "about:webauthn",
waitForLoad: true,
});
doc = tab.linkedBrowser.contentDocument;
});
registerCleanupFunction(async function () {
// Close tab.
await BrowserTestUtils.removeTab(tab);
});
function send_auth_data_and_check(auth_data) {
Services.obs.notifyObservers(
null,
"about-webauthn-prompt",
JSON.stringify({ type: "selected-device", auth_info: auth_data })
);
let info_text = doc.getElementById("info-text-div");
is(info_text.hidden, true, "Start prompt not hidden");
let info_section = doc.getElementById("token-info-section");
isnot(info_section.style.display, "none", "Info section hidden");
}
add_task(async function multiple_devices() {
Services.obs.notifyObservers(
null,
"about-webauthn-prompt",
JSON.stringify({ type: "select-device" })
);
let info_text = doc.getElementById("info-text-div");
is(info_text.hidden, false, "Start prompt hidden");
let field = doc.getElementById("info-text-field");
is(
field.getAttribute("data-l10n-id"),
"about-webauthn-text-select-device",
"Field does not prompt user to touch device for selection"
);
});
add_task(async function multiple_devices() {
send_auth_data_and_check(REAL_AUTH_INFO_1);
reset_about_page(doc);
send_auth_data_and_check(REAL_AUTH_INFO_2);
reset_about_page(doc);
send_auth_data_and_check(REAL_AUTH_INFO_3);
reset_about_page(doc);
});
// Yubikey BIO
const REAL_AUTH_INFO_1 = {
versions: ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE", "FIDO_2_1"],
extensions: [
"credProtect",
"hmac-secret",
"largeBlobKey",
"credBlob",
"minPinLength",
],
aaguid: [
216, 82, 45, 159, 87, 91, 72, 102, 136, 169, 186, 153, 250, 2, 243, 91,
],
options: {
plat: false,
rk: true,
clientPin: true,
up: true,
uv: true,
pinUvAuthToken: true,
noMcGaPermissionsWithClientPin: null,
largeBlobs: true,
ep: null,
bioEnroll: true,
userVerificationMgmtPreview: true,
uvBioEnroll: null,
authnrCfg: true,
uvAcfg: null,
credMgmt: true,
credentialMgmtPreview: true,
setMinPINLength: true,
makeCredUvNotRqd: true,
alwaysUv: false,
},
max_msg_size: 1200,
pin_protocols: [2, 1],
max_credential_count_in_list: 8,
max_credential_id_length: 128,
transports: ["usb"],
algorithms: [
{ alg: -7, type: "public-key" },
{ alg: -8, type: "public-key" },
],
max_ser_large_blob_array: 1024,
force_pin_change: false,
min_pin_length: 4,
firmware_version: 328966,
max_cred_blob_length: 32,
max_rpids_for_set_min_pin_length: 1,
preferred_platform_uv_attempts: 3,
uv_modality: 2,
certifications: null,
remaining_discoverable_credentials: 20,
vendor_prototype_config_commands: null,
};
// Yubikey 5
const REAL_AUTH_INFO_2 = {
versions: ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE"],
extensions: ["credProtect", "hmac-secret"],
aaguid: [
47, 192, 87, 159, 129, 19, 71, 234, 177, 22, 187, 90, 141, 185, 32, 42,
],
options: {
plat: false,
rk: true,
clientPin: true,
up: true,
uv: null,
pinUvAuthToken: null,
noMcGaPermissionsWithClientPin: null,
largeBlobs: null,
ep: null,
bioEnroll: null,
userVerificationMgmtPreview: null,
uvBioEnroll: null,
authnrCfg: null,
uvAcfg: null,
credMgmt: null,
credentialMgmtPreview: true,
setMinPINLength: null,
makeCredUvNotRqd: null,
alwaysUv: null,
},
max_msg_size: 1200,
pin_protocols: [1],
max_credential_count_in_list: 8,
max_credential_id_length: 128,
transports: ["nfc", "usb"],
algorithms: [
{ alg: -7, type: "public-key" },
{ alg: -8, type: "public-key" },
],
max_ser_large_blob_array: null,
force_pin_change: null,
min_pin_length: null,
firmware_version: null,
max_cred_blob_length: null,
max_rpids_for_set_min_pin_length: null,
preferred_platform_uv_attempts: null,
uv_modality: null,
certifications: null,
remaining_discoverable_credentials: null,
vendor_prototype_config_commands: null,
};
// Nitrokey 3
const REAL_AUTH_INFO_3 = {
versions: ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE"],
extensions: ["credProtect", "hmac-secret"],
aaguid: [
47, 192, 87, 159, 129, 19, 71, 234, 177, 22, 187, 90, 141, 185, 32, 42,
],
options: {
plat: false,
rk: true,
clientPin: true,
up: true,
uv: null,
pinUvAuthToken: null,
noMcGaPermissionsWithClientPin: null,
largeBlobs: null,
ep: null,
bioEnroll: null,
userVerificationMgmtPreview: null,
uvBioEnroll: null,
authnrCfg: null,
uvAcfg: null,
credMgmt: null,
credentialMgmtPreview: true,
setMinPINLength: null,
makeCredUvNotRqd: null,
alwaysUv: null,
},
max_msg_size: 1200,
pin_protocols: [1],
max_credential_count_in_list: 8,
max_credential_id_length: 128,
transports: ["nfc", "usb"],
algorithms: [
{ alg: -7, type: "public-key" },
{ alg: -8, type: "public-key" },
],
max_ser_large_blob_array: null,
force_pin_change: null,
min_pin_length: null,
firmware_version: null,
max_cred_blob_length: null,
max_rpids_for_set_min_pin_length: null,
preferred_platform_uv_attempts: null,
uv_modality: null,
certifications: null,
remaining_discoverable_credentials: null,
vendor_prototype_config_commands: null,
};

Просмотреть файл

@ -0,0 +1,49 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var doc, tab;
add_setup(async function () {
info("Starting about:webauthn");
tab = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
opening: "about:webauthn",
waitForLoad: true,
});
doc = tab.linkedBrowser.contentDocument;
});
registerCleanupFunction(async function () {
// Close tab.
await BrowserTestUtils.removeTab(tab);
});
add_task(async function verify_page_no_token() {
let info_text = doc.getElementById("info-text-div");
is(info_text.hidden, false, "info-text-div should be visible");
let categories = doc.getElementById("categories");
is(categories.hidden, true, "categories-sidebar should be invisible");
});
add_task(async function verify_no_auth_info() {
let field = doc.getElementById("info-text-field");
let promise = BrowserTestUtils.waitForMutationCondition(
field,
{ attributes: true, attributeFilter: ["data-l10n-id"] },
() =>
field.getAttribute("data-l10n-id") ===
"about-webauthn-text-non-ctap2-device"
);
Services.obs.notifyObservers(
null,
"about-webauthn-prompt",
JSON.stringify({ type: "selected-device", auth_info: null })
);
await promise;
let info_text = doc.getElementById("info-text-div");
is(info_text.hidden, false);
});

Просмотреть файл

@ -0,0 +1,11 @@
async function reset_about_page(doc) {
let info_text = doc.getElementById("info-text-div");
let msg = JSON.stringify({ action: "listen-finished-success" });
let promise = BrowserTestUtils.waitForMutationCondition(
info_text,
{ attributes: true, attributeFilter: ["hidden"] },
() => info_text.hidden !== false
);
Services.obs.notifyObservers(null, "about-webauthn-prompt", msg);
await promise;
}

Просмотреть файл

@ -124,6 +124,9 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android":
"components.conf",
]
if CONFIG["MOZ_WIDGET_TOOLKIT"] not in ("android", "windows"):
DIRS += ["aboutwebauthn"]
if CONFIG["MOZ_BUILD_APP"] == "browser":
DIRS += ["normandy", "messaging-system"]

Просмотреть файл

@ -0,0 +1,87 @@
# 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/.
### Localization for about:webauthn, a security token management page
# Page title
# 'WebAuthn' is a protocol name and should not be translated
about-webauthn-page-title = About WebAuthn
## Section titles
about-webauthn-info-section-title = Device info
about-webauthn-info-subsection-title = Authenticator info
about-webauthn-options-subsection-title = Authenticator options
## Info field texts
about-webauthn-text-connect-device = Please connect a security token.
# If multiple devices are plugged in, they will blink and we are asking the user to select one by touching the device they want.
about-webauthn-text-select-device = Please select your desired security token by touching the device.
# CTAP2 refers to Client to Authenticator Protocol version 2
about-webauthn-text-non-ctap2-device = Unable to manage options because your security token does not support CTAP2.
about-webauthn-text-not-available = Not available on this platform.
## Authenticator options fields
## Option fields correspond to the CTAP2 option IDs and definitions found in https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#option-id
about-webauthn-auth-option-uv = User verification
about-webauthn-auth-option-up = User presence
about-webauthn-auth-option-clientpin = Client PIN
about-webauthn-auth-option-rk = Resident key
about-webauthn-auth-option-plat = Platform device
# pinUvAuthToken should not be translated.
about-webauthn-auth-option-pinuvauthtoken = Command permissions (pinUvAuthToken)
# MakeCredential and GetAssertion should not be translated.
about-webauthn-auth-option-nomcgapermissionswithclientpin = No MakeCredential / GetAssertion permissions with client PIN
about-webauthn-auth-option-largeblobs = Large blobs
about-webauthn-auth-option-ep = Enterprise attestation
about-webauthn-auth-option-bioenroll = Biometric enrollment
# FIDO_2_1_PRE should not be translated.
about-webauthn-auth-option-userverificationmgmtpreview = Prototype of biometric enrollment (FIDO_2_1_PRE)
about-webauthn-auth-option-uvbioenroll = Biometric enrollment permission
about-webauthn-auth-option-authnrcfg = Authenticator config
about-webauthn-auth-option-uvacfg = Authenticator config permission
about-webauthn-auth-option-credmgmt = Credential management
about-webauthn-auth-option-credentialmgmtpreview = Prototype credential management
about-webauthn-auth-option-setminpinlength = Set minimum PIN length
# MakeCredential should not be translated.
about-webauthn-auth-option-makecreduvnotrqd = MakeCredential without user verification
about-webauthn-auth-option-alwaysuv = Always require user verification
# Shows when boolean value for an option is True. True should not be translated.
about-webauthn-auth-option-true = True
# Shows when boolean value of an option is False. False should not be translated.
about-webauthn-auth-option-false = False
# If the value is missing (null), it means a certain feature is not supported.
about-webauthn-auth-option-null = Not supported
## Authenticator info fields
## Info fields correspond to the CTAP2 authenticatorGetInfo field member name and definitions found in https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetInfo
about-webauthn-auth-info-vendor-prototype-config-commands = Vendor prototype config commands
about-webauthn-auth-info-remaining-discoverable-credentials = Remaining discoverable credentials
about-webauthn-auth-info-certifications = Certifications
about-webauthn-auth-info-uv-modality = User verification modality
about-webauthn-auth-info-preferred-platform-uv-attempts = Preferred platform user verification attempts
about-webauthn-auth-info-max-rpids-for-set-min-pin-length = Max relying party IDs for set minimum PIN length
about-webauthn-auth-info-max-cred-blob-length = Max credential blob length
about-webauthn-auth-info-firmware-version = Firmware version
about-webauthn-auth-info-min-pin-length = Minimum PIN length
about-webauthn-auth-info-force-pin-change = Force PIN change
about-webauthn-auth-info-max-ser-large-blob-array = Max size of large blob array
about-webauthn-auth-info-algorithms = Algorithms
about-webauthn-auth-info-transports = Transports
about-webauthn-auth-info-max-credential-id-length = Max credential ID length
about-webauthn-auth-info-max-credential-count-in-list = Max credential count in list
about-webauthn-auth-info-pin-protocols = PIN protocols
about-webauthn-auth-info-max-msg-size = Max message size
# AAGUID should not be translated.
about-webauthn-auth-info-aaguid = AAGUID
about-webauthn-auth-info-extensions = Extensions
about-webauthn-auth-info-versions = Versions
# Shows when boolean value for an info field is True. True should not be translated.
about-webauthn-auth-info-true = True
# Shows when boolean value for an info field is False. False should not be translated.
about-webauthn-auth-info-false = False
about-webauthn-auth-info-null = Not supported