Skip to content

TlsSession: send the configured client certificate on macOS - #136214

Merged
wfurt merged 3 commits into
dotnet:mainfrom
pcshrosbree:fix/tlssession-osx-configured-client-certificate
Oct 26, 2026
Merged

wfurt merged 3 commits into
dotnet:mainfrom
pcshrosbree:fix/tlssession-osx-configured-client-certificate

Conversation

@pcshrosbree

Copy link
Copy Markdown
Contributor

TL;DR: On macOS, a TlsSession client did not send its client certificate when the server asked for one. This PR makes TlsSession install the certificate the way SslStream already does. It also adds a test and runs two existing tests on macOS that were skipped for a wrong reason. The product change is 27 lines in one method.

Fixes #136213.

On macOS, SecureTransport stops the handshake at the server's CertificateRequest, and the PAL reports CredentialsNeeded. This happens even when a client certificate is configured. SslStream installs the certificate with SslStreamPal.TryUpdateClintCertificate and continues. TlsSession returned CertificateRequested to the caller instead. After SetClientCertificateContext, it resumed without installing the certificate, so the server received none. #136213 has the details. This is also why the macOS legs of #136096 and #136205 timed out.

Change

TlsSession.HandshakeBufferedCore, client side:

  • On CredentialsNeeded with a configured certificate, install it with TryUpdateClintCertificate and call InitializeSecurityContext again. SslStream.GenerateToken does the same.
  • On the resume after SetClientCertificateContext, install the certificate before calling InitializeSecurityContext.
  • Other platforms: on Windows, TryUpdateClintCertificate returns false, so neither step changes anything. On Linux, the OpenSSL PAL already installs a configured certificate inside the handshake call, so the first step does not run. On resume, the certificate is now installed just before that call instead of inside it.

TlsSessionTests:

  • New: ClientSession_ConfiguredClientCertificate_SentWhenServerRequestsIt, on all platforms.
  • Now run on macOS: ClientSession_WantCredentials_SetClientCertificateContext_ResumesHandshake and SetClientCertificateContext_ConcurrentSessionsOnSharedContext_DoNotRace. Their skip reason said SecureTransport does not surface a deferred credential prompt, but it does. The TLS 1.3 row of the first test returns early where TLS 1.3 is unavailable, like the other theories in the file.
  • Driver: DriveHandshakeWithExternalValidationAsync now throws on a status it does not handle. A test like the one in System.Net.Security.Tests.TlsSessionTests fail with timeouts #136205 then fails within seconds instead of after the 60-second timeout.

Testing

I ran TlsSessionTests in Release on osx-arm64 (macOS 27.0.1) and linux-x64, pairing test and product versions as below. In the table, main is commit b3c0988064f5:

Test version Product version macOS Linux
Before #136139 main the 3 isServer: False cases of AcceptWithDefaultValidation_CertificateContextTrust_HonorsChainPolicy time out after 60 s pass
Before #136139 this PR pass pass
This PR main the new test fails with CertificateRequested; the 2 re-enabled tests fail because the server receives no certificate pass
This PR this PR pass pass

I removed each of the two install steps in turn. Each removal fails only the tests for its own path on macOS.

With this PR, the System.Net.Security.Tests run excluding the OuterLoop and failing categories has no failures on either platform: 5,035 passed and 27 skipped on macOS, and 5,071 passed and 12 skipped on Linux.

I have not run this on Windows; the Windows legs of this PR will.

Note

This change was prepared with AI assistance (Anthropic Claude, checked by OpenAI Codex) at my request. I reviewed the change and the test runs before opening.

On macOS the PAL does not install a client certificate before the
handshake. SecureTransport stops at the server's CertificateRequest and
the PAL reports CredentialsNeeded. SslStream answers that by installing
the certificate with TryUpdateClintCertificate and continuing.
TlsSession did not install the certificate in either of these cases:

- With a ClientCertificateContext on the TlsContext, it returned
  CertificateRequested to the caller.
- After SetClientCertificateContext, it resumed the handshake without
  installing the certificate, so the server received none.

TlsSession now installs the configured certificate in both cases. On
Windows TryUpdateClintCertificate does nothing, and SChannel already
has the certificate. On Linux the OpenSSL PAL already installs it
inside the handshake call; on resume TlsSession now installs it just
before that call, with the same result.

Tests:

- New: a configured client certificate reaches a server that requires
  one.
- Two SetClientCertificateContext tests now run on macOS. Their skip
  reason said SecureTransport never asks for a deferred credential, but
  it does.
- The handshake driver now fails on a status it does not handle,
  instead of waiting for the 60-second timeout.

Found through dotnet#136205: the macOS legs timed out in
AcceptWithDefaultValidation_CertificateContextTrust_HonorsChainPolicy
until dotnet#136139 stopped that test's server from requesting a client
certificate.
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Oct 21, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Security-sensitive cross-platform handshake changes warrant maintainer review and confirmation of Windows validation; builds and tests were not independently run here.

No open issues 🎉

What changed in this PR

Fixes macOS TlsSession client-certificate delivery by following the existing SslStream installation flow.

Changes:

  • Installs configured and deferred client certificates before continuing the handshake.
  • Adds regression coverage and enables two existing tests on macOS.
  • Makes the test handshake driver fail immediately on unexpected statuses.
File Description
src/​libraries/​System.Net.Security/​tests/​FunctionalTests/​TlsSessionTests.cs Adds certificate-delivery coverage, enables macOS tests, and improves failure reporting.
src/​libraries/​System.Net.Security/​src/​System/​Net/​Security/​TlsSession.cs Installs client certificates during initial and resumed handshakes.

Review effort: Balanced

@rzikm rzikm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM in general, but I have couple of questions

The two TryUpdateClintCertificate blocks in the client handshake path
cover different cases. The comments now explain when each block runs.
The block before InitializeSecurityContext runs when the handshake
resumes after CertificateRequested, because SecureTransport does not
stop a second time. The block after InitializeSecurityContext runs when
the certificate was configured before the handshake, in which case
SecureTransport still stops.

Comments only; no code change.
The comment on each TryUpdateClintCertificate block now names its case
and says why the other block cannot cover it. On a resume after
CertificateRequested, SecureTransport does not stop again, so the
CredentialsNeeded path never runs. With a certificate configured before
the handshake, SecureTransport still stops, so the install follows that
stop.

Comments only; no code change.
@pcshrosbree

Copy link
Copy Markdown
Contributor Author

@rzikm, could you re-approve when you have a moment? The PR needs an approval on the latest commit, a23be97, and the only change since your approval is the two reworded comments in TlsSession.cs.

@wfurt

wfurt commented Oct 26, 2026

Copy link
Copy Markdown
Member

/ba-g test failures unrelated

@wfurt
wfurt merged commit 1fe42b5 into dotnet:main Oct 26, 2026
86 of 88 checks passed
@pcshrosbree

Copy link
Copy Markdown
Contributor Author

Thank you for the review and merge!

@pcshrosbree
pcshrosbree deleted the fix/tlssession-osx-configured-client-certificate branch October 26, 2026 23:04
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Oct 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Net.Security community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TlsSession client on macOS does not send its configured client certificate

4 participants