I'm an Android Developer who has to use KeyChain not KeyStore. The KeyStore variant of our code works. I need to add KeyChain equivalent.
this works
final char[] PASSWORD = "***SOMEPASSWORD****".toCharArray();
TrustManager[] trustManager;
SSLSocketFactory sslSocketFactory;
KeyStore keyStore;
InputStream inputStream = context.getResources().getAssets().open("xxxx-xxxxx-xxxxx-xxxx.pfx");
keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(inputStream,PASSWORD);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance (TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager))
{
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
trustManager = trustManagers;
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore,PASSWORD);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(),null,null);
sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15000, TimeUnit.MILLISECONDS).readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(15000, TimeUnit.MILLISECONDS).cookieJar(new ReactCookieJarContainer());
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManager[0]);
OkHttpClient okHttpClient = builder.build();
The problem is this line InputStream inputStream = context.getResources().getAssets().open("xxxx-xxxxx-xxxxx-xxxx.pfx"); we're not allowed to use the assets folder (for reasons outside the scope of this conversation) but we are allowed to put the self same file in the KeyChain so I did, and I can retrieve it using the following. X509Certificate[] chain = KeyChain.getCertificateChain(context, "xxxx-xxxxx-xxxxx-xxxx");
so since
X509Certificate[] chain = KeyChain.getCertificateChain(context, "xxxx-xxxxx-xxxxx-xxxx"); //this gets the correct X509Certificate
Gets the certificate via KeyChain my instinct was to swap it out with this:
X509TrustManager customTm = new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
try {
return X509Certificate[] chain = KeyChain.getCertificateChain(context, "xxxx-xxxxx-xxxxx-xxxx");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeyChainException e) {
e.printStackTrace();
}
return null;
}
};
TrustManager[] trustManager = new TrustManager[] { customTm };
sslContext.init(null, trustManager, null);
but it doesn't work, so my question is: How do I use the X509Certificate I have from the KeyChain as a drop in replacement to the asset I pulled into the KeyStore?
I figured it out, the code needs to become slightly different, but this wasn't documented anywhere I could find: I had to literally face roll the keyboard to get it done
so. the OP example was me using the pfx file in the assets folder.

In order to not use a from an asset file in the build environment (aka KeyStore), in order to load the cert direcly from a part of android OS at run time and get at it in code (aka KeyChain)(i.e. in order to load the cert you can see in the screen below on the device) ....
THIS IS AN illustration of where it is in android, BUT NOT MY SCREEN SHOT 
...use this code
final char[] PASSWORD = "***SOMEPASSWORD****".toCharArray();
TrustManager[] trustManager;
SSLSocketFactory sslSocketFactory;
KeyStore keyStore;
X509Certificate[] keychain;
PrivateKey privateKey;
keychain = KeyChain.getCertificateChain(context, "xkeyx-xaliasxx");
privateKey = KeyChain.getPrivateKey(context, "xkeyx-xaliasxx");
keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
keyStore.setKeyEntry("xkeyx-xaliasxx", privateKey, PASSWORD, keychain);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance (TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager))
{
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
trustManager = trustManagers;
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
keyManagerFactory.init(keyStore,PASSWORD);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManagerFactory.getKeyManagers(),null,null);
sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(15000, TimeUnit.MILLISECONDS).readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(15000, TimeUnit.MILLISECONDS).cookieJar(new ReactCookieJarContainer());
builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManager[0]);
OkHttpClient okHttpClient = builder.build();
NOTE this will only work AFTER the user in app has confirmed the certificate: so this code needs to have been run at least ONCE

KeyChain.choosePrivateKeyAlias(RN.getReactActivity(), (KeyChainAliasCallback) alias -> {
Log.d("cert", String.format("User has selected client certificate alias: %s should be xkeyx-xaliasxx", alias));
X509Certificate[] truechain;
try {
truechain = KeyChain.getCertificateChain(Context, alias);
if (truechain != null) {
Log.d("cert", "truechain count" + truechain.length);
}
else{
Log.d("cert", "truechain count is null");
}
} catch (InterruptedException e) {
Log.e("cert", "InterruptedException", e);
} catch (KeyChainException e) {
Log.e("cert", "KeyChainException", e);
}
}, null, null, null, -1, null);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With