TLS-Client-Authentication mit NGINX und PHP-FPM

18.06.2014 yahe administration legacy linux security

Seit ich es bei StartSSL gesehen habe, habe ich mich gefragt, wie ein 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.

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 3650 -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.

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 mit der Seriennummer "01" 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 Fall von macOS musste ich erst alles in eine "*.p12"-Datei überführen, damit die Schlüsselbundverwaltung das Schlüsselpaar importieren konnte. Dazu musste ich folgendes aufrufen:

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. Die Pfade in der Server-Konfiguration sind reine Platzhalter.

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 /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log info;

  root /var/www/htdocs/;

  add_header Referrer-Policy           "same-origin";
  add_header Strict-Transport-Security "max-age=604800";
  add_header X-Content-Type-Options    "nosniff";
  add_header X-Frame-Options           "SAMEORIGIN";
  add_header X-XSS-Protection          "1; mode=block";

  # 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 zu 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. Nach dieser Fingerübung muss NGINX neu gestartet werden.

/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

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

<?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

Mit diesem Setup kann man nun 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.


Search

Categories

administration (45)
arduino (12)
calcpw (3)
code (38)
hardware (20)
java (2)
legacy (113)
linux (31)
publicity (8)
raspberry (3)
review (2)
security (65)
thoughts (22)
update (11)
windows (17)
wordpress (19)