diff --git a/crates/quicproquo-server/src/tls.rs b/crates/quicproquo-server/src/tls.rs index 6bd3c75..4b42483 100644 --- a/crates/quicproquo-server/src/tls.rs +++ b/crates/quicproquo-server/src/tls.rs @@ -28,6 +28,9 @@ pub fn build_server_config( let cert_bytes = std::fs::read(cert_path).context("read cert")?; let key_bytes = std::fs::read(key_path).context("read key")?; + // Validate certificate expiry and warn about self-signed certs. + validate_certificate(&cert_bytes)?; + let cert_chain = vec![CertificateDer::from(cert_bytes)]; let key = PrivateKeyDer::try_from(key_bytes).map_err(|_| anyhow::anyhow!("invalid key"))?; @@ -76,3 +79,39 @@ fn generate_self_signed_cert(cert_path: &PathBuf, key_path: &PathBuf) -> anyhow: Ok(()) } + +/// Validate a DER-encoded X.509 certificate: bail if expired, warn if expiring +/// soon or self-signed. +fn validate_certificate(der_bytes: &[u8]) -> anyhow::Result<()> { + use x509_parser::prelude::*; + + let (_, cert) = X509Certificate::from_der(der_bytes) + .map_err(|e| anyhow::anyhow!("failed to parse X.509 certificate: {e}"))?; + + let validity = cert.validity(); + let now = ASN1Time::now(); + + if !validity.is_valid_at(now) { + anyhow::bail!( + "TLS certificate expired (not_after: {})", + validity.not_after + ); + } + + // Warn if expiring within 30 days. + let thirty_days = std::time::Duration::from_secs(30 * 24 * 60 * 60); + let cutoff = now.timestamp() + thirty_days.as_secs() as i64; + if validity.not_after.timestamp() < cutoff { + tracing::warn!( + not_after = %validity.not_after, + "TLS certificate expires within 30 days" + ); + } + + // Warn if self-signed (issuer == subject). + if cert.issuer() == cert.subject() { + tracing::warn!("TLS certificate is self-signed (issuer == subject)"); + } + + Ok(()) +}