5

I am creating a game using the Unity Engine(2017.2.0f3) on Windows 10 in which I am trying to get the HTML from a webpage. To my knowledge, Unity uses the "Mono" C# compiler and runtime v2.0 (according to the results of running mono --version in Unity\Editor\Data\Mono\bin), but also v5.0.1 (according to the results of running mono --version in Unity\Editor\Data\MonoBleedingEdge\bin). I am currently using the HTML Agility Pack for parsing. When attempting to access an HTTP secured website, such as dictionary.com, everything works as expected, but no matter what I do, I cannot access an HTTPS secured website, such as urbandictionary.com(Edit: Apparently, the problem is specific to this site, because after importing certificates using mozroots, github.com can be accessed), and always receive the exception: TlsException: The authentication or decryption has failed. I am aware that the HTML Agility Pack does not support HTTPS, and have wrote my code according to this answer. I've tried the solution described here and here, but to no avail. I've tried running mozroots, which I located in PathTo/Unity/Editor/Data/MonoBleedingEdge/lib/mono/4.5/mozroots, with the --import, --sync, and --quiet flags, but the same exception is thrown (if this did work, I'm skeptical about its portability, but perhaps I could implement my own version of mozroots, as suggested by the comments). Also, I've been informed that the version of Mono that Unity uses does not support TLS 1.2, which is problematic. If there is no solution to this problem, are there any workarounds?

Note: abridged for clarity

class MyWebClient : WebClient
{
    protected override WebRequest GetWebRequest(Uri address)
    {
        HttpWebRequest request = base.GetWebRequest(address) as HttpWebRequest;
        request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
        return request;
    }
}

class GetHtml
{
    public static void Get(string url)
    {
        string documentString = new MyWebClient().DownloadString(url);
        ...
    }
}

class CallingClass
{
     public void CallingMethod()
     {
         ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
         //With or without the above line ^, the same TlsException is thrown. 
         GetHtml.Get("https://www.urbandictionary.com/define.php?term=example");
         //HTTP works properly, but HTTPS, as in this example, does not.
     }
}

The log is as follows:

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 IOException: The authentication or decryption has failed.
Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (IAsyncResult asyncResult)
Rethrow as WebException: Error getting response stream (Write: The authentication or decryption has failed.): SendFailure
System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult)
System.Net.HttpWebRequest.GetResponse ()
System.Net.WebClient.GetWebResponse (System.Net.WebRequest request)
System.Net.WebClient.DownloadDataCore (System.Uri address, System.Object userToken)

Edit: I've tried updating the runtime version. The only option was to change from .Net 3.5 to 4.6 in the Project Settings->Player menu, which is experimental. I still get an exception when the code is run, but the output log is a little different.

TlsException: The authentication or decryption has failed.
Mono.Security.Protocol.Tls.RecordProtocol.EndReceiveRecord (System.IAsyncResult asyncResult) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Mono.Security.Protocol.Tls.SslClientStream.SafeEndReceiveRecord (System.IAsyncResult ar, System.Boolean ignoreEmpty) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Mono.Security.Protocol.Tls.SslClientStream.NegotiateAsyncWorker (System.IAsyncResult result) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Rethrow as IOException: The authentication or decryption has failed.
Mono.Security.Protocol.Tls.SslClientStream.EndNegotiateHandshake (System.IAsyncResult result) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Mono.Security.Protocol.Tls.SslStreamBase.AsyncHandshakeCallback (System.IAsyncResult asyncResult) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Rethrow as IOException: The authentication or decryption has failed.
Mono.Security.Protocol.Tls.SslStreamBase.EndRead (System.IAsyncResult asyncResult) (at <eb1224ae7b184cd09343d47f8a05481b>:0)
Mono.Net.Security.Private.LegacySslStream.EndAuthenticateAsClient (System.IAsyncResult asyncResult) (at <344dc4d3f1ad41809df78607b6121a41>:0)
Mono.Net.Security.Private.LegacySslStream.AuthenticateAsClient (System.String targetHost, System.Security.Cryptography.X509Certificates.X509CertificateCollection clientCertificates, System.Security.Authentication.SslProtocols enabledSslProtocols, System.Boolean checkCertificateRevocation) (at <344dc4d3f1ad41809df78607b6121a41>:0)
Mono.Net.Security.MonoTlsStream.CreateStream (System.Byte[] buffer) (at <344dc4d3f1ad41809df78607b6121a41>:0)
System.Net.WebConnection.CreateStream (System.Net.HttpWebRequest request) (at <344dc4d3f1ad41809df78607b6121a41>:0)
Rethrow as WebException: Error: SecureChannelFailure (The authentication or decryption has failed.)
System.Net.WebClient.DownloadDataInternal (System.Uri address, System.Net.WebRequest& request) (at <344dc4d3f1ad41809df78607b6121a41>:0)
System.Net.WebClient.DownloadString (System.Uri address) (at <344dc4d3f1ad41809df78607b6121a41>:0)
System.Net.WebClient.DownloadString (System.String address) (at <344dc4d3f1ad41809df78607b6121a41>:0)
Alex
  • 316
  • 4
  • 12
  • What version of `Mono` are you using? And on what platform? – SushiHangover Nov 25 '17 at 05:16
  • After running a version check in `Unity\Editor\Data\Mono\bin` the result was 2.0. Also, I am currently working on windows 10. – Alex Nov 25 '17 at 05:29
  • @Alex Mono, initially, has no certificate store. it's empty. In Mono 3.12+ there's a tool, `cert-sync`, used to synchronize certificates with those in the system store [Secure Socket Layer (SSL) / Transport Layer Security (TLS)](http://www.mono-project.com/docs/faq/security/). It can also be forced to accept what Firefox trusts using `System.Diagnostics.Process.Start("mozroots","--import --quiet");` – Jimi Nov 30 '17 at 16:13
  • I mean _in versions prior to 3.12 can be forced..._. Inserting a `--sync` switch to the command line can also be useful. – Jimi Nov 30 '17 at 16:25
  • @Jimi If I add `System.Diagnostics.Process.Start("mozroots","--import --quiet");` to my code, I get a `File Not Found` Exception. This is probably because I am using the Unity Engine and on a Windows platform. I'm not quite certain how I would go about accessing that functionality at the moment, so I cannot tell you if it works. Also, it would be preferable that the solution be as platform independent as possible, so if that is not the case, then would there be a way to make it so? – Alex Dec 01 '17 at 07:27
  • @Alex That would be the crossplatform solution. See this: [How can I add a certificate to the Mono Trust store?](https://answers.unity.com/questions/1112912/how-can-i-add-a-certificate-to-the-mono-trust-stor.html) and also the mozroots implementation: [Mozroots](https://github.com/mono/mono/blob/master/mcs/tools/security/mozroots.cs) – Jimi Dec 01 '17 at 12:55
  • 1
    Site you mention (urbandictionary) supports _only_ TLS version 1.2. Support for this TLS version was added in mono only in version 4.8 – Evk Dec 01 '17 at 21:12
  • @Jimi I located mozroots in `PathTo/Unity/Editor/Data/MonoBleedingEdge/lib/mono/4.5/mozroots`. Is mozroots automatically installed on all machines that run the exported game? If so, how will I know where it is located in order to run it? Also, when running mozroots as you specified, the command line pops up and requires that I type, 'yes' for every certificate that is to be imported, which is not ideal for me, and unacceptable for the end-user. Finally, after it is run, the same error occurs. – Alex Dec 01 '17 at 23:30
  • @Evk Is there any way that I can work around this issue or am I at an impasse? – Alex Dec 01 '17 at 23:33
  • @Alex You could upgrade to mono 4.8. It's somewhat experimental but maybe it's worth trying. – Jimi Dec 01 '17 at 23:48
  • @Alex I forgot to mention that using just `mozroots --import --sync` doesn't ask for confirmation. It's used for new installations or mass-updates, but you can use it any time. You could also implement your own (that link you already have). Mozroot is present in any installation of Mono. – Jimi Dec 01 '17 at 23:53
  • @Jimi I'm not aware of how I would go about upgrading Mono while still using the Unity Engine and maintaining cross-platform support; according to my admittedly cursory research, it is not feasible. Is there something I've missed? – Alex Dec 02 '17 at 00:00
  • @Alex For what I know, Unity works with Mono 4.8 (.net 4.6). I know they will (or maybe already do) support Mono 5.0. – Jimi Dec 02 '17 at 01:08
  • @Alex You were probably more interested in .net 2.0 standard. I had a look. It seems to support a smaller profile. Check it out. [Mono Upgrade](https://forum.unity.com/threads/future-plans-for-the-mono-runtime-upgrade.464327/page-2) – Jimi Dec 02 '17 at 01:20
  • @Jimi In the player settings menu I've changed the `Scripting Runtime Version` to `Experimental (.NET 4.6 Equivalent)`; previously, it was on `.NET 3.5`. I still get an exception when running the code, but the log is different now. Is this what you were referring to when you mentioned upgrading? If not, I'm not entirely sure what you were referring to. – Alex Dec 02 '17 at 03:57
  • I don't know anything about Unity unfortunately. You said you switched to equivalent of .NET 4.6 (what mono version is that by the way?) and error changed. Which error is it now? – Evk Dec 02 '17 at 08:06
  • @Evk I updated the question to contain the requested information. From what I understand the `.Net 4.6 Equivalent` is `Mono v5.0.1`. – Alex Dec 02 '17 at 09:37
  • @Alex Have you tried to synchronized the certificate cache wiith the system's, running `cert-sync`? "Upgrade" was referred to your project as a whole, should more properly translated to "Targeted Framework". Unity "ships" with Mono, but they're not strictly tied; of course they must understand each other. For now that support extends to .net 4.6, .net standard 2.0, C#6 and, partially, C#7. – Jimi Dec 02 '17 at 10:59
  • @Alex Comes to mind that some servers require SNI ([Server Name Indication](https://en.wikipedia.org/wiki/Server_Name_Indication)). That is not compatible with SSL3, so should be worthwhile cheching how the web request is made. – Jimi Dec 02 '17 at 11:32
  • @Jimi I'm not sure how I would go about running `cert-sync on` the Windows platform, and isn't `mozroots` supposed to solve that issue? I'm getting the impression that the only way to get this project to work is to write my own HTTPS code starting from sockets. – Alex Dec 03 '17 at 21:45
  • @Alex `cert-sync.bat` is under `/bin/`, `cert-sync.exe` under `/lib/mono/4.5/`. But it doesn't matter if you already have imported the certificates from mozilla. I made a test with a fresh install. Using [TlsTest](https://github.com/duplicati/duplicati/tree/master/thirdparty/TlsTest) and the test failed (tested `https://github.com`). Then I imported the certificates (with `mono mozroots --import --sync`) and the test completed gracefully. I then tested `https://www.urbandictionary.com` and the test **failed**. Some information is missing, here. – Jimi Dec 04 '17 at 03:22
  • Do they need a specific certificate-key? Those you obtain from a subscription? – Jimi Dec 04 '17 at 03:30
  • @Jimi I just tested and you're right. It seems to be a problem specific to `urbandictionary` since `github` worked. I'm not sure why that is though. I couldn't tell you if they have a specific certificate, and I'm not sure how I would go about obtaining that information either, but it ostensibly appears to function like any other site when using a web browser like chrome or firefox if that means anything. – Alex Dec 04 '17 at 05:28
  • @Alex I don't know about this site, maybe their web.config is set to work only with cookies (this is a wild guess, though). But they have an API which is free to use. `http://api.urbandictionary.com/v0/define?term=word` <- If you substitute "word" with a term you want the definition for, it will answer with a standard JSON file with all the definitions for that term. – Jimi Dec 04 '17 at 13:53
  • @Jimi After some research it appears that they use the `GlobalSign Organization Validation CA - SHA256 - G2` certificate, `TLS 1.2`, `ECDHE-RSA` with `P-256`, and `AES_128_GCM` (According to chrome). I've tried visiting it after turning cookies off on chrome, and it worked, but I wouldn't be surprised if it was more complicated than that. Though I would like to figure out the cause of this issue and resolve it, I guess I'll use the API in the meantime. – Alex Dec 04 '17 at 19:22

2 Answers2

2

This problem count be caused by the HTTP protocol. If the server uses secure HTTP (actually HTTPS), the error may be created when the underlying protocol fails to handle the X509 Certificate.

Try to change this line:

HttpWebRequest request = base.GetWebRequest(address) as HttpWebRequest;

to:

HttpWebRequest request = base.GetWebRequest(address) as HttpWebRequest; 
request.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;

Or, alternatively, apply the validation callback to the global filter using:

ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;

before any HttpWebRequest object is created within your code.

If you want to handle the certificate validation more carefully, implement the callback as showin here: https://msdn.microsoft.com/en-us/library/office/dd633677

This should fix your problem.

Tommaso Belluzzo
  • 23,232
  • 8
  • 74
  • 98
  • When trying to implement your first suggestion I am met with a syntax error saying: `'HttpWebRequest' does not contain a definition for 'ServerCertificateValidationCallback' and no extension method 'ServerCertificateValidationCallback' accepting a first argument of type 'HttpWebRequest' could be found.`. As for the second, I have already tried it, and for the sake of thoroughness tried it again, but with no change. – Alex Nov 25 '17 at 05:46
  • The error looks strange, I mean... I looked at the MSDN and I found it actually exists: https://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.servercertificatevalidationcallback(v=vs.110).aspx – Tommaso Belluzzo Nov 25 '17 at 14:17
  • According to the MSDN, `HttpWebRequest.ServerCertificateValidationCallback` is available since .Net 4.5. Unity appears to be using an older version of Mono (2.0), so it must not exist in that version. As for the second suggestion which seems to be a very popular solution, the code is compiled and run, but nothing appears to change as I still receive the same `TlsException`. – Alex Nov 25 '17 at 23:00
  • Ah damn! I missed that! Thanks @Alex! – Tommaso Belluzzo Nov 25 '17 at 23:00
  • The global `ServicePointManager` callback should be available in Mono according to GitHub. Did you try it? – thehennyy Nov 28 '17 at 09:43
2

After some rigorous research it turns out that the version of Mono that Unity(2017.2.0f3) currently uses does not support TLS 1.2, as explained by a developer here. Also, a careful examination of the exception log shows that internally, the legacy backend is being used: Mono.Net.Security.Private.LegacySslStream as opposed to Mono.Net.Security.Private.SslStream. As of right now, the only solution would involve third-party libraries, or workarounds.

Thanks to @Evk for pointing me in the right direction.

Alex
  • 316
  • 4
  • 12