1#[cfg(feature = "std")]
6use crate::error::Error;
7use crate::error::Result;
8use crate::types::{Credential, CredentialRef};
9use soft_fido2_ctap::authenticator::{
10 Authenticator as CtapAuthenticator, AuthenticatorConfig as CtapConfig,
11};
12use soft_fido2_ctap::callbacks::{
13 CredentialStorageCallbacks, PinStorageCallbacks, UpResult as CtapUpResult,
14 UserInteractionCallbacks, UvResult as CtapUvResult,
15};
16use soft_fido2_ctap::cbor::MAX_CTAP_MESSAGE_SIZE;
17use soft_fido2_ctap::types::{Credential as CtapCredential, PinState};
18use soft_fido2_ctap::{CommandDispatcher, StatusCode};
19
20use alloc::string::String;
21use alloc::sync::Arc;
22use alloc::vec;
23use alloc::vec::Vec;
24
25#[cfg(feature = "std")]
26use std::sync::{Mutex, OnceLock};
27
28#[cfg(not(feature = "std"))]
29use spin::Mutex;
30
31#[cfg(feature = "std")]
33static PRESET_PIN_HASH: OnceLock<Mutex<Option<[u8; 32]>>> = OnceLock::new();
34
35#[cfg(not(feature = "std"))]
37static PRESET_PIN_HASH: Mutex<Option<[u8; 32]>> = Mutex::new(None);
38
39struct NoOpPinStorage;
41
42impl PinStorageCallbacks for NoOpPinStorage {
43 fn load_pin_state(&self) -> core::result::Result<PinState, StatusCode> {
44 Err(StatusCode::Other)
45 }
46
47 fn save_pin_state(&self, _state: &PinState) -> core::result::Result<(), StatusCode> {
48 Ok(())
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum UpResult {
55 Denied,
56 Accepted,
57 Timeout,
58}
59
60impl From<UpResult> for CtapUpResult {
61 fn from(result: UpResult) -> Self {
62 match result {
63 UpResult::Denied => CtapUpResult::Denied,
64 UpResult::Accepted => CtapUpResult::Accepted,
65 UpResult::Timeout => CtapUpResult::Timeout,
66 }
67 }
68}
69
70impl From<CtapUpResult> for UpResult {
71 fn from(result: CtapUpResult) -> Self {
72 match result {
73 CtapUpResult::Denied => UpResult::Denied,
74 CtapUpResult::Accepted => UpResult::Accepted,
75 CtapUpResult::Timeout => UpResult::Timeout,
76 }
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum UvResult {
83 Denied,
84 Accepted,
85 AcceptedWithUp,
86 Timeout,
87}
88
89impl From<UvResult> for CtapUvResult {
90 fn from(result: UvResult) -> Self {
91 match result {
92 UvResult::Denied => CtapUvResult::Denied,
93 UvResult::Accepted => CtapUvResult::Accepted,
94 UvResult::AcceptedWithUp => CtapUvResult::AcceptedWithUp,
95 UvResult::Timeout => CtapUvResult::Timeout,
96 }
97 }
98}
99
100impl From<CtapUvResult> for UvResult {
101 fn from(result: CtapUvResult) -> Self {
102 match result {
103 CtapUvResult::Denied => UvResult::Denied,
104 CtapUvResult::Accepted => UvResult::Accepted,
105 CtapUvResult::AcceptedWithUp => UvResult::AcceptedWithUp,
106 CtapUvResult::Timeout => UvResult::Timeout,
107 }
108 }
109}
110
111pub trait AuthenticatorCallbacks: Send + Sync {
170 fn request_up(&self, info: &str, user_name: Option<&str>, rp_id: &str) -> Result<UpResult>;
172
173 fn request_uv(&self, info: &str, user_name: Option<&str>, rp_id: &str) -> Result<UvResult>;
175
176 fn write_credential(&self, credential: &CredentialRef) -> Result<()>;
178
179 fn read_credential(&self, cred_id: &[u8]) -> Result<Option<Credential>>;
181
182 fn delete_credential(&self, cred_id: &[u8]) -> Result<()>;
184
185 fn list_credentials(&self, rp_id: &str, user_id: Option<&[u8]>) -> Result<Vec<Credential>>;
187
188 fn select_credential(&self, _rp_id: &str, _credentials: &[Credential]) -> Result<usize> {
190 Ok(0)
191 }
192
193 fn enumerate_rps(&self) -> Result<Vec<(String, Option<String>, usize)>>;
201
202 fn credential_count(&self) -> Result<usize>;
208
209 fn get_timestamp_ms(&self) -> u64;
214}
215
216struct CallbackAdapter<C: AuthenticatorCallbacks> {
218 callbacks: Arc<C>,
219}
220
221impl<C: AuthenticatorCallbacks> soft_fido2_ctap::callbacks::PlatformCallbacks
222 for CallbackAdapter<C>
223{
224 fn get_timestamp_ms(&self) -> u64 {
225 self.callbacks.get_timestamp_ms()
226 }
227}
228
229impl<C: AuthenticatorCallbacks> UserInteractionCallbacks for CallbackAdapter<C> {
230 fn request_up(
231 &self,
232 info: &str,
233 user_name: Option<&str>,
234 rp_id: &str,
235 ) -> soft_fido2_ctap::Result<CtapUpResult> {
236 let result = self
237 .callbacks
238 .request_up(info, user_name, rp_id)
239 .map_err(|_| StatusCode::Other)?;
240 Ok(result.into())
241 }
242
243 fn request_uv(
244 &self,
245 info: &str,
246 user_name: Option<&str>,
247 rp_id: &str,
248 ) -> soft_fido2_ctap::Result<CtapUvResult> {
249 let result = self
250 .callbacks
251 .request_uv(info, user_name, rp_id)
252 .map_err(|_| StatusCode::Other)?;
253 Ok(result.into())
254 }
255
256 fn select_credential(
257 &self,
258 rp_id: &str,
259 _user_names: &[String],
260 ) -> soft_fido2_ctap::Result<usize> {
261 let credentials = self
264 .callbacks
265 .list_credentials(rp_id, None)
266 .map_err(|_| StatusCode::Other)?;
267
268 self.callbacks
269 .select_credential(rp_id, &credentials)
270 .map_err(|_| StatusCode::Other)
271 }
272}
273
274impl<C: AuthenticatorCallbacks> CredentialStorageCallbacks for CallbackAdapter<C> {
275 fn write_credential(&self, credential: &CtapCredential) -> soft_fido2_ctap::Result<()> {
276 let cred_ref = CredentialRef {
278 id: &credential.id,
279 rp_id: &credential.rp_id,
280 rp_name: credential.rp_name.as_deref(),
281 user_id: &credential.user_id,
282 user_name: credential.user_name.as_deref(),
283 user_display_name: credential.user_display_name.as_deref(),
284 sign_count: &credential.sign_count,
285 alg: &credential.algorithm,
286 private_key: &credential.private_key,
287 created: &credential.created,
288 discoverable: &credential.discoverable,
289 cred_protect: Some(&credential.cred_protect),
290 };
291
292 self.callbacks
293 .write_credential(&cred_ref)
294 .map_err(|_| StatusCode::Other)
295 }
296
297 fn delete_credential(&self, credential_id: &[u8]) -> soft_fido2_ctap::Result<()> {
298 self.callbacks
299 .delete_credential(credential_id)
300 .map_err(|_| StatusCode::Other)
301 }
302
303 fn read_credentials(
304 &self,
305 rp_id: &str,
306 user_id: Option<&[u8]>,
307 ) -> soft_fido2_ctap::Result<Vec<CtapCredential>> {
308 let credentials = self
309 .callbacks
310 .list_credentials(rp_id, user_id)
311 .map_err(|_| StatusCode::NoCredentials)?;
312 Ok(credentials.into_iter().map(|c| c.into()).collect())
313 }
314
315 fn credential_exists(&self, credential_id: &[u8]) -> soft_fido2_ctap::Result<bool> {
316 match self.callbacks.read_credential(credential_id) {
319 Ok(Some(_)) => Ok(true),
320 Ok(None) => Ok(false),
321 Err(_) => Ok(false),
322 }
323 }
324
325 fn get_credential(&self, credential_id: &[u8]) -> soft_fido2_ctap::Result<CtapCredential> {
326 let cred = self
329 .callbacks
330 .read_credential(credential_id)
331 .map_err(|_| StatusCode::NoCredentials)?
332 .ok_or(StatusCode::NoCredentials)?;
333 Ok(cred.into())
334 }
335
336 fn update_credential(&self, credential: &CtapCredential) -> soft_fido2_ctap::Result<()> {
337 self.write_credential(credential)
339 }
340
341 fn enumerate_rps(&self) -> soft_fido2_ctap::Result<Vec<(String, Option<String>, usize)>> {
342 self.callbacks
343 .enumerate_rps()
344 .map_err(|_| StatusCode::Other)
345 }
346
347 fn credential_count(&self) -> soft_fido2_ctap::Result<usize> {
348 self.callbacks
349 .credential_count()
350 .map_err(|_| StatusCode::Other)
351 }
352}
353
354#[derive(Debug, Clone)]
360pub struct AuthenticatorConfig {
361 pub aaguid: [u8; 16],
362 pub commands: Vec<crate::ctap::CtapCommand>,
363 pub options: Option<crate::options::AuthenticatorOptions>,
364 pub max_credentials: usize,
365 pub extensions: Vec<String>,
366 pub force_resident_keys: bool,
367 pub firmware_version: Option<u32>,
368 pub constant_sign_count: bool,
369 pub max_msg_size: usize,
370 pub device_name: Option<String>,
372 pub vendor_id: Option<u16>,
374 pub product_id: Option<u16>,
376 pub device_version: Option<u16>,
378}
379
380impl Default for AuthenticatorConfig {
381 fn default() -> Self {
382 Self {
383 aaguid: [0u8; 16],
384 commands: crate::ctap::CtapCommand::default_commands(),
385 options: None,
386 max_credentials: 100,
387 extensions: vec![],
388 force_resident_keys: true,
389 firmware_version: None,
390 constant_sign_count: false,
391 max_msg_size: MAX_CTAP_MESSAGE_SIZE,
392 device_name: None,
393 vendor_id: None,
394 product_id: None,
395 device_version: None,
396 }
397 }
398}
399
400impl AuthenticatorConfig {
401 pub fn builder() -> AuthenticatorConfigBuilder {
402 AuthenticatorConfigBuilder::default()
403 }
404}
405
406pub struct AuthenticatorConfigBuilder {
408 aaguid: [u8; 16],
409 commands: Vec<crate::ctap::CtapCommand>,
410 options: Option<crate::options::AuthenticatorOptions>,
411 max_credentials: usize,
412 extensions: Vec<String>,
413 force_resident_keys: bool,
414 firmware_version: Option<u32>,
415 constant_sign_count: bool,
416 max_msg_size: usize,
417 device_name: Option<String>,
418 vendor_id: Option<u16>,
419 product_id: Option<u16>,
420 device_version: Option<u16>,
421}
422
423impl Default for AuthenticatorConfigBuilder {
424 fn default() -> Self {
425 Self {
426 aaguid: [0u8; 16],
427 commands: vec![],
428 options: None,
429 max_credentials: 0,
430 extensions: vec![],
431 force_resident_keys: true,
432 firmware_version: None,
433 constant_sign_count: false,
434 max_msg_size: MAX_CTAP_MESSAGE_SIZE,
435 device_name: None,
436 vendor_id: None,
437 product_id: None,
438 device_version: None,
439 }
440 }
441}
442
443impl AuthenticatorConfigBuilder {
444 pub fn new() -> Self {
445 Self::default()
446 }
447
448 pub fn aaguid(mut self, aaguid: [u8; 16]) -> Self {
449 self.aaguid = aaguid;
450 self
451 }
452
453 pub fn commands(mut self, commands: Vec<crate::ctap::CtapCommand>) -> Self {
454 self.commands = commands;
455 self
456 }
457
458 pub fn options(mut self, options: crate::options::AuthenticatorOptions) -> Self {
459 self.options = Some(options);
460 self
461 }
462
463 pub fn max_credentials(mut self, max: usize) -> Self {
464 self.max_credentials = max;
465 self
466 }
467
468 pub fn extensions(mut self, extensions: Vec<String>) -> Self {
469 self.extensions = extensions;
470 self
471 }
472
473 pub fn force_resident_keys(mut self, force: bool) -> Self {
474 self.force_resident_keys = force;
475 self
476 }
477
478 pub fn firmware_version(mut self, version: u32) -> Self {
479 self.firmware_version = Some(version);
480 self
481 }
482
483 pub fn constant_sign_count(mut self, constant: bool) -> Self {
484 self.constant_sign_count = constant;
485 self
486 }
487
488 pub fn max_msg_size(mut self, size: usize) -> Self {
489 self.max_msg_size = size;
490 self
491 }
492
493 pub fn device_name(mut self, name: String) -> Self {
494 self.device_name = Some(name);
495 self
496 }
497
498 pub fn vendor_id(mut self, id: u16) -> Self {
499 self.vendor_id = Some(id);
500 self
501 }
502
503 pub fn product_id(mut self, id: u16) -> Self {
504 self.product_id = Some(id);
505 self
506 }
507
508 pub fn device_version(mut self, version: u16) -> Self {
509 self.device_version = Some(version);
510 self
511 }
512
513 pub fn build(self) -> AuthenticatorConfig {
514 AuthenticatorConfig {
515 aaguid: self.aaguid,
516 commands: if self.commands.is_empty() {
517 crate::ctap::CtapCommand::default_commands()
518 } else {
519 self.commands
520 },
521 options: self.options,
522 max_credentials: if self.max_credentials == 0 {
523 100
524 } else {
525 self.max_credentials
526 },
527 extensions: self.extensions,
528 force_resident_keys: self.force_resident_keys,
529 firmware_version: self.firmware_version,
530 constant_sign_count: self.constant_sign_count,
531 max_msg_size: self.max_msg_size,
532 device_name: self.device_name,
533 vendor_id: self.vendor_id,
534 product_id: self.product_id,
535 device_version: self.device_version,
536 }
537 }
538}
539
540pub struct Authenticator<C: AuthenticatorCallbacks> {
544 dispatcher: Arc<Mutex<CommandDispatcher<CallbackAdapter<C>>>>,
545}
546
547impl<C: AuthenticatorCallbacks> Authenticator<C> {
548 pub fn set_pin_hash(pin_hash: &[u8]) {
557 if pin_hash.len() == 32 {
558 let mut hash = [0u8; 32];
559 hash.copy_from_slice(pin_hash);
560
561 #[cfg(feature = "std")]
562 {
563 let lock = PRESET_PIN_HASH.get_or_init(|| Mutex::new(None));
564 if let Ok(mut guard) = lock.lock() {
565 *guard = Some(hash);
566 }
567 }
568
569 #[cfg(not(feature = "std"))]
570 {
571 *PRESET_PIN_HASH.lock() = Some(hash);
572 }
573 }
574 }
575
576 pub fn new(callbacks: C) -> Result<Self>
578 where
579 C: 'static,
580 {
581 Self::with_config(callbacks, AuthenticatorConfig::default())
582 }
583
584 pub fn with_config(callbacks: C, config: AuthenticatorConfig) -> Result<Self>
586 where
587 C: 'static,
588 {
589 Self::with_config_internal(callbacks, config, None::<NoOpPinStorage>)
590 }
591
592 pub fn with_config_and_pin_storage<P>(
594 callbacks: C,
595 config: AuthenticatorConfig,
596 pin_storage: P,
597 ) -> Result<Self>
598 where
599 C: 'static,
600 P: PinStorageCallbacks + Send + Sync + 'static,
601 {
602 Self::with_config_internal(callbacks, config, Some(pin_storage))
603 }
604
605 fn with_config_internal<P>(
606 callbacks: C,
607 config: AuthenticatorConfig,
608 pin_storage: Option<P>,
609 ) -> Result<Self>
610 where
611 C: 'static,
612 P: PinStorageCallbacks + Send + Sync + 'static,
613 {
614 let adapter = CallbackAdapter {
615 callbacks: Arc::new(callbacks),
616 };
617
618 let mut ctap_config = CtapConfig::new()
620 .with_aaguid(config.aaguid)
621 .with_max_credentials(config.max_credentials)
622 .with_extensions(config.extensions)
623 .with_force_resident_keys(config.force_resident_keys)
624 .with_constant_sign_count(config.constant_sign_count)
625 .with_max_msg_size(config.max_msg_size);
626
627 if let Some(fw_version) = config.firmware_version {
628 ctap_config = ctap_config.with_firmware_version(fw_version);
629 }
630
631 if let Some(ref hl_options) = config.options {
633 let ctap_options = soft_fido2_ctap::authenticator::AuthenticatorOptions {
634 plat: hl_options.plat,
635 rk: hl_options.rk,
636 client_pin: hl_options.client_pin,
637 up: hl_options.up,
638 uv: hl_options.uv,
639 always_uv: hl_options.always_uv.unwrap_or(false),
640 cred_mgmt: hl_options.cred_mgmt.unwrap_or(true),
641 authnr_cfg: false,
642 bio_enroll: hl_options.bio_enroll,
643 ep: hl_options.ep,
644 large_blobs: hl_options.large_blobs,
645 pin_uv_auth_token: hl_options.pin_uv_auth_token.unwrap_or(true),
646 set_min_pin_length: false,
647 make_cred_uv_not_rqd: hl_options.make_cred_uv_not_required.unwrap_or(false),
648 };
649 ctap_config = ctap_config.with_options(ctap_options);
650 }
651
652 let authenticator = CtapAuthenticator::new(ctap_config, adapter);
653
654 let authenticator = if let Some(storage) = pin_storage {
656 authenticator.with_pin_storage(storage)
657 } else {
658 authenticator
659 };
660
661 #[cfg(feature = "std")]
663 let mut authenticator = authenticator;
664 #[cfg(feature = "std")]
665 if let Some(lock) = PRESET_PIN_HASH.get()
666 && let Ok(mut guard) = lock.lock()
667 && let Some(pin_hash) = guard.take()
668 {
669 authenticator.set_pin_hash_for_testing(pin_hash);
670 }
671
672 #[cfg(not(feature = "std"))]
674 let mut authenticator = authenticator;
675 #[cfg(not(feature = "std"))]
676 {
677 let mut guard = PRESET_PIN_HASH.lock();
678 if let Some(pin_hash) = guard.take() {
679 authenticator.set_pin_hash_for_testing(pin_hash);
680 }
681 }
682
683 let dispatcher = CommandDispatcher::new(authenticator);
684
685 Ok(Self {
686 dispatcher: Arc::new(Mutex::new(dispatcher)),
687 })
688 }
689
690 pub fn handle(&mut self, request: &[u8], response: &mut Vec<u8>) -> Result<usize> {
701 #[cfg(feature = "std")]
702 let mut dispatcher = self.dispatcher.lock().map_err(|_| Error::Other)?;
703 #[cfg(not(feature = "std"))]
704 let mut dispatcher = self.dispatcher.lock();
705
706 match dispatcher.dispatch(request) {
708 Ok(response_data) => {
709 response.clear();
711 response.push(0x00); response.extend_from_slice(&response_data);
713 Ok(response.len())
714 }
715 Err(status_code) => {
716 *response = vec![status_code as u8];
718 Ok(1)
719 }
720 }
721 }
722
723 pub fn register_custom_command<F>(&mut self, command: u8, handler: F)
753 where
754 F: Fn(&[u8]) -> core::result::Result<Vec<u8>, StatusCode> + Send + Sync + 'static,
755 {
756 #[cfg(feature = "std")]
757 let mut dispatcher = self
758 .dispatcher
759 .lock()
760 .expect("Failed to lock dispatcher for custom command registration");
761 #[cfg(not(feature = "std"))]
762 let mut dispatcher = self.dispatcher.lock();
763
764 dispatcher
765 .authenticator_mut()
766 .register_custom_command(command, handler);
767 }
768}
769
770mod tests {
771 use super::*;
772
773 #[allow(dead_code)]
775 struct TestCallbacks;
776
777 impl AuthenticatorCallbacks for TestCallbacks {
778 fn request_up(&self, _: &str, _: Option<&str>, _: &str) -> Result<UpResult> {
779 Ok(UpResult::Accepted)
780 }
781
782 fn request_uv(&self, _: &str, _: Option<&str>, _: &str) -> Result<UvResult> {
783 Ok(UvResult::Accepted)
784 }
785
786 fn write_credential(&self, _: &CredentialRef) -> Result<()> {
787 Ok(())
788 }
789
790 fn read_credential(&self, _: &[u8]) -> Result<Option<Credential>> {
791 Ok(None)
792 }
793
794 fn delete_credential(&self, _: &[u8]) -> Result<()> {
795 Ok(())
796 }
797
798 fn list_credentials(&self, _: &str, _: Option<&[u8]>) -> Result<Vec<Credential>> {
799 Ok(vec![])
800 }
801
802 fn enumerate_rps(&self) -> Result<Vec<(String, Option<String>, usize)>> {
803 Ok(vec![])
804 }
805
806 fn credential_count(&self) -> Result<usize> {
807 Ok(0)
808 }
809
810 fn get_timestamp_ms(&self) -> u64 {
811 0
812 }
813 }
814
815 #[test]
816 fn test_authenticator_creation() {
817 let callbacks = TestCallbacks;
818 let config = AuthenticatorConfig::default();
819 let result = Authenticator::with_config(callbacks, config);
820 assert!(result.is_ok());
821 }
822
823 #[test]
824 fn test_config_builder() {
825 let config = AuthenticatorConfig::builder()
826 .aaguid([1u8; 16])
827 .max_credentials(50)
828 .build();
829
830 assert_eq!(config.aaguid, [1u8; 16]);
831 assert_eq!(config.max_credentials, 50);
832 }
833}