soft_fido2/
authenticator.rs

1//! FIDO2 Authenticator Implementation
2//!
3//! Provides a high-level FIDO2 authenticator with trait-based callbacks for user interaction.
4
5#[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/// Global PIN hash storage (std version with lazy initialization)
32#[cfg(feature = "std")]
33static PRESET_PIN_HASH: OnceLock<Mutex<Option<[u8; 32]>>> = OnceLock::new();
34
35/// Global PIN hash storage (no_std version, always initialized)
36#[cfg(not(feature = "std"))]
37static PRESET_PIN_HASH: Mutex<Option<[u8; 32]>> = Mutex::new(None);
38
39/// No-op PIN storage implementation used as type placeholder when no storage is configured
40struct 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/// User presence result
53#[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/// User verification result
81#[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
111/// Trait for handling authenticator callbacks
112///
113/// Implement this trait to provide custom user interaction and credential storage logic.
114///
115/// # Example
116///
117/// ```no_run
118/// use soft_fido2::{AuthenticatorCallbacks, UpResult, UvResult, Credential, CredentialRef};
119/// use std::collections::HashMap;
120///
121/// struct MyCallbacks {
122///     store: HashMap<Vec<u8>, Credential>,
123/// }
124///
125/// impl AuthenticatorCallbacks for MyCallbacks {
126///     fn request_up(&self, _info: &str, _user: Option<&str>, _rp: &str) -> soft_fido2::Result<UpResult> {
127///         Ok(UpResult::Accepted)
128///     }
129///
130///     fn request_uv(&self, _info: &str, _user: Option<&str>, _rp: &str) -> soft_fido2::Result<UvResult> {
131///         Ok(UvResult::Accepted)
132///     }
133///
134///     fn write_credential(&self, cred: &CredentialRef) -> soft_fido2::Result<()> {
135///         // Store credential
136///         Ok(())
137///     }
138///
139///     fn read_credential(&self, cred_id: &[u8]) -> soft_fido2::Result<Option<Credential>> {
140///         // Retrieve credential
141///         Ok(None)
142///     }
143///
144///     fn delete_credential(&self, cred_id: &[u8]) -> soft_fido2::Result<()> {
145///         // Delete credential
146///         Ok(())
147///     }
148///
149///     fn list_credentials(&self, rp_id: &str, _user_id: Option<&[u8]>) -> soft_fido2::Result<Vec<Credential>> {
150///         // List credentials for RP
151///         Ok(vec![])
152///     }
153///
154///     fn enumerate_rps(&self) -> soft_fido2::Result<Vec<(String, Option<String>, usize)>> {
155///         // Return list of (rp_id, rp_name, credential_count)
156///         Ok(vec![])
157///     }
158///
159///     fn credential_count(&self) -> soft_fido2::Result<usize> {
160///         // Return total credential count
161///         Ok(0)
162///     }
163///
164///     fn get_timestamp_ms(&self) -> u64 {
165///         0
166///     }
167/// }
168/// ```
169pub trait AuthenticatorCallbacks: Send + Sync {
170    /// Request user presence (e.g., tap security key, press button)
171    fn request_up(&self, info: &str, user_name: Option<&str>, rp_id: &str) -> Result<UpResult>;
172
173    /// Request user verification (e.g., PIN, biometric, password)
174    fn request_uv(&self, info: &str, user_name: Option<&str>, rp_id: &str) -> Result<UvResult>;
175
176    /// Store a credential
177    fn write_credential(&self, credential: &CredentialRef) -> Result<()>;
178
179    /// Read a specific credential
180    fn read_credential(&self, cred_id: &[u8]) -> Result<Option<Credential>>;
181
182    /// Delete a credential
183    fn delete_credential(&self, cred_id: &[u8]) -> Result<()>;
184
185    /// List all credentials for a relying party
186    fn list_credentials(&self, rp_id: &str, user_id: Option<&[u8]>) -> Result<Vec<Credential>>;
187
188    /// Select which credential to use from multiple matches
189    fn select_credential(&self, _rp_id: &str, _credentials: &[Credential]) -> Result<usize> {
190        Ok(0)
191    }
192
193    /// Enumerate all relying parties with stored credentials
194    ///
195    /// Used for credential management operations.
196    ///
197    /// # Returns
198    ///
199    /// Vector of tuples: (rp_id, rp_name, credential_count)
200    fn enumerate_rps(&self) -> Result<Vec<(String, Option<String>, usize)>>;
201
202    /// Get total number of discoverable credentials
203    ///
204    /// # Returns
205    ///
206    /// Total count of all discoverable credentials across all RPs
207    fn credential_count(&self) -> Result<usize>;
208
209    /// Get current timestamp in milliseconds since UNIX epoch
210    ///
211    /// Used for PIN token expiration and other time-sensitive operations.
212    /// In no_std environments, this must be provided by the platform.
213    fn get_timestamp_ms(&self) -> u64;
214}
215
216/// Callback adapter that implements soft-fido2-ctap traits
217struct 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        // Note: We don't use user_names from CTAP layer since we use credential-based selection
262        // Get credentials for this RP and let the trait implementation choose
263        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        // Convert CTAP credential to CredentialRef
277        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        // Try to read the credential with a placeholder RP ID
317        // Note: This is a limitation of the current design
318        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        // Try to read the credential with a placeholder RP ID
327        // Note: This is a limitation of the current design
328        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        // Update is same as write for our purposes
338        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// Note: CallbackAdapter automatically implements AuthenticatorCallbacks
355// because there's a blanket impl in soft-fido2-ctap for any type that implements
356// both UserInteractionCallbacks and CredentialStorageCallbacks
357
358/// Authenticator configuration
359#[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    /// USB/HID device name
371    pub device_name: Option<String>,
372    /// USB vendor ID
373    pub vendor_id: Option<u16>,
374    /// USB product ID
375    pub product_id: Option<u16>,
376    /// Device version number
377    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
406/// Builder for AuthenticatorConfig
407pub 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
540/// High-level FIDO2 authenticator
541///
542/// Provides a thread-safe authenticator that processes CTAP commands via callbacks.
543pub struct Authenticator<C: AuthenticatorCallbacks> {
544    dispatcher: Arc<Mutex<CommandDispatcher<CallbackAdapter<C>>>>,
545}
546
547impl<C: AuthenticatorCallbacks> Authenticator<C> {
548    /// Set the PIN hash for the authenticator (must be called before creating instance)
549    ///
550    /// The PIN hash will be applied to the next authenticator instance created.
551    /// This is useful for testing scenarios where you want to simulate a PIN being set.
552    ///
553    /// # Arguments
554    ///
555    /// * `pin_hash` - SHA-256 hash of the PIN (32 bytes)
556    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    /// Create a new authenticator with default configuration
577    pub fn new(callbacks: C) -> Result<Self>
578    where
579        C: 'static,
580    {
581        Self::with_config(callbacks, AuthenticatorConfig::default())
582    }
583
584    /// Create a new authenticator with custom configuration
585    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    /// Create a new authenticator with custom configuration and persistent PIN storage
593    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        // Create CTAP authenticator config
619        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        // Convert and apply high-level options to CTAP options
632        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        // Apply PIN storage if provided
655        let authenticator = if let Some(storage) = pin_storage {
656            authenticator.with_pin_storage(storage)
657        } else {
658            authenticator
659        };
660
661        // Apply preset PIN hash if available (std version)
662        #[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        // Apply preset PIN hash if available (no_std version)
673        #[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    /// Handle a CTAP request
691    ///
692    /// # Arguments
693    ///
694    /// * `request` - CTAP command bytes (command code + CBOR parameters)
695    /// * `response` - Buffer for response (will be resized as needed)
696    ///
697    /// # Returns
698    ///
699    /// Number of bytes written to response buffer
700    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        // Dispatch command
707        match dispatcher.dispatch(request) {
708            Ok(response_data) => {
709                // CTAP success response: [0x00 status] [CBOR data...]
710                response.clear();
711                response.push(0x00); // Success status byte
712                response.extend_from_slice(&response_data);
713                Ok(response.len())
714            }
715            Err(status_code) => {
716                // CTAP error response: [error status byte] (no CBOR data)
717                *response = vec![status_code as u8];
718                Ok(1)
719            }
720        }
721    }
722
723    /// Register a custom CTAP command handler
724    ///
725    /// This allows registering vendor-specific commands in the 0x40-0xFF range.
726    ///
727    /// # Example
728    ///
729    /// ```no_run
730    /// # use soft_fido2::{Authenticator, AuthenticatorCallbacks, UpResult, UvResult, Credential, CredentialRef};
731    /// # struct MyCallbacks;
732    /// # impl AuthenticatorCallbacks for MyCallbacks {
733    /// #     fn request_up(&self, _: &str, _: Option<&str>, _: &str) -> soft_fido2::Result<UpResult> { Ok(UpResult::Accepted) }
734    /// #     fn request_uv(&self, _: &str, _: Option<&str>, _: &str) -> soft_fido2::Result<UvResult> { Ok(UvResult::Accepted) }
735    /// #     fn write_credential(&self, _: &CredentialRef) -> soft_fido2::Result<()> { Ok(()) }
736    /// #     fn read_credential(&self, _: &[u8],) -> soft_fido2::Result<Option<Credential>> { Ok(None) }
737    /// #     fn delete_credential(&self, _: &[u8]) -> soft_fido2::Result<()> { Ok(()) }
738    /// #     fn list_credentials(&self, _: &str, _: Option<&[u8]>) -> soft_fido2::Result<Vec<Credential>> { Ok(vec![]) }
739    /// #     fn enumerate_rps(&self) -> soft_fido2::Result<Vec<(String, Option<String>, usize)>> { Ok(vec![]) }
740    /// #     fn credential_count(&self) -> soft_fido2::Result<usize> { Ok(0) }
741    /// #     fn get_timestamp_ms(&self) -> u64 { 0 }
742    /// # }
743    /// let callbacks = MyCallbacks;
744    /// let mut auth = Authenticator::new(callbacks).unwrap();
745    ///
746    /// // Register custom command 0x41
747    /// auth.register_custom_command(0x41, |request| {
748    ///     // Process custom command
749    ///     Ok(vec![0x01, 0x02, 0x03])
750    /// });
751    /// ```
752    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    // Simple test implementation of AuthenticatorCallbacks
774    #[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}