javaandroidssl

How to create a BKS (BouncyCastle) format Java Keystore that contains a client certificate chain


I'm writing an Android app that requires SSL client authentication. I know how to create a JKS keystore for a desktop Java application, but Android only supports the BKS format. Every way I've tried to create the keystore results in the following error:
handling exception: javax.net.ssl.SSLHandshakeException: null cert chain

So it looks like the client is never sending a proper certificate chain, probably because I'm not creating the keystore properly. I'm unable to enable SSL debugging like I can on the desktop, so that's making this much more difficult than it should be.

For reference the following is the command that IS working to create a BKS truststore:
keytool -importcert -v -trustcacerts -file "cacert.pem" -alias ca -keystore "mySrvTruststore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest


Here is the command I've tried that is NOT working to create a BKS client keystore:

cat clientkey.pem clientcert.pem cacert.pem > client.pem

keytool -import -v -file <(openssl x509 -in client.pem) -alias client -keystore "clientkeystore" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk16-145.jar" -storetype BKS -storepass testtest

Solution

  • Detailed Step by Step instructions I followed to achieve this

    MyHttpClient.java

    package com.arisglobal.aglite.network;
    
    import java.io.InputStream;
    import java.security.KeyStore;
    
    import org.apache.http.conn.ClientConnectionManager;
    import org.apache.http.conn.scheme.PlainSocketFactory;
    import org.apache.http.conn.scheme.Scheme;
    import org.apache.http.conn.scheme.SchemeRegistry;
    import org.apache.http.conn.ssl.SSLSocketFactory;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.impl.conn.SingleClientConnManager;
    
    import com.arisglobal.aglite.activity.R;
    
    import android.content.Context;
    
    public class MyHttpClient extends DefaultHttpClient {
    
        final Context context;
    
        public MyHttpClient(Context context) {
            this.context = context;
        }
    
        @Override
        protected ClientConnectionManager createClientConnectionManager() {
            SchemeRegistry registry = new SchemeRegistry();
    
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
    
            // Register for port 443 our SSLSocketFactory with our keystore to the ConnectionManager
            registry.register(new Scheme("https", newSslSocketFactory(), 443));
            return new SingleClientConnManager(getParams(), registry);
        }
    
        private SSLSocketFactory newSslSocketFactory() {
            try {
                // Get an instance of the Bouncy Castle KeyStore format
                KeyStore trusted = KeyStore.getInstance("BKS");
    
                // Get the raw resource, which contains the keystore with your trusted certificates (root and any intermediate certs)
                InputStream in = context.getResources().openRawResource(R.raw.aglite);
                try {
                    // Initialize the keystore with the provided trusted certificates.
                    // Also provide the password of the keystore
                    trusted.load(in, "aglite".toCharArray());
                } finally {
                    in.close();
                }
    
                // Pass the keystore to the SSLSocketFactory. The factory is responsible for the verification of the server certificate.
                SSLSocketFactory sf = new SSLSocketFactory(trusted);
    
                // Hostname verification from certificate
                // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
                sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
                return sf;
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
    }
    

    How to invoke the above code in your Activity class:

    DefaultHttpClient client = new MyHttpClient(getApplicationContext());
    HttpResponse response = client.execute(...);