web-dev-qa-db-fra.com

Désactiver SSL en tant que protocole dans HttpsURLConnection

En raison de la vulnérabilité POODLE , mon serveur, hébergé sur Amazon AWS, ne prend plus en charge SSLv3.

En conséquence, la première connexion HTTPS de mon application Android sur le serveur entraîne une erreur lors de l'établissement de la connexion.

Error reading server response: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x77d8ab68: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0x7339ad74:0x00000000)
       [....]
Caused by: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x77d8ab68: Failure in SSL library, usually a protocol error
error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0x7339ad74:0x00000000)
       at com.Android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.Java:448)
       at com.Android.okhttp.Connection.upgradeToTls(Connection.Java:146)
       at com.Android.okhttp.Connection.connect(Connection.Java:107)
       at com.Android.okhttp.internal.http.HttpEngine.connect(HttpEngine.Java:294)
       at com.Android.okhttp.internal.http.HttpEngine.sendSocketRequest(HttpEngine.Java:255)
       at com.Android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.Java:206)

L'erreur ne se produit que dans la première demande. Les demandes suivantes fonctionnent pendant un certain temps.

Pour résoudre ce problème, j'essaie de supprimer SSL de la liste des protocoles acceptés par le client Android et de m'assurer que je n'utilise que TLS. Pour ce faire, j'ai défini un SSLSocketFactory personnalisé qui supprime SSL de la liste des protocoles activés et des suites de chiffrement prises en charge.

/**
 * SSLSocketFactory that wraps one existing SSLSocketFactory and delegetes into it adding
 * a new cipher suite
 */
public class TLSOnlySocketFactory extends SSLSocketFactory {

    private final SSLSocketFactory delegate;

    public TLSOnlySocketFactory(SSLSocketFactory delegate) {
        this.delegate = delegate;
    }

    @Override
    public String[] getDefaultCipherSuites() {

        return getPreferredDefaultCipherSuites(this.delegate);
    }

    @Override
    public String[] getSupportedCipherSuites() {
        return getPreferredSupportedCipherSuites(this.delegate);
    }



    @Override
    public Socket createSocket(Socket s, String Host, int port, boolean autoClose) throws IOException {
        final Socket socket = this.delegate.createSocket(s, Host, port, autoClose);

        ((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
        ((SSLSocket)socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));

        return socket;
    }



   [.....]

        ((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
        ((SSLSocket) socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));

        return socket;
    }

    @Override
    public Socket createSocket(InetAddress Host, int port) throws IOException {
        final Socket socket = this.delegate.createSocket(Host, port);

        ((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
        ((SSLSocket) socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));

        return socket;
    }

    @Override
    public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
        final Socket socket = this.delegate.createSocket(address, port, localAddress, localPort);

        ((SSLSocket)socket).setEnabledCipherSuites(getPreferredDefaultCipherSuites(delegate));
        ((SSLSocket) socket).setEnabledProtocols(getEnabledProtocols((SSLSocket)socket));

        return socket;
    }

    private String[] getPreferredDefaultCipherSuites(SSLSocketFactory sslSocketFactory) {
        return getCipherSuites(sslSocketFactory.getDefaultCipherSuites());
    }

    private String[] getPreferredSupportedCipherSuites(SSLSocketFactory sslSocketFactory) {
        return getCipherSuites(sslSocketFactory.getSupportedCipherSuites());
    }

    private String[] getCipherSuites(String[] cipherSuites) {
        final ArrayList<String> suitesList = new ArrayList<String>(Arrays.asList(cipherSuites));
        final Iterator<String> iterator = suitesList.iterator();
        while (iterator.hasNext()) {
            final String cipherSuite = iterator.next();
            if (cipherSuite.contains("SSL")) {
                iterator.remove();
            }
        }
        return suitesList.toArray(new String[suitesList.size()]);
    }

    private String[] getEnabledProtocols(SSLSocket socket) {
        final ArrayList<String> protocolList = new ArrayList<String>(Arrays.asList(socket.getSupportedProtocols()));
        final Iterator<String> iterator = protocolList.iterator();
        while (iterator.hasNext()) {
            final String protocl = iterator.next();
            if (protocl.contains("SSL")) {
                iterator.remove();
            }
        }
        return protocolList.toArray(new String[protocolList.size()]);
     }

}

Comme vous le voyez, mon SSLSocketFactory délègue dans un autre SSLSocketFactory et supprime simplement SSL de la liste des protocoles activés.

J'établis cette usine comme

final TLSOnlySocketFactory tlsOnlySocketFactory = new TLSOnlySocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(tlsOnlySocketFactory);

Cela ne résout pas le problème. De temps en temps, je vois encore l'erreur quand la connexion a été établie. Curieusement, cela ne résout pas le problème, mais cela minimise clairement la fréquence du problème.

Comment puis-je forcer la connexion HttpsUrlConnection dans mon client Android à utiliser uniquement TLS?

Je vous remercie.

16
GaRRaPeTa

Je pense avoir résolu ce problème. L'idée fondamentale est la même que dans le code de la question (évitez SSLv3 comme seul protocole disponible), mais le code qui l'exécute est différent:

import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.lang.reflect.InvocationTargetException;
import Java.lang.reflect.Method;
import Java.net.InetAddress;
import Java.net.Socket;
import Java.net.SocketAddress;
import Java.net.SocketException;
import Java.nio.channels.SocketChannel;
import Java.util.ArrayList;
import Java.util.Arrays;
import Java.util.List;

import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;

    /**
     * {@link javax.net.ssl.SSLSocketFactory} that doesn't allow {@code SSLv3} only connections
     * <p>fixes https://github.com/koush/ion/issues/386</p>
     *
     * <p> see https://code.google.com/p/Android/issues/detail?id=78187 </p>
     */
    public class NoSSLv3Factory extends SSLSocketFactory {
        private final SSLSocketFactory delegate;

        public NoSSLv3Factory() {
            this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
        }

        @Override
        public String[] getDefaultCipherSuites() {
            return delegate.getDefaultCipherSuites();
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return delegate.getSupportedCipherSuites();
        }

        private static Socket makeSocketSafe(Socket socket) {
            if (socket instanceof SSLSocket) {
                socket = new NoSSLv3SSLSocket((SSLSocket) socket);
            }
            return socket;
        }

        @Override
        public Socket createSocket(Socket s, String Host, int port, boolean autoClose) throws IOException {
            return makeSocketSafe(delegate.createSocket(s, Host, port, autoClose));
        }

        @Override
        public Socket createSocket(String Host, int port) throws IOException {
            return makeSocketSafe(delegate.createSocket(Host, port));
        }

        @Override
        public Socket createSocket(String Host, int port, InetAddress localHost, int localPort) throws IOException {
            return makeSocketSafe(delegate.createSocket(Host, port, localHost, localPort));
        }

        @Override
        public Socket createSocket(InetAddress Host, int port) throws IOException {
            return makeSocketSafe(delegate.createSocket(Host, port));
        }

        @Override
        public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
            return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort));
        }

        /**
         * Created by robUx4 on 25/10/2014.
         */
        private static class DelegateSSLSocket extends SSLSocket {

            protected final SSLSocket delegate;

            DelegateSSLSocket(SSLSocket delegate) {
                this.delegate = delegate;
            }

            @Override
            public String[] getSupportedCipherSuites() {
                return delegate.getSupportedCipherSuites();
            }

            @Override
            public String[] getEnabledCipherSuites() {
                return delegate.getEnabledCipherSuites();
            }

            @Override
            public void setEnabledCipherSuites(String[] suites) {
                delegate.setEnabledCipherSuites(suites);
            }

            @Override
            public String[] getSupportedProtocols() {
                return delegate.getSupportedProtocols();
            }

            @Override
            public String[] getEnabledProtocols() {
                return delegate.getEnabledProtocols();
            }

            @Override
            public void setEnabledProtocols(String[] protocols) {
                delegate.setEnabledProtocols(protocols);
            }

            @Override
            public SSLSession getSession() {
                return delegate.getSession();
            }

            @Override
            public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
                delegate.addHandshakeCompletedListener(listener);
            }

            @Override
            public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
                delegate.removeHandshakeCompletedListener(listener);
            }

            @Override
            public void startHandshake() throws IOException {
                delegate.startHandshake();
            }

            @Override
            public void setUseClientMode(boolean mode) {
                delegate.setUseClientMode(mode);
            }

            @Override
            public boolean getUseClientMode() {
                return delegate.getUseClientMode();
            }

            @Override
            public void setNeedClientAuth(boolean need) {
                delegate.setNeedClientAuth(need);
            }

            @Override
            public void setWantClientAuth(boolean want) {
                delegate.setWantClientAuth(want);
            }

            @Override
            public boolean getNeedClientAuth() {
                return delegate.getNeedClientAuth();
            }

            @Override
            public boolean getWantClientAuth() {
                return delegate.getWantClientAuth();
            }

            @Override
            public void setEnableSessionCreation(boolean flag) {
                delegate.setEnableSessionCreation(flag);
            }

            @Override
            public boolean getEnableSessionCreation() {
                return delegate.getEnableSessionCreation();
            }

            @Override
            public void bind(SocketAddress localAddr) throws IOException {
                delegate.bind(localAddr);
            }

            @Override
            public synchronized void close() throws IOException {
                delegate.close();
            }

            @Override
            public void connect(SocketAddress remoteAddr) throws IOException {
                delegate.connect(remoteAddr);
            }

            @Override
            public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
                delegate.connect(remoteAddr, timeout);
            }

            @Override
            public SocketChannel getChannel() {
                return delegate.getChannel();
            }

            @Override
            public InetAddress getInetAddress() {
                return delegate.getInetAddress();
            }

            @Override
            public InputStream getInputStream() throws IOException {
                return delegate.getInputStream();
            }

            @Override
            public boolean getKeepAlive() throws SocketException {
                return delegate.getKeepAlive();
            }

            @Override
            public InetAddress getLocalAddress() {
                return delegate.getLocalAddress();
            }

            @Override
            public int getLocalPort() {
                return delegate.getLocalPort();
            }

            @Override
            public SocketAddress getLocalSocketAddress() {
                return delegate.getLocalSocketAddress();
            }

            @Override
            public boolean getOOBInline() throws SocketException {
                return delegate.getOOBInline();
            }

            @Override
            public OutputStream getOutputStream() throws IOException {
                return delegate.getOutputStream();
            }

            @Override
            public int getPort() {
                return delegate.getPort();
            }

            @Override
            public synchronized int getReceiveBufferSize() throws SocketException {
                return delegate.getReceiveBufferSize();
            }

            @Override
            public SocketAddress getRemoteSocketAddress() {
                return delegate.getRemoteSocketAddress();
            }

            @Override
            public boolean getReuseAddress() throws SocketException {
                return delegate.getReuseAddress();
            }

            @Override
            public synchronized int getSendBufferSize() throws SocketException {
                return delegate.getSendBufferSize();
            }

            @Override
            public int getSoLinger() throws SocketException {
                return delegate.getSoLinger();
            }

            @Override
            public synchronized int getSoTimeout() throws SocketException {
                return delegate.getSoTimeout();
            }

            @Override
            public boolean getTcpNoDelay() throws SocketException {
                return delegate.getTcpNoDelay();
            }

            @Override
            public int getTrafficClass() throws SocketException {
                return delegate.getTrafficClass();
            }

            @Override
            public boolean isBound() {
                return delegate.isBound();
            }

            @Override
            public boolean isClosed() {
                return delegate.isClosed();
            }

            @Override
            public boolean isConnected() {
                return delegate.isConnected();
            }

            @Override
            public boolean isInputShutdown() {
                return delegate.isInputShutdown();
            }

            @Override
            public boolean isOutputShutdown() {
                return delegate.isOutputShutdown();
            }

            @Override
            public void sendUrgentData(int value) throws IOException {
                delegate.sendUrgentData(value);
            }

            @Override
            public void setKeepAlive(boolean keepAlive) throws SocketException {
                delegate.setKeepAlive(keepAlive);
            }

            @Override
            public void setOOBInline(boolean oobinline) throws SocketException {
                delegate.setOOBInline(oobinline);
            }

            @Override
            public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
                delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
            }

            @Override
            public synchronized void setReceiveBufferSize(int size) throws SocketException {
                delegate.setReceiveBufferSize(size);
            }

            @Override
            public void setReuseAddress(boolean reuse) throws SocketException {
                delegate.setReuseAddress(reuse);
            }

            @Override
            public synchronized void setSendBufferSize(int size) throws SocketException {
                delegate.setSendBufferSize(size);
            }

            @Override
            public void setSoLinger(boolean on, int timeout) throws SocketException {
                delegate.setSoLinger(on, timeout);
            }

            @Override
            public synchronized void setSoTimeout(int timeout) throws SocketException {
                delegate.setSoTimeout(timeout);
            }

            @Override
            public void setSSLParameters(SSLParameters p) {
                delegate.setSSLParameters(p);
            }

            @Override
            public void setTcpNoDelay(boolean on) throws SocketException {
                delegate.setTcpNoDelay(on);
            }

            @Override
            public void setTrafficClass(int value) throws SocketException {
                delegate.setTrafficClass(value);
            }

            @Override
            public void shutdownInput() throws IOException {
                delegate.shutdownInput();
            }

            @Override
            public void shutdownOutput() throws IOException {
                delegate.shutdownOutput();
            }

            @Override
            public String toString() {
                return delegate.toString();
            }

            @Override
            public boolean equals(Object o) {
                return delegate.equals(o);
            }
        }

        /**
         * An {@link javax.net.ssl.SSLSocket} that doesn't allow {@code SSLv3} only connections
         * <p>fixes https://github.com/koush/ion/issues/386</p>
         */
        private static class NoSSLv3SSLSocket extends DelegateSSLSocket {

            private NoSSLv3SSLSocket(SSLSocket delegate) {
                super(delegate);

                String canonicalName = delegate.getClass().getCanonicalName();
                if (!canonicalName.equals("org.Apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl")) {
                    // try replicate the code from HttpConnection.setupSecureSocket()
                    try {
                        Method msetUseSessionTickets = delegate.getClass().getMethod("setUseSessionTickets", boolean.class);
                        if (null != msetUseSessionTickets) {
                            msetUseSessionTickets.invoke(delegate, true);
                        }
                    } catch (NoSuchMethodException ignored) {
                    } catch (InvocationTargetException ignored) {
                    } catch (IllegalAccessException ignored) {
                    }
                }
            }

            @Override
            public void setEnabledProtocols(String[] protocols) {
                if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
                    // no way jose
                    // see issue https://code.google.com/p/Android/issues/detail?id=78187
                    List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols()));
                    if (enabledProtocols.size() > 1) {
                        enabledProtocols.remove("SSLv3");
                    }
                    protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
                }
                super.setEnabledProtocols(protocols);
            }
        }

    }

et quelque part dans votre code, avant de créer la connexion:

    static {
    HttpsURLConnection.setDefaultSSLSocketFactory(new NoSSLv3Factory());
}

Ce code provient de https://code.google.com/p/Android/issues/detail?id=78187 , où vous pouvez trouver une explication complète de la raison pour laquelle cela se produit dans Android 4.X.

Cela fait une semaine que je suis en production et cela semble avoir fonctionné.

18
GaRRaPeTa

J'ai pris answer de @ GaRRaPeTa et je l'ai intégrée dans un appel de méthode simple mort. Vous pouvez utiliser la bibliothèque NetCipher pour obtenir une configuration TLS moderne lorsque vous utilisez la variable HttpsURLConnection d'Android. NetCipher configure l'instance `HttpsURLConnection pour utiliser la meilleure version TLS prise en charge, supprime la prise en charge de SSLv3 et configure la meilleure suite de chiffrements pour cette version TLS. Tout d’abord, ajoutez-le à votre build.gradle:

compile 'info.guardianproject.netcipher:netcipher:1.2'

Ou vous pouvez télécharger le fichier netcipher-1.2.jar et l'inclure directement dans votre application. Alors au lieu d'appeler:

HttpURLConnection connection = (HttpURLConnection) sourceUrl.openConnection();

Appelez ceci:

HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);
7

La ou les solutions ci-dessus ne fonctionnaient pas pour moi, c’est donc ce que j’ai appris et fait pour surmonter ce problème. 

Pour les appareils plus anciens que Android 5.0, le fournisseur de sécurité par défaut possédait les propriétés suivantes:

  1. Les protocoles TSLv1 et TSLv2 n'étaient pas activés par défaut 
  2. Le protocole SSLv3 n'est pas désactivé par défaut.

Une solution qui a fonctionné pour moi ici est de patcher le "Fournisseur" si nécessaire lors du démarrage de l'application, afin que SSLv3 ne soit plus sur sa liste de protocoles. Une façon simple de patcher Android depuis votre application est la suivante: (sachant que vous avez accès aux services Google Play Store)

private void updateAndroidSecurityProvider(Activity callingActivity) {
    try {
        ProviderInstaller.installIfNeeded(this);
    } catch (GooglePlayServicesRepairableException e) {
        // Thrown when Google Play Services is not installed, up-to-date, or enabled
        // Show dialog to allow users to install, update, or otherwise enable Google Play services.
        GooglePlayServicesUtil.getErrorDialog(e.getConnectionStatusCode(), callingActivity, 0);
    } catch (GooglePlayServicesNotAvailableException e) {
        Log.e("SecurityException", "Google Play Services not available.");
    }
}

Jetez un oeil à: https://developer.Android.com/training/articles/security-gms-provider.html?#patching pour plus d'informations. 

3
Meriton Husaj

En plus de la réponse de @ GaRRaPeTa, assurez-vous que la méthode makeSocketsafe détermine si le socket n'est pas encore converti en NoSSLv3SSLSocket pour éviter les problèmes de Stackoverflow:

    private static Socket makeSocketSafe(Socket socket) {
        if (socket instanceof SSLSocket && !(socket instanceof NoSSLv3SSLSocket)) {
            socket = new NoSSLv3SSLSocket((SSLSocket) socket);
        }
        return socket;
    }

PS. Vous ne pouvez pas commenter afin que ce soit sur un post séparé.

2
Pier Betos

Vous devez également savoir que vous pouvez forcer TLS v1.2 pour les périphériques Android 4.0 qui ne l’ont pas activé par défaut:

Cela devrait être dans la première ligne de votre application:

 try {
            ProviderInstaller.installIfNeeded(getApplicationContext());
            SSLContext sslContext;
            sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, null, null);
            sslContext.createSSLEngine();
        } catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException
                | NoSuchAlgorithmException | KeyManagementException e) {
            e.printStackTrace();
        }
0
Mayur Gangurde

J'ai récemment testé cela en utilisant SSLContext (car j'avais besoin d'un accès à Trustmanager) au lieu de mettre en œuvre ma propre NoSSLv3Factory et jusqu'à présent, je n'ai eu aucun problème.

private getSSLContext()
{
    /* Load the keyStore that includes self-signed cert as a "trusted" entry. */
    KeyStore keyStore = ...  //optional
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); //optional
    tmf.init(keyStore); //optional

    //This is the important line, specifying the cipher to use and cipher provider
    SSLContext sslContext = SSLContext.getInstance("TLSv1","AndroidOpenSSL");
    ctx.init(null, tmf.getTrustManagers(), null); //if trustmanager not used pass null as the second parameter    
    return sslContext;
}

Vous pouvez ensuite utiliser ceci dans votre objet HttpsURLConnection comme ceci:

...
URL url = new URL("https://yourwebapp.com/");
HttpsURLConnection webConnection = (HttpsURLConnection)url.openConnection();
webConnection.setSSLSocketFactory(getSSLContext())
...    

Cela signifie cependant que vous devrez rester au-dessus des vulnérabilités TLS et modifier le chiffrement spécifié si des vulnérabilités SSL/TLS sont divulguées publiquement.

Une liste des chiffrements et fournisseurs supportés que vous pouvez utiliser est listée ici

Le premier bloc de code, mineur de la modification clé pour ce scénario, était principalement tiré de this SO answer

0
skyjacks