1use std::collections::HashMap;
9use std::fs;
10use std::path::Path;
11
12use aes_gcm::aead::{Aead, KeyInit};
13use aes_gcm::{Aes256Gcm, Nonce as AesGcmNonce};
14use chacha20poly1305::{ChaCha20Poly1305, Nonce as ChaChaNonce};
15use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
16use serde::{Deserialize, Serialize};
17use sha2::{Digest, Sha256};
18use torsh_core::error::{Result, TorshError};
19
20use crate::package::Package;
21
22const PBKDF2_ITERATIONS: u32 = 100_000;
24
25const AES_GCM_NONCE_LEN: usize = 12;
27
28const CHACHA20_NONCE_LEN: usize = 12;
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct PackageSignature {
34 pub algorithm: SignatureAlgorithm,
36 pub signature: Vec<u8>,
38 pub public_key: Vec<u8>,
40 pub timestamp: chrono::DateTime<chrono::Utc>,
42 pub metadata: HashMap<String, String>,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
48pub enum SignatureAlgorithm {
49 Ed25519,
51 Rsa,
53 Ecdsa,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
59pub enum EncryptionAlgorithm {
60 Aes256Gcm,
62 ChaCha20Poly1305,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct EncryptedPackage {
69 pub algorithm: EncryptionAlgorithm,
71 pub encrypted_data: Vec<u8>,
73 pub nonce: Vec<u8>,
75 pub aad: Option<Vec<u8>>,
77 pub key_metadata: HashMap<String, String>,
79 pub timestamp: chrono::DateTime<chrono::Utc>,
81}
82
83pub struct PackageSigner {
85 signing_key: Option<SigningKey>,
86 verifying_keys: Vec<VerifyingKey>,
87 algorithm: SignatureAlgorithm,
88}
89
90pub struct PackageEncryptor {
92 algorithm: EncryptionAlgorithm,
93}
94
95#[derive(Debug, thiserror::Error)]
97pub enum SecurityError {
98 #[error("Signature verification failed: {0}")]
100 InvalidSignature(String),
101 #[error("Encryption error: {0}")]
103 EncryptionError(String),
104 #[error("Key error: {0}")]
106 KeyError(String),
107}
108
109impl PackageSigner {
110 pub fn new() -> Self {
118 let mut secret_bytes = [0u8; 32];
121 if let Err(err) = getrandom::fill(&mut secret_bytes) {
122 panic!("Failed to obtain entropy for signing key generation: {err}");
123 }
124
125 let signing_key = SigningKey::from_bytes(&secret_bytes);
126
127 Self {
128 signing_key: Some(signing_key),
129 verifying_keys: Vec::new(),
130 algorithm: SignatureAlgorithm::Ed25519,
131 }
132 }
133
134 pub fn from_signing_key(signing_key: SigningKey) -> Self {
136 Self {
137 signing_key: Some(signing_key),
138 verifying_keys: Vec::new(),
139 algorithm: SignatureAlgorithm::Ed25519,
140 }
141 }
142
143 pub fn verifier_only() -> Self {
145 Self {
146 signing_key: None,
147 verifying_keys: Vec::new(),
148 algorithm: SignatureAlgorithm::Ed25519,
149 }
150 }
151
152 pub fn add_trusted_key(&mut self, public_key: &[u8]) -> Result<()> {
154 let verifying_key =
155 VerifyingKey::from_bytes(public_key.try_into().map_err(|_| {
156 TorshError::InvalidArgument("Invalid public key length".to_string())
157 })?)
158 .map_err(|e| TorshError::InvalidArgument(format!("Invalid public key: {}", e)))?;
159
160 self.verifying_keys.push(verifying_key);
161 Ok(())
162 }
163
164 pub fn public_key(&self) -> Option<Vec<u8>> {
166 self.signing_key
167 .as_ref()
168 .map(|sk| sk.verifying_key().to_bytes().to_vec())
169 }
170
171 pub fn export_signing_key(&self) -> Option<Vec<u8>> {
173 self.signing_key.as_ref().map(|sk| sk.to_bytes().to_vec())
174 }
175
176 pub fn sign_package(&self, package: &Package) -> Result<PackageSignature> {
178 let signing_key = self
179 .signing_key
180 .as_ref()
181 .ok_or_else(|| TorshError::InvalidArgument("No signing key available".to_string()))?;
182
183 let package_data = self.package_digest(package)?;
185
186 let signature = signing_key.sign(&package_data);
188
189 let mut metadata = HashMap::new();
190 metadata.insert("package_name".to_string(), package.name().to_string());
191 metadata.insert(
192 "package_version".to_string(),
193 package.get_version().to_string(),
194 );
195
196 Ok(PackageSignature {
197 algorithm: self.algorithm,
198 signature: signature.to_bytes().to_vec(),
199 public_key: signing_key.verifying_key().to_bytes().to_vec(),
200 timestamp: chrono::Utc::now(),
201 metadata,
202 })
203 }
204
205 pub fn verify_package(&self, package: &Package, signature: &PackageSignature) -> Result<bool> {
207 if signature.algorithm != self.algorithm {
208 return Err(TorshError::InvalidArgument(format!(
209 "Unsupported signature algorithm: {:?}",
210 signature.algorithm
211 )));
212 }
213
214 let verifying_key =
216 VerifyingKey::from_bytes(signature.public_key.as_slice().try_into().map_err(|_| {
217 TorshError::InvalidArgument("Invalid public key length".to_string())
218 })?)
219 .map_err(|e| TorshError::InvalidArgument(format!("Invalid public key: {}", e)))?;
220
221 if !self.verifying_keys.is_empty()
223 && !self
224 .verifying_keys
225 .iter()
226 .any(|k| k.to_bytes() == verifying_key.to_bytes())
227 {
228 return Ok(false);
229 }
230
231 let package_data = self.package_digest(package)?;
233
234 let sig =
236 Signature::from_bytes(signature.signature.as_slice().try_into().map_err(|_| {
237 TorshError::InvalidArgument("Invalid signature length".to_string())
238 })?);
239
240 match verifying_key.verify(&package_data, &sig) {
242 Ok(()) => Ok(true),
243 Err(_) => Ok(false),
244 }
245 }
246
247 fn package_digest(&self, package: &Package) -> Result<Vec<u8>> {
249 let mut hasher = Sha256::new();
251
252 hasher.update(package.name().as_bytes());
254 hasher.update(package.get_version().as_bytes());
255
256 let mut resource_names: Vec<_> = package.resources().keys().collect();
258 resource_names.sort();
259
260 for name in resource_names {
261 if let Some(resource) = package.resources().get(name) {
262 hasher.update(name.as_bytes());
263 hasher.update(&resource.data);
264 }
265 }
266
267 Ok(hasher.finalize().to_vec())
268 }
269
270 pub fn save_signature<P: AsRef<Path>>(signature: &PackageSignature, path: P) -> Result<()> {
272 let serialized = oxicode::serde::encode_to_vec(signature, oxicode::config::standard())
273 .map_err(|e| TorshError::SerializationError(e.to_string()))?;
274
275 fs::write(path, serialized).map_err(|e| TorshError::IoError(e.to_string()))?;
276 Ok(())
277 }
278
279 pub fn load_signature<P: AsRef<Path>>(path: P) -> Result<PackageSignature> {
281 let data = fs::read(path).map_err(|e| TorshError::IoError(e.to_string()))?;
282
283 let (signature, _) = oxicode::serde::decode_from_slice(&data, oxicode::config::standard())
284 .map_err(|e| TorshError::SerializationError(e.to_string()))?;
285
286 Ok(signature)
287 }
288}
289
290impl Default for PackageSigner {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296impl PackageEncryptor {
297 pub fn new(algorithm: EncryptionAlgorithm) -> Self {
299 Self { algorithm }
300 }
301
302 pub fn encrypt_package_with_password(
304 &self,
305 package: &Package,
306 password: &str,
307 ) -> Result<EncryptedPackage> {
308 let package_data = oxicode::serde::encode_to_vec(package, oxicode::config::standard())
310 .map_err(|e| TorshError::SerializationError(e.to_string()))?;
311
312 let salt = self.generate_salt()?;
314 let key = self.derive_key_from_password(password, &salt)?;
315
316 let (encrypted_data, nonce) = self.encrypt_data(&package_data, &key)?;
318
319 let mut key_metadata = HashMap::new();
320 key_metadata.insert("kdf".to_string(), "pbkdf2".to_string());
321 key_metadata.insert("salt".to_string(), hex::encode(&salt));
322 key_metadata.insert("iterations".to_string(), "100000".to_string());
323
324 Ok(EncryptedPackage {
325 algorithm: self.algorithm,
326 encrypted_data,
327 nonce,
328 aad: None,
329 key_metadata,
330 timestamp: chrono::Utc::now(),
331 })
332 }
333
334 pub fn decrypt_package_with_password(
336 &self,
337 encrypted: &EncryptedPackage,
338 password: &str,
339 ) -> Result<Package> {
340 let salt_hex = encrypted
342 .key_metadata
343 .get("salt")
344 .ok_or_else(|| TorshError::InvalidArgument("Missing salt in metadata".to_string()))?;
345
346 let salt = hex::decode(salt_hex)
347 .map_err(|e| TorshError::InvalidArgument(format!("Invalid salt: {}", e)))?;
348
349 let key = self.derive_key_from_password(password, &salt)?;
351
352 let decrypted_data =
354 self.decrypt_data(&encrypted.encrypted_data, &encrypted.nonce, &key)?;
355
356 let (package, _) =
358 oxicode::serde::decode_from_slice(&decrypted_data, oxicode::config::standard())
359 .map_err(|e| TorshError::SerializationError(e.to_string()))?;
360
361 Ok(package)
362 }
363
364 fn encrypt_data(&self, data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
366 match self.algorithm {
367 EncryptionAlgorithm::Aes256Gcm => self.encrypt_aes_gcm(data, key),
368 EncryptionAlgorithm::ChaCha20Poly1305 => self.encrypt_chacha20(data, key),
369 }
370 }
371
372 fn decrypt_data(&self, encrypted: &[u8], nonce: &[u8], key: &[u8]) -> Result<Vec<u8>> {
374 match self.algorithm {
375 EncryptionAlgorithm::Aes256Gcm => self.decrypt_aes_gcm(encrypted, nonce, key),
376 EncryptionAlgorithm::ChaCha20Poly1305 => self.decrypt_chacha20(encrypted, nonce, key),
377 }
378 }
379
380 fn encrypt_aes_gcm(&self, data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
386 let cipher = Aes256Gcm::new_from_slice(key)
387 .map_err(|_| TorshError::InvalidArgument("Invalid key for AES-256-GCM".to_string()))?;
388
389 let nonce_bytes = self.generate_nonce(AES_GCM_NONCE_LEN)?;
390 let nonce = AesGcmNonce::try_from(nonce_bytes.as_slice())
391 .map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
392
393 let ciphertext = cipher
394 .encrypt(&nonce, data)
395 .map_err(|_| TorshError::InvalidArgument("Encryption failed".to_string()))?;
396
397 Ok((ciphertext, nonce_bytes))
398 }
399
400 fn decrypt_aes_gcm(&self, encrypted: &[u8], nonce: &[u8], key: &[u8]) -> Result<Vec<u8>> {
402 let cipher = Aes256Gcm::new_from_slice(key)
403 .map_err(|_| TorshError::InvalidArgument("Invalid key for AES-256-GCM".to_string()))?;
404
405 let nonce = AesGcmNonce::try_from(nonce)
406 .map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
407
408 cipher
409 .decrypt(&nonce, encrypted)
410 .map_err(|_| TorshError::InvalidArgument("Decryption failed".to_string()))
411 }
412
413 fn encrypt_chacha20(&self, data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>)> {
419 let cipher = ChaCha20Poly1305::new_from_slice(key).map_err(|_| {
420 TorshError::InvalidArgument("Invalid key for ChaCha20-Poly1305".to_string())
421 })?;
422
423 let nonce_bytes = self.generate_nonce(CHACHA20_NONCE_LEN)?;
424 let nonce = ChaChaNonce::try_from(nonce_bytes.as_slice())
425 .map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
426
427 let ciphertext = cipher
428 .encrypt(&nonce, data)
429 .map_err(|_| TorshError::InvalidArgument("Encryption failed".to_string()))?;
430
431 Ok((ciphertext, nonce_bytes))
432 }
433
434 fn decrypt_chacha20(&self, encrypted: &[u8], nonce: &[u8], key: &[u8]) -> Result<Vec<u8>> {
436 let cipher = ChaCha20Poly1305::new_from_slice(key).map_err(|_| {
437 TorshError::InvalidArgument("Invalid key for ChaCha20-Poly1305".to_string())
438 })?;
439
440 let nonce = ChaChaNonce::try_from(nonce)
441 .map_err(|_| TorshError::InvalidArgument("Invalid nonce".to_string()))?;
442
443 cipher
444 .decrypt(&nonce, encrypted)
445 .map_err(|_| TorshError::InvalidArgument("Decryption failed".to_string()))
446 }
447
448 fn derive_key_from_password(&self, password: &str, salt: &[u8]) -> Result<Vec<u8>> {
454 use pbkdf2::pbkdf2_hmac;
455
456 let mut key = vec![0u8; 32]; pbkdf2_hmac::<Sha256>(password.as_bytes(), salt, PBKDF2_ITERATIONS, &mut key);
460
461 Ok(key)
462 }
463
464 fn generate_salt(&self) -> Result<Vec<u8>> {
466 let mut salt = vec![0u8; 32];
467 getrandom::fill(&mut salt)
468 .map_err(|e| TorshError::InvalidArgument(format!("Failed to generate salt: {e}")))?;
469 Ok(salt)
470 }
471
472 fn generate_nonce(&self, len: usize) -> Result<Vec<u8>> {
474 let mut nonce = vec![0u8; len];
475 getrandom::fill(&mut nonce)
476 .map_err(|e| TorshError::InvalidArgument(format!("Failed to generate nonce: {e}")))?;
477 Ok(nonce)
478 }
479
480 pub fn save_encrypted<P: AsRef<Path>>(encrypted: &EncryptedPackage, path: P) -> Result<()> {
482 let serialized = oxicode::serde::encode_to_vec(encrypted, oxicode::config::standard())
483 .map_err(|e| TorshError::SerializationError(e.to_string()))?;
484
485 fs::write(path, serialized).map_err(|e| TorshError::IoError(e.to_string()))?;
486 Ok(())
487 }
488
489 pub fn load_encrypted<P: AsRef<Path>>(path: P) -> Result<EncryptedPackage> {
491 let data = fs::read(path).map_err(|e| TorshError::IoError(e.to_string()))?;
492
493 let (encrypted, _) = oxicode::serde::decode_from_slice(&data, oxicode::config::standard())
494 .map_err(|e| TorshError::SerializationError(e.to_string()))?;
495
496 Ok(encrypted)
497 }
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503
504 #[test]
505 fn test_package_signer_creation() {
506 let signer = PackageSigner::new();
507 assert!(signer.public_key().is_some());
508 assert!(signer.export_signing_key().is_some());
509 }
510
511 #[test]
512 fn test_sign_and_verify_package() {
513 let signer = PackageSigner::new();
514 let package = Package::new("test".to_string(), "1.0.0".to_string());
515
516 let signature = signer.sign_package(&package).unwrap();
517 assert_eq!(signature.algorithm, SignatureAlgorithm::Ed25519);
518
519 let is_valid = signer.verify_package(&package, &signature).unwrap();
520 assert!(is_valid);
521 }
522
523 #[test]
524 fn test_signature_fails_on_modified_package() {
525 let signer = PackageSigner::new();
526 let mut package = Package::new("test".to_string(), "1.0.0".to_string());
527
528 let signature = signer.sign_package(&package).unwrap();
529
530 package.add_source_file("new", "new content").unwrap();
532
533 let is_valid = signer.verify_package(&package, &signature).unwrap();
534 assert!(!is_valid);
535 }
536
537 #[test]
538 fn test_encrypt_decrypt_package() {
539 let encryptor = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
540 let package = Package::new("secret".to_string(), "1.0.0".to_string());
541 let password = "super_secret_password";
542
543 let encrypted = encryptor
544 .encrypt_package_with_password(&package, password)
545 .unwrap();
546 assert_eq!(encrypted.algorithm, EncryptionAlgorithm::Aes256Gcm);
547
548 let decrypted = encryptor
549 .decrypt_package_with_password(&encrypted, password)
550 .unwrap();
551 assert_eq!(decrypted.name(), package.name());
552 assert_eq!(decrypted.get_version(), package.get_version());
553 }
554
555 #[test]
556 fn test_decrypt_with_wrong_password_fails() {
557 let encryptor = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
558 let package = Package::new("secret".to_string(), "1.0.0".to_string());
559
560 let encrypted = encryptor
561 .encrypt_package_with_password(&package, "correct_password")
562 .unwrap();
563
564 let result = encryptor.decrypt_package_with_password(&encrypted, "wrong_password");
565 assert!(result.is_err());
566 }
567
568 #[test]
569 fn test_chacha20_encryption() {
570 let encryptor = PackageEncryptor::new(EncryptionAlgorithm::ChaCha20Poly1305);
571 let package = Package::new("test".to_string(), "1.0.0".to_string());
572 let password = "test_password";
573
574 let encrypted = encryptor
575 .encrypt_package_with_password(&package, password)
576 .unwrap();
577 assert_eq!(encrypted.algorithm, EncryptionAlgorithm::ChaCha20Poly1305);
578
579 let decrypted = encryptor
580 .decrypt_package_with_password(&encrypted, password)
581 .unwrap();
582 assert_eq!(decrypted.name(), package.name());
583 }
584
585 #[test]
586 fn test_trusted_key_verification() {
587 let signer = PackageSigner::new();
588 let mut verifier = PackageSigner::verifier_only();
589
590 verifier
592 .add_trusted_key(&signer.public_key().unwrap())
593 .unwrap();
594
595 let package = Package::new("test".to_string(), "1.0.0".to_string());
596 let signature = signer.sign_package(&package).unwrap();
597
598 let is_valid = verifier.verify_package(&package, &signature).unwrap();
599 assert!(is_valid);
600 }
601
602 #[test]
603 fn test_untrusted_key_verification_fails() {
604 let signer = PackageSigner::new();
605 let mut verifier = PackageSigner::verifier_only();
606
607 let other_signer = PackageSigner::new();
609 verifier
610 .add_trusted_key(&other_signer.public_key().unwrap())
611 .unwrap();
612
613 let package = Package::new("test".to_string(), "1.0.0".to_string());
614 let signature = signer.sign_package(&package).unwrap();
615
616 let is_valid = verifier.verify_package(&package, &signature).unwrap();
617 assert!(!is_valid);
618 }
619
620 #[test]
621 fn test_aes_gcm_roundtrip_and_format() {
622 let enc = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
623 let key = [0x11u8; 32];
624 let plaintext = b"torsh aes-256-gcm payload";
625
626 let (ciphertext, nonce) = enc.encrypt_aes_gcm(plaintext, &key).unwrap();
627 assert_eq!(nonce.len(), AES_GCM_NONCE_LEN);
630 assert_eq!(ciphertext.len(), plaintext.len() + 16);
631
632 let decrypted = enc.decrypt_aes_gcm(&ciphertext, &nonce, &key).unwrap();
633 assert_eq!(decrypted, plaintext);
634 }
635
636 #[test]
637 fn test_chacha20_roundtrip_and_format() {
638 let enc = PackageEncryptor::new(EncryptionAlgorithm::ChaCha20Poly1305);
639 let key = [0x22u8; 32];
640 let plaintext = b"torsh chacha20-poly1305 payload";
641
642 let (ciphertext, nonce) = enc.encrypt_chacha20(plaintext, &key).unwrap();
643 assert_eq!(nonce.len(), CHACHA20_NONCE_LEN);
644 assert_eq!(ciphertext.len(), plaintext.len() + 16);
645
646 let decrypted = enc.decrypt_chacha20(&ciphertext, &nonce, &key).unwrap();
647 assert_eq!(decrypted, plaintext);
648 }
649
650 #[test]
651 fn test_aes_gcm_tamper_is_rejected() {
652 let enc = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
653 let key = [0x33u8; 32];
654 let plaintext = b"authenticate me";
655
656 let (mut ciphertext, nonce) = enc.encrypt_aes_gcm(plaintext, &key).unwrap();
657 ciphertext[0] ^= 0x01;
659 assert!(enc.decrypt_aes_gcm(&ciphertext, &nonce, &key).is_err());
660 }
661
662 #[test]
663 fn test_chacha20_tamper_is_rejected() {
664 let enc = PackageEncryptor::new(EncryptionAlgorithm::ChaCha20Poly1305);
665 let key = [0x44u8; 32];
666 let plaintext = b"authenticate me too";
667
668 let (mut ciphertext, nonce) = enc.encrypt_chacha20(plaintext, &key).unwrap();
669 let last = ciphertext.len() - 1;
671 ciphertext[last] ^= 0x80;
672 assert!(enc.decrypt_chacha20(&ciphertext, &nonce, &key).is_err());
673 }
674
675 #[test]
676 fn test_pbkdf2_known_derivation_is_stable() {
677 let enc = PackageEncryptor::new(EncryptionAlgorithm::Aes256Gcm);
678
679 let derive = |salt: &[u8]| enc.derive_key_from_password("password", salt).unwrap();
684
685 let k1 = derive(b"salt");
689 let k2 = derive(b"salt");
690 assert_eq!(k1, k2, "PBKDF2 must be deterministic for identical inputs");
691 assert_eq!(k1.len(), 32, "derived key must be 256-bit");
692 assert_ne!(derive(b"other-salt"), k1, "salt must affect derivation");
693
694 let expected = "0394a2ede332c9a13eb82e9b24631604c31df978b4e2f0fbd2c549944f9d79a5";
697 assert_eq!(
698 hex::encode(&k1),
699 expected,
700 "PBKDF2 production-iteration KAT"
701 );
702 }
703}