1use aes_gcm::{
9 aead::{Aead, KeyInit},
10 Aes256Gcm, Nonce, Key
11};
12use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
13use hkdf::Hkdf;
14use rand_core::{OsRng, RngCore};
15use sha2::Sha256;
16use std::{fs, io::Write, path::Path};
17use thiserror::Error;
18use x25519_dalek::{EphemeralSecret, PublicKey, x25519};
19use zeroize::Zeroizing;
20use crate::types::EncryptionInfo;
21
22#[derive(Debug, Error)]
24pub enum EncryptionError {
25 #[error("Failed to generate random bytes: {0}")]
26 RandomGeneration(String),
27
28 #[error("Failed to encrypt data: {0}")]
29 EncryptionFailed(String),
30
31 #[error("Failed to decrypt data: {0}")]
32 DecryptionFailed(String),
33
34 #[error("Invalid key format: {0}")]
35 InvalidKey(String),
36
37 #[error("IO error: {0}")]
38 Io(#[from] std::io::Error),
39
40 #[error("Base64 decode error: {0}")]
41 Base64Decode(#[from] base64::DecodeError),
42
43 #[error("Invalid nonce size: expected 12 bytes, got {0}")]
44 InvalidNonceSize(usize),
45
46 #[error("Invalid auth tag size: expected 16 bytes, got {0}")]
47 InvalidAuthTagSize(usize),
48}
49
50
51pub struct EncryptionManager {
53 recipient_public_key: PublicKey,
55}
56
57#[derive(Debug)]
59pub struct EncryptionResult {
60 pub ciphertext: Vec<u8>,
62 pub info: EncryptionInfo,
64}
65
66pub struct KeyPair {
69 pub secret: Zeroizing<[u8; 32]>,
71 pub public: PublicKey,
73}
74
75impl EncryptionManager {
76 pub fn new(recipient_public_key: PublicKey) -> Self {
78 Self {
79 recipient_public_key,
80 }
81 }
82
83 pub fn from_base64_public_key(public_key_b64: &str) -> Result<Self, EncryptionError> {
85 let key_bytes = BASE64.decode(public_key_b64)?;
86 if key_bytes.len() != 32 {
87 return Err(EncryptionError::InvalidKey(
88 format!("Expected 32 bytes, got {}", key_bytes.len())
89 ));
90 }
91
92 let mut key_array = [0u8; 32];
93 key_array.copy_from_slice(&key_bytes);
94 let public_key = PublicKey::from(key_array);
95
96 Ok(Self::new(public_key))
97 }
98
99 pub fn from_public_key_file<P: AsRef<Path>>(path: P) -> Result<Self, EncryptionError> {
101 let key_bytes = fs::read(path)?;
102 if key_bytes.len() != 32 {
103 return Err(EncryptionError::InvalidKey(
104 format!("Expected 32 bytes in key file, got {}", key_bytes.len())
105 ));
106 }
107
108 let mut key_array = [0u8; 32];
109 key_array.copy_from_slice(&key_bytes);
110 let public_key = PublicKey::from(key_array);
111
112 Ok(Self::new(public_key))
113 }
114
115 pub fn encrypt(&self, plaintext: &[u8]) -> Result<EncryptionResult, EncryptionError> {
117 let ephemeral_secret = EphemeralSecret::random_from_rng(OsRng);
119 let ephemeral_public = PublicKey::from(&ephemeral_secret);
120
121 let shared_secret = ephemeral_secret.diffie_hellman(&self.recipient_public_key);
123
124 let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
126 let mut symmetric_key = [0u8; 32]; hkdf.expand(b"JMIX-AES256-GCM", &mut symmetric_key)
128 .map_err(|e| EncryptionError::EncryptionFailed(format!("HKDF expansion failed: {}", e)))?;
129
130 let mut iv = [0u8; 12];
132 OsRng.fill_bytes(&mut iv);
133
134 let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&symmetric_key));
136 let nonce = Nonce::from_slice(&iv);
137
138 let ciphertext = cipher.encrypt(nonce, plaintext)
139 .map_err(|e| EncryptionError::EncryptionFailed(format!("AES-GCM encryption failed: {}", e)))?;
140
141 if ciphertext.len() < 16 {
143 return Err(EncryptionError::EncryptionFailed("Ciphertext too short".to_string()));
144 }
145
146 let (data, auth_tag) = ciphertext.split_at(ciphertext.len() - 16);
147
148 let info = EncryptionInfo {
150 algorithm: "AES-256-GCM".to_string(),
151 ephemeral_public_key: BASE64.encode(ephemeral_public.as_bytes()),
152 iv: BASE64.encode(&iv),
153 auth_tag: BASE64.encode(auth_tag),
154 };
155
156 Ok(EncryptionResult {
157 ciphertext: data.to_vec(),
158 info,
159 })
160 }
161}
162
163pub struct DecryptionManager {
166 secret_key: Zeroizing<[u8; 32]>,
168}
169
170impl DecryptionManager {
171 pub fn new(secret_key: [u8; 32]) -> Self {
173 Self { secret_key: Zeroizing::new(secret_key) }
174 }
175
176 pub fn from_bytes(key_bytes: [u8; 32]) -> Self {
178 Self::new(key_bytes)
179 }
180
181 pub fn from_secret_key_file<P: AsRef<Path>>(path: P) -> Result<Self, EncryptionError> {
183 let key_bytes = fs::read(path)?;
184 if key_bytes.len() != 32 {
185 return Err(EncryptionError::InvalidKey(
186 format!("Expected 32 bytes in key file, got {}", key_bytes.len())
187 ));
188 }
189
190 let mut key_array = [0u8; 32];
191 key_array.copy_from_slice(&key_bytes);
192
193 Ok(Self::from_bytes(key_array))
194 }
195
196 pub fn decrypt(&self, ciphertext: &[u8], info: &EncryptionInfo) -> Result<Vec<u8>, EncryptionError> {
198 if info.algorithm != "AES-256-GCM" {
200 return Err(EncryptionError::DecryptionFailed(
201 format!("Unsupported algorithm: {}", info.algorithm)
202 ));
203 }
204
205 let ephemeral_public_bytes = BASE64.decode(&info.ephemeral_public_key)?;
207 if ephemeral_public_bytes.len() != 32 {
208 return Err(EncryptionError::InvalidKey(
209 format!("Invalid ephemeral public key length: {}", ephemeral_public_bytes.len())
210 ));
211 }
212
213 let mut key_array = [0u8; 32];
214 key_array.copy_from_slice(&ephemeral_public_bytes);
215 let ephemeral_public = PublicKey::from(key_array);
216
217 let iv_bytes = BASE64.decode(&info.iv)?;
219 let auth_tag_bytes = BASE64.decode(&info.auth_tag)?;
220
221 if iv_bytes.len() != 12 {
222 return Err(EncryptionError::InvalidNonceSize(iv_bytes.len()));
223 }
224
225 if auth_tag_bytes.len() != 16 {
226 return Err(EncryptionError::InvalidAuthTagSize(auth_tag_bytes.len()));
227 }
228
229 let shared_secret_bytes = x25519(*self.secret_key, ephemeral_public.to_bytes());
232
233 let hkdf = Hkdf::<Sha256>::new(None, &shared_secret_bytes);
235 let mut symmetric_key = [0u8; 32];
236 hkdf.expand(b"JMIX-AES256-GCM", &mut symmetric_key)
237 .map_err(|e| EncryptionError::DecryptionFailed(format!("HKDF expansion failed: {}", e)))?;
238
239 let mut full_ciphertext = ciphertext.to_vec();
241 full_ciphertext.extend_from_slice(&auth_tag_bytes);
242
243 let cipher = Aes256Gcm::new(Key::<Aes256Gcm>::from_slice(&symmetric_key));
245 let nonce = Nonce::from_slice(&iv_bytes);
246
247 let plaintext = cipher.decrypt(nonce, full_ciphertext.as_slice())
248 .map_err(|e| EncryptionError::DecryptionFailed(format!("AES-GCM decryption failed: {}", e)))?;
249
250 Ok(plaintext)
251 }
252}
253
254impl KeyPair {
255 pub fn generate() -> Self {
257 let mut secret = [0u8; 32];
258 OsRng.fill_bytes(&mut secret);
259
260 let mut base_point = [0u8; 32];
265 base_point[0] = 9;
266 let public_bytes = x25519(secret, base_point);
267 let public = PublicKey::from(public_bytes);
268
269 Self {
270 secret: Zeroizing::new(secret),
271 public
272 }
273 }
274
275 pub fn from_secret_bytes(secret_bytes: [u8; 32]) -> Self {
277 let mut base_point = [0u8; 32];
281 base_point[0] = 9;
282 let public_bytes = x25519(secret_bytes, base_point);
283 let public = PublicKey::from(public_bytes);
284 Self {
285 secret: Zeroizing::new(secret_bytes),
286 public
287 }
288 }
289
290 pub fn secret_bytes(&self) -> [u8; 32] {
292 *self.secret
293 }
294
295 pub fn public_bytes(&self) -> [u8; 32] {
297 self.public.to_bytes()
298 }
299
300 pub fn public_key_base64(&self) -> String {
302 BASE64.encode(self.public.as_bytes())
303 }
304
305 pub fn save_to_files<P: AsRef<Path>>(&self, secret_path: P, public_path: P) -> Result<(), EncryptionError> {
307 let mut secret_file = fs::File::create(secret_path)?;
309 secret_file.write_all(&self.secret_bytes())?;
310
311 let mut public_file = fs::File::create(public_path)?;
313 public_file.write_all(&self.public_bytes())?;
314
315 Ok(())
316 }
317
318 pub fn load_from_secret_file<P: AsRef<Path>>(secret_path: P) -> Result<Self, EncryptionError> {
320 let secret_bytes = fs::read(secret_path)?;
321 if secret_bytes.len() != 32 {
322 return Err(EncryptionError::InvalidKey(
323 format!("Expected 32 bytes in secret key file, got {}", secret_bytes.len())
324 ));
325 }
326
327 let mut key_array = [0u8; 32];
328 key_array.copy_from_slice(&secret_bytes);
329
330 Ok(Self::from_secret_bytes(key_array))
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337 use tempfile::TempDir;
338
339 #[test]
340 fn test_keypair_generation() {
341 let keypair = KeyPair::generate();
342
343 assert_eq!(keypair.secret_bytes().len(), 32);
345 assert_eq!(keypair.public_bytes().len(), 32);
346
347 let public_b64 = keypair.public_key_base64();
349 assert!(!public_b64.is_empty());
350 }
351
352 #[test]
353 fn test_keypair_save_load() -> Result<(), Box<dyn std::error::Error>> {
354 let temp_dir = TempDir::new()?;
355 let secret_path = temp_dir.path().join("secret.key");
356 let public_path = temp_dir.path().join("public.key");
357
358 let original_keypair = KeyPair::generate();
360 original_keypair.save_to_files(&secret_path, &public_path)?;
361
362 let loaded_keypair = KeyPair::load_from_secret_file(&secret_path)?;
364
365 assert_eq!(original_keypair.secret_bytes(), loaded_keypair.secret_bytes());
367 assert_eq!(original_keypair.public_bytes(), loaded_keypair.public_bytes());
368
369 Ok(())
370 }
371
372 #[test]
373 fn test_encryption_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
374 let recipient_keypair = KeyPair::generate();
376
377 let encryption_manager = EncryptionManager::new(recipient_keypair.public);
379 let decryption_manager = DecryptionManager::new(*recipient_keypair.secret);
380
381 let plaintext = b"Hello, JMIX encryption!";
383
384 let result = encryption_manager.encrypt(plaintext)?;
386 assert!(result.ciphertext.len() > 0);
387 assert_eq!(result.info.algorithm, "AES-256-GCM");
388
389 let decrypted = decryption_manager.decrypt(&result.ciphertext, &result.info)?;
391 assert_eq!(decrypted, plaintext);
392
393 Ok(())
394 }
395
396 #[test]
397 fn test_encryption_manager_from_base64() -> Result<(), Box<dyn std::error::Error>> {
398 let keypair = KeyPair::generate();
399 let public_b64 = keypair.public_key_base64();
400
401 let manager = EncryptionManager::from_base64_public_key(&public_b64)?;
402
403 let plaintext = b"Test message";
405 let result = manager.encrypt(plaintext)?;
406 assert!(result.ciphertext.len() > 0);
407
408 Ok(())
409 }
410
411 #[test]
412 fn test_encryption_different_ephemeral_keys() -> Result<(), Box<dyn std::error::Error>> {
413 let recipient_keypair = KeyPair::generate();
414 let encryption_manager = EncryptionManager::new(recipient_keypair.public);
415
416 let plaintext = b"Same message";
417
418 let result1 = encryption_manager.encrypt(plaintext)?;
420 let result2 = encryption_manager.encrypt(plaintext)?;
421
422 assert_ne!(result1.info.ephemeral_public_key, result2.info.ephemeral_public_key);
424 assert_ne!(result1.info.iv, result2.info.iv);
425 assert_ne!(result1.ciphertext, result2.ciphertext);
426
427 Ok(())
428 }
429
430 #[test]
431 fn test_invalid_decryption() -> Result<(), Box<dyn std::error::Error>> {
432 let recipient_keypair = KeyPair::generate();
433 let wrong_keypair = KeyPair::generate(); let encryption_manager = EncryptionManager::new(recipient_keypair.public);
436 let wrong_decryption_manager = DecryptionManager::new(*wrong_keypair.secret);
437
438 let plaintext = b"Secret message";
439 let result = encryption_manager.encrypt(plaintext)?;
440
441 let decrypt_result = wrong_decryption_manager.decrypt(&result.ciphertext, &result.info);
443 assert!(decrypt_result.is_err());
444
445 Ok(())
446 }
447}