diff --git a/scenarios/tls.benchmarks.yml b/scenarios/tls.benchmarks.yml index 276976b14..26d605c9b 100644 --- a/scenarios/tls.benchmarks.yml +++ b/scenarios/tls.benchmarks.yml @@ -15,12 +15,17 @@ jobs: project: src/BenchmarksApps/TLS/HttpSys/HttpSys.csproj readyStateText: Application started. variables: + # behavioral settings mTLS: false # enables settings on http.sys to negotiate client cert on connections tlsRenegotiation: false # enables client cert validation + tlsProtocols: "tls12" + # debug purpose settings certValidationConsoleEnabled: false httpSysLogs: false + tlsRegistryLogs: false statsEnabled: false - arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}} --httpSysLogs {{httpSysLogs}}" + logRequestInfo: false + arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}} --httpSysLogs {{httpSysLogs}} --tlsProtocols {{tlsProtocols}} --logRequestInfo {{logRequestInfo}} --tlsRegistryLogs {{tlsRegistryLogs}}" kestrelServer: source: @@ -29,11 +34,14 @@ jobs: project: src/BenchmarksApps/TLS/Kestrel/Kestrel.csproj readyStateText: Application started. variables: + # behavioral settings mTLS: false tlsRenegotiation: false + tlsProtocols: "tls12" + # debug purpose settings certValidationConsoleEnabled: false statsEnabled: false - arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}}" + arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}} --tlsProtocols {{tlsProtocols}}" scenarios: @@ -43,7 +51,7 @@ scenarios: application: job: httpSysServer load: - job: wrk + job: httpclient variables: path: /hello-world presetHeaders: connectionclose @@ -94,7 +102,7 @@ scenarios: application: job: kestrelServer load: - job: wrk + job: httpclient variables: path: /hello-world presetHeaders: connectionclose diff --git a/src/BenchmarksApps/TLS/HttpSys/ConfigurationHelpers.cs b/src/BenchmarksApps/TLS/HttpSys/ConfigurationHelpers.cs new file mode 100644 index 000000000..395958397 --- /dev/null +++ b/src/BenchmarksApps/TLS/HttpSys/ConfigurationHelpers.cs @@ -0,0 +1,40 @@ +using System.Security.Authentication; + +namespace HttpSys +{ + internal static class ConfigurationHelpers + { + public static SslProtocols ParseSslProtocols(string? supportedTlsVersions) + { + var protocols = SslProtocols.None; + if (string.IsNullOrEmpty(supportedTlsVersions)) + { + return protocols; + } + + foreach (var version in supportedTlsVersions.Split(',')) + { + switch (version.Trim().ToLower()) + { +#pragma warning disable SYSLIB0039 // Type or member is obsolete + case "tls11": + protocols |= SslProtocols.Tls11; + break; +#pragma warning restore SYSLIB0039 // Type or member is obsolete + case "tls12": + protocols |= SslProtocols.Tls12; + break; + case "tls13": + protocols |= SslProtocols.Tls13; + break; + case "any": + return SslProtocols.None; + default: + throw new ArgumentException($"Unsupported TLS version: {version}"); + } + } + + return protocols; + } + } +} diff --git a/src/BenchmarksApps/TLS/HttpSys/HttpSys.csproj b/src/BenchmarksApps/TLS/HttpSys/HttpSys.csproj index ffda0c4f9..be4a79951 100644 --- a/src/BenchmarksApps/TLS/HttpSys/HttpSys.csproj +++ b/src/BenchmarksApps/TLS/HttpSys/HttpSys.csproj @@ -1,4 +1,4 @@ - + net9.0 diff --git a/src/BenchmarksApps/TLS/HttpSys/Program.cs b/src/BenchmarksApps/TLS/HttpSys/Program.cs index 2ab338cb1..23708f5fb 100644 --- a/src/BenchmarksApps/TLS/HttpSys/Program.cs +++ b/src/BenchmarksApps/TLS/HttpSys/Program.cs @@ -1,12 +1,17 @@ using HttpSys; +using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Server.HttpSys; var builder = WebApplication.CreateBuilder(args); builder.Logging.ClearProviders(); var writeCertValidationEventsToConsole = bool.TryParse(builder.Configuration["certValidationConsoleEnabled"], out var certValidationConsoleEnabled) && certValidationConsoleEnabled; -var httpSysLoggingEnabled = bool.TryParse(builder.Configuration["httpSysLogs"], out var httpSysLogsEnabled) && httpSysLogsEnabled; +var httpSysLogsEnabled = bool.TryParse(builder.Configuration["httpSysLogs"], out var httpSysLogsConfig) && httpSysLogsConfig; +var tlsRegistryLogsEnabled = bool.TryParse(builder.Configuration["tlsRegistryLogs"], out var tlsRegistryLogsConfig) && tlsRegistryLogsConfig; +var logRequestInfo = bool.TryParse(builder.Configuration["logRequestInfo"], out var logRequestInfoConfig) && logRequestInfoConfig; var statsEnabled = bool.TryParse(builder.Configuration["statsEnabled"], out var connectionStatsEnabledConfig) && connectionStatsEnabledConfig; +var supportedTlsVersions = ConfigurationHelpers.ParseSslProtocols(builder.Configuration["tlsProtocols"]); + var mTlsEnabled = bool.TryParse(builder.Configuration["mTLS"], out var mTlsEnabledConfig) && mTlsEnabledConfig; var tlsRenegotiationEnabled = bool.TryParse(builder.Configuration["tlsRenegotiation"], out var tlsRenegotiationEnabledConfig) && tlsRenegotiationEnabledConfig; var listeningEndpoints = builder.Configuration["urls"] ?? "https://localhost:5000/"; @@ -76,6 +81,25 @@ void OnShutdown() } } +if (logRequestInfo) +{ + Console.WriteLine("Registered request logging middleware"); + var logged = false; + + app.Use(async (context, next) => + { + if (!logged) + { + logged = true; + Console.WriteLine("[RequestInfo]"); + Console.WriteLine("TLS Protocol: " + context.Features.Get()?.Protocol); + Console.WriteLine("---"); + } + + await next(); + }); +} + if (tlsRenegotiationEnabled) { // this is an http.sys middleware to get a cert @@ -108,9 +132,12 @@ void OnShutdown() }); } -await app.StartAsync(); - -if (httpSysLoggingEnabled) +RegistryController.EnableTls(supportedTlsVersions); +if (tlsRegistryLogsEnabled) +{ + RegistryController.ShowRegistryKeys(); +} +if (httpSysLogsEnabled) { NetShWrapper.Show(); } @@ -131,5 +158,7 @@ void OnShutdown() Console.WriteLine($"\tlistening endpoints: {listeningEndpoints}"); Console.WriteLine("--------------------------------"); +await app.StartAsync(); + Console.WriteLine("Application started."); await app.WaitForShutdownAsync(); \ No newline at end of file diff --git a/src/BenchmarksApps/TLS/HttpSys/RegistryController.cs b/src/BenchmarksApps/TLS/HttpSys/RegistryController.cs new file mode 100644 index 000000000..08d4ce361 --- /dev/null +++ b/src/BenchmarksApps/TLS/HttpSys/RegistryController.cs @@ -0,0 +1,140 @@ +using System.Security.Authentication; +using System.Text; +using Microsoft.Win32; + +namespace HttpSys; + +[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "benchmark only runs on windows")] +public static class RegistryController +{ + // see https://learn.microsoft.com/en-us/windows-server/security/tls/tls-registry-settings?tabs=diffie-hellman + private const string TLS12SubKey = @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server"; + private const string TLS13SubKey = @"SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.3\Server"; + + private static RegistryKey RootRegistryKey => Environment.Is64BitOperatingSystem + ? RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) + : RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); + + public static void ShowRegistryKeys() + { + var tls12DisabledByDefault = GetRegistryValue(TLS12SubKey, "DisabledByDefault"); + var tls12Enabled = GetRegistryValue(TLS12SubKey, "Enabled"); + + var tls13DisabledByDefault = GetRegistryValue(TLS13SubKey, "DisabledByDefault"); + var tls13Enabled = GetRegistryValue(TLS13SubKey, "Enabled"); + + var strBuilder = new StringBuilder("Registry TLS settings: \n"); + strBuilder.AppendLine($"\tTLS 1.2: DisabledByDefault='{tls12DisabledByDefault}', Enabled='{tls12Enabled}'"); + strBuilder.AppendLine($"\tTLS 1.3: DisabledByDefault='{tls13DisabledByDefault}', Enabled='{tls13Enabled}'"); + strBuilder.AppendLine("\t------"); + + Console.WriteLine(strBuilder.ToString()); + } + + public static void EnableTls(SslProtocols sslProtocols) + { + Console.WriteLine($"Configuring tls to match {sslProtocols}"); + + if (sslProtocols.HasFlag(SslProtocols.Tls12)) + { + EnableTls12(); + return; + } + if (sslProtocols.HasFlag(SslProtocols.Tls13)) + { + EnableTls13(); + return; + } + + Console.WriteLine("Enabling all TLS - no option specified"); + EnableAll(); + } + + private static void EnableAll() + { + // Enable TLS1.2 + SetRegistryValue(TLS12SubKey, "DisabledByDefault", value: 0, valueToOverride: 1); + SetRegistryValue(TLS12SubKey, "Enabled", value: 1, valueToOverride: 0); + + // Enable TLS1.3 + SetRegistryValue(TLS13SubKey, "DisabledByDefault", value: 0, valueToOverride: 1); + SetRegistryValue(TLS13SubKey, "Enabled", value: 1, valueToOverride: 0); + } + + private static void EnableTls12() + { + // Enable TLS1.2 + SetRegistryValue(TLS12SubKey, "DisabledByDefault", value: 0, valueToOverride: 1); + SetRegistryValue(TLS12SubKey, "Enabled", value: 1, valueToOverride: 0); + + // and disable TLS1.3 + SetRegistryValue(TLS13SubKey, "DisabledByDefault", value: 0, valueToOverride: 1); + SetRegistryValue(TLS13SubKey, "Enabled", value: 0, valueToOverride: 1); + } + + private static void EnableTls13() + { + // Enable TLS1.3 + SetRegistryValue(TLS13SubKey, "DisabledByDefault", value: 0, valueToOverride: 1); + SetRegistryValue(TLS13SubKey, "Enabled", value: 1, valueToOverride: 0); + + // and disable TLS1.2 + SetRegistryValue(TLS12SubKey, "DisabledByDefault", value: 0, valueToOverride: 1); + SetRegistryValue(TLS12SubKey, "Enabled", value: 0, valueToOverride: 1); + } + + private static void SetRegistryValue(string subKey, string name, int value, int valueToOverride) + { + var registrySubKey = GetAndCreateSubKey(subKey); + + var registryValue = registrySubKey.GetValue(name) as int?; + if (registryValue is null || registryValue == valueToOverride) + { + Console.WriteLine($"Setting value '{value}' on {subKey}\\{name}"); + registrySubKey.SetValue(name, value); + Console.WriteLine($"Successfully set value '{value}' on {subKey}\\{name}"); + } + } + + private static int? GetRegistryValue(string path, string name) + { + var localKey = RootRegistryKey; + + var registrySubKey = localKey.OpenSubKey(path); + if (registrySubKey is not null) + { + var value = registrySubKey.GetValue(name); + return value as int?; + } + + return null; + } + + private static RegistryKey GetAndCreateSubKey(string path) + { + var parts = path.Split(@"\"); + var localKey = RootRegistryKey; + + RegistryKey? registrySubKey = null; + var currentPath = parts[0] + @"\" + parts[1]; + var i = 1; + while (i <= parts.Length) + { + registrySubKey = localKey.OpenSubKey(currentPath, writable: true); + if (registrySubKey is null) + { + Console.WriteLine($"Registry subKey `{currentPath}` does not exist. Creating one..."); + registrySubKey = localKey.CreateSubKey(currentPath, writable: true); + Console.WriteLine($"Created Registry subKey `{currentPath}`"); + } + currentPath = string.Join(@"\", parts.Take(i++ + 1)); + } + + if (registrySubKey is null || registrySubKey.Name.Substring(localKey.Name.Length + 1) != path) + { + throw new ArgumentException($"failed to create registry subKey {path}"); + } + + return registrySubKey; + } +} diff --git a/src/BenchmarksApps/TLS/HttpSys/appsettings.Development.json b/src/BenchmarksApps/TLS/HttpSys/appsettings.Development.json index 116355112..b16b93f85 100644 --- a/src/BenchmarksApps/TLS/HttpSys/appsettings.Development.json +++ b/src/BenchmarksApps/TLS/HttpSys/appsettings.Development.json @@ -7,6 +7,9 @@ }, "mTLS": "false", "httpSysLogs": "true", + "tlsRegistryLogs": "true", "tlsRenegotiation": "true", - "certValidationConsoleEnabled": "true" + "certValidationConsoleEnabled": "true", + + "tlsProtocols": "tls12" } diff --git a/src/BenchmarksApps/TLS/Kestrel/ConfigurationHelpers.cs b/src/BenchmarksApps/TLS/Kestrel/ConfigurationHelpers.cs new file mode 100644 index 000000000..a516e3376 --- /dev/null +++ b/src/BenchmarksApps/TLS/Kestrel/ConfigurationHelpers.cs @@ -0,0 +1,39 @@ +using System.Security.Authentication; + +namespace Kestrel +{ + internal static class ConfigurationHelpers + { + public static SslProtocols ParseSslProtocols(string? supportedTlsVersions) + { + var protocols = SslProtocols.Tls12; // default it TLS 1.2 + if (string.IsNullOrEmpty(supportedTlsVersions)) + { + return protocols; + } + + protocols = SslProtocols.None; + foreach (var version in supportedTlsVersions.Split(',')) + { + switch (version.Trim().ToLower()) + { +#pragma warning disable SYSLIB0039 // Type or member is obsolete + case "tls11": + protocols |= SslProtocols.Tls11; + break; +#pragma warning restore SYSLIB0039 // Type or member is obsolete + case "tls12": + protocols |= SslProtocols.Tls12; + break; + case "tls13": + protocols |= SslProtocols.Tls13; + break; + default: + throw new ArgumentException($"Unsupported TLS version: {version}"); + } + } + + return protocols; + } + } +} diff --git a/src/BenchmarksApps/TLS/Kestrel/Program.cs b/src/BenchmarksApps/TLS/Kestrel/Program.cs index 9fa7b8dc9..479dd4cd6 100644 --- a/src/BenchmarksApps/TLS/Kestrel/Program.cs +++ b/src/BenchmarksApps/TLS/Kestrel/Program.cs @@ -1,7 +1,8 @@ using System.Net; using System.Net.Security; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; -using Microsoft.AspNetCore.Authentication.Certificate; +using Kestrel; using Microsoft.AspNetCore.Server.HttpSys; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Https; @@ -14,6 +15,7 @@ var tlsRenegotiationEnabled = bool.TryParse(builder.Configuration["tlsRenegotiation"], out var tlsRenegotiationEnabledConfig) && tlsRenegotiationEnabledConfig; var statsEnabled = bool.TryParse(builder.Configuration["statsEnabled"], out var connectionStatsEnabledConfig) && connectionStatsEnabledConfig; var listeningEndpoints = builder.Configuration["urls"] ?? "https://localhost:5000/"; +var supportedTlsVersions = ConfigurationHelpers.ParseSslProtocols(builder.Configuration["tlsProtocols"]); if (mTlsEnabled && tlsRenegotiationEnabled) { @@ -40,6 +42,8 @@ void ConfigureListen(KestrelServerOptions serverOptions, IConfigurationRoot conf // [SuppressMessage("Microsoft.Security", "CSCAN0220.DefaultPasswordContexts", Justification="Benchmark code, not a secret")] listenOptions.UseHttps("testCert.pfx", "testPassword", options => { + options.SslProtocols = supportedTlsVersions; + if (mTlsEnabled) { options.ClientCertificateMode = ClientCertificateMode.RequireCertificate; @@ -137,6 +141,7 @@ bool AllowAnyCertificateValidationWithLogging(X509Certificate2 certificate, X509 { Console.WriteLine($"\tenabled logging stats to console"); } +Console.WriteLine($"\tsupported TLS versions: {supportedTlsVersions}"); Console.WriteLine($"\tlistening endpoints: {listeningEndpoints}"); Console.WriteLine("--------------------------------");