Wie bekomme ich ein Self-Signed-Zertifikat auf ein iPhone, einen Androiden und am Besten auch noch auf ein Windows und ein MacOS? Und wie muss das verflixte Ding aussehen, dass es alle anstandslos fressen?
Eine Tragikomödie in vier Akten.
Am Anfang stand ein Websocket
Meine Kollegen können das Thema bestimmt nicht mehr hören: Ich habe an unseren letzten Maydays und in meiner Freizeit in meinem PalmOS-Emulator eine Brücke über Websocket aus dem Browser zu einem Python-Server gebaut, der dann Sockets aufmacht und PalmOS zu Netzwerk-Konnektivität verhilft. Bingo.
Cloudpilot
Cloudpilot ist ein mit C++, WebAssembly und Angular/Ionic gebauter Emulator, der die in den späten 90ern und frühen 2000ern populären PalmOS-PDAs emuliert. Der Emulator basiert auf dem seinerzeit offiziellen PalmOS-Emulator POSE und läuft als Progressive-Web-App auf iOS, Android sowie in Desktop-Browsern.
Letze Woche war denn endlich der letzte Baustein fertig, und ich konnte im Browser (Eudora für die alten Hasen) in PalmOS im Browser das Web browsen. Fein. Bier. Party. Deploy nach github.io, um das ganze im Build zu testen. Und dann ..? „Cannot connect to proxy“!
Party vorbei, Bissspuren im Laptop.
Der Grund hätte einem Webentwickler ja auch mal eher einfallen können: github.io ist HTTPS, und mein Websocket-Server ist HTTP, und damit ist das ganze Konstrukt Mixed Content, was alle Browser mit Selbstachtung heutzutage verbieten. Und nachdem es schwierig ist, ein gültiges Zertifikat für 192.168.178.20 zu bekommen, beginnt hier die Reise zu einem Self-Signed-Cert, welches mit iOS, Android, MacOS und Windows funktioniert.
Mixed Content
Mixed Content ist per Definition jeder Inhalt auf einer per HTTPS ausgelieferten Seite, der nicht per SSL gesichert ist, also per HTTP ausgeliefert wird. Für den Benutzer (der anhand der URL-Zeile glaubt, dass die Verbindung sicher ist) stellt dies ein Sicherheitsrisiko dar, weshalb alle modernen Browser Mixed Content verbieten.
Die Optionen
Ausnahmeregeln
Grundsätzlich kann man natürlich immer bei der Verwendung eines Self-Signed-Certs im Browser die Gruselwarnungen wegklicken, 5x laut „ja, ich will“ schreien und damit eine Ausnahmeregel einrichten.
Allerdings handelt es sich hier nicht um einen Pageload, sondern um einen Websocket-Request im Hintergrund, und so einfach geht das nicht. Man muss mindestens vorher in einem separaten Tab auf den Websocket-Endpunkt gehen, dort den ja-ich-will-Tanz aufführen und dann hoffen, dass das jetzt auch in dem anderen Tab für den Websocket-Request gilt.
Gutes UX sieht anders aus, und der Erfolg ist browserabhängig. Bei manchen funktioniert das, bei manchen verfällt die Ausnahme nach einiger Zeit, und unter iOS geht das anscheinend gar nicht – zumindest nicht, wenn die eigentliche Seite eine Progressive-Web-App auf dem Homescreen ist.
Das Zertifikat in den Zertifikatsspeicher importieren
Die saubere Alternative: man kann das Zertifikat in den Zertifikatsspeicher des Betriebssystems importieren und als entsprechend vertrauenswürdig markieren. Damit stellt sich dem Browser die Frage nach der Gültigkeit gar nicht, da das Zertifikat ja als „gültig“ markiert ist.
Allerdings: Das Prozedere dazu ist systemabhängig, und jedes System hat anscheinend andere Erwartungshaltungen daran, wie ein gültiges ungültiges Zertifikat auszusehen hat, damit ihm potentiell vertraut wird.
Dazu kommt eine Sicherheitsproblematik: Ein einmal importiertes und als vertrauenswürdig markiertes Zertifikat kann von einem Angreifer potentiell auch in einem anderen Kontext eingesetzt werden, z. B. um als Man-in-the-Middle SSL-Verbindungen aufzubrechen. Es gilt also, das Zertifikat möglichst so zu konfigurieren, das Missbrauch schwierig wird.
iOS
Import
Für den Import muss man das Zertifikat (es scheint so ziemlich jedes Format zu funktionieren) irgendwie auf das Gerät kopieren. iCloud funktioniert, vermutlich auch Mail. Wenn man das Zertifikat danach öffnet, wird es als zu installierendes Profil angelegt. Die Reise geht jetzt in die Einstellungen zu „Allgemein → Profile“; dort kann man das Zertifikat dann installieren. Danach geht die Fahrt weiter zu „Allgemein → Info → Zertifikatsvertrauenseinstellungen“ (sic), dort muss man das Zertifikat dann noch als „Trusted“ markieren.
Anforderungen an das Zertifikat
Ein Zertifikat ist zunächst schnell mit dem MacOS-Keyring oder mit OpenSSL direkt erstellt. Der Import ist im Netz gut dokumentiert (siehe oben), aber danach wird des Gesicht lang: es funktioniert … nicht – in den „Zertifikatsvertrauenseinstellungen“ taucht das Zertifikat nicht auf.
Grund ist, das iOS anscheinend erwartet, dass das Zertifikat die Extension basicConstraits=critical, CA:TRUE
gesetzt hat, also als Certificate Authority markiert ist. Ich finde das ein wenig gruselig, da ich somit einem Angreifer ermögliche, weitere Zertifkate mit diesem Cert zu signieren.
x509-Extensions
Extensions sind standardisierte Erweiterungen des x509-Standards, mit denen zusätzliche Informationen an Zertifikaten gespeichert werden können. Dadurch kann die Verwendung des Zertifikats eingeschränkt und Missbrauch erschwert werden.
Die basicConstraints-Extension gibt an, ob mit des Zertifikat als eine Certificate Authority (CA) verwendet werden kann, also weitere Zertifikate signieren darf.
Allerdings kann man das mitigieren, indem man auch noch die Extension keyUsage
als critical setzt und dabei keyCertSign
weglässt, also z. B. keyUsage=critical, serverAuth
. Das Fehlen von keyCertSign
macht eine Signatur mit diesem Zertifikat ungültig.
keyUsage
Mit der Extension keyUsage wird die Verwendung des im Zertifikat enthaltenen Schlüssels eingeschränkt. Die für diesem Post relevanten Werte sind
- serverAuth: Serverseitige Authentifikation (im Rahmen von SSL)
- keyCertSign: Signatur von weiteren Zertifikaten
Durch das Deklarieren einer Extension als critical wird das Zertifikat ungültig, falls der Client die Extension nicht versteht oder berücksichtigt.
Android
Import
Für den Import muss man das Zertifikat im DER-Format auf das Gerät kopieren. In den Einstellungen kann man sich dann unter „Sicherheit und Datenschutz“ über mehrere Ebenen zu „Verschlüsselung und Anmeldedaten“ und schliesslich zu „Vom Speicher installieren“ vorarbeiten. Der genaue Tipppfad hängt sicher vom Hersteller des vorliegenden Android-Specimens ab, aber dort kann man das Zertifikat dann für „Apps und VPN“ importieren. Das war’s.
Anforderungen an das Zertifikat
Mein erster Versuch hat natürlich nicht funkioniert. Grund ist die Extension extendedKeyUsage
, die ich gesetzt habe. Dort muss „digitalSignature“ drinstehen, sonst geht’s nicht (den anderen Systemen ist das wurst). Ich vermute, es funktioniert auch, wenn man die Extension weglässt, aber ich will das Zertifikat ja so weit wie möglich einschränken, wenn ich es schon installiere (und es eine auch noch eine CA ist).
extendedKeyUsage
Mit der Extension extendedKeyUsage wird die zulässige Verwendung des Schlüssels noch weiter konkretisiert. Konzeptionell beantwortet „keyUsage“ die Frage nach dem „was“, während „extendedKeyUsage“ das „wozu“ beantwortet. Die für diesen Post relevanten Werte sind
- keyEncipherment: Austausch des Verbindungsschlüssels im Rahmen vom SSL
- keyAgreement: Aushandeln des Verbindungsschlüssels ohne Austausch bei Perfect-Forward-Security-Algorithmen im Rahmen von SSL
- digitalSignature: Digitale Signatur
- cRLSign: Signatur des eigenen Revocation-Certificates, mit dem ein (kompromittiertes) Zertifikat ungültig gemacht werden kann
Durch das Deklarieren einer Extension als critical wird das Zertifikat ungültig, falls der Client die Extension nicht versteht oder berücksichtigt.
MacOS
Import
Der Import ist einfach: Doppelklick auf das Cert im Finder, und damit ist das Teil im Keystore. Im Keyring muss man jetzt das Cert doppelklicken, „Vertrauen“ ausklappen und dort für SSL auf „immer vertrauen“ stellen. Fertig.
Anforderungen an das Zertifikat
Tatsächlich scheint MacOS nicht sehr wählerisch zu sein; jedes meiner Zertifikate hat dort anstandslos funktioniert. Ob das gut oder schlecht ist, liegt wohl im Auge des Betrachters ¯\_(ツ)_/¯
Windows
Import
Für den Import muss man das Zertifikat doppelklicken. Danach beginnt ein lustiger Wizard, in dem man als „Zertifikatsspeicher“ unbedingt manuell „Vertrauenswürdige Stammzertifizierungsstellen“ wählen muss. Der Donaudampfschifffahrtskapitän lässt grüßen. Fertig.
Wenn man das Zertifikat später wieder loswerden will, dann muss man z. B. auf „Ausführen“ gehen und dort „certmgr.msc“ eintippen. Oder einen der anderen Wege aus dem Labyrinth wählen, die im Netz zu finden sind.
Anforderungen an das Zertifikat
Windows ist nicht wählerisch. Allerdings muss das Zertifikat korrekterweise als x509 v3 markiert sein, wenn Extensions aktiv sind. Ist ja auch konsequent, wenn Extensions erst mit v3 eingeführt wurden 🤣 Zumindest MacOS und iOS interessiert das einen feuchten Kehricht.
Firefox
Firefox? Firefox! Der verwendet nämlich normalerweise nicht den Zertifikatsspeicher des Systems, sondern seinen eigenen. Und offensichtlich finde nicht nur ich den Zwang zu CA:TRUE
doof, denn Firefox lehnt das Zertifikat nach Import aus genau diesem Grund beim Aufbau der Verbindung ab 🥳
Naja, gibt nicht alles im Leben. Muss man im Firefox halt doch eine Ausnahme setzen. Funktioniert dort aber auch problemlos, man kann die Dinger sogar in den Einstellungen verwalten.
Das goldene Zertifikat …
… das bisher überall funktioniert (und meines Erachtens nach halbwegs „sicher“ ist):
- v509 v3
- 365 Tage Gültigkeit
- RSA, 4096bit, sha256
- basicConstraints = critical, CA:TRUE, pathlen=0
- keyUsage = critical, serverAuth
- extendedKeyUsage = critical, keyEncipherment, keyAgreement, cRLSign, digitalSignature
- subjectAltName nach Bedarf
subjectAltName
Mit der Extension subjectAltName können zusätzlich zum CommonName (CN) weitere Namen angegeben werden, unter denen siich der Server mit diesem Zertifikat ausweissen kann. SANs können sowohl Hostnamen als auch IPs sein.
Tatsächlich werden die SANs von vielen Browsern heute als mindestens genauso wichtig angesehen wie der CN. Sprich: Zertifikate ohne SANs können abgelehnt werden, und manche Browser erwarten sogar, dass der CN nochmals in der Liste der SANs auftauch.
Der entgültige Python-Code zur Generierung des Zertifikats in Cloudpilot sieht somit so aus.
Photo by Liam Truong on Unsplash
Schreibe einen Kommentar