soft_fido2/
request.rs

1//! Request and response types for CTAP client operations
2
3use crate::error::{Error, Result};
4
5use soft_fido2_ctap::types::{RelyingParty, User};
6
7use alloc::string::String;
8use alloc::vec::Vec;
9
10/// A validated client data hash (must be exactly 32 bytes)
11///
12/// This newtype ensures that client data hashes are always the correct length,
13/// preventing runtime validation errors.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct ClientDataHash([u8; 32]);
16
17impl ClientDataHash {
18    /// Create a new ClientDataHash from a 32-byte array
19    pub fn new(hash: [u8; 32]) -> Self {
20        Self(hash)
21    }
22
23    /// Create a ClientDataHash from a slice
24    ///
25    /// # Errors
26    ///
27    /// Returns `Error::InvalidClientDataHash` if the slice is not exactly 32 bytes.
28    pub fn from_slice(slice: &[u8]) -> Result<Self> {
29        if slice.len() != 32 {
30            return Err(Error::InvalidClientDataHash);
31        }
32        let mut hash = [0u8; 32];
33        hash.copy_from_slice(slice);
34        Ok(Self(hash))
35    }
36
37    /// Get a reference to the underlying hash bytes
38    pub fn as_bytes(&self) -> &[u8; 32] {
39        &self.0
40    }
41
42    /// Get the hash as a slice
43    pub fn as_slice(&self) -> &[u8] {
44        &self.0
45    }
46}
47
48impl AsRef<[u8]> for ClientDataHash {
49    fn as_ref(&self) -> &[u8] {
50        &self.0
51    }
52}
53
54impl From<[u8; 32]> for ClientDataHash {
55    fn from(hash: [u8; 32]) -> Self {
56        Self::new(hash)
57    }
58}
59
60/// Type of credential
61#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
62pub enum CredentialType {
63    /// Public key credential (the only type currently defined in CTAP2)
64    #[default]
65    PublicKey,
66}
67
68impl CredentialType {
69    /// Get the string representation for CBOR encoding
70    pub fn as_str(&self) -> &'static str {
71        match self {
72            CredentialType::PublicKey => "public-key",
73        }
74    }
75}
76
77/// A credential descriptor identifying a specific credential
78///
79/// Used in getAssertion to specify which credentials are allowed.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub struct CredentialDescriptor {
82    /// The credential ID
83    pub id: Vec<u8>,
84    /// The type of credential (typically PublicKey)
85    pub credential_type: CredentialType,
86}
87
88impl CredentialDescriptor {
89    /// Create a new credential descriptor
90    pub fn new(id: Vec<u8>, credential_type: CredentialType) -> Self {
91        Self {
92            id,
93            credential_type,
94        }
95    }
96
97    /// Create a public key credential descriptor (convenience method)
98    pub fn public_key(id: Vec<u8>) -> Self {
99        Self {
100            id,
101            credential_type: CredentialType::PublicKey,
102        }
103    }
104}
105
106/// PIN/UV authentication protocol version
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[repr(u8)]
109pub enum PinUvAuthProtocol {
110    /// PIN/UV protocol version 1
111    V1 = 1,
112    /// PIN/UV protocol version 2 (recommended)
113    V2 = 2,
114}
115
116impl PinUvAuthProtocol {
117    /// Convert to u8 for CBOR encoding
118    pub fn as_u8(self) -> u8 {
119        self as u8
120    }
121}
122
123impl From<PinUvAuthProtocol> for u8 {
124    fn from(protocol: PinUvAuthProtocol) -> u8 {
125        protocol.as_u8()
126    }
127}
128
129/// Bundle of PIN/UV authentication parameter and protocol version
130///
131/// This ensures that the auth parameter and protocol version are always paired correctly.
132#[derive(Debug, Clone)]
133pub struct PinUvAuth {
134    param: Vec<u8>,
135    protocol: PinUvAuthProtocol,
136}
137
138impl PinUvAuth {
139    /// Create a new PIN/UV authentication bundle
140    ///
141    /// # Arguments
142    ///
143    /// * `param` - The authentication parameter bytes
144    /// * `protocol` - The protocol version used to generate the parameter
145    pub fn new(param: Vec<u8>, protocol: PinUvAuthProtocol) -> Self {
146        Self { param, protocol }
147    }
148
149    /// Get the authentication parameter bytes
150    pub fn param(&self) -> &[u8] {
151        &self.param
152    }
153
154    /// Get the protocol version
155    pub fn protocol(&self) -> PinUvAuthProtocol {
156        self.protocol
157    }
158
159    /// Get the protocol version as u8
160    pub fn protocol_u8(&self) -> u8 {
161        self.protocol.as_u8()
162    }
163}
164
165/// Request for creating a new credential (authenticatorMakeCredential)
166///
167/// Use the builder pattern to construct requests with optional parameters.
168#[derive(Debug)]
169pub struct MakeCredentialRequest {
170    pub(crate) client_data_hash: ClientDataHash,
171    pub(crate) rp: RelyingParty,
172    pub(crate) user: User,
173    pub(crate) pin_uv_auth: Option<PinUvAuth>,
174    pub(crate) timeout_ms: i32,
175    pub(crate) resident_key: Option<bool>,
176    pub(crate) user_verification: Option<bool>,
177}
178
179impl MakeCredentialRequest {
180    /// Create a new MakeCredentialRequest with required parameters
181    ///
182    /// # Arguments
183    ///
184    /// * `client_data_hash` - SHA-256 hash of the WebAuthn client data
185    /// * `rp` - Relying party information (ID and optional name)
186    /// * `user` - User information (ID, name, optional display name)
187    pub fn new(client_data_hash: ClientDataHash, rp: RelyingParty, user: User) -> Self {
188        Self {
189            client_data_hash,
190            rp,
191            user,
192            pin_uv_auth: None,
193            timeout_ms: 30000, // 30 second default
194            resident_key: None,
195            user_verification: None,
196        }
197    }
198
199    /// Set the PIN/UV authentication parameter
200    pub fn with_pin_uv_auth(mut self, auth: PinUvAuth) -> Self {
201        self.pin_uv_auth = Some(auth);
202        self
203    }
204
205    /// Set the timeout in milliseconds (default: 30000ms)
206    pub fn with_timeout(mut self, timeout_ms: i32) -> Self {
207        self.timeout_ms = timeout_ms;
208        self
209    }
210
211    /// Set whether to create a resident key (discoverable credential)
212    pub fn with_resident_key(mut self, resident_key: bool) -> Self {
213        self.resident_key = Some(resident_key);
214        self
215    }
216
217    /// Set whether to require user verification
218    pub fn with_user_verification(mut self, user_verification: bool) -> Self {
219        self.user_verification = Some(user_verification);
220        self
221    }
222
223    /// Get the client data hash
224    pub fn client_data_hash(&self) -> &ClientDataHash {
225        &self.client_data_hash
226    }
227
228    /// Get the relying party information
229    pub fn rp(&self) -> &RelyingParty {
230        &self.rp
231    }
232
233    /// Get the user information
234    pub fn user(&self) -> &User {
235        &self.user
236    }
237
238    /// Get the PIN/UV authentication parameter if set
239    pub fn pin_uv_auth(&self) -> Option<&PinUvAuth> {
240        self.pin_uv_auth.as_ref()
241    }
242
243    /// Get the timeout in milliseconds
244    pub fn timeout_ms(&self) -> i32 {
245        self.timeout_ms
246    }
247}
248
249/// Request for getting an assertion (authenticatorGetAssertion)
250///
251/// Use the builder pattern to construct requests with optional parameters.
252#[derive(Debug)]
253pub struct GetAssertionRequest {
254    pub(crate) client_data_hash: ClientDataHash,
255    pub(crate) rp_id: String,
256    pub(crate) allow_list: Vec<CredentialDescriptor>,
257    pub(crate) pin_uv_auth: Option<PinUvAuth>,
258    pub(crate) timeout_ms: i32,
259    pub(crate) user_verification: Option<bool>,
260}
261
262impl GetAssertionRequest {
263    /// Create a new GetAssertionRequest with required parameters
264    ///
265    /// # Arguments
266    ///
267    /// * `client_data_hash` - SHA-256 hash of the WebAuthn client data
268    /// * `rp_id` - Relying party identifier (domain)
269    pub fn new(client_data_hash: ClientDataHash, rp_id: impl Into<String>) -> Self {
270        Self {
271            client_data_hash,
272            rp_id: rp_id.into(),
273            allow_list: Vec::new(),
274            pin_uv_auth: None,
275            timeout_ms: 30000, // 30 second default
276            user_verification: None,
277        }
278    }
279
280    /// Add a single credential to the allow list
281    pub fn with_credential(mut self, credential: CredentialDescriptor) -> Self {
282        self.allow_list.push(credential);
283        self
284    }
285
286    /// Set the allow list to a specific set of credentials
287    pub fn with_credentials(mut self, credentials: Vec<CredentialDescriptor>) -> Self {
288        self.allow_list = credentials;
289        self
290    }
291
292    /// Set the PIN/UV authentication parameter
293    pub fn with_pin_uv_auth(mut self, auth: PinUvAuth) -> Self {
294        self.pin_uv_auth = Some(auth);
295        self
296    }
297
298    /// Set the timeout in milliseconds (default: 30000ms)
299    pub fn with_timeout(mut self, timeout_ms: i32) -> Self {
300        self.timeout_ms = timeout_ms;
301        self
302    }
303
304    /// Set whether to require user verification
305    pub fn with_user_verification(mut self, user_verification: bool) -> Self {
306        self.user_verification = Some(user_verification);
307        self
308    }
309
310    /// Get the client data hash
311    pub fn client_data_hash(&self) -> &ClientDataHash {
312        &self.client_data_hash
313    }
314
315    /// Get the relying party identifier
316    pub fn rp_id(&self) -> &str {
317        &self.rp_id
318    }
319
320    /// Get the allow list of credentials
321    pub fn allow_list(&self) -> &[CredentialDescriptor] {
322        &self.allow_list
323    }
324
325    /// Get the PIN/UV authentication parameter if set
326    pub fn pin_uv_auth(&self) -> Option<&PinUvAuth> {
327        self.pin_uv_auth.as_ref()
328    }
329
330    /// Get the timeout in milliseconds
331    pub fn timeout_ms(&self) -> i32 {
332        self.timeout_ms
333    }
334}
335
336/// PIN/UV auth token permissions
337///
338/// These correspond to the permission bits defined in FIDO 2.2 spec.
339#[derive(Debug, Clone, Copy, PartialEq, Eq)]
340#[repr(u8)]
341pub enum Permission {
342    /// Make credential permission (0x01)
343    MakeCredential = 0x01,
344    /// Get assertion permission (0x02)
345    GetAssertion = 0x02,
346    /// Credential management permission (0x04)
347    CredentialManagement = 0x04,
348    /// Bio enrollment permission (0x08)
349    BioEnrollment = 0x08,
350    /// Large blob write permission (0x10)
351    LargeBlobWrite = 0x10,
352    /// Authenticator configuration permission (0x20)
353    AuthenticatorConfiguration = 0x20,
354}
355
356impl Permission {
357    /// Convert to u8 bitmask value
358    pub fn to_u8(self) -> u8 {
359        self as u8
360    }
361}
362
363/// Request for credential management operations
364#[derive(Debug, Clone)]
365pub struct CredentialManagementRequest {
366    pin_uv_auth: Option<PinUvAuth>,
367}
368
369impl CredentialManagementRequest {
370    /// Create new credential management request
371    ///
372    /// # Arguments
373    /// * `pin_uv_auth` - Optional PIN/UV auth token with CredentialManagement permission
374    ///
375    /// # Important
376    /// For getCredsMetadata and enumerate RPs, the PIN/UV auth token MUST NOT
377    /// have a permissions RP ID parameter when obtained from clientPIN.
378    pub fn new(pin_uv_auth: Option<PinUvAuth>) -> Self {
379        Self { pin_uv_auth }
380    }
381
382    /// Get the PIN/UV auth bundle
383    pub fn pin_uv_auth(&self) -> Option<&PinUvAuth> {
384        self.pin_uv_auth.as_ref()
385    }
386}
387
388/// Request to enumerate credentials for a specific RP
389#[derive(Debug, Clone)]
390pub struct EnumerateCredentialsRequest {
391    pin_uv_auth: Option<PinUvAuth>,
392    rp_id_hash: [u8; 32],
393}
394
395impl EnumerateCredentialsRequest {
396    /// Create new enumerate credentials request
397    ///
398    /// # Arguments
399    /// * `pin_uv_auth` - Optional PIN/UV auth token with CredentialManagement permission
400    /// * `rp_id_hash` - SHA-256 hash of RP ID
401    pub fn new(pin_uv_auth: Option<PinUvAuth>, rp_id_hash: [u8; 32]) -> Self {
402        Self {
403            pin_uv_auth,
404            rp_id_hash,
405        }
406    }
407
408    /// Get the PIN/UV auth bundle
409    pub fn pin_uv_auth(&self) -> Option<&PinUvAuth> {
410        self.pin_uv_auth.as_ref()
411    }
412
413    /// Get the RP ID hash
414    pub fn rp_id_hash(&self) -> &[u8; 32] {
415        &self.rp_id_hash
416    }
417}
418
419/// Request to delete a credential
420#[derive(Debug, Clone)]
421pub struct DeleteCredentialRequest {
422    pin_uv_auth: Option<PinUvAuth>,
423    credential_id: Vec<u8>,
424}
425
426impl DeleteCredentialRequest {
427    /// Create new delete credential request
428    ///
429    /// # Arguments
430    /// * `pin_uv_auth` - Optional PIN/UV auth token with CredentialManagement permission
431    /// * `credential_id` - ID of credential to delete
432    ///
433    /// # Note
434    /// Platforms SHOULD also delete any associated large blobs after successful deletion.
435    pub fn new(pin_uv_auth: Option<PinUvAuth>, credential_id: Vec<u8>) -> Self {
436        Self {
437            pin_uv_auth,
438            credential_id,
439        }
440    }
441
442    /// Get the PIN/UV auth bundle
443    pub fn pin_uv_auth(&self) -> Option<&PinUvAuth> {
444        self.pin_uv_auth.as_ref()
445    }
446
447    /// Get the credential ID
448    pub fn credential_id(&self) -> &[u8] {
449        &self.credential_id
450    }
451}
452
453/// Request to update user information
454#[derive(Debug, Clone)]
455pub struct UpdateUserRequest {
456    pin_uv_auth: Option<PinUvAuth>,
457    credential_id: Vec<u8>,
458    user: User,
459}
460
461impl UpdateUserRequest {
462    /// Create new update user information request
463    ///
464    /// # Arguments
465    /// * `pin_uv_auth` - Optional PIN/UV auth token with CredentialManagement permission
466    /// * `credential_id` - ID of credential to update
467    /// * `user` - New user information
468    ///
469    /// # Important
470    /// - The user.id field MUST match the existing credential's user ID
471    /// - Empty fields in user parameter are removed from the credential
472    /// - Only name and displayName are updated (id is not changed)
473    pub fn new(pin_uv_auth: Option<PinUvAuth>, credential_id: Vec<u8>, user: User) -> Self {
474        Self {
475            pin_uv_auth,
476            credential_id,
477            user,
478        }
479    }
480
481    /// Get the PIN/UV auth bundle
482    pub fn pin_uv_auth(&self) -> Option<&PinUvAuth> {
483        self.pin_uv_auth.as_ref()
484    }
485
486    /// Get the credential ID
487    pub fn credential_id(&self) -> &[u8] {
488        &self.credential_id
489    }
490
491    /// Get the user information
492    pub fn user(&self) -> &User {
493        &self.user
494    }
495}