如何将 SslStream/AuthenticateAsServer 与自签名 cert/key 一起使用?

How can I use SslStream/AuthenticateAsServer with a self signed cert/key?

我想编写(使用 C# 和 dotnet 4.8)使用 TLS 确保安全的 TCP 服务。为此,我想将 SslStream 与 TcpListener 一起使用,但我不断收到奇怪的错误。

我的实验程序如下。简而言之:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

namespace TestTlsService
{
    class Program
    {
        static string tempCert;

        static void Main(string[] args)
        {
            /* Create a cert. */
            var cr = new CertificateRequest("cn=this.is.invalid", ECDsa.Create(), HashAlgorithmName.SHA256);
            using (var cert = cr.CreateSelfSigned(DateTime.UtcNow.AddDays(-1), DateTime.UtcNow.AddYears(+1)))
            {
                var exp = cert.Export(X509ContentType.Pfx);
                tempCert = Path.Combine(Path.GetTempPath(), "MySelfSignedCert.pfx");
                File.WriteAllBytes(tempCert, exp);
            }

            /* Launch a service thread. */
            Thread svc = new Thread(ServiceMain);
            svc.Start();
            Thread.Sleep(100);

            /* Connect as a client. */
            using (var tcp = new TcpClient())
            {
                tcp.Connect("localhost", 1984);
                var stream = tcp.GetStream();

                /* Read the line "GoTLS" from the server. */
                var insecureLine = ReadLine(stream);

                /* Hand over control to TLS. */
                using (var tls = new SslStream(stream, false, CheckCert))
                {
                    tls.AuthenticateAsClient("this.is.invalid");

                    /* Read a different line, this time securely. */
                    string line = ReadLine(tls);
                }
            }
        }

        static void ServiceMain()
        {
            /* Open a listener and start listening. */
            var listen = new TcpListener(IPAddress.Loopback, 1984);
            listen.Start();
            using (var tcp = listen.AcceptTcpClient())
            {
                /* Send "GoTLS" to the client insecurely. */
                var stream = tcp.GetStream();
                stream.Write(Encoding.ASCII.GetBytes("GoTLS\r\n"), 0, 7);

                /* Hand over control to TLS, using the self-signed cert from earlier. */
                using (var tls = new SslStream(stream))
                {
                    var cert = new X509Certificate2(tempCert);
                    tls.AuthenticateAsServer(cert);

                    /* Send a new message inside the secure channel. */
                    tls.Write(Encoding.ASCII.GetBytes("Hello\r\n"), 0, 7);
                }
            }
        }

        /* Simple function that just reads a line. */
        static string ReadLine(Stream stream)
        {
            byte[] line = new byte[100];
            int bytesIn = stream.Read(line, 0, 100);
            string lineAscii = Encoding.ASCII.GetString(line, 0, bytesIn);
            return lineAscii;
        }

        /* Accept all certificates. DO NOT COPY THIS INTO YOUR OWN CODE! */
        private static bool CheckCert(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            /* The full version will check the cert, but for now we'll just... */
            return true;
        }
    }
}

此程序在 AuthenticateAsServer 行中抛出 AuthenticationException 并显示消息“对 SSPI 的调用失败,请参阅内部异常。”而内部异常有“客户端和服务器无法通信,因为它们不具备通用算法”。我发现 SslStream 库找不到自己的通用协议的可能性很小。

我怀疑问题的根源在于我使用的是自签名证书,因为我能找到的所有 AuthenticateAsServer 示例都使用了预先生成的证书文件,唉,我没有没有。

如何在服务器端使用 SslStream 与自签名证书协商 TLS?


抢占预期的问题...

我知道客户端可以正常工作,因为我用邮件服务器替换了“localhost”(并跳过了 read-a-line)并且客户端线程可以正常读取远程邮件发送的安全欢迎行服务器在协商 TLS 后发送。

ServicePointManager.SecurityProtocol 设置为 Tls12 没有帮助。

如果删除 SslStream 之前的“GoTLS”交换,我会遇到同样的错误。

我已经搜索了错误消息。我能找到的所有答案都建议将 SecurityProtocol 设置为 Tls12,我已经尝试过了。

我尝试了其他格式的证书而不是 PFX,但我收到不同的错误,抱怨我缺少私钥。

不知道为什么,它不喜欢你颁发的ECDSA证书。如果您改为使用标准 RSA:

var cr = new CertificateRequest(new X500DistinguishedName("cn=this.is.invalid"), RSA.Create(), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

并保持其他一切不变 - 然后它将按预期工作。理论上,TLS 支持 ECC(椭圆曲线)密钥,但我不是这方面的专家,也不确定为什么 SslStream.AuthenticateAsServer 不喜欢该证书。希望您不需要专门的 ECC,那么使用 RSA 的上述解决方法就完全可以了。