romikavinda
romikavinda

Reputation: 11

OkHttp Mutual SSL in Android

I'm trying to implement mutual ssl on my android app with okhttp. Currently I have implemented certificate pinning by adding the server certificate to truststore by reading it from the raw folder through inputstream and adding it to keystore. What I'm unclear is how to add the client key pair to initialize okhttp client with mutual ssl. My current code is;

        try {

        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null, null);
        InputStream certInputStream = context.getResources().openRawResource(
                context.getResources().getIdentifier("server",
                        "raw", context.getPackageName()));

        BufferedInputStream bis = new BufferedInputStream(certInputStream);
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        while (bis.available() > 0) {
            Certificate cert = certificateFactory.generateCertificate(bis);
            keyStore.setCertificateEntry("s1ha", cert);
        }
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);
        trustManagers = trustManagerFactory.getTrustManagers();
        sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, trustManagers, new SecureRandom());
    } catch (Exception e) {
        e.printStackTrace();
    }

Can I copy the Keystore with client key pair to the raw folder and open that keystore instead of the default type and pass that to the trust manager and build the okhttp client?

I'm a newbie and I'm a bit confused about this. Have been searching for a guide for weeks but still hasn't found one clearly explaining the procedure. Okhttp custom trust found here okhttp seems promising but I'm not sure whether it implements mutual SSL Any help or sample code would be much appreciated.

Upvotes: 1

Views: 1761

Answers (2)

Yuri Schimke
Yuri Schimke

Reputation: 13498

If you load the certificate using an external file app into the phone, then you can use the approved Android KeyChain to set this up and let the user choose the certificate to use. This is probably the most secure option.

In this mode you have access to the private key for signing but can't usually access it's bytes or copy it externally. Which is why the key is provided by the key manager and not copied into a key store.

        KeyChain.choosePrivateKeyAlias(this,
                { alias -> storeAlias(alias) },
                arrayOf("RSA", "DSA"),  // List of acceptable key types. null for any
                null,  // issuer, null for any
                "server.cryptomix.com",  // host name of server requesting the cert, null if unavailable
                443,  // port of server requesting the cert, -1 if unavailable
                "mykey") // alias to preselect, null if unavailable
...
    private fun makeRequest(alias: String) {
                val pk = KeyChain.getPrivateKey(applicationContext, alias)!!
                val chain = KeyChain.getCertificateChain(applicationContext, alias)!!

                val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
                trustManagerFactory.init(null as KeyStore?)
                val trustManagers = trustManagerFactory.trustManagers

                val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
                keyManagerFactory.init(null, null)
                val keyManagers = keyManagerFactory.keyManagers

                val km = object : X509KeyManager {
                    override fun getClientAliases(keyType: String?, issuers: Array<Principal>): Array<String> {
                        // TODO check issuers
                        return arrayOf(alias)
                    }

                    override fun chooseClientAlias(keyType: Array<out String>?, issuers: Array<out Principal>?, socket: Socket?): String {
                        // TODO check issuers and socket hostname against expected hosts to use client auth
                        return alias
                    }

                    override fun getServerAliases(keyType: String?, issuers: Array<Principal>): Array<String> {
                        return arrayOf()
                    }

                    override fun chooseServerAlias(keyType: String?, issuers: Array<Principal>, socket: Socket): String {
                        return ""
                    }

                    override fun getCertificateChain(alias: String?): Array<X509Certificate> {
                        return chain
                    }

                    override fun getPrivateKey(alias: String?): PrivateKey {
                        return pk
                    }
                }

                val sslContext = SSLContext.getInstance("TLS")
                sslContext.init(arrayOf(km), trustManagers, null)

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

                val request: Request = Request.Builder()
                        .url("https://server.cryptomix.com/secure/")
                        .build()
                try {
                    client.newCall(request).execute().use { response ->
                        val string = response.body!!.string()
                        println(string)
                    }
                } catch (ioe: IOException) {
                    println(ioe)
                }
            }
    }

See also https://medium.com/@_kbremner/android-and-client-authentication-fd416af19a04

Upvotes: 3

Yuri Schimke
Yuri Schimke

Reputation: 13498

If you can load the certificates within your app then the code you need is roughly

    val keyPair: KeyPair = ...
    val certificate: X509Certificate = ...
    val intermediates: Array<X509Certificate> = ...

    val myClientCert = HeldCertificate(keyPair, certificate)
    
    val m = HandshakeCertificates.Builder()
      .addPlatformTrustedCertificates()
      .heldCertificate(myClientCert, *intermediates)
      .build()

    val client = OkHttpClient.Builder()
      .sslSocketFactory(m.sslSocketFactory(), m.trustManager)
      .build()

This indirectly copies it into a temporary Key Store which is why you need access to the bytes of the private key, making this overall less secure.

Upvotes: 1

Related Questions