refactor: migrate extension to TypeScript and add Material-UI components
This commit is contained in:
		
							
								
								
									
										526
									
								
								vault/src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										526
									
								
								vault/src/lib.rs
									
									
									
									
									
								
							| @@ -1,34 +1,32 @@ | ||||
| //! vault: Cryptographic keyspace and operations | ||||
|  | ||||
|  | ||||
| //! vault: Cryptographic keyspace and operations | ||||
|  | ||||
| pub mod data; | ||||
| pub use crate::data::{KeyEntry, KeyMetadata, KeyType}; | ||||
| pub use crate::session::SessionManager; | ||||
| pub use crate::data::{KeyType, KeyMetadata, KeyEntry}; | ||||
| mod error; | ||||
| mod crypto; | ||||
| mod error; | ||||
| pub mod rhai_bindings; | ||||
| mod rhai_sync_helpers; | ||||
| pub mod session; | ||||
| mod utils; | ||||
| mod rhai_sync_helpers; | ||||
| pub mod rhai_bindings; | ||||
|  | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| pub mod session_singleton; | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| pub mod wasm_helpers; | ||||
|  | ||||
|  | ||||
| pub use kvstore::traits::KVStore; | ||||
| use crate::crypto::kdf; | ||||
| use crate::crypto::random_salt; | ||||
| use data::*; | ||||
| use error::VaultError; | ||||
| use crate::crypto::random_salt; | ||||
| use crate::crypto::kdf; | ||||
| pub use kvstore::traits::KVStore; | ||||
|  | ||||
| use crate::crypto::cipher::{encrypt_chacha20, decrypt_chacha20}; | ||||
| use crate::crypto::cipher::{decrypt_chacha20, encrypt_chacha20}; | ||||
| use signature::SignatureEncoding; | ||||
| // TEMP: File-based debug logger for crypto troubleshooting | ||||
| use log::{debug}; | ||||
| use log::debug; | ||||
|  | ||||
| /// Vault: Cryptographic keyspace and operations | ||||
| pub struct Vault<S: KVStore> { | ||||
| @@ -43,8 +41,7 @@ fn encrypt_with_nonce_prepended(key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, | ||||
|     let nonce = random_salt(12); | ||||
|     debug!("nonce: {}", hex::encode(&nonce)); | ||||
|     // Always use ChaCha20Poly1305 for encryption | ||||
|     let ct = encrypt_chacha20(key, plaintext, &nonce) | ||||
|         .map_err(|e| VaultError::Crypto(e))?; | ||||
|     let ct = encrypt_chacha20(key, plaintext, &nonce).map_err(|e| VaultError::Crypto(e))?; | ||||
|     debug!("ct: {}", hex::encode(&ct)); | ||||
|     debug!("key: {}", hex::encode(key)); | ||||
|     let mut blob = nonce.clone(); | ||||
| @@ -60,17 +57,28 @@ impl<S: KVStore> Vault<S> { | ||||
|  | ||||
|     /// Create a new keyspace with the given name, password, and options. | ||||
|     /// Create a new keyspace with the given name and password. Always uses PBKDF2 and ChaCha20Poly1305. | ||||
| pub async fn create_keyspace(&mut self, name: &str, password: &[u8], tags: Option<Vec<String>>) -> Result<(), VaultError> { | ||||
|     pub async fn create_keyspace( | ||||
|         &mut self, | ||||
|         name: &str, | ||||
|         password: &[u8], | ||||
|         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() { | ||||
|         if self | ||||
|             .storage | ||||
|             .get(name) | ||||
|             .await | ||||
|             .map_err(|e| VaultError::Storage(format!("{e:?}")))? | ||||
|             .is_some() | ||||
|         { | ||||
|             debug!("keyspace '{}' already exists", name); | ||||
|             return Err(VaultError::Crypto("Keyspace already exists".to_string())); | ||||
|         } | ||||
|         debug!("entry: name={}", name); | ||||
|         use crate::crypto::{random_salt, kdf}; | ||||
|         use crate::data::{KeyspaceMetadata, KeyspaceData}; | ||||
|         use crate::crypto::{kdf, random_salt}; | ||||
|         use crate::data::{KeyspaceData, KeyspaceMetadata}; | ||||
|         use serde_json; | ||||
|          | ||||
|  | ||||
|         // 1. Generate salt | ||||
|         let salt = random_salt(16); | ||||
|         debug!("salt: {:?}", salt); | ||||
| @@ -112,7 +120,10 @@ pub async fn create_keyspace(&mut self, name: &str, password: &[u8], tags: Optio | ||||
|                 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!("success"); | ||||
|         Ok(()) | ||||
|     } | ||||
| @@ -121,10 +132,19 @@ pub async fn create_keyspace(&mut self, name: &str, password: &[u8], tags: Optio | ||||
|     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 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 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); | ||||
|                 } | ||||
| @@ -136,31 +156,42 @@ pub async fn create_keyspace(&mut self, name: &str, password: &[u8], tags: Optio | ||||
|     /// Unlock a keyspace by name and password, returning the decrypted data | ||||
|     /// Unlock a keyspace by name and password, returning the decrypted data | ||||
|     /// Always uses PBKDF2 and ChaCha20Poly1305. | ||||
|     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!("unlock_keyspace entry: name={}", name); | ||||
|         // use crate::crypto::kdf; // removed if not needed | ||||
|         use serde_json; | ||||
|         // 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:?}")))?; | ||||
|         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()))?; | ||||
|         if metadata.salt.len() != 16 { | ||||
|             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 | ||||
|         let key = kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000); | ||||
|         debug!("derived key: {} bytes", key.len()); | ||||
|      | ||||
|  | ||||
|         let ciphertext = &metadata.encrypted_blob; | ||||
|         if ciphertext.len() < 12 { | ||||
|             debug!("ciphertext too short: {}", ciphertext.len()); | ||||
|             return Err(VaultError::Crypto("Ciphertext too short".to_string())); | ||||
|         } | ||||
|      | ||||
|  | ||||
|         let (nonce, ct) = ciphertext.split_at(12); | ||||
| debug!("nonce: {}", hex::encode(nonce)); | ||||
| let plaintext = decrypt_chacha20(&key, ct, nonce).map_err(VaultError::Crypto)?; | ||||
|         debug!("nonce: {}", hex::encode(nonce)); | ||||
|         let plaintext = decrypt_chacha20(&key, ct, nonce).map_err(VaultError::Crypto)?; | ||||
|         debug!("plaintext decrypted: {} bytes", plaintext.len()); | ||||
|         // 4. Deserialize keyspace data | ||||
|         let keyspace_data: KeyspaceData = match serde_json::from_slice(&plaintext) { | ||||
| @@ -184,8 +215,14 @@ let plaintext = decrypt_chacha20(&key, ct, nonce).map_err(VaultError::Crypto)?; | ||||
|  | ||||
|     /// Add a new keypair to a keyspace (generates and stores a new keypair) | ||||
|     /// Add a new keypair to a keyspace (generates and stores a new keypair) | ||||
| /// If key_type is None, defaults to Secp256k1. | ||||
| pub async fn add_keypair(&mut self, keyspace: &str, password: &[u8], key_type: Option<KeyType>, metadata: Option<KeyMetadata>) -> Result<String, VaultError> { | ||||
|     /// If key_type is None, defaults to Secp256k1. | ||||
|     pub async fn add_keypair( | ||||
|         &mut self, | ||||
|         keyspace: &str, | ||||
|         password: &[u8], | ||||
|         key_type: Option<KeyType>, | ||||
|         metadata: Option<KeyMetadata>, | ||||
|     ) -> Result<String, VaultError> { | ||||
|         use crate::data::KeyEntry; | ||||
|         use rand_core::OsRng; | ||||
|         use rand_core::RngCore; | ||||
| @@ -194,7 +231,7 @@ pub async fn add_keypair(&mut self, keyspace: &str, password: &[u8], key_type: O | ||||
|         let mut data = self.unlock_keyspace(keyspace, password).await?; | ||||
|         // 2. Generate keypair | ||||
|         let key_type = key_type.unwrap_or(KeyType::Secp256k1); | ||||
|     let (private_key, public_key, id) = match key_type { | ||||
|         let (private_key, public_key, id) = match key_type { | ||||
|             KeyType::Ed25519 => { | ||||
|                 use ed25519_dalek::{SigningKey, VerifyingKey}; | ||||
|                 let mut bytes = [0u8; 32]; | ||||
| @@ -205,7 +242,7 @@ pub async fn add_keypair(&mut self, keyspace: &str, password: &[u8], key_type: O | ||||
|                 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; | ||||
|  | ||||
| @@ -215,7 +252,7 @@ pub async fn add_keypair(&mut self, keyspace: &str, password: &[u8], key_type: O | ||||
|                 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 { | ||||
| @@ -232,190 +269,291 @@ pub async fn add_keypair(&mut self, keyspace: &str, password: &[u8], key_type: O | ||||
|     } | ||||
|  | ||||
|     /// Remove a keypair by id from a keyspace | ||||
|     pub async fn remove_keypair(&mut self, keyspace: &str, password: &[u8], key_id: &str) -> Result<(), VaultError> { | ||||
|     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> { | ||||
|     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()) | ||||
|         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> { | ||||
|     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()))?; | ||||
|         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> { | ||||
|     async fn save_keyspace( | ||||
|         &mut self, | ||||
|         keyspace: &str, | ||||
|         password: &[u8], | ||||
|         data: &KeyspaceData, | ||||
|     ) -> Result<(), VaultError> { | ||||
|         debug!("save_keyspace entry: keyspace={}", keyspace); | ||||
|         use crate::crypto::kdf; | ||||
|         use serde_json; | ||||
|  | ||||
|     let meta_bytes = self.storage.get(keyspace).await.map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|     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 mut metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes).map_err(|e| VaultError::Serialization(e.to_string()))?; | ||||
|     debug!("metadata: salt={:?}", metadata.salt); | ||||
|     if metadata.salt.len() != 16 { | ||||
|         debug!("salt length {} != 16", metadata.salt.len()); | ||||
|         return Err(VaultError::Crypto("Salt length must be 16 bytes".to_string())); | ||||
|         let meta_bytes = self | ||||
|             .storage | ||||
|             .get(keyspace) | ||||
|             .await | ||||
|             .map_err(|e| VaultError::Storage(format!("{e:?}")))?; | ||||
|         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 mut metadata: KeyspaceMetadata = serde_json::from_slice(&meta_bytes) | ||||
|             .map_err(|e| VaultError::Serialization(e.to_string()))?; | ||||
|         debug!("metadata: salt={:?}", metadata.salt); | ||||
|         if metadata.salt.len() != 16 { | ||||
|             debug!("salt length {} != 16", metadata.salt.len()); | ||||
|             return Err(VaultError::Crypto( | ||||
|                 "Salt length must be 16 bytes".to_string(), | ||||
|             )); | ||||
|         } | ||||
|         // 2. Derive key | ||||
|         let key = kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000); | ||||
|         debug!("derived key: {} bytes", key.len()); | ||||
|         // 3. Serialize plaintext | ||||
|         let plaintext = match serde_json::to_vec(data) { | ||||
|             Ok(val) => val, | ||||
|             Err(e) => { | ||||
|                 debug!("serde_json data error: {}", e); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         debug!("plaintext serialized: {} bytes", plaintext.len()); | ||||
|         // 4. Generate nonce | ||||
|         let nonce = random_salt(12); | ||||
|         debug!("nonce: {}", hex::encode(&nonce)); | ||||
|         // 5. Encrypt | ||||
|         let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext)?; | ||||
|         debug!("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!("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!("success"); | ||||
|         Ok(()) | ||||
|     } | ||||
|     // 2. Derive key | ||||
|     let key = kdf::derive_key_pbkdf2(password, &metadata.salt, 32, 10_000); | ||||
|     debug!("derived key: {} bytes", key.len()); | ||||
|     // 3. Serialize plaintext | ||||
|     let plaintext = match serde_json::to_vec(data) { | ||||
|         Ok(val) => val, | ||||
|         Err(e) => { | ||||
|             debug!("serde_json data error: {}", e); | ||||
|             return Err(VaultError::Serialization(e.to_string())); | ||||
|         } | ||||
|     }; | ||||
|     debug!("plaintext serialized: {} bytes", plaintext.len()); | ||||
|     // 4. Generate nonce | ||||
|     let nonce = random_salt(12); | ||||
|     debug!("nonce: {}", hex::encode(&nonce)); | ||||
|     // 5. Encrypt | ||||
|     let encrypted_blob = encrypt_with_nonce_prepended(&key, &plaintext)?; | ||||
|     debug!("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!("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!("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()) | ||||
|     /// 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::{Signer, SigningKey}; | ||||
|                 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::{signature::Signer, SigningKey}; | ||||
|                 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()) | ||||
|     /// 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::{Signature, Verifier, VerifyingKey}; | ||||
|                 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::{signature::Verifier, Signature, VerifyingKey}; | ||||
|                 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!("encrypt"); | ||||
|  | ||||
|         // 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!("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!("serialization error: {}", e); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         debug!( | ||||
|             "salt={:?} (hex salt: {})", | ||||
|             meta.salt, | ||||
|             hex::encode(&meta.salt) | ||||
|         ); | ||||
|         // 2. Derive key | ||||
|         let key = kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000); | ||||
|         // 3. Generate nonce | ||||
|         let nonce = random_salt(12); | ||||
|         debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce)); | ||||
|         // 4. Encrypt | ||||
|         let ciphertext = encrypt_chacha20(&key, plaintext, &nonce).map_err(VaultError::Crypto)?; | ||||
|         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!("decrypt"); | ||||
|  | ||||
|         // 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!("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!("serialization error: {}", e); | ||||
|                 return Err(VaultError::Serialization(e.to_string())); | ||||
|             } | ||||
|         }; | ||||
|         debug!( | ||||
|             "salt={:?} (hex salt: {})", | ||||
|             meta.salt, | ||||
|             hex::encode(&meta.salt) | ||||
|         ); | ||||
|         // 2. Derive key | ||||
|         let key = kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000); | ||||
|         // 3. Extract nonce | ||||
|         let nonce = &ciphertext[..12]; | ||||
|         debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce)); | ||||
|         // 4. Decrypt | ||||
|         let plaintext = | ||||
|             decrypt_chacha20(&key, &ciphertext[12..], nonce).map_err(VaultError::Crypto)?; | ||||
|         Ok(plaintext) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// 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!("encrypt"); | ||||
|  | ||||
|     // 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!("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!("serialization error: {}", e); | ||||
|             return Err(VaultError::Serialization(e.to_string())); | ||||
|         } | ||||
|     }; | ||||
|     debug!("salt={:?} (hex salt: {})", meta.salt, hex::encode(&meta.salt)); | ||||
|     // 2. Derive key | ||||
|     let key = kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000); | ||||
|     // 3. Generate nonce | ||||
|     let nonce = random_salt(12); | ||||
|     debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(&nonce)); | ||||
|     // 4. Encrypt | ||||
|     let ciphertext = encrypt_chacha20(&key, plaintext, &nonce).map_err(VaultError::Crypto)?; | ||||
|     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!("decrypt"); | ||||
|  | ||||
|     // 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!("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!("serialization error: {}", e); | ||||
|             return Err(VaultError::Serialization(e.to_string())); | ||||
|         } | ||||
|     }; | ||||
|     debug!("salt={:?} (hex salt: {})", meta.salt, hex::encode(&meta.salt)); | ||||
|     // 2. Derive key | ||||
|     let key = kdf::derive_key_pbkdf2(password, &meta.salt, 32, 10_000); | ||||
|     // 3. Extract nonce | ||||
|     let nonce = &ciphertext[..12]; | ||||
|     debug!("nonce={:?} (hex nonce: {})", nonce, hex::encode(nonce)); | ||||
|     // 4. Decrypt | ||||
|     let plaintext = decrypt_chacha20(&key, &ciphertext[12..], nonce).map_err(VaultError::Crypto)?; | ||||
|     Ok(plaintext) | ||||
| } | ||||
| } | ||||
| @@ -136,6 +136,38 @@ impl<S: KVStore + Send + Sync> SessionManager<S> { | ||||
|         self.vault.sign(name, password, &keypair.id, message).await | ||||
|     } | ||||
|  | ||||
|     /// Verify a signature using the currently selected keypair | ||||
|     pub async fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, VaultError> { | ||||
|         let (name, password, _) = self | ||||
|             .unlocked_keyspace | ||||
|             .as_ref() | ||||
|             .ok_or(VaultError::Crypto("No keyspace unlocked".to_string()))?; | ||||
|         let keypair = self | ||||
|             .current_keypair() | ||||
|             .ok_or(VaultError::Crypto("No keypair selected".to_string()))?; | ||||
|         self.vault.verify(name, password, &keypair.id, message, signature).await | ||||
|     } | ||||
|  | ||||
|     /// Encrypt data using the keyspace symmetric cipher | ||||
|     /// Returns the encrypted data with the nonce prepended | ||||
|     pub async fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, VaultError> { | ||||
|         let (name, password, _) = self | ||||
|             .unlocked_keyspace | ||||
|             .as_ref() | ||||
|             .ok_or(VaultError::Crypto("No keyspace unlocked".to_string()))?; | ||||
|         self.vault.encrypt(name, password, plaintext).await | ||||
|     } | ||||
|  | ||||
|     /// Decrypt data using the keyspace symmetric cipher | ||||
|     /// Expects the nonce to be prepended to the ciphertext (as returned by encrypt) | ||||
|     pub async fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, VaultError> { | ||||
|         let (name, password, _) = self | ||||
|             .unlocked_keyspace | ||||
|             .as_ref() | ||||
|             .ok_or(VaultError::Crypto("No keyspace unlocked".to_string()))?; | ||||
|         self.vault.decrypt(name, password, ciphertext).await | ||||
|     } | ||||
|  | ||||
|     pub fn get_vault(&self) -> &Vault<S> { | ||||
|         &self.vault | ||||
|     } | ||||
| @@ -262,6 +294,38 @@ impl<S: KVStore> SessionManager<S> { | ||||
|         self.vault.sign(name, password, &keypair.id, message).await | ||||
|     } | ||||
|  | ||||
|     /// Verify a signature using the currently selected keypair | ||||
|     pub async fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, VaultError> { | ||||
|         let (name, password, _) = self | ||||
|             .unlocked_keyspace | ||||
|             .as_ref() | ||||
|             .ok_or(VaultError::Crypto("No keyspace unlocked".to_string()))?; | ||||
|         let keypair = self | ||||
|             .current_keypair() | ||||
|             .ok_or(VaultError::Crypto("No keypair selected".to_string()))?; | ||||
|         self.vault.verify(name, password, &keypair.id, message, signature).await | ||||
|     } | ||||
|  | ||||
|     /// Encrypt data using the keyspace symmetric cipher | ||||
|     /// Returns the encrypted data with the nonce prepended | ||||
|     pub async fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, VaultError> { | ||||
|         let (name, password, _) = self | ||||
|             .unlocked_keyspace | ||||
|             .as_ref() | ||||
|             .ok_or(VaultError::Crypto("No keyspace unlocked".to_string()))?; | ||||
|         self.vault.encrypt(name, password, plaintext).await | ||||
|     } | ||||
|  | ||||
|     /// Decrypt data using the keyspace symmetric cipher | ||||
|     /// Expects the nonce to be prepended to the ciphertext (as returned by encrypt) | ||||
|     pub async fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, VaultError> { | ||||
|         let (name, password, _) = self | ||||
|             .unlocked_keyspace | ||||
|             .as_ref() | ||||
|             .ok_or(VaultError::Crypto("No keyspace unlocked".to_string()))?; | ||||
|         self.vault.decrypt(name, password, ciphertext).await | ||||
|     } | ||||
|  | ||||
|     pub fn get_vault(&self) -> &Vault<S> { | ||||
|         &self.vault | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user