5

Client Environment is Xamarin Android Native TLS 1.2 SSL/TLS implementation (boringssl aka btls), using System.Net.WebSockets.ClientWebSocket. This is running on an Android 7.0 device. Visual Studio 2017 15.8.1, Xamarin.Android 9.0.0.18.

Server Environment is Windows .NET 4.7 running Fleck (WebSocket server) configured with TLS 1.2 using a certificate issued by a homemade (non-trusted anywhere on the globe) Certificate Authority (CA).

Assuming a homemade CA Cert (.pem or .cer format) has been installed on the android device via Settings->Security->Install from SD Card, the ClientWebSocket connects using TLS 1.2 without problems, as one would expect. Since this is a global solution to a local (one part of my app) problem, not to mention opening a security hole for the larger device ecosystem, I do not wish to require this setup.

I have then tried several methods to localize the trust of the CA to only my application without success. Regardless of approach, there is always the same exception thrown by ClientWebSocket.ConnectAsync(): A call to SSPI failed and Ssl error:1000007d:SSL routines:OPENSSL_internal:CERTIFICATE_VERIFY_FAILED at /Users/builder/jenkins/workspace/xamarin-android-d15-8/xamarin-android/external/mono/external/boringssl/ssl/handshake_client.c:1132

I created a sample windows server and console app and Xamarin.Forms Android app that demonstrates the issue and the attempts to workaround it described below. Included is a custom CA cert. The server code dynamically issues a client cert with SANs bound to your IP/hostnames for ease of repro.

Attempt 1:

Apply the android:networkSecurityConfig="@xml/network_security_config" attribute to the application element in the AndroidManifest.xml file, including resources Resources\raw\sample_ca.pem Resources\xml\network_security_config.xml

<?xml version="1.0" encoding="utf-8" ?>
<network-security-config>
  <base-config>
    <trust-anchors>
      <certificates src="@raw/sample_ca"/>
      <certificates src="system"/>
      <certificates src="user"/>
    </trust-anchors>
  </base-config>
</network-security-config>

This had no visible effect, and I cannot see anything in the debug output that would indicate that the runtime is even loading this. I have seen references to messages like this:

D/NetworkSecurityConfig: No Network Security Config specified, using platform default

However, with or without this in place I have never seen messages like this or similar. I really have no idea if it is being applied or not, or if btls implementation even uses/respects this.

Interestingly, since the Android minSdk is set to 24 and target sdk of 27 I would expect the lack of this declaration should cause TLS 1.2 to not work if I simply added the CA to the android device user certificate store. I suspect there are a few Xamarin bugs surrounding this.

Attempt 2:

Add the CA to the X509 Store, hoping btls uses that as a source of certificates. This approach works on Windows/.NET 4 (it does bring up a dialog to accept the addition of the certificate).

        X509Store store = new X509Store(StoreName.Root, StoreLocation.CurrentUser);
        store.Open(OpenFlags.ReadWrite);
        var certs = store.Certificates.Find(X509FindType.FindByThumbprint, cert.Thumbprint, false);
        if (certs.Count == 0)
            store.Add(cert);
        store.Close();

Attempt 3:

Handle ServerCertificateValidationCallback. This never gets called in Xamarin Android, but this approach works on Windows/.NET 4.

ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, errors) =>
{
    if (errors == SslPolicyErrors.None)
        return true;    
    //BUGBUG: Obviously there should be a much more extensive check here.
    if (certificate.Issuer == caCert.Issuer)
        return true;    
    return false;
};

There are some Mono issues surrounding btls and a pull request that makes this approach look possible in the near future.

Attempt 4:

Add the CA cert (and/or the cert issued from the CA) to the ClientWebSocket.Options ClientCertificates collection. Technically this should not work with CA Certs but should be the approach to use with self-signed certificates. I offer it here merely for completeness. As expected, it does not work either.

Full Repro Code

Easy to use code that demonstrates this issue with all of the attempted workarounds described above is available on GitHub.

Steven Bone
  • 588
  • 5
  • 16

1 Answers1

-2

I don't know, what the problem was, but I also had some problems, as I have started with my first https webservices. The reason was, that I have started with a self signed certificate, what does not work without ugly workarounds... So.. only use signed (trusted) certificates from a public vendor and you should not have any problem... To switch between http and https you only have to change the url (from http to https) - no further changes in the app ae needed. I normally do first (local) tests with the web service with http (the url ist loaded from an .ini file) and then copy the web service to the "real" webserver (with certificate and https url). I never had any problems (when a trusted certificate is used)...

  • As stated in the question, using a certificate issued by a provider in the device trusted root certificate store works fine. However, the other approaches SHOULD also work and I cannot make it so. These are NOT 'ugly workarounds' but rather valid use cases, which is why they exist in the frameworks. – Steven Bone Dec 14 '18 at 12:12
  • We have this kind of problem here in Brazil even when not working with a homemade CA. Here we have a government CA called ICP-Brasil that issues certificates that are not recognized by Android devices. So a solution for this problem is very welcome. – Carlos Beppler Jan 11 '19 at 23:41