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:
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"' |
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);
} |
<?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);
}