Apache mit SNI aufsetzen (Debian)

HTTPS ist schön und gut – solange man richtig kräftig dafür zahlt. Denn kostenlose Zertifikate (z.b. bei StartSSL) gibt es nur für eine Domain, Wildcard- oder Multi-Domain-Zertifikate kosten hingegen richtig viel Geld. Nun ist es aber so, dass man eventuell mehrere Domains mit unterschiedlichen Zertifikaten (soll ja kostenlos bleiben) auf einer IP nutzen möchte. Bis vor ein paar Jahren wäre das ein hoffnungsloses Unterfangen gewesen, da bei SSL normalerweise alles außer der IP verschlüsselt übertragen wird, d.h. auch der „Host“-Header, den der Client mit überträgt. Ohne diesen Header ist es dem Server nicht möglich, ein Request einer bestimmten Domain zuzuordnen. Er muss also erst das zur IP gehörende Zertifikat nutzen, um an den verchlüsselten Host-Eintrag zu gelangen. Ergo nur 1 Zertifikat pro IP.

Die Lösung dieses Problems heißt SNI (Server Name Indication) und wird seit ein paar Jahren von allen gängigen Browsern unterstützt:

  • Mozilla Firefox 2.0+
  • Opera 8.0+ (TLS 1.1 muss aktiviert sein)
  • Internet Explorer 7+ (Windows Vista oder neuer)
  • Google Chrome (Windows Vista oder neuer)
  • Safari Safari 3.2.1+ auf Mac OS X 10.5.6+ und Windows (Windows Vista oder neuer)

Durch SNI wird der gewünschte Host als „server_name“-Parameter bereits beim Verbinungsaufbau übergeben – keine Probleme bei der Zuordnung mehr. Das klingt an sich sehr gut, also habe ich mich auf die Suche nach einer Anleitung gemacht und musste feststellen, dass mein Debian Etch nicht die nötige OpenSSL-Version (mindestens 0.9.8f) installiert hat. Aber da gibt es ja noch mod_gnutls, damit soll es ohne Apache oder OpenSSL neu zu kompilieren gehen. Nachdem ich alles eingerichtet hatte dann die Enttäuschung: mod_gnutls funktioniert zwar aber nicht mit den Zertifikaten von StartCom, weil die nämlich „intermediate“ CA-Zertifikate benötigen um verifiziert werden zu können (und mod_gnutls das nicht unterstützt). Somit zeigte er mir immer eine Warnung nach dem Muster „unknown certificate issuer“ an. Toll, das hätte ich auch mit einem selbst signierten Zertifikat hinbekommen.
Nach Überwindung meiner Enttäuschung dachte ich mir dann: Na ja was solls, dist-upgrade wollte ich eh schon lange mal machen also ab zu lenny. Halt… zunächst natürlich noch ein Backup erstellt für den Fall der Fälle.

Alle folgenden Befehle werden als root ausgeführt. Also entweder als root einloggen oder (besser) per su bzw. sudo –s zum root werden.

Danach die /etc/apt/sources.list.d/debian.list bearbeiten:

1
2
3
4
5
6
7
# new lenny packages.
deb http://ftp2.de.debian.org/debian/ lenny main contrib non-free
deb http://security.debian.org/ lenny/updates main contrib non-free
 
# source packages.
deb-src http://ftp2.de.debian.org/debian/ lenny main contrib non-free
deb-src http://security.debian.org/ lenny/updates main contrib non-free

Speichern und

1
2
apt-get update
apt-get dist-upgrade

ausführen. Die Installation hat bei meinem vServer etwa 10 Minuten gedauert. Je nach Anzahl und Umfang der installierten Pakete kann es aber durchaus länger dauern. Die jeweiligen Default-Werte (N) für das beibehalten der Konfiguration sind eigentlich optimal. Ich konnte auch kein Programm feststellen, das ohne neue Config nicht mehr arbeitet. Das wa’s eigentlich schon, jetzt ist aus Etch Lenny geworden und OpenSSL liegt in Version 0.9.8g (Stand: 27.12.09) vor. Damit unterstützt es jetzt von Haus aus SNI. Leider unterstützt Apache in der von Debian Lenny standardmäßig installierten (und wirklich alten!) Version es aber noch nicht. Daher muss hier nochmal Hand angelegt werden. Die Anleitung dazu habe ich bei blog.mellenthin.de gefunden und nur leicht modifiziert, da bereits Apache 2.2.14-4 vorliegt. Die gerade aktuelle Version findet sich immer unter http://packages.debian.org/source/squeeze/apache2 und kann dort auch herunter geladen werden. Zunächst brauchen wir aber noch ein paar Tools (falls sie noch nicht installiert sind):

1
2
apt-get install dpkg-dev build-essential fakeroot libcap2-dev autoconf
apt-get build-dep apache2

Dann den Sourcecode laden:

1
2
3
wget http://ftp.de.debian.org/debian/pool/main/a/apache2/apache2_2.2.14-4.dsc
wget http://ftp.de.debian.org/debian/pool/main/a/apache2/apache2_2.2.14.orig.tar.gz
wget http://ftp.de.debian.org/debian/pool/main/a/apache2/apache2_2.2.14-4.diff.gz

mit dem Befehl

1
dpkg-source -x apache2_2.2.14-4.dsc

wird der Quellcode entpackt und mit

1
2
cd apache2-2.2.14/
dpkg-buildpackage -us -uc -rfakeroot

wird der Kompilier-Vorgang eingeleitet. Der Vorgang selbst kann lange dauern, bei mir waren es über 20 Minuten. Warnings während dieses Prozesses sind normal und können ignoriert werden.
Die fertigen Pakete liegen dann ein Verzeichnis höher zur Installation bereit:

1
2
cd ..
dpkg -i apache2_2.2.14-4_amd64.deb apache2.2-bin_2.2.14-4_amd64.deb apache2.2-common_2.2.14-4_amd64.deb apache2-mpm-event_2.2.14-4_amd64.deb apache2-mpm-itk_2.2.14-4_amd64.deb apache2-mpm-worker_2.2.14-4_amd64.deb apache2-mpm-prefork_2.2.14-4_amd64.deb apache2-suexec_2.2.14-4_amd64.deb apache2-utils_2.2.14-4_amd64.deb

Je nach verwendeter Version und Architektur (x32 oder x64) heißen die Pakete anders, der Namensteil vorn bleibt aber immer gleich. Bei der Installation werden die bestehenden Pakete von Apache durch die neuen 2.2.14er ersetzt, Apache wird dadurch SNI-fähig gemacht.

Jetzt ist es Zeit, den private key und die Zertifikatsanforderung (CSR) zu erzeugen. Das geht ganz einfach und schnell mit openssl:

[Update 2012: StartSSL verlangt nun eine Mindestschlüssellänge von 2048 Bit]

1
2
3
openssl genrsa -des3 2048 > ssl.key.org (hier ist ein Passwort erforderlich)
openssl req -new -key ./ssl.key > ssl.csr
openssl rsa -in ssl.key.org -out ssl.key

Der letzte Befehl schreibt den ungeschützten private key in die Datei ssl.key, das ist notwendig da sonst bei jedem Apache-Start (auch nach Server-Neustart) die Passwortabfrage kommt und der httpd so lange hängt. Daher immer gut auf den ssl.key aufpassen und am besten mittels

1
chmod 400 ssl.key

nur für den root-Account lesbar machen. Den ssl.csr nimmt man dann und übergibt ihn der CA seiner Wahl (bei mir: StartSSL). Diese erzeugt daraus dann die benötigte ssl.crt, welche man ebenfalls auf seinen Server hochladen muss. Damit ist das Zertifikat an sich bereit, nun muss Apache noch konfiguriert werden, es auch zu verwenden.

Falls die Direktive „NameVirtualHost“ noch nicht in der Apache-Config gesetzt ist muss dies nun nachgeholt werden (z.B. in der httpd.conf):

1
NameVirtualHost 196.17.23.52:443

Um zu verhindern, dass nicht-SSI-fähige Browser nur ein 403: forbidden angezeigt bekommen (stattdessen sollte man sie freundlich darauf hinweisen, auch im Interesse ihrer eigenen Sicherheit einen neueren Browser zu verwenden) erstellt man folgenden Eintrag (am besten wieder in der httpd.conf oder in der apache2.conf):

1
SSLStrictSNIVHostCheck off
1
2
3
4
5
6
7
8
<VirtualHost 196.17.23.52:443>
  # Because this virtual host is defined first, it will
  # be used as the default if the hostname is not received
  # in the SSL handshake, e.g. if the browser doesn't support
  # SNI.
  DocumentRoot /www/error/use_new_browser_html
  ServerName www.serverone.tld
</VirtualHost>

Für die einzelnen Adressen, die man jetzt per SSL zugreifbar machen möchte wird nun jeweils ein VirtualHost-Eintrag erstellt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<VirtualHost 196.17.23.52:443>
   ServerName serverone.tld:443
   DocumentRoot /var/www/vhosts/serverone
   #ErrorLog /usr/local/apache/logs/error_log
   #TransferLog /usr/local/apache/logs/access_log
   SSLEngine on
   SSLProtocol all -SSLv2
   SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM
   SSLCertificateFile /usr/local/apache/conf/ssl.crt
   SSLCertificateKeyFile /usr/local/apache/conf/ssl.key
   SSLCertificateChainFile /usr/local/apache/conf/sub.class1.server.ca.pem
   SSLCACertificateFile /usr/local/apache/conf/ca.pem
   SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
   #CustomLog /usr/local/apache/logs/ssl_request_log \
   #   "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</VirtualHost>

Für die Nutzer von ACP-Oberflächen wie z.B. Plesk ergibt sich hier eine Besonderheit. Die Admin-Systeme überschreiben nämlich gern ihre eigenen Dateien. Bei Plesk werden die Einstellungen für VirtualHosts z.B. in der /var/www/vhosts/serverone/conf/httpd.include liegen. Diese wird aber regelmäßig überschrieben. Als info steht dort, wo man seine Config hinspeichern muss, damit sie dauerhaft erhalten bleibt. Leider kann man dann nur ausgewählte Direktiven setzen (z.B. php-flags), keine vollständigen Einträge (dazu steht übrigens nichts im Handbuch). Daher legt man am besten für jeden zu erstellenden VirtualHost eine nameDesHosts.conf in /etc/apache2/conf.d/ an oder nutzt wieder die httpd.conf/apache2.conf, wobei die erstere Möglichkeit übersichtlicher ist, aber erfordert, dass das Verzeichnis conf.d auch eingelesen wird. Zusätzlich sollte man vorsichtshalber in Plesk das SSL-Hosting für diese Domain ausstellen, damit dessen Settings nicht versehentlich die selbst erstellten überschreiben.

Das SSLCertificateChainFile und das SSLCACertificateFile muss man sich dabei vom Anbieter holen, sofern das erforderlich ist (z.B. bei StartSSL). Anderenfalls lässt man die Einträge einfach weg. Auch der Eintrag für die Logfiles kann natürlich weggelassen werden.

Nach einem Neustart des Apachen kann man jetzt – sofern man alles richtig gemacht hat – alle Domains, für die ein gültiges Zertifikat existiert, ohne Fehlermeldungen aufrufen.