diff --git a/dom/credentialmanagement/tests/mochitest/test_credman_iframes.html b/dom/credentialmanagement/tests/mochitest/test_credman_iframes.html index e5d3fccc5516..4a2ebf980d70 100644 --- a/dom/credentialmanagement/tests/mochitest/test_credman_iframes.html +++ b/dom/credentialmanagement/tests/mochitest/test_credman_iframes.html @@ -22,35 +22,67 @@ diff --git a/dom/webauthn/WebAuthnController.cpp b/dom/webauthn/WebAuthnController.cpp index 544d10bdba2c..5b92d0ad4af1 100644 --- a/dom/webauthn/WebAuthnController.cpp +++ b/dom/webauthn/WebAuthnController.cpp @@ -18,6 +18,7 @@ #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsIThread.h" +#include "nsServiceManagerUtils.h" #include "nsTextFormatter.h" #include "mozilla/Telemetry.h" @@ -177,7 +178,8 @@ nsCOMPtr WebAuthnController::GetTransportImpl() { return mTransportImpl; } - nsCOMPtr transport = NewAuthrsTransport(); + nsCOMPtr transport( + do_GetService("@mozilla.org/webauthn/transport;1")); transport->SetController(this); return transport; } diff --git a/dom/webauthn/authrs_bridge/src/lib.rs b/dom/webauthn/authrs_bridge/src/lib.rs index de9eb98bcb32..2c44a2bd18e0 100644 --- a/dom/webauthn/authrs_bridge/src/lib.rs +++ b/dom/webauthn/authrs_bridge/src/lib.rs @@ -11,6 +11,7 @@ extern crate xpcom; use authenticator::{ authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs}, ctap2::attestation::AttestationStatement, + ctap2::commands::get_info::AuthenticatorVersion, ctap2::server::{ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, ResidentKeyRequirement, User, UserVerificationRequirement, @@ -23,7 +24,8 @@ use moz_task::RunnableBuilder; use nserror::{ nsresult, NS_ERROR_DOM_INVALID_STATE_ERR, NS_ERROR_DOM_NOT_ALLOWED_ERR, NS_ERROR_DOM_NOT_SUPPORTED_ERR, NS_ERROR_DOM_UNKNOWN_ERR, NS_ERROR_FAILURE, - NS_ERROR_NOT_AVAILABLE, NS_ERROR_NOT_IMPLEMENTED, NS_ERROR_NULL_POINTER, NS_OK, + NS_ERROR_INVALID_ARG, NS_ERROR_NOT_AVAILABLE, NS_ERROR_NOT_IMPLEMENTED, NS_ERROR_NULL_POINTER, + NS_OK, }; use nsstring::{nsACString, nsCString, nsString}; use serde_cbor; @@ -32,13 +34,13 @@ use std::sync::mpsc::{channel, Receiver, RecvError, Sender}; use std::sync::{Arc, Mutex}; use thin_vec::ThinVec; use xpcom::interfaces::{ - nsICtapRegisterArgs, nsICtapRegisterResult, nsICtapSignArgs, nsICtapSignResult, - nsIWebAuthnController, nsIWebAuthnTransport, + nsICredentialParameters, nsICtapRegisterArgs, nsICtapRegisterResult, nsICtapSignArgs, + nsICtapSignResult, nsIWebAuthnController, nsIWebAuthnTransport, }; use xpcom::{xpcom_method, RefPtr}; mod test_token; -use test_token::TestTokenManager; +use test_token::{TestTokenManager, TestTokenManagerState, TestTokenTransport}; fn make_prompt(action: &str, tid: u64, origin: &str, browsing_context_id: u64) -> String { format!( @@ -397,6 +399,7 @@ pub struct AuthrsTransport { auth_service: RefCell, // interior mutable for use in XPCOM methods controller: Controller, pin_receiver: Arc>, + test_token_manager: TestTokenManager, } impl AuthrsTransport { @@ -745,6 +748,114 @@ impl AuthrsTransport { Err(e) => Err(authrs_to_nserror(e)), } } + + xpcom_method!( + add_virtual_authenticator => AddVirtualAuthenticator( + protocol: *const nsACString, + transport: *const nsACString, + hasResidentKey: bool, + hasUserVerification: bool, + isUserConsenting: bool, + isUserVerified: bool) -> u64 + ); + fn add_virtual_authenticator( + &self, + protocol: &nsACString, + transport: &nsACString, + has_resident_key: bool, + has_user_verification: bool, + is_user_consenting: bool, + is_user_verified: bool, + ) -> Result { + let protocol = match protocol.to_string().as_str() { + "ctap1/u2f" => AuthenticatorVersion::U2F_V2, + "ctap2" => AuthenticatorVersion::FIDO_2_0, + "ctap2_1" => AuthenticatorVersion::FIDO_2_1, + _ => return Err(NS_ERROR_INVALID_ARG), + }; + match transport.to_string().as_str() { + "usb" | "nfc" | "ble" | "smart-card" | "hybrid" | "internal" => (), + _ => return Err(NS_ERROR_INVALID_ARG), + }; + self.test_token_manager.add_virtual_authenticator( + protocol, + has_resident_key, + has_user_verification, + is_user_consenting, + is_user_verified, + ) + } + + xpcom_method!(remove_virtual_authenticator => RemoveVirtualAuthenticator(authenticatorId: u64)); + fn remove_virtual_authenticator(&self, authenticator_id: u64) -> Result<(), nsresult> { + self.test_token_manager + .remove_virtual_authenticator(authenticator_id) + } + + xpcom_method!( + add_credential => AddCredential( + authenticatorId: u64, + credentialId: *const nsACString, + isResidentCredential: bool, + rpId: *const nsACString, + privateKey: *const nsACString, + userHandle: *const nsACString, + signCount: u32) + ); + fn add_credential( + &self, + authenticator_id: u64, + credential_id: &nsACString, + is_resident_credential: bool, + rp_id: &nsACString, + private_key: &nsACString, + user_handle: &nsACString, + sign_count: u32, + ) -> Result<(), nsresult> { + self.test_token_manager.add_credential( + authenticator_id, + credential_id.as_ref(), + private_key.as_ref(), + user_handle.as_ref(), + sign_count, + rp_id.to_string(), + is_resident_credential, + ) + } + + xpcom_method!(get_credentials => GetCredentials(authenticatorId: u64) -> ThinVec>>); + fn get_credentials( + &self, + authenticator_id: u64, + ) -> Result>>, nsresult> { + self.test_token_manager.get_credentials(authenticator_id) + } + + xpcom_method!(remove_credential => RemoveCredential(authenticatorId: u64, credentialId: *const nsACString)); + fn remove_credential( + &self, + authenticator_id: u64, + credential_id: &nsACString, + ) -> Result<(), nsresult> { + self.test_token_manager + .remove_credential(authenticator_id, credential_id.as_ref()) + } + + xpcom_method!(remove_all_credentials => RemoveAllCredentials(authenticatorId: u64)); + fn remove_all_credentials(&self, authenticator_id: u64) -> Result<(), nsresult> { + self.test_token_manager + .remove_all_credentials(authenticator_id) + } + + xpcom_method!(set_user_verified => SetUserVerified(authenticatorId: u64, isUserVerified: bool)); + fn set_user_verified( + &self, + authenticator_id: u64, + is_user_verified: bool, + ) -> Result<(), nsresult> { + self.test_token_manager + .set_user_verified(authenticator_id, is_user_verified) + } } #[no_mangle] @@ -761,15 +872,15 @@ pub extern "C" fn authrs_transport_constructor( auth_service.add_u2f_usb_hid_platform_transports(); } - match TestTokenManager::new() { - Ok(transport) => auth_service.add_transport(Box::new(transport)), - Err(_) => return NS_ERROR_FAILURE, - } + let test_token_manager_state = TestTokenManagerState::new(); + let transport = TestTokenTransport::new(Arc::clone(&test_token_manager_state)); + auth_service.add_transport(Box::new(transport)); let wrapper = AuthrsTransport::allocate(InitAuthrsTransport { auth_service: RefCell::new(auth_service), controller: Controller(RefCell::new(std::ptr::null())), pin_receiver: Arc::new(Mutex::new(None)), + test_token_manager: TestTokenManager::new(test_token_manager_state), }); unsafe { RefPtr::new(wrapper.coerce::()).forget(&mut *result); diff --git a/dom/webauthn/authrs_bridge/src/test_token.rs b/dom/webauthn/authrs_bridge/src/test_token.rs index 49b47811e64e..8479a02433b5 100644 --- a/dom/webauthn/authrs_bridge/src/test_token.rs +++ b/dom/webauthn/authrs_bridge/src/test_token.rs @@ -2,20 +2,7 @@ * 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 runloop::RunLoop; -use std::{ - io, - ops::DerefMut, - sync::{Arc, Mutex}, -}; - use authenticator::authenticatorservice::{AuthenticatorTransport, RegisterArgs, SignArgs}; -use authenticator::{RegisterResult, SignResult, StatusUpdate}; - -use authenticator::errors::{AuthenticatorError, CommandError, HIDError, U2FTokenError}; - -use authenticator::{FidoDevice, FidoDeviceIO, FidoProtocol, VirtualFidoDevice}; - use authenticator::crypto::{ecdsa_p256_sha256_sign_raw, COSEAlgorithm, COSEKey, SharedSecret}; use authenticator::ctap2::{ attestation::{ @@ -34,17 +21,25 @@ use authenticator::ctap2::{ Request, RequestCtap1, RequestCtap2, StatusCode, }, preflight::CheckKeyHandle, - server::{PublicKeyCredentialDescriptor, RelyingPartyWrapper, User}, + server::{PublicKeyCredentialDescriptor, RelyingParty, RelyingPartyWrapper, User}, }; - +use authenticator::errors::{AuthenticatorError, CommandError, HIDError, U2FTokenError}; use authenticator::{ctap2, statecallback::StateCallback}; - +use authenticator::{FidoDevice, FidoDeviceIO, FidoProtocol, VirtualFidoDevice}; +use authenticator::{RegisterResult, SignResult, StatusUpdate}; +use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_NOT_IMPLEMENTED, NS_OK}; +use nsstring::{nsACString, nsCString}; use rand::{thread_rng, RngCore}; -use std::cell::RefCell; -use std::sync::{ - atomic::{AtomicU32, Ordering}, - mpsc::Sender, -}; +use runloop::RunLoop; +use std::cell::{Ref, RefCell}; +use std::collections::{hash_map::Entry, HashMap}; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::mpsc::Sender; +use std::sync::{Arc, Mutex}; +use thin_vec::ThinVec; +use xpcom::interfaces::nsICredentialParameters; +use xpcom::{xpcom_method, RefPtr}; // All TestTokens use this fixed, randomly generated, AAGUID const VIRTUAL_TOKEN_AAGUID: AAGuid = AAGuid([ @@ -52,13 +47,13 @@ const VIRTUAL_TOKEN_AAGUID: AAGuid = AAGuid([ ]); #[derive(Debug)] -pub struct TestTokenCredential { - pub id: Vec, - pub privkey: Vec, - pub user_handle: Vec, - pub sign_count: AtomicU32, - pub is_discoverable_credential: bool, - pub rp: RelyingPartyWrapper, +struct TestTokenCredential { + id: Vec, + privkey: Vec, + user_handle: Vec, + sign_count: AtomicU32, + is_discoverable_credential: bool, + rp: RelyingPartyWrapper, } impl TestTokenCredential { @@ -99,53 +94,45 @@ impl TestTokenCredential { } #[derive(Debug)] -pub struct TestToken { - pub id: u64, - pub protocol: FidoProtocol, - pub versions: Vec, - pub transport: String, - pub is_user_consenting: bool, - pub has_user_verification: bool, - pub is_user_verified: bool, - pub has_resident_key: bool, +struct TestToken { + protocol: FidoProtocol, + versions: Vec, + has_resident_key: bool, + has_user_verification: bool, + is_user_consenting: bool, + is_user_verified: bool, // This is modified in `make_credentials` which takes a &TestToken, but we only allow one transaction at a time. - pub credentials: RefCell>, - pub pin_token: [u8; 32], - pub shared_secret: Option, - pub authenticator_info: Option, - pub counter: AtomicU32, + credentials: RefCell>, + pin_token: [u8; 32], + shared_secret: Option, + authenticator_info: Option, } impl TestToken { - pub fn new( - id: u64, + fn new( versions: Vec, - transport: String, - is_user_consenting: bool, - has_user_verification: bool, - is_user_verified: bool, has_resident_key: bool, + has_user_verification: bool, + is_user_consenting: bool, + is_user_verified: bool, ) -> TestToken { let mut pin_token = [0u8; 32]; thread_rng().fill_bytes(&mut pin_token); Self { - id, protocol: FidoProtocol::CTAP2, versions, - transport, - is_user_consenting, - has_user_verification, - is_user_verified, has_resident_key, + has_user_verification, + is_user_consenting, + is_user_verified, credentials: RefCell::new(vec![]), pin_token, shared_secret: None, authenticator_info: None, - counter: AtomicU32::new(0), } } - pub fn insert_credential( + fn insert_credential( &self, id: &[u8], privkey: &[u8], @@ -171,8 +158,11 @@ impl TestToken { } } - #[allow(dead_code)] - pub fn delete_credential(&mut self, id: &[u8]) -> bool { + fn get_credentials(&self) -> Ref> { + self.credentials.borrow() + } + + fn delete_credential(&mut self, id: &[u8]) -> bool { let mut credlist = self.credentials.borrow_mut(); if let Ok(idx) = credlist.binary_search_by_key(&id, |probe| &probe.id) { credlist.remove(idx); @@ -182,7 +172,11 @@ impl TestToken { false } - pub(crate) fn has_credential(&self, id: &[u8]) -> bool { + fn delete_all_credentials(&mut self) { + self.credentials.borrow_mut().clear(); + } + + fn has_credential(&self, id: &[u8]) -> bool { self.credentials .borrow() .binary_search_by_key(&id, |probe| &probe.id) @@ -555,44 +549,225 @@ impl VirtualFidoDevice for TestToken { } } -pub struct ManagerState { - pub tokens: Vec, +pub(crate) struct TestTokenManagerState { + tokens: HashMap, } -impl ManagerState { - pub fn new() -> Arc> { - Arc::new(Mutex::new(ManagerState { tokens: vec![] })) +impl TestTokenManagerState { + pub fn new() -> Arc> { + Arc::new(Mutex::new(TestTokenManagerState { + tokens: HashMap::new(), + })) } } -pub struct TestTokenManager { - state: Arc>, - queue: Option, +#[xpcom(implement(nsICredentialParameters), atomic)] +struct CredentialParameters { + credential_id: Vec, + is_resident_credential: bool, + rp_id: String, + private_key: Vec, + user_handle: Vec, + sign_count: u32, +} + +impl CredentialParameters { + xpcom_method!(get_credential_id => GetCredentialId() -> nsACString); + fn get_credential_id(&self) -> Result { + Ok(nsCString::from(&self.credential_id)) + } + + xpcom_method!(get_is_resident_credential => GetIsResidentCredential() -> bool); + fn get_is_resident_credential(&self) -> Result { + Ok(self.is_resident_credential) + } + + xpcom_method!(get_rp_id => GetRpId() -> nsACString); + fn get_rp_id(&self) -> Result { + Ok(nsCString::from(&self.rp_id)) + } + + xpcom_method!(get_private_key => GetPrivateKey() -> nsACString); + fn get_private_key(&self) -> Result { + Ok(nsCString::from(&self.private_key)) + } + + xpcom_method!(get_user_handle => GetUserHandle() -> nsACString); + fn get_user_handle(&self) -> Result { + Ok(nsCString::from(&self.user_handle)) + } + + xpcom_method!(get_sign_count => GetSignCount() -> u32); + fn get_sign_count(&self) -> Result { + Ok(self.sign_count) + } +} + +pub(crate) struct TestTokenManager { + state: Arc>, } impl TestTokenManager { - pub fn new() -> io::Result { - let state = ManagerState::new(); - { - // Bug 1838894: Remove this when we can add/remove authenticators - // dynamically - let mut guard = state.lock().unwrap(); - guard.deref_mut().tokens.push(TestToken::new( - /* id */ 0, - /* versions */ vec![AuthenticatorVersion::FIDO_2_1], - /* transport */ "usb".to_string(), - /* is_user_consenting */ true, - /* has_user_verification */ true, - /* is_user_verified */ true, - /* has_resident_key */ true, - )); - } + pub fn new(state: Arc>) -> Self { + Self { state } + } - Ok(Self { state, queue: None }) + pub fn add_virtual_authenticator( + &self, + protocol: AuthenticatorVersion, + has_resident_key: bool, + has_user_verification: bool, + is_user_consenting: bool, + is_user_verified: bool, + ) -> Result { + let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; + let token = TestToken::new( + vec![protocol], + has_resident_key, + has_user_verification, + is_user_consenting, + is_user_verified, + ); + loop { + let id = rand::random::() & 0x1f_ffff_ffff_ffffu64; // Make the id safe for JS (53 bits) + match guard.deref_mut().tokens.entry(id) { + Entry::Occupied(_) => continue, + Entry::Vacant(v) => { + v.insert(token); + return Ok(id); + } + }; + } + } + + pub fn remove_virtual_authenticator(&self, authenticator_id: u64) -> Result<(), nsresult> { + let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; + guard + .deref_mut() + .tokens + .remove(&authenticator_id) + .ok_or(NS_ERROR_INVALID_ARG)?; + Ok(()) + } + + pub fn add_credential( + &self, + authenticator_id: u64, + id: &[u8], + privkey: &[u8], + user_handle: &[u8], + sign_count: u32, + rp_id: String, + is_resident_credential: bool, + ) -> Result<(), nsresult> { + let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; + let token = guard + .deref_mut() + .tokens + .get_mut(&authenticator_id) + .ok_or(NS_ERROR_INVALID_ARG)?; + let rp = RelyingParty { + id: rp_id, + name: None, + icon: None, + }; + token.insert_credential( + id, + privkey, + &RelyingPartyWrapper::Data(rp), + is_resident_credential, + user_handle, + sign_count, + ); + Ok(()) + } + + pub fn get_credentials( + &self, + authenticator_id: u64, + ) -> Result>>, nsresult> { + let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; + let token = guard + .tokens + .get_mut(&authenticator_id) + .ok_or(NS_ERROR_INVALID_ARG)?; + let credentials = token.get_credentials(); + let mut credentials_parameters = ThinVec::with_capacity(credentials.len()); + for credential in credentials.deref() { + // CTAP1 credentials are not currently supported here. + let rp_id = credential + .rp + .id() + .map(|id| id.clone()) + .ok_or(NS_ERROR_NOT_IMPLEMENTED)?; + let credential_parameters = CredentialParameters::allocate(InitCredentialParameters { + credential_id: credential.id.clone(), + is_resident_credential: credential.is_discoverable_credential, + rp_id, + private_key: credential.privkey.clone(), + user_handle: credential.user_handle.clone(), + sign_count: credential.sign_count.load(Ordering::Relaxed), + }) + .query_interface::() + .ok_or(NS_ERROR_FAILURE)?; + credentials_parameters.push(Some(credential_parameters)); + } + Ok(credentials_parameters) + } + + pub fn remove_credential(&self, authenticator_id: u64, id: &[u8]) -> Result<(), nsresult> { + let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; + let token = guard + .deref_mut() + .tokens + .get_mut(&authenticator_id) + .ok_or(NS_ERROR_INVALID_ARG)?; + if token.delete_credential(id) { + Ok(()) + } else { + Err(NS_ERROR_INVALID_ARG) + } + } + + pub fn remove_all_credentials(&self, authenticator_id: u64) -> Result<(), nsresult> { + let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; + let token = guard + .deref_mut() + .tokens + .get_mut(&authenticator_id) + .ok_or(NS_ERROR_INVALID_ARG)?; + token.delete_all_credentials(); + Ok(()) + } + + pub fn set_user_verified( + &self, + authenticator_id: u64, + is_user_verified: bool, + ) -> Result<(), nsresult> { + let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?; + let mut token = guard + .deref_mut() + .tokens + .get_mut(&authenticator_id) + .ok_or(NS_ERROR_INVALID_ARG)?; + token.is_user_verified = is_user_verified; + Ok(()) } } -impl Drop for TestTokenManager { +pub(crate) struct TestTokenTransport { + state: Arc>, + queue: Option, +} + +impl TestTokenTransport { + pub fn new(state: Arc>) -> Self { + Self { state, queue: None } + } +} + +impl Drop for TestTokenTransport { fn drop(&mut self) { if let Some(queue) = self.queue.take() { queue.cancel(); @@ -600,7 +775,7 @@ impl Drop for TestTokenManager { } } -impl AuthenticatorTransport for TestTokenManager { +impl AuthenticatorTransport for TestTokenTransport { fn register( &mut self, timeout: u64, @@ -621,7 +796,7 @@ impl AuthenticatorTransport for TestTokenManager { move |alive| { let mut state_obj = state.lock().unwrap(); - for token in state_obj.tokens.deref_mut() { + for token in state_obj.tokens.values_mut() { let _ = token.init(); if ctap2::register( token, @@ -666,7 +841,7 @@ impl AuthenticatorTransport for TestTokenManager { move |alive| { let mut state_obj = state.lock().unwrap(); - for token in state_obj.tokens.deref_mut() { + for token in state_obj.tokens.values_mut() { let _ = token.init(); if ctap2::sign( token, diff --git a/dom/webauthn/components.conf b/dom/webauthn/components.conf new file mode 100644 index 000000000000..db2e801a8954 --- /dev/null +++ b/dom/webauthn/components.conf @@ -0,0 +1,14 @@ +# -*- 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/. + +Classes = [ + { + 'cid': '{ebe8a51d-bd54-4838-b031-cd2289990e14}', + 'contract_ids': ['@mozilla.org/webauthn/transport;1'], + 'headers': ['/dom/webauthn/AuthrsTransport.h'], + 'constructor': 'mozilla::dom::NewAuthrsTransport', + }, +] diff --git a/dom/webauthn/moz.build b/dom/webauthn/moz.build index 0bf59b1d9205..f3cf2cd4b2a1 100644 --- a/dom/webauthn/moz.build +++ b/dom/webauthn/moz.build @@ -9,6 +9,10 @@ with Files("**"): IPDL_SOURCES += ["PWebAuthnTransaction.ipdl"] +XPCOM_MANIFESTS += [ + "components.conf", +] + XPIDL_SOURCES += ["nsIU2FTokenManager.idl", "nsIWebAuthnController.idl"] XPIDL_MODULE = "dom_webauthn" diff --git a/dom/webauthn/nsIWebAuthnController.idl b/dom/webauthn/nsIWebAuthnController.idl index b5a2e949bac8..f71f57f6b3ec 100644 --- a/dom/webauthn/nsIWebAuthnController.idl +++ b/dom/webauthn/nsIWebAuthnController.idl @@ -188,9 +188,20 @@ interface nsIWebAuthnController : nsIU2FTokenManager [noscript] void finishSign(in uint64_t aTransactionId, in Array aResult); }; +[scriptable, uuid(6c4ecd9f-57c0-4d7d-8080-bf6e4d499f8f)] +interface nsICredentialParameters : nsISupports +{ + readonly attribute ACString credentialId; + readonly attribute bool isResidentCredential; + readonly attribute ACString rpId; + readonly attribute ACString privateKey; + readonly attribute ACString userHandle; + readonly attribute uint32_t signCount; +}; + // The nsIWebAuthnTransport interface allows a C++ implemented nsIWebAuthnController to interact // with authenticators written in both Rust and C++ -[uuid(e236a9b4-a26f-11ed-b6cc-07a9834e19b1)] +[scriptable, uuid(e236a9b4-a26f-11ed-b6cc-07a9834e19b1)] interface nsIWebAuthnTransport : nsISupports { attribute nsIWebAuthnController controller; @@ -198,6 +209,49 @@ interface nsIWebAuthnTransport : nsISupports void makeCredential(in uint64_t aTransactionId, in uint64_t browsingContextId, in nsICtapRegisterArgs args); void getAssertion(in uint64_t aTransactionId, in uint64_t browsingContextId, in nsICtapSignArgs args); + // Adds a virtual (software) authenticator for use in tests (particularly + // tests run via WebDriver). See + // https://w3c.github.io/webauthn/#sctn-automation-add-virtual-authenticator. + uint64_t addVirtualAuthenticator( + in ACString protocol, + in ACString transport, + in bool hasResidentKey, + in bool hasUserVerification, + in bool isUserConsenting, + in bool isUserVerified); + + // Removes a previously-added virtual authenticator, as identified by its + // id. See + // https://w3c.github.io/webauthn/#sctn-automation-remove-virtual-authenticator + void removeVirtualAuthenticator(in uint64_t authenticatorId); + + // Adds a credential to a previously-added authenticator. See + // https://w3c.github.io/webauthn/#sctn-automation-add-credential + void addCredential( + in uint64_t authenticatorId, + in ACString credentialId, + in bool isResidentCredential, + in ACString rpId, + in ACString privateKey, + in ACString userHandle, + in uint32_t signCount); + + // Gets all credentials that have been added to a virtual authenticator. + // See https://w3c.github.io/webauthn/#sctn-automation-get-credentials + Array getCredentials(in uint64_t authenticatorId); + + // Removes a credential from a virtual authenticator. See + // https://w3c.github.io/webauthn/#sctn-automation-remove-credential + void removeCredential(in uint64_t authenticatorId, in ACString credentialId); + + // Removes all credentials from a virtual authenticator. See + // https://w3c.github.io/webauthn/#sctn-automation-remove-all-credentials + void removeAllCredentials(in uint64_t authenticatorId); + + // 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); + // These are prompt callbacks but they're not intended to be called directly from // JavaScript---they are proxied through the nsIWebAuthnController first. [noscript] void pinCallback(in uint64_t aTransactionId, in ACString aPin); diff --git a/dom/webauthn/tests/browser/browser_fido_appid_extension.js b/dom/webauthn/tests/browser/browser_fido_appid_extension.js index 156cfcfb2097..3aa4dca8509d 100644 --- a/dom/webauthn/tests/browser/browser_fido_appid_extension.js +++ b/dom/webauthn/tests/browser/browser_fido_appid_extension.js @@ -10,6 +10,8 @@ let expectNotSupportedError = expectError("NotSupported"); let expectInvalidStateError = expectError("InvalidState"); let expectSecurityError = expectError("Security"); +add_virtual_authenticator(); + add_task(async function test_appid_unused() { // Open a new tab. let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL); diff --git a/dom/webauthn/tests/browser/browser_webauthn_ipaddress.js b/dom/webauthn/tests/browser/browser_webauthn_ipaddress.js index 2c3f8ea0251a..49ec10145052 100644 --- a/dom/webauthn/tests/browser/browser_webauthn_ipaddress.js +++ b/dom/webauthn/tests/browser/browser_webauthn_ipaddress.js @@ -4,6 +4,8 @@ "use strict"; +add_virtual_authenticator(); + let expectSecurityError = expectError("Security"); add_task(async function test_setup() { diff --git a/dom/webauthn/tests/browser/browser_webauthn_prompts.js b/dom/webauthn/tests/browser/browser_webauthn_prompts.js index 0915f2e3ea06..e9fa4c879dd1 100644 --- a/dom/webauthn/tests/browser/browser_webauthn_prompts.js +++ b/dom/webauthn/tests/browser/browser_webauthn_prompts.js @@ -22,6 +22,7 @@ add_task(test_register_direct_cancel); add_task(test_tab_switching); add_task(test_window_switching); add_task(async function test_setup_softtoken() { + add_virtual_authenticator(); return SpecialPowers.pushPrefEnv({ set: [ ["security.webauth.webauthn_enable_softtoken", true], diff --git a/dom/webauthn/tests/browser/browser_webauthn_telemetry.js b/dom/webauthn/tests/browser/browser_webauthn_telemetry.js index b752ea5b2653..2d8b7c9458a5 100644 --- a/dom/webauthn/tests/browser/browser_webauthn_telemetry.js +++ b/dom/webauthn/tests/browser/browser_webauthn_telemetry.js @@ -10,6 +10,8 @@ ChromeUtils.defineESModuleGetters(this, { const TEST_URL = "https://example.com/"; +add_virtual_authenticator(); + function getTelemetryForScalar(aName) { let scalars = TelemetryTestUtils.getProcessScalars("parent", true); return scalars[aName] || 0; diff --git a/dom/webauthn/tests/browser/head.js b/dom/webauthn/tests/browser/head.js index 96c5bff3fcc1..cae499240a3e 100644 --- a/dom/webauthn/tests/browser/head.js +++ b/dom/webauthn/tests/browser/head.js @@ -22,6 +22,26 @@ for (let script of scripts) { ); } +function add_virtual_authenticator(autoremove = true) { + let webauthnTransport = Cc["@mozilla.org/webauthn/transport;1"].getService( + Ci.nsIWebAuthnTransport + ); + let id = webauthnTransport.addVirtualAuthenticator( + "ctap2", + "internal", + true, + true, + true, + true + ); + if (autoremove) { + registerCleanupFunction(() => { + webauthnTransport.removeVirtualAuthenticator(id); + }); + } + return id; +} + function memcmp(x, y) { let xb = new Uint8Array(x); let yb = new Uint8Array(y); diff --git a/dom/webauthn/tests/mochitest.ini b/dom/webauthn/tests/mochitest.ini index 343864747fb9..414b06d7b7cc 100644 --- a/dom/webauthn/tests/mochitest.ini +++ b/dom/webauthn/tests/mochitest.ini @@ -18,6 +18,7 @@ skip-if = win10_2004 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) win10_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) win11_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) + toolkit == 'android' # Test sets security.webauth.webauthn_enable_usbtoken to true, which isn't applicable to android [test_webauthn_attestation_conveyance.html] fail-if = xorigin # NotAllowedError skip-if = @@ -48,6 +49,7 @@ skip-if = win10_2004 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) win10_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) win11_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) + toolkit == 'android' # Test disables all tokens, which is an unsupported configuration on android. [test_webauthn_make_credential.html] fail-if = xorigin # NotAllowedError skip-if = @@ -60,12 +62,14 @@ skip-if = win10_2004 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) win10_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) win11_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) + toolkit == 'android' # Test sets security.webauth.webauthn_enable_usbtoken to true, which isn't applicable to android [test_webauthn_get_assertion_dead_object.html] skip-if = win10_2004 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) win10_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) win11_2009 # Bug 1718296 (Windows 10 1903+ has its own window and U2F that we cannot control with tests.) [test_webauthn_override_request.html] +skip-if = toolkit == 'android' # Test sets security.webauth.webauthn_enable_usbtoken to true, which isn't applicable to android [test_webauthn_store_credential.html] fail-if = xorigin # NotAllowedError skip-if = diff --git a/dom/webauthn/tests/test_webauthn_abort_signal.html b/dom/webauthn/tests/test_webauthn_abort_signal.html index e7e898812d09..51b0d95ff351 100644 --- a/dom/webauthn/tests/test_webauthn_abort_signal.html +++ b/dom/webauthn/tests/test_webauthn_abort_signal.html @@ -22,9 +22,9 @@ is(aResult.code, DOMException.ABORT_ERR, "Expecting an AbortError"); } - add_task(() => { + add_task(async () => { // Enable USB tokens. - return SpecialPowers.pushPrefEnv({"set": [ + await SpecialPowers.pushPrefEnv({"set": [ ["security.webauth.webauthn_enable_softtoken", false], ["security.webauth.webauthn_enable_usbtoken", true], ]}); diff --git a/dom/webauthn/tests/test_webauthn_attestation_conveyance.html b/dom/webauthn/tests/test_webauthn_attestation_conveyance.html index 81852c1b775a..3f3b01fdbbbe 100644 --- a/dom/webauthn/tests/test_webauthn_attestation_conveyance.html +++ b/dom/webauthn/tests/test_webauthn_attestation_conveyance.html @@ -20,10 +20,11 @@ diff --git a/dom/webauthn/tests/test_webauthn_isexternalctap2securitykeysupported.html b/dom/webauthn/tests/test_webauthn_isexternalctap2securitykeysupported.html index 33f367dc1969..303e0d8baa13 100644 --- a/dom/webauthn/tests/test_webauthn_isexternalctap2securitykeysupported.html +++ b/dom/webauthn/tests/test_webauthn_isexternalctap2securitykeysupported.html @@ -18,9 +18,6 @@ diff --git a/dom/webauthn/tests/test_webauthn_make_credential.html b/dom/webauthn/tests/test_webauthn_make_credential.html index f179961cb66b..30b48a55149d 100644 --- a/dom/webauthn/tests/test_webauthn_make_credential.html +++ b/dom/webauthn/tests/test_webauthn_make_credential.html @@ -18,6 +18,10 @@ + @@ -13,6 +14,10 @@