May 12, 2024

Integration of mutual TLS (mTLS) authentication using Retrofit Android Kotlin

Integrating mutual TLS (mTLS) authentication in Kotlin involves configuring both the client and server sides.  Android using BouncyCastleProvider for the integration of mutual TLS (mTLS). You basically you need to have 3 files (.pem format) for this use case:

1. Client private key

2. Client certificate chain

3. Trusted server certificate

Here we are using  Retrofit (Retrofit2) Client for the mTLS integration


Step1 :  Create .pem file of the above mentioned files(Client private key,  Client certificate chain, Trusted server certificate). You can copy the certificate on notepad and save it as .pem file, Here is the sample file formats,

1. Client private key

-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHN18R6x5Oz+u6
SOXLoxIscz5GHR6cDcCLgyPax2XfXHdJs+h6fTy61WGM+aXEhR2SIwbj5997s34m
0MsbvkJrFmn0LHK1fuTLCihEEmxGdCGZA9xrwxFYAkEjP7D8v7cAWRMipYF/JP7V
U7xNUo+QSkZ0sOi9k6bNkABKL3+yP6PqAzsBoKIN5lN/YRLrppsDmk6nrRDo4R3C
D+8JQl9quEoOmL22Pc/qpOjL1jgOIFSE5y3gwbzDlfCYoAL5V+by1vu0yJShTTK8
oo5wvphcFfEHaQ9w5jFg2htdq99UER3BKuNDuL+zejqGQZCWb0Xsk8S5WBuX8l3B
rrg5giqNAgMBAAECggEAVRB/t9b9igmeTlzyQpHPIMvUu3uTpm742JmWpcSe61FA
XmhDzInNdLnIfbnb3p44kj4Coy5PbzKlm01sbNxA4BkiBPE1yen1J/2eU/LJ6QuN
Aok6ym46BuNjhdo12fxjKiqqwTk+6xZw4fIcb5ZH07SmoFupTzoadTkUcG58pcNp
Q6hg8WwCmQKBgQDTy5lI9AAjpqh7/XpQQrhGT2qHPjuQeU25Vnbt6GjI7OVDkvHL
LQdpyYprGQgs4s+5TGWNNARYC/cMAh1Ujv5Yw3jUWrR5V73IhZeg20bBQYWKuwDv
thVKblFw377cZAxl51R9QCX6O4oW8mRFLiMxORd0bD6YNrf/CyNMZJraYQKBgAE7
o0JbFJWxtV/qh5cpKAb0VpYKOngO6pkSuMzQhlINJVUUhPZJJBdl9+dy69KIkzOJ
nTIVXotkp5GuxZhe7jgrg7F7g6PkKCLTFzWYgVF/ZihoggxyEs/7xaTe6aZ/KILt
UMH/2bwaPVtYNfwWuu8qpurfWBzPVhIVU2c+AuQBAoGAXMbw10vyiznlhyMFw5kx
SzlBMqJBLJkzQBtpvXuT0lqqxTSNC3N4WxgVOLCHa6HqXiB0790YL8/RWunsXTk2
c7ugThP6iMPNVAycWkIF4vvHTwZ9RCSmEQabRaqGGLz/bhLL3fi3lPGCR+iW2Dxq
xv18lSy+LTQJ18/iHvAA6g==
-----END PRIVATE KEY-----

2. Client certificate chain

-----BEGIN CERTIFICATE-----
MIIGjTCCBXWgAwIBAgIJAIAGr4lBg6vhMA0GCSqGSIb3DQEBCwUAMIG0MQswCQYD
BQAwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
DVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkJhZFNTTDExMC8GA1UEAwwoQmFkU1NM
IENsaWVudCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xOTExMjcwMDE5
NTdaFw0yMTExMjYwMDE5NTdaMG8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp
Zm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZCYWRTU0wx
IjAgBgNVBAMMGUJhZFNTTCBDbGllbnQgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDHN18R6x5Oz+u6SOXLoxIscz5GHR6cDcCLgyPa
x2XfXHdJs+h6fTy61WGM+aXEhR2SIwbj5997s34m0MsbvkJrFmn0LHK1fuTLCihE
Xw41pTYYN6xNEdJ0vVejAHUAfVkeEuF4KnscYWd8Xv340IdcFKBOlZ65Ay/ZDowu
ebgAAAGPUjS5TgAABAMARjBEAiBpXLn4s9j2LFc4cvASbnFWKUpYP3oQyCTbFWGW
bGPMwBcqaGXvK62NlaRkwjnbkPM4MYvREM0bbAgZD2GHyANBTso8bdWvhLvmoSjs
FSqJUJp17AZ0x/ELWZd69v2zKW9UdPmw0evyVR19elh/7dmtF6wbewc4N4jxQnTq
IItuhIWKWB9edgJz65uZ9ubQWjXoa+9CuWcV/1KxuKCbLHdZXiboLrKm4S1WmMYW
d0sJm95H9mJzcLyhLF7iX2kK6K9ug1y02YCVXBC9WGZc2x6GMS7lDkXSkJFy3EWh
CmfxkmFGwOgwKt3Jd1pF9ftcSEMhu4WcMgxi9vZr9OdkJLxmk033sVKI/hnkPaHw
RKIruvlukArE/MGw1vCfUNWcWLI6RZ9kDVFZWl0w/FgHij2+N1/lOuTc63JQ1aHj
RmIw0y7t6zQF1ioiXZmgxX8mfhBn8UZvHTfCbNdreGHJ6+bCus4R2VkXbR0e633w
Qw==
-----END CERTIFICATE-----

3. Trusted server certificate

-----BEGIN CERTIFICATE-----
MIIGkDCCBXigAwIBAgIJAMnSnnHlWm38MA0GCSqGSIb3DQEBCwUAMIG0MQswCQYD
VQQGEwJVUzEQMA4GA1UECBMHQXJpem9uYTETMBEGA1UEBxMKU2NvdHRzZGFsZTEa
BQAwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
DVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBkJhZFNTTDExMC8GA1UEAwwoQmFkU1NM
IENsaWVudCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eTAeFw0xOTExMjcwMDE5
NTdaFw0yMTExMjYwMDE5NTdaMG8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp
Zm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZCYWRTU0wx
IjAgBgNVBAMMGUJhZFNTTCBDbGllbnQgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDHN18R6x5Oz+u6SOXLoxIscz5GHR6cDcCLgyPa
AbJ1Icr/+1CbVzk4K2k+knPVoPr8PYJ9SSnoreT8uNhuEfe2eS1GbBbcqEzE1znf
Fko9c4hzNMJvBJ7difUwlETU5ha8tYwn4HUP4JNSNPs0+fJ5Oql5MmSxDdtvNg4S
+VpQmtUllpC4+AMD2i8sAWKAslYvPlkMo/JhJkCUujRh5XgI+yy8BqcCAwEAAaOC
Az4wggM6MAwGA1UdEwEB/wQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
BwMCMA4GA1UdDwEB/wQEAwIFoDA5BgNVHR8EMjAwMC6gLKAqhihodHRwOi8vY3Js
LmdvZGFkZHkuY29tL2dkaWcyczEtMTI1OTEuY3JsMF0GA1UdIARWMFQwSAYLYIZI
AYb9bQEHFwEwOTA3BggrBgEFBQcCARYraHR0cDovL2NlcnRpZmljYXRlcy5nb2Rh
Eztn7A0CIQCNbL3lir3b0zB6/240tqI6TWL/eDBt3Un9LwnNlUyn3AB2ANq2v2s/
tbYin5vCu1xr6HCRcWy7UYSFNL2kPTBI1/urAAABjBoMfFsAAAQDAEcwRQIhAMTd
RFDg8qL4LyHxp+CsMR41DADbBbf0dm9QbRNDq1bVAiBoe3ZTfDWGbcO115X23Dzy
Zm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZCYWRTU0wx
IjAgBgNVBAMMGUJhZFNTTCBDbGllbnQgQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDHN18R6x5Oz+u6SOXLoxIscz5GHR6cDcCLgyPa
x2XfXHdJs+h6fTy61WGM+aXEhR2SIwbj5997s34m0MsbvkJrFmn0LHK1fuTLCihE
Xw41pTYYN6xNEdJ0vVejAHUAfVkeEuF4KnscYWd8Xv340IdcFKBOlZ65Ay/ZDowu
ebgAAAGPUjS5TgAABAMARjBEAiBpXLn4s9j2LFc4cvASbnFWKUpYP3oQyCTbFWGW
9R8Lag==
-----END CERTIFICATE-----


Once you create these files save these files on the raw folder inside the res folder,

res/raw/server_certificate.pem
res/raw/client_private_key.pem
res/raw/client_certificate_chin.pem


ApiService.kt file 

import okhttp3.ResponseBody
import retrofit2.http.*
interface ApiService {
    @POST("/apipath/function")
    suspend fun sendMtlsRequest(@HeaderMap headerMap: HashMap<String, Any>, @Body hashMap: HashMap<String, Any>): ResponseBody
}



Making API Call,

    fun sendApiRequest(context : Context, headerMap: HashMap<String, Any>, map: HashMap<String, Any>) = getApiResult<Nothing> {
        var attestationSpiService = getApiServiceWithMTLS(context)
        attestationSpiService.sendMtlsRequest(headerMap, map)
    }

Function to making ApiService,


import android.content.Context
import java.security.cert.CertificateFactory;
import java.io.InputStream;
import java.security.cert.Certificate;
import com.google.common.io.ByteStreams
import java.security.KeyStore
import java.util.Base64;
import javax.net.ssl.X509TrustManager
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.io.InputStream;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import okhttp3.OkHttpClient
import com.google.gson.GsonBuilder
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateException
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.security.KeyStoreException


fun getApiServiceWithMTLS(context : Context): ApiService {

        val certificateFactory: CertificateFactory = CertificateFactory.getInstance("X.509")

        var certificateChin = context.getResources().getIdentifier("server_certificate","raw", context.getPackageName());
        val trustedCertificateAsInputStream: InputStream = context.resources.openRawResource(certificateChin)
        val trustedCertificate: Certificate = certificateFactory.generateCertificate(trustedCertificateAsInputStream)
 
        val trustStore: KeyStore = createEmptyKeyStore("secret".toCharArray())
        trustStore.setCertificateEntry("edfapay-server", trustedCertificate);

        var pkeyBytes = ByteStreams.toByteArray(context.resources.openRawResource(R.raw.client_private_key))
        val privateKeyContent = String(pkeyBytes, StandardCharsets.UTF_8)
            .replace("-----BEGIN PRIVATE KEY-----", "")
            .replace(System.lineSeparator().toRegex(), "")
            .replace("-----END PRIVATE KEY-----", "")
            .replace("\\n","")

        var privateKeyAsBytes = Base64.getMimeDecoder().decode(privateKeyContent);

        val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
        val keySpec = PKCS8EncodedKeySpec(privateKeyAsBytes)

        val certificateChainAsInputStream: InputStream = context.resources.openRawResource(R.raw.client_certificate_chin)
        val certificateChain: Certificate = certificateFactory.generateCertificate(certificateChainAsInputStream)
        var certificate: Array<Certificate> =  arrayOf(certificateChain)

        val identityStore = createEmptyKeyStore("secret".toCharArray())
        var privateKey = keyFactory.generatePrivate(keySpec)
        identityStore.setKeyEntry("client", privateKey, "".toCharArray(), certificate);

        trustedCertificateAsInputStream.close();
        certificateChainAsInputStream.close();

        val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
        trustManagerFactory.init(trustStore);
        val trustManagers = trustManagerFactory.trustManagers

        val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
        keyManagerFactory.init(identityStore, "secret".toCharArray());
        val keyManagers = keyManagerFactory.keyManagers

        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(keyManagers, trustManagers, null);

        val socketFactory: SSLSocketFactory = sslContext.socketFactory

        val okHttpClient = OkHttpClient.Builder()
            .sslSocketFactory(socketFactory, trustManagers[0] as X509TrustManager)
            .build()

        val json = GsonBuilder().setLenient().create()

        val retrofit = Retrofit.Builder()
            .baseUrl("https://api.shidhints.com/")
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create(json))
            .build()
        return retrofit.create(ApiService::class.java)
    }


    @Throws(CertificateException::class, NoSuchAlgorithmException::class, IOException::class, KeyStoreException::class)
    fun createEmptyKeyStore(keyStorePassword: CharArray?): KeyStore {
        val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
        keyStore.load(null, keyStorePassword)
        return keyStore
    }



No comments:

Post a Comment