9.4 KiB
Vault Implementation Plan (Technical Appendix)
This document is a technical reference for contributors and maintainers of the Vault crate. It covers advanced implementation details, design rationale, and data models. For a high-level overview and usage, see vault.md
and architecture.md
.
Table of Contents
Design Principle: The vault crate will provide both a stateless (context-passing) API and an ergonomic session-based API. This ensures maximum flexibility for both library developers and application builders, supporting both functional and stateful usage patterns.
Design Principle: Stateless & Session APIs
The vault
crate is a modular, async, and WASM-compatible cryptographic keystore. It manages an encrypted keyspace (multiple keypairs), provides cryptographic APIs, and persists all data via the kvstore
trait. The design ensures all sensitive material is encrypted at rest and is portable across native and browser environments.
Core Components:
- Vault: Main manager for encrypted keyspace and cryptographic operations.
- KeyPair: Represents individual asymmetric keypairs (e.g., secp256k1, Ed25519).
- Symmetric Encryption Module: Handles encryption/decryption and key derivation.
- SessionManager (Optional): Maintains current context (e.g., selected keypair) for user sessions.
- KVStore: Async trait for backend-agnostic persistence (sled on native, IndexedDB on WASM).
You can design the vault crate to support both stateless and session-based (stateful) usage patterns. This gives maximum flexibility to both library developers and application builders.
Stateless API
- All operations require explicit context (unlocked keyspace, keypair, etc.) as arguments.
- No hidden or global state; maximally testable and concurrency-friendly.
- Example:
let keyspace = vault.unlock_keyspace("personal", b"password").await?; let signature = keyspace.sign("key1", &msg).await?;
Session Manager API
- Maintains in-memory state of unlocked keyspaces and current selections.
- Provides ergonomic methods for interactive apps (CLI, desktop, browser).
- Example:
let mut session = SessionManager::new(); session.unlock_keyspace("personal", b"password", &vault)?; session.select_keypair("key1"); let signature = session.current_keypair().unwrap().sign(&msg)?; session.logout(); // wipes all secrets from memory
How They Work Together
- The stateless API is the core, always available and used internally by the session manager.
- The session manager is a thin, optional layer that wraps the stateless API for convenience.
- Applications can choose which pattern fits their needs, or even mix both (e.g., use stateless for background jobs, session manager for user sessions).
Benefits
- Flexibility: Library users can pick the best model for their use case.
- Security: Session manager can enforce auto-lock, timeouts, and secure memory wiping.
- Simplicity: Stateless API is easy to test and reason about, while session manager improves UX for interactive flows.
Commitment: Provide Both APIs
- Both stateless and session-based APIs will be provided in the vault crate.
- Stateless API: For backend, automation, or library contexts—explicit, functional, and concurrency-friendly.
- Session manager API: For UI/UX-focused applications—ergonomic, stateful, and user-friendly.
Data Model
VaultMetadata & Keyspace Model
struct VaultMetadata {
name: String,
keyspaces: Vec<KeyspaceMetadata>,
// ... other vault-level metadata (optionally encrypted)
}
struct KeyspaceMetadata {
name: String,
salt: [u8; 16], // Unique salt for this keyspace
encrypted_blob: Vec<u8>, // All keypairs & secrets, encrypted with keyspace password
// ... other keyspace metadata
}
// The decrypted contents of a keyspace:
struct KeyspaceData {
keypairs: Vec<KeyEntry>,
// ... other keyspace-level metadata
}
struct KeyEntry {
id: String,
key_type: KeyType,
private_key: Vec<u8>, // Only present in memory after decryption
public_key: Vec<u8>,
metadata: Option<KeyMetadata>,
}
enum KeyType {
Secp256k1,
Ed25519,
// ...
}
- The vault contains a list of keyspaces, each with its own salt and encrypted blob.
- Each keyspace is unlocked independently using its password and salt.
- Key material is never stored unencrypted; only decrypted in memory after unlocking a keyspace.
3. API Design (Keyspace Model)
Vault
impl<S: KVStore + Send + Sync> Vault<S> {
async fn open(store: S) -> Result<Self, VaultError>;
async fn list_keyspaces(&self) -> Result<Vec<KeyspaceInfo>, VaultError>;
async fn create_keyspace(&mut self, name: &str, password: &[u8]) -> Result<(), VaultError>;
async fn delete_keyspace(&mut self, name: &str) -> Result<(), VaultError>;
async fn unlock_keyspace(&mut self, name: &str, password: &[u8]) -> Result<(), VaultError>;
async fn lock_keyspace(&mut self, name: &str);
// ...
}
Keyspace Management
impl Keyspace {
fn is_unlocked(&self) -> bool;
fn name(&self) -> &str;
async fn create_key(&mut self, key_type: KeyType, name: &str) -> Result<String, VaultError>;
async fn list_keys(&self) -> Result<Vec<KeyInfo>, VaultError>;
async fn sign(&self, key_id: &str, msg: &[u8]) -> Result<Signature, VaultError>;
async fn encrypt(&self, key_id: &str, plaintext: &[u8]) -> Result<Ciphertext, VaultError>;
async fn decrypt(&self, key_id: &str, ciphertext: &[u8]) -> Result<Vec<u8>, VaultError>;
async fn change_password(&mut self, old: &[u8], new: &[u8]) -> Result<(), VaultError>;
// ...
}
SessionManager
impl SessionManager {
fn select_key(&mut self, key_id: &str);
fn current_key(&self) -> Option<&KeyPair>;
}
vault/
├── src/
│ ├── lib.rs # Vault API and main logic
│ ├── data.rs # Data models: VaultData, KeyEntry, etc.
│ ├── crypto.rs # Symmetric/asymmetric crypto, key derivation
│ ├── session.rs # SessionManager
│ ├── error.rs # VaultError and error handling
│ └── utils.rs # Helpers, serialization, etc.
├── tests/
│ ├── native.rs # Native (sled) tests
│ └── wasm.rs # WASM (IndexedDB) tests
└── ...
Advanced Notes
- For further context on cryptographic choices, async patterns, and WASM compatibility, see
architecture.md
. - This appendix is intended for developers extending or maintaining the Vault implementation.
Cryptography: Crates and Algorithms
Crates:
aes-gcm
: AES-GCM authenticated encryption (WASM-compatible)chacha20poly1305
: ChaCha20Poly1305 authenticated encryption (WASM-compatible)pbkdf2
: Password-based key derivation (WASM-compatible)scrypt
: Alternative KDF, strong and WASM-compatiblek256
: secp256k1 ECDSA (Ethereum keys)ed25519-dalek
: Ed25519 keypairsrand_core
: Randomness, WASM-compatiblegetrandom
: Platform-agnostic RNG
Algorithm Choices:
- Vault Encryption:
- AES-256-GCM (default, via
aes-gcm
) - Optionally ChaCha20Poly1305 (via
chacha20poly1305
)
- AES-256-GCM (default, via
- Password Key Derivation:
- PBKDF2-HMAC-SHA256 (via
pbkdf2
) - Optionally scrypt (via
scrypt
)
- PBKDF2-HMAC-SHA256 (via
- Asymmetric Keypairs:
- secp256k1 (via
k256
) for Ethereum/EVM - Ed25519 (via
ed25519-dalek
) for general-purpose signatures
- secp256k1 (via
- Randomness:
- Use
rand_core
andgetrandom
for secure RNG in both native and WASM
- Use
Feature-to-Algorithm Mapping:
Feature | Crate(s) | Algorithm(s) |
---|---|---|
Vault encryption | aes-gcm, chacha20poly1305 | AES-256-GCM, ChaCha20Poly1305 |
Password KDF | pbkdf2, scrypt | PBKDF2-HMAC-SHA256, scrypt |
Symmetric encryption | aes-gcm, chacha20poly1305 | AES-256-GCM, ChaCha20Poly1305 |
secp256k1 keypairs | k256 | secp256k1 ECDSA |
Ed25519 keypairs | ed25519-dalek | Ed25519 |
Randomness | rand_core, getrandom | OS RNG |
7. WASM & Native Considerations
- Use only WASM-compatible crypto crates (
aes-gcm
,chacha20poly1305
,k256
,ed25519-dalek
, etc). - Use
wasm-bindgen
/wasm-bindgen-futures
for browser interop. - Use
tokio::task::spawn_blocking
for blocking crypto on native. - All APIs are async and runtime-agnostic.
6. Future Extensions
- Multi-user vaults (multi-password, access control)
- Hardware-backed key storage (YubiKey, WebAuthn)
- Key rotation and auditing
- Pluggable crypto algorithms
- Advanced metadata and tagging
7. References
- See
docs/Architecture.md
anddocs/kvstore-vault-architecture.md
for high-level design and rationale. - Crypto patterns inspired by industry best practices (e.g., Wire, Signal, Bitwarden).