key-vault 0.6.0

Enterprise-grade key management vault for Rust. 9-layer defense-in-depth: fragmentation, decoy bytes, codex transform, mlock + zeroize, constant-time ops, security monitoring. Pluggable key fetchers (TPM, keychain, file, env). Sub-microsecond access. REPS-compliant.
Documentation

9-Layer Defense Architecture

The complete defense stack:

Layer Defense Defends Against
1 Secure Acquisition (TPM, Keychain, etc.) Untrusted key sources
2 Memory Page Locking (mlock / VirtualLock) Swap files, hibernation
3 Fragment Strategy (variable chunks, shuffle) Pattern recognition, memory scraping
4 Decoy Bytes (self-referential filler) Entropy/frequency analysis
5 Codex Transformation (byte swap) Memory dump analysis
6 Constant-Time Operations Timing side-channels
7 Zero-On-Drop Use-after-free leakage
8 Security Monitor (failure detection) Brute-force, anomalous access
9 Audit Logging Forensic trail, compliance
10 (Bonus) Page Protection Toggling Snapshot attacks

Full details: see docs/SECURITY.md for the comprehensive security architecture.

Visual walkthrough: see docs/TRANSFORMATION.md for a step-by-step trace of what happens to a key as it passes through all the layers.


Current status

key-vault is pre-1.0; the public API is not yet stable. The 9-layer architecture above is the 1.0 design target. Each release lights up more of it. The "Features" section below documents the 1.0 surface; the table here records what is actually built today so you can match the README against the shipped code.

Component Status as of 0.6.0
Public type system (Error, Result, KeyHandle, KeyMetadata, RawKey, FetchContext, Fragments) shipped
Trait surfaces (KeyFetch, FragmentStrategy, DecoyStrategy, Codex, SecurityMonitor) shipped
Layer 2 — mlock / VirtualLock (via internal LockedBytes wrapper) shipped
Layer 3 — All four fragment strategies: StandardFragmenter, RandomFragmenter, InterleavedFragmenter, LayeredFragmenter shipped
Layer 4 — Decoy strategies (RandomDecoy, SelfReferenceDecoy, KeyDerivedDecoy) shipped
Layer 5 — Full codex stack: IdentityCodex, FnCodex, StaticCodex, DynamicCodex (table in LockedBytes) shipped
Layer 6 — Constant-time KeyHandle equality (via subtle::ConstantTimeEq) shipped
Layer 7 — Zero-on-drop (every fragment + layout buffer + intermediate plaintext + decoy buffer + codex table) shipped
BLAKE3 key normalization (wired through KeyVaultBuilder::normalize_with_blake3) shipped
TEE detection (detect_tee_capabilities) shipped (real x86_64 + Apple SE + AWS Nitro probes)
KeyVault::fragment / KeyVault::defragment convenience methods shipped (uses StandardFragmenter internally)
KeyVaultBuilder::with_chunk_range / with_decoy / with_codex shipped
Layer 1 — built-in fetchers (Keychain, File, Env, TPM) planned for 0.7.0
Layer 8 — Monitor implementations planned for 0.8.0
Layer 9 — Audit logging planned for 0.8.0
Multi-key vaults, rotation, master recovery planned for 0.9.0
Criterion benchmark suite planned for 0.10.0

Each phase's exit criteria, scope, and timeline are tracked in .dev/ROADMAP.md.


Features (the 1.0 design)

Defense-in-depth memory protection (1.0 design)

  • Memory page locking (mlock / VirtualLock) prevents key material from being swapped to disk
  • Fragment storage splits keys into variable-sized chunks at non-contiguous addresses
  • Self-referential decoy bytes statistically indistinguishable from real key material
  • Codex transformation (opt-in) adds byte-level obfuscation
  • Zero-on-drop via zeroize overwrites memory when keys leave scope
  • Constant-time comparisons via subtle prevent timing attacks
  • No debug exposureKeyHandle's Debug impl always prints KeyHandle(<redacted>) (shipped today)

Pluggable key fetchers (1.0 design, KeyFetch trait shipped)

The KeyFetch trait is in place today. Built-in implementations arrive in 0.7.0:

  • TPM 2.0 hardware fetcher — detection-only in 1.0, full integration deferred to 1.x
  • OS Keychain — macOS Keychain, Windows Credential Manager, Linux Secret Service
  • Encrypted file with permission checks
  • Environment variables for container deployments
  • Custom fetchers via the trait — bring your own HSM, KMS client, or proprietary source

Fragment strategies (all four shipped in 0.5.0)

  • Standard — variable chunks + Fisher-Yates shuffle, each chunk in its own mlock'd allocation
  • Random — chunks of variable size whose bytes come from non-contiguous positions in the key (no chunk holds a contiguous key substring longer than 1)
  • Interleaved — single large pool, key bytes scattered at random positions, gaps filled with CSPRNG padding
  • Layered — routes each fragmentation to a uniformly-picked sub-strategy; strategy index encoded in the layout header so defragment dispatches correctly
  • Custom — implement the FragmentStrategy trait

See docs/SECURITY.md for the per-strategy threat-model comparison.

Decoy strategies (1.0 design, DecoyStrategy trait shipped)

  • Random — raw RNG bytes (fastest, weakest) — shipped in 0.4.0
  • Self-Reference — real key bytes used as filler (strongest, recommended default) — shipped in 0.4.0
  • Key-Derived — BLAKE3-XOF bytes seeded by key + per-call nonce — shipped in 0.4.0

Codex layer (Layer 5, all four shipped in 0.6.0)

  • IdentityCodex — no transformation (default, max performance) — shipped in 0.2.0
  • FnCodex — user-provided closure (involution-only) — shipped in 0.2.0
  • StaticCodex — declarative swap table (from_swaps(&[(u8, u8)])) or fresh random involution (random_involution()) — shipped in 0.6.0
  • DynamicCodex — per-vault randomized involution with no fixed points — shipped in 0.6.0

All codex tables live in LockedBytes (mlock'd, zeroed on drop). Encoding/decoding is one memory load per byte (constant-time, branch-free).

Security monitoring (Layer 8, trait shipped)

  • Failed decryption detection — N failures in M seconds triggers configurable response — 0.8.0
  • Anomalous access patterns — detect sustained data exfiltration — 0.8.0
  • Threshold lockout — lock vault after threshold breach — 0.8.0
  • Pluggable sinks — log, metrics, webhook, custom — 0.8.0

Operational features

  • Master key recovery — fallback path for hardware failure — 0.9.0
  • Key rotation — atomic swap to new key without dropping access — 0.9.0
  • Multiple keys per vault — named keys with independent lifecycles — 0.9.0
  • TEE detection — check for Intel SGX, Intel TDX, AMD SEV, AMD SEV-SNP, ARM TrustZone, Apple Secure Enclave, AWS Nitro — shipped
  • Key normalization — BLAKE3 hash input to neutralize format-based pattern leaks — shipped in 0.3.0

Performance targets (1.0 design — not yet measured)

  • Key acquisition — sub-second from hardware, sub-millisecond from keychain
  • Key access (defrag into temporary buffer) — sub-microsecond (~500ns including audit + monitor)
  • Concurrent access — lock-free reads after vault initialization
  • Memory overhead — < 16 KiB per key (including fragment + decoy overhead)
  • Zero allocations on the hot path (after vault initialization)

Note on benchmark numbers: detailed criterion-backed benchmark numbers will land with v1.0.0 (Phase 0.10.0 in the roadmap). Until then, performance numbers are targets, not measurements.


Quick start

[dependencies]
key-vault = "0.6"
use key_vault::{DynamicCodex, KeyVaultBuilder, RawKey, SelfReferenceDecoy};
use key_vault::tee::detect_tee_capabilities;

// Build a vault with the full default stack: Layer 2 (mlock/VirtualLock)
// + Layer 3 (StandardFragmenter) + Layer 4 (SelfReferenceDecoy — the
// strongest decoy) + Layer 5 (per-vault DynamicCodex involution)
// + Layer 6 (ConstantTimeEq) + Layer 7 (zero-on-drop).
let vault = KeyVaultBuilder::new()
    .normalize_with_blake3(true) // default
    .with_codex(DynamicCodex::new().expect("codex"))
    .with_decoy(SelfReferenceDecoy)
    .build();

// Hand the vault some key material and get back an opaque, scattered,
// mlock'd, zeroed-on-drop representation with decoy chunks mixed in.
let raw = RawKey::new(b"my application key".to_vec());
let frags = vault.fragment(&raw).expect("fragment");

// Reassemble when you need to use it. With BLAKE3 normalization on,
// the recovered bytes are the 32-byte hash of the input.
let recovered = vault.defragment(&frags).expect("defragment");
assert_eq!(recovered.len(), 32);

// Snapshot the host's TEE capabilities at startup:
let caps = detect_tee_capabilities();
println!("{caps}");

Threat model

key-vault is designed to defend against:

  • Memory scraping by attackers with read access — malware, forensic tools
  • Forensic memory analysis — swap files, hibernation files, crash dumps
  • Statistical pattern recognition — entropy analysis, frequency analysis
  • Use-after-free leakage — keys persisting after they should have been wiped
  • Brute-force decryption attempts — failed attempts trigger alerts
  • Timing side-channels — constant-time operations
  • Insider threats / forensic compliance — full audit trail

It does NOT defend against:

  • Code execution within your process — an attacker who can call your reassembly logic
  • Hardware-level memory access (DMA attacks) — use IOMMU + hardware mitigations
  • Cold-boot attacks — use full disk encryption + power-down protocol
  • Side-channel attacks on cryptographic operations — that's crypt-io's job
  • Quantum computer attacks on asymmetric crypto — use post-quantum algorithms (symmetric defaults are PQ-safe)

See docs/SECURITY.md for the full threat model.


Documentation


Standards

  • REPS (Rust Efficiency & Performance Standards) governs every decision. See REPS.md.
  • MSRV: Rust 1.85.
  • Edition: 2024.
  • Cross-platform: Linux, macOS, Windows.

License

Dual-licensed under either of:

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.