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>