unity TlsException: 握手失败 UNITYTLS_X509VERIFY_FLAG_NOT_TRUSTED

Rem*_*_rm 6 c# ssl sslstream unity-game-engine tls1.2

我正在尝试更新我的应用程序TcpClient以使用 TLSSslStream而不是普通的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

并用于验证证书

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;
}
Run Code Online (Sandbox Code Playgroud)

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

使ValidateServerCertificate总回报true让我的客户端连接没有问题。

我尝试使用完全相同的代码在面向 .net framework 4.7.1 的独立 C# 控制台应用程序中复制该问题。在这个应用程序启动客户端将返回trueValidateServerCertificatesslPolicyErrors == SslPolicyErrors.None检查。

我知道该证书是由受信任的 CA 颁发的有效证书(通过控制台应用程序接受该证书的事实进行验证,并且它在浏览器中带有挂锁)。

为什么验证在 Unity 中失败,而在其他地方没有?

Rem*_*_rm 10

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

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

根据您使用的软件,您可能需要在证书中包含该链,或者将其保存在单独的文件中。来自whatsmychaincert(我绝不隶属于该站点,只是使用它):

注意:有些软件要求您将站点的证书(例如example.com.crt)和您的链证书(例如example.com.chain.crt)放在不同的文件中,而其他软件则要求您将您的链证书放在您站点的证书之后同一文件中的证书。

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


为什么浏览器/控制台应用程序可以验证:
软件在处理不完整链方面可以有不同的实现。它可以抛出一个错误,指出链已损坏,因此无法信任(就像 Unity 一样),或者缓存并保存中间体以供以后使用/从以前的会话中检索它(如浏览器和 Microsoft 的 .net (核心)做)。
正如这个答案中所解释的(强调我的)

通常,SSL/TLS 客户端会尝试验证从服务器收到的服务器证书链。如果这条链不能取悦客户,那么客户的行为取决于实现:一些客户干脆放弃;其他人(尤其是 Windows/Internet Explorer)将尝试使用本地已知的中间 CA 构建另一个链,并从其他证书中找到的 URL 下载证书(“权限信息访问”扩展名)。


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

我们可以通过系统特定的 TLS api 进行验证来解决这个问题,而不是像我们今天那样使用 OpenSSL/MbedTLS 来验证根证书,但是这个解决方案将无法跨平台工作。所以我们今天不想实现它,因为它会在某些但不是所有平台上对用户隐藏错误配置的服务器。


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