hashiverse_server_lib/transport/
https_transport_cert_refresher.rs1use anyhow::Context;
20use hashiverse_lib::tools::config::USE_PRODUCTION_LETS_ENCRYPT;
21use std::path::Path;
22use hashiverse_lib::tools::time::{TimeMillis};
23use hashiverse_lib::tools::{config, tools};
24use instant_acme::{Account, AuthorizationStatus, ChallengeType, Identifier, LetsEncrypt, NewAccount, NewOrder, OrderStatus, RetryPolicy};
25use log::{info, trace, warn};
26use parking_lot::RwLock;
27use rcgen::{CertificateParams, SanType, generate_simple_self_signed};
28use rustls::server::{ClientHello, ResolvesServerCert};
29use rustls::sign::CertifiedKey;
30use rustls_pki_types::pem::PemObject;
31use rustls_pki_types::{CertificateDer, PrivateKeyDer};
32use std::fs;
33use std::net::IpAddr;
34use std::path::PathBuf;
35use std::str::FromStr;
36use std::sync::Arc;
37use tokio_util::sync::CancellationToken;
38use hashiverse_lib::tools::time_provider::time_provider::{RealTimeProvider, TimeProvider};
39
40pub const FILENAME_LAST_REFRESHED: &str = "last_refreshed";
41pub const FILENAME_CERT: &str = "cert.pem";
42pub const FILENAME_KEY: &str = "key.pem";
43
44#[derive(Debug)]
45pub struct HttpsTransportCertRefresher {
46 force_local_network: bool,
47 path_certs: PathBuf,
48 filename_cert: PathBuf,
49 filename_key: PathBuf,
50 filename_last_refreshed: PathBuf,
51
52 ip: String,
53 port: u16,
54
55 pub base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>>,
57
58 pub challenge_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>>,
60}
61
62impl HttpsTransportCertRefresher {
63 pub async fn refresh_cert(&self) -> anyhow::Result<()> {
64 trace!("refreshing certificate");
65
66 let directory_url = match USE_PRODUCTION_LETS_ENCRYPT {
67 true => LetsEncrypt::Production.url().to_owned(),
68 false => LetsEncrypt::Staging.url().to_owned(),
69 };
70
71 let (account, _credentials) = Account::builder()?
72 .create(
73 &NewAccount {
74 contact: &[],
75 terms_of_service_agreed: true,
76 only_return_existing: false,
77 },
78 directory_url,
79 None,
80 )
81 .await?;
82
83 let ip_addr = IpAddr::from_str(&self.ip)?;
86 let identifiers = [Identifier::Ip(ip_addr)];
87 let new_order = NewOrder::new(&identifiers).profile("shortlived");
88
89 let mut order = account.new_order(&new_order).await?;
90 let mut challenge_url = "".to_string();
91 {
95 let mut authorizations = order.authorizations();
96 while let Some(result) = authorizations.next().await {
97 let mut authz = result?;
98 match authz.status {
99 AuthorizationStatus::Valid => continue,
100 AuthorizationStatus::Pending => {}
101 _ => {
102 warn!("unexpected AuthorizationStatus {:?}", authz.status);
103 continue;
104 }
105 }
106
107 let mut challenge = authz.challenge(ChallengeType::TlsAlpn01).ok_or_else(|| anyhow::anyhow!("no tlsalpn01 challenge found"))?;
108 challenge_url = challenge.url.clone();
109
110 {
112 let key_auth_sha256 = challenge.key_authorization().digest();
113
114 let mut certificate_params = CertificateParams::default();
115 certificate_params.subject_alt_names = vec![SanType::IpAddress(ip_addr)];
116 certificate_params.custom_extensions.push(rcgen::CustomExtension::new_acme_identifier(key_auth_sha256.as_ref()));
117
118 let key_pair = rcgen::KeyPair::generate()?;
119 let cert = certificate_params.self_signed(&key_pair)?;
120
121 let cert_chain = vec![rustls_pki_types::CertificateDer::from(cert.der().to_vec())];
122 let key_der = rustls_pki_types::PrivatePkcs8KeyDer::from(key_pair.serialize_der());
123 let key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&rustls_pki_types::PrivateKeyDer::Pkcs8(key_der)).map_err(|_| anyhow::anyhow!("Unsupported key type"))?;
124
125 let certified_key = CertifiedKey { cert: cert_chain, key, ocsp: None };
126
127 *self.challenge_cert.write() = Some(Arc::new(certified_key));
128 }
129
130 challenge.set_ready().await?;
132 }
133 }
134
135 trace!("challenge.url: {}", challenge_url);
137
138 let status = order.poll_ready(&RetryPolicy::default()).await?;
140 if status != OrderStatus::Ready {
141 anyhow::bail!("unexpected order status: {status:?}");
142 }
143
144 let private_key_pem = order.finalize().await?;
146 let cert_chain_pem = order.poll_certificate(&RetryPolicy::default()).await?;
147
148 info!("writing new certificate to disk");
150 fs::create_dir_all(&self.path_certs)?;
151 fs::write(&self.filename_cert, cert_chain_pem)?;
152 write_private_key_file(&self.filename_key, private_key_pem.as_bytes())?;
153 fs::write(&self.filename_last_refreshed, challenge_url)?;
154
155 info!("refreshed certificate");
156
157 Ok(())
158 }
159
160 pub fn reload_certs(&self) -> anyhow::Result<()> {
161 trace!("reloading certificate");
162
163 let certified_key: CertifiedKey = {
164 let bytes_cert = fs::read(&self.filename_cert)?;
165 let bytes_key = fs::read(&self.filename_key)?;
166
167 let cert_chain = CertificateDer::pem_slice_iter(&bytes_cert).collect::<Result<Vec<_>, _>>().with_context(|| format!("parsing certificates in {}", self.filename_cert.display()))?;
169 if cert_chain.is_empty() {
171 anyhow::bail!("No certificates found in {}", self.filename_cert.display());
172 }
173 let key_der = PrivateKeyDer::from_pem_slice(&bytes_key).map_err(|e| anyhow::anyhow!("No private key found in {}: {}", self.filename_key.display(), e))?;
174 let key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key_der).map_err(|_| anyhow::anyhow!("Unsupported key type"))?;
175
176 CertifiedKey { cert: cert_chain, key, ocsp: None }
177 };
178
179 *self.base_cert.write() = Some(Arc::new(certified_key));
180
181 Ok(())
182 }
183
184 fn certs_last_refreshed(&self) -> anyhow::Result<TimeMillis> {
185 let certs_last_refreshed = fs::metadata(&self.filename_last_refreshed)
186 .map(|metadata| metadata.modified())
187 .unwrap_or_else(|_| Ok(std::time::SystemTime::UNIX_EPOCH))
188 .with_context(|| "checking last refreshed filename")?;
189
190 let certs_last_refreshed: TimeMillis = certs_last_refreshed.into();
191
192 Ok(certs_last_refreshed)
193 }
194
195 pub async fn process(&self, cancellation_token: CancellationToken) -> anyhow::Result<()> {
196 let time_provider = RealTimeProvider;
197
198 let mut certs_last_attempted = TimeMillis::zero();
199
200 loop {
201 if cancellation_token.is_cancelled() {
202 break;
203 }
204
205 let result: anyhow::Result<()> = try {
206 if !self.force_local_network {
208 if 443u16 == self.port {
210 let now_millis = time_provider.current_time_millis();
212 if (now_millis - self.certs_last_refreshed()?) > config::MILLIS_TO_WAIT_BETWEEN_CERT_RENEWALS {
213 if (now_millis - certs_last_attempted) > config::MILLIS_TO_WAIT_BETWEEN_CERT_RENEWAL_FAILURES {
214 certs_last_attempted = now_millis;
215 self.refresh_cert().await?;
216 }
217 else {
218 trace!("we refreshed certs too recently to try again");
219 }
220 }
221 else {
222 trace!("we have a recent enough cert on disk");
223 }
224 }
225 else {
226 trace!("skipping cert refresh because port {} != 443", self.port);
227 }
228 }
229 else {
230 trace!("skipping cert refresh because force_local_network");
231 }
232
233 self.reload_certs()?;
235 };
236
237 if let Err(e) = result {
238 warn!("error while refreshing certs: {}", e);
239 }
240
241 tools::cancellable_sleep_millis(&time_provider, config::MILLIS_TO_WAIT_BETWEEN_CERT_RENEWAL_CHECKS, &cancellation_token).await;
242 }
243
244 trace!("stopped HttpsTransportCertRefresher");
245
246 Ok(())
247 }
248}
249
250fn write_private_key_file(path: &Path, contents: &[u8]) -> anyhow::Result<()> {
253 #[cfg(unix)]
254 {
255 use std::io::Write;
256 use std::os::unix::fs::OpenOptionsExt;
257 let mut file = fs::OpenOptions::new()
258 .write(true)
259 .create(true)
260 .truncate(true)
261 .mode(0o600)
262 .open(path)?;
263 file.write_all(contents)?;
264 }
265 #[cfg(not(unix))]
266 {
267 fs::write(path, contents)?;
268 }
269 Ok(())
270}
271
272impl HttpsTransportCertRefresher {
273 pub fn new(path_certs: PathBuf, ip: String, port: u16, force_local_network: bool) -> anyhow::Result<Self> {
274 let filename_cert = path_certs.join(FILENAME_CERT);
275 let filename_key = path_certs.join(FILENAME_KEY);
276 let filename_last_refreshed = path_certs.join(FILENAME_LAST_REFRESHED);
277
278 if !fs::exists(&filename_cert)? || !fs::exists(&filename_key)? {
282 info!("generating self-signed certs");
283 let subject_alt_names = vec![ip.clone()];
284
285 let certified_key = generate_simple_self_signed(subject_alt_names)?;
286 fs::create_dir_all(&path_certs)?;
287 fs::write(&filename_cert, certified_key.cert.pem())?;
288 write_private_key_file(&filename_key, certified_key.signing_key.serialize_pem().as_bytes())?;
289 }
290
291 let base_cert = Arc::new(RwLock::new(None));
293 let challenge_cert = Arc::new(RwLock::new(None));
294
295 Ok(Self {
296 force_local_network,
297 path_certs,
298 filename_cert,
299 filename_key,
300 filename_last_refreshed,
301 ip,
302 port,
303 base_cert,
304 challenge_cert,
305 })
306 }
307}
308
309impl ResolvesServerCert for HttpsTransportCertRefresher {
310 fn resolve(&self, client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
311 let is_acme = client_hello.alpn().map(|mut iter| iter.any(|proto| proto == b"acme-tls/1")).unwrap_or(false);
313
314 if is_acme {
315 info!("we have an acme challenge");
317 self.challenge_cert.read().clone()
318 }
319 else {
320 self.base_cert.read().clone()
322 }
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329 use hashiverse_lib::tools::tools::get_temp_dir;
330
331 const TEST_IP: &str = "127.0.0.1";
332
333 fn write_self_signed_pem_files(path_certs: &Path, extra_chain_certificate_count: usize) -> anyhow::Result<Vec<Vec<u8>>> {
336 fs::create_dir_all(path_certs)?;
337 let mut expected_chain_ders = Vec::new();
338 let mut cert_chain_pem = String::new();
339
340 let leaf_certified_key = generate_simple_self_signed(vec![TEST_IP.to_string()])?;
341 expected_chain_ders.push(leaf_certified_key.cert.der().to_vec());
342 cert_chain_pem.push_str(&leaf_certified_key.cert.pem());
343
344 for _ in 0..extra_chain_certificate_count {
345 let intermediate_certified_key = generate_simple_self_signed(vec![TEST_IP.to_string()])?;
346 expected_chain_ders.push(intermediate_certified_key.cert.der().to_vec());
347 cert_chain_pem.push_str(&intermediate_certified_key.cert.pem());
348 }
349
350 fs::write(path_certs.join(FILENAME_CERT), cert_chain_pem)?;
351 write_private_key_file(&path_certs.join(FILENAME_KEY), leaf_certified_key.signing_key.serialize_pem().as_bytes())?;
352 Ok(expected_chain_ders)
353 }
354
355 fn loaded_chain_ders(https_transport_cert_refresher: &HttpsTransportCertRefresher) -> Option<Vec<Vec<u8>>> {
356 https_transport_cert_refresher.base_cert.read().as_ref().map(|certified_key| certified_key.cert.iter().map(|certificate_der| certificate_der.as_ref().to_vec()).collect())
357 }
358
359 #[test]
360 fn new_generates_self_signed_pems_that_reload_certs_accepts() -> anyhow::Result<()> {
361 let (_temp_dir_handle, temp_dir_path) = get_temp_dir()?;
362 let path_certs = PathBuf::from(&temp_dir_path).join("certs");
363
364 let https_transport_cert_refresher = HttpsTransportCertRefresher::new(path_certs.clone(), TEST_IP.to_string(), 0, true)?;
365 assert!(path_certs.join(FILENAME_CERT).exists());
366 assert!(path_certs.join(FILENAME_KEY).exists());
367 assert!(https_transport_cert_refresher.base_cert.read().is_none());
368
369 https_transport_cert_refresher.reload_certs()?;
370 let loaded_chain_ders = loaded_chain_ders(&https_transport_cert_refresher).expect("base_cert populated after reload");
371 assert_eq!(loaded_chain_ders.len(), 1);
372 Ok(())
373 }
374
375 #[test]
376 fn reload_certs_loads_full_chain_in_order() -> anyhow::Result<()> {
377 let (_temp_dir_handle, temp_dir_path) = get_temp_dir()?;
378 let path_certs = PathBuf::from(&temp_dir_path).join("certs");
379 let expected_chain_ders = write_self_signed_pem_files(&path_certs, 1)?;
380
381 let https_transport_cert_refresher = HttpsTransportCertRefresher::new(path_certs, TEST_IP.to_string(), 0, true)?;
382 https_transport_cert_refresher.reload_certs()?;
383
384 assert_eq!(loaded_chain_ders(&https_transport_cert_refresher), Some(expected_chain_ders));
385 Ok(())
386 }
387
388 #[test]
389 fn reload_certs_errors_when_key_file_has_no_private_key() -> anyhow::Result<()> {
390 let (_temp_dir_handle, temp_dir_path) = get_temp_dir()?;
391 let path_certs = PathBuf::from(&temp_dir_path).join("certs");
392 write_self_signed_pem_files(&path_certs, 0)?;
393
394 let cert_pem = fs::read(path_certs.join(FILENAME_CERT))?;
396 fs::write(path_certs.join(FILENAME_KEY), cert_pem)?;
397
398 let https_transport_cert_refresher = HttpsTransportCertRefresher::new(path_certs, TEST_IP.to_string(), 0, true)?;
399 assert!(https_transport_cert_refresher.reload_certs().is_err());
400 assert!(https_transport_cert_refresher.base_cert.read().is_none());
401 Ok(())
402 }
403
404 #[test]
405 fn reload_certs_with_empty_cert_file_keeps_previous_certificate() -> anyhow::Result<()> {
406 let (_temp_dir_handle, temp_dir_path) = get_temp_dir()?;
407 let path_certs = PathBuf::from(&temp_dir_path).join("certs");
408 let expected_chain_ders = write_self_signed_pem_files(&path_certs, 0)?;
409
410 let https_transport_cert_refresher = HttpsTransportCertRefresher::new(path_certs.clone(), TEST_IP.to_string(), 0, true)?;
411 https_transport_cert_refresher.reload_certs()?;
412 assert_eq!(loaded_chain_ders(&https_transport_cert_refresher), Some(expected_chain_ders.clone()));
413
414 fs::write(path_certs.join(FILENAME_CERT), b"")?;
416 assert!(https_transport_cert_refresher.reload_certs().is_err());
417 assert_eq!(loaded_chain_ders(&https_transport_cert_refresher), Some(expected_chain_ders));
418 Ok(())
419 }
420
421 #[test]
422 fn reload_certs_errors_when_files_missing() -> anyhow::Result<()> {
423 let (_temp_dir_handle, temp_dir_path) = get_temp_dir()?;
424 let path_certs = PathBuf::from(&temp_dir_path).join("certs");
425
426 let https_transport_cert_refresher = HttpsTransportCertRefresher::new(path_certs.clone(), TEST_IP.to_string(), 0, true)?;
427 fs::remove_file(path_certs.join(FILENAME_KEY))?;
428 assert!(https_transport_cert_refresher.reload_certs().is_err());
429 assert!(https_transport_cert_refresher.base_cert.read().is_none());
430 Ok(())
431 }
432}