Skip to main content

webauthn/
error.rs

1//! Error types for the webauthn library.
2//!
3//! Every variant produces a message that aids debugging without leaking
4//! security-sensitive material (key bytes, challenge values, etc.).
5
6use thiserror::Error;
7
8/// All errors that can be returned by WebAuthn ceremony verification.
9#[derive(Debug, Error)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11pub enum WebAuthnError {
12    /// The client data JSON could not be decoded or is structurally invalid.
13    #[error("Invalid client data: {0}")]
14    InvalidClientData(String),
15
16    /// The challenge inside the client data does not match the issued challenge.
17    ///
18    /// Security-critical: a mismatch means the response was not produced for
19    /// this ceremony instance.
20    #[error("Challenge mismatch: expected challenge does not match response")]
21    ChallengeMismatch,
22
23    /// The `origin` field in the client data does not match the expected origin.
24    ///
25    /// Prevents a credential from one origin being replayed at another.
26    #[error("Origin mismatch: expected {expected}, got {got}")]
27    OriginMismatch { expected: String, got: String },
28
29    /// The RP ID hash in authenticator data does not equal SHA-256(rp_id).
30    ///
31    /// Ensures the authenticator bound the credential to the correct relying party.
32    #[error("RP ID hash mismatch")]
33    RpIdHashMismatch,
34
35    /// The User Present (UP) flag is not set in the authenticator data flags byte.
36    #[error("User Present flag not set")]
37    UserNotPresent,
38
39    /// The User Verification (UV) flag is not set, but the relying party has
40    /// `require_user_verification` enabled.
41    ///
42    /// The authenticator must perform user verification (PIN, biometric, etc.)
43    /// before the assertion is accepted.
44    #[error("User Verification flag not set")]
45    UserNotVerified,
46
47    /// The attestation object could not be decoded or is missing required fields.
48    #[error("Invalid attestation object: {0}")]
49    InvalidAttestationObject(String),
50
51    /// The authenticator data bytes are malformed or too short.
52    #[error("Invalid authenticator data: {0}")]
53    InvalidAuthenticatorData(String),
54
55    /// The COSE public key inside the credential data is invalid.
56    #[error("Invalid public key: {0}")]
57    InvalidPublicKey(String),
58
59    /// ECDSA signature verification returned a failure.
60    ///
61    /// The message was either tampered with or signed by the wrong key.
62    #[error("Signature verification failed")]
63    SignatureVerificationFailed,
64
65    /// The sign count in the assertion is not greater than the stored sign count.
66    ///
67    /// Indicates a possible authenticator clone or replay attack.
68    #[error("Sign count invalid: stored {stored}, received {received}")]
69    SignCountInvalid { stored: u32, received: u32 },
70
71    /// A CBOR decoding step failed.
72    #[error("CBOR decode error: {0}")]
73    CborDecodeError(String),
74
75    /// A base64url decoding step failed.
76    #[error("Base64 decode error: {0}")]
77    Base64DecodeError(String),
78
79    /// The challenge was issued too long ago and is no longer valid.
80    ///
81    /// Callers should generate a new challenge and restart the ceremony.
82    #[error("Challenge expired")]
83    ChallengeExpired,
84
85    /// The COSE algorithm identifier is not supported by this library.
86    ///
87    /// The `i64` is the raw COSE algorithm integer (e.g. `-7` = ES256, `-257` = RS256).
88    #[error("Unsupported algorithm: {0}")]
89    UnsupportedAlgorithm(i64),
90
91    /// `clientDataJSON` contains `crossOrigin: true` but the relying party has
92    /// `reject_cross_origin` enabled.
93    ///
94    /// Cross-origin credentials allow assertions from an iframe whose origin
95    /// differs from the top-level origin. When the RP does not expect embedded
96    /// usage, `crossOrigin: true` may indicate credential abuse.
97    #[error("Cross-origin credential use is not permitted by this relying party")]
98    CrossOriginNotAllowed,
99
100    /// The credential has the Backup Eligibility (BE) flag set, but this relying
101    /// party has `reject_backup_eligible` enabled.
102    ///
103    /// Use this policy when your threat model requires hardware-bound keys that
104    /// cannot be synced to a cloud or platform account.
105    #[error("Credential is backup-eligible but this relying party does not permit backed-up credentials")]
106    BackupEligibleNotAllowed,
107
108    /// The credential does not have the Backup Eligibility (BE) flag set, but
109    /// this relying party has `require_backup_eligible` enabled.
110    ///
111    /// Use this policy for consumer passkey deployments that depend on credential
112    /// sync (e.g. cross-device sign-in via iCloud Keychain or Google Password Manager).
113    #[error("Credential is not backup-eligible but this relying party requires backup-eligible credentials")]
114    BackupEligibilityRequired,
115
116    /// The Backup Eligibility (BE) flag in the authenticator data differs from
117    /// the value recorded at registration time.
118    ///
119    /// BE is immutable per spec — a mismatch indicates a possible credential
120    /// substitution attack (a different authenticator presenting the same credential ID).
121    #[error(
122        "Backup Eligibility flag changed since registration — credential may have been substituted"
123    )]
124    BackupEligibilityChanged,
125}
126
127/// Convenience alias so callers write `webauthn::Result<T>`.
128pub type Result<T> = std::result::Result<T, WebAuthnError>;