feat: Remove herodo from monorepo and update dependencies
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
Some checks are pending
Rhai Tests / Run Rhai Tests (push) Waiting to run
- Removed the `herodo` binary from the monorepo. This was done as part of the monorepo conversion process. - Updated the `Cargo.toml` file to reflect the removal of `herodo` and adjust dependencies accordingly. - Updated `src/lib.rs` and `src/rhai/mod.rs` to use the new `sal-vault` crate for vault functionality. This improves the modularity and maintainability of the project.
This commit is contained in:
271
vault/src/keyspace/README.md
Normal file
271
vault/src/keyspace/README.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Hero Vault Keypair Module
|
||||
|
||||
The Keypair module provides functionality for creating, managing, and using ECDSA keypairs for digital signatures and other cryptographic operations.
|
||||
|
||||
## Module Structure
|
||||
|
||||
The Keypair module is organized into:
|
||||
|
||||
- `keypair_types.rs` - Defines the KeyPair and related types.
|
||||
- `session_manager.rs` - Implements the core logic for managing keypairs and key spaces.
|
||||
- `mod.rs` - Module exports and public interface.
|
||||
|
||||
## Key Types
|
||||
|
||||
### KeyPair
|
||||
|
||||
The `KeyPair` type represents an ECDSA keypair used for digital signatures and other cryptographic operations.
|
||||
|
||||
```rust
|
||||
pub struct KeyPair {
|
||||
// Private fields
|
||||
// ...
|
||||
}
|
||||
|
||||
impl KeyPair {
|
||||
// Create a new random keypair
|
||||
pub fn new() -> Result<Self, CryptoError>;
|
||||
|
||||
// Create a keypair from an existing private key
|
||||
pub fn from_private_key(private_key: &[u8]) -> Result<Self, CryptoError>;
|
||||
|
||||
// Get the public key
|
||||
pub fn public_key(&self) -> &[u8];
|
||||
|
||||
// Sign a message
|
||||
pub fn sign(&self, message: &[u8]) -> Result<Vec<u8>, CryptoError>;
|
||||
|
||||
// Verify a signature
|
||||
pub fn verify(&self, message: &[u8], signature: &[u8]) -> Result<bool, CryptoError>;
|
||||
|
||||
// Derive an Ethereum address from the public key
|
||||
pub fn to_ethereum_address(&self) -> Result<String, CryptoError>;
|
||||
|
||||
// Export the private key (should be used with caution)
|
||||
pub fn export_private_key(&self) -> Result<Vec<u8>, CryptoError>;
|
||||
}
|
||||
```
|
||||
|
||||
### KeySpace
|
||||
|
||||
The `KeySpace` type represents a secure container for multiple keypairs, which can be encrypted and stored on disk.
|
||||
|
||||
```rust
|
||||
pub struct KeySpace {
|
||||
// Private fields
|
||||
// ...
|
||||
}
|
||||
|
||||
impl KeySpace {
|
||||
// Create a new key space
|
||||
pub fn new(name: &str, password: &str) -> Result<Self, CryptoError>;
|
||||
|
||||
// Load a key space from disk
|
||||
pub fn load(name: &str, password: &str) -> Result<Self, CryptoError>;
|
||||
|
||||
// Save the key space to disk
|
||||
pub fn save(&self) -> Result<(), CryptoError>;
|
||||
|
||||
// Create a new keypair in the key space
|
||||
pub fn create_keypair(&mut self, name: &str, password: &str) -> Result<&KeyPair, CryptoError>;
|
||||
|
||||
// Select a keypair for use
|
||||
pub fn select_keypair(&mut self, name: &str) -> Result<&KeyPair, CryptoError>;
|
||||
|
||||
// Get the currently selected keypair
|
||||
pub fn current_keypair(&self) -> Result<&KeyPair, CryptoError>;
|
||||
|
||||
// List all keypairs in the key space
|
||||
pub fn list_keypairs(&self) -> Result<Vec<String>, CryptoError>;
|
||||
|
||||
// Get a keypair by name
|
||||
pub fn get_keypair(&self, name: &str) -> Result<&KeyPair, CryptoError>;
|
||||
|
||||
// Remove a keypair from the key space
|
||||
pub fn remove_keypair(&mut self, name: &str) -> Result<(), CryptoError>;
|
||||
|
||||
// Rename a keypair
|
||||
pub fn rename_keypair(&mut self, old_name: &str, new_name: &str) -> Result<(), CryptoError>;
|
||||
|
||||
// Get the name of the key space
|
||||
pub fn name(&self) -> &str;
|
||||
}
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
### Key Space Management
|
||||
|
||||
The module provides functionality for creating, loading, and managing key spaces:
|
||||
|
||||
```rust
|
||||
// Create a new key space
|
||||
let mut space = KeySpace::new("my_space", "secure_password")?;
|
||||
|
||||
// Save the key space to disk
|
||||
space.save()?;
|
||||
|
||||
// Load a key space from disk
|
||||
let mut loaded_space = KeySpace::load("my_space", "secure_password")?;
|
||||
```
|
||||
|
||||
### Keypair Management
|
||||
|
||||
The module provides functionality for creating, selecting, and using keypairs:
|
||||
|
||||
```rust
|
||||
use crate::vault::keypair::{KeySpace, KeyPair};
|
||||
use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error
|
||||
|
||||
fn demonstrate_keypair_management() -> Result<(), CryptoError> {
|
||||
// Create a new key space
|
||||
let mut space = KeySpace::new("my_space", "secure_password")?;
|
||||
|
||||
// Create a new keypair in the key space
|
||||
let keypair = space.create_keypair("my_keypair", "secure_password")?;
|
||||
println!("Created keypair: {}", keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::<String>());
|
||||
|
||||
// Select a keypair for use
|
||||
space.select_keypair("my_keypair")?;
|
||||
println!("Selected keypair: {}", space.current_keypair()?.public_key().iter().map(|b| format!("{:02x}", b)).collect::<String>());
|
||||
|
||||
// List all keypairs in the key space
|
||||
let keypairs = space.list_keypairs()?;
|
||||
println!("Keypairs in space: {:?}", keypairs);
|
||||
|
||||
// Get a keypair by name
|
||||
let retrieved_keypair = space.get_keypair("my_keypair")?;
|
||||
println!("Retrieved keypair: {}", retrieved_keypair.public_key().iter().map(|b| format!("{:02x}", b)).collect::<String>());
|
||||
|
||||
// Rename a keypair
|
||||
space.rename_keypair("my_keypair", "new_name")?;
|
||||
println!("Renamed keypair to new_name");
|
||||
let keypairs_after_rename = space.list_keypairs()?;
|
||||
println!("Keypairs in space after rename: {:?}", keypairs_after_rename);
|
||||
|
||||
|
||||
// Remove a keypair from the key space
|
||||
space.remove_keypair("new_name")?;
|
||||
println!("Removed keypair new_name");
|
||||
let keypairs_after_remove = space.list_keypairs()?;
|
||||
println!("Keypairs in space after removal: {:?}", keypairs_after_remove);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Digital Signatures
|
||||
|
||||
The module provides functionality for signing and verifying messages using ECDSA:
|
||||
|
||||
```rust
|
||||
use crate::vault::keypair::KeySpace;
|
||||
use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error
|
||||
|
||||
fn demonstrate_digital_signatures() -> Result<(), CryptoError> {
|
||||
// Assuming a key space and selected keypair exist
|
||||
// let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space
|
||||
let mut space = KeySpace::new("temp_space_for_demo", "password")?; // Or create a new one for demo
|
||||
space.create_keypair("my_signing_key", "key_password")?;
|
||||
space.select_keypair("my_signing_key")?;
|
||||
|
||||
|
||||
// Sign a message using the selected keypair
|
||||
let keypair = space.current_keypair()?;
|
||||
let message = "This is a message to sign".as_bytes();
|
||||
let signature = keypair.sign(message)?;
|
||||
println!("Message signed. Signature: {:?}", signature);
|
||||
|
||||
// Verify a signature
|
||||
let is_valid = keypair.verify(message, &signature)?;
|
||||
println!("Signature valid: {}", is_valid);
|
||||
|
||||
// Example of invalid signature verification
|
||||
let invalid_signature = vec![0u8; signature.len()]; // A dummy invalid signature
|
||||
let is_valid_invalid = keypair.verify(message, &invalid_signature)?;
|
||||
println!("Invalid signature valid: {}", is_valid_invalid);
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Ethereum Address Derivation
|
||||
|
||||
The module provides functionality for deriving Ethereum addresses from keypairs:
|
||||
|
||||
```rust
|
||||
use crate::vault::keypair::KeySpace;
|
||||
use crate::vault::error::CryptoError; // Assuming CryptoError is in vault::error
|
||||
|
||||
fn demonstrate_ethereum_address_derivation() -> Result<(), CryptoError> {
|
||||
// Assuming a key space and selected keypair exist
|
||||
// let mut space = KeySpace::load("my_space", "secure_password")?; // Load existing space
|
||||
let mut space = KeySpace::new("temp_space_for_eth_demo", "password")?; // Or create a new one for demo
|
||||
space.create_keypair("my_eth_key", "key_password")?;
|
||||
space.select_keypair("my_eth_key")?;
|
||||
|
||||
// Derive an Ethereum address from a keypair
|
||||
let keypair = space.current_keypair()?;
|
||||
let address = keypair.to_ethereum_address()?;
|
||||
println!("Derived Ethereum address: {}", address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Including in Your Project
|
||||
|
||||
To include the Hero Vault Keypair module in your Rust project, add the following to your `Cargo.toml` file:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
hero_vault = "0.1.0" # Replace with the actual version
|
||||
```
|
||||
|
||||
Then, you can import and use the module in your Rust code:
|
||||
|
||||
```rust
|
||||
use hero_vault::vault::keypair::{KeySpace, KeyPair};
|
||||
use hero_vault::vault::error::CryptoError;
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Tests for the Keypair module are included within the source files, likely in `session_manager.rs` or `mod.rs` as inline tests.
|
||||
|
||||
To run the tests, navigate to the root directory of the project in your terminal and execute the following command:
|
||||
|
||||
```bash
|
||||
cargo test --lib vault::keypair
|
||||
```
|
||||
|
||||
This command will run all tests specifically within the `vault::keypair` module.
|
||||
|
||||
## 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
|
||||
|
||||
## Error Handling
|
||||
|
||||
The module uses the `CryptoError` type for handling errors that can occur during keypair operations:
|
||||
|
||||
- `InvalidKeyLength` - Invalid key length
|
||||
- `SignatureFormatError` - Signature format error
|
||||
- `KeypairAlreadyExists` - Keypair already exists
|
||||
- `KeypairNotFound` - Keypair not found
|
||||
- `NoActiveSpace` - No active key space
|
||||
- `NoKeypairSelected` - No keypair selected
|
||||
- `SerializationError` - Serialization error
|
||||
|
||||
## Examples
|
||||
|
||||
For examples of how to use the Keypair module, see the `examples/hero_vault` directory, particularly:
|
||||
|
||||
- `example.rhai` - Basic example demonstrating key management and signing
|
||||
- `advanced_example.rhai` - Advanced example with error handling
|
||||
- `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
|
@@ -1,72 +0,0 @@
|
||||
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!()
|
||||
}
|
||||
}
|
332
vault/src/keyspace/keypair_types.rs
Normal file
332
vault/src/keyspace/keypair_types.rs
Normal file
@@ -0,0 +1,332 @@
|
||||
use k256::ecdh::EphemeralSecret;
|
||||
/// Implementation of keypair functionality.
|
||||
use k256::ecdsa::{
|
||||
signature::{Signer, Verifier},
|
||||
Signature, SigningKey, VerifyingKey,
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::error::CryptoError;
|
||||
use crate::symmetric::implementation;
|
||||
|
||||
/// 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::de::{self, Visitor};
|
||||
use serde::{Deserializer, Serializer};
|
||||
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::de::{self, Visitor};
|
||||
use serde::{Deserializer, Serializer};
|
||||
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 using ECDH
|
||||
let ephemeral_secret = EphemeralSecret::random(&mut OsRng);
|
||||
let _shared_secret = ephemeral_secret.diffie_hellman(&recipient_key.into());
|
||||
|
||||
// Derive encryption key from the shared secret (e.g., using HKDF or hashing)
|
||||
// For simplicity, we'll hash the shared secret here
|
||||
let encryption_key = {
|
||||
let mut hasher = Sha256::default();
|
||||
hasher.update(recipient_public_key);
|
||||
// Use a fixed salt for testing purposes
|
||||
hasher.update(b"fixed_salt_for_testing");
|
||||
hasher.finalize().to_vec()
|
||||
};
|
||||
|
||||
// Encrypt the message using the derived key
|
||||
let ciphertext = implementation::encrypt_with_key(&encryption_key, message)
|
||||
.map_err(|e| CryptoError::EncryptionFailed(e.to_string()))?;
|
||||
|
||||
// Format: ephemeral_public_key || ciphertext
|
||||
let mut result = ephemeral_public_key
|
||||
.to_encoded_point(false)
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
result.extend_from_slice(&ciphertext);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 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 using ECDH
|
||||
let recipient_secret = EphemeralSecret::random(&mut OsRng);
|
||||
let _shared_secret = recipient_secret.diffie_hellman(&sender_key.into());
|
||||
|
||||
// Derive decryption key from the shared secret (using the same method as encryption)
|
||||
let decryption_key = {
|
||||
let mut hasher = Sha256::default();
|
||||
hasher.update(self.verifying_key.to_sec1_bytes());
|
||||
// Use the same fixed salt as in encryption
|
||||
hasher.update(b"fixed_salt_for_testing");
|
||||
hasher.finalize().to_vec()
|
||||
};
|
||||
|
||||
// Decrypt the message using the derived key
|
||||
implementation::decrypt_with_key(&decryption_key, 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()
|
||||
}
|
||||
}
|
16
vault/src/keyspace/mod.rs
Normal file
16
vault/src/keyspace/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
//! Key pair management functionality
|
||||
//!
|
||||
//! This module provides functionality for creating and managing ECDSA key pairs.
|
||||
|
||||
pub mod keypair_types;
|
||||
pub mod session_manager;
|
||||
|
||||
// Re-export public types and functions
|
||||
pub use keypair_types::{KeyPair, KeySpace};
|
||||
pub use session_manager::{
|
||||
clear_session, create_keypair, create_space, decrypt_asymmetric, derive_public_key,
|
||||
encrypt_asymmetric, get_current_space, get_selected_keypair, keypair_pub_key, keypair_sign,
|
||||
keypair_verify, list_keypairs, select_keypair, set_current_space, verify_with_public_key,
|
||||
};
|
||||
|
||||
// Tests are now in the tests/ directory
|
174
vault/src/keyspace/session_manager.rs
Normal file
174
vault/src/keyspace/session_manager.rs
Normal file
@@ -0,0 +1,174 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::error::CryptoError;
|
||||
use crate::keyspace::keypair_types::{KeyPair, KeySpace};
|
||||
|
||||
/// 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.
|
||||
pub static SESSION: Lazy<Mutex<Session>> = Lazy::new(|| Mutex::new(Session::default()));
|
||||
|
||||
// Session management and selected keypair operation functions will be added here
|
||||
/// 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)
|
||||
}
|
36
vault/src/keyspace/spec.md
Normal file
36
vault/src/keyspace/spec.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Keyspace Module Specification
|
||||
|
||||
This document explains the purpose and functionality of the `keyspace` module within the Hero Vault.
|
||||
|
||||
## Purpose of the Module
|
||||
|
||||
The `keyspace` module provides a secure and organized way to manage cryptographic keypairs. It allows for the creation, storage, loading, and utilization of keypairs within designated containers called keyspaces. This module is essential for handling sensitive cryptographic material securely.
|
||||
|
||||
## What is a Keyspace?
|
||||
|
||||
A keyspace is a logical container designed to hold multiple cryptographic keypairs. It is represented by the `KeySpace` struct in the code. Keyspaces can be encrypted and persisted to disk, providing a secure method for storing collections of keypairs. Each keyspace is identified by a unique name.
|
||||
|
||||
## What is a Keypair?
|
||||
|
||||
A keypair, represented by the `KeyPair` struct, is a fundamental cryptographic element consisting of a mathematically linked pair of keys: a public key and a private key. In this module, ECDSA (Elliptic Curve Digital Signature Algorithm) keypairs are used.
|
||||
|
||||
* **Private Key:** This key is kept secret and is used for operations like signing data or decrypting messages intended for the keypair's owner.
|
||||
* **Public Key:** This key can be shared openly and is used to verify signatures created by the corresponding private key or to encrypt messages that can only be decrypted by the private key.
|
||||
|
||||
## How Many Keypairs Per Space?
|
||||
|
||||
A keyspace can hold multiple keypairs. The `KeySpace` struct uses a `HashMap` to store keypairs, where each keypair is associated with a unique string name. There is no inherent, fixed limit on the number of keypairs a keyspace can contain, beyond the practical limitations of system memory.
|
||||
|
||||
## How Do We Load Them?
|
||||
|
||||
Keyspaces are loaded from persistent storage (disk) using the `KeySpace::load` function, which requires the keyspace name and a password for decryption. Once a `KeySpace` object is loaded into memory, it can be set as the currently active keyspace for the session using the `session_manager::set_current_space` function. Individual keypairs within the loaded keyspace are then accessed by their names using functions like `session_manager::select_keypair` and `session_manager::get_selected_keypair`.
|
||||
|
||||
## What Do They Do?
|
||||
|
||||
Keypairs within a keyspace are used to perform various cryptographic operations. The `KeyPair` struct provides methods for:
|
||||
|
||||
* **Digital Signatures:** Signing messages with the private key (`KeyPair::sign`) and verifying those signatures with the public key (`KeyPair::verify`).
|
||||
* **Ethereum Address Derivation:** Generating an Ethereum address from the public key (`KeyPair::to_ethereum_address`).
|
||||
* **Asymmetric Encryption/Decryption:** Encrypting data using a recipient's public key (`KeyPair::encrypt_asymmetric`) and decrypting data encrypted with the keypair's public key using the private key (`KeyPair::decrypt_asymmetric`).
|
||||
|
||||
The `session_manager` module provides functions that utilize the currently selected keypair to perform these operations within the context of the active session.
|
@@ -1,26 +0,0 @@
|
||||
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!()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user