- Remove hardcoded dependencies in kvstore Cargo.toml; use features instead. This allows for more flexible compilation for different targets (native vs. WASM). - Improve logging in vault crate using the `log` crate. This makes debugging easier and provides more informative output during execution. Native tests use `env_logger`, WASM tests use `console_log`. - Update README to reflect new logging best practices. - Add cfg attributes to native and wasm modules to improve clarity. - Update traits.rs to specify Send + Sync behavior expectations.
12 KiB
Vault Implementation Plan
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.
1. Architecture Overview
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).
Using Both Stateless and Session-Based APIs
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.
2. 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>;
}
Symmetric Encryption Module
- Derives a master key from password and salt (e.g., PBKDF2 or scrypt).
- Encrypts/decrypts vault data with AES-GCM or ChaCha20Poly1305.
4. Implementation Plan
- Define Data Structures
- VaultData, KeyEntry, KeyType, KeyMetadata, etc.
- Implement Symmetric Encryption
- Password-based key derivation (PBKDF2/scrypt)
- AES-GCM or ChaCha20Poly1305 encryption
- Vault Logic
- open, unlock, encrypt/decrypt, manage keypairs
- persist encrypted blob in kvstore
- KeyPair Management
- Generate, import, export, sign, verify
- Session Management
- Track selected key/context
- Error Handling
- VaultError enum for crypto, storage, and logic errors
- WASM Interop
- Use wasm-bindgen to expose async APIs as JS Promises
- Ensure all crypto crates are WASM-compatible
- Testing
- Native and WASM tests for all APIs
Design Decisions: Old Implementation, Current Plan, Open Questions, and Recommendations
Area | Old Implementation | Current Plan | Decision Left/Open | Recommendation & Rationale |
---|---|---|---|---|
KDF | PBKDF2-HMAC-SHA256 | PBKDF2 or scrypt (WASM-compatible) | Which as default? Both supported? Per-keyspace choice? | Use scrypt as default for new keyspaces (stronger against GPU attacks) |
Symmetric Encryption | ChaCha20Poly1305 | AES-256-GCM or ChaCha20Poly1305 | Which default? Both supported? Per-keyspace choice? | ChaCha20Poly1305 recommended for WASM and cross-platform. |
Key Types | secp256k1, Ed25519 | secp256k1, Ed25519 | Add more? Custom key types? | Keep secp256k1 and Ed25519 as default. |
Metadata Encryption | Unencrypted vault metadata | Unencrypted keyspace metadata | Option to encrypt metadata? | Unencrypted vault metadata for simplicity. |
Session Manager Features | No session manager, manual unlock | Optional session manager | Timeout, auto-lock, secure wipe, multi-user? | Implement optional session manager with timeout and secure memory wipe. |
Password Change/Recovery | Manual re-encrypt, no recovery | API for password change | Re-encrypt all? Recovery/MFA? | Re-encrypt keyspace on password change. |
WASM/Native Crypto | Native only | WASM-compatible crates | Native-only features? | Require WASM compatibility for all core features. |
Keyspace Sharing/Export | Manual export/import, share password | Share keyspace password | Explicit export/import flows? Auditing? | Add explicit export/import APIs. Log/audit sharing if privacy is a concern. |
Multi-user/Access Control | Single password per vault | Single password per keyspace | ACL, threshold unlock? | Single password per keyspace is simplest. |
Metadata/Tagging | Minimal metadata, no tags | Basic metadata, optional tags | Required/custom tags? Usage stats? | Support custom tags and creation date for keyspaces/keys. |
Storage Structure | Single JSON file (vault) | Keyspaces as blobs in vault metadata | Store as separate kvstore records? | Recommend storing each keyspace as a separate record in kvstore for easier backup/sync/restore. |
Error Handling | Basic error codes | VaultError enum | Granular or coarse? WASM/JS exposure? | Define granular error types and expose user-friendly errors for WASM/JS. |
Legend:
- Old Implementation: What was done in the previous (legacy) design.
- Current Plan: What is currently proposed in this implementation plan.
- Decision Left/Open: What remains to be finalized or clarified.
- Recommendation & Rationale: What is recommended for the new implementation and why, especially if it differs from the old approach.
5. File/Module Structure (Recommended)
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
└── ...
6. 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).