Webview avoid security alert from google play upon implementation of onReceivedSslError Webview avoid security alert from google play upon implementation of onReceivedSslError android android

Webview avoid security alert from google play upon implementation of onReceivedSslError


To properly handle SSL certificate validation, change your code to invoke SslErrorHandler.proceed() whenever the certificate presented by the server meets your expectations, and invoke SslErrorHandler.cancel() otherwise.

As email said, onReceivedSslError should handle user is going to a page with invalid cert, such like a notify dialog. You should not proceed it directly.

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


@Overridepublic void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {    final AlertDialog.Builder builder = new AlertDialog.Builder(this);    builder.setMessage(R.string.notification_error_ssl_cert_invalid);    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();}

More explain about the email.

Specifically, the implementation ignores all SSL certificate validation errors, making your app vulnerable to man-in-the-middle attacks.

The email says the default implement ignored an important SSL security problem. So we need to handle it in our own app which used WebView. Notify user with a alert dialog is a simple way.


I needed to check our truststore before show any message to the user so I did this:

public class MyWebViewClient extends WebViewClient {private static final String TAG = MyWebViewClient.class.getCanonicalName();Resources resources;Context context;public MyWebViewClient(Resources resources, Context context){    this.resources = resources;    this.context = context;}@Overridepublic void onReceivedSslError(WebView v, final SslErrorHandler handler, SslError er){    // first check certificate with our truststore    // if not trusted, show dialog to user    // if trusted, proceed    try {        TrustManagerFactory tmf = TrustManagerUtil.getTrustManagerFactory(resources);        for(TrustManager t: tmf.getTrustManagers()){            if (t instanceof X509TrustManager) {                X509TrustManager trustManager = (X509TrustManager) t;                Bundle bundle = SslCertificate.saveState(er.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;                trustManager.checkServerTrusted(x509Certificates, "ECDH_RSA");            }        }        Log.d(TAG, "Certificate from " + er.getUrl() + " is trusted.");        handler.proceed();    }catch(Exception e){        Log.d(TAG, "Failed to access " + er.getUrl() + ". Error: " + er.getPrimaryError());        final AlertDialog.Builder builder = new AlertDialog.Builder(context);        String message = "SSL Certificate error.";        switch (er.getPrimaryError()) {            case SslError.SSL_UNTRUSTED:                message = "O certificado não é confiável.";                break;            case SslError.SSL_EXPIRED:                message = "O certificado expirou.";                break;            case SslError.SSL_IDMISMATCH:                message = "Hostname inválido para o certificado.";                break;            case SslError.SSL_NOTYETVALID:                message = "O certificado é inválido.";                break;        }        message += " Deseja continuar mesmo assim?";        builder.setTitle("Erro");        builder.setMessage(message);        builder.setPositiveButton("Sim", new DialogInterface.OnClickListener() {            @Override            public void onClick(DialogInterface dialog, int which) {                handler.proceed();            }        });        builder.setNegativeButton("Não", new DialogInterface.OnClickListener() {            @Override            public void onClick(DialogInterface dialog, int which) {                handler.cancel();            }        });        final AlertDialog dialog = builder.create();        dialog.show();    }}}


The proposed solutions so far just bypass the security check, so they are not safe.

What I suggest is to embed the certificate(s) in the App, and when a SslError occurs, check that the server certificate matches one of the embedded certificates.

So here are the steps:

  1. Retrieve the certificate from the website.

    • Open the site on Safari
    • Click on the padlock icon near the website name
    • Click on Show Certificate
    • Drag and drop the certificate in a folder

see https://www.markbrilman.nl/2012/03/howto-save-a-certificate-via-safari-on-mac/

  1. Copy the certificate (.cer file) into the res/raw folder of your app

  2. In your code, load the certificate(s) by calling loadSSLCertificates()

    private static final int[] CERTIFICATES = {        R.raw.my_certificate,   // you can put several certificates};private ArrayList<SslCertificate> certificates = new ArrayList<>();private void loadSSLCertificates() {    try {        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");        for (int rawId : CERTIFICATES) {            InputStream inputStream = getResources().openRawResource(rawId);            InputStream certificateInput = new BufferedInputStream(inputStream);            try {                Certificate certificate = certificateFactory.generateCertificate(certificateInput);                if (certificate instanceof X509Certificate) {                    X509Certificate x509Certificate = (X509Certificate) certificate;                    SslCertificate sslCertificate = new SslCertificate(x509Certificate);                    certificates.add(sslCertificate);                } else {                    Log.w(TAG, "Wrong Certificate format: " + rawId);                }            } catch (CertificateException exception) {                Log.w(TAG, "Cannot read certificate: " + rawId);            } finally {                try {                    certificateInput.close();                    inputStream.close();                } catch (IOException e) {                    e.printStackTrace();                }            }        }    } catch (CertificateException e) {        e.printStackTrace();    }}
  3. When a SslError occurs, check that the server certificate matches one embedded certificate. Note that it is not possible to directly compare certificates, so I use SslCertificate.saveState to put the certificate data into a Bundle, and then I compare all the bundle entries.

    webView.setWebViewClient(new WebViewClient() {    @Override    public void onReceivedSslError(WebView view, final SslErrorHandler handler, SslError error) {        // Checks Embedded certificates        SslCertificate serverCertificate = error.getCertificate();        Bundle serverBundle = SslCertificate.saveState(serverCertificate);        for (SslCertificate appCertificate : certificates) {            if (TextUtils.equals(serverCertificate.toString(), appCertificate.toString())) { // First fast check                Bundle appBundle = SslCertificate.saveState(appCertificate);                Set<String> keySet = appBundle.keySet();                boolean matches = true;                for (String key : keySet) {                    Object serverObj = serverBundle.get(key);                    Object appObj = appBundle.get(key);                    if (serverObj instanceof byte[] && appObj instanceof byte[]) {     // key "x509-certificate"                        if (!Arrays.equals((byte[]) serverObj, (byte[]) appObj)) {                            matches = false;                            break;                        }                    } else if ((serverObj != null) && !serverObj.equals(appObj)) {                        matches = false;                        break;                    }                }                if (matches) {                    handler.proceed();                    return;                }            }        }        handler.cancel();        String message = "SSL Error " + error.getPrimaryError();        Log.w(TAG, message);    }});