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}"; } } } }