feat: introduce hero_vault

This commit is contained in:
Sameh Abouelsaad 2025-05-08 17:39:39 +03:00
parent 2cd9faf4fa
commit 98ab2e1536
20 changed files with 2442 additions and 0 deletions

View File

@ -28,6 +28,15 @@ rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
rand = "0.8.5" # Random number generation rand = "0.8.5" # Random number generation
clap = "2.33" # Command-line argument parsing clap = "2.33" # Command-line argument parsing
# Crypto dependencies
base64 = "0.21.0" # Base64 encoding/decoding
k256 = { version = "0.13.1", features = ["ecdsa"] } # Elliptic curve cryptography
once_cell = "1.18.0" # Lazy static initialization
sha2 = "0.10.7" # SHA-2 hash functions
chacha20poly1305 = "0.10.1" # ChaCha20Poly1305 AEAD cipher
ethers = { version = "2.0.7", features = ["legacy"] } # Ethereum library
dirs = "5.0.1" # Directory paths
# Optional features for specific OS functionality # Optional features for specific OS functionality
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = "0.26" # Unix-specific functionality nix = "0.26" # Unix-specific functionality

View File

@ -0,0 +1,49 @@
# Hero Vault Cryptography Examples
This directory contains examples demonstrating the Hero Vault cryptography functionality integrated into the SAL project.
## Overview
Hero Vault provides cryptographic operations including:
- Key space management (creation, loading, encryption, decryption)
- Keypair management (creation, selection, listing)
- Digital signatures (signing and verification)
- Symmetric encryption (key generation, encryption, decryption)
- Ethereum wallet functionality
## Directory Structure
- `rhai/` - Rhai script examples demonstrating Hero Vault functionality
- `example.rhai` - Basic example demonstrating key management, signing, and encryption
- `advanced_example.rhai` - Advanced example with error handling and more complex operations
- `key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk
- `load_existing_space.rhai` - Shows how to load a previously created key space and use its keypairs
- `README.md` - Documentation for the Rhai scripting API
## Running the Examples
You can run the examples using the `herodo` tool that comes with the SAL project:
```bash
# Run a single example
herodo --path rhai/example.rhai
# Run all examples using the provided script
./run_examples.sh
```
## Key Space Storage
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`).
## 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.
## Best Practices
1. **Use Strong Passwords**: Since the security of your key spaces depends on the strength of your passwords, use strong, unique passwords.
2. **Backup Key Spaces**: Regularly backup your key spaces directory to prevent data loss.
3. **Script Organization**: Split your scripts into logical units, with separate scripts for key creation and key usage.
4. **Error Handling**: Always check the return values of functions to ensure operations succeeded before proceeding.

View File

@ -0,0 +1,233 @@
// Advanced Rhai script example for Hero Vault Cryptography Module
// This script demonstrates conditional logic, error handling, and more complex operations
// Function to create a key space with error handling
fn setup_key_space(name, password) {
print("Attempting: Create key space: " + name);
let result = create_key_space(name, password);
if result {
print("✅ Create key space succeeded!");
return true;
} else {
print("❌ Create key space failed!");
}
return false;
}
// Function to create and select a keypair
fn setup_keypair(name, password) {
print("Attempting: Create keypair: " + name);
let result = create_keypair(name, password);
if result {
print("✅ Create keypair succeeded!");
print("Attempting: Select keypair: " + name);
let selected = select_keypair(name);
if selected {
print("✅ Select keypair succeeded!");
return true;
} else {
print("❌ Select keypair failed!");
}
} else {
print("❌ Create keypair failed!");
}
return false;
}
// Function to sign multiple messages
fn sign_messages(messages) {
let signatures = [];
for message in messages {
print("Signing message: " + message);
print("Attempting: Sign message");
let signature = sign(message);
if signature != "" {
print("✅ Sign message succeeded!");
signatures.push(#{
message: message,
signature: signature
});
} else {
print("❌ Sign message failed!");
}
}
return signatures;
}
// Function to verify signatures
fn verify_signatures(signed_messages) {
let results = [];
for item in signed_messages {
let message = item.message;
let signature = item.signature;
print("Verifying signature for: " + message);
print("Attempting: Verify signature");
let is_valid = verify(message, signature);
if is_valid {
print("✅ Verify signature succeeded!");
} else {
print("❌ Verify signature failed!");
}
results.push(#{
message: message,
valid: is_valid
});
}
return results;
}
// Function to encrypt multiple messages
fn encrypt_messages(messages) {
// Generate a symmetric key
print("Attempting: Generate symmetric key");
let key = generate_key();
if key == "" {
print("❌ Generate symmetric key failed!");
return [];
}
print("✅ Generate symmetric key succeeded!");
print("Using key: " + key);
let encrypted_messages = [];
for message in messages {
print("Encrypting message: " + message);
print("Attempting: Encrypt message");
let encrypted = encrypt(key, message);
if encrypted != "" {
print("✅ Encrypt message succeeded!");
encrypted_messages.push(#{
original: message,
encrypted: encrypted,
key: key
});
} else {
print("❌ Encrypt message failed!");
}
}
return encrypted_messages;
}
// Function to decrypt messages
fn decrypt_messages(encrypted_messages) {
let decrypted_messages = [];
for item in encrypted_messages {
let encrypted = item.encrypted;
let key = item.key;
let original = item.original;
print("Decrypting message...");
print("Attempting: Decrypt message");
let decrypted = decrypt(key, encrypted);
if decrypted != false {
let success = decrypted == original;
decrypted_messages.push(#{
decrypted: decrypted,
original: original,
success: success
});
if success {
print("Decryption matched original ✅");
} else {
print("Decryption did not match original ❌");
}
}
}
return decrypted_messages;
}
// Main script execution
print("=== Advanced Cryptography Script ===");
// Set up key space
let space_name = "advanced_space";
let password = "secure_password123";
if setup_key_space(space_name, password) {
print("\n--- Key space setup complete ---\n");
// Set up keypair
if setup_keypair("advanced_keypair", password) {
print("\n--- Keypair setup complete ---\n");
// Define messages to sign
let messages = [
"This is the first message to sign",
"Here's another message that needs signing",
"And a third message for good measure"
];
// Sign messages
print("\n--- Signing Messages ---\n");
let signed_messages = sign_messages(messages);
// Verify signatures
print("\n--- Verifying Signatures ---\n");
let verification_results = verify_signatures(signed_messages);
// Count successful verifications
let successful_verifications = verification_results.filter(|r| r.valid).len();
print("Successfully verified " + successful_verifications + " out of " + verification_results.len() + " signatures");
// Encrypt messages
print("\n--- Encrypting Messages ---\n");
let encrypted_messages = encrypt_messages(messages);
// Decrypt messages
print("\n--- Decrypting Messages ---\n");
let decryption_results = decrypt_messages(encrypted_messages);
// Count successful decryptions
let successful_decryptions = decryption_results.filter(|r| r.success).len();
print("Successfully decrypted " + successful_decryptions + " out of " + decryption_results.len() + " messages");
// Create Ethereum wallet
print("\n--- Creating Ethereum Wallet ---\n");
print("Attempting: Create Ethereum wallet");
let wallet_created = create_ethereum_wallet();
if wallet_created {
print("✅ Create Ethereum wallet succeeded!");
print("Attempting: Get Ethereum address");
let address = get_ethereum_address();
if address != "" {
print("✅ Get Ethereum address succeeded!");
print("Ethereum wallet address: " + address);
} else {
print("❌ Get Ethereum address failed!");
}
} else {
print("❌ Create Ethereum wallet failed!");
}
print("\n=== Script execution completed successfully! ===");
} else {
print("Failed to set up keypair. Aborting script.");
}
} else {
print("Failed to set up key space. Aborting script.");
}

View File

@ -0,0 +1,85 @@
// Example Rhai script for Hero Vault Cryptography Module
// This script demonstrates key management, signing, and encryption
// Step 1: Create and manage a key space
let space_name = "demo_space";
let password = "secure_password123";
print("Creating key space: " + space_name);
if create_key_space(space_name, password) {
print("✓ Key space created successfully");
// Step 2: Create and use keypairs
print("\nCreating keypairs...");
if create_keypair("signing_key", password) {
print("✓ Created signing keypair");
}
if create_keypair("encryption_key", password) {
print("✓ Created encryption keypair");
}
// List all keypairs
let keypairs = list_keypairs();
print("Available keypairs: " + keypairs);
// Step 3: Sign a message
print("\nPerforming signing operations...");
if select_keypair("signing_key") {
print("✓ Selected signing keypair");
let message = "This is a secure message that needs to be signed";
print("Message: " + message);
let signature = sign(message);
print("Signature: " + signature);
// Verify the signature
let is_valid = verify(message, signature);
if is_valid {
print("Signature verification: ✓ Valid");
} else {
print("Signature verification: ✗ Invalid");
}
}
// Step 4: Encrypt and decrypt data
print("\nPerforming encryption operations...");
// Generate a symmetric key
let sym_key = generate_key();
print("Generated symmetric key: " + sym_key);
// Encrypt a message
let secret = "This is a top secret message that must be encrypted";
print("Original message: " + secret);
let encrypted_data = encrypt(sym_key, secret);
print("Encrypted data: " + encrypted_data);
// Decrypt the message
let decrypted_data = decrypt(sym_key, encrypted_data);
print("Decrypted message: " + decrypted_data);
// Verify decryption was successful
if decrypted_data == secret {
print("✓ Encryption/decryption successful");
} else {
print("✗ Encryption/decryption failed");
}
// Step 5: Create an Ethereum wallet
print("\nCreating Ethereum wallet...");
if select_keypair("encryption_key") {
print("✓ Selected keypair for Ethereum wallet");
if create_ethereum_wallet() {
print("✓ Ethereum wallet created");
let address = get_ethereum_address();
print("Ethereum address: " + address);
}
}
print("\nScript execution completed successfully!");
}

View File

@ -0,0 +1,65 @@
// Example Rhai script demonstrating key space persistence for Hero Vault
// This script shows how to create, save, and load key spaces
// Step 1: Create a key space
let space_name = "persistent_space";
let password = "secure_password123";
print("Creating key space: " + space_name);
if create_key_space(space_name, password) {
print("✓ Key space created successfully");
// Step 2: Create keypairs in this space
print("\nCreating keypairs...");
if create_keypair("persistent_key1", password) {
print("✓ Created first keypair");
}
if create_keypair("persistent_key2", password) {
print("✓ Created second keypair");
}
// List all keypairs
let keypairs = list_keypairs();
print("Available keypairs: " + keypairs);
// Step 3: Clear the session (simulate closing and reopening the CLI)
print("\nClearing session (simulating restart)...");
// Note: In a real script, you would exit here and run a new script
// For demonstration purposes, we'll continue in the same script
// Step 4: Load the key space from disk
print("\nLoading key space from disk...");
if load_key_space(space_name, password) {
print("✓ Key space loaded successfully");
// Verify the keypairs are still available
let loaded_keypairs = list_keypairs();
print("Keypairs after loading: " + loaded_keypairs);
// Step 5: Use a keypair from the loaded space
print("\nSelecting and using a keypair...");
if select_keypair("persistent_key1") {
print("✓ Selected keypair");
let message = "This message was signed using a keypair from a loaded key space";
let signature = sign(message);
print("Message: " + message);
print("Signature: " + signature);
// Verify the signature
let is_valid = verify(message, signature);
if is_valid {
print("Signature verification: ✓ Valid");
} else {
print("Signature verification: ✗ Invalid");
}
}
} else {
print("✗ Failed to load key space");
}
} else {
print("✗ Failed to create key space");
}
print("\nScript execution completed!");

View File

@ -0,0 +1,65 @@
// Example Rhai script demonstrating loading an existing key space for Hero Vault
// This script shows how to load a previously created key space and use its keypairs
// Define the key space name and password
let space_name = "persistent_space";
let password = "secure_password123";
print("Loading existing key space: " + space_name);
// Load the key space from disk
if load_key_space(space_name, password) {
print("✓ Key space loaded successfully");
// List available keypairs
let keypairs = list_keypairs();
print("Available keypairs: " + keypairs);
// Use both keypairs to sign different messages
if select_keypair("persistent_key1") {
print("\nUsing persistent_key1:");
let message1 = "Message signed with the first keypair";
let signature1 = sign(message1);
print("Message: " + message1);
print("Signature: " + signature1);
let is_valid1 = verify(message1, signature1);
if is_valid1 {
print("Verification: ✓ Valid");
} else {
print("Verification: ✗ Invalid");
}
}
if select_keypair("persistent_key2") {
print("\nUsing persistent_key2:");
let message2 = "Message signed with the second keypair";
let signature2 = sign(message2);
print("Message: " + message2);
print("Signature: " + signature2);
let is_valid2 = verify(message2, signature2);
if is_valid2 {
print("Verification: ✓ Valid");
} else {
print("Verification: ✗ Invalid");
}
}
// Create an Ethereum wallet using one of the keypairs
print("\nCreating Ethereum wallet from persistent keypair:");
if select_keypair("persistent_key1") {
if create_ethereum_wallet() {
print("✓ Ethereum wallet created");
let address = get_ethereum_address();
print("Ethereum address: " + address);
} else {
print("✗ Failed to create Ethereum wallet");
}
}
} else {
print("✗ Failed to load key space. Make sure you've run key_persistence_example.rhai first.");
}
print("\nScript execution completed!");

50
src/crypto/error.rs Normal file
View File

@ -0,0 +1,50 @@
//! 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),
}
/// Convert CryptoError to SAL's Error type
impl From<CryptoError> for crate::Error {
fn from(err: CryptoError) -> Self {
crate::Error::Sal(err.to_string())
}
}

View File

@ -0,0 +1,228 @@
//! Implementation of Ethereum functionality.
use ethers::prelude::*;
use ethers::signers::{LocalWallet, Signer, Wallet};
use ethers::utils::hex;
use k256::ecdsa::SigningKey;
use std::str::FromStr;
use std::sync::Mutex;
use once_cell::sync::Lazy;
use sha2::{Sha256, Digest};
use crate::crypto::error::CryptoError;
use crate::crypto::keypair::KeyPair;
// Gnosis Chain configuration
pub const GNOSIS_CHAIN_ID: u64 = 100;
pub const GNOSIS_RPC_URL: &str = "https://rpc.gnosis.gateway.fm";
pub const GNOSIS_EXPLORER: &str = "https://gnosisscan.io";
/// An Ethereum wallet derived from a keypair.
#[derive(Debug, Clone)]
pub struct EthereumWallet {
pub address: Address,
pub wallet: Wallet<SigningKey>,
}
impl EthereumWallet {
/// Creates a new Ethereum wallet from a keypair.
pub fn from_keypair(keypair: &KeyPair) -> Result<Self, CryptoError> {
// Get the private key bytes from the keypair
let private_key_bytes = keypair.signing_key.to_bytes();
// Convert to a hex string (without 0x prefix)
let private_key_hex = hex::encode(private_key_bytes);
// Create an Ethereum wallet from the private key
let wallet = LocalWallet::from_str(&private_key_hex)
.map_err(|e| CryptoError::InvalidKeyLength)?
.with_chain_id(GNOSIS_CHAIN_ID);
// Get the Ethereum address
let address = wallet.address();
Ok(EthereumWallet {
address,
wallet,
})
}
/// Creates a new Ethereum wallet from a name and keypair (deterministic derivation).
pub fn from_name_and_keypair(name: &str, keypair: &KeyPair) -> Result<Self, CryptoError> {
// Get the private key bytes from the keypair
let private_key_bytes = keypair.signing_key.to_bytes();
// Create a deterministic seed by combining name and private key
let mut hasher = Sha256::default();
hasher.update(name.as_bytes());
hasher.update(&private_key_bytes);
let seed = hasher.finalize();
// Use the seed as a private key
let private_key_hex = hex::encode(seed);
// Create an Ethereum wallet from the derived private key
let wallet = LocalWallet::from_str(&private_key_hex)
.map_err(|e| CryptoError::InvalidKeyLength)?
.with_chain_id(GNOSIS_CHAIN_ID);
// Get the Ethereum address
let address = wallet.address();
Ok(EthereumWallet {
address,
wallet,
})
}
/// Creates a new Ethereum wallet from a private key.
pub fn from_private_key(private_key: &str) -> Result<Self, CryptoError> {
// Remove 0x prefix if present
let private_key_clean = private_key.trim_start_matches("0x");
// Create an Ethereum wallet from the private key
let wallet = LocalWallet::from_str(private_key_clean)
.map_err(|e| CryptoError::InvalidKeyLength)?
.with_chain_id(GNOSIS_CHAIN_ID);
// Get the Ethereum address
let address = wallet.address();
Ok(EthereumWallet {
address,
wallet,
})
}
/// Gets the Ethereum address as a string.
pub fn address_string(&self) -> String {
format!("{:?}", self.address)
}
/// Signs a message with the Ethereum wallet.
pub async fn sign_message(&self, message: &[u8]) -> Result<String, CryptoError> {
let signature = self.wallet.sign_message(message)
.await
.map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;
Ok(signature.to_string())
}
/// Gets the private key as a hex string.
pub fn private_key_hex(&self) -> String {
let bytes = self.wallet.signer().to_bytes();
hex::encode(bytes)
}
}
/// Global storage for Ethereum wallets.
static ETH_WALLETS: Lazy<Mutex<Vec<EthereumWallet>>> = Lazy::new(|| {
Mutex::new(Vec::new())
});
/// Creates an Ethereum wallet from the currently selected keypair.
pub fn create_ethereum_wallet() -> Result<EthereumWallet, CryptoError> {
// Get the currently selected keypair
let keypair = crate::crypto::keypair::get_selected_keypair()?;
// Create an Ethereum wallet from the keypair
let wallet = EthereumWallet::from_keypair(&keypair)?;
// Store the wallet
let mut wallets = ETH_WALLETS.lock().unwrap();
wallets.push(wallet.clone());
Ok(wallet)
}
/// Gets the current Ethereum wallet.
pub fn get_current_ethereum_wallet() -> Result<EthereumWallet, CryptoError> {
let wallets = ETH_WALLETS.lock().unwrap();
if wallets.is_empty() {
return Err(CryptoError::NoKeypairSelected);
}
Ok(wallets.last().unwrap().clone())
}
/// Clears all Ethereum wallets.
pub fn clear_ethereum_wallets() {
let mut wallets = ETH_WALLETS.lock().unwrap();
wallets.clear();
}
/// Formats an Ethereum balance for display.
pub fn format_eth_balance(balance: U256) -> String {
let wei = balance.as_u128();
let eth = wei as f64 / 1_000_000_000_000_000_000.0;
format!("{:.6} ETH", eth)
}
/// Gets the balance of an Ethereum address.
pub async fn get_balance(provider: &Provider<Http>, address: Address) -> Result<U256, CryptoError> {
provider.get_balance(address, None)
.await
.map_err(|e| CryptoError::SerializationError(format!("Failed to get balance: {}", e)))
}
/// Sends Ethereum from one address to another.
pub async fn send_eth(
wallet: &EthereumWallet,
provider: &Provider<Http>,
to: Address,
amount: U256,
) -> Result<H256, CryptoError> {
// Create a client with the wallet
let client = SignerMiddleware::new(
provider.clone(),
wallet.wallet.clone(),
);
// Create the transaction
let tx = TransactionRequest::new()
.to(to)
.value(amount)
.gas(21000);
// Send the transaction
let pending_tx = client.send_transaction(tx, None)
.await
.map_err(|e| CryptoError::SerializationError(format!("Failed to send transaction: {}", e)))?;
// Return the transaction hash instead of waiting for the receipt
Ok(pending_tx.tx_hash())
}
/// Creates a provider for the Gnosis Chain.
pub fn create_gnosis_provider() -> Result<Provider<Http>, CryptoError> {
Provider::<Http>::try_from(GNOSIS_RPC_URL)
.map_err(|e| CryptoError::SerializationError(format!("Failed to create Gnosis provider: {}", e)))
}
/// Creates an Ethereum wallet from a name and the currently selected keypair.
pub fn create_ethereum_wallet_from_name(name: &str) -> Result<EthereumWallet, CryptoError> {
// Get the currently selected keypair
let keypair = crate::crypto::keypair::get_selected_keypair()?;
// Create an Ethereum wallet from the name and keypair
let wallet = EthereumWallet::from_name_and_keypair(name, &keypair)?;
// Store the wallet
let mut wallets = ETH_WALLETS.lock().unwrap();
wallets.push(wallet.clone());
Ok(wallet)
}
/// Creates an Ethereum wallet from a private key.
pub fn create_ethereum_wallet_from_private_key(private_key: &str) -> Result<EthereumWallet, CryptoError> {
// Create an Ethereum wallet from the private key
let wallet = EthereumWallet::from_private_key(private_key)?;
// Store the wallet
let mut wallets = ETH_WALLETS.lock().unwrap();
wallets.push(wallet.clone());
Ok(wallet)
}

View File

@ -0,0 +1,12 @@
//! Ethereum wallet functionality
//!
//! This module provides functionality for creating and managing Ethereum wallets.
mod implementation;
// Re-export public types and functions
pub use implementation::{
EthereumWallet,
create_ethereum_wallet,
get_current_ethereum_wallet
};

View File

@ -0,0 +1,467 @@
//! Implementation of keypair functionality.
use k256::ecdsa::{SigningKey, VerifyingKey, signature::{Signer, Verifier}, Signature};
use rand::rngs::OsRng;
use serde::{Serialize, Deserialize};
use std::collections::HashMap;
use once_cell::sync::Lazy;
use std::sync::Mutex;
use sha2::{Sha256, Digest};
use crate::crypto::error::CryptoError;
/// A keypair for signing and verifying messages.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyPair {
pub name: String,
#[serde(with = "verifying_key_serde")]
pub verifying_key: VerifyingKey,
#[serde(with = "signing_key_serde")]
pub signing_key: SigningKey,
}
// Serialization helpers for VerifyingKey
mod verifying_key_serde {
use super::*;
use serde::{Serializer, Deserializer};
use serde::de::{self, Visitor};
use std::fmt;
pub fn serialize<S>(key: &VerifyingKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = key.to_sec1_bytes();
// Convert bytes to a Vec<u8> and serialize that instead
serializer.collect_seq(bytes)
}
struct VerifyingKeyVisitor;
impl<'de> Visitor<'de> for VerifyingKeyVisitor {
type Value = VerifyingKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array representing a verifying key")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
VerifyingKey::from_sec1_bytes(v).map_err(|e| {
log::error!("Error deserializing verifying key: {:?}", e);
E::custom(format!("invalid verifying key: {:?}", e))
})
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
// Collect all bytes from the sequence
let mut bytes = Vec::new();
while let Some(byte) = seq.next_element()? {
bytes.push(byte);
}
VerifyingKey::from_sec1_bytes(&bytes).map_err(|e| {
log::error!("Error deserializing verifying key from seq: {:?}", e);
de::Error::custom(format!("invalid verifying key from seq: {:?}", e))
})
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<VerifyingKey, D::Error>
where
D: Deserializer<'de>,
{
// Try to deserialize as bytes first, then as a sequence
deserializer.deserialize_any(VerifyingKeyVisitor)
}
}
// Serialization helpers for SigningKey
mod signing_key_serde {
use super::*;
use serde::{Serializer, Deserializer};
use serde::de::{self, Visitor};
use std::fmt;
pub fn serialize<S>(key: &SigningKey, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let bytes = key.to_bytes();
// Convert bytes to a Vec<u8> and serialize that instead
serializer.collect_seq(bytes)
}
struct SigningKeyVisitor;
impl<'de> Visitor<'de> for SigningKeyVisitor {
type Value = SigningKey;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a byte array representing a signing key")
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: de::Error,
{
SigningKey::from_bytes(v.into()).map_err(|e| {
log::error!("Error deserializing signing key: {:?}", e);
E::custom(format!("invalid signing key: {:?}", e))
})
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: de::SeqAccess<'de>,
{
// Collect all bytes from the sequence
let mut bytes = Vec::new();
while let Some(byte) = seq.next_element()? {
bytes.push(byte);
}
SigningKey::from_bytes(bytes.as_slice().into()).map_err(|e| {
log::error!("Error deserializing signing key from seq: {:?}", e);
de::Error::custom(format!("invalid signing key from seq: {:?}", e))
})
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<SigningKey, D::Error>
where
D: Deserializer<'de>,
{
// Try to deserialize as bytes first, then as a sequence
deserializer.deserialize_any(SigningKeyVisitor)
}
}
impl KeyPair {
/// Creates a new keypair with the given name.
pub fn new(name: &str) -> Self {
let signing_key = SigningKey::random(&mut OsRng);
let verifying_key = VerifyingKey::from(&signing_key);
KeyPair {
name: name.to_string(),
verifying_key,
signing_key,
}
}
/// Gets the public key bytes.
pub fn pub_key(&self) -> Vec<u8> {
self.verifying_key.to_sec1_bytes().to_vec()
}
/// Derives a public key from a private key.
pub fn pub_key_from_private(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
let signing_key = SigningKey::from_bytes(private_key.into())
.map_err(|_| CryptoError::InvalidKeyLength)?;
let verifying_key = VerifyingKey::from(&signing_key);
Ok(verifying_key.to_sec1_bytes().to_vec())
}
/// Signs a message.
pub fn sign(&self, message: &[u8]) -> Vec<u8> {
let signature: Signature = self.signing_key.sign(message);
signature.to_bytes().to_vec()
}
/// Verifies a message signature.
pub fn verify(&self, message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
let signature = Signature::from_bytes(signature_bytes.into())
.map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;
match self.verifying_key.verify(message, &signature) {
Ok(_) => Ok(true),
Err(_) => Ok(false), // Verification failed, but operation was successful
}
}
/// Verifies a message signature using only a public key.
pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
let verifying_key = VerifyingKey::from_sec1_bytes(public_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
let signature = Signature::from_bytes(signature_bytes.into())
.map_err(|e| CryptoError::SignatureFormatError(e.to_string()))?;
match verifying_key.verify(message, &signature) {
Ok(_) => Ok(true),
Err(_) => Ok(false), // Verification failed, but operation was successful
}
}
/// Encrypts a message using the recipient's public key.
/// This implements ECIES (Elliptic Curve Integrated Encryption Scheme):
/// 1. Generate an ephemeral keypair
/// 2. Derive a shared secret using ECDH
/// 3. Derive encryption key from the shared secret
/// 4. Encrypt the message using symmetric encryption
/// 5. Return the ephemeral public key and the ciphertext
pub fn encrypt_asymmetric(&self, recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Parse recipient's public key
let recipient_key = VerifyingKey::from_sec1_bytes(recipient_public_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
// Generate ephemeral keypair
let ephemeral_signing_key = SigningKey::random(&mut OsRng);
let ephemeral_public_key = VerifyingKey::from(&ephemeral_signing_key);
// Derive shared secret (this is a simplified ECDH)
// In a real implementation, we would use proper ECDH, but for this example:
let shared_point = recipient_key.to_encoded_point(false);
let shared_secret = {
let mut hasher = Sha256::default();
hasher.update(ephemeral_signing_key.to_bytes());
hasher.update(shared_point.as_bytes());
hasher.finalize().to_vec()
};
// Encrypt the message using the derived key
let ciphertext = crate::crypto::symmetric::encrypt_with_key(&shared_secret, message)
.map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
// Format: ephemeral_public_key || ciphertext
let mut result = ephemeral_public_key.to_sec1_bytes().to_vec();
result.extend_from_slice(&ciphertext);
Ok(result)
}
/// Decrypts a message using the recipient's private key.
/// This is the counterpart to encrypt_asymmetric.
pub fn decrypt_asymmetric(&self, ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
// The first 33 or 65 bytes (depending on compression) are the ephemeral public key
// For simplicity, we'll assume uncompressed keys (65 bytes)
if ciphertext.len() <= 65 {
return Err(CryptoError::DecryptionFailed("Ciphertext too short".to_string()));
}
// Extract ephemeral public key and actual ciphertext
let ephemeral_public_key = &ciphertext[..65];
let actual_ciphertext = &ciphertext[65..];
// Parse ephemeral public key
let sender_key = VerifyingKey::from_sec1_bytes(ephemeral_public_key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
// Derive shared secret (simplified ECDH)
let shared_point = sender_key.to_encoded_point(false);
let shared_secret = {
let mut hasher = Sha256::default();
hasher.update(self.signing_key.to_bytes());
hasher.update(shared_point.as_bytes());
hasher.finalize().to_vec()
};
// Decrypt the message using the derived key
crate::crypto::symmetric::decrypt_with_key(&shared_secret, actual_ciphertext)
.map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
}
}
/// A collection of keypairs.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct KeySpace {
pub name: String,
pub keypairs: HashMap<String, KeyPair>,
}
impl KeySpace {
/// Creates a new key space with the given name.
pub fn new(name: &str) -> Self {
KeySpace {
name: name.to_string(),
keypairs: HashMap::new(),
}
}
/// Adds a new keypair to the space.
pub fn add_keypair(&mut self, name: &str) -> Result<(), CryptoError> {
if self.keypairs.contains_key(name) {
return Err(CryptoError::KeypairAlreadyExists(name.to_string()));
}
let keypair = KeyPair::new(name);
self.keypairs.insert(name.to_string(), keypair);
Ok(())
}
/// Gets a keypair by name.
pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError> {
self.keypairs.get(name).ok_or(CryptoError::KeypairNotFound(name.to_string()))
}
/// Lists all keypair names in the space.
pub fn list_keypairs(&self) -> Vec<String> {
self.keypairs.keys().cloned().collect()
}
}
/// Session state for the current key space and selected keypair.
pub struct Session {
pub current_space: Option<KeySpace>,
pub selected_keypair: Option<String>,
}
impl Default for Session {
fn default() -> Self {
Session {
current_space: None,
selected_keypair: None,
}
}
}
/// Global session state.
static SESSION: Lazy<Mutex<Session>> = Lazy::new(|| {
Mutex::new(Session::default())
});
/// Creates a new key space with the given name.
pub fn create_space(name: &str) -> Result<(), CryptoError> {
let mut session = SESSION.lock().unwrap();
// Create a new space
let space = KeySpace::new(name);
// Set as current space
session.current_space = Some(space);
session.selected_keypair = None;
Ok(())
}
/// Sets the current key space.
pub fn set_current_space(space: KeySpace) -> Result<(), CryptoError> {
let mut session = SESSION.lock().unwrap();
session.current_space = Some(space);
session.selected_keypair = None;
Ok(())
}
/// Gets the current key space.
pub fn get_current_space() -> Result<KeySpace, CryptoError> {
let session = SESSION.lock().unwrap();
session.current_space.clone().ok_or(CryptoError::NoActiveSpace)
}
/// Clears the current session (logout).
pub fn clear_session() {
let mut session = SESSION.lock().unwrap();
session.current_space = None;
session.selected_keypair = None;
}
/// Creates a new keypair in the current space.
pub fn create_keypair(name: &str) -> Result<(), CryptoError> {
let mut session = SESSION.lock().unwrap();
if let Some(ref mut space) = session.current_space {
if space.keypairs.contains_key(name) {
return Err(CryptoError::KeypairAlreadyExists(name.to_string()));
}
let keypair = KeyPair::new(name);
space.keypairs.insert(name.to_string(), keypair);
// Automatically select the new keypair
session.selected_keypair = Some(name.to_string());
Ok(())
} else {
Err(CryptoError::NoActiveSpace)
}
}
/// Selects a keypair for use.
pub fn select_keypair(name: &str) -> Result<(), CryptoError> {
let mut session = SESSION.lock().unwrap();
if let Some(ref space) = session.current_space {
if !space.keypairs.contains_key(name) {
return Err(CryptoError::KeypairNotFound(name.to_string()));
}
session.selected_keypair = Some(name.to_string());
Ok(())
} else {
Err(CryptoError::NoActiveSpace)
}
}
/// Gets the currently selected keypair.
pub fn get_selected_keypair() -> Result<KeyPair, CryptoError> {
let session = SESSION.lock().unwrap();
if let Some(ref space) = session.current_space {
if let Some(ref keypair_name) = session.selected_keypair {
if let Some(keypair) = space.keypairs.get(keypair_name) {
return Ok(keypair.clone());
}
return Err(CryptoError::KeypairNotFound(keypair_name.clone()));
}
return Err(CryptoError::NoKeypairSelected);
}
Err(CryptoError::NoActiveSpace)
}
/// Lists all keypair names in the current space.
pub fn list_keypairs() -> Result<Vec<String>, CryptoError> {
let session = SESSION.lock().unwrap();
if let Some(ref space) = session.current_space {
Ok(space.keypairs.keys().cloned().collect())
} else {
Err(CryptoError::NoActiveSpace)
}
}
/// Gets the public key of the selected keypair.
pub fn keypair_pub_key() -> Result<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
Ok(keypair.pub_key())
}
/// Derives a public key from a private key.
pub fn derive_public_key(private_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
KeyPair::pub_key_from_private(private_key)
}
/// Signs a message with the selected keypair.
pub fn keypair_sign(message: &[u8]) -> Result<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
Ok(keypair.sign(message))
}
/// Verifies a message signature with the selected keypair.
pub fn keypair_verify(message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
let keypair = get_selected_keypair()?;
keypair.verify(message, signature_bytes)
}
/// Verifies a message signature with a public key.
pub fn verify_with_public_key(public_key: &[u8], message: &[u8], signature_bytes: &[u8]) -> Result<bool, CryptoError> {
KeyPair::verify_with_public_key(public_key, message, signature_bytes)
}
/// Encrypts a message for a recipient using their public key.
pub fn encrypt_asymmetric(recipient_public_key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
keypair.encrypt_asymmetric(recipient_public_key, message)
}
/// Decrypts a message that was encrypted with the current keypair's public key.
pub fn decrypt_asymmetric(ciphertext: &[u8]) -> Result<Vec<u8>, CryptoError> {
let keypair = get_selected_keypair()?;
keypair.decrypt_asymmetric(ciphertext)
}

14
src/crypto/keypair/mod.rs Normal file
View File

@ -0,0 +1,14 @@
//! Key pair management functionality
//!
//! This module provides functionality for creating and managing ECDSA key pairs.
mod implementation;
// Re-export public types and functions
pub use implementation::{
KeyPair, KeySpace,
create_space, set_current_space, get_current_space, clear_session,
create_keypair, select_keypair, get_selected_keypair, list_keypairs,
keypair_pub_key, derive_public_key, keypair_sign, keypair_verify,
verify_with_public_key, encrypt_asymmetric, decrypt_asymmetric
};

66
src/crypto/kvs/error.rs Normal file
View File

@ -0,0 +1,66 @@
//! Error types for the key-value store.
use std::fmt;
use thiserror::Error;
/// Errors that can occur when using the key-value store.
#[derive(Debug, Error)]
pub enum KvsError {
/// I/O error
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
/// Key not found
#[error("Key not found: {0}")]
KeyNotFound(String),
/// Store not found
#[error("Store not found: {0}")]
StoreNotFound(String),
/// Serialization error
#[error("Serialization error: {0}")]
Serialization(String),
/// Deserialization error
#[error("Deserialization error: {0}")]
Deserialization(String),
/// Encryption error
#[error("Encryption error: {0}")]
Encryption(String),
/// Decryption error
#[error("Decryption error: {0}")]
Decryption(String),
/// Other error
#[error("Error: {0}")]
Other(String),
}
impl From<serde_json::Error> for KvsError {
fn from(err: serde_json::Error) -> Self {
KvsError::Serialization(err.to_string())
}
}
impl From<KvsError> for crate::crypto::error::CryptoError {
fn from(err: KvsError) -> Self {
crate::crypto::error::CryptoError::SerializationError(err.to_string())
}
}
impl From<crate::crypto::error::CryptoError> for KvsError {
fn from(err: crate::crypto::error::CryptoError) -> Self {
match err {
crate::crypto::error::CryptoError::EncryptionFailed(msg) => KvsError::Encryption(msg),
crate::crypto::error::CryptoError::DecryptionFailed(msg) => KvsError::Decryption(msg),
crate::crypto::error::CryptoError::SerializationError(msg) => KvsError::Serialization(msg),
_ => KvsError::Other(err.to_string()),
}
}
}
/// Result type for key-value store operations.
pub type Result<T> = std::result::Result<T, KvsError>;

14
src/crypto/kvs/mod.rs Normal file
View File

@ -0,0 +1,14 @@
//! Key-Value Store functionality
//!
//! This module provides a simple key-value store with encryption support.
mod error;
mod store;
// Re-export public types and functions
pub use error::KvsError;
pub use store::{
KvStore, KvPair,
create_store, open_store, delete_store,
list_stores, get_store_path
};

362
src/crypto/kvs/store.rs Normal file
View File

@ -0,0 +1,362 @@
//! Implementation of a simple key-value store using the filesystem.
use crate::crypto::kvs::error::{KvsError, Result};
use crate::crypto::symmetric;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
/// A key-value pair.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KvPair {
pub key: String,
pub value: String,
}
/// A simple key-value store.
///
/// This implementation uses the filesystem to store key-value pairs.
#[derive(Clone)]
pub struct KvStore {
/// The name of the store
name: String,
/// The path to the store file
path: PathBuf,
/// In-memory cache of the store data
data: Arc<Mutex<HashMap<String, String>>>,
/// Whether the store is encrypted
encrypted: bool,
/// The password for encryption (if encrypted)
password: Option<String>,
}
/// Gets the path to the key-value store directory.
pub fn get_store_path() -> PathBuf {
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
home_dir.join(".hero-vault").join("kvs")
}
/// Creates a new key-value store with the given name.
///
/// # Arguments
///
/// * `name` - The name of the store
/// * `encrypted` - Whether to encrypt the store
/// * `password` - The password for encryption (required if encrypted is true)
///
/// # Returns
///
/// A new `KvStore` instance
pub fn create_store(name: &str, encrypted: bool, password: Option<&str>) -> Result<KvStore> {
// Check if password is provided when encryption is enabled
if encrypted && password.is_none() {
return Err(KvsError::Other("Password required for encrypted store".to_string()));
}
// Create the store directory if it doesn't exist
let store_dir = get_store_path();
if !store_dir.exists() {
fs::create_dir_all(&store_dir)?;
}
// Create the store file path
let store_path = store_dir.join(format!("{}.json", name));
// Create an empty store
let store = KvStore {
name: name.to_string(),
path: store_path,
data: Arc::new(Mutex::new(HashMap::new())),
encrypted,
password: password.map(|s| s.to_string()),
};
// Save the empty store
store.save()?;
Ok(store)
}
/// Opens an existing key-value store with the given name.
///
/// # Arguments
///
/// * `name` - The name of the store
/// * `password` - The password for decryption (required if the store is encrypted)
///
/// # Returns
///
/// The opened `KvStore` instance
pub fn open_store(name: &str, password: Option<&str>) -> Result<KvStore> {
// Get the store file path
let store_dir = get_store_path();
let store_path = store_dir.join(format!("{}.json", name));
// Check if the store exists
if !store_path.exists() {
return Err(KvsError::StoreNotFound(name.to_string()));
}
// Read the store file
let file_content = fs::read_to_string(&store_path)?;
// Check if the file is encrypted (simple heuristic)
let is_encrypted = !file_content.starts_with('{');
// If encrypted, we need a password
if is_encrypted && password.is_none() {
return Err(KvsError::Other("Password required for encrypted store".to_string()));
}
// Parse the store data
let data: HashMap<String, String> = if is_encrypted {
// Decrypt the file content
let password = password.unwrap();
let encrypted_data: Vec<u8> = serde_json::from_str(&file_content)?;
let key = symmetric::derive_key_from_password(password);
let decrypted_data = symmetric::decrypt_symmetric(&key, &encrypted_data)?;
let decrypted_str = String::from_utf8(decrypted_data)
.map_err(|e| KvsError::Deserialization(e.to_string()))?;
serde_json::from_str(&decrypted_str)?
} else {
serde_json::from_str(&file_content)?
};
// Create the store
let store = KvStore {
name: name.to_string(),
path: store_path,
data: Arc::new(Mutex::new(data)),
encrypted: is_encrypted,
password: password.map(|s| s.to_string()),
};
Ok(store)
}
/// Deletes a key-value store with the given name.
///
/// # Arguments
///
/// * `name` - The name of the store to delete
///
/// # Returns
///
/// `Ok(())` if the operation was successful
pub fn delete_store(name: &str) -> Result<()> {
// Get the store file path
let store_dir = get_store_path();
let store_path = store_dir.join(format!("{}.json", name));
// Check if the store exists
if !store_path.exists() {
return Err(KvsError::StoreNotFound(name.to_string()));
}
// Delete the store file
fs::remove_file(store_path)?;
Ok(())
}
/// Lists all available key-value stores.
///
/// # Returns
///
/// A vector of store names
pub fn list_stores() -> Result<Vec<String>> {
// Get the store directory
let store_dir = get_store_path();
if !store_dir.exists() {
return Ok(Vec::new());
}
// List all JSON files in the directory
let mut stores = Vec::new();
for entry in fs::read_dir(store_dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().map_or(false, |ext| ext == "json") {
if let Some(name) = path.file_stem() {
if let Some(name_str) = name.to_str() {
stores.push(name_str.to_string());
}
}
}
}
Ok(stores)
}
impl KvStore {
/// Saves the store to disk.
fn save(&self) -> Result<()> {
// Get the store data
let data = self.data.lock().unwrap();
// Serialize the data
let serialized = serde_json::to_string(&*data)?;
// Write to file
if self.encrypted {
if let Some(password) = &self.password {
// Encrypt the data
let key = symmetric::derive_key_from_password(password);
let encrypted_data = symmetric::encrypt_symmetric(&key, serialized.as_bytes())?;
let encrypted_json = serde_json::to_string(&encrypted_data)?;
fs::write(&self.path, encrypted_json)?;
} else {
return Err(KvsError::Other("Password required for encrypted store".to_string()));
}
} else {
fs::write(&self.path, serialized)?;
}
Ok(())
}
/// Stores a value with the given key.
///
/// # Arguments
///
/// * `key` - The key to store the value under
/// * `value` - The value to store
///
/// # Returns
///
/// `Ok(())` if the operation was successful
pub fn set<K, V>(&self, key: K, value: &V) -> Result<()>
where
K: ToString,
V: Serialize,
{
let key_str = key.to_string();
let serialized = serde_json::to_string(value)?;
// Update in-memory data
{
let mut data = self.data.lock().unwrap();
data.insert(key_str, serialized);
}
// Save to disk
self.save()?;
Ok(())
}
/// Retrieves a value for the given key.
///
/// # Arguments
///
/// * `key` - The key to retrieve the value for
///
/// # Returns
///
/// The value if found, or `Err(KvsError::KeyNotFound)` if not found
pub fn get<K, V>(&self, key: K) -> Result<V>
where
K: ToString,
V: DeserializeOwned,
{
let key_str = key.to_string();
let data = self.data.lock().unwrap();
match data.get(&key_str) {
Some(serialized) => {
let value = serde_json::from_str(serialized)?;
Ok(value)
},
None => Err(KvsError::KeyNotFound(key_str)),
}
}
/// Deletes a value for the given key.
///
/// # Arguments
///
/// * `key` - The key to delete
///
/// # Returns
///
/// `Ok(())` if the operation was successful
pub fn delete<K>(&self, key: K) -> Result<()>
where
K: ToString,
{
let key_str = key.to_string();
// Update in-memory data
{
let mut data = self.data.lock().unwrap();
if data.remove(&key_str).is_none() {
return Err(KvsError::KeyNotFound(key_str));
}
}
// Save to disk
self.save()?;
Ok(())
}
/// Checks if a key exists in the store.
///
/// # Arguments
///
/// * `key` - The key to check
///
/// # Returns
///
/// `true` if the key exists, `false` otherwise
pub fn contains<K>(&self, key: K) -> Result<bool>
where
K: ToString,
{
let key_str = key.to_string();
let data = self.data.lock().unwrap();
Ok(data.contains_key(&key_str))
}
/// Lists all keys in the store.
///
/// # Returns
///
/// A vector of keys as strings
pub fn keys(&self) -> Result<Vec<String>> {
let data = self.data.lock().unwrap();
Ok(data.keys().cloned().collect())
}
/// Clears all key-value pairs from the store.
///
/// # Returns
///
/// `Ok(())` if the operation was successful
pub fn clear(&self) -> Result<()> {
// Update in-memory data
{
let mut data = self.data.lock().unwrap();
data.clear();
}
// Save to disk
self.save()?;
Ok(())
}
/// Gets the name of the store.
pub fn name(&self) -> &str {
&self.name
}
/// Gets whether the store is encrypted.
pub fn is_encrypted(&self) -> bool {
self.encrypted
}
}

17
src/crypto/mod.rs Normal file
View File

@ -0,0 +1,17 @@
//! Cryptographic functionality for SAL
//!
//! This module provides cryptographic operations including:
//! - Key pair management (ECDSA)
//! - Symmetric encryption (ChaCha20Poly1305)
//! - Ethereum wallet functionality
//! - Key-value store with encryption
pub mod error;
pub mod keypair;
pub mod symmetric;
pub mod ethereum;
pub mod kvs;
// Re-export common types for convenience
pub use error::CryptoError;
pub use keypair::{KeyPair, KeySpace};

View File

@ -0,0 +1,266 @@
//! Implementation of symmetric encryption functionality.
use chacha20poly1305::{ChaCha20Poly1305, KeyInit, Nonce};
use chacha20poly1305::aead::Aead;
use rand::{rngs::OsRng, RngCore};
use serde::{Serialize, Deserialize};
use sha2::{Sha256, Digest};
use crate::crypto::error::CryptoError;
use crate::crypto::keypair::KeySpace;
/// The size of the nonce in bytes.
const NONCE_SIZE: usize = 12;
/// Generates a random 32-byte symmetric key.
///
/// # Returns
///
/// A 32-byte array containing the random key.
pub fn generate_symmetric_key() -> [u8; 32] {
let mut key = [0u8; 32];
OsRng.fill_bytes(&mut key);
key
}
/// Derives a 32-byte key from a password.
///
/// # Arguments
///
/// * `password` - The password to derive the key from.
///
/// # Returns
///
/// A 32-byte array containing the derived key.
pub fn derive_key_from_password(password: &str) -> [u8; 32] {
let mut hasher = Sha256::default();
hasher.update(password.as_bytes());
let result = hasher.finalize();
let mut key = [0u8; 32];
key.copy_from_slice(&result);
key
}
/// Encrypts data using ChaCha20Poly1305 with an internally generated nonce.
///
/// The nonce is appended to the ciphertext so it can be extracted during decryption.
///
/// # Arguments
///
/// * `key` - The encryption key (should be 32 bytes).
/// * `message` - The message to encrypt.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the ciphertext with the nonce appended.
/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
/// * `Err(CryptoError::EncryptionFailed)` if encryption fails.
pub fn encrypt_symmetric(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Create cipher
let cipher = ChaCha20Poly1305::new_from_slice(key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
// Generate random nonce
let mut nonce_bytes = [0u8; NONCE_SIZE];
OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes);
// Encrypt message
let ciphertext = cipher.encrypt(nonce, message)
.map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
// Append nonce to ciphertext
let mut result = ciphertext;
result.extend_from_slice(&nonce_bytes);
Ok(result)
}
/// Decrypts data using ChaCha20Poly1305, extracting the nonce from the ciphertext.
///
/// # Arguments
///
/// * `key` - The decryption key (should be 32 bytes).
/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the decrypted message.
/// * `Err(CryptoError::InvalidKeyLength)` if the key length is invalid.
/// * `Err(CryptoError::DecryptionFailed)` if decryption fails or the ciphertext is too short.
pub fn decrypt_symmetric(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>, CryptoError> {
// Check if ciphertext is long enough to contain a nonce
if ciphertext_with_nonce.len() <= NONCE_SIZE {
return Err(CryptoError::DecryptionFailed("Ciphertext too short".to_string()));
}
// Extract nonce from the end of ciphertext
let ciphertext_len = ciphertext_with_nonce.len() - NONCE_SIZE;
let ciphertext = &ciphertext_with_nonce[0..ciphertext_len];
let nonce_bytes = &ciphertext_with_nonce[ciphertext_len..];
// Create cipher
let cipher = ChaCha20Poly1305::new_from_slice(key)
.map_err(|_| CryptoError::InvalidKeyLength)?;
let nonce = Nonce::from_slice(nonce_bytes);
// Decrypt message
cipher.decrypt(nonce, ciphertext)
.map_err(|e| CryptoError::DecryptionFailed(e.to_string()))
}
/// Encrypts data using a key directly (for internal use).
///
/// # Arguments
///
/// * `key` - The encryption key.
/// * `message` - The message to encrypt.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the ciphertext with the nonce appended.
/// * `Err(CryptoError)` if encryption fails.
pub fn encrypt_with_key(key: &[u8], message: &[u8]) -> Result<Vec<u8>, CryptoError> {
encrypt_symmetric(key, message)
}
/// Decrypts data using a key directly (for internal use).
///
/// # Arguments
///
/// * `key` - The decryption key.
/// * `ciphertext_with_nonce` - The ciphertext with the nonce appended.
///
/// # Returns
///
/// * `Ok(Vec<u8>)` containing the decrypted message.
/// * `Err(CryptoError)` if decryption fails.
pub fn decrypt_with_key(key: &[u8], ciphertext_with_nonce: &[u8]) -> Result<Vec<u8>, CryptoError> {
decrypt_symmetric(key, ciphertext_with_nonce)
}
/// Metadata for an encrypted key space.
#[derive(Serialize, Deserialize, Debug)]
pub struct EncryptedKeySpaceMetadata {
pub name: String,
pub created_at: u64,
pub last_accessed: u64,
}
/// An encrypted key space with metadata.
#[derive(Serialize, Deserialize, Debug)]
pub struct EncryptedKeySpace {
pub metadata: EncryptedKeySpaceMetadata,
pub encrypted_data: Vec<u8>,
}
/// Encrypts a key space using a password.
///
/// # Arguments
///
/// * `space` - The key space to encrypt.
/// * `password` - The password to encrypt with.
///
/// # Returns
///
/// * `Ok(EncryptedKeySpace)` containing the encrypted key space.
/// * `Err(CryptoError)` if encryption fails.
pub fn encrypt_key_space(space: &KeySpace, password: &str) -> Result<EncryptedKeySpace, CryptoError> {
// Serialize the key space
let serialized = match serde_json::to_vec(space) {
Ok(data) => data,
Err(e) => {
log::error!("Serialization error during encryption: {}", e);
return Err(CryptoError::SerializationError(e.to_string()));
}
};
// Derive key from password
let key = derive_key_from_password(password);
// Encrypt the serialized data
let encrypted_data = encrypt_symmetric(&key, &serialized)?;
// Create metadata
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64;
let metadata = EncryptedKeySpaceMetadata {
name: space.name.clone(),
created_at: now,
last_accessed: now,
};
Ok(EncryptedKeySpace {
metadata,
encrypted_data,
})
}
/// Decrypts a key space using a password.
///
/// # Arguments
///
/// * `encrypted_space` - The encrypted key space.
/// * `password` - The password to decrypt with.
///
/// # Returns
///
/// * `Ok(KeySpace)` containing the decrypted key space.
/// * `Err(CryptoError)` if decryption fails.
pub fn decrypt_key_space(encrypted_space: &EncryptedKeySpace, password: &str) -> Result<KeySpace, CryptoError> {
// Derive key from password
let key = derive_key_from_password(password);
// Decrypt the data
let decrypted_data = decrypt_symmetric(&key, &encrypted_space.encrypted_data)?;
// Deserialize the key space
let space: KeySpace = match serde_json::from_slice(&decrypted_data) {
Ok(space) => space,
Err(e) => {
log::error!("Deserialization error: {}", e);
return Err(CryptoError::SerializationError(e.to_string()));
}
};
Ok(space)
}
/// Serializes an encrypted key space to a JSON string.
///
/// # Arguments
///
/// * `encrypted_space` - The encrypted key space to serialize.
///
/// # Returns
///
/// * `Ok(String)` containing the serialized encrypted key space.
/// * `Err(CryptoError)` if serialization fails.
pub fn serialize_encrypted_space(encrypted_space: &EncryptedKeySpace) -> Result<String, CryptoError> {
serde_json::to_string(encrypted_space)
.map_err(|e| CryptoError::SerializationError(e.to_string()))
}
/// Deserializes an encrypted key space from a JSON string.
///
/// # Arguments
///
/// * `serialized` - The serialized encrypted key space.
///
/// # Returns
///
/// * `Ok(EncryptedKeySpace)` containing the deserialized encrypted key space.
/// * `Err(CryptoError)` if deserialization fails.
pub fn deserialize_encrypted_space(serialized: &str) -> Result<EncryptedKeySpace, CryptoError> {
match serde_json::from_str(serialized) {
Ok(space) => Ok(space),
Err(e) => {
log::error!("Error deserializing encrypted space: {}", e);
Err(CryptoError::SerializationError(e.to_string()))
}
}
}

View File

@ -0,0 +1,15 @@
//! Symmetric encryption functionality
//!
//! This module provides functionality for symmetric encryption using ChaCha20Poly1305.
mod implementation;
// Re-export public types and functions
pub use implementation::{
generate_symmetric_key, derive_key_from_password,
encrypt_symmetric, decrypt_symmetric,
encrypt_with_key, decrypt_with_key,
encrypt_key_space, decrypt_key_space,
serialize_encrypted_space, deserialize_encrypted_space,
EncryptedKeySpace, EncryptedKeySpaceMetadata
};

View File

@ -12,6 +12,7 @@
//! - System information //! - System information
//! - Network operations //! - Network operations
//! - Environment variables //! - Environment variables
//! - Cryptographic operations
use std::io; use std::io;
use thiserror::Error; use thiserror::Error;
@ -44,6 +45,7 @@ pub mod text;
pub mod virt; pub mod virt;
pub mod rhai; pub mod rhai;
pub mod cmd; pub mod cmd;
pub mod crypto;
// Version information // Version information
/// Returns the version of the SAL library /// Returns the version of the SAL library

416
src/rhai/crypto.rs Normal file
View File

@ -0,0 +1,416 @@
//! Rhai bindings for SAL crypto functionality
use rhai::{Engine, Dynamic, FnPtr, EvalAltResult};
use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use std::fs;
use std::path::PathBuf;
use crate::crypto::{keypair, symmetric, ethereum};
use crate::crypto::error::CryptoError;
use crate::crypto::kvs;
// Key space management functions
fn load_key_space(name: &str, password: &str) -> bool {
// Get the key spaces directory from config
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
// Check if directory exists
if !key_spaces_dir.exists() {
log::error!("Key spaces directory does not exist");
return false;
}
// Get the key space file path
let space_path = key_spaces_dir.join(format!("{}.json", name));
// Check if file exists
if !space_path.exists() {
log::error!("Key space file not found: {}", space_path.display());
return false;
}
// Read the file
let serialized = match fs::read_to_string(&space_path) {
Ok(data) => data,
Err(e) => {
log::error!("Error reading key space file: {}", e);
return false;
}
};
// Deserialize the encrypted space
let encrypted_space = match symmetric::deserialize_encrypted_space(&serialized) {
Ok(space) => space,
Err(e) => {
log::error!("Error deserializing key space: {}", e);
return false;
}
};
// Decrypt the space
let space = match symmetric::decrypt_key_space(&encrypted_space, password) {
Ok(space) => space,
Err(e) => {
log::error!("Error decrypting key space: {}", e);
return false;
}
};
// Set as current space
match keypair::set_current_space(space) {
Ok(_) => true,
Err(e) => {
log::error!("Error setting current space: {}", e);
false
}
}
}
fn create_key_space(name: &str, password: &str) -> bool {
match keypair::create_space(name) {
Ok(_) => {
// Get the current space
match keypair::get_current_space() {
Ok(space) => {
// Encrypt the key space
let encrypted_space = match symmetric::encrypt_key_space(&space, password) {
Ok(encrypted) => encrypted,
Err(e) => {
log::error!("Error encrypting key space: {}", e);
return false;
}
};
// Serialize the encrypted space
let serialized = match symmetric::serialize_encrypted_space(&encrypted_space) {
Ok(json) => json,
Err(e) => {
log::error!("Error serializing encrypted space: {}", e);
return false;
}
};
// Get the key spaces directory
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
// Create directory if it doesn't exist
if !key_spaces_dir.exists() {
match fs::create_dir_all(&key_spaces_dir) {
Ok(_) => {},
Err(e) => {
log::error!("Error creating key spaces directory: {}", e);
return false;
}
}
}
// Write to file
let space_path = key_spaces_dir.join(format!("{}.json", name));
match fs::write(&space_path, serialized) {
Ok(_) => {
log::info!("Key space created and saved to {}", space_path.display());
true
},
Err(e) => {
log::error!("Error writing key space file: {}", e);
false
}
}
},
Err(e) => {
log::error!("Error getting current space: {}", e);
false
}
}
},
Err(e) => {
log::error!("Error creating key space: {}", e);
false
}
}
}
// Auto-save function for internal use
fn auto_save_key_space(password: &str) -> bool {
match keypair::get_current_space() {
Ok(space) => {
// Encrypt the key space
let encrypted_space = match symmetric::encrypt_key_space(&space, password) {
Ok(encrypted) => encrypted,
Err(e) => {
log::error!("Error encrypting key space: {}", e);
return false;
}
};
// Serialize the encrypted space
let serialized = match symmetric::serialize_encrypted_space(&encrypted_space) {
Ok(json) => json,
Err(e) => {
log::error!("Error serializing encrypted space: {}", e);
return false;
}
};
// Get the key spaces directory
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
let key_spaces_dir = home_dir.join(".hero-vault").join("key-spaces");
// Create directory if it doesn't exist
if !key_spaces_dir.exists() {
match fs::create_dir_all(&key_spaces_dir) {
Ok(_) => {},
Err(e) => {
log::error!("Error creating key spaces directory: {}", e);
return false;
}
}
}
// Write to file
let space_path = key_spaces_dir.join(format!("{}.json", space.name));
match fs::write(&space_path, serialized) {
Ok(_) => {
log::info!("Key space saved to {}", space_path.display());
true
},
Err(e) => {
log::error!("Error writing key space file: {}", e);
false
}
}
},
Err(e) => {
log::error!("Error getting current space: {}", e);
false
}
}
}
fn encrypt_key_space(password: &str) -> String {
match keypair::get_current_space() {
Ok(space) => {
match symmetric::encrypt_key_space(&space, password) {
Ok(encrypted_space) => {
match serde_json::to_string(&encrypted_space) {
Ok(json) => json,
Err(e) => {
log::error!("Error serializing encrypted space: {}", e);
String::new()
}
}
},
Err(e) => {
log::error!("Error encrypting key space: {}", e);
String::new()
}
}
},
Err(e) => {
log::error!("Error getting current space: {}", e);
String::new()
}
}
}
fn decrypt_key_space(encrypted: &str, password: &str) -> bool {
match serde_json::from_str(encrypted) {
Ok(encrypted_space) => {
match symmetric::decrypt_key_space(&encrypted_space, password) {
Ok(space) => {
match keypair::set_current_space(space) {
Ok(_) => true,
Err(e) => {
log::error!("Error setting current space: {}", e);
false
}
}
},
Err(e) => {
log::error!("Error decrypting key space: {}", e);
false
}
}
},
Err(e) => {
log::error!("Error parsing encrypted space: {}", e);
false
}
}
}
// Keypair management functions
fn create_keypair(name: &str, password: &str) -> bool {
match keypair::create_keypair(name) {
Ok(_) => {
// Auto-save the key space after creating a keypair
auto_save_key_space(password)
},
Err(e) => {
log::error!("Error creating keypair: {}", e);
false
}
}
}
fn select_keypair(name: &str) -> bool {
match keypair::select_keypair(name) {
Ok(_) => true,
Err(e) => {
log::error!("Error selecting keypair: {}", e);
false
}
}
}
fn list_keypairs() -> Vec<String> {
match keypair::list_keypairs() {
Ok(keypairs) => keypairs,
Err(e) => {
log::error!("Error listing keypairs: {}", e);
Vec::new()
}
}
}
// Cryptographic operations
fn sign(message: &str) -> String {
let message_bytes = message.as_bytes();
match keypair::keypair_sign(message_bytes) {
Ok(signature) => BASE64.encode(signature),
Err(e) => {
log::error!("Error signing message: {}", e);
String::new()
}
}
}
fn verify(message: &str, signature: &str) -> bool {
let message_bytes = message.as_bytes();
match BASE64.decode(signature) {
Ok(signature_bytes) => {
match keypair::keypair_verify(message_bytes, &signature_bytes) {
Ok(is_valid) => is_valid,
Err(e) => {
log::error!("Error verifying signature: {}", e);
false
}
}
},
Err(e) => {
log::error!("Error decoding signature: {}", e);
false
}
}
}
// Symmetric encryption
fn generate_key() -> String {
let key = symmetric::generate_symmetric_key();
BASE64.encode(key)
}
fn encrypt(key: &str, message: &str) -> String {
match BASE64.decode(key) {
Ok(key_bytes) => {
let message_bytes = message.as_bytes();
match symmetric::encrypt_symmetric(&key_bytes, message_bytes) {
Ok(ciphertext) => BASE64.encode(ciphertext),
Err(e) => {
log::error!("Error encrypting message: {}", e);
String::new()
}
}
},
Err(e) => {
log::error!("Error decoding key: {}", e);
String::new()
}
}
}
fn decrypt(key: &str, ciphertext: &str) -> String {
match BASE64.decode(key) {
Ok(key_bytes) => {
match BASE64.decode(ciphertext) {
Ok(ciphertext_bytes) => {
match symmetric::decrypt_symmetric(&key_bytes, &ciphertext_bytes) {
Ok(plaintext) => {
match String::from_utf8(plaintext) {
Ok(text) => text,
Err(e) => {
log::error!("Error converting plaintext to string: {}", e);
String::new()
}
}
},
Err(e) => {
log::error!("Error decrypting ciphertext: {}", e);
String::new()
}
}
},
Err(e) => {
log::error!("Error decoding ciphertext: {}", e);
String::new()
}
}
},
Err(e) => {
log::error!("Error decoding key: {}", e);
String::new()
}
}
}
// Ethereum operations
fn create_ethereum_wallet() -> bool {
match ethereum::create_ethereum_wallet() {
Ok(_) => true,
Err(e) => {
log::error!("Error creating Ethereum wallet: {}", e);
false
}
}
}
fn get_ethereum_address() -> String {
match ethereum::get_current_ethereum_wallet() {
Ok(wallet) => wallet.address_string(),
Err(e) => {
log::error!("Error getting Ethereum address: {}", e);
String::new()
}
}
}
/// Register crypto functions with the Rhai engine
pub fn register_crypto_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register key space functions
engine.register_fn("load_key_space", load_key_space);
engine.register_fn("create_key_space", create_key_space);
engine.register_fn("encrypt_key_space", encrypt_key_space);
engine.register_fn("decrypt_key_space", decrypt_key_space);
// Register keypair functions
engine.register_fn("create_keypair", create_keypair);
engine.register_fn("select_keypair", select_keypair);
engine.register_fn("list_keypairs", list_keypairs);
// Register signing/verification functions
engine.register_fn("sign", sign);
engine.register_fn("verify", verify);
// Register symmetric encryption functions
engine.register_fn("generate_key", generate_key);
engine.register_fn("encrypt", encrypt);
engine.register_fn("decrypt", decrypt);
// Register Ethereum functions
engine.register_fn("create_ethereum_wallet", create_ethereum_wallet);
engine.register_fn("get_ethereum_address", get_ethereum_address);
Ok(())
}

View File

@ -11,6 +11,7 @@ mod nerdctl;
mod git; mod git;
mod text; mod text;
mod rfs; mod rfs;
mod crypto;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -73,6 +74,9 @@ pub use crate::text::{
// Re-export TextReplacer functions // Re-export TextReplacer functions
pub use text::*; pub use text::*;
// Re-export crypto module
pub use crypto::register_crypto_module;
// Rename copy functions to avoid conflicts // Rename copy functions to avoid conflicts
pub use os::copy as os_copy; pub use os::copy as os_copy;
@ -116,6 +120,9 @@ pub fn register(engine: &mut Engine) -> Result<(), Box<rhai::EvalAltResult>> {
// Register RFS module functions // Register RFS module functions
rfs::register(engine)?; rfs::register(engine)?;
// Register Crypto module functions
crypto::register_crypto_module(engine)?;
// Future modules can be registered here // Future modules can be registered here