HTTP Public Key Pinning / HPKP -> Erklärung und Einrichtung

Ein Problem „by-Design“ ist, dass jede CA für jeden Webserver Zertifikate ausstellen kann. Kaufe ich mir bei der CA foo für die Domain www.example.org ein Zertifikat, bin ich nicht davor geschützt, dass die CA bar kein Zertifikat für die gleiche Domain ausstellt. Alles schon vorgekommen… Da Webbrowser von Haus aus hunderten Zertifizierungsstellen vertrauen ist das auch kein Problem theoretischer Natur mehr, und mit Superfish oder Privdog sogar brandaktuell.

Es gibt keine 100%tige Lösung für das Problem mit den CAs, aber eine, die einen deutlichen Gewinn an Sicherheit bringt, und das für jede HTTPS Verbindung. Das ganze heißt HTTP Public Key Pinning und ist einfach erklärt: Der Webserver sendet bei seiner Antwort in einem HTTP Header mindestens zwei Informationen: a) Pins von zwei Schlüsseln und b) die Information wie lange diese gültig sein sollen. Der Webbrowser merkt sich diese Informationen und verweigert die Kontaktaufname, wenn die Pin des gesendeten Zertifikates nicht mit einer Pin aus dem HTTP Header übereinstimmt. Eine Pin ist übrigens der base64 encodete SHA256-Hash Fingerprint eines public Keys eines Zertifikats [Update: Danke Puhh für die Korrektur].
Die empfohlene Gültigkeit der Informationen liegt bei 60 Tagen, Das erklärt auch warum es zwei Pins sein müssen. Verliert man nämlich einen Key, oder dieser wird wegen einer Sicherheitslücke unsicher (Gruß an Heartbleed), schließt man potentielle Leser im schlimmsten Fall 60 Tage lang von seiner Webseite aus. Um hier das Risiko zu verringern müssen immer zwei Pins angegeben werden wovon eine die eines Backup Keys ist.

In der Praxis sieht das wie folgt aus:

  • Zwei Keys generieren:
    openssl genrsa -out www.example.org.hpkp1.key 4096
    openssl genrsa -out www.example.org.hpkp2.key 4096
  • Mit dem ersten Key einen CSR erstellen:
    openssl req -new -sha256 -key www.example.org.hpkp1.key -out www.example.org.csr
  • Zertifikat mit dem CSR besorgen
  • Zertifikat im Apache vhost einbinden
  • HPKP Header generieren. Dafür kann zum Beispiel das Skript hpkp-gen genommen werden.
  • HPKP Header in Apache einrichten. Dafür muss zuerst das Modul Headers aktiviert sein:
    a2enmod headers

    und danach der generierte HPKP Header in den vhost eingetragen werden:

      ## Header rules
      ## as per http://httpd.apache.org/docs/2.2/mod/mod_headers.html#header
      Header always set Public-Key-Pins: 'max-age=5184000; pin-sha256="+sCGKoPvhK0bw4OcPAnWL7QYsM5wMe/mn1t8VYqY9mM="; pin-sha256="bumevWtKeyHRNs7ZXbyqVVVcbifEL8iDjAzPyQ60tBE="'
  • Monitoring anpassen. Dieses sollte meckern sobald das Zertifikat in weniger als 60 Tagen abläuft. Dann sollte man mit dem Backup Key einen neuen CSR erzeugen, ein neues Zertifikat hinterlegen, einen neuen Backup-Key erstellen und auch den HPKP Header in Apache anpassen und die Pins entsprechend durchrotieren.
  • SSLLabs Test machen. Dort sollte ganz unten im letzten Kasten und darin im vorletzten Abschnitt „Public Key Pinning (HPKP)“ in grün und mit yes stehen. HSTS+HPKP

HPKP bringt darüber hinaus noch etwas spannendes mit. Wenn der Webbrowser eine Verbindung ablehnt, weil der Pin des vom Webserver gesendeten Zertifikates nicht mit einer im HTTP Header angegebenen oder im Webbrowser gespeicherten Pin übereinstimmt, kann dieser eine bestimmte Stelle informieren. Diese Stelle kann im HPKP Header optional mit angegeben werden. Dafür wird dort noch eine report-uri=“http://www.example.org/hpkpReportUrl“ hinzugefügt. Der Header sieht dann zum Beispiel so aus:

Public-Key-Pins: 'max-age=5184000; pin-sha256="+sCGKoPvhK0bw4OcPAnWL7QYsM5wMe/mn1t8VYqY9mM="; pin-sha256="bumevWtKeyHRNs7ZXbyqVVVcbifEL8iDjAzPyQ60tBE="; report-uri="http://www.pregos.info/hpkp.php"'

Die Info wird im JSON Format übertragen und als POST gesendet.

Update
Hier das hpkp.php Skript:

<?php
 
/* script that can be used as a report-uri for HPKP.
   Will just send a mail to $hpkp_to with some info and json data
   Written by Hanno Böck, https://hboeck.de/
   License: CC0 / Public Domain
*/
 
$hpkp_to = "[insert email]";
$hpkp_log = "../hpkp.log";
 
$hpkp_info = "Host: ".$_SERVER['HTTP_HOST']."n";
$hpkp_info .= "Request URI: ".$_SERVER['REQUEST_URI']."n";
if ( array_key_exists('HTTP_REFERER', $_SERVER) ) {
$hpkp_info .= "Referrer: ".$_SERVER['HTTP_REFERER']."n";
}
$hpkp_info .= "Remote IP: ".$_SERVER['REMOTE_ADDR']."n";
$hpkp_info .= "User agent: ".$_SERVER['HTTP_USER_AGENT']."n";
$hpkp_info .= "CSP JSON POST data:nn";
$hpkp_info .= str_replace( ",", ",n", file_get_contents('php://input') );
$hpkp_info .= "nServer Info:nn";
$hpkp_info .= print_r($_SERVER, true);
 
 
mail($hpkp_to, "HPKP Warning from ".$_SERVER['HTTP_HOST'], $hpkp_info);
echo "ok";
 
if ($hpkp_log != "") {
	$f = fopen($hpkp_log, "a");
	fwrite($f, $hpkp_info);
	fclose($f);
}

Kostenlose HTTPS-Zertifikate von WoSign / China

Da Let’s encrypt noch nicht so weit ist, ich aber trotzdem immer mal wieder HTTPS Zertifikate haben möchte die von den bekannten Webbrowsern ohne Rückfrage akzeptiert werden hab ich mir schon mal das ein oder andere günstige gekauft. Den Großteil verwalte ich aber noch mit CAcert.

Für die Zertifikate zu bezahlen war mir aber schon immer ein Dorn im Auge. Schon früh hatte ich mir StartSSL angeschaut aber das war mir alles deutlich zu umständlich. Über Twitter wurde ich dann auf einen neuen chinesischen Anbieter hingewiesen:

Lange Rede kurzer Sinn. Das ganze ist sehr simpel und funktioniert super:

  • Key + CSR erzeugen:
    openssl req -new -nodes -newkey rsa:4096 -sha256 -keyout host.example.net.key -out host.example.net.csr
  • URL öffnen: https://buy.wosign.com/free/
  • Formular ausfüllen, Domain bestätigen, Zertifikat beantragen, auf Email warten
  • Nach Erhalt der ZIP Datei mit Zertifikat und Intermediates die Bundle-Datei öffnen und den letzten Eintrag entfernen
  • Im Webserver einbinden und SSL Server Test machen um zu prüfen ob alles korrekt ist

iodine – IPv4 über DNS tunneln

Ich habe schon öfter über Dinge geschrieben die unterwegs nützlich sind um Zugriff aufs Internet zu bekommen. Ein Tool habe ich bereits seit langem installiert, jedoch jetzt kürzlich das erste Mal praktisch genutzt: iodine.

Zugiodine ermöglicht es einem IPv4 Traffic über DNS zu tunneln. Das ist immer dann praktisch wenn der Internetzugriff per Firewall gesperrt, DNS aber erlaubt ist.

Die Hürden für ein iodine Setup sind erst einmal groß. Um den Server zu installieren bedarf es nicht nur eines entsprechenden Hosts auf dem das Paket installiert werden kann, sondern man muss auch in seinem DNS Einstellungen machen können. Bei mir funktioniert das folgende Setup:

Auf dem gewünschten Server habe ich das iodine Paket aus den Paketquellen installiert:

aptitude install iodine

Anschließend habe ich in der Konfigurationsdatei /etc/default/iodine die folgenden Einstellungen hinterlegt:

START_IODINED="true"
IODINED_ARGS="10.9.0.123 iodine.example.net -c"
IODINED_PASSWORD="MYSECRETPASSWORD"

Nun zum DNS. Dort benötigt man zwei Einträge. Angenommen der Host auf dem iodine installiert ist hat die IP 1.2.3.4, dann bekommt dieser zuerst einen ganz normalen IN A Eintrag. host.example.net zeigt auf 1.2.3.4. Das  ist nichts besonderes sondern so einen Eintrag setzt man ja eigentlich immer.

IN A

Der zweite Eintrag ist nun der abweichende. Wie in den IODINED_ARGS angegeben benötigen wir den DNS-Eintrag iodine.example.net. Dieser bekommt aber keinen IN A Eintrag, sondern als Nameserver den host.example.net eingetragen.

IN NS

Wenn der Server eingerichtet ist, kann man sich an den Client machen. Ich nutze den Network-Manager und dafür gibt es ein praktisches Plugin. Auf dem Client:

aptitude install network-manager-iodine-gnome

Anschließend kann man den nm-connection-editor starten und dort eine neue Verbindung vom Typ VPN – Iodine VPN Tunnel  hinzufügen. Als Verbindungsname wählt man das was man will, als Toplevel Domain trägt man iodine.example.net ein, und das auf dem Server unter IODINED_PASSWORD gesetzte Passwort speichert man auch optional ab. Fertig.

Schnell ist die Anbindung nicht, das ist technisch bedingt, aber sie ist stabil, selbst wenn man von dem Hotspot alle X Minuten per DHCP eine neue IP bekommt bricht der Tunnel nicht ab. Klasse!