1use std::collections::{HashMap, HashSet};
16use std::net::IpAddr;
17use std::path::PathBuf;
18use std::str::FromStr;
19use std::sync::Arc;
20
21use async_channel::{Receiver, Sender};
22use ferron_common::config::ServerConfigurationFilters;
23use ferron_common::get_entry;
24use ferron_common::logging::LogMessage;
25use instant_acme::ChallengeType;
26use rustls::crypto::CryptoProvider;
27use rustls::sign::CertifiedKey;
28use tokio::sync::RwLock;
29
30use crate::acme::{AcmeCache, AcmeConfig, AcmeOnDemandConfig, Http01DataLock, TlsAlpn01DataLock, TlsAlpn01Resolver};
31use crate::util::{
32 load_certs, load_private_key, CustomSniResolver, HostnameRadixTree, OneCertifiedKeyResolver, SniResolverLock,
33};
34
35pub struct TlsBuildContext {
47 pub tls_ports: HashMap<u16, CustomSniResolver>,
48 pub tls_port_locks: HashMap<u16, SniResolverLock>,
49 pub nonencrypted_ports: HashSet<u16>,
50 pub certified_keys_to_preload: HashMap<u16, Vec<Arc<CertifiedKey>>>,
51 pub used_sni_hostnames: HashSet<(u16, Option<String>)>,
52 pub automatic_tls_used_sni_hostnames: HashSet<(u16, Option<String>)>,
53 pub acme_tls_alpn_01_resolvers: HashMap<u16, TlsAlpn01Resolver>,
54 pub acme_tls_alpn_01_resolver_locks: HashMap<u16, Arc<RwLock<Vec<TlsAlpn01DataLock>>>>,
55 pub acme_http_01_resolvers: Arc<RwLock<Vec<Http01DataLock>>>,
56 pub acme_configs: Vec<AcmeConfig>,
57 pub acme_on_demand_configs: Vec<AcmeOnDemandConfig>,
58 pub acme_on_demand_tx: Sender<(String, u16)>,
59 pub acme_on_demand_rx: Receiver<(String, u16)>,
60}
61
62impl Default for TlsBuildContext {
63 fn default() -> Self {
64 let (acme_on_demand_tx, acme_on_demand_rx) = async_channel::unbounded();
65 Self {
66 tls_ports: HashMap::new(),
67 tls_port_locks: HashMap::new(),
68 nonencrypted_ports: HashSet::new(),
69 certified_keys_to_preload: HashMap::new(),
70 used_sni_hostnames: HashSet::new(),
71 automatic_tls_used_sni_hostnames: HashSet::new(),
72 acme_tls_alpn_01_resolvers: HashMap::new(),
73 acme_tls_alpn_01_resolver_locks: HashMap::new(),
74 acme_http_01_resolvers: Arc::new(RwLock::new(Vec::new())),
75 acme_configs: Vec::new(),
76 acme_on_demand_configs: Vec::new(),
77 acme_on_demand_tx,
78 acme_on_demand_rx,
79 }
80 }
81}
82
83pub fn read_default_port(config: Option<&ferron_common::config::ServerConfiguration>, is_https: bool) -> Option<u16> {
85 let fallback = if is_https { 443 } else { 80 };
86 config
87 .and_then(|c| {
88 if is_https {
89 get_entry!("default_https_port", c)
90 } else {
91 get_entry!("default_http_port", c)
92 }
93 })
94 .and_then(|e| e.values.first())
95 .map_or(Some(fallback), |v| {
96 if v.is_null() {
97 None
98 } else {
99 Some(v.as_i128().unwrap_or(fallback as i128) as u16)
100 }
101 })
102}
103
104pub fn resolve_sni_hostname(filters: &ServerConfigurationFilters) -> Option<String> {
106 filters.hostname.clone().or_else(|| {
107 if filters.ip.is_some_and(|ip| ip.is_loopback()) {
108 return Some("localhost".to_string());
110 }
111
112 match filters.ip {
114 Some(IpAddr::V4(addr)) => Some(addr.to_string()),
115 Some(IpAddr::V6(addr)) => Some(format!("[{addr}]")),
116 None => None,
117 }
118 })
119}
120
121fn ensure_tls_port_resolver(ctx: &mut TlsBuildContext, port: u16) -> &mut CustomSniResolver {
128 ctx.tls_ports.entry(port).or_insert_with(|| {
129 let list = Arc::new(RwLock::new(HostnameRadixTree::new()));
130 ctx.tls_port_locks.insert(port, list.clone());
131 CustomSniResolver::with_resolvers(list)
132 })
133}
134
135pub fn handle_manual_tls(
144 ctx: &mut TlsBuildContext,
145 crypto_provider: &CryptoProvider,
146 port: u16,
147 sni_hostname: Option<String>,
148 cert_path: &str,
149 key_path: &str,
150) -> anyhow::Result<()> {
151 let certs = load_certs(cert_path).map_err(|e| anyhow::anyhow!("Cannot load certificate {cert_path}: {e}"))?;
152
153 let key = load_private_key(key_path).map_err(|e| anyhow::anyhow!("Cannot load key {key_path}: {e}"))?;
154
155 let signing_key = crypto_provider
156 .key_provider
157 .load_private_key(key)
158 .map_err(|e| anyhow::anyhow!("Invalid private key {key_path}: {e}"))?;
159
160 let certified_key = Arc::new(CertifiedKey::new(certs, signing_key));
161
162 ctx
163 .certified_keys_to_preload
164 .entry(port)
165 .or_default()
166 .push(certified_key.clone());
167
168 let resolver = Arc::new(OneCertifiedKeyResolver::new(certified_key));
169 let sni_resolver = ensure_tls_port_resolver(ctx, port);
170
171 match &sni_hostname {
172 Some(host) => sni_resolver.load_host_resolver(host, resolver),
173 None => sni_resolver.load_fallback_resolver(resolver),
174 }
175
176 ctx.used_sni_hostnames.insert((port, sni_hostname));
177 Ok(())
178}
179
180fn parse_challenge_type(
182 server: &ferron_common::config::ServerConfiguration,
183) -> anyhow::Result<(ChallengeType, HashMap<String, String>)> {
184 let entry = get_entry!("auto_tls_challenge", server);
185
186 let ty = entry
187 .and_then(|e| e.values.first())
188 .and_then(|v| v.as_str())
189 .unwrap_or("tls-alpn-01")
190 .to_uppercase();
191
192 let params = entry
193 .map(|e| {
194 e.props
195 .iter()
196 .filter_map(|(k, v)| v.as_str().map(|s| (k.clone(), s.to_string())))
197 .collect()
198 })
199 .unwrap_or_default();
200
201 let challenge = match ty.as_str() {
202 "HTTP-01" => ChallengeType::Http01,
203 "TLS-ALPN-01" => ChallengeType::TlsAlpn01,
204 "DNS-01" => ChallengeType::Dns01,
205 _ => anyhow::bail!("Unsupported ACME challenge type: {ty}"),
206 };
207
208 Ok((challenge, params))
209}
210
211pub fn should_skip_server(server: &ferron_common::config::ServerConfiguration) -> bool {
213 server.filters.is_global_non_host() || (server.filters.is_global() && server.entries.is_empty())
214}
215
216pub fn manual_tls_entry(server: &ferron_common::config::ServerConfiguration) -> Option<(&str, &str)> {
218 let tls_entry = get_entry!("tls", server)?;
219
220 if tls_entry.values.len() != 2 {
221 return None;
222 }
223
224 let cert = tls_entry.values[0].as_str()?;
225 let key = tls_entry.values[1].as_str()?;
226
227 Some((cert, key))
228}
229
230pub fn handle_nonencrypted_ports(
232 ctx: &mut TlsBuildContext,
233 server: &ferron_common::config::ServerConfiguration,
234 default_http_port: Option<u16>,
235) {
236 if get_entry!("tls", server).is_some() {
238 return;
239 }
240
241 if get_entry!("auto_tls", server)
243 .and_then(|e| e.values.first())
244 .and_then(|v| v.as_bool())
245 .unwrap_or(false)
246 {
247 return;
248 }
249
250 if let Some(port) = server.filters.port.or(default_http_port) {
251 ctx.nonencrypted_ports.insert(port);
252 }
253}
254
255pub fn handle_automatic_tls(
265 ctx: &mut TlsBuildContext,
266 server: &ferron_common::config::ServerConfiguration,
267 port: u16,
268 sni_hostname: Option<String>,
269 crypto_provider: Arc<CryptoProvider>,
270 memory_acme_account_cache_data: Arc<RwLock<HashMap<String, Vec<u8>>>>,
271) -> anyhow::Result<Option<LogMessage>> {
272 let on_demand = get_entry!("auto_tls_on_demand", server)
273 .and_then(|e| e.values.first())
274 .and_then(|v| v.as_bool())
275 .unwrap_or(false);
276
277 if let Some(host) = &sni_hostname {
279 if host.parse::<IpAddr>().is_ok() {
280 return Ok(Some(LogMessage::new(
281 format!(
282 "Ferron's automatic TLS functionality doesn't support IP address-based identifiers, \
283 skipping SNI host \"{host}\"..."
284 ),
285 true,
286 )));
287 }
288 }
289
290 if sni_hostname.is_none() && !server.filters.is_global() && !server.filters.is_global_non_host() {
292 return Ok(Some(LogMessage::new(
293 "Skipping automatic TLS for a host without a SNI hostname...".to_string(),
294 true,
295 )));
296 }
297
298 let (challenge_type, challenge_params) = parse_challenge_type(server)?;
299
300 if let Some(sni_hostname) = &sni_hostname {
301 let is_wildcard_domain = sni_hostname.starts_with("*.");
302 if is_wildcard_domain && !on_demand {
303 match &challenge_type {
304 ChallengeType::Http01 => {
305 return Ok(Some(LogMessage::new(
306 format!(
307 "HTTP-01 ACME challenge doesn't support wildcard hostnames, skipping SNI host \"{sni_hostname}\"..."
308 ),
309 true,
310 )));
311 }
312 ChallengeType::TlsAlpn01 => {
313 return Ok(Some(LogMessage::new(
314 format!(
315 "TLS-ALPN-01 ACME challenge doesn't support wildcard hostnames, skipping SNI host \"{sni_hostname}\"..."
316 ),
317 true,
318 )));
319 }
320 _ => (),
321 }
322 }
323 }
324
325 let dns_provider = if challenge_type == ChallengeType::Dns01 {
327 let provider = challenge_params
328 .get("provider")
329 .ok_or_else(|| anyhow::anyhow!("DNS-01 challenge requires a provider"))?;
330 Some(ferron_load_modules::get_dns_provider(provider, &challenge_params).map_err(|e| anyhow::anyhow!(e))?)
331 } else {
332 None
333 };
334
335 if on_demand {
336 build_on_demand_acme(
337 ctx,
338 server,
339 port,
340 sni_hostname,
341 challenge_type,
342 dns_provider,
343 crypto_provider,
344 )?;
345 } else if let Some(host) = sni_hostname {
346 build_eager_acme(
347 ctx,
348 server,
349 port,
350 host,
351 challenge_type,
352 dns_provider,
353 crypto_provider,
354 memory_acme_account_cache_data,
355 )?;
356 }
357
358 Ok(None)
359}
360
361fn build_on_demand_acme(
367 ctx: &mut TlsBuildContext,
368 server: &ferron_common::config::ServerConfiguration,
369 port: u16,
370 sni_hostname: Option<String>,
371 challenge_type: ChallengeType,
372 dns_provider: Option<Arc<dyn ferron_common::dns::DnsProvider + Send + Sync>>,
373 crypto_provider: Arc<CryptoProvider>,
374) -> anyhow::Result<()> {
375 if challenge_type == ChallengeType::TlsAlpn01 {
377 let resolver_list = Arc::new(RwLock::new(Vec::new()));
378 ctx.acme_tls_alpn_01_resolver_locks.insert(port, resolver_list.clone());
379
380 ctx
381 .acme_tls_alpn_01_resolvers
382 .insert(port, TlsAlpn01Resolver::with_resolvers(resolver_list));
383 }
384
385 let fallback_sender = ctx.acme_on_demand_tx.clone();
387 let sni_resolver = ensure_tls_port_resolver(ctx, port);
388 sni_resolver.load_fallback_sender(fallback_sender, port);
389
390 let rustls_client_config =
391 super::acme::build_rustls_client_config(server, crypto_provider).map_err(|e| anyhow::anyhow!(e))?;
392
393 let config = AcmeOnDemandConfig {
394 rustls_client_config,
395 challenge_type,
396 contact: get_entry!("auto_tls_contact", server)
397 .and_then(|e| e.values.first())
398 .and_then(|v| v.as_str())
399 .map(|c| vec![format!("mailto:{c}")])
400 .unwrap_or_default(),
401 directory: super::acme::resolve_acme_directory(server),
402 eab_key: super::acme::parse_eab(server)?,
403 profile: get_entry!("auto_tls_profile", server)
404 .and_then(|e| e.values.first())
405 .and_then(|v| v.as_str())
406 .map(str::to_string),
407 cache_path: super::acme::resolve_acme_cache_path(server)?,
408 sni_resolver_lock: ctx
409 .tls_port_locks
410 .get(&port)
411 .cloned()
412 .unwrap_or_else(|| Arc::new(RwLock::new(HostnameRadixTree::new()))),
413 tls_alpn_01_resolver_lock: ctx
414 .acme_tls_alpn_01_resolver_locks
415 .get(&port)
416 .cloned()
417 .unwrap_or_else(|| Arc::new(RwLock::new(Vec::new()))),
418 http_01_resolver_lock: ctx.acme_http_01_resolvers.clone(),
419 dns_provider,
420 sni_hostname,
421 port,
422 };
423
424 ctx.acme_on_demand_configs.push(config);
425 ctx.automatic_tls_used_sni_hostnames.insert((port, None));
426
427 Ok(())
428}
429
430#[allow(clippy::too_many_arguments)]
436fn build_eager_acme(
437 ctx: &mut TlsBuildContext,
438 server: &ferron_common::config::ServerConfiguration,
439 port: u16,
440 sni_hostname: String,
441 challenge_type: ChallengeType,
442 dns_provider: Option<Arc<dyn ferron_common::dns::DnsProvider + Send + Sync>>,
443 crypto_provider: Arc<CryptoProvider>,
444 memory_acme_account_cache_data: Arc<RwLock<HashMap<String, Vec<u8>>>>,
445) -> anyhow::Result<()> {
446 let certified_key_lock = Arc::new(RwLock::new(None));
447 let tls_alpn_01_data_lock = Arc::new(RwLock::new(None));
448 let http_01_data_lock = Arc::new(RwLock::new(None));
449
450 let rustls_client_config =
451 super::acme::build_rustls_client_config(server, crypto_provider).map_err(|e| anyhow::anyhow!(e))?;
452 let (account_cache_path, certificate_cache_path) = super::acme::resolve_cache_paths(server, port, &sni_hostname)?;
453
454 let save_paths = get_entry!("auto_tls_save_data", server).and_then(|e| {
455 e.values
456 .first()
457 .and_then(|v| v.as_str())
458 .and_then(|v| PathBuf::from_str(v).ok())
459 .and_then(|v| {
460 e.values
461 .get(1)
462 .and_then(|v| v.as_str())
463 .and_then(|v| PathBuf::from_str(v).ok())
464 .map(|v2| (v, v2))
465 })
466 });
467
468 let acme_config = AcmeConfig {
469 rustls_client_config,
470 domains: vec![sni_hostname.clone()],
471 challenge_type: challenge_type.clone(),
472 contact: get_entry!("auto_tls_contact", server)
473 .and_then(|e| e.values.first())
474 .and_then(|v| v.as_str())
475 .map(|c| vec![format!("mailto:{c}")])
476 .unwrap_or_default(),
477 directory: super::acme::resolve_acme_directory(server),
478 eab_key: super::acme::parse_eab(server)?,
479 profile: get_entry!("auto_tls_profile", server)
480 .and_then(|e| e.values.first())
481 .and_then(|v| v.as_str())
482 .map(str::to_string),
483 account_cache: if let Some(account_cache_path) = account_cache_path {
484 AcmeCache::File(account_cache_path)
485 } else {
486 AcmeCache::Memory(memory_acme_account_cache_data)
487 },
488 certificate_cache: if let Some(certificate_cache_path) = certificate_cache_path {
489 AcmeCache::File(certificate_cache_path)
490 } else {
491 AcmeCache::Memory(Default::default())
492 },
493 certified_key_lock: certified_key_lock.clone(),
494 tls_alpn_01_data_lock: tls_alpn_01_data_lock.clone(),
495 http_01_data_lock: http_01_data_lock.clone(),
496 dns_provider,
497 renewal_info: None,
498 account: None,
499 save_paths,
500 post_obtain_command: get_entry!("auto_tls_post_obtain_command", server)
501 .and_then(|e| e.values.first())
502 .and_then(|v| v.as_str())
503 .map(str::to_string),
504 };
505
506 ctx.acme_configs.push(acme_config);
507
508 match challenge_type {
510 ChallengeType::Http01 => {
511 ctx.acme_http_01_resolvers.blocking_write().push(http_01_data_lock);
512 }
513 ChallengeType::TlsAlpn01 => {
514 let resolver = ctx.acme_tls_alpn_01_resolvers.entry(port).or_insert_with(|| {
515 let list = Arc::new(RwLock::new(Vec::new()));
516 ctx.acme_tls_alpn_01_resolver_locks.insert(port, list.clone());
517 TlsAlpn01Resolver::with_resolvers(list)
518 });
519
520 resolver.load_resolver(tls_alpn_01_data_lock);
521 }
522 _ => {}
523 }
524
525 let acme_resolver = Arc::new(crate::acme::AcmeResolver::new(certified_key_lock));
527 let sni_resolver = ensure_tls_port_resolver(ctx, port);
528 sni_resolver.load_host_resolver(&sni_hostname, acme_resolver);
529
530 ctx.automatic_tls_used_sni_hostnames.insert((port, Some(sni_hostname)));
531
532 Ok(())
533}