use std::collections::HashMap;
use std::fs;
use std::path::Path;
use aes_gcm::aead::{Aead, KeyInit};
use aes_gcm::{Aes256Gcm, Nonce as AesGcmNonce};
use chacha20poly1305::{ChaCha20Poly1305, Nonce as ChaChaNonce};
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use torsh_core::error::{Result, TorshError};
use crate::package::Package;
const PBKDF2_ITERATIONS: u32 = 100_000;
const AES_GCM_NONCE_LEN: usize = 12;
const CHACHA20_NONCE_LEN: usize = 12;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PackageSignature {
pub algorithm: SignatureAlgorithm,
pub signature: Vec<u8>,
pub public_key: Vec<u8>,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum SignatureAlgorithm {
Ed25519,
Rsa,
Ecdsa,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EncryptionAlgorithm {
Aes256Gcm,
ChaCha20Poly1305,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptedPackage {
pub algorithm: EncryptionAlgorithm,
pub encrypted_data: Vec<u8>,
pub nonce: Vec<u8>,
pub aad: Option<Vec<u8>>,
pub key_metadata: HashMap<String, String>,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
pub struct PackageSigner {
signing_key: Option<SigningKey>,
verifying_keys: Vec<VerifyingKey>,
algorithm: SignatureAlgorithm,
}
pub struct PackageEncryptor {
algorithm: EncryptionAlgorithm,
}
#[derive(Debug, thiserror::Error)]
pub enum SecurityError {
#[error("Signature verification failed: {0}")]
InvalidSignature(String),
#[error("Encryption error: {0}")]
EncryptionError(String),
#[error("Key error: {0}")]
KeyError(String),
}
impl PackageSigner {
pub fn new() -> Self {
let mut secret_bytes = [0u8; 32];
if let Err(err) = getrandom::fill(&mut secret_bytes) {
panic!("Failed to obtain entropy for signing key generation: {err}");
}
let signing_key = SigningKey::from_bytes(&secret_bytes);
Self {
signing_key: Some(signing_key),
verifying_keys: Vec::new(),
algorithm: SignatureAlgorithm::Ed25519,
}
}
pub fn from_signing_key(signing_key: SigningKey) -> Self {
Self {
signing_key: Some(signing_key),
verifying_keys: Vec::new(),
algorithm: SignatureAlgorithm::Ed25519,
}
}
pub fn verifier_only() -> Self {
Self {
signing_key: None,
verifying_keys: Vec::new(),
algorithm: SignatureAlgorithm::Ed25519,
}
}
pub fn add_trusted_key(&mut self, public_key: &[u8]) -> Result<()> {
let verifying_key =
VerifyingKey::from_bytes(public_key.try_into().map_err(|_| {
TorshError::InvalidArgument("Invalid public key length".to_string())
})?)
.map_err(|e| TorshError::InvalidArgument(format!("Invalid public key: {}", e)))?;
self.verifying_keys.push(verifying_key);
Ok(())
}
pub fn public_key(&self) -> Option<Vec<u8>> {
self.signing_key
.as_ref()
.map(|sk| sk.verifying_key().to_bytes().to_vec())
}
pub fn export_signing_key(&self) -> Option<Vec<u8>> {
self.signing_key.as_ref().map(|sk| sk.to_bytes().to_vec())
}
pub fn sign_package(&self, package: &Package) -> Result<PackageSignature> {
let signing_key = self
.signing_key
.as_ref()
.ok_or_else(|| TorshError::InvalidArgument("No signing key available".to_string()))?;
let package_data = self.package_digest(package)?;
let signature = signing_key.sign(&package_data);
let mut metadata = HashMap::new();
metadata.insert("package_name".to_string(), package.name().to_string());
metadata.insert(
"package_version".to_string(),
package.get_version().to_string(),
);
Ok(PackageSignature {
algorithm: self.algorithm,
signature: signature.to_bytes().to_vec(),
public_key: signing_key.verifying_key().to_bytes().to_vec(),
timestamp: chrono::Utc::now(),
metadata,
})
}
pub fn verify_package(&self, package: &Package, signature: &PackageSignature) -> Result<bool> {
if signature.algorithm != self.algorithm {
return Err(TorshError::InvalidArgument(format!(
"Unsupported signature algorithm: {:?}",
signature.algorithm
)));
}
let verifying_key =
VerifyingKey::from_bytes(signature.public_key.as_slice().try_into().map_err(|_| {
TorshError::InvalidArgument("Invalid public key length".to_string())
})?)
.map_err(|e| TorshError::InvalidArgument(format!("Invalid public key: {}", e)))?;
if !self.verifying_keys.is_empty()
&& !self
.verifying_keys
.iter()
.any(|k| k.to_bytes() == verifying_key.to_bytes())
{
return Ok(false);
}
let package_data = self.package_digest(package)?;
let sig =
Signature::from_bytes(signature.signature.as_slice().try_into().map_err(|_| {
TorshError::InvalidArgument("Invalid signature length".to_string())
})?);
match verifying_key.verify(&package_data, &sig) {
Ok(()) => Ok(true),
Err(_) => Ok(false),
}
}
fn package_digest(&self, package: &Package) -> Result<Vec<u8>> {
let mut hasher = Sha256::new();
hasher.update(package.name().as_bytes());
hasher.update(package.get_version().as_bytes());
let mut resource_names: Vec<_> = package.resources().keys().collect();
resource_names.sort();
for name in resource_names {
if let Some(resource) = package.resources().get(name) {
hasher.update(name.as_bytes());
hasher.update(&resource.data);
}
}
Ok(hasher.finalize().to_vec())
}
pub fn save_signature<P: AsRef<Path>>(signature: &PackageSignature, path: P) -> Result<()> {
let serialized = oxicode::serde::encode_to_vec(signature, oxicode::config::standard())
.map_err(|e| TorshError::SerializationError(e.to_string()))?;
fs::write(path, serialized).map_err(|e| TorshError::IoError(e.to_string()))?;
Ok(())
}
pub fn load_signature<P: AsRef<Path>>(path: P) -> Result<PackageSignature> {
let data = fs::read(path).map_err(|e| TorshError::IoError(e.to_string()))?;
let (signature, _) = oxicode::serde::decode_from_slice(&data, oxicode::config::standard())
.map_err(|e| TorshError::SerializationError(e.to_string()))?;
Ok(signature)
}
}
impl Default for PackageSigner {
fn default() -> Self {
Self::new()
}
}
impl PackageEncryptor {
pub fn new(algorithm: EncryptionAlgorithm) -> Self {
Self { algorithm }
}
pub fn encrypt_package_with_password(
&self,
package: &Package,
password: &str,
) -> Result<EncryptedPackage> {
let package_data = oxicode::serde::encode_to_vec(package, oxicode::config::standard())
.map_err(|e| TorshError::SerializationError(e.to_string()))?;
let salt = self.generate_salt()?;
let key = self.derive_key_from_password(password, &salt)?;
let (encrypted_data, nonce) = self.encrypt_data(&package_data, &key)?;
let mut key_metadata = HashMap::new();
key_metadata.insert("kdf".to_string(), "pbkdf2".to_string());
key_metadata.insert("salt".to_string(), hex::encode(&salt));
key_metadata.insert("iterations".to_string(), "100000".to_string());
Ok(EncryptedPackage {
algorithm: self.algorithm,
encrypted_data,
nonce,
aad: None,
key_metadata,
timestamp: chrono::Utc::now(),
})
}
pub fn decrypt_package_with_password(
&self,
encrypted: &EncryptedPackage,
password: &str,
) -> Result<Package> {
let salt_hex = encrypted
.key_metadata
.get("salt")
.ok_or_else(|| TorshError::InvalidArgument("Missing salt in metadata".to_string()))?;
let salt = hex::decode(salt_hex)
.map_err(|e| TorshError::InvalidArgument(format!("Invalid salt: {}", e)))?;
let key = self.derive_key_from_password(password, &salt)?;
let decrypted_data =
self.decrypt_data(&encrypted.encrypted_data, &encrypted.nonce, &key)?;
let (package, _) =
oxicode::serde::decode_from_slice(&decrypted_data, oxicode::config::standard())
.map_err(|e| TorshError::SerializationError(e.to_string()))?;
Ok(package)
}
fn encrypt_data(&self, data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
match self.algorithm {
EncryptionAlgorithm::Aes256Gcm => self.encrypt_aes_gcm(data, key),
EncryptionAlgorithm::ChaCha20Poly1305 => self.encrypt_chacha20(data, key),
}
}
fn decrypt_data(&self, encrypted: &[u8], nonce: &[u8], key: &[u8]) -> Result<Vec<u8>> {
match self.algorithm {
EncryptionAlgorithm::Aes256Gcm => self.decrypt_aes_gcm(encrypted, nonce, key),
EncryptionAlgorithm::ChaCha20Poly1305 => self.decrypt_chacha20(encrypted, nonce, key),
}
}
fn encrypt_aes_gcm(&self, data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
let cipher = Aes256Gcm::new_from_slice(key)
.map_err(|_| TorshError::InvalidArgument("Invalid key for AES-256-GCM".to_string()))?;
let nonce_bytes = self.generate_nonce(AES_GCM_NONCE_LEN)?;
let nonce = AesGcmNonce::try_from(nonce_bytes.as_slice())
.map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
let ciphertext = cipher
.encrypt(&nonce, data)
.map_err(|_| TorshError::InvalidArgument("Encryption failed".to_string()))?;
Ok((ciphertext, nonce_bytes))
}
fn decrypt_aes_gcm(&self, encrypted: &[u8], nonce: &[u8], key: &[u8]) -> Result<Vec<u8>> {
let cipher = Aes256Gcm::new_from_slice(key)
.map_err(|_| TorshError::InvalidArgument("Invalid key for AES-256-GCM".to_string()))?;
let nonce = AesGcmNonce::try_from(nonce)
.map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
cipher
.decrypt(&nonce, encrypted)
.map_err(|_| TorshError::InvalidArgument("Decryption failed".to_string()))
}
fn encrypt_chacha20(&self, data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
let cipher = ChaCha20Poly1305::new_from_slice(key).map_err(|_| {
TorshError::InvalidArgument("Invalid key for ChaCha20-Poly1305".to_string())
})?;
let nonce_bytes = self.generate_nonce(CHACHA20_NONCE_LEN)?;
let nonce = ChaChaNonce::try_from(nonce_bytes.as_slice())
.map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
let ciphertext = cipher
.encrypt(&nonce, data)
.map_err(|_| TorshError::InvalidArgument("Encryption failed".to_string()))?;
Ok((ciphertext, nonce_bytes))
}
fn decrypt_chacha20(&self, encrypted: &[u8], nonce: &[u8], key: &[u8]) -> Result<Vec<u8>> {
let cipher = ChaCha20Poly1305::new_from_slice(key).map_err(|_| {
TorshError::InvalidArgument("Invalid key for ChaCha20-Poly1305".to_string())
})?;
let nonce = ChaChaNonce::try_from(nonce)
.map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
cipher
.decrypt(&nonce, encrypted)
.map_err(|_| TorshError::InvalidArgument("Decryption failed".to_string()))
}
fn derive_key_from_password(&self, password: &str, salt: &[u8]) -> Result<Vec<u8>> {
use pbkdf2::pbkdf2_hmac;
let mut key = vec![0u8; 32];
pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, PBKDF2_ITERATIONS, &mut key);
Ok(key)
}
fn generate_salt(&self) -> Result<Vec<u8>> {
let mut salt = vec![0u8; 32];
getrandom::fill(&mut salt)
.map_err(|e| TorshError::InvalidArgument(format!("Failed to generate salt: {e}")))?;
Ok(salt)
}
fn generate_nonce(&self, len: usize) -> Result<Vec<u8>> {
let mut nonce = vec![0u8; len];
getrandom::fill(&mut nonce)
.map_err(|e| TorshError::InvalidArgument(format!("Failed to generate nonce: {e}")))?;
Ok(nonce)
}
pub fn save_encrypted<P: AsRef<Path>>(encrypted: &EncryptedPackage, path: P) -> Result<()> {
let serialized = oxicode::serde::encode_to_vec(encrypted, oxicode::config::standard())
.map_err(|e| TorshError::SerializationError(e.to_string()))?;
fs::write(path, serialized).map_err(|e| TorshError::IoError(e.to_string()))?;
Ok(())
}
pub fn load_encrypted<P: AsRef<Path>>(path: P) -> Result<EncryptedPackage> {
let data = fs::read(path).map_err(|e| TorshError::IoError(e.to_string()))?;
let (encrypted, _) = oxicode::serde::decode_from_slice(&data, oxicode::config::standard())
.map_err(|e| TorshError::SerializationError(e.to_string()))?;
Ok(encrypted)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_package_signer_creation() {
let signer = PackageSigner::new();
assert!(signer.public_key().is_some());
assert!(signer.export_signing_key().is_some());
}
#[test]
fn test_sign_and_verify_package() {
let signer = PackageSigner::new();
let package = Package::new("test".to_string(), "1.0.0".to_string());
let signature = signer.sign_package(&package).unwrap();
assert_eq!(signature.algorithm, SignatureAlgorithm::Ed25519);
let is_valid = signer.verify_package(&package, &signature).unwrap();
assert!(is_valid);
}
#[test]
fn test_signature_fails_on_modified_package() {
let signer = PackageSigner::new();
let mut package = Package::new("test".to_string(), "1.0.0".to_string());
let signature = signer.sign_package(&package).unwrap();
package.add_source_file("new", "new content").unwrap();
let is_valid = signer.verify_package(&package, &signature).unwrap();
assert!(!is_valid);
}
#[test]
fn test_encrypt_decrypt_package() {
let encryptor = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
let package = Package::new("secret".to_string(), "1.0.0".to_string());
let password = "super_secret_password";
let encrypted = encryptor
.encrypt_package_with_password(&package, password)
.unwrap();
assert_eq!(encrypted.algorithm, EncryptionAlgorithm::Aes256Gcm);
let decrypted = encryptor
.decrypt_package_with_password(&encrypted, password)
.unwrap();
assert_eq!(decrypted.name(), package.name());
assert_eq!(decrypted.get_version(), package.get_version());
}
#[test]
fn test_decrypt_with_wrong_password_fails() {
let encryptor = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
let package = Package::new("secret".to_string(), "1.0.0".to_string());
let encrypted = encryptor
.encrypt_package_with_password(&package, "correct_password")
.unwrap();
let result = encryptor.decrypt_package_with_password(&encrypted, "wrong_password");
assert!(result.is_err());
}
#[test]
fn test_chacha20_encryption() {
let encryptor = PackageEncryptor::new(EncryptionAlgorithm::ChaCha20Poly1305);
let package = Package::new("test".to_string(), "1.0.0".to_string());
let password = "test_password";
let encrypted = encryptor
.encrypt_package_with_password(&package, password)
.unwrap();
assert_eq!(encrypted.algorithm, EncryptionAlgorithm::ChaCha20Poly1305);
let decrypted = encryptor
.decrypt_package_with_password(&encrypted, password)
.unwrap();
assert_eq!(decrypted.name(), package.name());
}
#[test]
fn test_trusted_key_verification() {
let signer = PackageSigner::new();
let mut verifier = PackageSigner::verifier_only();
verifier
.add_trusted_key(&signer.public_key().unwrap())
.unwrap();
let package = Package::new("test".to_string(), "1.0.0".to_string());
let signature = signer.sign_package(&package).unwrap();
let is_valid = verifier.verify_package(&package, &signature).unwrap();
assert!(is_valid);
}
#[test]
fn test_untrusted_key_verification_fails() {
let signer = PackageSigner::new();
let mut verifier = PackageSigner::verifier_only();
let other_signer = PackageSigner::new();
verifier
.add_trusted_key(&other_signer.public_key().unwrap())
.unwrap();
let package = Package::new("test".to_string(), "1.0.0".to_string());
let signature = signer.sign_package(&package).unwrap();
let is_valid = verifier.verify_package(&package, &signature).unwrap();
assert!(!is_valid);
}
#[test]
fn test_aes_gcm_roundtrip_and_format() {
let enc = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
let key = [0x11u8; 32];
let plaintext = b"torsh aes-256-gcm payload";
let (ciphertext, nonce) = enc.encrypt_aes_gcm(plaintext, &key).unwrap();
assert_eq!(nonce.len(), AES_GCM_NONCE_LEN);
assert_eq!(ciphertext.len(), plaintext.len() + 16);
let decrypted = enc.decrypt_aes_gcm(&ciphertext, &nonce, &key).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_chacha20_roundtrip_and_format() {
let enc = PackageEncryptor::new(EncryptionAlgorithm::ChaCha20Poly1305);
let key = [0x22u8; 32];
let plaintext = b"torsh chacha20-poly1305 payload";
let (ciphertext, nonce) = enc.encrypt_chacha20(plaintext, &key).unwrap();
assert_eq!(nonce.len(), CHACHA20_NONCE_LEN);
assert_eq!(ciphertext.len(), plaintext.len() + 16);
let decrypted = enc.decrypt_chacha20(&ciphertext, &nonce, &key).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_aes_gcm_tamper_is_rejected() {
let enc = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
let key = [0x33u8; 32];
let plaintext = b"authenticate me";
let (mut ciphertext, nonce) = enc.encrypt_aes_gcm(plaintext, &key).unwrap();
ciphertext[0] ^= 0x01;
assert!(enc.decrypt_aes_gcm(&ciphertext, &nonce, &key).is_err());
}
#[test]
fn test_chacha20_tamper_is_rejected() {
let enc = PackageEncryptor::new(EncryptionAlgorithm::ChaCha20Poly1305);
let key = [0x44u8; 32];
let plaintext = b"authenticate me too";
let (mut ciphertext, nonce) = enc.encrypt_chacha20(plaintext, &key).unwrap();
let last = ciphertext.len() - 1;
ciphertext[last] ^= 0x80;
assert!(enc.decrypt_chacha20(&ciphertext, &nonce, &key).is_err());
}
#[test]
fn test_pbkdf2_known_derivation_is_stable() {
let enc = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
let derive = |salt: &[u8]| enc.derive_key_from_password("password", salt).unwrap();
let k1 = derive(b"salt");
let k2 = derive(b"salt");
assert_eq!(k1, k2, "PBKDF2 must be deterministic for identical inputs");
assert_eq!(k1.len(), 32, "derived key must be 256-bit");
assert_ne!(derive(b"other-salt"), k1, "salt must affect derivation");
let expected = "0394a2ede332c9a13eb82e9b24631604c31df978b4e2f0fbd2c549944f9d79a5";
assert_eq!(
hex::encode(&k1),
expected,
"PBKDF2 production-iteration KAT"
);
}
}