【问题标题】:How to disable SSLv3 in android for HttpsUrlConnection?如何在 android 中为 HttpsUrlConnection 禁用 SSLv3?
【发布时间】:2014-12-26 06:44:52
【问题描述】:

我们在 android 中编写了客户端应用程序,它使用 HttpsUrlConnection API 与 https 服务器连接。由于 Poodle 漏洞,我们需要在调用任何请求时从启用的协议列表中禁用 SSLv3。

我们跟踪了oracle捕获的guidelines

并在调用 url 连接之前添加以下行

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

此解决方案适用于普通的 java 程序。
当我们尝试连接仅适用于 SSLv3 协议的服务器时,我们得到了SSLHandShakeException

但问题是:同样的修复不适用于 android。我是否遗漏了某些东西,或者我应该为 android 尝试另一种方法?请提出建议。

【问题讨论】:

  • 您是否尝试创建自己的 SSLContext 并将其 SocketFactory 传递给 HttpsUrlConnection ?
  • 可以分享一个只支持 SSLv3 连接的服务 url 吗?
  • 嗨 Selvin,未尝试使用 SSLContext。我会尝试更新你。任何快速指针/代码 sn-p 都会很棒。

标签: java android client httpsurlconnection poodle-attack


【解决方案1】:

我通过使用wireshark分析数据包找到了解决方案。我发现,在建立安全连接时,android 从 TLSv1 回退到 SSLv3 。这是 android 版本

/*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 Services 安装更新的安全提供程序:

    ProviderInstaller.installIfNeeded(getApplicationContext());

这有效地使您的应用可以访问更新版本的 OpenSSL 和 Java 安全提供程序,其中包括对 SSLEngine 中的 TLSv1.2 的支持。安装新的提供程序后,您可以按照通常的方式创建支持 SSLv3、TLSv1、TLSv1.1 和 TLSv1.2 的 SSLEngine:

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

或者您可以使用engine.setEnabledProtocols 限制启用的协议。

不要忘记添加以下依赖项(latest version found here):

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

欲了解更多信息,请查看此link

【讨论】:

  • 谢谢!您的解决方案非常适合使用 okhttp 2.3.0 为版本低于 20 的手机启用 TLS v1.2。
  • 你绝对拯救了我的一天!我遇到了这个问题并尝试了一些不同的解决方案,但都没有奏效(比如指定 TLSv1.1 等)。您的解决方案从支持的协议中删除了 SSLv3,效果很好,并且没有在任何其他设备上中断。有趣的是,它以前适用于所有设备,但不适用于三星的 Galaxy S3 Mini...
  • 供参考,有问题的错误:code.google.com/p/android/issues/detail?id=78187
  • 我只是希望我能在几天前​​找到你的答案!谢谢!
  • 我刚刚使用了 ProviderInstaller.installIfNeeded(getApplicationContext());它解决了这个问题。谢谢。
【解决方案2】:

受 Bhavit S. Sengar 的 answer 的启发,它将该技术捆绑到一个非常简单的方法调用中。在使用 Android 的 HttpsURLConnection 时,您可以使用 NetCipher 库来获取现代 TLS 配置。 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);

【讨论】:

  • 如果要使用OKHttp怎么办?
  • 我试过了,我得到了网络密码 sslsocketfactory,但是,在调试器中检查 SSLv3 协议时仍然显示它。这是在安卓 5.1 上。我尝试过的一切,包括我自己的自定义 sslsocketfactory 类,我要么禁用 SSLv3 要么只启用 TLS ......导致了这种情况; SSLv3 协议仍然显示。
  • 这个库与使用所选答案中提到的 NoSSLv3 套接字相同还是有任何不同?
  • 是的,它使用与NoSSLv3SocketFactory非常相似的方法
  • 我们正在努力为同一 NetCipher 库添加对 Android 的 OkHTTP、Volley 和 Apache HttpClient 的支持。
【解决方案3】:

起初我尝试了 Bhavit S. Sengar 的 answer,它适用于大多数情况。但有时即使在 Android 4.4.4 设备上从启用的协议中删除 SSLv3 协议时也会出现问题。因此,Hans-Christoph Steiner 的 NetCipher 库非常适合解决这个问题,只要我可以测试它。

我们使用jsoup在不同的服务器上做一堆网页抓取,所以我们不能设置HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);。如果您使用 OkHttp,我认为这是同样的问题。

我们找到的最佳解决方案是将 NetCipher 中的 info.guardianproject.netcipher.client.TlsOnlySocketFactory 设置为静态块中的 DefaultSSLSocketFactory。所以它是为我们应用程序的整个运行时设置的:

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

如果您想查看完整的详细信息(使用trustAllCertificates),您可以使用here

【讨论】:

  • 不应该是TLSv1.2吗?
  • 非常感谢兄弟!你让我开心!
  • 还是 SSLHandshakeException
【解决方案4】:

使用此代码 sn-p,如果服务器启用 SSLv3,则握手将失败。

        SocketFactory sf = SSLSocketFactory.getDefault();
        SSLSocket socket = (SSLSocket) sf.createSocket("host-name", 443);
        socket.setEnabledProtocols(new String[] { "TLSv1"});
        socket.startHandshake();

【讨论】:

  • 我仍然收到 javax.net.ssl.SSLProtocolException: SSL handshake failed 错误。
  • 应该是TLSv1.2
【解决方案5】:
 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

【讨论】:

  • 你为什么要用所有的空值来初始化 sslContext?
【解决方案6】:

使用在 Android 上运行的PlayService publisher client libraries 我在运行sample 时遇到了同样的问题。

用上面的@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();

【讨论】:

    【解决方案7】:

    与 https 服务器连接,我们需要来自客户端的握手证书。一年前,我通过以下方式使用自签名证书解决了类似的问题-

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

    希望它会起作用:)

    【讨论】:

      【解决方案8】:

      其实我们不需要禁用 SSLV3 或 TLSV1.0,我们只需要在 android

      问题是 TLSv1.1 和 TLSv1.2 默认未在 Android

      这个解决方案解决了我的问题:https://stackoverflow.com/a/45853669/3448003

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-25
        • 2014-12-13
        • 2015-10-15
        • 2014-06-26
        • 1970-01-01
        相关资源
        最近更新 更多