Skip to main content

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    #[cfg_attr(feature = "serde", serde(rename = "pubKeyCredParams"))]
78    pub pub_key_cred_params: Vec<PubKeyCredParam>,
79    /// Timeout hint in milliseconds.
80    pub timeout: u64,
81    /// Exclude already-registered credentials.
82    #[cfg_attr(feature = "serde", serde(rename = "excludeCredentials"))]
83    pub exclude_credentials: Vec<ExcludeCredential>,
84    /// Attestation conveyance preference.
85    pub attestation: String,
86    /// Authenticator selection criteria.
87    #[cfg_attr(feature = "serde", serde(rename = "authenticatorSelection"))]
88    pub authenticator_selection: AuthenticatorSelection,
89}
90
91/// Relying party descriptor.
92#[derive(Debug, Clone)]
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94pub struct RelyingParty {
95    /// Relying party ID (effective domain).
96    pub id: String,
97    /// Human-readable relying party name.
98    pub name: String,
99}
100
101/// User descriptor for registration.
102#[derive(Debug, Clone)]
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
104pub struct WebauthnUser {
105    /// Unique user ID (Base64url-encoded).
106    pub id: String,
107    /// Display name (e.g. "Alice Johnson").
108    pub name: String,
109    /// Username (e.g. "alice").
110    #[cfg_attr(feature = "serde", serde(rename = "displayName"))]
111    pub display_name: String,
112}
113
114/// Public key credential parameter (algorithm + type).
115#[derive(Debug, Clone)]
116#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
117pub struct PubKeyCredParam {
118    /// Algorithm identifier (COSE algorithm).
119    pub alg: i32,
120    /// Credential type (always "public-key").
121    #[cfg_attr(feature = "serde", serde(rename = "type"))]
122    pub type_: String,
123}
124
125/// Credential to exclude from registration (prevent re-registration).
126#[derive(Debug, Clone)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct ExcludeCredential {
129    /// Base64url-encoded credential ID.
130    pub id: String,
131    /// Credential type.
132    #[cfg_attr(feature = "serde", serde(rename = "type"))]
133    pub type_: String,
134    /// Optional transports hint.
135    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
136    pub transports: Option<Vec<String>>,
137}
138
139/// Authenticator selection criteria.
140#[derive(Debug, Clone)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142pub struct AuthenticatorSelection {
143    /// Require resident key (discoverable credential).
144    #[cfg_attr(feature = "serde", serde(rename = "residentKey"))]
145    pub resident_key: String,
146    /// User verification requirement.
147    #[cfg_attr(feature = "serde", serde(rename = "userVerification"))]
148    pub user_verification: String,
149    /// Legacy L2 companion of `resident_key = "required"`: tells L2-only
150    /// clients a discoverable credential is mandatory. Only meaningful when
151    /// `resident_key` is `"required"` (WebAuthn L3 ยง5.4.4).
152    #[cfg_attr(feature = "serde", serde(rename = "requireResidentKey", default))]
153    pub require_resident_key: bool,
154}
155
156/// Response from the client during registration
157/// (`navigator.credentials.create()` response).
158///
159/// The integrator deserializes this from the browser payload and passes the
160/// fields to [`crate::verify_registration`].
161#[derive(Debug)]
162#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
163pub struct RegistrationResponse {
164    /// Client data JSON (Base64url-encoded).
165    pub client_data_json: String,
166    /// Attestation object (Base64url-encoded).
167    pub attestation_object: String,
168    /// Transports used (e.g. "usb", "nfc", "internal").
169    #[cfg_attr(feature = "serde", serde(default))]
170    pub transports: Vec<String>,
171}
172
173/// Options sent to the client for authentication
174/// (`navigator.credentials.get()`).
175#[derive(Debug)]
176#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
177pub struct AuthenticationOptions {
178    /// Server-generated challenge (Base64url-encoded).
179    pub challenge: String,
180    /// Relying party ID.
181    #[cfg_attr(feature = "serde", serde(rename = "rpId"))]
182    pub rp_id: String,
183    /// Allowed credential IDs for this authentication.
184    #[cfg_attr(feature = "serde", serde(rename = "allowCredentials"))]
185    pub allow_credentials: Vec<AllowCredential>,
186    /// Timeout hint in milliseconds.
187    pub timeout: u64,
188    /// User verification requirement.
189    #[cfg_attr(feature = "serde", serde(rename = "userVerification"))]
190    pub user_verification: String,
191}
192
193/// Credential allowed for authentication.
194#[derive(Debug, Clone)]
195#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
196pub struct AllowCredential {
197    /// Base64url-encoded credential ID.
198    pub id: String,
199    /// Credential type.
200    #[cfg_attr(feature = "serde", serde(rename = "type"))]
201    pub type_: String,
202    /// Optional transports hint.
203    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
204    pub transports: Option<Vec<String>>,
205}
206
207/// Response from the client during authentication
208/// (`navigator.credentials.get()` response).
209///
210/// The integrator deserializes this from the browser payload and passes the
211/// fields to [`crate::verify_authentication`].
212#[derive(Debug)]
213#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
214pub struct AuthenticationResponse {
215    /// Base64url-encoded credential ID.
216    pub id: String,
217    /// Client data JSON (Base64url-encoded).
218    pub client_data_json: String,
219    /// Authenticator data (Base64url-encoded).
220    pub authenticator_data: String,
221    /// Signature (Base64url-encoded).
222    pub signature: String,
223    /// User handle (Base64url-encoded, for resident keys).
224    pub user_handle: Option<String>,
225}
226
227/// Result of a successful registration.
228///
229/// The caller is responsible for persisting a [`WebauthnCredential`] built
230/// from the verified credential ID and COSE public key extracted from the
231/// authenticator data.
232#[derive(Debug)]
233#[cfg_attr(feature = "serde", derive(serde::Serialize))]
234pub struct RegistrationResult {
235    /// The credential ID that was registered (Base64url-encoded).
236    pub credential_id: String,
237    /// The attested COSE public key (raw CBOR bytes from the authenticator
238    /// data). Persist these bytes: they are the input to
239    /// [`crate::AuthenticationParams::public_key_cose`] for every future
240    /// authentication of this credential.
241    pub public_key_cose: Vec<u8>,
242    /// Device name (from client or auto-generated).
243    pub device_name: String,
244    /// Raw attestation format string from the attestation object (e.g.
245    /// "none", "packed", "fido-u2f").
246    pub attestation_format: String,
247    /// Verified attestation details (format, AAGUID, trust level, warnings).
248    ///
249    /// # Security note
250    ///
251    /// Only [`crate::attestation::TrustLevel::AttCa`] (a chain terminating
252    /// at a configured trust anchor) attests device provenance;
253    /// [`crate::attestation::TrustLevel::BasicAtt`] and
254    /// [`crate::attestation::TrustLevel::SelfAttested`] establish key
255    /// possession only. Inspect [`crate::attestation::AttestationResult::warnings`]
256    /// for trust-weakening flags.
257    pub attestation: crate::attestation::AttestationResult,
258    /// Whether user verification was performed.
259    pub user_verified: bool,
260    /// Backup eligibility (BE) flag: `true` for multi-device/syncable
261    /// credentials. Immutable; persist alongside the credential record.
262    pub backup_eligible: bool,
263    /// Backup state (BS) flag: whether the credential was backed up at
264    /// registration time.
265    pub backup_state: bool,
266}
267
268/// Result of a successful authentication.
269///
270/// # Security note
271///
272/// [`AuthenticationResult::new_sign_count`] MUST be persisted by the caller
273/// (compare-and-swap against the stored value) โ€” see [`WebauthnCredential::sign_count`].
274#[derive(Debug)]
275#[cfg_attr(feature = "serde", derive(serde::Serialize))]
276pub struct AuthenticationResult {
277    /// The credential ID that authenticated.
278    pub credential_id: String,
279    /// Updated sign count; persist this after verification succeeds.
280    pub new_sign_count: u32,
281    /// Whether user verification was performed. Under
282    /// [`crate::policy::UserVerificationPolicy::Required`] this is
283    /// guaranteed `true` (the ceremony is otherwise rejected).
284    pub user_verified: bool,
285    /// Backup state (BS) flag from the assertion: whether the credential is
286    /// currently backed up. Observed to change over time for synced
287    /// credentials; persist if you track it.
288    pub backup_state: bool,
289    /// Backup eligibility (BE) flag from the assertion. Should match the
290    /// value observed at registration; a mismatch indicates the authenticator
291    /// is violating the spec.
292    pub backup_eligible: bool,
293}