Как отключить SSLv3 в Android для HttpsUrlConnection?

Мы написали клиентское приложение на Android, которое подключается к https-серверам с помощью API HttpsUrlConnection. Из-за уязвимости Poodle нам необходимо отключить SSLv3 из списка включенных протоколов при вызове любого запроса.

Мы следовали рекомендациям, сформулированным оракул

и добавил следующую строку перед вызовом URL-соединения

java.lang.System.setProperty("https.protocols", "TLSv1");

Это решение отлично работает с обычной программой Java.
Мы получили SSLHandShakeException при попытке подключения к серверу, который работает только по протоколу SSLv3.

Но беспокоит то же исправление не работает для Android. Я что-то упустил или попробовать другой подход для Android? Пожалуйста, предложите.


person user1375399    schedule 30.10.2014    source источник
comment
Вы пытались создать собственный SSLContext и передать его SocketFactory в HttpsUrlConnection?   -  person Selvin    schedule 30.10.2014
comment
Можете ли вы поделиться URL-адресом службы, которая поддерживает только соединение SSLv3?   -  person Hardik Muliya    schedule 30.10.2014
comment
Привет, Селвин, не пробовал с SSLContext. Я постараюсь обновить вас. Любой быстрый указатель/фрагмент кода будет отличным.   -  person user1375399    schedule 30.10.2014


Ответы (8)


Я нашел решение для этого, проанализировав пакеты данных с помощью wireshark. Я обнаружил, что при создании безопасного соединения Android откатывается к SSLv3 с TLSv1 . Это ошибка в версиях Android ‹ 4.4, и ее можно решить, удалив протокол SSLv3 из списка включенных протоколов. Я создал собственный класс socketFactory с именем NoSSLv3SocketFactory.java. Используйте это, чтобы создать socketfactory.

/*Copyright 2015 Bhavit Singh Sengar
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.*/

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;


public class NoSSLv3SocketFactory extends SSLSocketFactory{
    private final SSLSocketFactory delegate;

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

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

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

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

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

private class NoSSLv3SSLSocket extends DelegateSSLSocket {

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

    }

    @Override
    public void setEnabledProtocols(String[] protocols) {
        if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {

            List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols()));
            if (enabledProtocols.size() > 1) {
                enabledProtocols.remove("SSLv3");
                System.out.println("Removed SSLv3 from enabled protocols");
            } else {
                System.out.println("SSL stuck with protocol available for " + String.valueOf(enabledProtocols));
            }
            protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
        }

        super.setEnabledProtocols(protocols);
    }
}

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

Используйте этот класс следующим образом при подключении:

SSLContext sslcontext = SSLContext.getInstance("TLSv1");

            sslcontext.init(null,
                    null,
                    null);
            SSLSocketFactory NoSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());

            HttpsURLConnection.setDefaultSSLSocketFactory(NoSSLv3Factory);
            l_connection = (HttpsURLConnection) l_url.openConnection();
            l_connection.connect();

ОБНОВЛЕНИЕ:

Теперь правильным решением будет установить более новый поставщик безопасности с помощью Сервисы Google Play:

    ProviderInstaller.installIfNeeded(getApplicationContext());

Это фактически дает вашему приложению доступ к более новой версии OpenSSL и Java Security Provider, которая включает поддержку TLSv1.2 в SSLEngine. После установки нового провайдера вы можете создать SSLEngine, который поддерживает SSLv3, TLSv1, TLSv1.1 и TLSv1.2, как обычно:

    SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
    sslContext.init(null, null, null);
    SSLEngine engine = sslContext.createSSLEngine();

Или вы можете ограничить включенные протоколы, используя engine.setEnabledProtocols.

Не забудьте добавить следующую зависимость (последняя версия найдена здесь):

compile 'com.google.android.gms:play-services-auth:11.8.0'

Для получения дополнительной информации перейдите по этой ссылке.

person Bhavit S. Sengar    schedule 29.04.2015
comment
Благодарю вас! Ваше решение идеально подходит для включения TLS v1.2 с использованием okhttp 2.3.0 для телефонов ниже версии 20. - person prijupaul; 07.05.2015
comment
Что вы подразумеваете под списком включенных протоколов? Это что-то вроде настройки, которую я могу найти в файле, таком как Android Manifest? Если я просто хочу отключить SSLv3, мне нужно взять ваш класс NoSSLv3SocketFactory и использовать его в моем коде вместо текущей реализации. Извините, я запутался и новичок. - person HelpMatters; 02.11.2015
comment
Каждая версия Android поддерживает ряд протоколов, вы можете проверить это здесь developer.android.com/reference/javax/net/ssl/ - person Bhavit S. Sengar; 04.11.2015
comment
Ваш код не работает, потому что setEnabledProtocols не вызывается и из-за проверки protocols.length == 1 - person user3316041; 18.01.2016
comment
не должен ли приведенный выше фрагмент также установить версию TLS на TLSv1.2 вместо TLSv1? - person source.rar; 05.05.2016
comment
Вы определенно спасли мой день! Я столкнулся с этой проблемой и попробовал несколько разных решений, и ни одно из них не сработало (например, указание TLSv1.1 и т. д.). Ваше решение, удаляющее SSLv3 из поддерживаемых протоколов, сработало хорошо и не сломалось ни на одном другом устройстве. Забавно, раньше это работало на всех устройствах, но только не на Galaxy S3 Mini от Samsung... - person byemute; 04.07.2016
comment
@BhavitS.Sengar, как я могу использовать его в запросе loopj get / в Ion get - person CLIFFORD P Y; 20.12.2016
comment
Для справки, рассматриваемая ошибка: code.google.com/p/ android/issues/detail?id=78187 - person bcoughlan; 21.12.2016
comment
Я просто хотел бы найти ваш ответ несколько дней назад! Спасибо! - person Leo supports Monica Cellio; 24.07.2018
comment
Я только что использовал ProviderInstaller.installIfNeeded(getApplicationContext()); и это решило проблему. Спасибо. - person tsig; 20.12.2018

Вдохновленный ответом Бхавита С. Сенгара, он объединил эту технику в очень простой вызов метода. Вы можете использовать библиотеку NetCipher, чтобы получить современную конфигурацию TLS при использовании Android HttpsURLConnection. NetCipher настраивает экземпляр HttpsURLConnection для использования наилучшей поддерживаемой версии TLS, удаляет поддержку SSLv3 и настраивает лучший набор шифров для этой версии TLS. Сначала добавьте его в свой build.gradle:

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

Или вы можете загрузить файл netcipher-1.2.jar и включить его непосредственно в свое приложение. Затем вместо вызова:

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

Назовите это:

HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);
person Hans-Christoph Steiner    schedule 08.09.2015
comment
Что делать, если я хочу использовать OKHttp? - person IgorGanapolsky; 23.10.2015
comment
Я попробовал это, и я получаю сетевой шифр sslsocketfactory, но протокол SSLv3 все еще отображается при проверке его в отладчике. Это на андроиде 5.1. Все, что я пробовал, включая мой собственный класс sslsocketfactory, где я либо отключаю SSLv3, либо включаю только TLS..., приводит к этому; протокол SSLv3 все еще отображается. - person wkhatch; 15.04.2016
comment
Является ли эта библиотека такой же, как использование сокета NoSSLv3, упомянутого в выбранном ответе, или она каким-либо образом отличается? - person source.rar; 05.05.2016
comment
Да, он использует очень похожий подход, как NoSSLv3SocketFactory - person Hans-Christoph Steiner; 07.05.2016
comment
Мы работаем над добавлением поддержки OkHTTP, Volley и Apache HttpClient для Android в ту же библиотеку NetCipher. - person Hans-Christoph Steiner; 07.05.2016
comment
@Hans-ChristophSteiner, включит ли это поддержку TLS для версий Android ниже 4.1? - person Skynet; 29.06.2016
comment
TLS v1.0 поддерживается Android с первой версии, поэтому поддержка SSLv2 и SSLv3 отключена. android-20 — первая версия Android, поддерживающая TLS v1.1 и v1.2. developer.android.com/reference/javax/net/ssl/SSLEngine. html - person Hans-Christoph Steiner; 01.08.2016
comment
NetCipher теперь поддерживает OkHTTP, Volley и т. д. Получите его с помощью gradle, используя: compile 'info.guardianproject.netcipher:netcipher-okhttp3:2.0.0-alpha1' - person Hans-Christoph Steiner; 01.11.2016
comment
Поддерживает ли он Apache HttpClient? - person Pallavi; 24.05.2017
comment
Да, вы можете использовать его с ч.бой HttpClient или Apache HttpClient для Android - person Hans-Christoph Steiner; 29.05.2017

Сначала я попробовал ответ Бхавита С. Сенгара и это работало в большинстве случаев. Но иногда возникают проблемы, даже если протокол SSLv3 был удален из включенных протоколов на устройстве Android 4.4.4. Таким образом, библиотека NetCipher от Hans-Christoph Steiner идеально подходит для решения этой проблемы, насколько я мог ее протестировать. .

Мы используем jsoup, чтобы сделать кучу веб-скрапинга на разных серверах, поэтому мы не можем установить HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);. Я предполагаю, что это та же проблема, если вы используете OkHttp.

Лучшее решение, к которому мы пришли, — установить info.guardianproject.netcipher.client.TlsOnlySocketFactory из NetCipher как DefaultSSLSocketFactory в статическом блоке. Итак, он установлен для всего времени выполнения нашего приложения:

SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
SSLSocketFactory noSSLv3Factory = new TlsOnlySocketFactory(sc.getSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(noSSLv3Factory);

Если вы хотите просмотреть все детали (с помощью trustAllCertificates), вы можете сделать это здесь.

person jonastheis    schedule 20.01.2016
comment
разве это не должно быть TLSv1.2? - person source.rar; 05.05.2016
comment
Большое спасибо бро! вы сделали мой день ! - person Fayçal; 24.01.2017
comment
все еще SSLHandshakeException - person Pradeep Bishnoi; 23.01.2018

используйте этот фрагмент кода, если сервер поддерживает SSLv3, тогда он не сможет установить связь.

        SocketFactory sf = SSLSocketFactory.getDefault();
        SSLSocket socket = (SSLSocket) sf.createSocket("host-name", 443);
        socket.setEnabledProtocols(new String[] { "TLSv1"});
        socket.startHandshake();
person Amit Rathore    schedule 30.10.2014
comment
Я все еще получаю ошибку javax.net.ssl.SSLProtocolException: SSL handshake failed. - person IgorGanapolsky; 23.10.2015
comment
Это должен быть TLSv1.2 - person savvisingh; 01.09.2017

 SSLContext sslContext = SSLContext.getInstance("TLSv1");
                sslContext.init(null, null, null);
                SSLSocketFactory socketFactory = sslContext.getSocketFactory();
                            httpURLConnection.setSSLSocketFactory(socketFactory);

При подключении HttpsURLConnection с использованием TSL создать безопасность не удалось, реализация Android переключится на SSLV3 для подключения.

См. этот http://code.google.com/p/android/issues/detail?id=78431

person kamal_tech_view    schedule 16.04.2015
comment
Почему вы запускаете sslContext со всеми нулями? - person IgorGanapolsky; 23.10.2015

Используя клиентские библиотеки PlayService для издателей, работающие на Android, я столкнулся с та же проблема при запуске образца.

Исправлено с помощью awnser @bhavit-s-sengar выше. Пришлось также изменить AndroidPublisherHelper.newTrustedTransport() на это:

SSLContext sslcontext = SSLContext.getInstance("TLSv1");
sslcontext.init(null, null, null);
//  NoSSLv3SocketFactory is @bhavit-s-sengar's http://stackoverflow.com/a/29946540/8524
SSLSocketFactory noSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());

NetHttpTransport.Builder netTransportBuilder = new NetHttpTransport.Builder();
netTransportBuilder.setSslSocketFactory(noSSLv3Factory);
HTTP_TRANSPORT = netTransportBuilder.build();
person Diederik    schedule 03.09.2015

Подключается к серверу https, нам нужен сертификат для рукопожатия со стороны клиента. 1 год назад я решил аналогичную проблему, используя сертификат самоподписания, следующим образом:

import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
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.TrustManager;
import javax.net.ssl.X509TrustManager;

public class HttpsTrustManager implements X509TrustManager {

private static TrustManager[] trustManagers;
private static final X509Certificate[] _AcceptedIssuers = new X509Certificate[]{};

@Override
public void checkClientTrusted(
        java.security.cert.X509Certificate[] x509Certificates, String s)
        throws java.security.cert.CertificateException {

}

@Override
public void checkServerTrusted(
        java.security.cert.X509Certificate[] x509Certificates, String s)
        throws java.security.cert.CertificateException {

}

public boolean isClientTrusted(X509Certificate[] chain) {
    return true;
}

public boolean isServerTrusted(X509Certificate[] chain) {
    return true;
}

@Override
public X509Certificate[] getAcceptedIssuers() {
    return _AcceptedIssuers;
}

public static void allowAllSSL() {
    HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {

        @Override
        public boolean verify(String arg0, SSLSession arg1) {
            return true;
        }

    });

    SSLContext context = null;
    if (trustManagers == null) {
        trustManagers = new TrustManager[]{new HttpsTrustManager()};
    }

    try {
        context = SSLContext.getInstance("TLS");
        context.init(null, trustManagers, new SecureRandom());
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (KeyManagementException e) {
        e.printStackTrace();
    }

    HttpsURLConnection.setDefaultSSLSocketFactory(context
            .getSocketFactory());
}

}

Использование на стороне клиента перед HttpsUrlConnection

HttpsTrustManager.allowAllSSL();

надеюсь получится :)

person Mostafizur Rahman    schedule 30.11.2016

На самом деле нам не нужно отключать SSLV3 или TLSV1.0, нам просто нужно включить TLSV1.1 или TLSv1.2 на устройствах Android ‹ 5.

Проблема заключается в том, что TLSv1.1 и TLSv1.2 не включены на Android ‹ 5 по умолчанию, и для подключения с использованием этого последнего безопасного протокола мы должны включить его на устройствах Android ‹ 5.

Это решение устранило мою проблему: https://stackoverflow.com/a/45853669/3448003

person Bajrang Hudda    schedule 24.08.2017