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

kotlin May 14, 2020

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.

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.