tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

lib.rs (68521B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #[macro_use]
      6 extern crate log;
      7 
      8 #[macro_use]
      9 extern crate xpcom;
     10 
     11 use authenticator::{
     12    authenticatorservice::{RegisterArgs, SignArgs},
     13    ctap2::attestation::{AAGuid, AttestationObject, AttestationStatement},
     14    ctap2::commands::{get_info::AuthenticatorVersion, PinUvAuthResult},
     15    ctap2::server::{
     16        AuthenticationExtensionsClientInputs, AuthenticationExtensionsPRFInputs,
     17        AuthenticationExtensionsPRFOutputs, AuthenticationExtensionsPRFValues,
     18        AuthenticatorAttachment, CredentialProtectionPolicy, PublicKeyCredentialDescriptor,
     19        PublicKeyCredentialParameters, PublicKeyCredentialUserEntity, RelyingParty,
     20        ResidentKeyRequirement, UserVerificationRequirement,
     21    },
     22    errors::AuthenticatorError,
     23    statecallback::StateCallback,
     24    AuthenticatorInfo, BioEnrollmentResult, CredentialManagementResult, InteractiveRequest,
     25    ManageResult, Pin, RegisterResult, SignResult, StateMachine, StatusPinUv, StatusUpdate,
     26 };
     27 use base64::Engine;
     28 use cstr::cstr;
     29 use moz_task::{get_main_thread, RunnableBuilder};
     30 use nserror::{
     31    nsresult, NS_ERROR_DOM_ABORT_ERR, NS_ERROR_DOM_INVALID_STATE_ERR, NS_ERROR_DOM_NOT_ALLOWED_ERR,
     32    NS_ERROR_DOM_OPERATION_ERR, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_NOT_AVAILABLE,
     33    NS_ERROR_NOT_IMPLEMENTED, NS_ERROR_NULL_POINTER, NS_OK,
     34 };
     35 use nsstring::{nsACString, nsAString, nsCString, nsString};
     36 use serde::Serialize;
     37 use serde_json::json;
     38 use std::cell::RefCell;
     39 use std::collections::HashMap;
     40 use std::fmt::Write;
     41 use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
     42 use std::sync::{Arc, Mutex, MutexGuard};
     43 use thin_vec::{thin_vec, ThinVec};
     44 use xpcom::interfaces::{
     45    nsICredentialParameters, nsIObserverService, nsIWebAuthnAttObj, nsIWebAuthnAutoFillEntry,
     46    nsIWebAuthnRegisterArgs, nsIWebAuthnRegisterPromise, nsIWebAuthnRegisterResult,
     47    nsIWebAuthnService, nsIWebAuthnSignArgs, nsIWebAuthnSignPromise, nsIWebAuthnSignResult,
     48 };
     49 use xpcom::{xpcom_method, RefPtr};
     50 mod about_webauthn_controller;
     51 use about_webauthn_controller::*;
     52 mod test_token;
     53 use test_token::TestTokenManager;
     54 
     55 fn authrs_to_nserror(e: AuthenticatorError) -> nsresult {
     56    match e {
     57        AuthenticatorError::CredentialExcluded => NS_ERROR_DOM_INVALID_STATE_ERR,
     58        _ => NS_ERROR_DOM_NOT_ALLOWED_ERR,
     59    }
     60 }
     61 
     62 fn should_cancel_prompts<T>(result: &Result<T, AuthenticatorError>) -> bool {
     63    match result {
     64        Err(AuthenticatorError::CredentialExcluded) | Err(AuthenticatorError::PinError(_)) => false,
     65        _ => true,
     66    }
     67 }
     68 
     69 // Using serde(tag="type") makes it so that, for example, BrowserPromptType::Cancel is serialized
     70 // as '{ type: "cancel" }', and BrowserPromptType::PinInvalid { retries: 5 } is serialized as
     71 // '{type: "pin-invalid", retries: 5}'.
     72 #[derive(Serialize)]
     73 #[serde(tag = "type", rename_all = "kebab-case")]
     74 enum BrowserPromptType<'a> {
     75    AlreadyRegistered,
     76    Cancel,
     77    DeviceBlocked,
     78    PinAuthBlocked,
     79    PinNotSet,
     80    Presence,
     81    SelectDevice,
     82    UvBlocked,
     83    PinRequired,
     84    SelectedDevice {
     85        auth_info: Option<AuthenticatorInfo>,
     86    },
     87    PinInvalid {
     88        retries: Option<u8>,
     89    },
     90    PinIsTooLong,
     91    PinIsTooShort,
     92    UvInvalid {
     93        retries: Option<u8>,
     94    },
     95    SelectSignResult {
     96        entities: &'a [PublicKeyCredentialUserEntity],
     97    },
     98    ListenSuccess,
     99    ListenError {
    100        error: Box<BrowserPromptType<'a>>,
    101    },
    102    CredentialManagementUpdate {
    103        result: CredentialManagementResult,
    104    },
    105    BioEnrollmentUpdate {
    106        result: BioEnrollmentResult,
    107    },
    108    UnknownError,
    109 }
    110 
    111 #[derive(Debug)]
    112 enum PromptTarget {
    113    Browser,
    114    AboutPage,
    115 }
    116 
    117 #[derive(Serialize)]
    118 struct BrowserPromptMessage<'a> {
    119    prompt: BrowserPromptType<'a>,
    120    tid: u64,
    121    origin: Option<&'a str>,
    122    #[serde(rename = "browsingContextId")]
    123    browsing_context_id: Option<u64>,
    124 }
    125 
    126 fn notify_observers(prompt_target: PromptTarget, json: nsString) -> Result<(), nsresult> {
    127    let main_thread = get_main_thread()?;
    128    let target = match prompt_target {
    129        PromptTarget::Browser => cstr!("webauthn-prompt"),
    130        PromptTarget::AboutPage => cstr!("about-webauthn-prompt"),
    131    };
    132 
    133    RunnableBuilder::new("AuthrsService::send_prompt", move || {
    134        if let Ok(obs_svc) = xpcom::components::Observer::service::<nsIObserverService>() {
    135            unsafe {
    136                obs_svc.NotifyObservers(std::ptr::null(), target.as_ptr(), json.as_ptr());
    137            }
    138        }
    139    })
    140    .dispatch(main_thread.coerce())
    141 }
    142 
    143 fn send_prompt(
    144    prompt: BrowserPromptType,
    145    tid: u64,
    146    origin: Option<&str>,
    147    browsing_context_id: Option<u64>,
    148 ) -> Result<(), nsresult> {
    149    let mut json = nsString::new();
    150    write!(
    151        json,
    152        "{}",
    153        json!(&BrowserPromptMessage {
    154            prompt,
    155            tid,
    156            origin,
    157            browsing_context_id
    158        })
    159    )
    160    .or(Err(NS_ERROR_FAILURE))?;
    161    notify_observers(PromptTarget::Browser, json)
    162 }
    163 
    164 fn cancel_prompts(tid: u64) -> Result<(), nsresult> {
    165    send_prompt(BrowserPromptType::Cancel, tid, None, None)?;
    166    Ok(())
    167 }
    168 
    169 #[xpcom(implement(nsIWebAuthnRegisterResult), atomic)]
    170 pub struct WebAuthnRegisterResult {
    171    // result is only borrowed mutably in `Anonymize`.
    172    result: RefCell<RegisterResult>,
    173 }
    174 
    175 impl WebAuthnRegisterResult {
    176    xpcom_method!(get_client_data_json => GetClientDataJSON() -> nsACString);
    177    fn get_client_data_json(&self) -> Result<nsCString, nsresult> {
    178        Err(NS_ERROR_NOT_AVAILABLE)
    179    }
    180 
    181    xpcom_method!(get_attestation_object => GetAttestationObject() -> ThinVec<u8>);
    182    fn get_attestation_object(&self) -> Result<ThinVec<u8>, nsresult> {
    183        let mut out = ThinVec::new();
    184        serde_cbor::to_writer(&mut out, &self.result.borrow().att_obj).or(Err(NS_ERROR_FAILURE))?;
    185        Ok(out)
    186    }
    187 
    188    xpcom_method!(get_attestation_consent_prompt_shown => GetAttestationConsentPromptShown() -> bool);
    189    fn get_attestation_consent_prompt_shown(&self) -> Result<bool, nsresult> {
    190        Ok(false)
    191    }
    192 
    193    xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
    194    fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
    195        let Some(credential_data) = &self.result.borrow().att_obj.auth_data.credential_data else {
    196            return Err(NS_ERROR_FAILURE);
    197        };
    198        Ok(credential_data.credential_id.as_slice().into())
    199    }
    200 
    201    xpcom_method!(get_transports => GetTransports() -> ThinVec<nsString>);
    202    fn get_transports(&self) -> Result<ThinVec<nsString>, nsresult> {
    203        // The list that we return here might be included in a future GetAssertion request as a
    204        // hint as to which transports to try. In production, we only support the "usb" transport.
    205        // In tests, the result is not very important, but we can at least return "internal" if
    206        // we're simulating platform attachment.
    207        if static_prefs::pref!("security.webauth.webauthn_enable_softtoken")
    208            && self.result.borrow().attachment == AuthenticatorAttachment::Platform
    209        {
    210            Ok(thin_vec![nsString::from("internal")])
    211        } else {
    212            Ok(thin_vec![nsString::from("usb")])
    213        }
    214    }
    215 
    216    xpcom_method!(get_hmac_create_secret => GetHmacCreateSecret() -> bool);
    217    fn get_hmac_create_secret(&self) -> Result<bool, nsresult> {
    218        let Some(hmac_create_secret) = self.result.borrow().extensions.hmac_create_secret else {
    219            return Err(NS_ERROR_NOT_AVAILABLE);
    220        };
    221        Ok(hmac_create_secret)
    222    }
    223 
    224    xpcom_method!(get_large_blob_supported => GetLargeBlobSupported() -> bool);
    225    fn get_large_blob_supported(&self) -> Result<bool, nsresult> {
    226        Err(NS_ERROR_NOT_AVAILABLE)
    227    }
    228 
    229    xpcom_method!(get_prf_enabled => GetPrfEnabled() -> bool);
    230    fn get_prf_enabled(&self) -> Result<bool, nsresult> {
    231        match self.result.borrow().extensions.prf {
    232            Some(AuthenticationExtensionsPRFOutputs {
    233                enabled: Some(prf_enabled),
    234                ..
    235            }) => Ok(prf_enabled),
    236            _ => Err(NS_ERROR_NOT_AVAILABLE),
    237        }
    238    }
    239 
    240    xpcom_method!(get_prf_results_first => GetPrfResultsFirst() -> ThinVec<u8>);
    241    fn get_prf_results_first(&self) -> Result<ThinVec<u8>, nsresult> {
    242        match &self.result.borrow().extensions.prf {
    243            Some(AuthenticationExtensionsPRFOutputs {
    244                results: Some(AuthenticationExtensionsPRFValues { first, .. }),
    245                ..
    246            }) => Ok(first.as_slice().into()),
    247            _ => Err(NS_ERROR_NOT_AVAILABLE),
    248        }
    249    }
    250 
    251    xpcom_method!(get_prf_results_second => GetPrfResultsSecond() -> ThinVec<u8>);
    252    fn get_prf_results_second(&self) -> Result<ThinVec<u8>, nsresult> {
    253        match &self.result.borrow().extensions.prf {
    254            Some(AuthenticationExtensionsPRFOutputs {
    255                results:
    256                    Some(AuthenticationExtensionsPRFValues {
    257                        second: Some(second),
    258                        ..
    259                    }),
    260                ..
    261            }) => Ok(second.as_slice().into()),
    262            _ => Err(NS_ERROR_NOT_AVAILABLE),
    263        }
    264    }
    265 
    266    xpcom_method!(get_cred_props_rk => GetCredPropsRk() -> bool);
    267    fn get_cred_props_rk(&self) -> Result<bool, nsresult> {
    268        let Some(cred_props) = &self.result.borrow().extensions.cred_props else {
    269            return Err(NS_ERROR_NOT_AVAILABLE);
    270        };
    271        Ok(cred_props.rk)
    272    }
    273 
    274    xpcom_method!(set_cred_props_rk => SetCredPropsRk(aCredPropsRk: bool));
    275    fn set_cred_props_rk(&self, _cred_props_rk: bool) -> Result<(), nsresult> {
    276        Err(NS_ERROR_NOT_IMPLEMENTED)
    277    }
    278 
    279    xpcom_method!(get_authenticator_attachment => GetAuthenticatorAttachment() -> nsAString);
    280    fn get_authenticator_attachment(&self) -> Result<nsString, nsresult> {
    281        match self.result.borrow().attachment {
    282            AuthenticatorAttachment::CrossPlatform => Ok(nsString::from("cross-platform")),
    283            AuthenticatorAttachment::Platform => Ok(nsString::from("platform")),
    284            AuthenticatorAttachment::Unknown => Err(NS_ERROR_NOT_AVAILABLE),
    285        }
    286    }
    287 
    288    xpcom_method!(has_identifying_attestation => HasIdentifyingAttestation() -> bool);
    289    fn has_identifying_attestation(&self) -> Result<bool, nsresult> {
    290        if self.result.borrow().att_obj.att_stmt != AttestationStatement::None {
    291            return Ok(true);
    292        }
    293        if let Some(data) = &self.result.borrow().att_obj.auth_data.credential_data {
    294            return Ok(data.aaguid != AAGuid::default());
    295        }
    296        Ok(false)
    297    }
    298 
    299    xpcom_method!(anonymize => Anonymize());
    300    fn anonymize(&self) -> Result<nsresult, nsresult> {
    301        self.result.borrow_mut().att_obj.anonymize();
    302        Ok(NS_OK)
    303    }
    304 }
    305 
    306 #[xpcom(implement(nsIWebAuthnAttObj), atomic)]
    307 pub struct WebAuthnAttObj {
    308    att_obj: AttestationObject,
    309 }
    310 
    311 impl WebAuthnAttObj {
    312    xpcom_method!(get_attestation_object => GetAttestationObject() -> ThinVec<u8>);
    313    fn get_attestation_object(&self) -> Result<ThinVec<u8>, nsresult> {
    314        let mut out = ThinVec::new();
    315        serde_cbor::to_writer(&mut out, &self.att_obj).or(Err(NS_ERROR_FAILURE))?;
    316        Ok(out)
    317    }
    318 
    319    xpcom_method!(get_authenticator_data => GetAuthenticatorData() -> ThinVec<u8>);
    320    fn get_authenticator_data(&self) -> Result<ThinVec<u8>, nsresult> {
    321        // TODO(https://github.com/mozilla/authenticator-rs/issues/302) use to_writer
    322        Ok(self.att_obj.auth_data.to_vec().into())
    323    }
    324 
    325    xpcom_method!(get_public_key => GetPublicKey() -> ThinVec<u8>);
    326    fn get_public_key(&self) -> Result<ThinVec<u8>, nsresult> {
    327        let Some(credential_data) = &self.att_obj.auth_data.credential_data else {
    328            return Err(NS_ERROR_FAILURE);
    329        };
    330        Ok(credential_data
    331            .credential_public_key
    332            .der_spki()
    333            .or(Err(NS_ERROR_NOT_AVAILABLE))?
    334            .into())
    335    }
    336 
    337    xpcom_method!(get_public_key_algorithm => GetPublicKeyAlgorithm() -> i32);
    338    fn get_public_key_algorithm(&self) -> Result<i32, nsresult> {
    339        let Some(credential_data) = &self.att_obj.auth_data.credential_data else {
    340            return Err(NS_ERROR_FAILURE);
    341        };
    342        // safe to cast to i32 by inspection of defined values
    343        Ok(credential_data.credential_public_key.alg as i32)
    344    }
    345 
    346    xpcom_method!(is_identifying => IsIdentifying() -> bool);
    347    fn is_identifying(&self) -> Result<bool, nsresult> {
    348        if self.att_obj.att_stmt != AttestationStatement::None {
    349            return Ok(true);
    350        }
    351        if let Some(data) = &self.att_obj.auth_data.credential_data {
    352            return Ok(data.aaguid != AAGuid::default());
    353        }
    354        Ok(false)
    355    }
    356 }
    357 
    358 #[xpcom(implement(nsIWebAuthnSignResult), atomic)]
    359 pub struct WebAuthnSignResult {
    360    result: SignResult,
    361 }
    362 
    363 impl WebAuthnSignResult {
    364    xpcom_method!(get_client_data_json => GetClientDataJSON() -> nsACString);
    365    fn get_client_data_json(&self) -> Result<nsCString, nsresult> {
    366        Err(NS_ERROR_NOT_AVAILABLE)
    367    }
    368 
    369    xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
    370    fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
    371        let Some(cred) = &self.result.assertion.credentials else {
    372            return Err(NS_ERROR_FAILURE);
    373        };
    374        Ok(cred.id.as_slice().into())
    375    }
    376 
    377    xpcom_method!(get_signature => GetSignature() -> ThinVec<u8>);
    378    fn get_signature(&self) -> Result<ThinVec<u8>, nsresult> {
    379        Ok(self.result.assertion.signature.as_slice().into())
    380    }
    381 
    382    xpcom_method!(get_authenticator_data => GetAuthenticatorData() -> ThinVec<u8>);
    383    fn get_authenticator_data(&self) -> Result<ThinVec<u8>, nsresult> {
    384        Ok(self.result.assertion.auth_data.to_vec().into())
    385    }
    386 
    387    xpcom_method!(get_user_handle => GetUserHandle() -> ThinVec<u8>);
    388    fn get_user_handle(&self) -> Result<ThinVec<u8>, nsresult> {
    389        let Some(user) = &self.result.assertion.user else {
    390            return Err(NS_ERROR_NOT_AVAILABLE);
    391        };
    392        Ok(user.id.as_slice().into())
    393    }
    394 
    395    xpcom_method!(get_user_name => GetUserName() -> nsACString);
    396    fn get_user_name(&self) -> Result<nsCString, nsresult> {
    397        let Some(user) = &self.result.assertion.user else {
    398            return Err(NS_ERROR_NOT_AVAILABLE);
    399        };
    400        let Some(name) = &user.name else {
    401            return Err(NS_ERROR_NOT_AVAILABLE);
    402        };
    403        Ok(nsCString::from(name))
    404    }
    405 
    406    xpcom_method!(get_authenticator_attachment => GetAuthenticatorAttachment() -> nsAString);
    407    fn get_authenticator_attachment(&self) -> Result<nsString, nsresult> {
    408        match self.result.attachment {
    409            AuthenticatorAttachment::CrossPlatform => Ok(nsString::from("cross-platform")),
    410            AuthenticatorAttachment::Platform => Ok(nsString::from("platform")),
    411            AuthenticatorAttachment::Unknown => Err(NS_ERROR_NOT_AVAILABLE),
    412        }
    413    }
    414 
    415    xpcom_method!(get_used_app_id => GetUsedAppId() -> bool);
    416    fn get_used_app_id(&self) -> Result<bool, nsresult> {
    417        self.result.extensions.app_id.ok_or(NS_ERROR_NOT_AVAILABLE)
    418    }
    419 
    420    xpcom_method!(set_used_app_id => SetUsedAppId(aUsedAppId: bool));
    421    fn set_used_app_id(&self, _used_app_id: bool) -> Result<(), nsresult> {
    422        Err(NS_ERROR_NOT_IMPLEMENTED)
    423    }
    424 
    425    xpcom_method!(get_large_blob_value => GetLargeBlobValue() -> ThinVec<u8>);
    426    fn get_large_blob_value(&self) -> Result<ThinVec<u8>, nsresult> {
    427        Err(NS_ERROR_NOT_AVAILABLE)
    428    }
    429 
    430    xpcom_method!(get_large_blob_written => GetLargeBlobWritten() -> bool);
    431    fn get_large_blob_written(&self) -> Result<bool, nsresult> {
    432        Err(NS_ERROR_NOT_AVAILABLE)
    433    }
    434 
    435    xpcom_method!(get_prf_maybe => GetPrfMaybe() -> bool);
    436    /// Return true if a PRF output is present, even if all attributes are absent.
    437    fn get_prf_maybe(&self) -> Result<bool, nsresult> {
    438        Ok(self.result.extensions.prf.is_some())
    439    }
    440 
    441    xpcom_method!(get_prf_results_first => GetPrfResultsFirst() -> ThinVec<u8>);
    442    fn get_prf_results_first(&self) -> Result<ThinVec<u8>, nsresult> {
    443        match &self.result.extensions.prf {
    444            Some(AuthenticationExtensionsPRFOutputs {
    445                results: Some(AuthenticationExtensionsPRFValues { first, .. }),
    446                ..
    447            }) => Ok(first.as_slice().into()),
    448            _ => Err(NS_ERROR_NOT_AVAILABLE),
    449        }
    450    }
    451 
    452    xpcom_method!(get_prf_results_second => GetPrfResultsSecond() -> ThinVec<u8>);
    453    fn get_prf_results_second(&self) -> Result<ThinVec<u8>, nsresult> {
    454        match &self.result.extensions.prf {
    455            Some(AuthenticationExtensionsPRFOutputs {
    456                results:
    457                    Some(AuthenticationExtensionsPRFValues {
    458                        second: Some(second),
    459                        ..
    460                    }),
    461                ..
    462            }) => Ok(second.as_slice().into()),
    463            _ => Err(NS_ERROR_NOT_AVAILABLE),
    464        }
    465    }
    466 }
    467 
    468 // A transaction may create a channel to ask a user for additional input, e.g. a PIN. The Sender
    469 // component of this channel is sent to an AuthrsServide in a StatusUpdate. AuthrsService
    470 // caches the sender along with the expected (u64) transaction ID, which is used as a consistency
    471 // check in callbacks.
    472 type PinReceiver = Option<(u64, Sender<Pin>)>;
    473 type SelectionReceiver = Option<(u64, Sender<Option<usize>>)>;
    474 
    475 fn status_callback(
    476    status_rx: Receiver<StatusUpdate>,
    477    tid: u64,
    478    origin: &String,
    479    browsing_context_id: u64,
    480    transaction: Arc<Mutex<Option<TransactionState>>>, /* Shared with an AuthrsService */
    481 ) -> Result<(), nsresult> {
    482    let origin = Some(origin.as_str());
    483    let browsing_context_id = Some(browsing_context_id);
    484    loop {
    485        match status_rx.recv() {
    486            Ok(StatusUpdate::SelectDeviceNotice) => {
    487                debug!("STATUS: Please select a device by touching one of them.");
    488                send_prompt(
    489                    BrowserPromptType::SelectDevice,
    490                    tid,
    491                    origin,
    492                    browsing_context_id,
    493                )?;
    494            }
    495            Ok(StatusUpdate::PresenceRequired) => {
    496                debug!("STATUS: Waiting for user presence");
    497                send_prompt(
    498                    BrowserPromptType::Presence,
    499                    tid,
    500                    origin,
    501                    browsing_context_id,
    502                )?;
    503            }
    504            Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
    505                let mut guard = transaction.lock().unwrap();
    506                let Some(transaction) = guard.as_mut() else {
    507                    warn!("STATUS: received status update after end of transaction.");
    508                    break;
    509                };
    510                transaction.pin_receiver.replace((tid, sender));
    511                send_prompt(
    512                    BrowserPromptType::PinRequired,
    513                    tid,
    514                    origin,
    515                    browsing_context_id,
    516                )?;
    517            }
    518            Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, retries))) => {
    519                let mut guard = transaction.lock().unwrap();
    520                let Some(transaction) = guard.as_mut() else {
    521                    warn!("STATUS: received status update after end of transaction.");
    522                    break;
    523                };
    524                transaction.pin_receiver.replace((tid, sender));
    525                send_prompt(
    526                    BrowserPromptType::PinInvalid { retries },
    527                    tid,
    528                    origin,
    529                    browsing_context_id,
    530                )?;
    531            }
    532            Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
    533                send_prompt(
    534                    BrowserPromptType::PinAuthBlocked,
    535                    tid,
    536                    origin,
    537                    browsing_context_id,
    538                )?;
    539            }
    540            Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
    541                send_prompt(
    542                    BrowserPromptType::DeviceBlocked,
    543                    tid,
    544                    origin,
    545                    browsing_context_id,
    546                )?;
    547            }
    548            Ok(StatusUpdate::PinUvError(StatusPinUv::PinNotSet)) => {
    549                send_prompt(
    550                    BrowserPromptType::PinNotSet,
    551                    tid,
    552                    origin,
    553                    browsing_context_id,
    554                )?;
    555            }
    556            Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(retries))) => {
    557                send_prompt(
    558                    BrowserPromptType::UvInvalid { retries },
    559                    tid,
    560                    origin,
    561                    browsing_context_id,
    562                )?;
    563            }
    564            Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
    565                send_prompt(
    566                    BrowserPromptType::UvBlocked,
    567                    tid,
    568                    origin,
    569                    browsing_context_id,
    570                )?;
    571            }
    572            Ok(StatusUpdate::PinUvError(StatusPinUv::PinIsTooShort))
    573            | Ok(StatusUpdate::PinUvError(StatusPinUv::PinIsTooLong(..))) => {
    574                // These should never happen.
    575                warn!("STATUS: Got unexpected StatusPinUv-error.");
    576            }
    577            Ok(StatusUpdate::InteractiveManagement(_)) => {
    578                debug!("STATUS: interactive management");
    579            }
    580            Ok(StatusUpdate::SelectResultNotice(sender, entities)) => {
    581                debug!("STATUS: select result notice");
    582                let mut guard = transaction.lock().unwrap();
    583                let Some(transaction) = guard.as_mut() else {
    584                    warn!("STATUS: received status update after end of transaction.");
    585                    break;
    586                };
    587                transaction.selection_receiver.replace((tid, sender));
    588                send_prompt(
    589                    BrowserPromptType::SelectSignResult {
    590                        entities: &entities,
    591                    },
    592                    tid,
    593                    origin,
    594                    browsing_context_id,
    595                )?;
    596            }
    597            Err(RecvError) => {
    598                debug!("STATUS: end");
    599                break;
    600            }
    601        }
    602    }
    603    Ok(())
    604 }
    605 
    606 #[derive(Clone)]
    607 struct RegisterPromise(RefPtr<nsIWebAuthnRegisterPromise>);
    608 
    609 impl RegisterPromise {
    610    fn resolve_or_reject(&self, result: Result<RegisterResult, nsresult>) -> Result<(), nsresult> {
    611        match result {
    612            Ok(result) => {
    613                let wrapped_result = WebAuthnRegisterResult::allocate(InitWebAuthnRegisterResult {
    614                    result: RefCell::new(result),
    615                })
    616                .query_interface::<nsIWebAuthnRegisterResult>()
    617                .ok_or(NS_ERROR_FAILURE)?;
    618                unsafe { self.0.Resolve(wrapped_result.coerce()) };
    619            }
    620            Err(result) => {
    621                unsafe { self.0.Reject(result) };
    622            }
    623        }
    624        Ok(())
    625    }
    626 }
    627 
    628 #[derive(Clone)]
    629 struct SignPromise(RefPtr<nsIWebAuthnSignPromise>);
    630 
    631 impl SignPromise {
    632    fn resolve_or_reject(&self, result: Result<SignResult, nsresult>) -> Result<(), nsresult> {
    633        match result {
    634            Ok(result) => {
    635                let wrapped_result =
    636                    WebAuthnSignResult::allocate(InitWebAuthnSignResult { result })
    637                        .query_interface::<nsIWebAuthnSignResult>()
    638                        .ok_or(NS_ERROR_FAILURE)?;
    639                unsafe { self.0.Resolve(wrapped_result.coerce()) };
    640            }
    641            Err(result) => {
    642                unsafe { self.0.Reject(result) };
    643            }
    644        }
    645        Ok(())
    646    }
    647 }
    648 
    649 #[derive(Clone)]
    650 enum TransactionPromise {
    651    Listen,
    652    Register(RegisterPromise),
    653    Sign(SignPromise),
    654 }
    655 
    656 impl TransactionPromise {
    657    fn reject(&self, err: nsresult) -> Result<(), nsresult> {
    658        match self {
    659            TransactionPromise::Listen => Ok(()),
    660            TransactionPromise::Register(promise) => promise.resolve_or_reject(Err(err)),
    661            TransactionPromise::Sign(promise) => promise.resolve_or_reject(Err(err)),
    662        }
    663    }
    664 }
    665 
    666 enum TransactionArgs {
    667    Sign(/* timeout */ u64, SignArgs),
    668 }
    669 
    670 struct TransactionState {
    671    tid: u64,
    672    browsing_context_id: u64,
    673    pending_args: Option<TransactionArgs>,
    674    promise: TransactionPromise,
    675    pin_receiver: PinReceiver,
    676    selection_receiver: SelectionReceiver,
    677    interactive_receiver: InteractiveManagementReceiver,
    678    puat_cache: Option<PinUvAuthResult>, // Cached credential to avoid repeated PIN-entries
    679 }
    680 
    681 // AuthrsService provides an nsIWebAuthnService built on top of authenticator-rs.
    682 #[xpcom(implement(nsIWebAuthnService), atomic)]
    683 pub struct AuthrsService {
    684    usb_token_manager: Mutex<StateMachine>,
    685    test_token_manager: TestTokenManager,
    686    transaction: Arc<Mutex<Option<TransactionState>>>,
    687 }
    688 
    689 impl AuthrsService {
    690    xpcom_method!(pin_callback => PinCallback(aTransactionId: u64, aPin: *const nsACString));
    691    fn pin_callback(&self, transaction_id: u64, pin: &nsACString) -> Result<(), nsresult> {
    692        if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
    693            let mut guard = self.transaction.lock().unwrap();
    694            let Some(transaction) = guard.as_mut() else {
    695                // No ongoing transaction
    696                return Err(NS_ERROR_FAILURE);
    697            };
    698            let Some((tid, channel)) = transaction.pin_receiver.take() else {
    699                // We weren't expecting a pin.
    700                return Err(NS_ERROR_FAILURE);
    701            };
    702            if tid != transaction_id {
    703                // The browser is confused about which transaction is active.
    704                // This shouldn't happen
    705                return Err(NS_ERROR_FAILURE);
    706            }
    707            channel
    708                .send(Pin::new(&pin.to_string()))
    709                .or(Err(NS_ERROR_FAILURE))
    710        } else {
    711            // Silently accept request, if all webauthn-options are disabled.
    712            // Used for testing.
    713            Ok(())
    714        }
    715    }
    716 
    717    xpcom_method!(selection_callback => SelectionCallback(aTransactionId: u64, aSelection: u64));
    718    fn selection_callback(&self, transaction_id: u64, selection: u64) -> Result<(), nsresult> {
    719        let mut guard = self.transaction.lock().unwrap();
    720        let Some(transaction) = guard.as_mut() else {
    721            // No ongoing transaction
    722            return Err(NS_ERROR_FAILURE);
    723        };
    724        let Some((tid, channel)) = transaction.selection_receiver.take() else {
    725            // We weren't expecting a selection.
    726            return Err(NS_ERROR_FAILURE);
    727        };
    728        if tid != transaction_id {
    729            // The browser is confused about which transaction is active.
    730            // This shouldn't happen
    731            return Err(NS_ERROR_FAILURE);
    732        }
    733        channel
    734            .send(Some(selection as usize))
    735            .or(Err(NS_ERROR_FAILURE))
    736    }
    737 
    738    xpcom_method!(get_is_uvpaa => GetIsUVPAA() -> bool);
    739    fn get_is_uvpaa(&self) -> Result<bool, nsresult> {
    740        if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
    741            Ok(false)
    742        } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
    743            Ok(self
    744                .test_token_manager
    745                .has_user_verifying_platform_authenticator())
    746        } else {
    747            Err(NS_ERROR_NOT_AVAILABLE)
    748        }
    749    }
    750 
    751    xpcom_method!(make_credential => MakeCredential(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsIWebAuthnRegisterArgs, aPromise: *const nsIWebAuthnRegisterPromise));
    752    fn make_credential(
    753        &self,
    754        tid: u64,
    755        browsing_context_id: u64,
    756        args: &nsIWebAuthnRegisterArgs,
    757        promise: &nsIWebAuthnRegisterPromise,
    758    ) -> Result<(), nsresult> {
    759        self.reset()?;
    760 
    761        let promise = RegisterPromise(RefPtr::new(promise));
    762 
    763        let mut origin = nsString::new();
    764        unsafe { args.GetOrigin(&mut *origin) }.to_result()?;
    765 
    766        let mut relying_party_id = nsString::new();
    767        unsafe { args.GetRpId(&mut *relying_party_id) }.to_result()?;
    768 
    769        let mut client_data_hash = ThinVec::new();
    770        unsafe { args.GetClientDataHash(&mut client_data_hash) }.to_result()?;
    771        let mut client_data_hash_arr = [0u8; 32];
    772        client_data_hash_arr.copy_from_slice(&client_data_hash);
    773 
    774        let mut timeout_ms = 0u32;
    775        unsafe { args.GetTimeoutMS(&mut timeout_ms) }.to_result()?;
    776 
    777        let mut exclude_list = ThinVec::new();
    778        unsafe { args.GetExcludeList(&mut exclude_list) }.to_result()?;
    779        let exclude_list = exclude_list
    780            .iter_mut()
    781            .map(|id| PublicKeyCredentialDescriptor {
    782                id: id.to_vec(),
    783                transports: vec![],
    784            })
    785            .collect();
    786 
    787        let mut relying_party_name = nsString::new();
    788        unsafe { args.GetRpName(&mut *relying_party_name) }.to_result()?;
    789 
    790        let mut user_id = ThinVec::new();
    791        unsafe { args.GetUserId(&mut user_id) }.to_result()?;
    792 
    793        let mut user_name = nsString::new();
    794        unsafe { args.GetUserName(&mut *user_name) }.to_result()?;
    795 
    796        let mut user_display_name = nsString::new();
    797        unsafe { args.GetUserDisplayName(&mut *user_display_name) }.to_result()?;
    798 
    799        let mut cose_algs = ThinVec::new();
    800        unsafe { args.GetCoseAlgs(&mut cose_algs) }.to_result()?;
    801        let pub_cred_params = cose_algs
    802            .iter()
    803            .filter_map(|alg| PublicKeyCredentialParameters::try_from(*alg).ok())
    804            .collect();
    805 
    806        let mut resident_key = nsString::new();
    807        unsafe { args.GetResidentKey(&mut *resident_key) }.to_result()?;
    808        let resident_key_req = if resident_key.eq("required") {
    809            ResidentKeyRequirement::Required
    810        } else if resident_key.eq("preferred") {
    811            ResidentKeyRequirement::Preferred
    812        } else if resident_key.eq("discouraged") {
    813            ResidentKeyRequirement::Discouraged
    814        } else {
    815            return Err(NS_ERROR_FAILURE);
    816        };
    817 
    818        let mut user_verification = nsString::new();
    819        unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
    820        let user_verification_req = if user_verification.eq("required") {
    821            UserVerificationRequirement::Required
    822        } else if user_verification.eq("discouraged") {
    823            UserVerificationRequirement::Discouraged
    824        } else {
    825            UserVerificationRequirement::Preferred
    826        };
    827 
    828        let mut authenticator_attachment = nsString::new();
    829        if unsafe { args.GetAuthenticatorAttachment(&mut *authenticator_attachment) }
    830            .to_result()
    831            .is_ok()
    832        {
    833            if authenticator_attachment.eq("platform") {
    834                return Err(NS_ERROR_FAILURE);
    835            }
    836        }
    837 
    838        let mut credential_protection_policy = None;
    839        let mut enforce_credential_protection_policy = None;
    840        let mut cred_protect_policy_value = nsCString::new();
    841        let mut enforce_cred_protect_value = false;
    842        if unsafe { args.GetCredentialProtectionPolicy(&mut *cred_protect_policy_value) }
    843            .to_result()
    844            .is_ok()
    845        {
    846            unsafe { args.GetEnforceCredentialProtectionPolicy(&mut enforce_cred_protect_value) }
    847                .to_result()?;
    848            credential_protection_policy = if cred_protect_policy_value
    849                .eq("userVerificationOptional")
    850            {
    851                Some(CredentialProtectionPolicy::UserVerificationOptional)
    852            } else if cred_protect_policy_value.eq("userVerificationOptionalWithCredentialIDList") {
    853                Some(CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIDList)
    854            } else if cred_protect_policy_value.eq("userVerificationRequired") {
    855                Some(CredentialProtectionPolicy::UserVerificationRequired)
    856            } else {
    857                return Err(NS_ERROR_FAILURE);
    858            };
    859            enforce_credential_protection_policy = Some(enforce_cred_protect_value);
    860        }
    861 
    862        let mut cred_props = false;
    863        unsafe { args.GetCredProps(&mut cred_props) }.to_result()?;
    864 
    865        let mut min_pin_length = false;
    866        unsafe { args.GetMinPinLength(&mut min_pin_length) }.to_result()?;
    867 
    868        let prf_input = (|| -> Option<AuthenticationExtensionsPRFInputs> {
    869            let mut prf: bool = false;
    870            unsafe { args.GetPrf(&mut prf) }.to_result().ok()?;
    871            if !prf {
    872                return None;
    873            }
    874 
    875            let eval = || -> Option<AuthenticationExtensionsPRFValues> {
    876                let mut prf_eval_first: ThinVec<u8> = ThinVec::new();
    877                let mut prf_eval_second: ThinVec<u8> = ThinVec::new();
    878                unsafe { args.GetPrfEvalFirst(&mut prf_eval_first) }
    879                    .to_result()
    880                    .ok()?;
    881                let has_second = unsafe { args.GetPrfEvalSecond(&mut prf_eval_second) }
    882                    .to_result()
    883                    .is_ok();
    884                Some(AuthenticationExtensionsPRFValues {
    885                    first: prf_eval_first.to_vec(),
    886                    second: has_second.then(|| prf_eval_second.to_vec()),
    887                })
    888            }();
    889 
    890            Some(AuthenticationExtensionsPRFInputs {
    891                eval,
    892                eval_by_credential: None,
    893            })
    894        })();
    895 
    896        let mut hmac_create_secret = None;
    897        let mut maybe_hmac_create_secret = false;
    898        if unsafe { args.GetHmacCreateSecret(&mut maybe_hmac_create_secret) }
    899            .to_result()
    900            .is_ok()
    901        {
    902            hmac_create_secret = Some(maybe_hmac_create_secret);
    903        }
    904 
    905        let origin = origin.to_string();
    906        let info = RegisterArgs {
    907            client_data_hash: client_data_hash_arr,
    908            relying_party: RelyingParty {
    909                id: relying_party_id.to_string(),
    910                name: None,
    911            },
    912            origin: origin.clone(),
    913            user: PublicKeyCredentialUserEntity {
    914                id: user_id.to_vec(),
    915                name: Some(user_name.to_string()),
    916                display_name: None,
    917            },
    918            pub_cred_params,
    919            exclude_list,
    920            user_verification_req,
    921            resident_key_req,
    922            extensions: AuthenticationExtensionsClientInputs {
    923                credential_protection_policy,
    924                enforce_credential_protection_policy,
    925                cred_props: cred_props.then_some(true),
    926                hmac_create_secret,
    927                min_pin_length: min_pin_length.then_some(true),
    928                prf: prf_input,
    929                ..Default::default()
    930            },
    931            pin: None,
    932            use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
    933        };
    934 
    935        let mut guard = self.transaction.lock().unwrap();
    936        *guard = Some(TransactionState {
    937            tid,
    938            browsing_context_id,
    939            pending_args: None,
    940            promise: TransactionPromise::Register(promise),
    941            pin_receiver: None,
    942            selection_receiver: None,
    943            interactive_receiver: None,
    944            puat_cache: None,
    945        });
    946        // drop the guard here to ensure we don't deadlock if the call to `register()` below
    947        // hairpins the state callback.
    948        drop(guard);
    949 
    950        let (status_tx, status_rx) = channel::<StatusUpdate>();
    951        let status_transaction = self.transaction.clone();
    952        let status_origin = info.origin.clone();
    953        RunnableBuilder::new("AuthrsService::MakeCredential::StatusReceiver", move || {
    954            let _ = status_callback(
    955                status_rx,
    956                tid,
    957                &status_origin,
    958                browsing_context_id,
    959                status_transaction,
    960            );
    961        })
    962        .may_block(true)
    963        .dispatch_background_task()?;
    964 
    965        let callback_transaction = self.transaction.clone();
    966        let callback_origin = info.origin.clone();
    967        let state_callback = StateCallback::<Result<RegisterResult, AuthenticatorError>>::new(
    968            Box::new(move |result| {
    969                let mut guard = callback_transaction.lock().unwrap();
    970                let Some(state) = guard.as_mut() else {
    971                    return;
    972                };
    973                if state.tid != tid {
    974                    return;
    975                }
    976                let TransactionPromise::Register(ref promise) = state.promise else {
    977                    return;
    978                };
    979                if let Err(AuthenticatorError::CredentialExcluded) = result {
    980                    let _ = send_prompt(
    981                        BrowserPromptType::AlreadyRegistered,
    982                        tid,
    983                        Some(&callback_origin),
    984                        Some(browsing_context_id),
    985                    );
    986                }
    987                if should_cancel_prompts(&result) {
    988                    // Some errors are accompanied by prompts that should persist after the
    989                    // operation terminates.
    990                    let _ = cancel_prompts(tid);
    991                }
    992                let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
    993                *guard = None;
    994            }),
    995        );
    996 
    997        // The authenticator crate provides an `AuthenticatorService` which can dispatch a request
    998        // in parallel to any number of transports. We only support the USB transport in production
    999        // configurations, so we do not need the full generality of `AuthenticatorService` here.
   1000        // We disable the USB transport in tests that use virtual devices.
   1001        if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
   1002            // TODO(Bug 1855290) Remove this presence prompt
   1003            send_prompt(
   1004                BrowserPromptType::Presence,
   1005                tid,
   1006                Some(&info.origin),
   1007                Some(browsing_context_id),
   1008            )?;
   1009            self.usb_token_manager.lock().unwrap().register(
   1010                timeout_ms.into(),
   1011                info,
   1012                status_tx,
   1013                state_callback,
   1014            );
   1015        } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
   1016            self.test_token_manager
   1017                .register(timeout_ms.into(), info, status_tx, state_callback);
   1018        } else {
   1019            return Err(NS_ERROR_FAILURE);
   1020        }
   1021 
   1022        Ok(())
   1023    }
   1024 
   1025    xpcom_method!(set_has_attestation_consent => SetHasAttestationConsent(aTid: u64, aHasConsent: bool));
   1026    fn set_has_attestation_consent(&self, _tid: u64, _has_consent: bool) -> Result<(), nsresult> {
   1027        Err(NS_ERROR_NOT_IMPLEMENTED)
   1028    }
   1029 
   1030    xpcom_method!(get_assertion => GetAssertion(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsIWebAuthnSignArgs, aPromise: *const nsIWebAuthnSignPromise));
   1031    fn get_assertion(
   1032        &self,
   1033        tid: u64,
   1034        browsing_context_id: u64,
   1035        args: &nsIWebAuthnSignArgs,
   1036        promise: &nsIWebAuthnSignPromise,
   1037    ) -> Result<(), nsresult> {
   1038        self.reset()?;
   1039 
   1040        let promise = SignPromise(RefPtr::new(promise));
   1041 
   1042        let mut origin = nsString::new();
   1043        unsafe { args.GetOrigin(&mut *origin) }.to_result()?;
   1044 
   1045        let mut relying_party_id = nsString::new();
   1046        unsafe { args.GetRpId(&mut *relying_party_id) }.to_result()?;
   1047 
   1048        let mut client_data_hash = ThinVec::new();
   1049        unsafe { args.GetClientDataHash(&mut client_data_hash) }.to_result()?;
   1050        let mut client_data_hash_arr = [0u8; 32];
   1051        client_data_hash_arr.copy_from_slice(&client_data_hash);
   1052 
   1053        let mut timeout_ms = 0u32;
   1054        unsafe { args.GetTimeoutMS(&mut timeout_ms) }.to_result()?;
   1055 
   1056        let mut allow_list = ThinVec::new();
   1057        unsafe { args.GetAllowList(&mut allow_list) }.to_result()?;
   1058        let allow_list = allow_list
   1059            .iter()
   1060            .map(|id| PublicKeyCredentialDescriptor {
   1061                id: id.to_vec(),
   1062                transports: vec![],
   1063            })
   1064            .collect();
   1065 
   1066        let mut user_verification = nsString::new();
   1067        unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
   1068        let mut user_verification_req = if user_verification.eq("required") {
   1069            UserVerificationRequirement::Required
   1070        } else if user_verification.eq("discouraged") {
   1071            UserVerificationRequirement::Discouraged
   1072        } else {
   1073            UserVerificationRequirement::Preferred
   1074        };
   1075 
   1076        let mut app_id = None;
   1077        let mut maybe_app_id = nsString::new();
   1078        match unsafe { args.GetAppId(&mut *maybe_app_id) }.to_result() {
   1079            Ok(_) => app_id = Some(maybe_app_id.to_string()),
   1080            _ => (),
   1081        }
   1082 
   1083        let prf_input = || -> Option<AuthenticationExtensionsPRFInputs> {
   1084            let mut prf: bool = false;
   1085            unsafe { args.GetPrf(&mut prf) }.to_result().ok()?;
   1086            if !prf {
   1087                return None;
   1088            }
   1089 
   1090            let eval = || -> Option<AuthenticationExtensionsPRFValues> {
   1091                let mut prf_eval_first: ThinVec<u8> = ThinVec::new();
   1092                let mut prf_eval_second: ThinVec<u8> = ThinVec::new();
   1093                unsafe { args.GetPrfEvalFirst(&mut prf_eval_first) }
   1094                    .to_result()
   1095                    .ok()?;
   1096                let has_second = unsafe { args.GetPrfEvalSecond(&mut prf_eval_second) }
   1097                    .to_result()
   1098                    .is_ok();
   1099                Some(AuthenticationExtensionsPRFValues {
   1100                    first: prf_eval_first.to_vec(),
   1101                    second: has_second.then(|| prf_eval_second.to_vec()),
   1102                })
   1103            }();
   1104 
   1105            let eval_by_credential =
   1106                || -> Option<HashMap<Vec<u8>, AuthenticationExtensionsPRFValues>> {
   1107                    let mut credential_ids: ThinVec<ThinVec<u8>> = ThinVec::new();
   1108                    let mut eval_by_cred_firsts: ThinVec<ThinVec<u8>> = ThinVec::new();
   1109                    let mut eval_by_cred_second_maybes: ThinVec<bool> = ThinVec::new();
   1110                    let mut eval_by_cred_seconds: ThinVec<ThinVec<u8>> = ThinVec::new();
   1111                    unsafe { args.GetPrfEvalByCredentialCredentialId(&mut credential_ids) }
   1112                        .to_result()
   1113                        .ok()?;
   1114                    unsafe { args.GetPrfEvalByCredentialEvalFirst(&mut eval_by_cred_firsts) }
   1115                        .to_result()
   1116                        .ok()?;
   1117                    unsafe {
   1118                        args.GetPrfEvalByCredentialEvalSecondMaybe(&mut eval_by_cred_second_maybes)
   1119                    }
   1120                    .to_result()
   1121                    .ok()?;
   1122                    unsafe { args.GetPrfEvalByCredentialEvalSecond(&mut eval_by_cred_seconds) }
   1123                        .to_result()
   1124                        .ok()?;
   1125                    if credential_ids.len() != eval_by_cred_firsts.len()
   1126                        || credential_ids.len() != eval_by_cred_second_maybes.len()
   1127                        || credential_ids.len() != eval_by_cred_seconds.len()
   1128                    {
   1129                        return None;
   1130                    }
   1131                    let mut result = HashMap::new();
   1132                    for i in 0..credential_ids.len() {
   1133                        result.insert(
   1134                            credential_ids[i].to_vec(),
   1135                            AuthenticationExtensionsPRFValues {
   1136                                first: eval_by_cred_firsts[i].to_vec(),
   1137                                second: eval_by_cred_second_maybes[i]
   1138                                    .then(|| eval_by_cred_seconds[i].to_vec()),
   1139                            },
   1140                        );
   1141                    }
   1142                    Some(result)
   1143                }();
   1144 
   1145            Some(AuthenticationExtensionsPRFInputs {
   1146                eval,
   1147                eval_by_credential,
   1148            })
   1149        }();
   1150 
   1151        // https://w3c.github.io/webauthn/#prf-extension
   1152        // "The hmac-secret extension provides two PRFs per credential: one which is used for
   1153        // requests where user verification is performed and another for all other requests.
   1154        // This extension [PRF] only exposes a single PRF per credential and, when implementing
   1155        // on top of hmac-secret, that PRF MUST be the one used for when user verification is
   1156        // performed. This overrides the UserVerificationRequirement if neccessary."
   1157        if prf_input.is_some() && user_verification_req == UserVerificationRequirement::Discouraged
   1158        {
   1159            user_verification_req = UserVerificationRequirement::Preferred;
   1160        }
   1161 
   1162        let mut conditionally_mediated = false;
   1163        unsafe { args.GetConditionallyMediated(&mut conditionally_mediated) }.to_result()?;
   1164 
   1165        let info = SignArgs {
   1166            client_data_hash: client_data_hash_arr,
   1167            relying_party_id: relying_party_id.to_string(),
   1168            origin: origin.to_string(),
   1169            allow_list,
   1170            user_verification_req,
   1171            user_presence_req: true,
   1172            extensions: AuthenticationExtensionsClientInputs {
   1173                app_id,
   1174                prf: prf_input,
   1175                ..Default::default()
   1176            },
   1177            pin: None,
   1178            use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
   1179        };
   1180 
   1181        let mut guard = self.transaction.lock().unwrap();
   1182        *guard = Some(TransactionState {
   1183            tid,
   1184            browsing_context_id,
   1185            pending_args: Some(TransactionArgs::Sign(timeout_ms as u64, info)),
   1186            promise: TransactionPromise::Sign(promise),
   1187            pin_receiver: None,
   1188            selection_receiver: None,
   1189            interactive_receiver: None,
   1190            puat_cache: None,
   1191        });
   1192 
   1193        if !conditionally_mediated {
   1194            // Immediately proceed to the modal UI flow.
   1195            self.do_get_assertion(None, guard)
   1196        } else {
   1197            // Cache the request and wait for the conditional UI to request autofill entries, etc.
   1198            Ok(())
   1199        }
   1200    }
   1201 
   1202    fn do_get_assertion(
   1203        &self,
   1204        mut selected_credential_id: Option<Vec<u8>>,
   1205        mut guard: MutexGuard<Option<TransactionState>>,
   1206    ) -> Result<(), nsresult> {
   1207        let Some(state) = guard.as_mut() else {
   1208            return Err(NS_ERROR_FAILURE);
   1209        };
   1210        let browsing_context_id = state.browsing_context_id;
   1211        let tid = state.tid;
   1212        let (timeout_ms, mut info) = match state.pending_args.take() {
   1213            Some(TransactionArgs::Sign(timeout_ms, info)) => (timeout_ms, info),
   1214            _ => return Err(NS_ERROR_FAILURE),
   1215        };
   1216 
   1217        if let Some(id) = selected_credential_id.take() {
   1218            if info.allow_list.is_empty() {
   1219                info.allow_list.push(PublicKeyCredentialDescriptor {
   1220                    id,
   1221                    transports: vec![],
   1222                });
   1223            } else {
   1224                // We need to ensure that the selected credential id
   1225                // was in the original allow_list.
   1226                info.allow_list.retain(|cred| cred.id == id);
   1227                if info.allow_list.is_empty() {
   1228                    return Err(NS_ERROR_FAILURE);
   1229                }
   1230            }
   1231        }
   1232 
   1233        let (status_tx, status_rx) = channel::<StatusUpdate>();
   1234        let status_transaction = self.transaction.clone();
   1235        let status_origin = info.origin.to_string();
   1236        RunnableBuilder::new("AuthrsService::GetAssertion::StatusReceiver", move || {
   1237            let _ = status_callback(
   1238                status_rx,
   1239                tid,
   1240                &status_origin,
   1241                browsing_context_id,
   1242                status_transaction,
   1243            );
   1244        })
   1245        .may_block(true)
   1246        .dispatch_background_task()?;
   1247 
   1248        let uniq_allowed_cred = if info.allow_list.len() == 1 {
   1249            info.allow_list.first().cloned()
   1250        } else {
   1251            None
   1252        };
   1253 
   1254        let callback_transaction = self.transaction.clone();
   1255        let state_callback = StateCallback::<Result<SignResult, AuthenticatorError>>::new(
   1256            Box::new(move |mut result| {
   1257                let mut guard = callback_transaction.lock().unwrap();
   1258                let Some(state) = guard.as_mut() else {
   1259                    return;
   1260                };
   1261                if state.tid != tid {
   1262                    return;
   1263                }
   1264                let TransactionPromise::Sign(ref promise) = state.promise else {
   1265                    return;
   1266                };
   1267                if uniq_allowed_cred.is_some() {
   1268                    // In CTAP 2.0, but not CTAP 2.1, the assertion object's credential field
   1269                    // "May be omitted if the allowList has exactly one credential." If we had
   1270                    // a unique allowed credential, then copy its descriptor to the output.
   1271                    if let Ok(inner) = result.as_mut() {
   1272                        inner.assertion.credentials = uniq_allowed_cred;
   1273                    }
   1274                }
   1275                if should_cancel_prompts(&result) {
   1276                    // Some errors are accompanied by prompts that should persist after the
   1277                    // operation terminates.
   1278                    let _ = cancel_prompts(tid);
   1279                }
   1280                let _ = promise.resolve_or_reject(result.map_err(authrs_to_nserror));
   1281                *guard = None;
   1282            }),
   1283        );
   1284 
   1285        // TODO(Bug 1855290) Remove this presence prompt
   1286        send_prompt(
   1287            BrowserPromptType::Presence,
   1288            tid,
   1289            Some(&info.origin),
   1290            Some(browsing_context_id),
   1291        )?;
   1292 
   1293        // As in `register`, we are intentionally avoiding `AuthenticatorService` here.
   1294        if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
   1295            self.usb_token_manager.lock().unwrap().sign(
   1296                timeout_ms as u64,
   1297                info,
   1298                status_tx,
   1299                state_callback,
   1300            );
   1301        } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
   1302            self.test_token_manager
   1303                .sign(timeout_ms as u64, info, status_tx, state_callback);
   1304        } else {
   1305            return Err(NS_ERROR_FAILURE);
   1306        }
   1307 
   1308        Ok(())
   1309    }
   1310 
   1311    xpcom_method!(has_pending_conditional_get => HasPendingConditionalGet(aBrowsingContextId: u64, aOrigin: *const nsAString) -> u64);
   1312    fn has_pending_conditional_get(
   1313        &self,
   1314        browsing_context_id: u64,
   1315        origin: &nsAString,
   1316    ) -> Result<u64, nsresult> {
   1317        let mut guard = self.transaction.lock().unwrap();
   1318        let Some(state) = guard.as_mut() else {
   1319            return Ok(0);
   1320        };
   1321        let Some(TransactionArgs::Sign(_, info)) = state.pending_args.as_ref() else {
   1322            return Ok(0);
   1323        };
   1324        if state.browsing_context_id != browsing_context_id {
   1325            return Ok(0);
   1326        }
   1327        if !info.origin.eq(&origin.to_string()) {
   1328            return Ok(0);
   1329        }
   1330        Ok(state.tid)
   1331    }
   1332 
   1333    xpcom_method!(get_autofill_entries => GetAutoFillEntries(aTransactionId: u64) -> ThinVec<Option<RefPtr<nsIWebAuthnAutoFillEntry>>>);
   1334    fn get_autofill_entries(
   1335        &self,
   1336        tid: u64,
   1337    ) -> Result<ThinVec<Option<RefPtr<nsIWebAuthnAutoFillEntry>>>, nsresult> {
   1338        let mut guard = self.transaction.lock().unwrap();
   1339        let Some(state) = guard.as_mut() else {
   1340            return Err(NS_ERROR_NOT_AVAILABLE);
   1341        };
   1342        if state.tid != tid {
   1343            return Err(NS_ERROR_NOT_AVAILABLE);
   1344        }
   1345        let Some(TransactionArgs::Sign(_, info)) = state.pending_args.as_ref() else {
   1346            return Err(NS_ERROR_NOT_AVAILABLE);
   1347        };
   1348        if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
   1349            // We don't currently support silent discovery for credentials on USB tokens.
   1350            return Ok(thin_vec![]);
   1351        } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
   1352            return self
   1353                .test_token_manager
   1354                .get_autofill_entries(&info.relying_party_id, &info.allow_list);
   1355        } else {
   1356            return Err(NS_ERROR_FAILURE);
   1357        }
   1358    }
   1359 
   1360    xpcom_method!(select_autofill_entry => SelectAutoFillEntry(aTid: u64, aCredentialId: *const ThinVec<u8>));
   1361    fn select_autofill_entry(&self, tid: u64, credential_id: &ThinVec<u8>) -> Result<(), nsresult> {
   1362        let mut guard = self.transaction.lock().unwrap();
   1363        let Some(state) = guard.as_mut() else {
   1364            return Err(NS_ERROR_FAILURE);
   1365        };
   1366        if tid != state.tid {
   1367            return Err(NS_ERROR_FAILURE);
   1368        }
   1369        self.do_get_assertion(Some(credential_id.to_vec()), guard)
   1370    }
   1371 
   1372    xpcom_method!(resume_conditional_get => ResumeConditionalGet(aTid: u64));
   1373    fn resume_conditional_get(&self, tid: u64) -> Result<(), nsresult> {
   1374        let mut guard = self.transaction.lock().unwrap();
   1375        let Some(state) = guard.as_mut() else {
   1376            return Err(NS_ERROR_FAILURE);
   1377        };
   1378        if tid != state.tid {
   1379            return Err(NS_ERROR_FAILURE);
   1380        }
   1381        self.do_get_assertion(None, guard)
   1382    }
   1383 
   1384    // Clears the transaction state if tid matches the ongoing transaction ID.
   1385    // Returns whether the tid was a match.
   1386    fn clear_transaction(&self, tid: u64) -> bool {
   1387        let mut guard = self.transaction.lock().unwrap();
   1388        let Some(state) = guard.as_ref() else {
   1389            return true; // workaround for Bug 1864526.
   1390        };
   1391        if state.tid != tid {
   1392            // Ignore the cancellation request if the transaction
   1393            // ID does not match.
   1394            return false;
   1395        }
   1396        // It's possible that we haven't dispatched the request to the usb_token_manager yet,
   1397        // e.g. if we're waiting for resume_make_credential. So reject the promise and drop the
   1398        // state here rather than from the StateCallback
   1399        let _ = state.promise.reject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
   1400        *guard = None;
   1401        true
   1402    }
   1403 
   1404    xpcom_method!(cancel => Cancel(aTransactionId: u64));
   1405    fn cancel(&self, tid: u64) -> Result<(), nsresult> {
   1406        if self.clear_transaction(tid) {
   1407            self.usb_token_manager.lock().unwrap().cancel();
   1408        }
   1409        Ok(())
   1410    }
   1411 
   1412    xpcom_method!(reset => Reset());
   1413    fn reset(&self) -> Result<(), nsresult> {
   1414        {
   1415            if let Some(state) = self.transaction.lock().unwrap().take() {
   1416                cancel_prompts(state.tid)?;
   1417                state.promise.reject(NS_ERROR_DOM_ABORT_ERR)?;
   1418            }
   1419        } // release the transaction lock so a StateCallback can take it
   1420        self.usb_token_manager.lock().unwrap().cancel();
   1421        Ok(())
   1422    }
   1423 
   1424    xpcom_method!(
   1425        add_virtual_authenticator => AddVirtualAuthenticator(
   1426            protocol: *const nsACString,
   1427            transport: *const nsACString,
   1428            hasResidentKey: bool,
   1429            hasUserVerification: bool,
   1430            isUserConsenting: bool,
   1431            isUserVerified: bool) -> nsACString
   1432    );
   1433    fn add_virtual_authenticator(
   1434        &self,
   1435        protocol: &nsACString,
   1436        transport: &nsACString,
   1437        has_resident_key: bool,
   1438        has_user_verification: bool,
   1439        is_user_consenting: bool,
   1440        is_user_verified: bool,
   1441    ) -> Result<nsCString, nsresult> {
   1442        let protocol = match protocol.to_string().as_str() {
   1443            "ctap1/u2f" => AuthenticatorVersion::U2F_V2,
   1444            "ctap2" => AuthenticatorVersion::FIDO_2_0,
   1445            "ctap2_1" => AuthenticatorVersion::FIDO_2_1,
   1446            _ => return Err(NS_ERROR_INVALID_ARG),
   1447        };
   1448        let transport = transport.to_string();
   1449        match transport.as_str() {
   1450            "usb" | "nfc" | "ble" | "smart-card" | "hybrid" | "internal" => (),
   1451            _ => return Err(NS_ERROR_INVALID_ARG),
   1452        };
   1453        self.test_token_manager
   1454            .add_virtual_authenticator(
   1455                protocol,
   1456                transport,
   1457                has_resident_key,
   1458                has_user_verification,
   1459                is_user_consenting,
   1460                is_user_verified,
   1461            )
   1462            .map(nsCString::from)
   1463    }
   1464 
   1465    xpcom_method!(remove_virtual_authenticator => RemoveVirtualAuthenticator(authenticatorId: *const nsACString));
   1466    fn remove_virtual_authenticator(&self, authenticator_id: &nsACString) -> Result<(), nsresult> {
   1467        self.test_token_manager
   1468            .remove_virtual_authenticator(&authenticator_id.to_utf8())
   1469    }
   1470 
   1471    xpcom_method!(
   1472        add_credential => AddCredential(
   1473            authenticatorId: *const nsACString,
   1474            credentialId: *const nsACString,
   1475            isResidentCredential: bool,
   1476            rpId: *const nsACString,
   1477            privateKey: *const nsACString,
   1478            userHandle: *const nsACString,
   1479            signCount: u32)
   1480    );
   1481    fn add_credential(
   1482        &self,
   1483        authenticator_id: &nsACString,
   1484        credential_id: &nsACString,
   1485        is_resident_credential: bool,
   1486        rp_id: &nsACString,
   1487        private_key: &nsACString,
   1488        user_handle: &nsACString,
   1489        sign_count: u32,
   1490    ) -> Result<(), nsresult> {
   1491        let credential_id = base64::engine::general_purpose::URL_SAFE_NO_PAD
   1492            .decode(credential_id)
   1493            .or(Err(NS_ERROR_INVALID_ARG))?;
   1494        let private_key = base64::engine::general_purpose::URL_SAFE_NO_PAD
   1495            .decode(private_key)
   1496            .or(Err(NS_ERROR_INVALID_ARG))?;
   1497        let user_handle = base64::engine::general_purpose::URL_SAFE_NO_PAD
   1498            .decode(user_handle)
   1499            .or(Err(NS_ERROR_INVALID_ARG))?;
   1500        self.test_token_manager.add_credential(
   1501            &authenticator_id.to_utf8(),
   1502            &credential_id,
   1503            &private_key,
   1504            &user_handle,
   1505            sign_count,
   1506            rp_id.to_string(),
   1507            is_resident_credential,
   1508        )
   1509    }
   1510 
   1511    xpcom_method!(get_credentials => GetCredentials(authenticatorId: *const nsACString) -> ThinVec<Option<RefPtr<nsICredentialParameters>>>);
   1512    fn get_credentials(
   1513        &self,
   1514        authenticator_id: &nsACString,
   1515    ) -> Result<ThinVec<Option<RefPtr<nsICredentialParameters>>>, nsresult> {
   1516        self.test_token_manager
   1517            .get_credentials(&authenticator_id.to_utf8())
   1518    }
   1519 
   1520    xpcom_method!(remove_credential => RemoveCredential(authenticatorId: *const nsACString, credentialId: *const nsACString));
   1521    fn remove_credential(
   1522        &self,
   1523        authenticator_id: &nsACString,
   1524        credential_id: &nsACString,
   1525    ) -> Result<(), nsresult> {
   1526        let credential_id = base64::engine::general_purpose::URL_SAFE_NO_PAD
   1527            .decode(credential_id)
   1528            .or(Err(NS_ERROR_INVALID_ARG))?;
   1529        self.test_token_manager
   1530            .remove_credential(&authenticator_id.to_utf8(), credential_id.as_ref())
   1531    }
   1532 
   1533    xpcom_method!(remove_all_credentials => RemoveAllCredentials(authenticatorId: *const nsACString));
   1534    fn remove_all_credentials(&self, authenticator_id: &nsACString) -> Result<(), nsresult> {
   1535        self.test_token_manager
   1536            .remove_all_credentials(&authenticator_id.to_utf8())
   1537    }
   1538 
   1539    xpcom_method!(set_user_verified => SetUserVerified(authenticatorId: *const nsACString, isUserVerified: bool));
   1540    fn set_user_verified(
   1541        &self,
   1542        authenticator_id: &nsACString,
   1543        is_user_verified: bool,
   1544    ) -> Result<(), nsresult> {
   1545        self.test_token_manager
   1546            .set_user_verified(&authenticator_id.to_utf8(), is_user_verified)
   1547    }
   1548 
   1549    xpcom_method!(listen => Listen());
   1550    pub(crate) fn listen(&self) -> Result<(), nsresult> {
   1551        // For now, we don't support softtokens
   1552        if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
   1553            return Ok(());
   1554        }
   1555 
   1556        {
   1557            let mut guard = self.transaction.lock().unwrap();
   1558            if guard.as_ref().is_some() {
   1559                // ignore listen() and continue with ongoing transaction
   1560                return Ok(());
   1561            }
   1562            *guard = Some(TransactionState {
   1563                tid: 0,
   1564                browsing_context_id: 0,
   1565                pending_args: None,
   1566                promise: TransactionPromise::Listen,
   1567                pin_receiver: None,
   1568                selection_receiver: None,
   1569                interactive_receiver: None,
   1570                puat_cache: None,
   1571            });
   1572        }
   1573 
   1574        // We may get from status_updates info about certain errors (e.g. PinErrors)
   1575        // which we want to present to the user. We will ignore the following error
   1576        // which is caused by us "hanging up" on the StatusUpdate-channel and return
   1577        // the PinError instead, via `upcoming_error`.
   1578        let upcoming_error = Arc::new(Mutex::new(None));
   1579        let upcoming_error_c = upcoming_error.clone();
   1580        let callback_transaction = self.transaction.clone();
   1581        let state_callback = StateCallback::<Result<ManageResult, AuthenticatorError>>::new(
   1582            Box::new(move |result| {
   1583                let mut guard = callback_transaction.lock().unwrap();
   1584                match guard.as_mut() {
   1585                    Some(state) => {
   1586                        match state.promise {
   1587                            TransactionPromise::Listen => (),
   1588                            _ => return,
   1589                        }
   1590                        *guard = None;
   1591                    }
   1592                    // We have no transaction anymore, this means cancel() was called
   1593                    None => (),
   1594                }
   1595                let msg = match result {
   1596                    Ok(_) => BrowserPromptType::ListenSuccess,
   1597                    Err(e) => {
   1598                        // See if we have a cached error that should replace this error
   1599                        let replacement = if let Ok(mut x) = upcoming_error_c.lock() {
   1600                            x.take()
   1601                        } else {
   1602                            None
   1603                        };
   1604                        let replaced_err = replacement.unwrap_or(e);
   1605                        let err = authrs_to_prompt(replaced_err);
   1606                        BrowserPromptType::ListenError {
   1607                            error: Box::new(err),
   1608                        }
   1609                    }
   1610                };
   1611                let _ = send_about_prompt(&msg);
   1612            }),
   1613        );
   1614 
   1615        // Calling `manage()` within the lock, to avoid race conditions
   1616        // where we might check listen_blocked, see that it's false,
   1617        // continue along, but in parallel `make_credential()` aborts the
   1618        // interactive process shortly after, setting listen_blocked to true,
   1619        // then accessing usb_token_manager afterwards and at the same time
   1620        // we do it here, causing a runtime crash for trying to mut-borrow it twice.
   1621        let (status_tx, status_rx) = channel::<StatusUpdate>();
   1622        let status_transaction = self.transaction.clone();
   1623        RunnableBuilder::new(
   1624            "AuthrsTransport::AboutWebauthn::StatusReceiver",
   1625            move || {
   1626                let _ = interactive_status_callback(status_rx, status_transaction, upcoming_error);
   1627            },
   1628        )
   1629        .may_block(true)
   1630        .dispatch_background_task()?;
   1631        if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
   1632            self.usb_token_manager.lock().unwrap().manage(
   1633                60 * 1000 * 1000,
   1634                status_tx,
   1635                state_callback,
   1636            );
   1637        } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
   1638            // We don't yet support softtoken
   1639        } else {
   1640            // Silently accept request, if all webauthn-options are disabled.
   1641            // Used for testing.
   1642        }
   1643        Ok(())
   1644    }
   1645 
   1646    xpcom_method!(run_command => RunCommand(c_cmd: *const nsACString));
   1647    pub fn run_command(&self, c_cmd: &nsACString) -> Result<(), nsresult> {
   1648        // Always test if it can be parsed from incoming JSON (even for tests)
   1649        let incoming: RequestWrapper =
   1650            serde_json::from_str(&c_cmd.to_utf8()).or(Err(NS_ERROR_DOM_OPERATION_ERR))?;
   1651        if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
   1652            let guard = self.transaction.lock().unwrap();
   1653            let puat = guard.as_ref().and_then(|g| g.puat_cache.clone());
   1654            let command = match incoming {
   1655                RequestWrapper::Quit => InteractiveRequest::Quit,
   1656                RequestWrapper::ChangePIN(a, b) => InteractiveRequest::ChangePIN(a, b),
   1657                RequestWrapper::SetPIN(a) => InteractiveRequest::SetPIN(a),
   1658                RequestWrapper::CredentialManagement(c) => {
   1659                    InteractiveRequest::CredentialManagement(c, puat)
   1660                }
   1661                RequestWrapper::BioEnrollment(c) => InteractiveRequest::BioEnrollment(c, puat),
   1662            };
   1663            match &guard.as_ref().unwrap().interactive_receiver {
   1664                Some(channel) => channel.send(command).or(Err(NS_ERROR_FAILURE)),
   1665                // Either we weren't expecting a pin, or the controller is confused
   1666                // about which transaction is active. Neither is recoverable, so it's
   1667                // OK to drop the PinReceiver here.
   1668                _ => Err(NS_ERROR_FAILURE),
   1669            }
   1670        } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
   1671            // We don't yet support softtoken
   1672            Ok(())
   1673        } else {
   1674            // Silently accept request, if all webauthn-options are disabled.
   1675            // Used for testing.
   1676            Ok(())
   1677        }
   1678    }
   1679 }
   1680 
   1681 #[no_mangle]
   1682 pub extern "C" fn authrs_service_constructor(result: *mut *const nsIWebAuthnService) -> nsresult {
   1683    let wrapper = AuthrsService::allocate(InitAuthrsService {
   1684        usb_token_manager: Mutex::new(StateMachine::new()),
   1685        test_token_manager: TestTokenManager::new(),
   1686        transaction: Arc::new(Mutex::new(None)),
   1687    });
   1688 
   1689    #[cfg(feature = "fuzzing")]
   1690    {
   1691        let fuzzing_config = static_prefs::pref!("fuzzing.webauthn.authenticator_config");
   1692        if fuzzing_config != 0 {
   1693            let is_user_verified = (fuzzing_config & 0x01) != 0;
   1694            let is_user_consenting = (fuzzing_config & 0x02) != 0;
   1695            let has_user_verification = (fuzzing_config & 0x04) != 0;
   1696            let has_resident_key = (fuzzing_config & 0x08) != 0;
   1697            let transport = nsCString::from(match (fuzzing_config & 0x10) >> 4 {
   1698                0 => "usb",
   1699                1 => "internal",
   1700                _ => unreachable!(),
   1701            });
   1702            let protocol = nsCString::from(match (fuzzing_config & 0x60) >> 5 {
   1703                0 => "", // reserved
   1704                1 => "ctap1/u2f",
   1705                2 => "ctap2",
   1706                3 => "ctap2_1",
   1707                _ => unreachable!(),
   1708            });
   1709            // If this fails it's probably because the protocol bits were zero,
   1710            // we'll just ignore it.
   1711            let _ = wrapper.add_virtual_authenticator(
   1712                &protocol,
   1713                &transport,
   1714                has_resident_key,
   1715                has_user_verification,
   1716                is_user_consenting,
   1717                is_user_verified,
   1718            );
   1719        }
   1720    }
   1721 
   1722    unsafe {
   1723        RefPtr::new(wrapper.coerce::<nsIWebAuthnService>()).forget(&mut *result);
   1724    }
   1725    NS_OK
   1726 }
   1727 
   1728 #[no_mangle]
   1729 pub extern "C" fn authrs_webauthn_att_obj_constructor(
   1730    att_obj_bytes: &ThinVec<u8>,
   1731    anonymize: bool,
   1732    result: *mut *const nsIWebAuthnAttObj,
   1733 ) -> nsresult {
   1734    if result.is_null() {
   1735        return NS_ERROR_NULL_POINTER;
   1736    }
   1737 
   1738    let mut att_obj: AttestationObject = match serde_cbor::from_slice(att_obj_bytes) {
   1739        Ok(att_obj) => att_obj,
   1740        Err(_) => return NS_ERROR_INVALID_ARG,
   1741    };
   1742 
   1743    if anonymize {
   1744        att_obj.anonymize();
   1745    }
   1746 
   1747    let wrapper = WebAuthnAttObj::allocate(InitWebAuthnAttObj { att_obj });
   1748 
   1749    unsafe {
   1750        RefPtr::new(wrapper.coerce::<nsIWebAuthnAttObj>()).forget(&mut *result);
   1751    }
   1752 
   1753    NS_OK
   1754 }