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.
This commit is contained in:
Namhyeon, Go 2025-12-23 00:58:38 +09:00
parent 782d3a34a5
commit 78e5de796e
6 changed files with 878 additions and 2 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{05EE55FD-76C6-482E-9886-72EB5BD3C621}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Catswords.TlsReport</RootNamespace>
<AssemblyName>Catswords.TlsReport</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tls12OfflineInspector.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -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();
}
}
}

View File

@ -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")]

View File

@ -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<Item>();
// 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<string>();
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<string>();
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<Item> 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<Item> items, Readiness readiness)
{
var rec = new List<string>();
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<string> Recommendations { get; }
public IReadOnlyList<Item> Items { get; }
public Report(Readiness readiness, IReadOnlyList<string> recommendations, IReadOnlyList<Item> items)
{
Readiness = readiness;
Recommendations = recommendations ?? Array.Empty<string>();
Items = items ?? Array.Empty<Item>();
}
public string ToText()
{
var lines = new List<string>();
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;
}
}
}
}

View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 18
VisualStudioVersion = 17.8.34322.80 VisualStudioVersion = 18.1.11312.151 d18.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WelsonJS.Toolkit", "WelsonJS.Toolkit\WelsonJS.Toolkit.csproj", "{D6007282-B4F7-4694-AC67-BB838D91B77A}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WelsonJS.Toolkit", "WelsonJS.Toolkit\WelsonJS.Toolkit.csproj", "{D6007282-B4F7-4694-AC67-BB838D91B77A}"
EndProject EndProject
@ -19,6 +19,8 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "WelsonJS.Cryptography.Test"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Catswords.Phantomizer", "Catswords.Phantomizer\Catswords.Phantomizer.csproj", "{59C67003-C14E-4703-927F-318BFF15F903}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Catswords.Phantomizer", "Catswords.Phantomizer\Catswords.Phantomizer.csproj", "{59C67003-C14E-4703-927F-318BFF15F903}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Catswords.TlsReport", "Catswords.TlsReport\Catswords.TlsReport.csproj", "{05EE55FD-76C6-482E-9886-72EB5BD3C621}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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|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.ActiveCfg = Release|Any CPU
{59C67003-C14E-4703-927F-318BFF15F903}.Release|x86.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE