/ security

How to communicate via HTTPS between two Tomcat servers using a self-signed certificate

TL;DR

There is no need to pay for an SSL certificate in order to communicate two applications through a REST API over HTTPS. You just need to know a few things about Java keytool and a little bit of setup to make it work!

Why?

REST APIs have become a natural part of software development. And, as the tendency to build small, numerous and interacting applications increases overtime, so does the need of REST APIs.
These interactions most of the time will occur within a network which we may or may not have control of, and anyone with access could potentially sniff or intercept a message.
This is why we believe that encrypting those interactions using HTTPS is a good practise, and should be considered by every developer, whether the security of the information is a critical part of the system or not. Plus, it only takes some minutes of your time.

How?

We will assume that you have some basic knowledge about HTTPS and how it works.

Consider a scenario in which we have two Java applications (A and B), running over two different Tomcat servers (SERVER-A and SERVER-B), and B consumes a service from A through a REST API.

Before encrypting the communication between them, we will need to learn about these two concepts:

Java Keystore

A Java Keystore is the file where our certificate will be stored, along with its private key. This certificate will be the one used to authenticate to a remote server and encrypt the outgoing responses.
The keystore will also contain all those certificates that we're willing to trust. This is, public keys used to verify the remote certificates that we don't already know.

Java Keytool

The Java Keytool is a key and certificate management command-line tool, bundled inside the JVM. We can use it to create self-signed ceriticates and install them in our servers, either for authentication or verification.

Creating self-signed certificate

Having said that, in order to enable our application B to send a request to A via HTTPS, first we should create a self-signed certificate for A. We can do this using the Java Keytool, running the following commands in SERVER-A terminal:

$ cd --
$ mkdir ssl
$ $JAVA_HOME/bin/keytool -genkey -alias SERVER-A -keyalg RSA -keystore ssl/SERVER-A.keystore

Then the keytool will prompt the following questions:

Enter keystore password:  password
What is your first and last name?
  [Unknown]:  example.com
What is the name of your organizational unit?
  [Unknown]:  IT
What is the name of your organization?
  [Unknown]:  The Office
What is the name of your City or Locality?
  [Unknown]:  Buenos Aires
What is the name of your State or Province?
  [Unknown]:  Buenos Aires
What is the two-letter country code for this unit?
  [Unknown]:  AR
Is CN=example.com, OU=IT, O=The Office, L=Buenos Aires, ST=Buenos Aires, C=AR correct?
  [no]:  yes

Enter key password for example.com
      (RETURN if same as keystore password):  password

It's important to notice that when asked for the first and last name, we must enter the server domain or IP address.
Finally, we should see a file called SERVER-A.keystore inside the ssl dir

Export your keystore

Now we should import our new self-signed certificate into the Java Keystore of SERVER-A Tomcat.
In order to do that we will export it to a .crt file, and then import it to the Java Keystore:

$ $JAVA_HOME/bin/keytool -exportcert -alias SERVER-A -keystore ssl/SERVER-A.keystore -file ssl/SERVER-A.crt -storepass password
$ sudo $JAVA_HOME/bin/keytool -importcert -file ssl/SERVER-A.crt -alias SERVER-A -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

By the way, changeit is the default password of the Java Keystore... in fact, we should change it :P

You will notice that we are importing our .crt file into $JAVA_HOME/jre/lib/security/cacerts... that's were the JVM stores our trusted certificates.

Then we should import the same .crt file into the Java Keystore of the SERVER-B Tomcat. We can do that running the next command line in SERVER-B terminal:

$ sudo $JAVA_HOME/bin/keytool -importcert -file ssl/SERVER-A.crt -alias SERVER-A -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

Enable HTTPS

Finally we need to enable HTTPS for the SERVER-A Tomcat. In order to do that, we will add a new connector to our server.xml configuration file:

<!-- Define a SSL Coyote HTTP/1.1 Connector on port 8443 -->
<Connector protocol="org.apache.coyote.http11.Http11NioProtocol"
           port="8443" maxThreads="200"
           scheme="https" secure="true" SSLEnabled="true"
           keystoreFile="${user.home}/SERVER-A.keystore" keystorePass="password"
           clientAuth="false" sslProtocol="TLS"
           sslEnabledProtocols="TLSv1.2"
           useServerCipherSuitesOrder="true"                                 
           ciphers="TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_DSS_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_DSS_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,TLS_EMPTY_RENEGOTIATION_INFO_SCSVF"
           />

You will notice that we are using a fixed list of ciphers for our new connector, which are the more recommended and less-vulnerable ones.
We also recommend using the 1.2 version (or greater) of the TLS protocol, since lower versions are vulnerable. You can read more more information here

Conclusion

Setting your own self-signed certificates is not as hard as it seems, and for 5 minutes of your time, it will save you a lot of unknown future problems!