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