Home > OS >  Does Java check expiration date of truststore?
Does Java check expiration date of truststore?

Time:07-08

Does the Java 11 sun.security.ssl.SSLSocketImpl#startHandshake implementation check the expiration date of the truststore? Meaning, if the truststore is expired, the SSL handshake will fail as it's not being validated by Java?

Thank you in advance for your help! Best regards

CodePudding user response:

TLDR: no

Java implements cert-chain (or formally path) validation as specified for PKIX in RFC 5280, which checks the current (or specified) time against the validity period (notBefore and notAfter=expiration) on each cert from the leaf up to but NOT including the 'anchor'. In fact 5280 does not require the anchor to even be a certificate or have a validity period; see section 6.1.1 and observe that validity is not among the initial parameters set (mostly) from the anchor.

This is somewhat obscured because Java does usually "provide the trust anchor ... in the form of a self-signed [i.e. root CA] certificate", as that section says "may" be done. The usual default truststore file, JRE/lib/cacerts (or for JSSE=SSL/TLS jssecacerts if it exists), does indeed contain root certs, as do the truststores used by many if not all other systems and libraries that use X.509/PKIX certs (Windows, MacOS, OpenSSL, NSS, and the SMIME part of GnuPG at minimum). If you use JSSE's default TrustManager it always uses that type of file, though you can specify a different file name, and if you use JSSE's default TrustManager implementations obtained from TrustManagerFactory they go one level higher by taking a KeyStore object containing/providing root certs -- which you can have created using something other than a file.

When JSSE needs to validate a cert-chain for a handshake, SSLSocketImpl (or SSLEngineImpl) doesn't do so directly but calls the TrustManager which was set from the factory and context (default or other), and the above default first calls checkTrustedInit which (once) calls sun.security.validator.Validator.getInstance to create either SimpleValidator or PKIXValidator which is then used to validate the cert-chain. The former (now considered obsolete but still in the code) directly implements a simpler/earlier version of the validation algorithm, and as you can see for the anchor/root at chain[chain.length-1] it does almost no checks before turning it into a sun.security.cert.TrustAnchor which is used to check the lower certs. The latter (now default) is more complicated and uses the CertPathValidator (and possibly CertPathBuilder) API(s), which end up doing the same thing.

If you want you can substitute your own TrustManager implementation which implements the standard differently (perhaps including checking validity period on the anchor cert) or even not at all (in which case of course the security of connections you make depends on what you implement).

Let's Encrypt actually makes use of behavior like this to support old versions of Android that don't have LE's own root cert 'ISRG Root X1' because they predate 2015-ish, and can't be updated usually because the manufacturer stopped supporting them or even stopped existing. Although they describe this only for Android, it actually works for Java also -- where it shouldn't be needed because most uses of Java, except maybe the embedded/card version ME, can be and are updated or at least the truststore maintained.

You can demonstrate this by running the below code to read in the expired DST-X3 root cert and use only it, and not the ISRG-X1 root that people normally should use, to validate a connection to a site that uses a LetsEncrypt cert with the 'compatibility' chain -- StackExchange itself including stackoverflow.com is such a site at least for now. You can't get DST-X3 from a current Java, where it has been deleted precisely because it is expired and should not be used (and of course deleting it does cause cert chains using it to fail validation!) or most other systems/devices if current. If you use an Oracle Java package older than last fall, it does have the DST-X3 cert; if you use an older OpenJDK it may use a private cacerts that does have DST-X3 but depending on which builder's package you use it may instead use an external (platform) truststore that is updated to remove DST-X3 even though Java itself is not updated. You can also get DST-X3 from LetsEncrypt or Identrust directly or from public repositories such as this one (also linked by LetsEncrypt).

    // file_containing_CAcert(e.g. DSTX3) hostname [port]
    X509Certificate c;
    try( InputStream is = new FileInputStream (args[0])) {
        c = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is);
    }
    System.out.println (c.getNotAfter() " " c.getSubjectDN());
    KeyStore ks = KeyStore.getInstance("PKCS12"); // any file-based
    ks.load(null); ks.setCertificateEntry("test", c);
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
    tmf.init(ks);
    SSLContext ctx = SSLContext.getInstance("TLS");
    ctx.init(null,  tmf.getTrustManagers(), null);
    SSLSocket s = (SSLSocket) ctx.getSocketFactory().createSocket(args[1], args.length>2? Integer.parseInt(args[2]): 443);
    s.startHandshake();
    System.out.println ("successful!");
    for( java.security.cert.Certificate t : s.getSession().getPeerCertificates() ){
        X509Certificate x = (X509Certificate) t;
        System.out.println (x.getNotAfter()   " "   x.getSubjectDN() );
    }
  • Related