1use crate::audit::{AuditEvent, AuditLogEntry, AuditLogger, NoOpLogger};
4use crate::{
5 crypto::{NonceGenerator, RandomNonceGenerator, RuntimeAead, AEAD},
6 key::VersionedKey,
7 KeyId, KeyMetadata, KeyState, Result,
8};
9use argon2::Argon2;
10use argon2::{Algorithm as Argon2Algorithm, Params, Version};
11use rand_chacha::ChaCha20Rng;
12use rand_core::{RngCore, SeedableRng};
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::fs;
16use std::path::{Path, PathBuf};
17use std::sync::{Arc, RwLock};
18use std::time::SystemTime;
19
20#[derive(Clone, Serialize, Deserialize)]
22struct VaultMetadata {
23 salt: Vec<u8>,
25 created_at: SystemTime,
27 format_version: u32,
29}
30
31impl std::fmt::Debug for VaultMetadata {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct("VaultMetadata")
34 .field("salt", &format!("[REDACTED {} bytes]", self.salt.len()))
35 .field("created_at", &self.created_at)
36 .field("format_version", &self.format_version)
37 .finish()
38 }
39}
40
41impl VaultMetadata {
42 fn new() -> Result<Self> {
44 let mut rng = ChaCha20Rng::from_entropy();
45 let mut salt = vec![0u8; 32];
46 rng.fill_bytes(&mut salt);
47
48 Ok(Self {
49 salt,
50 created_at: SystemTime::now(),
51 format_version: 1,
52 })
53 }
54}
55
56#[derive(Debug, Clone)]
58pub struct Argon2Config {
59 pub memory_kib: u32,
61 pub time_cost: u32,
63 pub parallelism: u32,
65}
66
67impl Default for Argon2Config {
68 fn default() -> Self {
69 Self {
70 memory_kib: 19456, time_cost: 3,
72 parallelism: 4,
73 }
74 }
75}
76
77pub trait KeyStore: Send + Sync {
79 fn store(&mut self, key: VersionedKey) -> Result<()>;
81
82 fn retrieve(&self, id: &KeyId) -> Result<VersionedKey>;
84
85 fn delete(&mut self, id: &KeyId) -> Result<()>;
87
88 fn list(&self) -> Result<Vec<KeyId>>;
90
91 fn update_metadata(&mut self, id: &KeyId, metadata: KeyMetadata) -> Result<()>;
93
94 fn find_by_state(&self, state: KeyState) -> Result<Vec<KeyId>>;
96
97 fn rotate_key(&mut self, id: &KeyId) -> Result<VersionedKey>;
99
100 fn get_key_versions(&self, id: &KeyId) -> Result<Vec<VersionedKey>>;
102
103 fn get_latest_key(&self, id: &KeyId) -> Result<VersionedKey>;
105}
106
107pub trait KeyLifeCycle: KeyStore {
112 fn deprecate_key(&mut self, id: &KeyId) -> Result<()>;
114
115 fn revoke_key(&mut self, id: &KeyId) -> Result<()>;
117
118 fn cleanup_old_versions(&mut self, id: &KeyId, keep_versions: usize) -> Result<Vec<KeyId>>;
120}
121
122pub trait PersistentStorage: KeyStore {
124 fn flush(&mut self) -> Result<()>;
126
127 fn load(&mut self) -> Result<()>;
129
130 fn location(&self) -> &str;
132}
133
134#[derive(Clone, Serialize, Deserialize)]
139struct PersistedKey {
140 metadata: KeyMetadata,
142 encrypted_key: Vec<u8>,
144}
145
146impl std::fmt::Debug for PersistedKey {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 f.debug_struct("PersistedKey")
149 .field("metadata", &self.metadata)
150 .field(
151 "encrypted_key",
152 &format!("[REDACTED {} bytes]", self.encrypted_key.len()),
153 )
154 .finish()
155 }
156}
157
158pub struct MemoryStore {
163 keys: Arc<RwLock<HashMap<KeyId, VersionedKey>>>,
164}
165
166impl Default for MemoryStore {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172impl MemoryStore {
173 pub fn new() -> Self {
175 Self {
176 keys: Arc::new(RwLock::new(HashMap::new())),
177 }
178 }
179
180 fn generate_new_key_material(algorithm: crate::Algorithm) -> Result<crate::key::SecretKey> {
182 use crate::crypto::{KeyGenerator, SimpleSymmetricKeyGenerator};
183 use rand_chacha::ChaCha20Rng;
184 use rand_core::SeedableRng;
185
186 let mut rng = ChaCha20Rng::from_entropy();
187 let generator = SimpleSymmetricKeyGenerator;
188 let params = crate::crypto::KeyGenParams {
189 algorithm,
190 seed: None,
191 key_size: None,
192 };
193
194 generator.generate_with_params(&mut rng, params)
195 }
196}
197
198impl KeyStore for MemoryStore {
199 fn store(&mut self, key: VersionedKey) -> Result<()> {
200 let key_id = key.metadata.id.clone();
201 let mut keys = self
202 .keys
203 .write()
204 .map_err(|_| crate::Error::storage("lock_acquire", "lock poisoned"))?;
205 keys.insert(key_id, key);
206 Ok(())
207 }
208
209 fn retrieve(&self, id: &KeyId) -> Result<VersionedKey> {
210 let keys = self
211 .keys
212 .read()
213 .map_err(|_| crate::Error::storage("lock_acquire", "lock poisoned"))?;
214 keys.get(id)
215 .cloned()
216 .ok_or_else(|| crate::Error::storage("retrieve", &format!("key not found: {id:?}")))
217 }
218
219 fn delete(&mut self, id: &KeyId) -> Result<()> {
220 let mut keys = self
221 .keys
222 .write()
223 .map_err(|_| crate::Error::storage("lock_acquire", "lock poisoned"))?;
224 keys.remove(id)
225 .ok_or_else(|| crate::Error::storage("delete", &format!("key not found: {id:?}")))?;
226 Ok(())
227 }
228
229 fn list(&self) -> Result<Vec<KeyId>> {
230 let keys = self
231 .keys
232 .read()
233 .map_err(|_| crate::Error::storage("lock_acquire", "lock poisoned"))?;
234 Ok(keys.keys().cloned().collect())
235 }
236
237 fn update_metadata(&mut self, id: &KeyId, metadata: KeyMetadata) -> Result<()> {
238 let mut keys = self
239 .keys
240 .write()
241 .map_err(|_| crate::Error::storage("lock_acquire", "lock poisoned"))?;
242 if let Some(versioned_key) = keys.get_mut(id) {
243 versioned_key.metadata = metadata;
244 Ok(())
245 } else {
246 Err(crate::Error::storage(
247 "update_metadata",
248 &format!("key not found: {id:?}"),
249 ))
250 }
251 }
252
253 fn find_by_state(&self, state: KeyState) -> Result<Vec<KeyId>> {
254 let keys = self
255 .keys
256 .read()
257 .map_err(|_| crate::Error::storage("lock_acquire", "lock poisoned"))?;
258 Ok(keys
259 .iter()
260 .filter(|(_, versioned_key)| versioned_key.metadata.state == state)
261 .map(|(id, _)| id.clone())
262 .collect())
263 }
264
265 fn rotate_key(&mut self, id: &KeyId) -> Result<VersionedKey> {
266 let current_key = self.get_latest_key(id)?;
267 let mut deprecated_metadata = current_key.metadata.clone();
268 deprecated_metadata.state = KeyState::Deprecated;
269 self.update_metadata(id, deprecated_metadata)?;
270
271 let new_version = current_key.metadata.version + 1;
272 let new_key_id = KeyId::generate_versioned(id, new_version)?;
273
274 let new_secret_key = Self::generate_new_key_material(current_key.metadata.algorithm)?;
275 let new_metadata = KeyMetadata {
276 id: new_key_id.clone(),
277 base_id: current_key.metadata.base_id.clone(),
278 algorithm: current_key.metadata.algorithm,
279 created_at: SystemTime::now(),
280 expires_at: current_key.metadata.expires_at,
281 state: KeyState::Active,
282 version: new_version,
283 };
284
285 let new_versioned_key = VersionedKey {
286 key: new_secret_key,
287 metadata: new_metadata,
288 };
289
290 self.store(new_versioned_key.clone())?;
292
293 Ok(new_versioned_key)
294 }
295
296 fn get_key_versions(&self, id: &KeyId) -> Result<Vec<VersionedKey>> {
297 let keys = self
298 .keys
299 .read()
300 .map_err(|_| crate::Error::storage("lock_acquire", "lock poisoned"))?;
301
302 let mut versions = Vec::new();
303
304 for (_store_id, key) in keys.iter() {
306 if &key.metadata.base_id == id {
307 versions.push(key.clone());
308 }
309 }
310
311 versions.sort_by_key(|k| k.metadata.version);
313
314 if versions.is_empty() {
315 return Err(crate::Error::storage(
316 "get_key_versions",
317 &format!("no versions found for key: {id:?}"),
318 ));
319 }
320 Ok(versions)
321 }
322
323 fn get_latest_key(&self, id: &KeyId) -> Result<VersionedKey> {
324 let versions = self.get_key_versions(id)?;
325
326 versions
328 .into_iter()
329 .filter(|k| matches!(k.metadata.state, KeyState::Active | KeyState::Rotating))
330 .max_by_key(|k| k.metadata.version)
331 .ok_or_else(|| {
332 crate::Error::storage(
333 "get_latest_key",
334 &format!("no active key found for: {id:?}"),
335 )
336 })
337 }
338}
339
340impl KeyLifeCycle for MemoryStore {
341 fn deprecate_key(&mut self, id: &KeyId) -> Result<()> {
342 let mut keys = self
343 .keys
344 .write()
345 .map_err(|_| crate::Error::storage("lock_acquire", "lock poisoned"))?;
346
347 if let Some(key) = keys.get_mut(id) {
348 if !matches!(key.metadata.state, KeyState::Active | KeyState::Rotating) {
349 return Err(crate::Error::InvalidKeyState {
350 key_id: format!("{:?}", id),
351 state: format!("{:?}", key.metadata.state),
352 operation: "deprecate_key".to_string(),
353 });
354 }
355
356 key.metadata.state = KeyState::Deprecated;
357 Ok(())
358 } else {
359 Err(crate::Error::storage(
360 "deprecate_key",
361 &format!("key not found: {id:?}"),
362 ))
363 }
364 }
365
366 fn revoke_key(&mut self, id: &KeyId) -> Result<()> {
367 let mut keys = self
368 .keys
369 .write()
370 .map_err(|_| crate::Error::storage("lock_acquire", "lock poisoned"))?;
371
372 if let Some(key) = keys.get_mut(id) {
373 key.metadata.state = KeyState::Revoked;
374 Ok(())
375 } else {
376 Err(crate::Error::storage(
377 "revoke_key",
378 &format!("key not found: {id:?}"),
379 ))
380 }
381 }
382
383 fn cleanup_old_versions(&mut self, id: &KeyId, keep_versions: usize) -> Result<Vec<KeyId>> {
384 let mut versions = self.get_key_versions(id)?;
385
386 versions.sort_by_key(|k| std::cmp::Reverse(k.metadata.version));
387
388 let mut removed_keys = Vec::new();
389
390 for key_to_remove in versions.iter().skip(keep_versions) {
391 if matches!(
392 key_to_remove.metadata.state,
393 KeyState::Revoked | KeyState::Deprecated
394 ) {
395 self.delete(&key_to_remove.metadata.id)?;
396 removed_keys.push(key_to_remove.metadata.id.clone());
397 }
398 }
399
400 Ok(removed_keys)
401 }
402}
403
404#[derive(Debug, Clone)]
408pub struct StorageConfig {
409 pub path: Option<String>,
411 pub encrypted: bool,
413 pub compressed: bool,
415 pub cache_size: usize,
417 pub argon2_config: Argon2Config,
419}
420
421impl Default for StorageConfig {
422 fn default() -> Self {
423 Self {
424 path: None,
425 encrypted: false,
426 compressed: false,
427 cache_size: 100,
428 argon2_config: Argon2Config::default(),
429 }
430 }
431}
432
433impl StorageConfig {
434 pub fn high_security() -> Self {
436 Self {
437 encrypted: true,
438 argon2_config: Argon2Config {
439 memory_kib: 65536,
440 time_cost: 4,
441 parallelism: 4,
442 },
443 ..Default::default()
444 }
445 }
446
447 pub fn balanced() -> Self {
449 Self {
450 encrypted: true,
451 argon2_config: Argon2Config::default(), ..Default::default()
453 }
454 }
455
456 pub fn fast_insecure() -> Self {
458 Self {
459 encrypted: true,
460 argon2_config: Argon2Config {
461 memory_kib: 8192, time_cost: 1,
463 parallelism: 1,
464 },
465 ..Default::default()
466 }
467 }
468}
469
470pub struct FileStore {
475 path: PathBuf,
477 keys: HashMap<KeyId, VersionedKey>,
479 config: StorageConfig,
481 master_key: Option<crate::key::SecretKey>,
483 vault_metadata: Option<VaultMetadata>,
485 audit_logger: Box<dyn AuditLogger>,
487}
488
489impl std::fmt::Debug for FileStore {
490 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
491 f.debug_struct("FileStore")
492 .field("path", &self.path)
493 .field("keys", &format!("[{} keys]", self.keys.len()))
494 .field("config", &self.config)
495 .field(
496 "master_key",
497 &if self.master_key.is_some() {
498 "[SET]"
499 } else {
500 "[NOT SET]"
501 },
502 )
503 .field("vault_metadata", &self.vault_metadata)
504 .field("audit_logger", &"[...]")
505 .finish()
506 }
507}
508
509impl FileStore {
510 pub fn new<P: AsRef<Path>>(path: P, config: StorageConfig) -> Result<Self> {
512 let path = path.as_ref().to_path_buf();
513
514 if !path.exists() {
515 fs::create_dir_all(&path)?;
516 }
517
518 let mut store = Self {
519 path,
520 keys: HashMap::new(),
521 config,
522 master_key: None,
523 vault_metadata: None,
524 audit_logger: Box::new(NoOpLogger), };
526
527 store.load_metadata()?; store.load()?;
532
533 Ok(store)
534 }
535
536 pub fn rekey(&mut self, new_password: &[u8]) -> Result<()> {
538 if !self.config.encrypted {
539 return Err(crate::Error::storage(
540 "rekey",
541 "encryption not enabled in config",
542 ));
543 }
544
545 if self.master_key.is_none() {
547 return Err(crate::Error::storage(
548 "rekey",
549 "store is locked - cannot rekey",
550 ));
551 }
552
553 let all_keys: Vec<_> = self.keys.values().cloned().collect();
555
556 let salt = &self
558 .vault_metadata
559 .as_ref()
560 .ok_or_else(|| crate::Error::storage("rekey", "vault metadata not initialized"))?
561 .salt;
562
563 let new_master_key =
565 Self::derive_master_key(new_password, salt, &self.config.argon2_config)?;
566
567 self.master_key = Some(new_master_key);
569
570 for key in all_keys {
572 self.store(key)?;
573 }
574
575 Ok(())
576 }
577
578 pub fn set_master_key(&mut self, key: crate::key::SecretKey) -> Result<()> {
580 if !self.config.encrypted {
581 return Err(crate::Error::storage(
582 "set_master_key",
583 "encryption not enabled in config",
584 ));
585 }
586 self.master_key = Some(key);
587 Ok(())
588 }
589
590 fn key_path(&self, id: &KeyId) -> PathBuf {
594 let filename = format!("{id:?}.json");
595 self.path.join(filename)
596 }
597
598 fn serialize_key(&self, key: &VersionedKey) -> Result<Vec<u8>> {
600 let key_bytes = key.key.expose_secret().to_vec();
601
602 let encrypted_key = if self.config.encrypted {
603 if let Some(master_key) = &self.master_key {
604 let aead = RuntimeAead;
605 let nonce_size = match master_key.algorithm() {
607 crate::Algorithm::XChaCha20Poly1305 => 24,
608 _ => 12, };
610 let mut nonce_gen =
611 RandomNonceGenerator::new(ChaCha20Rng::from_entropy(), nonce_size);
612
613 let key_id_bytes = format!("{:?}", key.metadata.id);
614 let nonce = nonce_gen.generate_nonce(key_id_bytes.as_bytes())?;
615 let encrypted_bytes = aead.encrypt(
616 master_key,
617 &nonce,
618 &key_bytes,
619 b"rust-keyvault-key-encryption",
620 )?;
621
622 let mut result = nonce;
623 result.extend_from_slice(&encrypted_bytes);
624 result
625 } else {
626 return Err(crate::Error::storage(
627 "serialize_key",
628 "encryption enabled but no master key set",
629 ));
630 }
631 } else {
632 key_bytes
633 };
634
635 let persisted = PersistedKey {
636 metadata: key.metadata.clone(),
637 encrypted_key,
638 };
639
640 serde_json::to_vec(&persisted).map_err(|e| {
641 crate::Error::storage("serialize_key", &format!("serialization failed: {e}"))
642 })
643 }
644
645 fn deserialize_key(&self, data: &[u8]) -> Result<VersionedKey> {
647 let persisted: PersistedKey = serde_json::from_slice(data).map_err(|e| {
648 crate::Error::storage("deserialize_key", &format!("deserialization failed: {e}"))
649 })?;
650
651 let key_bytes = if self.config.encrypted {
652 if let Some(master_key) = &self.master_key {
653 let aead = RuntimeAead;
654
655 let nonce_size = match master_key.algorithm() {
657 crate::Algorithm::XChaCha20Poly1305 => 24,
658 _ => 12, };
660
661 if persisted.encrypted_key.len() < nonce_size {
662 return Err(crate::Error::storage(
663 "deserialize_key",
664 "encrypted key too short - corrupted data",
665 ));
666 }
667
668 let (nonce, ciphertext) = persisted.encrypted_key.split_at(nonce_size);
669
670 aead.decrypt(
671 master_key,
672 nonce,
673 ciphertext,
674 b"rust-keyvault-key-encryption",
675 )?
676 } else {
677 return Err(crate::Error::storage(
678 "deserialize_key",
679 "encrypted key but no master key available",
680 ));
681 }
682 } else {
683 persisted.encrypted_key
684 };
685
686 let secret_key =
687 crate::key::SecretKey::from_bytes(key_bytes, persisted.metadata.algorithm)?;
688
689 Ok(VersionedKey {
690 key: secret_key,
691 metadata: persisted.metadata,
692 })
693 }
694
695 pub fn init_with_password(&mut self, password: &[u8]) -> Result<()> {
697 if !self.config.encrypted {
698 return Err(crate::Error::storage(
699 "init_with_password",
700 "encryption not enabled in config",
701 ));
702 }
703
704 let salt = &self
705 .vault_metadata
706 .as_ref()
707 .ok_or_else(|| {
708 crate::Error::storage("init_with_password", "vault metadata not initialized")
709 })?
710 .salt;
711
712 let result = Self::derive_master_key(password, salt, &self.config.argon2_config);
713
714 let success = result.is_ok();
716 let event = AuditEvent::AuthenticationAttempt {
717 success,
718 storage_path: self.path.to_string_lossy().to_string(),
719 };
720 self.audit_logger.log(AuditLogEntry::new(event))?;
721
722 let master_key = result?;
723 self.set_master_key(master_key)?;
724
725 Ok(())
726 }
727
728 pub fn derive_master_key(
730 password: &[u8],
731 salt: &[u8],
732 argon2_config: &Argon2Config,
733 ) -> Result<crate::key::SecretKey> {
734 let params = Params::new(
735 argon2_config.memory_kib,
736 argon2_config.time_cost,
737 argon2_config.parallelism,
738 Some(32), )
740 .map_err(|e| {
741 crate::Error::crypto("argon2_config", &format!("invalid Argon2 params: {}", e))
742 })?;
743
744 let argon2 = Argon2::new(Argon2Algorithm::Argon2id, Version::V0x13, params);
745
746 let mut key_bytes = [0u8; 32];
747 argon2
748 .hash_password_into(password, salt, &mut key_bytes)
749 .map_err(|e| {
750 crate::Error::crypto("argon2_hash", &format!("Argon2 derivation failed: {}", e))
751 })?;
752
753 crate::key::SecretKey::from_bytes(key_bytes.to_vec(), crate::Algorithm::ChaCha20Poly1305)
754 }
755
756 fn generate_new_key_material(
758 &self,
759 algorithm: crate::Algorithm,
760 ) -> Result<crate::key::SecretKey> {
761 use crate::crypto::{KeyGenerator, SimpleSymmetricKeyGenerator};
762 use rand_chacha::ChaCha20Rng;
763 use rand_core::SeedableRng;
764
765 let mut rng = ChaCha20Rng::from_entropy();
766 let generator = SimpleSymmetricKeyGenerator;
767 let params = crate::crypto::KeyGenParams {
768 algorithm,
769 seed: None,
770 key_size: None,
771 };
772
773 generator.generate_with_params(&mut rng, params)
774 }
775
776 fn metadata_path(&self) -> PathBuf {
777 self.path.join(".vault_metadata.json")
778 }
779
780 fn load_metadata(&mut self) -> Result<()> {
781 let metadata_path = self.metadata_path();
782
783 if metadata_path.exists() {
784 let data = fs::read(&metadata_path)?;
785 let metadata: VaultMetadata = serde_json::from_slice(&data).map_err(|e| {
786 crate::Error::storage(
787 "load_vault_metadata",
788 &format!("failed to parse vault metadata: {}", e),
789 )
790 })?;
791 self.vault_metadata = Some(metadata);
792 } else {
793 let metadata = VaultMetadata::new()?;
794 let data = serde_json::to_vec_pretty(&metadata).map_err(|e| {
795 crate::Error::storage(
796 "load_vault_metadata",
797 &format!("failed to serialize vault metadata: {}", e),
798 )
799 })?;
800 fs::write(&metadata_path, data)?;
801 self.vault_metadata = Some(metadata);
802 }
803
804 Ok(())
805 }
806
807 pub fn enable_audit_log<P: AsRef<Path>>(&mut self, log_path: P) -> Result<()> {
809 use crate::audit::FileAuditLogger;
810 self.audit_logger = Box::new(FileAuditLogger::new(log_path)?);
811 Ok(())
812 }
813
814 pub fn set_audit_logger(&mut self, logger: Box<dyn AuditLogger>) {
816 self.audit_logger = logger;
817 }
818
819 pub fn export_key(
824 &mut self,
825 id: &KeyId,
826 password: &[u8],
827 ) -> Result<crate::export::ExportedKey> {
828 use crate::export::ExportedKey;
829
830 let versioned_key = self.retrieve(id)?;
832
833 let exported = ExportedKey::new(
835 &versioned_key.key,
836 versioned_key.metadata.clone(),
837 password,
838 crate::Algorithm::XChaCha20Poly1305,
839 )?;
840
841 let event = AuditEvent::KeyAccessed {
843 key_id: format!("{:?}", id),
844 operation: "export".to_string(),
845 };
846 self.audit_logger.log(AuditLogEntry::new(event))?;
847
848 Ok(exported)
849 }
850
851 pub fn import_key(
856 &mut self,
857 exported: &crate::export::ExportedKey,
858 password: &[u8],
859 ) -> Result<KeyId> {
860 let key = exported.decrypt(password)?;
862
863 if key.algorithm() != exported.metadata.algorithm {
865 return Err(crate::Error::SerializationError {
866 operation: "import_key".to_string(),
867 message: "key algorithm mismatch with metadata".to_string(),
868 });
869 }
870
871 let versioned_key = VersionedKey {
873 key,
874 metadata: exported.metadata.clone(),
875 };
876
877 let key_id = versioned_key.metadata.id.clone();
878
879 self.store(versioned_key)?;
881
882 let event = AuditEvent::KeyAccessed {
884 key_id: format!("{:?}", key_id),
885 operation: "import".to_string(),
886 };
887 self.audit_logger.log(AuditLogEntry::new(event))?;
888
889 Ok(key_id)
890 }
891
892 pub fn backup(
905 &mut self,
906 password: &[u8],
907 config: crate::backup::BackupConfig,
908 ) -> Result<crate::backup::VaultBackup> {
909 use crate::backup::{BackupData, VaultInfo};
910
911 let mut exported_keys = Vec::new();
913
914 let key_ids: Vec<_> = self.keys.keys().cloned().collect();
916
917 let temp_password = b"temp-internal-export-password-for-backup";
919 for key_id in key_ids {
920 if let Ok(exported) = self.export_key(&key_id, temp_password) {
921 exported_keys.push(exported);
922 }
923 }
924
925 let audit_logs = if config.include_audit_logs {
927 None
930 } else {
931 None
932 };
933
934 let created_at = self
936 .vault_metadata
937 .as_ref()
938 .map(|m| m.created_at)
939 .unwrap_or_else(SystemTime::now);
940
941 let backup_data = BackupData {
943 keys: exported_keys,
944 audit_logs,
945 vault_info: VaultInfo {
946 created_at,
947 operation_count: 0, },
949 };
950
951 crate::backup::VaultBackup::new(&backup_data, password, &config)
953 }
954
955 pub fn restore(
967 &mut self,
968 backup: &crate::backup::VaultBackup,
969 password: &[u8],
970 ) -> Result<usize> {
971 let backup_data = backup.decrypt(password)?;
973
974 let temp_password = b"temp-internal-export-password-for-backup";
976 let mut imported_count = 0;
977
978 for exported_key in backup_data.keys {
979 if let Ok(_) = self.import_key(&exported_key, temp_password) {
980 imported_count += 1;
981 }
982 }
983
984 Ok(imported_count)
985 }
986}
987
988impl KeyStore for FileStore {
989 fn store(&mut self, key: VersionedKey) -> Result<()> {
990 let key_id = key.metadata.id.clone();
991 let key_path = self.key_path(&key_id);
992
993 let data = self.serialize_key(&key)?;
995
996 fs::write(&key_path, data)?;
998
999 self.keys.insert(key_id.clone(), key.clone());
1001
1002 let event = AuditEvent::KeyCreated {
1004 key_id: format!("{:?}", key_id),
1005 algorithm: key.metadata.algorithm,
1006 version: key.metadata.version,
1007 };
1008 self.audit_logger.log(AuditLogEntry::new(event))?;
1009
1010 Ok(())
1011 }
1012
1013 fn retrieve(&self, id: &KeyId) -> Result<VersionedKey> {
1014 if let Some(key) = self.keys.get(id) {
1016 return Ok(key.clone());
1017 }
1018
1019 let key_path = self.key_path(id);
1021 if !key_path.exists() {
1022 return Err(crate::Error::storage(
1023 "retrieve",
1024 &format!("key file not found: {id:?}"),
1025 ));
1026 }
1027
1028 let data = fs::read(&key_path)?;
1029 self.deserialize_key(&data)
1030 }
1031
1032 fn delete(&mut self, id: &KeyId) -> Result<()> {
1033 let key_path = self.key_path(id);
1034
1035 if key_path.exists() {
1037 fs::remove_file(&key_path)?;
1038 }
1039
1040 self.keys.remove(id).ok_or_else(|| {
1042 crate::Error::storage("remove_from_cache", &format!("key not found: {id:?}"))
1043 })?;
1044
1045 Ok(())
1046 }
1047
1048 fn list(&self) -> Result<Vec<KeyId>> {
1049 Ok(self.keys.keys().cloned().collect())
1050 }
1051
1052 fn update_metadata(&mut self, id: &KeyId, metadata: KeyMetadata) -> Result<()> {
1053 if let Some(versioned_key) = self.keys.get_mut(id) {
1054 versioned_key.metadata = metadata;
1055 let key_copy = versioned_key.clone();
1057 self.store(key_copy)?;
1058 Ok(())
1059 } else {
1060 Err(crate::Error::storage(
1061 "update_metadata",
1062 &format!("key not found: {id:?}"),
1063 ))
1064 }
1065 }
1066
1067 fn find_by_state(&self, state: KeyState) -> Result<Vec<KeyId>> {
1068 Ok(self
1069 .keys
1070 .iter()
1071 .filter(|(_, key)| key.metadata.state == state)
1072 .map(|(id, _)| id.clone())
1073 .collect())
1074 }
1075
1076 fn rotate_key(&mut self, id: &KeyId) -> Result<VersionedKey> {
1077 let current_key = self.get_latest_key(id)?;
1078 let old_version = current_key.metadata.version;
1079
1080 let mut deprecated_metadata = current_key.metadata.clone();
1081 deprecated_metadata.state = KeyState::Deprecated;
1082 self.update_metadata(id, deprecated_metadata)?;
1083
1084 let new_version = current_key.metadata.version + 1;
1085 let new_key_id = KeyId::generate_versioned(id, new_version)?;
1086
1087 let new_secret_key = self.generate_new_key_material(current_key.key.algorithm())?;
1088 let new_metadata = KeyMetadata {
1089 id: new_key_id.clone(),
1090 algorithm: current_key.metadata.algorithm,
1091 created_at: SystemTime::now(),
1092 expires_at: current_key.metadata.expires_at,
1093 state: KeyState::Active,
1094 version: new_version,
1095 base_id: current_key.metadata.base_id.clone(),
1096 };
1097
1098 let new_versioned_key = VersionedKey {
1099 key: new_secret_key,
1100 metadata: new_metadata,
1101 };
1102
1103 self.store(new_versioned_key.clone())?;
1104
1105 let event = AuditEvent::KeyRotated {
1107 base_id: format!("{:?}", id),
1108 old_version,
1109 new_version,
1110 };
1111 self.audit_logger.log(AuditLogEntry::new(event))?;
1112
1113 Ok(new_versioned_key)
1114 }
1115
1116 fn get_key_versions(&self, id: &KeyId) -> Result<Vec<VersionedKey>> {
1117 let mut versions = Vec::new();
1118
1119 for key in self.keys.values() {
1121 if &key.metadata.base_id == id {
1122 versions.push(key.clone());
1123 }
1124 }
1125
1126 versions.sort_by_key(|k| k.metadata.version);
1128
1129 if versions.is_empty() {
1130 return Err(crate::Error::storage(
1131 "sort_by_version_number",
1132 &format!("no versions found for key: {id:?}"),
1133 ));
1134 }
1135
1136 Ok(versions)
1137 }
1138
1139 fn get_latest_key(&self, id: &KeyId) -> Result<VersionedKey> {
1140 let versions = self.get_key_versions(id)?;
1141
1142 versions
1144 .into_iter()
1145 .filter(|k| matches!(k.metadata.state, KeyState::Active | KeyState::Rotating))
1146 .max_by_key(|k| k.metadata.version)
1147 .ok_or_else(|| {
1148 crate::Error::storage(
1149 "find_active_or_rotating_key",
1150 &format!("no active key found for: {id:?}"),
1151 )
1152 })
1153 }
1154}
1155
1156impl PersistentStorage for FileStore {
1157 fn flush(&mut self) -> Result<()> {
1158 let keys: Vec<_> = self.keys.values().cloned().collect();
1160 for key in keys {
1161 self.store(key)?;
1162 }
1163 Ok(())
1164 }
1165
1166 fn load(&mut self) -> Result<()> {
1167 self.keys.clear();
1168
1169 for entry in fs::read_dir(&self.path)? {
1171 let entry = entry?;
1172 let path = entry.path();
1173
1174 if path.file_name().and_then(|s| s.to_str()) == Some(".vault_metadata.json") {
1176 continue;
1177 }
1178
1179 if path.extension().and_then(|s| s.to_str()) == Some("json") {
1180 let data = fs::read(&path)?;
1181 match self.deserialize_key(&data) {
1182 Ok(key) => {
1183 self.keys.insert(key.metadata.id.clone(), key);
1184 }
1185 Err(e) => {
1186 eprintln!("WARNING: Failed to load key from {path:?}: {e}");
1187 }
1188 }
1189 }
1190 }
1191
1192 Ok(())
1193 }
1194
1195 fn location(&self) -> &str {
1196 self.path.to_str().unwrap_or("<invalid_path>")
1197 }
1198}
1199
1200impl KeyLifeCycle for FileStore {
1201 fn deprecate_key(&mut self, id: &KeyId) -> Result<()> {
1202 let key = self.retrieve(id)?;
1203
1204 if !matches!(key.metadata.state, KeyState::Active | KeyState::Rotating) {
1205 return Err(crate::Error::InvalidKeyState {
1206 key_id: format!("{:?}", id),
1207 state: format!("{:?}", key.metadata.state),
1208 operation: "deprecate_key".to_string(),
1209 });
1210 }
1211
1212 let mut new_metadata = key.metadata.clone();
1213 new_metadata.state = KeyState::Deprecated;
1214
1215 self.update_metadata(id, new_metadata)
1216 }
1217
1218 fn revoke_key(&mut self, id: &KeyId) -> Result<()> {
1219 let key = self.retrieve(id)?;
1220
1221 let mut new_metadata = key.metadata.clone();
1222 new_metadata.state = KeyState::Revoked;
1223
1224 self.update_metadata(id, new_metadata)
1225 }
1226
1227 fn cleanup_old_versions(&mut self, id: &KeyId, keep_versions: usize) -> Result<Vec<KeyId>> {
1228 let mut versions = self.get_key_versions(id)?;
1229
1230 versions.sort_by_key(|k| std::cmp::Reverse(k.metadata.version));
1232
1233 let mut removed_keys = Vec::new();
1234
1235 for key_to_remove in versions.iter().skip(keep_versions) {
1237 if matches!(
1238 key_to_remove.metadata.state,
1239 KeyState::Revoked | KeyState::Deprecated
1240 ) {
1241 self.delete(&key_to_remove.metadata.id)?;
1242 removed_keys.push(key_to_remove.metadata.id.clone());
1243 }
1244 }
1245
1246 Ok(removed_keys)
1247 }
1248}
1249
1250#[cfg(test)]
1251mod tests {
1252 use super::*;
1253
1254 #[test]
1255 fn test_storage_config_default() {
1256 let config = StorageConfig::default();
1257 assert!(!config.encrypted);
1258 assert!(!config.compressed);
1259 }
1260
1261 #[test]
1262 fn test_memory_store_basic_operations() {
1263 use crate::{key::SecretKey, Algorithm};
1264 use std::time::SystemTime;
1265
1266 let mut store = MemoryStore::new();
1267
1268 let key_id = KeyId::from_bytes([1; 16]);
1270 let secret_key = SecretKey::from_bytes(vec![0u8; 32], Algorithm::ChaCha20Poly1305).unwrap();
1271 let metadata = KeyMetadata {
1272 id: key_id.clone(),
1273 base_id: key_id.clone(),
1274 algorithm: Algorithm::ChaCha20Poly1305,
1275 created_at: SystemTime::now(),
1276 state: KeyState::Active,
1277 version: 1,
1278 expires_at: None,
1279 };
1280 let versioned_key = VersionedKey {
1281 key: secret_key,
1282 metadata: metadata.clone(),
1283 };
1284
1285 store.store(versioned_key).unwrap();
1287 let retrieved = store.retrieve(&key_id).unwrap();
1288 assert_eq!(retrieved.metadata.id, key_id);
1289 assert_eq!(retrieved.metadata.state, KeyState::Active);
1290
1291 let keys = store.list().unwrap();
1293 assert_eq!(keys.len(), 1);
1294 assert!(keys.contains(&key_id));
1295
1296 let active_keys = store.find_by_state(KeyState::Active).unwrap();
1298 assert_eq!(active_keys.len(), 1);
1299
1300 store.delete(&key_id).unwrap();
1302 let keys = store.list().unwrap();
1303 assert_eq!(keys.len(), 0);
1304 }
1305
1306 #[test]
1307 fn test_file_store_basic_operations() {
1308 use crate::{key::SecretKey, Algorithm};
1309 use std::time::SystemTime;
1310 use tempfile::tempdir;
1311
1312 let temp_dir = tempdir().unwrap();
1314 let config = StorageConfig::default();
1315 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1316
1317 let key_id = KeyId::from_bytes([2; 16]);
1319 let secret_key =
1320 SecretKey::from_bytes(vec![0x42; 32], Algorithm::ChaCha20Poly1305).unwrap();
1321 let metadata = KeyMetadata {
1322 id: key_id.clone(),
1323 base_id: key_id.clone(),
1324 algorithm: Algorithm::ChaCha20Poly1305,
1325 created_at: SystemTime::now(),
1326 expires_at: None,
1327 state: KeyState::Active,
1328 version: 1,
1329 };
1330 let versioned_key = VersionedKey {
1331 key: secret_key,
1332 metadata: metadata.clone(),
1333 };
1334
1335 store.store(versioned_key).unwrap();
1337 let retrieved = store.retrieve(&key_id).unwrap();
1338 assert_eq!(retrieved.metadata.id, key_id);
1339
1340 let store2 = FileStore::new(temp_dir.path(), StorageConfig::default()).unwrap();
1342 let retrieved2 = store2.retrieve(&key_id).unwrap();
1343 assert_eq!(retrieved2.metadata.id, key_id);
1344 }
1345
1346 #[test]
1347 fn test_file_store_encryption() {
1348 use crate::{key::SecretKey, Algorithm};
1349 use std::time::SystemTime;
1350 use tempfile::tempdir;
1351
1352 let temp_dir = tempdir().unwrap();
1354 let config = StorageConfig {
1355 encrypted: true,
1356 ..Default::default()
1357 };
1358 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1359
1360 store
1362 .init_with_password(b"super-secret-password-123")
1363 .unwrap();
1364
1365 let key_id = KeyId::from_bytes([3; 16]);
1367 let secret_key =
1368 SecretKey::from_bytes(vec![0xFF; 32], Algorithm::ChaCha20Poly1305).unwrap();
1369 let metadata = KeyMetadata {
1370 id: key_id.clone(),
1371 base_id: key_id.clone(),
1372 algorithm: Algorithm::ChaCha20Poly1305,
1373 created_at: SystemTime::now(),
1374 expires_at: None,
1375 state: KeyState::Active,
1376 version: 1,
1377 };
1378 let versioned_key = VersionedKey {
1379 key: secret_key,
1380 metadata,
1381 };
1382
1383 store.store(versioned_key.clone()).unwrap();
1385 let retrieved = store.retrieve(&key_id).unwrap();
1386
1387 assert_eq!(
1389 retrieved.key.expose_secret(),
1390 versioned_key.key.expose_secret()
1391 );
1392 assert_eq!(retrieved.metadata.id, key_id);
1393 assert_eq!(retrieved.metadata.algorithm, Algorithm::ChaCha20Poly1305);
1394
1395 let key_file = store.key_path(&key_id);
1397 let file_contents = std::fs::read_to_string(key_file).unwrap();
1398
1399 assert!(!file_contents.contains("FFFFFFFF")); assert!(!file_contents.contains("/////")); assert!(!file_contents.contains("255")); assert!(file_contents.contains("ChaCha20Poly1305")); assert!(file_contents.contains("encrypted_key")); let parsed: serde_json::Value = serde_json::from_str(&file_contents).unwrap();
1410 let encrypted_array = parsed["encrypted_key"].as_array().unwrap();
1411 assert!(encrypted_array.len() > 32); }
1413
1414 #[test]
1415 fn test_file_store_wrong_password_fails() {
1416 use crate::{key::SecretKey, Algorithm};
1417 use std::time::SystemTime;
1418 use tempfile::tempdir;
1419
1420 let temp_dir = tempdir().unwrap();
1421 let config = StorageConfig {
1422 encrypted: true,
1423 ..Default::default()
1424 };
1425
1426 let mut store1 = FileStore::new(temp_dir.path(), config.clone()).unwrap();
1428 store1.init_with_password(b"correct-password").unwrap();
1429
1430 let key_id = KeyId::from_bytes([4; 16]);
1431 let secret_key = SecretKey::from_bytes(vec![0xAB; 32], Algorithm::Aes256Gcm).unwrap();
1432 let metadata = KeyMetadata {
1433 id: key_id.clone(),
1434 base_id: key_id.clone(),
1435 algorithm: Algorithm::Aes256Gcm,
1436 created_at: SystemTime::now(),
1437 expires_at: None,
1438 state: KeyState::Active,
1439 version: 1,
1440 };
1441 let versioned_key = VersionedKey {
1442 key: secret_key,
1443 metadata,
1444 };
1445
1446 store1.store(versioned_key).unwrap();
1447
1448 let mut store2 = FileStore::new(temp_dir.path(), config).unwrap();
1450 store2.init_with_password(b"wrong-password").unwrap();
1451
1452 let result = store2.retrieve(&key_id);
1454 assert!(result.is_err());
1455
1456 match result.unwrap_err() {
1458 crate::Error::CryptoError { .. } => {} other => panic!("Expected crypto error, got: {:?}", other),
1460 }
1461 }
1462
1463 #[test]
1464 fn test_file_store_persistence_across_restarts() {
1465 use crate::{key::SecretKey, Algorithm};
1466 use std::time::SystemTime;
1467 use tempfile::tempdir;
1468
1469 let temp_dir = tempdir().unwrap();
1470 let config = StorageConfig {
1471 encrypted: true,
1472 ..Default::default()
1473 };
1474 let password = b"persistent-test-password";
1475
1476 let key_id = KeyId::from_bytes([5; 16]);
1477 let original_key_bytes = vec![0x12; 32]; {
1481 let mut store = FileStore::new(temp_dir.path(), config.clone()).unwrap();
1482 store.init_with_password(password).unwrap();
1483
1484 let secret_key =
1485 SecretKey::from_bytes(original_key_bytes.clone(), Algorithm::ChaCha20Poly1305)
1486 .unwrap();
1487 let metadata = KeyMetadata {
1488 id: key_id.clone(),
1489 base_id: key_id.clone(),
1490 algorithm: Algorithm::ChaCha20Poly1305,
1491 created_at: SystemTime::now(),
1492 expires_at: None,
1493 state: KeyState::Active,
1494 version: 1,
1495 };
1496 let versioned_key = VersionedKey {
1497 key: secret_key,
1498 metadata,
1499 };
1500
1501 store.store(versioned_key).unwrap();
1502 } {
1506 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1507 store.init_with_password(password).unwrap();
1508
1509 let retrieved = store.retrieve(&key_id).unwrap();
1510 assert_eq!(retrieved.key.expose_secret(), &original_key_bytes);
1511 assert_eq!(retrieved.metadata.algorithm, Algorithm::ChaCha20Poly1305);
1512 }
1513 }
1514
1515 #[test]
1516 fn test_custom_argon2_config() {
1517 use crate::{key::SecretKey, Algorithm};
1518 use std::time::SystemTime;
1519 use tempfile::tempdir;
1520
1521 let temp_dir = tempdir().unwrap();
1522
1523 let config = StorageConfig {
1525 encrypted: true,
1526 argon2_config: Argon2Config {
1527 memory_kib: 32768, time_cost: 4,
1529 parallelism: 2,
1530 },
1531 ..Default::default()
1532 };
1533
1534 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1535 store.init_with_password(b"test-password").unwrap();
1536
1537 let key_id = KeyId::from_bytes([10; 16]);
1538 let secret_key =
1539 SecretKey::from_bytes(vec![0x77; 32], Algorithm::ChaCha20Poly1305).unwrap();
1540 let metadata = KeyMetadata {
1541 id: key_id.clone(),
1542 base_id: key_id.clone(),
1543 algorithm: Algorithm::ChaCha20Poly1305,
1544 created_at: SystemTime::now(),
1545 expires_at: None,
1546 state: KeyState::Active,
1547 version: 1,
1548 };
1549 let versioned_key = VersionedKey {
1550 key: secret_key,
1551 metadata,
1552 };
1553
1554 store.store(versioned_key).unwrap();
1555 let retrieved = store.retrieve(&key_id).unwrap();
1556 assert_eq!(retrieved.metadata.id, key_id);
1557 }
1558
1559 #[test]
1560 fn test_memory_store_lifecycle() {
1561 use crate::{key::SecretKey, Algorithm};
1562 use std::time::SystemTime;
1563
1564 let mut store = MemoryStore::new();
1565
1566 let key_id = KeyId::from_bytes([20; 16]);
1568 let secret_key =
1569 SecretKey::from_bytes(vec![0x55; 32], Algorithm::ChaCha20Poly1305).unwrap();
1570 let metadata = KeyMetadata {
1571 id: key_id.clone(),
1572 base_id: key_id.clone(),
1573 algorithm: Algorithm::ChaCha20Poly1305,
1574 created_at: SystemTime::now(),
1575 state: KeyState::Active,
1576 version: 1,
1577 expires_at: None,
1578 };
1579 let versioned_key = VersionedKey {
1580 key: secret_key,
1581 metadata,
1582 };
1583 store.store(versioned_key).unwrap();
1584
1585 store.deprecate_key(&key_id).unwrap();
1587 let key = store.retrieve(&key_id).unwrap();
1588 assert_eq!(key.metadata.state, KeyState::Deprecated);
1589
1590 store.revoke_key(&key_id).unwrap();
1592 let key = store.retrieve(&key_id).unwrap();
1593 assert_eq!(key.metadata.state, KeyState::Revoked);
1594 }
1595
1596 #[test]
1597 fn test_memory_store_cleanup_old_versions() {
1598 use crate::{key::SecretKey, Algorithm};
1599 use std::time::SystemTime;
1600
1601 let mut store = MemoryStore::new();
1602
1603 let base_id = KeyId::generate_base().unwrap();
1605 let secret_key = SecretKey::generate(Algorithm::ChaCha20Poly1305).unwrap();
1606 let metadata = KeyMetadata {
1607 id: base_id.clone(),
1608 base_id: base_id.clone(),
1609 algorithm: Algorithm::ChaCha20Poly1305,
1610 created_at: SystemTime::now(),
1611 state: KeyState::Active,
1612 version: 1,
1613 expires_at: None,
1614 };
1615 let initial_key = VersionedKey {
1616 key: secret_key,
1617 metadata,
1618 };
1619 store.store(initial_key).unwrap();
1620
1621 for _ in 2..=5 {
1623 store.rotate_key(&base_id).unwrap();
1624 }
1625
1626 let versions = store.get_key_versions(&base_id).unwrap();
1628 for old_version in &versions[..3] {
1629 store.deprecate_key(&old_version.metadata.id).unwrap();
1630 }
1631
1632 let removed = store.cleanup_old_versions(&base_id, 2).unwrap();
1634 assert_eq!(removed.len(), 3);
1635
1636 let remaining = store.get_key_versions(&base_id).unwrap();
1638 assert!(remaining.len() <= 2);
1639 }
1640
1641 #[test]
1642 fn test_audit_logging() {
1643 use crate::{audit::MemoryAuditLogger, key::SecretKey, Algorithm};
1644 use std::time::SystemTime;
1645 use tempfile::tempdir;
1646
1647 let temp_dir = tempdir().unwrap();
1648 let config = StorageConfig::default();
1649 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1650
1651 let logger = Box::new(MemoryAuditLogger::new());
1653 store.set_audit_logger(logger);
1654
1655 let key_id = KeyId::from_bytes([99; 16]);
1657 let secret_key =
1658 SecretKey::from_bytes(vec![0x88; 32], Algorithm::ChaCha20Poly1305).unwrap();
1659 let metadata = KeyMetadata {
1660 id: key_id.clone(),
1661 base_id: key_id.clone(),
1662 algorithm: Algorithm::ChaCha20Poly1305,
1663 created_at: SystemTime::now(),
1664 expires_at: None,
1665 state: KeyState::Active,
1666 version: 1,
1667 };
1668 let versioned_key = VersionedKey {
1669 key: secret_key,
1670 metadata,
1671 };
1672
1673 store.store(versioned_key).unwrap();
1674
1675 }
1679
1680 #[test]
1681 fn test_high_security_config() {
1682 let config = StorageConfig::high_security();
1683 assert_eq!(config.argon2_config.memory_kib, 65536);
1684 assert_eq!(config.argon2_config.time_cost, 4);
1685 assert!(config.encrypted);
1686 }
1687
1688 #[test]
1689 fn test_safe_debug_implementations() {
1690 use crate::key::SecretKey;
1691 use crate::Algorithm;
1692 use tempfile::tempdir;
1693
1694 let metadata = VaultMetadata::new().unwrap();
1696 let debug_output = format!("{:?}", metadata);
1697 assert!(debug_output.contains("VaultMetadata"));
1698 assert!(debug_output.contains("REDACTED"));
1699 assert!(!debug_output.contains(&format!("{:?}", metadata.salt)));
1700
1701 let key_id = KeyId::from_bytes([42; 16]);
1703 let _secret_key =
1704 SecretKey::from_bytes(vec![0x11; 32], Algorithm::ChaCha20Poly1305).unwrap();
1705 let key_metadata = KeyMetadata {
1706 id: key_id.clone(),
1707 base_id: key_id.clone(),
1708 algorithm: Algorithm::ChaCha20Poly1305,
1709 created_at: SystemTime::now(),
1710 expires_at: None,
1711 state: KeyState::Active,
1712 version: 1,
1713 };
1714
1715 let persisted = PersistedKey {
1716 metadata: key_metadata,
1717 encrypted_key: vec![0xFF; 64],
1718 };
1719
1720 let debug_output = format!("{:?}", persisted);
1721 assert!(debug_output.contains("PersistedKey"));
1722 assert!(debug_output.contains("REDACTED"));
1723 assert!(debug_output.contains("64 bytes"));
1724
1725 let temp_dir = tempdir().unwrap();
1727 let config = StorageConfig {
1728 encrypted: true,
1729 ..Default::default()
1730 };
1731 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1732 store.init_with_password(b"test-password").unwrap();
1733
1734 let debug_output = format!("{:?}", store);
1735 assert!(debug_output.contains("FileStore"));
1736 assert!(debug_output.contains("[SET]")); assert!(!debug_output.contains("test-password"));
1738
1739 let secret = SecretKey::from_bytes(vec![0xAB; 32], Algorithm::Aes256Gcm).unwrap();
1741 let debug_output = format!("{:?}", secret);
1742 assert!(debug_output.contains("SecretKey"));
1743 assert!(debug_output.contains("REDACTED"));
1744 assert!(!debug_output.contains("0xAB"));
1745 }
1746
1747 #[test]
1748 fn test_file_store_export_import() {
1749 use crate::key::SecretKey;
1750 use crate::Algorithm;
1751 use tempfile::tempdir;
1752
1753 let temp_dir = tempdir().unwrap();
1755 let config = StorageConfig::default();
1756 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1757
1758 let key_id = KeyId::from_bytes([99; 16]);
1760 let secret_key =
1761 SecretKey::from_bytes(vec![0x42; 32], Algorithm::ChaCha20Poly1305).unwrap();
1762 let metadata = KeyMetadata {
1763 id: key_id.clone(),
1764 base_id: key_id.clone(),
1765 algorithm: Algorithm::ChaCha20Poly1305,
1766 created_at: SystemTime::now(),
1767 expires_at: None,
1768 state: KeyState::Active,
1769 version: 1,
1770 };
1771 let versioned_key = VersionedKey {
1772 key: secret_key,
1773 metadata,
1774 };
1775 store.store(versioned_key).unwrap();
1776
1777 let export_password = b"export-password-123";
1779 let exported = store.export_key(&key_id, export_password).unwrap();
1780
1781 assert_eq!(exported.metadata.algorithm, Algorithm::ChaCha20Poly1305);
1783 assert_eq!(exported.wrapping_algorithm, Algorithm::XChaCha20Poly1305);
1784
1785 let temp_dir2 = tempdir().unwrap();
1787 let config2 = StorageConfig::default();
1788 let mut store2 = FileStore::new(temp_dir2.path(), config2).unwrap();
1789
1790 let imported_id = store2.import_key(&exported, export_password).unwrap();
1792 assert_eq!(imported_id, key_id);
1793
1794 let retrieved = store2.retrieve(&imported_id).unwrap();
1796 assert_eq!(retrieved.metadata.algorithm, Algorithm::ChaCha20Poly1305);
1797 assert_eq!(retrieved.metadata.version, 1);
1798 assert_eq!(retrieved.key.expose_secret(), &vec![0x42; 32]);
1799 }
1800
1801 #[test]
1802 fn test_file_store_backup_restore() {
1803 use crate::backup::BackupConfig;
1804 use crate::key::SecretKey;
1805 use crate::Algorithm;
1806 use tempfile::tempdir;
1807
1808 let temp_dir = tempdir().unwrap();
1810 let config = StorageConfig::default();
1811 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1812
1813 let key_id1 = KeyId::from_bytes([1; 16]);
1815 let secret_key1 =
1816 SecretKey::from_bytes(vec![0x11; 32], Algorithm::ChaCha20Poly1305).unwrap();
1817 let metadata1 = KeyMetadata {
1818 id: key_id1.clone(),
1819 base_id: key_id1.clone(),
1820 algorithm: Algorithm::ChaCha20Poly1305,
1821 created_at: SystemTime::now(),
1822 expires_at: None,
1823 state: KeyState::Active,
1824 version: 1,
1825 };
1826 store
1827 .store(VersionedKey {
1828 key: secret_key1,
1829 metadata: metadata1,
1830 })
1831 .unwrap();
1832
1833 let key_id2 = KeyId::from_bytes([2; 16]);
1834 let secret_key2 =
1835 SecretKey::from_bytes(vec![0x22; 32], Algorithm::XChaCha20Poly1305).unwrap();
1836 let metadata2 = KeyMetadata {
1837 id: key_id2.clone(),
1838 base_id: key_id2.clone(),
1839 algorithm: Algorithm::XChaCha20Poly1305,
1840 created_at: SystemTime::now(),
1841 expires_at: None,
1842 state: KeyState::Active,
1843 version: 1,
1844 };
1845 store
1846 .store(VersionedKey {
1847 key: secret_key2,
1848 metadata: metadata2,
1849 })
1850 .unwrap();
1851
1852 let backup_password = b"backup-password-123";
1854 let backup_config = BackupConfig {
1855 include_audit_logs: false,
1856 compress: true,
1857 encryption_password: backup_password.to_vec(),
1858 comment: Some("Test backup".to_string()),
1859 };
1860
1861 let backup = store.backup(backup_password, backup_config).unwrap();
1862
1863 assert_eq!(backup.metadata.key_count, 2);
1865 assert!(backup.metadata.compressed);
1866 assert!(!backup.metadata.has_audit_logs);
1867
1868 let temp_dir2 = tempdir().unwrap();
1870 let config2 = StorageConfig::default();
1871 let mut store2 = FileStore::new(temp_dir2.path(), config2).unwrap();
1872
1873 let restored_count = store2.restore(&backup, backup_password).unwrap();
1874 assert_eq!(restored_count, 2);
1875
1876 let retrieved1 = store2.retrieve(&key_id1).unwrap();
1878 assert_eq!(retrieved1.key.expose_secret(), &vec![0x11; 32]);
1879 assert_eq!(retrieved1.metadata.algorithm, Algorithm::ChaCha20Poly1305);
1880
1881 let retrieved2 = store2.retrieve(&key_id2).unwrap();
1882 assert_eq!(retrieved2.key.expose_secret(), &vec![0x22; 32]);
1883 assert_eq!(retrieved2.metadata.algorithm, Algorithm::XChaCha20Poly1305);
1884 }
1885}