From 8963aaeaf56071f5b92027420d77d6df36eacfc6 Mon Sep 17 00:00:00 2001 From: venbrinoDev Date: Wed, 20 May 2026 10:16:03 +0100 Subject: [PATCH 1/4] feat: implement preemptive leaf certificate regeneration to prevent expiry in long-lived sandboxes --- crates/network/lib/tls/certgen.rs | 4 ++++ crates/network/lib/tls/state.rs | 38 ++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/crates/network/lib/tls/certgen.rs b/crates/network/lib/tls/certgen.rs index 93f11fdf0..afb908c0b 100644 --- a/crates/network/lib/tls/certgen.rs +++ b/crates/network/lib/tls/certgen.rs @@ -17,6 +17,8 @@ pub struct DomainCert { pub chain: Vec>, /// Leaf certificate private key. pub key: PrivateKeyDer<'static>, + /// Expiry time for the generated leaf certificate. + pub not_after: OffsetDateTime, /// Pre-built `ServerConfig` for this domain (avoids per-connection rebuild). pub server_config: std::sync::Arc, } @@ -29,6 +31,7 @@ pub struct DomainCert { pub fn generate_domain_cert(domain: &str, ca: &CertAuthority, validity_hours: u64) -> DomainCert { let now = OffsetDateTime::now_utc(); let params = build_domain_cert_params(domain, validity_hours, now); + let not_after = params.not_after; let key_pair = rcgen::KeyPair::generate().expect("failed to generate domain key pair"); @@ -51,6 +54,7 @@ pub fn generate_domain_cert(domain: &str, ca: &CertAuthority, validity_hours: u6 DomainCert { chain, key, + not_after, server_config: std::sync::Arc::new(server_config), } } diff --git a/crates/network/lib/tls/state.rs b/crates/network/lib/tls/state.rs index 752ee3be4..0570d58f4 100644 --- a/crates/network/lib/tls/state.rs +++ b/crates/network/lib/tls/state.rs @@ -7,6 +7,7 @@ use lru::LruCache; use rustls::DigitallySignedStruct; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier}; use rustls::pki_types::{CertificateDer, ServerName, UnixTime}; +use time::{Duration, OffsetDateTime}; use tokio_rustls::TlsConnector; use super::ca::CertAuthority; @@ -51,6 +52,10 @@ enum BypassPattern { #[derive(Debug)] struct NoVerify; +/// Refresh cached leaf certs shortly before expiry so long-lived sandboxes +/// do not start serving an already-expired intercept certificate. +const CERT_REFRESH_WINDOW: Duration = Duration::minutes(5); + //-------------------------------------------------------------------------------------------------- // Methods //-------------------------------------------------------------------------------------------------- @@ -103,7 +108,9 @@ impl TlsState { pub fn get_or_generate_cert(&self, domain: &str) -> Arc { let mut cache = self.cert_cache.lock().unwrap(); if let Some(cert) = cache.get(domain) { - return cert.clone(); + if cert.not_after > OffsetDateTime::now_utc() + CERT_REFRESH_WINDOW { + return cert.clone(); + } } let cert = Arc::new(certgen::generate_domain_cert( @@ -132,6 +139,35 @@ impl TlsState { } } +#[cfg(test)] +mod tests { + use super::*; + use crate::secrets::config::SecretsConfig; + + #[test] + fn regenerates_cached_domain_cert_when_near_expiry() { + let _ = rustls::crypto::ring::default_provider().install_default(); + let state = TlsState::new(TlsConfig::default(), SecretsConfig::default()); + let first = state.get_or_generate_cert("openrouter.ai"); + let original_not_after = first.not_after; + + { + let mut cache = state.cert_cache.lock().unwrap(); + let stale = Arc::new(DomainCert { + chain: first.chain.clone(), + key: first.key.clone_key(), + not_after: OffsetDateTime::now_utc() + Duration::seconds(30), + server_config: first.server_config.clone(), + }); + cache.put("openrouter.ai".to_string(), stale); + } + + let refreshed = state.get_or_generate_cert("openrouter.ai"); + assert!(refreshed.not_after > OffsetDateTime::now_utc() + Duration::hours(23)); + assert!(refreshed.not_after > original_not_after - Duration::minutes(10)); + } +} + //-------------------------------------------------------------------------------------------------- // Trait Implementations //-------------------------------------------------------------------------------------------------- From 3d2ecdd83dc65e192f717756820b70d0604c557b Mon Sep 17 00:00:00 2001 From: venbrinoDev Date: Wed, 20 May 2026 12:17:28 +0100 Subject: [PATCH 2/4] refactor(certgen, state): rename `not_after` to `expires_at` for clarity --- crates/network/lib/tls/certgen.rs | 12 ++++++------ crates/network/lib/tls/state.rs | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/network/lib/tls/certgen.rs b/crates/network/lib/tls/certgen.rs index afb908c0b..e9ffe6c55 100644 --- a/crates/network/lib/tls/certgen.rs +++ b/crates/network/lib/tls/certgen.rs @@ -18,7 +18,7 @@ pub struct DomainCert { /// Leaf certificate private key. pub key: PrivateKeyDer<'static>, /// Expiry time for the generated leaf certificate. - pub not_after: OffsetDateTime, + pub expires_at: OffsetDateTime, /// Pre-built `ServerConfig` for this domain (avoids per-connection rebuild). pub server_config: std::sync::Arc, } @@ -29,11 +29,11 @@ pub struct DomainCert { /// Generate a certificate for `domain` signed by the given CA. pub fn generate_domain_cert(domain: &str, ca: &CertAuthority, validity_hours: u64) -> DomainCert { - let now = OffsetDateTime::now_utc(); - let params = build_domain_cert_params(domain, validity_hours, now); - let not_after = params.not_after; + let now: OffsetDateTime = OffsetDateTime::now_utc(); + let params: CertificateParams = build_domain_cert_params(domain, validity_hours, now); + let expires_at: OffsetDateTime = params.not_after; - let key_pair = rcgen::KeyPair::generate().expect("failed to generate domain key pair"); + let key_pair: rcgen::KeyPair = rcgen::KeyPair::generate().expect("failed to generate domain key pair"); let cert_der = params .signed_by(&key_pair, &ca.cert, &ca.key_pair) @@ -54,7 +54,7 @@ pub fn generate_domain_cert(domain: &str, ca: &CertAuthority, validity_hours: u6 DomainCert { chain, key, - not_after, + expires_at, server_config: std::sync::Arc::new(server_config), } } diff --git a/crates/network/lib/tls/state.rs b/crates/network/lib/tls/state.rs index 0570d58f4..449ded10d 100644 --- a/crates/network/lib/tls/state.rs +++ b/crates/network/lib/tls/state.rs @@ -108,7 +108,7 @@ impl TlsState { pub fn get_or_generate_cert(&self, domain: &str) -> Arc { let mut cache = self.cert_cache.lock().unwrap(); if let Some(cert) = cache.get(domain) { - if cert.not_after > OffsetDateTime::now_utc() + CERT_REFRESH_WINDOW { + if cert.expires_at > OffsetDateTime::now_utc() + CERT_REFRESH_WINDOW { return cert.clone(); } } @@ -149,22 +149,22 @@ mod tests { let _ = rustls::crypto::ring::default_provider().install_default(); let state = TlsState::new(TlsConfig::default(), SecretsConfig::default()); let first = state.get_or_generate_cert("openrouter.ai"); - let original_not_after = first.not_after; + let original_expires_at = first.expires_at; { let mut cache = state.cert_cache.lock().unwrap(); let stale = Arc::new(DomainCert { chain: first.chain.clone(), key: first.key.clone_key(), - not_after: OffsetDateTime::now_utc() + Duration::seconds(30), + expires_at: OffsetDateTime::now_utc() + Duration::seconds(30), server_config: first.server_config.clone(), }); cache.put("openrouter.ai".to_string(), stale); } let refreshed = state.get_or_generate_cert("openrouter.ai"); - assert!(refreshed.not_after > OffsetDateTime::now_utc() + Duration::hours(23)); - assert!(refreshed.not_after > original_not_after - Duration::minutes(10)); + assert!(refreshed.expires_at > OffsetDateTime::now_utc() + Duration::hours(23)); + assert!(refreshed.expires_at > original_expires_at - Duration::minutes(10)); } } From 76342a8eff606844398edf7cc397ebd31e67308c Mon Sep 17 00:00:00 2001 From: venbrinoDev Date: Wed, 20 May 2026 12:56:32 +0100 Subject: [PATCH 3/4] style: format key pair generation for better readability --- crates/network/lib/tls/certgen.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/network/lib/tls/certgen.rs b/crates/network/lib/tls/certgen.rs index e9ffe6c55..405f45dd0 100644 --- a/crates/network/lib/tls/certgen.rs +++ b/crates/network/lib/tls/certgen.rs @@ -33,7 +33,8 @@ pub fn generate_domain_cert(domain: &str, ca: &CertAuthority, validity_hours: u6 let params: CertificateParams = build_domain_cert_params(domain, validity_hours, now); let expires_at: OffsetDateTime = params.not_after; - let key_pair: rcgen::KeyPair = rcgen::KeyPair::generate().expect("failed to generate domain key pair"); + let key_pair: rcgen::KeyPair = + rcgen::KeyPair::generate().expect("failed to generate domain key pair"); let cert_der = params .signed_by(&key_pair, &ca.cert, &ca.key_pair) From 3effa757109fb5a2a313ac8001e9e6621cce3aa5 Mon Sep 17 00:00:00 2001 From: venbrinoDev Date: Wed, 20 May 2026 14:07:31 +0100 Subject: [PATCH 4/4] refactor(tls): simplify certificate retrieval logic in `get_or_generate_cert` --- crates/network/lib/tls/state.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/network/lib/tls/state.rs b/crates/network/lib/tls/state.rs index 449ded10d..75df4bd54 100644 --- a/crates/network/lib/tls/state.rs +++ b/crates/network/lib/tls/state.rs @@ -107,10 +107,10 @@ impl TlsState { /// Get or generate a certificate for the given domain. pub fn get_or_generate_cert(&self, domain: &str) -> Arc { let mut cache = self.cert_cache.lock().unwrap(); - if let Some(cert) = cache.get(domain) { - if cert.expires_at > OffsetDateTime::now_utc() + CERT_REFRESH_WINDOW { - return cert.clone(); - } + if let Some(cert) = cache.get(domain) + && cert.expires_at > OffsetDateTime::now_utc() + CERT_REFRESH_WINDOW + { + return cert.clone(); } let cert = Arc::new(certgen::generate_domain_cert(