Android 在 HttpsURLConnection 中禁用 SSL 作为协议
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26633349/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Disable SSL as a protocol in HttpsURLConnection
提问by GaRRaPeTa
Due to the POODLEvulnerability, my server, hosted in Amazon AWS does no longer support SSLv3.
由于POODLE漏洞,我托管在 Amazon AWS 中的服务器不再支持 SSLv3。
As a result, the first HTTPS connection my Android app does against the server results in an error when the connection was being established.
因此,在建立连接时,我的 Android 应用程序针对服务器进行的第一个 HTTPS 连接会导致错误。
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)
The error happens only in the first request. Subsequent requests work for some time.
错误仅发生在第一个请求中。后续请求会工作一段时间。
To fix this I'm trying to remove SSL from the list of protocols accepted by the Android client, and ensure I'm going only with TLS. To do this, I set a custom SSLSocketFactory which removes SSL from the list of enabled protocols and supported cypher suites.
为了解决这个问题,我试图从 Android 客户端接受的协议列表中删除 SSL,并确保我只使用 TLS。为此,我设置了一个自定义 SSLSocketFactory,它从启用的协议和支持的密码套件列表中删除 SSL。
/**
* 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()]);
}
}
As you see, my SSLSocketFactory delegates into another SSLSocketFactory and what it does is simply removing SSL from the list of enabled protocols.
如您所见,我的 SSLSocketFactory 委托给另一个 SSLSocketFactory,它所做的只是从启用的协议列表中删除 SSL。
I establish this factory as
我把这家工厂建立为
final TLSOnlySocketFactory tlsOnlySocketFactory = new TLSOnlySocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
HttpsURLConnection.setDefaultSSLSocketFactory(tlsOnlySocketFactory);
This does NOT fix the issue. From time to time, I still see the error when the connection was established. Oddly enough, this does not fix it, but it clearly minimises the occurrences of the issue.
这不能解决问题。有时,我仍然会在建立连接时看到错误。奇怪的是,这并没有解决它,但它显然最大限度地减少了问题的发生。
How could I force the HttpsUrlConnection in my Android client to use only TLS?
如何强制 Android 客户端中的 HttpsUrlConnection 仅使用 TLS?
Thank you.
谢谢你。
回答by GaRRaPeTa
I think I have solved this. The fundamental idea is the same than in the code in the question (avoid SSLv3 as the only protocol available), but the code performing it is different:
我想我已经解决了这个问题。基本思想与问题中的代码相同(避免 SSLv3 作为唯一可用的协议),但执行它的代码不同:
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);
}
}
}
and somewhere in your code, before creating the Connection:
在您的代码中的某处,在创建连接之前:
static {
HttpsURLConnection.setDefaultSSLSocketFactory(new NoSSLv3Factory());
}
This code is taken from https://code.google.com/p/android/issues/detail?id=78187, where you can find a fully explanation on why this is happening in Android 4.X.
此代码取自https://code.google.com/p/android/issues/detail?id=78187,您可以在其中找到有关为什么在 Android 4.X 中发生这种情况的完整解释。
I've had this in production from one week and seems to have done the trick.
我从一个星期开始就在生产中使用它,并且似乎已经成功了。
回答by Hans-Christoph Steiner
I took @GaRRaPeTa's answerand bundled it into a dead simple method call. You can use the NetCipherlibrary to get a modern TLS config when using Android's HttpsURLConnection
. NetCipher configures the `HttpsURLConnection instance to use the best supported TLS version, removes SSLv3 support, and configures the best suite of ciphers for that TLS version. First, add it to your build.gradle:
我接受了@GaRRaPeTa 的回答并将其捆绑到一个简单的方法调用中。您可以使用NetCipher库在使用 Android 的HttpsURLConnection
. NetCipher 将 `HttpsURLConnection 实例配置为使用受支持的最佳 TLS 版本,移除 SSLv3 支持,并为该 TLS 版本配置最佳密码套件。首先,将它添加到你的build.gradle:
compile 'info.guardianproject.netcipher:netcipher:1.2'
Or you can download the netcipher-1.2.jarand include it directly in your app. Then instead of calling:
或者您可以下载netcipher-1.2.jar并将其直接包含在您的应用程序中。然后而不是调用:
HttpURLConnection connection = (HttpURLConnection) sourceUrl.openConnection();
Call this:
调用这个:
HttpsURLConnection connection = NetCipher.getHttpsURLConnection(sourceUrl);
回答by Meriton Husaj
The above solution(s) didn't work for me, therefore this is what I learned and did to overcome this issue.
上述解决方案对我不起作用,因此这是我为克服这个问题而学到的和所做的。
For older devices than Android 5.0, the default security provider had those properties:
对于 Android 5.0 之前的设备,默认安全提供程序具有以下属性:
- TSLv1 and TSLv2 protocols were not enable by default
- SSLv3 protocol is not disable by default.
- 默认情况下未启用 TSLv1 和 TSLv2 协议
- 默认情况下不禁用 SSLv3 协议。
A solution that worked for me here is to patch the "Provider" if needed when starting the app, so it will no longer have SSLv3 on it's list of protocols. A straightforward way to patch Android from your app is this: (considering you have access to Google Play Store services.)
在这里对我有用的解决方案是在启动应用程序时根据需要修补“提供程序”,因此它的协议列表中将不再包含 SSLv3。从您的应用程序修补 Android 的一种直接方法是:(考虑到您可以访问 Google Play 商店服务。)
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.");
}
}
Take a look at: https://developer.android.com/training/articles/security-gms-provider.html?#patchingfor more info.
查看:https: //developer.android.com/training/articles/security-gms-provider.html?#patching了解更多信息。
回答by Pier Betos
Aside from @GaRRaPeTa's response, please make that the makeSocketsafe method determines if the socket is not yet converted to NoSSLv3SSLSocket to prevent Stackoverflow issues:
除了@GaRRaPeTa 的响应,请让 makeSocketsafe 方法确定套接字是否尚未转换为 NoSSLv3SSLSocket 以防止 Stackoverflow 问题:
private static Socket makeSocketSafe(Socket socket) {
if (socket instanceof SSLSocket && !(socket instanceof NoSSLv3SSLSocket)) {
socket = new NoSSLv3SSLSocket((SSLSocket) socket);
}
return socket;
}
PS. Cannot comment so that it's on a separate post.
附注。不能评论,所以它在一个单独的帖子上。
回答by skyHymans
I've recently tested this using SSLContext (as I needed access to Trustmanager) instead of implementing my own NoSSLv3Factory and so far I haven't had any problems.
我最近使用 SSLContext(因为我需要访问 Trustmanager)而不是实现我自己的 NoSSLv3Factory 对此进行了测试,到目前为止我没有遇到任何问题。
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;
}
You could then use this in your HttpsURLConnection object like so:
然后您可以在您的 HttpsURLConnection 对象中使用它,如下所示:
...
URL url = new URL("https://yourwebapp.com/");
HttpsURLConnection webConnection = (HttpsURLConnection)url.openConnection();
webConnection.setSSLSocketFactory(getSSLContext())
...
This does mean that you'll have to stay on top of any TLS vulnerabilities though and modify the specified cipher if any SSL/TLS vulnerabilities are publicly disclosed.
这确实意味着您必须掌握任何 TLS 漏洞,并在公开披露任何 SSL/TLS 漏洞时修改指定的密码。
A list of supported ciphers and providers you can use are listed here
The first code block, minor the key change for this scenario, was primarily taken from this SO answer
第一个代码块,这个场景的关键变化,主要取自这个 SO answer
回答by Mayur Gangurde
Also you should know that you can force TLS v1.2 for Android 4.0 devices that don't have it enabled by default:
您还应该知道,您可以为默认情况下未启用的 Android 4.0 设备强制使用 TLS v1.2:
This should be in the first line of your 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();
}