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 std::fs;
31use std::net::IpAddr;
32use std::path::PathBuf;
33use std::str::FromStr;
34use std::sync::Arc;
35use tokio_util::sync::CancellationToken;
36use hashiverse_lib::tools::time_provider::time_provider::{RealTimeProvider, TimeProvider};
37
38pub const FILENAME_LAST_REFRESHED: &str = "last_refreshed";
39pub const FILENAME_CERT: &str = "cert.pem";
40pub const FILENAME_KEY: &str = "key.pem";
41
42#[derive(Debug)]
43pub struct HttpsTransportCertRefresher {
44 force_local_network: bool,
45 path_certs: PathBuf,
46 filename_cert: PathBuf,
47 filename_key: PathBuf,
48 filename_last_refreshed: PathBuf,
49
50 ip: String,
51 port: u16,
52
53 pub base_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>>,
55
56 pub challenge_cert: Arc<RwLock<Option<Arc<CertifiedKey>>>>,
58}
59
60impl HttpsTransportCertRefresher {
61 pub async fn refresh_cert(&self) -> anyhow::Result<()> {
62 trace!("refreshing certificate");
63
64 let directory_url = match USE_PRODUCTION_LETS_ENCRYPT {
65 true => LetsEncrypt::Production.url().to_owned(),
66 false => LetsEncrypt::Staging.url().to_owned(),
67 };
68
69 let (account, _credentials) = Account::builder()?
70 .create(
71 &NewAccount {
72 contact: &[],
73 terms_of_service_agreed: true,
74 only_return_existing: false,
75 },
76 directory_url,
77 None,
78 )
79 .await?;
80
81 let ip_addr = IpAddr::from_str(&self.ip)?;
84 let identifiers = [Identifier::Ip(ip_addr)];
85 let new_order = NewOrder::new(&identifiers).profile("shortlived");
86
87 let mut order = account.new_order(&new_order).await?;
88 let mut challenge_url = "".to_string();
89 {
93 let mut authorizations = order.authorizations();
94 while let Some(result) = authorizations.next().await {
95 let mut authz = result?;
96 match authz.status {
97 AuthorizationStatus::Valid => continue,
98 AuthorizationStatus::Pending => {}
99 _ => {
100 warn!("unexpected AuthorizationStatus {:?}", authz.status);
101 continue;
102 }
103 }
104
105 let mut challenge = authz.challenge(ChallengeType::TlsAlpn01).ok_or_else(|| anyhow::anyhow!("no tlsalpn01 challenge found"))?;
106 challenge_url = challenge.url.clone();
107
108 {
110 let key_auth_sha256 = challenge.key_authorization().digest();
111
112 let mut certificate_params = CertificateParams::default();
113 certificate_params.subject_alt_names = vec![SanType::IpAddress(ip_addr)];
114 certificate_params.custom_extensions.push(rcgen::CustomExtension::new_acme_identifier(key_auth_sha256.as_ref()));
115
116 let key_pair = rcgen::KeyPair::generate()?;
117 let cert = certificate_params.self_signed(&key_pair)?;
118
119 let cert_chain = vec![rustls_pki_types::CertificateDer::from(cert.der().to_vec())];
120 let key_der = rustls_pki_types::PrivatePkcs8KeyDer::from(key_pair.serialize_der());
121 let key = rustls::crypto::ring::sign::any_supported_type(&rustls_pki_types::PrivateKeyDer::Pkcs8(key_der)).map_err(|_| anyhow::anyhow!("Unsupported key type"))?;
122
123 let certified_key = CertifiedKey { cert: cert_chain, key, ocsp: None };
124
125 *self.challenge_cert.write() = Some(Arc::new(certified_key));
126 }
127
128 challenge.set_ready().await?;
130 }
131 }
132
133 trace!("challenge.url: {}", challenge_url);
135
136 let status = order.poll_ready(&RetryPolicy::default()).await?;
138 if status != OrderStatus::Ready {
139 anyhow::bail!("unexpected order status: {status:?}");
140 }
141
142 let private_key_pem = order.finalize().await?;
144 let cert_chain_pem = order.poll_certificate(&RetryPolicy::default()).await?;
145
146 info!("writing new certificate to disk");
148 fs::create_dir_all(&self.path_certs)?;
149 fs::write(&self.filename_cert, cert_chain_pem)?;
150 write_private_key_file(&self.filename_key, private_key_pem.as_bytes())?;
151 fs::write(&self.filename_last_refreshed, challenge_url)?;
152
153 info!("refreshed certificate");
154
155 Ok(())
156 }
157
158 pub fn reload_certs(&self) -> anyhow::Result<()> {
159 trace!("reloading certificate");
160
161 let certified_key: CertifiedKey = {
162 let bytes_cert = fs::read(&self.filename_cert)?;
163 let bytes_key = fs::read(&self.filename_key)?;
164
165 let cert_chain = rustls_pemfile::certs(&mut &bytes_cert[..]).collect::<Result<Vec<_>, _>>()?;
167 let key_der = rustls_pemfile::private_key(&mut &bytes_key[..])?.ok_or_else(|| anyhow::anyhow!("No private key found in {}", self.filename_key.display()))?;
168 let key = rustls::crypto::ring::sign::any_supported_type(&key_der).map_err(|_| anyhow::anyhow!("Unsupported key type"))?;
169
170 CertifiedKey { cert: cert_chain, key, ocsp: None }
171 };
172
173 *self.base_cert.write() = Some(Arc::new(certified_key));
174
175 Ok(())
176 }
177
178 fn certs_last_refreshed(&self) -> anyhow::Result<TimeMillis> {
179 let certs_last_refreshed = fs::metadata(&self.filename_last_refreshed)
180 .map(|metadata| metadata.modified())
181 .unwrap_or_else(|_| Ok(std::time::SystemTime::UNIX_EPOCH))
182 .with_context(|| "checking last refreshed filename")?;
183
184 let certs_last_refreshed: TimeMillis = certs_last_refreshed.into();
185
186 Ok(certs_last_refreshed)
187 }
188
189 pub async fn process(&self, cancellation_token: CancellationToken) -> anyhow::Result<()> {
190 let time_provider = RealTimeProvider;
191
192 let mut certs_last_attempted = TimeMillis::zero();
193
194 loop {
195 if cancellation_token.is_cancelled() {
196 break;
197 }
198
199 let result: anyhow::Result<()> = try {
200 if !self.force_local_network {
202 if 443u16 == self.port {
204 let now_millis = time_provider.current_time_millis();
206 if (now_millis - self.certs_last_refreshed()?) > config::MILLIS_TO_WAIT_BETWEEN_CERT_RENEWALS {
207 if (now_millis - certs_last_attempted) > config::MILLIS_TO_WAIT_BETWEEN_CERT_RENEWAL_FAILURES {
208 certs_last_attempted = now_millis;
209 self.refresh_cert().await?;
210 }
211 else {
212 trace!("we refreshed certs too recently to try again");
213 }
214 }
215 else {
216 trace!("we have a recent enough cert on disk");
217 }
218 }
219 else {
220 trace!("skipping cert refresh because port {} != 443", self.port);
221 }
222 }
223 else {
224 trace!("skipping cert refresh because force_local_network");
225 }
226
227 self.reload_certs()?;
229 };
230
231 if let Err(e) = result {
232 warn!("error while refreshing certs: {}", e);
233 }
234
235 tools::cancellable_sleep_millis(&time_provider, config::MILLIS_TO_WAIT_BETWEEN_CERT_RENEWAL_CHECKS, &cancellation_token).await;
236 }
237
238 trace!("stopped HttpsTransportCertRefresher");
239
240 Ok(())
241 }
242}
243
244fn write_private_key_file(path: &Path, contents: &[u8]) -> anyhow::Result<()> {
247 #[cfg(unix)]
248 {
249 use std::io::Write;
250 use std::os::unix::fs::OpenOptionsExt;
251 let mut file = fs::OpenOptions::new()
252 .write(true)
253 .create(true)
254 .truncate(true)
255 .mode(0o600)
256 .open(path)?;
257 file.write_all(contents)?;
258 }
259 #[cfg(not(unix))]
260 {
261 fs::write(path, contents)?;
262 }
263 Ok(())
264}
265
266impl HttpsTransportCertRefresher {
267 pub fn new(path_certs: PathBuf, ip: String, port: u16, force_local_network: bool) -> anyhow::Result<Self> {
268 let filename_cert = path_certs.join(FILENAME_CERT);
269 let filename_key = path_certs.join(FILENAME_KEY);
270 let filename_last_refreshed = path_certs.join(FILENAME_LAST_REFRESHED);
271
272 if !fs::exists(&filename_cert)? || !fs::exists(&filename_key)? {
276 info!("generating self-signed certs");
277 let subject_alt_names = vec![ip.clone()];
278
279 let certified_key = generate_simple_self_signed(subject_alt_names)?;
280 fs::create_dir_all(&path_certs)?;
281 fs::write(&filename_cert, certified_key.cert.pem())?;
282 write_private_key_file(&filename_key, certified_key.signing_key.serialize_pem().as_bytes())?;
283 }
284
285 let base_cert = Arc::new(RwLock::new(None));
287 let challenge_cert = Arc::new(RwLock::new(None));
288
289 Ok(Self {
290 force_local_network,
291 path_certs,
292 filename_cert,
293 filename_key,
294 filename_last_refreshed,
295 ip,
296 port,
297 base_cert,
298 challenge_cert,
299 })
300 }
301}
302
303impl ResolvesServerCert for HttpsTransportCertRefresher {
304 fn resolve(&self, client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
305 let is_acme = client_hello.alpn().map(|mut iter| iter.any(|proto| proto == b"acme-tls/1")).unwrap_or(false);
307
308 if is_acme {
309 info!("we have an acme challenge");
311 self.challenge_cert.read().clone()
312 }
313 else {
314 self.base_cert.read().clone()
316 }
317 }
318}