如何在android中为HttpsUrlConnection禁用SSLv3?

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

我们遵循由oracle捕获的指导方针: http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html

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

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

这个解决scheme正常的Java程序工作正常。 当试图连接一个只能使用SSLv3协议的服务器时,我们得到了SSLHandShakeException。

但担心的是 :相同的修复程序不适用于Android。 我错过了什么,或者我应该尝试另一种方法为Android? 请build议。

通过使用wireshark分析数据包,我find了解决scheme。 我发现,在build立安全连接的同时,android从TLSv1回落到SSLv3 。 这是Android版本<4.4的错误,可以通过从已启用协议列表中删除SSLv3协议来解决。 我做了一个名为NoSSLv3SocketFactory.java的自定义socketFactory类。 使用这个来创build一个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(); 

受到Bhavit S. Sengar的回答的启发,它将这种技术捆绑在一个简单的方法调用中。 使用Android的HttpsURLConnection时,您可以使用NetCipher库来获取现代TLSconfiguration。 NetCipher将HttpsURLConnection实例configuration为使用支持最好的TLS版本,删除SSLv3支持,并为该TLS版本configuration最佳的密码套件。 首先,将其添加到您的build.gradle

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

或者,您可以下载netcipher-1.2.jar,并将其直接包含在您的应用程序中。 然后,而不是调用:

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

调用这个:

 HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl); 

起初,我尝试了Bhavit S Sengar的答案 ,并且在大多数情况下都是有效的。 但有时甚至在Android 4.4.4设备上的已启用协议中删除SSLv3协议时也存在问题。 所以Hans-Christoph Steiner的NetCipher库是完全可以解决这个问题的。

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

我们最好的解决scheme是将info.guardianproject.netcipher.client.TlsOnlySocketFactory的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 ),你可以在这里做。

使用这个代码片段,如果服务器是SSLv3启用,那么将握手失败。

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

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

希望它会工作:)

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

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

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

使用在Android上运行的PlayService发布者客户端库时,运行示例时遇到同样的问题。

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

其实我们不需要禁用SSLV3或TLSV1.0,我们只需要在android <5设备中启用TLSV1.1或TLSv1.2即可。

问题是TLSv1.1和TLSv1.2默认情况下未在Android <5上启用,要使用这些最新的安全协议进行连接,我们必须在Android <5设备中启用。

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