简体   繁体   中英

Windows/Linux dotnet core SslStream differences

I'm trying to get SslStream with local (file) certificates working with dotnet core 5. On Linux (Alpine Linux 3.14.0), everything functions as intended with servers authenticating the remote client. On Windows (Windows 10 Enterprise, version 20H2), it seems that the authentication procedure is still trying to use the Windows certificate store to validate even though the certificate validation should be overridden by the SslStream constructor.

Is this a bug in the Windows implementation of SslStream, or am I missing a required configuration to force it to only use the loaded certificate files?

Test program below. The program will generate a CA and certificates for a client and server. It will then create 2 threads to test a SslStream using those certificates. Linux runs without any issues, but Windows will throw a System.ComponentModel.Win32Exception (0x80090304): The Local Security Authority cannot be contacted when it runs.

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-----");
        }
    }
}

I'm not sure if it's just a bug on Windows, but I switched to RSA instead of ECDSA and now it works on both Linux and Windows.

Key generation is a lot slower (not sure about processing overhead from data transfer yet), so if anyone has a solution for using ECDSA on Windows I'd prefer that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM