I am building an App that fetches some JSON
data via HTTPS
, using Retrofit with a custom OkHttp
client. It works fine on KitKat. Once I move to Android 5, 6 or 7, SSL
handshakes fail.
The server supports TLSv1
and nothing else. It also uses an ancient, expired, self-signed certificate. Tested it with the Qualys' SSL tool, which told me that all versions of Android should be able to connect. Here's what I've got:
public class HTTPClient {
public static OkHttpClient getUnsafeOkHttpClient() {
try {
// Create a trust manager that does not validate certificate chains
final TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
// Create a ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
//URL url = new URL(ApiIntentService.getHostAddress());
//final SSLSocketFactory sslSocketFactory = new NoSSLv3SocketFactory(url);
// ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS)
// .tlsVersions(TlsVersion.TLS_1_0)
// .cipherSuites(
// CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
// CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
// CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
// CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
// CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
// CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
// CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
// CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
// CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA)
// .build();
// String hostname = "api.server.domain";
// CertificatePinner certificatePinner = new CertificatePinner.Builder()
// .add(hostname, "sha256/3Iiwgs3a0qjPCnBQzW/GeHhPbZvhaJtxKvMJJVO5KdU=")
// .build();
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
// builder.connectionSpecs(Collections.singletonList(spec));
// builder.certificatePinner(certificatePinner);
builder.sslSocketFactory(sslSocketFactory);
builder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
builder.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic("user", "pass");
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
});
OkHttpClient okHttpClient = builder.build();
return okHttpClient;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
I am aware how terribly butchered in terms of security this code is. My supervisor demanded it to be that way, and I informed him how bad this is.
I have tried 2 things to solve my problem, they're commented out in that piece of code - certificate pinning and requesting TLSv1
specifically, with a list of ciphers. Found those two in other questions, but they changed nothing (stack trace is exactly the same).
Here are the interesting bits of the stack trace:
I/RETROFIT: Data retrieval failed! javax.net.ssl.SSLHandshakeException: Handshake failed
*snip*
Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb43eb200: 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:770 0xac6fedd4:0x00000000)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
It looks as if Android tries to use SSLv3
all of a sudden, but Wireshark shows communication via TLSv1
. It starts with Client Hello
, as it should, but the server immediately answers with Handshake Failure (40)
.
All help is greatly appreaciated, since I'm all out of ideas. Please ask for clarification if it's needed.
Thank you.
The solution for me was adding more ciphers as acceptable for OkHttpClient. Since API 21, some TLS certificates are deprecated for Android. This might help:
ConnectionSpec spec = new
ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_2)
.cipherSuites(
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)
.build();
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Collections.singletonList(spec))
.build();
For more info please visit: https://github.com/square/okhttp/wiki/HTTPS