How to make an HTTP Request with SSL certificate in Kotlin using a PEM file

With CURL you can send a request using the PEM file, but in Java/Kotlin, it's not that easy. You need some magic.

To send a request using CURL you just need to use this option:

CURLOPT_SSLCERT => "path/to/pem/file"

Well, if you want to send a CURL request from Java/Kotlin you can do something like this.

But I want to use HTTP Client to send request. So I need something else.

To send an HTTP request with SSL Certificate in Kotlin, we need to use a Keystore and we need certificates.

To export certificates from your PEM file, you have to run these commands:

# Extract key
openssl pkey -in foo.pem -out foo-key.pem

# Extract all the certs
openssl crl2pkcs7 -nocrl -certfile file.pem |
  openssl pkcs7 -print_certs -out file-certs.pem

Then follow this tutorial to generate a keystore.ks file.

When you have keystore.ks file(and password for this file), you can use it to create an SSL Context and use it in the request.

Here you can find a function that creates an HTTP Client. You can use this Client to send an HTTP request with SSL Certificate.

    fun buildClient(): HttpClient {
        //locate keystore file with password to that file
        val keystore = "path/to/keystore.ks"
        val keystorePass = "password"
        
        val ks = KeyStore.getInstance("pkcs12")
        val kmf = KeyManagerFactory.getInstance("SunX509")
        //create new ssl context
        val sslContext = SSLContext.getInstance("SSL")
        //load your file to keystore
        ks.load(FileInputStream(keystore), keystorePass.toCharArray())
        kmf.init(ks, keystorePass.toCharArray())
		//set global system properties
        System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol")
        System.setProperty("javax.net.ssl.keyStoreType", "pkcs12")
        System.setProperty("javax.net.ssl.keyStore", keystore)
        System.setProperty("javax.net.ssl.keyStorePassword", keystorePass)

		//initialize sslContext
        sslContext.init(kmf.keyManagers, getTrustAllCert(), null)

        val client = java.net.http.HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .sslContext(sslContext)

                .build()
		//you can send request with your ssl certificate
        return client
    }
    
       private fun getTrustAllCert(): Array<TrustManager> {

        return arrayOf(object : X509TrustManager {
            override fun getAcceptedIssuers(): Array<X509Certificate>? = null
            override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
            override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) {}
        })
    }

However, if you don't want to use all these tricks,  you can parse the PEM file directly in your code.

See the example below:


fun buildClient(): HttpClient {
       val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
        override fun getAcceptedIssuers(): Array<X509Certificate>? = null
        override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
        override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) {}
    })


    val context = SSLContext.getInstance("TLS")
    val certAndKey = Files.readAllBytes(Paths.get("/path/to/file.pem"))
    
    //get cert and key
    val certBytes = parseDERFromPEM(certAndKey, "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----")
    val keyBytes = parseDERFromPEM(certAndKey, "-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----")
    val cert = generateCertificateFromDER(certBytes)
    val key = generatePrivateKeyFromDER(keyBytes)
    val keystore = KeyStore.getInstance("JKS")
    
    keystore.load(null)
    keystore.setCertificateEntry("alias", cert)
    keystore.setKeyEntry("alias", key, "password".toCharArray(), arrayOf<Certificate>(cert))
    //save certificate to keyManager
    val kmf = KeyManagerFactory.getInstance("SunX509")
    kmf.init(keystore, "password".toCharArray())
    
    context.init(kmf.keyManagers, trustAllCerts, null)
    
    val client = java.net.http.HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .sslContext(context)

            .build()

    return client
   
}


private fun parseDERFromPEM(pem: ByteArray, beginDelimiter: String, endDelimiter: String): ByteArray {
    val data = String(pem)
    var tokens = data.split(beginDelimiter).toTypedArray()
    tokens = tokens[1].split(endDelimiter).toTypedArray()
    
    return DatatypeConverter.parseBase64Binary(tokens[0])
}

@Throws(InvalidKeySpecException::class, NoSuchAlgorithmException::class)
private fun generatePrivateKeyFromDER(keyBytes: ByteArray): RSAPrivateKey {
    val spec = PKCS8EncodedKeySpec(keyBytes)
    val factory = KeyFactory.getInstance("RSA")
    
    return factory.generatePrivate(spec) as RSAPrivateKey
}

@Throws(CertificateException::class)
private fun generateCertificateFromDER(certBytes: ByteArray): X509Certificate {
    val factory = CertificateFactory.getInstance("X.509")

    return factory.generateCertificate(ByteArrayInputStream(certBytes)) as X509Certificate
}

I was a bit shocked that I need to write so many lines of code to achieve this in Kotlin/Java. Although using CURL it is so easy to do.