mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Fix unit test
This commit is contained in:
parent
e04c4b6f21
commit
ca5c1e8869
4 changed files with 1 additions and 181 deletions
|
|
@ -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");
|
const mod = await import("./runPipCommand.js");
|
||||||
runPip = mod.runPip;
|
runPip = mod.runPip;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -160,14 +160,7 @@ export async function installSafeChainCA() {
|
||||||
}
|
}
|
||||||
if (platform === "darwin") {
|
if (platform === "darwin") {
|
||||||
// macOS: use security CLI
|
// macOS: use security CLI
|
||||||
await safeSpawn("sudo", [
|
await safeSpawn("sudo", ["security", "add-trusted-cert", "-d", "-r", "trustRoot", "-k", DARWIN_CA_PATH, caPath], { stdio: "inherit" });
|
||||||
"security",
|
|
||||||
"add-trusted-cert",
|
|
||||||
"-d",
|
|
||||||
"-r", "trustRoot",
|
|
||||||
"-k", DARWIN_CA_PATH,
|
|
||||||
caPath
|
|
||||||
], { stdio: "inherit" });
|
|
||||||
} else if (platform === "linux") {
|
} else if (platform === "linux") {
|
||||||
// Linux: use update-ca-certificates
|
// Linux: use update-ca-certificates
|
||||||
await safeSpawn("sudo", ["cp", caPath, LINUX_CA_PATH], { stdio: "inherit" });
|
await safeSpawn("sudo", ["cp", caPath, LINUX_CA_PATH], { stdio: "inherit" });
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue