30 Mai 2023

Gepinnte Zertifikate mit openssl und cURL

Kommunikation sollte verschlüsselt stattfinden. Ein Standard heutzutage ist TLS. Das hat durchaus Schwächen, jedoch funktioniert es in der Praxis. TLS hat leider das Designproblem, dass es hier zentrale Stellen gibt, die Zertifikate für beliebige Domains ausstellen können. Diese Stellen sind sogenannte CAs. Viele davon werden bei den gängigen Browsern und Betriebssystemen mitgeliefert. Das ist an sich auch eine gute Idee, so kann der Computer verifizieren, ob jemand vertrauenswürdiges bestätigt, dass der Server tatsächlich der ist, zu dem man sich verbinden möchte. Ob man jedoch allen mitgelieferten CAs von Amerika über Europa bis nach Ostasien vertrauen kann, liegt sicherlich im Auge des Betrachters.

In manchen Fällen hat man für sein (eigenes) Programm oder Skript einen eigenen Server und es ist wichtig, dass hier kein gefälschtes Zertifikat genutzt wird. Oder man hat keinen Platz für verschiedene CAs. Hier kann es sinnvoll sein, nicht ein Zertifikat sondern direkt den öffentlichen Schlüssel des Servers abzuspeichern. Als Beispiel nehme ich hier die IP-Abfrage von diesem Projekt.

Zunächst braucht man ein Zertifikat für den Server. Dies kann beispielsweise mit Lets-Encrypt oder einer anderen CA erstellt werden. Falls ihr schon einen Server mit Zertifikat habt, könnt ihr diesen Schritt überpringen. Auch ein sogenanntes self-signed Zertifikat bietet sich hier an; so einem Zertifikat wird normalerweise nicht vertraut, jedoch stellen wir hier die Sicherheit über ein festes, bekanntes Zertfikat statt über CAs sicher, so dass auch ein Zertifikat ohne öffentliche CA hier funktioniert.

Um eine Anfrage mit einem bekannten Key zu machen, muss zunächst der Key bekannt sein. Dieser lässt sich über zwei Wege herausfinden: Falls es euer eigener Server ist und ihr die Zertifikatsdatei habt, könnt ihr den Key mit folgendem Befehl extrahieren, wobei test.pem die Zertifikatstatei ist.

$ openssl x509 -pubkey -noout -in test.pem > test.pub

Falls ihr das Zertifikat als Datei nicht habt, könnt ihr es direkt vom Server anfragen. Das geht beispielsweise mit folgendem Befehl. Dabei solltet ihr bei der Ausgabe auf der Konsole schauen, ob der Schlüssel auch verifiziert werden kann, falls der Server ein öffentlich bekanntes Zertifikat hat.

$ openssl s_client -connect test.ipoac.de:443 < /dev/null | openssl x509 -pubkey -noout > test.pub

In beiden Fällen liegt der Schlüssel anschießend unter test.pub.

Um eine normale HTTPS-Anfrage an den Server zu schicken und dabei mit dem eben extrahierten Key die Verbindung zu verifizieren, könnt ihr folgenden Befehl nutzen:

$ curl --pinnedpubkey test.pub https://test.ipoac.de/iponly.php

Standardmäßig prüft cURL trotz gepinntem Schlüssel, ob das Zertifikat des Servers valide ist. Falls ihr ein self-signed Zertifikat verwendet oder ein Zertifikat, das nicht von einer bekannten CA signiert wurde, wird cURL daher die Verbindung ablehnen. Hierfür könnt ihr den Parameter insecure verwenden. In diesem Fall ist der Vorgang durch den gepinnten Schlüssel nicht unsicher, da hierüber sichergestellt wird, dass der Server auch der ist, mit dem ihr euch verbinden möchtet. Der ganze Befehl sieht daher folgendermaßen aus:

$ curl --insecure --pinnedpubkey test.pub https://test.ipoac.de/iponly.php