OkHttp Transport Layer Security (TLS) library providing approachable APIs for using TLS, including certificate handling, certificate authorities, and client authentication
—
Utility functions for encoding and decoding X.509 certificates in PEM format, enabling easy certificate persistence, exchange, and interoperability with other TLS tools and systems.
Parse X.509 certificates from PEM-encoded strings.
/**
* Extension function to decode a PEM-encoded certificate string into an X509Certificate
* @receiver String containing PEM-encoded certificate
* @return X509Certificate parsed from the PEM data
* @throws IllegalArgumentException if parsing fails or certificate is invalid
*/
fun String.decodeCertificatePem(): X509CertificateUsage Example:
val pemCertificate = """
-----BEGIN CERTIFICATE-----
MIIBYTCCAQegAwIBAgIBKjAKBggqhkjOPQQDAjApMRQwEgYDVQQLEwtlbmdpbmVl
cmluZzERMA8GA1UEAxMIY2FzaC5hcHAwHhcNNzAwMTAxMDAwMDA1WhcNNzAwMTAx
MDAwMDEwWjApMRQwEgYDVQQLEwtlbmdpbmVlcmluZzERMA8GA1UEAxMIY2FzaC5h
cHAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASda8ChkQXxGELnrV/oBnIAx3dD
ocUOJfdz4pOJTP6dVQB9U3UBiW5uSX/MoOD0LL5zG3bVyL3Y6pDwKuYvfLNhoyAw
HjAcBgNVHREBAf8EEjAQhwQBAQEBgghjYXNoLmFwcDAKBggqhkjOPQQDAgNIADBF
AiAyHHg1N6YDDQiY920+cnI5XSZwEGhAtb9PYWO8bLmkcQIhAI2CfEZf3V/obmdT
yyaoEufLKVXhrTQhRfodTeigi4RX
-----END CERTIFICATE-----
""".trimIndent()
val certificate = pemCertificate.decodeCertificatePem()
println("Subject: ${certificate.subjectX500Principal.name}")
println("Issuer: ${certificate.issuerX500Principal.name}")Encode X.509 certificates to PEM format strings.
/**
* Extension function to encode an X509Certificate as a PEM-formatted string
* @receiver X509Certificate to encode
* @return String containing the certificate in PEM format with proper headers
*/
fun X509Certificate.certificatePem(): StringUsage Example:
// Create or load a certificate
val heldCert = HeldCertificate.Builder()
.commonName("example.com")
.build()
// Export to PEM format
val pemString = heldCert.certificate.certificatePem()
println(pemString)
// Output format:
// -----BEGIN CERTIFICATE-----
// MIIBSjCB8aADAgECAgEBMAoGCCqGSM49BAMCMC8xLTArBgNVBAMTJDJiYWY3NzVl
// LWE4MzUtNDM5ZS1hYWE2LTgzNmNiNDlmMGM3MTAeFw0xODA3MTMxMjA0MzJaFw0x
// ODA3MTQxMjA0MzJaMC8xLTArBgNVBAMTJDJiYWY3NzVlLWE4MzUtNDM5ZS1hYWE2
// LTgzNmNiNDlmMGM3MTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDmlOiZ3dxA2
// zw1KwqGNsKVUZbkUVj5cxV1jDbSTvTlOjSj6LR0Ovys9RFdrjcbbMLWvSvMQgHch
// k8Q50c6Kb34wCgYIKoZIzj0EAwIDSAAwRQIhAJkXiCbIR3zxuH5SQR5PEAPJ+ntg
// msOSMaAKwAePESf+AiBlxbEu6YpHt1ZJoAhMAv6raYnwSp/A94eJGlJynQ0igQ==
// -----END CERTIFICATE-----
// Save to file
File("certificate.pem").writeText(pemString)Export a complete certificate chain in PEM format.
// Create certificate chain
val rootCa = HeldCertificate.Builder()
.certificateAuthority(1)
.commonName("Root CA")
.build()
val intermediateCa = HeldCertificate.Builder()
.certificateAuthority(0)
.commonName("Intermediate CA")
.signedBy(rootCa)
.build()
val serverCert = HeldCertificate.Builder()
.commonName("server.example.com")
.signedBy(intermediateCa)
.build()
// Export chain as concatenated PEM
val chainPem = buildString {
append(serverCert.certificate.certificatePem())
append("\n")
append(intermediateCa.certificate.certificatePem())
append("\n")
append(rootCa.certificate.certificatePem())
}
// Save certificate chain
File("cert-chain.pem").writeText(chainPem)Export both certificate and private key together.
val heldCert = HeldCertificate.Builder()
.addSubjectAlternativeName("api.example.com")
.build()
// Create combined PEM bundle
val bundlePem = buildString {
append(heldCert.certificatePem())
append("\n")
append(heldCert.privateKeyPkcs8Pem())
}
// This bundle can later be loaded with HeldCertificate.decode()
val reloaded = HeldCertificate.decode(bundlePem)Load and validate certificates from PEM files.
// Load certificate from file
val pemContent = File("server.pem").readText()
val certificate = pemContent.decodeCertificatePem()
// Validate certificate properties
println("Subject: ${certificate.subjectX500Principal.name}")
println("Valid from: ${certificate.notBefore}")
println("Valid until: ${certificate.notAfter}")
println("Serial number: ${certificate.serialNumber}")
// Check if certificate is still valid
try {
certificate.checkValidity()
println("Certificate is valid")
} catch (e: Exception) {
println("Certificate is not valid: ${e.message}")
}
// Extract subject alternative names
val sanExtension = certificate.subjectAlternativeNames
sanExtension?.forEach { san ->
val type = san[0] as Int
val value = san[1] as String
when (type) {
2 -> println("DNS: $value")
7 -> println("IP: $value")
}
}Export certificates for use with other TLS tools.
val serverCert = HeldCertificate.Builder()
.addSubjectAlternativeName("nginx.example.com")
.rsa2048() // Use RSA for broader compatibility
.duration(365, TimeUnit.DAYS)
.build()
// Export for nginx
val certPem = serverCert.certificatePem()
val keyPem = serverCert.privateKeyPkcs8Pem()
File("nginx.crt").writeText(certPem)
File("nginx.key").writeText(keyPem)
// Export for Apache (PKCS#1 format preferred)
val rsaKeyPem = serverCert.privateKeyPkcs1Pem()
File("apache.key").writeText(rsaKeyPem)
// Export for Java keystore import (requires openssl)
// openssl pkcs12 -export -in nginx.crt -inkey nginx.key -out keystore.p12Create a CA bundle file with multiple trusted roots.
val caCertificates = listOf(
loadCertFromResource("root-ca-1.crt"),
loadCertFromResource("root-ca-2.crt"),
loadCertFromResource("custom-ca.crt")
)
// Create CA bundle
val caBundlePem = buildString {
caCertificates.forEach { cert ->
append(cert.certificatePem())
append("\n")
}
}
File("ca-bundle.pem").writeText(caBundlePem)
// Use bundle with HandshakeCertificates
val trustedCerts = caBundlePem.split("-----END CERTIFICATE-----")
.filter { it.contains("-----BEGIN CERTIFICATE-----") }
.map { "$it-----END CERTIFICATE-----" }
.map { it.decodeCertificatePem() }
val handshake = HandshakeCertificates.Builder()
.apply {
trustedCerts.forEach { addTrustedCertificate(it) }
}
.build()Handle common certificate parsing issues.
fun safeParseCertificate(pemData: String): X509Certificate? {
return try {
pemData.decodeCertificatePem()
} catch (e: IllegalArgumentException) {
when {
"failed to decode certificate" in e.message.orEmpty() -> {
println("Invalid PEM format or corrupted certificate data")
null
}
"string does not include a certificate" in e.message.orEmpty() -> {
println("No certificate found in PEM data")
null
}
else -> {
println("Certificate parsing error: ${e.message}")
null
}
}
}
}
// Usage
val certificate = safeParseCertificate(pemData)
if (certificate != null) {
println("Successfully parsed certificate: ${certificate.subjectX500Principal.name}")
} else {
println("Failed to parse certificate")
}Validate certificate chains and expiration.
fun validateCertificateChain(certificates: List<X509Certificate>): Boolean {
if (certificates.isEmpty()) return false
return try {
// Check each certificate's validity period
certificates.forEach { cert ->
cert.checkValidity()
}
// Verify certificate chain (simplified)
for (i in 0 until certificates.size - 1) {
val cert = certificates[i]
val issuer = certificates[i + 1]
cert.verify(issuer.publicKey)
}
true
} catch (e: Exception) {
println("Certificate chain validation failed: ${e.message}")
false
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-squareup-okhttp3--okhttp-tls