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}
50
51/// Options sent to the client for credential registration
52/// (`navigator.credentials.create()`).
53#[derive(Debug)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55pub struct RegistrationOptions {
56    /// Server-generated challenge (Base64url-encoded).
57    pub challenge: String,
58    /// Relying party information.
59    pub rp: RelyingParty,
60    /// User information for the new credential.
61    pub user: WebauthnUser,
62    /// Required public key parameters.
63    pub pub_key_cred_params: Vec<PubKeyCredParam>,
64    /// Timeout hint in milliseconds.
65    pub timeout: u64,
66    /// Exclude already-registered credentials.
67    pub exclude_credentials: Vec<ExcludeCredential>,
68    /// Attestation conveyance preference.
69    pub attestation: String,
70    /// Authenticator selection criteria.
71    pub authenticator_selection: AuthenticatorSelection,
72}
73
74/// Relying party descriptor.
75#[derive(Debug, Clone)]
76#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
77pub struct RelyingParty {
78    /// Relying party ID (effective domain).
79    pub id: String,
80    /// Human-readable relying party name.
81    pub name: String,
82}
83
84/// User descriptor for registration.
85#[derive(Debug, Clone)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87pub struct WebauthnUser {
88    /// Unique user ID (Base64url-encoded).
89    pub id: String,
90    /// Display name (e.g. "Alice Johnson").
91    pub name: String,
92    /// Username (e.g. "alice").
93    pub display_name: String,
94}
95
96/// Public key credential parameter (algorithm + type).
97#[derive(Debug, Clone)]
98#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
99pub struct PubKeyCredParam {
100    /// Algorithm identifier (COSE algorithm).
101    pub alg: i32,
102    /// Credential type (always "public-key").
103    pub type_: String,
104}
105
106/// Credential to exclude from registration (prevent re-registration).
107#[derive(Debug, Clone)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
109pub struct ExcludeCredential {
110    /// Base64url-encoded credential ID.
111    pub id: String,
112    /// Credential type.
113    pub type_: String,
114    /// Optional transports hint.
115    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
116    pub transports: Option<Vec<String>>,
117}
118
119/// Authenticator selection criteria.
120#[derive(Debug, Clone)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122pub struct AuthenticatorSelection {
123    /// Require resident key (discoverable credential).
124    pub resident_key: String,
125    /// User verification requirement.
126    pub user_verification: String,
127}
128
129/// Response from the client during registration
130/// (`navigator.credentials.create()` response).
131///
132/// The integrator deserializes this from the browser payload and passes the
133/// fields to [`crate::verify_registration`].
134#[derive(Debug)]
135#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
136pub struct RegistrationResponse {
137    /// Client data JSON (Base64url-encoded).
138    pub client_data_json: String,
139    /// Attestation object (Base64url-encoded).
140    pub attestation_object: String,
141    /// Transports used (e.g. "usb", "nfc", "internal").
142    #[cfg_attr(feature = "serde", serde(default))]
143    pub transports: Vec<String>,
144}
145
146/// Options sent to the client for authentication
147/// (`navigator.credentials.get()`).
148#[derive(Debug)]
149#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
150pub struct AuthenticationOptions {
151    /// Server-generated challenge (Base64url-encoded).
152    pub challenge: String,
153    /// Relying party ID.
154    pub rp_id: String,
155    /// Allowed credential IDs for this authentication.
156    pub allow_credentials: Vec<AllowCredential>,
157    /// Timeout hint in milliseconds.
158    pub timeout: u64,
159    /// User verification requirement.
160    pub user_verification: String,
161}
162
163/// Credential allowed for authentication.
164#[derive(Debug, Clone)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
166pub struct AllowCredential {
167    /// Base64url-encoded credential ID.
168    pub id: String,
169    /// Credential type.
170    pub type_: String,
171    /// Optional transports hint.
172    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
173    pub transports: Option<Vec<String>>,
174}
175
176/// Response from the client during authentication
177/// (`navigator.credentials.get()` response).
178///
179/// The integrator deserializes this from the browser payload and passes the
180/// fields to [`crate::verify_authentication`].
181#[derive(Debug)]
182#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
183pub struct AuthenticationResponse {
184    /// Base64url-encoded credential ID.
185    pub id: String,
186    /// Client data JSON (Base64url-encoded).
187    pub client_data_json: String,
188    /// Authenticator data (Base64url-encoded).
189    pub authenticator_data: String,
190    /// Signature (Base64url-encoded).
191    pub signature: String,
192    /// User handle (Base64url-encoded, for resident keys).
193    pub user_handle: Option<String>,
194}
195
196/// Result of a successful registration.
197///
198/// The caller is responsible for persisting a [`WebauthnCredential`] built
199/// from the verified credential ID and COSE public key extracted from the
200/// authenticator data.
201#[derive(Debug)]
202#[cfg_attr(feature = "serde", derive(serde::Serialize))]
203pub struct RegistrationResult {
204    /// The credential ID that was registered (Base64url-encoded).
205    pub credential_id: String,
206    /// Device name (from client or auto-generated).
207    pub device_name: String,
208    /// Raw attestation format string from the attestation object (e.g.
209    /// "none", "packed", "fido-u2f").
210    pub attestation_format: String,
211    /// Verified attestation details (format, AAGUID, trust level, warnings).
212    ///
213    /// # Security note
214    ///
215    /// Only [`crate::attestation::TrustLevel::AttCa`] (a chain terminating
216    /// at a configured trust anchor) attests device provenance;
217    /// [`crate::attestation::TrustLevel::BasicAtt`] and
218    /// [`crate::attestation::TrustLevel::SelfAttested`] establish key
219    /// possession only. Inspect [`crate::attestation::AttestationResult::warnings`]
220    /// for trust-weakening flags.
221    pub attestation: crate::attestation::AttestationResult,
222    /// Whether user verification was performed.
223    pub user_verified: bool,
224}
225
226/// Result of a successful authentication.
227///
228/// # Security note
229///
230/// [`AuthenticationResult::new_sign_count`] MUST be persisted by the caller
231/// (compare-and-swap against the stored value) — see [`WebauthnCredential::sign_count`].
232#[derive(Debug)]
233#[cfg_attr(feature = "serde", derive(serde::Serialize))]
234pub struct AuthenticationResult {
235    /// The credential ID that authenticated.
236    pub credential_id: String,
237    /// Updated sign count; persist this after verification succeeds.
238    pub new_sign_count: u32,
239    /// Whether user verification was performed.
240    pub user_verified: bool,
241}