1use mockforge_core::config::HttpTlsConfig;
6use mockforge_core::Result;
7use std::sync::Arc;
8use tokio_rustls::TlsAcceptor;
9use tracing::{error, 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 cert_bytes: Vec<Vec<u8>> = certs(&mut cert_reader).map_err(|e| {
36 mockforge_core::Error::generic(format!(
37 "Failed to parse certificate file {}: {}",
38 config.cert_file, e
39 ))
40 })?;
41 let server_certs = cert_bytes.into_iter().map(rustls::Certificate).collect::<Vec<_>>();
42
43 if server_certs.is_empty() {
44 return Err(mockforge_core::Error::generic(format!(
45 "No certificates found in {}",
46 config.cert_file
47 )));
48 }
49
50 let key_file = File::open(&config.key_file).map_err(|e| {
52 mockforge_core::Error::generic(format!(
53 "Failed to open private key file {}: {}",
54 config.key_file, e
55 ))
56 })?;
57 let mut key_reader = BufReader::new(key_file);
58 let mut keys: Vec<Vec<u8>> = pkcs8_private_keys(&mut key_reader).map_err(|e| {
59 mockforge_core::Error::generic(format!(
60 "Failed to parse private key file {}: {}",
61 config.key_file, e
62 ))
63 })?;
64
65 if keys.is_empty() {
66 return Err(mockforge_core::Error::generic(format!(
67 "No private keys found in {}",
68 config.key_file
69 )));
70 }
71
72 let server_config = if config.require_client_cert {
75 if let Some(ref ca_file_path) = config.ca_file {
77 let ca_file = File::open(ca_file_path).map_err(|e| {
79 mockforge_core::Error::generic(format!(
80 "Failed to open CA certificate file {}: {}",
81 ca_file_path, e
82 ))
83 })?;
84 let mut ca_reader = BufReader::new(ca_file);
85 let ca_certs: Vec<Vec<u8>> = certs(&mut ca_reader).map_err(|e| {
86 mockforge_core::Error::generic(format!(
87 "Failed to parse CA certificate file {}: {}",
88 ca_file_path, e
89 ))
90 })?;
91
92 let ca_certs = ca_certs.into_iter().map(rustls::Certificate).collect::<Vec<_>>();
93
94 let mut root_store = rustls::RootCertStore::empty();
95 for cert in ca_certs {
96 root_store.add(&cert).map_err(|e| {
97 mockforge_core::Error::generic(format!(
98 "Failed to add CA certificate to root store: {}",
99 e
100 ))
101 })?;
102 }
103
104 rustls::server::ServerConfig::builder()
106 .with_safe_defaults()
107 .with_client_cert_verifier(Arc::new(
108 rustls::server::AllowAnyAuthenticatedClient::new(root_store),
109 ))
110 .with_single_cert(server_certs, rustls::PrivateKey(keys.remove(0)))
111 .map_err(|e| {
112 mockforge_core::Error::generic(format!("TLS config error (mTLS): {}", e))
113 })?
114 } else {
115 return Err(mockforge_core::Error::generic(
116 "Client certificate required (require_client_cert=true) but no CA file provided",
117 ));
118 }
119 } else {
120 rustls::server::ServerConfig::builder()
122 .with_safe_defaults()
123 .with_no_client_auth()
124 .with_single_cert(server_certs, rustls::PrivateKey(keys.remove(0)))
125 .map_err(|e| mockforge_core::Error::generic(format!("TLS config error: {}", e)))?
126 };
127
128 if config.min_version == "1.3" {
132 info!("TLS 1.3 requested (rustls safe defaults support both 1.2 and 1.3)");
133 } else if config.min_version != "1.2" && !config.min_version.is_empty() {
134 tracing::warn!(
135 "Unsupported TLS version: {}, using rustls safe defaults (1.2+)",
136 config.min_version
137 );
138 }
139
140 if !config.cipher_suites.is_empty() {
142 info!("Custom cipher suites specified but rustls uses safe defaults");
145 }
146
147 info!("TLS acceptor configured successfully");
148 Ok(TlsAcceptor::from(Arc::new(server_config)))
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use std::io::Write;
155 use tempfile::NamedTempFile;
156
157 fn create_test_cert() -> (NamedTempFile, NamedTempFile) {
158 let cert = NamedTempFile::new().unwrap();
161 let key = NamedTempFile::new().unwrap();
162
163 writeln!(cert.as_file(), "-----BEGIN CERTIFICATE-----").unwrap();
165 writeln!(cert.as_file(), "TEST").unwrap();
166 writeln!(cert.as_file(), "-----END CERTIFICATE-----").unwrap();
167
168 writeln!(key.as_file(), "-----BEGIN PRIVATE KEY-----").unwrap();
169 writeln!(key.as_file(), "TEST").unwrap();
170 writeln!(key.as_file(), "-----END PRIVATE KEY-----").unwrap();
171
172 (cert, key)
173 }
174
175 #[test]
176 fn test_tls_config_validation() {
177 let (cert, key) = create_test_cert();
178
179 let config = HttpTlsConfig {
180 enabled: true,
181 cert_file: cert.path().to_string_lossy().to_string(),
182 key_file: key.path().to_string_lossy().to_string(),
183 ca_file: None,
184 min_version: "1.2".to_string(),
185 cipher_suites: Vec::new(),
186 require_client_cert: false,
187 };
188
189 let result = load_tls_acceptor(&config);
192 assert!(result.is_err()); }
194
195 #[test]
196 fn test_mtls_requires_ca() {
197 let (cert, key) = create_test_cert();
198
199 let config = HttpTlsConfig {
200 enabled: true,
201 cert_file: cert.path().to_string_lossy().to_string(),
202 key_file: key.path().to_string_lossy().to_string(),
203 ca_file: None,
204 min_version: "1.2".to_string(),
205 cipher_suites: Vec::new(),
206 require_client_cert: true, };
208
209 let result = load_tls_acceptor(&config);
210 assert!(result.is_err());
211 let err_msg = format!("{}", result.err().unwrap());
212 assert!(err_msg.contains("no CA file provided") || err_msg.contains("CA file"));
213 }
214}