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}