tor-browser

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

about_webauthn_controller.rs (7614B)


      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 use super::*;
      6 use authenticator::{
      7    ctap2::commands::StatusCode,
      8    errors::{CommandError, HIDError},
      9    BioEnrollmentCmd, CredManagementCmd, InteractiveUpdate, PinError,
     10 };
     11 use serde::{Deserialize, Serialize};
     12 
     13 pub(crate) type InteractiveManagementReceiver = Option<Sender<InteractiveRequest>>;
     14 pub(crate) fn send_about_prompt(prompt: &BrowserPromptType) -> Result<(), nsresult> {
     15    let json = nsString::from(&serde_json::to_string(&prompt).unwrap_or_default());
     16    notify_observers(PromptTarget::AboutPage, json)
     17 }
     18 
     19 // A wrapper around InteractiveRequest, that leaves out the PUAT
     20 // so that we can easily de/serialize it to/from JSON for the JS-side
     21 // and then add our cached PUAT, if we have one.
     22 #[derive(Debug, Serialize, Deserialize)]
     23 pub enum RequestWrapper {
     24    Quit,
     25    ChangePIN(Pin, Pin),
     26    SetPIN(Pin),
     27    CredentialManagement(CredManagementCmd),
     28    BioEnrollment(BioEnrollmentCmd),
     29 }
     30 
     31 pub(crate) fn authrs_to_prompt<'a>(e: AuthenticatorError) -> BrowserPromptType<'a> {
     32    match e {
     33        AuthenticatorError::PinError(PinError::PinIsTooShort) => BrowserPromptType::PinIsTooShort,
     34        AuthenticatorError::PinError(PinError::PinNotSet) => BrowserPromptType::PinNotSet,
     35        AuthenticatorError::PinError(PinError::PinRequired) => BrowserPromptType::PinRequired,
     36        AuthenticatorError::PinError(PinError::PinIsTooLong(_)) => BrowserPromptType::PinIsTooLong,
     37        AuthenticatorError::PinError(PinError::InvalidPin(r)) => {
     38            BrowserPromptType::PinInvalid { retries: r }
     39        }
     40        AuthenticatorError::PinError(PinError::PinAuthBlocked) => BrowserPromptType::PinAuthBlocked,
     41        AuthenticatorError::PinError(PinError::PinBlocked) => BrowserPromptType::DeviceBlocked,
     42        AuthenticatorError::PinError(PinError::UvBlocked) => BrowserPromptType::UvBlocked,
     43        AuthenticatorError::PinError(PinError::InvalidUv(r)) => {
     44            BrowserPromptType::UvInvalid { retries: r }
     45        }
     46        AuthenticatorError::CancelledByUser
     47        | AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
     48            StatusCode::KeepaliveCancel,
     49            _,
     50        ))) => BrowserPromptType::Cancel,
     51        _ => BrowserPromptType::UnknownError,
     52    }
     53 }
     54 
     55 pub(crate) fn cache_puat(
     56    transaction: Arc<Mutex<Option<TransactionState>>>,
     57    puat: Option<PinUvAuthResult>,
     58 ) {
     59    let mut guard = transaction.lock().unwrap();
     60    if let Some(transaction) = guard.as_mut() {
     61        transaction.puat_cache = puat;
     62    };
     63 }
     64 
     65 pub(crate) fn interactive_status_callback(
     66    status_rx: Receiver<StatusUpdate>,
     67    transaction: Arc<Mutex<Option<TransactionState>>>, /* Shared with an AuthrsTransport */
     68    upcoming_error: Arc<Mutex<Option<AuthenticatorError>>>,
     69 ) -> Result<(), nsresult> {
     70    loop {
     71        match status_rx.recv() {
     72            Ok(StatusUpdate::InteractiveManagement(InteractiveUpdate::StartManagement((
     73                tx,
     74                auth_info,
     75            )))) => {
     76                let mut guard = transaction.lock().unwrap();
     77                let Some(transaction) = guard.as_mut() else {
     78                    warn!("STATUS: received status update after end of transaction.");
     79                    break;
     80                };
     81                transaction.interactive_receiver.replace(tx);
     82                let prompt = BrowserPromptType::SelectedDevice { auth_info };
     83                send_about_prompt(&prompt)?;
     84            }
     85            Ok(StatusUpdate::InteractiveManagement(
     86                InteractiveUpdate::CredentialManagementUpdate((cfg_result, puat_res)),
     87            )) => {
     88                cache_puat(transaction.clone(), puat_res); // We don't care if we fail here. Worst-case: User has to enter PIN more often.
     89                let prompt = BrowserPromptType::CredentialManagementUpdate { result: cfg_result };
     90                send_about_prompt(&prompt)?;
     91                continue;
     92            }
     93            Ok(StatusUpdate::InteractiveManagement(InteractiveUpdate::BioEnrollmentUpdate((
     94                bio_res,
     95                puat_res,
     96            )))) => {
     97                cache_puat(transaction.clone(), puat_res); // We don't care if we fail here. Worst-case: User has to enter PIN more often.
     98                let prompt = BrowserPromptType::BioEnrollmentUpdate { result: bio_res };
     99                send_about_prompt(&prompt)?;
    100                continue;
    101            }
    102            Ok(StatusUpdate::SelectDeviceNotice) => {
    103                info!("STATUS: Please select a device by touching one of them.");
    104                let prompt = BrowserPromptType::SelectDevice;
    105                send_about_prompt(&prompt)?;
    106            }
    107            Ok(StatusUpdate::PinUvError(e)) => {
    108                let mut guard = transaction.lock().unwrap();
    109                let Some(transaction) = guard.as_mut() else {
    110                    warn!("STATUS: received status update after end of transaction.");
    111                    break;
    112                };
    113                let autherr = match e {
    114                    StatusPinUv::PinRequired(pin_sender) => {
    115                        transaction.pin_receiver.replace((0, pin_sender));
    116                        send_about_prompt(&BrowserPromptType::PinRequired)?;
    117                        continue;
    118                    }
    119                    StatusPinUv::InvalidPin(pin_sender, r) => {
    120                        transaction.pin_receiver.replace((0, pin_sender));
    121                        send_about_prompt(&BrowserPromptType::PinInvalid { retries: r })?;
    122                        continue;
    123                    }
    124                    StatusPinUv::PinIsTooShort => {
    125                        AuthenticatorError::PinError(PinError::PinIsTooShort)
    126                    }
    127                    StatusPinUv::PinIsTooLong(s) => {
    128                        AuthenticatorError::PinError(PinError::PinIsTooLong(s))
    129                    }
    130                    StatusPinUv::InvalidUv(r) => {
    131                        send_about_prompt(&BrowserPromptType::UvInvalid { retries: r })?;
    132                        continue;
    133                    }
    134                    StatusPinUv::PinAuthBlocked => {
    135                        AuthenticatorError::PinError(PinError::PinAuthBlocked)
    136                    }
    137                    StatusPinUv::PinBlocked => AuthenticatorError::PinError(PinError::PinBlocked),
    138                    StatusPinUv::PinNotSet => AuthenticatorError::PinError(PinError::PinNotSet),
    139                    StatusPinUv::UvBlocked => AuthenticatorError::PinError(PinError::UvBlocked),
    140                };
    141                // We will cause auth-rs to return an error, once we leave this block
    142                // due to us 'hanging up'. Before we do that, we will safe the actual
    143                // error that caused this, so our callback-function can return the true
    144                // error to JS, instead of "cancelled by user".
    145                let guard = upcoming_error.lock();
    146                if let Ok(mut entry) = guard {
    147                    entry.replace(autherr);
    148                } else {
    149                    return Err(NS_ERROR_DOM_INVALID_STATE_ERR);
    150                }
    151                warn!("STATUS: Pin Error {:?}", e);
    152                break;
    153            }
    154 
    155            Ok(_) => {
    156                // currently not handled
    157                continue;
    158            }
    159            Err(RecvError) => {
    160                info!("STATUS: end");
    161                break;
    162            }
    163        }
    164    }
    165    Ok(())
    166 }