Stephan Herbers

Sichere TLS Konfiguration mit NGINX

Über den Autor: Ich bin Fachinformatiker für Systemintegration. Was ich hier zusammen geschrieben habe, habe ich mir in den letzten Jahren angelesen. Ich beziehe mich bei meiner Auslegung von sicher, auf das, was von der Mehrheit (oder was ich glaube die Mehrheit zu sein) der Meinungen im IT-Sicherheits Umfeld, als sicher gilt. Wer objektiv falsche Aussagen findet, oder Fragen hat, soll sich bitte bei mir melden, damit ich das korrigieren / ergänzen kann.

Zuletzt aktualisiert: 04.01.2018

1 Grundlagen und Ressourcen

1.1 Einleitung

Diese Seite gibt eine Einführung in die sichere Einrichtung von TLS mit NGINX. Sicher heißt hier, dass nur Protokolle / Verschlüsselungsalgorithmen / Schlüsselaustausch Cipher benutzt werden, zu denen NOCH keine, ausnutzbaren, Schwachstellen bekannt sind.

TLS steht für Transport Layer Security. In den Konfigurationsparametern kommt noch die Abkürzung SSL (Secure Sockets Layer) vor. Der Begriff SSL ist veraltet, wird aber noch weitläufig benutzt. Das SSL Protokoll wird in der hier gezeigten Konfiguration nicht einmal mehr verwendet.

Gedacht ist die hier besprochene Konfiguration, für Leute denen die Sicherheit ihrer Besucher mehr Wert ist, als die Unterstützung von veralteten Browsern. Mit veralteten Browsern ist Internet Explorer <= 10, Android <= 4.3, Safari <= 6.0.4 gemeint. Eine ausführliche Liste findet sich in Abschnitt 3.3 Handshake Simulationen. Auf solche Nutzer darf man keine Rücksicht nehmen, da dies unter Umständen die Sicherheit aller anderen Benutzer gefähren kann. Andere Browser (Desktop/Mobil) verursachen keine Probleme.

Diese Konfiguration erlaubt keine unverschlüsselten Verbindungen, da es offensichtlich die größte Schwachstelle ist, wenn der Benutzer die Möglichkeit hat über HTTP statt HTTPS zu verbinden. Selbst wenn der Benutzer selbst lieber verschlüsseln würde, kann ein Man-in-the-middle (MITM) dem Benutzer vorgaukeln, es gebe keine HTTPS Seite und dann die Verbindung abhören. HSTS kann hier nicht immer Abhilfe schaffen (Bei der ersten Verbindung zum Beispiel).

Es werden ausschließlich Cipher mit Forward Secrecy angeboten, da davon ausgegangen werden kann, dass die meisten/alle verschlüsselten Verbindungen, heutzutage für Jahre gespeichert werden. Ohne Forward Secrecy besteht dann die Gefahr, das gespeicherte Verbindungen im Nachhinein durch Erlangen des privaten Schlüssels (Einbruch in den Server / Zwang zur Herausgabe durch Ermittlungsbehörden), entschlüsselt werden können.

Es wird ausschließlich, Elliptic Curve Cryptography (ECC) für den Schlüsselaustausch verwendet. ECC verwendet andere mathematische Verfahren als RSA und benötigt dadurch, bei vergleichbarer Sicherheit, eine kürzere Schlüssellänge. Das bringt eine deutliche Performancesteigerung beim Schlüsselaustausch. Ein 256 bit ECC Schlüssel entspricht dabei ca. einem 3072 bit RSA Schlüssel.

1.2 Mögliche Schwachstellen

Certificate authorities: Wenn man einen Aluhut auf hat, kann man auch die CAs selbst als Schwachstelle ansehen. Diese können nämlich, freiwillig oder gezwungen, zusätzliche, gültige Zertifikate, für die eigene Domain erstellen und diese an Dritte (Geheimdienste, Ermittlungsbehörden) weitergeben. Diese können zwar nicht direkt zur Entschlüsselung der Verbindung benutzt werden, Angreifer können sich aber zwischen Benutzer und Server setzen und eine Ende-zu-Ende verschlüsselte Verbindung vorgaukeln. Auch ist es möglich, und schon passiert, das in die Server der CAs eingebrochen wird und die Root Zertifikate kopiert wurden. Die Angreifer können sich dann beliebig selbst Zertifikate ausstellen. Hier kann Certificate Transparancy helfen.

1.3 SSL Labs

Die Website SSL Labs hat sich darauf spezialisiert, Informationen und Werkzeuge, zum Thema SSL/TLS zur Verfügung zu stellen. Wer seine Website verschlüsselt anbieten möchte, egal mit welchem Webserver, sollte sich vorher das PDF SSL/TLS Deployment Best Practices durchlesen. Auf knapp 10 Seiten wird das Wissen vermittelt, welches zur Bereitstellung einer sicheren SSL/TLS Verbindung benötigt wird. Im Folgenden wird bei der Erklärung der Konfiguration, nochmal das Wichtigste zusammen gefasst. Wenn der Server fertig eingerichtet ist, kann mit dem SSL Server Test überprüft werden, ob alles funktioniert; dazu weiter unten mehr.

1.4 Zertifikate

Zertifikate sollten heutzutage einen ECDSA Schlüssel verwenden. ECDSA ist schneller als RSA bei gleicher oder besserer Sicherheit und wir von allen modernen Browsern unterstützt. Nicht alle Certificate authorities bieten schon ECDSA Zertifikate an, Let's Encrypt aber schon.

Mit Let’s Encrypt gibt es eine CA von Mozilla, der EFF, Akamai und anderen nennenswerten Organisationen, die kostenlos Zertifikate zur Verfügung stellt. Diese CA ist vollständig automatisiert, sodass Zertifikate in weniger als einer Minuten bereitstehen. Dazu gibt es eine Vielzahl von Clients die das Ausstellen automatisieren, die Zertifikate automatisch erneuern, und sogar den Webserver konfigurieren können. Die Zertifikate von Let’s Encrypt sind für drei Monate gültig.

2 Konfiguration

Genug der Einführung, jetzt kommen wir zur Konfiguration von NGINX. Die einzelnen Komponenten der Konfiguration, werden jeweils ausführlich erklärt.

2.1 HTTP Umleitung

Zuerst werden alle Anfragen auf Port 80 (HTTP) nach Port 443 (HTTPS) umgeleitet. Oft findet man, das Konfigurationen eine rewrite Regel nutzen, anstatt return 301. Das funktiniert zwar, geht aber besser.

server { listen 80; listen [::]:80; server_name example.com; return 301 https://$server_name$request_uri; }

Es wird für IPv4 listen 80 und für IPv6 listen [::]:80 auf Port 80 gelauscht. Der server_name gibt an, dass nur Anfragen nach example.com, von dieser Konfiguration, beantwortet werden. Dies wird benötigt, wenn man eine oder mehr Subdomains hat, die auf die selbe NGINX Instanz zeigen. return 301 ist die Umleitung auf https (Durch HTTP Status Code 301 - Moved Permanently), alle Argumente in der URL werden übergeben.

2.2 TLS und SPDY bzw. HTTP/2 aktivieren

SPDY ist das Protokol von Google das als Nachfolger von HTTP/1.1 konzipiert ist. Es ist außerdem die Grundlage für HTTP/2. SPDY steht ausschließlich für HTTPS Verbindungen zur Verfügung und benötigt keine Anpassung an der Website. SPDY beschleunigt die HTTPS Verbindung deutlich und wird von allen modernen Browsern unterstützt. NGINX unterstützt SPDY seit Version 1.3.15 in Version 2 und setzt OpenSSL 1.0.1 verraus. Seit NGINX 1.5.10 steht die aktuelle SPDY Version 3.1 bereit. Mit NGINX 1.9.5 ist das SPDY Modul durch das HTTP/2 Modul ersetzt worden. Browser die kein SDPY oder HTTP/2 sprechen, nutzen TLS über HTTP/1.1. Manchmal sieht man noch eine Kombination aus listen 443 und ssl on, dies ist veraltet und sollte nicht mehr verwendet werden.

server { # listen 443 ssl spdy; listen 443 ssl http2; # listen [::]:443 ssl spdy; listen [::]:443 ssl http2; server_name example.com; ssl_certificate /pfad/zum/zertifikat/example.com.crt; ssl_certificate_key /pfad/zum/privaten/schlüssel/private.key;

Es wird für IPv4 listen 443 ssl http2 und für IPv6 listen [::]:443 ssl http2 auf Port 443 gelauscht und TLS mit HTTP/1.1 bzw. HTTP/2 oder SPDY aktiviert. Mit ssl_certificate wird das zu benutzende Zertifikat ausgewählt. Das Zertifikat sollte alle Zwischenzertifikate beinhalten. Mit ssl_certificate_key wird der private Schlüssel ausgewählt. Er muss mindestens 2048 bit haben.

2.3 Protokolle

Alle modernen Browser sprechen TLS 1.2. TLS 1.2 ist die neuste Version und es sind keine Schwachstellen dafür bekannt. TLS 1.3 steht kurz vor der Fertigstellung und ich werde es in die Konfiguration übernehmen wenn es fertig ist.

ssl_protocols TLSv1.2;

Ein einfaches ssl_protocols reicht aus, um ausschließlich TLS Version 1.2 zu aktivieren.

2.4 Cipher

ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA; ssl_prefer_server_ciphers on;

Die Zeile ssl_ciphers wählt die zu benutzenden Cipher aus. Die Reihenfolge bestimmt welche zuerst versucht werden, bis eine Cipher gefunden wird, die beide Seiten sprechen. ssl_prefer_server_ciphers weißt NGINX an, zu ignorieren, welche Cipher der Browser bevorzugt, um eine möglichst starke/schnelle Cipher zu wählen. Um ECDHE zu nutzen wird mindesten OpenSSL 1.0.0 benötigt. Um CHACHA20 und POLY1305 zu nutzen wird mindesten OpenSSL 1.1.0 benötigt.

Beschreibung der einzelnen Buchstaben in den Cipher:

EC steht für Elliptic Curve Cryptography.
DHE steht für Diffie-Hellman-Schlüsselaustausch. Das E steht für Ephemeral (vergänglich) und gibt an das Forward Secrecy benutzt wird.
DSA steht für Digital Signature Algorithm. ECDSA wird zum signieren verwendet.
CHACHA20 ist eine moderne Stromverschlüsselung und wird als ein möglicher Nachfolger von AES gehandelt. Wikipedia.
POLY1305 ist ein moderner Message Authentication Code (MAC) und dient dazu die Integrität der Daten sicherzustellen. Wikipedia.
AES steht für Advanced Encryption Standard. AES ist eine Blockverschlüsselung, die zur tatsächlichen Verschlüsselung der Verbindung benutzt wird.
GCM ist der Galois/Counter Mode. GCM ist ein Mode für AES der die Daten verschlüsselt und gleichzeitig die Authentizität der verschlüsselten Daten gewährleistet. GCM gilt als schnell und effizient und im Gegensatz zu CBC, sind noch keine praktischen Schwachstellen bekannt.
CBC ist der Cipher Block Chaining Mode. CBC ist ein Mode für AES. Gegen CBC in Verbindung mit TLS 1.0 sind Angriffe bekannt, die aber sehr unwahrscheinlich sind.
SHA ist der Secure Hash Algorithm. SHA wird zur Integritätsprüfung der Daten benutzt.

Begründung für die Reihenfolge der Cipher

CHACHA20 mit POLY1305 und AES-GCM sind beides AEAD Cipher. Mit TLS 1.3 sind nur noch AEAD Cipher zugelassen. CHACHA20 mit POLY1305 ist auf Prozessoren die keine Hardware Beschleinigung für GCM haben deutlich schneller als GCM, darunter fallen im Moment die meisten mobilen Prozessoren für Smartphones. GCM ist CBC vorzuziehen, da für GCM noch keine Schwachstellen bekannt sind. Siehe dazu auch den Blogeintrag vom Google Online Secruity Team.

2.5 HTTP Strict Transport Security (HSTS)

HTTP Strict Transport Security (RFC 6797) ist eine Antwort des Servers (HTTP Header) an den Browser, wenn dieser eine verschlüsselte Verbindung herstellt. Der Server sagt dem Browser damit, dass der Browser in der nächsten Zeit (Länge wird in der Antwort angegeben, und bei jedem neuen Aufruf wieder auf das Maximum gesetzt), nur noch verschlüsselt mit dem Server kommunizieren soll. Jede unverschlüsselte Verbindung und jede Verbindung mit einem selbst unterschriebenen Zertifikat wird abgebrochen. Dies ist ein Schutz gegen MITM Angriffe, da diese jetzt ein gültiges Zertifikat brauchen um dem Benutzer eine falsche Seite unterzuschieben. Zusäzlich werden alle http:// Links die auf die eigene Domain zeigen in https:// Links umgewandelt, noch bevor der Server kontaktiert wird. Das Maximalalter max-age=31536000 wird in Sekunden angegeben und entpricht hier einem Jahr. Außerdem sollte man mit dem Parameter includeSubDomains angeben, dass alle Subdomains ebenfalls nur mit HTTPS aufzurufen sind.

Wie am Anfang erwähnt ist ein Besucher beim ersten Aufrufen der Website nicht durch HSTS geschützt, da er den Header noch nie empfangen hat. Um dieses Problem zu beheben kann man sich bei Google in eine Liste eintragen lassen, in der Websiten aufgeführt werden für die der HSTS Header schon bei Installation des Browsers enthalten ist. Die HSTS Preload Liste wird im Moment von Chrome, Firefox, Internet Explorer 11, Safari, Opera und Edge genutzt. Unter https://hstspreload.org/ kann man sich in die Liste eintragen lassen wenn man alle dort aufgeführten Vorraussetzungen erfüllt, wie zum Beispiel den preload Parameter.

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";

Achtung: Wenn man seine Seite wieder unverschlüsselt anbieten möchte, muss man auf hstspreload.org die Austragung beantragen, die allerdings mehrere Monate dauern kann.

2.6 Public Key Pinning für HTTP (HPKP)

Da HPKP bald aus Chrome wieder entfernt wird, da die Probleme den Nutzen übersteigen, habe ich mich entschieden den Abschnitt zu entfernen. Chrome ist der einzige Browser der dies, meines Wissens nach, bis jetzt angekündigt hat, ich gehe aber davon aus, dass die anderen Browser folgen werden.

Zum Weiterlesen:
Chrome: Intent To Deprecate And Remove: Public Key Pinning

2.7 OCSP Stapling

Das Online Certificate Status Protocol wird auf der Client Seite (Hier: Browser) benutzt, um die Gültigkeit von Zertifikaten zu überprüfen. Vor OCSP wurden Zertifikate mithilfe von Certificate Revocation List (CRL) überprüft. OCSP kann schneller über zurückgezogene Zertifikate informieren, hat aber auch zwei Nachteile: Es wird nämlich, bei jedem Aufruf eines Zertifikates, eine Anfrage an die CA gestellt, die das Zertifikat herrausgegeben hat. Daraus ergibt sich zum Einen, eine große Last auf den Servern der CAs und zum Anderen, ist es ein Problem für die Privatsphäre des Nutzers, da die CA über jeden verschlüsselten Seitenaufruf Bescheid weiß. Beide Probleme können mit OCSP Stapling vermieden werden.

Beim OCSP Stapling hohlt der Server sich regelmäßig (einige Stunden bis einige Tage), bei der CA eine Unterschrift die angibt, dass das Zertifikat nicht zurückgezogen wurde. Diese Unterschrift schickt der Server dann beim Verbindungsaufbau mit und der Browser muss nicht mehr selbst bei der CA nachfragen. OCSP Stapling benötigt eine NGINX Version von 1.3.7 oder höher.

ssl_stapling on; ssl_trusted_certificate /pfad/bundle.ca.pem; ssl_stapling_verify on;

Ein einfaches ssl_stapling on reicht schon damit es funktioniert. Die ssl_stapling_verify Zeile hilft dabei, dass dem Server keine manipulierte Unterschrift untergeschoben wird. Die Anfrage an den CA Server läuft nämlich über normales http und könnte auf dem Weg verändert werden. Mit der Verify Option prüft NGINX vorher, anhand des Zertifikats der CA, ob die Unterschrift echt ist. In der ssl_trusted_certificate Zeile ist die Datei anzugeben, die das Root Zertifikat und alle Zwischenzertifikate der CA enthält.

Probleme von OCSP

OCSP hört sich im ersten Moment so an als würde es alle Probleme dem dem Zurückziehen von Zertifikaten lösen, es gibt aber ein Problem was das Protokoll fast nutzlos macht. Wenn der Browser keine Verbindung zum OCSP Server de CA aufbauen kann, wird das Zertifikat als Gültig anerkannt (Soft-Fail). Der Grund warum man Zertifikate zurück zieht, ist der dass das Zertifikat inkl. Private Key in die Hände eines Angreifers gefallen ist. Damit dieser Angreifer das Zerifikat nun nutzen kann um einen Benutzer auf einer gefälschten Seite falsche Sicherheit vorzutäuschen, muss er als MITM den Benutzer auf seinen eigenen Server umleiten. Wenn der Angreifer aber die Möglichkeit hat den Benutzer umzuleiten kann er auch die Verbindung zum OCSP Server unterbinden. Adam Langley hat das auf seinem Blog schön zusammengefasst. Google Chrome nutzt OCSP aus diesem Gründen mittlerweile nicht mehr.

Warum ich trotzdem schreibe wie man OSCP Stapling macht ist folgender: Die meisten Browser prüfen noch über OCSP und mit OCSP Stapling kann man dann den Datenschutz seiner Besucher etwas besser gewähren. Es gibt außerdem die Möglichkeit, dass Zertifikate einen Flag enthalten können, dass dem Browser sagt das er vom Webserver eine OCSP Staple Antwort zu erwarten hat und wenn er diese nicht bekommt er die Verbindung nicht aufbauen darf (OCSP Must-Staple). Let’s Encrypt Zertifikate unterstützen dieses Flag und wird von manchen Clients bereits unterstützt.

2.8 Wiederverwendung von TLS Verbindungen (Session Resumption)

Damit nicht bei jedem Seitenaufruf wieder der komplette TLS Handshake durchgeführt werden muss, lassen sich bestimmte Parameter speichern und später wiederbenutzen. Dies erspart dem Server unnötige Rechenzeit. Achtung: Session Resumption macht in vielen Fällen Forward Secrecy zunichte da nun die temporären Schlüssel sehr lange auf Server Seite gespeichert werden können. Es gibt zwei Arten von Session Resumption: Session IDs und Session Tickets. Bei Session IDs speichert der Server die TLS Verbindungsdaten in einem Cache und liest sie wieder ein wenn der Client wiederkommt und bei Session Tickets verschlüsselt der Server die TLS Verbindungsdaten mit einem Schlüssel und schickt diese dem Client, dieser wiederum gibt dem Server dieses Ticket wenn er sich das nächste mal verbindet. Ein Angreifer kann, wenn er Zugriff auf den Server erhält, den Session Cache oder den Ticket Schlüssel auslesen und damit alle Verbindungen nachträglich entschlüsseln, etwas das Forward Secrecy eigentlich verhindern soll. Da NGINX keine Möglichkeit bietet den Session Cache regelmäßig zu löschen, bleibt nur die Option den Session Cache zu deaktivieren. Der Parameter ssl_session_timeout gibt nur an das die Sessions nach einer Zeit nicht mehr benutzt werden, sie befinden sich danach aber weiterhin auf der Festplatte.

ssl_session_timeout 10m; ssl_session_cache off; ssl_session_tickets on; ssl_session_ticket_key /pfad/nginx_ticketkey;

Bleibt also nur Session Tickets, aber auch hier lässt sich bei NGINX nicht einstellen das der Ticket Schlüssel regelmäßig gewechselt wird. Dies lässt sich aber mit einem Cronjob selbst bauen. Dazu gibt man NGINX an Session Tickets zu nutzen ssl_session_tickets on und das als Schlüssel eine von uns angelegte Datei zu verwenden ssl_session_ticket_key.

Als Ticket Key wird eine Datei mit 80 byte Zufall benötigt. Dies lässt sich mit folgendem Script einfach erledigen:

#!/bin/bash head -c 80 /dev/urandom > /pfad/nginx_ticketkey systemctl reload nginx

Dieses Script sollte nun mindestens einmal am Tag per Cronjob ausgeführt werden. 80 Byte Zufall werden erst ab NGINX 1.11.8 unterstützt, mit vorherigen Versionen nimm man hier 48 Byte.

2.9 Komplette Konfiguration

server { listen 80; listen [::]:80; server_name example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name example.com; ssl_certificate /pfad/zum/zertifikat/example.com.crt; ssl_certificate_key /pfad/zum/privaten/schlüssel/private.key; ssl_protocols TLSv1.2; ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA; ssl_prefer_server_ciphers on; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; ssl_stapling on; ssl_trusted_certificate /pfad/bundle.ca.pem; ssl_stapling_verify on; ssl_session_timeout 10m; ssl_session_cache off; ssl_session_tickets on; ssl_session_ticket_key /pfad/nginx_ticketkey; [Der Rest eurer NGINX Konfiguration]

3 SSL Server Test

Um zu überprüfen, ob auch alles funktioniert, empfiehlt sich der SSL Server Test von SSL Labs. Am Beispiel meines eigenen Servers, werde ich kurz die wichtigsten Ergebnisse zusammenfassen.

3.1 Zusammenfassung

summary

Die Kriterien für die Bewertung findet man im SSL Server Rating Guide

3.2 Cipher

cipher

Hier kann man gut sehen, ob der Server auch alle Cipher anbieten kann, und ob er dies auch in der richtigen Reihenfolge tut.

3.3 Handshake Simulationen

handshake

Hier kann man sehen, welcher Browser, welche Cipher auswählt. Ein Klick auf die Browsernamen zeigt an, welche Protokolle, Cipher und Erweiterungen die Browser unterstützen.

3.4 Details

details

Außerdem sieht man, das alle konfigurierten Optionen (HSTS, OCSP Stapling, Forward Secrecy, Session Resumption), funktionieren.

Changelog

04.01.2018 - HPKP Abschnitt entfernt, DHE entfernt, TLS 1.0 und 1.1 entfernt, RSA entfernt, Rechtschreibfehler korrigiert, Danke Marcel
13.07.2017 - Rechtschreibfehler korrigiert, Danke Lennart :-)
03.02.2017 - Tote Links bereinigt, Kleine Textverbesserungen
30.12.2015 - HTTP/2 hinzugefügt, Problematik mit Forward Secrecy und Session Resumption angesprochen
10.01.2015 - HPKP hinzugefügt, HSTS um Subdomains und preload erweitert, SPDY hinzugefügt, Probleme mit OCSP erläutert
10.08.2014 - Auf eine dynamische Cipherliste umgestellt
13.03.2014 - Unterstützung für Android 2.3 hinzugefügt
09.03.2014 - Firefox unterstützt nun auch AES GCM / Bilder Skalierung gefixt
22.01.2014 - Kapitel 3 an den neuen SSL Server Rating Guide angepasst
31.12.2013 - StartSSL Anleitung hinzugefügt, GCM Beschreibung verdeutlicht, 4096 bit Key benutzt
04.12.2013 - Formatierung vereinheitlicht
27.11.2013 - Erste Veröffentlichung