mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Add tests for the proxy
This commit is contained in:
parent
b935f8d4f4
commit
f4cdf91fc9
3 changed files with 658 additions and 0 deletions
|
|
@ -0,0 +1,172 @@
|
|||
import { before, after, describe, it } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import net from "net";
|
||||
import tls from "tls";
|
||||
import {
|
||||
createSafeChainProxy,
|
||||
mergeSafeChainProxyEnvironmentVariables,
|
||||
} from "./registryProxy.js";
|
||||
|
||||
describe("registryProxy.connectTunnel", () => {
|
||||
let proxy, proxyHost, proxyPort;
|
||||
|
||||
before(async () => {
|
||||
proxy = createSafeChainProxy();
|
||||
await proxy.startServer();
|
||||
const envVars = mergeSafeChainProxyEnvironmentVariables([]);
|
||||
const proxyUrl = new URL(envVars.HTTPS_PROXY);
|
||||
proxyHost = proxyUrl.hostname;
|
||||
proxyPort = parseInt(proxyUrl.port, 10);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await proxy.stopServer();
|
||||
});
|
||||
|
||||
it("should establish a tunnel for HTTP connect", async () => {
|
||||
const socket = await connectToProxy(proxyHost, proxyPort);
|
||||
const tunnelResponse = await establishHttpsTunnel(
|
||||
socket,
|
||||
"postman-echo.com",
|
||||
443
|
||||
);
|
||||
|
||||
assert.ok(tunnelResponse.includes("HTTP/1.1 200 Connection Established"));
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
it("should send HTTPS request through the established tunnel", async () => {
|
||||
const socket = await connectToProxy(proxyHost, proxyPort);
|
||||
await establishHttpsTunnel(socket, "postman-echo.com", 443);
|
||||
const httpsResponse = await sendHttpsRequestThroughTunnel(
|
||||
socket,
|
||||
"GET",
|
||||
new URL("https://postman-echo.com/status/200")
|
||||
);
|
||||
|
||||
assert.ok(httpsResponse.includes("HTTP/1.1 200 OK"));
|
||||
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
describe("Error Handling", () => {
|
||||
it("should return 502 Bad Gateway for invalid hostname", async () => {
|
||||
const socket = await connectToProxy(proxyHost, proxyPort);
|
||||
const connectRequest = `CONNECT invalid.hostname.that.does.not.exist:443 HTTP/1.1\r\nHost: invalid.hostname.that.does.not.exist:443\r\n\r\n`;
|
||||
socket.write(connectRequest);
|
||||
|
||||
let responseData = "";
|
||||
await new Promise((resolve) => {
|
||||
socket.once("data", (data) => {
|
||||
responseData += data.toString();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
assert.ok(responseData.includes("HTTP/1.1 502 Bad Gateway"));
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
it("should handle client disconnect during tunnel establishment", async () => {
|
||||
const socket = await connectToProxy(proxyHost, proxyPort);
|
||||
const connectRequest = `CONNECT postman-echo.com:443 HTTP/1.1\r\nHost: postman-echo.com:443\r\n\r\n`;
|
||||
socket.write(connectRequest);
|
||||
|
||||
// Immediately destroy the socket before tunnel is fully established
|
||||
socket.destroy();
|
||||
|
||||
// If no crash occurs, the test passes
|
||||
assert.ok(true);
|
||||
});
|
||||
|
||||
|
||||
it("should handle socket errors without crashing", async () => {
|
||||
const socket = await connectToProxy(proxyHost, proxyPort);
|
||||
|
||||
socket.on("error", () => {
|
||||
// Error handler is set to prevent crashes
|
||||
});
|
||||
|
||||
const connectRequest = `CONNECT postman-echo.com:443 HTTP/1.1\r\nHost: postman-echo.com:443\r\n\r\n`;
|
||||
socket.write(connectRequest);
|
||||
|
||||
// Force an error by destroying the socket
|
||||
socket.destroy();
|
||||
|
||||
// Wait a bit to ensure error handling completes
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Test passes if no unhandled error crashes the process
|
||||
assert.ok(true);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function connectToProxy(host, port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const socket = net.connect({ host, port }, () => {
|
||||
resolve(socket);
|
||||
});
|
||||
|
||||
socket.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function establishHttpsTunnel(socket, targetHost, targetPort) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const connectRequest = `CONNECT ${targetHost}:${targetPort} HTTP/1.1\r\nHost: ${targetHost}:${targetPort}\r\n\r\n`;
|
||||
socket.write(connectRequest);
|
||||
|
||||
let responseData = "";
|
||||
const onData = (data) => {
|
||||
responseData += data.toString();
|
||||
if (responseData.includes("\r\n\r\n")) {
|
||||
socket.removeListener("data", onData);
|
||||
socket.removeListener("error", onError);
|
||||
resolve(responseData);
|
||||
}
|
||||
};
|
||||
|
||||
const onError = (err) => {
|
||||
socket.removeListener("data", onData);
|
||||
socket.removeListener("error", onError);
|
||||
reject(err);
|
||||
};
|
||||
|
||||
socket.on("data", onData);
|
||||
socket.on("error", onError);
|
||||
});
|
||||
}
|
||||
|
||||
function sendHttpsRequestThroughTunnel(socket, verb, url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tlsSocket = tls.connect(
|
||||
{
|
||||
socket: socket,
|
||||
servername: url.hostname,
|
||||
},
|
||||
() => {
|
||||
tlsSocket.write(
|
||||
`${verb} ${url.pathname} HTTP/1.1\r\nHost: ${url.hostname}\r\nConnection: close\r\n\r\n`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
let tlsData = "";
|
||||
|
||||
tlsSocket.on("data", (data) => {
|
||||
tlsData += data.toString();
|
||||
});
|
||||
|
||||
tlsSocket.on("end", () => {
|
||||
resolve(tlsData);
|
||||
});
|
||||
|
||||
tlsSocket.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
import { before, after, describe, it } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import http from "http";
|
||||
import {
|
||||
createSafeChainProxy,
|
||||
mergeSafeChainProxyEnvironmentVariables,
|
||||
} from "./registryProxy.js";
|
||||
|
||||
describe("registryProxy.httpProxy", () => {
|
||||
let proxy, proxyHost, proxyPort;
|
||||
let testHttpServer, testHttpServerPort;
|
||||
|
||||
before(async () => {
|
||||
// Start safe-chain proxy
|
||||
proxy = createSafeChainProxy();
|
||||
await proxy.startServer();
|
||||
const envVars = mergeSafeChainProxyEnvironmentVariables([]);
|
||||
const proxyUrl = new URL(envVars.HTTPS_PROXY);
|
||||
proxyHost = proxyUrl.hostname;
|
||||
proxyPort = parseInt(proxyUrl.port, 10);
|
||||
|
||||
// Start a test HTTP server to forward requests to
|
||||
testHttpServer = http.createServer((req, res) => {
|
||||
if (req.url === "/test") {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end("HTTP test response");
|
||||
} else if (req.url === "/echo-headers") {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify(req.headers));
|
||||
} else if (req.url === "/echo-method") {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end(req.method);
|
||||
} else if (req.url === "/post-echo") {
|
||||
let body = "";
|
||||
req.on("data", (chunk) => {
|
||||
body += chunk.toString();
|
||||
});
|
||||
req.on("end", () => {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end(body);
|
||||
});
|
||||
} else if (req.url === "/404") {
|
||||
res.writeHead(404, { "Content-Type": "text/plain" });
|
||||
res.end("Not Found");
|
||||
} else {
|
||||
res.writeHead(200, { "Content-Type": "text/plain" });
|
||||
res.end("OK");
|
||||
}
|
||||
});
|
||||
|
||||
testHttpServerPort = await new Promise((resolve) => {
|
||||
testHttpServer.listen(0, () => {
|
||||
resolve(testHttpServer.address().port);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await proxy.stopServer();
|
||||
await new Promise((resolve) => {
|
||||
testHttpServer.close(() => resolve());
|
||||
setTimeout(resolve, 1000);
|
||||
});
|
||||
});
|
||||
|
||||
it("should forward HTTP GET requests", async () => {
|
||||
const response = await makeHttpProxyRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
`http://localhost:${testHttpServerPort}/test`,
|
||||
"GET"
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert.strictEqual(response.body, "HTTP test response");
|
||||
});
|
||||
|
||||
it("should forward HTTP POST requests with body", async () => {
|
||||
const postData = "test post data";
|
||||
const response = await makeHttpProxyRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
`http://localhost:${testHttpServerPort}/post-echo`,
|
||||
"POST",
|
||||
postData
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert.strictEqual(response.body, postData);
|
||||
});
|
||||
|
||||
it("should preserve request headers", async () => {
|
||||
const response = await makeHttpProxyRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
`http://localhost:${testHttpServerPort}/echo-headers`,
|
||||
"GET",
|
||||
null,
|
||||
{
|
||||
"X-Custom-Header": "test-value",
|
||||
"User-Agent": "test-agent/1.0",
|
||||
}
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
const headers = JSON.parse(response.body);
|
||||
assert.strictEqual(headers["x-custom-header"], "test-value");
|
||||
assert.strictEqual(headers["user-agent"], "test-agent/1.0");
|
||||
});
|
||||
|
||||
it("should preserve HTTP methods", async () => {
|
||||
const methods = ["GET", "POST", "PUT", "DELETE"];
|
||||
|
||||
for (const method of methods) {
|
||||
const response = await makeHttpProxyRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
`http://localhost:${testHttpServerPort}/echo-method`,
|
||||
method
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert.strictEqual(response.body, method);
|
||||
}
|
||||
});
|
||||
|
||||
it("should forward 404 responses correctly", async () => {
|
||||
const response = await makeHttpProxyRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
`http://localhost:${testHttpServerPort}/404`,
|
||||
"GET"
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
assert.strictEqual(response.body, "Not Found");
|
||||
});
|
||||
|
||||
it("should handle invalid host with 502 Bad Gateway", async () => {
|
||||
const response = await makeHttpProxyRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"http://invalid-host-that-does-not-exist.test:9999/test",
|
||||
"GET"
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 502);
|
||||
assert.ok(response.body.includes("Bad Gateway"));
|
||||
});
|
||||
|
||||
it("should handle HTTPS URLs sent to HTTP proxy", async () => {
|
||||
// Some clients incorrectly send https:// URLs to the HTTP proxy handler
|
||||
// instead of using CONNECT. The proxy should handle this gracefully.
|
||||
const response = await makeHttpProxyRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"https://registry.npmjs.org/lodash",
|
||||
"GET"
|
||||
);
|
||||
|
||||
// Should successfully forward the HTTPS request
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert.ok(response.body.includes("lodash"));
|
||||
});
|
||||
|
||||
it("should handle unsupported protocols with 502", async () => {
|
||||
const response = await makeHttpProxyRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"ftp://example.com/file.txt",
|
||||
"GET"
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 502);
|
||||
assert.ok(response.body.includes("Unsupported protocol"));
|
||||
});
|
||||
});
|
||||
|
||||
function makeHttpProxyRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
targetUrl,
|
||||
method = "GET",
|
||||
body = null,
|
||||
extraHeaders = {}
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
hostname: proxyHost,
|
||||
port: proxyPort,
|
||||
path: targetUrl,
|
||||
method: method,
|
||||
headers: {
|
||||
Host: new URL(targetUrl).host,
|
||||
...extraHeaders,
|
||||
},
|
||||
};
|
||||
|
||||
const req = http.request(options, (res) => {
|
||||
let responseBody = "";
|
||||
|
||||
res.on("data", (chunk) => {
|
||||
responseBody += chunk.toString();
|
||||
});
|
||||
|
||||
res.on("end", () => {
|
||||
resolve({
|
||||
statusCode: res.statusCode,
|
||||
headers: res.headers,
|
||||
body: responseBody,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
if (body) {
|
||||
req.write(body);
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
261
packages/safe-chain/src/registryProxy/registryProxy.mitm.spec.js
Normal file
261
packages/safe-chain/src/registryProxy/registryProxy.mitm.spec.js
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
import { before, after, describe, it } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import net from "net";
|
||||
import tls from "tls";
|
||||
import {
|
||||
createSafeChainProxy,
|
||||
mergeSafeChainProxyEnvironmentVariables,
|
||||
} from "./registryProxy.js";
|
||||
import { getCaCertPath } from "./certUtils.js";
|
||||
import fs from "fs";
|
||||
|
||||
describe("registryProxy.mitm", () => {
|
||||
let proxy, proxyHost, proxyPort;
|
||||
|
||||
before(async () => {
|
||||
proxy = createSafeChainProxy();
|
||||
await proxy.startServer();
|
||||
const envVars = mergeSafeChainProxyEnvironmentVariables([]);
|
||||
const proxyUrl = new URL(envVars.HTTPS_PROXY);
|
||||
proxyHost = proxyUrl.hostname;
|
||||
proxyPort = parseInt(proxyUrl.port, 10);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await proxy.stopServer();
|
||||
});
|
||||
|
||||
it("should intercept HTTPS requests to npm registry", async () => {
|
||||
const response = await makeRegistryRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.npmjs.org",
|
||||
"/lodash"
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
assert.ok(response.body.includes("lodash"));
|
||||
});
|
||||
|
||||
it("should allow non-malicious package downloads", async () => {
|
||||
const response = await makeRegistryRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.npmjs.org",
|
||||
"/lodash/-/lodash-4.17.21.tgz"
|
||||
);
|
||||
|
||||
// Should get a response (200 or redirect, but not 403 blocked)
|
||||
assert.notStrictEqual(response.statusCode, 403);
|
||||
});
|
||||
|
||||
it("should handle 404 responses correctly", async () => {
|
||||
const response = await makeRegistryRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.npmjs.org",
|
||||
"/this-package-definitely-does-not-exist-12345"
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 404);
|
||||
});
|
||||
|
||||
it("should handle query parameters in URL", async () => {
|
||||
const response = await makeRegistryRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.npmjs.org",
|
||||
"/lodash?write=true"
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
});
|
||||
|
||||
it("should generate valid certificates for yarn registry", async () => {
|
||||
const response = await makeRegistryRequest(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.yarnpkg.com",
|
||||
"/lodash"
|
||||
);
|
||||
|
||||
assert.strictEqual(response.statusCode, 200);
|
||||
});
|
||||
|
||||
it("should generate certificate with correct hostname in CN", async () => {
|
||||
const { cert } = await makeRegistryRequestAndGetCert(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.npmjs.org",
|
||||
"/lodash"
|
||||
);
|
||||
|
||||
// Check certificate common name matches the target hostname
|
||||
assert.strictEqual(cert.subject.CN, "registry.npmjs.org");
|
||||
|
||||
// Check Subject Alternative Name includes the hostname
|
||||
const san = cert.subjectaltname;
|
||||
assert.ok(san.includes("registry.npmjs.org"));
|
||||
|
||||
// Check certificate is issued by safe-chain CA
|
||||
assert.strictEqual(cert.issuer.CN, "safe-chain proxy");
|
||||
});
|
||||
|
||||
it("should generate different certificates for different hostnames", async () => {
|
||||
const { cert: cert1 } = await makeRegistryRequestAndGetCert(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.npmjs.org",
|
||||
"/lodash"
|
||||
);
|
||||
|
||||
const { cert: cert2 } = await makeRegistryRequestAndGetCert(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.yarnpkg.com",
|
||||
"/lodash"
|
||||
);
|
||||
|
||||
// Different hostnames should have different certificates
|
||||
assert.notStrictEqual(cert1.fingerprint, cert2.fingerprint);
|
||||
assert.strictEqual(cert1.subject.CN, "registry.npmjs.org");
|
||||
assert.strictEqual(cert2.subject.CN, "registry.yarnpkg.com");
|
||||
});
|
||||
|
||||
it("should cache generated certificates for same hostname", async () => {
|
||||
const { cert: cert1 } = await makeRegistryRequestAndGetCert(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.npmjs.org",
|
||||
"/lodash"
|
||||
);
|
||||
|
||||
const { cert: cert2 } = await makeRegistryRequestAndGetCert(
|
||||
proxyHost,
|
||||
proxyPort,
|
||||
"registry.npmjs.org",
|
||||
"/package/lodash"
|
||||
);
|
||||
|
||||
// Same hostname should get the same certificate (fingerprint)
|
||||
assert.strictEqual(cert1.fingerprint, cert2.fingerprint);
|
||||
});
|
||||
});
|
||||
|
||||
async function makeRegistryRequest(proxyHost, proxyPort, targetHost, path) {
|
||||
// Step 1: Connect to proxy
|
||||
const socket = await new Promise((resolve, reject) => {
|
||||
const sock = net.connect({ host: proxyHost, port: proxyPort }, () => {
|
||||
resolve(sock);
|
||||
});
|
||||
sock.on("error", reject);
|
||||
});
|
||||
|
||||
// Step 2: Send CONNECT request
|
||||
await new Promise((resolve) => {
|
||||
const connectRequest = `CONNECT ${targetHost}:443 HTTP/1.1\r\nHost: ${targetHost}:443\r\n\r\n`;
|
||||
socket.write(connectRequest);
|
||||
socket.once("data", resolve);
|
||||
});
|
||||
|
||||
// Step 3: Upgrade to TLS using the proxy's CA cert
|
||||
const tlsSocket = tls.connect({
|
||||
socket: socket,
|
||||
servername: targetHost,
|
||||
ca: fs.readFileSync(getCaCertPath()),
|
||||
rejectUnauthorized: true,
|
||||
});
|
||||
|
||||
await new Promise((resolve) => {
|
||||
tlsSocket.on("secureConnect", resolve);
|
||||
});
|
||||
|
||||
// Step 4: Send HTTP request over TLS
|
||||
const httpRequest = `GET ${path} HTTP/1.1\r\nHost: ${targetHost}\r\nConnection: close\r\n\r\n`;
|
||||
tlsSocket.write(httpRequest);
|
||||
|
||||
// Step 5: Read response
|
||||
return new Promise((resolve, reject) => {
|
||||
let data = "";
|
||||
|
||||
tlsSocket.on("data", (chunk) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
|
||||
tlsSocket.on("end", () => {
|
||||
const lines = data.split("\r\n");
|
||||
const statusLine = lines[0];
|
||||
const statusCode = parseInt(statusLine.split(" ")[1]);
|
||||
|
||||
// Find body after empty line
|
||||
const emptyLineIndex = lines.findIndex(line => line === "");
|
||||
const body = lines.slice(emptyLineIndex + 1).join("\r\n");
|
||||
|
||||
resolve({ statusCode, body });
|
||||
});
|
||||
|
||||
tlsSocket.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function makeRegistryRequestAndGetCert(proxyHost, proxyPort, targetHost, path) {
|
||||
// Step 1: Connect to proxy
|
||||
const socket = await new Promise((resolve, reject) => {
|
||||
const sock = net.connect({ host: proxyHost, port: proxyPort }, () => {
|
||||
resolve(sock);
|
||||
});
|
||||
sock.on("error", reject);
|
||||
});
|
||||
|
||||
// Step 2: Send CONNECT request
|
||||
await new Promise((resolve) => {
|
||||
const connectRequest = `CONNECT ${targetHost}:443 HTTP/1.1\r\nHost: ${targetHost}:443\r\n\r\n`;
|
||||
socket.write(connectRequest);
|
||||
socket.once("data", resolve);
|
||||
});
|
||||
|
||||
// Step 3: Upgrade to TLS and capture certificate
|
||||
const tlsSocket = tls.connect({
|
||||
socket: socket,
|
||||
servername: targetHost,
|
||||
ca: fs.readFileSync(getCaCertPath()),
|
||||
rejectUnauthorized: true,
|
||||
});
|
||||
|
||||
let peerCert;
|
||||
await new Promise((resolve) => {
|
||||
tlsSocket.on("secureConnect", () => {
|
||||
peerCert = tlsSocket.getPeerCertificate();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Step 4: Send HTTP request over TLS
|
||||
const httpRequest = `GET ${path} HTTP/1.1\r\nHost: ${targetHost}\r\nConnection: close\r\n\r\n`;
|
||||
tlsSocket.write(httpRequest);
|
||||
|
||||
// Step 5: Read response
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
let data = "";
|
||||
|
||||
tlsSocket.on("data", (chunk) => {
|
||||
data += chunk.toString();
|
||||
});
|
||||
|
||||
tlsSocket.on("end", () => {
|
||||
const lines = data.split("\r\n");
|
||||
const statusLine = lines[0];
|
||||
const statusCode = parseInt(statusLine.split(" ")[1]);
|
||||
|
||||
// Find body after empty line
|
||||
const emptyLineIndex = lines.findIndex(line => line === "");
|
||||
const body = lines.slice(emptyLineIndex + 1).join("\r\n");
|
||||
|
||||
resolve({ statusCode, body });
|
||||
});
|
||||
|
||||
tlsSocket.on("error", reject);
|
||||
});
|
||||
|
||||
return { cert: peerCert, response };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue