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);
}
Related
I need to use SSLV3 on android 8. I use direct socket connection. I do not use http connection. My server only supports SSLV3 and for reasons I can not change the server side. I need a solution on the client side. Android 8 does not support ssl (https://developer.android.com/about/versions/oreo/android-8.0-changes.html). Could someone help me solve this problem?
25766-26143/xx.com.xxx E/GeneralError: cipherSuite SSL_RSA_WITH_RC4_128_SHA is not supported.
java.lang.IllegalArgumentException: cipherSuite SSL_RSA_WITH_RC4_128_SHA is not supported.
at com.android.org.conscrypt.NativeCrypto.checkEnabledCipherSuites(NativeCrypto.java:968)
at com.android.org.conscrypt.SSLParametersImpl.setEnabledCipherSuites(SSLParametersImpl.java:264)
at com.android.org.conscrypt.OpenSSLSocketImpl.setEnabledCipherSuites(OpenSSLSocketImpl.java:895)
at xxx.com.xxx.communication.TrustAllSSLSocketFactory.createSocket(TrustAllSSLSocketFactory.java:128)
I am trying to send a POST transaction from Xamarin.Forms using TLS1.2 but I see them arriving to the server as TLS 1.1.
I have configured Android options:
HttpClient impletemtation as Android
SSL/TLS implementation as Native TLS 1.2+
I am implementing and executing in VisualStudio 2017, and using Android 6.0 in the emulator.
Regarding the code, I set some enviroment variables:
System.Environment.SetEnvironmentVariable("MONO_TLS_PROVIDER", "btls");
System.Environment.SetEnvironmentVariable("XA_TLS_PROVIDER", "btls");
System.Environment.SetEnvironmentVariable("XA_HTTP_CLIENT_HANDLER_TYPE", "Xamarin.Android.Net.AndroidClientHandler");
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
Finally, POST is sent with:
using (HttpClient client = new HttpClient())
or
using (HttpClient client = new HttpClient(new NativeMessageHandler()))
or
using (HttpClient client = new HttpClient(new Xamarin.Android.Net.AndroidClientHandler()))
{
try
{
HttpResponseMessage responseHttp = await client.PostAsync(new Uri(new Uri(Constants.ApiBaseUrl), "authorize"), content);
...
Where Constants.ApiBaseUrl contains a url with https://<> format.
The problem is, when POST is sent I have no exceptions, but in my server I see with Wireshark the transaction as:
I have also tried in other way, using:
HttpWebRequest httpWebRequest = WebRequest.CreateHttp(new Uri(new Uri(Constants.ApiBaseUrl), "authorize"));
httpWebRequest.Method = "POST";
httpWebRequest.Credentials = CredentialCache.DefaultNetworkCredentials;
Stream sw = httpWebRequest.GetRequestStream();
sw.Write(contentByte, 0, contentByte.Length);
HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
With and without my own certificates using:
httpWebRequest.ClientCertificates = cryptoSvc.x509HostCertificates;
In this case, if I use
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
I get the exception
RestService-SendJsonDataAsync ERROR: Error: SecureChannelFailure
(**Ssl error:100000f0:SSL routines:OPENSSL_internal:UNSUPPORTED_PROTOCOL**
at /Users/builder/jenkins/workspace/xamarin-android/xamarin-android/external/mono/external/boringssl/ssl/handshake_client.c:808)
Without that line it is also arriving as TLS1.1.
Does anyone have any idea or suggestion about what is wrong in my case, please?
Thanks so much for your time and help.
I believe in Android 6.0, TLS 1.2 is enabled by default:
https://developer.android.com/reference/javax/net/ssl/SSLSocket.html
But, you have BTLS configuration and Native TLS 1.2, I think these are conflicting. BTLS is the Boring TLS implementation, designed to provide TLS 1.2 support on older versions of Android. I think you can safely remove those lines.
I'd first confirm what the server is allowing in terms of security and if possible have the server limit HTTPS connections to only TLS 1.2.
If you'd like to try forcing only TLS 1.2 connections on the client side, take a look at this answer (note this is in Java, but the process is the same):
How to set TLS version on apache HttpClient
I was able to work around this problem by downloading and installed VS 2022 Community - Preview. I then created a MAUI .net app and used the same code that I used in VS 2019.
I am using android nv-websocket-client library (both 2.0 and 1.31 versions) and I am trying to open a wss: connection; however, the connection fails with 503 Service not available error message.
Upon investigating I found that HAProxy requires the clients to use the SNI extension, otherwise such error is returned regardless of the Host: header (I am using HAProxy as a loadbalancer).
Upon investigating further (with tcpdump/wireshark) I found that the client does not send SNI, a wrong certificate is returned (for a different domain), yet the client continues with the TLS connection and actually sends the HTTP request (as if no certificate checking was performed?).
My code is basically:
ws = new WebSocketFactory().createSocket(wsurl);
ws.addHeader("Authorization", "Bearer " + Config.getToken());
ws.addListener(this);
ws.connectAsynchronously();
I didn't find an easy way to set up the SSLSocketFactory, however it seems to me that the code in nv-websocket-client just uses the SSLSocketFactory.getDefault(), which should be correct? SSLCertificateSocketFactory seems to be deprecated in favour of this approach.
Am I missing some key piece about SSL setup, is this and Android bug or is this and Android 'feature'?
Recently I acquired a new domain name to use with an existing Android application. I also bought SSL certificate from a trusted CA (Comodo). When I browse to the new domain with a web browser, everything works as expected - no errors about SSL certificate. Same with HttpUrlConnection, but for some reason Apaches HttpClient generates an SSLException:
javax.net.ssl.SSLException: hostname in certificate didn't match: my.new.domain != my.old.domain OR my.old.domain
What's more interesting, some of the devices that I tried with worked fine for about a week after the change, and stopped working after that. Other devices stopped working right away.
If I use the old domain name in client code, everything works correctly.
I'm using version 4.3.3 from here of the HttpClient for Android. I realize that converting to HttpUrlConnection would indeed solve my problem, but I'm interested in WHY this is happening - from where does the HttpClient pick up the old domain name? Is it some misconfiguration on the server, or does Apaches HttpClient have some sort of internal dns cache? Testing with a fresh emulator instance raised the same exception, so the problem is not related to caching.
My own investigations got stuck - all I could find was instructions for disabling hostname verification completely, or instructions for self-signed certificates.
That's probably because Apache HttpClient does not support SNI (server name indication), where you can have multiple certificates behind the same IP address. This means, that it does not send the target hostname inside the SSL handshake and thus the server has only the target IP address to decide which certificate it should use and just uses the default certificate for the IP - which is probably the wrong one.
As per Google recommendation, I am using HttpsURLConnection for my api-level 15 project.
My test case is very simple :
URL url = new URL(STATS);
HttpsURLConnection we = (HttpsURLConnection)url.openConnection();
InputStream in = new BufferedInputStream(we.getInputStream());
When I connect to my server over WiFi, everything works fine.
When I connect to my server over 3g, I am getting an error in my Apache logs :
Hostname 202.139.83.152 provided via SNI and hostname myserver.com provided via HTTP are different
Now the 202.139.83.152 address is the proxy address of my mobile providers APN.
I have dumped out the 'Client Hello' packet of both requests and the Handshake Protocol/Extension:server_name field contains the target hostname (myserver.com) for the wifi request but the APN proxy address for the 3g request.
Is this :
Something I have coded incorrectly
Something I have configured incorrectly on my phone (Samsung Galaxy S3)
Something I have configured incorrectly on my server
Something evil my mobile provider is doing
A bug in the Android libraries
My server is using a dedicated ip address for this vhost.
I can successfully make a request over 3g using a simple subclass of DefaultHttpClient but as my min API level is 15, I was hoping to go down the 'preferred' path.
Any suggestions would be very greatfully received. I've spent way too much time trying to get this basic thing working.
My colleague who is handling the iPhone development for this project shakes his head because his code 'just works out of the box'.
It turns out that this is a known issue.
I have found a workaround although I'm not sure how robust it is. What works for me so far is to disable the Proxy when making the connection :
HttpsURLConnection http = (HttpsURLConnection) url.openConnection(Proxy.NO_PROXY);
I hope this helps someone.
Here is my server side workaround for Apache (working for the last year on Apache2 - Apache/2.2.14).
Recompiled Apache/mod_ssl.so after having changed ssl_engine_kernel.c by removing the "return HTTP_BAD_REQUEST;" for the strcmp(host, servername) check :
if (strcmp(host, servername)) {
ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
"Hostname %s provided via SNI and hostname %s provided"
" via HTTP are different", servername, host);
//return HTTP_BAD_REQUEST; // REMOVE THIS LINE
You will still get the error log message but not the error 400 response code.