Filip Ekberg
Filip Ekberg

Reputation: 36297

Does the Web View on Android support SSL?

The WebView control on android, does it support SSL?

I am trying to load a web page that uses a trusted ssl certificate but the WebView is just white.

Any suggestions?

Upvotes: 35

Views: 40383

Answers (5)

Tom Rutchik
Tom Rutchik

Reputation: 1702

A little variation on Gowski's solution:

                webView.setWebViewClient(new WebViewClient() {
                    @Override
                    public void onReceivedSslError (WebView view, final SslErrorHandler handler, SslError error) {

                        try {
                            X509Certificate cert[] = new X509Certificate[1];
                            boolean isTrusted = false;
                            if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) {
                                // for andoid sdk < version 29, the SslCertificate does not
                                // contain a public method for getting the x509Certificate. The
                                // following code is the trick used to get the x509Certificate
                                // from the SslCertificate
                                Bundle bundle = SslCertificate.saveState(error.getCertificate());
                                byte[] bytes = bundle.getByteArray("x509-certificate");
                                if (bytes == null) {
                                 } else {
                                    try {
                                        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
                                        cert[0] = (X509Certificate)certFactory.generateCertificate(new ByteArrayInputStream(bytes));
                                     } catch (CertificateException e) {
                                        cert[0] = null;
                                    }
                                }
                            } else {
                                // for andoid sdk >= version 29, the SslCertificate contains
                                // a method for getting the x509Certificate
                                cert[0] = error.getCertificate().getX509Certificate();
                            }
                            if (cert[0] != null) {
                                TrustManagerFactory tmfactory = null;
                                tmfactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                                tmfactory.init(CoreInternal.getTrustStore());
                                for (TrustManager trustManager : tmfactory.getTrustManagers()) {
                                    if (trustManager instanceof X509TrustManager) {
                                        try {
                                            ((X509TrustManager) trustManager).checkServerTrusted(cert, "RSA");
                                            isTrusted = true;
                                            break;
                                        } catch (CertificateException e) {
                                        }
                                    }
                                }
                                if (isTrusted) handler.proceed();
                                else {
                                    Core.getLog().e(TAG, activity.getString(R.string.errorUntrustedServer)
                                            + ": " + error.getUrl());
                                    handler.cancel();
                                }
                            }
                        } catch (NoSuchAlgorithmException | KeyStoreException e) {
                            handler.cancel();
                        }
                    }
                });

After getting the SSL error, this routine verifies the server certificate against my private trust store with is obtained by my method CoreInternal.getTrustStore() which is a Java keystore containing my applications private trusted certificates. If you want to use this code, you'll have to provide your own equivalent code for getting your own private keystore that you use to store your own personal trusted certificates. As you can see, for android sdk < version 29, you have to do a trick to get the x509 certificate from the SslCertificate that you obtain from "error". Beginning, in android sdk version 29, SslCertificate provided a public method for obtaining the x509certifacte from the SslCertificate. In this code, I aslo have a wrapper class around the Android Log class so if you want to use it, you need to modify that to meet your own requirements.

I'm providing this solution, because it wasn't clear to me from Gowski's code what keystore was being used to validate the certificate against. It doesn't make sense to try and validate against Androids internal trust store, because you're getting this error because the server certificate is not trusted using the system trust store. His code doesn't explain what mySslCertificate.getX509TrustManager() is doing as far as the trust store being used to validate against.

Upvotes: 1

Gowsik
Gowsik

Reputation: 1126

Google play rejected my app and then I did this...

 @Override
    public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {

        try {

            //Get the X509 trust manager from your ssl certificate
            X509TrustManager trustManager = mySslCertificate.getX509TrustManager();

            //Get the certificate from error object
            Bundle bundle = SslCertificate.saveState(error.getCertificate());
            X509Certificate x509Certificate;
            byte[] bytes = bundle.getByteArray("x509-certificate");
            if (bytes == null) {
                x509Certificate = null;
            } else {
                CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
                Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes));
                x509Certificate = (X509Certificate) cert;
            }
            X509Certificate[] x509Certificates = new X509Certificate[1];
            x509Certificates[0] = x509Certificate;

            // check weather the certificate is trusted
            trustManager.checkServerTrusted(x509Certificates, "ECDH_RSA");

            Log.e(TAG, "Certificate from " + error.getUrl() + " is trusted.");
            handler.proceed();
        } catch (Exception e) {
            Log.e(TAG, "Failed to access " + error.getUrl() + ". Error: " + error.getPrimaryError());
            final AlertDialog.Builder builder = new AlertDialog.Builder(WebViewActivity.this);
            String message = "SSL Certificate error.";
            switch (error.getPrimaryError()) {
                case SslError.SSL_UNTRUSTED:
                    message = "The certificate authority is not trusted.";
                    break;
                case SslError.SSL_EXPIRED:
                    message = "The certificate has expired.";
                    break;
                case SslError.SSL_IDMISMATCH:
                    message = "The certificate Hostname mismatch.";
                    break;
                case SslError.SSL_NOTYETVALID:
                    message = "The certificate is not yet valid.";
                    break;
            }
            message += " Do you want to continue anyway?";

            builder.setTitle("SSL Certificate Error");
            builder.setMessage(message);
            builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    handler.proceed();
                }
            });
            builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    handler.cancel();
                }
            });
            final AlertDialog dialog = builder.create();
            dialog.show();
        }
    }

After making the above changes Google play accepted my apk

And to generate your ssl trust manager pls check this answer

Upvotes: 8

Anant Shah
Anant Shah

Reputation: 4044

To properly handle SSL certificate validationoogle play according to updated Security Policy, Change your code to invoke SslErrorHandler.proceed() whenever the certificate presented by the server meets your expectations, and invoke SslErrorHandler.cancel() otherwise.

For example, I add an alert dialog to make user have confirmed and seems Google no longer shows warning.

    @Override
    public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {
    final AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());
    String message = "SSL Certificate error.";
        switch (error.getPrimaryError()) {
            case SslError.SSL_UNTRUSTED:
                message = "The certificate authority is not trusted.";
                break;
            case SslError.SSL_EXPIRED:
                message = "The certificate has expired.";
                break;
            case SslError.SSL_IDMISMATCH:
                message = "The certificate Hostname mismatch.";
                break;
            case SslError.SSL_NOTYETVALID:
                message = "The certificate is not yet valid.";
                break;
        }
        message += " Do you want to continue anyway?";

        builder.setTitle("SSL Certificate Error");
        builder.setMessage(message);
    builder.setPositiveButton("continue", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            handler.proceed();
        }
    });
    builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            handler.cancel();
        }
    });
    final AlertDialog dialog = builder.create();
    dialog.show();
}

After this changes it will not show warning.

Upvotes: 7

Golam Mawla Newton
Golam Mawla Newton

Reputation: 195

You have to enable webview setting to view SSL based website:

webView.getSetting().setDomStorageEnable(true);

Upvotes: -3

chris
chris

Reputation: 1184

Not an expert, just what i could find on the web. from what I understand, the WebView does indeed support ssl, however, the blank screen is an indication that the WebView does not believe that the certificate is valid. This may happen with a certificate that is self-signed or a from a root auth that is not set up in android (perfectly valid cert does not validate). In any case, if you are using froyo or better you can try something like:

import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.SslErrorHandler;
import android.net.http.SslError;

...

engine = (WebView) findViewById(R.id.my_webview);
engine.setWebViewClient(new WebViewClient() {

    @Override
    public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) {
        handler.proceed();
    }
});

Upvotes: 78

Related Questions