172 votes

Acceptation des connexions HTTPS avec des certificats auto-signés

Je essaie de faire des connexions HTTPS, en utilisant la librairie HttpClient, mais le problème est que, puisque le certificat n'est pas signé par une Autorité de Certification (CA) reconnue comme Verisign,GlobalSIgn, etc., répertoriée dans l'ensemble des Certificats de Confiance Android, je reçois constamment l'erreur javax.net.ssl.SSLException: Certificat du serveur non fiable.

J'ai vu des solutions où vous acceptez simplement tous les certificats, mais que faire si je veux demander à l'utilisateur ?

Je veux obtenir une boîte de dialogue similaire à celle du navigateur, laissant à l'utilisateur le choix de continuer ou non. Idéalement, j'aimerais utiliser le même magasin de certificats que le navigateur. Des idées ?

0 votes

Cette solution acceptée a fonctionné pour moi- stackoverflow.com/questions/2642777/…

174voto

Nikolay Moskvin Points 772

La première chose que vous devez faire est de définir le niveau de vérification. Ces niveaux ne sont pas si nombreux:

  • ALLOW_ALL_HOSTNAME_VERIFIER
  • BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
  • STRICT_HOSTNAME_VERIFIER

Bien que la méthode setHostnameVerifier() soit obsolète pour la nouvelle bibliothèque Apache, elle est normale pour la version dans le SDK Android. Et donc nous prenons ALLOW_ALL_HOSTNAME_VERIFIER et nous le définissons dans la méthode factory SSLSocketFactory.setHostnameVerifier().

Ensuite, vous devez définir notre factory pour le protocole en https. Pour ce faire, il suffit d'appeler la méthode SchemeRegistry.register().

Ensuite, vous devez créer un DefaultHttpClient avec SingleClientConnManager. Aussi dans le code ci-dessous, vous pouvez voir que par défaut, notre indicateur sera également utilisé (ALLOW_ALL_HOSTNAME_VERIFIER) grâce à la méthode HttpsURLConnection.setDefaultHostnameVerifier()

Le code ci-dessous fonctionne pour moi:

HostnameVerifier hostnameVerifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

DefaultHttpClient client = new DefaultHttpClient();

SchemeRegistry registry = new SchemeRegistry();
SSLSocketFactory socketFactory = SSLSocketFactory.getSocketFactory();
socketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
registry.register(new Scheme("https", socketFactory, 443));
SingleClientConnManager mgr = new SingleClientConnManager(client.getParams(), registry);
DefaultHttpClient httpClient = new DefaultHttpClient(mgr, client.getParams());

// Set verifier     
HttpsURLConnection.setDefaultHostnameVerifier(hostnameVerifier);

// Exemple d'envoi de requête http
final String url = "https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);

6 votes

Je ne peux malheureusement pas faire fonctionner ce code, j'obtiens toujours le message "Certificat du serveur non fiable". Y a-t-il des autorisations supplémentaires que je dois définir pour que cela fonctionne ?

0 votes

Non, autorisation standard

0 votes

Cela ne fonctionne pas pour moi non plus. Je reçois toujours l'erreur "non approuvé". Pourriez-vous montrer le reste du code sur la façon de poster après tout cela. De cette façon, nous pouvons nous assurer que nous postons correctement? Merci

130voto

saxos Points 1494

Les étapes principales suivantes sont nécessaires pour établir une connexion sécurisée à partir des autorités de certification qui ne sont pas considérées comme fiables par la plateforme Android.

Comme demandé par de nombreux utilisateurs, j'ai reflété les parties les plus importantes de mon article de blog ici :

  1. Saisissez tous les certificats nécessaires (racine et tout certificat intermédiaire CA)
  2. Créez un keystore avec keytool et le fournisseur BouncyCastle et importez les certificats
  3. Chargez le keystore dans votre application Android et utilisez-le pour les connexions sécurisées (je recommande d'utiliser le Apache HttpClient au lieu du standard java.net.ssl.HttpsURLConnection (plus facile à comprendre, plus performant)

Saisir les certificats

Vous devez obtenir tous les certificats qui constituent une chaîne depuis le certificat de point de terminaison jusqu'à la CA racine. Cela signifie, tout certificat intermédiaire CA (s'ils existent) et aussi le certificat de la CA racine. Vous n'avez pas besoin d'obtenir le certificat de point de terminaison.

Créer le keystore

Téléchargez le fournisseur BouncyCastle et stockez-le dans un emplacement connu. Assurez-vous également que vous pouvez invoquer la commande keytool (généralement située sous le dossier bin de votre installation de JRE).

Importez maintenant les certificats obtenus (ne importez pas le certificat de point de terminaison) dans un keystore formaté BouncyCastle.

Je ne l'ai pas testé, mais je pense que l'ordre d'importation des certificats est important. Cela signifie, importez d'abord le certificat de l'intermédiaire CA le plus bas, puis remontez jusqu'au certificat de la CA racine.

Avec la commande suivante, un nouveau keystore (si ce n'est pas déjà le cas) avec le mot de passe mysecret sera créé et le certificat de l'intermédiaire CA sera importé. J'ai également défini le fournisseur BouncyCastle, où il peut être trouvé sur mon système de fichiers et le format du keystore. Exécutez cette commande pour chaque certificat de la chaîne.

keytool -importcert -v -trustcacerts -file "path_to_cert/interm_ca.cer" -alias IntermediateCA -keystore "res/raw/mykeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

Vérifiez si les certificats ont été importés correctement dans le keystore :

keytool -list -keystore "res/raw/mykeystore.bks" -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath "path_to_bouncycastle/bcprov-jdk16-145.jar" -storetype BKS -storepass mysecret

Devrait afficher toute la chaîne :

RootCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 24:77:D9:A8:91:D1:3B:FA:88:2D:C2:FF:F8:CD:33:93
IntermediateCA, 22.10.2010, trustedCertEntry, Thumbprint (MD5): 98:0F:C3:F8:39:F7:D8:05:07:02:0D:E3:14:5B:29:43

Maintenant vous pouvez copier le keystore en tant que ressource brute dans votre application Android sous res/raw/

Utiliser le keystore dans votre application

Tout d'abord, nous devons créer un HttpClient Apache personnalisé qui utilise notre keystore pour les connexions HTTPS :

import org.apache.http.*

public class MyHttpClient extends DefaultHttpClient {

    final Context context;

    public MyHttpClient(Context context) {
        this.context = context;
    }

    @Override
    protected ClientConnectionManager createClientConnectionManager() {
        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        // Enregistrer pour le port 443 notre SSLSocketFactory avec notre keystore
        // au ConnectionManager
        registry.register(new Scheme("https", newSslSocketFactory(), 443));
        return new SingleClientConnManager(getParams(), registry);
    }

    private SSLSocketFactory newSslSocketFactory() {
        try {
            // Obtenir une instance du format Bouncy Castle KeyStore
            KeyStore trusted = KeyStore.getInstance("BKS");
            // Obtenez la ressource brute, qui contient le keystore avec
            // vos certificats de confiance (certificats root et intermédiaires)
            InputStream in = context.getResources().openRawResource(R.raw.mykeystore);
            try {
                // Initialiser le keystore avec les certificats de confiance fournis
                // Fournir également le mot de passe du keystore
                trusted.load(in, "mysecret".toCharArray());
            } finally {
                in.close();
            }
            // Passez le keystore au SSLSocketFactory. L'usine est responsable
            // de la vérification du certificat du serveur.
            SSLSocketFactory sf = new SSLSocketFactory(trusted);
            // Vérification du nom d'hôte à partir du certificat
            // http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html#d4e506
            sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
            return sf;
        } catch (Exception e) {
            throw new AssertionError(e);
        }
    }
}

Nous avons créé notre HttpClient personnalisé, maintenant nous pouvons l'utiliser pour des connexions sécurisées. Par exemple lorsque nous effectuons un appel GET à une ressource REST :

// Instancier le HttpClient personnalisé
DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23");
// Exécuter l'appel GET et obtenir la réponse
HttpResponse getResponse = client.execute(get);
HttpEntity responseEntity = getResponse.getEntity();

C'est tout ;)

9 votes

Il est uniquement utile pour obtenir des certificats avant de mettre en ligne votre application. Cela n'aide pas vraiment les utilisateurs à accepter leurs propres certificats pour votre application.

0 votes

Salut à tous, est-ce que quelqu'un peut me dire le processus de validation pour le keystore avec le truststore pour l'implémentation ci-dessus??? merci d'avance.

0 votes

Cela a bien fonctionné.. mais maintenant j'ai un problème lorsque je reçois la clé du certificat sur le serveur. Il semble étrange qu'à chaque fois que je mets à jour le certificat sur mon serveur, le stockage côté client doit également être mis à jour. Il doit y avoir une meilleure façon :|

22voto

S.D. Points 12816

Si vous avez un certificat personnalisé/auto-signé sur le serveur qui n'est pas là sur l'appareil, vous pouvez utiliser la classe ci-dessous pour le charger et l'utiliser côté client dans Android :

Placez le fichier de certificat *.crt dans /res/raw afin qu'il soit disponible à partir de R.raw.*

Utilisez la classe ci-dessous pour obtenir un HTTPClient ou HttpsURLConnection qui aura une fabrique de socket utilisant ce certificat :

package com.example.customssl;

import android.content.Context;
import org.apache.http.client.HttpClient;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;

public class CustomCAHttpsProvider {

    /**
     * Crée un {@link org.apache.http.client.HttpClient} qui est configuré pour fonctionner avec un certificat d'autorité personnalisé.
     *
     * @param context       Contexte de l'application
     * @param certRawResId  R.raw.id du fichier de certificat (*.crt). Doit être stocké dans /res/raw.
     * @param allowAllHosts Si vrai, le client ne vérifiera pas le serveur par rapport aux noms d'hôte du certificat.
     * @return Client Http.
     * @throws Exception S'il y a une erreur d'initialisation du client.
     */
    public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception {

        // construire le dépôt de clés avec le certificat ca
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // initialiser la fabrique de socket SSL avec le dépôt de clés
        SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);

        // ignorer la vérification de sécurité du nom d'hôte si spécifiée
        if (allowAllHosts) {
            sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        // paramètres http de base pour le client
        HttpParams params = new BasicHttpParams();

        // registre de schéma normal avec notre fabrique de socket ssl pour "https"
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));

        // créer un gestionnaire de connexion
        ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

        // créer un client http
        return new DefaultHttpClient(cm, params);
    }

    /**
     * Crée un {@link javax.net.ssl.HttpsURLConnection} qui est configuré pour fonctionner avec un certificat d'autorité personnalisé.
     *
     * @param urlString     chaîne d'URL distante.
     * @param context       Contexte de l'application
     * @param certRawResId  R.raw.id du fichier de certificat (*.crt). Doit être stocké dans /res/raw.
     * @param allowAllHosts Si vrai, le client ne vérifiera pas le serveur par rapport aux noms d'hôte du certificat.
     * @return Connexion url http.
     * @throws Exception S'il y a une erreur d'initialisation de la connexion.
     */
    public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId,
                                                           boolean allowAllHosts) throws Exception {

        // construire le dépôt de clés avec le certificat ca
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // Crée un gestionnaire de confiance qui fait confiance aux AC de notre dépôt de clés
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        // Créer un SSLContext qui utilise notre gestionnaire de confiance
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);

        // Créer une connexion à partir de l'URL
        URL url = new URL(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());

        // ignorer la vérification de sécurité du nom d'hôte si spécifiée
        if (allowAllHosts) {
            urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        return urlConnection;
    }

    private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        // initialiser un dépôt de clés par défaut
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);

        // lire et ajouter l'autorité de certification
        Certificate cert = readCert(context, certRawResId);
        keyStore.setCertificateEntry("ca", cert);

        return keyStore;
    }

    private static Certificate readCert(Context context, int certResourceId) throws CertificateException, IOException {

        // lire la ressource de certificat
        InputStream caInput = context.getResources().openRawResource(certResourceId);

        Certificate ca;
        try {
            // générer un certificat
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            ca = cf.generateCertificate(caInput);
        } finally {
            caInput.close();
        }

        return ca;
    }

}

Points clés :

  1. Les objets Certificate sont générés à partir de fichiers .crt.
  2. Un KeyStore par défaut est créé.
  3. keyStore.setCertificateEntry("ca", cert) ajoute le certificat au dépôt de clés sous l'alias "ca". Vous pouvez modifier le code pour ajouter plus de certificats (CA intermédiaires, etc).
  4. L'objectif principal est de générer une SSLSocketFactory qui peut ensuite être utilisée par HTTPClient ou HttpsURLConnection.
  5. La SSLSocketFactory peut être configurée davantage, par exemple pour ignorer la vérification du nom d'hôte, etc.

Plus d'informations sur : http://developer.android.com/training/articles/security-ssl.html

0 votes

Où puis-je obtenir des fichiers .crt ? Télécharger depuis un serveur ?

0 votes

@zionpi Le fichier de certificat sera le même que celui utilisé par le serveur activé pour TLS auquel vous vous connectez.

0 votes

Merci! C'était tellement facile!

6voto

Markus Lenger Points 119

La première réponse n'a pas fonctionné pour moi. Après quelques recherches, j'ai trouvé les informations nécessaires sur "Android Developer" : https://developer.android.com/training/articles/security-ssl.html#SelfSigned

Créer une implémentation vide de X509TrustManager a fait l'affaire :

private static class MyTrustManager implements X509TrustManager
{

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType)
         throws CertificateException
    {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType)
        throws CertificateException
    {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers()
    {
        return null;
    }

}

...

HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
try
{
    // Créer un SSLContext qui utilise notre TrustManager
    SSLContext context = SSLContext.getInstance("TLS");
    TrustManager[] tmlist = {new MyTrustManager()};
    context.init(null, tmlist, null);
    conn.setSSLSocketFactory(context.getSocketFactory());
}
catch (NoSuchAlgorithmException e)
{
    throw new IOException(e);
} catch (KeyManagementException e)
{
    throw new IOException(e);
}
conn.setRequestMethod("GET");
int rcode = conn.getResponseCode();

Veuillez noter que cette implémentation vide de TrustManager n'est qu'un exemple et son utilisation dans un environnement de production pourrait causer une grave menace pour la sécurité !

1 votes

Juste pour information - je ne sais pas si c'était comme ça à l'époque, mais ils semblent fortement décourager cette approche maintenant (voir la note)

4voto

emmby Points 35359

Voici comment vous pouvez ajouter des certificats supplémentaires à votre KeyStore pour éviter ce problème : Faire confiance à tous les certificats en utilisant HttpClient via HTTPS

Cela ne demandera pas à l'utilisateur comme vous le demandez, mais cela rendra moins probable que l'utilisateur rencontre une erreur de "Certificat de serveur non approuvé".

0 votes

Juste à des fins de test, vous ne pouvez pas publier une application sur le Play Store avec ce truc car elle sera rejetée

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X