Due to the POODLE vulnerability, my server, hosted in Amazon AWS does no longer support 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.
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.
/**
* 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.
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?
Thank you.
Also you should know that you can force TLS v1.2 for Android 4.0 devices that don't have it enabled by default:
This should be in the first line of your Application:
I took @GaRRaPeTa's answer and bundled it into a dead simple method call. You can use the NetCipher library 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:Or you can download the netcipher-1.2.jar and include it directly in your app. Then instead of calling:
Call this:
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:
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.)
Take a look at: https://developer.android.com/training/articles/security-gms-provider.html?#patching for more info.
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:
and somewhere in your code, before creating the Connection:
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.
I've had this in production from one week and seems to have done the trick.
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.
You could then use this in your HttpsURLConnection object like so:
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.
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
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:
PS. Cannot comment so that it's on a separate post.