claim169_core/error.rs
1//! Error types for Claim 169 decoding and cryptographic operations.
2//!
3//! This module provides two main error types:
4//!
5//! - [`Claim169Error`]: High-level errors from the decoding pipeline
6//! - [`CryptoError`]: Lower-level cryptographic operation errors
7//!
8//! # Error Handling
9//!
10//! All public functions return [`Result<T>`] which uses [`Claim169Error`]:
11//!
12//! ```rust,ignore
13//! use claim169_core::{Decoder, Claim169Error};
14//!
15//! match Decoder::new(qr_content).allow_unverified().decode() {
16//! Ok(result) => println!("Decoded: {:?}", result.claim169.full_name),
17//! Err(Claim169Error::Expired(ts)) => println!("Credential expired at {}", ts),
18//! Err(Claim169Error::SignatureInvalid(msg)) => println!("Bad signature: {}", msg),
19//! Err(e) => println!("Error: {}", e),
20//! }
21//! ```
22//!
23//! # Error Categories
24//!
25//! | Error Type | Description |
26//! |------------|-------------|
27//! | `Base45Decode` | Invalid Base45 encoding in QR string |
28//! | `Decompress*` | Decompression failure or limit exceeded |
29//! | `CoseParse`, `CborParse` | Invalid binary structure |
30//! | `SignatureInvalid` | Signature verification failed |
31//! | `DecryptionFailed` | Decryption failed |
32//! | `Expired`, `NotYetValid` | Timestamp validation failed |
33//! | `Claim169NotFound` | Required claim not in payload |
34
35use thiserror::Error;
36
37/// Errors that can occur during Claim 169 QR decoding.
38///
39/// This enum covers all errors in the decoding pipeline, from Base45 decoding
40/// through CBOR parsing to signature verification and timestamp validation.
41///
42/// # Converting from CryptoError
43///
44/// [`CryptoError`] automatically converts to `Claim169Error` via the `From` trait:
45///
46/// ```rust,ignore
47/// let crypto_err = CryptoError::VerificationFailed;
48/// let claim_err: Claim169Error = crypto_err.into();
49/// ```
50#[derive(Debug, Error)]
51pub enum Claim169Error {
52 /// Invalid Base45 encoding in QR string
53 #[error("invalid Base45 encoding: {0}")]
54 Base45Decode(String),
55
56 /// Failed to decompress zlib data
57 #[error("decompression failed: {0}")]
58 Decompress(String),
59
60 /// Decompressed data exceeds safety limit
61 #[error("decompression limit exceeded: max {max_bytes} bytes")]
62 DecompressLimitExceeded { max_bytes: usize },
63
64 /// Invalid COSE structure
65 #[error("invalid COSE structure: {0}")]
66 CoseParse(String),
67
68 /// Unsupported COSE message type
69 #[error("unsupported COSE type: expected Sign1, Sign, Encrypt0, or Encrypt, got {0}")]
70 UnsupportedCoseType(String),
71
72 /// COSE signature verification failed
73 #[error("signature verification failed: {0}")]
74 SignatureInvalid(String),
75
76 /// COSE decryption failed
77 #[error("decryption failed: {0}")]
78 DecryptionFailed(String),
79
80 /// Invalid CBOR structure
81 #[error("invalid CBOR: {0}")]
82 CborParse(String),
83
84 /// CWT parsing failed
85 #[error("CWT parsing failed: {0}")]
86 CwtParse(String),
87
88 /// Claim 169 not found in CWT payload
89 #[error("claim 169 not found in CWT payload")]
90 Claim169NotFound,
91
92 /// Invalid Claim 169 structure
93 #[error("invalid claim 169 structure: {0}")]
94 Claim169Invalid(String),
95
96 /// Unsupported cryptographic algorithm
97 #[error("unsupported algorithm: {0}")]
98 UnsupportedAlgorithm(String),
99
100 /// Key not found for the given key ID
101 #[error("key not found for kid: {0:?}")]
102 KeyNotFound(Option<Vec<u8>>),
103
104 /// Credential has expired
105 #[error("credential expired at timestamp {0}")]
106 Expired(i64),
107
108 /// Credential is not yet valid
109 #[error("credential not valid until timestamp {0}")]
110 NotYetValid(i64),
111
112 /// Crypto operation error
113 #[error("crypto error: {0}")]
114 Crypto(String),
115
116 /// I/O error
117 #[error("I/O error: {0}")]
118 Io(#[from] std::io::Error),
119
120 // ========== Encoding errors ==========
121 /// CBOR encoding failed
122 #[error("CBOR encoding failed: {0}")]
123 CborEncode(String),
124
125 /// Signing failed
126 #[error("signing failed: {0}")]
127 SignatureFailed(String),
128
129 /// Encryption failed
130 #[error("encryption failed: {0}")]
131 EncryptionFailed(String),
132
133 /// Encoding configuration error
134 #[error("encoding configuration error: {0}")]
135 EncodingConfig(String),
136
137 /// Decoding configuration error
138 #[error("decoding configuration error: {0}")]
139 DecodingConfig(String),
140}
141
142/// Errors specific to cryptographic operations.
143///
144/// These errors occur within [`SignatureVerifier`](crate::SignatureVerifier),
145/// [`Decryptor`](crate::Decryptor), and related crypto implementations.
146///
147/// `CryptoError` automatically converts to [`Claim169Error`] when returned
148/// from the main decoding functions.
149///
150/// # Implementing Custom Crypto
151///
152/// When implementing custom cryptographic backends, return appropriate
153/// `CryptoError` variants:
154///
155/// ```rust,ignore
156/// impl SignatureVerifier for MyHsmVerifier {
157/// fn verify(&self, algorithm: Algorithm, key_id: Option<&[u8]>,
158/// data: &[u8], signature: &[u8]) -> CryptoResult<()> {
159/// if !self.has_key(key_id) {
160/// return Err(CryptoError::KeyNotFound);
161/// }
162/// if !self.supports_algorithm(algorithm) {
163/// return Err(CryptoError::UnsupportedAlgorithm(format!("{:?}", algorithm)));
164/// }
165/// // ... perform verification
166/// if !valid {
167/// return Err(CryptoError::VerificationFailed);
168/// }
169/// Ok(())
170/// }
171/// }
172/// ```
173#[derive(Debug, Error)]
174pub enum CryptoError {
175 /// Invalid key format
176 #[error("invalid key format: {0}")]
177 InvalidKeyFormat(String),
178
179 /// Key not found
180 #[error("key not found")]
181 KeyNotFound,
182
183 /// Signature verification failed
184 #[error("signature verification failed")]
185 VerificationFailed,
186
187 /// Decryption failed
188 #[error("decryption failed: {0}")]
189 DecryptionFailed(String),
190
191 /// Signing failed (for custom signer callbacks)
192 #[error("signing failed: {0}")]
193 SigningFailed(String),
194
195 /// Encryption failed (for custom encryptor callbacks)
196 #[error("encryption failed: {0}")]
197 EncryptionFailed(String),
198
199 /// Unsupported algorithm
200 #[error("unsupported algorithm: {0}")]
201 UnsupportedAlgorithm(String),
202
203 /// Generic crypto error
204 #[error("{0}")]
205 Other(String),
206}
207
208impl From<CryptoError> for Claim169Error {
209 fn from(err: CryptoError) -> Self {
210 match err {
211 CryptoError::VerificationFailed => {
212 Claim169Error::SignatureInvalid("verification failed".to_string())
213 }
214 CryptoError::DecryptionFailed(msg) => Claim169Error::DecryptionFailed(msg),
215 CryptoError::SigningFailed(msg) => Claim169Error::SignatureFailed(msg),
216 CryptoError::EncryptionFailed(msg) => Claim169Error::EncryptionFailed(msg),
217 CryptoError::UnsupportedAlgorithm(alg) => Claim169Error::UnsupportedAlgorithm(alg),
218 CryptoError::KeyNotFound => Claim169Error::KeyNotFound(None),
219 other => Claim169Error::Crypto(other.to_string()),
220 }
221 }
222}
223
224/// Result type for Claim 169 operations
225pub type Result<T> = std::result::Result<T, Claim169Error>;
226
227/// Result type for crypto operations
228pub type CryptoResult<T> = std::result::Result<T, CryptoError>;
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_error_display() {
236 let err = Claim169Error::Base45Decode("invalid character at position 5".to_string());
237 assert!(err.to_string().contains("Base45"));
238 assert!(err.to_string().contains("position 5"));
239 }
240
241 #[test]
242 fn test_crypto_error_conversion() {
243 let crypto_err = CryptoError::VerificationFailed;
244 let claim_err: Claim169Error = crypto_err.into();
245
246 assert!(matches!(claim_err, Claim169Error::SignatureInvalid(_)));
247 }
248
249 #[test]
250 fn test_decompression_limit_error() {
251 let err = Claim169Error::DecompressLimitExceeded { max_bytes: 65536 };
252 assert!(err.to_string().contains("65536"));
253 }
254}