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