using Microsoft.Win32;
using System;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Threading.Tasks;
namespace AIKKey.Infrastructure
{
///
/// Win7 系统层 SChannel 默认不启用 TLS 1.2 协议,即使代码层(Flurl.Http /
/// websocket-sharp 连接级 SslProtocols)显式指定 TLS 1.2,底层握手仍会失败
/// (表现:websocket-sharp 关闭码 1015、HTTP 请求报"未能创建 SSL/TLS 安全通道")。
/// 需要同时写入两层注册表并重启程序才能生效:
/// 1. .NET 层:SchUseStrongCrypto=1
/// 2. 系统层:SCHANNEL\Protocols\TLS 1.2 的 Enabled=1 / DisabledByDefault=0
///
public static class TlsRegistryHelper
{
private const string NetFrameworkKeyPath = @"SOFTWARE\Microsoft\.NETFramework\v4.0.30319";
private const string ValueName = "SchUseStrongCrypto";
// 系统级 SChannel 协议开关
private const string SchannelTls12ClientKeyPath =
@"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client";
private const string SchannelTls12ServerKeyPath =
@"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server";
private const string EnabledValueName = "Enabled";
private const string DisabledByDefaultValueName = "DisabledByDefault";
///
/// 是否 Windows 7(含 SP1)。Win7 的 SChannel 默认不启用 TLS 1.2,
/// 需要系统级注册表 + 重启程序才生效。
///
public static bool IsWindows7 =>
Environment.OSVersion.Platform == PlatformID.Win32NT &&
Environment.OSVersion.Version.Major == 6 &&
Environment.OSVersion.Version.Minor == 1;
///
/// 检测并写入两层 TLS 1.2 注册表配置(.NET 层 SchUseStrongCrypto + 系统层 SChannel)。
/// 写入 HKLM 需要管理员权限,无权限时只累计到 ,不抛异常。
///
/// 本次是否实际写入了注册表(需要重启进程才能完全生效)。
/// 写入失败的原因(可能包含多个键的错误信息)。
/// 与 相同:true 表示至少一处已写入。
public static bool EnsureTls12Enabled(out bool changed, out string error)
{
changed = false;
error = string.Empty;
// ---- 1. .NET 层:SchUseStrongCrypto=1 ----
TryWriteDwordIfNeeded(RegistryView.Registry64, NetFrameworkKeyPath, ValueName, 1, ref changed, ref error);
// ---- 2. 系统层:SChannel TLS 1.2 Client / Server 启用 ----
TryWriteDwordIfNeeded(RegistryView.Registry64, SchannelTls12ClientKeyPath, EnabledValueName, 1, ref changed, ref error);
TryWriteDwordIfNeeded(RegistryView.Registry64, SchannelTls12ClientKeyPath, DisabledByDefaultValueName, 0, ref changed, ref error);
TryWriteDwordIfNeeded(RegistryView.Registry64, SchannelTls12ServerKeyPath, EnabledValueName, 1, ref changed, ref error);
TryWriteDwordIfNeeded(RegistryView.Registry64, SchannelTls12ServerKeyPath, DisabledByDefaultValueName, 0, ref changed, ref error);
return changed;
}
///
/// 检测两层 TLS 1.2 配置是否已生效(.NET 层或系统层任一已启用即可)。
/// 用于区分"尚未配置(需管理员运行一次)"与"已配置但可能需重启"。
///
public static bool IsTls12Enabled()
{
return IsDwordValue(RegistryView.Registry64, NetFrameworkKeyPath, ValueName, 1)
|| IsDwordValue(RegistryView.Registry64, SchannelTls12ClientKeyPath, EnabledValueName, 1);
}
private static bool IsDwordValue(RegistryView view, string keyPath, string valueName, int expected)
{
try
{
using var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view);
using var key = baseKey.OpenSubKey(keyPath);
return key?.GetValue(valueName, 0) is int value && value == expected;
}
catch
{
return false;
}
}
private static void TryWriteDwordIfNeeded(
RegistryView view,
string keyPath,
string valueName,
int value,
ref bool changed,
ref string error)
{
try
{
using var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view);
using var key = baseKey.OpenSubKey(keyPath, writable: true)
?? baseKey.CreateSubKey(keyPath, writable: true);
if (key is null)
{
error += $"无法打开或创建注册表键: {keyPath} ({view})。";
return;
}
var current = key.GetValue(valueName, -1);
if (current is int cur && cur == value)
{
return; // 已生效,无需写入
}
key.SetValue(valueName, value, RegistryValueKind.DWord);
changed = true;
}
catch (UnauthorizedAccessException ex)
{
error += $"无权限写入 {keyPath}\\{valueName} ({view}): {ex.Message}。请以管理员身份运行一次,或手动执行 reg add 命令。";
}
catch (Exception ex)
{
error += $"写入 {keyPath}\\{valueName} ({view}) 失败: {ex.Message}。";
}
}
///
/// 读取当前两层 TLS 1.2 注册表配置的实际值,用于诊断为何 Win7 仍无法协商 TLS 1.2。
///
public static string GetRegistryStatus()
{
var sb = new StringBuilder();
sb.AppendLine($"SchUseStrongCrypto(.NET): {ReadDword(NetFrameworkKeyPath, ValueName)}");
sb.AppendLine($"SCHANNEL TLS1.2 Client Enabled: {ReadDword(SchannelTls12ClientKeyPath, EnabledValueName)}, DisabledByDefault: {ReadDword(SchannelTls12ClientKeyPath, DisabledByDefaultValueName)}");
sb.AppendLine($"SCHANNEL TLS1.2 Server Enabled: {ReadDword(SchannelTls12ServerKeyPath, EnabledValueName)}, DisabledByDefault: {ReadDword(SchannelTls12ServerKeyPath, DisabledByDefaultValueName)}");
return sb.ToString();
}
///
/// 用 SslStream 对指定主机做一次 TLS 1.2 握手探测。
/// 代码层 SslProtocols 只是"请求"协议,最终能否协商取决于系统 SChannel 是否启用 TLS 1.2
/// (Win7 缺补丁/注册表未生效时,此处会抛"未能创建 SSL/TLS 安全通道")。
///
public static async Task ProbeTls12HandshakeAsync(string host, int port)
{
try
{
using var tcp = new TcpClient();
var connectTask = tcp.ConnectAsync(host, port);
if (await Task.WhenAny(connectTask, Task.Delay(TimeSpan.FromSeconds(8))) != connectTask)
{
return $"TCP 连接 {host}:{port} 超时(网络不通或防火墙拦截)";
}
await connectTask;
using var ssl = new SslStream(tcp.GetStream(), false, (s, cert, chain, errors) => true);
var handshakeTask = ssl.AuthenticateAsClientAsync(host, null, SslProtocols.Tls12, false);
if (await Task.WhenAny(handshakeTask, Task.Delay(TimeSpan.FromSeconds(8))) != handshakeTask)
{
return $"TLS 1.2 握手 {host}:{port} 超时";
}
await handshakeTask;
return $"TLS 1.2 握手成功: {host}:{port} (协议={ssl.SslProtocol})";
}
catch (Exception ex)
{
return $"TLS 1.2 握手失败: {host}:{port} — {ex.GetType().Name}: {ex.Message}";
}
}
private static string ReadDword(string keyPath, string valueName)
{
try
{
using var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
using var key = baseKey.OpenSubKey(keyPath);
var value = key?.GetValue(valueName, -1);
return value is int i ? i.ToString() : $"未设置({value ?? "null"})";
}
catch (Exception ex)
{
return $"读取失败: {ex.Message}";
}
}
}
}