ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Secure Your Sockets with JSSE
Pages: 1, 2, 3, 4, 5

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>


Pages: 1, 2, 3, 4, 5

Next Pagearrow