< 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
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 SSLThe 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.javaimport 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.javaimport 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 SSLThis 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 AuthenticationIn 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.
Enabling an additional cipher suite is accomplished through the following steps.
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.javaimport 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.javaimport 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 AuthenticationUsing 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.
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.javaimport 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.javaimport 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 AuthenticationThis section presents the scenario in which both SSL server and client authentications are required. Note the following.
From an implementation perspective, these two requirements imply the following two points, respectively.
The complete servlet code is shown in Listing 13.7. Listing 13.7. SSLSecretNumberServletWithServerAndClientAuth.javaimport 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.javaimport 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 > |