current commit
260920.21 · https: handle invalid TLS options in proxied requests
// This tests that invalid TLS options do not result in an uncaught exception
// after an HTTPS proxy tunnel has been established.

import * as common from '../common/index.mjs';
import assert from 'node:assert';
import { once } from 'events';
import fixtures from '../common/fixtures.js';
import { createProxyServer } from '../common/proxy-server.js';

if (!common.hasCrypto)
  common.skip('missing crypto');

// https must be dynamically imported so that builds without crypto support
// can skip it.
const { default: https } = await import('node:https');

const server = https.createServer({
  cert: fixtures.readKey('agent8-cert.pem'),
  key: fixtures.readKey('agent8-key.pem'),
}, common.mustNotCall());
server.on('error', common.mustNotCall());
server.listen(0);
await once(server, 'listening');

const { proxy, logs } = createProxyServer();
proxy.listen(0);
await once(proxy, 'listening');

const serverHost = `localhost:${server.address().port}`;

// tls.connect() throws for these options while building the secure context,
// before any handshake happens.
const testCases = [
  { options: { minVersion: 'definitely-invalid' }, code: 'ERR_TLS_INVALID_PROTOCOL_VERSION' },
  { options: { ciphers: 123 }, code: 'ERR_INVALID_ARG_TYPE' },
  { options: { secureProtocol: 'definitely-invalid' }, code: 'ERR_TLS_INVALID_PROTOCOL_METHOD' },
];

for (const { options, code } of testCases) {
  const agent = new https.Agent({
    ca: fixtures.readKey('fake-startcom-root-cert.pem'),
    proxyEnv: {
      HTTPS_PROXY: `http://localhost:${proxy.address().port}`,
    },
  });
  const req = https.get({
    host: 'localhost',
    port: server.address().port,
    path: '/test',
    agent,
    ...options,
  }, common.mustNotCall());
  const [err] = await once(req, 'error');
  assert.strictEqual(err.code, code);
  agent.destroy();
}

// Verify that the requests went through the proxy and the tunnel was established.
const requests = logs.filter((log) => !('error' in log));
// The client resets the tunnel as soon as tls.connect() throws, which the
// proxy may observe as an ECONNRESET while still relaying it.
const errors = logs.filter((log) =>
  'error' in log && log.error.code !== 'ECONNRESET');

assert.deepStrictEqual(requests, testCases.map(() => ({
  method: 'CONNECT',
  url: serverHost,
  headers: {
    'host': serverHost,
  },
})));
assert.deepStrictEqual(errors, []);

proxy.close();
server.close();