I learned most of what I know about TLS certificates through reading APIs, source code, and specs. My instincts from dealing with ASN.1, X.509, and TLS as a user is that it the APIs are complex, and I should stay away️ from the even more complex implementations.
So I read lots of specs. I found these difficult to learn from because they aren’t self-contained. Each builds upon others and some go back to the days before ASCII! But I did find a couple very helpful guides: this one from Let’s Encrypt and one from RSA written in 1993.
Next I started writing tests and code, building up from small ASN.1 primitives all the way to full nested objects with type hints. Yesterday I reached my goal and changed okhttp-tls to not need Bouncy Castle to create certificates. The code that does it is about 2,000 lines of Kotlin. Hooray!
Now that I have the code I’m shocked at how simple this stuff can be. It takes just six data classes to model a signed certificate:
data class Certificate( val tbsCertificate: TbsCertificate, val signatureAlgorithm: AlgorithmIdentifier, val signatureValue: ByteString ) data class TbsCertificate( val version: Long, val serialNumber: BigInteger, val signature: AlgorithmIdentifier, val issuer: Map<String, Any?>, val validity: Validity, val subject: Map<String, Any?>, val subjectPublicKeyInfo: SubjectPublicKeyInfo, val extensions: List<Extension> ) data class AlgorithmIdentifier( val algorithm: String, val parameters: Any? ) data class Validity( val notBefore: Instant, val notAfter: Instant ) data class SubjectPublicKeyInfo( val algorithm: AlgorithmIdentifier, val publicKey: ByteString ) data class Extension( val id: String, val critical: Boolean, val value: Any? )
The above code is simplified from certificates.kt in OkHttp, but not by much. It feels great to learn that certificates aren’t so scary; it was just the specs and APIs that were wrapped around them.