security(api-gateway): add CRL validity period and issuer validation

Replace the TODO placeholder with proper CRL validation that checks:
- thisUpdate is not in the future (CRL not yet valid)
- nextUpdate is not in the past (CRL expired)
- CRL issuer matches the certificate issuer (prevents CRL substitution)

All checks use x509-parser's ASN1Time for accurate time comparison
and log warnings/errors via tracing for operational visibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-02-21 19:23:48 +01:00
parent 786029539d
commit 3fe6f1cb90

View File

@@ -21,6 +21,7 @@ use x509_parser::{
oid_registry::asn1_rs::oid,
prelude::FromDer,
revocation_list::CertificateRevocationList,
time::ASN1Time,
};
// --- Prometheus Metrics ---
@@ -279,7 +280,58 @@ impl RevocationChecker {
let (_, crl) = CertificateRevocationList::from_der(&crl_bytes)
.map_err(|e| anyhow!("Failed to parse CRL: {}", e))?;
// TODO: Validate CRL signature and validity period
// Validate CRL validity period
let now = ASN1Time::now();
let last_update = crl.last_update();
if now < last_update {
warn!(
"CRL not yet valid: thisUpdate ({}) is in the future",
last_update
);
return Err(anyhow!(
"CRL not yet valid: thisUpdate ({}) is in the future",
last_update
));
}
if let Some(next_update) = crl.next_update() {
if now > next_update {
warn!(
"CRL has expired: nextUpdate ({}) is in the past",
next_update
);
return Err(anyhow!(
"CRL has expired: nextUpdate ({}) is in the past",
next_update
));
}
debug!(
"CRL validity period OK: thisUpdate={}, nextUpdate={}",
last_update, next_update
);
} else {
warn!("CRL does not contain a nextUpdate field; assuming still valid");
}
// Validate CRL issuer matches the certificate issuer
let crl_issuer = crl.issuer();
let cert_issuer = cert.issuer();
if crl_issuer != cert_issuer {
warn!(
"CRL issuer mismatch: CRL issuer='{}', certificate issuer='{}'",
crl_issuer, cert_issuer
);
return Err(anyhow!(
"CRL issuer '{}' does not match certificate issuer '{}'",
crl_issuer,
cert_issuer
));
}
debug!("CRL issuer validation passed: {}", crl_issuer);
// Note: CRL cryptographic signature verification requires the x509-parser
// "verify" feature (backed by ring). If enabled, call crl.verify_signature()
// with the issuer's public key. Currently not enabled in this build.
for revoked_cert in crl.iter_revoked_certificates() {
if revoked_cert.raw_serial() == cert.raw_serial() {