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}