use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use log::info;
use serde::{Deserialize, Serialize};
use crate::{
sop::{Certificate, Sop, SopError},
DEFAULT_CERT_FILENAME, DEFAULT_KEY_FILENAME, DEFAULT_VALUES_FILENAME,
};
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Store {
#[serde(default)]
certs: HashMap<String, Vec<u8>>,
kv: HashMap<String, Vec<u8>>,
}
impl Store {
pub fn push_cert(&mut self, name: &str, cert: &Certificate) {
self.certs.insert(name.into(), cert.as_bytes().to_vec());
}
pub fn cert_names(&self) -> impl Iterator<Item = &str> {
self.certs.keys().map(|k| k.as_str())
}
pub fn get_cert(&self, name: &str) -> Option<Certificate> {
self.certs.get(name).map(|c| Certificate::new(c.to_vec()))
}
pub fn remove_cert(&mut self, name: &str) -> Result<(), StoreError> {
if !self.certs.contains_key(name) {
Err(StoreError::NoSuchCertificate(name.into()))
} else if self.certs.len() == 1 {
Err(StoreError::MustKeepOnlyCertificate)
} else {
self.certs.remove(name);
Ok(())
}
}
fn certs(&self) -> Vec<Certificate> {
self.certs
.values()
.map(|c| Certificate::new(c.to_vec()))
.collect()
}
pub fn from_bytes(filename: &Path, data: &[u8]) -> Result<Self, StoreError> {
serde_json::from_slice(data).map_err(|err| StoreError::Parse(filename.into(), err))
}
pub fn to_bytes(&self) -> Result<Vec<u8>, StoreError> {
serde_json::to_vec(&self).map_err(StoreError::ToJson)
}
pub fn insert(&mut self, name: &str, value: &[u8]) {
self.kv.insert(name.into(), value.into());
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &[u8])> {
self.kv.iter().map(|(k, v)| (k.as_str(), v.as_slice()))
}
pub fn get(&self, name: &str) -> Option<&[u8]> {
self.kv.get(name).map(|v| v.as_slice())
}
pub fn remove(&mut self, name: &str) {
self.kv.remove(name);
}
pub fn rename(&mut self, old: &str, new: &str) -> Result<(), StoreError> {
if let Some(value) = self.kv.remove(old) {
if self.kv.contains_key(new) {
Err(StoreError::AlreadyExists(old.into(), new.into()))
} else {
self.kv.insert(new.into(), value);
Ok(())
}
} else {
Err(StoreError::DoesNotExist(old.into()))
}
}
pub fn import(&mut self, other: &Self) {
for (name, cert) in other.certs.iter() {
self.certs.insert(name.to_string(), cert.to_vec());
}
for (key, value) in other.kv.iter() {
self.kv.insert(key.to_string(), value.to_vec());
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
#[error("failed to parse store file {0} as JSON")]
Parse(PathBuf, #[source] serde_json::Error),
#[error("failed to serialize file as JSON")]
ToJson(#[source] serde_json::Error),
#[error("can't rename value {0}: it does not exist")]
DoesNotExist(String),
#[error("can't rename value {0} to {1}: it does not exist")]
AlreadyExists(String, String),
#[error("store does not have a certificate names {0}")]
NoSuchCertificate(String),
#[error("can't remove only certificate in store")]
MustKeepOnlyCertificate,
}
pub struct EncryptedStore {
values: PathBuf,
key_file: PathBuf,
cert_file: PathBuf,
sop: Sop,
}
impl EncryptedStore {
pub fn new(sop: Sop, dirname: &Path) -> Self {
Self {
values: dirname.join(DEFAULT_VALUES_FILENAME),
key_file: dirname.join(DEFAULT_KEY_FILENAME),
cert_file: dirname.join(DEFAULT_CERT_FILENAME),
sop,
}
}
pub fn install_key(&self, key_filename: &Path) -> Result<(), EncryptedStoreError> {
copy_file(key_filename, &self.key_file)?;
Ok(())
}
pub fn install_cert(&self, cert_filename: &Path) -> Result<(), EncryptedStoreError> {
copy_file(cert_filename, &self.cert_file)?;
Ok(())
}
pub fn read_store(&self) -> Result<Store, EncryptedStoreError> {
info!("read and decrypt {}", self.values.display());
let data = std::fs::read(&self.values)
.map_err(|err| EncryptedStoreError::Load(self.values.clone(), err))?;
let cleartext = self.sop.decrypt(data).map_err(EncryptedStoreError::Sop)?;
Store::from_bytes(&self.values, &cleartext).map_err(EncryptedStoreError::Store)
}
pub fn write_store(&self, store: &Store) -> Result<(), EncryptedStoreError> {
let data = store.to_bytes()?;
self.sop
.encrypt(data, &store.certs(), &self.values)
.map_err(EncryptedStoreError::Sop)?;
Ok(())
}
pub fn extract_cert(&self) -> Result<Certificate, EncryptedStoreError> {
self.sop.extract_cert().map_err(EncryptedStoreError::Sop)
}
}
fn copy_file(input: &Path, store_key: &Path) -> Result<(), EncryptedStoreError> {
info!("copy file {} to {}", input.display(), store_key.display());
std::fs::copy(input, store_key)
.map_err(|err| EncryptedStoreError::CopyFile(input.into(), err))?;
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum EncryptedStoreError {
#[error("failed to copy file {0} to store")]
CopyFile(PathBuf, #[source] std::io::Error),
#[error("SOP implementation failed")]
Sop(#[source] SopError),
#[error("failed to read encrypted store {0}")]
Load(PathBuf, #[source] std::io::Error),
#[error("failed to save encrypted store as {0}")]
Save(PathBuf, #[source] SopError),
#[error(transparent)]
Store(#[from] StoreError),
}