mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Combine NODE_EXTRA_CA_CERTS with Safe Chain's certificate bundle
This commit is contained in:
parent
15cc6ff7fe
commit
7086cfa277
2 changed files with 71 additions and 2 deletions
|
|
@ -6,6 +6,7 @@ import certifi from "certifi";
|
||||||
import tls from "node:tls";
|
import tls from "node:tls";
|
||||||
import { X509Certificate } from "node:crypto";
|
import { X509Certificate } from "node:crypto";
|
||||||
import { getCaCertPath } from "./certUtils.js";
|
import { getCaCertPath } from "./certUtils.js";
|
||||||
|
import { ui } from "../environment/userInteraction.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a PEM string contains only parsable cert blocks.
|
* Check if a PEM string contains only parsable cert blocks.
|
||||||
|
|
@ -93,3 +94,68 @@ export function getCombinedCaBundlePath() {
|
||||||
cachedPath = target;
|
cachedPath = target;
|
||||||
return cachedPath;
|
return cachedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read user certificate file.
|
||||||
|
* @param {string} certPath - Path to certificate file
|
||||||
|
* @returns {string | null} Certificate PEM content or null if invalid/unreadable
|
||||||
|
*/
|
||||||
|
function readUserCertificateFile(certPath) {
|
||||||
|
try {
|
||||||
|
// Validate path is a string and not attempting path traversal
|
||||||
|
if (typeof certPath !== "string" || certPath.includes("..") || certPath.startsWith("/")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(certPath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const certPathAbsolute = path.resolve(certPath);
|
||||||
|
// Verify it's an absolute path (cross-platform)
|
||||||
|
if (!path.isAbsolute(certPathAbsolute)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(certPathAbsolute, "utf8");
|
||||||
|
return content && isParsable(content) ? content : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine user's existing NODE_EXTRA_CA_CERTS with Safe Chain's CA certificate.
|
||||||
|
* If user has NODE_EXTRA_CA_CERTS set, it's merged with Safe Chain CA.
|
||||||
|
*
|
||||||
|
* @param {string | undefined} userCertPath - User's existing NODE_EXTRA_CA_CERTS path (if any)
|
||||||
|
* @returns {string} Path to the final CA bundle
|
||||||
|
*/
|
||||||
|
export function getCombinedCaBundlePathWithUserCerts(userCertPath) {
|
||||||
|
const parts = [];
|
||||||
|
|
||||||
|
// 1) Add Safe Chain CA (for MITM'd registries)
|
||||||
|
const safeChainPath = getCaCertPath();
|
||||||
|
try {
|
||||||
|
const safeChainPem = fs.readFileSync(safeChainPath, "utf8");
|
||||||
|
if (isParsable(safeChainPem)) parts.push(safeChainPem.trim());
|
||||||
|
} catch {
|
||||||
|
// Ignore if Safe Chain CA is not available
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Add user's certificates if provided
|
||||||
|
if (userCertPath) {
|
||||||
|
const userPem = readUserCertificateFile(userCertPath);
|
||||||
|
if (userPem) {
|
||||||
|
parts.push(userPem.trim());
|
||||||
|
ui.writeWarning(`Safe-chain: Merging user's NODE_EXTRA_CA_CERTS from ${userCertPath}`);
|
||||||
|
} else {
|
||||||
|
ui.writeWarning(`Safe-chain: Could not read or parse user's NODE_EXTRA_CA_CERTS from ${userCertPath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalCombined = parts.filter(Boolean).join("\n");
|
||||||
|
const target = path.join(os.tmpdir(), `safe-chain-ca-bundle-${Date.now()}.pem`);
|
||||||
|
fs.writeFileSync(target, finalCombined, { encoding: "utf8" });
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import * as http from "http";
|
||||||
import { tunnelRequest } from "./tunnelRequestHandler.js";
|
import { tunnelRequest } from "./tunnelRequestHandler.js";
|
||||||
import { mitmConnect } from "./mitmRequestHandler.js";
|
import { mitmConnect } from "./mitmRequestHandler.js";
|
||||||
import { handleHttpProxyRequest } from "./plainHttpProxy.js";
|
import { handleHttpProxyRequest } from "./plainHttpProxy.js";
|
||||||
import { getCaCertPath } from "./certUtils.js";
|
import { getCombinedCaBundlePathWithUserCerts } from "./certBundle.js";
|
||||||
import { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { createInterceptorForUrl } from "./interceptors/createInterceptorForEcoSystem.js";
|
import { createInterceptorForUrl } from "./interceptors/createInterceptorForEcoSystem.js";
|
||||||
|
|
@ -37,10 +37,13 @@ function getSafeChainProxyEnvironmentVariables() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyUrl = `http://localhost:${state.port}`;
|
const proxyUrl = `http://localhost:${state.port}`;
|
||||||
|
const userNodeExtraCaCerts = process.env.NODE_EXTRA_CA_CERTS;
|
||||||
|
const caCertPath = getCombinedCaBundlePathWithUserCerts(userNodeExtraCaCerts);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
HTTPS_PROXY: proxyUrl,
|
HTTPS_PROXY: proxyUrl,
|
||||||
GLOBAL_AGENT_HTTP_PROXY: proxyUrl,
|
GLOBAL_AGENT_HTTP_PROXY: proxyUrl,
|
||||||
NODE_EXTRA_CA_CERTS: getCaCertPath(),
|
NODE_EXTRA_CA_CERTS: caCertPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue