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.