.NET 10 client-certificate behavior when all explicit certificate sources are null

Manica Moscon Advogados 0 Reputation points
2026-10-27T14:50:05.2+00:00

We are evaluating a Windows x64 HTTPS client using .NET 10 LTS, HttpClient, and a newly created, exclusive SocketsHttpHandler with dedicated SslClientAuthenticationOptions. This is a proposed configuration, not a report of an installed or tested runtime.

Before the first request, the proposed options are:

ClientCertificates = null

LocalCertificateSelectionCallback = null (no custom delegate)

ClientCertificateContext = null

The handler and options would not be shared with or copied from an existing client. Application code would not load or import client certificates or query certificate stores. Normal server-certificate validation would remain unchanged.

Does this configuration guarantee that no client certificate is presented, including when the server sends a TLS CertificateRequest? If not, under what conditions could automatic selection or provider fallback supply one?

Please cite a primary, versioned contract or, alternatively, versioned implementation evidence linking these options through client-certificate selection to the Windows TLS credential. Please specify applicable .NET 10 servicing builds, Windows limitations, TLS 1.2/1.3 paths where supported, and the SocketsHttpHandler-to-SslStream relationship.

We do not assume that a null collection equals an empty collection, or that no callback equals a callback returning null. Please identify any essential behavioral distinction. Our question concerns certificate presentation, not a guarantee of zero certificate-store lookups.

The guide we reviewed, TLS/SSL best practices, lists the three input mechanisms but does not state whether their joint absence guarantees that no certificate is presented.

Developer technologies | .NET | .NET Runtime

4 answers

Sort by: Most helpful
  1. Nguyen Dam (WICLOUD CORPORATION) 1,010 Reputation points Microsoft External Staff Moderator
    2026-10-28T04:13:15.1933333+00:00

    Hello @Manica Moscon Advogados and thank you for your questions.

    For the first part of your question, there is no absolute guarantee in practice, even though the intended behavior is that no client certificate should be supplied under this configuration.
    For example, this issue reports that SslStream could reuse a cached Windows TLS credential carrying a client certificate from another connection in the same process. This appears to be an implementation bug rather than ordinary automatic selection or certificate-store fallback. 

    For the second part of your question, I regret to say that there does not appear to be a public, versioned contract addressing the exact behavior you described. 

    The implementation evidence you are requesting would be extremely difficult to document, let alone trace in full. Someone would need to trace the implementation from SocketsHttpHandler through SslStream, managed client-certificate selection, and Windows Schannel credential acquisition for each relevant .NET 10 servicing build, while separately accounting for Windows versions, servicing changes, TLS paths, and known implementation defects. 

    I am also afraid that tracing the versioned implementation may not establish what you need. Versioned source can show how a particular runtime build is implemented, but not necessarily whether that behavior is guaranteed and so forth. 
    For the most part, some reliance on Microsoft’s published documentation is necessary when determining the supported and intended behavior. If the runtime does not behave as intended, the practical option would generally be to reproduce the issue and report it to the .NET runtime developers. Internal design decisions and servicing guarantees can ultimately only be confirmed by the product team. 

    If you have any further questions, please feel free to let me know. If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation. Thank you. 

    Was this answer helpful?

    2 people found this answer helpful.

  2. AI answer

    2026-10-27T14:51:55.08+00:00

    The available public contracts do not establish that these three null values guarantee that no client certificate is presented, nor do they establish that Schannel will automatically select one; a security guarantee requires version-pinned .NET 10 implementation evidence that is not present in the cited material.

    What is established

    • SocketsHttpHandler.SslOptions is an SslClientAuthenticationOptions instance containing the client TLS settings.^1^
    • SslClientAuthenticationOptions is the property bag used by SslStream client authentication, and its documentation explicitly identifies SocketsHttpHandler as a consumer.^2^
    • ClientCertificates is nullable and identifies certificates “to be considered” for client authentication. The contract does not define whether null and an empty collection produce different Windows credentials.^3^
    • The documented certificate-supply mechanisms are ClientCertificates, LocalCertificateSelectionCallback, and ClientCertificateContext; the latter has been available since .NET 8.^4^
    • A selection callback chooses a local certificate and has a nullable return type. Its API contract does not say that returning null prohibits subsequent runtime or provider selection.^5^

    Consequently, neither of these equivalences is documented for .NET 10:

    ClientCertificates == null  ≡  ClientCertificates.Count == 0
    callback == null            ≡  callback present and returning null
    

    The older .NET Framework selection documentation says that an empty associated collection results in anonymous credentials, but it is explicitly a .NET Framework selection model—not a versioned .NET 10 SocketsHttpHandler/SslStream contract—so it cannot close this question.^6^

    Automatic selection and Windows fallback

    HttpClientHandler.ClientCertificateOptions = Automatic is a documented store-selection mode, while its default is Manual.^7^ That mode does not directly answer the proposed case because it uses an exclusive SocketsHttpHandler, not HttpClientHandler, and no corresponding automatic-selection option is established here.

    Schannel supports credentials supplied by the caller or preexisting credentials, and a client authentication certificate is optional unless requested by the server.^8^^9^ Those statements alone do not prove that .NET 10 requests automatic store selection when the three managed inputs are null. The missing evidence is:

    1. The exact .NET 10 servicing tag or commit.
    2. The SocketsHttpHandler HTTPS connection path into SslStream.
    3. Certificate-selection handling for all three absent inputs.
    4. The SCH_CREDENTIALS/SCHANNEL_CRED fields and flags passed to AcquireCredentialsHandle.
    5. Whether those flags permit provider/store selection after no managed certificate was selected.
    6. Separate handling, if any, for TLS 1.2 and TLS 1.3.

    Without that chain, no concrete fallback condition—or suppression guarantee—can be stated safely.

    Version and platform scope

    No exact .NET 10 servicing build can be named from these materials. .NET servicing updates are cumulative in support terms, and only the current servicing update remains supported after the next one ships; this does not prove invariant certificate-selection behavior across all 10.0 patches.^10^

    On Windows, .NET TLS behavior is mediated through OS cryptographic APIs such as Schannel.^11^ TLS 1.2 support depends on the Windows version and configuration. Schannel TLS 1.3 is supported only beginning with Windows 11 and Windows Server 2022; enabling it on earlier Windows versions is not supported.^12^

    How to validate the proposed deployment

    Treat this as an implementation-validation requirement rather than relying on the null settings as a contractual prohibition:

    1. Record the exact runtime using dotnet --info and the Windows build.
    2. Test against a server that sends CertificateRequest, separately under TLS 1.2 and—on Windows 11/Server 2022 or later—TLS 1.3.
    3. Capture the handshake with Wireshark or equivalent; Microsoft recommends wire inspection for determining which certificates were exchanged.^11^
    4. Verify that the client’s Certificate message contains an empty certificate list rather than a certificate chain. For TLS 1.3, decrypting the handshake may be necessary to inspect messages after ServerHello.
    5. Repeat with suitable certificates installed in each store relevant to the process identity. This tests presentation behavior; it does not prove that no store lookup occurred.
    6. Re-run the test for each .NET servicing update unless Microsoft publishes a contract guaranteeing this behavior.

    A callback returning null may be included as defense-in-depth, but the cited .NET 10 delegate contract is insufficient to describe it as a guaranteed prohibition against all Schannel fallback.


    References

    1. SocketsHttpHandler.SslOptions Property-Definition
    2. SslClientAuthenticationOptions Class-Definition
    3. SslClientAuthenticationOptions.ClientCertificates Property-Definition
    4. TLS/SSL best practices
    5. LocalCertificateSelectionCallback Delegate-Definition
    6. Certificate Selection and Validation
    7. HttpClientHandler.ClientCertificateOptions Property-Definition
    8. AcquireCredentialsHandle (Schannel) function
    9. Obtaining Schannel Credentials
    10. Releases and support for .NET
    11. Troubleshoot SslStream authentication issues
    12. Protocols in TLS/SSL (Schannel SSP)
    AI-generated content may be incorrect. Read our transparency notes for more information.

    Was this answer helpful?

    1 person found this answer helpful.
  3. Paul Thomas 80 Reputation points
    2026-10-30T06:26:11.2033333+00:00

    You asked for versioned implementation evidence, so I traced the path in the dotnet/runtime source on the release/10.0 branch (commit 18874f0d9047ec03fdce1fdc32a7e168e2bc4735). In short, the design intent is clear and the code enforces it: with all three sources null, SslStream selects no certificate, and on Windows it tells Schannel not to pick one on its own either. There is one known Windows bug that breaks this, and its fix is not yet in a released .NET 10 build.

    1. SocketsHttpHandler to SslStream

    SocketsHttpHandler passes its SslOptions (an SslClientAuthenticationOptions) to SslStream.AuthenticateAsClientAsync. It makes a shallow clone first and adds no certificate source of its own (ConnectHelper.cs, line 43).

    The "automatic" store selection you may have read about belongs to HttpClientHandler, not to SocketsHttpHandler. When HttpClientHandler.ClientCertificateOptions = ClientCertificateOption.Automatic, it installs a LocalCertificateSelectionCallback that picks an eligible certificate from the user's store (HttpClientHandler.cs, lines 275–282). A directly created SocketsHttpHandler doesn't do that.

    2. Managed certificate selection

    SslStream.SelectClientCertificate() (SslStream.Protocol.cs, line 319) only has these sources, checked in order:

    • ClientCertificateContext, if it isn't null
    • LocalCertificateSelectionCallback, if it isn't null
    • ClientCertificates, only when it isn't null and Count > 0

    There is no other branch. It doesn't fall back to the certificate store. With all three null, it returns null.

    On your null-versus-empty questions:

    • ClientCertificates = null vs. an empty collection: with no callback, both give the same result, because every branch that reads the collection requires Count > 0.
    • No callback vs. a callback that returns null: both result in no certificate. One side effect to know about is that when a callback is set, SslStream replaces a null ClientCertificates with an empty collection before it invokes the callback (_sslAuthenticationOptions.ClientCertificates ??= new X509CertificateCollection()).

    3. The Windows Schannel credential

    With no selected certificate, AcquireClientCredentials (line 575) acquires an outbound credential with no certificate. Both Windows credential paths set SCH_CRED_NO_DEFAULT_CREDS for clients:

    • SslStreamPal.Windows.cs, lines 305–310: the SCHANNEL_CRED path (TLS 1.2 and below)
    • lines 392–397: the SCH_CREDENTIALS path, which supports TLS 1.3

    The Windows documentation for SCH_CRED_NO_DEFAULT_CREDS says it prevents Schannel from attempting to automatically supply a certificate chain for client authentication. SCH_CREDENTIALS (and so TLS 1.3) is used on Windows builds 18836 and later, except with EncryptionPolicy.NoEncryption (lines 24–26 and 237–239). Older builds use SCHANNEL_CRED. Either way, the flag is set on both TLS 1.2 and TLS 1.3 paths.

    4. The exception: a known credential-cache bug on Windows

    The moderator's link is the relevant caveat: dotnet/runtime#132563. On Windows, a client with none of the three sources set could send the certificate that a different SslStream in the same process had used through ClientCertificateContext (or a certificate chain). The cause was the shared Schannel credential cache.

    • It was fixed in PR #132079, which was merged into main (.NET 11) on 2026-08-12.
    • The reporter confirmed that it still occurs on 10.0.11, 9.0.19 and 8.0.30, on both TLS 1.2 and TLS 1.3.
    • The .NET 10 backport (PR #136622, tracked in #136532) is still open today, and the release/10.0 branch doesn't contain the fix yet.

    The precondition is another SslStream in the same process that presented a certificate. Your design loads no certificates, so your code never creates that condition. But a library or another component in the same process could. If that matters for your guarantee, either run this client in a process that never does mutual TLS, or wait for a .NET 10 servicing release that includes PR #136622. Watch that PR to see which release it ships in.

    5. Verifying it on your build

    Because this is implementation behavior, not a documented contract, test it on the exact runtime you ship. Run a test server that requests but doesn't require a client certificate (for example, Kestrel with ClientCertificateMode.AllowCertificate), and log whether a certificate arrives (HttpContext.Connection.ClientCertificate). Do this for TLS 1.2 and TLS 1.3, and in a process that also performs a ClientCertificateContext handshake first, to cover the bug above.

    Note that I traced this in source and haven't run it on Windows. The file and line references are to that commit, so they stay valid when the branch moves.

    If this answers your question, please consider marking it as the accepted answer so others with the same requirement can find it.

    Was this answer helpful?

    0 comments No comments

  4. Deleted

    This answer has been deleted due to a violation of our Code of Conduct. The answer was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.


    Comments have been turned off. Learn more

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.