What you will learn

  1. TLS basics
  2. Digital certificates on MQ server
  3. CipherSpec and client set up

What you will need

  1. Docker
  2. The latest MQ Docker image from Docker Hub
  3. JmsPutGet.java sample and its pre-reqs

TLS and MQ

In previous tutorials (Ready, Set, Connect and MQ with JMS) we showed how to set up and run 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 full contents of the message, including things like user ID, as well as the message data, travelled in plain text and could potentially be intercepted by a third party.
This standalone tutorial builds on concepts in the previous tutorials and introduces one approach for securing messages in transit through the use of Transport Layer Security (TLS) for MQ.

TLS is a powerful cryptographic protocol that uses encryption to protect data in transit. TLS encryption makes use of private and public key pairs with which authentication can happen and the client and server can establish cipher settings and a shared key to encrypt the information they exchange during the remainder of the session. TLS allows for different configurations. For this basic tutorial we will set up the simplest configuration, in which we provide a certificate to the server side only.

In production environments, the queue manager most likely has a digital certificate that is issued by a certificate authority. Your MQ administrator would provide any certificates that you might need with your client application. For the purpose of this tutorial we will be creating a self-signed certificate, containing the server’s private key, and extracting the public key for use by the client from it. A self-signed certificate is a certificate which is signed by using its own private key. It should not be used in a production environment, but is useful for us to test connection settings.

Using TLS with MQ in Docker

To demonstrate the basic MQ TLS configuration, we will use Docker to create separate server and client containers. We can make use of a pre-built MQ Docker image from Docker Hub to provide both the tooling (runmqakm) we need to create and populate the server keyStore and client trustStore, and also to quickly and easily get a queue manager up and running for our client to connect to.

As MQ data in Docker containers is not persistent by default, we will make use of a Docker volume. We will create and then persist the self-signed certificate on the Docker volume. For simplicity, both the server and client containers will be mounted on the same volume. This will make it easier when we want to copy the server’s public certificate from one container across to the the client’s trustStore in another.

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

  • In a temporary Docker container, use runmqakm to create the server keyStore and create and add the self-signed certificate the MQ queue manager will need for its TLS settings.
  • In this same container, use runmqakm to extract the server’s public key that will be needed by the client application. This temporary container can then be deleted.
  • Start an MQ queue manager (our server) running in a Docker container which is set up for TLS encrypted messages on a SVRCONN channel.
  • Start a Docker container for the client. Use the MQ Docker image again in order to use the runmqakm tool to create the client trustStore and copy the server’s public key into it.
  • Edit the JMS sample client code to enable it to send encrypted messages to the MQ queue manager.

Setting up the server keyStore and certificate

Our first step is to create a Docker volume which we will call ‘qmdata’. We will use this to store any data we wish to be persistent, both our server and client Docker containers will be mounted to it. If you do not already have Docker version 17.06 or above installed, see Ready, Set, Connect! (Docker) Section 1 for details on how to get it.

In your command line interface type:

docker volume create qmdata

If you don’t already have the latest MQ in Docker image, get one from Docker Hub:

docker pull ibmcom/mq:latest

When it’s done, check which images you have:

docker images

You’ll see output similar to:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ibmcom/mq           latest              4c0dd6b16b70        2 days ago          773MB

We now want to run a container from the latest pre-built MQ in Docker image, add the Docker volume we created and enter the container as soon as it starts.

docker run -ti --entrypoint=/bin/bash --volume qmdata:/mnt/mqm ibmcom/mq:latest

Once inside the container, change to the directory that is mounted on the volume:

cd /mnt/mqm

Create a folder for digital certificates in this directory

mkdir -p MQServer/certs

and then change directory into this new folder:

cd MQServer/certs

Create a key database (also called the keyStore or certificate store), and add and stash the password for it.

runmqakm -keydb -create -db key.p12 -pw k3ypassw0rd -type pkcs12 -expire 1000 -stash

Check what you have:

ls

You’ll see:

key.p12  key.sth

The database key.p12 is the keyStore and key.sth is the file with the stashed password.

If you want to check the contents of the keyStore, you can run the command:

runmqakm -cert -list all -db key.p12 -stashed

At this point you’ll see:

No certificates were found.

Use the runmqakm tool to create a self-signed certificate. The command line options we are using determine where the certificate should be held (key.p12), the label attached to the certificate (ibmwebspheremqqm1), the distinguished name to be included in the certificate (cn=qm,o=ibm,c=uk) and the keysize (2048 bits).

runmqakm -cert -create -db key.p12 -label ibmwebspheremqqm1 -dn "cn=qm,o=ibm,c=uk" -size 2048 -default_cert yes -stashed

If we look at the contents of the keyStore now, we’ll find that this self-signed certificate has been generated and added.

runmqakm -cert -list all -db key.p12 -stashed

You’ll see:

Certificates found
* default, - personal, ! trusted, # secret key
-       ibmwebspheremqqm1

We now need to extract the public key that the client application will need to be able to communicate with the queue manager. This is saved to a file named QM1.cert.

runmqakm -cert -extract -db key.p12 -stashed -label ibmwebspheremqqm1 -target QM1.cert

Check what you have:

ls

You’ll see:

QM1.cert key.p12  key.sth 

The QM1.cert file has the queue manager’s public key certificate.

When you exit the container it will automatically end but the certificate data will persist in the Docker volume.

exit

The container is no longer needed so it can be removed. If you do not know the container id from earlier, use the following command to find it:

docker ps -a

You can then use the rm command to remove it:

docker rm /your-container-id/

The digital certificate will be available for the queue manager when we run the server container.

MQ queue manager in Docker

We are now ready to run our container to start up the queue manager.

First create a Docker network so that the MQ server and the client application can communicate between their separate Docker containers.

docker network create mq-demo-network

Start up a new container, start the queue manager and let it pick up the certificate from the Docker volume. We are including commands to attach the volume, specify the network, and the location of the keyStore with its password.

docker run --env LICENSE=accept --env MQ_QMGR_NAME=QM1 --volume qmdata:/mnt/mqm --publish 1414:1414 --publish 9443:9443 --network mq-demo-network --network-alias qmgr --detach --env MQ_APP_PASSWORD=passw0rd --env MQ_TLS_KEYSTORE=/mnt/mqm/MQServer/certs/key.p12 --env MQ_TLS_PASSPHRASE=k3ypassw0rd ibmcom/mq:latest

Give the container a moment, then check to see it is running.

docker ps

You’ll see a similar output to:

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                                            NAMES
2b67d17ef597        ibmcom/mq:latest    "runmqdevserver"    2 minutes ago       Up 2 minutes        0.0.0.0:1414->1414/tcp, 0.0.0.0:9443->9443/tcp   priceless_villani

Use the -ti option to get command line access inside the container.

docker exec -ti /your-container-id/ /bin/bash

Once inside the container, start the MQSC interface for the queue manager.

runmqsc QM1

When you see,

Starting MQSC for queue manager QM1

the interface is ready for your input.

Issue the command to show the channel configuration for the channel the application will use:

DISPLAY CHANNEL(DEV.APP.SVRCONN)

You will see:

AMQ8414I: Display Channel details.
   CHANNEL(DEV.APP.SVRCONN)                CHLTYPE(SVRCONN)
   ALTDATE(2018-04-20)                     ALTTIME(17.01.22)
   CERTLABL( )                             COMPHDR(NONE)
   COMPMSG(NONE)                           DESCR( )
   DISCINT(0)                              HBINT(300)
   KAINT(AUTO)                             MAXINST(999999999)
   MAXINSTC(999999999)                     MAXMSGL(4194304)
   MCAUSER(app)                            MONCHL(QMGR)
   RCVDATA( )                              RCVEXIT( )
   SCYDATA( )                              SCYEXIT( )
   SENDDATA( )                             SENDEXIT( )
   SHARECNV(10)                            SSLCAUTH(OPTIONAL)
   SSLCIPH(TLS_RSA_WITH_AES_128_CBC_SHA256)
   SSLPEER( )                              TRPTYPE(TCP)
        

The DEV.APP.SVRCONN channel has been configured to use the TLS_RSA_WITH_AES_128_CBC_SHA256 CipherSpec. When the SSLCIPH option is set, it turns on TLS encryption for any connections to the queue manager using this channel. 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.

The other property to note is SSLCAUTH, which is set to OPTIONAL in this case. This allows for both 1-Way and 2-Way TLS authentication. The server authentication by the client is mandatory so the server always needs a certificate. This is 1-Way authentication. If the client also has a certificate, 2-way authentication can happen. If a client provides a certificate then it will be used for authentication, however if it does not then client authentication does not happen but the connection is still allowed. We are using 1-Way authentication in this tutorial as only the server has a certificate, so our TLS configuration is set up for encryption and server authentication only. The client authentication is carried out separately using the application name and password.

Exit the MQSC interface.

END

Exit the queue manager container.

exit

The container will continue to run so we can connect to the queue manager from the client container.

Setting up the client’s trustStore

We are now going to start up a new container for the client. This is also mounted on the Docker volume and needs access to the Docker network we created. We are running the container in interactive mode in order to gain command line access as soon as it starts.

docker run -ti --entrypoint=/bin/bash  --volume qmdata:/mnt/mqm --network mq-demo-network ibmcom/mq:latest

Change to the directory that is mounted on the volume:

cd /mnt/mqm

Create a folder for digital certificates and then change directory into this new folder:

mkdir -p MQClient/certs
cd MQClient/certs

Use runmqakm to create a client trustStore.

runmqakm -keydb -create -db client_key.p12 -pw tru5tpassw0rd -type pkcs12 -expire 1000

A trustStore and a keyStore are both used to store SSL Certificates. Their name denotes the type of certificate they contain. Truststores are used to store public certificates whilst keyStores are used to store private certificates of the client or server.

Let’s check what we’ve created

ls

You will see:

client_key.p12

Again the runmqakm list option can be used to inspect the trustStore. At this point it’s empty.

runmqakm -cert -list all -db client_key.p12 -pw tru5tpassw0rd

From the /mnt/mqm/MQClient/certs folder, run the command to add the public key certificate to the client’s trustStore.

runmqakm -cert -add -label QM1.cert -db client_key.p12 -type pkcs12 -pw tru5tpassw0rd -trust enable -file ../../MQServer/certs/QM1.cert

Inspecting the contents of the trustStore will now show the queue manager’s public key.

runmqakm -cert -list all -db client_key.p12 -pw tru5tpassw0rd

As you can see the certificate has been added and it is a trusted certificate. We set this when we enabled the trust option on adding the certificate in the last runmqakm command.

Certificates found
* default, - personal, ! trusted, # secret key
!       QM1.cert

We have now completed the TLS administrative set up of our tutorial scenario, and are ready to use the JMS sample to try and send a message from the client container to the queue inside the queue manager container.

Setting up the client Docker container

In order to edit and run our JMS application, we first need to equip our client container with the tools we need. Firstly, we need the wget and vi tools, in order to get the pre-req jars we need and edit the JMS client code. In the client container run:

apt-get -qq update && apt-get -qq install wget && apt-get -qq install vim

We also need a JDK in order to compile our code, let’s download IBM SDK, Java Technology Edition, Version 8. Typically you would not install the JDK in the location we are going to, but as containers are temporary we are choosing to install to this location in order for it to persist, in case the container is deleted.

From the MQClient directory, create a new directory called opt and change directory into it:

cd /mnt/mqm/MQClient
mkdir opt
cd opt

Using wget we can retrieve the JDK we need:

wget http://public.dhe.ibm.com/ibmdl/export/pub/systems/cloud/runtimes/java/8.0.5.16/linux/x86_64/ibm-java-sdk-8.0-5.16-x86_64-archive.bin

Add execute permission to the file:

chmod +x ibm-java-sdk-8.0-5.16-x86_64-archive.bin

Now we can install Java. Follow the on screen instructions and accept all defaults.

./ibm-java-sdk-8.0-5.16-x86_64-archive.bin

Finally ensure that the JDK is on the system path:

export PATH=$(pwd)/ibm-java-x86_64-80/bin:$PATH

The final part of the preparation is to get a copy of the JMS application’s pre-requisite jar files. Let’s create a libs directory off of our MQClient folder:

cd ..
mkdir libs

Change directory into this folder and use wget to add the two pre-requisite jar files into this libs folder:

cd libs
  • Get the latest version of the IBM MQ com.ibm.mq.allclient.jar:
    wget http://central.maven.org/maven2/com/ibm/mq/com.ibm.mq.allclient/9.0.4.0/com.ibm.mq.allclient-9.0.4.0.jar
  • Get the latest JMS API jms.jar:
    wget http://central.maven.org/maven2/javax/jms/javax.jms-api/2.0.1/javax.jms-api-2.0.1.jar

Move back out to your base client directory, /mnt/mqm/MQClient and create a directory structure for the JMS sample in the MQClient folder.

cd ..
mkdir -p com/ibm/mq/samples/jms

From the MQClient/com/ibm/mq/samples/jms directory, issue the command to grab the JmsPutGet.java sample from GitHub.

cd com/ibm/mq/samples/jms
wget https://raw.githubusercontent.com/ibm-messaging/mq-dev-samples/master/gettingStarted/jms/JmsPutGet.java

Edit, compile and run your application to test the connection

You can now use vi to edit the the JMSPutGet.java file. (If you are unfamiliar with the vi editor, there are a multitude of good guides online. You will need to use either the arrow keys or the keyboard keys ‘h’, ‘j’, ‘k’ and ‘l’ to navigate around the file; ‘i’ to enter insert mode and ‘Esc’ to return back to command mode; and :wq to save your changes and exit out of the vi editor.)

First, let’s set up our application to try and connect without providing TLS capabilities. Use

vi JmsPutGet.java

to start editing the file, and replace the HOST and APP_PASSWORD variables to match your queue manager configuration. In our set up we set HOST, or network-alias, to be “qmgr” and the application password is set as “passw0rd”. All other variables should be left as default.

// Create variables for the connection to MQ
private static final String HOST = "_YOUR_HOSTNAME_"; // Host name or IP address
private static final int PORT = 1414; // Listener port for your queue manager
private static final String CHANNEL = "DEV.APP.SVRCONN"; // Channel name
private static final String QMGR = "QM1"; // Queue manager name
private static final String APP_USER = "APP"; // User name that application uses to connect to MQ
private static final String APP_PASSWORD = "_APP_PASSWORD_"; // Password that the application uses to connect to MQ
private static final String QUEUE_NAME = "DEV.QUEUE.1"; // Queue that the application uses to put and get messages to and from

Let’s compile and run this application as we did (successfully) in the MQ with JMS tutorial.

To compile the application, go back to the MQClient directory.

cd /mnt/mqm/MQClient/

Use javac to compile your application.

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

To confirm that the sample is compiled we can check the output in the jms folder:

ls com/ibm/mq/samples/jms/

You should now see a .class file accompanying the .java file.

JmsPutGet.class  JmsPutGet.java

Run your application:

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

Oh dear! The connection is obviously rejected by the queue manager, and we get a lot of errors. Looking at the errors you’ll be able to see that among them there was a Remote CipherSpec error for channel ‘DEV.APP.SVRCONN’, as we didn’t specify an encryption cipher on the client side.

Let’s now try again, setting up TLS on the client side properly. First we need to edit our JMSPutGet.java file again, in order to specify the CipherSuite we wish to use. This needs to be the equivalent of the CipherSpec specified on the channel, which we saw in the channel definition in Section 4. SSL and TLS security protocols in IBM MQ lists the CipherSpecs supported by IBM MQ and their equivalent CipherSuites supported by both the IBM Java Runtime Environment (JRE) supplied with IBM MQ (used in this tutorial) and the Oracle Java JRE. The TLS_RSA_WITH_AES_128_CBC_SHA256 CipherSpec maps to the SSL_RSA_WITH_AES_128_CBC_SHA256 CipherSuite, and we can specify this in our application by adding the property to the other connection factory settings.

cd com/ibm/mq/samples/jms
vi JmsPutGet.java

The JMS code to add:

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

After using vi to add the WMQ_SSL_CIPHER_SUITE property to the JMSPutGet.java file, we need to recompile the application code (from the /mnt/mqm/MQClient directory):

cd /mnt/mqm/MQClient
javac -cp ./libs/com.ibm.mq.allclient-9.0.4.0.jar:./libs/javax.jms-api-2.0.1.jar com/ibm/mq/samples/jms/JmsPutGet.java

The final part of the TLS set up on the client side is to provide the location, type and password of the client trustStore, where the server’s public key is kept. These can be given as command line options at runtime. If these were omitted, the connection would be rejected and we would see errors including SSL handshake failed.

Run your application with the client trustStore information options:

java -Djavax.net.ssl.trustStoreType=pkcs12 -Djavax.net.ssl.trustStore=./certs/client_key.p12 -Djavax.net.ssl.trustStorePassword=tru5tpassw0rd -cp ./libs/com.ibm.mq.allclient-9.0.4.0.jar:./libs/javax.jms-api-2.0.1.jar:. com.ibm.mq.samples.jms.JmsPutGet

Success! You will see a similar output to:

Sent message:

  JMSMessage class: jms_text
  JMSType:          null
  JMSDeliveryMode:  2
  JMSDeliveryDelay: 0
  JMSDeliveryTime:  1524586597891
  JMSExpiration:    0
  JMSPriority:      4
  JMSMessageID:     ID:414d5120514d312020202020202020204252df5a02a64b23
  JMSTimestamp:     1524586597891
  JMSCorrelationID: null
  JMSDestination:   queue:///DEV.QUEUE.1
  JMSReplyTo:       null
  JMSRedelivered:   false
    JMSXAppID: JmsPutGet (JMS)
    JMSXDeliveryCount: 0
    JMSXUserID: app
    JMS_IBM_PutApplType: 28
    JMS_IBM_PutDate: 20180424
    JMS_IBM_PutTime: 16163796
Your lucky number today is 859

Received message:
Your lucky number today is 859
SUCCESS
        

Congratulations! You configured basic TLS for MQ so the queue manager and the client application can encrypt their communication as it flows over the internet. You used Docker to create a self signed digital certificate for the queue manager, started a queue manager in another container, and copied the certificate with the queue manager’s public key to the client application’s environment.
This enabled the client and the server to negotiate a shared secret/secret key with which their session is then encrypted.
You have an understanding of the basics of what needs to be configured both on the MQ server and the client application sides, to switch TLS on for MQ. Although this initial set up is done with a self-signed certificate for the server (and not a certificate issued by a Certificate Authority), and the authentication of the client is carried out by use of the username and password rather than with client digital certificate, you have nonetheless enabled the client and the server to communicate privately and securely.