¡Cifrad, cifrad, malditos!

Con todo lo que está cayendo en temas de privacidad y seguridad y con la potencia de las máquinas actuales, no hay excusas para no emplear cifrado en todas nuestras comunicaciones en Internet.

En este artículo no voy a entrar en el aspecto técnico ni activista de desplegar cifrado en las comunicaciones. Baste con decir, de momento, que no hay ningún motivo para NO cifrar y muchos, muchísimos, para sí hacerlo.

A pesar de ser muy insistente en todo este tema, he sido un pobre ejemplo práctico hasta ahora. Llevo usando HTTPS mucho tiempo, pero siempre he usado certificados autofirmados. No voy a entrar en los detalles del estado vergonzoso de las autoridades de certificación o que el despliegue de DNSSEC progrese a paso de tortuga. Habrá tiempo de tratar todo eso en otros artículos.

Desde el 3 de noviembre de 2014 empleo certificados X.509 validados por los navegadores para:

Lo que sigue describe este despliegue para entornos Unix o similares (Linux), el servidor web Apache y el programa OpenSSL. No contaré los detalles básicos porque ya se explican en infinidad de páginas web.

Lo básico

El primer paso consiste en generar un certificado X.509:

$ openssl genrsa -out www.jcea.key 2048

Con la configuración actual, esto generará un certificado X.509 RSA [2] de 2048 bits y SHA-1 [1] como algoritmo de hash. El fichero generado debe protegerse como oro en paño, así que posiblemente haya que cambiar su propietario y los permisos de acceso en el sistema de ficheros. Debe mantenerse secreto a toda costa.

Lo siguiente es generar un Certificate Signing Request para que una autoridad de certificación firme nuestra clave y los navegadores web la consideren válida:

$ openssl req -new -key www.jcea.es.key -out www.jcea.es.csr

Aquí OpenSSL nos hará multitud de preguntas que normalmente tendríamos que rellenar con diligencia. La más importante es el campo Common Name, porque será el nombre DNS que van a verificar los navegadores. En mi caso, no obstante, voy a usar StartSSL como autoridad de certificación y StartSSL sólo usa la clave pública RSA ignorando el resto de campos [3].

Una vez que tenemos el Certificate Signing Request, se lo hacemos llegar a la autoridad de certificación. En mi caso hago un copia&pega en StartSSL, que me deja elegir un nombre de dominio de tercer nivel bajo mi dominio jcea.es [4]. Por ejemplo, www.jcea.es o blog.jcea.es.

StartSSL nos devuelve la clave pública RSA con el dominio indicado, formando un certificado X.509 reconocido por la inmensa mayoría de los navegadores [5]. Copiamos ese certificado junto al fichero privado generado antes. Su pareja, vaya.

[1] SHA-1 es un algoritmo en extinción. La opción recomendada hoy en día es SHA-256, pero no es compatible con versiones antiguas de Android y otros navegadores anticuados. Para un certificado que va a durar un año y que debe ser lo más compatible posible, aún debemos usar SHA-1.
[2] Los certificados de curvas elípticas son más pequeños y más rápidos. Las patentes solían ser un problema, pero hoy en día hay curvas de uso libre. El problema fundamental, como en el caso anterior, es la compatibilidad. Si necesitas que tu certificado sea lo más compatible posible, debes usar RSA.
[3]

StartSSL proporciona certificados X.509 gratuitos para uso no comercial. Algunas limitaciones adicionales son, por ejemplo, que solo se pueden firmar nombres de dominio de tres niveles (lo que excluye cosas como www.es.python.org) y que no podemos personalizar los certificados con nuestros detalles de identidad para darles mayor autenticidad ante los usuarios. Tampoco se pueden revocar sin pagar por ello. Pero los navegadores los reconocen como válidos y son gratis. Es lo que hay.

Por supuesto, StartSSL también expide certificados de pago sin esos problemas.

[4] Los certificados generados por StartSSL serán válidos para dos nombres de dominio diferentes: El dominio de tercer nivel seleccionado y el propio dominio de segundo nivel jcea.es. Tengo la hipótesis de que esa "cortesía" es para evitar que una entidad utilice certificados gratuitos para terceras partes (clientes). Dado que un certificado con esas características es válido también para jcea.es, es muy arriesgado que nadie más tenga acceso al mismo.
[5] Es recomendable descargar el certificado intermedio de StartSSL y configurar el Apache para que lo envíe en la negociación TLS. De esta forma la compatibilidad es mayor, aunque la negociación TLS medirá unos pocos Kbytes más.

El siguiente paso consiste en configurar Apache para que emplee dicho certificado:

SSLEngine on
SSLCertificateChainFile /PATH/startssl-sub.class1.server.ca.pem
SSLCertificateFile      /PATH/.SSL/www.jcea.es.cert
SSLCertificateKeyFile   /PATH/.SSL/www.jcea.es.key

Reiniciamos Apache, nos conectamos con un navegador y comprobamos que estamos empleando el certificado X.509 nuevo y que el navegador lo acepta sin problemas.

ATENCIÓN: StartSSL tiene un retardo de una hora o más entre la generación de un certificado X.509 nuevo y su inclusión en su sistema OCSP. Si durante este periodo alguien hace una consulta Online Certificate Status Protocol recibirá un error de certificado desconocido y el navegador lo rechazará. Es un error catastrófico, peor que si el servidor Online Certificate Status Protocol se cae y no responde en absoluto.

Por tanto un certificado StartSSL nuevo no puede usarse inmediatamente. Esto es algo a tener muy en cuenta, por ejemplo, en la renovación anual de certificados. Hay que dejar pasar unas horas.

Desconozco la situación con otras autoridades de certificación.

Online Certificate Status Protocol

Ya he descrito en otros artículos el uso de Online Certificate Status Protocol, su conveniencia y sus problemas de privacidad. También he descrito cómo resolverlo mediante OCSP Stapling. Mi consejo es emplear OCSP Stapling siempre que sea posible. Ganarás seguridad, privacidad y velocidad. Echa un vistazo a:

Hospedar múltiples servidores HTTPS en la misma IP

HTTP permite colgar varios nombres de dominio de un mismo servidor web porque el navegador envía el nombre del servidor al que quiere conectarse en una cabecera HTTP.

Pero al principio de los tiempos, el protocolo SSL negociaba una conexión segura y luego enviaba una petición HTTP a través de ella. Por tanto, durante la negociación SSL todavía no tenemos la cabecera HTTP necesaria para identificar el nombre del servidor que nos interesa. Debido a ello, el servidor web no sabe qué certificado debe emplear durante la negociación SSL. Eso hacía que tradicionalmente se colgase un solo servicio HTTPS por dirección IP. Esto era uno de los muchos inconvenientes para la difusión del cifrado generalizado en el tráfico Internet.

Evoluciones posteriores del protocolo SSL (Transport Layer Security) añadieron una extensión llamada Server Name Indication. Dicha extensión permite indicar al servidor HTTPS a qué nombre de servidor nos estamos intentando conectar. De esta manera el servidor HTTPS empleará el certificado correcto para la negociación TLS.

El problema de Server Name Indication es que hay navegadores antiguos que no lo soportan. Uno de los más destacados es Internet Explorer sobre Windows XP y también versiones antiguas (pero aún relevantes) de Android. Cada vez será menos problema, pero es algo que deberíamos saber.

Este problema también afecta a librerías y lenguajes de programación. Dependiendo de vuestras necesidades y usuarios puede ser un problema serio.

Redirección al HTTPS desde el HTTP

Si estamos pasando a cifrado HTTPS una web que antes era HTTP, seguiremos teniendo accesos HTTP durante un tiempo potencialmente muy largo. Buscadores, lectores, etc. De hecho, si alguien ha puesto un enlace hacia nosotros desde su web, ese tráfico nunca desaparecerá del todo.

Por ello es importante mantener la web HTTP y sustituir todos los accesos por redirecciones hacia HTTPS. Redirecciones permanentes.

Podemos añadir lo siguiente a la configuración apache del servidor virtual HTTP:

RewriteEngine on
RewriteRule (.*) https://www.jcea.es%{REQUEST_URI} [R=301,L]

Si tenemos varios sitios HTTP y no nos importa perder los detalles de las redirecciones en los logs de acceso, podemos reemplazar todos esos servidores virtuales por uno solo con esta redirección genérica:

RewriteEngine on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

HTTP Strict Transport Security

Cuando escribimos una URL parcial en el navegador, éste se conectará por HTTP por defecto. Esto es problemático si estamos en una red potencialmente hostil, como una WIFI pública.

Los navegadores modernos soportan una cabecera HTTP interesante: HTTP Strict Transport Security.

Si se envía una cabecera HTTP Strict Transport Security durante una sesión HTTPS con un certificado reconocido por defecto (no sirven los certificados autofirmados), el navegador tomará nota de que el servidor EXIGE que las conexiones para ese dominio se realicen SIEMPRE por HTTPS, aunque el usuario no lo diga. En la cabecera se indica durante cuánto tiempo se forzará el acceso por HTTPS.

Añadimos lo siguiente en la configuración del servidor HTTPS:

Header always set Strict-Transport-Security "max-age=2592000;"

Esta línea indica al navegador que todos los accesos deben hacerse al HTTPS durante los siguientes 30 días (2592000 segundos = 30*24*60*60 segundos). Una web seria debería indicar un tiempo mayor, potencialmente eterno. Al menos seis meses o un año. Yo, en cambio, no sé muy bien qué va a pasar con StartSSL. Si me caduca el certificado y tengo problemas para renovarlo, será catastrófico... durante un mes. Nadie podrá acceder. Veremos cómo evoluciona el asunto.

Obsérvese que no estoy usando el atributo includeSubDomains, ya que tengo muchos subdominios. Algunos sin cifrado y otros con certificados autofirmados y otros con cifrados mixtos con certificados X.509 autenticados por autoridades de certificación reconocidas y otros con certificados autofirmados. Un dominio menos complicado debería usar includeSubDomains y protegerse por completo.

Cambiar los enlaces

Si estamos usando cabeceras HTTP Strict Transport Security en el tráfico HTTPS, el navegador convertirá automáticamente cada acceso a un enlace HTTP a HTTPS, de forma automática y transparente. Pero para ayudar a los buscadores y no confundir a los usuarios, es buena idea cambiar los enlaces que tengamos por ahí en nuestra web a HTTPS. Esto puede ser fácil o difícil, según la tecnología que tengamos por detrás.

En este blog, por ejemplo, basta con editar conf.py y hacer algo de este estilo:

for i in *.rst
do
  sed -e 's=http://blog.jcea=https://blog.jcea=g' --in-place "$i"
done

Por supuesto, previa copia de seguridad :).

Mixed Content

Una de las consecuencias de usar HTTPS es no poder incluir contenido externo accesible solo por HTTP. Que sea o no problemático depende de cada sitio web. En mi caso sí he tenido algunos problemas, y en ciertas situaciones ha sido necesario acceder a dicho contenido a través de un proxy. En fin, problemas míos.

Más información:

Seguir usando certificados autofirmados para aplicaciones privadas

Emplear certificados X.509 firmados por autoridades de certificación reconocidas mola y tal, pero tiene sus problemas. Un certificado chulo cuesta dinero y caduca al cabo de un año.

Para entornos donde el público es un grupo reducido y de confianza, sigo prefiriendo certificados autofirmados. Puedes crearlos para que expiren en diez años, renovarlos es sencillo y es gratis. Si el público es una comunidad pequeña y cerrada, la confianza se puede gestionar por una vía paralela.

Crear una autoridad de certificación propia es una opción frecuente y la recomendaría cuando eres el único consumidor de la misma. Pero pedir a otros usuarios que confían en mí que añadan una entidad de certificación cuestionable capaz de impersonar cualquier conexión HTTPS que establezcan, me parece pedir demasiado. Vamos, yo no instalaría algo así en mi navegador si me lo pidiese otro.

¿Y qué pasa con aplicaciones no HTTP?

La web no es el único servicio que debe cifrarse con TLS, aunque sea lo único que ven el 99.999% de los usuarios. Las conversaciones de Chat (XMPP e IRC) también son importantes. Las videoconferencias. El correo electrónico.

Temas de un futuro artículo.