Skip to main content

ferron/
acme.rs

1use std::{
2  collections::HashMap,
3  error::Error,
4  future::Future,
5  net::IpAddr,
6  ops::{Deref, Sub},
7  path::PathBuf,
8  pin::Pin,
9  sync::Arc,
10  time::{Duration, SystemTime},
11};
12
13use base64::Engine;
14use bytes::Bytes;
15use hyper::Request;
16use hyper_util::client::legacy::Client as HyperClient;
17use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
18use instant_acme::{
19  Account, AccountCredentials, AuthorizationStatus, BodyWrapper, BytesResponse, CertificateIdentifier, ChallengeType,
20  ExternalAccountKey, HttpClient, Identifier, NewAccount, NewOrder, OrderStatus, RenewalInfo, RetryPolicy,
21};
22use rcgen::{CertificateParams, CustomExtension, KeyPair};
23use rustls::{
24  crypto::CryptoProvider,
25  server::{ClientHello, ResolvesServerCert},
26  sign::CertifiedKey,
27  ClientConfig,
28};
29use rustls_pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer};
30use serde::{Deserialize, Serialize};
31use tokio::{io::AsyncWriteExt, sync::RwLock, time::Instant};
32use x509_parser::prelude::{FromDer, X509Certificate};
33use xxhash_rust::xxh3::xxh3_128;
34
35use crate::util::SniResolverLock;
36use ferron_common::dns::DnsProvider;
37use ferron_common::logging::ErrorLogger;
38
39pub const ACME_TLS_ALPN_NAME: &[u8] = b"acme-tls/1";
40const SECONDS_BEFORE_RENEWAL: u64 = 86400; // 1 day before expiration
41
42pub type TlsAlpn01DataLock = Arc<RwLock<Option<(Arc<CertifiedKey>, String)>>>;
43pub type Http01DataLock = Arc<RwLock<Option<(String, String)>>>;
44
45/// Represents the configuration for the ACME client.
46pub struct AcmeConfig {
47  /// The Rustls client configuration to use for ACME communication.
48  pub rustls_client_config: ClientConfig,
49  /// The domains for which to request certificates.
50  pub domains: Vec<String>,
51  /// The type of challenge to use for ACME certificate issuance.
52  pub challenge_type: ChallengeType,
53  /// The contact information for the ACME account.
54  pub contact: Vec<String>,
55  /// The directory URL for the ACME server.
56  pub directory: String,
57  /// The optional EAB key
58  pub eab_key: Option<Arc<ExternalAccountKey>>,
59  /// The optional ACME profile name
60  pub profile: Option<String>,
61  /// The cache for storing ACME account information.
62  pub account_cache: AcmeCache,
63  /// The cache for storing ACME certificate information.
64  pub certificate_cache: AcmeCache,
65  /// The lock for managing the certified key.
66  pub certified_key_lock: Arc<RwLock<Option<Arc<CertifiedKey>>>>,
67  /// The lock for managing the TLS-ALPN-01 data.
68  pub tls_alpn_01_data_lock: TlsAlpn01DataLock,
69  /// The lock for managing the HTTP-01 data.
70  pub http_01_data_lock: Http01DataLock,
71  /// The ACME DNS provider.
72  pub dns_provider: Option<Arc<dyn DnsProvider + Send + Sync>>,
73  /// The certificate renewal information.
74  pub renewal_info: Option<(RenewalInfo, Instant)>,
75  /// The ACME account information
76  pub account: Option<Account>,
77  /// The paths to TLS certificate and private key files to save the obtained certificate and private key.
78  pub save_paths: Option<(PathBuf, PathBuf)>,
79  /// The command to execute after certificates and private key are obtained,
80  /// with environment variables `FERRON_ACME_DOMAIN`, `FERRON_ACME_CERT_PATH` and `FERRON_ACME_KEY_PATH` set.
81  pub post_obtain_command: Option<String>,
82}
83
84/// Represents the type of cache to use for storing ACME data.
85pub enum AcmeCache {
86  /// Use an in-memory cache.
87  Memory(Arc<RwLock<HashMap<String, Vec<u8>>>>),
88  /// Use a file-based cache.
89  File(PathBuf),
90}
91
92impl AcmeCache {
93  /// Gets data from the cache.
94  async fn get(&self, key: &str) -> Option<Vec<u8>> {
95    match self {
96      AcmeCache::Memory(cache) => cache.read().await.get(key).cloned(),
97      AcmeCache::File(path) => tokio::fs::read(path.join(key)).await.ok(),
98    }
99  }
100
101  /// Sets data in the cache.
102  async fn set(&self, key: &str, value: Vec<u8>) -> Result<(), std::io::Error> {
103    match self {
104      AcmeCache::Memory(cache) => {
105        cache.write().await.insert(key.to_string(), value);
106        Ok(())
107      }
108      AcmeCache::File(path) => {
109        tokio::fs::create_dir_all(path).await.unwrap_or_default();
110        let mut open_options = tokio::fs::OpenOptions::new();
111        open_options.write(true).create(true).truncate(true);
112
113        #[cfg(unix)]
114        open_options.mode(0o600); // Don't allow others to read or write
115
116        let mut file = open_options.open(path.join(key)).await?;
117        file.write_all(&value).await?;
118        file.flush().await.unwrap_or_default();
119
120        Ok(())
121      }
122    }
123  }
124
125  /// Removes data from the cache.
126  async fn remove(&self, key: &str) {
127    match self {
128      AcmeCache::Memory(cache) => {
129        cache.write().await.remove(key);
130      }
131      AcmeCache::File(path) => {
132        let _ = tokio::fs::remove_file(path.join(key)).await;
133      }
134    }
135  }
136}
137
138#[derive(Serialize, Deserialize)]
139struct CertificateCacheData {
140  certificate_chain_pem: String,
141  private_key_pem: String,
142}
143
144/// Represents the on-demand configuration for the ACME client.
145pub struct AcmeOnDemandConfig {
146  /// The Rustls client configuration to use for ACME communication.
147  pub rustls_client_config: ClientConfig,
148  /// The type of challenge to use for ACME certificate issuance.
149  pub challenge_type: ChallengeType,
150  /// The contact information for the ACME account.
151  pub contact: Vec<String>,
152  /// The directory URL for the ACME server.
153  pub directory: String,
154  /// The optional EAB key
155  pub eab_key: Option<Arc<ExternalAccountKey>>,
156  /// The optional ACME profile name
157  pub profile: Option<String>,
158  /// The path to the cache directory for storing ACME information.
159  pub cache_path: Option<PathBuf>,
160  /// The lock for managing the SNI resolver.
161  pub sni_resolver_lock: SniResolverLock,
162  /// The lock for managing the TLS-ALPN-01 resolver.
163  pub tls_alpn_01_resolver_lock: Arc<RwLock<Vec<TlsAlpn01DataLock>>>,
164  /// The lock for managing the HTTP-01 resolver.
165  pub http_01_resolver_lock: Arc<RwLock<Vec<Http01DataLock>>>,
166  /// The ACME DNS provider.
167  pub dns_provider: Option<Arc<dyn DnsProvider + Send + Sync>>,
168  /// The SNI hostname.
169  pub sni_hostname: Option<String>,
170  /// The port to use for ACME communication.
171  pub port: u16,
172}
173
174/// Checks if the TLS certificate is valid
175fn check_certificate_validity(
176  certificate: &CertificateDer,
177  renewal_info: Option<&RenewalInfo>,
178) -> Result<bool, Box<dyn Error + Send + Sync>> {
179  if let Some(renewal_info) = renewal_info {
180    return Ok(SystemTime::now() < renewal_info.suggested_window.start);
181  }
182  let (_, x509_certificate) = X509Certificate::from_der(certificate)?;
183  let validity = x509_certificate.validity();
184  if let Some(time_to_expiration) = validity.time_to_expiration() {
185    let time_before_expiration = if let Some(valid_duration) = validity.not_after.sub(validity.not_before) {
186      (valid_duration.whole_seconds().unsigned_abs() / 2).min(SECONDS_BEFORE_RENEWAL)
187    } else {
188      SECONDS_BEFORE_RENEWAL
189    };
190    if time_to_expiration >= Duration::from_secs(time_before_expiration) {
191      return Ok(true);
192    }
193  }
194  Ok(false)
195}
196
197/// Determines the account cache key
198fn get_account_cache_key(config: &AcmeConfig) -> String {
199  format!(
200    "account_{}",
201    base64::engine::general_purpose::URL_SAFE_NO_PAD
202      .encode(xxh3_128(format!("{};{}", &config.contact.join(","), &config.directory).as_bytes()).to_be_bytes())
203  )
204}
205
206/// Determines the certificate cache key
207fn get_certificate_cache_key(config: &AcmeConfig) -> String {
208  let mut domains = config.domains.clone();
209  domains.sort_unstable();
210  let domains_joined = domains.join(",");
211  format!(
212    "certificate_{}",
213    base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(
214      xxh3_128(
215        format!(
216          "{}{}",
217          domains_joined,
218          config.profile.as_ref().map_or("".to_string(), |p| format!(";{p}"))
219        )
220        .as_bytes()
221      )
222      .to_be_bytes()
223    )
224  )
225}
226
227/// Determines the account cache key
228fn get_hostname_cache_key(config: &AcmeOnDemandConfig) -> String {
229  format!(
230    "hostname_{}",
231    base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(
232      xxh3_128(
233        format!(
234          "{}{}",
235          &config.port,
236          config.sni_hostname.as_ref().map_or("".to_string(), |h| format!(";{h}"))
237        )
238        .as_bytes()
239      )
240      .to_be_bytes()
241    )
242  )
243}
244
245/// Saves the obtained certificate and private key to files if the save paths are configured, and executes the post-obtain command if configured.
246async fn post_process_obtained_certificate(
247  config: &AcmeConfig,
248  certificate_pem: &str,
249  private_key_pem: &str,
250) -> Result<(), Box<dyn Error + Send + Sync>> {
251  if let Some((cert_path, key_path)) = &config.save_paths {
252    tokio::fs::write(cert_path, certificate_pem).await?;
253
254    let mut open_options = tokio::fs::OpenOptions::new();
255    open_options.write(true).create(true).truncate(true);
256
257    #[cfg(unix)]
258    open_options.mode(0o600); // Don't allow others to read or write the private key
259
260    let mut file = open_options.open(key_path).await?;
261    file.write_all(private_key_pem.as_bytes()).await?;
262    file.flush().await.unwrap_or_default();
263
264    if let Some(command) = &config.post_obtain_command {
265      let mut command_shlex = shlex::Shlex::new(command);
266      let Some(command) = command_shlex.next() else {
267        Err(anyhow::anyhow!("Invalid post-obtain command"))?
268      };
269      let mut command = tokio::process::Command::new(command);
270      for arg in command_shlex {
271        command.arg(arg);
272      }
273      command
274        .env("FERRON_ACME_DOMAIN", config.domains.join(","))
275        .env("FERRON_ACME_CERT_PATH", cert_path)
276        .env("FERRON_ACME_KEY_PATH", key_path)
277        .stdin(std::process::Stdio::null())
278        .stdout(std::process::Stdio::null())
279        .stderr(std::process::Stdio::null())
280        .spawn()?;
281    }
282  }
283
284  Ok(())
285}
286
287/// Checks if the TLS certificate (cached or live) is valid. If cached certificate is valid, installs the cached certificate
288pub async fn check_certificate_validity_or_install_cached(
289  config: &mut AcmeConfig,
290  acme_account: Option<&Account>,
291) -> Result<bool, Box<dyn Error + Send + Sync>> {
292  if let Some(certified_key) = config.certified_key_lock.read().await.as_deref() {
293    if let Some(certificate) = certified_key.cert.first() {
294      if let Some(acme_account) = acme_account {
295        if config
296          .renewal_info
297          .as_ref()
298          .is_none_or(|v| v.1.elapsed() > Duration::ZERO)
299        {
300          if let Ok(certificate_id) = CertificateIdentifier::try_from(certificate) {
301            if let Ok(renewal_info) = acme_account.renewal_info(&certificate_id).await {
302              let mut renewal_instant = Instant::now();
303              renewal_instant += renewal_info.1;
304              config.renewal_info = Some((renewal_info.0, renewal_instant));
305            }
306          }
307        }
308      }
309      if check_certificate_validity(certificate, config.renewal_info.as_ref().map(|i| &i.0))? {
310        return Ok(true);
311      }
312    }
313  }
314
315  let certificate_cache_key = get_certificate_cache_key(config);
316
317  if let Some(serialized_certificate_cache_data) = config.certificate_cache.get(&certificate_cache_key).await {
318    if let Ok(certificate_data) = serde_json::from_slice::<CertificateCacheData>(&serialized_certificate_cache_data) {
319      // Corrupted certificates would be skipped
320      if let Ok(certs) =
321        CertificateDer::pem_slice_iter(certificate_data.certificate_chain_pem.as_bytes()).collect::<Result<Vec<_>, _>>()
322      {
323        if let Some(certificate) = certs.first() {
324          if let Some(acme_account) = acme_account {
325            if config
326              .renewal_info
327              .as_ref()
328              .is_none_or(|v| v.1.elapsed() > Duration::ZERO)
329            {
330              if let Ok(certificate_id) = CertificateIdentifier::try_from(certificate) {
331                if let Ok(renewal_info) = acme_account.renewal_info(&certificate_id).await {
332                  let mut renewal_instant = Instant::now();
333                  renewal_instant += renewal_info.1;
334                  config.renewal_info = Some((renewal_info.0, renewal_instant));
335                }
336              }
337            }
338          }
339          if check_certificate_validity(certificate, config.renewal_info.as_ref().map(|i| &i.0))? {
340            // Corrupted private key would be skipped
341            if let Ok(private_key) = PrivateKeyDer::from_pem_slice(certificate_data.private_key_pem.as_bytes()) {
342              let signing_key = CryptoProvider::get_default()
343                .ok_or(anyhow::anyhow!("Cannot get default crypto provider"))?
344                .key_provider
345                .load_private_key(private_key)?;
346
347              *config.certified_key_lock.write().await = Some(Arc::new(CertifiedKey::new(certs, signing_key)));
348
349              let _ = post_process_obtained_certificate(
350                config,
351                &certificate_data.certificate_chain_pem,
352                &certificate_data.private_key_pem,
353              )
354              .await;
355
356              return Ok(true);
357            }
358          }
359        }
360      }
361    }
362  }
363
364  Ok(false)
365}
366
367/// Provisions TLS certificates using the ACME protocol.
368pub async fn provision_certificate(
369  config: &mut AcmeConfig,
370  error_logger: &ErrorLogger,
371) -> Result<(), Box<dyn Error + Send + Sync>> {
372  let account_cache_key = get_account_cache_key(config);
373  let certificate_cache_key = get_certificate_cache_key(config);
374  let mut had_cache_error = false;
375
376  let acme_account = if let Some(acme_account) = config.account.take() {
377    acme_account
378  } else {
379    let acme_account_builder =
380      Account::builder_with_http(Box::new(HttpsClientForAcme::new(config.rustls_client_config.clone())));
381
382    if let Some(account_credentials) = config
383      .account_cache
384      .get(&account_cache_key)
385      .await
386      .and_then(|c| serde_json::from_slice::<AccountCredentials>(&c).ok())
387    {
388      acme_account_builder.from_credentials(account_credentials).await?
389    } else {
390      let (account, account_credentials) = acme_account_builder
391        .create(
392          &NewAccount {
393            contact: config.contact.iter().map(|s| s.deref()).collect::<Vec<_>>().as_slice(),
394            terms_of_service_agreed: true,
395            only_return_existing: false,
396          },
397          config.directory.clone(),
398          config.eab_key.as_deref(),
399        )
400        .await?;
401
402      if let Err(err) = config
403        .account_cache
404        .set(&account_cache_key, serde_json::to_vec(&account_credentials)?)
405        .await
406      {
407        if !had_cache_error {
408          error_logger
409            .log(&format!(
410              "Failed to access the ACME cache: {}. Ferron can't use ACME caching",
411              err
412            ))
413            .await;
414          had_cache_error = true;
415        }
416      }
417
418      account
419    }
420  };
421
422  if check_certificate_validity_or_install_cached(config, Some(&acme_account)).await? {
423    // Certificate is still valid, no need to renew
424    config.account.replace(acme_account);
425    return Ok(());
426  }
427
428  let acme_identifiers_vec = config
429    .domains
430    .iter()
431    .map(|s| {
432      if let Ok(ip) = s.parse::<IpAddr>() {
433        Identifier::Ip(ip)
434      } else {
435        Identifier::Dns(s.to_string())
436      }
437    })
438    .collect::<Vec<_>>();
439
440  let mut acme_new_order = NewOrder::new(&acme_identifiers_vec);
441  if let Some(profile) = &config.profile {
442    acme_new_order = acme_new_order.profile(profile);
443  }
444
445  let mut acme_order = match acme_account.new_order(&acme_new_order).await {
446    Ok(order) => order,
447    Err(instant_acme::Error::Api(problem)) => {
448      if problem.r#type.as_deref() == Some("urn:ietf:params:acme:error:accountDoesNotExist") {
449        // Remove non-existent account from the cache
450        config.account_cache.remove(&account_cache_key).await;
451      }
452      Err(instant_acme::Error::Api(problem))?
453    }
454    Err(err) => Err(err)?,
455  };
456  let mut dns_01_identifiers = Vec::new();
457  let mut acme_authorizations = acme_order.authorizations();
458  while let Some(acme_authorization) = acme_authorizations.next().await {
459    let mut acme_authorization = acme_authorization?;
460    match acme_authorization.status {
461      AuthorizationStatus::Pending => {}
462      AuthorizationStatus::Valid => continue,
463      _ => Err(anyhow::anyhow!("Invalid ACME authorization status"))?,
464    }
465
466    let mut challenge = acme_authorization
467      .challenge(config.challenge_type.clone())
468      .ok_or(anyhow::anyhow!(
469        "The ACME server doesn't support the requested challenge type"
470      ))?;
471
472    let identifier = match challenge.identifier().identifier {
473      Identifier::Dns(identifier) => identifier.to_string(),
474      Identifier::Ip(ip) => ip.to_string(),
475      _ => Err(anyhow::anyhow!("Unsupported ACME identifier type",))?,
476    };
477
478    let key_authorization = challenge.key_authorization();
479    match config.challenge_type {
480      ChallengeType::TlsAlpn01 => {
481        let mut params = CertificateParams::new(vec![identifier.clone()])?;
482        params.custom_extensions.push(CustomExtension::new_acme_identifier(
483          key_authorization.digest().as_ref(),
484        ));
485        let key_pair = KeyPair::generate()?;
486        let certificate = params.self_signed(&key_pair)?;
487        let private_key = PrivateKeyDer::try_from(key_pair.serialize_der())?;
488
489        let signing_key = CryptoProvider::get_default()
490          .ok_or(anyhow::anyhow!("Cannot get default crypto provider"))?
491          .key_provider
492          .load_private_key(private_key)?;
493
494        *config.tls_alpn_01_data_lock.write().await = Some((
495          Arc::new(CertifiedKey::new(vec![certificate.der().to_owned()], signing_key)),
496          identifier.clone(),
497        ));
498      }
499      ChallengeType::Http01 => {
500        let key_auth_value = key_authorization.as_str();
501        *config.http_01_data_lock.write().await = Some((challenge.token.clone(), key_auth_value.to_string()));
502      }
503      ChallengeType::Dns01 => {
504        if let Some(dns_provider) = &config.dns_provider {
505          dns_provider
506            .remove_acme_txt_record(&identifier)
507            .await
508            .unwrap_or_default();
509          dns_provider
510            .set_acme_txt_record(&identifier, &key_authorization.dns_value())
511            .await?;
512          // Wait for DNS propagation
513          tokio::time::sleep(Duration::from_secs(60)).await;
514          dns_01_identifiers.push(identifier.clone());
515        } else {
516          Err(anyhow::anyhow!("No DNS provider configured."))?;
517        }
518      }
519      _ => (),
520    }
521
522    challenge.set_ready().await?;
523  }
524
525  let acme_order_status = acme_order.poll_ready(&RetryPolicy::default()).await?;
526  match acme_order_status {
527    OrderStatus::Ready => (), // It's alright!
528    OrderStatus::Invalid => Err(anyhow::anyhow!("ACME order is invalid"))?,
529    _ => Err(anyhow::anyhow!("ACME order is not ready"))?,
530  }
531
532  let finalize_closure = async {
533    let private_key_pem = acme_order.finalize().await?;
534    let certificate_chain_pem = acme_order.poll_certificate(&RetryPolicy::default()).await?;
535
536    if let Err(err) = post_process_obtained_certificate(config, &certificate_chain_pem, &private_key_pem).await {
537      error_logger
538        .log(&format!(
539          "Failed to save or post-process the obtained certificate: {}",
540          err
541        ))
542        .await;
543    }
544
545    let certificate_cache_data = CertificateCacheData {
546      certificate_chain_pem: certificate_chain_pem.clone(),
547      private_key_pem: private_key_pem.clone(),
548    };
549
550    if let Err(err) = config
551      .certificate_cache
552      .set(&certificate_cache_key, serde_json::to_vec(&certificate_cache_data)?)
553      .await
554    {
555      if !had_cache_error {
556        error_logger
557          .log(&format!(
558            "Failed to access the ACME cache: {}. Ferron can't use ACME caching",
559            err
560          ))
561          .await;
562        had_cache_error = true;
563      }
564    }
565
566    let certs = CertificateDer::pem_slice_iter(certificate_chain_pem.as_bytes())
567      .collect::<Result<Vec<_>, _>>()
568      .map_err(|e| match e {
569        rustls_pki_types::pem::Error::Io(err) => err,
570        err => std::io::Error::other(err),
571      })?;
572    let private_key = (match PrivateKeyDer::from_pem_slice(private_key_pem.as_bytes()) {
573      Ok(private_key) => Ok(private_key),
574      Err(rustls_pki_types::pem::Error::Io(err)) => Err(err),
575      Err(err) => Err(std::io::Error::other(err)),
576    })?;
577
578    let signing_key = CryptoProvider::get_default()
579      .ok_or(anyhow::anyhow!("Cannot get default crypto provider"))?
580      .key_provider
581      .load_private_key(private_key)?;
582
583    config.account.replace(acme_account);
584
585    *config.certified_key_lock.write().await = Some(Arc::new(CertifiedKey::new(certs, signing_key)));
586
587    Ok::<_, Box<dyn Error + Send + Sync>>(())
588  };
589
590  let result = finalize_closure.await;
591
592  // Cleanup
593  match config.challenge_type {
594    ChallengeType::TlsAlpn01 => {
595      *config.tls_alpn_01_data_lock.write().await = None;
596    }
597    ChallengeType::Http01 => {
598      *config.http_01_data_lock.write().await = None;
599    }
600    ChallengeType::Dns01 => {
601      if let Some(dns_provider) = &config.dns_provider {
602        for identifier in dns_01_identifiers {
603          dns_provider
604            .remove_acme_txt_record(&identifier)
605            .await
606            .unwrap_or_default();
607        }
608      }
609    }
610    _ => {}
611  };
612
613  result?;
614
615  Ok(())
616}
617
618/// Obtains the list of domains for which `AcmeOnDemandConfig` was converted into `AcmeConfig` from cache.
619pub async fn get_cached_domains(config: &AcmeOnDemandConfig) -> Vec<String> {
620  if let Some(pathbuf) = config.cache_path.clone() {
621    let hostname_cache_key = get_hostname_cache_key(config);
622    let hostname_cache = AcmeCache::File(pathbuf);
623    let cache_data = hostname_cache.get(&hostname_cache_key).await;
624    if let Some(data) = cache_data {
625      serde_json::from_slice(&data).unwrap_or_default()
626    } else {
627      Vec::new()
628    }
629  } else {
630    Vec::new()
631  }
632}
633
634/// Adds the domain to the cache.
635pub async fn add_domain_to_cache(
636  config: &AcmeOnDemandConfig,
637  domain: &str,
638) -> Result<(), Box<dyn Error + Send + Sync>> {
639  if let Some(pathbuf) = config.cache_path.clone() {
640    let hostname_cache_key = get_hostname_cache_key(config);
641    let hostname_cache = AcmeCache::File(pathbuf);
642    let mut cached_domains = get_cached_domains(config).await;
643    cached_domains.push(domain.to_string());
644    let data = serde_json::to_vec(&cached_domains)?;
645    hostname_cache.set(&hostname_cache_key, data).await?;
646  }
647  Ok(())
648}
649
650/// Converts a `AcmeOnDemandConfig` into an `AcmeConfig`
651pub async fn convert_on_demand_config(
652  config: &AcmeOnDemandConfig,
653  sni_hostname: String,
654  memory_acme_account_cache_data: Arc<RwLock<HashMap<String, Vec<u8>>>>,
655) -> AcmeConfig {
656  let (account_cache_path, cert_cache_path) = if let Some(mut pathbuf) = config.cache_path.clone() {
657    let base_pathbuf = pathbuf.clone();
658    let append_hash = base64::engine::general_purpose::URL_SAFE_NO_PAD
659      .encode(xxh3_128(format!("{}-{sni_hostname}", config.port).as_bytes()).to_be_bytes());
660    pathbuf.push(append_hash);
661    (Some(base_pathbuf), Some(pathbuf))
662  } else {
663    (None, None)
664  };
665
666  let certified_key_lock = Arc::new(tokio::sync::RwLock::new(None));
667  let tls_alpn_01_data_lock = Arc::new(tokio::sync::RwLock::new(None));
668  let http_01_data_lock = Arc::new(tokio::sync::RwLock::new(None));
669
670  // Insert new locked data
671  config.sni_resolver_lock.write().await.insert(
672    sni_hostname.clone(),
673    Arc::new(AcmeResolver::new(certified_key_lock.clone())),
674  );
675  match config.challenge_type {
676    ChallengeType::TlsAlpn01 => {
677      config
678        .tls_alpn_01_resolver_lock
679        .write()
680        .await
681        .push(tls_alpn_01_data_lock.clone());
682    }
683    ChallengeType::Http01 => {
684      config
685        .http_01_resolver_lock
686        .write()
687        .await
688        .push(http_01_data_lock.clone());
689    }
690    _ => (),
691  };
692
693  AcmeConfig {
694    rustls_client_config: config.rustls_client_config.clone(),
695    domains: vec![sni_hostname],
696    challenge_type: config.challenge_type.clone(),
697    contact: config.contact.clone(),
698    directory: config.directory.clone(),
699    eab_key: config.eab_key.clone(),
700    profile: config.profile.clone(),
701    account_cache: if let Some(account_cache_path) = account_cache_path {
702      AcmeCache::File(account_cache_path)
703    } else {
704      AcmeCache::Memory(memory_acme_account_cache_data.clone())
705    },
706    certificate_cache: if let Some(cert_cache_path) = cert_cache_path {
707      AcmeCache::File(cert_cache_path)
708    } else {
709      AcmeCache::Memory(Arc::new(tokio::sync::RwLock::new(HashMap::new())))
710    },
711    certified_key_lock: certified_key_lock.clone(),
712    tls_alpn_01_data_lock: tls_alpn_01_data_lock.clone(),
713    http_01_data_lock: http_01_data_lock.clone(),
714    dns_provider: config.dns_provider.clone(),
715    renewal_info: None,
716    account: None,
717    save_paths: None,
718    post_obtain_command: None,
719  }
720}
721
722/// An ACME resolver resolving one certified key
723#[derive(Debug)]
724pub struct AcmeResolver {
725  certified_key_lock: Arc<RwLock<Option<Arc<CertifiedKey>>>>,
726}
727
728impl AcmeResolver {
729  /// Creates an ACME resolver
730  pub fn new(certified_key_lock: Arc<RwLock<Option<Arc<CertifiedKey>>>>) -> Self {
731    Self { certified_key_lock }
732  }
733}
734
735impl ResolvesServerCert for AcmeResolver {
736  fn resolve(&self, _client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
737    self.certified_key_lock.blocking_read().clone()
738  }
739}
740
741struct HttpsClientForAcme(HyperClient<hyper_rustls::HttpsConnector<HttpConnector>, BodyWrapper<Bytes>>);
742
743impl HttpsClientForAcme {
744  fn new(tls_config: ClientConfig) -> Self {
745    Self(
746      HyperClient::builder(TokioExecutor::new()).build(
747        hyper_rustls::HttpsConnectorBuilder::new()
748          .with_tls_config(tls_config)
749          .https_or_http()
750          .enable_http1()
751          .enable_http2()
752          .build(),
753      ),
754    )
755  }
756}
757
758impl HttpClient for HttpsClientForAcme {
759  fn request(
760    &self,
761    req: Request<BodyWrapper<Bytes>>,
762  ) -> Pin<Box<dyn Future<Output = Result<BytesResponse, instant_acme::Error>> + Send>> {
763    HttpClient::request(&self.0, req)
764  }
765}
766
767/// The TLS-ALPN-01 ACME challenge certificate resolver
768#[derive(Debug)]
769pub struct TlsAlpn01Resolver {
770  resolvers: Arc<tokio::sync::RwLock<Vec<TlsAlpn01DataLock>>>,
771}
772
773impl TlsAlpn01Resolver {
774  /// Creates a TLS-ALPN-01 resolver
775  #[allow(dead_code)]
776  pub fn new() -> Self {
777    Self {
778      resolvers: Arc::new(tokio::sync::RwLock::new(Vec::new())),
779    }
780  }
781
782  /// Creates a TLS-ALPN-01 resolver with provided resolver list lock
783  pub fn with_resolvers(resolvers: Arc<tokio::sync::RwLock<Vec<TlsAlpn01DataLock>>>) -> Self {
784    Self { resolvers }
785  }
786
787  /// Loads a certificate resolver lock
788  pub fn load_resolver(&self, resolver: TlsAlpn01DataLock) {
789    self.resolvers.blocking_write().push(resolver);
790  }
791}
792
793impl ResolvesServerCert for TlsAlpn01Resolver {
794  fn resolve(&self, client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
795    let hostname = client_hello.server_name().map(|hn| hn.strip_suffix('.').unwrap_or(hn));
796
797    // If blocking_read() method is used when only Tokio is used, the program would panic on resolving a TLS certificate.
798    #[cfg(any(feature = "runtime-vibeio", feature = "runtime-monoio"))]
799    let resolver_locks = self.resolvers.blocking_read();
800    #[cfg(feature = "runtime-tokio")]
801    let resolver_locks = futures_executor::block_on(async { self.resolvers.read().await });
802
803    for resolver_lock in &*resolver_locks {
804      if let Some(hostname) = hostname {
805        #[cfg(any(feature = "runtime-vibeio", feature = "runtime-monoio"))]
806        let resolver_data = resolver_lock.blocking_read().clone();
807        #[cfg(feature = "runtime-tokio")]
808        let resolver_data = futures_executor::block_on(async { resolver_lock.read().await }).clone();
809        if let Some(resolver_data) = resolver_data {
810          let (cert, host) = resolver_data;
811          if host.parse::<IpAddr>().is_ok() || host == hostname {
812            return Some(cert);
813          }
814        }
815      }
816    }
817    None
818  }
819}