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
You should see properties for the channel, including
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:
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).
Create a keystore (a
.kdb file) using the MQ security tool command
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:
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:
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
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
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:
Don’t forget to recompile your application! We recompile with this command:
javac -cp ./com.ibm.mq.allclient-184.108.40.206.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-220.127.116.11.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!
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:
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:
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
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
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.
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:
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:
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-18.104.22.168.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=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-22.214.171.124.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:
As you can see, the
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.
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.