TLS/SSL certificate exchange in Java

In today’s blog post, we will take a look on how to handle (somewhat self-signed) certificates in Java.

1. Example server using spring-boot

Let’s set up a minimal spring-boot RESTful service. Basically a ‘Hello world’-type one. Nothing fancy, just for definiteness.

package pls.gooby;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;

/**
 * Main application class.
 */
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        new SpringApplicationBuilder(Application.class)
            .run(args);
    }
}
package pls.gooby;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * A rest controller.
 */
@RestController
public class MainController {

    @RequestMapping(value = "/",
                    method = RequestMethod.GET)
    public String mainPage() {
        return "Yes, this is Gooby.\n";
    }

}

I recommend setting up dependency management with gradle or Maven. Let’s give a build.gradle

buildscript {
    repositories {
        mavenLocal()
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.3.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'spring-boot'

group = 'pls.gooby'
version = '0.1'
def artifactId = projectDir.name
def versionNumber = version
sourceCompatibility = 1.8

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {

    //Spring framework
    compile 'org.springframework.boot:spring-boot-starter-web'

    //Tests
    testCompile('junit:junit:4.12') {
        exclude module: 'hamcrest-core'
    }
    testCompile 'org.hamcrest:hamcrest-all:1.3'
    testCompile 'org.mockito:mockito-all:1.10.19'
}

Using e.g. the gradle wrapper with

> ./gradlew bootRun

gets the whole up and running, and can be reached via browser or curl

> curl http://localhost:8080
> Yes, this is Gooby.

Now, we want to add TLS encryption to the whole story. Luckily spring-boot makes it very easy, just put the following line in your application.properties

server.port=8080
security.require-ssl=true
server.ssl.protocol=TLS
server.ssl.key-store=keystore.jks
server.ssl.key-store-password=goobyserverpassword
server.ssl.keyAlias=goobyserver

We will see how to get the keystore.jks file. It is a file where the certificate of our service is stored. Where to get a certificate you ask? Buy one or create one yourself! Being always short on money I prefer to choose the latter option.

2. Getting delusional: become your own certificate authority

We will not just create a self-signed certificate, but rather our own certificate authority (CA). This allows us to issue certificates for multiple services, while keeping client implementations rather easy, see below. Creating a CA is in fact very easy using OpenSSL, all you have to do is

openssl req -config /etc/ssl/openssl.cnf -new -x509 -keyout gooby-ca-key.pem -out gooby-ca-certificate.pem -days 365

You will be asked to fill in some information, a password (let’s assume it is password for it is ‘goobycapassword‘) for your private key, ending up with two files

  • gooby-ca-key.pem, which is the private key and can be used to sign/issue certificates. Keep it safe and secret!
  • gooby-ca-certificate.pem, the certificate of the CA itself.

Now can create the aforementioned keystore.jks for our tomcat/jetty/whatever, using the keytool

keytool -keystore keystore.jks -genkey -alias goobyserver

You will be asked to enter a password, make sure it’s identical to the one in the application.properties (here ‘goobyserverpassword‘). Use this password also for when asked. Also make sure that the aliases match. Now we have a keystore, but it is basically empty. We will ask our CA to sign a certificate for us.

keytool -keystore keystore.jks -certreq -alias goobyserver -keyalg rsa -file goobyserver.csr

We now have a file goobyserver.csr, which represents a certificate signing request. We we now fulfill generously:

openssl x509 -req -CA gooby-ca-certificate.pem -CAkey gooby-ca-key.pem -in goobyserver.csr -out goobyserver.cer -days 365 -CAcreateserial

You will be asked for a password, enter ‘goobycapassword‘. Alright, let’s add the certificate goobyserver.cer to the keystore.

keytool -import -keystore keystore.jks -file goobyserver.cer -alias goobyserver

As a password enter ‘goobyserverpassword‘. Now the keystore.jks is done and ready to ship. Running the spring-boot application again, and curling/browsering it will tell you that TLS is working

> curl -k https://localhost:8080
> Yes, this is Gooby.

Since the certificate is basically issued by our own you will to use the -k option in curl (or add an exception in the browser to view the result).

3. Consuming the RESTful service

One of the more interesting use cases would be if you have a client that wants to consume our service and validate the certificate. One way to do it, is to create a trust store and add our CA to it.

keytool -import -file gooby-ca-certificate.pem -alias goobyCAroot -keystore goobyclienttruststore.jks

Now we have tell the client to use it. This can be achieved by setting system properties. Let’s formulate it in a test

package pls.gooby;

import java.util.Properties;

import javax.net.ssl.HttpsURLConnection;

import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;

import org.junit.Ignore;
import org.junit.Test;

import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

/**
 * Tesing the certificate chain.
 */
public class CertificateChainTest {

    @Ignore("execute locally with running server to check certificate acceptance")
    @Test
    public void testRestExchanges() {
        HttpsURLConnection
            .setDefaultHostnameVerifier(
             (hostname, session) -> true);

        Properties systemProps = System.getProperties();
        systemProps.put("javax.net.ssl.trustStore",
                        "goobyclienttruststore.jks");
        System.setProperties(systemProps);

        RestTemplate restClient = new RestTemplate();

        String answer = restClient
            .exchange(https://localhost:8080,
                      HttpMethod.GET, null,
                      String.class).getBody();
        assertThat(answer, is(notNullValue()));
    }

}

Notice that we do not verify the host name. Also our goobyclienttruststore.jks only accepts certificates issued by the gooby CA. In particular replacing ‘https://localhost:8080‘ with ‘https://www.google.com‘ will fail.

Advertisements

About goobypl5

pizza baker, autodidact, particle physicist
This entry was posted in Programming, Security, Security/Encryption and tagged , , , , , . Bookmark the permalink.

Share your thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s