java – troubleshooting SSL/TLS truststores

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?

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s