1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! TLS support using rustls
//!
//! Provides a [`TlsListener`] that wraps a TCP listener with TLS termination,
//! implementing [`axum::serve::Listener`] for seamless integration with axum's server.
use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::{TcpListener, TcpStream};
use tokio_rustls::rustls::ServerConfig;
use tokio_rustls::server::TlsStream;
use tokio_rustls::TlsAcceptor;
use crate::config::TlsConfig;
use crate::error::Result;
/// A TLS-enabled listener wrapping a [`TcpListener`] with a [`TlsAcceptor`].
///
/// Implements [`axum::serve::Listener`] so it can be used as a drop-in
/// replacement for `TcpListener` when calling `axum::serve()`.
pub struct TlsListener {
tcp: TcpListener,
acceptor: TlsAcceptor,
}
impl TlsListener {
/// Create a new TLS listener from an existing TCP listener and server config.
pub fn new(tcp: TcpListener, server_config: Arc<ServerConfig>) -> Self {
Self {
tcp,
acceptor: TlsAcceptor::from(server_config),
}
}
}
impl axum::serve::Listener for TlsListener {
type Io = TlsStream<TcpStream>;
type Addr = SocketAddr;
fn accept(&mut self) -> impl std::future::Future<Output = (Self::Io, Self::Addr)> + Send {
let acceptor = self.acceptor.clone();
let tcp = &mut self.tcp;
async move {
loop {
// Accept a TCP connection using the tokio TcpListener method (not
// the axum Listener trait method, which handles errors internally).
let (stream, addr) = match TcpListener::accept(tcp).await {
Ok((stream, addr)) => (stream, addr),
Err(e) => {
tracing::error!("TCP accept error: {}", e);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
continue;
}
};
// Perform TLS handshake. On failure, log and try the next connection.
match acceptor.accept(stream).await {
Ok(tls_stream) => return (tls_stream, addr),
Err(e) => {
tracing::warn!("TLS handshake failed from {}: {}", addr, e);
continue;
}
}
}
}
}
fn local_addr(&self) -> io::Result<Self::Addr> {
self.tcp.local_addr()
}
}
/// Load a rustls [`ServerConfig`] from PEM certificate and key files.
///
/// Reads the certificate chain and private key from disk and constructs
/// a server configuration with no client authentication required.
pub fn load_server_config(tls_config: &TlsConfig) -> Result<Arc<ServerConfig>> {
use rustls_pki_types::pem::PemObject;
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
use tokio_rustls::rustls;
// Read certificate chain
let cert_chain: Vec<rustls::pki_types::CertificateDer<'static>> =
CertificateDer::pem_file_iter(&tls_config.cert_path)
.map_err(|e| {
crate::error::Error::Internal(format!(
"Failed to open TLS cert file '{}': {}",
tls_config.cert_path.display(),
e
))
})?
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(|e| {
crate::error::Error::Internal(format!("Failed to parse TLS certificates: {}", e))
})?;
if cert_chain.is_empty() {
return Err(crate::error::Error::Internal(
"TLS cert file contains no certificates".to_string(),
));
}
// Read private key (first PEM-encoded key found in the file)
let key = PrivateKeyDer::from_pem_file(&tls_config.key_path).map_err(|e| {
crate::error::Error::Internal(format!(
"Failed to parse TLS private key from '{}': {}",
tls_config.key_path.display(),
e
))
})?;
// Install the chosen rustls crypto provider before any builder call.
// Without this, `ServerConfig::builder()` panics when multiple providers
// are compiled into the binary (common via transitive deps).
crate::crypto::ensure_default_crypto_provider();
// Build server config
let config = ServerConfig::builder()
.with_no_client_auth()
.with_single_cert(cert_chain, key)
.map_err(|e| {
crate::error::Error::Internal(format!("Failed to build TLS server config: {}", e))
})?;
Ok(Arc::new(config))
}