0
# PEM Utilities
1
2
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.
3
4
## Capabilities
5
6
### Certificate PEM Decoding
7
8
Parse X.509 certificates from PEM-encoded strings.
9
10
```kotlin { .api }
11
/**
12
* Extension function to decode a PEM-encoded certificate string into an X509Certificate
13
* @receiver String containing PEM-encoded certificate
14
* @return X509Certificate parsed from the PEM data
15
* @throws IllegalArgumentException if parsing fails or certificate is invalid
16
*/
17
fun String.decodeCertificatePem(): X509Certificate
18
```
19
20
**Usage Example:**
21
22
```kotlin
23
val pemCertificate = """
24
-----BEGIN CERTIFICATE-----
25
MIIBYTCCAQegAwIBAgIBKjAKBggqhkjOPQQDAjApMRQwEgYDVQQLEwtlbmdpbmVl
26
cmluZzERMA8GA1UEAxMIY2FzaC5hcHAwHhcNNzAwMTAxMDAwMDA1WhcNNzAwMTAx
27
MDAwMDEwWjApMRQwEgYDVQQLEwtlbmdpbmVlcmluZzERMA8GA1UEAxMIY2FzaC5h
28
cHAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASda8ChkQXxGELnrV/oBnIAx3dD
29
ocUOJfdz4pOJTP6dVQB9U3UBiW5uSX/MoOD0LL5zG3bVyL3Y6pDwKuYvfLNhoyAw
30
HjAcBgNVHREBAf8EEjAQhwQBAQEBgghjYXNoLmFwcDAKBggqhkjOPQQDAgNIADBF
31
AiAyHHg1N6YDDQiY920+cnI5XSZwEGhAtb9PYWO8bLmkcQIhAI2CfEZf3V/obmdT
32
yyaoEufLKVXhrTQhRfodTeigi4RX
33
-----END CERTIFICATE-----
34
""".trimIndent()
35
36
val certificate = pemCertificate.decodeCertificatePem()
37
println("Subject: ${certificate.subjectX500Principal.name}")
38
println("Issuer: ${certificate.issuerX500Principal.name}")
39
```
40
41
### Certificate PEM Encoding
42
43
Encode X.509 certificates to PEM format strings.
44
45
```kotlin { .api }
46
/**
47
* Extension function to encode an X509Certificate as a PEM-formatted string
48
* @receiver X509Certificate to encode
49
* @return String containing the certificate in PEM format with proper headers
50
*/
51
fun X509Certificate.certificatePem(): String
52
```
53
54
**Usage Example:**
55
56
```kotlin
57
// Create or load a certificate
58
val heldCert = HeldCertificate.Builder()
59
.commonName("example.com")
60
.build()
61
62
// Export to PEM format
63
val pemString = heldCert.certificate.certificatePem()
64
println(pemString)
65
66
// Output format:
67
// -----BEGIN CERTIFICATE-----
68
// MIIBSjCB8aADAgECAgEBMAoGCCqGSM49BAMCMC8xLTArBgNVBAMTJDJiYWY3NzVl
69
// LWE4MzUtNDM5ZS1hYWE2LTgzNmNiNDlmMGM3MTAeFw0xODA3MTMxMjA0MzJaFw0x
70
// ODA3MTQxMjA0MzJaMC8xLTArBgNVBAMTJDJiYWY3NzVlLWE4MzUtNDM5ZS1hYWE2
71
// LTgzNmNiNDlmMGM3MTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDmlOiZ3dxA2
72
// zw1KwqGNsKVUZbkUVj5cxV1jDbSTvTlOjSj6LR0Ovys9RFdrjcbbMLWvSvMQgHch
73
// k8Q50c6Kb34wCgYIKoZIzj0EAwIDSAAwRQIhAJkXiCbIR3zxuH5SQR5PEAPJ+ntg
74
// msOSMaAKwAePESf+AiBlxbEu6YpHt1ZJoAhMAv6raYnwSp/A94eJGlJynQ0igQ==
75
// -----END CERTIFICATE-----
76
77
// Save to file
78
File("certificate.pem").writeText(pemString)
79
```
80
81
## Common Usage Patterns
82
83
### Certificate Chain Export
84
85
Export a complete certificate chain in PEM format.
86
87
```kotlin
88
// Create certificate chain
89
val rootCa = HeldCertificate.Builder()
90
.certificateAuthority(1)
91
.commonName("Root CA")
92
.build()
93
94
val intermediateCa = HeldCertificate.Builder()
95
.certificateAuthority(0)
96
.commonName("Intermediate CA")
97
.signedBy(rootCa)
98
.build()
99
100
val serverCert = HeldCertificate.Builder()
101
.commonName("server.example.com")
102
.signedBy(intermediateCa)
103
.build()
104
105
// Export chain as concatenated PEM
106
val chainPem = buildString {
107
append(serverCert.certificate.certificatePem())
108
append("\n")
109
append(intermediateCa.certificate.certificatePem())
110
append("\n")
111
append(rootCa.certificate.certificatePem())
112
}
113
114
// Save certificate chain
115
File("cert-chain.pem").writeText(chainPem)
116
```
117
118
### Certificate and Key Bundle
119
120
Export both certificate and private key together.
121
122
```kotlin
123
val heldCert = HeldCertificate.Builder()
124
.addSubjectAlternativeName("api.example.com")
125
.build()
126
127
// Create combined PEM bundle
128
val bundlePem = buildString {
129
append(heldCert.certificatePem())
130
append("\n")
131
append(heldCert.privateKeyPkcs8Pem())
132
}
133
134
// This bundle can later be loaded with HeldCertificate.decode()
135
val reloaded = HeldCertificate.decode(bundlePem)
136
```
137
138
### Certificate Validation
139
140
Load and validate certificates from PEM files.
141
142
```kotlin
143
// Load certificate from file
144
val pemContent = File("server.pem").readText()
145
val certificate = pemContent.decodeCertificatePem()
146
147
// Validate certificate properties
148
println("Subject: ${certificate.subjectX500Principal.name}")
149
println("Valid from: ${certificate.notBefore}")
150
println("Valid until: ${certificate.notAfter}")
151
println("Serial number: ${certificate.serialNumber}")
152
153
// Check if certificate is still valid
154
try {
155
certificate.checkValidity()
156
println("Certificate is valid")
157
} catch (e: Exception) {
158
println("Certificate is not valid: ${e.message}")
159
}
160
161
// Extract subject alternative names
162
val sanExtension = certificate.subjectAlternativeNames
163
sanExtension?.forEach { san ->
164
val type = san[0] as Int
165
val value = san[1] as String
166
when (type) {
167
2 -> println("DNS: $value")
168
7 -> println("IP: $value")
169
}
170
}
171
```
172
173
### Cross-Tool Compatibility
174
175
Export certificates for use with other TLS tools.
176
177
```kotlin
178
val serverCert = HeldCertificate.Builder()
179
.addSubjectAlternativeName("nginx.example.com")
180
.rsa2048() // Use RSA for broader compatibility
181
.duration(365, TimeUnit.DAYS)
182
.build()
183
184
// Export for nginx
185
val certPem = serverCert.certificatePem()
186
val keyPem = serverCert.privateKeyPkcs8Pem()
187
188
File("nginx.crt").writeText(certPem)
189
File("nginx.key").writeText(keyPem)
190
191
// Export for Apache (PKCS#1 format preferred)
192
val rsaKeyPem = serverCert.privateKeyPkcs1Pem()
193
File("apache.key").writeText(rsaKeyPem)
194
195
// Export for Java keystore import (requires openssl)
196
// openssl pkcs12 -export -in nginx.crt -inkey nginx.key -out keystore.p12
197
```
198
199
### Certificate Authority Bundle
200
201
Create a CA bundle file with multiple trusted roots.
202
203
```kotlin
204
val caCertificates = listOf(
205
loadCertFromResource("root-ca-1.crt"),
206
loadCertFromResource("root-ca-2.crt"),
207
loadCertFromResource("custom-ca.crt")
208
)
209
210
// Create CA bundle
211
val caBundlePem = buildString {
212
caCertificates.forEach { cert ->
213
append(cert.certificatePem())
214
append("\n")
215
}
216
}
217
218
File("ca-bundle.pem").writeText(caBundlePem)
219
220
// Use bundle with HandshakeCertificates
221
val trustedCerts = caBundlePem.split("-----END CERTIFICATE-----")
222
.filter { it.contains("-----BEGIN CERTIFICATE-----") }
223
.map { "$it-----END CERTIFICATE-----" }
224
.map { it.decodeCertificatePem() }
225
226
val handshake = HandshakeCertificates.Builder()
227
.apply {
228
trustedCerts.forEach { addTrustedCertificate(it) }
229
}
230
.build()
231
```
232
233
## Error Handling
234
235
### Common Parsing Errors
236
237
Handle common certificate parsing issues.
238
239
```kotlin
240
fun safeParseCertificate(pemData: String): X509Certificate? {
241
return try {
242
pemData.decodeCertificatePem()
243
} catch (e: IllegalArgumentException) {
244
when {
245
"failed to decode certificate" in e.message.orEmpty() -> {
246
println("Invalid PEM format or corrupted certificate data")
247
null
248
}
249
"string does not include a certificate" in e.message.orEmpty() -> {
250
println("No certificate found in PEM data")
251
null
252
}
253
else -> {
254
println("Certificate parsing error: ${e.message}")
255
null
256
}
257
}
258
}
259
}
260
261
// Usage
262
val certificate = safeParseCertificate(pemData)
263
if (certificate != null) {
264
println("Successfully parsed certificate: ${certificate.subjectX500Principal.name}")
265
} else {
266
println("Failed to parse certificate")
267
}
268
```
269
270
### Certificate Validation
271
272
Validate certificate chains and expiration.
273
274
```kotlin
275
fun validateCertificateChain(certificates: List<X509Certificate>): Boolean {
276
if (certificates.isEmpty()) return false
277
278
return try {
279
// Check each certificate's validity period
280
certificates.forEach { cert ->
281
cert.checkValidity()
282
}
283
284
// Verify certificate chain (simplified)
285
for (i in 0 until certificates.size - 1) {
286
val cert = certificates[i]
287
val issuer = certificates[i + 1]
288
cert.verify(issuer.publicKey)
289
}
290
291
true
292
} catch (e: Exception) {
293
println("Certificate chain validation failed: ${e.message}")
294
false
295
}
296
}
297
```