use crate::B64_URL_SAFE_NO_PAD;
use async_trait::async_trait;
use base64::Engine;
use std::{
io::{Error as IoError, ErrorKind},
path::Path,
};
#[cfg(feature = "use_async_std")]
use async_std::{
fs::{create_dir_all as cdall, read, OpenOptions},
io::WriteExt,
os::unix::fs::OpenOptionsExt,
};
#[cfg(feature = "use_tokio")]
use tokio::{
fs::{create_dir_all, read, OpenOptions},
io::AsyncWriteExt,
};
use crate::crypto::sha256_hasher;
#[async_trait]
pub trait AcmeCache {
type Error: CacheError;
async fn read_account(&self, contacts: &[&str]) -> Result<Option<Vec<u8>>, Self::Error>;
async fn write_account(&self, contacts: &[&str], data: &[u8]) -> Result<(), Self::Error>;
async fn write_certificate(
&self,
domains: &[String],
directory_url: &str,
key_pem: &str,
certificate_pem: &str,
) -> Result<(), Self::Error>;
}
#[async_trait]
impl<P> AcmeCache for P
where
P: AsRef<Path> + Send + Sync,
{
type Error = IoError;
async fn read_account(&self, contacts: &[&str]) -> Result<Option<Vec<u8>>, Self::Error> {
let file = cached_key_file_name(contacts);
let mut path = self.as_ref().to_path_buf();
path.push(file);
match read(path).await {
Ok(content) => Ok(Some(content)),
Err(err) => match err.kind() {
ErrorKind::NotFound => Ok(None),
_ => Err(err),
},
}
}
async fn write_account(&self, contacts: &[&str], contents: &[u8]) -> Result<(), Self::Error> {
let mut path = self.as_ref().to_path_buf();
create_dir_all(&path).await?;
path.push(cached_key_file_name(contacts));
Ok(write(path, contents).await?)
}
async fn write_certificate(
&self,
domains: &[String],
directory_url: &str,
key_pem: &str,
certificate_pem: &str,
) -> Result<(), Self::Error> {
let hash = {
let mut ctx = sha256_hasher();
for domain in domains {
ctx.update(domain.as_ref());
ctx.update(&[0])
}
ctx.update(directory_url.as_bytes());
B64_URL_SAFE_NO_PAD.encode(ctx.finish())
};
let file = AsRef::<Path>::as_ref(self).join(&format!("cached_cert_{}", hash));
let content = format!("{}\n{}", key_pem, certificate_pem);
write(&file, &content).await?;
Ok(())
}
}
pub trait CacheError: std::error::Error + Send + Sync + 'static {}
impl<T> CacheError for T where T: std::error::Error + Send + Sync + 'static {}
#[cfg(feature = "use_async_std")]
async fn create_dir_all(a: impl AsRef<Path>) -> Result<(), IoError> {
let p = a.as_ref();
let p = <&async_std::path::Path>::from(p);
cdall(p).await
}
#[cfg(not(any(feature = "use_tokio", feature = "use_async_std")))]
async fn create_dir_all(_a: impl AsRef<Path>) -> Result<(), IoError> {
Err(IoError::new(
ErrorKind::NotFound,
"no async backend selected",
))
}
#[cfg(not(any(feature = "use_tokio", feature = "use_async_std")))]
async fn read(_a: impl AsRef<Path>) -> Result<Vec<u8>, IoError> {
Err(IoError::new(
ErrorKind::NotFound,
"no async backend selected",
))
}
#[cfg(not(any(feature = "use_tokio", feature = "use_async_std")))]
async fn write(_a: impl AsRef<Path>, _c: impl AsRef<[u8]>) -> Result<(), IoError> {
Err(IoError::new(
ErrorKind::NotFound,
"no async backend selected",
))
}
#[cfg(any(feature = "use_tokio", feature = "use_async_std"))]
async fn write(file_path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> Result<(), IoError> {
let mut file = OpenOptions::new();
file.write(true).create(true).truncate(true);
#[cfg(unix)]
file.mode(0o600); let mut buffer = file.open(file_path.as_ref()).await?;
buffer.write_all(content.as_ref()).await?;
Ok(())
}
fn cached_key_file_name(contact: &[&str]) -> String {
let mut ctx = sha256_hasher();
for el in contact {
ctx.update(el.as_ref());
ctx.update(&[0])
}
let hash = B64_URL_SAFE_NO_PAD.encode(ctx.finish());
format!("cached_account_{}", hash)
}