Certificate Decoder · Security
Certificate Decoder & Chain Checker
Paste one certificate, a fullchain.pem, or the whole output of openssl s_client -showcerts. Every X.509 field is decoded, the chain order is checked and corrected with the reason it was wrong, and each link gets a real signature verdict — verified, failed, or not-checked with the algorithm named. A private key in the paste is skipped, never parsed, and never sent anywhere: there is no server here to send it to.
Runs in your browser — nothing you paste leaves this page. How we prove that
Certificate decoder and chain checker playground
Paste a certificate, a fullchain.pem, or the whole output of openssl s_client -showcerts — everything outside the BEGIN/END markers is ignored. A private key is recognised and skipped, never parsed: there is no server here to send it to.
Results update as you type — press Enter to run now.
RFC 6125: a wildcard covers exactly one leftmost label, and an IP address must appear as an IP SAN. commonName is only consulted when the certificate has no SAN at all.
Paste a certificate or a whole chain to see every field decoded, the chain order checked, and each signature verified — all inside this tab.
The Gap
It works in your browser. That is not evidence.
A browser repairs a broken certificate chain for you. If an intermediate is missing, Chrome and Safari can fetch it from the certificate’s own authorityInfoAccess URL, or reuse one they cached from an unrelated site — the padlock appears and the fault stays invisible. curl, Go, Python and every CI runner do none of that. They use exactly the certificates you sent and fail with unable to get local issuer certificate. That asymmetry is the single most common way a certificate deploy passes a manual check and breaks a webhook at 2am.
The other half of the problem is that certificate output is unreadable on purpose. openssl x509 -text is 60 lines of hex per certificate, chains have no visible order, and the question you actually have — is anything missing, is anything expired, does this cover my hostname — is nowhere in that dump. This page answers those three questions first and keeps the full field list underneath.
It is also the wrong thing to ask a chatbot. A language model reading a certificate dump is pattern-matching text: it reads notAfter off the wrong certificate, does date arithmetic wrong in both directions, cheerfully asserts that a wildcard covers two labels, and cannot verify a signature at all because verifying one requires actually doing the maths. This page parses the DER byte by byte and runs the signature through Web Crypto — so you can check the answer you were given against a result you can reproduce with openssl on your own machine.
Working on a token rather than a certificate? The JWT Decoder reads and verifies a JWS, and the Hash Generator computes the same SHA-256 you would compare a fingerprint against.
The Pipeline
How it works.
Five deterministic steps, all inside your browser tab — a hand-rolled DER reader that returns null instead of throwing, and Web Crypto for the one part that is real cryptography.
-
Find the blocks, ignore everything else.
Only text between BEGIN and END markers is read, so an openssl s_client transcript pastes as-is. A PRIVATE KEY block is recognised by its label and skipped without being parsed; a CSR or a PKCS#7 bundle gets told what it is and which command converts it.
-
Base64 to DER, DER to structure.
A hand-rolled reader walks the DER — no parser library, so nothing throws on a mangled paste. DER is a canonical encoding, which is what lets a minimal reader be exact rather than merely permissive: BER-only constructs like indefinite lengths are refused instead of guessed at.
-
Read X.509 the way RFC 5280 defines it.
Serial as exact BigInt decimal, a multi-valued RDN joined rather than flattened, the UTCTime pivot at 2049 respected, and IPv4 and IPv6 SANs formatted properly. Values are shown verbatim — a decoder that sanitised what is in the certificate would be hiding the thing you came to see.
-
Link the chain by key identifier, not by name.
Each certificate's authorityKeyIdentifier is matched against its issuer's subjectKeyIdentifier, so a rolled-over CA with the same name as its predecessor resolves correctly and a cross-signed pair is reported as a cross-signed pair instead of a duplicate.
-
Verify each signature with Web Crypto.
The signed bytes are the tbsCertificate slice, checked against the issuer's public key: RSA PKCS#1 v1.5 with SHA-1/256/384/512, ECDSA on P-256/384/521, Ed25519 where the browser has it. Every link is verified, failed, or not-checked with the algorithm named — never a bare boolean.
Reference
The error you got, and what it means.
Nine messages cover almost every certificate failure in production. Most of them are the same two faults — a missing intermediate, or a name that is not in the SAN list — wearing different vendors’ wording.
| Message | What it actually means |
|---|---|
| unable to get local issuer certificate curl, openssl, Python, Node | The chain stops below a root the client trusts — an intermediate is missing. Append it to the file the server sends. |
| x509: certificate signed by unknown authority Go | The same fault in Go's wording: either a missing intermediate, or a private CA root that this machine does not trust. |
| self-signed certificate in certificate chain curl, openssl | The chain includes a self-signed certificate the client does not trust. Usually a private CA root, sometimes a root sent needlessly. |
| self-signed certificate curl, openssl | The leaf issued itself. Nothing outside it vouches for the name — fine for a test box, never for a public endpoint. |
| NET::ERR_CERT_AUTHORITY_INVALID Chrome, Edge | The browser could not build a path to a trusted root: a missing intermediate it could not fetch, or an untrusted CA. |
| NET::ERR_CERT_COMMON_NAME_INVALID Chrome, Edge | No subjectAltName matches the hostname. A matching commonName does not help — browsers stopped reading it in 2017. |
| NET::ERR_CERT_DATE_INVALID · certificate has expired everywhere | notAfter is in the past, or notBefore is in the future, or the clock on the complaining machine is wrong. |
| certificate is not yet valid curl, openssl, Java | notBefore has not arrived. Either the certificate was issued ahead of a rotation, or clock skew — check both ends. |
| unsupported certificate purpose openssl verify | The extendedKeyUsage does not include serverAuth, so the certificate is not usable for TLS server authentication. |
What correct chain order looks like
Leaf first, then every intermediate. The root is optional — and when it is present it is only bytes, because trust comes from the client’s own store.
fullchain.pem — leaf first, root optional and last
1 CN = shop.example.com ← leaf (CA:FALSE, carries the SANs)
2 CN = Example Labs Intermediate R3 ← signs the leaf (CA:TRUE, pathlen:0)
3 CN = Example Labs Root X1 ← self-signed; the client already has it
each certificate's authorityKeyIdentifier
points at the subjectKeyIdentifier of the one below it Get the chain, then check the claim
A ground-truth tool should be verifiable against something else. Every fingerprint on this page is the SHA-256 of the whole DER — the same bytes openssl x509 -fingerprint hashes.
# The chain your server actually sends, ready to paste above
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
# The claim this page makes is checkable against openssl on your own machine
openssl x509 -in isrgrootx1.pem -noout -fingerprint -sha256
SHA256 Fingerprint=96:BC:EC:06:26:49:76:F3:74:60:77:9A:CF:28:C5:A7:
CF:E8:A3:C0:AA:E1:1A:8F:FC:EE:05:C0:BD:DF:08:C6
# ...and against the value ISRG publishes for Root X1. Three sources, one answer. Next Step
The chain is fine. Now check what it is protecting.
A certificate is one link in a chain of things that have to agree: the JWT Decoder reads and verifies the tokens the endpoint hands out, the Hash Generator computes the digest you compare a fingerprint against, and the Reverse DNS / PTR Helper checks that the name in the certificate is the name that resolves. All of them, like this page, run entirely in your browser.
3 certificates — chain order OK
leaf shop.example.com notAfter 2031-06-01
intermediate Example Labs Intermediate R3
root Example Labs Root X1 self-signed
signature verified leaf ← intermediate RSA PKCS#1 v1.5 with SHA-256
signature verified int ← root RSA PKCS#1 v1.5 with SHA-256
hostname www.shop.example.com covered by *.shop.example.com FAQ
Questions, answered.
Tap a question to expand the answer.
What does "unable to get local issuer certificate" mean?
It means the chain your server sent stops before it reaches anything the client trusts — almost always because an intermediate certificate is missing. A client walks upward from the leaf: leaf → intermediate → root. It only has roots in its trust store, so if the intermediate that links the two is absent, the walk dead-ends and OpenSSL reports error 20 with that message. Paste your chain here and the missing intermediate is named by its exact distinguished name, which is the string you need to find the right file to append.
Why does my certificate work in a browser but fail in curl or CI?
Because browsers repair incomplete chains and command-line clients do not. When an intermediate is missing, Chrome, Edge and Safari can fetch it from the URL in the certificate's authorityInfoAccess extension, or reuse one they cached from a different site — so the padlock appears and the problem stays invisible. curl, openssl s_client, Go's crypto/tls, Python's requests and most language runtimes do none of that: they use exactly the certificates you sent them and fail. That asymmetry is why "it works in my browser" is not evidence, and it is the single most common cause of a deploy that passes a manual check and breaks a webhook.
Should fullchain.pem include the root certificate?
No — and it does no harm either. A client trusts a root because that root is already in its own trust store; a copy sent over the wire proves nothing, because an attacker could send any root they liked. So including it only adds around 1–2 KB to every single handshake. What you must include is the leaf first, then every intermediate above it. This tool labels each certificate by role and tells you which of the two situations you are in: a root that is merely redundant, or an intermediate that is genuinely missing.
What is the difference between a leaf, an intermediate and a root?
The leaf is the certificate for your hostname: it carries the subjectAltName list, it has CA:FALSE in its basicConstraints, and it is what the server presents. An intermediate is a CA certificate that signs leaves on a root's behalf; it has CA:TRUE, usually with pathlen:0 to stop it delegating further. A root is a CA certificate that signed itself — its subject and issuer are identical — and it is trusted only because it ships inside operating systems and browsers. CAs keep roots offline and sign through intermediates precisely so that a compromised signing key can be replaced without reissuing the root.
Does this tool tell me whether a certificate is trusted?
Deliberately not, and that is the honest answer rather than a limitation to work around. "Trusted" is a property of the client, not of the certificate: Chrome, Firefox, macOS, Windows and your Java runtime all carry different root lists, and roots get distrusted between releases. Shipping a copy of one trust store into this page would let it print a confident "trusted" for a root that was removed from Chrome last month — exactly the class of confidently-wrong answer this tool exists to catch. What it does instead is checkable arithmetic: the chain is internally consistent, each signature verified against the key above it, and the fingerprints are there for you to compare against what a CA publishes.
How does a wildcard certificate actually match a hostname?
A wildcard replaces exactly one label, and only the leftmost one (RFC 6125 §6.4.3). So *.example.com matches api.example.com and www.example.com; it does not match a.b.example.com, because that needs two labels, and it does not match bare example.com, because there is no label to replace — which is why certificates normally list the apex as a second SAN. A wildcard is also never honoured in the top two labels (*.com is meaningless) and never inside a label (w*.example.com is not a valid pattern to match against). The hostname field on this page applies each of those rules and tells you which one decided the verdict.
Is it safe to paste a certificate here — or a private key?
A certificate is public by design: it is sent in cleartext at the start of every TLS handshake, and for any publicly trusted certificate it is already published in Certificate Transparency logs. So there is nothing to protect. A private key is the opposite, and this page still handles it safely: the whole tool is a static page with no server behind it, so if you paste a PRIVATE KEY block it is recognised by its label, never parsed, and never sent anywhere — the page tells you so explicitly. That is not something most certificate decoders can say, because most of them post your input to a backend. If you did paste a key into one of those, rotate it.
Why does a signature sometimes say "not checked" instead of "failed"?
Because those are two completely different answers and merging them would be a lie. "Failed" means the issuer's public key was applied to the signature and it did not match — the certificate was altered, or that is not the issuer. "Not checked" means the check could not be performed at all, and the reason is always named: RSASSA-PSS carries its hash, salt length and mask function inside its parameters and this tool declines to guess them; SHA-224, MD5 and DSA are not implemented by Web Crypto; Ed25519 needs Chrome 137 or Safari 17. A boolean would print "not verified" for all of those and let you read it as "forged". Every chain link here is one of three states, never two.
My certificate has the right commonName. Why do browsers still reject it?
Because browsers stopped reading commonName in 2017. Chrome 58 and Firefox 48 removed the fallback, and every current client requires the hostname to appear in the subjectAltName extension instead — a certificate with a perfect CN and no SAN matches nothing at all and produces NET::ERR_CERT_COMMON_NAME_INVALID. It is still legal to put the name in the CN as well, and most CAs do, but it is decoration. This tool shows the SAN list separately from the subject for exactly that reason, and warns when a certificate has no SAN at all.
More free, private DevOps tools.
The Certificate Decoder is one tool in OpsCanopy — a growing canopy of browser-based validators, converters and testers that never touch a server.
More in Security
39 free tools, every one offline-capable — opscanopy.com works with no signup and nothing uploaded.
Related security tools: the JWT Decoder, the Hash Generator for the digests behind a fingerprint, and the Reverse DNS / PTR Helper for checking that the name in a certificate is the name that resolves — or browse the full tools directory.
Provided as-is for convenience. This page reports what is inside a certificate and whether each signature checks out; it does not and will not claim a certificate is trusted — trust lives in the client’s own root store, and there is no revocation or Certificate Transparency check here either (both need the network). OpsCanopy is free and open.