Files
alknet/docs/architecture/decisions/038-seed-lifecycle-memory-security.md
glm-5.1 04e969982e feat(secret): add alknet-secret crate and architecture spec for Phase 3
Create the alknet-secret crate with BIP39 mnemonic generation, SLIP-0010
Ed25519 HD key derivation, AES-256-GCM encryption, and SecretProtocol
irpc service definition. This is Phase 3.1 from the integration plan.

Architecture changes:
- Promote secret-service.md to reviewed status with full spec format
  (crate structure, public API, security model, phase progression,
   ADR/OQ cross-references, wire format compatibility section)
- Add ADR-038 (seed lifecycle and memory security): zeroize for v1,
  mlock deferred to Phase B
- Add OQ-SEC-01 (mlock/VirtualLock for seed RAM) to open-questions.md
- Update README.md with ADR-038 and secret-service status

Crate structure:
- src/mnemonic.rs: BIP39 phrase generation, validation, seed derivation
- src/derivation.rs: SLIP-0010 HD key derivation, path constants (74')
- src/encryption.rs: AES-256-GCM encrypt/decrypt, EncryptedData type
- src/protocol.rs: SecretProtocol irpc enum, DerivedKey, KeyType
- src/service.rs: SecretServiceHandle with Unlock/Lock lifecycle
- 40 passing tests (unit + integration + doc)
2026-06-09 13:49:53 +00:00

137 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-038: Seed Lifecycle and Memory Security
## Status
Accepted
## Context
The alknet-secret crate holds the master BIP39 seed phrase in RAM. This seed is
the root of trust for all derived keys (identity, encryption, signing). If the
seed is leaked — through memory dumps, swap files, or core dumps — an attacker
can derive every key in the system.
Security-conscious key management systems typically employ three defenses:
1. **Zeroize**: Overwrite sensitive memory before deallocating. Prevents
stale-data reads from freed memory.
2. **Memory locking** (`mlock`/`VirtualLock`): Prevent the OS from paging
sensitive RAM to disk. Prevents swap-file leakage.
3. **Constant-time comparison**: Prevent timing side-channels when comparing
keys or tokens.
The question is: which of these should alknet-secret adopt in v1, and which
should be deferred?
## Decision
**Phase 3 (v1): Zeroize only. Defer mlock and constant-time comparison to
Phase B.**
- All sensitive types (seed bytes, derived private keys, passphrase strings)
derive `Zeroize` and implement `Drop` to call `zeroize()` before deallocation.
- The `Lock` operation calls `zeroize()` on the seed and all cached derived
keys, then drops them.
- `mlock`/`VirtualLock` and constant-time comparison are not included in v1.
### Rationale for deferring mlock
1. **Complexity**: `mlock` requires root/CAP_IPC_LOCK on Linux or
`SeLockMemory` on Windows. The crate should work in unprivileged contexts
(development, testing, single-user nodes) without requiring system
configuration changes.
2. **Performance**: `mlock` locks physical pages, which are typically 4KB.
Locking many small buffers wastes physical memory. The seed (64 bytes) and
derived keys (3264 bytes each) are tiny — the real risk is swap-file
leakage, which `zeroize` partially mitigates by wiping before free.
3. **Deployment flexibility**: Production head nodes running as root or with
`CAP_IPC_LOCK` can add `mlock` in Phase B. Development and CLI nodes
shouldn't need it.
4. **Audit surface**: `mlock` introduces platform-specific code paths (Linux
vs macOS vs Windows) that should be audited together, not bolted on
incrementally.
### Rationale for deferring constant-time comparison
The `SecretProtocol` service receives requests over irpc (local mpsc or remote
QUIC). Comparison timing is not observable by callers — they send a message and
wait for a response. The comparison that matters (auth token verification) is
in alknet-core's `IdentityProvider`, not in alknet-secret. Key derivation
results (DerivedKey) are not compared against attacker-controlled input within
this crate.
### Zeroize implementation
```rust
use zeroize::Zeroize;
#[derive(Zeroize)]
#[zeroize(drop)]
struct SeedHolder {
seed: Vec<u8>,
}
#[derive(Zeroize)]
#[zeroize(drop)]
struct DerivedKeyCache {
keys: HashMap<String, Vec<u8>>,
}
```
`#[zeroize(drop)]` ensures that `Drop` calls `zeroize()` on all fields,
overwriting memory before deallocation. This is a compile-time guarantee —
forgetting to zeroize a field is a compile error.
### Lock lifecycle
```
Unlock(passphrase)
→ validate mnemonic (if restoring) or generate new
→ derive master key from seed
→ store seed in SeedHolder (Zeroize-protected)
→ cache empty (keys derived on demand)
DeriveEd25519/DeriveEncryptionKey/Encrypt/Decrypt
→ require unlocked state (error if locked)
→ derive key, return result
→ optionally cache derived key
Lock
→ zeroize all cached derived keys
→ zeroize seed
→ drop all sensitive material
→ service returns to locked state
```
## Consequences
- **Positive**: Zeroize is zero-cost at compile time, minimal dependency
(`zeroize` crate is ~500 lines, no `unsafe` on stable), and provides
meaningful protection against stale-memory reads.
- **Positive**: Lock effectively purges all sensitive material. After Lock,
the process memory contains no useful secret data.
- **Positive**: No platform-specific code paths in v1. The crate compiles and
runs everywhere without privilege requirements.
- **Negative**: Without `mlock`, the OS can page the seed to swap before
zeroization occurs. This is a window of vulnerability that Phase B closes.
The risk is acceptable for v1 because swap-file extraction requires root
access or physical access to the machine — the same threat model as reading
process memory directly.
- **Negative**: Without constant-time comparison, timing side-channels exist
in theory. In practice, no comparison in alknet-secret operates on
attacker-controlled input, so the risk is nil within this crate.
- **Negative**: `zeroize` adds a dependency. The `zeroize` crate is widely
used in Rust crypto (ring, ed25519-dalek, x25519-dalek) and is a de facto
standard.
## References
- [secret-service.md](../secret-service.md) — Security model, Lock/Unlock lifecycle
- [ADR-027](027-crate-decomposition.md) — Crate decomposition (alknet-secret is independent)
- [credentials.md](../credentials.md) — SecretStoreCredentialProvider integration
- `zeroize` crate — https://crates.io/crates/zeroize