Reputation: 11
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
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
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