DEV Community

Lejdi
Lejdi

Posted on

Android - Configure Retrofit to use certificate and server's private key

Quick intro to certificates and keys

When developing mobile apps, it is very likely that you communicate with some backend server to send and receive data. If you just send it in clear text, anyone could intercept your traffic and read the data. Obvious solution to this problem is encryption. The most common solution is encrypting HTTP traffic using the TLS (HTTPS).

TLS is using type of cryptography called asymmetric cryptography, which utilizes public certificates and private keys.

Public certificate contains some well-known information, like public key, it's validity and information about authority that issue your certificate.

Public and private key have this characteristic, that if you encrypt data using one of the keys, it can be only decrypted using the other one. Encryption in Android takes advantage of this property. Android device is encrypting the data using server's public key, and then the server can decode it using it's private key.

To make sure your app exchanges data with the correct server, it's certificate must be issued by one of the Certificate Authorities (CA). In the context of Android, there is a list of trusted CA's, so the certificates issued by any CA included in the list can be trusted by the system.

Scenario

There might happen a case where server's security configuration requires you to communicate using public certificate generated specifically for your app, which is not issued by any CA.

Solution

In the solution I'm using HTTP client called Retrofit in version 2.9.0.

In order to succesfully establish connection in such scenario you need the public certificate and server's private key. They can be placed for example in project resources' raw folder in .crt and .pem files. I do not recommend it however because they can be easily obtained using reverse engineering of our app source code. The best practice is generating such secrets at a runtime.

In order to configure your Retrofit client correctly, you have to add one additional dependency:
com.squareup.okhttp3:okhttp-tls:$version
In my case the version is 5.0.0-alpha.2

Now let's add the code that encrypts our retrofit's communication using custom public certificate.
The first thing you need to represent public certificate as InputStream. In case you store it in resources, you can simply write

val pubCertInputStream = context.resources.openRawResource(R.raw.pub_cert)
Enter fullscreen mode Exit fullscreen mode

Then you need to represent the private key itself (without the "BEGIN/END RSA PRIVATE KEY" stuff) as a string.

Then you use the private key string to build PrivateKey object:

private fun loadPrivateKey(): PrivateKey? {
    val encoded: ByteArray = Base64.decode(privateKeyString, Base64.DEFAULT)
    val keySpec = PKCS8EncodedKeySpec(encoded)
    val kf = KeyFactory.getInstance("RSA")
    return kf.generatePrivate(keySpec)
}
Enter fullscreen mode Exit fullscreen mode

Now you have to build OkHttpClient object:

    val cf: CertificateFactory = CertificateFactory.getInstance("X.509")
    val x509Certificate: X509Certificate = cf.generateCertificate(pubCertInputStream) as X509Certificate //1

    val keyPair = KeyPair(x509Certificate.publicKey, loadPrivateKey()) //2

    val handshakeCertificates: HandshakeCertificates = HandshakeCertificates.Builder()
        .addPlatformTrustedCertificates()
        .heldCertificate(HeldCertificate(keyPair, x509Certificate))
        .build() //3

    val client = OkHttpClient
        .Builder()
        .sslSocketFactory(handshakeCertificates.sslSocketFactory(), handshakeCertificates.trustManager)
        .build() //4
Enter fullscreen mode Exit fullscreen mode

Explanation:
1- create CertificateFactory in order to generate X509Certificate object based on InputStream
2- create KeyPair containing both private and public keys
3- build HandshakeCertificates object, which contains all CA's trusted by Android system and your keypair
4- build OkHttpClient with your custom sslSocketFactory and trustManager

Finally you can build your retrofit object with your custom client included:

val retrofit: Retrofit = retrofit.client(client).build()
Enter fullscreen mode Exit fullscreen mode

And that's it, now you can communicate with backend server using retrofit, and it's own certificate.

Discussion (0)