7.2.2.2.8. Conexión segura

El protocolo HTTP no es seguro para la transmisión de contraseñas y otros datos confidenciales, puesto que toda la información viaja en claro. La solución, como en el caso de otros protocolos no seguros, es encapsularlo dentro del protocolo criptográfico SSL/TLS1, a fin de que el protocolo viaje cifrado. Al HTTP encapsulado dentro de TLS es a lo que se denomina HTTPs Por lo general, el puerto reservado para comunicación HTTPs es el 443/TCP.

Consecuentemente, hacer que un servidor ofrezca páginas seguras se limita a:

  • Obtener un certificado que permita el cifrado TLS.

  • Desarrollar la configuración que hace que el servidor escuche en el puerto 443 tal como se hace cuando sirve HTTP, pero indicando que en este caso debe usar el certificado anterior para cifrar.

Nota

Encapsular el tráfico HTTP dentro de TLS supone que todos los paquetes viajan cifrados y que, en consecuencia, todo el contenido HTTP, incluida la cabecera, está cifrado. Esto inutiliza cualquier cacheo de contenido o cualquier filtrado web que se base en la URL o el propio contenido.2

7.2.2.2.8.1. Obtención del certificado

Para la obtención del certificado podemos optar por dos vías: crear un certificado autofirmado o hacernos con un certificado expedido por una autoridad acreditadora.

Nota

Estos certificados no son exclusivos para el servicio web. Son certificados que se usan con el protocolo TLS y, en consecuencia, sirven para cualquier protocolo que se cifre con él. Por ejemplo, también son válidos para usados con los protocolos SMTP o IMAP.

7.2.2.2.8.1.1. Autofirmado

La ventaja de hacer un certificado así es la inmediatez y gratuidad de su obtención, pero a costa de no ser confiable para el cliente, que tendrá que hacer un acto de fe al aceptarlo la primera vez que lo recibe.

Advertencia

Con toda lógica, los navegadores cada vez son más estrictos en la admisión de estos certificados autofirmados y, además, hay proxies web que los rechazan e impiden la conexión de los clientes a los que protegen. Por tanto, lo más recomendable es pasarse al siguiente epigrafe.

Para la creación de certificados, debe usarse openssl3. El método más sencillo, no obstante, es hacerlo mediante make-ssl-cert:

# apt-get install ssl-cert

La postinstalación genera directamente la clave private en /etc/ssl/private/ssl-cert-snakeoil.key y la pública en /etc/ssl/certs/ssl-cert-snakeoil.pem y crea un certificado para el nombre completo de la máquina4. Si nuestra intención es que el certificado sirva para distintos nombres (por ejemplo, porque tenemos definidos varios dominios virtuales), entonces es necesario volver a generar los certificados:

# make-ssl-cert /usr/share/ssl-cert/ssleay.cnf keycert.pem

Así, mediante una interfaz amigable hecha en whiptail, podemos generar otro distinto. La manera más sencilla de obtener un certificado válido para cualquier nombre del domino es contestar a la primera pregunta con *.example.net. La segunda podemos dejarla en blanco. La orden genera un único fichero con las claves pública y privada. Para separarlas y dejarlas en su ubicación predeterminada:

# sed '1,/-END PRIVATE KEY-/d' keycert.pem > /etc/ssl/certs/ssl-cert-snakeoil.pem
# sed '/-END PRIVATE KEY-/q' keycert.pem > /etc/ssl/private/ssl-cert-snakeoil.key

¡Y listo! Ya tenemos un certificado no fiable para crear conexiones SSL.

7.2.2.2.8.1.2. Acreditado

El principal problema de estos certificados es que siempre ha habido que pagar por ellos. Sin embargo, desde mediados de 2016, una autoridad de certificación, Let”s Encrypt, facilita la obtención gratuita de certificados de un modo bastante cómodo. Tienen la desventaja de que caducan a los tres meses, pero proporcionan un mecanismo automático de renovación bastante cómodo.

Para la creación y renovación de estos certificados acreditados, necesitamos instalar el paquete certbot:

# apt-get install certbot

Antes, no obstante, de usar el programa es preciso entender cómo Let’s Encrypt se cerciora de que somos quien decimos ser. Como la aplicación corre en el servidor que usará el certificado, se supone que al conectar vía web a la máquina con cuyo nombre pedimos el certificado, se accede efectivamente a ella. Como esto es así, certbot deja localmente en el directorio accesible por el servidor web unos ficheros y, desde sus servidores intenta acceder a ellos vía web: si lo consigue es que somos los propietarios del nombre y, por tanto, ha comprobado nuestra identidad y puede emitirnos el certificado.

Como los certificados se usaran luego con distinto software, certbot tiene distintos plugins para, además de crear o renover el certificado, proceder a su instalación en ellos. Nosotros, no obstante, sólo repararemos en dos:

  • standalone, que debe usarse si prevemos que no tendremos ocupado nunca el puerto 80. Con este plugin, el propio certboot levanta temporalmente un servidor web en el proceso de creación o renovación. No debe usarse si montamos un servidor web, porque en ese caso, la renovación fallará levantando el servidor web temporal al encontrar ocupado el puerto.

  • webroot, que implica tener un servidor web instalado e indicarle a certboot cuál es el directorio raíz del mismo.

Nosotros usaremos este último, suponiendo que ya tenemos listo el servidor web y que nuestro directorio raíz es /srv/www, Tras ello, podemos lanzar la generación del certificado así:

# certbot certonly --webroot -w /srv/www -d www.example.net \
   --non-interactive --agree-tos --email licencias@iesmiravent.es \
   --post-hook "/etc/letsencrypt/posthook.sh"

Como resultado de la orden tendremos dentro de /etc/letsencrypt/live/www.example.net las claves generadas y, además, justamente tras la generación, se habrá ejecutado el script que pasemos con la opción --post-hook5. Lo que realmente haya que hacer para que la nueva clave sea efectiva dependerá de cómo se tenga configurado el servidor. La gracia de incluir el script es que certboot viene con un timer de systemd para intentar diariamente la renovación del script y que este proceso de renovación también lanza el script. Por tanto, podremos olvidarnos por completo de estar al tanto de la actualización.

En un configuración simple en que el propio nginx se encarga del cifrado y utilizamos la configuración propuesta en el próximo epígrafe, basta con que el script se limite a reiniciar nginx. Por tanto:

#!/bin/sh

systemctl reload-or-restart nginx.service

Para casos más complejos, como cuando del cifrado se encarga haproxy, es necesario juntar las claves pública y privada (fullchain.pem y privkey.pem) en un único fichero. Este script se encarga de ello.

Nota

Es posible también generar un certificado asociados a varios nombres repitiendo las opciones -w y -d, de modo que las opciones -d harán referencia al -w que las precede. Por ejemplo:

# certbot certonly --webroot -w /srv/www/main -d example.net -d www.example.net \
   -w /srv/www/blog -d blog.example.net --non-interactive --agree-tos \
   --email licencias@iesmiravent.es --post-hook "/etc/letsencrypt/posthook.sh"

Es común que en algún momento añadamos un nuevo sitio al servidor y necesitemos que este sitio también use cifrado. Podemos ampliar el uso del mismo certificado al nuevo sitio usando la opción --expand:

# certbot certonly --webroot --expand -w /srv/www -d www.example.net \
   -w /srv/www/moodle -d moodle.example.net \
   --non-interactive --agree-tos --email licencias@iesmiravent.es \
   --post-hook "/etc/letsencrypt/posthook.sh"

Hay, eso sí, que enumerar todos los dominios a los que ya estaba asociado el servidor y añadir los nuevos.

7.2.2.2.8.2. Configuración

Para configurar un dominio virtual servidor por HTTPs es necesario crear el fichero /etc/nginx/conf.d/ssl.conf6 que dependiendo de cómo hallamos generado el certificado variará:

  • Para el certificado autofirmado, éste.

  • Para el certificado con Let’s Encrypt este otro.

Luego, tomando como referencia la configuración más básica, basta con incluir los siguientes cambios:

server {
   listen   443 ssl;

   server_name _;

   root /srv/www;
   try_files $uri $uri/ =404;

   include snippets/snakeoil.conf;
}

O sea, incluimos un fichero /etc/nginx/snippets/snakeoil.conf que indica cuáles son las claves de cifrado para el sitio y que deben ser las que genera make-ssl-cert. El fichero ya está incluido en el paquete de debian y, como no podía ser de otra firma, contiene estas dos directivas:

ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

Sin embargo, el propio fichero avisa que no deberían usarse en un servidor real. Si hacemos caso y utilizamos las certificadas por Let’s Encrypt, entonces deberemos indicar las claves que generamos con él de un modo análogo a cómo hicimos antes:

server {
   listen   443 ssl;

   server_name _;

   root /srv/www;
   try_files $uri $uri/ =404;

   include snippets/letsencrypt.conf;
}

y el fichero /etc/nginx/snippets/letsencrypt.conf queda:

ssl_certificate /etc/letsencrypt/live/www.example.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/www.example.net/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/www.example.net/fullchain.pem;

Las configuraciones anteriores sólo ofrecerían servicio a través del puerto 443. Si queremos ofrecerlo a través del 80 también, podemos hacer lo siguiente:

server {
   listen   80;
   listen   443 ssl;

   server_name _;

   root /srv/www;
   try_files $uri $uri/ =404;

   include snippets/snakeoil.conf;

   if ($https != "on") {
      return 301 https://$host$uri$is_args$args;
   }

   # Cabecera HSTS (fuerza a que el certificado sea válido)
   add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}

Advertencia

El if provoca que, si el tráfico es no seguro, se repita la petición por el puerto seguro. Si queremos permitir ambos podemos eliminar este bloque. Añadimos, además, la cabecera HSTS para informar al cliente de que use HTTPsy de que no acepte bajo ningún concepto un certificado no fiable (p.e. uno autofirmado)7. Para más información, consulte el epígrafe dedicado al ataque SSLstrip.

Múltiples dominios virtual

Cuando el servidor define varios dominios virtuales y dos o más de ellos usan tráfico seguro debe tenerse en cuenta que, en principio, el servidor web conoce cuál es el nombre de máquina usado en la petición a través del comando GET o las cabeceras HTTP. Al ser el tráfico cifrado, el acceso a esta información no es accesible hasta que no se use el certificado correspondiente para descifrarla. Ahora bien, si cada dominio virtual seguro usa un certificado diferente, ¿cómo puede elegirse el adecuado si el nombre está cifrado? Para solucionar este problema se creó la extensión SNI al protocolo SSL, que soportan la mayor parte de los navegadores modernos y, muy probablemente la versión de nginx que se esté utilizando:

# nginx -V

En caso de que nginx soporte SNI, puede definirse un certificado diferente en cada sitio a través de ssl_certificate y ssl_certificate_key. Para los sitios seguros en que no se defina un certificado, se usará el declarado en el servidor predeterminado8. Una configuración posible puede ser esta:

server {
   listen   80       default_server;
   listen   443 ssl  default_server;

   server name _;

   include snippets/letsencrypt.conf;

   # Resto de configuración.
}

server {
   listen 80;
   listen 443;  # Nótese que no hace falta ni usar ssl.

   server_name  moodle.example.net;

   # Configuración para moodle
}

server  {
   listen 80;
   listen 443 ssl;

   server_name  alt.example.net;

   include snippets/altcert.conf;

   # Configuración para este sitio.
}

Con esta configuración, el sitio moodle.example.net usará el mismo certificado que el sitio predeterminado (el incluido en snippets/letsencrypt.conf, mientras que el sitio alt.example.net utiliza un certificado distinto. Podemos cerciorarnos de que esto realmente funciona haciendo una consulte con el navegador y consultando los certificados, o desde la consola con openssl al servidor:

$ openssl s_client -servername alt.example.net -connect alt.example.net:https < /dev/null | grep 'CN ='

Advertencia

openssl no usa SNI a menos que se use la opción -servername, de modo que haga las pruebas incluyéndola.

Notas al pie

1

TLS es simplemente la evolución de la versión 3 de SSL. A menudo suele decirse SSL para referise también a TLS.

2

En principio, establecer un filtrado de sitios web para clientes sólo puede hacerse manipulando la resolución DNS, que sí que no está cifrada.

3

Por ejemplo, para generar un certificado cuya validez sea de diez años:

# openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/ssl/private/ssl-cert-snakeoil.key \
   -out /etc/ssl/certs/ssl-cert-snakeoil.pem
4

o sea, el que se obtiene así:

$ hostname -f
5

También hay una opción -pre-hook para ejecutar antes.

6

Las únicas directivas relativas al cifrado que se encuentran en nginx.conf son estas:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on

de ahí que no se hayan incluido en nuestra propia configuración.

7

Para más información sobre esta cabecera, consulte el ataque SSLstrip.

8

Lo cual no es ningún problema, puesto que un certificado digital se puede asociar a varios nombres.