Troubleshooting TLS issues involving a Java app can be difficult if the division of responsibility means that responsibility for the certs and truststores is not vested in a team with any Java skill. This post is aimed at someone, like me, in that situation.
SSLPoke
Atlassian (JIRA/Confluence/Bitbucket) provide a simple program that does about as much as openssl -connect. It’s linked off this knowledge base article and can be downloaded direct from here as a class, here or here as source.
It’s called SSLPoke – there’s various clones of it out there, but all paths seem to go back to Atlassian.
The compiled class is compatible with Java 1.5 or higher – if you’re lucky enough to find yourself troubleshooting TLS using, say, Java 1.4*, you’ll get the following error.
Exception in thread "main" java.lang.UnsupportedClassVersionError: SSLPoke (Unsupported major.minor version 49.0)
In which case you’ll need to compile it (Google will help with that)
* under the circumstances, that probably should be ‘SSL’ not ‘TLS’!
Java Class Versions
Not likely that an OS old enough to be running java 1.4 will do this, but Centos/RHEL7 does, which is helpful.
$ file SSLPoke.class SSLPoke.class: compiled Java class data, version 49.0 (Java 1.5)
For the author’s reference, other class versions are as follows. It’s not exhaustive, because the author doesn’t want to play with Java <1.4, let alone to have to be tackling mismatches between archaeological classes AND archaeological JREs.
More information at the sources rgagnon.com and wikipedia and also docs.oracle.com which elaborates on how complicated 45.x and 46.x were back in 1.0-1.2 days.
48.0 Java 1.4 49.0 Java SE 5.0 50.0 Java SE 6 51.0 Java SE 7 52.0 Java SE 8 53.0 Java SE 9 54.0 Java SE 10 55.0 Java SE 11 56.0 Java SE 12 57.0 Java SE 13
Using SSLPoke
- Assumption: the class is downloaded to /tmp – if it’s elsewhere, modify the class path parameter.
- If the class file is in your current working directory, java doesn’t need the classpath parameter (objection!)
Note: It’s not helpful to document it like this (lacking the classpath param) because the reader – you, or anyone else, is left guessing where this class came from.
$ java -classpath /tmp SSLPoke google.com 443 Successfully connected $ echo $? 0
- This means TLS handshaking worked.
Truststores
It’ll have used the default trust store, probably:
$JAVA_HOME/jre/lib/security/cacerts
On a machine that uses /etc/alternatives to map /usr/bin/java, I believe this will translate to:
/etc/alternatives/jre/lib/security/cacerts
I’m testing on a Centos7 machine with openjdk 1.8. The cacerts file isn’t actually stored amongst the JVM, but if it’s Oracle java, it probably is.
$ readlink -f /bin/java /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-1.el7_6.x86_64/jre/bin/java $ readlink -f /etc/alternatives/jre/lib/security/cacerts /etc/pki/ca-trust/extracted/java/cacerts
You may be looking at an install of java which isn’t nicely integrated with /etc/alternatives, in which case you’ll have to figure out $JAVA_HOME from
- What you can see in a process listing or
- By using lsof -p <pid> to see where exactly that dang unqualified java binary is.
If you’re looking for another trust store to “play with,” you may find this:
/etc/pki/java/cacerts
In my case, it’s a link to the same file as $JAVA_HOME/jre/lib/security/cacerts.
In a corporate environment, I’d anticipate you may be using neither of these.
I’d expect you to need to trust a corporate certificate authority (CA) (and optionally public CAs) so you might have
- A truststore with just the corporate root, or
- An old hybrid truststore that was built at some point with the corporate root added to a JRE’s cacerts truststore, or maybe
- Some dynamic mechanism to roll a hybrid truststore, adding the corporate root to the latest cacerts provided when the JRE is updated.
Specify an alternative truststore by adding the following parameter:
-Djavax.net.ssl.trustStore=/etc/pki/java/cacerts_site
SSLPoke failures
My puppet master is serving up the console using a puppet certificate; ie it’s self signed.
$ java -classpath /tmp SSLPoke puppet 443
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to
find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:397)
[blah blah]
$ echo $?
1
This is the error you get with any certificate that isn’t trusted – self signed, signed by an unknown public CA, or by the corporate CA (if the root isn’t in the default truststore)
You can troubleshoot this in great detail by adding another parameter:
-Djavax.net.debug=ssl
Although, if you’re simply looking for binary result – the truststore is ‘right’ or not – the debugging output has a pretty poor signal to noise ratio.
In theory, the volume can be turned down, eg:
-Djavax.net.debug=ssl:trustmanager
The options are:
$ java -Djavax.net.debug=help -classpath /tmp SSLPoke puppet 443 all turn on all debugging ssl turn on ssl debugging The following can be used with ssl: record enable per-record tracing handshake print each handshake message keygen print key generation data session print session activity defaultctx print default SSL initialization sslctx print SSLContext tracing sessioncache print session cache tracing keymanager print key manager tracing trustmanager print trust manager tracing pluggability print pluggability tracing handshake debugging can be widened with: data hex dump of each handshake message verbose verbose handshake message printing record debugging can be widened with: plaintext hex dump of record plaintext packet print raw SSL/TLS packets
However, it doesn’t seem to work in openjdk. Looks like the widening options do work, just in case the debug output wasn’t noisy enough.
Similarly, Redhat suggest some extra debugging here:
-Djava.security.debug=access:stack
On openjdk 1.8, it doesn’t seem to make any difference.
Off piste ramble
I couldn’t get the class supplied with that Redhat solutions page to run – I think one of the classes required is missing.
The source code loads a bunch of classes on lines 3-7
import java.net.URL; import java.io.*; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession;
But rather than throw an error at that point, it waits until some actual code is encountered, and then fails, referring to the line with the code on it.
Exception in thread "main" java.lang.NoClassDefFoundError: JavaHttpsClient$1 at JavaHttpsClient.main(JavaHttpsClient.java:13) Caused by: java.lang.ClassNotFoundException: JavaHttpsClient$1
What is it with programming languages and unhelpful errors?