Skip to content Skip to sidebar Skip to footer

Request With Automatic Or User Selection Of Appropriate Client Certificate

I'm developing an hybrid cordova app which might connect to different servers. Some of them do require a client certificate. On an Android mobile the corresponding root cert + clie

Solution 1:

You can use a certificate previously installed in Android KeyChain (the system key store) extending X509ExtendedKeyManager to configure the SSLContext used by URLConnection

The certificate is referenced by an alias that you need. To prompt user for selection with a dialog similar to chrome use:

KeyChain.choosePrivateKeyAlias(this, this, // CallbacknewString[] {"RSA", "DSA"}, // Any key types.null, // Any issuers.null, // Any host
            -1, // Any portDEFAULT_ALIAS);

This is the code to configure the SSL connection using a custom KeyManager. It uses the default TrustManager and HostnameVerifier. You will need to configure them if the server is using a self signed certificate not present in Android default truststore (trusting all certificates is not recommended)

//Configure trustManager if needed
TrustManager[] trustManagers = null;

//Configure keyManager to select the private key and the certificate chain from KeyChainKeyManagerkeyManager= KeyChainKeyManager.fromAlias(
            context, mClientCertAlias);

//Configure SSLContextSSLContextsslContext= SSLContext.getInstance("TLS");
sslContext.init(newKeyManager[] {keyManager}, trustManagers, null);


//Perform the connectionURLurl=newURL( versionUrl );
HttpsURLConnectionurlConnection= ( HttpsURLConnection ) url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
//urlConnection.setHostnameVerifier(hostnameVerifier);  //Configure hostnameVerifier if needed
urlConnection.setConnectTimeout( 10000 );
InputStreamin= urlConnection.getInputStream();

Finally here you have and a full implementation of the custom X509ExtendedKeyManager extracted from here and here that is in charge of selecting the client certificate. I have extracted the required code.

publicstaticclassKeyChainKeyManagerextendsX509ExtendedKeyManager {
    private final String mClientAlias;
    private final X509Certificate[] mCertificateChain;
    private final PrivateKey mPrivateKey;

        /**
         * Builds an instance of a KeyChainKeyManager using the given certificate alias.
         * If for any reason retrieval of the credentials from the system {@link android.security.KeyChain} fails,
         * a {@code null} value will be returned.
         */publicstaticKeyChainKeyManagerfromAlias(Context context, String alias)
                throws CertificateException {
            X509Certificate[] certificateChain;
            try {
                certificateChain = KeyChain.getCertificateChain(context, alias);
            } catch (KeyChainException e) {
                thrownewCertificateException(e);
            } catch (InterruptedException e) {
                thrownewCertificateException(e);
            }

            PrivateKey privateKey;
            try {
                privateKey = KeyChain.getPrivateKey(context, alias);
            } catch (KeyChainException e) {
                thrownewCertificateException(e);
            } catch (InterruptedException e) {
                thrownewCertificateException(e);
            }

            if (certificateChain == null || privateKey == null) {
                thrownewCertificateException("Can't access certificate from keystore");
            }

            returnnewKeyChainKeyManager(alias, certificateChain, privateKey);
        }

        privateKeyChainKeyManager(
                String clientAlias, X509Certificate[] certificateChain, PrivateKey privateKey) {
            mClientAlias = clientAlias;
            mCertificateChain = certificateChain;
            mPrivateKey = privateKey;
        }


        @OverridepublicStringchooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) {
            return mClientAlias;
        }

        @Overridepublic X509Certificate[] getCertificateChain(String alias) {
            return mCertificateChain;
        }

        @OverridepublicPrivateKeygetPrivateKey(String alias) {
            return mPrivateKey;
        }

         @Overridepublic final StringchooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
            // not a client SSLSocket callbackthrownewUnsupportedOperationException();
        }

        @Overridepublic final String[] getClientAliases(String keyType, Principal[] issuers) {
            // not a client SSLSocket callbackthrownewUnsupportedOperationException();
        }

        @Overridepublic final String[] getServerAliases(String keyType, Principal[] issuers) {
            // not a client SSLSocket callbackthrownewUnsupportedOperationException();
        }
    }
}

I did not test it. Report any error!

Solution 2:

If your URLs are still in development stage (not production version), you can skip those SSL/NON-SSL certificates installing to access the URLs.

Here is how to skip SSL validation : Call when activity onCreate() or where your need before accessing URL.

publicstaticvoidskipSSLValidation() {
        try {
            TrustManager[] trustAllCerts = newTrustManager[]{
                    newX509TrustManager() {
                        public X509Certificate[] getAcceptedIssuers() {
                    /* Create a new array with room for an additional trusted certificate. */returnnew X509Certificate[0];
                        }

                        @OverridepublicvoidcheckClientTrusted(X509Certificate[] certs, String authType) {
                        }

                        @OverridepublicvoidcheckServerTrusted(X509Certificate[] certs, String authType) {
                        }
                    }
            };

            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, trustAllCerts, newSecureRandom());
            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HttpsURLConnection.setDefaultHostnameVerifier(newHostnameVerifier() {
                @Overridepublicbooleanverify(String arg0, SSLSession arg1) {
                    returntrue;
                }
            });
        } catch (Exception e) {
            // pass
        }
    }

Note : If your HTTPS URLs are valid, you will no require to use server-generated certificates. You should using this method for testing/development only. For release/production you don't have to use this method.

Post a Comment for "Request With Automatic Or User Selection Of Appropriate Client Certificate"