@drawoharaik ❤️ dit! << klik hier 🐛 🫖 🧚
/multi-domain-https-with-server-name-indication
gepubliceerd op: 2014-12-18

(Dit is een verkorte versie van Ja, Virginia, je kunt SNI gebruiken die oorspronkelijk verscheen op Spikes Stuff… And Things… blog)

Wanneer je veilig met een webserver verbindt met HTTPS, wordt de beveiliging onderhandeld met TLS. Twee dingen gebeuren, de identiteit van de server wordt geverifieerd en de verbinding wordt versleuteld.

De verificatie is belangrijk, het maakt niet uit of de verbinding is versleuteld als je op een of andere manier bent omgeleid naar de server van een kwaadaardige. De verificatie kan echter problematisch zijn als een webserver meer dan één hostnaam bedient.

Je kunt de gruwelijke details lezen, maar de vereenvoudigde versie van het proces is dat de server een gesigneerd Public key certificate verstuurt dat overeen moet komen met de hostnaam in de URL. Als een cliënt naar dojo4.com gaat, moet het certificaat voor dojo4.com zijn, als dat niet het geval is, geeft de browser een grote waarschuwingsmelding weer.

Technisch is het mogelijk om meerdere hostnamen op een certificaat te hebben, in feite is het gebruikelijk om bijvoorbeeld zowel "dojo4.com" als "www.dojo.com" te hebben, om af te zijn. Het is echter een enorme pijn in de kont om hostnamen toe te voegen en te verwijderen van een certificaat. Je moet de uitgever een nieuw certificaat laten genereren en het oude intrekken. En als je werkt met een Content Delivery Network, is het zeer onwaarschijnlijk dat ze je hostnamen aan hun certificaat toevoegen.

Oorspronkelijk ondersteunde TLS één certificaat per webserver (of meer correct, per IP-adres dat aan de webserver is gekoppeld) Server Name Indication (SNI) werd aan TLS toegevoegd om dit probleem op te lossen. Aan het begin van de TLS-onderhandeling geeft de cliënt de naam van de host door waarmee ze probeert te verbinden, zodat de server vervolgens een juist certificaatbestand kan selecteren en versturen. Probleem opgelost!

Behalve… Niet alle browsers ondersteunen SNI. Iedereen weet dit, en als gevolg hiervan neigen ze SNI te overslaan en direct over te gaan op per site gedediceerde IP's of zelfs meerdere servers. Dit is een dure optie, met name bij het werken met CDNs zoals CloudFront. Toen dit voor me aan de orde was, besloot ik te kijken wat "niet alle browsers" echt betekent.

Blijkt dat SNI breed wordt ondersteund, waarbij de grote problemen liggen bij IE8 en lager en elke versie van IE die op Windows XP draait (omdat de onderliggende OS-bibliotheek SNI niet ondersteunt). Er zijn ook enkele oude versies van Android die geen ondersteuning bieden.

Dus zullen de meeste bezoekers geen problemen hebben met SNI en de groep die dat wel doet, is klein genoeg om ze als een speciaal geval te behandelen.

Voor browsers zonder SNI-ondersteuning is de workaround om ze om te leiden naar een certificaat dat werkt of naar een sarcastische "upgrade je browser"-pagina. Als je zoekt, vind je een hoop oplossingen rond het bouwen van whitelists van goede browsers en/of blacklists van slechte, en deze lijsten vervolgens gebruiken in serverside-omleidingsregels. Lelijk. De lijsten moeten worden onderhouden en afhankelijk van de server breekt het cachen.

Er is een slimme manier. Terwijl ik door een zee van voorbeeld-Apache-omleidingsconfiguraties waadde, vond ik het in deze post. De kern van de post kan worden samengevat als volgt: als een browser die SNI niet ondersteunt, probeert SNI-inhoud te laden, krijgt het een fout. Als we dit op de achtergrond testen en onderscheid maken tussen fout en succes, kunnen we de bezoeker omleiden. En de eenvoudigste manier om dat te doen is een poging om een afbeelding van één pixel aan de pagina toe te voegen.

In de code ziet het er zo uit:

function secure_redirect() {
   var img=document.createElement('img'); // maak een img-element.
   // Stel de bron in op een SNI-URL van een afbeelding van één pixel
   img.src='https://www.example.org/pixel.gif';
   // Dit wordt uitgevoerd als SNI werkt.
   img.onload = function() {
      // Leid om naar de beveiligde pagina.
      window.location.href = "https://example.org/";
   };
   // Dit wordt uitgevoerd als SNI niet werkt.
   img.onerror = function(e) {
      // Leid om naar een andere locatie
      window.location.href = "http://example.org/snarky-old-browser-message";
   };
   // Toon de afbeelding niet echt
   img.style.display='none';
   // maar voeg het toe aan de pagina zodat het geladen wordt.
   document.body.appendChild(img);
  }

Hier maak ik gebruik van twee HTML-callbacks op de img-tag, 'OnLoad' die wordt afgevuurd wanneer een afbeelding met het laden klaar is, en 'OnError' die wordt afgevuurd als de afbeelding niet kan worden geladen. Als een browser SNI niet ondersteunt, mislukt het laden van de afbeelding vanwege een certificaatfout, waardoor 'OnError' wordt afgevuurd. Maar omdat we de afbeelding toevoegen aan een al geladen pagina, geeft het geen foutmelding in de browser.

Nu kunnen we testen op SNI en gebrek aan ondersteuning elegant afhandelen. Kerstmis is gered!

Hoewel, wat we echt hebben bereikt is iets sluws. Merk op dat de code niet echt test op SNI, maar op de mogelijkheid om de afbeelding veilig te laden. Als de HTTPS-URL in kwestie SNI niet echt vereist, omdat er maar één certificaat is of het eerste certificaat overeenkomt met het aangevraagde domein, werkt het nog steeds. Het probleem is vereenvoudigd tot "Kan de browser van deze bezoeker de beveiligde site weergeven of niet?" en aan het eind van de dag is dat alles wat we echt van belang vinden.