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.
But I’m foolish and I don’t like that okhttp-tls depends on Bouncy Castle to create certificates. “How hard could it be to create a well-formed TLS certificate? It’s just bytes!...”
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.