Time for another IBM MQ example. This time it is for connecting to IBM MQ with a Java client over SSL. I’m going to use self-signed certificates in this example to eliminate any certificate chain problems. The source code for the Java client can be found below. Time to start creating the user to use for this
A. Create a new user that the client can use to connect with. Let’s call him “bob”
* Bob needs to be created on OS level since this is what IBM MQ uses to authorise users
* Bob should NOT be a member of the mqm group since it would make him a privileged user (and privileged users are blocked by default)
* Bob does not need login privileges on OS level (/sbin/nologin)
B. Now we need to grant this user some basic privileges
I am here going to use the setmqaut program but you can also use MQExplorer if you like a GUI more
setmqaut -m MYQM01 -t qmgr -p bob -all +connect +inq setmqaut -m MYQM01 -t queue -n MYQUEUE -p bob -all +put +get
Connect and inquiry for the queue manager and get and put on our example queue.
C. Time to create the client certificate and place it into a jks
For this I am going to use the ikeycmd program shipped with MQ (<mqserver installation dir>/java/jre64/jre/bin/ikeycmd)
Create client database
ikeycmd -keydb -create -db "client.jks" -pw clientpass -type jks
Create client certificate
ikeycmd -cert -create -db "client.jks" -pw clientpass -label ibmwebspheremqbob -dn "CN=bob,OU=bob,O=Bobs Company,C=SE" -expire 365
One thing to note here is the label for the client certificate. This has to be in the form of: ‘ibmwebspheremq’ + ‘client username’ (all lower case)
In our case ‘ibmwebspheremqbob’ since our user is called Bob
To check that the certificate is in the jks
ikeycmd -cert -list -db "client.jks" -pw clientpass
This will give you the label of the certificates in the jks. In our case: ‘ibmwebpsheremqbob’
D. Let us create the server certificate now
Create server database
ikeycmd -keydb -create -db "/var/mqm/qmgrs/MYQM01/ssl/MYQM01.kdb" -pw serverpass -type cms -expire 1000 -stash
Create server certificate
ikeycmd -cert -create -db "/var/mqm/qmgrs/MYQM01/ssl/MYQM01.kdb" -pw serverpass -label ibmwebspheremqmyqm01 -dn "CN=MYQM01,OU=ICC,O=GU,C=SE" -expire 1000 -sig_alg SHA256_WITH_RSA
A word about the certificate label here. Default name for this certificate is in the form of: ‘ibmwebspheremq’ + ‘queue manager name’ (all lower case)
In our case this becomes: ‘ibmwebpsheremqmyqm01’
If you need to change the label (or want to be able to present different certificates on different channels) you can set the CERTLABL property on the queue manager (or channel). The CERTLABL label should then correspond to the label in the .kbd file
To check that the certificate is in the kbd
ikeycmd -cert -list -db "/var/mqm/qmgrs/MYQM01/ssl/MYQM01.kdb" -pw serverpass
This will give you the label of the certificates in the kbd. In our case: ‘ibmwebpsheremqmyqm01’
E. Time for the certificate exchange between client and server
Extract the public part of the client certificate
ikeycmd -cert -extract -db "client.jks" -pw clientpass -label ibmwebspheremqbob -target client.crt -format ascii
Add the client cert to the server
ikeycmd -cert -add -db "/var/mqm/qmgrs/MYQM01/ssl/MYQM01.kdb" -pw serverpass -label ibmwebspheremqbob -file client.crt -format ascii
Check so that both certificates are in the kbd
ikeycmd -cert -list -db "/var/mqm/qmgrs/MYQM01/ssl/MYQM01.kdb" -pw serverpass
This should yield the result:
ibmwebspheremqbob ibmwebspheremqmyqm01
F. and now from the server to the client
Extract the public part of the server certificate
ikeycmd -cert -extract -db "/var/mqm/qmgrs/MYQM01/ssl/MYQM01.kdb" -pw serverpass -label ibmwebspheremqmyqm01 -target MYQM01.crt -format ascii
Add the server certificate to the client jks
ikeycmd -cert -add -db "client.jks" -pw clientpass -label ibmwebspheremqmyqm01 -file MYQM01.crt -format ascii
Check so that both certificates are in the jks
ikeycmd -cert -list -db "client.jks" -pw clientpass
This should now yield the result:
ibmwebspheremqmyqm01 ibmwebspheremqbob
Certificates are now done. Time to setup the MQ objects. For this I’m going to use the runmqsc program, eq. runmqsc MYQM01
G. Queue manager needs to know where the kdb file is
ALTER QMGR SSLKEYR('/var/mqm/qmgrs/MYQM01/ssl/MYQM01')
NOTE: We omit the ‘.kbd’ when setting the path in the queue manager
Another thing we are going to do is to set FIPS to false in this example
ALTER QMGR SSLFIPS(NO)
H. There is also need for a server connection channel for the client to connect to
DEFINE CHANNEL('CLIENTS') CHLTYPE(SVRCONN) TRPTYPE(TCP) SSLCIPH(TLS_RSA_WITH_AES_128_CBC_SHA256) MCAUSER('bob') SSLCAUTH(REQUIRED) REPLACE
A few words on SSLCIPH and SSLCAUTH. SSLCIPH is the encryption cipher to use. This cipher has to exist both on the MQ server and in the Java installation that the client runs on.
SSLCAUTH(REQUIRED) tells MQ that the client also should present a certificate when connecting, so called two-way certificate exchange.
I. When all MQ objects have been created/changed we need to refresh the SSL cache in MQ
REFRESH SECURITY TYPE(SSL)
J. Time to test our setup with a little bit of java code
——————————————–
JAVA EXAMPLE CLIENT CODE
——————————————–
import java.io.IOException; import java.util.Hashtable; import com.ibm.mq.*; import com.ibm.mq.constants.MQConstants; public class MQClients { static private String CHANNEL = "CLIENTS"; static private int PORT = 1414; static private String HOST = "mymqhost.se"; static private String QMANAGER = "MYQM01"; static private String QUEUE = "MYQUEUE"; static private String USER = "bob"; static private Hashtable<String, Object> props = new Hashtable<String, Object>(); static MQQueueManager qMgr = null; static private void putMsgOnQueue(String message) { // Disabling IBM cipher suite mapping due to // using Oracle Java and not IBM Java System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false"); // Enabling SSL debug to view the communication //System.setProperty("javax.net.debug", "ssl:handshake"); System.setProperty("javax.net.ssl.trustStore","client.jks"); System.setProperty("javax.net.ssl.trustStorePassword","clientpass"); System.setProperty("javax.net.ssl.keyStore","client.jks"); System.setProperty("javax.net.ssl.keyStorePassword","clientpass"); props.put(MQConstants.CHANNEL_PROPERTY, CHANNEL); props.put(MQConstants.PORT_PROPERTY, PORT); props.put(MQConstants.HOST_NAME_PROPERTY, HOST); props.put(MQConstants.USER_ID_PROPERTY, USER); props.put(MQConstants.PASSWORD_PROPERTY, "secret"); // Bobs OS password props.put(MQConstants.SSL_CIPHER_SUITE_PROPERTY, "TLS_RSA_WITH_AES_128_CBC_SHA256"); try { qMgr = new MQQueueManager(QMANAGER, props); // MQOO_OUTPUT = Open the queue to put messages // MQOO_INPUT_AS_Q_DEF = Using queue-defined defaults int openOptions = MQConstants.MQOO_OUTPUT; // creating destination MQQueue queue = qMgr.accessQueue(QUEUE, openOptions); // specify the message options... MQPutMessageOptions pmo = new MQPutMessageOptions(); // Default // MQPMO_ASYNC_RESPONSE = MQPUT operation is completed without the // application waiting for the queue manager to complete the call // Using this option can improve messaging performance, // particularly for applications using client bindings. pmo.options = MQConstants.MQPMO_ASYNC_RESPONSE; // create message MQMessage mqMessage = new MQMessage(); System.out.println("Writing message to queue: " + QUEUE); mqMessage.writeString(message); // Put message on queue queue.put(mqMessage, pmo); // Close queue queue.close(); // Get status MQAsyncStatus asyncStatus = qMgr.getAsyncStatus(); // Print status code (0 = successful) System.out.println(asyncStatus.reasonCode); } catch (MQException e) { System.out.println("The connection to MQ could not be established." + e.getMessage()); } catch (IOException e) { System.out.println("Error while writing message." + e.getMessage()); } finally { try { qMgr.disconnect(); } catch (MQException e) { System.out.println("The connection could not be closed." + e.getMessage()); } } } static private void getMsgsFromQueue(){ // Disabling IBM cipher suite mapping due to // using Oracle Java and not IBM Java System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false"); // Enabling SSL debug to view the communication //System.setProperty("javax.net.debug", "ssl:handshake"); System.setProperty("javax.net.ssl.trustStore","client.jks"); System.setProperty("javax.net.ssl.trustStorePassword","clientpass"); System.setProperty("javax.net.ssl.keyStore","client.jks"); System.setProperty("javax.net.ssl.keyStorePassword","clientpass"); props.put(MQConstants.CHANNEL_PROPERTY, CHANNEL); props.put(MQConstants.PORT_PROPERTY, PORT); props.put(MQConstants.HOST_NAME_PROPERTY, HOST); props.put(MQConstants.USER_ID_PROPERTY, USER); props.put(MQConstants.PASSWORD_PROPERTY, "secret"); // Bobs OS password props.put(MQConstants.SSL_CIPHER_SUITE_PROPERTY, "TLS_RSA_WITH_AES_128_CBC_SHA256"); try { qMgr = new MQQueueManager(QMANAGER, props); // MQOO_INPUT_SHARED = Open the queue to read messages int openOptions = MQConstants.MQOO_INPUT_SHARED; // creating destination MQQueue queue = qMgr.accessQueue(QUEUE, openOptions); // specify the message options... MQGetMessageOptions gmo = new MQGetMessageOptions(); // Default gmo.options = MQConstants.MQPMO_ASYNC_RESPONSE; // create message MQMessage mqMessage = new MQMessage(); // Get message from queue System.out.println("Fetching message from queue: " + QUEUE); queue.get(mqMessage, gmo); // Get a message from MQMessage String message = mqMessage.readStringOfByteLength(mqMessage.getMessageLength()); // Display message System.out.println(message); // Close queue queue.close(); // Get status MQAsyncStatus asyncStatus = qMgr.getAsyncStatus(); // Print status code (0 = successful) System.out.println(asyncStatus.reasonCode); } catch (MQException e) { System.out.println("The connection to MQ could not be established." + e.getMessage()); } catch (IOException e) { System.out.println("Error while fetching the message." + e.getMessage()); } finally { try { qMgr.disconnect(); } catch (MQException e) { System.out.println("The connection could not be closed." + e.getMessage()); } } } public static void main(String[] args) { putMsgOnQueue("world "); getMsgsFromQueue(); } }
NOTE: The SSL_CIPHER_SUITE_PROPERTY in the client must be the same cipher that is defined on the channel SSLCIPH property.
A few troubleshooting tips
* IBM MQ and Java does not always agree on naming so the cipher you choose in MQ might have another name in Oracle/Sun/IBM Java - be sure to check the cipher name translation table on IBM site before giving up * The error logs for the queue manager can be found in /var/mqm/qmanagers/MYQM01/errors * Make sure the GSKit is installed in your MQ installation. If not you are in the risk of getting strange errors like "MQRC_UNSUPPORTED_CIPHER_SUITE" even though they are correct * To enable SSL verbose logging uncomment the line System.setProperty("javax.net.debug", "ssl:handshake") in the example code above
Tested on IBM MQ 9.0.5.0 on Red Hat 7, client using Java version 1.8.0_141 and IBM com.ibm.mq.allclients v9.0.4.0 on OSX 10.13.6