1use crate::{crypto::{RuntimeAead, AEAD, RandomNonceGenerator, NonceGenerator}, key::VersionedKey, KeyId, KeyMetadata, KeyState, Result};
4use std::collections::HashMap;
5use std::sync::{Arc, RwLock};
6use std::path::{Path, PathBuf};
7use std::fs;
8use serde::{Deserialize, Serialize};
9use rand_chacha::ChaCha20Rng;
10use rand_core::SeedableRng;
11use std::time::SystemTime;
12use argon2::Argon2;
13use argon2::{Algorithm as Argon2Algorithm, Version, Params};
14
15const KEYVAULT_SALT: &[u8] = b"rust-keyvault-argon2-salt-v1-fixed-32b";
16
17pub trait KeyStore: Send + Sync {
19 fn store(&mut self, key: VersionedKey) -> Result<()>;
21
22 fn retrieve(&self, id: &KeyId) -> Result<VersionedKey>;
24
25 fn delete(&mut self, id: &KeyId) -> Result<()>;
27
28 fn list(&self) -> Result<Vec<KeyId>>;
30
31 fn update_metadata(&mut self, id: &KeyId, metadata: KeyMetadata) -> Result<()>;
33
34 fn find_by_state(&self, state: KeyState) -> Result<Vec<KeyId>>;
36
37 fn rotate_key(&mut self, id: &KeyId) -> Result<VersionedKey>;
39
40 fn get_key_versions(&self, id: &KeyId) -> Result<Vec<VersionedKey>>;
42
43 fn get_latest_key(&self, id: &KeyId) -> Result<VersionedKey>;
45}
46
47pub trait KeyLifeCycle: KeyStore {
52 fn deprecate_key(&mut self, id: &KeyId) -> Result<()>;
54
55 fn revoke_key(&mut self, id: &KeyId) -> Result<()>;
57
58 fn cleanup_old_versions(&mut self, id: &KeyId, keep_versions: usize) -> Result<Vec<KeyId>>;
60}
61
62pub trait PersistentStorage: KeyStore {
64 fn flush(&mut self) -> Result<()>;
66
67 fn load(&mut self) -> Result<()>;
69
70 fn location(&self) -> &str;
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
79struct PersistedKey {
80 metadata: KeyMetadata,
82 encrypted_key: Vec<u8>,
84}
85
86pub trait EncryptedStore: KeyStore {
91 fn init_with_password(&mut self, password: &[u8]) -> Result<()>;
93
94 fn rekey(&mut self, new_password: &[u8]) -> Result<()>;
96
97 fn is_unlocked(&self) -> bool;
99}
100
101pub struct MemoryStore {
106 keys: Arc<RwLock<HashMap<KeyId, VersionedKey>>>,
107}
108
109impl Default for MemoryStore {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115impl MemoryStore {
116 pub fn new() -> Self {
118 Self {
119 keys: Arc::new(RwLock::new(HashMap::new())),
120 }
121 }
122
123 fn generate_new_key_material(algorithm: crate::Algorithm) -> Result<crate::key::SecretKey> {
125 use crate::crypto::{SimpleSymmetricKeyGenerator, KeyGenerator};
126 use rand_chacha::ChaCha20Rng;
127 use rand_core::SeedableRng;
128
129 let mut rng = ChaCha20Rng::from_entropy();
130 let generator = SimpleSymmetricKeyGenerator;
131 let params = crate::crypto::KeyGenParams {
132 algorithm,
133 seed: None,
134 key_size: None,
135 };
136
137 generator.generate_with_params(&mut rng, params)
138 }
139}
140
141impl KeyStore for MemoryStore {
142 fn store(&mut self, key: VersionedKey) -> Result<()> {
143 let key_id = key.metadata.id.clone();
144 let mut keys = self.keys.write().map_err(|_| crate::Error::storage("lock poisoned"))?;
145 keys.insert(key_id, key);
146 Ok(())
147 }
148
149 fn retrieve(&self, id: &KeyId) -> Result<VersionedKey> {
150 let keys = self.keys.read().map_err(|_| crate::Error::storage("lock poisoned"))?;
151 keys.get(id)
152 .cloned()
153 .ok_or_else(|| crate::Error::storage(format!("key not found: {id:?}")))
154 }
155
156 fn delete(&mut self, id: &KeyId) -> Result<()> {
157 let mut keys = self.keys.write().map_err(|_| crate::Error::storage("lock poisoned"))?;
158 keys.remove(id)
159 .ok_or_else(|| crate::Error::storage(format!("key not found: {id:?}")))?;
160 Ok(())
161 }
162
163 fn list(&self) -> Result<Vec<KeyId>> {
164 let keys = self.keys.read().map_err(|_| crate::Error::storage("lock poisoned"))?;
165 Ok(keys.keys().cloned().collect())
166 }
167
168 fn update_metadata(&mut self, id: &KeyId, metadata: KeyMetadata) -> Result<()> {
169 let mut keys = self.keys.write().map_err(|_| crate::Error::storage("lock poisoned"))?;
170 if let Some(versioned_key) = keys.get_mut(id) {
171 versioned_key.metadata = metadata;
172 Ok(())
173 } else {
174 Err(crate::Error::storage(format!("key not found: {id:?}")))
175 }
176 }
177
178 fn find_by_state(&self, state: KeyState) -> Result<Vec<KeyId>> {
179 let keys = self.keys.read().map_err(|_| crate::Error::storage("lock poisoned"))?;
180 Ok(keys
181 .iter()
182 .filter(|(_, versioned_key)| versioned_key.metadata.state == state)
183 .map(|(id, _)| id.clone())
184 .collect())
185 }
186
187 fn rotate_key(&mut self, id: &KeyId) -> Result<VersionedKey> {
188 let current_key = self.get_latest_key(id)?;
189 let mut depracated_metadata = current_key.metadata.clone();
190 depracated_metadata.state = KeyState::Deprecated;
191 self.update_metadata(id, depracated_metadata)?;
192
193 let new_version = current_key.metadata.version + 1;
194 let new_key_id = KeyId::generate_versioned(id, new_version)?;
195
196 let new_secret_key = Self::generate_new_key_material(current_key.metadata.algorithm)?;
197 let new_metadata = KeyMetadata {
198 id: new_key_id.clone(),
199 base_id: current_key.metadata.base_id.clone(),
200 algorithm: current_key.metadata.algorithm,
201 created_at: SystemTime::now(),
202 expires_at: current_key.metadata.expires_at,
203 state: KeyState::Active,
204 version: new_version,
205 };
206
207 let new_versioned_key = VersionedKey {
208 key: new_secret_key,
209 metadata: new_metadata,
210 };
211
212 self.store(new_versioned_key.clone())?;
214
215 Ok(new_versioned_key)
216 }
217
218 fn get_key_versions(&self, id: &KeyId) -> Result<Vec<VersionedKey>> {
219 let keys = self.keys.read().map_err(|_| crate::Error::storage("lock poisoned"))?;
220
221 let mut versions = Vec::new();
222
223 for (_store_id, key) in keys.iter() {
225 if &key.metadata.base_id == id {
226 versions.push(key.clone());
227 }
228 }
229
230 versions.sort_by_key(|k| k.metadata.version);
232
233 if versions.is_empty() {
234 return Err(crate::Error::storage(format!("no versions found for this key: {id:?}")))
235 }
236 Ok(versions)
237 }
238
239 fn get_latest_key(&self, id: &KeyId) -> Result<VersionedKey> {
240 let versions = self.get_key_versions(id)?;
241
242 versions
244 .into_iter()
245 .filter(|k| matches!(k.metadata.state, KeyState::Active | KeyState::Rotating))
246 .max_by_key(|k| k.metadata.version)
247 .ok_or_else(|| crate::Error::storage(format!("no active key found for: {id:?}")))
248 }
249}
250
251#[derive(Debug, Clone)]
255pub struct StorageConfig {
256 pub path: Option<String>,
258 pub encrypted: bool,
260 pub compressed: bool,
262 pub cache_size: usize,
264}
265
266impl Default for StorageConfig {
267 fn default() -> Self {
268 Self {
269 path: None,
270 encrypted: false,
271 compressed: false,
272 cache_size: 100,
273 }
274 }
275}
276
277pub struct FileStore {
282 path: PathBuf,
284 keys: HashMap<KeyId, VersionedKey>,
286 config: StorageConfig,
288 master_key: Option<crate::key::SecretKey>,
290}
291
292impl FileStore {
293 pub fn new<P: AsRef<Path>>(path: P, config: StorageConfig) -> Result<Self> {
295 let path = path.as_ref().to_path_buf();
296
297 if !path.exists() {
299 fs::create_dir_all(&path)?;
300 }
301
302 let mut store = Self {
303 path,
304 keys: HashMap::new(),
305 config,
306 master_key: None,
307 };
308
309 store.load()?;
311
312 Ok(store)
313 }
314
315 pub fn set_master_key(&mut self, key: crate::key::SecretKey) -> Result<()> {
317 if !self.config.encrypted {
318 return Err(crate::Error::storage("encryption not enabled in config"));
319 }
320 self.master_key = Some(key);
321 Ok(())
322 }
323
324 fn key_path(&self, id: &KeyId) -> PathBuf {
328 let filename = format!("{id:?}.json");
329 self.path.join(filename)
330 }
331
332 fn serialize_key(&self, key: &VersionedKey) -> Result<Vec<u8>> {
334 let key_bytes = key.key.expose_secret().to_vec();
335
336 let encrypted_key = if self.config.encrypted {
337 if let Some(master_key) = &self.master_key {
338 let aead = RuntimeAead;
339 let mut nonce_gen = RandomNonceGenerator::new(
340 ChaCha20Rng::from_entropy(),
341 RuntimeAead::NONCE_SIZE
342 );
343
344 let key_id_bytes = format!("{:?}", key.metadata.id);
345 let nonce = nonce_gen.generate_nonce(key_id_bytes.as_bytes())?;
346 let encrypted_bytes = aead.encrypt(
347 master_key,
348 &nonce,
349 &key_bytes,
350 b"rust-keyvault-key-encryption",
351 )?;
352
353 let mut result = nonce;
354 result.extend_from_slice(&encrypted_bytes);
355 result
356 } else {
357 return Err(crate::Error::storage("encryption enabled but no master key set"));
358 }
359 } else {
360 key_bytes
361 };
362
363 let persisted = PersistedKey {
364 metadata: key.metadata.clone(),
365 encrypted_key,
366 };
367
368 serde_json::to_vec(&persisted)
369 .map_err(|e| crate::Error::storage(format!("serilization failed: {e}")))
370 }
371
372 fn deserialize_key(&self, data: &[u8]) -> Result<VersionedKey> {
374 let persisted: PersistedKey = serde_json::from_slice(data)
375 .map_err(|e| crate::Error::storage(format!("deserialization failed: {e}")))?;
376
377 let key_bytes = if self.config.encrypted {
378 if let Some(master_key) = &self.master_key {
379 let aead = RuntimeAead;
380
381 if persisted.encrypted_key.len() < RuntimeAead::NONCE_SIZE {
382 return Err(crate::Error::storage("encrypted key too short - corrupted data"));
383 }
384
385 let (nonce, ciphertext) = persisted.encrypted_key.split_at(RuntimeAead::NONCE_SIZE);
386
387 aead.decrypt(
388 master_key,
389 nonce,
390 ciphertext,
391 b"rust-keyvault-key-encryption"
392 )?
393 } else {
394 return Err(crate::Error::storage("encrypted key but no master key available"));
395 }
396 } else {
397 persisted.encrypted_key
398 };
399
400 let secret_key = crate::key::SecretKey::from_bytes(key_bytes, persisted.metadata.algorithm)?;
401
402 Ok(VersionedKey {
403 key: secret_key,
404 metadata: persisted.metadata,
405 })
406 }
407
408 pub fn init_with_password(&mut self, password: &[u8]) -> Result<()> {
410 if !self.config.encrypted {
411 return Err(crate::Error::storage("encryption not enabled in config"));
412 }
413
414 let salt = KEYVAULT_SALT;
415 let master_key = Self::derive_master_key(password, salt)?;
416 self.set_master_key(master_key)?;
417
418 Ok(())
419 }
420
421 pub fn derive_master_key(password: &[u8], salt: &[u8]) -> Result<crate::key::SecretKey> {
423 let params = Params::new(
424 19456,
425 2,
426 1,
427 Some(32),
428 ).map_err(|e| crate::Error::crypto(format!("invalid Argon2 params: {e}")))?;
429
430 let argon2 = Argon2::new(Argon2Algorithm::Argon2id, Version::V0x13, params);
431
432 let mut key_bytes = [0u8; 32];
433 argon2.hash_password_into(password, salt, &mut key_bytes)
434 .map_err(|e| crate::Error::crypto(format!("Argon2 derivation failed: {e}")))?;
435
436 crate::key::SecretKey::from_bytes(key_bytes.to_vec(), crate::Algorithm::ChaCha20Poly1305)
437 }
438
439 fn generate_new_key_material(&self, algorithm: crate::Algorithm) -> Result<crate::key::SecretKey> {
441 use crate::crypto::{SimpleSymmetricKeyGenerator, KeyGenerator};
442 use rand_chacha::ChaCha20Rng;
443 use rand_core::SeedableRng;
444
445 let mut rng = ChaCha20Rng::from_entropy();
446 let generator = SimpleSymmetricKeyGenerator;
447 let params = crate::crypto::KeyGenParams {
448 algorithm,
449 seed: None,
450 key_size: None,
451 };
452
453 generator.generate_with_params(&mut rng, params)
454 }
455}
456
457impl EncryptedStore for FileStore {
458 fn init_with_password(&mut self, password: &[u8]) -> Result<()> {
460 if !self.config.encrypted {
461 return Err(crate::Error::storage("encryption not enabled in config"));
462 }
463
464 let salt = KEYVAULT_SALT;
465 let master_key = Self::derive_master_key(password, salt)?;
466 self.set_master_key(master_key)?;
467
468 Ok(())
469 }
470
471 fn rekey(&mut self, new_password: &[u8]) -> Result<()> {
472 if !self.config.encrypted {
473 return Err(crate::Error::storage("encryption not enabled in config"));
474 }
475
476 if self.master_key.is_none() {
478 return Err(crate::Error::storage("store is locked - cannot rekey"));
479 }
480
481 let all_keys: Vec<_> = self.keys.values().cloned().collect();
483
484 let salt = KEYVAULT_SALT;
486 let new_master_key = Self::derive_master_key(new_password, salt)?;
487
488 self.master_key = Some(new_master_key);
490
491 for key in all_keys {
493 self.store(key)?;
494 }
495
496 Ok(())
497 }
498
499 fn is_unlocked(&self) -> bool {
503 self.master_key.is_some()
504 }
505}
506
507impl KeyStore for FileStore {
508 fn store(&mut self, key: VersionedKey) -> Result<()> {
509 let key_id = key.metadata.id.clone();
510 let key_path = self.key_path(&key_id);
511
512 let data = self.serialize_key(&key)?;
514
515 fs::write(&key_path, data)?;
517
518 self.keys.insert(key_id, key);
520
521 Ok(())
522 }
523
524 fn retrieve(&self, id: &KeyId) -> Result<VersionedKey> {
525 if let Some(key) = self.keys.get(id) {
527 return Ok(key.clone());
528 }
529
530 let key_path = self.key_path(id);
532 if !key_path.exists() {
533 return Err(crate::Error::storage(format!("key file not found: {id:?}")));
534 }
535
536 let data = fs::read(&key_path)?;
537 self.deserialize_key(&data)
538 }
539
540 fn delete(&mut self, id: &KeyId) -> Result<()> {
541 let key_path = self.key_path(id);
542
543 if key_path.exists() {
545 fs::remove_file(&key_path)?;
546 }
547
548 self.keys.remove(id)
550 .ok_or_else(|| crate::Error::storage(format!("key not found: {id:?}")))?;
551
552 Ok(())
553 }
554
555 fn list(&self) -> Result<Vec<KeyId>> {
556 Ok(self.keys.keys().cloned().collect())
557 }
558
559 fn update_metadata(&mut self, id: &KeyId, metadata: KeyMetadata) -> Result<()> {
560 if let Some(versioned_key) = self.keys.get_mut(id) {
561 versioned_key.metadata = metadata;
562 let key_copy = versioned_key.clone();
564 self.store(key_copy)?;
565 Ok(())
566 } else {
567 Err(crate::Error::storage(format!("key not found: {id:?}")))
568 }
569 }
570
571 fn find_by_state(&self, state: KeyState) -> Result<Vec<KeyId>> {
572 Ok(self.keys
573 .iter()
574 .filter(|(_, key)| key.metadata.state == state)
575 .map(|(id, _)| id.clone())
576 .collect())
577 }
578
579 fn rotate_key(&mut self, id: &KeyId) -> Result<VersionedKey> {
580 let current_key = self.get_latest_key(id)?;
581
582 let mut deprecated_metadata = current_key.metadata.clone();
583 deprecated_metadata.state = KeyState::Deprecated;
584 self.update_metadata(id, deprecated_metadata)?;
585
586 let new_version = current_key.metadata.version + 1;
587 let new_key_id = KeyId::generate_versioned(id, new_version)?;
588
589 let new_secret_key = self.generate_new_key_material(current_key.key.algorithm())?;
590 let new_metadata = KeyMetadata {
591 id: new_key_id.clone(),
592 algorithm: current_key.metadata.algorithm,
593 created_at: SystemTime::now(),
594 expires_at: current_key.metadata.expires_at,
595 state: KeyState::Active,
596 version: new_version,
597 base_id: current_key.metadata.base_id.clone(),
598 };
599
600 let new_versioned_key = VersionedKey {
601 key: new_secret_key,
602 metadata: new_metadata,
603 };
604
605 self.store(new_versioned_key.clone())?;
607
608 Ok(new_versioned_key)
609 }
610
611 fn get_key_versions(&self, id: &KeyId) -> Result<Vec<VersionedKey>> {
612 let mut versions = Vec::new();
613
614 for key in self.keys.values() {
616 if &key.metadata.base_id == id {
617 versions.push(key.clone());
618 }
619 }
620
621 versions.sort_by_key(|k| k.metadata.version);
623
624 if versions.is_empty() {
625 return Err(crate::Error::storage(format!("no versions found for key: {id:?}")));
626 }
627
628 Ok(versions)
629 }
630
631 fn get_latest_key(&self, id: &KeyId) -> Result<VersionedKey> {
632 let versions = self.get_key_versions(id)?;
633
634 versions
636 .into_iter()
637 .filter(|k| matches!(k.metadata.state, KeyState::Active | KeyState::Rotating))
638 .max_by_key(|k| k.metadata.version)
639 .ok_or_else(|| crate::Error::storage(format!("no active key found for: {id:?}")))
640 }
641}
642
643impl PersistentStorage for FileStore {
644 fn flush(&mut self) -> Result<()> {
645 let keys: Vec<_> = self.keys.values().cloned().collect();
647 for key in keys {
648 self.store(key)?;
649 }
650 Ok(())
651 }
652
653 fn load(&mut self) -> Result<()> {
654 self.keys.clear();
655
656 for entry in fs::read_dir(&self.path)? {
658 let entry = entry?;
659 let path = entry.path();
660
661 if path.extension().and_then(|s| s.to_str()) == Some("json") {
662 let data = fs::read(&path)?;
663 match self.deserialize_key(&data) {
664 Ok(key) => {
665 self.keys.insert(key.metadata.id.clone(), key);
666 }
667 Err(e) => {
668 eprintln!("WARNING: Failed to load key from {path:?}: {e}");
669 }
670 }
671 }
672 }
673
674 Ok(())
675 }
676
677 fn location(&self) -> &str {
678 self.path.to_str().unwrap_or("<invalid_path>")
679 }
680}
681
682impl KeyLifeCycle for FileStore {
683 fn deprecate_key(&mut self, id: &KeyId) -> Result<()> {
684 let key = self.retrieve(id)?;
685
686 if !matches!(key.metadata.state, KeyState::Active | KeyState::Rotating) {
687 return Err(crate::Error::InvalidKeyState(
688 format!("cannot deprecate key in state: {:?}", key.metadata.state)
689 ));
690 }
691
692 let mut new_metadata = key.metadata.clone();
693 new_metadata.state = KeyState::Deprecated;
694
695 self.update_metadata(id, new_metadata)
696 }
697
698 fn revoke_key(&mut self, id: &KeyId) -> Result<()> {
699 let key = self.retrieve(id)?;
700
701 let mut new_metadata = key.metadata.clone();
702 new_metadata.state = KeyState::Revoked;
703
704 self.update_metadata(id, new_metadata)
705 }
706
707 fn cleanup_old_versions(&mut self, id: &KeyId, keep_versions: usize) -> Result<Vec<KeyId>> {
708 let mut versions = self.get_key_versions(id)?;
709
710 versions.sort_by_key(|k| std::cmp::Reverse(k.metadata.version));
712
713 let mut removed_keys = Vec::new();
714
715 for key_to_remove in versions.iter().skip(keep_versions) {
717 if matches!(key_to_remove.metadata.state, KeyState::Revoked | KeyState::Deprecated) {
718 self.delete(&key_to_remove.metadata.id)?;
719 removed_keys.push(key_to_remove.metadata.id.clone());
720 }
721 }
722
723 Ok(removed_keys)
724 }
725}
726
727#[cfg(test)]
728mod tests {
729 use super::*;
730
731 #[test]
732 fn test_storage_config_default() {
733 let config = StorageConfig::default();
734 assert!(!config.encrypted);
735 assert!(!config.compressed);
736 }
737
738 #[test]
739 fn test_memory_store_basic_operations() {
740 use crate::{Algorithm, key::SecretKey};
741 use std::time::SystemTime;
742
743 let mut store = MemoryStore::new();
744
745 let key_id = KeyId::from_bytes([1; 16]);
747 let secret_key = SecretKey::from_bytes(vec![0u8; 32], Algorithm::ChaCha20Poly1305).unwrap();
748 let metadata = KeyMetadata {
749 id: key_id.clone(),
750 base_id: key_id.clone(),
751 algorithm: Algorithm::ChaCha20Poly1305,
752 created_at: SystemTime::now(),
753 state: KeyState::Active,
754 version: 1,
755 expires_at: None,
756 };
757 let versioned_key = VersionedKey {
758 key: secret_key,
759 metadata: metadata.clone(),
760 };
761
762 store.store(versioned_key).unwrap();
764 let retrieved = store.retrieve(&key_id).unwrap();
765 assert_eq!(retrieved.metadata.id, key_id);
766 assert_eq!(retrieved.metadata.state, KeyState::Active);
767
768 let keys = store.list().unwrap();
770 assert_eq!(keys.len(), 1);
771 assert!(keys.contains(&key_id));
772
773 let active_keys = store.find_by_state(KeyState::Active).unwrap();
775 assert_eq!(active_keys.len(), 1);
776
777 store.delete(&key_id).unwrap();
779 let keys = store.list().unwrap();
780 assert_eq!(keys.len(), 0);
781 }
782
783 #[test]
784 fn test_file_store_basic_operations() {
785 use tempfile::tempdir;
786 use crate::{Algorithm, key::SecretKey};
787 use std::time::SystemTime;
788
789 let temp_dir = tempdir().unwrap();
791 let config = StorageConfig::default();
792 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
793
794 let key_id = KeyId::from_bytes([2; 16]);
796 let secret_key = SecretKey::from_bytes(vec![0x42; 32], Algorithm::ChaCha20Poly1305).unwrap();
797 let metadata = KeyMetadata {
798 id: key_id.clone(),
799 base_id: key_id.clone(),
800 algorithm: Algorithm::ChaCha20Poly1305,
801 created_at: SystemTime::now(),
802 expires_at: None,
803 state: KeyState::Active,
804 version: 1,
805 };
806 let versioned_key = VersionedKey {
807 key: secret_key,
808 metadata: metadata.clone(),
809 };
810
811 store.store(versioned_key).unwrap();
813 let retrieved = store.retrieve(&key_id).unwrap();
814 assert_eq!(retrieved.metadata.id, key_id);
815
816 let store2 = FileStore::new(temp_dir.path(), StorageConfig::default()).unwrap();
818 let retrieved2 = store2.retrieve(&key_id).unwrap();
819 assert_eq!(retrieved2.metadata.id, key_id);
820 }
821
822 #[test]
823 fn test_file_store_encryption() {
824 use tempfile::tempdir;
825 use crate::{Algorithm, key::SecretKey};
826 use std::time::SystemTime;
827
828 let temp_dir = tempdir().unwrap();
830 let config = StorageConfig {
831 encrypted: true,
832 ..Default::default()
833 };
834 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
835
836 store.init_with_password(b"super-secret-password-123").unwrap();
838 assert!(store.is_unlocked());
839
840 let key_id = KeyId::from_bytes([3; 16]);
842 let secret_key = SecretKey::from_bytes(vec![0xFF; 32], Algorithm::ChaCha20Poly1305).unwrap();
843 let metadata = KeyMetadata {
844 id: key_id.clone(),
845 base_id: key_id.clone(),
846 algorithm: Algorithm::ChaCha20Poly1305,
847 created_at: SystemTime::now(),
848 expires_at: None,
849 state: KeyState::Active,
850 version: 1,
851 };
852 let versioned_key = VersionedKey { key: secret_key, metadata };
853
854 store.store(versioned_key.clone()).unwrap();
856 let retrieved = store.retrieve(&key_id).unwrap();
857
858 assert_eq!(retrieved.key.expose_secret(), versioned_key.key.expose_secret());
860 assert_eq!(retrieved.metadata.id, key_id);
861 assert_eq!(retrieved.metadata.algorithm, Algorithm::ChaCha20Poly1305);
862
863 let key_file = store.key_path(&key_id);
865 let file_contents = std::fs::read_to_string(key_file).unwrap();
866
867 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();
878 let encrypted_array = parsed["encrypted_key"].as_array().unwrap();
879 assert!(encrypted_array.len() > 32); }
881
882 #[test]
883 fn test_file_store_wrong_password_fails() {
884 use tempfile::tempdir;
885 use crate::{Algorithm, key::SecretKey};
886 use std::time::SystemTime;
887
888 let temp_dir = tempdir().unwrap();
889 let config = StorageConfig { encrypted: true, ..Default::default() };
890
891 let mut store1 = FileStore::new(temp_dir.path(), config.clone()).unwrap();
893 store1.init_with_password(b"correct-password").unwrap();
894
895 let key_id = KeyId::from_bytes([4; 16]);
896 let secret_key = SecretKey::from_bytes(vec![0xAB; 32], Algorithm::Aes256Gcm).unwrap();
897 let metadata = KeyMetadata {
898 id: key_id.clone(),
899 base_id: key_id.clone(),
900 algorithm: Algorithm::Aes256Gcm,
901 created_at: SystemTime::now(),
902 expires_at: None,
903 state: KeyState::Active,
904 version: 1,
905 };
906 let versioned_key = VersionedKey { key: secret_key, metadata };
907
908 store1.store(versioned_key).unwrap();
909
910 let mut store2 = FileStore::new(temp_dir.path(), config).unwrap();
912 store2.init_with_password(b"wrong-password").unwrap();
913
914 let result = store2.retrieve(&key_id);
916 assert!(result.is_err());
917
918 match result.unwrap_err() {
920 crate::Error::CryptoError(_) => {}, other => panic!("Expected crypto error, got: {:?}", other),
922 }
923 }
924
925 #[test]
926 fn test_file_store_persistence_across_restarts() {
927 use tempfile::tempdir;
928 use crate::{Algorithm, key::SecretKey};
929 use std::time::SystemTime;
930
931 let temp_dir = tempdir().unwrap();
932 let config = StorageConfig { encrypted: true, ..Default::default() };
933 let password = b"persistent-test-password";
934
935 let key_id = KeyId::from_bytes([5; 16]);
936 let original_key_bytes = vec![0x12; 32]; {
940 let mut store = FileStore::new(temp_dir.path(), config.clone()).unwrap();
941 store.init_with_password(password).unwrap();
942
943 let secret_key = SecretKey::from_bytes(original_key_bytes.clone(), Algorithm::ChaCha20Poly1305).unwrap();
944 let metadata = KeyMetadata {
945 id: key_id.clone(),
946 base_id: key_id.clone(),
947 algorithm: Algorithm::ChaCha20Poly1305,
948 created_at: SystemTime::now(),
949 expires_at: None,
950 state: KeyState::Active,
951 version: 1,
952 };
953 let versioned_key = VersionedKey { key: secret_key, metadata };
954
955 store.store(versioned_key).unwrap();
956 } {
960 let mut store = FileStore::new(temp_dir.path(), config).unwrap();
961 store.init_with_password(password).unwrap();
962
963 let retrieved = store.retrieve(&key_id).unwrap();
964 assert_eq!(retrieved.key.expose_secret(), &original_key_bytes);
965 assert_eq!(retrieved.metadata.algorithm, Algorithm::ChaCha20Poly1305);
966 }
967 }
968}