Skip to main content

torsh_package/
security.rs

1//! Security features for package signing and encryption
2//!
3//! This module provides cryptographic security features including:
4//! - Digital signatures for package integrity and authenticity
5//! - Encryption for sensitive model packages
6//! - Key management and verification
7
8use std::collections::HashMap;
9use std::fs;
10use std::path::Path;
11
12use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
13use ring::aead;
14use serde::{Deserialize, Serialize};
15use sha2::{Digest, Sha256};
16use torsh_core::error::{Result, TorshError};
17
18use crate::package::Package;
19
20/// Package signature information
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct PackageSignature {
23    /// Signature algorithm used
24    pub algorithm: SignatureAlgorithm,
25    /// Signature data
26    pub signature: Vec<u8>,
27    /// Public key for verification
28    pub public_key: Vec<u8>,
29    /// Timestamp when signature was created
30    pub timestamp: chrono::DateTime<chrono::Utc>,
31    /// Additional signature metadata
32    pub metadata: HashMap<String, String>,
33}
34
35/// Supported signature algorithms
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub enum SignatureAlgorithm {
38    /// Ed25519 digital signature
39    Ed25519,
40    /// RSA signature (future support)
41    Rsa,
42    /// ECDSA signature (future support)
43    Ecdsa,
44}
45
46/// Encryption algorithm
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
48pub enum EncryptionAlgorithm {
49    /// AES-256-GCM authenticated encryption
50    Aes256Gcm,
51    /// ChaCha20-Poly1305 authenticated encryption
52    ChaCha20Poly1305,
53}
54
55/// Encrypted package container
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct EncryptedPackage {
58    /// Encryption algorithm used
59    pub algorithm: EncryptionAlgorithm,
60    /// Encrypted package data
61    pub encrypted_data: Vec<u8>,
62    /// Nonce/IV for decryption
63    pub nonce: Vec<u8>,
64    /// Additional authenticated data (AAD)
65    pub aad: Option<Vec<u8>>,
66    /// Key derivation metadata
67    pub key_metadata: HashMap<String, String>,
68    /// Timestamp of encryption
69    pub timestamp: chrono::DateTime<chrono::Utc>,
70}
71
72/// Package signer for creating and verifying signatures
73pub struct PackageSigner {
74    signing_key: Option<SigningKey>,
75    verifying_keys: Vec<VerifyingKey>,
76    algorithm: SignatureAlgorithm,
77}
78
79/// Package encryptor for encrypting and decrypting packages
80pub struct PackageEncryptor {
81    algorithm: EncryptionAlgorithm,
82}
83
84/// Security error types
85#[derive(Debug, thiserror::Error)]
86pub enum SecurityError {
87    /// Signature verification failed
88    #[error("Signature verification failed: {0}")]
89    InvalidSignature(String),
90    /// Encryption/decryption error
91    #[error("Encryption error: {0}")]
92    EncryptionError(String),
93    /// Key management error
94    #[error("Key error: {0}")]
95    KeyError(String),
96}
97
98impl PackageSigner {
99    /// Create a new package signer with generated key pair
100    pub fn new() -> Self {
101        // Generate random bytes for the signing key
102        use ring::rand::{SecureRandom, SystemRandom};
103        let rng = SystemRandom::new();
104        let mut secret_bytes = [0u8; 32];
105        rng.fill(&mut secret_bytes)
106            .expect("Failed to generate random key");
107
108        let signing_key = SigningKey::from_bytes(&secret_bytes);
109
110        Self {
111            signing_key: Some(signing_key),
112            verifying_keys: Vec::new(),
113            algorithm: SignatureAlgorithm::Ed25519,
114        }
115    }
116
117    /// Create a signer from an existing signing key
118    pub fn from_signing_key(signing_key: SigningKey) -> Self {
119        Self {
120            signing_key: Some(signing_key),
121            verifying_keys: Vec::new(),
122            algorithm: SignatureAlgorithm::Ed25519,
123        }
124    }
125
126    /// Create a verifier (no signing capability)
127    pub fn verifier_only() -> Self {
128        Self {
129            signing_key: None,
130            verifying_keys: Vec::new(),
131            algorithm: SignatureAlgorithm::Ed25519,
132        }
133    }
134
135    /// Add a trusted public key for verification
136    pub fn add_trusted_key(&mut self, public_key: &[u8]) -> Result<()> {
137        let verifying_key =
138            VerifyingKey::from_bytes(public_key.try_into().map_err(|_| {
139                TorshError::InvalidArgument("Invalid public key length".to_string())
140            })?)
141            .map_err(|e| TorshError::InvalidArgument(format!("Invalid public key: {}", e)))?;
142
143        self.verifying_keys.push(verifying_key);
144        Ok(())
145    }
146
147    /// Get public key for verification
148    pub fn public_key(&self) -> Option<Vec<u8>> {
149        self.signing_key
150            .as_ref()
151            .map(|sk| sk.verifying_key().to_bytes().to_vec())
152    }
153
154    /// Export signing key (use with caution!)
155    pub fn export_signing_key(&self) -> Option<Vec<u8>> {
156        self.signing_key.as_ref().map(|sk| sk.to_bytes().to_vec())
157    }
158
159    /// Sign a package
160    pub fn sign_package(&self, package: &Package) -> Result<PackageSignature> {
161        let signing_key = self
162            .signing_key
163            .as_ref()
164            .ok_or_else(|| TorshError::InvalidArgument("No signing key available".to_string()))?;
165
166        // Serialize package for signing
167        let package_data = self.package_digest(package)?;
168
169        // Create signature
170        let signature = signing_key.sign(&package_data);
171
172        let mut metadata = HashMap::new();
173        metadata.insert("package_name".to_string(), package.name().to_string());
174        metadata.insert(
175            "package_version".to_string(),
176            package.get_version().to_string(),
177        );
178
179        Ok(PackageSignature {
180            algorithm: self.algorithm,
181            signature: signature.to_bytes().to_vec(),
182            public_key: signing_key.verifying_key().to_bytes().to_vec(),
183            timestamp: chrono::Utc::now(),
184            metadata,
185        })
186    }
187
188    /// Verify package signature
189    pub fn verify_package(&self, package: &Package, signature: &PackageSignature) -> Result<bool> {
190        if signature.algorithm != self.algorithm {
191            return Err(TorshError::InvalidArgument(format!(
192                "Unsupported signature algorithm: {:?}",
193                signature.algorithm
194            )));
195        }
196
197        // Get verifying key from signature
198        let verifying_key =
199            VerifyingKey::from_bytes(signature.public_key.as_slice().try_into().map_err(|_| {
200                TorshError::InvalidArgument("Invalid public key length".to_string())
201            })?)
202            .map_err(|e| TorshError::InvalidArgument(format!("Invalid public key: {}", e)))?;
203
204        // Check if the key is trusted
205        if !self.verifying_keys.is_empty()
206            && !self
207                .verifying_keys
208                .iter()
209                .any(|k| k.to_bytes() == verifying_key.to_bytes())
210        {
211            return Ok(false);
212        }
213
214        // Compute package digest
215        let package_data = self.package_digest(package)?;
216
217        // Parse signature
218        let sig =
219            Signature::from_bytes(signature.signature.as_slice().try_into().map_err(|_| {
220                TorshError::InvalidArgument("Invalid signature length".to_string())
221            })?);
222
223        // Verify signature
224        match verifying_key.verify(&package_data, &sig) {
225            Ok(()) => Ok(true),
226            Err(_) => Ok(false),
227        }
228    }
229
230    /// Compute package digest for signing
231    fn package_digest(&self, package: &Package) -> Result<Vec<u8>> {
232        // Create a deterministic digest of the package
233        let mut hasher = Sha256::new();
234
235        // Hash package metadata
236        hasher.update(package.name().as_bytes());
237        hasher.update(package.get_version().as_bytes());
238
239        // Hash resources in sorted order for determinism
240        let mut resource_names: Vec<_> = package.resources().keys().collect();
241        resource_names.sort();
242
243        for name in resource_names {
244            if let Some(resource) = package.resources().get(name) {
245                hasher.update(name.as_bytes());
246                hasher.update(&resource.data);
247            }
248        }
249
250        Ok(hasher.finalize().to_vec())
251    }
252
253    /// Save signature to file
254    pub fn save_signature<P: AsRef<Path>>(signature: &PackageSignature, path: P) -> Result<()> {
255        let serialized = oxicode::serde::encode_to_vec(signature, oxicode::config::standard())
256            .map_err(|e| TorshError::SerializationError(e.to_string()))?;
257
258        fs::write(path, serialized).map_err(|e| TorshError::IoError(e.to_string()))?;
259        Ok(())
260    }
261
262    /// Load signature from file
263    pub fn load_signature<P: AsRef<Path>>(path: P) -> Result<PackageSignature> {
264        let data = fs::read(path).map_err(|e| TorshError::IoError(e.to_string()))?;
265
266        let (signature, _) = oxicode::serde::decode_from_slice(&data, oxicode::config::standard())
267            .map_err(|e| TorshError::SerializationError(e.to_string()))?;
268
269        Ok(signature)
270    }
271}
272
273impl Default for PackageSigner {
274    fn default() -> Self {
275        Self::new()
276    }
277}
278
279impl PackageEncryptor {
280    /// Create a new package encryptor
281    pub fn new(algorithm: EncryptionAlgorithm) -> Self {
282        Self { algorithm }
283    }
284
285    /// Encrypt a package with a password
286    pub fn encrypt_package_with_password(
287        &self,
288        package: &Package,
289        password: &str,
290    ) -> Result<EncryptedPackage> {
291        // Serialize package
292        let package_data = oxicode::serde::encode_to_vec(package, oxicode::config::standard())
293            .map_err(|e| TorshError::SerializationError(e.to_string()))?;
294
295        // Derive key from password using PBKDF2
296        let salt = self.generate_salt();
297        let key = self.derive_key_from_password(password, &salt)?;
298
299        // Encrypt data
300        let (encrypted_data, nonce) = self.encrypt_data(&package_data, &key)?;
301
302        let mut key_metadata = HashMap::new();
303        key_metadata.insert("kdf".to_string(), "pbkdf2".to_string());
304        key_metadata.insert("salt".to_string(), hex::encode(&salt));
305        key_metadata.insert("iterations".to_string(), "100000".to_string());
306
307        Ok(EncryptedPackage {
308            algorithm: self.algorithm,
309            encrypted_data,
310            nonce,
311            aad: None,
312            key_metadata,
313            timestamp: chrono::Utc::now(),
314        })
315    }
316
317    /// Decrypt a package with a password
318    pub fn decrypt_package_with_password(
319        &self,
320        encrypted: &EncryptedPackage,
321        password: &str,
322    ) -> Result<Package> {
323        // Extract salt from metadata
324        let salt_hex = encrypted
325            .key_metadata
326            .get("salt")
327            .ok_or_else(|| TorshError::InvalidArgument("Missing salt in metadata".to_string()))?;
328
329        let salt = hex::decode(salt_hex)
330            .map_err(|e| TorshError::InvalidArgument(format!("Invalid salt: {}", e)))?;
331
332        // Derive key from password
333        let key = self.derive_key_from_password(password, &salt)?;
334
335        // Decrypt data
336        let decrypted_data =
337            self.decrypt_data(&encrypted.encrypted_data, &encrypted.nonce, &key)?;
338
339        // Deserialize package
340        let (package, _) =
341            oxicode::serde::decode_from_slice(&decrypted_data, oxicode::config::standard())
342                .map_err(|e| TorshError::SerializationError(e.to_string()))?;
343
344        Ok(package)
345    }
346
347    /// Encrypt data with AES-256-GCM
348    fn encrypt_data(&self, data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
349        match self.algorithm {
350            EncryptionAlgorithm::Aes256Gcm => self.encrypt_aes_gcm(data, key),
351            EncryptionAlgorithm::ChaCha20Poly1305 => self.encrypt_chacha20(data, key),
352        }
353    }
354
355    /// Decrypt data
356    fn decrypt_data(&self, encrypted: &[u8], nonce: &[u8], key: &[u8]) -> Result<Vec<u8>> {
357        match self.algorithm {
358            EncryptionAlgorithm::Aes256Gcm => self.decrypt_aes_gcm(encrypted, nonce, key),
359            EncryptionAlgorithm::ChaCha20Poly1305 => self.decrypt_chacha20(encrypted, nonce, key),
360        }
361    }
362
363    /// Encrypt with AES-256-GCM
364    fn encrypt_aes_gcm(&self, data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
365        let unbound_key = aead::UnboundKey::new(&aead::AES_256_GCM, key)
366            .map_err(|_| TorshError::InvalidArgument("Invalid key for AES-256-GCM".to_string()))?;
367
368        let nonce_bytes = self.generate_nonce(aead::NONCE_LEN);
369        let nonce = aead::Nonce::try_assume_unique_for_key(&nonce_bytes)
370            .map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
371
372        let sealing_key = aead::LessSafeKey::new(unbound_key);
373
374        let mut in_out = data.to_vec();
375        sealing_key
376            .seal_in_place_append_tag(nonce, aead::Aad::empty(), &mut in_out)
377            .map_err(|_| TorshError::InvalidArgument("Encryption failed".to_string()))?;
378
379        Ok((in_out, nonce_bytes))
380    }
381
382    /// Decrypt with AES-256-GCM
383    fn decrypt_aes_gcm(&self, encrypted: &[u8], nonce: &[u8], key: &[u8]) -> Result<Vec<u8>> {
384        let unbound_key = aead::UnboundKey::new(&aead::AES_256_GCM, key)
385            .map_err(|_| TorshError::InvalidArgument("Invalid key for AES-256-GCM".to_string()))?;
386
387        let nonce = aead::Nonce::try_assume_unique_for_key(nonce)
388            .map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
389
390        let opening_key = aead::LessSafeKey::new(unbound_key);
391
392        let mut in_out = encrypted.to_vec();
393        let decrypted = opening_key
394            .open_in_place(nonce, aead::Aad::empty(), &mut in_out)
395            .map_err(|_| TorshError::InvalidArgument("Decryption failed".to_string()))?;
396
397        Ok(decrypted.to_vec())
398    }
399
400    /// Encrypt with ChaCha20-Poly1305
401    fn encrypt_chacha20(&self, data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
402        let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, key).map_err(|_| {
403            TorshError::InvalidArgument("Invalid key for ChaCha20-Poly1305".to_string())
404        })?;
405
406        let nonce_bytes = self.generate_nonce(aead::NONCE_LEN);
407        let nonce = aead::Nonce::try_assume_unique_for_key(&nonce_bytes)
408            .map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
409
410        let sealing_key = aead::LessSafeKey::new(unbound_key);
411
412        let mut in_out = data.to_vec();
413        sealing_key
414            .seal_in_place_append_tag(nonce, aead::Aad::empty(), &mut in_out)
415            .map_err(|_| TorshError::InvalidArgument("Encryption failed".to_string()))?;
416
417        Ok((in_out, nonce_bytes))
418    }
419
420    /// Decrypt with ChaCha20-Poly1305
421    fn decrypt_chacha20(&self, encrypted: &[u8], nonce: &[u8], key: &[u8]) -> Result<Vec<u8>> {
422        let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, key).map_err(|_| {
423            TorshError::InvalidArgument("Invalid key for ChaCha20-Poly1305".to_string())
424        })?;
425
426        let nonce = aead::Nonce::try_assume_unique_for_key(nonce)
427            .map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
428
429        let opening_key = aead::LessSafeKey::new(unbound_key);
430
431        let mut in_out = encrypted.to_vec();
432        let decrypted = opening_key
433            .open_in_place(nonce, aead::Aad::empty(), &mut in_out)
434            .map_err(|_| TorshError::InvalidArgument("Decryption failed".to_string()))?;
435
436        Ok(decrypted.to_vec())
437    }
438
439    /// Derive encryption key from password using PBKDF2
440    fn derive_key_from_password(&self, password: &str, salt: &[u8]) -> Result<Vec<u8>> {
441        use ring::pbkdf2;
442
443        let iterations =
444            std::num::NonZeroU32::new(100_000).expect("100_000 is a valid non-zero u32");
445        let mut key = vec![0u8; 32]; // 256-bit key
446
447        pbkdf2::derive(
448            pbkdf2::PBKDF2_HMAC_SHA256,
449            iterations,
450            salt,
451            password.as_bytes(),
452            &mut key,
453        );
454
455        Ok(key)
456    }
457
458    /// Generate random salt
459    fn generate_salt(&self) -> Vec<u8> {
460        use ring::rand::{SecureRandom, SystemRandom};
461
462        let rng = SystemRandom::new();
463        let mut salt = vec![0u8; 32];
464        rng.fill(&mut salt).expect("Failed to generate salt");
465        salt
466    }
467
468    /// Generate random nonce
469    fn generate_nonce(&self, len: usize) -> Vec<u8> {
470        use ring::rand::{SecureRandom, SystemRandom};
471
472        let rng = SystemRandom::new();
473        let mut nonce = vec![0u8; len];
474        rng.fill(&mut nonce).expect("Failed to generate nonce");
475        nonce
476    }
477
478    /// Save encrypted package to file
479    pub fn save_encrypted<P: AsRef<Path>>(encrypted: &EncryptedPackage, path: P) -> Result<()> {
480        let serialized = oxicode::serde::encode_to_vec(encrypted, oxicode::config::standard())
481            .map_err(|e| TorshError::SerializationError(e.to_string()))?;
482
483        fs::write(path, serialized).map_err(|e| TorshError::IoError(e.to_string()))?;
484        Ok(())
485    }
486
487    /// Load encrypted package from file
488    pub fn load_encrypted<P: AsRef<Path>>(path: P) -> Result<EncryptedPackage> {
489        let data = fs::read(path).map_err(|e| TorshError::IoError(e.to_string()))?;
490
491        let (encrypted, _) = oxicode::serde::decode_from_slice(&data, oxicode::config::standard())
492            .map_err(|e| TorshError::SerializationError(e.to_string()))?;
493
494        Ok(encrypted)
495    }
496}
497
498#[cfg(test)]
499mod tests {
500    use super::*;
501
502    #[test]
503    fn test_package_signer_creation() {
504        let signer = PackageSigner::new();
505        assert!(signer.public_key().is_some());
506        assert!(signer.export_signing_key().is_some());
507    }
508
509    #[test]
510    fn test_sign_and_verify_package() {
511        let signer = PackageSigner::new();
512        let package = Package::new("test".to_string(), "1.0.0".to_string());
513
514        let signature = signer.sign_package(&package).unwrap();
515        assert_eq!(signature.algorithm, SignatureAlgorithm::Ed25519);
516
517        let is_valid = signer.verify_package(&package, &signature).unwrap();
518        assert!(is_valid);
519    }
520
521    #[test]
522    fn test_signature_fails_on_modified_package() {
523        let signer = PackageSigner::new();
524        let mut package = Package::new("test".to_string(), "1.0.0".to_string());
525
526        let signature = signer.sign_package(&package).unwrap();
527
528        // Modify package
529        package.add_source_file("new", "new content").unwrap();
530
531        let is_valid = signer.verify_package(&package, &signature).unwrap();
532        assert!(!is_valid);
533    }
534
535    #[test]
536    fn test_encrypt_decrypt_package() {
537        let encryptor = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
538        let package = Package::new("secret".to_string(), "1.0.0".to_string());
539        let password = "super_secret_password";
540
541        let encrypted = encryptor
542            .encrypt_package_with_password(&package, password)
543            .unwrap();
544        assert_eq!(encrypted.algorithm, EncryptionAlgorithm::Aes256Gcm);
545
546        let decrypted = encryptor
547            .decrypt_package_with_password(&encrypted, password)
548            .unwrap();
549        assert_eq!(decrypted.name(), package.name());
550        assert_eq!(decrypted.get_version(), package.get_version());
551    }
552
553    #[test]
554    fn test_decrypt_with_wrong_password_fails() {
555        let encryptor = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
556        let package = Package::new("secret".to_string(), "1.0.0".to_string());
557
558        let encrypted = encryptor
559            .encrypt_package_with_password(&package, "correct_password")
560            .unwrap();
561
562        let result = encryptor.decrypt_package_with_password(&encrypted, "wrong_password");
563        assert!(result.is_err());
564    }
565
566    #[test]
567    fn test_chacha20_encryption() {
568        let encryptor = PackageEncryptor::new(EncryptionAlgorithm::ChaCha20Poly1305);
569        let package = Package::new("test".to_string(), "1.0.0".to_string());
570        let password = "test_password";
571
572        let encrypted = encryptor
573            .encrypt_package_with_password(&package, password)
574            .unwrap();
575        assert_eq!(encrypted.algorithm, EncryptionAlgorithm::ChaCha20Poly1305);
576
577        let decrypted = encryptor
578            .decrypt_package_with_password(&encrypted, password)
579            .unwrap();
580        assert_eq!(decrypted.name(), package.name());
581    }
582
583    #[test]
584    fn test_trusted_key_verification() {
585        let signer = PackageSigner::new();
586        let mut verifier = PackageSigner::verifier_only();
587
588        // Add the signer's public key as trusted
589        verifier
590            .add_trusted_key(&signer.public_key().unwrap())
591            .unwrap();
592
593        let package = Package::new("test".to_string(), "1.0.0".to_string());
594        let signature = signer.sign_package(&package).unwrap();
595
596        let is_valid = verifier.verify_package(&package, &signature).unwrap();
597        assert!(is_valid);
598    }
599
600    #[test]
601    fn test_untrusted_key_verification_fails() {
602        let signer = PackageSigner::new();
603        let mut verifier = PackageSigner::verifier_only();
604
605        // Add a different public key as trusted
606        let other_signer = PackageSigner::new();
607        verifier
608            .add_trusted_key(&other_signer.public_key().unwrap())
609            .unwrap();
610
611        let package = Package::new("test".to_string(), "1.0.0".to_string());
612        let signature = signer.sign_package(&package).unwrap();
613
614        let is_valid = verifier.verify_package(&package, &signature).unwrap();
615        assert!(!is_valid);
616    }
617}