Compare commits
	
		
			2 Commits
		
	
	
		
			61f5331804
			...
			f1806eb788
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f1806eb788 | |||
|  | 6e5d9b35e8 | 
| @@ -1,64 +1,76 @@ | ||||
| # Hero Vault Cryptography Examples | ||||
| # SAL Vault Examples | ||||
|  | ||||
| This directory contains examples demonstrating the Hero Vault cryptography functionality integrated into the SAL project. | ||||
| This directory contains examples demonstrating the SAL Vault functionality. | ||||
|  | ||||
| ## Overview | ||||
|  | ||||
| Hero Vault provides cryptographic operations including: | ||||
| SAL Vault provides secure key management and cryptographic operations including: | ||||
|  | ||||
| - Key space management (creation, loading, encryption, decryption) | ||||
| - Keypair management (creation, selection, listing) | ||||
| - Digital signatures (signing and verification) | ||||
| - Symmetric encryption (key generation, encryption, decryption) | ||||
| - Ethereum wallet functionality | ||||
| - Smart contract interactions | ||||
| - Key-value store with encryption | ||||
| - Vault creation and management | ||||
| - KeySpace operations (encrypted key-value stores) | ||||
| - Symmetric key generation and operations | ||||
| - Asymmetric key operations (signing and verification) | ||||
| - Secure key derivation from passwords | ||||
|  | ||||
| ## Example Files | ||||
| ## Current Status | ||||
|  | ||||
| - `example.rhai` - Basic example demonstrating key management, signing, and encryption | ||||
| - `advanced_example.rhai` - Advanced example with error handling, conditional logic, and more complex operations | ||||
| - `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk | ||||
| - `load_existing_space.rhai` - Shows how to load a previously created key space and use its keypairs | ||||
| - `contract_example.rhai` - Demonstrates loading a contract ABI and interacting with smart contracts | ||||
| - `agung_send_transaction.rhai` - Demonstrates sending native tokens on the Agung network | ||||
| - `agung_contract_with_args.rhai` - Shows how to interact with contracts with arguments on Agung | ||||
| ⚠️ **Note**: The vault module is currently being updated to use Lee's implementation. | ||||
| The Rhai scripting integration is temporarily disabled while we adapt the examples | ||||
| to work with the new vault API. | ||||
|  | ||||
| ## Running the Examples | ||||
| ## Available Operations | ||||
|  | ||||
| You can run the examples using the `herodo` tool that comes with the SAL project: | ||||
| - **Vault Management**: Create and manage vault instances | ||||
| - **KeySpace Operations**: Open encrypted key-value stores within vaults | ||||
| - **Symmetric Encryption**: Generate keys and encrypt/decrypt data | ||||
| - **Asymmetric Operations**: Create keypairs, sign messages, verify signatures | ||||
|  | ||||
| ```bash | ||||
| # Run a single example | ||||
| herodo --path example.rhai | ||||
| ## Example Files (Legacy - Sameh's Implementation) | ||||
|  | ||||
| # Run all examples using the provided script | ||||
| ./run_examples.sh | ||||
| ⚠️ **These examples are currently archived and use the previous vault implementation**: | ||||
|  | ||||
| - `_archive/example.rhai` - Basic example demonstrating key management, signing, and encryption | ||||
| - `_archive/advanced_example.rhai` - Advanced example with error handling and complex operations | ||||
| - `_archive/key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk | ||||
| - `_archive/load_existing_space.rhai` - Shows how to load a previously created key space | ||||
| - `_archive/contract_example.rhai` - Demonstrates smart contract interactions (Ethereum) | ||||
| - `_archive/agung_send_transaction.rhai` - Demonstrates Ethereum transactions on Agung network | ||||
| - `_archive/agung_contract_with_args.rhai` - Shows contract interactions with arguments | ||||
|  | ||||
| ## Current Implementation (Lee's Vault) | ||||
|  | ||||
| The current vault implementation provides: | ||||
|  | ||||
| ```rust | ||||
| // Create a new vault | ||||
| let vault = Vault::new(&path).await?; | ||||
|  | ||||
| // Open an encrypted keyspace | ||||
| let keyspace = vault.open_keyspace("my_space", "password").await?; | ||||
|  | ||||
| // Perform cryptographic operations | ||||
| // (API documentation coming soon) | ||||
| ``` | ||||
|  | ||||
| ## Key Space Storage | ||||
| ## Migration Status | ||||
|  | ||||
| Key spaces are stored in the `~/.hero-vault/key-spaces/` directory by default. Each key space is stored in a separate JSON file named after the key space (e.g., `my_space.json`). | ||||
|  | ||||
| ## Ethereum Functionality | ||||
|  | ||||
| The Hero Vault module provides comprehensive Ethereum wallet functionality: | ||||
|  | ||||
| - Creating and managing wallets for different networks | ||||
| - Sending ETH transactions | ||||
| - Checking balances | ||||
| - Interacting with smart contracts (read and write functions) | ||||
| - Support for multiple networks (Ethereum, Gnosis, Peaq, Agung, etc.) | ||||
| - ✅ **Vault Core**: Lee's implementation is active | ||||
| - ✅ **Archive**: Sameh's implementation preserved in `vault/_archive/` | ||||
| - ⏳ **Rhai Integration**: Being developed for Lee's implementation | ||||
| - ⏳ **Examples**: Will be updated to use Lee's API | ||||
| - ❌ **Ethereum Features**: Not available in Lee's implementation | ||||
|  | ||||
| ## Security | ||||
|  | ||||
| Key spaces are encrypted with ChaCha20Poly1305 using a key derived from the provided password. The encryption ensures that the key material is secure at rest. | ||||
| The vault uses: | ||||
|  | ||||
| ## Best Practices | ||||
| - **ChaCha20Poly1305** for symmetric encryption | ||||
| - **Password-based key derivation** for keyspace encryption | ||||
| - **Secure key storage** with proper isolation | ||||
|  | ||||
| 1. **Use Strong Passwords**: Since the security of your key spaces depends on the strength of your passwords, use strong, unique passwords. | ||||
| 2. **Backup Key Spaces**: Regularly backup your key spaces directory to prevent data loss. | ||||
| 3. **Script Organization**: Split your scripts into logical units, with separate scripts for key creation and key usage. | ||||
| 4. **Error Handling**: Always check the return values of functions to ensure operations succeeded before proceeding. | ||||
| 5. **Network Selection**: When working with Ethereum functionality, be explicit about which network you're targeting to avoid confusion. | ||||
| 6. **Gas Management**: For Ethereum transactions, consider gas costs and set appropriate gas limits. | ||||
| ## Next Steps | ||||
|  | ||||
| 1. **Rhai Integration**: Implement Rhai bindings for Lee's vault | ||||
| 2. **New Examples**: Create examples using Lee's simpler API | ||||
| 3. **Documentation**: Complete API documentation for Lee's implementation | ||||
| 4. **Migration Guide**: Provide guidance for users migrating from Sameh's implementation | ||||
|   | ||||
| @@ -96,8 +96,9 @@ pub use sal_text::rhai::register_text_module; | ||||
| // Re-export net module | ||||
| pub use sal_net::rhai::register_net_module; | ||||
|  | ||||
| // Re-export crypto module | ||||
| pub use sal_vault::rhai::register_crypto_module; | ||||
| // Re-export crypto module - TEMPORARILY DISABLED | ||||
| // TODO: Implement rhai module for Lee's vault implementation | ||||
| // pub use sal_vault::rhai::register_crypto_module; | ||||
|  | ||||
| // Re-export kubernetes module | ||||
| pub use sal_kubernetes::rhai::register_kubernetes_module; | ||||
| @@ -158,8 +159,9 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> { | ||||
|  | ||||
|     // RFS module functions are now registered as part of sal_virt above | ||||
|  | ||||
|     // Register Crypto module functions | ||||
|     register_crypto_module(engine)?; | ||||
|     // Register Crypto module functions - TEMPORARILY DISABLED | ||||
|     // TODO: Implement rhai module for Lee's vault implementation | ||||
|     // register_crypto_module(engine)?; | ||||
|  | ||||
|     // Register Kubernetes module functions | ||||
|     register_kubernetes_module(engine)?; | ||||
|   | ||||
| @@ -216,7 +216,7 @@ fn test_module_registration_functions() { | ||||
|     assert!(sal_rhai::register_os_module(&mut engine).is_ok()); | ||||
|     assert!(sal_rhai::register_process_module(&mut engine).is_ok()); | ||||
|     assert!(sal_rhai::register_git_module(&mut engine).is_ok()); | ||||
|     assert!(sal_rhai::register_crypto_module(&mut engine).is_ok()); | ||||
|     // assert!(sal_rhai::register_crypto_module(&mut engine).is_ok()); // Temporarily disabled | ||||
|     assert!(sal_rhai::register_redisclient_module(&mut engine).is_ok()); | ||||
|     assert!(sal_rhai::register_postgresclient_module(&mut engine).is_ok()); | ||||
|     assert!(sal_rhai::register_mycelium_module(&mut engine).is_ok()); | ||||
|   | ||||
| @@ -3,45 +3,28 @@ name = "sal-vault" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| authors = ["PlanetFirst <info@incubaid.com>"] | ||||
| description = "SAL Vault - Cryptographic functionality including key management, digital signatures, symmetric encryption, Ethereum wallets, and encrypted key-value store" | ||||
| description = "SAL Vault - Secure key management and cryptographic operations" | ||||
| repository = "https://git.threefold.info/herocode/sal" | ||||
| license = "Apache-2.0" | ||||
| keywords = ["vault", "crypto", "keys", "security", "sal"] | ||||
| categories = ["cryptography", "api-bindings"] | ||||
|  | ||||
| [features] | ||||
| # native = ["kv/native"] | ||||
| # wasm = ["kv/web"] | ||||
| # Features temporarily disabled due to external dependency issues | ||||
|  | ||||
| [dependencies] | ||||
| # Core cryptographic 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 = ["ecdsa", "ecdh"] } | ||||
| sha2 = "0.10.7" | ||||
| rand = "0.8.5" | ||||
|  | ||||
| # Ethereum dependencies | ||||
| ethers = { version = "2.0.7", features = ["legacy"] } | ||||
| hex = "0.4" | ||||
|  | ||||
| # Serialization and data handling | ||||
| serde = { version = "1.0", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
| base64 = "0.22.1" | ||||
|  | ||||
| # Error handling | ||||
| thiserror = "2.0.12" | ||||
|  | ||||
| # Async runtime and utilities | ||||
| tokio = { version = "1.45.0", features = ["full"] } | ||||
| once_cell = "1.18.0" | ||||
|  | ||||
| # File system utilities | ||||
| dirs = "6.0.0" | ||||
|  | ||||
| # Rhai scripting support | ||||
| rhai = { version = "1.12.0", features = ["sync"] } | ||||
|  | ||||
| # UUID generation | ||||
| uuid = { version = "1.16.0", features = ["v4"] } | ||||
|  | ||||
| # Logging | ||||
| log = "0.4" | ||||
|  | ||||
| [dev-dependencies] | ||||
| tempfile = "3.5" | ||||
| tokio-test = "0.4.4" | ||||
| k256 = { version = "0.13.4", features = ["ecdh"] } | ||||
| sha2 = "0.10.9" | ||||
| # kv = { git = "https://git.ourworld.tf/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" } | ||||
| # Temporarily disabled due to broken external dependencies | ||||
| bincode = { version = "2.0.1", features = ["serde"] } | ||||
| pbkdf2 = "0.12.2" | ||||
|   | ||||
							
								
								
									
										224
									
								
								vault/README.md
									
									
									
									
									
								
							
							
						
						
									
										224
									
								
								vault/README.md
									
									
									
									
									
								
							| @@ -1,166 +1,148 @@ | ||||
| # SAL Vault (`sal-vault`) | ||||
| # SAL Vault | ||||
|  | ||||
| SAL Vault is a comprehensive cryptographic library that provides secure key management, digital signatures, symmetric encryption, Ethereum wallet functionality, and encrypted key-value storage. | ||||
| A secure, encrypted key-value store system for the System Abstraction Layer (SAL). | ||||
|  | ||||
| ## Installation | ||||
| ## Overview | ||||
|  | ||||
| Add this to your `Cargo.toml`: | ||||
| SAL Vault provides a two-tiered encrypted storage system: | ||||
|  | ||||
| ```toml | ||||
| [dependencies] | ||||
| sal-vault = "0.1.0" | ||||
| ``` | ||||
| 1. **Vault**: A collection of encrypted keyspaces | ||||
| 2. **KeySpace**: An individual encrypted key-value store within a vault | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| ### Core Cryptographic Operations | ||||
| - **Symmetric Encryption**: ChaCha20Poly1305 AEAD cipher for secure data encryption | ||||
| - **Key Derivation**: PBKDF2-based key derivation from passwords | ||||
| - **Digital Signatures**: ECDSA signing and verification using secp256k1 curves | ||||
| - **Key Management**: Secure keypair generation and storage | ||||
| - **Secure Storage**: ChaCha20Poly1305 encryption for all data | ||||
| - **Password-Based Encryption**: Keyspaces are encrypted using password-derived keys | ||||
| - **Cross-Platform**: Works on both native and WASM targets | ||||
| - **Async API**: Fully asynchronous operations | ||||
| - **Type Safety**: Strong typing with comprehensive error handling | ||||
|  | ||||
| ### Keyspace Management | ||||
| - **Multiple Keyspaces**: Organize keys into separate, password-protected spaces | ||||
| - **Session Management**: Secure session handling with automatic cleanup | ||||
| - **Keypair Organization**: Named keypairs within keyspaces for easy management | ||||
| ## Architecture | ||||
|  | ||||
| ### Ethereum Integration | ||||
| - **Wallet Functionality**: Create and manage Ethereum wallets from keypairs | ||||
| - **Transaction Signing**: Sign Ethereum transactions securely | ||||
| - **Smart Contract Interaction**: Call read functions on smart contracts | ||||
| - **Multi-Network Support**: Support for different Ethereum networks | ||||
| ``` | ||||
| Vault | ||||
| ├── KeySpace 1 (encrypted with password A) | ||||
| ├── KeySpace 2 (encrypted with password B) | ||||
| └── KeySpace N (encrypted with password N) | ||||
| ``` | ||||
|  | ||||
| ### Key-Value Store | ||||
| - **Encrypted Storage**: Store key-value pairs with automatic encryption | ||||
| - **Secure Persistence**: Data is encrypted before being written to disk | ||||
| - **Type Safety**: Strongly typed storage and retrieval operations | ||||
|  | ||||
| ### Rhai Scripting Integration | ||||
| - **Complete API Exposure**: All vault functionality available in Rhai scripts | ||||
| - **Session Management**: Script-accessible session and keyspace management | ||||
| - **Cryptographic Operations**: Encryption, signing, and verification in scripts | ||||
| Each keyspace is independently encrypted, allowing different access controls and security boundaries. | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| ### Basic Cryptographic Operations | ||||
| ### Creating a Vault | ||||
|  | ||||
| ```rust | ||||
| use sal_vault::symmetric::implementation::{encrypt_symmetric, decrypt_symmetric, generate_symmetric_key}; | ||||
| use sal_vault::{Vault, Error}; | ||||
| use std::path::Path; | ||||
|  | ||||
| // Generate a symmetric key | ||||
| let key = generate_symmetric_key(); | ||||
|  | ||||
| // Encrypt data | ||||
| let message = b"Hello, World!"; | ||||
| let encrypted = encrypt_symmetric(&key, message)?; | ||||
|  | ||||
| // Decrypt data | ||||
| let decrypted = decrypt_symmetric(&key, &encrypted)?; | ||||
| ``` | ||||
|  | ||||
| ### Keyspace and Keypair Management | ||||
|  | ||||
| ```rust | ||||
| use sal_vault::keyspace::{KeySpace, KeyPair}; | ||||
|  | ||||
| // Create a new keyspace | ||||
| let mut keyspace = KeySpace::new("my_keyspace"); | ||||
|  | ||||
| // Add a keypair | ||||
| keyspace.add_keypair("main_key")?; | ||||
|  | ||||
| // Sign data | ||||
| if let Some(keypair) = keyspace.keypairs.get("main_key") { | ||||
|     let message = b"Important message"; | ||||
|     let signature = keypair.sign(message); | ||||
|     let is_valid = keypair.verify(message, &signature)?; | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), Error> { | ||||
|     // Create a new vault at the specified path | ||||
|     let vault = Vault::new(Path::new("./my_vault")).await?; | ||||
|      | ||||
|     // Open an encrypted keyspace | ||||
|     let keyspace = vault.open_keyspace("user_data", "secure_password").await?; | ||||
|      | ||||
|     // Use the keyspace for encrypted storage | ||||
|     // (KeySpace API documentation coming soon) | ||||
|      | ||||
|     Ok(()) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Ethereum Wallet Operations | ||||
| ### WASM Support | ||||
|  | ||||
| The vault also supports WASM targets with browser-compatible storage: | ||||
|  | ||||
| ```rust | ||||
| use sal_vault::ethereum::wallet::EthereumWallet; | ||||
| use sal_vault::ethereum::networks::NetworkConfig; | ||||
|  | ||||
| // Create wallet from keypair | ||||
| let network = NetworkConfig::mainnet(); | ||||
| let wallet = EthereumWallet::from_keypair(&keypair, network)?; | ||||
|  | ||||
| // Get wallet address | ||||
| let address = wallet.address(); | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| async fn wasm_example() -> Result<(), Error> { | ||||
|     let vault = Vault::new().await?; // No path needed for WASM | ||||
|     let keyspace = vault.open_keyspace("session_data", "password").await?; | ||||
|     Ok(()) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Rhai Scripting | ||||
| ## Security | ||||
|  | ||||
| ```rhai | ||||
| // Create and manage keyspaces | ||||
| create_key_space("personal", "secure_password"); | ||||
| select_keyspace("personal"); | ||||
| ### Encryption | ||||
|  | ||||
| // Create and use keypairs | ||||
| create_keypair("signing_key"); | ||||
| select_keypair("signing_key"); | ||||
| - **Algorithm**: ChaCha20Poly1305 (AEAD) | ||||
| - **Key Derivation**: PBKDF2 with secure parameters | ||||
| - **Nonce Generation**: Cryptographically secure random nonces | ||||
| - **Authentication**: Built-in authentication prevents tampering | ||||
|  | ||||
| // Sign and verify data | ||||
| let message = "Important document"; | ||||
| let signature = sign(message); | ||||
| let is_valid = verify(message, signature); | ||||
| ### Best Practices | ||||
|  | ||||
| // Symmetric encryption | ||||
| let key = generate_key(); | ||||
| let encrypted = encrypt(key, "secret data"); | ||||
| let decrypted = decrypt(key, encrypted); | ||||
| ``` | ||||
|  | ||||
| ## Security Features | ||||
|  | ||||
| - **Memory Safety**: All sensitive data is handled securely in memory | ||||
| - **Secure Random Generation**: Uses cryptographically secure random number generation | ||||
| - **Password-Based Encryption**: Keyspaces are protected with password-derived keys | ||||
| - **Session Isolation**: Each session maintains separate state and security context | ||||
| - **Constant-Time Operations**: Critical operations use constant-time implementations | ||||
| 1. **Strong Passwords**: Use strong, unique passwords for each keyspace | ||||
| 2. **Secure Storage**: Store vault files in secure locations | ||||
| 3. **Access Control**: Limit filesystem access to vault directories | ||||
| 4. **Backup Strategy**: Implement secure backup procedures | ||||
| 5. **Key Rotation**: Periodically change keyspace passwords | ||||
|  | ||||
| ## Error Handling | ||||
|  | ||||
| The library provides comprehensive error handling through the `CryptoError` enum: | ||||
| The vault uses a comprehensive error system: | ||||
|  | ||||
| ```rust | ||||
| use sal_vault::error::CryptoError; | ||||
| use sal_vault::Error; | ||||
|  | ||||
| match some_crypto_operation() { | ||||
|     Ok(result) => println!("Success: {:?}", result), | ||||
|     Err(CryptoError::InvalidKeyLength) => println!("Invalid key length provided"), | ||||
|     Err(CryptoError::EncryptionFailed(msg)) => println!("Encryption failed: {}", msg), | ||||
|     Err(CryptoError::KeypairNotFound(name)) => println!("Keypair '{}' not found", name), | ||||
|     Err(e) => println!("Other error: {}", e), | ||||
| match vault.open_keyspace("test", "password").await { | ||||
|     Ok(keyspace) => { | ||||
|         // Success - use the keyspace | ||||
|     } | ||||
|     Err(Error::IOError(io_err)) => { | ||||
|         // Handle I/O errors (file system issues) | ||||
|     } | ||||
|     Err(Error::CryptoError(crypto_err)) => { | ||||
|         // Handle cryptographic errors (wrong password, corruption) | ||||
|     } | ||||
|     Err(other) => { | ||||
|         // Handle other errors | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Testing | ||||
| ## Migration from Previous Implementation | ||||
|  | ||||
| The package includes comprehensive tests covering all functionality: | ||||
| This vault implementation replaces the previous Ethereum-focused vault. Key differences: | ||||
|  | ||||
| ```bash | ||||
| # Run all tests | ||||
| cargo test | ||||
| ### What's New | ||||
| - ✅ Simpler, more focused API | ||||
| - ✅ Better cross-platform support | ||||
| - ✅ Improved security model | ||||
| - ✅ Cleaner error handling | ||||
|  | ||||
| # Run specific test categories | ||||
| cargo test crypto_tests | ||||
| cargo test rhai_integration_tests | ||||
| ``` | ||||
| ### What's Changed | ||||
| - ❌ No Ethereum wallet functionality | ||||
| - ❌ No smart contract integration | ||||
| - ❌ No built-in signing operations | ||||
| - ⏳ Rhai scripting integration (coming soon) | ||||
|  | ||||
| **Note**: The Rhai integration tests use global state and are automatically serialized using a test mutex to prevent interference between parallel test runs. | ||||
| ### Archived Implementation | ||||
|  | ||||
| ## Dependencies | ||||
| The previous implementation is preserved in `_archive/` for reference and potential feature extraction. | ||||
|  | ||||
| - `chacha20poly1305`: Symmetric encryption | ||||
| - `k256`: Elliptic curve cryptography | ||||
| - `ethers`: Ethereum functionality | ||||
| - `serde`: Serialization support | ||||
| - `rhai`: Scripting integration | ||||
| - `tokio`: Async runtime support | ||||
| ## Development Status | ||||
|  | ||||
| - ✅ **Core Vault**: Complete and functional | ||||
| - ✅ **KeySpace Operations**: Basic implementation ready | ||||
| - ✅ **Encryption**: Secure ChaCha20Poly1305 implementation | ||||
| - ⏳ **Rhai Integration**: In development | ||||
| - ⏳ **Extended API**: Additional convenience methods planned | ||||
| - ⏳ **Documentation**: API docs being completed | ||||
|  | ||||
| ## Contributing | ||||
|  | ||||
| When contributing to the vault module: | ||||
|  | ||||
| 1. Maintain security-first approach | ||||
| 2. Ensure cross-platform compatibility | ||||
| 3. Add comprehensive tests for new features | ||||
| 4. Update documentation for API changes | ||||
| 5. Consider WASM compatibility for new features | ||||
|  | ||||
| ## License | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0. | ||||
| This module is part of the SAL project and follows the same licensing terms. | ||||
|   | ||||
							
								
								
									
										47
									
								
								vault/_archive/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								vault/_archive/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| [package] | ||||
| name = "sal-vault" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| authors = ["PlanetFirst <info@incubaid.com>"] | ||||
| description = "SAL Vault - Cryptographic functionality including key management, digital signatures, symmetric encryption, Ethereum wallets, and encrypted key-value store" | ||||
| repository = "https://git.threefold.info/herocode/sal" | ||||
| license = "Apache-2.0" | ||||
|  | ||||
| [dependencies] | ||||
| # Core cryptographic dependencies | ||||
| chacha20poly1305 = "0.10.1" | ||||
| k256 = { version = "0.13.4", features = ["ecdsa", "ecdh"] } | ||||
| sha2 = "0.10.7" | ||||
| rand = "0.8.5" | ||||
|  | ||||
| # Ethereum dependencies | ||||
| ethers = { version = "2.0.7", features = ["legacy"] } | ||||
| hex = "0.4" | ||||
|  | ||||
| # Serialization and data handling | ||||
| serde = { version = "1.0", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
| base64 = "0.22.1" | ||||
|  | ||||
| # Error handling | ||||
| thiserror = "2.0.12" | ||||
|  | ||||
| # Async runtime and utilities | ||||
| tokio = { version = "1.45.0", features = ["full"] } | ||||
| once_cell = "1.18.0" | ||||
|  | ||||
| # File system utilities | ||||
| dirs = "6.0.0" | ||||
|  | ||||
| # Rhai scripting support | ||||
| rhai = { version = "1.12.0", features = ["sync"] } | ||||
|  | ||||
| # UUID generation | ||||
| uuid = { version = "1.16.0", features = ["v4"] } | ||||
|  | ||||
| # Logging | ||||
| log = "0.4" | ||||
|  | ||||
| [dev-dependencies] | ||||
| tempfile = "3.5" | ||||
| tokio-test = "0.4.4" | ||||
							
								
								
									
										166
									
								
								vault/_archive/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								vault/_archive/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,166 @@ | ||||
| # SAL Vault (`sal-vault`) | ||||
|  | ||||
| SAL Vault is a comprehensive cryptographic library that provides secure key management, digital signatures, symmetric encryption, Ethereum wallet functionality, and encrypted key-value storage. | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| Add this to your `Cargo.toml`: | ||||
|  | ||||
| ```toml | ||||
| [dependencies] | ||||
| sal-vault = "0.1.0" | ||||
| ``` | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| ### Core Cryptographic Operations | ||||
| - **Symmetric Encryption**: ChaCha20Poly1305 AEAD cipher for secure data encryption | ||||
| - **Key Derivation**: PBKDF2-based key derivation from passwords | ||||
| - **Digital Signatures**: ECDSA signing and verification using secp256k1 curves | ||||
| - **Key Management**: Secure keypair generation and storage | ||||
|  | ||||
| ### Keyspace Management | ||||
| - **Multiple Keyspaces**: Organize keys into separate, password-protected spaces | ||||
| - **Session Management**: Secure session handling with automatic cleanup | ||||
| - **Keypair Organization**: Named keypairs within keyspaces for easy management | ||||
|  | ||||
| ### Ethereum Integration | ||||
| - **Wallet Functionality**: Create and manage Ethereum wallets from keypairs | ||||
| - **Transaction Signing**: Sign Ethereum transactions securely | ||||
| - **Smart Contract Interaction**: Call read functions on smart contracts | ||||
| - **Multi-Network Support**: Support for different Ethereum networks | ||||
|  | ||||
| ### Key-Value Store | ||||
| - **Encrypted Storage**: Store key-value pairs with automatic encryption | ||||
| - **Secure Persistence**: Data is encrypted before being written to disk | ||||
| - **Type Safety**: Strongly typed storage and retrieval operations | ||||
|  | ||||
| ### Rhai Scripting Integration | ||||
| - **Complete API Exposure**: All vault functionality available in Rhai scripts | ||||
| - **Session Management**: Script-accessible session and keyspace management | ||||
| - **Cryptographic Operations**: Encryption, signing, and verification in scripts | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| ### Basic Cryptographic Operations | ||||
|  | ||||
| ```rust | ||||
| use sal_vault::symmetric::implementation::{encrypt_symmetric, decrypt_symmetric, generate_symmetric_key}; | ||||
|  | ||||
| // Generate a symmetric key | ||||
| let key = generate_symmetric_key(); | ||||
|  | ||||
| // Encrypt data | ||||
| let message = b"Hello, World!"; | ||||
| let encrypted = encrypt_symmetric(&key, message)?; | ||||
|  | ||||
| // Decrypt data | ||||
| let decrypted = decrypt_symmetric(&key, &encrypted)?; | ||||
| ``` | ||||
|  | ||||
| ### Keyspace and Keypair Management | ||||
|  | ||||
| ```rust | ||||
| use sal_vault::keyspace::{KeySpace, KeyPair}; | ||||
|  | ||||
| // Create a new keyspace | ||||
| let mut keyspace = KeySpace::new("my_keyspace"); | ||||
|  | ||||
| // Add a keypair | ||||
| keyspace.add_keypair("main_key")?; | ||||
|  | ||||
| // Sign data | ||||
| if let Some(keypair) = keyspace.keypairs.get("main_key") { | ||||
|     let message = b"Important message"; | ||||
|     let signature = keypair.sign(message); | ||||
|     let is_valid = keypair.verify(message, &signature)?; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Ethereum Wallet Operations | ||||
|  | ||||
| ```rust | ||||
| use sal_vault::ethereum::wallet::EthereumWallet; | ||||
| use sal_vault::ethereum::networks::NetworkConfig; | ||||
|  | ||||
| // Create wallet from keypair | ||||
| let network = NetworkConfig::mainnet(); | ||||
| let wallet = EthereumWallet::from_keypair(&keypair, network)?; | ||||
|  | ||||
| // Get wallet address | ||||
| let address = wallet.address(); | ||||
| ``` | ||||
|  | ||||
| ### Rhai Scripting | ||||
|  | ||||
| ```rhai | ||||
| // Create and manage keyspaces | ||||
| create_key_space("personal", "secure_password"); | ||||
| select_keyspace("personal"); | ||||
|  | ||||
| // Create and use keypairs | ||||
| create_keypair("signing_key"); | ||||
| select_keypair("signing_key"); | ||||
|  | ||||
| // Sign and verify data | ||||
| let message = "Important document"; | ||||
| let signature = sign(message); | ||||
| let is_valid = verify(message, signature); | ||||
|  | ||||
| // Symmetric encryption | ||||
| let key = generate_key(); | ||||
| let encrypted = encrypt(key, "secret data"); | ||||
| let decrypted = decrypt(key, encrypted); | ||||
| ``` | ||||
|  | ||||
| ## Security Features | ||||
|  | ||||
| - **Memory Safety**: All sensitive data is handled securely in memory | ||||
| - **Secure Random Generation**: Uses cryptographically secure random number generation | ||||
| - **Password-Based Encryption**: Keyspaces are protected with password-derived keys | ||||
| - **Session Isolation**: Each session maintains separate state and security context | ||||
| - **Constant-Time Operations**: Critical operations use constant-time implementations | ||||
|  | ||||
| ## Error Handling | ||||
|  | ||||
| The library provides comprehensive error handling through the `CryptoError` enum: | ||||
|  | ||||
| ```rust | ||||
| use sal_vault::error::CryptoError; | ||||
|  | ||||
| match some_crypto_operation() { | ||||
|     Ok(result) => println!("Success: {:?}", result), | ||||
|     Err(CryptoError::InvalidKeyLength) => println!("Invalid key length provided"), | ||||
|     Err(CryptoError::EncryptionFailed(msg)) => println!("Encryption failed: {}", msg), | ||||
|     Err(CryptoError::KeypairNotFound(name)) => println!("Keypair '{}' not found", name), | ||||
|     Err(e) => println!("Other error: {}", e), | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Testing | ||||
|  | ||||
| The package includes comprehensive tests covering all functionality: | ||||
|  | ||||
| ```bash | ||||
| # Run all tests | ||||
| cargo test | ||||
|  | ||||
| # Run specific test categories | ||||
| cargo test crypto_tests | ||||
| cargo test rhai_integration_tests | ||||
| ``` | ||||
|  | ||||
| **Note**: The Rhai integration tests use global state and are automatically serialized using a test mutex to prevent interference between parallel test runs. | ||||
|  | ||||
| ## Dependencies | ||||
|  | ||||
| - `chacha20poly1305`: Symmetric encryption | ||||
| - `k256`: Elliptic curve cryptography | ||||
| - `ethers`: Ethereum functionality | ||||
| - `serde`: Serialization support | ||||
| - `rhai`: Scripting integration | ||||
| - `tokio`: Async runtime support | ||||
|  | ||||
| ## License | ||||
|  | ||||
| Licensed under the Apache License, Version 2.0. | ||||
							
								
								
									
										160
									
								
								vault/_archive/src/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								vault/_archive/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. | ||||
							
								
								
									
										53
									
								
								vault/_archive/src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								vault/_archive/src/error.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| //! Error types for cryptographic operations | ||||
|  | ||||
| use thiserror::Error; | ||||
|  | ||||
| /// Errors that can occur during cryptographic operations | ||||
| #[derive(Error, Debug)] | ||||
| pub enum CryptoError { | ||||
|     /// Invalid key length | ||||
|     #[error("Invalid key length")] | ||||
|     InvalidKeyLength, | ||||
|  | ||||
|     /// Encryption failed | ||||
|     #[error("Encryption failed: {0}")] | ||||
|     EncryptionFailed(String), | ||||
|  | ||||
|     /// Decryption failed | ||||
|     #[error("Decryption failed: {0}")] | ||||
|     DecryptionFailed(String), | ||||
|  | ||||
|     /// Signature format error | ||||
|     #[error("Signature format error: {0}")] | ||||
|     SignatureFormatError(String), | ||||
|  | ||||
|     /// Keypair already exists | ||||
|     #[error("Keypair already exists: {0}")] | ||||
|     KeypairAlreadyExists(String), | ||||
|  | ||||
|     /// Keypair not found | ||||
|     #[error("Keypair not found: {0}")] | ||||
|     KeypairNotFound(String), | ||||
|  | ||||
|     /// No active key space | ||||
|     #[error("No active key space")] | ||||
|     NoActiveSpace, | ||||
|  | ||||
|     /// No keypair selected | ||||
|     #[error("No keypair selected")] | ||||
|     NoKeypairSelected, | ||||
|  | ||||
|     /// Serialization error | ||||
|     #[error("Serialization error: {0}")] | ||||
|     SerializationError(String), | ||||
|  | ||||
|     /// Invalid address format | ||||
|     #[error("Invalid address format: {0}")] | ||||
|     InvalidAddress(String), | ||||
|  | ||||
|     /// Smart contract error | ||||
|     #[error("Smart contract error: {0}")] | ||||
|     ContractError(String), | ||||
| } | ||||
|  | ||||
| // Note: Error conversion to main SAL crate will be handled at the integration level | ||||
							
								
								
									
										23
									
								
								vault/_archive/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								vault/_archive/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| //! SAL Vault: Cryptographic functionality for SAL | ||||
| //! | ||||
| //! This package provides cryptographic operations including: | ||||
| //! - Key space management (creation, loading, encryption, decryption) | ||||
| //! - Key pair management (ECDSA) | ||||
| //! - Digital signatures (signing and verification) | ||||
| //! - Symmetric encryption (ChaCha20Poly1305) | ||||
| //! - Ethereum wallet functionality | ||||
| //! - Key-value store with encryption | ||||
|  | ||||
| pub mod error; | ||||
| pub mod ethereum; | ||||
| pub mod keyspace; | ||||
| pub mod kvs; | ||||
| pub mod symmetric; | ||||
|  | ||||
| // Rhai integration module | ||||
| pub mod rhai; | ||||
|  | ||||
| // Re-export modules | ||||
| // Re-export common types for convenience | ||||
| pub use error::CryptoError; | ||||
| pub use keyspace::{KeyPair, KeySpace}; | ||||
| @@ -1,53 +1,109 @@ | ||||
| //! Error types for cryptographic operations | ||||
|  | ||||
| use thiserror::Error; | ||||
|  | ||||
| /// Errors that can occur during cryptographic operations | ||||
| #[derive(Error, Debug)] | ||||
| pub enum CryptoError { | ||||
|     /// Invalid key length | ||||
|     #[error("Invalid key length")] | ||||
|     InvalidKeyLength, | ||||
|  | ||||
|     /// Encryption failed | ||||
|     #[error("Encryption failed: {0}")] | ||||
|     EncryptionFailed(String), | ||||
|  | ||||
|     /// Decryption failed | ||||
|     #[error("Decryption failed: {0}")] | ||||
|     DecryptionFailed(String), | ||||
|  | ||||
|     /// Signature format error | ||||
|     #[error("Signature format error: {0}")] | ||||
|     SignatureFormatError(String), | ||||
|  | ||||
|     /// Keypair already exists | ||||
|     #[error("Keypair already exists: {0}")] | ||||
|     KeypairAlreadyExists(String), | ||||
|  | ||||
|     /// Keypair not found | ||||
|     #[error("Keypair not found: {0}")] | ||||
|     KeypairNotFound(String), | ||||
|  | ||||
|     /// No active key space | ||||
|     #[error("No active key space")] | ||||
|     NoActiveSpace, | ||||
|  | ||||
|     /// No keypair selected | ||||
|     #[error("No keypair selected")] | ||||
|     NoKeypairSelected, | ||||
|  | ||||
|     /// Serialization error | ||||
|     #[error("Serialization error: {0}")] | ||||
|     SerializationError(String), | ||||
|  | ||||
|     /// Invalid address format | ||||
|     #[error("Invalid address format: {0}")] | ||||
|     InvalidAddress(String), | ||||
|  | ||||
|     /// Smart contract error | ||||
|     #[error("Smart contract error: {0}")] | ||||
|     ContractError(String), | ||||
| #[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 (temporarily disabled) | ||||
|     // KV(kv::error::KVError), | ||||
|     /// An error while encoding/decoding the keyspace. | ||||
|     Coding, | ||||
| } | ||||
|  | ||||
| // Note: Error conversion to main SAL crate will be handled at the integration level | ||||
| 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()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										151
									
								
								vault/src/keyspace.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										151
									
								
								vault/src/keyspace.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,151 @@ | ||||
| // #[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::{symmetric::SymmetricKey, Key}, | ||||
| }; | ||||
|  | ||||
| // use kv::KVStore; | ||||
|  | ||||
| /// Configuration to use for bincode en/decoding. | ||||
| const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard(); | ||||
|  | ||||
| // Temporarily using simple file-based storage instead of external KV store | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| use std::fs; | ||||
|  | ||||
| // #[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 { | ||||
|     /// Path to the keyspace file (native only) | ||||
|     #[cfg(not(target_arch = "wasm32"))] | ||||
|     path: std::path::PathBuf, | ||||
|     /// Storage name for WASM | ||||
|     #[cfg(target_arch = "wasm32")] | ||||
|     name: String, | ||||
|     /// 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 keyspace_file = path.join(KEYSPACE_NAME); | ||||
|         let mut ks = Self { | ||||
|             path: keyspace_file, | ||||
|             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 mut ks = Self { | ||||
|             name: name.to_string(), | ||||
|             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)?; | ||||
|  | ||||
|         #[cfg(not(target_arch = "wasm32"))] | ||||
|         { | ||||
|             fs::write(&self.path, &value)?; | ||||
|         } | ||||
|  | ||||
|         #[cfg(target_arch = "wasm32")] | ||||
|         { | ||||
|             // For WASM, we'll use localStorage or similar | ||||
|             // This is a placeholder - would need proper WASM storage implementation | ||||
|             log::warn!("WASM storage not yet implemented"); | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     /// Loads the encrypted keyspace from the underlying storage | ||||
|     async fn load_keyspace(&mut self) -> Result<(), Error> { | ||||
|         #[cfg(not(target_arch = "wasm32"))] | ||||
|         { | ||||
|             if !self.path.exists() { | ||||
|                 // Keyspace doesn't exist yet, nothing to do here | ||||
|                 return Ok(()); | ||||
|             } | ||||
|  | ||||
|             let ks = fs::read(&self.path)?; | ||||
|             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; | ||||
|         } | ||||
|  | ||||
|         #[cfg(target_arch = "wasm32")] | ||||
|         { | ||||
|             // For WASM, we'll use localStorage or similar | ||||
|             // This is a placeholder - would need proper WASM storage implementation | ||||
|             log::warn!("WASM storage not yet implemented"); | ||||
|         } | ||||
|  | ||||
|         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!() | ||||
|     } | ||||
| } | ||||
| @@ -1,23 +1,51 @@ | ||||
| //! SAL Vault: Cryptographic functionality for SAL | ||||
| //! | ||||
| //! This package provides cryptographic operations including: | ||||
| //! - Key space management (creation, loading, encryption, decryption) | ||||
| //! - Key pair management (ECDSA) | ||||
| //! - Digital signatures (signing and verification) | ||||
| //! - Symmetric encryption (ChaCha20Poly1305) | ||||
| //! - Ethereum wallet functionality | ||||
| //! - Key-value store with encryption | ||||
|  | ||||
| pub mod error; | ||||
| pub mod ethereum; | ||||
| pub mod key; | ||||
| pub mod keyspace; | ||||
| pub mod kvs; | ||||
| pub mod symmetric; | ||||
|  | ||||
| // Rhai integration module | ||||
| pub mod rhai; | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| use std::path::{Path, PathBuf}; | ||||
|  | ||||
| // Re-export modules | ||||
| // Re-export common types for convenience | ||||
| pub use error::CryptoError; | ||||
| pub use keyspace::{KeyPair, KeySpace}; | ||||
| 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