1use mockforge_core::config::HttpTlsConfig;
6use mockforge_core::Result;
7use std::sync::Arc;
8use tokio_rustls::TlsAcceptor;
9use tracing::info;
10
11pub fn load_tls_acceptor(config: &HttpTlsConfig) -> Result<TlsAcceptor> {
18 use rustls_pemfile::{certs, pkcs8_private_keys};
19 use std::fs::File;
20 use std::io::BufReader;
21
22 info!(
23 "Loading TLS certificate from {} and key from {}",
24 config.cert_file, config.key_file
25 );
26
27 let cert_file = File::open(&config.cert_file).map_err(|e| {
29 mockforge_core::Error::generic(format!(
30 "Failed to open certificate file {}: {}",
31 config.cert_file, e
32 ))
33 })?;
34 let mut cert_reader = BufReader::new(cert_file);
35 let server_certs: Vec<rustls::pki_types::CertificateDer<'static>> = certs(&mut cert_reader)
36 .collect::<std::result::Result<Vec<_>, _>>()
37 .map_err(|e| {
38 mockforge_core::Error::generic(format!(
39 "Failed to parse certificate file {}: {}",
40 config.cert_file, e
41 ))
42 })?;
43
44 if server_certs.is_empty() {
45 return Err(mockforge_core::Error::generic(format!(
46 "No certificates found in {}",
47 config.cert_file
48 )));
49 }
50
51 let key_file = File::open(&config.key_file).map_err(|e| {
53 mockforge_core::Error::generic(format!(
54 "Failed to open private key file {}: {}",
55 config.key_file, e
56 ))
57 })?;
58 let mut key_reader = BufReader::new(key_file);
59 let pkcs8_keys: Vec<rustls::pki_types::PrivatePkcs8KeyDer<'static>> =
60 pkcs8_private_keys(&mut key_reader)
61 .collect::<std::result::Result<Vec<_>, _>>()
62 .map_err(|e| {
63 mockforge_core::Error::generic(format!(
64 "Failed to parse private key file {}: {}",
65 config.key_file, e
66 ))
67 })?;
68 let mut keys: Vec<rustls::pki_types::PrivateKeyDer<'static>> = pkcs8_keys
69 .into_iter()
70 .map(|k| rustls::pki_types::PrivateKeyDer::Pkcs8(k))
71 .collect();
72
73 if keys.is_empty() {
74 return Err(mockforge_core::Error::generic(format!(
75 "No private keys found in {}",
76 config.key_file
77 )));
78 }
79
80 let mtls_mode = if !config.mtls_mode.is_empty() && config.mtls_mode != "off" {
84 config.mtls_mode.as_str()
85 } else if config.require_client_cert {
86 "required"
87 } else {
88 "off"
89 };
90
91 let server_config = match mtls_mode {
92 "required" => {
93 if let Some(ref ca_file_path) = config.ca_file {
95 let ca_file = File::open(ca_file_path).map_err(|e| {
97 mockforge_core::Error::generic(format!(
98 "Failed to open CA certificate file {}: {}",
99 ca_file_path, e
100 ))
101 })?;
102 let mut ca_reader = BufReader::new(ca_file);
103 let ca_certs: Vec<rustls::pki_types::CertificateDer<'static>> =
104 certs(&mut ca_reader).collect::<std::result::Result<Vec<_>, _>>().map_err(
105 |e| {
106 mockforge_core::Error::generic(format!(
107 "Failed to parse CA certificate file {}: {}",
108 ca_file_path, e
109 ))
110 },
111 )?;
112
113 let mut root_store = rustls::RootCertStore::empty();
114 for cert in &ca_certs {
115 root_store.add(cert.clone()).map_err(|e| {
116 mockforge_core::Error::generic(format!(
117 "Failed to add CA certificate to root store: {}",
118 e
119 ))
120 })?;
121 }
122
123 let client_verifier =
124 rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
125 .build()
126 .map_err(|e| {
127 mockforge_core::Error::generic(format!(
128 "Failed to build client verifier: {}",
129 e
130 ))
131 })?;
132
133 let key = keys.remove(0);
134
135 rustls::server::ServerConfig::builder()
137 .with_client_cert_verifier(client_verifier.into())
138 .with_single_cert(server_certs, key)
139 .map_err(|e| {
140 mockforge_core::Error::generic(format!(
141 "TLS config error (mTLS required): {}",
142 e
143 ))
144 })?
145 } else {
146 return Err(mockforge_core::Error::generic(
147 "mTLS mode 'required' requires --tls-ca (CA certificate file)",
148 ));
149 }
150 }
151 "optional" => {
152 if let Some(ref ca_file_path) = config.ca_file {
154 let ca_file = File::open(ca_file_path).map_err(|e| {
156 mockforge_core::Error::generic(format!(
157 "Failed to open CA certificate file {}: {}",
158 ca_file_path, e
159 ))
160 })?;
161 let mut ca_reader = BufReader::new(ca_file);
162 let ca_certs: Vec<rustls::pki_types::CertificateDer<'static>> =
163 certs(&mut ca_reader).collect::<std::result::Result<Vec<_>, _>>().map_err(
164 |e| {
165 mockforge_core::Error::generic(format!(
166 "Failed to parse CA certificate file {}: {}",
167 ca_file_path, e
168 ))
169 },
170 )?;
171
172 let mut root_store = rustls::RootCertStore::empty();
173 for cert in &ca_certs {
174 root_store.add(cert.clone()).map_err(|e| {
175 mockforge_core::Error::generic(format!(
176 "Failed to add CA certificate to root store: {}",
177 e
178 ))
179 })?;
180 }
181
182 let client_verifier =
183 rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
184 .build()
185 .map_err(|e| {
186 mockforge_core::Error::generic(format!(
187 "Failed to build client verifier: {}",
188 e
189 ))
190 })?;
191
192 let key = keys.remove(0);
193
194 rustls::server::ServerConfig::builder()
200 .with_client_cert_verifier(client_verifier.into())
201 .with_single_cert(server_certs, key)
202 .map_err(|e| {
203 mockforge_core::Error::generic(format!(
204 "TLS config error (mTLS optional): {}",
205 e
206 ))
207 })?
208 } else {
209 info!("mTLS optional mode specified but no CA file provided, using standard TLS");
211 let key = keys.remove(0);
212 rustls::server::ServerConfig::builder()
213 .with_no_client_auth()
214 .with_single_cert(server_certs, key)
215 .map_err(|e| {
216 mockforge_core::Error::generic(format!("TLS config error: {}", e))
217 })?
218 }
219 }
220 _ => {
221 let key = keys.remove(0);
223 rustls::server::ServerConfig::builder()
224 .with_no_client_auth()
225 .with_single_cert(server_certs, key)
226 .map_err(|e| mockforge_core::Error::generic(format!("TLS config error: {}", e)))?
227 }
228 };
229
230 if config.min_version == "1.3" {
234 info!("TLS 1.3 requested (rustls safe defaults support both 1.2 and 1.3)");
235 } else if config.min_version != "1.2" && !config.min_version.is_empty() {
236 tracing::warn!(
237 "Unsupported TLS version: {}, using rustls safe defaults (1.2+)",
238 config.min_version
239 );
240 }
241
242 if !config.cipher_suites.is_empty() {
244 info!("Custom cipher suites specified but rustls uses safe defaults");
247 }
248
249 info!("TLS acceptor configured successfully");
250 Ok(TlsAcceptor::from(Arc::new(server_config)))
251}
252
253pub fn load_tls_server_config(
258 config: &HttpTlsConfig,
259) -> std::result::Result<Arc<rustls::server::ServerConfig>, Box<dyn std::error::Error + Send + Sync>>
260{
261 use rustls_pemfile::{certs, pkcs8_private_keys};
262 use std::fs::File;
263 use std::io::BufReader;
264 use std::sync::Arc;
265
266 info!(
267 "Loading TLS certificate from {} and key from {}",
268 config.cert_file, config.key_file
269 );
270
271 let cert_file = File::open(&config.cert_file)
273 .map_err(|e| format!("Failed to open certificate file {}: {}", config.cert_file, e))?;
274 let mut cert_reader = BufReader::new(cert_file);
275 let server_certs: Vec<rustls::pki_types::CertificateDer<'static>> = certs(&mut cert_reader)
276 .collect::<std::result::Result<Vec<_>, _>>()
277 .map_err(|e| format!("Failed to parse certificate file {}: {}", config.cert_file, e))?;
278
279 if server_certs.is_empty() {
280 return Err(format!("No certificates found in {}", config.cert_file).into());
281 }
282
283 let key_file = File::open(&config.key_file)
285 .map_err(|e| format!("Failed to open private key file {}: {}", config.key_file, e))?;
286 let mut key_reader = BufReader::new(key_file);
287 let pkcs8_keys: Vec<rustls::pki_types::PrivatePkcs8KeyDer<'static>> =
288 pkcs8_private_keys(&mut key_reader)
289 .collect::<std::result::Result<Vec<_>, _>>()
290 .map_err(|e| format!("Failed to parse private key file {}: {}", config.key_file, e))?;
291 let mut keys: Vec<rustls::pki_types::PrivateKeyDer<'static>> = pkcs8_keys
292 .into_iter()
293 .map(|k| rustls::pki_types::PrivateKeyDer::Pkcs8(k))
294 .collect();
295
296 if keys.is_empty() {
297 return Err(format!("No private keys found in {}", config.key_file).into());
298 }
299
300 let mtls_mode = if !config.mtls_mode.is_empty() && config.mtls_mode != "off" {
302 config.mtls_mode.as_str()
303 } else if config.require_client_cert {
304 "required"
305 } else {
306 "off"
307 };
308
309 let server_config = match mtls_mode {
310 "required" => {
311 if let Some(ref ca_file_path) = config.ca_file {
312 let ca_file = File::open(ca_file_path).map_err(|e| {
313 format!("Failed to open CA certificate file {}: {}", ca_file_path, e)
314 })?;
315 let mut ca_reader = BufReader::new(ca_file);
316 let ca_certs: Vec<rustls::pki_types::CertificateDer<'static>> =
317 certs(&mut ca_reader).collect::<std::result::Result<Vec<_>, _>>().map_err(
318 |e| format!("Failed to parse CA certificate file {}: {}", ca_file_path, e),
319 )?;
320
321 let mut root_store = rustls::RootCertStore::empty();
322 for cert in &ca_certs {
323 root_store.add(cert.clone()).map_err(|e| {
324 format!("Failed to add CA certificate to root store: {}", e)
325 })?;
326 }
327
328 let client_verifier =
329 rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
330 .build()
331 .map_err(|e| format!("Failed to build client verifier: {}", e))?;
332
333 let key = keys.remove(0);
334
335 rustls::server::ServerConfig::builder()
336 .with_client_cert_verifier(client_verifier.into())
337 .with_single_cert(server_certs, key)
338 .map_err(|e| format!("TLS config error (mTLS required): {}", e))?
339 } else {
340 return Err("mTLS mode 'required' requires CA certificate file".to_string().into());
341 }
342 }
343 "optional" => {
344 if let Some(ref ca_file_path) = config.ca_file {
345 let ca_file = File::open(ca_file_path).map_err(|e| {
346 format!("Failed to open CA certificate file {}: {}", ca_file_path, e)
347 })?;
348 let mut ca_reader = BufReader::new(ca_file);
349 let ca_certs: Vec<rustls::pki_types::CertificateDer<'static>> =
350 certs(&mut ca_reader).collect::<std::result::Result<Vec<_>, _>>().map_err(
351 |e| format!("Failed to parse CA certificate file {}: {}", ca_file_path, e),
352 )?;
353
354 let mut root_store = rustls::RootCertStore::empty();
355 for cert in &ca_certs {
356 root_store.add(cert.clone()).map_err(|e| {
357 format!("Failed to add CA certificate to root store: {}", e)
358 })?;
359 }
360
361 let client_verifier =
362 rustls::server::WebPkiClientVerifier::builder(Arc::new(root_store))
363 .build()
364 .map_err(|e| format!("Failed to build client verifier: {}", e))?;
365
366 let key = keys.remove(0);
367
368 rustls::server::ServerConfig::builder()
369 .with_client_cert_verifier(client_verifier.into())
370 .with_single_cert(server_certs, key)
371 .map_err(|e| format!("TLS config error (mTLS optional): {}", e))?
372 } else {
373 let key = keys.remove(0);
374 rustls::server::ServerConfig::builder()
375 .with_no_client_auth()
376 .with_single_cert(server_certs, key)
377 .map_err(|e| format!("TLS config error: {}", e))?
378 }
379 }
380 _ => {
381 let key = keys.remove(0);
382 rustls::server::ServerConfig::builder()
383 .with_no_client_auth()
384 .with_single_cert(server_certs, key)
385 .map_err(|e| format!("TLS config error: {}", e))?
386 }
387 };
388
389 Ok(Arc::new(server_config))
390}
391
392#[cfg(test)]
393mod tests {
394 use super::*;
395 use std::io::Write;
396 use tempfile::NamedTempFile;
397
398 fn create_test_cert() -> (NamedTempFile, NamedTempFile) {
399 let cert = NamedTempFile::new().unwrap();
402 let key = NamedTempFile::new().unwrap();
403
404 writeln!(cert.as_file(), "-----BEGIN CERTIFICATE-----").unwrap();
406 writeln!(cert.as_file(), "TEST").unwrap();
407 writeln!(cert.as_file(), "-----END CERTIFICATE-----").unwrap();
408
409 writeln!(key.as_file(), "-----BEGIN PRIVATE KEY-----").unwrap();
410 writeln!(key.as_file(), "TEST").unwrap();
411 writeln!(key.as_file(), "-----END PRIVATE KEY-----").unwrap();
412
413 (cert, key)
414 }
415
416 #[test]
417 fn test_tls_config_validation() {
418 let (cert, key) = create_test_cert();
419
420 let config = HttpTlsConfig {
421 enabled: true,
422 cert_file: cert.path().to_string_lossy().to_string(),
423 key_file: key.path().to_string_lossy().to_string(),
424 ca_file: None,
425 min_version: "1.2".to_string(),
426 cipher_suites: Vec::new(),
427 require_client_cert: false,
428 mtls_mode: "off".to_string(),
429 };
430
431 let result = load_tls_acceptor(&config);
434 assert!(result.is_err()); }
436
437 #[test]
438 fn test_mtls_requires_ca() {
439 let (cert, key) = create_test_cert();
440
441 let config = HttpTlsConfig {
442 enabled: true,
443 cert_file: cert.path().to_string_lossy().to_string(),
444 key_file: key.path().to_string_lossy().to_string(),
445 ca_file: None,
446 min_version: "1.2".to_string(),
447 cipher_suites: Vec::new(),
448 require_client_cert: true, mtls_mode: "required".to_string(),
450 };
451
452 let result = load_tls_acceptor(&config);
453 assert!(result.is_err());
454 let err_msg = format!("{}", result.err().unwrap());
455 assert!(err_msg.contains("no CA file provided") || err_msg.contains("CA file"));
456 }
457}