1use std::collections::{HashMap, HashSet};
8use std::fs;
9use std::path::Path;
10use std::pin::Pin;
11use std::sync::Arc;
12use std::time::Duration;
13
14use arc_swap::ArcSwap;
15use certon::{
16 AcmeIssuer, CertResolver, Certificate, Config as CertAutoConfig, FileStorage, OnDemandConfig,
17 Storage,
18};
19use rustls::RootCertStore;
20use rustls::server::ResolvesServerCert;
21use rustls::sign::CertifiedKey;
22use tokio::task::JoinHandle;
23use tokio_rustls::TlsAcceptor;
24use tracing::{debug, error, info, warn};
25
26use crate::ProxyError;
27use crate::config::{
28 AcmeConfig, AppConfig, CertAuthority, ChallengeType, ClientAuthConfig, DnsProviderConfig,
29 OnDemandTlsConfig, SiteTlsConfig, TlsConfig,
30};
31
32pub struct TlsManager {
56 certon_config: Option<Arc<CertAutoConfig>>,
58
59 resolver: Arc<CompositeResolver>,
61
62 server_config: ArcSwap<rustls::ServerConfig>,
64
65 maintenance_handle: Option<JoinHandle<()>>,
67
68 challenge_map: Arc<tokio::sync::RwLock<HashMap<String, String>>>,
71}
72
73impl TlsManager {
74 pub async fn build(config: &AppConfig) -> Result<Self, ProxyError> {
89 let challenge_map: Arc<tokio::sync::RwLock<HashMap<String, String>>> =
90 Arc::new(tokio::sync::RwLock::new(HashMap::new()));
91
92 let mut manual_certs: HashMap<String, Arc<CertifiedKey>> = HashMap::new();
94 let mut acme_domains: Vec<String> = Vec::new();
95
96 for site in &config.sites {
97 if let Some(ref site_tls) = site.tls {
98 match load_manual_cert(site_tls) {
99 Ok(certified_key) => {
100 info!(
101 host = %site.host,
102 cert = %site_tls.cert,
103 "loaded manual TLS certificate"
104 );
105 manual_certs.insert(site.host.clone(), certified_key);
106 }
107 Err(e) => {
108 error!(
109 host = %site.host,
110 cert = %site_tls.cert,
111 key = %site_tls.key,
112 error = %e,
113 "failed to load manual TLS certificate"
114 );
115 return Err(ProxyError::Internal(format!(
116 "failed to load TLS certificate for {}: {e}",
117 site.host
118 )));
119 }
120 }
121 } else if config.tls.is_some() {
122 acme_domains.push(site.host.clone());
124 }
125 }
126
127 let (certon_config, cert_resolver, maintenance_handle) =
129 if let Some(ref tls_config) = config.tls {
130 if let Some(ref acme_config) = tls_config.acme {
131 let (ca_config, resolver, handle) = setup_acme(
132 acme_config,
133 &acme_domains,
134 challenge_map.clone(),
135 tls_config.on_demand.as_ref(),
136 &config.sites,
137 )
138 .await?;
139 (Some(Arc::new(ca_config)), Some(resolver), Some(handle))
140 } else {
141 (None, None, None)
142 }
143 } else {
144 (None, None, None)
145 };
146
147 let composite = Arc::new(CompositeResolver {
149 manual_certs: tokio::sync::RwLock::new(manual_certs),
150 acme_resolver: cert_resolver,
151 });
152
153 let client_verifier = if let Some(ref tls_config) = config.tls {
155 if let Some(ref client_auth) = tls_config.client_auth {
156 Some(build_client_verifier(client_auth)?)
157 } else {
158 None
159 }
160 } else {
161 None
162 };
163
164 let rustls_config = build_server_config(
166 composite.clone(),
167 client_verifier.as_ref(),
168 config.tls.as_ref(),
169 );
170
171 if let Some(ref tls_config) = config.tls
173 && tls_config.ocsp_stapling
174 {
175 info!(
176 "OCSP stapling enabled (handled by certon for ACME certs; \
177 manual certs require AIA extension parsing)"
178 );
179 }
180
181 Ok(Self {
182 certon_config,
183 resolver: composite,
184 server_config: ArcSwap::from_pointee(rustls_config),
185 maintenance_handle,
186 challenge_map,
187 })
188 }
189
190 pub fn acceptor(&self) -> TlsAcceptor {
196 let config = self.server_config.load_full();
197 TlsAcceptor::from(config)
198 }
199
200 pub fn server_config(&self) -> Arc<rustls::ServerConfig> {
202 self.server_config.load_full()
203 }
204
205 pub fn challenge_map(&self) -> Arc<tokio::sync::RwLock<HashMap<String, String>>> {
210 self.challenge_map.clone()
211 }
212
213 pub async fn reload(&self, config: &AppConfig) -> Result<(), ProxyError> {
223 info!("reloading TLS configuration");
224
225 let mut new_manual: HashMap<String, Arc<CertifiedKey>> = HashMap::new();
227 let mut acme_domains: Vec<String> = Vec::new();
228
229 for site in &config.sites {
230 if let Some(ref site_tls) = site.tls {
231 match load_manual_cert(site_tls) {
232 Ok(certified_key) => {
233 info!(
234 host = %site.host,
235 cert = %site_tls.cert,
236 "reloaded manual TLS certificate"
237 );
238 new_manual.insert(site.host.clone(), certified_key);
239 }
240 Err(e) => {
241 error!(
242 host = %site.host,
243 error = %e,
244 "failed to reload manual TLS certificate"
245 );
246 return Err(ProxyError::Internal(format!(
247 "failed to reload TLS certificate for {}: {e}",
248 site.host
249 )));
250 }
251 }
252 } else if config.tls.is_some() {
253 acme_domains.push(site.host.clone());
254 }
255 }
256
257 {
259 let mut guard = self.resolver.manual_certs.write().await;
260 *guard = new_manual;
261 }
262
263 if !acme_domains.is_empty()
265 && let Some(ref ca_config) = self.certon_config
266 && let Err(e) = ca_config.manage_sync(&acme_domains).await
267 {
268 warn!(
269 error = %e,
270 "failed to manage new ACME domains during reload"
271 );
272 }
273
274 let client_verifier = if let Some(ref tls_config) = config.tls {
276 if let Some(ref client_auth) = tls_config.client_auth {
277 match build_client_verifier(client_auth) {
278 Ok(v) => Some(v),
279 Err(e) => {
280 error!(error = %e, "failed to rebuild client cert verifier during reload");
281 None
282 }
283 }
284 } else {
285 None
286 }
287 } else {
288 None
289 };
290
291 let new_config = build_server_config(
293 self.resolver.clone(),
294 client_verifier.as_ref(),
295 config.tls.as_ref(),
296 );
297 self.server_config.store(Arc::new(new_config));
298
299 info!("TLS configuration reloaded successfully");
300 Ok(())
301 }
302
303 pub fn stop_maintenance(&self) {
308 if let Some(ref ca_config) = self.certon_config {
309 ca_config.cache.stop();
310 info!("certon maintenance loop stopped");
311 }
312 }
313}
314
315impl Drop for TlsManager {
316 fn drop(&mut self) {
317 if let Some(ref ca_config) = self.certon_config {
319 ca_config.cache.stop();
320 }
321 if let Some(ref handle) = self.maintenance_handle {
323 handle.abort();
324 }
325 }
326}
327
328struct CompositeResolver {
339 manual_certs: tokio::sync::RwLock<HashMap<String, Arc<CertifiedKey>>>,
341 acme_resolver: Option<Arc<CertResolver>>,
343}
344
345impl std::fmt::Debug for CompositeResolver {
346 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
347 f.debug_struct("CompositeResolver")
348 .field("manual_certs", &"<RwLock<HashMap>>")
349 .field("acme_resolver", &self.acme_resolver.as_ref().map(|_| "..."))
350 .finish()
351 }
352}
353
354impl ResolvesServerCert for CompositeResolver {
355 fn resolve(&self, client_hello: rustls::server::ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
356 if let Some(sni) = client_hello.server_name()
358 && let Ok(guard) = self.manual_certs.try_read()
359 && let Some(ck) = guard.get(sni)
360 {
361 debug!(sni = %sni, "serving manual TLS certificate");
362 return Some(ck.clone());
363 }
364
365 if let Some(ref resolver) = self.acme_resolver {
367 return resolver.resolve(client_hello);
368 }
369
370 None
371 }
372}
373
374fn build_client_verifier(
385 client_auth: &ClientAuthConfig,
386) -> Result<Arc<dyn rustls::server::danger::ClientCertVerifier>, ProxyError> {
387 let mut root_store = RootCertStore::empty();
388
389 for ca_path in &client_auth.ca_certs {
390 let pem_data = fs::read(ca_path).map_err(|e| {
391 ProxyError::Internal(format!("failed to read CA cert file {ca_path}: {e}"))
392 })?;
393
394 let mut reader = std::io::BufReader::new(pem_data.as_slice());
395 let certs: Vec<_> = rustls_pemfile::certs(&mut reader)
396 .collect::<Result<Vec<_>, _>>()
397 .map_err(|e| {
398 ProxyError::Internal(format!(
399 "failed to parse PEM certificates from {ca_path}: {e}"
400 ))
401 })?;
402
403 if certs.is_empty() {
404 return Err(ProxyError::Internal(format!(
405 "no certificates found in CA file: {ca_path}"
406 )));
407 }
408
409 for cert in certs {
410 root_store.add(cert).map_err(|e| {
411 ProxyError::Internal(format!("failed to add CA cert from {ca_path}: {e}"))
412 })?;
413 }
414
415 info!(path = %ca_path, "loaded CA certificate for client auth");
416 }
417
418 let builder = rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store));
419
420 let verifier = if client_auth.required {
421 info!("mTLS: client certificates required");
422 builder.build().map_err(|e| {
423 ProxyError::Internal(format!("failed to build client cert verifier: {e}"))
424 })?
425 } else {
426 info!("mTLS: client certificates optional");
427 builder.allow_unauthenticated().build().map_err(|e| {
428 ProxyError::Internal(format!(
429 "failed to build optional client cert verifier: {e}"
430 ))
431 })?
432 };
433
434 Ok(verifier)
435}
436
437async fn setup_acme(
446 acme_config: &AcmeConfig,
447 domains: &[String],
448 challenge_map: Arc<tokio::sync::RwLock<HashMap<String, String>>>,
449 on_demand_config: Option<&OnDemandTlsConfig>,
450 sites: &[crate::config::SiteConfig],
451) -> Result<(CertAutoConfig, Arc<CertResolver>, JoinHandle<()>), ProxyError> {
452 let storage: Arc<dyn Storage> = Arc::new(FileStorage::default());
453
454 let ca_url = match acme_config.ca {
456 CertAuthority::LetsEncrypt => certon::LETS_ENCRYPT_PRODUCTION,
457 CertAuthority::LetsEncryptStaging => certon::LETS_ENCRYPT_STAGING,
458 CertAuthority::ZeroSsl => certon::ZEROSSL_PRODUCTION,
459 };
460
461 let mut issuer_builder = AcmeIssuer::builder()
462 .ca(ca_url)
463 .email(&acme_config.email)
464 .agreed(true)
465 .storage(storage.clone());
466
467 match acme_config.challenge {
469 ChallengeType::Http01 => {
470 let solver = Arc::new(SharedMapHttp01Solver {
473 challenges: challenge_map,
474 });
475 issuer_builder = issuer_builder
476 .http01_solver(solver)
477 .disable_tlsalpn_challenge(true);
478 }
479 ChallengeType::TlsAlpn01 => {
480 issuer_builder = issuer_builder.disable_http_challenge(true);
481 }
482 ChallengeType::Dns01 => {
483 if let Some(ref dns_cfg) = acme_config.dns_provider {
484 let provider = create_dns_provider(dns_cfg)?;
485 let dns_solver = certon::Dns01Solver::new(provider);
486 issuer_builder = issuer_builder
487 .dns01_solver(Arc::new(dns_solver))
488 .disable_http_challenge(true)
489 .disable_tlsalpn_challenge(true);
490 } else {
491 return Err(ProxyError::Internal(
492 "DNS-01 challenge requires a dns-provider configuration".into(),
493 ));
494 }
495 }
496 }
497
498 if let Some(ref eab_cfg) = acme_config.eab {
500 let hmac_bytes = base64_decode_hmac(&eab_cfg.hmac_key)?;
501 issuer_builder =
502 issuer_builder.external_account(certon::acme_client::ExternalAccountBinding {
503 kid: eab_cfg.kid.clone(),
504 hmac_key: hmac_bytes,
505 });
506 }
507
508 let issuer = Arc::new(issuer_builder.build());
509
510 let mut config_builder = CertAutoConfig::builder()
512 .storage(storage)
513 .issuers(vec![issuer.clone()]);
514
515 if let Some(od_config) = on_demand_config {
517 let on_demand = build_on_demand_config(od_config, &issuer, sites)?;
518 config_builder = config_builder.on_demand(Arc::new(on_demand));
519 info!("on-demand TLS configured");
520 }
521
522 let ca_config = config_builder.build();
523
524 if !domains.is_empty() {
526 info!(domains = ?domains, "managing ACME certificates");
527 ca_config.manage_sync(domains).await.map_err(|e| {
528 ProxyError::Internal(format!("ACME certificate management failed: {e}"))
529 })?;
530 }
531
532 let resolver = if ca_config.on_demand.is_some() {
536 let on_demand = ca_config.on_demand.clone().unwrap();
537 Arc::new(CertResolver::with_on_demand(
538 ca_config.cache.clone(),
539 on_demand,
540 ))
541 } else {
542 Arc::new(CertResolver::new(ca_config.cache.clone()))
543 };
544
545 let maintenance_handle = certon::start_maintenance(&ca_config);
547 info!("certon maintenance loop started");
548
549 Ok((ca_config, resolver, maintenance_handle))
550}
551
552fn build_on_demand_config(
554 od_config: &OnDemandTlsConfig,
555 issuer: &Arc<certon::AcmeIssuer>,
556 sites: &[crate::config::SiteConfig],
557) -> Result<OnDemandConfig, ProxyError> {
558 let allowlist: HashSet<String> = sites.iter().map(|s| s.host.to_lowercase()).collect();
560
561 type DecisionFn = dyn Fn(&str) -> bool + Send + Sync;
563 let decision_func: Option<Arc<DecisionFn>> = if let Some(ref ask_url) = od_config.ask {
564 let url = ask_url.clone();
565 Some(Arc::new(move |domain: &str| {
566 check_ask_url_blocking(&url, domain)
567 }))
568 } else {
569 None
570 };
571
572 let rate_limit = od_config.rate_limit.map(|max_per_minute| {
574 Arc::new(certon::rate_limiter::RateLimiter::new(
575 max_per_minute as usize,
576 Duration::from_secs(60),
577 ))
578 });
579
580 let issuer_for_obtain = Arc::clone(issuer);
582 type ObtainFn = dyn Fn(String) -> Pin<Box<dyn std::future::Future<Output = certon::Result<()>> + Send>>
583 + Send
584 + Sync;
585 let obtain_func: Arc<ObtainFn> = Arc::new(move |domain: String| {
586 let issuer = Arc::clone(&issuer_for_obtain);
587 Box::pin(async move {
588 info!(domain = %domain, "on-demand TLS: obtaining certificate");
589 match issuer
590 .issue_for_domains(std::slice::from_ref(&domain))
591 .await
592 {
593 Ok(_cert) => {
594 info!(domain = %domain, "on-demand TLS: certificate obtained");
595 Ok(())
596 }
597 Err(e) => {
598 error!(domain = %domain, error = %e, "on-demand TLS: failed to obtain certificate");
599 Err(e)
600 }
601 }
602 })
603 });
604
605 Ok(OnDemandConfig {
606 decision_func,
607 host_allowlist: if allowlist.is_empty() {
608 None
609 } else {
610 Some(allowlist)
611 },
612 rate_limit,
613 obtain_func: Some(obtain_func),
614 })
615}
616
617fn check_ask_url_blocking(ask_url: &str, domain: &str) -> bool {
622 let url = format!("{}?domain={}", ask_url, domain);
623 debug!(url = %url, "on-demand TLS: checking ask URL");
624
625 let result = std::panic::catch_unwind(|| {
628 tokio::task::block_in_place(|| {
629 tokio::runtime::Handle::current().block_on(async {
630 let client = reqwest::Client::builder()
631 .timeout(Duration::from_secs(5))
632 .build()
633 .map_err(|e| format!("client build error: {e}"))?;
634 let resp = client
635 .get(&url)
636 .send()
637 .await
638 .map_err(|e| format!("request error: {e}"))?;
639 Ok::<bool, String>(resp.status().is_success())
640 })
641 })
642 });
643
644 match result {
645 Ok(Ok(allowed)) => {
646 debug!(url = %url, allowed = %allowed, "on-demand TLS: ask URL response");
647 allowed
648 }
649 Ok(Err(e)) => {
650 warn!(url = %url, error = %e, "on-demand TLS: ask URL request failed");
651 false
652 }
653 Err(_) => {
654 warn!(url = %url, "on-demand TLS: ask URL check failed (no runtime)");
655 false
656 }
657 }
658}
659
660struct SharedMapHttp01Solver {
671 challenges: Arc<tokio::sync::RwLock<HashMap<String, String>>>,
672}
673
674#[async_trait::async_trait]
675impl certon::Solver for SharedMapHttp01Solver {
676 async fn present(&self, _domain: &str, token: &str, key_auth: &str) -> certon::Result<()> {
677 debug!(token = %token, "presenting HTTP-01 challenge token");
678 let mut map = self.challenges.write().await;
679 map.insert(token.to_string(), key_auth.to_string());
680 Ok(())
681 }
682
683 async fn cleanup(&self, _domain: &str, token: &str, _key_auth: &str) -> certon::Result<()> {
684 debug!(token = %token, "cleaning up HTTP-01 challenge token");
685 let mut map = self.challenges.write().await;
686 map.remove(token);
687 Ok(())
688 }
689}
690
691fn load_manual_cert(site_tls: &SiteTlsConfig) -> Result<Arc<CertifiedKey>, ProxyError> {
701 let cert_path = Path::new(&site_tls.cert);
702 let key_path = Path::new(&site_tls.key);
703
704 let cert = Certificate::from_pem_files(cert_path, key_path).map_err(|e| {
705 ProxyError::Internal(format!(
706 "failed to parse PEM certificate/key ({}, {}): {e}",
707 site_tls.cert, site_tls.key
708 ))
709 })?;
710
711 let certified_key = certon::handshake::cert_to_certified_key(&cert).map_err(|e| {
712 ProxyError::Internal(format!(
713 "failed to convert certificate to CertifiedKey: {e}"
714 ))
715 })?;
716
717 Ok(certified_key)
718}
719
720fn resolve_protocol_versions(
728 min_version: Option<&str>,
729 max_version: Option<&str>,
730) -> Option<Vec<&'static rustls::SupportedProtocolVersion>> {
731 if min_version.is_none() && max_version.is_none() {
732 return None;
733 }
734
735 let all: [&'static rustls::SupportedProtocolVersion; 2] =
737 [&rustls::version::TLS12, &rustls::version::TLS13];
738
739 let version_index = |v: &str| match v {
740 "1.2" => Some(0usize),
741 "1.3" => Some(1usize),
742 _ => None,
743 };
744
745 let min_idx: usize = min_version.and_then(version_index).unwrap_or(0);
746 let max_idx: usize = max_version.and_then(version_index).unwrap_or(all.len() - 1);
747
748 let versions: Vec<&'static rustls::SupportedProtocolVersion> = all[min_idx..=max_idx].to_vec();
749
750 if versions.is_empty() {
751 None
752 } else {
753 Some(versions)
754 }
755}
756
757fn build_provider_with_suites(suite_names: &[String]) -> rustls::crypto::CryptoProvider {
759 use rustls::crypto::ring::cipher_suite;
760 let all_suites: &[rustls::SupportedCipherSuite] = &[
762 cipher_suite::TLS13_AES_256_GCM_SHA384,
763 cipher_suite::TLS13_AES_128_GCM_SHA256,
764 cipher_suite::TLS13_CHACHA20_POLY1305_SHA256,
765 cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
766 cipher_suite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
767 cipher_suite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
768 cipher_suite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
769 cipher_suite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
770 cipher_suite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
771 ];
772
773 let selected: Vec<rustls::SupportedCipherSuite> = all_suites
774 .iter()
775 .filter(|s: &&rustls::SupportedCipherSuite| {
776 let name = format!("{:?}", s.suite());
777 suite_names
778 .iter()
779 .any(|n| name.contains(n.as_str()) || n.as_str() == name.as_str())
780 })
781 .copied()
782 .collect();
783
784 let suites = if selected.is_empty() {
785 warn!(
786 "no matching cipher suites found for {:?}, using defaults",
787 suite_names
788 );
789 rustls::crypto::ring::default_provider().cipher_suites
790 } else {
791 selected
792 };
793
794 rustls::crypto::CryptoProvider {
795 cipher_suites: suites,
796 ..rustls::crypto::ring::default_provider()
797 }
798}
799
800fn filter_kx_groups(curve_names: &[String]) -> Vec<&'static dyn rustls::crypto::SupportedKxGroup> {
805 use rustls::crypto::ring::kx_group;
806 let all: &[(&str, &'static dyn rustls::crypto::SupportedKxGroup)] = &[
807 ("x25519", kx_group::X25519),
808 ("secp256r1", kx_group::SECP256R1),
809 ("secp384r1", kx_group::SECP384R1),
810 ];
811 let mut selected: Vec<&'static dyn rustls::crypto::SupportedKxGroup> = Vec::new();
812 for name in curve_names {
813 let lower = name.to_ascii_lowercase();
814 for &(n, group) in all {
815 if n == lower {
816 selected.push(group);
817 }
818 }
819 }
820 if selected.is_empty() {
821 warn!(
822 "no matching ECDH curves for {:?}, using defaults",
823 curve_names
824 );
825 rustls::crypto::ring::default_provider().kx_groups
826 } else {
827 selected
828 }
829}
830
831fn build_server_config(
834 resolver: Arc<dyn ResolvesServerCert>,
835 client_verifier: Option<&Arc<dyn rustls::server::danger::ClientCertVerifier>>,
836 tls_config: Option<&TlsConfig>,
837) -> rustls::ServerConfig {
838 let provider = if let Some(cfg) = tls_config {
841 let mut p = if !cfg.cipher_suites.is_empty() {
842 build_provider_with_suites(&cfg.cipher_suites)
843 } else {
844 rustls::crypto::ring::default_provider()
845 };
846 if !cfg.ecdh_curves.is_empty() {
847 p.kx_groups = filter_kx_groups(&cfg.ecdh_curves);
848 }
849 Arc::new(p)
850 } else {
851 Arc::new(rustls::crypto::ring::default_provider())
852 };
853
854 let versions = tls_config.and_then(|cfg| {
856 resolve_protocol_versions(cfg.min_version.as_deref(), cfg.max_version.as_deref())
857 });
858
859 let builder = if let Some(ref versions) = versions {
860 rustls::ServerConfig::builder_with_provider(provider)
861 .with_protocol_versions(versions)
862 .expect("TLS protocol versions are valid")
863 } else {
864 rustls::ServerConfig::builder_with_provider(provider)
865 .with_safe_default_protocol_versions()
866 .expect("default protocol versions are valid")
867 };
868
869 if let Some(verifier) = client_verifier {
870 builder
871 .with_client_cert_verifier(Arc::clone(verifier))
872 .with_cert_resolver(resolver)
873 } else {
874 builder.with_no_client_auth().with_cert_resolver(resolver)
875 }
876}
877
878fn base64_decode_hmac(input: &str) -> Result<Vec<u8>, ProxyError> {
889 let normalised: String = input
891 .trim()
892 .chars()
893 .map(|c| match c {
894 '-' => '+',
895 '_' => '/',
896 other => other,
897 })
898 .collect();
899 decode_base64(&normalised)
900 .ok_or_else(|| ProxyError::Internal("invalid base64 in EAB HMAC key".into()))
901}
902
903fn decode_base64(input: &str) -> Option<Vec<u8>> {
906 const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
907
908 let input = input.trim();
909 if input.is_empty() {
910 return Some(Vec::new());
911 }
912
913 let mut output = Vec::with_capacity(input.len() * 3 / 4);
914 let mut buf: u32 = 0;
915 let mut bits: u32 = 0;
916
917 for &b in input.as_bytes() {
918 if b == b'=' {
919 break;
920 }
921 let val = match TABLE.iter().position(|&c| c == b) {
922 Some(v) => v as u32,
923 None => {
924 if b == b'\n' || b == b'\r' || b == b' ' {
925 continue;
926 }
927 return None;
928 }
929 };
930 buf = (buf << 6) | val;
931 bits += 6;
932 if bits >= 8 {
933 bits -= 8;
934 output.push((buf >> bits) as u8);
935 buf &= (1 << bits) - 1;
936 }
937 }
938
939 Some(output)
940}
941
942fn create_dns_provider(
945 cfg: &DnsProviderConfig,
946) -> Result<Box<dyn certon::DnsProvider>, ProxyError> {
947 match cfg.provider.as_str() {
948 "cloudflare" => Ok(Box::new(crate::tls::dns::CloudflareDns::new(cfg)?)),
949 "route53" => Ok(Box::new(crate::tls::dns::Route53Dns::new(cfg)?)),
950 "digitalocean" => Ok(Box::new(crate::tls::dns::DigitalOceanDns::new(cfg)?)),
951 "dnsimple" => Ok(Box::new(crate::tls::dns::DnSimpleDns::new(cfg)?)),
952 "porkbun" => Ok(Box::new(crate::tls::dns::PorkbunDns::new(cfg)?)),
953 "ovh" => Ok(Box::new(crate::tls::dns::OvhDns::new(cfg)?)),
954 "desec" => Ok(Box::new(crate::tls::dns::DesecDns::new(cfg)?)),
955 "bunny" => Ok(Box::new(crate::tls::dns::BunnyDns::new(cfg)?)),
956 "rfc2136" => Ok(Box::new(crate::tls::dns::Rfc2136Dns::new(cfg)?)),
957 other => Err(ProxyError::Internal(format!(
958 "unknown DNS provider: {other}"
959 ))),
960 }
961}
962
963#[cfg(test)]
968mod tests {
969 use super::*;
970
971 #[test]
972 fn build_server_config_creates_valid_config_no_client_auth() {
973 #[derive(Debug)]
975 struct NullResolver;
976 impl ResolvesServerCert for NullResolver {
977 fn resolve(
978 &self,
979 _client_hello: rustls::server::ClientHello<'_>,
980 ) -> Option<Arc<CertifiedKey>> {
981 None
982 }
983 }
984
985 let config = build_server_config(Arc::new(NullResolver), None, None);
986 assert!(config.alpn_protocols.is_empty());
988 }
989
990 #[test]
991 fn build_server_config_with_client_verifier() {
992 #[derive(Debug)]
993 struct NullResolver;
994 impl ResolvesServerCert for NullResolver {
995 fn resolve(
996 &self,
997 _client_hello: rustls::server::ClientHello<'_>,
998 ) -> Option<Arc<CertifiedKey>> {
999 None
1000 }
1001 }
1002
1003 let config = build_server_config(Arc::new(NullResolver), None, None);
1006 assert!(config.alpn_protocols.is_empty());
1007 }
1008}