Ok - after digging a lot more, I finally got this working. Much thanks to @Dave G and this tutorial: Configuring two-way SSL authentication on Tomcat from which most of these instructions are paraphrased.
Generally, the steps to get mutual authentication functional are as follows:
- Create a certificate for the tomcat server. The client has to trust this certificate.
- Create a keystore for the tomcat server, and import the server certificate into it.
- Create a certificate for the client. The server has to trust this certificate.
- Import the client certificate into the server keystore
- Update the tomcat server.xml file with the correct Connector XML.
The above steps are necessary on the server. Once completed, to set up the client, do the following:
- Copy the client certificate from the server to the client.
- Use the client certificate when communicating with the server (this process varies with the nature of the client application).
For the certificate configuration, I executed the following on the server machine:
# For the following commands, set the values in parenthesis to be whatever makes sense for your environment. The parenthesis are not necessary for the command.
# This is an all-in-one command that generates a certificate for the server and places it in a keystore file, while setting both the certifcate password and the keystore password.
# The net result is a file called "tomcat.keystore".
keytool -genkeypair -alias (serveralias) -keyalg RSA -dname "CN=(server-fqdn),OU=(organizationalunit),O=(organization),L=(locality),ST=(state),C=(country)" -keystore tomcat.keystore -keypass (password) -storepass (password)
# This is the all-in-one command that generates the certificate for the client and places it in a keystore file, while setting both the certificate password and the keystore password.
# The net result is a file called "client.keystore"
keytool -genkeypair -alias (clientalias) -keyalg RSA -dname "CN=(client),OU=(organizationalunit),O=(organization),L=(locality),ST=(state),C=(country)" -keypass (password) -keystore client.keystore -storepass (password)
# This command exports the client certificate.
# The net result is a file called "client.cer" in your home directory.
keytool -exportcert -rfc -alias (clientalias) -file client.cer -keypass (password) -keystore client.keystore -storepass (password)
# This command imports the client certificate into the "tomcat.keystore" file.
keytool -importcert -alias (clientalias) -file client.cer -keystore tomcat.keystore -storepass (password) -noprompt
Certificates should now be set up appropriately. The next step is to configure your connector in the tomcat server.xml. Add a connector element that looks like this:
<Connector port="8443"
maxThreads="150"
scheme="https"
secure="true"
SSLEnabled="true"
truststoreFile="/full/path/to/tomcat.keystore"
truststorePass="(password)"
keystoreFile="/full/path/to/tomcat.keystore"
keystorePass="(password)"
clientAuth="true"
keyAlias="serverkey"
sslProtocol="TLS"/>
Note that in the above XML:
- The "port" attribute can be whatever you want.
- The "keystoreFile" and "truststoreFile" attributes should be full paths. Tomcat does not look in the same directory as server.xml by default.
- The "keystorePass" and "truststorePass" attributes should match the (password) value you used in the creation of the tomcat.keystore file.
- The "clientAuth" attribute must be set to "true". This is what triggers mutual authentication.
Additionally, in the server.xml, ensure that you DO NOT have an AprLifecycleListner defined. The XML for that listener will look something like this:
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
That element should be delete/commented out. The AprLifecycleListener does not get configured the same way as described above, and will not work with these instructions.
Restart tomcat. The server configuration should be complete.
I tested my work using Firefox, because it's easy to add client certificates to it. Open up Firefox and try to connect to an endpoint of your tomcat service on the port defined in your connector.
Ex: https://mytomcatdomain.com:8443/test
When you do this, you should get the standard alert from Firefox about an untrusted connection because we created a self-signed certificate for our Tomcat server. Add an exception for the certificate so that our client (Firefox) trusts our server (Tomcat).
Once you've added the exception, you should get a "Secure Connection Failed" message. The error code is "ssl_error_bad_cert_alert". This confirms that our Tomcat server is requesting authentication from the client. The request is failing because we have not configured Firefox to send our trusted client certificate yet.
To configure Firefox, we need to do a little more magic:
// Create a file called DumpPrivateKey.java. The contents should look like so:
public class DumpPrivateKey {
public static void main(String[] args) throws Exception {
final String keystoreName = args[0];
final String keystorePassword = args[1];
final String alias = args[2];
java.security.KeyStore ks = java.security.KeyStore.getInstance("jks");
ks.load(new java.io.FileInputStream(keystoreName), keystorePassword.toCharArray());
System.out.println("-----BEGIN PRIVATE KEY-----");
System.out.println(new sun.misc.BASE64Encoder().encode(ks.getKey(alias, keystorePassword.toCharArray()).getEncoded()));
System.out.println("-----END PRIVATE KEY-----");
}
}
Compile the java file with the following command:
javac DumpPrivateKey.java
Now we're going to use this little utility to extract a key from the client.keystore file we create above. Copy the client.keystore and client.cer files into the same directory as your DumpPrivateKey class. Execute the following:
# This extracts the client key from the client keystore
java DumpPrivateKey client.keystore (password) clientkey > clientkey.pkcs8
# This creates a client.p12 file that can be used by Firefox
openssl pkcs12 -export -in client.cer -inkey clientkey.pkcs8 -password pass:(password) -out client.p12
Note that in the above code, (password) should be the password you used to create the client.keystore.
Open up Firefox preferences. Click on the "Certificates" tab. Click on the "View Certificates" button. Click on the "Your Certificates" tab.
Click on the "Import" button and browse to the "client.p12" file that was created previously. You should be prompted to enter the password for the client certificate.
Assuming the "client.p12" was imported successfully, you can now refresh you Firefox page, and you should get a successful response from your Tomcat server endpoint.