soft_fido2_ctap/
callbacks.rs

1//! Callback traits for authenticator user interaction and credential storage
2//!
3//! These traits define the interface between the CTAP protocol implementation
4//! and the platform-specific user interaction and storage mechanisms.
5
6use crate::StatusCode;
7use crate::types::{Credential, PinState};
8
9use alloc::string::String;
10use alloc::vec::Vec;
11
12/// Result of a user presence check
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum UpResult {
15    /// User denied the operation
16    Denied,
17    /// User accepted (presence confirmed)
18    Accepted,
19    /// Operation timed out waiting for user
20    Timeout,
21}
22
23impl UpResult {
24    /// Check if user presence was confirmed
25    pub fn is_accepted(self) -> bool {
26        self == Self::Accepted
27    }
28}
29
30/// Result of a user verification check
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum UvResult {
33    /// User verification denied
34    Denied,
35    /// User verification accepted
36    Accepted,
37    /// User verification accepted with user presence also confirmed
38    AcceptedWithUp,
39    /// Operation timed out
40    Timeout,
41}
42
43impl UvResult {
44    /// Check if user verification succeeded
45    pub fn is_verified(self) -> bool {
46        matches!(self, Self::Accepted | Self::AcceptedWithUp)
47    }
48
49    /// Check if user presence was also confirmed
50    pub fn has_up(self) -> bool {
51        self == Self::AcceptedWithUp
52    }
53}
54
55/// Callbacks for user interaction
56///
57/// These callbacks are invoked by the authenticator to request user consent
58/// and verification during CTAP operations.
59pub trait UserInteractionCallbacks {
60    /// Request user presence confirmation
61    ///
62    /// This is typically a simple "tap to confirm" action.
63    ///
64    /// # Arguments
65    ///
66    /// * `info` - Context information (e.g., "Register", "Authenticate")
67    /// * `user_name` - User name if available
68    /// * `rp_id` - Relying party identifier
69    ///
70    /// # Returns
71    ///
72    /// Result indicating whether user presence was confirmed
73    fn request_up(
74        &self,
75        info: &str,
76        user_name: Option<&str>,
77        rp_id: &str,
78    ) -> Result<UpResult, StatusCode>;
79
80    /// Request user verification
81    ///
82    /// This typically involves biometric verification or PIN entry.
83    ///
84    /// # Arguments
85    ///
86    /// * `info` - Context information
87    /// * `user_name` - User name if available
88    /// * `rp_id` - Relying party identifier
89    ///
90    /// # Returns
91    ///
92    /// Result indicating whether user verification succeeded
93    fn request_uv(
94        &self,
95        info: &str,
96        user_name: Option<&str>,
97        rp_id: &str,
98    ) -> Result<UvResult, StatusCode>;
99
100    /// Request user to select from multiple credentials
101    ///
102    /// Called during getAssertion when multiple credentials match.
103    ///
104    /// # Arguments
105    ///
106    /// * `rp_id` - Relying party identifier
107    /// * `user_names` - List of user names for available credentials
108    ///
109    /// # Returns
110    ///
111    /// Index of selected credential, or error
112    fn select_credential(&self, rp_id: &str, user_names: &[String]) -> Result<usize, StatusCode>;
113}
114
115/// Callbacks for credential storage operations
116///
117/// These callbacks handle persistent storage and retrieval of credentials.
118pub trait CredentialStorageCallbacks {
119    /// Write (store) a credential
120    ///
121    /// # Arguments
122    ///
123    /// * `credential` - The credential to store
124    ///
125    /// # Returns
126    ///
127    /// Success or error status
128    fn write_credential(&self, credential: &Credential) -> Result<(), StatusCode>;
129
130    /// Delete a credential by ID
131    ///
132    /// # Arguments
133    ///
134    /// * `credential_id` - ID of credential to delete
135    ///
136    /// # Returns
137    ///
138    /// Success or error status
139    fn delete_credential(&self, credential_id: &[u8]) -> Result<(), StatusCode>;
140
141    /// Read credentials for a specific RP and user
142    ///
143    /// # Arguments
144    ///
145    /// * `rp_id` - Relying party identifier
146    /// * `user_id` - User handle (optional - if None, return all for RP)
147    ///
148    /// # Returns
149    ///
150    /// List of matching credentials
151    fn read_credentials(
152        &self,
153        rp_id: &str,
154        user_id: Option<&[u8]>,
155    ) -> Result<Vec<Credential>, StatusCode>;
156
157    /// Check if a credential exists
158    ///
159    /// # Arguments
160    ///
161    /// * `credential_id` - Credential ID to check
162    ///
163    /// # Returns
164    ///
165    /// True if credential exists
166    fn credential_exists(&self, credential_id: &[u8]) -> Result<bool, StatusCode>;
167
168    /// Get credential by ID
169    ///
170    /// # Arguments
171    ///
172    /// * `credential_id` - Credential ID
173    ///
174    /// # Returns
175    ///
176    /// The credential if found
177    fn get_credential(&self, credential_id: &[u8]) -> Result<Credential, StatusCode>;
178
179    /// Update credential (e.g., increment signature counter)
180    ///
181    /// # Arguments
182    ///
183    /// * `credential` - Updated credential
184    ///
185    /// # Returns
186    ///
187    /// Success or error status
188    fn update_credential(&self, credential: &Credential) -> Result<(), StatusCode>;
189
190    /// Enumerate all relying parties with stored credentials
191    ///
192    /// Used for credential management.
193    ///
194    /// # Returns
195    ///
196    /// List of (rp_id, rp_name, credential_count) tuples
197    fn enumerate_rps(&self) -> Result<Vec<(String, Option<String>, usize)>, StatusCode>;
198
199    /// Get total number of discoverable credentials
200    ///
201    /// # Returns
202    ///
203    /// Count of discoverable credentials
204    fn credential_count(&self) -> Result<usize, StatusCode>;
205}
206
207/// Callbacks for PIN state persistence
208pub trait PinStorageCallbacks {
209    /// Load PIN state from persistent storage
210    fn load_pin_state(&self) -> Result<PinState, StatusCode>;
211
212    /// Save PIN state to persistent storage
213    fn save_pin_state(&self, state: &PinState) -> Result<(), StatusCode>;
214}
215
216/// Callbacks for platform capabilities
217pub trait PlatformCallbacks {
218    /// Get current timestamp in milliseconds since UNIX epoch
219    ///
220    /// This is used for PIN token expiration and other time-sensitive operations.
221    /// In `no_std` environments, this must be implemented using a hardware timer
222    /// or other time source.
223    fn get_timestamp_ms(&self) -> u64;
224}
225
226/// Combined callbacks interface for core authenticator operations
227///
228/// Combines user interaction, credential storage, and platform callbacks.
229pub trait AuthenticatorCallbacks:
230    UserInteractionCallbacks + CredentialStorageCallbacks + PlatformCallbacks
231{
232    // This trait is intentionally empty - it just combines the callback traits
233}
234
235// Blanket implementation: any type implementing all traits also implements AuthenticatorCallbacks
236impl<T> AuthenticatorCallbacks for T where
237    T: UserInteractionCallbacks + CredentialStorageCallbacks + PlatformCallbacks
238{
239}
240
241#[cfg(test)]
242mod tests {
243    use super::*;
244
245    #[test]
246    fn test_up_result() {
247        assert!(UpResult::Accepted.is_accepted());
248        assert!(!UpResult::Denied.is_accepted());
249        assert!(!UpResult::Timeout.is_accepted());
250    }
251
252    #[test]
253    fn test_uv_result() {
254        assert!(UvResult::Accepted.is_verified());
255        assert!(UvResult::AcceptedWithUp.is_verified());
256        assert!(!UvResult::Denied.is_verified());
257        assert!(!UvResult::Timeout.is_verified());
258
259        assert!(!UvResult::Accepted.has_up());
260        assert!(UvResult::AcceptedWithUp.has_up());
261    }
262
263    // Mock implementation for testing
264    pub struct MockCallbacks;
265
266    impl PlatformCallbacks for MockCallbacks {
267        fn get_timestamp_ms(&self) -> u64 {
268            0
269        }
270    }
271
272    impl UserInteractionCallbacks for MockCallbacks {
273        fn request_up(
274            &self,
275            _info: &str,
276            _user_name: Option<&str>,
277            _rp_id: &str,
278        ) -> Result<UpResult, StatusCode> {
279            Ok(UpResult::Accepted)
280        }
281
282        fn request_uv(
283            &self,
284            _info: &str,
285            _user_name: Option<&str>,
286            _rp_id: &str,
287        ) -> Result<UvResult, StatusCode> {
288            Ok(UvResult::Accepted)
289        }
290
291        fn select_credential(
292            &self,
293            _rp_id: &str,
294            _user_names: &[String],
295        ) -> Result<usize, StatusCode> {
296            Ok(0) // Select first credential
297        }
298    }
299
300    impl CredentialStorageCallbacks for MockCallbacks {
301        fn write_credential(&self, _credential: &Credential) -> Result<(), StatusCode> {
302            Ok(())
303        }
304
305        fn delete_credential(&self, _credential_id: &[u8]) -> Result<(), StatusCode> {
306            Ok(())
307        }
308
309        fn read_credentials(
310            &self,
311            _rp_id: &str,
312            _user_id: Option<&[u8]>,
313        ) -> Result<Vec<Credential>, StatusCode> {
314            Ok(vec![])
315        }
316
317        fn credential_exists(&self, _credential_id: &[u8]) -> Result<bool, StatusCode> {
318            Ok(false)
319        }
320
321        fn get_credential(&self, _credential_id: &[u8]) -> Result<Credential, StatusCode> {
322            Err(StatusCode::NoCredentials)
323        }
324
325        fn update_credential(&self, _credential: &Credential) -> Result<(), StatusCode> {
326            Ok(())
327        }
328
329        fn enumerate_rps(&self) -> Result<Vec<(String, Option<String>, usize)>, StatusCode> {
330            Ok(vec![])
331        }
332
333        fn credential_count(&self) -> Result<usize, StatusCode> {
334            Ok(0)
335        }
336    }
337
338    impl PinStorageCallbacks for MockCallbacks {
339        fn load_pin_state(&self) -> Result<PinState, StatusCode> {
340            // Return default state (no PIN set)
341            Ok(PinState::new())
342        }
343
344        fn save_pin_state(&self, _state: &PinState) -> Result<(), StatusCode> {
345            // Mock: don't actually persist
346            Ok(())
347        }
348    }
349
350    #[test]
351    fn test_mock_callbacks() {
352        let callbacks = MockCallbacks;
353
354        // Test user interaction callbacks
355        let up = callbacks.request_up("Test", None, "example.com").unwrap();
356        assert_eq!(up, UpResult::Accepted);
357
358        let uv = callbacks.request_uv("Test", None, "example.com").unwrap();
359        assert_eq!(uv, UvResult::Accepted);
360
361        // Test storage callbacks
362        assert!(!callbacks.credential_exists(&[1, 2, 3]).unwrap());
363        assert_eq!(callbacks.credential_count().unwrap(), 0);
364    }
365
366    #[test]
367    fn test_pin_storage_callbacks() {
368        let callbacks = MockCallbacks;
369
370        // Test PIN storage callbacks (optional trait)
371        let pin_state = callbacks.load_pin_state().unwrap();
372        assert!(!pin_state.is_pin_set());
373        assert!(!pin_state.is_blocked());
374        assert_eq!(pin_state.retries, 8);
375
376        callbacks.save_pin_state(&pin_state).unwrap();
377    }
378
379    #[test]
380    fn test_authenticator_callbacks_blanket_impl() {
381        let callbacks = MockCallbacks;
382
383        // Should work as AuthenticatorCallbacks due to blanket impl
384        fn _accepts_combined_callbacks<T: AuthenticatorCallbacks>(_callbacks: &T) {}
385        _accepts_combined_callbacks(&callbacks);
386    }
387}