use std::collections::HashMap;
use std::fmt;
use std::sync::{Arc, Mutex, PoisonError};
pub use zeroize::Zeroizing;
#[cfg(unix)]
mod service;
#[cfg(all(test, unix))]
mod tests;
#[derive(Clone)]
pub struct Secrets {
application: String,
store: Store,
}
#[derive(Clone)]
enum Store {
Memory(Arc<Mutex<HashMap<String, Zeroizing<Vec<u8>>>>>),
#[cfg(unix)]
System(service::Keyring),
}
impl Secrets {
pub fn system(application: &str) -> Result<Self, SecretError> {
#[cfg(unix)]
{
let address = std::env::var("DBUS_SESSION_BUS_ADDRESS").unwrap_or_default();
let keyring = service::Keyring::open(&address)?;
Ok(Self { application: application.to_owned(), store: Store::System(keyring) })
}
#[cfg(not(unix))]
{
let _ = application;
Err(SecretError::Unavailable)
}
}
#[must_use]
pub fn memory(application: &str) -> Self {
Self { application: application.to_owned(), store: Store::Memory(Arc::default()) }
}
#[must_use]
pub fn is_system(&self) -> bool {
!matches!(self.store, Store::Memory(_))
}
pub fn store(&self, account: &str, label: &str, secret: &[u8]) -> Result<(), SecretError> {
match &self.store {
Store::Memory(kept) => {
let _ = label;
lock(kept).insert(account.to_owned(), Zeroizing::new(secret.to_vec()));
Ok(())
}
#[cfg(unix)]
Store::System(keyring) => keyring.store(&self.application, account, label, secret),
}
}
pub fn lookup(&self, account: &str) -> Result<Option<Zeroizing<Vec<u8>>>, SecretError> {
match &self.store {
Store::Memory(kept) => Ok(lock(kept).get(account).cloned()),
#[cfg(unix)]
Store::System(keyring) => keyring.lookup(&self.application, account),
}
}
pub fn delete(&self, account: &str) -> Result<(), SecretError> {
match &self.store {
Store::Memory(kept) => {
lock(kept).remove(account);
Ok(())
}
#[cfg(unix)]
Store::System(keyring) => keyring.delete(&self.application, account),
}
}
}
fn lock(
kept: &Mutex<HashMap<String, Zeroizing<Vec<u8>>>>,
) -> std::sync::MutexGuard<'_, HashMap<String, Zeroizing<Vec<u8>>>> {
kept.lock().unwrap_or_else(PoisonError::into_inner)
}
impl fmt::Debug for Secrets {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let place = if self.is_system() { "system keyring" } else { "memory" };
f.debug_struct("Secrets").field("application", &self.application).field("store", &place).finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SecretError {
Unavailable,
Locked,
Denied,
Other(String),
}
impl fmt::Display for SecretError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unavailable => f.write_str("there is no system keyring to keep secrets in"),
Self::Locked => f.write_str("the keyring is locked and was not opened"),
Self::Denied => f.write_str("the keyring refused, or its prompt was dismissed"),
Self::Other(reason) => write!(f, "the keyring answered: {reason}"),
}
}
}
impl std::error::Error for SecretError {}