GZIP y SSL en servicios Web

Al desarrollar un servicio REST es conveniente asegurar el servicio y optimizarlo, dos opciones que tenemos es utilizar comunicaciones bajo SSL y comprimir el recurso web.

Estas tareas son repetitivas y siempre que desarrollo un servicio web me toca lidiar con estos apartados y cada vez lo hago de una manera distinta así que esta vez me he decidido a documentarlo mediante un artículo para en un futuro poder tenerlo como referencia.

En este articulo voy a hablar sobre como encriptar y comprimir las comunicaciones entre un servicio REST y un cliente Android, aunque también se podrá usar una navegador, como Firefox.

El servicio REST está montado sobre una máquina Linux con PHP y desarrollado con Laravel 5.2, por otra parte en el lado del cliente se usa Android con la librería Volley para comunicaciones asincronas.

Decir que este artículo no va a tratar ni de Laravel ni de Volley, aparecerá código correspondiente a estas librerías/frameworks pero no se explicarán, así mismo la instalación y configuración inicial de apache tampoco serán explicadas.

El punto de partida es un servidor Linux con Apache y un servicio REST funcionando, por parte del cliente el punto de partida es un dispositivo Android capaz de consumir el servicio mediante Volley.

El artículo se va a dividir en 2 partes, por una parte la explicación de como hacer funcionar la compresión y por otra parte como hacer funcionar las conexiones seguras.

Comprimir la respuesta del servidor

Cuando tenemos un servicio que va a ser consumido desde un dispositivo móvil me atrevo a decir que la compresión de la respuesta es obligatoria, teniendo en cuenta que las operadoras móviles tarifican por MB consumido, comprimiendo los datos podemos reducir el tamaño en hasta 4 o 5 del tamaño original.

A diferencia de lo que mucha gente opina en internet (como por ejemplo en StackOverflow: http://stackoverflow.com/questions/21144992/android-volley-gzip-response) para hacer funcionar la compresión GZIP en volley no hace falta hacer absolutamente nada. Lo único que tenemos que configurar es nuestro servidor apache.

Primero tenemos que activar el modulo deflate que es el que nos permite realizar la compresión, para ello en la terminal ejecutamos lo siguiente

sudo a2enmod deflate

Ahora lo que tenemos que hacer es modificar el fichero /etc/apache2/apache2.conf y añadir las siguientes directivas

El commando AddOutputFilterByType DEFLATE comprimirá todo aquellos que coincida con el mime-type que le sigue, en este ejemplo comprimirá los ficheros javascript, json, xml, css...

Dependiendo del nivel de compresión que queramos podemos añadir la siguiente directiva

DeflateCompressionLevel 1-9

El valor 1 implica la mínima compresión posible y 9 la máxima. Si no especificamos comprimirá con el máximo nivel posible.

Ahora reiniciamos el servicio apache:

sudo service apache2 restart

Para hacer las pruebas me he creado un recurso simple en laravel

Donde lo que se imprime es un texto bastante grande generado mediante un Lorem ipsum generator (http://es.lipsum.com/).

Si accedemos al recurso en firefox y presionamos Ctrl + Shift + C y vamos a la pestaña “Network”

veremos lo siguiente (Si no aparece nada, una vez se esté posicionado en la pestaña network volver a cargar el recurso)

GZIP en funcionamiento

Podemos ver como el tamaño es de 89KB pero lo que se ha transimitido es 23.87KB, unas 4 veces menos, una ahorro a tener muy en cuenta. Si pulsamos sobre esta petición podremos ver lo siguiente:

Cabeceras de la Resquest y Response

Podemos ver como en las cabeceras de la petición (Request headers) podemos ver la cabecera:

Accept-Encoding: “gzip, deflate”

y en las cabeceras de la respuesta (Response headers) podemos ver la cabecera:

Content-Encoding: “gzip”

Esas 2 cabecerás es lo único necesario para que funcione la compresión GZIP. ¿Pero esto funciona en Volley? Vamos a comprobarlo, para ello vamos a usar wireshark y esnifar las comunicaciones.

En mi caso tengo mi router (192.168.1.101), mi servidor linux (192.168.1.109) y mi Android, del cual no se su ip ni me interesa. Tras una petición de Android el flujo de comunicación será el siguiente:

1 – Android manda la petición a mi router

2 – Mi router envia la petición al servidor

3 – El procesa la petición y genera la respuesta, y la manda al router

4 – Finalmente el router reenvia la petición a mi Android.

Abrimos ahora wireshark y empezamos a capturar tráfico, ahora nos dirijimos a la aplicación Android y hacemos la petición al servidor, cuando hayamos mandado la petición y hayamos recibido la respuesta pausamos wireshark.

Seguramente aunque lo hayamos estado capturando tráfico solamente durante unos segundos tendremos miles de entradas en wireshark. Para aclararnos un poco tenemos que usar la busqueda mediante filtros, para encontrar la petición basta con usar el siguiente comando:

http.request.uri matches "example"

En example debemos poner alguna parte que sepamos de la url de nuestro recurso y así podremos localizar nuestra petición y ver lo siguiente

Request en Wireshark

Esta vez he eliminado la URL de la petición ya que uso un servicio privado. Pero podemos ver como volley ha añadido la cabecera

Accept-Encoding: gzip

Ahora para localizar la respuesta del servidor podemos usar el siguiente filtro

ip.src == 192.168.1.109 && ip.dst == 192.168.1.101 && http

Response en wireshark

Podemos ver como el servidor contesta con la cabecera

Content-Encoding: gzip

Además Wireshark nos dice en la penultima linea que el contenido está codificado con GZIP

Por tanto, resumiendo, volley añade automáticamente la cabecera necesaria para comprimir los datos (esto lo hace mediante el uso de la clase HttpUrlConnection) por contra para que el servidor acepte compresión por GZIP hay que seguir los pasos explicados anteriormnete

IMPORTANTE: Bajo ningún concepto añadir manualmente la cabecera Accept-Encoding: gzip en el método getHeaders de la Request de Volley ya que si se añade la cabecerá manualmente habrá que hacer la descompresión de los datos en GZIP manualmente mediante GZIPInputStream. Tamibén es impostante saber que la clase HttpUrlConnection unicamente está disponible a partir de la API 9 de Android en versiones anteriores usa HttpClient y por tanto la descompresión habrá que hacerla manualmente.

Establecer comunicaciones seguras

En esta parte hay una mayor carga teórica, la cual no voy a explicar, entiendo que si alguien encuentra esto es porque tiene nociones de certificados, autoridades de certificación, cadenas de confianza y claves privadas.

Para empezar debemos generar nuestro certificado y nuestra clave privada, para ello podemos utilizar openssl en linux,

openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.crt -days 1000

con openssl hacemos una petición de certificado x509 generando con ello una nueva clave privada RSA de 2048 bits llamada key.pem, el certificado generado será cert.crt y tendrá una validez de 1000 días.

Generación de certificado

A continuación debemos guardar estos dos ficheros generados en un lugar seguro, por ejemplo /etc/apache2/ssl

Ahora tenemos que configurar un host virtual con ssl, por lo general apache viene con uno por defecto, para activarlo debemos ejecutar el siguiente comando:

sudo a2ensite default-ssl.conf

Las 2 primeras líneas de este fichero son

<IfModule mod_ssl.c>

<VirtualHost _default_:443>

Si queremos podemos cambiar el puerto que por defecto es el 443. Tenemos que asegurarnos de que está presente la siguiente directiva

SSLEngine on

y especificar nuestro certificado y clave privada

SSLCertificateFile /etc/apache2/ssl/cert.crt
SSLCertificateKeyFile /etc/apache2/ssl/key.pem

Por último reiniciamos apache mediante

sudo service apache2 restart

Si ahora accedemos al recurso mediante un navegador como firefox (y mediante el protocolo HTTPS y no HTTP) se mostrará lo siguiente

SSL en navegador

Si vamos a Advanced y le damos a Add Exception… y luego a Confirm Security Exception ya podremos acceder al recurso. Ahora falta modificar la aplicación Android para que sea capaz de comunicar mediante ssl con el servicio web.

Ahora si abrimos las herramientas de desarrollador de firefox con Ctrl + Shift + C

ssl3

Vemos como ha aparecido un candado a la izquierda del dominio y además aparece información de seguridad.

Lo primero que debemos hacer es generar un keystore para almacenar nuestro certificado y poder usarlo en Android, para ello nos bajamos de aquí el fichero bcprov-ext-jdk15on-1.46.jar y ejecutamos la siguiente línea en la terminal de Linux

sudo keytool -importcert -v -trustcacerts -file cert.crt -alias IntermediateCA -keystore "./keystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath bcprov-ext-jdk15on-1.46.jar -storetype BKS -storepass "PASSWORD"

Debemos ejecutarlo desde el directorio donde tengamos el fichero bcprov-ext-jdk15on-1.46.jar y el certificado.

Con esto se nos habrá creado un fichero con extensión bks que debemos importar en la carpeta RAW de Android.

En Android lo que debemos hacer ahora es cambiar las rutas y cambiar http por https. Por ejemplo yo siempre tengo las rutas en constantes de la siguiente forma

El tema de las interrogaciones es por el hecho de que siempre que uso URL's me genero un sistema de bindings de parámetros.

A continuación debemos crear un nuevo stack para Volley, en mi caso a dicha clase le he llamado HurlSslStack que extiende de HurlStack. En esta clase lo que debemos hacer es crear el contexto SSL, para ello el constructor de clase recibe un stream del keystore que hemos creado más arriba. En esta clase debemos implementar el método createConnection que será el encargado de crear la conexión, crear el contexto SSL y verificar el host, para verificar el host debemos verificar que el campo "Issue to" o "Emitido a" cuando hemos creado el certificado coincide.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *