From 78e5de796e5caf46ee1dd16d4bb6a8674fbe8970 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Tue, 23 Dec 2025 00:58:38 +0900 Subject: [PATCH] Add Catswords.TlsReport TLS 1.2 offline inspector tool Introduces the Catswords.TlsReport project, a .NET Framework 4.7.2 console application for offline inspection of Windows TLS 1.2 readiness. Includes project files, configuration, and a comprehensive Tls12OfflineInspector utility that checks OS, registry, and crypto policy for TLS 1.2 support. Updates the solution file to include the new project. --- .../Catswords.TlsReport/App.config | 6 + .../Catswords.TlsReport.csproj | 54 ++ .../Catswords.TlsReport/Program.cs | 18 + .../Properties/AssemblyInfo.cs | 33 + .../Tls12OfflineInspector.cs | 755 ++++++++++++++++++ WelsonJS.Augmented/WelsonJS.Augmented.sln | 14 +- 6 files changed, 878 insertions(+), 2 deletions(-) create mode 100644 WelsonJS.Augmented/Catswords.TlsReport/App.config create mode 100644 WelsonJS.Augmented/Catswords.TlsReport/Catswords.TlsReport.csproj create mode 100644 WelsonJS.Augmented/Catswords.TlsReport/Program.cs create mode 100644 WelsonJS.Augmented/Catswords.TlsReport/Properties/AssemblyInfo.cs create mode 100644 WelsonJS.Augmented/Catswords.TlsReport/Tls12OfflineInspector.cs diff --git a/WelsonJS.Augmented/Catswords.TlsReport/App.config b/WelsonJS.Augmented/Catswords.TlsReport/App.config new file mode 100644 index 0000000..56efbc7 --- /dev/null +++ b/WelsonJS.Augmented/Catswords.TlsReport/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/WelsonJS.Augmented/Catswords.TlsReport/Catswords.TlsReport.csproj b/WelsonJS.Augmented/Catswords.TlsReport/Catswords.TlsReport.csproj new file mode 100644 index 0000000..ff98449 --- /dev/null +++ b/WelsonJS.Augmented/Catswords.TlsReport/Catswords.TlsReport.csproj @@ -0,0 +1,54 @@ + + + + + Debug + AnyCPU + {05EE55FD-76C6-482E-9886-72EB5BD3C621} + Exe + Catswords.TlsReport + Catswords.TlsReport + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WelsonJS.Augmented/Catswords.TlsReport/Program.cs b/WelsonJS.Augmented/Catswords.TlsReport/Program.cs new file mode 100644 index 0000000..bc6c5db --- /dev/null +++ b/WelsonJS.Augmented/Catswords.TlsReport/Program.cs @@ -0,0 +1,18 @@ +using System; + +namespace Catswords.TlsReport +{ + internal class Program + { + static void Main(string[] args) + { + Console.WriteLine("Catswords TLS 1.2 Offline Inspector"); + Console.WriteLine("https://catswords.com"); + Console.WriteLine(); + + var report = Tls12OfflineInspector.Evaluate(); + Console.WriteLine(report.ToText()); + Console.WriteLine(); + } + } +} diff --git a/WelsonJS.Augmented/Catswords.TlsReport/Properties/AssemblyInfo.cs b/WelsonJS.Augmented/Catswords.TlsReport/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..afd88d6 --- /dev/null +++ b/WelsonJS.Augmented/Catswords.TlsReport/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해 +// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면 +// 이러한 특성 값을 변경하세요. +[assembly: AssemblyTitle("Catswords.TlsReport")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Catswords.TlsReport")] +[assembly: AssemblyCopyright("Copyright © 2025")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에 +// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면 +// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요. +[assembly: ComVisible(false)] + +// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다. +[assembly: Guid("05ee55fd-76c6-482e-9886-72eb5bd3c621")] + +// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다. +// +// 주 버전 +// 부 버전 +// 빌드 번호 +// 수정 버전 +// +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/WelsonJS.Augmented/Catswords.TlsReport/Tls12OfflineInspector.cs b/WelsonJS.Augmented/Catswords.TlsReport/Tls12OfflineInspector.cs new file mode 100644 index 0000000..468cd73 --- /dev/null +++ b/WelsonJS.Augmented/Catswords.TlsReport/Tls12OfflineInspector.cs @@ -0,0 +1,755 @@ +// Tls12OfflineInspector.cs (single file) +// Offline-only TLS 1.2 readiness inspector for Windows. +// - No network probing / no actual TLS handshake. +// - Produces structured report with Pass/Info/Warn/Fail items. +// +// Example: +// var report = Tls12OfflineInspector.Evaluate(); +// Console.WriteLine(report.ToText()); + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Security.Authentication; +using System.Security.Principal; +using Microsoft.Win32; + +namespace Catswords.TlsReport +{ + public static class Tls12OfflineInspector + { + // ---------------------------- + // Public entry point + // ---------------------------- + + public static Report Evaluate() + { + var ctx = new Context(); + var items = new List(); + + // 0) Environment / runtime surface + items.Add(CheckOsVersion(ctx)); + items.Add(CheckProcessBitness(ctx)); + items.Add(CheckTls12EnumAvailable(ctx)); + items.Add(CheckSslProtocolsTls12Available(ctx)); + + // 1) OS crypto DLL presence (sanity) + items.Add(CheckSystemDllPresence(ctx, "schannel.dll")); + items.Add(CheckSystemDllPresence(ctx, "bcrypt.dll")); + items.Add(CheckSystemDllPresence(ctx, "crypt32.dll")); + items.Add(CheckSystemDllPresence(ctx, "ncrypt.dll")); + + // 2) Schannel protocol policy + items.Add(CheckSchannelTls12KeyPresence(ctx)); + items.Add(CheckSchannelTls12ClientPolicy(ctx)); + items.Add(CheckSchannelTls12ServerPolicy(ctx)); + items.Add(CheckLegacyProtocolKeyHints(ctx)); + + // 3) WinHTTP defaults / legacy guidance + items.Add(CheckWinHttpDefaultSecureProtocols(ctx)); + items.Add(CheckWinHttpDefaultSecureProtocolsIncludesTls12(ctx)); + + // 4) .NET Framework policy keys + items.Add(CheckDotNetStrongCrypto(ctx)); + items.Add(CheckDotNetSystemDefaultTlsVersions(ctx)); + items.Add(CheckDotNetV2StrongCrypto(ctx)); + items.Add(CheckDotNetV2SystemDefaultTlsVersions(ctx)); + + // 5) Process-level legacy setting + items.Add(CheckServicePointManagerProtocol(ctx)); + + // 6) Cipher suite policy / crypto hardening + items.Add(CheckCipherSuitePolicyKey(ctx)); + items.Add(CheckCipherSuitePolicyFunctions(ctx)); + items.Add(CheckSchannelHashesAndKeyExchangeExplicitDisables(ctx)); + items.Add(CheckSchannelStrongCiphersExplicitDisableHeuristic(ctx)); + + // 7) FIPS policy + items.Add(CheckFipsPolicy(ctx)); + + // 8) Proxy hints + items.Add(CheckWinHttpProxyPresence(ctx)); + items.Add(CheckWinInetUserProxyPresence(ctx)); + + // 9) Schannel logging (diagnostics) + items.Add(CheckSchannelEventLogging(ctx)); + + return BuildReport(items); + } + + // ---------------------------- + // Context + // ---------------------------- + + private sealed class Context + { + public Version OsVersion = Environment.OSVersion.Version; + public bool Is64BitProcess = (IntPtr.Size == 8); + public string SystemDir = Environment.SystemDirectory.TrimEnd('\\'); + } + + // ---------------------------- + // Check factories: Pass/Info/Warn/Fail + // ---------------------------- + + private static Item Pass(string id, string detail) => new Item(id, Level.Pass, detail); + private static Item Info(string id, string detail) => new Item(id, Level.Info, detail); + private static Item Warn(string id, string detail) => new Item(id, Level.Warn, detail); + private static Item Fail(string id, string detail) => new Item(id, Level.Fail, detail); + + // ---------------------------- + // Checks: Environment / runtime + // ---------------------------- + + private static Item CheckOsVersion(Context ctx) + { + string hint = GetWindowsNameHint(ctx.OsVersion); + + if (ctx.OsVersion.Major >= 10) + return Info("OS Version", "Windows " + hint + " (" + ctx.OsVersion + "). TLS 1.2 is generally available by default."); + + if (ctx.OsVersion.Major == 6 && ctx.OsVersion.Minor >= 3) + return Info("OS Version", "Windows " + hint + " (" + ctx.OsVersion + "). TLS 1.2 exists, but legacy WinHTTP/.NET policy may require updates/registry."); + + return Warn("OS Version", "Windows " + hint + " (" + ctx.OsVersion + "). TLS 1.2 readiness is uncertain on older OS."); + } + + private static Item CheckProcessBitness(Context ctx) + { + return Info("Process Bitness", ctx.Is64BitProcess ? "64-bit process" : "32-bit process"); + } + + private static Item CheckTls12EnumAvailable(Context ctx) + { + try + { + var _ = SecurityProtocolType.Tls12; + return Pass("Runtime Enum: SecurityProtocolType.Tls12", "Available."); + } + catch + { + return Fail("Runtime Enum: SecurityProtocolType.Tls12", "Not available. Runtime is likely too old."); + } + } + + private static Item CheckSslProtocolsTls12Available(Context ctx) + { + try + { + var _ = SslProtocols.Tls12; + return Pass("Runtime Enum: SslProtocols.Tls12", "Available."); + } + catch + { + return Fail("Runtime Enum: SslProtocols.Tls12", "Not available. Runtime is likely too old."); + } + } + + // ---------------------------- + // Checks: DLL presence + // ---------------------------- + + private static Item CheckSystemDllPresence(Context ctx, string dll) + { + string path = ctx.SystemDir + "\\" + dll; + bool exists; + try { exists = System.IO.File.Exists(path); } + catch { exists = false; } + + if (exists) return Pass("System DLL present: " + dll, path); + return Fail("System DLL present: " + dll, "Missing: " + path); + } + + // ---------------------------- + // Checks: Schannel protocol policy + // ---------------------------- + + private static Item CheckSchannelTls12KeyPresence(Context ctx) + { + const string basePath = @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2"; + bool clientExists = RegistryKeyExists(Registry.LocalMachine, basePath + @"\Client"); + bool serverExists = RegistryKeyExists(Registry.LocalMachine, basePath + @"\Server"); + + if (clientExists || serverExists) + return Info("Schannel TLS 1.2 keys present", "Client=" + clientExists + ", Server=" + serverExists + " (absence is normal on modern Windows)."); + + return Info("Schannel TLS 1.2 keys present", "No explicit TLS 1.2 keys found (common on modern Windows; defaults may still enable TLS 1.2)."); + } + + private static Item CheckSchannelTls12ClientPolicy(Context ctx) + { + const string k = @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client"; + var enabled = ReadDwordHKLM(k, "Enabled"); + var disabledByDefault = ReadDwordHKLM(k, "DisabledByDefault"); + + if (enabled == null && disabledByDefault == null) + return Info("Schannel TLS 1.2 Client policy", "No explicit policy (common on modern Windows)."); + + if (enabled == 0 || disabledByDefault == 1) + return Fail("Schannel TLS 1.2 Client policy", "TLS 1.2 Client appears disabled (Enabled=0 or DisabledByDefault=1)."); + + if (enabled == 1 && (disabledByDefault == null || disabledByDefault == 0)) + return Pass("Schannel TLS 1.2 Client policy", "Enabled=1 and DisabledByDefault=0 (or missing)."); + + return Warn("Schannel TLS 1.2 Client policy", "Ambiguous: Enabled=" + ToS(enabled) + ", DisabledByDefault=" + ToS(disabledByDefault) + "."); + } + + private static Item CheckSchannelTls12ServerPolicy(Context ctx) + { + const string k = @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server"; + var enabled = ReadDwordHKLM(k, "Enabled"); + var disabledByDefault = ReadDwordHKLM(k, "DisabledByDefault"); + + if (enabled == null && disabledByDefault == null) + return Info("Schannel TLS 1.2 Server policy", "No explicit policy (common on modern Windows)."); + + if (enabled == 0 || disabledByDefault == 1) + return Warn("Schannel TLS 1.2 Server policy", "TLS 1.2 Server appears disabled (may be intentional if not a server)."); + + if (enabled == 1 && (disabledByDefault == null || disabledByDefault == 0)) + return Pass("Schannel TLS 1.2 Server policy", "Enabled=1 and DisabledByDefault=0 (or missing)."); + + return Warn("Schannel TLS 1.2 Server policy", "Ambiguous: Enabled=" + ToS(enabled) + ", DisabledByDefault=" + ToS(disabledByDefault) + "."); + } + + private static Item CheckLegacyProtocolKeyHints(Context ctx) + { + bool tls12ClientKey = RegistryKeyExists(Registry.LocalMachine, + @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client"); + + bool tls10ClientKey = RegistryKeyExists(Registry.LocalMachine, + @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client"); + bool tls11ClientKey = RegistryKeyExists(Registry.LocalMachine, + @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client"); + bool ssl3ClientKey = RegistryKeyExists(Registry.LocalMachine, + @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Client"); + + if (!tls12ClientKey && (tls10ClientKey || tls11ClientKey || ssl3ClientKey)) + return Warn("Legacy protocol policy hints", "Found SSL3/TLS1.0/TLS1.1 policy keys while TLS1.2 client key is absent. Review hardening baseline."); + + return Info("Legacy protocol policy hints", "No strong legacy-protocol policy hints detected (presence/absence is not definitive)."); + } + + // ---------------------------- + // Checks: WinHTTP defaults + // ---------------------------- + + private static Item CheckWinHttpDefaultSecureProtocols(Context ctx) + { + const string k64 = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"; + const string k32 = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"; + + var v64 = ReadDwordHKLM(k64, "DefaultSecureProtocols"); + var v32 = ReadDwordHKLM(k32, "DefaultSecureProtocols"); + + if (v64 == null && v32 == null) + return Info("WinHTTP DefaultSecureProtocols", "Not set (normal on modern Windows; may be needed on legacy OS like Win7/2012)."); + + return Info("WinHTTP DefaultSecureProtocols", "x64=" + ToHex(v64) + ", x86=" + ToHex(v32)); + } + + private static Item CheckWinHttpDefaultSecureProtocolsIncludesTls12(Context ctx) + { + const string k64 = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"; + const string k32 = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp"; + + var v64 = ReadDwordHKLM(k64, "DefaultSecureProtocols"); + var v32 = ReadDwordHKLM(k32, "DefaultSecureProtocols"); + + if (v64 == null && v32 == null) + return Info("WinHTTP DefaultSecureProtocols includes TLS 1.2", "DefaultSecureProtocols not present (unknown/default)."); + + const int TLS12_FLAG = 0x00000800; + bool ok64 = v64 != null && (v64.Value & TLS12_FLAG) == TLS12_FLAG; + bool ok32 = v32 != null && (v32.Value & TLS12_FLAG) == TLS12_FLAG; + + if (ok64 || ok32) + return Pass("WinHTTP DefaultSecureProtocols includes TLS 1.2", "Detected TLS 1.2 flag. x64=" + ToHex(v64) + ", x86=" + ToHex(v32)); + + return Warn("WinHTTP DefaultSecureProtocols includes TLS 1.2", "TLS 1.2 flag not detected. x64=" + ToHex(v64) + ", x86=" + ToHex(v32)); + } + + // ---------------------------- + // Checks: .NET Framework policy keys + // ---------------------------- + + private static Item CheckDotNetStrongCrypto(Context ctx) + { + const string k64 = @"SOFTWARE\Microsoft\.NETFramework\v4.0.30319"; + const string k32 = @"SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319"; + + var v64 = ReadDwordHKLM(k64, "SchUseStrongCrypto"); + var v32 = ReadDwordHKLM(k32, "SchUseStrongCrypto"); + + if (v64 == 1 || v32 == 1) + return Pass(".NET SchUseStrongCrypto", "Enabled. x64=" + ToS(v64) + ", x86=" + ToS(v32)); + + if (v64 == null && v32 == null) + return Info(".NET SchUseStrongCrypto", "Not explicitly set (common on modern OS/.NET)."); + + return Warn(".NET SchUseStrongCrypto", "Not set to 1. x64=" + ToS(v64) + ", x86=" + ToS(v32)); + } + + private static Item CheckDotNetSystemDefaultTlsVersions(Context ctx) + { + const string k64 = @"SOFTWARE\Microsoft\.NETFramework\v4.0.30319"; + const string k32 = @"SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v4.0.30319"; + + var v64 = ReadDwordHKLM(k64, "SystemDefaultTlsVersions"); + var v32 = ReadDwordHKLM(k32, "SystemDefaultTlsVersions"); + + if (v64 == 1 || v32 == 1) + return Pass(".NET SystemDefaultTlsVersions", "Enabled. x64=" + ToS(v64) + ", x86=" + ToS(v32)); + + if (v64 == null && v32 == null) + return Info(".NET SystemDefaultTlsVersions", "Not explicitly set (common on modern OS/.NET)."); + + return Warn(".NET SystemDefaultTlsVersions", "Not set to 1. x64=" + ToS(v64) + ", x86=" + ToS(v32)); + } + + private static Item CheckDotNetV2StrongCrypto(Context ctx) + { + // .NET 2.0/3.0/3.5 line uses v2.0.50727. + // On many systems, these values might not exist; treat as Info/Warn. + const string k64 = @"SOFTWARE\Microsoft\.NETFramework\v2.0.50727"; + const string k32 = @"SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v2.0.50727"; + + var v64 = ReadDwordHKLM(k64, "SchUseStrongCrypto"); + var v32 = ReadDwordHKLM(k32, "SchUseStrongCrypto"); + + if (v64 == 1 || v32 == 1) + return Pass(".NET(v2.0.50727) SchUseStrongCrypto", "Enabled. x64=" + ToS(v64) + ", x86=" + ToS(v32)); + + if (v64 == null && v32 == null) + { + // On many modern OS, this isn't set. For legacy .NET apps, warn (because default can be TLS 1.0). + return Info(".NET(v2.0.50727) SchUseStrongCrypto", "Not explicitly set (common). Legacy .NET 2.0/3.5 apps may still default to older protocols."); + } + + return Warn(".NET(v2.0.50727) SchUseStrongCrypto", "Not set to 1. x64=" + ToS(v64) + ", x86=" + ToS(v32)); + } + + private static Item CheckDotNetV2SystemDefaultTlsVersions(Context ctx) + { + // .NET 2.0/3.0/3.5 line uses v2.0.50727. + const string k64 = @"SOFTWARE\Microsoft\.NETFramework\v2.0.50727"; + const string k32 = @"SOFTWARE\Wow6432Node\Microsoft\.NETFramework\v2.0.50727"; + + var v64 = ReadDwordHKLM(k64, "SystemDefaultTlsVersions"); + var v32 = ReadDwordHKLM(k32, "SystemDefaultTlsVersions"); + + if (v64 == 1 || v32 == 1) + return Pass(".NET(v2.0.50727) SystemDefaultTlsVersions", "Enabled. x64=" + ToS(v64) + ", x86=" + ToS(v32)); + + if (v64 == null && v32 == null) + { + // Same rationale: not always present, but matters for legacy CLR apps. + return Info(".NET(v2.0.50727) SystemDefaultTlsVersions", "Not explicitly set (common). Legacy .NET 2.0/3.5 apps may not follow OS default TLS without updates/keys."); + } + + return Warn(".NET(v2.0.50727) SystemDefaultTlsVersions", "Not set to 1. x64=" + ToS(v64) + ", x86=" + ToS(v32)); + } + + // ---------------------------- + // Checks: Process-level (legacy) + // ---------------------------- + + private static Item CheckServicePointManagerProtocol(Context ctx) + { + try + { + var current = ServicePointManager.SecurityProtocol; + bool hasTls12 = (current & SecurityProtocolType.Tls12) == SecurityProtocolType.Tls12; + + if (hasTls12) + return Pass("ServicePointManager.SecurityProtocol includes TLS 1.2", current.ToString()); + + return Info("ServicePointManager.SecurityProtocol includes TLS 1.2", + current.ToString() + " (process-level; can be set at runtime for legacy stacks)."); + } + catch (Exception ex) + { + return Warn("ServicePointManager.SecurityProtocol includes TLS 1.2", + "Could not read: " + ex.GetType().Name + ": " + ex.Message); + } + } + + // ---------------------------- + // Checks: Cipher suite policy / crypto hardening + // ---------------------------- + + private static Item CheckCipherSuitePolicyKey(Context ctx) + { + const string k = @"SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL\00010002"; + bool exists = RegistryKeyExists(Registry.LocalMachine, k); + + if (!exists) + return Info("Cipher suite policy key", "No explicit policy key found (default cipher suite selection applies)."); + + return Info("Cipher suite policy key", "Policy key exists (cipher suites may be explicitly controlled)."); + } + + private static Item CheckCipherSuitePolicyFunctions(Context ctx) + { + const string k = @"SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL\00010002"; + if (!RegistryKeyExists(Registry.LocalMachine, k)) + return Info("Cipher suite policy Functions", "Policy key not present."); + + var funcs = ReadMultiStringHKLM(k, "Functions"); + if (funcs == null) + return Warn("Cipher suite policy Functions", "Functions value not readable or not set."); + if (funcs.Length == 0) + return Fail("Cipher suite policy Functions", "Functions list is empty. This can block TLS handshakes."); + return Info("Cipher suite policy Functions", "Functions count=" + funcs.Length); + } + + private static Item CheckSchannelHashesAndKeyExchangeExplicitDisables(Context ctx) + { + var checks = new (string key, string label)[] + { + (@"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Hashes\SHA", "SHA"), + (@"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Hashes\SHA256", "SHA256"), + (@"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Hashes\SHA384", "SHA384"), + (@"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\KeyExchangeAlgorithms\PKCS", "RSA (PKCS)"), + (@"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\KeyExchangeAlgorithms\ECDH", "ECDH"), + (@"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\KeyExchangeAlgorithms\Diffie-Hellman", "DH"), + }; + + var disabled = new List(); + foreach (var c in checks) + { + var enabled = ReadDwordHKLM(c.key, "Enabled"); + if (enabled == 0) disabled.Add(c.label); + } + + if (disabled.Count > 0) + return Warn("Schannel Hash/KeyExchange disables", "Explicitly disabled: " + string.Join(", ", disabled)); + + return Info("Schannel Hash/KeyExchange disables", "No explicit disables detected for common Hash/KeyExchange components."); + } + + private static Item CheckSchannelStrongCiphersExplicitDisableHeuristic(Context ctx) + { + var aesKeys = new[] + { + @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\AES 128/128", + @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers\AES 256/256", + }; + + var disabled = new List(); + foreach (var k in aesKeys) + { + var enabled = ReadDwordHKLM(k, "Enabled"); + if (enabled == 0) disabled.Add(k.Split('\\').Last()); + } + + if (disabled.Count > 0) + return Warn("Schannel AES ciphers explicitly disabled", "Disabled: " + string.Join(", ", disabled)); + + return Info("Schannel AES ciphers explicitly disabled", "No explicit AES cipher disable detected (absence is normal)."); + } + + // ---------------------------- + // Checks: FIPS policy + // ---------------------------- + + private static Item CheckFipsPolicy(Context ctx) + { + const string k = @"SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy"; + var enabled = ReadDwordHKLM(k, "Enabled"); + + if (enabled == 1) + return Warn("FIPS policy", "Enabled=1. Crypto/TLS behavior may change depending on libraries and cipher suite policies."); + if (enabled == 0) + return Info("FIPS policy", "Enabled=0."); + return Info("FIPS policy", "Not set (unknown/default)."); + } + + // ---------------------------- + // Checks: Proxy hints + // ---------------------------- + + private static Item CheckWinHttpProxyPresence(Context ctx) + { + const string k = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\Connections"; + var bytes = ReadBinaryHKLM(k, "WinHttpSettings"); + + if (bytes == null || bytes.Length == 0) + return Info("WinHTTP proxy presence", "WinHttpSettings not found (no evidence of WinHTTP proxy config)."); + + return Info("WinHTTP proxy presence", "WinHttpSettings exists (proxy may be configured; can affect certs/endpoints)."); + } + + private static Item CheckWinInetUserProxyPresence(Context ctx) + { + const string k = @"Software\Microsoft\Windows\CurrentVersion\Internet Settings"; + var proxyEnable = ReadDwordHKCU(k, "ProxyEnable"); + var proxyServer = ReadStringHKCU(k, "ProxyServer"); + + if (proxyEnable == 1 && !string.IsNullOrEmpty(proxyServer)) + return Info("WinINet user proxy", "ProxyEnable=1, ProxyServer=" + proxyServer); + + return Info("WinINet user proxy", "No obvious per-user proxy configuration detected."); + } + + // ---------------------------- + // Checks: Schannel event logging + // ---------------------------- + + private static Item CheckSchannelEventLogging(Context ctx) + { + const string k = @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL"; + var v = ReadDwordHKLM(k, "EventLogging"); + + if (v == null) + return Info("Schannel EventLogging", "Not set (default)."); + + return Info("Schannel EventLogging", "EventLogging=" + v + " (enables Schannel logs in Event Viewer)."); + } + + // ---------------------------- + // Report build + // ---------------------------- + + private static Report BuildReport(List items) + { + bool tls12ClientDisabled = items.Any(i => i.Id == "Schannel TLS 1.2 Client policy" && i.Level == Level.Fail); + bool missingCryptoDll = items.Any(i => i.Id.StartsWith("System DLL present: ", StringComparison.OrdinalIgnoreCase) && i.Level == Level.Fail); + bool cipherPolicyEmpty = items.Any(i => i.Id == "Cipher suite policy Functions" && i.Level == Level.Fail); + + bool anyWarn = items.Any(i => i.Level == Level.Warn); + + Readiness readiness; + if (tls12ClientDisabled || missingCryptoDll || cipherPolicyEmpty) + readiness = Readiness.NotReady; + else if (anyWarn) + readiness = Readiness.ProbablyReadyButRisky; + else + readiness = Readiness.ProbablyReady; + + var rec = BuildRecommendations(items, readiness); + return new Report(readiness, rec, items); + } + + private static string[] BuildRecommendations(List items, Readiness readiness) + { + var rec = new List(); + + bool tls12ClientDisabled = items.Any(i => i.Id == "Schannel TLS 1.2 Client policy" && i.Level == Level.Fail); + bool winhttpTls12FlagWarn = items.Any(i => i.Id == "WinHTTP DefaultSecureProtocols includes TLS 1.2" && i.Level == Level.Warn); + bool dotnetWarn = + items.Any(i => i.Id == ".NET SchUseStrongCrypto" && i.Level == Level.Warn) || + items.Any(i => i.Id == ".NET SystemDefaultTlsVersions" && i.Level == Level.Warn); + bool cipherPolicyEmpty = items.Any(i => i.Id == "Cipher suite policy Functions" && i.Level == Level.Fail); + bool aesDisabled = items.Any(i => i.Id == "Schannel AES ciphers explicitly disabled" && i.Level == Level.Warn); + bool fipsEnabled = items.Any(i => i.Id == "FIPS policy" && i.Level == Level.Warn); + + if (tls12ClientDisabled) + rec.Add("TLS 1.2 is explicitly disabled in Schannel Client policy. Enable it (Enabled=1, DisabledByDefault=0) or remove the disabling baseline."); + + if (cipherPolicyEmpty) + rec.Add("Cipher suite policy exists but Functions list is empty. Populate cipher suites or remove the policy; otherwise TLS handshakes will fail."); + + if (winhttpTls12FlagWarn) + rec.Add("WinHTTP DefaultSecureProtocols does not show TLS 1.2 flag. On legacy OS, apply KB3140245 guidance and include TLS 1.2 in DefaultSecureProtocols."); + + if (dotnetWarn) + rec.Add("For legacy .NET Framework apps, consider setting SchUseStrongCrypto=1 and SystemDefaultTlsVersions=1 under .NETFramework\\v4.0.30319 (both 64/32-bit)."); + + if (aesDisabled) + rec.Add("AES cipher(s) are explicitly disabled in Schannel. Modern TLS 1.2 endpoints typically require AES; re-enable unless you have a specific policy reason."); + + if (fipsEnabled) + rec.Add("FIPS policy is enabled. Ensure your cipher suite policy and TLS stacks remain compatible with FIPS constraints."); + + if (rec.Count == 0) + { + if (readiness == Readiness.ProbablyReady) + rec.Add("No offline red flags detected. TLS 1.2 is probably supported by OS/runtime policy."); + else + rec.Add("Some offline hints suggest risk. If failures occur, review Schannel policy, cipher suite policy, WinHTTP defaults, and .NET strong crypto settings."); + } + + return rec.ToArray(); + } + + // ---------------------------- + // Models + // ---------------------------- + + public enum Level { Pass, Info, Warn, Fail } + public enum Readiness { ProbablyReady, ProbablyReadyButRisky, NotReady } + + public sealed class Item + { + public string Id { get; } + public Level Level { get; } + public string Detail { get; } + + public Item(string id, Level level, string detail) + { + Id = id ?? ""; + Level = level; + Detail = detail ?? ""; + } + + public override string ToString() => "[" + Level + "] " + Id + " - " + Detail; + } + + public sealed class Report + { + public Readiness Readiness { get; } + public IReadOnlyList Recommendations { get; } + public IReadOnlyList Items { get; } + + public Report(Readiness readiness, IReadOnlyList recommendations, IReadOnlyList items) + { + Readiness = readiness; + Recommendations = recommendations ?? Array.Empty(); + Items = items ?? Array.Empty(); + } + + public string ToText() + { + var lines = new List(); + lines.Add("TLS 1.2 Offline Readiness: " + Readiness); + lines.Add(""); + + if (Recommendations.Count > 0) + { + lines.Add("Recommendations:"); + for (int i = 0; i < Recommendations.Count; i++) + lines.Add(" - " + Recommendations[i]); + lines.Add(""); + } + + lines.Add("Checks:"); + foreach (var item in Items) + lines.Add(" " + item.ToString()); + + return string.Join(Environment.NewLine, lines.ToArray()); + } + } + + // ---------------------------- + // Registry helpers + // ---------------------------- + + private static bool RegistryKeyExists(RegistryKey root, string subKeyPath) + { + try { using (var k = root.OpenSubKey(subKeyPath, false)) return k != null; } + catch { return false; } + } + + private static int? ReadDwordHKLM(string subKeyPath, string valueName) + { + try + { + using (var key = Registry.LocalMachine.OpenSubKey(subKeyPath, false)) + { + if (key == null) return null; + object v = key.GetValue(valueName, null); + if (v == null) return null; + if (v is int i) return i; + if (v is byte[] b && b.Length >= 4) return BitConverter.ToInt32(b, 0); + return null; + } + } + catch { return null; } + } + + private static string[] ReadMultiStringHKLM(string subKeyPath, string valueName) + { + try + { + using (var key = Registry.LocalMachine.OpenSubKey(subKeyPath, false)) + { + if (key == null) return null; + return key.GetValue(valueName, null) as string[]; + } + } + catch { return null; } + } + + private static byte[] ReadBinaryHKLM(string subKeyPath, string valueName) + { + try + { + using (var key = Registry.LocalMachine.OpenSubKey(subKeyPath, false)) + { + if (key == null) return null; + return key.GetValue(valueName, null) as byte[]; + } + } + catch { return null; } + } + + private static int? ReadDwordHKCU(string subKeyPath, string valueName) + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(subKeyPath, false)) + { + if (key == null) return null; + object v = key.GetValue(valueName, null); + if (v == null) return null; + if (v is int i) return i; + if (v is byte[] b && b.Length >= 4) return BitConverter.ToInt32(b, 0); + return null; + } + } + catch { return null; } + } + + private static string ReadStringHKCU(string subKeyPath, string valueName) + { + try + { + using (var key = Registry.CurrentUser.OpenSubKey(subKeyPath, false)) + { + if (key == null) return null; + return key.GetValue(valueName, null) as string; + } + } + catch { return null; } + } + + // ---------------------------- + // Formatting helpers + // ---------------------------- + + private static string ToS(int? v) => v.HasValue ? v.Value.ToString() : "null"; + private static string ToHex(int? v) => v.HasValue ? ("0x" + v.Value.ToString("X")) : "null"; + + private static string GetWindowsNameHint(Version v) + { + if (v.Major >= 10) return "10/11+"; + if (v.Major == 6 && v.Minor == 3) return "8.1/2012 R2"; + if (v.Major == 6 && v.Minor == 2) return "8/2012"; + if (v.Major == 6 && v.Minor == 1) return "7/2008 R2"; + if (v.Major == 6 && v.Minor == 0) return "Vista/2008"; + return "Unknown"; + } + + // ---------------------------- + // Optional: Admin check (extension point) + // ---------------------------- + + public static bool IsAdministrator() + { + try + { + using (var identity = WindowsIdentity.GetCurrent()) + { + var principal = new WindowsPrincipal(identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } + catch + { + return false; + } + } + } +} \ No newline at end of file diff --git a/WelsonJS.Augmented/WelsonJS.Augmented.sln b/WelsonJS.Augmented/WelsonJS.Augmented.sln index 74d1463..ef50abd 100644 --- a/WelsonJS.Augmented/WelsonJS.Augmented.sln +++ b/WelsonJS.Augmented/WelsonJS.Augmented.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34322.80 +# Visual Studio Version 18 +VisualStudioVersion = 18.1.11312.151 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WelsonJS.Toolkit", "WelsonJS.Toolkit\WelsonJS.Toolkit.csproj", "{D6007282-B4F7-4694-AC67-BB838D91B77A}" EndProject @@ -19,6 +19,8 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "WelsonJS.Cryptography.Test" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Catswords.Phantomizer", "Catswords.Phantomizer\Catswords.Phantomizer.csproj", "{59C67003-C14E-4703-927F-318BFF15F903}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Catswords.TlsReport", "Catswords.TlsReport\Catswords.TlsReport.csproj", "{05EE55FD-76C6-482E-9886-72EB5BD3C621}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -91,6 +93,14 @@ Global {59C67003-C14E-4703-927F-318BFF15F903}.Release|Any CPU.Build.0 = Release|Any CPU {59C67003-C14E-4703-927F-318BFF15F903}.Release|x86.ActiveCfg = Release|Any CPU {59C67003-C14E-4703-927F-318BFF15F903}.Release|x86.Build.0 = Release|Any CPU + {05EE55FD-76C6-482E-9886-72EB5BD3C621}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05EE55FD-76C6-482E-9886-72EB5BD3C621}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05EE55FD-76C6-482E-9886-72EB5BD3C621}.Debug|x86.ActiveCfg = Debug|Any CPU + {05EE55FD-76C6-482E-9886-72EB5BD3C621}.Debug|x86.Build.0 = Debug|Any CPU + {05EE55FD-76C6-482E-9886-72EB5BD3C621}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05EE55FD-76C6-482E-9886-72EB5BD3C621}.Release|Any CPU.Build.0 = Release|Any CPU + {05EE55FD-76C6-482E-9886-72EB5BD3C621}.Release|x86.ActiveCfg = Release|Any CPU + {05EE55FD-76C6-482E-9886-72EB5BD3C621}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE