Merge branch 'development_lee'
This commit is contained in:
		
							
								
								
									
										21
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								Cargo.toml
									
									
									
									
									
								
							@@ -10,18 +10,24 @@ keywords = ["system", "os", "abstraction", "platform", "filesystem"]
 | 
			
		||||
categories = ["os", "filesystem", "api-bindings"]
 | 
			
		||||
readme = "README.md"
 | 
			
		||||
 | 
			
		||||
[workspace]
 | 
			
		||||
members = [".", "vault"]
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
anyhow = "1.0.98"
 | 
			
		||||
base64 = "0.22.1"  # Base64 encoding/decoding
 | 
			
		||||
base64 = "0.22.1" # Base64 encoding/decoding
 | 
			
		||||
cfg-if = "1.0"
 | 
			
		||||
chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher
 | 
			
		||||
clap = "2.34.0" # Command-line argument parsing
 | 
			
		||||
dirs = "6.0.0"     # Directory paths
 | 
			
		||||
dirs = "6.0.0" # Directory paths
 | 
			
		||||
env_logger = "0.11.8" # Logger implementation
 | 
			
		||||
ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library
 | 
			
		||||
glob = "0.3.1" # For file pattern matching
 | 
			
		||||
jsonrpsee = "0.25.1"
 | 
			
		||||
k256 = { version = "0.13.4", features = ["ecdsa", "ecdh"] } # Elliptic curve cryptography
 | 
			
		||||
k256 = { version = "0.13.4", features = [
 | 
			
		||||
    "ecdsa",
 | 
			
		||||
    "ecdh",
 | 
			
		||||
] } # Elliptic curve cryptography
 | 
			
		||||
lazy_static = "1.4.0" # For lazy initialization of static variables
 | 
			
		||||
libc = "0.2"
 | 
			
		||||
log = "0.4" # Logging facade
 | 
			
		||||
@@ -38,7 +44,7 @@ serde = { version = "1.0", features = [
 | 
			
		||||
    "derive",
 | 
			
		||||
] } # For serialization/deserialization
 | 
			
		||||
serde_json = "1.0" # For JSON handling
 | 
			
		||||
sha2 = "0.10.7"    # SHA-2 hash functions
 | 
			
		||||
sha2 = "0.10.7" # SHA-2 hash functions
 | 
			
		||||
tempfile = "3.5" # For temporary file operations
 | 
			
		||||
tera = "1.19.0" # Template engine for text rendering
 | 
			
		||||
thiserror = "2.0.12" # For error handling
 | 
			
		||||
@@ -63,8 +69,11 @@ windows = { version = "0.61.1", features = [
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
mockall = "0.13.1" # For mocking in tests
 | 
			
		||||
tempfile = "3.5"   # For tests that need temporary files/directories
 | 
			
		||||
tokio = { version = "1.28", features = ["full", "test-util"] } # For async testing
 | 
			
		||||
tempfile = "3.5" # For tests that need temporary files/directories
 | 
			
		||||
tokio = { version = "1.28", features = [
 | 
			
		||||
    "full",
 | 
			
		||||
    "test-util",
 | 
			
		||||
] } # For async testing
 | 
			
		||||
 | 
			
		||||
[[bin]]
 | 
			
		||||
name = "herodo"
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,8 @@ use std::str::FromStr;
 | 
			
		||||
use std::sync::Mutex;
 | 
			
		||||
use tokio::runtime::Runtime;
 | 
			
		||||
 | 
			
		||||
use crate::vault::ethereum;
 | 
			
		||||
use crate::vault::keyspace::session_manager as keypair;
 | 
			
		||||
use crate::vault::ethereum::contract_utils::{convert_token_to_rhai, prepare_function_arguments};
 | 
			
		||||
use crate::vault::{ethereum, keyspace};
 | 
			
		||||
 | 
			
		||||
use crate::vault::symmetric::implementation as symmetric_impl;
 | 
			
		||||
// Global Tokio runtime for blocking async operations
 | 
			
		||||
@@ -73,7 +73,7 @@ fn load_key_space(name: &str, password: &str) -> bool {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Set as current space
 | 
			
		||||
    match keypair::set_current_space(space) {
 | 
			
		||||
    match keyspace::set_current_space(space) {
 | 
			
		||||
        Ok(_) => true,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Error setting current space: {}", e);
 | 
			
		||||
@@ -83,10 +83,10 @@ fn load_key_space(name: &str, password: &str) -> bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn create_key_space(name: &str, password: &str) -> bool {
 | 
			
		||||
    match keypair::create_space(name) {
 | 
			
		||||
    match keyspace::session_manager::create_space(name) {
 | 
			
		||||
        Ok(_) => {
 | 
			
		||||
            // Get the current space
 | 
			
		||||
            match keypair::get_current_space() {
 | 
			
		||||
            match keyspace::get_current_space() {
 | 
			
		||||
                Ok(space) => {
 | 
			
		||||
                    // Encrypt the key space
 | 
			
		||||
                    let encrypted_space = match symmetric_impl::encrypt_key_space(&space, password)
 | 
			
		||||
@@ -151,7 +151,7 @@ fn create_key_space(name: &str, password: &str) -> bool {
 | 
			
		||||
 | 
			
		||||
// Auto-save function for internal use
 | 
			
		||||
fn auto_save_key_space(password: &str) -> bool {
 | 
			
		||||
    match keypair::get_current_space() {
 | 
			
		||||
    match keyspace::get_current_space() {
 | 
			
		||||
        Ok(space) => {
 | 
			
		||||
            // Encrypt the key space
 | 
			
		||||
            let encrypted_space = match symmetric_impl::encrypt_key_space(&space, password) {
 | 
			
		||||
@@ -207,7 +207,7 @@ fn auto_save_key_space(password: &str) -> bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn encrypt_key_space(password: &str) -> String {
 | 
			
		||||
    match keypair::get_current_space() {
 | 
			
		||||
    match keyspace::get_current_space() {
 | 
			
		||||
        Ok(space) => match symmetric_impl::encrypt_key_space(&space, password) {
 | 
			
		||||
            Ok(encrypted_space) => match serde_json::to_string(&encrypted_space) {
 | 
			
		||||
                Ok(json) => json,
 | 
			
		||||
@@ -232,7 +232,7 @@ fn decrypt_key_space(encrypted: &str, password: &str) -> bool {
 | 
			
		||||
    match serde_json::from_str(encrypted) {
 | 
			
		||||
        Ok(encrypted_space) => {
 | 
			
		||||
            match symmetric_impl::decrypt_key_space(&encrypted_space, password) {
 | 
			
		||||
                Ok(space) => match keypair::set_current_space(space) {
 | 
			
		||||
                Ok(space) => match keyspace::set_current_space(space) {
 | 
			
		||||
                    Ok(_) => true,
 | 
			
		||||
                    Err(e) => {
 | 
			
		||||
                        log::error!("Error setting current space: {}", e);
 | 
			
		||||
@@ -252,35 +252,35 @@ fn decrypt_key_space(encrypted: &str, password: &str) -> bool {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Keypair management functions
 | 
			
		||||
fn create_keypair(name: &str, password: &str) -> bool {
 | 
			
		||||
    match keypair::create_keypair(name) {
 | 
			
		||||
// keyspace management functions
 | 
			
		||||
fn create_keyspace(name: &str, password: &str) -> bool {
 | 
			
		||||
    match keyspace::create_keypair(name) {
 | 
			
		||||
        Ok(_) => {
 | 
			
		||||
            // Auto-save the key space after creating a keypair
 | 
			
		||||
            // Auto-save the key space after creating a keyspace
 | 
			
		||||
            auto_save_key_space(password)
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Error creating keypair: {}", e);
 | 
			
		||||
            log::error!("Error creating keyspace: {}", e);
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn select_keypair(name: &str) -> bool {
 | 
			
		||||
    match keypair::select_keypair(name) {
 | 
			
		||||
fn select_keyspace(name: &str) -> bool {
 | 
			
		||||
    match keyspace::select_keypair(name) {
 | 
			
		||||
        Ok(_) => true,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Error selecting keypair: {}", e);
 | 
			
		||||
            log::error!("Error selecting keyspace: {}", e);
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn list_keypairs() -> Vec<String> {
 | 
			
		||||
    match keypair::list_keypairs() {
 | 
			
		||||
        Ok(keypairs) => keypairs,
 | 
			
		||||
fn list_keyspaces() -> Vec<String> {
 | 
			
		||||
    match keyspace::list_keypairs() {
 | 
			
		||||
        Ok(keyspaces) => keyspaces,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Error listing keypairs: {}", e);
 | 
			
		||||
            log::error!("Error listing keyspaces: {}", e);
 | 
			
		||||
            Vec::new()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -289,7 +289,7 @@ fn list_keypairs() -> Vec<String> {
 | 
			
		||||
// Cryptographic operations
 | 
			
		||||
fn sign(message: &str) -> String {
 | 
			
		||||
    let message_bytes = message.as_bytes();
 | 
			
		||||
    match keypair::keypair_sign(message_bytes) {
 | 
			
		||||
    match keyspace::keypair_sign(message_bytes) {
 | 
			
		||||
        Ok(signature) => BASE64.encode(signature),
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Error signing message: {}", e);
 | 
			
		||||
@@ -301,7 +301,7 @@ fn sign(message: &str) -> String {
 | 
			
		||||
fn verify(message: &str, signature: &str) -> bool {
 | 
			
		||||
    let message_bytes = message.as_bytes();
 | 
			
		||||
    match BASE64.decode(signature) {
 | 
			
		||||
        Ok(signature_bytes) => match keypair::keypair_verify(message_bytes, &signature_bytes) {
 | 
			
		||||
        Ok(signature_bytes) => match keyspace::keypair_verify(message_bytes, &signature_bytes) {
 | 
			
		||||
            Ok(is_valid) => is_valid,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                log::error!("Error verifying signature: {}", e);
 | 
			
		||||
@@ -881,10 +881,10 @@ pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResu
 | 
			
		||||
    engine.register_fn("encrypt_key_space", encrypt_key_space);
 | 
			
		||||
    engine.register_fn("decrypt_key_space", decrypt_key_space);
 | 
			
		||||
 | 
			
		||||
    // Register keypair functions
 | 
			
		||||
    engine.register_fn("create_keypair", create_keypair);
 | 
			
		||||
    engine.register_fn("select_keypair", select_keypair);
 | 
			
		||||
    engine.register_fn("list_keypairs", list_keypairs);
 | 
			
		||||
    // Register keyspace functions
 | 
			
		||||
    engine.register_fn("create_keyspace", create_keyspace);
 | 
			
		||||
    engine.register_fn("select_keyspace", select_keyspace);
 | 
			
		||||
    engine.register_fn("list_keyspaces", list_keyspaces);
 | 
			
		||||
 | 
			
		||||
    // Register signing/verification functions
 | 
			
		||||
    engine.register_fn("sign", sign);
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@ use sha2::{Digest, Sha256};
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
use super::networks::NetworkConfig;
 | 
			
		||||
use crate::vault;
 | 
			
		||||
use crate::vault::error::CryptoError;
 | 
			
		||||
use crate::vault::keyspace::KeyPair;
 | 
			
		||||
 | 
			
		||||
/// An Ethereum wallet derived from a keypair.
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
@@ -49,7 +49,7 @@ impl EthereumWallet {
 | 
			
		||||
    /// Creates a new Ethereum wallet from a name and keypair (deterministic derivation) for a specific network.
 | 
			
		||||
    pub fn from_name_and_keypair(
 | 
			
		||||
        name: &str,
 | 
			
		||||
        keypair: &vault::keyspace::keypair_types::KeyPair,
 | 
			
		||||
        keypair: &KeyPair,
 | 
			
		||||
        network: NetworkConfig,
 | 
			
		||||
    ) -> Result<Self, CryptoError> {
 | 
			
		||||
        // Get the private key bytes from the keypair
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
use k256::ecdh::EphemeralSecret;
 | 
			
		||||
/// Implementation of keypair functionality.
 | 
			
		||||
use k256::ecdsa::{
 | 
			
		||||
    signature::{Signer, Verifier},
 | 
			
		||||
@@ -205,31 +206,32 @@ impl KeyPair {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Encrypts a message using the recipient's public key.
 | 
			
		||||
    /// This implements a simplified version of ECIES (Elliptic Curve Integrated Encryption Scheme):
 | 
			
		||||
    /// 1. Generate a random symmetric key
 | 
			
		||||
    /// 2. Encrypt the message with the symmetric key
 | 
			
		||||
    /// 3. Encrypt the symmetric key with the recipient's public key
 | 
			
		||||
    /// 4. Return the encrypted key and the ciphertext
 | 
			
		||||
    /// This implements ECIES (Elliptic Curve Integrated Encryption Scheme):
 | 
			
		||||
    /// 1. Generate an ephemeral keypair
 | 
			
		||||
    /// 2. Derive a shared secret using ECDH
 | 
			
		||||
    /// 3. Derive encryption key from the shared secret
 | 
			
		||||
    /// 4. Encrypt the message using symmetric encryption
 | 
			
		||||
    /// 5. Return the ephemeral public key and the ciphertext
 | 
			
		||||
    pub fn encrypt_asymmetric(
 | 
			
		||||
        &self,
 | 
			
		||||
        recipient_public_key: &[u8],
 | 
			
		||||
        message: &[u8],
 | 
			
		||||
    ) -> Result<Vec<u8>, CryptoError> {
 | 
			
		||||
        // Validate recipient's public key format
 | 
			
		||||
        VerifyingKey::from_sec1_bytes(recipient_public_key)
 | 
			
		||||
        // Parse recipient's public key
 | 
			
		||||
        let recipient_key = VerifyingKey::from_sec1_bytes(recipient_public_key)
 | 
			
		||||
            .map_err(|_| CryptoError::InvalidKeyLength)?;
 | 
			
		||||
 | 
			
		||||
        // Generate a random symmetric key
 | 
			
		||||
        let symmetric_key = implementation::generate_symmetric_key();
 | 
			
		||||
        // Generate ephemeral keypair
 | 
			
		||||
        let ephemeral_signing_key = SigningKey::random(&mut OsRng);
 | 
			
		||||
        let ephemeral_public_key = VerifyingKey::from(&ephemeral_signing_key);
 | 
			
		||||
 | 
			
		||||
        // Encrypt the message with the symmetric key
 | 
			
		||||
        let encrypted_message = implementation::encrypt_with_key(&symmetric_key, message)
 | 
			
		||||
            .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
 | 
			
		||||
        // Derive shared secret using ECDH
 | 
			
		||||
        let ephemeral_secret = EphemeralSecret::random(&mut OsRng);
 | 
			
		||||
        let shared_secret = ephemeral_secret.diffie_hellman(&recipient_key.into());
 | 
			
		||||
 | 
			
		||||
        // Encrypt the symmetric key with the recipient's public key
 | 
			
		||||
        // For simplicity, we'll just use the recipient's public key to derive an encryption key
 | 
			
		||||
        // This is not secure for production use, but works for our test
 | 
			
		||||
        let key_encryption_key = {
 | 
			
		||||
        // Derive encryption key from the shared secret (e.g., using HKDF or hashing)
 | 
			
		||||
        // For simplicity, we'll hash the shared secret here
 | 
			
		||||
        let encryption_key = {
 | 
			
		||||
            let mut hasher = Sha256::default();
 | 
			
		||||
            hasher.update(recipient_public_key);
 | 
			
		||||
            // Use a fixed salt for testing purposes
 | 
			
		||||
@@ -237,16 +239,16 @@ impl KeyPair {
 | 
			
		||||
            hasher.finalize().to_vec()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Encrypt the symmetric key
 | 
			
		||||
        let encrypted_key = implementation::encrypt_with_key(&key_encryption_key, &symmetric_key)
 | 
			
		||||
        // Encrypt the message using the derived key
 | 
			
		||||
        let ciphertext = implementation::encrypt_with_key(&encryption_key, message)
 | 
			
		||||
            .map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
 | 
			
		||||
 | 
			
		||||
        // Format: encrypted_key_length (4 bytes) || encrypted_key || encrypted_message
 | 
			
		||||
        let mut result = Vec::new();
 | 
			
		||||
        let key_len = encrypted_key.len() as u32;
 | 
			
		||||
        result.extend_from_slice(&key_len.to_be_bytes());
 | 
			
		||||
        result.extend_from_slice(&encrypted_key);
 | 
			
		||||
        result.extend_from_slice(&encrypted_message);
 | 
			
		||||
        // Format: ephemeral_public_key || ciphertext
 | 
			
		||||
        let mut result = ephemeral_public_key
 | 
			
		||||
            .to_encoded_point(false)
 | 
			
		||||
            .as_bytes()
 | 
			
		||||
            .to_vec();
 | 
			
		||||
        result.extend_from_slice(&ciphertext);
 | 
			
		||||
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
@@ -254,32 +256,28 @@ impl KeyPair {
 | 
			
		||||
    /// Decrypts a message using the recipient's private key.
 | 
			
		||||
    /// This is the counterpart to encrypt_asymmetric.
 | 
			
		||||
    pub fn decrypt_asymmetric(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
 | 
			
		||||
        // The format is: encrypted_key_length (4 bytes) || encrypted_key || encrypted_message
 | 
			
		||||
        if ciphertext.len() <= 4 {
 | 
			
		||||
        // The first 33 or 65 bytes (depending on compression) are the ephemeral public key
 | 
			
		||||
        // For simplicity, we'll assume uncompressed keys (65 bytes)
 | 
			
		||||
        if ciphertext.len() <= 65 {
 | 
			
		||||
            return Err(CryptoError::DecryptionFailed(
 | 
			
		||||
                "Ciphertext too short".to_string(),
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Extract the encrypted key length
 | 
			
		||||
        let mut key_len_bytes = [0u8; 4];
 | 
			
		||||
        key_len_bytes.copy_from_slice(&ciphertext[0..4]);
 | 
			
		||||
        let key_len = u32::from_be_bytes(key_len_bytes) as usize;
 | 
			
		||||
        // Extract ephemeral public key and actual ciphertext
 | 
			
		||||
        let ephemeral_public_key = &ciphertext[..65];
 | 
			
		||||
        let actual_ciphertext = &ciphertext[65..];
 | 
			
		||||
 | 
			
		||||
        // Check if the ciphertext is long enough
 | 
			
		||||
        if ciphertext.len() <= 4 + key_len {
 | 
			
		||||
            return Err(CryptoError::DecryptionFailed(
 | 
			
		||||
                "Ciphertext too short".to_string(),
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        // Parse ephemeral public key
 | 
			
		||||
        let sender_key = VerifyingKey::from_sec1_bytes(ephemeral_public_key)
 | 
			
		||||
            .map_err(|_| CryptoError::InvalidKeyLength)?;
 | 
			
		||||
 | 
			
		||||
        // Extract the encrypted key and the encrypted message
 | 
			
		||||
        let encrypted_key = &ciphertext[4..4 + key_len];
 | 
			
		||||
        let encrypted_message = &ciphertext[4 + key_len..];
 | 
			
		||||
        // Derive shared secret using ECDH
 | 
			
		||||
        let recipient_secret = EphemeralSecret::random(&mut OsRng);
 | 
			
		||||
        let shared_secret = recipient_secret.diffie_hellman(&sender_key.into());
 | 
			
		||||
 | 
			
		||||
        // Decrypt the symmetric key
 | 
			
		||||
        // Use the same key derivation as in encryption
 | 
			
		||||
        let key_encryption_key = {
 | 
			
		||||
        // Derive decryption key from the shared secret (using the same method as encryption)
 | 
			
		||||
        let decryption_key = {
 | 
			
		||||
            let mut hasher = Sha256::default();
 | 
			
		||||
            hasher.update(self.verifying_key.to_sec1_bytes());
 | 
			
		||||
            // Use the same fixed salt as in encryption
 | 
			
		||||
@@ -287,13 +285,9 @@ impl KeyPair {
 | 
			
		||||
            hasher.finalize().to_vec()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Decrypt the symmetric key
 | 
			
		||||
        let symmetric_key = implementation::decrypt_with_key(&key_encryption_key, encrypted_key)
 | 
			
		||||
            .map_err(|e| CryptoError::DecryptionFailed(format!("Failed to decrypt key: {}", e)))?;
 | 
			
		||||
 | 
			
		||||
        // Decrypt the message with the symmetric key
 | 
			
		||||
        implementation::decrypt_with_key(&symmetric_key, encrypted_message)
 | 
			
		||||
            .map_err(|e| CryptoError::DecryptionFailed(format!("Failed to decrypt message: {}", e)))
 | 
			
		||||
        // Decrypt the message using the derived key
 | 
			
		||||
        implementation::decrypt_with_key(&decryption_key, actual_ciphertext)
 | 
			
		||||
            .map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -355,7 +355,7 @@ impl KvStore {
 | 
			
		||||
        // Save to disk
 | 
			
		||||
        self.save()?;
 | 
			
		||||
 | 
			
		||||
       Ok(())
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Gets the name of the store.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								vault/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								vault/.cargo/config.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
			
		||||
[target.wasm32-unknown-unknown]
 | 
			
		||||
rustflags = ['--cfg', 'getrandom_backend="wasm_js"']
 | 
			
		||||
							
								
								
									
										22
									
								
								vault/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vault/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "vault"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2024"
 | 
			
		||||
 | 
			
		||||
[features]
 | 
			
		||||
native = ["kv/native"]
 | 
			
		||||
wasm = ["kv/web"]
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
getrandom = { version = "0.3.3", features = ["wasm_js"] }
 | 
			
		||||
rand = "0.9.1"
 | 
			
		||||
# We need to pull v0.2.x to enable the "js" feature for wasm32 builds
 | 
			
		||||
getrandom_old = { package = "getrandom", version = "0.2.16", features = ["js"] }
 | 
			
		||||
serde = { version = "1.0.219", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0.140"
 | 
			
		||||
chacha20poly1305 = "0.10.1"
 | 
			
		||||
k256 = { version = "0.13.4", features = ["ecdh"] }
 | 
			
		||||
sha2 = "0.10.9"
 | 
			
		||||
kv = { git = "https://git.ourworld.tf/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" }
 | 
			
		||||
bincode = { version = "2.0.1", features = ["serde"] }
 | 
			
		||||
pbkdf2 = "0.12.2"
 | 
			
		||||
							
								
								
									
										160
									
								
								vault/src/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								vault/src/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,160 @@
 | 
			
		||||
# Hero Vault Cryptography Module
 | 
			
		||||
 | 
			
		||||
The Hero Vault module provides comprehensive cryptographic functionality for the SAL project, including key management, digital signatures, symmetric encryption, Ethereum wallet operations, and a secure key-value store.
 | 
			
		||||
 | 
			
		||||
## Module Structure
 | 
			
		||||
 | 
			
		||||
The Hero Vault module is organized into several submodules:
 | 
			
		||||
 | 
			
		||||
- `error.rs` - Error types for cryptographic operations
 | 
			
		||||
- `keypair/` - ECDSA keypair management functionality
 | 
			
		||||
- `symmetric/` - Symmetric encryption using ChaCha20Poly1305
 | 
			
		||||
- `ethereum/` - Ethereum wallet and smart contract functionality
 | 
			
		||||
- `kvs/` - Encrypted key-value store
 | 
			
		||||
 | 
			
		||||
## Key Features
 | 
			
		||||
 | 
			
		||||
### Key Space Management
 | 
			
		||||
 | 
			
		||||
The module provides functionality for creating, loading, and managing key spaces. A key space is a secure container for cryptographic keys, which can be encrypted and stored on disk.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Create a new key space
 | 
			
		||||
let space = KeySpace::new("my_space", "secure_password")?;
 | 
			
		||||
 | 
			
		||||
// Save the key space to disk
 | 
			
		||||
space.save()?;
 | 
			
		||||
 | 
			
		||||
// Load a key space from disk
 | 
			
		||||
let loaded_space = KeySpace::load("my_space", "secure_password")?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Keypair Management
 | 
			
		||||
 | 
			
		||||
The module provides functionality for creating, selecting, and using ECDSA keypairs for digital signatures.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Create a new keypair in the active key space
 | 
			
		||||
let keypair = space.create_keypair("my_keypair", "secure_password")?;
 | 
			
		||||
 | 
			
		||||
// Select a keypair for use
 | 
			
		||||
space.select_keypair("my_keypair")?;
 | 
			
		||||
 | 
			
		||||
// List all keypairs in the active key space
 | 
			
		||||
let keypairs = space.list_keypairs()?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Digital Signatures
 | 
			
		||||
 | 
			
		||||
The module provides functionality for signing and verifying messages using ECDSA.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Sign a message using the selected keypair
 | 
			
		||||
let signature = space.sign("This is a message to sign")?;
 | 
			
		||||
 | 
			
		||||
// Verify a signature
 | 
			
		||||
let is_valid = space.verify("This is a message to sign", &signature)?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Symmetric Encryption
 | 
			
		||||
 | 
			
		||||
The module provides functionality for symmetric encryption using ChaCha20Poly1305.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Generate a new symmetric key
 | 
			
		||||
let key = space.generate_key()?;
 | 
			
		||||
 | 
			
		||||
// Encrypt a message
 | 
			
		||||
let encrypted = space.encrypt(&key, "This is a secret message")?;
 | 
			
		||||
 | 
			
		||||
// Decrypt a message
 | 
			
		||||
let decrypted = space.decrypt(&key, &encrypted)?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Ethereum Wallet Functionality
 | 
			
		||||
 | 
			
		||||
The module provides comprehensive Ethereum wallet functionality, including:
 | 
			
		||||
 | 
			
		||||
- Creating and managing wallets for different networks
 | 
			
		||||
- Sending ETH transactions
 | 
			
		||||
- Checking balances
 | 
			
		||||
- Interacting with smart contracts
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Create an Ethereum wallet
 | 
			
		||||
let wallet = EthereumWallet::new(keypair)?;
 | 
			
		||||
 | 
			
		||||
// Get the wallet address
 | 
			
		||||
let address = wallet.get_address()?;
 | 
			
		||||
 | 
			
		||||
// Send ETH
 | 
			
		||||
let tx_hash = wallet.send_eth("0x1234...", "1000000000000000")?;
 | 
			
		||||
 | 
			
		||||
// Check balance
 | 
			
		||||
let balance = wallet.get_balance("0x1234...")?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Smart Contract Interactions
 | 
			
		||||
 | 
			
		||||
The module provides functionality for interacting with smart contracts on EVM-based blockchains.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Load a contract ABI
 | 
			
		||||
let contract = Contract::new(provider, "0x1234...", abi)?;
 | 
			
		||||
 | 
			
		||||
// Call a read-only function
 | 
			
		||||
let result = contract.call_read("balanceOf", vec!["0x5678..."])?;
 | 
			
		||||
 | 
			
		||||
// Call a write function
 | 
			
		||||
let tx_hash = contract.call_write("transfer", vec!["0x5678...", "1000"])?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Key-Value Store
 | 
			
		||||
 | 
			
		||||
The module provides an encrypted key-value store for securely storing sensitive data.
 | 
			
		||||
 | 
			
		||||
```rust
 | 
			
		||||
// Create a new store
 | 
			
		||||
let store = KvStore::new("my_store", "secure_password")?;
 | 
			
		||||
 | 
			
		||||
// Set a value
 | 
			
		||||
store.set("api_key", "secret_api_key")?;
 | 
			
		||||
 | 
			
		||||
// Get a value
 | 
			
		||||
let api_key = store.get("api_key")?;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Error Handling
 | 
			
		||||
 | 
			
		||||
The module uses a comprehensive error type (`CryptoError`) for handling errors that can occur during cryptographic operations:
 | 
			
		||||
 | 
			
		||||
- `InvalidKeyLength` - Invalid key length
 | 
			
		||||
- `EncryptionFailed` - Encryption failed
 | 
			
		||||
- `DecryptionFailed` - Decryption failed
 | 
			
		||||
- `SignatureFormatError` - Signature format error
 | 
			
		||||
- `KeypairAlreadyExists` - Keypair already exists
 | 
			
		||||
- `KeypairNotFound` - Keypair not found
 | 
			
		||||
- `NoActiveSpace` - No active key space
 | 
			
		||||
- `NoKeypairSelected` - No keypair selected
 | 
			
		||||
- `SerializationError` - Serialization error
 | 
			
		||||
- `InvalidAddress` - Invalid address format
 | 
			
		||||
- `ContractError` - Smart contract error
 | 
			
		||||
 | 
			
		||||
## Ethereum Networks
 | 
			
		||||
 | 
			
		||||
The module supports multiple Ethereum networks, including:
 | 
			
		||||
 | 
			
		||||
- Gnosis Chain
 | 
			
		||||
- Peaq Network
 | 
			
		||||
- Agung Network
 | 
			
		||||
 | 
			
		||||
## Security Considerations
 | 
			
		||||
 | 
			
		||||
- Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password
 | 
			
		||||
- Private keys are never stored in plaintext
 | 
			
		||||
- The module uses secure random number generation for key creation
 | 
			
		||||
- All cryptographic operations use well-established libraries and algorithms
 | 
			
		||||
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
For examples of how to use the Hero Vault module, see the `examples/hero_vault` directory.
 | 
			
		||||
							
								
								
									
										109
									
								
								vault/src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								vault/src/error.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
/// Errors encountered while using the vault
 | 
			
		||||
pub enum Error {
 | 
			
		||||
    /// An error during cryptographic operations
 | 
			
		||||
    Crypto(CryptoError),
 | 
			
		||||
    /// An error while performing an I/O operation
 | 
			
		||||
    IOError(std::io::Error),
 | 
			
		||||
    /// A corrupt keyspace is returned if a keyspace can't be decrypted
 | 
			
		||||
    CorruptKeyspace,
 | 
			
		||||
    /// An error in the used key value store
 | 
			
		||||
    KV(kv::error::KVError),
 | 
			
		||||
    /// An error while encoding/decoding the keyspace.
 | 
			
		||||
    Coding,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl core::fmt::Display for Error {
 | 
			
		||||
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            Error::Crypto(e) => f.write_fmt(format_args!("crypto: {e}")),
 | 
			
		||||
            Error::IOError(e) => f.write_fmt(format_args!("io: {e}")),
 | 
			
		||||
            Error::CorruptKeyspace => f.write_str("corrupt keyspace"),
 | 
			
		||||
            Error::KV(e) => f.write_fmt(format_args!("kv: {e}")),
 | 
			
		||||
            Error::Coding => f.write_str("keyspace coding failed"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl core::error::Error for Error {}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
/// Errors generated by the vault or keys.
 | 
			
		||||
///
 | 
			
		||||
/// These errors are intentionally vague to avoid issues such as padding oracles.
 | 
			
		||||
pub enum CryptoError {
 | 
			
		||||
    /// Key size is not valid for this type of key
 | 
			
		||||
    InvalidKeySize,
 | 
			
		||||
    /// Something went wrong while trying to encrypt data
 | 
			
		||||
    EncryptionFailed,
 | 
			
		||||
    /// Something went wrong while trying to decrypt data
 | 
			
		||||
    DecryptionFailed,
 | 
			
		||||
    /// Something went wrong while trying to sign a message
 | 
			
		||||
    SigningError,
 | 
			
		||||
    /// The signature is invalid for this message and public key
 | 
			
		||||
    SignatureFailed,
 | 
			
		||||
    /// The signature does not have the expected size
 | 
			
		||||
    InvalidSignatureSize,
 | 
			
		||||
    /// Trying to load a key which is not the expected format,
 | 
			
		||||
    InvalidKey,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl core::fmt::Display for CryptoError {
 | 
			
		||||
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            CryptoError::InvalidKeySize => f.write_str("provided key is not the correct size"),
 | 
			
		||||
            CryptoError::EncryptionFailed => f.write_str("encryption failure"),
 | 
			
		||||
            CryptoError::DecryptionFailed => f.write_str("decryption failure"),
 | 
			
		||||
            CryptoError::SigningError => f.write_str("signature generation failure"),
 | 
			
		||||
            CryptoError::SignatureFailed => f.write_str("signature verification failure"),
 | 
			
		||||
            CryptoError::InvalidSignatureSize => {
 | 
			
		||||
                f.write_str("provided signature does not have the expected size")
 | 
			
		||||
            }
 | 
			
		||||
            CryptoError::InvalidKey => f.write_str("the provided bytes are not a valid key"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl core::error::Error for CryptoError {}
 | 
			
		||||
 | 
			
		||||
impl From<CryptoError> for Error {
 | 
			
		||||
    fn from(value: CryptoError) -> Self {
 | 
			
		||||
        Self::Crypto(value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<std::io::Error> for Error {
 | 
			
		||||
    fn from(value: std::io::Error) -> Self {
 | 
			
		||||
        Self::IOError(value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<kv::error::KVError> for Error {
 | 
			
		||||
    fn from(value: kv::error::KVError) -> Self {
 | 
			
		||||
        Self::KV(value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<bincode::error::DecodeError> for Error {
 | 
			
		||||
    fn from(_: bincode::error::DecodeError) -> Self {
 | 
			
		||||
        Self::Coding
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<bincode::error::EncodeError> for Error {
 | 
			
		||||
    fn from(_: bincode::error::EncodeError) -> Self {
 | 
			
		||||
        Self::Coding
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<k256::ecdsa::Error> for CryptoError {
 | 
			
		||||
    fn from(_: k256::ecdsa::Error) -> Self {
 | 
			
		||||
        Self::InvalidKey
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<k256::elliptic_curve::Error> for CryptoError {
 | 
			
		||||
    fn from(_: k256::elliptic_curve::Error) -> Self {
 | 
			
		||||
        Self::InvalidKey
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								vault/src/key.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								vault/src/key.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
use asymmetric::AsymmetricKeypair;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use signature::SigningKeypair;
 | 
			
		||||
use symmetric::SymmetricKey;
 | 
			
		||||
 | 
			
		||||
pub mod asymmetric;
 | 
			
		||||
pub mod signature;
 | 
			
		||||
pub mod symmetric;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
 | 
			
		||||
pub enum KeyType {
 | 
			
		||||
    /// The key can be used for symmetric key encryption
 | 
			
		||||
    Symmetric,
 | 
			
		||||
    /// The key can be used for asymmetric encryption
 | 
			
		||||
    Asymmetric,
 | 
			
		||||
    /// The key can be used for digital signatures
 | 
			
		||||
    Signature,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Key holds generic information about a key
 | 
			
		||||
#[derive(Clone, Deserialize, Serialize)]
 | 
			
		||||
pub struct Key {
 | 
			
		||||
    /// The mode of the key
 | 
			
		||||
    mode: KeyType,
 | 
			
		||||
    /// Raw bytes of the key
 | 
			
		||||
    raw_key: Vec<u8>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Key {
 | 
			
		||||
    /// Try to downcast this `Key` to a [`SymmetricKey`]
 | 
			
		||||
    pub fn as_symmetric(&self) -> Option<SymmetricKey> {
 | 
			
		||||
        if matches!(self.mode, KeyType::Symmetric) {
 | 
			
		||||
            SymmetricKey::from_bytes(&self.raw_key).ok()
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Try to downcast this `Key` to an [`AsymmetricKeypair`]
 | 
			
		||||
    pub fn as_asymmetric(&self) -> Option<AsymmetricKeypair> {
 | 
			
		||||
        if matches!(self.mode, KeyType::Asymmetric) {
 | 
			
		||||
            AsymmetricKeypair::from_bytes(&self.raw_key).ok()
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Try to downcast this `Key` to a [`SigningKeypair`]
 | 
			
		||||
    pub fn as_signing(&self) -> Option<SigningKeypair> {
 | 
			
		||||
        if matches!(self.mode, KeyType::Signature) {
 | 
			
		||||
            SigningKeypair::from_bytes(&self.raw_key).ok()
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<SymmetricKey> for Key {
 | 
			
		||||
    fn from(value: SymmetricKey) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            mode: KeyType::Symmetric,
 | 
			
		||||
            raw_key: Vec::from(value.as_raw_bytes()),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<AsymmetricKeypair> for Key {
 | 
			
		||||
    fn from(value: AsymmetricKeypair) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            mode: KeyType::Asymmetric,
 | 
			
		||||
            raw_key: value.as_raw_private_key(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<SigningKeypair> for Key {
 | 
			
		||||
    fn from(value: SigningKeypair) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            mode: KeyType::Signature,
 | 
			
		||||
            raw_key: value.as_raw_private_key(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										161
									
								
								vault/src/key/asymmetric.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								vault/src/key/asymmetric.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
			
		||||
//! An implementation of asymmetric cryptography using SECP256k1 ECDH with ChaCha20Poly1305
 | 
			
		||||
//! for the actual encryption.
 | 
			
		||||
 | 
			
		||||
use k256::{SecretKey, ecdh::diffie_hellman, elliptic_curve::sec1::ToEncodedPoint};
 | 
			
		||||
use sha2::Sha256;
 | 
			
		||||
 | 
			
		||||
use crate::{error::CryptoError, key::symmetric::SymmetricKey};
 | 
			
		||||
 | 
			
		||||
/// A keypair for use in asymmetric encryption operations.
 | 
			
		||||
pub struct AsymmetricKeypair {
 | 
			
		||||
    /// Private part of the key
 | 
			
		||||
    private: SecretKey,
 | 
			
		||||
    /// Public part of the key
 | 
			
		||||
    public: k256::PublicKey,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The public key part of an asymmetric keypair.
 | 
			
		||||
#[derive(Debug, PartialEq, Eq)]
 | 
			
		||||
pub struct PublicKey(k256::PublicKey);
 | 
			
		||||
 | 
			
		||||
impl AsymmetricKeypair {
 | 
			
		||||
    /// Generates a new random keypair
 | 
			
		||||
    pub fn new() -> Result<Self, CryptoError> {
 | 
			
		||||
        let mut raw_private = [0u8; 32];
 | 
			
		||||
        rand::fill(&mut raw_private);
 | 
			
		||||
        let sk = SecretKey::from_slice(&raw_private)
 | 
			
		||||
            .expect("Key is provided generated with fixed valid size");
 | 
			
		||||
        let pk = sk.public_key();
 | 
			
		||||
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            private: sk,
 | 
			
		||||
            public: pk,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a new key from existing bytes.
 | 
			
		||||
    pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
 | 
			
		||||
        if bytes.len() == 32 {
 | 
			
		||||
            let sk = SecretKey::from_slice(&bytes).expect("Key was checked to be a valid size");
 | 
			
		||||
            let pk = sk.public_key();
 | 
			
		||||
            Ok(Self {
 | 
			
		||||
                private: sk,
 | 
			
		||||
                public: pk,
 | 
			
		||||
            })
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(CryptoError::InvalidKeySize)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// View the raw bytes of the private key of this keypair.
 | 
			
		||||
    pub(crate) fn as_raw_private_key(&self) -> Vec<u8> {
 | 
			
		||||
        self.private.as_scalar_primitive().to_bytes().to_vec()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the public part of this keypair.
 | 
			
		||||
    pub fn public_key(&self) -> PublicKey {
 | 
			
		||||
        PublicKey(self.public.clone())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Encrypt data for a receiver. First a shared secret is derived using the own private key and
 | 
			
		||||
    /// the receivers public key. Then, this shared secret is used for symmetric encryption of the
 | 
			
		||||
    /// plaintext. The receiver can decrypt this by generating the same shared secret, using his
 | 
			
		||||
    /// own private key and our public key.
 | 
			
		||||
    pub fn encrypt(
 | 
			
		||||
        &self,
 | 
			
		||||
        remote_key: &PublicKey,
 | 
			
		||||
        plaintext: &[u8],
 | 
			
		||||
    ) -> Result<Vec<u8>, CryptoError> {
 | 
			
		||||
        let mut symmetric_key = [0u8; 32];
 | 
			
		||||
        diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine())
 | 
			
		||||
            .extract::<Sha256>(None)
 | 
			
		||||
            .expand(&[], &mut symmetric_key)
 | 
			
		||||
            .map_err(|_| CryptoError::InvalidKeySize)?;
 | 
			
		||||
 | 
			
		||||
        let sym_key = SymmetricKey::from_bytes(&symmetric_key)?;
 | 
			
		||||
 | 
			
		||||
        sym_key.encrypt(plaintext)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Decrypt data from a sender. The remote key must be the public key of the keypair used by
 | 
			
		||||
    /// the sender to encrypt this message.
 | 
			
		||||
    pub fn decrypt(
 | 
			
		||||
        &self,
 | 
			
		||||
        remote_key: &PublicKey,
 | 
			
		||||
        ciphertext: &[u8],
 | 
			
		||||
    ) -> Result<Vec<u8>, CryptoError> {
 | 
			
		||||
        let mut symmetric_key = [0u8; 32];
 | 
			
		||||
        diffie_hellman(self.private.to_nonzero_scalar(), remote_key.0.as_affine())
 | 
			
		||||
            .extract::<Sha256>(None)
 | 
			
		||||
            .expand(&[], &mut symmetric_key)
 | 
			
		||||
            .map_err(|_| CryptoError::InvalidKeySize)?;
 | 
			
		||||
 | 
			
		||||
        let sym_key = SymmetricKey::from_bytes(&symmetric_key)?;
 | 
			
		||||
 | 
			
		||||
        sym_key.decrypt(ciphertext)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PublicKey {
 | 
			
		||||
    /// Import a public key from raw bytes
 | 
			
		||||
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
 | 
			
		||||
        Ok(Self(k256::PublicKey::from_sec1_bytes(bytes)?))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the raw bytes of this `PublicKey`, which can be transferred to another party.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The public key is SEC-1 encoded and compressed.
 | 
			
		||||
    pub fn as_bytes(&self) -> Box<[u8]> {
 | 
			
		||||
        self.0.to_encoded_point(true).to_bytes()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    /// Export a public key and import it later
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn import_public_key() {
 | 
			
		||||
        let kp = super::AsymmetricKeypair::new().expect("Can generate new keypair");
 | 
			
		||||
        let pk1 = kp.public_key();
 | 
			
		||||
        let pk_bytes = pk1.as_bytes();
 | 
			
		||||
        let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(pk1, pk2);
 | 
			
		||||
    }
 | 
			
		||||
    /// Make sure 2 random keypairs derive the same shared secret (and thus encryption key), by
 | 
			
		||||
    /// encrypting a random message, decrypting it, and verifying it matches.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn encrypt_and_decrypt() {
 | 
			
		||||
        let kp1 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
 | 
			
		||||
        let kp2 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
 | 
			
		||||
 | 
			
		||||
        let pk1 = kp1.public_key();
 | 
			
		||||
        let pk2 = kp2.public_key();
 | 
			
		||||
 | 
			
		||||
        let message = b"this is a random message to encrypt and decrypt";
 | 
			
		||||
 | 
			
		||||
        let enc = kp1.encrypt(&pk2, message).expect("Can encrypt message");
 | 
			
		||||
        let dec = kp2.decrypt(&pk1, &enc).expect("Can decrypt message");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(message.as_slice(), dec.as_slice());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Use a different public key for decrypting than the expected one, this should fail the
 | 
			
		||||
    /// decryption process as we use AEAD encryption with the symmetric key.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn decrypt_with_wrong_key() {
 | 
			
		||||
        let kp1 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
 | 
			
		||||
        let kp2 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
 | 
			
		||||
        let kp3 = super::AsymmetricKeypair::new().expect("Can generate new keypair");
 | 
			
		||||
 | 
			
		||||
        let pk2 = kp2.public_key();
 | 
			
		||||
        let pk3 = kp3.public_key();
 | 
			
		||||
 | 
			
		||||
        let message = b"this is a random message to encrypt and decrypt";
 | 
			
		||||
 | 
			
		||||
        let enc = kp1.encrypt(&pk2, message).expect("Can encrypt message");
 | 
			
		||||
        let dec = kp2.decrypt(&pk3, &enc);
 | 
			
		||||
 | 
			
		||||
        assert!(dec.is_err());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								vault/src/key/signature.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								vault/src/key/signature.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
//! An implementation of digitial signatures using secp256k1 ECDSA.
 | 
			
		||||
 | 
			
		||||
use k256::ecdsa::{
 | 
			
		||||
    Signature, SigningKey, VerifyingKey,
 | 
			
		||||
    signature::{Signer, Verifier},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::error::CryptoError;
 | 
			
		||||
 | 
			
		||||
pub struct SigningKeypair {
 | 
			
		||||
    sk: SigningKey,
 | 
			
		||||
    vk: VerifyingKey,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Eq)]
 | 
			
		||||
pub struct PublicKey(VerifyingKey);
 | 
			
		||||
 | 
			
		||||
impl SigningKeypair {
 | 
			
		||||
    /// Generates a new random keypair
 | 
			
		||||
    pub fn new() -> Result<Self, CryptoError> {
 | 
			
		||||
        let mut raw_private = [0u8; 32];
 | 
			
		||||
        rand::fill(&mut raw_private);
 | 
			
		||||
        let sk = SigningKey::from_slice(&raw_private)
 | 
			
		||||
            .expect("Key is provided generated with fixed valid size");
 | 
			
		||||
        let vk = sk.verifying_key().to_owned();
 | 
			
		||||
 | 
			
		||||
        Ok(Self { sk, vk })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a new key from existing bytes.
 | 
			
		||||
    pub(crate) fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
 | 
			
		||||
        if bytes.len() == 32 {
 | 
			
		||||
            let sk = SigningKey::from_slice(&bytes).expect("Key was checked to be a valid size");
 | 
			
		||||
            let vk = sk.verifying_key().to_owned();
 | 
			
		||||
            Ok(Self { sk, vk })
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(CryptoError::InvalidKeySize)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// View the raw bytes of the private key of this keypair.
 | 
			
		||||
    pub(crate) fn as_raw_private_key(&self) -> Vec<u8> {
 | 
			
		||||
        self.sk.as_nonzero_scalar().to_bytes().to_vec()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the public part of this keypair.
 | 
			
		||||
    pub fn public_key(&self) -> PublicKey {
 | 
			
		||||
        PublicKey(self.vk)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sign data with the private key of this `SigningKeypair`. Other parties can use the public
 | 
			
		||||
    /// key to verify the signature. The generated signature is a detached signature.
 | 
			
		||||
    pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError> {
 | 
			
		||||
        let sig: Signature = self.sk.sign(message);
 | 
			
		||||
        Ok(sig.to_vec())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PublicKey {
 | 
			
		||||
    /// Import a public key from raw bytes
 | 
			
		||||
    pub fn from_bytes(bytes: &[u8]) -> Result<Self, CryptoError> {
 | 
			
		||||
        Ok(Self(VerifyingKey::from_sec1_bytes(bytes)?))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the raw bytes of this `PublicKey`, which can be transferred to another party.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The public key is SEC-1 encoded and compressed.
 | 
			
		||||
    pub fn as_bytes(&self) -> Box<[u8]> {
 | 
			
		||||
        self.0.to_encoded_point(true).to_bytes()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn verify_signature(&self, message: &[u8], sig: &[u8]) -> Result<(), CryptoError> {
 | 
			
		||||
        let sig = Signature::from_slice(sig).map_err(|_| CryptoError::InvalidKeySize)?;
 | 
			
		||||
        self.0
 | 
			
		||||
            .verify(message, &sig)
 | 
			
		||||
            .map_err(|_| CryptoError::SignatureFailed)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
 | 
			
		||||
    /// Generate a key, get the public key, export the bytes of said public key, import them again
 | 
			
		||||
    /// as a public key, and verify the keys match. This make sure public keys can be exchanged.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn recover_public_key() {
 | 
			
		||||
        let sk = super::SigningKeypair::new().expect("Can generate new key");
 | 
			
		||||
        let pk = sk.public_key();
 | 
			
		||||
        let pk_bytes = pk.as_bytes();
 | 
			
		||||
 | 
			
		||||
        let pk2 = super::PublicKey::from_bytes(&pk_bytes).expect("Can import public key");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(pk, pk2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Sign a message and validate the signature with the public key. Together with the above test
 | 
			
		||||
    /// this makes sure a remote system can receive our public key and validate messages we sign.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn validate_signature() {
 | 
			
		||||
        let sk = super::SigningKeypair::new().expect("Can generate new key");
 | 
			
		||||
        let pk = sk.public_key();
 | 
			
		||||
 | 
			
		||||
        let message = b"this is an arbitrary message we want to sign";
 | 
			
		||||
 | 
			
		||||
        let sig = sk.sign(message).expect("Message can be signed");
 | 
			
		||||
 | 
			
		||||
        assert!(pk.verify_signature(message, &sig).is_ok());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Make sure a signature which is tampered with does not pass signature validation
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn corrupt_signature_does_not_validate() {
 | 
			
		||||
        let sk = super::SigningKeypair::new().expect("Can generate new key");
 | 
			
		||||
        let pk = sk.public_key();
 | 
			
		||||
 | 
			
		||||
        let message = b"this is an arbitrary message we want to sign";
 | 
			
		||||
 | 
			
		||||
        let mut sig = sk.sign(message).expect("Message can be signed");
 | 
			
		||||
 | 
			
		||||
        // Tamper with the sig
 | 
			
		||||
        sig[0] = sig[0].wrapping_add(1);
 | 
			
		||||
 | 
			
		||||
        assert!(pk.verify_signature(message, &sig).is_err());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Make sure a valid signature does not work for a message which has been modified
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn tampered_message_does_not_validate() {
 | 
			
		||||
        let sk = super::SigningKeypair::new().expect("Can generate new key");
 | 
			
		||||
        let pk = sk.public_key();
 | 
			
		||||
 | 
			
		||||
        let message = b"this is an arbitrary message we want to sign";
 | 
			
		||||
        let mut message_clone = message.to_vec();
 | 
			
		||||
 | 
			
		||||
        let sig = sk.sign(message).expect("Message can be signed");
 | 
			
		||||
 | 
			
		||||
        // Modify the message
 | 
			
		||||
        message_clone[0] = message[0].wrapping_add(1);
 | 
			
		||||
 | 
			
		||||
        assert!(pk.verify_signature(&message_clone, &sig).is_err());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										151
									
								
								vault/src/key/symmetric.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								vault/src/key/symmetric.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,151 @@
 | 
			
		||||
//! An implementation of symmetric keys for ChaCha20Poly1305 encryption.
 | 
			
		||||
//!
 | 
			
		||||
//! The ciphertext is authenticated.
 | 
			
		||||
//! The 12-byte nonce is appended to the generated ciphertext.
 | 
			
		||||
//! Keys are 32 bytes in size.
 | 
			
		||||
 | 
			
		||||
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce, aead::Aead};
 | 
			
		||||
 | 
			
		||||
use crate::error::CryptoError;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Eq)]
 | 
			
		||||
pub struct SymmetricKey([u8; 32]);
 | 
			
		||||
 | 
			
		||||
/// Size of a nonce in ChaCha20Poly1305.
 | 
			
		||||
const NONCE_SIZE: usize = 12;
 | 
			
		||||
 | 
			
		||||
impl SymmetricKey {
 | 
			
		||||
    /// Generate a new random SymmetricKey.
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        let mut key = [0u8; 32];
 | 
			
		||||
        rand::fill(&mut key);
 | 
			
		||||
        Self(key)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a new key from existing bytes.
 | 
			
		||||
    pub(crate) fn from_bytes(bytes: &[u8]) -> Result<SymmetricKey, CryptoError> {
 | 
			
		||||
        if bytes.len() == 32 {
 | 
			
		||||
            let mut key = [0u8; 32];
 | 
			
		||||
            key.copy_from_slice(bytes);
 | 
			
		||||
            Ok(SymmetricKey(key))
 | 
			
		||||
        } else {
 | 
			
		||||
            Err(CryptoError::InvalidKeySize)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// View the raw bytes of this key
 | 
			
		||||
    pub(crate) fn as_raw_bytes(&self) -> &[u8; 32] {
 | 
			
		||||
        &self.0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Encrypt a plaintext with the key. A nonce is generated and appended to the end of the
 | 
			
		||||
    /// message.
 | 
			
		||||
    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, CryptoError> {
 | 
			
		||||
        // Create cipher
 | 
			
		||||
        let cipher = ChaCha20Poly1305::new_from_slice(&self.0)
 | 
			
		||||
            .expect("Key is a fixed 32 byte array so size is always ok");
 | 
			
		||||
 | 
			
		||||
        // Generate random nonce
 | 
			
		||||
        let mut nonce_bytes = [0u8; NONCE_SIZE];
 | 
			
		||||
        rand::fill(&mut nonce_bytes);
 | 
			
		||||
        let nonce = Nonce::from_slice(&nonce_bytes);
 | 
			
		||||
 | 
			
		||||
        // Encrypt message
 | 
			
		||||
        let mut ciphertext = cipher
 | 
			
		||||
            .encrypt(nonce, plaintext)
 | 
			
		||||
            .map_err(|_| CryptoError::EncryptionFailed)?;
 | 
			
		||||
 | 
			
		||||
        // Append nonce to ciphertext
 | 
			
		||||
        ciphertext.extend_from_slice(&nonce_bytes);
 | 
			
		||||
 | 
			
		||||
        Ok(ciphertext)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Decrypts a ciphertext with appended nonce.
 | 
			
		||||
    pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
 | 
			
		||||
        // Check if ciphertext is long enough to contain a nonce
 | 
			
		||||
        if ciphertext.len() <= NONCE_SIZE {
 | 
			
		||||
            return Err(CryptoError::DecryptionFailed);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Extract nonce from the end of ciphertext
 | 
			
		||||
        let ciphertext_len = ciphertext.len() - NONCE_SIZE;
 | 
			
		||||
        let nonce_bytes = &ciphertext[ciphertext_len..];
 | 
			
		||||
        let ciphertext = &ciphertext[0..ciphertext_len];
 | 
			
		||||
 | 
			
		||||
        // Create cipher
 | 
			
		||||
        let cipher = ChaCha20Poly1305::new_from_slice(&self.0)
 | 
			
		||||
            .expect("Key is a fixed 32 byte array so size is always ok");
 | 
			
		||||
 | 
			
		||||
        let nonce = Nonce::from_slice(nonce_bytes);
 | 
			
		||||
 | 
			
		||||
        // Decrypt message
 | 
			
		||||
        cipher
 | 
			
		||||
            .decrypt(nonce, ciphertext)
 | 
			
		||||
            .map_err(|_| CryptoError::DecryptionFailed)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Derives a new symmetric key from a password.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Derivation is done using pbkdf2 with Sha256 hashing.
 | 
			
		||||
    pub fn derive_from_password(password: &str) -> Self {
 | 
			
		||||
        /// Salt to use for PBKDF2. This needs to be consistent accross runs to generate the same
 | 
			
		||||
        /// key. Additionally, it does not really matter what this is, as long as its unique.
 | 
			
		||||
        const SALT: &[u8; 10] = b"vault_salt";
 | 
			
		||||
        /// Amount of rounds to use for key generation. More rounds => more cpu time. Changing this
 | 
			
		||||
        /// also chagnes the generated keys.
 | 
			
		||||
        const ROUNDS: u32 = 100_000;
 | 
			
		||||
 | 
			
		||||
        let mut key = [0; 32];
 | 
			
		||||
 | 
			
		||||
        pbkdf2::pbkdf2_hmac::<sha2::Sha256>(password.as_bytes(), SALT, ROUNDS, &mut key);
 | 
			
		||||
 | 
			
		||||
        Self(key)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
 | 
			
		||||
    /// Using the same password derives the same key
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn same_password_derives_same_key() {
 | 
			
		||||
        const EXPECTED_KEY: [u8; 32] = [
 | 
			
		||||
            4, 179, 233, 202, 225, 70, 211, 200, 7, 73, 115, 1, 85, 149, 90, 42, 160, 68, 16, 106,
 | 
			
		||||
            136, 19, 197, 195, 153, 145, 179, 21, 37, 13, 37, 90,
 | 
			
		||||
        ];
 | 
			
		||||
        const PASSWORD: &str = "test123";
 | 
			
		||||
 | 
			
		||||
        let key = super::SymmetricKey::derive_from_password(PASSWORD);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(key.0, EXPECTED_KEY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Make sure an encrypted value with some key can be decrypted with the same key
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn can_decrypt() {
 | 
			
		||||
        let key = super::SymmetricKey::new();
 | 
			
		||||
 | 
			
		||||
        let message = b"this is a message to decrypt";
 | 
			
		||||
 | 
			
		||||
        let enc = key.encrypt(message).expect("Can encrypt message");
 | 
			
		||||
        let dec = key.decrypt(&enc).expect("Can decrypt message");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(message.as_slice(), dec.as_slice());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Make sure a value encrypted with one key can't be decrypted with a different key. Since we
 | 
			
		||||
    /// use AEAD encryption we will notice this when trying to decrypt
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn different_key_cant_decrypt() {
 | 
			
		||||
        let key1 = super::SymmetricKey::new();
 | 
			
		||||
        let key2 = super::SymmetricKey::new();
 | 
			
		||||
 | 
			
		||||
        let message = b"this is a message to decrypt";
 | 
			
		||||
 | 
			
		||||
        let enc = key1.encrypt(message).expect("Can encrypt message");
 | 
			
		||||
        let dec = key2.decrypt(&enc);
 | 
			
		||||
 | 
			
		||||
        assert!(dec.is_err());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										131
									
								
								vault/src/keyspace.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								vault/src/keyspace.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
// #[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
// mod fallback;
 | 
			
		||||
// #[cfg(target_arch = "wasm32")]
 | 
			
		||||
// mod wasm;
 | 
			
		||||
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
#[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    error::Error,
 | 
			
		||||
    key::{Key, symmetric::SymmetricKey},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use kv::KVStore;
 | 
			
		||||
 | 
			
		||||
/// Configuration to use for bincode en/decoding.
 | 
			
		||||
const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard();
 | 
			
		||||
 | 
			
		||||
// #[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
// use fallback::KeySpace as Ks;
 | 
			
		||||
// #[cfg(target_arch = "wasm32")]
 | 
			
		||||
// use wasm::KeySpace as Ks;
 | 
			
		||||
 | 
			
		||||
#[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
use kv::native::NativeStore;
 | 
			
		||||
#[cfg(target_arch = "wasm32")]
 | 
			
		||||
use kv::wasm::WasmStore;
 | 
			
		||||
 | 
			
		||||
const KEYSPACE_NAME: &str = "vault_keyspace";
 | 
			
		||||
 | 
			
		||||
/// A keyspace represents a group of stored cryptographic keys. The storage is encrypted, a
 | 
			
		||||
/// password must be provided when opening the KeySpace to decrypt the keys.
 | 
			
		||||
pub struct KeySpace {
 | 
			
		||||
    // store: Ks,
 | 
			
		||||
    #[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
    store: NativeStore,
 | 
			
		||||
    #[cfg(target_arch = "wasm32")]
 | 
			
		||||
    store: WasmStore,
 | 
			
		||||
    /// A collection of all keys stored in the KeySpace, in decrypted form.
 | 
			
		||||
    keys: HashMap<String, Key>,
 | 
			
		||||
    /// The encryption key used to encrypt/decrypt this keyspace.
 | 
			
		||||
    encryption_key: SymmetricKey,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Wasm32 constructor
 | 
			
		||||
#[cfg(target_arch = "wasm32")]
 | 
			
		||||
impl KeySpace {}
 | 
			
		||||
 | 
			
		||||
/// Non-wasm constructor
 | 
			
		||||
#[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
impl KeySpace {
 | 
			
		||||
    /// Open the keyspace at the provided path using the given key for encryption.
 | 
			
		||||
    pub async fn open(path: &Path, encryption_key: SymmetricKey) -> Result<Self, Error> {
 | 
			
		||||
        let store = NativeStore::open(&path.display().to_string())?;
 | 
			
		||||
        let mut ks = Self {
 | 
			
		||||
            store,
 | 
			
		||||
            keys: HashMap::new(),
 | 
			
		||||
            encryption_key,
 | 
			
		||||
        };
 | 
			
		||||
        ks.load_keyspace().await?;
 | 
			
		||||
        Ok(ks)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(target_arch = "wasm32")]
 | 
			
		||||
impl KeySpace {
 | 
			
		||||
    pub async fn open(name: &str, encryption_key: SymmetricKey) -> Result<Self, Error> {
 | 
			
		||||
        let store = WasmStore::open(name).await?;
 | 
			
		||||
        let mut ks = Self {
 | 
			
		||||
            store,
 | 
			
		||||
            keys: HashMap::new(),
 | 
			
		||||
            encryption_key,
 | 
			
		||||
        };
 | 
			
		||||
        ks.load_keyspace().await?;
 | 
			
		||||
        Ok(ks)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Exposed methods, platform independant
 | 
			
		||||
impl KeySpace {
 | 
			
		||||
    /// Get a [`Key`] previously stored under the provided name.
 | 
			
		||||
    pub async fn get(&self, key: &str) -> Result<Option<Key>, Error> {
 | 
			
		||||
        Ok(self.keys.get(key).cloned())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Store a [`Key`] under the provided name.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This overwrites the existing key if one is already stored with the same name.
 | 
			
		||||
    pub async fn set(&mut self, key: String, value: Key) -> Result<(), Error> {
 | 
			
		||||
        self.keys.insert(key, value);
 | 
			
		||||
        self.save_keyspace().await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Delete the [`Key`] stored under the provided name.
 | 
			
		||||
    pub async fn delete(&mut self, key: &str) -> Result<(), Error> {
 | 
			
		||||
        self.keys.remove(key);
 | 
			
		||||
        self.save_keyspace().await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Iterate over all stored [`keys`](Key) in the KeySpace
 | 
			
		||||
    pub async fn iter(&self) -> Result<impl Iterator<Item = (&String, &Key)>, Error> {
 | 
			
		||||
        Ok(self.keys.iter())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Encrypt all keys and save them to the underlying store
 | 
			
		||||
    async fn save_keyspace(&self) -> Result<(), Error> {
 | 
			
		||||
        let encoded_keys = bincode::serde::encode_to_vec(&self.keys, BINCODE_CONFIG)?;
 | 
			
		||||
        let value = self.encryption_key.encrypt(&encoded_keys)?;
 | 
			
		||||
        // Put in store
 | 
			
		||||
        Ok(self.store.set(KEYSPACE_NAME, &value).await?)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Loads the encrypted keyspace from the underlying storage
 | 
			
		||||
    async fn load_keyspace(&mut self) -> Result<(), Error> {
 | 
			
		||||
        let Some(ks) = self.store.get(KEYSPACE_NAME).await? else {
 | 
			
		||||
            // Keyspace doesn't exist yet, nothing to do here
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let raw = self.encryption_key.decrypt(&ks)?;
 | 
			
		||||
 | 
			
		||||
        let (decoded_keys, _): (HashMap<String, Key>, _) =
 | 
			
		||||
            bincode::serde::decode_from_slice(&raw, BINCODE_CONFIG)?;
 | 
			
		||||
 | 
			
		||||
        self.keys = decoded_keys;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								vault/src/keyspace/fallback.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								vault/src/keyspace/fallback.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
			
		||||
use std::{collections::HashMap, io::Write, path::PathBuf};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    error::Error,
 | 
			
		||||
    key::{Key, symmetric::SymmetricKey},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Magic value used as header in decrypted keyspace files.
 | 
			
		||||
const KEYSPACE_MAGIC: [u8; 14] = [
 | 
			
		||||
    118, 97, 117, 108, 116, 95, 107, 101, 121, 115, 112, 97, 99, 101,
 | 
			
		||||
]; //"vault_keyspace"
 | 
			
		||||
 | 
			
		||||
/// A KeySpace using the filesystem as storage
 | 
			
		||||
pub struct KeySpace {
 | 
			
		||||
    /// Path to file on disk
 | 
			
		||||
    path: PathBuf,
 | 
			
		||||
    /// Decrypted keys held in the store
 | 
			
		||||
    keystore: HashMap<String, Key>,
 | 
			
		||||
    /// The encryption key used to encrypt/decrypt the storage.
 | 
			
		||||
    encryption_key: SymmetricKey,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl KeySpace {
 | 
			
		||||
    /// Opens the `KeySpace`. If it does not exist, it will be created. The provided encryption key
 | 
			
		||||
    /// will be used for Encrypting and Decrypting the content of the KeySpace.
 | 
			
		||||
    async fn open(path: PathBuf, encryption_key: SymmetricKey) -> Result<Self, Error> {
 | 
			
		||||
        /// If the path does not exist, create it first and write the encrypted magic header
 | 
			
		||||
        if !path.exists() {
 | 
			
		||||
            // Since we checked path does not exist, the only errors here can be actual IO errors
 | 
			
		||||
            // (unless something else creates the same file at the same time).
 | 
			
		||||
            let mut file = std::fs::File::create_new(path)?;
 | 
			
		||||
            let content = encryption_key.encrypt(&KEYSPACE_MAGIC)?;
 | 
			
		||||
            file.write_all(&content)?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Load file, try to decrypt, verify magic header, deserialize keystore
 | 
			
		||||
        let mut file = std::fs::File::open(path)?;
 | 
			
		||||
        let mut buffer = Vec::new();
 | 
			
		||||
        file.read_to_end(&mut buffer)?;
 | 
			
		||||
        if buffer.len() < KEYSPACE_MAGIC.len() {
 | 
			
		||||
            return Err(Error::CorruptKeyspace);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if buffer[..KEYSPACE_MAGIC.len()] != KEYSPACE_MAGIC {
 | 
			
		||||
            return Err(Error::CorruptKeyspace);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO: Actual deserialization
 | 
			
		||||
 | 
			
		||||
        todo!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get a [`Key`] previously stored under the provided name.
 | 
			
		||||
    async fn get(&self, key: &str) -> Result<Option<Key>, Error> {
 | 
			
		||||
        todo!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Store a [`Key`] under the provided name.
 | 
			
		||||
    async fn set(&self, key: &str, value: Key) -> Result<(), Error> {
 | 
			
		||||
        todo!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Delete the [`Key`] stored under the provided name.
 | 
			
		||||
    async fn delete(&self, key: &str) -> Result<(), Error> {
 | 
			
		||||
        todo!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Iterate over all stored [`keys`](Key) in the KeySpace
 | 
			
		||||
    async fn iter(&self) -> Result<impl Iterator<Item = (String, Key)>, Error> {
 | 
			
		||||
        todo!()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								vault/src/keyspace/wasm.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								vault/src/keyspace/wasm.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
use crate::{error::Error, key::Key};
 | 
			
		||||
 | 
			
		||||
/// KeySpace represents an IndexDB keyspace
 | 
			
		||||
pub struct KeySpace {}
 | 
			
		||||
 | 
			
		||||
impl KeySpace {
 | 
			
		||||
    /// Get a [`Key`] previously stored under the provided name.
 | 
			
		||||
    async fn get(&self, key: &str) -> Result<Option<Key>, Error> {
 | 
			
		||||
        todo!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Store a [`Key`] under the provided name.
 | 
			
		||||
    async fn set(&self, key: &str, value: Key) -> Result<(), Error> {
 | 
			
		||||
        todo!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Delete the [`Key`] stored under the provided name.
 | 
			
		||||
    async fn delete(&self, key: &str) -> Result<(), Error> {
 | 
			
		||||
        todo!();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Iterate over all stored [`keys`](Key) in the KeySpace
 | 
			
		||||
    async fn iter(&self) -> Result<impl Iterator<Item = (String, Key)>, Error> {
 | 
			
		||||
        todo!()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								vault/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								vault/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
pub mod error;
 | 
			
		||||
pub mod key;
 | 
			
		||||
pub mod keyspace;
 | 
			
		||||
 | 
			
		||||
#[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
use std::path::{Path, PathBuf};
 | 
			
		||||
 | 
			
		||||
use crate::{error::Error, key::symmetric::SymmetricKey, keyspace::KeySpace};
 | 
			
		||||
 | 
			
		||||
/// Vault is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where
 | 
			
		||||
/// each [`space`](KeySpace) is itself an encrypted key-value store
 | 
			
		||||
pub struct Vault {
 | 
			
		||||
    #[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
    path: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
impl Vault {
 | 
			
		||||
    /// Create a new store at the given path, creating the path if it does not exist yet.
 | 
			
		||||
    pub async fn new(path: &Path) -> Result<Self, Error> {
 | 
			
		||||
        if path.exists() {
 | 
			
		||||
            if !path.is_dir() {
 | 
			
		||||
                return Err(Error::IOError(std::io::Error::new(
 | 
			
		||||
                    std::io::ErrorKind::InvalidInput,
 | 
			
		||||
                    "expected directory",
 | 
			
		||||
                )));
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            std::fs::create_dir_all(path)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            path: path.to_path_buf(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Vault {
 | 
			
		||||
    /// Open a keyspace with the given name
 | 
			
		||||
    pub async fn open_keyspace(&self, name: &str, password: &str) -> Result<KeySpace, Error> {
 | 
			
		||||
        let encryption_key = SymmetricKey::derive_from_password(password);
 | 
			
		||||
        #[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
        {
 | 
			
		||||
            let path = self.path.join(name);
 | 
			
		||||
            KeySpace::open(&path, encryption_key).await
 | 
			
		||||
        }
 | 
			
		||||
        #[cfg(target_arch = "wasm32")]
 | 
			
		||||
        {
 | 
			
		||||
            KeySpace::open(name, encryption_key).await
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user