Testen von HTTPS-Clients mit openssl zur Simulation eines Servers
In diesem Artikel wird beschrieben, wie Sie Ihren HTTPS-Client oder -Browser mit openssl testen. Um Ihren HTTPS-Client zu testen, benötigen Sie einen HTTPS-Server oder einen Webserver, z. B. IIS, apache, nginx oder openssl. Sie benötigen auch einige Testfälle. Es gibt drei häufige Fehlermodi in SSL/TLS:
- Der Client stellt die Verbindung her, wenn dies nicht der Fall sein sollte.
- Die Verbindung schlägt fehl, wenn sie erfolgreich sein sollte, und
- Die Verbindung wird ordnungsgemäß hergestellt, aber die Daten werden bei der Übertragung beschädigt.
- Es gibt einen 4. Fehlermodus: Es kann sein, dass die Daten nicht sicher übertragen werden. Dieser Fehlermodus liegt außerhalb des Rahmens dieses Artikels.
Um sicherzustellen, dass alle Probleme, die beim Testen aufgedeckt werden, auf Probleme in Ihrem HTTPS-Client zurückzuführen sind, möchten wir einen "bekanntermaßen guten" HTTPS-Server verwenden. Wir wollen auch einen Server, der "pedantisch" oder "unversöhnlich" ist. OpenSSL erfüllt genau diese Anforderungen.
In diesem Artikel werde ich beschreiben, wie Sie den Befehl openssl s_server verwenden, um ein HTTPS-Server zu sein. Es gibt viele Konfigurationselemente, die genau richtig sein müssen, daher werde ich Ihnen nicht nur zeigen, wie Sie es richtig machen, sondern ich werde Ihnen auch mitteilen, was schief gelaufen ist und wie ich sie diagnostiziert und behoben habe.
Ein "Client" ist ein Computer oder ein Computerprogramm, das eine Verbindung zu einem "Server" herstellt. Ein "Server" ist ein Computerprogramm, das darauf wartet, dass eine Verbindung von einem "Client" eintrifft. Für HTTP und HTTPS gibt es "Browser" und "Clients". Browser sind für die Interaktion mit Menschen konzipiert und verfügen in der Regel über grafische Benutzeroberflächen. Alle Browser sind HTTP/HTTPS-Clients.
Es gibt jedoch HTTP/HTTPS-Clients, die keine Browser sind. Diese Clients sind für den Einsatz als automatisierte Systeme konzipiert. Der kluge Serverdesigner stellt sicher, dass sein System effektiv mit HTTPS-Clients, die Browser sind, und HTTPS-Clients, die keine Browser sind, verwendet werden kann.
In diesem Tutorial lernen Sie:
- So wählen Sie einen guten HTTPS-Client oder Browser aus
- So verwenden Sie openssl als HTTPS-Server
- Verwenden eines HTTPS-Servers zum Testen eines HTTPS-Clients
Verwendete Softwareanforderungen und Konventionen
So testen Sie Ihren HTTPS-Client Schritt für Schritt Anleitung
Ich werde die Adjektive "rechtschaffen" verwenden, um anzuzeigen, dass ein Test etwas richtig gemacht hat, und "irrtümlich", um anzuzeigen, dass ein Test etwas falsch gemacht hat. Wenn ein Test fehlschlägt, obwohl er sollte, dann ist das ein rechtschaffener Fehler. Wenn ein Test bestanden wird, obwohl er nicht bestanden werden sollte, dann ist das ein fehlerhafter Durchgang.
Ich wollte einen HTTPS-Client verwenden, den ich nach Belieben kaputt machen und reparieren kann, und ich habe ihn gefunden: den http-Befehl (er ist in github als httpie). Wenn ich die Option -verify=no verwende, dann ist der Client kaputt: Er wird fälschlicherweise Tests bestehen. Ich war nicht in der Lage, einen fehlerhaften Fehler zu erstellen, und das ist eine gute Sache, da es bedeutet, dass etwas nicht stimmt, wenn der Client ausfällt.
Das Herzstück des SSL/TLS-Protokolls (sie haben den Namen geändert und sonst wenig) sind zwei Dateien, ein "Zertifikat" (oder kurz "cert") und ein geheimer "Schlüssel". Überall im Protokoll fragt ein Ende der Verbindung das andere Ende nach einem Zertifikat. Am ersten Ende werden einige der Informationen im Zertifikat verwendet, um ein mathematisches Rätsel zu erstellen, das nur von jemandem beantwortet werden kann, der den geheimen Schlüssel hat. Der geheime Schlüssel verlässt nie seine Maschine: Die Lösung des Problems bedeutet, dass das nahe Ende weiß, dass das andere Ende den Schlüssel hat, aber nicht, was der Schlüssel ist.
Der Befehl openssl ist im Wesentlichen eine Befehlszeilenschnittstelle zu libssl. Er enthält einen Rohserver, der mit dem Unterbefehl s_server aufgerufen wird. OpenSSL benötigt ein öffentliches Zertifikat/ein privates Schlüsselpaar. In meinem Fall hatte ich sie bereits für meinen Produktions-Webserver. Ich habe sie von let's encrypt bekommen, kostenlos.
Als Proof-of-Concept, dass der Server ordnungsgemäß funktioniert, kopierte ich das Zertifikat und den Schlüssel auf meine Entwicklungsmaschine und startete den openssl HTTPS-Server.
Auf der Serverseite:
openssl s_server -status_verbose -HTTP -cert fullchain.pem -key privkey.pem
Using default temp DH parameters
ACCEPT
Mein erster Versuch ist gescheitert!
http --verify=yes jeffs-desktop:4433/index.html
http: error: ConnectionError: (Connection aborted., RemoteDisconnected(Remote end closed connection without response)) while doing GET request to URL: http://jeffs-desktop:4433/index.html
Erste Hypothese: Der Schlüssel und das Zertifikat stimmen nicht überein. Ich habe das überprüft:
openssl x509 -noout -modulus -in fullchain.pemls | openssl md5
(stdin)= b9dbd040d9a0c3b5d3d50af46bc87784
openssl rsa -noout -modulus -in privkey.pem | openssl md5
(stdin)= b9dbd040d9a0c3b5d3d50af46bc87784
Sie passen zusammen. Warum scheitert das also? Da mein Zertifikat für linuxconfig.dns.net ist, verwende ich jedoch jeffs-desktop als Hostnamen.
jeffs@jeffs-desktop:~/documents$ openssl x509 -text -noout -in fullchain.pem | fgrep CN
Issuer: C = US, O = Let’s Encrypt, CN = R3
Subject: CN = linuxconfig.ddns.net
Das ist ein gerechter Fehler: Der Server war falsch konfiguriert und mein Client hat es erkannt. Hätte ich die Option the
-verify=no verwendet, hätte ich einen defekten Client und das Problem nicht erkannt. Beachten Sie, dass alle übertragenen Daten immer noch sicher gegen einen Lauscher sind. Ich kann dieses Problem beheben, indem ich meine /etc/hosts-Datei mit meinen eigenen IPv4- und IPv6-Adressen ändere.
192.168.1.149 linuxconfig.ddns.net
2601:602:8500:b65:155a:7b81:65c:21fa linuxconfig.ddns.net
(Übrigens, die Leichtigkeit, mit der Sie eine IP-Adresse fälschen können, ist in erster Linie eine der Motivationen von SSL/TLS).
Versuchen Sie es erneut. Auf der Serverseite:
openssl s_server -status_verbose -HTTP -cert fullchain.pem -key privkey.pem
Using default temp DH parameters
ACCEPT
Auf der Client-Seite:
http --verify=yes https://linuxconfig.ddns.net:4433/index.html
On the server side, I get the error message:
140101997737280:error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca:../ssl/record/rec_layer_s3.c:1543:SSL alert number 48
On the client side, I get the error message:
http: error: SSLError: HTTPSConnectionPool(host='linuxconfig.ddns.net', port=4433): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1131)'))) while doing GET request to URL: https://linuxconfig.ddns.net:4433/
Diese Fehlermeldung ist CERTIFICATE_VERIFY_FAILED ein wichtiger Hinweis: Sie bedeutet, dass die Zertifizierungsstelle (Certificate Authority, CA) des Zertifikats nicht verifiziert werden konnte. Da der Client das Zertifikat nicht überprüfen konnte, konnte die Verbindung nicht hergestellt werden. Dies ist ein weiteres gerechtes Versagen.
Das Zertifikat selbst könnte gefälscht sein – und der Kunde hat keine Möglichkeit, es zu erfahren. Das Zertifikat verweist jedoch auf eine Zertifizierungsstelle, und die Zertifizierungsstelle weiß entweder, dass das Zertifikat gültig ist, oder sie lehnt die Überprüfung ab. Woher wissen wir, dass die Zertifizierungsstelle vertrauenswürdig ist?
Die Zertifizierungsstelle selbst verfügt über ein Zertifikat, ein Zwischenzertifikat, und dieses Zertifikat verweist auf eine andere Zertifizierungsstelle. Schließlich erreicht diese Zertifikatkette ein Stammzertifikat. Ein Root-Zertifikat signiert sich selbst und ist daher per Definition vertrauenswürdig. In diesem Fall ist etwas mit dieser Zertifikatskette, dieser Vertrauenskette, schief gelaufen.
openssl s_client -showcerts -connect linuxconfig.ddns.net:4433
CONNECTED(00000003)
depth=0 CN = linuxconfigan.ddns.net
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = linuxconfigan.ddns.net
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:CN = linuxconfigan.ddns.net
i:C = US, O = Let's Encrypt, CN = R3
-----BEGIN CERTIFICATE-----
Ich weiß, dass mein Produktionsserver ordnungsgemäß funktioniert. So soll die Kette aussehen (beachten Sie die Portnummer 443, nicht 4433):
openssl s_client -showcerts -connect linuxconfig.ddns.net:443
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = linuxconfig.ddns.net
verify return:1
---
Certificate chain
0 s:CN = linuxconfig.ddns.net
i:C = US, O = Let's Encrypt, CN = R3
-----BEGIN CERTIFICATE-----
MIIFYjCCBEqgAwIBAgISA0MTOSmISSsIyRls8O/2XpAaMA0GCSqGSIb3DQEBCwUA
...
-----END CERTIFICATE-----
1 s:C = US, O = Let's Encrypt, CN = R3
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
-----BEGIN CERTIFICATE----
...
-----END CERTIFICATE-----
2 s:C = US, O = Internet Security Research Group, CN = ISRG Root X1
i:O = Digital Signature Trust Co., CN = DST Root CA X3
-----BEGIN CERTIFICATE-----
…
Von hier aus gibt es zwei Möglichkeiten, wie Sie vorgehen können: Ich kann die Zertifikatsüberprüfung deaktivieren oder das Zertifikat von Let's Encrypt zur Liste der bekannten Zertifizierungsstellen hinzufügen. Das Deaktivieren der Verifizierung ist schnell und sicher. Das Hinzufügen der Zertifizierungsstelle zur Liste der bekannten Zertifizierungsstellen ist eher geheimnisvoll. Lassen Sie uns beides tun. Auf der Serverseite habe ich noch nichts angerührt. Auf der Client-Seite deaktiviere ich die Verifizierung und erhalte Folgendes:
http –verify=no https://linuxconfig.ddns.net:4433/index.html
http: error: ConnectionError: ('Connection aborted.', BadStatusLine('\n')) while doing GET request to URL: https://linuxconfig.ddns.net:4433/index.html
echo $?
1
Diese Fehlermeldung teilt mir mit, dass ein Verstoß gegen das HTTP-Protokoll (nicht HTTPS) aufgetreten ist. Der Server hat die erste Zeile der Datei index.html bereitgestellt, obwohl er einen HTTP-Return-Header-Block hätte zurückgeben sollen. Dies ist ein serverseitiger Fehler, der alle HTTP-Clients beschädigen würde. Ein sorgfältiger Blick in die Dokumentation sagt mir, dass ich die Option -WWW (nicht -www) mit openssl anstelle der Option -HTTP verwenden soll. Das mache ich:
openssl s_server -status_verbose -WWW -cert fullchain.pem -key privkey.pem und es funktioniert ordnungsgemäß, mit der Einschränkung, dass ich die Zertifikatsvalidierung noch nicht zum Laufen gebracht habe.
http -verify=no https://linuxconfig.ddns.net:4433/helloworld.c
HTTP/1.0 200 ok
Content-type: text/plain
#include
int main (int argc, char *argv[]) {
printf("Hello, world\n\n");
}
Da ich -verify=no verwendet habe, ist dies tatsächlich ein fehlerhafter Durchlauf.
Um zu überprüfen, ob meine Zertifikatskette gültig ist, kann ich den Befehl openssl verify verwenden:
openssl verify -purpose sslserver fullchain.pem
CN = linuxconfig.ddns.net
error 20 at 0 depth lookup: unable to get local issuer certificate
error cert.pem: verification failed
Die schnelle Lösung bestand darin, den Befehl openssl s_server auf meinem Produktionswebserver mit Produktionskonfigurationsdateien auszuprobieren. Dies ist (einigermaßen) sicher, da der openssl-Server auf Port 4433 ausgeführt wird, während mein Produktionsserver auf Port 443 ausgeführt wird.
openssl s_server -status_verbose -WWW \
-cert /etc/letsencrypt/live/linuxconfig.ddns.net/fullchain.pem \
-key /etc/letsencrypt/live/linuxconfig.ddns.net/privkey.pem -accept 4433
Hmm. Nginx arbeitet wie ein Champion. OpenSSL ist es nicht. Aus diesem Grund ist openssl eine bessere Testumgebung als nginx: Wenn die Konfiguration von nginx falsch ist, wird es versuchen, sich durchzuwursteln. Wenn die Konfiguration von openssl falsch ist, werden Sie darauf hingewiesen. Die Konfiguration von openSSL wird in /etc/ssl/openssl.cnf gespeichert.
Es heißt, dass sich die CA-Zertifikate in /etc/ssl/certs befinden. Das Root-Zertifikat der Internet Services Research Group (ISRG) befindet sich dort. Aber das Zwischenzertifikat von Let's Encrypt ist es nicht. Das macht in gewisser Weise Sinn: Let's encrypt hat einen wunderbaren Certbot, der alles über nginx wusste, als ich es laufen ließ, aber ich habe certbot nicht mit openssl laufen lassen, also war das Zertifikat von let's encrypt nicht in /etc/ssl/certs/ . Ich habe das Zertifikat von let's encrypt mit dem:
wget https://letsencrypt.org/certs/lets-encrypt-r3.pem
Der obige Befehl, kopierte die Datei lets_encrypt_r3.pem nach /etc/ssl/certs/, führte das c_rehash Programm aus, und voila:
openssl verify -CApath /etc/ssl/certs/ \ /etc/letsencrypt/live/linuxconfig.ddns.net/fullchain.pem
/etc/letsencrypt/live/linuxconfig.ddns.net/fullchain.pem: OK
Das ist schön, aber der Test ist, kann ich helloworld.c sehen?
http --verify=yes https://linuxconfig.ddns.net:4433/helloworld.c
HTTP/1.0 200 ok
Content-type: text/plain
#include
int main (int argc, char *argv[]) {
printf("Hello, world\n\n");
}
Ja. Ich habe jetzt überprüft, dass mein funktionierender HTTPS-Client rechtmäßig besteht und zu Recht fehlschlägt, zumindest für die Testfälle, mit denen ich gearbeitet habe. Es gibt noch einige andere Dinge, die bei SSL/TLS schief gehen, wie z.B. Certificate Revocation Lists (CRLs), aber ich hoffe, Sie haben eine gute Idee.
Als Nächstes möchte ich überprüfen, ob Dateien, die zwischen dem openssl HTTPS-Server und meinem HTTPS-Client gesendet werden, nicht einmal ein Bit beschädigt werden. Ich kann nicht überprüfen, ob jede Datei ohne Fehler übertragen wird, aber was ich tun kann, ist, eine große Binärdatei zu übertragen, zu überprüfen, ob sie korrekt übertragen wurde, und dann daraus abzuleiten, dass große Dateien nicht beschädigt werden.
Ich habe den Befehl ls -lorS verwendet, um eine große Datei zu finden, ihre SHA256-Summe berechnet, sie mit openssl als Server übertragen, die empfangene Datei gespeichert und die SHA256-Summe für diese Datei berechnet. Die Summen von SHA 256 sollten übereinstimmen.
Auf der Serverseite:
ls -lorS | tail -1
-rw-rw-r-- 1 jeffs 121329853 May 23 2020 CybersecurityEssentials.pdf
sha256sum CybersecurityEssentials.pdf
49a49c8e525a3d6830fce1c1ee0bfce2d3dd4b000eeff5925b074802e62024e0 CybersecurityEssentials.pdf
Auf der Client-Seite:
http --verify=no https://linuxconfig.ddns.net:4433/CybersecurityEssentials.pdf -o /tmp/CybersecurityEssentials.pdf
sha256sum /tmp/CybersecurityEssentials.pdf
49a49c8e525a3d6830fce1c1ee0bfce2d3dd4b000eeff5925b074802e62024e0 /tmp/CybersecurityEssentials.pdf
Diese PDF-Datei ist 121 MB groß, groß genug für meine Zwecke. Die SHA256-Summen stimmen überein, so dass die Datei korrekt übertragen wurde.
Schlussfolgerung
In diesem Artikel habe ich die häufigsten Fehlermodi des HTTPS-Protokolls beschrieben. Ich habe einige Kriterien für die Auswahl eines HTTPS-Servers verwendet, der zum Testen eines HTTPS-Clients verwendet werden soll, und openssl ausgewählt. Ich habe einen einfach zu bedienenden HTTPS-Client ausgewählt. Ich habe einige gängige Fehlermodi gezeigt und festgestellt, dass der Client diese Fehler erkannt hat.
Der schwierige Teil bestand darin, openssl richtig zu konfigurieren, also habe ich gezeigt, was schief gehen kann und wie man es beheben kann. Schließlich habe ich gezeigt, dass ich mit openssl als Server und meinem HTTPS-Client eine Datei ohne Datenbeschädigung übertragen kann.

