soft_fido2/
types.rs

1//! High-level types for FIDO2/WebAuthn credentials
2//!
3//! This module provides ergonomic types for working with FIDO2 credentials at the
4//! application level. The main difference from the CTAP protocol layer is the use
5//! of grouped structures for better API usability:
6//!
7//! ```rust
8//! use soft_fido2::types::Credential;
9//!
10//! # let credential = Credential {
11//! #     id: vec![1, 2, 3],
12//! #     rp: soft_fido2::RelyingParty::new("example.com".into()),
13//! #     user: soft_fido2::User::new(vec![1, 2, 3]),
14//! #     sign_count: 0,
15//! #     alg: -7,
16//! #     private_key: soft_fido2_ctap::SecBytes::new(vec![0u8; 32]),
17//! #     created: 0,
18//! #     discoverable: false,
19//! #     extensions: soft_fido2::Extensions::default(),
20//! # };
21//! // Grouped fields for ergonomics
22//! println!("RP: {}", credential.rp.id);
23//! println!("User: {}", credential.user.id[0]);
24//! ```
25//!
26//! ## Type Organization
27//!
28//! - **[`RelyingParty`]** and **[`User`]**: Basic WebAuthn entities (from CTAP layer)
29//! - **[`Credential`]**: Owned credential with grouped fields for API ergonomics
30//! - **[`CredentialRef`]**: Zero-copy borrowed credential for callback interfaces
31//! - **[`Extensions`]**: WebAuthn extension data (credProtect, hmac-secret)
32//!
33//! ## Converting Between Layers
34//!
35//! Use [`From`] trait to convert between high-level and CTAP protocol representations:
36//!
37//! ```rust
38//! # use soft_fido2::types::Credential;
39//! # let high_level_cred = Credential {
40//! #     id: vec![1, 2, 3],
41//! #     rp: soft_fido2::RelyingParty::new("example.com".into()),
42//! #     user: soft_fido2::User::new(vec![1, 2, 3]),
43//! #     sign_count: 0,
44//! #     alg: -7,
45//! #     private_key: soft_fido2_ctap::SecBytes::new(vec![0u8; 32]),
46//! #     created: 0,
47//! #     discoverable: false,
48//! #     extensions: soft_fido2::Extensions::default(),
49//! # };
50//! // Convert to CTAP protocol format (flat structure)
51//! let ctap_cred: soft_fido2_ctap::types::Credential = high_level_cred.into();
52//! ```
53
54use crate::error::{Error, Result};
55
56use soft_fido2_ctap::SecBytes;
57
58use alloc::borrow::ToOwned;
59use alloc::string::ToString;
60use alloc::vec::Vec;
61
62use serde::{Deserialize, Serialize};
63
64pub use soft_fido2_ctap::types::{RelyingParty, User};
65
66/// Credential extension data
67#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
68pub struct Extensions {
69    /// Credential protection level
70    pub cred_protect: Option<u8>,
71    /// HMAC secret extension
72    pub hmac_secret: Option<bool>,
73}
74
75/// Owned representation of a FIDO2 credential
76#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
77pub struct Credential {
78    /// Credential ID (max 64 bytes)
79    pub id: Vec<u8>,
80    /// Relying party information
81    pub rp: RelyingParty,
82    /// User information
83    pub user: User,
84    /// Signature counter
85    pub sign_count: u32,
86    /// Algorithm (-7 for ES256)
87    pub alg: i32,
88    /// Private key bytes (32 bytes for ES256)
89    ///
90    /// # Security
91    ///
92    /// - **With `std` feature** (default): Protected using `SecVec` which:
93    ///   - Zeros memory on drop using `mlock`
94    ///   - Prevents swapping to disk
95    ///   - Uses constant-time equality
96    /// - **Without `std` (no_std)**: Stored as plain `Vec<u8>` for compatibility.
97    ///   No memory protection is provided in no_std environments.
98    ///
99    /// The storage boundary (this field) is the primary protection point for
100    /// long-term credential storage.
101    pub private_key: SecBytes,
102    /// Creation timestamp
103    pub created: i64,
104    /// Is resident key
105    pub discoverable: bool,
106    /// Extension data
107    pub extensions: Extensions,
108}
109
110/// Borrowed (zero-copy) representation of a FIDO2 credential
111///
112/// This type is used for FFI callbacks to avoid heap allocations.
113/// All fields are borrowed and have no ownership.
114#[derive(Clone, Copy, Debug)]
115pub struct CredentialRef<'a> {
116    /// Credential ID (max 64 bytes)
117    pub id: &'a [u8],
118    /// Relying party ID (max 128 bytes)
119    pub rp_id: &'a str,
120    /// Relying party name (optional, max 64 bytes)
121    pub rp_name: Option<&'a str>,
122    /// User ID (max 64 bytes)
123    pub user_id: &'a [u8],
124    /// User name
125    pub user_name: Option<&'a str>,
126    /// User display name (optional)
127    pub user_display_name: Option<&'a str>,
128    /// Signature counter
129    pub sign_count: &'a u32,
130    /// Algorithm (-7 for ES256)
131    pub alg: &'a i32,
132    /// Private key bytes (32 bytes for ES256)
133    pub private_key: &'a SecBytes,
134    /// Creation timestamp
135    pub created: &'a i64,
136    /// Is resident key
137    pub discoverable: &'a bool,
138    /// Credential protection level
139    pub cred_protect: Option<&'a u8>,
140}
141
142impl<'a> CredentialRef<'a> {
143    /// Convert borrowed credential to owned Credential
144    pub fn to_owned(&self) -> Credential {
145        Credential {
146            id: self.id.to_vec(),
147            rp: RelyingParty {
148                id: self.rp_id.to_string(),
149                name: self.rp_name.map(|s| s.to_string()),
150            },
151            user: User {
152                id: self.user_id.to_vec(),
153                name: self.user_name.map(|s| s.to_string()),
154                display_name: self.user_display_name.map(|s| s.to_string()),
155            },
156            sign_count: self.sign_count.to_owned(),
157            alg: self.alg.to_owned(),
158            private_key: self.private_key.to_owned(),
159            created: self.created.to_owned(),
160            discoverable: self.discoverable.to_owned(),
161            extensions: Extensions {
162                cred_protect: self.cred_protect.copied(),
163                hmac_secret: None,
164            },
165        }
166    }
167
168    /// Serialize credential to bytes (CBOR)
169    pub fn to_bytes(&self) -> Result<Vec<u8>> {
170        let owned = self.to_owned();
171        let mut buf = Vec::new();
172        soft_fido2_ctap::cbor::into_writer(&owned, &mut buf).map_err(|_| Error::Other)?;
173        Ok(buf)
174    }
175}
176
177impl Credential {
178    /// Serialize credential to bytes (CBOR)
179    pub fn to_bytes(&self) -> Result<Vec<u8>> {
180        let mut buf = Vec::new();
181        soft_fido2_ctap::cbor::into_writer(self, &mut buf).map_err(|_| Error::Other)?;
182        Ok(buf)
183    }
184
185    /// Deserialize credential from bytes (CBOR)
186    pub fn from_bytes(data: &[u8]) -> Result<Self> {
187        soft_fido2_ctap::cbor::decode(data).map_err(|_| Error::Other)
188    }
189}
190
191impl From<soft_fido2_ctap::types::Credential> for Credential {
192    fn from(cred: soft_fido2_ctap::types::Credential) -> Self {
193        Credential {
194            id: cred.id,
195            rp: RelyingParty {
196                id: cred.rp_id,
197                name: cred.rp_name,
198            },
199            user: User {
200                id: cred.user_id,
201                name: cred.user_name,
202                display_name: cred.user_display_name,
203            },
204            sign_count: cred.sign_count,
205            alg: cred.algorithm,
206            private_key: cred.private_key,
207            created: cred.created,
208            discoverable: cred.discoverable,
209            extensions: Extensions {
210                cred_protect: Some(cred.cred_protect),
211                hmac_secret: None,
212            },
213        }
214    }
215}
216
217impl From<Credential> for soft_fido2_ctap::types::Credential {
218    fn from(cred: Credential) -> Self {
219        soft_fido2_ctap::types::Credential {
220            id: cred.id,
221            rp_id: cred.rp.id,
222            rp_name: cred.rp.name,
223            user_id: cred.user.id,
224            user_name: cred.user.name,
225            user_display_name: cred.user.display_name,
226            sign_count: cred.sign_count,
227            algorithm: cred.alg,
228            private_key: cred.private_key,
229            created: cred.created,
230            discoverable: cred.discoverable,
231            cred_protect: cred.extensions.cred_protect.unwrap_or(1),
232        }
233    }
234}