TLS-Client-Authentication mit Nginx und PHP-FPM

Seit ich es bei StartSSL.com gesehen habe, habe ich mich gefragt, wie ein ordentlicher Login über TLS-Client-Zertifikate funktionieren kann. So schwierig, wie man es sich im ersten Moment vorstellt, ist es eigentlich gar nicht.

Als erstes muss man sich einmal um die PKI-Infrastruktur kümmern. Genauer gesagt muss man eine Certificate Authority anlegen, die die benötigten Client-Zertifikate erstellt. Für kleinere Installationen und Tests kann man das natürlich erst einmal via OpenSSL tun. Für größere Installationen würde ich hingegen zu ordentlichen CA-Lösungen raten (wie z.B. der EJCBA).

Wir fangen also mit dem Erstellen der CA und des selbstsignierten CA-Zertifikats an. An dieser Stelle ist es in Ordnung, dass das CA-Root-Zertifikat selbstsigniert ist, da ihr ja auch später die Client-Zertifikate ausgeben werdet. Im Gegensatz zu TLS-Server-Zertifikaten gibt es hier keinen weit verbreiteten Truststore in den einzelnen Browsern, den ihr berücksichtigen müsst. Sogar im Gegenteil: Schließlich wollt nur ihr in der Lage sein, jemandem Zugriff auf eure Anwendung zu geben.

1
2
3
4
5
6
mkdir -p /var/www/ssl/client/
chmod 700 /var/www/ssl/client/
cd /var/www/ssl/client/

openssl genrsa -des3 -out ./ca.key 4096
openssl req -new -x509 -days 365 -key ./ca.key -out ./ca.crt

Als nächstes wollen wir das erste Client-Zertifikat erstellen. Dieses wird in diesem Fall direkt von der Root-CA signiert. Ihr könnt natürlich noch eine Zwischen-CA erstellen, aber für kleinere Installationen und Tests ist das nicht unbedingt notwendig.

1
2
3
4
5
cd ~/

openssl genrsa -des3 -out ./client.key 1024
openssl req -new -key ./client.key -out ./client.csr
openssl x509 -req -days 365 -in ./client.csr -CA /var/www/ssl/client/ca.crt -CAkey /var/www/ssl/client/ca.key -set_serial 01 -out ./client.crt

Ihr habt nun ein Zertifikat (in der Datei "client.crt") und den zugehörigen privaten Schlüssel (in der Datei "client.key") - diese müsst ihr nun in euren jeweiligen Browser importieren. Das Vorgehen dazu unterscheidet sich von Browser zu Browser und System zu System. In meinem Fall musste ich erst alles in eine *.p12-Datei überführen, damit die Schlüsselbundverwaltung von Mac OS X das Schlüsselpaar importieren konnte. Dazu musste ich noch folgendes aufrufen:

1
2
3
cd ~/

openssl pkcs12 -export -clcerts -inkey ./client.key -in ./client.crt -out ./client.p12 -name "<sprechender Name>"

Nachdem das erledigt ist, müssen wir Nginx beibringen, die Zertifikate zu verarbeiten. Dabei setze ich auf die SSL-Funktionen von Nginx auf. Wie die eingerichtet werden, hatte ich bereits vor einiger Zeit beschrieben. Die Pfade in der Server-Konfiguration sind reine Platzhalter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
server {
  listen 80;
  listen 443 ssl;

  ssl_certificate     /var/www/ssl/example_com.crt;
  ssl_certificate_key /var/www/ssl/example_com.key;

  ssl_client_certificate /var/www/ssl/client/ca.crt;
  ssl_verify_client      optional;
  #ssl_verify_client      on;

  server_name example.com;

  access_log /home/sftpuser/example_com/logs/access.log;
  error_log  /home/sftpuser/example_com/logs/error.log info;

  root /home/sftpuser/example_com/docs/;

  # switch on HSTS
  add_header Strict-Transport-Security "max-age=604800";
  add_header X-Frame-Options           "SAMEORIGIN";

  # enable php
  location ~ ^(.*)\.php$ {
    if (!-f $request_filename) {
      return 404;
    }

    include      /srv/conf/nginx/fastcgi_params;
    fastcgi_pass unix:/var/www/sockets/example_com.socket;

    fastcgi_param TLS_SUCCESS $ssl_client_verify;
    fastcgi_param TLS_DN      $ssl_client_s_dn;
    fastcgi_param TLS_CERT    $ssl_client_cert;
  }

  #deactivate later on
  #autoindex on;
}

Im ersten Schritt benötigt der Server ein TLS-Server-Zertifikat, welches von einem vertrauenswürdigen Zertifikatsersteller stammen sollte. Anschließend muss Nginx die CA bekannt gemacht werden, die die Client-Zertifikate ausstellt - dabei handelt es sich um die gerade erst erstellte, eigene CA. Zudem muss über "ssl_verify_client" die TLS-Client-Authentication aktiviert werden. Hier gibt es die Möglichkeit, sie per "on" verpflichtend für alle Nutzer zu aktivieren oder aber per "optional" die Client-Authentication optional machen. Ist sie verpflichtend, dann werden alle fehlerhaften Anfragen abgewiesen.

Damit nun das Backend PHP-FPM weiß, dass sich ein Client per TLS-Client-Authentisierung eingeloggt hat und vor allem, welcher Nutzer das war, müssen wir noch ein paar CGI-Parameter bereitstellen. Diese stehen den eigentlichen PHP-Scripten dann über die Variable "$_SERVER[]" zur Verfügung. (Wie Nginx zusammen mit PHP-FPM aufgesetzt werden können, kann in einem früheren Blogartikel nachgelesen werden.)

Nach dieser Fingerübung muss Nginx neu gestartet werden.

1
/etc/init.d/nginx restart

Nun kann bereits das erste Mal getestet werden, ob die Grundkonfiguration stimmt. Man sollte entsprechend (je nach Browser und System) soweit kommen, dass die Authentisierung funktioniert.

Safari will auf das Schlüsselpaar zugreifen

Safari will auf das Schlüsselpaar zugreifen

Wenn das funktioniert, könnt ihr euch eine Testseite bauen. In "/home/sftpuser/example_com/docs/index.php" könnte z.B. folgendes stehen, womit einfach nur die CGI-Parameter ausgegeben werden:

1
2
3
4
5
<?php
  print("TLS_SUCCESS: " . $_SERVER["TLS_SUCCESS"] . "<br/>\n");
  print("TLS_DN: " . $_SERVER["TLS_DN"] . "<br/>\n");
  print("TLS_CERT: " . $_SERVER["TLS_CERT"] . "<br/>\n");
?>

Sollte auch das funktionieren, seid ihr bereits fertig mit dem Einrichten und Testen der TLS-Client-Authentisierung in Nginx und PHP-FPM.

PHP-Seite zeigt Daten der TLS-Client-Authentisierung

PHP-Seite zeigt Daten der TLS-Client-Authentisierung

Mit diesem Setup kann man nun einige tolle Dinge anstellen: Zum Beispiel kann man nun sämtliche Nutzerpasswörter bei kritischen Applikationen durch TLS-Zertifikate ersetzen oder aber eine Zwei-Faktor-Authentisierung mit Hilfe dieser Zertifikate realisieren. Genauso kann man auf diese Art und Weise einen Single-Sign-On realisieren. Die Nutzer der einzelnen Anwendungen würden jeweils über "TLS_DN" identifiziert werden und bräuchten praktischerweise nur ein Zertifikat für alle Anwendungen. Wichtig dafür wäre nur, dass alle Anwendungen der gleichen Root-CA vertrauen - der Rest erledigt sich dann von selbst.

Authentisierte Grüße, /CN=Kenny

1 Kommentar » Schreibe einen Kommentar

  1. Das Ändern aller Nutzerpasswörter ist sehr praktisch. Genau aus diesem Grund mag ich ein neues Setup einrichten. Werde es mal mit deiner Methode versuchen zu realisieren. Für deine Bemühungen vielen Dank 🙂

    LG

    Clemens

Schreibe einen Kommentar

Um Ihnen beim weiteren Kommentieren auf dieser Webseite die erneute Eingabe Ihrer Daten zu ersparen, wird beim Absenden Ihres Kommentars ein Cookie an Ihren Browser gesendet und von diesem gespeichert. Mit dem Absenden eines Kommentars auf dieser Webseite stimmen Sie der Speicherung und Übertragung dieses Cookies explizit zu.

Pflichtfelder sind mit * markiert.