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