oxicrypto_core/error.rs
1/// Unified error type for all OxiCrypto operations.
2#[derive(Debug, Clone, PartialEq, Eq)]
3#[cfg_attr(feature = "serde", derive(serde::Serialize))]
4pub enum CryptoError {
5 /// Supplied key has wrong length or is otherwise invalid.
6 InvalidKey,
7 /// Supplied nonce/IV has wrong length or is otherwise invalid.
8 InvalidNonce,
9 /// Authentication tag verification failed (AEAD open / MAC verify).
10 InvalidTag,
11 /// Output buffer is too small for the requested operation.
12 BufferTooSmall,
13 /// General bad-input condition (e.g. zero-length KDF output requested).
14 BadInput,
15 /// An internal or backend error with a static message.
16 ///
17 /// **Serde note:** When deserializing, the `&'static str` payload cannot
18 /// be reconstructed from arbitrary data in a `no_std + alloc` crate.
19 /// The variant is deserialized as `Internal("")` — the serialized form
20 /// preserves the string for observability/logging; round-trip lossiness
21 /// is intentional.
22 Internal(&'static str),
23 /// Key-exchange or encapsulation/decapsulation failure (e.g. ML-KEM).
24 Kex,
25 /// Signature generation or verification failure (e.g. ML-DSA).
26 Sign,
27 /// RNG-specific failure (e.g. `getrandom` unavailable).
28 Rng,
29 /// Encoding / decoding failure (DER, PEM, SEC1, etc.).
30 Encoding,
31 /// Requested algorithm is not compiled-in or not supported at runtime.
32 UnsupportedAlgorithm,
33}
34
35// ---------------------------------------------------------------------------
36// Manual `Deserialize` implementation
37// ---------------------------------------------------------------------------
38//
39// Derived `Deserialize` would bind lifetime `'de` to the `&'static str` in
40// `Internal`, producing `impl Deserialize<'static>` only — unusable with
41// most codecs that operate on shorter-lived slices.
42//
43// Instead we manually implement `Deserialize` as `DeserializeOwned`-compatible:
44// the `Internal` variant reads the payload as an owned `String` (then drops it)
45// and always reconstructs `Internal("")`. All other variants contain no
46// borrowed data and round-trip exactly.
47
48#[cfg(feature = "serde")]
49impl<'de> serde::Deserialize<'de> for CryptoError {
50 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
51 where
52 D: serde::Deserializer<'de>,
53 {
54 // Mirror the variant names used by the derived `Serialize`.
55 #[derive(serde::Deserialize)]
56 #[serde(rename = "CryptoError")]
57 enum Repr {
58 InvalidKey,
59 InvalidNonce,
60 InvalidTag,
61 BufferTooSmall,
62 BadInput,
63 /// Payload deserialized as owned String, discarded afterward.
64 Internal(#[allow(dead_code)] alloc::string::String),
65 Kex,
66 Sign,
67 Rng,
68 Encoding,
69 UnsupportedAlgorithm,
70 }
71
72 let repr = Repr::deserialize(deserializer)?;
73 Ok(match repr {
74 Repr::InvalidKey => CryptoError::InvalidKey,
75 Repr::InvalidNonce => CryptoError::InvalidNonce,
76 Repr::InvalidTag => CryptoError::InvalidTag,
77 Repr::BufferTooSmall => CryptoError::BufferTooSmall,
78 Repr::BadInput => CryptoError::BadInput,
79 // String payload is intentionally discarded; &'static str
80 // cannot be reconstructed from deserialized data without unsafe.
81 Repr::Internal(_) => CryptoError::Internal(""),
82 Repr::Kex => CryptoError::Kex,
83 Repr::Sign => CryptoError::Sign,
84 Repr::Rng => CryptoError::Rng,
85 Repr::Encoding => CryptoError::Encoding,
86 Repr::UnsupportedAlgorithm => CryptoError::UnsupportedAlgorithm,
87 })
88 }
89}
90
91impl core::fmt::Display for CryptoError {
92 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
93 match self {
94 CryptoError::InvalidKey => write!(f, "invalid key"),
95 CryptoError::InvalidNonce => write!(f, "invalid nonce"),
96 CryptoError::InvalidTag => write!(f, "invalid authentication tag"),
97 CryptoError::BufferTooSmall => write!(f, "output buffer too small"),
98 CryptoError::BadInput => write!(f, "bad input"),
99 CryptoError::Internal(msg) => write!(f, "internal error: {msg}"),
100 CryptoError::Kex => write!(f, "key exchange or encapsulation failure"),
101 CryptoError::Sign => write!(f, "signature generation or verification failure"),
102 CryptoError::Rng => write!(f, "random number generator failure"),
103 CryptoError::Encoding => write!(f, "encoding or decoding failure"),
104 CryptoError::UnsupportedAlgorithm => write!(f, "unsupported algorithm"),
105 }
106 }
107}
108
109// `core::error::Error` is stable since Rust 1.81; implement it unconditionally
110// so that `CryptoError` satisfies bounds like `rand_core::TryRng::Error`
111// which require `core::error::Error` regardless of the `std` feature.
112// Note: `std::error::Error` re-exports `core::error::Error` in Rust 1.81+,
113// so we only implement it once here rather than separately for each gate.
114impl core::error::Error for CryptoError {}
115
116#[cfg(feature = "std")]
117extern crate std;
118
119#[cfg(feature = "std")]
120impl From<CryptoError> for std::io::Error {
121 fn from(e: CryptoError) -> Self {
122 std::io::Error::other(alloc::format!("{e}"))
123 }
124}