Request with automatic or user selection of the corresponding client certificate

I am developing a cordova hybrid application that can connect to various servers. Some of them require a client certificate.

The corresponding root certificate certificate is installed on the Android mobile phone.

In the Chrome browser, I get the following dialog box to select the appropriate client certificate for the web connection.

Choose certificate on Chrome

With the cordova plugin cordova -client-cert-authentication plugin, the same dialog appears for Http (s) requests in WebView.

My question is how to achieve the selection of an automatic certificate for Http (s) requests on the platform on the native Android platform without explicitly declaring the corresponding client certificate. Or is there something similar to choosing a certificate user , as implemented in Chrome?

This is the current implementation that 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 } 
+5
source share
2 answers

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

The certificate is referenced by the alias you need. To request the user to select with a dialog similar to using chrome:

 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 for setting up an SSL connection using a custom KeyManager . It uses the default values โ€‹โ€‹of TrustManager and HostnameVerifier . You will need to configure them if the server uses a self-signed certificate that is not in the default supermarket for Android (it is not recommended to trust all certificates)

 //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, you have the full implementation of the custom X509ExtendedKeyManager extracted from here and here , which is responsible for selecting the client certificate. I 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 have not tested. Report any error!

+3
source

If your URLs are still under development (non-production version), you can skip these SSL / NON-SSL certificates to access the URLs.

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

 public static void skipSSLValidation() { try { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { public X509Certificate[] getAcceptedIssuers() { /* Create a new array with room for an additional trusted certificate. */ return new X509Certificate[0]; } @Override public void checkClientTrusted(X509Certificate[] certs, String authType) { } @Override public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String arg0, SSLSession arg1) { return true; } }); } catch (Exception e) { // pass } } 

Note. If your HTTPS URLs are valid, you wonโ€™t need to use server certificates. You should use this method for testing / development only. For release / production you do not need to use this method.

-1
source

Source: https://habr.com/ru/post/1269527/


All Articles