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
至此,程序可以正常连接到服务器。
请先 登录后发表评论 ~