1use std::fmt;
18use std::path::PathBuf;
19use std::sync::{Arc, RwLock};
20
21use rustls::ServerConfig;
22use rustls::pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject};
23use rustls::server::{ClientHello, ResolvesServerCert};
24use rustls::sign::CertifiedKey;
25
26use crate::error::{ServerError, ServerResult, TlsKind};
27
28#[allow(clippy::result_large_err)]
35pub fn load_certs(file_path: &str) -> ServerResult<Vec<CertificateDer<'static>>> {
36 let path = PathBuf::from(file_path);
37 let iter = CertificateDer::pem_file_iter(file_path).map_err(|e| ServerError::TlsLoad {
38 kind: TlsKind::Certificate,
39 path: path.clone(),
40 reason: e.to_string(),
41 })?;
42
43 let mut certs = Vec::new();
44 for (idx, item) in iter.enumerate() {
45 let cert = item.map_err(|e| ServerError::TlsLoad {
46 kind: TlsKind::Certificate,
47 path: path.clone(),
48 reason: format!("failed to parse certificate #{}: {}", idx + 1, e),
49 })?;
50 certs.push(cert);
51 }
52
53 if certs.is_empty() {
54 return Err(ServerError::TlsLoad {
55 kind: TlsKind::Certificate,
56 path,
57 reason: "no certificates found in PEM file".to_owned(),
58 });
59 }
60
61 Ok(certs)
62}
63
64#[allow(clippy::result_large_err)]
69pub fn load_private_key(file_path: &str) -> ServerResult<PrivateKeyDer<'static>> {
70 PrivateKeyDer::from_pem_file(file_path).map_err(|e| ServerError::TlsLoad {
71 kind: TlsKind::PrivateKey,
72 path: PathBuf::from(file_path),
73 reason: e.to_string(),
74 })
75}
76
77#[derive(Debug, Clone)]
81pub struct TlsReloadError {
82 pub reason: String,
83}
84
85impl fmt::Display for TlsReloadError {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 write!(f, "TLS cert reload failed: {}", self.reason)
88 }
89}
90
91impl std::error::Error for TlsReloadError {}
92
93fn make_certified_key(
97 certs: Vec<CertificateDer<'static>>,
98 key: PrivateKeyDer<'static>,
99) -> Result<CertifiedKey, TlsReloadError> {
100 let signing_key =
101 rustls::crypto::ring::sign::any_supported_type(&key).map_err(|e| TlsReloadError {
102 reason: format!("unsupported private key type: {}", e),
103 })?;
104 Ok(CertifiedKey::new(certs, signing_key))
105}
106
107#[derive(Debug)]
123pub struct ReloadableCertResolver {
124 inner: RwLock<Arc<CertifiedKey>>,
125}
126
127impl ReloadableCertResolver {
128 pub fn new(
130 certs: Vec<CertificateDer<'static>>,
131 key: PrivateKeyDer<'static>,
132 ) -> Result<Self, TlsReloadError> {
133 let ck = make_certified_key(certs, key)?;
134 Ok(Self {
135 inner: RwLock::new(Arc::new(ck)),
136 })
137 }
138
139 pub fn reload_from_paths(&self, cert_path: &str, key_path: &str) -> Result<(), TlsReloadError> {
144 let certs = load_certs(cert_path).map_err(|e| TlsReloadError {
145 reason: e.to_string(),
146 })?;
147 let key = load_private_key(key_path).map_err(|e| TlsReloadError {
148 reason: e.to_string(),
149 })?;
150 let new_ck = make_certified_key(certs, key)?;
151
152 let mut guard = self.inner.write().map_err(|_| TlsReloadError {
153 reason: "cert RwLock poisoned".to_owned(),
154 })?;
155 *guard = Arc::new(new_ck);
156 log::info!("TLS certificate reloaded successfully from {}", cert_path);
157 Ok(())
158 }
159}
160
161impl ResolvesServerCert for ReloadableCertResolver {
162 fn resolve(&self, _client_hello: ClientHello<'_>) -> Option<Arc<CertifiedKey>> {
163 self.inner.read().ok().map(|g| Arc::clone(&*g))
164 }
165}
166
167pub fn build_server_config_static(
174 certs: Vec<CertificateDer<'static>>,
175 key: PrivateKeyDer<'static>,
176) -> Result<ServerConfig, String> {
177 let mut config = ServerConfig::builder()
178 .with_no_client_auth()
179 .with_single_cert(certs, key)
180 .map_err(|e| format!("failed to build rustls ServerConfig: {}", e))?;
181 config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
182 Ok(config)
183}
184
185pub fn build_server_config_reloadable(
190 certs: Vec<CertificateDer<'static>>,
191 key: PrivateKeyDer<'static>,
192) -> Result<(ServerConfig, Arc<ReloadableCertResolver>), TlsReloadError> {
193 let resolver = Arc::new(ReloadableCertResolver::new(certs, key)?);
194 let mut config = ServerConfig::builder()
195 .with_no_client_auth()
196 .with_cert_resolver(Arc::clone(&resolver) as Arc<dyn ResolvesServerCert>);
197 config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
198 Ok((config, resolver))
199}
200
201#[cfg(test)]
204mod tests {
205 use super::*;
206
207 fn write_pem_file(path: &str, content: &str) {
208 std::fs::write(path, content).unwrap();
209 }
210
211 const TEST_CERT_PEM: &str = "-----BEGIN CERTIFICATE-----\n\
215MIIBczCCARmgAwIBAgIUNNKjB+m5H6ZCjEPHNFEL5GYW3/UwCgYIKoZIzj0EAwIw\n\
216DzENMAsGA1UEAwwEdGVzdDAeFw0yNjA1MjIwMjQ0NTZaFw0zNjA1MTkwMjQ0NTZa\n\
217MA8xDTALBgNVBAMMBHRlc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQTo544\n\
218m3Yk+4kNlcFXR8RL5rtGVqrZohzvanN7oUiIYXzpofwYNBLqLg9AOZPeiX32aizX\n\
219wqEBuYMV4B6gBj1Ho1MwUTAdBgNVHQ4EFgQUxoL28LxPMcYmwNAvUCIaaZp02xAw\n\
220HwYDVR0jBBgwFoAUxoL28LxPMcYmwNAvUCIaaZp02xAwDwYDVR0TAQH/BAUwAwEB\n\
221/zAKBggqhkjOPQQDAgNIADBFAiEA2IO7sD+CIM4OWZkF0SMCmrnus/xQbNFBICXg\n\
222YNQ/K+oCIGlsqHA+PmxwUknuDDS5dQF26iNztRz2PY4diIfWxLNi\n\
223-----END CERTIFICATE-----\n";
224
225 const TEST_KEY_PEM: &str = "-----BEGIN EC PRIVATE KEY-----\n\
226MHcCAQEEIBK3C/2yAvhbvjxP7f5aCgVZN9udnXStns0xKk7LQ3RnoAoGCCqGSM49\n\
227AwEHoUQDQgAEE6OeOJt2JPuJDZXBV0fES+a7Rlaq2aIc72pze6FIiGF86aH8GDQS\n\
2286i4PQDmT3ol99mos18KhAbmDFeAeoAY9Rw==\n\
229-----END EC PRIVATE KEY-----\n";
230
231 #[test]
232 fn load_certs_returns_error_for_missing_file() {
233 let result = load_certs("/nonexistent/cert.pem");
234 assert!(result.is_err());
235 }
236
237 #[test]
238 fn load_private_key_returns_error_for_missing_file() {
239 let result = load_private_key("/nonexistent/key.pem");
240 assert!(result.is_err());
241 }
242
243 #[test]
244 fn reloadable_resolver_init_and_reload_bad_path_keeps_old_cert() {
245 let cert_path = "/tmp/apimock_test_cert.pem";
246 let key_path = "/tmp/apimock_test_key.pem";
247 write_pem_file(cert_path, TEST_CERT_PEM);
248 write_pem_file(key_path, TEST_KEY_PEM);
249
250 let certs = load_certs(cert_path).expect("load test cert");
251 let key = load_private_key(key_path).expect("load test key");
252 let resolver = ReloadableCertResolver::new(certs, key).expect("build resolver");
253
254 let result = resolver.reload_from_paths("/no/cert.pem", "/no/key.pem");
256 assert!(result.is_err(), "expected error for missing paths");
257
258 let guard = resolver.inner.read().unwrap();
260 drop(guard);
261 }
262
263 #[test]
264 fn reloadable_resolver_reload_from_same_files_succeeds() {
265 let cert_path = "/tmp/apimock_test_cert2.pem";
266 let key_path = "/tmp/apimock_test_key2.pem";
267 write_pem_file(cert_path, TEST_CERT_PEM);
268 write_pem_file(key_path, TEST_KEY_PEM);
269
270 let certs = load_certs(cert_path).expect("load test cert");
271 let key = load_private_key(key_path).expect("load test key");
272 let resolver = ReloadableCertResolver::new(certs, key).expect("build resolver");
273
274 let result = resolver.reload_from_paths(cert_path, key_path);
276 assert!(
277 result.is_ok(),
278 "reload from same valid files must succeed: {:?}",
279 result
280 );
281 }
282
283 #[test]
284 fn build_server_config_reloadable_returns_resolver() {
285 let cert_path = "/tmp/apimock_test_cert3.pem";
286 let key_path = "/tmp/apimock_test_key3.pem";
287 write_pem_file(cert_path, TEST_CERT_PEM);
288 write_pem_file(key_path, TEST_KEY_PEM);
289
290 let certs = load_certs(cert_path).unwrap();
291 let key = load_private_key(key_path).unwrap();
292 let result = build_server_config_reloadable(certs, key);
293 assert!(
294 result.is_ok(),
295 "build_server_config_reloadable failed: {:?}",
296 result
297 );
298 let (_config, resolver) = result.unwrap();
299 let reload = resolver.reload_from_paths(cert_path, key_path);
301 assert!(reload.is_ok());
302 }
303}