1use crate::error::{Error, Result};
35use std::path::{Path, PathBuf};
36use std::sync::{Arc, Mutex, MutexGuard};
37use std::time::{Duration, SystemTime};
38use tokio::net::TcpStream;
39use tokio_rustls::rustls::pki_types::pem::PemObject;
40use tokio_rustls::rustls::pki_types::{CertificateDer, PrivateKeyDer};
41use tokio_rustls::rustls::server::WebPkiClientVerifier;
42use tokio_rustls::rustls::{RootCertStore, ServerConfig};
43use tokio_rustls::server::TlsStream;
44use tokio_rustls::TlsAcceptor;
45
46struct Source {
47 cert_path: PathBuf,
48 key_path: PathBuf,
49 modified: Option<(SystemTime, SystemTime)>,
50}
51
52struct ClientAuth {
53 roots: RootCertStore,
54 required: bool,
55}
56
57struct State {
58 acceptor: TlsAcceptor,
59 chain_pem: Vec<u8>,
61 key_pem: Vec<u8>,
62 source: Option<Source>,
63}
64
65#[derive(Clone)]
68pub struct TlsConfig {
69 state: Arc<Mutex<State>>,
70 client_auth: Arc<Mutex<Option<Arc<ClientAuth>>>>,
71 handshake_timeout: Duration,
72}
73
74fn tls_error(what: &str, detail: impl std::fmt::Display) -> Error {
75 Error::internal(format!("TLS: {}: {}", what, detail))
76}
77
78fn certs_from_pem(pem: &[u8]) -> Result<Vec<CertificateDer<'static>>> {
79 let certs: std::result::Result<Vec<_>, _> = CertificateDer::pem_slice_iter(pem).collect();
80 let certs = certs.map_err(|e| tls_error("invalid certificate PEM", e))?;
81 if certs.is_empty() {
82 return Err(tls_error("no certificate found in the PEM data", "empty"));
83 }
84 Ok(certs)
85}
86
87fn build_server_config(
88 chain_pem: &[u8],
89 key_pem: &[u8],
90 client_auth: Option<&ClientAuth>,
91) -> Result<ServerConfig> {
92 let chain = certs_from_pem(chain_pem)?;
93 let key = PrivateKeyDer::from_pem_slice(key_pem)
94 .map_err(|e| tls_error("no usable private key in the PEM data", e))?;
95
96 let builder = ServerConfig::builder_with_provider(Arc::new(
97 tokio_rustls::rustls::crypto::ring::default_provider(),
98 ))
99 .with_safe_default_protocol_versions()
100 .map_err(|e| tls_error("protocol versions", e))?;
101 let builder = match client_auth {
102 None => builder.with_no_client_auth(),
103 Some(auth) => {
104 let mut verifier = WebPkiClientVerifier::builder_with_provider(
105 Arc::new(auth.roots.clone()),
106 Arc::new(tokio_rustls::rustls::crypto::ring::default_provider()),
107 );
108 if !auth.required {
109 verifier = verifier.allow_unauthenticated();
110 }
111 builder.with_client_cert_verifier(
112 verifier
113 .build()
114 .map_err(|e| tls_error("client certificate verifier", e))?,
115 )
116 }
117 };
118 let mut config = builder
119 .with_single_cert(chain, key)
120 .map_err(|e| tls_error("certificate and key do not match", e))?;
121 config.alpn_protocols = vec![b"http/1.1".to_vec()];
122 Ok(config)
123}
124
125fn modified(path: &Path) -> Option<SystemTime> {
126 std::fs::metadata(path).ok()?.modified().ok()
127}
128
129impl TlsConfig {
130 pub fn from_pem(chain_pem: &[u8], key_pem: &[u8]) -> Result<Self> {
133 let config = build_server_config(chain_pem, key_pem, None)?;
134 Ok(Self {
135 state: Arc::new(Mutex::new(State {
136 acceptor: TlsAcceptor::from(Arc::new(config)),
137 chain_pem: chain_pem.to_vec(),
138 key_pem: key_pem.to_vec(),
139 source: None,
140 })),
141 client_auth: Arc::new(Mutex::new(None)),
142 handshake_timeout: Duration::from_secs(10),
143 })
144 }
145
146 pub fn from_pem_files(cert_path: impl AsRef<Path>, key_path: impl AsRef<Path>) -> Result<Self> {
148 let (cert_path, key_path) = (
149 cert_path.as_ref().to_path_buf(),
150 key_path.as_ref().to_path_buf(),
151 );
152 let chain = std::fs::read(&cert_path)
153 .map_err(|e| tls_error(&format!("cannot read {}", cert_path.display()), e))?;
154 let key = std::fs::read(&key_path)
155 .map_err(|e| tls_error(&format!("cannot read {}", key_path.display()), e))?;
156 let this = Self::from_pem(&chain, &key)?;
157 this.lock().source = Some(Source {
158 modified: modified(&cert_path).zip(modified(&key_path)),
159 cert_path,
160 key_path,
161 });
162 Ok(this)
163 }
164
165 fn lock(&self) -> MutexGuard<'_, State> {
166 self.state.lock().unwrap_or_else(|e| e.into_inner())
167 }
168
169 fn client_auth_policy(&self) -> Option<Arc<ClientAuth>> {
170 self.client_auth
171 .lock()
172 .unwrap_or_else(|e| e.into_inner())
173 .clone()
174 }
175
176 pub fn client_auth(self, ca_pem: &[u8], required: bool) -> Result<Self> {
183 let mut roots = RootCertStore::empty();
184 for ca in certs_from_pem(ca_pem)? {
185 roots
186 .add(ca)
187 .map_err(|e| tls_error("unusable CA certificate", e))?;
188 }
189 let auth = Arc::new(ClientAuth { roots, required });
190 let config = {
192 let st = self.lock();
193 build_server_config(&st.chain_pem, &st.key_pem, Some(&auth))?
194 };
195 *self.client_auth.lock().unwrap_or_else(|e| e.into_inner()) = Some(auth);
196 self.lock().acceptor = TlsAcceptor::from(Arc::new(config));
197 Ok(self)
198 }
199
200 pub fn handshake_timeout(mut self, timeout: Duration) -> Self {
202 self.handshake_timeout = timeout;
203 self
204 }
205
206 pub fn reload(&self) -> Result<bool> {
212 let (cert_path, key_path, previous) = {
213 let st = self.lock();
214 let Some(src) = &st.source else {
215 return Ok(false);
216 };
217 (src.cert_path.clone(), src.key_path.clone(), src.modified)
218 };
219 let now = modified(&cert_path).zip(modified(&key_path));
220 if previous.is_some() && now == previous {
221 return Ok(false);
222 }
223 let chain = std::fs::read(&cert_path)
224 .map_err(|e| tls_error(&format!("cannot read {}", cert_path.display()), e))?;
225 let key = std::fs::read(&key_path)
226 .map_err(|e| tls_error(&format!("cannot read {}", key_path.display()), e))?;
227 let auth = self.client_auth_policy();
228 let config = build_server_config(&chain, &key, auth.as_deref())?;
229 let mut st = self.lock();
230 st.acceptor = TlsAcceptor::from(Arc::new(config));
231 st.chain_pem = chain;
232 st.key_pem = key;
233 if let Some(src) = st.source.as_mut() {
234 src.modified = now;
235 }
236 Ok(true)
237 }
238
239 pub(crate) async fn accept(
242 &self,
243 stream: TcpStream,
244 ) -> std::io::Result<(TlsStream<TcpStream>, Option<Vec<u8>>)> {
245 let acceptor = self.lock().acceptor.clone();
246 let tls = tokio::time::timeout(self.handshake_timeout, acceptor.accept(stream))
247 .await
248 .map_err(|_| {
249 std::io::Error::new(std::io::ErrorKind::TimedOut, "TLS handshake timed out")
250 })??;
251 let cert = tls
252 .get_ref()
253 .1
254 .peer_certificates()
255 .and_then(|c| c.first())
256 .map(|c| c.as_ref().to_vec());
257 Ok((tls, cert))
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 fn self_signed() -> (String, String) {
266 let ck = rcgen::generate_simple_self_signed(vec!["localhost".to_string()]).unwrap();
267 (ck.cert.pem(), ck.signing_key.serialize_pem())
268 }
269
270 #[test]
271 fn valid_pem_builds_a_config() {
272 let (cert, key) = self_signed();
273 assert!(TlsConfig::from_pem(cert.as_bytes(), key.as_bytes()).is_ok());
274 }
275
276 #[test]
277 fn bad_material_is_reported_not_panicked() {
278 let (cert, key) = self_signed();
279 let (_, other_key) = self_signed();
280 let msg = |r: Result<TlsConfig>| r.err().expect("should fail").message().to_string();
281
282 assert!(msg(TlsConfig::from_pem(b"", key.as_bytes())).contains("no certificate"));
283 assert!(msg(TlsConfig::from_pem(b"garbage", key.as_bytes())).contains("TLS"));
284 assert!(msg(TlsConfig::from_pem(cert.as_bytes(), b"")).contains("private key"));
285 assert!(msg(TlsConfig::from_pem(
286 cert.as_bytes(),
287 b"-----BEGIN PRIVATE KEY-----\nAAAA\n-----END PRIVATE KEY-----\n"
288 ))
289 .contains("TLS"));
290 assert!(
291 msg(TlsConfig::from_pem(cert.as_bytes(), other_key.as_bytes()))
292 .contains("do not match")
293 );
294 assert!(
295 msg(TlsConfig::from_pem_files("/nope/cert.pem", "/nope/key.pem"))
296 .contains("cannot read")
297 );
298 }
299
300 #[test]
301 fn reload_applies_changes_and_keeps_the_old_certificate_on_errors() {
302 let dir = std::env::temp_dir().join(format!("ullun-tls-unit-{}", std::process::id()));
303 std::fs::create_dir_all(&dir).unwrap();
304 let (cert_path, key_path) = (dir.join("cert.pem"), dir.join("key.pem"));
305 let (c1, k1) = self_signed();
306 std::fs::write(&cert_path, &c1).unwrap();
307 std::fs::write(&key_path, &k1).unwrap();
308
309 let tls = TlsConfig::from_pem_files(&cert_path, &key_path).unwrap();
310 assert!(!tls.reload().unwrap(), "unchanged files are not re-read");
311
312 std::thread::sleep(Duration::from_millis(1100));
314 let (c2, k2) = self_signed();
315 std::fs::write(&cert_path, &c2).unwrap();
316 std::fs::write(&key_path, &k2).unwrap();
317 assert!(tls.reload().unwrap());
318 assert!(!tls.reload().unwrap());
319
320 std::thread::sleep(Duration::from_millis(1100));
322 std::fs::write(&cert_path, "half written").unwrap();
323 assert!(tls.reload().is_err());
324 assert!(tls.reload().is_err(), "still broken, still reported");
325 std::fs::remove_dir_all(&dir).ok();
326 }
327
328 #[test]
329 fn configs_from_pem_text_do_not_reload_but_take_client_auth() {
330 let (cert, key) = self_signed();
331 let tls = TlsConfig::from_pem(cert.as_bytes(), key.as_bytes()).unwrap();
332 assert!(!tls.reload().unwrap());
333 assert!(tls.clone().client_auth(b"", true).is_err());
335 assert!(tls.clone().client_auth(key.as_bytes(), true).is_err());
336 assert!(tls.client_auth(cert.as_bytes(), true).is_ok());
337 }
338}