Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 + client certificate is installed.

On Chrome browser I get the following dialog to choose the corresponding client certificate for the Web connection.

Choose certificate on Chrome

With the cordova plugin cordova-client-cert-authentication the same dialog pops up for Http(s) requests within the WebView.

My question is how to achieve a automatic certificate selection on Http(s) requests on the native Android platform without explicitly declaring the corresponding client certificate. Or is there something similiar to the user selection of certificate like implemented on Chrome?

This is the current implementation, which throws a handshake exception:

try {
    URL url = new URL( versionUrl );
    HttpsURLConnection urlConnection = ( HttpsURLConnection ) url.openConnection();

    urlConnection.setConnectTimeout( 10000 );

    InputStream in = urlConnection.getInputStream();
}
catch(Exception e)
{
    //javax.net.ssl.SSLHandshakeException: Handshake failed
}
like image 260
kerosene Avatar asked Jul 05 '17 14:07

kerosene


People also ask

How do I get a client authentication certificate?

Create a client certificate request. After receiving the certificate, export it to a password-protected PKCS12 file and send the password and the file to the user. Make sure the file is securely sent.

What does it mean when a website requires a client certificate?

Client or User Identity And the reason to see why is simple – client certificates play a vital role in ensuring people are safe on line. As the name indicates, they are used to identify a client or a user, authenticating the client to the server and establishing precisely who they are.

How do I issue a certificate to a client?

There are CAs that will happily issue client certificates just by validating an email address. Generally, certificates are issued for signing emails, encrypting emails, and identifying a client. In all three cases, you basically just want to associate a key with a person identified by their email address.


1 Answers

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, // Callback
            new String[] {"RSA", "DSA"}, // Any key types.
            null, // Any issuers.
            null, // Any host
            -1, // Any port
            DEFAULT_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 KeyChain
KeyManager keyManager = KeyChainKeyManager.fromAlias(
            context, mClientCertAlias);

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


//Perform the connection
URL url = new URL( versionUrl );
HttpsURLConnection urlConnection = ( HttpsURLConnection ) url.openConnection();
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
//urlConnection.setHostnameVerifier(hostnameVerifier);  //Configure hostnameVerifier if needed
urlConnection.setConnectTimeout( 10000 );
InputStream in = 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.

public static class KeyChainKeyManager extends X509ExtendedKeyManager {
    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.
         */
        public static KeyChainKeyManager fromAlias(Context context, String alias)
                throws CertificateException {
            X509Certificate[] certificateChain;
            try {
                certificateChain = KeyChain.getCertificateChain(context, alias);
            } catch (KeyChainException e) {
                throw new CertificateException(e);
            } catch (InterruptedException e) {
                throw new CertificateException(e);
            }

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

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

            return new KeyChainKeyManager(alias, certificateChain, privateKey);
        }

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


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

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

        @Override
        public PrivateKey getPrivateKey(String alias) {
            return mPrivateKey;
        }

         @Override
        public final String chooseServerAlias( String keyType, Principal[] issuers, Socket socket) {
            // not a client SSLSocket callback
            throw new UnsupportedOperationException();
        }

        @Override
        public final String[] getClientAliases(String keyType, Principal[] issuers) {
            // not a client SSLSocket callback
            throw new UnsupportedOperationException();
        }

        @Override
        public final String[] getServerAliases(String keyType, Principal[] issuers) {
            // not a client SSLSocket callback
            throw new UnsupportedOperationException();
        }
    }
}

I did not test it. Report any error!

like image 192
pedrofb Avatar answered Oct 14 '22 10:10

pedrofb