sal-modular/docs/vault_impl_plan.md
Sameh Abouelsaad cea2d7e655 feat: Refactor kvstore and vault to use features and logging
- 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.
2025-05-15 16:42:19 +03:00

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

  1. Define Data Structures
    • VaultData, KeyEntry, KeyType, KeyMetadata, etc.
  2. Implement Symmetric Encryption
    • Password-based key derivation (PBKDF2/scrypt)
    • AES-GCM or ChaCha20Poly1305 encryption
  3. Vault Logic
    • open, unlock, encrypt/decrypt, manage keypairs
    • persist encrypted blob in kvstore
  4. KeyPair Management
    • Generate, import, export, sign, verify
  5. Session Management
    • Track selected key/context
  6. Error Handling
    • VaultError enum for crypto, storage, and logic errors
  7. WASM Interop
    • Use wasm-bindgen to expose async APIs as JS Promises
    • Ensure all crypto crates are WASM-compatible
  8. 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.

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-compatible
  • k256: secp256k1 ECDSA (Ethereum keys)
  • ed25519-dalek: Ed25519 keypairs
  • rand_core: Randomness, WASM-compatible
  • getrandom: Platform-agnostic RNG

Algorithm Choices:

  • Vault Encryption:
    • AES-256-GCM (default, via aes-gcm)
    • Optionally ChaCha20Poly1305 (via chacha20poly1305)
  • Password Key Derivation:
    • PBKDF2-HMAC-SHA256 (via pbkdf2)
    • Optionally scrypt (via scrypt)
  • Asymmetric Keypairs:
    • secp256k1 (via k256) for Ethereum/EVM
    • Ed25519 (via ed25519-dalek) for general-purpose signatures
  • Randomness:
    • Use rand_core and getrandom for secure RNG in both native and WASM

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 and docs/kvstore-vault-architecture.md for high-level design and rationale.
  • Crypto patterns inspired by industry best practices (e.g., Wire, Signal, Bitwarden).