Tag Archives: java - Page 8

Setup IBM MQ v9 for Java clients over SSL

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

How to check a certificate chain in a jks

This can be done in a number of ways. One common way is to simply but the jks in its environment and see if it works. This is however a little late for my taste. I like to check it BEFORE i put it into a running environment. I will here show 2 ways to check a certificate chain:

  1. Manually check the cert using keytool
  2. Check the chain using openSSL

1. Lets start with the manual check:

keytool -list -v -keystore my.certificate.chain.jks | grep -A 1 "Owner"

This command will list all certifications (and keys) Owner (CN) and Issuer (CN) something like this:

  1. Owner: CN=app.tankmin.se, OU=Secure Link SSL, OU=Tankmin…
    Issuer: CN=Network Solutions OV Server CA 2, O=Network Solutions L.L.C….
  2. Owner: CN=Network Solutions OV Server CA 2, O=Network Solutions L.L.C….
    Issuer: CN=USERTrust RSA Certification Authority, O=The USERTRUST Network…
  3. Owner: CN=USERTrust RSA Certification Authority, O=The USERTRUST Network…
    Issuer: CN=AddTrust External CA Root, OU=AddTrust External TTP Network…
  4. Owner: CN=AddTrust External CA Root, OU=AddTrust External TTP Network…
    Issuer: CN=AddTrust External CA Root, OU=AddTrust External TTP Network…

I have here rearrange them with my application certificate at the top and the root CA certification at the bottom. I have also given every entry a number to easier be able to explain the logic behind the chain.

From here it is pretty simple to see that:

  1. Issuer of my application certificate (no. 1) is Network Solutions OV Server CA 2 and
  2. Issuer of that certificate (no. 2) is USERTrust RSA Certification Authority, and
  3. Issuer of that certificate (no. 3) is AddTrust External CA Root which is the root CA (no. 4)

Since all certificates are linked together down to the root CA the chain is complete. Note: The root CA certificate will always be self-signed

2. And now, if you do not want to do all the above you can use openSSL to verify your application certificate with the following command:

openssl verify -CAfile root.pem -untrusted intermediate.pem application.pem
-CAFile is the root certificate
-untrusted is the intermidiate (if any) certificates
application.pem is your application certificate

The openSSL command above will check the chain to your application certificate and give you a:

application.pem: OK

if all is good

Tested on OpenSSL 1.0.2o 27 Mar 2018 and Java 1.8.0_121

My HTMLEncode function in Java

Every now and then I have to work in an environment where imports of frameworks is prohibited and it in times like that that I had to create my own HTMLEncode function. Here is the result:

public static String HTMLEncode(String inputString) {
 
		// Check if string contains ANY special characters (<>"&)
		if(inputString.indexOf("<") != -1 || 
                   inputString.indexOf(">") != -1 || 
                   inputString.indexOf("\"") != -1 ||
                   inputString.indexOf("&") != -1) {
 
			char c;
			StringBuffer out = new StringBuffer();
			for(int i=0; i < inputString.length(); i++) {
 
			    c = inputString.charAt(i);
 
			    if(c=='"' || c=='<' || c=='>') {
			       out.append("&#"+(int)c+";");
			    }
			    else if(c == '&'){
                                // Is &-sign preceding an HTML entity?
			    	if(inputString.indexOf("&amp;", i) == i || 
			    		inputString.indexOf("& #38;", i) == i ||
		    			inputString.indexOf("& lt;", i)  == i || 
		    			inputString.indexOf("& #60;", i) == i ||
		    			inputString.indexOf("& gt;", i)  == i ||
		    			inputString.indexOf("& #62;", i) == i ||
		    			inputString.indexOf("& quot;", i)== i ||
		    			inputString.indexOf("& #34;", i) == i ){
 
			    		out.append(c);
 
			    	}
			    	else {
			    		out.append("&#"+(int)c+";");
			    	}
			    }
			    else {
			        out.append(c);
			    }		
			}			
			return out.toString();
		}
		else {
			return inputString;			
		}				
	}

NOTE! The spaces inside the strings on row 21 thru 27 are only there for displaying purposes. In real code these spaces should be removed