Der Konnektor
Die KocoBox ist ein Telematik Konnektor der Compugroup Medical, von denen sich laut Aussagen des Herstellers gut 60.000 Geräte vor Ort bei Kunden befinden. Es existieren seit Einführung im Jahre 2015 zwei Versionen, die sich in der Hardware, jedoch nicht nennenswert in der Software unterscheiden. Mit der KocoBox verbinden sich Leistungserbringer zur Telematik Infrastruktur (TI) des Gesundheitssystems. Zu den Leistungserbringern gehören Ärzte, Apotheken, Krankenkassen und seit Mitte 2025 auch arztverwandte Heilberufe.
Die Kosten für das Gerät betrugen gut 2.000 - 3.000 € inkl. Installation. Softwareaktualisierungen mit neuen Funktionen des digitalisierten Gesundheitssystems wurden regelmäßig ausgeliefert und mit einigen hundert Euro gesondert in Rechnung gestellt. Auch der Betrieb kostete noch einmal einen 2 - 3 stelligen Betrag im Monat. Man munkelte, dass das recht teuer für eine VPN Box sei, obgleich der Vergleich sicherlich hinkt: Der Betrieb der dahinterliegenden Services war und ist sicherlich etwas aufwändiger als bei einer einfachen VPN Verbindung.
An den Konnektor angebundene Geräte (Arbeitsplätze, Kartenterminals, Clientsysteme) werden durch das so genannte Infomodell in eine rechtliche Stellung zueinander gebracht werden. Sind Geräte in diesem Infomodell miteinander verknüpft, dann – und nur dann – dürfen sie miteinander interagieren, z.B. darf dann auf einem Arbeitsplatz mit einem Clientsystem eine Karte aus einem Kartenterminal eingelesen werden. Während der Arbeitsplatz die PC Hardware bezeichnet, bezeichnet der Infomodelleintrag "ClientSystem" die darauf installierte Primärsystemsoftware des Leistungserbringers, also das Arztinformationssystem, die Apothekensoftware, etc. Zu dem Infomodelleintrag des Clientsystems kann ein TLS-Zertifikat generiert werden, welches in der Primärsystemsoftware hinterlegt wird und mit welchem der Datenverkehr zwischen Software und Konnektor Ende-zu-Ende verschlüsselt wird.
Das standardmäßige Absichern dieser Datenkommunikation mittels TLS-Zertifikat wurde erst einige Zeit nach Einführung des Konnektors etabliert. Zum Leidwesen der Leistungserbringer war die Laufzeit dieser Zertifikate auf ein Jahr begrenzt – je nach Sichtweise war das Primärziel des jährlichen Wechsels entweder die Erhöhung der Sicherheit oder die der Quartalszahlen der Dienstleister.
Im Jahre 2022 wurde dann die Laufzeit neu ausgestellter Zertifikate auf die Restlaufzeit des jeweiligen Konnektors angehoben. Bei Neugeräten beträgt die Zeit 4 - 5 Jahre, bei Bestandsgeräten zwar deutlich weniger, aber meist immer noch deutlich länger als ein Jahr. Auch wenn der Hauptgrund für die erneute Erstellung eines Zertifikates nach der Ersteinrichtung somit im Laufe des Jahres 2022 weggefallen ist, so gab es dennoch weitere Gründe, die dazu führten, ein neues Clientsystem-Zertifikat zu generieren: Der Wechsel des Primärsystems, das Hinzufügen eines zusätzlichen Dienstes mit einer eigenen Clientsystem ID und eigenem Zertifikat, der Verlust des ursprünglichen privaten TLS-Zertifikates nach Migration der Arbeitsplatzhardware, der Wechsel des TLS-Zertifikate-Typs von RSA auf ECC, der Tausch des Konnektors und der damit verbundenen Neueinrichtung eines Neuen, um einige davon zu nennen.
Der Bug
Der Bug besteht streng genommen zu Beginn an. Es ließen sich bestimmte Funktionen im Konnektor nur mit dem Firefox bedienen, nicht mit anderen Browsern wie Chrome oder Edge. Betroffen waren alle Funktionen, bei denen am Ende etwas aus dem Konnektor herauspurzelt, also ein Download gestartet wird: Log Dateien herunterladen, Backup erstellen, aber auch die Generierung von Clientsystem-Zertifikaten. Zu Beginn der Einführung im Jahr 2015 war der Firefox weit verbreitet. Jedoch verlor der Firefox in den letzten Jahren immer mehr Marktanteile, die Ursache des Problems wurde jedoch einfach nicht behoben. Man musste einen Firefox installieren, um z.B. Clientsystem Zertifikate zu generieren. Das Thema wurde also 10 Jahre [sic!] verschleppt. Bis zum 26. Juni 2024.
An diesem Tag erschien der Firefox in der Version 127.0.2. Mit diesem konnte auch der Firefox keinen Download mehr anstoßen, und somit entfiel mehr oder weniger generell die Möglichkeit, weiterhin Zertifikate zu generieren.
Foren und die Hotline füllten sich mit Beschwerden, aber eine Lösung war nicht in Sicht. "Ihr müsst einen alten Firefox herunterladen", war der einzige Rat.
Damit dieser keine Sicherheitslücken aufweist, sollte man nicht irgend einen alten Firefox herunterladen, sondern die letzte ESR Version, die über Langzeitunterstützung verfügt. Blöd nur: Schon wenige Tage später im Juli erschien der Firefox in Version 128, und diese Version war eine ESR Version, mit dem es natürlich auch nicht mehr klappte. Der allgemeine Tipp war nun: "Lade dir die alte ESR Version des Firefox herunter, die einzige noch unterstützte Firefox Version 115, mit der der Download weiterhin klappt."
Es war natürlich nur eine Frage der Zeit, bis auch die aus dem Support fiel. Ganz davon ab, dass es in vielen Einrichtungen aus gutem Grund Richtlinien gab, die die Installation wahlloser Altsoftware verbietet, unabhängig davon, ob nun noch Softwareaktualisierungen hierfür erschienen oder eben nicht.
Die Kenntnisnahme
An mein Ohr drang das Thema irgendwann im September 2024. Zu Beginn nahm ich das Thema nicht ernst, weil unser eigenes Produkt Managed TI nicht betroffen war. Für unsere Kunden wurde das Zertifikat mit dem von mir geschriebenen "SelfService" heruntergeladen – das funktionierte anstandslos. Bis das Thema dann auch bei uns durchschlug: Der von uns eingesetzte Jumphost auf Debian Basis machte einen Sprung von der Firefox ESR 115 auf Version 128. Zwar war der SelfService weiterhin nicht betroffen, aber wenn der L2-Support manuell ein Zertifikat auf der Konnektor-Weboberfläche generieren wollte, schlug nun auch dies fehl. Und unter Linux konnte auch niemand mal eben einen alten Firefox installieren. Also wurde das Thema eskaliert, wir betrieben immerhin den Jumphost. Wir behalfen uns damit, das Installationspaket des Firefox auf den alten Stand zurückzurollen.
Auf die Frage an die zuständige Abteilung, wann mit einer Lösung zu rechnen war, fiel mir die Kinnlade herab: In einem Jahr. Das sei schließlich zertifizierte Software auf den Konnektoren, da könne man nicht einfach so mal eben einen Patch herausgeben. Dieser müsse erneut die Zertifizierung durchlaufen. Ähh... geil. Danke für die schnelle und kompetente Hilfe.
Um die prozentuale Anzahl an den 60.000 Konnektoren zu schätzen, bei denen die Notwendigkeit bestand, nach der Ersteinrichtung erneut ein TLS-Zertifikat zu generieren, die somit von dem Bug betroffen waren, fällt schwer. Wenn ich raten müsste: 10%, also 6.000 Geräte. Und am Ende besteht der Bug bis heute, stand Juni 2025. Die fehlerkorrigierte Version steht zwar in den Startlöchern, veröffentlicht ist sie jedoch noch nicht.
Die Analyse
Tatsächlich gestaltete sich die Analyse relativ einfach, hatte ich doch für die Programmierung des SelfService nahezu alle Funktionen der JSON Schnittstelle des Konnektors durch Reverse Engineering erarbeitet. Es stellte sich heraus: Die Weboberfläche sendet beim Auslösen der Funktion zum Generieren eines Zertifikates eine Anfrage an den Konnektor, die eine plain-text Antwort erwartet. Der Konnektor sendet jedoch keine plain-text Antwort zurück, sondern eine Zip-Datei. Der Firefox Browser ignoriert diesen Sachverhalt bis incl. Version 127.0.1, alle anderen Browser verwerfen die Antwort, weil sie ungültig ist. Warum scheitert der SelfService hieran nicht? Weil er eine eigens konstruierte Anfrage stellt und nicht auf das Webformular zurückgreift.
Der Workaround
Immer wenn ich Dritte dabei beobachtete, diesen Workaround anzuwenden, sah alles ein bisschen nach Magie aus.
„Jede hinreichend fortschrittliche Technologie ist von Magie nicht zu unterscheiden.“
(Arthur C. Clarke)
Der Workaround bestand darin: Erstelle ein Lesezeichen im Browser mit folgendem Inhalt und klicke es an. Dann funktioniert das Generieren von TLS-Zertifikaten wieder.
javascript:(function()%7Blet%20clientsystem%20%3D%20prompt(%22Bitte%20geben%20Sie%20die%20Clientsystem%20
ID%20ein%22%2C%20%22%22)%3B%0Alet%20certtype%20%3D%20prompt(%22Bitte%20geben%20Sie%20den%20Zertifikatstyp%20
ein%3A%20RSA_2048%20%3A%20RSA_3072%20%3A%20ECC_NIST%20%3A%20ECC_BRAINPOOL_P256_R1%22%2C%20%22RSA_2048%22)%3B%0A
let%20xtoken%20%3D%20document.getElementById(%22x-token%22).value%0Alet%20cert%20%3D%20%22%2Fadministration%2Fdownload
%2Fgenerate-clientsystemcertificate%2F%22%2Bclientsystem%2B%22.zip%3Fxtoken%3D%22%2Bxtoken%2B%22%26certificateTyp
%3D%22%2Bcerttype.replace(%2F%5Cs%2Fg%2C%20'')%3B%0Avar%20xhr%20%3D%20new%20XMLHttpRequest()%3B%0Axhr.responseType
%20%3D%20'blob'%3B%0Axhr.open('GET'%2C%20cert%2C%20true)%3B%0Axhr.send(null)%3B%0Axhr.onload%20%3D%20function%20(e)%20
%7B%0A%20%20%20%20var%20blob%20%3D%20e.currentTarget.response%3B%0A%20%20%20%20saveBlob(blob%2C%20clientsystem%2B%22.zip%22
)%3B%0A%7D%0Afunction%20saveBlob(blob%2C%20fileName)%20%7B%0A%20%20%20%20var%20a%20%3D%20document.createElement('a')%3B%0A
%20%20%20%20a.href%20%3D%20window.URL.createObjectURL(blob)%3B%0A%20%20%20%20a.download%20%3D%20fileName%3B%0A%20%20%20
%20a.dispatchEvent(new%20MouseEvent('click'))%3B%0A%7D%7D)()%3B
Was macht der Workaround? Er fragt die zur Generierung des Zertifikats benötigten Parameter beim Benutzer ab und schickt die Anfrage an den Konnektor. Allerdings deklariert er die erwartete Antwort nicht als plain-text, sondern als application/octet-stream. So wird eine ZIP-Datei korrekt kodiert.
Und so sieht der Quellcode aus, bevor er zu einem Bookmarklet konvertiert wurde:
let clientsystem = prompt("Bitte geben Sie die Clientsystem ID ein", "");
let certtype = prompt("Bitte geben Sie den Zertifikatstyp ein: RSA_2048 : RSA_3072 : ECC_NIST : ECC_BRAINPOOL_P256_R1", "RSA_2048");
let xtoken = document.getElementById("x-token").value
let cert = "/administration/download/generate-clientsystemcertificate/"+clientsystem+".zip?xtoken="+xtoken+"&certificateTyp="+certtype.replace(/\s/g, '');
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.open('GET', cert, true);
xhr.send(null);
xhr.onload = function (e) {
var blob = e.currentTarget.response;
saveBlob(blob, clientsystem+".zip");
}
function saveBlob(blob, fileName) {
var a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = fileName;
a.dispatchEvent(new MouseEvent('click'));
}
Nanos gigantum humeris insidentes
Wie kommt man auf eine solche Lösung?
Mir war seit mindestens einer Dekade das Konzept des Bookmarklets bekannt. Ein Bookmarklet, das ist ein Stück Javascript-Code, welches in die aktuelle Browsersitzung injiziert und ausgeführt wird. Meine erste Bekanntschaft machte ich mit einer Funktion namens "Linkify", die im aktuellen Browsertab nach http-Adressen sucht, die keine Links sind und diese so umschreibt, dass man sie anklicken kann. Eine unheimlich praktische Funktion u.a. im Heise-Forum, die ich bis heute unverändert nutze. Später wurde mir bekannt, dass aktuelle Browser-Plugins den Großteil ihrer Funktion auf diese Weise abwickeln.
Damals, vor einer Dekade war es für mich völlig unmöglich, so etwas selbst zu programmieren. Ich war kein Anwendungsentwickler, sondern Fachinformatiker Systemintegration. Das hatte ich auf meinem IHK Prüfungzeugnis verbrieft, und damit war klar, dass ich keine Software schreiben muss. Ich machte mich lieber stundenlang auf die Suche nach Lösungen, die andere geschrieben hatte.
Nun, dieses Konzept hatte sich geändert. In den letzten Jahren hatte ich gelernt, dass Software selbst programmieren manchmal der einzige Weg zu einer Lösung ist, meist schneller geht als sich auf die Suche zu machen und Quelltext zu schreiben meist einfacher ist, als den von anderen zu lesen. "Sei niemals zu faul dir die Zeit zu nehmen, das richtige Werkzeug zu holen." war mein Mantra. Also arbeitete ich mich in Javascript ein. Prinzipiell musste ich ja sogar nur die bereits vorliegende Python Anfrage in Javascript nachbilden und nachher in ein Bookmarklet konvertierten (wofür es fertige Online Dienste gab) – wenn man es so formuliert, war es eigentlich kein Hexenwerk mehr.