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

[HTTP/3] Suppress LocalCertificateSelectionCallback when QUIC is used #56094

Closed
wants to merge 1 commit into from

Conversation

ManickaP
Copy link
Member

Fixes #56090

@ghost
Copy link

ghost commented Jul 21, 2021

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

Issue Details

Fixes #56090

Author: ManickaP
Assignees: -
Labels:

area-System.Net.Http

Milestone: -

@@ -50,6 +50,7 @@ protected static HttpClientHandler CreateHttpClientHandler(Version useVersion =
{
SocketsHttpHandler socketsHttpHandler = (SocketsHttpHandler)GetUnderlyingSocketsHttpHandler(handler);
SetQuicImplementationProvider(socketsHttpHandler, quicImplementationProvider);
socketsHttpHandler.SslOptions.LocalCertificateSelectionCallback = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This defaults to null. What's setting it to non-null?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIU: HttpClientHandler.ClientCertificateOptions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it gets set in default ctor of client handler:

ClientCertificateOptions = ClientCertificateOption.Manual;

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks. What happens when this isn't nulled out? If our QUIC implementation doesn't support a non-null LocalCertificateSelectionCallback, doesn't this mean we're making our tests pass even though all normal usage will fail?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before #55877 LocalCertificateSelectionCallback was just ignored silently, so I guess if something weird was passed in the callback we didn't catch it, if that's what you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally think that reverting might be safer because it's possible there were other things like this implicit LocalCertificateSelectionCallback... 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into the code, all this hidden HttpClientHandler LocalCertificateSelectionCallback does is selects a cert from ClientCertificates collection based on having private key and checking its extensions. And that first part is what we do in msquic (selecting the one with private key). So despite ignoring the callback, it might still work as excepted in most cases. But @wfurt would have to chime in here for more details, this the extent of my knowledge with certs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HttpClientHandler callback:

internal static X509Certificate2? GetEligibleClientCertificate(X509Certificate2Collection candidateCerts)
{
if (candidateCerts.Count == 0)
{
return null;
}
foreach (X509Certificate2 cert in candidateCerts)
{
if (!cert.HasPrivateKey)
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(candidateCerts, $"Skipping current X509Certificate2 {cert.GetHashCode()} since it doesn't have private key. Certificate Subject: {cert.Subject}, Thumbprint: {cert.Thumbprint}.");
}
continue;
}
if (IsValidClientCertificate(cert))
{
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(candidateCerts, $"Choosing X509Certificate2 {cert.GetHashCode()} as the Client Certificate. Certificate Subject: {cert.Subject}, Thumbprint: {cert.Thumbprint}.");
}
return cert;
}
}
if (NetEventSource.Log.IsEnabled())
{
NetEventSource.Info(candidateCerts, "No eligible client certificate found.");
}
return null;
}

S.N.Quic code:

if (options.ClientAuthenticationOptions.ClientCertificates != null)
{
foreach (var cert in options.ClientAuthenticationOptions.ClientCertificates)
{
try
{
if (((X509Certificate2)cert).HasPrivateKey)
{
// Pick first certificate with private key.
certificate = cert;
break;
}
}
catch { }
}
}

It's not the same thing, but there are some some similarities...

Copy link
Member Author

@ManickaP ManickaP Jul 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But @stephentoub is right, new HttpClient + H/3 request will crash anyway. My hack mitigates this only in our tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reverting #56097 was the right choice. Sorry for the mess, I'm not sure how I missed it. The problem with LocalCertificateSelectionCallback is that it is not supported by MsQuic. so We either need to go back to ignoring it or fix the tests.

@ManickaP
Copy link
Member Author

This still will need follow up in our code, since HttpClientHandler uses LocalCertificateSelectionCallback internally in ClientCertificateOptions:

public ClientCertificateOption ClientCertificateOptions
{
get => _clientCertificateOptions;
set
{
switch (value)
{
case ClientCertificateOption.Manual:
#if TARGET_BROWSER
_clientCertificateOptions = value;
#else
ThrowForModifiedManagedSslOptionsIfStarted();
_clientCertificateOptions = value;
_underlyingHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate(ClientCertificates)!;
#endif
break;
case ClientCertificateOption.Automatic:
#if TARGET_BROWSER
_clientCertificateOptions = value;
#else
ThrowForModifiedManagedSslOptionsIfStarted();
_clientCertificateOptions = value;
_underlyingHandler.SslOptions.LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, acceptableIssuers) => CertificateHelper.GetEligibleClientCertificate()!;
#endif
break;
default:
throw new ArgumentOutOfRangeException(nameof(value));
}
}
}

Thus it gets sets indirectly and user might not have a clue what set off the check.

@wfurt Do you have any ideas how to solve this somewhat nicely?

@ghost
Copy link

ghost commented Jul 21, 2021

Hello @ManickaP!

Because this pull request has the auto-merge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

Do note that I've been instructed to only help merge pull requests of this repository that have been opened for at least 10 minutes, a condition that will be fulfilled in about 94 seconds. No worries though, I will be back when the time is right! 😉

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@karelz
Copy link
Member

karelz commented Jul 21, 2021

Closing in favor of revert PR #56097, which has been merged -- see above discussion.

@karelz karelz closed this Jul 21, 2021
@ManickaP ManickaP deleted the mapichov/56090_fix branch July 22, 2021 09:11
@karelz karelz added this to the 6.0.0 milestone Jul 22, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[HTTP/3] MsQuic tests failed massively
5 participants