key-vault 1.0.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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
# Security Architecture

> **9-Layer Defense-in-Depth + 1 Bonus Layer**
>
> This document is the full security architecture for `key-vault`. Every layer is documented with what it defends against, what it does NOT defend against, performance impact, and how it composes with other layers.

---

## Why layers

No single defense is bulletproof. A sophisticated attacker with unlimited time and access can defeat any one technique. Real-world security comes from layered defenses where:

1. Each layer adds friction
2. Layers are independent (defeating one doesn't defeat the others)
3. Multiple layers must fail simultaneously for total compromise
4. The cost of attack exceeds the value of the target

The 0.1% of elite attackers may still win. The other 99.9% — opportunistic attackers, automated tools, forensic recovery — get stopped at one of the layers.

---

## The 9 layers (plus bonus)

```
┌─────────────────────────────────────────────────────────────┐
│  LAYER 1: Secure Acquisition (TPM/HSM/Keychain)             │
│  ├── Hardware-rooted trust where available                  │
│  └── Untrusted input is never accepted                      │
├─────────────────────────────────────────────────────────────┤
│  LAYER 2: Memory Page Locking (mlock / VirtualLock)         │
│  ├── Prevents swap to disk                                  │
│  └── Reduces hibernation/coredump exposure                  │
├─────────────────────────────────────────────────────────────┤
│  LAYER 3: Fragment Strategy (key splicing + shuffling)      │
│  ├── Variable chunk sizes (1-8 bytes typical)               │
│  ├── Variable chunk counts (8-64 per scatter)               │
│  └── Per-vault randomized layout                            │
├─────────────────────────────────────────────────────────────┤
│  LAYER 4: Decoy Bytes (nonsense filler)                     │
│  ├── Self-referential (looks like real key material)        │
│  ├── Key-derived (matches statistical profile)              │
│  └── Defeats entropy/frequency analysis                     │
├─────────────────────────────────────────────────────────────┤
│  LAYER 5: Codex Transformation (byte swap)                  │
│  ├── Per-vault or per-build transformation table            │
│  ├── Involutional (encode == decode operation)              │
│  └── Adds work for attacker even with memory access         │
├─────────────────────────────────────────────────────────────┤
│  LAYER 6: Constant-Time Operations                          │
│  ├── No timing side-channels on key comparisons             │
│  └── subtle::ConstantTimeEq for all equality checks         │
├─────────────────────────────────────────────────────────────┤
│  LAYER 7: Zero-On-Drop                                      │
│  ├── zeroize crate overwrites memory on drop                │
│  └── No persistence in freed memory                         │
├─────────────────────────────────────────────────────────────┤
│  LAYER 8: Security Monitor (failure detection)              │
│  ├── Failed decryption attempts trigger alerts              │
│  ├── Anomalous access patterns logged                       │
│  └── Pluggable: webhook, metrics, logs, custom              │
├─────────────────────────────────────────────────────────────┤
│  LAYER 9: Audit Logging (access tracking)                   │
│  ├── Every key access logged with context                   │
│  ├── Caller identification, timestamp, thread               │
│  └── Anomalies surface for forensic review                  │
├─────────────────────────────────────────────────────────────┤
│  BONUS LAYER 10: Page Protection Toggling                   │
│  ├── PROT_NONE when not in use                              │
│  ├── PROT_READ only during reassembly                       │
│  └── Reduces vulnerability window                           │
└─────────────────────────────────────────────────────────────┘
```

---

## Layer 1: Secure Acquisition

### What it does

Determines where keys come from. The root of trust for everything else.

### Defends against

- Untrusted user input becoming a key
- Hardcoded keys in source code
- Keys in environment variables read by other processes
- Keys in files with weak permissions

### Implementation

The `KeyFetch` trait is the pluggable acquisition mechanism:

```rust
pub trait KeyFetch: Send + Sync {
    fn fetch(&self, ctx: &FetchContext) -> Result<RawKey>;
}
```

Built-in fetchers (all feature-gated):

| Fetcher | Source | Security level |
|---------|--------|---------------|
| `TpmFetch` | TPM 2.0 hardware | Highest (hardware-backed) |
| `KeychainFetch` | OS keychain | High (OS-protected) |
| `FileFetch` | Encrypted file | Medium (encrypted at rest) |
| `EnvFetch` | Environment variable | Low (process-readable) |

### Performance

- One-time cost at startup: 1ms - 100ms depending on source
- Zero impact at runtime: keys are fetched once, then cached in fragmented form

### Trade-offs

- TPM: Highest security, slowest acquisition (~100ms), requires hardware
- Keychain: Good security, fast (<10ms), requires OS support
- File: Acceptable security with encryption, fast (<1ms)
- Env: Lowest security, fastest (<1µs), suitable for development only

---

## Layer 2: Memory Page Locking (mlock)

### What it does

Tells the OS "do not move these memory pages to disk." Pages stay in RAM, never written to swap or hibernation.

### Defends against

- Swap file persistence (keys ending up on disk via OS paging)
- Hibernation file exposure (partially — some platforms handle locked pages specially)
- Forensic recovery from disk
- Cold-boot attacks (reduces but doesn't eliminate)

### Does NOT defend against

- Privileged code reading RAM directly
- Hardware compromise (e.g., DMA attacks)
- Kernel-level forensic tools

### Implementation

Linux/macOS: `mlock(addr, len)` and `munlock(addr, len)`
Windows: `VirtualLock(addr, size)` and `VirtualUnlock(addr, size)`

Applied automatically to every fragment allocation. Pages are unlocked on drop after zeroize.

### Performance

- One-time cost: ~1µs per `mlock` call (rare path)
- Zero runtime cost: doesn't affect read/write operations

### Limitations

- Locked memory counts against process limits (RLIMIT_MEMLOCK on Linux)
- Linux requires CAP_IPC_LOCK or appropriate ulimit
- Windows: works at page granularity

---

## Layer 3: Fragment Strategy

### What it does

Splits the key into variable-size chunks at non-contiguous memory addresses. The original key never exists as contiguous bytes in memory after fragmentation.

### Defends against

- Memory scraping for high-entropy regions
- Pattern matching for known key formats
- Linear memory dumps
- Adjacent memory leaks (a vulnerability finds the wrong chunk)

### Does NOT defend against

- Reverse engineering of the reassembly logic
- Targeted attacks that can trace allocations

### Implementation

```
ORIGINAL KEY:    0fx03mmqrhxhrk13
FRAGMENTS:       ["hxh", "03", "m", "fx", "k13", "qrr", "0m"]  (shuffled)
STORED AT:       0x7fa1b2c3 (allocation 1), 0x7fa1b400 (allocation 2), ...
                 (non-contiguous addresses, mlock'd)
```

Variable chunk sizes: 1-8 bytes typical (configurable via `frag_min`, `frag_max`)
Variable chunk counts: 8-64 chunks per scatter (depends on key length)
Per-scatter random seed: each vault initialization gets fresh randomization

### Strategies (shipped in 0.5.0)

| Strategy                | Storage shape                              | Layout encoding                        | Defends best against    | Memory overhead |
|-------------------------|--------------------------------------------|----------------------------------------|-------------------------|-----------------|
| `StandardFragmenter`    | Many `LockedBytes`, each 1–8 contiguous key bytes, shuffled order | One u32 offset per chunk; `u32::MAX` sentinel for decoy chunks | Pattern recognition + linear scans | 1–2× key (with decoy) |
| `RandomFragmenter`      | Many `LockedBytes` (default 1–4 bytes), each holding bytes drawn from **non-contiguous** key positions | Per chunk: `(size: u32, pos[0]..pos[size-1]: u32)` | Contiguous-format recognition (DER, PEM, ASCII-armor) | 5× key (4 bytes layout per real byte) |
| `InterleavedFragmenter` | One large `LockedBytes` pool (default 4× key length), key bytes scattered at random positions, padding filled with CSPRNG bytes | `(pool_size: u32, pos[0]..pos[key_len-1]: u32)` | Byte-level statistical analysis | 4× key |
| `LayeredFragmenter`     | Whatever the picked sub-strategy produces, with a 4-byte strategy-index header prepended to its layout | `(strategy_idx: u32, sub_strategy_layout)` | Sophisticated attackers who know one strategy but not which | Same as picked sub-strategy |

#### Per-strategy threat focus

- **`StandardFragmenter`** (the baseline): chunks are contiguous runs of
  key bytes. The defense is "find a chunk, you got 1–8 bytes." When
  combined with `SelfReferenceDecoy` (Layer 4), an attacker also can't
  tell which chunks are real.

- **`RandomFragmenter`**: each chunk's bytes are *non-contiguous in the
  original key*. Even a successful chunk read reveals at most
  `max_chunk` (default 4) bytes from random positions, with no
  contiguous run. Defeats format-recognition attacks that need a
  contiguous header.

- **`InterleavedFragmenter`**: one large pool, key bytes at random
  positions among CSPRNG-random padding. An attacker who reads the pool
  cannot distinguish key bytes from padding without the layout map.
  The trade-off: the pool is a single contiguous allocation, so a
  partial-read attack against the pool yields a substring of the pool
  (mostly padding, occasionally a key byte). Best paired with
  `LayeredFragmenter` so the attacker doesn't even know they have a
  pool.

- **`LayeredFragmenter`**: each `fragment` call picks one sub-strategy
  uniformly at random and delegates. The attacker has to guess which
  sub-strategy was used to know what layout encoding to apply.
  Composition through routing (rather than chained transformations)
  avoids materializing the key between layers.

#### Decoy compatibility

- `StandardFragmenter::with_decoy` — full support; emits paired real and
  decoy chunks recognized via the `u32::MAX` sentinel.
- `RandomFragmenter` — no built-in decoy slot in 0.5.0. Combine with
  `LayeredFragmenter` if you want decoy + non-contiguous scatter.
- `InterleavedFragmenter` — no separate decoy: the pool padding itself
  is CSPRNG noise that doubles as decoy.
- `LayeredFragmenter` — inherits decoy behavior from whichever
  sub-strategy was picked for a given fragmentation.

### Performance

- Fragmentation: ~100-500ns per key (one-time setup)
- Reassembly (defrag): ~50-200ns per key access (hot path)
- Memory overhead: ~2-4x key size due to fragments + decoy

---

## Layer 4: Decoy Bytes

### What it does

Fills the spaces around real fragments with "nonsense" bytes that look indistinguishable from real key material.

### Defends against

- Entropy analysis (finding high-entropy regions in memory)
- Frequency analysis (statistical patterns in byte distributions)
- Pattern recognition (recognizing where key bytes are)

### Does NOT defend against

- Knowing the fragmentation algorithm and reversing it
- Attackers with code execution that can call defrag logic

### Implementation

```
KEY:           0fx03mmqrhxhrk13
FRAGMENTS:     ["hxh", "03", "m", "fx", "k13", "qrr", "0m"]
DECOY BYTES:   "x0", "x0", "mhx", "qx0", "fxh", "101", "qrr", "0hxq", "rrx", "0mx"
                ↓ (interleaved)
FINAL OUTPUT:  x0hxhmhx03qrr0fxh13mqrrx0mx01hxh...
                ↑    ↑      ↑  ↑          ↑
                decoy real   decoy real   decoy
```

Notice how the decoy bytes contain fragments OF the key (`hxh`, `0m`, `qrr`). An attacker can't determine which is real and which is decoy without the position map.

### Strategies

| Strategy | Approach | Strength |
|----------|----------|----------|
| `RandomDecoy` | Pure CSPRNG bytes | Weak — high entropy distinguishes from key bytes |
| `KeyDerivedDecoy` | Hash of key material | Medium — same entropy profile as key |
| `SelfReferenceDecoy` | Real key bytes as filler | Strong — indistinguishable from real fragments |

### Performance

- Decoy generation: ~50-100ns per key (one-time setup)
- Storage overhead: configurable via `frag_len` (target output length)

---

## Layer 5: Codex Transformation

### What it does

Applies a byte-level swap transformation to all bytes (real key + decoy) before storage. Adds an obfuscation layer that even a successful memory dump must defeat.

### Defends against

- Memory dump analysis (attacker sees transformed bytes, not real ones)
- Reverse engineering pattern attacks
- "Read the memory and try it as a key" attacks

### Does NOT defend against

- Attackers with the codex table (e.g., from reverse-engineered binary)
- Cryptographic attacks (this is obfuscation, not encryption)
- Sophisticated frequency analysis if transformation is naive

### Implementation

The `Codex` trait defines the transformation:

```rust
pub trait Codex: Send + Sync {
    fn encode(&self, byte: u8) -> u8;
    fn decode(&self, byte: u8) -> u8;
}
```

**For involution-based codices (encode = decode):**

```rust
// Static lookup table — built into the binary
pub struct StaticCodex([u8; 256]);

impl StaticCodex {
    /// Build from a list of swap pairs.
    /// `[('h', '%'), ('k', '$'), ('0', '#')]` means:
    ///   h ↔ % (encode h → %, encode % → h, decode same)
    ///   k ↔ $
    ///   0 ↔ #
    /// Bytes not in swap pairs pass through unchanged.
    pub fn from_swaps(swaps: &[(u8, u8)]) -> Self;

    /// Generate a random involution.
    /// Produces a permutation where applying it twice returns the original.
    /// Used by DynamicCodex for per-vault randomization.
    pub fn random_involution(rng: &mut impl RngCore) -> Self;
}

// User-provided closure
pub struct FnCodex<F>(F);

impl<F: Fn(u8) -> u8 + Send + Sync> Codex for FnCodex<F> {
    fn encode(&self, byte: u8) -> u8 { (self.0)(byte) }
    fn decode(&self, byte: u8) -> u8 { (self.0)(byte) }  // assumes involution
}
```

### Example transformation

```
PRE-CODEX:    0fx03mmqrhxhrk13
CODEX TABLE:  h ↔ %, k ↔ $, 0 ↔ #
POST-CODEX:   #fx#3mmqr%x%r$13
              ↑    ↑     ↑   ↑
              0→#  0→#   h→% k→$
```

To decode: same operation. Apply codex.decode() (or codex.encode(), they're the same for involutions). Original key returns.

### Variants

| Codex | Description |
|-------|-------------|
| `IdentityCodex` | No transformation (default, max perf) |
| `StaticCodex` | Build-time transformation table (private builds customize this) |
| `DynamicCodex` | Per-vault randomized table |
| `FnCodex` | User-provided closure |

### Performance

- Per-byte transformation: ~5-10ns (lookup table)
- For a 256-bit key with full reassembly: ~80-160ns total
- For 256-byte fragmented output (with decoy): ~1.3-2.6µs

**Feature-gated.** Opt-in via `codex` feature. Default off for maximum performance.

### Hive DB pattern

Hive DB ships its own `HiveCodex` with a proprietary swap table:

```rust
// In hive-key-vault (private crate)
pub struct HiveCodex;

impl Codex for HiveCodex {
    fn encode(&self, byte: u8) -> u8 {
        // Proprietary transformation, never exposed
    }
    fn decode(&self, byte: u8) -> u8 {
        self.encode(byte)  // Involution
    }
}
```

---

## Layer 6: Constant-Time Operations

### What it does

Ensures that operations on key material take the same amount of time regardless of the key values. Defeats timing side-channel attacks.

### Defends against

- Timing attacks on key comparison
- Side-channel leakage through CPU branch prediction
- Cache-timing attacks (partially — depends on implementation)

### Does NOT defend against

- Power analysis
- Electromagnetic emanation analysis
- Most cache-timing attacks on cryptographic operations (that's the crypto library's job)

### Implementation

All key comparisons use `subtle::ConstantTimeEq`:

```rust
use subtle::ConstantTimeEq;

if key_a.ct_eq(&key_b).into() {
    // ...
}
```

NEVER:

```rust
if key_a == key_b {  // VULNERABLE — early-exit on first mismatch
    // ...
}
```

### Performance

- Constant-time comparison of 32 bytes: ~10-20ns
- Compared to non-CT comparison: ~5ns difference (acceptable)

---

## Layer 7: Zero-On-Drop

### What it does

When key material leaves scope (function returns, struct drops), the memory is overwritten with zeros. Subsequent reads of that memory location yield zeros, not stale key bytes.

### Defends against

- Use-after-free leakage
- Stale data in freed memory pools
- Forensic recovery of recently-freed memory

### Does NOT defend against

- Memory access during the lifetime of the key
- Hardware-level memory persistence (rare, mostly DRAM rowhammer territory)

### Implementation

The `zeroize` crate provides `Zeroize` and `ZeroizeOnDrop`:

```rust
use zeroize::{Zeroize, ZeroizeOnDrop};

#[derive(Zeroize, ZeroizeOnDrop)]
struct KeyMaterial {
    bytes: Vec<u8>,
}
```

Compiler optimizations might otherwise elide the overwrite (since the memory is about to be freed). `zeroize` uses volatile writes to prevent this.

### Performance

- Zeroize 32 bytes: ~5ns
- Zeroize 4 KiB page: ~500ns
- Runs only on drop, never on access path

---

## Layer 8: Security Monitor

### What it does

Detects anomalous behavior and triggers configured response (logging, alerts, lockout).

### Defends against

- Brute-force decryption attempts (failed decrypts trigger alerts)
- Anomalous access patterns (unusual caller, unusual time, unusual frequency)
- Slow data exfiltration (sustained access from one source)

### Does NOT defend against

- One-shot successful attacks
- Attacks below detection thresholds
- Attacks that defeat the monitor itself

### Implementation

```rust
pub trait SecurityMonitor: Send + Sync {
    /// Called when a decryption attempt fails (wrong key, tampered data, etc.)
    fn on_decryption_failure(&self, ctx: &FailureContext);

    /// Called when access patterns look anomalous
    fn on_anomalous_access(&self, ctx: &AccessContext);

    /// Called when a threshold is breached (N failures in M seconds)
    fn on_threshold_breach(&self, ctx: &ThresholdContext);
}
```

Built-in implementations:

- `NoMonitor` — disabled (default)
- `LogMonitor` — logs events via `log-io` or `tracing`
- `MetricsMonitor` — increments counters in `metrics-lib`
- `WebhookMonitor` — POSTs to alert endpoint
- `CompositeMonitor` — chains multiple

### Thresholds

Configurable per vault:

```rust
VaultConfig {
    max_failures_before_alert: 5,          // After 5 failures, alert
    max_failures_before_lockout: 10,       // After 10 failures, lock vault
    failure_window_seconds: 60,            // Window for counting
    anomaly_detection_enabled: true,
}
```

### Performance

- Success path (no failure): zero cost
- Failure path: cost of the monitor call (typically <100µs for logging)

### Hive DB pattern

Hive DB plugs in its own monitor that:
1. Triggers admin alerts via webhook
2. Locks the affected vault
3. Optionally takes the database offline pending investigation

---

## Layer 9: Audit Logging

### What it does

Records every key access with context: who, what, when, where. Provides forensic trail for incident response.

### Defends against

- Undetected long-term access (everything is logged)
- Insider threats (anomalies surface in audit logs)
- Compliance violations (HIPAA, SOC 2, PCI-DSS require audit trails)

### Does NOT defend against

- Attacks that complete before logs are reviewed
- Attacks that modify the audit log (use `audit-trail` with hash chaining)

### Implementation

Every successful key access produces an audit event:

```rust
AuditEvent {
    timestamp: SystemTime,
    vault_id: VaultId,
    key_id: KeyId,
    operation: AccessKind,        // Fetch, Defrag, Rotate, ...
    caller: CallerContext,         // Module, function, file:line
    thread_id: ThreadId,
    success: bool,
    metadata: HashMap<String, Value>,
}
```

Routed through `log-io` if available, or a user-provided sink.

### Performance

- Per-access cost: ~50-100ns
- Async log shipping (out of hot path)
- Feature-gated via `audit` flag

---

## Bonus Layer 10: Page Protection Toggling

### What it does

Memory pages holding fragments are set to `PROT_NONE` (no read/write access) when not actively being read. Reset to `PROT_READ` briefly during reassembly. Returned to `PROT_NONE` after.

### Defends against

- Snapshot attacks (memory dump at an instant — sees only inaccessible pages)
- Page-cache-only attacks (pages can't be read directly)

### Does NOT defend against

- Attackers who can call mprotect themselves
- Hardware DMA attacks

### Implementation

- Linux/macOS: `mprotect(addr, len, PROT_NONE)` / `mprotect(addr, len, PROT_READ)`
- Windows: `VirtualProtect(addr, size, PAGE_NOACCESS)` / `VirtualProtect(addr, size, PAGE_READONLY)`

### Performance

- Mprotect cost: ~1-2µs per call
- Per-access overhead: ~2-4µs (toggle on + toggle off)

**Significant performance impact.** Available as opt-in feature for highest-security use cases. Not recommended for hot paths.

---

## Defense Composition

How layers compose:

```
                     ATTACKER WITH MEMORY READ ACCESS
                       Layer 10: PROT_NONE
                          (can't read pages)
                       Layer 2: mlock
                          (can't read from swap)
                  Layer 3+4: Fragments + Decoy
                  (can't tell real from filler)
                       Layer 5: Codex
                  (bytes are transformed)
                                  X
                          ATTACKER STOPPED
```

For an attacker to succeed, they must:

1. Bypass Layer 10 (page protection) — requires code execution OR
2. Bypass Layer 2 (mlock) — requires kernel access AND
3. Defeat Layer 3+4 (fragmentation + decoy) — requires reverse engineering of strategies AND
4. Reverse Layer 5 (codex) — requires knowing the transformation table

Each is a meaningful step. Together, they're exponentially harder than any single one.

---

## Threat Model

`key-vault` is designed for these threats:

| Threat | Defense |
|--------|---------|
| Process memory scraper | Layers 2, 3, 4, 5, 10 |
| Swap file forensics | Layer 2 |
| Hibernation file analysis | Layer 2 |
| Timing side-channels | Layer 6 |
| Memory persistence | Layer 7 |
| Brute-force | Layer 8 |
| Insider threats | Layer 9 |
| Sustained exfiltration | Layers 8, 9 |
| Reverse engineering | Layers 5, 10 |

`key-vault` is NOT designed for these threats:

- Kernel-level rootkit with full memory access (use TEE if available)
- Hardware DMA attacks (use IOMMU + hardware mitigations)
- Cold-boot attacks (use full disk encryption + power-down protocol)
- Side-channel attacks on crypto operations (that's `crypt-io`'s job)
- Quantum computer attacks on asymmetric crypto (use post-quantum algorithms)

---

## Performance Cost Summary

Cumulative cost on the **hot path** (key access via defragmentation):

| Layer | Cost | Cumulative |
|-------|------|------------|
| Layer 3: Defragment | 50-200ns | 200ns |
| Layer 5: Codex (optional) | 80-160ns | 360ns |
| Layer 6: Constant-time | 10-20ns | 380ns |
| Layer 9: Audit (optional) | 50-100ns | 480ns |
| **Total (full stack)** | | **~500ns** |

For a 32-byte (256-bit) key access. Well within the sub-microsecond target.

On the **startup path** (one-time):

| Layer | Cost |
|-------|------|
| Layer 1: Acquisition | 1-100ms |
| Layer 2: mlock setup | 10-100µs |
| Layer 3: Fragmentation | 1-10µs |
| Layer 4: Decoy generation | 1-10µs |
| Layer 5: Codex init | 1-5µs |
| **Total** | **~1-100ms** |

Acceptable for application startup.

---

## Configuration

```toml
# .vault.toml (or via builder API)

[vault]
key_normalization = "blake3"        # Hash input to fixed-size
frag_min = 1                         # Min fragment length
frag_max = 4                         # Max fragment length
frag_level = 2                       # Strength level (1-5)
frag_len = 256                       # Target output length
frag_symbols = "#%$&"                # Symbol set for decoy bytes

[security]
mlock_enabled = true                 # Layer 2
fragment_strategy = "standard"       # Layer 3
decoy_strategy = "self-reference"    # Layer 4
codex_enabled = false                # Layer 5 (opt-in)
constant_time = true                 # Layer 6
zeroize_on_drop = true               # Layer 7
monitor = "log+webhook"              # Layer 8
audit_enabled = true                 # Layer 9
page_protection = false              # Layer 10 (opt-in, perf impact)

[monitor]
max_failures_before_alert = 5
max_failures_before_lockout = 10
failure_window_seconds = 60
```

---

## Verification methodology (shipped in 0.11.0)

The 1.0 contract makes specific security claims. Each is backed by a
runnable verification:

| Claim | Verification | Location |
|-------|--------------|----------|
| Fragment round-trip is identity across arbitrary inputs | `proptest` sweeps over `StandardFragmenter`, `InterleavedFragmenter`, `RandomFragmenter`, plus the `with_chunk_range` variant | `tests/proptest_invariants.rs` |
| Every codex implementation satisfies `decode(encode(b)) == b` for every byte | `proptest` full byte-range sweep for `IdentityCodex`, `StaticCodex`, `DynamicCodex` | `tests/proptest_invariants.rs` |
| `SelfReferenceDecoy` only emits bytes drawn from the source key | `proptest` (cross-platform) + dedicated fuzz target | `tests/proptest_invariants.rs`, `fuzz/fuzz_targets/fuzz_decoy_strategies.rs` |
| `KeyHandle::Debug` never reveals the internal id | `proptest` regex assertion + unit-test sweep over 1024 handles | `tests/proptest_invariants.rs`, `src/handle.rs` |
| Concurrent readers never observe a torn read across rotation | 4-thread reader loop vs. 50 rotations, asserting byte uniformity | `tests/proptest_invariants.rs` |
| Layer 2: `mlock` actually pins pages on Linux | `/proc/self/status` `VmLck` delta after 64 registrations | `tests/mlock_verified.rs` (cfg = `target_os = "linux"`) |
| No panics / infinite loops / OOMs on arbitrary inputs | `cargo-fuzz` targets, one per fetcher / fragment strategy / decoy strategy / codex / vault end-to-end | `fuzz/fuzz_targets/*.rs` |
| Hot-path allocation profile | `dhat`-based example binary measuring `with_key` × 100,000 | `examples/dhat_hot_path.rs` |
| Constant-time `KeyHandle` equality | Built on `subtle::ConstantTimeEq` — same primitive `ring` / `aws-lc-rs` use | `src/handle.rs` (the dependency does the heavy lifting; we apply it) |

### Running the verifications

```bash
# All cross-platform property tests run as part of the normal gate.
cargo test --all-features

# Linux-only mlock verification (skipped on macOS/Windows by cfg gate).
cargo test --all-features --test mlock_verified

# Hot-path allocation profile.
cargo run --release --example dhat_hot_path
# → produces dhat-heap.json

# cargo-fuzz targets (Linux/macOS; nightly Rust).
cd fuzz
cargo +nightly fuzz run fuzz_fragment_standard -- -max_total_time=3600
# Repeat per target. Roadmap requires 1 CPU-hour per target for 1.0 sign-off.
```

### What's not yet verified by automation

- **Constant-time property of the full `with_key` call** (not just
  `KeyHandle::eq`) — `dudect` is the gold standard; deferred to a
  post-0.11 follow-up because dudect's runtime hookup is intrusive.
- **`zeroize` actually overwrites memory after drop** — the `zeroize`
  crate's own tests cover this for the primitive; an end-to-end test
  reading freed memory through raw pointers requires `unsafe` outside
  the crate's safety contract.
- **Cold-boot resistance** — out of scope, requires hardware testing.

---

## When to Disable Layers

There are legitimate reasons to disable layers:

| Layer | Reason to disable | Risk |
|-------|------|------|
| mlock | Memory-constrained system | Keys may swap to disk |
| Codex | Maximum performance needed | One less obfuscation layer |
| Monitor | Embedded system without log infrastructure | No anomaly detection |
| Audit | Maximum performance needed | No forensic trail |
| Page protection | Latency-sensitive hot path | Memory readable when active |

Document the trade-off when disabling any layer. Never disable layers 1, 3, 4, 6, 7 — these have minimal performance cost and high security value.

---

<sub>key-vault Security Architecture - Copyright (c) 2026 James Gober. Apache-2.0 OR MIT.</sub>