@cdxgen/cdxgen 13.4.0 → 13.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +25 -5
- package/bin/evinse.js +5 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +283 -1
- package/data/rules/cbom-security.yaml +235 -4
- package/data/rules/go-binaries.yaml +46 -0
- package/data/rules/golem-go.yaml +80 -0
- package/data/rules/hbom-compliance.yaml +40 -22
- package/data/rules/hbom-performance.yaml +7 -1
- package/data/rules/host-topology.yaml +1 -1
- package/data/rules/obom-runtime.yaml +31 -2
- package/data/rules/php-runtime.yaml +143 -0
- package/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +28 -3
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +73 -4
- package/lib/cli/jvmBom.js +126 -20
- package/lib/cli/managedBom.js +711 -71
- package/lib/cli/nativeBom.js +225 -28
- package/lib/core/env.js +30 -8
- package/lib/core/fs.js +28 -19
- package/lib/core/httpClient.js +74 -45
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +30 -20
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +229 -44
- package/lib/ecosystems/parsers-python.js +11 -10
- package/lib/ecosystems/rubyutils.js +6 -3
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +2 -1
- package/lib/ecosystems/scalasem.js +3 -1
- package/lib/evinser/evinser.js +126 -18
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +33 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +6 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +500 -58
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +168 -5
- package/lib/inventory/evidenceUtils.js +436 -23
- package/lib/inventory/formulationParsers.js +3 -2
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +89 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +487 -0
- package/lib/inventory/phpHostProbe.js +641 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/source.js +207 -3
- package/lib/inventory/spdx.js +144 -0
- package/lib/inventory/traceRunner.js +5 -1
- package/lib/inventory/tracedCrypto.js +129 -0
- package/lib/managers/binary.js +529 -183
- package/lib/managers/docker.js +166 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +34 -2
- package/lib/server/server.js +186 -1
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +5 -5
- package/lib/stages/postgen/ruleEngine.js +58 -1
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/pregen.js +117 -5
- package/lib/validator/bomValidator.js +26 -5
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +26 -7
- package/lib/validator/generated/validate-1.7.mjs +31 -12
- package/lib/validator/generated/validate-2.0.mjs +30 -11
- package/package.json +17 -17
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +9 -2
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +20 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +18 -2
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/projectTooling.d.ts +41 -0
- package/types/lib/core/projectTooling.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +26 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +10 -8
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +85 -13
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +18 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +4 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +25 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +10 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
- package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +14 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +2 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts +18 -0
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +145 -6
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +6 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +40 -2
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +29 -10
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/goBuildInfo.d.ts +10 -0
- package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +6 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/phpHostProbe.d.ts +35 -0
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rootfsCerts.d.ts +27 -0
- package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +28 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +15 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +4 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +22 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +10 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +12 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/email.d.ts +19 -0
- package/types/lib/parsers/email.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +13 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +8 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +14 -1
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -5,6 +5,7 @@ import { parse as loadYaml } from "yaml";
|
|
|
5
5
|
|
|
6
6
|
import { safeExistsSync } from "../core/fs.js";
|
|
7
7
|
import { sanitizeBomPropertyValue } from "../core/propertySanitizer.js";
|
|
8
|
+
import { isAgentSkillSpecValid } from "./agentSkillSpec.js";
|
|
8
9
|
import { parseJsonLike } from "./jsonLike.js";
|
|
9
10
|
import {
|
|
10
11
|
credentialIndicatorsForText,
|
|
@@ -279,18 +280,12 @@ function parseSkillFile(filePath, raw) {
|
|
|
279
280
|
);
|
|
280
281
|
}
|
|
281
282
|
// Record whether the frontmatter satisfies the open Agent Skills constraints
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
const specValid =
|
|
285
|
-
typeof metadata?.name === "string" &&
|
|
286
|
-
/^[a-z0-9-]{1,64}$/u.test(metadata.name) &&
|
|
287
|
-
typeof metadata?.description === "string" &&
|
|
288
|
-
metadata.description.length >= 1 &&
|
|
289
|
-
metadata.description.length <= 1024;
|
|
283
|
+
// as a single reviewer-friendly boolean rather than copying the raw values
|
|
284
|
+
// twice.
|
|
290
285
|
addUniqueProperty(
|
|
291
286
|
component.properties,
|
|
292
287
|
"cdx:skill:specValid",
|
|
293
|
-
|
|
288
|
+
isAgentSkillSpecValid(metadata) ? "true" : "false",
|
|
294
289
|
);
|
|
295
290
|
const skillDir = dirname(filePath);
|
|
296
291
|
for (const [subdir, property] of [
|
package/lib/inventory/deps.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lstatSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { homedir
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
3
|
import { delimiter as _delimiter, basename, dirname, join } from "node:path";
|
|
4
4
|
|
|
5
5
|
import { build, Purl } from "@cdxgen/cdx-purl";
|
|
@@ -10,6 +10,7 @@ import { commandOutputText } from "../core/buildLedger.js";
|
|
|
10
10
|
import { parseMavenArgs } from "../core/env.js";
|
|
11
11
|
import {
|
|
12
12
|
getAllFiles,
|
|
13
|
+
getTmpDir,
|
|
13
14
|
multiChecksumFile,
|
|
14
15
|
safeExistsSync,
|
|
15
16
|
safeMkdtempSync,
|
|
@@ -99,7 +100,8 @@ function jarPurl(group, name, version, classifier = undefined) {
|
|
|
99
100
|
* Collect maven dependencies
|
|
100
101
|
*
|
|
101
102
|
* @param {string} mavenCmd Maven command to use
|
|
102
|
-
* @param {string} basePath Path to the maven project
|
|
103
|
+
* @param {string} basePath Path to the maven project. Without it Maven is not
|
|
104
|
+
* run, and only the local repository is read when includeCacheDir is set.
|
|
103
105
|
* @param {boolean} cleanup Remove temporary directories
|
|
104
106
|
* @param {boolean} includeCacheDir Include maven and gradle cache directories
|
|
105
107
|
*/
|
|
@@ -118,7 +120,7 @@ export async function collectMvnDependencies(
|
|
|
118
120
|
safeExistsSync(repo),
|
|
119
121
|
) ||
|
|
120
122
|
join(homedir(), ".m2", "repository");
|
|
121
|
-
const tempDir = safeMkdtempSync(join(
|
|
123
|
+
const tempDir = safeMkdtempSync(join(getTmpDir(), "mvn-deps-"));
|
|
122
124
|
// No -U: forcing update checks re-requests every maven-metadata.xml and
|
|
123
125
|
// every artifact Maven already knows is missing, once per module.
|
|
124
126
|
let copyArgs = [
|
|
@@ -178,7 +180,7 @@ export async function collectMvnDependencies(
|
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
// Clean up
|
|
181
|
-
if (cleanup && tempDir?.startsWith(
|
|
183
|
+
if (cleanup && tempDir?.startsWith(getTmpDir())) {
|
|
182
184
|
safeRmSync(tempDir, { recursive: true, force: true });
|
|
183
185
|
}
|
|
184
186
|
return jarNSMapping;
|
package/lib/inventory/display.js
CHANGED
|
@@ -14,6 +14,7 @@ import { readReachablesSlices } from "./atomUtils.js";
|
|
|
14
14
|
import { formatOccurrenceEvidence } from "./evidenceUtils.js";
|
|
15
15
|
import {
|
|
16
16
|
formatHbomHardwareClassSummary,
|
|
17
|
+
getHbomPrivilegeGuidance,
|
|
17
18
|
getHbomSummary,
|
|
18
19
|
isHbomLikeBom,
|
|
19
20
|
} from "./hbomAnalysis.js";
|
|
@@ -475,7 +476,9 @@ export const buildTableSummaryLines = (
|
|
|
475
476
|
);
|
|
476
477
|
if (hbomSummary.requiresPrivilegedEnrichment) {
|
|
477
478
|
summaryLines.push(
|
|
478
|
-
|
|
479
|
+
hbomSummary.platform === "windows"
|
|
480
|
+
? getHbomPrivilegeGuidance(hbomSummary.platform)
|
|
481
|
+
: "Permission-sensitive enrichments were skipped or blocked. Re-run with --privileged where policy allows.",
|
|
479
482
|
);
|
|
480
483
|
}
|
|
481
484
|
}
|
|
@@ -526,6 +529,7 @@ const HBOM_COLUMN_PRIORITY = Object.freeze([
|
|
|
526
529
|
["cdx:hbom:securityMode", "securityMode"],
|
|
527
530
|
["cdx:hbom:health", "health"],
|
|
528
531
|
["cdx:hbom:smartStatus", "smartStatus"],
|
|
532
|
+
["cdx:hbom:healthStatus", "healthStatus"],
|
|
529
533
|
["cdx:hbom:powerSource", "powerSource"],
|
|
530
534
|
["cdx:hbom:maximumCapacity", "maximumCapacity"],
|
|
531
535
|
["cdx:hbom:chargePercent", "chargePercent"],
|
|
@@ -546,6 +550,7 @@ const HBOM_COLUMN_PRIORITY = Object.freeze([
|
|
|
546
550
|
["cdx:hostview:kernel_modules:count", "kernelMods"],
|
|
547
551
|
["cdx:hostview:mount_hardening:count", "runtimeMounts"],
|
|
548
552
|
["cdx:hostview:runtime-storage:count", "runtimeStorage"],
|
|
553
|
+
["cdx:hostview:logical_drives:count", "runtimeDrives"],
|
|
549
554
|
["cdx:hostview:linkedRuntimeCategoryCount", "runtimeLinks"],
|
|
550
555
|
]);
|
|
551
556
|
|
|
@@ -616,6 +621,7 @@ const HBOM_CLASS_PROPERTY_PRIORITY = Object.freeze({
|
|
|
616
621
|
storage: Object.freeze([
|
|
617
622
|
["cdx:hbom:capacity", "capacity"],
|
|
618
623
|
["cdx:hbom:smartStatus", "smart"],
|
|
624
|
+
["cdx:hbom:healthStatus", "healthStatus"],
|
|
619
625
|
["cdx:hbom:wearPercentageUsed", "wearUsed"],
|
|
620
626
|
["cdx:hbom:transport", "transport"],
|
|
621
627
|
["cdx:hbom:firmwareVersion", "firmware"],
|
|
@@ -630,6 +636,7 @@ const HBOM_CLASS_PROPERTY_PRIORITY = Object.freeze({
|
|
|
630
636
|
["cdx:hbom:isRemovable", "removable"],
|
|
631
637
|
["cdx:hostview:mount_hardening:count", "runtimeMounts"],
|
|
632
638
|
["cdx:hostview:runtime-storage:count", "runtimeStorage"],
|
|
639
|
+
["cdx:hostview:logical_drives:count", "runtimeDrives"],
|
|
633
640
|
]),
|
|
634
641
|
sensor: Object.freeze([
|
|
635
642
|
["cdx:hbom:temperatureCelsius", "tempC"],
|
|
@@ -870,8 +877,13 @@ export function printTable(
|
|
|
870
877
|
}
|
|
871
878
|
if (
|
|
872
879
|
bomJson.metadata?.component &&
|
|
873
|
-
["operating-system", "platform"].includes(
|
|
880
|
+
["operating-system", "platform"].includes(
|
|
881
|
+
bomJson.metadata.component.type,
|
|
882
|
+
) &&
|
|
883
|
+
!filterTypes?.includes("cryptographic-asset")
|
|
874
884
|
) {
|
|
885
|
+
// A filtered crypto view stays a crypto view even on an OS BOM; only the
|
|
886
|
+
// unfiltered component listing falls through to the OS table.
|
|
875
887
|
return printOSTable(bomJson);
|
|
876
888
|
}
|
|
877
889
|
if (isHbomLikeBom(bomJson) && !filterTypes?.includes("cryptographic-asset")) {
|
|
@@ -1037,7 +1049,13 @@ const locationComparator = (a, b) => {
|
|
|
1037
1049
|
const tmpB = b.split("#");
|
|
1038
1050
|
if (tmpA.length === 2 && tmpB.length === 2) {
|
|
1039
1051
|
if (tmpA[0] === tmpB[0]) {
|
|
1040
|
-
|
|
1052
|
+
// Line numbers sort numerically; other fragments (a secret key such
|
|
1053
|
+
// as data.tls.crt) by text, never NaN.
|
|
1054
|
+
const lineA = Number(tmpA[1]);
|
|
1055
|
+
const lineB = Number(tmpB[1]);
|
|
1056
|
+
return Number.isNaN(lineA) || Number.isNaN(lineB)
|
|
1057
|
+
? tmpA[1].localeCompare(tmpB[1])
|
|
1058
|
+
: lineA - lineB;
|
|
1041
1059
|
}
|
|
1042
1060
|
}
|
|
1043
1061
|
}
|
|
@@ -2125,3 +2143,182 @@ export function displaySelfThreatModel(
|
|
|
2125
2143
|
process.exit(1);
|
|
2126
2144
|
}
|
|
2127
2145
|
}
|
|
2146
|
+
|
|
2147
|
+
function certificateComponents(bomJson) {
|
|
2148
|
+
return (bomJson?.components || []).filter(
|
|
2149
|
+
(component) => component?.cryptoProperties?.assetType === "certificate",
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
function auditedBomRefs(bomJson) {
|
|
2154
|
+
// Audit findings land as document annotations whose location names the
|
|
2155
|
+
// component; a certificate listed there is flagged in the table. The
|
|
2156
|
+
// annotation text is rendered as a markdown property table, so the rows
|
|
2157
|
+
// are parsed back out of it (the same way the REPL's audit view does).
|
|
2158
|
+
const refs = new Set();
|
|
2159
|
+
for (const annotation of bomJson?.annotations || []) {
|
|
2160
|
+
const text = typeof annotation?.text === "string" ? annotation.text : "";
|
|
2161
|
+
for (const line of text.split(/\r?\n/)) {
|
|
2162
|
+
if (!line.includes("cdx:audit:location:bomRef")) {
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
const cells = line.split("|").map((cell) => cell.trim());
|
|
2166
|
+
if (
|
|
2167
|
+
cells.length >= 2 &&
|
|
2168
|
+
cells[1] === "cdx:audit:location:bomRef" &&
|
|
2169
|
+
cells[2]
|
|
2170
|
+
) {
|
|
2171
|
+
refs.add(cells[2]);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
return refs;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* Print a table of the BOM's certificate components: role, name, issuer,
|
|
2180
|
+
* expiry, the SPKI prefix and how many locations each certificate appears
|
|
2181
|
+
* in, with a marker when the audit flagged it.
|
|
2182
|
+
*
|
|
2183
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
2184
|
+
* @returns {void}
|
|
2185
|
+
*/
|
|
2186
|
+
export function printCertificatesTable(bomJson) {
|
|
2187
|
+
const certificates = certificateComponents(bomJson);
|
|
2188
|
+
if (!certificates.length) {
|
|
2189
|
+
console.log(
|
|
2190
|
+
"No certificate components found. Generate a CBOM with --include-crypto to use this view.",
|
|
2191
|
+
);
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
const flagged = auditedBomRefs(bomJson);
|
|
2195
|
+
const stream = createStream({
|
|
2196
|
+
columnDefault: { width: 24 },
|
|
2197
|
+
columnCount: 6,
|
|
2198
|
+
columns: [
|
|
2199
|
+
{ width: 13 },
|
|
2200
|
+
{ width: 30 },
|
|
2201
|
+
{ width: 30 },
|
|
2202
|
+
{ width: 21 },
|
|
2203
|
+
{ width: 18 },
|
|
2204
|
+
{ width: 5, alignment: "center" },
|
|
2205
|
+
],
|
|
2206
|
+
});
|
|
2207
|
+
stream.write([
|
|
2208
|
+
"Role",
|
|
2209
|
+
"Name",
|
|
2210
|
+
"Issuer CN",
|
|
2211
|
+
"Not valid after",
|
|
2212
|
+
"SPKI (first 16)",
|
|
2213
|
+
"Locs",
|
|
2214
|
+
]);
|
|
2215
|
+
for (const certificate of certificates) {
|
|
2216
|
+
const issuerCn = commonNameFromIssuer(certificate);
|
|
2217
|
+
stream.write([
|
|
2218
|
+
getPropertyValue(certificate, "cdx:crypto:certRole") || "?",
|
|
2219
|
+
certificate.name || "",
|
|
2220
|
+
issuerCn ||
|
|
2221
|
+
certificate.cryptoProperties?.certificateProperties?.issuerName ||
|
|
2222
|
+
"",
|
|
2223
|
+
certificate.cryptoProperties?.certificateProperties?.notValidAfter || "",
|
|
2224
|
+
(getPropertyValue(certificate, "cdx:crypto:spkiSha256") || "").slice(
|
|
2225
|
+
0,
|
|
2226
|
+
16,
|
|
2227
|
+
),
|
|
2228
|
+
`${certificate.evidence?.occurrences?.length || 0}${flagged.has(certificate["bom-ref"]) ? " ⚠" : ""}`,
|
|
2229
|
+
]);
|
|
2230
|
+
}
|
|
2231
|
+
stream.end();
|
|
2232
|
+
console.log();
|
|
2233
|
+
console.log(`Found ${certificates.length} certificate component(s).`);
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
function commonNameFromIssuer(certificate) {
|
|
2237
|
+
const issuer =
|
|
2238
|
+
certificate.cryptoProperties?.certificateProperties?.issuerName || "";
|
|
2239
|
+
const parts = issuer.split(",").map((part) => part.trim());
|
|
2240
|
+
const cn = parts.find((part) => part.startsWith("CN="));
|
|
2241
|
+
return cn ? cn.slice(3) : parts[0];
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/**
|
|
2245
|
+
* Print the issuer chain above a certificate and the certificates issued
|
|
2246
|
+
* under it, walking the verified `dependsOn` edges.
|
|
2247
|
+
*
|
|
2248
|
+
* @param {Object} bomJson CycloneDX BOM JSON object
|
|
2249
|
+
* @param {string} selector bom-ref, SPKI hex prefix or certificate ref prefix
|
|
2250
|
+
* @returns {void}
|
|
2251
|
+
*/
|
|
2252
|
+
export function printCertificateChain(bomJson, selector) {
|
|
2253
|
+
const query = String(selector || "").trim();
|
|
2254
|
+
if (!query) {
|
|
2255
|
+
console.log("Usage: .certchain <bom-ref|spki-prefix|cert-prefix>");
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
const certificates = certificateComponents(bomJson);
|
|
2259
|
+
const byRef = new Map(certificates.map((c) => [c["bom-ref"], c]));
|
|
2260
|
+
const edges = new Map(
|
|
2261
|
+
(bomJson?.dependencies || []).map((dep) => [
|
|
2262
|
+
dep.ref,
|
|
2263
|
+
(dep.dependsOn || []).filter((ref) => byRef.has(ref)),
|
|
2264
|
+
]),
|
|
2265
|
+
);
|
|
2266
|
+
const issuersOf = new Map();
|
|
2267
|
+
for (const [ref, issuerRefs] of edges) {
|
|
2268
|
+
for (const issuerRef of issuerRefs) {
|
|
2269
|
+
if (!issuersOf.has(issuerRef)) {
|
|
2270
|
+
issuersOf.set(issuerRef, []);
|
|
2271
|
+
}
|
|
2272
|
+
issuersOf.get(issuerRef).push(ref);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
let target;
|
|
2276
|
+
if (byRef.has(query)) {
|
|
2277
|
+
target = byRef.get(query);
|
|
2278
|
+
} else {
|
|
2279
|
+
target = certificates.find(
|
|
2280
|
+
(certificate) =>
|
|
2281
|
+
certificate["bom-ref"].includes(query) ||
|
|
2282
|
+
(
|
|
2283
|
+
getPropertyValue(certificate, "cdx:crypto:spkiSha256") || ""
|
|
2284
|
+
).startsWith(query.toLowerCase()),
|
|
2285
|
+
);
|
|
2286
|
+
}
|
|
2287
|
+
if (!target) {
|
|
2288
|
+
console.log(`No certificate matches '${query}'.`);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
const describe = (certificate) =>
|
|
2292
|
+
`${getPropertyValue(certificate, "cdx:crypto:certRole") || "?"} ${certificate.name} (${(getPropertyValue(certificate, "cdx:crypto:spkiSha256") || "").slice(0, 16)}…)`;
|
|
2293
|
+
const chain = [];
|
|
2294
|
+
let cursor = target["bom-ref"];
|
|
2295
|
+
const seen = new Set([cursor]);
|
|
2296
|
+
while (edges.has(cursor)) {
|
|
2297
|
+
const next = (edges.get(cursor) || []).filter((ref) => !seen.has(ref));
|
|
2298
|
+
if (!next.length) {
|
|
2299
|
+
break;
|
|
2300
|
+
}
|
|
2301
|
+
cursor = next[0];
|
|
2302
|
+
seen.add(cursor);
|
|
2303
|
+
chain.push(byRef.get(cursor));
|
|
2304
|
+
}
|
|
2305
|
+
console.log(`Chain above ${describe(target)}:`);
|
|
2306
|
+
if (chain.length) {
|
|
2307
|
+
chain.forEach((certificate, index) => {
|
|
2308
|
+
console.log(`${" ".repeat(index + 1)}← ${describe(certificate)}`);
|
|
2309
|
+
});
|
|
2310
|
+
} else {
|
|
2311
|
+
console.log(" (no verified issuer in this BOM)");
|
|
2312
|
+
}
|
|
2313
|
+
const issued = (issuersOf.get(target["bom-ref"]) || []).filter((ref) =>
|
|
2314
|
+
byRef.has(ref),
|
|
2315
|
+
);
|
|
2316
|
+
console.log(`Issued under ${describe(target)}:`);
|
|
2317
|
+
if (issued.length) {
|
|
2318
|
+
for (const ref of issued) {
|
|
2319
|
+
console.log(` → ${describe(byRef.get(ref))}`);
|
|
2320
|
+
}
|
|
2321
|
+
} else {
|
|
2322
|
+
console.log(" (none in this BOM)");
|
|
2323
|
+
}
|
|
2324
|
+
}
|