1

My Xamarin.Android app makes requests to a web service running on https. The certificate may be self-signed, so I need to do the certificate check on my own. I configured my own TrustManager. But from what I found, the method CheckServerTrusted should throw a CertificateException if the certificate is not trusted. If I do that, then my app crashes. The CertificateException is not handled anywhere.

var aUrl = new URL(url);
var connection = (HttpURLConnection)aUrl.OpenConnection();

try
{
    var httpsConn = connection as HttpsURLConnection;
    if (httpsConn != null)
    {
        var tm = new CustomX509TrustManager();
        var sslCtx = SSLContext.GetInstance("TLSv1.2");
        sslCtx.Init(null, new ITrustManager[] { tm }, null);

        httpsConn.HostnameVerifier = new CustomHostnameVerifier();
        httpsConn.SSLSocketFactory = sslCtx.SocketFactory;
    }

    System.Diagnostics.Debug.WriteLine("Connecting ...");

    //connection.Connect();
    await connection.ConnectAsync();

    System.Diagnostics.Debug.WriteLine("Never reached");
}
catch (Exception ex)
{
    System.Diagnostics.Debug.WriteLine($"Exception!\n{ex}");
}
finally
{
    System.Diagnostics.Debug.WriteLine("Never reached");
}
System.Diagnostics.Debug.WriteLine("Never reached");

The CustomX509TrustManager is as simple as it can be:

class CustomX509TrustManager : Java.Lang.Object, IX509TrustManager
{
    public void CheckClientTrusted(Java.Security.Cert.X509Certificate[] chain, string authType)
    {
    }

    public void CheckServerTrusted(Java.Security.Cert.X509Certificate[] certificates, string authType)
    {
        throw new Java.Security.Cert.CertificateException();
    }

    Java.Security.Cert.X509Certificate[] IX509TrustManager.GetAcceptedIssuers()
    {
        return new Java.Security.Cert.X509Certificate[0];
    }
}

In connection.ConnectAsync() the remote server is contacted and the certificate is checked in CustomX509TrustManager.CheckServerTrusted. This throws the CertificateException. But that exception is not caught in my try/catch in the calling method. The app crashes with this stack trace:

UNHANDLED EXCEPTION:
Java.Security.Cert.CertificateException: Exception of type 'Java.Security.Cert.CertificateException' was thrown.
  at SimpleWebRequest.MainActivity+CustomX509TrustManager.CheckServerTrusted (Java.Security.Cert.X509Certificate[] certificates, System.String authType) [0x00001] in D:\Temp\SimpleWebRequest\SimpleWebRequest\MainActivity.cs:162 
  at Javax.Net.Ssl.IX509TrustManagerInvoker.n_CheckServerTrusted_arrayLjava_security_cert_X509Certificate_Ljava_lang_String_ (System.IntPtr jnienv, System.IntPtr native__this, System.IntPtr native_chain, System.IntPtr native_authType) [0x00028] in /Users/builder/data/lanes/4009/9578cdcd/source/monodroid/src/Mono.Android/platforms/android-25/src/generated/Javax.Net.Ssl.IX509TrustManager.cs:129 
  at (wrapper dynamic-method) System.Object:5109f213-8693-4899-a3db-70ed54baf307 (intptr,intptr,intptr,intptr)
  --- End of managed Java.Security.Cert.CertificateException stack trace ---

I tried several things with strange results.

If I use connection.Connect() instead of ConnectAsync, then the catch block is run, but the finally block is not and also the code afterwards does not run anymore. With ConnectAsync, the catch block is also not run.

If I get the default TrustManager and set it in the SSLSocketFactory like I did with mine, then it works. Certificates are rejected and the exception can be caught. So I don't think that I just only have the catch on the wrong thread.

If I use my own TrustManager, but I delegate the call to CheckServerTrusted to the default TrustManager, then it fails as if I'd throw the exception myself.

So I think I'm doing something wrong either in my CustomX509TrustManager or the way how I pass it to the SSLSocketFactory.

Unfortunately when you search for custom TrustMangagers then most people just want to ignore all certificate errors. That works for me, the problem arises if I do check the cert and want to reject it.

Michael Rumpler
  • 305
  • 2
  • 17

2 Answers2

0

But from what I found, the method CheckServerTrusted should throw a CertificateException if the certificate is not trusted. If I do that, then my app crashes. The CertificateException is not handled anywhere.

I think problem is exactly how you deal in the CheckServerTrusted method, you coded like this:

public void CheckServerTrusted(Java.Security.Cert.X509Certificate[] certificates, string authType)
{
    throw new Java.Security.Cert.CertificateException();
}

This is always throw a unhandled new CertificateException when your CheckServerTrusted method is called.

I think you can check this blog: Self-Signed Certificates and Xamarin.Android. From what I see, the CheckServerTrusted method can be used to wrap the trust manager in another trust manager to allow other certificates to be validated. When you customize this method, you don't need to throw such an exception.

Grace Feng
  • 16,564
  • 2
  • 22
  • 45
  • Of course that was only a simplification. My method only throws when the cert is not valid. I have most of my code from the page you linked. There the defaultTrustManager also throws a CertificateException. I don't know the whole cert of the server in advance. I only know the thumbprint. Thus I cannot use Matts approach of an own localTrustManager. – Michael Rumpler Mar 16 '17 at 17:28
0

I had the same problem. After a few hours of try-and-error, I found out that the exception is unhandled in Debug mode only. If the app is built in Release mode, the exception is catched.