Performing Mutual TLS Authentication with Rest Assured (via Apache HTTP Client)

It's possible that you want to perform mutual TLS authentication to further secure your APIs.

If you're writing a Java project, it's possible you're using Rest Assured to interact with your API.

But it's not immediately obvious how we can actually set it up within Rest Assured. I've found that the auth() method, which returns an AuthenticationSpecification, does not seem to work (I have raised an issue upstream).

The Solution

The solution, as per rohitkadam19's reply on How to make HTTPS GET call with certificate in Rest-Assured java is to create a custom org.apache.http.conn.ssl.SSLSocketFactory that can be used by Rest Assured, which will provide the client certificates.

I would recommend extracting this out into a couple of helper methods which can be found below:

import io.restassured.RestAssured;
import io.restassured.config.RestAssuredConfig;
import io.restassured.config.SSLConfig;
import io.restassured.specification.RequestSpecification;
import java.io.FileInputStream;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import org.apache.http.HttpStatus;
import org.apache.http.conn.ssl.SSLSocketFactory;

// ...

/*
 * Example code
 */

public static void clientCertWithKeyStore()
    throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException,
        KeyManagementException {
  clientCertSpecification(
          "/path/to/keystore.jks",
          "keystore-pass")
      .log()
      .all()
      .get("https://client.badssl.com/")
      .then()
      .log()
      .all()
      .statusCode(HttpStatus.SC_OK);
}

public static void clientCertWithTrustStore()
    throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException,
        KeyManagementException {
  clientCertSpecification(
          "/path/to/keystore.jks",
          "keystore-pass",
          "/path/to/truststore.jks",
          "truststore-pass")
      .log()
      .all()
      .get("https://localhost:8443")
      .then()
      .log()
      .all()
      .statusCode(HttpStatus.SC_OK);
}

/*
 * Helper methods
 */

private static RequestSpecification clientCertSpecification(
    String keyStorePath, String keyStorePass)
    throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException,
        KeyManagementException {
  return clientCertSpecification(keyStorePath, keyStorePass, null, null);
}

private static RequestSpecification clientCertSpecification(
    String keyStorePath, String keyStorePass, String trustStorePath, String trustStorePass)
    throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException,
        KeyManagementException {
  return clientCertSpecification(
      keyStorePath,
      keyStorePass,
      KeyStore.getDefaultType(),
      trustStorePath,
      trustStorePass,
      KeyStore.getDefaultType());
}

private static RequestSpecification clientCertSpecification(
    String keyStorePath,
    String keyStorePass,
    String keyStoreType,
    String trustStorePath,
    String trustStorePass,
    String trustStoreType)
    throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException,
        KeyManagementException {

  KeyStore keyStore = loadKeyStore(keyStorePath, keyStorePass.toCharArray(), keyStoreType);
  SSLSocketFactory clientAuthFactory = new SSLSocketFactory(keyStore, keyStorePass);
  if (null != trustStorePath) {
    KeyStore trustStore =
        loadKeyStore(trustStorePath, trustStorePass.toCharArray(), trustStoreType);
    clientAuthFactory = new SSLSocketFactory(keyStore, keyStorePass, trustStore);
  }

  SSLConfig sslConfig =
      RestAssuredConfig.config().getSSLConfig().with().sslSocketFactory(clientAuthFactory);
  RestAssuredConfig config = RestAssured.config().sslConfig(sslConfig);

  return RestAssured.given().config(config);
}

private static KeyStore loadKeyStore(String path, char[] password, String storeType) {
  KeyStore keyStore;
  try {
    keyStore = KeyStore.getInstance(storeType);
    keyStore.load(new FileInputStream(path), password);
  } catch (Exception ex) {
    throw new RuntimeException("Error while extracting the keystore", ex);
  }
  return keyStore;
}

private static KeyStore loadKeyStore(String path, char[] password) {
  return loadKeyStore(path, password, KeyStore.getDefaultType());
}

Testing

So how can you actually test this thing works? In January I found mtls.dev, a great resource for generating all the setup for performing MTLS (both client-side and server-side) as well as example code to run a simple server locally.

I'd thoroughly recommend using it, especially to validate that you've got Rest Assured set up correctly.

I have tested this with Rest Assured v3.3.0 and v4.3.0.

Generating the Keystore

To generate the keystore, we need both our private key (client-key.pem in this example), and the corresponding public certificate (client.pem in this example).

We can then use openssl to generate a PKCS12 keystore:

openssl pkcs12 -export -inkey client-key.pem -in client.pem -out keystore.p12

Generating the Truststore

To generate the truststore, we need the public certificate (be it a leaf, intermediate, or root certificate) for the server (ca.pem in this example). I have documented how you can do this with openssl in Extracting SSL/TLS Certificate Chains Using OpenSSL.

We can then use the keytool command from our Java install to import/create a keystore:

keytool -import -alias ca -file ca.pem -keystore truststore.jks

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #certificates #mutual-tls #java #rest-assured.

This post was filed under articles.

Has this content helped you? Please consider supporting me so I can continue to create content like this!

Related Posts

Other posts you may be interested in:

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.