Request with automatic or user selection of appropriate client certificate

后端 未结 2 920
一生所求
一生所求 2021-01-02 14:49

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

相关标签:
2条回答
  • 2021-01-02 15:27

    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!

    0 讨论(0)
  • 2021-01-02 15:37

    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.

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

    0 讨论(0)
提交回复
热议问题