Ssl – Getting JMX working under Tomcat 7 with SSL and a self-signed cert

javajmxssltomcat

I'm trying to get JMX working under Tomcat 7.0.23 with SSL. The servers are located in AWS, which means all the hosts are NATed, and I need to use JmxRemoteLifecycleListener to explicitly set the two ports used by JMX. I've been doing a lot of reading on the subject but I just can't get all the pieces working together properly.

I can get JMX working fine without SSL. I have downloaded the version of catalina-jmx-remote.jar for my version of Tomcat and installed it in my tomcat/lib directory. My server.xml contains:

  <Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener" 
        rmiRegistryPortPlatform="1099" rmiServerPortPlatform="1098" />

When I launch Tomcat with the following settings I can connect with an insecure session:

-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.password 
-Dcom.sun.management.jmxremote.access.file=/path/to/jmxremote.access 
-Djava.rmi.server.hostname=<public IP of server> 
-Dcom.sun.management.jmxremote.ssl=false

However if I change these to the following then I'm unable to establish an SSL connection:

-Dcom.sun.management.jmxremote 
-Dcom.sun.management.jmxremote.password.file=/path/to/jmxremote.password 
-Dcom.sun.management.jmxremote.access.file=/path/to/jmxremote.access 
-Djava.rmi.server.hostname=<public IP of server> 
-Dcom.sun.management.jmxremote.ssl=true
-Dcom.sun.management.jmxremote.registry.ssl=true
-Dcom.sun.management.jmxremote.ssl.need.client.auth=false 
-Dcom.sun.management.jmxremote.authenticate=true
-Djavax.net.ssl.keyStore=/path/to/keystore.dat 
-Djavax.net.ssl.keyStorePassword=<password>
-Djavax.net.ssl.trustStore=/path/to/truststore.dat 
-Djavax.net.ssl.trustStorePassword=<password>

keystore.dat contains just a single certificate created via:

openssl x509 -outform der -in cert.pem -out cert.der
keytool -import -alias tomcat -keystore keystore.dat -file cert.der -storepass <password>

truststore.dat contains a full copy of the java cacerts plus the CA cert for my self-signed cert:

cp $JAVA_HOME/jre/lib/security/cacerts truststore.dat
keytool -storepasswd -storepass changeit -new <password> -keystore truststore.dat
keytool -import -trustcacerts -file mycacert.pem -alias myalias -keystore truststore.dat -storepass <password>

After launching Tomcat I've tried connecting via jconsole but it can't establish a connection. I tried to verify SSL using openssl but it looks like Tomcat isn't making use of the cert:

$ openssl s_client -connect <host>:1099
CONNECTED(00000003)
140735160957372:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:s23_lib.c:177:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 322 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
---

I've verified that my local keystore and truststore are set up properly by exporting the keys and verifying the cert chain (combined.pem is all the CA certs from truststore.dat and cert.pem is my cert from keystore.dat):

$ openssl verify -verbose -purpose sslserver -CAfile combined.pem cert.pem
cert.pem: OK

So now I'm at a complete loss. The cert and CA cert look correct. Unencrypted JMX connections work. But I can't seem to get the connection to use SSL. What am I missing here?

I don't know if this is just a red herring or not, but I don't see any way to specify what cert in the keyStore is used by JMX. Some of what I read implies that it just uses a cert with the alias "tomcat". Is that correct?

Best Answer

It took me a while to figure this out but I eventually got it. The key is how the keystore & truststore files are set up when creating self-signed certs. Here's a bash script I wrote to make it easy to remember:

DNAME="CN=foo JMX, OU=prodops, O=foo.com, L=Somewhere, S=XX, C=US"
DAYS=3650
PASSWORD=<password>
CACERTS="/path/to/java/jre/lib/security/cacerts"

rm -f jconsole* tomcat*

# First, create the keystore and truststore for the application, tomcat in this case.  Use $CACERTS as the basis for the new keystore & truststore so that all public CA's remain intact:

keytool -genkey -alias tomcat -keyalg RSA -validity ${DAYS} -keystore tomcat.keystore -storepass ${PASSWORD} -keypass ${PASSWORD} -dname "${DNAME}"
cp ${CACERTS} tomcat.truststore
keytool -storepasswd -keystore tomcat.truststore -storepass changeit -new ${PASSWORD}
keytool -genkey -alias tomcat -keyalg RSA -validity ${DAYS} -keystore tomcat.truststore -storepass ${PASSWORD} -keypass ${PASSWORD} -dname "${DNAME}"

# And do the same for the JMX client, jconsole in this case:

keytool -genkey -alias jconsole -keyalg RSA -validity ${DAYS} -keystore jconsole.keystore -storepass ${PASSWORD} -keypass ${PASSWORD} -dname "${DNAME}"
cp ${CACERTS} jconsole.truststore
keytool -storepasswd -keystore jconsole.truststore -storepass changeit -new ${PASSWORD}
keytool -genkey -alias jconsole -keyalg RSA -validity ${DAYS} -keystore jconsole.truststore -storepass ${PASSWORD} -keypass ${PASSWORD} -dname "${DNAME}"

# Then, export the public certificates from the keystores:

keytool -export -alias tomcat -keystore tomcat.keystore -file tomcat.cer -storepass ${PASSWORD}
keytool -export -alias jconsole -keystore jconsole.keystore -file jconsole.cer -storepass ${PASSWORD}

# Finally, import the certificates into the truststores. Again, this allows the application (tomcat) to trust the client (jconsole), and vice-versa:

keytool -import -alias jconsole -file jconsole.cer -keystore tomcat.truststore -storepass ${PASSWORD} -noprompt
keytool -import -alias tomcat -file tomcat.cer -keystore jconsole.truststore -storepass ${PASSWORD} -noprompt

rm -f *.cer