key_vault/vault/mod.rs
1//! The vault itself.
2//!
3//! In this phase [`KeyVault`] is a skeleton: it holds a configuration and
4//! exposes the builder API, but it does not yet store keys — fragmentation,
5//! mlock, and zeroize land in Phase 0.3. The shape of the public API is
6//! finalized here so that downstream crates can begin compiling against it.
7//!
8//! ```
9//! use key_vault::{KeyVault, KeyVaultBuilder};
10//!
11//! // The builder follows the standard fluent pattern. None of the methods
12//! // perform I/O — construction is cheap and infallible.
13//! let _vault: KeyVault = KeyVaultBuilder::new().build();
14//! ```
15
16use alloc::sync::Arc;
17use core::sync::atomic::AtomicBool;
18use core::sync::atomic::Ordering;
19
20/// Vault configuration.
21///
22/// Concrete fields are added in later phases as each layer comes online —
23/// fragment strategy in 0.3/0.5, decoy in 0.4, codex in 0.6, monitor in 0.8.
24/// Today this struct exists so that `KeyVaultBuilder::build` has somewhere to
25/// store its decisions.
26#[derive(Debug, Default, Clone)]
27#[non_exhaustive]
28pub struct VaultConfig {
29 /// If `true`, raw key material is BLAKE3-normalized to 32 bytes before
30 /// fragmentation. Default is `true`.
31 pub key_normalization: bool,
32}
33
34impl VaultConfig {
35 /// Default-on configuration.
36 #[must_use]
37 pub fn new() -> Self {
38 Self {
39 key_normalization: true,
40 }
41 }
42}
43
44/// In-memory key vault.
45///
46/// The vault is the entry point for everything `key-vault` does. Application
47/// code constructs one via [`KeyVaultBuilder`], registers keys against it, and
48/// receives [`KeyHandle`](crate::KeyHandle)s in return. The vault itself is
49/// cheap to clone (it is `Arc`-backed internally) and safe to share across
50/// threads.
51///
52/// Key registration, access, rotation, and recovery are introduced in later
53/// phases. This skeleton exposes only the public shape — construction,
54/// cloning, and the lock-out indicator — so that downstream crates can wire
55/// against it now.
56#[derive(Clone)]
57pub struct KeyVault {
58 inner: Arc<VaultInner>,
59}
60
61struct VaultInner {
62 config: VaultConfig,
63 /// Set to `true` when a [`SecurityMonitor`](crate::SecurityMonitor)
64 /// threshold breach has put the vault into lock-out state. Lock-out is
65 /// not yet driven by the monitor — that arrives in Phase 0.8.
66 locked_out: AtomicBool,
67}
68
69impl KeyVault {
70 /// Returns `true` if the vault is in lock-out state.
71 ///
72 /// Lock-out is the [`SecurityMonitor`](crate::SecurityMonitor)'s response
73 /// to repeated failures: once the threshold is crossed, access to every
74 /// key in the vault is denied until the configured recovery condition is
75 /// met. In Phase 0.2 the lock-out flag exists but is never set; Phase 0.8
76 /// connects it to monitor events.
77 #[must_use]
78 pub fn is_locked_out(&self) -> bool {
79 self.inner.locked_out.load(Ordering::Acquire)
80 }
81
82 /// Snapshot of the vault's configuration.
83 #[must_use]
84 pub fn config(&self) -> &VaultConfig {
85 &self.inner.config
86 }
87}
88
89impl core::fmt::Debug for KeyVault {
90 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
91 f.debug_struct("KeyVault")
92 .field("locked_out", &self.is_locked_out())
93 .field("config", &self.inner.config)
94 .finish()
95 }
96}
97
98/// Fluent builder for [`KeyVault`].
99///
100/// The builder is the only way to construct a vault; the inherent
101/// `KeyVault::new` constructor is intentionally not provided so that future
102/// required configuration cannot be silently bypassed.
103#[derive(Debug, Default, Clone)]
104pub struct KeyVaultBuilder {
105 config: VaultConfig,
106}
107
108impl KeyVaultBuilder {
109 /// Start a new builder with default configuration.
110 #[must_use]
111 pub fn new() -> Self {
112 Self {
113 config: VaultConfig::new(),
114 }
115 }
116
117 /// Enable or disable BLAKE3 normalization of input key material.
118 ///
119 /// Default: `true`. Disabling normalization preserves the original byte
120 /// pattern of the key in storage, which can leak format cues (DER
121 /// envelopes, PEM markers, ASCII-armored data). Disable only when you
122 /// have a specific reason to preserve the original bytes.
123 #[must_use]
124 pub fn normalize_with_blake3(mut self, enabled: bool) -> Self {
125 self.config.key_normalization = enabled;
126 self
127 }
128
129 /// Finalize and produce a [`KeyVault`].
130 ///
131 /// Infallible in this phase — later phases may move this to a
132 /// `Result`-returning shape if validation is added.
133 #[must_use]
134 pub fn build(self) -> KeyVault {
135 KeyVault {
136 inner: Arc::new(VaultInner {
137 config: self.config,
138 locked_out: AtomicBool::new(false),
139 }),
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use alloc::format;
148
149 #[test]
150 fn builder_defaults_to_normalization_on() {
151 let v = KeyVaultBuilder::new().build();
152 assert!(v.config().key_normalization);
153 }
154
155 #[test]
156 fn builder_can_disable_normalization() {
157 let v = KeyVaultBuilder::new().normalize_with_blake3(false).build();
158 assert!(!v.config().key_normalization);
159 }
160
161 #[test]
162 fn fresh_vault_is_not_locked_out() {
163 let v = KeyVaultBuilder::new().build();
164 assert!(!v.is_locked_out());
165 }
166
167 #[test]
168 fn debug_does_not_panic() {
169 let v = KeyVaultBuilder::new().build();
170 let _ = format!("{v:?}");
171 }
172}