mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Adapt per comments
This commit is contained in:
parent
890fee83ad
commit
297a264fe0
3 changed files with 27 additions and 50 deletions
|
|
@ -115,41 +115,20 @@ function loadCa() {
|
||||||
const keyPath = path.join(certFolder, "ca-key.pem");
|
const keyPath = path.join(certFolder, "ca-key.pem");
|
||||||
const certPath = path.join(certFolder, "ca-cert.pem");
|
const certPath = path.join(certFolder, "ca-cert.pem");
|
||||||
|
|
||||||
let existingPrivateKey = null;
|
|
||||||
|
|
||||||
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
|
||||||
const privateKeyPem = fs.readFileSync(keyPath, "utf8");
|
const privateKeyPem = fs.readFileSync(keyPath, "utf8");
|
||||||
const certPem = fs.readFileSync(certPath, "utf8");
|
const certPem = fs.readFileSync(certPath, "utf8");
|
||||||
const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
|
const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
|
||||||
const certificate = forge.pki.certificateFromPem(certPem);
|
const certificate = forge.pki.certificateFromPem(certPem);
|
||||||
|
|
||||||
existingPrivateKey = privateKey;
|
|
||||||
|
|
||||||
// Don't return a cert that is valid for less than 1 hour
|
// Don't return a cert that is valid for less than 1 hour
|
||||||
// Some extensions were added in a later phase, ensure it has them or regenerate
|
|
||||||
const oneHourFromNow = new Date(Date.now() + 60 * 60 * 1000);
|
const oneHourFromNow = new Date(Date.now() + 60 * 60 * 1000);
|
||||||
/** @type {any} */
|
if (certificate.validity.notAfter > oneHourFromNow) {
|
||||||
const basicConstraints = certificate.getExtension("basicConstraints");
|
|
||||||
const hasCriticalBasicConstraints = Boolean(
|
|
||||||
basicConstraints && basicConstraints.critical
|
|
||||||
);
|
|
||||||
const hasSubjectKeyIdentifier = Boolean(
|
|
||||||
certificate.getExtension("subjectKeyIdentifier")
|
|
||||||
);
|
|
||||||
const hasAuthorityKeyIdentifier = Boolean(
|
|
||||||
certificate.getExtension("authorityKeyIdentifier")
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
certificate.validity.notAfter > oneHourFromNow &&
|
|
||||||
hasCriticalBasicConstraints &&
|
|
||||||
hasSubjectKeyIdentifier &&
|
|
||||||
hasAuthorityKeyIdentifier
|
|
||||||
) {
|
|
||||||
return { privateKey, certificate };
|
return { privateKey, certificate };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { privateKey, certificate } = generateCa(existingPrivateKey || undefined);
|
const { privateKey, certificate } = generateCa();
|
||||||
fs.mkdirSync(certFolder, { recursive: true });
|
fs.mkdirSync(certFolder, { recursive: true });
|
||||||
fs.writeFileSync(keyPath, forge.pki.privateKeyToPem(privateKey));
|
fs.writeFileSync(keyPath, forge.pki.privateKeyToPem(privateKey));
|
||||||
fs.writeFileSync(certPath, forge.pki.certificateToPem(certificate));
|
fs.writeFileSync(certPath, forge.pki.certificateToPem(certificate));
|
||||||
|
|
@ -157,21 +136,8 @@ function loadCa() {
|
||||||
return { privateKey, certificate };
|
return { privateKey, certificate };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function generateCa() {
|
||||||
* Reconstruct the public key from the existing private key so renewed/self-signed CA certificates keep the same key material,
|
const keys = forge.pki.rsa.generateKeyPair(2048);
|
||||||
* preserving SKI/AKI continuity
|
|
||||||
* @param {forge.pki.PrivateKey} [existingPrivateKey]
|
|
||||||
*/
|
|
||||||
function generateCa(existingPrivateKey) {
|
|
||||||
const keys = existingPrivateKey
|
|
||||||
? {
|
|
||||||
privateKey: existingPrivateKey,
|
|
||||||
publicKey: forge.pki.setRsaPublicKey(
|
|
||||||
/** @type {any} */(existingPrivateKey).n,
|
|
||||||
/** @type {any} */(existingPrivateKey).e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
: forge.pki.rsa.generateKeyPair(2048);
|
|
||||||
|
|
||||||
const cert = forge.pki.createCertificate();
|
const cert = forge.pki.createCertificate();
|
||||||
cert.publicKey = keys.publicKey;
|
cert.publicKey = keys.publicKey;
|
||||||
|
|
@ -205,7 +171,7 @@ function generateCa(existingPrivateKey) {
|
||||||
keyIdentifier,
|
keyIdentifier,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
cert.sign(/** @type {any} */(keys.privateKey), forge.md.sha256.create());
|
cert.sign(keys.privateKey, forge.md.sha256.create());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
privateKey: keys.privateKey,
|
privateKey: keys.privateKey,
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,6 @@ const knownPipRegistries = [
|
||||||
"pythonhosted.org",
|
"pythonhosted.org",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Pattern for sdist extensions
|
|
||||||
const sdistExtWithMetadataRe = /\.(tar\.gz|zip|tar\.bz2|tar\.xz)(\.metadata)?$/i;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
* @returns {import("./interceptorBuilder.js").Interceptor | undefined}
|
* @returns {import("./interceptorBuilder.js").Interceptor | undefined}
|
||||||
|
|
@ -40,8 +37,9 @@ function buildPipInterceptor(registry) {
|
||||||
// Per python, packages that differ only by hyphen vs underscore are considered the same.
|
// Per python, packages that differ only by hyphen vs underscore are considered the same.
|
||||||
const hyphenName = packageName?.includes("_") ? packageName.replace(/_/g, "-") : packageName;
|
const hyphenName = packageName?.includes("_") ? packageName.replace(/_/g, "-") : packageName;
|
||||||
|
|
||||||
const isMalicious = await isMalwarePackage(packageName, version)
|
const isMalicious =
|
||||||
|| await isMalwarePackage(hyphenName, version);
|
await isMalwarePackage(packageName, version)
|
||||||
|
|| await isMalwarePackage(hyphenName, version);
|
||||||
|
|
||||||
if (isMalicious) {
|
if (isMalicious) {
|
||||||
reqContext.blockMalware(packageName, version);
|
reqContext.blockMalware(packageName, version);
|
||||||
|
|
@ -83,17 +81,20 @@ function parsePipPackageFromUrl(url, registry) {
|
||||||
// Example sdist: https://files.pythonhosted.org/packages/xx/yy/requests-2.28.1.tar.gz
|
// Example sdist: https://files.pythonhosted.org/packages/xx/yy/requests-2.28.1.tar.gz
|
||||||
|
|
||||||
// Wheel (.whl) and Poetry's preflight metadata (.whl.metadata)
|
// Wheel (.whl) and Poetry's preflight metadata (.whl.metadata)
|
||||||
if (filename.endsWith(".whl") || filename.endsWith(".whl.metadata")) {
|
// Examples:
|
||||||
const base = filename.endsWith(".whl")
|
// foo_bar-2.0.0-py3-none-any.whl
|
||||||
? filename.slice(0, -4)
|
// foo_bar-2.0.0-py3-none-any.whl.metadata
|
||||||
: filename.slice(0, -".whl.metadata".length);
|
const wheelExtRe = /\.whl(?:\.metadata)?$/;
|
||||||
|
const wheelExtMatch = filename.match(wheelExtRe);
|
||||||
|
if (wheelExtMatch) {
|
||||||
|
const base = filename.replace(wheelExtRe, "");
|
||||||
const firstDash = base.indexOf("-");
|
const firstDash = base.indexOf("-");
|
||||||
if (firstDash > 0) {
|
if (firstDash > 0) {
|
||||||
const dist = base.slice(0, firstDash); // may contain underscores
|
const dist = base.slice(0, firstDash); // may contain underscores
|
||||||
const rest = base.slice(firstDash + 1); // version + the rest of tags
|
const rest = base.slice(firstDash + 1); // version + the rest of tags
|
||||||
const secondDash = rest.indexOf("-");
|
const secondDash = rest.indexOf("-");
|
||||||
const rawVersion = secondDash >= 0 ? rest.slice(0, secondDash) : rest;
|
const rawVersion = secondDash >= 0 ? rest.slice(0, secondDash) : rest;
|
||||||
packageName = dist; // preserve underscores
|
packageName = dist;
|
||||||
version = rawVersion;
|
version = rawVersion;
|
||||||
// Reject "latest" as it's a placeholder, not a real version
|
// Reject "latest" as it's a placeholder, not a real version
|
||||||
// When version is "latest", this signals the URL doesn't contain actual version info
|
// When version is "latest", this signals the URL doesn't contain actual version info
|
||||||
|
|
@ -106,6 +107,7 @@ function parsePipPackageFromUrl(url, registry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source dist (sdist) and potential metadata sidecars (e.g., .tar.gz.metadata)
|
// Source dist (sdist) and potential metadata sidecars (e.g., .tar.gz.metadata)
|
||||||
|
const sdistExtWithMetadataRe = /\.(tar\.gz|zip|tar\.bz2|tar\.xz)(\.metadata)?$/i;
|
||||||
const sdistExtMatch = filename.match(sdistExtWithMetadataRe);
|
const sdistExtMatch = filename.match(sdistExtWithMetadataRe);
|
||||||
if (sdistExtMatch) {
|
if (sdistExtMatch) {
|
||||||
const base = filename.replace(sdistExtWithMetadataRe, "");
|
const base = filename.replace(sdistExtWithMetadataRe, "");
|
||||||
|
|
@ -122,7 +124,6 @@ function parsePipPackageFromUrl(url, registry) {
|
||||||
return { packageName, version };
|
return { packageName, version };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown file type or invalid
|
// Unknown file type or invalid
|
||||||
return { packageName: undefined, version: undefined };
|
return { packageName: undefined, version: undefined };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,11 @@ describe("pipInterceptor", async () => {
|
||||||
url: "https://pypi.org/packages/source/f/foo_bar/foo_bar-2.0.0-py3-none-any.whl",
|
url: "https://pypi.org/packages/source/f/foo_bar/foo_bar-2.0.0-py3-none-any.whl",
|
||||||
expected: { packageName: "foo-bar", version: "2.0.0" },
|
expected: { packageName: "foo-bar", version: "2.0.0" },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Poetry preflight metadata alongside wheel (.whl.metadata)
|
||||||
|
url: "https://files.pythonhosted.org/packages/xx/yy/foo_bar-2.0.0-py3-none-any.whl.metadata",
|
||||||
|
expected: { packageName: "foo-bar", version: "2.0.0" },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: "https://files.pythonhosted.org/packages/xx/yy/foo_bar-2.0.0-py3-none-any.whl",
|
url: "https://files.pythonhosted.org/packages/xx/yy/foo_bar-2.0.0-py3-none-any.whl",
|
||||||
expected: { packageName: "foo-bar", version: "2.0.0" },
|
expected: { packageName: "foo-bar", version: "2.0.0" },
|
||||||
|
|
@ -46,6 +51,11 @@ describe("pipInterceptor", async () => {
|
||||||
url: "https://pypi.org/packages/source/f/foo_bar/foo_bar-2.0.0b1.tar.gz",
|
url: "https://pypi.org/packages/source/f/foo_bar/foo_bar-2.0.0b1.tar.gz",
|
||||||
expected: { packageName: "foo-bar", version: "2.0.0b1" },
|
expected: { packageName: "foo-bar", version: "2.0.0b1" },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// sdist with metadata sidecar (.tar.gz.metadata)
|
||||||
|
url: "https://files.pythonhosted.org/packages/xx/yy/foo_bar-2.0.0.tar.gz.metadata",
|
||||||
|
expected: { packageName: "foo-bar", version: "2.0.0" },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
url: "https://pypi.org/packages/source/f/foo_bar/foo_bar-2.0.0rc1.tar.gz",
|
url: "https://pypi.org/packages/source/f/foo_bar/foo_bar-2.0.0rc1.tar.gz",
|
||||||
expected: { packageName: "foo-bar", version: "2.0.0rc1" },
|
expected: { packageName: "foo-bar", version: "2.0.0rc1" },
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue