Secure communication between IBM MQ endpoints with TLS

In other tutorials (the “Ready, set, connect” series and “Write and run your first IBM MQ JMS application“) we showed you how to set up and use point-to-point messaging between a JMS application and an MQ server. In these tutorials, the communication between the client and the server that flows over the internet was not encrypted. This meant that the contents of the message travelled in plain text and could have been viewed by a third party.

This tutorial will get you started with securing messages in transit through the use of Transport Layer Security (TLS) for MQ.

It’s important to note that the default developer configuration that are used in the other Ready, set, connect tutorials includes authentication and authorization controls for the app and admin users.

TLS is a cryptographic encryption protocol that protects data in transit. See Figure 1 for an explanation of how the TLS handshake between a client and a server works:

Figure 1: A TLS handshake

Graphic showing a TLS handshake

In Figure 1:

  1. The server and client communicate to establish connection settings.
  2. The client verifies the server certificate.
  3. The client generates a cipher and encrypts it using the server’s public key. This is shared with the server and used to generate a symmetric key to encrypt the remainder of the session.

TLS authentication methods include anonymous and mutual authentication.

In this tutorial, we will set up the simplest configuration, in which we provide a certificate to the server side only.

How does TLS work in IBM MQ?

TLS is used in MQ to secure channels. It provides both integrity and encryption protection. When a channel is enabled for TLS, any messages that are passed by the channel will be protected while in transit. You can learn more about TLS in the IBM MQ documentation.

In a production environment, the queue manager will usually have a digital certificate issued by a certificate authority. Your MQ administrator should provide any certificates that you might need to use with your client application.

In this tutorial, we will be creating a self-signed certificate and extracting the public key from it for the client to use. A self-signed certificate is signed with its own private key; that is, “I am trustworthy because I say so.” It should not be used in a production environment, but it is useful in this example to understand how the TLS configuration works.

To demonstrate the basic MQ TLS configuration, we will use the IBM MQ Docker image to run an MQ server in a Docker container. We could run the server wherever we like, but the MQ container makes it easy to configure security from an MQ administration perspective You can learn more about the IBM MQ Docker image in Docker Hub.

Prerequisites

Steps

These are the steps we’ll go through to set up TLS:

  1. Create TLS objects: Use OpenSSL to create the TLS objects, a self-signed server key and certificate. Then, create a client keystore to store a copy of the server certificate on the client side, so the client knows it can trust the server
  2. Set up the MQ server: Start an MQ queue manager (our server) running in a Docker container which is set up for TLS encrypted messages
  3. Secure an application: Edit some sample code to enable it to send encrypted messages to the queue manager

Step 1. Create TLS objects

We need to create a server key and certificate. Then, we need to create a client keystore, either using JMS or other MQ client libraries.

Create a server key and certificate

To create the certificates we need to secure our channel, we will use OpenSSL, which is installed by default on most machines. Check if you have OpenSSL installed by entering this command in your terminal:

openssl version

If you need OpenSSL, you need to download it.

Create a new directory on your machine and navigate into it in your terminal.

Now we can create the server key and certificate with this command:

openssl req -newkey rsa:2048 -nodes -keyout key.key -x509 -days 365 -out key.crt

You will be prompted to enter some information. Put whatever you like, it’s a self-signed certificate so it’s for your eyes only. Verify the certificate has been created successfully with this command:

openssl x509 -text -noout -in key.crt

You should see the information you entered and other certificate properties such as the public key and the signature algorithm.

Create a client keystore

The instructions for creating a client keystore differ slightly for Java code using the JMS API and code in other languages that use the MQ Client Libraries (such as C, Python, Node.js, or Golang).

Follow the set of steps that follow based on your language.

Creating a JMS keystore (Java)

For our example, we will be securing some Java code written for JMS. We will use keytool (a Java security tool), which is included with Java JREs and SDKs. To create a .jks client keystore and import our sever certificate into it, enter:

keytool -keystore clientkey.jks -storetype jks -importcert -file key.crt -alias server-certificate

You will be prompted to create a password. Be sure to remember the password that you set as you’ll need it later on.

Listing the contents of the directory should yield something like this:

Output of the keytool command

Move the clientkey.jks file into a new folder in a different directory that you will remember. A good idea is to create a new folder in the root directory of your client application and put the .jks file there.

Now you should have only the .crt file and the .key file in your current directory. If this is not the case, the container will not work! You’ll need to return to the beginning of step 1 and check that you completed all of the tasks so that these files are in the right place.

Creating a keystore for MQI-based client applications (C, Python, Node.js, Golang)

If you’re using MQI, whatever language you write your application in (such as C, Python, Node.js, or Golang), these steps will help you create a client keystore and import our server certificate into it.

If you don’t have the MQ client libraries installed, it’s time to get them:

* For MacOS, follow the steps in the [MQ MacOS Toolkit tutorial](/tutorials/mq-macos-dev).

* For Windows or Linux MQ clients, download and install one from the [IBM Support site](https://www.ibm.com/support/pages/node/712701).

Now that you have the MQ client libraries, you’ll have the MQ security command line tool, runmqakm. Enter this command to create a keystore in .kdb format and store the password in a .sth file. In this example, we used the password passw0rd but you can change this to be one of your choosing.

runmqakm -keydb -create -db clientkey.kdb -pw [!!pick_a_passw0rd_here!!] -type pkcs12 -expire 1000 -stash

We have generated a stash file that contains the keystore password to simplify the following steps. If you would prefer to enter the keystore password manually then removde the -stash option from the command and be sure to remember the password you set!

Next, import the server’s public key certificate into the client keystore by entering this command:

runmqakm -cert -add -label QM1.cert -db clientkey.kdb -stashed -trust enable -file key.crt

If you list the contents of the current directory, you should see these files:

Output of the keytool command

Move the clientkey.kdb and clientkey.sth files into a new folder in a different directory that you will remember. A good idea is to create a new folder in the root directory of your client application and put the files there.

Now you should have only the .crt file and the .key file in your current directory. If this is not the case, the container will not work! You’ll need to return to the beginning of step 1 and check that you completed all of the tasks so that these files are in the right place.

Step 2. Set up the MQ server

Our first step in setting up the MQ server is to install or start Docker.

If you completed the “Get an IBM MQ queue for development in a container” tutorial, you installed Docker in Step 1 and you installed the MQ in Docker image in Step 2. If you did not complete this tutorial, complete step 1 and step 2 in that tutorial now.

You are now ready to run a container from the latest MQ Docker image. If you completed the “Get an IBM MQ queue for development in a container” tutorial, you will need to stop this container by using the docker stop <container_id> command or you need to select different ports to map to on your host OS.

To run the latest MQ Docker image, enter the following command in your terminal, specifying the full path to the directory that contains your .key and .crt files. In this example, we used the password passw0rd for the app user but you should specify the one you used previously.

docker run --name mqtls --env LICENSE=accept --env MQ_QMGR_NAME=QM1 --volume [!!path to directory with key and crt files!!]:/etc/mqm/pki/keys/mykey --publish 1414:1414 --publish 9443:9443 --detach --env MQ_APP_PASSWORD=passw0rd ibmcom/mq:latest

Enter the following command to see a container ID:

docker ps

You should see output similar to this:

Output from docker ps command

Be sure to make a note of your container ID as you will need this later on.

If your container is not running…

A common problem is not having the correct read permissions set on the key.key file. Since the user and group owners of this file will unlikely exist inside the container, and depending on your umask settings, we need to check if the file is readable inside the container.

Check the docker logs for the container:

docker logs <container_id>

If the key file cannot be read, you will see an error similar to:

Output of sample error message from docker logs

The file permissions can then be changed with the chmod command:

chmod o+r <path_to_key_file>/key.key

Verify that security has been enabled

Let’s have a look at the queue manager to check that security has been enabled. Exec into the container with this command:

docker exec -it <container_id> bash

Then, enter this command:

runmqsc QM1

This command allows you to use the MQSC interface for the queue manager. Find out about channel properties by entering this command:

DISPLAY CHANNEL(DEV.APP.SVRCONN)

You’ll see output similar to:

Output about the channel properties

We see that the SSLCIPH option has been configured to use the ANY_TLS12 CipherSpec. When the SSLCIPH option is set, it turns on TLS encryption for any connections to the queue manager using this channel. Having the CipherSpec set to ANY_TLS12 works exactly how you’d expect: the channel will allow connections with any valid TLS 1.2 CipherSpec. Read more about the ANY_TLS12 CipherSpec in this community article.

In this tutorial, we use anonymous (server-only) authentication, as we authenticate the client with the application name and password. The CERTLABL option is the label for the certificate we supplied implicitly during the Docker run step above.

Figure 2: TLS authentication methods

Graphic showing TLS authentication methods

In Figure 2:

  1. Anonymous authentication: The server provides a certificate to the client.
  2. Mutual authentication: Both the server and the client provide a certificate and authenticate each other.

We will need to specify the same CipherSpec on the client side for the client and server to be able to connect and carry out the TLS handshake.

Exit the MQSC interface with exit, and exit the container with exit too.

Step 3. Secure an application

The instructions for securing an application differ slightly for JMS applications and MQI client applications (such as C, Python, Node.js, or Golang). Follow the set of steps based on your language.

Secure and run a JMS application (Java)

Get a JMS application up and running

First, follow the steps in the “Write and run your first IBM MQ JMS application” to get up and running with a JMS sample application, JmsPutGet.java. Make sure that you have the hostname and port number set up to match the details of your new TLS-enabled container.

Run a JMS application without encryption

Let’s see what happens if we try to connect to the queue manager without encryption on the client side.

From your top level application directory (MQClient if you’ve followed the JMS tutorial) compile the application with

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

and run with

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

The connection is rejected by the queue manager, with a lot of errors. You’ll see an MQRC_JSSE_ERROR, as well as a Remote CipherSpec error for channel DEV.APP.SVRCONN as we didn’t specify an encryption cipher on the client side. The stack will have parts that look like this:

Output of running JMS application

If your failed with a different error, try our handy cheatsheet to help you debug the error.

Encrypt the JMS application

Let’s add encryption to the client application. We’ll choose a specific ANY_TLS12-compatible CipherSpec but you can also set the spec to be *TLS12. Edit the JmsPutGet.java sample to add this line underneath the other connection factory properties:

cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "TLS_RSA_WITH_AES_128_CBC_SHA256");

Compile as above. When running, use this command, which includes setting some environment variables. You’ll need to edit the command before you run it to add in the fully qualified name of your trust store and its password.

java -Djavax.net.ssl.trustStoreType=jks -Djavax.net.ssl.trustStore=/your_key_directory/clientkey.jks -Djavax.net.ssl.trustStorePassword= -Dcom.ibm.mq.cfg.useIBMCipherMappings=false -cp ./com.ibm.mq.allclient-9.1.4.0.jar:./javax.jms-api-2.0.1.jar:. com.ibm.mq.samples.jms.JmsPutGet

Success! You will see something like:

Output from running an encrypted JMS app

Note: the -Dcom.ibm.mq.cfg.useIBMCipherMappings=false argument in the run command was necessary as we are using a non-IBM JRE. If you want to use an IBM JRE, simply remove this argument.

Secure and run an MQI application (C, Python, Node.js, or Golang)

For this tutorial, we’re only showing an example in one language: Python. For high-quality sample code securing other languages, have a look here. We’re assuming you have a Python installation and are in a Python environment where you’re happy to develop MQ applications.

Get a python application up and running

The MQ client functionality is implemented in Python with PyMQI, which is an open-source Python wrapper over the MQ client. Installing it is as easy as typing the following command in your terminal:

pip install pymqi

Let’s try and connect to an encrypted channel and send a message using an unencrypted Python program. Copy this (simple) Python program (which puts and then gets a message from a queue) and save it as a .py file:

import pymqi

queue_manager = 'QM1'
queue_name = 'DEV.QUEUE.1'
message = 'Hello from Python!'

cd = pymqi.CD()
cd.ChannelName = 'DEV.APP.SVRCONN'
cd.ConnectionName = 'localhost(1414)'
cd.ChannelType = pymqi.CMQC.MQCHT_CLNTCONN
cd.TransportType = pymqi.CMQC.MQXPT_TCP

qmgr = pymqi.QueueManager(None)
qmgr.connect_with_options(queue_manager, user='app', password='[!!password for user "app"!!]', cd=cd)

put_queue = pymqi.Queue(qmgr, queue_name)
put_queue.put(message)

get_queue = pymqi.Queue(qmgr, queue_name)
print(get_queue.get())

put_queue.close()
get_queue.close()

qmgr.disconnect()
Run a python application without encryption

Now we have our Python application, run it with this command:

python [!!name you gave the program!!]

The connection will be rejected by the queue manager, and you will see an error message as part of the returned output:

Output from running non-encrypted python application

This is because our unencrypted Python application tried to connect to an encrypted channel, which throws the error above.

Encrypt the python application

Let’s add encryption to our client application. We’ll choose a specific ANY_TLS12-compatible CipherSpec but you could also set the spec to be ANY_TLS12. After the cd.TransportType = pymqi.CMQC.MQXPT_TCP line, add these lines:

cd.SSLCipherSpec = 'TLS_RSA_WITH_AES_128_CBC_SHA256'
sco = pymqi.SCO()
sco.KeyRepository = '[!!your_keystore_location here!!]' # include file name but not file extension

Add the sco variable into your connect_with_options function like this:

qmgr.connect_with_options(queue_manager, user='app', password='[!!password for user "app"!!]', cd=cd, sco=sco)

Run your application again, and you should see:

Output of running encrypted python app

Summary and next steps

Congratulations! You configured basic TLS for MQ so the queue manager and the client application can now encrypt their communication as it flows over the internet. You used OpenSSL to create a self-signed digital certificate for the queue manager. This enabled the client and the server to negotiate a shared secret key to encrypt their session.

You should now understand what needs to be configured on the MQ server and the client application side, to use TLS with MQ. You have enabled the client and the server to communicate privately and securely using a self-signed certificate. Remember to upgrade to a real certificate before you put your apps into production!

To see production-style samples with encryption and logging enabled, visit our IBM Messaging GitHub repo.