vault.rs (4391B)
1 #[cfg(feature = "os-keyring")] 2 use alloc::format; 3 use alloc::string::String; 4 5 use crate::error::RadrootsSecretVaultAccessError; 6 7 pub trait RadrootsSecretVault: Send + Sync { 8 fn store_secret(&self, slot: &str, secret: &str) -> Result<(), RadrootsSecretVaultAccessError>; 9 10 fn load_secret(&self, slot: &str) -> Result<Option<String>, RadrootsSecretVaultAccessError>; 11 12 fn remove_secret(&self, slot: &str) -> Result<(), RadrootsSecretVaultAccessError>; 13 } 14 15 #[cfg(feature = "memory-vault")] 16 #[derive(Debug, Clone, Default)] 17 pub struct RadrootsSecretVaultMemory { 18 entries: std::sync::Arc<std::sync::RwLock<std::collections::HashMap<String, String>>>, 19 } 20 21 #[cfg(feature = "memory-vault")] 22 impl RadrootsSecretVaultMemory { 23 #[must_use] 24 pub fn new() -> Self { 25 Self::default() 26 } 27 } 28 29 #[cfg(feature = "memory-vault")] 30 impl RadrootsSecretVault for RadrootsSecretVaultMemory { 31 fn store_secret(&self, slot: &str, secret: &str) -> Result<(), RadrootsSecretVaultAccessError> { 32 let mut guard = self 33 .entries 34 .write() 35 .map_err(|_| RadrootsSecretVaultAccessError::Backend("memory vault poisoned".into()))?; 36 guard.insert(String::from(slot), String::from(secret)); 37 Ok(()) 38 } 39 40 fn load_secret(&self, slot: &str) -> Result<Option<String>, RadrootsSecretVaultAccessError> { 41 let guard = self 42 .entries 43 .read() 44 .map_err(|_| RadrootsSecretVaultAccessError::Backend("memory vault poisoned".into()))?; 45 Ok(guard.get(slot).cloned()) 46 } 47 48 fn remove_secret(&self, slot: &str) -> Result<(), RadrootsSecretVaultAccessError> { 49 let mut guard = self 50 .entries 51 .write() 52 .map_err(|_| RadrootsSecretVaultAccessError::Backend("memory vault poisoned".into()))?; 53 guard.remove(slot); 54 Ok(()) 55 } 56 } 57 58 #[cfg(feature = "os-keyring")] 59 #[derive(Debug, Clone)] 60 pub struct RadrootsSecretVaultOsKeyring { 61 service_name: String, 62 } 63 64 #[cfg(feature = "os-keyring")] 65 impl RadrootsSecretVaultOsKeyring { 66 #[must_use] 67 pub fn new(service_name: impl Into<String>) -> Self { 68 Self { 69 service_name: service_name.into(), 70 } 71 } 72 } 73 74 #[cfg(feature = "os-keyring")] 75 impl Default for RadrootsSecretVaultOsKeyring { 76 fn default() -> Self { 77 Self::new("org.radroots.secret-vault") 78 } 79 } 80 81 #[cfg(feature = "os-keyring")] 82 impl RadrootsSecretVault for RadrootsSecretVaultOsKeyring { 83 fn store_secret(&self, slot: &str, secret: &str) -> Result<(), RadrootsSecretVaultAccessError> { 84 let entry = keyring::Entry::new(self.service_name.as_str(), slot) 85 .map_err(|source| RadrootsSecretVaultAccessError::Backend(format!("{source}")))?; 86 entry 87 .set_password(secret) 88 .map_err(|source| RadrootsSecretVaultAccessError::Backend(format!("{source}"))) 89 } 90 91 fn load_secret(&self, slot: &str) -> Result<Option<String>, RadrootsSecretVaultAccessError> { 92 let entry = keyring::Entry::new(self.service_name.as_str(), slot) 93 .map_err(|source| RadrootsSecretVaultAccessError::Backend(format!("{source}")))?; 94 match entry.get_password() { 95 Ok(secret) => Ok(Some(secret)), 96 Err(keyring::Error::NoEntry) => Ok(None), 97 Err(source) => Err(RadrootsSecretVaultAccessError::Backend(format!("{source}"))), 98 } 99 } 100 101 fn remove_secret(&self, slot: &str) -> Result<(), RadrootsSecretVaultAccessError> { 102 let entry = keyring::Entry::new(self.service_name.as_str(), slot) 103 .map_err(|source| RadrootsSecretVaultAccessError::Backend(format!("{source}")))?; 104 match entry.delete_credential() { 105 Ok(_) | Err(keyring::Error::NoEntry) => Ok(()), 106 Err(source) => Err(RadrootsSecretVaultAccessError::Backend(format!("{source}"))), 107 } 108 } 109 } 110 111 #[cfg(all(test, feature = "memory-vault"))] 112 mod tests { 113 use super::*; 114 115 #[test] 116 fn memory_vault_round_trip() { 117 let vault = RadrootsSecretVaultMemory::new(); 118 vault.store_secret("alice", "abc123").expect("store"); 119 let loaded = vault.load_secret("alice").expect("load"); 120 assert_eq!(loaded.as_deref(), Some("abc123")); 121 vault.remove_secret("alice").expect("remove"); 122 let loaded = vault.load_secret("alice").expect("load"); 123 assert!(loaded.is_none()); 124 } 125 }