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

Add support in Kestrel to configure signature algorithms and cipher suites on a per-SNI basis. #58560

Open
1 task done
avparuch opened this issue Oct 22, 2024 · 0 comments
Open
1 task done
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions

Comments

@avparuch
Copy link
Contributor

avparuch commented Oct 22, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

Kestrel currently allows configuration of a wide variety of TLS settings on a per-SNI basis via SslStream. (such as choosing the server TLS certificate for a given SNI, whether to negotiate a client certificate eagerly or in a delayed manner, the list of distinguished names to send in the TLS handshake, etc)

However, Kestrel does not allow the configuration of signature algorithms on any platform. Also, Kestrel does not allow configuration of cipher suites on Windows. HTTP.sys provides this functionality on Windows and I'm requesting this capability on Kestrel (on both Windows and Linux).

Note: The signature algorithms and cipher suites in Windows are centrally set via Schannel registry keys. Both Kestrel and HTTP.sys delegate to using settings chosen by Schannel. However, HTTP.Sys provides APIs to modify the centrally selected settings on a per-SNI basis - and that capability is what I'm requesting on Kestrel. For a multi-tenant reverse proxy, the ability to choose TLS settings on a per-SNI basis is crucial.

Describe the solution you'd like

I understand that the API shape exposed via ServerOptionsSelectionCallback is not quite the same as HTTP.sys. That is why I'm only describing how HTTP.sys achieves the capability I need. I'm not suggesting a particular API shape for how Kestrel may implement the feature.

The below code uses HTTP.Sys APIs to configure signature algorithms and cipher suites on a per SNI basis.
Note: The code below is not meant to be used in production as-is. The intent is to provide a high-level overview of how per-SNI TLS settings are configured on HTTP.sys.

// Let's configure TLS settings for foo.com via HTTP.sys APIs. This is done in 2 steps.

// Step 1: On HTTP.sys, the TLS configuration for any SNI is created by invoking HttpSetServiceConfiguration 
// and passing in a record of type HttpServiceConfigSslSniCertInfo. This incantation creates the "normal" TLS settings on the SNI.

// Step 2: If we need to configure more advanced TLS settings on the SNI, this is done by invoking 
// HttpSetServiceConfiguration and passing in a record of type HttpServiceConfigSslSniCertInfoEx.

// In the below example, let's configure the "normal" TLS settings on foo.com and then configure advanced TLS settings such as disabling Diffie-Helman KeyExchangeAlgorithm and disabling the RSA_PSS signature algorithm on the foo.com SNI.

// Step 1: Configure "normal" TLS settings on foo.com.

// The HTTP.sys SNI config. We are going to stuff all the TLS settings into this config.
HTTP_SERVICE_CONFIG_SSL_SNI_SET config = {};

// Let's set Host in the HTTP.sys SNI config.
PCWSTR Host = L"foo.com";
config.KeyDesc.Host = (PWSTR)Host;

SOCKADDR_STORAGE addr = {};
SOCKADDR_IN* inAddr = (SOCKADDR_IN*)&addr;
inAddr->sin_port = htons(443);
inAddr->sin_family = AF_INET;
inAddr->sin_addr.S_un.S_addr = 0; // for HTTP.sys SNI configuration, address must be 0.

// Let's set the IP and port in the HTTP.sys SNI config.
config.KeyDesc.IpPort = addr;

// Let's set TLS flags in the HTTP.sys SNI config.
// There are various TLS settings that can be configured here. Please see lines 2491- 2509 of http.h for a full list of flags in the additional context section of this github issue.
// In this case, we are turning on client certificate negotiation and turning off QUIC.

config.ParamDesc.DefaultFlags = HTTP_SERVICE_CONFIG_SSL_FLAG_NEGOTIATE_CLIENT_CERT | HTTP_SERVICE_CONFIG_SSL_FLAG_DISABLE_QUIC;

// Let's set the thumbprint of foo.com and the certificate store in the HTTP.sys SNI config.
char thumbprint[] = { 0x98, 0x99, ... }; // Let's assume this array is filled out properly and contains a legitimate thumbprint.
config.ParamDesc.pSslHash = thumbprint;
config.ParamDesc.pSslCertStoreName = (PWSTR)L"MY";

// Finally, let's call HTTP.sys and pass in the config for foo.com.  Step 1 is complete!
HttpSetServiceConfiguration(NULL, HttpServiceConfigSslSniCertInfo, &config, sizeof(config), NULL);

// Step 2: Let's configure the advanced (extended) TLS settings on foo.com.

// The HTTP.sys extended SNI config.
HTTP_SERVICE_CONFIG_SSL_SNI_SET_EX configEx = {};

// Set Host and IP, port.
configEx.KeyDesc.Host = (PWSTR)Host;
configEx.KeyDesc.IpPort = addr;

// Disable Diffie-Helman Key Exchange  
CRYPTO_SETTINGS settings[2] = {};
settings[0].eAlgorithmUsage = TlsParametersCngAlgUsageKeyExchange;
UNICODE_STRING dhString;
RtlInitUnicodeString(&dhString, L"DH");
settings[0].strCngAlgId = dhString;

// Disable RSA-PSS signature algorithm.
settings[1].eAlgorithmUsage = TlsParametersCngAlgUsageCertSig;
UNICODE_STRING rsapssString;
RtlInitUnicodeString(&rsapssString, L"SCH_RSA_PSS_PAD");
settings[1].strCngAlgId = rsapssString;

TLS_PARAMETERS params[1] = {};
params[0].pDisabledCrypto = settings;
params[0].cDisabledCrypto = 2;

HTTP_TLS_RESTRICTIONS_PARAM restrictions = {};
restrictions.RestrictionCount = 1;
restrictions.TlsRestrictions = params;

configEx.ParamDesc.ParamType = ExParamTypeTlsRestrictions;
configEx.ParamDesc.HttpTlsRestrictionsParam = restrictions;

// Call HTTP.sys again and pass in the extended config for foo.com. Step 2 is complete!
HttpSetServiceConfiguration(NULL, HttpServiceConfigSslSniCertInfoEx, &configEx, sizeof(configEx), NULL);

Additional context

I'm aware that Kestrel has a SslServerAuthenticationOptions.CipherSuitesPolicy. However, this throws a PlatformNotSupported Exception on Windows.

Linking useful documentation that make this feature possible:

  1. Windows: https://learn.microsoft.com/en-us/windows/win32/api/schannel/ns-schannel-crypto_settings (This is what the above HTTP.sys code leverages to configure signature algorithms and cipher suites.)
  2. Linux: https://docs.openssl.org/3.0/man3/SSL_CTX_set1_sigalgs/. See: SL_CTX_set1_client_sigalgs()

snippet from http.h for the various flags that can be used while configuring TLS settings:
Image

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Oct 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Projects
None yet
Development

No branches or pull requests

1 participant