Skip to main content

key_vault/
error.rs

1//! Error type for the vault.
2//!
3//! Every fallible operation on a [`KeyVault`](crate::KeyVault) and on the pluggable
4//! trait implementations returns [`Result<T>`], which is shorthand for
5//! [`core::result::Result<T, Error>`].
6//!
7//! The variants intentionally carry only sanitized information. **Raw key bytes,
8//! cryptographic material, decoy contents, and fragment layouts MUST NEVER appear
9//! inside an [`Error`].** The error path is one of the data egress routes that an
10//! attacker can observe (logs, panics, traces, alert webhooks), so it is held to
11//! the same redaction discipline as [`KeyHandle`](crate::KeyHandle)'s `Debug`
12//! impl.
13//!
14//! [`Error`] is `#[non_exhaustive]` — new variants may be added in minor releases
15//! and consumers must include a wildcard arm when matching.
16
17use alloc::borrow::Cow;
18use alloc::string::String;
19use core::fmt;
20
21/// Convenient shorthand for results returned by the vault and its trait
22/// implementations.
23pub type Result<T> = core::result::Result<T, Error>;
24
25/// A redaction-safe error type covering every failure mode the vault can
26/// surface.
27///
28/// Variants are coarse on purpose: callers branch on category (acquisition vs.
29/// storage vs. policy) and use the embedded message for diagnostics. Variants
30/// must remain free of key material — see the module-level documentation.
31#[non_exhaustive]
32#[derive(Debug)]
33pub enum Error {
34    /// A [`KeyFetch`](crate::KeyFetch) implementation failed to obtain key
35    /// material from its source.
36    ///
37    /// The `source` is a short identifier for the fetcher (for example
38    /// `"keychain"`, `"file"`, `"env"`). The `reason` is a redacted, human-readable
39    /// explanation that **must not** include the key bytes, the credential, or any
40    /// secret-equivalent value.
41    Acquisition {
42        /// Short identifier for the fetcher that produced the failure.
43        source: Cow<'static, str>,
44        /// Sanitized explanation. Never key material.
45        reason: String,
46    },
47
48    /// A lookup failed because no key matching the requested identifier is
49    /// registered in the vault.
50    KeyNotFound,
51
52    /// Fragmenting the raw key into the configured storage layout failed.
53    ///
54    /// Reasons include: invalid configuration (for example `frag_max < frag_min`),
55    /// or an internal invariant being violated by a custom
56    /// [`FragmentStrategy`](crate::FragmentStrategy).
57    Fragment(String),
58
59    /// Reassembling fragments back into a usable key failed.
60    ///
61    /// This is almost always an internal error: the fragmenter and defragmenter
62    /// disagreed on the layout, or storage was corrupted.
63    Defragment(String),
64
65    /// A decoy strategy could not produce filler bytes for the configured
66    /// output length.
67    Decoy(String),
68
69    /// A [`Codex`](crate::Codex) failed to encode or decode a byte. Custom codex
70    /// implementations may return this if they want to refuse specific inputs;
71    /// the built-in codices never do.
72    Codex(String),
73
74    /// The vault is locked out: a [`SecurityMonitor`](crate::SecurityMonitor)
75    /// threshold has been crossed and access is denied until the configured
76    /// recovery condition is met.
77    LockedOut,
78
79    /// Acquiring or releasing OS-level memory page locks (mlock / VirtualLock)
80    /// failed. This typically means the process has hit its memlock rlimit and
81    /// the operator must raise it.
82    MemoryLock(String),
83
84    /// The configuration passed to the [`KeyVaultBuilder`](crate::KeyVaultBuilder)
85    /// is internally inconsistent.
86    InvalidConfig(String),
87
88    /// An internal invariant was violated. Indicates a bug in `key-vault` itself;
89    /// please file an issue.
90    Internal(&'static str),
91}
92
93impl fmt::Display for Error {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            Self::Acquisition { source, reason } => {
97                write!(f, "key acquisition from {source} failed: {reason}")
98            }
99            Self::KeyNotFound => f.write_str("no key registered under the requested identifier"),
100            Self::Fragment(reason) => write!(f, "fragmentation failed: {reason}"),
101            Self::Defragment(reason) => write!(f, "defragmentation failed: {reason}"),
102            Self::Decoy(reason) => write!(f, "decoy generation failed: {reason}"),
103            Self::Codex(reason) => write!(f, "codex transformation failed: {reason}"),
104            Self::LockedOut => f.write_str("vault is locked out by security monitor threshold"),
105            Self::MemoryLock(reason) => write!(f, "memory lock operation failed: {reason}"),
106            Self::InvalidConfig(reason) => write!(f, "invalid vault configuration: {reason}"),
107            Self::Internal(reason) => write!(f, "internal vault invariant violated: {reason}"),
108        }
109    }
110}
111
112#[cfg(feature = "std")]
113impl std::error::Error for Error {}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use alloc::format;
119    use alloc::string::ToString;
120
121    #[test]
122    fn display_uses_sanitized_template() {
123        let e = Error::Acquisition {
124            source: Cow::Borrowed("keychain"),
125            reason: "user denied access".to_string(),
126        };
127        let rendered = format!("{e}");
128        assert!(rendered.contains("keychain"));
129        assert!(rendered.contains("user denied access"));
130    }
131
132    #[test]
133    fn key_not_found_has_stable_message() {
134        let rendered = format!("{}", Error::KeyNotFound);
135        assert!(rendered.contains("no key"));
136    }
137
138    #[test]
139    fn debug_does_not_panic_for_any_variant() {
140        for e in [
141            Error::KeyNotFound,
142            Error::Fragment("x".to_string()),
143            Error::Defragment("x".to_string()),
144            Error::Decoy("x".to_string()),
145            Error::Codex("x".to_string()),
146            Error::LockedOut,
147            Error::MemoryLock("x".to_string()),
148            Error::InvalidConfig("x".to_string()),
149            Error::Internal("x"),
150        ] {
151            let _ = format!("{e:?}");
152        }
153    }
154}