forked from mirrors/gecko-dev
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:
parent
e95a094d18
commit
f078d4fd81
21 changed files with 34 additions and 2181 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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]
|
||||
|
|
@ -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,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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");
|
||||
});
|
||||
|
|
@ -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"
|
||||
);
|
||||
}
|
||||
|
|
@ -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"]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in a new issue