зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1854618 - Implement about:webauthn page to display authenticator info (r=jschanck,desktop-theme-reviewers,bolsson)
Differential Revision: https://phabricator.services.mozilla.com/D188973
This commit is contained in:
Родитель
b2c34cc36b
Коммит
f4b1edc284
|
@ -263,6 +263,11 @@ var allowlist = [
|
||||||
file: "resource://gre/localization/en-US/toolkit/about/aboutThirdParty.ftl",
|
file: "resource://gre/localization/en-US/toolkit/about/aboutThirdParty.ftl",
|
||||||
platforms: ["linux", "macosx"],
|
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
|
// Bug 1973834 - referenced by aboutWindowsMessages.html which is only for Windows
|
||||||
{
|
{
|
||||||
file: "resource://gre/localization/en-US/toolkit/about/aboutWindowsMessages.ftl",
|
file: "resource://gre/localization/en-US/toolkit/about/aboutWindowsMessages.ftl",
|
||||||
|
|
|
@ -131,6 +131,10 @@ static const RedirEntry kRedirMap[] = {
|
||||||
nsIAboutModule::IS_SECURE_CHROME_UI},
|
nsIAboutModule::IS_SECURE_CHROME_UI},
|
||||||
{"mozilla", "chrome://global/content/mozilla.html",
|
{"mozilla", "chrome://global/content/mozilla.html",
|
||||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT},
|
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",
|
{"neterror", "chrome://global/content/aboutNetError.xhtml",
|
||||||
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
|
||||||
nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
|
nsIAboutModule::URI_CAN_LOAD_IN_CHILD | nsIAboutModule::ALLOW_SCRIPT |
|
||||||
|
|
|
@ -44,6 +44,8 @@ if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] == 'windows':
|
||||||
about_pages.append('windows-messages')
|
about_pages.append('windows-messages')
|
||||||
if not defined('MOZ_GLEAN_ANDROID'):
|
if not defined('MOZ_GLEAN_ANDROID'):
|
||||||
about_pages.append('glean')
|
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']
|
Headers = ['/docshell/build/nsDocShellModule.h']
|
||||||
|
|
||||||
|
|
|
@ -369,5 +369,8 @@ AndroidWebAuthnService::SetUserVerified(uint64_t authenticatorId,
|
||||||
return NS_ERROR_NOT_IMPLEMENTED;
|
return NS_ERROR_NOT_IMPLEMENTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
AndroidWebAuthnService::Listen() { return NS_ERROR_NOT_IMPLEMENTED; }
|
||||||
|
|
||||||
} // namespace dom
|
} // namespace dom
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
|
@ -160,4 +160,12 @@ WebAuthnService::SetUserVerified(uint64_t authenticatorId,
|
||||||
return mPlatformService->SetUserVerified(authenticatorId, isUserVerified);
|
return mPlatformService->SetUserVerified(authenticatorId, isUserVerified);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NS_IMETHODIMP
|
||||||
|
WebAuthnService::Listen() {
|
||||||
|
if (StaticPrefs::security_webauth_webauthn_enable_softtoken()) {
|
||||||
|
return mTestService->Listen();
|
||||||
|
}
|
||||||
|
return mPlatformService->Listen();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace mozilla::dom
|
} // namespace mozilla::dom
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use authenticator::{InteractiveRequest, InteractiveUpdate};
|
||||||
|
|
||||||
|
pub(crate) type InteractiveManagementReceiver = Option<Sender<InteractiveRequest>>;
|
||||||
|
pub(crate) fn send_about_prompt(prompt: &BrowserPromptType) -> Result<(), nsresult> {
|
||||||
|
let json = nsString::from(&serde_json::to_string(&prompt).unwrap_or_default());
|
||||||
|
notify_observers(PromptTarget::AboutPage, json)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn interactive_status_callback(
|
||||||
|
status_rx: Receiver<StatusUpdate>,
|
||||||
|
transaction: Arc<Mutex<Option<TransactionState>>>, /* Shared with an AuthrsTransport */
|
||||||
|
) -> Result<(), nsresult> {
|
||||||
|
loop {
|
||||||
|
match status_rx.recv() {
|
||||||
|
Ok(StatusUpdate::InteractiveManagement(InteractiveUpdate::StartManagement((
|
||||||
|
tx,
|
||||||
|
auth_info,
|
||||||
|
)))) => {
|
||||||
|
let mut guard = transaction.lock().unwrap();
|
||||||
|
let Some(transaction) = guard.as_mut() else {
|
||||||
|
warn!("STATUS: received status update after end of transaction.");
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
transaction.interactive_receiver.replace(tx);
|
||||||
|
let prompt = BrowserPromptType::SelectedDevice { auth_info };
|
||||||
|
send_about_prompt(&prompt)?;
|
||||||
|
}
|
||||||
|
Ok(StatusUpdate::SelectDeviceNotice) => {
|
||||||
|
info!("STATUS: Please select a device by touching one of them.");
|
||||||
|
let prompt = BrowserPromptType::SelectDevice;
|
||||||
|
send_about_prompt(&prompt)?;
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
// currently not handled
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(RecvError) => {
|
||||||
|
info!("STATUS: end");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -20,7 +20,8 @@ use authenticator::{
|
||||||
},
|
},
|
||||||
errors::AuthenticatorError,
|
errors::AuthenticatorError,
|
||||||
statecallback::StateCallback,
|
statecallback::StateCallback,
|
||||||
Pin, RegisterResult, SignResult, StateMachine, StatusPinUv, StatusUpdate,
|
AuthenticatorInfo, ManageResult, Pin, RegisterResult, SignResult, StateMachine, StatusPinUv,
|
||||||
|
StatusUpdate,
|
||||||
};
|
};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use cstr::cstr;
|
use cstr::cstr;
|
||||||
|
@ -44,7 +45,8 @@ use xpcom::interfaces::{
|
||||||
nsIWebAuthnSignPromise, nsIWebAuthnSignResult,
|
nsIWebAuthnSignPromise, nsIWebAuthnSignResult,
|
||||||
};
|
};
|
||||||
use xpcom::{xpcom_method, RefPtr};
|
use xpcom::{xpcom_method, RefPtr};
|
||||||
|
mod about_webauthn_controller;
|
||||||
|
use about_webauthn_controller::*;
|
||||||
mod test_token;
|
mod test_token;
|
||||||
use test_token::TestTokenManager;
|
use test_token::TestTokenManager;
|
||||||
|
|
||||||
|
@ -77,6 +79,9 @@ enum BrowserPromptType<'a> {
|
||||||
SelectDevice,
|
SelectDevice,
|
||||||
UvBlocked,
|
UvBlocked,
|
||||||
PinRequired,
|
PinRequired,
|
||||||
|
SelectedDevice {
|
||||||
|
auth_info: Option<AuthenticatorInfo>,
|
||||||
|
},
|
||||||
PinInvalid {
|
PinInvalid {
|
||||||
retries: Option<u8>,
|
retries: Option<u8>,
|
||||||
},
|
},
|
||||||
|
@ -87,6 +92,14 @@ enum BrowserPromptType<'a> {
|
||||||
SelectSignResult {
|
SelectSignResult {
|
||||||
entities: &'a [PublicKeyCredentialUserEntity],
|
entities: &'a [PublicKeyCredentialUserEntity],
|
||||||
},
|
},
|
||||||
|
ListenSuccess,
|
||||||
|
ListenError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum PromptTarget {
|
||||||
|
Browser,
|
||||||
|
AboutPage,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
|
@ -98,13 +111,29 @@ struct BrowserPromptMessage<'a> {
|
||||||
browsing_context_id: Option<u64>,
|
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(
|
fn send_prompt(
|
||||||
prompt: BrowserPromptType,
|
prompt: BrowserPromptType,
|
||||||
tid: u64,
|
tid: u64,
|
||||||
origin: Option<&str>,
|
origin: Option<&str>,
|
||||||
browsing_context_id: Option<u64>,
|
browsing_context_id: Option<u64>,
|
||||||
) -> Result<(), nsresult> {
|
) -> Result<(), nsresult> {
|
||||||
let main_thread = get_main_thread()?;
|
|
||||||
let mut json = nsString::new();
|
let mut json = nsString::new();
|
||||||
write!(
|
write!(
|
||||||
json,
|
json,
|
||||||
|
@ -117,18 +146,7 @@ fn send_prompt(
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.or(Err(NS_ERROR_FAILURE))?;
|
.or(Err(NS_ERROR_FAILURE))?;
|
||||||
RunnableBuilder::new("AuthrsService::send_prompt", move || {
|
notify_observers(PromptTarget::Browser, json)
|
||||||
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> {
|
fn cancel_prompts(tid: u64) -> Result<(), nsresult> {
|
||||||
|
@ -499,6 +517,7 @@ impl SignPromise {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum TransactionPromise {
|
enum TransactionPromise {
|
||||||
|
Listen,
|
||||||
Register(RegisterPromise),
|
Register(RegisterPromise),
|
||||||
Sign(SignPromise),
|
Sign(SignPromise),
|
||||||
}
|
}
|
||||||
|
@ -506,6 +525,7 @@ enum TransactionPromise {
|
||||||
impl TransactionPromise {
|
impl TransactionPromise {
|
||||||
fn reject(&self, err: nsresult) -> Result<(), nsresult> {
|
fn reject(&self, err: nsresult) -> Result<(), nsresult> {
|
||||||
match self {
|
match self {
|
||||||
|
TransactionPromise::Listen => Ok(()),
|
||||||
TransactionPromise::Register(promise) => promise.resolve_or_reject(Err(err)),
|
TransactionPromise::Register(promise) => promise.resolve_or_reject(Err(err)),
|
||||||
TransactionPromise::Sign(promise) => promise.resolve_or_reject(Err(err)),
|
TransactionPromise::Sign(promise) => promise.resolve_or_reject(Err(err)),
|
||||||
}
|
}
|
||||||
|
@ -525,6 +545,7 @@ struct TransactionState {
|
||||||
promise: TransactionPromise,
|
promise: TransactionPromise,
|
||||||
pin_receiver: PinReceiver,
|
pin_receiver: PinReceiver,
|
||||||
selection_receiver: SelectionReceiver,
|
selection_receiver: SelectionReceiver,
|
||||||
|
interactive_receiver: InteractiveManagementReceiver,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthrsService provides an nsIWebAuthnService built on top of authenticator-rs.
|
// AuthrsService provides an nsIWebAuthnService built on top of authenticator-rs.
|
||||||
|
@ -717,6 +738,7 @@ impl AuthrsService {
|
||||||
browsing_context_id,
|
browsing_context_id,
|
||||||
pending_args: Some(TransactionArgs::Register(timeout_ms as u64, info)),
|
pending_args: Some(TransactionArgs::Register(timeout_ms as u64, info)),
|
||||||
promise: TransactionPromise::Register(promise),
|
promise: TransactionPromise::Register(promise),
|
||||||
|
interactive_receiver: None,
|
||||||
pin_receiver: None,
|
pin_receiver: None,
|
||||||
selection_receiver: None,
|
selection_receiver: None,
|
||||||
});
|
});
|
||||||
|
@ -752,6 +774,10 @@ impl AuthrsService {
|
||||||
let Some(TransactionArgs::Register(timeout_ms, info)) = state.pending_args.take() else {
|
let Some(TransactionArgs::Register(timeout_ms, info)) = state.pending_args.take() else {
|
||||||
return Err(NS_ERROR_FAILURE);
|
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_tx, status_rx) = channel::<StatusUpdate>();
|
||||||
let status_transaction = self.transaction.clone();
|
let status_transaction = self.transaction.clone();
|
||||||
|
@ -803,6 +829,7 @@ impl AuthrsService {
|
||||||
let _ = cancel_prompts(tid);
|
let _ = cancel_prompts(tid);
|
||||||
}
|
}
|
||||||
let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
|
let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
|
||||||
|
*guard = None;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -935,6 +962,7 @@ impl AuthrsService {
|
||||||
let _ = cancel_prompts(tid);
|
let _ = cancel_prompts(tid);
|
||||||
}
|
}
|
||||||
let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
|
let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
|
||||||
|
*guard = None;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -966,6 +994,7 @@ impl AuthrsService {
|
||||||
browsing_context_id,
|
browsing_context_id,
|
||||||
pending_args: None,
|
pending_args: None,
|
||||||
promise: TransactionPromise::Sign(promise),
|
promise: TransactionPromise::Sign(promise),
|
||||||
|
interactive_receiver: None,
|
||||||
pin_receiver: None,
|
pin_receiver: None,
|
||||||
selection_receiver: None,
|
selection_receiver: None,
|
||||||
});
|
});
|
||||||
|
@ -1143,6 +1172,81 @@ impl AuthrsService {
|
||||||
self.test_token_manager
|
self.test_token_manager
|
||||||
.set_user_verified(authenticator_id, is_user_verified)
|
.set_user_verified(authenticator_id, is_user_verified)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xpcom_method!(listen => Listen());
|
||||||
|
pub(crate) fn listen(&self) -> Result<(), nsresult> {
|
||||||
|
// For now, we don't support softtokens
|
||||||
|
if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut guard = self.transaction.lock().unwrap();
|
||||||
|
if guard.as_ref().is_some() {
|
||||||
|
// ignore listen() and continue with ongoing transaction
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
*guard = Some(TransactionState {
|
||||||
|
tid: 0,
|
||||||
|
browsing_context_id: 0,
|
||||||
|
pending_args: None,
|
||||||
|
promise: TransactionPromise::Listen,
|
||||||
|
interactive_receiver: None,
|
||||||
|
pin_receiver: None,
|
||||||
|
selection_receiver: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let callback_transaction = self.transaction.clone();
|
||||||
|
let state_callback = StateCallback::<Result<ManageResult, AuthenticatorError>>::new(
|
||||||
|
Box::new(move |result| {
|
||||||
|
let mut guard = callback_transaction.lock().unwrap();
|
||||||
|
let Some(state) = guard.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match state.promise {
|
||||||
|
TransactionPromise::Listen => (),
|
||||||
|
_ => return,
|
||||||
|
}
|
||||||
|
*guard = None;
|
||||||
|
let msg = match result {
|
||||||
|
Ok(_) => BrowserPromptType::ListenSuccess,
|
||||||
|
Err(_) => BrowserPromptType::ListenError,
|
||||||
|
};
|
||||||
|
let _ = send_about_prompt(&msg);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calling `manage()` within the lock, to avoid race conditions
|
||||||
|
// where we might check listen_blocked, see that it's false,
|
||||||
|
// continue along, but in parallel `make_credential()` aborts the
|
||||||
|
// interactive process shortly after, setting listen_blocked to true,
|
||||||
|
// then accessing usb_token_manager afterwards and at the same time
|
||||||
|
// we do it here, causing a runtime crash for trying to mut-borrow it twice.
|
||||||
|
let (status_tx, status_rx) = channel::<StatusUpdate>();
|
||||||
|
let status_transaction = self.transaction.clone();
|
||||||
|
RunnableBuilder::new(
|
||||||
|
"AuthrsTransport::AboutWebauthn::StatusReceiver",
|
||||||
|
move || {
|
||||||
|
let _ = interactive_status_callback(status_rx, status_transaction);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.may_block(true)
|
||||||
|
.dispatch_background_task()?;
|
||||||
|
if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
|
||||||
|
self.usb_token_manager.lock().unwrap().manage(
|
||||||
|
60 * 1000 * 1000,
|
||||||
|
status_tx,
|
||||||
|
state_callback,
|
||||||
|
);
|
||||||
|
} else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
|
||||||
|
// We don't yet support softtoken
|
||||||
|
} else {
|
||||||
|
// Silently accept request, if all webauthn-options are disabled.
|
||||||
|
// Used for testing.
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|
|
@ -90,4 +90,7 @@ interface nsIWebAuthnService : nsISupports
|
||||||
// Sets the "isUserVerified" bit on a virtual authenticator. See
|
// Sets the "isUserVerified" bit on a virtual authenticator. See
|
||||||
// https://w3c.github.io/webauthn/#sctn-automation-set-user-verified
|
// https://w3c.github.io/webauthn/#sctn-automation-set-user-verified
|
||||||
void setUserVerified(in uint64_t authenticatorId, in bool isUserVerified);
|
void setUserVerified(in uint64_t authenticatorId, in bool isUserVerified);
|
||||||
|
|
||||||
|
// about:webauthn-specific functions
|
||||||
|
void listen();
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
@import url("chrome://global/skin/in-content/common.css");
|
||||||
|
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info-text-div {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ctap-listen-div {
|
||||||
|
padding-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
cursor: pointer;
|
||||||
|
/* Center category names */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-category {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 830px){
|
||||||
|
#categories > .category {
|
||||||
|
padding-inline-start: 5px;
|
||||||
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#main-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-info-flex-box {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-info-flex-child {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token-info-flex-child#authenticator-options {
|
||||||
|
margin-inline-end: 2px;
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta
|
||||||
|
http-equiv="Content-Security-Policy"
|
||||||
|
content="default-src chrome:; style-src chrome:; object-src 'none'"
|
||||||
|
/>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title data-l10n-id="about-webauthn-page-title"></title>
|
||||||
|
<link rel="stylesheet" href="chrome://global/content/aboutWebauthn.css" />
|
||||||
|
<script src="chrome://global/content/aboutWebauthn.js"></script>
|
||||||
|
<link rel="localization" href="toolkit/about/aboutWebauthn.ftl" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="body">
|
||||||
|
<div id="info-text-div">
|
||||||
|
<label
|
||||||
|
id="info-text-field"
|
||||||
|
data-l10n-id="about-webauthn-text-connect-device"
|
||||||
|
></label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div hidden id="categories">
|
||||||
|
<div id="info-tab-button" class="category" selected="true">
|
||||||
|
<span
|
||||||
|
class="tablinks"
|
||||||
|
data-l10n-id="about-webauthn-info-section-title"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div hidden id="main-content">
|
||||||
|
<div hidden class="tabcontent token-info-section" id="token-info-section">
|
||||||
|
<h2
|
||||||
|
class="categoryTitle"
|
||||||
|
data-l10n-id="about-webauthn-info-section-title"
|
||||||
|
></h2>
|
||||||
|
<div id="ctap2-token-info" class="token-info-flex-box" display="flex">
|
||||||
|
<div id="ctap2-token-info-options" class="token-info-flex-child">
|
||||||
|
<h3 data-l10n-id="about-webauthn-options-subsection-title"></h3>
|
||||||
|
<table id="authenticator-options"></table>
|
||||||
|
</div>
|
||||||
|
<div id="ctap2-token-info-info" class="token-info-flex-child">
|
||||||
|
<h3 data-l10n-id="about-webauthn-info-subsection-title"></h3>
|
||||||
|
<table id="authenticator-info"></table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,142 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
let AboutWebauthnService = null;
|
||||||
|
|
||||||
|
var AboutWebauthnManagerJS = {
|
||||||
|
_topic: "about-webauthn-prompt",
|
||||||
|
_initialized: false,
|
||||||
|
_l10n: null,
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (this._initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._l10n = new Localization(["toolkit/about/aboutWebauthn.ftl"], true);
|
||||||
|
Services.obs.addObserver(this, this._topic);
|
||||||
|
this._initialized = true;
|
||||||
|
reset_page();
|
||||||
|
},
|
||||||
|
|
||||||
|
uninit() {
|
||||||
|
Services.obs.removeObserver(this, this._topic);
|
||||||
|
},
|
||||||
|
|
||||||
|
observe(aSubject, aTopic, aData) {
|
||||||
|
let data = JSON.parse(aData);
|
||||||
|
|
||||||
|
// We have token
|
||||||
|
if (data.type == "selected-device") {
|
||||||
|
this._curr_data = data.auth_info;
|
||||||
|
document.getElementById("token-info-section").style.display = "block";
|
||||||
|
this.show_ui_based_on_authenticator_info(data);
|
||||||
|
} else if (data.type == "select-device") {
|
||||||
|
set_info_text("about-webauthn-text-select-device");
|
||||||
|
} else if (data.type == "listen-success" || data.type == "listen-error") {
|
||||||
|
reset_page();
|
||||||
|
AboutWebauthnService.listen();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
show_authenticator_options(options, element, l10n_base) {
|
||||||
|
let table = document.getElementById(element);
|
||||||
|
var empty_table = document.createElement("table");
|
||||||
|
empty_table.id = element;
|
||||||
|
table.parentNode.replaceChild(empty_table, table);
|
||||||
|
table = document.getElementById(element);
|
||||||
|
for (let key in options) {
|
||||||
|
if (key == "options") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Create an empty <tr> element and add it to the 1st position of the table:
|
||||||
|
var row = table.insertRow(0);
|
||||||
|
|
||||||
|
// Insert new cells (<td> elements) at the 1st and 2nd position of the "new" <tr> element:
|
||||||
|
var cell1 = row.insertCell(0);
|
||||||
|
var cell2 = row.insertCell(1);
|
||||||
|
|
||||||
|
// Add some text to the new cells:
|
||||||
|
let key_text = this._l10n.formatValueSync(
|
||||||
|
l10n_base + "-" + key.toLowerCase().replace(/_/g, "-")
|
||||||
|
);
|
||||||
|
var key_node = document.createTextNode(key_text);
|
||||||
|
cell1.appendChild(key_node);
|
||||||
|
var raw_value = JSON.stringify(options[key]);
|
||||||
|
var value = raw_value;
|
||||||
|
if (["true", "false", "null"].includes(raw_value)) {
|
||||||
|
value = this._l10n.formatValueSync(l10n_base + "-" + raw_value);
|
||||||
|
}
|
||||||
|
var value_node = document.createTextNode(value);
|
||||||
|
cell2.appendChild(value_node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
show_ui_based_on_authenticator_info(data) {
|
||||||
|
// Hide the "Please plug in a token"-message
|
||||||
|
document.getElementById("info-text-div").hidden = true;
|
||||||
|
// Show options, based on what the token supports
|
||||||
|
if (data.auth_info) {
|
||||||
|
document.getElementById("main-content").hidden = false;
|
||||||
|
document.getElementById("categories").hidden = false;
|
||||||
|
this.show_authenticator_options(
|
||||||
|
data.auth_info.options,
|
||||||
|
"authenticator-options",
|
||||||
|
"about-webauthn-auth-option"
|
||||||
|
);
|
||||||
|
this.show_authenticator_options(
|
||||||
|
data.auth_info,
|
||||||
|
"authenticator-info",
|
||||||
|
"about-webauthn-auth-info"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Currently auth-rs doesn't send this, because it filters out ctap2-devices.
|
||||||
|
// U2F / CTAP1 tokens can't be managed
|
||||||
|
set_info_text("about-webauthn-text-non-ctap2-device");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function set_info_text(l10nId) {
|
||||||
|
document.getElementById("info-text-div").hidden = false;
|
||||||
|
let field = document.getElementById("info-text-field");
|
||||||
|
field.setAttribute("data-l10n-id", l10nId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset_page() {
|
||||||
|
// Hide all main sections
|
||||||
|
document.getElementById("main-content").hidden = true;
|
||||||
|
document.getElementById("categories").hidden = true;
|
||||||
|
|
||||||
|
// Only display the "please connect a device" - text
|
||||||
|
set_info_text("about-webauthn-text-connect-device");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onLoad() {
|
||||||
|
AboutWebauthnManagerJS.init();
|
||||||
|
try {
|
||||||
|
AboutWebauthnService.listen();
|
||||||
|
} catch (ex) {
|
||||||
|
set_info_text("about-webauthn-text-not-available");
|
||||||
|
AboutWebauthnManagerJS.uninit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
AboutWebauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
|
||||||
|
Ci.nsIWebAuthnService
|
||||||
|
);
|
||||||
|
document.addEventListener("DOMContentLoaded", onLoad);
|
||||||
|
window.addEventListener("beforeunload", event => {
|
||||||
|
AboutWebauthnManagerJS.uninit();
|
||||||
|
if (AboutWebauthnService) {
|
||||||
|
AboutWebauthnService.cancel(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
// Do nothing if we fail to create a singleton instance,
|
||||||
|
// showing the default no-module message.
|
||||||
|
console.error(ex);
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
toolkit.jar:
|
||||||
|
content/global/aboutWebauthn.css (content/aboutWebauthn.css)
|
||||||
|
content/global/aboutWebauthn.html (content/aboutWebauthn.html)
|
||||||
|
content/global/aboutWebauthn.js (content/aboutWebauthn.js)
|
|
@ -0,0 +1,13 @@
|
||||||
|
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||||
|
# vim: set filetype=python:
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
with Files("**"):
|
||||||
|
BUG_COMPONENT = ("Core", "DOM: Web Authentication")
|
||||||
|
|
||||||
|
if CONFIG["MOZ_WIDGET_TOOLKIT"] not in ("windows", "android"):
|
||||||
|
BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"]
|
||||||
|
|
||||||
|
JAR_MANIFESTS += ["jar.mn"]
|
|
@ -0,0 +1,11 @@
|
||||||
|
[DEFAULT]
|
||||||
|
head = head.js
|
||||||
|
prefs =
|
||||||
|
security.webauth.webauthn=true
|
||||||
|
security.webauth.webauthn_enable_softtoken=false
|
||||||
|
security.webauth.webauthn_enable_android_fido2=false
|
||||||
|
security.webauth.webauthn_enable_usbtoken=false
|
||||||
|
security.webauthn.ctap2=true
|
||||||
|
|
||||||
|
[browser_aboutwebauthn_info.js]
|
||||||
|
[browser_aboutwebauthn_no_token.js]
|
|
@ -0,0 +1,218 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var doc, tab;
|
||||||
|
|
||||||
|
add_setup(async function () {
|
||||||
|
info("Starting about:webauthn");
|
||||||
|
tab = await BrowserTestUtils.openNewForegroundTab({
|
||||||
|
gBrowser,
|
||||||
|
opening: "about:webauthn",
|
||||||
|
waitForLoad: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
doc = tab.linkedBrowser.contentDocument;
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCleanupFunction(async function () {
|
||||||
|
// Close tab.
|
||||||
|
await BrowserTestUtils.removeTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
function send_auth_data_and_check(auth_data) {
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"about-webauthn-prompt",
|
||||||
|
JSON.stringify({ type: "selected-device", auth_info: auth_data })
|
||||||
|
);
|
||||||
|
|
||||||
|
let info_text = doc.getElementById("info-text-div");
|
||||||
|
is(info_text.hidden, true, "Start prompt not hidden");
|
||||||
|
|
||||||
|
let info_section = doc.getElementById("token-info-section");
|
||||||
|
isnot(info_section.style.display, "none", "Info section hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function multiple_devices() {
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"about-webauthn-prompt",
|
||||||
|
JSON.stringify({ type: "select-device" })
|
||||||
|
);
|
||||||
|
|
||||||
|
let info_text = doc.getElementById("info-text-div");
|
||||||
|
is(info_text.hidden, false, "Start prompt hidden");
|
||||||
|
let field = doc.getElementById("info-text-field");
|
||||||
|
is(
|
||||||
|
field.getAttribute("data-l10n-id"),
|
||||||
|
"about-webauthn-text-select-device",
|
||||||
|
"Field does not prompt user to touch device for selection"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function multiple_devices() {
|
||||||
|
send_auth_data_and_check(REAL_AUTH_INFO_1);
|
||||||
|
reset_about_page(doc);
|
||||||
|
send_auth_data_and_check(REAL_AUTH_INFO_2);
|
||||||
|
reset_about_page(doc);
|
||||||
|
send_auth_data_and_check(REAL_AUTH_INFO_3);
|
||||||
|
reset_about_page(doc);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Yubikey BIO
|
||||||
|
const REAL_AUTH_INFO_1 = {
|
||||||
|
versions: ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE", "FIDO_2_1"],
|
||||||
|
extensions: [
|
||||||
|
"credProtect",
|
||||||
|
"hmac-secret",
|
||||||
|
"largeBlobKey",
|
||||||
|
"credBlob",
|
||||||
|
"minPinLength",
|
||||||
|
],
|
||||||
|
aaguid: [
|
||||||
|
216, 82, 45, 159, 87, 91, 72, 102, 136, 169, 186, 153, 250, 2, 243, 91,
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
plat: false,
|
||||||
|
rk: true,
|
||||||
|
clientPin: true,
|
||||||
|
up: true,
|
||||||
|
uv: true,
|
||||||
|
pinUvAuthToken: true,
|
||||||
|
noMcGaPermissionsWithClientPin: null,
|
||||||
|
largeBlobs: true,
|
||||||
|
ep: null,
|
||||||
|
bioEnroll: true,
|
||||||
|
userVerificationMgmtPreview: true,
|
||||||
|
uvBioEnroll: null,
|
||||||
|
authnrCfg: true,
|
||||||
|
uvAcfg: null,
|
||||||
|
credMgmt: true,
|
||||||
|
credentialMgmtPreview: true,
|
||||||
|
setMinPINLength: true,
|
||||||
|
makeCredUvNotRqd: true,
|
||||||
|
alwaysUv: false,
|
||||||
|
},
|
||||||
|
max_msg_size: 1200,
|
||||||
|
pin_protocols: [2, 1],
|
||||||
|
max_credential_count_in_list: 8,
|
||||||
|
max_credential_id_length: 128,
|
||||||
|
transports: ["usb"],
|
||||||
|
algorithms: [
|
||||||
|
{ alg: -7, type: "public-key" },
|
||||||
|
{ alg: -8, type: "public-key" },
|
||||||
|
],
|
||||||
|
max_ser_large_blob_array: 1024,
|
||||||
|
force_pin_change: false,
|
||||||
|
min_pin_length: 4,
|
||||||
|
firmware_version: 328966,
|
||||||
|
max_cred_blob_length: 32,
|
||||||
|
max_rpids_for_set_min_pin_length: 1,
|
||||||
|
preferred_platform_uv_attempts: 3,
|
||||||
|
uv_modality: 2,
|
||||||
|
certifications: null,
|
||||||
|
remaining_discoverable_credentials: 20,
|
||||||
|
vendor_prototype_config_commands: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Yubikey 5
|
||||||
|
const REAL_AUTH_INFO_2 = {
|
||||||
|
versions: ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE"],
|
||||||
|
extensions: ["credProtect", "hmac-secret"],
|
||||||
|
aaguid: [
|
||||||
|
47, 192, 87, 159, 129, 19, 71, 234, 177, 22, 187, 90, 141, 185, 32, 42,
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
plat: false,
|
||||||
|
rk: true,
|
||||||
|
clientPin: true,
|
||||||
|
up: true,
|
||||||
|
uv: null,
|
||||||
|
pinUvAuthToken: null,
|
||||||
|
noMcGaPermissionsWithClientPin: null,
|
||||||
|
largeBlobs: null,
|
||||||
|
ep: null,
|
||||||
|
bioEnroll: null,
|
||||||
|
userVerificationMgmtPreview: null,
|
||||||
|
uvBioEnroll: null,
|
||||||
|
authnrCfg: null,
|
||||||
|
uvAcfg: null,
|
||||||
|
credMgmt: null,
|
||||||
|
credentialMgmtPreview: true,
|
||||||
|
setMinPINLength: null,
|
||||||
|
makeCredUvNotRqd: null,
|
||||||
|
alwaysUv: null,
|
||||||
|
},
|
||||||
|
max_msg_size: 1200,
|
||||||
|
pin_protocols: [1],
|
||||||
|
max_credential_count_in_list: 8,
|
||||||
|
max_credential_id_length: 128,
|
||||||
|
transports: ["nfc", "usb"],
|
||||||
|
algorithms: [
|
||||||
|
{ alg: -7, type: "public-key" },
|
||||||
|
{ alg: -8, type: "public-key" },
|
||||||
|
],
|
||||||
|
max_ser_large_blob_array: null,
|
||||||
|
force_pin_change: null,
|
||||||
|
min_pin_length: null,
|
||||||
|
firmware_version: null,
|
||||||
|
max_cred_blob_length: null,
|
||||||
|
max_rpids_for_set_min_pin_length: null,
|
||||||
|
preferred_platform_uv_attempts: null,
|
||||||
|
uv_modality: null,
|
||||||
|
certifications: null,
|
||||||
|
remaining_discoverable_credentials: null,
|
||||||
|
vendor_prototype_config_commands: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Nitrokey 3
|
||||||
|
const REAL_AUTH_INFO_3 = {
|
||||||
|
versions: ["U2F_V2", "FIDO_2_0", "FIDO_2_1_PRE"],
|
||||||
|
extensions: ["credProtect", "hmac-secret"],
|
||||||
|
aaguid: [
|
||||||
|
47, 192, 87, 159, 129, 19, 71, 234, 177, 22, 187, 90, 141, 185, 32, 42,
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
plat: false,
|
||||||
|
rk: true,
|
||||||
|
clientPin: true,
|
||||||
|
up: true,
|
||||||
|
uv: null,
|
||||||
|
pinUvAuthToken: null,
|
||||||
|
noMcGaPermissionsWithClientPin: null,
|
||||||
|
largeBlobs: null,
|
||||||
|
ep: null,
|
||||||
|
bioEnroll: null,
|
||||||
|
userVerificationMgmtPreview: null,
|
||||||
|
uvBioEnroll: null,
|
||||||
|
authnrCfg: null,
|
||||||
|
uvAcfg: null,
|
||||||
|
credMgmt: null,
|
||||||
|
credentialMgmtPreview: true,
|
||||||
|
setMinPINLength: null,
|
||||||
|
makeCredUvNotRqd: null,
|
||||||
|
alwaysUv: null,
|
||||||
|
},
|
||||||
|
max_msg_size: 1200,
|
||||||
|
pin_protocols: [1],
|
||||||
|
max_credential_count_in_list: 8,
|
||||||
|
max_credential_id_length: 128,
|
||||||
|
transports: ["nfc", "usb"],
|
||||||
|
algorithms: [
|
||||||
|
{ alg: -7, type: "public-key" },
|
||||||
|
{ alg: -8, type: "public-key" },
|
||||||
|
],
|
||||||
|
max_ser_large_blob_array: null,
|
||||||
|
force_pin_change: null,
|
||||||
|
min_pin_length: null,
|
||||||
|
firmware_version: null,
|
||||||
|
max_cred_blob_length: null,
|
||||||
|
max_rpids_for_set_min_pin_length: null,
|
||||||
|
preferred_platform_uv_attempts: null,
|
||||||
|
uv_modality: null,
|
||||||
|
certifications: null,
|
||||||
|
remaining_discoverable_credentials: null,
|
||||||
|
vendor_prototype_config_commands: null,
|
||||||
|
};
|
|
@ -0,0 +1,49 @@
|
||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var doc, tab;
|
||||||
|
|
||||||
|
add_setup(async function () {
|
||||||
|
info("Starting about:webauthn");
|
||||||
|
tab = await BrowserTestUtils.openNewForegroundTab({
|
||||||
|
gBrowser,
|
||||||
|
opening: "about:webauthn",
|
||||||
|
waitForLoad: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
doc = tab.linkedBrowser.contentDocument;
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCleanupFunction(async function () {
|
||||||
|
// Close tab.
|
||||||
|
await BrowserTestUtils.removeTab(tab);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function verify_page_no_token() {
|
||||||
|
let info_text = doc.getElementById("info-text-div");
|
||||||
|
is(info_text.hidden, false, "info-text-div should be visible");
|
||||||
|
let categories = doc.getElementById("categories");
|
||||||
|
is(categories.hidden, true, "categories-sidebar should be invisible");
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function verify_no_auth_info() {
|
||||||
|
let field = doc.getElementById("info-text-field");
|
||||||
|
let promise = BrowserTestUtils.waitForMutationCondition(
|
||||||
|
field,
|
||||||
|
{ attributes: true, attributeFilter: ["data-l10n-id"] },
|
||||||
|
() =>
|
||||||
|
field.getAttribute("data-l10n-id") ===
|
||||||
|
"about-webauthn-text-non-ctap2-device"
|
||||||
|
);
|
||||||
|
Services.obs.notifyObservers(
|
||||||
|
null,
|
||||||
|
"about-webauthn-prompt",
|
||||||
|
JSON.stringify({ type: "selected-device", auth_info: null })
|
||||||
|
);
|
||||||
|
await promise;
|
||||||
|
|
||||||
|
let info_text = doc.getElementById("info-text-div");
|
||||||
|
is(info_text.hidden, false);
|
||||||
|
});
|
|
@ -0,0 +1,11 @@
|
||||||
|
async function reset_about_page(doc) {
|
||||||
|
let info_text = doc.getElementById("info-text-div");
|
||||||
|
let msg = JSON.stringify({ action: "listen-finished-success" });
|
||||||
|
let promise = BrowserTestUtils.waitForMutationCondition(
|
||||||
|
info_text,
|
||||||
|
{ attributes: true, attributeFilter: ["hidden"] },
|
||||||
|
() => info_text.hidden !== false
|
||||||
|
);
|
||||||
|
Services.obs.notifyObservers(null, "about-webauthn-prompt", msg);
|
||||||
|
await promise;
|
||||||
|
}
|
|
@ -124,6 +124,9 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android":
|
||||||
"components.conf",
|
"components.conf",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if CONFIG["MOZ_WIDGET_TOOLKIT"] not in ("android", "windows"):
|
||||||
|
DIRS += ["aboutwebauthn"]
|
||||||
|
|
||||||
if CONFIG["MOZ_BUILD_APP"] == "browser":
|
if CONFIG["MOZ_BUILD_APP"] == "browser":
|
||||||
DIRS += ["normandy", "messaging-system"]
|
DIRS += ["normandy", "messaging-system"]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
### Localization for about:webauthn, a security token management page
|
||||||
|
|
||||||
|
# Page title
|
||||||
|
# 'WebAuthn' is a protocol name and should not be translated
|
||||||
|
about-webauthn-page-title = About WebAuthn
|
||||||
|
|
||||||
|
## Section titles
|
||||||
|
|
||||||
|
about-webauthn-info-section-title = Device info
|
||||||
|
about-webauthn-info-subsection-title = Authenticator info
|
||||||
|
about-webauthn-options-subsection-title = Authenticator options
|
||||||
|
|
||||||
|
## Info field texts
|
||||||
|
|
||||||
|
about-webauthn-text-connect-device = Please connect a security token.
|
||||||
|
# If multiple devices are plugged in, they will blink and we are asking the user to select one by touching the device they want.
|
||||||
|
about-webauthn-text-select-device = Please select your desired security token by touching the device.
|
||||||
|
# CTAP2 refers to Client to Authenticator Protocol version 2
|
||||||
|
about-webauthn-text-non-ctap2-device = Unable to manage options because your security token does not support CTAP2.
|
||||||
|
about-webauthn-text-not-available = Not available on this platform.
|
||||||
|
|
||||||
|
## Authenticator options fields
|
||||||
|
## Option fields correspond to the CTAP2 option IDs and definitions found in https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#option-id
|
||||||
|
|
||||||
|
about-webauthn-auth-option-uv = User verification
|
||||||
|
about-webauthn-auth-option-up = User presence
|
||||||
|
about-webauthn-auth-option-clientpin = Client PIN
|
||||||
|
about-webauthn-auth-option-rk = Resident key
|
||||||
|
about-webauthn-auth-option-plat = Platform device
|
||||||
|
# pinUvAuthToken should not be translated.
|
||||||
|
about-webauthn-auth-option-pinuvauthtoken = Command permissions (pinUvAuthToken)
|
||||||
|
# MakeCredential and GetAssertion should not be translated.
|
||||||
|
about-webauthn-auth-option-nomcgapermissionswithclientpin = No MakeCredential / GetAssertion permissions with client PIN
|
||||||
|
about-webauthn-auth-option-largeblobs = Large blobs
|
||||||
|
about-webauthn-auth-option-ep = Enterprise attestation
|
||||||
|
about-webauthn-auth-option-bioenroll = Biometric enrollment
|
||||||
|
# FIDO_2_1_PRE should not be translated.
|
||||||
|
about-webauthn-auth-option-userverificationmgmtpreview = Prototype of biometric enrollment (FIDO_2_1_PRE)
|
||||||
|
about-webauthn-auth-option-uvbioenroll = Biometric enrollment permission
|
||||||
|
about-webauthn-auth-option-authnrcfg = Authenticator config
|
||||||
|
about-webauthn-auth-option-uvacfg = Authenticator config permission
|
||||||
|
about-webauthn-auth-option-credmgmt = Credential management
|
||||||
|
about-webauthn-auth-option-credentialmgmtpreview = Prototype credential management
|
||||||
|
about-webauthn-auth-option-setminpinlength = Set minimum PIN length
|
||||||
|
# MakeCredential should not be translated.
|
||||||
|
about-webauthn-auth-option-makecreduvnotrqd = MakeCredential without user verification
|
||||||
|
about-webauthn-auth-option-alwaysuv = Always require user verification
|
||||||
|
# Shows when boolean value for an option is True. True should not be translated.
|
||||||
|
about-webauthn-auth-option-true = True
|
||||||
|
# Shows when boolean value of an option is False. False should not be translated.
|
||||||
|
about-webauthn-auth-option-false = False
|
||||||
|
# If the value is missing (null), it means a certain feature is not supported.
|
||||||
|
about-webauthn-auth-option-null = Not supported
|
||||||
|
|
||||||
|
## Authenticator info fields
|
||||||
|
## Info fields correspond to the CTAP2 authenticatorGetInfo field member name and definitions found in https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#authenticatorGetInfo
|
||||||
|
|
||||||
|
about-webauthn-auth-info-vendor-prototype-config-commands = Vendor prototype config commands
|
||||||
|
about-webauthn-auth-info-remaining-discoverable-credentials = Remaining discoverable credentials
|
||||||
|
about-webauthn-auth-info-certifications = Certifications
|
||||||
|
about-webauthn-auth-info-uv-modality = User verification modality
|
||||||
|
about-webauthn-auth-info-preferred-platform-uv-attempts = Preferred platform user verification attempts
|
||||||
|
about-webauthn-auth-info-max-rpids-for-set-min-pin-length = Max relying party IDs for set minimum PIN length
|
||||||
|
about-webauthn-auth-info-max-cred-blob-length = Max credential blob length
|
||||||
|
about-webauthn-auth-info-firmware-version = Firmware version
|
||||||
|
about-webauthn-auth-info-min-pin-length = Minimum PIN length
|
||||||
|
about-webauthn-auth-info-force-pin-change = Force PIN change
|
||||||
|
about-webauthn-auth-info-max-ser-large-blob-array = Max size of large blob array
|
||||||
|
about-webauthn-auth-info-algorithms = Algorithms
|
||||||
|
about-webauthn-auth-info-transports = Transports
|
||||||
|
about-webauthn-auth-info-max-credential-id-length = Max credential ID length
|
||||||
|
about-webauthn-auth-info-max-credential-count-in-list = Max credential count in list
|
||||||
|
about-webauthn-auth-info-pin-protocols = PIN protocols
|
||||||
|
about-webauthn-auth-info-max-msg-size = Max message size
|
||||||
|
# AAGUID should not be translated.
|
||||||
|
about-webauthn-auth-info-aaguid = AAGUID
|
||||||
|
about-webauthn-auth-info-extensions = Extensions
|
||||||
|
about-webauthn-auth-info-versions = Versions
|
||||||
|
# Shows when boolean value for an info field is True. True should not be translated.
|
||||||
|
about-webauthn-auth-info-true = True
|
||||||
|
# Shows when boolean value for an info field is False. False should not be translated.
|
||||||
|
about-webauthn-auth-info-false = False
|
||||||
|
about-webauthn-auth-info-null = Not supported
|
Загрузка…
Ссылка в новой задаче