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.
This commit is contained in:
parent
7d7f94f114
commit
cea2d7e655
85
docs/native-wasm-build-plan.md
Normal file
85
docs/native-wasm-build-plan.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Plan: Ensuring Native and WASM Builds Work for the Vault/KVStore System
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
This document outlines the steps and requirements to guarantee that both native (desktop/server) and WASM (browser) builds of the `vault` and `kvstore` crates work seamlessly, securely, and efficiently.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Architecture Principles
|
||||||
|
- **Async/Await Everywhere:** All APIs must be async and runtime-agnostic (no Tokio requirement in library code).
|
||||||
|
- **KVStore Trait:** Use an async trait for storage, with platform-specific implementations (sled for native, IndexedDB/idb for WASM).
|
||||||
|
- **Conditional Compilation:** Use `#[cfg(target_arch = "wasm32")]` and `#[cfg(not(target_arch = "wasm32"))]` to select code and dependencies.
|
||||||
|
- **No Blocking in WASM:** All I/O and crypto operations must be async and non-blocking in browser builds.
|
||||||
|
- **WASM-Compatible Crypto:** Only use crypto crates that compile to WASM (e.g., `aes-gcm`, `chacha20poly1305`, `k256`, `rand_core`).
|
||||||
|
- **Separation of Concerns:** All encryption and password logic resides in `vault`, not `kvstore`.
|
||||||
|
- **Stateless and Session APIs:** Provide both stateless (context-passing) and session-based APIs in `vault`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Cargo.toml and Dependency Management
|
||||||
|
- **Native:**
|
||||||
|
- `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]`
|
||||||
|
- `tokio` (with only supported features)
|
||||||
|
- `sled`
|
||||||
|
- **WASM:**
|
||||||
|
- `[target.'cfg(target_arch = "wasm32")'.dependencies]`
|
||||||
|
- `idb`
|
||||||
|
- `wasm-bindgen`, `wasm-bindgen-futures`
|
||||||
|
- **Crypto:**
|
||||||
|
- Only include crates that are WASM-compatible for both targets.
|
||||||
|
- **No unconditional `tokio`** in `vault` or `kvstore`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Code Organization
|
||||||
|
- **KVStore Trait:**
|
||||||
|
- Define as async trait (using `async_trait`).
|
||||||
|
- Implement for sled (native) and idb (WASM), using `#[cfg]`.
|
||||||
|
- **Vault:**
|
||||||
|
- All persistence must go through the KVStore trait.
|
||||||
|
- All cryptography must be WASM-compatible.
|
||||||
|
- No direct file or blocking I/O in WASM.
|
||||||
|
- **Runtime:**
|
||||||
|
- Only use `tokio` in binaries or native-specific code.
|
||||||
|
- In WASM, use `wasm-bindgen-futures::spawn_local` for async tasks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Platform-Specific Guidelines
|
||||||
|
- **Native (Desktop/Server):**
|
||||||
|
- Use `sled` for storage.
|
||||||
|
- Use `tokio::task::spawn_blocking` for blocking I/O if needed.
|
||||||
|
- All async code should work with any runtime.
|
||||||
|
- **WASM (Browser):**
|
||||||
|
- Use `idb` crate for IndexedDB storage.
|
||||||
|
- All code must be non-blocking and compatible with the browser event loop.
|
||||||
|
- Use `wasm-bindgen` and `wasm-bindgen-futures` for JS interop and async.
|
||||||
|
- Expose APIs with `#[wasm_bindgen]` for JS usage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Testing
|
||||||
|
- **Native:** `cargo test`
|
||||||
|
- **WASM:** `cargo test --target wasm32-unknown-unknown --release` (or use `wasm-pack test`)
|
||||||
|
- **Separate tests** for native and WASM backends in `tests/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Checklist for Compliance
|
||||||
|
- [ ] No unconditional `tokio` usage in library code
|
||||||
|
- [ ] All dependencies are WASM-compatible (where needed)
|
||||||
|
- [ ] All storage goes through async KVStore trait
|
||||||
|
- [ ] No blocking I/O or native-only APIs in WASM
|
||||||
|
- [ ] All cryptography is WASM-compatible
|
||||||
|
- [ ] Both stateless and session APIs are available in `vault`
|
||||||
|
- [ ] All APIs are async and runtime-agnostic
|
||||||
|
- [ ] Native and WASM tests both pass
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. References
|
||||||
|
- See `docs/Architecture.md`, `docs/kvstore-vault-architecture.md`, and `docs/vault_impl_plan.md` for architectural background and rationale.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
By following this plan, the codebase will be robust, portable, and secure on both native and browser platforms, and will adhere to all project architectural guidelines.
|
164
docs/vault.md
Normal file
164
docs/vault.md
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
🧱 Vault Crate Architecture
|
||||||
|
1. VaultStore
|
||||||
|
Purpose: Central manager for all keyspaces.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
Maintain metadata about keyspaces.
|
||||||
|
|
||||||
|
Provide interfaces to create, load, and manage keyspaces.
|
||||||
|
|
||||||
|
Ensure each keyspace is encrypted with its own password.
|
||||||
|
|
||||||
|
2. KeySpace
|
||||||
|
Purpose: Isolated environment containing multiple keypairs.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
Securely store and manage keypairs.
|
||||||
|
|
||||||
|
Provide cryptographic operations like signing and encryption.
|
||||||
|
|
||||||
|
Handle encryption/decryption using a password-derived key.
|
||||||
|
|
||||||
|
3. KeyPair
|
||||||
|
Purpose: Represents an individual cryptographic keypair.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
Perform cryptographic operations such as signing and verification.
|
||||||
|
|
||||||
|
Support key export and import functionalities.
|
||||||
|
|
||||||
|
4. Symmetric Encryption Module
|
||||||
|
Purpose: Provides encryption and decryption functionalities.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
Encrypt and decrypt data using algorithms like ChaCha20Poly1305.
|
||||||
|
|
||||||
|
Derive encryption keys from passwords using PBKDF2.
|
||||||
|
|
||||||
|
5. SessionManager
|
||||||
|
Purpose: Manages the active context for cryptographic operations, simplifying API usage.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
|
||||||
|
Maintain the currently selected keypair.
|
||||||
|
|
||||||
|
Provide simplified methods for cryptographic operations without repeatedly specifying the keypair.
|
||||||
|
|
||||||
|
🔐 Security Model
|
||||||
|
Per-KeySpace Encryption: Each keyspace is encrypted independently using a key derived from its password.
|
||||||
|
|
||||||
|
VaultStore Metadata: Stores non-sensitive metadata about keyspaces, such as their names and creation dates. This metadata can be stored in plaintext or encrypted based on security requirements.
|
||||||
|
|
||||||
|
🧪 API Design
|
||||||
|
VaultStore
|
||||||
|
rust
|
||||||
|
Copy
|
||||||
|
Edit
|
||||||
|
pub struct VaultStore {
|
||||||
|
// Internal fields
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VaultStore {
|
||||||
|
pub fn new() -> Self;
|
||||||
|
pub fn load() -> Result<Self, VaultError>;
|
||||||
|
pub fn save(&self) -> Result<(), VaultError>;
|
||||||
|
|
||||||
|
pub fn list_keyspaces(&self) -> Vec<KeyspaceMetadata>;
|
||||||
|
pub fn create_keyspace(&mut self, name: &str, password: &str) -> Result<(), VaultError>;
|
||||||
|
pub fn delete_keyspace(&mut self, name: &str) -> Result<(), VaultError>;
|
||||||
|
pub fn rename_keyspace(&mut self, old_name: &str, new_name: &str) -> Result<(), VaultError>;
|
||||||
|
pub fn load_keyspace(&self, name: &str, password: &str) -> Result<KeySpace, VaultError>;
|
||||||
|
}
|
||||||
|
KeySpace
|
||||||
|
rust
|
||||||
|
Copy
|
||||||
|
Edit
|
||||||
|
pub struct KeySpace {
|
||||||
|
// Internal fields
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeySpace {
|
||||||
|
pub fn new(name: &str, password: &str) -> Result<Self, VaultError>;
|
||||||
|
pub fn save(&self) -> Result<(), VaultError>;
|
||||||
|
|
||||||
|
pub fn list_keypairs(&self) -> Vec<String>;
|
||||||
|
pub fn create_keypair(&mut self, name: &str) -> Result<(), VaultError>;
|
||||||
|
pub fn delete_keypair(&mut self, name: &str) -> Result<(), VaultError>;
|
||||||
|
pub fn rename_keypair(&mut self, old_name: &str, new_name: &str) -> Result<(), VaultError>;
|
||||||
|
pub fn get_keypair(&self, name: &str) -> Result<KeyPair, VaultError>;
|
||||||
|
|
||||||
|
pub fn sign(&self, keypair_name: &str, message: &[u8]) -> Result<Vec<u8>, VaultError>;
|
||||||
|
pub fn verify(&self, keypair_name: &str, message: &[u8], signature: &[u8]) -> Result<bool, VaultError>;
|
||||||
|
}
|
||||||
|
KeyPair
|
||||||
|
rust
|
||||||
|
Copy
|
||||||
|
Edit
|
||||||
|
pub struct KeyPair {
|
||||||
|
// Internal fields
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyPair {
|
||||||
|
pub fn new() -> Self;
|
||||||
|
pub fn from_private_key(private_key: &[u8]) -> Result<Self, VaultError>;
|
||||||
|
|
||||||
|
pub fn public_key(&self) -> Vec<u8>;
|
||||||
|
pub fn private_key(&self) -> Vec<u8>;
|
||||||
|
|
||||||
|
pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, VaultError>;
|
||||||
|
pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, VaultError>;
|
||||||
|
}
|
||||||
|
SessionManager (Optional)
|
||||||
|
rust
|
||||||
|
Copy
|
||||||
|
Edit
|
||||||
|
pub struct SessionManager {
|
||||||
|
keyspace: KeySpace,
|
||||||
|
active_keypair: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionManager {
|
||||||
|
pub fn new(keyspace: KeySpace, keypair_name: &str) -> Result<Self, VaultError>;
|
||||||
|
pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, VaultError>;
|
||||||
|
pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, VaultError>;
|
||||||
|
pub fn switch_keypair(&mut self, keypair_name: &str) -> Result<(), VaultError>;
|
||||||
|
}
|
||||||
|
📦 Storage Structure
|
||||||
|
Copy
|
||||||
|
Edit
|
||||||
|
vault_store/
|
||||||
|
├── metadata.json
|
||||||
|
└── keyspaces/
|
||||||
|
├── alice.ksp
|
||||||
|
├── bob.ksp
|
||||||
|
└── ...
|
||||||
|
metadata.json: Contains metadata about each keyspace, such as name and creation date.
|
||||||
|
|
||||||
|
keyspaces/: Directory containing encrypted keyspace files.
|
||||||
|
|
||||||
|
🔄 Integration with kvstore
|
||||||
|
Native Environment
|
||||||
|
Storage Backend: Utilize the local filesystem or a persistent database (e.g., SQLite) for storing VaultStore and KeySpace data.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
Initialize VaultStore and load existing keyspaces.
|
||||||
|
|
||||||
|
Perform cryptographic operations using KeySpace and KeyPair.
|
||||||
|
|
||||||
|
Persist changes to disk or database.
|
||||||
|
|
||||||
|
Browser Environment (WASM)
|
||||||
|
Storage Backend: Use browser storage APIs like localStorage or IndexedDB for persisting data.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
Compile the vault crate to WebAssembly.
|
||||||
|
|
||||||
|
Interact with the vault API through JavaScript bindings.
|
||||||
|
|
||||||
|
Store and retrieve encrypted keyspaces using browser storage.
|
276
docs/vault_impl_plan.md
Normal file
276
docs/vault_impl_plan.md
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
# 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:
|
||||||
|
```rust
|
||||||
|
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:
|
||||||
|
```rust
|
||||||
|
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
|
||||||
|
```rust
|
||||||
|
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
|
||||||
|
```rust
|
||||||
|
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
|
||||||
|
```rust
|
||||||
|
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
|
||||||
|
```rust
|
||||||
|
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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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`](https://crates.io/crates/aes-gcm): AES-GCM authenticated encryption (WASM-compatible)
|
||||||
|
- [`chacha20poly1305`](https://crates.io/crates/chacha20poly1305): ChaCha20Poly1305 authenticated encryption (WASM-compatible)
|
||||||
|
- [`pbkdf2`](https://crates.io/crates/pbkdf2): Password-based key derivation (WASM-compatible)
|
||||||
|
- [`scrypt`](https://crates.io/crates/scrypt): Alternative KDF, strong and WASM-compatible
|
||||||
|
- [`k256`](https://crates.io/crates/k256): secp256k1 ECDSA (Ethereum keys)
|
||||||
|
- [`ed25519-dalek`](https://crates.io/crates/ed25519-dalek): Ed25519 keypairs
|
||||||
|
- [`rand_core`](https://crates.io/crates/rand_core): Randomness, WASM-compatible
|
||||||
|
- [`getrandom`](https://crates.io/crates/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).
|
@ -8,24 +8,21 @@ path = "src/lib.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
sled = { version = "0.34", optional = true }
|
|
||||||
idb = { version = "0.4", optional = true }
|
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
||||||
[features]
|
|
||||||
default = []
|
|
||||||
native = ["sled", "tokio"]
|
|
||||||
web = ["idb"]
|
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
tokio = { version = "1.45", optional = true, default-features = false, features = ["rt-multi-thread", "macros"] }
|
sled = { version = "0.34" }
|
||||||
|
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
idb = "0.4"
|
idb = { version = "0.4" }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
|
||||||
wasm-bindgen-test = "0.3"
|
wasm-bindgen-test = "0.3"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
native = []
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! # Runtime Requirement
|
//! # Runtime Requirement
|
||||||
//!
|
//!
|
||||||
|
#![cfg(not(target_arch = "wasm32"))]
|
||||||
//! **A Tokio runtime must be running to use this backend.**
|
//! **A Tokio runtime must be running to use this backend.**
|
||||||
//! This library does not start or manage a runtime; it assumes that all async methods are called from within an existing Tokio runtime context (e.g., via `#[tokio::main]` or `tokio::test`).
|
//! This library does not start or manage a runtime; it assumes that all async methods are called from within an existing Tokio runtime context (e.g., via `#[tokio::main]` or `tokio::test`).
|
||||||
//!
|
//!
|
||||||
@ -10,11 +11,18 @@
|
|||||||
//! # Example
|
//! # Example
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use crate::traits::KVStore;
|
//! Native backend for kvstore using sled
|
||||||
use crate::error::{KVError, Result};
|
//! Only compiled for non-wasm32 targets
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use crate::traits::KVStore;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use crate::error::{KVError, Result};
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use sled::Db;
|
use sled::Db;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -16,6 +16,11 @@ use crate::error::Result;
|
|||||||
/// - contains_key (was exists)
|
/// - contains_key (was exists)
|
||||||
/// - keys
|
/// - keys
|
||||||
/// - clear
|
/// - clear
|
||||||
|
/// Async key-value store interface for both native and WASM backends.
|
||||||
|
///
|
||||||
|
/// For native (non-wasm32) backends, implementers should be `Send + Sync` to support async usage.
|
||||||
|
/// For WASM (wasm32) backends, `Send + Sync` is not required.
|
||||||
|
#[async_trait::async_trait]
|
||||||
pub trait KVStore {
|
pub trait KVStore {
|
||||||
async fn get(&self, key: &str) -> Result<Option<Vec<u8>>>;
|
async fn get(&self, key: &str) -> Result<Option<Vec<u8>>>;
|
||||||
async fn set(&self, key: &str, value: &[u8]) -> Result<()>;
|
async fn set(&self, key: &str, value: &[u8]) -> Result<()>;
|
||||||
|
@ -13,12 +13,15 @@
|
|||||||
//!
|
//!
|
||||||
|
|
||||||
|
|
||||||
|
//! WASM backend for kvstore using IndexedDB (idb crate)
|
||||||
|
//! Only compiled for wasm32 targets
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
use crate::traits::KVStore;
|
use crate::traits::KVStore;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
use crate::error::{KVError, Result};
|
use crate::error::{KVError, Result};
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use idb::{Database, TransactionMode, Factory};
|
use idb::{Database, TransactionMode, Factory};
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
@ -18,7 +18,10 @@ chacha20poly1305 = "0.10"
|
|||||||
k256 = { version = "0.13", features = ["ecdsa"] }
|
k256 = { version = "0.13", features = ["ecdsa"] }
|
||||||
ed25519-dalek = "2.1"
|
ed25519-dalek = "2.1"
|
||||||
rand_core = "0.6"
|
rand_core = "0.6"
|
||||||
|
log = "0.4"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
env_logger = "0.11"
|
||||||
|
console_log = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
@ -12,22 +12,20 @@
|
|||||||
|
|
||||||
## Logging Best Practices
|
## Logging Best Practices
|
||||||
|
|
||||||
This crate uses the [`log`](https://docs.rs/log) crate for all logging. To see logs in your application or tests, you must initialize a logger:
|
This crate uses the [`log`](https://docs.rs/log) crate for logging. For native tests, use [`env_logger`](https://docs.rs/env_logger); for WASM tests, use [`console_log`](https://docs.rs/console_log).
|
||||||
|
|
||||||
- **Native (desktop/server):**
|
- Native (in tests):
|
||||||
- Add `env_logger` as a dev-dependency.
|
```rust
|
||||||
- Initialize in your main or test:
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
```rust
|
log::info!("test started");
|
||||||
let _ = env_logger::builder().is_test(true).try_init();
|
```
|
||||||
```
|
- WASM (in tests):
|
||||||
- **WASM (browser):**
|
```rust
|
||||||
- Add `console_log` as a dev-dependency.
|
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
||||||
- Initialize in your main or test:
|
log::debug!("wasm test started");
|
||||||
```rust
|
```
|
||||||
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
|
||||||
```
|
|
||||||
|
|
||||||
Then use logging macros (`log::debug!`, `log::info!`, `log::warn!`, `log::error!`) throughout your code and tests.
|
Use `log::debug!`, `log::info!`, `log::error!`, etc., throughout the codebase for consistent and idiomatic logging. Do not prefix messages with [DEBUG], [ERROR], etc. The log level is handled by the logger.
|
||||||
|
|
||||||
## Usage Example
|
## Usage Example
|
||||||
|
|
||||||
|
157
vault/src/lib.rs
157
vault/src/lib.rs
@ -18,22 +18,7 @@ use crate::crypto::random_salt;
|
|||||||
use crate::crypto::cipher::{encrypt_chacha20, decrypt_chacha20, encrypt_aes_gcm, decrypt_aes_gcm};
|
use crate::crypto::cipher::{encrypt_chacha20, decrypt_chacha20, encrypt_aes_gcm, decrypt_aes_gcm};
|
||||||
use signature::SignatureEncoding;
|
use signature::SignatureEncoding;
|
||||||
// TEMP: File-based debug logger for crypto troubleshooting
|
// TEMP: File-based debug logger for crypto troubleshooting
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
use log::{debug, info, error};
|
||||||
fn debug_log(msg: &str) {
|
|
||||||
use std::fs::OpenOptions;
|
|
||||||
use std::io::Write;
|
|
||||||
let mut f = OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open("/tmp/vault_crypto_debug.log")
|
|
||||||
.unwrap();
|
|
||||||
writeln!(f, "{}", msg).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
fn debug_log(_msg: &str) {
|
|
||||||
// No-op in WASM
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Vault: Cryptographic keyspace and operations
|
/// Vault: Cryptographic keyspace and operations
|
||||||
pub struct Vault<S: KVStore> {
|
pub struct Vault<S: KVStore> {
|
||||||
@ -46,30 +31,30 @@ fn encrypt_with_nonce_prepended(key: &[u8], plaintext: &[u8], cipher: &str) -> R
|
|||||||
use crate::crypto::random_salt;
|
use crate::crypto::random_salt;
|
||||||
use crate::crypto;
|
use crate::crypto;
|
||||||
let nonce = random_salt(12);
|
let nonce = random_salt(12);
|
||||||
debug_log(&format!("[DEBUG][ENCRYPT_HELPER] nonce: {}", hex::encode(&nonce)));
|
debug!("nonce: {}", hex::encode(&nonce));
|
||||||
let (ct, _key_hex) = match cipher {
|
let (ct, _key_hex) = match cipher {
|
||||||
"chacha20poly1305" => {
|
"chacha20poly1305" => {
|
||||||
let ct = encrypt_chacha20(key, plaintext, &nonce)
|
let ct = encrypt_chacha20(key, plaintext, &nonce)
|
||||||
.map_err(|e| VaultError::Crypto(e))?;
|
.map_err(|e| VaultError::Crypto(e))?;
|
||||||
debug_log(&format!("[DEBUG][ENCRYPT_HELPER] ct: {}", hex::encode(&ct)));
|
debug!("ct: {}", hex::encode(&ct));
|
||||||
debug_log(&format!("[DEBUG][ENCRYPT_HELPER] key: {}", hex::encode(key)));
|
debug!("key: {}", hex::encode(key));
|
||||||
(ct, hex::encode(key))
|
(ct, hex::encode(key))
|
||||||
},
|
},
|
||||||
"aes-gcm" => {
|
"aes-gcm" => {
|
||||||
let ct = encrypt_aes_gcm(key, plaintext, &nonce)
|
let ct = encrypt_aes_gcm(key, plaintext, &nonce)
|
||||||
.map_err(|e| VaultError::Crypto(e))?;
|
.map_err(|e| VaultError::Crypto(e))?;
|
||||||
debug_log(&format!("[DEBUG][ENCRYPT_HELPER] ct: {}", hex::encode(&ct)));
|
debug!("ct: {}", hex::encode(&ct));
|
||||||
debug_log(&format!("[DEBUG][ENCRYPT_HELPER] key: {}", hex::encode(key)));
|
debug!("key: {}", hex::encode(key));
|
||||||
(ct, hex::encode(key))
|
(ct, hex::encode(key))
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
debug_log(&format!("[DEBUG][ENCRYPT_HELPER] unsupported cipher: {}", cipher));
|
debug!("unsupported cipher: {}", cipher);
|
||||||
return Err(VaultError::Other(format!("Unsupported cipher: {cipher}")));
|
return Err(VaultError::Other(format!("Unsupported cipher: {cipher}")));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut blob = nonce.clone();
|
let mut blob = nonce.clone();
|
||||||
blob.extend_from_slice(&ct);
|
blob.extend_from_slice(&ct);
|
||||||
debug_log(&format!("[DEBUG][ENCRYPT_HELPER] ENCRYPTED (nonce|ct): {}", hex::encode(&blob)));
|
debug!("ENCRYPTED (nonce|ct): {}", hex::encode(&blob));
|
||||||
Ok(blob)
|
Ok(blob)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,50 +67,50 @@ impl<S: KVStore> Vault<S> {
|
|||||||
pub async fn create_keyspace(&mut self, name: &str, password: &[u8], kdf: &str, cipher: &str, tags: Option<Vec<String>>) -> Result<(), VaultError> {
|
pub async fn create_keyspace(&mut self, name: &str, password: &[u8], kdf: &str, cipher: &str, tags: Option<Vec<String>>) -> Result<(), VaultError> {
|
||||||
// Check if keyspace already exists
|
// Check if keyspace already exists
|
||||||
if self.storage.get(name).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?.is_some() {
|
if self.storage.get(name).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?.is_some() {
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] ERROR: keyspace '{}' already exists", name));
|
debug!("keyspace '{}' already exists", name);
|
||||||
return Err(VaultError::Crypto("Keyspace already exists".to_string()));
|
return Err(VaultError::Crypto("Keyspace already exists".to_string()));
|
||||||
}
|
}
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] entry: name={}", name));
|
debug!("entry: name={}", name);
|
||||||
use crate::crypto::{random_salt, kdf};
|
use crate::crypto::{random_salt, kdf};
|
||||||
use crate::data::{KeyspaceMetadata, KeyspaceData};
|
use crate::data::{KeyspaceMetadata, KeyspaceData};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
// 1. Generate salt
|
// 1. Generate salt
|
||||||
let salt = random_salt(16);
|
let salt = random_salt(16);
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] salt: {:?}", salt));
|
debug!("salt: {:?}", salt);
|
||||||
// 2. Derive key
|
// 2. Derive key
|
||||||
let key = match kdf {
|
let key = match kdf {
|
||||||
"scrypt" => match kdf::derive_key_scrypt(password, &salt, 32) {
|
"scrypt" => match kdf::derive_key_scrypt(password, &salt, 32) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] kdf scrypt error: {}", e));
|
debug!("kdf scrypt error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &salt, 32, 10_000),
|
"pbkdf2" => kdf::derive_key_pbkdf2(password, &salt, 32, 10_000),
|
||||||
_ => {
|
_ => {
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] unsupported KDF: {}", kdf));
|
debug!("unsupported KDF: {}", kdf);
|
||||||
return Err(VaultError::Other(format!("Unsupported KDF: {kdf}")));
|
return Err(VaultError::Other(format!("Unsupported KDF: {kdf}")));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] derived key: {} bytes", key.len()));
|
debug!("derived key: {} bytes", key.len());
|
||||||
// 3. Prepare initial keyspace data
|
// 3. Prepare initial keyspace data
|
||||||
let keyspace_data = KeyspaceData { keypairs: vec![] };
|
let keyspace_data = KeyspaceData { keypairs: vec![] };
|
||||||
let plaintext = match serde_json::to_vec(&keyspace_data) {
|
let plaintext = match serde_json::to_vec(&keyspace_data) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] serde_json error: {}", e));
|
debug!("serde_json error: {}", e);
|
||||||
return Err(VaultError::Serialization(e.to_string()));
|
return Err(VaultError::Serialization(e.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] plaintext serialized: {} bytes", plaintext.len()));
|
debug!("plaintext serialized: {} bytes", plaintext.len());
|
||||||
// 4. Generate nonce (12 bytes for both ciphers)
|
// 4. Generate nonce (12 bytes for both ciphers)
|
||||||
let nonce = random_salt(12);
|
let nonce = random_salt(12);
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] nonce: {}", hex::encode(&nonce)));
|
debug!("nonce: {}", hex::encode(&nonce));
|
||||||
// 5. Encrypt
|
// 5. Encrypt
|
||||||
let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, cipher)?;
|
let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, cipher)?;
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] encrypted_blob: {} bytes", encrypted_blob.len()));
|
debug!("encrypted_blob: {} bytes", encrypted_blob.len());
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] encrypted_blob (hex): {}", hex::encode(&encrypted_blob)));
|
debug!("encrypted_blob (hex): {}", hex::encode(&encrypted_blob));
|
||||||
// 6. Compose metadata
|
// 6. Compose metadata
|
||||||
let metadata = KeyspaceMetadata {
|
let metadata = KeyspaceMetadata {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
@ -140,12 +125,12 @@ impl<S: KVStore> Vault<S> {
|
|||||||
let meta_bytes = match serde_json::to_vec(&metadata) {
|
let meta_bytes = match serde_json::to_vec(&metadata) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][CREATE_KEYSPACE] serde_json metadata error: {}", e));
|
debug!("serde_json metadata error: {}", e);
|
||||||
return Err(VaultError::Serialization(e.to_string()));
|
return Err(VaultError::Serialization(e.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.storage.set(name, &meta_bytes).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
self.storage.set(name, &meta_bytes).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
||||||
debug_log("[DEBUG][CREATE_KEYSPACE] success");
|
debug!("success");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,18 +152,15 @@ impl<S: KVStore> Vault<S> {
|
|||||||
|
|
||||||
/// Unlock a keyspace by name and password, returning the decrypted data
|
/// Unlock a keyspace by name and password, returning the decrypted data
|
||||||
pub async fn unlock_keyspace(&self, name: &str, password: &[u8]) -> Result<KeyspaceData, VaultError> {
|
pub async fn unlock_keyspace(&self, name: &str, password: &[u8]) -> Result<KeyspaceData, VaultError> {
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] entry: name={} password={}", name, hex::encode(password)));
|
debug!("unlock_keyspace entry: name={}", name);
|
||||||
use crate::crypto::{kdf};
|
use crate::crypto::{kdf};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
// 1. Fetch keyspace metadata
|
// 1. Fetch keyspace metadata
|
||||||
let meta_bytes = self.storage.get(name).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
let meta_bytes = self.storage.get(name).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] got meta_bytes: {}", meta_bytes.as_ref().map(|v| v.len()).unwrap_or(0)));
|
|
||||||
let meta_bytes = meta_bytes.ok_or(VaultError::KeyspaceNotFound(name.to_string()))?;
|
let meta_bytes = meta_bytes.ok_or(VaultError::KeyspaceNotFound(name.to_string()))?;
|
||||||
let metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?;
|
let metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?;
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] metadata: kdf={} cipher={} salt={:?} encrypted_blob_len={}", metadata.kdf, metadata.cipher, metadata.salt, metadata.encrypted_blob.len()));
|
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] ENCRYPTED_BLOB (hex): {}", hex::encode(&metadata.encrypted_blob)));
|
|
||||||
if metadata.salt.len() != 16 {
|
if metadata.salt.len() != 16 {
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] ERROR: salt length {} != 16", metadata.salt.len()));
|
debug!("salt length {} != 16", metadata.salt.len());
|
||||||
return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string()));
|
return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string()));
|
||||||
}
|
}
|
||||||
// 2. Derive key
|
// 2. Derive key
|
||||||
@ -186,57 +168,57 @@ impl<S: KVStore> Vault<S> {
|
|||||||
"scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) {
|
"scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] kdf scrypt error: {}", e));
|
debug!("kdf scrypt error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000),
|
"pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000),
|
||||||
_ => {
|
_ => {
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] unsupported KDF: {}", metadata.kdf));
|
debug!("unsupported KDF: {}", metadata.kdf);
|
||||||
return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf)));
|
return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] derived key: {} bytes", key.len()));
|
debug!("derived key: {} bytes", key.len());
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] derived key (hex): {}", hex::encode(&key)));
|
debug!("derived key (hex): {}", hex::encode(&key));
|
||||||
// 3. Split nonce and ciphertext
|
// 3. Split nonce and ciphertext
|
||||||
let ciphertext = &metadata.encrypted_blob;
|
let ciphertext = &metadata.encrypted_blob;
|
||||||
if ciphertext.len() < 12 {
|
if ciphertext.len() < 12 {
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] ciphertext too short: {}", ciphertext.len()));
|
debug!("ciphertext too short: {}", ciphertext.len());
|
||||||
return Err(VaultError::Crypto("Ciphertext too short".to_string()));
|
return Err(VaultError::Crypto("Ciphertext too short".to_string()));
|
||||||
}
|
}
|
||||||
let (nonce, ct) = ciphertext.split_at(12);
|
let (nonce, ct) = ciphertext.split_at(12);
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] nonce: {} ct: {}", hex::encode(nonce), hex::encode(ct)));
|
debug!("nonce: {}", hex::encode(nonce));
|
||||||
// 4. Decrypt
|
// 4. Decrypt
|
||||||
let plaintext = match metadata.cipher.as_str() {
|
let plaintext = match metadata.cipher.as_str() {
|
||||||
"chacha20poly1305" => match decrypt_chacha20(&key, ct, nonce) {
|
"chacha20poly1305" => match decrypt_chacha20(&key, ct, nonce) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] chacha20poly1305 error: {}", e));
|
debug!("chacha20poly1305 error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) {
|
"aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] aes-gcm error: {}", e));
|
debug!("aes-gcm error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] unsupported cipher: {}", metadata.cipher));
|
debug!("unsupported cipher: {}", metadata.cipher);
|
||||||
return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher)));
|
return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] plaintext decrypted: {} bytes", plaintext.len()));
|
debug!("plaintext decrypted: {} bytes", plaintext.len());
|
||||||
// 4. Deserialize keyspace data
|
// 4. Deserialize keyspace data
|
||||||
let keyspace_data: KeyspaceData = match serde_json::from_slice(&plaintext) {
|
let keyspace_data: KeyspaceData = match serde_json::from_slice(&plaintext) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] serde_json data error: {}", e));
|
debug!("serde_json data error: {}", e);
|
||||||
return Err(VaultError::Serialization(e.to_string()));
|
return Err(VaultError::Serialization(e.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug_log("[DEBUG][UNLOCK_KEYSPACE] success");
|
debug!("success");
|
||||||
Ok(keyspace_data)
|
Ok(keyspace_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,17 +298,17 @@ impl<S: KVStore> Vault<S> {
|
|||||||
|
|
||||||
/// Save the updated keyspace data (helper)
|
/// Save the updated keyspace data (helper)
|
||||||
async fn save_keyspace(&mut self, keyspace: &str, password: &[u8], data: &KeyspaceData) -> Result<(), VaultError> {
|
async fn save_keyspace(&mut self, keyspace: &str, password: &[u8], data: &KeyspaceData) -> Result<(), VaultError> {
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] entry: keyspace={} password={}", keyspace, hex::encode(password)));
|
debug!("save_keyspace entry: keyspace={}", keyspace);
|
||||||
use crate::crypto::kdf;
|
use crate::crypto::kdf;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
// 1. Fetch metadata
|
// 1. Fetch metadata
|
||||||
let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] got meta_bytes: {}", meta_bytes.as_ref().map(|v| v.len()).unwrap_or(0)));
|
debug!("got meta_bytes: {}", meta_bytes.as_ref().map(|v| v.len()).unwrap_or(0));
|
||||||
let meta_bytes = meta_bytes.ok_or(VaultError::KeyspaceNotFound(keyspace.to_string()))?;
|
let meta_bytes = meta_bytes.ok_or(VaultError::KeyspaceNotFound(keyspace.to_string()))?;
|
||||||
let mut metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?;
|
let mut metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?;
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] metadata: kdf={} cipher={} salt={:?}", metadata.kdf, metadata.cipher, metadata.salt));
|
debug!("metadata: kdf={} cipher={} salt={:?}", metadata.kdf, metadata.cipher, metadata.salt);
|
||||||
if metadata.salt.len() != 16 {
|
if metadata.salt.len() != 16 {
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] ERROR: salt length {} != 16", metadata.salt.len()));
|
debug!("salt length {} != 16", metadata.salt.len());
|
||||||
return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string()));
|
return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string()));
|
||||||
}
|
}
|
||||||
// 2. Derive key
|
// 2. Derive key
|
||||||
@ -334,43 +316,43 @@ impl<S: KVStore> Vault<S> {
|
|||||||
"scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) {
|
"scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] kdf scrypt error: {}", e));
|
debug!("kdf scrypt error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000),
|
"pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000),
|
||||||
_ => {
|
_ => {
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] unsupported KDF: {}", metadata.kdf));
|
debug!("unsupported KDF: {}", metadata.kdf);
|
||||||
return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf)));
|
return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] derived key: {} bytes", key.len()));
|
debug!("derived key: {} bytes", key.len());
|
||||||
// 3. Serialize plaintext
|
// 3. Serialize plaintext
|
||||||
let plaintext = match serde_json::to_vec(data) {
|
let plaintext = match serde_json::to_vec(data) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] serde_json data error: {}", e));
|
debug!("serde_json data error: {}", e);
|
||||||
return Err(VaultError::Serialization(e.to_string()));
|
return Err(VaultError::Serialization(e.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] plaintext serialized: {} bytes", plaintext.len()));
|
debug!("plaintext serialized: {} bytes", plaintext.len());
|
||||||
// 4. Generate nonce
|
// 4. Generate nonce
|
||||||
let nonce = random_salt(12);
|
let nonce = random_salt(12);
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] nonce: {}", hex::encode(&nonce)));
|
debug!("nonce: {}", hex::encode(&nonce));
|
||||||
// 5. Encrypt
|
// 5. Encrypt
|
||||||
let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, &metadata.cipher)?;
|
let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, &metadata.cipher)?;
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] encrypted_blob: {} bytes", encrypted_blob.len()));
|
debug!("encrypted_blob: {} bytes", encrypted_blob.len());
|
||||||
// 6. Store new encrypted blob
|
// 6. Store new encrypted blob
|
||||||
metadata.encrypted_blob = encrypted_blob;
|
metadata.encrypted_blob = encrypted_blob;
|
||||||
let meta_bytes = match serde_json::to_vec(&metadata) {
|
let meta_bytes = match serde_json::to_vec(&metadata) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][SAVE_KEYSPACE] serde_json metadata error: {}", e));
|
debug!("serde_json metadata error: {}", e);
|
||||||
return Err(VaultError::Serialization(e.to_string()));
|
return Err(VaultError::Serialization(e.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.storage.set(keyspace, &meta_bytes).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
self.storage.set(keyspace, &meta_bytes).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
||||||
debug_log("[DEBUG][SAVE_KEYSPACE] success");
|
debug!("success");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,62 +415,62 @@ impl<S: KVStore> Vault<S> {
|
|||||||
/// Encrypt a message using the keyspace symmetric cipher
|
/// Encrypt a message using the keyspace symmetric cipher
|
||||||
/// (for simplicity, uses keyspace password-derived key)
|
/// (for simplicity, uses keyspace password-derived key)
|
||||||
pub async fn encrypt(&self, keyspace: &str, password: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, VaultError> {
|
pub async fn encrypt(&self, keyspace: &str, password: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, VaultError> {
|
||||||
debug_log("[DEBUG][ENTER] encrypt");
|
debug!("encrypt");
|
||||||
debug_log(&format!("[DEBUG][encrypt] keyspace={}", keyspace));
|
debug!("keyspace={}", keyspace);
|
||||||
use crate::crypto::{kdf};
|
use crate::crypto::{kdf};
|
||||||
// 1. Load keyspace metadata
|
// 1. Load keyspace metadata
|
||||||
let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
||||||
let meta_bytes = match meta_bytes {
|
let meta_bytes = match meta_bytes {
|
||||||
Some(val) => val,
|
Some(val) => val,
|
||||||
None => {
|
None => {
|
||||||
debug_log("[DEBUG][ERR] encrypt: keyspace not found");
|
debug!("keyspace not found");
|
||||||
return Err(VaultError::Other("Keyspace not found".to_string()));
|
return Err(VaultError::Other("Keyspace not found".to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let meta: KeyspaceMetadata = match serde_json::from_slice(&meta_bytes) {
|
let meta: KeyspaceMetadata = match serde_json::from_slice(&meta_bytes) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][ERR] encrypt: serialization error: {}", e));
|
debug!("serialization error: {}", e);
|
||||||
return Err(VaultError::Serialization(e.to_string()));
|
return Err(VaultError::Serialization(e.to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
debug_log(&format!("[DEBUG][encrypt] salt={:?} cipher={} (hex salt: {})", meta.salt, meta.cipher, hex::encode(&meta.salt)));
|
debug!("salt={:?} cipher={} (hex salt: {})", meta.salt, meta.cipher, hex::encode(&meta.salt));
|
||||||
// 2. Derive key
|
// 2. Derive key
|
||||||
let key = match meta.kdf.as_str() {
|
let key = match meta.kdf.as_str() {
|
||||||
"scrypt" => match kdf::derive_key_scrypt(password, &meta.salt, 32) {
|
"scrypt" => match kdf::derive_key_scrypt(password, &meta.salt, 32) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][ERR] encrypt: kdf scrypt error: {}", e));
|
debug!("kdf scrypt error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000),
|
"pbkdf2" => kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000),
|
||||||
_ => {
|
_ => {
|
||||||
debug_log(&format!("[DEBUG][ERR] encrypt: unsupported KDF: {}", meta.kdf));
|
debug!("unsupported KDF: {}", meta.kdf);
|
||||||
return Err(VaultError::Other(format!("Unsupported KDF: {}", meta.kdf)));
|
return Err(VaultError::Other(format!("Unsupported KDF: {}", meta.kdf)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 3. Generate nonce
|
// 3. Generate nonce
|
||||||
let nonce = random_salt(12);
|
let nonce = random_salt(12);
|
||||||
debug_log(&format!("[DEBUG][encrypt] nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce)));
|
debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce));
|
||||||
// 4. Encrypt
|
// 4. Encrypt
|
||||||
let ciphertext = match meta.cipher.as_str() {
|
let ciphertext = match meta.cipher.as_str() {
|
||||||
"chacha20poly1305" => match encrypt_chacha20(&key, plaintext, &nonce) {
|
"chacha20poly1305" => match encrypt_chacha20(&key, plaintext, &nonce) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][ERR] encrypt: chacha20poly1305 error: {}", e));
|
debug!("chacha20poly1305 error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"aes-gcm" => match encrypt_aes_gcm(&key, plaintext, &nonce) {
|
"aes-gcm" => match encrypt_aes_gcm(&key, plaintext, &nonce) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][ERR] encrypt: aes-gcm error: {}", e));
|
debug!("aes-gcm error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
debug_log(&format!("[DEBUG][ERR] encrypt: unsupported cipher: {}", meta.cipher));
|
debug!("unsupported cipher: {}", meta.cipher);
|
||||||
return Err(VaultError::Other(format!("Unsupported cipher: {}", meta.cipher)));
|
return Err(VaultError::Other(format!("Unsupported cipher: {}", meta.cipher)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -501,58 +483,57 @@ pub async fn encrypt(&self, keyspace: &str, password: &[u8], plaintext: &[u8]) -
|
|||||||
/// Decrypt a message using the keyspace symmetric cipher
|
/// Decrypt a message using the keyspace symmetric cipher
|
||||||
/// (for simplicity, uses keyspace password-derived key)
|
/// (for simplicity, uses keyspace password-derived key)
|
||||||
pub async fn decrypt(&self, keyspace: &str, password: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, VaultError> {
|
pub async fn decrypt(&self, keyspace: &str, password: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, VaultError> {
|
||||||
debug_log("[DEBUG][ENTER] decrypt");
|
debug!("decrypt");
|
||||||
debug_log(&format!("[DEBUG][decrypt] keyspace={}", keyspace));
|
debug!("keyspace={}", keyspace);
|
||||||
use crate::crypto::{kdf};
|
use crate::crypto::{kdf};
|
||||||
// 1. Fetch metadata
|
// 1. Fetch metadata
|
||||||
let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?;
|
||||||
let meta_bytes = meta_bytes.ok_or(VaultError::KeyspaceNotFound(keyspace.to_string()))?;
|
let meta_bytes = meta_bytes.ok_or(VaultError::KeyspaceNotFound(keyspace.to_string()))?;
|
||||||
let metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?;
|
let metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?;
|
||||||
debug_log(&format!("[DEBUG][decrypt] salt={:?} cipher={} (hex salt: {})", metadata.salt, metadata.cipher, hex::encode(&metadata.salt)));
|
debug!("salt={:?} cipher={} (hex salt: {})", metadata.salt, metadata.cipher, hex::encode(&metadata.salt));
|
||||||
// 2. Derive key
|
// 2. Derive key
|
||||||
let key = match metadata.kdf.as_str() {
|
let key = match metadata.kdf.as_str() {
|
||||||
"scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) {
|
"scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][ERR] decrypt: storage error: {:?}", e));
|
debug!("storage error: {:?}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000),
|
"pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000),
|
||||||
_ => {
|
_ => {
|
||||||
debug_log(&format!("[DEBUG][ERR] decrypt: unsupported KDF: {}", metadata.kdf));
|
debug!("unsupported KDF: {}", metadata.kdf);
|
||||||
return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf)));
|
return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 3. Split nonce and ciphertext
|
// 3. Split nonce and ciphertext
|
||||||
if ciphertext.len() < 12 {
|
if ciphertext.len() < 12 {
|
||||||
debug_log(&format!("[DEBUG][ERR] decrypt: ciphertext too short: {}", ciphertext.len()));
|
debug!("ciphertext too short: {}", ciphertext.len());
|
||||||
return Err(VaultError::Crypto("Ciphertext too short".to_string()));
|
return Err(VaultError::Crypto("Ciphertext too short".to_string()));
|
||||||
}
|
}
|
||||||
let (nonce, ct) = ciphertext.split_at(12);
|
let (nonce, ct) = ciphertext.split_at(12);
|
||||||
debug_log(&format!("[DEBUG][decrypt] nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce)));
|
debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce));
|
||||||
// 4. Decrypt
|
// 4. Decrypt
|
||||||
let plaintext = match metadata.cipher.as_str() {
|
let plaintext = match metadata.cipher.as_str() {
|
||||||
"chacha20poly1305" => match decrypt_chacha20(&key, ct, nonce) {
|
"chacha20poly1305" => match decrypt_chacha20(&key, ct, nonce) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][ERR] decrypt: chacha20poly1305 error: {}", e));
|
debug!("chacha20poly1305 error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) {
|
"aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) {
|
||||||
Ok(val) => val,
|
Ok(val) => val,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug_log(&format!("[DEBUG][ERR] decrypt: aes-gcm error: {}", e));
|
debug!("aes-gcm error: {}", e);
|
||||||
return Err(VaultError::Crypto(e));
|
return Err(VaultError::Crypto(e));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
debug_log(&format!("[DEBUG][ERR] decrypt: unsupported cipher: {}", metadata.cipher));
|
debug!("unsupported cipher: {}", metadata.cipher);
|
||||||
return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher)));
|
return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(plaintext)
|
Ok(plaintext)
|
||||||
}
|
}
|
||||||
|
|
||||||
} // <-- Close the impl block
|
} // <-- Close the impl block
|
||||||
|
@ -3,20 +3,12 @@
|
|||||||
use vault::{Vault, KeyType, KeyMetadata};
|
use vault::{Vault, KeyType, KeyMetadata};
|
||||||
use kvstore::native::NativeStore;
|
use kvstore::native::NativeStore;
|
||||||
|
|
||||||
fn debug_log(msg: &str) {
|
use log::{debug, info, error};
|
||||||
use std::fs::OpenOptions;
|
|
||||||
use std::io::Write;
|
|
||||||
let mut f = OpenOptions::new()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open("vault_crypto_debug.log")
|
|
||||||
.unwrap();
|
|
||||||
writeln!(f, "{}", msg).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_keypair_management_and_crypto() {
|
async fn test_keypair_management_and_crypto() {
|
||||||
debug_log("[DEBUG][TEST] test_keypair_management_and_crypto started");
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
|
debug!("test_keypair_management_and_crypto started");
|
||||||
// Use NativeStore for native tests
|
// Use NativeStore for native tests
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
let store = NativeStore::open("vault_native_test").expect("Failed to open native store");
|
let store = NativeStore::open("vault_native_test").expect("Failed to open native store");
|
||||||
@ -27,39 +19,39 @@ async fn test_keypair_management_and_crypto() {
|
|||||||
let keyspace = &format!("testspace_{}", chrono::Utc::now().timestamp_nanos());
|
let keyspace = &format!("testspace_{}", chrono::Utc::now().timestamp_nanos());
|
||||||
let password = b"supersecret";
|
let password = b"supersecret";
|
||||||
|
|
||||||
debug_log(&format!("[DEBUG][TEST] keyspace: {} password: {}", keyspace, hex::encode(password)));
|
debug!("keyspace: {} password: {}", keyspace, hex::encode(password));
|
||||||
debug_log("[DEBUG][TEST] before create_keyspace");
|
debug!("before create_keyspace");
|
||||||
vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await.unwrap();
|
vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await.unwrap();
|
||||||
|
|
||||||
debug_log(&format!("[DEBUG][TEST] after create_keyspace: keyspace={} password={}", keyspace, hex::encode(password)));
|
debug!("after create_keyspace: keyspace={} password={}", keyspace, hex::encode(password));
|
||||||
debug_log("[DEBUG][TEST] before add Ed25519 keypair");
|
debug!("before add Ed25519 keypair");
|
||||||
let key_id = vault.add_keypair(keyspace, password, KeyType::Ed25519, Some(KeyMetadata { name: Some("edkey".into()), created_at: None, tags: None })).await;
|
let key_id = vault.add_keypair(keyspace, password, KeyType::Ed25519, Some(KeyMetadata { name: Some("edkey".into()), created_at: None, tags: None })).await;
|
||||||
match &key_id {
|
match &key_id {
|
||||||
Ok(_) => debug_log("[DEBUG][TEST] after add Ed25519 keypair (Ok)"),
|
Ok(_) => debug!("after add Ed25519 keypair (Ok)"),
|
||||||
Err(e) => debug_log(&format!("[DEBUG][TEST] after add Ed25519 keypair (Err): {:?}", e)),
|
Err(e) => debug!("after add Ed25519 keypair (Err): {:?}", e),
|
||||||
}
|
}
|
||||||
let key_id = key_id.unwrap();
|
let key_id = key_id.unwrap();
|
||||||
debug_log("[DEBUG][TEST] before add secp256k1 keypair");
|
debug!("before add secp256k1 keypair");
|
||||||
let secp_id = vault.add_keypair(keyspace, password, KeyType::Secp256k1, Some(KeyMetadata { name: Some("secpkey".into()), created_at: None, tags: None })).await.unwrap();
|
let secp_id = vault.add_keypair(keyspace, password, KeyType::Secp256k1, Some(KeyMetadata { name: Some("secpkey".into()), created_at: None, tags: None })).await.unwrap();
|
||||||
|
|
||||||
debug_log("[DEBUG][TEST] before list_keypairs");
|
debug!("before list_keypairs");
|
||||||
let keys = vault.list_keypairs(keyspace, password).await.unwrap();
|
let keys = vault.list_keypairs(keyspace, password).await.unwrap();
|
||||||
assert_eq!(keys.len(), 2);
|
assert_eq!(keys.len(), 2);
|
||||||
|
|
||||||
debug_log("[DEBUG][TEST] before export Ed25519 keypair");
|
debug!("before export Ed25519 keypair");
|
||||||
let (priv_bytes, pub_bytes) = vault.export_keypair(keyspace, password, &key_id).await.unwrap();
|
let (priv_bytes, pub_bytes) = vault.export_keypair(keyspace, password, &key_id).await.unwrap();
|
||||||
assert!(!priv_bytes.is_empty() && !pub_bytes.is_empty());
|
assert!(!priv_bytes.is_empty() && !pub_bytes.is_empty());
|
||||||
|
|
||||||
debug_log("[DEBUG][TEST] before sign Ed25519");
|
debug!("before sign Ed25519");
|
||||||
let msg = b"hello world";
|
let msg = b"hello world";
|
||||||
let sig = vault.sign(keyspace, password, &key_id, msg).await.unwrap();
|
let sig = vault.sign(keyspace, password, &key_id, msg).await.unwrap();
|
||||||
debug_log("[DEBUG][TEST] before verify Ed25519");
|
debug!("before verify Ed25519");
|
||||||
let ok = vault.verify(keyspace, password, &key_id, msg, &sig).await.unwrap();
|
let ok = vault.verify(keyspace, password, &key_id, msg, &sig).await.unwrap();
|
||||||
assert!(ok);
|
assert!(ok);
|
||||||
|
|
||||||
debug_log("[DEBUG][TEST] before sign secp256k1");
|
debug!("before sign secp256k1");
|
||||||
let sig2 = vault.sign(keyspace, password, &secp_id, msg).await.unwrap();
|
let sig2 = vault.sign(keyspace, password, &secp_id, msg).await.unwrap();
|
||||||
debug_log("[DEBUG][TEST] before verify secp256k1");
|
debug!("before verify secp256k1");
|
||||||
let ok2 = vault.verify(keyspace, password, &secp_id, msg, &sig2).await.unwrap();
|
let ok2 = vault.verify(keyspace, password, &secp_id, msg, &sig2).await.unwrap();
|
||||||
assert!(ok2);
|
assert!(ok2);
|
||||||
|
|
||||||
|
@ -10,107 +10,108 @@ wasm_bindgen_test_configure!(run_in_browser);
|
|||||||
#[wasm_bindgen_test(async)]
|
#[wasm_bindgen_test(async)]
|
||||||
async fn wasm_test_keypair_management_and_crypto() {
|
async fn wasm_test_keypair_management_and_crypto() {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
||||||
let store = WasmStore::open("vault_idb_test").await.expect("Failed to open IndexedDB store");
|
let store = WasmStore::open("vault_idb_test").await.expect("Failed to open IndexedDB store");
|
||||||
let mut vault = Vault::new(store);
|
let mut vault = Vault::new(store);
|
||||||
let keyspace = "wasmspace";
|
let keyspace = "wasmspace";
|
||||||
let password = b"supersecret";
|
let password = b"supersecret";
|
||||||
println!("[DEBUG] Initialized vault and IndexedDB store");
|
log::debug!("Initialized vault and IndexedDB store");
|
||||||
|
|
||||||
// Step 1: Create keyspace
|
// Step 1: Create keyspace
|
||||||
match vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await {
|
match vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await {
|
||||||
Ok(_) => println!("[DEBUG] Created keyspace"),
|
Ok(_) => log::debug!("Created keyspace"),
|
||||||
Err(e) => { println!("[ERROR] Failed to create keyspace: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to create keyspace: {:?}", e); return; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Add Ed25519 keypair
|
// Step 2: Add Ed25519 keypair
|
||||||
let key_id = match vault.add_keypair(keyspace, password, KeyType::Ed25519, Some(KeyMetadata { name: Some("edkey".into()), created_at: None, tags: None })).await {
|
let key_id = match vault.add_keypair(keyspace, password, KeyType::Ed25519, Some(KeyMetadata { name: Some("edkey".into()), created_at: None, tags: None })).await {
|
||||||
Ok(id) => { println!("[DEBUG] Added Ed25519 keypair: {}", id); id },
|
Ok(id) => { log::debug!("Added Ed25519 keypair: {}", id); id },
|
||||||
Err(e) => { println!("[ERROR] Failed to add Ed25519 keypair: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to add Ed25519 keypair: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 3: Add Secp256k1 keypair
|
// Step 3: Add Secp256k1 keypair
|
||||||
let secp_id = match vault.add_keypair(keyspace, password, KeyType::Secp256k1, Some(KeyMetadata { name: Some("secpkey".into()), created_at: None, tags: None })).await {
|
let secp_id = match vault.add_keypair(keyspace, password, KeyType::Secp256k1, Some(KeyMetadata { name: Some("secpkey".into()), created_at: None, tags: None })).await {
|
||||||
Ok(id) => { println!("[DEBUG] Added Secp256k1 keypair: {}", id); id },
|
Ok(id) => { log::debug!("Added Secp256k1 keypair: {}", id); id },
|
||||||
Err(e) => { println!("[ERROR] Failed to add Secp256k1 keypair: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to add Secp256k1 keypair: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Step 4: List keypairs
|
// Step 4: List keypairs
|
||||||
let keys = match vault.list_keypairs(keyspace, password).await {
|
let keys = match vault.list_keypairs(keyspace, password).await {
|
||||||
Ok(keys) => { println!("[DEBUG] Listed keypairs: {:?}", keys); keys },
|
Ok(keys) => { log::debug!("Listed keypairs: {:?}", keys); keys },
|
||||||
Err(e) => { println!("[ERROR] Failed to list keypairs: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to list keypairs: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
if keys.len() != 2 {
|
if keys.len() != 2 {
|
||||||
println!("[ERROR] Expected 2 keypairs, got {}", keys.len());
|
log::debug!("Expected 2 keypairs, got {}", keys.len());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Export Ed25519 keypair
|
// Step 5: Export Ed25519 keypair
|
||||||
let (priv_bytes, pub_bytes) = match vault.export_keypair(keyspace, password, &key_id).await {
|
let (priv_bytes, pub_bytes) = match vault.export_keypair(keyspace, password, &key_id).await {
|
||||||
Ok((priv_bytes, pub_bytes)) => {
|
Ok((priv_bytes, pub_bytes)) => {
|
||||||
println!("[DEBUG] Exported Ed25519 keypair, priv: {} bytes, pub: {} bytes", priv_bytes.len(), pub_bytes.len());
|
log::debug!("Exported Ed25519 keypair, priv: {} bytes, pub: {} bytes", priv_bytes.len(), pub_bytes.len());
|
||||||
(priv_bytes, pub_bytes)
|
(priv_bytes, pub_bytes)
|
||||||
},
|
},
|
||||||
Err(e) => { println!("[ERROR] Failed to export Ed25519 keypair: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to export Ed25519 keypair: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
if priv_bytes.is_empty() || pub_bytes.is_empty() {
|
if priv_bytes.is_empty() || pub_bytes.is_empty() {
|
||||||
println!("[ERROR] Exported Ed25519 keypair bytes are empty");
|
log::debug!("Exported Ed25519 keypair bytes are empty");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Sign and verify with Ed25519
|
// Step 6: Sign and verify with Ed25519
|
||||||
let msg = b"hello wasm";
|
let msg = b"hello wasm";
|
||||||
let sig = match vault.sign(keyspace, password, &key_id, msg).await {
|
let sig = match vault.sign(keyspace, password, &key_id, msg).await {
|
||||||
Ok(sig) => { println!("[DEBUG] Signed message with Ed25519"); sig },
|
Ok(sig) => { log::debug!("Signed message with Ed25519"); sig },
|
||||||
Err(e) => { println!("[ERROR] Failed to sign with Ed25519: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to sign with Ed25519: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
let ok = match vault.verify(keyspace, password, &key_id, msg, &sig).await {
|
let ok = match vault.verify(keyspace, password, &key_id, msg, &sig).await {
|
||||||
Ok(ok) => { println!("[DEBUG] Verified Ed25519 signature: {}", ok); ok },
|
Ok(ok) => { log::debug!("Verified Ed25519 signature: {}", ok); ok },
|
||||||
Err(e) => { println!("[ERROR] Failed to verify Ed25519 signature: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to verify Ed25519 signature: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
if !ok {
|
if !ok {
|
||||||
println!("[ERROR] Ed25519 signature verification failed");
|
log::debug!("Ed25519 signature verification failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 7: Sign and verify with Secp256k1
|
// Step 7: Sign and verify with Secp256k1
|
||||||
let sig2 = match vault.sign(keyspace, password, &secp_id, msg).await {
|
let sig2 = match vault.sign(keyspace, password, &secp_id, msg).await {
|
||||||
Ok(sig) => { println!("[DEBUG] Signed message with Secp256k1"); sig },
|
Ok(sig) => { log::debug!("Signed message with Secp256k1"); sig },
|
||||||
Err(e) => { println!("[ERROR] Failed to sign with Secp256k1: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to sign with Secp256k1: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
let ok2 = match vault.verify(keyspace, password, &secp_id, msg, &sig2).await {
|
let ok2 = match vault.verify(keyspace, password, &secp_id, msg, &sig2).await {
|
||||||
Ok(ok) => { println!("[DEBUG] Verified Secp256k1 signature: {}", ok); ok },
|
Ok(ok) => { log::debug!("Verified Secp256k1 signature: {}", ok); ok },
|
||||||
Err(e) => { println!("[ERROR] Failed to verify Secp256k1 signature: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to verify Secp256k1 signature: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
if !ok2 {
|
if !ok2 {
|
||||||
println!("[ERROR] Secp256k1 signature verification failed");
|
log::debug!("Secp256k1 signature verification failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 8: Encrypt and decrypt
|
// Step 8: Encrypt and decrypt
|
||||||
let ciphertext = match vault.encrypt(keyspace, password, msg).await {
|
let ciphertext = match vault.encrypt(keyspace, password, msg).await {
|
||||||
Ok(ct) => { println!("[DEBUG] Encrypted message"); ct },
|
Ok(ct) => { log::debug!("Encrypted message"); ct },
|
||||||
Err(e) => { println!("[ERROR] Failed to encrypt message: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to encrypt message: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
let plaintext = match vault.decrypt(keyspace, password, &ciphertext).await {
|
let plaintext = match vault.decrypt(keyspace, password, &ciphertext).await {
|
||||||
Ok(pt) => { println!("[DEBUG] Decrypted message"); pt },
|
Ok(pt) => { log::debug!("Decrypted message"); pt },
|
||||||
Err(e) => { println!("[ERROR] Failed to decrypt message: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to decrypt message: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
if plaintext != msg {
|
if plaintext != msg {
|
||||||
println!("[ERROR] Decrypted message does not match original");
|
log::debug!("Decrypted message does not match original");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 9: Remove Ed25519 keypair
|
// Step 9: Remove Ed25519 keypair
|
||||||
match vault.remove_keypair(keyspace, password, &key_id).await {
|
match vault.remove_keypair(keyspace, password, &key_id).await {
|
||||||
Ok(_) => println!("[DEBUG] Removed Ed25519 keypair"),
|
Ok(_) => log::debug!("Removed Ed25519 keypair"),
|
||||||
Err(e) => { println!("[ERROR] Failed to remove Ed25519 keypair: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to remove Ed25519 keypair: {:?}", e); return; }
|
||||||
}
|
}
|
||||||
let keys = match vault.list_keypairs(keyspace, password).await {
|
let keys = match vault.list_keypairs(keyspace, password).await {
|
||||||
Ok(keys) => { println!("[DEBUG] Listed keypairs after removal: {:?}", keys); keys },
|
Ok(keys) => { log::debug!("Listed keypairs after removal: {:?}", keys); keys },
|
||||||
Err(e) => { println!("[ERROR] Failed to list keypairs after removal: {:?}", e); return; }
|
Err(e) => { log::debug!("Failed to list keypairs after removal: {:?}", e); return; }
|
||||||
};
|
};
|
||||||
if keys.len() != 1 {
|
if keys.len() != 1 {
|
||||||
println!("[ERROR] Expected 1 keypair after removal, got {}", keys.len());
|
log::debug!("Expected 1 keypair after removal, got {}", keys.len());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,121 +0,0 @@
|
|||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error")
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error")
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error")
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error")
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error")
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Err): Crypto("decryption error: aead::Error")
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace_1747303028801159410 password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace_1747303028801159410 password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Ok)
|
|
||||||
[DEBUG][TEST] before add secp256k1 keypair
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace_1747303185421006752 password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace_1747303185421006752 password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Ok)
|
|
||||||
[DEBUG][TEST] before add secp256k1 keypair
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace_1747303743371199079 password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace_1747303743371199079 password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Ok)
|
|
||||||
[DEBUG][TEST] before add secp256k1 keypair
|
|
||||||
[DEBUG][TEST] before list_keypairs
|
|
||||||
[DEBUG][TEST] before export Ed25519 keypair
|
|
||||||
[DEBUG][TEST] before sign Ed25519
|
|
||||||
[DEBUG][TEST] before verify Ed25519
|
|
||||||
[DEBUG][TEST] before sign secp256k1
|
|
||||||
[DEBUG][TEST] before verify secp256k1
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace_1747304555613901420 password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace_1747304555613901420 password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Ok)
|
|
||||||
[DEBUG][TEST] before add secp256k1 keypair
|
|
||||||
[DEBUG][TEST] before list_keypairs
|
|
||||||
[DEBUG][TEST] before export Ed25519 keypair
|
|
||||||
[DEBUG][TEST] before sign Ed25519
|
|
||||||
[DEBUG][TEST] before verify Ed25519
|
|
||||||
[DEBUG][TEST] before sign secp256k1
|
|
||||||
[DEBUG][TEST] before verify secp256k1
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace_1747310570021504019 password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace_1747310570021504019 password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Ok)
|
|
||||||
[DEBUG][TEST] before add secp256k1 keypair
|
|
||||||
[DEBUG][TEST] before list_keypairs
|
|
||||||
[DEBUG][TEST] before export Ed25519 keypair
|
|
||||||
[DEBUG][TEST] before sign Ed25519
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace_1747310702751219893 password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace_1747310702751219893 password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Ok)
|
|
||||||
[DEBUG][TEST] before add secp256k1 keypair
|
|
||||||
[DEBUG][TEST] before list_keypairs
|
|
||||||
[DEBUG][TEST] before export Ed25519 keypair
|
|
||||||
[DEBUG][TEST] before sign Ed25519
|
|
||||||
[DEBUG][TEST] before verify Ed25519
|
|
||||||
[DEBUG][TEST] before sign secp256k1
|
|
||||||
[DEBUG][TEST] before verify secp256k1
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace_1747311247795239358 password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace_1747311247795239358 password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Ok)
|
|
||||||
[DEBUG][TEST] before add secp256k1 keypair
|
|
||||||
[DEBUG][TEST] before list_keypairs
|
|
||||||
[DEBUG][TEST] before export Ed25519 keypair
|
|
||||||
[DEBUG][TEST] before sign Ed25519
|
|
||||||
[DEBUG][TEST] before verify Ed25519
|
|
||||||
[DEBUG][TEST] before sign secp256k1
|
|
||||||
[DEBUG][TEST] before verify secp256k1
|
|
||||||
[DEBUG][TEST] test_keypair_management_and_crypto started
|
|
||||||
[DEBUG][TEST] keyspace: testspace_1747311770351800477 password: 7375706572736563726574
|
|
||||||
[DEBUG][TEST] before create_keyspace
|
|
||||||
[DEBUG][TEST] after create_keyspace: keyspace=testspace_1747311770351800477 password=7375706572736563726574
|
|
||||||
[DEBUG][TEST] before add Ed25519 keypair
|
|
||||||
[DEBUG][TEST] after add Ed25519 keypair (Ok)
|
|
||||||
[DEBUG][TEST] before add secp256k1 keypair
|
|
||||||
[DEBUG][TEST] before list_keypairs
|
|
||||||
[DEBUG][TEST] before export Ed25519 keypair
|
|
||||||
[DEBUG][TEST] before sign Ed25519
|
|
||||||
[DEBUG][TEST] before verify Ed25519
|
|
||||||
[DEBUG][TEST] before sign secp256k1
|
|
||||||
[DEBUG][TEST] before verify secp256k1
|
|
Binary file not shown.
Binary file not shown.
BIN
vault/vault_native_test/snap.000000000000E741
Normal file
BIN
vault/vault_native_test/snap.000000000000E741
Normal file
Binary file not shown.
156
vault_crypto_debug.log
Normal file
156
vault_crypto_debug.log
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
Compiling vault v0.1.0 (/home/sameh/sal-modular/vault)
|
||||||
|
warning: unused import: `KeyInit`
|
||||||
|
--> vault/src/crypto.rs:5:26
|
||||||
|
|
|
||||||
|
5 | use aes_gcm::{Aes256Gcm, KeyInit as AesKeyInit};
|
||||||
|
| ^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(unused_imports)]` on by default
|
||||||
|
|
||||||
|
warning: unused import: `signature::SignatureEncoding`
|
||||||
|
--> vault/src/lib.rs:130:13
|
||||||
|
|
|
||||||
|
130 | use signature::SignatureEncoding;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: unused import: `k256::elliptic_curve::sec1::ToEncodedPoint`
|
||||||
|
--> vault/src/lib.rs:148:21
|
||||||
|
|
|
||||||
|
148 | use k256::elliptic_curve::sec1::ToEncodedPoint;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: enum `KdfType` is never used
|
||||||
|
--> vault/src/crypto.rs:14:14
|
||||||
|
|
|
||||||
|
14 | pub enum KdfType {
|
||||||
|
| ^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(dead_code)]` on by default
|
||||||
|
|
||||||
|
warning: enum `CipherType` is never used
|
||||||
|
--> vault/src/crypto.rs:36:14
|
||||||
|
|
|
||||||
|
36 | pub enum CipherType {
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: struct `SessionManager` is never constructed
|
||||||
|
--> vault/src/session.rs:6:12
|
||||||
|
|
|
||||||
|
6 | pub struct SessionManager {
|
||||||
|
| ^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
warning: associated function `new` is never used
|
||||||
|
--> vault/src/session.rs:14:12
|
||||||
|
|
|
||||||
|
13 | impl SessionManager {
|
||||||
|
| ------------------- associated function in this implementation
|
||||||
|
14 | pub fn new() -> Self {
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
warning: `vault` (lib) generated 7 warnings
|
||||||
|
warning: unused import: `async_trait::async_trait`
|
||||||
|
--> vault/tests/mock_store.rs:2:5
|
||||||
|
|
|
||||||
|
2 | use async_trait::async_trait;
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: `#[warn(unused_imports)]` on by default
|
||||||
|
|
||||||
|
warning: `vault` (test "keypair_management") generated 1 warning (run `cargo fix --test "keypair_management"` to apply 1 suggestion)
|
||||||
|
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.87s
|
||||||
|
Running tests/keypair_management.rs (target/debug/deps/keypair_management-96fd8e08e64dfc60)
|
||||||
|
|
||||||
|
running 1 test
|
||||||
|
|
||||||
|
thread 'test_keypair_management_and_crypto' panicked at vault/tests/keypair_management.rs:21:160:
|
||||||
|
called `Result::unwrap()` on an `Err` value: Crypto("decryption error: aead::Error")
|
||||||
|
stack backtrace:
|
||||||
|
0: rust_begin_unwind
|
||||||
|
at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/std/src/panicking.rs:695:5
|
||||||
|
1: core::panicking::panic_fmt
|
||||||
|
at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/panicking.rs:75:14
|
||||||
|
2: core::result::unwrap_failed
|
||||||
|
at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/result.rs:1704:5
|
||||||
|
3: core::result::Result<T,E>::unwrap
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:1109:23
|
||||||
|
4: keypair_management::test_keypair_management_and_crypto::{{closure}}
|
||||||
|
at ./tests/keypair_management.rs:21:18
|
||||||
|
5: <async_std::task::builder::SupportTaskLocals<F> as core::future::future::Future>::poll::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:199:17
|
||||||
|
6: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/task_locals_wrapper.rs:60:13
|
||||||
|
7: std::thread::local::LocalKey<T>::try_with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12
|
||||||
|
8: std::thread::local::LocalKey<T>::with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15
|
||||||
|
9: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/task_locals_wrapper.rs:55:9
|
||||||
|
10: <async_std::task::builder::SupportTaskLocals<F> as core::future::future::Future>::poll
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:197:13
|
||||||
|
11: <futures_lite::future::Or<F1,F2> as core::future::future::Future>::poll
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/futures-lite-2.6.0/src/future.rs:454:33
|
||||||
|
12: async_executor::State::run::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.2/src/lib.rs:752:32
|
||||||
|
13: async_executor::Executor::run::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.2/src/lib.rs:343:34
|
||||||
|
14: async_executor::LocalExecutor::run::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-executor-1.13.2/src/lib.rs:647:34
|
||||||
|
15: async_io::driver::block_on::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-2.4.0/src/driver.rs:199:37
|
||||||
|
16: std::thread::local::LocalKey<T>::try_with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12
|
||||||
|
17: std::thread::local::LocalKey<T>::with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15
|
||||||
|
18: async_io::driver::block_on
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-io-2.4.0/src/driver.rs:175:5
|
||||||
|
19: async_global_executor::reactor::block_on::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-global-executor-2.4.1/src/reactor.rs:3:18
|
||||||
|
20: async_global_executor::reactor::block_on
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-global-executor-2.4.1/src/reactor.rs:12:5
|
||||||
|
21: async_global_executor::executor::block_on::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-global-executor-2.4.1/src/executor.rs:26:36
|
||||||
|
22: std::thread::local::LocalKey<T>::try_with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12
|
||||||
|
23: std::thread::local::LocalKey<T>::with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15
|
||||||
|
24: async_global_executor::executor::block_on
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-global-executor-2.4.1/src/executor.rs:26:5
|
||||||
|
25: async_std::task::builder::Builder::blocking::{{closure}}::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:171:25
|
||||||
|
26: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/task_locals_wrapper.rs:60:13
|
||||||
|
27: std::thread::local::LocalKey<T>::try_with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12
|
||||||
|
28: std::thread::local::LocalKey<T>::with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15
|
||||||
|
29: async_std::task::task_locals_wrapper::TaskLocalsWrapper::set_current
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/task_locals_wrapper.rs:55:9
|
||||||
|
30: async_std::task::builder::Builder::blocking::{{closure}}
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:168:17
|
||||||
|
31: std::thread::local::LocalKey<T>::try_with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:310:12
|
||||||
|
32: std::thread::local::LocalKey<T>::with
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:274:15
|
||||||
|
33: async_std::task::builder::Builder::blocking
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/builder.rs:161:9
|
||||||
|
34: async_std::task::block_on::block_on
|
||||||
|
at /home/sameh/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/async-std-1.13.1/src/task/block_on.rs:31:5
|
||||||
|
35: keypair_management::test_keypair_management_and_crypto
|
||||||
|
at ./tests/keypair_management.rs:9:1
|
||||||
|
36: keypair_management::test_keypair_management_and_crypto::{{closure}}
|
||||||
|
at ./tests/keypair_management.rs:9:19
|
||||||
|
37: core::ops::function::FnOnce::call_once
|
||||||
|
at /home/sameh/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
|
||||||
|
38: core::ops::function::FnOnce::call_once
|
||||||
|
at /rustc/05f9846f893b09a1be1fc8560e33fc3c815cfecb/library/core/src/ops/function.rs:250:5
|
||||||
|
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
|
||||||
|
test test_keypair_management_and_crypto ... FAILED
|
||||||
|
|
||||||
|
failures:
|
||||||
|
|
||||||
|
failures:
|
||||||
|
test_keypair_management_and_crypto
|
||||||
|
|
||||||
|
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 32.97s
|
||||||
|
|
||||||
|
error: test failed, to rerun pass `-p vault --test keypair_management`
|
Loading…
Reference in New Issue
Block a user