Páginas

sábado, 11 de mayo de 2013

Conexiones VPN PPTP y OpenVPN desde Ubuntu 12.04

Introducción a las VPN

Una VPN (Virtual Private Network) es una tecnología que permite extender de forma segura una red privada sobre otra pública como Internet.

Comunmente las VPN se usan para trabajar en la red de una empresa sin estar físicamente en ella. Una vez conectados al servidor VPN de la empresa, nuestro ordenador pasará a formar parte de la red de la empresa, accediendo a los servidores y recursos disponibles en la misma.

El mecanismo se describe el siguiente diagrama:

Características de una VPN

Una VPN debe garantizar dos cuestiones fundamentales:
  • Autenticación de usuario. Cuando conectamos al servidor de VPN deberemos identificarnos con un certificado o un usuario/contraseña. De este modo se restringe el acceso a personas no autorizadas.
  • Cifrado de datos. Dado que la conexión a una VPN implica la circulación de datos por redes "públicas", si queremos garantizar la confidencialidad de los mismos deberemos cifrarlos. El cifrado se realiza con algoritmos de clave simétrica (DES o 3DES) cuyas claves han sido intercambiadas previamente mediante el típico handshaking SSL.
Tipos de VPN según el mecanismo de conexión

Hay varios tipos de VPN:
  • VPN punto a punto. Muy poco usada en la actualidad. Esta modalidad implica la conexión al servidor de VPN mediante líneas de datos dedicadas: modem, RDSI, etc.
  • Tunneling. Es el tipo de VPN más frecuente hoy en día. El servidor VPN de la empresa es visible en Internet y conectamos al mismo a través de nuestro ISP (proveedor de acceso a internet). La conexión está cifrada para evitar "escuchas" no deseadas. El protocolo de red usado en la empresa (por ejemplo IPX) queda encapsulado y cifrado dentro del protocolo que usamos para transmitir/recibir datos en Internet (por ejemplo TCP/IP).
Usos típicos de las VPN

Hoy en día las VPN se usan también en escenarios distintos del escenario para el que fueron ideadas (conexión remota a la red local de la empresa). Algunos de estos escenarios son:
  • Aislar dentro de la LAN (red de area local) de una empresa una subred con información sensible. Por ejemplo podríamos tener todos los servidores y ordenadores del departamento de investigación y desarrollo en una VPN montada sobre la propia LAN para evitar escuchas de los paquetes por parte de empleados desleales, la información de la VPN se mueve por la LAN, pero cifrada.
  • Navegación anónima en Internet. Navegar por Internet con nuestra propia dirección IP y sin encriptar acarrea deficiencias de seguridad: se dejan muchos rastros de nuestras actividades en todo tipo de servidores, hay portales como Netflix que sólo son visibles desde determinadas zonas geográficas (EEUU), las empresas colocan proxies que limitan el acceso a determinados servicios (youtube, facebook, irc, descargas, etc), etc.


Hay muchos proveedores de VPN en Internet que permiten una navegación segura y anónima. Muchos son de pago y haya algunos gratuítos. Ojo con estos últimos, no adelantamos mucho navegando en modo cifrado si el servidor VPN no es confiable, Yo nunca realizaría una transacción bancaria o un pago con tarjeta a través de un servidor de este tipo.

La utilización de una conexión VPN gratuíta o de pago nos permite:

  • Que nuestros paquetes de datos estén cifrados, por lo que nadie conoce qué estamos realizando.
  • Podemos conectar a un servidor VPN de la zona geográfica que nos interese (por ejemplo EEUU), con lo que accederíamos a portales como Netflix.
  • Si el proxy de la empresa no nos deja acceder a YouTube, podemos conectar a un servidor VPN y acceder con la IP de la red virtual a YouTube. El proxy de la empresa no puede ver los datos de nuestros paquetes, dado que van cifrados, por lo que no puede restringirnos el acceso.

Protocolos VPN

El protocolo estándar de facto es el IPSEC, pero también están PPTP, L2F, L2TP, SSL/TLS, SSH, etc.

Los distintos protocolos VPN tienen puntos fuertes y puntos débiles. Vamos a comparar las tres más comunes:

  • PPTP. PPTP (Protocolo de Túnel Punto a Punto) es un buen protocolo VPN ligero que ofrece una seguridad básica en línea y velocidades rápidas. PPTP está integrado en una gran variedad de equipos de sobremesa y dispositivos móviles y cuenta con encriptación de 128 bits. PPTP es una buena elección si OpenVPN no está disponible en su dispositivo y la velocidad es la máxima prioridad.
  • IPSEC. L2TP (Protocolo de tunelización de Capa 2) con IPsec (Seguridad de IP) es un protocolo integrado muy seguro para una amplia variedad de dispositivos de escritorio y móviles. L2TP/IPsec ofrece un encriptado de 256-bits, pero la seguridad extra de sobrecarga requiere más uso del CPU que PPTP. L2TP/IPsec es una elección excelente si OpenVPN no está disponible en su dispositivo, pero usted quiere más seguridad que PPTP.
  • OpenVPN(SSL). OpenVPN es el protocolo VPN premier diseñado para redes de banda ancha modernas, pero no es compatible con los dispositivos móviles y tablets. OpenVPN ofrece un encripado de 256-bits y es extremadamente estable y rápido en las redes con largas distancias y alto tiempo de espera. Provee mayor seguridad que PPTP y requere menor uso de CPU que L2TP/IPsec. OpenVPN es el protocolo recomendado para escritorios incluyendo Windows, Mac OS X, y Linux.

Lo habitual es utilizar software tanto en el servidor como en los clientes VPN, pero también se puede montar la VPN mediante hardware, lo que da una mayor velocidad a las comunicaciones, pero es mucho mas caro y ofrece menos versatilidad.

Conectar a un servidor VPN desde Ubuntu 12.04

Instalación del software cliente

Vamos a ver ahora como conectar a un servidor VPN con Ubuntu 12.04. Lo primero será instalar el software cliente. En Ubuntu 12.04 viene preinstalado con un cliente para el protocolo PPTP, vamos a instalar también un cliente para el protocolo SSL (Openvpn). Para ello ejecutaremos en la consola el comando:

    sudo apt-get install network-manager-pptp
    sudo apt-get install network-manager-openvpn

Por último reiniciamos el equipo

Configurar una conexión VPN a un servidor VPN de tipo PPTP

Vamos a conectar con una red VPN-PPTP gratuíta (http://www.vpnbook.com/) (ojo con la fiabilidad de estos servicios gratuítos). Los pasos a dar son:

  • Abrimos el panel de configuración de la red, haciendo clic con el botón derecho del ratón en el icono de red de la esquina superior derecha de la pantalla y seleccionando la opción "Configuración de la red".
  • Bajo la lista de conexiones de red, pulsamos el botón "+" para añadir la interfaz VPN y pulsamos crear.
  • Elegimos ahora una conexión de tipo PPTP y pulsamos crear.
  • En la pantalla de configuración introducimos los datos proporcionados por el servidor VPN:

        Pasarela: euro1.vpnbook.com
        Usuario: vpnbook
        Contraseña: adv7ebeh
        Opciones avanzadas. Activamos la opción usar cifrado MPPE

Pulsamos el botón Guardar y listo.

Ya sólo queda activar la conexión VPN PPTP gratuíta y navegar un poco. Ojo donde nos metemos con estas conexiones, sólo hay que probar la velocidad de conexión con portales como http://www.speedtest.com para ver que el servidor VPN está en Rumanía.

Configurar una conexión VPN a un servidor VPN de tipo OpenVPN(SSL)

Vamos a conectar con una red VPN-OpenVPN gratuíta,  para este ejemplo hemos elegido vpnbook (http://www.vpnbook.com/). Ojo con la fiabilidad de estos servicios gratuítos.

Para conectar a una VPN con SSL, usando por elemplo la implementación OpenVPN necesitaríamos:

Certificado cliente. El servidor tiene las claves públicas de los clientes autorizados.
Certificado del servidor. El cliente tiene la clave pública del servidor al que se conecta.

En este ejemplo, no hacemos llegar la clave pública de nuestro certificado (FNMT, DNIe, etc), a vpnbook.com, dado que es un servidor gratuíto. Ellos ofrecen un kit de conexión que incluye la clave pública de un cliente de prueba.

Los pasos a dar son:

  • Descargaremos los certificados del servidor de la URL: http://www.vpnbook.com/free-openvpn-account/VPNBook.com-OpenVPN-Euro1.zip
  • Descomprimimos el zip descargado en algún directorio. Hay varios ficheros de configuración (*.ovpn). Vamos a trabajar con "vpnbook-euro1-tcp80.ovpn", elegimos este fichero porque usará el puerto 80, abierto en la mayoría de routers y usará TCP, protocolo no restringido por los ISP (proveedores de Internet). Dentro de este fichero (pnbook-euro1-tcp80.ovpn) tenemos los datos de conexión y las claves públicas/privadas que necesitaremos para conectar. Abrimos este fichero con gedit y sacamos tres ficheros:

        Fichero cert.pem: Tendrá las líneas comprendidas entre <cert> y </cert>

-----BEGIN CERTIFICATE-----
MIID6DCCA1GgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBoDELMAkGA1UEBhMCQ0gx
DzANBgNVBAgTBlp1cmljaDEPMA0GA1UEBxMGWnVyaWNoMRQwEgYDVQQKEwt2cG5i
b29rLmNvbTELMAkGA1UECxMCSVQxFDASBgNVBAMTC3ZwbmJvb2suY29tMRQwEgYD
VQQpEwt2cG5ib29rLmNvbTEgMB4GCSqGSIb3DQEJARYRYWRtaW5AdnBuYm9vay5j
b20wHhcNMTMwNTA2MDMyMTIxWhcNMjMwNTA0MDMyMTIxWjB4MQswCQYDVQQGEwJD
SDEPMA0GA1UECBMGWnVyaWNoMQ8wDQYDVQQHEwZadXJpY2gxFDASBgNVBAoTC3Zw
bmJvb2suY29tMQ8wDQYDVQQDEwZjbGllbnQxIDAeBgkqhkiG9w0BCQEWEWFkbWlu
QHZwbmJvb2suY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkTM/8E+JH
CjskqMIwgYDrNCBTWZLa+qKkJjZ/rliJomTfVYwKwv1AHYYU6RHpCxS1qFp3BEKL
vQlASuzycSv1FGnNiLmg94fqzzWdmjs1XWosnLqbOwxx2Ye/1WoakSHia0pItoZk
xK7/fllm42+Qujri/ERGga5Cb/TfiP6pUQIDAQABo4IBVzCCAVMwCQYDVR0TBAIw
ADAtBglghkgBhvhCAQ0EIBYeRWFzeS1SU0EgR2VuZXJhdGVkIENlcnRpZmljYXRl
MB0GA1UdDgQWBBTDr4BCNSdOEh+Lx6+4RRK11x8XcDCB1QYDVR0jBIHNMIHKgBRZ
4KGhnll1W+K/KJVFl/C2+KM+JqGBpqSBozCBoDELMAkGA1UEBhMCQ0gxDzANBgNV
BAgTBlp1cmljaDEPMA0GA1UEBxMGWnVyaWNoMRQwEgYDVQQKEwt2cG5ib29rLmNv
bTELMAkGA1UECxMCSVQxFDASBgNVBAMTC3ZwbmJvb2suY29tMRQwEgYDVQQpEwt2
cG5ib29rLmNvbTEgMB4GCSqGSIb3DQEJARYRYWRtaW5AdnBuYm9vay5jb22CCQCk
baY7CL3pNTATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZI
hvcNAQEFBQADgYEAoDgD8mpVPnHUh7RhQziwhp8APC8K3jToZ0Dv4MYXQnzyXziH
QbewJZABCcOKYS0VRB/6zYX/9dIBogA/ieLgLrXESIeOp1SfP3xt+gGXSiJaohyA
/NLsTi/Am8OP211IFLyDLvPqZuqlh/+/GOLcMCeCrMj4RYxWstNxtguGQFc=
-----END CERTIFICATE-----


        Fichero ca.pem: Tendrá las líneas comprendidas entre <ca> y </ca>

-----BEGIN CERTIFICATE-----
MIIDyzCCAzSgAwIBAgIJAKRtpjsIvek1MA0GCSqGSIb3DQEBBQUAMIGgMQswCQYD
VQQGEwJDSDEPMA0GA1UECBMGWnVyaWNoMQ8wDQYDVQQHEwZadXJpY2gxFDASBgNV
BAoTC3ZwbmJvb2suY29tMQswCQYDVQQLEwJJVDEUMBIGA1UEAxMLdnBuYm9vay5j
b20xFDASBgNVBCkTC3ZwbmJvb2suY29tMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkB2
cG5ib29rLmNvbTAeFw0xMzA0MjQwNDA3NDhaFw0yMzA0MjIwNDA3NDhaMIGgMQsw
CQYDVQQGEwJDSDEPMA0GA1UECBMGWnVyaWNoMQ8wDQYDVQQHEwZadXJpY2gxFDAS
BgNVBAoTC3ZwbmJvb2suY29tMQswCQYDVQQLEwJJVDEUMBIGA1UEAxMLdnBuYm9v
ay5jb20xFDASBgNVBCkTC3ZwbmJvb2suY29tMSAwHgYJKoZIhvcNAQkBFhFhZG1p
bkB2cG5ib29rLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyNwZEYs6
WN+j1zXYLEwiQMShc1mHmY9f9cx18hF/rENG+TBgaS5RVx9zU+7a9X1P3r2OyLXi
WzqvEMmZIEhij8MtCxbZGEEUHktkbZqLAryIo8ubUigqke25+QyVLDIBuqIXjpw3
hJQMXIgMic1u7TGsvgEUahU/5qbLIGPNDlUCAwEAAaOCAQkwggEFMB0GA1UdDgQW
BBRZ4KGhnll1W+K/KJVFl/C2+KM+JjCB1QYDVR0jBIHNMIHKgBRZ4KGhnll1W+K/
KJVFl/C2+KM+JqGBpqSBozCBoDELMAkGA1UEBhMCQ0gxDzANBgNVBAgTBlp1cmlj
aDEPMA0GA1UEBxMGWnVyaWNoMRQwEgYDVQQKEwt2cG5ib29rLmNvbTELMAkGA1UE
CxMCSVQxFDASBgNVBAMTC3ZwbmJvb2suY29tMRQwEgYDVQQpEwt2cG5ib29rLmNv
bTEgMB4GCSqGSIb3DQEJARYRYWRtaW5AdnBuYm9vay5jb22CCQCkbaY7CL3pNTAM
BgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAKaoCEWk2pitKjbhChjl1rLj
6FwAZ74bcX/YwXM4X4st6k2+Fgve3xzwUWTXinBIyz/WDapQmX8DHk1N3Y5FuRkv
wOgathAN44PrxLAI8kkxkngxby1xrG7LtMmpATxY7fYLOQ9yHge7RRZKDieJcX3j
+ogTneOl2w6P0xP6lyI6
-----END CERTIFICATE-----


        Fichero key.pem: Tendrá las líneas comprendidas entre <key> y </key>

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCkTM/8E+JHCjskqMIwgYDrNCBTWZLa+qKkJjZ/rliJomTfVYwK
wv1AHYYU6RHpCxS1qFp3BEKLvQlASuzycSv1FGnNiLmg94fqzzWdmjs1XWosnLqb
Owxx2Ye/1WoakSHia0pItoZkxK7/fllm42+Qujri/ERGga5Cb/TfiP6pUQIDAQAB
AoGANX508WQf9nVUUFlJ8LUZnnr4U2sEr5uPPNbcQ7ImTZm8MiMOV6qo/ikesMw5
8qCS+5p26e1PJWRFENPUVhOW9c07z+nRMyHBQzFnNAFD7TiayjNk1gz1oIXarceR
edNGFDdWCwXh+nJJ6whbQn9ioyTg9aqScrcATmHQxTit0GECQQDR5FmwC7g0eGwZ
VHgSc/bZzo0q3VjNGakrA2zSXWUWrE0ybBm2wJNBYKAeskzWxoc6/gJa8mKEU+Vv
ugGb+J/tAkEAyGSEmWROUf4WX5DLl6nkjShdyv4LAQpByhiwLjmiZL7F4/irY4fo
ct2Ii5uMzwERRvHjJ7yzJJic8gkEca2adQJABxjZj4JV8DBCN3kLtlQFfMfnLhPd
9NFxTusGuvY9fM7GrXXKSMuqLwO9ZkxRHNIJsIz2N20Kt76+e1CmzUdS4QJAVvbQ
WKUgHBMRcI2s3PecuOmQspxG+D+UR3kpVBYs9F2aEZIEBuCfLuIW9Mcfd2I2NjyY
4NDSSYp1adAh/pdhVQJBANDrlnodYDu6A+a4YO9otjd+296/T8JpePI/KNxk7N0A
gm7SAhk379I6hr5NXdBbvTedlb1ULrhWV8lpwZ9HW2k=
-----END RSA PRIVATE KEY-----


  • Abrimos el panel de configuración de la red, haciendo clic con el botón derecho del ratón en el icono de red de la esquina superior derecha de la pantalla y seleccionando la opción "Configuración de la red".
  • Bajo la lista de conexiones de red, pulsamos el botón "+" para añadir la interfaz VPN y pulsamos crear.
  • Elegimos ahora una conexión de tipo PPTP y pulsamos crear.
  • En la pantalla de configuración introducimos los datos proporcionados por el servidor VPN:

        Pasarela: euro1.vpnbook.com
        Autenticación Tipo: Contraseña concertificado TLS
        Usuario: vpnbook
        Contraseña: adv7ebeh
        Certificado usuario: Seleccionamos el fichero cert.pem
        Certificado ca: Seleccionamos el fichero ca.pem
        Clave privada: Seleccionamos el fichero key.pem
        Opciones avanzadas. Ajustaremos:
                Puerto 80
                Compresion LZO
                Conexion TCP
                Seguridad AES-128-CBC

Pulsamos el botón Guardar y listo.

Ya sólo queda activar la conexión VPN OpenVPN(SSL) gratuíta y navegar un poco. Ojo donde nos metemos con estas conexiones, sólo hay que probar la velocidad de conexión con portales como http://www.speedtest.com para ver que el servidor VPN está en Rumanía.
   
Referencias

http://geekland.hol.es/conectarse-a-un-servidor-vpn-gratis/
http://es.giganews.com/vyprvpn/compare-vpn-protocols.html
http://es.wikipedia.org/wiki/Red_privada_virtual

viernes, 26 de abril de 2013

Ejemplo JMS Topic con Spring 2.5 y ActiveMQ

Introducción

En el siguiente ejemplo montamos una demostración de cómo configurar un publicador (publisher) y un suscriptor (suscriber) sobre un topic JMS usando Spring 2.5 para configurar todo el sistema.

Como servidor JMS usaremos Apache ActiveMQ.

En una entrada anterior ya hemos comentado cómo instalar ActiveMQ, así pues, vamos directamente con el ejemplo.

Proyecto publicador (publisher)

Este jar implementa un sencillo publicador de mensajes que entrega sus mensajes a sus N suscriptores mediante una cola de tipo "topic" en un servidor JMS.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>inerel</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>inerel-jms-topic-productor</artifactId>
  <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
          <version>2.5</version>
      </dependency>
      <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.8.0</version>
      </dependency>
  </dependencies>
</project>



applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- URL del servidor JMS                             -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <bean id="activemq.connectionfactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- CONFIGURACION PRODUCTOR VOLCANDO SOBRE UNA TOPIC -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el nombre de la TOPIC en ActiveMQ      -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.cola-topic" class="org.apache.activemq.command.ActiveMQTopic">
            <constructor-arg index="0" value="cola-topic"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el JmsTemplate:                        -->
        <!--  pubSubDomain==true => Cola tipo TOPIC           -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory" ref="activemq.connectionfactory"/>
            <property name="pubSubDomain" value="true"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el generador de peticiones             -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="productorPeticiones" class="es.ine.sgtic.jms.topic.productor.impl.ProductorPeticionesImpl">
            <property name="jmsTemplate" ref="activemq.jmsTemplate"/>
            <property name="destino" ref="activemq.cola-topic"/>
        </bean>
</beans>


AppTestProductorTopic

package es.ine.app;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import es.ine.sgtic.jms.topic.productor.IProductorPeticiones;

public class AppTestProductorTopic {

    public static void main(String args[]){
   
        //Instanciamos la factoría de beans
        final BeanFactory fabricaDeBeans = new XmlBeanFactory(new FileSystemResource("src/main/resources/applicationContext.xml"));
       
        //Obtenemos el bean productor de peticiones
        final IProductorPeticiones productor = (IProductorPeticiones) fabricaDeBeans.getBean("productorPeticiones");
       
        //Generamos la petición y ala enviamos
        productor.generarPeticion();
    }
}


IProductorPeticiones.java

package es.ine.sgtic.jms.topic.productor;

public interface IProductorPeticiones {
  
    public void generarPeticion();
}

ProductorPeticionesImpl .java

package es.ine.sgtic.jms.topic.productor.impl;

import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;
import javax.jms.Topic;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import es.ine.sgtic.jms.topic.productor.IProductorPeticiones;

public class ProductorPeticionesImpl implements IProductorPeticiones {

    private static final String CAMPO_CONTENIDO = "campo-contenido";
    private static final String MENSAJE = "En un lugar de la mancha";
    private JmsTemplate jmsTemplate = null;
    private Topic destino = null;

    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void setDestino(Topic destino) {
        this.destino = destino;
    }

    public void generarPeticion() {
        //Generamos y enviamos una petición
        jmsTemplate.send(this.destino,
                         new MessageCreator() {
                            public Message createMessage(Session session) throws JMSException {
                                MapMessage mensaje = session.createMapMessage();
                                mensaje.setString(CAMPO_CONTENIDO, MENSAJE);
                                return mensaje;
                            }
                        });
    }
}


Proyecto suscriptor (suscriber)

Este jar implementa un sencillo suscriptor que apunta a cierta cola JMS de tipo "topic", por lo que recibe un mensaje cada vez que un publicador deja algo en la cola.

Nosotros sólo implementamos el consumidor de los mensajes (id="consumidorPeticiones"), es Spring quien proporciona un listener (id="listenerPeticiones") sobre la cola "topic".

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>inerel</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>inerel-jms-topic-consumidor</artifactId>
  <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
          <version>2.5</version>
      </dependency>
      <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.8.0</version>
      </dependency>
  </dependencies>
</project>



applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:jms="http://www.springframework.org/schema/jms"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
        http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd">

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- URL del servidor JMS                             -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <bean id="activemq.connectionfactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- CONFIGURACION CONSUMIDOR LEYENDO DE UNA TOPIC    -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el nombre de la TOPIC en ActiveMQ      -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.cola-topic" class="org.apache.activemq.command.ActiveMQTopic">
            <constructor-arg index="0" value="cola-topic"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Consumidor de las peticiones                     -->      
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="consumidorPeticiones" class="es.ine.sgtic.jms.topic.consumidor.impl.ConsumidorAvisosTopicImpl"/>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Listener de las peticiones                       -->      
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <jms:listener-container connection-factory="activemq.connectionfactory" destination-type="topic">
            <jms:listener id="listenerPeticiones" destination="cola-topic" ref="consumidorPeticiones"/>
        </jms:listener-container>
</beans> 


AppTestConsumidorTopic.java

package es.ine.sgtic.app;

import javax.jms.JMSException;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.jms.listener.DefaultMessageListenerContainer;

public class AppTestConsumidorTopic {

    public static void main(String[] args) throws JMSException {

        //Instanciamos la factoría de beans
        final BeanFactory factoriaBeans = new XmlBeanFactory(new FileSystemResource("src/main/resources/applicationContext.xml"));
       
        //Instanciamos el bean escuchador de peticiones, con esto queda activo y a la escucha
        final DefaultMessageListenerContainer listenerPeticiones = (DefaultMessageListenerContainer) factoriaBeans.getBean("listenerPeticiones");
       
        //TODO: Si necesitásemos instanciar otro consumidor podríamos invocar:
        //listenerPeticiones.initialize();
    }
}


ConsumidorAvisosTopicImpl.java

package es.ine.sgtic.jms.topic.consumidor.impl;

import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageListener;

public class ConsumidorAvisosTopicImpl implements MessageListener {

    private static final String CAMPO_CONTENIDO = "campo-contenido";
  
    @Override
    public void onMessage(Message mensaje) {
        try {
            if (mensaje instanceof MapMessage) {
                final MapMessage mapMessage = (MapMessage) mensaje;
                System.out.println(mapMessage.getString(CAMPO_CONTENIDO));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

lunes, 22 de abril de 2013

Ejemplo JMS Queue con Spring 2.5 y ActiveMQ

Introducción

En el siguiente ejemplo montamos una demostración de cómo configurar un productor y un consumidor sobre una queue JMS usando Spring 2.5 para configurar todo el sistema.

Como servidor JMS usaremos Apache ActiveMQ.

Instalación ActiveMQ

1) Para descargar Apache ActiveMQ desde la página "http://activemq.apache.org/download.html". En la fecha de realización de este ejemplo la última versión disponible es la 5.8.0.

2) Se descomprime la distribución de ActiveMQ descargada, en el directorio que deseemos. En Ubunti se descomprime con:

     tar zxvf apache-activemq-X.X.X-bin.tar.gz

3) Arrancamos el servidor, para lo que necesitaremos lanzar el script "activemq" que tenemos en el subdirectorio bin. La forma de lanzarlo será:

     [dir-instalacion]/bin/activemq start

Nota. También podemos parar reiniciar o verificar el estado del servidor con el mismo script variando el parámetro. Están disponibles los parámetros: stop, restart o status.

4) Para verificar que tenemos correctamente iniciado el servidor, podemos acceder a la consola web del mismo en la URL: http://localhost:8161/ con el usuario/contraseña admin/admin.

5) Para acceder al área de administración del servidor deberemos hacer clic en el enlace "Manage ActiveMQ broker"

Proyecto productor

Este jar implementa un sencillo productor de mensajes que apunta a una cola a la que hemos llamado "cola-queue" en el servidor.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>inerel</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>inerel-jms-queue-productor</artifactId>
  <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
          <version>2.5</version>
      </dependency>
      <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.8.0</version>
      </dependency>
  </dependencies>
</project>



applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- URL del servidor JMS                             -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <bean id="activemq.connectionfactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- CONFIGURACION PRODUCTOR VOLCANDO SOBRE UNA QUEUE -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el nombre de la QUEUE en ActiveMQ      -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.cola-queue" class="org.apache.activemq.command.ActiveMQQueue">
            <constructor-arg index="0" value="cola-queue"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el JmsTemplate:                        -->
        <!--  pubSubDomain==false => Cola tipo QUEUE          -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory" ref="activemq.connectionfactory"/>
            <property name="pubSubDomain" value="false"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el generador de peticiones             -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="productorPeticiones" class="es.ine.sgtic.jms.queue.productor.impl.ProductorPeticionesImpl">
            <property name="jmsTemplate" ref="activemq.jmsTemplate"/>
            <property name="destino" ref="activemq.cola-queue"/>
        </bean>
</beans>


AppTestProductorQueue

package es.ine.sgtic.app;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import es.ine.sgtic.jms.queue.productor.IProductorPeticiones;

public class AppTestProductorQueue {

    public static void main(String[] args) {
       
        //Instanciamos la factoría de beans
        final BeanFactory fabricaDeBeans = new XmlBeanFactory(new FileSystemResource("src/main/resources/applicationContext.xml"));
       
        //Obtenemos el bean productor de peticiones
        final IProductorPeticiones productor = (IProductorPeticiones) fabricaDeBeans.getBean("productorPeticiones");
       
        //Generamos la petición y ala enviamos
        productor.generarPeticion();
    } 
}


IProductorPeticiones.java

package es.ine.sgtic.jms.queue.productor;

public interface IProductorPeticiones {
   
    public void generarPeticion();
}


ProductorPeticionesImpl .java

package es.ine.sgtic.jms.queue.productor.impl;

import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.Session;

import org.springframework.jms.core.JmsTemplate;
import org.springframework.jms.core.MessageCreator;

import es.ine.sgtic.jms.queue.productor.IProductorPeticiones;

public class ProductorPeticionesImpl implements IProductorPeticiones {

    private static final String CAMPO_CONTENIDO = "campo-contenido";
    private static final String MENSAJE = "En un lugar de la mancha"; 
    private JmsTemplate jmsTemplate = null;
    private Destination destino = null;

    public void setJmsTemplate(JmsTemplate jmsTemplate) { 
        this.jmsTemplate = jmsTemplate; 
    }

    public void setDestino(Destination destino) {
        this.destino = destino; 
    }

    public void generarPeticion() {
        //Generamos y enviamos una petición
        jmsTemplate.send(this.destino,
                         new MessageCreator() { 
                            public Message createMessage(Session session) throws JMSException { 
                                MapMessage mensaje = session.createMapMessage(); 
                                mensaje.setString(CAMPO_CONTENIDO, MENSAJE); 
                                return mensaje; 
                            }
                        });
    }
}


Proyecto consumidor

Este jar implementa un sencillo consumidor de mensajes que apunta a una cola a la que hemos llamado "cola-queue" en el servidor.

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>es.ine.sgtic</groupId>
    <artifactId>inerel</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>inerel-jms-queue-consumidor</artifactId>
  <dependencies>
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
          <version>2.5</version>
      </dependency>
      <dependency>
          <groupId>org.apache.activemq</groupId>
          <artifactId>activemq-all</artifactId>
          <version>5.8.0</version>
      </dependency>
  </dependencies>
</project>



applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- URL del servidor JMS                             -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <bean id="activemq.connectionfactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="tcp://127.0.0.1:61616"/>
    </bean>

    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
    <!-- CONFIGURACION CONSUMIDOR LEYENDO DE UNA QUEUE    -->
    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el nombre de la QUEUE en ActiveMQ      -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.cola-queue" class="org.apache.activemq.command.ActiveMQQueue">
            <constructor-arg index="0" value="cola-queue"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el JmsTemplate:                        -->
        <!--  pubSubDomain==false => Cola tipo QUEUE          -->
        <!--  receiveTimeout==xxx => Timeout espera ms        -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="activemq.jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
            <property name="connectionFactory" ref="activemq.connectionfactory"/>
            <property name="defaultDestination" ref="activemq.cola-queue"/>
            <property name="pubSubDomain" value="false"/>
            <property name="receiveTimeout" value="1000"/>
        </bean>
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <!-- Ajustamos el consumidor de peticiones            -->
        <!-- ++++++++++++++++++++++++++++++++++++++++++++++++ -->
        <bean id="consumidorPeticiones" class="es.ine.sgtic.jms.queue.consumidor.impl.ConsumidorPeticionesImpl">
            <property name="jmsTemplate" ref="activemq.jmsTemplate"/>
        </bean>
</beans> 


AppTestConsumidorQueue.java

package es.ine.sgtic.app;

import javax.jms.JMSException;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;

import es.ine.sgtic.jms.queue.consumidor.IConsumidorPeticiones;

public class AppTestConsumidorQueue {

    public static void main(String[] args) throws JMSException { 

        //Instanciamos la factoría de beans
        final BeanFactory factoriaBeans = new XmlBeanFactory(new FileSystemResource("src/main/resources/applicationContext.xml"));
       
        //Obtenemos el bean consumidor de peticiones
        final IConsumidorPeticiones consumidor = (IConsumidorPeticiones) factoriaBeans.getBean("consumidorPeticiones");
       
        //Obtenemos y procesamos una petición
        final String peticion = consumidor.consumirPeticion();
        System.out.println(peticion);
    }
}


IConsumidorPeticiones.java

package es.ine.sgtic.jms.queue.consumidor;

import javax.jms.JMSException;

public interface IConsumidorPeticiones {
   
    public String consumirPeticion() throws JMSException;
}


ConsumidorPeticionesImpl.java

package es.ine.sgtic.jms.queue.consumidor.impl;

import javax.jms.JMSException;
import javax.jms.MapMessage;

import org.springframework.jms.core.JmsTemplate;

import es.ine.sgtic.jms.queue.consumidor.IConsumidorPeticiones;

public class ConsumidorPeticionesImpl implements IConsumidorPeticiones {
   
    private static final String CAMPO_CONTENIDO = "campo-contenido";
   
    private JmsTemplate jmsTemplate = null;
   
    public void setJmsTemplate(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }
   
    public String consumirPeticion() throws JMSException {
       
        //Descargamos y procesamos una petición
        final MapMessage mensaje = (MapMessage) jmsTemplate.receive();
        return mensaje.getString(CAMPO_CONTENIDO);
    } 
}

Introducción a JMS

Introducción

JMS (Java Message Service) es la solución tecnológica ofertada por Java para sistemas de comunicación asíncronos.

Arquitectura de JMS

En la arquitectura JMS participan las siguientes entidades:
  • Clientes JMS. Son las aplicaciones que envian y/o reciben mensajes a través de JMS.
  • Mensajes. Son los datos intercambiados.
  • Destinos JMS. Son los almacenes intermedios donde se almacenan los mensajes.
Destinos JMS

Los servicios de mensajería ofrecen dos tipos de destinos:
  • Colas (tipo queue). Una aplicación (productor) genera un mensaje, lo deposita en la cola y cuando la segunda aplicación (consumidor) se conecta al servicio de mensajería, recoge el mensaje. Habitualmente en una cola sólo participan un productor y un consumidor, pero podrían existir varios productores y varios consumidores colaborando (sistemas de alto rendimiento). En este caso lo fundamental es que cada mensaje se procesa una única vez, no importa qué consumidor lo procese. Los mensajes salen de la cola en orden de llegada, el primero que se recibió es el primero que se recoge (FIFO).

  • Publicador/Suscriptores (tipo topic). Una aplicación (publicador) genera un mensaje, lo publica en el servicio JMS y lo reciben todas las aplicaciones (suscriptores) que esten suscritas al servicio JMS en que se publicó el mensaje. Habitualmente en una cola participa un publicador y muchos suscriptores, pero podrían existir varios publicadores.

Persistencia

Dado que los servicios JMS son asíncronos, los mensajes son almacenados en las colas (tipo queue ó tipo topic) hasta ser recibidos por sus destinatarios. Se debe garantizar que ningún mensaje se pierda incluso cuando el servidor JMS se apague súbitamente. Por ello, los servidores JMS suelen ofrecer la posibilidad de persistir los mensajes en algún medio no volatil como una base de datos.

Seguridad

Como en cualquier mecanismo de comunicación, la seguridad es un aspecto fundamental que siempre debe tenerse en cuenta. Son posibles dos acciones:

Autenticación. Los servidores JMS ofrecen mecanismos para autenticar tanto a los depositantes como a receptores de los mensajes, dado que de otro modo podrían entrar en las colas de mensajes de procedencia desconocida. Lo habitual son comunicaciones SSL. Siempre es posible que las aplicaciones que se comunican a través e JMS implementen sus propios sistemas de autenticación, lo que las hace más interdependientes, pero puede ser necesario si el servidor JMS no es confiable.

Cifrado. Los sistemas de persistencia antes mencionados almacenan los mensajes en una BBDD, lo que los expone a consultas indeseables por parte de terceros. Por ello es siempre conveniente que las aplicaciones cifren los datos intercambiados. El mecanismo de cifrado también se puede dejar en manos del servidor JMS o de la BBDD que persista los mensajes, si ámbos sistemas son confiables.

Disponibilidad

Cualquier sistema de comunicaciones síncrono que pase a ser asíncrono, pasará de dos a tres entidades participando en el intercambio de datos. Si se desea garantizar la tolerancia a fallos 24x7 deberá garantizarse la tolerancia a fallos tanto en las aplicaciones que intercambian los datos como en el servidor JMS.

jueves, 4 de abril de 2013

Openssl. Generar certificado autofirmado para firmar applets

Si necesitamos un certificado autofirmado para firmar applets y poder depurarlos en el navegador podemos usar OpenSSL, que es un conjunto de utilidades de criptografía.

Para instalarlas en Ubuntu deberemos ejecutar:

$ sudo apt-get install openssl

Una vez instalada, podemos verificar si todo está correcto tecleando:

$ openssl version

Lo que nos debería generar una salida del tipo "OpenSSL 1.0.1 14 Mar 2012", que obviamente dependerá de la versión instalada.

Para generar un certificado autofirmado que nos sirva para firmar applets, necesitaremos añadir la extension digitalSignature, por lo que deberemos ejecutar:

$ openssl req -new -x509 -days 3650 -extensions v3_req -keyout key.pem -out cert.pem

Esto desencadena el procedimiento de generación del certificado, el proceso nos solicitará:

- Password (dos veces)
- Código de dos letras del país [ES]
- Provincia [Madrid]
- Ciudad [Madrid]
- Organización [Dune]
- Unidad [Java]
- Nombre [egdp1970]
- eMail [egdp1970@gmail.com]

$ openssl pkcs12 -export -in cert.pem -inkey key.pem -out keystore.p12

Esto desencadena el procedimiento de volcado del certificado en un keystore tipo PKCS12, el proceso nos solicitará:

- Password del certificado (la misma que tecleamos antes dos veces)
- Password del keystore pkcs12 que se va a crear (dos veces)

Nota. v3_req es uno de los niveles de requisitos definicos en el fichero /etc/ssl/openssl.cnf para que el certificado autofirmado sirva para firmar applets deberemos asegurarnos de que contiene:

    [ v3_req ]
    keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment

viernes, 22 de marzo de 2013

Validacion de certificados mediante OCSP en Java

Introducción.

En esta entrada vamos a mostrar como realizar una petición a un servidor OCSP para determinar el estado de revocación de un certificado.

En el ejemplo se han usado las librerías Bouncy Castle (JDK 1.5 - JDK 1.7) disponibles en los repositorios MAVEN.

Dependencias.

Las dependencias del proyecto son:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>es.ine.sgtic</groupId>
  <artifactId>cliente-ocsp</artifactId>
  <version>1.0</version>
  <dependencies>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcprov-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcprov-ext-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcpkix-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcmail-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.bouncycastle</groupId>
          <artifactId>bcpg-jdk15on</artifactId>
          <version>1.48</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
  </dependencies>
</project>


Codigo.

El código fuente del ejemplo es el siguiente:

package es.ine.sgtic;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;

public class TestOCSP {

//    private static final String URL_OCSP_RESPONDER = "http://apus.cert.fnmt.es/appsUsuario/ocsp/OcspResponder"; 

//    private static final String URL_OCSP_RESPONDER = "http://ocspape.cert.fnmt.es/ocspape/OcspResponder";
//    private static final String URL_OCSP_RESPONDER = "http://ocsp.dnie.es";
    private static final String URL_OCSP_RESPONDER = "http://ocsp.dnielectronico.es/";
  
    public static void main (String args[]) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, OCSPException, OperatorCreationException{

        //Cargamos el almacen de certificados que contiene el certificado a verificar
        final InputStream fis = TestOCSP.class.getClassLoader().getResourceAsStream("almacen.p12");
        final KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(fis,"passwordAlmacen".toCharArray());

        //Sacamos el certificado del que necesitamos conocer su estado de revocacion
        final X509Certificate certAnalizable = (X509Certificate)ks.getCertificate("
aliasCert");

        //Sacamos el certificado raíz de la cadena de certificación
        final Certificate[] chain = ks.getCertificateChain("
aliasCert");
        final X509Certificate certRaiz = (X509Certificate)chain[1];

        //Se carga el proveedor necesario para la petición OCSP
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        //Generamos el ID del cetificado que estamos buscando
        final CertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build().get(CertificateID.HASH_SHA1), new X509CertificateHolder(certRaiz.getEncoded()), certAnalizable.getSerialNumber());

        //Instanciamos el generador de requestOCSP
        final OCSPReqBuilder ocspPeticionBuilder = new OCSPReqBuilder();
        ocspPeticionBuilder.addRequest(id);
//
//        //create details for nonce extension
//        final BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
//        final Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true, new DEROctetString(nonce.toByteArray()));
//        ocspPeticionBuilder.setRequestExtensions(new Extensions(new Extension[] { ext }));

        //Se genera la petición con el certificado a verificar y su número de serie
        final OCSPReq ocspPeticion = ocspPeticionBuilder.build();

        //Se establece la conexión HTTP con el ocsp del DNIe
        final URL url = new URL(URL_OCSP_RESPONDER);
        final HttpURLConnection con = (HttpURLConnection)url.openConnection();

        //Se configuran las propiedades de la petición HTTP
        con.setRequestProperty("Content-Type", "application/ocsp-request");
        con.setRequestProperty("Accept", "application/ocsp-response");
        con.setDoOutput(true);
        final OutputStream out = con.getOutputStream();
        final DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out));

        //Se obtiene la respuesta del servidos OCSP del DNIe
        dataOut.write(ocspPeticion.getEncoded());
        dataOut.flush();
        dataOut.close();

        //Se parsea la respuesta y se obtiene el estado del certificado retornado por el OCSP
        final InputStream in = (InputStream)con.getContent();
        final OCSPResp ocspRespuesta = new OCSPResp(in);
        int estado = ocspRespuesta.getStatus();

        //Analizamos la respuesta
        if (estado==OCSPResp.SUCCESSFUL) {
            System.out.println("OCSP-RESPONDER: PETICION TRATADA CORRECTAMENTE");
            System.out.println(tratarRespuestaOK(ocspRespuesta));
        } else if (estado==OCSPResp.MALFORMED_REQUEST) {
            System.out.println("OCSP-RESPONDER: ERROR. PETICION MAL FORMADA");
        } else if (estado==OCSPResp.INTERNAL_ERROR) {
            System.out.println("OCSP-RESPONDER: ERROR. ERROR INTERNO DEL SERVIDOR");
        } else if (estado==OCSPResp.TRY_LATER) {
            System.out.println("OCSP-RESPONDER: ERROR. REPETIR LA PETICION MAS TARDE");
        } else if (estado==OCSPResp.SIG_REQUIRED) {
            System.out.println("OCSP-RESPONDER: ERROR. PETICION DEBE SER FIRMADA");
        } else if (estado==OCSPResp.UNAUTHORIZED) {
            System.out.println("OCSP-RESPONDER: ERROR. PETICION OCSP NO ESTA AUTORIZADA");
        } else {
            System.out.println("OCSP-RESPONDER: ERROR. TIPO DE RESPUESTA DESCONOCIDO");
        }
    }

    private static String tratarRespuestaOK(final OCSPResp ocspRespuesta) throws OCSPException, IOException {

        //TODO:Afinar este método
      
        final Object responseObject = ocspRespuesta.getResponseObject();
        final BasicOCSPResp basicOCSPResp = (BasicOCSPResp) responseObject;
      
        final SingleResp[] responses = basicOCSPResp.getResponses();
        if (responses.length == 1) {
            final SingleResp resp = responses[0];
            final Object status = resp.getCertStatus();
            if (status == CertificateStatus.GOOD) {
                return "ESTADO DEL CERTIFICADO: VALIDO";
            }
            else if (status instanceof org.bouncycastle.ocsp.RevokedStatus) {
                return "ESTADO DEL CERTIFICADO: REVOCADO";
            }
            else {
                return "ESTADO DEL CERTIFICADO: DESCONOCIDO";
            }
        }
        return "VARIAS RESPUESTAS. AFINAR EL CODIGO";
    }
}


Alternativas.

Una posible alternativa a picar código en Java es usar openssl.

Las peticiones a los cuatro servidores OCSP indicados en el código equivaldrían a:

openssl ocsp -url http://apus.cert.fnmt.es/appsUsuario/ocsp/OcspResponder -resp_text -issuer FNMT.pem -cert aliasCert.pem
openssl ocsp -url http://ocspape.cert.fnmt.es/ocspape/OcspResponder -resp_text -issuer FNMT.pem -cert
aliasCert.pem
openssl ocsp -url http://ocsp.dnie.es -resp_text -issuer FNMT.pem -cert aliasCert.pem
openssl ocsp -url http://ocsp.dnielectronico.es/ -resp_text -issuer FNMT.pem -cert aliasCert.pem

Pendiente.

Sería interesante realizar las pruebas con un certificado válido, uno revocado y uno caducado (expirado) para analizar a fondo la respuesta contenida en el objeto BasicOCSPResp. En las pruebas, con un certificado expirado tenemos la salida:

OCSP-RESPONDER: PETICION TRATADA CORRECTAMENTE
ESTADO DEL CERTIFICADO: DESCONOCIDO


Hay que profundizar también en el mecanismo para firmar la petición, dado que por ejemplo el servidor: http://apus.cert.fnmt.es/appsUsuario/ocsp/OcspResponder pide la petición firmada.

Por último conviene aclarar si cualquier servidor OCSP ofrece información sobre cualquier certificado o solo por los emitidos por ciertas CA, de ahí el exceso de respuestas con: "ESTADO DEL CERTIFICADO: DESCONOCIDO".

Enlaces de interes:

http://tools.ietf.org/html/rfc2560
http://www.bouncycastle.org/latest_releases.html

sábado, 16 de marzo de 2013

Subreports con JasperReports usando JRBeanCollectionDataSource

Introducción

Siempre que usamos Jasperreports para montar informes, pasamos todos los parámetros, incluso las etiquetas, desde el aplicativo Java. Esto ofrece varias ventajas:
  • Si por ejemplo cambia el motor de la BBDD de un aplicativo en producción, únicamente ajustamos la configuración de Hibernate sin regenerar los informes.
  • Si necesitamos el informe en varios idiomas, controlamos el idioma del usuario desde el aplicativo Java (por ejemplo recogiendo las preferencias del navegador con JSF).
Hasta la fecha siempre montábamos informes simples, es decir, con una única banda de detalle. Ahora que hemos necesitado montar algunos informes con varias bandas de detalle hemos necesitado incluir "subreports" y, como en mecanismo de envío de las distintas "JRBeanCollectionDataSource" asociadas a cada banda de detalle no me ha parecido trivial, dejo resumidos los pasos a realizar.

Clase Java para generar el informe

package org.dune.app;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.util.JRLoader;

import org.dune.pojo.Coche;
import org.dune.pojo.Telefono;

public class TestReport {

    //Datos cabecera
    private static Map<String,Object> datosCabeceraReport;
   
    //Datos detalle
    private static List<Coche> datosDetalleReport;
    private static List<Telefono> datosDetalleSubReport;
   
    public static void main(String args[]) throws JRException{
       
        //Preparamos los datos de cabecera del report, cuando en
        //el report.jrxml añadamos los Parameter "titulo" y "fecha"
        //tendremos que asegurarnos que sean de tipo "String"
        datosCabeceraReport = new HashMap<String,Object>();
        datosCabeceraReport.put("titulo", "Inventario de bienes");
        datosCabeceraReport.put("fecha", "15/03/2013");
       
        //Preparamos los datos de detalle del report
        datosDetalleReport = new ArrayList<Coche>();
        datosDetalleReport.add(new Coche("1234-AAA", "Peugeot 207"));
        datosDetalleReport.add(new Coche("5678-AAA", "Peugeot 307"));
        datosDetalleReport.add(new Coche("9012-AAA", "Peugeot 407"));
       
        //Preparamos los datos de detalle del subreport
        datosDetalleSubReport = new ArrayList<Telefono>();
        datosDetalleSubReport.add(new Telefono("111111111", "Nokia 1000"));
        datosDetalleSubReport.add(new Telefono("222222222", "Nokia 2000"));
        datosDetalleSubReport.add(new Telefono("333333333", "Nokia 3000"));
       
        //Añadimos los datos de detalle del subreport al Map de parametros del
        //padre y montandolos sobre un JRBeanCollectionDataSource. Cuando en
        //el report.jrxml añadamos el Parameter "datosSubRep" tendremos que
        //forzar el tipo a: "net.sf.jasperreports.engine.data.JRBeanCollectionDataSource"
        datosCabeceraReport.put("datosSubRep", new JRBeanCollectionDataSource(datosDetalleSubReport));
       
        //Montamos el objeto JRMapCollectionDataSource con los datos de la bandas de detalle del report
        final JRBeanCollectionDataSource datosReport = new JRBeanCollectionDataSource(datosDetalleReport);
       
        //Generamos el informe compilado
        creaInforme(datosCabeceraReport, datosReport);
    }
   
    private static void creaInforme(final Map<String,Object> datosCabeceraReport, final JRBeanCollectionDataSource datosReport) throws JRException{
       
        //Cargamos el informe previamente compilado
        final String urlReport = TestReport.class.getResource("/report.jasper").getFile();
        final JasperReport informe = (JasperReport) JRLoader.loadObject(urlReport);

        //Añadimos los datos
        final JasperPrint informePrint = JasperFillManager.fillReport(informe, datosCabeceraReport, datosReport);

        //Exportamos a pdf
        JasperExportManager.exportReportToPdfFile(informePrint,"./src/main/resources/report.pdf");
    }
}


Cuestiones fundamentales

Los detalles fundamentales se han introducido como comentarios en el código de la clase anterior, pero merece la pena repasarlos:
  • Los "parámetros estáticos" del report padre, esto es, todos aquellos que no aparecen en la banda de detalle del informe padre, se pasan al JasperFillManager dentro del objeto: final Map<String,Object> datosCabeceraReport.
  • Los datos de detalle del subreport (lista de telefonos) se pasan al JasperFillManager como un parámetro de tipo JRBeanCollectionDataSource dentro del objeto: final Map<String,Object> datosCabeceraReport .
  • Los datos de detalle del report (lista de coches) padre se pasan al JasperFillManager dentro del objeto: final JRBeanCollectionDataSource datosReport.
  • En el report.jrxml hemos definido los parámetros: titulo, fecha y datosSubRep. Los dos primeros, de tipo String, se usarán en la banda título del report padre. El tercer parámetro, de tipo JRBeanCollectionDataSource, se usará en la propiedad "Data Source Expression" del subreport: $P{datosSubRep}.
  • En el report.jrxml hemos definido los "Fields": matricula y modelo, que son las columnas del área de detalle y que son los atributos de la clase Coche.java.
  • En el subreport.jrxml hemos definido los "Fields": numero y tipo, que son las columnas del área de detalle y que son los atributos de la clase Telefono.java.
Clases java con los "POJO" de las bandas de detalle

La clase Coche.java se usará en la banda de detalle del informe padre.

package org.dune.pojo;

public class Coche {

    private String matricula;
    private String modelo;
       
    public Coche(final String matricula, final String modelo) {
        super();
        this.matricula = matricula;
        this.modelo = modelo;
    }

    public String getMatricula() {
        return matricula;
    }

    public void setMatricula(String matricula) {
        this.matricula = matricula;
    }

    public String getModelo() {
        return modelo;
    }

    public void setModelo(String modelo) {
        this.modelo = modelo;
    }
}


La clase Telefono.java se usará en la banda de detalle del subinforme.

package org.dune.pojo;

public class Telefono {

    private String numero;
    private String tipo;

    public Telefono(final String numero, final String tipo) {
        this.numero = numero;
        this.tipo = tipo;
    }

    public String getNumero() {
        return numero;
    }

    public void setNumero(String numero) {
        this.numero = numero;
    }

    public String getTipo() {
        return tipo;
    }

    public void setTipo(String tipo) {
        this.tipo = tipo;
    }
}


report.jrxml

El aspecto general del informe padre en el diseñador es:


El código xml del informe padre es:

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="report1" language="groovy" pageWidth="595" pageHeight="842" columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
    <property name="ireport.zoom" value="1.4641000000000008"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <parameter name="titulo" class="java.lang.String"/>
    <parameter name="fecha" class="java.lang.String"/>
    <parameter name="datosSubRep" class="net.sf.jasperreports.engine.data.JRBeanCollectionDataSource"/>
    <field name="matricula" class="java.lang.String"/>
    <field name="modelo" class="java.lang.String"/>
    <title>
        <band height="51" splitType="Stretch">
            <rectangle radius="5">
                <reportElement mode="Transparent" x="2" y="4" width="547" height="44"/>
            </rectangle>
            <textField>
                <reportElement x="61" y="26" width="488" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{fecha}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="6" y="6" width="55" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[TITULO:]]></text>
            </staticText>
            <staticText>
                <reportElement x="6" y="26" width="55" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[FECHA:]]></text>
            </staticText>
            <textField>
                <reportElement x="61" y="6" width="488" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{titulo}]]></textFieldExpression>
            </textField>
        </band>
    </title>
    <pageHeader>
        <band height="73">
            <rectangle radius="5">
                <reportElement positionType="Float" mode="Opaque" x="2" y="50" width="547" height="22" backcolor="#C68B8B"/>
            </rectangle>
            <rectangle radius="5">
                <reportElement mode="Opaque" x="2" y="1" width="547" height="22" forecolor="#010101" backcolor="#C68B8B"/>
            </rectangle>
            <staticText>
                <reportElement x="6" y="2" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[TELEFONOS:]]></text>
            </staticText>
            <staticText>
                <reportElement positionType="Float" x="6" y="51" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[COCHES:]]></text>
            </staticText>
            <subreport>
                <reportElement x="0" y="24" width="555" height="24"/>
                <dataSourceExpression><![CDATA[$P{datosSubRep}]]></dataSourceExpression>
                <subreportExpression class="java.lang.String"><![CDATA["/home/eduardo/trabajo/workspaces-helios/wksTestJasper/TestJasper/src/main/resources/subreport.jasper"]]></subreportExpression>
            </subreport>
        </band>
    </pageHeader>
    <columnHeader>
        <band height="25" splitType="Stretch">
            <rectangle radius="5">
                <reportElement mode="Transparent" x="2" y="2" width="547" height="22"/>
            </rectangle>
            <staticText>
                <reportElement x="104" y="3" width="445" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[MODELO]]></text>
            </staticText>
            <staticText>
                <reportElement x="6" y="3" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[MATRICULA]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="27" splitType="Stretch">
            <rectangle radius="5">
                <reportElement stretchType="RelativeToBandHeight" isPrintRepeatedValues="false" mode="Transparent" x="2" y="2" width="547" height="22" isPrintWhenDetailOverflows="true"/>
            </rectangle>
            <textField>
                <reportElement x="6" y="3" width="100" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{matricula}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="104" y="3" width="445" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{modelo}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>


subreport.jrxml

El aspecto general del subinforme en el diseñador es:


El código xml del subinforme padre es:

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="subreport" language="groovy" pageWidth="555" pageHeight="100" columnWidth="555" leftMargin="0" rightMargin="0" topMargin="0" bottomMargin="0">
    <property name="ireport.zoom" value="1.464100000000001"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <field name="numero" class="java.lang.String"/>
    <field name="tipo" class="java.lang.String"/>
    <columnHeader>
        <band height="25" splitType="Stretch">
            <rectangle radius="5">
                <reportElement mode="Transparent" x="2" y="2" width="547" height="22"/>
            </rectangle>
            <staticText>
                <reportElement x="6" y="3" width="100" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[NUMERO]]></text>
            </staticText>
            <staticText>
                <reportElement x="106" y="3" width="441" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[TIPO]]></text>
            </staticText>
        </band>
    </columnHeader>
    <detail>
        <band height="25" splitType="Stretch">
            <rectangle radius="5">
                <reportElement stretchType="RelativeToTallestObject" isPrintRepeatedValues="false" mode="Transparent" x="2" y="1" width="547" height="22" isPrintWhenDetailOverflows="true"/>
            </rectangle>
            <textField>
                <reportElement x="106" y="2" width="441" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{tipo}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="6" y="2" width="100" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{numero}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>


Aspecto del informe generado



Dependencias

Las dependencias Maven del proyecto son:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.dune</groupId>
  <artifactId>TestJasper</artifactId>
  <version>1.0</version>
  <dependencies>
      <dependency>
          <groupId>net.sf.jasperreports</groupId>
          <artifactId>jasperreports</artifactId>
          <version>4.0.0</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
      <dependency>
          <groupId>org.codehaus.groovy</groupId>
          <artifactId>groovy-all</artifactId>
          <version>1.7.5</version>
          <type>jar</type>
          <scope>compile</scope>
      </dependency>
  </dependencies>
</project>