CWE-298: Improper Validation of Certificate Expiration
Overview
Improper validation of expired certificates occurs when an application accepts an SSL/TLS certificate outside its validity period. X.509 certificates carry "Not Before" and "Not After" dates defining the window in which the certificate is valid. Past that window the certificate is no longer an active assertion by the issuer: the private key may have been compromised or abandoned, and revocation information for an expired certificate may no longer be maintained. The weakness usually comes from disabled certificate validation, a custom TLS callback that ignores validity dates, or a misconfigured TLS library.
Relationship to Other CWEs
- CWE-298 (this page) - the dates: the application accepts a certificate outside its validity period, so an assertion the issuer has stopped making is still trusted.
- CWE-295 (Improper Certificate Validation) - the parent MITRE records for this page and for the other certificate checks listed here. Use it when validation is switched off wholesale rather than failing one specific check; the language-specific pages live there.
- CWE-296 (Improper Following of a Certificate's Chain of Trust) - the chain: the certificate does not lead to a trusted root, whether it is self-signed, signed by an untrusted CA, or missing a valid intermediate.
- CWE-297 (Improper Validation of Certificate with Host Mismatch) - the name: the chain is valid but nothing checks that the certificate identifies the host being connected to. Chain and name are separate checks, and a client can pass one while failing the other.
- CWE-299 (Improper Check for Certificate Revocation) - revocation: the chain is valid and the certificate is in date, but the issuer has withdrawn it since. Expiry is the end date the issuer scheduled; revocation is the issuer withdrawing the certificate before it.
OWASP Classification
A07:2025 - Authentication Failures
Risk
Medium to High: Expired certificates may indicate compromised infrastructure, abandoned services, or lack of security maintenance. If an application ignores expiration and an attacker has the corresponding private key, the attacker can impersonate the service despite the certificate no longer being valid.
Remediation Steps
Core Principle: Reject any certificate presented outside its validity period, and automate renewal so your own certificates do not expire in service.
Enable Expiration Validation
Every mainstream TLS stack checks the validity period by default, as part of path validation. What varies is whether the stack also lets you switch off just the date check, leaving chain validation intact, and three common ones do:
- .NET:
X509VerificationFlags.IgnoreNotTimeValidon anX509ChainPolicy, or aRemoteCertificateValidationCallbackthat accepts a chain whose only status flag isX509ChainStatusFlags.NotTimeValid. Measured on .NET 10 against a self-signed certificate that expired a year ago, with the certificate placed inChainPolicy.CustomTrustStoreandTrustModeset toCustomRootTrustso that the root is not also at fault:Build()returnsfalsewith the default policy andtruewith the flag set, reportingNotTimeValidin both cases. Without the custom trust the chain fails onUntrustedRooteither way, which is worth knowing if you reproduce it. Microsoft's ownSslStreamguidance publishes a callback of this shape, so it turns up in code written from the documentation. - Java:
PKIXParameters.setDate(Date)validates the chain as of a fixed instant rather than now, so a date pinned in the past accepts every certificate that has expired since. - OpenSSL:
X509_V_FLAG_NO_CHECK_TIMEon the verification parameters.
Those are the findings that are genuinely about expiry: remove the override and let the stack apply its own clock. The rest are not date-specific at all, and divide into two shapes:
- Validation disabled wholesale takes the validity period with it. A hand-built context with verification set to none, or a client option that accepts any certificate, drops the date check along with the chain check. CWE-295 carries the concrete form of that mistake and its fix for Python, JavaScript, Java, C# and Go.
- A custom verification callback inspects the chain itself and never compares the validity dates against the current time. A callback that returns success on any path it does not explicitly reject has this defect by construction.
Prefer the platform's default verification over a callback. Where a callback is genuinely required - pinning, a private PKI, a non-standard trust decision - it must reject anything it does not positively approve, and it must still apply the platform checks rather than replace them.
Implement Certificate Monitoring
Expiry is predictable, so alert on it before it happens:
- Alert 30, 14 and 7 days before each certificate's expiration date.
- Keep an inventory of the certificates in use.
- Monitor every endpoint continuously.
Use Automated Certificate Management
Automate issuance and renewal rather than tracking dates by hand:
- ACME protocol: Let's Encrypt, ZeroSSL for automated issuance and renewal
- Cloud providers: AWS ACM, Azure Key Vault, GCP Certificate Manager
- cert-manager: Kubernetes certificate automation
- Certbot: Automated renewal for web servers
Test Expiration Validation
Verify your code rejects expired certificates and still accepts valid ones:
# Baseline: a valid public certificate must still connect
curl https://badssl.com/ # expect: 200
# Test with expired certificate (should fail)
curl https://expired.badssl.com/
# Check certificate expiration (< /dev/null so s_client does not wait on stdin)
openssl s_client -connect example.com:443 -servername example.com \
< /dev/null 2>/dev/null | openssl x509 -noout -dates
Do not skip the baseline: a client that refuses every certificate passes the
expired-certificate test, and a trust store narrowed to an internal CA fails only
against real public endpoints. Run both curl lines through the application's own
HTTP client too, so the code path that carried the finding is the one exercised.