当前位置:首页 > 资讯列表 >Windows Server 2022 之前版本中 TLS 1.3 证书导致 SSL 连接失败的成因及应对方案

Windows Server 2022 之前版本中 TLS 1.3 证书导致 SSL 连接失败的成因及应对方案

发布时间:2026-06-05 17:45:22 分类:营销学堂

问题背景

GoDaddy 注册局提供了 TLS 1.3 版本的证书和密钥,我们使用现有的 EPP 程序连接其注册局服务器时,由于对方采用的是 TLS 1.3 协议,而运行环境为 Windows Server 2019,系统原生不支持 TLS 1.3 及其加密套件,导致程序无法正常建立连接。最终通过自定义 BouncyCastle 的 DefaultTlsClient,绕过 Windows 自带的加密套件,成功实现了连接。

问题排查与解决过程

1初始连接失败

现有程序在配置证书及服务器端口后,连接时报错:

Unhandled exception. System.Security.Authentication.AuthenticationException: Authentication failed because the remote party sent a TLS alert: 'HandshakeFailure'.

2使用 OpenSSL 验证连接

尝试使用 OpenSSL 连接服务器:

openssl s_client -connect server:port -cert cert.crt -key key.pembash

该方法可以正常连接服务器并获取返回信息,由此判断证书及服务器端口配置本身没有问题。

3分析 OpenSSL 连接信息

通过 OpenSSL 连接输出的信息进行分析,关键内容如下:

SSL handshake has read 8394 bytes and written 436 bytes Verification error: self signed certificate in certificate chain --- New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Server public key is 2048 bit Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE No ALPN negotiated Early data was not sent Verify return code: 19 (self signed certificate in certificate chain)

可以看到服务器协商使用的是 TLSv1.3 协议,加密套件为 TLS_AES_256_GCM_SHA384

4尝试指定加密套件

根据上述分析,尝试在代码中明确指定 TLS 1.3 协议及加密套件:

var clientOptions = new SslClientAuthenticationOptions { EnabledSslProtocols = SslProtocols.Tls13, CipherSuitesPolicy = new CipherSuitesPolicy( new[] { TlsCipherSuite.TLS_AES_256_GCM_SHA384 }), };csharp

修改后程序提示不支持该加密套件。经排查发现:当前运行环境为 Windows Server 2019,该系统不支持 TLS 1.3 协议及 TLS_AES_256_GCM_SHA384 加密套件。而 .NET 运行时的加密能力依赖于操作系统提供的加密套件,因此无法绕过此系统限制。

说明:Windows Server 2022 开始才原生支持 TLS 1.3,Windows Server 2019 及更早版本均不支持。

5使用 BouncyCastle 绕过系统限制

考虑到之前同事曾使用 BouncyCastle 实现国密证书的 SSL 连接,于是尝试采用类似方式绕过 Windows 自带的加密套件。通过查阅代码及 AI 搜索,自定义了以下实现:

自定义 TLS 1.3 客户端

public class Tlsv13Client : DefaultTlsClient { private readonly string sniName; private TlsAuthentication tlsAuthentication; private X509Certificate[] certificates; private RsaKeyParameters privateKey; public Tlsv13Client(string sniName) : base(new BcTlsCrypto(new SecureRandom())) { this.sniName = sniName; this.algorithm = algorithm; } public override void Init(TlsClientContext context) { base.Init(context); var cryptoParameters = new TlsCryptoParameters(m_context); var crypto = Crypto as BcTlsCrypto; tlsAuthentication = new Tlsv13Authentication(crypto, privateKey, cryptoParameters, certificates); } public Stream AuthAsClient(Stream input) { var obj = new TlsClientProtocol(input); obj.Connect(this); return obj.Stream; } public Stream AuthAsClient(Stream input, byte[] ca, byte[] cert, byte[] privateKey, string password) { return AuthAsClient(input, new byte[][] { ca, cert }, privateKey, password); } public Stream AuthAsClient(Stream input, byte[][] certs, byte[] privateKey, string password) { List<X509Certificate> certList = new List<X509Certificate>(); foreach (var item in certs) { if (item == null || item.Length == 0) continue; var c = ReadFromBytes(item) as X509Certificate; if (c == null) continue; certList.Add(c); } var keyObject = ReadFromBytes(privateKey, password); AsymmetricKeyParameter _key; if (keyObject is AsymmetricCipherKeyPair keyPair) _key = keyPair.Private; else if (keyObject is AsymmetricKeyParameter parameter) _key = parameter; else throw new InvalidOperationException(); SetClientCertificates(certList.ToArray(), _key); return AuthAsClient(input); } private static object ReadFromBytes(byte[] input, string password = "") { using (var stream = new MemoryStream(input)) { using (TextReader reader = new StreamReader(stream)) { return new Org.BouncyCastle.OpenSsl.PemReader(reader, ClearPassword.Create(password)).ReadObject(); } } } private void SetClientCertificates(X509Certificate[] certificates, AsymmetricKeyParameter privateKey) { RsaKeyParameters rsaKeyParameters = privateKey as RsaKeyParameters ?? throw new ArgumentException(null, nameof(privateKey)); this.privateKey = rsaKeyParameters; this.certificates = certificates; } protected override IList GetSniServerNames() { if (!string.IsNullOrEmpty(sniName)) { return new ServerName[] { new ServerName(0, Encoding.ASCII.GetBytes(sniName)) }; } return null; } protected override ProtocolVersion[] GetSupportedVersions() { return ProtocolVersion.TLSv13.Only(); } public override TlsAuthentication GetAuthentication() { return tlsAuthentication; } protected override int[] GetSupportedCipherSuites() { return new int[] { CipherSuite.TLS_AES_256_GCM_SHA384, CipherSuite.TLS_AES_128_GCM_SHA256, CipherSuite.TLS_CHACHA20_POLY1305_SHA256, CipherSuite.TLS_AES_128_CCM_SHA256, CipherSuite.TLS_AES_128_CCM_8_SHA256 }; } }csharp

自定义 TLS 1.3 认证实现

internal class Tlsv13Authentication : TlsAuthentication { private readonly BcTlsCrypto crypto; private readonly RsaKeyParameters privateKey; private readonly TlsCryptoParameters cryptoParameters; private readonly X509Certificate[] certificates; public Tlsv13Authentication(BcTlsCrypto crypto, RsaKeyParameters privateKey, TlsCryptoParameters cryptoParameters, X509Certificate[] certificates) { this.crypto = crypto; this.privateKey = privateKey; this.cryptoParameters = cryptoParameters; this.certificates = certificates; } public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest) { if (certificates == null || certificates.Length == 0) { throw new ArgumentException("no certificates"); } CertificateEntry[] array = new CertificateEntry[certificates.Length]; int num = 0; X509Certificate[] array2 = certificates; foreach (X509Certificate x509Certificate in array2) { array[num] = new CertificateEntry(new BcTlsCertificate(crypto, x509Certificate.CertificateStructure), null); num++; } return new BcDefaultTlsCredentialedSigner(cryptoParameters, crypto, privateKey, new Certificate(certificateRequest.GetCertificateRequestContext(), array), SignatureAndHashAlgorithm.rsa_pss_pss_sha384); } public void NotifyServerCertificate(TlsServerCertificate serverCertificate) { } }csharp

至此,程序可以正常连接到服务器。

补充说明:证书格式问题

起初使用 PFX 格式的证书文件进行读取,但读取后连接时报错证书无效,推测在读取 PFX 文件的过程中可能丢失了 CA 证书和中间证书。后来改为将证书和密钥分别导出为独立的 PEM 文件,并将 CA 证书与中间证书保存在一起,之后即可正常连接。

总结

项目说明
根本原因Windows Server 2019 及以下版本不支持 TLS 1.3 协议及其加密套件,.NET 运行时依赖系统加密能力
解决思路使用 BouncyCastle 库自行实现 TLS 1.3 客户端,绕过操作系统加密套件限制
核心实现继承 DefaultTlsClient,重写 GetSupportedVersions()GetSupportedCipherSuites(),配合自定义 TlsAuthentication 完成认证
注意事项证书文件建议使用 PEM 格式分别存放,避免 PFX 格式读取时丢失 CA 证书和中间证书