Unity TlsException:握手失败UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED

Unity TlsException: Handshake failed UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED

我正在尝试更新我的应用程序的 TcpClient 以将 TLS 与 SslStream 一起使用而不是正常的 Stream,我为此使用的代码似乎在Unity,但在我的 Unity 2019.1.8(也在 2018 年和 2017 年测试过)项目中集成时失败。

要建立一个连接并打开一个新的SslStream我使用下面的代码:

public static void InitClient(string hostName, int port, string certificateName)
{
    client = new TcpClient(hostName, port);

    if (client.Client.Connected)
    {
        Debug.LogFormat("Client connected succesfully");
    }
    else
    {
        Debug.LogErrorFormat("Client couldn't connect");
        return;
    }

    stream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);

    try
    {
        stream.AuthenticateAsClient(certificateName);
    }
    catch (AuthenticationException e)
    {
        Debug.LogErrorFormat("Error authenticating: {0}", e);
        if (e.InnerException != null)
        {
            Debug.LogErrorFormat("Inner exception: {0}", e);
        }
        Debug.LogErrorFormat("Authentication failed - closing connection");
        stream.Close();
        client.Close();
    }
}

并用于验证证书

public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    if (sslPolicyErrors == SslPolicyErrors.None)
        return true;

    Debug.LogErrorFormat("Certificate error: {0}", sslPolicyErrors);
    return false;
}

在 Unity 2019.1.8 中,客户端连接并将尝试验证远程证书,但失败并显示错误 TlsException: Handshake failed - error code: UNITYTLS_INTERNAL_ERROR, verify result: UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED.

ValidateServerCertificate 始终 return true 让我的客户端连接没有问题。

我尝试使用完全相同的代码在针对 .net Framework 4.7.1 的独立 C# 控制台应用程序中复制该问题。在此应用程序中启动客户端将 return trueValidateServerCertificatesslPolicyErrors == SslPolicyErrors.None 检查。

我知道该证书是由受信任的 CA 颁发的有效证书(已从控制台应用程序接受该证书,并且在浏览器中有一个挂锁这一事实验证)。

为什么在 Unity 中验证失败,但在其他任何地方都失败?

为什么 Unity 无法验证选项 1:
尽管证书有效且正确(例如,在网络浏览器中使用它时有效),但它包含到根 CA 的中间证书链。这导致无法形成信任链(Unity 没有 cache/retrieve 中间体),导致 UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED 标志被设置。

为了解决这个问题,我需要将证书链附加到我的叶证书中,以便 Unity 可以验证整个链直到根 CA。要查找证书的证书链,您可以使用“TLS 证书链编写器”。

根据您使用的软件,您可能需要将链包含在证书中,或者将其保存在单独的文件中。来自 whatsmychaincert(我与该站点没有任何关联,只是使用它):

Note: some software requires you to put your site's certificate (e.g. example.com.crt) and your chain certificates (e.g. example.com.chain.crt) in separate files, while other software requires you to put your chain certificates after your site's certificate in the same file.

为什么 Unity 无法验证选项 2:
当您的服务器的 SSL 证书过期时,Unity 将抛出完全相同的 UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED 错误,而没有证书已过期的附加信息,因此请确保“有效期至”日期在未来(这也会导致证书失效)在网络浏览器中使用时被拒绝)。


为什么 browser/Console 应用 可以 验证:
软件在处理不完整链的方式上可以有不同的实现。它可以抛出一个错误,指出链条已损坏,因此不可信(就像 Unity 的情况一样),或者缓存并保存中间值以备后用 use/retrieve 它来自以前的会话(如浏览器和微软的.net(核心) 做).
this answer 中所述(强调我的)

In general, SSL/TLS clients will try to validate the server certificate chain as received from the server. If that chain does not please the client, then the client's behaviour depends on the implementation: some clients simply give up; others (especially Windows/Internet Explorer) will try to build another chain using locally known intermediate CA and also downloading certificates from URL found in other certificates (the "authority information access" extension).


由于跨平台兼容性,Unity 在未提供链时不信任证书是有意做出的决定,如[问题 1115214 的答案][3] 中所述:

We may be able to solve this issue by doing a verification via the system specific TLS api instead of using OpenSSL/MbedTLS to validate against root certificates as we do today, however this solution would then not work cross-platform. So we don't want to implement it today, as it would hide the misconfigured server from the user on some but not all platforms.


我在问这个问题时发现这是针对我的特殊情况的解决方案,因此决定自行回答以供将来参考。然而 UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED 可以有各种各样的原因,这只是其中之一。

对于使用 UnityWebRequestLet's Encrypt 时遇到此问题的任何人:

There is a known issue on Unity's Issue Tracker 您会发现它仅在某些版本中得到修复:

  • 已在 2020.1.0a11
  • 中修复
  • 已在 2019.3.3f1
  • 中修复
  • 已在 2018.4.18f1
  • 中修复

更改现有项目的 Unity 版本并非易事,因此我们选择购买合法的 SSL 证书,虽然不便宜,但却是最简单的解决方案。

我的解决方案是:

> Public class ForceAcceptAll : CertificateHandler {
>     protected override bool ValidateCertificate(byte[] certificateData)
>     {
>         return true;
>     } }

//--------

var cert = new ForceAcceptAll();
 
// www is a UnityWebRequest
www.certificateHandler = cert;

注意:如果使用不当(并且被 Google 发现,您的构建可能会被拒绝)。

所以慎用。

更多信息here