1use std::{
2 collections::{HashMap, HashSet},
3 error::Error,
4 path::PathBuf,
5 str::FromStr,
6 sync::Arc,
7 time::Duration,
8};
9
10use base64::Engine;
11use instant_acme::{ExternalAccountKey, LetsEncrypt};
12use rustls::{client::WebPkiServerVerifier, crypto::CryptoProvider, ClientConfig};
13use rustls_platform_verifier::BuilderVerifierExt;
14use tokio::sync::RwLock;
15use tokio_util::sync::CancellationToken;
16use xxhash_rust::xxh3::xxh3_128;
17
18use crate::acme::{
19 add_domain_to_cache, convert_on_demand_config, provision_certificate, AcmeConfig, AcmeOnDemandConfig,
20};
21use ferron_common::{get_entry, get_value, util::match_hostname};
22use ferron_common::{logging::ErrorLogger, util::NoServerVerifier};
23
24pub fn build_rustls_client_config(
26 server_configuration: &ferron_common::config::ServerConfiguration,
27 crypto_provider: Arc<CryptoProvider>,
28) -> Result<ClientConfig, Box<dyn Error + Send + Sync>> {
29 build_raw_rustls_client_config(
30 get_value!("auto_tls_no_verification", server_configuration)
31 .and_then(|v| v.as_bool())
32 .unwrap_or(false),
33 crypto_provider,
34 )
35}
36
37fn build_raw_rustls_client_config(
39 no_verification: bool,
40 crypto_provider: Arc<CryptoProvider>,
41) -> Result<ClientConfig, Box<dyn Error + Send + Sync>> {
42 Ok(
43 (if no_verification {
44 ClientConfig::builder_with_provider(crypto_provider.clone())
45 .with_safe_default_protocol_versions()?
46 .dangerous()
47 .with_custom_certificate_verifier(Arc::new(NoServerVerifier::new()))
48 } else if let Ok(client_config) = BuilderVerifierExt::with_platform_verifier(
49 ClientConfig::builder_with_provider(crypto_provider.clone()).with_safe_default_protocol_versions()?,
50 ) {
51 client_config
52 } else {
53 ClientConfig::builder_with_provider(crypto_provider.clone())
54 .with_safe_default_protocol_versions()?
55 .with_webpki_verifier(
56 WebPkiServerVerifier::builder(Arc::new(rustls::RootCertStore {
57 roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
58 }))
59 .build()?,
60 )
61 })
62 .with_no_client_auth(),
63 )
64}
65
66pub fn resolve_acme_directory(server_configuration: &ferron_common::config::ServerConfiguration) -> String {
68 if let Some(directory) = get_value!("auto_tls_directory", server_configuration).and_then(|v| v.as_str()) {
69 directory.to_string()
70 } else if get_value!("auto_tls_letsencrypt_production", server_configuration)
71 .and_then(|v| v.as_bool())
72 .unwrap_or(true)
73 {
74 LetsEncrypt::Production.url().to_string()
75 } else {
76 LetsEncrypt::Staging.url().to_string()
77 }
78}
79
80pub fn parse_eab(
82 server_configuration: &ferron_common::config::ServerConfiguration,
83) -> Result<Option<Arc<ExternalAccountKey>>, anyhow::Error> {
84 Ok(
85 if let Some((Some(eab_key_id), Some(eab_key_hmac))) =
86 get_entry!("auto_tls_eab", server_configuration).map(|entry| {
87 (
88 entry.values.first().and_then(|v| v.as_str()),
89 entry.values.get(1).and_then(|v| v.as_str()),
90 )
91 })
92 {
93 match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(eab_key_hmac.trim_end_matches('=')) {
94 Ok(decoded_key) => Some(Arc::new(ExternalAccountKey::new(eab_key_id.to_string(), &decoded_key))),
95 Err(err) => Err(anyhow::anyhow!("Failed to decode EAB key HMAC: {}", err))?,
96 }
97 } else {
98 None
99 },
100 )
101}
102
103pub fn resolve_acme_cache_path(
104 server_configuration: &ferron_common::config::ServerConfiguration,
105) -> Result<Option<PathBuf>, anyhow::Error> {
106 let acme_default_directory = dirs::data_local_dir().and_then(|mut p| {
107 p.push("ferron-acme");
108 p.into_os_string().into_string().ok()
109 });
110 Ok(
111 if let Some(acme_cache_path_str) =
112 get_value!("auto_tls_cache", server_configuration).map_or(acme_default_directory.as_deref(), |v| {
113 if v.is_null() {
114 None
115 } else if let Some(v) = v.as_str() {
116 Some(v)
117 } else {
118 acme_default_directory.as_deref()
119 }
120 })
121 {
122 Some(PathBuf::from_str(acme_cache_path_str).map_err(|_| anyhow::anyhow!("Invalid ACME cache path"))?)
123 } else {
124 None
125 },
126 )
127}
128
129pub fn resolve_cache_paths(
131 server_configuration: &ferron_common::config::ServerConfiguration,
132 port: u16,
133 sni_hostname: &str,
134) -> Result<(Option<PathBuf>, Option<PathBuf>), anyhow::Error> {
135 let acme_cache_path_option = resolve_acme_cache_path(server_configuration)?;
136 let (account_cache_path, cert_cache_path) = if let Some(mut pathbuf) = acme_cache_path_option {
137 let base_pathbuf = pathbuf.clone();
138 let append_hash = base64::engine::general_purpose::URL_SAFE_NO_PAD
139 .encode(xxh3_128(format!("{port}-{sni_hostname}").as_bytes()).to_be_bytes());
140 pathbuf.push(append_hash);
141 (Some(base_pathbuf), Some(pathbuf))
142 } else {
143 (None, None)
144 };
145 Ok((account_cache_path, cert_cache_path))
146}
147
148#[allow(clippy::too_many_arguments)]
150pub async fn background_acme_task(
151 acme_configs: Vec<AcmeConfig>,
152 acme_on_demand_configs: Vec<AcmeOnDemandConfig>,
153 memory_acme_account_cache_data: Arc<RwLock<HashMap<String, Vec<u8>>>>,
154 acme_on_demand_rx: async_channel::Receiver<(String, u16)>,
155 on_demand_tls_ask_endpoint: Option<hyper::Uri>,
156 on_demand_tls_ask_endpoint_verify: bool,
157 acme_logger: ErrorLogger,
158 crypto_provider: Arc<CryptoProvider>,
159 existing_combinations: HashSet<(String, u16)>,
160 cancel_token: Option<CancellationToken>,
161) {
162 let acme_logger = Arc::new(acme_logger);
163
164 let acme_configs_mutex = Arc::new(tokio::sync::Mutex::new(acme_configs));
166
167 let prevent_file_race_conditions_sem = Arc::new(tokio::sync::Semaphore::new(1));
168
169 let acme_logger_clone = acme_logger.clone();
170 let acme_configs_mutex_clone = acme_configs_mutex.clone();
171 if !acme_on_demand_configs.is_empty() {
172 let cancel_token_clone = cancel_token.clone();
173 let cancelled_future = async move {
174 if let Some(token) = cancel_token_clone {
175 token.cancelled().await
176 } else {
177 futures_util::future::pending().await
178 }
179 };
180
181 tokio::spawn(async move {
183 tokio::select! {
184 biased;
185
186 _ = cancelled_future => {},
187 _ = background_on_demand_acme_task(
188 existing_combinations,
189 acme_on_demand_rx,
190 on_demand_tls_ask_endpoint,
191 on_demand_tls_ask_endpoint_verify,
192 acme_logger_clone,
193 crypto_provider,
194 acme_configs_mutex_clone,
195 acme_on_demand_configs,
196 memory_acme_account_cache_data,
197 prevent_file_race_conditions_sem,
198 ) => {}
199 }
200 });
201 }
202
203 let mut cancelled_future = Box::pin(async move {
204 if let Some(token) = cancel_token {
205 token.cancelled().await
206 } else {
207 futures_util::future::pending().await
208 }
209 });
210
211 loop {
212 for acme_config in &mut *(tokio::select! {
213 biased;
214 _ = &mut cancelled_future => {
215 return;
216 },
217 result = acme_configs_mutex.lock() => result
218 }) {
219 if let Err(acme_error) = tokio::select! {
220 biased;
221 _ = &mut cancelled_future => {
222 return;
223 },
224 result = provision_certificate(acme_config, &acme_logger) => result
225 } {
226 acme_logger
227 .log(&format!("Error while obtaining a TLS certificate: {acme_error}"))
228 .await
229 }
230 }
231 tokio::time::sleep(Duration::from_secs(10)).await;
232 }
233}
234
235#[allow(clippy::too_many_arguments)]
237#[inline]
238pub async fn background_on_demand_acme_task(
239 existing_combinations: HashSet<(String, u16)>,
240 acme_on_demand_rx: async_channel::Receiver<(String, u16)>,
241 on_demand_tls_ask_endpoint: Option<hyper::Uri>,
242 on_demand_tls_ask_endpoint_verify: bool,
243 acme_logger: Arc<ErrorLogger>,
244 crypto_provider: Arc<CryptoProvider>,
245 acme_configs_mutex: Arc<tokio::sync::Mutex<Vec<AcmeConfig>>>,
246 acme_on_demand_configs: Vec<AcmeOnDemandConfig>,
247 memory_acme_account_cache_data: Arc<RwLock<HashMap<String, Vec<u8>>>>,
248 prevent_file_race_conditions_sem: Arc<tokio::sync::Semaphore>,
249) {
250 let acme_on_demand_configs = Arc::new(acme_on_demand_configs);
251 let mut existing_combinations = existing_combinations;
252 while let Ok(received_data) = acme_on_demand_rx.recv().await {
253 let on_demand_tls_ask_endpoint = on_demand_tls_ask_endpoint.clone();
254 if let Some(on_demand_tls_ask_endpoint) = on_demand_tls_ask_endpoint {
255 let mut url_parts = on_demand_tls_ask_endpoint.into_parts();
256 let path_and_query_str = if let Some(path_and_query) = url_parts.path_and_query {
257 let query = path_and_query.query();
258 let query = if let Some(query) = query {
259 format!("{}&domain={}", query, urlencoding::encode(&received_data.0))
260 } else {
261 format!("domain={}", urlencoding::encode(&received_data.0))
262 };
263 format!("{}?{}", path_and_query.path(), query)
264 } else {
265 format!("/?domain={}", urlencoding::encode(&received_data.0))
266 };
267 url_parts.path_and_query = Some(match path_and_query_str.parse() {
268 Ok(parsed) => parsed,
269 Err(err) => {
270 acme_logger
271 .log(&format!(
272 "Error while formatting the URL for on-demand TLS request: {err}"
273 ))
274 .await;
275 continue;
276 }
277 });
278 let endpoint_url = match hyper::Uri::from_parts(url_parts) {
279 Ok(parsed) => parsed,
280 Err(err) => {
281 acme_logger
282 .log(&format!(
283 "Error while formatting the URL for on-demand TLS request: {err}"
284 ))
285 .await;
286 continue;
287 }
288 };
289 let crypto_provider = crypto_provider.clone();
290 let ask_closure = async {
291 let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
292 .build::<_, http_body_util::Empty<hyper::body::Bytes>>(
293 hyper_rustls::HttpsConnectorBuilder::new()
294 .with_tls_config(build_raw_rustls_client_config(
295 !on_demand_tls_ask_endpoint_verify,
296 crypto_provider,
297 )?)
298 .https_or_http()
299 .enable_http1()
300 .enable_http2()
301 .build(),
302 );
303 let request = hyper::Request::builder()
304 .method(hyper::Method::GET)
305 .uri(endpoint_url)
306 .body(http_body_util::Empty::<hyper::body::Bytes>::new())?;
307 let response = client.request(request).await?;
308
309 Ok::<_, Box<dyn Error + Send + Sync>>(response.status().is_success())
310 };
311 match ask_closure.await {
312 Ok(true) => (),
313 Ok(false) => {
314 acme_logger
315 .log(&format!(
316 "The TLS certificate cannot be issued for \"{}\" hostname",
317 &received_data.0
318 ))
319 .await;
320 continue;
321 }
322 Err(err) => {
323 acme_logger
324 .log(&format!(
325 "Error while determining if the TLS certificate can be issued for \"{}\" hostname: {err}",
326 &received_data.0
327 ))
328 .await;
329 continue;
330 }
331 }
332 }
333 if existing_combinations.contains(&received_data) {
334 continue;
335 } else {
336 existing_combinations.insert(received_data.clone());
337 }
338 let (sni_hostname, port) = received_data;
339 let acme_configs_mutex = acme_configs_mutex.clone();
340 let acme_on_demand_configs = acme_on_demand_configs.clone();
341 let memory_acme_account_cache_data = memory_acme_account_cache_data.clone();
342 let prevent_file_race_conditions_sem = prevent_file_race_conditions_sem.clone();
343 tokio::spawn(async move {
344 for acme_on_demand_config in acme_on_demand_configs.iter() {
345 if match_hostname(acme_on_demand_config.sni_hostname.as_deref(), Some(&sni_hostname))
346 && acme_on_demand_config.port == port
347 {
348 let sem_guard = prevent_file_race_conditions_sem.acquire().await;
349 add_domain_to_cache(acme_on_demand_config, &sni_hostname)
350 .await
351 .unwrap_or_default();
352 drop(sem_guard);
353
354 acme_configs_mutex.lock().await.push(
355 convert_on_demand_config(
356 acme_on_demand_config,
357 sni_hostname.clone(),
358 memory_acme_account_cache_data,
359 )
360 .await,
361 );
362 break;
363 }
364 }
365 });
366 }
367}