SSL/TLS is not in Kansas
TLS is very complex and very often confusing. It can - and probably will - send you to dark places before you eventually find the working solution.
Table of contents
- Introduction
- Generate a Certificate Authority
- Generate a Server Certificate
- Protect the certificates!
- Teach java to trust you
- Arguments with Java
Introduction
Today I can diagnose most TLS issues, but know that several cute, innocent kittens had to be sacrificed to get to this point. Hopefully many more cute kittens can be spared now that you’re reading this guide.
TLS is dramatically easier to implement in 2019 and beyond thanks to Let’s Encrypt. Many applications now exist to automate valid TLS certificates. My current favourite is Traefik.
NOTE: the below requires OpenSSL, not the inferior LibreSSL as shipped in MacOS.
Generate a Certificate Authority
We first need an OpenSSL configuration snippet. Save as ca.toml
:
# OpenSSL configuration file.
[ req ]
default_bits = 2048
distinguished_name = req_distinguished_name
string_mask = utf8only
default_md = sha256
x509_extensions = v3_ca
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
0.organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
commonName = Common Name
emailAddress = Email Address
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
Create a key and a certificate for the Certificate Authority:
openssl req -x509 \
-config ca.toml \
-newkey rsa:4096 \
-keyout ca.key.pem -nodes \
-out ca.crt.pem \
-days 36500 \
-subj '/CN=Local Certificate Authority'
Generate a Server Certificate
We first need an OpenSSL configuration snippet. Save as server.toml
:
# OpenSSL configuration file.
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = *.default
DNS.3 = *.default.svc.cluster.local
Create a key and a certificate request for the Server:
openssl req -new \
-newkey rsa:2048 \
-keyout server.key.pem -nodes \
-out server.csr.pem \
-subj '/CN=Server Certificate'
NOTE: In the past it was typical to put the DNS address in the subject
field, but this is not strictly correct. The subject should be used as a descriptive subject, with DNS in the subjectAltName
field.
Create the key and certificate for the Server:
openssl x509 -req -sha256 \
-CA ca.crt.pem \
-CAkey ca.key.pem \
-CAcreateserial \
-extfile server.toml \
-in server.csr.pem \
-out server.crt.pem \
-days 36500
cat server.crt.pem ca.crt.pem > server.fullchain.crt.pem
Protect the certificates!
Certificates and keys generated by OpenSSL are in plain text unless stored in a key store.
Depending on the application, you may need to protect the certificates. A key store is encrypted and protected by a password. OpenSSL keys can be exported to a PKCS12 key store, which is the industry standard format:
openssl pkcs12 -export \
-in crt.pem \
-inkey key.pem \
-out keystore.p12 \
-password pass:stellirin \
-name APP
A lot of software is written in Java. For a long time Java has had its own key store format that is not compatible with PKCS12 key stores.
Oracle finally transitioned to the PKCS12 format from Java 9. However many applications still require the Java key store format. We can import keys from a PKCS12 key store into a Java key store.
keytool -importkeystore \
-srckeystore keystore.p12 \
-srcstoretype PKCS12 \
-srcstorepass 'stellirin' \
-destkeystore keystore.jks \
-deststoretype JKS \
-deststorepass 'stellirin' \
-alias APP
Key stores are password protected, and the keys inside them are also be password protected. These passwords may differ. Many legacy Java applications cannot easily read a key that has a different password to the key store password.
When you import a key from a PKCS12 Keystore to a Java Keystore, the key will be password protected with the PKCS12 key store password. Therefore you should use the same PKCS12 key store password for your Java key store password.
If your key password differs from your Java key store password, you can change the key password:
keytool -list \
-keystore keystore.jks \
-keypasswd \
-alias APP
Teach java to trust you
Generating certificates is not useful if nobody trusts them. Java uses a trust store to store trusted certificates.
By default Java includes a trust store with certificates from most popular Certificate Authorities. However if you need to use a self generated certificate then you have two options:
- add the certificate to the default trust store
- create a new trust store and use only that
Actually a trust store is simply a key store with only public certificates:
keytool -import \
-file crt.pem \
-keystore truststore.jks \
-storepass 'stellirin' \
-alias APP
The default trust store has the default password changeit
. This is a potential weak point as an attacker could add their own certificate to the default trust store and it will be accepted by Java without error.
Arguments with Java
Java arguments can be used to set the key store and trust store:
-Djavax.net.ssl.keyStore=/path/to/keystore.jks
-Djavax.net.ssl.keyStorePassword=stellirin
-Djavax.net.ssl.trustStore=/path/to/truststore.jks
-Djavax.net.ssl.trustStorePassword=stellirin