412 votes

Faire confiance à tous les certificats en utilisant HttpClient sur HTTPS

A récemment posté une question concernant l' HttpClient sur Https (trouvé ici). J'ai fait quelques progrès, mais j'ai couru dans de nouvelles questions. Comme avec mon dernier problème, je n'arrive pas à trouver un exemple de n'importe où qui fonctionne pour moi. En gros, je veux que mon client d'accepter un certificat (parce que je ne suis jamais pointant vers un serveur), mais je reçois un javax.net.ssl.SSLException: Not trusted server certificate exception.

C'est donc ce que j'ai:

public void connect() throws A_WHOLE_BUNCH_OF_EXCEPTIONS {

    HttpPost post = new HttpPost(new URI(PROD_URL));
    post.setEntity(new StringEntity(BODY));

    KeyStore trusted = KeyStore.getInstance("BKS");
    trusted.load(null, "".toCharArray());
    SSLSocketFactory sslf = new SSLSocketFactory(trusted);
    sslf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

    SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(new Scheme ("https", sslf, 443));
    SingleClientConnManager cm = new SingleClientConnManager(post.getParams(),
            schemeRegistry);

    HttpClient client = new DefaultHttpClient(cm, post.getParams());
    HttpResponse result = client.execute(post);
}

Et voici l'erreur que je reçois:

W/System.err(  901): javax.net.ssl.SSLException: Not trusted server certificate
W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:360)
W/System.err(  901):    at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:92)
W/System.err(  901):    at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:321)
W/System.err(  901):    at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:129)
W/System.err(  901):    at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164)
W/System.err(  901):    at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119)
W/System.err(  901):    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:348)
W/System.err(  901):    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
W/System.err(  901):    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
W/System.err(  901):    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465)
W/System.err(  901):    at me.harrisonlee.test.ssl.MainActivity.connect(MainActivity.java:129)
W/System.err(  901):    at me.harrisonlee.test.ssl.MainActivity.access$0(MainActivity.java:77)
W/System.err(  901):    at me.harrisonlee.test.ssl.MainActivity$2.run(MainActivity.java:49)
W/System.err(  901): Caused by: java.security.cert.CertificateException: java.security.InvalidAlgorithmParameterException: the trust anchors set is empty
W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:157)
W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:355)
W/System.err(  901):    ... 12 more
W/System.err(  901): Caused by: java.security.InvalidAlgorithmParameterException: the trust anchors set is empty
W/System.err(  901):    at java.security.cert.PKIXParameters.checkTrustAnchors(PKIXParameters.java:645)
W/System.err(  901):    at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:89)
W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.TrustManagerImpl.<init>(TrustManagerImpl.java:89)
W/System.err(  901):    at org.apache.harmony.xnet.provider.jsse.TrustManagerFactoryImpl.engineGetTrustManagers(TrustManagerFactoryImpl.java:134)
W/System.err(  901):    at javax.net.ssl.TrustManagerFactory.getTrustManagers(TrustManagerFactory.java:226)
W/System.err(  901):    at org.apache.http.conn.ssl.SSLSocketFactory.createTrustManagers(SSLSocketFactory.java:263)
W/System.err(  901):    at org.apache.http.conn.ssl.SSLSocketFactory.<init>(SSLSocketFactory.java:190)
W/System.err(  901):    at org.apache.http.conn.ssl.SSLSocketFactory.<init>(SSLSocketFactory.java:216)
W/System.err(  901):    at me.harrisonlee.test.ssl.MainActivity.connect(MainActivity.java:107)
W/System.err(  901):    ... 2 more

516voto

emmby Points 35359

En gros, vous avez quatre solutions possibles pour résoudre un "Pas Confiance" exception sur Android à l'aide de httpclient:

  1. Faire confiance à tous les certificats. Ne pas le faire, sauf si vous savez vraiment ce que vous faites.
  2. Créer un personnalisé SSLSocketFactory qui ne se fie que de votre certificat. Cela fonctionne tant que vous savez exactement quels sont les serveurs que vous allez connecter, mais dès que vous avez besoin pour vous connecter à un nouveau serveur avec un certificat SSL différent, vous aurez besoin de mettre à jour votre application.
  3. Créer un fichier de magasin de clés qui contient Android "liste principale" de certificats, puis ajoutez votre propre. Si l'un de ces certificats expire en bas de la route, vous êtes responsable de la mise à jour de votre application. Je ne peux pas penser à une raison pour le faire.
  4. Créer un personnalisé SSLSocketFactory qui utilise le haut-certificat de magasin de clés, mais retombe sur un autre fichier de clés pour tout ce qui ne parvient pas à vérifier avec la valeur par défaut.

Cette réponse utilise la solution #4, ce qui me semble le plus solide.

La solution est d'utiliser un SSLSocketFactory qui peut accepter plusieurs fichiers de clés, vous permettant de vous fournir votre propre fichier de clés avec vos propres certificats. Cela vous permet de charger d'autres haut-niveau les certificats tels que Thawte qui pourraient être manquantes sur certains appareils Android. Il vous permet également de charger vos propres certificats auto-signés. Il va utiliser le microphone par défaut du dispositif des certificats d'abord, et de revenir sur vos certificats supplémentaires seulement si nécessaire.

Tout d'abord, vous aurez envie de déterminer quel cert qui vous manque dans votre fichier de clés. Exécutez la commande suivante:

openssl s_client -connect www.yourserver.com:443

Et vous allez voir le résultat comme suit:

Certificate chain
 0 s:/O=www.yourserver.com/OU=Go to 
   https://www.thawte.com/repository/index.html/OU=Thawte SSL123 
   certificate/OU=Domain Validated/CN=www.yourserver.com
   i:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
 1 s:/C=US/O=Thawte, Inc./OU=Domain Validated SSL/CN=Thawte DV SSL CA
   i:/C=US/O=thawte, Inc./OU=Certification Services Division/OU=(c) 
   2006 thawte, Inc. - For authorized use only/CN=thawte Primary Root CA

Comme vous pouvez le voir, notre certificat racine est de Thawte. Accédez à votre fournisseur de site web et de trouver le certificat correspondant. Pour nous, c'était ici, et vous pouvez voir que celui que nous avons besoin était l'un des droits d'Auteur 2006.

Si vous utilisez un certificat auto-signé, vous n'avez pas besoin de faire l'étape précédente, car vous avez déjà votre certificat de signature.

Ensuite, créez un fichier de magasin de clés contenant le manque de signature de certificat. Crazybob a explique comment le faire sur Android, mais l'idée est de faire ce qui suit:

export CLASSPATH=bcprov-jdk16-145.jar
CERTSTORE=res/raw/mystore.bks
if [ -a $CERTSTORE ]; then
    rm $CERTSTORE || exit 1
fi
keytool \
      -import \
      -v \
      -trustcacerts \
      -alias 0 \
      -file <(openssl x509 -in mycert.pem) \
      -keystore $CERTSTORE \
      -storetype BKS \
      -provider org.bouncycastle.jce.provider.BouncyCastleProvider \
      -providerpath /usr/share/java/bcprov.jar \
      -storepass some-password

Vous remarquerez que le script ci-dessus place le résultat dans res/raw/mystore.bks. Maintenant vous avez un fichier que vous chargez dans votre application Android qui fournit le certificat manquant(s).

Pour ce faire, inscrivez votre SSLSocketFactory pour le SSL schéma:

final SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", createAdditionalCertsSSLSocketFactory(), 443));

// and then however you create your connection manager, I use ThreadSafeClientConnManager
final HttpParams params = new BasicHttpParams();
...
final ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params,schemeRegistry);

Pour créer votre SSLSocketFactory:

protected org.apache.http.conn.ssl.SSLSocketFactory createAdditionalCertsSSLSocketFactory() {
    try {
        final KeyStore ks = KeyStore.getInstance("BKS");

        // the bks file we generated above
        final InputStream in = context.getResources().openRawResource( R.raw.mystore);  
        try {
            // don't forget to put the password used above in strings.xml/mystore_password
            ks.load(in, context.getString( R.string.mystore_password ).toCharArray());
        } finally {
            in.close();
        }

        return new AdditionalKeyStoresSSLSocketFactory(ks);

    } catch( Exception e ) {
        throw new RuntimeException(e);
    }
}

Et enfin, le AdditionalKeyStoresSSLSocketFactory code, qui accepte votre nouveau fichier de clés et vérifie si les haut-KeyStore ne parvient pas à valider un certificat SSL:

/**
 * Allows you to trust certificates from additional KeyStores in addition to
 * the default KeyStore
 */
public class AdditionalKeyStoresSSLSocketFactory extends SSLSocketFactory {
    protected SSLContext sslContext = SSLContext.getInstance("TLS");

    public AdditionalKeyStoresSSLSocketFactory(KeyStore keyStore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(null, null, null, null, null, null);
        sslContext.init(null, new TrustManager[]{new AdditionalKeyStoresTrustManager(keyStore)}, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }



    /**
     * Based on http://download.oracle.com/javase/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#X509TrustManager
     */
    public static class AdditionalKeyStoresTrustManager implements X509TrustManager {

        protected ArrayList<X509TrustManager> x509TrustManagers = new ArrayList<X509TrustManager>();


        protected AdditionalKeyStoresTrustManager(KeyStore... additionalkeyStores) {
            final ArrayList<TrustManagerFactory> factories = new ArrayList<TrustManagerFactory>();

            try {
                // The default Trustmanager with default keystore
                final TrustManagerFactory original = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                original.init((KeyStore) null);
                factories.add(original);

                for( KeyStore keyStore : additionalkeyStores ) {
                    final TrustManagerFactory additionalCerts = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                    additionalCerts.init(keyStore);
                    factories.add(additionalCerts);
                }

            } catch (Exception e) {
                throw new RuntimeException(e);
            }



            /*
             * Iterate over the returned trustmanagers, and hold on
             * to any that are X509TrustManagers
             */
            for (TrustManagerFactory tmf : factories)
                for( TrustManager tm : tmf.getTrustManagers() )
                    if (tm instanceof X509TrustManager)
                        x509TrustManagers.add( (X509TrustManager)tm );


            if( x509TrustManagers.size()==0 )
                throw new RuntimeException("Couldn't find any X509TrustManagers");

        }

        /*
         * Delegate to the default trust manager.
         */
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            final X509TrustManager defaultX509TrustManager = x509TrustManagers.get(0);
            defaultX509TrustManager.checkClientTrusted(chain, authType);
        }

        /*
         * Loop over the trustmanagers until we find one that accepts our server
         */
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            for( X509TrustManager tm : x509TrustManagers ) {
                try {
                    tm.checkServerTrusted(chain,authType);
                    return;
                } catch( CertificateException e ) {
                    // ignore
                }
            }
            throw new CertificateException();
        }

        public X509Certificate[] getAcceptedIssuers() {
            final ArrayList<X509Certificate> list = new ArrayList<X509Certificate>();
            for( X509TrustManager tm : x509TrustManagers )
                list.addAll(Arrays.asList(tm.getAcceptedIssuers()));
            return list.toArray(new X509Certificate[list.size()]);
        }
    }

}

430voto

Daniel Points 2632

Remarque: Ne pas l'implémenter dans le code de production vous allez jamais à utiliser sur un réseau, vous n'avez pas entièrement confiance. Surtout tout ce qui passe sur le réseau internet public.

Votre question est tout ce que je veux savoir. Après j'ai fait quelques recherches, la conclusion est la suivante.

Dans HttpClient façon, vous devez créer une classe personnalisée de org.apache.http.conn.le protocole ssl.SSLSocketFactory, pas le un org.apache.http.conn.le protocole ssl.SSLSocketFactory lui-même. Quelques indices peuvent être trouvés dans ce post SSL Personnalisé manipulation ne fonctionne plus sur Android 2.2 FroYo.

Un exemple c'est comme ...

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.conn.ssl.SSLSocketFactory;
public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");

    public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
        super(truststore);

        TrustManager tm = new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

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

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

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }
}

et l'utilisation de cette classe lors de la création de l'instance de HttpClient.

public HttpClient getNewHttpClient() {
    try {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        trustStore.load(null, null);

        SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
        sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);

        SchemeRegistry registry = new SchemeRegistry();
        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        registry.register(new Scheme("https", sf, 443));

        ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);

        return new DefaultHttpClient(ccm, params);
    } catch (Exception e) {
        return new DefaultHttpClient();
    }
}

BTW, le lien ci-dessous pour quelqu'un qui est à la recherche pour HttpURLConnection solution. Connexion Https Android

J'ai testé les deux types de solutions sur froyo, et ils fonctionnent tous comme un charme dans mon cas. Enfin, à l'aide de HttpURLConnection peut faire face à des problèmes de redirection, mais c'est au-delà de la rubrique.

Remarque: Avant de vous décider à faire confiance à tous les certificats, vous devez probablement connaître le site bien et n'est pas nocif pour l'utilisateur final.

En effet, le risque que vous prenez doivent être considérées avec précaution, y compris l'effet de hacker le faux site mentionné dans les observations suivantes que j'ai beaucoup apprécié. Dans certains cas, bien qu'il puisse être difficile de prendre soin de tous les certificats, vous feriez mieux de savoir implicite des inconvénients de confiance en eux.

86voto

Alok Gupta Points 148

Ajoutez ce code avant le `` et ce sera fait. J'ai compris.

J’espère que cela vous aide.

35voto

Dan Points 1459

C'est une mauvaise idée. Faire confiance à un certificat n'est (très) légèrement mieux que d'utiliser des pas de SSL. Quand vous dites "je veux que mon client d'accepter un certificat (parce que je ne suis jamais pointant vers un serveur)" vous êtes en supposant que cela signifie que d'une certaine façon pointant vers "un serveur" est sécuritaire, qu'il n'est pas sur un réseau public.

Vous êtes complètement ouvert à un man-in-the-middle attack, en faisant confiance à un certificat. N'importe qui peut le proxy de votre connexion par l'établissement d'un distinct connexion SSL avec vous et avec la fin du serveur. Le MITM a ensuite accès à l'ensemble de votre demande et la réponse. Sauf si vous n'avez pas vraiment besoin de SSL en premier lieu (votre message n'a rien sensibles, et ne fait pas d'authentification) vous ne devriez pas faire confiance à tous les certificats à l'aveuglette.

Vous devriez envisager d'ajouter au public cert à un jks à l'aide de keytool, et en l'utilisant pour construire votre prise en usine, comme ceci:

    KeyStore ks = KeyStore.getInstance("JKS");

    // get user password and file input stream
    char[] password = ("mykspassword")).toCharArray();
    ClassLoader cl = this.getClass().getClassLoader();
    InputStream stream = cl.getResourceAsStream("myjks.jks");
    ks.load(stream, password);
    stream.close();

    SSLContext sc = SSLContext.getInstance("TLS");
    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");

    kmf.init(ks, password);
    tmf.init(ks);

    sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(),null);

    return sc.getSocketFactory();

Cela a un inconvénient à regarder dehors pour. Le certificat expire par la suite, et le code d'arrêt de travail à l'époque. Vous pouvez facilement déterminer quand cela se produira en regardant le cert.

10voto

L’API de HttpComponents a obtenu changé. Il fonctionne avec le code ci-dessous.

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