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