如何在 android 中禁用 HttpsUrlConnection 的 SSLv3?

问题描述 投票:0回答:9

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

我们遵循了甲骨文捕获的指南

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

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

这个解决方案适用于普通的 java 程序。

当我们尝试连接仅适用于 SSLv3 协议的服务器时,我们得到了
SSLHandShakeException

但令人担忧的是:同样的修复不适用于Android。我是否遗漏了什么或者我应该尝试另一种 Android 方法?请推荐。

java android client httpsurlconnection poodle-attack
9个回答
48
投票
我通过使用wireshark分析数据包找到了解决方案。我发现,在建立安全连接时,android 从

TLSv1 回落到 SSLv3 。这是 Android 版本的一个错误 < 4.4 , and it can be solved by removing the SSLv3 protocol from Enabled Protocols list. I made a custom socketFactory class called NoSSLv3SocketFactory.java. Use this to make a 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 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

 限制启用的协议。

不要忘记添加以下依赖项(

此处找到最新版本):

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

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

链接


15
投票
受到 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);
    

6
投票
起初我尝试了 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

),您可以在
这里进行。


4
投票
使用此代码片段,如果服务器启用了 SSLv3,则握手将失败。

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

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

使用 TSL 创建安全性的 HttpsURLConnection 失败,Android 实现将回退到 SSLV3 进行连接。

请参考此

http://code.google.com/p/android/issues/detail?id=78431


0
投票
使用在 Android 上运行的

PlayService 发布者客户端库,我在运行 sample 时遇到了同样的问题。

用上面@bhavit-s-sengar 的 awnser 修复了它。 还必须将

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

0
投票
与 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();

希望它会起作用:)


0
投票
其实我们不需要禁用SSLV3或TLSV1.0,我们只需要在android中启用TLSV1.1或TLSv1.2即可

< 5 devices.

问题是 Android 上未启用 TLSv1.1 和 TLSv1.2

<5 by default and to connect using these latest secure protocol we must have to enable in Android <5 devices.

这个解决方案解决了我的问题:

https://stackoverflow.com/a/45853669/3448003


0
投票
TLS 实施在旧设备上可能仍然存在问题。但您可以通过这种方式将新应用程序打包到您的应用程序中:

implementation 'org.conscrypt:conscrypt-android:1.0.1'

Security.insertProviderAt(Conscrypt.newProvider(), 1);
之后一切对我来说都很好。

但 apk 大小会受到影响。

© www.soinside.com 2019 - 2024. All rights reserved.