<h1 align="center">
<img width="99" alt="Rust logo" src="https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg"><br>
<b>key-vault</b>
<br><sub><sup>API REFERENCE</sup></sub>
</h1>
<p align="center">
<b><a href="#installation">Installation</a></b>
·
<b><a href="#quick-start">Quick Start</a></b>
·
<b><a href="#public-apis">Public APIs</a></b>
·
<b><a href="#examples">Examples</a></b>
·
<b><a href="#api-safety">API Safety</a></b>
·
<b><a href="#notes">Notes</a></b>
</p>
<p align="center">
<i>Complete public-API reference for <code>key-vault</code> 1.0.0.</i>
<br>
<i>For the 9-layer architecture see <a href="SECURITY.md">SECURITY.md</a>.
For a per-version change log see <a href="../CHANGELOG.md">CHANGELOG.md</a>.</i>
</p>
<hr>
## Installation
### Default installation
Add to `Cargo.toml`:
```toml
[dependencies]
key-vault = "0.9"
```
### Install via terminal
```bash
cargo add key-vault
```
### Minimum supported Rust version
**Rust 1.85** (edition 2024). Older toolchains will not build.
### Cargo features
| `std` | ✅ | Standard-library types. Required by the current implementation. |
| `mlock` | ✅ | `mlock` / `VirtualLock` page locking on `LockedBytes`. |
| `zeroize` | ✅ | `zeroize` integration; zero-on-drop on every key buffer. |
| `fragment-standard` | ✅ | `StandardFragmenter` (the default). |
| `decoy-self-ref` | ✅ | `SelfReferenceDecoy` (the recommended default). |
| `fetcher-keychain` | | `KeychainFetch` via the `keyring` crate (0.7.0). |
| `codex` | | Marker for the codex layer (currently informational; codex types are always available). |
| `monitor`, `audit` | | Layer 8 / 9 integration (0.8.0). |
| `tee-detect` | | TEE-capability detection (always available; this flag is informational). |
| `preset-balanced` | | `std` + `mlock` + `zeroize` + `fragment-standard` + `decoy-self-ref`. |
| `preset-paranoid` | | All defaults + every fragmenter + all decoys + codex + monitor + audit + TEE detect. |
| `preset-fast` | | `std` + `fragment-standard` + `decoy-random` (no `mlock`, no zeroize). |
<a href="#top">↑ TOP</a>
<hr>
## Error handling and panic guarantees
- Every fallible operation returns [`Result<T>`](#resultt) — an alias for
`core::result::Result<T, Error>`.
- [`Error`](#error) is `#[non_exhaustive]`; new variants are added in
minor releases. Match wildcards (`_ => ...`) are required.
- **No `unwrap` / `expect` / `panic!`** in the public API. The crate is
REPS-compliant; every panic in library code is a bug.
- **No raw key bytes in any `Error` variant.** Failure messages are
redaction-clean — safe to log, safe to include in audit records, safe
to ship to monitoring sinks.
- **Debug** impls for every key-adjacent type print `<redacted>` for key
material. The `KeyHandle::Debug` impl prints exactly `KeyHandle(<redacted>)`
regardless of the underlying id.
<a href="#top">↑ TOP</a>
<hr>
## Quick Start
The minimal, on-by-default stack: BLAKE3 normalization + `StandardFragmenter`
+ `SelfReferenceDecoy` + `DynamicCodex` + mlock + zero-on-drop +
constant-time handle equality.
```rust
use key_vault::{DynamicCodex, KeyVaultBuilder, RawKey, SelfReferenceDecoy};
use key_vault::tee::detect_tee_capabilities;
# fn main() -> Result<(), key_vault::Error> {
// Build a vault wiring up Layers 2 (mlock), 3 (StandardFragmenter),
// 4 (SelfReferenceDecoy), 5 (DynamicCodex), 6 (ConstantTimeEq), 7 (zero-on-drop).
let vault = KeyVaultBuilder::new()
.normalize_with_blake3(true) // default
.with_codex(DynamicCodex::new()?) // Layer 5
.with_decoy(SelfReferenceDecoy) // Layer 4
.build();
// Fragment a key. Returns an opaque `Fragments` token.
let raw = RawKey::new(b"my application key".to_vec());
let fragments = vault.fragment(&raw)?;
// Defragment when you need the bytes back. With normalization on, the
// recovered material is the 32-byte BLAKE3 hash of the original input.
let recovered = vault.defragment(&fragments)?;
assert_eq!(recovered.len(), 32);
// Optionally check the host's TEE capabilities at startup.
let caps = detect_tee_capabilities();
println!("{caps}");
# Ok(())
# }
```
<a href="#top">↑ TOP</a>
<hr>
## Public APIs
### `VERSION`
```rust
pub const VERSION: &str;
```
Source: `src/lib.rs`
Crate version string populated by Cargo at build time. Equal to
`env!("CARGO_PKG_VERSION")`.
**Example:**
```rust
assert!(key_vault::VERSION.starts_with("0."));
```
<hr>
### `Error`
Source: `src/error.rs`
The crate-wide error type. `#[non_exhaustive]`; future variants additive.
**No variant carries raw key material** — error messages are redaction-clean.
**Variants:**
| `Acquisition { source, reason }` | A `KeyFetch` impl failed; `source` names the fetcher, `reason` is sanitized prose. |
| `KeyNotFound` | Requested key id is not registered with the vault. |
| `Fragment(String)` | Fragmentation failed (configuration error or input outside supported bounds). |
| `Defragment(String)` | Reassembly failed (layout/chunk mismatch, corruption). |
| `Decoy(String)` | A `DecoyStrategy` cannot produce the requested output. |
| `Codex(String)` | A `Codex` rejected an input (e.g. conflicting swap pairs in `StaticCodex::from_swaps`). |
| `LockedOut` | A `SecurityMonitor` threshold lockout is in effect. |
| `MemoryLock(String)` | mlock/VirtualLock operation failed at the OS layer. |
| `InvalidConfig(String)` | Builder produced an internally inconsistent configuration. |
| `Internal(&'static str)` | Crate invariant violated; please file an issue. |
**Example:**
```rust
use key_vault::{Error, KeyVaultBuilder, RawKey};
# fn main() -> Result<(), Error> {
let vault = KeyVaultBuilder::new().normalize_with_blake3(false).build();
let err = vault.fragment(&RawKey::new(Vec::new())).unwrap_err();
match err {
Error::Fragment(reason) => assert!(reason.contains("empty")),
other => panic!("expected Fragment, got {other:?}"),
}
# Ok(())
# }
```
<hr>
### `Result<T>`
```rust
pub type Result<T> = core::result::Result<T, Error>;
```
Source: `src/error.rs`
Shorthand for fallible vault operations.
<hr>
### `KeyHandle`
Source: `src/handle.rs`
Opaque, redacted reference to a registered key. `Copy + Clone + Eq + Hash`.
**Key properties:**
- `Debug` always prints `KeyHandle(<redacted>)` — never the underlying id.
- `PartialEq` routes through [`subtle::ConstantTimeEq`] so equality is
constant-time.
- `Hash` is implemented manually to remain consistent with `PartialEq`
(equal handles always hash equal).
- The inner numeric id is `pub(crate)` only — outside callers cannot
read it.
**Public methods:**
- `KeyHandle::__for_test() -> Self` — placeholder constructor for
doctests and external tests. Not part of the supported API; do not
use in production.
**Example:**
```rust
use key_vault::KeyHandle;
let h = KeyHandle::__for_test();
let rendered = format!("{h:?}");
assert_eq!(rendered, "KeyHandle(<redacted>)");
```
<hr>
### `KeyId`
Source: `src/handle.rs`
Process-wide handle identifier (`NonZeroU64` newtype). Public surface
exposes the type itself only; the inner value is crate-private. Use it
where APIs need a `KeyId` parameter; never depend on its numeric value.
`Debug` prints `KeyId(<redacted>)`.
<hr>
### `KeyMetadata`
Source: `src/metadata.rs`
Public, non-secret information about a registered key. Safe to log.
**Read accessors:**
- `length(&self) -> usize` — raw key length, in bytes.
- `algorithm(&self) -> Option<AlgorithmHint>` — optional algorithm hint.
- `registered_since_epoch(&self) -> Duration` — registration time as a
`Duration` since `UNIX_EPOCH`.
The constructor (`new`) is crate-internal; vault registration produces
metadata in 0.9+.
**Example:**
```rust
// `KeyMetadata` is produced by the vault. Use the accessors to inspect
// non-secret properties (length, algorithm hint, registration time).
fn report(metadata: &key_vault::KeyMetadata) -> String {
format!(
"key is {} bytes, algorithm = {:?}",
metadata.length(),
metadata.algorithm()
)
}
```
<hr>
### `AlgorithmHint`
Source: `src/metadata.rs`
```rust
#[non_exhaustive]
pub enum AlgorithmHint {
Symmetric128, Symmetric256,
Ed25519, X25519, P256, P384,
Rsa2048, Rsa3072, Rsa4096,
Hmac, Other,
}
```
Advisory tag attached to `KeyMetadata`. The vault does not verify that
the registered bytes match the named algorithm — the variant exists so
audit trails and monitors can label events meaningfully.
<hr>
### `RawKey`
Source: `src/fetcher/mod.rs`
Container for raw key material exchanged between `KeyFetch` impls, the
vault, and the fragmenter. **`RawKey` exposes no method that returns
`&[u8]` to outside callers** — only its `len()` is observable from
outside the crate. `Debug` redacts contents.
**Constructors:**
- `RawKey::new(bytes: Vec<u8>) -> Self` — wrap an existing buffer.
**Read accessors:**
- `len(&self) -> usize` — length in bytes.
- `is_empty(&self) -> bool` — whether the buffer is zero-length.
`Debug` prints `RawKey { len, bytes: "<redacted>" }`.
**`Drop`** (0.9+) volatile-zeroes the internal `Vec<u8>` before the
underlying allocation is freed. `write_volatile` per byte + a `SeqCst`
compiler fence; same pattern as `LockedBytes`. The drop guarantee
applies to every `RawKey`, including the temporary buffers returned
by `KeyVault::defragment` and the ones handed to `with_key` closures.
**Example:**
```rust
use key_vault::RawKey;
let key = RawKey::new(b"my application key".to_vec());
assert_eq!(key.len(), 18);
assert!(!key.is_empty());
let rendered = format!("{key:?}");
assert!(rendered.contains("<redacted>"));
```
<hr>
### `FetchContext`
Source: `src/fetcher/mod.rs`
Information given to a `KeyFetch` impl. `#[non_exhaustive]`.
**Fields:**
- `pub key_name: String` — logical name of the key being requested.
**Constructors:**
- `FetchContext::new(key_name: impl Into<String>) -> Self`
**Example:**
```rust
use key_vault::FetchContext;
let ctx = FetchContext::new("db-primary");
assert_eq!(ctx.key_name, "db-primary");
```
<hr>
### `Fragments`
Source: `src/fragment/mod.rs`
Opaque token returned by `FragmentStrategy::fragment` and consumed by
`FragmentStrategy::defragment`. Holds the chunks and the locked layout
buffer; storage layout is intentionally a black box from the public-API
side.
**Public accessors:**
- `chunk_count(&self) -> usize` — number of chunks (real + decoy if
configured).
`Debug` prints `Fragments { chunks: N, total_len: M, contents: "<opaque>" }`.
**Example:**
```rust
use key_vault::{KeyVaultBuilder, RawKey};
# fn main() -> Result<(), key_vault::Error> {
let vault = KeyVaultBuilder::new().normalize_with_blake3(false).build();
let raw = RawKey::new(vec![0u8; 32]);
let fragments = vault.fragment(&raw)?;
assert!(fragments.chunk_count() >= 4); // 32 / 8 (max chunk size) = 4
# Ok(())
# }
```
<hr>
### `KeyVault`
Source: `src/vault/mod.rs`
The vault itself. `Arc`-backed, `Clone`, `Send + Sync`. Construct via
[`KeyVaultBuilder`](#keyvaultbuilder).
**Named-key registry (0.9+):**
- `register(name: impl Into<String>, key: RawKey) -> Result<KeyHandle>`
— fragment a key and insert it under `name`. Returns the opaque
handle. Errors on duplicate name (`Error::InvalidConfig`) or
when the vault is locked out.
- `unregister(handle: KeyHandle) -> Result<()>` — remove. The
underlying `Fragments` (and its `LockedBytes` chunks) drop and
zeroize when the last `Arc` reference goes away.
- `with_key<F, T>(handle, f: F) -> Result<T>` where
`F: FnOnce(&[u8]) -> T` — scoped byte access. The slice handed to
`f` is valid only during the call; the temporary `RawKey` zeroes
on drop.
- `rotate(handle, new_key: RawKey) -> Result<()>` — atomic swap via
`ArcSwap::rcu`. Concurrent readers see either the old or the new
fragmentation, never a torn read.
- `contains(handle: KeyHandle) -> bool`
- `metadata(handle: KeyHandle) -> Option<KeyMetadata>` — non-secret
per-key metadata.
- `handle_for_name(name: &str) -> Option<KeyHandle>` — lookup.
- `key_count() -> usize` — number of registered keys.
**Master-key emergency unlock (0.9+):**
- `unlock_with_master(attempt: &[u8]) -> Result<()>` — verify the
caller's bytes against the stored BLAKE3 digest in constant time
(via `subtle::ConstantTimeEq`). On match, clears the lockout flag
and failure tracker. On mismatch, reports the failure under the
reserved name `"<master>"` and returns `Error::Acquisition`.
- `has_master_key() -> bool` — whether a master credential was
registered at build time.
**One-shot fragment / defragment:**
- `fragment(&self, key: &RawKey) -> Result<Fragments>` — pipeline:
optional BLAKE3 normalize → optional codex encode → fragmenter.fragment.
Returns `Error::LockedOut` when the vault is in lock-out state.
- `defragment(&self, fragments: &Fragments) -> Result<RawKey>` —
inverse: fragmenter.defragment → optional codex decode. Returns
`Error::LockedOut` when the vault is in lock-out state.
**Lockout + monitor controls:**
- `is_locked_out(&self) -> bool` — `true` if a threshold breach has
put the vault in lock-out state.
- `clear_lockout(&self)` — operator escape hatch. Resets the lockout
flag and clears the failure tracker.
- `report_failure(&self, key_name: &str, note: Option<&'static str>)`
— caller-driven failure reporting. Forwards to the configured
monitor and feeds the threshold detector.
- `report_anomalous_access(&self, key_name: &str, note: Option<&'static str>)`
— caller-driven anomaly reporting (no state change).
- `config(&self) -> &VaultConfig` — snapshot of the configuration.
**Example:**
```rust
use key_vault::{KeyVault, KeyVaultBuilder, RawKey};
# fn main() -> Result<(), key_vault::Error> {
let vault: KeyVault = KeyVaultBuilder::new()
.normalize_with_blake3(false)
.build();
let raw = RawKey::new(b"my application key".to_vec());
let fragments = vault.fragment(&raw)?;
let recovered = vault.defragment(&fragments)?;
assert_eq!(recovered.len(), raw.len());
assert!(!vault.is_locked_out());
# Ok(())
# }
```
<hr>
### `KeyVaultBuilder`
Source: `src/vault/mod.rs`
Fluent builder for [`KeyVault`](#keyvault).
**Constructors:**
- `KeyVaultBuilder::new() -> Self` — default-on configuration:
normalization enabled, default-range `StandardFragmenter`, no decoy,
no codex.
- `KeyVaultBuilder::default()` — same as `new()`.
**Builder methods:**
- `normalize_with_blake3(self, enabled: bool) -> Self` — toggle BLAKE3
input normalization. Default `true`.
- `with_chunk_range(self, min: usize, max: usize) -> Self` — customize
the fragmenter chunk-size range. Replaces any previously-set decoy.
- `with_decoy<D: DecoyStrategy + 'static>(self, decoy: D) -> Self` —
attach a Layer-4 decoy strategy.
- `with_codex<C: Codex + 'static>(self, codex: C) -> Self` — attach a
Layer-5 codex.
- `with_monitor<M: SecurityMonitor + 'static>(self, monitor: M) -> Self`
— attach a Layer-8 monitor.
- `with_failure_threshold(self, max: u32, window: Duration) -> Self` —
configure the threshold detector. `max = 0` disables lockout.
- `with_master_key(self, master: RawKey) -> Self` — register a
master-key credential at build time. Stores the BLAKE3 digest of
`master`; plaintext drops + zeroes immediately. Used by
`KeyVault::unlock_with_master` for emergency unlock.
- `with_audit_sink<A: AuditSink + 'static>(self, sink: A) -> Self`
— attach a Layer-9 audit sink. Every public op emits an
`AuditEvent` through it. Default is `NoAudit`.
- `build(self) -> KeyVault` — finalize. Infallible.
**Example:**
```rust
use key_vault::{DynamicCodex, KeyVaultBuilder, SelfReferenceDecoy};
# fn main() -> Result<(), key_vault::Error> {
let vault = KeyVaultBuilder::new()
.normalize_with_blake3(true)
.with_chunk_range(2, 6)
.with_decoy(SelfReferenceDecoy)
.with_codex(DynamicCodex::new()?)
.build();
# let _ = vault;
# Ok(())
# }
```
<hr>
### `VaultConfig`
Source: `src/vault/mod.rs`
`#[non_exhaustive]` configuration struct exposed by `KeyVault::config()`.
**Fields:**
- `pub key_normalization: bool` — whether BLAKE3 normalization is on.
- `pub max_failures_before_lockout: u32` — threshold count. `0` =
disabled (default).
- `pub failure_window: Duration` — sliding-window size for the
failure counter. Default: 60 seconds.
<hr>
### `KeyFetch` (trait, Layer 1)
Source: `src/fetcher/mod.rs`
Pluggable source of raw key material.
```rust
pub trait KeyFetch: Send + Sync {
fn fetch(&self, ctx: &FetchContext) -> Result<RawKey>;
fn describe(&self) -> Cow<'_, str>;
}
```
**Contract:**
- No retries. A failure to find a key is a configuration error, not a
transient.
- No caching. The vault calls `fetch` exactly once per registration.
- Sanitized errors. Returned `Error::Acquisition.reason` must not include
key material or secret-equivalent values.
Four built-in implementations shipped in 0.7.0:
[`EnvFetch`](#envfetch), [`FileFetch`](#filefetch),
[`KeychainFetch`](#keychainfetch), [`TpmFetch`](#tpmfetch). Each is
feature-gated.
**Example (custom impl):**
```rust
use std::borrow::Cow;
use key_vault::{Error, FetchContext, KeyFetch, RawKey, Result};
struct StaticFetch(Vec<u8>);
impl KeyFetch for StaticFetch {
fn fetch(&self, _ctx: &FetchContext) -> Result<RawKey> {
Ok(RawKey::new(self.0.clone()))
}
fn describe(&self) -> Cow<'_, str> {
Cow::Borrowed("static-test")
}
}
```
<hr>
### `EnvFetch`
Source: `src/fetcher/env.rs` · Feature: `fetcher-env`
Reads key bytes from a named process environment variable. The variable
**name** appears in error messages for diagnostics; the variable
**value** is never logged.
**Constructors:**
- `EnvFetch::new(var_name: impl Into<String>) -> Self`
**Threat profile.** Lowest-security built-in. Anything in the process
environment is readable by other processes with the right privileges
(`/proc/<pid>/environ` on Linux), debuggers, and crash-dump tooling.
Best for development and orchestration-managed deployments (Kubernetes
Secrets → env, systemd `EnvironmentFile=` with restricted permissions).
**Example:**
```rust,no_run
use key_vault::{EnvFetch, FetchContext, KeyFetch};
# fn main() -> Result<(), key_vault::Error> {
let fetcher = EnvFetch::new("MY_APP_KEY");
let raw = fetcher.fetch(&FetchContext::new("primary"))?;
# let _ = raw;
# Ok(())
# }
```
<hr>
### `FileFetch`
Source: `src/fetcher/file.rs` · Feature: `fetcher-file`
Reads key bytes from a file on disk. **On Unix, rejects files with
permissions stricter than `0o600` by default** (any bit in `0o077`
set). Windows trusts NTFS ACLs.
**Constructors:**
- `FileFetch::new(path: impl Into<PathBuf>) -> Self` — strict perms on.
**Builder methods:**
- `allow_loose_perms(self) -> Self` — disable the Unix permission gate
(not recommended outside of tests).
**Read accessors:**
- `path(&self) -> &Path` — the configured path. Used in audit / error
attribution.
**Threat profile.** Higher than `EnvFetch` (POSIX permissions confine
access). Lower than `KeychainFetch` (bytes live on disk in cleartext).
Pair with OS-level disk encryption (LUKS / FileVault / BitLocker) for
encryption-at-rest. AEAD-encrypted file format is on the post-1.0
backlog.
**Example:**
```rust,no_run
use key_vault::{FetchContext, FileFetch, KeyFetch};
# fn main() -> Result<(), key_vault::Error> {
let fetcher = FileFetch::new("/etc/myapp/key.bin");
let raw = fetcher.fetch(&FetchContext::new("primary"))?;
# let _ = raw;
# Ok(())
# }
```
<hr>
### `KeychainFetch`
Source: `src/fetcher/keychain.rs` · Feature: `fetcher-keychain`
Reads from the OS native credential store via the
[`keyring`](https://crates.io/crates/keyring) crate:
- **macOS** — Keychain Services
- **Windows** — Credential Manager
- **Linux** — Secret Service (gnome-keyring, KWallet)
**Constructors:**
- `KeychainFetch::new(service: impl Into<String>, account: impl Into<String>) -> Self`
`service` is the application/namespace name; `account` is the entry
identifier within that service. Both appear in failure messages.
`keyring::Error` variants are mapped to short, discriminant-only
strings — platform-specific error details never appear in `Error`
messages.
**Threat profile.** Highest-security general-purpose backend short of
dedicated hardware. The OS confines access to the user account (and on
macOS to the signing identity).
**Example:**
```rust,no_run
use key_vault::{FetchContext, KeyFetch, KeychainFetch};
# fn main() -> Result<(), key_vault::Error> {
let fetcher = KeychainFetch::new("my-app", "primary-key");
let raw = fetcher.fetch(&FetchContext::new("primary"))?;
# let _ = raw;
# Ok(())
# }
```
<hr>
### `TpmFetch`
Source: `src/fetcher/tpm.rs` · Feature: `fetcher-tpm`
TPM 2.0 fetcher — **detection-only in 1.0**. Always returns
`Error::Acquisition` with a documented message. Full integration
(`tss-esapi` wiring, unsealing, attestation) arrives in 1.x.
Use [`tee::detect_tee_capabilities()`](#teedetect_tee_capabilities) to
probe for TPM presence at startup. `TpmFetch` itself can be wired into
composite fetcher chains today so the 1.x upgrade is automatic.
**Constructors:**
- `TpmFetch` is a unit struct — construct directly: `TpmFetch`.
**Example:**
```rust
use key_vault::{FetchContext, KeyFetch, TpmFetch};
let err = TpmFetch.fetch(&FetchContext::new("k")).unwrap_err();
// 1.0 ships detection only; full integration in the 1.x line.
assert!(format!("{err}").contains("TPM"));
```
<hr>
### `FragmentStrategy` (trait, Layer 3)
Source: `src/fragment/mod.rs`
Splits a `RawKey` into a `Fragments` token and reassembles it.
```rust
pub trait FragmentStrategy: Send + Sync {
fn fragment(&self, key: &RawKey) -> Result<Fragments>;
fn defragment(&self, fragments: &Fragments) -> Result<RawKey>;
fn describe(&self) -> Cow<'_, str>;
}
```
**Contract:**
- Round-trip: `defragment(&fragment(&key)?)?` must produce `key`
byte-for-byte.
- Variable layout per call: two consecutive `fragment` calls on the same
input must produce distinct `Fragments` layouts.
- `Send + Sync`.
Four built-in implementations follow.
<hr>
### `StandardFragmenter`
Source: `src/fragment/standard.rs`
Default Layer-3 implementation. Variable-size chunks (default `min=1`,
`max=8`), Fisher-Yates shuffle, each chunk in its own `LockedBytes`
allocation.
**Constructors:**
- `StandardFragmenter::new() -> Self` — default chunk range.
- `StandardFragmenter::with_chunk_range(min: usize, max: usize) -> Self`
— `min` clamped to `>= 1`, `max` clamped to `>= min`.
- `StandardFragmenter::default()` — same as `new`.
**Builder methods:**
- `with_decoy<D: DecoyStrategy + 'static>(self, decoy: D) -> Self` —
emit decoy chunks alongside real ones; defragment recognizes them via
a `u32::MAX` sentinel in the layout buffer.
**Example:**
```rust
use key_vault::{FragmentStrategy, RawKey, StandardFragmenter, SelfReferenceDecoy};
# fn main() -> Result<(), key_vault::Error> {
let frag = StandardFragmenter::with_chunk_range(2, 6)
.with_decoy(SelfReferenceDecoy);
let raw = RawKey::new(b"some key material".to_vec());
let fragments = frag.fragment(&raw)?;
let recovered = frag.defragment(&fragments)?;
assert_eq!(recovered.len(), raw.len());
# Ok(())
# }
```
<hr>
### `RandomFragmenter`
Source: `src/fragment/random.rs`
Non-contiguous byte scatter. Each chunk's bytes come from independently-
chosen random positions of the original key — no chunk contains a
contiguous run of key bytes longer than 1.
**Threat focus.** Defeats contiguous-format recognition attacks (DER
envelopes, PEM markers, ASCII-armored data, JWT headers).
**Constructors:**
- `RandomFragmenter::new() -> Self` — default chunk range (1–4).
- `RandomFragmenter::with_chunk_range(min: usize, max: usize) -> Self`
**Example:**
```rust
use key_vault::{FragmentStrategy, RandomFragmenter, RawKey};
# fn main() -> Result<(), key_vault::Error> {
let frag = RandomFragmenter::new();
let raw = RawKey::new((0u8..32).collect());
let fragments = frag.fragment(&raw)?;
let recovered = frag.defragment(&fragments)?;
assert_eq!(recovered.len(), 32);
# Ok(())
# }
```
<hr>
### `InterleavedFragmenter`
Source: `src/fragment/interleaved.rs`
Single-pool byte placement. Allocates one `LockedBytes` pool (default
4× key length) and writes key bytes at random positions, padding the
gaps with CSPRNG bytes.
**Threat focus.** Defeats byte-level statistical analysis of the pool.
**Constructors:**
- `InterleavedFragmenter::new() -> Self` — default pool factor of 4.
- `InterleavedFragmenter::with_pool_factor(factor: usize) -> Self` —
factor clamped to `>= 2`.
**Example:**
```rust
use key_vault::{FragmentStrategy, InterleavedFragmenter, RawKey};
# fn main() -> Result<(), key_vault::Error> {
let frag = InterleavedFragmenter::with_pool_factor(6);
let raw = RawKey::new(vec![0xa5; 32]);
let fragments = frag.fragment(&raw)?;
// Single chunk (the pool) of size 32 * 6 = 192 bytes.
assert_eq!(fragments.chunk_count(), 1);
# Ok(())
# }
```
<hr>
### `LayeredFragmenter`
Source: `src/fragment/layered.rs`
Composition by random routing among sub-strategies. Each `fragment`
call picks a sub-strategy uniformly at random; the picked index is
prepended to the layout as a 4-byte LE header so `defragment` dispatches
correctly.
**Constructors:**
- `LayeredFragmenter::new(sub_strategies: Vec<Arc<dyn FragmentStrategy>>) -> Result<Self>`
— empty list returns `Error::InvalidConfig`.
**Read accessors:**
- `sub_strategy_count(&self) -> usize` — number of sub-strategies in
the rotation.
**Example:**
```rust
use std::sync::Arc;
use key_vault::{
FragmentStrategy, InterleavedFragmenter, LayeredFragmenter,
RandomFragmenter, RawKey, StandardFragmenter,
};
# fn main() -> Result<(), key_vault::Error> {
let frag = LayeredFragmenter::new(vec![
Arc::new(StandardFragmenter::new()) as Arc<dyn FragmentStrategy>,
Arc::new(InterleavedFragmenter::new()) as Arc<dyn FragmentStrategy>,
Arc::new(RandomFragmenter::new()) as Arc<dyn FragmentStrategy>,
])?;
assert_eq!(frag.sub_strategy_count(), 3);
let raw = RawKey::new(vec![0u8; 32]);
let fragments = frag.fragment(&raw)?;
let recovered = frag.defragment(&fragments)?;
assert_eq!(recovered.len(), 32);
# Ok(())
# }
```
<hr>
### `DecoyStrategy` (trait, Layer 4)
Source: `src/decoy/mod.rs`
Generates filler bytes that surround real key fragments.
```rust
pub trait DecoyStrategy: Send + Sync {
fn generate(&self, key: &RawKey, output_len: usize) -> Result<Vec<u8>>;
fn describe(&self) -> Cow<'_, str>;
}
```
**Contract:**
- For strategies that derive filler from the key, mix in a fresh
per-call nonce so two consecutive `generate` calls produce different
output.
- No accidental key recovery — a decoy must never emit a contiguous run
of bytes that matches the real key.
<hr>
### `RandomDecoy`
Source: `src/decoy/random.rs`
Pure CSPRNG bytes from `getrandom`. Weakest of the three built-in
strategies — uniformly random distribution is distinguishable from key
material that has format markers.
```rust
use key_vault::{DecoyStrategy, RandomDecoy, RawKey};
# fn main() -> Result<(), key_vault::Error> {
let key = RawKey::new(b"anything".to_vec());
let bytes = RandomDecoy.generate(&key, 32)?;
assert_eq!(bytes.len(), 32);
# Ok(())
# }
```
<hr>
### `SelfReferenceDecoy`
Source: `src/decoy/self_reference.rs`
For each output byte, sample an independent random index into the key
and emit `key[idx]`. The decoy's byte-value distribution is **identical**
to the key's — strongest indistinguishability, **recommended default**.
```rust
use key_vault::{DecoyStrategy, RawKey, SelfReferenceDecoy};
# fn main() -> Result<(), key_vault::Error> {
let key = RawKey::new(vec![0xa1, 0xb2, 0xc3]);
let decoy = SelfReferenceDecoy.generate(&key, 32)?;
// Every decoy byte is drawn from the key's byte set.
for b in &decoy {
assert!([0xa1, 0xb2, 0xc3].contains(b));
}
# Ok(())
# }
```
<hr>
### `KeyDerivedDecoy`
Source: `src/decoy/key_derived.rs`
BLAKE3-XOF seeded by `key bytes ‖ 32-byte CSPRNG nonce`. Per-call nonce
ensures fresh output even for the same key. Middle ground between
`RandomDecoy` and `SelfReferenceDecoy`.
```rust
use key_vault::{DecoyStrategy, KeyDerivedDecoy, RawKey};
# fn main() -> Result<(), key_vault::Error> {
let key = RawKey::new(b"k".to_vec());
let a = KeyDerivedDecoy.generate(&key, 32)?;
let b = KeyDerivedDecoy.generate(&key, 32)?;
// Per-call nonce: two consecutive calls produce different output.
assert_ne!(a, b);
# Ok(())
# }
```
<hr>
### `Codex` (trait, Layer 5)
Source: `src/codex/mod.rs`
Byte-wise involution applied to all stored bytes.
```rust
pub trait Codex: Send + Sync {
fn encode(&self, byte: u8) -> u8;
fn decode(&self, byte: u8) -> u8;
}
```
**Contract:**
- Involution: `decode(encode(x)) == x` for every byte.
- Constant-time, branch-free; canonical shape is a 256-byte lookup table.
<hr>
### `IdentityCodex`
Source: `src/codex/identity.rs`
No-op codex. `encode(x) == decode(x) == x`. Default Layer-5
implementation.
```rust
use key_vault::{Codex, IdentityCodex};
let c = IdentityCodex;
assert_eq!(c.encode(0xab), 0xab);
assert_eq!(c.decode(0xab), 0xab);
```
<hr>
### `FnCodex<F>`
Source: `src/codex/mod.rs`
Wraps a user-provided closure. The closure **must be an involution** —
nothing in the type system enforces this; violation will corrupt every
stored key.
**Constructors:**
- `FnCodex::new(f: F) -> Self` where `F: Fn(u8) -> u8 + Send + Sync`.
**Example (XOR with fixed mask is an involution):**
```rust
use key_vault::codex::{Codex, FnCodex};
assert_eq!(codex.decode(codex.encode(byte)), byte);
}
```
<hr>
### `StaticCodex`
Source: `src/codex/static_codex.rs`
256-byte involution lookup table held in a `LockedBytes` buffer
(mlock'd, zeroed on drop).
**Constructors:**
- `StaticCodex::from_swaps(swaps: &[(u8, u8)]) -> Result<Self>` —
declarative. Each `(a, b)` means `a ↔ b`. Bytes not in any pair are
fixed points. Returns `Error::Codex` if a byte appears in more than
one swap pair.
- `StaticCodex::random_involution() -> Result<Self>` — pair up all 256
bytes randomly. No fixed points. Returns `Error::Internal` on
CSPRNG failure.
**Example (declarative):**
```rust
use key_vault::{Codex, StaticCodex};
# fn main() -> Result<(), key_vault::Error> {
let codex = StaticCodex::from_swaps(&[(b'0', b'#'), (b'A', b'@')])?;
assert_eq!(codex.encode(b'0'), b'#');
assert_eq!(codex.encode(b'B'), b'B'); // fixed point
# Ok(())
# }
```
**Example (random):**
```rust
use key_vault::{Codex, StaticCodex};
# fn main() -> Result<(), key_vault::Error> {
let codex = StaticCodex::random_involution()?;
for byte in 0u8..=255 {
assert_eq!(codex.decode(codex.encode(byte)), byte);
assert_ne!(codex.encode(byte), byte); // no fixed points
}
# Ok(())
# }
```
<hr>
### `DynamicCodex`
Source: `src/codex/dynamic.rs`
Per-vault random involution. Thin wrapper around
`StaticCodex::random_involution()` for the common case.
**Constructors:**
- `DynamicCodex::new() -> Result<Self>` — fresh random table.
**Example:**
```rust
use key_vault::{Codex, DynamicCodex};
# fn main() -> Result<(), key_vault::Error> {
let codex = DynamicCodex::new()?;
for byte in 0u8..=255 {
assert_eq!(codex.decode(codex.encode(byte)), byte);
}
# Ok(())
# }
```
<hr>
### `SecurityMonitor` (trait, Layer 8)
Source: `src/monitor/mod.rs`
Outbound channel for anomaly events.
```rust
pub trait SecurityMonitor: Send + Sync {
fn on_decryption_failure(&self, ctx: &FailureContext);
fn on_anomalous_access(&self, ctx: &AccessContext);
fn on_threshold_breach(&self, ctx: &ThresholdContext);
}
```
**Contract:**
- Non-blocking: monitor calls must return promptly. Network/disk work
belongs on a background thread.
- No panics. No key material in calls.
- `Send + Sync`.
Built-in implementations (`NoMonitor`, `LogMonitor`, `MetricsMonitor`,
`WebhookMonitor`, `CompositeMonitor`) arrive in 0.8.0.
<hr>
### `NoMonitor`
Source: `src/monitor/no_monitor.rs`
Inert default. Discards every event. Always available.
```rust
use key_vault::{KeyVaultBuilder, NoMonitor};
let _vault = KeyVaultBuilder::new().with_monitor(NoMonitor).build();
```
<hr>
### `CompositeMonitor`
Source: `src/monitor/composite.rs`
Fan-out across `Vec<Arc<dyn SecurityMonitor>>`. Inner monitors are
called in registration order; an empty list yields a no-op monitor.
**Constructors:**
- `CompositeMonitor::new(inner: Vec<Arc<dyn SecurityMonitor>>) -> Self`
**Read accessors:**
- `len(&self) -> usize`
- `is_empty(&self) -> bool`
```rust
use std::sync::Arc;
use key_vault::{CompositeMonitor, NoMonitor, SecurityMonitor};
let composite = CompositeMonitor::new(vec![
Arc::new(NoMonitor) as Arc<dyn SecurityMonitor>,
Arc::new(NoMonitor) as Arc<dyn SecurityMonitor>,
]);
assert_eq!(composite.len(), 2);
```
<hr>
### `LogMonitor`
Source: `src/monitor/log_monitor.rs` · Feature: `monitor-tracing`
Emits structured `tracing` events. `on_decryption_failure` and
`on_anomalous_access` log at `warn!`; `on_threshold_breach` logs at
`error!`. All events target `key_vault::monitor`. Fields are
structured (`key_name`, `consecutive_failures`, `window_elapsed_ms`,
etc.) for easy filtering.
**Constructors:**
- `LogMonitor::new() -> Self`
- `LogMonitor::default()` — same.
Stateless; one instance can be shared across all vaults.
```rust
use key_vault::{KeyVaultBuilder, LogMonitor};
let _vault = KeyVaultBuilder::new().with_monitor(LogMonitor::new()).build();
```
<hr>
### `AuditSink` (trait, Layer 9)
Source: `src/audit/mod.rs`
Outbound channel for the vault's audit trail.
```rust,ignore
pub trait AuditSink: Send + Sync {
fn on_event(&self, event: &AuditEvent);
}
```
**Contract:**
- **Non-blocking.** Sink calls must return promptly. Network / disk
work belongs on a background worker.
- **No panics.** A panicking sink implementation is a bug in the
implementation, not the vault.
- **No back-pressure.** If the sink is overloaded, shed events
internally — never block the caller.
- **`Send + Sync`.** Sinks are shared across threads.
Built-in implementations: [`NoAudit`](#noaudit) (default),
[`LogAudit`](#logaudit) (feature `monitor-tracing`).
A blanket `impl AuditSink for Arc<dyn AuditSink>` lets callers pass
pre-wrapped sinks.
<hr>
### `AuditEvent`
Source: `src/audit/mod.rs`
`#[non_exhaustive]`. Single record in the vault's audit trail.
**Fields:**
- `pub timestamp: Duration` — since UNIX_EPOCH.
- `pub key_name: String` — never key bytes. Empty for one-shot
`fragment` / `defragment`; `"<master>"` for master-unlock attempts.
- `pub kind: AccessKind` — discriminant of the operation.
- `pub thread_id: std::thread::ThreadId`.
- `pub note: Cow<'static, str>` — caller-supplied free-text label.
<hr>
### `AccessKind`
Source: `src/audit/mod.rs`
```rust,ignore
#[non_exhaustive]
pub enum AccessKind {
Register,
Unregister,
Read,
Rotate,
OneShotFragment,
OneShotDefragment,
MasterUnlockAttempt { matched: bool },
}
```
Implements `Display` for human-readable labels (`"register"`,
`"master-unlock-ok"` / `"master-unlock-fail"`, etc.).
<hr>
### `NoAudit`
Source: `src/audit/no_audit.rs`
Inert default. Discards every event. Always available. The vault
uses this when no sink is configured.
<hr>
### `LogAudit`
Source: `src/audit/log_audit.rs` · Feature: `monitor-tracing`
Emits structured `tracing` events at `info!` level on the
`key_vault::audit` target. Fields are structured (`key_name`,
`kind`, `thread_id`, `timestamp_ms_since_epoch`, `note`) for easy
filtering.
```rust
use key_vault::{KeyVaultBuilder, LogAudit};
let _vault = KeyVaultBuilder::new().with_audit_sink(LogAudit::new()).build();
```
<hr>
### `FailureContext`
Source: `src/monitor/mod.rs`
`#[non_exhaustive]`. Passed to `SecurityMonitor::on_decryption_failure`.
**Fields:**
- `pub key_name: String`
- `pub consecutive_failures: u32`
- `pub window_elapsed: Duration`
- `pub note: Cow<'static, str>`
<hr>
### `AccessContext`
Source: `src/monitor/mod.rs`
`#[non_exhaustive]`. Passed to `SecurityMonitor::on_anomalous_access`.
**Fields:**
- `pub key_name: String`
- `pub note: Cow<'static, str>`
<hr>
### `ThresholdContext`
Source: `src/monitor/mod.rs`
`#[non_exhaustive]`. Passed to `SecurityMonitor::on_threshold_breach`.
**Fields:**
- `pub key_name: String`
- `pub failures_in_window: u32`
- `pub window: Duration`
- `pub lockout_triggered: bool`
<hr>
### `tee::detect_tee_capabilities`
Source: `src/tee/mod.rs`
```rust
#[must_use]
pub fn detect_tee_capabilities() -> TeeCapabilities;
```
Probe the host platform for available Trusted Execution Environments.
Side-effect-free; suitable for calling at process startup. Reads a
handful of CPUID instructions on x86_64 and (on Linux) the DMI sysfs
vendor string. Never opens privileged files, loads drivers, or makes
network calls.
**Example:**
```rust
use key_vault::tee::{detect_tee_capabilities, Detection};
let caps = detect_tee_capabilities();
if caps.any_detected() {
println!("TEE available: {caps}");
}
let sgx_present = matches!(caps.sgx, Detection::Detected);
```
<hr>
### `tee::TeeCapabilities`
Source: `src/tee/mod.rs`
`#[non_exhaustive]` snapshot of every TEE probe.
**Fields:**
- `pub sgx: Detection` — Intel SGX (CPUID.07H EBX[2] on x86_64).
- `pub tdx: Detection` — Intel TDX (CPUID.21H "IntelTDX " signature).
- `pub sev: Detection` — AMD SEV (CPUID 0x8000001F EAX[1]).
- `pub sev_snp: Detection` — AMD SEV-SNP (CPUID 0x8000001F EAX[4]).
- `pub trustzone: Detection` — ARM TrustZone (always `Unknown` in 1.0).
- `pub secure_enclave: Detection` — Apple Secure Enclave
(`Detected` on `aarch64-apple-darwin`).
- `pub nitro: Detection` — AWS Nitro Enclaves (Linux DMI vendor).
**Methods:**
- `any_detected(self) -> bool` — `true` if at least one probe positively
confirmed a TEE. `Unknown` does not count.
`Display` produces a single-line summary suitable for logging.
<hr>
### `tee::Detection`
Source: `src/tee/mod.rs`
```rust
#[non_exhaustive]
pub enum Detection {
Detected,
NotDetected,
Unknown,
}
```
`Unknown` is distinct from `NotDetected` — it means "this platform
can't be probed from userspace." Treating `Unknown` as
"not available" is the safe default for selecting fetchers.
**Methods:**
- `is_detected(self) -> bool` — `true` only for `Detected`.
`Display` prints `"detected"` / `"not detected"` / `"unknown"`.
<a href="#top">↑ TOP</a>
<hr>
## Examples
### Full default stack (every shipped layer)
```rust
use key_vault::{
DynamicCodex, KeyVaultBuilder, RawKey, SelfReferenceDecoy,
};
# fn main() -> Result<(), key_vault::Error> {
let vault = KeyVaultBuilder::new()
.normalize_with_blake3(true)
.with_codex(DynamicCodex::new()?)
.with_decoy(SelfReferenceDecoy)
.build();
let raw = RawKey::new(b"my application key".to_vec());
let fragments = vault.fragment(&raw)?;
let recovered = vault.defragment(&fragments)?;
assert_eq!(recovered.len(), 32); // BLAKE3 normalization → 32 bytes
# Ok(())
# }
```
### Minimal vault (no normalization, no decoy, no codex)
```rust
use key_vault::{KeyVaultBuilder, RawKey};
# fn main() -> Result<(), key_vault::Error> {
let vault = KeyVaultBuilder::new()
.normalize_with_blake3(false)
.build();
let raw = RawKey::new(b"raw bytes here".to_vec());
let fragments = vault.fragment(&raw)?;
let recovered = vault.defragment(&fragments)?;
assert_eq!(recovered.len(), raw.len());
# Ok(())
# }
```
### Custom Layer-3 fragmenter selection with composition
```rust
use std::sync::Arc;
use key_vault::{
FragmentStrategy, InterleavedFragmenter, LayeredFragmenter,
RandomFragmenter, RawKey, StandardFragmenter,
};
# fn main() -> Result<(), key_vault::Error> {
// Route each fragmentation to one of three strategies.
let composite = LayeredFragmenter::new(vec![
Arc::new(StandardFragmenter::with_chunk_range(2, 6)) as Arc<dyn FragmentStrategy>,
Arc::new(InterleavedFragmenter::with_pool_factor(8)) as Arc<dyn FragmentStrategy>,
Arc::new(RandomFragmenter::new()) as Arc<dyn FragmentStrategy>,
])?;
let raw = RawKey::new(vec![0u8; 64]);
let fragments = composite.fragment(&raw)?;
let recovered = composite.defragment(&fragments)?;
assert_eq!(recovered.len(), 64);
# Ok(())
# }
```
### Declarative codex (private build)
```rust
use key_vault::{KeyVaultBuilder, StaticCodex};
# fn main() -> Result<(), key_vault::Error> {
// Build-time-known swap table for a private deployment.
let codex = StaticCodex::from_swaps(&[
(b'0', b'#'),
(b'A', b'@'),
(b'h', b'!'),
])?;
let vault = KeyVaultBuilder::new()
.with_codex(codex)
.build();
# let _ = vault;
# Ok(())
# }
```
### Custom `KeyFetch`
```rust
use std::borrow::Cow;
use key_vault::{Error, FetchContext, KeyFetch, RawKey, Result};
struct EnvironmentFetch {
var_name: String,
}
impl KeyFetch for EnvironmentFetch {
fn fetch(&self, _ctx: &FetchContext) -> Result<RawKey> {
let value = std::env::var(&self.var_name).map_err(|_| {
Error::Acquisition {
source: Cow::Borrowed("env"),
reason: format!("variable {} not set", self.var_name),
}
})?;
Ok(RawKey::new(value.into_bytes()))
}
fn describe(&self) -> Cow<'_, str> {
Cow::Borrowed("env")
}
}
```
<a href="#top">↑ TOP</a>
<hr>
## API Safety
**Public-API guarantees in 0.6.0:**
| Zero `unsafe` in public API | Code review; the only `unsafe` blocks are in `tee::x86_64::safe_cpuid_count` and the `mlock`/`VirtualLock` shims in `src/memory/`, all crate-private. |
| No key bytes leak via `Debug` | `KeyHandle` / `KeyId` / `RawKey` / `Fragments` all print `<redacted>` or `<opaque>`. 1024-handle sweep test in CI. |
| No key bytes in `Error` variants | All variants carry only sanitized prose. |
| Constant-time handle equality | `KeyHandle: subtle::ConstantTimeEq`; `PartialEq` routes through it. `Hash` consistent. |
| Zero-on-drop | Every `LockedBytes` (fragments, layout buffer, codex table) volatile-zeroes its bytes before unlocking and freeing. |
| mlock / VirtualLock | Applied unconditionally to every `LockedBytes` allocation. Soft-fails (records `is_locked = false`) when `RLIMIT_MEMLOCK` is exceeded. |
| `RawKey` bytes not exposed | `RawKey::as_bytes()` is `pub(crate)`; outside callers see only `len()`. |
| Round-trip for every `FragmentStrategy` | 1000-iteration stress + per-strategy unit tests in CI. |
| Codex involution property | 256-byte sweep verified for `StaticCodex`, `DynamicCodex`, `FnCodex`, `IdentityCodex`. |
**Threat model.** See [`docs/SECURITY.md`](SECURITY.md) for the full
per-layer architecture and threat-model coverage.
<a href="#top">↑ TOP</a>
<hr>
## Notes
### What's not in 0.11.0 (yet)
- **`MetricsMonitor`** (metrics-lib integration) and **`WebhookMonitor`**
(HTTP POST to alert endpoint) — deferred to post-1.0; both require
external dependencies. Build a custom `SecurityMonitor` impl in the
meantime.
- **Master-key-as-KEK** — the master is an emergency-unlock
credential only. Sealing other keys with the master needs a
key-derivation scheme; deferred to post-1.0.
- **`dudect`-asserted constant-time** for the full `with_key` path —
`KeyHandle::eq` uses `subtle::ConstantTimeEq` and inherits its
guarantee; the full call's constant-time property is not yet
automated. Deferred to post-1.0.
- **True zero-allocation hot path** — `dhat` measures ~2 allocations
per `with_key` call (defragment buffer + dispatch glue). Both
could be eliminated with a thread-local reusable buffer; the
trade-off makes that deferred to post-1.0.
- **`frag_len` / `frag_symbols` configuration knobs** — deferred to a
later phase.
### Stability
`key-vault` is pre-1.0. The public API surface listed above will receive
additions in every minor release through 0.9.x; renames and removals
are possible but flagged in the CHANGELOG. The 1.0.0 stability contract
takes effect at the v1.0.0 tag.
### Cross-platform support
- **Linux**: x86_64, aarch64. mlock via `libc`. AWS Nitro detection via
DMI sysfs.
- **macOS**: x86_64, aarch64 (Apple Silicon). mlock via `libc`. Secure
Enclave detection via target triple.
- **Windows**: x86_64. VirtualLock via `windows-sys`. No Apple SE / AWS
Nitro detection.
CI exercises every combination on `stable` and pinned MSRV `1.85.0`.
### Licensing
Apache-2.0 OR MIT, at your option. See [`LICENSE-APACHE`](../LICENSE-APACHE)
and [`LICENSE-MIT`](../LICENSE-MIT).
---
<sub>key-vault API Reference — Copyright (c) 2026 James Gober. Apache-2.0 OR MIT.</sub>