use crate::config::TlsServerConfig;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
use std::future::Future;
use std::io;
use std::net::SocketAddr;
use std::path::Path;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{ready, Context, Poll};
use std::time::Duration;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::server::TlsStream as RustlsStream;
use tokio_rustls::{Accept, TlsAcceptor};
#[derive(Debug, thiserror::Error)]
pub enum TlsConfigError {
#[error("failed to read `{path}`: {source}")]
Read {
path: String,
#[source]
source: io::Error,
},
#[error("`{path}` is not valid PEM: {reason}")]
Pem { path: String, reason: String },
#[error(
"`{path}` and the certificate in `{cert_path}` do not form a usable identity: {reason}"
)]
Identity {
path: String,
cert_path: String,
reason: String,
},
}
pub(super) fn server_config(
config: &TlsServerConfig,
) -> Result<rustls::ServerConfig, TlsConfigError> {
let certs = load_cert_chain(&config.cert_path)?;
let key = load_private_key(&config.key_path)?;
let provider = Arc::new(rustls::crypto::ring::default_provider());
let mut server_config = rustls::ServerConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|error| TlsConfigError::Identity {
path: config.key_path.clone(),
cert_path: config.cert_path.clone(),
reason: error.to_string(),
})?
.with_no_client_auth()
.with_single_cert(certs, key)
.map_err(|error| TlsConfigError::Identity {
path: config.key_path.clone(),
cert_path: config.cert_path.clone(),
reason: error.to_string(),
})?;
server_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
Ok(server_config)
}
fn load_cert_chain(path: &str) -> Result<Vec<CertificateDer<'static>>, TlsConfigError> {
let mut reader = io::BufReader::new(open(path)?);
let certs = rustls_pemfile::certs(&mut reader)
.collect::<Result<Vec<_>, _>>()
.map_err(|error| TlsConfigError::Pem {
path: path.to_owned(),
reason: error.to_string(),
})?;
if certs.is_empty() {
return Err(TlsConfigError::Pem {
path: path.to_owned(),
reason: "no CERTIFICATE section found; expected the PEM chain, leaf first".to_owned(),
});
}
Ok(certs)
}
fn load_private_key(path: &str) -> Result<PrivateKeyDer<'static>, TlsConfigError> {
let mut reader = io::BufReader::new(open(path)?);
rustls_pemfile::private_key(&mut reader)
.map_err(|error| TlsConfigError::Pem {
path: path.to_owned(),
reason: error.to_string(),
})?
.ok_or_else(|| TlsConfigError::Pem {
path: path.to_owned(),
reason: "no PRIVATE KEY section found; expected a PKCS#8, RSA, or EC private key"
.to_owned(),
})
}
fn open(path: &str) -> Result<std::fs::File, TlsConfigError> {
std::fs::File::open(Path::new(path)).map_err(|source| TlsConfigError::Read {
path: path.to_owned(),
source,
})
}
pub(super) struct TlsListener {
tcp: TcpListener,
acceptor: TlsAcceptor,
}
impl TlsListener {
pub(super) fn new(tcp: TcpListener, config: rustls::ServerConfig) -> Self {
Self {
tcp,
acceptor: TlsAcceptor::from(Arc::new(config)),
}
}
}
impl axum::serve::Listener for TlsListener {
type Io = TlsIo;
type Addr = SocketAddr;
async fn accept(&mut self) -> (Self::Io, Self::Addr) {
loop {
match self.tcp.accept().await {
Ok((stream, addr)) => {
return (
TlsIo::Handshaking(Box::new(self.acceptor.accept(stream))),
addr,
)
}
Err(error) => handle_accept_error(error).await,
}
}
}
fn local_addr(&self) -> io::Result<Self::Addr> {
self.tcp.local_addr()
}
}
async fn handle_accept_error(error: io::Error) {
if matches!(
error.kind(),
io::ErrorKind::ConnectionRefused
| io::ErrorKind::ConnectionAborted
| io::ErrorKind::ConnectionReset
) {
return;
}
tracing::error!("accept error: {error}");
accept_retry_pause().await;
}
#[allow(clippy::disallowed_methods)]
async fn accept_retry_pause() {
tokio::time::sleep(ACCEPT_RETRY_PAUSE).await;
}
const ACCEPT_RETRY_PAUSE: Duration = Duration::from_secs(1);
pub(super) enum TlsIo {
Handshaking(Box<Accept<TcpStream>>),
Ready(Box<RustlsStream<TcpStream>>),
Failed,
}
impl TlsIo {
fn poll_stream(
&mut self,
cx: &mut Context<'_>,
) -> Poll<io::Result<Pin<&mut RustlsStream<TcpStream>>>> {
if let Self::Handshaking(accept) = self {
match Pin::new(accept.as_mut()).poll(cx) {
Poll::Ready(Ok(stream)) => *self = Self::Ready(Box::new(stream)),
Poll::Ready(Err(error)) => {
*self = Self::Failed;
return Poll::Ready(Err(error));
}
Poll::Pending => return Poll::Pending,
}
}
match self {
Self::Ready(stream) => Poll::Ready(Ok(Pin::new(stream.as_mut()))),
Self::Handshaking(_) | Self::Failed => Poll::Ready(Err(handshake_failed())),
}
}
}
fn handshake_failed() -> io::Error {
io::Error::new(
io::ErrorKind::InvalidData,
"tls handshake failed on this connection",
)
}
impl AsyncRead for TlsIo {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
ready!(self.get_mut().poll_stream(cx))?.poll_read(cx, buf)
}
}
impl AsyncWrite for TlsIo {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
ready!(self.get_mut().poll_stream(cx))?.poll_write(cx, buf)
}
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
ready!(self.get_mut().poll_stream(cx))?.poll_write_vectored(cx, bufs)
}
fn is_write_vectored(&self) -> bool {
true
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
ready!(self.get_mut().poll_stream(cx))?.poll_flush(cx)
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
match self.get_mut() {
TlsIo::Ready(stream) => Pin::new(stream.as_mut()).poll_shutdown(cx),
TlsIo::Handshaking(_) | TlsIo::Failed => Poll::Ready(Ok(())),
}
}
}