Source code

Revision control

Copy as Markdown

Other Tools

/* 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 authenticator::authenticatorservice::{RegisterArgs, SignArgs};
use authenticator::crypto::{ecdsa_p256_sha256_sign_raw, COSEAlgorithm, COSEKey, SharedSecret};
use authenticator::ctap2::{
attestation::{
AAGuid, AttestationObject, AttestationStatement, AttestationStatementPacked,
AttestedCredentialData, AuthenticatorData, AuthenticatorDataFlags, Extension,
},
client_data::ClientDataHash,
commands::{
client_pin::{ClientPIN, ClientPinResponse, PINSubcommand},
get_assertion::{GetAssertion, GetAssertionResponse, GetAssertionResult},
get_info::{AuthenticatorInfo, AuthenticatorOptions, AuthenticatorVersion},
get_version::{GetVersion, U2FInfo},
make_credentials::{MakeCredentials, MakeCredentialsResult},
reset::Reset,
selection::Selection,
RequestCtap1, RequestCtap2, StatusCode,
},
preflight::CheckKeyHandle,
server::{
AuthenticatorAttachment, PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity,
RelyingParty,
},
};
use authenticator::errors::{AuthenticatorError, CommandError, HIDError, U2FTokenError};
use authenticator::{ctap2, statecallback::StateCallback};
use authenticator::{FidoDevice, FidoDeviceIO, FidoProtocol, VirtualFidoDevice};
use authenticator::{RegisterResult, SignResult, StatusUpdate};
use base64::Engine;
use moz_task::RunnableBuilder;
use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_OK};
use nsstring::{nsACString, nsAString, nsCString, nsString};
use rand::{thread_rng, RngCore};
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, nsIWebAuthnAutoFillEntry};
use xpcom::{xpcom_method, RefPtr};
// All TestTokens use this fixed, randomly generated, AAGUID
const VIRTUAL_TOKEN_AAGUID: AAGuid = AAGuid([
0x68, 0xe1, 0x00, 0xa5, 0x0b, 0x47, 0x91, 0x04, 0xb8, 0x54, 0x97, 0xa9, 0xba, 0x51, 0x06, 0x38,
]);
#[derive(Debug)]
struct TestTokenCredential {
id: Vec<u8>,
privkey: Vec<u8>,
user_handle: Vec<u8>,
sign_count: AtomicU32,
is_discoverable_credential: bool,
rp: RelyingParty,
}
impl TestTokenCredential {
fn assert(
&self,
client_data_hash: &ClientDataHash,
flags: AuthenticatorDataFlags,
) -> Result<GetAssertionResponse, HIDError> {
let credentials = Some(PublicKeyCredentialDescriptor {
id: self.id.clone(),
transports: vec![],
});
let auth_data = AuthenticatorData {
rp_id_hash: self.rp.hash(),
flags,
counter: self.sign_count.fetch_add(1, Ordering::Relaxed),
credential_data: None,
extensions: Extension::default(),
};
let user = Some(PublicKeyCredentialUserEntity {
id: self.user_handle.clone(),
..Default::default()
});
let mut data = auth_data.to_vec();
data.extend_from_slice(client_data_hash.as_ref());
let signature =
ecdsa_p256_sha256_sign_raw(&self.privkey, &data).or(Err(HIDError::DeviceError))?;
Ok(GetAssertionResponse {
credentials,
auth_data,
signature,
user,
number_of_credentials: Some(1),
})
}
}
#[derive(Debug)]
struct TestToken {
protocol: FidoProtocol,
transport: String,
versions: Vec<AuthenticatorVersion>,
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.
credentials: RefCell<Vec<TestTokenCredential>>,
pin_token: [u8; 32],
shared_secret: Option<SharedSecret>,
authenticator_info: Option<AuthenticatorInfo>,
}
impl TestToken {
fn new(
versions: Vec<AuthenticatorVersion>,
transport: String,
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 {
protocol: FidoProtocol::CTAP2,
transport,
versions,
has_resident_key,
has_user_verification,
is_user_consenting,
is_user_verified,
credentials: RefCell::new(vec![]),
pin_token,
shared_secret: None,
authenticator_info: None,
}
}
fn insert_credential(
&self,
id: &[u8],
privkey: &[u8],
rp: &RelyingParty,
is_discoverable_credential: bool,
user_handle: &[u8],
sign_count: u32,
) {
let c = TestTokenCredential {
id: id.to_vec(),
privkey: privkey.to_vec(),
rp: rp.clone(),
is_discoverable_credential,
user_handle: user_handle.to_vec(),
sign_count: AtomicU32::new(sign_count),
};
let mut credlist = self.credentials.borrow_mut();
match credlist.binary_search_by_key(&id, |probe| &probe.id) {
Ok(_) => {}
Err(idx) => credlist.insert(idx, c),
}
}
fn get_credentials(&self) -> Ref<Vec<TestTokenCredential>> {
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);
return true;
}
false
}
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)
.is_ok()
}
fn max_supported_version(&self) -> AuthenticatorVersion {
self.authenticator_info
.as_ref()
.map_or(AuthenticatorVersion::U2F_V2, |info| {
info.max_supported_version()
})
}
}
impl FidoDevice for TestToken {
fn pre_init(&mut self) -> Result<(), HIDError> {
Ok(())
}
fn should_try_ctap2(&self) -> bool {
true
}
fn initialized(&self) -> bool {
true
}
fn is_u2f(&mut self) -> bool {
true
}
fn get_shared_secret(&self) -> Option<&SharedSecret> {
self.shared_secret.as_ref()
}
fn set_shared_secret(&mut self, shared_secret: SharedSecret) {
self.shared_secret = Some(shared_secret);
}
fn get_authenticator_info(&self) -> Option<&AuthenticatorInfo> {
self.authenticator_info.as_ref()
}
fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) {
self.authenticator_info = Some(authenticator_info);
}
fn get_protocol(&self) -> FidoProtocol {
self.protocol
}
fn downgrade_to_ctap1(&mut self) {
self.protocol = FidoProtocol::CTAP1
}
}
impl FidoDeviceIO for TestToken {
fn send_msg_cancellable<Out, Req: RequestCtap1<Output = Out> + RequestCtap2<Output = Out>>(
&mut self,
msg: &Req,
keep_alive: &dyn Fn() -> bool,
) -> Result<Out, HIDError> {
if !self.initialized() {
return Err(HIDError::DeviceNotInitialized);
}
match self.get_protocol() {
FidoProtocol::CTAP1 => self.send_ctap1_cancellable(msg, keep_alive),
FidoProtocol::CTAP2 => self.send_cbor_cancellable(msg, keep_alive),
}
}
fn send_cbor_cancellable<Req: RequestCtap2>(
&mut self,
msg: &Req,
_keep_alive: &dyn Fn() -> bool,
) -> Result<Req::Output, HIDError> {
msg.send_to_virtual_device(self)
}
fn send_ctap1_cancellable<Req: RequestCtap1>(
&mut self,
msg: &Req,
_keep_alive: &dyn Fn() -> bool,
) -> Result<Req::Output, HIDError> {
msg.send_to_virtual_device(self)
}
}
impl VirtualFidoDevice for TestToken {
fn check_key_handle(&self, req: &CheckKeyHandle) -> Result<(), HIDError> {
let credlist = self.credentials.borrow();
let req_rp_hash = req.rp.hash();
let eligible_cred_iter = credlist.iter().filter(|x| x.rp.hash() == req_rp_hash);
for credential in eligible_cred_iter {
if req.key_handle == credential.id {
return Ok(());
}
}
Err(HIDError::DeviceError)
}
fn client_pin(&self, req: &ClientPIN) -> Result<ClientPinResponse, HIDError> {
match req.subcommand {
PINSubcommand::GetKeyAgreement => {
// We don't need to save, or even know, the private key for the public key returned
// here because we have access to the shared secret derived on the client side.
let (_private, public) = COSEKey::generate(COSEAlgorithm::ECDH_ES_HKDF256)
.map_err(|_| HIDError::DeviceError)?;
Ok(ClientPinResponse {
key_agreement: Some(public),
..Default::default()
})
}
PINSubcommand::GetPinUvAuthTokenUsingUvWithPermissions => {
// TODO: permissions
if !self.is_user_consenting || !self.is_user_verified {
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::OperationDenied,
None,
)));
}
let secret = match self.shared_secret.as_ref() {
Some(secret) => secret,
_ => return Err(HIDError::DeviceError),
};
let encrypted_pin_token = match secret.encrypt(&self.pin_token) {
Ok(token) => token,
_ => return Err(HIDError::DeviceError),
};
Ok(ClientPinResponse {
pin_token: Some(encrypted_pin_token),
..Default::default()
})
}
_ => Err(HIDError::UnsupportedCommand),
}
}
fn get_assertion(&self, req: &GetAssertion) -> Result<Vec<GetAssertionResult>, HIDError> {
// Algorithm 6.2.2 from CTAP 2.1
// 1. zero length pinUvAuthParam
// (not implemented)
// 2. Validate pinUvAuthParam
// Handled by caller
// 3. Initialize "uv" and "up" bits to false
let mut flags = AuthenticatorDataFlags::empty();
// 4. Handle all options
// 4.1 and 4.2
let effective_uv_opt =
req.options.user_verification.unwrap_or(false) && req.pin_uv_auth_param.is_none();
// 4.3
if effective_uv_opt && !self.has_user_verification {
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::InvalidOption,
None,
)));
}
// 4.4 rk
// (not implemented, we don't encode it)
// 4.5
let effective_up_opt = req.options.user_presence.unwrap_or(true);
// 5. alwaysUv
// (not implemented)
// 6. User verification
// TODO: Permissions, (maybe) validate pinUvAuthParam
if self.is_user_verified && (effective_uv_opt || req.pin_uv_auth_param.is_some()) {
flags |= AuthenticatorDataFlags::USER_VERIFIED;
}
// 7. Locate credentials
let credlist = self.credentials.borrow();
let req_rp_hash = req.rp.hash();
let eligible_cred_iter = credlist.iter().filter(|x| x.rp.hash() == req_rp_hash);
// 8. Set up=true if evidence of user interaction was provided in step 6.
// (not applicable, we use pinUvAuthParam)
// 9. User presence test
if effective_up_opt {
if self.is_user_consenting {
flags |= AuthenticatorDataFlags::USER_PRESENT;
} else {
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::UpRequired,
None,
)));
}
}
// 10. Extensions
// (not implemented)
let mut assertions: Vec<GetAssertionResult> = vec![];
if !req.allow_list.is_empty() {
// 11. Non-discoverable credential case
// return at most one assertion matching an allowed credential ID
for credential in eligible_cred_iter {
if req.allow_list.iter().any(|x| x.id == credential.id) {
let mut assertion: GetAssertionResponse =
credential.assert(&req.client_data_hash, flags)?;
if req.allow_list.len() == 1
&& self.max_supported_version() == AuthenticatorVersion::FIDO_2_0
{
// CTAP 2.0 authenticators are allowed to omit the credential ID in the
// response if the allow list contains exactly one entry. This behavior is
// a common source of bugs, e.g. Bug 1864504, so we'll exercise it here.
assertion.credentials = None;
}
assertions.push(GetAssertionResult {
assertion: assertion.into(),
attachment: AuthenticatorAttachment::Unknown,
extensions: Default::default(),
});
break;
}
}
} else {
// 12. Discoverable credential case
// return any number of assertions from credentials bound to this RP ID
for credential in eligible_cred_iter.filter(|x| x.is_discoverable_credential) {
let assertion = credential.assert(&req.client_data_hash, flags)?.into();
assertions.push(GetAssertionResult {
assertion,
attachment: AuthenticatorAttachment::Unknown,
extensions: Default::default(),
});
}
}
if assertions.is_empty() {
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::NoCredentials,
None,
)));
}
Ok(assertions)
}
fn get_info(&self) -> Result<AuthenticatorInfo, HIDError> {
// This is a CTAP2.1 device with internal user verification support
Ok(AuthenticatorInfo {
versions: self.versions.clone(),
options: AuthenticatorOptions {
platform_device: self.transport == "internal",
resident_key: self.has_resident_key,
pin_uv_auth_token: Some(self.has_user_verification),
user_verification: Some(self.has_user_verification),
..Default::default()
},
..Default::default()
})
}
fn get_version(&self, _req: &GetVersion) -> Result<U2FInfo, HIDError> {
Err(HIDError::UnsupportedCommand)
}
fn make_credentials(&self, req: &MakeCredentials) -> Result<MakeCredentialsResult, HIDError> {
// Algorithm 6.1.2 from CTAP 2.1
// 1. zero length pinUvAuthParam
// (not implemented)
// 2. Validate pinUvAuthParam
// Handled by caller
// 3. Validate pubKeyCredParams
if !req
.pub_cred_params
.iter()
.any(|x| x.alg == COSEAlgorithm::ES256)
{
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::UnsupportedAlgorithm,
None,
)));
}
// 4. initialize "uv" and "up" bits to false
let mut flags = AuthenticatorDataFlags::empty();
// 5. process all options
// 5.1 and 5.2
let effective_uv_opt =
req.options.user_verification.unwrap_or(false) && req.pin_uv_auth_param.is_none();
// 5.3
if effective_uv_opt && !self.has_user_verification {
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::InvalidOption,
None,
)));
}
// 5.4
if req.options.resident_key.unwrap_or(false) && !self.has_resident_key {
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::UnsupportedOption,
None,
)));
}
// 5.6 and 5.7
// Nothing to do. We don't provide a way to set up=false.
// 6. alwaysUv option ID
// (not implemented)
// 7. and 8. makeCredUvNotRqd option ID
// (not implemented)
// 9. enterprise attestation
// (not implemented)
// 11. User verification
// TODO: Permissions, (maybe) validate pinUvAuthParam
if self.is_user_verified {
flags |= AuthenticatorDataFlags::USER_VERIFIED;
}
// 12. exclude list
// TODO: credProtect
if req.exclude_list.iter().any(|x| self.has_credential(&x.id)) {
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::CredentialExcluded,
None,
)));
}
// 13. Set up=true if evidence of user interaction was provided in step 11.
// (not applicable, we use pinUvAuthParam)
// 14. User presence test
if self.is_user_consenting {
flags |= AuthenticatorDataFlags::USER_PRESENT;
} else {
return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::UpRequired,
None,
)));
}
// 15. process extensions
let mut extensions = Extension::default();
if req.extensions.min_pin_length == Some(true) {
// a real authenticator would
// 1) return an actual minimum pin length, and
// 2) check the RP ID against an allowlist before providing any data
extensions.min_pin_length = Some(4);
}
if extensions.has_some() {
flags |= AuthenticatorDataFlags::EXTENSION_DATA;
}
// 16. Generate a new credential.
let (private, public) =
COSEKey::generate(COSEAlgorithm::ES256).map_err(|_| HIDError::DeviceError)?;
let counter = 0;
// 17. and 18. Store credential
//
// All of the credentials that we create are "resident"---we store the private key locally,
// and use a random value for the credential ID. The `req.options.resident_key` field
// determines whether we make the credential "discoverable".
let mut id = [0u8; 32];
thread_rng().fill_bytes(&mut id);
self.insert_credential(
&id,
&private,
&req.rp,
req.options.resident_key.unwrap_or(false),
&req.user.clone().unwrap_or_default().id,
counter,
);
// 19. Generate attestation statement
flags |= AuthenticatorDataFlags::ATTESTED;
let auth_data = AuthenticatorData {
rp_id_hash: req.rp.hash(),
flags,
counter,
credential_data: Some(AttestedCredentialData {
aaguid: VIRTUAL_TOKEN_AAGUID,
credential_id: id.to_vec(),
credential_public_key: public,
}),
extensions,
};
let mut data = auth_data.to_vec();
data.extend_from_slice(req.client_data_hash.as_ref());
let sig = ecdsa_p256_sha256_sign_raw(&private, &data).or(Err(HIDError::DeviceError))?;
let att_stmt = AttestationStatement::Packed(AttestationStatementPacked {
alg: COSEAlgorithm::ES256,
sig: sig.as_slice().into(),
attestation_cert: vec![],
});
let result = MakeCredentialsResult {
attachment: AuthenticatorAttachment::Unknown,
att_obj: AttestationObject {
att_stmt,
auth_data,
},
extensions: Default::default(),
};
Ok(result)
}
fn reset(&self, _req: &Reset) -> Result<(), HIDError> {
Err(HIDError::UnsupportedCommand)
}
fn selection(&self, _req: &Selection) -> Result<(), HIDError> {
Err(HIDError::UnsupportedCommand)
}
}
#[xpcom(implement(nsICredentialParameters), atomic)]
struct CredentialParameters {
credential_id: Vec<u8>,
is_resident_credential: bool,
rp_id: String,
private_key: Vec<u8>,
user_handle: Vec<u8>,
sign_count: u32,
}
impl CredentialParameters {
xpcom_method!(get_credential_id => GetCredentialId() -> nsACString);
fn get_credential_id(&self) -> Result<nsCString, nsresult> {
Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD
.encode(&self.credential_id)
.into())
}
xpcom_method!(get_is_resident_credential => GetIsResidentCredential() -> bool);
fn get_is_resident_credential(&self) -> Result<bool, nsresult> {
Ok(self.is_resident_credential)
}
xpcom_method!(get_rp_id => GetRpId() -> nsACString);
fn get_rp_id(&self) -> Result<nsCString, nsresult> {
Ok(nsCString::from(&self.rp_id))
}
xpcom_method!(get_private_key => GetPrivateKey() -> nsACString);
fn get_private_key(&self) -> Result<nsCString, nsresult> {
Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD
.encode(&self.private_key)
.into())
}
xpcom_method!(get_user_handle => GetUserHandle() -> nsACString);
fn get_user_handle(&self) -> Result<nsCString, nsresult> {
Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD
.encode(&self.user_handle)
.into())
}
xpcom_method!(get_sign_count => GetSignCount() -> u32);
fn get_sign_count(&self) -> Result<u32, nsresult> {
Ok(self.sign_count)
}
}
#[xpcom(implement(nsIWebAuthnAutoFillEntry), atomic)]
struct WebAuthnAutoFillEntry {
rp: String,
credential_id: Vec<u8>,
}
impl WebAuthnAutoFillEntry {
xpcom_method!(get_provider => GetProvider() -> u8);
fn get_provider(&self) -> Result<u8, nsresult> {
Ok(nsIWebAuthnAutoFillEntry::PROVIDER_TEST_TOKEN)
}
xpcom_method!(get_user_name => GetUserName() -> nsAString);
fn get_user_name(&self) -> Result<nsString, nsresult> {
Ok(nsString::from("Test User"))
}
xpcom_method!(get_rp_id => GetRpId() -> nsAString);
fn get_rp_id(&self) -> Result<nsString, nsresult> {
Ok(nsString::from(&self.rp))
}
xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
Ok(self.credential_id.as_slice().into())
}
}
#[derive(Default)]
pub(crate) struct TestTokenManager {
state: Arc<Mutex<HashMap<u64, TestToken>>>,
}
impl TestTokenManager {
pub fn new() -> Self {
Default::default()
}
pub fn add_virtual_authenticator(
&self,
protocol: AuthenticatorVersion,
transport: String,
has_resident_key: bool,
has_user_verification: bool,
is_user_consenting: bool,
is_user_verified: bool,
) -> Result<u64, nsresult> {
let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
let token = TestToken::new(
vec![protocol],
transport,
has_resident_key,
has_user_verification,
is_user_consenting,
is_user_verified,
);
loop {
let id = rand::random::<u64>() & 0x1f_ffff_ffff_ffffu64; // Make the id safe for JS (53 bits)
match guard.deref_mut().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()
.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()
.get_mut(&authenticator_id)
.ok_or(NS_ERROR_INVALID_ARG)?;
let rp = RelyingParty::from(rp_id);
token.insert_credential(
id,
privkey,
&rp,
is_resident_credential,
user_handle,
sign_count,
);
Ok(())
}
pub fn get_credentials(
&self,
authenticator_id: u64,
) -> Result<ThinVec<Option<RefPtr<nsICredentialParameters>>>, nsresult> {
let mut guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
let token = guard
.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 credential_parameters = CredentialParameters::allocate(InitCredentialParameters {
credential_id: credential.id.clone(),
is_resident_credential: credential.is_discoverable_credential,
rp_id: credential.rp.id.clone(),
private_key: credential.privkey.clone(),
user_handle: credential.user_handle.clone(),
sign_count: credential.sign_count.load(Ordering::Relaxed),
})
.query_interface::<nsICredentialParameters>()
.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()
.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()
.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 token = guard
.deref_mut()
.get_mut(&authenticator_id)
.ok_or(NS_ERROR_INVALID_ARG)?;
token.is_user_verified = is_user_verified;
Ok(())
}
pub fn register(
&self,
_timeout_ms: u64,
ctap_args: RegisterArgs,
status: Sender<StatusUpdate>,
callback: StateCallback<Result<RegisterResult, AuthenticatorError>>,
) {
if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
return;
}
let state_obj = self.state.clone();
// Registration doesn't currently block, but it might in a future version, so we run it on
// a background thread.
let _ = RunnableBuilder::new("TestTokenManager::register", move || {
// TODO(Bug 1854278) We should actually run one thread per token here
// and attempt to fulfill this request in parallel.
for token in state_obj.lock().unwrap().values_mut() {
let _ = token.init();
if ctap2::register(
token,
ctap_args.clone(),
status.clone(),
callback.clone(),
&|| true,
) {
// callback was called
return;
}
}
// Send an error, if the callback wasn't called already.
callback.call(Err(AuthenticatorError::U2FToken(U2FTokenError::NotAllowed)));
})
.may_block(true)
.dispatch_background_task();
}
pub fn sign(
&self,
_timeout_ms: u64,
ctap_args: SignArgs,
status: Sender<StatusUpdate>,
callback: StateCallback<Result<SignResult, AuthenticatorError>>,
) {
if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
return;
}
let state_obj = self.state.clone();
// Signing can block during signature selection, so we need to run it on a background thread.
let _ = RunnableBuilder::new("TestTokenManager::sign", move || {
// TODO(Bug 1854278) We should actually run one thread per token here
// and attempt to fulfill this request in parallel.
for token in state_obj.lock().unwrap().values_mut() {
let _ = token.init();
if ctap2::sign(
token,
ctap_args.clone(),
status.clone(),
callback.clone(),
&|| true,
) {
// callback was called
return;
}
}
// Send an error, if the callback wasn't called already.
callback.call(Err(AuthenticatorError::U2FToken(U2FTokenError::NotAllowed)));
})
.may_block(true)
.dispatch_background_task();
}
pub fn has_platform_authenticator(&self) -> bool {
if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
return false;
}
for token in self.state.lock().unwrap().values_mut() {
let _ = token.init();
if token.transport.as_str() == "internal" {
return true;
}
}
false
}
pub fn get_autofill_entries(
&self,
rp_id: &str,
credential_filter: &Vec<PublicKeyCredentialDescriptor>,
) -> Result<ThinVec<Option<RefPtr<nsIWebAuthnAutoFillEntry>>>, nsresult> {
let guard = self.state.lock().map_err(|_| NS_ERROR_FAILURE)?;
let mut entries = ThinVec::new();
for token in guard.values() {
let credentials = token.get_credentials();
for credential in credentials.deref() {
// The relying party ID must match.
if !rp_id.eq(&credential.rp.id) {
continue;
}
// Only discoverable credentials are admissible.
if !credential.is_discoverable_credential {
continue;
}
// Only credentials listed in the credential filter (if it is
// non-empty) are admissible.
if credential_filter.len() > 0
&& credential_filter
.iter()
.find(|cred| cred.id == credential.id)
.is_none()
{
continue;
}
let entry = WebAuthnAutoFillEntry::allocate(InitWebAuthnAutoFillEntry {
rp: credential.rp.id.clone(),
credential_id: credential.id.clone(),
})
.query_interface::<nsIWebAuthnAutoFillEntry>()
.ok_or(NS_ERROR_FAILURE)?;
entries.push(Some(entry));
}
}
Ok(entries)
}
}