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 }