Skip to main content

allsource_core/security/
encryption.rs

1/// Field-Level Encryption for Sensitive Data
2///
3/// Provides transparent encryption/decryption of sensitive fields using:
4/// - AES-256-GCM for symmetric encryption
5/// - Envelope encryption pattern
6/// - Key rotation support
7/// - Per-field encryption keys
8use crate::error::{AllSourceError, Result};
9use aes_gcm::{
10    aead::{Aead, KeyInit, OsRng},
11    Aes256Gcm, Nonce,
12};
13use base64::{engine::general_purpose, Engine as _};
14use dashmap::DashMap;
15use parking_lot::RwLock;
16use serde::{Deserialize, Serialize};
17use std::sync::Arc;
18
19/// Encryption configuration
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct EncryptionConfig {
22    /// Enable encryption
23    pub enabled: bool,
24
25    /// Key rotation period in days
26    pub key_rotation_days: u32,
27
28    /// Algorithm to use
29    pub algorithm: EncryptionAlgorithm,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
33pub enum EncryptionAlgorithm {
34    Aes256Gcm,
35    ChaCha20Poly1305,
36}
37
38impl Default for EncryptionConfig {
39    fn default() -> Self {
40        Self {
41            enabled: true,
42            key_rotation_days: 90,
43            algorithm: EncryptionAlgorithm::Aes256Gcm,
44        }
45    }
46}
47
48/// Encrypted data with metadata
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct EncryptedData {
51    /// Ciphertext (base64 encoded)
52    pub ciphertext: String,
53
54    /// Nonce/IV (base64 encoded)
55    pub nonce: String,
56
57    /// Key ID used for encryption
58    pub key_id: String,
59
60    /// Algorithm used
61    pub algorithm: EncryptionAlgorithm,
62
63    /// Version for key rotation
64    pub version: u32,
65}
66
67/// Data encryption key
68#[derive(Debug, Clone)]
69struct DataEncryptionKey {
70    key_id: String,
71    key_bytes: Vec<u8>,
72    version: u32,
73    created_at: chrono::DateTime<chrono::Utc>,
74    active: bool,
75}
76
77/// Field-level encryption manager
78pub struct FieldEncryption {
79    config: Arc<RwLock<EncryptionConfig>>,
80
81    // Data encryption keys (DEKs) - using DashMap for lock-free concurrent access
82    deks: Arc<DashMap<String, DataEncryptionKey>>,
83
84    // Active key for encryption
85    active_key_id: Arc<RwLock<Option<String>>>,
86}
87
88impl FieldEncryption {
89    /// Create new field encryption manager
90    pub fn new(config: EncryptionConfig) -> Result<Self> {
91        let manager = Self {
92            config: Arc::new(RwLock::new(config)),
93            deks: Arc::new(DashMap::new()),
94            active_key_id: Arc::new(RwLock::new(None)),
95        };
96
97        // Generate initial key
98        manager.rotate_keys()?;
99
100        Ok(manager)
101    }
102
103    /// Encrypt a string value
104    pub fn encrypt_string(&self, plaintext: &str, field_name: &str) -> Result<EncryptedData> {
105        if !self.config.read().enabled {
106            return Err(AllSourceError::ValidationError(
107                "Encryption is disabled".to_string(),
108            ));
109        }
110
111        let active_key_id = self.active_key_id.read();
112        let key_id = active_key_id
113            .as_ref()
114            .ok_or_else(|| AllSourceError::ValidationError("No active encryption key".to_string()))?
115            .clone();
116
117        let dek_ref = self.deks.get(&key_id).ok_or_else(|| {
118            AllSourceError::ValidationError("Encryption key not found".to_string())
119        })?;
120        let dek = dek_ref.value();
121
122        // Use AES-256-GCM
123        let cipher = Aes256Gcm::new_from_slice(&dek.key_bytes)
124            .map_err(|e| AllSourceError::ValidationError(format!("Invalid key: {e}")))?;
125
126        // Generate random nonce
127        let nonce_bytes = aes_gcm::aead::rand_core::RngCore::next_u64(&mut OsRng).to_le_bytes();
128        let mut nonce_array = [0u8; 12];
129        nonce_array[..8].copy_from_slice(&nonce_bytes);
130        let nonce = Nonce::from_slice(&nonce_array);
131
132        // Encrypt with associated data (field name) for integrity
133        let ciphertext = cipher
134            .encrypt(nonce, plaintext.as_bytes())
135            .map_err(|e| AllSourceError::ValidationError(format!("Encryption failed: {e}")))?;
136
137        Ok(EncryptedData {
138            ciphertext: general_purpose::STANDARD.encode(&ciphertext),
139            nonce: general_purpose::STANDARD.encode(nonce.as_slice()),
140            key_id: key_id.clone(),
141            algorithm: self.config.read().algorithm.clone(),
142            version: dek.version,
143        })
144    }
145
146    /// Decrypt a string value
147    pub fn decrypt_string(&self, encrypted: &EncryptedData) -> Result<String> {
148        if !self.config.read().enabled {
149            return Err(AllSourceError::ValidationError(
150                "Encryption is disabled".to_string(),
151            ));
152        }
153
154        let dek_ref = self.deks.get(&encrypted.key_id).ok_or_else(|| {
155            AllSourceError::ValidationError(format!(
156                "Encryption key {} not found",
157                encrypted.key_id
158            ))
159        })?;
160        let dek = dek_ref.value();
161
162        // Decode base64
163        let ciphertext = general_purpose::STANDARD
164            .decode(&encrypted.ciphertext)
165            .map_err(|e| {
166                AllSourceError::ValidationError(format!("Invalid ciphertext encoding: {e}"))
167            })?;
168
169        let nonce_bytes = general_purpose::STANDARD
170            .decode(&encrypted.nonce)
171            .map_err(|e| AllSourceError::ValidationError(format!("Invalid nonce encoding: {e}")))?;
172
173        let nonce = Nonce::from_slice(&nonce_bytes);
174
175        // Decrypt
176        let cipher = Aes256Gcm::new_from_slice(&dek.key_bytes)
177            .map_err(|e| AllSourceError::ValidationError(format!("Invalid key: {e}")))?;
178
179        let plaintext_bytes = cipher
180            .decrypt(nonce, ciphertext.as_ref())
181            .map_err(|e| AllSourceError::ValidationError(format!("Decryption failed: {e}")))?;
182
183        String::from_utf8(plaintext_bytes)
184            .map_err(|e| AllSourceError::ValidationError(format!("Invalid UTF-8: {e}")))
185    }
186
187    /// Rotate encryption keys
188    pub fn rotate_keys(&self) -> Result<()> {
189        let mut active_key_id = self.active_key_id.write();
190
191        // Generate new key
192        let key_id = uuid::Uuid::new_v4().to_string();
193        let mut key_bytes = vec![0u8; 32]; // 256 bits for AES-256
194        aes_gcm::aead::rand_core::RngCore::fill_bytes(&mut OsRng, &mut key_bytes);
195
196        let version = self.deks.len() as u32 + 1;
197
198        let new_key = DataEncryptionKey {
199            key_id: key_id.clone(),
200            key_bytes,
201            version,
202            created_at: chrono::Utc::now(),
203            active: true,
204        };
205
206        // Deactivate old keys
207        for mut entry in self.deks.iter_mut() {
208            entry.value_mut().active = false;
209        }
210
211        // Add new key
212        self.deks.insert(key_id.clone(), new_key);
213        *active_key_id = Some(key_id);
214
215        Ok(())
216    }
217
218    /// Get encryption statistics
219    pub fn get_stats(&self) -> EncryptionStats {
220        let active_key_id = self.active_key_id.read();
221
222        EncryptionStats {
223            enabled: self.config.read().enabled,
224            total_keys: self.deks.len(),
225            active_key_version: active_key_id
226                .as_ref()
227                .and_then(|id| self.deks.get(id))
228                .map(|entry| entry.value().version)
229                .unwrap_or(0),
230            algorithm: self.config.read().algorithm.clone(),
231        }
232    }
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct EncryptionStats {
237    pub enabled: bool,
238    pub total_keys: usize,
239    pub active_key_version: u32,
240    pub algorithm: EncryptionAlgorithm,
241}
242
243/// Trait for types that can be encrypted
244pub trait Encryptable {
245    fn encrypt(&self, encryption: &FieldEncryption, field_name: &str) -> Result<EncryptedData>;
246    fn decrypt(encrypted: &EncryptedData, encryption: &FieldEncryption) -> Result<Self>
247    where
248        Self: Sized;
249}
250
251impl Encryptable for String {
252    fn encrypt(&self, encryption: &FieldEncryption, field_name: &str) -> Result<EncryptedData> {
253        encryption.encrypt_string(self, field_name)
254    }
255
256    fn decrypt(encrypted: &EncryptedData, encryption: &FieldEncryption) -> Result<Self> {
257        encryption.decrypt_string(encrypted)
258    }
259}
260
261/// Helper for encrypting JSON values
262pub fn encrypt_json_value(
263    value: &serde_json::Value,
264    encryption: &FieldEncryption,
265    field_name: &str,
266) -> Result<EncryptedData> {
267    let json_string = serde_json::to_string(value)
268        .map_err(|e| AllSourceError::ValidationError(format!("JSON serialization failed: {e}")))?;
269
270    encryption.encrypt_string(&json_string, field_name)
271}
272
273/// Helper for decrypting JSON values
274pub fn decrypt_json_value(
275    encrypted: &EncryptedData,
276    encryption: &FieldEncryption,
277) -> Result<serde_json::Value> {
278    let json_string = encryption.decrypt_string(encrypted)?;
279
280    serde_json::from_str(&json_string)
281        .map_err(|e| AllSourceError::ValidationError(format!("JSON deserialization failed: {e}")))
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn test_encryption_creation() {
290        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
291        let stats = encryption.get_stats();
292
293        assert!(stats.enabled);
294        assert_eq!(stats.total_keys, 1);
295        assert_eq!(stats.active_key_version, 1);
296    }
297
298    #[test]
299    fn test_encrypt_decrypt_string() {
300        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
301        let plaintext = "sensitive data";
302
303        let encrypted = encryption.encrypt_string(plaintext, "test_field").unwrap();
304        assert_ne!(encrypted.ciphertext, plaintext);
305
306        let decrypted = encryption.decrypt_string(&encrypted).unwrap();
307        assert_eq!(decrypted, plaintext);
308    }
309
310    #[test]
311    fn test_encrypt_decrypt_json() {
312        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
313        let value = serde_json::json!({
314            "username": "john_doe",
315            "ssn": "123-45-6789",
316            "credit_card": "4111-1111-1111-1111"
317        });
318
319        let encrypted = encrypt_json_value(&value, &encryption, "sensitive_data").unwrap();
320        let decrypted = decrypt_json_value(&encrypted, &encryption).unwrap();
321
322        assert_eq!(decrypted, value);
323    }
324
325    #[test]
326    fn test_key_rotation() {
327        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
328        let plaintext = "sensitive data";
329
330        // Encrypt with first key
331        let encrypted1 = encryption.encrypt_string(plaintext, "test").unwrap();
332        let key_id1 = encrypted1.key_id.clone();
333
334        // Rotate keys
335        encryption.rotate_keys().unwrap();
336
337        // Encrypt with new key
338        let encrypted2 = encryption.encrypt_string(plaintext, "test").unwrap();
339        let key_id2 = encrypted2.key_id.clone();
340
341        // Keys should be different
342        assert_ne!(key_id1, key_id2);
343        assert_eq!(encrypted2.version, 2);
344
345        // Should still be able to decrypt data encrypted with old key
346        let decrypted1 = encryption.decrypt_string(&encrypted1).unwrap();
347        assert_eq!(decrypted1, plaintext);
348
349        // Should be able to decrypt data encrypted with new key
350        let decrypted2 = encryption.decrypt_string(&encrypted2).unwrap();
351        assert_eq!(decrypted2, plaintext);
352    }
353
354    #[test]
355    fn test_multiple_key_rotations() {
356        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
357        let plaintext = "test data";
358
359        let mut encrypted_data = Vec::new();
360
361        // Create data with multiple key versions
362        for _ in 0..5 {
363            let encrypted = encryption.encrypt_string(plaintext, "test").unwrap();
364            encrypted_data.push(encrypted);
365            encryption.rotate_keys().unwrap();
366        }
367
368        // Should be able to decrypt all versions
369        for encrypted in &encrypted_data {
370            let decrypted = encryption.decrypt_string(encrypted).unwrap();
371            assert_eq!(decrypted, plaintext);
372        }
373
374        let stats = encryption.get_stats();
375        assert_eq!(stats.total_keys, 6); // Initial + 5 rotations
376        assert_eq!(stats.active_key_version, 6);
377    }
378
379    #[test]
380    fn test_disabled_encryption() {
381        let config = EncryptionConfig {
382            enabled: false,
383            ..Default::default()
384        };
385
386        let encryption = FieldEncryption::new(config).unwrap();
387        let plaintext = "test";
388
389        let result = encryption.encrypt_string(plaintext, "test");
390        assert!(result.is_err());
391    }
392
393    #[test]
394    fn test_encryption_config_default() {
395        let config = EncryptionConfig::default();
396        assert!(config.enabled);
397        assert_eq!(config.key_rotation_days, 90);
398        assert_eq!(config.algorithm, EncryptionAlgorithm::Aes256Gcm);
399    }
400
401    #[test]
402    fn test_encryption_algorithm_equality() {
403        assert_eq!(
404            EncryptionAlgorithm::Aes256Gcm,
405            EncryptionAlgorithm::Aes256Gcm
406        );
407        assert_ne!(
408            EncryptionAlgorithm::Aes256Gcm,
409            EncryptionAlgorithm::ChaCha20Poly1305
410        );
411    }
412
413    #[test]
414    fn test_encryption_config_serde() {
415        let config = EncryptionConfig::default();
416        let json = serde_json::to_string(&config).unwrap();
417        let parsed: EncryptionConfig = serde_json::from_str(&json).unwrap();
418        assert_eq!(parsed.enabled, config.enabled);
419        assert_eq!(parsed.algorithm, config.algorithm);
420    }
421
422    #[test]
423    fn test_encrypted_data_serde() {
424        let encrypted = EncryptedData {
425            ciphertext: "encrypted_data".to_string(),
426            nonce: "nonce_value".to_string(),
427            key_id: "key-123".to_string(),
428            algorithm: EncryptionAlgorithm::Aes256Gcm,
429            version: 1,
430        };
431
432        let json = serde_json::to_string(&encrypted).unwrap();
433        let parsed: EncryptedData = serde_json::from_str(&json).unwrap();
434        assert_eq!(parsed.ciphertext, encrypted.ciphertext);
435        assert_eq!(parsed.key_id, encrypted.key_id);
436        assert_eq!(parsed.version, encrypted.version);
437    }
438
439    #[test]
440    fn test_encrypt_empty_string() {
441        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
442        let plaintext = "";
443
444        let encrypted = encryption.encrypt_string(plaintext, "test_field").unwrap();
445        let decrypted = encryption.decrypt_string(&encrypted).unwrap();
446        assert_eq!(decrypted, plaintext);
447    }
448
449    #[test]
450    fn test_encrypt_long_string() {
451        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
452        let plaintext = "a".repeat(10000);
453
454        let encrypted = encryption.encrypt_string(&plaintext, "test_field").unwrap();
455        let decrypted = encryption.decrypt_string(&encrypted).unwrap();
456        assert_eq!(decrypted, plaintext);
457    }
458
459    #[test]
460    fn test_encrypt_unicode_string() {
461        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
462        let plaintext = "日本語テスト 🎉 émojis";
463
464        let encrypted = encryption.encrypt_string(plaintext, "test_field").unwrap();
465        let decrypted = encryption.decrypt_string(&encrypted).unwrap();
466        assert_eq!(decrypted, plaintext);
467    }
468
469    #[test]
470    fn test_encryption_stats() {
471        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
472
473        let stats = encryption.get_stats();
474        assert!(stats.enabled);
475        assert_eq!(stats.total_keys, 1);
476        assert_eq!(stats.active_key_version, 1);
477        assert_eq!(stats.algorithm, EncryptionAlgorithm::Aes256Gcm);
478    }
479
480    #[test]
481    fn test_decrypt_with_invalid_key() {
482        let encryption1 = FieldEncryption::new(EncryptionConfig::default()).unwrap();
483        let encryption2 = FieldEncryption::new(EncryptionConfig::default()).unwrap();
484
485        let plaintext = "test data";
486        let encrypted = encryption1.encrypt_string(plaintext, "test").unwrap();
487
488        // Try to decrypt with different encryption instance (different keys)
489        let result = encryption2.decrypt_string(&encrypted);
490        assert!(result.is_err());
491    }
492
493    #[test]
494    fn test_encryption_different_fields() {
495        let encryption = FieldEncryption::new(EncryptionConfig::default()).unwrap();
496
497        let data1 = "data for field 1";
498        let data2 = "data for field 2";
499
500        let encrypted1 = encryption.encrypt_string(data1, "field1").unwrap();
501        let encrypted2 = encryption.encrypt_string(data2, "field2").unwrap();
502
503        // Even same data produces different ciphertext due to random nonce
504        let encrypted1_again = encryption.encrypt_string(data1, "field1").unwrap();
505        assert_ne!(encrypted1.ciphertext, encrypted1_again.ciphertext);
506
507        // Both should decrypt correctly
508        assert_eq!(encryption.decrypt_string(&encrypted1).unwrap(), data1);
509        assert_eq!(encryption.decrypt_string(&encrypted2).unwrap(), data2);
510    }
511}