Context and testing with Java :
I've been using Badssl to test certificate verification for a tiny Java client. It used to work well in January. But as of today, there's something failing : the certificate for Badssl base domain (badssl.com) is not verified anymore by the following piece of code :
public static void main(String[] args) throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException {
KeyStore keystore=KeyStore.getInstance("JKS");
FileInputStream stream = new FileInputStream(getTrustStore());
keystore.load(stream, getTrustStorePassword());
X509TrustManager secureTrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
PKIXParameters params = null;
try {
CertPath certPath = CertificateFactory.getInstance("X.509").generateCertPath(Arrays.asList(chain));
params = new PKIXParameters(keystore);
params.setRevocationEnabled(true);
CertPathValidator.getInstance("PKIX").validate(certPath, params);
} catch (CertPathValidatorException e) {
if (e.getMessage().startsWith("Certificate has been revoked")) {
System.out.println("One certificate of the trust chain has been revoked : " + e.getMessage());
} else {
System.out.println(chain[2]); // Displays the root CA - ISRG X1
System.out.println("============TRUSTANCHORS==============");
// Displays all trusted certs, including ISRG X1
for (TrustAnchor t : params.getTrustAnchors()) {
System.out.println(t.toString());
}
// This is where the code unexpectedly exits
System.out.println("Unexpected error during certificate revocation validation : " + e.getMessage());
}
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | CertificateException | KeyStoreException e) {
System.out.println("Unexpected error during certificate revocation validation : " + e.getMessage());
}
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
}
The error message that comes along with the exit is :
Path does not chain with any of the trust anchors
I've been digging around, (hence the System.out in the middle), and found out that there is a slight difference between the certificate my webclient gets, and the certificate in Java's truststore :
First System.out (displaying badssl.com certificate chain - expiry in 2024):
Version: V3
Subject: CN=ISRG Root X1, O=Internet Security Research Group, C=US
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 4096 bits
params: null
modulus: 709477870415445373015359016562426660610553770685944520893298396600226760899977879191004898543350831842119174188613678136510262472550532722234131754439181090009824131001234702144200501816519311599904090606194984753842587622398776018408050245574116028550608708896478977104703101364577377554823893350339376892984086676842821506637376561471221178677513035811884589888230947855482554780924844280661412982827405878164907670403886160896655313460186264922042760067692235383478494519985672059698752915965998412445946254227413232257276525240006651483130792248112417425846451951438781260632137645358927568158361961710185115502577127010922344394993078948994750404287047493247048147066090211292167313905862438457453781042040498702821432013765502024105065778257759178356925494156447570322373310256999609083201778278588599854706241788119448943034477370959349516873162063461521707809689839710972753590949570167489887658749686740890549110678989462474318310617765270337415238713770800711236563610171101328052424145478220993016515262478543813796899677215192789612682845145008993144513547444131126029557147570005369943143213525671105288817016183804256755470528641042403865830064493168693765438364296560479053823886598989258655438933191724193029337334607
public exponent: 65537
Validity: [From: Wed Jan 20 20:14:03 CET 2021,
To: Mon Sep 30 20:14:03 CEST 2024]
Issuer: CN=DST Root CA X3, O=Digital Signature Trust Co.
SerialNumber: [ 40017721 37d4e942 b8ee76aa 3c640ab7]
Second System.out (displaying java's truststore - expiry in 2035):
Version: V3
Subject: CN=ISRG Root X1, O=Internet Security Research Group, C=US
Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11
Key: Sun RSA public key, 4096 bits
params: null
modulus: 709477870415445373015359016562426660610553770685944520893298396600226760899977879191004898543350831842119174188613678136510262472550532722234131754439181090009824131001234702144200501816519311599904090606194984753842587622398776018408050245574116028550608708896478977104703101364577377554823893350339376892984086676842821506637376561471221178677513035811884589888230947855482554780924844280661412982827405878164907670403886160896655313460186264922042760067692235383478494519985672059698752915965998412445946254227413232257276525240006651483130792248112417425846451951438781260632137645358927568158361961710185115502577127010922344394993078948994750404287047493247048147066090211292167313905862438457453781042040498702821432013765502024105065778257759178356925494156447570322373310256999609083201778278588599854706241788119448943034477370959349516873162063461521707809689839710972753590949570167489887658749686740890549110678989462474318310617765270337415238713770800711236563610171101328052424145478220993016515262478543813796899677215192789612682845145008993144513547444131126029557147570005369943143213525671105288817016183804256755470528641042403865830064493168693765438364296560479053823886598989258655438933191724193029337334607
public exponent: 65537
Validity: [From: Thu Jun 04 13:04:38 CEST 2015,
To: Mon Jun 04 13:04:38 CEST 2035]
Issuer: CN=ISRG Root X1, O=Internet Security Research Group, C=US
SerialNumber: [ 8210cfb0 d240e359 4463e0bb 63828b00]
So it appears what I have in my truststore doesn't match what badssl.com sends me.
Test with web browsers :
Still, when I browse badssl.com (Tried with multiple browsers), it works properly (the certificate chain send contains the 2035 expiry date certificate contained in my OS (and in the Java truststore).
Test with openssl CLI :
I performed another test via openssl command line (get badssl.com certificate via openssl, then just format it in human-readable fashion) :
openssl s_client -showcerts -connect badssl.com:443 </dev/null 2>/dev/null| sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' | tac | sed '/BEGIN CERT/q' | tac | openssl x509 -text -noout'
And I also get the 2024 expiry date.
Summary :
So it seems to me badssl.com is sending me a different certificate chain when I call him :
- via browser (tried out edge, chromium, firefox) -> 2035.
- via Java or openssl CLI -> 2024.
Am I missing something, or is this some weird behavior on badssl side ? Plus, should Java TrustManager validate this cert, despite the strange certificate chain send by badssl ? TL;DR : Should I correct my Java code or should some things change on badssl side to make it work ?
Also, if something has an explanation of how that odd behavior is possible (side effect of a specific configuration, load balancer not updated, ...), I would be grateful to understand how that can happen !
Thanks!