0

我发现了导致 TLS 身份验证失败的特定事件序列,并在之前成功时出现错误“客户端和服务器无法通信,因为它们不具备通用算法”。这是 SChannel 中的错误吗?

使用 .NET 4.7.2 和 c# 开发。

PC 通过相互 TLS 身份验证相互连接。每台 PC 都支持客户端和服务器连接,并且可能是从一台 PC 连接的服务器,但客户端连接到另一台 PC。每台 PC 都有自己的唯一证书 (.pfx),我们将其用于服务器和客户端连接。

例如,PC1 和 PC2 都在与 PC3 通信。PC1 与 PC3 建立连接。PC3使用它的 .pfx调用SslStream.AuthenticateAsServer并且成功了。

然后 PC3 与 PC2 建立客户端连接并使用相同的 .pfx调用SslStream.AuthenticateAsClient 。现在失败并出现错误“客户端和服务器无法通信,因为它们没有通用算法”

有趣的是,如果所有应用程序都重新启动并且以相反的顺序建立连接,那么行为就会发生变化。如果首先建立从 PC3 到 PC2 的客户端连接,则连接成功,但随后从 PC1 到 PC3 的服务器连接失败并出现同样的错误。

一旦连接在一个方向上成功,我们就很好,后续连接也会成功,但如果我们尝试在另一个方向上使用相同的证书,那么除非重新启动应用程序,否则它将失败。

此外,我在“AuthenticateAs”方法中指定了 SslProtocols.None。(推荐用于 .NET 4.7.2 及更高版本)。如果我更改为指定 SslProtocols.Tls12 则两个连接都可以正常工作。

我不知道如何进一步调试。有什么想法吗?SChannel 是否保留某种内部状态,阻止我对客户端和服务器连接使用相同的证书?

我在下面放了一些示例代码来阐明我的意思。请注意,这只是显示“服务器”和“客户端”代码以重现此问题的示例。

 
        private static bool ValidateCertificate(object sender, X509Certificate certificate, X509Chain chain,
    SslPolicyErrors sslPolicyErrors)
        {
            // do some proper validation of cert...
            return true;
        }
        private X509Certificate UserCertificateSelectionCallback(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
        {
            if (localCertificates != null && localCertificates.Count > 0)
                return localCertificates[0];
            return null;
        }

        public async void Listen()
        {
            var tcpListener = new TcpListener(IPAddress.Any, Port);
            try
            {
                tcpListener.Start();
                Console.WriteLine("Server listening...");
                var client = await tcpListener.AcceptTcpClientAsync();
                Console.WriteLine("Server authenticating...");
                var sslStream = new SslStream(client.GetStream(), false, ValidateCertificate);
                var cert = new X509Certificate2("test.pfx", "password");
                await sslStream.AuthenticateAsServerAsync(cert, true, System.Security.Authentication.SslProtocols.None, false);
                Console.WriteLine("Server authenticated.");

                // wait 5 secs then drop connection
                await Task.Delay(5000);
                sslStream.Dispose();
                cert.Reset();
                Console.WriteLine("Server dropped.");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                if (ex.InnerException != null)
                    Console.WriteLine(ex.InnerException.Message);
            }
            finally
            {
                tcpListener.Stop();
                Console.WriteLine("Server stopped Listening.");
            }
        }

        public async void Connect()
        {
            try
            {
                using (var socket = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, ProtocolType.Tcp))
                {
                    Console.WriteLine("Client connecting...");
                    await socket.ConnectAsync(Host, Port);

                    Console.WriteLine("Client authenticating...");

                    var sslStream = new SslStream(new NetworkStream(socket, true), false, ValidateCertificate, UserCertificateSelectionCallback, EncryptionPolicy.RequireEncryption);

                    using (var cert = new X509Certificate2("test.pfx", "password"))
                    {
                        var certificateCollection = new X509Certificate2Collection(cert);
                        await sslStream.AuthenticateAsClientAsync(Host, certificateCollection, System.Security.Authentication.SslProtocols.None, false);
                        Console.WriteLine("Client authenticated");

                        // wait 5 secs then drop connection
                        await Task.Delay(5000);
                    }

                    socket.Close();
                    Console.WriteLine("Client dropped");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                if (ex.InnerException != null)
                    Console.WriteLine(ex.InnerException.Message);
            }
        }
4

0 回答 0