Floating Octothorpe

Java and TrustStores

This post is going to go over how to use custom x509 certificates with Java clients.

Normally Java applications will use CA certificates provided by the operating system. In the case of CentOS 7 certificates can be found in a TrustStore called /etc/pki/ca-trust/extracted/java/cacerts.

However if you are doing anything like using a self signed certificate connections will fail. This happens because the Java client cannot verify the identity of the server it's connecting to, without a corresponding certificate.

Example code

Below is a short Java class which attempts to connect to a URL and print the HTTP return code:

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

class Example {

    public static void main(String[] args) {
        try {
            URL url = new URL(args[0]);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            System.out.printf("Connecting to : %s\n", url);
            System.out.printf("Return code   : %d\n", connection.getResponseCode());
        } catch (ArrayIndexOutOfBoundsException error) {
            System.err.println("Missing URL argument.");
        } catch (MalformedURLException error) {
        } catch (IOException error) {

The certificate used by example.com is signed by DigiCert Inc. Therefore it can be verified using the certificates found in /etc/pki/ca-trust/extracted/java/cacerts:

$ java Example https://example.com
Connecting to : https://example.com
Return code   : 200

However trying to connect to a web server using a self signed certificate will fail:

$ java Example https://foobar.localdomain
Connecting to : https://foobar.localdomain
sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Downloading the certificate

The first thing to do is get a copy of the CA certificate used by the server you're trying to connect to. If you have access to the server you can just copy the file. Alternatively you can use the s_client command in OpenSSL:

$ echo | openssl s_client -connect foobar.localdomain:443

The contents of a x509 certificate can easily be checked using the following OpenSSL command:

openssl x509 -in foobar.localdomain.pem -noout -text

You can also use the following curl command to verify you have the correct certificate:

curl -I --cacert foobar.localdomain.pem https://foobar.localdomain

X509v3 basic constraints

Before continuing it's worth pointing out a gotcha. For this post I set up Apache on CentOS 7. When you install the mod_ssl package the following files are created by the package scriptlet using OpenSSL:

The default OpenSSL configuration (/etc/pki/tls/openssl.cnf) has the following lines:

[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE

As a result the self-signed certificate will have the CA boolean set to false:

  X509v3 extensions:
        X509v3 Basic Constraints:

As per the x509 rfc, if the CA boolean is false, it should not be used to verify certificate signatures. To get around this the certificate needs to be regenerated with the CA boolean set to true.

Adding to the system TrustStore

Under CentOS 7 adding the new certificate is just a case of running the following:

cp foobar.localdomain.pem /etc/pki/ca-trust/source/anchors/

This will update the entries in /etc/pki/ca-trust/extracted/java/cacerts. You can verify the new certificate is present using the keytool command:

$ keytool -list \
  -keystore /etc/pki/ca-trust/extracted/java/cacerts \
  -storepass changeit
foobar.localdomain, 16-Jul-2016, trustedCertEntry,
Certificate fingerprint (SHA1): 75:EE:B3:4E:68:17:43:57:D9:A6:B1:6B:19:B2:7C:69:ED:0B:39:6F

Note: Adding files to /etc/pki/ca-trust/source/anchors/ will normally require root access.

Using a custom TrustStore

Alternatively a custom TrustStore file can be created. This is helpful if you are not able to change the system TrustStore. Note that Java will not look at the system TrustStore if a custom TrustStore is used.

To create a new TrustStore and import the certificate the following keytool command can be run:

keytool -import \
  -alias foobar.localdomain \
  -keystore cacerts.ts \
  -file foobar.localdomain.pem

Note: It's not possible to set an empty password. Instead you need to use the Java default password changeit. Alternatively you can set a TrustStore password and then provide this to Java at runtime.

Once the TrustStore has been created you can set the javax.net.ssl.trustStore option to get Java to use it:

$ java -Djavax.net.ssl.trustStore=cacerts.ts \
  Example https://foobar.localdomain
Connecting to : https://foobar.localdomain
Return code   : 200

If you used a non-standard password you will also need to use the javax.net.ssl.trustStorePassowrd option:

$ java -Djavax.net.ssl.trustStore=cacerts.ts  \
       -Djavax.net.ssl.trustStorePassword=password \
       Example https://foobar.localdomain
Connecting to : https://foobar.localdomain
Return code   : 200