All posts

From a PEM to a Running Stack — Part 1: Why My Certificate Just Wouldn't Fit

OpenSSL · keytool · Docker · PKCS#12

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.

ERROR: toDerInputStream rejects tag type 45

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.

CASE #1A .p12 THAT WASN'T

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.

CASE #2DOUBLE-ENCODED

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.

CASE #3THE INVISIBLE CRLF

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

  1. File extension ≠ format. Check with file and head what's really inside.
  2. .pfx and .p12 are identical — just renaming is often enough.
  3. Java errors about "tag type" or "asn1" almost always mean a format problem, not a content problem.
  4. tag type 45 = text where binary was expected (the - of BEGIN).
  5. Double Base64 encoding is recognized by the LS0tLS1 pattern in the second line.
  6. CRLF line endings are invisible but deadly — cat -A unmasks 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."

Comments (0)

No comments yet. Be the first!

Write a Comment