web-dev-qa-db-fra.com

Acceptation SSL auto-signée sur Android

Comment accepter un certificat auto-signé dans Java sur Android?

Un exemple de code serait parfait.

J'ai cherché partout sur Internet et bien que certaines personnes prétendent avoir trouvé la solution, elle ne fonctionne pas ou il n'y a pas d'exemple de code pour la sauvegarder.

52
Faisal Abid

J'ai cette fonctionnalité dans exchangeIt, qui se connecte à Microsoft exchange via WebDav. Voici un code pour créer un HttpClient qui se connectera aux certificats auto-signés via SSL:

SchemeRegistry schemeRegistry = new SchemeRegistry();
// http scheme
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
// https scheme
schemeRegistry.register(new Scheme("https", new EasySSLSocketFactory(), 443));

HttpParams params = new BasicHttpParams();
params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE, new ConnPerRouteBean(30));
params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);

L'EasySSLSocketFactory est ici , et l'EasyX509TrustManager est ici .

Le code pour exchangeIt est open source, et hébergé sur googlecode ici , si vous avez des problèmes. Je n'y travaille plus activement, mais le code devrait fonctionner.

Notez que depuis Android 2.2 le processus a un peu changé, vérifiez donc this pour que le code ci-dessus fonctionne.

37
Brian Yarger

Comme EJP l'a correctement commenté, "Les lecteurs doivent noter que cette technique est radicalement peu sûre. SSL n'est pas sécurisé à moins qu'au moins un homologue ne soit authentifié. Voir RFC 2246."

Cela dit, voici une autre façon, sans cours supplémentaires:

import Java.security.SecureRandom;
import Java.security.cert.CertificateException;
import Java.security.cert.X509Certificate;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

private void trustEveryone() {
    try {
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier(){
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }});
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, new X509TrustManager[]{new X509TrustManager(){
            public void checkClientTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public void checkServerTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {}
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }}}, new SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(
                context.getSocketFactory());
    } catch (Exception e) { // should never happen
        e.printStackTrace();
    }
}
37
Chris Boyle

J'ai rencontré ce problème hier, lors de la migration de l'API RESTful de notre entreprise vers HTTPS, mais en utilisant des certificats SSL auto-signés.

J'ai cherché partout, mais toutes les réponses marquées "correctes" que j'ai trouvées consistaient à désactiver la validation de certificat, remplaçant clairement tout le sens de SSL.

J'ai finalement trouvé une solution:

  1. Créer un magasin de clés local

    Pour permettre à votre application de valider vos certificats auto-signés, vous devez fournir un magasin de clés personnalisé avec les certificats d'une manière qui Android peut faire confiance à votre point de terminaison.

Le format de ces magasins de clés personnalisés est "BKS" de BouncyCastle , vous avez donc besoin de la version 1.46 de BouncyCastleProvider que vous pouvez télécharger ici .

Vous avez également besoin de votre certificat auto-signé, je suppose qu'il s'appelle self_cert.pem.

Maintenant, la commande pour créer votre fichier de clés est:

<!-- language: lang-sh -->

    $ keytool -import -v -trustcacerts -alias 0 \
    -file *PATH_TO_SELF_CERT.PEM* \
    -keystore *PATH_TO_KEYSTORE* \
    -storetype BKS \
    -provider org.bouncycastle.jce.provider.BouncyCastleProvider \
    -providerpath *PATH_TO_bcprov-jdk15on-146.jar* \
    -storepass *STOREPASS*

PATH_TO_KEYSTORE pointe vers un fichier dans lequel votre fichier de clés sera créé. Il NE DOIT PAS EXISTER .

PATH_TO_bcprov-jdk15on-146.jar.JAR est le chemin d'accès au fichier .jar téléchargé.

STOREPASS est votre nouveau mot de passe de fichier de clés.

  1. Inclure KeyStore dans votre application

Copiez votre fichier de clés nouvellement créé à partir de PATH_TO_KEYSTORE à res/raw/certs.bks ( certs.bks est juste le nom du fichier; vous pouvez utiliser le nom de votre choix)

Créez une clé dans res/values/strings.xml avec

<!-- language: lang-xml -->

    <resources>
    ...
        <string name="store_pass">*STOREPASS*</string>
    ...
    </resources>
  1. Créez une classe this qui hérite de DefaultHttpClient

    import Android.content.Context;
    import Android.util.Log;
    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.SSLSocketFactory;
    import org.Apache.http.impl.client.DefaultHttpClient;
    import org.Apache.http.params.HttpParams;
    
    import Java.io.IOException;
    import Java.io.InputStream;
    import Java.security.*;
    
    public class MyHttpClient extends DefaultHttpClient {
    
        private static Context appContext = null;
        private static HttpParams params = null;
        private static SchemeRegistry schmReg = null;
        private static Scheme httpsScheme = null;
        private static Scheme httpScheme = null;
        private static String TAG = "MyHttpClient";
    
        public MyHttpClient(Context myContext) {
    
            appContext = myContext;
    
            if (httpScheme == null || httpsScheme == null) {
                httpScheme = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
                httpsScheme = new Scheme("https", mySSLSocketFactory(), 443);
            }
    
            getConnectionManager().getSchemeRegistry().register(httpScheme);
            getConnectionManager().getSchemeRegistry().register(httpsScheme);
    
        }
    
        private SSLSocketFactory mySSLSocketFactory() {
            SSLSocketFactory ret = null;
            try {
                final KeyStore ks = KeyStore.getInstance("BKS");
    
                final InputStream inputStream = appContext.getResources().openRawResource(R.raw.certs);
    
                ks.load(inputStream, appContext.getString(R.string.store_pass).toCharArray());
                inputStream.close();
    
                ret = new SSLSocketFactory(ks);
            } catch (UnrecoverableKeyException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyStoreException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (KeyManagementException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (NoSuchAlgorithmException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (IOException ex) {
                Log.d(TAG, ex.getMessage());
            } catch (Exception ex) {
                Log.d(TAG, ex.getMessage());
            } finally {
                return ret;
            }
        }
    }
    

Utilisez maintenant simplement une instance de **MyHttpClient** comme vous le feriez avec **DefaultHttpClient** pour effectuer vos requêtes HTTPS, et il utilisera et validera correctement vos certificats SSL auto-signés.

HttpResponse httpResponse;

HttpPost httpQuery = new HttpPost("https://yourserver.com");
... set up your query ...

MyHttpClient myClient = new MyHttpClient(myContext);

try{

    httpResponse = myClient.(peticionHttp);

    // Check for 200 OK code
    if (httpResponse.getStatusLine().getStatusCode() == HttpURLConnection.HTTP_OK) {
        ... do whatever you want with your response ...
    }

}catch (Exception ex){
    Log.d("httpError", ex.getMessage());
}

Sauf si j'ai raté quelque chose, les autres réponses sur cette page sont DANGEREUSES et sont fonctionnellement équivalentes à ne pas utiliser SSL du tout. Si vous faites confiance aux certificats auto-signés sans effectuer de vérifications supplémentaires pour vous assurer que les certificats sont ceux que vous attendez, alors n'importe qui peut créer un certificat auto-signé et peut prétendre être votre serveur. À ce stade, vous n'avez aucune réelle sécurité.

La seule manière légitime de le faire (sans écrire une pile SSL complète) consiste à ajouter une ancre de confiance supplémentaire à approuver pendant le processus de vérification du certificat. Les deux impliquent de coder en dur le certificat d'ancrage de confiance dans votre application et de l'ajouter à toutes les ancres de confiance fournies par le système d'exploitation (sinon vous ne pourrez pas vous connecter à votre site si vous obtenez un vrai certificat).

Je connais deux façons de procéder:

  1. Créez un magasin de clés de confiance personnalisé comme décrit sur http://www.ibm.com/developerworks/Java/library/j-customssl/#8

  2. Créez une instance personnalisée de X509TrustManager et remplacez la méthode getAcceptedIssuers pour renvoyer un tableau contenant votre certificat:

    public X509Certificate[] getAcceptedIssuers()
    {
        X509Certificate[] trustedAnchors =
            super.getAcceptedIssuers();
    
        /* Create a new array with room for an additional trusted certificate. */
        X509Certificate[] myTrustedAnchors = new X509Certificate[trustedAnchors.length + 1];
        System.arraycopy(trustedAnchors, 0, myTrustedAnchors, 0, trustedAnchors.length);  
    
        /* Load your certificate.
    
           Thanks to http://stackoverflow.com/questions/11857417/x509trustmanager-override-without-allowing-all-certs
           for this bit.
         */
        InputStream inStream = new FileInputStream("fileName-of-cert");
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream);
        inStream.close();
    
        /* Add your anchor cert as the last item in the array. */
        myTrustedAnchors[trustedAnchors.length] = cert;
    
        return myTrustedAnchors;
    }
    

Notez que ce code n'est pas testé et peut même ne pas compiler, mais devrait au moins vous orienter dans la bonne direction.

15
dgatwood

La réponse de Brian Yarger fonctionne dans Android 2.2 également si vous modifiez la surcharge de méthode createSocket la plus importante comme suit. Il m'a fallu un certain temps pour que les SSL auto-signés fonctionnent.

public Socket createSocket(Socket socket, String Host, int port, boolean autoClose) throws IOException, UnknownHostException {
    return getSSLContext().getSocketFactory().createSocket(socket, Host, port, autoClose);
}
4
Dmitry S.

@Chris - Publier cela comme une réponse car je ne peux pas encore ajouter de commentaires. Je me demande si votre approche est censée fonctionner lors de l'utilisation d'une webView. Je ne peux pas le faire ainsi Android 2.3 - au lieu de cela, je reçois juste un écran blanc.

Après quelques recherches supplémentaires, je suis tombé sur ce correctif simple pour gérer les erreurs SSL dans une webView qui a fonctionné comme un charme pour moi.

Dans le gestionnaire, je vérifie si je suis dans un mode de développement spécial et j'appelle handler.proceed (), sinon j'appelle handler.cancel (). Cela me permet de faire du développement contre un certificat auto-signé sur un site Web local.

0
Yaakov

Sur Android, HttpProtocolParams accepte ProtocolVersion plutôt que HttpVersion.

ProtocolVersion pv = new ProtocolVersion("HTTP", 1, 1);
HttpProtocolParams.setVersion(params, pv);
0
James