From ca5c1e8869e964aec1e8ed57d2919b183c4051ce Mon Sep 17 00:00:00 2001 From: Reinier Criel Date: Mon, 10 Nov 2025 10:58:18 -0800 Subject: [PATCH] Fix unit test --- .../packagemanager/pip/runPipCommand.spec.js | 7 -- .../src/registryProxy/certBundle.js | 95 ------------------- .../src/registryProxy/certBundle.spec.js | 71 -------------- .../safe-chain/src/registryProxy/certUtils.js | 9 +- 4 files changed, 1 insertion(+), 181 deletions(-) delete mode 100644 packages/safe-chain/src/registryProxy/certBundle.js delete mode 100644 packages/safe-chain/src/registryProxy/certBundle.spec.js diff --git a/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js b/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js index d7a0f93..ff1a103 100644 --- a/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js +++ b/packages/safe-chain/src/packagemanager/pip/runPipCommand.spec.js @@ -28,13 +28,6 @@ describe("runPipCommand environment variable handling", () => { }, }); - // Mock certBundle to return a test combined bundle path - mock.module("../../registryProxy/certBundle.js", { - namedExports: { - getCombinedCaBundlePath: () => "/tmp/test-combined-ca.pem", - }, - }); - const mod = await import("./runPipCommand.js"); runPip = mod.runPip; }); diff --git a/packages/safe-chain/src/registryProxy/certBundle.js b/packages/safe-chain/src/registryProxy/certBundle.js deleted file mode 100644 index 956279d..0000000 --- a/packages/safe-chain/src/registryProxy/certBundle.js +++ /dev/null @@ -1,95 +0,0 @@ -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -// @ts-ignore - certifi has no type definitions -import certifi from "certifi"; -import tls from "node:tls"; -import { X509Certificate } from "node:crypto"; -import { getCaCertPath } from "./certUtils.js"; - -/** - * Check if a PEM string contains only parsable cert blocks. - * @param {string} pem - PEM-encoded certificate string - * @returns {boolean} - */ -function isParsable(pem) { - if (!pem || typeof pem !== "string") return false; - const begin = "-----BEGIN CERTIFICATE-----"; - const end = "-----END CERTIFICATE-----"; - const blocks = []; - - let idx = 0; - while (idx < pem.length) { - const start = pem.indexOf(begin, idx); - if (start === -1) break; - const stop = pem.indexOf(end, start + begin.length); - if (stop === -1) break; - const blockEnd = stop + end.length; - blocks.push(pem.slice(start, blockEnd)); - idx = blockEnd; - } - - if (blocks.length === 0) return false; - try { - for (const b of blocks) { - // throw if invalid - new X509Certificate(b); - } - return true; - } catch { - return false; - } -} - -/** @type {string | null} */ -let cachedPath = null; - -/** - * Build a combined CA bundle for Python and Node HTTPS flows. - * - Includes Safe Chain CA (for MITM of known registries) - * - Includes Mozilla roots via npm `certifi` (public HTTPS) - * - Includes Node's built-in root certificates as a portable fallback - * @returns {string} Path to the combined CA bundle PEM file - */ -export function getCombinedCaBundlePath() { - if (cachedPath && fs.existsSync(cachedPath)) return cachedPath; - - // Concatenate PEM files - const parts = []; - - // 1) 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) certifi (Mozilla CA bundle for all public HTTPS) - try { - const certifiPem = fs.readFileSync(certifi, "utf8"); - if (isParsable(certifiPem)) parts.push(certifiPem.trim()); - } catch { - // Ignore if certifi bundle is not available - } - - // 3) Node's built-in root certificates - try { - const nodeRoots = tls.rootCertificates; - if (Array.isArray(nodeRoots) && nodeRoots.length) { - for (const rootPem of nodeRoots) { - if (typeof rootPem !== "string") continue; - if (isParsable(rootPem)) parts.push(rootPem.trim()); - } - } - } catch { - // Ignore if unavailable - } - - const combined = parts.filter(Boolean).join("\n"); - const target = path.join(os.tmpdir(), "safe-chain-ca-bundle.pem"); - fs.writeFileSync(target, combined, { encoding: "utf8" }); - cachedPath = target; - return cachedPath; -} diff --git a/packages/safe-chain/src/registryProxy/certBundle.spec.js b/packages/safe-chain/src/registryProxy/certBundle.spec.js deleted file mode 100644 index 2f26d51..0000000 --- a/packages/safe-chain/src/registryProxy/certBundle.spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import { describe, it, beforeEach, mock } from "node:test"; -import assert from "node:assert"; -import fs from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import tls from "node:tls"; - -// Utility to remove the generated bundle so the module rebuilds it on demand -function removeBundleIfExists() { - const target = path.join(os.tmpdir(), "safe-chain-ca-bundle.pem"); - try { - if (fs.existsSync(target)) fs.unlinkSync(target); - } catch { - // ignore - } -} - -describe("certBundle.getCombinedCaBundlePath", () => { - beforeEach(() => { - mock.restoreAll(); - removeBundleIfExists(); - }); - - it("includes Safe Chain CA when parsable and produces a PEM bundle", async () => { - // Prepare a temporary Safe Chain CA file with a recognizable marker and a valid cert block - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pipcabundle-")); - const safeChainPath = path.join(tmpDir, "safechain-ca.pem"); - const marker = "# SAFE_CHAIN_TEST_MARKER"; - const rootPem = typeof tls.rootCertificates?.[0] === "string" ? tls.rootCertificates[0] : ""; - assert.ok(rootPem.includes("BEGIN CERTIFICATE"), "Environment lacks Node root certificates for test"); - fs.writeFileSync(safeChainPath, `${marker}\n${rootPem}`, "utf8"); - - // Mock the certUtils.getCaCertPath to return our temp file - mock.module("./certUtils.js", { - namedExports: { - getCaCertPath: () => safeChainPath, - }, - }); - - const { getCombinedCaBundlePath } = await import("./certBundle.js"); - const bundlePath = getCombinedCaBundlePath(); - assert.ok(fs.existsSync(bundlePath), "Bundle path should exist"); - const contents = fs.readFileSync(bundlePath, "utf8"); - assert.match(contents, /-----BEGIN CERTIFICATE-----/); - assert.ok(contents.includes(marker), "Bundle should include Safe Chain CA content when parsable"); - }); - - it("ignores invalid Safe Chain CA but still builds from other sources", async () => { - // Write an invalid file (no cert blocks) - const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pipcabundle-")); - const safeChainPath = path.join(tmpDir, "safechain-invalid.pem"); - const invalidMarker = "INVALID_SAFE_CHAIN_CONTENT"; - fs.writeFileSync(safeChainPath, invalidMarker, "utf8"); - - // Mock the certUtils.getCaCertPath to return our invalid file - mock.module("./certUtils.js", { - namedExports: { - getCaCertPath: () => safeChainPath, - }, - }); - - // Ensure fresh build - removeBundleIfExists(); - const { getCombinedCaBundlePath } = await import("./certBundle.js"); - const bundlePath = getCombinedCaBundlePath(); - assert.ok(fs.existsSync(bundlePath), "Bundle path should exist"); - const contents = fs.readFileSync(bundlePath, "utf8"); - assert.match(contents, /-----BEGIN CERTIFICATE-----/, "Bundle should contain certificate blocks from certifi/Node roots"); - assert.ok(!contents.includes(invalidMarker), "Bundle should not include invalid Safe Chain content"); - }); -}); diff --git a/packages/safe-chain/src/registryProxy/certUtils.js b/packages/safe-chain/src/registryProxy/certUtils.js index 8d2e89c..181cfe3 100644 --- a/packages/safe-chain/src/registryProxy/certUtils.js +++ b/packages/safe-chain/src/registryProxy/certUtils.js @@ -160,14 +160,7 @@ export async function installSafeChainCA() { } if (platform === "darwin") { // macOS: use security CLI - await safeSpawn("sudo", [ - "security", - "add-trusted-cert", - "-d", - "-r", "trustRoot", - "-k", DARWIN_CA_PATH, - caPath - ], { stdio: "inherit" }); + 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" });