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/. */
12 authenticatorservice::{RegisterArgs, SignArgs},
14 ctap2::attestation::AttestationObject,
15 ctap2::commands::get_info::AuthenticatorVersion,
17 AuthenticationExtensionsClientInputs, PublicKeyCredentialDescriptor,
18 PublicKeyCredentialParameters, RelyingParty, ResidentKeyRequirement, User,
19 UserVerificationRequirement,
21 errors::{AuthenticatorError, PinError, U2FTokenError},
22 statecallback::StateCallback,
23 Assertion, Pin, RegisterResult, SignResult, StateMachine, StatusPinUv, StatusUpdate,
26 use moz_task::RunnableBuilder;
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,
33 use nsstring::{nsACString, nsCString, nsString};
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};
46 use test_token::TestTokenManager;
48 fn make_prompt(action: &str, tid: u64, origin: &str, browsing_context_id: u64) -> String {
50 r#"{{"is_ctap2":true,"action":"{action}","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id}}}"#,
54 fn make_uv_invalid_error_prompt(
57 browsing_context_id: u64,
61 r#"{{"is_ctap2":true,"action":"uv-invalid","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id},"retriesLeft":{retries}}}"#,
65 fn make_pin_required_prompt(
68 browsing_context_id: u64,
73 r#"{{"is_ctap2":true,"action":"pin-required","tid":{tid},"origin":"{origin}","browsingContextId":{browsing_context_id},"wasInvalid":{was_invalid},"retriesLeft":{retries}}}"#,
77 fn authrs_to_nserror(e: &AuthenticatorError) -> nsresult {
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,
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))?;
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);
115 Err(NS_ERROR_FAILURE)
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);
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
127 Ok(thin_vec![nsString::from("usb")])
130 xpcom_method!(get_status => GetStatus() -> nsresult);
131 fn get_status(&self) -> Result<nsresult, nsresult> {
134 Err(e) => Ok(authrs_to_nserror(e)),
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))?;
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())
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);
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);
167 Ok(key.der_spki().or(Err(NS_ERROR_NOT_AVAILABLE))?.into())
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)
176 Err(NS_ERROR_FAILURE)
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);
196 Err(NS_ERROR_FAILURE)
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);
206 Err(NS_ERROR_FAILURE)
209 xpcom_method!(get_authenticator_data => GetAuthenticatorData() -> ThinVec<u8>);
210 fn get_authenticator_data(&self) -> Result<ThinVec<u8>, nsresult> {
213 .map(|assertion| assertion.auth_data.to_vec().into())
214 .or(Err(NS_ERROR_FAILURE))
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);
226 Err(NS_ERROR_FAILURE)
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));
238 Err(NS_ERROR_NOT_AVAILABLE)
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);
249 Err(NS_ERROR_FAILURE)
252 xpcom_method!(get_status => GetStatus() -> nsresult);
253 fn get_status(&self) -> Result<nsresult, nsresult> {
256 Err(e) => Ok(authrs_to_nserror(e)),
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.
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
270 unsafe impl Send for Controller {}
273 fn init(&self, ptr: *const nsIWebAuthnController) -> Result<(), nsresult> {
278 fn send_prompt(&self, tid: u64, msg: &str) {
279 if (*self.0.borrow()).is_null() {
280 warn!("Controller not initialized");
283 let notification_str = nsCString::from(msg);
285 (**(self.0.borrow())).SendPromptNotificationPreformatted(tid, &*notification_str);
292 result: Result<RegisterResult, AuthenticatorError>,
293 ) -> Result<(), nsresult> {
294 if (*self.0.borrow()).is_null() {
295 return Err(NS_ERROR_FAILURE);
297 let wrapped_result = CtapRegisterResult::allocate(InitCtapRegisterResult { result })
298 .query_interface::<nsICtapRegisterResult>()
299 .ok_or(NS_ERROR_FAILURE)?;
301 (**(self.0.borrow())).FinishRegister(tid, wrapped_result.coerce());
309 result: Result<SignResult, AuthenticatorError>,
310 ) -> Result<(), nsresult> {
311 if (*self.0.borrow()).is_null() {
312 return Err(NS_ERROR_FAILURE);
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();
320 Err(e) => assertions.push(
321 CtapSignResult::allocate(InitCtapSignResult { result: Err(e) })
322 .query_interface::<nsICtapSignResult>(),
325 for assertion in result.assertions {
327 CtapSignResult::allocate(InitCtapSignResult {
328 result: Ok(assertion),
330 .query_interface::<nsICtapSignResult>(),
337 (**(self.0.borrow())).FinishSign(tid, &mut assertions);
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>)>;
350 status_rx: Receiver<StatusUpdate>,
353 browsing_context_id: u64,
354 controller: Controller,
355 pin_receiver: Arc<Mutex<PinReceiver>>, /* Shared with an AuthrsTransport */
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, ¬ification_str);
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, ¬ification_str);
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));
377 let notification_str =
378 make_pin_required_prompt(tid, origin, browsing_context_id, false, -1);
379 controller.send_prompt(tid, ¬ification_str);
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));
388 let notification_str = make_pin_required_prompt(
393 attempts.map_or(-1, |x| x as i64),
395 controller.send_prompt(tid, ¬ification_str);
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, ¬ification_str);
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, ¬ification_str);
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, ¬ification_str);
411 Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
412 let notification_str = make_uv_invalid_error_prompt(
416 attempts.map_or(-1, |x| x as i64),
418 controller.send_prompt(tid, ¬ification_str);
420 Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
421 let notification_str = make_prompt("uv-blocked", tid, origin, browsing_context_id);
422 controller.send_prompt(tid, ¬ification_str);
424 Ok(StatusUpdate::PinUvError(StatusPinUv::PinIsTooShort))
425 | Ok(StatusUpdate::PinUvError(StatusPinUv::PinIsTooLong(..))) => {
426 // These should never happen.
427 warn!("STATUS: Got unexpected StatusPinUv-error.");
429 Ok(StatusUpdate::InteractiveManagement(_)) => {
430 debug!("STATUS: interactive management");
433 debug!("STATUS: end");
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)
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)
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))?;
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),
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));
491 browsing_context_id: u64,
492 args: *const nsICtapRegisterArgs,
493 ) -> Result<(), nsresult> {
495 return Err(NS_ERROR_NULL_POINTER);
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
517 .map(|id| PublicKeyCredentialDescriptor {
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
539 .filter_map(|alg| PublicKeyCredentialParameters::try_from(*alg).ok())
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
551 return Err(NS_ERROR_FAILURE);
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
561 UserVerificationRequirement::Preferred
564 let mut authenticator_attachment = nsString::new();
565 if unsafe { args.GetAuthenticatorAttachment(&mut *authenticator_attachment) }
569 if authenticator_attachment.eq("platform") {
570 return Err(NS_ERROR_FAILURE);
574 let mut attestation_conveyance_preference = nsString::new();
575 unsafe { args.GetAttestationConveyancePreference(&mut *attestation_conveyance_preference) }
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),
587 let info = RegisterArgs {
588 client_data_hash: client_data_hash_arr,
589 relying_party: RelyingParty {
590 id: relying_party_id.to_string(),
593 origin: origin.to_string(),
595 id: user_id.to_vec(),
596 name: Some(user_name.to_string()),
601 user_verification_req,
603 extensions: Default::default(),
605 use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
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",
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();
641 Err(e @ AuthenticatorError::CredentialExcluded) => {
642 let notification_str = make_prompt(
643 "already-registered",
648 controller.send_prompt(tid, ¬ification_str);
653 let _ = controller.finish_register(tid, result);
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(
668 } else if static_prefs::pref!("security.webauth.webauthn_enable_softtoken") {
669 self.test_token_manager.register(
676 return Err(NS_ERROR_FAILURE);
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));
690 browsing_context_id: u64,
691 args: *const nsICtapSignArgs,
692 ) -> Result<(), nsresult> {
694 return Err(NS_ERROR_NULL_POINTER);
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
716 .map(|id| PublicKeyCredentialDescriptor {
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
729 UserVerificationRequirement::Preferred
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()),
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 || {
754 .dispatch_background_task()?;
756 let uniq_allowed_cred = if allow_list.len() == 1 {
757 allow_list.first().cloned()
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())
772 assertion.credentials = uniq_allowed_cred;
775 let _ = controller.finish_sign(tid, result);
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(),
784 user_verification_req,
785 user_presence_req: true,
786 extensions: AuthenticationExtensionsClientInputs {
791 use_ctap1_fallback: !static_prefs::pref!("security.webauthn.ctap2"),
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(
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);
806 return Err(NS_ERROR_FAILURE);
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();
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
836 fn add_virtual_authenticator(
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),
851 match transport.to_string().as_str() {
852 "usb" | "nfc" | "ble" | "smart-card" | "hybrid" | "internal" => (),
853 _ => return Err(NS_ERROR_INVALID_ARG),
855 self.test_token_manager.add_virtual_authenticator(
858 has_user_verification,
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)
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,
882 authenticator_id: u64,
883 credential_id: &nsACString,
884 is_resident_credential: bool,
886 private_key: &nsACString,
887 user_handle: &nsACString,
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
895 .or(Err(NS_ERROR_INVALID_ARG))?;
896 let user_handle = base64::engine::general_purpose::URL_SAFE_NO_PAD
898 .or(Err(NS_ERROR_INVALID_ARG))?;
899 self.test_token_manager.add_credential(
906 is_resident_credential,
910 xpcom_method!(get_credentials => GetCredentials(authenticatorId: u64) -> ThinVec<Option<RefPtr<nsICredentialParameters>>>);
913 authenticator_id: u64,
914 ) -> Result<ThinVec<Option<RefPtr<nsICredentialParameters>>>, nsresult> {
915 self.test_token_manager.get_credentials(authenticator_id)
918 xpcom_method!(remove_credential => RemoveCredential(authenticatorId: u64, credentialId: *const nsACString));
919 fn remove_credential(
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())
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)
937 xpcom_method!(set_user_verified => SetUserVerified(authenticatorId: u64, isUserVerified: bool));
938 fn set_user_verified(
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)
949 pub extern "C" fn authrs_transport_constructor(
950 result: *mut *const nsIWebAuthnTransport,
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)),
959 #[cfg(feature = "fuzzing")]
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 {
972 let protocol = nsCString::from(match (fuzzing_config & 0x60) >> 5 {
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(
985 has_user_verification,
993 RefPtr::new(wrapper.coerce::<nsIWebAuthnTransport>()).forget(&mut *result);
999 pub extern "C" fn authrs_webauthn_att_obj_constructor(
1000 att_obj_bytes: &ThinVec<u8>,
1002 result: *mut *const nsIWebAuthnAttObj,
1004 if result.is_null() {
1005 return NS_ERROR_NULL_POINTER;
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,
1014 att_obj.anonymize();
1017 let wrapper = WebAuthnAttObj::allocate(InitWebAuthnAttObj { att_obj });
1020 RefPtr::new(wrapper.coerce::<nsIWebAuthnAttObj>()).forget(&mut *result);