1use std::path::{Path, PathBuf};
22use std::sync::Arc;
23
24use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
25use tokio_rustls::rustls::ServerConfig;
26use tokio_rustls::TlsAcceptor;
27
28use waf_core::TlsConfig;
29
30pub struct TlsMaterial {
32 pub cert_chain: Vec<CertificateDer<'static>>,
33 pub private_key: PrivateKeyDer<'static>,
34}
35
36pub trait TlsCertSource: Send + Sync {
40 fn load(&self) -> Result<TlsMaterial, TlsError>;
41}
42
43#[derive(Debug)]
46pub enum TlsError {
47 Io { path: PathBuf, source: std::io::Error },
48 NoCertificates(PathBuf),
49 NoPrivateKey(PathBuf),
50 Rustls(tokio_rustls::rustls::Error),
51}
52
53impl std::fmt::Display for TlsError {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 match self {
56 Self::Io { path, source } =>
57 write!(f, "cannot read TLS file {}: {source}", path.display()),
58 Self::NoCertificates(p) =>
59 write!(f, "no PEM certificates found in {}", p.display()),
60 Self::NoPrivateKey(p) =>
61 write!(f, "no PEM private key found in {}", p.display()),
62 Self::Rustls(e) => write!(f, "TLS configuration error: {e}"),
63 }
64 }
65}
66
67impl std::error::Error for TlsError {}
68
69pub struct FileCertSource {
71 cert_path: PathBuf,
72 key_path: PathBuf,
73}
74
75impl FileCertSource {
76 pub fn new(cert_path: impl Into<PathBuf>, key_path: impl Into<PathBuf>) -> Self {
77 Self { cert_path: cert_path.into(), key_path: key_path.into() }
78 }
79}
80
81impl TlsCertSource for FileCertSource {
82 fn load(&self) -> Result<TlsMaterial, TlsError> {
83 Ok(TlsMaterial {
84 cert_chain: load_certs(&self.cert_path)?,
85 private_key: load_key(&self.key_path)?,
86 })
87 }
88}
89
90fn load_certs(path: &Path) -> Result<Vec<CertificateDer<'static>>, TlsError> {
91 let data = std::fs::read(path).map_err(|source| TlsError::Io { path: path.to_path_buf(), source })?;
92 let mut reader: &[u8] = &data;
93 let certs: Vec<CertificateDer<'static>> = rustls_pemfile::certs(&mut reader)
94 .collect::<Result<_, _>>()
95 .map_err(|source| TlsError::Io { path: path.to_path_buf(), source })?;
96 if certs.is_empty() {
97 return Err(TlsError::NoCertificates(path.to_path_buf()));
98 }
99 Ok(certs)
100}
101
102fn load_key(path: &Path) -> Result<PrivateKeyDer<'static>, TlsError> {
103 let data = std::fs::read(path).map_err(|source| TlsError::Io { path: path.to_path_buf(), source })?;
104 let mut reader: &[u8] = &data;
105 rustls_pemfile::private_key(&mut reader)
106 .map_err(|source| TlsError::Io { path: path.to_path_buf(), source })?
107 .ok_or_else(|| TlsError::NoPrivateKey(path.to_path_buf()))
108}
109
110pub fn build_server_config(source: &dyn TlsCertSource, alpn: &[String]) -> Result<ServerConfig, TlsError> {
114 let material = source.load()?;
115 let provider = Arc::new(tokio_rustls::rustls::crypto::ring::default_provider());
116 let mut cfg = ServerConfig::builder_with_provider(provider)
117 .with_safe_default_protocol_versions()
118 .map_err(TlsError::Rustls)?
119 .with_no_client_auth()
120 .with_single_cert(material.cert_chain, material.private_key)
121 .map_err(TlsError::Rustls)?;
122 cfg.alpn_protocols = alpn.iter().map(|p| p.as_bytes().to_vec()).collect();
123 Ok(cfg)
124}
125
126pub fn acceptor_from_source(
133 tls: &TlsConfig,
134 source: Option<Arc<dyn TlsCertSource>>,
135) -> Result<Option<TlsAcceptor>, TlsError> {
136 if !tls.enabled {
137 return Ok(None);
138 }
139 let cfg = match source {
140 Some(s) => build_server_config(s.as_ref(), &tls.alpn)?,
141 None => build_server_config(&FileCertSource::new(&tls.cert_path, &tls.key_path), &tls.alpn)?,
142 };
143 Ok(Some(TlsAcceptor::from(Arc::new(cfg))))
144}
145
146pub fn acceptor_from_config(tls: &TlsConfig) -> Result<Option<TlsAcceptor>, TlsError> {
149 acceptor_from_source(tls, None)
150}