13.5 Examples

 < Day Day Up > 

The SSL support built into a J2EE container may sometimes be insufficient to address the security requirements of a Web application. J2EE programs with the possibility to engage in SSL sessions autonomously are more flexible and may allow addressing those security requirements that are not met by the hosting J2EE product. For example, a servlet may need to contact a remote program to retrieve a secret number and then transfer that number to a client, as shown in Figure 13.3.

Figure 13.3. Scenario Favorable for Using SSL from a Servlet

graphics/13fig03.gif

Although the HTTP communication between the servlet and the client can be protected via HTTPS declaratively , this solution may not be an option in the communication between the servlet and the remote secret-number generator. To protect this communication, the servlet and the secret-number generator need to establish an SSL connection independently of the servlet container. The sample code in this section demonstrates this scenario, showing how to use the JSSE API from within a servlet.

The code in this section also shows how to convert an unsecure socket connection to a secure socket connection that uses the SSL protocol. In fact, in many cases, programs need to be modified because they were originally written without thinking of any security implications or because security requirements arose afterward. In the scenario we describe, a possibility could be to retrieve the secret number from the remote server through a regular socket connectionone that does not use any form of encryption. In such a case, however, eavesdroppers could get at the random number easily and even change it and communicate a different value to the servlet. It is therefore necessary to activate a form of protection, such as SSL, to guarantee data integrity and authenticity.

13.5.1 Basic Scenario without SSL

The sample code in Listing 13.1 shows how to implement a servlet, SecretNumberServlet, that communicates with an external server using an unsecure socket. The SecretNumberServlet program is activated when its init() method is executed by the servlet container. The servlet container communicates to the servlet the host name of the external server responsible for generating the secret number and the port number on which the external server is listening. When a client contacts the servlet, the servlet's service() method is invoked. At that point, the servlet opens an unsecure socket connection with the external server, retrieves the secret number, and communicates it back to the client through the servlet container.

As we have already observed , the communication between the client and the servlet can be protected using HTTPS. However, the communication between the servlet and the external server must be protected programmatically. The example in this section does not do that. The servlet and the external server communicate using a regular TCP/IP socket connection. Therefore, use of this servlet is indicated in those network topologies in which the servlet container and external server are located in a protected networkone in which wire tapping is unfeasible. Section 13.5.2 on page 468 shows how the code presented in this section can be made secure using SSL.

Listing 13.1. SecretNumberServlet.java
 import java.io.InputStream; import java.io.IOException; import java.io.DataInputStream; import java.net.Socket; import java.net.UnknownHostException; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /**  * This servlet connects to an external server, retrieves  * from it a secret number, and communicates it to the  * client. The communication between the servlet and the  * external server is not protected. Therefore, usage of  * this servlet is indicated when the servlet container and  * external server are located in the same intranet.  */ public class SecretNumberServlet extends HttpServlet {    private String server;    private int port;    /**     * Initializes the servlet by setting the initialization     * parameters: the external server host name and port     * number.     *     * @param config a ServletConfig object used by the     *        servlet container to pass information to the     *        servlet during initialization.     * @throws ServletException if the servlet intialization     *         cannot be executed.     */    public void init(ServletConfig config)       throws ServletException    {       super.init(config);       try       {          port = (new Integer(getInitParameter("port"))).             intValue();       }       catch (NumberFormatException nfe)       {          log("<port> must be a parsable integer");          throw new ServletException(nfe.getMessage());       }       String server = getInitParameter("server");    }    /**     * Opens a socket connection with the external server,     * retrieves the secret number generated from the     * external server, and communicates it to the client.     * This method is executed each time a client invokes     * the servlet.     *     * @param request an HttpServletRequest object     *        containing information about the client's     *        request.     * @param response an HttpServletResponse object in     *        which the servlet stores information to be     *        sent to the client.     * @throws ServletException if an exception occurs that     *         interferes with the servlet's normal     *         operation.     * @throws IOException if an I/O exception occurs.     */    public void service(HttpServletRequest request,       HttpServletResponse response)       throws ServletException, IOException    {       log("Requesting connection from " + server +          " on port " + port + "...");       Socket s = null;       try       {          // Open socket connection with the external          // server.          s = new Socket(server, port);       }       catch (UnknownHostException uhe)       {          log("Server " + server + " is unknown");          throw new ServletException(uhe.getMessage());       }       log("Connected to server " + server);       // Get the secret number from the external server.       InputStream in = s.getInputStream();       DataInputStream dis = new DataInputStream(in);       int secretNumber = dis.readInt();       // Send the response to the client in HTML format.       ServletOutputStream out =          response.getOutputStream();       response.setContentType("text/html");       out.println("<HTML><HEAD><TITLE>" +          "Secret Number Page</TITLE></HEAD></HTML>");       out.println("<BODY><H1>Secret Number: " +          secretNumber + "</H1></BODY>");       out.println("</HTML>");       dis.close();       in.close();       s.close();    } } 

The servlet SecretNumberServlet in Listing 13.1 can be deployed in any J2EE-compliant Web container. During deployment, the initialization parameters server and port must be set to the fully qualified host name of the server machine and the number of the port on which the server is listening, respectively.

Listing 13.2 shows the code of the server program responsible for generating random numbers and communicating them to its clients .

Listing 13.2. Server.java
 import java.io.OutputStream; import java.io.IOException; import java.io.DataOutputStream; import java.util.Random; import java.net.Socket; import java.net.ServerSocket; /**  * Server class for random number generation. This server  * runs as a process that external client applications can  * contact by opening an appropriate socket. Every time a  * client contacts the server application, a new Thread is  * generated. The server application produces a new random  * number, communicates it to the client, and closes the  * connection with the client.  */ public class Server extends Thread {    private Socket client;    private static Random randomGenerator = new Random();    /**     * Public constructor. Initializes the server by setting     * the Socket to communicate with the client.     *     * @param client a Socket object representing the     *        client application this server is     *        communicating with.     */    public Server(Socket client)    {       this.client = client;    }    /**     * Generates a random number and communicates it to the     * the client.     */    public void run()    {       int secretNumber = randomGenerator.nextInt();       System.out.println("Secret Number: " + secretNumber);       try       {          OutputStream out = client.getOutputStream();          DataOutputStream dos = new DataOutputStream(out);          dos.writeInt(secretNumber);          dos.flush();          dos.close();          out.close();          client.close();       }       catch(IOException e)       {          System.out.println(e);       }    }    /**     * Launches the server application. This method expects     * the port number to be passed on the command line.     *     * @param args a String[] array whose first element     *        must represent the port number this server     *        will be listening on. The port number is     *        expected to be an integer between 1025 and     *        65536. Values not in this range will cause the     *        application to quit.     */    public static void main(String args[])    {       if (args.length == 0)       {          System.out.println("Usage: java Server <port>");          System.exit(0);       }       int port = 0;       try       {          port = (new Integer(args[0])).intValue();       }       catch (NumberFormatException nfe)       {          System.out.println("Usage: java Server <port>");          System.out.println             ("<port> must be a parsable integer");          System.exit(0);       }       if (port <= 1024  port > 65536)       {          System.out.println("Usage: java Server <port>");          System.out.println             ("<port> must be an integer in the range " +             "1025-65536");          System.exit(0);       }       System.out.println("Server starting...");       ServerSocket ss = null;       // Try to start the server. At this point, problems       // may arise if another process is already listening       // on the selected port.       try       {          ss = new ServerSocket(port);       }       catch (IOException ioe)       {          System.out.println             ("There is already a server running on port " +             port + "\n" + ioe);          System.exit(0);       }       System.out.println          ("Server started on port " + port);       System.out.println("Waiting for clients...");       // Start an endless loop in which the server is       // constantly waiting for clients to connect.       while (true)       {          Socket client = null;          try          {             client = ss.accept();          }          catch (IOException ioe)          {             System.out.println("Unable to accept " +                "connection from client\n" + ioe);             System.exit(0);          }          System.out.println             ("Request from client received...");          Server server = new Server(client);          server.start();       }    } } 

The server code in Listing 13.2 can be launched from the command line by entering the command

  java Server   port  

where port is a valid TCP port number not already in use in the range, 102565536.

13.5.2 Scenarios with SSL

This section shows how the code shown in Section 13.5.1 on page 463 can be modified so that the communication between the servlet and the external server is protected by the SSL protocol. Both the servlet and the server application that we are going to present here use the SSL support provided through the JSSE API.

The code presented in this section has the same structure as the code in Section 13.5.1. However, to use secure sockets, both the servlet and the external server need to use an SSL socket factory. The servlet uses the javax.net.ssl.SSLSocketFactory class to generate a javax.net.ssl.SSLSocket object and communicate with the external server. The external server uses the javax.net.ssl.SSLServerSocketFactory class to generate a javax.net.ssl.SSLServerSocket and communicate with the servlet. Because SSL is used to protect the communication between the servlet and the external servlet, the secret number generated by the external servlet is encrypted before being sent to the servlet and is transmitted in a confidential manner.

The external server that generates the secret number is a regular Java application that can run on any J2SE platform. As we noticed in footnote 7 on page 460, starting with J2SE V1.4, JSSE is an integral part of the J2SE platform. Therefore, no modification is necessary to a J2SE V1.4 platform to enable the external server to run on it. However, JSSE is not an integral part of a J2EE platform. Therefore, to enable the servlets presented in this section to make use of JSSE, the Web container hosting the servlets must have the JSSE API installed and available in the Java runtime class path with the necessary authorizations granted. Typically, the JSSE is added as an extension. Additionally, a JSSE provider must be installed and configured for use by the J2EE programs that need to establish SSL connections (see Listing 11.1 on page 384 and Section 11.1.3.3 on page 387).

13.5.2.1 Scenario with No Authentication

In this section, we assume for simplicity that the external server does not require any authentication. (In Section 13.5.2.2 on page 476 and Section 13.5.2.3 on page 484, we show how to enforce SSL authentication.) Additionally, as in this example, we do not provide certificates for the external server that generates secret numbers, we enable the anonymous cipher suite SSL_DH_anon_EXPORT_ WITH_DES40_CBC_SHA in both the client and the server. Anonymous cipher suites are supported by most JSSE providers even though not all the JSSE providers enable them. [8] As we said in step 3 on page 453, neither the client nor the server is authenticated in anonymous cipher suites.

[8] Sun Microsystems' JSSE provider supports anonymous cipher suites, which by default are enabled. However, anonymous cipher suites do not enforce authentication. Therefore, they are not considered secure. For this reason, IBM's JSSE provider supports anonymous cipher suites but by default does not enable them. Someone who wants to use anonymous cipher suites with the IBM JSSE provider will have to write a trust manager (see Section 13.4.2 on page 461).

Enabling an additional cipher suite is accomplished through the following steps.

  1. All the enabled cipher suites, in the form of an array of String objects, are obtained from the SSLSocket representing the communication. The SSLSocket class offers the getEnabledCipherSuites() method for this purpose.

  2. The additional cipher suite is added to the array.

  3. The array of cipher suite Strings augmented with the additional cipher suite is passed as a parameter to the SSLSocket 's setEnabledCipherSuites() method.

When the cipher suite that the client and the server negotiate during the SSL handshake is not an anonymous cipher suite, the server can disable SSL client authentication by passing the boolean false to the setNeedClientAuth() method on the SSLServerSocket object responsible for the communication with the client.

The servlet code is shown in Listing 13.3.

Listing 13.3. SSLSecretNumberServlet.java
 import java.io.InputStream; import java.io.IOException; import java.io.DataInputStream; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /**  * This servlet connects to an external server, retrieves  * from it a secret number, and communicates it to the  * client. The communication between the servlet and the  * external server is SSL-protected. Therefore, usage of  * this servlet is indicated when the information exchanged  * between the servlet container and external server demands  * confidentiality.  */ public class SSLSecretNumberServlet extends HttpServlet {    private String server;    private int port;    /**     * Initializes the servlet by setting the initialization     * parameters, the external server host name and port     * number.     *     * @param config a ServletConfig object used by the     *        servlet container to pass information to the     *        servlet during initialization.     * @throws ServletException if the servlet initialization     *         cannot be executed.     */    public void init(ServletConfig config)       throws ServletException    {       super.init(config);       try       {          port = (new Integer(getInitParameter("port"))).             intValue();       }       catch (NumberFormatException nfe)       {          log("<port> must be a parsable integer");          throw new ServletException(nfe.getMessage());       }       String server = getInitParameter("server");    }    /**     * Opens a secure socket connection with the external     * server, retrieves the secret number generated from     * the external server, and communicates it to the     * client. This method is executed each time a client     * invokes the servlet.     *     * @param request an HttpServletRequest object     *        containing information about the client's     *        request.     * @param response an HttpServletResponse object in     *        which the servlet stores information to be     *        sent to the client.     * @throws ServletException if an exception occurs that     *         interferes with the servlet's normal     *         operation.     * @throws IOException if an I/O exception occurs,     *         preventing this method from executing.     */    public void service(HttpServletRequest request,       HttpServletResponse response)       throws ServletException, IOException    {       log("Requesting connection from " + server +          " on port " + port + "...");       SSLSocket s = null;       SSLSocketFactory sslFact = (SSLSocketFactory)          SSLSocketFactory.getDefault();       try       {          // Open socket connection with the external server.          s = (SSLSocket) sslFact.createSocket(server, port);       }       catch (IOException ioe)       {          log("Server " + server + " is unknown");          throw new ServletException(ioe.getMessage());       }       // Enable anonymous cipher suite       String[] cipherSuites = s.getEnabledCipherSuites();       String[] encs = new String[cipherSuites.length + 1];       for (int i = 0; i < cipherSuites.length; i++)          encs[i] = cipherSuites[i];       encs[cipherSuites.length] =          "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA";       s.setEnabledCipherSuites(encs);       log("Connected to server " + server);       // Get the secret number from the external server.       InputStream in = s.getInputStream();       DataInputStream dis = new DataInputStream(in);       int secretNumber = dis.readInt();       // Send the response to the client in HTML format.       ServletOutputStream out = response.getOutputStream();       response.setContentType("text/html");       out.println("<HTML><HEAD><TITLE>" +          "Secret Number Page</TITLE></HEAD></HTML>");       out.println("<BODY><H1>Secret Number: " +          secretNumber + "</H1></BODY>");       out.println("</HTML>");       dis.close();       in.close();       s.close();    } } 

The servlet SSLSecretNumberServlet in Listing 13.3 can be deployed in any J2EE-compliant, JSSE-enabled Web container. During deployment, the initialization parameters server and port must be set to the fully qualified host name of the server machine and the number of the port on which the server is listening, respectively.

Listing 13.4 shows the code of the server program responsible for generating random numbers and communicating them to its clients through SSL socket connections.

Listing 13.4. SSLServer.java
 import java.io.OutputStream; import java.io.IOException; import java.io.DataOutputStream; import java.util.Random; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; /**  * Server class for random number generation. This server  * runs as a process that external client applications can  * contact by opening an appropriate socket. Every time a  * client contacts the server application, a new Thread is  * generated. The server application produces a new random  * number, communicates it to the client, and closes the  * connection with the client. The communication between  * the server and client is protected by SSL. An anonymous  * cipher suite is enabled.  */ public class SSLServer extends Thread {    private SSLSocket client;    private static Random randomGenerator = new Random();    /**     * Public constructor. Initializes the server by setting     * the Socket to communicate with the client.     *     * @param client an SSLSocket object representing the     *        client application this server is     *        communicating with.     */    public SSLServer(SSLSocket c)    {       this.client = c;    }    /**     * Generates a random number and communicates it to the     * the client. The communication is protected by SSL.     */    public void run()    {       int secretNumber = randomGenerator.nextInt();       System.out.println("Secret Number: " + secretNumber);       try       {          OutputStream out = client.getOutputStream();          DataOutputStream dos = new DataOutputStream(out);          dos.writeInt(secretNumber);          dos.flush();          dos.close();          out.close();          client.close();       }       catch(IOException e)       {          System.out.println(e);       }    }    /**     * Launches the server application. This method expects     * the port number to be passed on the command line.     *     * @param args a String[] array whose first element     *        must represent the port number this server     *        will be listening on. The port number is     *        expected to be an integer between 1025 and     *        65536. Values not in this range will cause the     *        application to quit.     */    public static void main(String args[])    {       if (args.length == 0)       {          System.out.println("Usage: java Server <port>");          System.exit(0);       }       int port = 0;       try       {          port = (new Integer(args[0])).intValue();       }       catch (NumberFormatException nfe)       {          System.out.println("Usage: java Server <port>");          System.out.println             ("<port> must be a parsable integer");          System.exit(0);       }       if (port <= 1024  port > 65536)       {          System.out.println("Usage: java Server <port>");          System.out.println             ("<port> must be an integer in the range " +             "1025-65536");          System.exit(0);       }       System.out.println("Server starting...");       SSLServerSocketFactory sslSrvFact =          (SSLServerSocketFactory)          SSLServerSocketFactory.getDefault();       SSLServerSocket ss = null;       // Try to start the server. At this point, problems       // may arise if another process is already listening       // on the selected port.       try       {          ss = (SSLServerSocket)             sslSrvFact.createServerSocket(port);       }       catch (IOException ioe)       {          System.out.println             ("There is already a server running on port " +             port + "\n" + ioe);          System.exit(0);       }       System.out.println          ("Server started on port " + port);       System.out.println("Waiting for clients...");       // Enable anonymous cipher suite       String[] cipherSuites =          ss.getEnabledCipherSuites();       String[] encs = new String[cipherSuites.length + 1];       for (int i = 0; i < cipherSuites.length; i++)          encs[i] = cipherSuites[i];       encs[cipherSuites.length] =          "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA";       ss.setEnabledCipherSuites(encs);       // Start an endless loop in which the server is       // constantly waiting for clients to connect.       while (true)       {          SSLSocket client = null;          try          {             client = (SSLSocket) ss.accept();          }          catch (IOException ioe)          {             System.out.println("Unable to accept " +                "connection from client\n" + ioe);             System.exit(0);          }          System.out.println             ("Request from client received...");          SSLServer server = new SSLServer(client);          server.start();       }    } } 

The server code in Listing 13.4 can be launched from the command line by entering the command

  java SSLServer   port  

where port is a valid TCP port number not already in use in the range 102565536.

13.5.2.2 Scenario with SSL Server Authentication

Using anonymous cipher suites as we did in Section 13.5.2.1 on page 469 is not recommended, because neither the server nor the client is authenticated. The only advantage in using anonymous cipher suites is that the communication between the client and the server is encrypted.

In this section, we show how the servlet and the external server presented in Listings 13.3 and 13.4, respectively, can be modified so that the external server is authenticated. For SSL server authentication to work, the external server must present the servlet with a valid certificate from its own keystore. In order for the servlet to trust the server, at least one of the certificates in the certificate chain of the server certificate must be in the servlet's truststore .

From an implementation perspective, these two requirements imply the following two points, respectively.

  1. The servlet must create a javax.net.ssl.SSLContext and initialize it with a TrustManagerFactory object. The TrustManagerFactory must have been previously initialized with a KeyStore object and the password to access the keystore file. The KeyStore object represents the truststore against which the servlet will attempt to authenticate the external server. At this point, the SSLContext can be used to generate an SSLSocketFactory , which is responsible for creating SSLSocket s, as in Listing 13.3.

  2. Similarly, the server must create an SSLContext and initialize it with a javax.net.ssl.KeyManagerFactory object. The KeyManagerFactory object itself must have been previously initialized with a KeyStore object and the password to access the keystore file. The KeyStore object represents the keystore from which the server will extract the certificate that will be presented to the servlet for server authentication. At this point, the SSLContext can be used to generate an SSLServerSocketFactory , which is responsible for creating SSLServerSocket s, as in Listing 13.4.

Section 8.1.2.2 on page 256 explains how to use the keytool command line utility to export certificates from a keystore, importing trusted certificates into a truststore, and obtain a certificate issued by a CA.

The servlet code is shown in Listing 13.5.

Listing 13.5. SSLSecretNumberServletWithServerAuth.java
 import java.io.InputStream; import java.io.IOException; import java.io.DataInputStream; import java.io.FileInputStream; import java.security.KeyStore; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /**  * This servlet connects to an external server, retrieves  * from it a secret number, and communicates it to the  * client. The communication between the servlet and the  * external server is SSL-protected and supports SSL server  * authentication. Therefore, usage of this servlet is  * indicated when the information exchanged between the  * servlet container and external server demands  * confidentiality and server authentication.  */ public class SSLSecretNumberServletWithServerAuth    extends HttpServlet {    private String server;    private int port;    /**     * Initializes the servlet by setting the initialization     * parameters and the external server host name and port     * number.     *     * @param config a ServletConfig object used by the servlet     *        container to pass information to the servlet     *        during the initialization process.     * @throws ServletException if the servlet initialization     *         cannot be executed.     */    public void init(ServletConfig config)       throws ServletException    {       super.init(config);       try       {          port = (new Integer(getInitParameter("port"))).             intValue();       }       catch (NumberFormatException nfe)       {          log("<port> must be a parsable integer");          throw new ServletException(nfe.getMessage());       }       String server = getInitParameter("server");    }    /**     * Opens a secure socket connection with the external     * server, retrieves the secret number generated from the     * external server, and communicates it to the client. This     * method is executed each time a client invokes the     * servlet.     *     * @param request an HttpServletRequest object containing     *        information about the client's request.     * @param response an HttpServletResponse object in which     *        the servlet stores information to be sent to     *        the client.     * @throws ServletException if an exception occurs that     *         interferes with the servlet's normal operation.     * @throws IOException if an I/O exception occurs.     */    public void service(HttpServletRequest request,       HttpServletResponse response)       throws ServletException, IOException    {       log("Requesting connection from " + server +          " on port " + port + "...");       // The keystore password is hardcoded       char[] passwd = "xyz123".toCharArray();       SSLSocket s = null;       try       {          // Create an SSLContext instance implementing the          // TLS protocol.          SSLContext ctx = SSLContext.getInstance("TLS");          // Create a TrustManagerFactory implementing the          // X.509 key management algorithm.          TrustManagerFactory tmf =             TrustManagerFactory.getInstance("IbmX509");          // Create a KeyStore instance implementing the          // Java KeyStore (JKS) algorithm.          KeyStore ks = KeyStore.getInstance("JKS");          // Load the KeyStore file trustStoreFile,          // representing the truststore that the servlet          // will use to authenticate the server          ks.load(new FileInputStream("trustStoreFile"),             passwd);          // Initialize the TrustManagerFactory object with          // the KeyStore.          tmf.init(ks);          // Initialize the SSLContext with the          // KeyManagerFactory.          ctx.init(null, tmf.getTrustManagers(), null);          // Create an SSLSocketFactory instance from the          // SSLContext and generate an SSLSocket from it          SSLSocketFactory sslFact = ctx.getSocketFactory();          s = (SSLSocket)             sslFact.createSocket(server, port);       }       catch (Exception e)       {          // Catch any Exception and turn it into a          // ServletException          throw new ServletException(e.getMessage());       }       SSLSession session = s.getSession();       log("Connected to server " + server + "\nCipher suite:"          + session.getCipherSuite());       // Get the secret number from the external server.       InputStream in = s.getInputStream();       DataInputStream dis = new DataInputStream(in);       int secretNumber = dis.readInt();       // Send the response to the client in HTML format.       ServletOutputStream out = response.getOutputStream();       response.setContentType("text/html");       out.println("<HTML><HEAD><TITLE>" +          "Secret Number Page</TITLE></HEAD></HTML>");       out.println("<BODY><H1>Secret Number: " +          secretNumber + "</H1></BODY>");       out.println("</HTML>");       dis.close();       in.close();       s.close();    } } 

The servlet SSLSecretNumberServletWithServerAuth in Listing 13.5 can be deployed in any J2EE-compliant, JSSE-enabled Web container. During deployment, the initialization parameters server and port must be set to the fully qualified host name of the server machine and the number of the port on which the server is listening, respectively.

This servlet requires the presence of a truststore file in the same directory as the servlet class. The truststore file name is hard-coded as trustStoreFile and the password to access it as xyz123 . The J2SE and J2EE reference implementations provide the keytool command line utility to manage keystores (see Section 8.1.2.2 on page 256). In particular, the -genkey option of this command can be used to create a new keystore.

Listing 13.6 shows the code of the server program responsible for generating random numbers and communicating them to its clients through SSL socket connections in which server authentication is enabled.

Listing 13.6. SSLServerWithServerAuth.java
 import java.io.OutputStream; import java.io.IOException; import java.io.DataOutputStream; import java.io.FileInputStream; import java.util.Random; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.KeyManagerFactory; import java.security.KeyStore; /**  * Server class for random number generation. This server  * runs as a process that external client applications can  * contact by opening an appropriate socket. Every time a  * client contacts the server application, a new Thread is  * generated. The server application produces a new random  * number, communicates it to the client, and closes the  * connection with the client. The communication between  * the server and client is protected by SSL. Server  * authentication is enabled.  */ public class SSLServerWithServerAuth extends Thread {    private SSLSocket client;    private static Random randomGenerator = new Random();    /**     * Public constructor. Initializes the server by setting     * the Socket to communicate with the client.     *     * @param client an SSLSocket object representing the     *        client application this server is     *        communicating with.     */    public SSLServerWithServerAuth(SSLSocket c)    {       this.client = c;    }    /**     * Generates a random number and communicates it to the     * the client. The communication is protected by SSL.     */    public void run()    {       int secretNumber = randomGenerator.nextInt();       System.out.println("Secret Number: " + secretNumber);       try       {          OutputStream out = client.getOutputStream();          DataOutputStream dos = new DataOutputStream(out);          dos.writeInt(secretNumber);          dos.flush();          dos.close();          out.close();          client.close();       }       catch (IOException e)       {          System.out.println(e);       }    }    /**     * Launches the server application. This method expects     * the port number to be passed on the command line.     *     * @param args a String[] array whose first element     *       must represent the port number this server     *       will be listening on. The port number is     *       expected to be an integer between 1025 and     *       65536. Values not in this range will cause the     *       application to quit.     */    public static void main(String args[])    {       if (args.length == 0)       {          System.out.println("Usage: java Server <port>");          System.exit(0);       }       int port = 0;       try       {          port = (new Integer(args[0])).intValue();       }       catch (NumberFormatException nfe)       {          System.out.println("Usage: java Server <port>");          System.out.println             ("<port> must be a parsable integer");          System.exit(0);       }       if (port <= 1024  port > 65536)       {          System.out.println("Usage: java Server <port>");          System.out.println             ("<port> must be an integer in the range " +             "1025-65536");          System.exit(0);       }       // The keystore password is hardcoded       char[] passwd = "abc123".toCharArray();       SSLContext ctx = null;       try       {          // Create an SSLContext instance implementing          // the TLS protocol.          ctx = SSLContext.getInstance("TLS");          // Create a KeyManagerFactory implementing the          // X.509 key management algorithm.          KeyManagerFactory kmf =             KeyManagerFactory.getInstance("IbmX509");          // Open up the KeyStore in order to present the          // server's certificates to the client.          KeyStore ks = KeyStore.getInstance("JKS");          // Load the KeyStore file keyStoreFile          ks.load (new FileInputStream("keyStoreFile"),             passwd);          // Initialize the KeyManagerFactory object with          // the KeyStore.          kmf.init(ks, passwd);          // Initialize the SSLContext with the          // KeyManagerFactory.          ctx.init(kmf.getKeyManagers(), null, null);       }       catch (Exception e)       {          System.out.println             ("Unable to initialize SSLContext " +             e.getMessage());          System.exit(0);       }       System.out.println("Server starting...");       // Create an SSLServerSocketFactory instance from       // the SSLContext and generate an SSLServerSocket       // from it.       SSLServerSocketFactory sslSrvFact =          ctx.getServerSocketFactory();       SSLServerSocket ss = null;       // Try to start the server. At this point, problems       // may arise if another process is already listening       // on the selected port.       try       {          ss = (SSLServerSocket)             sslSrvFact.createServerSocket(port);       }       catch (IOException ioe)       {          System.out.println             ("There is already a server running on " +             " port " + port + "\n" + ioe);          System.exit(0);       }       System.out.println("Server started on port " +          port);       System.out.println("Waiting for clients...");       // Start an endless loop in which the server is       // constantly waiting for clients to connect.       while (true)       {          SSLSocket client = null;          try          {             client = (SSLSocket) ss.accept();             SSLSession session = client.getSession();             System.out.println ("Connected. Cipher suite: "                + session.getCipherSuite());          }          catch (IOException ioe)          {             System.out.println("Unable to accept " +                "connection from client\n" + ioe);             System.exit(0);          }          System.out.println             ("Request from client received...");          SSLServerWithServerAuth server =             new SSLServerWithServerAuth(client);          server.start();       }    } } 

The server code in Listing 13.6 requires the presence of a keystore file in the same directory as the server class. The keystore file name is hard-coded as keyStoreFile and the password to access it as abc123 .

This server can be launched from the command line by entering the command.

  java SSLServerWithServerAuth   port  

where port is a valid TCP port number not already in use in the range 102565536.

13.5.2.3 Scenario with Both SSL Server and Client Authentication

This section presents the scenario in which both SSL server and client authentications are required. Note the following.

  1. As in Section 13.5.2.2 on page 476, in order for SSL server authentication to succeed, the external server must present the servlet with a valid certificate from its own keystore, and at least one of the certificates in the certificate chain of the server certificate must be present in the servlet's truststore.

  2. In addition, because this time SSL client authentication is required, the servlet also must present the server a valid certificate from its own keystore; for the server to trust the servlet, at least one of the certificates in the certificate chain of the client must be present in the server's truststore.

From an implementation perspective, these two requirements imply the following two points, respectively.

  1. The servlet must create an SSLContext and initialize it with a KeyManagerFactory object and a TrustManagerFactory object.

    • The KeyManagerFactory must have been previously initialized with a KeyStore object and the password to access the keystore file. The KeyStore object represents the keystore from which the servlet will extract the certificate to present to the server to perform client authentication.

    • The TrustManagerFactory must have been previously initialized with a KeyStore object and the password to access the keystore file. The KeyStore object represents the truststore against which the servlet will attempt to authenticate the external server to perform server authentication.

    At this point, the SSLContext can be used to generate an SSLSocketFactory , which is responsible for creating SSLSockets as in the examples of Listings 13.3 and 13.5.

  2. Similarly, the server must create an SSLContext and initialize it with a KeyManagerFactory object and a TrustManagerFactory object.

    • The KeyManagerFactory must have been previously initialized with a KeyStore object and the password to access the keystore file. The KeyStore object represents the keystore from which the server will extract the certificate to present to the servlet to perform server authentication.

    • The TrustManagerFactory must have been previously initialized with a KeyStore object and the password to access the keystore file. The KeyStore object represents the truststore against which the server will attempt to authenticate the servlet to perform client authentication.

    At this point, the SSLContext can be used to generate an SSLServerSocketFactory , which is responsible for creating SSLServerSocket s as in the examples of Listing 13.4 and 13.6.

The complete servlet code is shown in Listing 13.7.

Listing 13.7. SSLSecretNumberServletWithServerAndClientAuth.java
 import java.io.InputStream; import java.io.IOException; import java.io.DataInputStream; import java.io.FileInputStream; import java.security.KeyStore; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /**  * This servlet connects to an external server, retrieves  * from it a secret number, and communicates it to the  * client. The communication between the servlet and the  * external server is SSL-protected. Both SSL client and  * server authentications are enabled. Therefore, usage of  * this servlet is indicated when the information exchanged  * between the servlet container and external server demands  * authentication and confidentiality.  */ public class SSLSecretNumberServletWithServerAndClientAuth    extends HttpServlet {    private String server;    private int port;    /**     * Initializes the servlet by setting the initialization     * parameters, the external server host name and port     * number.     *     * @param config a ServletConfig object used by the servlet     *        container to pass information to the servlet     *        during initialization.     * @throws ServletException if the servlet initialization     *         cannot be executed.     */    public void init(ServletConfig config)       throws ServletException    {       super.init(config);       try       {          port = (new Integer(getInitParameter("port"))).             intValue();       }       catch (NumberFormatException nfe)       {          log("port must be a parsable integer");          throw new ServletException(nfe.getMessage());       }       String server = getInitParameter("server");    }    /**     * Opens a secure socket connection with the external     * server, retrieves the secret number generated from     * the external server, and communicates it to the     * client. This method is executed each time a client     * invokes the servlet.     *     * @param request an HttpServletRequest object     *        containing information about the client's     *        request.     * @param response an HttpServletResponse object in     *        which the servlet stores information to be     *        sent to the client.     * @throws ServletException if an exception occurs that     *         interferes with the servlet's normal     *         operation.     * @throws IOException if an I/O exception occurs.     */    public void service(HttpServletRequest request,       HttpServletResponse response)       throws ServletException, IOException    {       log("Requesting connection from " + server +          " on port " + port + "...");       // The truststore and keystore passwords are       // both hardcoded       char[] passwd = "xyz123".toCharArray();       char[] passwd2 = "client123".toCharArray();       SSLSocket s = null;       try       {          // Create an SSLContext instance implementing          // the TLS protocol.          SSLContext ctx = SSLContext.getInstance("TLS");          // Create a TrustManagerFactory implementing the          // X.509 key management algorithm.          TrustManagerFactory tmf =             TrustManagerFactory.getInstance("IbmX509");          // Create a KeyStore instance implementing the          // Java KeyStore (JKS) algorithm.          KeyStore ks = KeyStore.getInstance("JKS");          // Load the KeyStore file keyStoreFile.          ks.load(new             FileInputStream("trustStoreFile"), passwd);          // Initialize the TrustManagerFactory object with          // the KeyStore.          tmf.init(ks);          // Since the server requires client authentication          // the client must present its own certificate to          // the server.          // Create a KeyManagerFactory implementing the          // X.509 key management algorithm.          KeyManagerFactory kmf =             KeyManagerFactory.getInstance("IbmX509");          // Create a KeyStore instance implementing the          // Java KeyStore (JKS) algorithm.          KeyStore ks2 = KeyStore.getInstance("JKS");          //  Load the KeyStore file keyClientStore          ks2.load(new FileInputStream("keyClientStore"),             passwd2);          // Initialize the KeyManagerFactory object with          // the KeyStore.          kmf.init(ks2, passwd2);          // Initialize the SSLContext with the          // KeyManagerFactory and TrustManagerFactory.          ctx.init(kmf.getKeyManagers(),             tmf.getTrustManagers(), null);          // Create an SSLSocketFactory instance from the          // SSLContext and generate an SSLSocket from it.          SSLSocketFactory sslFact = ctx.getSocketFactory();          s = (SSLSocket) sslFact.createSocket             (server, port);       }       catch (Exception e)       {          // Catch any Exception and turn it into a          // ServletException          throw new ServletException(e.getMessage());       }       SSLSession session = s.getSession();       log("Connected to server " + server + "\nCipher suite:"          + session.getCipherSuite());       // Get the secret number from the external server.       InputStream in = s.getInputStream();       DataInputStream dis = new DataInputStream(in);       int secretNumber = dis.readInt();       // Send the response to the client in HTML format.       ServletOutputStream out = response.getOutputStream();       response.setContentType("text/html");       out.println("<HTML><HEAD><TITLE>" +          "Secret Number Page</TITLE></HEAD></HTML>");       out.println("<BODY><H1>Secret Number: " +          secretNumber + "</H1></BODY>");       out.println("</HTML>");       dis.close();       in.close();       s.close();    } } 

This servlet requires the presence of a keystore file and a truststore file in the same directory as the servlet class. The keystore file name is hard-coded as keyClientStore and the password to access it as client123 . The truststore file name is hard-coded as trustStoreFile and the password to access it as xyz123 .

The server code is shown in Listing 13.8.

Listing 13.8. SSLServerWithServerAndClientAuth.java
 import java.io.OutputStream; import java.io.IOException; import java.io.DataOutputStream; import java.io.FileInputStream; import java.util.Random; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.TrustManagerFactory; import java.security.KeyStore; /**  * Server class for random number generation. This server  * runs as a process that external client applications can  * contact by opening an appropriate socket. Every time a  * client contacts the server application, a new Thread is  * generated. The server application produces a new random  * number, communicates it to the client, and closes the  * connection with the client. The communication between  * the server and client is protected by SSL. This server  * supports server authentication and requires client  * authentication.  */ public class SSLServerWithServerAndClientAuth extends Thread {    private SSLSocket client;    private static Random randomGenerator = new Random();    /**     * Public constructor. Initializes the server by setting     * the Socket to communicate with the client.     *     * @param client an SSLSocket object representing the     *       client application this server is     *       communicating with.     */    public SSLServerWithServerAndClientAuth(SSLSocket c)    {       this.client = c;    }    /**     * Generates a random number and communicates it to the     * the client. The communication is protected by SSL.     */    public void run()    {       int secretNumber = randomGenerator.nextInt();       System.out.println("Secret Number:" + secretNumber);       try       {          OutputStream out = client.getOutputStream();          DataOutputStream dos = new DataOutputStream(out);          dos.writeInt(secretNumber);          dos.flush();          dos.close();          out.close();          client.close();       }       catch (IOException e)       {          System.out.println(e);       }    }    /**     * Launches the server application. This method expects     * the port number to be passed on the command line.     *     * @param args a String[] array whose first element     *       must represent the port number this server     *       will be listening on. The port number is     *       expected to be an integer between 1025 and     *       65536. Values not in this range will cause the     *       application to quit.     */    public static void main(String args[])    {       if (args.length == 0)       {          System.out.println("Usage: java Server <port>");          System.exit(0);       }       int port = 0;       try       {          port = (new Integer(args[0])).intValue();       }       catch (NumberFormatException nfe)       {          System.out.println("Usage: java Server <port>");          System.out.println             ("<port> must be a parsable integer");          System.exit(0);       }       if (port <= 1024  port > 65536)       {          System.out.println("Usage: java Server <port>");          System.out.println             ("<port> must be an integer in the range " +             "1025-65536");          System.exit(0);       }       // The keystore and truststore passwords are       // both hardcoded       char[] passwd = "abc123".toCharArray();       char[] passwd2 = "trust123".toCharArray();       SSLContext ctx = null;       try       {          // Create an SSLContext instance implementing          // the TLS protocol.          ctx = SSLContext.getInstance("TLS");          // Create a KeyManagerFactory implementing the          // X.509 key management algorithm.          KeyManagerFactory kmf =             KeyManagerFactory.getInstance("IbmX509");          // Open up the KeyStore in order to present the          // Server's certificates to the client          KeyStore ks = KeyStore.getInstance("JKS");          // Load the KeyStore file keyStoreFile          ks.load (new FileInputStream("keyStoreFile"),             passwd);          // Initialize the KeyManagerFactory object with          // the KeyStore.          kmf.init(ks, passwd);          // Create a TrustManagerFactory implementing the          // X.509 key management algorithm.          TrustManagerFactory tmf =             TrustManagerFactory.getInstance("IbmX509");          // Since client authentication will be requested,          // the server must be able to trust the client.          KeyStore ts = KeyStore.getInstance("JKS");          ts.load (new FileInputStream("trustServerStore"),             passwd2);          // Initialize the TrustManagerFactory object with          // the TrustStore          tmf.init(ts);          // Initialize the SSLContext with the          // KeyManagerFactory and TrustManagerFactory          ctx.init(kmf.getKeyManagers(),             tmf.getTrustManagers(), null);       }       catch (Exception e)       {          System.out.println             ("Unable to initialize SSLContext " +             e.getMessage());          System.exit(0);       }       System.out.println("Server starting...");       // Create an SSLServerSocketFactory instance from the       // SSLContext and generate an SSLServerSocket from it.       SSLServerSocketFactory sslSrvFact =          ctx.getServerSocketFactory();       SSLServerSocket ss = null;       // Try to start the server. At this point, problems       // may arise if another process is already listening       // on the selected port.       try       {          ss = (SSLServerSocket)             sslSrvFact.createServerSocket(port);          // Require client authentication          ss.setNeedClientAuth(true);       }       catch (IOException ioe)       {          System.out.println             ("There is already a server running on port "             + port + "\n" + ioe);          System.exit(0);       }       System.out.println("Server started on port " + port);       System.out.println("Waiting for clients...");       // Start an endless loop in which the server is       // constantly waiting for clients to connect.       while (true)       {          SSLSocket client = null;          try          {             client = (SSLSocket)ss.accept();             SSLSession session = client.getSession();             System.out.println("Connected.  Cipher Suite: " +                session.getCipherSuite());          }          catch (IOException ioe)          {             System.out.println("Unable to accept " +                "connection from client\n" + ioe);             System.exit(0);          }          System.out.println             ("Request from client received...");          SSLServerWithServerAndClientAuth server =             new SSLServerWithServerAndClientAuth(client);          server.start();       }    } } 

This server requires the presence of a keystore file and a truststore file in the same directory as the server class. The keystore file name is hard-coded as keyStoreFile and the password to access it as abc123 . The truststore file name is hard-coded as trustServerStore and the password to access it as trust123 .

The server code in Listing 13.8 can be launched from the command line by entering the command

  java SSLServerWithServerAndClientAuth   port  

where port is a valid TCP port number not already in use in the range 102565536.

 < Day Day Up > 


Enterprise Java Security. Building Secure J2EE Applications
Enterprise Javaв„ў Security: Building Secure J2EEв„ў Applications
ISBN: 0321118898
EAN: 2147483647
Year: 2004
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net