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.
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.
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.sslis 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.certis useful if you need to manipulate individual certificates, which we'll do later in the article.java.iois 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); |
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");
}
}
}
|
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.
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 methodsrunClient()andclose()on the object. These methods are provided on the superclass --SimpleSSLClient-- and are not intended to be overridden.handleCommandLineOption()anddisplayUsage()allow each subclass to add options on the command line without needing to update the parent class. They are both called from therunClient()method.getSSLSocketFactory()is the interesting method. A JSSE secure socket is always constructed from anSSLSocketFactoryobject. 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 theSSLSocketFactoryappropriately.
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.
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.
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.
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.
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()andgetServerAliases()provide an array of valid aliases for use withSSLSockets andSSLServerSockets respectively.chooseClientAlias()andchooseServerAlias()return a single valid alias.getCertificateChain()andgetPrivateKey()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:
- JSSE calls
chooseClientAliasto find the alias to use. chooseClientAliascallsgetClientAliaseson the realX509KeyManager, to find a list of valid aliases so that it can check whether the required alias is valid.- JSSE calls
getCertificateChainandgetPrivateKey-- specifying the correct alias -- on ourX509KeyManager, 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;
}
|
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.
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
HandshakeCompletedListenerto gather information about a connection - Obtain an
SSLSocketFactoryfrom anSSLContext - 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.
- Download the article source code for use with J2SE 1.4.
- If you're working with Sun's JSSE 1.0.3, under J2SE 1.2 or 1.3, download the revised article source code.
- The tutorial "Using JSSE for secure socket communication"
(developerWorks, April 2002) is a good introduction to JSSE.
- JavaWorld also offers a good introductory article on JSSE, "Build secure network applications with SSL and the JSSE API" (May 2001).
- "Implement HTTPS tunneling with JSSE" is another excellent article from JavaWorld, showing how to use HTTP and the
HttpsUrlConnectionclass to tunnel HTTPS through firewalls. - ONJava.com's "Secure your sockets with JSSE" (May 2001) demonstrates how to develop a secure Web server and client using JSSE.
- See Sun's javadoc on
X509KeyManagerfor a complete discussion of all its methods. - "Security challenges for Enterprise Java in an e-business environment," from the IBM Systems Journal, gives a thorough overview of the security features of a J2EE Web application server.
- Sun, of course, has the official JSSE user guides for JSSE 1.0.3 and JSSE for J2SE 1.4.
- The developerWorks Java
security discussion forum is a good place to learn more about secure sockets and related security solutions.
- You'll find hundreds of articles about every aspect of Java programming in the developerWorks Java technology zone.
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.
Comments (Undergoing maintenance)





