Skip to content

Commit 5e7eb42

Browse files
authored
feat: disable tls-resumption for tls jobs (#2073)
1 parent efb0c21 commit 5e7eb42

File tree

5 files changed

+238
-58
lines changed

5 files changed

+238
-58
lines changed

Diff for: scenarios/tls.benchmarks.yml

+11
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
variables:
88
serverPort: 5000
99

10+
# these scripts allow to disable (or rollback changes) to the SChannel registry
11+
# this allows to disable TLS resumption on windows level
12+
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;"
13+
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;"
14+
1015
jobs:
1116
httpSysServer:
1217
source:
@@ -65,6 +70,8 @@ scenarios:
6570
tls-handshakes-httpsys:
6671
application:
6772
job: httpSysServer
73+
beforeScript: "{{disableTlsResumptionScript}}"
74+
afterScript: "{{rollbackTlsResumptionScript}}"
6875
load:
6976
job: httpclient
7077
variables:
@@ -77,6 +84,8 @@ scenarios:
7784
mTls-handshakes-httpsys:
7885
application:
7986
job: httpSysServer
87+
beforeScript: "{{disableTlsResumptionScript}}"
88+
afterScript: "{{rollbackTlsResumptionScript}}"
8089
variables:
8190
mTLS: true # enables settings on http.sys to negotiate client cert on connections
8291
tlsRenegotiation: true # enables client cert validation
@@ -97,6 +106,8 @@ scenarios:
97106
tls-renegotiation-httpsys:
98107
application:
99108
job: httpSysServer
109+
beforeScript: "{{disableTlsResumptionScript}}"
110+
afterScript: "{{rollbackTlsResumptionScript}}"
100111
variables:
101112
mTLS: false
102113
tlsRenegotiation: true
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace HttpSys.NetSh
2+
{
3+
public class SslCertBinding
4+
{
5+
public string CertificateThumbprint { get; set; }
6+
7+
public string ApplicationId { get; set; }
8+
9+
/// <summary>
10+
/// if mutual TLS is enabled
11+
/// </summary>
12+
public NetShFlag NegotiateClientCertificate { get; set; }
13+
14+
public NetShFlag DisableSessionIdTlsResumption { get; set; }
15+
public NetShFlag EnableSessionTicketTlsResumption { get; set; }
16+
17+
public override string ToString() => $"""
18+
Certificate thumbprint: {CertificateThumbprint}
19+
Application ID: {ApplicationId}
20+
Negotiate client certificate: {NegotiateClientCertificate}
21+
Disable Session ID TLS Resumption: {DisableSessionIdTlsResumption}
22+
Enable Session Ticket TLS Resumption: {EnableSessionTicketTlsResumption}
23+
-----
24+
""";
25+
}
26+
27+
public enum NetShFlag
28+
{
29+
NotSet = 0,
30+
31+
Disabled = 1,
32+
Enable = 2
33+
}
34+
}

Diff for: src/BenchmarksApps/TLS/HttpSys/NetShWrapper.cs

+144-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Diagnostics;
22
using System.Security.Cryptography.X509Certificates;
33
using System.Text.RegularExpressions;
4+
using HttpSys.NetSh;
45

56
namespace HttpSys
67
{
@@ -28,12 +29,49 @@ public static void DeleteBinding(string ipPort)
2829
Console.WriteLine("Disabled http.sys settings for mTLS");
2930
}
3031

31-
public static bool BindingExists(string ipPort, out string certThumbprint, out string appId)
32+
33+
34+
public static bool TryGetSslCertBinding(string ipPort, out SslCertBinding result)
3235
{
33-
certThumbprint = string.Empty;
34-
appId = string.Empty;
36+
result = new SslCertBinding();
3537

36-
var bindings = ExecuteNetShCommand("http show sslcert");
38+
/*
39+
Example of output:
40+
-----------------
41+
IP:port : <ip:port>
42+
Certificate Hash : <hash>
43+
Application ID : {<guid>}
44+
Certificate Store Name : <store-name>
45+
Verify Client Certificate Revocation : Enabled
46+
Verify Revocation Using Cached Client Certificate Only : Disabled
47+
Usage Check : Enabled
48+
Revocation Freshness Time : 0
49+
URL Retrieval Timeout : 0
50+
Ctl Identifier : (null)
51+
Ctl Store Name : (null)
52+
DS Mapper Usage : Disabled
53+
Negotiate Client Certificate : Disabled
54+
Reject Connections : Disabled
55+
Disable HTTP2 : Not Set
56+
Disable QUIC : Not Set
57+
Disable TLS1.2 : Not Set
58+
Disable TLS1.3 : Not Set
59+
Disable OCSP Stapling : Not Set
60+
Enable Token Binding : Not Set
61+
Log Extended Events : Not Set
62+
Disable Legacy TLS Versions : Not Set
63+
Enable Session Ticket : Not Set
64+
Disable Session ID : Not Set
65+
Enable Caching Client Hello : Not Set
66+
Extended Properties:
67+
PropertyId : 0
68+
Receive Window : 1048576
69+
Extended Properties:
70+
PropertyId : 1
71+
Max Settings Per Frame : 2796202
72+
Max Settings Per Minute : 4294967295
73+
*/
74+
var bindings = ExecuteNetShCommand($"http show sslcert ipport={ipPort}");
3775
if (string.IsNullOrEmpty(bindings) || !bindings.Contains(ipPort))
3876
{
3977
return false;
@@ -43,22 +81,51 @@ public static bool BindingExists(string ipPort, out string certThumbprint, out s
4381
var thumbprintMatch = Regex.Match(bindings, @"Certificate Hash\s+:\s+([a-fA-F0-9]+)");
4482
if (thumbprintMatch.Success)
4583
{
46-
certThumbprint = thumbprintMatch.Groups[1].Value;
84+
result.CertificateThumbprint = thumbprintMatch.Groups[1].Value;
4785
}
4886

4987
// Extract the application ID
5088
var appIdMatch = Regex.Match(bindings, @"Application ID\s+:\s+{([a-fA-F0-9-]+)}");
5189
if (appIdMatch.Success)
5290
{
53-
appId = appIdMatch.Groups[1].Value;
91+
result.ApplicationId = appIdMatch.Groups[1].Value;
92+
}
93+
94+
var negotiateClientCertEnabledRegex = Regex.Match(bindings, @"Negotiate Client Certificate\s+:\s+([a-zA-Z0-9]+)");
95+
if (negotiateClientCertEnabledRegex.Success)
96+
{
97+
var negotiateClientCertValue = negotiateClientCertEnabledRegex.Groups[1].Value;
98+
result.NegotiateClientCertificate = ParseNetShFlag(negotiateClientCertValue);
99+
}
100+
101+
var disableSessionId = Regex.Match(bindings, @"Disable Session ID\s+:\s+([a-zA-Z0-9 ]+)");
102+
if (disableSessionId.Success)
103+
{
104+
var disableSessionIdValue = disableSessionId.Groups[1].Value;
105+
result.DisableSessionIdTlsResumption = ParseNetShFlag(disableSessionIdValue);
106+
}
107+
108+
var enableSessionTicket = Regex.Match(bindings, @"Enable Session Ticket\s+:\s+([a-zA-Z0-9 ]+)");
109+
if (enableSessionTicket.Success)
110+
{
111+
var enableSessionTicketValue = enableSessionTicket.Groups[1].Value;
112+
result.EnableSessionTicketTlsResumption = ParseNetShFlag(enableSessionTicketValue);
54113
}
55114

56115
return true;
116+
117+
NetShFlag ParseNetShFlag(string prop) => prop switch
118+
{
119+
"Not Set" => NetShFlag.NotSet,
120+
"Disable" or "Disabled" => NetShFlag.Disabled,
121+
"Enable" or "Enabled" or "Set" => NetShFlag.Enable,
122+
_ => throw new ArgumentOutOfRangeException(nameof(prop), $"unexpected netsh flag '{prop}' for ssl cert binding"),
123+
};
57124
}
58125

59-
public static void Show()
126+
public static void LogSslCertBinding(string ipPort)
60127
{
61-
ExecuteNetShCommand("http show sslcert", alwaysLogOutput: true);
128+
ExecuteNetShCommand($"http show sslcert ipport={ipPort}", alwaysLogOutput: true);
62129
}
63130

64131
public static void SetTestCertBinding(string ipPort, bool enableClientCertNegotiation)
@@ -76,7 +143,7 @@ public static void SetTestCertBinding(string ipPort, bool enableClientCertNegoti
76143
}
77144

78145
string certThumbprint = certificate.Thumbprint;
79-
SetCertBinding(ipPort, certThumbprint, enableClientCertNegotiation: enableClientCertNegotiation);
146+
AddCertBinding(ipPort, certThumbprint, clientCertNegotiation: enableClientCertNegotiation ? NetShFlag.Enable : NetShFlag.Disabled);
80147

81148
Console.WriteLine("Configured binding for testCert for http.sys");
82149
}
@@ -108,16 +175,77 @@ public static bool TrySelfSignCertificate(string ipPort, out string certThumbpri
108175
}
109176
}
110177

111-
public static void SetCertBinding(string ipPort, string certThumbprint, string appId = null, bool enableClientCertNegotiation = false)
178+
public static void AddCertBinding(
179+
string ipPort, string certThumbprint,
180+
string? appId = null,
181+
NetShFlag clientCertNegotiation = NetShFlag.Disabled,
182+
NetShFlag disablesessionid = NetShFlag.Enable,
183+
NetShFlag enablesessionticket = NetShFlag.Disabled)
184+
=> CertBindingCore("add", ipPort, certThumbprint, appId, clientCertNegotiation, disablesessionid, enablesessionticket);
185+
186+
public static void UpdateCertBinding(string ipPort, SslCertBinding binding) => UpdateCertBinding(
187+
ipPort,
188+
binding.CertificateThumbprint,
189+
binding.ApplicationId,
190+
binding.NegotiateClientCertificate ,
191+
binding.DisableSessionIdTlsResumption,
192+
binding.EnableSessionTicketTlsResumption);
193+
194+
public static void UpdateCertBinding(
195+
string ipPort, string certThumbprint,
196+
string? appId = null,
197+
NetShFlag clientCertNegotiation = NetShFlag.Disabled,
198+
NetShFlag disablesessionid = NetShFlag.Enable,
199+
NetShFlag enablesessionticket = NetShFlag.Disabled)
200+
=> CertBindingCore("update", ipPort, certThumbprint, appId, clientCertNegotiation, disablesessionid, enablesessionticket);
201+
202+
private static void CertBindingCore(
203+
string httpOperation,
204+
string ipPort, string certThumbprint,
205+
string? appId = null,
206+
NetShFlag clientcertnegotiation = NetShFlag.Disabled,
207+
NetShFlag disablesessionid = NetShFlag.Enable,
208+
NetShFlag enablesessionticket = NetShFlag.Disabled)
112209
{
113-
var negotiateClientCert = enableClientCertNegotiation ? "enable" : "disable";
114210
if (string.IsNullOrEmpty(appId))
115211
{
116212
appId = "00000000-0000-0000-0000-000000000000";
117213
}
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}");
214+
215+
var clientcertnegotiationFlag = GetFlagValue(clientcertnegotiation);
216+
var disablesessionidFlag = GetFlagValue(disablesessionid);
217+
var enablesessionticketFlag = GetFlagValue(enablesessionticket);
218+
string command = $"http {httpOperation} sslcert ipport={ipPort} certstorename=MY certhash={certThumbprint} appid={{{appId}}}";
219+
220+
if (clientcertnegotiationFlag != null)
221+
{
222+
command += $" clientcertnegotiation={clientcertnegotiationFlag}";
223+
}
224+
225+
// below options are supported only in later versions of HTTP.SYS
226+
// you can identify if it is available by running `netsh http add sslcert help`
227+
// ---
228+
// workaround is to control SChannel settings via registry
229+
230+
//if (disablesessionidFlag != null)
231+
//{
232+
// command += $" disablesessionid={disablesessionidFlag}";
233+
//}
234+
//if (enablesessionticketFlag != null)
235+
//{
236+
// command += $" enablesessionticket={enablesessionticketFlag}";
237+
//}
238+
239+
ExecuteNetShCommand(command, alwaysLogOutput: true);
240+
Console.WriteLine($"Performed cert binding for {ipPort}");
241+
242+
string? GetFlagValue(NetShFlag flag) => flag switch
243+
{
244+
NetShFlag.NotSet => null,
245+
NetShFlag.Disabled => "disable",
246+
NetShFlag.Enable => "enable",
247+
_ => throw new ArgumentOutOfRangeException(nameof(flag)),
248+
};
121249
}
122250

123251
private static string ExecutePowershellCommand(string command, bool alwaysLogOutput = false)
@@ -126,7 +254,7 @@ private static string ExecutePowershellCommand(string command, bool alwaysLogOut
126254
private static string ExecuteNetShCommand(string command, bool alwaysLogOutput = false)
127255
=> ExecuteCommand("netsh", command, alwaysLogOutput);
128256

129-
private static string ExecuteCommand(string fileName, string command, bool alwaysLogOutput = false)
257+
private static string ExecuteCommand(string fileName, string command, bool logOutput = false)
130258
{
131259
ProcessStartInfo processInfo = new ProcessStartInfo(fileName, command)
132260
{
@@ -141,7 +269,7 @@ private static string ExecuteCommand(string fileName, string command, bool alway
141269
string output = process.StandardOutput.ReadToEnd();
142270
process.WaitForExit();
143271

144-
if (alwaysLogOutput)
272+
if (logOutput)
145273
{
146274
Console.WriteLine(output);
147275
}

0 commit comments

Comments
 (0)