By default Tomcat makes use of the cacerts file provided with the Java runtime environment located under: C:\Program Files\Java\jre6\lib\security.
Since the JRE installer likes to remove this file when it is updated or uninstalled I needed to find a way to re-configure Tomcat so it uses an different keystore.
System Setup
- Start without Java or Tomcat installed.
- Install JRE (this is the x64 version): http://download.oracle.com/otn-pub/java/jdk/6u30-b12/jre-6u30-windows-x64.exe
- Create JAVA_HOME system wide variable: setx.exe JAVA_HOME "C:\Program Files\Java\jre6" /M
- Install Tomcat: http://archive.apache.org/dist/tomcat/tomcat-6/v6.0.29/bin/apache-tomcat-6.0.29.exe
- Select all defaults for the Tomcat Normal installation.
- Get Portecle to create new and view contents of JKS keystores: http://cdnetworks-us-2.dl.sourceforge.net/project/portecle/portecle/1.7/portecle-1.7.zip
Testing
- First we’ll create a test Java webapp that makes an HTTPS connection.
- We’ll create a test JSP page (source code at the bottom) using the URLConnection class in Java to connect to https://www.paypal.com/ and fetch its page content.
- If the connection is successful the page content is displayed below the “We are connected to HTTPS URL?” message.
- If the connection is not successful the exception error message will be displayed.
- The test JSP shows we can connect to https://www.paypal.com/ successfully and retrieve it’s content:
Why Does This Work?
In order for the URLConnection object to successfully make a connection the certificate from Paypal’s certificate signer must be pre-installed in the JRE cacerts JKS keystore. Let’s make sure…
Paypal’s certificate is signed by a VeriSign certificate.
The SHA1 thumbprint of the VeriSign cert is: 4E:B6:D5:78:49:9B:1C:CF:5F:58:1E:AD:56:BE:3D:9B:67:44:A5:E5
We have this VeriSign certificate pre-installed in the JRE cacerts JKS keystore:
More Testing…
First lets verify the test JSP works by removing all certificates from the JRE cacerts keystore and re-trying the test webapp.
We’ll use some PowerShell to remove all certificates from the JRE cacerts JKS keystore.
Resultant empty JRE cacerts keystore:
For good measure lets restart Tomcat to make sure our changes take effect with a PowerShell one liner: restart-service tomcat*
After restarting Tomcat the HTTPS connection is now broken!
As a sanity check we’ll install only the VeriSign certificate in the JRE cacerts keystore all by itself:
Now the HTTPS connection works again!
Changing the Tomcat Keystore
Because JRE 6 removes it’s cacerts file when it is uninstalled or upgraded I wanted to re-configure Tomcat to use a different keystore so that my certificates being used for HTTPS endpoints wouldn't be deleted. Figuring out how turned out to be quite an adventure.
First I checked the Tomcat documentation for server.xml and found this:
keystoreFile | The pathname of the keystore file where you have stored the server certificate to be loaded. By default, the pathname is the file ".keystore" in the operating system home directory of the user that is running Tomcat. If your keystoreType doesn't need a file use "" (empty string) for this parameter. |
truststoreFile | The trust store file to use to validate client certificates. The default is the value of thejavax.net.ssl.trustStore system property. If neither this attribute nor the default system property is set, no trust store will be configured. |
The keystoreFile setting is for Tomcat’s private key for HTTPS connectors and the truststoreFile setting is for client authentication but hey lets try both!
Below is a connector configured to point to a single JKS keystore with the VeriSign certificate inside. Here is the server.xml connector for port 8080.
… And what is inside of C:\mykeystore.jks:
After restarting Tomcat things aren't looking good:
The solution
It turns out a couple of Java JVM parameters are needed to point Tomcat to a different keystore.
These can be set via the Tomcat configuration utility: "C:\Program Files\Apache Software Foundation\Tomcat 6.0\bin\tomcat6w.exe" //ES//Tomcat6
This tool stores values in the registry: HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Apache Software Foundation\Procrun 2.0\Tomcat6\Parameters\Java\Options
The Tomcat connector was reverted back to the default:
After restarting Tomcat again… The final result: Success! Tomcat is now using C:\mykeystore.jks instead of the JRE cacerts keystore.
Great article - I am just starting to learn about the certificate stores. So WHO preinstalled the Paypal's certificate? I tried your example and used a different https site, and I didn't have any issue?
ReplyDeleteI'm glad you like it! The cacerts file is pre-populated with public key certificates of supposedly trusted root certificate authorities. When you update JRE you get an updated cacerts file with new CA certificates. Sometimes CA certificates get revoked as well if they become compromised. Firefox distributes their browser with it's own CA certificate database cert8.db. Most other things use the Windows certificate store which gets updated from Microsoft update.
ReplyDeleteHm, still something not right. In after setting it with Tomcat7 I'm getting: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
ReplyDeleteAny clue what might be wrong?
This is a very reckless approach to a medium complexity issue. The correct course of action should have been to use certificate chaining of your ca Root validated private key to a Root CA certificate established in the TrustStore.
ReplyDeleteChaining:
[A trust B]
[B trust C]
[C trust D]
Since A (a Root CA in my Trust Store) is trusted, if i establish a proper chain in my keystore, then D (my Private Certificate) is also inherently Trusted
The approach being taking short-circuits the chaining and intermingles public and private keys.
Currently with the more recent security hardenings this approach will also have difficulty due to the fact that SHA1 certificates can;t reference e CA root CERTS for what I assume is this common anti-pattern
I have seen this approach popularized by those using non ca root validated self signed certificates , which are no longer supported in Java BTW, as a way to bypass security essentials