Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Secure Internet Programming with Java 2, Standard Edition (J2SE) 1.4

 
 

Articles Index


Any information transmitted over computer networks, or the Internet, is subject to interception. Some of that information could be sensitive, such as credit card numbers and other personal data. To make the Internet more useful in an enterprise setting and for e-commerce, applications must protect their users' information, using encryption, authentication, and secure communications protocols. The secure Hypertext Transfer Protocol (HTTPS), which is HTTP over the Secure Sockets Layer (SSL), is already being used successfully for e-commerce applications.

The Java Secure Socket Extension (JSSE), which is a set of Java packages that enable secure Internet communications, is a framework and 100% Pure Java implementation of the Secure Socket Layer (SSL). These packages enable you ,the Java developer, to develop secure network applications that feature the secure passage of data between a client and a server running any application protocol, such as HTTP, FTP, Telnet, or NTTP, over TCP/IP.

The good news is that JSSE has been integrated into the Java 2 SDK, Standard Edition, version 1.4 (J2SE 1.4). This means if you have J2SE 1.4 installed, then you can build secure Internet applications based on SSL without downloading any additional packages. This two-part series of articles provides a hands-on tutorial explaining how to develop secure Internet applications for today's and tomorrow's market. This article is concerned with the server-side and the next article will be concerned with the client-side. This article starts by presenting a detailed overview of SSL, then shows you how to:

  • Use the JSSE APIs
  • Integrate SSL into your existing client-server applications
  • Develop a simple HTTP server
  • Revise the HTTP server to handle HTTPS requests
  • Generate your own certificates using the keytool delivered with J2SE
  • Develop, configure, and run a secure HTTP server

Overview of SSL

The SSL protocol, which was developed by Netscape in 1994, allows clients (Web browsers, typically) and HTTP servers to communicate over a secure connection. It offers encryption, source authentication, and data integrity as means to protect information exchanged over insecure, public networks. There are several versions of SSL: SSL 2.0 has security weaknesses and is hardly used today; SSL 3.0 is universally supported; and finally the Transport Layer Security (TLS), which is an improvement on SSL 3.0, has been adopted as an Internet standard and is supported by almost all recent software.

Encryption protects data from unauthorized use by converting it to an apparently meaningless form before transmission. The data is encrypted by one side (the client or the server), transmitted, decrypted by the other side, then processed.

Source authentication is a method of verifying the data sender's identity. The first time a browser or other client attempts to communicate with a Web server over a secure connection, the server presents the client with a set of credentials in the form of a certificate.

Certificates are issued and validated by trusted authorities known as certification authorities (CAs). A certificate represents the public-key identity of a person. It is a signed document that says: I certify that the public key in this document belongs to the entity named in this document. Signed (certificate authority). Well-known CAs include Verisign, Entrust, and Thawte. Note that the certificates used with SSL/TLS today are X.509 certificates.

Data integrity refers to means of ensuring that data has not been modified in transit.

SSL and the TCP/IP Protocol Stack

As the name Secure Sockets Layer indicates, SSL connections act like sockets connected by TCP. Therefore, you can think of SSL connections as secure TCP connections since the place for SSL in the protocol stack is right above TCP and below the application layer as shown in Figure 1. It is important to note, however, that SSL doesn't support some of the TCP features such as out-of-band data.

figure 1
Figure 1: SSL and the TCP/IP protocol stack

Negotiable Encryption

Among the features of SSL that have made it the de facto standard vehicle for secure e-commerce transactions is its support for negotiable encryption and authentication algorithms. The designers of SSL realized that not all parties will use the same client software and consequently not all clients will include any particular encryption algorithm. The same is true for servers. The client and server at the two ends of a connection negotiate the encryption and decryption algorithms (cipher suites) during their initial handshake. It may turn out that they do not have sufficient algorithms in common, in which case the connection attempt will fail.

Note that while SSL allows both the client and the server to authenticate each other, typically only the server is authenticated in the SSL layer. Clients are customarily authenticated in the application layer, through the use of passwords sent over an SSL-protected channel. This pattern is common in banking, stock trading, and other secure Web applications.

The SSL full handshake protocol is illustrated in Figure 2. It shows the sequences of messages exchanged during the SSL handshake.

figure 2
Figure 2: SSL handshake protocol

These messages mean:

  1. ClientHello: The client sends the server information such as SSL protocol version, session id, and cipher suites information such cryptographic algorithms and key sizes supported.
  2. ServerHello: The server chooses the best cipher suite that both the client and server support and sends this information to the client.
  3. Certificate: The server sends the client its certificate which contains the server's public key. While this message is optional, it is used when server authentication is required. In other words, it is used to confirm the server's identity to the client.
  4. Certificate Request: This message is sent only if the server requires the client to authenticate itself. Most e-commerce applications do not require the client to authenticate itself.
  5. Server Key Exchange: This message is sent if the certificate, which contains the server's public key, is not sufficient for key exchange.
  6. ServerHelloDone: This message informs the client that the server finished the initial negotiation process.
  7. Certificate: This message is sent only if the server requested the client to authenticate itself.
  8. Client Key Exchange: The client generates a secret key to be shared between the client and server. If the Rivest-Shamir-Adelman (RSA) encryption algorithm is used, the client encrypts the key using the server's public key and sends it to the server. The server uses its private or secret key to decrypt the message and retrieves the shared secret key. Now, client and server share a secret key that has been distributed securely.
  9. Certificate Verify: If the server requested to authenticate the client, this message allows the server to complete the authentication process.
  10. Change Cipher Spec: The client asks the server to change to encrypted mode.
  11. Finished: The client tells the server it is ready for secure communication.
  12. Change Cipher Spec: The server asks the client to change to encrypted mode.
  13. Finished: The server tells the client it is ready for secure communication. This marks the end of the SSL handshake.
  14. Encrypted Data: The client and server can now start exchanging encrypted messages over a secure communication channel.

JSSE

The Java Secure Socket Extension (JSSE) provides a framework and a 100% Pure Java implementation of the SSL and TLS protocols. It provides mechanisms for data encryption, server authentication, message integrity, and optional client authentication. What is fascinating about JSSE is that is abstracts the complex underlying cryptographic algorithms and thus minimizes the risk of creating subtle and dangerous security vulnerabilities. In addition, it makes the development of secure applications quite simple by allowing you to seamlessly integrate SSL into your applications. The JSSE framework is capable of supporting many different secure communication protocols such as SSL 2.0 and 3.0 and TLS 1.0, but the J2SE v1.4.1 implements SSL 3.0 and TLS 1.0.

Programming with JSSE

The JSSE APIs supplement the java.security and java.net packages by providing extended networking socket classes, trust and key managers, and a socket factory framework for encapsulating socket creation behavior. These classes are included in the packages javax.net and javax.net.ssl.

SSLSocket and SSLServerSocket

The javax.net.ssl.SSLSocket is a subclass of the java.net.Socket class. Therefore, it supports all the standard Socket methods and adds additional methods specific to secure sockets. The javax.net.ssl.SSLServerSocket class is analogous to the SSLSocket class except that it is used to create server sockets.

Creating an instance of SSLSocket can be done in two ways:

  1. As an instance of SSLSocketFactory by invoking one of the createSocket methods on that class.
  2. Through the accept method on the SSLServerSocket.

SSLSocketFactory and SSLServerSocketFactory

The javax.net.ssl.SSLSocketFactory class is an object factory for creating secure sockets, and the javax.net.ssl.SSLServerSocketFactory is an object factory for creating server sockets.

An SSLSocketFactory instance can be obtained in two ways:

  1. Get the default factory by calling SSLSocketFactory.getDefault.
  2. Construct a new factory with specified configured behavior.

Note that the default factory is configured to enable server authentication only.

Making Existing Client/Server Applications Secure

Incorporating SSL into existing client/server applications to make them secure can be easily done using a few lines of JSSE code. The lines highlighted in bold in the following example show the code necessary to make a server secure:

import java.io.*;
import javax.net.ssl.*;

public class Server {
   int port = portNumber;
   SSLServerSocket server;
   try {
      SSLServerSocketFactory factory = 
        (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
      server = (SSLServerSocket) 
        factory.createServerSocket(portNumber);
      SSLSocket client = (SSLSocket) 
        server.accept();
      
      // Create input and output streams as usual
      // send secure messages to client through the 
      // output stream
      // receive secure messages from client through 
      // the input stream
   } catch(Exception e) {
   }
}

The lines highlighted in bold in the following example show the code necessary to make a client secure:

import java.io.*;
import javax.net.ssl.*;

public class Client {
   ...
   try {
      SSLSocketFactory factory = (SSLSocketFactory)
        SSLSocketFactory.getDefault();
      server = (SSLServerSocket) 
        factory.createServerSocket(portNumber);
      SSLSocket client = (SSLSOcket) 
        factory.createSocket(serverHost, port);
      
      // Create input and output streams as usual
      // send secure messages to server through the 
      // output stream receive secure
      // messages from server through the input stream
   } catch(Exception e) {
   }
}



SunJSSE Provider

The J2SE v1.4.1 release comes with a JSSE provider, SunJSSE, that comes installed and pre-registered with the Java Cryptography Architecture. Think of the SunJSSE provider as the name of the implementation. It supplies implementations of the SSL v3.0 and TLS v1.0 as well as most common SSL and TLS cipher suites. If you wish to find the list of cipher suites that are supported by your implementation of SSL (SunJSSE in this case), make a call to the getSupportedCipherSuites method in SSLSocket. Not all of these cipher suites, however, might be enabled. To find out which ones are enabled, call the method getEnabledCipherSuites. The list can be modified by calling setEnabledCipherSuites.

A Complete Example

I found that the most complex issue when working with JSSE is related to system configuration and managing certificates and keys. Throughout this example, I demonstrate how to develop, configure, and run a complete HTTP server application that supports the GET request method.

Overview of HTTP

The Hypertext Transfer Protocol (HTTP) is a request-reply application protocol. This protocol supports a fixed set of methods such as GET, POST, PUT, DELETE, etc. The GET method is commonly used to request resources from a Web server. Here are two sample GET requests:

GET / HTTP/1.0 <empty-line>
GET /names.html HTTP/1.0 <empty-line>

Insecure HTTP Server

In order to develop an HTTP server, you need to understand how the HTTP protocol works. This server, however, is simple since it only supports the GET request method. A sample implementation is shown in Code Sample 1. This is a multi-threaded HTTP server where the ProcessConnection class is used to run each new request in a different thread. When the server receives a request from the browser, it parses the request to find out which document is being requested. If the requested document is available on the server, the shipDocument method is used to send the requested document to the server. If the document is not found, an error message is sent to the server.

Code Sample 1: HttpServer.java

import java.io.*;
import java.net.*;
import java.util.StringTokenizer;

/** 
 * This class implements a multithreaded simple HTTP 
 * server that supports the GET request method.
 * It listens on port 44, waits client requests, and 
 * serves documents.
 */

public class HttpServer {
   // The port number which the server 
   // will be listening on
   public static final int HTTP_PORT = 8080;

   public ServerSocket getServer() throws Exception {
      return new ServerSocket(HTTP_PORT);
   }

   // multi-threading -- create a new connection 
   // for each request
   public void run() {
      ServerSocket listen;
      try {
         listen = getServer();
         while(true) {
            Socket client = listen.accept();
            ProcessConnection cc = new 
              ProcessConnection(client);
         }
      } catch(Exception e) {
         System.out.println("Exception: 
           "+e.getMessage());
      }
   }

   // main program
   public static void main(String argv[]) throws 
     Exception {
      HttpServer httpserver = new HttpServer();
      httpserver.run();
   }
}


class ProcessConnection extends Thread {
   Socket client;
   BufferedReader is;
   DataOutputStream os;
   
   public ProcessConnection(Socket s) { // constructor
      client = s;
      try {
         is = new BufferedReader(new InputStreamReader
           (client.getInputStream()));
         os = new DataOutputStream(client.getOutputStream());
      } catch (IOException e) {
         System.out.println("Exception: "+e.getMessage());
      }
      this.start(); // Thread starts here...this start() 
        will call run()
   }
 
   public void run() {
      try {
         // get a request and parse it.
         String request = is.readLine();
         System.out.println( "Request: "+request );
         StringTokenizer st = new StringTokenizer( request );
            if ( (st.countTokens() >= 2) && 
              st.nextToken().equals("GET") ) {
               if ( (request = 
                 st.nextToken()).startsWith("/") )
                  request = request.substring( 1 );
               if ( request.equals("") )
                  request = request + "index.html";
               File f = new File(request);
               shipDocument(os, f);
            } else {
               os.writeBytes( "400 Bad Request" );
            } 
            client.close();
      } catch (Exception e) {
         System.out.println("Exception: " + 
           e.getMessage());
      } 
   }

  /**
   * Read the requested file and ships it 
   * to the browser if found.
   */
   public static void shipDocument(DataOutputStream out, 
     File f) throws Exception {
       try {
          DataInputStream in = new 
            DataInputStream(new FileInputStream(f));
          int len = (int) f.length();
          byte[] buf = new byte[len];
          in.readFully(buf);
          in.close();
          out.writeBytes("HTTP/1.0 200 OK\r\n");
          out.writeBytes("Content-Length: " + 
            f.length() +"\r\n");
          out.writeBytes("Content-Type: 
            text/html\r\n\r\n");
          out.write(buf);
          out.flush();
       } catch (Exception e) {
          out.writeBytes("<html><head><title>error</title>
            </head><body>\r\n\r\n");
          out.writeBytes("HTTP/1.0 400 " + e.getMessage() + "\r\n");
          out.writeBytes("Content-Type: text/html\r\n\r\n");
          out.writeBytes("</body></html>");
          out.flush();
       } finally {
          out.close();
       }
   }
}

To experiment with the HttpServer class:

  1. Copy HttpServer and save it in a file called HttpServer.java in a directory of your choice.
  2. Compile the HttpServer.java using javac.
  3. Create some sample HTML files including "index.html", which is the default document served in this example
  4. Run the HttpServer. The server runs on port 8080.
  5. Open a web browser and make a request such as http://localhost:8080 or http://127.0.0.1:8080/index.html.

Note: Can you think of any malicious URLs that the HttpServer can handle? What about something like http://serverDomainName:8080/../../etc/passwd or http://serverDomainName:8080//somefile.txt. As an exercise, modify HttpServer so that such URLs are not allowed. Hint: write your own customer SecurityManager or use java.lang.SecurityManager. You can install this security manager by adding the statement System.setSecurityManager(new Java.lang.SecurityManager) as the first line in the main method. Try it!

Extending HttpServer to Handle https:// URLs

Now, let's modify the HttpServer class, making it secure. I'd like the HTTP server to be capable of handling https:// URLs. As I mentioned earlier, JSSE allows you to integrate SSL into your applications quite easily.

Creating a Server Certificate

As I mentioned earlier, SSL uses certificates for authentication. Certificates must be created for clients and servers that need to communicate securely using SSL. JSSE uses certificates created using the Java keytool shipped with J2SE. I used the following command to create an RSA certificate for the HTTP server.

prompt> keytool -genkey -keystore serverkeys -keyalg rsa -alias qusay

This command will generate a certificate referenced by the alias qusay, and will be stored in a file named serverkeys. The tool prompted me for information to generate the certificate. The information I entered is highlighted in bold:

Enter keystore password:  hellothere
What is your first and last name?
  [Unknown]:  ultra.domain.com
What is the name of your organizational unit?
  [Unknown]:  Training and Consulting
What is the name of your organization?
  [Unknown]:  javacourses.com
What is the name of your City or Locality?
  [Unknown]:  Toronto
What is the name of your State or Province?
  [Unknown]:  Ontario
What is the two-letter country code for this unit?
  [Unknown]:  CA
Is CN=ultra, OU=Training and Consulting, 
O=javacourses.com, L=Toronto, ST=Ontario, C=CA correct?
  [no]:  yes

Enter key password for <qusay>
        (RETURN if same as keystore password):  hiagain

As you can see, the keytool prompted me to enter a password for the keystore meaning that in order for the server to access the keystore it must know that password. Also, the tool asked me to enter a password for the alias. If you like, such password information can be related on the keytool command line using the options -storepass and -keypass. Note that I used the name "ultra.domain.com" for the first and last name. This name is the hypothetical name of my machine. You should enter the hostname or the IP address of the server's machine.

When you run the keytool command, it may take a few seconds to generate the certificate depending on the speed of your machine.

Once a certificate for my server has been generated, I can revise my HttpServer to make it secure. If you examine the HttpServer class, you'll notice that the getServer method is used to return a server socket. This means, the only method I need to modify is the getServer method so that it returns a secure server socket. The changes are highlighted in bold in Code Sample 2. Notice that I have changed the port number to 443. This is the default port number for HTTPs. It is important to note that port numbers between 0 and 1023 are reserved. If you run HttpsServer on a different port number, the url should be: https://localhost:portnumber but if you run it on 443 then the URL is: https://localhost.

Code Sample 2: HttpsServer.java

import java.io.*;
import java.net.*;
import javax.net.*;
import javax.net.ssl.*;
import java.security.*;
import java.util.StringTokenizer;

/** 
 * This class implements a multithreaded simple HTTPS 
 * server that supports the GET request method.
 * It listens on port 44, waits client requests
 * and serves documents.
 */

public class HttpsServer {

   String keystore = "serverkeys";
   char keystorepass[] = "hellothere".toCharArray();
   char keypassword[] = "hiagain".toCharArray();

   // The port number which the server will be listening on
   public static final int HTTPS_PORT = 443;

    
   public ServerSocket getServer() throws Exception {

      KeyStore ks = KeyStore.getInstance("JKS");
      ks.load(new FileInputStream(keystore), keystorepass);
      KeyManagerFactory kmf = 
        KeyManagerFactory.getInstance("SunX509");
      kmf.init(ks, keypassword);
      SSLContext sslcontext = 
        SSLContext.getInstance("SSLv3");
      sslcontext.init(kmf.getKeyManagers(), null, null);
      ServerSocketFactory ssf = 
        sslcontext.getServerSocketFactory();
      SSLServerSocket serversocket = (SSLServerSocket) 
        ssf.createServerSocket(HTTPS_PORT);
      return serversocket;

    }


   // multi-threading -- create a new connection 
   // for each request
   public void run() {
      ServerSocket listen;
      try {
         listen = getServer();
         while(true) {
            Socket client = listen.accept();
            ProcessConnection cc = new 
              ProcessConnection(client);
         }
      } catch(Exception e) {
         System.out.println("Exception: "+e.getMessage());
      }
   }

   // main program
   public static void main(String argv[]) throws Exception {
      HttpsServer https = new HttpsServer();
      https.run();
   }
}

The lines:

String keystore = "serverkeys";
char keystorepass[] = "hellothere".toCharArray();
char keypassword[] = "hiagain".toCharArray();

specify the name of the keystore, its password, and the key password. Hardcoding the passwords into the code is not a good idea for production code, however. They can be specified on the command line when running the server.

The rest of the JSSE related code is in the getServer method:

  • It access the serverkeys keystore. The JKS is the Java KeyStore (a type of keystore created by keytool).
  • The KeyManagerFactory is used to create an X.509 key manager for the keystore.
  • An SSLContext is an environment for implementing JSSE. It is used to create a ServerSocketFactory that in turn used to create a SSLServerSocket. Although we specify SSL 3.0, the implementation that is returned will often support other protocol versions, such as TLS 1.0. Older browsers, however, use SSL 3.0 more widely.

Note that by default client authentication is not required. For if you wish for your server to require client authentication, use: serversocket.setNeedClientAuth(true).

To experiment with the HttpsServer class:

  1. Copy the HttpsServer and ProcessConnection classes into a file named HttpsServer.java
  2. Save this file in the same directory where the serverkeys file was created by the keytool
  3. Compile the HttpsServer.java using javac
  4. Run the HttpsServer. By default it runs on port 443, but if you cannot start it on this port, choose another port number greater than 1024.
  5. Open a web browser and enter the request: https://localhost or https://127.0.0.1. This assumes the server is running on port 443. If not, then use: https://localhost:port

When you enter an https:// URL in the browser, you get a security alert popup window like the one in Figure 3. This is because the HTTP server certificate was self-generated. In other words, it was generated by an unknown certification authority, one that was not found among the certification authorities your browser keeps in its store. You have the option to view the certificate (check whether it is a proper certificate and discover who signed it) and then install it, reject the certificate, or accept the certificate.

figure 3
Figure 3: Server certificate issued by an unknown certification authority

Note: Generating your own certificate is fine for internal private systems. For public systems, however, it is a good idea to get a certificate from a well known Certification Authority in order to avoid the browser security alert.

If you accept the certificate you will be able to see the page behind the secure connection, and future access to the same Web site will not cause the browser to issue a security alert. Note that there are many Web sites that use HTTPS whose certificates were either self-generated or generated by unknown CAs. As an example, try to visit: https://www.jam.ca. If you have never visited this Web site, you will see a security alert like the one in Figure 3.

Note: When you accept the certificate, it is only for that session. In other words, once you completely exit the browser it is forgotten. Both Netscape and Microsoft Internet Explorer (MSIE) allow you to install a certificate permanently. To do this in MSIE, select "View Certificate" from Figure 3 and from the new window select "Install Certificate".

Conclusion

This article presented a detailed overview of SSL and described the JSSE framework and implementation. The examples presented in this article show how easy it is to seamlessly integrate SSL into your client/server applications. This article presented a secure http server that you can use as a base for experimentation. Stay tuned for more information on the JSSE APIs and a web browser capable of handling HTTPS requests.

For More Information

Acknowledgments

Special thanks to Andreas Sterbenz of Sun Microsystems whose feedback helped me improve the article.

About the Author

Qusay H. Mahmoud provides Java consulting and training services. He has published dozens of articles on Java, and is the author of Distributed Programming with Java (Manning Publications, 1999) and Learning Wireless Java (O'Reilly, 2002).