web-dev-qa-db-fra.com

Webview éviter les alertes de sécurité de Google Play lors de l'implémentation de onReceivedSslError

J'ai un lien qui va s'ouvrir dans webview. Le problème est qu'il ne peut pas être ouvert jusqu'à ce que je substitue onReceivedSslError comme ceci:

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

Je reçois une alerte de sécurité de Google Play disant:

Alerte de sécurité Votre application utilise une implémentation non sécurisée du gestionnaire WebViewClient.onReceivedSslError. Plus précisément, la mise en œuvre ignore toutes les erreurs de validation de certificat SSL, ce qui rend votre application vulnérable aux attaques de type "man-in-the-middle". Un attaquant pourrait modifier le contenu de WebView affecté, lire les données transmises (telles que les informations de connexion) et exécuter du code dans l'application à l'aide de JavaScript.

Pour gérer correctement la validation du certificat SSL, modifiez votre code pour invoquer SslErrorHandler.proceed () chaque fois que le certificat présenté par le serveur répond à vos attentes et appelez SslErrorHandler.cancel () sinon. Une alerte par courrier électronique contenant les applications et les classes concernées a été envoyée à l'adresse de votre compte de développeur.

Veuillez remédier à cette vulnérabilité dès que possible et incrémenter le numéro de version de l'APK mis à niveau. Pour plus d'informations sur le gestionnaire d'erreurs SSL, consultez notre documentation dans le centre d'aide pour les développeurs. Pour d’autres questions techniques, vous pouvez envoyer un message à https://www.stackoverflow.com/questions et utiliser les balises "Android-security" et "SslErrorHandler". Si vous utilisez une bibliothèque tierce responsable, veuillez en informer le tiers et travailler avec lui pour résoudre le problème.

Pour confirmer que vous avez mis à niveau correctement, téléchargez la version mise à jour sur la console pour développeur et revenez au bout de cinq heures. Si l'application n'a pas été correctement mise à niveau, nous afficherons un avertissement.

Notez que, même si ces problèmes spécifiques ne concernent pas toutes les applications utilisant WebView SSL, il est préférable de rester à jour sur tous les correctifs de sécurité. Les applications comportant des vulnérabilités exposant les utilisateurs à un risque de compromission peuvent être considérées comme des produits dangereux en violation de la politique de contenu et de la section 4.4 de l'accord de distribution pour les développeurs.

Assurez-vous que toutes les applications publiées sont conformes à l'accord de distribution pour les développeurs et à la politique de contenu. Si vous avez des questions ou des inquiétudes, veuillez contacter notre équipe d'assistance via le centre d'aide des développeurs Google Play.

Si je supprime onReceivedSslError (handler.proceed()), la page ne s'ouvre pas.

Est-il possible que je puisse ouvrir une page dans une vue Web et éviter une alerte de sécurité

56
captaindroid

Pour gérer correctement la validation du certificat SSL, modifiez votre code pour invoquer SslErrorHandler.proceed () chaque fois que le certificat présenté par le serveur répond à vos attentes et appelez SslErrorHandler.cancel () sinon.

Comme le dit l'email, onReceivedSslError devrait gérer que l'utilisateur se rend sur une page avec un certificat invalide, tel qu'un dialogue de notification. Vous ne devriez pas procéder directement.

Par exemple, j’ajoute une boîte de dialogue d’alerte pour que l’utilisateur soit confirmé et semble que Google n’affiche plus d’avertissement.


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

Plus d'expliquer sur l'e-mail.

Plus précisément, la mise en œuvre ignore toutes les erreurs de validation de certificat SSL, ce qui rend votre application vulnérable aux attaques de type "man-in-the-middle".

Le courrier électronique indique que l'implémentation par défaut ignorait un problème de sécurité SSL important. Nous devons donc le gérer dans notre propre application utilisant WebView. Avertir l'utilisateur avec un dialogue d'alerte est un moyen simple.

97
sakiM

Le correctif qui fonctionne pour moi est simplement de désactiver la fonction onReceivedSslError définie dans AuthorizationWebViewClient. Dans ce cas handler.cancel sera appelé en cas d'erreur SSL. Cependant, cela fonctionne bien avec les certificats SSL One Drive. Testé sur Android 2.3.7, Android 5.1.

6
Naveen Prince P

Selon alerte de sécurité Google: implémentation non sécurisée de l'interface X509TrustManager , Google Play ne prend pas en charge X509TrustManager à partir du 11 juillet 2016:

Bonjour développeur Google Play,

Votre ou vos applications répertoriées à la fin de cet e-mail utilisent une implémentation non sécurisée de l'interface X509TrustManager. Plus précisément, l'implémentation ignore toutes les erreurs de validation de certificat SSL lors de l'établissement d'une connexion HTTPS vers un hôte distant, ce qui rend votre application vulnérable aux attaques de type "man-in-the-middle". Un attaquant pourrait lire les données transmises (telles que les identifiants de connexion) et même modifier les données transmises sur la connexion HTTPS. Si vous avez plus de 20 applications concernées dans votre compte, consultez la console de développeur pour obtenir une liste complète.

Pour gérer correctement la validation du certificat SSL, modifiez votre code dans la méthode checkServerTrusted de votre interface X509TrustManager personnalisée pour qu'il lève soit CertificateException soit IllegalArgumentException lorsque le certificat présenté par le serveur ne répond pas à vos attentes. Pour les questions techniques, vous pouvez poster sur Stack Overflow et utiliser les balises "Android-security" et "TrustManager".

Veuillez traiter ce problème dès que possible et incrémenter le numéro de version de l'APK mis à niveau. À compter du 17 mai 2016, Google Play bloquera la publication de toute nouvelle application ou mise à jour contenant l'implémentation non sécurisée de l'interface X509TrustManager.

Pour confirmer que vous avez apporté les modifications correctes, envoyez la version mise à jour de votre application à la console développeur et revenez au bout de cinq heures. Si l'application n'a pas été correctement mise à niveau, nous afficherons un avertissement.

Bien que ces problèmes spécifiques n’affectent pas nécessairement toutes les applications avec la mise en œuvre TrustManager, il est préférable de ne pas ignorer les erreurs de validation du certificat SSL. Les applications comportant des vulnérabilités exposant les utilisateurs à un risque de compromission peuvent être considérées comme des produits dangereux en violation de la politique de contenu et de la section 4.4 de l'accord de distribution pour les développeurs.

...

5
sathish baddam

Les solutions proposées jusqu'à présent ne font que contourner le contrôle de sécurité, elles ne sont donc pas sûres.

Ce que je suggère est d'intégrer le (s) certificat (s) dans l'application, et lorsqu'une erreur SslError se produit, vérifiez que le certificat de serveur correspond à l'un des certificats incorporés.

Alors voici les étapes:

  1. Récupérez le certificat sur le site.

    • Ouvrez le site sur Safari
    • Cliquez sur l'icône du cadenas près du nom du site.
    • Cliquez sur Afficher le certificat
    • Glissez et déposez le certificat dans un dossier

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

  1. Copiez le certificat (fichier .cer) dans le dossier res/raw de votre application.

  2. Dans votre code, chargez le (s) certificat (s) en appelant 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. Lorsqu'une erreur SslError se produit, vérifiez que le certificat de serveur correspond à un certificat intégré. Notez qu'il n'est pas possible de comparer directement les certificats. J'utilise donc SslCertificate.saveState pour mettre les données de certificat dans un Bundle, puis je compare toutes les entrées de bundle.

    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);
        }
    
    
    });
    
4
Arnaud SmartFun

J'avais besoin de vérifier notre magasin de clés de confiance avant d'afficher tout message à l'utilisateur. J'ai donc fait ceci:

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

Vous pouvez utiliser SslError pour show, des informations sur l'erreur de ce certificat, et vous pouvez écrire dans votre boîte de dialogue la chaîne du type 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);    }
2
Pabel