Backed out 3 changesets (bug 1854908, bug 1854618, bug 1854620) for build bustages. CLOSED TREE

Backed out changeset b87eea9fe548 (bug 1854908)
Backed out changeset 501726f36e9c (bug 1854620)
Backed out changeset 84dd48da983c (bug 1854618)
This commit is contained in:
Narcis Beleuzu 2023-11-01 00:14:29 +02:00
parent e95a094d18
commit f078d4fd81
21 changed files with 34 additions and 2181 deletions

View file

@ -263,11 +263,6 @@ 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",

View file

@ -131,10 +131,6 @@ 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 |

View file

@ -44,8 +44,6 @@ 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']

View file

@ -369,13 +369,5 @@ AndroidWebAuthnService::SetUserVerified(uint64_t authenticatorId,
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
AndroidWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
NS_IMETHODIMP
AndroidWebAuthnService::RunCommand(const nsACString& cmd) {
return NS_ERROR_NOT_IMPLEMENTED;
}
} // namespace dom
} // namespace mozilla

View file

@ -160,20 +160,4 @@ 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();
}
NS_IMETHODIMP
WebAuthnService::RunCommand(const nsACString& cmd) {
if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) {
return mTestService->RunCommand(cmd);
}
return mPlatformService->RunCommand(cmd);
}
} // namespace mozilla::dom

View file

@ -1,155 +0,0 @@
/* 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::{
ctap2::commands::{PinUvAuthResult, StatusCode},
errors::{CommandError, HIDError},
CredManagementCmd, InteractiveRequest, InteractiveUpdate, PinError,
};
use serde::{Deserialize, Serialize};
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)
}
// A wrapper around InteractiveRequest, that leaves out the PUAT
// so that we can easily de/serialize it to/from JSON for the JS-side
// and then add our cached PUAT, if we have one.
#[derive(Debug, Serialize, Deserialize)]
pub enum RequestWrapper {
Quit,
ChangePIN(Pin, Pin),
SetPIN(Pin),
CredentialManagement(CredManagementCmd),
}
pub(crate) fn authrs_to_prompt<'a>(e: AuthenticatorError) -> BrowserPromptType<'a> {
match e {
AuthenticatorError::PinError(PinError::PinIsTooShort) => BrowserPromptType::PinIsTooShort,
AuthenticatorError::PinError(PinError::PinRequired) => BrowserPromptType::PinRequired,
AuthenticatorError::PinError(PinError::PinIsTooLong(_)) => BrowserPromptType::PinIsTooLong,
AuthenticatorError::PinError(PinError::InvalidPin(r)) => {
BrowserPromptType::PinInvalid { retries: r }
}
AuthenticatorError::PinError(PinError::PinAuthBlocked) => BrowserPromptType::PinAuthBlocked,
AuthenticatorError::PinError(PinError::PinBlocked) => BrowserPromptType::DeviceBlocked,
AuthenticatorError::PinError(PinError::UvBlocked) => BrowserPromptType::UvBlocked,
AuthenticatorError::PinError(PinError::InvalidUv(r)) => {
BrowserPromptType::UvInvalid { retries: r }
}
AuthenticatorError::CancelledByUser
| AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
StatusCode::KeepaliveCancel,
_,
))) => BrowserPromptType::Cancel,
_ => BrowserPromptType::UnknownError,
}
}
pub(crate) fn cache_puat(
transaction: Arc<Mutex<Option<TransactionState>>>,
puat: Option<PinUvAuthResult>,
) {
let mut guard = transaction.lock().unwrap();
if let Some(transaction) = guard.as_mut() {
transaction.puat_cache = puat;
};
}
pub(crate) fn interactive_status_callback(
status_rx: Receiver<StatusUpdate>,
transaction: Arc<Mutex<Option<TransactionState>>>, /* Shared with an AuthrsTransport */
upcoming_error: Arc<Mutex<Option<AuthenticatorError>>>,
) -> 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::InteractiveManagement(
InteractiveUpdate::CredentialManagementUpdate((cfg_result, puat_res)),
)) => {
cache_puat(transaction.clone(), puat_res); // We don't care if we fail here. Worst-case: User has to enter PIN more often.
let prompt = BrowserPromptType::CredentialManagementUpdate { result: cfg_result };
send_about_prompt(&prompt)?;
continue;
}
Ok(StatusUpdate::SelectDeviceNotice) => {
info!("STATUS: Please select a device by touching one of them.");
let prompt = BrowserPromptType::SelectDevice;
send_about_prompt(&prompt)?;
}
Ok(StatusUpdate::PinUvError(e)) => {
let mut guard = transaction.lock().unwrap();
let Some(transaction) = guard.as_mut() else {
warn!("STATUS: received status update after end of transaction.");
break;
};
let autherr = match e {
StatusPinUv::PinRequired(pin_sender) => {
transaction.pin_receiver.replace((0, pin_sender));
send_about_prompt(&BrowserPromptType::PinRequired)?;
continue;
}
StatusPinUv::InvalidPin(pin_sender, r) => {
transaction.pin_receiver.replace((0, pin_sender));
send_about_prompt(&BrowserPromptType::PinInvalid { retries: r })?;
continue;
}
StatusPinUv::PinIsTooShort => {
AuthenticatorError::PinError(PinError::PinIsTooShort)
}
StatusPinUv::PinIsTooLong(s) => {
AuthenticatorError::PinError(PinError::PinIsTooLong(s))
}
StatusPinUv::InvalidUv(r) => {
send_about_prompt(&BrowserPromptType::UvInvalid { retries: r })?;
continue;
}
StatusPinUv::PinAuthBlocked => {
AuthenticatorError::PinError(PinError::PinAuthBlocked)
}
StatusPinUv::PinBlocked => AuthenticatorError::PinError(PinError::PinBlocked),
StatusPinUv::PinNotSet => AuthenticatorError::PinError(PinError::PinNotSet),
StatusPinUv::UvBlocked => AuthenticatorError::PinError(PinError::UvBlocked),
};
// We will cause auth-rs to return an error, once we leave this block
// due to us 'hanging up'. Before we do that, we will safe the actual
// error that caused this, so our callback-function can return the true
// error to JS, instead of "cancelled by user".
let guard = upcoming_error.lock();
if let Ok(mut entry) = guard {
entry.replace(autherr);
} else {
return Err(NS_ERROR_DOM_INVALID_STATE_ERR);
}
warn!("STATUS: Pin Error {:?}", e);
break;
}
Ok(_) => {
// currently not handled
continue;
}
Err(RecvError) => {
info!("STATUS: end");
break;
}
}
}
Ok(())
}

View file

@ -11,7 +11,7 @@ extern crate xpcom;
use authenticator::{
authenticatorservice::{RegisterArgs, SignArgs},
ctap2::attestation::AttestationObject,
ctap2::commands::{get_info::AuthenticatorVersion, PinUvAuthResult},
ctap2::commands::get_info::AuthenticatorVersion,
ctap2::server::{
AuthenticationExtensionsClientInputs, AuthenticatorAttachment,
PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
@ -20,16 +20,15 @@ use authenticator::{
},
errors::AuthenticatorError,
statecallback::StateCallback,
AuthenticatorInfo, CredentialManagementResult, InteractiveRequest, ManageResult, Pin,
RegisterResult, SignResult, StateMachine, StatusPinUv, StatusUpdate,
Pin, RegisterResult, SignResult, StateMachine, StatusPinUv, StatusUpdate,
};
use base64::Engine;
use cstr::cstr;
use moz_task::{get_main_thread, RunnableBuilder};
use nserror::{
nsresult, NS_ERROR_DOM_ABORT_ERR, NS_ERROR_DOM_INVALID_STATE_ERR, NS_ERROR_DOM_NOT_ALLOWED_ERR,
NS_ERROR_DOM_OPERATION_ERR, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_NOT_AVAILABLE,
NS_ERROR_NOT_IMPLEMENTED, NS_ERROR_NULL_POINTER, NS_OK,
NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_NOT_AVAILABLE, NS_ERROR_NOT_IMPLEMENTED,
NS_ERROR_NULL_POINTER, NS_OK,
};
use nsstring::{nsACString, nsAString, nsCString, nsString};
use serde::Serialize;
@ -45,8 +44,7 @@ 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;
@ -79,14 +77,9 @@ enum BrowserPromptType<'a> {
SelectDevice,
UvBlocked,
PinRequired,
SelectedDevice {
auth_info: Option<AuthenticatorInfo>,
},
PinInvalid {
retries: Option<u8>,
},
PinIsTooLong,
PinIsTooShort,
RegisterDirect,
UvInvalid {
retries: Option<u8>,
@ -94,20 +87,6 @@ enum BrowserPromptType<'a> {
SelectSignResult {
entities: &'a [PublicKeyCredentialUserEntity],
},
ListenSuccess,
ListenError {
error: Box<BrowserPromptType<'a>>,
},
CredentialManagementUpdate {
result: CredentialManagementResult,
},
UnknownError,
}
#[derive(Debug)]
enum PromptTarget {
Browser,
AboutPage,
}
#[derive(Serialize)]
@ -119,29 +98,13 @@ 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,
@ -154,7 +117,18 @@ fn send_prompt(
})
)
.or(Err(NS_ERROR_FAILURE))?;
notify_observers(PromptTarget::Browser, json)
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())
}
fn cancel_prompts(tid: u64) -> Result<(), nsresult> {
@ -525,7 +499,6 @@ impl SignPromise {
#[derive(Clone)]
enum TransactionPromise {
Listen,
Register(RegisterPromise),
Sign(SignPromise),
}
@ -533,7 +506,6 @@ 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)),
}
@ -553,8 +525,6 @@ struct TransactionState {
promise: TransactionPromise,
pin_receiver: PinReceiver,
selection_receiver: SelectionReceiver,
interactive_receiver: InteractiveManagementReceiver,
puat_cache: Option<PinUvAuthResult>, // Cached credential to avoid repeated PIN-entries
}
// AuthrsService provides an nsIWebAuthnService built on top of authenticator-rs.
@ -568,29 +538,23 @@ pub struct AuthrsService {
impl AuthrsService {
xpcom_method!(pin_callback => PinCallback(aTransactionId: u64, aPin: *const nsACString));
fn pin_callback(&self, transaction_id: u64, pin: &nsACString) -> Result<(), nsresult> {
if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
let mut guard = self.transaction.lock().unwrap();
let Some(transaction) = guard.as_mut() else {
// No ongoing transaction
return Err(NS_ERROR_FAILURE);
};
let Some((tid, channel)) = transaction.pin_receiver.take() else {
// We weren't expecting a pin.
return Err(NS_ERROR_FAILURE);
};
if tid != transaction_id {
// The browser is confused about which transaction is active.
// This shouldn't happen
return Err(NS_ERROR_FAILURE);
}
channel
.send(Pin::new(&pin.to_string()))
.or(Err(NS_ERROR_FAILURE))
} else {
// Silently accept request, if all webauthn-options are disabled.
// Used for testing.
Ok(())
let mut guard = self.transaction.lock().unwrap();
let Some(transaction) = guard.as_mut() else {
// No ongoing transaction
return Err(NS_ERROR_FAILURE);
};
let Some((tid, channel)) = transaction.pin_receiver.take() else {
// We weren't expecting a pin.
return Err(NS_ERROR_FAILURE);
};
if tid != transaction_id {
// The browser is confused about which transaction is active.
// This shouldn't happen
return Err(NS_ERROR_FAILURE);
}
channel
.send(Pin::new(&pin.to_string()))
.or(Err(NS_ERROR_FAILURE))
}
xpcom_method!(selection_callback => SelectionCallback(aTransactionId: u64, aSelection: u64));
@ -753,10 +717,8 @@ 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,
puat_cache: None,
});
if none_attestation
@ -790,10 +752,6 @@ 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();
@ -845,7 +803,6 @@ impl AuthrsService {
let _ = cancel_prompts(tid);
}
let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
*guard = None;
}),
);
@ -978,7 +935,6 @@ impl AuthrsService {
let _ = cancel_prompts(tid);
}
let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
*guard = None;
}),
);
@ -1010,10 +966,8 @@ impl AuthrsService {
browsing_context_id,
pending_args: None,
promise: TransactionPromise::Sign(promise),
interactive_receiver: None,
pin_receiver: None,
selection_receiver: None,
puat_cache: None,
});
// As in `register`, we are intentionally avoiding `AuthenticatorService` here.
@ -1189,136 +1143,6 @@ 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,
puat_cache: None,
});
}
// We may get from status_updates info about certain errors (e.g. PinErrors)
// which we want to present to the user. We will ignore the following error
// which is caused by us "hanging up" on the StatusUpdate-channel and return
// the PinError instead, via `upcoming_error`.
let upcoming_error = Arc::new(Mutex::new(None));
let upcoming_error_c = upcoming_error.clone();
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();
match guard.as_mut() {
Some(state) => {
match state.promise {
TransactionPromise::Listen => (),
_ => return,
}
*guard = None;
}
// We have no transaction anymore, this means cancel() was called
None => (),
}
let msg = match result {
Ok(_) => BrowserPromptType::ListenSuccess,
Err(e) => {
// See if we have a cached error that should replace this error
let replacement = if let Ok(mut x) = upcoming_error_c.lock() {
x.take()
} else {
None
};
let replaced_err = replacement.unwrap_or(e);
let err = authrs_to_prompt(replaced_err);
BrowserPromptType::ListenError {
error: Box::new(err),
}
}
};
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, upcoming_error);
},
)
.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(())
}
xpcom_method!(run_command => RunCommand(c_cmd: *const nsACString));
pub fn run_command(&self, c_cmd: &nsACString) -> Result<(), nsresult> {
// Always test if it can be parsed from incoming JSON (even for tests)
let incoming: RequestWrapper =
serde_json::from_str(&c_cmd.to_utf8()).or(Err(NS_ERROR_DOM_OPERATION_ERR))?;
if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
let guard = self.transaction.lock().unwrap();
let puat = guard.as_ref().and_then(|g| g.puat_cache.clone());
let command = match incoming {
RequestWrapper::Quit => InteractiveRequest::Quit,
RequestWrapper::ChangePIN(a, b) => InteractiveRequest::ChangePIN(a, b),
RequestWrapper::SetPIN(a) => InteractiveRequest::SetPIN(a),
RequestWrapper::CredentialManagement(c) => {
InteractiveRequest::CredentialManagement(c, puat)
}
};
match &guard.as_ref().unwrap().interactive_receiver {
Some(channel) => channel.send(command).or(Err(NS_ERROR_FAILURE)),
// Either we weren't expecting a pin, or the controller is confused
// about which transaction is active. Neither is recoverable, so it's
// OK to drop the PinReceiver here.
_ => Err(NS_ERROR_FAILURE),
}
} else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
// We don't yet support softtoken
Ok(())
} else {
// Silently accept request, if all webauthn-options are disabled.
// Used for testing.
Ok(())
}
}
}
#[no_mangle]

View file

@ -90,8 +90,4 @@ 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();
void runCommand(in ACString aCommand);
};

View file

@ -1,79 +0,0 @@
/* 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;
}
#ctap-listen-result {
font-weight: 600;
font-size: 1.5em;
padding-inline-start: 15px;
&.success {
background-color: var(--green-50);
}
&.error {
background-color: var(--red-50);
}
}
.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;
}
.button-row {
display: inline-block;
margin-inline-end: 5px;
}

View file

@ -1,180 +0,0 @@
<!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 hidden id="pin-tab-button" class="category">
<span
class="tablinks"
data-l10n-id="about-webauthn-pin-section-title"
></span>
</div>
<div id="credentials-tab-button" class="category">
<span
class="tablinks"
data-l10n-id="about-webauthn-credential-management-section-title"
></span>
</div>
</div>
<div hidden id="main-content">
<div id="ctap-listen-div">
<label id="ctap-listen-result"></label>
</div>
<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 hidden class="tabcontent pin-section" id="set-change-pin-section">
<h2
class="categoryTitle"
data-l10n-id="about-webauthn-pin-section-title"
></h2>
<div id="new-pin-div">
<label
for="new-pin"
data-l10n-id="about-webauthn-new-pin-label"
></label>
<input type="password" id="new-pin" name="new-pin" required />
<label
for="new-pin-repeat"
data-l10n-id="about-webauthn-repeat-pin-label"
></label>
<input
type="password"
id="new-pin-repeat"
name="new-pin-repeat"
required
/>
</div>
<div id="current-pin-div">
<label
for="current-pin"
data-l10n-id="about-webauthn-current-pin-label"
></label>
<input type="password" id="current-pin" name="current-pin" required />
</div>
<button
disabled
id="set-pin-button"
data-l10n-id="about-webauthn-current-set-pin-button"
></button>
<button
disabled
id="change-pin-button"
data-l10n-id="about-webauthn-current-change-pin-button"
></button>
<label id="set-change-pin-result" class="ctap-result"></label>
</div>
<div
hidden
class="tabcontent credential-management-section"
id="credential-management-section"
>
<h2
class="categoryTitle"
data-l10n-id="about-webauthn-credential-management-section-title"
></h2>
<div
hidden
id="credential-list-subsection"
class="token-info-flex-child"
>
<h3
data-l10n-id="about-webauthn-credential-list-subsection-title"
></h3>
<div hidden id="credential-list-empty-label">
<label
hidden
data-l10n-id="about-webauthn-credential-list-empty"
></label>
</div>
<table id="credential-list"></table>
</div>
<button
class="credentials-button"
id="list-credentials-button"
data-l10n-id="about-webauthn-list-credentials-button"
></button>
</div>
<div
hidden
class="tabcontent pin-required-section"
id="pin-required-section"
>
<h2
class="categoryTitle"
data-l10n-id="about-webauthn-pin-required-section-title"
></h2>
<div id="pin-div">
<label
for="pin-required"
data-l10n-id="about-webauthn-pin-required-label"
></label>
<input
type="password"
id="pin-required"
name="pin-required"
required
autofocus
/>
</div>
<button
id="send-pin-button"
class="button-row"
data-l10n-id="about-webauthn-send-pin-button"
></button>
<button
id="cancel-send-pin-button"
class="button-row"
data-l10n-id="about-webauthn-cancel-button"
></button>
</div>
</div>
</body>
</html>

View file

@ -1,493 +0,0 @@
/* 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,
_current_tab: "",
_previous_tab: "",
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") {
fake_click_event_for_id("info-tab-button");
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 == "pin-required") {
open_pin_required_tab();
} else if (data.type == "pin-invalid") {
let retries = data.retries ? data.retries : 0;
show_results_banner(
"error",
"about-webauthn-results-pin-invalid-error",
JSON.stringify({ retriesLeft: retries })
);
open_pin_required_tab();
} else if (data.type == "credential-management-update") {
credential_management_in_progress(false);
if (data.result.CredentialList) {
show_results_banner("success", "about-webauthn-results-success");
this.show_credential_list(data.result.CredentialList.credential_list);
} else {
// DeleteSuccess or UpdateSuccess
show_results_banner("success", "about-webauthn-results-success");
list_credentials();
}
} else if (data.type == "listen-success") {
reset_page();
// Show results
show_results_banner("success", "about-webauthn-results-success");
this._reset_in_progress = "";
AboutWebauthnService.listen();
} else if (data.type == "listen-error") {
reset_page();
if (!data.error) {
show_results_banner("error", "about-webauthn-results-general-error");
} else if (data.error.type == "pin-auth-blocked") {
show_results_banner(
"error",
"about-webauthn-results-pin-auth-blocked-error"
);
} else if (data.error.type == "device-blocked") {
show_results_banner(
"error",
"about-webauthn-results-pin-blocked-error"
);
} else if (data.error.type == "pin-is-too-short") {
show_results_banner(
"error",
"about-webauthn-results-pin-too-short-error"
);
} else if (data.error.type == "pin-is-too-long") {
show_results_banner(
"error",
"about-webauthn-results-pin-too-long-error"
);
} else if (data.error.type == "pin-invalid") {
let retries = data.error.retries
? JSON.stringify({ retriesLeft: data.error.retries })
: null;
show_results_banner(
"error",
"about-webauthn-results-pin-invalid-error",
retries
);
} else if (data.error.type == "cancel") {
show_results_banner(
"error",
"about-webauthn-results-cancelled-by-user-error"
);
} else {
show_results_banner("error", "about-webauthn-results-general-error");
}
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"
);
// Check if token supports PINs
if (data.auth_info.options.clientPin != null) {
document.getElementById("pin-tab-button").style.display = "flex";
if (data.auth_info.options.clientPin === true) {
// It has a Pin set
document.getElementById("change-pin-button").style.display = "block";
document.getElementById("set-pin-button").style.display = "none";
document.getElementById("current-pin-div").hidden = false;
} else {
// It does not have a Pin set yet
document.getElementById("change-pin-button").style.display = "none";
document.getElementById("set-pin-button").style.display = "block";
document.getElementById("current-pin-div").hidden = true;
}
} else {
document.getElementById("pin-tab-button").style.display = "none";
}
if (
data.auth_info.options.credMgmt ||
data.auth_info.options.credentialMgmtPreview
) {
document.getElementById("credentials-tab-button").style.display =
"flex";
} else {
document.getElementById("credentials-tab-button").style.display =
"none";
}
} 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");
}
},
show_credential_list(credential_list) {
// We may have temporarily hidden the tab when asking the user for a PIN
// so we have to show it again.
fake_click_event_for_id("credentials-tab-button");
document.getElementById("credential-list-subsection").hidden = false;
let table = document.getElementById("credential-list");
var empty_table = document.createElement("table");
empty_table.id = "credential-list";
table.parentNode.replaceChild(empty_table, table);
if (!credential_list.length) {
document.getElementById("credential-list-empty-label").hidden = false;
return;
}
document.getElementById("credential-list-empty-label").hidden = true;
table = document.getElementById("credential-list");
credential_list.forEach(rp => {
// Add some text to the new cells:
let key_text = rp.rp.id;
rp.credentials.forEach(cred => {
let value_text = cred.user.name;
// Create an empty <tr> element and add it to the 1st position of the table:
var row = table.insertRow(0);
var key_node = document.createTextNode(key_text);
var value_node = document.createTextNode(value_text);
row.insertCell(0).appendChild(key_node);
row.insertCell(1).appendChild(value_node);
var delete_button = document.createElement("button");
delete_button.classList.add("credentials-button");
// TODO: Garbage-can symbol instead? See https://bugzilla.mozilla.org/show_bug.cgi?id=1859727
delete_button.setAttribute(
"data-l10n-id",
"about-webauthn-delete-button"
);
delete_button.addEventListener("click", function () {
credential_management_in_progress(true);
let cmd = {
CredentialManagement: { DeleteCredential: cred.credential_id },
};
AboutWebauthnService.runCommand(JSON.stringify(cmd));
});
row.insertCell(2).appendChild(delete_button);
});
});
},
};
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 show_results_banner(result, l10n, l10n_args) {
document.getElementById("ctap-listen-div").hidden = false;
let ctap_result = document.getElementById("ctap-listen-result");
ctap_result.setAttribute("data-l10n-id", l10n);
ctap_result.classList.add(result);
if (l10n_args) {
ctap_result.setAttribute("data-l10n-args", l10n_args);
}
}
function hide_results_banner() {
document
.getElementById("ctap-listen-result")
.classList.remove("success", "error");
document.getElementById("ctap-listen-div").hidden = true;
}
function credential_management_in_progress(in_progress) {
let buttons = Array.from(
document.getElementsByClassName("credentials-button")
);
buttons.forEach(button => {
button.disabled = in_progress;
});
}
function fake_click_event_for_id(id) {
// Not using document.getElementById(id).click();
// here, because we have to add additional data, so we don't
// hide the results-div here, if there is any. 'Normal' clicking
// by the user will hide it.
const evt = new CustomEvent("click", {
detail: { skip_results_clearing: true },
});
document.getElementById(id).dispatchEvent(evt);
}
function reset_page() {
// Hide all main sections
document.getElementById("main-content").hidden = true;
document.getElementById("categories").hidden = true;
// Clear results and input fields
hide_results_banner();
var divs = Array.from(document.getElementsByTagName("input"));
divs.forEach(div => {
div.value = "";
});
sidebar_set_disabled(false);
// ListCredentials
credential_management_in_progress(false);
document.getElementById("credential-list-subsection").hidden = true;
// Only display the "please connect a device" - text
set_info_text("about-webauthn-text-connect-device");
AboutWebauthnManagerJS._previous_tab = "";
AboutWebauthnManagerJS._current_tab = "";
}
function sidebar_set_disabled(disabled) {
var cats = Array.from(document.getElementsByClassName("category"));
cats.forEach(cat => {
if (disabled) {
cat.classList.add("disabled-category");
} else {
cat.classList.remove("disabled-category");
}
});
}
function check_pin_repeat_is_correct(button) {
let pin = document.getElementById("new-pin");
let pin_repeat = document.getElementById("new-pin-repeat");
let has_current_pin = !document.getElementById("current-pin-div").hidden;
let current_pin = document.getElementById("current-pin");
let can_enable_button =
pin.value != null && pin.value != "" && pin.value == pin_repeat.value;
if (has_current_pin && !current_pin.value) {
can_enable_button = false;
}
if (!can_enable_button) {
pin.classList.add("different");
pin_repeat.classList.add("different");
document.getElementById("set-pin-button").disabled = true;
document.getElementById("change-pin-button").disabled = true;
return false;
}
pin.classList.remove("different");
pin_repeat.classList.remove("different");
document.getElementById("set-pin-button").disabled = false;
document.getElementById("change-pin-button").disabled = false;
return true;
}
function send_pin() {
close_pin_required_tab();
let pin = document.getElementById("pin-required").value;
AboutWebauthnService.pinCallback(0, pin);
}
function set_pin() {
let pin = document.getElementById("new-pin").value;
let cmd = { SetPIN: pin };
AboutWebauthnService.runCommand(JSON.stringify(cmd));
}
function change_pin() {
let curr_pin = document.getElementById("current-pin").value;
let new_pin = document.getElementById("new-pin").value;
let cmd = { ChangePIN: [curr_pin, new_pin] };
AboutWebauthnService.runCommand(JSON.stringify(cmd));
}
function list_credentials() {
credential_management_in_progress(true);
let cmd = { CredentialManagement: "GetCredentials" };
AboutWebauthnService.runCommand(JSON.stringify(cmd));
}
function cancel_transaction() {
credential_management_in_progress(false);
AboutWebauthnService.cancel(0);
}
async function onLoad() {
AboutWebauthnManagerJS.init();
try {
AboutWebauthnService.listen();
} catch (ex) {
set_info_text("about-webauthn-text-not-available");
AboutWebauthnManagerJS.uninit();
return;
}
document.getElementById("set-pin-button").addEventListener("click", set_pin);
document
.getElementById("change-pin-button")
.addEventListener("click", change_pin);
document
.getElementById("list-credentials-button")
.addEventListener("click", list_credentials);
document
.getElementById("new-pin")
.addEventListener("input", check_pin_repeat_is_correct);
document
.getElementById("new-pin-repeat")
.addEventListener("input", check_pin_repeat_is_correct);
document
.getElementById("current-pin")
.addEventListener("input", check_pin_repeat_is_correct);
document
.getElementById("info-tab-button")
.addEventListener("click", open_info_tab);
document
.getElementById("pin-tab-button")
.addEventListener("click", open_pin_tab);
document
.getElementById("credentials-tab-button")
.addEventListener("click", open_credentials_tab);
document
.getElementById("send-pin-button")
.addEventListener("click", send_pin);
document
.getElementById("cancel-send-pin-button")
.addEventListener("click", cancel_transaction);
}
function open_tab(evt, tabName) {
var tabcontent, tablinks;
// Hide all others
tabcontent = Array.from(document.getElementsByClassName("tabcontent"));
tabcontent.forEach(tab => {
tab.style.display = "none";
});
// Display the one we selected
document.getElementById(tabName).style.display = "block";
// If this is a temporary overlay, like pin-required, we don't
// touch the sidebar and which button is selected.
if (!evt.detail.temporary_overlay) {
tablinks = Array.from(document.getElementsByClassName("category"));
tablinks.forEach(tablink => {
tablink.removeAttribute("selected");
tablink.disabled = false;
});
evt.currentTarget.setAttribute("selected", "true");
}
if (!evt.detail.skip_results_clearing) {
hide_results_banner();
}
sidebar_set_disabled(false);
AboutWebauthnManagerJS._previous_tab = AboutWebauthnManagerJS._current_tab;
AboutWebauthnManagerJS._current_tab = tabName;
}
function open_info_tab(evt) {
open_tab(evt, "token-info-section");
}
function open_pin_tab(evt) {
open_tab(evt, "set-change-pin-section");
}
function open_credentials_tab(evt) {
open_tab(evt, "credential-management-section");
}
function open_pin_required_tab() {
// Remove any old value we might have had
document.getElementById("pin-required").value = "";
const evt = new CustomEvent("click", {
detail: {
temporary_overlay: true,
skip_results_clearing: true, // We might be called multiple times, if PIN was invalid
},
});
open_tab(evt, "pin-required-section");
document.getElementById("pin-required").focus();
// This is a temporary overlay, so we don't want the
// user to click away from it, unless via the Cancel-button.
sidebar_set_disabled(true);
}
function close_pin_required_tab() {
const evt = new CustomEvent("click", {
detail: { temporary_overlay: true },
});
open_tab(evt, AboutWebauthnManagerJS._previous_tab);
sidebar_set_disabled(false);
}
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);
}

View file

@ -1,8 +0,0 @@
# 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)

View file

@ -1,13 +0,0 @@
# -*- 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"]

View file

@ -1,13 +0,0 @@
[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_credentials.js]
[browser_aboutwebauthn_info.js]
[browser_aboutwebauthn_no_token.js]
[browser_aboutwebauthn_pin.js]

View file

@ -1,346 +0,0 @@
/* 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_credential_list(credlist) {
let num_of_creds = 0;
credlist.forEach(domain => {
domain.credentials.forEach(c => {
num_of_creds += 1;
});
});
let msg = JSON.stringify({
type: "credential-management-update",
result: {
CredentialList: {
existing_resident_credentials_count: num_of_creds,
max_possible_remaining_resident_credentials_count: 20,
credential_list: credlist,
},
},
});
Services.obs.notifyObservers(null, "about-webauthn-prompt", msg);
}
function check_cred_buttons_disabled(disabled) {
let buttons = Array.from(doc.getElementsByClassName("credentials-button"));
buttons.forEach(button => {
is(button.disabled, disabled);
});
return buttons;
}
async function send_authinfo_and_open_cred_section(ops) {
reset_about_page(doc);
send_auth_info_and_check_categories(doc, ops);
let button = doc.getElementById("pin-tab-button");
is(button.style.display, "none", "pin-tab-button in the sidebar not hidden");
if (ops.credMgmt !== null || ops.credentialMgmtPreview !== null) {
let credentials_tab_button = doc.getElementById("credentials-tab-button");
// Check if credentials section is visible
isnot(
credentials_tab_button.style.display,
"none",
"credentials button in the sidebar not visible"
);
// Click the section and wait for it to open
let credentials_section = doc.getElementById(
"credential-management-section"
);
credentials_tab_button.click();
isnot(
credentials_section.style.display,
"none",
"credentials section not visible"
);
is(
credentials_tab_button.getAttribute("selected"),
"true",
"credentials section button not selected"
);
}
}
add_task(async function cred_mgmt_not_supported() {
// Not setting credMgmt at all should lead to not showing it in the sidebar
send_authinfo_and_open_cred_section({
credMgmt: null,
credentialMgmtPreview: null,
});
let credentials_tab_button = doc.getElementById("credentials-tab-button");
is(
credentials_tab_button.style.display,
"none",
"credentials button in the sidebar visible"
);
});
add_task(async function cred_mgmt_supported() {
// Setting credMgmt should show the button in the sidebar
// The function is checking this for us.
send_authinfo_and_open_cred_section({
credMgmt: true,
credentialMgmtPreview: null,
});
// Setting credMgmtPreview should show the button in the sidebar
send_authinfo_and_open_cred_section({
credMgmt: null,
credentialMgmtPreview: true,
});
// Setting both should also work
send_authinfo_and_open_cred_section({
credMgmt: true,
credentialMgmtPreview: true,
});
});
add_task(async function cred_mgmt_empty_list() {
send_authinfo_and_open_cred_section({ credMgmt: true });
let list_credentials_button = doc.getElementById("list-credentials-button");
isnot(
list_credentials_button.style.display,
"none",
"credentials button in the sidebar not visible"
);
// Credential list should initially not be visible
let credential_list = doc.getElementById("credential-list-subsection");
is(credential_list.hidden, true, "credentials list visible");
list_credentials_button.click();
is(
list_credentials_button.disabled,
true,
"credentials button not disabled while op in progress"
);
send_credential_list([]);
is(list_credentials_button.disabled, false, "credentials button disabled");
is(credential_list.hidden, false, "credentials list visible");
let credential_list_empty_label = doc.getElementById(
"credential-list-empty-label"
);
is(
credential_list_empty_label.hidden,
false,
"credentials list empty label not visible"
);
});
add_task(async function cred_mgmt_real_data() {
send_authinfo_and_open_cred_section({ credMgmt: true });
let list_credentials_button = doc.getElementById("list-credentials-button");
list_credentials_button.click();
send_credential_list(REAL_DATA);
is(list_credentials_button.disabled, false, "credentials button disabled");
let credential_list = doc.getElementById("credential-list");
is(
credential_list.rows.length,
4,
"credential list table doesn't contain 4 credentials"
);
is(credential_list.rows[0].cells[0].textContent, "webauthn.io");
is(credential_list.rows[0].cells[1].textContent, "fasdfasd");
is(credential_list.rows[1].cells[0].textContent, "webauthn.io");
is(credential_list.rows[1].cells[1].textContent, "twetwetw");
is(credential_list.rows[2].cells[0].textContent, "webauthn.io");
is(credential_list.rows[2].cells[1].textContent, "hhhhhg");
is(credential_list.rows[3].cells[0].textContent, "example.com");
is(credential_list.rows[3].cells[1].textContent, "A. Nother");
let buttons = check_cred_buttons_disabled(false);
// 4 for each cred + 1 for the list button
is(buttons.length, 5);
list_credentials_button.click();
buttons = check_cred_buttons_disabled(true);
send_credential_list(REAL_DATA);
buttons[2].click();
check_cred_buttons_disabled(true);
});
const REAL_DATA = [
{
rp: { id: "example.com" },
rp_id_hash: [
163, 121, 166, 246, 238, 175, 185, 165, 94, 55, 140, 17, 128, 52, 226,
117, 30, 104, 47, 171, 159, 45, 48, 171, 19, 210, 18, 85, 134, 206, 25,
71,
],
credentials: [
{
user: {
id: [65, 46, 32, 78, 111, 116, 104, 101, 114],
name: "A. Nother",
},
credential_id: {
id: [
195, 239, 221, 151, 76, 77, 255, 242, 217, 50, 87, 144, 238, 79,
199, 120, 234, 148, 142, 69, 163, 133, 189, 254, 74, 138, 119, 140,
197, 171, 36, 215, 191, 176, 36, 111, 113, 158, 204, 147, 101, 200,
20, 239, 191, 174, 51, 15,
],
type: "public-key",
},
public_key: {
1: 2,
3: -7,
"-1": 1,
"-2": [
195, 239, 221, 151, 76, 77, 255, 242, 217, 50, 87, 144, 238, 235,
230, 51, 155, 142, 121, 60, 136, 63, 80, 184, 41, 238, 217, 61, 1,
206, 253, 141,
],
"-3": [
15, 81, 111, 204, 199, 48, 18, 121, 134, 243, 26, 49, 6, 244, 25,
156, 188, 71, 245, 122, 93, 47, 218, 235, 25, 222, 191, 116, 20, 14,
195, 114,
],
},
cred_protect: 1,
large_blob_key: [
223, 32, 77, 171, 223, 133, 38, 175, 229, 40, 85, 216, 203, 79, 194,
223, 32, 191, 119, 241, 115, 6, 101, 180, 92, 194, 208, 193, 181, 163,
164, 64,
],
},
],
},
{
rp: { id: "webauthn.io" },
rp_id_hash: [
116, 166, 234, 146, 19, 201, 156, 47, 116, 178, 36, 146, 179, 32, 207, 64,
38, 42, 148, 193, 169, 80, 160, 57, 127, 41, 37, 11, 96, 132, 30, 240,
],
credentials: [
{
user: { id: [97, 71, 104, 111, 97, 71, 104, 110], name: "hhhhhg" },
credential_id: {
id: [
64, 132, 129, 5, 185, 62, 86, 253, 199, 113, 219, 14, 207, 113, 145,
78, 177, 198, 130, 217, 122, 105, 225, 111, 32, 227, 237, 209, 6,
220, 202, 234, 144, 227, 246, 42, 73, 68, 37, 142, 95, 139, 224, 36,
156, 168, 118, 181,
],
type: "public-key",
},
public_key: {
1: 2,
3: -7,
"-1": 1,
"-2": [
64, 132, 129, 5, 185, 62, 86, 253, 199, 113, 219, 14, 207, 22, 10,
241, 230, 152, 5, 204, 35, 94, 22, 191, 213, 2, 247, 220, 227, 62,
76, 182,
],
"-3": [
13, 30, 30, 149, 170, 118, 78, 115, 101, 218, 245, 52, 154, 242, 67,
146, 17, 184, 112, 225, 51, 47, 242, 157, 195, 80, 76, 101, 147,
161, 3, 185,
],
},
cred_protect: 1,
large_blob_key: [
10, 67, 27, 233, 8, 115, 69, 191, 105, 213, 77, 123, 210, 118, 193,
234, 3, 12, 234, 228, 215, 106, 24, 228, 102, 247, 255, 156, 99, 196,
215, 230,
],
},
{
user: {
id: [100, 72, 100, 108, 100, 72, 100, 108, 100, 72, 99],
name: "twetwetw",
},
credential_id: {
id: [
29, 8, 25, 57, 66, 234, 22, 27, 227, 141, 77, 93, 233, 234, 251, 61,
100, 199, 176, 97, 112, 48, 172, 118, 145, 0, 156, 76, 156, 215, 18,
25, 118, 32, 241, 127, 13, 177, 249, 101, 26, 209, 142, 116, 74, 95,
117, 29,
],
type: "public-key",
},
public_key: {
1: 2,
3: -7,
"-1": 1,
"-2": [
29, 8, 25, 57, 66, 234, 22, 27, 227, 141, 77, 93, 233, 154, 113,
177, 251, 161, 54, 76, 150, 15, 6, 143, 117, 214, 232, 215, 118, 41,
116, 19,
],
"-3": [
201, 6, 43, 178, 3, 249, 175, 123, 149, 81, 127, 20, 116, 152, 238,
84, 52, 113, 3, 165, 176, 105, 200, 137, 209, 0, 141, 50, 42, 192,
174, 26,
],
},
cred_protect: 1,
large_blob_key: [
125, 175, 155, 1, 14, 247, 182, 241, 234, 66, 115, 236, 200, 223, 176,
88, 88, 88, 202, 173, 147, 217, 9, 193, 114, 7, 29, 169, 224, 179,
187, 188,
],
},
{
user: {
id: [90, 109, 70, 122, 90, 71, 90, 104, 99, 50, 81],
name: "fasdfasd",
},
credential_id: {
id: [
58, 174, 92, 116, 17, 108, 28, 203, 233, 192, 182, 60, 80, 236, 133,
196, 98, 32, 103, 53, 107, 48, 46, 236, 228, 166, 21, 33, 228, 75,
85, 191, 71, 18, 214, 177, 56, 254, 89, 28, 187, 220, 241, 7, 21,
11, 45, 151,
],
type: "public-key",
},
public_key: {
1: 2,
3: -7,
"-1": 1,
"-2": [
58, 174, 92, 116, 17, 108, 28, 203, 233, 192, 182, 60, 80, 111, 150,
192, 102, 255, 211, 156, 5, 186, 29, 105, 154, 79, 14, 2, 106, 159,
57, 156,
],
"-3": [
182, 164, 251, 221, 237, 36, 239, 109, 146, 184, 146, 29, 143, 16,
35, 188, 84, 148, 247, 83, 181, 40, 88, 111, 245, 13, 254, 206, 242,
164, 234, 159,
],
},
cred_protect: 1,
large_blob_key: [
113, 154, 217, 69, 45, 108, 115, 20, 104, 43, 214, 120, 253, 93, 223,
204, 125, 234, 220, 148, 98, 118, 98, 157, 175, 41, 154, 97, 87, 233,
208, 171,
],
},
],
},
];

View file

@ -1,218 +0,0 @@
/* 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,
};

View file

@ -1,49 +0,0 @@
/* 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);
});

View file

@ -1,205 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const ABOUT_URL = "about:webauthn";
var doc, tab;
async function send_authinfo_and_open_pin_section(ops) {
reset_about_page(doc);
send_auth_info_and_check_categories(doc, ops);
if (ops.clientPin !== null) {
let pin_tab_button = doc.getElementById("pin-tab-button");
// Check if PIN section is visible
isnot(
pin_tab_button.style.display,
"none",
"PIN button in the sidebar not visible"
);
// Click the section and wait for it to open
let pin_section = doc.getElementById("set-change-pin-section");
pin_tab_button.click();
isnot(pin_section.style.display, "none", "PIN section not visible");
is(
pin_tab_button.getAttribute("selected"),
"true",
"PIN section button not selected"
);
}
}
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 pin_not_supported() {
// Not setting clientPIN at all should lead to not showing it in the sidebar
send_authinfo_and_open_pin_section({ uv: true, clientPin: null });
// Check if PIN section is invisible
let pin_tab_button = doc.getElementById("pin-tab-button");
is(pin_tab_button.style.display, "none", "PIN button in the sidebar visible");
});
add_task(async function pin_already_set() {
send_authinfo_and_open_pin_section({ clientPin: true });
let set_pin_button = doc.getElementById("set-pin-button");
is(set_pin_button.style.display, "none", "Set PIN button visible");
let change_pin_button = doc.getElementById("change-pin-button");
isnot(
change_pin_button.style.display,
"none",
"Change PIN button not visible"
);
let current_pin_div = doc.getElementById("current-pin-div");
is(current_pin_div.hidden, false, "Current PIN field not visible");
// Test that the button is only active if both inputs are set the same
let new_pin = doc.getElementById("new-pin");
let new_pin_repeat = doc.getElementById("new-pin-repeat");
let current_pin = doc.getElementById("current-pin");
set_text(new_pin, "abcdefg");
is(change_pin_button.disabled, true, "Change PIN button not disabled");
set_text(new_pin_repeat, "abcde");
is(change_pin_button.disabled, true, "Change PIN button not disabled");
set_text(new_pin_repeat, "abcdefg");
is(change_pin_button.disabled, true, "Change PIN button not disabled");
set_text(current_pin, "1234567");
is(change_pin_button.disabled, false, "Change PIN button disabled");
});
add_task(async function pin_not_yet_set() {
send_authinfo_and_open_pin_section({ clientPin: false });
let set_pin_button = doc.getElementById("set-pin-button");
isnot(set_pin_button.style.display, "none", "Set PIN button not visible");
let change_pin_button = doc.getElementById("change-pin-button");
is(change_pin_button.style.display, "none", "Change PIN button visible");
let current_pin_div = doc.getElementById("current-pin-div");
is(current_pin_div.hidden, true, "Current PIN field visible");
// Test that the button is only active if both inputs are set the same
let new_pin = doc.getElementById("new-pin");
let new_pin_repeat = doc.getElementById("new-pin-repeat");
set_text(new_pin, "abcdefg");
is(set_pin_button.disabled, true, "Set PIN button not disabled");
set_text(new_pin_repeat, "abcde");
is(set_pin_button.disabled, true, "Set PIN button not disabled");
set_text(new_pin_repeat, "abcdefg");
is(set_pin_button.disabled, false, "Set PIN button disabled");
});
add_task(async function pin_switch_back_and_forth() {
// This will click the PIN section button
send_authinfo_and_open_pin_section({ clientPin: false });
let pin_tab_button = doc.getElementById("pin-tab-button");
let pin_section = doc.getElementById("set-change-pin-section");
let info_tab_button = doc.getElementById("info-tab-button");
let info_section = doc.getElementById("token-info-section");
// Now click the "info"-button and verify the correct buttons are highlighted
info_tab_button.click();
// await info_promise;
isnot(info_section.style.display, "none", "info section not visible");
is(
info_tab_button.getAttribute("selected"),
"true",
"Info tab button not selected"
);
isnot(
pin_tab_button.getAttribute("selected"),
"true",
"PIN tab button selected"
);
// Click back to the PIN section
pin_tab_button.click();
isnot(pin_section.style.display, "none", "PIN section not visible");
is(
pin_tab_button.getAttribute("selected"),
"true",
"PIN tab button not selected"
);
isnot(
info_tab_button.getAttribute("selected"),
"true",
"Info button selected"
);
});
add_task(async function invalid_pin() {
send_authinfo_and_open_pin_section({ clientPin: true });
let pin_tab_button = doc.getElementById("pin-tab-button");
// Click the section and wait for it to open
pin_tab_button.click();
is(
pin_tab_button.getAttribute("selected"),
"true",
"PIN section button not selected"
);
let change_pin_button = doc.getElementById("change-pin-button");
// Test that the button is only active if both inputs are set the same
let new_pin = doc.getElementById("new-pin");
let new_pin_repeat = doc.getElementById("new-pin-repeat");
let current_pin = doc.getElementById("current-pin");
// Needed to activate change_pin_button
set_text(new_pin, "abcdefg");
set_text(new_pin_repeat, "abcdefg");
set_text(current_pin, "1234567");
// This should silently error out since we have no authenticator
change_pin_button.click();
// Now we fake a response from the authenticator, saying the PIN was invalid
let pin_required = doc.getElementById("pin-required-section");
let msg = JSON.stringify({ type: "pin-required" });
Services.obs.notifyObservers(null, "about-webauthn-prompt", msg);
isnot(pin_required.style.display, "none", "PIN required dialog not visible");
let info_tab_button = doc.getElementById("info-tab-button");
is(
info_tab_button.classList.contains("disabled-category"),
true,
"Sidebar not disabled"
);
let pin_field = doc.getElementById("pin-required");
let send_pin_button = doc.getElementById("send-pin-button");
set_text(pin_field, "654321");
send_pin_button.click();
is(
pin_required.style.display,
"none",
"PIN required dialog did not disappear"
);
let pin_section = doc.getElementById("set-change-pin-section");
isnot(pin_section.style.display, "none", "PIN section did not reappear");
});

View file

@ -1,43 +0,0 @@
function set_text(field, text) {
field.value = text;
field.dispatchEvent(new Event("input"));
}
async function reset_about_page(doc) {
let info_text = doc.getElementById("info-text-div");
let msg = JSON.stringify({ type: "listen-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;
}
async function send_auth_info_and_check_categories(doc, ops) {
let info_text = doc.getElementById("info-text-div");
let msg = JSON.stringify({
type: "selected-device",
auth_info: { options: ops },
});
let promise = BrowserTestUtils.waitForMutationCondition(
info_text,
{ attributes: true, attributeFilter: ["hidden"] },
() => info_text.hidden
);
Services.obs.notifyObservers(null, "about-webauthn-prompt", msg);
await promise;
let categories = doc.getElementById("categories");
is(categories.hidden, false, "categories sidebar not visible");
// Info should be shown always, so we use it as a canary
let info_tab_button = doc.getElementById("info-tab-button");
isnot(
info_tab_button.style.display,
"none",
"Info button in the sidebar not visible"
);
}

View file

@ -124,9 +124,6 @@ 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"]

View file

@ -1,127 +0,0 @@
# 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
about-webauthn-pin-section-title = PIN Management
about-webauthn-credential-management-section-title = Manage credentials
about-webauthn-pin-required-section-title = PIN required
## 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.
## Results label
about-webauthn-results-success = Success!
about-webauthn-results-general-error = Error!
# Variables:
# $retriesLeft (Number): number of tries left
about-webauthn-results-pin-invalid-error =
{ $retriesLeft ->
[0] Error: Incorrect PIN. Try again.
[one] Error: Incorrect PIN. Try again. You have one attempt left.
*[other] Error: Incorrect PIN. Try again. You have { $retriesLeft } attempts left.
}
about-webauthn-results-pin-blocked-error = Error: There are no attempts left and your device has been locked, because the wrong PIN was provided too many times. The device needs a reset.
about-webauthn-results-pin-too-short-error = Error: The given PIN is too short.
about-webauthn-results-pin-too-long-error = Error: The given PIN is too long.
about-webauthn-results-pin-auth-blocked-error = Error: There were too many failed attempts in a row and PIN authentication has been temporarily blocked. Your device needs a power cycle (unplug and re-insert).
about-webauthn-results-cancelled-by-user-error = Error: Operation has been canceled by the user.
## Labels
about-webauthn-new-pin-label = New PIN:
about-webauthn-repeat-pin-label = Repeat new PIN:
about-webauthn-current-pin-label = Current PIN:
about-webauthn-pin-required-label = Please enter your PIN:
about-webauthn-credential-list-subsection-title = Credentials:
about-webauthn-credential-list-empty = No credentials found on device.
## Buttons
about-webauthn-current-set-pin-button = Set PIN
about-webauthn-current-change-pin-button = Change PIN
# List is a verb, as in "Show list of credentials"
about-webauthn-list-credentials-button = List credentials
about-webauthn-cancel-button = Cancel
about-webauthn-send-pin-button = OK
about-webauthn-delete-button = Delete
## 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