mockforge_core/
encryption.rs

1//! End-to-end encryption module for MockForge
2//!
3//! This module has been refactored into sub-modules for better organization:
4//! - algorithms: Core encryption algorithms (AES-GCM, ChaCha20-Poly1305)
5//! - key_management: Key generation, storage, and lifecycle management
6//! - auto_encryption: Automatic encryption configuration and processing
7//! - derivation: Key derivation functions (Argon2, PBKDF2)
8//! - errors: Error types and handling for encryption operations
9//!
10//! ## Key Management Architecture
11//!
12
13#![allow(dead_code)]
14#[allow(deprecated)]
15pub use errors::*;
16pub use key_management::{FileKeyStorage, KeyStorage, KeyStore as KeyManagementStore};
17
18use crate::workspace_persistence::WorkspacePersistence;
19#[allow(deprecated)]
20use aes_gcm::{
21    aead::{generic_array::GenericArray, Aead, KeyInit},
22    Aes256Gcm, Nonce,
23};
24use argon2::{
25    password_hash::{PasswordHasher, SaltString},
26    Argon2, Params,
27};
28use base64::{engine::general_purpose, Engine as _};
29use chacha20poly1305::{ChaCha20Poly1305, Key as ChaChaKey};
30use pbkdf2::pbkdf2_hmac;
31use rand::{rng, Rng};
32use serde::{Deserialize, Serialize};
33use sha2::Sha256;
34use std::fmt;
35use thiserror::Error;
36use tracing;
37
38#[cfg(target_os = "windows")]
39use windows::Win32::Security::Credentials::{
40    CredDeleteW, CredReadW, CredWriteW, CREDENTIALW, CRED_PERSIST_LOCAL_MACHINE, CRED_TYPE_GENERIC,
41};
42
43/// Errors that can occur during encryption/decryption operations
44#[derive(Error, Debug)]
45pub enum EncryptionError {
46    #[error("Encryption failure: {0}")]
47    Encryption(String),
48    #[error("Decryption failure: {0}")]
49    Decryption(String),
50    #[error("Invalid key: {0}")]
51    InvalidKey(String),
52    #[error("Invalid ciphertext: {0}")]
53    InvalidCiphertext(String),
54    #[error("Key derivation failure: {0}")]
55    KeyDerivation(String),
56    #[error("Generic encryption error: {message}")]
57    Generic { message: String },
58}
59
60pub type Result<T> = std::result::Result<T, EncryptionError>;
61
62/// Encryption algorithms supported
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum EncryptionAlgorithm {
65    Aes256Gcm,
66    ChaCha20Poly1305,
67}
68
69impl fmt::Display for EncryptionAlgorithm {
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        match self {
72            EncryptionAlgorithm::Aes256Gcm => write!(f, "aes256-gcm"),
73            EncryptionAlgorithm::ChaCha20Poly1305 => write!(f, "chacha20-poly1305"),
74        }
75    }
76}
77
78/// Key derivation methods
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum KeyDerivationMethod {
81    Pbkdf2,
82    Argon2,
83}
84
85impl fmt::Display for KeyDerivationMethod {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            KeyDerivationMethod::Pbkdf2 => write!(f, "pbkdf2"),
89            KeyDerivationMethod::Argon2 => write!(f, "argon2"),
90        }
91    }
92}
93
94/// Cryptographic key for encryption operations
95pub struct EncryptionKey {
96    algorithm: EncryptionAlgorithm,
97    key_data: Vec<u8>,
98}
99
100impl EncryptionKey {
101    /// Create a new encryption key from raw bytes
102    pub fn new(algorithm: EncryptionAlgorithm, key_data: Vec<u8>) -> Result<Self> {
103        let expected_len = match algorithm {
104            EncryptionAlgorithm::Aes256Gcm => 32,        // 256 bits
105            EncryptionAlgorithm::ChaCha20Poly1305 => 32, // 256 bits
106        };
107
108        if key_data.len() != expected_len {
109            return Err(EncryptionError::InvalidKey(format!(
110                "Key must be {} bytes for {}, got {}",
111                expected_len,
112                algorithm,
113                key_data.len()
114            )));
115        }
116
117        Ok(Self {
118            algorithm,
119            key_data,
120        })
121    }
122
123    /// Derive a key from a password using PBKDF2
124    pub fn from_password_pbkdf2(
125        password: &str,
126        salt: Option<&[u8]>,
127        algorithm: EncryptionAlgorithm,
128    ) -> Result<Self> {
129        let salt = salt.map(|s| s.to_vec()).unwrap_or_else(|| rng().random::<[u8; 32]>().to_vec());
130
131        let mut key = vec![0u8; 32];
132        pbkdf2_hmac::<Sha256>(password.as_bytes(), &salt, 100_000, &mut key);
133
134        Self::new(algorithm, key)
135    }
136
137    /// Derive a key from a password using Argon2
138    pub fn from_password_argon2(
139        password: &str,
140        salt: Option<&[u8]>,
141        algorithm: EncryptionAlgorithm,
142    ) -> Result<Self> {
143        let salt_string = if let Some(salt) = salt {
144            SaltString::encode_b64(salt)
145                .map_err(|e| EncryptionError::KeyDerivation(e.to_string()))?
146        } else {
147            // Generate a random salt
148            let mut salt_bytes = [0u8; 32];
149            rng().fill(&mut salt_bytes);
150            SaltString::encode_b64(&salt_bytes)
151                .map_err(|e| EncryptionError::KeyDerivation(e.to_string()))?
152        };
153
154        let argon2 = Argon2::new(
155            argon2::Algorithm::Argon2id,
156            argon2::Version::V0x13,
157            Params::new(65536, 3, 1, Some(32))
158                .map_err(|e| EncryptionError::KeyDerivation(e.to_string()))?,
159        );
160
161        let hash = argon2
162            .hash_password(password.as_bytes(), &salt_string)
163            .map_err(|e| EncryptionError::KeyDerivation(e.to_string()))?;
164
165        let key_bytes = hash
166            .hash
167            .ok_or_else(|| EncryptionError::KeyDerivation("Failed to derive key hash".to_string()))?
168            .as_bytes()
169            .to_vec();
170        Self::new(algorithm, key_bytes)
171    }
172
173    /// Encrypt plaintext data
174    pub fn encrypt(&self, plaintext: &str, associated_data: Option<&[u8]>) -> Result<String> {
175        match self.algorithm {
176            EncryptionAlgorithm::Aes256Gcm => self.encrypt_aes_gcm(plaintext, associated_data),
177            EncryptionAlgorithm::ChaCha20Poly1305 => {
178                self.encrypt_chacha20(plaintext, associated_data)
179            }
180        }
181    }
182
183    /// Decrypt ciphertext data
184    pub fn decrypt(&self, ciphertext: &str, associated_data: Option<&[u8]>) -> Result<String> {
185        match self.algorithm {
186            EncryptionAlgorithm::Aes256Gcm => self.decrypt_aes_gcm(ciphertext, associated_data),
187            EncryptionAlgorithm::ChaCha20Poly1305 => {
188                self.decrypt_chacha20(ciphertext, associated_data)
189            }
190        }
191    }
192
193    fn encrypt_aes_gcm(&self, plaintext: &str, associated_data: Option<&[u8]>) -> Result<String> {
194        let key = GenericArray::from_slice(&self.key_data);
195        let cipher = Aes256Gcm::new(key);
196        let nonce: [u8; 12] = rng().random(); // 96-bit nonce
197        let nonce = Nonce::from(nonce);
198
199        let ciphertext = cipher
200            .encrypt(&nonce, plaintext.as_bytes())
201            .map_err(|e| EncryptionError::Encryption(e.to_string()))?;
202
203        let mut result = nonce.to_vec();
204        result.extend_from_slice(&ciphertext);
205
206        // If associated data is provided, include it
207        if let Some(aad) = associated_data {
208            result.extend_from_slice(aad);
209        }
210
211        Ok(general_purpose::STANDARD.encode(&result))
212    }
213
214    fn decrypt_aes_gcm(&self, ciphertext: &str, associated_data: Option<&[u8]>) -> Result<String> {
215        let data = general_purpose::STANDARD
216            .decode(ciphertext)
217            .map_err(|e| EncryptionError::InvalidCiphertext(e.to_string()))?;
218
219        if data.len() < 12 {
220            return Err(EncryptionError::InvalidCiphertext("Ciphertext too short".to_string()));
221        }
222
223        let nonce = GenericArray::from_slice(&data[0..12]);
224        let ciphertext_len = if let Some(aad) = &associated_data {
225            // Associated data is included at the end
226            let aad_len = aad.len();
227            data.len() - 12 - aad_len
228        } else {
229            data.len() - 12
230        };
231
232        let ciphertext = &data[12..12 + ciphertext_len];
233        let key = GenericArray::from_slice(&self.key_data);
234        let cipher = Aes256Gcm::new(key);
235
236        let plaintext = cipher
237            .decrypt(nonce, ciphertext.as_ref())
238            .map_err(|e| EncryptionError::Decryption(e.to_string()))?;
239
240        String::from_utf8(plaintext)
241            .map_err(|e| EncryptionError::Decryption(format!("Invalid UTF-8: {}", e)))
242    }
243
244    pub fn encrypt_chacha20(
245        &self,
246        plaintext: &str,
247        _associated_data: Option<&[u8]>,
248    ) -> Result<String> {
249        let key = ChaChaKey::from_slice(&self.key_data);
250        let cipher = ChaCha20Poly1305::new(key);
251        let nonce: [u8; 12] = rng().random(); // 96-bit nonce for ChaCha20-Poly1305
252        let nonce = chacha20poly1305::Nonce::from_slice(&nonce);
253
254        let ciphertext = cipher
255            .encrypt(nonce, plaintext.as_bytes())
256            .map_err(|e| EncryptionError::Encryption(e.to_string()))?;
257
258        let mut result = nonce.to_vec();
259        result.extend_from_slice(&ciphertext);
260
261        Ok(general_purpose::STANDARD.encode(&result))
262    }
263
264    pub fn decrypt_chacha20(
265        &self,
266        ciphertext: &str,
267        _associated_data: Option<&[u8]>,
268    ) -> Result<String> {
269        let data = general_purpose::STANDARD
270            .decode(ciphertext)
271            .map_err(|e| EncryptionError::InvalidCiphertext(e.to_string()))?;
272
273        if data.len() < 12 {
274            return Err(EncryptionError::InvalidCiphertext("Ciphertext too short".to_string()));
275        }
276
277        let nonce = chacha20poly1305::Nonce::from_slice(&data[0..12]);
278        let ciphertext_data = &data[12..];
279        let key = ChaChaKey::from_slice(&self.key_data);
280        let cipher = ChaCha20Poly1305::new(key);
281
282        let plaintext = cipher
283            .decrypt(nonce, ciphertext_data.as_ref())
284            .map_err(|e| EncryptionError::Decryption(e.to_string()))?;
285
286        String::from_utf8(plaintext)
287            .map_err(|e| EncryptionError::Decryption(format!("Invalid UTF-8: {}", e)))
288    }
289}
290
291/// Key store for managing encryption keys
292pub struct KeyStore {
293    keys: std::collections::HashMap<String, EncryptionKey>,
294}
295
296impl KeyStore {
297    /// Create a new empty key store
298    pub fn new() -> Self {
299        Self {
300            keys: std::collections::HashMap::new(),
301        }
302    }
303
304    /// Store a key with a given identifier
305    pub fn store_key(&mut self, id: String, key: EncryptionKey) {
306        self.keys.insert(id, key);
307    }
308
309    /// Retrieve a key by identifier
310    pub fn get_key(&self, id: &str) -> Option<&EncryptionKey> {
311        self.keys.get(id)
312    }
313
314    /// Remove a key
315    pub fn remove_key(&mut self, id: &str) -> bool {
316        self.keys.remove(id).is_some()
317    }
318
319    /// List all key identifiers
320    pub fn list_keys(&self) -> Vec<String> {
321        self.keys.keys().cloned().collect()
322    }
323
324    /// Derive and store a key from password
325    pub fn derive_and_store_key(
326        &mut self,
327        id: String,
328        password: &str,
329        algorithm: EncryptionAlgorithm,
330        method: KeyDerivationMethod,
331    ) -> Result<()> {
332        let key = match method {
333            KeyDerivationMethod::Pbkdf2 => {
334                EncryptionKey::from_password_pbkdf2(password, None, algorithm)?
335            }
336            KeyDerivationMethod::Argon2 => {
337                EncryptionKey::from_password_argon2(password, None, algorithm)?
338            }
339        };
340        self.store_key(id, key);
341        Ok(())
342    }
343}
344
345impl Default for KeyStore {
346    fn default() -> Self {
347        Self::new()
348    }
349}
350
351/// Global key store instance
352static KEY_STORE: once_cell::sync::OnceCell<KeyStore> = once_cell::sync::OnceCell::new();
353
354/// Initialize the global key store
355pub fn init_key_store() -> &'static KeyStore {
356    KEY_STORE.get_or_init(KeyStore::default)
357}
358
359/// Get the global key store
360pub fn get_key_store() -> Option<&'static KeyStore> {
361    KEY_STORE.get()
362}
363
364/// Master key manager for OS keychain integration
365pub struct MasterKeyManager {
366    _service_name: String,
367    _account_name: String,
368}
369
370impl MasterKeyManager {
371    /// Create a new master key manager
372    pub fn new() -> Self {
373        Self {
374            _service_name: "com.mockforge.encryption".to_string(),
375            _account_name: "master_key".to_string(),
376        }
377    }
378
379    /// Generate and store a new master key in the OS keychain
380    pub fn generate_master_key(&self) -> Result<()> {
381        let master_key_bytes: [u8; 32] = rand::random();
382        let master_key_b64 = general_purpose::STANDARD.encode(master_key_bytes);
383
384        // In a real implementation, this would use OS-specific keychain APIs
385        // For now, we'll store it in a secure location or environment variable
386        #[cfg(target_os = "macos")]
387        {
388            // Use macOS Keychain
389            self.store_in_macos_keychain(&master_key_b64)?;
390        }
391        #[cfg(target_os = "linux")]
392        {
393            // Use Linux keyring or secure storage
394            self.store_in_linux_keyring(&master_key_b64)?;
395        }
396        #[cfg(target_os = "windows")]
397        {
398            // Use Windows Credential Manager
399            self.store_in_windows_credential_manager(&master_key_b64)?;
400        }
401
402        Ok(())
403    }
404
405    /// Retrieve the master key from OS keychain
406    pub fn get_master_key(&self) -> Result<EncryptionKey> {
407        let master_key_b64 = self.retrieve_from_keychain()?;
408        let master_key_bytes = general_purpose::STANDARD
409            .decode(master_key_b64)
410            .map_err(|e| EncryptionError::InvalidKey(e.to_string()))?;
411
412        if master_key_bytes.len() != 32 {
413            return Err(EncryptionError::InvalidKey("Invalid master key length".to_string()));
414        }
415
416        EncryptionKey::new(EncryptionAlgorithm::ChaCha20Poly1305, master_key_bytes)
417    }
418
419    /// Check if master key exists
420    pub fn has_master_key(&self) -> bool {
421        self.retrieve_from_keychain().is_ok()
422    }
423
424    // Platform-specific implementations (simplified for now)
425    #[cfg(target_os = "macos")]
426    fn store_in_macos_keychain(&self, key: &str) -> Result<()> {
427        use std::os::unix::fs::PermissionsExt;
428
429        let home = std::env::var("HOME").map_err(|_| {
430            EncryptionError::InvalidKey("HOME environment variable not set".to_string())
431        })?;
432        let key_path = std::path::Path::new(&home).join(".mockforge").join("master_key");
433
434        // Create directory if it doesn't exist
435        if let Some(parent) = key_path.parent() {
436            std::fs::create_dir_all(parent).map_err(|e| {
437                EncryptionError::InvalidKey(format!("Failed to create directory: {}", e))
438            })?;
439        }
440
441        // Write the key
442        std::fs::write(&key_path, key).map_err(|e| {
443            EncryptionError::InvalidKey(format!("Failed to write master key: {}", e))
444        })?;
445
446        // Set permissions to 600 (owner read/write only)
447        let mut perms = std::fs::metadata(&key_path)
448            .map_err(|e| EncryptionError::InvalidKey(format!("Failed to get metadata: {}", e)))?
449            .permissions();
450        perms.set_mode(0o600);
451        std::fs::set_permissions(&key_path, perms).map_err(|e| {
452            EncryptionError::InvalidKey(format!("Failed to set permissions: {}", e))
453        })?;
454
455        Ok(())
456    }
457
458    #[cfg(target_os = "linux")]
459    fn store_in_linux_keyring(&self, key: &str) -> Result<()> {
460        use std::os::unix::fs::PermissionsExt;
461
462        let home = std::env::var("HOME").map_err(|_| {
463            EncryptionError::InvalidKey("HOME environment variable not set".to_string())
464        })?;
465        let key_path = std::path::Path::new(&home).join(".mockforge").join("master_key");
466
467        // Create directory if it doesn't exist
468        if let Some(parent) = key_path.parent() {
469            std::fs::create_dir_all(parent).map_err(|e| {
470                EncryptionError::InvalidKey(format!("Failed to create directory: {}", e))
471            })?;
472        }
473
474        // Write the key
475        std::fs::write(&key_path, key).map_err(|e| {
476            EncryptionError::InvalidKey(format!("Failed to write master key: {}", e))
477        })?;
478
479        // Set permissions to 600 (owner read/write only)
480        let mut perms = std::fs::metadata(&key_path)
481            .map_err(|e| EncryptionError::InvalidKey(format!("Failed to get metadata: {}", e)))?
482            .permissions();
483        perms.set_mode(0o600);
484        std::fs::set_permissions(&key_path, perms).map_err(|e| {
485            EncryptionError::InvalidKey(format!("Failed to set permissions: {}", e))
486        })?;
487
488        Ok(())
489    }
490
491    #[cfg(target_os = "windows")]
492    fn store_in_windows_credential_manager(&self, key: &str) -> Result<()> {
493        use std::ffi::OsString;
494        use std::os::windows::ffi::OsStringExt;
495        use windows::core::PCWSTR;
496        use windows::Win32::Foundation::ERROR_NO_SUCH_LOGON_SESSION;
497        use windows::Win32::Security::Credentials::{
498            CredWriteW, CREDENTIALW, CRED_PERSIST_LOCAL_MACHINE, CRED_TYPE_GENERIC,
499        };
500
501        let target_name = "MockForge/MasterKey";
502        let target_name_wide: Vec<u16> =
503            OsString::from(target_name).encode_wide().chain(std::iter::once(0)).collect();
504
505        let credential_blob: Vec<u16> =
506            OsString::from(key).encode_wide().chain(std::iter::once(0)).collect();
507
508        let mut credential = CREDENTIALW {
509            Flags: 0,
510            Type: CRED_TYPE_GENERIC,
511            TargetName: PCWSTR::from_raw(target_name_wide.as_ptr()),
512            Comment: PCWSTR::null(),
513            LastWritten: windows::Win32::Foundation::FILETIME::default(),
514            CredentialBlobSize: (credential_blob.len() * 2) as u32,
515            CredentialBlob: credential_blob.as_ptr() as *mut u8,
516            Persist: CRED_PERSIST_LOCAL_MACHINE,
517            AttributeCount: 0,
518            Attributes: std::ptr::null_mut(),
519            TargetAlias: PCWSTR::null(),
520            UserName: PCWSTR::null(),
521        };
522
523        unsafe {
524            CredWriteW(&mut credential, 0).map_err(|e| {
525                EncryptionError::InvalidKey(format!("Failed to store credential: {:?}", e))
526            })?;
527        }
528
529        Ok(())
530    }
531
532    fn retrieve_from_keychain(&self) -> Result<String> {
533        // Try platform-specific keychain first
534        #[cfg(target_os = "macos")]
535        {
536            let home = std::env::var("HOME").map_err(|_| {
537                EncryptionError::InvalidKey("HOME environment variable not set".to_string())
538            })?;
539            let key_path = std::path::Path::new(&home).join(".mockforge").join("master_key");
540            let key_str = std::fs::read_to_string(&key_path).map_err(|_| {
541                EncryptionError::InvalidKey("Master key not found in keychain".to_string())
542            })?;
543            // Trim whitespace/newlines that might have been added during storage
544            Ok(key_str.trim().to_string())
545        }
546
547        #[cfg(target_os = "linux")]
548        {
549            let home = std::env::var("HOME").map_err(|_| {
550                EncryptionError::InvalidKey("HOME environment variable not set".to_string())
551            })?;
552            let key_path = std::path::Path::new(&home).join(".mockforge").join("master_key");
553            let key_str = std::fs::read_to_string(&key_path).map_err(|_| {
554                EncryptionError::InvalidKey("Master key not found in keychain".to_string())
555            })?;
556            // Trim whitespace/newlines that might have been added during storage
557            Ok(key_str.trim().to_string())
558        }
559
560        #[cfg(target_os = "windows")]
561        {
562            // Windows Credential Manager
563            self.retrieve_from_windows_credential_manager()
564        }
565
566        #[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
567        {
568            // Fallback for other platforms
569            let key_str = std::env::var("MOCKFORGE_MASTER_KEY").map_err(|_| {
570                EncryptionError::InvalidKey("Master key not found in keychain".to_string())
571            })?;
572            // Trim whitespace/newlines that might have been added
573            Ok(key_str.trim().to_string())
574        }
575    }
576
577    #[cfg(target_os = "windows")]
578    fn retrieve_from_windows_credential_manager(&self) -> Result<String> {
579        use std::ffi::OsString;
580        use std::os::windows::ffi::OsStringExt;
581        use windows::core::PCWSTR;
582        use windows::Win32::Security::Credentials::{
583            CredFree, CredReadW, CREDENTIALW, CRED_TYPE_GENERIC,
584        };
585
586        let target_name = "MockForge/MasterKey";
587        let target_name_wide: Vec<u16> =
588            OsString::from(target_name).encode_wide().chain(std::iter::once(0)).collect();
589
590        let mut credential_ptr: *mut CREDENTIALW = std::ptr::null_mut();
591
592        unsafe {
593            CredReadW(
594                PCWSTR::from_raw(target_name_wide.as_ptr()),
595                CRED_TYPE_GENERIC,
596                0,
597                &mut credential_ptr,
598            )
599            .map_err(|e| {
600                EncryptionError::InvalidKey(format!("Failed to read credential: {:?}", e))
601            })?;
602
603            if credential_ptr.is_null() {
604                return Err(EncryptionError::InvalidKey("Credential not found".to_string()));
605            }
606
607            // Dereference the credential pointer
608            let credential = &*credential_ptr;
609
610            // Convert the credential blob back to string
611            // The blob is stored as UTF-16, so we need to convert it properly
612            let blob_slice = std::slice::from_raw_parts(
613                credential.CredentialBlob as *const u16,
614                credential.CredentialBlobSize as usize / 2, // Divide by 2 for UTF-16
615            );
616
617            let credential_str = OsString::from_wide(blob_slice)
618                .to_string_lossy()
619                .trim_end_matches('\0')
620                .to_string();
621
622            // Free the credential
623            CredFree(credential_ptr as *mut std::ffi::c_void);
624
625            Ok(credential_str)
626        }
627    }
628}
629
630impl Default for MasterKeyManager {
631    fn default() -> Self {
632        Self::new()
633    }
634}
635
636/// Workspace key manager for handling per-workspace encryption keys
637pub struct WorkspaceKeyManager {
638    master_key_manager: MasterKeyManager,
639    key_storage: std::cell::RefCell<FileKeyStorage>,
640}
641
642impl WorkspaceKeyManager {
643    /// Create a new workspace key manager
644    pub fn new() -> Self {
645        Self {
646            master_key_manager: MasterKeyManager::new(),
647            key_storage: std::cell::RefCell::new(FileKeyStorage::new()),
648        }
649    }
650
651    /// Create a workspace key manager with custom key storage path
652    pub fn with_storage_path<P: AsRef<std::path::Path>>(path: P) -> Self {
653        Self {
654            master_key_manager: MasterKeyManager::new(),
655            key_storage: std::cell::RefCell::new(FileKeyStorage::with_path(path)),
656        }
657    }
658
659    /// Generate a new workspace key and encrypt it with the master key
660    pub fn generate_workspace_key(&self, workspace_id: &str) -> Result<String> {
661        // Generate a new 32-byte workspace key
662        let workspace_key_bytes: [u8; 32] = rand::random();
663
664        // Get the master key to encrypt the workspace key
665        let master_key = self.master_key_manager.get_master_key()?;
666
667        // Encrypt the workspace key with the master key
668        let workspace_key_b64 = master_key.encrypt_chacha20(
669            &general_purpose::STANDARD.encode(workspace_key_bytes),
670            Some(workspace_id.as_bytes()),
671        )?;
672
673        // Store the encrypted workspace key (in database or secure storage)
674        self.store_workspace_key(workspace_id, &workspace_key_b64)?;
675
676        Ok(workspace_key_b64)
677    }
678
679    /// Get the decrypted workspace key for a given workspace
680    pub fn get_workspace_key(&self, workspace_id: &str) -> Result<EncryptionKey> {
681        let encrypted_key_b64 = self.retrieve_workspace_key(workspace_id)?;
682        let master_key = self.master_key_manager.get_master_key()?;
683
684        let decrypted_key_b64 =
685            master_key.decrypt_chacha20(&encrypted_key_b64, Some(workspace_id.as_bytes()))?;
686
687        let workspace_key_bytes = general_purpose::STANDARD
688            .decode(decrypted_key_b64)
689            .map_err(|e| EncryptionError::InvalidKey(e.to_string()))?;
690
691        if workspace_key_bytes.len() != 32 {
692            return Err(EncryptionError::InvalidKey("Invalid workspace key length".to_string()));
693        }
694
695        EncryptionKey::new(EncryptionAlgorithm::ChaCha20Poly1305, workspace_key_bytes)
696    }
697
698    /// Check if workspace key exists
699    pub fn has_workspace_key(&self, workspace_id: &str) -> bool {
700        self.retrieve_workspace_key(workspace_id).is_ok()
701    }
702
703    /// Generate a backup string for the workspace key (for sharing between devices)
704    pub fn generate_workspace_key_backup(&self, workspace_id: &str) -> Result<String> {
705        let encrypted_key = self.retrieve_workspace_key(workspace_id)?;
706
707        // Create a human-readable backup format like:
708        // YKV2DK-HT1MD0-8EB48W-PPWHVA-TYJT14-1NWBYN-V874M9-RKJ41R-W95MY0
709        let backup_string = self.format_backup_string(&encrypted_key);
710
711        Ok(backup_string)
712    }
713
714    /// Restore workspace key from backup string
715    pub fn restore_workspace_key_from_backup(
716        &self,
717        workspace_id: &str,
718        backup_string: &str,
719    ) -> Result<()> {
720        let encrypted_key = self.parse_backup_string(backup_string)?;
721        self.store_workspace_key(workspace_id, &encrypted_key)
722    }
723
724    // Storage methods using secure file-based storage
725    fn store_workspace_key(&self, workspace_id: &str, encrypted_key: &str) -> Result<()> {
726        self.key_storage
727            .borrow_mut()
728            .store_key(&workspace_id.to_string(), encrypted_key.as_bytes())
729            .map_err(|e| {
730                EncryptionError::InvalidKey(format!("Failed to store workspace key: {:?}", e))
731            })
732    }
733
734    fn retrieve_workspace_key(&self, workspace_id: &str) -> Result<String> {
735        // First try the new secure storage
736        match self.key_storage.borrow().retrieve_key(&workspace_id.to_string()) {
737            Ok(encrypted_bytes) => String::from_utf8(encrypted_bytes).map_err(|e| {
738                EncryptionError::InvalidKey(format!("Invalid UTF-8 in stored key: {}", e))
739            }),
740            Err(_) => {
741                // Fall back to old file-based storage for backward compatibility
742                let old_key_file = format!("workspace_{}_key.enc", workspace_id);
743                match std::fs::read_to_string(&old_key_file) {
744                    Ok(encrypted_key) => {
745                        // Migrate to new storage
746                        if let Err(e) = self
747                            .key_storage
748                            .borrow_mut()
749                            .store_key(&workspace_id.to_string(), encrypted_key.as_bytes())
750                        {
751                            tracing::warn!(
752                                "Failed to migrate workspace key to new storage: {:?}",
753                                e
754                            );
755                        } else {
756                            // Try to remove old file
757                            let _ = std::fs::remove_file(&old_key_file);
758                        }
759                        Ok(encrypted_key)
760                    }
761                    Err(_) => Err(EncryptionError::InvalidKey(format!(
762                        "Workspace key not found for: {}",
763                        workspace_id
764                    ))),
765                }
766            }
767        }
768    }
769
770    fn format_backup_string(&self, encrypted_key: &str) -> String {
771        // Convert to a format like: XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX
772        let chars: Vec<char> = encrypted_key.chars().collect();
773        let mut result = String::new();
774
775        for (i, &ch) in chars.iter().enumerate() {
776            if i > 0 && i % 6 == 0 && i < chars.len() - 1 {
777                result.push('-');
778            }
779            result.push(ch);
780        }
781
782        // Pad or truncate to create consistent format
783        if result.len() > 59 {
784            // 9 groups of 6 chars + 8 dashes = 54 + 8 = 62, but we want 59 for readability
785            result.truncate(59);
786        }
787
788        result
789    }
790
791    fn parse_backup_string(&self, backup_string: &str) -> Result<String> {
792        // Remove dashes and return the encrypted key
793        Ok(backup_string.replace("-", ""))
794    }
795}
796
797impl Default for WorkspaceKeyManager {
798    fn default() -> Self {
799        Self::new()
800    }
801}
802
803/// Configuration for automatic encryption of sensitive fields
804#[derive(Debug, Clone, Serialize, Deserialize)]
805pub struct AutoEncryptionConfig {
806    /// Whether automatic encryption is enabled
807    pub enabled: bool,
808    /// List of header names to automatically encrypt
809    pub sensitive_headers: Vec<String>,
810    /// List of JSON field paths to automatically encrypt in request/response bodies
811    pub sensitive_fields: Vec<String>,
812    /// List of environment variable names to automatically encrypt
813    pub sensitive_env_vars: Vec<String>,
814    /// Custom patterns for detecting sensitive data (regex)
815    pub sensitive_patterns: Vec<String>,
816}
817
818impl Default for AutoEncryptionConfig {
819    fn default() -> Self {
820        Self {
821            enabled: false,
822            sensitive_headers: vec![
823                "authorization".to_string(),
824                "x-api-key".to_string(),
825                "x-auth-token".to_string(),
826                "cookie".to_string(),
827                "set-cookie".to_string(),
828            ],
829            sensitive_fields: vec![
830                "password".to_string(),
831                "token".to_string(),
832                "secret".to_string(),
833                "key".to_string(),
834                "credentials".to_string(),
835            ],
836            sensitive_env_vars: vec![
837                "API_KEY".to_string(),
838                "SECRET_KEY".to_string(),
839                "PASSWORD".to_string(),
840                "TOKEN".to_string(),
841                "DATABASE_URL".to_string(),
842            ],
843            sensitive_patterns: vec![
844                r"\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b".to_string(), // Credit card numbers
845                r"\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b".to_string(),            // SSN pattern
846            ],
847        }
848    }
849}
850
851/// Automatic encryption processor for sensitive data
852pub struct AutoEncryptionProcessor {
853    config: AutoEncryptionConfig,
854    workspace_manager: WorkspaceKeyManager,
855    workspace_id: String,
856}
857
858impl AutoEncryptionProcessor {
859    /// Create a new auto-encryption processor
860    pub fn new(workspace_id: &str, config: AutoEncryptionConfig) -> Self {
861        Self {
862            config,
863            workspace_manager: WorkspaceKeyManager::new(),
864            workspace_id: workspace_id.to_string(),
865        }
866    }
867
868    /// Process headers and encrypt sensitive ones
869    pub fn process_headers(
870        &self,
871        headers: &mut std::collections::HashMap<String, String>,
872    ) -> Result<()> {
873        if !self.config.enabled {
874            return Ok(());
875        }
876
877        let workspace_key = self.workspace_manager.get_workspace_key(&self.workspace_id)?;
878
879        for (key, value) in headers.iter_mut() {
880            if self.is_sensitive_header(key) && !self.is_already_encrypted(value) {
881                *value = workspace_key.encrypt_chacha20(value, Some(key.as_bytes()))?;
882            }
883        }
884
885        Ok(())
886    }
887
888    /// Process JSON data and encrypt sensitive fields
889    pub fn process_json(&self, json: &mut serde_json::Value) -> Result<()> {
890        if !self.config.enabled {
891            return Ok(());
892        }
893
894        let workspace_key = self.workspace_manager.get_workspace_key(&self.workspace_id)?;
895        self.process_json_recursive(json, &workspace_key, Vec::new())?;
896
897        Ok(())
898    }
899
900    /// Process environment variables and encrypt sensitive ones
901    pub fn process_env_vars(
902        &self,
903        env_vars: &mut std::collections::HashMap<String, String>,
904    ) -> Result<()> {
905        if !self.config.enabled {
906            return Ok(());
907        }
908
909        let workspace_key = self.workspace_manager.get_workspace_key(&self.workspace_id)?;
910
911        for (key, value) in env_vars.iter_mut() {
912            if self.is_sensitive_env_var(key) && !self.is_already_encrypted(value) {
913                *value = workspace_key.encrypt_chacha20(value, Some(key.as_bytes()))?;
914            }
915        }
916
917        Ok(())
918    }
919
920    /// Check if a header should be encrypted
921    fn is_sensitive_header(&self, header_name: &str) -> bool {
922        self.config
923            .sensitive_headers
924            .iter()
925            .any(|h| h.eq_ignore_ascii_case(header_name))
926    }
927
928    /// Check if an environment variable should be encrypted
929    fn is_sensitive_env_var(&self, var_name: &str) -> bool {
930        self.config.sensitive_env_vars.iter().any(|v| v.eq_ignore_ascii_case(var_name))
931    }
932
933    /// Check if a field path should be encrypted
934    fn is_sensitive_field(&self, field_path: &[String]) -> bool {
935        let default_field = String::new();
936        let field_name = field_path.last().unwrap_or(&default_field);
937
938        // Check exact field names
939        if self.config.sensitive_fields.iter().any(|f| f.eq_ignore_ascii_case(field_name)) {
940            return true;
941        }
942
943        // Check field path patterns
944        let path_str = field_path.join(".");
945        for pattern in &self.config.sensitive_patterns {
946            if let Ok(regex) = regex::Regex::new(pattern) {
947                if regex.is_match(&path_str) || regex.is_match(field_name) {
948                    return true;
949                }
950            }
951        }
952
953        false
954    }
955
956    /// Check if a value appears to already be encrypted
957    fn is_already_encrypted(&self, value: &str) -> bool {
958        // Simple heuristic: encrypted values are usually base64 and longer than plaintext
959        value.len() > 100 && general_purpose::STANDARD.decode(value).is_ok()
960    }
961
962    /// Recursively process JSON to encrypt sensitive fields
963    fn process_json_recursive(
964        &self,
965        json: &mut serde_json::Value,
966        workspace_key: &EncryptionKey,
967        current_path: Vec<String>,
968    ) -> Result<()> {
969        match json {
970            serde_json::Value::Object(obj) => {
971                for (key, value) in obj.iter_mut() {
972                    let mut new_path = current_path.clone();
973                    new_path.push(key.clone());
974
975                    if let serde_json::Value::String(ref mut s) = value {
976                        if self.is_sensitive_field(&new_path) && !self.is_already_encrypted(s) {
977                            let path_str = new_path.join(".");
978                            let path_bytes = path_str.as_bytes();
979                            *s = workspace_key.encrypt_chacha20(s, Some(path_bytes))?;
980                        }
981                    } else {
982                        self.process_json_recursive(value, workspace_key, new_path)?;
983                    }
984                }
985            }
986            serde_json::Value::Array(arr) => {
987                for (index, item) in arr.iter_mut().enumerate() {
988                    let mut new_path = current_path.clone();
989                    new_path.push(index.to_string());
990                    self.process_json_recursive(item, workspace_key, new_path)?;
991                }
992            }
993            _ => {} // Primitive values are handled at the object level
994        }
995
996        Ok(())
997    }
998}
999
1000/// Utility functions for encryption operations
1001pub mod utils {
1002    use super::*;
1003
1004    /// Check if encryption is enabled for a workspace
1005    pub async fn is_encryption_enabled_for_workspace(
1006        persistence: &WorkspacePersistence,
1007        workspace_id: &str,
1008    ) -> Result<bool> {
1009        // Try to load workspace and check settings
1010        if let Ok(workspace) = persistence.load_workspace(workspace_id).await {
1011            return Ok(workspace.config.auto_encryption.enabled);
1012        }
1013        // Fallback: check if workspace key exists (for backward compatibility)
1014        let manager = WorkspaceKeyManager::new();
1015        Ok(manager.has_workspace_key(workspace_id))
1016    }
1017
1018    /// Get the auto-encryption config for a workspace
1019    pub async fn get_auto_encryption_config(
1020        persistence: &WorkspacePersistence,
1021        workspace_id: &str,
1022    ) -> Result<AutoEncryptionConfig> {
1023        let workspace = persistence.load_workspace(workspace_id).await.map_err(|e| {
1024            EncryptionError::Generic {
1025                message: format!("Failed to load workspace: {}", e),
1026            }
1027        })?;
1028        Ok(workspace.config.auto_encryption)
1029    }
1030
1031    /// Encrypt data for a specific workspace
1032    pub fn encrypt_for_workspace(workspace_id: &str, data: &str) -> Result<String> {
1033        let manager = WorkspaceKeyManager::new();
1034        let key = manager.get_workspace_key(workspace_id)?;
1035        key.encrypt_chacha20(data, None)
1036    }
1037
1038    /// Decrypt data for a specific workspace
1039    pub fn decrypt_for_workspace(workspace_id: &str, encrypted_data: &str) -> Result<String> {
1040        let manager = WorkspaceKeyManager::new();
1041        let key = manager.get_workspace_key(workspace_id)?;
1042        key.decrypt_chacha20(encrypted_data, None)
1043    }
1044}
1045
1046/// Encrypt text using a stored key
1047pub fn encrypt_with_key(
1048    key_id: &str,
1049    plaintext: &str,
1050    associated_data: Option<&[u8]>,
1051) -> Result<String> {
1052    let store = get_key_store()
1053        .ok_or_else(|| EncryptionError::InvalidKey("Key store not initialized".to_string()))?;
1054
1055    let key = store
1056        .get_key(key_id)
1057        .ok_or_else(|| EncryptionError::InvalidKey(format!("Key '{}' not found", key_id)))?;
1058
1059    key.encrypt(plaintext, associated_data)
1060}
1061
1062/// Decrypt text using a stored key
1063pub fn decrypt_with_key(
1064    key_id: &str,
1065    ciphertext: &str,
1066    associated_data: Option<&[u8]>,
1067) -> Result<String> {
1068    let store = get_key_store()
1069        .ok_or_else(|| EncryptionError::InvalidKey("Key store not initialized".to_string()))?;
1070
1071    let key = store
1072        .get_key(key_id)
1073        .ok_or_else(|| EncryptionError::InvalidKey(format!("Key '{}' not found", key_id)))?;
1074
1075    key.decrypt(ciphertext, associated_data)
1076}
1077
1078mod algorithms;
1079mod auto_encryption;
1080mod derivation;
1081mod errors;
1082mod key_management;
1083mod key_rotation;
1084
1085#[cfg(test)]
1086mod tests {
1087    use super::*;
1088    use once_cell::sync::Lazy;
1089    use std::sync::Mutex;
1090
1091    static MASTER_KEY_TEST_LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
1092
1093    #[test]
1094    fn test_aes_gcm_encrypt_decrypt() {
1095        let key = EncryptionKey::from_password_pbkdf2(
1096            "test_password",
1097            None,
1098            EncryptionAlgorithm::Aes256Gcm,
1099        )
1100        .unwrap();
1101
1102        let plaintext = "Hello, World!";
1103        let ciphertext = key.encrypt(plaintext, None).unwrap();
1104        let decrypted = key.decrypt(&ciphertext, None).unwrap();
1105
1106        assert_eq!(plaintext, decrypted);
1107    }
1108
1109    #[test]
1110    fn test_chacha20_encrypt_decrypt() {
1111        let key = EncryptionKey::from_password_pbkdf2(
1112            "test_password",
1113            None,
1114            EncryptionAlgorithm::ChaCha20Poly1305,
1115        )
1116        .unwrap();
1117
1118        let plaintext = "Hello, World!";
1119        let ciphertext = key.encrypt(plaintext, None).unwrap();
1120        let decrypted = key.decrypt(&ciphertext, None).unwrap();
1121
1122        assert_eq!(plaintext, decrypted);
1123    }
1124
1125    #[test]
1126    fn test_key_store() {
1127        let mut store = KeyStore::new();
1128
1129        store
1130            .derive_and_store_key(
1131                "test_key".to_string(),
1132                "test_password",
1133                EncryptionAlgorithm::Aes256Gcm,
1134                KeyDerivationMethod::Pbkdf2,
1135            )
1136            .unwrap();
1137
1138        assert!(store.get_key("test_key").is_some());
1139        assert!(store.list_keys().contains(&"test_key".to_string()));
1140
1141        store.remove_key("test_key");
1142        assert!(store.get_key("test_key").is_none());
1143    }
1144
1145    #[test]
1146    fn test_invalid_key_length() {
1147        let result = EncryptionKey::new(EncryptionAlgorithm::Aes256Gcm, vec![1, 2, 3]);
1148        assert!(matches!(result, Err(EncryptionError::InvalidKey(_))));
1149    }
1150
1151    #[test]
1152    fn test_invalid_ciphertext() {
1153        let key = EncryptionKey::from_password_pbkdf2("test", None, EncryptionAlgorithm::Aes256Gcm)
1154            .unwrap();
1155        let result = key.decrypt("invalid_base64!", None);
1156        assert!(matches!(result, Err(EncryptionError::InvalidCiphertext(_))));
1157    }
1158
1159    #[test]
1160    fn test_chacha20_encrypt_decrypt_12byte_nonce() {
1161        let key = EncryptionKey::from_password_pbkdf2(
1162            "test_password",
1163            None,
1164            EncryptionAlgorithm::ChaCha20Poly1305,
1165        )
1166        .unwrap();
1167
1168        let plaintext = "Hello, World! This is a test of ChaCha20-Poly1305 with 12-byte nonce.";
1169        let ciphertext = key.encrypt_chacha20(plaintext, None).unwrap();
1170        let decrypted = key.decrypt_chacha20(&ciphertext, None).unwrap();
1171
1172        assert_eq!(plaintext, decrypted);
1173    }
1174
1175    #[test]
1176    fn test_secure_function_template() {
1177        use crate::templating::expand_str;
1178
1179        // Test that secure() function uses ChaCha20-Poly1305
1180        let template = r#"{{secure "test message"}}"#;
1181        let result = expand_str(template);
1182
1183        // The result should be a base64-encoded ciphertext (not the original message)
1184        assert_ne!(result, "test message");
1185        assert!(!result.is_empty());
1186
1187        // The result should be valid base64
1188        assert!(general_purpose::STANDARD.decode(&result).is_ok());
1189    }
1190
1191    #[test]
1192    fn test_master_key_manager() {
1193        let _guard = MASTER_KEY_TEST_LOCK.lock().unwrap();
1194
1195        let manager = MasterKeyManager::new();
1196
1197        // Clean up any existing master key from previous tests
1198        #[cfg(any(target_os = "macos", target_os = "linux"))]
1199        {
1200            if let Ok(home) = std::env::var("HOME") {
1201                let key_path = std::path::Path::new(&home).join(".mockforge").join("master_key");
1202                let _ = std::fs::remove_file(&key_path);
1203            }
1204        }
1205
1206        // Initially should not have a master key
1207        assert!(!manager.has_master_key());
1208
1209        // Generate a master key
1210        manager.generate_master_key().unwrap();
1211        assert!(manager.has_master_key());
1212
1213        // Should be able to retrieve the master key
1214        let master_key = manager.get_master_key().unwrap();
1215        assert_eq!(master_key.algorithm, EncryptionAlgorithm::ChaCha20Poly1305);
1216    }
1217
1218    #[test]
1219    fn test_workspace_key_manager() {
1220        let _guard = MASTER_KEY_TEST_LOCK.lock().unwrap();
1221
1222        // First ensure we have a valid master key
1223        let master_manager = MasterKeyManager::new();
1224        let needs_generation =
1225            !master_manager.has_master_key() || master_manager.get_master_key().is_err();
1226        if needs_generation && master_manager.generate_master_key().is_err() {
1227            // Skip test if we can't set up master key
1228            return;
1229        }
1230
1231        let workspace_manager = WorkspaceKeyManager::new();
1232        let workspace_id = &format!("test_workspace_{}", uuid::Uuid::new_v4());
1233
1234        // Initially should not have a workspace key
1235        assert!(!workspace_manager.has_workspace_key(workspace_id));
1236
1237        // Generate a workspace key
1238        let encrypted_key = workspace_manager.generate_workspace_key(workspace_id).unwrap();
1239        assert!(workspace_manager.has_workspace_key(workspace_id));
1240        assert!(!encrypted_key.is_empty());
1241
1242        // Should be able to retrieve and use the workspace key
1243        let workspace_key = workspace_manager.get_workspace_key(workspace_id).unwrap();
1244        assert_eq!(workspace_key.algorithm, EncryptionAlgorithm::ChaCha20Poly1305);
1245
1246        // Test encryption/decryption with workspace key
1247        let test_data = "sensitive workspace data";
1248        let ciphertext = workspace_key.encrypt_chacha20(test_data, None).unwrap();
1249        let decrypted = workspace_key.decrypt_chacha20(&ciphertext, None).unwrap();
1250        assert_eq!(test_data, decrypted);
1251    }
1252
1253    #[test]
1254    fn test_backup_string_formatting() {
1255        let manager = WorkspaceKeyManager::new();
1256
1257        // Test backup string formatting with a shorter key
1258        let test_key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrst";
1259        let backup = manager.format_backup_string(test_key);
1260
1261        // Should contain dashes
1262        assert!(backup.contains('-'));
1263
1264        // Should be able to parse back
1265        let parsed = manager.parse_backup_string(&backup).unwrap();
1266        assert_eq!(parsed, test_key);
1267    }
1268
1269    #[test]
1270    fn test_auto_encryption_processor() {
1271        let _guard = MASTER_KEY_TEST_LOCK.lock().unwrap();
1272
1273        // Setup workspace with encryption enabled
1274        let master_manager = MasterKeyManager::new();
1275        let needs_key =
1276            !master_manager.has_master_key() || master_manager.get_master_key().is_err();
1277        if needs_key && master_manager.generate_master_key().is_err() {
1278            eprintln!("Skipping test_auto_encryption_processor: Failed to generate master key");
1279            return;
1280        }
1281
1282        let workspace_manager = WorkspaceKeyManager::new();
1283        let workspace_id = &format!("test_auto_encrypt_workspace_{}", uuid::Uuid::new_v4());
1284
1285        workspace_manager.generate_workspace_key(workspace_id).unwrap();
1286
1287        let config = AutoEncryptionConfig {
1288            enabled: true,
1289            ..AutoEncryptionConfig::default()
1290        };
1291
1292        let processor = AutoEncryptionProcessor::new(workspace_id, config);
1293
1294        // Test header encryption
1295        let mut headers = std::collections::HashMap::new();
1296        headers.insert("Authorization".to_string(), "Bearer my-secret-token".to_string());
1297        headers.insert("Content-Type".to_string(), "application/json".to_string());
1298
1299        processor.process_headers(&mut headers).unwrap();
1300
1301        // Authorization header should be encrypted
1302        assert_ne!(headers["Authorization"], "Bearer my-secret-token");
1303        assert!(general_purpose::STANDARD.decode(&headers["Authorization"]).is_ok());
1304
1305        // Content-Type should remain unchanged
1306        assert_eq!(headers["Content-Type"], "application/json");
1307    }
1308
1309    #[test]
1310    #[ignore] // Requires system keychain access
1311    fn test_json_field_encryption() {
1312        let _guard = MASTER_KEY_TEST_LOCK.lock().unwrap();
1313
1314        // Setup workspace
1315        let master_manager = MasterKeyManager::new();
1316        if !master_manager.has_master_key() || master_manager.get_master_key().is_err() {
1317            master_manager.generate_master_key().unwrap();
1318        }
1319
1320        let workspace_manager = WorkspaceKeyManager::new();
1321        let workspace_id = &format!("test_json_workspace_{}", uuid::Uuid::new_v4());
1322
1323        workspace_manager.generate_workspace_key(workspace_id).unwrap();
1324
1325        // Verify key is accessible from different manager instance
1326        let another_manager = WorkspaceKeyManager::new();
1327        assert!(another_manager.has_workspace_key(workspace_id));
1328
1329        let config = AutoEncryptionConfig {
1330            enabled: true,
1331            ..AutoEncryptionConfig::default()
1332        };
1333
1334        let processor = AutoEncryptionProcessor::new(workspace_id, config);
1335
1336        // Test JSON encryption
1337        let mut json = serde_json::json!({
1338            "username": "testuser",
1339            "password": "secret123",
1340            "email": "test@example.com",
1341            "nested": {
1342                "token": "my-api-token",
1343                "normal_field": "normal_value"
1344            }
1345        });
1346
1347        processor.process_json(&mut json).unwrap();
1348
1349        // Password and token should be encrypted
1350        assert_ne!(json["password"], "secret123");
1351        assert_ne!(json["nested"]["token"], "my-api-token");
1352
1353        // Username and email should remain unchanged
1354        assert_eq!(json["username"], "testuser");
1355        assert_eq!(json["email"], "test@example.com");
1356        assert_eq!(json["nested"]["normal_field"], "normal_value");
1357    }
1358
1359    #[test]
1360    fn test_env_var_encryption() {
1361        let _guard = MASTER_KEY_TEST_LOCK.lock().unwrap();
1362
1363        // Setup workspace
1364        let master_manager = MasterKeyManager::new();
1365        if !master_manager.has_master_key() || master_manager.get_master_key().is_err() {
1366            master_manager.generate_master_key().unwrap();
1367        }
1368
1369        let workspace_manager = WorkspaceKeyManager::new();
1370        let workspace_id = &format!("test_env_workspace_{}", uuid::Uuid::new_v4());
1371
1372        workspace_manager.generate_workspace_key(workspace_id).unwrap();
1373
1374        let config = AutoEncryptionConfig {
1375            enabled: true,
1376            ..AutoEncryptionConfig::default()
1377        };
1378
1379        let processor = AutoEncryptionProcessor::new(workspace_id, config);
1380
1381        // Test environment variable encryption
1382        let mut env_vars = std::collections::HashMap::new();
1383        env_vars.insert("API_KEY".to_string(), "sk-1234567890abcdef".to_string());
1384        env_vars
1385            .insert("DATABASE_URL".to_string(), "postgres://user:pass@host:5432/db".to_string());
1386        env_vars.insert("NORMAL_VAR".to_string(), "normal_value".to_string());
1387
1388        processor.process_env_vars(&mut env_vars).unwrap();
1389
1390        // Sensitive env vars should be encrypted
1391        assert_ne!(env_vars["API_KEY"], "sk-1234567890abcdef");
1392        assert_ne!(env_vars["DATABASE_URL"], "postgres://user:pass@host:5432/db");
1393
1394        // Normal var should remain unchanged
1395        assert_eq!(env_vars["NORMAL_VAR"], "normal_value");
1396    }
1397
1398    #[test]
1399    fn test_encryption_utils() {
1400        let _guard = MASTER_KEY_TEST_LOCK.lock().unwrap();
1401
1402        // Setup workspace
1403        let master_manager = MasterKeyManager::new();
1404        let needs_key =
1405            !master_manager.has_master_key() || master_manager.get_master_key().is_err();
1406        if needs_key && master_manager.generate_master_key().is_err() {
1407            eprintln!("Skipping test_encryption_utils: Failed to generate master key");
1408            return;
1409        }
1410
1411        let workspace_manager = WorkspaceKeyManager::new();
1412        let workspace_id = &format!("test_utils_workspace_{}", uuid::Uuid::new_v4());
1413        workspace_manager.generate_workspace_key(workspace_id).unwrap();
1414
1415        // Test utility functions - check if key exists (encryption enabled)
1416        assert!(workspace_manager.has_workspace_key(workspace_id));
1417
1418        // Verify key is accessible from a different manager instance
1419        let another_manager = WorkspaceKeyManager::new();
1420        assert!(another_manager.has_workspace_key(workspace_id));
1421
1422        let test_data = "test data for utils";
1423        let encrypted = utils::encrypt_for_workspace(workspace_id, test_data).unwrap();
1424        let decrypted = utils::decrypt_for_workspace(workspace_id, &encrypted).unwrap();
1425
1426        assert_eq!(test_data, decrypted);
1427        assert_ne!(encrypted, test_data);
1428    }
1429}