简体   繁体   中英

Check in the onReceivedSslError() method of a WebViewClient if a certificate is signed from a specific self-signed CA

I would like to override the onReceivedSslError() of a WebViewClient . Here I want to check if the error.getCertificate() certificate is signed from a self-signed CA and, only in this case , call the handler.proceed() . In pseudo-code:

@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    SslCertificate serverCertificate = error.getCertificate();

    if (/* signed from my self-signed CA */) {
        handler.proceed();
    }
    else {
        super.onReceivedSslError(view, handler, error);
    }
}

The public key of my CA is saved in a BouncyCastle resource called rootca.bks . How can I do?

I think you can try as the following:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    try {
        WebView webView = (WebView) findViewById(R.id.webView);
        if (webView != null) {
            // Get cert from raw resource...
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            InputStream caInput = getResources().openRawResource(R.raw.rootca); // stored at \app\src\main\res\raw
            final Certificate certificate = cf.generateCertificate(caInput);
            caInput.close();

            String url = "https://www.yourserver.com";
            webView.setWebViewClient(new WebViewClient() {                    
                @Override
                public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                    // Get cert from SslError
                    SslCertificate sslCertificate = error.getCertificate();
                    Certificate cert = getX509Certificate(sslCertificate);
                    if (cert != null && certificate != null){
                        try {
                            // Reference: https://developer.android.com/reference/java/security/cert/Certificate.html#verify(java.security.PublicKey)
                            cert.verify(certificate.getPublicKey()); // Verify here...
                            handler.proceed();
                        } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) {
                            super.onReceivedSslError(view, handler, error);
                            e.printStackTrace();
                        }
                    } else {
                        super.onReceivedSslError(view, handler, error);
                    }
                }
            });

            webView.loadUrl(url);
        }
    } catch (Exception e){
        e.printStackTrace();
    }
}

// credits to @Heath Borders at http://stackoverflow.com/questions/20228800/how-do-i-validate-an-android-net-http-sslcertificate-with-an-x509trustmanager
private Certificate getX509Certificate(SslCertificate sslCertificate){
    Bundle bundle = SslCertificate.saveState(sslCertificate);
    byte[] bytes = bundle.getByteArray("x509-certificate");
    if (bytes == null) {
        return null;
    } else {
        try {
            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
            return certFactory.generateCertificate(new ByteArrayInputStream(bytes));
        } catch (CertificateException e) {
            return null;
        }
    }
}

If failed validation, logcat will have some information such as java.security.SignatureException: Signature was not verified...

If success, here's a screenshot:

BNK的截图

I think this should work ( SSL_IDMISMATCH means "Hostname mismatch").

@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    SslCertificate serverCertificate = error.getCertificate();

    if (error.hasError(SSL_UNTRUSTED)) {
        // Check if Cert-Domain equals the Uri-Domain
        String certDomain = serverCertificate.getIssuedTo().getCName();
        if(certDomain.equals(new URL(error.getUrl()).getHost())) {
          handler.proceed();
        }
    }
    else {
        super.onReceivedSslError(view, handler, error);
    }
}

If "hasError()" is not working, try error.getPrimaryError() == SSL_IDMISMATCH

Check Documentation of SslError for all error-types.

EDIT: I tested the function on my own self-cert server (its a Xampp), and I got Error #3. That means you have to check for error.hasError(SslError.SSL_UNTRUSTED) for a self-signed cert.

based on documentation:

Have you tried using the method getIssuedBy().getDName() of class SslCertificate. This method returns a String representing "The entity that issued this certificate".

Take a look here: http://developer.android.com/reference/android/net/http/SslCertificate.html#getIssuedBy()

Then you just need to know wich string is returned when it is self signed.

EDIT: I think that if it is selfsigned, that should return empty string, and if not, it would return the entity

Regards

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM