From a PEM to a Running Stack — Part 1: Why My Certificate Just Wouldn't Fit
A four-part series on how "just embed a certificate real quick" became a full-day adventure — and what it taught me about X.509, Java keystores, and Docker.
I had a self-signed root certificate as root.pem. A freshly set-up Docker app (an ERP stack with Keycloak as the auth server) insisted on a root.p12. My first thought: "It's just a different file format — convert once and done."
Spoiler: it ended up being four blog posts. What makes this journey instructive isn't a single tricky command. It's the realization that "certificate" is an umbrella term for half a dozen different things that all look alike but behave completely differently. Once you've internalized that, you debug these problems in minutes instead of hours.
First: what is what?
Before we convert anything, it's worth taking a look at the formats we'll run into:
- PEM (
.pem,.crt,.cer): A text format. You recognize it by the lines-----BEGIN CERTIFICATE-----. It can hold a single certificate, several of them, a private key, or all of it together. - DER: The same as PEM, only binary instead of text. In an editor it looks like data salad.
- PKCS#12 (
.p12,.pfx): A binary container that typically bundles a certificate and a private key together — password-protected. Windows calls it.pfx, the Linux world calls it.p12. It is exactly the same format.
The first aha-moment: a .pfx from Windows can simply be renamed to root.p12. No conversion needed. That alone would have saved me a lot of work early on.
The decisive question: what's inside?
Before you convert anything, you should know what the source file actually contains. A truststore (for trusting others) only needs a public certificate. A server keystore (for identifying yourself) additionally needs the private key.
The fastest check on a PEM file:
grep -E "BEGIN (CERTIFICATE|PRIVATE KEY|RSA PRIVATE KEY)" meine-datei.pem
If it only shows CERTIFICATE, there's no key in there. If PRIVATE KEY shows up too, both are included.
So I dutifully converted and started Keycloak. The receipt:
ERROR: Failed to initialize truststore: /certificates/root.p12, type: PKCS12 ERROR: toDerInputStream rejects tag type 45
tag type 45 sounds cryptic, but it's a wonderful hint once you know how to read it: 45 is the ASCII code for the character -. Java wanted to read a binary PKCS#12 file and instead found text — namely the start of -----BEGIN CERTIFICATE-----.
In other words: my root.p12 was in truth a text file (PEM) that just happened to be named .p12. The check:
EVIDENCE: 45 is the ASCII code for - — the dash that begins -----BEGIN CERTIFICATE-----.
file root.p12 # says "ASCII text" instead of "data"? -> not real PKCS#12 head -c 60 root.p12 # shows -----BEGIN...? -> it's PEM
VERDICT: the file extension says nothing about the contents. file and head are your best friends.
It got stranger. Another certificate file looked like this:
-----BEGIN CERTIFICATE----- LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tDQpNSUlH...
Looks normal at first — BEGIN CERTIFICATE, then Base64. But openssl x509 refused:
Could not find certificate ... wrong tag ... nested asn1 error
The trick is in the second line. LS0tLS1CRUdJTiBD... is itself Base64 again — namely of -----BEGIN CERTIFICATE-----. The file was double-encoded: someone had taken a finished PEM and run it through another Base64 encoding, then hand-wrapped fresh BEGIN/END lines around it.
That happens more easily than you'd think, for example when you accidentally apply certutil -encode to something that is already Base64.
The repair — remove the header and footer, kill the Windows line endings, decode once:
grep -v "CERTIFICATE-----" kaputt.crt | tr -d '\r' | base64 -d > sauber.crt openssl x509 -in sauber.crt -noout -subject -issuer
VERDICT: if that last command now cleanly prints subject= and issuer=, the file is rescued. The LS0tLS1 pattern in line two is the tell-tale sign of double Base64.
A recurring troublemaker throughout the whole story was CRLF line endings (\r\n), which sneak in the moment a file is edited or copied on Windows. You make them visible like this:
cat -A datei.pem | head -5
EVIDENCE: if ^M$ shows up at the end of a line, CRLF is in play.
Remove them:
sed -i 's/\r$//' datei.pem
VERDICT: these inconspicuous \r bytes cost me hours in several places, because they subtly sabotage commands and passwords without ever being visible.
What Part 1 taught me
- File extension ≠ format. Check with
fileandheadwhat's really inside. .pfxand.p12are identical — just renaming is often enough.- Java errors about "tag type" or "asn1" almost always mean a format problem, not a content problem.
tag type 45= text where binary was expected (the-ofBEGIN).- Double Base64 encoding is recognized by the
LS0tLS1pattern in the second line. - CRLF line endings are invisible but deadly —
cat -Aunmasks them.
In the next part, we'll look at how to build a keystore out of these clean building blocks that Java actually accepts — and why "OpenSSL can open the file" is a long way from "and so can Java."