Skip to content

Commit 55b23b5

Browse files
authored
fix: integrate mutual TLS scenario with underlying http.sys server (#2044)
1 parent 9b5402f commit 55b23b5

File tree

6 files changed

+173
-6
lines changed

6 files changed

+173
-6
lines changed

build/trend-scenarios.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ parameters:
108108
- displayName: "HttpSys Windows: mTLS Handshakes"
109109
arguments: --scenario mTls-handshakes-httpsys $(tlsJobs) --property scenario=HttpSysMutualTLSHandshakes --application.options.requiredOperatingSystem windows
110110

111+
- displayName: "HttpSys Windows: TLS Renegotiation"
112+
arguments: --scenario tls-renegotiation-httpsys $(tlsJobs) --property scenario=HttpSysTLSRenegotiation --application.options.requiredOperatingSystem windows
113+
111114
- displayName: "Kestrel Linux: TLS Handshakes"
112115
arguments: --scenario tls-handshakes-kestrel $(tlsJobs) --property scenario=KestrelTLSHandshakes --application.options.requiredOperatingSystem linux
113116

scenarios/tls.benchmarks.yml

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ jobs:
1515
project: src/BenchmarksApps/TLS/HttpSys/HttpSys.csproj
1616
readyStateText: Application started.
1717
variables:
18-
mTLS: false
18+
mTLS: false # enables settings on http.sys to negotiate client cert on connections
19+
tlsRenegotiation: false # enables client cert validation
1920
certValidationConsoleEnabled: false
21+
httpSysLogs: false
2022
statsEnabled: false
21-
arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}}"
23+
arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}} --httpSysLogs {{httpSysLogs}}"
2224

2325
kestrelServer:
2426
source:
@@ -52,7 +54,29 @@ scenarios:
5254
application:
5355
job: httpSysServer
5456
variables:
55-
mTLS: true
57+
mTLS: true # enables settings on http.sys to negotiate client cert on connections
58+
tlsRenegotiation: true # enables client cert validation
59+
httpSysLogs: false # only for debug purposes
60+
certValidationConsoleEnabled: false # only for debug purposes
61+
serverPort: 8080 # IMPORTANT: not to intersect with other tests in case http.sys configuration impacts other benchmarks
62+
load:
63+
job: httpclient
64+
variables:
65+
serverPort: 8080 # in sync with server
66+
path: /hello-world
67+
presetHeaders: connectionclose
68+
connections: 32
69+
serverScheme: https
70+
certPath: https://raw.githubusercontent.com/aspnet/Benchmarks/refs/heads/main/src/BenchmarksApps/TLS/HttpSys/testCert.pfx
71+
certPwd: testPassword
72+
73+
tls-renegotiation-httpsys:
74+
application:
75+
job: httpSysServer
76+
variables:
77+
mTLS: false
78+
tlsRenegotiation: true
79+
httpSysLogs: false # only for debug purposes
5680
certValidationConsoleEnabled: false # only for debug purposes
5781
load:
5882
job: httpclient

src/BenchmarksApps/TLS/HttpSys/HttpSys.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@
66
<ImplicitUsings>enable</ImplicitUsings>
77
</PropertyGroup>
88

9+
<ItemGroup>
10+
<None Update="testCert.pfx">
11+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
12+
</None>
13+
</ItemGroup>
14+
915
</Project>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System.Diagnostics;
2+
using System.Security.Cryptography.X509Certificates;
3+
4+
namespace HttpSys
5+
{
6+
public static class NetShWrapper
7+
{
8+
public static void DisableHttpSysMutualTlsIfExists(string ipPort)
9+
{
10+
try
11+
{
12+
DisableHttpSysMutualTls(ipPort);
13+
}
14+
catch
15+
{
16+
// ignore
17+
}
18+
}
19+
20+
public static void DisableHttpSysMutualTls(string ipPort)
21+
{
22+
Console.WriteLine("Disabling mTLS for http.sys");
23+
24+
string command = $"http delete sslcert ipport={ipPort}";
25+
ExecuteNetShCommand(command);
26+
27+
Console.WriteLine("Disabled http.sys settings for mTLS");
28+
}
29+
30+
public static void Show()
31+
{
32+
ExecuteNetShCommand("http show sslcert", alwaysLogOutput: true);
33+
}
34+
35+
public static void EnableHttpSysMutualTls(string ipPort)
36+
{
37+
Console.WriteLine("Setting up mTLS for http.sys");
38+
39+
var certificate = LoadCertificate();
40+
Console.WriteLine("Loaded `testCert.pfx` from local file system");
41+
using (var store = new X509Store(StoreName.My, StoreLocation.LocalMachine))
42+
{
43+
store.Open(OpenFlags.ReadWrite);
44+
store.Add(certificate);
45+
Console.WriteLine("Added `testCert.pfx` to localMachine cert store");
46+
store.Close();
47+
}
48+
49+
string certThumbprint = certificate.Thumbprint;
50+
string appId = Guid.NewGuid().ToString();
51+
52+
string command = $"http add sslcert ipport={ipPort} certstorename=MY certhash={certThumbprint} appid={{{appId}}} clientcertnegotiation=enable";
53+
ExecuteNetShCommand(command);
54+
55+
Console.WriteLine("Configured http.sys settings for mTLS");
56+
}
57+
58+
private static void ExecuteNetShCommand(string command, bool alwaysLogOutput = false)
59+
{
60+
ProcessStartInfo processInfo = new ProcessStartInfo("netsh", command)
61+
{
62+
RedirectStandardOutput = true,
63+
RedirectStandardError = true,
64+
UseShellExecute = false,
65+
CreateNoWindow = true
66+
};
67+
68+
Console.WriteLine($"Executing command: `netsh {command}`");
69+
using Process process = Process.Start(processInfo)!;
70+
string output = process.StandardOutput.ReadToEnd();
71+
process.WaitForExit();
72+
73+
if (alwaysLogOutput)
74+
{
75+
Console.WriteLine(output);
76+
}
77+
78+
if (process.ExitCode != 0)
79+
{
80+
throw new InvalidOperationException($"netsh command execution failure: {output}");
81+
}
82+
}
83+
84+
private static X509Certificate2 LoadCertificate()
85+
=> File.Exists("testCert.pfx")
86+
? X509CertificateLoader.LoadPkcs12FromFile("testCert.pfx", "testPassword", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable)
87+
: X509CertificateLoader.LoadPkcs12FromFile("../testCert.pfx", "testPassword", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
88+
}
89+
}

src/BenchmarksApps/TLS/HttpSys/Program.cs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
using Microsoft.AspNetCore.Mvc;
1+
using HttpSys;
22
using Microsoft.AspNetCore.Server.HttpSys;
33

44
var builder = WebApplication.CreateBuilder(args);
55
builder.Logging.ClearProviders();
66

77
var writeCertValidationEventsToConsole = bool.TryParse(builder.Configuration["certValidationConsoleEnabled"], out var certValidationConsoleEnabled) && certValidationConsoleEnabled;
8+
var httpSysLoggingEnabled = bool.TryParse(builder.Configuration["httpSysLogs"], out var httpSysLogsEnabled) && httpSysLogsEnabled;
89
var statsEnabled = bool.TryParse(builder.Configuration["statsEnabled"], out var connectionStatsEnabledConfig) && connectionStatsEnabledConfig;
910
var mTlsEnabled = bool.TryParse(builder.Configuration["mTLS"], out var mTlsEnabledConfig) && mTlsEnabledConfig;
11+
var tlsRenegotiationEnabled = bool.TryParse(builder.Configuration["tlsRenegotiation"], out var tlsRenegotiationEnabledConfig) && tlsRenegotiationEnabledConfig;
1012
var listeningEndpoints = builder.Configuration["urls"] ?? "https://localhost:5000/";
13+
var httpsIpPort = listeningEndpoints.Split(";").First(x => x.Contains("https")).Replace("https://", "");
1114

1215
#pragma warning disable CA1416 // Can be launched only on Windows (HttpSys)
1316
builder.WebHost.UseHttpSys(options =>
@@ -17,7 +20,7 @@
1720
});
1821
#pragma warning restore CA1416 // Can be launched only on Windows (HttpSys)
1922

20-
var app = builder.Build();
23+
var app = builder.Build();
2124

2225
app.MapGet("/hello-world", () =>
2326
{
@@ -40,6 +43,40 @@
4043
}
4144

4245
if (mTlsEnabled)
46+
{
47+
var hostAppLifetime = app.Services.GetRequiredService<IHostApplicationLifetime>();
48+
hostAppLifetime!.ApplicationStopping.Register(OnShutdown);
49+
50+
void OnShutdown()
51+
{
52+
Console.WriteLine("Application shutdown started.");
53+
54+
try
55+
{
56+
NetShWrapper.DisableHttpSysMutualTls(ipPort: httpsIpPort);
57+
}
58+
catch
59+
{
60+
Console.WriteLine("Failed to disable HTTP.SYS mTLS settings");
61+
throw;
62+
}
63+
}
64+
65+
try
66+
{
67+
// if not executed, following command (enable http.sys mutual tls) will fail because binding exists
68+
NetShWrapper.DisableHttpSysMutualTlsIfExists(ipPort: httpsIpPort);
69+
70+
NetShWrapper.EnableHttpSysMutualTls(ipPort: httpsIpPort);
71+
}
72+
catch
73+
{
74+
Console.WriteLine($"Http.Sys configuration for mTLS failed");
75+
throw;
76+
}
77+
}
78+
79+
if (tlsRenegotiationEnabled)
4380
{
4481
// this is an http.sys middleware to get a cert
4582
Console.WriteLine("Registered client cert validation middleware");
@@ -72,6 +109,12 @@
72109
}
73110

74111
await app.StartAsync();
112+
113+
if (httpSysLoggingEnabled)
114+
{
115+
NetShWrapper.Show();
116+
}
117+
75118
Console.WriteLine("Application Info:");
76119
if (mTlsEnabled)
77120
{

src/BenchmarksApps/TLS/HttpSys/appsettings.Development.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
"Microsoft.AspNetCore": "Warning"
66
}
77
},
8-
"mTLS": "true",
8+
"mTLS": "false",
9+
"httpSysLogs": "true",
10+
"tlsRenegotiation": "true",
911
"certValidationConsoleEnabled": "true"
1012
}

0 commit comments

Comments
 (0)