Vom PEM zum laufenden Stack – Teil 1: Warum mein Zertifikat einfach nicht passen wollte

OpenSSL · keytool · Docker · PKCS#12

Vom PEM zum laufenden Stack – Teil 1: Warum mein Zertifikat einfach nicht passen wollte

Eine vierteilige Serie darüber, wie aus einem „schnell mal ein Zertifikat einbinden" ein tagesfüllendes Abenteuer wurde – und was ich dabei über X.509, Java-Keystores und Docker gelernt habe.

ERROR: toDerInputStream rejects tag type 45

Ich hatte ein selbstsigniertes Root-Zertifikat als root.pem. Eine frisch eingerichtete Docker-Anwendung (es ging um einen ERP-Stack mit Keycloak als Authentifizierungsserver) wollte aber partout eine root.p12. Mein erster Gedanke: „Das ist doch nur ein anderes Dateiformat, einmal konvertieren und fertig."

Spoiler: Es waren am Ende vier Blogeinträge. Was diese Reise lehrreich macht, ist nicht ein einzelner kniffliger Befehl. Es ist die Erkenntnis, dass „Zertifikat" ein Sammelbegriff für ein halbes Dutzend verschiedener Dinge ist, die alle ähnlich aussehen, sich aber völlig unterschiedlich verhalten. Wer das einmal verstanden hat, debuggt solche Probleme in Minuten statt in Stunden.


Erstmal: Was ist überhaupt was?

Bevor wir konvertieren, lohnt sich ein Blick auf die Formate, die uns begegnen werden:

  • PEM (.pem, .crt, .cer): Ein Textformat. Du erkennst es an den Zeilen -----BEGIN CERTIFICATE-----. Es kann ein Zertifikat enthalten, mehrere, einen privaten Schlüssel, oder alles zusammen.
  • DER: Dasselbe wie PEM, nur in binär statt Text. Sieht im Editor aus wie Datensalat.
  • PKCS#12 (.p12, .pfx): Ein binärer Container, der typischerweise Zertifikat und privaten Schlüssel zusammen bündelt – passwortgeschützt. Windows nennt es .pfx, die Linux-Welt .p12. Es ist exakt dasselbe Format.

Der erste Aha-Moment: Eine .pfx aus Windows kannst du einfach in root.p12 umbenennen. Kein Konvertieren nötig. Das hätte mir früh viel Arbeit gespart.

Die entscheidende Frage: Was steckt drin?

Bevor man irgendetwas konvertiert, sollte man wissen, was die Quelldatei überhaupt enthält. Ein Truststore (zum Vertrauen anderer) braucht nur ein öffentliches Zertifikat. Ein Server-Keystore (zum Sich-Ausweisen) braucht zusätzlich den privaten Schlüssel.

Der schnellste Check bei einer PEM-Datei:

grep -E "BEGIN (CERTIFICATE|PRIVATE KEY|RSA PRIVATE KEY)" meine-datei.pem

Steht da nur CERTIFICATE, ist kein Schlüssel drin. Steht auch PRIVATE KEY dabei, ist beides enthalten.

CASE #1EINE .p12, DIE KEINE WAR

Ich habe also brav konvertiert und Keycloak gestartet. Die Quittung:

ERROR: Failed to initialize truststore: /certificates/root.p12, type: PKCS12
ERROR: toDerInputStream rejects tag type 45

tag type 45 klingt kryptisch, ist aber ein wunderbarer Hinweis, wenn man ihn zu lesen weiß: 45 ist der ASCII-Code für das Zeichen -. Java wollte eine binäre PKCS#12-Datei lesen und fand stattdessen Text vor – nämlich den Anfang von -----BEGIN CERTIFICATE-----.

Mit anderen Worten: Meine root.p12 war in Wahrheit eine Textdatei (PEM), die nur .p12 hieß. Der Check:

BEWEIS: 45 ist der ASCII-Code für - – den Bindestrich, mit dem -----BEGIN CERTIFICATE----- beginnt.

file root.p12          # says "ASCII text" instead of "data"?  -> not real PKCS#12
head -c 60 root.p12    # shows -----BEGIN...?  -> it's PEM

URTEIL: Die Dateiendung sagt nichts über den Inhalt. file und head sind deine besten Freunde.

CASE #2DOPPELT KODIERT

Es wurde kurioser. Eine andere Zertifikatsdatei sah so aus:

-----BEGIN CERTIFICATE-----
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlH...

Sieht erstmal normal aus – BEGIN CERTIFICATE, dann Base64. Aber openssl x509 weigerte sich:

Could not find certificate ... wrong tag ... nested asn1 error

Der Trick steckt in der zweiten Zeile. LS0tLS1CRUdJTiBD... ist selbst wieder Base64 – und zwar von -----BEGIN CERTIFICATE-----. Die Datei war doppelt kodiert: Jemand hatte ein fertiges PEM nochmal durch eine Base64-Kodierung geschickt und von Hand neue BEGIN/END-Zeilen drumherum gesetzt.

Das passiert leichter als man denkt, etwa wenn man certutil -encode versehentlich auf etwas anwendet, das bereits Base64 ist.

Die Reparatur – Kopf- und Fußzeile entfernen, Windows-Zeilenenden killen, einmal dekodieren:

grep -v "CERTIFICATE-----" kaputt.crt | tr -d '\r' | base64 -d > sauber.crt
openssl x509 -in sauber.crt -noout -subject -issuer

URTEIL: Wenn der letzte Befehl jetzt sauber subject= und issuer= ausgibt, ist die Datei gerettet. Das LS0tLS1-Muster in der zweiten Zeile ist das verräterische Zeichen für doppeltes Base64.

CASE #3DAS UNSICHTBARE CRLF

Ein wiederkehrender Störenfried in der ganzen Geschichte waren CRLF-Zeilenenden (\r\n), die sich einschleichen, sobald eine Datei mal unter Windows bearbeitet oder kopiert wurde. Sichtbar macht man sie so:

cat -A datei.pem | head -5

BEWEIS: Tauchen am Zeilenende ^M$ auf, sind CRLF im Spiel.

Entfernen:

sed -i 's/\r$//' datei.pem

URTEIL: Diese unscheinbaren \r haben mich an mehreren Stellen Stunden gekostet, weil sie Befehle und Passwörter subtil sabotieren, ohne sichtbar zu sein.

Was mich Teil 1 gelehrt hat

  1. Dateiendung ≠ Format. Prüfe mit file und head, was wirklich drin ist.
  2. .pfx und .p12 sind identisch – nur umbenennen reicht oft.
  3. Java-Fehler über „tag type" oder „asn1" bedeuten fast immer ein Formatproblem, kein Inhaltsproblem.
  4. tag type 45 = Text wo Binär erwartet wurde (das - von BEGIN).
  5. Doppelte Base64-Kodierung erkennt man am LS0tLS1-Muster in der zweiten Zeile.
  6. CRLF-Zeilenenden sind unsichtbar, aber tödlich – cat -A enttarnt sie.

Im nächsten Teil geht es darum, wie man aus diesen sauberen Bausteinen einen Keystore baut, den Java tatsächlich akzeptiert – und warum „OpenSSL kann die Datei öffnen" noch lange nicht heißt „Java auch".

Kommentare (0)

Noch keine Kommentare. Sei der Erste!

Kommentar schreiben