Backed out changeset 793702f190e3 (bug 1853819) for bc failures on browser_webauthn_p...
[gecko.git] / dom / webauthn / authrs_bridge / src / lib.rs
blob0efc6d44b2c1da425f49b62bcf211393628c12c1
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/. */
5 #[macro_use]
6 extern crate log;
8 #[macro_use]
9 extern crate xpcom;
11 use authenticator::{
12     authenticatorservice::{RegisterArgs, SignArgs},
13     crypto::COSEKeyType,
14     ctap2::attestation::AttestationObject,
15     ctap2::commands::get_info::AuthenticatorVersion,
16     ctap2::server::{
17         AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor,
18         PublicKeyCredentialParameters, RelyingParty, ResidentKeyRequirement, User,
19         UserVerificationRequirement,
20     },
21     errors::{AuthenticatorError, PinError, U2FTokenError},
22     statecallback::StateCallback,
23     Assertion, Pin, RegisterResult, SignResult, StateMachine, StatusPinUv, StatusUpdate,
25 use base64::Engine;
26 use moz_task::RunnableBuilder;
27 use nserror::{
28     nsresult, NS_ERROR_DOM_INVALID_STATE_ERR, NS_ERROR_DOM_NOT_ALLOWED_ERR,
29     NS_ERROR_DOM_NOT_SUPPORTED_ERR, NS_ERROR_DOM_UNKNOWN_ERR, NS_ERROR_FAILURE,
30     NS_ERROR_INVALID_ARG, NS_ERROR_NOT_AVAILABLE, NS_ERROR_NOT_IMPLEMENTED, NS_ERROR_NULL_POINTER,
31     NS_OK,
33 use nsstring::{nsACString, nsCString, nsString};
34 use serde_cbor;
35 use std::cell::RefCell;
36 use std::sync::mpsc::{channel, Receiver, RecvError, Sender};
37 use std::sync::{Arc, Mutex};
38 use thin_vec::{thin_vec, ThinVec};
39 use xpcom::interfaces::{
40     nsICredentialParameters, nsICtapRegisterArgs, nsICtapRegisterResult, nsICtapSignArgs,
41     nsICtapSignResult, nsIWebAuthnAttObj, nsIWebAuthnController, nsIWebAuthnTransport,
43 use xpcom::{xpcom_method, RefPtr};
45 mod test_token;
46 use test_token::TestTokenManager;
48 fn make_prompt(action: &str, tid: u64, origin: &str, browsing_context_id: u64) -> String {
49     format!(
50         r#"{{"is_ctap2":true,"action":"{action}","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id}}}"#,
51     )
54 fn make_uv_invalid_error_prompt(
55     tid: u64,
56     origin: &str,
57     browsing_context_id: u64,
58     retries: i64,
59 ) -> String {
60     format!(
61         r#"{{"is_ctap2":true,"action":"uv-invalid","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id},"retriesLeft":{retries}}}"#,
62     )
65 fn make_pin_required_prompt(
66     tid: u64,
67     origin: &str,
68     browsing_context_id: u64,
69     was_invalid: bool,
70     retries: i64,
71 ) -> String {
72     format!(
73         r#"{{"is_ctap2":true,"action":"pin-required","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id},"wasInvalid":{was_invalid},"retriesLeft":{retries}}}"#,
74     )
77 fn authrs_to_nserror(e: &AuthenticatorError) -> nsresult {
78     match e {
79         AuthenticatorError::U2FToken(U2FTokenError::NotSupported) => NS_ERROR_DOM_NOT_SUPPORTED_ERR,
80         AuthenticatorError::U2FToken(U2FTokenError::InvalidState) => NS_ERROR_DOM_INVALID_STATE_ERR,
81         AuthenticatorError::U2FToken(U2FTokenError::NotAllowed) => NS_ERROR_DOM_NOT_ALLOWED_ERR,
82         AuthenticatorError::PinError(PinError::PinRequired) => NS_ERROR_DOM_INVALID_STATE_ERR,
83         AuthenticatorError::PinError(PinError::InvalidPin(_)) => NS_ERROR_DOM_INVALID_STATE_ERR,
84         AuthenticatorError::PinError(PinError::PinAuthBlocked) => NS_ERROR_DOM_INVALID_STATE_ERR,
85         AuthenticatorError::PinError(PinError::PinBlocked) => NS_ERROR_DOM_INVALID_STATE_ERR,
86         AuthenticatorError::PinError(PinError::PinNotSet) => NS_ERROR_DOM_INVALID_STATE_ERR,
87         AuthenticatorError::CredentialExcluded => NS_ERROR_DOM_INVALID_STATE_ERR,
88         _ => NS_ERROR_DOM_UNKNOWN_ERR,
89     }
92 #[xpcom(implement(nsICtapRegisterResult), atomic)]
93 pub struct CtapRegisterResult {
94     result: Result<RegisterResult, AuthenticatorError>,
97 impl CtapRegisterResult {
98     xpcom_method!(get_attestation_object => GetAttestationObject() -> ThinVec<u8>);
99     fn get_attestation_object(&self) -> Result<ThinVec<u8>, nsresult> {
100         let mut out = ThinVec::new();
101         let make_cred_res = self.result.as_ref().or(Err(NS_ERROR_FAILURE))?;
102         serde_cbor::to_writer(&mut out, &make_cred_res.att_obj).or(Err(NS_ERROR_FAILURE))?;
103         Ok(out)
104     }
106     xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
107     fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
108         let mut out = ThinVec::new();
109         if let Ok(make_cred_res) = &self.result {
110             if let Some(credential_data) = &make_cred_res.att_obj.auth_data.credential_data {
111                 out.extend_from_slice(&credential_data.credential_id);
112                 return Ok(out);
113             }
114         }
115         Err(NS_ERROR_FAILURE)
116     }
118     xpcom_method!(get_transports => GetTransports() -> ThinVec<nsString>);
119     fn get_transports(&self) -> Result<ThinVec<nsString>, nsresult> {
120         if self.result.is_err() {
121             return Err(NS_ERROR_FAILURE);
122         }
123         // The list that we return here might be included in a future GetAssertion request as a
124         // hint as to which transports to try. We currently only support the USB transport. If
125         // that changes, we will need a mechanism to track which transport was used for a
126         // request.
127         Ok(thin_vec![nsString::from("usb")])
128     }
130     xpcom_method!(get_status => GetStatus() -> nsresult);
131     fn get_status(&self) -> Result<nsresult, nsresult> {
132         match &self.result {
133             Ok(_) => Ok(NS_OK),
134             Err(e) => Ok(authrs_to_nserror(e)),
135         }
136     }
139 #[xpcom(implement(nsIWebAuthnAttObj), atomic)]
140 pub struct WebAuthnAttObj {
141     att_obj: AttestationObject,
144 impl WebAuthnAttObj {
145     xpcom_method!(get_attestation_object => GetAttestationObject() -> ThinVec<u8>);
146     fn get_attestation_object(&self) -> Result<ThinVec<u8>, nsresult> {
147         let mut out = ThinVec::new();
148         serde_cbor::to_writer(&mut out, &self.att_obj).or(Err(NS_ERROR_FAILURE))?;
149         Ok(out)
150     }
152     xpcom_method!(get_authenticator_data => GetAuthenticatorData() -> ThinVec<u8>);
153     fn get_authenticator_data(&self) -> Result<ThinVec<u8>, nsresult> {
154         // TODO(https://github.com/mozilla/authenticator-rs/issues/302) use to_writer
155         Ok(self.att_obj.auth_data.to_vec().into())
156     }
158     xpcom_method!(get_public_key => GetPublicKey() -> ThinVec<u8>);
159     fn get_public_key(&self) -> Result<ThinVec<u8>, nsresult> {
160         let Some(credential_data) = &self.att_obj.auth_data.credential_data else {
161             return Err(NS_ERROR_FAILURE);
162         };
163         // We only support encoding (some) EC2 keys in DER SPKI format.
164         let COSEKeyType::EC2(ref key) = credential_data.credential_public_key.key else {
165             return Err(NS_ERROR_NOT_AVAILABLE);
166         };
167         Ok(key.der_spki().or(Err(NS_ERROR_NOT_AVAILABLE))?.into())
168     }
170     xpcom_method!(get_public_key_algorithm => GetPublicKeyAlgorithm() -> i32);
171     fn get_public_key_algorithm(&self) -> Result<i32, nsresult> {
172         if let Some(credential_data) = &self.att_obj.auth_data.credential_data {
173             // safe to cast to i32 by inspection of defined values
174             Ok(credential_data.credential_public_key.alg as i32)
175         } else {
176             Err(NS_ERROR_FAILURE)
177         }
178     }
181 #[xpcom(implement(nsICtapSignResult), atomic)]
182 pub struct CtapSignResult {
183     result: Result<Assertion, AuthenticatorError>,
186 impl CtapSignResult {
187     xpcom_method!(get_credential_id => GetCredentialId() -> ThinVec<u8>);
188     fn get_credential_id(&self) -> Result<ThinVec<u8>, nsresult> {
189         let mut out = ThinVec::new();
190         if let Ok(assertion) = &self.result {
191             if let Some(cred) = &assertion.credentials {
192                 out.extend_from_slice(&cred.id);
193                 return Ok(out);
194             }
195         }
196         Err(NS_ERROR_FAILURE)
197     }
199     xpcom_method!(get_signature => GetSignature() -> ThinVec<u8>);
200     fn get_signature(&self) -> Result<ThinVec<u8>, nsresult> {
201         let mut out = ThinVec::new();
202         if let Ok(assertion) = &self.result {
203             out.extend_from_slice(&assertion.signature);
204             return Ok(out);
205         }
206         Err(NS_ERROR_FAILURE)
207     }
209     xpcom_method!(get_authenticator_data => GetAuthenticatorData() -> ThinVec<u8>);
210     fn get_authenticator_data(&self) -> Result<ThinVec<u8>, nsresult> {
211         self.result
212             .as_ref()
213             .map(|assertion| assertion.auth_data.to_vec().into())
214             .or(Err(NS_ERROR_FAILURE))
215     }
217     xpcom_method!(get_user_handle => GetUserHandle() -> ThinVec<u8>);
218     fn get_user_handle(&self) -> Result<ThinVec<u8>, nsresult> {
219         let mut out = ThinVec::new();
220         if let Ok(assertion) = &self.result {
221             if let Some(user) = &assertion.user {
222                 out.extend_from_slice(&user.id);
223                 return Ok(out);
224             }
225         }
226         Err(NS_ERROR_FAILURE)
227     }
229     xpcom_method!(get_user_name => GetUserName() -> nsACString);
230     fn get_user_name(&self) -> Result<nsCString, nsresult> {
231         if let Ok(assertion) = &self.result {
232             if let Some(user) = &assertion.user {
233                 if let Some(name) = &user.name {
234                     return Ok(nsCString::from(name));
235                 }
236             }
237         }
238         Err(NS_ERROR_NOT_AVAILABLE)
239     }
241     xpcom_method!(get_rp_id_hash => GetRpIdHash() -> ThinVec<u8>);
242     fn get_rp_id_hash(&self) -> Result<ThinVec<u8>, nsresult> {
243         // assertion.auth_data.rp_id_hash
244         let mut out = ThinVec::new();
245         if let Ok(assertion) = &self.result {
246             out.extend_from_slice(&assertion.auth_data.rp_id_hash.0);
247             return Ok(out);
248         }
249         Err(NS_ERROR_FAILURE)
250     }
252     xpcom_method!(get_status => GetStatus() -> nsresult);
253     fn get_status(&self) -> Result<nsresult, nsresult> {
254         match &self.result {
255             Ok(_) => Ok(NS_OK),
256             Err(e) => Ok(authrs_to_nserror(e)),
257         }
258     }
261 /// Controller wraps a raw pointer to an nsIWebAuthnController. The AuthrsTransport struct holds a
262 /// Controller which we need to initialize from the SetController XPCOM method.  Since XPCOM
263 /// methods take &self, Controller must have interior mutability.
264 #[derive(Clone)]
265 struct Controller(RefCell<*const nsIWebAuthnController>);
267 /// Our implementation of nsIWebAuthnController in WebAuthnController.cpp has the property that its
268 /// XPCOM methods are safe to call from any thread, hence a raw pointer to an nsIWebAuthnController
269 /// is Send.
270 unsafe impl Send for Controller {}
272 impl Controller {
273     fn init(&self, ptr: *const nsIWebAuthnController) -> Result<(), nsresult> {
274         self.0.replace(ptr);
275         Ok(())
276     }
278     fn send_prompt(&self, tid: u64, msg: &str) {
279         if (*self.0.borrow()).is_null() {
280             warn!("Controller not initialized");
281             return;
282         }
283         let notification_str = nsCString::from(msg);
284         unsafe {
285             (**(self.0.borrow())).SendPromptNotificationPreformatted(tid, &*notification_str);
286         }
287     }
289     fn finish_register(
290         &self,
291         tid: u64,
292         result: Result<RegisterResult, AuthenticatorError>,
293     ) -> Result<(), nsresult> {
294         if (*self.0.borrow()).is_null() {
295             return Err(NS_ERROR_FAILURE);
296         }
297         let wrapped_result = CtapRegisterResult::allocate(InitCtapRegisterResult { result })
298             .query_interface::<nsICtapRegisterResult>()
299             .ok_or(NS_ERROR_FAILURE)?;
300         unsafe {
301             (**(self.0.borrow())).FinishRegister(tid, wrapped_result.coerce());
302         }
303         Ok(())
304     }
306     fn finish_sign(
307         &self,
308         tid: u64,
309         result: Result<SignResult, AuthenticatorError>,
310     ) -> Result<(), nsresult> {
311         if (*self.0.borrow()).is_null() {
312             return Err(NS_ERROR_FAILURE);
313         }
315         // If result is an error, we return a single CtapSignResult that has its status field set
316         // to an error. Otherwise we convert the entries of SignResult (= Vec<Assertion>) into
317         // CtapSignResults with OK statuses.
318         let mut assertions: ThinVec<Option<RefPtr<nsICtapSignResult>>> = ThinVec::new();
319         match result {
320             Err(e) => assertions.push(
321                 CtapSignResult::allocate(InitCtapSignResult { result: Err(e) })
322                     .query_interface::<nsICtapSignResult>(),
323             ),
324             Ok(result) => {
325                 for assertion in result.assertions {
326                     assertions.push(
327                         CtapSignResult::allocate(InitCtapSignResult {
328                             result: Ok(assertion),
329                         })
330                         .query_interface::<nsICtapSignResult>(),
331                     );
332                 }
333             }
334         }
336         unsafe {
337             (**(self.0.borrow())).FinishSign(tid, &mut assertions);
338         }
339         Ok(())
340     }
343 // The state machine creates a Sender<Pin>/Receiver<Pin> channel in ask_user_for_pin. It passes the
344 // Sender through status_callback, which stores the Sender in the pin_receiver field of an
345 // AuthrsTransport. The u64 in PinReceiver is a transaction ID, which the AuthrsTransport uses the
346 // transaction ID as a consistency check.
347 type PinReceiver = Option<(u64, Sender<Pin>)>;
349 fn status_callback(
350     status_rx: Receiver<StatusUpdate>,
351     tid: u64,
352     origin: &String,
353     browsing_context_id: u64,
354     controller: Controller,
355     pin_receiver: Arc<Mutex<PinReceiver>>, /* Shared with an AuthrsTransport */
356 ) {
357     loop {
358         match status_rx.recv() {
359             Ok(StatusUpdate::SelectDeviceNotice) => {
360                 debug!("STATUS: Please select a device by touching one of them.");
361                 let notification_str =
362                     make_prompt("select-device", tid, origin, browsing_context_id);
363                 controller.send_prompt(tid, &notification_str);
364             }
365             Ok(StatusUpdate::PresenceRequired) => {
366                 debug!("STATUS: Waiting for user presence");
367                 let notification_str = make_prompt("presence", tid, origin, browsing_context_id);
368                 controller.send_prompt(tid, &notification_str);
369             }
370             Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
371                 let guard = pin_receiver.lock();
372                 if let Ok(mut entry) = guard {
373                     entry.replace((tid, sender));
374                 } else {
375                     return;
376                 }
377                 let notification_str =
378                     make_pin_required_prompt(tid, origin, browsing_context_id, false, -1);
379                 controller.send_prompt(tid, &notification_str);
380             }
381             Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
382                 let guard = pin_receiver.lock();
383                 if let Ok(mut entry) = guard {
384                     entry.replace((tid, sender));
385                 } else {
386                     return;
387                 }
388                 let notification_str = make_pin_required_prompt(
389                     tid,
390                     origin,
391                     browsing_context_id,
392                     true,
393                     attempts.map_or(-1, |x| x as i64),
394                 );
395                 controller.send_prompt(tid, &notification_str);
396             }
397             Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
398                 let notification_str =
399                     make_prompt("pin-auth-blocked", tid, origin, browsing_context_id);
400                 controller.send_prompt(tid, &notification_str);
401             }
402             Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
403                 let notification_str =
404                     make_prompt("device-blocked", tid, origin, browsing_context_id);
405                 controller.send_prompt(tid, &notification_str);
406             }
407             Ok(StatusUpdate::PinUvError(StatusPinUv::PinNotSet)) => {
408                 let notification_str = make_prompt("pin-not-set", tid, origin, browsing_context_id);
409                 controller.send_prompt(tid, &notification_str);
410             }
411             Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
412                 let notification_str = make_uv_invalid_error_prompt(
413                     tid,
414                     origin,
415                     browsing_context_id,
416                     attempts.map_or(-1, |x| x as i64),
417                 );
418                 controller.send_prompt(tid, &notification_str);
419             }
420             Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
421                 let notification_str = make_prompt("uv-blocked", tid, origin, browsing_context_id);
422                 controller.send_prompt(tid, &notification_str);
423             }
424             Ok(StatusUpdate::PinUvError(StatusPinUv::PinIsTooShort))
425             | Ok(StatusUpdate::PinUvError(StatusPinUv::PinIsTooLong(..))) => {
426                 // These should never happen.
427                 warn!("STATUS: Got unexpected StatusPinUv-error.");
428             }
429             Ok(StatusUpdate::InteractiveManagement(_)) => {
430                 debug!("STATUS: interactive management");
431             }
432             Err(RecvError) => {
433                 debug!("STATUS: end");
434                 return;
435             }
436         }
437     }
440 // AuthrsTransport provides an nsIWebAuthnTransport interface to an AuthenticatorService. This
441 // allows an nsIWebAuthnController to dispatch MakeCredential and GetAssertion requests to USB HID
442 // tokens. The AuthrsTransport struct also keeps 1) a pointer to the active nsIWebAuthnController,
443 // which is used to prompt the user for input and to return results to the controller; and
444 // 2) a channel through which to receive a pin callback.
445 #[xpcom(implement(nsIWebAuthnTransport), atomic)]
446 pub struct AuthrsTransport {
447     usb_token_manager: RefCell<StateMachine>, // interior mutable for use in XPCOM methods
448     test_token_manager: TestTokenManager,
449     controller: Controller,
450     pin_receiver: Arc<Mutex<PinReceiver>>,
453 impl AuthrsTransport {
454     xpcom_method!(get_controller => GetController() -> *const nsIWebAuthnController);
455     fn get_controller(&self) -> Result<RefPtr<nsIWebAuthnController>, nsresult> {
456         Err(NS_ERROR_NOT_IMPLEMENTED)
457     }
459     // # Safety
460     //
461     // This will mutably borrow the controller pointer through a RefCell. The caller must ensure
462     // that at most one WebAuthn transaction is active at any given time.
463     xpcom_method!(set_controller => SetController(aController: *const nsIWebAuthnController));
464     fn set_controller(&self, controller: *const nsIWebAuthnController) -> Result<(), nsresult> {
465         self.controller.init(controller)
466     }
468     xpcom_method!(pin_callback => PinCallback(aTransactionId: u64, aPin: *const nsACString));
469     fn pin_callback(&self, transaction_id: u64, pin: &nsACString) -> Result<(), nsresult> {
470         let mut guard = self.pin_receiver.lock().or(Err(NS_ERROR_FAILURE))?;
471         match guard.take() {
472             // The pin_receiver is single-use.
473             Some((tid, channel)) if tid == transaction_id => channel
474                 .send(Pin::new(&pin.to_string()))
475                 .or(Err(NS_ERROR_FAILURE)),
476             // Either we weren't expecting a pin, or the controller is confused
477             // about which transaction is active. Neither is recoverable, so it's
478             // OK to drop the PinReceiver here.
479             _ => Err(NS_ERROR_FAILURE),
480         }
481     }
483     // # Safety
484     //
485     // This will mutably borrow usb_token_manager through a RefCell. The caller must ensure that at
486     // most one WebAuthn transaction is active at any given time.
487     xpcom_method!(make_credential => MakeCredential(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsICtapRegisterArgs));
488     fn make_credential(
489         &self,
490         tid: u64,
491         browsing_context_id: u64,
492         args: *const nsICtapRegisterArgs,
493     ) -> Result<(), nsresult> {
494         if args.is_null() {
495             return Err(NS_ERROR_NULL_POINTER);
496         }
497         let args = unsafe { &*args };
499         let mut origin = nsString::new();
500         unsafe { args.GetOrigin(&mut *origin) }.to_result()?;
502         let mut relying_party_id = nsString::new();
503         unsafe { args.GetRpId(&mut *relying_party_id) }.to_result()?;
505         let mut client_data_hash = ThinVec::new();
506         unsafe { args.GetClientDataHash(&mut client_data_hash) }.to_result()?;
507         let mut client_data_hash_arr = [0u8; 32];
508         client_data_hash_arr.copy_from_slice(&client_data_hash);
510         let mut timeout_ms = 0u32;
511         unsafe { args.GetTimeoutMS(&mut timeout_ms) }.to_result()?;
513         let mut exclude_list = ThinVec::new();
514         unsafe { args.GetExcludeList(&mut exclude_list) }.to_result()?;
515         let exclude_list = exclude_list
516             .iter_mut()
517             .map(|id| PublicKeyCredentialDescriptor {
518                 id: id.to_vec(),
519                 transports: vec![],
520             })
521             .collect();
523         let mut relying_party_name = nsString::new();
524         unsafe { args.GetRpName(&mut *relying_party_name) }.to_result()?;
526         let mut user_id = ThinVec::new();
527         unsafe { args.GetUserId(&mut user_id) }.to_result()?;
529         let mut user_name = nsString::new();
530         unsafe { args.GetUserName(&mut *user_name) }.to_result()?;
532         let mut user_display_name = nsString::new();
533         unsafe { args.GetUserDisplayName(&mut *user_display_name) }.to_result()?;
535         let mut cose_algs = ThinVec::new();
536         unsafe { args.GetCoseAlgs(&mut cose_algs) }.to_result()?;
537         let pub_cred_params = cose_algs
538             .iter()
539             .filter_map(|alg| PublicKeyCredentialParameters::try_from(*alg).ok())
540             .collect();
542         let mut resident_key = nsString::new();
543         unsafe { args.GetResidentKey(&mut *resident_key) }.to_result()?;
544         let resident_key_req = if resident_key.eq("required") {
545             ResidentKeyRequirement::Required
546         } else if resident_key.eq("preferred") {
547             ResidentKeyRequirement::Preferred
548         } else if resident_key.eq("discouraged") {
549             ResidentKeyRequirement::Discouraged
550         } else {
551             return Err(NS_ERROR_FAILURE);
552         };
554         let mut user_verification = nsString::new();
555         unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
556         let user_verification_req = if user_verification.eq("required") {
557             UserVerificationRequirement::Required
558         } else if user_verification.eq("discouraged") {
559             UserVerificationRequirement::Discouraged
560         } else {
561             UserVerificationRequirement::Preferred
562         };
564         let mut authenticator_attachment = nsString::new();
565         if unsafe { args.GetAuthenticatorAttachment(&mut *authenticator_attachment) }
566             .to_result()
567             .is_ok()
568         {
569             if authenticator_attachment.eq("platform") {
570                 return Err(NS_ERROR_FAILURE);
571             }
572         }
574         let mut attestation_conveyance_preference = nsString::new();
575         unsafe { args.GetAttestationConveyancePreference(&mut *attestation_conveyance_preference) }
576             .to_result()?;
577         let none_attestation = attestation_conveyance_preference.eq("none");
579         // TODO(Bug 1593571) - Add this to the extensions
580         // let mut hmac_create_secret = None;
581         // let mut maybe_hmac_create_secret = false;
582         // match unsafe { args.GetHmacCreateSecret(&mut maybe_hmac_create_secret) }.to_result() {
583         //     Ok(_) => hmac_create_secret = Some(maybe_hmac_create_secret),
584         //     _ => (),
585         // }
587         let info = RegisterArgs {
588             client_data_hash: client_data_hash_arr,
589             relying_party: RelyingParty {
590                 id: relying_party_id.to_string(),
591                 name: None,
592             },
593             origin: origin.to_string(),
594             user: User {
595                 id: user_id.to_vec(),
596                 name: Some(user_name.to_string()),
597                 display_name: None,
598             },
599             pub_cred_params,
600             exclude_list,
601             user_verification_req,
602             resident_key_req,
603             extensions: Default::default(),
604             pin: None,
605             use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
606         };
608         let (status_tx, status_rx) = channel::<StatusUpdate>();
609         let pin_receiver = self.pin_receiver.clone();
610         let controller = self.controller.clone();
611         let status_origin = origin.to_string();
612         RunnableBuilder::new(
613             "AuthrsTransport::MakeCredential::StatusReceiver",
614             move || {
615                 status_callback(
616                     status_rx,
617                     tid,
618                     &status_origin,
619                     browsing_context_id,
620                     controller,
621                     pin_receiver,
622                 )
623             },
624         )
625         .may_block(true)
626         .dispatch_background_task()?;
628         let controller = self.controller.clone();
629         let callback_origin = origin.to_string();
630         let state_callback = StateCallback::<Result<RegisterResult, AuthenticatorError>>::new(
631             Box::new(move |result| {
632                 let result = match result {
633                     Ok(mut make_cred_res) => {
634                         // Tokens always provide attestation, but the user may have asked we not
635                         // include the attestation statement in the response.
636                         if none_attestation {
637                             make_cred_res.att_obj.anonymize();
638                         }
639                         Ok(make_cred_res)
640                     }
641                     Err(e @ AuthenticatorError::CredentialExcluded) => {
642                         let notification_str = make_prompt(
643                             "already-registered",
644                             tid,
645                             &callback_origin,
646                             browsing_context_id,
647                         );
648                         controller.send_prompt(tid, &notification_str);
649                         Err(e)
650                     }
651                     Err(e) => Err(e),
652                 };
653                 let _ = controller.finish_register(tid, result);
654             }),
655         );
657         // The authenticator crate provides an `AuthenticatorService` which can dispatch a request
658         // in parallel to any number of transports. We only support the USB transport in production
659         // configurations, so we do not need the full generality of `AuthenticatorService` here.
660         // We disable the USB transport in tests that use virtual devices.
661         if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
662             self.usb_token_manager.borrow_mut().register(
663                 timeout_ms as u64,
664                 info.into(),
665                 status_tx,
666                 state_callback,
667             );
668         } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
669             self.test_token_manager.register(
670                 timeout_ms as u64,
671                 info.into(),
672                 status_tx,
673                 state_callback,
674             );
675         } else {
676             return Err(NS_ERROR_FAILURE);
677         }
679         Ok(())
680     }
682     // # Safety
683     //
684     // This will mutably borrow usb_token_manager through a RefCell. The caller must ensure that at
685     // most one WebAuthn transaction is active at any given time.
686     xpcom_method!(get_assertion => GetAssertion(aTid: u64, aBrowsingContextId: u64, aArgs: *const nsICtapSignArgs));
687     fn get_assertion(
688         &self,
689         tid: u64,
690         browsing_context_id: u64,
691         args: *const nsICtapSignArgs,
692     ) -> Result<(), nsresult> {
693         if args.is_null() {
694             return Err(NS_ERROR_NULL_POINTER);
695         }
696         let args = unsafe { &*args };
698         let mut origin = nsString::new();
699         unsafe { args.GetOrigin(&mut *origin) }.to_result()?;
701         let mut relying_party_id = nsString::new();
702         unsafe { args.GetRpId(&mut *relying_party_id) }.to_result()?;
704         let mut client_data_hash = ThinVec::new();
705         unsafe { args.GetClientDataHash(&mut client_data_hash) }.to_result()?;
706         let mut client_data_hash_arr = [0u8; 32];
707         client_data_hash_arr.copy_from_slice(&client_data_hash);
709         let mut timeout_ms = 0u32;
710         unsafe { args.GetTimeoutMS(&mut timeout_ms) }.to_result()?;
712         let mut allow_list = ThinVec::new();
713         unsafe { args.GetAllowList(&mut allow_list) }.to_result()?;
714         let allow_list: Vec<_> = allow_list
715             .iter_mut()
716             .map(|id| PublicKeyCredentialDescriptor {
717                 id: id.to_vec(),
718                 transports: vec![],
719             })
720             .collect();
722         let mut user_verification = nsString::new();
723         unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?;
724         let user_verification_req = if user_verification.eq("required") {
725             UserVerificationRequirement::Required
726         } else if user_verification.eq("discouraged") {
727             UserVerificationRequirement::Discouraged
728         } else {
729             UserVerificationRequirement::Preferred
730         };
732         let mut app_id = None;
733         let mut maybe_app_id = nsString::new();
734         match unsafe { args.GetAppId(&mut *maybe_app_id) }.to_result() {
735             Ok(_) => app_id = Some(maybe_app_id.to_string()),
736             _ => (),
737         }
739         let (status_tx, status_rx) = channel::<StatusUpdate>();
740         let pin_receiver = self.pin_receiver.clone();
741         let controller = self.controller.clone();
742         let status_origin = origin.to_string();
743         RunnableBuilder::new("AuthrsTransport::GetAssertion::StatusReceiver", move || {
744             status_callback(
745                 status_rx,
746                 tid,
747                 &status_origin,
748                 browsing_context_id,
749                 controller,
750                 pin_receiver,
751             )
752         })
753         .may_block(true)
754         .dispatch_background_task()?;
756         let uniq_allowed_cred = if allow_list.len() == 1 {
757             allow_list.first().cloned()
758         } else {
759             None
760         };
762         let controller = self.controller.clone();
763         let state_callback = StateCallback::<Result<SignResult, AuthenticatorError>>::new(
764             Box::new(move |mut result| {
765                 if uniq_allowed_cred.is_some() {
766                     // In CTAP 2.0, but not CTAP 2.1, the assertion object's credential field
767                     // "May be omitted if the allowList has exactly one credential." If we had
768                     // a unique allowed credential, then copy its descriptor to the output.
769                     if let Ok(Some(assertion)) =
770                         result.as_mut().map(|result| result.assertions.first_mut())
771                     {
772                         assertion.credentials = uniq_allowed_cred;
773                     }
774                 }
775                 let _ = controller.finish_sign(tid, result);
776             }),
777         );
779         let info = SignArgs {
780             client_data_hash: client_data_hash_arr,
781             relying_party_id: relying_party_id.to_string(),
782             origin: origin.to_string(),
783             allow_list,
784             user_verification_req,
785             user_presence_req: true,
786             extensions: AuthenticationExtensionsClientInputs {
787                 app_id,
788                 ..Default::default()
789             },
790             pin: None,
791             use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
792         };
794         // As in `register`, we are intentionally avoiding `AuthenticatorService` here.
795         if static_prefs::pref!("security.webauth.webauthn_enable_usbtoken") {
796             self.usb_token_manager.borrow_mut().sign(
797                 timeout_ms as u64,
798                 info.into(),
799                 status_tx,
800                 state_callback,
801             );
802         } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
803             self.test_token_manager
804                 .sign(timeout_ms as u64, info.into(), status_tx, state_callback);
805         } else {
806             return Err(NS_ERROR_FAILURE);
807         }
809         Ok(())
810     }
812     // # Safety
813     //
814     // This will mutably borrow usb_token_manager through a RefCell. The caller must ensure that at
815     // most one WebAuthn transaction is active at any given time.
816     xpcom_method!(cancel => Cancel());
817     fn cancel(&self) -> Result<(), nsresult> {
818         // We may be waiting for a pin. Drop the channel to release the
819         // state machine from `ask_user_for_pin`.
820         drop(self.pin_receiver.lock().or(Err(NS_ERROR_FAILURE))?.take());
822         self.usb_token_manager.borrow_mut().cancel();
824         Ok(())
825     }
827     xpcom_method!(
828         add_virtual_authenticator => AddVirtualAuthenticator(
829             protocol: *const nsACString,
830             transport: *const nsACString,
831             hasResidentKey: bool,
832             hasUserVerification: bool,
833             isUserConsenting: bool,
834             isUserVerified: bool) -> u64
835     );
836     fn add_virtual_authenticator(
837         &self,
838         protocol: &nsACString,
839         transport: &nsACString,
840         has_resident_key: bool,
841         has_user_verification: bool,
842         is_user_consenting: bool,
843         is_user_verified: bool,
844     ) -> Result<u64, nsresult> {
845         let protocol = match protocol.to_string().as_str() {
846             "ctap1/u2f" => AuthenticatorVersion::U2F_V2,
847             "ctap2" => AuthenticatorVersion::FIDO_2_0,
848             "ctap2_1" => AuthenticatorVersion::FIDO_2_1,
849             _ => return Err(NS_ERROR_INVALID_ARG),
850         };
851         match transport.to_string().as_str() {
852             "usb" | "nfc" | "ble" | "smart-card" | "hybrid" | "internal" => (),
853             _ => return Err(NS_ERROR_INVALID_ARG),
854         };
855         self.test_token_manager.add_virtual_authenticator(
856             protocol,
857             has_resident_key,
858             has_user_verification,
859             is_user_consenting,
860             is_user_verified,
861         )
862     }
864     xpcom_method!(remove_virtual_authenticator => RemoveVirtualAuthenticator(authenticatorId: u64));
865     fn remove_virtual_authenticator(&self, authenticator_id: u64) -> Result<(), nsresult> {
866         self.test_token_manager
867             .remove_virtual_authenticator(authenticator_id)
868     }
870     xpcom_method!(
871         add_credential => AddCredential(
872             authenticatorId: u64,
873             credentialId: *const nsACString,
874             isResidentCredential: bool,
875             rpId: *const nsACString,
876             privateKey: *const nsACString,
877             userHandle: *const nsACString,
878             signCount: u32)
879     );
880     fn add_credential(
881         &self,
882         authenticator_id: u64,
883         credential_id: &nsACString,
884         is_resident_credential: bool,
885         rp_id: &nsACString,
886         private_key: &nsACString,
887         user_handle: &nsACString,
888         sign_count: u32,
889     ) -> Result<(), nsresult> {
890         let credential_id = base64::engine::general_purpose::URL_SAFE_NO_PAD
891             .decode(credential_id)
892             .or(Err(NS_ERROR_INVALID_ARG))?;
893         let private_key = base64::engine::general_purpose::URL_SAFE_NO_PAD
894             .decode(private_key)
895             .or(Err(NS_ERROR_INVALID_ARG))?;
896         let user_handle = base64::engine::general_purpose::URL_SAFE_NO_PAD
897             .decode(user_handle)
898             .or(Err(NS_ERROR_INVALID_ARG))?;
899         self.test_token_manager.add_credential(
900             authenticator_id,
901             &credential_id,
902             &private_key,
903             &user_handle,
904             sign_count,
905             rp_id.to_string(),
906             is_resident_credential,
907         )
908     }
910     xpcom_method!(get_credentials => GetCredentials(authenticatorId: u64) -> ThinVec<Option<RefPtr<nsICredentialParameters>>>);
911     fn get_credentials(
912         &self,
913         authenticator_id: u64,
914     ) -> Result<ThinVec<Option<RefPtr<nsICredentialParameters>>>, nsresult> {
915         self.test_token_manager.get_credentials(authenticator_id)
916     }
918     xpcom_method!(remove_credential => RemoveCredential(authenticatorId: u64, credentialId: *const nsACString));
919     fn remove_credential(
920         &self,
921         authenticator_id: u64,
922         credential_id: &nsACString,
923     ) -> Result<(), nsresult> {
924         let credential_id = base64::engine::general_purpose::URL_SAFE_NO_PAD
925             .decode(credential_id)
926             .or(Err(NS_ERROR_INVALID_ARG))?;
927         self.test_token_manager
928             .remove_credential(authenticator_id, credential_id.as_ref())
929     }
931     xpcom_method!(remove_all_credentials => RemoveAllCredentials(authenticatorId: u64));
932     fn remove_all_credentials(&self, authenticator_id: u64) -> Result<(), nsresult> {
933         self.test_token_manager
934             .remove_all_credentials(authenticator_id)
935     }
937     xpcom_method!(set_user_verified => SetUserVerified(authenticatorId: u64, isUserVerified: bool));
938     fn set_user_verified(
939         &self,
940         authenticator_id: u64,
941         is_user_verified: bool,
942     ) -> Result<(), nsresult> {
943         self.test_token_manager
944             .set_user_verified(authenticator_id, is_user_verified)
945     }
948 #[no_mangle]
949 pub extern "C" fn authrs_transport_constructor(
950     result: *mut *const nsIWebAuthnTransport,
951 ) -> nsresult {
952     let wrapper = AuthrsTransport::allocate(InitAuthrsTransport {
953         usb_token_manager: RefCell::new(StateMachine::new()),
954         test_token_manager: TestTokenManager::new(),
955         controller: Controller(RefCell::new(std::ptr::null())),
956         pin_receiver: Arc::new(Mutex::new(None)),
957     });
959     #[cfg(feature = "fuzzing")]
960     {
961         let fuzzing_config = static_prefs::pref!("fuzzing.webauthn.authenticator_config");
962         if fuzzing_config != 0 {
963             let is_user_verified = (fuzzing_config & 0x01) != 0;
964             let is_user_consenting = (fuzzing_config & 0x02) != 0;
965             let has_user_verification = (fuzzing_config & 0x04) != 0;
966             let has_resident_key = (fuzzing_config & 0x08) != 0;
967             let transport = nsCString::from(match (fuzzing_config & 0x10) >> 4 {
968                 0 => "usb",
969                 1 => "internal",
970                 _ => unreachable!(),
971             });
972             let protocol = nsCString::from(match (fuzzing_config & 0x60) >> 5 {
973                 0 => "", // reserved
974                 1 => "ctap1/u2f",
975                 2 => "ctap2",
976                 3 => "ctap2_1",
977                 _ => unreachable!(),
978             });
979             // If this fails it's probably because the protocol bits were zero,
980             // we'll just ignore it.
981             let _ = wrapper.add_virtual_authenticator(
982                 &protocol,
983                 &transport,
984                 has_resident_key,
985                 has_user_verification,
986                 is_user_consenting,
987                 is_user_verified,
988             );
989         }
990     }
992     unsafe {
993         RefPtr::new(wrapper.coerce::<nsIWebAuthnTransport>()).forget(&mut *result);
994     }
995     NS_OK
998 #[no_mangle]
999 pub extern "C" fn authrs_webauthn_att_obj_constructor(
1000     att_obj_bytes: &ThinVec<u8>,
1001     anonymize: bool,
1002     result: *mut *const nsIWebAuthnAttObj,
1003 ) -> nsresult {
1004     if result.is_null() {
1005         return NS_ERROR_NULL_POINTER;
1006     }
1008     let mut att_obj: AttestationObject = match serde_cbor::from_slice(att_obj_bytes) {
1009         Ok(att_obj) => att_obj,
1010         Err(_) => return NS_ERROR_INVALID_ARG,
1011     };
1013     if anonymize {
1014         att_obj.anonymize();
1015     }
1017     let wrapper = WebAuthnAttObj::allocate(InitWebAuthnAttObj { att_obj });
1019     unsafe {
1020         RefPtr::new(wrapper.coerce::<nsIWebAuthnAttObj>()).forget(&mut *result);
1021     }
1023     NS_OK