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 | ## Overview | ||||||
|  |  | ||||||
| Hero Vault provides cryptographic operations including: | SAL Vault provides secure key management and cryptographic operations including: | ||||||
|  |  | ||||||
| - Key space management (creation, loading, encryption, decryption) | - Vault creation and management | ||||||
| - Keypair management (creation, selection, listing) | - KeySpace operations (encrypted key-value stores) | ||||||
| - Digital signatures (signing and verification) | - Symmetric key generation and operations | ||||||
| - Symmetric encryption (key generation, encryption, decryption) | - Asymmetric key operations (signing and verification) | ||||||
| - Ethereum wallet functionality | - Secure key derivation from passwords | ||||||
| - Smart contract interactions |  | ||||||
| - Key-value store with encryption |  | ||||||
|  |  | ||||||
| ## Example Files | ## Current Status | ||||||
|  |  | ||||||
| - `example.rhai` - Basic example demonstrating key management, signing, and encryption | ⚠️ **Note**: The vault module is currently being updated to use Lee's implementation. | ||||||
| - `advanced_example.rhai` - Advanced example with error handling, conditional logic, and more complex operations | The Rhai scripting integration is temporarily disabled while we adapt the examples | ||||||
| - `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk | to work with the new vault API. | ||||||
| - `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 |  | ||||||
|  |  | ||||||
| ## 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 | ## Example Files (Legacy - Sameh's Implementation) | ||||||
| # Run a single example |  | ||||||
| herodo --path example.rhai |  | ||||||
|  |  | ||||||
| # Run all examples using the provided script | ⚠️ **These examples are currently archived and use the previous vault implementation**: | ||||||
| ./run_examples.sh |  | ||||||
|  | - `_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`). | - ✅ **Vault Core**: Lee's implementation is active | ||||||
|  | - ✅ **Archive**: Sameh's implementation preserved in `vault/_archive/` | ||||||
| ## Ethereum Functionality | - ⏳ **Rhai Integration**: Being developed for Lee's implementation | ||||||
|  | - ⏳ **Examples**: Will be updated to use Lee's API | ||||||
| The Hero Vault module provides comprehensive Ethereum wallet functionality: | - ❌ **Ethereum Features**: Not available in Lee's implementation | ||||||
|  |  | ||||||
| - 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.) |  | ||||||
|  |  | ||||||
| ## Security | ## 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. | ## Next Steps | ||||||
| 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. | 1. **Rhai Integration**: Implement Rhai bindings for Lee's vault | ||||||
| 4. **Error Handling**: Always check the return values of functions to ensure operations succeeded before proceeding. | 2. **New Examples**: Create examples using Lee's simpler API | ||||||
| 5. **Network Selection**: When working with Ethereum functionality, be explicit about which network you're targeting to avoid confusion. | 3. **Documentation**: Complete API documentation for Lee's implementation | ||||||
| 6. **Gas Management**: For Ethereum transactions, consider gas costs and set appropriate gas limits. | 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 | // Re-export net module | ||||||
| pub use sal_net::rhai::register_net_module; | pub use sal_net::rhai::register_net_module; | ||||||
|  |  | ||||||
| // Re-export crypto module | // Re-export crypto module - TEMPORARILY DISABLED | ||||||
| pub use sal_vault::rhai::register_crypto_module; | // TODO: Implement rhai module for Lee's vault implementation | ||||||
|  | // pub use sal_vault::rhai::register_crypto_module; | ||||||
|  |  | ||||||
| // Re-export kubernetes module | // Re-export kubernetes module | ||||||
| pub use sal_kubernetes::rhai::register_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 |     // RFS module functions are now registered as part of sal_virt above | ||||||
|  |  | ||||||
|     // Register Crypto module functions |     // Register Crypto module functions - TEMPORARILY DISABLED | ||||||
|     register_crypto_module(engine)?; |     // TODO: Implement rhai module for Lee's vault implementation | ||||||
|  |     // register_crypto_module(engine)?; | ||||||
|  |  | ||||||
|     // Register Kubernetes module functions |     // Register Kubernetes module functions | ||||||
|     register_kubernetes_module(engine)?; |     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_os_module(&mut engine).is_ok()); | ||||||
|     assert!(sal_rhai::register_process_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_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_redisclient_module(&mut engine).is_ok()); | ||||||
|     assert!(sal_rhai::register_postgresclient_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()); |     assert!(sal_rhai::register_mycelium_module(&mut engine).is_ok()); | ||||||
|   | |||||||
| @@ -3,45 +3,28 @@ name = "sal-vault" | |||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| edition = "2021" | edition = "2021" | ||||||
| authors = ["PlanetFirst <info@incubaid.com>"] | 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" | repository = "https://git.threefold.info/herocode/sal" | ||||||
| license = "Apache-2.0" | 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] | [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" | chacha20poly1305 = "0.10.1" | ||||||
| k256 = { version = "0.13.4", features = ["ecdsa", "ecdh"] } | k256 = { version = "0.13.4", features = ["ecdh"] } | ||||||
| sha2 = "0.10.7" | sha2 = "0.10.9" | ||||||
| rand = "0.8.5" | # kv = { git = "https://git.ourworld.tf/samehabouelsaad/sal-modular", package = "kvstore", rev = "9dce815daa" } | ||||||
|  | # Temporarily disabled due to broken external dependencies | ||||||
| # Ethereum dependencies | bincode = { version = "2.0.1", features = ["serde"] } | ||||||
| ethers = { version = "2.0.7", features = ["legacy"] } | pbkdf2 = "0.12.2" | ||||||
| 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" |  | ||||||
|   | |||||||
							
								
								
									
										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 | 1. **Vault**: A collection of encrypted keyspaces | ||||||
| [dependencies] | 2. **KeySpace**: An individual encrypted key-value store within a vault | ||||||
| sal-vault = "0.1.0" |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Features | ## Features | ||||||
|  |  | ||||||
| ### Core Cryptographic Operations | - **Secure Storage**: ChaCha20Poly1305 encryption for all data | ||||||
| - **Symmetric Encryption**: ChaCha20Poly1305 AEAD cipher for secure data encryption | - **Password-Based Encryption**: Keyspaces are encrypted using password-derived keys | ||||||
| - **Key Derivation**: PBKDF2-based key derivation from passwords | - **Cross-Platform**: Works on both native and WASM targets | ||||||
| - **Digital Signatures**: ECDSA signing and verification using secp256k1 curves | - **Async API**: Fully asynchronous operations | ||||||
| - **Key Management**: Secure keypair generation and storage | - **Type Safety**: Strong typing with comprehensive error handling | ||||||
|  |  | ||||||
| ### Keyspace Management | ## Architecture | ||||||
| - **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 | Vault | ||||||
| - **Transaction Signing**: Sign Ethereum transactions securely | ├── KeySpace 1 (encrypted with password A) | ||||||
| - **Smart Contract Interaction**: Call read functions on smart contracts | ├── KeySpace 2 (encrypted with password B) | ||||||
| - **Multi-Network Support**: Support for different Ethereum networks | └── KeySpace N (encrypted with password N) | ||||||
|  | ``` | ||||||
|  |  | ||||||
| ### Key-Value Store | Each keyspace is independently encrypted, allowing different access controls and security boundaries. | ||||||
| - **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 | ## Usage | ||||||
|  |  | ||||||
| ### Basic Cryptographic Operations | ### Creating a Vault | ||||||
|  |  | ||||||
| ```rust | ```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 | #[tokio::main] | ||||||
| let key = generate_symmetric_key(); | async fn main() -> Result<(), Error> { | ||||||
|  |     // Create a new vault at the specified path | ||||||
| // Encrypt data |     let vault = Vault::new(Path::new("./my_vault")).await?; | ||||||
| let message = b"Hello, World!"; |      | ||||||
| let encrypted = encrypt_symmetric(&key, message)?; |     // Open an encrypted keyspace | ||||||
|  |     let keyspace = vault.open_keyspace("user_data", "secure_password").await?; | ||||||
| // Decrypt data |      | ||||||
| let decrypted = decrypt_symmetric(&key, &encrypted)?; |     // Use the keyspace for encrypted storage | ||||||
| ``` |     // (KeySpace API documentation coming soon) | ||||||
|  |      | ||||||
| ### Keyspace and Keypair Management |     Ok(()) | ||||||
|  |  | ||||||
| ```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 | ### WASM Support | ||||||
|  |  | ||||||
|  | The vault also supports WASM targets with browser-compatible storage: | ||||||
|  |  | ||||||
| ```rust | ```rust | ||||||
| use sal_vault::ethereum::wallet::EthereumWallet; | #[cfg(target_arch = "wasm32")] | ||||||
| use sal_vault::ethereum::networks::NetworkConfig; | async fn wasm_example() -> Result<(), Error> { | ||||||
|  |     let vault = Vault::new().await?; // No path needed for WASM | ||||||
| // Create wallet from keypair |     let keyspace = vault.open_keyspace("session_data", "password").await?; | ||||||
| let network = NetworkConfig::mainnet(); |     Ok(()) | ||||||
| let wallet = EthereumWallet::from_keypair(&keypair, network)?; | } | ||||||
|  |  | ||||||
| // Get wallet address |  | ||||||
| let address = wallet.address(); |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### Rhai Scripting | ## Security | ||||||
|  |  | ||||||
| ```rhai | ### Encryption | ||||||
| // Create and manage keyspaces |  | ||||||
| create_key_space("personal", "secure_password"); |  | ||||||
| select_keyspace("personal"); |  | ||||||
|  |  | ||||||
| // Create and use keypairs | - **Algorithm**: ChaCha20Poly1305 (AEAD) | ||||||
| create_keypair("signing_key"); | - **Key Derivation**: PBKDF2 with secure parameters | ||||||
| select_keypair("signing_key"); | - **Nonce Generation**: Cryptographically secure random nonces | ||||||
|  | - **Authentication**: Built-in authentication prevents tampering | ||||||
|  |  | ||||||
| // Sign and verify data | ### Best Practices | ||||||
| let message = "Important document"; |  | ||||||
| let signature = sign(message); |  | ||||||
| let is_valid = verify(message, signature); |  | ||||||
|  |  | ||||||
| // Symmetric encryption | 1. **Strong Passwords**: Use strong, unique passwords for each keyspace | ||||||
| let key = generate_key(); | 2. **Secure Storage**: Store vault files in secure locations | ||||||
| let encrypted = encrypt(key, "secret data"); | 3. **Access Control**: Limit filesystem access to vault directories | ||||||
| let decrypted = decrypt(key, encrypted); | 4. **Backup Strategy**: Implement secure backup procedures | ||||||
| ``` | 5. **Key Rotation**: Periodically change keyspace passwords | ||||||
|  |  | ||||||
| ## 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 | ## Error Handling | ||||||
|  |  | ||||||
| The library provides comprehensive error handling through the `CryptoError` enum: | The vault uses a comprehensive error system: | ||||||
|  |  | ||||||
| ```rust | ```rust | ||||||
| use sal_vault::error::CryptoError; | use sal_vault::Error; | ||||||
|  |  | ||||||
| match some_crypto_operation() { | match vault.open_keyspace("test", "password").await { | ||||||
|     Ok(result) => println!("Success: {:?}", result), |     Ok(keyspace) => { | ||||||
|     Err(CryptoError::InvalidKeyLength) => println!("Invalid key length provided"), |         // Success - use the keyspace | ||||||
|     Err(CryptoError::EncryptionFailed(msg)) => println!("Encryption failed: {}", msg), |     } | ||||||
|     Err(CryptoError::KeypairNotFound(name)) => println!("Keypair '{}' not found", name), |     Err(Error::IOError(io_err)) => { | ||||||
|     Err(e) => println!("Other error: {}", e), |         // 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 | ### What's New | ||||||
| # Run all tests | - ✅ Simpler, more focused API | ||||||
| cargo test | - ✅ Better cross-platform support | ||||||
|  | - ✅ Improved security model | ||||||
|  | - ✅ Cleaner error handling | ||||||
|  |  | ||||||
| # Run specific test categories | ### What's Changed | ||||||
| cargo test crypto_tests | - ❌ No Ethereum wallet functionality | ||||||
| cargo test rhai_integration_tests | - ❌ 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 | ## Development Status | ||||||
| - `k256`: Elliptic curve cryptography |  | ||||||
| - `ethers`: Ethereum functionality | - ✅ **Core Vault**: Complete and functional | ||||||
| - `serde`: Serialization support | - ✅ **KeySpace Operations**: Basic implementation ready | ||||||
| - `rhai`: Scripting integration | - ✅ **Encryption**: Secure ChaCha20Poly1305 implementation | ||||||
| - `tokio`: Async runtime support | - ⏳ **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 | ## 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 | #[derive(Debug)] | ||||||
|  | /// Errors encountered while using the vault | ||||||
| use thiserror::Error; | pub enum Error { | ||||||
|  |     /// An error during cryptographic operations | ||||||
| /// Errors that can occur during cryptographic operations |     Crypto(CryptoError), | ||||||
| #[derive(Error, Debug)] |     /// An error while performing an I/O operation | ||||||
| pub enum CryptoError { |     IOError(std::io::Error), | ||||||
|     /// Invalid key length |     /// A corrupt keyspace is returned if a keyspace can't be decrypted | ||||||
|     #[error("Invalid key length")] |     CorruptKeyspace, | ||||||
|     InvalidKeyLength, |     /// An error in the used key value store (temporarily disabled) | ||||||
|  |     // KV(kv::error::KVError), | ||||||
|     /// Encryption failed |     /// An error while encoding/decoding the keyspace. | ||||||
|     #[error("Encryption failed: {0}")] |     Coding, | ||||||
|     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 | 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 error; | ||||||
| pub mod ethereum; | pub mod key; | ||||||
| pub mod keyspace; | pub mod keyspace; | ||||||
| pub mod kvs; |  | ||||||
| pub mod symmetric; |  | ||||||
|  |  | ||||||
| // Rhai integration module | #[cfg(not(target_arch = "wasm32"))] | ||||||
| pub mod rhai; | use std::path::{Path, PathBuf}; | ||||||
|  |  | ||||||
| // Re-export modules | use crate::{error::Error, key::symmetric::SymmetricKey, keyspace::KeySpace}; | ||||||
| // Re-export common types for convenience |  | ||||||
| pub use error::CryptoError; | /// Vault is a 2 tiered key-value store. That is, it is a collection of [`spaces`](KeySpace), where | ||||||
| pub use keyspace::{KeyPair, KeySpace}; | /// 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