Webview evita la alerta de seguridad de google play tras la implementación de onReceivedSslError

Tengo un enlace que se abrirá en la vista web. El problema es que no puede abrirse hasta que anule onReceivedSslError de esta manera:

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

Recibo una alerta de seguridad de google play que dice:

Alerta de seguridad Su aplicación tiene una implementación insegura del controlador WebViewClient.onReceivedSslError. Específicamente, la implementación ignora todos los errores de validación de certificados SSL, lo que hace que su aplicación sea vulnerable a los ataques man-in-the-middle. Un atacante podría cambiar el contenido del WebView afectado, leer los datos transmitidos (como las credenciales de inicio de sesión) y ejecutar código dentro de la aplicación usando JavaScript.

Para gestionar adecuadamente la validación de certificados SSL, cambie su código para invocar a SslErrorHandler.proceed () siempre que el certificado presentado por el servidor cumpla con sus expectativas e invoque a SslErrorHandler.cancel () en caso contrario. Se ha enviado una alerta por correo electrónico que contiene la (s) aplicación (es) afectada (s) y la (s) clase (es) a la dirección de su cuenta de desarrollador.

Solucione esta vulnerabilidad lo antes posible e incremente el número de versión de la APK actualizada. Para obtener más información sobre el manejador de errores de SSL, consulte nuestra documentación en el Centro de ayuda para desarrolladores. Para otras preguntas técnicas, puede publicar en https://www.stackoverflow.com/questions y usar las tags “android-security” y “SslErrorHandler”. Si está utilizando una biblioteca de terceros responsable de esto, notifique a la Tercero y trabaje con ellos para abordar el problema.

Para confirmar que se ha actualizado correctamente, suba la versión actualizada a Developer Console y vuelva a consultarla después de cinco horas. Si la aplicación no se ha actualizado correctamente, mostraremos una advertencia.

Tenga en cuenta que, si bien estos problemas específicos pueden no afectar a todas las aplicaciones que usan SSL de WebView, es mejor mantenerse al día con todos los parches de seguridad. Las aplicaciones con vulnerabilidades que exponen a los usuarios al riesgo de compromiso pueden considerarse productos peligrosos en violación de la Política de contenido y la sección 4.4 del Acuerdo de distribución para desarrolladores.

Asegúrese de que todas las aplicaciones publicadas cumplan con el Acuerdo de distribución de progtwigdores y la Política de contenido. Si tiene preguntas o inquietudes, comuníquese con nuestro equipo de asistencia técnica a través del Centro de ayuda para progtwigdores de Google Play.

Si onReceivedSslError (handler.proceed()) , la página no se abrirá.

De todos modos, puedo abrir la página en la vista web y evitar la alerta de seguridad.

Para gestionar adecuadamente la validación de certificados SSL, cambie su código para invocar a SslErrorHandler.proceed () siempre que el certificado presentado por el servidor cumpla con sus expectativas e invoque a SslErrorHandler.cancel () en caso contrario.

Como dijo el correo electrónico, onReceivedSslError debería manejar que el usuario vaya a una página con un certificado inválido, como un diálogo de notificación. No deberías proceder directamente.

Por ejemplo, agregué un cuadro de diálogo de alerta para que el usuario haya confirmado y parece que Google ya no muestra ninguna advertencia.


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

Más explicaciones sobre el correo electrónico.

Específicamente, la implementación ignora todos los errores de validación de certificados SSL, lo que hace que su aplicación sea vulnerable a los ataques man-in-the-middle.

El correo electrónico dice que el implemento predeterminado ignoró un importante problema de seguridad SSL. Entonces, debemos manejarlo en nuestra propia aplicación que usó WebView. Notificar al usuario con un diálogo de alerta es una manera simple.

La solución que funciona para mí es simplemente desactivar la función onReceivedSslError definida en AuthorizationWebViewClient . En este caso, se handler.cancel a handler.cancel en caso de error de SSL. Sin embargo, funciona bien con los certificados de One Drive SSL. Probado en Android 2.3.7, Android 5.1.

Según Google Security Alert: implementación insegura de la interfaz X509TrustManager , Google Play no admitirá X509TrustManager partir del 11 de julio de 2016:

Hola desarrollador de Google Play,

Su (s) aplicación (es) listadas al final de este correo electrónico usan una implementación insegura de la interfaz X509TrustManager. Específicamente, la implementación ignora todos los errores de validación de certificados SSL al establecer una conexión HTTPS a un host remoto, lo que hace que su aplicación sea vulnerable a los ataques man-in-the-middle. Un atacante podría leer los datos transmitidos (como las credenciales de inicio de sesión) e incluso cambiar los datos transmitidos en la conexión HTTPS. Si tiene más de 20 aplicaciones afectadas en su cuenta, consulte la Consola de desarrollador para obtener una lista completa.

Para manejar adecuadamente la validación de certificados SSL, cambie su código en el método checkServerTrusted de su interfaz X509TrustManager personalizada para generar CertificateException o IllegalArgumentException cada vez que el certificado presentado por el servidor no cumpla con sus expectativas. Para preguntas técnicas, puede publicar en Stack Overflow y usar las tags “android-security” y “TrustManager”.

Responda este problema lo antes posible e incremente el número de versión de la APK actualizada. A partir del 17 de mayo de 2016, Google Play bloqueará la publicación de cualquier aplicación o actualización nueva que contenga la implementación insegura de la interfaz X509TrustManager.

Para confirmar que realizó los cambios correctos, envíe la versión actualizada de su aplicación a la Consola de desarrollador y vuelva a consultarla después de cinco horas. Si la aplicación no se ha actualizado correctamente, mostraremos una advertencia.

Si bien estos problemas específicos pueden no afectar a todas las aplicaciones con la implementación de TrustManager, es mejor no ignorar los errores de validación de certificados SSL. Las aplicaciones con vulnerabilidades que exponen a los usuarios al riesgo de compromiso pueden considerarse productos peligrosos en violación de la Política de contenido y la sección 4.4 del Acuerdo de distribución para desarrolladores.

Puede usar SslError para mostrar, alguna información sobre el error de este certificado, y puede escribir en su diálogo la cadena del tipo de 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); } 

Necesitaba consultar nuestro almacén de confianza antes de mostrarle cualquier mensaje al usuario, así que hice esto:

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

Las soluciones propuestas hasta el momento solo pasan por alto el control de seguridad, por lo que no son seguros.

Lo que sugiero es incrustar los certificados en la aplicación, y cuando se produce un error SslError, compruebe que el certificado del servidor coincida con uno de los certificados incrustados.

Así que aquí están los pasos:

  1. Recupere el certificado del sitio web.

    • Abra el sitio en Safari
    • Haga clic en el ícono del candado cerca del nombre del sitio web
    • Haga clic en Mostrar certificado
    • Arrastra y suelta el certificado en una carpeta

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

  1. Copie el certificado (archivo .cer) en la carpeta res / raw de su aplicación

  2. En su código, cargue los certificados llamando a loadSSLCertificates ()

     private static final int[] CERTIFICATES = { R.raw.my_certificate, // you can put several certificates }; private ArrayList 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. Cuando se produce un SslError, compruebe que el certificado del servidor coincida con un certificado incrustado. Tenga en cuenta que no es posible comparar directamente los certificados, por lo que utilizo SslCertificate.saveState para poner los datos del certificado en un paquete, y luego comparo todas las entradas del paquete.

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

En mi situación: este error se produjo cuando intentamos actualizar la aplicación cargada en la tienda de Google Play y obtener un error de SSL: luego usé el siguiente código

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