rust_keyvault/
storage.rs

1//! Storage backend traits and implementations
2
3use 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/// Salt storage for master key derivation
21#[derive(Clone, Serialize, Deserialize)]
22struct VaultMetadata {
23    /// Unique Salt for this vault instance
24    salt: Vec<u8>,
25    /// Vault creation timestamp
26    created_at: SystemTime,
27    /// Version of the vault format
28    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    /// Create new metadata with random salt
43    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///
57#[derive(Debug, Clone)]
58pub struct Argon2Config {
59    /// Memory cost in KiB (default: 19456 = ~19 MiB)
60    pub memory_kib: u32,
61    /// Time cost (iterations, default: 3)
62    pub time_cost: u32,
63    /// Parallelism (threads, default: 4)
64    pub parallelism: u32,
65}
66
67impl Default for Argon2Config {
68    fn default() -> Self {
69        Self {
70            memory_kib: 19456, // 19 MiB
71            time_cost: 3,
72            parallelism: 4,
73        }
74    }
75}
76
77/// Trait for key storage backends
78pub trait KeyStore: Send + Sync {
79    /// Store a versioned key
80    fn store(&mut self, key: VersionedKey) -> Result<()>;
81
82    /// Retrieve a key by ID
83    fn retrieve(&self, id: &KeyId) -> Result<VersionedKey>;
84
85    /// Delete a key
86    fn delete(&mut self, id: &KeyId) -> Result<()>;
87
88    /// List all kety IDs
89    fn list(&self) -> Result<Vec<KeyId>>;
90
91    /// Update key metadata
92    fn update_metadata(&mut self, id: &KeyId, metadata: KeyMetadata) -> Result<()>;
93
94    /// Find keys by state
95    fn find_by_state(&self, state: KeyState) -> Result<Vec<KeyId>>;
96
97    /// Rotate a key to a new version
98    fn rotate_key(&mut self, id: &KeyId) -> Result<VersionedKey>;
99
100    /// Get all verions of a key (sorted by version number)
101    fn get_key_versions(&self, id: &KeyId) -> Result<Vec<VersionedKey>>;
102
103    /// Get the latest active version of a key
104    fn get_latest_key(&self, id: &KeyId) -> Result<VersionedKey>;
105}
106
107/// Extended trait for advanced key lifecycle management
108///
109/// Provides methods for managing key states and cleanup policies
110/// This exxtends `KeyStore` with operations for key deprecation and revocation
111pub trait KeyLifeCycle: KeyStore {
112    /// Mark a particular key as deprecated (key should be able to decrypt but not encrypt)
113    fn deprecate_key(&mut self, id: &KeyId) -> Result<()>;
114
115    /// Revoke a key (key should not be used for any operations)
116    fn revoke_key(&mut self, id: &KeyId) -> Result<()>;
117
118    /// Clean up old versions based on policy
119    fn cleanup_old_versions(&mut self, id: &KeyId, keep_versions: usize) -> Result<Vec<KeyId>>;
120}
121
122/// Trait for persistent storage backends
123pub trait PersistentStorage: KeyStore {
124    /// Flush any pending writes to persistent storage
125    fn flush(&mut self) -> Result<()>;
126
127    /// Load keys from persistent storage
128    fn load(&mut self) -> Result<()>;
129
130    /// Get the storage location/path
131    fn location(&self) -> &str;
132}
133
134/// Serializable wrapper for persisted keys (excludes secret material)
135///
136/// Contains key metadata and the encrypted key material for disk storage
137/// The actual secret ket bytes are encrypted when `StorageConfig.encrypted` is true
138#[derive(Clone, Serialize, Deserialize)]
139struct PersistedKey {
140    /// Key metadata
141    metadata: KeyMetadata,
142    /// Encrypted key material (as bytes)
143    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
158/// In-memory key store
159///
160/// Thread-safe storage backend for testing and high performance scenerios
161/// where persistence is not required, please remember that all keys are lost when dropped
162pub 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    /// Create a new in-memory store
174    pub fn new() -> Self {
175        Self {
176            keys: Arc::new(RwLock::new(HashMap::new())),
177        }
178    }
179
180    /// Generate new key material for the given algorithm
181    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        // Store the new key
291        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        // Look for all te keys with the same base ID but different versions
305        for (_store_id, key) in keys.iter() {
306            if &key.metadata.base_id == id {
307                versions.push(key.clone());
308            }
309        }
310
311        // Sort the IDs by version number
312        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        // Find the latest active or rotating key
327        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/// Configuration for file-based storage
405///
406/// Controls encryption, compression and caching behaviour for persistent storage.
407#[derive(Debug, Clone)]
408pub struct StorageConfig {
409    /// Path to storage location (file, directory, etc.)
410    pub path: Option<String>,
411    /// Enable encryption at rest
412    pub encrypted: bool,
413    /// Enable compression
414    pub compressed: bool,
415    /// Maximum number of keys to cache in memory
416    pub cache_size: usize,
417    ///
418    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    /// Create a config with high security Argon2 parameters
435    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    ///
448    pub fn balanced() -> Self {
449        Self {
450            encrypted: true,
451            argon2_config: Argon2Config::default(), // 19 MiB, t=3, p=4
452            ..Default::default()
453        }
454    }
455
456    ///
457    pub fn fast_insecure() -> Self {
458        Self {
459            encrypted: true,
460            argon2_config: Argon2Config {
461                memory_kib: 8192, // 8 MiB - INSECURE, testing only!
462                time_cost: 1,
463                parallelism: 1,
464            },
465            ..Default::default()
466        }
467    }
468}
469
470/// File-based key store with optional encryption
471///
472/// Provides persistent storage of cryptographic keys with optional encryption at rest
473/// The key are cached in memory for performance and automatically loaded from disk
474pub struct FileStore {
475    /// Directory path for key storage
476    path: PathBuf,
477    /// In-memory cache of loaded keys
478    keys: HashMap<KeyId, VersionedKey>,
479    /// Configuration
480    config: StorageConfig,
481    /// Master key for encryption (optional, i.e. if enabled)
482    master_key: Option<crate::key::SecretKey>,
483    /// Metadata for the key vault for instance (e.g. salt)
484    vault_metadata: Option<VaultMetadata>,
485    /// Audit logger for security events
486    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    /// Create a new FileStore at the given path
511    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), // should default to no-op
525        };
526
527        // Load or create vault metadata FIRST
528        store.load_metadata()?; // ← Add this line
529
530        // Then load keys
531        store.load()?;
532
533        Ok(store)
534    }
535
536    ///
537    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        // Ensure we have a current master key
546        if self.master_key.is_none() {
547            return Err(crate::Error::storage(
548                "rekey",
549                "store is locked - cannot rekey",
550            ));
551        }
552
553        // Collect all keys that need re-encryption
554        let all_keys: Vec<_> = self.keys.values().cloned().collect();
555
556        // Get salt from vault metadata
557        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        // Derive new master key with the config
564        let new_master_key =
565            Self::derive_master_key(new_password, salt, &self.config.argon2_config)?;
566
567        // Set the new master key
568        self.master_key = Some(new_master_key);
569
570        // Re-encrypt all keys with the new master key
571        for key in all_keys {
572            self.store(key)?;
573        }
574
575        Ok(())
576    }
577
578    /// Set master key for encryption
579    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    /// Get the file path for a key ID
591    ///
592    /// Returns the filesystem path where the key's own encrypted data is stored
593    fn key_path(&self, id: &KeyId) -> PathBuf {
594        let filename = format!("{id:?}.json");
595        self.path.join(filename)
596    }
597
598    /// Serialize the key and optionally encrypt a key
599    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                // Use the nonce size appropriate for the master key's algorithm
606                let nonce_size = match master_key.algorithm() {
607                    crate::Algorithm::XChaCha20Poly1305 => 24,
608                    _ => 12, // ChaCha20Poly1305 and AES-256-GCM both use 12
609                };
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    /// Deserialize and optionally decrypt a key
646    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                // Determine nonce size based on master key algorithm
656                let nonce_size = match master_key.algorithm() {
657                    crate::Algorithm::XChaCha20Poly1305 => 24,
658                    _ => 12, // ChaCha20Poly1305 and AES-256-GCM both use 12
659                };
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    /// Initialize with password-derived master key (now using per-vault salt)
696    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        // Audit log authentication attempt
715        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    /// Derive a master key from a password using Argon2id
729    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), // output length: 32 bytes
739        )
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    /// Generate new key material for the given algorithm
757    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    /// Enable audit logging to a file
808    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    /// Set a custom audit logger
815    pub fn set_audit_logger(&mut self, logger: Box<dyn AuditLogger>) {
816        self.audit_logger = logger;
817    }
818
819    /// Export a key to a secure, portable format
820    ///
821    /// The key is encrypted with a password-derived key using Argon2id.
822    /// The exported key includes all metadata and can be imported into another vault.
823    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        // Retrieve the key
831        let versioned_key = self.retrieve(id)?;
832
833        // Use XChaCha20Poly1305 for export (supports larger nonces for safety)
834        let exported = ExportedKey::new(
835            &versioned_key.key,
836            versioned_key.metadata.clone(),
837            password,
838            crate::Algorithm::XChaCha20Poly1305,
839        )?;
840
841        // Audit log the export
842        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    /// Import a key from an exported format
852    ///
853    /// Validates the key, decrypts it with the provided password, and stores it in the vault.
854    /// The key will maintain its original metadata (algorithm, version, etc.).
855    pub fn import_key(
856        &mut self,
857        exported: &crate::export::ExportedKey,
858        password: &[u8],
859    ) -> Result<KeyId> {
860        // Decrypt the key
861        let key = exported.decrypt(password)?;
862
863        // Verify the algorithm matches metadata
864        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        // Create versioned key
872        let versioned_key = VersionedKey {
873            key,
874            metadata: exported.metadata.clone(),
875        };
876
877        let key_id = versioned_key.metadata.id.clone();
878
879        // Store the key
880        self.store(versioned_key)?;
881
882        // Audit log the import
883        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    /// Create a full backup of the vault
893    ///
894    /// # Arguments
895    /// * `password` - Password to encrypt the backup
896    /// * `config` - Backup configuration
897    ///
898    /// # Returns
899    /// The encrypted backup that can be saved to a file
900    ///
901    /// # Security
902    /// The backup is encrypted using Argon2id key derivation and XChaCha20Poly1305 AEAD.
903    /// All key material is protected with high-security parameters.
904    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        // Collect all keys in the vault
912        let mut exported_keys = Vec::new();
913
914        // Get all key IDs from cache
915        let key_ids: Vec<_> = self.keys.keys().cloned().collect();
916
917        // Export each key with a temporary password
918        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        // Collect audit logs if requested
926        let audit_logs = if config.include_audit_logs {
927            // Note: We would need to add a method to get all audit entries
928            // For now, we'll skip this
929            None
930        } else {
931            None
932        };
933
934        // Get vault creation time
935        let created_at = self
936            .vault_metadata
937            .as_ref()
938            .map(|m| m.created_at)
939            .unwrap_or_else(SystemTime::now);
940
941        // Create backup data
942        let backup_data = BackupData {
943            keys: exported_keys,
944            audit_logs,
945            vault_info: VaultInfo {
946                created_at,
947                operation_count: 0, // Could track this in metadata
948            },
949        };
950
951        // Create encrypted backup
952        crate::backup::VaultBackup::new(&backup_data, password, &config)
953    }
954
955    /// Restore a vault from a backup
956    ///
957    /// # Arguments
958    /// * `backup` - The encrypted backup to restore
959    /// * `password` - Password used to encrypt the backup
960    ///
961    /// # Errors
962    /// Returns an error if:
963    /// - Password is incorrect
964    /// - Backup is corrupted
965    /// - Keys cannot be imported
966    pub fn restore(
967        &mut self,
968        backup: &crate::backup::VaultBackup,
969        password: &[u8],
970    ) -> Result<usize> {
971        // Decrypt the backup
972        let backup_data = backup.decrypt(password)?;
973
974        // Import all keys
975        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        // Serialize to bytes
994        let data = self.serialize_key(&key)?;
995
996        // Write to file
997        fs::write(&key_path, data)?;
998
999        // Update in-memory cache
1000        self.keys.insert(key_id.clone(), key.clone());
1001
1002        // Audit log the operation
1003        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        // Check cache first
1015        if let Some(key) = self.keys.get(id) {
1016            return Ok(key.clone());
1017        }
1018
1019        // Load from disk
1020        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        // Remove from disk
1036        if key_path.exists() {
1037            fs::remove_file(&key_path)?;
1038        }
1039
1040        // Remove from cachestell
1041        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            // Re-persist to disk
1056            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        // Audit log rotation
1106        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        // Look for all keys with the same base ID but different versions
1120        for key in self.keys.values() {
1121            if &key.metadata.base_id == id {
1122                versions.push(key.clone());
1123            }
1124        }
1125
1126        // Sort by version number
1127        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        // Find the latest active or rotating key
1143        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        // Re-persist all keys to ensure consistency
1159        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        // Read all .json files in that directory (except vault metadata)
1170        for entry in fs::read_dir(&self.path)? {
1171            let entry = entry?;
1172            let path = entry.path();
1173
1174            // Skip the vault metadata file - it has a different format
1175            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        // Sort by version (newest first)
1231        versions.sort_by_key(|k| std::cmp::Reverse(k.metadata.version));
1232
1233        let mut removed_keys = Vec::new();
1234
1235        // Keep the specified number of versions, remove the rest
1236        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        // Create a test key
1269        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        // Test store and retrieve
1286        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        // Test list
1292        let keys = store.list().unwrap();
1293        assert_eq!(keys.len(), 1);
1294        assert!(keys.contains(&key_id));
1295
1296        // Test find by state
1297        let active_keys = store.find_by_state(KeyState::Active).unwrap();
1298        assert_eq!(active_keys.len(), 1);
1299
1300        // Test delete
1301        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        // Create temporary directory
1313        let temp_dir = tempdir().unwrap();
1314        let config = StorageConfig::default();
1315        let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1316
1317        // Create test key
1318        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        // Test store and retrieve
1336        store.store(versioned_key).unwrap();
1337        let retrieved = store.retrieve(&key_id).unwrap();
1338        assert_eq!(retrieved.metadata.id, key_id);
1339
1340        // Test persistence (create new store instance)
1341        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        // Create encrypted store
1353        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        // Initialize with password and verify unlock state
1361        store
1362            .init_with_password(b"super-secret-password-123")
1363            .unwrap();
1364
1365        // Create and store a key
1366        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 and retrieve - verify round-trip works
1384        store.store(versioned_key.clone()).unwrap();
1385        let retrieved = store.retrieve(&key_id).unwrap();
1386
1387        // Verify the key material and metadata match
1388        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        // Verify file is actually encrypted (contains no plaintext key material)
1396        let key_file = store.key_path(&key_id);
1397        let file_contents = std::fs::read_to_string(key_file).unwrap();
1398
1399        // The file should NOT contain the raw key bytes in any common format
1400        assert!(!file_contents.contains("FFFFFFFF")); // Hex representation
1401        assert!(!file_contents.contains("/////")); // Base64 for 0xFF repeated
1402        assert!(!file_contents.contains("255")); // JSON number representation
1403
1404        // But it should contain the expected structure
1405        assert!(file_contents.contains("ChaCha20Poly1305")); // Algorithm in metadata
1406        assert!(file_contents.contains("encrypted_key")); // Field name
1407
1408        // Verify the encrypted_key field contains binary data (not readable text)
1409        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); // Should be nonce(12) + ciphertext(32) + tag(16) = 60 bytes minimum
1412    }
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        // Create and populate store with correct password
1427        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        // Try to read with wrong password - should fail
1449        let mut store2 = FileStore::new(temp_dir.path(), config).unwrap();
1450        store2.init_with_password(b"wrong-password").unwrap();
1451
1452        // This should fail because decryption will fail with wrong master key
1453        let result = store2.retrieve(&key_id);
1454        assert!(result.is_err());
1455
1456        // The error should be a crypto error (AEAD decryption failure)
1457        match result.unwrap_err() {
1458            crate::Error::CryptoError { .. } => {} // Expected
1459            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]; // 32 bytes for ChaCha20Poly1305
1478
1479        // First session: create and store key
1480        {
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        } // store goes out of scope, simulating restart
1503
1504        // Second session: load and verify key
1505        {
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        // Use custom Argon2 config
1524        let config = StorageConfig {
1525            encrypted: true,
1526            argon2_config: Argon2Config {
1527                memory_kib: 32768, // 32 MiB
1528                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        // Create and store a key
1567        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        // Test deprecate
1586        store.deprecate_key(&key_id).unwrap();
1587        let key = store.retrieve(&key_id).unwrap();
1588        assert_eq!(key.metadata.state, KeyState::Deprecated);
1589
1590        // Test revoke
1591        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        // Create base key with multiple versions
1604        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        // Create 5 versions total
1622        for _ in 2..=5 {
1623            store.rotate_key(&base_id).unwrap();
1624        }
1625
1626        // Deprecate old versions
1627        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        // Cleanup, keep only 2 most recent
1633        let removed = store.cleanup_old_versions(&base_id, 2).unwrap();
1634        assert_eq!(removed.len(), 3);
1635
1636        // Verify remaining
1637        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        // Set up memory logger for testing
1652        let logger = Box::new(MemoryAuditLogger::new());
1653        store.set_audit_logger(logger);
1654
1655        // Create and store a key
1656        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        // Verify audit log captured the event
1676        // Note: To actually verify, we'd need to extract the logger back out
1677        // For now, this tests that auditing doesn't break functionality
1678    }
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        // Test VaultMetadata redacts salt
1695        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        // Test PersistedKey redacts encrypted_key
1702        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        // Test FileStore redacts sensitive data
1726        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]")); // master_key is set
1737        assert!(!debug_output.contains("test-password"));
1738
1739        // Test SecretKey redacts bytes
1740        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        // Create store
1754        let temp_dir = tempdir().unwrap();
1755        let config = StorageConfig::default();
1756        let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1757
1758        // Create and store a key
1759        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        // Export the key
1778        let export_password = b"export-password-123";
1779        let exported = store.export_key(&key_id, export_password).unwrap();
1780
1781        // Verify export structure
1782        assert_eq!(exported.metadata.algorithm, Algorithm::ChaCha20Poly1305);
1783        assert_eq!(exported.wrapping_algorithm, Algorithm::XChaCha20Poly1305);
1784
1785        // Create a second store
1786        let temp_dir2 = tempdir().unwrap();
1787        let config2 = StorageConfig::default();
1788        let mut store2 = FileStore::new(temp_dir2.path(), config2).unwrap();
1789
1790        // Import into second store
1791        let imported_id = store2.import_key(&exported, export_password).unwrap();
1792        assert_eq!(imported_id, key_id);
1793
1794        // Verify the imported key
1795        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        // Create original vault with multiple keys
1809        let temp_dir = tempdir().unwrap();
1810        let config = StorageConfig::default();
1811        let mut store = FileStore::new(temp_dir.path(), config).unwrap();
1812
1813        // Add multiple keys
1814        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        // Create backup
1853        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        // Verify backup metadata
1864        assert_eq!(backup.metadata.key_count, 2);
1865        assert!(backup.metadata.compressed);
1866        assert!(!backup.metadata.has_audit_logs);
1867
1868        // Create new vault and restore
1869        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        // Verify restored keys
1877        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}