在 WebView loadUrl() 中信任证书颁发机构

Gre*_*tor 3 android ca ssl-certificate x509certificate android-webview

根据安全扫描程序的结果,我需要限制应用程序信任的证书颁发机构。

扫描结果指出 处的线webView.loadUrl("https://example.com/page");。我看到了如何创建一个使用我的 TrustManager 的 SslSocketFactory,但我在 WebView 中没有看到允许我设置它的 API。

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

有哪些可能的方法来实现这一目标?

Yeu*_*ung 7

我认为WebViewClientonReceivedSslError方法将是一个很好的切入点。

首先,按照https://developer.android.com/training/articles/security-ssl.html#UnknownCa 中完全相同的代码段来准备 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);

    }
Run Code Online (Sandbox Code Playgroud)

然后,扩展自定义 WebViewClient 类,检查来自/sf/answers/446560411/ 的片段

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();

    }       

}
Run Code Online (Sandbox Code Playgroud)

最后,设置CheckServerTrustedWebViewClientWebView

webView.setWebViewClient(new CheckServerTrustedWebViewClient());
Run Code Online (Sandbox Code Playgroud)

然而,有一个问题。准备好的 CA 证书是服务器的确切签名(中间 CA 不是根 CA)。只提供根 CA 证书是行不通的。TrustManager 不是可以在运行时下载服务器证书链吗?有什么建议吗?