Skip to main content

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