Feat: add vault module
This commit is contained in:
		| @@ -8,8 +8,31 @@ path = "src/lib.rs" | ||||
|  | ||||
| [dependencies] | ||||
| kvstore = { path = "../kvstore" } | ||||
| scrypt = "0.11" | ||||
| sha2 = "0.10" | ||||
| aes-gcm = "0.10" | ||||
| pbkdf2 = "0.12" | ||||
| signature = "2.2" | ||||
| async-trait = "0.1" | ||||
| chacha20poly1305 = "0.10" | ||||
| k256 = "0.13" | ||||
| k256 = { version = "0.13", features = ["ecdsa"] } | ||||
| ed25519-dalek = "2.1" | ||||
| rand_core = "0.6" | ||||
| thiserror = "1" | ||||
| serde = { version = "1", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
| hex = "0.4" | ||||
|  | ||||
| [dev-dependencies] | ||||
| console_error_panic_hook = "0.1" | ||||
| tokio = { version = "1.0", features = ["rt", "macros"] } | ||||
| async-std = { version = "1", features = ["attributes"] } | ||||
| wasm-bindgen-test = "0.3" | ||||
| chrono = "0.4" | ||||
|  | ||||
| [target.'cfg(target_arch = "wasm32")'.dependencies] | ||||
| getrandom = { version = "0.2", features = ["js"] } | ||||
| wasm-bindgen = "0.2" | ||||
| js-sys = "0.3" | ||||
| console_error_panic_hook = "0.1" | ||||
|  | ||||
|   | ||||
							
								
								
									
										70
									
								
								vault/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								vault/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,70 @@ | ||||
| # vault: Cryptographic Vault for Native and WASM | ||||
|  | ||||
| `vault` provides a secure, async, and cross-platform cryptographic key management system. It leverages the `kvstore` crate for persistent storage and supports both native (desktop/server) and WASM (browser) environments. | ||||
|  | ||||
| ## Features | ||||
| - **Keyspace management**: Create, unlock, and manage encrypted keyspaces. | ||||
| - **Keypair operations**: Add, remove, list, export, and use keypairs for signing and verification. | ||||
| - **End-to-end encryption**: All key material is encrypted at rest using modern ciphers (ChaCha20Poly1305, AES-GCM). | ||||
| - **Async API**: All operations are async and runtime-agnostic. | ||||
| - **Cross-platform**: Native uses `sled` via `kvstore::native::NativeStore`, WASM uses IndexedDB via `kvstore::wasm::WasmStore`. | ||||
| - **Pluggable logging**: Uses the standard `log` crate for logging, with recommended backends for native (`env_logger`) and WASM (`console_log`). | ||||
|  | ||||
| ## 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: | ||||
|  | ||||
| - **Native (desktop/server):** | ||||
|   - Add `env_logger` as a dev-dependency. | ||||
|   - Initialize in your main or test: | ||||
|     ```rust | ||||
|     let _ = env_logger::builder().is_test(true).try_init(); | ||||
|     ``` | ||||
| - **WASM (browser):** | ||||
|   - Add `console_log` as a dev-dependency. | ||||
|   - Initialize in your main or test: | ||||
|     ```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. | ||||
|  | ||||
| ## Usage Example | ||||
|  | ||||
| ```rust | ||||
| use vault::{Vault, KeyType, KeyMetadata}; | ||||
| use kvstore::native::NativeStore; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
|     let store = NativeStore::open("/tmp/vaultdb").unwrap(); | ||||
|     let mut vault = Vault::new(store); | ||||
|     let keyspace = "myspace"; | ||||
|     let password = b"secret"; | ||||
|     vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await.unwrap(); | ||||
|     let key_id = vault.add_keypair(keyspace, password, KeyType::Ed25519, None).await.unwrap(); | ||||
|     println!("Created keypair: {}", key_id); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| For WASM/browser, use `kvstore::wasm::WasmStore` and initialize logging with `console_log`. | ||||
|  | ||||
| ## Testing | ||||
|  | ||||
| ### Native | ||||
| ```sh | ||||
| cargo test -p vault --features native | ||||
| ``` | ||||
|  | ||||
| ### WASM | ||||
| ```sh | ||||
| wasm-pack test --headless --firefox | ||||
| ``` | ||||
|  | ||||
| ## Security Notes | ||||
| - All cryptographic operations use vetted RustCrypto crates. | ||||
| - Password-based key derivation uses PBKDF2 by default (10,000 iterations). | ||||
| - All sensitive data is encrypted before storage. | ||||
|  | ||||
| ## License | ||||
| MIT OR Apache-2.0 | ||||
							
								
								
									
										62
									
								
								vault/src/crypto.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								vault/src/crypto.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| //! Crypto utilities for the vault crate | ||||
|  | ||||
| //! Crypto utilities for the vault crate | ||||
| use chacha20poly1305::{ChaCha20Poly1305, KeyInit as ChaChaKeyInit, aead::{Aead, generic_array::GenericArray}}; | ||||
| use aes_gcm::Aes256Gcm; | ||||
| use pbkdf2::pbkdf2_hmac; | ||||
| use scrypt::{scrypt, Params as ScryptParams}; | ||||
| use sha2::Sha256; | ||||
| use rand_core::{RngCore, OsRng as RandOsRng}; | ||||
|  | ||||
| pub mod kdf { | ||||
|     use super::*; | ||||
|  | ||||
|  | ||||
|     pub fn derive_key_scrypt(password: &[u8], salt: &[u8], key_len: usize) -> Result<Vec<u8>, String> { | ||||
|         let params = ScryptParams::recommended(); | ||||
|         let mut key = vec![0u8; key_len]; | ||||
|         scrypt(password, salt, ¶ms, &mut key).map_err(|e| e.to_string())?; | ||||
|         Ok(key) | ||||
|     } | ||||
|  | ||||
|     pub fn derive_key_pbkdf2(password: &[u8], salt: &[u8], key_len: usize, iterations: u32) -> Vec<u8> { | ||||
|         let mut key = vec![0u8; key_len]; | ||||
|         pbkdf2_hmac::<Sha256>(password, salt, iterations, &mut key); | ||||
|         key | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub mod cipher { | ||||
|     use super::*; | ||||
|  | ||||
|  | ||||
|     pub fn encrypt_chacha20(key: &[u8], plaintext: &[u8], nonce: &[u8]) -> Result<Vec<u8>, String> { | ||||
|         let cipher = ChaCha20Poly1305::new(GenericArray::from_slice(key)); | ||||
|         cipher.encrypt(GenericArray::from_slice(nonce), plaintext) | ||||
|             .map_err(|e| format!("encryption error: {e}")) | ||||
|     } | ||||
|  | ||||
|     pub fn decrypt_chacha20(key: &[u8], ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>, String> { | ||||
|         let cipher = ChaCha20Poly1305::new(GenericArray::from_slice(key)); | ||||
|         cipher.decrypt(GenericArray::from_slice(nonce), ciphertext) | ||||
|             .map_err(|e| format!("decryption error: {e}")) | ||||
|     } | ||||
|  | ||||
|     pub fn encrypt_aes_gcm(key: &[u8], plaintext: &[u8], nonce: &[u8]) -> Result<Vec<u8>, String> { | ||||
|         let cipher = Aes256Gcm::new(GenericArray::from_slice(key)); | ||||
|         cipher.encrypt(GenericArray::from_slice(nonce), plaintext) | ||||
|             .map_err(|e| format!("encryption error: {e}")) | ||||
|     } | ||||
|  | ||||
|     pub fn decrypt_aes_gcm(key: &[u8], ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>, String> { | ||||
|         let cipher = Aes256Gcm::new(GenericArray::from_slice(key)); | ||||
|         cipher.decrypt(GenericArray::from_slice(nonce), ciphertext) | ||||
|             .map_err(|e| format!("decryption error: {e}")) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn random_salt(len: usize) -> Vec<u8> { | ||||
|     let mut salt = vec![0u8; len]; | ||||
|     RandOsRng.fill_bytes(&mut salt); | ||||
|     salt | ||||
| } | ||||
							
								
								
									
										50
									
								
								vault/src/data.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								vault/src/data.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| //! Data models for the vault crate | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||||
| pub struct VaultMetadata { | ||||
|     pub name: String, | ||||
|     pub keyspaces: Vec<KeyspaceMetadata>, | ||||
|     // ... other vault-level metadata | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||||
| pub struct KeyspaceMetadata { | ||||
|     pub name: String, | ||||
|     pub salt: [u8; 16], // Unique salt for this keyspace | ||||
|     pub kdf: String,    // e.g. "scrypt" or "pbkdf2" | ||||
|     pub cipher: String, // e.g. "chacha20poly1305" or "aes-gcm" | ||||
|     pub encrypted_blob: Vec<u8>, | ||||
|     pub created_at: Option<u64>, // Unix timestamp | ||||
|     pub tags: Option<Vec<String>>, | ||||
|     // ... other keyspace metadata | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||||
| pub struct KeyspaceData { | ||||
|     pub keypairs: Vec<KeyEntry>, | ||||
|     // ... other keyspace-level metadata | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||||
| pub struct KeyEntry { | ||||
|     pub id: String, | ||||
|     pub key_type: KeyType, | ||||
|     pub private_key: Vec<u8>, // Only present in memory after decryption | ||||
|     pub public_key: Vec<u8>, | ||||
|     pub metadata: Option<KeyMetadata>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||||
| pub enum KeyType { | ||||
|     Secp256k1, | ||||
|     Ed25519, | ||||
|     // ... | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] | ||||
| pub struct KeyMetadata { | ||||
|     pub name: Option<String>, | ||||
|     pub created_at: Option<u64>, | ||||
|     pub tags: Option<Vec<String>>, | ||||
|     // ... | ||||
| } | ||||
							
								
								
									
										21
									
								
								vault/src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vault/src/error.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| //! Error types for the vault crate | ||||
|  | ||||
| #[derive(Debug, thiserror::Error)] | ||||
| pub enum VaultError { | ||||
|     #[error("Storage error: {0}")] | ||||
|     Storage(String), | ||||
|     #[error("Crypto error: {0}")] | ||||
|     Crypto(String), | ||||
|     #[error("Unauthorized")] | ||||
|     Unauthorized, | ||||
|     #[error("Keyspace not found: {0}")] | ||||
|     KeyspaceNotFound(String), | ||||
|     #[error("Key not found: {0}")] | ||||
|     KeyNotFound(String), | ||||
|     #[error("Invalid password")] | ||||
|     InvalidPassword, | ||||
|     #[error("Serialization error: {0}")] | ||||
|     Serialization(String), | ||||
|     #[error("Other: {0}")] | ||||
|     Other(String), | ||||
| } | ||||
							
								
								
									
										563
									
								
								vault/src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										563
									
								
								vault/src/lib.rs
									
									
									
									
									
								
							| @@ -1,27 +1,558 @@ | ||||
| //! vault: Cryptographic keyspace and operations | ||||
|  | ||||
|  | ||||
| //! vault: Cryptographic keyspace and operations | ||||
|  | ||||
| mod data; | ||||
| pub use crate::data::{KeyType, KeyMetadata}; | ||||
| mod error; | ||||
| mod crypto; | ||||
| mod session; | ||||
| mod utils; | ||||
|  | ||||
| use kvstore::KVStore; | ||||
| use data::*; | ||||
| use error::VaultError; | ||||
| use crate::crypto::random_salt; | ||||
|  | ||||
| #[derive(Debug, thiserror::Error)] | ||||
| pub enum VaultError { | ||||
|     #[error("Storage error: {0}")] | ||||
|     Storage(String), | ||||
|     #[error("Crypto error: {0}")] | ||||
|     Crypto(String), | ||||
|     #[error("Unauthorized")] | ||||
|     Unauthorized, | ||||
| use crate::crypto::cipher::{encrypt_chacha20, decrypt_chacha20, encrypt_aes_gcm, decrypt_aes_gcm}; | ||||
| use signature::SignatureEncoding; | ||||
| // TEMP: File-based debug logger for crypto troubleshooting | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| 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(); | ||||
| } | ||||
|  | ||||
| pub struct Vault<S: KVStore + Send + Sync> { | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| fn debug_log(_msg: &str) { | ||||
|     // No-op in WASM | ||||
| } | ||||
|  | ||||
| /// Vault: Cryptographic keyspace and operations | ||||
| pub struct Vault<S: KVStore> { | ||||
|     storage: S, | ||||
|     // ... other fields | ||||
|     // Optionally: cache of unlocked keyspaces, etc. | ||||
| } | ||||
|  | ||||
| impl<S: KVStore + Send + Sync> Vault<S> { | ||||
|     /// Creates a new keyspace. Implementation pending. | ||||
|     pub async fn create_keyspace(_dummy: ()) -> Result<(), VaultError> { | ||||
|         todo!("Implement create_keyspace") | ||||
|     } | ||||
|     // ... other API stubs | ||||
| /// Helper to encrypt and prepend nonce to ciphertext for keyspace storage | ||||
| fn encrypt_with_nonce_prepended(key: &[u8], plaintext: &[u8], cipher: &str) -> Result<Vec<u8>, VaultError> { | ||||
|     use crate::crypto::random_salt; | ||||
|     use crate::crypto; | ||||
|     let nonce = random_salt(12); | ||||
|     debug_log(&format!("[DEBUG][ENCRYPT_HELPER] nonce: {}", hex::encode(&nonce))); | ||||
|     let (ct, _key_hex) = match cipher { | ||||
|         "chacha20poly1305" => { | ||||
|             let ct = encrypt_chacha20(key, plaintext, &nonce) | ||||
|                 .map_err(|e| VaultError::Crypto(e))?; | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] ct: {}", hex::encode(&ct))); | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] key: {}", hex::encode(key))); | ||||
|             (ct, hex::encode(key)) | ||||
|         }, | ||||
|         "aes-gcm" => { | ||||
|             let ct = encrypt_aes_gcm(key, plaintext, &nonce) | ||||
|                 .map_err(|e| VaultError::Crypto(e))?; | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] ct: {}", hex::encode(&ct))); | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] key: {}", hex::encode(key))); | ||||
|             (ct, hex::encode(key)) | ||||
|         }, | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ENCRYPT_HELPER] unsupported cipher: {}", cipher)); | ||||
|             return Err(VaultError::Other(format!("Unsupported cipher: {cipher}"))); | ||||
|         } | ||||
|     }; | ||||
|     let mut blob = nonce.clone(); | ||||
|     blob.extend_from_slice(&ct); | ||||
|     debug_log(&format!("[DEBUG][ENCRYPT_HELPER] ENCRYPTED (nonce|ct): {}", hex::encode(&blob))); | ||||
|     Ok(blob) | ||||
| } | ||||
|  | ||||
| impl<S: KVStore> Vault<S> { | ||||
|     pub fn new(storage: S) -> Self { | ||||
|         Self { storage } | ||||
|     } | ||||
|  | ||||
|     /// Create a new keyspace with the given name, password, and options. | ||||
|     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 | ||||
|         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)); | ||||
|             return Err(VaultError::Crypto("Keyspace already exists".to_string())); | ||||
|         } | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] entry: name={}", name)); | ||||
|         use crate::crypto::{random_salt, kdf}; | ||||
|         use crate::data::{KeyspaceMetadata, KeyspaceData}; | ||||
|         use serde_json; | ||||
|          | ||||
|         // 1. Generate salt | ||||
|         let salt = random_salt(16); | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] salt: {:?}", salt)); | ||||
|         // 2. Derive key | ||||
|         let key = match kdf { | ||||
|             "scrypt" => match kdf::derive_key_scrypt(password, &salt, 32) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][CREATE_KEYSPACE] kdf scrypt error: {}", e)); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             "pbkdf2" => kdf::derive_key_pbkdf2(password, &salt, 32, 10_000), | ||||
|             _ => { | ||||
|                 debug_log(&format!("[DEBUG][CREATE_KEYSPACE] unsupported KDF: {}", kdf)); | ||||
|                 return Err(VaultError::Other(format!("Unsupported KDF: {kdf}"))); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] derived key: {} bytes", key.len())); | ||||
|         // 3. Prepare initial keyspace data | ||||
|         let keyspace_data = KeyspaceData { keypairs: vec![] }; | ||||
|         let plaintext = match serde_json::to_vec(&keyspace_data) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][CREATE_KEYSPACE] serde_json error: {}", e)); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] plaintext serialized: {} bytes", plaintext.len())); | ||||
|         // 4. Generate nonce (12 bytes for both ciphers) | ||||
|         let nonce = random_salt(12); | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] nonce: {}", hex::encode(&nonce))); | ||||
|         // 5. Encrypt | ||||
|         let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, cipher)?; | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] encrypted_blob: {} bytes", encrypted_blob.len())); | ||||
|         debug_log(&format!("[DEBUG][CREATE_KEYSPACE] encrypted_blob (hex): {}", hex::encode(&encrypted_blob))); | ||||
|         // 6. Compose metadata | ||||
|         let metadata = KeyspaceMetadata { | ||||
|             name: name.to_string(), | ||||
|             salt: salt.try_into().unwrap_or([0u8; 16]), | ||||
|             kdf: kdf.to_string(), | ||||
|             cipher: cipher.to_string(), | ||||
|             encrypted_blob, | ||||
|             created_at: Some(crate::utils::now()), | ||||
|             tags, | ||||
|         }; | ||||
|         // 7. Store in kvstore (keyed by keyspace name) | ||||
|         let meta_bytes = match serde_json::to_vec(&metadata) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][CREATE_KEYSPACE] serde_json metadata error: {}", e)); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         self.storage.set(name, &meta_bytes).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|         debug_log("[DEBUG][CREATE_KEYSPACE] success"); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// List all keyspaces (metadata only, not decrypted) | ||||
|     pub async fn list_keyspaces(&self) -> Result<Vec<KeyspaceMetadata>, VaultError> { | ||||
|         use serde_json; | ||||
|         // 1. List all keys in kvstore | ||||
|         let keys = self.storage.keys().await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|         let mut keyspaces = Vec::new(); | ||||
|         for key in keys { | ||||
|             if let Some(bytes) = self.storage.get(&key).await.map_err(|e| VaultError::Storage(format!("{e:?}")))? { | ||||
|                 if let Ok(meta) = serde_json::from_slice::<KeyspaceMetadata>(&bytes) { | ||||
|                     keyspaces.push(meta); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         Ok(keyspaces) | ||||
|     } | ||||
|  | ||||
|     /// Unlock a keyspace by name and password, returning the decrypted data | ||||
|     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))); | ||||
|         use crate::crypto::{kdf}; | ||||
|         use serde_json; | ||||
|         // 1. Fetch keyspace metadata | ||||
|         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 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 { | ||||
|             debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] ERROR: salt length {} != 16", metadata.salt.len())); | ||||
|             return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string())); | ||||
|         } | ||||
|         // 2. Derive key | ||||
|         let key = match metadata.kdf.as_str() { | ||||
|             "scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] kdf scrypt error: {}", e)); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             "pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000), | ||||
|             _ => { | ||||
|                 debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] 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_log(&format!("[DEBUG][UNLOCK_KEYSPACE] derived key (hex): {}", hex::encode(&key))); | ||||
|         // 3. Split nonce and ciphertext | ||||
|         let ciphertext = &metadata.encrypted_blob; | ||||
|         if ciphertext.len() < 12 { | ||||
|             debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] ciphertext too short: {}", ciphertext.len())); | ||||
|             return Err(VaultError::Crypto("Ciphertext too short".to_string())); | ||||
|         } | ||||
|         let (nonce, ct) = ciphertext.split_at(12); | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] nonce: {} ct: {}", hex::encode(nonce), hex::encode(ct))); | ||||
|         // 4. Decrypt | ||||
|         let plaintext = match metadata.cipher.as_str() { | ||||
|             "chacha20poly1305" => match decrypt_chacha20(&key, ct, nonce) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] chacha20poly1305 error: {}", e)); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             "aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] aes-gcm error: {}", e)); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             _ => { | ||||
|                 debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] unsupported cipher: {}", metadata.cipher)); | ||||
|                 return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher))); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] plaintext decrypted: {} bytes", plaintext.len())); | ||||
|         // 4. Deserialize keyspace data | ||||
|         let keyspace_data: KeyspaceData = match serde_json::from_slice(&plaintext) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][UNLOCK_KEYSPACE] serde_json data error: {}", e)); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         debug_log("[DEBUG][UNLOCK_KEYSPACE] success"); | ||||
|         Ok(keyspace_data) | ||||
|     } | ||||
|  | ||||
|     /// Lock a keyspace (remove from cache, if any) | ||||
|     /// Lock a keyspace (remove from cache, if any) | ||||
|     pub fn lock_keyspace(&mut self, _name: &str) { | ||||
|         // Optional: clear from in-memory cache | ||||
|     } | ||||
|  | ||||
|     // --- Keypair Management APIs --- | ||||
|  | ||||
|     /// Add a new keypair to a keyspace (generates and stores a new keypair) | ||||
|     pub async fn add_keypair(&mut self, keyspace: &str, password: &[u8], key_type: KeyType, metadata: Option<KeyMetadata>) -> Result<String, VaultError> { | ||||
|         use crate::data::KeyEntry; | ||||
|         use rand_core::OsRng; | ||||
|         use rand_core::RngCore; | ||||
|  | ||||
|         // 1. Unlock keyspace | ||||
|         let mut data = self.unlock_keyspace(keyspace, password).await?; | ||||
|         // 2. Generate keypair | ||||
|         let (private_key, public_key, id) = match key_type { | ||||
|             KeyType::Ed25519 => { | ||||
|                 use ed25519_dalek::{SigningKey, VerifyingKey}; | ||||
|                 let mut bytes = [0u8; 32]; | ||||
|                 OsRng.fill_bytes(&mut bytes); | ||||
|                 let signing = SigningKey::from_bytes(&bytes); | ||||
|                 let verifying: VerifyingKey = (&signing).into(); | ||||
|                 let priv_bytes = signing.to_bytes().to_vec(); | ||||
|                 let pub_bytes = verifying.to_bytes().to_vec(); | ||||
|                 let id = hex::encode(&pub_bytes); | ||||
|                 (priv_bytes, pub_bytes, id) | ||||
|             }, | ||||
|             KeyType::Secp256k1 => { | ||||
|                 use k256::ecdsa::SigningKey; | ||||
|  | ||||
|                 let sk = SigningKey::random(&mut OsRng); | ||||
|                 let pk = sk.verifying_key(); | ||||
|                 let priv_bytes = sk.to_bytes().to_vec(); | ||||
|                 let pub_bytes = pk.to_encoded_point(false).as_bytes().to_vec(); | ||||
|                 let id = hex::encode(&pub_bytes); | ||||
|                 (priv_bytes, pub_bytes, id) | ||||
|             }, | ||||
|         }; | ||||
|         // 3. Add to keypairs | ||||
|         let entry = KeyEntry { | ||||
|             id: id.clone(), | ||||
|             key_type, | ||||
|             private_key, | ||||
|             public_key, | ||||
|             metadata, | ||||
|         }; | ||||
|         data.keypairs.push(entry); | ||||
|         // 4. Re-encrypt and store | ||||
|         self.save_keyspace(keyspace, password, &data).await?; | ||||
|         Ok(id) | ||||
|     } | ||||
|  | ||||
|     /// Remove a keypair by id from a keyspace | ||||
|     pub async fn remove_keypair(&mut self, keyspace: &str, password: &[u8], key_id: &str) -> Result<(), VaultError> { | ||||
|         let mut data = self.unlock_keyspace(keyspace, password).await?; | ||||
|         data.keypairs.retain(|k| k.id != key_id); | ||||
|         self.save_keyspace(keyspace, password, &data).await | ||||
|     } | ||||
|  | ||||
|     /// List all keypairs in a keyspace (public info only) | ||||
|     pub async fn list_keypairs(&self, keyspace: &str, password: &[u8]) -> Result<Vec<(String, KeyType)>, VaultError> { | ||||
|         let data = self.unlock_keyspace(keyspace, password).await?; | ||||
|         Ok(data.keypairs.iter().map(|k| (k.id.clone(), k.key_type.clone())).collect()) | ||||
|     } | ||||
|  | ||||
|     /// Export a keypair's private and public key by id | ||||
|     pub async fn export_keypair(&self, keyspace: &str, password: &[u8], key_id: &str) -> Result<(Vec<u8>, Vec<u8>), VaultError> { | ||||
|         let data = self.unlock_keyspace(keyspace, password).await?; | ||||
|         let key = data.keypairs.iter().find(|k| k.id == key_id).ok_or(VaultError::KeyNotFound(key_id.to_string()))?; | ||||
|         Ok((key.private_key.clone(), key.public_key.clone())) | ||||
|     } | ||||
|  | ||||
|     /// Save the updated keyspace data (helper) | ||||
|     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))); | ||||
|         use crate::crypto::kdf; | ||||
|         use serde_json; | ||||
|         // 1. Fetch metadata | ||||
|         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))); | ||||
|         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()))?; | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] metadata: kdf={} cipher={} salt={:?}", metadata.kdf, metadata.cipher, metadata.salt)); | ||||
|         if metadata.salt.len() != 16 { | ||||
|             debug_log(&format!("[DEBUG][SAVE_KEYSPACE] ERROR: salt length {} != 16", metadata.salt.len())); | ||||
|             return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string())); | ||||
|         } | ||||
|         // 2. Derive key | ||||
|         let key = match metadata.kdf.as_str() { | ||||
|             "scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) { | ||||
|                 Ok(val) => val, | ||||
|                 Err(e) => { | ||||
|                     debug_log(&format!("[DEBUG][SAVE_KEYSPACE] kdf scrypt error: {}", e)); | ||||
|                     return Err(VaultError::Crypto(e)); | ||||
|                 } | ||||
|             }, | ||||
|             "pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000), | ||||
|             _ => { | ||||
|                 debug_log(&format!("[DEBUG][SAVE_KEYSPACE] unsupported KDF: {}", metadata.kdf)); | ||||
|                 return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf))); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] derived key: {} bytes", key.len())); | ||||
|         // 3. Serialize plaintext | ||||
|         let plaintext = match serde_json::to_vec(data) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][SAVE_KEYSPACE] serde_json data error: {}", e)); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] plaintext serialized: {} bytes", plaintext.len())); | ||||
|         // 4. Generate nonce | ||||
|         let nonce = random_salt(12); | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] nonce: {}", hex::encode(&nonce))); | ||||
|         // 5. Encrypt | ||||
|         let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext, &metadata.cipher)?; | ||||
|         debug_log(&format!("[DEBUG][SAVE_KEYSPACE] encrypted_blob: {} bytes", encrypted_blob.len())); | ||||
|         // 6. Store new encrypted blob | ||||
|         metadata.encrypted_blob = encrypted_blob; | ||||
|         let meta_bytes = match serde_json::to_vec(&metadata) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][SAVE_KEYSPACE] serde_json metadata error: {}", e)); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         self.storage.set(keyspace, &meta_bytes).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|         debug_log("[DEBUG][SAVE_KEYSPACE] success"); | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Sign a message with a stored keypair in a keyspace | ||||
|     /// | ||||
|     /// # Arguments | ||||
|     /// * `keyspace` - Keyspace name | ||||
|     /// * `password` - Keyspace password | ||||
|     /// * `key_id` - Keypair ID | ||||
|     /// * `message` - Message to sign | ||||
|     pub async fn sign(&self, keyspace: &str, password: &[u8], key_id: &str, message: &[u8]) -> Result<Vec<u8>, VaultError> { | ||||
|         let data = self.unlock_keyspace(keyspace, password).await?; | ||||
|         let key = data.keypairs.iter().find(|k| k.id == key_id).ok_or(VaultError::KeyNotFound(key_id.to_string()))?; | ||||
|         match key.key_type { | ||||
|             KeyType::Ed25519 => { | ||||
|                 use ed25519_dalek::{SigningKey, Signer}; | ||||
|                 let signing = SigningKey::from_bytes(&key.private_key.clone().try_into().map_err(|_| VaultError::Crypto("Invalid Ed25519 private key length".to_string()))?); | ||||
|                 let sig = signing.sign(message); | ||||
|                 Ok(sig.to_bytes().to_vec()) | ||||
|             } | ||||
|             KeyType::Secp256k1 => { | ||||
|                 use k256::ecdsa::{SigningKey, signature::Signer}; | ||||
|                 let arr: &[u8; 32] = key.private_key.as_slice().try_into().map_err(|_| VaultError::Crypto("Invalid secp256k1 private key length".to_string()))?; | ||||
|                 let sk = SigningKey::from_bytes(arr.into()).map_err(|e| VaultError::Crypto(e.to_string()))?; | ||||
|                 let sig: k256::ecdsa::DerSignature = sk.sign(message); | ||||
|                 Ok(sig.to_vec()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Verify a signature with a stored keypair in a keyspace | ||||
|     /// | ||||
|     /// # Arguments | ||||
|     /// * `keyspace` - Keyspace name | ||||
|     /// * `password` - Keyspace password | ||||
|     /// * `key_id` - Keypair ID | ||||
|     /// * `message` - Message that was signed | ||||
|     /// * `signature` - Signature to verify | ||||
|     pub async fn verify(&self, keyspace: &str, password: &[u8], key_id: &str, message: &[u8], signature: &[u8]) -> Result<bool, VaultError> { | ||||
|         let data = self.unlock_keyspace(keyspace, password).await?; | ||||
|         let key = data.keypairs.iter().find(|k| k.id == key_id).ok_or(VaultError::KeyNotFound(key_id.to_string()))?; | ||||
|         match key.key_type { | ||||
|             KeyType::Ed25519 => { | ||||
|                 use ed25519_dalek::{VerifyingKey, Signature, Verifier}; | ||||
|                 let verifying = VerifyingKey::from_bytes(&key.public_key.clone().try_into().map_err(|_| VaultError::Crypto("Invalid Ed25519 public key length".to_string()))?) | ||||
|                     .map_err(|e| VaultError::Crypto(e.to_string()))?; | ||||
|                 let sig = Signature::from_bytes(&signature.try_into().map_err(|_| VaultError::Crypto("Invalid Ed25519 signature length".to_string()))?); | ||||
|                 Ok(verifying.verify(message, &sig).is_ok()) | ||||
|             } | ||||
|             KeyType::Secp256k1 => { | ||||
|                 use k256::ecdsa::{VerifyingKey, Signature, signature::Verifier}; | ||||
|                 let pk = VerifyingKey::from_sec1_bytes(&key.public_key).map_err(|e| VaultError::Crypto(e.to_string()))?; | ||||
|                 let sig = Signature::from_der(signature).map_err(|e| VaultError::Crypto(e.to_string()))?; | ||||
|                 Ok(pk.verify(message, &sig).is_ok()) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
| /// Encrypt a message using the keyspace symmetric cipher | ||||
| /// (for simplicity, uses keyspace password-derived key) | ||||
| pub async fn encrypt(&self, keyspace: &str, password: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, VaultError> { | ||||
|     debug_log("[DEBUG][ENTER] encrypt"); | ||||
|     debug_log(&format!("[DEBUG][encrypt] keyspace={}", keyspace)); | ||||
|     use crate::crypto::{kdf}; | ||||
|     // 1. Load keyspace metadata | ||||
|     let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|     let meta_bytes = match meta_bytes { | ||||
|         Some(val) => val, | ||||
|         None => { | ||||
|             debug_log("[DEBUG][ERR] encrypt: keyspace not found"); | ||||
|             return Err(VaultError::Other("Keyspace not found".to_string())); | ||||
|         } | ||||
|     }; | ||||
|     let meta: KeyspaceMetadata = match serde_json::from_slice(&meta_bytes) { | ||||
|         Ok(val) => val, | ||||
|         Err(e) => { | ||||
|             debug_log(&format!("[DEBUG][ERR] encrypt: serialization error: {}", e)); | ||||
|             return Err(VaultError::Serialization(e.to_string())); | ||||
|         } | ||||
|     }; | ||||
|     debug_log(&format!("[DEBUG][encrypt] salt={:?} cipher={} (hex salt: {})", meta.salt, meta.cipher, hex::encode(&meta.salt))); | ||||
|     // 2. Derive key | ||||
|     let key = match meta.kdf.as_str() { | ||||
|         "scrypt" => match kdf::derive_key_scrypt(password, &meta.salt, 32) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] encrypt: kdf scrypt error: {}", e)); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         "pbkdf2" => kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000), | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ERR] encrypt: unsupported KDF: {}", meta.kdf)); | ||||
|             return Err(VaultError::Other(format!("Unsupported KDF: {}", meta.kdf))); | ||||
|         } | ||||
|     }; | ||||
|     // 3. Generate nonce | ||||
|     let nonce = random_salt(12); | ||||
|     debug_log(&format!("[DEBUG][encrypt] nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce))); | ||||
|     // 4. Encrypt | ||||
|     let ciphertext = match meta.cipher.as_str() { | ||||
|         "chacha20poly1305" => match encrypt_chacha20(&key, plaintext, &nonce) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] encrypt: chacha20poly1305 error: {}", e)); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         "aes-gcm" => match encrypt_aes_gcm(&key, plaintext, &nonce) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] encrypt: aes-gcm error: {}", e)); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ERR] encrypt: unsupported cipher: {}", meta.cipher)); | ||||
|             return Err(VaultError::Other(format!("Unsupported cipher: {}", meta.cipher))); | ||||
|         } | ||||
|     }; | ||||
|     // 5. Prepend nonce to ciphertext | ||||
|     let mut out = nonce; | ||||
|     out.extend_from_slice(&ciphertext); | ||||
|     Ok(out) | ||||
| } | ||||
|  | ||||
| /// Decrypt a message using the keyspace symmetric cipher | ||||
| /// (for simplicity, uses keyspace password-derived key) | ||||
| pub async fn decrypt(&self, keyspace: &str, password: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, VaultError> { | ||||
|     debug_log("[DEBUG][ENTER] decrypt"); | ||||
|     debug_log(&format!("[DEBUG][decrypt] keyspace={}", keyspace)); | ||||
|     use crate::crypto::{kdf}; | ||||
|     // 1. Fetch metadata | ||||
|     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 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))); | ||||
|     // 2. Derive key | ||||
|     let key = match metadata.kdf.as_str() { | ||||
|         "scrypt" => match kdf::derive_key_scrypt(password, &metadata.salt, 32) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] decrypt: storage error: {:?}", e)); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         "pbkdf2" => kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000), | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ERR] decrypt: unsupported KDF: {}", metadata.kdf)); | ||||
|             return Err(VaultError::Other(format!("Unsupported KDF: {}", metadata.kdf))); | ||||
|         } | ||||
|     }; | ||||
|     // 3. Split nonce and ciphertext | ||||
|     if ciphertext.len() < 12 { | ||||
|         debug_log(&format!("[DEBUG][ERR] decrypt: ciphertext too short: {}", ciphertext.len())); | ||||
|         return Err(VaultError::Crypto("Ciphertext too short".to_string())); | ||||
|     } | ||||
|     let (nonce, ct) = ciphertext.split_at(12); | ||||
|     debug_log(&format!("[DEBUG][decrypt] nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce))); | ||||
|     // 4. Decrypt | ||||
|     let plaintext = match metadata.cipher.as_str() { | ||||
|         "chacha20poly1305" => match decrypt_chacha20(&key, ct, nonce) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] decrypt: chacha20poly1305 error: {}", e)); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         "aes-gcm" => match decrypt_aes_gcm(&key, ct, nonce) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug_log(&format!("[DEBUG][ERR] decrypt: aes-gcm error: {}", e)); | ||||
|                 return Err(VaultError::Crypto(e)); | ||||
|             } | ||||
|         }, | ||||
|         _ => { | ||||
|             debug_log(&format!("[DEBUG][ERR] decrypt: unsupported cipher: {}", metadata.cipher)); | ||||
|             return Err(VaultError::Other(format!("Unsupported cipher: {}", metadata.cipher))); | ||||
|         } | ||||
|     }; | ||||
|     Ok(plaintext) | ||||
| } | ||||
|  | ||||
| } // <-- Close the impl block | ||||
|   | ||||
							
								
								
									
										22
									
								
								vault/src/session.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vault/src/session.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| //! Session manager for the vault crate (optional) | ||||
|  | ||||
| use crate::data::KeyspaceData; | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| pub struct SessionManager { | ||||
|     unlocked_keyspaces: HashMap<String, KeyspaceData>, | ||||
|     current_keyspace: Option<String>, | ||||
|     current_keypair: Option<String>, | ||||
|     // ... | ||||
| } | ||||
|  | ||||
| impl SessionManager { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
|             unlocked_keyspaces: HashMap::new(), | ||||
|             current_keyspace: None, | ||||
|             current_keypair: None, | ||||
|         } | ||||
|     } | ||||
|     // ... methods for unlock, lock, select, timeout, etc. | ||||
| } | ||||
							
								
								
									
										19
									
								
								vault/src/utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								vault/src/utils.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| //! Utility functions for the vault crate | ||||
|  | ||||
| // Add serialization helpers, random salt generation, etc. | ||||
|  | ||||
| /// Returns the current unix timestamp as u64 (seconds since epoch) | ||||
| pub fn now() -> u64 { | ||||
|     #[cfg(target_arch = "wasm32")] | ||||
|     { | ||||
|         // Use JS Date.now() in milliseconds, convert to seconds | ||||
|         use wasm_bindgen::prelude::*; | ||||
|         let date = js_sys::Date::new_0(); | ||||
|         (date.get_time() / 1000.0) as u64 | ||||
|     } | ||||
|     #[cfg(not(target_arch = "wasm32"))] | ||||
|     { | ||||
|         use std::time::{SystemTime, UNIX_EPOCH}; | ||||
|         SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										75
									
								
								vault/tests/keypair_management.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								vault/tests/keypair_management.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| #![cfg(not(target_arch = "wasm32"))] | ||||
| //! Tests for vault keypair management and crypto operations | ||||
| use vault::{Vault, KeyType, KeyMetadata}; | ||||
| use kvstore::native::NativeStore; | ||||
|  | ||||
| fn debug_log(msg: &str) { | ||||
|     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] | ||||
| async fn test_keypair_management_and_crypto() { | ||||
|     debug_log("[DEBUG][TEST] test_keypair_management_and_crypto started"); | ||||
|     // Use NativeStore for native tests | ||||
|     #[cfg(not(target_arch = "wasm32"))] | ||||
|     let store = NativeStore::open("vault_native_test").expect("Failed to open native store"); | ||||
|     #[cfg(not(target_arch = "wasm32"))] | ||||
|     let mut vault = Vault::new(store); | ||||
|     #[cfg(target_arch = "wasm32")] | ||||
|     compile_error!("This test is not intended for wasm32 targets"); | ||||
|     let keyspace = &format!("testspace_{}", chrono::Utc::now().timestamp_nanos()); | ||||
|     let password = b"supersecret"; | ||||
|  | ||||
|     debug_log(&format!("[DEBUG][TEST] keyspace: {} password: {}", keyspace, hex::encode(password))); | ||||
|     debug_log("[DEBUG][TEST] before create_keyspace"); | ||||
|     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_log("[DEBUG][TEST] 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; | ||||
|     match &key_id { | ||||
|         Ok(_) => debug_log("[DEBUG][TEST] after add Ed25519 keypair (Ok)"), | ||||
|         Err(e) => debug_log(&format!("[DEBUG][TEST] after add Ed25519 keypair (Err): {:?}", e)), | ||||
|     } | ||||
|     let key_id = key_id.unwrap(); | ||||
|     debug_log("[DEBUG][TEST] 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(); | ||||
|  | ||||
|     debug_log("[DEBUG][TEST] before list_keypairs"); | ||||
|     let keys = vault.list_keypairs(keyspace, password).await.unwrap(); | ||||
|     assert_eq!(keys.len(), 2); | ||||
|  | ||||
|     debug_log("[DEBUG][TEST] before export Ed25519 keypair"); | ||||
|     let (priv_bytes, pub_bytes) = vault.export_keypair(keyspace, password, &key_id).await.unwrap(); | ||||
|     assert!(!priv_bytes.is_empty() && !pub_bytes.is_empty()); | ||||
|  | ||||
|     debug_log("[DEBUG][TEST] before sign Ed25519"); | ||||
|     let msg = b"hello world"; | ||||
|     let sig = vault.sign(keyspace, password, &key_id, msg).await.unwrap(); | ||||
|     debug_log("[DEBUG][TEST] before verify Ed25519"); | ||||
|     let ok = vault.verify(keyspace, password, &key_id, msg, &sig).await.unwrap(); | ||||
|     assert!(ok); | ||||
|  | ||||
|     debug_log("[DEBUG][TEST] before sign secp256k1"); | ||||
|     let sig2 = vault.sign(keyspace, password, &secp_id, msg).await.unwrap(); | ||||
|     debug_log("[DEBUG][TEST] before verify secp256k1"); | ||||
|     let ok2 = vault.verify(keyspace, password, &secp_id, msg, &sig2).await.unwrap(); | ||||
|     assert!(ok2); | ||||
|  | ||||
|     // Encrypt and decrypt | ||||
|     let ciphertext = vault.encrypt(keyspace, password, msg).await.unwrap(); | ||||
|     let plaintext = vault.decrypt(keyspace, password, &ciphertext).await.unwrap(); | ||||
|     assert_eq!(plaintext, msg); | ||||
|  | ||||
|     // Remove a keypair | ||||
|     vault.remove_keypair(keyspace, password, &key_id).await.unwrap(); | ||||
|     let keys = vault.list_keypairs(keyspace, password).await.unwrap(); | ||||
|     assert_eq!(keys.len(), 1); | ||||
| } | ||||
							
								
								
									
										35
									
								
								vault/tests/mock_store.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								vault/tests/mock_store.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| //! In-memory mock key-value store for testing vault logic (native only) | ||||
| use kvstore::KVStore; | ||||
| use std::collections::HashMap; | ||||
| use std::sync::{Arc, Mutex}; | ||||
|  | ||||
| #[derive(Clone, Default)] | ||||
| pub struct MockStore { | ||||
|     inner: Arc<Mutex<HashMap<String, Vec<u8>>>>, | ||||
| } | ||||
|  | ||||
| #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] | ||||
| #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] | ||||
| impl KVStore for MockStore { | ||||
|     async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, kvstore::KVError> { | ||||
|         Ok(self.inner.lock().unwrap().get(key).cloned()) | ||||
|     } | ||||
|     async fn set(&self, key: &str, value: &[u8]) -> Result<(), kvstore::KVError> { | ||||
|         self.inner.lock().unwrap().insert(key.to_string(), value.to_vec()); | ||||
|         Ok(()) | ||||
|     } | ||||
|     async fn remove(&self, key: &str) -> Result<(), kvstore::KVError> { | ||||
|         self.inner.lock().unwrap().remove(key); | ||||
|         Ok(()) | ||||
|     } | ||||
|     async fn contains_key(&self, key: &str) -> Result<bool, kvstore::KVError> { | ||||
|         Ok(self.inner.lock().unwrap().contains_key(key)) | ||||
|     } | ||||
|     async fn keys(&self) -> Result<Vec<String>, kvstore::KVError> { | ||||
|         Ok(self.inner.lock().unwrap().keys().cloned().collect()) | ||||
|     } | ||||
|     async fn clear(&self) -> Result<(), kvstore::KVError> { | ||||
|         self.inner.lock().unwrap().clear(); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										128
									
								
								vault/tests/wasm_keypair_management.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								vault/tests/wasm_keypair_management.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,128 @@ | ||||
| #![cfg(target_arch = "wasm32")] | ||||
| //! WASM/browser tests for vault keypair management and crypto operations | ||||
| use wasm_bindgen_test::*; | ||||
| use vault::{Vault, KeyType, KeyMetadata}; | ||||
| use kvstore::wasm::WasmStore; | ||||
| use console_error_panic_hook; | ||||
|  | ||||
| wasm_bindgen_test_configure!(run_in_browser); | ||||
|  | ||||
| #[wasm_bindgen_test(async)] | ||||
| async fn wasm_test_keypair_management_and_crypto() { | ||||
|     console_error_panic_hook::set_once(); | ||||
|     let store = WasmStore::open("vault_idb_test").await.expect("Failed to open IndexedDB store"); | ||||
|     let mut vault = Vault::new(store); | ||||
|     let keyspace = "wasmspace"; | ||||
|     let password = b"supersecret"; | ||||
|     println!("[DEBUG] Initialized vault and IndexedDB store"); | ||||
|  | ||||
|     // Step 1: Create keyspace | ||||
|     match vault.create_keyspace(keyspace, password, "pbkdf2", "chacha20poly1305", None).await { | ||||
|         Ok(_) => println!("[DEBUG] Created keyspace"), | ||||
|         Err(e) => { println!("[ERROR] Failed to create keyspace: {:?}", e); return; } | ||||
|     } | ||||
|  | ||||
|     // 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 { | ||||
|         Ok(id) => { println!("[DEBUG] Added Ed25519 keypair: {}", id); id }, | ||||
|         Err(e) => { println!("[ERROR] Failed to add Ed25519 keypair: {:?}", e); return; } | ||||
|     }; | ||||
|  | ||||
|     // 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 { | ||||
|         Ok(id) => { println!("[DEBUG] Added Secp256k1 keypair: {}", id); id }, | ||||
|         Err(e) => { println!("[ERROR] Failed to add Secp256k1 keypair: {:?}", e); return; } | ||||
|     }; | ||||
|  | ||||
|     // Step 4: List keypairs | ||||
|     let keys = match vault.list_keypairs(keyspace, password).await { | ||||
|         Ok(keys) => { println!("[DEBUG] Listed keypairs: {:?}", keys); keys }, | ||||
|         Err(e) => { println!("[ERROR] Failed to list keypairs: {:?}", e); return; } | ||||
|     }; | ||||
|     if keys.len() != 2 { | ||||
|         println!("[ERROR] Expected 2 keypairs, got {}", keys.len()); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 5: Export Ed25519 keypair | ||||
|     let (priv_bytes, pub_bytes) = match vault.export_keypair(keyspace, password, &key_id).await { | ||||
|         Ok((priv_bytes, pub_bytes)) => { | ||||
|             println!("[DEBUG] Exported Ed25519 keypair, priv: {} bytes, pub: {} bytes", priv_bytes.len(), pub_bytes.len()); | ||||
|             (priv_bytes, pub_bytes) | ||||
|         }, | ||||
|         Err(e) => { println!("[ERROR] Failed to export Ed25519 keypair: {:?}", e); return; } | ||||
|     }; | ||||
|     if priv_bytes.is_empty() || pub_bytes.is_empty() { | ||||
|         println!("[ERROR] Exported Ed25519 keypair bytes are empty"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 6: Sign and verify with Ed25519 | ||||
|     let msg = b"hello wasm"; | ||||
|     let sig = match vault.sign(keyspace, password, &key_id, msg).await { | ||||
|         Ok(sig) => { println!("[DEBUG] Signed message with Ed25519"); sig }, | ||||
|         Err(e) => { println!("[ERROR] Failed to sign with Ed25519: {:?}", e); return; } | ||||
|     }; | ||||
|     let ok = match vault.verify(keyspace, password, &key_id, msg, &sig).await { | ||||
|         Ok(ok) => { println!("[DEBUG] Verified Ed25519 signature: {}", ok); ok }, | ||||
|         Err(e) => { println!("[ERROR] Failed to verify Ed25519 signature: {:?}", e); return; } | ||||
|     }; | ||||
|     if !ok { | ||||
|         println!("[ERROR] Ed25519 signature verification failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 7: Sign and verify with Secp256k1 | ||||
|     let sig2 = match vault.sign(keyspace, password, &secp_id, msg).await { | ||||
|         Ok(sig) => { println!("[DEBUG] Signed message with Secp256k1"); sig }, | ||||
|         Err(e) => { println!("[ERROR] Failed to sign with Secp256k1: {:?}", e); return; } | ||||
|     }; | ||||
|     let ok2 = match vault.verify(keyspace, password, &secp_id, msg, &sig2).await { | ||||
|         Ok(ok) => { println!("[DEBUG] Verified Secp256k1 signature: {}", ok); ok }, | ||||
|         Err(e) => { println!("[ERROR] Failed to verify Secp256k1 signature: {:?}", e); return; } | ||||
|     }; | ||||
|     if !ok2 { | ||||
|         println!("[ERROR] Secp256k1 signature verification failed"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 8: Encrypt and decrypt | ||||
|     let ciphertext = match vault.encrypt(keyspace, password, msg).await { | ||||
|         Ok(ct) => { println!("[DEBUG] Encrypted message"); ct }, | ||||
|         Err(e) => { println!("[ERROR] Failed to encrypt message: {:?}", e); return; } | ||||
|     }; | ||||
|     let plaintext = match vault.decrypt(keyspace, password, &ciphertext).await { | ||||
|         Ok(pt) => { println!("[DEBUG] Decrypted message"); pt }, | ||||
|         Err(e) => { println!("[ERROR] Failed to decrypt message: {:?}", e); return; } | ||||
|     }; | ||||
|     if plaintext != msg { | ||||
|         println!("[ERROR] Decrypted message does not match original"); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     // Step 9: Remove Ed25519 keypair | ||||
|     match vault.remove_keypair(keyspace, password, &key_id).await { | ||||
|         Ok(_) => println!("[DEBUG] Removed Ed25519 keypair"), | ||||
|         Err(e) => { println!("[ERROR] Failed to remove Ed25519 keypair: {:?}", e); return; } | ||||
|     } | ||||
|     let keys = match vault.list_keypairs(keyspace, password).await { | ||||
|         Ok(keys) => { println!("[DEBUG] Listed keypairs after removal: {:?}", keys); keys }, | ||||
|         Err(e) => { println!("[ERROR] Failed to list keypairs after removal: {:?}", e); return; } | ||||
|     }; | ||||
|     if keys.len() != 1 { | ||||
|         println!("[ERROR] Expected 1 keypair after removal, got {}", keys.len()); | ||||
|         return; | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| wasm_bindgen_test_configure!(run_in_browser); | ||||
|  | ||||
| #[wasm_bindgen_test] | ||||
| fn sanity_check() { | ||||
|     assert_eq!(2 + 2, 4); | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										121
									
								
								vault/vault_crypto_debug.log
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								vault/vault_crypto_debug.log
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| [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 | ||||
							
								
								
									
										4
									
								
								vault/vault_native_test/conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								vault/vault_native_test/conf
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| segment_size: 524288 | ||||
| use_compression: false | ||||
| version: 0.34 | ||||
| vQ<> | ||||
							
								
								
									
										
											BIN
										
									
								
								vault/vault_native_test/db
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vault/vault_native_test/db
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								vault/vault_native_test/snap.00000000000022A1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								vault/vault_native_test/snap.00000000000022A1
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user