Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using X509Certificate from KeyChain in OKHttpClient sslSocketFactory

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?

like image 654
Mr Heelis Avatar asked Jan 27 '26 03:01

Mr Heelis


1 Answers

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.

enter image description here

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 enter image description here

...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

enter image description here

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);
like image 151
Mr Heelis Avatar answered Jan 28 '26 16:01

Mr Heelis



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!