1import java.security.KeyStore
2import java.security.cert.CertificateException
3import java.security.cert.CertificateFactory
4import java.security.cert.X509Certificate
5import javax.net.ssl.TrustManagerFactory
6import javax.net.ssl.X509TrustManager
7import kotlin.io.path.Path
8import kotlin.io.path.inputStream
9
10fun load(path: String): X509Certificate =
11 Path(path).inputStream().use { CertificateFactory.getInstance("X.509").generateCertificate(it) as X509Certificate }
12
13/** The JDK's TLS trust manager, trusting only the given roots — the same check an HTTPS client makes. */
14fun trustManagerFor(vararg roots: X509Certificate): X509TrustManager {
15 val store = KeyStore.getInstance("PKCS12").apply {
16 load(null, null)
17 roots.forEachIndexed { i, root -> setCertificateEntry("root-$i", root) }
18 }
19 val factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply { init(store) }
20 return factory.trustManagers.filterIsInstance<X509TrustManager>().single()
21}
22
23fun main() {
24 val intermediate = load("intermediate.pem")
25 val trusted = trustManagerFor(load("root.pem"))
26 // chain: as a server sends it, leaf first; "ECDHE_ECDSA" is the key exchange the check assumes
27 trusted.checkServerTrusted(arrayOf(load("leaf.pem"), intermediate), "ECDHE_ECDSA")
28 println("valid: api.example.com -> Issuing CA 1 -> Root CA")
29
30 val cases = listOf(
31 Triple("impostor root", arrayOf(load("leaf.pem"), intermediate), trustManagerFor(load("impostor-root.pem"))),
32 Triple("expired leaf", arrayOf(load("expired.pem"), intermediate), trusted),
33 Triple("missing intermediate", arrayOf(load("leaf.pem")), trusted),
34 )
35 for ((label, chain, manager) in cases) {
36 try {
37 manager.checkServerTrusted(chain, "ECDHE_ECDSA")
38 } catch (e: CertificateException) {
39 println("$label: ${e.message}")
40 }
41 }
42}
43