Combine certificates

This commit is contained in:
Reinier Criel 2025-10-30 16:00:32 -07:00
parent 1755fe829c
commit f38a12c6d5
8 changed files with 243 additions and 153 deletions

View file

@ -0,0 +1,90 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import certifi from "certifi";
import tls from "node:tls";
import { X509Certificate } from "node:crypto";
import { getCaCertPath } from "../../../registryProxy/certUtils.js";
/**
* Check if a PEM string contains only parsable cert blocks.
*/
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;
}
}
let cachedPath = null;
/**
* Build a combined CA bundle specifically for pip 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
* */
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-python-ca-bundle.pem");
fs.writeFileSync(target, combined, { encoding: "utf8" });
cachedPath = target;
return cachedPath;
}