34

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?

Filip Ekberg
  • 36,033
  • 20
  • 126
  • 183

5 Answers5

77

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();
    }
});
Piotr
  • 5,543
  • 1
  • 31
  • 37
chris
  • 1,172
  • 12
  • 15
  • hmmm, well, looking further I find that WebView does for sure support ssl. It has `getCertificate()`, `clearSslPreferences()` and `setCertificate(SslCertificate certificate)` methods for this. The problem, as I see it, is getting webkit to tell us what the problem is. in checking around, you might look into [this](http://developer.android.com/reference/android/webkit/SslErrorHandler.html) which is ssl error handling. – chris May 12 '11 at 13:48
  • @chris You say this works on froyo or better, which I am using, but both SslErrorHandler and SslError 'cannot be resolved to a type, and I cannot import anything... any suggestions? – RyanG Sep 09 '11 at 15:51
  • 2
    This post helped me out a lot! Just note you need to be developing for at least 2.2 and use the following includes for the Ssl stuff: import android.webkit.*; import android.net.http.*; – RyanG Sep 12 '11 at 00:43
  • first eclipse says me that onReceivedSslError is unused and it does change me nothing.. https site still not be loaded :( – Jayyrus Oct 08 '12 at 17:29
  • 2
    Thanks to your answer, Google had to check all the apps on Play which had the same exact implementation and send warning email to the developer :) – Ammar Oct 22 '15 at 18:59
  • 8
    To warn anyone actually using this in a production app, this will allow a MitM attack on your app. More info here: http://stanford.edu/~pcm2d/blog/ssl.html – Austyn Mahoney Jan 27 '16 at 21:47
  • 2
    Yes actually I was thinking that just bypass the SSL error is not a good way to do this, and will cause issues later. I should find a way to add the site certificate in root keystore of the phone, so it can be trusted. But on the other side, it seems to be overkilled to create a whole certificate store management just to display a HTTPS url in a WebView. Any help on this is appreciated :) – Alex Aug 26 '16 at 08:32
  • If you don't want to use it in production just check for BuildConfig.DEBUG. If true do workaround, if not call super. onReceivedSslError(...) – luckyhandler Aug 30 '16 at 14:30
  • 1
    If you do that, the app will be rejected by google play when release/update. Security alert Your app is using an unsafe implementation of the X509TrustManager interface with an Apache HTTP client, resulting in a security vulnerability. Please see this Google Help Center article for details, including the deadline for fixing the vulnerability. updating Android SystemWebView to v55 will fix some related issue. – Mahmoud May 14 '17 at 10:11
7

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.

petey
  • 16,914
  • 6
  • 65
  • 97
Anant Shah
  • 3,744
  • 1
  • 35
  • 48
7

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

Gowsik
  • 1,096
  • 1
  • 12
  • 25
0

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.

Tom Rutchik
  • 1,183
  • 1
  • 12
  • 15
-3

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

webView.getSetting().setDomStorageEnable(true);
Pang
  • 9,564
  • 146
  • 81
  • 122