我发现了导致 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);
}
}