perf: reuse TLS sessions across connections to the same server - #4529
Merged
Merged
Conversation
Keep the session ticket issued by a successful TLS handshake in memory and present it on later connections to the same server, so pooled and short-lived connections skip the certificate exchange and, on TLS 1.2, one round trip. Sessions are keyed by the ssl object and its certificate material, host, port, rejectUnauthorized and verifyIdentity, captured when the handshake starts, so a session established under a lax configuration is never resumed by a strict one. A failed handshake or a failed identity check drops the cached session. Closes #4521
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #4529 +/- ##
==========================================
+ Coverage 92.53% 92.62% +0.08%
==========================================
Files 93 94 +1
Lines 15995 16092 +97
Branches 2308 2337 +29
==========================================
+ Hits 14801 14905 +104
+ Misses 1194 1187 -7
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Closes #4521
What
Every new TLS connection ran a full handshake: certificate chain on the wire, chain verification, fresh key derivation. This keeps the session ticket issued by a successful handshake in memory and presents it on the next connection to the same server, so a pool refilling, a pool cluster, or an application opening short-lived connections skips the certificate exchange and, on TLS 1.2, one round trip. Nothing is persisted.
How
lib/tls_session_cache.js: aWeakMapfrom thesslconfig object to an LRU of at most 100 peers, mirroring Node's ownhttps.Agentdefault. ATlsSessionSlotis created whenstartTLSbegins and captures everything that shaped the verification of that handshake: thesslobject identity, a snapshot of its certificate material (ca,cert,key,ciphers,passphrase,minVersion,maxVersion), host, port,rejectUnauthorizedandverifyIdentity. Tickets arrive asynchronously, on TLS 1.3 after the handshake and possibly more than once, so the slot is what a late ticket is stored into, never the current state of a shared and possibly mutatedsslobject.lib/base/connection.js:startTLSpasses the cached ticket as thesessionoption oftls.connectand stores the newest ticket from the socket's'session'event.Security properties, all covered by tests:
verifyIdentitycheck passed. A ticket issued before that point (TLS 1.2 issues it during the handshake) is held back until then and discarded on failure.rejectUnauthorizedandverifyIdentityare part of the key, and changing any certificate material invalidates every session stored for thatsslobject.checkServerIdentityon a resumed session (the peer certificate is not re-sent,getPeerCertificate()returns an empty object), so the driver's manual identity check is skipped on resumed sessions too. That check would otherwise fail with "Cert is empty"; it is safe to skip because a ticket can only be resumed under the policy that verified the identity when it was issued.rejectUnauthorizedstays on by default and is untouched.Measured
100 sequential connect +
SELECT 1+end()against Docker servers on this machine,ssl: { ca, rejectUnauthorized: false }, best of 3 interleaved rounds. "Fresh" creates a newsslobject per connection (no reuse by design), "shared" reuses one object, which is what a pool does.sslobjectsslobjectClient CPU on TLS 1.3 was within noise on this box (the secure context is still rebuilt per connection on master; the cache from #4522 removes that). The wall-clock gain on TLS 1.2 comes from the abbreviated handshake skipping the DHE exchange and one round trip.
Tests
test/unit/connection/test-tls-session-cache.test.mts: keying, policy isolation, material invalidation, late tickets bound to their own handshake, LRU bound.test/unit/connection/test-tls-session-resumption.test.mts: runs a localtls.Serverand drivesstartTLSdirectly: resumption, identity failures never resumed past, lax session never resumed by a strict config, dropped session after a failed handshake, material change. It needs a certificate chain that actually verifies, and the existing fixtures intest/fixtures/ssl/certsdo not (the server certificate's subject equals its issuer, so OpenSSL reports it as self-signed at depth 0, which is why the whole suite runs withrejectUnauthorized: false).test/fixtures/ssl/chainadds a small CA + server pair valid forresumption.test, with themkcerts.shthat produced it.test/integration/connection/test-tls-session-resumption.test.mts(MYSQL_USE_TLS=1): the second pooled connection reportsisSessionReused() === true, and MySQL confirms it withSsl_sessions_reused = 1.Verified locally: full suite with
MYSQL_USE_TLS=1on MySQL 8.3 and MariaDB 11.8 (CI images with the fixture certs),test/integration/connectionon MySQL 5.7 (TLS 1.2) and on MySQL 8.3 with compression, plusnpm run lint,npm run typecheck.Docs
website/docs/documentation/ssl.mdxgains a "TLS Session Resumption" section, including the hint to reuse onesslobject acrosscreateConnectioncalls.Not included
No option to turn resumption off. A rejected ticket degrades to a full handshake, so I could not find a case that needs one; it is easy to add later as an
ssloption if someone asks.