1use serde::{Deserialize, Serialize};
7
8use crate::error::encryption_error;
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")]
158#[derive(zeroize::ZeroizeOnDrop)]
159pub struct Aes256GcmEncryptor {
160 key: [u8; 32],
161}
162
163#[cfg(feature = "encryption")]
164#[allow(clippy::missing_panics_doc)] impl Aes256GcmEncryptor {
166 pub fn new(key: &[u8]) -> Result<Self> {
172 let key: [u8; 32] = key.try_into().map_err(|_| {
173 encryption_error(format!(
174 "Invalid key length: expected 32 bytes, got {}",
175 key.len()
176 ))
177 })?;
178 Ok(Self { key })
179 }
180
181 #[must_use]
183 pub fn generate_key() -> [u8; 32] {
184 let mut key = [0u8; 32];
185 getrandom::fill(&mut key).expect("system RNG failed");
186 key
187 }
188
189 #[must_use]
191 pub fn generate_nonce() -> [u8; 12] {
192 let mut nonce = [0u8; 12];
193 getrandom::fill(&mut nonce).expect("system RNG failed");
194 nonce
195 }
196
197 pub fn encrypt(&self, plaintext: &[u8]) -> Result<EncryptedData> {
203 self.encrypt_with_nonce(plaintext, &Self::generate_nonce())
204 }
205
206 pub fn encrypt_with_nonce(&self, plaintext: &[u8], nonce: &[u8; 12]) -> Result<EncryptedData> {
212 use aes_gcm::{
213 aead::{Aead, KeyInit},
214 Aes256Gcm, Nonce,
215 };
216
217 let cipher = Aes256Gcm::new_from_slice(&self.key)
218 .map_err(|e| encryption_error(format!("Failed to create cipher: {e}")))?;
219
220 let nonce_obj = Nonce::from(*nonce);
221 let ciphertext = cipher
222 .encrypt(&nonce_obj, plaintext)
223 .map_err(|e| encryption_error(format!("Encryption failed: {e}")))?;
224
225 let tag_start = ciphertext.len().saturating_sub(16);
227 let tag = ciphertext[tag_start..].to_vec();
228
229 Ok(EncryptedData {
230 ciphertext,
231 nonce: nonce.to_vec(),
232 tag,
233 })
234 }
235
236 pub fn decrypt(&self, ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>> {
242 use aes_gcm::{
243 aead::{Aead, KeyInit},
244 Aes256Gcm, Nonce,
245 };
246
247 let nonce: [u8; 12] = nonce.try_into().map_err(|_| {
248 encryption_error(format!(
249 "Invalid nonce length: expected 12 bytes, got {}",
250 nonce.len()
251 ))
252 })?;
253
254 let cipher = Aes256Gcm::new_from_slice(&self.key)
255 .map_err(|e| encryption_error(format!("Failed to create cipher: {e}")))?;
256
257 let nonce_obj = Nonce::from(nonce);
258 cipher
259 .decrypt(&nonce_obj, ciphertext)
260 .map_err(|e| encryption_error(format!("Decryption failed: {e}")))
261 }
262}
263
264#[cfg(feature = "encryption-chacha")]
266#[derive(zeroize::ZeroizeOnDrop)]
267pub struct ChaCha20Poly1305Encryptor {
268 key: [u8; 32],
269}
270
271#[cfg(feature = "encryption-chacha")]
272#[allow(clippy::missing_panics_doc)] impl ChaCha20Poly1305Encryptor {
274 pub fn new(key: &[u8]) -> Result<Self> {
280 let key: [u8; 32] = key.try_into().map_err(|_| {
281 encryption_error(format!(
282 "Invalid key length: expected 32 bytes, got {}",
283 key.len()
284 ))
285 })?;
286 Ok(Self { key })
287 }
288
289 #[must_use]
291 pub fn generate_key() -> [u8; 32] {
292 let mut key = [0u8; 32];
293 getrandom::fill(&mut key).expect("system RNG failed");
294 key
295 }
296
297 #[must_use]
299 pub fn generate_nonce() -> [u8; 12] {
300 let mut nonce = [0u8; 12];
301 getrandom::fill(&mut nonce).expect("system RNG failed");
302 nonce
303 }
304
305 pub fn encrypt(&self, plaintext: &[u8]) -> Result<EncryptedData> {
311 self.encrypt_with_nonce(plaintext, &Self::generate_nonce())
312 }
313
314 pub fn encrypt_with_nonce(&self, plaintext: &[u8], nonce: &[u8; 12]) -> Result<EncryptedData> {
320 use chacha20poly1305::{
321 aead::{Aead, KeyInit},
322 ChaCha20Poly1305, Nonce,
323 };
324
325 let cipher = ChaCha20Poly1305::new_from_slice(&self.key)
326 .map_err(|e| encryption_error(format!("Failed to create cipher: {e}")))?;
327
328 let nonce_obj = Nonce::from(*nonce);
329 let ciphertext = cipher
330 .encrypt(&nonce_obj, plaintext)
331 .map_err(|e| encryption_error(format!("Encryption failed: {e}")))?;
332
333 let tag_start = ciphertext.len().saturating_sub(16);
335 let tag = ciphertext[tag_start..].to_vec();
336
337 Ok(EncryptedData {
338 ciphertext,
339 nonce: nonce.to_vec(),
340 tag,
341 })
342 }
343
344 pub fn decrypt(&self, ciphertext: &[u8], nonce: &[u8]) -> Result<Vec<u8>> {
350 use chacha20poly1305::{
351 aead::{Aead, KeyInit},
352 ChaCha20Poly1305, Nonce,
353 };
354
355 let nonce: [u8; 12] = nonce.try_into().map_err(|_| {
356 encryption_error(format!(
357 "Invalid nonce length: expected 12 bytes, got {}",
358 nonce.len()
359 ))
360 })?;
361
362 let cipher = ChaCha20Poly1305::new_from_slice(&self.key)
363 .map_err(|e| encryption_error(format!("Failed to create cipher: {e}")))?;
364
365 let nonce_obj = Nonce::from(nonce);
366 cipher
367 .decrypt(&nonce_obj, ciphertext)
368 .map_err(|e| encryption_error(format!("Decryption failed: {e}")))
369 }
370}
371
372#[cfg(feature = "key-wrapping")]
378#[derive(Debug, Clone)]
379pub struct WrappedKeyData {
380 pub wrapped_key: Vec<u8>,
382 pub ephemeral_public_key: Vec<u8>,
384}
385
386#[cfg(feature = "key-wrapping")]
392pub struct EcdhEsKeyWrapper {
393 recipient_public_key: p256::PublicKey,
395}
396
397#[cfg(feature = "key-wrapping")]
398impl EcdhEsKeyWrapper {
399 #[must_use]
403 pub fn new(recipient_public_key: p256::PublicKey) -> Self {
404 Self {
405 recipient_public_key,
406 }
407 }
408
409 pub fn wrap(&self, content_key: &[u8]) -> Result<WrappedKeyData> {
422 use aes_kw::{cipher::KeyInit, KwAes256};
423 use hkdf::Hkdf;
424 use p256::ecdh::EphemeralSecret;
425 use p256::elliptic_curve::Generate;
426 use sha2::Sha256;
427
428 let ephemeral_secret = EphemeralSecret::generate();
430 let ephemeral_public = p256::PublicKey::from(&ephemeral_secret);
431
432 let shared_secret = ephemeral_secret.diffie_hellman(&self.recipient_public_key);
434
435 let hkdf = Hkdf::<Sha256>::new(None, shared_secret.raw_secret_bytes());
438 let mut kek_bytes = [0u8; 32];
439 hkdf.expand(b"ECDH-ES+A256KW", &mut kek_bytes)
440 .map_err(|e| encryption_error(format!("HKDF expansion failed: {e}")))?;
441
442 let kek = KwAes256::new(&kek_bytes.into());
444 let mut wrapped = vec![0u8; content_key.len() + 8]; kek.wrap_key(content_key, &mut wrapped)
446 .map_err(|e| encryption_error(format!("AES key wrap failed: {e}")))?;
447
448 let ephemeral_public_bytes = ephemeral_public.to_sec1_bytes().to_vec();
450
451 Ok(WrappedKeyData {
452 wrapped_key: wrapped,
453 ephemeral_public_key: ephemeral_public_bytes,
454 })
455 }
456}
457
458#[cfg(feature = "key-wrapping")]
464pub struct EcdhEsKeyUnwrapper {
465 recipient_secret: p256::SecretKey,
467}
468
469#[cfg(feature = "key-wrapping")]
470impl EcdhEsKeyUnwrapper {
471 #[must_use]
473 pub fn new(recipient_secret: p256::SecretKey) -> Self {
474 Self { recipient_secret }
475 }
476
477 pub fn unwrap(&self, data: &WrappedKeyData) -> Result<Vec<u8>> {
484 use aes_kw::{cipher::KeyInit, KwAes256};
485 use hkdf::Hkdf;
486 use sha2::Sha256;
487
488 let ephemeral_public = p256::PublicKey::from_sec1_bytes(&data.ephemeral_public_key)
490 .map_err(|e| encryption_error(format!("Invalid ephemeral public key: {e}")))?;
491
492 let shared_secret = p256::ecdh::diffie_hellman(
494 self.recipient_secret.to_nonzero_scalar(),
495 ephemeral_public.as_affine(),
496 );
497
498 let hkdf = Hkdf::<Sha256>::new(None, shared_secret.raw_secret_bytes());
500 let mut kek_bytes = [0u8; 32];
501 hkdf.expand(b"ECDH-ES+A256KW", &mut kek_bytes)
502 .map_err(|e| encryption_error(format!("HKDF expansion failed: {e}")))?;
503
504 let kek = KwAes256::new(&kek_bytes.into());
506 let unwrapped_len = data
507 .wrapped_key
508 .len()
509 .checked_sub(8)
510 .ok_or_else(|| encryption_error("Wrapped key too short"))?;
511 let mut unwrapped = vec![0u8; unwrapped_len];
512 kek.unwrap_key(&data.wrapped_key, &mut unwrapped)
513 .map_err(|e| encryption_error(format!("AES key unwrap failed: {e}")))?;
514 Ok(unwrapped)
515 }
516}
517
518#[cfg(feature = "key-wrapping-rsa")]
524#[derive(Debug, Clone)]
525pub struct RsaWrappedKeyData {
526 pub wrapped_key: Vec<u8>,
528}
529
530#[cfg(feature = "key-wrapping-rsa")]
535pub struct RsaOaepKeyWrapper {
536 recipient_public_key: rsa::RsaPublicKey,
537}
538
539#[cfg(feature = "key-wrapping-rsa")]
540impl RsaOaepKeyWrapper {
541 #[must_use]
543 pub fn new(recipient_public_key: rsa::RsaPublicKey) -> Self {
544 Self {
545 recipient_public_key,
546 }
547 }
548
549 pub fn wrap(&self, content_key: &[u8]) -> Result<RsaWrappedKeyData> {
555 use rsa::oaep::EncryptingKey;
556 use rsa::sha2::Sha256;
557 use rsa::traits::RandomizedEncryptor;
558
559 let encrypting_key = EncryptingKey::<Sha256>::new(self.recipient_public_key.clone());
560 let wrapped_key = encrypting_key
561 .encrypt_with_rng(&mut rand_core::UnwrapErr(getrandom::SysRng), content_key)
562 .map_err(|e| encryption_error(format!("RSA-OAEP wrap failed: {e}")))?;
563
564 Ok(RsaWrappedKeyData { wrapped_key })
565 }
566}
567
568#[cfg(feature = "key-wrapping-rsa")]
572pub struct RsaOaepKeyUnwrapper {
573 recipient_private_key: rsa::RsaPrivateKey,
574}
575
576#[cfg(feature = "key-wrapping-rsa")]
577impl RsaOaepKeyUnwrapper {
578 #[must_use]
580 pub fn new(recipient_private_key: rsa::RsaPrivateKey) -> Self {
581 Self {
582 recipient_private_key,
583 }
584 }
585
586 pub fn unwrap(&self, data: &RsaWrappedKeyData) -> Result<Vec<u8>> {
592 use rsa::oaep::DecryptingKey;
593 use rsa::sha2::Sha256;
594 use rsa::traits::Decryptor;
595
596 let decrypting_key = DecryptingKey::<Sha256>::new(self.recipient_private_key.clone());
597 decrypting_key
598 .decrypt(&data.wrapped_key)
599 .map_err(|e| encryption_error(format!("RSA-OAEP unwrap failed: {e}")))
600 }
601}
602
603#[cfg(feature = "key-wrapping-pbes2")]
609#[derive(Debug, Clone)]
610pub struct Pbes2WrappedKeyData {
611 pub wrapped_key: Vec<u8>,
613 pub salt: Vec<u8>,
615 pub iterations: u32,
617}
618
619#[cfg(feature = "key-wrapping-pbes2")]
624pub struct Pbes2KeyWrapper {
625 password: zeroize::Zeroizing<Vec<u8>>,
626 iterations: u32,
627}
628
629#[cfg(feature = "key-wrapping-pbes2")]
630impl Pbes2KeyWrapper {
631 pub const DEFAULT_ITERATIONS: u32 = 600_000;
633
634 pub const MIN_ITERATIONS: u32 = 10_000;
636
637 pub const MAX_ITERATIONS: u32 = 10_000_000;
639
640 pub fn new(password: impl AsRef<[u8]>, iterations: u32) -> Result<Self> {
647 if !(Self::MIN_ITERATIONS..=Self::MAX_ITERATIONS).contains(&iterations) {
648 return Err(encryption_error(format!(
649 "PBKDF2 iterations must be between {} and {}, got {iterations}",
650 Self::MIN_ITERATIONS,
651 Self::MAX_ITERATIONS
652 )));
653 }
654 Ok(Self {
655 password: zeroize::Zeroizing::new(password.as_ref().to_vec()),
656 iterations,
657 })
658 }
659
660 pub fn wrap(&self, content_key: &[u8]) -> Result<Pbes2WrappedKeyData> {
669 use aes_kw::{cipher::KeyInit, KwAes256};
670
671 let mut salt = [0u8; 16];
673 getrandom::fill(&mut salt)
674 .map_err(|e| encryption_error(format!("System RNG failed: {e}")))?;
675
676 let mut kek_bytes = [0u8; 32];
678 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(&self.password, &salt, self.iterations, &mut kek_bytes);
679
680 let kek = KwAes256::new(&kek_bytes.into());
682 let mut wrapped = vec![0u8; content_key.len() + 8];
683 kek.wrap_key(content_key, &mut wrapped)
684 .map_err(|e| encryption_error(format!("PBES2 AES key wrap failed: {e}")))?;
685
686 Ok(Pbes2WrappedKeyData {
687 wrapped_key: wrapped,
688 salt: salt.to_vec(),
689 iterations: self.iterations,
690 })
691 }
692}
693
694#[cfg(feature = "key-wrapping-pbes2")]
699pub struct Pbes2KeyUnwrapper {
700 password: zeroize::Zeroizing<Vec<u8>>,
701}
702
703#[cfg(feature = "key-wrapping-pbes2")]
704impl Pbes2KeyUnwrapper {
705 #[must_use]
707 pub fn new(password: impl AsRef<[u8]>) -> Self {
708 Self {
709 password: zeroize::Zeroizing::new(password.as_ref().to_vec()),
710 }
711 }
712
713 pub fn unwrap(&self, data: &Pbes2WrappedKeyData) -> Result<Vec<u8>> {
723 use aes_kw::{cipher::KeyInit, KwAes256};
724
725 if !(Pbes2KeyWrapper::MIN_ITERATIONS..=Pbes2KeyWrapper::MAX_ITERATIONS)
727 .contains(&data.iterations)
728 {
729 return Err(encryption_error(format!(
730 "PBKDF2 iterations must be between {} and {}, got {}",
731 Pbes2KeyWrapper::MIN_ITERATIONS,
732 Pbes2KeyWrapper::MAX_ITERATIONS,
733 data.iterations
734 )));
735 }
736
737 let mut kek_bytes = [0u8; 32];
739 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(
740 &self.password,
741 &data.salt,
742 data.iterations,
743 &mut kek_bytes,
744 );
745
746 let kek = KwAes256::new(&kek_bytes.into());
748 let unwrapped_len = data
749 .wrapped_key
750 .len()
751 .checked_sub(8)
752 .ok_or_else(|| encryption_error("Wrapped key too short"))?;
753 let mut unwrapped = vec![0u8; unwrapped_len];
754 kek.unwrap_key(&data.wrapped_key, &mut unwrapped)
755 .map_err(|e| encryption_error(format!("PBES2 AES key unwrap failed: {e}")))?;
756 Ok(unwrapped)
757 }
758}
759
760#[cfg(all(test, feature = "encryption"))]
761mod tests {
762 use super::*;
763
764 #[test]
765 fn test_encrypt_decrypt() {
766 let key = Aes256GcmEncryptor::generate_key();
767 let encryptor = Aes256GcmEncryptor::new(&key).unwrap();
768
769 let plaintext = b"Hello, World! This is a test message.";
770 let encrypted = encryptor.encrypt(plaintext).unwrap();
771
772 assert_ne!(&encrypted.ciphertext[..plaintext.len()], plaintext);
773
774 let decrypted = encryptor
775 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
776 .unwrap();
777 assert_eq!(decrypted, plaintext);
778 }
779
780 #[test]
781 fn test_wrong_key_fails() {
782 let key1 = Aes256GcmEncryptor::generate_key();
783 let key2 = Aes256GcmEncryptor::generate_key();
784
785 let encryptor1 = Aes256GcmEncryptor::new(&key1).unwrap();
786 let encryptor2 = Aes256GcmEncryptor::new(&key2).unwrap();
787
788 let plaintext = b"Secret message";
789 let encrypted = encryptor1.encrypt(plaintext).unwrap();
790
791 let result = encryptor2.decrypt(&encrypted.ciphertext, &encrypted.nonce);
792 assert!(result.is_err());
793 }
794
795 #[test]
796 fn test_tampered_data_fails() {
797 let key = Aes256GcmEncryptor::generate_key();
798 let encryptor = Aes256GcmEncryptor::new(&key).unwrap();
799
800 let plaintext = b"Original message";
801 let mut encrypted = encryptor.encrypt(plaintext).unwrap();
802
803 if !encrypted.ciphertext.is_empty() {
805 encrypted.ciphertext[0] ^= 0xFF;
806 }
807
808 let result = encryptor.decrypt(&encrypted.ciphertext, &encrypted.nonce);
809 assert!(result.is_err());
810 }
811
812 #[test]
813 fn test_empty_plaintext() {
814 let key = Aes256GcmEncryptor::generate_key();
815 let encryptor = Aes256GcmEncryptor::new(&key).unwrap();
816
817 let plaintext = b"";
818 let encrypted = encryptor.encrypt(plaintext).unwrap();
819 let decrypted = encryptor
820 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
821 .unwrap();
822
823 assert_eq!(decrypted, plaintext);
824 }
825
826 #[test]
827 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
828 fn test_large_plaintext() {
829 let key = Aes256GcmEncryptor::generate_key();
830 let encryptor = Aes256GcmEncryptor::new(&key).unwrap();
831
832 let plaintext: Vec<u8> = (0..1024 * 1024).map(|i| (i % 256) as u8).collect();
834 let encrypted = encryptor.encrypt(&plaintext).unwrap();
835 let decrypted = encryptor
836 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
837 .unwrap();
838
839 assert_eq!(decrypted, plaintext);
840 }
841
842 #[test]
843 fn test_encryption_metadata_serialization() {
844 let metadata = EncryptionMetadata {
845 algorithm: EncryptionAlgorithm::Aes256Gcm,
846 kdf: Some(KeyDerivation {
847 algorithm: KdfAlgorithm::Argon2id,
848 salt: "base64salt".to_string(),
849 iterations: None,
850 memory: Some(65536),
851 parallelism: Some(4),
852 }),
853 wrapped_key: None,
854 key_management: None,
855 recipients: vec![],
856 };
857
858 let json = serde_json::to_string_pretty(&metadata).unwrap();
859 assert!(json.contains("AES-256-GCM"));
860 assert!(json.contains("Argon2id"));
861
862 let deserialized: EncryptionMetadata = serde_json::from_str(&json).unwrap();
863 assert_eq!(deserialized.algorithm, metadata.algorithm);
864 }
865
866 #[test]
867 fn test_key_management_algorithm_roundtrip() {
868 let variants = [
869 (KeyManagementAlgorithm::EcdhEsA256kw, "\"ECDH-ES+A256KW\""),
870 (KeyManagementAlgorithm::RsaOaep256, "\"RSA-OAEP-256\""),
871 (
872 KeyManagementAlgorithm::Pbes2HsA256kw,
873 "\"PBES2-HS256+A256KW\"",
874 ),
875 ];
876 for (alg, expected_json) in &variants {
877 let json = serde_json::to_string(alg).unwrap();
878 assert_eq!(&json, expected_json);
879 let parsed: KeyManagementAlgorithm = serde_json::from_str(&json).unwrap();
880 assert_eq!(&parsed, alg);
881 }
882 }
883
884 #[test]
885 fn test_encryption_metadata_with_key_management() {
886 let metadata = EncryptionMetadata {
887 algorithm: EncryptionAlgorithm::Aes256Gcm,
888 kdf: None,
889 wrapped_key: Some("wrapped-key-base64".to_string()),
890 key_management: Some(KeyManagementAlgorithm::EcdhEsA256kw),
891 recipients: vec![Recipient {
892 id: "recipient-1".to_string(),
893 encrypted_key: "enc-key-base64".to_string(),
894 algorithm: Some("ECDH-ES+A256KW".to_string()),
895 ephemeral_public_key: Some("ephemeral-pk-base64".to_string()),
896 }],
897 };
898
899 let json = serde_json::to_string_pretty(&metadata).unwrap();
900 assert!(json.contains("ECDH-ES+A256KW"));
901 assert!(json.contains("ephemeralPublicKey"));
902
903 let parsed: EncryptionMetadata = serde_json::from_str(&json).unwrap();
904 assert_eq!(parsed.key_management, metadata.key_management);
905 assert_eq!(
906 parsed.recipients[0].ephemeral_public_key,
907 Some("ephemeral-pk-base64".to_string())
908 );
909 }
910
911 #[test]
912 fn test_encryption_metadata_backward_compat() {
913 let json = r#"{
915 "algorithm": "AES-256-GCM",
916 "recipients": [{
917 "id": "r1",
918 "encryptedKey": "key-data"
919 }]
920 }"#;
921 let metadata: EncryptionMetadata = serde_json::from_str(json).unwrap();
922 assert!(metadata.key_management.is_none());
923 assert!(metadata.recipients[0].ephemeral_public_key.is_none());
924 }
925}
926
927#[cfg(all(test, feature = "encryption-chacha"))]
928mod chacha_tests {
929 use super::*;
930
931 #[test]
932 fn test_chacha_encrypt_decrypt() {
933 let key = ChaCha20Poly1305Encryptor::generate_key();
934 let encryptor = ChaCha20Poly1305Encryptor::new(&key).unwrap();
935
936 let plaintext = b"Hello, World! This is a test message.";
937 let encrypted = encryptor.encrypt(plaintext).unwrap();
938
939 assert_ne!(&encrypted.ciphertext[..plaintext.len()], plaintext);
940
941 let decrypted = encryptor
942 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
943 .unwrap();
944 assert_eq!(decrypted, plaintext);
945 }
946
947 #[test]
948 fn test_chacha_wrong_key_fails() {
949 let key1 = ChaCha20Poly1305Encryptor::generate_key();
950 let key2 = ChaCha20Poly1305Encryptor::generate_key();
951
952 let encryptor1 = ChaCha20Poly1305Encryptor::new(&key1).unwrap();
953 let encryptor2 = ChaCha20Poly1305Encryptor::new(&key2).unwrap();
954
955 let plaintext = b"Secret message";
956 let encrypted = encryptor1.encrypt(plaintext).unwrap();
957
958 let result = encryptor2.decrypt(&encrypted.ciphertext, &encrypted.nonce);
959 assert!(result.is_err());
960 }
961
962 #[test]
963 fn test_chacha_tampered_data_fails() {
964 let key = ChaCha20Poly1305Encryptor::generate_key();
965 let encryptor = ChaCha20Poly1305Encryptor::new(&key).unwrap();
966
967 let plaintext = b"Original message";
968 let mut encrypted = encryptor.encrypt(plaintext).unwrap();
969
970 if !encrypted.ciphertext.is_empty() {
972 encrypted.ciphertext[0] ^= 0xFF;
973 }
974
975 let result = encryptor.decrypt(&encrypted.ciphertext, &encrypted.nonce);
976 assert!(result.is_err());
977 }
978
979 #[test]
980 fn test_chacha_empty_plaintext() {
981 let key = ChaCha20Poly1305Encryptor::generate_key();
982 let encryptor = ChaCha20Poly1305Encryptor::new(&key).unwrap();
983
984 let plaintext = b"";
985 let encrypted = encryptor.encrypt(plaintext).unwrap();
986 let decrypted = encryptor
987 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
988 .unwrap();
989
990 assert_eq!(decrypted, plaintext);
991 }
992
993 #[test]
994 fn test_chacha_encryption_algorithm_enum() {
995 let algo = EncryptionAlgorithm::ChaCha20Poly1305;
996 assert_eq!(algo.as_str(), "ChaCha20-Poly1305");
997 assert_eq!(algo.key_size(), 32);
998 assert_eq!(algo.nonce_size(), 12);
999
1000 let json = serde_json::to_string(&algo).unwrap();
1001 assert_eq!(json, "\"ChaCha20-Poly1305\"");
1002
1003 let deserialized: EncryptionAlgorithm = serde_json::from_str(&json).unwrap();
1004 assert_eq!(deserialized, algo);
1005 }
1006}
1007
1008#[cfg(all(test, feature = "key-wrapping"))]
1009mod key_wrapping_tests {
1010 use super::*;
1011
1012 fn generate_keypair() -> (p256::SecretKey, p256::PublicKey) {
1014 use p256::elliptic_curve::Generate;
1015 let secret = p256::SecretKey::generate();
1016 let public = secret.public_key();
1017 (secret, public)
1018 }
1019
1020 #[test]
1021 fn test_wrap_unwrap_roundtrip() {
1022 let (secret, public) = generate_keypair();
1023
1024 let content_key = Aes256GcmEncryptor::generate_key();
1026
1027 let wrapper = EcdhEsKeyWrapper::new(public);
1029 let wrapped = wrapper.wrap(&content_key).unwrap();
1030
1031 assert_eq!(wrapped.wrapped_key.len(), content_key.len() + 8);
1033 assert_eq!(wrapped.ephemeral_public_key.len(), 65);
1035
1036 let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1038 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1039
1040 assert_eq!(recovered, content_key);
1041 }
1042
1043 #[test]
1044 fn test_multi_recipient_wrap() {
1045 let (secret_a, public_a) = generate_keypair();
1046 let (secret_b, public_b) = generate_keypair();
1047
1048 let content_key = Aes256GcmEncryptor::generate_key();
1049
1050 let wrapper_a = EcdhEsKeyWrapper::new(public_a);
1052 let wrapped_a = wrapper_a.wrap(&content_key).unwrap();
1053
1054 let wrapper_b = EcdhEsKeyWrapper::new(public_b);
1055 let wrapped_b = wrapper_b.wrap(&content_key).unwrap();
1056
1057 let unwrapper_a = EcdhEsKeyUnwrapper::new(secret_a);
1059 let recovered_a = unwrapper_a.unwrap(&wrapped_a).unwrap();
1060 assert_eq!(recovered_a, content_key);
1061
1062 let unwrapper_b = EcdhEsKeyUnwrapper::new(secret_b);
1063 let recovered_b = unwrapper_b.unwrap(&wrapped_b).unwrap();
1064 assert_eq!(recovered_b, content_key);
1065
1066 assert!(unwrapper_a.unwrap(&wrapped_b).is_err());
1068 assert!(unwrapper_b.unwrap(&wrapped_a).is_err());
1069 }
1070
1071 #[test]
1072 fn test_wrong_private_key_fails() {
1073 let (_secret, public) = generate_keypair();
1074 let (wrong_secret, _wrong_public) = generate_keypair();
1075
1076 let content_key = Aes256GcmEncryptor::generate_key();
1077
1078 let wrapper = EcdhEsKeyWrapper::new(public);
1079 let wrapped = wrapper.wrap(&content_key).unwrap();
1080
1081 let unwrapper = EcdhEsKeyUnwrapper::new(wrong_secret);
1083 let result = unwrapper.unwrap(&wrapped);
1084 assert!(result.is_err());
1085 }
1086
1087 #[test]
1088 fn test_tampered_wrapped_key_fails() {
1089 let (secret, public) = generate_keypair();
1090
1091 let content_key = Aes256GcmEncryptor::generate_key();
1092
1093 let wrapper = EcdhEsKeyWrapper::new(public);
1094 let mut wrapped = wrapper.wrap(&content_key).unwrap();
1095
1096 if !wrapped.wrapped_key.is_empty() {
1098 wrapped.wrapped_key[0] ^= 0xFF;
1099 }
1100
1101 let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1102 let result = unwrapper.unwrap(&wrapped);
1103 assert!(result.is_err());
1104 }
1105
1106 #[test]
1107 fn test_tampered_ephemeral_key_fails() {
1108 let (secret, public) = generate_keypair();
1109
1110 let content_key = Aes256GcmEncryptor::generate_key();
1111
1112 let wrapper = EcdhEsKeyWrapper::new(public);
1113 let mut wrapped = wrapper.wrap(&content_key).unwrap();
1114
1115 if wrapped.ephemeral_public_key.len() > 1 {
1118 wrapped.ephemeral_public_key[1] ^= 0xFF;
1119 }
1120
1121 let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1122 let result = unwrapper.unwrap(&wrapped);
1123 assert!(result.is_err());
1125 }
1126
1127 #[test]
1128 fn test_integration_encrypt_wrap_unwrap_decrypt() {
1129 let (secret, public) = generate_keypair();
1130
1131 let content_key = Aes256GcmEncryptor::generate_key();
1133 let encryptor = Aes256GcmEncryptor::new(&content_key).unwrap();
1134 let plaintext = b"Codex document content for encryption";
1135 let encrypted = encryptor.encrypt(plaintext).unwrap();
1136
1137 let wrapper = EcdhEsKeyWrapper::new(public);
1139 let wrapped = wrapper.wrap(&content_key).unwrap();
1140
1141 let metadata = EncryptionMetadata {
1143 algorithm: EncryptionAlgorithm::Aes256Gcm,
1144 kdf: None,
1145 wrapped_key: None,
1146 key_management: Some(KeyManagementAlgorithm::EcdhEsA256kw),
1147 recipients: vec![Recipient {
1148 id: "recipient-1".to_string(),
1149 encrypted_key: base64::Engine::encode(
1150 &base64::engine::general_purpose::STANDARD,
1151 &wrapped.wrapped_key,
1152 ),
1153 algorithm: Some("ECDH-ES+A256KW".to_string()),
1154 ephemeral_public_key: Some(base64::Engine::encode(
1155 &base64::engine::general_purpose::STANDARD,
1156 &wrapped.ephemeral_public_key,
1157 )),
1158 }],
1159 };
1160
1161 let json = serde_json::to_string(&metadata).unwrap();
1163 let parsed: EncryptionMetadata = serde_json::from_str(&json).unwrap();
1164
1165 let recipient = &parsed.recipients[0];
1167 let wrapped_data = WrappedKeyData {
1168 wrapped_key: base64::Engine::decode(
1169 &base64::engine::general_purpose::STANDARD,
1170 &recipient.encrypted_key,
1171 )
1172 .unwrap(),
1173 ephemeral_public_key: base64::Engine::decode(
1174 &base64::engine::general_purpose::STANDARD,
1175 recipient.ephemeral_public_key.as_ref().unwrap(),
1176 )
1177 .unwrap(),
1178 };
1179
1180 let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1181 let recovered_key = unwrapper.unwrap(&wrapped_data).unwrap();
1182
1183 let decryptor = Aes256GcmEncryptor::new(&recovered_key).unwrap();
1185 let decrypted = decryptor
1186 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
1187 .unwrap();
1188
1189 assert_eq!(decrypted, plaintext);
1190 }
1191
1192 #[test]
1193 fn test_wrap_16_byte_key() {
1194 let (secret, public) = generate_keypair();
1196 let content_key = [0x42u8; 16]; let wrapper = EcdhEsKeyWrapper::new(public);
1199 let wrapped = wrapper.wrap(&content_key).unwrap();
1200 assert_eq!(wrapped.wrapped_key.len(), 24); let unwrapper = EcdhEsKeyUnwrapper::new(secret);
1203 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1204 assert_eq!(recovered, content_key);
1205 }
1206}
1207
1208#[cfg(all(test, feature = "key-wrapping-rsa"))]
1209mod rsa_oaep_tests {
1210 use super::*;
1211
1212 fn generate_rsa_keypair(bits: usize) -> (rsa::RsaPrivateKey, rsa::RsaPublicKey) {
1213 let private_key =
1214 rsa::RsaPrivateKey::new(&mut rand_core::UnwrapErr(getrandom::SysRng), bits).unwrap();
1215 let public_key = rsa::RsaPublicKey::from(&private_key);
1216 (private_key, public_key)
1217 }
1218
1219 #[test]
1220 fn test_rsa_oaep_roundtrip_2048() {
1221 let (private_key, public_key) = generate_rsa_keypair(2048);
1222 let content_key = Aes256GcmEncryptor::generate_key();
1223
1224 let wrapper = RsaOaepKeyWrapper::new(public_key);
1225 let wrapped = wrapper.wrap(&content_key).unwrap();
1226
1227 let unwrapper = RsaOaepKeyUnwrapper::new(private_key);
1228 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1229
1230 assert_eq!(recovered, content_key);
1231 }
1232
1233 #[test]
1234 fn test_rsa_oaep_roundtrip_4096() {
1235 let (private_key, public_key) = generate_rsa_keypair(4096);
1236 let content_key = Aes256GcmEncryptor::generate_key();
1237
1238 let wrapper = RsaOaepKeyWrapper::new(public_key);
1239 let wrapped = wrapper.wrap(&content_key).unwrap();
1240
1241 let unwrapper = RsaOaepKeyUnwrapper::new(private_key);
1242 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1243
1244 assert_eq!(recovered, content_key);
1245 }
1246
1247 #[test]
1248 fn test_rsa_oaep_wrong_key_fails() {
1249 let (_private_key, public_key) = generate_rsa_keypair(2048);
1250 let (wrong_private_key, _wrong_public_key) = generate_rsa_keypair(2048);
1251 let content_key = Aes256GcmEncryptor::generate_key();
1252
1253 let wrapper = RsaOaepKeyWrapper::new(public_key);
1254 let wrapped = wrapper.wrap(&content_key).unwrap();
1255
1256 let unwrapper = RsaOaepKeyUnwrapper::new(wrong_private_key);
1257 let result = unwrapper.unwrap(&wrapped);
1258 assert!(result.is_err());
1259 }
1260
1261 #[test]
1262 fn test_rsa_oaep_tampered_data_fails() {
1263 let (private_key, public_key) = generate_rsa_keypair(2048);
1264 let content_key = Aes256GcmEncryptor::generate_key();
1265
1266 let wrapper = RsaOaepKeyWrapper::new(public_key);
1267 let mut wrapped = wrapper.wrap(&content_key).unwrap();
1268
1269 if !wrapped.wrapped_key.is_empty() {
1270 wrapped.wrapped_key[0] ^= 0xFF;
1271 }
1272
1273 let unwrapper = RsaOaepKeyUnwrapper::new(private_key);
1274 let result = unwrapper.unwrap(&wrapped);
1275 assert!(result.is_err());
1276 }
1277
1278 #[test]
1279 fn test_rsa_oaep_integration_encrypt_wrap_unwrap_decrypt() {
1280 let (private_key, public_key) = generate_rsa_keypair(2048);
1281
1282 let content_key = Aes256GcmEncryptor::generate_key();
1284 let encryptor = Aes256GcmEncryptor::new(&content_key).unwrap();
1285 let plaintext = b"Codex document encrypted with RSA-OAEP key wrapping";
1286 let encrypted = encryptor.encrypt(plaintext).unwrap();
1287
1288 let wrapper = RsaOaepKeyWrapper::new(public_key);
1290 let wrapped = wrapper.wrap(&content_key).unwrap();
1291
1292 let unwrapper = RsaOaepKeyUnwrapper::new(private_key);
1294 let recovered_key = unwrapper.unwrap(&wrapped).unwrap();
1295
1296 let decryptor = Aes256GcmEncryptor::new(&recovered_key).unwrap();
1297 let decrypted = decryptor
1298 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
1299 .unwrap();
1300
1301 assert_eq!(decrypted, plaintext);
1302 }
1303
1304 #[test]
1305 fn test_rsa_oaep_metadata_serialization() {
1306 let metadata = EncryptionMetadata {
1307 algorithm: EncryptionAlgorithm::Aes256Gcm,
1308 kdf: None,
1309 wrapped_key: None,
1310 key_management: Some(KeyManagementAlgorithm::RsaOaep256),
1311 recipients: vec![Recipient {
1312 id: "rsa-recipient".to_string(),
1313 encrypted_key: "wrapped-key-base64".to_string(),
1314 algorithm: Some("RSA-OAEP-256".to_string()),
1315 ephemeral_public_key: None,
1316 }],
1317 };
1318
1319 let json = serde_json::to_string_pretty(&metadata).unwrap();
1320 assert!(json.contains("RSA-OAEP-256"));
1321 assert!(!json.contains("ephemeralPublicKey"));
1323
1324 let parsed: EncryptionMetadata = serde_json::from_str(&json).unwrap();
1325 assert_eq!(
1326 parsed.key_management,
1327 Some(KeyManagementAlgorithm::RsaOaep256)
1328 );
1329 }
1330}
1331
1332#[cfg(all(test, feature = "key-wrapping-pbes2"))]
1333mod pbes2_tests {
1334 use super::*;
1335
1336 #[test]
1337 fn test_pbes2_roundtrip() {
1338 let password = b"correct horse battery staple";
1339 let content_key = Aes256GcmEncryptor::generate_key();
1340
1341 let wrapper = Pbes2KeyWrapper::new(password, Pbes2KeyWrapper::DEFAULT_ITERATIONS).unwrap();
1342 let wrapped = wrapper.wrap(&content_key).unwrap();
1343
1344 assert_eq!(wrapped.wrapped_key.len(), content_key.len() + 8);
1345 assert_eq!(wrapped.salt.len(), 16);
1346 assert_eq!(wrapped.iterations, Pbes2KeyWrapper::DEFAULT_ITERATIONS);
1347
1348 let unwrapper = Pbes2KeyUnwrapper::new(password);
1349 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1350
1351 assert_eq!(recovered, content_key);
1352 }
1353
1354 #[test]
1355 fn test_pbes2_wrong_password_fails() {
1356 let content_key = Aes256GcmEncryptor::generate_key();
1357
1358 let wrapper =
1359 Pbes2KeyWrapper::new(b"correct password", Pbes2KeyWrapper::MIN_ITERATIONS).unwrap();
1360 let wrapped = wrapper.wrap(&content_key).unwrap();
1361
1362 let unwrapper = Pbes2KeyUnwrapper::new(b"wrong password");
1363 let result = unwrapper.unwrap(&wrapped);
1364 assert!(result.is_err());
1365 }
1366
1367 #[test]
1368 fn test_pbes2_tampered_salt_fails() {
1369 let password = b"my password";
1370 let content_key = Aes256GcmEncryptor::generate_key();
1371
1372 let wrapper = Pbes2KeyWrapper::new(password, Pbes2KeyWrapper::MIN_ITERATIONS).unwrap();
1373 let mut wrapped = wrapper.wrap(&content_key).unwrap();
1374
1375 if !wrapped.salt.is_empty() {
1377 wrapped.salt[0] ^= 0xFF;
1378 }
1379
1380 let unwrapper = Pbes2KeyUnwrapper::new(password);
1381 let result = unwrapper.unwrap(&wrapped);
1382 assert!(result.is_err());
1383 }
1384
1385 #[test]
1386 fn test_pbes2_different_iteration_counts() {
1387 let password = b"shared password";
1388 let content_key = Aes256GcmEncryptor::generate_key();
1389
1390 for &iterations in &[10_000u32, 100_000, 1_000_000] {
1392 let wrapper = Pbes2KeyWrapper::new(password, iterations).unwrap();
1393 let wrapped = wrapper.wrap(&content_key).unwrap();
1394 assert_eq!(wrapped.iterations, iterations);
1395
1396 let unwrapper = Pbes2KeyUnwrapper::new(password);
1397 let recovered = unwrapper.unwrap(&wrapped).unwrap();
1398 assert_eq!(recovered, content_key);
1399 }
1400 }
1401
1402 #[test]
1403 fn test_pbes2_iteration_bounds() {
1404 assert!(Pbes2KeyWrapper::new(b"password", 0).is_err());
1406 assert!(Pbes2KeyWrapper::new(b"password", 1).is_err());
1407 assert!(Pbes2KeyWrapper::new(b"password", 9_999).is_err());
1408
1409 assert!(Pbes2KeyWrapper::new(b"password", 10_000).is_ok());
1411
1412 assert!(Pbes2KeyWrapper::new(b"password", 10_000_000).is_ok());
1414
1415 assert!(Pbes2KeyWrapper::new(b"password", 10_000_001).is_err());
1417 assert!(Pbes2KeyWrapper::new(b"password", u32::MAX).is_err());
1418 }
1419
1420 #[test]
1421 fn test_pbes2_unwrap_rejects_bad_iterations() {
1422 let unwrapper = Pbes2KeyUnwrapper::new(b"password");
1423 let data = Pbes2WrappedKeyData {
1424 wrapped_key: vec![0u8; 40],
1425 salt: vec![0u8; 16],
1426 iterations: 0,
1427 };
1428 assert!(unwrapper.unwrap(&data).is_err());
1429 }
1430
1431 #[test]
1432 fn test_pbes2_integration_encrypt_wrap_unwrap_decrypt() {
1433 let password = b"document encryption password";
1434
1435 let content_key = Aes256GcmEncryptor::generate_key();
1437 let encryptor = Aes256GcmEncryptor::new(&content_key).unwrap();
1438 let plaintext = b"Codex document with password-based key wrapping";
1439 let encrypted = encryptor.encrypt(plaintext).unwrap();
1440
1441 let wrapper = Pbes2KeyWrapper::new(password, Pbes2KeyWrapper::MIN_ITERATIONS).unwrap();
1443 let wrapped = wrapper.wrap(&content_key).unwrap();
1444
1445 let unwrapper = Pbes2KeyUnwrapper::new(password);
1447 let recovered_key = unwrapper.unwrap(&wrapped).unwrap();
1448
1449 let decryptor = Aes256GcmEncryptor::new(&recovered_key).unwrap();
1450 let decrypted = decryptor
1451 .decrypt(&encrypted.ciphertext, &encrypted.nonce)
1452 .unwrap();
1453
1454 assert_eq!(decrypted, plaintext);
1455 }
1456
1457 #[test]
1458 fn test_pbes2_metadata_serialization() {
1459 let metadata = EncryptionMetadata {
1460 algorithm: EncryptionAlgorithm::Aes256Gcm,
1461 kdf: None,
1462 wrapped_key: None,
1463 key_management: Some(KeyManagementAlgorithm::Pbes2HsA256kw),
1464 recipients: vec![],
1465 };
1466
1467 let json = serde_json::to_string(&metadata).unwrap();
1468 assert!(json.contains("PBES2-HS256+A256KW"));
1469
1470 let parsed: EncryptionMetadata = serde_json::from_str(&json).unwrap();
1471 assert_eq!(
1472 parsed.key_management,
1473 Some(KeyManagementAlgorithm::Pbes2HsA256kw)
1474 );
1475 }
1476}