Cree aplicaciones de red seguras con SSL y la API JSSE

Internet es un lugar peligroso. Es demasiado fácil espiar, falsificar y robar información desprotegida mientras viaja por los cables. El mes pasado, escribí el artículo final de una serie sobre certificados X.509 e infraestructura de clave pública (PKI), las tecnologías que protegen la mayor parte de la actividad de comercio electrónico en Internet. Cerca del final del artículo, sugerí mirar el protocolo SSL (Secure Socket Layer) para aprender cómo se usan los certificados X.509 en la práctica. SSL es la aplicación principal de X.509: casi todos los navegadores y los servidores web y de aplicaciones más populares la admiten.

Este mes, exploraré SSL implementado por JSSE (Java Secure Socket Extension) y le mostraré cómo construir aplicaciones de red seguras en Java usando SSL y JSSE.

Comencemos con una demostración simple. JSSE proporciona un kit de herramientas SSL para aplicaciones Java. Además de las clases e interfaces necesarias, JSSE proporciona un práctico conmutador de depuración de línea de comandos que puede utilizar para observar el protocolo SSL en acción. Además de proporcionar información útil para depurar una aplicación recalcitrante, jugar con el kit de herramientas es una excelente manera de mojarse los pies con SSL y JSSE.

Para ejecutar la demostración, primero debe compilar la siguiente clase:

Prueba de clase pública {public static void main (String [] arstring) {try {new java.net.URL ("//" + arstring [0] + "/"). getContent (); } captura (excepción de excepción) {excepción.printStackTrace (); }}}

A continuación, debe activar la depuración SSL y ejecutar la aplicación anterior. La aplicación se conecta al sitio web seguro que especifique en la línea de comando utilizando el protocolo SSL a través de HTTPS. La primera opción carga el controlador de protocolo HTTPS. La segunda opción, la opción de depuración, hace que el programa imprima su comportamiento. Aquí está el comando (reemplácelo con el nombre de un servidor web seguro):

 java -Djava.protocol.handler.pkgs = com.sun.net.ssl.internal.www.protocol -Djavax.net.debug = ssl Test  

Necesita instalar JSSE; consulte Recursos si no está seguro de cómo hacerlo.

Ahora vayamos al grano y hablemos de SSL y JSSE.

Una breve mirada a SSL

El código de la introducción demuestra la forma más sencilla de agregar SSL a sus aplicaciones: a través de la java.net.URLclase. Este enfoque es útil, pero no lo suficientemente flexible como para permitirle crear una aplicación segura que utilice sockets genéricos.

Antes de mostrarle cómo agregar esa flexibilidad, echemos un vistazo rápido a las características de SSL.

Como su nombre indica, SSL tiene como objetivo proporcionar a las aplicaciones un conjunto de herramientas seguro similar a un socket. Idealmente, debería ser fácil convertir una aplicación que usa sockets regulares en una aplicación que usa SSL.

SSL aborda tres problemas de seguridad importantes:

  1. Proporciona autenticación, lo que ayuda a garantizar la legitimidad de las entidades involucradas en un diálogo.
  2. Proporciona privacidad. SSL ayuda a garantizar que un tercero no pueda descifrar el diálogo entre dos entidades.
  3. Mantiene la integridad. El uso de un MAC (código de autenticación de mensajes), que es similar a una suma de verificación, ayuda a garantizar que un diálogo entre dos entidades no sea modificado por un tercero.

SSL depende en gran medida de la criptografía de clave pública y de clave secreta. Utiliza criptografía de clave secreta para cifrar de forma masiva los datos intercambiados entre dos aplicaciones. SSL proporciona la solución ideal porque los algoritmos de clave secreta son seguros y rápidos. La criptografía de clave pública, que es más lenta que la criptografía de clave secreta, es una mejor opción para la autenticación y el intercambio de claves.

La implementación de referencia JSSE de Sun viene con toda la tecnología necesaria para agregar SSL a sus aplicaciones. Incluye soporte de criptografía RSA (Rivest-Shamir-Adleman), el estándar de facto para la seguridad en Internet. Incluye una implementación de SSL 3.0, el estándar SSL actual, y TLS (Transport Layer Security) 1.0, la próxima generación de SSL. JSSE también proporciona un conjunto de API para crear y utilizar sockets seguros.

La API JSSE

La arquitectura de seguridad de Java utiliza en gran medida el patrón de diseño de fábrica . Para los no iniciados, el patrón de diseño Factory utiliza objetos de fábrica especiales para construir instancias, en lugar de llamar directamente a sus constructores. (Consulte Recursos para conocer los pros y los contras de la clase de fábrica).

En JSSE, todo comienza con la fábrica; hay una fábrica de sockets SSL y una fábrica de sockets de servidor SSL. Dado que los sockets genéricos y los sockets de servidor ya son bastante fundamentales para la programación de redes Java, asumiré que está familiarizado con los dos y comprende sus funciones y diferencias. Si no es así, le recomiendo que obtenga un buen libro sobre programación de redes Java.

SSLSocketFactory

Los métodos de la javax.net.ssl.SSLSocketFactoryclase se dividen en tres categorías. La primera consiste en un solo método estático que recupera la fábrica de sockets SSL por defecto: static SocketFactory getDefault().

La segunda categoría consta de cuatro métodos heredados de javax.net.SocketFactoryese espejo de los cuatro constructores clave que se encuentran en la java.net.Socketclase y un método que envuelve un socket existente con un socket SSL. Cada uno devuelve un socket SSL:

  1. Socket createSocket(String host, int port)
  2. Socket createSocket(String host, int port, InetAddress clientHost, int clientPort)
  3. Socket createSocket(InetAddress host, int port)
  4. Socket createSocket(InetAddress host, int port, InetAddress clientHost, int clientPort)
  5. Socket createSocket(Socket socket, String host, int port, boolean autoClose)

The two methods in the third category return the list of SSL cipher suites that are enabled by default, and the complete list of supported SSL cipher suites:

  1. String [] getDefaultCipherSuites()
  2. String [] getSupportedCipherSuites()

A cipher suite is a combination of cryptographic algorithms that define a particular level of security for an SSL connection. A cipher suite defines whether the connection is encrypted, whether content integrity is verified, and how authentication occurs.

SSLServerSocketFactory

Methods on the javax.net.ssl.SSLServerSocketFactory class fall into the same three categories as SSLSocketFactory. First, there is the single static method that retrieves the default SSL server socket factory: static ServerSocketFactory getDefault().

The methods that return SSL server sockets mirror the constructors found in the java.net.ServerSocket class:

  1. ServerSocket createServerSocket(int port)
  2. ServerSocket createServerSocket(int port, int backlog)
  3. ServerSocket createServerSocket(int port, int backlog, InetAddress address)

Finally, the SSLServerSocketFactory features the two methods that return the list of ciphers enabled by default and the list of supported ciphers, respectively:

  1. String [] getDefaultCipherSuites()
  2. String [] getSupportedCipherSuites()

So far, the API is pretty straightforward.

SSLSocket

Things get interesting in the javax.net.ssl.SSLSocket class. I assume you are already familiar with the methods provided by its parent, the Socket class, so I will concentrate on the methods that provide SSL-related functionality.

Like the two SSL factory classes, the first two methods listed below retrieve the enabled and supported SSL cipher suites, respectively. The third method sets the enabled cipher suites. An application can use the third operation to upgrade or downgrade the range of acceptable security that the application will allow:

  1. String [] getEnabledCipherSuites()
  2. String [] getSupportedCipherSuites()
  3. void setEnabledCipherSuites(String [] suites)

These two methods determine whether the socket can establish new SSL sessions, which maintain connection details -- like the shared secret key -- between connections:

  1. boolean getEnableSessionCreation()
  2. void setEnableSessionCreation(boolean flag)

The next two methods determine whether the socket will require client authentication. The methods only make sense when invoked on server mode sockets. Remember, according to the SSL specification, client authentication is optional. For example, most Web applications don't require it:

  1. boolean getNeedClientAuth()
  2. void setNeedClientAuth(boolean need)

The methods below change the socket from client mode to server mode. This affects who initiates the SSL handshake and who authenticates first:

  1. boolean getUseClientMode()
  2. void setUseClientMode(boolean mode)

Method void startHandshake() forces an SSL handshake. It's possible, but not common, to force a new handshake operation in an existing connection.

Method SSLSession getSession() retrieves the SSL session. You will seldom need to access the SSL session directly.

The two methods listed below add and remove an SSL handshake listener object. The handshake listener object is notified whenever an SSL handshake operation completes on the socket.

  1. void addHandshakeCompletedListener(HandshakeCompletedListener listener)
  2. void removeHandshakeCompletedListener(HandshakeCompletedListener listener)

SSLServerSocket

The javax.net.ssl.SSLServerSocket class is similar to the javax.net.ssl.SSLSocket class; it doesn't require much individual attention. In fact, the set of methods on javax.net.ssl.SSLServerSocket class is a subset of the methods on the javax.net.ssl.SSLSocket class.

The first two methods listed below retrieve the enabled and supported SSL cipher suites. The third method sets the enabled cipher suite:

  1. String [] getEnabledCipherSuites()
  2. String [] getSupportedCipherSuites()
  3. void setEnabledCipherSuites(String [] suites)

These two methods control whether or not the server socket can establish new SSL sessions:

  1. boolean getEnableSessionCreation()
  2. void setEnableSessionCreation(boolean flag)

The following methods determine whether the accepted sockets will require client authentication:

  1. boolean getNeedClientAuth()
  2. void setNeedClientAuth(boolean flag)

The methods below change the accepted socket from client mode to server mode:

  1. boolean getUseClientMode()
  2. void setUseClientMode(boolean flag)

A simple example

To make this toolkit tutorial clearer, I've included the source code for a simple server and a compatible client below. It's a secure variation on the typical echo application that many introductory networking texts provide.

The server, shown below, uses JSSE to create a secure server socket. It listens on the server socket for connections from secure clients. When running the server, you must specify the keystore to use. The keystore contains the server's certificate. I have created a simple keystore that contains a single certificate. (See Resources to download the certificate.)

import java.io.InputStream; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; public class EchoServer { public static void main(String [] arstring) { try { SSLServerSocketFactory sslserversocketfactory = (SSLServerSocketFactory)SSLServerSocketFactory.getDefault(); SSLServerSocket sslserversocket = (SSLServerSocket)sslserversocketfactory.createServerSocket(9999); SSLSocket sslsocket = (SSLSocket)sslserversocket.accept(); InputStream inputstream = sslsocket.getInputStream(); InputStreamReader inputstreamreader = new InputStreamReader(inputstream); BufferedReader bufferedreader = new BufferedReader(inputstreamreader); String string = null; while ((string = bufferedreader.readLine()) != null) { System.out.println(string); System.out.flush(); } } catch (Exception exception) { exception.printStackTrace(); } } } 

Use the following command to start the server (foobar is both the name of the keystore file and its password):

 java -Djavax.net.ssl.keyStore=foobar -Djavax.net.ssl.keyStorePassword=foobar EchoServer 

The client, shown below, uses JSSE to securely connect to the server. When running the client, you must specify the truststore to use, which contains the list of trusted certificates. I have created a simple truststore that contains a single certificate. (See Resources to download the certificate.)

import java.io.InputStream; import java.io.OutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; public class EchoClient { public static void main(String [] arstring) { try { SSLSocketFactory sslsocketfactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); SSLSocket sslsocket = (SSLSocket)sslsocketfactory.createSocket("localhost", 9999); InputStream inputstream = System.in; InputStreamReader inputstreamreader = new InputStreamReader(inputstream); BufferedReader bufferedreader = new BufferedReader(inputstreamreader); OutputStream outputstream = sslsocket.getOutputStream(); OutputStreamWriter outputstreamwriter = new OutputStreamWriter(outputstream); BufferedWriter bufferedwriter = new BufferedWriter(outputstreamwriter); String string = null; while ((string = bufferedreader.readLine()) != null) { bufferedwriter.write(string + '\n'); bufferedwriter.flush(); } } catch (Exception exception) { exception.printStackTrace(); } } } 

Utilice el siguiente comando para iniciar el cliente ( foobares tanto el nombre del archivo de almacén de confianza como su contraseña):

 java -Djavax.net.ssl.trustStore = foobar -Djavax.net.ssl.trustStorePassword = foobar EchoClient