SNI, webové servery a prohlížeče

Správa linuxového serveru: SNI, webové servery a prohlížeče

SNI (Server Name Indication) je metoda umožňující provoz více HTTPS virtuálních webů (virtual host) s různými certifikáty na jedné jediné IP adrese. V tomto článku se dozvíte, jak to vypadá s podporou SNI na straně prohlížečů i na straně serverů, a pochopitelně se také dozvíte, jak SNI nakonfigurovat na nejpoužívanějších linuxových webových serverech.

Úvod

Zásadním problémem HTTPS bylo po dlouhou dobu pořadí vykonávaných činností, kdy se v prvním kroku vytvořilo SSL spojení, a teprve přes něj se přenesl HTTP požadavek se jménem webu, který klient požadoval. To znamenalo, že webový server nemohl při vytváření SSL spojení vědět, který certifikát má klientovi předložit. V praxi to znamenalo (a bohužel stále v případě mnoha serverů znamená), že na jedné IP adrese mohly být virtuální weby pouze s jedním certifikátem. Pokud jste chtěli použít pro každý virtuální web jiný certifikát, museli jste si zajistit odpovídající počet IP adres a každé IP adrese přiřadit konkrétní certifikát (a webovou prezentaci).

Tento problém se bohužel nedá dost dobře řešit na úrovni certifikátů – i když je možné vytvořit certifikát pro více domén, certifikát může být vydán pouze pro jednu osobu či organizaci. Řešení tedy muselo přijít v podobě rozšíření protokolů SSL a TLS, a právě tomuto rozšíření se říká SNI (Server Name Indication). Toto rozšíření umožňuje prohlížeči předat jméno požadovaného webu ještě před vytvářením šifrovaného spojení, což dává serveru možnost servírovat i na jediné IP adrese různé certifikáty podle toho, který web klient požaduje.

Podpora v prohlížečích

Aby mohlo SNI fungovat, musí být podporováno na straně serveru, ale také na straně klienta. Jako správci serverů máte serverovou část pod kontrolou, tudíž hlavní problém spočívá v podpoře na straně klientů. V této oblasti se sice hnuly ledy a řada prohlížečů dnes SNI bez problémů podporuje, ale ani zdaleka ne všechny a ne na všech systémech. Problémy jsou zejména na Windows XP a mobilních zařízeních, ale i na Linuxu. Aktuální seznam naleznete na Wikipedii. Dle tohoto zdroje SNI podporují následující verze prohlížečů:

SNI naopak nepodporuje třeba Konqueror, IE a Safari na XP, wget, Windows Mobile až po 6.5, Oracle Java JSSE atd. Co se Konqueroru týče, podpora SNI snad velmi brzy přibude (měla by být součástí verze 4.7), ale než se nový Konqueror dostane do distribucí, chvíli to ještě potrvá.

Jak je vidět, není to sice úplně špatné, ale stejně tak to ještě pořád není to pravé ořechové. Pokud se tedy budete rozhodovat o nasazení SNI, určitě berte na vědomí, že ne všichni vaši klienti jej patrně podporují.

S tím se pojí zásadní otázka – co se stane, pokud váš server navštíví klient nepodporující SNI? Server by měl zareagovat více či méně podle očekávání, tzn. měl by použít výchozí certifikát (obvykle první certifikát v definici virtuálních webů) a po vytvoření SSL spojení naservírovat klientovi správný web. Klient se tedy na web dostane, ale ne bez obvyklého varování o problému s předloženým certifikátem.

Podpora ve webových serverech

Dle Wikipedie podporují SNI webové servery v následujících verzích:

S výjimkou serveru Apache, kde zmiňuji i řešení pro starší systémy (Debian Lenny), by příklady uvedené níže měly fungovat až v Debianu Squeeze s tím, že na starších verzích Debianu jsem testování neprováděl.

SNI a Apache

Máte-li k dispozici Apache 2.2.12 nebo novější, můžete použít mod_ssl. Na starších systémech musíte použít mod_gnutls. Následující ukázka je určena pro mod_ssl:

Listen 443
    
NameVirtualHost *:443

<VirtualHost *:443>
  DocumentRoot /var/www/example1.cz
  ServerName www.example1.cz

  SSLEngine On
  SSLCertificateFile /etc/apache2/example1.pem
  SSLCertificateKeyFile /etc/apache2/example1.key

</VirtualHost>

<VirtualHost *:443>
  DocumentRoot /var/www/example2.cz
  ServerName www.example2.cz
  
  SSLEngine On
  SSLCertificateFile /etc/apache2/example2.pem
  SSLCertificateKeyFile /etc/apache2/example2.key

</VirtualHost>

Jak je vidět, na samotném nastavení není nic obtížného, postačí klasický NameVirtualHost a pak jednotlivé virtuální weby lišící se jménem a certifikáty. V případě, že Apache získá požadavek od nekompatibilního klienta, použije certifikát prvního takto definovaného virtuálního webu, tedy v tomto případě to bude example1.

Pokud byste raději, aby Apache v případě nekompatibilního klienta vygeneroval chybovou hlášku 403 a nedovolil klientovi přístup, nastavte volbu SSLStrictSNIVHostCheck na „on“ (výchozí nastavení je „off“, takže pokud tuto volbu neuvedete, Apache se zachová, jak bylo uvedeno výše).

Máte-li starší systém se starší verzí Apache, zbývá vám kromě možnosti použít novější Apache z backportů jen mod_gnutls. Konfigurace SNI tímto způsobem by vypadala takto (nezapomeňte modul gnutls nejprve nainstalovat a aktivovat):

Listen 443
    
NameVirtualHost *:443

<VirtualHost *:443>
  DocumentRoot /var/www/example1.cz
  ServerName www.example1.cz

  GnuTLSEnable on
  GnuTLSExportCertificates on
  GnuTLSCertificateFile /etc/apache2/example1.pem
  GnuTLSKeyFile /etc/apache2/example1.key
 
</VirtualHost>

<VirtualHost *:443>
  DocumentRoot /var/www/example2.cz
  ServerName www.example2.cz

  GnuTLSEnable on
  GnuTLSExportCertificates on
  GnuTLSCertificateFile /etc/apache2/example2.pem
  GnuTLSKeyFile /etc/apache2/example2.key
 
</VirtualHost>

SNI a Lighttpd

Pokud máte aktivovaný modul ssl, postačí do definice virtuálních webů přidat direktivu ssl.pemfile s odkazem na soubor obsahující jak certifikát, tak privátní klíč, takto:

$HTTP["host"] == "www.example1.cz" {
    server.document-root = "/var/www/example1.cz"
    ssl.pemfile = "/etc/lighttpd/server.pem" 
}
$HTTP["host"] == "www.example2.cz" {
    server.document-root = "/var/www/example2.cz"
    ssl.pemfile = "/etc/lighttpd/server2.pem" 
}

SNI a Cherokee

Cherokee používá SNI již ve výchozím nastavení jako mechanismus pro odlišení SSL virtuálních webů, takže zde není návod třeba. Jediné, na co byste si měli dát pozor, je přiřazení certifikátu výchozímu virtuálnímu serveru, jehož certifikát se použije v případě, že klient SNI nepodporuje.

SNI a Nginx

Použití SNI v případě serveru Nginx je také relativně jednoduché, resp. postačí podobná strategie jako v případě serveru Apache, tj. normálně definovat virtuální weby běžící na portu 443 se zapnutým ssl (příklad viz níže). Stejně jako u Apache platí i zde, že certifikát prvního virtuálního webu se použije jako výchozí certifikát pro nekompatibilní klienty.

Abyste si ověřili, zdali Nginx podporuje SNI, resp. byl zkompilován s podporou SNI, můžete použít následující příkaz:

nginx -V

Ve výpisu byste měli vidět následující řádku:

TLS SNI support enabled

Pokud ji neuvidíte, nezbude vám než si Nginx zkompilovat ručně. Následuje příklad konfigurace SNI v Nginx:

server {
  listen   443;
  server_name  www.example1.cz;

  ssl  on;
  ssl_certificate  /etc/nginx/example2.pem;
  ssl_certificate_key /etc/nginx/example2.key;

  location / {
    root   /var/www/example1.cz;
    index  index.html index.htm;
  }
}

server {
  listen   443;
  server_name  www.example2.cz;

  ssl  on;
  ssl_certificate  /etc/nginx/example1.pem;
  ssl_certificate_key /etc/nginx/example1.key;

  location / {
    root   /var/www/example2.cz;
    index  index.html index.htm;
  }
}

Pár slov na závěr

V úvodu zmíněný problém s HTTPS řeší nejenom SNI, ale také svým způsobem protokol IPv6, i když přechod klientů na IPv6 bude možná podstatně pomalejší než přechod klientů na prohlížeče schopné používat SNI. Faktem nicméně je, že podpora SNI na straně klientů v tuto chvíli stále není bezproblémová a nějakou dobu ještě nebude, což celkem logicky brzdí nasazení SNI na produkční servery.