use std::{collections::BTreeMap, fmt::Debug, sync::Arc};
use dashmap::DashMap;
use faststr::FastStr;
use parking_lot::RwLock;
use rustls::{crypto::ring::sign::any_supported_type, sign::CertifiedKey};
use sub_host::sub_host;
use tokio::time;
use crate::{
cert::Cert,
error::{Error, Result},
};
#[cfg(feature = "cert_dir")]
mod dir;
#[cfg(feature = "cert_dir")]
pub use dir::CertDir;
pub trait LoadCert: Send + Sync + 'static + Debug {
fn load(&self, host: impl Into<FastStr>) -> Result<Option<Cert>>;
}
#[derive(Debug)]
pub struct CertLoader<L: LoadCert> {
pub host_cert: DashMap<FastStr, Arc<CertifiedKey>>,
pub expire: RwLock<BTreeMap<i64, Vec<FastStr>>>,
pub loader: L,
}
impl<L: LoadCert> CertLoader<L> {
pub fn new(loader: L) -> Arc<Self> {
let s = Arc::new(Self {
host_cert: DashMap::new(),
expire: RwLock::new(BTreeMap::new()),
loader,
});
let s2 = s.clone();
tokio::spawn(async move {
let mut interval = time::interval(time::Duration::from_secs(86400));
loop {
interval.tick().await;
s2.rm_expired(2);
}
});
s
}
pub fn rm_expired(&self, days: i64) {
let mut to_rm_expire = Vec::new();
for (expire, hosts) in self.expire.read().iter() {
if *expire < chrono::Utc::now().timestamp() + days * 24 * 60 * 60 {
for host in hosts {
self.host_cert.remove(host);
}
to_rm_expire.push(*expire);
} else {
break;
}
}
if !to_rm_expire.is_empty() {
let mut expire_write = self.expire.write();
for expire in to_rm_expire {
expire_write.remove(&expire);
}
}
}
pub fn get(&self, host: impl Into<String>) -> Result<Arc<CertifiedKey>> {
let host = host.into();
let mut h = host.clone();
let mut wait_check_sub = true;
loop {
if let Some(c) = self.host_cert.get::<FastStr>(&h.clone().into()) {
return Ok(c.clone());
}
if let Some(cert) = self.loader.load(h.clone())? {
let cert = Arc::new(cert);
let key =
any_supported_type(&cert.key).map_err(|e| Error::PrivateKeyUnsupported(e.to_string()))?;
let certified_key = Arc::new(CertifiedKey::new(cert.fullchain.clone(), key));
if let Some(domain) = cert.host_li.first() {
self.host_cert.insert(domain.clone(), certified_key.clone());
self
.expire
.write()
.entry(cert.expire)
.or_default()
.push(domain.clone());
}
return Ok(certified_key);
}
if wait_check_sub {
wait_check_sub = false;
} else {
break;
}
if let Some(sub) = sub_host(h) {
h = sub;
} else {
break;
}
}
Err(Error::CertNotFound(host))
}
}
impl<L: LoadCert> rustls::server::ResolvesServerCert for CertLoader<L> {
fn resolve(
&self,
client_hello: rustls::server::ClientHello,
) -> Option<Arc<rustls::sign::CertifiedKey>> {
let host = FastStr::from(client_hello.server_name()?.to_string());
self.get(host).ok()
}
}