SSL-Verifizierung mit PHP schlägt fehlt (Fehler 14090086)
14.10.2017
Wenn dich PHP mit dieser Fehlermeldung begrüßt, dann liegt es wohl daran, dass beim Abruf einer SSL-Ressource die Identität nicht verifiziert werden konnte. Und das ist auch gut so, denn der Sinn von SSL ist ja das Herstellen einer gesicherten Verbindung.
So sieht die Fehlermeldung bei Verwendung von file_get_contents(); aus. Aber auch andere Funktionen, mit denen man auf externe Ressourcen verweisen kann, werfen diesen Fehler, wie z.B. imagecreatefrompng();.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Warning: file_get_contents(): SSL operation failed with code 1. OpenSSL Error messages: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed in /website/script.php on line 50 Warning: file_get_contents(): Failed to enable crypto in /website/script.php on line 50 Warning: file_get_contents(https://www.nickyreinert.de/foobar.json): failed to open stream: operation failed in /website/script.php on line 50 |
Die Ursache ist simpel: PHP kennt den Aussteller des Zertifikates für https://www.nickyreinert.de nicht und verweigert aus Sicherheitsgründen den Aufbau einer Verbindung. Wir müssen also entweder dafür sorgen, dass PHP dem Aussteller vertraut oder festlegen, dass PHP die Zertifikate gar nicht erst prüft.
Verifizierung des SSL Hosts unterdrücken
Das ist die quick’n’dirty Lösung. Zumindest file_get_contents(); kann mit Parametern gefüttert werden, die die Verifizierung der Ressource unterdrücken:
1 2 3 4 5 6 7 8 9 |
$stream = stream_context_create(array( $stream = stream_context_create(array( "ssl"=>array( "verify_peer"=> false, "verify_peer_name"=> false, ), 'http' => array( 'timeout' => 30 ) ) ); $result = file_get_contents($url, 0, $stream); |
Wer auf eine eigene Ressource zurückgreift, z.B. im lokalen Netz oder zu Testzwecken, kann damit leben. Aus Sicherheitsgründen ist die Lösung allerdings nicht zu empfehlen. Außerdem funktioniert das nicht, wenn man Funktionen nutzt, bei denen diese Optionen nicht gesetzt werden können, wie z.B. imagecreatefromjpeg();
Die Zertifikatskette manuell einrichten
Man kommt also nicht darum, die saubere Lösung zu nutzen. Dazu muss man wissen, dass es in den allermeisten Fällen nicht um das eine Zertifikat geht, sondern um die Zertifikatskette. Diese beinhaltet auch die Zertifikate der Stellen, die dem infragekommenden Server https://www.nickyreinert.de das Zertifikat ausgestellt haben. Wenn PHP diese Stellen nicht kennt, geht es auch davon aus, dass die von dort ausgestellten Zertifikate nicht gültig sind.
Um die Zertifikatskette zu erhalten, kannst du einen Service wie https://whatsmychaincert.com/ nutzen. Dieser liefert dir eine Datei mit der kompletten Zertifikatskette.
PHP die Zertifikatskette mitteilen
Die Datei mit der Zertifikateskette gehört nun an einen Ort, den PHP erreichen kann. Dann musst du PHP noch mitteilen, dass es auch diese Zertifikatskette berücksichtigen soll. Auch das passiert über den Parameter, den ich oben schon angesprochen habe. Doch diesmal erlauben wir PHP, den SSL-Host zu verfizieren und verweisen auf die Zertifikatskette, die wir oben erstellt haben:
1 2 3 4 5 6 7 8 |
$stream = stream_context_create(array( "ssl"=>array( "cafile" => "www.nickyreinert.de.pem", "verify_peer"=> true, "verify_peer_name"=> true, ),'http' => array( 'timeout' => $this->configUrlTimeOut ) ) ; $config = file_get_contents($url, 0, $stream); |
Geschafft. PHP sollte nun, zur Laufzeit, den SSL-Host überprüfen und dabei auf die Zertifikate zurückgreifen, die die Authentizität einwandfrei bestätigen.
Man kann den Verweis auch an anderer Stelle definieren. Die PHP-Funktion openssl_get_cert_locations(); teilt uns mit, wo PHP nach gültigen Zertifikaten sucht:
1 2 3 4 5 6 7 8 9 10 11 |
Array ( [default_cert_file] => /Applications/XAMPP/xamppfiles/share/openssl/cert.pem [default_cert_file_env] => SSL_CERT_FILE [default_cert_dir] => /Applications/XAMPP/xamppfiles/share/openssl/certs [default_cert_dir_env] => SSL_CERT_DIR [default_private_dir] => /Applications/XAMPP/xamppfiles/share/openssl/private [default_default_cert_area] => /Applications/XAMPP/xamppfiles/share/openssl [ini_cafile] => /Applications/XAMPP/xamppfiles/share/curl/curl-ca-bundle.crt [ini_capath] => ) |
Hier fällt z.B. die Datei /Applications/XAMPP/xamppfiles/share/curl/curl-ca-bundle.crt auf. Diese wird in der php.ini mit dem Parameter
1 |
openssl.cafile=/Applications/XAMPP/xamppfiles/share/curl/curl-ca-bundle.crt |
gesetzt. Auch in dieser Datei liegen eine Menge von Root- bzw. Intermediate-Zertifikaten. Mitunter macht es mehr Sinn, diese Einstellung in der php.ini zu nutzen, damit auch curl() darauf zurückgreifen kann.
Wenn du PHP-FPM benutzt, ist die Config-Datei etwas anders aufgebaut:
1 |
php_admin_value[openssl.cafile] = /Applications/XAMPP/xamppfiles/share/curl/curl-ca-bundle.crt |
Zusammenfassung
Die SSL-Fehlermeldung hat ihren Sinn. PHP ist nicht in der Lage, die Authentizität des Servers zu überprüfen. Der korrekte Weg, das Problem zu beheben, ist das Zertifikat der entsprechenden Seite herunterzuladen, die Intermediate-Zertifikate und das Root-Zertifikat zu besorgen und alle Zertifikate im PEM-Format in eine Text-Datei zu packen.
Danach kannst du entweder auf die URL zugreifen und über den Stream-Kontext auf diese Datei mit der Zertifikats-Kette verweisen, oder du legst in den globalen PHP-Einstellungen fest, dass PHP diese Datei beim nächsten Mal berücksichtigen soll.