Secure Your Sockets with JSSE

21
Secure Your Sockets with JSSE Java's APIs are great for developing networked or fully distributed applications. The java.net package makes it a cinch to develop custom socket-based client-server applications. The java.rmi packages make it possible to develop distributed object systems with minimal programming. Other packages, such as Apache's SOAP , provide the capability to develop distributed applications that communicate with non-Java-based objects. Networked applications, by their very nature, require close attention to security. The Secure Sockets Layer (SSL) protocol was developed by Netscape in 1994 as a common solution to client-server communication security issues. SSL supports a flexible client- server authentication scheme and provides for algorithm-independent encrypted client- server communication. SSL runs as a layer between the Transport Control Protocol (TCP) and application layer protocols, such as HTTP and SMTP. The current version of SSL is 3.0. It has been standardized by the Internet Engineering Task Force (IETF) as Transport Layer Security (TLS) version 1.0. There are few changes between TLS 1.0 and SSL 3.0, but it is good idea to continue to use SSL 3.0 until older, non-TLS-capable browsers fade away. The Java Secure Socket Extension Since SSL and TLS provide a standard, flexible, secure, and easy-to-use solution for basic client-server communication issues, the question naturally arises, "How can I use SSL within my Java programs?" Fortunately, Sun has answered that question with a complete SSL/TLS API, the Java Secure Socket Extension (JSSE). The current version of JSSE is 1.02, freely available from Sun's Web site . In this column, I'll show you how to install JSSE and use it to implement HTTPS (i.e., HTTP over SSL). I'll provide you with an example of a mini-HTTPS server and Java clients that support SSL. I'll then show you how to setup a bi-directional SSL scheme where clients authenticate servers and servers authenticate clients. Downloading and Installing JSSE Since JSSE does not come with the Java 2 SDK, you must download it from Sun's site and integrate it with your current JDK installation. 1. Download JSSE by going to http://www.javasoft.com/products/jsse/ and following the links at the bottom of the page. I suggest downloading both the software and documentation. Since JSSE is subject to export controls, you'll have to agree to these controls if you download JSSE from the U.S. or Canada.

Transcript of Secure Your Sockets with JSSE

Secure Your Sockets with JSSE Java's APIs are great for developing networked or fully distributed applications. The java.net package makes it a cinch to develop custom socket-based client-server applications. The java.rmi packages make it possible to develop distributed object systems with minimal programming. Other packages, such as Apache's SOAP, provide the capability to develop distributed applications that communicate with non-Java-based objects.

Networked applications, by their very nature, require close attention to security. The Secure Sockets Layer (SSL) protocol was developed by Netscape in 1994 as a common solution to client-server communication security issues. SSL supports a flexible client-server authentication scheme and provides for algorithm-independent encrypted client-server communication. SSL runs as a layer between the Transport Control Protocol (TCP) and application layer protocols, such as HTTP and SMTP.

The current version of SSL is 3.0. It has been standardized by the Internet Engineering Task Force (IETF) as Transport Layer Security (TLS) version 1.0. There are few changes between TLS 1.0 and SSL 3.0, but it is good idea to continue to use SSL 3.0 until older, non-TLS-capable browsers fade away.

The Java Secure Socket Extension

Since SSL and TLS provide a standard, flexible, secure, and easy-to-use solution for basic client-server communication issues, the question naturally arises, "How can I use SSL within my Java programs?" Fortunately, Sun has answered that question with a complete SSL/TLS API, the Java Secure Socket Extension (JSSE). The current version of JSSE is 1.02, freely available from Sun's Web site.

In this column, I'll show you how to install JSSE and use it to implement HTTPS (i.e., HTTP over SSL). I'll provide you with an example of a mini-HTTPS server and Java clients that support SSL. I'll then show you how to setup a bi-directional SSL scheme where clients authenticate servers and servers authenticate clients.

Downloading and Installing JSSE

Since JSSE does not come with the Java 2 SDK, you must download it from Sun's site and integrate it with your current JDK installation.

1. Download JSSE by going to http://www.javasoft.com/products/jsse/ and following the links at the bottom of the page. I suggest downloading both the software and documentation. Since JSSE is subject to export controls, you'll have to agree to these controls if you download JSSE from the U.S. or Canada.

2. After downloading JSSE, you should have a file named jsse-1_0_2-do.zip. Unzip this file to produce a folder named jsse1.0.2.

3. Within the jsse1.0.2 folder you'll find a lib directory and within the lib directory, you'll find the files jsse.jar, jcert.jar, and jnet.jar. Copy these files to the lib/ext subdirectory of your Java home directory. Use the program shown in Listing 1 to find your Java home directory. (It may not be where you think it is.) You should also copy these JAR files to the jre/lib/ext directory off of where the Java 2 SDK is installed.

4. Test your JSSE installation by running the JSSETest program shown in Listing 2.

Listing 1. The ShowJavaHome program.

public class ShowJavaHome { public static void main(String[] args) { System.out.println(System.getProperty("java.home")); } }

Listing 2. The JSSETest program.

import java.security.*; public class JSSETest { public static void main(String[] args) { try { Class.forName("com.sun.net.ssl.internal.ssl.Provider"); }catch(Exception e) { System.out.println("JSSE is NOT installed correctly!"); System.exit(1); } System.out.println("JSSE is installed correctly!"); } }

Certificates, Keystores, and Truststores

Since SSL uses certificates for authentication, we'll need to create certificates for our clients and servers. JSSE can use certificates created by the java keytool, so creating these certificates will be straightforward.

However, before we begin, there is one issue that you should be aware of. JSSE differentiates between regular keystores and truststores. Keystores (from JSSE's perspective) are databases of key pairs and certificates that are used to set up SSL authentication. Truststores are keystores that are used to verify the identities of other clients and servers. When a client or server is setting up an SSL session, it will retrieve its

certificates and keys from its keystore. When it verifies the identities of other clients or servers, it will retrieve trusted certification authority (CA) certificates from its truststores.

JSSE looks for truststores using the following algorithm.

1. If the javax.net.ssl.trustStore system property is defined, then the value of this property is used as the truststore's location.

2. If the file lib/security/jssecacerts file is defined off of the java.home directory, then the jssecacerts file is used as the truststore.

3. If the file lib/security/cacerts file is defined off of the java.home directory, then the cacerts file is used as the truststore.

You can find the value of the javax.net.ssl.trustStore property using the ShowTrustStore program provided in Listing 3.

Listing 3. The ShowTrustStore program.

public class ShowTrustStore { public static void main(String[] args) { String trustStore = System.getProperty("javax.net.ssl.trustStore"); if(trustStore == null) System.out.println("javax.net.ssl.trustStore is not defined"); else System.out.println("javax.net.ssl.trustStore = " + trustStore); } }

Generating a Server Certificate

Server certificates can be generated with a single keytool command. I used the following command to create an RSA certificate, referenced by the alias of jamie, and stored in a keystore named certs.

keytool -genkey -keystore certs -keyalg rsa -alias jamie -storepass serverkspw -keypass serverpw

The keytool then prompted me for information to put into the certificate. My answers are shown in bold.

What is your first and last name? [Unknown]: enpower What is the name of your organizational unit? [Unknown]: Software Development What is the name of your organization? [Unknown]: Toolery.com What is the name of your City or Locality? [Unknown]: Chula Vista What is the name of your State or Province? [Unknown]: CA

What is the two-letter country code for this unit? [Unknown]: US Is <CN=enpower, OU=Software Development, O=Toolery.com, L=Chula Vista, ST=CA, C=US> correct? [no]: y

Note that I used a keystore password of serverkspw and a key password of serverpw. Go ahead and use these same values for the time being. You can use a different alias if you like. Also, enter your own information for the certificate. I used my machine name (enpower) for the first and last name of the certificate. You should do the same. If your machine does not have a name, use it's IP address. The enpower name is the name of my laptop's manufacturer.

A Secure Web Server

Now that we have a server certificate, all we need is a Java web server to take advantage of the certificate. Listing 4 provides an HTTP server that I've used in a few of my Java books. It is a fairly primitive server. I don't recommend using it for production systems. But it is small and works for simple HTTP-related examples.

Listing 5 provides a class named SecureServer that extends HTTPServer to provide support for SSL. As you can see, it is only about 50 lines. By following this example, you'll be able to see how easy it is to add SSL support to an existing HTTP application.

Compiling and Running SecureServer

Compile HTTPServer from within your working directory. (The same one that contains cacerts.) Then compile SecureServer. Next, create an HTML file named index.htm to be served in your directory. You can use the one shown in Listing 6 if you want.

Now start the server by entering java SecureServer from a console window. You may have to wait about a minute or two for the server to begin taking requests. The seeding of the secure random number generator slows things down. If you have a server currently running on port 443 (the HTTPS port), you'll have to disable it in order to get SecureServer to work.

When you run SecureServer, it will generate the following output.

SecureServer version 1.0 SecureServer is listening on port 443.

Now use a browser to establish an SSL connection to SecureServer. Since my machine name is enpower, I'll enter https://enpower/ in the Internet Explorer 5.5 address bar. Internet Explorer contacts SecureServer and tries to set up an SSL connection. SecureServer then sends IE its certificate. Because the certificate is not signed by a valid certificate authority, Internet Explorer displays the following popup.

When I click on the View Certificate button, this dialog box appears.

The above dialog explains why Internet Explorer balked at the certificate. If you click the Details tab, you can view the information that is contained in the certificate.

If you click the Certification Path tab, you'll see that the certificate is self-signed.

After clicking the OK button and accepting the certificate, Internet Explorer displays the following content.

That's all there is to setting up server-side SSL. Next I'll discuss how SecureServer works and then show you how to set up SSL on the client side.

How SecureServer Works

The first thing that you should notice about SecureServer is that it imports the following packages:

import javax.net.*; import javax.net.ssl.*; import com.sun.net.ssl.*;

These are the basic packages that are part of the JSSE API. The javax.net package provides the SocketFactory and ServerSocketFactory classes, which are used to replace normal TCP sockets with SSL sockets. The javax.net.ssl package provides classes and interfaces for establishing and managing an SSL session. The com.sun.net.ssl package provides the underlying key management classes and interfaces.

There is another JSSE package named javax.security.cert that provides additional public key certificate support. However, we don't need this package for SecureServer.

SecureServer defines the following field variables:

String KEYSTORE = "certs"; char[] KEYSTOREPW = "serverkspw".toCharArray(); char[] KEYPW = "serverpw".toCharArray(); boolean requireClientAuthentication;

These variables contain the names of the keystore, the keystore password, and key password. Note that you shouldn't hardwire your passwords into your code. Also, never use String objects to store passwords because they are immutable and cannot by overwritten. Use a char array instead. The requireClientAuthentication field should be set to true if you want the server to authenticate the client's certificate (more on this later).

The main() method simply creates a SecureServer object and invokes its run() method. The SecureServer constructor passes the server's name, version, and port values to the superclass constructor (HTTPServer).

The getServerSocket() method is where all of the SSL action takes place. It overrides the getServerSocket() method of HTTPServer to substitute a SSLServerSocket for an ordinary TCP ServerSocket.

The getServerSocket() method begins by registering JSSE as a cryptographic provider.

Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());

It then accesses the cacerts keystore. JKS stands for Java keystore, which is the type of keystore created by the keytool.

KeyStore keystore = KeyStore.getInstance("JKS"); keystore.load(new FileInputStream(KEYSTORE), KEYSTOREPW);

The Security and KeyStore classes are defined in the java.security package, which is part of the standard Java 2 SDK.

A KeyManagerFactory is used to create a X.509 key manager for the keystore. KeyManagerFactory is defined in com.sun.net.ssl.

KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(keystore, KEYPW);

Now that we have key management out of the way, we need to establish an SSLContext. An SSLContext is an environment for implementing JSSE. It is used to create a ServerSocketFactory, which is used to create an SSLServerSocket.

SSLContext sslc = SSLContext.getInstance("SSLv3");

The SSLContext is set to use SSL 3.0 instead of TLS 1.0. I tend to use SSL 3.0 because of its support among older browser. The SSLContext is initialized to work with our key manager.

sslc.init(kmf.getKeyManagers(), null, null);

Next, we create a ServerSocketFactory from the SSLContext.

ServerSocketFactory ssf = sslc.getServerSocketFactory();

And finally, we create the SSLServerSocket.

SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(serverPort);

At this point, we are not using client authentication.

serverSocket.setNeedClientAuth(requireClientAuthentication);

Listing 4. A simple HTTP server.

import java.net.*; import java.io.*; import java.util.*; // Small, simple HTTP server public class HTTPServer { String NAME; String VERSION; int serverPort; // No command line parameters are required public static void main(String args[]){ HTTPServer server = new HTTPServer("HTTPServer", "1.0", 80); server.run(); } // Create an HTTPServer for a particular TCP port public HTTPServer(String name, String version, int port) { this.NAME = name; this.VERSION = version; this.serverPort = port; } // Display name and version number public void displayVersionInfo(){ System.out.println(NAME+" version "+VERSION); } // Run until interrupted public void run() { displayVersionInfo(); try { // Get a server socket ServerSocket server = getServerSocket(); int localPort = server.getLocalPort(); // Let us know that you're listening System.out.println(NAME+" is listening on port "+localPort+"."); do { // Accept a connection Socket client = server.accept(); // Handle the connection with a separate thread (new HTTPServerThread(client)).start(); } while(true); } catch(Exception ex) {

System.out.println("Unable to listen on "+serverPort+"."); ex.printStackTrace(); System.exit(1); } } // Get a server socket on the hard-wired server port ServerSocket getServerSocket() throws Exception { return new ServerSocket(serverPort); } } // Handle a single server connection class HTTPServerThread extends Thread { Socket client; // Keep track of the client socket public HTTPServerThread(Socket client) { this.client = client; } // Thread entry point public void run() { try { // Display info about the connection describeConnection(client); // Create a stream to send data to the client BufferedOutputStream outStream = new BufferedOutputStream(client.getOutputStream()); HTTPInputStream inStream = new HTTPInputStream(client.getInputStream()); // Get the client's request HTTPRequest request = inStream.getRequest(); // Display info about it request.log(); // Sorry, we only handle gets if(request.isGetRequest()) processGetRequest(request,outStream); System.out.println("Request completed. Closing connection."); }catch(IOException ex) { System.out.println("IOException occurred when processing request."); } try { client.close(); }catch(IOException ex) { System.out.println("IOException occurred when closing socket."); } } // Display info about the connection void describeConnection(Socket client) { String destName = client.getInetAddress().getHostName(); String destAddr = client.getInetAddress().getHostAddress(); int destPort = client.getPort(); System.out.println("Accepted connection to "+destName+" (" +destAddr+")"+" on port "+destPort+"."); } // Process an HTTP GET void processGetRequest(HTTPRequest request,BufferedOutputStream outStream) throws IOException { /* If you want to use this in a secure environment then you should place some restrictions on the requested file name */ String fileName = request.getFileName(); File file = new File(fileName); // Give them the requested file if(file.exists()) sendFile(outStream,file);

else System.out.println("File "+file.getCanonicalPath()+" does not exist."); } // A simple HTTP 1.0 response void sendFile(BufferedOutputStream out,File file) { try { DataInputStream in = new DataInputStream(new FileInputStream(file)); int len = (int) file.length(); byte buffer[] = new byte[len]; in.readFully(buffer); in.close(); out.write("HTTP/1.0 200 OK\r\n".getBytes()); out.write(("Content-Length: " + buffer.length + "\r\n").getBytes()); out.write("Content-Type: text/html\r\n\r\n".getBytes()); out.write(buffer); out.flush(); out.close(); System.out.println("File sent: "+file.getCanonicalPath()); System.out.println("Number of bytes: "+len); }catch(Exception ex){ try { out.write(("HTTP/1.0 400 " + "No can do" + "\r\n").getBytes()); out.write("Content-Type: text/html\r\n\r\n".getBytes()); }catch(IOException ioe) { } System.out.println("Error retrieving "+file); } } } // Convenience class for reading client requests class HTTPInputStream extends FilterInputStream { public HTTPInputStream(InputStream in) { super(in); } // Get a line public String readLine() throws IOException { StringBuffer result=new StringBuffer(); boolean finished = false; boolean cr = false; do { int ch = -1; ch = read(); if(ch==-1) return result.toString(); result.append((char) ch); if(cr && ch==10){ result.setLength(result.length()-2); return result.toString(); } if(ch==13) cr = true; else cr=false; } while (!finished); return result.toString(); } // Get the whole request public HTTPRequest getRequest() throws IOException { HTTPRequest request = new HTTPRequest(); String line; do { line = readLine(); if(line.length()>0) request.addLine(line); else break; }while(true); return request;

} } // Used to process GET requests class HTTPRequest { Vector lines = new Vector(); public HTTPRequest() { } public void addLine(String line) { lines.addElement(line); } // Is this a GET or isn't it? boolean isGetRequest() { if(lines.size() > 0) { String firstLine = (String) lines.elementAt(0); if(firstLine.length() > 0) if(firstLine.substring(0,3).equalsIgnoreCase("GET")) return true; } return false; } // What do they want to get? String getFileName() { if(lines.size()>0) { String firstLine = (String) lines.elementAt(0); String fileName = firstLine.substring(firstLine.indexOf(" ")+1); int n = fileName.indexOf(" "); if(n!=-1) fileName = fileName.substring(0,n); try { if(fileName.charAt(0) == '/') fileName = fileName.substring(1); } catch(StringIndexOutOfBoundsException ex) {} if(fileName.equals("")) fileName = "index.htm"; if(fileName.charAt(fileName.length()-1)=='/') fileName+="index.htm"; return fileName; }else return ""; } // Display some info so we know what's going on void log() { System.out.println("Received the following request:"); for(int i=0;i<lines.size();++i) System.out.println((String) lines.elementAt(i)); } }

Listing 5. Extending the HTTP sever with SSL support.

import java.net.*; import java.io.*; import java.util.*; import java.security.*; import javax.net.*; import javax.net.ssl.*; import com.sun.net.ssl.*; public class SecureServer extends HTTPServer { String KEYSTORE = "certs";

char[] KEYSTOREPW = "serverkspw".toCharArray(); char[] KEYPW = "serverpw".toCharArray(); boolean requireClientAuthentication; public static void main(String args[]){ SecureServer server = new SecureServer(); server.run(); } public SecureServer(String name, String version, int port, boolean requireClientAuthentication) { super(name, version, port); this.requireClientAuthentication = requireClientAuthentication; } public SecureServer() { this("SecureServer", "1.0", 443, false); } ServerSocket getServerSocket() throws Exception { // Make sure that JSSE is available Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); // A keystore is where keys and certificates are kept // Both the keystore and individual private keys should be password protected KeyStore keystore = KeyStore.getInstance("JKS"); keystore.load(new FileInputStream(KEYSTORE), KEYSTOREPW); // A KeyManagerFactory is used to create key managers KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); // Initialize the KeyManagerFactory to work with our keystore kmf.init(keystore, KEYPW); // An SSLContext is an environment for implementing JSSE // It is used to create a ServerSocketFactory SSLContext sslc = SSLContext.getInstance("SSLv3"); // Initialize the SSLContext to work with our key managers sslc.init(kmf.getKeyManagers(), null, null); // Create a ServerSocketFactory from the SSLContext ServerSocketFactory ssf = sslc.getServerSocketFactory(); // Socket to me SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(serverPort); // Authenticate the client? serverSocket.setNeedClientAuth(requireClientAuthentication); // Return a ServerSocket on the desired port (443) return serverSocket; } }

Listing 6. A sample HTML file (index.htm).

<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>Welcome to Java Security using JSSE</title> </head> <body>

<h1>Welcome to Java Security using JSSE</h1> <p>This page was securely sent using SSL version 3.0.</p> </body> </html>

Securing Java Clients

Your Java applications may also require clients to support SSL. Client-side SSL is even easier to support than server-side SSL. Listing 7 presents a basic text-based Java browser. You can use it for browsing text-based Web pages. For example, if you enter java Browser http://onjava.com, you'll get a ton of HTML markup.

Listing 8 presents SecureBrowser, which extends Browser to provide SSL support. You can run SecureBrowser against a site that implements SSL, such as Sun's secure web site. For example, java SecureBrowser https://www.sun.com will establish a secure connection to Sun's web page and download a lot of markup securely.

The trick to implementing SSL on the client is to register JSSE and set the java.protocol.handler.pkgs system property so that JSSE automatically is used to handle https URLs.

Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); System.setProperty("java.protocol.handler.pkgs","com.sun.net.ssl.internal.www.protocol");

You may wonder what happens when you run SecureBrowser against SecureServer. It doesn't work. That's because SecureBrowser won't accept SecureServer's phony certificate. However, we can trick SecureBrowser into accepting SecureServer's certificate. Here's how:

1. Use

keytool

to export the server certificate from the certs keystore.

keytool -export -keystore certs -alias jamie -file server.cer Enter keystore password: serverkspw Certificate stored in file <server.cer>

2. Use keytool to create a new keystore named jssecacerts (which will be used as a truststore by SecureBrowser). Import server.cer into jssecacerts.

keytool -import -keystore jssecacerts -alias jamie -file server.cer Enter keystore password: 12345678 Owner: CN=enpower, OU=Software Development, O=Toolery.com, L=Chula

Vista, ST=CA, C=US Issuer: CN=enpower, OU=Software Development, O=Toolery.com, L=Chula Vista, ST=CA, C=US Serial number: 3ae5d0fc Valid from: Tue Apr 24 12:16:12 PDT 2001 until: Mon Jul 23 12:16:12 PDT 2001 Certificate fingerprints: MD5: A9:00:67:FF:7A:1B:D4:4A:D5:33:72:97:C5:88:0B:6D SHA1: 16:40:79:8A:11:BC:F8:AE:96:0D:FF:30:46:B5:62:0F:E2:18:56:7F Trust this certificate? [no]: y Certificate was added to keystore

3. Finally, copy jssecacerts to the lib/security subdirectory of your java.home directory. (On your client machine.)

Now SecureBrowser will use jssecacerts as a truststore to authenticate SecureServer.

When I run SecureBrowser against SecureServer, I get the following output:

java SecureBrowser https://enpower/ THE HEADERS ----------- KEY: Content-Length VALUE: 487 KEY: Content-Type VALUE: text/html THE CONTENT ----------- <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>Welcome to Java Security using JSSE</title> </head> <body> <h1>Welcome to Java Security using JSSE</h1> <p>This page was securely sent using SSL version 3.0.</p> </body> </html>

Listing 7. A Basic Java Browser.

import java.io.*; import java.net.*; import java.security.*; // A simple text-based browser public class Browser {

String urlString; // You must supply the URL to be browsed public static void main(String[] args) throws Exception { if(args.length != 1) { System.out.println("Usage: java Browser url"); System.exit(1); } Browser browser = new Browser(args[0]); browser.run(); } // Construct a browser object public Browser(String urlString) { this.urlString = urlString; } // Get the URL public void run() throws Exception { URL url = new URL(urlString); HttpURLConnection urlc = (HttpURLConnection) url.openConnection(); System.out.println("THE HEADERS"); System.out.println("-----------"); for(int i=1;;++i) { String key; String value; if((key = urlc.getHeaderFieldKey(i)) == null) break; if((value = urlc.getHeaderField(i)) == null) break; System.out.println("KEY: " + key); System.out.println("VALUE: " + value); } BufferedReader reader = new BufferedReader( new InputStreamReader(urlc.getInputStream())); String line; System.out.println("THE CONTENT"); System.out.println("-----------"); while((line = reader.readLine()) != null) System.out.println(line); } }

Listing 8. A Browser that Supports Basic SSL

import java.io.*; import java.net.*; import java.security.*; // Extend Browser to use SSL public class SecureBrowser extends Browser { // Must supply URL in command line public static void main(String[] args) throws Exception { if(args.length != 1) { System.out.println("Usage: java SecureBrowser url"); System.exit(1); } SecureBrowser browser = new SecureBrowser(args[0]); browser.run(); } // Construct a SecureBrowser public SecureBrowser(String urlString) { super(urlString);

// Register JSSE Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider()); // Here's the trick! // Simply set the protocol handler property to use SSL. System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol"); } }

Performing Mutual Authentication

In some applications, client authentication is of paramount importance.

So far, we have SecureBrowser and SecureServer running against each other. SecureServer provides its certificate to SecureBrowser and SecureBrowser authenticates SecureServer. Then they set up a secure communication channel. This scenario is still a little one-sided. How does SecureServer know that it is talking to SecureBrowser? That's where mutual authentication comes in.

Creating a Client Certificate

The first thing that we need to do is to create a certificate for our client. This is accomplished using the keytool.

keytool -genkey -keyalg rsa -alias jaworski Enter keystore password: 12345678 What is your first and last name? [Unknown]: libretto70ct What is the name of your organizational unit? [Unknown]: Software Development What is the name of your organization? [Unknown]: Toolery.com What is the name of your City or Locality? [Unknown]: Chula Vista What is the name of your State or Province? [Unknown]: CA What is the two-letter country code for this unit? [Unknown]: US Is <CN=libretto70ct, OU=Software Development, O=Toolery.com, L=Chula Vista, ST=CA, C=US> correct? [no]: y Enter key password for <jaworski> (RETURN if same as keystore password):

I created the above certificate on another computer named Libretto70CT.

The next thing that we need to do is to export the certificate. Again, we'll use the keytool.

keytool -export -alias jaworski -file jj.cer Enter keystore password: 12345678 Certificate stored in file <jj.cer>

Now, we import the certificate (jj.cer) into a keystore on the server. This keystore will function as the server's truststore.

keytool -import -alias jaworski -file jj.cer Enter keystore password: 12345678 Owner: CN=libretto70ct, OU=Software Development, O=Toolery.com, L=Chula Vista, ST=CA, C=US Issuer: CN=libretto70ct, OU=Software Development, O=Toolery.com, L=Chula Vista, ST=CA, C=US Serial number: 3ae65817 Valid from: Tue Apr 24 21:52:39 PDT 2001 until: Mon Jul 23 21:52:39 PDT 2001 Certificate fingerprints: MD5: BA:28:EC:44:B9:01:AA:6A:AF:3B:87:CB:73:26:6A:78 SHA1: 71:B5:C1:F3:88:78:1E:33:2F:0B:66:60:8D:20:71:E5:6D:89:71:00 Trust this certificate? [no]: y Certificate was added to keystore

The keystore that is created from the above command is the default keystore named .keystore. Rename it to jssecacerts and put it in the lib/security subdirectory of your java.home directory. This will make your server aware of the client's key.

Modifying SecureServer and SecureBrowser

We want SecureBrowser to present its certificate to SecureServer so that SecureServer can also authenticate SecureBrowser. The change required on SecureServer's part is minimal. Simply change the following line in SecureServer's constructor from

this("SecureServer", "1.0", 443, false);

to

this("SecureServer", "1.0", 443, true);

This turns on mutual authentication in SecureServer.

On the client side, we must tell the underlying SSL implementation which keystore to use and which password to use to access the keystore. This amounts to setting the javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword properties. On my Libretto, the default keystore that was created is named .keystore and is located in the C:\Windows directory. Simply add the following two lines to the bottom of the SecureBrowser constructor.

System.setProperty("javax.net.ssl.keyStore","c:\\windows\\.keystore"); System.setProperty("javax.net.ssl.keyStorePassword","12345678");

Seeing it in Action

All that's left is to restart SecureServer and access it with SecureBrowser. Make sure that you give SecureServer a minute or two to start up. Then startup SecureBrowser on your client machine. (I recommend using separate machines because it simplifies the process of managing keystores and truststores.

java SecureBrowser https://enpower/ THE HEADERS ----------- KEY: Content-Length VALUE: 487 KEY: Content-Type VALUE: text/html THE CONTENT ----------- <?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>Welcome to Java Security using JSSE</title> </head> <body> <h1>Welcome to Java Security using JSSE</h1> <p>This page was securely sent using SSL version 3.0.</p> </body> </html>

SecureServer reports the transaction as follows:

java SecureServer SecureServer version 1.0 SecureServer is listening on port 443. Accepted connection to LIBRETTO70CT (192.168.1.70) on port 1098. Received the following request: GET / HTTP/1.1 User-Agent: Java1.3.0_01 Host: enpower Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive File sent: C:\WINDOWS\Desktop\JSSE\index.htm Number of bytes: 487 Request completed. Closing connection.

When you are finished with these examples, go back and delete all of the keystores that you've created to keep any trusted certificates from laying around on your system.

Jamie Jaworski is a Java security expert and author, focused on cryptography and such.