1use std::path::PathBuf;
8use std::sync::Arc;
9
10use crate::{PemSource, TlsError};
11
12#[derive(Debug, Clone)]
27pub struct ServerTlsConfig {
28 pub cert: PemSource,
30 pub key: PemSource,
32 pub client_ca: Option<PemSource>,
35 pub alpn: Vec<Vec<u8>>,
38}
39
40impl ServerTlsConfig {
41 pub fn builder() -> ServerTlsConfigBuilder {
43 ServerTlsConfigBuilder::default()
44 }
45
46 pub fn into_rustls_config(self) -> Result<rustls::ServerConfig, TlsError> {
66 crate::ensure_default_provider();
67
68 let cert_bytes = self.cert.read()?;
69 let key_bytes = self.key.read()?;
70
71 let certs = crate::load_certs_from_pem(cert_bytes.as_slice())?;
72 let key = crate::load_key_from_pem(key_bytes.as_slice())?;
73
74 let builder = rustls::ServerConfig::builder();
75 let server_builder = match self.client_ca {
76 Some(ca_src) => {
77 let ca_bytes = ca_src.read()?;
78 let ca_certs = crate::load_certs_from_pem(ca_bytes.as_slice())?;
79 let mut roots = rustls::RootCertStore::empty();
80 for ca in ca_certs {
81 roots.add(ca)?;
82 }
83 let verifier =
84 rustls::server::WebPkiClientVerifier::builder(Arc::new(roots)).build()?;
85 builder.with_client_cert_verifier(verifier)
86 }
87 None => builder.with_no_client_auth(),
88 };
89
90 let mut config = server_builder.with_single_cert(certs, key)?;
91 config.alpn_protocols = self.alpn;
92 Ok(config)
93 }
94}
95
96#[derive(Debug, Default, Clone)]
98pub struct ServerTlsConfigBuilder {
99 cert: Option<PemSource>,
100 key: Option<PemSource>,
101 client_ca: Option<PemSource>,
102 alpn: Vec<Vec<u8>>,
103}
104
105impl ServerTlsConfigBuilder {
106 pub fn cert(mut self, src: PemSource) -> Self {
108 self.cert = Some(src);
109 self
110 }
111
112 pub fn key(mut self, src: PemSource) -> Self {
114 self.key = Some(src);
115 self
116 }
117
118 pub fn with_alpn<I, S>(mut self, protocols: I) -> Self
123 where
124 I: IntoIterator<Item = S>,
125 S: Into<Vec<u8>>,
126 {
127 self.alpn = protocols.into_iter().map(Into::into).collect();
128 self
129 }
130
131 pub fn cert_pem_file(self, path: impl Into<PathBuf>) -> Self {
133 self.cert(PemSource::Path(path.into()))
134 }
135
136 pub fn cert_pem_bytes(self, bytes: impl Into<Vec<u8>>) -> Self {
138 self.cert(PemSource::Bytes(bytes.into()))
139 }
140
141 pub fn key_pem_file(self, path: impl Into<PathBuf>) -> Self {
143 self.key(PemSource::Path(path.into()))
144 }
145
146 pub fn key_pem_bytes(self, bytes: impl Into<Vec<u8>>) -> Self {
148 self.key(PemSource::Bytes(bytes.into()))
149 }
150
151 pub fn require_client_ca_pem_file(self, path: impl Into<PathBuf>) -> Self {
153 self.require_client_ca(PemSource::Path(path.into()))
154 }
155
156 pub fn require_client_ca_pem_bytes(self, bytes: impl Into<Vec<u8>>) -> Self {
158 self.require_client_ca(PemSource::Bytes(bytes.into()))
159 }
160
161 pub fn require_client_ca(mut self, src: PemSource) -> Self {
163 self.client_ca = Some(src);
164 self
165 }
166
167 pub fn build(self) -> Result<ServerTlsConfig, TlsError> {
186 let cert = self.cert.ok_or(TlsError::MissingField("cert"))?;
187 let key = self.key.ok_or(TlsError::MissingField("key"))?;
188 Ok(ServerTlsConfig {
189 cert,
190 key,
191 client_ca: self.client_ca,
192 alpn: self.alpn,
193 })
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::PemSource;
201
202 #[test]
203 fn debug_of_config_does_not_leak_key_bytes() {
204 let cfg = ServerTlsConfig::builder()
205 .cert_pem_bytes(vec![10, 20, 30])
206 .key_pem_bytes(vec![201, 202, 203])
207 .build()
208 .unwrap();
209 let rendered = format!("{cfg:?}");
210 assert!(
211 !rendered.contains("201") && !rendered.contains("202"),
212 "config Debug must not leak key bytes: {rendered}"
213 );
214 assert!(
215 rendered.contains("redacted"),
216 "expected redaction marker: {rendered}"
217 );
218 }
219
220 #[test]
221 fn builder_returns_config_when_cert_and_key_provided() {
222 let cfg = ServerTlsConfig::builder()
223 .cert_pem_bytes(b"--FAKE CERT--".to_vec())
224 .key_pem_bytes(b"--FAKE KEY--".to_vec())
225 .build()
226 .unwrap();
227 assert!(matches!(cfg.cert, PemSource::Bytes(_)));
228 assert!(matches!(cfg.key, PemSource::Bytes(_)));
229 }
230
231 #[test]
232 fn builder_errors_when_cert_is_missing() {
233 let err = ServerTlsConfig::builder()
234 .key_pem_bytes(vec![1])
235 .build()
236 .unwrap_err();
237 assert!(matches!(err, TlsError::MissingField("cert")));
238 }
239
240 #[test]
241 fn builder_errors_when_key_is_missing() {
242 let err = ServerTlsConfig::builder()
243 .cert_pem_bytes(vec![1])
244 .build()
245 .unwrap_err();
246 assert!(matches!(err, TlsError::MissingField("key")));
247 }
248
249 #[test]
250 fn cert_pem_file_creates_path_source() {
251 let cfg = ServerTlsConfig::builder()
252 .cert_pem_file("/etc/server.crt")
253 .key_pem_bytes(vec![1])
254 .build()
255 .unwrap();
256 assert!(matches!(cfg.cert, PemSource::Path(_)));
257 }
258
259 #[test]
260 fn client_ca_defaults_to_none() {
261 let cfg = ServerTlsConfig::builder()
262 .cert_pem_bytes(vec![1])
263 .key_pem_bytes(vec![2])
264 .build()
265 .unwrap();
266 assert!(cfg.client_ca.is_none());
267 }
268
269 #[test]
270 fn require_client_ca_pem_bytes_enables_mtls() {
271 let cfg = ServerTlsConfig::builder()
272 .cert_pem_bytes(vec![1])
273 .key_pem_bytes(vec![2])
274 .require_client_ca_pem_bytes(b"--FAKE CA--".to_vec())
275 .build()
276 .unwrap();
277 assert!(matches!(cfg.client_ca, Some(PemSource::Bytes(_))));
278 }
279
280 #[test]
281 fn require_client_ca_pem_file_enables_mtls() {
282 let cfg = ServerTlsConfig::builder()
283 .cert_pem_bytes(vec![1])
284 .key_pem_bytes(vec![2])
285 .require_client_ca_pem_file("/etc/ca.crt")
286 .build()
287 .unwrap();
288 assert!(matches!(cfg.client_ca, Some(PemSource::Path(_))));
289 }
290
291 #[test]
292 fn alpn_defaults_to_empty() {
293 let cfg = ServerTlsConfig::builder()
294 .cert_pem_bytes(vec![1])
295 .key_pem_bytes(vec![2])
296 .build()
297 .unwrap();
298 assert!(cfg.alpn.is_empty());
299 }
300
301 #[test]
302 fn with_alpn_sets_protocols() {
303 let cfg = ServerTlsConfig::builder()
304 .cert_pem_bytes(vec![1])
305 .key_pem_bytes(vec![2])
306 .with_alpn(["h2", "http/1.1"])
307 .build()
308 .unwrap();
309 assert_eq!(cfg.alpn, vec![b"h2".to_vec(), b"http/1.1".to_vec()]);
310 }
311
312 fn rcgen_self_signed() -> (Vec<u8>, Vec<u8>) {
313 let b = rcgen::generate_simple_self_signed(vec!["example.com".into()]).unwrap();
314 (
315 b.cert.pem().into_bytes(),
316 b.signing_key.serialize_pem().into_bytes(),
317 )
318 }
319
320 #[test]
321 fn into_rustls_config_succeeds_with_real_cert_and_key() {
322 let (cert, key) = rcgen_self_signed();
323 let cfg = ServerTlsConfig::builder()
324 .cert_pem_bytes(cert)
325 .key_pem_bytes(key)
326 .build()
327 .unwrap();
328
329 let _rustls = cfg.into_rustls_config().unwrap();
330 }
331
332 #[test]
333 fn into_rustls_config_succeeds_with_mtls_client_ca() {
334 let (cert, key) = rcgen_self_signed();
335 let (ca, _) = rcgen_self_signed();
336 let cfg = ServerTlsConfig::builder()
337 .cert_pem_bytes(cert)
338 .key_pem_bytes(key)
339 .require_client_ca_pem_bytes(ca)
340 .build()
341 .unwrap();
342
343 let _rustls = cfg.into_rustls_config().unwrap();
344 }
345
346 #[test]
347 fn into_rustls_config_propagates_alpn_to_rustls() {
348 let (cert, key) = rcgen_self_signed();
349 let cfg = ServerTlsConfig::builder()
350 .cert_pem_bytes(cert)
351 .key_pem_bytes(key)
352 .with_alpn(["h2"])
353 .build()
354 .unwrap();
355
356 let rustls = cfg.into_rustls_config().unwrap();
357 assert_eq!(rustls.alpn_protocols, vec![b"h2".to_vec()]);
358 }
359
360 #[test]
361 fn into_rustls_config_rejects_cert_key_mismatch() {
362 let (cert, _) = rcgen_self_signed();
363 let (_, other_key) = rcgen_self_signed();
364 let cfg = ServerTlsConfig::builder()
365 .cert_pem_bytes(cert)
366 .key_pem_bytes(other_key)
367 .build()
368 .unwrap();
369
370 let err = cfg.into_rustls_config().unwrap_err();
371 assert!(
372 matches!(err, TlsError::Rustls(_)),
373 "cert/key mismatch must surface as TlsError::Rustls, got {err:?}"
374 );
375 }
376
377 #[test]
378 fn into_rustls_config_errors_on_malformed_cert_pem() {
379 let (_, key) = rcgen_self_signed();
380 let cfg = ServerTlsConfig::builder()
381 .cert_pem_bytes(b"not a pem".to_vec())
382 .key_pem_bytes(key)
383 .build()
384 .unwrap();
385
386 let err = cfg.into_rustls_config().unwrap_err();
387 assert!(
388 matches!(err, TlsError::NoCertificates),
389 "malformed cert PEM must surface as NoCertificates, got {err:?}"
390 );
391 }
392}