Using SSL client certificates with mqtt-client

SSL client certificates offer one of todays most secure avenues for establishing safe and trustworthy connections across untrusted networks. In this guide we will demonstrate key elements required to make use of client certificates with HiveMQ's mqtt client library.

Prerequisites

It is assumed, that you have followed our guide on creating self signed client certificates and have the following files containing your key/certificate pairs:

broker.hivemq.local-keystore.jks, broker.hivemq.local-truststore.jks - used by the broker

client1-keystore.jks, client1-truststore.jks - used by the client

 

Further, HiveMQ will need to have a TLS listener configured in its listeners section of config.xml.

Your passwords must match the ones chosen during certificate creation.

<tls-tcp-listener> <port>8883</port> <bind-address>0.0.0.0</bind-address> <tls> <keystore> <path>/path/to/keystores/broker.hivemq.local-keystore.jks</path> <password>secret</password> <private-key-password>secret</private-key-password> </keystore> <truststore> <path>/path/to/keystores/client1-truststore.jks</path> <password>secret</password> </truststore> <client-authentication-mode>REQUIRED</client-authentication-mode> </tls> </tls-tcp-listener>

 

Create a key store

To make our code more readable we will store the paths to both our keystore and truststore in String variables to be used throughout.

final String pathToJKS = "/path/to/keystores/client1-keystore.jks"; final String pathToJKSTrustStore = "/path/to/keystores/client1-truststore.jks";

 

From here we will:

  • define our keyStore

  • from it, load the certificate

  • instanciate a KeyManagerFactory

final KeyStore keyStore = KeyStore.getInstance("JKS"); final InputStream inKey = new FileInputStream(pathToJKS); keyStore.load(inKey, "secret".toCharArray()); final Certificate certificate = keyStore.getCertificate("mqtt-client"); final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, "secret".toCharArray());

Create our trust store

Next we will setup a truststore. The truststore holds credentials of other machines that we intend to trust.

final KeyStore trustStore = KeyStore.getInstance("JKS"); final InputStream in = new FileInputStream(pathToJKSTrustStore); trustStore.load(in, "secret".toCharArray()); final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore);

Create an SSL context

By building our SSL context, we define and enumerate a list of TLS options, protocol versions and ciphers we accept.

final SSLContext sslCtx = SSLContext.getInstance("TLSv1.2"); sslCtx.init(null, tmf.getTrustManagers(), null);

 

Initialise a client

At this point we have all the pieces required for a client to start communicating with HiveMQ using its individual certificate.

final Mqtt5AsyncClient client = Mqtt5Client.builder() .serverHost("localhost") .identifier("client1") .sslConfig() .protocols(Arrays.asList("TLSv1.2")) .keyManagerFactory(kmf) .trustManagerFactory(tmf) .applySslConfig() .serverPort(8883) .buildAsync();


We can verify our connection has been established by instructing the client to do so and seeing a corresponding entry in HiveMQ’s event.log

client.connect().get();


event.log:

2020-04-03 11:01:33,701 - Client ID: client1, IP: 127.0.0.1, Clean Start: true, Session Expiry: 0 connected.

 

This is guide only presents a minimal code example and not ready for use in production environments

 

 

public class HiveMQClient { public static void main(String[] args) throws Exception { final String pathToJKS = "/path/to/keystores/client1-keystore.jks"; final String pathToJKSTrustStore = "/path/to/keystores/client1-truststore.jks"; //Create key store final KeyStore keyStore = KeyStore.getInstance("JKS"); final InputStream inKey = new FileInputStream(pathToJKS); keyStore.load(inKey, "secret".toCharArray()); final Certificate certificate = keyStore.getCertificate("mqtt-client"); final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, "secret".toCharArray()); //Create trust store final KeyStore trustStore = KeyStore.getInstance("JKS"); final InputStream in = new FileInputStream(pathToJKSTrustStore); trustStore.load(in, "secret".toCharArray()); final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustStore); // Build SSL context final SSLContext sslCtx = SSLContext.getInstance("TLSv1.2"); sslCtx.init(null, tmf.getTrustManagers(), null); final List<Mqtt5Publish> message = Collections.synchronizedList(new ArrayList<>()); final Mqtt5AsyncClient client = Mqtt5Client.builder() .serverHost("localhost") .sslConfig() .identifier("client1") .protocols(Arrays.asList("TLSv1.2")) .keyManagerFactory(kmf) .trustManagerFactory(tmf) .applySslConfig() .serverPort(8883) .buildAsync(); client.connect().get(); } }