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}