webauthn-kit 0.3.6

Standalone WebAuthn/FIDO2 passkey verification kit: CTAP2 authenticator data parsing, COSE ES256/ES384/EdDSA/RS256 signature verification via ring, enforceable credential policies (UV, backup eligibility, algorithm allowlists), challenge freshness/replay protection and sign-count clone detection.
Documentation
//! `WebAuthn` credential descriptor and protocol DTOs (options, responses,
//! results).
//!
//! This module is **storage-free**. It defines the [`WebauthnCredential`]
//! record (including the sign-count state that callers must persist) and the
//! JSON wire structures exchanged with the browser. Persisting, looking up,
//! and updating credentials is the integrator's job; see the crate-level
//! documentation for the recommended storage contract.

/// A registered `WebAuthn` credential.
///
/// The integrator persists one record per credential. [`WebauthnCredential::sign_count`]
/// is security-relevant state: after every successful
/// [`crate::verify_authentication`], the caller must atomically persist
/// [`AuthenticationResult::new_sign_count`] (monotonic counter used for
/// authenticator-clone detection).
///
/// # Security note
///
/// `public_key_cose` is parsed and signature-checked on every authentication,
/// but the bytes stored at registration time should be treated as trusted
/// only because they were validated by [`crate::verify_registration`] before
/// persisting. Never accept a credential record whose provenance you cannot
/// establish.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WebauthnCredential {
    /// Base64url-encoded credential ID.
    pub credential_id: String,
    /// COSE public key bytes (CBOR-encoded).
    pub public_key_cose: Vec<u8>,
    /// Counter to prevent replay/clone attacks. Persist the updated value
    /// returned by [`crate::verify_authentication`] after each login.
    pub sign_count: u32,
    /// Human-readable device name (e.g. "`YubiKey` 5 NFC").
    pub device_name: String,
    /// Registration timestamp (Unix seconds).
    pub registered_at: i64,
    /// Last authentication timestamp (Unix seconds).
    pub last_used_at: i64,
    /// Attestation format (e.g. "none", "packed", "fido-u2f", "android-key").
    ///
    /// Raw informational string; see the registration result's
    /// [`crate::attestation::AttestationResult`] for the verified trust
    /// level.
    pub attestation_format: String,
    /// Whether user verification (biometrics/PIN) was performed at registration.
    pub user_verified: bool,
    /// Backup eligibility (BE) flag at registration: `true` when the
    /// credential is a multi-device/syncable credential. Immutable for the
    /// lifetime of the credential โ€” persist and keep for policy decisions.
    ///
    /// # Security note
    ///
    /// BE=1 means account recovery follows the user's *cloud account* rules,
    /// not this credential's confinement to one device. See
    /// [`crate::policy::BackupPolicy`] for the trade-offs.
    pub backup_eligible: bool,
    /// Backup state (BS) flag at registration: whether the credential was
    /// backed up at ceremony time. Volatile โ€” superseded by the BS flag
    /// reported on every [`AuthenticationResult`].
    pub backup_state: bool,
}

/// Options sent to the client for credential registration
/// (`navigator.credentials.create()`).
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RegistrationOptions {
    /// Server-generated challenge (Base64url-encoded).
    pub challenge: String,
    /// Relying party information.
    pub rp: RelyingParty,
    /// User information for the new credential.
    pub user: WebauthnUser,
    /// Required public key parameters.
    #[cfg_attr(feature = "serde", serde(rename = "pubKeyCredParams"))]
    pub pub_key_cred_params: Vec<PubKeyCredParam>,
    /// Timeout hint in milliseconds.
    pub timeout: u64,
    /// Exclude already-registered credentials.
    #[cfg_attr(feature = "serde", serde(rename = "excludeCredentials"))]
    pub exclude_credentials: Vec<ExcludeCredential>,
    /// Attestation conveyance preference.
    pub attestation: String,
    /// Authenticator selection criteria.
    #[cfg_attr(feature = "serde", serde(rename = "authenticatorSelection"))]
    pub authenticator_selection: AuthenticatorSelection,
}

/// Relying party descriptor.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RelyingParty {
    /// Relying party ID (effective domain).
    pub id: String,
    /// Human-readable relying party name.
    pub name: String,
}

/// User descriptor for registration.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct WebauthnUser {
    /// Unique user ID (Base64url-encoded).
    pub id: String,
    /// Display name (e.g. "Alice Johnson").
    pub name: String,
    /// Username (e.g. "alice").
    #[cfg_attr(feature = "serde", serde(rename = "displayName"))]
    pub display_name: String,
}

/// Public key credential parameter (algorithm + type).
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PubKeyCredParam {
    /// Algorithm identifier (COSE algorithm).
    pub alg: i32,
    /// Credential type (always "public-key").
    #[cfg_attr(feature = "serde", serde(rename = "type"))]
    pub type_: String,
}

/// Credential to exclude from registration (prevent re-registration).
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ExcludeCredential {
    /// Base64url-encoded credential ID.
    pub id: String,
    /// Credential type.
    #[cfg_attr(feature = "serde", serde(rename = "type"))]
    pub type_: String,
    /// Optional transports hint.
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub transports: Option<Vec<String>>,
}

/// Authenticator selection criteria.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AuthenticatorSelection {
    /// Require resident key (discoverable credential).
    #[cfg_attr(feature = "serde", serde(rename = "residentKey"))]
    pub resident_key: String,
    /// User verification requirement.
    #[cfg_attr(feature = "serde", serde(rename = "userVerification"))]
    pub user_verification: String,
    /// Legacy L2 companion of `resident_key = "required"`: tells L2-only
    /// clients a discoverable credential is mandatory. Only meaningful when
    /// `resident_key` is `"required"` (WebAuthn L3 ยง5.4.4).
    #[cfg_attr(feature = "serde", serde(rename = "requireResidentKey", default))]
    pub require_resident_key: bool,
}

/// Response from the client during registration
/// (`navigator.credentials.create()` response).
///
/// The integrator deserializes this from the browser payload and passes the
/// fields to [`crate::verify_registration`].
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct RegistrationResponse {
    /// Client data JSON (Base64url-encoded).
    pub client_data_json: String,
    /// Attestation object (Base64url-encoded).
    pub attestation_object: String,
    /// Transports used (e.g. "usb", "nfc", "internal").
    #[cfg_attr(feature = "serde", serde(default))]
    pub transports: Vec<String>,
}

/// Options sent to the client for authentication
/// (`navigator.credentials.get()`).
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AuthenticationOptions {
    /// Server-generated challenge (Base64url-encoded).
    pub challenge: String,
    /// Relying party ID.
    #[cfg_attr(feature = "serde", serde(rename = "rpId"))]
    pub rp_id: String,
    /// Allowed credential IDs for this authentication.
    #[cfg_attr(feature = "serde", serde(rename = "allowCredentials"))]
    pub allow_credentials: Vec<AllowCredential>,
    /// Timeout hint in milliseconds.
    pub timeout: u64,
    /// User verification requirement.
    #[cfg_attr(feature = "serde", serde(rename = "userVerification"))]
    pub user_verification: String,
}

/// Credential allowed for authentication.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AllowCredential {
    /// Base64url-encoded credential ID.
    pub id: String,
    /// Credential type.
    #[cfg_attr(feature = "serde", serde(rename = "type"))]
    pub type_: String,
    /// Optional transports hint.
    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
    pub transports: Option<Vec<String>>,
}

/// Response from the client during authentication
/// (`navigator.credentials.get()` response).
///
/// The integrator deserializes this from the browser payload and passes the
/// fields to [`crate::verify_authentication`].
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
pub struct AuthenticationResponse {
    /// Base64url-encoded credential ID.
    pub id: String,
    /// Client data JSON (Base64url-encoded).
    pub client_data_json: String,
    /// Authenticator data (Base64url-encoded).
    pub authenticator_data: String,
    /// Signature (Base64url-encoded).
    pub signature: String,
    /// User handle (Base64url-encoded, for resident keys).
    pub user_handle: Option<String>,
}

/// Result of a successful registration.
///
/// The caller is responsible for persisting a [`WebauthnCredential`] built
/// from the verified credential ID and COSE public key extracted from the
/// authenticator data.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct RegistrationResult {
    /// The credential ID that was registered (Base64url-encoded).
    pub credential_id: String,
    /// Device name (from client or auto-generated).
    pub device_name: String,
    /// Raw attestation format string from the attestation object (e.g.
    /// "none", "packed", "fido-u2f").
    pub attestation_format: String,
    /// Verified attestation details (format, AAGUID, trust level, warnings).
    ///
    /// # Security note
    ///
    /// Only [`crate::attestation::TrustLevel::AttCa`] (a chain terminating
    /// at a configured trust anchor) attests device provenance;
    /// [`crate::attestation::TrustLevel::BasicAtt`] and
    /// [`crate::attestation::TrustLevel::SelfAttested`] establish key
    /// possession only. Inspect [`crate::attestation::AttestationResult::warnings`]
    /// for trust-weakening flags.
    pub attestation: crate::attestation::AttestationResult,
    /// Whether user verification was performed.
    pub user_verified: bool,
    /// Backup eligibility (BE) flag: `true` for multi-device/syncable
    /// credentials. Immutable; persist alongside the credential record.
    pub backup_eligible: bool,
    /// Backup state (BS) flag: whether the credential was backed up at
    /// registration time.
    pub backup_state: bool,
}

/// Result of a successful authentication.
///
/// # Security note
///
/// [`AuthenticationResult::new_sign_count`] MUST be persisted by the caller
/// (compare-and-swap against the stored value) โ€” see [`WebauthnCredential::sign_count`].
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct AuthenticationResult {
    /// The credential ID that authenticated.
    pub credential_id: String,
    /// Updated sign count; persist this after verification succeeds.
    pub new_sign_count: u32,
    /// Whether user verification was performed. Under
    /// [`crate::policy::UserVerificationPolicy::Required`] this is
    /// guaranteed `true` (the ceremony is otherwise rejected).
    pub user_verified: bool,
    /// Backup state (BS) flag from the assertion: whether the credential is
    /// currently backed up. Observed to change over time for synced
    /// credentials; persist if you track it.
    pub backup_state: bool,
    /// Backup eligibility (BE) flag from the assertion. Should match the
    /// value observed at registration; a mismatch indicates the authenticator
    /// is violating the spec.
    pub backup_eligible: bool,
}