'Java trustmanager behavior on expired certificates

Does java's TrustManager implementation ignore if a certificate has expired?
I tried the following:
- Using keytool and parameter -startdate "1970/01/01 00:00:00" I created a P12 keystore with an expired certificate.
- I exported the certificate:

Keystore type: PKCS12
Keystore provider: SunJSSE

Your keystore contains 1 entry

Alias name: fake
Creation date: 5 ╠ά± 2011
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=Malicious, OU=Mal, O=Mal, L=Fake, ST=GR, C=GR
Issuer: CN=Malicious, OU=Mal, O=Mal, L=Fake, ST=GR, C=GR
Serial number: -1c20
Valid from: Thu Jan 01 00:00:00 EET 1970 until: Fri Jan 02 00:00:00 EET 1970
Certificate fingerprints:
         MD5:  A9:BE:3A:3D:45:24:1B:4F:3C:9B:2E:02:E3:57:86:11
         SHA1: 21:9D:E1:04:09:CF:10:58:73:C4:62:3C:46:4C:76:A3:81:56:88:4D
         Signature algorithm name: SHA1withRSA
         Version: 3


*******************************************

I used this certificate as server certificate for Tomcat.
Then using an apache httpClient I connected to tomcat, but first I added the expired certificate to the client's trust-store (using a TrustManager

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

and loading the expired certificate).
I was expecting the connection to fail.
Instead the connection succeeds.
Using System.setProperty("javax.net.debug", "ssl");
I see:

***
Found trusted certificate:
[
[
  Version: V3
  Subject: CN=Malicious, OU=Mal, O=Mal, L=Fake, ST=GR, C=GR
  Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5

  Key:  Sun RSA public key, 1024 bits
  modulus: 10350555024148635338735220482157687267055139906998169922552357357346372886164908067983097037540922519808845662295379579697361784480052371935565129553860304254832565723373586277732296157572040989796830623403187557540749531267846797324326299709274902019299
  public exponent: 65537
  Validity: [From: Thu Jan 01 00:00:00 EET 1970,
               To: Fri Jan 02 00:00:00 EET 1970]
  Issuer: CN=Malicious, OU=Mal, O=Mal, L=Fake, ST=GR, C=GR
  SerialNumber: [   -1c20]

]

I see that in TLS handshake the expired certificate is send by Tomcat connector.
But the client (i.e. the TrustManager) does not reject the connection.
Is this the default behavior?
Am I suppose to configure the trustmanager somehow to check for expiration?

UPDATE:
I found that the actual TrustManager used is X509TrustManagerImpl. Here X509TrustManagerImpl says that this class has a minimal logic.May be I am using the wrong TrustManager?

UPDATE2: From the javadoc X509TrustManager it is not clear if it checks for certificate expiration

void checkServerTrusted(X509Certificate[] chain,String authType)
                                throws CertificateException  

Given the partial or complete certificate chain provided by the peer, build a certificate path to a trusted root and return if it can be validated and is trusted for server SSL authentication based on the authentication type.The authentication type is the key exchange algorithm portion of the cipher suites represented as a String, such as "RSA", "DHE_DSS". Note: for some exportable cipher suites, the key exchange algorithm is determined at run time during the handshake. For instance, for TLS_RSA_EXPORT_WITH_RC4_40_MD5, the authType should be RSA_EXPORT when an ephemeral RSA key is used for the key exchange, and RSA when the key from the server certificate is used. Checking is case-sensitive.

Thanks



Solution 1:[1]

I've just had a similar issue myself while overriding checkServerTrusted.

Turns out that if you need to check expiration you can call X509Certificate.checkValidity() and it will throw either a CertificateExpiredException or a CertificateNotYetValidException. Both of these extend CertificateException so they can be happily thrown by checkServerTrusted.

To solve your problem you could implement a new X509TrustManager which creates your original instance in its constructor, implements all methods as calls to the original instance, and adds a call to checkValidity for each certificate in certs[] inside checkServerTrusted.

Solution 2:[2]

I believe IBM's JSSE checks for expiry while Sun's does not.

Solution 3:[3]

Very old thread but I thought I'd share some code that implements Matt Lyons' suggestion above (I think!). I looked at a lot of questions on this but didn't find any actual code examples. AFAIK, this code is ok for Java 6, 7, 8 (not sure about earlier/later versions).

This is where I create the HTTPS URL connection that checks SSL certs against a local truststore (but also checks for cert expiry)

public HttpURLConnection getHttpsUrlConnection(URL url) {
    HttpURLConnection connection = null;
    try {
        // Create the connection
        connection = (HttpsURLConnection) url.openConnection();
        ((HttpsURLConnection) connection).setSSLSocketFactory(getSslSocketFactoryThatChecksCertsAgainstLocalKeystore());
    } catch (IOException e) {
        e.printStackTrace();
    }
    return connection;
} 

private SSLSocketFactory getSslSocketFactoryThatChecksCertsAgainstLocalKeystore() {
    try {
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        // I'm using a custom keystore here (useful tips about working with keystores here: https://www.baeldung.com/java-keystore)
        // I think you can use the default keystore (JRE cacerts file) by passing in null here (cast to KeyStore) 
        trustManagerFactory.init(getLocalKeystore());
        // Get the trust managers (I think there's normally only one)
        TrustManager trustManagers[] = trustManagerFactory.getTrustManagers();
        // Create an array of MyX509TrustManager objects to extend/replace trustManagers
        MyX509TrustManager myTrustManagers[] = new MyX509TrustManager[trustManagers.length];
        for (int i = 0; i < myTrustManagers.length; i++) {
            if (trustManagers[i] instanceof X509TrustManager) {
                // For each trust manager, create a new MyX509TrustManager (which will check for expired certs!)
                myTrustManagers[i] = new MyX509TrustManager((X509TrustManager) trustManagers[i]);
            }
        }
        // Create a ssl socket factory using my trust manager(s) that will include expired cert checking
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, myTrustManagers, null);
        return sslContext.getSocketFactory();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

This is the class that implements X509TrustManager (and contains a reference to a "real" X509TrustManager which does almost all of the processing).

import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate;

public class MyX509TrustManager implements X509TrustManager {

    private X509TrustManager impl;
    
    public MyX509TrustManager(X509TrustManager impl) {
        this.impl = impl;
    }
    
    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
        impl.checkClientTrusted(x509Certificates, s);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
        impl.checkServerTrusted(x509Certificates, s);
        checkCertExpiry(x509Certificates);
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return impl.getAcceptedIssuers();
    }
    
    private void checkCertExpiry(X509Certificate[] x509Certificates) throws CertificateException {
        long currentTime = System.currentTimeMillis();
        for (int i = 0; i < x509Certificates.length; i++) {
            if (currentTime > x509Certificates[i].getNotAfter().getTime()) {
                throw new CertificateExpiredException("Cert expired on " + x509Certificates[i].getNotAfter());
            } else if (currentTime < x509Certificates[i].getNotBefore().getTime()) {
                throw new CertificateExpiredException("Cert will not be valid until " + x509Certificates[i].getNotBefore());
            }
        }
    }
}

There is probably a more elegant way to do this. Suggestions welcome!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Matt Lyons-Wood
Solution 2 user207421
Solution 3