use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::Arc,
};
#[cfg(feature = "async-std")]
use async_std::{fs, io, prelude::*, sync::RwLock};
use once_cell::sync::OnceCell;
#[cfg(feature = "tokio")]
use tokio::{fs, io, io::AsyncReadExt, sync::RwLock};
use zeroize::Zeroizing;
use crate::Key;
#[cfg(feature = "unstable")]
pub mod api;
#[cfg(not(feature = "unstable"))]
mod api;
mod error;
mod item;
mod secret;
pub use error::{Error, InvalidItemError, WeakKeyError};
pub use item::Item;
pub use secret::Secret;
type ItemDefinition = (String, HashMap<String, String>, Zeroizing<Vec<u8>>, bool);
#[derive(Debug)]
pub struct Keyring {
keyring: Arc<RwLock<api::Keyring>>,
path: PathBuf,
mtime: RwLock<Option<std::time::SystemTime>>,
key: RwLock<OnceCell<Key>>,
secret: Arc<Secret>,
}
impl Keyring {
pub async fn load_default() -> Result<Self, Error> {
#[cfg(feature = "tracing")]
tracing::debug!("Loading default keyring file");
let secret = secret::retrieve().await?;
Self::load(api::Keyring::default_path()?, secret).await
}
pub async fn load(path: impl AsRef<Path>, secret: Secret) -> Result<Self, Error> {
#[cfg(feature = "tracing")]
tracing::debug!("Trying to load keyring file at {:?}", path.as_ref());
let (mtime, keyring) = match fs::File::open(path.as_ref()).await {
Err(err) if err.kind() == io::ErrorKind::NotFound => {
#[cfg(feature = "tracing")]
tracing::debug!("Keyring file not found, creating a new one");
(None, api::Keyring::new())
}
Err(err) => return Err(err.into()),
Ok(mut file) => {
#[cfg(feature = "tracing")]
tracing::debug!("Keyring file found, loading it content");
let mtime = file.metadata().await?.modified().ok();
let mut content = Vec::new();
file.read_to_end(&mut content).await?;
let keyring = api::Keyring::try_from(content.as_slice())?;
(mtime, keyring)
}
};
Ok(Self {
keyring: Arc::new(RwLock::new(keyring)),
path: path.as_ref().to_path_buf(),
mtime: RwLock::new(mtime),
key: Default::default(),
secret: Arc::new(secret),
})
}
pub async fn n_items(&self) -> usize {
self.keyring.read().await.items.len()
}
pub async fn items(&self) -> Vec<Result<Item, InvalidItemError>> {
let mut opt_key = self.key.write().await;
let key = self.derive_key(&mut opt_key).await;
let keyring = self.keyring.read().await;
keyring
.items
.iter()
.map(|e| {
(*e).clone().decrypt(key).map_err(|err| {
InvalidItemError::new(
err,
e.hashed_attributes.keys().map(|x| x.to_string()).collect(),
)
})
})
.collect()
}
pub async fn search_items(&self, attributes: HashMap<&str, &str>) -> Result<Vec<Item>, Error> {
let mut opt_key = self.key.write().await;
let key = self.derive_key(&mut opt_key).await;
let keyring = self.keyring.read().await;
keyring.search_items(attributes, key)
}
pub async fn lookup_item(
&self,
attributes: HashMap<&str, &str>,
) -> Result<Option<Item>, Error> {
let mut opt_key = self.key.write().await;
let key = self.derive_key(&mut opt_key).await;
let keyring = self.keyring.read().await;
keyring.lookup_item(attributes, key)
}
pub async fn delete(&self, attributes: HashMap<&str, &str>) -> Result<(), Error> {
{
let mut opt_key = self.key.write().await;
let key = self.derive_key(&mut opt_key).await;
let mut keyring = self.keyring.write().await;
keyring.remove_items(attributes, key)?;
};
self.write().await
}
pub async fn create_item(
&self,
label: &str,
attributes: HashMap<&str, &str>,
secret: impl AsRef<[u8]>,
replace: bool,
) -> Result<(), Error> {
{
let mut opt_key = self.key.write().await;
let key = self.derive_key(&mut opt_key).await;
let mut keyring = self.keyring.write().await;
if replace {
keyring.remove_items(attributes.clone(), key)?;
}
let item = Item::new(label, attributes, secret);
let encrypted_item = item.encrypt(key)?;
keyring.items.push(encrypted_item);
};
self.write().await
}
pub async fn replace_item_index(&self, index: usize, item: &Item) -> Result<(), Error> {
{
let mut opt_key = self.key.write().await;
let key = self.derive_key(&mut opt_key).await;
let mut keyring = self.keyring.write().await;
if let Some(item_store) = keyring.items.get_mut(index) {
*item_store = item.encrypt(key)?;
} else {
return Err(Error::InvalidItemIndex(index));
}
}
self.write().await
}
pub async fn delete_item_index(&self, index: usize) -> Result<(), Error> {
{
let mut keyring = self.keyring.write().await;
if index < keyring.items.len() {
keyring.items.remove(index);
} else {
return Err(Error::InvalidItemIndex(index));
}
}
self.write().await
}
pub(crate) async fn create_items(&self, items: Vec<ItemDefinition>) -> Result<(), Error> {
let mut opt_key = self.key.write().await;
let key = self.derive_key(&mut opt_key).await;
let mut keyring = self.keyring.write().await;
for (label, attributes, secret, replace) in items {
if replace {
keyring.remove_items(attributes.clone(), key)?;
}
let item = Item::new(label, attributes, &*secret);
let encrypted_item = item.encrypt(key)?;
keyring.items.push(encrypted_item);
}
#[cfg(feature = "tracing")]
tracing::debug!("Writing keyring back to the file");
keyring.dump(&self.path, *self.mtime.read().await).await?;
Ok(())
}
pub async fn write(&self) -> Result<(), Error> {
#[cfg(feature = "tracing")]
tracing::debug!("Writing keyring back to the file {:?}", self.path);
{
let mtime = self.mtime.read().await;
let mut keyring = self.keyring.write().await;
#[cfg(feature = "tracing")]
tracing::debug!("Current modified time {:?}", mtime);
keyring.dump(&self.path, *mtime).await?;
};
if let Ok(modified) = fs::metadata(&self.path).await?.modified() {
#[cfg(feature = "tracing")]
tracing::debug!("New modified time {:?}", modified);
self.mtime.write().await.replace(modified);
}
Ok(())
}
async fn derive_key<'a>(&'a self, key: &'a mut OnceCell<Key>) -> &'a Key {
let keyring = Arc::clone(&self.keyring);
let secret = Arc::clone(&self.secret);
#[cfg(feature = "async-std")]
let newkey = async_global_executor::spawn_blocking(move || {
async_std::task::block_on(async { keyring.read().await.derive_key(&secret) })
})
.await;
#[cfg(feature = "tokio")]
let newkey =
tokio::task::spawn_blocking(move || keyring.blocking_read().derive_key(&secret))
.await
.unwrap();
key.get_or_init(|| newkey)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "async-std")]
#[async_std::test]
async fn repeated_write() -> Result<(), Error> {
repeated_write_().await
}
#[cfg(feature = "tokio")]
#[tokio::test]
async fn repeated_write() -> Result<(), Error> {
repeated_write_().await
}
async fn repeated_write_() -> Result<(), Error> {
let path = std::path::PathBuf::from("../../tests/test.keyring");
let secret = Secret::from(vec![1, 2]);
let keyring = Keyring::load(&path, secret).await?;
keyring.write().await?;
keyring.write().await?;
Ok(())
}
#[cfg(feature = "async-std")]
#[async_std::test]
async fn delete() -> Result<(), Error> {
let path = std::path::PathBuf::from("../../tests/test-delete.keyring");
let keyring = Keyring::load(&path, strong_key()).await?;
keyring
.create_item("Label", Default::default(), "secret", false)
.await?;
keyring.delete_item_index(0).await?;
let result = keyring.delete_item_index(100).await;
assert!(matches!(result, Err(Error::InvalidItemIndex(100))));
Ok(())
}
#[async_std::test]
async fn write_with_weak_key() -> Result<(), Error> {
let path = std::path::PathBuf::from("../../tests/write_with_weak_key.keyring");
let secret = Secret::from(vec![1, 2]);
let keyring = Keyring::load(&path, secret).await?;
let result = keyring
.create_item("label", Default::default(), "my-password", false)
.await;
assert!(matches!(
result,
Err(Error::WeakKey(WeakKeyError::PasswordTooShort(2)))
));
Ok(())
}
#[async_std::test]
async fn write_with_strong_key() -> Result<(), Error> {
let path = std::path::PathBuf::from("../../tests/write_with_strong_key.keyring");
let keyring = Keyring::load(&path, strong_key()).await?;
keyring
.create_item("label", Default::default(), "my-password", false)
.await?;
Ok(())
}
fn strong_key() -> Secret {
Secret::from([1, 2].into_iter().cycle().take(64).collect::<Vec<_>>())
}
}