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

Developers using Kestrel can configure the list of CAs per-hostname #45456

Closed
Tracked by #44314
avparuch opened this issue Dec 2, 2020 · 11 comments
Closed
Tracked by #44314

Developers using Kestrel can configure the list of CAs per-hostname #45456

avparuch opened this issue Dec 2, 2020 · 11 comments
Assignees
Labels
area-System.Net.Security Bottom Up Work Not part of a theme, epic, or user story Cost:M Work that requires one engineer up to 2 weeks Priority:1 Work that is critical for the release, but we could probably ship without Team:Libraries User Story A single user-facing feature. Can be grouped under an epic.
Milestone

Comments

@avparuch
Copy link

avparuch commented Dec 2, 2020

AB#1253385
Our Service has the need to configure the certificate trust lists sent in the "Certificate Request" of the TLS handshake on a per-hostname basis. Currently, we configure this via Http.Sys APIs.

We want to move to Kestrel, however SSLStream does not currently support this feature per my understanding.

This issue was raised in #31097. # 1 and # 3 mentioned at the bottom of that issue were addressed in .NET 5.0. Unfortunately, # 2 was not addressed in .NET 5.0.

Let's go through the specific details/scenarios:

As a pre-requisite, the following registry key is set in order for the server to send Trusted Issuer List during TLS handshake. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\SendTrustedIssuerList is set to 1. Details : https://docs.microsoft.com/en-us/windows-server/security/tls/what-s-new-in-tls-ssl-schannel-ssp-overview

Scenario 1: Mutual Auth with no defined CTL store.

We configure a binding as follows:

netsh http add sslcert hostnameport=pas.windows.net:443 certhash=EB3C3B94F10E948463929BCF4C7000C1E7BD0AC1 appid='{4dc3e181-e14b-4a21-b022-59fc669b0914}' certstorename=MY clientcertnegotiation=enable verifyclientcertrevocation=disable.

To emphasize, I have not picked any particular CTL store for this binding.

When a request comes in for the hostname pas.windows.net : during TLS handshake, in the "Certificate Request", the "Distinguished Names" is populated based on the trusted root of the server. (by default, since I did not pick a particular CTL store in the binding).

Please look at frame 19 of the attached wireshark capture. (Transport Layer Security -> TLS 1.2 record layer ->HandShake Protocol: Certificate Request -> DistinguishedNames).

The relevant snippet from frame 19 is below. The snippet shows that "Distinguished Names" is populated from trusted root on the server.

packet19

Below snippet shows the trusted root store on the machine: the "Distinguished Names" above are populated from the trusted root shown below.

CTL1

Scenario 2: Mutual Auth: a defined CTL store with a non-empty list

We configure a binding as follows:

netsh http add sslcert hostnameport=device.login.microsoftonline.com:443 certhash=963B55D3E94101B70F1654FBF90D6006BAFAD513 appid='{4dc3e181-e14b-4a21-b022-59fc669b0914}' certstorename=MY clientcertnegotiation=enable verifyclientcertrevocation=disable sslctlstorename=DeviceLoginCTLStore

To emphasize, I have picked a particular sslctlstore (DeviceLoginCTLStore) for this binding.

When a request comes in for the hostname device.login.microsoftonline.com : during TLS handshake, in the "Certificate Request", the "Distinguished Names" list is populated from the CTL store specified in the binding (which is (DeviceLoginCTLStore)).

Please look at frame 43 of the attached wireshark capture. (Transport Layer Security -> TLS 1.2 record layer ->HandShake Protocol: Certificate Request -> DistinguishedNames).

The relevant snippet from frame 43 is below. You will see only one entry in the Distinguished Names because that particular store (DeviceLoginCTLStore) has only one entry.

packet43

Below snippet shows the DeviceLoginCTLStore on the server: the "Distinguished Names" above are populated from the DeviceLoginCTLStore below.

CTL2

Scenario 3: Mutual Auth: a defined CTL store with a empty list

We configure a binding as follows:

netsh http add sslcert hostnameport=v2.aaddc.activedirectory.windowsazure.com:443 certhash=963B55D3E94101B70F1654FBF90D6006BAFAD513 appid='{4dc3e181-e14b-4a21-b022-59fc669b0914}' certstorename=MY clientcertnegotiation=enable verifyclientcertrevocation=disable sslctlstorename=EmptyCTLStore

To emphasize, I have picked a particular sslctlstore (EmptyCTLStore) for this binding. EmptyCTLStore happens to be empty.

When a request comes in for the hostname v2.aaddc.activedirectory.windowsazure.com : during TLS handshake, in the "Certificate Request", the "Distinguished Names" list is populated from the CTL store specified in the binding (which is (EmptyCTLStore)).

Please look at frame 55 of the attached wireshark capture. (Transport Layer Security -> TLS 1.2 record layer ->HandShake Protocol: Certificate Request -> DistinguishedNames).

The relevant snippet from frame 55 is below. You will see zero entries in the Distinguished Names because that particular store (EmptyCTLStore) has no entries.

image

Below snippet shows the EmptyCTLStore on the server:

EmptyCTL

serverhello.zip

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-System.Net.Security untriaged New issue has not been triaged by the area owner labels Dec 2, 2020
@ghost
Copy link

ghost commented Dec 2, 2020

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Our Service has the need to configure the certificate trust lists sent in the "Certificate Request" of the TLS handshake on a per-hostname basis. Currently, we configure this via Http.Sys APIs.

We want to move to Kestrel, however SSLStream does not currently support this feature per my understanding.

This issue was raised in #31097. # 1 and # 3 mentioned at the bottom of this issue were addressed in .NET 5.0. Unfortunately, # 2 was not addressed in .NET 5.0.

Let's go through the specific details/scenarios:

As a pre-requisite, the following registry key is set in order for the server to send Trusted Issuer List during TLS handshake. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\SendTrustedIssuerList is set to 1. Details : https://docs.microsoft.com/en-us/windows-server/security/tls/what-s-new-in-tls-ssl-schannel-ssp-overview

Scenario 1: Mutual Auth with no defined CTL store.

We configure a binding as follows:

netsh http add sslcert hostnameport=pas.windows.net:443 certhash=EB3C3B94F10E948463929BCF4C7000C1E7BD0AC1 appid='{4dc3e181-e14b-4a21-b022-59fc669b0914}' certstorename=MY clientcertnegotiation=enable verifyclientcertrevocation=disable.

To emphasize, I have not picked any particular CTL store for this binding.

When a request comes in for the hostname pas.windows.net : during TLS handshake, in the "Certificate Request", the "Distinguished Names" is populated based on the trusted root of the server. (by default, since I did not pick a particular CTL store in the binding).

Please look at frame 19 of the attached wireshark capture. (Transport Layer Security -> TLS 1.2 record layer ->HandShake Protocol: Certificate Request -> DistinguishedNames). The relevant snippet from frame 19 is below. The snippet shows that "Distinguished Names" is populated from trusted root on the server.

packet19

Scenario 2: Mutual Auth: a defined CTL store with a non-empty list

We configure a binding as follows:

netsh http add sslcert hostnameport=device.login.microsoftonline.com:443 certhash=963B55D3E94101B70F1654FBF90D6006BAFAD513 appid='{4dc3e181-e14b-4a21-b022-59fc669b0914}' certstorename=MY clientcertnegotiation=enable verifyclientcertrevocation=disable sslctlstorename=DeviceLoginCTLStore

To emphasize, I have picked a particular sslctlstore (DeviceLoginCTLStore) for this binding.

When a request comes in for the hostname device.login.microsoftonline.com : during TLS handshake, in the "Certificate Request", the "Distinguished Names" list is populated from the CTL store specified in the binding (which is (DeviceLoginCTLStore)).

Please look at frame 43 of the attached wireshark capture. (Transport Layer Security -> TLS 1.2 record layer ->HandShake Protocol: Certificate Request -> DistinguishedNames). The relevant snippet from frame 43 is below. You will see only one entry in the Distinguished Names because that particular store (DeviceLoginCTLStore) has only one entry.

packet43

Scenario 3: Mutual Auth: a defined CTL store with a empty list

We configure a binding as follows:

netsh http add sslcert hostnameport=v2.aaddc.activedirectory.windowsazure.com:443 certhash=963B55D3E94101B70F1654FBF90D6006BAFAD513 appid='{4dc3e181-e14b-4a21-b022-59fc669b0914}' certstorename=MY clientcertnegotiation=enable verifyclientcertrevocation=disable sslctlstorename=EmptyCTLStore

To emphasize, I have picked a particular sslctlstore (EmptyCTLStore) for this binding. EmptyCTLStore happens to be empty.

When a request comes in for the hostname v2.aaddc.activedirectory.windowsazure.com : during TLS handshake, in the "Certificate Request", the "Distinguished Names" list is populated from the CTL store specified in the binding (which is (EmptyCTLStore)).

Please look at frame 55 of the attached wireshark capture. (Transport Layer Security -> TLS 1.2 record layer ->HandShake Protocol: Certificate Request -> DistinguishedNames). The relevant snippet from frame 55 is below. You will see zero entries in the Distinguished Names because that particular store has no entries.

image

serverhello.zip

Author: avparuch
Assignees: -
Labels:

area-System.Net.Security, untriaged

Milestone: -

@wfurt wfurt added the api-needs-work API needs work before it is approved, it is NOT ready for implementation label Dec 2, 2020
@karelz karelz added this to the 6.0.0 milestone Dec 2, 2020
@karelz karelz added Bottom Up Work Not part of a theme, epic, or user story and removed untriaged New issue has not been triaged by the area owner labels Dec 3, 2020
@halter73
Copy link
Member

halter73 commented Dec 7, 2020

netsh http commands configure IIS/HTTP.Sys but not Kestrel/SslStream. You can configure per-hostname SslServerAuthenticationOptions via the new .NET 5 UseHttps() overload that takes a ServerOptionsSelectionCallback.

Or, if you're using an older version of .NET Core and just need to configure the cert itself per-hostname, you can use HttpsConnectionAdapterOptions.ServerCertificateSelector.

@avparuch
Copy link
Author

avparuch commented Dec 7, 2020

netsh http commands configure IIS/HTTP.Sys but not Kestrel/SslStream. You can configure per-hostname SslServerAuthenticationOptions via the new .NET 5 UseHttps() overload that takes a ServerOptionsSelectionCallback.

Or, if you're using an older version of .NET Core and just need to configure the cert itself per-hostname, you can use HttpsConnectionAdapterOptions.ServerCertificateSelector.

netsh http commands were provided as an example of how they help configure Http.Sys, the ask is to provide a way to configure the same feature on Kestrel. The ServerOptionsSelectionCallback added in .NET 5.0 does not allow us to configure CTL stores per binding.

@wfurt
Copy link
Member

wfurt commented Dec 8, 2020

We will need to probably extent SslServerAuthenticationOptions to make certificate request more configurable.

@wfurt
Copy link
Member

wfurt commented Dec 9, 2020

It seems like this may be feasible.

While on Windows, this seems to be tightly linked to X509Store via hRootStore in SCH_CREDENTIALS structure (server only), OpenSSL and macOS have way how to influence this well.

SSL_CTX_set_client_CA_list and later SSL_CTX_set0_CA_list

void SSL_CTX_set0_CA_list(SSL_CTX *ctx, STACK_OF(X509_NAME) *name_list);
void SSL_set0_CA_list(SSL *s, STACK_OF(X509_NAME) *name_list);

macOS has SSLSetCertificateAuthorities

func SSLSetCertificateAuthorities(_ context: SSLContext, 
                                  _ certificateOrArray: CFTypeRef, 
                                  _ replaceExisting: Bool) -> OSStatus

While linkage to X5609Store seems limiting as one cannot store this in database or other source, I'm not sure if we can make schannel to take simple list. Since we can iterate X509Store outside of windows, we can come up with list expected by the API.

Since both X509Store and SslServerAuthenticationOptions are mutable, we would probably need to iterate for each SSL session.

To limit the expense, we may choose to link this somehow to SslStreamCertificateContext as that is meant to be re-used over many SSL session so we could pay the expense only once when created. Since this may not be used commonly perhaps avoiding SslServerAuthenticationOptions may be beneficial as well.

cc: @bartonjs @davidfowl @Tratcher for any additional thoughts.

@karelz karelz added the Cost:S Work that requires one engineer up to 1 week label Dec 10, 2020
@karelz karelz added Cost:M Work that requires one engineer up to 2 weeks Priority:1 Work that is critical for the release, but we could probably ship without Team:Libraries and removed Cost:S Work that requires one engineer up to 1 week labels Jan 4, 2021
@karelz karelz changed the title Enable per-hostname CTL stores in Kestrel. Kestrel can configure list of CAs per-hostname Jan 4, 2021
@karelz karelz added the User Story A single user-facing feature. Can be grouped under an epic. label Jan 4, 2021
@danmoseley danmoseley changed the title Kestrel can configure list of CAs per-hostname Developers using Kestrel can configure the list of CAs per-hostname Jan 20, 2021
@wfurt
Copy link
Member

wfurt commented Jan 28, 2021

It seems like this will not be feasible on current Windows.

After some digging, I discovered that Http.sys use SetCredentialsAttributes(SECPKG_ATTR_CLIENT_CERT_POLICY) and that call is available only in kernel mode and returns SEC_E_UNSUPPORTED_FUNCTION for us. There is alternative to specify custom CA trust. But that is additive e.g. feeding it with empty store would still behave like scenario 1.

It seems like coming Co Windows release will have mechanism to do this but I don't know if it make sense to make new feature that does not function on all existing Windows versions.
I also verified that the global system SendTrustedIssuerList registry is mandatory and there is no way how to control this via API. That makes the functionality hard to use IMHO. (there is no such limitation on other platforms AFAIK)

@dreijer
Copy link

dreijer commented Jun 14, 2021

Just piling on here: Supporting custom trusted issuers lists through Kestrel is a feature that we're looking very much forward to as well for a product we're trying to ship. Is there any ETA at this time on when this could potentially land?

@wfurt
Copy link
Member

wfurt commented Jun 14, 2021

What is your primary platform (if any) @dreijer? There are some technical difficulties and limits on Windows.

@dreijer
Copy link

dreijer commented Jun 15, 2021

What is your primary platform (if any) @dreijer? There are some technical difficulties and limits on Windows.

We're targeting Windows, but @avparuch, who filed the original issue, already has a proposed fix pending against Windows to address those limitations.

@wfurt
Copy link
Member

wfurt commented Jun 15, 2021

There is no real Windows fix. more just a hack. That will still not give option to control it programmatically or selectively and it will be bound to system store.

@karelz karelz removed the api-needs-work API needs work before it is approved, it is NOT ready for implementation label Jun 16, 2021
@wfurt
Copy link
Member

wfurt commented Jul 16, 2021

This should be available in preview7 on Windows. It currently depends on Windows 10 Insider preview.
Linux work is tracked by #55802 for post-6.0.

@wfurt wfurt closed this as completed Jul 16, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Aug 15, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net.Security Bottom Up Work Not part of a theme, epic, or user story Cost:M Work that requires one engineer up to 2 weeks Priority:1 Work that is critical for the release, but we could probably ship without Team:Libraries User Story A single user-facing feature. Can be grouped under an epic.
Projects
None yet
Development

No branches or pull requests

6 participants