web-dev-qa-db-fra.com

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

J'essaie d'établir des connexions HTTPS en utilisant HttpClient lib, mais le problème est que, puisque le certificat n'est pas signé par une autorité de certification reconnue, comme Verisign , - GlobalSIgn , etc., répertorié dans l'ensemble de Android Certificats de confiance, je continue à recevoir javax.net.ssl.SSLException: Not trusted server certificate.

J'ai déjà vu des solutions où vous acceptez simplement tous les certificats, mais que se passe-t-il si je veux demander à l'utilisateur?

Je veux obtenir un dialogue similaire à celui du navigateur, laissant l'utilisateur décider de continuer ou non. De préférence, j'aimerais utiliser le même magasin de certificats que le navigateur. Des idées?

148
Morten

La première chose à faire est de définir le niveau de vérification. Ces niveaux ne sont pas tellement:

  • 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, mais pour la version sous Android, _ SDK est normal. Nous prenons donc ALLOW_ALL_HOSTNAME_VERIFIER et le configurons dans la fabrique de méthodes SSLSocketFactory.setHostnameVerifier().

Ensuite, vous devez configurer notre usine pour le protocole sur https. Pour cela, appelez simplement la méthode SchemeRegistry.register().

Ensuite, vous devez créer une DefaultHttpClient avec SingleClientConnManager. En outre, dans le code ci-dessous, vous pouvez voir que, par défaut, notre indicateur (ALLOW_ALL_HOSTNAME_VERIFIER) est également utilisé par 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);

// Example send http request
final String url = "https://encrypted.google.com/";
HttpPost httpPost = new HttpPost(url);
HttpResponse response = httpClient.execute(httpPost);
170
Nikolay Moskvin

Les étapes principales suivantes sont nécessaires pour établir une connexion sécurisée à partir d'autorités de certification qui ne sont pas considérées comme fiables par la plate-forme 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 requis (racine et toutes les autorités de certification intermédiaires)
  2. Créez un magasin de clés avec keytool et le fournisseur BouncyCastle et importez les certificats
  3. Chargez le magasin de clés dans votre application Android et utilisez-le pour les connexions sécurisées (je vous recommande d'utiliser le Apache HttpClient au lieu du standard Java.net.ssl.HttpsURLConnection (plus facile à comprendre, plus performant)

Prenez les certs

Vous devez obtenir tous les certificats qui construisent une chaîne à partir du certificat de point de terminaison jusqu'au niveau de l'autorité de certification racine. Cela signifie que tous les certificats de l'autorité de certification intermédiaire (le cas échéant), ainsi que le certificat de l'autorité de certification racine. Vous n’avez pas besoin d’obtenir le certificat de point final.

Créer le magasin de clés

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

Importez maintenant les certificats obtenus (n'importez pas le certificat de point final) dans un magasin de clés au format BouncyCastle.

Je ne l’ai pas testé, mais j'estime que l'ordre d'importation des certificats est important. Cela signifie que vous importez d'abord le certificat de l'autorité de certification intermédiaire le plus bas, puis jusqu'au certificat de l'autorité de certification racine.

Avec la commande suivante, un nouveau magasin de clés (s'il n'est pas déjà présent) avec le mot de passe , mysecret sera créé et le certificat de l'autorité de certification intermédiaire 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 magasin de clés. 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 magasin de clés:

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

Doit sortir 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

Vous pouvez maintenant copier le magasin de clés en tant que ressource brute dans votre application Android sous res/raw/.

Utiliser le magasin de clés dans votre application

Tout d’abord, nous devons créer un Apache HttpClient personnalisé qui utilise notre magasin de clés pour les connexions HTTPS:

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));
      // Register for port 443 our SSLSocketFactory with our keystore
      // to the ConnectionManager
      registry.register(new Scheme("https", newSslSocketFactory(), 443));
      return new SingleClientConnManager(getParams(), registry);
  }

  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 = context.getResources().openRawResource(R.raw.mykeystore);
          try {
              // Initialize the keystore with the provided trusted certificates
              // Also provide the password of the keystore
              trusted.load(in, "mysecret".toCharArray());
          } finally {
              in.close();
          }
          // Pass the keystore to the SSLSocketFactory. The factory is responsible
          // for the verification of the server certificate.
          SSLSocketFactory sf = new SSLSocketFactory(trusted);
          // Hostname verification from certificate
          // 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é, nous pouvons maintenant l'utiliser pour des connexions sécurisées. Par exemple, lorsque nous appelons GET vers une ressource REST.

// Instantiate the custom HttpClient
DefaultHttpClient client = new MyHttpClient(getApplicationContext());
HttpGet get = new HttpGet("https://www.mydomain.ch/rest/contacts/23");
// Execute the GET call and obtain the response
HttpResponse getResponse = client.execute(get);
HttpEntity responseEntity = getResponse.getEntity();

C'est ça ;)

121
saxos

Si vous avez un certificat personnalisé/auto-signé sur le serveur qui n’existe pas sur le périphérique, vous pouvez utiliser la classe ci-dessous pour le charger et l’utiliser côté client sous 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 sockets 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 {

    /**
     * Creates a {@link org.Apache.http.client.HttpClient} which is configured to work with a custom authority
     * certificate.
     *
     * @param context       Application Context
     * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
     * @param allowAllHosts If true then client will not check server against Host names of certificate.
     * @return Http Client.
     * @throws Exception If there is an error initializing the client.
     */
    public static HttpClient getHttpClient(Context context, int certRawResId, boolean allowAllHosts) throws Exception {


        // build key store with ca certificate
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // init ssl socket factory with key store
        SSLSocketFactory sslSocketFactory = new SSLSocketFactory(keyStore);

        // skip hostname security check if specified
        if (allowAllHosts) {
            sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        // basic http params for client
        HttpParams params = new BasicHttpParams();

        // normal scheme registry with our ssl socket factory for "https"
        SchemeRegistry schemeRegistry = new SchemeRegistry();
        schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));

        // create connection manager
        ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

        // create http client
        return new DefaultHttpClient(cm, params);
    }

    /**
     * Creates a {@link javax.net.ssl.HttpsURLConnection} which is configured to work with a custom authority
     * certificate.
     *
     * @param urlString     remote url string.
     * @param context       Application Context
     * @param certRawResId  R.raw.id of certificate file (*.crt). Should be stored in /res/raw.
     * @param allowAllHosts If true then client will not check server against Host names of certificate.
     * @return Http url connection.
     * @throws Exception If there is an error initializing the connection.
     */
    public static HttpsURLConnection getHttpsUrlConnection(String urlString, Context context, int certRawResId,
                                                           boolean allowAllHosts) throws Exception {

        // build key store with ca certificate
        KeyStore keyStore = buildKeyStore(context, certRawResId);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        // Create an SSLContext that uses our TrustManager
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, tmf.getTrustManagers(), null);

        // Create a connection from url
        URL url = new URL(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
        urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());

        // skip hostname security check if specified
        if (allowAllHosts) {
            urlConnection.setHostnameVerifier(new AllowAllHostnameVerifier());
        }

        return urlConnection;
    }

    private static KeyStore buildKeyStore(Context context, int certRawResId) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
        // init a default key store
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);

        // read and add certificate authority
        Certificate cert = readCert(context, certRawResId);
        keyStore.setCertificateEntry("ca", cert);

        return keyStore;
    }

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

        // read certificate resource
        InputStream caInput = context.getResources().openRawResource(certResourceId);

        Certificate ca;
        try {
            // generate a certificate
            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 magasin de clés sous le pseudonyme "ca". Vous modifiez le code pour ajouter plus de certificats (CA intermédiaire, etc.).
  4. L'objectif principal est de générer une SSLSocketFactory qui puisse ensuite être utilisée par HTTPClient ou HttpsURLConnection.
  5. SSLSocketFactory peut être configuré 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

16
S.D.

J'étais frustré d'essayer de connecter mon Android App à mon service RESTful à l'aide de https. De plus, j'étais un peu agacé par toutes les réponses suggérant de désactiver complètement la vérification des certificats. Si vous le faites, quel est le point de https?

Après avoir googlé sur le sujet pendant un moment, j'ai finalement trouvé la solution this où les fichiers jar externes ne sont pas nécessaires, mais seulement Android API. Merci à Andrew Smith, qui l'a posté en juillet 2014

 /**
 * Set up a connection to myservice.domain using HTTPS. An entire function
 * is needed to do this because myservice.domain has a self-signed certificate.
 * 
 * The caller of the function would do something like:
 * HttpsURLConnection urlConnection = setUpHttpsConnection("https://littlesvr.ca");
 * InputStream in = urlConnection.getInputStream();
 * And read from that "in" as usual in Java
 * 
 * Based on code from:
 * https://developer.Android.com/training/articles/security-ssl.html#SelfSigned
 */
public static HttpsURLConnection setUpHttpsConnection(String urlString)
{
    try
    {
        // Load CAs from an InputStream
        // (could be from a resource or ByteArrayInputStream or ...)
        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        // My CRT file that I put in the assets folder
        // I got this file by following these steps:
        // * Go to https://littlesvr.ca using Firefox
        // * Click the padlock/More/Security/View Certificate/Details/Export
        // * Saved the file as littlesvr.crt (type X.509 Certificate (PEM))
        // The MainActivity.context is declared as:
        // public static Context context;
        // And initialized in MainActivity.onCreate() as:
        // MainActivity.context = getApplicationContext();
        InputStream caInput = new BufferedInputStream(MainActivity.context.getAssets().open("littlesvr.crt"));
        Certificate ca = cf.generateCertificate(caInput);
        System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());

        // Create a KeyStore containing our trusted CAs
        String keyStoreType = KeyStore.getDefaultType();
        KeyStore keyStore = KeyStore.getInstance(keyStoreType);
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);

        // Create an SSLContext that uses our TrustManager
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);

        // Tell the URLConnection to use a SocketFactory from our SSLContext
        URL url = new URL(urlString);
        HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection();
        urlConnection.setSSLSocketFactory(context.getSocketFactory());

        return urlConnection;
    }
    catch (Exception ex)
    {
        Log.e(TAG, "Failed to establish SSL connection to server: " + ex.toString());
        return null;
    }
}

Cela a fonctionné bien pour ma maquette App.

6
Gonzalo Fernández

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

Créer une implémentation vide de X509TrustManager a eu l'effet escompté:

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
{
    // Create an SSLContext that uses our 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 TustManager n'est qu'un exemple et que son utilisation dans un environnement productif provoquerait une grave menace pour la sécurité!

6
Markus Lenger

Google recommande l'utilisation de Android Volley pour les connexions HTTP/HTTPS , étant donné que HttpClient est obsolète. Donc, vous connaissez le bon choix :).

Et aussi, les certificats SSL NEVER NUKE (JAMAIS !!!).

Nuke SSL Certificates est totalement contraire à l'objectif de SSL, qui est de promouvoir la sécurité . Si vous prévoyez de bombarder tous les certificats SSL fournis, l'utilisation de SSL est absente. Une meilleure solution serait de ne pas utiliser SSL ou une meilleure solution, ce serait de créer un TrustManager personnalisé sur votre App + en utilisant Android Volley pour les connexions HTTP/HTTPS.

Voici un Gist que j'ai créé, avec un LoginApp de base, effectuant des connexions HTTPS, à l'aide d'un certificat auto-signé sur le serveur, accepté sur l'application.

Voici également un autre Gist qui peut vous aider à créer des certificats SSL auto-signés pour la configuration sur votre serveur et l'utilisation du certificat sur votre application. Très important: vous devez copier le fichier .crt généré par le script ci-dessus dans le répertoire "raw" de votre Android. _ projet.

6
ivanleoncz

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

Il ne demandera pas à l'utilisateur comme vous le demandez, mais cela réduira le risque que l'utilisateur rencontre une erreur "Certificat de serveur non approuvé".

4
emmby

moyen le plus simple de créer un certificat SSL

Ouvrez Firefox (je suppose que c'est aussi possible avec Chrome, mais c'est plus facile pour moi avec FF)

Visitez votre site de développement avec un certificat SSL auto-signé.

Cliquez sur le certificat (à côté du nom du site)

Cliquez sur "Plus d'informations"

Cliquez sur "Voir le certificat"

Cliquez sur "Détails"

Cliquez sur "Exporter ..."

Choisissez "Certificat X.509 avec chaîne (PEM)", sélectionnez le dossier et le nom pour le sauvegarder, puis cliquez sur "Enregistrer"

Allez en ligne de commande, dans le répertoire où vous avez téléchargé le fichier pem et exécutez "openssl x509 -inform PEM -outform DM -in .pem -out .crt"

Copiez le fichier .crt à la racine du dossier/sdcard dans votre périphérique Android. À l'intérieur de votre périphérique Android, sélectionnez Paramètres> Sécurité> Installer à partir du stockage.

Il devrait détecter le certificat et vous permettre de l'ajouter à l'appareil. Accédez à votre site de développement.

La première fois, il devrait vous demander de confirmer l’exception de sécurité. C'est tout.

Le certificat devrait fonctionner avec tous les navigateurs installés sur votre Android (Navigateur, Chrome, Opera, Dolphin ...).

N'oubliez pas que si vous distribuez vos fichiers statiques à partir d'un domaine différent (nous sommes tous des pervers de la vitesse d'une page), vous devez également ajouter le certificat pour ce domaine.

2
Mani kandan

Aucune de ces corrections n'a fonctionné pour ma plate-forme de développement ciblant le SDK 16, version 4.1.2, j'ai donc trouvé une solution de contournement.

Mon application stocke les données sur le serveur à l'aide de " http://www.example.com/page.php?data=somedata "

Récemment, page.php a été déplacé vers " https://www.secure-example.com/page.php " et je continue à recevoir "javax.net.ssl.SSLException: certificat de serveur non approuvé".

Au lieu d'accepter tous les certificats pour une seule page, à partir de ce guide J'ai résolu mon problème en écrivant mon propre page.php publié sur " http://www.example.com/page .php "

<?php

caronte ("https://www.secure-example.com/page.php");

function caronte($url) {
    // build curl request
    $ch = curl_init();
    foreach ($_POST as $a => $b) {
        $post[htmlentities($a)]=htmlentities($b);
    }
    curl_setopt($ch, CURLOPT_URL,$url);
    curl_setopt($ch, CURLOPT_POST, 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS,http_build_query($post));

    // receive server response ...
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $server_output = curl_exec ($ch);
    curl_close ($ch);

    echo $server_output;
}

?>
1
Rudy

J'ai écrit une petite bibliothèque ssl-utils-Android pour faire confiance à un certificat particulier sur Android.

Vous pouvez simplement charger n'importe quel certificat en donnant le nom de fichier depuis le répertoire assets.

Usage:

OkHttpClient client = new OkHttpClient();
SSLContext sslContext = SslUtils.getSslContextForCertificateFile(context, "BPClass2RootCA-sha2.cer");
client.setSslSocketFactory(sslContext.getSocketFactory());
1
klimat

C'est un problème résultant du manque de support SNI (Identification du nom du serveur) inA, ndroid 2.x. Je me débattais avec ce problème pendant une semaine jusqu'à ce que je tombe sur la question suivante, qui non seulement donne un bon fond du problème, mais fournit également une solution efficace et dépourvue de faille de sécurité.

Erreur 'Pas de certificat homologue' dans Android 2.3 mais PAS dans 4

0
alumat

Cela sera peut-être utile ... cela fonctionne sur Java clients utilisant des certificats auto-signés (le certificat n'est pas vérifié). Soyez prudent et utilisez-le uniquement pour les cas de développement car ce n'est pas du tout sécurisé !!

Comment ignorer les erreurs de certificat SSL dans Apache HttpClient 4.

J'espère que cela fonctionnera sur Android en ajoutant simplement la bibliothèque HttpClient ... bonne chance !!

0
EmP