Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/webauthn/authrs_bridge/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 34 kB image not shown  

Quelle  test_token.rs   Sprache: unbekannt

 
/* 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,
        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};

// 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
        // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-makeCred-authnr-alg

        // 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
        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.
                let mut 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.
                let mut 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,
        };

        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;
                    }
                    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) {
                let mut 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()
        })
    }

    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
        // https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-makeCred-authnr-alg

        // 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 let Some(req_hmac_or_prf) = &req.extensions.hmac_secret {
            match req_hmac_or_prf {
                HmacCreateSecretOrPrf::HmacCreateSecret(true) | HmacCreateSecretOrPrf::Prf => {
                    extensions.hmac_secret = Some(HmacSecretResponse::Confirmed(true));
                }
                _ => (),
            }
        }

        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)
    }
}

[ Dauer der Verarbeitung: 0.6 Sekunden  (vorverarbeitet)  ]