As mentioned in Playing With Certs, I had found a defunct Google Chrome extension. nCode is an extension that handles various encoding/decoding options, the PEM Certificate decoding being of primary interest.
The Certificate input looks like so:
It’s straightforward enough: You enter your PEM-encoded certificate, click Decode, and the output field should populate with your certificate details. When I first tried, rather than being greeted by certificate details, I instead saw “undefined.” What was going on here? My certificate was a valid and well-formed, there should be no reason for it to not work. I had to do some digging.
The extension page had a link to the source code on GitHub, and reading through it led me to this snippet:
So, nCode is making a call out to the developer’s backend to handle the decode, rather than doing it locally. Had I not found the code on GitHub, I could have reached the same conclusion by using the Inspect Popup functionality to view the Console output and Network traffic generated by nCode.
Note that these screenshots may not be exactly what I saw, but you get the point. I’ve since registered the backend domain and pointed it to my own host, so the errors may have differed prior to then.
From this point, I checked to see why the requests were failing, including trying to ping the api.ryanallen.ninja site. Nothing came back, so I checked to see if the domain was registered, and registered it as it was not.
I ended up writing a barebones endpoint on Netlify which gave me HTTPS and 125,000 free function invocations per month.
I’ve since lost the collection of articles I read while researching how to turn a PEM-encoded certificate into human-readable text, but it should suffice to say that I’m exceptionally grateful that the Crypto module exists. Sticking with the example Google.com certificate, let’s briefly look at what’s going on under the hood. PEM Encoded Google.com Certificate:
If you don’t recognize the blob of text, it’s base64-encoded data. The == and the end of the blob is one dead giveaway, it is padding as defined in RFC 4648, which establishes the Base64 encoding standard. This is mandated by RFC 7468, which relates to certificate encoding. It says that “The encoded data MUST be a BER…encoded ASN.1 Certificate structure…” which brings us to Abstract Syntax Notation One. Borrowing from Wikipedia, “ASN.1 is a standard interface description language (IDL, think Protobuf or OpenAPI spec) for defining data structures that can be serialized and deserialized in a cross-platform way.” For our purposes, it means that when we try to run a Base64-decode on the certifacte in Notepad++, we get this: So, what is going on here? Why can’t we read all of the text? Our text editor is treating all of the content as UTF-8 encoded text. There it is again, encoding. In short, the text editor is trying to decode the data according to the UTF-8 specification, rather than the BER encoded ASN.1 Certificate structure, known as X.509. Referring to the X.509 spec, we are going to drill down into lines 8 and 9 of the text 240129080447Z which I suspect are timestamps.
Validity ::= SEQUENCE { notBefore Time, notAfter Time }
Time ::= CHOICE { utcTime UTCTime, generalTime GeneralizedTime }
At this point we have, as it says, a CHOICE between UTCTime and GeneralizedTime. According to the spec, certificate validity dates through the year 2049 must be encoded as UTCTime. Thankfully, UTCTime is built into ASN.1 as such: ‘UTCTime values take the form of either “YYMMDDhhmm[ss]Z” or…’ Z being Zulu time.
Returning to nCode, when we decode the certifacte, we see the following:
All of that and more is possible with little effort on our part thanks to the dozens contributors to the Crypto module of nodejs. Much appreciation to all of them.
In a future post, I’ll go into detail about what exactly these unreadable bits are: