[LinuxFocus-icon]
Hogar  |  Mapa  |  Indice  |  Busqueda

Noticias | Arca | Enlaces | Sobre LF
Este documento está disponible en los siguientes idiomas: English  Castellano  Deutsch  Francais  Nederlands  Russian  Turkce  

convert to palmConvert to GutenPalm
or to PalmDoc

[Photo of the Author]
por Chianglin Ng
<chglin(at)singnet.com.sg>

Sobre el autor:

Vivo en Singapur, un país moderno multiracial localizado en el Sudeste de Asia. He estado usando linux por los últimos dos años aproximadamente. La primera distribución con la que empezé fue redhat 6.2. Hoy uso redhat 8.0 en casa. Tambié uso debian woody ocasionalmente.



Taducido al español por:
Juan Carlos Piedra <jcpiedra(at)troglo.org>

Contenidos:

 

Accessing PostgreSQL through JDBC via a Java SSL tunnel

[Illustration]

Resumen:

Este artículo muestra cómo configurar accesso JDBC para PostgreSQL en redhat 8.0 y cómo crear un túnel SSL usando las Extensiones de Sockets Seguros Java de Sun, para habilitar el acceso seguro a una base de datos postgres remota.

_________________ _________________ _________________

 

Introducción

Mientras aprendía sobre postgres y JDBC, encontré el problema de accesar una base de datos remota de forma segura via JDBC.   
Las conexiones JDBC no son cifradas y un "network sniffer" (un programa para interceptar datos de conexiones en la red) puede recoger información sensitiva fácilmente. Hay muchas maneras para prevenir esto. El manual de postgres revela que uno puede compilar postgres con soporte para SSL o usar túneles SSH.

En lugar de utilizar alguno de esos métodos, me gustaría usar java propiamente. El JDK 1.4.1 Java de Sun incluye las extensiones java para sockets seguros, los cuales pueden ser usados para crear conexiones SSL. El JDK también provee una herramienta de llaves para la creación de llaves públicas y privadas, certificados digitales y almacenes de llaves. Por lo tanto, es relativamente fácil construir un par de servidores proxy basados en java que puedan reenviar la información de la red de forma segura.  

Configurando PostgreSQL para JDBC en redhat 8.0

Las instrucciones dadas aquí son para redhat 8.0 pero los principios generales son aplicables a otras distribuciones. Usted necesita instalar PostgreSQL y los manejadores correspondientes de JDBC si no lo ha hecho todavía. En redhat 8, ud. puede usar el comando rpm e instalar el JDK 1.4.1 de Sun. El JDK de Sun viene con algunas restricciones criptográficas debido a las regulaciones de exportación de los Estados Unidos. Para conseguir criptografía avanzada puede descargar los ficheros de políticas del JCE (en inglés: Java Cryptographic Extensions, Extensiones de Criptografía Java). Visite el sitio web Java de Sun para más información.

He instalado el JDK 1.4.1 en /opt y configurado la variable de ambiente JAVA_HOME para apuntar a mi directorio JDK. También he actualizado mi trayectoria (PATH) para incluir el directorio que contiene los ejecutables de JDK. Las líneas siguientes fueron añadidas a mi fichero .bash_profile. 

JAVA_HOME = /opt/j2sdk1.4.1_01
PATH = /opt/j2sdk1.4.1_01/bin:$PATH
export JAVA_HOME PATH

Los limitados ficheros de políticas de cifrado que vienen con el JDK de Sun han sido reemplazados con los ficheros ilimitados en el JCE. Para habilitar java para que encuentre los manejadores para postgres, copié los manejadores postgres-jdbc en mi directorio de extensiones de Java (/opt/j2sdk1.4.1_01/jre/lib/ext). En redhat 8.0, los manejadores postgres-jdbc están localizados en /usr/share/pgsql.

Si esta es su primera instalación postgresql, usted tendrá que crear una nueva base de datos y una nueva cuenta de usuario. Primero, utilice el comando "su" para obtener acceso al super usuario (root) y dé inicio al servicio postgres. Luego cámbiese a la cuenta de administración por defecto de postgres.

su root
password:******
[root#localhost]#/etc/init.d/postgresql start
[root#localhost]# Starting postgresql service: [ OK ]
[root#localhost]# su postgres
[bash]$

Hay que crear una nueva cuenta de usuario y una nueva base de datos de postgres.

[bash]$:createuser
Enter name of user to add: chianglin
Shall the new user be allowed to create databases? (y/n) y
Shall the new user be allowed to create more new users? (y/n) y
CREATE USER
[bash]$createdb chianglin
CREATE DATABASE

He creado una nueva cuenta de administrador de postgres que corresponde a mi cuenta de usuario de linux y una base de datos con el mismo nombre. Cuando ud. ejecuta la herramienta psql, tratará de conectarse a una base de datos que corresponde al nombre la cuenta de usuario actual.   Refiérase al manual postgres para más detalles sobre la administración de cuentas y bases de datos. Para configurar una clave de acceso para la cuenta recién creada, puede ejecutar psql e invocar el comando ALTER USER. Ingrese al sistema usando su cuenta de usuario normal e inicie psql. Escriba el siguiente comando 

ALTER USER chianglin WITH PASSWORD 'test1234' ;

Para permitir conexiones tcp/ip, ud. necesita editar el fichero postgresql.conf y habilitar la opción tcpip_socket. En redhat 8, este fichero está localizado en /var/lib/pgsql/data. Como super usuario (root) configure lo siguiente:

tcpip_socket=true

El último paso es editar el fichero pg_hba.conf. Este fichero especifica los ordenadores que pueden conectarse a la base de datos postgres. He añadido una única entrada para un ordenador especificando la dirección de lazo cerrado (loopback) de mi pc, usando autenticación con contraseña. Usted necesita cambiarse al super usuario para editar este fichero .

host sameuser 127.0.0.1 255.255.255.255 password

Reinicie postgres y todos los cambios de configuración se harán efectivos.

 

Diseñando el túnel SSL de Java

Luego del paso anterior, postgres está listo para aceptar conexiones JDBC locales no seguras. Para accesar postgres de forma segura remotamente, alguna forma de repetición es requerida.

El siguiente diagrama muestra cómo esta repetición debería funcionar.

La figura Uno muestra como los servidores proxy deberían
funcionar

La aplicación JDBC se conectará al proxy cliente, el cual enviará todos los datos a través de la conexión SSL a nuestro proxy servidor remoto. El proxy servidor sencillamente remitirá todos los paquetes hacia postgres y enviará respuestas a través de la conexión SSL de vuelta al proxy cliente, el cual a su vez reenviará los datos a la aplicación JDBC. Este proceso será enteramente transparente para la aplicación JDBC.

En el diagrama se observa que desde el lado del servidor, existirá la necesidad de obtener los datos del flujo entrante seguro y enviarlos al flujo de salida local que se encuentra conectado al servidor. Lo contrario también es cierto, es necesario obtener los datos del flujo entrante local conectado al servidor y enviarlos a flujo de salida seguro. El mismo concepto también aplica para el cliente. Para implementar esto se pueden utilizar subprocesos. El siguiente diagrama lo muestra

El diagrama muestra cómo los 4 subprocesos repetidores trabajan  

Creando almacenes de llaves, llaves y certificados

Una conexión SSL usualmente require autenticación del servidor. La autenticación del cliente es opcional. En este caso, prefiero tener ambas autenticaciones, servidor y cliente. Esto significa que tengo que crear certificados y llaves para el cliente y para el servidor. Hago esto usando la herramienta para llaves (keytool) incluida en el JDK de Java. Tendré un par de almacenes de llaves (keystores), uno en el cliente y otro en el servidor. El primer almacén de llaves es necesario para guardar la llave privada de el servidor y el segundo para almacenar los certificados en los que confía el servidor.

Lo siguiente muestra la creación de un almacén de llaves, una llave privada y un certificado público firmado para el servidor.

keytool -genkey -alias serverprivate -keystore servestore -keyalg rsa -keysize 2048

Enter keystore password: storepass1
What is your first and last name?
[Unknown]: ServerMachine
What is the name of your organizational unit?
[Unknown]: ServerOrg
What is the name of your organization?
[Unknown]: ServerOrg
What is the name of your City or Locality?
[Unknown]: Singapore
What is the name of your State or Province?
[Unknown]: Singapore
What is the two-letter country code for this unit?
[Unknown]: SG
Is CN=ServerMachine, OU=ServerOrg, O=ServerOrg, L=Singapore, ST=Singapore, C= [no]: yes
Enter key password for <serverprivate>
(RETURN if same as keystore password): prikeypass0 </serverprivate>

Observe que las contraseñas son requeridas dos veces. La primera es para el almacén de llaves y la segunda para la llave privada. Una vez hecho esto, se exporta en un fichero el certificado público del servidor que será utilizado por el cliente para autenticar al servidor.

keytool -export -alias serverprivate -keystore -rfc servestore -file server.cer

Lo anterior exportará el certificado público firmado del servidor hacia el fichero server.cer. Desde el cliente importe este fichero en el almacén de llaves que guarda todos los certificados públicos en los que el cliente confía.

keytool -import -alias trustservercert -file server.cer -keystore clienttruststore

El comando anterior importará el certificado público del servidor en un almacén de llaves llamado clienttruststore. Si este almacén no existe aun, será creado y ud. requerirá ingresar una contraseña para el almacén.

En este punto, su sistema será capaz de facilitar una conexión SSL que provee la autenticación del servidor.
Debido a que también quiero autenticar al cliente, necesitaré crear una llave privada y otra pública para el cliente en un nuevo almacén, exportar el certificado público del cliente, e importarlo en un nuevo almacén de llaves en el servidor.

Al final de este proceso, deben existir dos almacenes de llaves en el servidor, uno contiene su llave privada y el otro contiene los certificados fiables. Lo mismo ocurre en el cliente.

Para poder ejecutar el código de ejemplo que doy más adelante, es esencial que ud. utilice la misma contraseña para cada almacén de llaves que crea en cada máquina. Esto significa que los dos almacenes de llaves en el servidor deben tener la misma contraseña. Lo mismo aplica para los dos almacenes de llaves en el cliente. 

Puede referirse a la documentación de Sun para aprender más sobre el uso de la herramienta para llaves (keytool).  

Implementando las clases

Mis clases utilizarán las extensiones de Sockets Seguros Java de Sun (Java Secured Socket extensions, en inglés). La guía de referencia para JSSE de Sun está disponible en http://java.sun.com/j2se/1.4.1/docs/guide/security/jsse/JSSERefGuide.html. Para una conexión SSL, ud. necesita una instancia del objeto SSLContext suministrado por JSSE. Inicialice SSLContext con las opciones que desee y obtendrá una clase SocketFactory segura. La clase SocketFactory puede ser usada para crear los sockets SSL.

En mi implementación, existirá una clase proxy para el servidor y una para el cliente, con el fin de construir el túnel SSL. Ya que ambos utilizarán una conexión SSL, deben heredar de una clase SSLConnection base. Esta clase será responsable de configurar el SSLContext inicial que será usado para el proxy del cliente y el del servidor. Finalmente, necesitamos implementar otra clase para los subprocesos repetidores. En total son 4 clases.
Lo siguiente muestra la sección de código perteneciente a la clase SSLConnection

Sección de Código de la clase SSLConnection

/* initKeyStore método para cargar los almacenes de llaves que contienen la llave privada y los certificados fiables */

public void initKeyStores(String key , String trust , char[] storepass)
{
      // mykey contiene mi propio certificado y llave privada, mytrust contiene todos los certificados de confianza
  try {
      //obtener instancias del almacén de llaves JKS de Sun
     mykey = KeyStore.getInstance("JKS" , "SUN");
     mytrust = KeyStore.getInstance("JKS", "SUN");

    //cargar los almacenes de llaves
   mykey.load(new FileInputStream(key)  ,storepass);
   mytrust.load(new FileInputStream(trust) ,storepass );
    }
 catch(Exception e) {
    System.err.println(e.getMessage());
    System.exit(1);
    }
}

/* initSSLContext método para obtener un SSLContext e inicializarlo con el protocolo SSL y los datos de los almacenes de llaves */
public void initSSLContext(char[] storepass , char[] keypass) {
    try{
    //obtener un SSLContext del JSSE de Sun
    ctx = SSLContext.getInstance("TLSv1" , "SunJSSE") ;
    //inicializar los almacenes de llaves
    initKeyStores(key , trust , storepass) ;

    //Crear la llave y las trust manager factories para manejar los certificados
    //en la llave y los almacenes de confianza
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509" ,
    "SunJSSE");
    tmf.init(mytrust);

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509" ,
    "SunJSSE");
    kmf.init(mykey , keypass);

    //Inicializar el SSLContext con los datos de los almacenes de llaves
    ctx.init(kmf.getKeyManagers() , tmf.getTrustManagers() ,null) ;
    }
    catch(Exception e) {
    System.err.println(e.getMessage());
    System.exit(1);
    }

}

El método initSSLContext crea un SSLContext del JSSE de Sun. Durante la creación, ud. puede especificar el protocolo SSL a utilizar. En este caso, he escogido usar TLS (Transport Layer Security) versión 1. Una vez que una instancia del SSLContext se obtiene, se inicializa con los datos de los almacenes de llaves.


La siguiente sección de código pertenece a la clase SSLRelayServer que funcionará en la misma máquina que la base de datos postgres. Esta clase retransmitirá toda la información del cliente desde la conexión SSL hacia postgres y viceversa.

Clase SSLRelayServer

/* initSSLServerSocket método que obtendrá el SSLContext de su super clase SSLConnection. Luego creará un objeto SSLServerSocketFactory que será utilizado para crear un SSLServerSocket. */

public void initSSLServerSocket(int localport) {
      try{
           //obtener la fábrica de sockets ssl
           SSLServerSocketFactory ssf = (getMySSLContext()).getServerSocketFactory();

            //crear el socket ssl
           ss = ssf.createServerSocket(localport);
           ((SSLServerSocket)ss).setNeedClientAuth(true);
      }
   catch(Exception e) {
      System.err.println(e.getMessage());
      System.exit(1);
    }
 }

// comenzar a escuchar en el SSLServerSocket y esperar por conexiones entrantes de clientes
public void startListen(int localport , int destport) {

    System.out.println("SSLRelay server started at " + (new Date()) + "  " +
                     "listening on port " + localport + "  " +  "relaying to port " + destport );

 while(true) {
      try {
         SSLSocket incoming = (SSLSocket) ss.accept();
         incoming.setSoTimeout(10*60*1000); // tiempo de expiración en 10 minutos
         System.out.println((new Date() ) + " connection from " + incoming );
         createHandlers(incoming, destport); // crear 2 nuevos subprocesos para manejar las conexiones entrantes
       }
    catch(IOException e ) {
        System.err.println(e);
        }
    }
}


La clase RelayApp, el proxy cliente, es similar a SSLRelayServer. Hereda de SSLConnection y usa 2 subprocesos para hacer el reenvío. La diferencia consiste en que crea un SSLSocket para conectarse al servidor remoto en lugar de un SSLServerSocket para recibir conexiones entrantes. La última clase que necesitamos es el subproceso que hace el reenvío efectivo. Sencillamente lee los datos de un flujo de entrada y los escribe a un flujo de salida.

El código completo para las cuatro clases está disponible aquí (example285-0.1.tar.gz).    

Ejecución de los proxies y pruebas

En el cliente, ud. necesitará estos ficheros SSLConnection.java, RelayIntoOut.java y RelayApp.java. En el servidor, ud. necesita SSLRelayServer.java, RelayIntoOut.java y SSLConnection.java. Póngalos juntos en un directorio. Para compilar el proxy cliente, ejecute el siguiente comando.

javac RelayApp.java

Para compilar el proxy servidor, ejecute lo siguiente

javac SSLRelayServer.java

En nuestra máquina servidor ejecutando postgres, se puede iniciar SSLRelayServer con 6 parámetros en la línea de comandos. Estos son

  1. Trayectoria completa hacia el almacén de llaves que contiene la llave privada del servidor creada anteriormente con la herramienta de llaves (keytool)
  2. Trayectoria completa a su almacén de llaves en el servidor que contiene los certificados de clientes fiables
  3. Contraseña para sus almacenes de llaves
  4. Contraseña para la llave privada de su servidor
  5. Puerto en que este servidor de repetición escucha
  6. Puerto en el cual reenviar datos ( puerto del servidor, en este caso postgresql tiene por defecto el 5432)

java SSLRelayServer servestore trustclientcert storepass1 prikeypass0 2001 5432

Una vez que el proxy servidor está en ejecución, ud. puede dar inicio al proxy cliente. El proxy cliente recibe 7 parámetros, los adicionales son el nombre del servidor o dirección IP del servidor al que se está conectando. Los argumentos son

  1. Trayectoria completa al almacén de llaves que contiene la llave privada del cliente
  2. Trayectoria completa al almacén de llaves del cliente que contiene los certificados fiables del servidor.
  3. Contraseña de su almacén de llaves
  4. Contraseña de su llave privada del cliente
  5. Nombre del servidor o dirección IP
  6. Número de puerto del servidor de repetición destinatario (en el ejemplo arriba, es 2001)
  7. Número de puerto de la aplicación a la cual se le hace la repetición, en este caso postgresql, debe usar el 5432

java RelayApp clientstore trustservercert clistorepass1 cliprikeypass0 localhost 2001 5432

Una vez que el túnel SSL es establecido, ud. puede dar inicio a su aplicación JDBC y conectarse a postgres de la manera usual. El proceso de reenvío será transparente para su aplicación JDBC. Este artículo ya es muy largo y no daré ejemplos para una aplicación JDBC aquí. El manual de postgres y la guía de aprendizaje de Sun contienen muchos ejemplos sobre JDBC.

Si ud. quiere ejecutar todo en una única máquina durante el período de pruebas, lo puede hacer. Hay dos maneras para hacerlo, una es configurar su base de datos postgres para que escuche en un puerto diferente, o puede cambiar el puerto que RelayApp usa para enviar datos a otro puerto diferente. Puedo usar lo último para ilustrar una prueba simple. Primero, cierre RelayApp, necesitará enviarle una señal de terminación presionando [ctrl] c. Utilize el mismo método para detener el proxy SSLRelayServer.

De inicio a RelayApp de nuevo con el siguiente comando. El único cambio es el número de puerto, ahora es 2002.

java RelayApp clientstore trustservercert clistorepass1 cliprikeypass0 localhost 2001 2002

La mejor aplicación que se puede usar para pruebas es el mismo psql. Enviaremos todo el tráfico de psql hacia postgres a través de nuestro túnel. Ejecute el siguiente comando para dar inicio a psql para las pruebas.

psql -h localhost -p 2002

Este comando ordena a psql a conectarse a localhost en el puerto 2002 en el cual nuestro RelayApp está escuchando. Luego de entrar su contraseña de postgres, puede comenzar a ejecutar comandos SQL de forma usual y probar la conexión SSL que está ahora haciendo el reenvío.

 

Una nota sobre seguridad

No es buena idea especificar contraseñas como parámetros en la línea de comandos si ud. comparte un ordenador. Esto debido a que alguien que ejecute el comando ps -auxww será capaz de ver la línea de comandos completa de su proceso, incluyendo las contraseñas. Es mejor almacenar las contraseñas en una forma no cifrada en otro fichero y dejar a su aplicación de java leerlas a partir ahí. Alternativamente puede usar Swing de Java para crear una caja de diálogo que pida una contraseña.

 

Conclusión

Es simple usar el JSSE de Sun para la creación de un túnel SSL que puede ser usado por postgres. De hecho, cualquier otra aplicación que requiera de una conexión segura puede probablemente utilizar este túnel SSL. Existen muchas formas de agregar criptografía a su conexión, solo use su editor de linux favorito y empieze a escribir código. Diviértase!

 

Enlaces Útiles

 

Formulario de "talkback" para este artículo

Cada artículo tiene su propia página de "talkback". A través de esa página puedes enviar un comentario o consultar los comentarios de otros lectores
 Ir a la página de "talkback" 

Contactar con el equipo de LinuFocus
© Chianglin Ng, FDL
LinuxFocus.org
Información sobre la traducción:
en --> -- : Chianglin Ng <chglin(at)singnet.com.sg>
en --> es: Juan Carlos Piedra <jcpiedra(at)troglo.org>

2003-03-24, generated by lfparser version 2.34