1use serde::{Deserialize, Serialize};
7
8use crate::error::{encryption_error, invalid_manifest};
9use crate::Result;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, strum::Display)]
13pub enum EncryptionAlgorithm {
14 #[serde(rename = "AES-256-GCM")]
16 #[strum(serialize = "AES-256-GCM")]
17 Aes256Gcm,
18 #[serde(rename = "ChaCha20-Poly1305")]
20 #[strum(serialize = "ChaCha20-Poly1305")]
21 ChaCha20Poly1305,
22}
23
24impl EncryptionAlgorithm {
25 #[must_use]
27 pub const fn as_str(&self) -> &'static str {
28 match self {
29 Self::Aes256Gcm => "AES-256-GCM",
30 Self::ChaCha20Poly1305 => "ChaCha20-Poly1305",
31 }
32 }
33
34 #[must_use]
36 pub const fn key_size(&self) -> usize {
37 match self {
38 Self::Aes256Gcm | Self::ChaCha20Poly1305 => 32,
40 }
41 }
42
43 #[must_use]
45 pub const fn nonce_size(&self) -> usize {
46 match self {
47 Self::Aes256Gcm | Self::ChaCha20Poly1305 => 12,
49 }
50 }
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct EncryptionMetadata {
57 pub algorithm: EncryptionAlgorithm,
59
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub kdf: Option<KeyDerivation>,
63
64 #[serde(default, skip_serializing_if = "Option::is_none")]
66 pub wrapped_key: Option<String>,
67
68 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub key_management: Option<KeyManagementAlgorithm>,
71
72 #[serde(default, skip_serializing_if = "Vec::is_empty")]
74 pub recipients: Vec<Recipient>,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub enum KeyManagementAlgorithm {
80 #[serde(rename = "ECDH-ES+A256KW")]
82 EcdhEsA256kw,
83 #[serde(rename = "RSA-OAEP-256")]
85 RsaOaep256,
86 #[serde(rename = "PBES2-HS256+A256KW")]
88 Pbes2HsA256kw,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct KeyDerivation {
95 pub algorithm: KdfAlgorithm,
97
98 pub salt: String,
100
101 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub iterations: Option<u32>,
104
105 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub memory: Option<u32>,
108
109 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub parallelism: Option<u32>,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
116pub enum KdfAlgorithm {
117 #[serde(rename = "PBKDF2-SHA256")]
119 Pbkdf2Sha256,
120 Argon2id,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct Recipient {
128 pub id: String,
130
131 pub encrypted_key: String,
133
134 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub algorithm: Option<String>,
137
138 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub ephemeral_public_key: Option<String>,
141}
142
143#[derive(Debug, Clone)]
145pub struct EncryptedData {
146 pub ciphertext: Vec<u8>,
148
149 pub nonce: Vec<u8>,
151
152 pub tag: Vec<u8>,
154}
155
156#[cfg(feature = "encryption")]
158pub struct Aes256GcmEncryptor {
159 key: [u8; 32],
160}
161
162#[cfg(feature = "encryption")]
163#[allow(clippy::missing_panics_doc)] impl Aes256GcmEncryptor {
165 pub fn new(key: &[u8]) -> Result<Self> {
171 let key: [u8; 32] = key.try_into().map_err(|_| {
172 invalid_manifest(format!(
173 "Invalid key length: expected 32 bytes, got {}",
174 key.len()
175 ))
176 })?;
177 Ok(Self { key })
178 }
179
180 #[must_use]
182 pub fn generate_key() -> [u8; 32] {
183 let mut key = [0u8; 32];
184 getrandom::fill(&mut key).expect("system RNG failed");
185 key
186 }
187
188 #[must_use]
190 pub fn generate_nonce() -> [u8; 12] {
191 let mut nonce = [0u8; 12];
192 getrandom::fill(&mut nonce).expect("system RNG failed");
193 nonce
194 }
195
196 pub fn encrypt(&self, plaintext: &[u8]) -> Result<EncryptedData> {
202 self.encrypt_with_nonce(plaintext, &Self::generate_nonce())
203 }
204
205 pub fn encrypt_with_nonce(&self, plaintext: &[u8], nonce: &[u8; 12]) -> Result<EncryptedData> {
211 use aes_gcm::{
212 aead::{Aead, KeyInit},
213 Aes256Gcm, Nonce,
214 };
215
216 let cipher = Aes256Gcm::new_from_slice(&self.key)
217 .map_err(|e| invalid_manifest(format!("Failed to create cipher: {e}")))?;
218
219 let nonce_obj = Nonce::from(*nonce);
220 let ciphertext = cipher
221 .encrypt(&nonce_obj, plaintext)
222 .map_err(|e| invalid_manifest(format!("Encryption failed: {e}")))?;
223
224 let tag_start = ciphertext.len().saturating_sub(16);
226 let tag = ciphertext[tag_start..].to_vec();
227
228 Ok(EncryptedData {
229 ciphertext,
230 nonce: nonce.to_vec(),
231 tag,
232 })
233 }
234
235 pub fn decrypt(&self, ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>> {
241 use aes_gcm::{
242 aead::{Aead, KeyInit},
243 Aes256Gcm, Nonce,
244 };
245
246 let nonce: [u8; 12] = nonce.try_into().map_err(|_| {
247 invalid_manifest(format!(
248 "Invalid nonce length: expected 12 bytes, got {}",
249 nonce.len()
250 ))
251 })?;
252
253 let cipher = Aes256Gcm::new_from_slice(&self.key)
254 .map_err(|e| invalid_manifest(format!("Failed to create cipher: {e}")))?;
255
256 let nonce_obj = Nonce::from(nonce);
257 cipher
258 .decrypt(&nonce_obj, ciphertext)
259 .map_err(|e| invalid_manifest(format!("Decryption failed: {e}")))
260 }
261}
262
263#[cfg(feature = "encryption-chacha")]
265pub struct ChaCha20Poly1305Encryptor {
266 key: [u8; 32],
267}
268
269#[cfg(feature = "encryption-chacha")]
270#[allow(clippy::missing_panics_doc)] impl ChaCha20Poly1305Encryptor {
272 pub fn new(key: &[u8]) -> Result<Self> {
278 let key: [u8; 32] = key.try_into().map_err(|_| {
279 invalid_manifest(format!(
280 "Invalid key length: expected 32 bytes, got {}",
281 key.len()
282 ))
283 })?;
284 Ok(Self { key })
285 }
286
287 #[must_use]
289 pub fn generate_key() -> [u8; 32] {
290 let mut key = [0u8; 32];
291 getrandom::fill(&mut key).expect("system RNG failed");
292 key
293 }
294
295 #[must_use]
297 pub fn generate_nonce() -> [u8; 12] {
298 let mut nonce = [0u8; 12];
299 getrandom::fill(&mut nonce).expect("system RNG failed");
300 nonce
301 }
302
303 pub fn encrypt(&self, plaintext: &[u8]) -> Result<EncryptedData> {
309 self.encrypt_with_nonce(plaintext, &Self::generate_nonce())
310 }
311
312 pub fn encrypt_with_nonce(&self, plaintext: &[u8], nonce: &[u8; 12]) -> Result<EncryptedData> {
318 use chacha20poly1305::{
319 aead::{Aead, KeyInit},
320 ChaCha20Poly1305, Nonce,
321 };
322
323 let cipher = ChaCha20Poly1305::new_from_slice(&self.key)
324 .map_err(|e| invalid_manifest(format!("Failed to create cipher: {e}")))?;
325
326 let nonce_obj = Nonce::from(*nonce);
327 let ciphertext = cipher
328 .encrypt(&nonce_obj, plaintext)
329 .map_err(|e| invalid_manifest(format!("Encryption failed: {e}")))?;
330
331 let tag_start = ciphertext.len().saturating_sub(16);
333 let tag = ciphertext[tag_start..].to_vec();
334
335 Ok(EncryptedData {
336 ciphertext,
337 nonce: nonce.to_vec(),
338 tag,
339 })
340 }
341
342 pub fn decrypt(&self, ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>> {
348 use chacha20poly1305::{
349 aead::{Aead, KeyInit},
350 ChaCha20Poly1305, Nonce,
351 };
352
353 let nonce: [u8; 12] = nonce.try_into().map_err(|_| {
354 invalid_manifest(format!(
355 "Invalid nonce length: expected 12 bytes, got {}",
356 nonce.len()
357 ))
358 })?;
359
360 let cipher = ChaCha20Poly1305::new_from_slice(&self.key)
361 .map_err(|e| invalid_manifest(format!("Failed to create cipher: {e}")))?;
362
363 let nonce_obj = Nonce::from(nonce);
364 cipher
365 .decrypt(&nonce_obj, ciphertext)
366 .map_err(|e| invalid_manifest(format!("Decryption failed: {e}")))
367 }
368}
369
370#[cfg(feature = "key-wrapping")]
376#[derive(Debug, Clone)]
377pub struct WrappedKeyData {
378 pub wrapped_key: Vec<u8>,
380 pub ephemeral_public_key: Vec<u8>,
382}
383
384#[cfg(feature = "key-wrapping")]
390pub struct EcdhEsKeyWrapper {
391 recipient_public_key: p256::PublicKey,
393}
394
395#[cfg(feature = "key-wrapping")]
396impl EcdhEsKeyWrapper {
397 #[must_use]
401 pub fn new(recipient_public_key: p256::PublicKey) -> Self {
402 Self {
403 recipient_public_key,
404 }
405 }
406
407 pub fn wrap(&self, content_key: &[u8]) -> Result<WrappedKeyData> {
420 use aes_kw::{cipher::KeyInit, KwAes256};
421 use hkdf::Hkdf;
422 use p256::ecdh::EphemeralSecret;
423 use p256::elliptic_curve::Generate;
424 use sha2::Sha256;
425
426 let ephemeral_secret = EphemeralSecret::generate();
428 let ephemeral_public = p256::PublicKey::from(&ephemeral_secret);
429
430 let shared_secret = ephemeral_secret.diffie_hellman(&self.recipient_public_key);
432
433 let hkdf = Hkdf::<Sha256>::new(None, shared_secret.raw_secret_bytes());
436 let mut kek_bytes = [0u8; 32];
437 hkdf.expand(b"ECDH-ES+A256KW", &mut kek_bytes)
438 .map_err(|e| encryption_error(format!("HKDF expansion failed: {e}")))?;
439
440 let kek = KwAes256::new(&kek_bytes.into());
442 let mut wrapped = vec![0u8; content_key.len() + 8]; kek.wrap_key(content_key, &mut wrapped)
444 .map_err(|e| encryption_error(format!("AES key wrap failed: {e}")))?;
445
446 let ephemeral_public_bytes = ephemeral_public.to_sec1_bytes().to_vec();
448
449 Ok(WrappedKeyData {
450 wrapped_key: wrapped,
451 ephemeral_public_key: ephemeral_public_bytes,
452 })
453 }
454}
455
456#[cfg(feature = "key-wrapping")]
462pub struct EcdhEsKeyUnwrapper {
463 recipient_secret: p256::SecretKey,
465}
466
467#[cfg(feature = "key-wrapping")]
468impl EcdhEsKeyUnwrapper {
469 #[must_use]
471 pub fn new(recipient_secret: p256::SecretKey) -> Self {
472 Self { recipient_secret }
473 }
474
475 pub fn unwrap(&self, data: &WrappedKeyData) -> Result<Vec<u8>> {
482 use aes_kw::{cipher::KeyInit, KwAes256};
483 use hkdf::Hkdf;
484 use sha2::Sha256;
485
486 let ephemeral_public = p256::PublicKey::from_sec1_bytes(&data.ephemeral_public_key)
488 .map_err(|e| encryption_error(format!("Invalid ephemeral public key: {e}")))?;
489
490 let shared_secret = p256::ecdh::diffie_hellman(
492 self.recipient_secret.to_nonzero_scalar(),
493 ephemeral_public.as_affine(),
494 );
495
496 let hkdf = Hkdf::<Sha256>::new(None, shared_secret.raw_secret_bytes());
498 let mut kek_bytes = [0u8; 32];
499 hkdf.expand(b"ECDH-ES+A256KW", &mut kek_bytes)
500 .map_err(|e| encryption_error(format!("HKDF expansion failed: {e}")))?;
501
502 let kek = KwAes256::new(&kek_bytes.into());
504 let unwrapped_len = data
505 .wrapped_key
506 .len()
507 .checked_sub(8)
508 .ok_or_else(|| encryption_error("Wrapped key too short"))?;
509 let mut unwrapped = vec![0u8; unwrapped_len];
510 kek.unwrap_key(&data.wrapped_key, &mut unwrapped)
511 .map_err(|e| encryption_error(format!("AES key unwrap failed: {e}")))?;
512 Ok(unwrapped)
513 }
514}
515
516#[cfg(feature = "key-wrapping-rsa")]
522#[derive(Debug, Clone)]
523pub struct RsaWrappedKeyData {
524 pub wrapped_key: Vec<u8>,
526}
527
528#[cfg(feature = "key-wrapping-rsa")]
533pub struct RsaOaepKeyWrapper {
534 recipient_public_key: rsa::RsaPublicKey,
535}
536
537#[cfg(feature = "key-wrapping-rsa")]
538impl RsaOaepKeyWrapper {
539 #[must_use]
541 pub fn new(recipient_public_key: rsa::RsaPublicKey) -> Self {
542 Self {
543 recipient_public_key,
544 }
545 }
546
547 pub fn wrap(&self, content_key: &[u8]) -> Result<RsaWrappedKeyData> {
553 use rsa::oaep::EncryptingKey;
554 use rsa::sha2::Sha256;
555 use rsa::traits::RandomizedEncryptor;
556
557 let encrypting_key = EncryptingKey::<Sha256>::new(self.recipient_public_key.clone());
558 let wrapped_key = encrypting_key
559 .encrypt_with_rng(&mut rand_core::UnwrapErr(getrandom::SysRng), content_key)
560 .map_err(|e| encryption_error(format!("RSA-OAEP wrap failed: {e}")))?;
561
562 Ok(RsaWrappedKeyData { wrapped_key })
563 }
564}
565
566#[cfg(feature = "key-wrapping-rsa")]
570pub struct RsaOaepKeyUnwrapper {
571 recipient_private_key: rsa::RsaPrivateKey,
572}
573
574#[cfg(feature = "key-wrapping-rsa")]
575impl RsaOaepKeyUnwrapper {
576 #[must_use]
578 pub fn new(recipient_private_key: rsa::RsaPrivateKey) -> Self {
579 Self {
580 recipient_private_key,
581 }
582 }
583
584 pub fn unwrap(&self, data: &RsaWrappedKeyData) -> Result<Vec<u8>> {
590 use rsa::oaep::DecryptingKey;
591 use rsa::sha2::Sha256;
592 use rsa::traits::Decryptor;
593
594 let decrypting_key = DecryptingKey::<Sha256>::new(self.recipient_private_key.clone());
595 decrypting_key
596 .decrypt(&data.wrapped_key)
597 .map_err(|e| encryption_error(format!("RSA-OAEP unwrap failed: {e}")))
598 }
599}
600
601#[cfg(feature = "key-wrapping-pbes2")]
607#[derive(Debug, Clone)]
608pub struct Pbes2WrappedKeyData {
609 pub wrapped_key: Vec<u8>,
611 pub salt: Vec<u8>,
613 pub iterations: u32,
615}
616
617#[cfg(feature = "key-wrapping-pbes2")]
622pub struct Pbes2KeyWrapper {
623 password: Vec<u8>,
624 iterations: u32,
625}
626
627#[cfg(feature = "key-wrapping-pbes2")]
628impl Pbes2KeyWrapper {
629 pub const DEFAULT_ITERATIONS: u32 = 600_000;
631
632 #[must_use]
634 pub fn new(password: impl AsRef<[u8]>, iterations: u32) -> Self {
635 Self {
636 password: password.as_ref().to_vec(),
637 iterations,
638 }
639 }
640
641 pub fn wrap(&self, content_key: &[u8]) -> Result<Pbes2WrappedKeyData> {
650 use aes_kw::{cipher::KeyInit, KwAes256};
651
652 let mut salt = [0u8; 16];
654 getrandom::fill(&mut salt)
655 .map_err(|e| encryption_error(format!("System RNG failed: {e}")))?;
656
657 let mut kek_bytes = [0u8; 32];
659 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(&self.password, &salt, self.iterations, &mut kek_bytes);
660
661 let kek = KwAes256::new(&kek_bytes.into());
663 let mut wrapped = vec![0u8; content_key.len() + 8];
664 kek.wrap_key(content_key, &mut wrapped)
665 .map_err(|e| encryption_error(format!("PBES2 AES key wrap failed: {e}")))?;
666
667 Ok(Pbes2WrappedKeyData {
668 wrapped_key: wrapped,
669 salt: salt.to_vec(),
670 iterations: self.iterations,
671 })
672 }
673}
674
675#[cfg(feature = "key-wrapping-pbes2")]
680pub struct Pbes2KeyUnwrapper {
681 password: Vec<u8>,
682}
683
684#[cfg(feature = "key-wrapping-pbes2")]
685impl Pbes2KeyUnwrapper {
686 #[must_use]
688 pub fn new(password: impl AsRef<[u8]>) -> Self {
689 Self {
690 password: password.as_ref().to_vec(),
691 }
692 }
693
694 pub fn unwrap(&self, data: &Pbes2WrappedKeyData) -> Result<Vec<u8>> {
703 use aes_kw::{cipher::KeyInit, KwAes256};
704
705 let mut kek_bytes = [0u8; 32];
707 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(
708 &self.password,
709 &data.salt,
710 data.iterations,
711 &mut kek_bytes,
712 );
713
714 let kek = KwAes256::new(&kek_bytes.into());
716 let unwrapped_len = data
717 .wrapped_key
718 .len()
719 .checked_sub(8)
720 .ok_or_else(|| encryption_error("Wrapped key too short"))?;
721 let mut unwrapped = vec![0u8; unwrapped_len];
722 kek.unwrap_key(&data.wrapped_key, &mut unwrapped)
723 .map_err(|e| encryption_error(format!("PBES2 AES key unwrap failed: {e}")))?;
724 Ok(unwrapped)
725 }
726}
727
728#[cfg(all(test, feature = "encryption"))]
729mod tests {
730 use super::*;
731
732 #[test]
733 fn test_encrypt_decrypt() {
734 let key = Aes256GcmEncryptor::generate_key();
735 let encryptor = Aes256GcmEncryptor::new(&key).unwrap();
736
737 let plaintext = b"Hello, World! This is a test message.";
738 let encrypted = encryptor.encrypt(plaintext).unwrap();
739
740 assert_ne!(&encrypted.ciphertext[..plaintext.len()], plaintext);
741
742 let decrypted = encryptor
743 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
744 .unwrap();
745 assert_eq!(decrypted, plaintext);
746 }
747
748 #[test]
749 fn test_wrong_key_fails() {
750 let key1 = Aes256GcmEncryptor::generate_key();
751 let key2 = Aes256GcmEncryptor::generate_key();
752
753 let encryptor1 = Aes256GcmEncryptor::new(&key1).unwrap();
754 let encryptor2 = Aes256GcmEncryptor::new(&key2).unwrap();
755
756 let plaintext = b"Secret message";
757 let encrypted = encryptor1.encrypt(plaintext).unwrap();
758
759 let result = encryptor2.decrypt(&encrypted.ciphertext, &encrypted.nonce);
760 assert!(result.is_err());
761 }
762
763 #[test]
764 fn test_tampered_data_fails() {
765 let key = Aes256GcmEncryptor::generate_key();
766 let encryptor = Aes256GcmEncryptor::new(&key).unwrap();
767
768 let plaintext = b"Original message";
769 let mut encrypted = encryptor.encrypt(plaintext).unwrap();
770
771 if !encrypted.ciphertext.is_empty() {
773 encrypted.ciphertext[0] ^= 0xFF;
774 }
775
776 let result = encryptor.decrypt(&encrypted.ciphertext, &encrypted.nonce);
777 assert!(result.is_err());
778 }
779
780 #[test]
781 fn test_empty_plaintext() {
782 let key = Aes256GcmEncryptor::generate_key();
783 let encryptor = Aes256GcmEncryptor::new(&key).unwrap();
784
785 let plaintext = b"";
786 let encrypted = encryptor.encrypt(plaintext).unwrap();
787 let decrypted = encryptor
788 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
789 .unwrap();
790
791 assert_eq!(decrypted, plaintext);
792 }
793
794 #[test]
795 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
796 fn test_large_plaintext() {
797 let key = Aes256GcmEncryptor::generate_key();
798 let encryptor = Aes256GcmEncryptor::new(&key).unwrap();
799
800 let plaintext: Vec<u8> = (0..1024 * 1024).map(|i| (i % 256) as u8).collect();
802 let encrypted = encryptor.encrypt(&plaintext).unwrap();
803 let decrypted = encryptor
804 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
805 .unwrap();
806
807 assert_eq!(decrypted, plaintext);
808 }
809
810 #[test]
811 fn test_encryption_metadata_serialization() {
812 let metadata = EncryptionMetadata {
813 algorithm: EncryptionAlgorithm::Aes256Gcm,
814 kdf: Some(KeyDerivation {
815 algorithm: KdfAlgorithm::Argon2id,
816 salt: "base64salt".to_string(),
817 iterations: None,
818 memory: Some(65536),
819 parallelism: Some(4),
820 }),
821 wrapped_key: None,
822 key_management: None,
823 recipients: vec![],
824 };
825
826 let json = serde_json::to_string_pretty(&metadata).unwrap();
827 assert!(json.contains("AES-256-GCM"));
828 assert!(json.contains("Argon2id"));
829
830 let deserialized: EncryptionMetadata = serde_json::from_str(&json).unwrap();
831 assert_eq!(deserialized.algorithm, metadata.algorithm);
832 }
833
834 #[test]
835 fn test_key_management_algorithm_roundtrip() {
836 let variants = [
837 (KeyManagementAlgorithm::EcdhEsA256kw, "\"ECDH-ES+A256KW\""),
838 (KeyManagementAlgorithm::RsaOaep256, "\"RSA-OAEP-256\""),
839 (
840 KeyManagementAlgorithm::Pbes2HsA256kw,
841 "\"PBES2-HS256+A256KW\"",
842 ),
843 ];
844 for (alg, expected_json) in &variants {
845 let json = serde_json::to_string(alg).unwrap();
846 assert_eq!(&json, expected_json);
847 let parsed: KeyManagementAlgorithm = serde_json::from_str(&json).unwrap();
848 assert_eq!(&parsed, alg);
849 }
850 }
851
852 #[test]
853 fn test_encryption_metadata_with_key_management() {
854 let metadata = EncryptionMetadata {
855 algorithm: EncryptionAlgorithm::Aes256Gcm,
856 kdf: None,
857 wrapped_key: Some("wrapped-key-base64".to_string()),
858 key_management: Some(KeyManagementAlgorithm::EcdhEsA256kw),
859 recipients: vec![Recipient {
860 id: "recipient-1".to_string(),
861 encrypted_key: "enc-key-base64".to_string(),
862 algorithm: Some("ECDH-ES+A256KW".to_string()),
863 ephemeral_public_key: Some("ephemeral-pk-base64".to_string()),
864 }],
865 };
866
867 let json = serde_json::to_string_pretty(&metadata).unwrap();
868 assert!(json.contains("ECDH-ES+A256KW"));
869 assert!(json.contains("ephemeralPublicKey"));
870
871 let parsed: EncryptionMetadata = serde_json::from_str(&json).unwrap();
872 assert_eq!(parsed.key_management, metadata.key_management);
873 assert_eq!(
874 parsed.recipients[0].ephemeral_public_key,
875 Some("ephemeral-pk-base64".to_string())
876 );
877 }
878
879 #[test]
880 fn test_encryption_metadata_backward_compat() {
881 let json = r#"{
883 "algorithm": "AES-256-GCM",
884 "recipients": [{
885 "id": "r1",
886 "encryptedKey": "key-data"
887 }]
888 }"#;
889 let metadata: EncryptionMetadata = serde_json::from_str(json).unwrap();
890 assert!(metadata.key_management.is_none());
891 assert!(metadata.recipients[0].ephemeral_public_key.is_none());
892 }
893}
894
895#[cfg(all(test, feature = "encryption-chacha"))]
896mod chacha_tests {
897 use super::*;
898
899 #[test]
900 fn test_chacha_encrypt_decrypt() {
901 let key = ChaCha20Poly1305Encryptor::generate_key();
902 let encryptor = ChaCha20Poly1305Encryptor::new(&key).unwrap();
903
904 let plaintext = b"Hello, World! This is a test message.";
905 let encrypted = encryptor.encrypt(plaintext).unwrap();
906
907 assert_ne!(&encrypted.ciphertext[..plaintext.len()], plaintext);
908
909 let decrypted = encryptor
910 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
911 .unwrap();
912 assert_eq!(decrypted, plaintext);
913 }
914
915 #[test]
916 fn test_chacha_wrong_key_fails() {
917 let key1 = ChaCha20Poly1305Encryptor::generate_key();
918 let key2 = ChaCha20Poly1305Encryptor::generate_key();
919
920 let encryptor1 = ChaCha20Poly1305Encryptor::new(&key1).unwrap();
921 let encryptor2 = ChaCha20Poly1305Encryptor::new(&key2).unwrap();
922
923 let plaintext = b"Secret message";
924 let encrypted = encryptor1.encrypt(plaintext).unwrap();
925
926 let result = encryptor2.decrypt(&encrypted.ciphertext, &encrypted.nonce);
927 assert!(result.is_err());
928 }
929
930 #[test]
931 fn test_chacha_tampered_data_fails() {
932 let key = ChaCha20Poly1305Encryptor::generate_key();
933 let encryptor = ChaCha20Poly1305Encryptor::new(&key).unwrap();
934
935 let plaintext = b"Original message";
936 let mut encrypted = encryptor.encrypt(plaintext).unwrap();
937
938 if !encrypted.ciphertext.is_empty() {
940 encrypted.ciphertext[0] ^= 0xFF;
941 }
942
943 let result = encryptor.decrypt(&encrypted.ciphertext, &encrypted.nonce);
944 assert!(result.is_err());
945 }
946
947 #[test]
948 fn test_chacha_empty_plaintext() {
949 let key = ChaCha20Poly1305Encryptor::generate_key();
950 let encryptor = ChaCha20Poly1305Encryptor::new(&key).unwrap();
951
952 let plaintext = b"";
953 let encrypted = encryptor.encrypt(plaintext).unwrap();
954 let decrypted = encryptor
955 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
956 .unwrap();
957
958 assert_eq!(decrypted, plaintext);
959 }
960
961 #[test]
962 fn test_chacha_encryption_algorithm_enum() {
963 let algo = EncryptionAlgorithm::ChaCha20Poly1305;
964 assert_eq!(algo.as_str(), "ChaCha20-Poly1305");
965 assert_eq!(algo.key_size(), 32);
966 assert_eq!(algo.nonce_size(), 12);
967
968 let json = serde_json::to_string(&algo).unwrap();
969 assert_eq!(json, "\"ChaCha20-Poly1305\"");
970
971 let deserialized: EncryptionAlgorithm = serde_json::from_str(&json).unwrap();
972 assert_eq!(deserialized, algo);
973 }
974}
975
976#[cfg(all(test, feature = "key-wrapping"))]
977mod key_wrapping_tests {
978 use super::*;
979
980 fn generate_keypair() -> (p256::SecretKey, p256::PublicKey) {
982 use p256::elliptic_curve::Generate;
983 let secret = p256::SecretKey::generate();
984 let public = secret.public_key();
985 (secret, public)
986 }
987
988 #[test]
989 fn test_wrap_unwrap_roundtrip() {
990 let (secret, public) = generate_keypair();
991
992 let content_key = Aes256GcmEncryptor::generate_key();
994
995 let wrapper = EcdhEsKeyWrapper::new(public);
997 let wrapped = wrapper.wrap(&content_key).unwrap();
998
999 assert_eq!(wrapped.wrapped_key.len(), content_key.len() + 8);
1001 assert_eq!(wrapped.ephemeral_public_key.len(), 65);
1003
1004 let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1006 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1007
1008 assert_eq!(recovered, content_key);
1009 }
1010
1011 #[test]
1012 fn test_multi_recipient_wrap() {
1013 let (secret_a, public_a) = generate_keypair();
1014 let (secret_b, public_b) = generate_keypair();
1015
1016 let content_key = Aes256GcmEncryptor::generate_key();
1017
1018 let wrapper_a = EcdhEsKeyWrapper::new(public_a);
1020 let wrapped_a = wrapper_a.wrap(&content_key).unwrap();
1021
1022 let wrapper_b = EcdhEsKeyWrapper::new(public_b);
1023 let wrapped_b = wrapper_b.wrap(&content_key).unwrap();
1024
1025 let unwrapper_a = EcdhEsKeyUnwrapper::new(secret_a);
1027 let recovered_a = unwrapper_a.unwrap(&wrapped_a).unwrap();
1028 assert_eq!(recovered_a, content_key);
1029
1030 let unwrapper_b = EcdhEsKeyUnwrapper::new(secret_b);
1031 let recovered_b = unwrapper_b.unwrap(&wrapped_b).unwrap();
1032 assert_eq!(recovered_b, content_key);
1033
1034 assert!(unwrapper_a.unwrap(&wrapped_b).is_err());
1036 assert!(unwrapper_b.unwrap(&wrapped_a).is_err());
1037 }
1038
1039 #[test]
1040 fn test_wrong_private_key_fails() {
1041 let (_secret, public) = generate_keypair();
1042 let (wrong_secret, _wrong_public) = generate_keypair();
1043
1044 let content_key = Aes256GcmEncryptor::generate_key();
1045
1046 let wrapper = EcdhEsKeyWrapper::new(public);
1047 let wrapped = wrapper.wrap(&content_key).unwrap();
1048
1049 let unwrapper = EcdhEsKeyUnwrapper::new(wrong_secret);
1051 let result = unwrapper.unwrap(&wrapped);
1052 assert!(result.is_err());
1053 }
1054
1055 #[test]
1056 fn test_tampered_wrapped_key_fails() {
1057 let (secret, public) = generate_keypair();
1058
1059 let content_key = Aes256GcmEncryptor::generate_key();
1060
1061 let wrapper = EcdhEsKeyWrapper::new(public);
1062 let mut wrapped = wrapper.wrap(&content_key).unwrap();
1063
1064 if !wrapped.wrapped_key.is_empty() {
1066 wrapped.wrapped_key[0] ^= 0xFF;
1067 }
1068
1069 let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1070 let result = unwrapper.unwrap(&wrapped);
1071 assert!(result.is_err());
1072 }
1073
1074 #[test]
1075 fn test_tampered_ephemeral_key_fails() {
1076 let (secret, public) = generate_keypair();
1077
1078 let content_key = Aes256GcmEncryptor::generate_key();
1079
1080 let wrapper = EcdhEsKeyWrapper::new(public);
1081 let mut wrapped = wrapper.wrap(&content_key).unwrap();
1082
1083 if wrapped.ephemeral_public_key.len() > 1 {
1086 wrapped.ephemeral_public_key[1] ^= 0xFF;
1087 }
1088
1089 let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1090 let result = unwrapper.unwrap(&wrapped);
1091 assert!(result.is_err());
1093 }
1094
1095 #[test]
1096 fn test_integration_encrypt_wrap_unwrap_decrypt() {
1097 let (secret, public) = generate_keypair();
1098
1099 let content_key = Aes256GcmEncryptor::generate_key();
1101 let encryptor = Aes256GcmEncryptor::new(&content_key).unwrap();
1102 let plaintext = b"Codex document content for encryption";
1103 let encrypted = encryptor.encrypt(plaintext).unwrap();
1104
1105 let wrapper = EcdhEsKeyWrapper::new(public);
1107 let wrapped = wrapper.wrap(&content_key).unwrap();
1108
1109 let metadata = EncryptionMetadata {
1111 algorithm: EncryptionAlgorithm::Aes256Gcm,
1112 kdf: None,
1113 wrapped_key: None,
1114 key_management: Some(KeyManagementAlgorithm::EcdhEsA256kw),
1115 recipients: vec![Recipient {
1116 id: "recipient-1".to_string(),
1117 encrypted_key: base64::Engine::encode(
1118 &base64::engine::general_purpose::STANDARD,
1119 &wrapped.wrapped_key,
1120 ),
1121 algorithm: Some("ECDH-ES+A256KW".to_string()),
1122 ephemeral_public_key: Some(base64::Engine::encode(
1123 &base64::engine::general_purpose::STANDARD,
1124 &wrapped.ephemeral_public_key,
1125 )),
1126 }],
1127 };
1128
1129 let json = serde_json::to_string(&metadata).unwrap();
1131 let parsed: EncryptionMetadata = serde_json::from_str(&json).unwrap();
1132
1133 let recipient = &parsed.recipients[0];
1135 let wrapped_data = WrappedKeyData {
1136 wrapped_key: base64::Engine::decode(
1137 &base64::engine::general_purpose::STANDARD,
1138 &recipient.encrypted_key,
1139 )
1140 .unwrap(),
1141 ephemeral_public_key: base64::Engine::decode(
1142 &base64::engine::general_purpose::STANDARD,
1143 recipient.ephemeral_public_key.as_ref().unwrap(),
1144 )
1145 .unwrap(),
1146 };
1147
1148 let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1149 let recovered_key = unwrapper.unwrap(&wrapped_data).unwrap();
1150
1151 let decryptor = Aes256GcmEncryptor::new(&recovered_key).unwrap();
1153 let decrypted = decryptor
1154 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
1155 .unwrap();
1156
1157 assert_eq!(decrypted, plaintext);
1158 }
1159
1160 #[test]
1161 fn test_wrap_16_byte_key() {
1162 let (secret, public) = generate_keypair();
1164 let content_key = [0x42u8; 16]; let wrapper = EcdhEsKeyWrapper::new(public);
1167 let wrapped = wrapper.wrap(&content_key).unwrap();
1168 assert_eq!(wrapped.wrapped_key.len(), 24); let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1171 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1172 assert_eq!(recovered, content_key);
1173 }
1174}
1175
1176#[cfg(all(test, feature = "key-wrapping-rsa"))]
1177mod rsa_oaep_tests {
1178 use super::*;
1179
1180 fn generate_rsa_keypair(bits: usize) -> (rsa::RsaPrivateKey, rsa::RsaPublicKey) {
1181 let private_key =
1182 rsa::RsaPrivateKey::new(&mut rand_core::UnwrapErr(getrandom::SysRng), bits).unwrap();
1183 let public_key = rsa::RsaPublicKey::from(&private_key);
1184 (private_key, public_key)
1185 }
1186
1187 #[test]
1188 fn test_rsa_oaep_roundtrip_2048() {
1189 let (private_key, public_key) = generate_rsa_keypair(2048);
1190 let content_key = Aes256GcmEncryptor::generate_key();
1191
1192 let wrapper = RsaOaepKeyWrapper::new(public_key);
1193 let wrapped = wrapper.wrap(&content_key).unwrap();
1194
1195 let unwrapper = RsaOaepKeyUnwrapper::new(private_key);
1196 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1197
1198 assert_eq!(recovered, content_key);
1199 }
1200
1201 #[test]
1202 fn test_rsa_oaep_roundtrip_4096() {
1203 let (private_key, public_key) = generate_rsa_keypair(4096);
1204 let content_key = Aes256GcmEncryptor::generate_key();
1205
1206 let wrapper = RsaOaepKeyWrapper::new(public_key);
1207 let wrapped = wrapper.wrap(&content_key).unwrap();
1208
1209 let unwrapper = RsaOaepKeyUnwrapper::new(private_key);
1210 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1211
1212 assert_eq!(recovered, content_key);
1213 }
1214
1215 #[test]
1216 fn test_rsa_oaep_wrong_key_fails() {
1217 let (_private_key, public_key) = generate_rsa_keypair(2048);
1218 let (wrong_private_key, _wrong_public_key) = generate_rsa_keypair(2048);
1219 let content_key = Aes256GcmEncryptor::generate_key();
1220
1221 let wrapper = RsaOaepKeyWrapper::new(public_key);
1222 let wrapped = wrapper.wrap(&content_key).unwrap();
1223
1224 let unwrapper = RsaOaepKeyUnwrapper::new(wrong_private_key);
1225 let result = unwrapper.unwrap(&wrapped);
1226 assert!(result.is_err());
1227 }
1228
1229 #[test]
1230 fn test_rsa_oaep_tampered_data_fails() {
1231 let (private_key, public_key) = generate_rsa_keypair(2048);
1232 let content_key = Aes256GcmEncryptor::generate_key();
1233
1234 let wrapper = RsaOaepKeyWrapper::new(public_key);
1235 let mut wrapped = wrapper.wrap(&content_key).unwrap();
1236
1237 if !wrapped.wrapped_key.is_empty() {
1238 wrapped.wrapped_key[0] ^= 0xFF;
1239 }
1240
1241 let unwrapper = RsaOaepKeyUnwrapper::new(private_key);
1242 let result = unwrapper.unwrap(&wrapped);
1243 assert!(result.is_err());
1244 }
1245
1246 #[test]
1247 fn test_rsa_oaep_integration_encrypt_wrap_unwrap_decrypt() {
1248 let (private_key, public_key) = generate_rsa_keypair(2048);
1249
1250 let content_key = Aes256GcmEncryptor::generate_key();
1252 let encryptor = Aes256GcmEncryptor::new(&content_key).unwrap();
1253 let plaintext = b"Codex document encrypted with RSA-OAEP key wrapping";
1254 let encrypted = encryptor.encrypt(plaintext).unwrap();
1255
1256 let wrapper = RsaOaepKeyWrapper::new(public_key);
1258 let wrapped = wrapper.wrap(&content_key).unwrap();
1259
1260 let unwrapper = RsaOaepKeyUnwrapper::new(private_key);
1262 let recovered_key = unwrapper.unwrap(&wrapped).unwrap();
1263
1264 let decryptor = Aes256GcmEncryptor::new(&recovered_key).unwrap();
1265 let decrypted = decryptor
1266 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
1267 .unwrap();
1268
1269 assert_eq!(decrypted, plaintext);
1270 }
1271
1272 #[test]
1273 fn test_rsa_oaep_metadata_serialization() {
1274 let metadata = EncryptionMetadata {
1275 algorithm: EncryptionAlgorithm::Aes256Gcm,
1276 kdf: None,
1277 wrapped_key: None,
1278 key_management: Some(KeyManagementAlgorithm::RsaOaep256),
1279 recipients: vec![Recipient {
1280 id: "rsa-recipient".to_string(),
1281 encrypted_key: "wrapped-key-base64".to_string(),
1282 algorithm: Some("RSA-OAEP-256".to_string()),
1283 ephemeral_public_key: None,
1284 }],
1285 };
1286
1287 let json = serde_json::to_string_pretty(&metadata).unwrap();
1288 assert!(json.contains("RSA-OAEP-256"));
1289 assert!(!json.contains("ephemeralPublicKey"));
1291
1292 let parsed: EncryptionMetadata = serde_json::from_str(&json).unwrap();
1293 assert_eq!(
1294 parsed.key_management,
1295 Some(KeyManagementAlgorithm::RsaOaep256)
1296 );
1297 }
1298}
1299
1300#[cfg(all(test, feature = "key-wrapping-pbes2"))]
1301mod pbes2_tests {
1302 use super::*;
1303
1304 #[test]
1305 fn test_pbes2_roundtrip() {
1306 let password = b"correct horse battery staple";
1307 let content_key = Aes256GcmEncryptor::generate_key();
1308
1309 let wrapper = Pbes2KeyWrapper::new(password, Pbes2KeyWrapper::DEFAULT_ITERATIONS);
1310 let wrapped = wrapper.wrap(&content_key).unwrap();
1311
1312 assert_eq!(wrapped.wrapped_key.len(), content_key.len() + 8);
1313 assert_eq!(wrapped.salt.len(), 16);
1314 assert_eq!(wrapped.iterations, Pbes2KeyWrapper::DEFAULT_ITERATIONS);
1315
1316 let unwrapper = Pbes2KeyUnwrapper::new(password);
1317 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1318
1319 assert_eq!(recovered, content_key);
1320 }
1321
1322 #[test]
1323 fn test_pbes2_wrong_password_fails() {
1324 let content_key = Aes256GcmEncryptor::generate_key();
1325
1326 let wrapper = Pbes2KeyWrapper::new(b"correct password", 1000);
1327 let wrapped = wrapper.wrap(&content_key).unwrap();
1328
1329 let unwrapper = Pbes2KeyUnwrapper::new(b"wrong password");
1330 let result = unwrapper.unwrap(&wrapped);
1331 assert!(result.is_err());
1332 }
1333
1334 #[test]
1335 fn test_pbes2_tampered_salt_fails() {
1336 let password = b"my password";
1337 let content_key = Aes256GcmEncryptor::generate_key();
1338
1339 let wrapper = Pbes2KeyWrapper::new(password, 1000);
1340 let mut wrapped = wrapper.wrap(&content_key).unwrap();
1341
1342 if !wrapped.salt.is_empty() {
1344 wrapped.salt[0] ^= 0xFF;
1345 }
1346
1347 let unwrapper = Pbes2KeyUnwrapper::new(password);
1348 let result = unwrapper.unwrap(&wrapped);
1349 assert!(result.is_err());
1350 }
1351
1352 #[test]
1353 fn test_pbes2_different_iteration_counts() {
1354 let password = b"shared password";
1355 let content_key = Aes256GcmEncryptor::generate_key();
1356
1357 for &iterations in &[1000u32, 10_000, 100_000] {
1359 let wrapper = Pbes2KeyWrapper::new(password, iterations);
1360 let wrapped = wrapper.wrap(&content_key).unwrap();
1361 assert_eq!(wrapped.iterations, iterations);
1362
1363 let unwrapper = Pbes2KeyUnwrapper::new(password);
1364 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1365 assert_eq!(recovered, content_key);
1366 }
1367 }
1368
1369 #[test]
1370 fn test_pbes2_integration_encrypt_wrap_unwrap_decrypt() {
1371 let password = b"document encryption password";
1372
1373 let content_key = Aes256GcmEncryptor::generate_key();
1375 let encryptor = Aes256GcmEncryptor::new(&content_key).unwrap();
1376 let plaintext = b"Codex document with password-based key wrapping";
1377 let encrypted = encryptor.encrypt(plaintext).unwrap();
1378
1379 let wrapper = Pbes2KeyWrapper::new(password, 1000);
1381 let wrapped = wrapper.wrap(&content_key).unwrap();
1382
1383 let unwrapper = Pbes2KeyUnwrapper::new(password);
1385 let recovered_key = unwrapper.unwrap(&wrapped).unwrap();
1386
1387 let decryptor = Aes256GcmEncryptor::new(&recovered_key).unwrap();
1388 let decrypted = decryptor
1389 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
1390 .unwrap();
1391
1392 assert_eq!(decrypted, plaintext);
1393 }
1394
1395 #[test]
1396 fn test_pbes2_metadata_serialization() {
1397 let metadata = EncryptionMetadata {
1398 algorithm: EncryptionAlgorithm::Aes256Gcm,
1399 kdf: None,
1400 wrapped_key: None,
1401 key_management: Some(KeyManagementAlgorithm::Pbes2HsA256kw),
1402 recipients: vec![],
1403 };
1404
1405 let json = serde_json::to_string(&metadata).unwrap();
1406 assert!(json.contains("PBES2-HS256+A256KW"));
1407
1408 let parsed: EncryptionMetadata = serde_json::from_str(&json).unwrap();
1409 assert_eq!(
1410 parsed.key_management,
1411 Some(KeyManagementAlgorithm::Pbes2HsA256kw)
1412 );
1413 }
1414}