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