Webview избегайте предупреждения безопасности от google play при реализации onReceivedSslError

у меня есть ссылка, которая откроется в WebView. Проблема его нельзя открыть, пока я не переопределить onReceivedSslError такой:

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

Я получаю предупреждение о безопасности от google play, говоря:

предупреждение системы безопасности Ваше приложение имеет небезопасную реализацию WebViewClient.обработчик onReceivedSslError. В частности, реализация игнорирует все ошибки проверки сертификатов SSL, что делает ваше приложение уязвимым для атак типа "человек в середине". - злоумышленник может изменить содержимое WebView, прочитать переданные данные (например, учетные данные для входа) и выполнить код внутри приложения с помощью JavaScript.

чтобы правильно обработать проверку сертификата SSL, измените код для вызова SslErrorHandler.продолжить () всякий раз, когда сертификат, представленный сервером, соответствует вашим ожиданиям, и вызвать SslErrorHandler.cancel() в противном случае. Уведомление по электронной почте, содержащее затронутые приложения и классы, было отправлено в учетную запись разработчика адрес.

пожалуйста, устраните эту уязвимость как можно скорее и увеличьте номер версии обновленного APK. Дополнительные сведения об обработчике ошибок SSL см. В документации в справочном центре для разработчиков. Для других технических вопросов, вы можете опубликовать вhttps://www.stackoverflow.com/questions и используйте теги "android-security" и " SslErrorHandler."Если вы используете стороннюю библиотеку, которая несет ответственность за это, пожалуйста, сообщите 3rd партия и работа с ними для решения этой проблемы.

чтобы подтвердить правильность обновления, загрузите обновленную версию в консоль разработчика и проверьте ее через пять часов. Если приложение не было правильно обновлено, мы отобразим предупреждение.

обратите внимание, что хотя эти конкретные проблемы не могут повлиять на каждое приложение, использующее WebView SSL, лучше оставаться в курсе всех исправлений безопасности. Приложения с уязвимостями, которые подвергают пользователей риску компромисса могут считаться опасными продуктами в нарушение политики содержания и раздела 4.4 соглашения о дистрибуции разработчика.

убедитесь, что все опубликованные приложения соответствуют Соглашению о распространении и политике контента разработчика. Если у вас есть вопросы или проблемы, обратитесь в нашу службу поддержки через справочный центр Google Play Developer.

Если я удалить onReceivedSslError (handler.proceed()), то страница не открывается.

есть в любом случае я могу открыть страница в webview и избегайте предупреждения безопасности.

7 ответов


чтобы правильно обработать проверку сертификата SSL, измените код на вызовите SslErrorHandler.продолжить() всякий раз, когда сертификат сервер отвечает вашим ожиданиям и вызывает SslErrorHandler.cancel() в противном случае.

как сказал email,onReceivedSslError должен обрабатывать пользователь переходит на страницу с недопустимым сертификатом, например, диалог уведомления. Вы не должны действовать напрямую.

например, я добавляю диалоговое окно предупреждения, чтобы пользователь подтвердил и, похоже, Google больше не показывает предупреждение.


@Override
public 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();
}

подробнее об электронной почте.

в частности, реализация игнорирует все проверки сертификатов SSL ошибки, что делает ваше приложение уязвимым для атак "человек в середине".

в письме говорится, что реализация по умолчанию проигнорировала важную проблему безопасности SSL. Поэтому нам нужно обработать его в нашем собственном приложении, которое использовало WebView. Уведомить Пользователя с помощью диалогового окна оповещения просто путь.


Fix, который работает для меня, просто отключает определена в AuthorizationWebViewClient. В этом случае handler.cancel будет вызываться в случае ошибки SSL. Однако он хорошо работает с одним диском SSL-сертификатов. Протестированы на Android 2.3.7, Андроид 5.1.


по данным Google Security Alert: небезопасная реализация интерфейса X509TrustManager, Google Play не будет поддерживать X509TrustManager с 11 июля 2016 года:

Привет Google Play Разработчик,

ваши приложения, перечисленные в конце этого письма, используют небезопасное реализация интерфейса X509TrustManager. В частности, реализация игнорирует все ошибки проверки сертификата SSL при установка HTTPS-соединения с удаленным хозяин, таким образом делая ваше приложение уязвимо для атак типа "человек в середине". Злоумышленник мог читать переданные данные (например, учетные данные для входа) и даже изменить данные передается по HTTPS-соединению. Если у вас более 20 пострадавших приложения в вашей учетной записи, пожалуйста, проверьте консоль разработчика для полного список.

чтобы правильно обработать проверку сертификата SSL, измените код в checkServerTrusted метод пользовательского интерфейса X509TrustManager для поднимаете CertificateException или IllegalArgumentException когда сертификат сервера не соответствует ожидания. Для технических вопросов вы можете опубликовать в Stack Overflow и используйте теги "android-security" и " TrustManager."

пожалуйста, решить эту проблему как можно скорее и увеличить номер версии обновленного APK. Начало 17 Мая 2016 Года, Google Play заблокирует публикацию любых новых приложений или обновлений, содержащих небезопасная реализация из X509TrustManager интерфейс.

чтобы подтвердить, что вы внесли правильные изменения, отправьте обновленную версию из вашего приложения на консоль разработчика и проверить обратно через пять часов. Если приложение не было правильно обновлено, мы отобразим предупреждение.

хотя эти конкретные проблемы не могут повлиять на каждое приложение с Реализация TrustManager, лучше не игнорировать сертификат SSL ошибка проверки. Приложения с уязвимостями, которые подвергают пользователей риску компрометирующими могут считаться опасные продукты в нарушение Политика контента и раздел 4.4 дистрибутива разработчика Соглашение.

...


вы можете использовать SslError для показа, некоторую информацию об ошибке этого сертифицированного, и вы можете написать в своем диалоговом окне строку типа error.

@Override
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    final SslErrorHandler handlerFinal;
    handlerFinal = handler;
    int mensaje ;
    switch(error.getPrimaryError()) {
        case SslError.SSL_DATE_INVALID:
            mensaje = R.string.notification_error_ssl_date_invalid;
            break;
        case SslError.SSL_EXPIRED:
            mensaje = R.string.notification_error_ssl_expired;
            break;
        case SslError.SSL_IDMISMATCH:
            mensaje = R.string.notification_error_ssl_idmismatch;
            break;
        case SslError.SSL_INVALID:
            mensaje = R.string.notification_error_ssl_invalid;
            break;
        case SslError.SSL_NOTYETVALID:
            mensaje = R.string.notification_error_ssl_not_yet_valid;
            break;
        case SslError.SSL_UNTRUSTED:
            mensaje = R.string.notification_error_ssl_untrusted;
            break;
        default:
            mensaje = R.string.notification_error_ssl_cert_invalid;
    }

    AppLogger.e("OnReceivedSslError handel.proceed()");

    View.OnClickListener acept = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            dialog.dismiss();
            handlerFinal.proceed();
        }
    };

    View.OnClickListener cancel = new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            dialog.dismiss();
            handlerFinal.cancel();
        }
    };

    View.OnClickListener listeners[] = {cancel, acept};
    dialog = UiUtils.showDialog2Buttons(activity, R.string.info, mensaje, R.string.popup_custom_cancelar, R.string.popup_custom_cancelar, listeners);    }

Мне нужно было проверить наш truststore, прежде чем показывать какое-либо сообщение пользователю, поэтому я сделал это:

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;
}

@Override
public 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();
    }
}
}

предлагаемые решения до сих пор просто обходят проверку безопасности, поэтому они небезопасны.

Я предлагаю внедрить сертификат(ы) в приложение, и когда происходит SslError, проверьте, что сертификат сервера соответствует одному из встроенных сертификатов.

Итак, вот шаги:

  1. получить сертификат с сайта.

    • откройте сайт на Safari
    • нажмите на значок замка рядом с название сайта
    • нажмите на показать удостоверение
    • перетащите сертификат в папку

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

  1. копия сертификата (.cer file) в папку res/raw вашего приложения

  2. в коде загрузите сертификат(ы), вызвав 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. при возникновении SslError убедитесь, что сертификат сервера соответствует одному встроенному сертификату. Обратите внимание, что напрямую сравнивать сертификаты невозможно, поэтому я использую SslCertificate.saveState, чтобы поместить данные сертификата в пакет, а затем я сравниваю все записи пакета.

    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);
        }
    
    
    });
    

в моей ситуации:эта ошибка произошла, когда мы пытаемся обновить apk загружен в Google Play store и получение ошибки SSL: Тогда я использовал следующий код

private class MyWebViewClient extends WebViewClient {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            view.loadUrl(url);
            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            try {
                progressDialog.dismiss();
            } catch (WindowManager.BadTokenException e) {
                e.printStackTrace();
            }
            super.onPageFinished(view, url);
        }

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

 final AlertDialog.Builder builder = new AlertDialog.Builder(PayNPayWebActivity.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();
        }
    }