使用HTTPS进行WebClient.DownloadString时出现“身份验证或解密失败”的错误。

5

我正在使用Unity引擎(2017.2.0f3)在Windows 10上创建一个游戏,我试图从网页中获取HTML。据我所知,Unity使用“Mono”C#编译器和运行时v2.0(根据在“Unity\Editor\Data\Mono\bin”中运行“mono --version”的结果),但也使用v5.0.1(根据在“Unity\Editor\Data\MonoBleedingEdge\bin”中运行“mono --version”的结果)。我目前正在使用HTML Agility Pack进行解析。当尝试访问HTTP安全网站(如dictionary.com)时,一切正常,但无论我做什么,我都无法访问HTTPS安全网站(如urbandictionary.com)(编辑: 显然,问题是特定于这个站点的,因为使用mozroots导入证书后,可以访问github.com),并且总是收到异常:TlsException: The authentication or decryption has failed. 我知道HTML Agility Pack不支持HTTPS,并根据this answer编写了我的代码。我尝试了herehere中描述的解决方案,但都没有成功。我尝试运行mozroots,将其定位在PathTo/Unity/Editor/Data/MonoBleedingEdge/lib/mono/4.5/mozroots,使用--import--sync--quiet标志,但仍然抛出相同的异常(如果这样做可以工作,我对其可移植性持怀疑态度,但也许我可以根据评论中的建议实现自己的版本)。此外,我被告知Unity使用的Mono版本不支持TLS 1.2,这是有问题的。如果没有解决这个问题的方法,是否有任何解决方法?

注意:为了清晰起见而缩短

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.
     }
}

日志如下:

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)

编辑:我尝试更新运行时版本。唯一的选项是在“项目设置-〉播放器”菜单中从.Net 3.5更改为4.6,这是实验性的。当代码运行时,我仍然会收到异常,但输出日志有点不同。

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)

1
你提到的网站(urbandictionary)仅支持TLS版本1.2。对于这个TLS版本的支持只在Mono 4.8版本中添加了。 - Evk
@Evk 我有没有办法绕过这个问题,还是我已经陷入了僵局? - Alex
很遗憾,我对Unity一无所知。你说你切换到了等效的.NET 4.6(顺便问一下,那是哪个Mono版本?),错误也改变了。现在出现了哪个错误? - Evk
@Jimi 我不确定如何在Windows平台上运行cert-sync,而且mozroots不是应该解决这个问题吗?我得到的印象是让这个项目工作的唯一方法是从套接字开始编写自己的HTTPS代码。 - Alex
@Jimi 我刚刚测试了一下,你是对的。这似乎是urbandictionary特有的问题,因为github可以正常工作。不过我不确定原因。我无法告诉你他们是否有特定的证书,也不知道如何获取这些信息,但在使用像Chrome或Firefox这样的Web浏览器时,它表面上看起来像任何其他网站一样运行,如果这有什么意义的话。 - Alex
显示剩余20条评论
2个回答

2
这个问题可能是由于HTTP协议引起的。如果服务器使用安全的HTTP(实际上是HTTPS),当底层协议无法处理X509证书时,可能会出现错误。
尝试更改这一行:
HttpWebRequest request = base.GetWebRequest(address) as HttpWebRequest;

to:

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

或者,您可以使用以下方法将验证回调函数应用于全局过滤器:

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

在您的代码中创建任何HttpWebRequest对象之前。

如果您想更仔细地处理证书验证,请按照此处所示实现回调函数:https://msdn.microsoft.com/en-us/library/office/dd633677

这应该可以解决您的问题。


当尝试实现您的第一个建议时,我遇到了语法错误,提示:“HttpWebRequest”不包含“ServerCertificateValidationCallback”的定义,并且找不到接受类型为“HttpWebRequest”的第一个参数的扩展方法“ServerCertificateValidationCallback”。至于第二个建议,我已经尝试过了,并且为了彻底性再次尝试了一遍,但没有任何改变。 - Alex
错误看起来很奇怪,我的意思是...我查看了MSDN并发现它实际上存在:https://msdn.microsoft.com/zh-cn/library/system.net.httpwebrequest.servercertificatevalidationcallback(v=vs.110).aspx - Tommaso Belluzzo
根据MSDN的说明,HttpWebRequest.ServerCertificateValidationCallback自.NET 4.5版本起可用。Unity似乎使用较旧版本的Mono(2.0),因此该函数在该版本中不存在。至于第二个建议,这似乎是一个非常流行的解决方案,代码已编译并运行,但似乎没有任何变化,我仍然收到相同的TlsException错误。 - Alex
啊,该死!我错过了!谢谢@Alex! - Tommaso Belluzzo
根据 GitHub 的说法,全局 ServicePointManager 回调应该在 Mono 中可用。你试过了吗? - thehennyy

2
经过一番严格的研究,发现Unity(2017.2.0f3)目前使用的Mono版本不支持TLS 1.2,正如开发人员在这里所解释的那样。同时,仔细检查异常日志可以发现,内部正在使用遗留后端:Mono.Net.Security.Private.LegacySslStream而不是Mono.Net.Security.Private.SslStream。目前唯一的解决方案涉及第三方库或解决方法。

感谢@Evk指引我正确的方向。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接