4

From a result of security scan program, I have a need to restrict the Certificate Authorities the app trusts.

The scan result points out the line at webView.loadUrl("https://example.com/page");. I see how I can create a SslSocketFactory that uses my TrustManager, but I don't see an API in WebView that allows me to set that.

https://developer.android.com/training/articles/security-ssl.html#UnknownCa

What are some possible ways to achieve this?

  • I would avoid any "security scan program" that does not provide details of how to address problems that it reports. – CommonsWare Oct 31 '16 at 19:28
  • @CommonsWare The "Recommendation" section in the report has the exact same snippet of code in the link above and just suggests using Certificate pinning or custom keystore. Nothing other info than this. – GreenTeaIterator Oct 31 '16 at 19:34
  • AFAIK, neither of those are possible with `WebView`. – CommonsWare Oct 31 '16 at 19:38

2 Answers2

10

I think WebViewClient 's onReceivedSslError method will be a good entry point.

First of all, follow the exact same snippet from https://developer.android.com/training/articles/security-ssl.html#UnknownCa to prepare TrustManager.

    TrustManagerFactory tmf = null;

    private void initTrustStore() throws
            java.security.cert.CertificateException, FileNotFoundException,
            IOException, KeyStoreException, NoSuchAlgorithmException {

        // Create a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore trustedKeyStore = KeyStore.getInstance(keyStoreType);
        trustedKeyStore.load(null, null);

        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        InputStream caInput = new BufferedInputStream(
                    getResources().getAssets().open("ca.crt"));
            Certificate ca;
            try {
                ca = cf.generateCertificate(caInput);
                Log.d(TAG, "ca-root DN=" + ((X509Certificate) ca).getSubjectDN());
            }
            finally {
                caInput.close();
            }
            trustedKeyStore.setCertificateEntry("ca", ca);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(trustedKeyStore);

    }

Then, extends custom WebViewClient class, checking snippet from https://stackoverflow.com/a/6379434/1099884

private class CheckServerTrustedWebViewClient extends WebViewClient{
    public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
        Log.d(TAG, "onReceivedSslError");
        boolean passVerify = false;

        if(error.getPrimaryError() == SslError.SSL_UNTRUSTED){
            SslCertificate cert = error.getCertificate();
            String subjectDN = cert.getIssuedTo().getDName();
            Log.d(TAG, "subjectDN: "+subjectDN);
            try{
                Field f = cert.getClass().getDeclaredField("mX509Certificate");
                f.setAccessible(true);
                X509Certificate x509 = (X509Certificate)f.get(cert);

                X509Certificate[] chain = {x509};
                for (TrustManager trustManager: tmf.getTrustManagers()) {
                    if (trustManager instanceof X509TrustManager) {
                        X509TrustManager x509TrustManager = (X509TrustManager)trustManager;
                        try{
                            x509TrustManager.checkServerTrusted(chain, "generic");
                            passVerify = true;break;
                        }catch(Exception e){
                            Log.e(TAG, "verify trustManager failed", e);
                            passVerify = false;
                        }
                    }
                }
                Log.d(TAG, "passVerify: "+passVerify);
            }catch(Exception e){
                Log.e(TAG, "verify cert fail", e);
            }
        }
        if(passVerify == true)handler.proceed();
        else handler.cancel();

    }       

}

Finally, set the CheckServerTrustedWebViewClient to WebView

webView.setWebViewClient(new CheckServerTrustedWebViewClient());

However, there is one problem. The prepared CA certificate is the exact one sign the server one (intermediate-CA NOT root CA). Only provide root CA certificate will not work. Isn't TrustManager can download server certificate chain on runtime? Any suggestion?

Community
  • 1
  • 1
Yeung
  • 2,202
  • 2
  • 27
  • 50
  • `Identity equality for arguments of types Int and Int is deprecated` (error!!.primaryError === SslError.SSL_UNTRUSTED) – Iman Marashi Nov 08 '21 at 14:17
2

The doc seems to be updated:

Fortunately, you can teach your application to trust custom CAs by configuring your application's Network Security Config, without needing to modify the code inside your application.

blog post I think to be related

Chris Chen
  • 121
  • 2