Um die Inhalte einer Website vor unauthorisierten Benutzern zu schützen, installiert man gerne eine Passwortabfrage. In den meisten Fällen reicht das auch aus. In manchen Fällen ist die Website aber derart schützenswert, dass eine weitere Stufe des Zugriffsschutzes hinzugenommen werden muss.
Dazu eignet sich die Client-Zertifikatsauthentifizierung ziemlich gut. Für den Zugang zum Webserver benötigt man also nicht nur Benutzername und Passwort, sondern muss dazu auch noch ein Clientzertifikat haben. Ohne Zertifikat nützen BN/PW herzlich wenig, denn man gelangt nicht einmal so weit sie einzusetzen.
Wie funktioniert das?
Der SSL/TLS-verschlüsselte Webserver aktiviert mit einer weiteren Direktive einen Mechanismus, der dafür sorgt, dass bei Kontakt zum Webserver der Client sich per Zertifikat authentifizieren muss. Dafür muss ein Clientzertifikat im Zertifikatspeicher des Clients bzw. in dessen Browser (bpsw. Firefox) installiert werden. Bei Abfrage durch den Webserver (Apache2) wird der Client sein Zertifikat zum Server senden. Dieser nimmt einen Vergleich vor und erlaubt oder verweigert dem Client den Zugriff auf die Website. Somit schafft der Website-User es nicht mal auf die Login-Seite, da im Vorfeld jede Kommunikation verweigert wird.
Voraussetzungen
Es muss ein Zertifikat auf dem Webserver installiert sein und ein Clientzertifikat, basierend auf dem Serverzertifikat, ausgestellt und auf dem Client installiert werden.
Serverzertifikat erstellen
Beginnen wir damit den Private-Key für den Server zu erzeugen. Das erreichen wir mit folgender Zeile. Der Key hat eine Schlüssellänge von 4096 Bit und wird mit dem Dateinamen server.priv.key gespeichert.
openssl genrsa -out server.priv.key 4096
Danach erstellen wir das Certificate Signing Request.
openssl req -new -key server.priv.key -out server.csr
Es ist völlig ausreichen, wenn man am Ende den Common Name (FQDN) korrekt ausfüllt. Danach kann man ganz einfach den Key selbst signieren, um ein gültiges Serverzertifikat zu erhalten. Das geht so:
openssl x509 -in server.csr -out server.crt -req -signkey \
server.priv.key -days 365
Das Zertifikat ist 365 Tage gültig und wird als server.crt abgespeichert. Die Dateien server.crt und server.priv.key in ein für den Apache2 zugänglichen Pfad abgelegt werden. Ich habe mich für /etc/apache2/ssl/ entschieden und lege sie dort ab. In die Konfiguration für den vHost trage ich nun den Anteil für SSL ein.
<VirtualHost *:443>
...
ServerName mytest.lokal
SSLEngine on
SSLCertificateKeyFile /etc/apache2/ssl/server.priv.key
SSLCertificateFile /etc/apache2/ssl/server.crt
...
</VirtualHost>
Nun den Apache mit systemctl restart apache2.service neu starten und die Website sollte per HTTPS erreichbar sein.
Benutzer- bzw. Clientzertifikat erstellen
Jetzt brauchen wir noch das Zertifikat für den Client. Damit wir uns das Zertifikat gescheit ausstellen können, konfigurieren wir uns openSSL ein bisschen. Dazu führen wir folgende Zeile aus:
sed -i 's/= .\/demoCA/= \/root\/ca/g' /etc/ssl/openssl.cnf
Alternativ kann man auch in der /etc/ssl/openssl.cnf in Zeile 42 den Pfad per Hand ändern. Danach legen wir noch die notwendigen Verzeichnisse und Dateien an.
mkdir /root/ca
cd /root/ca
mkdir certs crl csr newcerts private
touch index.txt
echo 1000 > serial
Ab jetzt können wir endlich mit dem Benutzer-Zertifikat beginnen. Einfach die folgenden Kommandos der Reihe nach ausführen:
openssl genrsa -des3 -out user_1_cert.key
openssl req -new -key user_1_cert.key -out user_1_cert.req
openssl ca -cert /etc/apache2/ssl/server.crt -keyfile \
/etc/apache2/ssl/server.priv.key -out user_1_cert.crt \
-in user_1_cert.req
Jetzt haben wir das Benutzerzertifikat erzeugt, aber es geht so noch nicht zu verwenden. Wir müssen es noch in ein Format bringen, welches die Browser verstehen und importieren können. Dazu nutzt man üblicherweise das p12-Format. Folgende Zeile erzeugt uns was wir brauchen. Außerdem wird ein Export-Passwort abgefragt. Wir wählen natürlich ein vernünftiges nach dem üblichen Regeln der Kunst, um kein zu lasches Passwort zu erhalten. Dazu gehören 12 Zeichen Länge, inbegriffen Sonderzeichen, Groß- und Kleinschreibung sowie Zahlen.
openssl pkcs12 -export -inkey user_1_cert.key \
-name "User_1" -in user_1_cert.crt \
-certfile /etc/apache2/ssl/server.crt -out user_1_cert.p12
Die Datei user_1_cert_p12 muss nun dem entsprechenden Benutzer, der die Website besuchen können soll, zur Verfügung gestellt werden samt dem dazugehörigen Export-Passwort. Ein Import ohne Passwort ist nicht möglich!
Apache2 Cert-Auth aktivieren
Damit der Apache2 die Zertifikatsauthentifikation nutzt, ist eine Direktive in der .conf des vHosts zu setzen. Direkt unter dem Teil wo das SSL-Zertifikat eingebunden ist kann auch gleich folgendes hinzugefügt werden (blau markiert):
<VirtualHost *:443>
...
ServerName mytest.lokal
SSLEngine on
SSLCertificateKeyFile /etc/apache2/ssl/server.priv.key
SSLCertificateFile /etc/apache2/ssl/server.crt
SSLCACertificateFile /etc/apache2/ssl/server.crt
SSLVerifyClient require
SSLVerifyDepth 1
SSLOptions +StdEnvVars
...
</VirtualHost>
Nun den Apache neu starten. Beim Versuch die Website zu erreichen erhält man nun folgende Fehlermeldung mit dem Firefox:
Zertifikat importieren
Firefox Browser
Unter Firefox funktioniert der Import ganz leicht unter -> Einstellungen -> Datenschutz & Sicherheit -> Zertifikate anzeigen. Im folgenden Fenster auf den Kartenreiter "Ihre Zertifikate" und danach "Importieren..." klicken. Danach die Website erneut aufrufen nun erhält man aber eine Identifikationsanfrage. Diese bestätigt man dann mit "ok" und schon kann man die Website sehen.
Windowszertifikatspeicher
Für den Internetexplorer sowie Google Chrome muss man das Zertifikat in den Zertifikatsspeicher von Windows laden. Der einfachste Weg ist das Zertifikat mit rechter Maustaste anzuklicken und "PFX installieren" zu wählen. Dann öffnet sich ein Fenster. Die restlichen Schritte sind selbsterklärend.
Fehlermeldung für Clients ohne Zertifikat
Wie bereits oben im Abbild gezeigt und auch erklärt, erhält ein Client ohne Zertifikat keinen Zugriff auf die Website. Stattdessen gibt es diese Fehlermeldung wie oben gezeigt. Dem einfachen Benutzer sagt sowas nichts und weiß sich dann auch nicht zu helfen. Es wäre nicht schlecht, wenn diese Clients dann eine Information bekämen mit der sie mehr anfangen können. Zum Beispiel ein Hinweis, dass Sie vom Administrator bzw. Website-Betreiber ein Zertifikat benötigen und sich an ihn wenden sollen.
Ist das eine wirklich so schlaue Idee?
Das liegt im Auge des Betrachters. Mir persönlich würde es missfallen. Warum? Schauen wir uns die Kommunikation mal genauer an:
HTTPS ist nichts weiter als HTTP getunnelt. Mehr nicht, aber auch nicht weniger. Die zertifikatsbasierte Clientauthentifizierung prüft beim Aufbau der Verbindung, ob der Client ein gültiges Zertifikat hat. Hat er das nicht, so gibt es erst gar keine Kommunikation. Es wird kein Tunnel aufgebaut und folglich auch kein HTTP.
Möchte ich aber eine Infopage haben, dann muss ich irgendwie ja doch den Zugriff erlauben und sei es nur für diese eine html-Seite. Das bedeutet also: Ich weiche das Konzept auf, um eine Seite anzuzeigen, die ich eigentlich nicht brauche.
Konfiguration der Fehlerseite
Innerhalb des vHosts, also zwischen <VirtualHost *:443> und </VirtualHost> muss noch eine die Directory-Direktive stehen und SSLVerifyClient
muss von required
zu optional
geändert werden. Und natürlich muss nun eine .html-Datei hinterlegt werden worin die Fehlermeldung hinterlegt ist, um den Benutzer ohne Zertifikat zu informieren. In diesem Beispiel habe ich sie error.html genannt.
Alles in allem müsste die vHost-Konfiguration dann so ausschauen:
<VirtualHost *:443>
ServerName pki.silvesterlangen.de
DocumentRoot /var/www/html/
SSLEngine on
SSLCertificateFile /etc/apache2/ssl/server.crt
SSLCertificateKeyFile /etc/apache2/ssl/server.priv.key
SSLCACertificateFile /etc/apache2/ssl/server.crt
SSLVerifyClient optional
SSLVerifyDepth 1
SSLOptions +StdEnvVars
<directory "/var/www/html">
RewriteEngine on
RewriteCond %{SSL:SSL_CLIENT_VERIFY} !^SUCCESS$
RewriteRule .* error.html [L]
</directory>
</VirtualHost>
<VirtualHost *:80>
ServerName pki.silvesterlangen.de
Redirect permanent / https://192.168.2.67/
</VirtualHost>
Den Redirect für Port 80 auf HTTPS habe ich hier mit dazugepackt.