mirror of
https://github.com/gnh1201/welsonjs.git
synced 2026-04-18 18:18:42 +00:00
759 lines
33 KiB
C#
759 lines
33 KiB
C#
// 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 Microsoft.Win32;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Security.Authentication;
|
|
using System.Security.Principal;
|
|
|
|
namespace Catswords.TlsReport
|
|
{
|
|
public static class Tls12OfflineInspector
|
|
{
|
|
// ----------------------------
|
|
// Public entry point
|
|
// ----------------------------
|
|
|
|
public static Report Evaluate()
|
|
{
|
|
var ctx = new Context();
|
|
var items = new List<Item>();
|
|
if (!IsAdministrator())
|
|
items.Add(Warn("Administrator privileges", "Not running as Administrator; some registry checks may not be accessible."));
|
|
|
|
// 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 = Path.Combine(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 Fail("Cipher suite policy Functions", "Policy key exists, but Functions value is missing, unreadable, or not REG_MULTI_SZ. This can block TLS handshakes.");
|
|
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 long l) return (int)l; // Handle 64-bit to 32-bit conversion
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
} |