/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* 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,
HmacSecretResponse,
},
client_data::ClientDataHash,
commands::{
client_pin::{ClientPIN, ClientPinResponse, PINSubcommand},
get_assertion::{
GetAssertion, GetAssertionResponse, GetAssertionResult, HmacGetSecretOrPrf,
HmacSecretExtension,
},
get_info::{AuthenticatorInfo, AuthenticatorOptions, AuthenticatorVersion},
get_version::{GetVersion, U2FInfo},
make_credentials::{HmacCreateSecretOrPrf, 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};
let user = Some(PublicKeyCredentialUserEntity {
id: self.user_handle.clone(),
..Default::default()
});
letmut 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))?;
#[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 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 = matchself.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),
}
}
// 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 { ifself.is_user_consenting {
flags |= AuthenticatorDataFlags::USER_PRESENT;
} else { return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::UpRequired,
None,
)));
}
}
// 10. Extensions let hmac_secret_response = match &req.extensions.hmac_secret {
Some(HmacGetSecretOrPrf::Prf(HmacSecretExtension {
salt1, salt2: None, ..
})) => { // Not much point in using an actual PRF here, the identity function // will work since salt1 is guaranteed to be 32 bytes. letmut eval = vec![0u8; 32];
eval[..].copy_from_slice(salt1); self.get_shared_secret()
.map(|secret| secret.encrypt(&eval).ok())
.flatten()
}
Some(HmacGetSecretOrPrf::Prf(HmacSecretExtension {
salt1,
salt2: Some(salt2),
..
})) => { // Likewise, the identity function is fine for tests. letmut eval = vec![0u8; 64];
eval[0..32].copy_from_slice(salt1);
eval[32..64].copy_from_slice(salt2); self.get_shared_secret()
.map(|secret| secret.encrypt(&eval).ok())
.flatten()
}
_ => None,
};
letmut 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) { letmut 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;
}
assertion.auth_data.extensions = Extension::default();
assertion.auth_data.extensions.hmac_secret = match &hmac_secret_response {
Some(resp) => Some(HmacSecretResponse::Secret(resp.clone())),
None => 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) { letmut assertion: GetAssertionResponse =
credential.assert(&req.client_data_hash, flags)?.into();
assertion.auth_data.extensions = Extension::default();
assertion.auth_data.extensions.hmac_secret = match &hmac_secret_response {
Some(resp) => Some(HmacSecretResponse::Secret(resp.clone())),
None => None,
};
assertions.push(GetAssertionResult {
assertion: assertion.into(),
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()
})
}
// 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 ifself.is_user_consenting {
flags |= AuthenticatorDataFlags::USER_PRESENT;
} else { return Err(HIDError::Command(CommandError::StatusCode(
StatusCode::UpRequired,
None,
)));
}
// 15. process extensions letmut 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". letmut 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,
);
// 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();
}
// 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();
}
pubfn has_platform_authenticator(&self) -> bool { if !static_prefs::pref!("security.webauth.webauthn_enable_softtoken") { returnfalse;
}
for token inself.state.lock().unwrap().values_mut() { let _ = token.init(); if token.transport.as_str() == "internal" { returntrue;
}
}
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)
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.15 Sekunden
(vorverarbeitet am 2026-06-19)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.