Question

Comment accepter un certificat auto-signé en Java sous Android?

Un exemple de code serait parfait.

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

Était-ce utile?

La solution

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);

EasySSLSocketFactory est ici , et le EasyX509TrustManager est ici .

Le code pour exchangeIt est open source et hébergé sur googlecode ici , si vous en avez un. quelque problème que ce soit. Je ne travaille plus activement là-dessus, mais le code devrait fonctionner.

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

Autres conseils

Comme EJP l'a correctement commenté, "Le lecteur doit noter que cette technique est radicalement peu sûre." SSL n'est pas sécurisé sauf si au moins un homologue est authentifié. Voir RFC 2246. "

Cela dit, voici une autre méthode, sans classes 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();
    }
}

J'ai rencontré ce problème hier lors de la migration de l'API RESTful de notre société vers HTTPS, mais à l'aide de certificats SSL auto-signés.

J'ai regardé partout, mais tous les "corrects" Les réponses marquées que j'ai trouvées consistaient à désactiver la validation des certificats, en annulant clairement tout le sens de SSL.

Je suis finalement arrivé à 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 fichier de clés personnalisé contenant les certificats de manière à ce que Android puisse 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 pour pouvoir télécharger ici .

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

La commande permettant de créer votre magasin de clés est la suivante:

<!-- 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 magasin de clés sera créé. Il NE DOIT PAS EXISTER .

PATH_TO_bcprov-jdk15on-146.jar.JAR est le chemin d'accès à la bibliothèque .jar téléchargée.

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

  1. Inclure KeyStore dans votre application

Copiez le fichier de clés que vous venez de créer de PATH_TO_KEYSTORE vers res / raw / certs.bks ( certs.bks n'est que le nom du fichier. 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éer une classe 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;
            }
        }
    }
    

Maintenant, utilisez simplement une instance de ** MyHttpClient ** comme vous le feriez avec ** DefaultHttpClient ** pour effectuer vos requêtes HTTPS. 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 quelque chose m'a échappé, les autres réponses sur cette page sont DANGEREUSES, et leur fonctionnement équivaut fonctionnellement à ne pas utiliser du tout le protocole SSL. Si vous faites confiance à des certificats auto-signés sans faire d'autres vérifications pour vous assurer que les certificats sont ceux que vous attendez, n'importe qui peut créer un certificat auto-signé et prétendre être votre serveur. À ce stade, vous n’avez aucune sécurité réelle.

La seule manière légitime de le faire (sans écrire une pile SSL complète) consiste à ajouter une ancre sécurisée supplémentaire à sécuriser lors du processus de vérification du certificat. Les deux impliquent de coder en dur le certificat d'ancre 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 véritable certificat).

Je connais deux manières de procéder:

  1. Créez un magasin de confiance personnalisé, comme décrit à l'adresse http. : //www.ibm.com/developerworks/java/library/j-customssl/#8

  2. Créez une instance personnalisée de X509TrustManager et substituez 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'a pas encore été testé et peut même pas être compilé, mais doit au moins vous guider dans la bonne direction.

La réponse de Brian Yarger fonctionne également dans Android 2.2 si vous modifiez comme suit la surcharge de la méthode createSocket, plus importante. 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);
}

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

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

@Chris - Poster ceci comme réponse car je ne peux pas ajouter de commentaires (pour le moment). Je me demande si votre approche est censée fonctionner avec une vue Web. Je ne parviens pas à le faire sur Android 2.3. Je viens d’obtenir un écran blanc.

Après quelques recherches supplémentaires, je suis tombé sur ce solution simple pour la gestion des erreurs SSL dans une WebView qui fonctionnait à merveille pour moi.

Dans le gestionnaire, je vérifie si je suis dans un mode 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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top