X.509 and ASN.1
Canonical digital certificate format. RFC 5280 (Internet X.509 PKI Certificate and CRL Profile) is the reference. Everything involving TLS, S/MIME, code signing, ICP-Brasil derives from here.
Structure of an X.509 v3 cert
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate, -- "to be signed"
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
TBSCertificate ::= SEQUENCE {
version [0] EXPLICIT Version DEFAULT v1,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity, -- notBefore + notAfter
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo,
extensions [3] EXPLICIT Extensions OPTIONAL
}Encoding: ASN.1 DER (Distinguished Encoding Rules) — canonical binary. PEM = base64 of DER with -----BEGIN CERTIFICATE----- envelope. Inspection: openssl x509 -in cert.pem -text -noout.
Critical extensions (v3)
| Extension | OID | What it's for |
|---|---|---|
| BasicConstraints | 2.5.29.19 | CA:TRUE/FALSE + pathLenConstraint. Distinguishes a CA from an end-entity. A CA forged by an end-entity was the basis of several historical attacks |
| SubjectAltName (SAN) | 2.5.29.17 | DNS names, IPs, emails. The subject CN has been deprecated for hostname matching since 2017 (CA/B BR). Browsers only look at the SAN |
| KeyUsage | 2.5.29.15 | digitalSignature, keyEncipherment, keyCertSign, … restricts operations |
| ExtKeyUsage (EKU) | 2.5.29.37 | serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, OCSPSigning |
| CRLDistributionPoints | 2.5.29.31 | Where to fetch the CRL for revocation |
| AuthorityInfoAccess (AIA) | 1.3.6.1.5.5.7.1.1 | OCSP responder URLs + issuer cert |
| CertificatePolicies | 2.5.29.32 | Policy OIDs (e.g., ICP-Brasil A1A3A4 have specific OIDs) |
| SCTList | 1.3.6.1.4.1.11129.2.4.2 | Embedding of Signed Certificate Timestamps (CT logs) |
| NameConstraints | 2.5.29.30 | Restricts the domains a sub-CA may issue for (CRITICAL in delegated sub-CAs, ignored in the field) |
Trust path validation
RFC 5280 §6 defines the algorithm. Summary:
- Chain: end-entity → intermediate(s) → root. The root is the trust anchor (already trusted by configuration).
- Each (cert, issuer) pair checks: valid signature (
issuer.publicKeyverifiescert.signatureValue),notBefore ≤ now ≤ notAfter, compatible BasicConstraintsKeyUsageEKU, not revoked (OCSPCRLCRLite/embedded SCTs). - Common pitfall: confusing "it's in the chain sent by the server" with "it's in the trust store". The TLS server sends the chain up to (and excluding) the root; the client splices in its local root.
Operational pitfalls
- CN ≠ SAN: never rely on the CN. A modern cert without a SAN is invalid for hostname binding.
- Long validity: the public maximum in 2026 is 398 days (CA/B BR §6.3.2). Roadmap heading toward 90 days (Apple proposal 2024) and 47 days (Google proposal).
- Signature algorithm: SHA-1 banned in public CAs since 2017. A cert with
sha1WithRSAEncryptionin production is a red flag. - Cross-sign: the same subject+key can have multiple certs issued by different issuers — useful for root transition, see
06-ssl-and-tls/05-creating-a-letsencrypt-style-ca.kmd§Cross-signing. - Path building: given a pool of intermediates, finding the right chain is NP-hard in the worst case; serious libs have heuristics. Real failures occur when intermediates are missing.
ASN.1 — enough to survive
ASN.1 = abstract type description language. DER = canonical binary encoding (TLV: Tag + Length + Value).
SEQUENCE= ordered structSET= unordered structOCTET STRING= bytesBIT STRING= bits (with a padding offset)OBJECT IDENTIFIER(OID) = hierarchical type identifier (1.2.840.113549.1.1.11= sha256WithRSAEncryption)UTCTime(YYMMDDHHMMSSZ, valid until 2049) vsGeneralizedTime(YYYYMMDDHHMMSSZ)
Tooling to inspect: openssl asn1parse, dumpasn1, lapo.it/asn1js (browser).
Canonical libs
- Go:
crypto/x509(stdlib) — complete and ergonomic - Rust:
rustls-webpki(path validation),x509-parser(parsing) - Dart:
package:basic_utils(limited parsing; for heavy ops, FFI bridge) - Python:
cryptography.x509