Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Square.OkHttp with Android 4.2, a TLS 1.2 site and SSL self-signed certificates #1

Closed
mazloumi opened this issue Jul 23, 2015 · 9 comments

Comments

@mazloumi
Copy link

I found the square.OkHttp on Nuget and I tried to use it in Xamarin but it throws an error. Can you perhaps help?

Here is the code I am using in the MainActivity class:

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
StrictMode.SetThreadPolicy(policy);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().Url("https://<yourserver>").Build();
Response response = await client.NewCall(request).ExecuteAsync();
string body = response.Body().String();
System.Diagnostics.Debug.WriteLine (body);

And the error I 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.<ThrowAsync>m__0 (object) <IL 0x00006, 0x0003b>
[MonoDroid] at Android.App.SyncContext/<Post>c__AnonStorey0.<>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.019412e5-7311-4cc0-a58e-164d30934145 (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=0xb8c5b2e8: 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 0x971ea990:0x00000000)
[MonoDroid]     at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:448)
[MonoDroid]     at com.squareup.okhttp.internal.http.SocketConnector.connectTls(SocketConnector.java:103)
[MonoDroid]     at com.squareup.okhttp.Connection.connect(Connection.java:143)
[MonoDroid]     at com.squareup.okhttp.Connection.connectAndSetOwner(Connection.java:185)
[MonoDroid]     at com.squareup.okhttp.OkHttpClient$1.connectAndSetOwner(OkHttpClient.java:128)
[MonoDroid]     at com.squareup.okhttp.internal.http.HttpEngine.nextConnection(HttpEngine.java:341)
[MonoDroid]     at com.squareup.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:330)
[MonoDroid]     at com.squareup.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:248)
[MonoDroid]     at com.squareup.okhttp.Call.getResponse(Call.java:273)
[MonoDroid]     at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:230)
[MonoDroid]     at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:201)
[MonoDroid]     at com.squareup.okhttp.Call.access$100(Call.java:36)
[MonoDroid]     at com.squareup.okhttp.Call$AsyncCall.execute(Call.java:164)
[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]     Suppressed: javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb8c29508: 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 0x971ea990:0x00000000)
[MonoDroid]         ... 17 more
[MonoDroid]     Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb8c29508: 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 0x971ea990: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
[MonoDroid] Caused by: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb8c5b2e8: 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 0x971ea990: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
[AndroidRuntime] Shutting down VM
@mattleibow
Copy link
Owner

Does this fix it? http://bit.ly/1Mn90In
In that gist, I created an wrapper socket factory that made sure to enable all the supported protocols:

sslSocket.SetEnabledProtocols (sslSocket.GetSupportedProtocols ());

Docs: http://developer.android.com/reference/javax/net/ssl/SSLSocket.html

Note, you shouldn't have to adjust the thread policy:

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().Url("https://<yourserver>").Build();
// UI thread here
Response response = await client.NewCall(request).ExecuteAsync(); // run on a new thread here
// UI thread here
string body = await response.Body().StringAsync(); // read on a new thread here
// UI thread here
System.Diagnostics.Debug.WriteLine (body); 

@mattleibow
Copy link
Owner

There are several ways you can use async and Task to read the response:

var body = response.Body (); // no work
using (var bodyStream = body.ByteStream ())
using (var memory = new MemoryStream ()) {
    await bodyStream.CopyToAsync (memory); // read work
}
var bodyBytes = await body.BytesAsync (); // read work
var bodyString = await body.StringAsync (); // read work 

@mattleibow
Copy link
Owner

Example usage of the gist in the provided example would be:

OkHttpClient client = new OkHttpClient ();
// only include our custom factory for the older Androids
if (Android.OS.Build.VERSION.SdkInt < BuildVersionCodes.L) {
    client.SetSslSocketFactory (new CompleteSSLSocketFactory ());
}
Request request = new Request.Builder ().Url ("https://<yourserver>").Build ();
// UI thread here
Response response = await client.NewCall (request).ExecuteAsync (); // run on a new thread here
// UI thread here
string body = await response.Body ().StringAsync (); // read on a new thread here
// UI thread here
System.Diagnostics.Debug.WriteLine (body); 

@mazloumi
Copy link
Author

Thank you. This works well but introduced a new challenge. Prior to this solution I used the HttpClientHandler in the PCL project as the http client and all I needed to do is to add the following to the MainActivity OnCreate method to allow self-signed server certificates.

ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(AllwaysGoodCertificate);

And provide the following method:

private static bool AllwaysGoodCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors policyErrors) {
    return true;
}

With the new solution self-signed certificates won't work. Following the examples provided here

http://stackoverflow.com/questions/1217141/self-signed-ssl-acceptance-android

there seem to be two approaches:

A) Provide my own host name verifier and trust manager
B) Create a socket factory that reads from your own local key store used in the app and create an http client that registers its own scheme using the socketfactory

Any thoughts?

@mattleibow
Copy link
Owner

@mazloumi It really depends on what the self-signed certificates are used for.

If it is purely for development, then you could probably safely just ignore all certificates. It is highly advisable to make sure any such code is wrapped in #if DEBUG blocks to avoid any accidental deployment to a production server.

However, it is not too hard to provide the certificate in the Android app. I have updated the gist to demonstrate 2 ways in which we can handle self-signed certificates, or certificates from an unknown CA.

The gist that has been updated: http://bit.ly/1Mn90In
I used this to build a new certificate on Windows for IIS: http://bit.ly/1LJaaNy (script/config) and http://indy.fulgan.com/SSL/openssl-1.0.2d-x64_86-win64.zip (OpenSSL)

Here is the code that will take a .cer file from a website and create a new keystone at runtime. This can then be used to create a trust manager that is then given to a SSL context which is where we get our SSL socket factory from:

// Load our certificate from resources (we created this one using OpenSSL and 
// saved as a .cer using Windows' certlm console)
var certificateFactory = CertificateFactory.GetInstance ("X.509");
Certificate certificate;
using (var stream = Application.Context.Resources.OpenRawResource (Resource.Raw.selfsigned)) {
    certificate = certificateFactory.GenerateCertificate (stream);
}

// Create a KeyStore containing our trusted CAs
var keyStore = KeyStore.GetInstance (KeyStore.DefaultType);
keyStore.Load (null, null);
keyStore.SetCertificateEntry ("ca", certificate);

// Create a TrustManager that trusts the CAs in our KeyStore
var trustManagerFactory = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm);
trustManagerFactory.Init (keyStore);
var trustManager = trustManagerFactory.GetTrustManagers () [0].JavaCast<IX509TrustManager> ();

// Create an SSLContext that uses our TrustManager
var context = SSLContext.GetInstance ("TLSv1.2");
context.Init (null, new ITrustManager[]{ trustManager }, null);

// apply the new context
var socketFactory = context.SocketFactory;

Important note: use JavaCast<IX509TrustManager> () instead of as or a normal cast. This is because, although the type is really a IX509TrustManager on the Java side, it is just a ITrustManager on the C# side. (http://developer.xamarin.com/api/member/Android.Runtime.Extensions.JavaCast%3CTResult%3E/)

It is highly advisable to not just use a single certificate as done here:

context.Init (null, new ITrustManager[]{ trustManager }, null);

Doing this will prevent all other SSL sites from working. You can use a wrapped trust manager that first tries the default trust manager, and then tries our custom trust manager. Depending on which requests are more common, they can be the other way around:

public void CheckServerTrusted (X509Certificate[] chain, string authType)
{
    try {
        defaultTrustManager.CheckServerTrusted (chain, authType);
    } catch (CertificateException) {
        localTrustManager.CheckServerTrusted (chain, authType);
    }
}

Here are some docs that are a good read: http://developer.android.com/training/articles/security-ssl.html#UnknownCa

@mattleibow mattleibow changed the title Issue with using square.OkHttp with a TLS 1.2 site Using Square.OkHttp with Android 4.2, a TLS 1.2 site and SSL self-signed certificates Jul 25, 2015
@mazloumi
Copy link
Author

Thank you. This was very helpful.

@mazloumi
Copy link
Author

Matthew,

Have you seen examples out there of using self-signed client certificates?

Kind regards,
Nima

On 24 Jul 2015, at 23:48, Matthew Leibowitz notifications@github.com wrote:

It really depends on what the self-signed certificates are used for.

If it is purely for development, then you could probably safely just ignore all certificates. It is highly advisable to make sure any such code is wrapped in #if DEBUG blocks to avoid any accidental deployment to a production server.

However, it is not too hard to provide the certificate in the Android app. I have updated the gist to demonstrate 2 ways in which we can handle self-signed certificates, or certificates from an unknown CA.

The gist that has been updated: http://bit.ly/1Mn90In

Here is the code that will take a .cer file from a website and crate a new keystone at runtime. This can then be used to create a trust manager that is then given to a SSL context which is where we get our SSL socket factory from:

// Load our certificate from resources (we created this one using OpenSSL and
// saved as a .cer using Windows' certlm console)
var certificateFactory = CertificateFactory.GetInstance ("X.509");
Certificate certificate;
using (var stream = Application.Context.Resources.OpenRawResource (Resource.Raw.selfsigned)) {
certificate = certificateFactory.GenerateCertificate (stream);
}

// Create a KeyStore containing our trusted CAs
var keyStore = KeyStore.GetInstance (KeyStore.DefaultType);
keyStore.Load (null, null);
keyStore.SetCertificateEntry ("ca", certificate);

// Create a TrustManager that trusts the CAs in our KeyStore
var trustManagerFactory = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm);
trustManagerFactory.Init (keyStore);
var trustManager = trustManagerFactory.GetTrustManagers () [0].JavaCast ();

// Create an SSLContext that uses our TrustManager
var context = SSLContext.GetInstance ("TLSv1.2");
context.Init (null, new ITrustManager[]{ trustManager }, null);

// apply the new context
var socketFactory = context.SocketFactory;
It is highly advisable to not just use a single certificate as done here:

context.Init (null, new ITrustManager[]{ trustManager }, null);
Doing this will prevent all other SSL sites from working. You can use a wrapped trust manager that first tries the default trust manager, and then tries our custom trust manager. Depending on which requests are more common, they can be the other way around:

public void CheckServerTrusted (X509Certificate[] chain, string authType)
{
try {
defaultTrustManager.CheckServerTrusted (chain, authType);
} catch (CertificateException) {
localTrustManager.CheckServerTrusted (chain, authType);
}
}
Here are some docs that are a good read: http://developer.android.com/training/articles/security-ssl.html#UnknownCa


Reply to this email directly or view it on GitHub.

@josephkandi
Copy link

Mazloumi
If i were you i would avoid the trouble and get a free ssl certificate from Comodo, its great for testing without the hassle.

@mattleibow
Copy link
Owner

@mazloumi, what @josephkandi suggested is probably the best option, then, there is no issues at all. However, if that is not possible, you should be able to use certificates from a trusted source - as in the example of https://certs.cac.washington.edu/CAtest. I wouldn't have my server using my self-signed certificates. Especially since I many not be the only client. Also, when using self-signed certificates, any changes to the certificate will require updates to all the clients - which cannot always be guaranteed.
The only real case where self-signed certificates might be used would be in an internal, enterprise situation, where the server and the clients are carefully controlled. But then again, why not just buy a certificate and avoid any issues altogether.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants