Skip to main content

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}