//! "Self" TLS mode: a real, publicly trusted certificate obtained
//! autonomously from Let's Encrypt via TLS-ALPN-01, using `rustls-acme`.
//!
//! TLS-ALPN-01 validates by connecting to port 443 of the domain's resolved
//! address -- this is fixed by the ACME protocol, not a choice this crate
//! makes. [`crate::config::Config::validate`] enforces that `bind`/`remote`
//! use port 443 whenever this mode is selected.
use std::path::PathBuf;
use std::sync::Arc;
use rustls_acme::caches::DirCache;
use rustls_acme::{AcmeConfig, is_tls_alpn_challenge};
use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;
use tokio_rustls::LazyConfigAcceptor;
use tokio_stream::StreamExt;
use crate::carrier::BoxedStream;
use crate::error::{Error, Result};
use crate::logging::{Level, Logger};
pub struct AcmeAcceptor {
challenge: Arc,
default: Arc,
}
impl AcmeAcceptor {
/// Starts autonomous certificate acquisition/renewal for `domain` in the
/// background (spawns a task that drives issuance/renewal for the
/// lifetime of the process) and returns an acceptor immediately;
/// individual connections may arrive before the first certificate has
/// been issued, in which case the TLS handshake with real clients will
/// simply fail until issuance completes.
pub fn start(domain: String, cache_dir: PathBuf, logger: Logger) -> Self {
let mut state = AcmeConfig::new([domain])
.cache(DirCache::new(cache_dir))
.directory_lets_encrypt(true)
.state();
let challenge = state.challenge_rustls_config();
let default = state.default_rustls_config();
tokio::spawn(async move {
loop {
match state.next().await {
Some(Ok(event)) => {
logger.record(Level::Debug, &format!("acme: {event:?}"));
}
Some(Err(error)) => {
logger.record(Level::Warning, &format!("acme error: {error:?}"));
}
None => break,
}
}
});
Self { challenge, default }
}
/// Completes a TLS handshake on `stream`. TLS-ALPN-01 challenge
/// connections from the CA are answered and then closed; the caller
/// should treat [`None`] as "nothing more to do with this connection".
pub async fn accept(&self, stream: TcpStream) -> Result