Securing HiveMQ Broker Deployments With Intermediate CA Certificates

by Diego Duarte
26 min read

In a standard security practice, the Root Certificate Authority (CA) is not utilized directly for signing server or client certificates. Instead, the Root CA's primary role is to establish one or more Intermediate Certificate Authorities. These Intermediate CAs are trusted entities, designated by the Root CA, to handle the task of signing certificates. Adopting this hierarchical approach serves a critical purpose: it enables the root key to remain offline and largely inactive, thereby enhancing overall security. If the intermediate key becomes compromised, the Root CA can respond effectively by revoking the compromised intermediate certificate. Following this revocation, the Root CA can then generate a new cryptographic key pair for a replacement Intermediate CA.

The root key is a highly sensitive asset; its exposure or compromise could have severe implications for the entire trust infrastructure. By minimizing the usage of the root key and employing Intermediate CAs for day-to-day certification tasks, the security of the digital certificate ecosystem is significantly bolstered.

An Intermediate Certificate Authority (CA) operates as a delegated entity, possessing the authority to issue and sign digital certificates on behalf of the Root CA. This delegation is established when the Root CA, which sits at the apex of the trust hierarchy, signs the Intermediate CA's certificate. This process effectively creates a 'chain of trust' extending the Root CA's trustworthiness to the certificates issued by the Intermediate CA.

In this guide, we set out to establish a private Certificate Authority (CA) that mirrors the structural framework of public CAs. At the core of this architecture will be a single Root CA, which serves as the foundational trust anchor. From this Root CA, we will have the ability to spawn various Intermediate CAs, thereby extending the trust chain. This setup becomes particularly relevant in the context of secure MQTT communications using HiveMQ Broker, where the implementation of certificates is crucial for ensuring the integrity and confidentiality of the data transmitted. However, it is important to note that we will not describe the certificate revocation process, including the use of Certificate Revocation Lists (CRLs) or Online Certificate Status Protocol (OCSP) responders. These critical aspects of certificate management deserve a thorough exploration and will be the focus of a future dedicated article.

isualizing certificate hierarchy in KeyStore Explorer and the SAN extension.Root CA Directory Structure

The first step is to create the directory structure and initialize some files used during the CA operation.

NOTE: Some of the following commands require root privileges, log in as root user, or use sudo to execute the commands.

  • Choose a directory (/root/ca) to store all keys, certificates, and two empty databases index.txt and serial.txt files.

mkdir -p /etc/ssl/hivemq/root/ca


NOTE: The default location to install certificates is /etc/ssl but might be different on each operating system. This enables multiple services to use the same certificate without overly complicated file permissions.

  • Change the current directory to the Root CA directory.

cd /etc/ssl/hivemq/root/ca
  • Create all folder structures required.

mkdir -p certs crl newcerts private intermediate intermediate/certs intermediate/crl intermediate/csr intermediate/newcerts intermediate/private ../keystores

NOTE: The "Keystores" and "Truststores" will be output to a directory named keystores one level above your working directory.

  • Protects "private" folders against any access from other users.

chmod 700 private intermediate/private

The database in index.txt is a plaintext file that contains certificate information, one certificate per line.

touch index.txt intermediate/index.txt

When creating a new CA certificate, it’s important to initialize the certificate serial numbers with a random number generator. This is very useful if you ever end up creating and deploying multiple CA certificates with the same distinguished name (common if you make a mistake and need to start over); conflicts will be avoided because the certificates will have different serial numbers.

openssl rand -hex 16 | tee serial.txt intermediate/serial.txt

Add a crlnumber file to the Intermediate CA directory tree. crlnumber is used to keep track of certificate revocation lists.

echo 1000 > intermediate/crlnumber

In our current example, the intermediate folder is located within /root/ca for simplicity and ease of demonstration. However, it's important to underscore that this setup is not recommended for real-world applications. In practice, maintaining the Root CA and Intermediate CA on separate machines is a critical security measure.

Preparing OpenSSL Configuration Files

You must create configuration files for OpenSSL to use. Before we can create a CA, we need to prepare configuration files ca-openssl.cnf and intermediate-openssl.cnf that will tell OpenSSL exactly how we want things set up.

Create the OpenSSL Root CA Configuration File

nano /etc/ssl/hivemq/root/ca/ca-openssl.cnf
  • Copy the following configuration into the OpenSSL Root CA configuration file. You must modify the components of the CA’s distinguished name. Change the [ req_distinguished_name ] section as per your company information.

# OpenSSL Root CA configuration file.
# Copy this file to /root/ca/ca-openssl.cnf

# The [ ca ] section is mandatory. Here we tell OpenSSL to use the options from the [ CA_default ] section.
[ ca ]
default_ca = CA_default

# The [ CA_default ] section contains a range of defaults. Ensure you declare the directory you chose earlier (/root/ca).
[ CA_default ]
# Directory and file locations.
# Modify dir variable to match their respective absolute paths (Use pwd command to show your current working directory)
dir               = /etc/ssl/hivemq/root/ca
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial.txt
RANDFILE          = $dir/private/random

# The root key and root certificate.
private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.cert.pem

# SHA-256 hashing algorithm is set as the default.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 365
preserve          = no
policy            = policy_strict

# Apply policy_strict for all Root CA signatures, as the Root CA is only being used to create Intermediate CAs.
[ policy_strict ]
# The Root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of man ca.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

# Options from the [ req ] section are applied when creating certificates or certificate signing requests.
[ req ]
# Options for the req tool (man req).
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only
prompt              = no

# SHA-256 hashing algorithm is set as the default.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

# The [ req_distinguished_name ] section declares the information normally required in a certificate signing request. You can optionally specify some defaults.
[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name (full name)
localityName                    = Locality Name (eg, city)
organizationName                = Organization Name (eg, company)
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name (e.g. server FQDN or YOUR name)
emailAddress                    = Email Address

# Specify Root CA defaults.
countryName             = DE
stateOrProvinceName     = Bavaria
localityName            = Landshut
organizationName        = HiveMQ
organizationalUnitName  = Customer Services
commonName              = HiveMQ Root CA
emailAddress            = email@hivemq.com

# The next few sections are extensions that can be applied when signing certificates. For example, passing the -extensions v3_ca command-line argument will apply the options set in [ v3_ca ].
# Apply the v3_ca extension when we create the root certificate.
[ v3_ca ]
# Extensions for a typical CA (man x509v3_config).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

# Apply the v3_ca_intermediate extension when we create the intermediate certificate. pathlen:0 ensures that no further certificate authorities can be below the Intermediate CA.
[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (man x509v3_config).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign


Create the OpenSSL Intermediate CA Configuration File

nano /etc/ssl/hivemq/root/ca/intermediate/intermediate-openssl.cnf
  • Copy the following configuration into the OpenSSL Intermediate CA configuration file. Here again, you must modify the components of the CA’s distinguished name. Change the [ req_distinguished_name ] section as per your company information.

OpenSSL Intermediate CA configuration file.
# Copy this file to /root/ca/intermediate/intermediate-openssl.cnf

# The [ ca ] section is mandatory. Here we tell OpenSSL to use the options from the [ CA_default ] section.
[ ca ]
default_ca = CA_default

# The [ CA_default ] section contains a range of defaults. Ensure you declare the directory you chose earlier (/root/ca/intermediate).
[ CA_default ]
# Directory and file locations.
# Modify dir variable to match their respective absolute paths (Use pwd command to show your current working directory)
dir               = /etc/ssl/hivemq/root/ca/intermediate
certs             = $dir/certs
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial.txt
RANDFILE          = $dir/private/random

# The intermediate key and intermediate certificate.
private_key       = $dir/private/intermediate.key.pem
certificate       = $dir/certs/intermediate.cert.pem

# For certificate revocation lists.
crlnumber        = $dir/crlnumber
crl              = $dir/crl/intermediate.crl.pem
crl_extensions   = crl_ext
default_crl_days = 30

# SHA-256 hashing algorithm is set as the default.
default_md        = sha256

# Keep subjectAltName (SAN) extension when the Intermediate CA signs the CSR.
copy_extensions   = copy

# Create multiple certificates with the same subject.
unique_subject    = no

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 365
preserve          = no
policy            = policy_loose

# Apply policy_loose for all Intermediate CA signatures, as the Intermediate CA is signing server and client certificates that may come from various third parties.
[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the (ca man) page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

# Options from the [ req ] section are applied when creating certificates or certificate signing requests.
[ req ]
# Options for the req tool (man req).
default_bits        = 2048
distinguished_name  = req_distinguished_name
string_mask         = utf8only
prompt              = no

# SHA-256 hashing algorithm is set as the default.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

# The [ req_distinguished_name ] section declares the information normally required in a certificate signing request. You can optionally specify some defaults.
[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name (full name)
localityName                    = Locality Name (eg, city)
organizationName                = Organization Name (eg, company)
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name (e.g. server FQDN or YOUR name)
emailAddress                    = Email Address

# Specify Intermediate CA defaults.
countryName             = DE
stateOrProvinceName     = Bavaria
localityName            = Landshut
organizationName        = HiveMQ
organizationalUnitName  = Customer Services
commonName              = HiveMQ Intermediate CA
emailAddress            = email@hivemq.com

# The next few sections are extensions that can be applied when signing certificates. For example, passing the -extensions v3_ca command-line argument will apply the options set in [ v3_ca ].
# Apply the v3_ca extension when we create the root certificate.
[ v3_ca ]
# Extensions for a typical CA (man x509v3_config).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

# Apply the server_cert extension when signing server certificates, such as those used for HiveMQ Broker servers/nodes.
[ server_cert ]
# Extensions for server certificates (man x509v3_config).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

# Apply the client_cert extension when signing client certificates, such as those used for MQTT client authentication.
[ client_cert ]
# Extensions for client certificates (man x509v3_config).
basicConstraints = CA:FALSE
nsCertType = client
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth

# The crl_ext extension is automatically applied when creating [certificate revocation lists.
[ crl_ext ]
# Extension for CRLs (man x509v3_config).
authorityKeyIdentifier = keyid:always

The initial arrangement of the folder structure, once all necessary files are in place, will appear as follows:

/etc/ssl/hivemq/root/ca
├── ca-openssl.cnf
├── certs
├── crl
├── index.txt
├── intermediate
│   ├── certs
│   ├── crl
│   ├── crlnumber
│   ├── csr
│   ├── index.txt
│   ├── intermediate-openssl.cnf
│   ├── newcerts
│   ├── private
│   └── serial.txt
├── newcerts
├── private
└── serial.txt

10 directories, 7 files


Create the Root Certificate

Acting as a certificate authority (CA) means dealing with cryptographic pairs of private keys and public certificates. The very first cryptographic pair we’ll create is the root pair. This consists of the root key (ca.key.pem) and root certificate (ca.cert.pem). This pair forms the identity of our CA.

First, we must create a Private Key and Root CA certificate.

  • Change the current directory to the Root CA directory.

cd /etc/ssl/hivemq/root/ca
  • Generate an RSA private key (ca.key.pem).

openssl genrsa -aes256 \
	-passout pass:changeme \
	-out private/ca.key.pem 4096

NOTE: You must enter the passphrase/password for ca.key.pem. The parameter -passout pass:changeme sets the password to encrypt the key. In this case, the password is changeme. Replacing changeme with a strong, unique password in a real-world scenario is important. As you progress through this guide, you will encounter various parameters like -passout and -passin. Please make sure to replace these with the password of your choice.

  • Ensure that only authorized users have the necessary access to the root key.

chmod 400 private/ca.key.pem

Use the root key (ca.key.pem) to create a root certificate (ca.cert.pem). Give the root certificate a long expiry date, such as ten years. Once the root certificate expires, all certificates signed by the CA become invalid.

openssl req \
	-config ca-openssl.cnf \
	-extensions v3_ca \
	-key private/ca.key.pem \
	-passin pass:changeme \
	-new -x509 \
	-days 3652 \
	-sha256 \
	-out certs/ca.cert.pem
  • Secure root certificate by making it universally readable while preventing any modifications.

chmod 444 certs/ca.cert.pem

NOTE: Whenever you use the req tool, you must specify a configuration file with the -config option; otherwise, OpenSSL will use the default configuration file /etc/ssl/openssl.cnf.

  • Verify the root certificate.

openssl x509 -noout -text -in certs/ca.cert.pem

The output shows:

  • the Signature Algorithm used (sha256WithRSAEncryption)

  • the dates of certificate Validity

  • the Public-Key bit length (4096 bit)

  • the Issuer, which is the entity that signed the certificate (C = DE, ST = Bavaria, L = Landshut, O = HiveMQ, OU = Customer Services, CN = HiveMQ Root CA, emailAddress = email@hivemq.com)

  • the Subject, which refers to the certificate itself

The Issuer and Subject are identical as the certificate is self-signed. Note that all root certificates are self-signed.

The output also shows the X509v3 extensions. We applied the v3_ca extension, so the options from [ v3_ca ] should be reflected in the output.

Create the Intermediate Pair

As before, we take two steps to create the Intermediate CA. First, we create a Private Key and Intermediate CA certificate. Make sure to use a strong password.

  • Change the current directory to the Root CA directory.

cd /etc/ssl/hivemq/root/ca
  • Generate an RSA private key (intermediate.key.pem).

openssl genrsa -aes256 \
	-passout pass:changeme \
	-out intermediate/private/intermediate.key.pem 4096
  • Ensure that only authorized users have the necessary access to the intermediate key.

chmod 400 intermediate/private/intermediate.key.pem

Create the intermediate certificate. Use the intermediate key to create a certificate signing request (CSR). The details should generally match the Root CA. The Common Name, however, must be different.

Ensure you specify the Intermediate CA configuration file (intermediate/intermediate-openssl.cnf).

openssl req \
	-config intermediate/intermediate-openssl.cnf \
	-key intermediate/private/intermediate.key.pem \
	-passin pass:changeme \
	-new -sha256 \
	-out intermediate/csr/intermediate.csr.pem

To create an intermediate certificate, use the Root CA with the v3_intermediate_ca extension to sign the intermediate CSR. The intermediate certificate should be valid for a shorter period than the root certificate. Ten years would be reasonable.

This time, specify the Root CA configuration file (/root/ca/ca-openssl.cnf).

openssl ca \
	-config ca-openssl.cnf \
	-extensions v3_intermediate_ca \
	-batch \
	-passin pass:changeme \
	-days 3650 \
	-notext -md sha256 \
	-in intermediate/csr/intermediate.csr.pem \
	-out intermediate/certs/intermediate.cert.pem
  • Secure intermediate certificate by making it universally readable while preventing any modifications.

chmod 444 intermediate/certs/intermediate.cert.pem

The index.txt file is where the OpenSSL CA tool stores the certificate database. Do not delete or edit this file by hand.

  • Now the index.txt file should contain only one line that refers to the intermediate certificate.

cat index.txt

Output:

V       331209001052Z           329C324F628F61099FA7A2B899AF31AC        unknown /C=DE/ST=Bavaria/O=HiveMQ/OU=Customer Services/CN=HiveMQ Intermediate CA/emailAddress=email@hivemq.com

Each line contains six values separated by tabs:

  • Status flag (V for valid, R for revoked, E for expired)

  • Expiration date (in YYMMDDHHMMSSZ format)

  • Revocation date or empty if not revoked

  • Serial number (hexadecimal)

  • File location or unknown if not known

  • Distinguished name

As we did for the root certificate, check if the details of the intermediate certificate are correct.

  • Verify the intermediate certificate.

openssl x509 \
	-noout -text \
	-in intermediate/certs/intermediate.cert.pem
  • Verify the intermediate certificate against the root certificate. An OK indicates that the chain of trust is intact.

openssl verify \
	-CAfile certs/ca.cert.pem intermediate/certs/intermediate.cert.pem

If Root and Intermediate certificates are OK, it is time to create the certificate chain. When an application tries to verify a certificate signed by the Intermediate CA, it must also verify the intermediate certificate against the root certificate. To complete the chain of trust, create a CA certificate chain to present to the application.

To create the CA certificate chain, concatenate the intermediate and root certificates together. We will use this file later to verify certificates signed by the Intermediate CA.

cat intermediate/certs/intermediate.cert.pem certs/ca.cert.pem > intermediate/certs/ca-chain.cert.pem
  • Secure the CA certificate chain by making it universally readable while preventing any modifications.

chmod 444 intermediate/certs/ca-chain.cert.pem

NOTE: The certificate chain file must include the root certificate because no client application knows about it yet. A better option is to install your root certificate on every client that needs to connect. In that case, the chain file need only contain your intermediate certificate.

Server Certificate (sign server)

Next, we will create a certificate and key for our server/broker, sign it, and generate the Keystore to be used by HiveMQ.

We will be signing certificates using our Intermediate CA. We will use these signed certificates to authenticate clients connecting to HiveMQ Broker.

Our root and intermediate pairs are 4096 bits. Server and client certificates normally expire after one year, so we can safely use 2048 bits instead. Although 4096 bits are slightly more secure than 2048 bits, they slow down TLS handshakes and significantly increase processor load during handshakes. For this reason, we will use 2048-bit pairs.

In the following examples, you must replace broker1.hivemq.com / broker2.hivemq.com / broker3.hivemq.com with the FQDN of the individual nodes you are creating these for.

Generate the server’s private key, and use the private key to create a certificate signing request (CSR). The CSR details don’t need to match the Intermediate CA.

  • Change the current directory to the Root CA directory.

cd /etc/ssl/hivemq/root/ca
  • Generate an RSA private key (broker.key.pem).

openssl genrsa -aes256 \
	-passout pass:changeme \
	-out intermediate/private/broker.key.pem 2048
  • Ensure that only authorized users have the necessary access to the server/broker key.

chmod 400 intermediate/private/broker.key.pem

When configuring server and client certificates, it's essential to understand that the Common Name (CN) must be distinct from the names used in your Root or Intermediate certificates. To enhance versatility, we incorporate the Subject Alternative Name (SAN) into the server/broker certificate. This allows the certificate to remain valid for other broker nodes' Fully Qualified Domain Names (FQDNs). This strategy enables using a single certificate across multiple CNs, simplifying certificate management. It's important to note that when a certificate includes a Subject Alternative Name, the Common Name(s) are no longer considered, as SAN takes precedence.

openssl req \
	-config intermediate/intermediate-openssl.cnf \
	-key intermediate/private/broker.key.pem \
	-passin pass:changeme \
	-new -sha256 \
	-out intermediate/csr/broker.csr.pem \
	-subj "/C=DE/ST=Bavaria/L=Landshut/O=HiveMQ/OU=Customer Services/CN=HiveMQ Broker" \
	-addext "subjectAltName = DNS:broker1.hivemq.com,DNS:broker2.hivemq.com,DNS:broker3.hivemq.com,DNS:localhost,IP:127.0.0.1"

An additional benefit of using Subject Alternative Name (SAN) in server certificates is the ability to include multiple domain names (DNS) under a single Common Name (For example, broker1.hivemq.com and broker1.hivemq.cloud)

  • Sign the server’s key and generate its certificate. To create a certificate, use the Intermediate CA to sign the CSR. Since the certificate will be used on a server, use the server_cert extension. Certificates are usually given a validity of one year.

openssl ca \
	-config intermediate/intermediate-openssl.cnf \
	-extensions server_cert \
	-batch \
	-passin pass:changeme \
	-days 365 -notext -md sha256 \
	-in intermediate/csr/broker.csr.pem \
	-out intermediate/certs/broker.cert.pem
  • Secure the server/broker certificate by making it universally readable while preventing any modifications.

chmod 444 intermediate/certs/broker.cert.pem
  • Verify server/broker certificate.

openssl x509 -noout -text -in intermediate/certs/broker.cert.pem
  • Use the CA certificate chain file we created earlier (ca-chain.cert.pem) to verify that the new server certificate has a valid chain of trust.

openssl verify \
	-CAfile intermediate/certs/ca-chain.cert.pem intermediate/certs/broker.cert.pem

We now have all the necessary parts to produce a Server Keystore.

  • Concatenate the certificate chain, be careful here to avoid "key values mismatch” error. Create the server certificate chain concatenated in the right order: SERVER CERTIFICATE > INTERMEDIATE CA CERTIFICATE > ROOT CA CERTIFICATE

cat intermediate/certs/broker.cert.pem intermediate/certs/intermediate.cert.pem certs/ca.cert.pem > ../keystores/broker.chain.pem

isualizing certificate hierarchy in KeyStore Explorer and the SAN extension.

  • Import the server/broker certificate chain and the private key into a PKCS12 container.

openssl pkcs12 \
	-export -in ../keystores/broker.chain.pem -passin pass:changeme \
	-inkey intermediate/private/broker.key.pem -passout pass:changeme > ../keystores/broker.p12
  • Import the contents of the PKCS12 container into a JKS container.

keytool -importkeystore \
	-trustcacerts \
	-srcstorepass changeme \
	-srckeystore ../keystores/broker.p12 \
	-deststorepass changeme \
	-destkeystore ../keystores/hivemq-keystore.jks \
	-srcstoretype pkcs12 \
	-alias 1 -destalias "HiveMQ Broker Certificate"
  • Verify the Broker Keystore.

keytool -list -keystore ../keystores/hivemq-keystore.jks -storepass changeme
  • Remove the PKCS12 container and the server concatenated certificate.

rm -f ../keystores/broker.p12 ../keystores/broker.chain.pem

Visualizing certificate hierarchy in KeyStore Explorer and the SAN extension.

isualizing certificate hierarchy in KeyStore Explorer and the SAN extension.

At this point, we can securely connect to the broker using the server/broker certificate. You can test by adding the following TLS listener configuration in the config.xml file.

nano /opt/hivemq/conf/config.xml
<!-- Secure connection -->
        <tls-tcp-listener>
            <name>secure-listener</name>
            <port>8883</port>
            <bind-address>0.0.0.0</bind-address>
            <tls>
                <keystore>
                    <!-- Configuring the path to the key store -->
                    <path>/etc/ssl/hivemq/root/keystores/hivemq-keystore.jks</path>
                    <!-- The password of the key store -->
                    <password>changeme</password>
                    <!-- The password of the private key -->
                    <private-key-password>changeme</private-key-password>
                </keystore>
            </tls>
        </tls-tcp-listener>

Testing Server/Broker TLS connection by using broker certificate file with HiveMQ MQTT CLI.

mqtt pub -h broker1.hivemq.com -s -p 8883 -i TLSClient -t 'secure/connection/test' -m 'TLS works with server PEM certificate!' --cafile "/path/to/broker.cert.pem" -d

Now let's configure your cluster transport to use TLS as well. Because we use the same certificate for all of our HiveMQ Broker nodes, we set the same hivemq-keystore.jks as our Cluster Server Keystore and Server Truststore.

NOTE: Note that this step is applicable only for deploying a cluster setup. If your deployment involves a single HiveMQ Broker, this step can be omitted as it is not necessary.

<cluster>
    <enabled>true</enabled>
    <transport>
        <tcp>
            <bind-address>YOUR_CLUSTER_NODE_IP_HERE</bind-address>
            <bind-port>7800</bind-port>
            <tls>
                <enabled>true</enabled>
                <server-keystore>
                    <path>/etc/ssl/hivemq/root/keystores/hivemq-keystore.jks</path>
                    <password>changeme</password>
                    <private-key-password>changeme</private-key-password>
                </server-keystore>
                <server-certificate-truststore>
                    <path>/etc/ssl/hivemq/root/keystores/hivemq-keystore.jks</path>
                    <password>changeme</password>
                </server-certificate-truststore>
            </tls>
        </tcp>
    </transport>

    <discovery>
        <extension/>
    </discovery>
</cluster>

Client Certificates (sign client)

Now we can start creating certificates that our clients can present to the server while establishing a connection. You may replace mqtt-client with any desired name.

As before, our starting point is to generate the client's private key.

  • Change the current directory to the Root CA directory.

cd /etc/ssl/hivemq/root/ca
  • Generate an RSA private key (mqtt-client.key.pem).

openssl genrsa -aes256 \
	-passout pass:changeme \
	-out intermediate/private/mqtt-client.key.pem 2048
  • Ensure that only authorized users have the necessary access to the client key.

chmod 400 intermediate/private/mqtt-client.key.pem

Use the private key to create a certificate signing request (CSR). The CSR details don’t need to match the Intermediate CA. For client certificates, Common Name can be any unique identifier (eg. a client name).

openssl req \
	-config intermediate/intermediate-openssl.cnf \
	-key intermediate/private/mqtt-client.key.pem \
	-passin pass:changeme \
	-new -sha256 \
	-out intermediate/csr/mqtt-client.csr.pem \
	-subj "/C=DE/ST=Bavaria/L=Landshut/O=HiveMQ/OU=Customer Services/CN=HiveMQ MQTT Client"

Sign the client’s key and generate its certificate. To create a certificate, use the Intermediate CA to sign the CSR. Since the certificate will be used for client authentication, use the client_cert extension. Certificates are usually given a validity of one year, though a CA will typically give a few days extra for convenience.

openssl ca \
	-config intermediate/intermediate-openssl.cnf \
	-extensions client_cert \
	-batch \
	-passin pass:changeme \
	-days 375 -notext -md sha256 \
	-in intermediate/csr/mqtt-client.csr.pem \
	-out intermediate/certs/mqtt-client.cert.pem
  • Secure the client certificate by making it universally readable while preventing any modifications.

chmod 444 intermediate/certs/mqtt-client.cert.pem
  • Verify MQTT client certificate.

openssl x509 -noout -text -in intermediate/certs/mqtt-client.cert.pem
  • Use the CA certificate chain file we created earlier (ca-chain.cert.pem) to verify that the new client certificate has a valid chain of trust.

openssl verify \
	-CAfile intermediate/certs/ca-chain.cert.pem intermediate/certs/mqtt-client.cert.pem


Time to Generate the Client’s Truststore

  • Concatenate the certificate chain, but be careful here to avoid "key values mismatch” error. Create the client certificate chain concatenated in the right order: CLIENT CERTIFICATE > INTERMEDIATE CA CERTIFICATE > ROOT CA CERTIFICATE

cat intermediate/certs/mqtt-client.cert.pem intermediate/certs/intermediate.cert.pem certs/ca.cert.pem > ../keystores/mqtt-client.chain.pem

isualizing certificate hierarchy in KeyStore Explorer and the SAN extension.

  • Import the client certificate chain and the private key into a PKCS12 container.

openssl pkcs12 \
	-export -in ../keystores/mqtt-client.chain.pem -passin pass:changeme \
	-inkey intermediate/private/mqtt-client.key.pem -passout pass:changeme > ../keystores/mqtt-client.p12
  • Import the contents of the PKCS12 container into a JKS container.

keytool -importkeystore \
	-trustcacerts \
	-srcstorepass changeme \
	-srckeystore ../keystores/mqtt-client.p12 \
	-deststorepass changeme \
	-destkeystore ../keystores/mqtt-client-truststore.jks \
	-srcstoretype pkcs12 \
	-alias 1 -destalias "MQTT Client Certificate"

Given that clients present the entire certificate chain on connection, you only need to include the trusted Intermediate CA certificate in the HiveMQ Client Truststore.

keytool -import -trustcacerts -alias 'Intermediate CA Certificate' -file intermediate/certs/intermediate.cert.pem -noprompt -keystore ../keystores/mqtt-client-truststore.jks -storepass changeme
  • Verify the MQTT Client Truststore.

keytool -list -keystore ../keystores/mqtt-client-truststore.jks -storepass changeme
  • Remove the PKCS12 container and the concatenated certificate.

rm -f ../keystores/mqtt-client.p12 ../keystores/mqtt-client.chain.pem

isualizing certificate hierarchy in KeyStore Explorer and the SAN extension.

At this point, we can set the communication between the client and server using mutual TLS encryption (use of both client and server certificate). You can test by adding the following TLS listener configuration in config.xml file.

nano /opt/hivemq/conf/config.xml

 

<!-- Secure connection -->
        <tls-tcp-listener>
            <name>secure-listener</name>
            <port>8883</port>
            <bind-address>0.0.0.0</bind-address>
            <tls>
                <keystore>
                    <!-- Configuring the path to the key store -->
                    <path>/etc/ssl/hivemq/root/keystores/hivemq-keystore.jks</path>
                    <!-- The password of the key store -->
                    <password>changeme</password>
                    <!-- The password of the private key -->
                    <private-key-password>changeme</private-key-password>
                </keystore>
                <!-- The way HiveMQ authenticates client certificates - REQUIRED: A client certificate must be present -->
                <client-authentication-mode>REQUIRED</client-authentication-mode>
                <truststore>
                    <!-- Configuring the path to the trust store -->
                    <path>/etc/ssl/hivemq/root/keystores/mqtt-client-truststore.jks</path>
                    <!-- The password of the trust store -->
                    <password>changeme</password>
                </truststore>
            </tls>
        </tls-tcp-listener>

Notice that we set the <client-authentication-mode> to REQUIRED. This setting ensures that only MQTT clients that use a certificate that is part of the configured trust store are allowed to connect.

Testing Mutual TLS connection by using certificates files with HiveMQ MQTT CLI

Here, we need to enter our mqtt-client.key.pem private key password to access the key.

mqtt pub -h broker1.hivemq.com -s -p 8883 -i mutualTLSClient -t 'mutual/tls/connection/test' -m 'Mutual TLS works!' --cafile "/path/to/intermediate.cert.pem" --key "/path/to/mqtt-client.key.pem" --cert "/path/to/mqtt-client.cert.pem" -d

Visualizing certificate hierarchy in KeyStore Explorer and the SAN extension.Testing Mutual TLS connection by using Keystore and Truststores files with HiveMQ MQTT CLI:

The MQTT CLI offers support through the direct usage of Keystores and Truststores files (.jks), enabling Mutual TLS connections to the broker, refer to the example provided below. Here, we need to enter the KeyStore and TrustStore passwords.

mqtt pub -h broker1.hivemq.com -s -p 8883 -i mutualTLSClient -t 'mutual/tls/connection/test' -m 'Mutual TLS works!' --ks "/path/to/mqtt-client-truststore.jks" --ts "/path/to/hivemq-keystore.jks" -d

correct usage of Certificates, KeyStore and TrustStore with the HiveMQ MQTT CLI

This demonstrates the correct usage of Certificates, KeyStore and TrustStore with the HiveMQ MQTT CLI.

Software/Tool Versions and Operating System Used in This Guide

  • HiveMQ 4.23

  • MQTT CLI 4.23

  • OpenSSL 3.0.2

  • KeyStore Explorer 5.5.3

  • Ubuntu 22.04.3 LTS

Conclusion

Securing HiveMQ Broker deployments with Intermediate CA Certificates is a fundamental step towards fortifying the trust infrastructure of your digital ecosystem. By adopting a hierarchical approach and minimizing the usage of the root key, this guide empowers you to establish a robust security framework that safeguards the integrity and confidentiality of your MQTT communications.

Diego Duarte

Diego Duarte is a Senior Support Engineer at HiveMQ. Over the years, he has collaborated with many companies worldwide. He is passionate about problem-solving, innovative solutions, and troubleshooting complex technical challenges. A smart home aficionado and tech enthusiast at heart, you'll often find Diego actively participating in various communities and channels dedicated to IoT and home automation, sharing his expertise and learning from fellow enthusiasts.

  • Contact Diego Duarte via e-mail

Related content:

HiveMQ logo
Review HiveMQ on G2