From 3f44486d33a69a2e69f0c32d3714ca392f1bf457 Mon Sep 17 00:00:00 2001 From: Dmitrii Korolev Date: Thu, 27 Mar 2025 14:40:50 +0100 Subject: [PATCH] try with httpsys api --- scenarios/tls.benchmarks.yml | 11 --- .../TLS/HttpSys/NetSh/SslCertBinding.cs | 7 +- .../TLS/HttpSys/NetShWrapper.cs | 86 ++++++++++++------- src/BenchmarksApps/TLS/HttpSys/Program.cs | 25 +++--- 4 files changed, 73 insertions(+), 56 deletions(-) diff --git a/scenarios/tls.benchmarks.yml b/scenarios/tls.benchmarks.yml index fd477d45b..8e7f50e20 100644 --- a/scenarios/tls.benchmarks.yml +++ b/scenarios/tls.benchmarks.yml @@ -7,11 +7,6 @@ variables: serverPort: 5000 - # these scripts allow to disable (or rollback changes) to the SChannel registry - # this allows to disable TLS resumption on windows level - disableTlsResumptionScript: powershell -Command "New-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\' -Name MaximumCacheSize -PropertyType DWord -Value 0 -ErrorAction Ignore; New-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\' -Name ServerCacheTime -PropertyType DWord -Value 0 -ErrorAction Ignore; Restart-Service -Name Http -Force;" - rollbackTlsResumptionScript: powershell -Command "Remove-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL' -Name MaximumCacheSize -ErrorAction Ignore; Remove-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL' -Name ServerCacheTime -ErrorAction Ignore; Restart-Service -Name Http -Force;" - jobs: httpSysServer: source: @@ -70,8 +65,6 @@ scenarios: tls-handshakes-httpsys: application: job: httpSysServer - beforeScript: "{{disableTlsResumptionScript}}" - afterScript: "{{rollbackTlsResumptionScript}}" load: job: httpclient variables: @@ -84,8 +77,6 @@ scenarios: mTls-handshakes-httpsys: application: job: httpSysServer - beforeScript: "{{disableTlsResumptionScript}}" - afterScript: "{{rollbackTlsResumptionScript}}" variables: mTLS: true # enables settings on http.sys to negotiate client cert on connections tlsRenegotiation: true # enables client cert validation @@ -106,8 +97,6 @@ scenarios: tls-renegotiation-httpsys: application: job: httpSysServer - beforeScript: "{{disableTlsResumptionScript}}" - afterScript: "{{rollbackTlsResumptionScript}}" variables: mTLS: false tlsRenegotiation: true diff --git a/src/BenchmarksApps/TLS/HttpSys/NetSh/SslCertBinding.cs b/src/BenchmarksApps/TLS/HttpSys/NetSh/SslCertBinding.cs index eaff90e27..c19d35822 100644 --- a/src/BenchmarksApps/TLS/HttpSys/NetSh/SslCertBinding.cs +++ b/src/BenchmarksApps/TLS/HttpSys/NetSh/SslCertBinding.cs @@ -24,11 +24,12 @@ public override string ToString() => $""" """; } + [Flags] public enum NetShFlag { - NotSet = 0, + NotSet = 0, - Disabled = 1, - Enable = 2 + Disabled = 1, + Enable = 2, } } diff --git a/src/BenchmarksApps/TLS/HttpSys/NetShWrapper.cs b/src/BenchmarksApps/TLS/HttpSys/NetShWrapper.cs index 1cfee48d1..99b0ddf71 100644 --- a/src/BenchmarksApps/TLS/HttpSys/NetShWrapper.cs +++ b/src/BenchmarksApps/TLS/HttpSys/NetShWrapper.cs @@ -5,9 +5,37 @@ namespace HttpSys { - public static class NetShWrapper + public class NetShWrapper { - public static void DeleteBindingIfExists(string ipPort) + public bool SupportsDisableSessionId { get; } + public bool SupportsEnableSessionTicket { get; } + + public NetShWrapper() + { + var sslCertCapabilitiesText = ExecuteNetShCommand($"http add sslcert help"); + if (string.IsNullOrEmpty(sslCertCapabilitiesText)) + { + throw new InvalidOperationException("Failed to determine http.sys capabilities"); + } + + if (sslCertCapabilitiesText.Contains("disablesessionid")) + { + SupportsDisableSessionId = true; + } + + if (sslCertCapabilitiesText.Contains("enablesessionticket")) + { + SupportsEnableSessionTicket = true; + } + + Console.WriteLine($""" + Http.SYS Capabilities: + - SupportsDisableSessionId: {SupportsDisableSessionId} (if not supported, renegotiation will most likely be enabled by default) + - SupportsEnableSessionTicket: {SupportsEnableSessionTicket} + """); + } + + public void DeleteBindingIfExists(string ipPort) { try { @@ -19,7 +47,7 @@ public static void DeleteBindingIfExists(string ipPort) } } - public static void DeleteBinding(string ipPort) + public void DeleteBinding(string ipPort) { Console.WriteLine("Disabling mTLS for http.sys"); @@ -29,9 +57,7 @@ public static void DeleteBinding(string ipPort) Console.WriteLine("Disabled http.sys settings for mTLS"); } - - - public static bool TryGetSslCertBinding(string ipPort, out SslCertBinding result) + public bool TryGetSslCertBinding(string ipPort, out SslCertBinding result) { result = new SslCertBinding(); @@ -71,7 +97,7 @@ public static bool TryGetSslCertBinding(string ipPort, out SslCertBinding result Max Settings Per Frame : 2796202 Max Settings Per Minute : 4294967295 */ - var bindings = ExecuteNetShCommand($"http show sslcert ipport={ipPort}"); + var bindings = ExecuteNetShCommand($"http show sslcert ipport={ipPort}", ignoreErrorExit: true); if (string.IsNullOrEmpty(bindings) || !bindings.Contains(ipPort)) { return false; @@ -123,12 +149,12 @@ public static bool TryGetSslCertBinding(string ipPort, out SslCertBinding result }; } - public static void LogSslCertBinding(string ipPort) + public void LogSslCertBinding(string ipPort) { ExecuteNetShCommand($"http show sslcert ipport={ipPort}", alwaysLogOutput: true); } - public static void SetTestCertBinding(string ipPort, bool enableClientCertNegotiation) + public void SetTestCertBinding(string ipPort, bool enableClientCertNegotiation) { Console.WriteLine("Setting up binding for testCert for http.sys"); @@ -148,7 +174,7 @@ public static void SetTestCertBinding(string ipPort, bool enableClientCertNegoti Console.WriteLine("Configured binding for testCert for http.sys"); } - public static bool TrySelfSignCertificate(string ipPort, out string certThumbprint) + public bool TrySelfSignCertificate(string ipPort, out string certThumbprint) { certThumbprint = string.Empty; try @@ -175,7 +201,7 @@ public static bool TrySelfSignCertificate(string ipPort, out string certThumbpri } } - public static void AddCertBinding( + public void AddCertBinding( string ipPort, string certThumbprint, string? appId = null, NetShFlag clientCertNegotiation = NetShFlag.Disabled, @@ -183,7 +209,7 @@ public static void AddCertBinding( NetShFlag enablesessionticket = NetShFlag.Disabled) => CertBindingCore("add", ipPort, certThumbprint, appId, clientCertNegotiation, disablesessionid, enablesessionticket); - public static void UpdateCertBinding(string ipPort, SslCertBinding binding) => UpdateCertBinding( + public void UpdateCertBinding(string ipPort, SslCertBinding binding) => UpdateCertBinding( ipPort, binding.CertificateThumbprint, binding.ApplicationId, @@ -191,7 +217,7 @@ public static void UpdateCertBinding(string ipPort, SslCertBinding binding) => U binding.DisableSessionIdTlsResumption, binding.EnableSessionTicketTlsResumption); - public static void UpdateCertBinding( + public void UpdateCertBinding( string ipPort, string certThumbprint, string? appId = null, NetShFlag clientCertNegotiation = NetShFlag.Disabled, @@ -199,7 +225,7 @@ public static void UpdateCertBinding( NetShFlag enablesessionticket = NetShFlag.Disabled) => CertBindingCore("update", ipPort, certThumbprint, appId, clientCertNegotiation, disablesessionid, enablesessionticket); - private static void CertBindingCore( + private void CertBindingCore( string httpOperation, string ipPort, string certThumbprint, string? appId = null, @@ -224,17 +250,15 @@ private static void CertBindingCore( // below options are supported only in later versions of HTTP.SYS // you can identify if it is available by running `netsh http add sslcert help` - // --- - // workaround is to control SChannel settings via registry - - //if (disablesessionidFlag != null) - //{ - // command += $" disablesessionid={disablesessionidFlag}"; - //} - //if (enablesessionticketFlag != null) - //{ - // command += $" enablesessionticket={enablesessionticketFlag}"; - //} + + if (SupportsDisableSessionId && disablesessionidFlag != null) + { + command += $" disablesessionid={disablesessionidFlag}"; + } + if (SupportsEnableSessionTicket && enablesessionticketFlag != null) + { + command += $" enablesessionticket={enablesessionticketFlag}"; + } ExecuteNetShCommand(command, alwaysLogOutput: true); Console.WriteLine($"Performed cert binding for {ipPort}"); @@ -248,13 +272,13 @@ private static void CertBindingCore( }; } - private static string ExecutePowershellCommand(string command, bool alwaysLogOutput = false) - => ExecuteCommand("powershell.exe", command, alwaysLogOutput); + private static string ExecutePowershellCommand(string command, bool ignoreErrorExit = false, bool alwaysLogOutput = false) + => ExecuteCommand("powershell.exe", command, ignoreErrorExit, alwaysLogOutput); - private static string ExecuteNetShCommand(string command, bool alwaysLogOutput = false) - => ExecuteCommand("netsh", command, alwaysLogOutput); + private static string ExecuteNetShCommand(string command, bool ignoreErrorExit = false, bool alwaysLogOutput = false) + => ExecuteCommand("netsh", command, ignoreErrorExit, alwaysLogOutput); - private static string ExecuteCommand(string fileName, string command, bool logOutput = false) + private static string ExecuteCommand(string fileName, string command, bool ignoreErrorExit = false, bool logOutput = false) { ProcessStartInfo processInfo = new ProcessStartInfo(fileName, command) { @@ -274,7 +298,7 @@ private static string ExecuteCommand(string fileName, string command, bool logOu Console.WriteLine(output); } - if (process.ExitCode != 0) + if (!ignoreErrorExit && process.ExitCode != 0) { throw new InvalidOperationException($"{fileName} command execution failure: {output}"); } diff --git a/src/BenchmarksApps/TLS/HttpSys/Program.cs b/src/BenchmarksApps/TLS/HttpSys/Program.cs index ac2399ba4..febb261f2 100644 --- a/src/BenchmarksApps/TLS/HttpSys/Program.cs +++ b/src/BenchmarksApps/TLS/HttpSys/Program.cs @@ -20,15 +20,17 @@ var mTLSNetShFlag = mTlsEnabled ? NetShFlag.Enable : NetShFlag.Disabled; +var netshWrapper = new NetShWrapper(); + // verify there is an netsh http sslcert binding for specified ip:port -if (!NetShWrapper.TryGetSslCertBinding(httpsIpPort, out var sslCertBinding)) +if (!netshWrapper.TryGetSslCertBinding(httpsIpPort, out var sslCertBinding)) { Console.WriteLine($"No binding existed. Need to self-sign it and bind to '{httpsIpPort}'"); - if (!NetShWrapper.TrySelfSignCertificate(httpsIpPort, out var originalCertThumbprint)) + if (!netshWrapper.TrySelfSignCertificate(httpsIpPort, out var originalCertThumbprint)) { throw new ApplicationException($"Failed to setup ssl binding for '{httpsIpPort}'. Please unblock the VM."); } - NetShWrapper.AddCertBinding( + netshWrapper.AddCertBinding( httpsIpPort, originalCertThumbprint, disablesessionid: NetShFlag.Enable, @@ -36,17 +38,18 @@ clientCertNegotiation: mTLSNetShFlag); } -Console.WriteLine("Current netsh ssl certificate binding: " + sslCertBinding); +Console.WriteLine("Current netsh ssl certificate binding: \n" + sslCertBinding); if ( // those flags can be set only on later versions of HTTP.SYS; so only considering mTLS here - // sslCertBinding.DisableSessionIdTlsResumption != NetShFlag.Enable || sslCertBinding.EnableSessionTicketTlsResumption != NetShFlag.Disabled || - sslCertBinding.NegotiateClientCertificate != mTLSNetShFlag) + (netshWrapper.SupportsDisableSessionId && sslCertBinding.DisableSessionIdTlsResumption != NetShFlag.Enable) + || (netshWrapper.SupportsEnableSessionTicket && (sslCertBinding.EnableSessionTicketTlsResumption == NetShFlag.Enable)) + || sslCertBinding.NegotiateClientCertificate != mTLSNetShFlag) { Console.WriteLine($"Need to prepare ssl-cert binding for the run."); - Console.WriteLine($"Expected configuration: mTLS={mTLSNetShFlag}"); + Console.WriteLine($"Expected configuration: mTLS={mTLSNetShFlag}; disableSessionId={NetShFlag.Enable}; enableSessionTicket={NetShFlag.Disabled}"); - NetShWrapper.UpdateCertBinding( + netshWrapper.UpdateCertBinding( httpsIpPort, sslCertBinding.CertificateThumbprint, appId: sslCertBinding.ApplicationId, @@ -140,7 +143,7 @@ await app.StartAsync(); -NetShWrapper.LogSslCertBinding(httpsIpPort); +netshWrapper.LogSslCertBinding(httpsIpPort); Console.WriteLine("Application Info:"); if (mTlsEnabled) @@ -162,11 +165,11 @@ await app.WaitForShutdownAsync(); Console.WriteLine("Application stopped."); -if (NetShWrapper.TryGetSslCertBinding(httpsIpPort, out sslCertBinding) && mTLSNetShFlag == NetShFlag.Enable) +if (netshWrapper.TryGetSslCertBinding(httpsIpPort, out sslCertBinding) && mTLSNetShFlag == NetShFlag.Enable) { // update the sslCert binding to disable "negotiate client cert" (aka mTLS) to not break other tests. Console.WriteLine($"Rolling back mTLS setting for sslCert binding at '{httpsIpPort}'"); sslCertBinding.NegotiateClientCertificate = NetShFlag.Disabled; - NetShWrapper.UpdateCertBinding(httpsIpPort, sslCertBinding); + netshWrapper.UpdateCertBinding(httpsIpPort, sslCertBinding); } \ No newline at end of file