webauthn_kit/credential.rs
1//! `WebAuthn` credential descriptor and protocol DTOs (options, responses,
2//! results).
3//!
4//! This module is **storage-free**. It defines the [`WebauthnCredential`]
5//! record (including the sign-count state that callers must persist) and the
6//! JSON wire structures exchanged with the browser. Persisting, looking up,
7//! and updating credentials is the integrator's job; see the crate-level
8//! documentation for the recommended storage contract.
9
10/// A registered `WebAuthn` credential.
11///
12/// The integrator persists one record per credential. [`WebauthnCredential::sign_count`]
13/// is security-relevant state: after every successful
14/// [`crate::verify_authentication`], the caller must atomically persist
15/// [`AuthenticationResult::new_sign_count`] (monotonic counter used for
16/// authenticator-clone detection).
17///
18/// # Security note
19///
20/// `public_key_cose` is parsed and signature-checked on every authentication,
21/// but the bytes stored at registration time should be treated as trusted
22/// only because they were validated by [`crate::verify_registration`] before
23/// persisting. Never accept a credential record whose provenance you cannot
24/// establish.
25#[derive(Debug, Clone)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27pub struct WebauthnCredential {
28 /// Base64url-encoded credential ID.
29 pub credential_id: String,
30 /// COSE public key bytes (CBOR-encoded).
31 pub public_key_cose: Vec<u8>,
32 /// Counter to prevent replay/clone attacks. Persist the updated value
33 /// returned by [`crate::verify_authentication`] after each login.
34 pub sign_count: u32,
35 /// Human-readable device name (e.g. "`YubiKey` 5 NFC").
36 pub device_name: String,
37 /// Registration timestamp (Unix seconds).
38 pub registered_at: i64,
39 /// Last authentication timestamp (Unix seconds).
40 pub last_used_at: i64,
41 /// Attestation format (e.g. "none", "packed", "fido-u2f", "android-key").
42 ///
43 /// Raw informational string; see the registration result's
44 /// [`crate::attestation::AttestationResult`] for the verified trust
45 /// level.
46 pub attestation_format: String,
47 /// Whether user verification (biometrics/PIN) was performed at registration.
48 pub user_verified: bool,
49 /// Backup eligibility (BE) flag at registration: `true` when the
50 /// credential is a multi-device/syncable credential. Immutable for the
51 /// lifetime of the credential โ persist and keep for policy decisions.
52 ///
53 /// # Security note
54 ///
55 /// BE=1 means account recovery follows the user's *cloud account* rules,
56 /// not this credential's confinement to one device. See
57 /// [`crate::policy::BackupPolicy`] for the trade-offs.
58 pub backup_eligible: bool,
59 /// Backup state (BS) flag at registration: whether the credential was
60 /// backed up at ceremony time. Volatile โ superseded by the BS flag
61 /// reported on every [`AuthenticationResult`].
62 pub backup_state: bool,
63}
64
65/// Options sent to the client for credential registration
66/// (`navigator.credentials.create()`).
67#[derive(Debug)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69pub struct RegistrationOptions {
70 /// Server-generated challenge (Base64url-encoded).
71 pub challenge: String,
72 /// Relying party information.
73 pub rp: RelyingParty,
74 /// User information for the new credential.
75 pub user: WebauthnUser,
76 /// Required public key parameters.
77 pub pub_key_cred_params: Vec<PubKeyCredParam>,
78 /// Timeout hint in milliseconds.
79 pub timeout: u64,
80 /// Exclude already-registered credentials.
81 pub exclude_credentials: Vec<ExcludeCredential>,
82 /// Attestation conveyance preference.
83 pub attestation: String,
84 /// Authenticator selection criteria.
85 pub authenticator_selection: AuthenticatorSelection,
86}
87
88/// Relying party descriptor.
89#[derive(Debug, Clone)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91pub struct RelyingParty {
92 /// Relying party ID (effective domain).
93 pub id: String,
94 /// Human-readable relying party name.
95 pub name: String,
96}
97
98/// User descriptor for registration.
99#[derive(Debug, Clone)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
101pub struct WebauthnUser {
102 /// Unique user ID (Base64url-encoded).
103 pub id: String,
104 /// Display name (e.g. "Alice Johnson").
105 pub name: String,
106 /// Username (e.g. "alice").
107 pub display_name: String,
108}
109
110/// Public key credential parameter (algorithm + type).
111#[derive(Debug, Clone)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113pub struct PubKeyCredParam {
114 /// Algorithm identifier (COSE algorithm).
115 pub alg: i32,
116 /// Credential type (always "public-key").
117 pub type_: String,
118}
119
120/// Credential to exclude from registration (prevent re-registration).
121#[derive(Debug, Clone)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123pub struct ExcludeCredential {
124 /// Base64url-encoded credential ID.
125 pub id: String,
126 /// Credential type.
127 pub type_: String,
128 /// Optional transports hint.
129 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
130 pub transports: Option<Vec<String>>,
131}
132
133/// Authenticator selection criteria.
134#[derive(Debug, Clone)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136pub struct AuthenticatorSelection {
137 /// Require resident key (discoverable credential).
138 pub resident_key: String,
139 /// User verification requirement.
140 pub user_verification: String,
141 /// Legacy L2 companion of `resident_key = "required"`: tells L2-only
142 /// clients a discoverable credential is mandatory. Only meaningful when
143 /// `resident_key` is `"required"` (WebAuthn L3 ยง5.4.4).
144 #[cfg_attr(feature = "serde", serde(default))]
145 pub require_resident_key: bool,
146}
147
148/// Response from the client during registration
149/// (`navigator.credentials.create()` response).
150///
151/// The integrator deserializes this from the browser payload and passes the
152/// fields to [`crate::verify_registration`].
153#[derive(Debug)]
154#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
155pub struct RegistrationResponse {
156 /// Client data JSON (Base64url-encoded).
157 pub client_data_json: String,
158 /// Attestation object (Base64url-encoded).
159 pub attestation_object: String,
160 /// Transports used (e.g. "usb", "nfc", "internal").
161 #[cfg_attr(feature = "serde", serde(default))]
162 pub transports: Vec<String>,
163}
164
165/// Options sent to the client for authentication
166/// (`navigator.credentials.get()`).
167#[derive(Debug)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169pub struct AuthenticationOptions {
170 /// Server-generated challenge (Base64url-encoded).
171 pub challenge: String,
172 /// Relying party ID.
173 pub rp_id: String,
174 /// Allowed credential IDs for this authentication.
175 pub allow_credentials: Vec<AllowCredential>,
176 /// Timeout hint in milliseconds.
177 pub timeout: u64,
178 /// User verification requirement.
179 pub user_verification: String,
180}
181
182/// Credential allowed for authentication.
183#[derive(Debug, Clone)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
185pub struct AllowCredential {
186 /// Base64url-encoded credential ID.
187 pub id: String,
188 /// Credential type.
189 pub type_: String,
190 /// Optional transports hint.
191 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
192 pub transports: Option<Vec<String>>,
193}
194
195/// Response from the client during authentication
196/// (`navigator.credentials.get()` response).
197///
198/// The integrator deserializes this from the browser payload and passes the
199/// fields to [`crate::verify_authentication`].
200#[derive(Debug)]
201#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
202pub struct AuthenticationResponse {
203 /// Base64url-encoded credential ID.
204 pub id: String,
205 /// Client data JSON (Base64url-encoded).
206 pub client_data_json: String,
207 /// Authenticator data (Base64url-encoded).
208 pub authenticator_data: String,
209 /// Signature (Base64url-encoded).
210 pub signature: String,
211 /// User handle (Base64url-encoded, for resident keys).
212 pub user_handle: Option<String>,
213}
214
215/// Result of a successful registration.
216///
217/// The caller is responsible for persisting a [`WebauthnCredential`] built
218/// from the verified credential ID and COSE public key extracted from the
219/// authenticator data.
220#[derive(Debug)]
221#[cfg_attr(feature = "serde", derive(serde::Serialize))]
222pub struct RegistrationResult {
223 /// The credential ID that was registered (Base64url-encoded).
224 pub credential_id: String,
225 /// Device name (from client or auto-generated).
226 pub device_name: String,
227 /// Raw attestation format string from the attestation object (e.g.
228 /// "none", "packed", "fido-u2f").
229 pub attestation_format: String,
230 /// Verified attestation details (format, AAGUID, trust level, warnings).
231 ///
232 /// # Security note
233 ///
234 /// Only [`crate::attestation::TrustLevel::AttCa`] (a chain terminating
235 /// at a configured trust anchor) attests device provenance;
236 /// [`crate::attestation::TrustLevel::BasicAtt`] and
237 /// [`crate::attestation::TrustLevel::SelfAttested`] establish key
238 /// possession only. Inspect [`crate::attestation::AttestationResult::warnings`]
239 /// for trust-weakening flags.
240 pub attestation: crate::attestation::AttestationResult,
241 /// Whether user verification was performed.
242 pub user_verified: bool,
243 /// Backup eligibility (BE) flag: `true` for multi-device/syncable
244 /// credentials. Immutable; persist alongside the credential record.
245 pub backup_eligible: bool,
246 /// Backup state (BS) flag: whether the credential was backed up at
247 /// registration time.
248 pub backup_state: bool,
249}
250
251/// Result of a successful authentication.
252///
253/// # Security note
254///
255/// [`AuthenticationResult::new_sign_count`] MUST be persisted by the caller
256/// (compare-and-swap against the stored value) โ see [`WebauthnCredential::sign_count`].
257#[derive(Debug)]
258#[cfg_attr(feature = "serde", derive(serde::Serialize))]
259pub struct AuthenticationResult {
260 /// The credential ID that authenticated.
261 pub credential_id: String,
262 /// Updated sign count; persist this after verification succeeds.
263 pub new_sign_count: u32,
264 /// Whether user verification was performed. Under
265 /// [`crate::policy::UserVerificationPolicy::Required`] this is
266 /// guaranteed `true` (the ceremony is otherwise rejected).
267 pub user_verified: bool,
268 /// Backup state (BS) flag from the assertion: whether the credential is
269 /// currently backed up. Observed to change over time for synced
270 /// credentials; persist if you track it.
271 pub backup_state: bool,
272 /// Backup eligibility (BE) flag from the assertion. Should match the
273 /// value observed at registration; a mismatch indicates the authenticator
274 /// is violating the spec.
275 pub backup_eligible: bool,
276}