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}