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 certificateapp.pem
: the certificate to be used by my Java app, in PEM formatapp-key.pem
: the unencrypted RSA key for the app, as generated by theopenssl 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
):
keytool -importkeystore -srckeystore app.p12 -srcstoretype PKCS12 \
-srcstorepass changeit -destkeystore app.jks -deststorepass changeit
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:
openssl pkcs12 -export -name app-cert -in app.pem -inkey app-key.pem \
-password pass:changeit -out app.p12 -certfile ca.pem -caname root-ca
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.
openssl pkcs12 -export -name app-cert -in app.pem -inkey app-key.pem \
-password pass:changeit -out app.p12
keytool -importcert -keystore app.p12 -storetype PKCS12 -storepass changeit \
-file ca.pem -alias root-ca -noprompt
As you can see, it is possible to create a PKCS12 file that will work, but we are still relying on keytool
.
Comments
Post a Comment