Certificado SSL con Let's Encrypt (y ajustes a Nginx)

Como comentaba en el artículo pasado, para poder usar el protocolo HTTP/2 en los browsers más conocidos es necesario servir las páginas sobre una conexión segura, para lo cual, a su vez, necesitamos un certificado expedido por un CA (Certificate Authority). Por suerte para todos nosotros que no podemos pagar 100 dólares anuales por un certificado, Let's Encrypt es un CA y nos permite obtener un certificado de manera simple y gratuita.

Vamos a ver el paso a paso para tener el certificado instalado en nuestro server Ubuntu 16.04.

Instalar el certificado

UPDATE: Recomiendo utilizar la versión mas reciente del certbot, y no la que está en el repo de Ubuntu: https://embrolio.com/entry/update-certificado-ssl-con-let-s-encrypt.

Para instalar el certificado voy a suponer que tenemos acceso SSH a nuestro server, seguramente porque contratamos alguna de las VPS de 5 dólares que ya hemos reseñado acá :D

Let's encrypt utiliza clientes ACME (Automatic Certificate Management Environment) para facilitar la instalación del certificado. La idea básica es que nosotros demostremos tener control sobre el dominio y entonces let's encrypt nos provee el certificado.

Como primer paso, instalamos letsencrypt desde el repo de Ubuntu:

sudo apt install letsencrypt 

El certbot tiene instalaciones autómaticas para algunos servers, pero para nginx es aún experimental (y aparte yo prefiero modifcar a mano la config del server), asi que vamos a usar la opción de certonly, que lo único que hace es generar el certificado, sin tocar la configuración del server:

letsencrypt certonly --webroot -w /var/www/example -d example.com -d www.example.com

Este comando pide un único certificado válido para example.com y www.example.com, informando que el webroot para el sitio example.com está en /var/www/example.

Y eso es todo de parte de Let's Encrypt. El certificado queda instalado en /etc/letsencrypt/live/example.com/fullchain.pem y la llave en /etc/letsencrypt/live/example.com/privkey.pem.

Configurar Nginx

Ahora que ya tenemos nuestro certificado, vamos a editar la configuración de nginx (/etc/nginx/sites-available/default o el vhosts que corresponda):

server {
    listen 443 deferred ssl http2 default_server;
    listen [::]:443 deferred ssl http2 default_server;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    #Continúa la config del server...

Nos aseguramos que todo esté bien y reiniciamos nginx:

nginx -t
service nginx restart

En este momento ya podemos servir nuestro sitio por https, pero faltaría un detalle para obtener una A en el sitio ssl lab: configurar el cifrado y modificar el DHE (Ephemeral Diffie-Hellman) que utiliza Nginx por default.

Buscando la A en SSL Labs

Pueden ver el puntaje de este sitio en https://www.ssllabs.com/ssltest/analyze.html?d=embrolio.com&latest.

Lo primero que vamos a modificar es el protocolo. Teniendo en cuenta que los protocolos SSLv2 y SSLv3 tienen vulnerabilidades conocidas, solo usaremos los protocolos TLS. El puntaje ideal en protocol support sería dado si usamos unicamente TLSv1.2, pero como nosotros también buscamos la mayor compatibilidad posible, vamos a sacrificar el 100 en el apartado Protocol Support y usar las 3 versiones de TLS:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

Lo que sigue es configurar el cifrado, acá nuevamente se nos presenta la opción de hacerlo más seguro y menos compatible, o buscar el equilibrio. En la Wiki de la fundación Mozilla hay una entrada sobre configuración TLS con recomendaciones para cada nivel. En mi caso, voy a ir por una opción intermedia, nuevamente resignando el puntaje perfecto en favor de la compatibilidad:

ssl_prefer_server_ciphers on;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';

Ya casi estamos, el último paso para obtener una A es modificar los parámetros que nginx utiliza para el intercambio de claves DHE. Para esto vamos a generar una nueva clave

openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096

y decirle a Nginx que la utilice:

ssl_dhparam ssl/dhparam.pem;

A esta altura ya deberíamos tener la A en SSL Labs (repito, si buscan la A+ a costa de perder compatibilidad, usen menos ciphers y protocolos).0 Pero ya que estamos, terminemos de configurar nginx para una conexión segura y agreguemos algunos campos más. La parte de ssl se nginx queda mas o menos así:

http {
        #SSL 
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';

        ssl_session_cache shared:SSL:32m;
        ssl_buffer_size 8k;
        ssl_session_timeout 60m;
        ssl_session_tickets off;

        ssl_dhparam ssl/dhparam.pem;
        ssl_ecdh_curve secp384r1;

        add_header Strict-Transport-Security "max-age=31536000; includeSubdomains";
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;

        #Resto de la config
}

Para seguir leyendo del tema: