使用相同的 TLS 证书来验证客户端或服务器连接,然后第二次使用失败

问题描述

我发现特定的事件序列会导致 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),ValidateCertificate,UserCertificateSelectionCallback,EncryptionPolicy.RequireEncryption);

                    using (var cert = new X509Certificate2("test.pfx","password"))
                    {
                        var certificateCollection = new X509Certificate2Collection(cert);
                        await sslStream.AuthenticateAsClientAsync(Host,certificateCollection,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);
            }
        }

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)