@drawohara¡me encanta esto! << haz clic aquí 🐛 🫖 🧚
/multi-domain-https-with-server-name-indication
publicado el: 2014-12-18

(Esta es una versión condensada de Sí, Virginia, puedes usar SNI que apareció originalmente en el blog Cosas... y cosas... de Spike)

Cuando te conectas a un servidor web de manera segura usando HTTPS, la seguridad se negocia utilizando TLS. Dos cosas ocurren: se verifica la identidad del servidor y se cifra la conexión.

La verificación es importante; no importa si la conexión está cifrada si de alguna manera has sido redirigido a un servidor malintencionado. Sin embargo, esa verificación puede ser problemática si un servidor web está sirviendo más de un nombre de host.

Puedes leer los detalles escabrosos, pero la versión simplificada del proceso es que el servidor envía un certificado de clave pública firmado que debe coincidir con el nombre de host en la URL. Si un cliente navega a dojo4.com, entonces el certificado debe ser para dojo4.com; si no lo es, el navegador muestra una gran advertencia de seguridad.

Técnicamente, es posible tener múltiples nombres de host en un certificado; de hecho, es común tener, por ejemplo, tanto "dojo4.com" como "www.dojo4.com" por completitud. Sin embargo, es una tremenda molestia agregar y eliminar nombres de host de un certificado. Debes hacer que el emisor genere uno nuevo y revocar el anterior. Y, si estás trabajando con una Red de Entrega de Contenidos, es poco probable que agreguen tus nombres de host a su certificado.

Originalmente, TLS soportaba un certificado por servidor web (o más correctamente, por dirección IP adjunta al servidor web). Se agregó Indicación de Nombre de Servidor (SNI) a TLS para resolver este problema. Al inicio de la negociación TLS, el cliente le dice al servidor el nombre del host al que intenta conectarse y el servidor puede entonces seleccionar y enviar el archivo de certificado correcto. ¡Problema resuelto!

Excepto... No todos los navegadores soportan SNI. Todos saben esto, y como resultado, tienden a saltarse SNI e ir directamente a IPs dedicadas por sitio o incluso múltiples servidores. Esta es una opción costosa, especialmente cuando se trabaja con CDNs como CloudFront. Cuando esto surgió para mí, decidí ver qué significaba realmente "no todos los navegadores".

Resulta que SNI es ampliamente soportado, con los grandes problemas siendo IE8 y versiones anteriores, y cualquier versión de IE ejecutándose en Windows XP (porque la biblioteca subyacente del sistema operativo no soporta SNI). También hay algunas versiones antiguas de Android que carecen de soporte.

Entonces, la mayoría de los visitantes no tendrán problemas con SNI y el grupo que sí los tiene es lo suficientemente pequeño como para que podamos manejarlos como un caso especial.

Para los navegadores sin soporte de SNI, la solución alternativa es redirigirlos a un certificado que funcione o a una página sarcástica de "actualiza tu navegador". Si buscas en Google, encontrarás varias soluciones sobre la construcción de listas blancas de navegadores buenos y/o listas negras de malos y luego usar esas listas en reglas de redirección del lado del servidor. Feo. Las listas deben ser mantenidas y, dependiendo del servidor, rompen el caché.

Hay una manera más inteligente. Mientras navegaba por un mar de configuraciones de redirección de Apache, lo encontré en esta publicación. La idea central de la publicación se puede destilar en esto: si un navegador que no soporta SNI intenta cargar contenido SNI, obtendrá un error. Si probamos esto en segundo plano y diferenciamos entre error y éxito, entonces podemos redirigir al visitante en consecuencia. Y la forma más sencilla de hacerlo es intentar agregar una imagen de un píxel a la página.

En código se ve así:

function secure_redirect() {
   var img=document.createElement('img'); // crear un elemento de imagen.
   // Establecer el src a una URL SNI de una imagen de un píxel
   img.src='https://www.example.org/pixel.gif';
   // Esto se ejecuta si SNI funciona.
   img.onload = function() {
      // Redirigir a la página segura.
      window.location.href = "https://example.org/";
   };
   // Esto se ejecuta si SNI no funciona.
   img.onerror = function(e) {
      // Redirigir a otro lugar
      window.location.href = "http://example.org/snarky-old-browser-message";
   };
   // No mostrar realmente la imagen
   img.style.display='none';
   // pero adjuntarla a las páginas para que se cargue.
   document.body.appendChild(img);
  }

Aquí estoy aprovechando dos callbacks de HTML en la etiqueta img, 'OnLoad', que se dispara cuando una imagen termina de cargarse, y 'OnError', que se dispara si la imagen no puede cargarse. Si un navegador no soporta SNI, la imagen fallará en cargarse debido a un error de certificado, disparando 'OnError'. Sin embargo, como estamos agregando la imagen a una página ya cargada, no generará un error en el navegador.

Ahora podemos probar SNI y manejar la falta de soporte de manera elegante. ¡La Navidad está salvada!

Sin embargo, lo que realmente hemos llegado es a algo más inteligente. Nota que el código no está realmente probando SNI, solo la capacidad de cargar la imagen de manera segura. Si la URL HTTPS en cuestión no requiere realmente SNI, hay solo un certificado o el primer certificado coincide con el dominio solicitado, aún funciona. El problema se ha reducido a "¿Puede el navegador de este visitante mostrar el sitio seguro o no?" y al final del día, eso es todo lo que realmente nos importa.