Skip to content

Commit 976ce03

Browse files
authored
fix: re-set the netsh sslcert bindings after reconfiguration (#2067)
1 parent 95365f5 commit 976ce03

File tree

4 files changed

+105
-27
lines changed

4 files changed

+105
-27
lines changed

scenarios/tls.benchmarks.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ jobs:
2020
tlsRenegotiation: false # enables client cert validation
2121
# debug settings
2222
certValidationConsoleEnabled: false
23-
httpSysLogs: false
2423
statsEnabled: false
2524
logRequestDetails: false
26-
arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}} --httpSysLogs {{httpSysLogs}} --logRequestDetails {{logRequestDetails}}"
25+
arguments: "--urls https://{{serverAddress}}:{{serverPort}} --mTLS {{mTLS}} --certValidationConsoleEnabled {{certValidationConsoleEnabled}} --statsEnabled {{statsEnabled}} --tlsRenegotiation {{tlsRenegotiation}} --logRequestDetails {{logRequestDetails}}"
2726

2827
kestrelServer:
2928
source:
@@ -81,7 +80,6 @@ scenarios:
8180
variables:
8281
mTLS: true # enables settings on http.sys to negotiate client cert on connections
8382
tlsRenegotiation: true # enables client cert validation
84-
httpSysLogs: false # only for debug purposes
8583
certValidationConsoleEnabled: false # only for debug purposes
8684
serverPort: 8080 # IMPORTANT: not to intersect with other tests in case http.sys configuration impacts other benchmarks
8785
load:
@@ -102,7 +100,6 @@ scenarios:
102100
variables:
103101
mTLS: false
104102
tlsRenegotiation: true
105-
httpSysLogs: false # only for debug purposes
106103
certValidationConsoleEnabled: false # only for debug purposes
107104
load:
108105
job: httpclient

src/BenchmarksApps/TLS/HttpSys/NetShWrapper.cs

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
using System.Diagnostics;
22
using System.Security.Cryptography.X509Certificates;
3+
using System.Text.RegularExpressions;
34

45
namespace HttpSys
56
{
67
public static class NetShWrapper
78
{
8-
public static void DisableHttpSysMutualTlsIfExists(string ipPort)
9+
public static void DeleteBindingIfExists(string ipPort)
910
{
1011
try
1112
{
12-
DisableHttpSysMutualTls(ipPort);
13+
DeleteBinding(ipPort);
1314
}
1415
catch
1516
{
1617
// ignore
1718
}
1819
}
1920

20-
public static void DisableHttpSysMutualTls(string ipPort)
21+
public static void DeleteBinding(string ipPort)
2122
{
2223
Console.WriteLine("Disabling mTLS for http.sys");
2324

@@ -27,14 +28,42 @@ public static void DisableHttpSysMutualTls(string ipPort)
2728
Console.WriteLine("Disabled http.sys settings for mTLS");
2829
}
2930

31+
public static bool BindingExists(string ipPort, out string certThumbprint, out string appId)
32+
{
33+
certThumbprint = string.Empty;
34+
appId = string.Empty;
35+
36+
var bindings = ExecuteNetShCommand("http show sslcert");
37+
if (string.IsNullOrEmpty(bindings) || !bindings.Contains(ipPort))
38+
{
39+
return false;
40+
}
41+
42+
// Extract the certificate thumbprint
43+
var thumbprintMatch = Regex.Match(bindings, @"Certificate Hash\s+:\s+([a-fA-F0-9]+)");
44+
if (thumbprintMatch.Success)
45+
{
46+
certThumbprint = thumbprintMatch.Groups[1].Value;
47+
}
48+
49+
// Extract the application ID
50+
var appIdMatch = Regex.Match(bindings, @"Application ID\s+:\s+{([a-fA-F0-9-]+)}");
51+
if (appIdMatch.Success)
52+
{
53+
appId = appIdMatch.Groups[1].Value;
54+
}
55+
56+
return true;
57+
}
58+
3059
public static void Show()
3160
{
3261
ExecuteNetShCommand("http show sslcert", alwaysLogOutput: true);
3362
}
3463

35-
public static void EnableHttpSysMutualTls(string ipPort)
64+
public static void SetTestCertBinding(string ipPort, bool enableClientCertNegotiation)
3665
{
37-
Console.WriteLine("Setting up mTLS for http.sys");
66+
Console.WriteLine("Setting up binding for testCert for http.sys");
3867

3968
var certificate = LoadCertificate();
4069
Console.WriteLine("Loaded `testCert.pfx` from local file system");
@@ -47,25 +76,67 @@ public static void EnableHttpSysMutualTls(string ipPort)
4776
}
4877

4978
string certThumbprint = certificate.Thumbprint;
50-
string appId = Guid.NewGuid().ToString();
79+
SetCertBinding(ipPort, certThumbprint, enableClientCertNegotiation: enableClientCertNegotiation);
5180

52-
string command = $"http add sslcert ipport={ipPort} certstorename=MY certhash={certThumbprint} appid={{{appId}}} clientcertnegotiation=enable";
53-
ExecuteNetShCommand(command);
81+
Console.WriteLine("Configured binding for testCert for http.sys");
82+
}
5483

55-
Console.WriteLine("Configured http.sys settings for mTLS");
84+
public static bool TrySelfSignCertificate(string ipPort, out string certThumbprint)
85+
{
86+
certThumbprint = string.Empty;
87+
try
88+
{
89+
// Extract the IP address from ipPort
90+
string ipAddress = ipPort.Split(':')[0];
91+
92+
// Generate a self-signed certificate using PowerShell
93+
string command = $"New-SelfSignedCertificate -CertStoreLocation cert:\\LocalMachine\\My -DnsName {ipAddress}";
94+
string output = ExecutePowershellCommand(command);
95+
96+
// Extract the thumbprint from the output
97+
var lines = output.Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
98+
var lastLine = lines[^1];
99+
certThumbprint = lastLine.Split(" ", StringSplitOptions.RemoveEmptyEntries)[0];
100+
101+
Console.WriteLine($"Self-signed certificate for {ipAddress}");
102+
return true;
103+
}
104+
catch (Exception ex)
105+
{
106+
Console.WriteLine("Failed to self-sign the certificate: " + ex.Message);
107+
return false;
108+
}
109+
}
110+
111+
public static void SetCertBinding(string ipPort, string certThumbprint, string appId = null, bool enableClientCertNegotiation = false)
112+
{
113+
var negotiateClientCert = enableClientCertNegotiation ? "enable" : "disable";
114+
if (string.IsNullOrEmpty(appId))
115+
{
116+
appId = "00000000-0000-0000-0000-000000000000";
117+
}
118+
string command = $"http add sslcert ipport={ipPort} certstorename=MY certhash={certThumbprint} appid={{{appId}}} clientcertnegotiation={negotiateClientCert}";
119+
ExecuteNetShCommand(command);
120+
Console.WriteLine($"Performed cert bindign for {ipPort}");
56121
}
57122

58-
private static void ExecuteNetShCommand(string command, bool alwaysLogOutput = false)
123+
private static string ExecutePowershellCommand(string command, bool alwaysLogOutput = false)
124+
=> ExecuteCommand("powershell.exe", command, alwaysLogOutput);
125+
126+
private static string ExecuteNetShCommand(string command, bool alwaysLogOutput = false)
127+
=> ExecuteCommand("netsh", command, alwaysLogOutput);
128+
129+
private static string ExecuteCommand(string fileName, string command, bool alwaysLogOutput = false)
59130
{
60-
ProcessStartInfo processInfo = new ProcessStartInfo("netsh", command)
131+
ProcessStartInfo processInfo = new ProcessStartInfo(fileName, command)
61132
{
62133
RedirectStandardOutput = true,
63134
RedirectStandardError = true,
64135
UseShellExecute = false,
65136
CreateNoWindow = true
66137
};
67138

68-
Console.WriteLine($"Executing command: `netsh {command}`");
139+
Console.WriteLine($"Executing command: `{fileName} {command}`");
69140
using Process process = Process.Start(processInfo)!;
70141
string output = process.StandardOutput.ReadToEnd();
71142
process.WaitForExit();
@@ -77,8 +148,10 @@ private static void ExecuteNetShCommand(string command, bool alwaysLogOutput = f
77148

78149
if (process.ExitCode != 0)
79150
{
80-
throw new InvalidOperationException($"netsh command execution failure: {output}");
151+
throw new InvalidOperationException($"{fileName} command execution failure: {output}");
81152
}
153+
154+
return output;
82155
}
83156

84157
private static X509Certificate2 LoadCertificate()

src/BenchmarksApps/TLS/HttpSys/Program.cs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
builder.Logging.ClearProviders();
88

99
// behavioral
10-
var httpSysLoggingEnabled = bool.TryParse(builder.Configuration["httpSysLogs"], out var httpSysLogsEnabled) && httpSysLogsEnabled;
1110
var mTlsEnabled = bool.TryParse(builder.Configuration["mTLS"], out var mTlsEnabledConfig) && mTlsEnabledConfig;
1211
var tlsRenegotiationEnabled = bool.TryParse(builder.Configuration["tlsRenegotiation"], out var tlsRenegotiationEnabledConfig) && tlsRenegotiationEnabledConfig;
1312
var listeningEndpoints = builder.Configuration["urls"] ?? "https://localhost:5000/";
@@ -18,6 +17,17 @@
1817
var statsEnabled = bool.TryParse(builder.Configuration["statsEnabled"], out var connectionStatsEnabledConfig) && connectionStatsEnabledConfig;
1918
var logRequestDetails = bool.TryParse(builder.Configuration["logRequestDetails"], out var logRequestDetailsConfig) && logRequestDetailsConfig;
2019

20+
// existing netsh bindings to restore after the benchmark run
21+
if (!NetShWrapper.BindingExists(httpsIpPort, out var originalCertThumbprint, out var originalAppId))
22+
{
23+
Console.WriteLine($"No binding existed. Need to self-sign it and bind to '{httpsIpPort}'");
24+
if (!NetShWrapper.TrySelfSignCertificate(httpsIpPort, out originalCertThumbprint))
25+
{
26+
throw new ApplicationException($"Failed to setup ssl binding for '{httpsIpPort}'. Please unblock the VM.");
27+
}
28+
NetShWrapper.SetCertBinding(httpsIpPort, originalCertThumbprint);
29+
}
30+
2131
#pragma warning disable CA1416 // Can be launched only on Windows (HttpSys)
2232
builder.WebHost.UseHttpSys(options =>
2333
{
@@ -81,7 +91,9 @@ void OnShutdown()
8191

8292
try
8393
{
84-
NetShWrapper.DisableHttpSysMutualTls(ipPort: httpsIpPort);
94+
NetShWrapper.DeleteBinding(ipPort: httpsIpPort);
95+
NetShWrapper.SetCertBinding(ipPort: httpsIpPort, certThumbprint: originalCertThumbprint, appId: originalAppId);
96+
NetShWrapper.Show();
8597
}
8698
catch
8799
{
@@ -93,9 +105,8 @@ void OnShutdown()
93105
try
94106
{
95107
// if not executed, following command (enable http.sys mutual tls) will fail because binding exists
96-
NetShWrapper.DisableHttpSysMutualTlsIfExists(ipPort: httpsIpPort);
97-
98-
NetShWrapper.EnableHttpSysMutualTls(ipPort: httpsIpPort);
108+
NetShWrapper.DeleteBindingIfExists(ipPort: httpsIpPort);
109+
NetShWrapper.SetTestCertBinding(ipPort: httpsIpPort, enableClientCertNegotiation: true);
99110
}
100111
catch
101112
{
@@ -138,10 +149,7 @@ void OnShutdown()
138149

139150
await app.StartAsync();
140151

141-
if (httpSysLoggingEnabled)
142-
{
143-
NetShWrapper.Show();
144-
}
152+
NetShWrapper.Show();
145153

146154
Console.WriteLine("Application Info:");
147155
if (mTlsEnabled)

src/BenchmarksApps/TLS/HttpSys/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dotnetRunMessages": true,
77
"launchBrowser": true,
88
"launchUrl": "hello-world",
9-
"applicationUrl": "https://localhost:5000;http://localhost:5001",
9+
"applicationUrl": "https://127.0.0.1:5000;http://localhost:5001",
1010
"environmentVariables": {
1111
"ASPNETCORE_ENVIRONMENT": "Development"
1212
}

0 commit comments

Comments
 (0)