Getting a Java program to use a TLS MQ channel, and use an external USB keystore.

This project started off trying to get a java program to use an HSM (external keystore on a USB). This was not well documented, so I have documented my path.

I had set up my keystore on the USB HSM device as described here. This post covers the MQ side of it.

MQ provides some JMS samples.

I could use JmsBrowser using local bindings with the jms.sh script.

. /opt/mqm/java/bin/setjmsenv64
java -Djava.library.path=/opt/mqm/java/lib64 JmsBrowser -m QMA -d CP0000

To use a client channel (QMACLIENT) was easy, the program is configured to allow a channel name to be passed as a parameter using the -l option.

. /opt/mqm/java/bin/setjmsenv64
java -Djava.library.path=/opt/mqm/java/lib64 JmsBrowser -m QMA -d CP0000 -h localhost -p 1414 -l QMACLIENT

To use a TLS channel (QMAQCLIENTTLS) proved harder

. /opt/mqm/java/bin/setjmsenv64
java -Djava.library.path=/opt/mqm/java/lib64 JmsBrowser -m QMA -d CP0000 -h localhost -p 1414 -l QMAQCLIENTTLS

This gave me

CC=2;RC=2397;AMQ9641: Remote CipherSpec error for channel ‘QMAQCLIENTTLS’ to host ”. [3=QMAQCLIENTTLS]

It was hard to find where to specify the CipherSpec. The documentation said “Using the environment variables MQCHLLIB to specify the directory where the table is located, and MQCHLTAB to specify the file name of the table.” I tried this and got the same error.

I collected a trace using the Java option -Dcom.ibm.msg.client.commonservices.trace.status=ON , but the trace showed the cipher spec was null.

I gave up with this program and used an enhanced version of the program. JMS programs can use JNDI as a file repository of configuration information and the program can extract the information. For example you can configure Connection Factories(cf) which are MQ connections, and Q objects – which are queues.

Use JNDI to store parameters

JMS programs can use a JNDI interface to access configuration parameters stored in an external file.
I used this article to get me started.

My JMSAdmin.config had

INITIAL_CONTEXT_FACTORY=com.sun.jndi.fscontext.RefFSContextFactory
PROVIDER_URL=file:///home/colinpaice/mq/JNDI-Directory
SECURITY_AUTHENTICATION=none

I used the following commands to define a connection factory called CF1, using the channel, QMACLIENT, and a queue reference called MYQUEUE pointing to queue CP0000.

mkdir JNDI-Directory
/opt/mqm/java/bin/JMSAdmin -v -cfg JMSAdmin.config
define cf(CF1) transport(client) channel(QMACLIENT)
define q(MYQUEUE) queue(CP0000)
end

I used a bash script to run the program, using the JmsJndiBrowser sample (JmsBrowser with JNDI support).

. /opt/mqm/java/bin/setjmsenv64
java -Djava.library.path=/opt/mqm/java/lib64 JmsJndiBrowser -i file:///home/colinpaice/mq/JNDI-Directory -c CF1 -d MYQUEUE

This failed with

JMSCMQ0001: IBM MQ call failed with compcode ‘2’ (‘MQCC_FAILED’) reason ‘2400’ (‘MQRC_UNSUPPORTED_CIPHER_SUITE’).

Which was a surprise to me. I looked in the trace, and there was nothing obviously wrong. The channel was not configured for TLS.

I used the following command in the JMS Admin tool, to specify a TLS channel and configure the cipher suite

alter cf(CF1) channel(QMAQCLIENTTLS) SSLCIPHERSUITE(ANY_TLS12)

Note. If I displayed cf(CF1) it gave me SSLCIPHERSUITE(*TLS12).

This gave me unable to find valid certification path to requested target, which was good progress because it meant the cipher spec had been accepted.

I added in the Java parameters to identify the key store and trust store

. /opt/mqm/java/bin/setjmsenv64

kss=”-Djavax.net.ssl.keyStore=/home/colinpaice/ssl/ssl2/ecec.p12″
ksp=”-Djavax.net.ssl.keyStorePassword=password”
kst=”-Djavax.net.ssl.keyStoreType=pkcs12″
tss=”-Djavax.net.ssl.trustStore=/home/colinpaice/ssl/ssl2/dantrust.p12″
tsp=”-Djavax.net.ssl.trustStorePassword=password”
tst=”-Djavax.net.ssl.trustStoreType=pkcs12″
ks=”$kss $ksp $kst”
ts=”$tss $tsp $tst”

java $ks $ts -Djava.library.path=/opt/mqm/java/lib64 JmsJndiBrowser -i file:///home/colinpaice/mq/JNDI-Directory -c CF1 -d MYQUEUE

and it worked successfully.

Use the pkcs11 HSM external keystore.

I changed the file to specify the external keystore (with format pkcs11) on a USB device rather than a file on disk (format pkcs12).

kss=”-Djavax.net.ssl.keyStore=NONE”
kst=”-Djavax.net.ssl.keyStoreType=pkcs11″
ksp=”-Djavax.net.ssl.keyStorePassword=12345678″

but this gave

find /java.security.KeyStoreException: pkcs11 not found
java.security.NoSuchAlgorithmException: pkcs11 KeyStore not available

You have to tell Java where to find the pkcs11 code. This is configured in the java.security file. You can find it using find $JAVA_HOME/ -name java.security . This gave me the file name /usr/lib/jvm/java-8-oracle/jre/lib/security/java.security

It had the list of security providers,

security.provider.1=sun.security.provider.Sun
security.provider.2=sun.security.rsa.SunRsaSign
security.provider.3=sun.security.ec.SunEC
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
security.provider.5=com.sun.crypto.provider.SunJCE
security.provider.6=sun.security.jgss.SunProvider
security.provider.7=com.sun.security.sasl.Provider
security.provider.8=org.jcp.xml.dsig.internal.dom.XMLDSigRI
security.provider.9=sun.security.smartcardio.SunPCSC

and is missing an entry for pkcs11.

You can edit this file, or override it. To override it, create a file like colin.java.properties with

security.provider.10=SunPKCS11 /home/colinpaice/mq/nitrokey.cfg

where

  • .10 is the next in sequence after the .9 in the java.security file
  • SunPKCS11 says use the Sun module.
  • /home/colinpaice/mq/nitrokey.cfg use this configuration file for the pkcs11 device.

Tell Java to use this override file, by using the Java option -Djava.security.properties=/home/colinpaice/mq/colin.java.properties.

The configuration file name for the pkcs11 is /home/colinpaice/mq/nitrokey.cfg with content

name = nitrokey
library = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
slot=0

With these parameters the output from the JmsJndiBrowser command was

Initial context found!
Browse starts
No more messages
SUCCESS

Success – and it only took me half a day! The hardest part was known what to specify for the security.provider…… parameter.

4 thoughts on “Getting a Java program to use a TLS MQ channel, and use an external USB keystore.

  1. wrt your failure to use JmsBrowser with a CCDT (that you gave up on). Remember that if you are planning to use a CCDT, you must not over-ride things like the channel name in the program. Remove all the flags that specify channel attributes, -h -p and -l.

    Like

    1. Good points – how do get JMSBrowser to use a CCDT? Specify the environment variables and remove the other options?
      I dont think I can specify the SSLCryptoHardware in the CCDT, do I have to use environment variables for this?

      Like

      1. Looking at the source of JMSBrowser, I can’t see how you can use it with a CCDT. Java clients do not pay attention to the MQCHLLIB/TAB environment variables not the mqclient.ini equivalents, and the provision in JMS to supply a CCDT is not used by this sample. Also, if you do not specific the channel name and/or hostname details it hard codes them in the program so there is no way to not specify them. I guess that is why JmsJndiBrowser exists! It is the sample I always use because setting it up with a CCDT is nice and easy!

        Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s