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>;