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 challenge has already been consumed by a previous ceremony.
86    ///
87    /// This error is only returned when the relying party has opted in to
88    /// single-use challenge enforcement via
89    /// [`crate::RelyingParty::enforce_single_use_challenges`]. Issue a fresh
90    /// challenge and restart the ceremony.
91    #[error("Challenge was already used in a previous ceremony")]
92    ChallengePreviouslyUsed,
93
94    /// The COSE algorithm identifier is not supported by this library.
95    ///
96    /// The `i64` is the raw COSE algorithm integer (e.g. `-7` = ES256, `-257` = RS256).
97    #[error("Unsupported algorithm: {0}")]
98    UnsupportedAlgorithm(i64),
99
100    /// `clientDataJSON` contains `crossOrigin: true` but the relying party has
101    /// `reject_cross_origin` enabled.
102    ///
103    /// Cross-origin credentials allow assertions from an iframe whose origin
104    /// differs from the top-level origin. When the RP does not expect embedded
105    /// usage, `crossOrigin: true` may indicate credential abuse.
106    #[error("Cross-origin credential use is not permitted by this relying party")]
107    CrossOriginNotAllowed,
108
109    /// The credential has the Backup Eligibility (BE) flag set, but this relying
110    /// party has `reject_backup_eligible` enabled.
111    ///
112    /// Use this policy when your threat model requires hardware-bound keys that
113    /// cannot be synced to a cloud or platform account.
114    #[error("Credential is backup-eligible but this relying party does not permit backed-up credentials")]
115    BackupEligibleNotAllowed,
116
117    /// The credential does not have the Backup Eligibility (BE) flag set, but
118    /// this relying party has `require_backup_eligible` enabled.
119    ///
120    /// Use this policy for consumer passkey deployments that depend on credential
121    /// sync (e.g. cross-device sign-in via iCloud Keychain or Google Password Manager).
122    #[error("Credential is not backup-eligible but this relying party requires backup-eligible credentials")]
123    BackupEligibilityRequired,
124
125    /// The Backup Eligibility (BE) flag in the authenticator data differs from
126    /// the value recorded at registration time.
127    ///
128    /// BE is immutable per spec — a mismatch indicates a possible credential
129    /// substitution attack (a different authenticator presenting the same credential ID).
130    #[error(
131        "Backup Eligibility flag changed since registration — credential may have been substituted"
132    )]
133    BackupEligibilityChanged,
134
135    /// The `x5c` certificate chain in an attestation statement is structurally
136    /// invalid: a certificate in the chain is not signed by the next certificate,
137    /// or the DER encoding is malformed.
138    ///
139    /// The inner string identifies which link in the chain failed and why.
140    #[error("Attestation certificate chain invalid: {0}")]
141    AttestationChainInvalid(String),
142
143    /// The `x5c` chain is structurally valid but its root certificate is not
144    /// signed by any of the configured trust anchors.
145    ///
146    /// This error is only returned when the relying party has configured at least
147    /// one trust anchor via [`crate::RelyingParty::trust_anchors`]. When no
148    /// trust anchors are configured the chain structure is still verified but
149    /// the root is accepted unconditionally.
150    #[error("Attestation root certificate is not trusted by any configured trust anchor")]
151    AttestationRootUntrusted,
152}
153
154/// Convenience alias so callers write `webauthn::Result<T>`.
155pub type Result<T> = std::result::Result<T, WebAuthnError>;