Über Zertifikatsketten und Ablaufdaten

Heute haben ein paar Dinge ganz schön geknarzt, die mit Let's Encrypt, OpenSSL und CentOS zu tun haben und deren Aufarbeitung vielleicht auch im Detail für euch interessant ist. Direkt vorweg: Die Erreichbarkeit deiner bei uns gehosteten Dinge per HTTPS war davon *nicht* beeinträchtigt.

Über Zertifikatsketten und Ablaufdaten

Heute haben ein paar Dinge ganz schön geknarzt, die mit Let's Encrypt, OpenSSL und CentOS zu tun haben und deren Aufarbeitung vielleicht auch im Detail für euch interessant ist. Direkt vorweg: Die Erreichbarkeit deiner bei uns gehosteten Dinge per HTTPS war davon nicht beeinträchtigt.

Problematisch war allerdings die Erreichbarkeit anderer HTTPS-URLs von unseren Systemen aus, wenn jene Webserver Zertifikate von Let's Encrypt eingesetzt haben. Hier haben sich dann Warnungen ergeben, die darauf hindeuteten, dass ein Root-Zertifikat abgelaufen sei. Was jetzt erstmal danach klingt, dass wir da etwas nicht rechtzeitig aktualisiert hätten, war tatsächlich vielmehr darauf zurückzuführen, dass wir bei uns noch etwas vorliegen hatten, was eigentlich weg musste - nämlich ein altes und damit eigentlich unnützes Root-Zertifikat. Aber der Reihe nach.

Wie sieht so eine Zertifikatskette aus?

Let's-Encrypt-Zertifikate sind in den meisten Fällen so strukturiert: Das Zertifikat selbst wird von einem Zwischenzertifikat signiert ("R3"), jenes Zwischenzertifikat ist von einem Root-Zertifikat signiert ("ISRG Root X1"), das alle zeitgemäßen Systeme bereits als gültiges Root-Zertifikat akzeptieren (auch unsere), und jenes Root-Zertifikat ist aus historischen Gründen von einem weiteren Root-Zertifikat signiert ("DST Root CA X3"), das früher von Let's Encrypt zum Signieren von Zwischenzertifikaten eingesetzt wurde. Und eben jenes "DST Root CA X3" ist gestern abgelaufen, und zwar im Grunde ersatzlos, denn das "ISRG Root X1" ist inzwischen weit genug verbreitet, so dass "DST Root CA X3" nicht mehr nötig ist.

Im Mai 2021 hat Let's Encrypt den Schritt vollzogen, sein "R3"-Zertifikat nicht mehr von "DST Root CA X3" signieren zu lassen, sondern von "ISRG Root X1". Da die von Let's Encrypt ausgestellten Zertifikate immer nur 90 Tage gültig sind, sind alle vor dieser Umstellung ausgestellten Zertifikate inzwischen ohnehin abgelaufen.

Von "ISRG Root X1" gibt es nun aber zwei Varianten: Eine davon ist selbst-signiert, das heißt, sie wird nur dann akzeptiert, wenn das Root-Zertifikat als vertrauenswürdig auf dem betreffenden System schon vorliegt. Die andere Variante davon ist von "DST Root CA X3" signiert. Sie ist vor allem für ältere Systeme da, die zum Beispiel keine Updates mehr bekommen und damit auch nicht von ihrem Herausgeber das neuere "ISRG Root X1" bekommen. Da die älteren Systeme aber in der Regel das "DST Root CA X3" haben, können sie auf diesem Umweg nun "ISRG Root X1" aber dennoch akzeptieren. So weit, so gut. Speziell bei älteren Android-Devices wird das "DST Root CA X3" sogar über sein eigentliches Ablaufdatum hinaus akzeptiert, so dass hier eine hohe Kompatibilität mit Let's-Encrypt-Zertifikaten selbst auf alten Geräten besteht.

Ein abgelaufenes, aber eh unnötiges Zertifikat - ja und?

Nun ist "DST Root CA X3" also gestern abgelaufen, und eigentlich würden wir da gerne sagen: Na und; keine große Sache - wir haben ja schon seit einer halben Ewigkeit das neuere "ISRG Root X1" in unserem Zertifikatsspeicher. So einfach ist es dann tatsächlich aber leider doch nicht.

Jeder HTTPS-Webserver gibt beim TLS-Handshake alle zum Validieren nötigen Zertifikate zurück, also im Fall von Let's-Encrypt-Zertifikaten zusätzlich noch das "R3" und dann in der Regel auch noch das "ISRG Root X1", das von "DST Root CA X3" signiert ist (nicht das selbst-signierte). Auf der Seite Production Chain Changes von Let's Encrypt lässt sich das gut nachlesen: Website-Betreiber haben die Wahl, ob sie die kürzere, modernere Chain ausgeben, die aber mit älteren Geräten weniger kompatibel ist, oder eben die kompatiblere, die noch eine Cross-Signatur von "DST Root CA X3" enthält. Standardmäßig wird letztere verwendet - auch wir verwenden diese längere, aber kompatiblere Chain. Wie gesagt: Insbesondere ältere Android-Geräte können dieser Chain sogar auch noch nach dem Ablaufdatum des Root-Zertifikats folgen. Es scheint also erstmal keinen Grund zu geben, warum die kürzere Chain verwendet werden sollte: Mehr Kompatibilität, mehr gut.

Was OpenSSL 1.0.2 damit zu tun hat

Und damit kommen wir zur Rolle von OpenSSL in diesem Szenario, denn jetzt kann man sich natürlich die Frage stellen: Ich als HTTPS-Client bekomme ein Zertifikat, das ist signiert von "R3", das bekomme ich direkt mit, und das ist signiert von "ISRG Root X1", und das... ha, das habe ich doch in meinem lokalen Zertifikatsspeicher als vertrauenswürdig gespeichert! Super, dann bin ich ja fertig.

Dieses Verhalten ist auch das, was OpenSSL ab Version 1.1.0 an den Tag legt: Es beendet die Validierung, sobald es beim Verfolgen der Chain auf das erste Zertifikat trifft, dem es vertraut.

Das von uns eingesetzte CentOS 7 (mit Support bis 2024) und andere (zum Beispiel Ubuntu 16.04 mit Support bis 2026) benutzen aber noch OpenSSL 1.0.2, das von CentOS auch weiterhin mit Sicherheitsupdates versorgt wird. Es unterscheidet sich aber bei unserem aktuellen Thema an einem wichtigen Punkt: Es hört nämlich nicht beim ersten vertrauenswürdigen Zertifikat mit der Validierung auf, sondern es macht noch weiter. Dabei gelangt es dann auch zum eigentlich ja gar nicht mehr nötigen "DST Root CA X3", und das ist nun abgelaufen - und damit validiert OpenSSL jenes Zertifikat dann nicht mehr. Es gibt ein Flag, mit dem sich dieses Verhalten umstellen lässt (openssl verify ... -trusted_first), aber das ist eben erst ab OpenSSL 1.1.0 defaultmäßig eingeschaltet:

When X509_V_FLAG_TRUSTED_FIRST is set, construction of the certificate chain in X509_verify_cert(3) will search the trust store for issuer certificates before searching the provided untrusted certificates. Local issuer certificates are often more likely to satisfy local security requirements and lead to a locally trusted root. This is especially important when some certificates in the trust store have explicit trust settings (see "TRUST SETTINGS" in x509(1)). As of OpenSSL 1.1.0 this option is on by default.

Das OpenSSL-Team hat offenbar vorhergesehen, welche Probleme mit Let's Encrypt und OpenSSL 1.0.2 entstehen, wenn man das alte Root-Zertifikat weiterhin im Zertifikatsspeicher hat, und hat zwei Wochen vor Ablauf einen ausführlichen Blogpost dazu veröffentlicht.

Red Hat handelt, erst in RHEL, dann in CentOS

Lange Rede, kurzer Sinn: Problem war nicht, dass uns ein Zertifikat fehlte. Problem war vielmehr, dass wir noch ein Zertifikat hatten, nämlich das "DST Root CA X3".

Einen Tag nach jenem Blogpost, am 14. September, hat Red Hat das Zertifikat in Red Hat Enterprise Linux entfernt:

$ rpm -q --changelog ca-certificates | head -4
* Di Sep 14 2021 Bob Relyea <rrelyea@redhat.com> - 2021.2.50-72
- Fix expired certificate.
-    Removing:
-     # Certificate "DST Root CA X3"

Bis aus den Paketen von Red Hat Enterprise Linux dann auch Pakete für CentOS entstanden sind, ist dann auch nochmal über eine Woche vergangen:

$ rpm -qi ca-certificates | grep ^Build
Build Date  : Do 23 Sep 2021 10:14:56 CEST
Build Host  : x86-01.bsys.centos.org

Dieser Schritt der Entfernung des abgelaufenen Zertifikats beschränkt sich für uns insofern effektiv auf ein yum upgrade, um das Paket ca-certificates zu aktualisieren. Das machen wir bei uns natürlich so oder so regelmäßig, um die euch bereitgestellte Software aktuell zu halten. Wir machen es aber normalerweise nicht täglich, sondern in der Regel einmal pro Woche, etwas in Etappen verteilt, um bei unerwarteten Problemen schnell zurückrollen zu können, bevor zu großes Drama entstanden ist - man weiß ja nie. Wenn es um kritische Sicherheitslücken geht, halten wir uns natürlich nicht mehr an dieses Schema, sondern installieren jene Updates sofort; in der Regel überbrücken wir die Zeit bis dahin auch mit Workarounds, sofern es welche gibt, wie zum Beispiel bei der sudo-Schwachstelle im Januar.

Unser letzter wöchentlicher Update-Batch lief... an eben jenem 23. September, unmittelbar bevor das aktualisierte Paket auf den Mirror-Servern aufgetaucht ist. Mit der nächsten Update-Runde wäre es insofern mit drin gewesen - aber da kam uns der Ablauf des alten "DST Root CA X3" leider zuvor. Das tut uns sehr leid - auch wenn wir security-relevante Kanäle so gut wie möglich verfolgen und dann unser Bestes geben, darauf zu reagieren, so ist das anstehende Problem mit dem abgelaufenen, unnötigen Root-Zertifikat schlicht an uns vorbeigegangen. Dass es dadurch kurzzeitig nicht mehr möglich war, von unseren Servern aus andere Let's-Encrypt-Websites abzurufen, ist schlicht: Mist. Und wenn es euch tröstet: Das hat uns auch intern an vereinzelten Stellen etwas in den Hintern gebissen und dabei auch noch ein anderes Thema im Zusammenhang mit Zertifikatsspeichern aufgeworfen, zu dem es aber noch einen separaten Blogpost geben wird.

Fazit

Die gute Nachricht ist: Innerhalb weniger als 24h konnte das Problem überall gefixt werden, und die Umstellung vom früher externen Root-Zertifikat auf das nun von Let's Encrypt selbst herausgegebene und bereits weit verbreitete eigene Root-Zertifikat ist damit abgeschlossen und sollte sich in dieser Größenordnung auch nicht mehr wiederholen.