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?

  • JavaScript alert not working in Android WebView
  • Refresh images on FragmentStatePagerAdapter on resuming activity
  • how to create android linearlayout with RTL horizontal orientation
  • How do I import material design library to Android Studio?
  • AnalyticsService not registered in the app manifest - error
  • Is it possible to set a release date on the google play store?
  • Test a weak reference before using it java
  • Android SMS Content (content://sms/sent)
  • Android ImageView: Fit Width
  • Inheriting AppCompat 22.1.1 Dialog colorAccent from app theme doesn't work
  • How to Upload apk expansion files in new Developer console
  • Implicit intent to uninstall application?
  • 3 Solutions collect form web for “Check in the onReceivedSslError() method of a WebViewClient if a certificate is signed from a specific self-signed CA”

    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's screenshot

    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

    Android Babe is a Google Android Fan, All about Android Phones, Android Wear, Android Dev and Android Games Apps and so on.