Java and TLS certificates

Recently I have been working on a private project concerning Zookeeper and security. Since version 3.5, Zookeeper supports mTLS, an authentication mechanism that also adds encrypted communication. Two birds with one stone, you might say. In this journey, I experimented with exposing certificates in Java, and I’m going to share what I learned here.

Using certificates and keys in Java

The scenario I’m going to explore is when I generate some certificates and keys with OpenSSL and I want to use them with a Java application, as explained in my earlier article, Creating a CA for mTLS with OpenSSL.

In particular, I start out with these files:

  • ca.pem: my root CA certificate
  • app.pem: the certificate to be used by my Java app, in PEM format
  • app-key.pem: the unencrypted RSA key for the app, as generated by the openssl genrsa command.

The files I want to create are:

  • keystore: a file that contains my app certificate and key to use for authentication and encryption;
  • trust store: a file that contains the CA certificate to establish chain of trust for connecting clients.

Conveniently, I want these two to be the same file, ie. store all objects in one file and use that as both the key store and the trust store.

Java supports certificates in the following formats:

  • JKS or Java Keystore: a custom format native to Java, managed by keytool that is bundled with the Java JDK; CA certificates are stored in the JDK in this format;
  • PKCS12: a standard format that supports storing many objects in one file;
  • PEM: a Base64-encoded format used by OpenSSL and many other tools.

Note: in this article, whenever encryption passwords are used for any of these files, I am using “changeit” as the password. Make sure to use secure passwords in your setup!

PEM

The PEM format in my opinion is by far the most interoperable (and simple) format to use for certificates and keys. However, it doesn’t work out of the box. Java only supports keys in PEM files in the PKCS8 format, therefore the key must be converted.

You can verify the difference by examining the key files:

  • app-key.pem begins with -----BEGIN RSA PRIVATE KEY-----
  • app-key-p8.pem begins with -----BEGIN PRIVATE KEY-----

To create an archive of your application certificate and key and the CA certificate, you can simply do:

And done! app-full.pem will work with Java with no further modifications.

The upside to this format is that it can be created and managed by OpenSSL only, and does not require the presence of a JDK. If you are generating keys in a controlled environment such as a container, this reduces the size of your container image significantly. Of course, if this is not a concern for you, using keytool is perfectly fine. You might also find that a specific app does require a JKS or PKCS12 keystore, in which case you can use one of the methods explained below.

JKS

When one does research on how to add keys to Java, JKS is still the most popular format. One drawback of it is that it’s only supported by Java and managed with keytool and therefore comes with additional maintenance.

Adding a certificate to a keystore is fairly simple:

The -alias switch is defining a “friendly name” for your certificate and you can choose it be whatever you want. Here I am using app-cert.

This command adds your certificate to the app.jks. If it doesn’t exist, it will be created using the supplied password.

Things get more complicated when you also want to add a private key. As it happens, you can only add private keys to a JKS file from a PKCS12 keystore. If you have that already, you can import both objects using this command (see below how to create app.p12):

Adding the CA certificate then, is piece of cake:

PKCS12

Being a standard format, PKCS12 looks to be the perfect candidate to store your certificates and keys in. There is a road bump though, as you will see later.

To create a PKCS12 file from your app certificate and private key, use this command:

This command will add both certificates to the PKCS12 file app.p12. However, Java will not use the embedded CA certificate, because the storage envelope, called a “bag” does not have a required custom ASN.1 property.

If you examine your app.p12 using the command

You will be missing this property on your CA certificate:

Bag Attributes
    friendlyName: root-ca
    2.16.840.1.113894.746875.1.1: <Unsupported tag 6> # <-- missing

Unfortunately, as of writing this, OpenSSL does not support adding properties to bags, therefore this will never work with Java. The solution to this is to use keytool to add the CA certificate.

As you can see, it is possible to create a PKCS12 file that will work, but we are still relying on keytool.

Comments

Popular posts from this blog

Creating a CA for mTLS with OpenSSL

Disk encryption with SSH remote unlocking on Debian 11