Configuring mutual TLS authentication for an IBM MQ queue manager and client messaging app – IBM Developer

Join the Digital Developer Conference: AIOps & Integration to propel your AI-powered automation skills Register for free

Configuring mutual TLS authentication for a messaging application

In this tutorial, we’ll take an in-depth, hands-on look at how TLS authentication works with IBM MQ. We explained key TLS and security concepts in our introductory MQ with TLS tutorial.

In this tutorial, we’ll show you how to secure the queue manager and a client application, enabling them to complete a two-way TLS handshake and secure a messaging channel. Although we’re using a Java example, the steps apply to securing applications more generally with MQ.

What we’ll do

We’ll first set up anonymous authentication (where only the MQ server provides a certificate), and then we’ll set up mutual authentication (both the MQ server and your client application provide a certificate). We’ll use self-signed certificates, which are useful to explain the concepts behind what we’re doing, but remember that a self-signed certificate should never be used in a production environment. In production, you will need to supply certificates signed by a Certificate Authority (CA).

We’ll walk you through setting up mutual authentication for a Java application. We’ll use the JmsPutGet.java application, which we created in this MQ Java application tutorial, but the steps in this tutorial are applicable in concept to any type of messaging application that you want to secure and the server-side setup will be the same for all application languages.

We’ll start by enabling anonymous authentication, where only the server provides a certificate to the client. This model is often used with username and password authentication from the client side, so the connection is secure without the need for a server to store credentials for every client application it trusts. Importantly, the client verifies the server has a trusted certificate before it sends data to it.

Unlike our other TLS tutorial, where we set up anonymous authentication for MQ in a container, in this tutorial we’ll set up TLS on a local MQ installation, so that each step in the process is easy to follow. The steps shown will work for Linux and should work for Windows with small syntax changes.

After we’ve enabled anonymous authentication, we’ll build on that and set up mutual authentication. This is where both the client and server provide certificates to verify each other when they communicate.

Ready? Let’s get started!

Setting up anonymous authentication

First, we’ll need to set some properties on the queue manager channel we’re going to secure. We can do this via the console or the command line. Here, we’ll use the command line. If you followed this tutorial on setting up a queue manager, you should have a queue manager called QM1. Enter your queue manager’s command line interface with

runmqsc QM1

Now, type:

DISPLAY CHANNEL(DEV.APP.SVRCONN)

You should see properties for the channel, including SSLCIPH AND SSLCAUTH parameters. We’ll alter these so that they’ll work for anonymous authentication.

ALTER CHANNEL(DEV.APP.SVRCONN) CHLTYPE(SVRCONN) SSLCIPH(ANY_TLS12)
ALTER CHANNEL(DEV.APP.SVRCONN) CHLTYPE(SVRCONN) SSLCAUTH(OPTIONAL)

If you display the channel again, you should see output that looks like this:

Queue Manager Channel

Next, type EXIT to exit the runmqsc interface.

Now that we’ve prepared the channel for anonymous authentication, move into the ssl directory for your queue manager (ours is QM1).

cd /var/mqm/qmgrs/QM1/ssl

Create a keystore (a .kdb file) using the MQ security tool command runmqakm:

runmqakm -keydb -create -db key.kdb -pw <password> -stash
sudo chgrp mqm *
sudo chmod 640 *

Don’t forget to set your own password!

We give the mqm group read access to the keystore and other files here because MQ requires this access. If you display the contents of your directory, you’ll see that three other files have been created. The key.sth file stores the password that you set; the other two files can be ignored. The contents of your directory should look something like this:

keystore directory contents

Next, create a self-signed certificate and private key and put them in the keystore. The command below sets a label, which must have a specific format: ibmwebspheremq<QueueManagerNameInLowercase>.

In our example, the correct command for a queue manager QM1 is this command:

runmqakm -cert -create -db key.kdb -stashed -dn "cn=qm,o=ibm,c=uk" -label ibmwebspheremqqm1

Notice that for our distinguished name ( -dn ) flag, we specify the common name (cn=qm), organization name (o=ibm) and country (c=uk). We’ll see these credentials on the queue manager certificate when we run with SSL logging enabled.

Now, let’s extract the queue manager certificate, which we’ll then give to the client application.

runmqakm -cert -extract -label ibmwebspheremqqm1 -db key.kdb -stashed -file QM.cert

Now that we’ve extracted our certificate, we need to save it into the truststore of our application (the store containing the trusted certificates), which we’ll also need to create.

We are using a Java application as an example in this tutorial. If you’re using another language, the same concepts will apply but more specific information on how to do this can be found in the marked answer to this StackOverflow question, which was written by the MQ team. The answer talks about Python, but other languages that use the MQI API work in this way too, including C, Golang, Node.js, and more. The key difference will be the way that you specify CipherSpecs in the code. You can explore other examples of CipherSpecs enabled in applications written in various languages in our samples repo.

In this example, our Java application uses JMS and can be found in the folder /home/user/mqApp.

Let’s navigate to the location of our client application and create a client truststore (a store that contains all certificates the client trusts in PKCS#12 format), using a Java command line tool, keytool. This step will also import the queue manager certificate so the client application knows it can trust the queue manager.

cd /home/user/mqApp
keytool -keystore clientTruststore.p12 -storetype pkcs12 -storepass <put your password here!> -importcert -file /var/mqm/qmgrs/QM1/ssl/QM.cert -alias server-certificate

You’ll be asked if you want to trust the certificate you’re adding. So, type yes.

Finally, we need to add information to the client messaging application about the CipherSpec we’re using. Open up your Java messaging application in an editor of your choice and add the following line to the application.

cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12 ");

This line tells your app to use the most secure TLS 1.2 CipherSpec that the queue manager supports. In the screenshot below, we’ve added the line after the other variables that we set in the JmsPutGet.java application, which we set up in our previous tutorial:

CipherSpec info

Don’t forget to recompile your application! We recompile with this command:

javac -cp ./com.ibm.mq.allclient-9.2.0.0.jar:./javax.jms-api-2.0.1.jar com/ibm/mq/samples/jms/JmsPutGet.java

Here’s what we’ve done thus far:

  • Told the MQ channel to use anonymous TLS authentication
  • Created a server keystore to store the server’s public and private keys
  • Extracted the server certificate
  • Created a client truststore
  • Stored the server certificate in the client truststore
  • Added CipherSpec information to our application
  • Recompiled our application so it’s ready to run

Now that’s all set up, we can run our client application. We have to specify the location of our truststore as a command-line argument when we issue the Java run command, so that when our application connects to MQ and receives the server certificate it can check that it’s present in the client truststore.

When we run the MQ Java application, we add arguments telling Java where to find the trusted certificates and how to access them. As in our example, we aren’t using an IBM JRE so we also add an argument to specify this (useIBMCipherMappings=false). The full command we use becomes:

java -Djavax.net.ssl.trustStore=clientTruststore.p12 -Djavax.net.ssl.trustStorePassword=<your password here> -Dcom.ibm.mq.cfg.useIBMCipherMappings=false -cp ./com.ibm.mq.allclient-9.2.0.0.jar:./javax.jms-api-2.0.1.jar:. com.ibm.mq.samples.jms.JmsPutGet

Press enter and the message is sent via a secure channel!

Message sent via secure channel

Adding debug information

If we want to see the information that the server and client send to each other when we run our application, we can add these arguments when we issue the run command:

-Djavax.net.debug=true -Djavax.net.debug=ssl:handshake

This will provide extra information on the negotiation between the client and server. For example, when we run our application with this command, we can see the part of the conversation where the server presents a certificate:

Adding debugging info

If you look at more of the debugging info, you can also see where the client sends no certificates (as this is anonymous authentication) and where the two parties exchange encryption information.

Setting up mutual authentication

Now that we’ve sent the message verifying the server certificate, we can also require that the client application provides a certificate to the server, thereby setting up mutual authentication.

The two sides complete a two-way handshake, where both sides negotiate a CipherSpec to use and present and verify each other’s certificates by comparing them to the certificates in their truststores. Once this has happened, both the server and client know they can trust each other.

You don’t have to use mutual authentication to secure a connection. Instead, you can secure the client application by using username-and-password authentication, which is why some applications don’t need it. You should always ensure that your applications are secured in some way.

In Java, it’s good practice to make your keystores (that contain your application’s private keys) and truststores (that contain certificates of things you trust, such as a queue manager) separate stores. Using MQ with other languages, however, it’s common to see a keystore (in MQ, a .kdb file) also used as a truststore, which will contain the server’s private key and trusted client certificates. We usually refer to this object as a key repository.

First, we’ll need to set the channel authentication to required so that both the server and client will need to provide a trusted certificate. Do this with these commands:

runmqsc QM1
ALTER CHANNEL(DEV.APP.SVRCONN) CHLTYPE(SVRCONN) SSLCAUTH(REQUIRED)
EXIT

Now we’re ready to create a public and private key pair for the application. Enter your application directory and create a key and keystore with these commands:

cd /home/user/mqApp
keytool -genkeypair -keyalg RSA -alias client-key -keystore clientKeystore.p12 -storepass <put_your_password_here> -storetype pkcs12

Enter details about your application and organization when prompted, then type yes to confirm your details. You can view the certificates in your keystore with this command:

keytool -list -v -keystore clientKeystore.p12

View certificates in keystore

We can see there is a certificate with the alias client-key with the details you provided. Extract the client certificate to the file clientCert.crt with this command:

keytool -export -alias client-key -file clientCertificate.crt -keystore clientKeystore.p12

You should be able to see the file clientCertificate.crt in your application directory.

Now, we’ll add that certificate to the queue manager’s key repository, so the server knows that it can trust the client. Navigate back to your queue manager’s ssl directory and add the certificate to the key repository (the key.kdb file).

cd /var/mqm/qmgrs/QM1/ssl
runmqakm -cert -add -db key.kdb -stashed -label ibmwebspheremqapp -file /home/user/mqApp/clientCertificate.crt

In our example, we label the certificate ibmwebspheremqapp to allow the server to associate the certificate with the application when it receives a connection request as part of the TLS handshake.

List the certificates in the key repository with this command:

runmqakm -cert -list -db key.kdb -stashed

You should see the queue manager’s personal certificate, and also the trusted certificate with the label ibmwebspheremqapp we added.

Listing the certificates

Now that we’ve made changes to our keystore, we can force our queue manager to pick up these changes by issuing a command directly to our queue manager one last time:

runmqsc QM1
REFRESH SECURITY(*) TYPE(SSL)
EXIT

Finally, we just need to move back to our application directory:

cd /home/user/mqApp

Then, add an extra pair of command-line arguments (on top of the pair we added in the anonymous authentication stage) to our java run command, to specify the client keystore and password:

-Djavax.net.ssl.keyStore=<clientKeystore.p12> -Djavax.net.ssl.keyStorePassword=<put_your_password_here>

So, to run our application with mutual TLS authentication enabled, we run this command:

java -Djavax.net.ssl.trustStore=clientTruststore.p12 -Djavax.net.ssl.trustStorePassword=<put_your_password_here> -Djavax.net.ssl.keyStore=clientKeystore.p12 -Djavax.net.ssl.keyStorePassword=<put_your_password_here> -Dcom.ibm.mq.cfg.useIBMCipherMappings=false -cp ./com.ibm.mq.allclient-9.2.0.0.jar:./javax.jms-api-2.0.1.jar:. com.ibm.mq.samples.jms.JmsPutGet

And, we should see that the message has been sent and received successfully!

If we want to view additional security handshake information, remember we can get additional debugging information by adding the arguments -Djavax.net.debug=true and -Djavax.net.debug=ssl:handshake to our command-line arguments when we run our application.

Using these arguments, we run our app with this command:

java -Djavax.net.ssl.trustStore=clientTruststore.p12 -Djavax.net.ssl.trustStorePassword=<put_your_password_here> -Djavax.net.ssl.keyStore=clientKeystore.p12 -Djavax.net.ssl.keyStorePassword=<put_your_password_here> -Dcom.ibm.mq.cfg.useIBMCipherMappings=false -Djavax.net.debug=true -Djavax.net.debug=ssl:handshake -cp ./com.ibm.mq.allclient-9.2.0.0.jar:./javax.jms-api-2.0.1.jar:. com.ibm.mq.samples.jms.JmsPutGet

Again, we can see the server present its certificate, but now we can see the client presenting its certificate too:

Listing the client and server certificates

As you can see, the issuer and subject flags contain the details that you entered when you created the client keys. Finally, we can see that after all the authentication steps, the message was sent and received as expected.

Secure message sent and received

Using mutual TLS in production

We have secured a queue manager and client application and allowed them to communicate with mutual TLS authentication. We accomplished this using self-signed certificates, which again should not be used in production. To use mutual TLS in production, we simply need to import certificates signed by a certificate authority rather than those that are self-signed.

Summary and next steps

In this tutorial, we first configured a queue manager to use anonymous TLS authentication so a client can authenticate the queue manager when they connect. Then, we added mutual TLS authentication to allow the queue manger to authenticate the client application too. We also discussed how IBM MQ handles security.

For more information on using TLS with MQ, review this docs page on working with TLS. If you’d like to learn more of the basics about TLS with IBM MQ, try this introductory TLS tutorial, which discusses anonymous authentication in multiple languages. If you want to play with some TLS-enabled samples in a language of your choice, check out our github repo, which contains some code samples that can use in your own projects.