How to stop mono from preventing authentication

Hi everyone!

I am trying to connect to a server that requires a client certificate. I connect with httpWebRequest, and in a console App I made in Visual Sudio, it works like a charm!

But when I try it in Unity, it doesn’t work. (the 2 are identical)

It throws me this TlsException at the GetResponse:

TlsException: The authentication or decryption has failed.
Mono.Security.Protocol.Tls.RecordProtocol.ProcessAlert (AlertLevel alertLevel, AlertDescription alertDesc)
Mono.Security.Protocol.Tls.RecordProtocol.InternalReceiveRecordCallback (IAsyncResult asyncResult)
Rethrow as WebException: Error getting response stream (ReadDone1): ReceiveFailure
System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult)
System.Net.HttpWebRequest.GetResponse ()

So, from what I understand, the problem comes from Mono that prevent the authentication for some security reasons. Any idea how to override it?

Here is the code used if it can help:

ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(ValidateServerCertificate);
X509Certificate2 clientCertificate = new X509Certificate2(path + ".p12", "password");
byte[] data = Encoding.ASCII.GetBytes("some data".ToCharArray());

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(address);
request.AuthenticationLevel = AuthenticationLevel.MutualAuthRequested;
request.ClientCertificates.Add(clientCertificate);
request.Method = "POST";
request.ContentType = "application/json";

request.ContentLength = data.Length;
Stream dataStream = request.GetRequestStream();
dataStream.Write(data, 0, data.Length);
dataStream.Close();
HttpWebResponse response = (HttpWebResponse)request.GetResponse();

dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream);
string responseFromServer = reader.ReadToEnd();
reader.Close();
dataStream.Close();
response.Close();

Okay so this issue is pretty dumb and required a LOT of googling for me to get as far as I did.

As per How to validate SSL certificates when using HttpWebRequest - Questions & Answers - Unity Discussions

There are (possibly) two reasons why certificate is rejected:

  1. X509ChainStatusFlags.RevocationStatusUnknown (may be GoDaddy only)

  2. X509ChainStatusFlags.UntrustedRoot

Item 1 seems to come from a Mono bug, or rather, a lazy implementation of certificate authentication in Mono that doesn’t support the proper encryption. see: https://bugzilla.xamarin.com/show_bug.cgi?id=19031

The workaround to this issue is to simply ignore RevocationStatusUnknown unfortunately. I used this gist: https://gist.github.com/Bmackenzie/ab01c333147ab09bc348 and you can add this code basically wherever is prudent. You’ll be using the following libs: System.Net.Security, System.Security.Cryptography.X509Certificates, and System.Security.Permissions

Item 2 follows from the way mono works. It uses its own certificate store, which by default is empty. See: FAQ: Security | Mono . So you will need to populate the cert store. See How can I add a certificate to the Mono Trust store? - Questions & Answers - Unity Discussions for one solution, and adapt as needed for your platform. In particular note that the exes you need for importing certs is in /Applications/Unity/Unity.app/Contents/Frameworks/MonoBleedingEdge/lib/mono/4.5/

Of course Unity throws you another wrench by not keeping mono up to date, and mozroots.exe crashes! See Included mozroots.exe crashing in Unity - Questions & Answers - Unity Discussions for a workaround.

Personally, I opted to just use certmgr.exe because I only needed the cert chain for one website (for now) and I don’t mind adding them manually. The man page for this utility is here: certmgr(1): Mono Certificate Manager - Linux man page

Unfortunately even after all of this I’m only getting the untrusted root fix on just my dev machine, and when I use the build on another machine I get the same issue. I’ll be asking the best way to distribute certificates with a build in my own question.

Hope this helps.

There is also another reason this happens which I discovered today.

The SSLStream class provided by Unity (Mono 2.x) does not support SNI. This will cause it to fail when connecting to endpoints that use SNI, most notably CloudFront distributions.

I don’t believe that the methods in UnityEngine.Networking.* suffer from the same problem but I am using BestHTTP which uses the outdated SSLStream under the hood.

The good news is that Unity 2017.1 has “experimental support for new scripting runtime. This includes Mono 4.8 and IL2CPP with support for C# 6 and .NET 4.6”. This should solve the issue.
See: https://unity3d.com/unity/whats-new/unity-2017.1.0