| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent fcd642f commit 04201f8
4 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -22,6 +22,7 @@ Extends: [`PoolOptions`](/docs/docs/api/Pool.md#parameter-pooloptions) | |||
| 22 | 22 | * **password** `string` (optional) - SOCKS5 proxy password for authentication. Can also be provided in the proxy URL. | |
| 23 | 23 | * **connect** `Function` (optional) - Custom connector function for the proxy connection. | |
| 24 | 24 | * **proxyTls** `BuildOptions` (optional) - TLS options for the proxy connection (when using SOCKS5 over TLS). | |
| 25 | + * **requestTls** `BuildOptions` (optional) - TLS options applied to the HTTPS connection to the target server through the SOCKS5 tunnel. Use this to configure `ca`, `cert`, `key`, `rejectUnauthorized`, `servername`, etc. for the target HTTPS endpoint. | ||
| 25 | 26 | ||
| 26 | 27 | Examples: | |
| 27 | 28 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -142,7 +142,8 @@ class ProxyAgent extends DispatcherBase { | |||
| 142 | 142 | factory: agentFactory, | |
| 143 | 143 | username: opts.username || username, | |
| 144 | 144 | password: opts.password || password, | |
| 145 | - proxyTls: opts.proxyTls | ||
| 145 | + proxyTls: opts.proxyTls, | ||
| 146 | + requestTls: opts.requestTls | ||
| 146 | 147 | }) | |
| 147 | 148 | } | |
| 148 | 149 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -19,6 +19,7 @@ const kProxyAuth = Symbol('proxy auth') | |||
| 19 | 19 | const kProxyProtocol = Symbol('proxy protocol') | |
| 20 | 20 | const kPools = Symbol('pools') | |
| 21 | 21 | const kConnector = Symbol('connector') | |
| 22 | + const kRequestTls = Symbol('request tls settings') | ||
| 22 | 23 | ||
| 23 | 24 | // Static flag to ensure warning is only emitted once per process | |
| 24 | 25 | let experimentalWarningEmitted = false | |
@@ -53,6 +54,7 @@ class Socks5ProxyAgent extends DispatcherBase { | |||
| 53 | 54 | this[kProxyUrl] = url | |
| 54 | 55 | this[kProxyHeaders] = options.headers || {} | |
| 55 | 56 | this[kProxyProtocol] = options.proxyTls ? 'https:' : 'http:' | |
| 57 | + this[kRequestTls] = options.requestTls | ||
| 56 | 58 | ||
| 57 | 59 | // Extract auth from URL or options | |
| 58 | 60 | this[kProxyAuth] = { | |
@@ -199,9 +201,9 @@ class Socks5ProxyAgent extends DispatcherBase { | |||
| 199 | 201 | } | |
| 200 | 202 | debug('upgrading to TLS') | |
| 201 | 203 | finalSocket = tls.connect({ | |
| 204 | + ...this[kRequestTls], | ||
| 202 | 205 | socket, | |
| 203 | - servername: targetHost, | ||
| 204 | - ...connectOpts.tls || {} | ||
| 206 | + servername: this[kRequestTls]?.servername || targetHost | ||
| 205 | 207 | }) | |
| 206 | 208 | ||
| 207 | 209 | await new Promise((resolve, reject) => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,14 +1,53 @@ | |||
| 1 | 1 | 'use strict' | |
| 2 | 2 | ||
| 3 | 3 | const net = require('node:net') | |
| 4 | + const https = require('node:https') | ||
| 4 | 5 | const { tspl } = require('@matteo.collina/tspl') | |
| 5 | - const { test } = require('node:test') | ||
| 6 | - const { request } = require('..') | ||
| 6 | + const { test, after } = require('node:test') | ||
| 7 | + const { request, ProxyAgent } = require('..') | ||
| 7 | 8 | const { InvalidArgumentError } = require('../lib/core/errors') | |
| 8 | 9 | const Socks5ProxyAgent = require('../lib/dispatcher/socks5-proxy-agent') | |
| 9 | 10 | const { createServer } = require('node:http') | |
| 10 | 11 | const { TestSocks5Server } = require('./fixtures/socks5-test-server') | |
| 11 | 12 | ||
| 13 | + const tlsCerts = (() => { | ||
| 14 | + const forge = require('node-forge') | ||
| 15 | + const createCert = (cn, issuer, keyLength = 2048) => { | ||
| 16 | + const keys = forge.pki.rsa.generateKeyPair(keyLength) | ||
| 17 | + const cert = forge.pki.createCertificate() | ||
| 18 | + cert.publicKey = keys.publicKey | ||
| 19 | + cert.serialNumber = '' + Date.now() | ||
| 20 | + cert.validity.notBefore = new Date() | ||
| 21 | + cert.validity.notAfter = new Date() | ||
| 22 | + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10) | ||
| 23 | + const attrs = [{ name: 'commonName', value: cn }] | ||
| 24 | + cert.setSubject(attrs) | ||
| 25 | + const isCa = issuer === undefined | ||
| 26 | + cert.setExtensions([ | ||
| 27 | + { name: 'basicConstraints', cA: isCa }, | ||
| 28 | + { name: 'subjectAltName', altNames: [{ type: 2, value: cn }] } | ||
| 29 | + ]) | ||
| 30 | + const alg = forge.md.sha256.create() | ||
| 31 | + if (issuer !== undefined) { | ||
| 32 | + cert.setIssuer(issuer.certificate.subject.attributes) | ||
| 33 | + cert.sign(issuer.privateKey, alg) | ||
| 34 | + } else { | ||
| 35 | + cert.setIssuer(attrs) | ||
| 36 | + cert.sign(keys.privateKey, alg) | ||
| 37 | + } | ||
| 38 | + return { privateKey: keys.privateKey, publicKey: keys.publicKey, certificate: cert } | ||
| 39 | + } | ||
| 40 | + const root = createCert('socks5-test-ca') | ||
| 41 | + const server = createCert('localhost', root) | ||
| 42 | + return { | ||
| 43 | + root: { crt: forge.pki.certificateToPem(root.certificate) }, | ||
| 44 | + server: { | ||
| 45 | + key: forge.pki.privateKeyToPem(server.privateKey), | ||
| 46 | + crt: forge.pki.certificateToPem(server.certificate) | ||
| 47 | + } | ||
| 48 | + } | ||
| 49 | + })() | ||
| 50 | + | ||
| 12 | 51 | test('Socks5ProxyAgent - constructor validation', async (t) => { | |
| 13 | 52 | const p = tspl(t, { plan: 4 }) | |
| 14 | 53 | ||
@@ -432,3 +471,87 @@ test('Socks5ProxyAgent - URL parsing edge cases', async (t) => { | |||
| 432 | 471 | ||
| 433 | 472 | await p.completed | |
| 434 | 473 | }) | |
| 474 | + | ||
| 475 | + test('Socks5ProxyAgent - requestTls is honored for target HTTPS connection (GHSA-vmh5-mc38-953g)', async (t) => { | ||
| 476 | + const p = tspl(t, { plan: 2 }) | ||
| 477 | + | ||
| 478 | + // HTTPS server with a cert signed by tlsCerts.root, NOT in Node's default trust store | ||
| 479 | + const server = https.createServer({ | ||
| 480 | + key: tlsCerts.server.key, | ||
| 481 | + cert: tlsCerts.server.crt | ||
| 482 | + }, (req, res) => { | ||
| 483 | + res.writeHead(200, { 'content-type': 'application/json' }) | ||
| 484 | + res.end(JSON.stringify({ ok: true })) | ||
| 485 | + }) | ||
| 486 | + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) | ||
| 487 | + const serverPort = server.address().port | ||
| 488 | + | ||
| 489 | + // SOCKS5 server that forwards CONNECT to the local HTTPS server | ||
| 490 | + const socksServer = new TestSocks5Server() | ||
| 491 | + const socksAddress = await socksServer.listen() | ||
| 492 | + | ||
| 493 | + // Use Socks5ProxyAgent directly with requestTls | ||
| 494 | + const proxyAgent = new Socks5ProxyAgent(`socks5://127.0.0.1:${socksAddress.port}`, { | ||
| 495 | + requestTls: { | ||
| 496 | + ca: [tlsCerts.root.crt] | ||
| 497 | + } | ||
| 498 | + }) | ||
| 499 | + | ||
| 500 | + after(async () => { | ||
| 501 | + await proxyAgent.close() | ||
| 502 | + server.close() | ||
| 503 | + await socksServer.close() | ||
| 504 | + }) | ||
| 505 | + | ||
| 506 | + // The HTTPS request via SOCKS5 should succeed because requestTls.ca contains the root CA | ||
| 507 | + // that signed the server cert. Without honoring requestTls, Node would default to Mozilla | ||
| 508 | + // CA bundle and reject the cert. | ||
| 509 | + const response = await request(`https://localhost:${serverPort}/`, { | ||
| 510 | + dispatcher: proxyAgent | ||
| 511 | + }) | ||
| 512 | + p.strictEqual(response.statusCode, 200, 'request should succeed when requestTls.ca is honored') | ||
| 513 | + const body = await response.body.json() | ||
| 514 | + p.deepStrictEqual(body, { ok: true }) | ||
| 515 | + | ||
| 516 | + await p.completed | ||
| 517 | + }) | ||
| 518 | + | ||
| 519 | + test('ProxyAgent forwards requestTls to Socks5ProxyAgent (GHSA-vmh5-mc38-953g)', async (t) => { | ||
| 520 | + const p = tspl(t, { plan: 2 }) | ||
| 521 | + | ||
| 522 | + const server = https.createServer({ | ||
| 523 | + key: tlsCerts.server.key, | ||
| 524 | + cert: tlsCerts.server.crt | ||
| 525 | + }, (req, res) => { | ||
| 526 | + res.writeHead(200, { 'content-type': 'application/json' }) | ||
| 527 | + res.end(JSON.stringify({ ok: true })) | ||
| 528 | + }) | ||
| 529 | + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) | ||
| 530 | + const serverPort = server.address().port | ||
| 531 | + | ||
| 532 | + const socksServer = new TestSocks5Server() | ||
| 533 | + const socksAddress = await socksServer.listen() | ||
| 534 | + | ||
| 535 | + // Use top-level ProxyAgent with socks5 URI + requestTls; the option must be forwarded. | ||
| 536 | + const proxyAgent = new ProxyAgent({ | ||
| 537 | + uri: `socks5://127.0.0.1:${socksAddress.port}`, | ||
| 538 | + requestTls: { | ||
| 539 | + ca: [tlsCerts.root.crt] | ||
| 540 | + } | ||
| 541 | + }) | ||
| 542 | + | ||
| 543 | + after(async () => { | ||
| 544 | + await proxyAgent.close() | ||
| 545 | + server.close() | ||
| 546 | + await socksServer.close() | ||
| 547 | + }) | ||
| 548 | + | ||
| 549 | + const response = await request(`https://localhost:${serverPort}/`, { | ||
| 550 | + dispatcher: proxyAgent | ||
| 551 | + }) | ||
| 552 | + p.strictEqual(response.statusCode, 200, 'request should succeed when ProxyAgent forwards requestTls') | ||
| 553 | + const body = await response.body.json() | ||
| 554 | + p.deepStrictEqual(body, { ok: true }) | ||
| 555 | + | ||
| 556 | + await p.completed | ||
| 557 | + }) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments