Windows/Linux dotnet core SslStream 差异
Posted
技术标签:
【中文标题】Windows/Linux dotnet core SslStream 差异【英文标题】:Windows/Linux dotnet core SslStream differences 【发布时间】:2021-10-04 06:47:42 【问题描述】:我正在尝试使用与 dotnet core 5 一起使用的本地(文件)证书获取 SslStream。在 Linux(Alpine Linux 3.14.0)上,一切都按预期运行,服务器对远程客户端进行身份验证。在 Windows(Windows 10 企业版,版本 20H2)上,即使证书验证应该被 SslStream 构造函数覆盖,身份验证过程似乎仍在尝试使用 Windows 证书存储进行验证。
这是 SslStream 的 Windows 实现中的错误,还是我缺少强制它仅使用加载的证书文件的必要配置?
下面的测试程序。该程序将为客户端和服务器生成 CA 和证书。然后它将创建 2 个线程来使用这些证书测试 SslStream
。 Linux 运行时没有任何问题,但 Windows 在运行时会抛出 System.ComponentModel.Win32Exception (0x80090304): The Local Security Authority cannot be contacted
。
using System;
namespace sslstream
class Program
static bool VerifyCertificate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate, System.Security.Cryptography.X509Certificates.X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
return true; //TODO: verify certificate chain and hostnames
static void RunClient()
var clientCert = new System.Security.Cryptography.X509Certificates.X509Certificate2("Client.pfx");
var collection = new System.Security.Cryptography.X509Certificates.X509CertificateCollection(new System.Security.Cryptography.X509Certificates.X509Certificate[] clientCert );
using var client = new System.Net.Sockets.TcpClient();
client.Connect(System.Net.IPAddress.Loopback, 12345);
var clientStream = client.GetStream();
using var sStream = new System.Net.Security.SslStream(clientStream, false, new System.Net.Security.RemoteCertificateValidationCallback(VerifyCertificate), null, System.Net.Security.EncryptionPolicy.RequireEncryption);
sStream.AuthenticateAsClient("127.0.0.1", collection, System.Security.Authentication.SslProtocols.Tls12, false);
sStream.Write(new byte[1] 55 );
static void RunServer()
var serverCert = new System.Security.Cryptography.X509Certificates.X509Certificate2("127.0.0.1.pfx");
var listener = new System.Net.Sockets.TcpListener(new System.Net.IPEndPoint(System.Net.IPAddress.Loopback, 12345));
listener.Start();
using var client = listener.AcceptTcpClient();
var clientStream = client.GetStream();
using var sStream = new System.Net.Security.SslStream(clientStream, false, new System.Net.Security.RemoteCertificateValidationCallback(VerifyCertificate), null, System.Net.Security.EncryptionPolicy.RequireEncryption);
sStream.AuthenticateAsServer(serverCert, true, System.Security.Authentication.SslProtocols.Tls12, false);
var fiftyFive = sStream.ReadByte();
if (fiftyFive != 55)
throw new Exception($"Expected 55, got fiftyFive");
static void Main(string[] args)
if (!System.IO.File.Exists("CA.pfx"))
MakeCertificates();
CRNG.Dispose();
var t1 = new System.Threading.Thread(RunServer);
t1.Start();
//TODO: wait for server to start before starting client
System.Threading.Thread.Sleep(1000);
var t2 = new System.Threading.Thread(RunClient);
t2.Start();
t1.Join();
t2.Join();
static void MakeCertificates()
MakeCA();
MakeCert("127.0.0.1");
MakeCert("Client");
static void MakeCA()
var ecdsa = System.Security.Cryptography.ECDsa.Create(); // generate asymmetric key pair
var req = new System.Security.Cryptography.X509Certificates.CertificateRequest($"cn=Certificate Authority", ecdsa, System.Security.Cryptography.HashAlgorithmName.SHA256);
req.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension(true, false, 0, true));
req.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension(req.PublicKey, false));
var cert = req.CreateSelfSigned(System.DateTimeOffset.Now, System.DateTimeOffset.Now.AddYears(1000));
System.IO.File.WriteAllBytes("CA.pfx", cert.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pfx));
System.IO.File.WriteAllText("CA.crt",
"-----BEGIN CERTIFICATE-----\r\n"
+ System.Convert.ToBase64String(cert.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Cert), System.Base64FormattingOptions.InsertLineBreaks)
+ "\r\n-----END CERTIFICATE-----");
static System.Security.Cryptography.RandomNumberGenerator CRNG = System.Security.Cryptography.RandomNumberGenerator.Create();
static void MakeCert(string cn)
var ecdsa = System.Security.Cryptography.ECDsa.Create(); // generate asymmetric key pair
var ca = new System.Security.Cryptography.X509Certificates.X509Certificate2("CA.pfx");
var req = new System.Security.Cryptography.X509Certificates.CertificateRequest($"cn=cn", ecdsa, System.Security.Cryptography.HashAlgorithmName.SHA256);
req.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension(false, false, 0, false));
req.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509KeyUsageExtension(System.Security.Cryptography.X509Certificates.X509KeyUsageFlags.DigitalSignature | System.Security.Cryptography.X509Certificates.X509KeyUsageFlags.NonRepudiation, false));
req.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension(req.PublicKey, false));
req.CertificateExtensions.Add(new System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension(new System.Security.Cryptography.OidCollection new System.Security.Cryptography.Oid("1.3.6.1.5.5.7.3.8") , true));
var serial = new byte[20];
CRNG.GetBytes(serial);
var cert = req.Create(ca, System.DateTime.Now, System.DateTime.Now.AddYears(500), serial);
cert = System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions.CopyWithPrivateKey(cert, ecdsa);
System.IO.File.WriteAllBytes($"cn.pfx", cert.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pfx));
System.IO.File.WriteAllText($"cn.crt",
"-----BEGIN CERTIFICATE-----\r\n"
+ System.Convert.ToBase64String(cert.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Cert), System.Base64FormattingOptions.InsertLineBreaks)
+ "\r\n-----END CERTIFICATE-----");
【问题讨论】:
【参考方案1】:我不确定这是否只是 Windows 上的一个错误,但我切换到 RSA 而不是 ECDSA,现在它可以在 Linux 和 Windows 上运行。
密钥生成要慢得多(尚不确定数据传输的处理开销),所以如果有人有在 Windows 上使用 ECDSA 的解决方案,我会更喜欢。
【讨论】:
以上是关于Windows/Linux dotnet core SslStream 差异的主要内容,如果未能解决你的问题,请参考以下文章
rabbitmq, windows/linux, c/c++/node.js/golang/dotnet
.NET Core的“dotnet restore”“dotnet build”和“dotnet run”命令都是用来干什么的?