1use crate::B64_URL_SAFE_NO_PAD;
9use async_trait::async_trait;
10use base64::Engine;
11use std::{
12 io::{Error as IoError, ErrorKind},
13 path::Path,
14};
15
16#[cfg(feature = "use_async_std")]
17use async_std::{
18 fs::{create_dir_all as cdall, read, OpenOptions},
19 io::WriteExt,
20 os::unix::fs::OpenOptionsExt,
21};
22#[cfg(feature = "use_tokio")]
23use tokio::{
24 fs::{create_dir_all, read, OpenOptions},
25 io::AsyncWriteExt,
26};
27
28use crate::crypto::sha256_hasher;
29
30#[async_trait]
32pub trait AcmeCache {
33 type Error: CacheError;
35
36 async fn read_account(&self, contacts: &[&str]) -> Result<Option<Vec<u8>>, Self::Error>;
40
41 async fn write_account(&self, contacts: &[&str], data: &[u8]) -> Result<(), Self::Error>;
48
49 async fn write_certificate(
64 &self,
65 domains: &[String],
66 directory_url: &str,
67 key_pem: &str,
68 certificate_pem: &str,
69 ) -> Result<(), Self::Error>;
70}
71
72#[async_trait]
73impl<P> AcmeCache for P
74where
75 P: AsRef<Path> + Send + Sync,
76{
77 type Error = IoError;
78
79 async fn read_account(&self, contacts: &[&str]) -> Result<Option<Vec<u8>>, Self::Error> {
80 let file = cached_key_file_name(contacts);
81 let mut path = self.as_ref().to_path_buf();
82 path.push(file);
83 match read(path).await {
84 Ok(content) => Ok(Some(content)),
85 Err(err) => match err.kind() {
86 ErrorKind::NotFound => Ok(None),
87 _ => Err(err),
88 },
89 }
90 }
91
92 async fn write_account(&self, contacts: &[&str], contents: &[u8]) -> Result<(), Self::Error> {
93 let mut path = self.as_ref().to_path_buf();
94 create_dir_all(&path).await?;
95 path.push(cached_key_file_name(contacts));
96 Ok(write(path, contents).await?)
97 }
98
99 async fn write_certificate(
100 &self,
101 domains: &[String],
102 directory_url: &str,
103 key_pem: &str,
104 certificate_pem: &str,
105 ) -> Result<(), Self::Error> {
106 let hash = {
107 let mut ctx = sha256_hasher();
108 for domain in domains {
109 ctx.update(domain.as_ref());
110 ctx.update(&[0])
111 }
112 ctx.update(directory_url.as_bytes());
114 B64_URL_SAFE_NO_PAD.encode(ctx.finish())
115 };
116 let file = AsRef::<Path>::as_ref(self).join(&format!("cached_cert_{}", hash));
117 let content = format!("{}\n{}", key_pem, certificate_pem);
118 write(&file, &content).await?;
119 Ok(())
120 }
121}
122
123pub trait CacheError: std::error::Error + Send + Sync + 'static {}
125
126impl<T> CacheError for T where T: std::error::Error + Send + Sync + 'static {}
127
128#[cfg(feature = "use_async_std")]
129async fn create_dir_all(a: impl AsRef<Path>) -> Result<(), IoError> {
130 let p = a.as_ref();
131 let p = <&async_std::path::Path>::from(p);
132 cdall(p).await
133}
134
135#[cfg(not(any(feature = "use_tokio", feature = "use_async_std")))]
136async fn create_dir_all(_a: impl AsRef<Path>) -> Result<(), IoError> {
137 Err(IoError::new(
138 ErrorKind::NotFound,
139 "no async backend selected",
140 ))
141}
142#[cfg(not(any(feature = "use_tokio", feature = "use_async_std")))]
143async fn read(_a: impl AsRef<Path>) -> Result<Vec<u8>, IoError> {
144 Err(IoError::new(
145 ErrorKind::NotFound,
146 "no async backend selected",
147 ))
148}
149#[cfg(not(any(feature = "use_tokio", feature = "use_async_std")))]
150async fn write(_a: impl AsRef<Path>, _c: impl AsRef<[u8]>) -> Result<(), IoError> {
151 Err(IoError::new(
152 ErrorKind::NotFound,
153 "no async backend selected",
154 ))
155}
156#[cfg(any(feature = "use_tokio", feature = "use_async_std"))]
157async fn write(file_path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> Result<(), IoError> {
158 let mut file = OpenOptions::new();
159 file.write(true).create(true).truncate(true);
160 #[cfg(unix)]
161 file.mode(0o600); let mut buffer = file.open(file_path.as_ref()).await?;
163 buffer.write_all(content.as_ref()).await?;
164 Ok(())
165}
166
167fn cached_key_file_name(contact: &[&str]) -> String {
168 let mut ctx = sha256_hasher();
169 for el in contact {
170 ctx.update(el.as_ref());
171 ctx.update(&[0])
172 }
173 let hash = B64_URL_SAFE_NO_PAD.encode(ctx.finish());
174 format!("cached_account_{}", hash)
175}