Custom SSL for advanced JSSE developers

Use JSSE to customize the properties of your SSL connections

JSSE brings secure communications to Java applications, by using SSL to encrypt and protect data as it travels across a network. In this advanced look at the technology, Java middleware developer Ian Parkinson delves into the lesser-known aspects of the JSSE API, showing you how to program your way around some of the restrictions of SSL. Learn how to dynamically select the KeyStore and TrustStore, relax JSSE's password-matching requirements, and build your own customized KeyManager implementation.

Share:

Ian Parkinson (ianp@hursley.ibm.com), Software Engineer, WebSphere MQ, IBM

Ian Parkinson joined IBM in 1998, after graduating from Oxford University. After a stint programming the mainframe z/OS system, he now works on the Java interfaces for IBM's WebSphere MQ messaging product. He works at the Hursley Laboratory in the UK. Ian has been known to react badly to the suggestion that Java is too slow. You can contact him at ianp@hursley.ibm.com.



01 September 2002

Also available in Japanese

JSSE (Java Secure Socket Extension) enables Java applications to communicate securely over the internet using SSL. Because developerWorks already offers a tutorial covering the basic usage of JSSE (see Resources), we'll concentrate here on a more advanced usage of the technology. This article will demonstrate how to use the JSSE interface to tailor the properties of an SSL connection.

We'll start by developing a very simple secure client/server chat application. As we build up the client side of this application, I'll demonstrate how to customize the KeyStore and TrustStore files so that we can load both of them from the client's filesystem. Next, we'll focus on certificates and identities. By selecting different certificates from the KeyStore, you can represent your client differently to different servers. This advanced functionality comes in particularly handy if your client application needs to connect to multiple peers, or even if it needs to masquerade as different users.

Because this article focuses on more advanced topics, it is assumed that you have prior experience with JSSE. To run the examples you'll need a Java SDK with a correctly installed and configured JSSE provider. The J2SE 1.4 SDK comes with JSSE already installed and configured. If you're using J2SE 1.2 or 1.3 you will need to obtain a JSSE implementation and install it. See Resources to download the JSSE extension.

The JSSE API only became a standard with J2SE 1.4, and slight variations exist between earlier JSSE implementations. Examples here are based on the 1.4 API. Wherever necessary, I'll highlight the changes needed to make the examples work with Sun's JSSE implementation for J2SE 1.2 and 1.3.

Getting started: Setup

Before we delve too deeply into JSSE, let's familiarize ourselves with the client/server application we're going to use. SimpleSSLServer and SimpleSSLClient are the two components of our demonstration application. In order to run the examples, you'll need to have set up several KeyStore and TrustStore files on each end of the application. Specifically, you'll need:

  • A KeyStore file for the client, called clientKeys, containing a certificate each for the fictional correspondents Alice and Bob
  • A KeyStore file for the server, called serverKeys, containing a certificate for the server
  • A TrustStore file for the client, called clientTrust, containing the server's certificate
  • A TrustStore file for the server, called serverTrust, containing Alice's and Bob's certificate

Use your favorite key management utility to put these together, using alice, bob and server as the key aliases and password for all the store and certificate passwords. If you don't know how to do this, see the sidebar "Setting up KeyStore and TrustStore files."

Next, download the jar files that accompany this article. These files contain both source and compiled versions of the client/server application, so you can begin using them as soon as they're in your CLASSPATH.


Establishing a secure connection

To run SimpleSSLServer, we enter the following (somewhat lengthy) command:

 java -Djavax.net.ssl.keyStore=serverKeys
                -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=serverTrust
                -Djavax.net.ssl.trustStorePassword=password SimpleSSLServer

You can see that we've specified the KeyStore, which is used to identify the server, and also the password set in the KeyStore. Because the server will require client authentication, we've also supplied it with a TrustStore. By specifying the TrustStore we ensure that SSLSimpleServer will trust certificates presented by SSLSimpleClient. After the server has initialized itself, you'll get the following report:

 SimpleSSLServer running on port 49152

After that, the server will await a connection from the client. If you want to run the server on a different port, specify -port xxx at the end of the command, replacing the variable xxx with your chosen port.

Next, we set up the client component of our application. From a different console window, enter the following command:

 java -Djavax.net.ssl.keyStore=clientKeys
                -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=clientTrust
                -Djavax.net.ssl.trustStorePassword=password SimpleSSLClient

The client will by default attempt to connect to a server running on port 49152 on localhost. You can change the host using the -host and -port arguments on the command line. When the client has connected to the server, you'll get the message:

 Connected

Meanwhile, the server will report the connection request and display the distinguished name presented by the client as follows:

 1: New connection request 1: Request from CN=Bob,
                OU=developerWorks, O=IBM, L=Winchester, ST=Hampshire, C=UK

To test the new connection, try typing some text into the client, press Return, and watch the server echo that text. To kill the client, press Ctrl-C on the client console. The server will log the disconnect as follows:

 1: Client disconnected

There's no need to kill the server; we can just leave it running throughout our various exercises.


Inside SimpleSSLServer

Although we're going to deal mostly with the client application for the rest of this article, it's worth looking over the server code as well. Along with getting a feel for how the server application works, you'll learn how to use the HandshakeCompletedListener interface to retrieve information about the SSL connection.

SimpleSSLServer.java begins with three import statements, as follows:

 import javax.net.ssl.*; import java.security.cert.*;
                import java.io.*;
  • javax.net.ssl is the most important of the three statements; it contains most of the core JSSE classes and we need it to do any work with SSL.
  • java.security.cert is useful if you need to manipulate individual certificates, which we'll do later in the article.
  • java.io is the standard Java I/O package. In this case, we're using it to handle data received and sent through the secure socket.

Next, the main() method checks the command line for the optional -port argument. It then obtains the default SSLServerSocketFactory, constructs a SimpleSSLServer object, passing the factory to the constructor, and starts the server, as shown below:

 SSLServerSocketFactory ssf=
                (SSLServerSocketFactory)SSLServerSocketFactory.getDefault(); SimpleSSLServer
                server=new SimpleSSLServer(ssf, port); server.start();

Because the server runs on a separate thread, main() exits once it is up and running. The new thread calls the run() method, which results in the creation of an SSLServerSocket, and sets up the server to require client authentication, as shown below:

 SSLServerSocket serverSocket=
                (SSLServerSocket)serverSocketFactory.createServerSocket(port);
                serverSocket.setNeedClientAuth(true);

HandshakeCompletedListener

After it's been activated, the run() method sits in an infinite loop, awaiting requests from clients. Each socket is associated with an implementation of HandshakeCompletedListener, which is used to display the distinguished name (DN) from the client's certificate. The socket's InputStream is wrapped in an InputDisplayer, which runs as another thread and echoes data coming through the socket to System.out. The main loop of SimpleSSLServer is shown in Listing 1:

Listing 1. SimpleSSLServer main loop
 while (true) { String
                ident=String.valueOf(id++); // Wait for a connection request. SSLSocket
                socket=(SSLSocket)serverSocket.accept(); // We add in a HandshakeCompletedListener,
                which allows us to // peek at the certificate provided by the client.
                HandshakeCompletedListener hcl=new SimpleHandshakeListener(ident);
                socket.addHandshakeCompletedListener(hcl); InputStream in=socket.getInputStream();
                new InputDisplayer(ident, in); }

Our HandshakeCompletedListener, SimpleHandshakeListener, provides an implementation of the handshakeCompleted() method. This method is called by the JSSE infrastructure when the SSL handshake phase has completed, and passes (in a HandshakeCompletedEvent object) information regarding the connection. We use this to obtain and display the client's DN, as shown in Listing 2:

Listing 2. SimpleHandshakeListener
 class SimpleHandshakeListener implements
                HandshakeCompletedListener { String ident; /** * Constructs a
                SimpleHandshakeListener with the given * identifier. * @param ident Used to identify
                output from this Listener. */ public SimpleHandshakeListener(String ident) {
                this.ident=ident; } /** Invoked upon SSL handshake completion. */ public void
                handshakeCompleted(HandshakeCompletedEvent event) { // Display the peer specified in
                the certificate. try { X509Certificate
                    cert=(X509Certificate)event.getPeerCertificates()[0]; String
                    peer=cert.getSubjectDN().getName(); System.out.println(ident+": Request from
                "+peer); } catch (SSLPeerUnverifiedException pue) { System.out.println(ident+": Peer
                unverified"); } } }

Running the server app on J2SE 1.2 or 1.3

If you're running the SimpleSSLServer application on J2SE 1.2 or 1.3, you'll need to use a slightly different (and now outdated) JSSE API. Instead of importing , use javax.security.cert. This package also contains the class called X509Certificate; however, to get an array of certificate objects from the HandshakeCompletedEvent you'll have to use the getPeerCertificateChain() method rather than the getPeerCertificates() method.

Highlighted in red are the two crucial lines: getPeerCertificates returns the chain of certificates as an array of X509Certificate objects. These certificate objects establish the peer's (that is, the client's) identity. The first in this array is the certificate of the client itself; the last is normally the CA certificate. Once we have the peer's certificate, we get the DN and display it to System.out. X509Certificate is defined in the package java.security.cert.


Inside SimpleSSLClient

The first client application we'll look at does nothing clever. We will, however, extend it in later examples to demonstrate more advanced features. SimpleSSLClient is set up to allow subclasses to be easily added. The following four methods are intended to be overridden:

  • main() is called, of course, when the class is run from the command line. For each subclass, main() must construct an object of the appropriate class and call the methods runClient() and close() on the object. These methods are provided on the superclass -- SimpleSSLClient -- and are not intended to be overridden.
  • handleCommandLineOption() and displayUsage() allow each subclass to add options on the command line without needing to update the parent class. They are both called from the runClient() method.
  • getSSLSocketFactory() is the interesting method. A JSSE secure socket is always constructed from an SSLSocketFactory object. By constructing a customized socket factory we can customize the behavior of JSSE. For the purpose of future exercises, each SimpleSSLClient subclass implements this method and customizes the SSLSocketFactory appropriately.

For the time being, SimpleSSLClient only understands the -host and -port parameters, which allow the user to direct the client at the server. In this first basic example, getSSLSocketFactory returns the (JVM-wide) default factory, as shown below:

 protected SSLSocketFactory getSSLSocketFactory()
                throws IOException, GeneralSecurityException { return
                (SSLSocketFactory)SSLSocketFactory.getDefault(); }

The runClient() method, which is called from the main() method of the subclass, deals with the command-line arguments, and then obtains the SSLSocketFactory to use from the subclass. It then connects to the server, using the connect() method, and begins transmitting data over the secure channel, using the transmit() method.

The connect() method is fairly straightforward. After using the SSLSocketFactory to connect to the server, it calls startHandshake on the secure socket. This forces JSSE to complete the SSL handshake phase, and so triggers our HandshakeCompletedListener on the server side. While JSSE does start the handshake automatically, it only does so when data is first sent across the socket. Because we won't send any data until the user types a message on the keyboard, but we want the server to report the connection immediately, we need to force the handshake using startHandshake.

The transmit() method is also fairly straightforward. Its first task is to wrap the input source in an appropriate Reader, as shown below:

 BufferedReader reader=new BufferedReader( new
                InputStreamReader(in));

We're using the BufferedReader because it will split the input into individual lines for us.

Next, the transmit() method wraps the output stream -- in this case, the OutputStream provided by the secure socket -- in an appropriate Writer. The server expects the text to be encoded in UTF-8, so we ask the OutputStreamWriter to use this encoding:

 writer=new
                OutputStreamWriter(socket.getOutputStream(), "UTF-8");

The main loop is easy; as you can see in Listing 3, it looks much like the main loop of InputDisplayer in SimpleSSLServer:

Listing 3. Main loop of SimpleSSLClient
 boolean done=false; while (!done) {
                String line=reader.readLine(); if (line!=null) { writer.write(line);
                writer.write('\n'); writer.flush(); } else done=true; }

So much for the basic JSSE server and client code. We can now go on to extend SimpleSSLClient and see some other implementations of getSSLSocketFactory.


A do-it-yourself KeyStore

Remember how we ran SimpleSSLClient? The command was as follows:

 java -Djavax.net.ssl.keyStore=clientKeys
                -Djavax.net.ssl.keyStorePassword=password -Djavax.net.ssl.trustStore=clientTrust
                -Djavax.net.ssl.trustStorePassword=password SimpleSSLClient

The command is annoyingly long! Fortunately, this example and the one that follows will show you how to set up an SSLSocketFactory with hardcoded paths to the KeyStore and TrustStore. In addition to reducing the length of the above command, the techniques you'll learn will let you set up multiple SSLSocketFactory objects, each with different KeyStore and TrustStore settings. Without this kind of configuration, every secure connection within a JVM must use the same KeyStore and TrustStore. While this is acceptable for smaller applications, larger applications may need to connect to multiple peers on behalf of many different users.

Introducing CustomKeyStoreClient

For the first example, we'll use the example application CustomKeyStoreClient (found in the article source) to dynamically define a KeyStore. Before we look at the source code, let's see our CustomKeyStoreClient in action. For this exercise, we'll specify the TrustStore but not the KeyStore. Enter the following arguments on CustomKeyStoreClient's command line and we'll see what happens:

 java -Djavax.net.ssl.trustStore=clientTrust
                -Djavax.net.ssl.trustStorePassword=password CustomKeyStoreClient

Assuming the client connects fine, the server will report that a valid certificate has been presented. The connection succeeds because CustomKeyStoreClient.java has been hardcoded with the name of the KeyStore (clientKeys) and the password (password). If you chose a different filename or password for your client KeyStore then you can use the new command-line options -ks and -kspass to specify them.

Looking at the source code for CustomKeystoreClient.java, the first thing that getSSLSocketFactory does is call a helper method, getKeyManagers(). We'll worry about how this works later; for the time being simply note that it returns an array of KeyManager objects which have been set up with the required KeyStore file and password.

Listing 4. CustomKeyStoreClient.getSSLSocketFactory
 protected SSLSocketFactory
                getSSLSocketFactory() throws IOException, GeneralSecurityException { // Call
                getKeyManagers to get suitable key managers KeyManager[] kms=getKeyManagers(); //
                Now construct a SSLContext using these KeyManagers. We // specify a null
                TrustManager and SecureRandom, indicating that the // defaults should be used.
                SSLContext context=SSLContext.getInstance("SSL"); context.init(kms, null, null); //
                Finally, we get a SocketFactory, and pass it to SimpleSSLClient. SSLSocketFactory
                ssf=context.getSocketFactory(); return ssf; }

After getting the KeyManager array, getSSLSocketFactory performs some essential setup work common to all customizations of JSSE. To construct an SSLSocketFactory, an application obtains an instance of SSLContext, initializes it, and then uses the SSLContext to generate an SSLSocketFactory.

When getting the SSLContext, we specify a protocol of "SSL"; we could also put here specific versions of the SSL (or TLS) protocol and force communications to take place at that particular level. By specifying "SSL" we allow JSSE to default to the highest level it can support.

The first argument to SSLContext.init is the KeyManager array to use. The second argument, which is left null here, is a similar array of TrustManager objects, which we'll play with later. By leaving the second argument null, we're telling JSSE to use the default TrustStore, which picks up settings from the javax.net.ssl.trustStore and javax.net.ssl.trustStorePassword system properties. The third parameter would allow us to override JSSE's random number generator (RNG). The RNG is a sensitive area of SSL, and the misuse of this parameter could render your connections insecure. We'll leave this parameter null, allowing JSSE to use the default -- and secure! -- SecureRandom object.

Loading the KeyStore

Next, we'll look at how getKeyManagers loads and initializes the array of KeyManagers. Start with the code in Listing 5, then we'll discuss what's happening.

Listing 5. Loading and initializing KeyManagers
 protected KeyManager[]
                getKeyManagers() throws IOException, GeneralSecurityException { // First, get the
                default KeyManagerFactory. String alg=KeyManagerFactory.getDefaultAlgorithm();
                KeyManagerFactory kmFact=KeyManagerFactory.getInstance(alg); // Next, set up the
                KeyStore to use. We need to load the file into // a KeyStore instance.
                FileInputStream fis=new FileInputStream(keyStore); KeyStore
                ks=KeyStore.getInstance("jks"); ks.load(fis, keyStorePassword.toCharArray());
                fis.close(); // Now we initialize the TrustManagerFactory with this KeyStore
                kmFact.init(ks, keyStorePassword.toCharArray()); // And now get the TrustManagers
                KeyManager[] kms=kmFact.getKeyManagers(); return kms; }

Our first job is to obtain a KeyManagerFactory, but to do this we need to know which algorithm to use. Fortunately, JSSE makes the default KeyManagerFactory algorithm available. The default can be configured using the ssl.KeyManagerFactory.algorithm security property.

Next, the getKeyManagers() method loads the KeyStore file. This involves establishing an InputStream from the file, obtaining an instance of KeyStore, and loading the KeyStore from the InputStream. As well as the InputStream, the KeyStore needs to know the format of the stream (we use the default, "jks") and the store password. The store password must be supplied as an array of characters.

CustomKeyStoreClient package imports

To access the class, we have to import both javax.net.ssl and java.security.cert. Other classes, such as SSLContext and KeyManagerFactory, are members of javax.net.ssl from J2SE 1.4 onwards. Under J2SE 1.2 or 1.3 these classes are not in a standard location; for example the Sun JSSE implementation has them in com.sun.net.ssl.

A potentially useful trick to note is that KeyStore.load takes any InputStream. Your application could build this from anywhere; as well as a file, you could source it across a network, from a mobile device, or even generate the stream on the fly.

After we've got our KeyStore loaded up, we use it to initialize the KeyManagerFactory that we created earlier. Again, we need to specify a password, this time the individual certificate password. Usually with JSSE, each certificate in the KeyStore needs to have the same password as the KeyStore itself. By constructing the KeyManagerFactory yourself you can override this restriction.

After the KeyManagerFactory is initialized, it's trivial to use the getKeyManagers() method to get the array of appropriate KeyManager objects.

With CustomKeyStoreClient we've seen how to load a KeyStore from any location -- here we used the local filesystem -- and also how to allow different passwords to be used for the certificate and for the KeyStore itself. We'll later see how to allow each certificate in the KeyStore to have a different password. Although we've concentrated on the client side for this example, we could use the same techniques on the server side to build an appropriate SSLServerSocketFactory object.

CustomTrustStoreClient package imports

Once again, the classes used in this example will be found in different packages for different JSSE providers. Under J2SE 1.4, is found in javax.net.ssl; under J2SE 1.2 or 1.3, it will normally be in com.sun.net.ssl.


Roll your own TrustStore

Not surprisingly, overriding JSSE's default TrustStore is very similar to what we've just done with the KeyStore. CustomTrustStoreClient (found in the article source) has been set up to use both a hardcoded KeyStore and a hardcoded TrustStore. All you need to begin running it is the command: java CustomTrustStoreClient.

CustomTrustStoreClient expects the KeyStore to be a file called clientKeys with a password of password. It expects the TrustStore to be a file called clientTrust with a password of password. As with CustomKeyStoreClient, you can override these defaults using the -ks, -kspass, -ts, and -tspass arguments.

Much of getSSLSocketFactory() is identical to the same method in CustomKeyStoreClient. We even call the getKeyManagers() method from CustomKeyStoreClient to obtain the same array of customized KeyManager objects as in the previous example. This time, though, getSSLSocketFactory also has to obtain an array of customized TrustManager objects. In Listing 6, we see how getSSLSocketFactory uses the helper method getTrustManagers() to obtain the customized TrustManager objects:

Listing 6. How getSSLSocketFactory uses TrustManagers
 protected
                SSLSocketFactory getSSLSocketFactory() throws IOException, GeneralSecurityException
                { // Call getTrustManagers to get suitable trust managers TrustManager[]
                tms=getTrustManagers(); // Call getKeyManagers (from CustomKeyStoreClient) to get
                suitable // key managers KeyManager[] kms=getKeyManagers(); // Next construct and
                initialize a SSLContext with the KeyStore and // the TrustStore. We use the default
                SecureRandom. SSLContext context=SSLContext.getInstance("SSL"); context.init(kms,
                tms, null); // Finally, we get a SocketFactory, and pass it to SimpleSSLClient.
                SSLSocketFactory ssf=context.getSocketFactory(); return ssf; }

This time, when initializing the context, we override both KeyStore and TrustStore. We still let JSSE use its default SecureRandom, however, by passing null as the third parameter.

Not surprisingly, getTrustManagers also closely parallels the CustomKeyStoreClient equivalent, as shown in Listing 7:

Listing 7. Loading and initializing TrustManagers
 protected TrustManager[]
                getTrustManagers() throws IOException, GeneralSecurityException { // First, get the
                default TrustManagerFactory. String alg=TrustManagerFactory.getDefaultAlgorithm();
                TrustManagerFactory tmFact=TrustManagerFactory.getInstance(alg); // Next, set up the
                TrustStore to use. We need to load the file into // a KeyStore instance.
                FileInputStream fis=new FileInputStream(trustStore); KeyStore
                ks=KeyStore.getInstance("jks"); ks.load(fis, trustStorePassword.toCharArray());
                fis.close(); // Now we initialize the TrustManagerFactory with this KeyStore
                tmFact.init(ks); // And now get the TrustManagers TrustManager[]
                tms=tmFact.getTrustManagers(); return tms; }

The getTrustManagers() method begins by instantiating a TrustManagerFactory based on the default algorithm, just as before. It then loads the TrustStore file into a KeyStore object -- yes, the naming is very unfortunate -- and initializes the TrustManagerFactory.

Unlike the KeyStore equivalent, notice that there is no need to supply a password when initializing the TrustManagerFactory. Unlike private keys, trusted certificates need not be secured with individual passwords.

So far we've seen how to override the KeyStore and TrustStore dynamically. With the completion of these two examples, you should have a good idea of how to set up a KeyManagerFactory and a TrustManagerFactory and use these to seed an SSLContext. Our final example is a little more tricky: we're going to build our own implementation of the KeyManager.


Custom KeyManager setup: Choosing an alias

When running the previous versions of the client application, did you notice which certificate DN was displayed by the server? We deliberately set the client KeyStore up to have two acceptable certificates, one for Alice and one for Bob. In this case, JSSE will choose whichever certificate it sees fit. In my installation, it always seems to pick Bob's certificate, but your JSSE may behave differently.

Our example application, SelectAliasClient, allows you to choose which certificate to present. Since we named each certificate in the Keystore by an alias, alice or bob, selecting Alice's certificate is a matter of entering the command: java SelectAliasClient -alias alice.

When the client connects and the SSL handshake completes, the server will respond with the following:

 1: New connection request 1: Request from CN=Alice,
                OU=developerWorks, O=IBM, L=Winchester, ST=Hampshire, C=UK

(or whatever values you chose when creating Alice's certificate). Similarly, if you select Bob's certificate, enter: java SelectAliasClient -alias bob and the server will report this:

 2: New connection request 2: Request from CN=Bob,
                OU=developerWorks, O=IBM, L=Winchester, ST=Hampshire, C=UK

Custom KeyManager implementation

To force the choice of a particular alias, we're going to write an implementation of X509KeyManager, the KeyManager normally used by JSSE for SSL communication. Our implementation will contain a genuine X509KeyManager and simply pass most of the calls through. The only method it will intercept is chooseClientAlias(); our implementation checks to see whether the required alias is valid or not, and returns it if it is.

The X509KeyManager interface uses a number of methods during the SSL handshake phase to retrieve the key, which is then used to identify the peer. You'll find a reference for all the methods in the Resources section. The following methods will be important for this exercise:

  • getClientAliases() and getServerAliases() provide an array of valid aliases for use with SSLSockets and SSLServerSockets respectively.
  • chooseClientAlias() and chooseServerAlias() return a single valid alias.
  • getCertificateChain() and getPrivateKey() each take the alias as a parameter, and return information regarding the identified certificate.

Flow of control in the custom KeyManager

The flow of control will work something like this:

  1. JSSE calls chooseClientAlias to find the alias to use.
  2. chooseClientAlias calls getClientAliases on the real X509KeyManager, to find a list of valid aliases so that it can check whether the required alias is valid.
  3. JSSE calls getCertificateChain and getPrivateKey -- specifying the correct alias -- on our X509KeyManager, which lets the call drop through to the wrapped KeyManager.

The chooseClientAlias() method of our KeyManager, AliasForcingKeyManager(), actually needs to call getClientAliases() multiple times, once for each key type supported by the JSSE installation, as shown in Listing 8:

Listing 8. Forcing the choice of alias
 public String
                chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { // For
                each keyType, call getClientAliases on the base KeyManager // to find valid aliases.
                If our requested alias is found, select it // for return. boolean aliasFound=false;
                for (int i=0; i<keyType.length && !aliasFound; i++) { String[]
                validAliases=baseKM.getClientAliases(keyType[i], issuers); if (validAliases!=null) {
                for (int j=0; j<validAliases.length && !aliasFound; j++) { if
                (validAliases[j].equals(alias)) aliasFound=true; } } } if (aliasFound) return alias;
                else return null; }

AliasForcingKeyManager requires implementations of the other five methods of X509KeyManager; these just call their counterparts on baseKM.

It now remains to use AliasForcingKeyManager instead of the normal KeyManager. This happens in getSSLSocketFactory, which first calls getKeyManagers and getTrustManagers from the other examples, but then wraps each KeyManager returned from getKeyManagers in an instance of AliasForcingKeyManager, as shown in Listing 9:

Listing 9. Wrapping X509KeyManagers
 protected SSLSocketFactory
                getSSLSocketFactory() throws IOException, GeneralSecurityException { // Call the
                superclasses to get suitable trust and key managers KeyManager[]
                kms=getKeyManagers(); TrustManager[] tms=getTrustManagers(); // If the alias has
                been specified, wrap recognized KeyManagers // in AliasForcingKeyManager instances.
                if (alias!=null) { for (int i=0; i<kms.length; i++) { // We can only deal with
                instances of X509KeyManager if (kms[i] instanceof X509KeyManager) kms[i]=new
                AliasForcingKeyManager((X509KeyManager)kms[i], alias); } } // Now construct a
                SSLContext using these (possibly wrapped) // KeyManagers, and the TrustManagers. We
                still use a null // SecureRandom, indicating that the defaults should be used.
                SSLContext context=SSLContext.getInstance("SSL"); context.init(kms, tms, null); //
                Finally, we get a SocketFactory, and pass it to SimpleSSLClient. SSLSocketFactory
                ssf=context.getSocketFactory(); return ssf; }

KeyManager repackaging

Both and X509KeyManager moved from a vendor-specific package under J2SE 1.2 and 1.3 into javax.net.ssl in J2SE 1.4; The method signatures for X509KeyManager were changed slightly when the interface was moved.

You can use the techniques we've explored here to override any aspect of the KeyManager. Similarly, you could use them to replace the TrustManager, altering JSSE's mechanism for deciding whether or not to trust certificates flowed from the remote peer.


Conclusion

We've discussed a fair number of tricks and techniques in this article, so let's close with a quick review. You should now have a beginner's understanding of how to:

  • Use HandshakeCompletedListener to gather information about a connection
  • Obtain an SSLSocketFactory from an SSLContext
  • Use a custom, dynamic TrustStore or KeyStore
  • Relax JSSE's restriction that KeyStore passwords and individual certificate passwords must match
  • Use your own KeyManager to force the choice of identifying certificate

Where appropriate, I've also suggested ways to extend these techniques for various application scenarios. The trick of wrapping an X509KeyManager in your own implementation can be used with many other classes in JSSE, and there are certainly more interesting things to do with the TrustStore and KeyStore than just loading a hardcoded file.

Regardless of how you choose to implement the advanced JSSE customizations demonstrated here, none of them should be approached carelessly. When tinkering with the guts of SSL, it's important to keep in mind that a mistake could render your connections insecure.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10703
ArticleTitle=Custom SSL for advanced JSSE developers
publish-date=09012002