SSL for Java Spark Framework on Jetty deployed in a Docker container

Srimal Fernando
9 min readSep 2, 2021

--

I recently came across an interesting web development on Git which is written in Java using Spark framework (not to be confused with Apache Spark) which was new to me and piqued my interest.

Java Spark framework runs on Jetty Web Server. Although there are many interesting dimensions to discover within this framework and this implementation, this article focuses on one; configuring the site to use HTTPS protocol rather than HTTP.

According to Spark Documentation, the framework can configure the underlying jetty server with just a few lines of configuration and a keystore. But if you haven’t worked with Java Keystores before, this might be a bit less straightforward than Spark imagined. I’ll lay out my journey in achieving this with a caveat: I’m deploying on Docker 19.03.12 on Ubuntu 20.04 (for this instance on DigitalOcean). However, many of these commands are similar or easily translatable to windows. Let’s get to the specifics.

Install JDK/JRE (for Keytool)

You need Keytool for creating the Java keystore file which will house your keys and certificates. You can either create this on a different container, on your local machine or on the OS on cloud that hosts the container housing your Spark application; It doesn’t matter. All we need is the jks file to be finally placed somewhere the Spark code can read at (in my case in some directory that will end up in the final image along with spark code).

Check for Keytool:

keytool  -help

If you have already have it, great!

But if it says ‘ not recognized’ then we may need to install it. But if you’re on Windows (and you have Java, [jre/jdk etc.], installed already), it may be an unknown ‘PATH’ variable that’s making command-line to not understand the command.

On windows, try a similar path as below for a keytool.exe

C:\Program Files\Java\jdk1.6.0_26\bin\keytool.exe

or

C:\Program Files\Java\jre1.8.0_111\bin\keytool.exe

or

C:\Program Files\Android\Android Studio\jre\bin\keytool.exe

If you found the keytool, go ahead and add the path to Environment Variables on windows. Now keytool must be recognized as a command.

If you can’t find keytool on your environment, you may need to install it. In windows you can download JDK/JRE installer and keytool should come with it. On Ubuntu on my DigitalOcean droplet, I will do the following to install jdk/jre.

sudo apt update

and

sudo apt install default-jre

or

sudo apt install default-jdk

then make sure to check if keytool is available now. I will continue assuming you found it.

Creating the Java Keystore

Let’s bring up a terminal and start creating a keystore with our keytool. I will be using Let’s Encrypt to issue us a certificate in this article. If you wish to get an SSL certificate from elsewhere changes in the steps should be minimal but I may post a generic article on that later.

keytool -genkeypair -alias simple-cert -keyalg RSA -keysize 2048 -keystore letsencrypt.jks -dname "CN=jks-simple-cert.firecube.xyz" -storepass test12345

Let’s break it down..

Key Store Name: letsencrypt.jks
Key Store Password: test12345
Key Name (alias): simple-cert
Domain Name: jks-simple-cert.firecube.xyz
Web Private Key: RSA 2048

this should create a jks file in your current folder by the name letsencrypt.jks. Note that Key Name(alias) and password will come in handy in later steps.

Note that the domain name can be a main domain or a sub domain. You can even get a wild-card SSL by using *.domain.com instead of specifying subdomain.

Generating Certificate Signing Request (CSR)

We now have a keystore with generated keypairs. Now we need a CSR to provide to a certificate authority so they can issue us a certificate. We use the Java Keystore we just created for this (letsencrypt.jks). Make sure your terminal is still working in the same directory or make sure to give the full path to wherever the jks file is at.

We use the keystore password we specified and the alias of the keypair we generated to export the CSR for that keypair.

keytool -certreq -alias simple-cert -keystore letsencrypt.jks -file jks-simple-cert_firecube_xyz.csr -storepass test12345 -ext san=dns:jks-simple-cert.firecube.xyz

Domain Name: jks-simple-cert.firecube.xyz
SAN Name: jks-simple-cert.firecube.xyz
Key Name (alias): simple-cert

You should now see a CSR file exported to the same directory in the name we mentioned in the command :

jks-simple-cert_firecube_xyz.csr

We’ll need this in the next step.

Using CERTBOT to request for an SSL certificate

Here we will use CERTBOT to request an SSL certificate be issued in response to your certificate signing request; i.e the CSR file generated in earlier step.

Note that you will need to complete some challenges with CERTBOT to verify ownership of the domain. While Let’s Encrypt is compatible with many types of challenges, CERTBOT at the moment only works with DNS challenges. This means you need access to your domain’s DNS Zone.

Note: If you bought the domain from one registrar (for ex: GoDaddy) and pointed the domain to a different place where you host (let’s say DigitalOcean Droplet) or CloudFlare, if you pointed using nameservers, you need to edit DNS records in the latter DNS zone. DNS records in original registrar (GoDaddy) will have no effect.

certbot certonly --manual  --csr jks-simple-cert_firecube_xyz.csr --preferred-challenges "dns"

where jks-simple-cert_firecube_xyz.csr is the CSR file.

My current directory in the terminal has the CSR file we generated. Therefore I will not have the fully qualified path of the CSR file and will just mention the name. But if you have to work with a CSR file elsewhere, simply mention the path as below.

certbot certonly  --manual --csr ./root/keys/jks-simple-cert_firecube_xyz.csr --preferred-challenges "dns"

Since you asked CERTBOT to throw you a DNS challenge, you should see something like below, asking you to add a DNS record. In my case a TXT record.

You will see your domain name in place of <domain.com> instead.

Also make sure that the final record reflects the above record name. Some DNS editors suffixes in the domain name to your record automatically, in such a case your record need only be _acme-challenge ,the editor will add .domain.com.

Make sure to add the lowest TTL possible! this is very important.

For DigitalOcean the lowest TTL is 30 seconds. For GoDaddy, lowest is 600 seconds (10 mins).

Some DNS zones will have the record propagated in no time. some will take too long. Default in most DNS zone editors is 48 hours! while this is good for normal usage, it is bad for a domain verification. You cannot go ahead with CERTBOT if your new DNS TXT record cannot be seen. As mentioned you can use an online tool to check if the record is live, or you can check on your terminal (use a different terminal) as below,

nslookup  -type=TXT  _acme-challenge.domain.com.

If this returns the challenge CERTBOT gave you, you’re good to go. You can press enter to continue with CERTBOT. If for some reason your record is not live and you cannot keep the terminal session open until it does, don’t press enter, simply exit. When you’re finally ready, do the CERTBOT request again with the same CSR we did in the beginning of this section and CERTBOT will resume the same challenge. If you press enter without exiting, it will renew the challenge, then you need to mess with DNS all over again.

Now, if you’re sure DNS record is live, hit enter. This should make CERTBOT issue you 3 certificates in PEM format.

That’s done. Let’s see how to import these certificates to your java keystore.

Import Certificates to Java Keystore

Java Keystore accepts certificates in cer format, not pem. There is no need to do any special conversion, cer files are pem; we only need to change extensions.

for simplicity I will rename each of the issued certificates with my alias (in keystore) prepended, like below:

0000_cert.pemsimple-cert.cer

0000_chain.pemsimple-cert-intermediate-chain.cer

0001_chain.pemsimple-cert-with-chain.cer

But for our purpose we only need the last one. The full certificate chain.

Let’s get on the terminal, refer to our keystore file (we need the password and alias again), and import these cer file to match with the correct keypair we generated (that we got the CSR for).

keytool -importcert -alias simple-cert -keystore letsencrypt.jks -storepass test12345 -file .\jks-simple-cert-with-chain.cer

Here, after the -file flag, point to the cer file. Absolute path is not always expected. Just the name is enough if you’re in the same directory.

You will get the following warning:

Go ahead, enter y. Without the signed authority’s chain, our chain is not trusted. As of writing this article, let’s encrypt has deprecated use of X3 intermediates (actually, all of X1, X2, X3, and X4). So we’ll take R3 Intermediate which is relevant at the moment. But do check which intermediate certificate works when you decide to do this from Let’s Encrypt’s Chain of Trust page.

Remember, we used RSA algorithm in generating our keystore (refer the keytool command) and we need the pem, therefore we get the following version.

Let’s Encrypt Chain of Trust page

Place the downloaded R3 pem file in the same directory as your other cer files for ease of use. If it is not in cer format, change the extension so it finally looks like this:

lets-encrypt-r3.cer

Now, let’s import this certificate as well to our keystore as below,

keytool -import -trustcacerts -alias intermediate -file .\lets-encrypt-r3.cer -keystore .\letsencrypt.jks -storepass test12345

Note the alias is different, not “simple-cert”. Also note that paths need not be mentioned if all are in the current work directory.

Now the resulting keystore should have all the necessary certificates to be trusted.

Certificate and renewal

Note the certificate issued is only allowed for 3 months. You can use the same CSR and re-apply for a new certificate before expiry simply as we originally did in the Using ‘CERTBOT to request for an SSL certificate’ section above.

If your DNS manager has an API, write a script to automate it so you don’t have to do all of this manually every 3 months.

Enabling HTTPS in Java Spark framework

Now that we have a working java keystore file in jks format, all we need to do is to make sure to place it somewhere the code can access and will end up in your docker container.

Let’s say I store my jks file in ‘security’ sub folder in my application. For Ex:

application/security/letsencrypt.jks

Where I write the code,

./security/letsencrypt.jks

should refer to the jks file correctly. (I’m emphasizing this because if your code cannot find the jks file, https won’t work).

Finally, before any routing is done, in my case in the application’s entry-point, I will place the following line to use https for all routing afterwards.

secure("./security/letsencrypt.jks", "test12345", null, null);

This stems from the following in Spark Framework:

secure(keystoreFilePath, keystorePassword, truststoreFilePath, truststorePassword);

This line will do the necessary Jetty configuration through the framework.

Hurray! That’s it! Now your Java Spark Web Application must work well in https if you were to visit https://your-domain.com

Footnote

Do note that if you visit http://your-domain.com , your web application might be unreachable. You can further configure Jetty to handle the http traffic. But I found it easier to use a reverse proxy to deal with the http traffic. I will link an article for that as soon as it’s written.

Finally, if you run your docker container exposing 443 port as standard, just the domain should get you to the application with no further configuration or having to specify port.

This comment thread on Let’s Encrypt (A bit confusing than this article) really helped me.

--

--

Srimal Fernando
Srimal Fernando

Written by Srimal Fernando

A software engineer with strong opinions. Finds solace in science fiction. Believes everyone has a story to tell and every story is a story worth telling.

Responses (1)