Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion lib/base/connection.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const PacketParser = require('../packet_parser.js');
const Packets = require('../packets/index.js');
const Commands = require('../commands/index.js');
const ConnectionConfig = require('../connection_config.js');
const TlsSessionSlot = require('../tls_session_cache.js');
const CharsetToEncoding = require('../constants/charset_encodings.js');
const {
traceCallback,
Expand Down Expand Up @@ -396,7 +397,16 @@ class BaseConnection extends EventEmitter {
? undefined
: this.config.host;

const sessionSlot = new TlsSessionSlot(
this.config.ssl,
this.config.host,
this.config.port,
rejectUnauthorized,
verifyIdentity
);
let secureEstablished = false;
let peerAccepted = false;
let issuedSession = null;
this.stream.removeAllListeners('data');
const secureSocket = Tls.connect(
{
Expand All @@ -408,33 +418,47 @@ class BaseConnection extends EventEmitter {
return undefined;
},
secureContext,
session: sessionSlot.get(),
isServer: false,
socket: this.stream,
servername,
},
() => {
secureEstablished = true;
if (rejectUnauthorized) {
if (rejectUnauthorized && !secureSocket.isSessionReused()) {
if (typeof servername === 'string' && verifyIdentity) {
const cert = secureSocket.getPeerCertificate(true);
const serverIdentityCheckError = Tls.checkServerIdentity(
servername,
cert
);
if (serverIdentityCheckError) {
sessionSlot.delete();
onSecure(serverIdentityCheckError);
return;
}
}
}
peerAccepted = true;
if (issuedSession !== null) {
sessionSlot.set(issuedSession);
}
onSecure();
}
);
secureSocket.on('session', (session) => {
if (peerAccepted) {
sessionSlot.set(session);
} else {
issuedSession = session;
}
});
// error handler for secure socket
secureSocket.on('error', (err) => {
if (secureEstablished) {
this._handleNetworkError(err);
} else {
sessionSlot.delete();
onSecure(err);
}
});
Expand Down
73 changes: 73 additions & 0 deletions lib/tls_session_cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use strict';

const { createLRU } = require('lru.min');

const MAX_PEERS_PER_SSL_CONFIG = 100;

const caches = new WeakMap();

function snapshotMaterial(ssl) {
return {
ca: ssl.ca,
cert: ssl.cert,
ciphers: ssl.ciphers,
key: ssl.key,
passphrase: ssl.passphrase,
minVersion: ssl.minVersion,
maxVersion: ssl.maxVersion,
};
}

function sameMaterial(a, b) {
return (
a.ca === b.ca &&
a.cert === b.cert &&
a.ciphers === b.ciphers &&
a.key === b.key &&
a.passphrase === b.passphrase &&
a.minVersion === b.minVersion &&
a.maxVersion === b.maxVersion
);
}

class TlsSessionSlot {
constructor(ssl, host, port, rejectUnauthorized, verifyIdentity) {
this.ssl = ssl;
this.material = snapshotMaterial(ssl);
this.key = `${host}:${port}:${Boolean(rejectUnauthorized)}:${Boolean(verifyIdentity)}`;
}

_sessions() {
const cache = caches.get(this.ssl);
if (cache !== undefined && sameMaterial(cache.material, this.material)) {
return cache.sessions;
}
return undefined;
}

get() {
const sessions = this._sessions();
return sessions === undefined ? undefined : sessions.get(this.key);
}

set(session) {
let sessions = this._sessions();
if (sessions === undefined) {
if (!sameMaterial(this.material, snapshotMaterial(this.ssl))) {
return;
}
sessions = createLRU({ max: MAX_PEERS_PER_SSL_CONFIG });
caches.set(this.ssl, { material: this.material, sessions });
}
sessions.set(this.key, session);
}

delete() {
const sessions = this._sessions();
if (sessions !== undefined) {
sessions.delete(this.key);
}
}
}

module.exports = TlsSessionSlot;
28 changes: 28 additions & 0 deletions test/fixtures/ssl/chain/ca-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDDzL+33x8dUvvG
dUki+t8rSFEKesDrG5sh9aJF2ofB0DLZUmtFNOaBm/axPii26By9T2tI4Scj1G2+
7pBuRB3K9i4l8X7HLdlPSkRTpoRnElYnucjECI/wwWN6A4ZIuyqaaaaoMDZF3uqh
x7Ea7XpLl82rlEr7iZuuBAGfIVulk6chOZEpvr8cUqrxrkHjhRoxePGiwx48sBBk
o+GhSDfiCg2hsP9ahYafFukRHJiA02FXVITnVfOYs2r1+XyumXuHHDD6BytfL7EP
SDMTzRoPP4yu9BWGmeH2Wp2zHCF0UY8BX+FuF/Paa1oPX+Hbu2C2ZnYOvDcKWqkB
1l1fq01/AgMBAAECggEAU+oy+oNSKvevN0l57Fd3gO3P6b1dZAXjUUUcZHK5fxuT
R2eW2MTIy6CDtEfPHEDTXGEmukfgxe7bkBpfZRqlaCBk+9cJviSMv3o1WWKC2UfX
C9SIUlfXFNhVIWxUR2SL6DzCc8+h2wUkKuzSHxfLM42eVFxMXIyH36cQ6NnblNS4
Je+9xeU3QB2UKJUh0XttGVXY5qxL2AJJtHqFZdT0irYz/GcuZvNhcRdIzUYkqC8h
RFjdkYsk+gAqoJCyQN9zqej+BWmu3ZJEFBOlIWPh94UE3H/ZKl8ZEbF+BxJoM1/T
nZ6ONlclS0Y/AakmWSmUnYoDsZICalL+RMX/2Tr0CQKBgQDygBDSx/lFP6HrRzIB
EGzt0Dt6pT1T8VT3nBVJ7Xj7pkEA69/t0JdLXWNET7rxCWYG+MakhRRWXawgo3gc
ofuupPCh8exWwGY1sLclo4RJyGEIouqx/DLROQwmJMmcH4fu5Gu1IczjZa3K3ypL
unCqIWAom9u0PUEocGVv4D4fqQKBgQDOsySQFY6dgoqs6o1dyf3SrafPJiAXhIZJ
sqYG2oBZ/Y4CbaEZ8OgiixRaRa6B2AzzYbYMgwsXgM6MMJQnB3SvX1T2Hbjk4MPJ
4vlBWDtB20PYkH+foTRRLOqAyvOmwETD6jQSJlseNwNTynD8GCHu1upyZvZwtMKx
Bt5H59xc5wKBgFtatXvCi/xzcVtCMetGtdKNPKsCbu0doEW7jiWQiaA7zIc9VNmx
WB2zK79DTgiZ4s7Dj4nQ/I1gsoSFMK2QtFDhoClSCxfPWXHmhchvJlnpW+y1+zZb
7QT2ucXw2+at1ja/5HeTKS2NU4B2u+dVcDYXZrNFq+nlnZNPbFdOIfkpAoGBAIMg
BBbfhgyH7r9JQIr6A+ptvaHc1sj0ilggW9DHNltgTYFe2K9jM3AOeMfXop++VmDj
YKDJVPUBAobOnn2v7ib+vCAxtMSFjc76DROWDGl31q+A42V5132Tl+RCW5UyLa13
WrcU3ALr9VFngIt8J7KjZjFS/g17rnKtH7/P5+yLAoGBAJNVY+mhv8sL+da9sSz0
D/Ck5Gt7M9mzrOCFILgPbehdV9pwlMan6pMffTuESlUSEBWFoeWdSQri9rI8hyRz
oME0Xs4BsPHaA7ytRR8Dy0q+hkZJjf8h8yWEbOZzT3GJsLKMZpyEyracHGPGpkLG
ckZ0LXOkKvCMiBZL7NIxwbrZ
-----END PRIVATE KEY-----
19 changes: 19 additions & 0 deletions test/fixtures/ssl/chain/ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDFTCCAf2gAwIBAgIUITBXG1QoNXTMMVA0So/ANwMKEJ4wDQYJKoZIhvcNAQEL
BQAwGTEXMBUGA1UEAwwOTXlTUUwyIHRlc3QgQ0EwIBcNMjYwOTA1MDgzNzA3WhgP
MjEyNjA4MTIwODM3MDdaMBkxFzAVBgNVBAMMDk15U1FMMiB0ZXN0IENBMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw8y/t98fHVL7xnVJIvrfK0hRCnrA
6xubIfWiRdqHwdAy2VJrRTTmgZv2sT4otugcvU9rSOEnI9Rtvu6QbkQdyvYuJfF+
xy3ZT0pEU6aEZxJWJ7nIxAiP8MFjegOGSLsqmmmmqDA2Rd7qocexGu16S5fNq5RK
+4mbrgQBnyFbpZOnITmRKb6/HFKq8a5B44UaMXjxosMePLAQZKPhoUg34goNobD/
WoWGnxbpERyYgNNhV1SE51XzmLNq9fl8rpl7hxww+gcrXy+xD0gzE80aDz+MrvQV
hpnh9lqdsxwhdFGPAV/hbhfz2mtaD1/h27tgtmZ2Drw3ClqpAdZdX6tNfwIDAQAB
o1MwUTAdBgNVHQ4EFgQUut4Z0pY00oj8SCul9yb8m05K7ZwwHwYDVR0jBBgwFoAU
ut4Z0pY00oj8SCul9yb8m05K7ZwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B
AQsFAAOCAQEAbobP6GMQjg8Mfn0IdqEKNv0etOGoJyLSb7kcCD6sec4nrKaofjnz
z2l56vxShs1bzb99eyv6pmYLJEDNeflVIc+Q8mOpa/wio80U1/RPpzQxY8b0FtrV
6HRjusIKqwm3pSfr5xxiJhmyO2H++fw73nOc9GyA9HbydfiTNmsbDm2fm9gqqJCJ
fLtboXlj+brg/tc84OOUO0o7DTKRx2uswdNZ1l8cml3+32RdnwqCZNsioKk45478
k/ZFI48LH3DDqT9bNFj72hRFV78hHfTWiyNYOIe/z9cNgfINfHjtlSHMtTTKK6Nh
jA6C0GVfwxdotuJApuJIVgVLIYsm1qhiAQ==
-----END CERTIFICATE-----
16 changes: 16 additions & 0 deletions test/fixtures/ssl/chain/mkcerts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# A CA and a server certificate that verify as a chain, for unit tests that
# run their own TLS server. The server certificate is valid for the DNS name
# `resumption.test` only.

openssl req -x509 -newkey rsa:2048 -nodes -days 36500 \
-subj "/CN=MySQL2 test CA" -keyout ca-key.pem -out ca.pem

openssl req -newkey rsa:2048 -nodes -subj "/CN=resumption.test" \
-keyout server-key.pem -out server-req.pem

openssl x509 -req -in server-req.pem -days 36500 \
-CA ca.pem -CAkey ca-key.pem -set_serial 01 \
-extfile <(printf "subjectAltName=DNS:resumption.test") \
-out server-cert.pem

rm server-req.pem
19 changes: 19 additions & 0 deletions test/fixtures/ssl/chain/server-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDDjCCAfagAwIBAgIBATANBgkqhkiG9w0BAQsFADAZMRcwFQYDVQQDDA5NeVNR
TDIgdGVzdCBDQTAgFw0yNjA5MDUwODM3MDdaGA8yMTI2MDgxMjA4MzcwN1owGjEY
MBYGA1UEAwwPcmVzdW1wdGlvbi50ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA1Duxok5UeTVaL237ciWR5Dor1aPG7ry4xIcL5MwXhp372P9R38KO
PyPDrd/Q+mjxJNCdokd3eaY9983BDACSj4Xvfq1U8+drWkZIMaPgAqSpHbbTGfor
TEk8UFZh2hcVFsmHA8bowDLMuM0b2VX8gOE1YIG2PPNhJqYeUjcFXPjj/HTxDTlp
GK/fT3VKHNqiERBjV7fJfWaHBhb0JKjzpVZ42MbfpT/P8x37Pe6m3W7/dYybJMoU
Sgb7ZHhGOkAu6WpVOMc5qQkHbqvlwVcVhQ8aRr7V33dl4DS80SU72pCeiWtxPQmW
sKauqxVOBTwTE81pZvBp9AgT8H8Cwz1BpwIDAQABo14wXDAaBgNVHREEEzARgg9y
ZXN1bXB0aW9uLnRlc3QwHQYDVR0OBBYEFCzrsvWzuKC6Fm7dNKffaI4dtJciMB8G
A1UdIwQYMBaAFLreGdKWNNKI/Egrpfcm/JtOSu2cMA0GCSqGSIb3DQEBCwUAA4IB
AQBZeZe0lYrVbvEo8ODm4K2Spjo1uHDUvRYfLU2VFzOTwWYTLKnVkI7Nf2XOa0hZ
B8BidNNFrKIQh/zOi3hKPcaRmwdY/AvIIpD6HsEwapLPM+JY/msUCkdNyPN8w3ye
ZWBNgauouNcUDgITGhLn/YU2PYwPepBIb9DXOxQA4ugmDc+HNDkfjm6BVQWbZFzV
eP3O7I/aADqT36vCpWmxvg2yEuqwxsldEdoGRq7T4SFuhrjGW/XNIT9qeFeLpLmS
OrXYIiI3lH7KZKfH4u6rOWByl/sQFF1mwkbV7+a52rHma7M+3XKwTfGj72WMg8uc
hg372sydZvHNZL3XsV0kEUme
-----END CERTIFICATE-----
28 changes: 28 additions & 0 deletions test/fixtures/ssl/chain/server-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDUO7GiTlR5NVov
bftyJZHkOivVo8buvLjEhwvkzBeGnfvY/1Hfwo4/I8Ot39D6aPEk0J2iR3d5pj33
zcEMAJKPhe9+rVTz52taRkgxo+ACpKkdttMZ+itMSTxQVmHaFxUWyYcDxujAMsy4
zRvZVfyA4TVggbY882Emph5SNwVc+OP8dPENOWkYr99PdUoc2qIREGNXt8l9ZocG
FvQkqPOlVnjYxt+lP8/zHfs97qbdbv91jJskyhRKBvtkeEY6QC7palU4xzmpCQdu
q+XBVxWFDxpGvtXfd2XgNLzRJTvakJ6Ja3E9CZawpq6rFU4FPBMTzWlm8Gn0CBPw
fwLDPUGnAgMBAAECggEAMmHxa3x45UVbIYNUe+abWbjNiywUH7zBiDYfBrPEqfp7
PSpvf1Uqe6a7310QuUGMbZbwtQOOQ1zJvTglKfpA0L2U+1eeoUBA5e9lWBN1LGt2
kk1ClBoliTihjtjS5fkw0nYJDUZ9HpNwsjZO9CtLhDTyfPSOFK4aVBh/fjj9lvI4
H7o+E+vPmBWY+iECODhz6LiQAR/0me6FsWa4lUu63e5YOuqg2dr4ldoSnD01s5fy
0e++vnrf3YVHvTCV9Aa0uosxPHKtCb2a5aD8NU2gUvXhRaVPvgYMR2wkiYB+t93+
5X1y3AomtnFmmYPfIdka9MavUf5NXWhde1VIFIoI8QKBgQD3W3VO4vGqil0wDDri
bDSk/oy+nXtLlT6yNHJt41qpT2gYWd2xaivNYrhs6ikbwQ394lesFOqvgaR5ryCw
SW7aXjeyY+G5NDOaVSYMSxaSPCb0zwXlRREPRar/FqG6937+0Q/HoQCL/ggFxVdJ
gpoPiI+9x0wQvjm5/UxynsLP5QKBgQDbpg9/tOUwhZ2JD0PPsg1rdozYmO7UsKb/
kbGGFxx2mqAdzRa7CnltvElgzw1aIP3xYrjrzOA+Xw6I6GD6mX+8cz3DUZ+cfPOg
UaKEF0duDHt6BNwppUNiQx/fiDYp1h+yShLtef2cJXATggUXNQ/qw+qSxByLChME
nyKUMXm6mwKBgAOjcMJMTEm5500BoQwbk1qp8+AJ6Ppfo6BzbQt4wKik5l8VU1F8
wlAvF49ikmA6Ir6NScTaOemC+qxfixeMvPixUhhpp7RVtDyc7AZaRGa6Q8huEyQg
M/QJjk5aQt7BLTkKUjUZhSyJ/Aw5ruMB7Mo1tAeSmufUJH074Jf/BdaRAoGBAJ78
cyl/+YkGngHQP8g9XANV+xroj6758tb4USP65Ipg39bSVUqriTtJ118wX7zGNqmb
SLi9Qe9DhNTKRy/9HX19eM5A7aQquXlovZaY9wYMGPA4RlgKnFyqs45sHLBpoeW+
QjcMXurAGoC0UxT6PnwytT7onrRxZVSqDzBvI2pTAoGAZuaEWM5ZgWhz7wDQPCxq
SMebg++vGLDSehXbbge1j6VNoQc4/jnBHpJ/NeVK3QhWjxnbLFUc5WeY4KVpEEMx
NE/HyoBirn6LjbxT1qrV2aergrMbdHJy8G8jVH8aX6JjE6gEoeA7sMhwxlMWxLbe
8waNGUhP6aoy3J7lxVS6VVk=
-----END PRIVATE KEY-----
44 changes: 44 additions & 0 deletions test/integration/connection/test-tls-session-resumption.test.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { RowDataPacket } from '../../../index.js';
import process from 'node:process';
import { describe, it, skip, strict } from 'poku';
import driver from '../../../index.js';
import { config, getConfig, getMysqlVersion } from '../../common.test.mjs';

if (process.env.MYSQL_USE_TLS !== '1' || config.ssl === undefined) {
skip('TLS session resumption needs MYSQL_USE_TLS=1');
}

type StatusRow = RowDataPacket & { Variable_name: string; Value: string };

const ssl = config.ssl;

await describe('TLS session resumption across pooled connections', async () => {
const pool = driver.createPool({ ...getConfig(), ssl }).promise();

const first = await pool.getConnection();
await first.query('SELECT 1');
const second = await pool.getConnection();
const { isMariaDB } = await getMysqlVersion(second);
const [status] = await second.query<StatusRow[]>(
"SHOW SESSION STATUS LIKE 'Ssl_sessions_reused'"
);

it('should resume the session of the first pooled connection', () => {
// @ts-expect-error: internal access
strict.equal(first.connection.stream.isSessionReused(), false);
// @ts-expect-error: internal access
strict.equal(second.connection.stream.isSessionReused(), true);
});

it('should be confirmed by the server', () => {
if (isMariaDB) {
return;
}

strict.equal(status[0].Value, '1');
});

first.release();
second.release();
await pool.end();
});
Loading
Loading