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

Unable to get acceptableIssuers from LocalCertificateSelectionCallback #52499

Closed
denisvasilik opened this issue May 8, 2021 · 11 comments · Fixed by #63200
Closed

Unable to get acceptableIssuers from LocalCertificateSelectionCallback #52499

denisvasilik opened this issue May 8, 2021 · 11 comments · Fixed by #63200
Assignees
Labels
area-System.Net.Security bug os-linux Linux OS (any supported distro)
Milestone

Comments

@denisvasilik
Copy link

denisvasilik commented May 8, 2021

I'm using a mTLS setup and wanted to get a list of acceptableIssuers from the LocalCertificateSelectionCallback at the client application. This works great on Windows, but fails on Ubuntu 20.04. Here is a sample application I used for reproduction and a snippet of the relevant location:

private static X509Certificate SelectClientCertificate(
    object sender,
    string targetHost,
    X509CertificateCollection localCertificates,
    X509Certificate remoteCertificate,
    string[] acceptableIssuers)
{
    //
    // * Is only called once when running on Linux and does *not* provide
    //   acceptable issuers.
    //
    // * Is called twice when running on Windows and does provide
    //   acceptable issuers.
    //
    return localCertificates[0];
}

Configuration

Working configuration:

  • Window 10
  • .NET 5.0.200

Errornous configuration:

  • Ubuntu 20.04
  • .NET 5.0.201

Quick Analysis

During debugging I figured out that on Windows the method InitializeSecurityContext returns SecurityStatusPalErrorCode.CredentialsNeeded (when appropriate). As a consequence, the LocalCertificateSelectionCallback is called a second time with proper content of acceptable issuers. When looking at the InitializeSecurityContext or HandshakeInternal routine on Linux, it never returns SecurityStatusPalErrorCode.CredentialsNeeded. Instead it returns SecurityStatusPalErrorCode.ContinueNeeded which does not trigger LocalCertificateSelectionCallback. Hence, there's no second invocation of LocalCertificateSelectionCallback providing the acceptable issuers.

If this is a bug (and not a configuration issue) I would like to work on it in order to provide a fix.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Net.Security untriaged New issue has not been triaged by the area owner labels May 8, 2021
@ghost
Copy link

ghost commented May 8, 2021

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

Issue Details

I'm using a mTLS setup and wanted to get a list of acceptableIssuers from the LocalCertificateSelectionCallback at the client application. This works great on Windows, but fails on Ubuntu 20.04. Here is a sample application I used for reproduction and a snippet of the relevant location:

private static X509Certificate SelectClientCertificate(
    object sender,
    string targetHost,
    X509CertificateCollection localCertificates,
    X509Certificate remoteCertificate,
    string[] acceptableIssuers)
{
    //
    // * Is only called once when running on Linux and does *not* provide
    //   acceptable issuers.
    //
    // * Is called twice when running on Windows and does provide
    //   acceptable issuers.
    //
    return localCertificates[0];
}

Configuration

Working configuration:

  • Window 10
  • .NET 5.0.200

Errornous configuration:

  • Ubuntu 20.04
  • .NET 5.0.201

Quick Analysis

During debugging I figured out that on Windows the method InitializeSecurityContext returns SecurityStatusPalErrorCode.CredentialsNeeded (when appropriate). As a consequence, the LocalCertificateSelectionCallback is called a second time with proper content of acceptable issuers. When looking at the InitializeSecurityContext or HandshakeInternal routine on Linux, it never returns SecurityStatusPalErrorCode.CredentialsNeeded. Instead it returns SecurityStatusPalErrorCode.ContinueNeeded which does not trigger LocalCertificateSelectionCallback. Hence, there's no second invocation of LocalCertificateSelectionCallback providing the acceptable issuers.

If this is a bug (and not a configuration issue) I would like to work on it in order to provide a fix.

Author: denisvasilik
Assignees: -
Labels:

area-System.Net.Security, untriaged

Milestone: -

@wfurt
Copy link
Member

wfurt commented May 9, 2021

What site are you connecting to @denisvasilik ? Does your peer provide list of CAs? Perhaps you can post packet capture of the handshake.

@denisvasilik
Copy link
Author

For testing purposes I am trying to connect to mail.denisvasilik.com. In this case the server provides a Let's Encrypt server certificate and accepts a private PKI client certificate issued by denisvasilik-sa-root1-client1 or denisvasilik-ca-root1.

Sample application output on Windows

CN=denisvasilik-sa-root1-client1, O=denisvasilik, L=Munich, S=Bavaria, C=DE
CN=denisvasilik-ca-root1, O=denisvasilik, L=Munich, S=Bavaria, C=DE
Exception: No credentials are available in the security package

Note: I provided a dummy client certificate and key in the repository so it's not possible to finish the handshake successfully, but it's enough to retrieve the acceptable issuers.

Sample application output on Linux

Exception: No credentials are available in the security package

Here are no acceptableIssuers are provided.

I added a trace of the TLS handshake to the repro repository.

Thank you for your support, if you need further information just let me know.

@wfurt
Copy link
Member

wfurt commented May 10, 2021

I check and it seems like the server sends two names:

Distinguished Name: (id-at-commonName=denisvasilik-sa-root1-client1,id-at-organizationName=denisvasilik,id-at-localityName=Munich,id-at-stateOrProvinceName=Bavaria,id-at-countryName=DE)
Distinguished Name: (id-at-commonName=denisvasilik-ca-root1,id-at-organizationName=denisvasilik,id-at-localityName=Munich,id-at-stateOrProvinceName=Bavaria,id-at-countryName=DE)

This will need some deeper investigation. The mechanism on Linux is probably different. We will probably need to call SSL_get0_peer_CA_list() (or equivalent) to get it.

@wfurt wfurt added bug os-linux Linux OS (any supported distro) labels May 10, 2021
@karelz karelz added this to the Future milestone May 11, 2021
@karelz karelz removed the untriaged New issue has not been triaged by the area owner label May 11, 2021
@karelz
Copy link
Member

karelz commented May 11, 2021

Triage: Rare scenario, likely won't happen in 6.0. We should take a look later though, add a test and fix it.

@wfurt
Copy link
Member

wfurt commented May 11, 2021

When #45456 is done, we will be able to write tests for this. (without external dependency)
We may do the get/set CA list together.

@denisvasilik
Copy link
Author

Sounds great to me, I am looking forward working together on this issue.

@wfurt wfurt self-assigned this Oct 5, 2021
@wfurt wfurt modified the milestones: Future, 7.0.0 Oct 5, 2021
@wfurt
Copy link
Member

wfurt commented Oct 5, 2021

The fundamental problem is that the callback runs before the server sends the list. When I run the repro, remoteCertificate is also null and it should not be.

From archeology prospective @bartonjs started with dotnet/corefx#3736 back then in 1.0.
Then it was fixed with dotnet/corefx@d3be6bb for HTTP. (and probably not SslStream) and then eventually removed as dead code in #43793 when CurlHandler was removed.

To make it work, we will need to bring back CryptoNative_SslCtxSetClientCertCallback. (or something similar)
The fact that remoteCertificate is somewhat more concerning.

@JulesRenz
Copy link

JulesRenz commented Nov 24, 2021

Hi, I am also currently encountering this issue on Linux and OSX (works fine on windows). However, I am running .NET Core 3.1. (customer requires compliance with .NET Standard 2.1) is there a way to backport this fix onto 3.1 once it is fixed?

Many thanks in advance!
JR

@karelz
Copy link
Member

karelz commented Nov 24, 2021

All .NET 3.1+ versions are compliant with .NET Standard 2.1. Why exactly does it force you to use .NET Core 3.1 and not something newer - e.g. .NET 6 which is also LTS?

@JulesRenz
Copy link

JulesRenz commented Nov 24, 2021

Hi, Thanks for your comment! You are of course right, I was under the illusion, that we would need to stick with 3.1, but now, that you mentioned it, I fail to recall my thought process. Sorry for the inconvenience, I'm looking forward to this fix!

Edit: assuming I'll use .NET 6.0 - will this fix be present there (the current milestone is indicating 7.0.0)

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Dec 29, 2021
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jan 12, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Feb 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net.Security bug os-linux Linux OS (any supported distro)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants