Tomcat 4.0.3, J2SE 1.4, and Programmatic SSL


LATEST DISCLAIMER: This document is ancient, it is (likely) irrelevant unless you are stuck with an old JRE and Tomcat.

Update (10/24/02): I have recieved two emails since publishing this article from people who were in the same situation, the same *dire* and *hopeless* situation, who found this article helpful. James Cushing wrote me and stated that if you download Tomcat 4.1 LE then the situation outlined below should disappear – I have not tried this myself, but I trust it works.. Good luck to all and may all your deadlines be met.

DISCLAIMER: This document is dated. The Jakarta group is a very quick development team and there is no telling when the information in this document will be obsolete. Keep the following in mind: This document refers to Tomcat 4.0.3, but may be relevant to all Tomcat 4.0 releases, and possibly all Tomcat 4.x releases. If you find the information in this document to be incorrect please email me and I will correct it accordingly.

This article deals with what may be a rare situation, when an instance of Tomcat is configured to serve pages over SSL, and it must also perform programmatic SSL. If you are using the J2SE 1.4 with Tomcat 4.0.3 there are some issues with using the newly available javax.net.ssl classes ( you must use the legacy classes from JSSE 1.0.2 – com.sun.net.ssl ). This is not a problem per se, but my journey to find this answer was long and relying on software documentation from Sun proved to be my biggest mistake. I hope that this article may help someone who is caught in a similar situation.

For the purposes of this article, assume that you have two instances of Tomcat 4.0.3 installed with J2SE 1.4. Both instances are configured to serve pages via HTTPS, and both instances need to communicate with each other using SSL. To make this example more concrete, let’s assume that one of the Tomcat instances is configured to provide Stock Quotes over SSL, and another machine is configured to programmatically query this “feed”.

The problems and solutions discussed below are also relevant if you are developing a process that runs within the same JVM as Tomcat that needs to access any URL with an “https” scheme. This article and the solutions described may also be relevant to anyone who is trying to use SOAP over HTTPS from a Tomcat 4.0.3 instance, and the following arcticle will be especially relevant to anyone who wishes to perform automatic key or trust management with the interfaces provided by the JSSE. I’m assuming that the reader already has a basic understanding of Tomcat configuration.

DISCLAIMER: This document is dated. The Jakarta group is a very quick development team and there is no telling when the information in this document will be obsolete. Keep the following in mind: This document refers to Tomcat 4.0.3, but may be relevant to all Tomcat 4.0 releases, and possibly all Tomcat 4.x releases. If you find the information in this document to be incorrect please email me and I will correct it accordingly.

The Easy Part: Configuring an HTTPS Connector

Configuring Tomcat to serve pages over SSL is straightforward, and the process described in the documentation that is available on the Jakarta website. There are a few caveats such as installing JSSE 1.0.2 if you are running J2SE 1.3, or running into some problems using the keytool utility that ships with the JDK. This article deals only with the J2SE 1.4 which includes the JSSE and JCE and already has protocol handlers configured in ${JAVA_HOME}/jre/lib./security.properties.

Note: This article doesn’t deal with J2SE 1.3, but I need to mention that JSSE 1.0.2 does not fully implement an https handler. If you start using JSSE 1.0.2 with J2SE 1.3 you may run into some of the problems I ran into. Response Codes were not visible from clients, and fully configuring JSSE 1.0.2 taxed my patience. I’m very lazy, so I punted on 1.3 and upgraded to 1.4 where JSSE is ready and available ( or so I had thought…. )

The process for configuring Tomcat to respond to HTTPS request is fully explained in the Jakarta documentation, and I do not wish to duplicate that document here. Please refer to the documentation that is available in the references section of this article for more detail. The process for configuring Tomcat 4.0 for SSL consists of two steps:

  • Create a keystore using the keytool utilities which is included in the JDK.
  • Modify your server.xml file to enable the HTTPS connector.
Q: What is your name?, A: http://www.discursive.com

keytool utility asks a very misleading question.

The logical answer to this question would be your name – “Joe Smith”. The less than obvious answer to this question is “machine.domain.com”.

You must supply the fully qualified host name of the server, or you will need to write your own HostnameVerifier. If you do enter your name into this field, the system will still work when a browser meets your HTTPS connector for the first time. The user will be asked to trust the certificate from “Joe Smith”. If you try to access this HTTPS connector from another Java program using JSSE, you will encounter exception when the HostnameVerifier rejects “Joe Smith” as an invalid hostname. The reason why Sun chose to ask you for your first name and last name is beyond me, but if you type in “Joe Smith”, you will run into problems with Programmatic SSL.

Coding the Client – Take 1

At this point, we’ve got our Server set up to serve content over HTTPS, and we’re going to write a component that periodically retrieves the stock quotes for Lucent and Cicso. Assume that the quote feed is available on the server in a context with a path of “/feed” and the URL to get both the Cisco and Lucent quotes is “https://server.domain.com:8443/feed/Stock?CSCO+LU”. This component will run on the client instance of Tomcat and it will be a simple TimerTask object that we’ve created at startup.

The system in question could consist of an arbitrary number of machines, and in order for these machines to communicate over SSL with no extra programming we are required to export certificates from the server’s keystore and import these certificates into the client’s truststore. In order to avoid a very costly configuration, we have decided to implement an X509TrustManager and HostnameVerifier to relax the requirements for certificates. This is a relatively straightforward process, and the JSSE documentation provides examples. Since we are using J2SE 1.4 the process is even easier than JSSE 1.0.2 w/ J2SE 1.3.

The example is simplistic to make this article more readable – you don’t want to blindly trust all certificates that are presented to a given client. You should at least check some of the properties of the X509Certificate such as the Issuer Distinguished Name or the Subject Distinguished Name.

Sample HttpsTest.java

package com.discursive.sample.tomcatssl;

import java.io.*;
import java.net.*;
import java.security.*;
import java.security.cert.*;
import java.util.*;
import javax.net.ssl.*;

public class HttpsTest extends java.util.TimerTask {

  // We would never hardcode this literal in a real system,
  // this is only for this article.
  private String url = "https://server:8443/feed/Stock?CSCO+LU";

  // Create an anonymous class to trust all certificates.
  // This is bad style, you should create a separate class.
  private X509TrustManager xtm = new X509TrustManager() {
       public void checkClientTrusted(X509Certificate[] chain,
                                String authType) {}
       public void checkServerTrusted(X509Certificate[] chain,
                                String authType) {}
       public X509Certificate[] getAcceptedIssuers() {
    return null;
 }
    };

  // Create an class to trust all hosts
  private HostnameVerifier hnv = new HostnameVerifier() {
       public boolean verify(String hostname,
                             SSLSession session) {
    return true;
       }
    };

  // In this function we configure our system with a less stringent
  // hostname verifier and X509 trust manager.  This code is
  // executed once, and calls the static methods of HttpsURLConnection
  public HttpsTest() {
    // Initialize the TLS SSLContext with
    // our TrustManager
    SSLContext sslContext = null;
    try {
       sslContext = SSLContext.getInstance("TLS");
 X509TrustManager[] xtmArray = new X509TrustManager[] { xtm };
 sslContext.init( null,
                        xtmArray,
                        new java.security.SecureRandom() );
    } catch( GeneralSecurityException gse ) {
       // Print out some error message and deal with this exception...
    }

    // Set the default SocketFactory and HostnameVerifier
    // for javax.net.ssl.HttpsURLConnection
    if( sslContext != null ) {
       HttpsURLConnection.setDefaultSSLSocketFactory(
                   sslContext.getSocketFactory() );
    }
    HttpsURLConnection.setDefaultHostnameVerifier( hnv );
  }

  // This function is called periodically, the important thing
  // to note here is that there is no special code that needs to
  // be added to deal with a "HTTPS" URL.  All of the trust
  // management, verification, is handled by the HttpsURLConnection.
  public void run() {
    try {

        URLConnection urlCon = (new URL(url)).openConnection();
        urlCon.getInputStream();
        //  .... Whatever we want to do with these quotes...

    } catch( MalformedURLException mue ) {
        // Log an error message, and handle this exception...
    } catch( IOException ioe ) {
        // ditto
    }
  }
}
 

This code looks good, the calls to HttpsURLConnection, setDefaultHostnameVerifier and setDefaultSSLSocketFactory look adequate to configure this relaxed trust. You’ve written the code and you are getting ready to run this program, you’ve thrown away your JSSE 1.0.2 documentation and you are working off the the JDK 1.4 JSSE documentation. Excited to have finally solved this problem you are surprised by the fact that….it doesn’t work. Determined not to let the computer win, you try to get to the bottom of the problem by reading up on JSSE in J2SE 1.4 – this will get you nowhere. If you get really frustrated, you’ll checkout the jakarta-tomcat-4.0 module from the Apache CVS repository, and you’ll be one step closer to the real problem.

What you need to know is that code internal to Catalina ( I think org.apache.catalina.net.SSLServerSocketFactory), adds the old security provider and overrides the protocol handler that is already configured in the J2SE 1.4. You’ve created the X509TrustManager and HostnameVerifier, and you’ve even set the default properties of the HttpsURLConnection, but the javax.net.ssl.HttpsURLConnection class is *never* used. Probe a little further, add some debugging and print out the class name of the URLConnection in your run() function. The URLConnection is not a javax.net.ssl.HttpsURLConnection at all, it is a com.sun.net.ssl.HttpsConnection.

Tomcat 4.0.3 with J2SE 1.4: You need to use deprecated JSSE classes

Ignore the code example above, you need to use the deprecated classes in the com.sun.net.ssl package. Your X509TrustManager needs to extend com.sun.net.ssl.X509TrustManager, and your HostnameVerifier needs to extend com.sun.net.ssl.HostnameVerifier. The classes themselves have different method signatures and you will need to deal with deprecation warnings at compilation time.

Another option would be to force the URL class to use a different URLStreamHandler, but, if your situation is similar to mine, you already have tons of code that uses URL.openConnection() and you don’t feel like changing every call to this function. I haven’t tested this or pursued this option as it is not feasible in my current development environment. If you are currently working in a large development team, it is preferable to find a solution that works “behind the scenes”. I’ve found that getting groups of programmers to adhere to standards or change their ways is a difficult task at best.

Another option would be to alter the Catalina source code and use a customized version of the product that does not change the protocol handlers or add security providers. I did not pursue this option because I wanted my code to work with an unmodified Tomcat 4.0.3 download from Jakarta.

Coding the Client – Take 2

Adapting the client code to work in J2SE 1.4 using the deprecated JSSE 1.0.2 is easy. Just pretend that you are using JSSE 1.0.2 in J2SE 1.4. Two things need to be done:

1. Change “import javax.net.ssl.*;” to “import com.sun.net.ssl.*;”.

   import java.io.*;
 import java.net.*;
 import java.security.*;
 import java.security.cert.*;
 import java.util.*;
 import com.sun.net.ssl.*;
 

2. Modify the X509TrustManager and the HostnameVerifier to match the deprecated API.

   // Create an anonymous class to trust all certificates.
 // This is bad style, you should create a separate class.
 private X509TrustManager xtm = new X509TrustManager() {
    public boolean isClientTrusted(X509Certificate[] chain) {
 return true;
    }
    public boolean isServerTrusted(X509Certificate[] chain) {
 return true;
    }
    public X509Certificate[] getAcceptedIssuers() {
 return null;
    }
 };

 // Create an class to trust all hosts
 private HostnameVerifier hnv = new HostnameVerifier() {
    public boolean verify(String hostname,
      String session) {
 return true;
    }
 };
 

You should put the calls to HttpsURLConnection.setDefaultHostnameVerifier() and setDefaultSSLSocketFactory() during a startup class when the web application is loaded, but to keep it simple I put the code in the constructor of the TimerTask object. You could also run the code in the constructor from a static initializer in a class that gets loaded on startup, but static initializers are hard to debug and can lead to confusing error messages. The best place for this code is the init() function of a Servlet that has a element in the web.xml of that particular web context.

Summary

The above can be summarized in a few points:

  • If you need to perform any work with the JSSE API in Tomcat 4.0.3, you will need to pretend that the new JSSE in J2SE 1.4 is not available. Import and use JSSE 1.0.2 classes from com.sun.net.ssl.
  • This is all happening because when you register a protocol handler or a new security provider you are doing so by calling a static method on a class like Security. This may be out of line, but wouldn’t it be more flexible if you could have different security contexts? This would be like having multiple ClassLoader objects with hierarchical relationships. This way you could have a context that still used the JSSE 1.0.2 API ( say code internal to Catalina ), and you could have your own code run in a separate context. This would allow you to mix different security providers and protocol handlers in larger systems. Instead, what happens is that one vendor’s library could call Security.addProvider(Provider provider) out of the blue and change everything. ( I fully expect someone to tell me that this already exists, and that I haven’t looked hard enough. )
  • It helps to know what is going on. If you are using Tomcat please take the time to download the source code and understand what is happening. Also, it is clear that the Jakarta group needs more help writing documentation. Maybe some of us lazy users should lend a hand.
  • This is no one’s fault, ( this is not even a “bug” ). The computer was doing exactly as it was told.
Useful References

Jakarta Tomcat 4.0 “SSL Configuration HOW-TO”

JSSE Homepage

JSSE ( bundled with J2SE 1.4 ) Reference Guide

JSSE 1.0.2 Reference Guide

Apache Axis, an implementation of SOAP