Unfortunately I've stucked on getting my app able to use TLS 1.2 protocol on Android version < 5.0.
I use okhttp3 library for creating and firing up proper requests. Everything works perfect on devices running Lollipop +.
I am building OkHttpClient instance like this:
SSLContext context = SSLContext.getInstance("TLS"); // tried TLSv1.2
context.init(null, trustManagers, null);
List<ConnectionSpec> specs = new ArrayList<>();
specs.add(ConnectionSpec.MODERN_TLS);
specs.add(ConnectionSpec.CLEARTEXT);
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(new TlsSocketFactory(context.getSocketFactory()), (X509TrustManager)trustManagers[0])
.connectionSpecs(specs)
.build();
Inside TlsSocketFactory class I am setting enabled protocols:
private Socket enableTLSOnSocket(Socket socket) {
if(socket != null && (socket instanceof SSLSocket)) {
((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1", "TLSv1.2"});
}
return socket;
}
After executing request i get this issue:
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x67531570: 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:744 0x5dfc97e8:0x00000000)
I have made a lot of research about this kind of issue, but unfortunately none of presented solutions did the trick. I have already tried:
Installing proper security provider link
Creating TLSSocketFactory class (similar is presented here: link)
Creating NoSSLv3SocketFactory class (presented as solution here: link)
Checking if sever uses proper ciphers (and it does)
Curious thing is calling getSupportedSSLParameters().getProtocols() returns [SSLv3, TLSv1, TLSv1.1, TLSv1.2] but calling getDefaultSSLParameters().getProtocols() returns only [SSLv3, TLSv1]
Do you guys have any ideas about this problem?
Replace MODERN_TLS with COMPATIBLE_TLS. That is necessary to enable obsolete TLS versions.
See OkHttp’s HTTPS page for details.
Related
I have 2 devices in the wild that are not able to connect to my TLS v1.2 endpoint. All others seem able to, including browsers, PostMan and iOS devices.
The devices are running Android 5 & 7 (so there should not be a problem with the TLS v1.2 support).
Note: This is not a self-signed certificate. It is signed by Amazon.
Immediate thoughts were:
Android fragmentation - perhaps the devices (one is a Kindle Fire 7)
are not including the correct certificates into the OS. It wouldn't
be the first time that a device manufacturer made an odd decision
that breaks functionality.
API is being accessed via a proxy, and there actually is a Man-In-The-Middle, correctly being detected.
Fixing (1) means bundling our certificate, and leads to the usual problems when our cert expires.
I would prefer to get the user to install a debug build that confirms whether (1) or (2) is the problem. Such build would inspect the SSL Certificate provided by the server/proxy, and log that back to me.
Web Frameworks:
Retrofit v2.3.0
OkHttp v3.9.1
Question:
How do I inspect the information of the SSL Certificate that the device is seeing when hitting my endpoint?
Update per comment from #SangeetSuresh:
It turns out there are 2 different exceptions being thrown.
The Kindle Fire 7" Tablet (KFAUWI, OS 5.1.1) is throwing the one I have started to investigate, which this question is meant to have focused on. i.e. basic SSL failure.
java.security.cert.CertPathValidatorException:
Trust anchor for certification path not found.
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:331)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:232)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:114)
The LG device (LG-SP200, OS 7.1.2) is having the connection closed by the peer, which should be addressed under a new question if not solved here:
javax.net.ssl.SSLHandshakeException:
Connection closed by peer
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(NativeCrypto.java)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:360)
at okhttp3.internal.connection.RealConnection.connectTls(RealConnection.java:299)
Robby Cornelissen provided the basic answer in a comment referencing the OkHttp Response:
the information should be available from
response.handshake().peerCertificates().
A simple Interceptor was implemented to inspect the certificates, given a valid handshake:
private static class SslCertificateLogger implements Interceptor {
public static final String TAG = "SSL";
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response;
try {
response = chain.proceed(request);
} catch (Exception e) {
Log.d(TAG, "<-- HTTP FAILED: " + e);
throw e;
}
Handshake handshake = response.handshake();
if (handshake == null) {
Log.d(TAG, "no handshake");
return response;
}
Log.d(TAG, "handshake success");
List<Certificate> certificates = handshake.peerCertificates();
if (certificates == null) {
Log.d(TAG, "no peer certificates");
return response;
}
String s;
for (Certificate certificate : certificates) {
s = certificate.toString();
Log.d(TAG, s);
}
return response;
}
}
This gets added to the OkHttpClient as per normal:
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.addInterceptor(new SslCertificateLogger())
.build();
A similar solution was proposed by Sangeet Suresh that references the Retrofit Response object:
response?.raw()?.handshake() I think this will help you
Here the important information being the fact that Retrofit gives access to the raw OkHttp response in this manner.
This would not be used in an Interceptor but rather at a higher level, in the actual Retrofit handling code, after getting a Retrofit Response<> from the API.
Converting his Kotlin solution back to Java could yield something like this:
okhttp3.Response raw = httpResponse.raw();
if (raw != null) {
Handshake handshake = raw.handshake();
if (handshake != null) {
List<Certificate> certificates = handshake.peerCertificates();
if (certificates != null) {
for (Certificate certificate : certificates) {
Log.d(TAG, certificate.toString());
}
}
}
}
Both solutions work fine, provided the handshake() is not null i.e. when the handshake succeeds.
Given that this is an investigation into failed handshakes, a further step was required to "trust all certificates" (NB debug builds only!).
This has been documented many times - here is one such version:
Unsafe SSL Client (do not do this in production)
I want to proxy network traffic for an Android emulator.
I can't seem to get it to work.
My emulator is booted up using this:
emulator #Nexus_5X_API_23 -http-proxy 10.0.1.17:8888
The IP and port points to what Charles reports in the Help menu.
The SSL certificate is installed. I can open the emulator browser and Charles shows me all traffic. The browser updates as usual.
All seems good so far.
Now I attempt to run my app. My first network call goes out successfully through Charles. The response returns and Charles displays it. However the response isn't passed to the app successfully.
I've set a breakpoint in the error callback and I can see a com.android.volley.NoConnectionError which is caused by java.io.IOException: unexpected end of stream on Connection.
Why doesn't Charles pass the result back back properly to the app?
Do I need to do what's defined at the end of the configuration page on Charles?
HttpHost httpproxy = new HttpHost("192.168.0.101", 8888, "http");
httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY,httpproxy);
This doesn't seem correct - what am I missing?
In order to help solving
java.io.IOException: unexpected end of stream on Connection
issue please answer the questions in the comments.
As regards to
HttpHost httpproxy = new HttpHost("192.168.0.101", 8888, "http");
httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY,httpproxy);
it will not help you just like that.
In fact this is another way to apply a proxy which IMO is even better than '-http-proxy' because you can apply it anywhere not only for an emulator and it is only for this app build.
You need to apply the proxy in the Http Stack you use. for example:
public class ProxyHurlStack extends HurlStack {
#Override
protected HttpURLConnection createConnection(URL url) throws IOException {
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.0.1.17", 8888));
HttpURLConnection conn = (HttpURLConnection) url.openConnection(proxy);
return conn;
}
}
and then for you debug build you can use smth like:
requestQueue = Volley.newRequestQueue(context, new ProxyHurlStack());
The only way to this is that install the SSL (Secure Socket Layer) certificate at server end then no need to set any Charles proxy.
Every enterprise has two development environments,
Development ( http ) : doesn't need any SSL certificate
Production ( https ) : needs SSL certificate
When production environment installs the SSL certificate then android emulator and Volley library automatically detects the network connection and your were able to consume the apis/web services as expected.
No other solution to this problem.
I'm trying to connect to a web server for my application that requires an SSL client certificate for authentication. From the standard documentation, I can't tell how to reuse an SSL session for multiple requests (I don't want to have to a full SSL handshake for every single request as this causes major overhead). Can someone point me in the right direction?
EDITI've seen in other posts that HttpClient might be a solution but as of Android 6.0 this has been deprecated in favor of HttpsUrlConnection. Using the following code:
SSLContext sslContext;
HttpsUrlConnection connection = url.openConnection();
connection.setSSLSocketFactory(sslContext.getSocketFactory());
Does a new FULL SSL handshake occur (probably right?) everytime I create a new connection. How do I reuse the session?
I can't tell how to reuse an SSL session for multiple requests
It's automatic.
(I don't want to have to a full SSL handshake for every single request as this causes major overhead)
It would cause major overhead, if it ever happened, but it doesn't.
Does a new FULL SSL handshake occur (probably right?) everytime I create a new connection?
No.
How do I reuse the session?
It should happen by default, subject to session timeouts at the server.
I have used retrofit for google Places and direction API integration.Recently I got update from Google Maps for Work Support Team that In order to make sure your applications are not impacted you need to verify that the HTTPS client you are using supports SHA-256.
They have provided one test url(https://cert-test.sandbox.google.com) to validate that http-client is compatible or not
I have used https://cert-test.sandbox.google.com to validate with Retrofit but It is failing and giving me exception as mentioned below:
javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0x1027ce0: Failure in SSL library, usually a protocol error
To be clear, I have used plain okhttpclient with this integration.
please do needful if anybody have fix for it.
Looks that server is using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 cipher. It only started being supported in API 20 (L Preview). You can see a list of supported ciphers by API level on the SSLSocket docs.
Try running your test on a 5.0 or above device. For example, the following code is successful on a device running 5.0, but gets an SSL exception on 4.4.4 --
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().get().url("https://cert-test.sandbox.google.com/")
.build();
response = client.newCall(request).execute();
Log.d(TAG, "code = " + response.code());
but the same is also true for the equivalent URLConnection code --
HttpsURLConnection con = (HttpsURLConnection) new URL("https://cert-test.sandbox.google.com/").openConnection();
con.connect();
Log.d(TAG, "code = " + con.getResponseCode());
The issue is not retrofit or okhttp, but the limitations of the default security providers provided on older phones.
You can work around it by installing a new provider. Google makes one available through google play services, and is easy to install. One line (plus exception handling, etc).
try {
ProviderInstaller.installIfNeeded(this);
} catch (GooglePlayServicesRepairableException e) {
// Fix it
} catch (GooglePlayServicesNotAvailableException e) {
// Skip it
}
For a full example, see "Updating Your Security Provider to Protect Against SSL Exploits" from Google.
Doing this will allow the above two code blocks to run on lower API versions if the phone has Google Play installed.
DefaultHttpClient in Android 5.0 Lollipop seems to be broken. It can not set the connection to some sites that were successfully set by previous versions of Android.
For example I try to connect to https://uralsg.megafon.ru
//Create httpclient like in https://stackoverflow.com/questions/18523784/ssl-tls-protocols-and-cipher-suites-with-the-androidhttpclient
HttpClient client = new DefaultHttpClient(manager, params);
HttpGet httpGet = new HttpGet("https://uralsg.megafon.ru");
HttpResponse client = httpclient.execute(httpGet);
This code works in Android 2.3-4.4, but fails on Android 5.0 (devices and emulator) with error Connection closed by peer.
Of course this is understandable because Android 5.0 tries to connect this old server with TLSv1.2 and modern ciphers and it does not support them.
Ok, using the sample code in SSL/TLS protocols and cipher suites with the AndroidHttpClient we limit the protocol and cipher to TLSv1 and SSL_RSA_WITH_RC4_128_MD5. Now it fails with a different error:
javax.net.ssl.SSLHandshakeException: Handshake failed
caused by
error:140943FC:SSL routines:SSL3_READ_BYTES:sslv3 alert bad record mac
(external/openssl/ssl/s3_pkt.c:1286 0x7f74c1ef16e0:0x00000003)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake
And of course this code runs smoothly on Android 2.3-4.4.
I examined the traffic with wireshark:
302 4002.147873000 192.168.156.30 83.149.32.13 TLSv1 138 Client Hello
303 4002.185362000 83.149.32.13 192.168.156.30 TLSv1 133 Server Hello
304 4002.186700000 83.149.32.13 192.168.156.30 TLSv1 1244 Certificate
305 4002.186701000 83.149.32.13 192.168.156.30 TLSv1 63 Server Hello Done
307 4002.188117000 192.168.156.30 83.149.32.13 TLSv1 364 Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
308 4002.240695000 83.149.32.13 192.168.156.30 TLSv1 61 Alert (Level: Fatal, Description: Bad Record MAC)
You can see that connection was established but server alerted because it probably could not decode encrypted handshake message.
I didn't manage to connect to https://uralsg.megafon.ru using HttpClient on Android 5.0. Stock browser does connect it though. Android 2.3-4.4 connects this site in any way without any difficulties.
Is there any way to make it possible for HttpClient to connect such sites? This is only one example, I am sure there are plenty of legacy servers that couldn't be connected by Android 5.0 and HttpClient.
update: it turned out to be a bug in the back-end, not android 5, though indeed with the cipher in question.
I had the same problem. For me it turned out to be the cipher TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 which was chosen from android 5's (updated) set of default ciphers.
As soon as i removed it from the client list of acceptable ciphers, connections worked again.
The android 5 change log mentions:
AES-GCM (AEAD) cipher suites are now enabled,
I'm pretty sure this is the culprit. As soon as TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 is preferred (by the server), the connection will fail.
Note that TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 works.
My guess is that either the Android implementation of TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 is buggy, or the one of the server you're talking to.
Solutions:
Remove TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 from the available ciphers on the server (no app redeployment needed).
Remove TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 from the list of ciphers the client offers (during the CLIENT_HELLO).
You can do that on the client side by implementing your own SSLSocketFactory and calling
sslSocket.setEnabledCipherSuites(String[] suites);
on SSLSocket creation.
edit: note that this isn't necessarily an android bug, it might be that the server implementation is faulty. if your problem is indeed caused by the cipher, please leave a comment on the android bug tracker](https://code.google.com/p/android/issues/detail?id=81603). thank you!
I tried changing the cipherSuites in a custom socket factory, but that did not help. In my case, I had to remove the TLSv1.1 and TLSv1.2 protocols from the socket's EnabledProtocols. It appears that some older servers do not handle the protocol negotiation for the new protocols very well. There are various examples out there for creating a custom socket factory, such as How to override the cipherlist sent to the server by Android when using HttpsURLConnection?, and other ones for Apache sockets. That being done, I just called the following AdjustSocket method to remove the protocols.
private void AdjustSocket(Socket socket)
{
String[] protocols = ((SSLSocket) socket).getSSLParameters().getProtocols();
ArrayList<String> protocolList = new ArrayList<String>(Arrays.asList(protocols));
for (int ii = protocolList.size() - 1; ii >= 0; --ii )
{
if ((protocolList.get(ii).contains("TLSv1.1")) || (protocolList.get(ii).contains("TLSv1.2")))
protocolList.remove(ii);
}
protocols = protocolList.toArray(new String[protocolList.size()]);
((SSLSocket)socket).setEnabledProtocols(protocols);
}