Skip to main content

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}