web-dev-qa-db-fra.com

Comment puis-je faire Android Volley effectuer une requête HTTPS, en utilisant un certificat auto-signé par une autorité de certification inconnue?

Avant de poser la question, j'ai trouvé quelques liens, que j'ai vérifiés, un par un, et aucun d'eux, ne me donne une solution:

Le seul lien que j'ai trouvé jusqu'à présent est celui-ci, qui donne deux approches: Faire une requête HTTPS en utilisant Android Volley

  • 1º Indique d'importer certaines classes dans votre application, alors qu'en effet, il y a une autre classe qui doit être importée, et les classes utilisent des bibliothèques obsolètes de "Apache.org"
  • 2º Un exemple pour NUKE tous les certificats SSL (assez mauvaise idée ...)


J'ai aussi trouvé ce blog, qui a beaucoup d'explications, mais à la fin, j'ai réalisé que les exemples utilisent des bibliothèques obsolètes de "Apache.org" et aussi, le blog lui-même n'a pas de contenu pour Android Volley. https://nelenkov.blogspot.mx/2011/12/using-custom-certificate-trust-store-on.html


Il y a aussi ce lien de Android et le code de la section "Autorité de certification inconnue", qui donne une bonne idée de la solution, mais le code lui-même, manque de quelque chose dans son structure (plainte d'Android Studio ...): https://developer.Android.com/training/articles/security-ssl.html

Mais cette citation du lien, semble le concept de base pour résoudre le problème.

"Un TrustManager est ce que le système utilise pour valider les certificats du serveur et, en en créant un à partir d'un magasin de clés avec une ou plusieurs autorités de certification, ce seront les seules autorités de certification approuvées par ce TrustManager. Étant donné le nouveau TrustManager, l'exemple initialise un nouveau SSLContext qui fournit un SSLSocketFactory que vous pouvez utiliser pour remplacer le SSLSocketFactory par défaut de HttpsURLConnection. De cette façon, la connexion utilisera vos autorités de certification pour la validation de certificat. "



Et maintenant, voici mon problème: J'ai un serveur web qui utilise un certificat auto-signé et j'ai créé un "BKS truststore" basé sur son certificat . J'ai importé le magasin de clés de confiance de BKS dans mon Android APP et maintenant, j'ai le code suivant sur mon application (je poste juste ici le MainActivity, qui est la seule classe qui est pertinente pour cela). sujet jusqu'à présent, je suppose):

package com.domain.myapp;


import Android.content.Context;
import Android.content.Intent;
import Android.os.Bundle;
import Android.support.v7.app.AppCompatActivity;
import Android.util.Log;
import Android.view.View;

import Android.widget.EditText;
import Android.widget.Toast;

import com.Android.volley.Request;
import com.Android.volley.RequestQueue;
import com.Android.volley.Response;
import com.Android.volley.VolleyError;
import com.Android.volley.toolbox.HurlStack;
import com.Android.volley.toolbox.StringRequest;
import com.Android.volley.toolbox.Volley;

import Java.io.InputStream;
import Java.security.KeyStore;
import Java.util.HashMap;
import Java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;


public class LoginScreen extends AppCompatActivity {

Context ctx          = null;
InputStream inStream = null;
HurlStack hurlStack  = null;

EditText username    = null;
EditText password    = null;
String loginStatus   = null;

public LoginScreen() {

    try {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        KeyStore ks = KeyStore.getInstance("BKS");
        inStream = ctx.getApplicationContext().getResources().openRawResource(R.raw.mytruststore);
        ks.load(inStream, null);
        inStream.close();
        tmf.init(ks);
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);
        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        hurlStack = new HurlStack(null, sslSocketFactory);
    } catch (Exception e){
        Log.d("Exception:",e.toString());
    }
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login_screen);
    username = (EditText) findViewById(R.id.user);
    password = (EditText) findViewById(R.id.passwd);
}

public void login(View view) {

    RequestQueue queue = Volley.newRequestQueue(this, hurlStack);
    final String url = "https://myserver.domain.com/app/login";

    StringRequest postRequest = new StringRequest(Request.Method.POST, url,
            new Response.Listener<String>()
            {
                @Override
                public void onResponse(String response) {
                    Log.d("Response", response);
                    loginStatus = "OK";
                }
            },
            new Response.ErrorListener()
            {
                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.d("Error.Response", String.valueOf(error));
                    loginStatus = "NOK";
                }
            }
    ) {
        @Override
        protected Map<String, String> getParams()
        {
            Map<String, String>  params = new HashMap<String, String>();
            params.put("username", String.valueOf(user));
            params.put("domain", String.valueOf(passwd));

            return params;
        }
    };
    queue.add(postRequest);

    if (loginStatus == "OK") {
        Intent intent = new Intent(LoginScreen.this, OptionScreen.class);
        startActivity(intent);
    } else {
        Toast.makeText(getApplicationContext(), "Login failed",Toast.LENGTH_SHORT).show();
    }
}

}

En ce qui concerne la classe constructeur, j'ai pris la liberté de copier le code, en mettant quelques commentaires sur ce que je comprends de chaque partie:

try {
// I have a TrustManagerFactory object
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
// I have a KeyStore considering BKS (BOUNCY CASTLE) KeyStore object
KeyStore ks = KeyStore.getInstance("BKS");
// I have configured a inputStream using my TrustStore file as a Raw Resource
inStream = ctx.getApplicationContext().getResources().openRawResource(R.raw.mytruststore);
// I have loaded my Raw Resource into the KeyStore object
ks.load(inStream, null);
inStream.close();
// I have initialiazed my Trust Manager Factory, using my Key Store Object
tmf.init(ks);
// I have created a new SSL Context object
SSLContext sslContext = SSLContext.getInstance("TLS");
// I have initialized my new SSL Context, with the configured Trust Managers found on my Trust Store
sslContext.init(null, tmf.getTrustManagers(), null);
// I have configured a HttpClientStack, using my brand new Socket Context
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
hurlStack = new HurlStack(null, sslSocketFactory);
} catch (Exception e){
Log.d("Exception:",e.toString());
}

Après cela, dans une autre méthode de classe, j'ai le RequestQueue, en utilisant le HttpClientStack que j'ai configuré sur le Class COnstructor:

RequestQueue queue = Volley.newRequestQueue(this, hurlStack);
final String url = "https://myserver.domain.com/app/login";
StringRequest postRequest = new StringRequest(Request.Method.POST, url,new Response.Listener<String>()
    {
    ...
    ...
    }

Lorsque j'exécute mon application, en donnant l'utilisateur et le mot de passe attendus par mon WebServer, je peux voir dans le Android Monitor from Android Studio) les messages suivants:

09-17 21: 57: 13.842 20617-20617/com.domain.myapp D/Error.Response: com.Android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: Java.security.cert.CertPathValidatorException: ancre de confiance pour chemin de certification introuvable.

Après toutes ces explications, j'ai la question suivante:

  • Quoi d'autre doit être configuré pour que Android accepte le certificat SSL de l'autorité de certification du TrustManager personnalisé que j'ai configuré chez le constructeur de la classe?

Pardonnez-moi, mais je suis débutant sur la programmation Android, ainsi que sur Java, alors peut-être que je fais une terrible erreur ...

Toute aide serait très appréciée.

[~ # ~] mise à jour [~ # ~]

J'ai amélioré le constructeur de la classe, en faisant un meilleur regroupement des instructions et en utilisant également le KeyManagerFactory , ce qui semble être assez important dans ce processus. Voici:

public class LoginScreen extends AppCompatActivity {

...
...

  public LoginScreen() {

    try {
        inStream = this.getApplicationContext().getResources().openRawResource(R.raw.mytruststore);

        KeyStore ks = KeyStore.getInstance("BKS");
        ks.load(inStream, "bks*password".toCharArray());
        inStream.close();

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("X509");
        kmf.init(ks, "bks*password".toCharArray());

        TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
        tmf.init(ks);

        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(kmf.getKeyManagers(),tmf.getTrustManagers(), null);
        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
        hurlStack = new HurlStack(null, sslSocketFactory);
    } catch (Exception e){
        Log.d("Exception:",e.toString());
    }

  }

...
...

}

Quoi qu'il en soit, j'ai toujours des problèmes ..

Réponse: com.Android.volley.NoConnectionError: javax.net.ssl.SSLHandshakeException: Java.security.cert.CertPathValidatorException: ancre de confiance pour le chemin de certification introuvable.

14
codermx

j'ai implémenté https en créant une nouvelle requestQueue dans ma classe de volley par le code suivant

public RequestQueue getRequestQueue() {
    if (mRequestQueue == null) {
        mRequestQueue = Volley.newRequestQueue(getApplicationContext(), new HurlStack(null, newSslSocketFactory()));

    }

    return mRequestQueue;
}

private SSLSocketFactory newSslSocketFactory() {
    try {
        // Get an instance of the Bouncy Castle KeyStore format
        KeyStore trusted = KeyStore.getInstance("BKS");
        // Get the raw resource, which contains the keystore with
        // your trusted certificates (root and any intermediate certs)
        InputStream in = getApplicationContext().getResources().openRawResource(R.raw.keystore);
        try {
            // Initialize the keystore with the provided trusted certificates
            // Provide the password of the keystore
            trusted.load(in, KEYSTORE_PASSWORD);
        } finally {
            in.close();
        }

        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(trusted);

        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        SSLSocketFactory sf = context.getSocketFactory();
        return sf;
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}
5
Assem Mahrous

J'ai rencontré un problème similaire dans le passé et la solution pour le même qui a fonctionné était d'installer l'autorité de certification intermédiaire sur le côté serveur .

Ce qui est intéressant à noter ici, c'est que la visite de ce serveur dans la plupart des navigateurs de bureau ne provoque pas une erreur comme une autorité de certification complètement inconnue ou un certificat de serveur auto-signé. En effet, la plupart des navigateurs de bureau mettent en cache les autorités de certification intermédiaires approuvées au fil du temps. Une fois qu'un navigateur a visité et appris l'existence d'une autorité de certification intermédiaire à partir d'un site, il n'est pas nécessaire que l'autorité de certification intermédiaire soit incluse dans la chaîne de certificats la prochaine fois.

Certains sites le font intentionnellement pour les serveurs Web secondaires utilisés pour servir les ressources. Par exemple, leur page HTML principale peut être servie par un serveur avec une chaîne de certificats complète, mais des serveurs pour des ressources telles que des images, CSS ou JavaScript n'incluent pas l'autorité de certification, vraisemblablement pour économiser la bande passante. Malheureusement, ces serveurs peuvent parfois fournir un service Web que vous essayez d'appeler à partir de votre application Android, ce qui n'est pas aussi indulgent).

Configurez le serveur pour inclure l'autorité de certification intermédiaire dans la chaîne de serveurs. La plupart des autorités de certification fournissent de la documentation sur la façon de procéder pour tous les serveurs Web courants. C'est la seule approche si vous avez besoin que le site fonctionne avec les navigateurs par défaut Android au moins via Android 4.2.

Vous pouvez suivre les étapes mentionnées ici autorité de certification intermédiaire manquante

Un autre exemple Qu'est-ce qu'un certificat intermédiaire?

FYI trust-anchor-not-found-for-Android-ssl-connection

Les navigateurs peuvent accepter l'autorité de certification racine mais Android SDK peut ne pas faire la même chose car les navigateurs mettent en cache la même chose. Les navigateurs mettent en cache certificats intermédiaires et les utiliser entre différents sites. De ce fait, si vous manquez le certificat intermédiaire, les utilisateurs aléatoires recevront une erreur de confiance, tandis que d'autres ne le feront pas. Les certificats intermédiaires sont-ils mis en cache dans Firefox? =

2
Anurag Singh