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