This commit is contained in:
Reinier Criel 2025-11-10 10:46:15 -08:00
parent 76acf43128
commit e04c4b6f21
7 changed files with 92 additions and 19 deletions

View file

@ -39,3 +39,7 @@ export function setEcoSystem(setting) {
export const LOGGING_SILENT = "silent";
export const LOGGING_NORMAL = "normal";
export const LOGGING_VERBOSE = "verbose";
// OS trust store paths
export const DARWIN_CA_PATH = "/Library/Keychains/System.keychain";
export const LINUX_CA_PATH = "/usr/local/share/ca-certificates/safe-chain-ca.crt";

View file

@ -1,7 +1,11 @@
import { ui } from "../../environment/userInteraction.js";
import { safeSpawn } from "../../utils/safeSpawn.js";
import { mergeSafeChainProxyEnvironmentVariables } from "../../registryProxy/registryProxy.js";
import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
import { installSafeChainCA } from "../../registryProxy/certUtils.js";
function shouldMockCAInstall() {
return process.env.SAFE_CHAIN_TEST_SKIP_CA_INSTALL === "1";
}
/**
* @param {string} command
@ -11,15 +15,11 @@ import { getCombinedCaBundlePath } from "../../registryProxy/certBundle.js";
*/
export async function runPip(command, args) {
try {
// Install Safe Chain CA in OS trust store before running pip, unless in test mode
if (!shouldMockCAInstall()) {
await installSafeChainCA();
}
const env = mergeSafeChainProxyEnvironmentVariables(process.env);
// Always provide Python with a complete CA bundle (Safe Chain CA + Mozilla + Node built-in roots)
// so that any network request made by pip, including those outside explicit CLI args,
// validates correctly under both MITM'd and tunneled HTTPS.
const combinedCaPath = getCombinedCaBundlePath();
env.REQUESTS_CA_BUNDLE = combinedCaPath;
env.SSL_CERT_FILE = combinedCaPath;
const result = await safeSpawn(command, args, {
stdio: "inherit",
env,

View file

@ -2,6 +2,8 @@ import forge from "node-forge";
import path from "path";
import fs from "fs";
import os from "os";
import { safeSpawn } from "../utils/safeSpawn.js";
import { DARWIN_CA_PATH, LINUX_CA_PATH } from "../config/settings.js";
const certFolder = path.join(os.homedir(), ".safe-chain", "certs");
const ca = loadCa();
@ -116,3 +118,69 @@ function generateCa() {
certificate: cert,
};
}
/**
* Checks if the Safe Chain CA certificate is already installed in the OS trust store.
* @returns {Promise<boolean>}
*/
export async function isSafeChainCAInstalled() {
const platform = os.platform();
try {
if (platform === "darwin") {
// macOS: check System Keychain for cert
const res = await safeSpawn("security", ["find-certificate", "-c", "safe-chain proxy", DARWIN_CA_PATH], { stdio: "pipe" });
return res.stdout.includes("safe-chain proxy");
} else if (platform === "linux") {
// Linux: check for CA file
return fs.existsSync(LINUX_CA_PATH);
} else if (platform === "win32") {
// Windows: check Root store for cert
return await safeSpawn("certutil", ["-store", "Root", "safe-chain proxy"], { stdio: "pipe" }).then(res => res.stdout.includes("safe-chain proxy"));
}
} catch (err) {
// If check fails, assume not installed
return false;
}
return false;
}
/**
* Installs the Safe Chain CA certificate in the OS trust store.
* Uses platform-specific commands. Optionally uses npm packages if available.
* @returns {Promise<void>}
*/
export async function installSafeChainCA() {
const caPath = getCaCertPath();
const platform = os.platform();
try {
const alreadyInstalled = await isSafeChainCAInstalled();
if (alreadyInstalled) {
console.log("Safe Chain CA already installed in OS trust store.");
return;
}
if (platform === "darwin") {
// macOS: use security CLI
await safeSpawn("sudo", [
"security",
"add-trusted-cert",
"-d",
"-r", "trustRoot",
"-k", DARWIN_CA_PATH,
caPath
], { stdio: "inherit" });
} else if (platform === "linux") {
// Linux: use update-ca-certificates
await safeSpawn("sudo", ["cp", caPath, LINUX_CA_PATH], { stdio: "inherit" });
await safeSpawn("sudo", ["update-ca-certificates"], { stdio: "inherit" });
} else if (platform === "win32") {
// Windows: use certutil
await safeSpawn("certutil", ["-addstore", "-f", "Root", caPath], { stdio: "inherit" });
} else {
throw new Error("Unsupported OS for automatic CA installation. Please install manually.");
}
console.log("Safe Chain CA installed in OS trust store.");
} catch (/** @type any */ error) {
console.error("Failed to install Safe Chain CA:", error.message);
throw error;
}
}