Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: disable tls-resumption for tls jobs #2073

Merged
merged 26 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions scenarios/tls.benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
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:
Expand Down Expand Up @@ -65,6 +70,8 @@ scenarios:
tls-handshakes-httpsys:
application:
job: httpSysServer
beforeScript: "{{disableTlsResumptionScript}}"
afterScript: "{{rollbackTlsResumptionScript}}"
load:
job: httpclient
variables:
Expand All @@ -77,6 +84,8 @@ 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
Expand All @@ -97,6 +106,8 @@ scenarios:
tls-renegotiation-httpsys:
application:
job: httpSysServer
beforeScript: "{{disableTlsResumptionScript}}"
afterScript: "{{rollbackTlsResumptionScript}}"
variables:
mTLS: false
tlsRenegotiation: true
Expand Down
34 changes: 34 additions & 0 deletions src/BenchmarksApps/TLS/HttpSys/NetSh/SslCertBinding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace HttpSys.NetSh
{
public class SslCertBinding
{
public string CertificateThumbprint { get; set; }

Check warning on line 5 in src/BenchmarksApps/TLS/HttpSys/NetSh/SslCertBinding.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Non-nullable property 'CertificateThumbprint' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

public string ApplicationId { get; set; }

Check warning on line 7 in src/BenchmarksApps/TLS/HttpSys/NetSh/SslCertBinding.cs

View workflow job for this annotation

GitHub Actions / Build & Test (ubuntu-latest)

Non-nullable property 'ApplicationId' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.

/// <summary>
/// if mutual TLS is enabled
/// </summary>
public NetShFlag NegotiateClientCertificate { get; set; }

public NetShFlag DisableSessionIdTlsResumption { get; set; }
public NetShFlag EnableSessionTicketTlsResumption { get; set; }

public override string ToString() => $"""
Certificate thumbprint: {CertificateThumbprint}
Application ID: {ApplicationId}
Negotiate client certificate: {NegotiateClientCertificate}
Disable Session ID TLS Resumption: {DisableSessionIdTlsResumption}
Enable Session Ticket TLS Resumption: {EnableSessionTicketTlsResumption}
-----
""";
}

public enum NetShFlag
{
NotSet = 0,

Disabled = 1,
Enable = 2
}
}
160 changes: 144 additions & 16 deletions src/BenchmarksApps/TLS/HttpSys/NetShWrapper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using HttpSys.NetSh;

namespace HttpSys
{
Expand Down Expand Up @@ -28,12 +29,49 @@ public static void DeleteBinding(string ipPort)
Console.WriteLine("Disabled http.sys settings for mTLS");
}

public static bool BindingExists(string ipPort, out string certThumbprint, out string appId)


public static bool TryGetSslCertBinding(string ipPort, out SslCertBinding result)
{
certThumbprint = string.Empty;
appId = string.Empty;
result = new SslCertBinding();

var bindings = ExecuteNetShCommand("http show sslcert");
/*
Example of output:
-----------------
IP:port : <ip:port>
Certificate Hash : <hash>
Application ID : {<guid>}
Certificate Store Name : <store-name>
Verify Client Certificate Revocation : Enabled
Verify Revocation Using Cached Client Certificate Only : Disabled
Usage Check : Enabled
Revocation Freshness Time : 0
URL Retrieval Timeout : 0
Ctl Identifier : (null)
Ctl Store Name : (null)
DS Mapper Usage : Disabled
Negotiate Client Certificate : Disabled
Reject Connections : Disabled
Disable HTTP2 : Not Set
Disable QUIC : Not Set
Disable TLS1.2 : Not Set
Disable TLS1.3 : Not Set
Disable OCSP Stapling : Not Set
Enable Token Binding : Not Set
Log Extended Events : Not Set
Disable Legacy TLS Versions : Not Set
Enable Session Ticket : Not Set
Disable Session ID : Not Set
Enable Caching Client Hello : Not Set
Extended Properties:
PropertyId : 0
Receive Window : 1048576
Extended Properties:
PropertyId : 1
Max Settings Per Frame : 2796202
Max Settings Per Minute : 4294967295
*/
var bindings = ExecuteNetShCommand($"http show sslcert ipport={ipPort}");
if (string.IsNullOrEmpty(bindings) || !bindings.Contains(ipPort))
{
return false;
Expand All @@ -43,22 +81,51 @@ public static bool BindingExists(string ipPort, out string certThumbprint, out s
var thumbprintMatch = Regex.Match(bindings, @"Certificate Hash\s+:\s+([a-fA-F0-9]+)");
if (thumbprintMatch.Success)
{
certThumbprint = thumbprintMatch.Groups[1].Value;
result.CertificateThumbprint = thumbprintMatch.Groups[1].Value;
}

// Extract the application ID
var appIdMatch = Regex.Match(bindings, @"Application ID\s+:\s+{([a-fA-F0-9-]+)}");
if (appIdMatch.Success)
{
appId = appIdMatch.Groups[1].Value;
result.ApplicationId = appIdMatch.Groups[1].Value;
}

var negotiateClientCertEnabledRegex = Regex.Match(bindings, @"Negotiate Client Certificate\s+:\s+([a-zA-Z0-9]+)");
if (negotiateClientCertEnabledRegex.Success)
{
var negotiateClientCertValue = negotiateClientCertEnabledRegex.Groups[1].Value;
result.NegotiateClientCertificate = ParseNetShFlag(negotiateClientCertValue);
}

var disableSessionId = Regex.Match(bindings, @"Disable Session ID\s+:\s+([a-zA-Z0-9 ]+)");
if (disableSessionId.Success)
{
var disableSessionIdValue = disableSessionId.Groups[1].Value;
result.DisableSessionIdTlsResumption = ParseNetShFlag(disableSessionIdValue);
}

var enableSessionTicket = Regex.Match(bindings, @"Enable Session Ticket\s+:\s+([a-zA-Z0-9 ]+)");
if (enableSessionTicket.Success)
{
var enableSessionTicketValue = enableSessionTicket.Groups[1].Value;
result.EnableSessionTicketTlsResumption = ParseNetShFlag(enableSessionTicketValue);
}

return true;

NetShFlag ParseNetShFlag(string prop) => prop switch
{
"Not Set" => NetShFlag.NotSet,
"Disable" or "Disabled" => NetShFlag.Disabled,
"Enable" or "Enabled" or "Set" => NetShFlag.Enable,
_ => throw new ArgumentOutOfRangeException(nameof(prop), $"unexpected netsh flag '{prop}' for ssl cert binding"),
};
}

public static void Show()
public static void LogSslCertBinding(string ipPort)
{
ExecuteNetShCommand("http show sslcert", alwaysLogOutput: true);
ExecuteNetShCommand($"http show sslcert ipport={ipPort}", alwaysLogOutput: true);
}

public static void SetTestCertBinding(string ipPort, bool enableClientCertNegotiation)
Expand All @@ -76,7 +143,7 @@ public static void SetTestCertBinding(string ipPort, bool enableClientCertNegoti
}

string certThumbprint = certificate.Thumbprint;
SetCertBinding(ipPort, certThumbprint, enableClientCertNegotiation: enableClientCertNegotiation);
AddCertBinding(ipPort, certThumbprint, clientCertNegotiation: enableClientCertNegotiation ? NetShFlag.Enable : NetShFlag.Disabled);

Console.WriteLine("Configured binding for testCert for http.sys");
}
Expand Down Expand Up @@ -108,16 +175,77 @@ public static bool TrySelfSignCertificate(string ipPort, out string certThumbpri
}
}

public static void SetCertBinding(string ipPort, string certThumbprint, string appId = null, bool enableClientCertNegotiation = false)
public static void AddCertBinding(
string ipPort, string certThumbprint,
string? appId = null,
NetShFlag clientCertNegotiation = NetShFlag.Disabled,
NetShFlag disablesessionid = NetShFlag.Enable,
NetShFlag enablesessionticket = NetShFlag.Disabled)
=> CertBindingCore("add", ipPort, certThumbprint, appId, clientCertNegotiation, disablesessionid, enablesessionticket);

public static void UpdateCertBinding(string ipPort, SslCertBinding binding) => UpdateCertBinding(
ipPort,
binding.CertificateThumbprint,
binding.ApplicationId,
binding.NegotiateClientCertificate ,
binding.DisableSessionIdTlsResumption,
binding.EnableSessionTicketTlsResumption);

public static void UpdateCertBinding(
string ipPort, string certThumbprint,
string? appId = null,
NetShFlag clientCertNegotiation = NetShFlag.Disabled,
NetShFlag disablesessionid = NetShFlag.Enable,
NetShFlag enablesessionticket = NetShFlag.Disabled)
=> CertBindingCore("update", ipPort, certThumbprint, appId, clientCertNegotiation, disablesessionid, enablesessionticket);

private static void CertBindingCore(
string httpOperation,
string ipPort, string certThumbprint,
string? appId = null,
NetShFlag clientcertnegotiation = NetShFlag.Disabled,
NetShFlag disablesessionid = NetShFlag.Enable,
NetShFlag enablesessionticket = NetShFlag.Disabled)
{
var negotiateClientCert = enableClientCertNegotiation ? "enable" : "disable";
if (string.IsNullOrEmpty(appId))
{
appId = "00000000-0000-0000-0000-000000000000";
}
string command = $"http add sslcert ipport={ipPort} certstorename=MY certhash={certThumbprint} appid={{{appId}}} clientcertnegotiation={negotiateClientCert}";
ExecuteNetShCommand(command);
Console.WriteLine($"Performed cert bindign for {ipPort}");

var clientcertnegotiationFlag = GetFlagValue(clientcertnegotiation);
var disablesessionidFlag = GetFlagValue(disablesessionid);
var enablesessionticketFlag = GetFlagValue(enablesessionticket);
string command = $"http {httpOperation} sslcert ipport={ipPort} certstorename=MY certhash={certThumbprint} appid={{{appId}}}";

if (clientcertnegotiationFlag != null)
{
command += $" clientcertnegotiation={clientcertnegotiationFlag}";
}

// 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}";
//}

ExecuteNetShCommand(command, alwaysLogOutput: true);
Console.WriteLine($"Performed cert binding for {ipPort}");

string? GetFlagValue(NetShFlag flag) => flag switch
{
NetShFlag.NotSet => null,
NetShFlag.Disabled => "disable",
NetShFlag.Enable => "enable",
_ => throw new ArgumentOutOfRangeException(nameof(flag)),
};
}

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

private static string ExecuteCommand(string fileName, string command, bool alwaysLogOutput = false)
private static string ExecuteCommand(string fileName, string command, bool logOutput = false)
{
ProcessStartInfo processInfo = new ProcessStartInfo(fileName, command)
{
Expand All @@ -141,7 +269,7 @@ private static string ExecuteCommand(string fileName, string command, bool alway
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();

if (alwaysLogOutput)
if (logOutput)
{
Console.WriteLine(output);
}
Expand Down
Loading
Loading