GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — tls: fix authorized state on no-cert TLS1.3 client cert resumption by pimterry · Pull Request #64677 · nodejs/node

Conversation

@pimterry

@pimterry pimterry commented Jul 22, 2026

Copy link
Copy Markdown
Member

Previously if you used TLS 1.3 and the server requested a client cert, but the client didn't send one, and you used rejectUnauthorized: false (e.g. to manually verify state with some extra conditions) then the initial session would report authorized=false, but resuming the session later would report authorized=true.

This doesn't match TLS 1.2 behaviour (which correctly preserves authorization state & errors across resumption). It's not inevitable and it wasn't intentional - this is an artifact of our internal logic for handling PSKs in TLS 1.3 inside ncrypto (here) that wasn't properly validated on this side.

We now correctly report the authorization state and/or error from the original connection in all cases. This also adds a matrix test that fully checks the invariant over all combinations of TLS 1.2/1.3 + valid-cert/invalid-cert/no-cert: authorized state after resume should always match the initial state.

Fixes #35317 - note there is some debate in there (cc @bnoordhuis) but I think there's confusion in some of the descriptions and it's actually just a clear bug.

See also #64584 which was opened just now, to simply document the current state.

This PR is either a fix for the current state (in which case we could close #64584) or a semver-major change (in which case we can keep the document PR for now, and merge this to clean that up to change the behaviour as a major bump). Opinions from @nodejs/crypto would be helpful. This is arguably a security fix but it has been directly described in public in that issue as intended behaviour for nearly 6 years.

The scope is limited to rejectUnauthorized: false cases, which does imply a degree of taking your own responsibility for validation - but even if you enable that option we still shouldn't lie to you unnecessarily, as we do today.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@nodejs-github-bot

nodejs-github-bot commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/crypto
  • @nodejs/net
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot nodejs-github-bot added needs-ci PRs that need a full CI run. tls Issues and PRs related to the tls subsystem. labels Jul 22, 2026
@pimterry pimterry mentioned this pull request Jul 22, 2026
Reloading page bypasses TLS client authentication #35317
Closed
@pimterry
pimterry force-pushed the fix-tls-1.3-client-cert-resume branch from 005f950 to 8ec1d60 Compare July 22, 2026 14:59
@pimterry
tls: fix authorized state on no-cert TLS1.3 client cert resumption
428a444
Previously if you used TLS 1.3 and the server requested a client cert,
but the client didn't send one, and you used rejectUnauthorized:false
the resumed session would report authorized=true. This doesn't match TLS
1.2 behaviour or make any sense, and was purely an artifact of our
internal logic for handling TLS 1.3 resumption details. We now correctly
report the authorization state and/or error from the original connection
in all cases, with a matrix test that fully checks the invariant:
authorized state after resume should always match the initial state.

Signed-off-by: Tim Perry <[email protected]>
@pimterry
pimterry force-pushed the fix-tls-1.3-client-cert-resume branch from 8ec1d60 to 428a444 Compare July 22, 2026 17:13
@pimterry

pimterry commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Boringssl is an actual failure here... Seems TLS resumption works quite differently over there and so the tests stall. I'll dig into it tomorrow.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@codecov

codecov Bot commented Jul 22, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.13%. Comparing base (fbab458) to head (892a748).
⚠️ Report is 258 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #64677      +/-   ##
==========================================
- Coverage   90.15%   90.13%   -0.02%     
==========================================
  Files         741      741              
  Lines      242207   242119      -88     
  Branches    45606    45598       -8     
==========================================
- Hits       218354   218228     -126     
- Misses      15350    15388      +38     
  Partials     8503     8503              
Files with missing lines Coverage Δ
lib/internal/tls/wrap.js 95.12% <100.00%> (+0.01%) ⬆️

... and 37 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pimterry
Fix resumption tests on BoringSSL
892a748
jasnell
jasnell approved these changes Jul 23, 2026
View reviewed changes
@pimterry pimterry added commit-queue-squash PRs the Commit Queue should land as one squashed commit. request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. labels Jul 23, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Jul 23, 2026
@nodejs-github-bot

This comment was marked as outdated.

Sign in to view
@nodejs-github-bot

This comment was marked as outdated.

Sign in to view
@nodejs-github-bot

This comment was marked as outdated.

Sign in to view
@nodejs-github-bot

nodejs-github-bot commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/75086/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@trivikr trivikr assigned pimterry Aug 7, 2026
@trivikr

trivikr commented Aug 7, 2026

Copy link
Copy Markdown
Member

@pimterry Did you mean to add commit-queue label too?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pimterry pimterry added the commit-queue PRs queued for automated landing through the Commit Queue. label Aug 7, 2026
@pimterry

pimterry commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

Thanks for the reminder @trivikr! Lost this somewhere, added to the queue.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot nodejs-github-bot removed the commit-queue PRs queued for automated landing through the Commit Queue. label Aug 7, 2026
@panva panva added the commit-queue PRs queued for automated landing through the Commit Queue. label Aug 8, 2026
@nodejs-github-bot nodejs-github-bot removed the commit-queue PRs queued for automated landing through the Commit Queue. label Aug 8, 2026
@nodejs-github-bot
nodejs-github-bot merged commit c59cd6b into nodejs:main Aug 8, 2026
88 of 89 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot

nodejs-github-bot commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Landed in c59cd6b

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

aduh95 pushed a commit that referenced this pull request Aug 13, 2026
@pimterry @aduh95
tls: fix authorized state on no-cert TLS1.3 client cert resumption
52b2c73
Previously if you used TLS 1.3 and the server requested a client cert,
but the client didn't send one, and you used rejectUnauthorized:false
the resumed session would report authorized=true. This doesn't match TLS
1.2 behaviour or make any sense, and was purely an artifact of our
internal logic for handling TLS 1.3 resumption details. We now correctly
report the authorization state and/or error from the original connection
in all cases, with a matrix test that fully checks the invariant:
authorized state after resume should always match the initial state.

Signed-off-by: Tim Perry <[email protected]>
PR-URL: #64677
Reviewed-By: James M Snell <[email protected]>
aduh95 pushed a commit that referenced this pull request Aug 25, 2026
@pimterry @aduh95
tls: fix authorized state on no-cert TLS1.3 client cert resumption
15da4a1
Previously if you used TLS 1.3 and the server requested a client cert,
but the client didn't send one, and you used rejectUnauthorized:false
the resumed session would report authorized=true. This doesn't match TLS
1.2 behaviour or make any sense, and was purely an artifact of our
internal logic for handling TLS 1.3 resumption details. We now correctly
report the authorization state and/or error from the original connection
in all cases, with a matrix test that fully checks the invariant:
authorized state after resume should always match the initial state.

Signed-off-by: Tim Perry <[email protected]>
PR-URL: #64677
Reviewed-By: James M Snell <[email protected]>
@github-actions github-actions Bot mentioned this pull request Aug 25, 2026
2026-08-26, Version 26.8.0 (Current) #65551
Merged
@tgies tgies mentioned this pull request Aug 27, 2026
tls: server getPeerCertificate(true) drops the peer certificate chain since v26.8.0 (regressed by #64677) #65579
Closed
aduh95 pushed a commit that referenced this pull request Aug 27, 2026
@pimterry @aduh95
tls: fix authorized state on no-cert TLS1.3 client cert resumption
dcf65c5
Previously if you used TLS 1.3 and the server requested a client cert,
but the client didn't send one, and you used rejectUnauthorized:false
the resumed session would report authorized=true. This doesn't match TLS
1.2 behaviour or make any sense, and was purely an artifact of our
internal logic for handling TLS 1.3 resumption details. We now correctly
report the authorization state and/or error from the original connection
in all cases, with a matrix test that fully checks the invariant:
authorized state after resume should always match the initial state.

Signed-off-by: Tim Perry <[email protected]>
PR-URL: #64677
Reviewed-By: James M Snell <[email protected]>
@pimterry pimterry mentioned this pull request Aug 27, 2026
doc: remove outdated TLS authorized docs warning #65597
Merged
@tgies tgies mentioned this pull request Aug 27, 2026
tls: read the peer certificate chain without consuming it #65602
Merged
@tgies

tgies commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

FYI: This caused #65579 by tripping over the pre-existing yet unreported bug that getPeerX509Certificate() actually mutates the certificate chain object owned by the TLS stack as it walks it. Filed #65602 which fixes getPeerX509Certificate() and adds a new cheaper hasPeerCertificate() to use here rather than materializing the whole cert chain just to test if it's null.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

nodejs-github-bot pushed a commit that referenced this pull request Aug 31, 2026
@tgies
tls: read the peer certificate chain without consuming it
705646f
X509Certificate::GetPeerCert() built the certificate objects by deleting
entries from the stack returned by SSL_get_peer_cert_chain(), which is
owned by the SSL session. The first read emptied it, so any later read
by getPeerCertificate() or getPeerX509Certificate(), on either peer, saw
a truncated chain or nothing. Copy each issuer with X509_dup instead and
leave the session's stack untouched.

onServerSocketSecure() only called getPeerX509Certificate() to check
whether a peer certificate was present, building the whole chain on
every server handshake; that is what first exposed the destructive read.
Use a lightweight hasPeerCertificate() binding for the presence check.

Signed-off-by: Tony Gies <[email protected]>
PR-URL: #65602
Fixes: #65579
Refs: #64677
Reviewed-By: Filip Skokan <[email protected]>
Reviewed-By: Tim Perry <[email protected]>
aduh95 pushed a commit that referenced this pull request Sep 3, 2026
@tgies @aduh95
tls: read the peer certificate chain without consuming it
714a787
X509Certificate::GetPeerCert() built the certificate objects by deleting
entries from the stack returned by SSL_get_peer_cert_chain(), which is
owned by the SSL session. The first read emptied it, so any later read
by getPeerCertificate() or getPeerX509Certificate(), on either peer, saw
a truncated chain or nothing. Copy each issuer with X509_dup instead and
leave the session's stack untouched.

onServerSocketSecure() only called getPeerX509Certificate() to check
whether a peer certificate was present, building the whole chain on
every server handshake; that is what first exposed the destructive read.
Use a lightweight hasPeerCertificate() binding for the presence check.

Signed-off-by: Tony Gies <[email protected]>
PR-URL: #65602
Fixes: #65579
Refs: #64677
Reviewed-By: Filip Skokan <[email protected]>
Reviewed-By: Tim Perry <[email protected]>
@github-actions github-actions Bot mentioned this pull request Sep 7, 2026
2026-09-08, Version 24.21.0 'Krypton' (LTS) #65873
Merged
aduh95 pushed a commit that referenced this pull request Sep 15, 2026
@tgies @aduh95
tls: read the peer certificate chain without consuming it
6a3d8a6
X509Certificate::GetPeerCert() built the certificate objects by deleting
entries from the stack returned by SSL_get_peer_cert_chain(), which is
owned by the SSL session. The first read emptied it, so any later read
by getPeerCertificate() or getPeerX509Certificate(), on either peer, saw
a truncated chain or nothing. Copy each issuer with X509_dup instead and
leave the session's stack untouched.

onServerSocketSecure() only called getPeerX509Certificate() to check
whether a peer certificate was present, building the whole chain on
every server handshake; that is what first exposed the destructive read.
Use a lightweight hasPeerCertificate() binding for the presence check.

Signed-off-by: Tony Gies <[email protected]>
PR-URL: #65602
Fixes: #65579
Refs: #64677
Reviewed-By: Filip Skokan <[email protected]>
Reviewed-By: Tim Perry <[email protected]>
aduh95 pushed a commit that referenced this pull request Sep 19, 2026
@tgies @aduh95
tls: read the peer certificate chain without consuming it
656116b
X509Certificate::GetPeerCert() built the certificate objects by deleting
entries from the stack returned by SSL_get_peer_cert_chain(), which is
owned by the SSL session. The first read emptied it, so any later read
by getPeerCertificate() or getPeerX509Certificate(), on either peer, saw
a truncated chain or nothing. Copy each issuer with X509_dup instead and
leave the session's stack untouched.

onServerSocketSecure() only called getPeerX509Certificate() to check
whether a peer certificate was present, building the whole chain on
every server handshake; that is what first exposed the destructive read.
Use a lightweight hasPeerCertificate() binding for the presence check.

Signed-off-by: Tony Gies <[email protected]>
PR-URL: #65602
Fixes: #65579
Refs: #64677
Reviewed-By: Filip Skokan <[email protected]>
Reviewed-By: Tim Perry <[email protected]>
aduh95 pushed a commit that referenced this pull request Sep 19, 2026
@tgies @aduh95
tls: read the peer certificate chain without consuming it
ffd3de2
X509Certificate::GetPeerCert() built the certificate objects by deleting
entries from the stack returned by SSL_get_peer_cert_chain(), which is
owned by the SSL session. The first read emptied it, so any later read
by getPeerCertificate() or getPeerX509Certificate(), on either peer, saw
a truncated chain or nothing. Copy each issuer with X509_dup instead and
leave the session's stack untouched.

onServerSocketSecure() only called getPeerX509Certificate() to check
whether a peer certificate was present, building the whole chain on
every server handshake; that is what first exposed the destructive read.
Use a lightweight hasPeerCertificate() binding for the presence check.

Signed-off-by: Tony Gies <[email protected]>
PR-URL: #65602
Fixes: #65579
Refs: #64677
Reviewed-By: Filip Skokan <[email protected]>
Reviewed-By: Tim Perry <[email protected]>
@legrego legrego mentioned this pull request Oct 23, 2026
PKI authentication fails with intermediate client CAs on Node.js 24.21.0 elastic/kibana#300126
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@jasnell jasnell jasnell approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@pimterry pimterry

Labels

commit-queue-squash PRs the Commit Queue should land as one squashed commit. needs-ci PRs that need a full CI run. tls Issues and PRs related to the tls subsystem.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Reloading page bypasses TLS client authentication

Uh oh!

There was an error while loading. Please reload this page.

6 participants

@pimterry @nodejs-github-bot @trivikr @tgies @jasnell @panva
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.