Páginas

jueves, 28 de junio de 2012

Apache y PHP en Ubuntu 12.04

Instalación de Apache

Para instalar Apache en Ubuntu 12.04 deberemos teclear:

sudo apt-get install apache2

Tras esto, tendremos instalado el apache, cosa fácilmente verificable tecleando en el navegador la URL:

http://localhost

Si queremos cambiar el contenido de la página de inicio deberemos trabar en la carpeta /var/www, donde tenemos el fichero index.html que también podremos ajustar

Para detener y reiniciar el servidor, tenemos el script "/etc/init.d/apache" que admite distintos parámetros

sudo /etc/init.d/apache2 stop
sudo /etc/init.d/apache2 start
sudo /etc/init.d/apache2 restart

Para instalar PHP5 deberemos teclear en la línea de comandos:

sudo apt-get install php5 libapache2-mod-php5

Después reiniciamos el apache con:

sudo /etc/init.d/apache restart

Si todo ha funcionado, para ver los detalles de la instalación del PHP5 podemos crear el fichero /var/www/info.php con el siguiente contenido:

<?php
phpinfo();
?>
 
Al meter luego en el navegador la URL:http://localhost/info.php veremos que la instalación de PHP está lista.

Nota. Con todos estos pasos tendremos instalado Apache como servicio del sistema, por lo que siempre se iniciará al arrancar el servidor. Si necesitamos que Apache sólo arranque cuando queramos, tendremos que quitar el lnk del rc.

El inicio automático de servicios está controlado en los ficheros:  

/etc/rc{level}.d

donde {level} se refiere a runlevel, que es el modo de operación de los sistemas operativos linux. Hay diferentes runlevel:
  • 0. Ordenador apagado.
  • 1-5. Ordenador encendido, se diferencian en el número de servicios iniciados.
  • 6. Ordenador reiniciando.
Para sacar el apache de todos los niveles operativos donde se ha configurado como servicio arrancable, el comando adecuado es:

sudo update-rc.d -f apache2 remove

Nos mostrará una salida del tipo:

Removing any system startup links for /etc/init.d/apache2 ...
   /etc/rc0.d/K09apache2
   /etc/rc1.d/K09apache2
   /etc/rc2.d/S91apache2
   /etc/rc3.d/S91apache2
   /etc/rc4.d/S91apache2
   /etc/rc5.d/S91apache2
   /etc/rc6.d/K09apache2

miércoles, 20 de junio de 2012

Montar un pendrive en modo lectura/escritura

En ocasiones el pendrive queda montado en modo read-only, por lo que no podemos eliminar el contenido del mismo ni formatearlo.

Una forma solucionarlo es volver a montar la unidad en modo rw:

sudo mount -o remount,rw '/media/523A-7B67'

miércoles, 30 de mayo de 2012

EJB 3.0 vs JSF2+Spring+Hibernate

Vamos a comentar brevemente lo que se define en la especificación EJB 3.0, para luego compararlo con las funcionlidades equivalentes ofrecidas por la terna JSF2+Spring+Hibernate que aplicamos en nuestro equipo de desarrollo:

EJB 3.0.

Es una de los apartados de la especificación J2EE. La especificación define los objetos de negocio (los EJB) y el comportamiento que deben tener los servidores de aplicaciones que quieran cumplir la especificación en su área de gestión de estos EJB, a este área de gestión se le llama comunmente Contenedor.

Entity Bean: Son los objetos del lado del servidor que almacena los datos. Hay dos tipos:
  • Container Managed Bean. El contenedor se encarga de almacenar y recuperar los datos del objeto de entidad mediante el mapeo de las columnas de una tabla con los atributos de un objeto.
  • Bean Managed Persistence. El propio objeto entidad se encarga, mediante una base de datos u otro mecanismo, de almacenar y recuperar los datos a los que se refiere, esto es los mecanismos de persistencia los ha generado el programador.
Session Bean: EJB de Sesión (Session EJBs): gestionan el flujo de la información entre el servidor y el usuario. Puede haber dos tipos:
  • Statefull. Cada bean de sesión con estado, almacena los datos que persisten de una pantalla a otra como un carrito de la compra. Ofrece la funcionalidad común a N pantallas. Cuando el cliente termina el bean se borra.
  • Stateless. Cada bean de sesión sin estado, almacena los datos asociados a una pantalla y la lógica de negocio asociada a una pantalla. La pantalla X visualizada por cada uno de los 1000 usuarios conectados simultáneamente a la página del detalle de un banco tienen su propio bean. Los contenidos de las variables no se conservan entre llamada y llamada.

Message-Drive Bean: Son beans para tareas asíncrono. Usan el API JMS (Java Messaging System). Los hay de varios tipos:

  • Queue. N-Productores -> 1-Consumidor. Hay N generadores de mensajes que se apuntan a una cola donde dejan sus peticiones y un solo consumidor las va atendiendo por orden de llegada.
  • Topic. N-Productores -> N-Consumidores. Hay N generadores de mensajes que se envían al topic, desde allí se envía una copia para cada uno de los consumidores, todos ellos reciben cada mensaje.
Equivalencias con nuestra infraestructura (JSF+Spring+Hibernate):

Container Managed Bean. Clases mapeadas con JPA + hibernate. Usamos hibernate como motor de HQL e implementador de JPA. Utilizamos este tipo de objetos en la capa de acceso a datos cuando la BBDD es relacional.
Bean Managed Persistence. Spring JDBC Template. Utilizamos este tipo de objetos en la capa de acceso a datos cuando la BBDD no es relacional.
Statefull. Los SessionBean de JSF (JSF ofrece además los ApplicationBean, que viven durante el tiempo que el portal está levantado)
Stateless. Los RequestBean de JSF (JSF ofrece también algo mas, los ViewBean que dan respaldo a N peticiones siempre que se navegue en la misma pantalla)
Queue. No usamos JMS en ningún proyecto, usamos el planificador Quartz que viene con Spring para los tratamientos asíncronos donde cierto "consumidor" debe tratar las peticiones que se han almacenado en una cola, en nuestro caso esta cola es la BBDD. Si quisiésemos trabajar con JMS usaríamos el JmsTemplate de Spring.
Topic. Esta funcionalidad no se nos ha planteado en ningún proyecto, pero si se plantease usaríamos el JmsTemplate de Spring.

domingo, 27 de mayo de 2012

Manejo del DNIe con javax.smartcardio en Ubuntu

Una pequeña aplicación de ejemplo de acceso al DNIe para firmar y verificar la firma electrónica con el API javax.smartcardio de Oracle.

Son dos clases:
  • AppTest. Clase con el main que desencadena el ejemplo
  • UtilsDnie. Clase con los métodos para detectar el lector, pedir el dni, firmar y verificar la firma
Antes de comenzar aclarar que en el código se incluyen a "fuego" las ubicaciones de un par de librería, por lo que conviene ajustar estos path antes de probar.
  • Librería /usr/lib/i386-linux-gnu/libpcsclite.so de manejo de lectores de tarjetas.
  • Librería /usr/lib/opensc-pkcs11.so de manejo de las funcionalidades criptográficas del DNIe.
Clase AppTest
 
package ord.edu.app;

public class AppTest {

    private static final String TEXTO_A_FIRMAR  = "texto de ejemplo a firmar";
    private static final String PIN_ACCESO_DNIE = "xxxxxxxxxx";

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

        final UtilsDNIe ud = new UtilsDNIe();

        //Solicitamos el DNIe
        if(ud.solicitaDNI()) {

            //Si se ha insertado el DNIe firmamos
            final byte[] firma = ud.firmaDatos(PIN_ACCESO_DNIE,
                    TEXTO_A_FIRMAR.getBytes());

            //Verificamos la firma
            final boolean resultadoFirma = ud.verificaFirmaDatos(PIN_ACCESO_DNIE,
                    TEXTO_A_FIRMAR.getBytes(),
                    firma);
            System.out.println(resultadoFirma);
        }
    }
}


Clase UtilsDnie
 
package ord.edu.app;

import java.io.ByteArrayInputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.Certificate;
import java.util.Enumeration;
import java.util.List;

import javax.smartcardio.Card;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.TerminalFactory;

import sun.security.pkcs11.SunPKCS11;

public class UtilsDNIe {

    private static final String CERT_AUTEN = "CertAutenticacion";
    private static final String CERT_FIRMA = "CertFirmaDigital";
    private static final long TIMEOUT_INSERCION_DNIE = 5000;

    /**
     * Solicita al usuario la inclusión del DNIe en cualquiera de los lectores
     * de tarjetas PC/SC detectados.
     *
     * PC/SC (Personal Computer/Smart Card) es un conjunto de especificaciones para la
     * integración de tarjetas inteligentes en ordenadores personales. En particular se
     * define un API de programación que permite a los desarrolladores trabajar de forma
     * uniforme con lectores de tarjetas de distintos fabricantes (que cumplan con la
     * especificación).
     *
     * Como tenemos instalada la libería libpcsclite.so, accedemos al DNIe mediante el
     * proveedor SunPCSC, ajustando la propiedad "sun.security.smartcardio.library" del
     * sistema tal y como se documenta en la URL:
     * http://docs.oracle.com/javase/6/docs/technotes/guides/security/
     * SunProviders.html#SunPCSCProvider
     *
     * @return Card conectado al DNIe
     * @throws CardException Se se ha detectado algú problema con la tarjeta
     */
    public boolean solicitaDNI() throws CardException  {

        System.setProperty("sun.security.smartcardio.library",
                "/usr/lib/i386-linux-gnu/libpcsclite.so");

        //Recorremos la lista de lectores detectado pidiendo el DNIe
        Card card = null;
        CardTerminal terminal;
        final TerminalFactory factory = TerminalFactory.getDefault();
        final List<CardTerminal> terminals = factory.terminals().list();

        for (int i = 0; i < terminals.size(); i++) {

            terminal = terminals.get(i);
            //Por darle un punto interactivo indicamos al usuario
            //que inserte el DNIe y esperamos un poco
            System.out.println("Inserte su DNIe en: "+terminal.getName());
            terminal.waitForCardPresent(TIMEOUT_INSERCION_DNIE);

            if(terminal.isCardPresent()) {
                System.out.println("Detectada tarjeta, conectando");
                //Conectamos usando cualquier protocolo "*"
                card = terminal.connect("*");
                break;
            } else {
                System.out.println("No se ha detectado el DNIe en: "+terminal.getName());
            }           
        }

        //Se ha detectado el DNIe, devolvemos true y desconectamos reseteando la conexión
        if (card!=null){
            card.disconnect(true);
            return true;
        }

        //No se ha detectado el DNIe, devolvemos false
        return false;
    }

    /**
     * Firmamos los datos recibidos en el DNIe
     *
     * @param pinAcceso Número PIN de acceso al DNIe
     * @param datos Datos a firmar
     * @return Datos firmados
     * @throws Exception Problemas durante el proceso de firmado
     */
    public byte[] firmaDatos(String pinAcceso, byte[] datos) throws Exception {

        //Ajustamos el proveedor PKCS11
        final SunPKCS11 sunpkcs11 = ajustaPKCS11ProviderUbuntu();

        //Accedemos al almacen de certificados del DNIe con el pin.
        final KeyStore keyStore = KeyStore.getInstance("PKCS11", sunpkcs11);
        char[] pin = pinAcceso.toCharArray();
        keyStore.load(null, pin);

        //Recogemos la lista de alias de certificados del dispositivo
        final Enumeration<String> enumeration = keyStore.aliases();

        //Buscamos el alias del certificado de Firma, en el DNIe hay dos, uno para
        //firmar (CertFirmaDigital) y otro para autenticarse (CertAutenticacion)
        String alias = null;
        while(enumeration.hasMoreElements()) {

            alias = enumeration.nextElement().toString();
            if(CERT_FIRMA.equals(alias)) {
                break;
            }
        }

        //Se se ha encontrado el certificado de firma seguimos
        if(CERT_FIRMA.equals(alias)) {

            //Recogemos la clave privada del certificado de firma
            final Key key = keyStore.getKey(CERT_FIRMA, pin);

            //Firmamos (en el dnie) los datos recibidos
            final Signature firmador = Signature.getInstance("SHA1withRSA");
            firmador.initSign((PrivateKey)key);
            firmador.update(datos);
            final byte[] firma = firmador.sign();

            //devolvemos la firma
            return firma;

        } else {
            throw new Exception("No se encontro el certificado CertFirmaDigital de firma");
        }
    }

    /**
     * Validamos la firma recibida en el DNIe
     *
     * @param pinAcceso Número PIN de acceso al DNIe
     * @param datos Datos que se han firmado
     * @param firma Firma a verificar
     * @return Resultado de la validación de la firma
     * @throws Exception Problemas durante el proceso de validación
     */
    public boolean verificaFirmaDatos(String pinAcceso, byte[] datos, byte[] firma)
        throws Exception {

        //Ajustamos el proveedor PKCS11
        final SunPKCS11 sunpkcs11 = ajustaPKCS11ProviderUbuntu();

        //Accedemos al almacen de certificados del DNIe con el pin.
        final KeyStore keyStore = KeyStore.getInstance("PKCS11", sunpkcs11);
        char[] pin = pinAcceso.toCharArray();
        keyStore.load(null, pin);

        //Recogemos la lista de alias de certificados del dispositivo
        final Enumeration<String> enumeration = keyStore.aliases();

        //Buscamos el alias del certificado de Firma, en el DNIe hay dos, uno para
        //firmar (CertFirmaDigital) y otro para autenticarse (CertAutenticacion)
        String alias = null;
        while(enumeration.hasMoreElements()) {

            alias = enumeration.nextElement().toString();
            if(CERT_FIRMA.equals(alias)) {
                break;
            }
        }

        //Se se ha encontrado el certificado de firma seguimos
        if(CERT_FIRMA.equals(alias)) {

            //Recogemos el certificado de firma
            final Certificate certificado = keyStore.getCertificate(alias);

            //Validamos la firma de los datos (en el dnie)
            final Signature verificadorFirma = Signature.getInstance("SHA1withRSA");
            verificadorFirma.initVerify(certificado.getPublicKey());
            verificadorFirma.update(datos);

            return verificadorFirma.verify(firma);

        } else {
            throw new Exception("No se encontro el certificado CertFirmaDigital de firma");
        }
    }

    /**
     * Ajustamos el proveedor SunPKCS11 apuntando a las librerías nativas pkcs11
     * del sistema. El proveedor SunPKCS11 no realiza funciones criptográficas
     * por si mismo, sino que permite a las aplicaciones Java usar los APIS JCA/JCE
     * para acceder a estas funcionalidades en las librerías nativas pkcs11.   
     */
    private SunPKCS11 ajustaPKCS11ProviderUbuntu() {

        final String pkcs11config = "name = DNIe\nlibrary = /usr/lib/opensc-pkcs11.so\n";
        final SunPKCS11 sunpkcs11 = new SunPKCS11(
                new ByteArrayInputStream(pkcs11config.getBytes()));
        Security.addProvider(sunpkcs11);
        return sunpkcs11;
        //TODO: Amplicar a otros sistemas operativos
    }
}

viernes, 25 de mayo de 2012

Boolean.TRUE versus true

El otro día comentábamos en el trabajo si era mejor, en cuanto a rendimiento, que en un método booleano se devolviese true o se devolviese Boolean.TRUE. Como en estas cuestiones mas vale una prueba que 2 horas de elucubraciones, tiramos el siguiente código:

public class AppTest {

    private static final int NUM_VUELTAS = 1000000000;

    public static void main(String[] args) throws Exception {
      
        long inic = System.currentTimeMillis();
        for (int x=0;x<NUM_VUELTAS;x++){
            for (int y=0;y<NUM_VUELTAS;y++){
                returnTrue1();
            }
        }
        System.out.println("return (boolean)true:"+(System.currentTimeMillis()-inic)+"ms");
      
        inic = System.currentTimeMillis();
        for (int x=0;x<NUM_VUELTAS;x++){
            for (int y=0;y<NUM_VUELTAS;y++){
                returnTrue2();
            }
        }
        System.out.println("return (boolean)true:"+(System.currentTimeMillis()-inic)+"ms");
      
        inic = System.currentTimeMillis();
        for (int x=0;x<NUM_VUELTAS;x++){
            for (int y=0;y<NUM_VUELTAS;y++){
                returnTrue3();
            }
        }
        System.out.println("return (boolean)true:"+(System.currentTimeMillis()-inic)+"ms");
      
        inic = System.currentTimeMillis();
        for (int x=0;x<NUM_VUELTAS;x++){
            for (int y=0;y<NUM_VUELTAS;y++){
                returnTrue4();
            }
        }
        System.out.println("return (boolean)true:"+(System.currentTimeMillis()-inic)+"ms");
    }
 
    private static boolean returnTrue1(){
        return true;
    }
 
    private static boolean returnTrue2(){
        return Boolean.TRUE;
    }
 
    private static Boolean returnTrue3(){
        return true;
    }
 
    private static Boolean returnTrue4(){
        return Boolean.TRUE;
    }
}
Los resultados, tras la primera ejecución los resultados parecían confusos:

return (boolean)true:6ms
return (boolean)Boolean.TRUE:4ms
return (Boolean)true:5ms
return (Boolean)Boolean.TRUE:4ms
Pero alterando el orden de las llamadas la confusión se disipó:

return (boolean)Boolean.TRUE:7ms
return (Boolean)true:5ms
return (Boolean)Boolean.TRUE:4ms
return (boolean)true:4ms
return (Boolean)true:7ms
return (Boolean)Boolean.TRUE:4ms
return (boolean)true:4ms
return (boolean)Boolean.TRUE:4ms




return (Boolean)Boolean.TRUE:7ms
return (boolean)true:4ms
return (boolean)Boolean.TRUE:4ms
return (Boolean)true:5ms

Resumiendo los tiempos:

  • returnTrue1: 6+4+4+4 --> 18 
  • returnTrue2: 4+7+4+4 --> 19 
  • returnTrue3: 5+5+7+5 --> 22 
  • returnTrue4: 4+4+7+7 --> 22

Analizando qué hace cada método:
  • returnTrue1. Devuelve un true. 
  • returnTrue2. Lee a una dirección de memoria y devuelve un "autounboxing" de su contenido. 
  • returnTrue3. Hace un "autoboxing" sobre un true. 
  • returnTrue4. Devuelve una dirección  de memoria.
A mi entender, los resultados de más rápido a más lento deberían haber sido:
  • returnTrue1 y returnTrue4. Igual de rápidas. 
  • returnTrue3.
  • returnTrue2.
Conclusión. Discutir sobre qué es más óptimo en este tema es perder el tiempo, dado que parece depender en gran medida de las optimizaciones internas del compilador. Es mas interesante elegir la opción que de mas claridad al código. 

Nota. Sobre autoboxing y autounboxing.

autounboxing. Es la transformación de objeto a tipo básico, por dentro se realiza una llamada a Boolean.booleanValue(Boolean.TRUE):

   boolean boolVar = Boolean.TRUE;

autoboxing. Es la otra transformación de tipo básico a objeto, por dentro se realiza una llamada a Boolean.valueOf(true):

   Boolean boolVar = true;

jueves, 24 de mayo de 2012

Habilitar USB en Virtualbox sobre Ubuntu 12.04

En un Virtualbox instalado sobre Ubuntu 12.04 hemos tenido problemas para poder acceder a dispositivos USB, concretamente al lector de tarjetas C3PO LTC31

La solución pasa por:

1) Añadir nuestro usuario al grupo vboxusers mediante el siguiente comando, donde xxxxxxxx el el idusuario que hace login: 

sudo usermod -aG vboxusers xxxxxxxxxx

Tras este ajuste reiniciamos sesión (si no, no tiene efecto).

2) En la configuración de cada servidor virtual que deba acceder al dispositivo USB deberemos añadir un filtro con la opción "Añadir filtro desde dispositivo", eligiendo el dispositivo USB que necesitemos hacer visible.

Nota. Si queremos manejar dispositivos USB 2.0, deberemos añadir el "Oracle VM VirtualBox Extension Pack" en: Archivo/Preferencias/Extensiones

miércoles, 23 de mayo de 2012

Enlaces para verificación de tarjetas criptográficas

Para verificar que tenemos perfectamente instalos los módulos criptográficos de las tarjetas de la FNMT, DNIe y CACERT, tenemos estos enlaces:

DNIe: https://av-dnie.cert.fnmt.es/compruebacert/compruebacert
FNMT: https://www.cert.fnmt.es/index.php?cha=cit&sec=8&fpage=1&lang=es
CACERT: https://secure.cacert.org/index.php?id=4

Nota. Para acceder al enlace del DNIe deberemos tener un DNIe en un lector de tarjetas del ordenador. Igualmenet para acceder a la página de CACERT deberemos tener un certificado CACERT. En cualquier otro caso obtendremos un error "ssl_error_handshake_failure_alert" dado que no tenemos el certificado adecuado para negociar la conexión SSL.