@cyclonedx/cdxgen
Version:
Creates CycloneDX Software Bill of Materials (SBOM) from source or container image
111 lines (99 loc) • 3.17 kB
JavaScript
#!/usr/bin/env node
import fs from "node:fs";
import process from "node:process";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { signBom } from "../lib/helpers/bomSigner.js";
import {
getNonCycloneDxErrorMessage,
isCycloneDxBom,
} from "../lib/helpers/bomUtils.js";
import { retrieveCdxgenVersion, safeExistsSync } from "../lib/helpers/utils.js";
const _yargs = yargs(hideBin(process.argv));
const args = _yargs
.option("input", {
alias: "i",
default: "bom.json",
description: "Input SBOM json to sign.",
})
.option("output", {
alias: "o",
description: "Output signed SBOM json. Defaults to overwriting input.",
})
.option("private-key", {
alias: "k",
demandOption: true,
description: "Private key in PEM format.",
})
.option("algorithm", {
alias: "a",
default: "RS512",
description: "JSF Signature Algorithm (e.g., RS512, ES256, Ed25519).",
})
.option("mode", {
alias: "m",
default: "replace",
choices: ["replace", "signers", "chain"],
description:
"Signature mode. Use 'signers' for multi-signing, 'chain' for sequential chaining.",
})
.option("key-id", {
description:
"Optional identifier for the key (keyId) to embed in the signature block.",
})
.option("sign-components", {
type: "boolean",
default: true,
description:
"Sign granular components. Disable (--no-sign-components) when appending multi-signatures.",
})
.option("sign-services", {
type: "boolean",
default: true,
description:
"Sign granular services. Disable (--no-sign-services) when appending multi-signatures.",
})
.option("sign-annotations", {
type: "boolean",
default: true,
description:
"Sign granular annotations. Disable (--no-sign-annotations) when appending multi-signatures.",
})
.scriptName("cdx-sign")
.version(retrieveCdxgenVersion())
.help()
.wrap(Math.min(120, yargs().terminalWidth())).argv;
if (!safeExistsSync(args.input)) {
console.error(`Input file '${args.input}' not found.`);
process.exit(1);
}
if (!safeExistsSync(args.privateKey)) {
console.error(`Private key file '${args.privateKey}' not found.`);
process.exit(1);
}
try {
const bomJson = JSON.parse(fs.readFileSync(args.input, "utf8"));
const privateKey = fs.readFileSync(args.privateKey, "utf8");
if (!isCycloneDxBom(bomJson)) {
console.error(getNonCycloneDxErrorMessage(bomJson, "cdx-sign"));
process.exit(1);
}
const signedBom = signBom(bomJson, {
privateKey,
algorithm: args.algorithm,
keyId: args.keyId,
mode: args.mode,
signComponents: args.signComponents,
signServices: args.signServices,
signAnnotations: args.signAnnotations,
});
const outputPath = args.output || args.input;
fs.writeFileSync(outputPath, JSON.stringify(signedBom, null, null));
console.log(`Successfully signed BOM and saved to '${outputPath}'`);
console.log(
`Mode: ${args.mode} | Algorithm: ${args.algorithm}${args.keyId ? ` | KeyId: ${args.keyId}` : ""}`,
);
} catch (error) {
console.error("SBOM signing failed:", error.message);
process.exit(1);
}