RMI mano a mano con SSL: construyendo aplicaciones distribuidas seguras

Visita la Web personal del autor.

Introducción

Cuando construimos aplicaciones distribuidas es posible aprovechar en toda su extensión el paradigma de la programación orientada a objetos. Los objetos no son solo elementos independientes, sino que además, se encuentran distribuidos en diversos ordenadores conectados a través de una red. Se puede decir, que nos acercamos un poco más a como funciona el mundo real, donde los objetos que manejamos diariamente no suelen estar en el mismo sitio.

SUMARIO: La programación distribuida orientada a objetos es una potente tecnología a nuestro alcance.

En la mayoría de los casos, las aplicaciones distribuidas funcionan siguiendo un modelo cliente/servidor. Uno o más servidores crean unos objetos locales y luego atienden peticiones de acceso sobre esos objetos provenientes de clientes situados en lugares remotos de la red. Por lo tanto, de forma general, podemos decir que las necesidades de una aplicación distribuida son:

  • Mecanismos para localizar los objetos en la red.
  • Comunicación con los objetos remotos.
  • Mecanismos de intercambio de información (paso de parámetros).

Diversas tecnologías nos proporcionan estos servicios y sin duda, la mas popular (hasta el momento) ha sido CORBA (Common Object Request Broker Architecture). CORBA es una tecnología creada y mantenida por el OMG (Object Management Group), un consorcio formado por un grupo de empresas implicadas en el uso de programación distribuida. La principal característica de CORBA es su independencia del lenguaje de programación mediante el uso de una interfaz de programación común denominada IDL (CORBA Interface Definition Language).

Desde Java es posible usar CORBA mediante Java IDL y aprovechar todas sus características, sin embargo, los ingenieros de SUN debieron pensar que esta solución era poco elegante y decidieron implementar una tecnología propia de programación distribuida orientada a objetos totalmente basada en JAVA. Esta tecnología es conocida como RMI (Remote Method Invocation).

RMI proporciona una potente herramienta al programador JAVA poniendo a su alcance una capacidad de programación distribuida 100% JAVA. La idea básica de RMI es que, objetos ejecutándose en una VM(Virtual Machine) sean capaces invocar métodos de objetos ejecutándose en VM's diferentes. Haciendo notar que las VM's pueden estar en la misma maquina o en maquinas distintas conectadas por una red.

SUMARIO: RMI proporciona una capacidad de programación distribuida 100% JAVA.

En el presente artículo estudiaremos las características de RMI, sus cualidades de seguridad y las herramientas a nuestro alcance para construir aplicaciones distribuidas seguras.

Caracteristicas del RMI

Entre las principales características de RMI podemos encontrar:

  • Sencillez.
  • Transparencia
  • Paso de objetos por valor (como parámetros de los métodos).
  • Implementación 100% JAVA.
  • Independencia del protocolo de comunicación.

En RMI, cuando los objetos remotos ya han sido referenciados, el programador los puede utilizar igual que si estuviesen en la maquina local, accediendo a sus métodos y manejándolos de forma normal. La capa RMI permanece oculta al programador proporcionando mayor claridad al código y mayor comodidad a la hora de programar la aplicación.

El paso de objetos por valor se revela como una de las características que diferencian a RMI de las otras tecnologías de programación distribuida. Esta potente posibilidad se basa en el uso del concepto de "Serialización" para el paso de objetos a través de la red. Objetos de todo tipo, tanto nativos JAVA como definidos por el usuario, puedes ser transferidos de forma totalmente transparente al programador usando esta técnica.

Por otro lado, la principal ventaja de RMI, su implementación 100% JAVA, es también su principal inconveniente. RMI no puede ser utilizado por otros lenguajes de programación, por lo que no es posible utilizarlo en aplicaciones distribuidas donde se utilizan distintos lenguajes. Por lo tanto, en la actualidad, las soluciones basadas en RMI se reducen a aquellas aplicaciones que se puedan implementar completamente en JAVA, cosa que habitualmente no suele ocurrir en el mundo real.

SUMARIO: La principal ventaja de RMI, su implementación 100% JAVA, es también su principal inconveniente.

RMI vs. CORBA

Pero, ¿porque utilizar RMI y no utilizar CORBA?. Sin duda, es casi obligatorio realizar una comparativa entre ambas tecnologías debido a que, efectivamente, podemos elegir entre las dos para implementar nuestras aplicaciones distribuidas. Numerosos artículos cubren esta discusión y prácticamente llegan a una misma conclusión: si nuestra aplicación distribuida se desarrolla totalmente en JAVA, utiliza RMI.

Por otro lado, CORBA es una tecnología asentada con el paso de los años y con un sólido soporte de muchas empresas. Ciertamente RMI no esta en la misma situación y si elegimos utilizarla debemos fiarnos de la "promesa" de SUN de seguir soportando la tecnología RMI, mejorándola e incrementándola, de forma que nuestras aplicaciones podrán seguir funcionado en el futuro. En cualquier caso, el objetivo publico de SUN es hacer compatibles ambas tecnologías, quizás de la mano de IIOP(Internet Inter-Orb Protocol).

Arquitectura RMI

La arquitectura de RMI consta de tres capas:

  • La capa stub/skeleton.
  • La capa de referencia remota.
  • La capa de transporte.

Cada capa es independiente de las otras y tiene definido su propio interfaz y protocolo, de forma que una capa puede ser cambiada sin afectar a las otras. Por ejemplo, la capa de transporte puede utilizar distintos protocolos como TCP, UDP…etc sin que el resto de las capas se vean afectadas en su funcionamiento.

SUMARIO: Cada capa es independiente de las otras y tiene definido su propio interfaz y protocolo.

La comunicación entre las capas se realiza por medio de la abstracción de stream o flujos de datos.

El punto de contacto de la aplicación cliente con el objeto remoto se hace por medio de un stub local. Este stub implementa la interfaz del objeto remoto y gestiona toda la comunicación con el server a través de la capa de referencia remota. A todos los efectos, el stub es la representación local del objeto remoto. Entre sus responsabilidades destacan:

  • Inicializar las llamadas a los objetos remotos
  • Serializar los argumentos para enviarlos por la red
  • Deserializar los argumentos devueltos en las llamadas

En la parte del servidor, el skeleton es el equivalente al stub en el cliente. Se encarga de traducir las invocaciones que provienen de la capa de referencia remota, así como de gestionar las respuestas. Entre sus actividades destacan:

  • Deserializar los argumentos
  • Hacer las llamadas a los métodos de la implantación del objeto remoto
  • Serializar los valores de retorno

La capa de referencia remota descansa debajo de la capa Stub/Skeleton. Está formada por dos entidades distintas, el cliente y el servidor, que se comunican a través de la capa de transporte. Es responsable de implementar la política de comunicación, que puede ser de distintos tipos:

  • Invocación unicast punto-punto.
  • Invocación a grupos de objetos.
  • Estrategias de reconexión.

SUMARIO: La capa de transporte es responsable del establecimiento y mantenimiento de la conexión.

La capa de transporte es responsable del establecimiento y mantenimiento de la conexión, proporcionando una canal de comunicación fiable entre las capas del referencia remota del cliente y del servidor. Sus principales responsabilidades son:

  • Establecimiento y mantenimiento de la conexión.
  • Atender a llamadas entrantes.
  • Establecer la comunicación para las llamadas entrantes.

La capa de transporte de RMI , en la realidad, esta implementada por medio de sockets. Estos sockets son creados por unos objetos denominados SocketFactories, que no son mas que fabricas de sockets. En JAVA 1.2 se han incluido las Custom Socket Factories, fabricas de sockets definidas por el programador, que le permiten crear sus propios sockets. La utilización de Custom Socket Factories pone al alcance del programador la implementación de técnicas de compresión, encriptación….etc en el nivel de transporte de RMI.

Por lo tanto, podemos redibujar la arquitectura RMI para que represente el uso de los sockets como se puede ver en la Figura 4.

Analisis de las propiedades de seguridad de RMI

RMI extiende la filosofía de seguridad de JAVA a la hora de controlar la ejecución de las clases. El concepto de SandBox o caja de arena, se extiende a la programación distribuida de la mano del Security Manager que controla la descarga y funcionamiento de las clases provenientes de la red. Es obligatorio en cualquier implementación RMI crear una instancia del SecurityManager que nos va a garantizar un nivel de seguridad similar al que tienen los applets.

SUMARIO: Es obligatorio en cualquier implementación RMI crear una instancia del SecurityManager.

¿Pero que ocurre en las comunicaciones a través de la red?. RMI no implementa ninguna política de seguridad en la capa de transporte. Las comunicaciones se realizan en "texto plano", por lo que, conceptos básicos de seguridad como: autentificación, confidencialidad…etc no se tienen en cuenta. El servidor RMI no autentifica las peticiones de acceso sobre sus objetos, de forma que un posible cliente ilícito podría tener acceso a los objetos. Esto es un grave inconveniente en aplicaciones que funcionen sobre redes de comunicación inseguras, como puede ser Internet.

Este problema puede ser resuelto mediante el uso de las Custom Socket Factories, que como ya hemos dicho en el punto anterior, permiten al programador crear su propio protocolo de encriptación e implementarlo en sus propios sockets. Pero pocos pueden aspirar a programar un protocolo de encriptación seguro en el poco tiempo que se suele tener para desarrollar una aplicación. La utilización de implementaciones existentes y probadas suelen ser una garantía de calidad y suelen evitarnos rompederos de cabeza. La propuesta de SUN para encriptar las comunicaciones RMI es SSL (Secure Socket Layer).

¿Pero que es SSL?

SSL son las siglas de Secure Socket Layer o capa segura de sockets. Es una tecnología desarrollada por Netscape para asegurar la privacidad y fiabilidad de las comunicaciones entre dos aplicaciones. Utiliza un sistema de encriptación asimétrico basado en claves publica/privada para negociar una clave que se utiliza para establecer una comunicación basada en encriptación simétrica. SSL es el protocolo de encriptación más utilizado en Internet en estos momentos y es el más usado en servidores web donde se solicita información confidencial.

SUMARIO: SSL es el protocolo de encriptación más utilizado en Internet en estos momentos.

SSL es utilizado por el nivel de aplicación como capa de transporte de forma totalmente transparente independiente del protocolo utilizado, por lo que es una opción ideal para dotar a nuestras aplicaciones RMI de un alto nivel de seguridad con muy poco esfuerzo.

Las principales propiedades de seguridad proporcionadas por SSL son:

  • Comunicación segura basada en encriptación simétrica.
  • Autentificación y negociación basada en encriptación asimétrica.
  • Comunicación fiable basada en protocolos de integridad de mensajes.

SSL puede ser utilizado en JAVA para obtener un nivel de sockets seguro, que a su vez puede ser utilizado por RMI. SUN todavía no proporciona un conjunto de clases que permitan utilizar SSL pero recomienda una serie de implementaciones comerciales que pueden ser utilizadas. Las referencias a estas implementaciones pueden ser encontradas en [4].

Analisis de las propiedades de seguirdad SSL

SSL tiene dos fases en su proceso de comunicación. En la primera fase se establece una comunicación basada en encriptación asimétrica donde el cliente y el servidor intercambian los primeros mensajes y realizan la negociación de los parámetros de la sesión. Esta fase esta soportada por un protocolo conocido como HandShake (estrechamiento de manos). En la segunda fase se establece la verdadera sesión de comunicación donde las aplicaciones intercambian información.

Se pueden producir ataques sobre el protocolo en la comunicación a través de la red, sobre los mensajes intercambiados entre el cliente y el servidor. Se supone que un posible atacante podría realizar diversas operaciones ilícitas sobre los mensajes como:

  • Sustitución.
  • Eliminación.
  • Interceptación.
  • Desencriptacion.

SSL ha sido diseñado teniendo en cuenta este tipo de ataques e implementa mecanismos para detectarlos. Se puede encontrar información detallada en [5].

SUMARIO: Existe la posibilidad de autentificar a los participantes de la conexión por medio del uso de Certificados.

Entre las opciones de comunicación de SSL existe la posibilidad de autentificar a los participantes de la conexión por medio del uso de Certificados. Un Certificado no es mas que una identificación que puede ser comprobada por una Entidad de Verificación como puede ser VeriSing. Sin embargo, SSL permite también que los participantes no sean autentificados. Por lo tanto, podemos destacar los siguientes modos de comunicación de SSL:

  • Comunicación anónima: Ninguno de los participantes esta autentificado.
  • Server autentificado.
  • Cliente autentificado.
  • Ambos autentificados.

Evidentemente, la opción mas segura es aquella en la que tanto el cliente como el servidor están autentificados.

SSL puede sufrir un ataque directo a la comunicación encriptada por métodos de fuerza bruta. Los métodos de encriptación de clave publica/privada se basan en funciones matemáticas de "un solo sentido". Esto no quiere decir realmente que no se pueda conseguir calcular la función inversa, si no que es mucho mas costoso computacionalmente que calcular la función normal. El tiempo necesario para desencriptar un mensaje depende en gran medida de la longitud de las claves utilizadas en la encriptación. Actualmente se considera que una clave de 40 bits es poco segura y una clave de 154 bit es prácticamente invulnerable. La longitud de claves utilizada por SSL depende de la implementación utilizada. Para versiones distribuidas por empresas Norte Americanas esta longitud esta limitada a 40 bits debido a las restricciones de exportación del gobierno de los EEUU. Para versiones de empresas Europeas o de otros lugares, es posible usar longitudes mayores, incluso de 154 bits.

Comprendiendo las socket factories

Si queremos entender como SSL se integra en nuestra aplicación debemos comprender como funcionan las Custom Socket Factories y para esto, nada mejor que crear una y utilizarla. Los pasos a seguir son los siguientes.

  • Extender las clases FilterInputStream y FilterOutputStream creando EjemploInputStream y EjemploOutputStream.
  • Extender la clase Socket utilizando los Streams creados en el punto anterior y crear nuestro nuevo socket EjemploSocket.
  • Crear la nueva Socket Factory a la que llamaremos EjemploSocketFactory que utilice los nuevos sockets creados en el punto anterior.

Nuestro nuevo nivel de Sockets no va a hacer nada especial, simplemente pasará los datos a la clase superior. Por lo tanto nuestra nueva Socket Factory solo tiene sentido a efectos didácticos y no tiene sentido usarla en un programa real. Pero por esto mismo resulta ideal para mostrar de forma sencilla los pasos que deberíamos seguir para escribir una Socket Factory realmente útil.

ESCRIBIENDO EjemploInputStream y EjemploOutputStream.

La capa de sockets utiliza el concepto de Stream para implementar la conexión a la red. Al crear un nuevo tipo de socket tenemos que redefinir los streams que va a devolver nuestro nuevo socket. Esto se hace mediante el código del Listado 1.

Listado1

import java.io.*;

class EjemploInputStream extends FilterInputStream
{
   public EjemploInputStream(InputStream in) 
   {
      super(in);
   }
   public int read() throws IOException 
   {
      int dato;
      dato=in.read();
      //POSIBLE TRATAMIENTO DATOS;
      return(dato);
   }
   public int read(byte b[], int off, int len) throws IOException 
   {
      int devuelve;
      devuelve=in.read(b,off,len);
      //POSIBLE TRATAMIENTO DATOS;
      return(devuelve);
   }
}
/**********************************************/
import java.io.*;
      
class EjemploOutputStream extends FilterOutputStream
{
   public EjemploOutputStream(OutputStream out) 
   {
       super(out);
   }
   public void write(int b) throws IOException 
   {    
      //POSIBLE TRATAMIENTO DATOS;
      out.write(b);
   }
   public void write(byte b[], int off, int len) throws IOException 
   {
      //POSIBLE TRATAMIENTO DATOS;
      out.write(b,off,len);
   }
   public void flush() throws IOException 
   {
      out.flush();
   }
}

En esta parte de código es donde definimos realmente el comportamiento de nuestra capa de transporte. Los algoritmos necesarios para tratar nuestros datos pueden ser implementados en los métodos write y read.

ESCRIBIENDO EjemploSocket

EjemploInputStream y EjemploOutputStream son utilizados por la clase EjemploSocket que vemos implementada en el Listado 2.

Listado 2
import java.io.*;
import java.net.*;
      
class EjemploSocket extends Socket
{

   // InputStream devuelto por EjemploSocket 
   private InputStream in;
   // OutputStream devuelto por EjemploSocket 
   private OutputStream out;

   public EjemploSocket() 
   { 
      super(); 
   }
   public EjemploSocket(String host, int port) throws IOException 
   {
      super(host, port);
   }
   public InputStream getInputStream()  throws IOException 
   {
      if (in == null) 
      {
         in = new EjemploInputStream(super.getInputStream());
      }
      return in;
   }
   public OutputStream getOutputStream() throws IOException 
   {
      if (out == null) 
      {
         out = new EjemploOutputStream(super.getOutputStream());
      }
      return out;
   }
   public synchronized void close() throws IOException 
   {
      OutputStream o = getOutputStream();
      o.flush();
      super.close();
   }
}

Nuestro nuevo EjemploSocket simplemente reescribe los métodos getOutputStream y getInputStream para devolver el nuevo tipo de streams. Mediante las llamadas: in = new EjemploInputStream(super.getInputStream()) y out = new EjemploOutputStream(super.getOutputStream()) la clase EjemploSocket obtiene un nuevo stream de la clase superior socket y se lo pasa a nuestras clases EjemploOutputStream y EjemploInputStream que lo utilizan oportunamente para crear nuevos tipos de streams donde los métodos write y read han sido escritos por nosotros. Eventualmente, en estos métodos, podríamos hacer un tratamiento de los datos (en el punto indicado con el comentario "POSIBLE TRATAMIENTO DATOS"), encriptando o comprimiento o cualquier otra operación que nos interesase.

ESCRIBIENDO EjemploSocketFactory.

Nos queda extender la clase RMISocketFactory para crear una clase propia que genere nuestro nuevo tipo de sockets. El código necesario para hacerlo se presenta en el Listado 3.

Listado3
import java.io.*; 
import java.net.*; 
import java.rmi.server.*; 

public class EjemploSocketFactory extends RMISocketFactory
{ 
   public Socket createSocket(String host, int port) throws IOException 
   { 
      EjemploSocket socket = new EjemploSocket(host, port); 
      return socket; 
   } 
   public ServerSocket createServerSocket(int i) throws IOException 
   { 
      return(new ServerSocket(i));
   }
}

Como podemos ver, la única parte relevante del código es la implementación del método createSocket. Este método, que devuelve nuestro nuevo tipo de sockets, será utilizado por el nivel RMI de forma interna para obtener nuevos sockets.

UTILIZANDO EjemploSocketFactory.

Por ultimo, lo único que nos queda por hacer es utilizar nuestra EjemploSocketFactory en un programa. Para ello hemos creado un nuevo programa que nos dice si en una determinada dirección IP existe un servidor RMI o no. Lo hemos llamado RMISnifer y su código se puede encontrar en el CD-ROM de la revista. Como se puede ver, nuestro programa sustituye la Socket Factory por defecto de RMI mediante la línea de código:

RMISocketFactory.setSocketFactory(new EjemploSocketFactory());

RMISocketFactory es una clase abstracta que implementa la Socket Factory utilizada por RMI. Es posible sustituir la Socket Factory por defecto por medio del método setSocketFactory.

UTILIZANDO SSL

Una vez comprendido el funcionamiento de las Socket Factories podemos adentrarnos en la utilización de SSL. En este apartado vamos a ver como podemos utilizar SSL para construir nuestras aplicaciones. Para ello vamos a utilizar una implementación comercial de SSL desarrollada por Phaos Tecnology[1]. Nuestro ejemplo va a ser una implementación del popular programa Hello World pero que va a funcionar de forma distribuida utilizando RMI y SSL. En realidad, es una extensión de los ejemplos presentados por SUN para mostrar el funcionamiento RMI adaptándolos para que funcionen con SSL.

Los pasos a seguir son los siguientes:

  1. Escribir el interfaz para el objeto remoto (Interfaz Hello).
  2. Escribir el cliente remoto (SSLHelloClient.java).
  3. Escribir el objeto local que funcionara de forma remota (SSLHelloImpl.java).
  4. Reescribir la clase RegistryImpl (SSLRegistryImpl.java).
  5. Escribir la SocketFactory (SSLSocketFactory.java).

INTERFAZ HELLO

Al diseñar el interfaz remoto definimos los métodos que los clientes van a poder usar en el objeto. La implementación del interfaz la encontramos en el Listado 4.

Listado 4
import java.rmi.*;
import java.rmi.server.*;

interface Hello extends Remote 
{
    String sayHello() throws RemoteException;
}

ESCRIBIENDO SSLHelloClient.java

El cliente tiene que seguir los siguientes pasos para poder acceder a los métodos del objeto remoto:

  1. Instalar la SSLSocketFactory en el RMI.
  2. Obtener la referencia al objeto remoto.
  3. Usar el método o métodos del objeto remoto como si fuera un objeto local.

Estos pasos se pueden ver implementados en el Listado 5.

Listado 5
/ *
  Copyright (c) 1998 Phaos Technology Corporation. All rights reserved. 
  Reproducido con autorización del autor. 
*/

  import java.rmi.*;
  import java.rmi.server.*;

  //Importar la APIs del Phaos SSLava Toolkit.
  import crysec.*;
  import crysec.SSL.*;

  //Version del Hello Client que usa SSL
  public class SSLHelloClient
 { 
    public static void main(String arg[])
    {
      try
      {
        // instalar la  SSL socket factory
        RMISocketFactory.setSocketFactory(new SSLSocketFactory());
       //Obtener la referencia del objeto remoto.
        Hello o = (Hello)Naming.lookup("//" + arg[0] + "/HelloServer");
       //Imprimir un mensaje llamando al metodo sayHello del objeto remoto.
        System.out.println("Message: " + o.sayHello());
      }

      catch (Exception e) 
      {
        e.printStackTrace();
      }
    }
  }

ESCRIBIENDO SSLHelloImpl.java

SSLHelloImpl.java es la implantación del objeto que va a ser accesible a través de RMI.

Los pasos a seguir para implementarlo son:

  1. Implementar los métodos definidos en el interfaz Hello.
  2. Inicializar los certificados del server.
  3. Inicializar los parámetros de sesión SSL.
  4. Instalar la SSLSocketFactory por medio de setSocketFactory().
  5. Instalar el Security Manager.
  6. Crear la nueva instancia de SSLHelloImpl.
  7. Dar de alta el nuevo objeto en el registry por medio de Naming.rebind("/HelloServer",o) para que sea accesible por los clientes.

Podemos ver todas estas operaciones en el Listado 6.

Listado 6
/ *
  Copyright (c) 1998 Phaos Technology Corporation. All rights reserved.
  Reproducido con autorización del autor. 
 */

  import java.rmi.*;
  import java.rmi.server.*;
  import java.util.*;
  import java.io.*;

  //Importar la APIs del Phaos SSLava Toolkit.
  import crysec.*;
  import crysec.SSL.*;

  public class SSLHelloImpl extends UnicastRemoteObject implements Hello
  {
    private String name;

   //Constructor.
    public SSLHelloImpl(String s) throws RemoteException 
    {
      name = s;
    }

   //El método del objeto.
    public String sayHello() throws RemoteException 
    {
      return "SSL Hello World!";
    }

   //Main.
    public static void main(String args[])
    {
      try
      {
        // Inicializamos lo certificados del servidor.
        SSLCertificate cert = new SSLCertificate();
        cert.certificateList = new Vector();
        cert.certificateList.addElement(new X509(new File("server-cert.der")));
        cert.certificateList.addElement(new X509(new File("ca-cert.der")));

        // Inicializar el contexto SSL del objeto.
        SSLParams params = new SSLParams();
        params.setServerCert(cert);
    // se requiere autentificacion del cliente. 
        params.setRequestClientCert(true);   

        //Instalamos la SSLSocketFactory
        RMISocketFactory.setSocketFactory(new SSLSocketFactory(params));

        //Instalamos el SecurityManager.
        System.setSecurityManager(new RMISecurityManager());

        //Creamos la instancia del objeto.
        SSLHelloImpl o = new SSLHelloImpl("HelloServer");
        //Damos de alta el objeto en el registry
        Naming.rebind("/HelloServer", o);
       //Mensaje informativo de que todo ha ido bien.
        System.out.println("HelloServer bound in registry");
      }

      catch (Exception e) {e.printStackTrace();
    }
  }

ESCRIBIENDO SSLRegistryImpl.java

Es preciso crear una nueva versión del registry para que soporte SSL. EL registry es el objeto que atiende a las necesidades de localización de objetos en el sistema RMI. Reescribiendolo para que soporte SSL, podemos controlar la autenticidad de los clientes que reclaman información sobre los objetos . Podemos ver esta implementación en el Listado 7.

Listado 7
/ *
  Copyright (c) 1998 Phaos Technology Corporation. All rights reserved. 
  Reproducido con autorización del autor. 
 */

  import java.io.*;
  import java.util.*;
  import java.rmi.*;
  import java.rmi.registry.*;
  import java.rmi.server.*;

  import sun.rmi.registry.*;

  // Importar la APIs del Phaos SSLava Toolkit.
  import phaos.crysec.*;
  import phaos.crysec.SSL.*;

  public class SSLRegistryImpl extends RegistryImpl implements Registry 
  {
    public SSLRegistryImpl() throws RemoteException 
    {
    }

    public SSLRegistryImpl(int port) throws RemoteException 
    {
      super(port);
    }

    public static void main(String arg[]) 
    {
      try 
      {
        int port = Registry.REGISTRY_PORT;
        if (arg.length == 1)
        port = Integer.parseInt(arg[0]);

        // Inicializar los certificados del servidor.
        SSLCertificate cert = new SSLCertificate();
        cert.certificateList = new Vector();
        cert.certificateList.addElement(new X509(new File("server-cert.der")));
        cert.certificateList.addElement(new X509(new File("ca-cert.der")));

        // initiacializar los parametros de sesion de SSL.
        SSLParams params = new SSLParams();
        params.setServerCert(cert);
        params.setRequestClientCert(true); // requerir autorización del cliente.
        short cs[] = 
          { 
            SSLParams.SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
            SSLParams.SSL_RSA_WITH_3DES_EDE_CBC_SHA 
          };
          params.setServerCipherSuites(cs); 

          RMISocketFactory.setSocketFactory(new SSLSocketFactory(params));

          System.setSecurityManager(new RMISecurityManager());

          SSLRegistryImpl o = new SSLRegistryImpl(port);

          while (true) 
          {
            try 
            {
              Thread.sleep(Long.MAX_VALUE);
            }

            catch (InterruptedException e) 
            {
            }
          }
        }

        catch (Exception e) {e.printStackTrace();
      }
    }
  }

ESCRIBIENDO SSLSocketFactory.java

Por ultimo nos queda escribir la SSLSocketFactory que generara los sockets SSL. Los pasos a seguir son:

  1. Crear la SSLSocketFactory con los nuevo parámetros.
  2. Reescribir los métodos createSocket() y createServerSocket() para que devuelvan SSLSockets.

El código de la implementación se puede ver en el Listado 8.

Listado 8
/ *
  Copyright (c) 1998 Phaos Technology Corporation. All rights reserved. 
  Reproducido con autorización del autor. 
 */

  import java.net.*;
  import java.io.*;
  import java.rmi.server.*;

  // Importar la APIs del Phaos SSLava Toolkit.
  import phaos.crysec.SSL.*;

  // Extender la RMISocketFactory standard para que use SSL.
  public class SSLSocketFactory extends RMISocketFactory 
  {
    // Los parametros SSL del objeto.
    protected SSLParams params;

    public SSLSocketFactory()
    {
      this(new SSLParams());
    }

    public SSLSocketFactory(SSLParams p) 
    {
      params = p;
    }

    public Socket createSocket(String host, int port) throws IOException
    {
      return new SSLSocket(host, port, params);
    }

    public ServerSocket createServerSocket(int port) throws IOException 
    {
      return new SSLServerSocket(port, params);
    }
  }

CONCLUSION

Como conclusión podemos decir que el uso de RMI y SSL nos proporciona una elegante y segura tecnología para crear nuestras aplicaciones distribuidas. Sin embargo, puede ser un gran inconveniente utilizar una implementación comercial de SSL por su coste. Esto se resolverá cuando SUN publique su versión de SSL (que esperemos sea gratuita), pero, los resarrolladores europeos seguiremos teniendo la restricción de exportación de 40 bits y por lo tanto una seguridad demasiado endeble en nuestras aplicaciones. Pero las versiones europeas de SSL seguirán siendo una opción muy interesante a considerar en los presupuestos de los nuevos proyectos.

BIBLIOGRAFIA Y REFERENCIAS

COMPARTE ESTE ARTÍCULO

ENVIAR A UN AMIGO
COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN GOOGLE +
ARTÍCULO ANTERIOR

SIGUIENTE ARTÍCULO

¡SÉ EL PRIMERO EN COMENTAR!
Conéctate o Regístrate para dejar tu comentario.