You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
93 lines
3.8 KiB
93 lines
3.8 KiB
using Org.BouncyCastle.Crypto.Tls;
|
|
using Org.BouncyCastle.Security;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
namespace AIK.Service.Service
|
|
{
|
|
/// <summary>
|
|
/// BouncyCastle 纯托管 TLS 1.2 握手封装,供 HTTP(BouncyCastleTlsHandler)与
|
|
/// WebSocket(BouncyCastleWebSocketClient)共用。完全绕过 Win7 SChannel 对 TLS 1.2 的限制。
|
|
/// </summary>
|
|
internal static class BouncyCastleTlsHelper
|
|
{
|
|
/// <summary>
|
|
/// 在已建立的 TCP 流上执行 BouncyCastle TLS 1.2 握手(含 SNI 与证书链校验),
|
|
/// 返回可读写的 TLS 流。握手为阻塞操作,调用方可在后台线程执行。
|
|
/// </summary>
|
|
public static Stream ConnectTlsStream(Stream innerStream, string host)
|
|
{
|
|
var protocol = new TlsClientProtocol(innerStream, new SecureRandom());
|
|
protocol.Connect(new BouncyTlsClient(host));
|
|
return protocol.Stream;
|
|
}
|
|
|
|
/// <summary>
|
|
/// BouncyCastle TLS 1.2 客户端,支持 SNI(server_name 扩展)。
|
|
/// 证书验证:使用 .NET X509Chain 校验,RevocationMode=NoCheck 以兼容 Win7 离线环境。
|
|
/// </summary>
|
|
private sealed class BouncyTlsClient : DefaultTlsClient
|
|
{
|
|
private readonly string _host;
|
|
|
|
public BouncyTlsClient(string host)
|
|
: base()
|
|
{
|
|
_host = host;
|
|
}
|
|
|
|
public override IDictionary GetClientExtensions()
|
|
{
|
|
var extensions = base.GetClientExtensions() ?? new Hashtable();
|
|
// SNI:多域名虚拟主机(CDN/nginx)必须带 server_name,否则握手被拒
|
|
var serverNames = new ServerNameList(new List<ServerName> { new ServerName(0, _host) });
|
|
TlsExtensionsUtilities.AddServerNameExtension(extensions, serverNames);
|
|
return extensions;
|
|
}
|
|
|
|
public override TlsAuthentication GetAuthentication() => new BouncyTlsAuthentication();
|
|
}
|
|
|
|
private sealed class BouncyTlsAuthentication : TlsAuthentication
|
|
{
|
|
public void NotifyServerCertificate(Certificate serverCertificate)
|
|
{
|
|
if (serverCertificate == null || serverCertificate.Length == 0)
|
|
{
|
|
throw new TlsFatalAlert(AlertDescription.bad_certificate);
|
|
}
|
|
|
|
// 将 BouncyCastle 证书转为 .NET X509Certificate2 并做链校验
|
|
var chain = new X509Chain { ChainPolicy = { RevocationMode = X509RevocationMode.NoCheck } };
|
|
var certs = new List<X509Certificate2>();
|
|
try
|
|
{
|
|
for (int i = 0; i < serverCertificate.Length; i++)
|
|
{
|
|
certs.Add(new X509Certificate2(serverCertificate.GetCertificateAt(i).GetEncoded()));
|
|
}
|
|
foreach (var extra in certs.Skip(1))
|
|
{
|
|
chain.ChainPolicy.ExtraStore.Add(extra);
|
|
}
|
|
bool valid = chain.Build(certs[0]);
|
|
if (!valid)
|
|
{
|
|
var errors = string.Join("; ", chain.ChainStatus.Select(s => s.StatusInformation.Trim()));
|
|
throw new TlsFatalAlert(AlertDescription.bad_certificate, new Exception($"证书链校验失败: {errors}"));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
foreach (var c in certs) c.Dispose();
|
|
}
|
|
}
|
|
|
|
public TlsCredentials GetClientCredentials(Org.BouncyCastle.Crypto.Tls.CertificateRequest certificateRequest) => null;
|
|
}
|
|
}
|
|
}
|
|
|