Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions crates/network/lib/tls/certgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub struct DomainCert {
pub chain: Vec<CertificateDer<'static>>,
/// Leaf certificate private key.
pub key: PrivateKeyDer<'static>,
/// Expiry time for the generated leaf certificate.
pub expires_at: OffsetDateTime,
/// Pre-built `ServerConfig` for this domain (avoids per-connection rebuild).
pub server_config: std::sync::Arc<rustls::ServerConfig>,
}
Expand All @@ -27,10 +29,12 @@ 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 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)
Expand All @@ -51,6 +55,7 @@ pub fn generate_domain_cert(domain: &str, ca: &CertAuthority, validity_hours: u6
DomainCert {
chain,
key,
expires_at,
server_config: std::sync::Arc::new(server_config),
}
}
Expand Down
38 changes: 37 additions & 1 deletion crates/network/lib/tls/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
//--------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -102,7 +107,9 @@ impl TlsState {
/// Get or generate a certificate for the given domain.
pub fn get_or_generate_cert(&self, domain: &str) -> Arc<DomainCert> {
let mut cache = self.cert_cache.lock().unwrap();
if let Some(cert) = cache.get(domain) {
if let Some(cert) = cache.get(domain)
&& cert.expires_at > OffsetDateTime::now_utc() + CERT_REFRESH_WINDOW
{
return cert.clone();
}

Expand Down Expand Up @@ -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_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(),
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.expires_at > OffsetDateTime::now_utc() + Duration::hours(23));
assert!(refreshed.expires_at > original_expires_at - Duration::minutes(10));
}
}

//--------------------------------------------------------------------------------------------------
// Trait Implementations
//--------------------------------------------------------------------------------------------------
Expand Down
Loading