Open decraw opened 9 years ago
Hi, I also have issues with connecting to websites using TLS. The following code works when I call the google website but not my own website that is using a CA signed certificate.
var handler = new NativeMessageHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
var client = new HttpClient(handler);
var stuff = await client.GetStringAsync("https://www.google.com");
System.Diagnostics.Debug.WriteLine (stuff);
It may be that it works with google because it is using SSLv3 instead of TLS 1.2
Google:
Supported versions: SSLv3 TLSv1.0 TLSv1.1 TLSv1.2 Deflate compression: no Supported cipher suites (ORDER IS NOT SIGNIFICANT): SSLv3 RSA_WITH_RC4_128_MD5 RSA_WITH_RC4_128_SHA RSA_WITH_3DES_EDE_CBC_SHA RSA_WITH_AES_128_CBC_SHA RSA_WITH_AES_256_CBC_SHA TLS_ECDHE_RSA_WITH_RC4_128_SHA TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (TLSv1.0: idem) (TLSv1.1: idem) TLSv1.2 RSA_WITH_RC4_128_MD5 RSA_WITH_RC4_128_SHA RSA_WITH_3DES_EDE_CBC_SHA RSA_WITH_AES_128_CBC_SHA RSA_WITH_AES_256_CBC_SHA RSA_WITH_AES_128_CBC_SHA256 RSA_WITH_AES_256_CBC_SHA256 TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_RC4_128_SHA TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
Server certificate(s): 5d1b140376f38d49f93f3f83385f890ea60bd5c6: CN=www.google.com, O=Google Inc, L=Mountain View, ST=California, C=US
Minimal encryption strength: strong encryption (96-bit or more) Achievable encryption strength: strong encryption (96-bit or more) BEAST status: protected CRIME status: protected
Our website only supports TLS 1.2:
Supported versions: TLSv1.2 Deflate compression: no Supported cipher suites (ORDER IS NOT SIGNIFICANT): TLSv1.2 RSA_WITH_AES_128_CBC_SHA RSA_WITH_AES_256_CBC_SHA
Server certificate(s): 3c66369603213fd186ede4d2b320a21e0d577f7a: CN=www.foo.bar, OU=Domain Control Validated
Minimal encryption strength: strong encryption (96-bit or more) Achievable encryption strength: strong encryption (96-bit or more) BEAST status: protected CRIME status: protected
The exception we get is this:
[MonoDroid] UNHANDLED EXCEPTION:
[MonoDroid] Javax.Net.Ssl.SSLHandshakeException: Exception of type 'Javax.Net.Ssl.SSLHandshakeException' was thrown.
[MonoDroid] at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () <IL 0x00011, 0x0004b>
[MonoDroid] at System.Runtime.CompilerServices.AsyncMethodBuilderCore.
I know that in my own project, I encountered this same issue. What I discovered was that before Android Jelly Bean, they did not support TLS 1.1 and 1.2. This caused devices running before then not to be able to connect to my server that did not accept anything less.
In JB and KitKat, devices supported this, but it was not enabled by default. This leads to them not being able to connect either.
Lollipop changed to have support enabled by default, therefore those devices worked for me.
The fix employed by myself was to make a custom SSLSocketFactory that is used to supply the OkHttp client.
It then does
SSLSocket socket = (SSLSocket) _factory.CreateSocket(address, port, localAddress, localPort);
socket.SetEnabledProtocols(socket.GetSupportedProtocols());
For each CreateSocket method in there.
And in the NativeMessageHandler constructor, I added:
var factory = new ImprovedSSLSocketFactory();
client.SetSslSocketFactory(factory);
This allowed me to communicate with TLS 1.1 and 1.2.
I can post the code better if desired, but my git skills are not great for a pull request.
Thanks adammeaney , I will try that and see if it works. I can probably put together a pull request for this code.
Adammeaney...can you provide a sample of that? Banging my newbie Android developer head against the wall over here.
I'm targeting API 19
This is a pretty lazy feeling implementation, but it works for me.
using Javax.Net.Ssl;
namespace ModernHttpClient
{
internal class ImprovedSSLSocketFactory : SSLSocketFactory
{
SSLSocketFactory _factory = (SSLSocketFactory)Default;
public override string[] GetDefaultCipherSuites()
{
return _factory.GetDefaultCipherSuites();
}
public override string[] GetSupportedCipherSuites()
{
return _factory.GetSupportedCipherSuites();
}
public override Java.Net.Socket CreateSocket(Java.Net.InetAddress address, int port, Java.Net.InetAddress localAddress, int localPort)
{
SSLSocket socket = (SSLSocket)_factory.CreateSocket(address, port, localAddress, localPort);
socket.SetEnabledProtocols(socket.GetSupportedProtocols());
return socket;
}
public override Java.Net.Socket CreateSocket(Java.Net.InetAddress host, int port)
{
SSLSocket socket = (SSLSocket)_factory.CreateSocket(host, port);
socket.SetEnabledProtocols(socket.GetSupportedProtocols());
return socket;
}
public override Java.Net.Socket CreateSocket(string host, int port, Java.Net.InetAddress localHost, int localPort)
{
SSLSocket socket = (SSLSocket)_factory.CreateSocket(host, port, localHost, localPort);
socket.SetEnabledProtocols(socket.GetSupportedProtocols());
return socket;
}
public override Java.Net.Socket CreateSocket(string host, int port)
{
SSLSocket socket = (SSLSocket)_factory.CreateSocket(host, port);
socket.SetEnabledProtocols(socket.GetSupportedProtocols());
return socket;
}
public override Java.Net.Socket CreateSocket(Java.Net.Socket s, string host, int port, bool autoClose)
{
SSLSocket socket = (SSLSocket)_factory.CreateSocket(s, host, port, autoClose);
socket.SetEnabledProtocols(socket.GetSupportedProtocols());
return socket;
}
protected override void Dispose(bool disposing)
{
_factory.Dispose();
base.Dispose(disposing);
}
public override Java.Net.Socket CreateSocket()
{
SSLSocket socket = (SSLSocket)_factory.CreateSocket();
socket.SetEnabledProtocols(socket.GetSupportedProtocols());
return socket;
}
}
}
Thanks adammeaney I'll give it a spin
Is it possible to do this without changing the NativeMessageHandler code?
Not that I was able to discover.
The problem was that the sockets normally created did not know they supported TLS 1.1 or 1.2 further back.
There may be another way to tell the device they should be supported. I have seen code using something called ServicePointManager when I was looking for this, but none of that code seemed to resolve my issues. This was the first things I discovered.
If we get lucky, @paulcbetts will patch something similar into ModernHttpClient eventually, so we can go back to just using his released package.
I tried to use your code, but it does not work for me
here is the url that does not work:
Have you a change to check if it works for you?
I wrote this code before the changes were made to use the official OkHttp client in this repo.
With the official client, I do not seem to get the same results as before. I have not yet bothered to determine why, as I have it working for now and don't have the time to deal with http clients not working quite right.
Is it someone that have a solution on this?
An official fix would be nice, but Adam's code appears to work for me with a fork of the repo as it stands today.
Can the commit to kunni80's forked repo please be reintegrated?
I'm hoping this get merged soon.
I am attempting to use ModernHttpClient to get around the mono limitation of not supporting HTTPS/TLS 1.1. For PCI reasons this is necessary in my app.
ModernHttpClient works great to resolve the issue on iPhone, but on android, I get the following when attempting an HTTP GET to a server that only support TLS 1.1 and above.