anaisbetts / ModernHttpClient

HttpClient implementations that use platform-native HTTP clients for :rocket:
MIT License
659 stars 261 forks source link

Error when connecting from Android to a server that does not support TLS 1.0 #173

Open decraw opened 9 years ago

decraw commented 9 years ago

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.

07-16 22:04:25.797 I/mono-stdout(12467): Javax.Net.Ssl.SSLException: Exception of type 'Javax.Net.Ssl.SSLException' was thrown.
07-16 22:04:25.797 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.T07-16 22:04:25.797 I/mono-stdout(12467):   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000b] in <filename unknown>:0 
07-16 22:04:25.797 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0004e] in <filename unknown>:0 
07-16 22:04:25.797 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x0002e] in <filename unknown>:0 
07-16 22:04:25.797 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x0000b] in <filename unknown>:0 
07-16 22:04:25.797 I/mono-stdout(12467):   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1+ConfiguredTaskAwaiter[OkHttp.Response].GetResult () [0x00000] in <filename unknown>:0 
07-16 22:04:25.797 I/mono-stdout(12467):   at ModernHttpClient.NativeMessageHandler+<SendAsync>d__8.MoveNext () [0x0000e] in d:\Downloads\ModernHttpClient-2.4.2\ModernHttpClient-2.4.2\src\ModernHttpClient\Android\OkHttpNetworkHandler.cs:191 
07-16 22:04:25.797 I/mono-stdout(12467): --- End of stack trace from previous location where exception was thrown ---
07-16 22:04:25.797 I/mono-stdout(12467):   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000b] in <filename unknown>:0 
07-16 22:04:25.797 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0004e] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x0002e] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x0000b] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467):   at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1+ConfiguredTaskAwaiter[System.Net.Http.HttpResponseMessage].GetResult () [0x00000] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467):   at System.Net.Http.HttpClient+<SendAsyncWorker>c__async0.MoveNext () [0x000f3] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467): --- End of stack trace from previous location where exception was thrown ---
07-16 22:04:25.801 I/mono-stdout(12467):   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000b] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0004e] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x0002e] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x0000b] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467):   at System.Runtime.CompilerServices.TaskAwaiter`1[System.Net.Http.HttpResponseMessage].GetResult () [0x00000] in <filename unknown>:0 
07-16 22:04:25.801 I/mono-stdout(12467):   at Playground.Android.MainActivity+<>c__DisplayClassa+<<OnCreate>b__7>d__d.MoveNext () [0x00181] in d:\Downloads\ModernHttpClient-2.4.2\ModernHttpClient-2.4.2\src\Playground.Android\MainActivity.cs:108 
07-16 22:04:25.801 I/mono-stdout(12467):   --- End of managed exception stack trace ---
07-16 22:04:25.801 I/mono-stdout(12467): javax.net.ssl.SSLException: SSL handshake aborted: ssl=0xb9c0c8f0: I/O error during system call, Connection reset by peer
07-16 22:04:25.801 I/mono-stdout(12467):    at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:405)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.squareup.okhttp.Connection.upgradeToTls(Connection.java:242)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.squareup.okhttp.Connection.connect(Connection.java:159)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java:175)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.squareup.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:120)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:330)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:319)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.squareup.okhttp.Call.getResponse(Call.java:271)
07-16 22:04:25.801 I/mono-stdout(12467):    at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:228)
07-16 22:04:25.805 I/mono-stdout(12467):    at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:199)
07-16 22:04:25.805 I/mono-stdout(12467):    at com.squareup.okhttp.Call.access$100(Call.java:34)
07-16 22:04:25.805 I/mono-stdout(12467):    at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:162)
07-16 22:04:25.805 I/mono-stdout(12467):    at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33)
07-16 22:04:25.805 I/mono-stdout(12467):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
07-16 22:04:25.805 I/mono-stdout(12467):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
07-16 22:04:25.805 I/mono-stdout(12467):    at java.lang.Thread.run(Thread.java:841)
Thread finished:  #3
The thread 'Unknown' (0x3) has exited with code 0 (0x0).
mazloumi commented 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.m0 (object) <IL 0x00006, 0x0003b> [MonoDroid] at Android.App.SyncContext/cAnonStorey0.<>m__0 () [0x00000] in /Users/builder/data/lanes/1879/5f55a9ef/source/monodroid/src/Mono.Android/src/Android.App/SyncContext.cs:18 [MonoDroid] at Java.Lang.Thread/RunnableImplementor.Run () [0x0000b] in /Users/builder/data/lanes/1879/5f55a9ef/source/monodroid/src/Mono.Android/src/Java.Lang/Thread.cs:36 [MonoDroid] at Java.Lang.IRunnableInvoker.n_Run (intptr,intptr) [0x00009] in /Users/builder/data/lanes/1879/5f55a9ef/source/monodroid/src/Mono.Android/platforms/android-21/src/generated/Java.Lang.IRunnable.cs:71 [MonoDroid] at (wrapper dynamic-method) object.db6b7d7d-be63-41ee-8f1c-1c176a7678e5 (intptr,intptr) <IL 0x00011, 0x0001f> [MonoDroid] --- End of managed exception stack trace --- [MonoDroid] javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb9430130: Failure in SSL library, usually a protocol error [MonoDroid] error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version (external/openssl/ssl/s23_clnt.c:741 0x97235990:0x00000000) [MonoDroid] at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:448) [MonoDroid] at com.squareup.okhttp.Connection.upgradeToTls(Connection.java:242) [MonoDroid] at com.squareup.okhttp.Connection.connect(Connection.java:159) [MonoDroid] at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java:175) [MonoDroid] at com.squareup.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:120) [MonoDroid] at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:330) [MonoDroid] at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:319) [MonoDroid] at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:241) [MonoDroid] at com.squareup.okhttp.Call.getResponse(Call.java:271) [MonoDroid] at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:228) [MonoDroid] at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:199) [MonoDroid] at com.squareup.okhttp.Call.access$100(Call.java:34) [MonoDroid] at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:162) [MonoDroid] at com.squareup.okhttp.internal.NamedRunnable.run(NamedRunnable.java:33) [MonoDroid] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) [MonoDroid] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) [MonoDroid] at java.lang.Thread.run(Thread.java:841) [MonoDroid] Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb9430130: Failure in SSL library, usually a protocol error [MonoDroid] error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version (external/openssl/ssl/s23_clnt.c:741 0x97235990:0x00000000) [MonoDroid] at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method) [MonoDroid] at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:405) [MonoDroid] ... 16 more

adammeaney commented 9 years ago

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.

Source

decraw commented 9 years ago

Thanks adammeaney , I will try that and see if it works. I can probably put together a pull request for this code.

PrintsCharming commented 8 years ago

Adammeaney...can you provide a sample of that? Banging my newbie Android developer head against the wall over here.

I'm targeting API 19

adammeaney commented 8 years ago

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;
        }
    }
}
PrintsCharming commented 8 years ago

Thanks adammeaney I'll give it a spin

jorgenstorlie commented 8 years ago

Is it possible to do this without changing the NativeMessageHandler code?

adammeaney commented 8 years ago

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.

jorgenstorlie commented 8 years ago

I tried to use your code, but it does not work for me

here is the url that does not work:

"https://appgateway.contendo.no/tngwebinfo?params=%7B%22session_id%22%3A%220913184FBDEB4ECBA6D9E0DF3BCF6A8E%22%2C%22version%22%3A%222%22%7D"

Have you a change to check if it works for you?

adammeaney commented 8 years ago

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.

jorgenstorlie commented 8 years ago

Is it someone that have a solution on this?

jamespettigrew commented 8 years ago

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.

adamfisher commented 8 years ago

Can the commit to kunni80's forked repo please be reintegrated?

walalm commented 8 years ago

I'm hoping this get merged soon.