]> dgit.raspbian.org Git - nodejs.git/commitdiff
https: bind identity checks to session reuse
authorMatteo Collina <hello@matteocollina.com>
Tue, 23 Jun 2026 10:47:47 +0000 (12:47 +0200)
committerBastien Roucariès <rouca@debian.org>
Sun, 16 Aug 2026 17:34:06 +0000 (19:34 +0200)
PR-URL: https://github.com/nodejs-private/node-private/pull/934
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
CVE-ID: CVE-2026-58040
origin: backport, https://github.com/nodejs/node/commit/51123159fe863073d7dcaf5c88b23bad8aae1a62

Gbp-Pq: Topic sec
Gbp-Pq: Name CVE-2026-58040.patch

lib/https.js
test/parallel/test-https-agent-checkserveridentity-reuse.js [new file with mode: 0644]

index 818f95d5af77e98db9096649bf15b9fd0ad3134f..641981e98fe78502a8dcc84d23a3a0bb1b4d3765 100644 (file)
@@ -34,6 +34,7 @@ const {
   ObjectSetPrototypeOf,
   ReflectApply,
   ReflectConstruct,
+  Symbol,
 } = primordials;
 
 const {
@@ -44,6 +45,8 @@ assertCrypto();
 
 const tls = require('tls');
 const { Agent: HttpAgent } = require('_http_agent');
+const kPerRequestCheckServerIdentity = Symbol('per-request checkServerIdentity');
+let perRequestCheckServerIdentityIndex = 0;
 const {
   httpServerPreClose,
   Server: HttpServer,
@@ -145,7 +148,9 @@ function createConnection(port, host, options) {
 
   debug('createConnection', options);
 
-  if (options._agentKey) {
+  const reuseSession = options._agentKey &&
+                       !options[kPerRequestCheckServerIdentity];
+  if (reuseSession) {
     const session = this._getSession(options._agentKey);
     if (session) {
       debug('reuse session for %j', options._agentKey);
@@ -158,7 +163,10 @@ function createConnection(port, host, options) {
 
   const socket = tls.connect(options);
 
-  if (options._agentKey) {
+  if (options[kPerRequestCheckServerIdentity])
+    socket[kPerRequestCheckServerIdentity] = true;
+
+  if (reuseSession) {
     // Cache new session for reuse
     socket.on('session', (session) => {
       this._cacheSession(options._agentKey, session);
@@ -208,6 +216,12 @@ function Agent(options) {
 ObjectSetPrototypeOf(Agent.prototype, HttpAgent.prototype);
 ObjectSetPrototypeOf(Agent, HttpAgent);
 Agent.prototype.createConnection = createConnection;
+Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) {
+  if (socket[kPerRequestCheckServerIdentity])
+    return false;
+
+  return FunctionPrototypeCall(HttpAgent.prototype.keepSocketAlive, this, socket);
+};
 
 function getPfxAgentKey(pfx, passphrase) {
   if (!ArrayIsArray(pfx))
@@ -316,6 +330,9 @@ Agent.prototype.getName = function getName(options = kEmptyObject) {
   if (options.privateKeyEngine)
     name += options.privateKeyEngine;
 
+  if (options[kPerRequestCheckServerIdentity])
+    name += `:${options[kPerRequestCheckServerIdentity]}`;
+
   return name;
 };
 
@@ -356,6 +373,22 @@ Agent.prototype._evictSession = function _evictSession(key) {
 
 const globalAgent = new Agent();
 
+function hasAgentCheckServerIdentity(options) {
+  let { agent } = options;
+  if (agent === false)
+    return false;
+
+  if (agent === null || agent === undefined) {
+    if (typeof options.createConnection === 'function')
+      return false;
+    agent = module.exports.globalAgent;
+  }
+
+  return agent?.options?.checkServerIdentity !== undefined;
+}
+
+
+
 /**
  * Makes a request to a secure web server.
  * @param {...any} args
@@ -375,6 +408,13 @@ function request(...args) {
     ObjectAssign(options, ArrayPrototypeShift(args));
   }
 
+  if (options.checkServerIdentity !== undefined &&
+      options.checkServerIdentity !== tls.checkServerIdentity &&
+      !hasAgentCheckServerIdentity(options)) {
+    options[kPerRequestCheckServerIdentity] =
+      ++perRequestCheckServerIdentityIndex;
+  }
+
   options._defaultAgent = module.exports.globalAgent;
   ArrayPrototypeUnshift(args, options);
 
diff --git a/test/parallel/test-https-agent-checkserveridentity-reuse.js b/test/parallel/test-https-agent-checkserveridentity-reuse.js
new file mode 100644 (file)
index 0000000..2c339c3
--- /dev/null
@@ -0,0 +1,113 @@
+'use strict';
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const assert = require('assert');
+const fixtures = require('../common/fixtures');
+const https = require('https');
+const { once } = require('events');
+
+const key = fixtures.readKey('agent1-key.pem');
+const cert = fixtures.readKey('agent1-cert.pem');
+const ca = fixtures.readKey('ca1-cert.pem');
+const expectedError = /rejected by callback/;
+
+function request(options) {
+  return new Promise((resolve, reject) => {
+    const req = https.get({
+      host: '127.0.0.1',
+      servername: 'agent1',
+      ca: [ca],
+      ...options,
+    }, (res) => {
+      const socket = res.socket;
+      res.resume();
+      res.on('end', () => resolve({
+        socket,
+        reusedSocket: req.reusedSocket,
+      }));
+    });
+
+    req.on('error', reject);
+  });
+}
+
+const server = https.createServer({
+  key,
+  cert,
+  minVersion: 'TLSv1.2',
+  maxVersion: 'TLSv1.2',
+}, (req, res) => {
+  res.end('ok');
+});
+
+(async function() {
+  server.listen(0);
+  await once(server, 'listening');
+
+  const port = server.address().port;
+  let acceptCalls = 0;
+  let rejectCalls = 0;
+  const acceptingCheck = () => {
+    acceptCalls++;
+  };
+  const rejectingCheck = () => {
+    rejectCalls++;
+    return new Error('rejected by callback');
+  };
+
+  const sessionAgent = new https.Agent();
+  const keepAliveAgent = new https.Agent({
+    keepAlive: true,
+    maxCachedSessions: 0,
+  });
+  const agentLevelAgent = new https.Agent({
+    checkServerIdentity: acceptingCheck,
+  });
+
+  try {
+    await request({
+      port,
+      agent: sessionAgent,
+      checkServerIdentity: acceptingCheck,
+    });
+    assert.deepStrictEqual(sessionAgent._sessionCache.map, {});
+    await assert.rejects(request({
+      port,
+      agent: sessionAgent,
+      checkServerIdentity: rejectingCheck,
+    }), expectedError);
+
+    await request({
+      port,
+      agent: keepAliveAgent,
+      checkServerIdentity: acceptingCheck,
+    });
+    await assert.rejects(request({
+      port,
+      agent: keepAliveAgent,
+      checkServerIdentity: rejectingCheck,
+    }), expectedError);
+
+    const first = await request({
+      port,
+      agent: agentLevelAgent,
+    });
+    assert.strictEqual(first.socket.isSessionReused(), false);
+    const second = await request({
+      port,
+      agent: agentLevelAgent,
+    });
+    assert.strictEqual(second.socket.isSessionReused(), true);
+
+    assert.strictEqual(acceptCalls, 3);
+    assert.strictEqual(rejectCalls, 2);
+  } finally {
+    sessionAgent.destroy();
+    keepAliveAgent.destroy();
+    agentLevelAgent.destroy();
+    server.close();
+    await once(server, 'close');
+  }
+})().then(common.mustCall());