use std::sync::Arc;
use anyhow::{anyhow, Result};
use camino::Utf8PathBuf;
use papaya::HashMap as Papaya;
use tracing::info;
use unicase::UniCase;
use crate::{
RunContext,
certificates::HostCertificate,
};
#[derive(Debug)]
pub struct CertStore {
_context: Arc<RunContext>,
by_host: Papaya<UniCase<String>, HostCertificate>,
by_file: Papaya<Utf8PathBuf, HostCertificate>,
}
impl CertStore {
pub fn new(_context: Arc<RunContext>) -> Result<Self> {
info!("Loading host certificates");
let certstore = Self {
_context,
by_host: Papaya::new(),
by_file: Papaya::new(),
};
Ok(certstore)
}
pub fn by_host(&self, host: &str) -> Option<HostCertificate> {
let pmap = self.by_host.pin();
let host = UniCase::new(host.to_string());
pmap.get(&host)
.cloned()
}
pub fn by_wildcard(&self, host: &str) -> Option<HostCertificate> {
host.split_once('.')
.and_then(|(_host, domain)| {
let wildcard = format!("*.{domain}");
self.by_host(&wildcard)
})
}
pub fn by_file(&self, file: &Utf8PathBuf) -> Option<HostCertificate> {
let pmap = self.by_file.pin();
pmap.get(file)
.cloned()
}
pub fn upsert(&self, newcert: HostCertificate) -> Result<()> {
for hostname in newcert.hostnames().iter() {
let host = UniCase::new(hostname.clone());
info!("Updating/inserting certificate for {host}");
self.by_host.pin().update_or_insert(host, |_old| newcert.clone(), newcert.clone());
}
let keyfile = newcert.keyfile().to_path_buf();
let certfile = newcert.certfile().to_path_buf();
let by_file = self.by_file.pin();
by_file.update_or_insert(keyfile, |_old| newcert.clone(), newcert.clone());
by_file.update_or_insert(certfile, |_old| newcert.clone(), newcert.clone());
Ok(())
}
pub fn upsert_all(&self, newcerts: Vec<HostCertificate>) -> Result<()> {
for hc in newcerts {
self.upsert(hc)?;
}
Ok(())
}
pub fn update(&self, newcert: HostCertificate) -> Result<()> {
for hostname in newcert.hostnames().iter() {
info!("Updating certificate for {hostname}");
let host = UniCase::new(hostname.clone());
self.by_host.pin().update(host, |_old| newcert.clone())
.ok_or(anyhow!("Matching host for {} not found in cert store", hostname))?;
}
let keyfile = newcert.keyfile().to_path_buf();
let certfile = newcert.certfile().to_path_buf();
let by_file = self.by_file.pin();
by_file.update(keyfile, |_old| newcert.clone())
.ok_or(anyhow!("File {} not found in cert store", newcert.keyfile()))?;
by_file.update(certfile, |_old| newcert.clone())
.ok_or(anyhow!("File {} not found in cert store", newcert.certfile()))?;
Ok(())
}
pub fn watchlist(&self) -> Vec<Utf8PathBuf> {
let by_host = self.by_host.pin();
by_host.values()
.filter(|h| h.watch())
.flat_map(|h| [
h.keyfile().to_path_buf(),
h.certfile().to_path_buf()
])
.collect()
}
}