Improper Validation of Certificate Expiration
Description
Improper Validation of Certificate Expiration is a vulnerability that occurs when certificate expiration dates are not validated or are incorrectly validated, so trust may be assigned to certificates that have been abandoned due to age. Certificates include expiration dates as a security mechanism to limit the window during which a compromised certificate can be used. When applications fail to check or ignore expiration dates, they may accept certificates that should no longer be trusted. Over time, expired certificates become increasingly risky as the likelihood that they have been compromised or their private keys have been exposed increases.
Risk
Accepting expired certificates undermines a fundamental security control in the certificate lifecycle. As certificates age past their expiration, the risk of compromise increases through various vectors: private keys may have been leaked, cryptographic algorithms may have been weakened, the certificate subject's circumstances may have changed, or revocation information may no longer be available. Expired certificates are particularly dangerous because organizations may stop protecting their private keys once certificates expire, assuming they are no longer in use. Attackers who obtain expired certificate private keys can use them against applications that don't validate expiration. The security benefit of certificate-based authentication is significantly reduced when expiration is not enforced.
Solution
Implement checks for certificate expiration dates and reject expired certificates. Use TLS library functions that include expiration checking as part of standard validation. Never add exceptions for expired certificates in production code. Inform users about certificate expiration problems and provide remediation steps when certificates are about to expire. Monitor certificate expiration dates proactively and renew before expiration. When implementing certificate pinning, validate all certificate properties including expiration before pinning. Implement automated certificate renewal processes to prevent legitimate services from presenting expired certificates. Consider implementing warnings for certificates approaching expiration.
Common Consequences
| Impact | Details |
|---|---|
| Integrity, Authentication | Scope: Integrity, Authentication Data from systems using expired certificates may be corrupted through malicious spoofing. Expired certificates may enable attackers who have obtained the certificate's private key to impersonate trusted systems, intercept communications, and modify data in transit. |
Example Code
Vulnerable Code (C/OpenSSL)
The following examples demonstrate improper certificate expiration validation:
// Vulnerable: Accepting expired certificates
#include <openssl/ssl.h>
#include <openssl/x509.h>
int vulnerable_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
int err = X509_STORE_CTX_get_error(ctx);
// Vulnerable: Accepting expired certificates
if (err == X509_V_ERR_CERT_HAS_EXPIRED) {
return 1; // Accept expired - DANGEROUS!
}
// Also vulnerable: Accepting not-yet-valid certificates
if (err == X509_V_ERR_CERT_NOT_YET_VALID) {
return 1; // Accept future-dated certificates
}
return preverify_ok;
}
int vulnerable_check_cert(SSL *ssl) {
X509 *cert = SSL_get_peer_certificate(ssl);
long result = SSL_get_verify_result(ssl);
// Vulnerable: Ignoring expiration errors
if ((result == X509_V_OK) ||
(result == X509_V_ERR_CERT_HAS_EXPIRED)) {
// Treats expired as valid!
return 1;
}
return 0;
}
# Vulnerable: Python ignoring expiration
import ssl
import socket
from datetime import datetime
def vulnerable_connect(hostname, port):
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.verify_mode = ssl.CERT_REQUIRED
context.load_default_certs()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context.wrap_socket(sock, server_hostname=hostname)
try:
ssl_sock.connect((hostname, port))
except ssl.SSLCertVerificationError as e:
# Vulnerable: Ignoring expiration errors
if "certificate has expired" in str(e).lower():
print("Warning: Certificate expired (ignored)")
# Creates new socket without verification
context2 = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context2.check_hostname = False
context2.verify_mode = ssl.CERT_NONE
sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context2.wrap_socket(sock2)
ssl_sock.connect((hostname, port))
return ssl_sock
// Vulnerable: Java ignoring expiration
import javax.net.ssl.*;
import java.security.cert.*;
import java.util.Date;
public class VulnerableExpirationCheck implements X509TrustManager {
private final X509TrustManager defaultTm;
public VulnerableExpirationCheck() throws Exception {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init((KeyStore) null);
defaultTm = (X509TrustManager) tmf.getTrustManagers()[0];
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
try {
defaultTm.checkServerTrusted(chain, authType);
} catch (CertificateExpiredException e) {
// Vulnerable: Ignoring expiration!
System.out.println("Warning: Certificate expired, continuing anyway");
// Does not rethrow - accepts expired certificate
}
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public X509Certificate[] getAcceptedIssuers() {
return defaultTm.getAcceptedIssuers();
}
}
Fixed Code (C/OpenSSL)
// Fixed: Proper certificate expiration validation
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <time.h>
// Fixed: Strict verification callback
int strict_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
if (!preverify_ok) {
int err = X509_STORE_CTX_get_error(ctx);
int depth = X509_STORE_CTX_get_error_depth(ctx);
X509 *cert = X509_STORE_CTX_get_current_cert(ctx);
char subject[256];
X509_NAME_oneline(X509_get_subject_name(cert), subject, sizeof(subject));
fprintf(stderr, "Certificate error at depth %d:\n", depth);
fprintf(stderr, " Subject: %s\n", subject);
fprintf(stderr, " Error: %s\n", X509_verify_cert_error_string(err));
// Fixed: Reject expired certificates
if (err == X509_V_ERR_CERT_HAS_EXPIRED) {
fprintf(stderr, " Certificate has expired - rejecting\n");
return 0; // Reject
}
// Fixed: Reject not-yet-valid certificates
if (err == X509_V_ERR_CERT_NOT_YET_VALID) {
fprintf(stderr, " Certificate not yet valid - rejecting\n");
return 0; // Reject
}
return 0; // Reject all errors
}
return 1;
}
int secure_check_cert(SSL *ssl, const char *hostname) {
X509 *cert = SSL_get_peer_certificate(ssl);
if (cert == NULL) {
fprintf(stderr, "No certificate presented\n");
return 0;
}
// Fixed: Check verification result - NO exceptions for expiration
long result = SSL_get_verify_result(ssl);
if (result != X509_V_OK) {
fprintf(stderr, "Certificate verification failed: %s\n",
X509_verify_cert_error_string(result));
X509_free(cert);
return 0;
}
// Additional manual expiration check
time_t now = time(NULL);
if (X509_cmp_time(X509_get0_notBefore(cert), &now) > 0) {
fprintf(stderr, "Certificate not yet valid\n");
X509_free(cert);
return 0;
}
if (X509_cmp_time(X509_get0_notAfter(cert), &now) < 0) {
fprintf(stderr, "Certificate has expired\n");
X509_free(cert);
return 0;
}
// Verify hostname
if (X509_check_host(cert, hostname, strlen(hostname), 0, NULL) != 1) {
fprintf(stderr, "Hostname verification failed\n");
X509_free(cert);
return 0;
}
X509_free(cert);
return 1; // Fully validated including expiration
}
# Fixed: Python with proper expiration checking
import ssl
import socket
import certifi
from datetime import datetime
def secure_connect(hostname, port):
# Use default context which validates expiration
context = ssl.create_default_context(cafile=certifi.where())
context.check_hostname = True
context.verify_mode = ssl.CERT_REQUIRED
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_sock = context.wrap_socket(sock, server_hostname=hostname)
try:
ssl_sock.connect((hostname, port))
except ssl.SSLCertVerificationError as e:
# Fixed: Don't ignore ANY verification errors including expiration
print(f"Certificate verification failed: {e}")
raise # Propagate error, don't suppress
# Additional check: warn if certificate expiring soon
cert = ssl_sock.getpeercert()
not_after = datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
days_until_expiry = (not_after - datetime.utcnow()).days
if days_until_expiry < 30:
print(f"Warning: Certificate expires in {days_until_expiry} days")
return ssl_sock
// Fixed: Java with proper expiration checking
import javax.net.ssl.*;
import java.security.cert.*;
import java.util.Date;
public class SecureExpirationCheck {
public static SSLContext createSecureContext() throws Exception {
// Use default TrustManager which validates expiration
TrustManagerFactory tmf = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, tmf.getTrustManagers(), null);
// Default TrustManagers reject expired certificates
return ctx;
}
public static void verifyNotExpired(X509Certificate cert)
throws CertificateExpiredException, CertificateNotYetValidException {
// Fixed: Explicit expiration check
cert.checkValidity(); // Throws if expired or not yet valid
}
public static void secureConnect(String hostname, int port) throws Exception {
SSLContext ctx = createSecureContext();
SSLSocketFactory factory = ctx.getSocketFactory();
try (SSLSocket socket = (SSLSocket) factory.createSocket(hostname, port)) {
// Enable hostname verification
SSLParameters params = socket.getSSLParameters();
params.setEndpointIdentificationAlgorithm("HTTPS");
socket.setSSLParameters(params);
socket.startHandshake();
// Fixed: Additional manual check after handshake
SSLSession session = socket.getSession();
X509Certificate[] certs = (X509Certificate[]) session.getPeerCertificates();
for (X509Certificate cert : certs) {
// Throws CertificateExpiredException if expired
verifyNotExpired(cert);
}
// Certificate chain validated and not expired
}
}
}
The fix ensures certificate expiration is always validated and expired certificates are rejected.
Exploited in the Wild
Accepting Expired Certificates (Various Applications, Ongoing)
Applications that accept expired certificates have been exploited by attackers who obtained the private keys of expired certificates, either through theft, compromise of systems that stopped protecting keys after expiration, or through purchasing domains with expired certificates.
Expired CA Certificates (PKI Infrastructure, Historical)
Expired intermediate or root CA certificates that were still trusted by applications enabled attackers to continue using certificates signed by those CAs even after intended revocation through expiration.
Tools to Test/Exploit
-
testssl.sh — Command line tool for testing SSL/TLS including certificate expiration.
-
openssl s_client — Tool for testing SSL/TLS connections and viewing certificate dates.
-
badssl.com/expired — Test site with expired certificate for testing.
CVE Examples
Certificate expiration validation failures are typically documented as part of broader certificate validation issues (CWE-295) rather than receiving standalone CVEs.
References
-
MITRE Corporation. "CWE-298: Improper Validation of Certificate Expiration." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/298.html
-
RFC 5280. "Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile." https://tools.ietf.org/html/rfc5280
-
OWASP Foundation. "Transport Layer Protection Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html