@cdxgen/cdxgen 13.6.0 → 13.7.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.
Files changed (264) hide show
  1. package/README.md +15 -8
  2. package/bin/audit.js +7 -4
  3. package/bin/cdxgen.js +256 -37
  4. package/bin/convert.js +146 -22
  5. package/bin/evinse.js +25 -3
  6. package/bin/repl.js +47 -3
  7. package/bin/sign.js +211 -34
  8. package/bin/tracebom.js +18 -6
  9. package/bin/validate.js +18 -3
  10. package/bin/verify.js +113 -5
  11. package/data/behavior-taxonomy.schema.json +1491 -0
  12. package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
  13. package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
  14. package/data/pqc-migration.json +239 -0
  15. package/data/remediations.json +52 -0
  16. package/data/rules/cbom-security.yaml +4 -3
  17. package/data/rules/package-integrity.yaml +28 -0
  18. package/lib/audit/index.js +25 -3
  19. package/lib/cli/bomAssembly.js +80 -1
  20. package/lib/cli/cliOptions.js +121 -17
  21. package/lib/cli/index.js +609 -898
  22. package/lib/cli/jsBom.js +3 -0
  23. package/lib/cli/jvmBom.js +365 -40
  24. package/lib/cli/managedBom.js +19 -3
  25. package/lib/cli/nativeBom.js +10 -0
  26. package/lib/core/env.js +26 -0
  27. package/lib/core/fs.js +126 -3
  28. package/lib/core/globPatterns.js +218 -0
  29. package/lib/core/processTree.js +99 -12
  30. package/lib/core/projectConfig.js +332 -25
  31. package/lib/core/toolRegistry.js +15 -1
  32. package/lib/ecosystems/denoutils.js +2 -2
  33. package/lib/ecosystems/golem.js +3 -8
  34. package/lib/ecosystems/gradleutils.js +1082 -6
  35. package/lib/ecosystems/jsEvidence.js +90 -38
  36. package/lib/ecosystems/kosi.js +3 -5
  37. package/lib/ecosystems/npmVersionResolution.js +376 -0
  38. package/lib/ecosystems/parsers-python.js +89 -28
  39. package/lib/ecosystems/parsers-rust.js +117 -15
  40. package/lib/ecosystems/remote/huggingface.js +22 -0
  41. package/lib/ecosystems/rusi.js +2 -4
  42. package/lib/ecosystems/scalasem.js +2 -1
  43. package/lib/evinser/evinser.js +196 -34
  44. package/lib/evinser/scalasem.js +5 -1
  45. package/lib/helpers/agenticToolutils.js +112 -1
  46. package/lib/helpers/bomSigner.js +43 -9
  47. package/lib/helpers/jssSigner.js +1276 -0
  48. package/lib/inventory/agentBlueprint.js +193 -0
  49. package/lib/inventory/agentToolNames.js +145 -0
  50. package/lib/inventory/aiCollector.js +153 -7
  51. package/lib/inventory/aiInventory.js +44 -8
  52. package/lib/inventory/aiProviders.js +324 -0
  53. package/lib/inventory/bomUtils.js +8 -1
  54. package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
  55. package/lib/inventory/citations.js +31 -2
  56. package/lib/inventory/communityAiConfigParser.js +978 -80
  57. package/lib/inventory/cyclonedx20.js +152 -0
  58. package/lib/inventory/cyclonedx20Common.js +286 -0
  59. package/lib/inventory/cyclonedx20Derived.js +159 -0
  60. package/lib/inventory/cyclonedx20Parties.js +552 -0
  61. package/lib/inventory/cyclonedx20Read.js +768 -0
  62. package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
  63. package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
  64. package/lib/inventory/cyclonedx20ReadContext.js +380 -0
  65. package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
  66. package/lib/inventory/cyclonedx20ReadSections.js +599 -0
  67. package/lib/inventory/cyclonedx20Render.js +766 -0
  68. package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
  69. package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
  70. package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
  71. package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
  72. package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
  73. package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
  74. package/lib/inventory/cyclonedx20RenderSections.js +499 -0
  75. package/lib/inventory/cyclonedx20Tables.js +594 -0
  76. package/lib/inventory/deps.js +106 -13
  77. package/lib/inventory/dosai.js +236 -1
  78. package/lib/inventory/dosaiParsers.js +3 -6
  79. package/lib/inventory/dotnetRuntime.js +164 -14
  80. package/lib/inventory/ecosystemFormulationParsers.js +979 -0
  81. package/lib/inventory/envcontext.js +132 -38
  82. package/lib/inventory/evidenceUtils.js +2 -1
  83. package/lib/inventory/formulationParsers.js +95 -13
  84. package/lib/inventory/hbom.js +45 -6
  85. package/lib/inventory/hbomLoader.js +166 -0
  86. package/lib/inventory/mcpConfigParser.js +5 -0
  87. package/lib/inventory/occurrenceKey.js +90 -0
  88. package/lib/inventory/pqcMigration.js +426 -0
  89. package/lib/inventory/rustFormulationParser.js +21 -10
  90. package/lib/inventory/source.js +65 -6
  91. package/lib/parsers/huggingfaceManifest.js +9 -1
  92. package/lib/parsers/huggingfaceUtils.js +18 -0
  93. package/lib/parsers/kiroPermissions.js +166 -0
  94. package/lib/server/server.js +36 -34
  95. package/lib/stages/postgen/postgen.js +41 -1
  96. package/lib/stages/postgen/specVersionCompat.js +76 -0
  97. package/lib/validator/bomValidator.js +8 -1
  98. package/lib/validator/complianceEngine.js +30 -3
  99. package/lib/validator/complianceRules.js +1416 -4
  100. package/lib/validator/generated/validate-2.0.mjs +57554 -13851
  101. package/lib/validator/index.js +48 -7
  102. package/lib/validator/reporters/json.js +6 -0
  103. package/package.json +9 -9
  104. package/types/bin/repl.d.ts.map +1 -1
  105. package/types/lib/audit/index.d.ts +6 -2
  106. package/types/lib/audit/index.d.ts.map +1 -1
  107. package/types/lib/cli/bomAssembly.d.ts +16 -0
  108. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  109. package/types/lib/cli/cliOptions.d.ts +6 -0
  110. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  111. package/types/lib/cli/index.d.ts.map +1 -1
  112. package/types/lib/cli/jsBom.d.ts +2 -1
  113. package/types/lib/cli/jsBom.d.ts.map +1 -1
  114. package/types/lib/cli/jvmBom.d.ts +15 -0
  115. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  116. package/types/lib/cli/managedBom.d.ts.map +1 -1
  117. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  118. package/types/lib/core/boundedFile.d.ts +30 -0
  119. package/types/lib/core/boundedFile.d.ts.map +1 -1
  120. package/types/lib/core/env.d.ts +10 -0
  121. package/types/lib/core/env.d.ts.map +1 -1
  122. package/types/lib/core/fs.d.ts +4 -0
  123. package/types/lib/core/fs.d.ts.map +1 -1
  124. package/types/lib/core/globPatterns.d.ts +70 -0
  125. package/types/lib/core/globPatterns.d.ts.map +1 -0
  126. package/types/lib/core/paths.d.ts +9 -0
  127. package/types/lib/core/paths.d.ts.map +1 -1
  128. package/types/lib/core/processTree.d.ts +16 -5
  129. package/types/lib/core/processTree.d.ts.map +1 -1
  130. package/types/lib/core/projectConfig.d.ts +47 -8
  131. package/types/lib/core/projectConfig.d.ts.map +1 -1
  132. package/types/lib/core/toolRegistry.d.ts.map +1 -1
  133. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  134. package/types/lib/ecosystems/gradleutils.d.ts +248 -8
  135. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  136. package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
  137. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  139. package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
  140. package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
  141. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  142. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  143. package/types/lib/ecosystems/pythonutils.d.ts +1 -1
  144. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  145. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  146. package/types/lib/ecosystems/scalasem.d.ts +58 -16
  147. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  148. package/types/lib/evinser/evinser.d.ts +61 -9
  149. package/types/lib/evinser/evinser.d.ts.map +1 -1
  150. package/types/lib/evinser/scalasem.d.ts.map +1 -1
  151. package/types/lib/helpers/agenticToolutils.d.ts +53 -19
  152. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
  153. package/types/lib/helpers/bomSigner.d.ts +20 -3
  154. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  155. package/types/lib/helpers/jssSigner.d.ts +288 -0
  156. package/types/lib/helpers/jssSigner.d.ts.map +1 -0
  157. package/types/lib/helpers/vsixutils.d.ts +303 -19
  158. package/types/lib/helpers/vsixutils.d.ts.map +1 -1
  159. package/types/lib/inventory/agentBlueprint.d.ts +70 -0
  160. package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
  161. package/types/lib/inventory/agentToolNames.d.ts +51 -0
  162. package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
  163. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  164. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  165. package/types/lib/inventory/aiProviders.d.ts +85 -0
  166. package/types/lib/inventory/aiProviders.d.ts.map +1 -0
  167. package/types/lib/inventory/bomUtils.d.ts.map +1 -1
  168. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
  169. package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
  170. package/types/lib/inventory/citations.d.ts +5 -0
  171. package/types/lib/inventory/citations.d.ts.map +1 -1
  172. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  173. package/types/lib/inventory/cyclonedx20.d.ts +99 -0
  174. package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
  175. package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
  176. package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
  177. package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
  178. package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
  179. package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
  180. package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
  181. package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
  182. package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
  183. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
  184. package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
  185. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
  186. package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
  187. package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
  188. package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
  189. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
  190. package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
  191. package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
  192. package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
  193. package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
  194. package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
  195. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
  196. package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
  197. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
  198. package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
  199. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
  200. package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
  201. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
  202. package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
  203. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
  204. package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
  205. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
  206. package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
  207. package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
  208. package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
  209. package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
  210. package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
  211. package/types/lib/inventory/deps.d.ts +35 -2
  212. package/types/lib/inventory/deps.d.ts.map +1 -1
  213. package/types/lib/inventory/dosai.d.ts +19 -0
  214. package/types/lib/inventory/dosai.d.ts.map +1 -1
  215. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  216. package/types/lib/inventory/dotnetRuntime.d.ts +51 -4
  217. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -1
  218. package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
  219. package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
  220. package/types/lib/inventory/envcontext.d.ts +22 -6
  221. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  222. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  223. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  224. package/types/lib/inventory/hbom.d.ts +11 -0
  225. package/types/lib/inventory/hbom.d.ts.map +1 -1
  226. package/types/lib/inventory/hbomLoader.d.ts +54 -0
  227. package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
  228. package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
  229. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
  230. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  231. package/types/lib/inventory/occurrenceKey.d.ts +52 -0
  232. package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
  233. package/types/lib/inventory/pqcMigration.d.ts +84 -0
  234. package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
  235. package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
  236. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  237. package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
  238. package/types/lib/inventory/scalaDedupe.d.ts +43 -0
  239. package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
  240. package/types/lib/inventory/source.d.ts +15 -0
  241. package/types/lib/inventory/source.d.ts.map +1 -1
  242. package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
  243. package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
  244. package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
  245. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  246. package/types/lib/parsers/kiroPermissions.d.ts +62 -0
  247. package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
  248. package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
  249. package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
  250. package/types/lib/server/server.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/postgen.d.ts +7 -0
  252. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  253. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  254. package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
  255. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  256. package/types/lib/validator/bomValidator.d.ts +5 -1
  257. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  258. package/types/lib/validator/complianceEngine.d.ts +4 -4
  259. package/types/lib/validator/complianceEngine.d.ts.map +1 -1
  260. package/types/lib/validator/complianceRules.d.ts +14 -2
  261. package/types/lib/validator/complianceRules.d.ts.map +1 -1
  262. package/types/lib/validator/index.d.ts +10 -0
  263. package/types/lib/validator/index.d.ts.map +1 -1
  264. package/types/lib/validator/reporters/json.d.ts.map +1 -1
package/bin/sign.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import crypto from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import process from "node:process";
5
6
 
@@ -10,14 +11,23 @@ import {
10
11
  readEnvironmentVariable,
11
12
  retrieveCdxgenVersion,
12
13
  safeExistsSync,
14
+ safeWriteSync,
13
15
  } from "../lib/ecosystems/utils.js";
14
16
  import {
15
17
  checkSignatureEntries,
16
18
  displayValue,
17
19
  signBom,
18
20
  } from "../lib/helpers/bomSigner.js";
21
+ import {
22
+ countersignJss,
23
+ removeJssSignatures,
24
+ selectJssAlgorithmForKey,
25
+ signJssBom,
26
+ verifyJss,
27
+ } from "../lib/helpers/jssSigner.js";
19
28
  import {
20
29
  getNonCycloneDxErrorMessage,
30
+ isCycloneDx20SpecVersion,
21
31
  isCycloneDxBom,
22
32
  } from "../lib/inventory/bomUtils.js";
23
33
  import { parseStrictJson } from "../lib/parsers/strictJson.js";
@@ -41,16 +51,18 @@ const args = _yargs
41
51
  })
42
52
  .option("algorithm", {
43
53
  alias: "a",
44
- default: readEnvironmentVariable("SBOM_SIGN_ALGORITHM") || "RS512",
54
+ default: readEnvironmentVariable("SBOM_SIGN_ALGORITHM"),
55
+ defaultDescription:
56
+ "SBOM_SIGN_ALGORITHM, else RS512 for JSF (1.x) or derived from the key for JSS (2.0)",
45
57
  description:
46
- "JSF Signature Algorithm (e.g., RS512, ES256, Ed25519). It must match the key type.",
58
+ "Signature algorithm (e.g., RS512, ES256, Ed25519). It must match the key type. For a CycloneDX 2.0 (JSS) document it defaults to ES256/ES384/ES512 by curve, RS256 for rsa, PS256 for rsa-pss, Ed25519 or Ed448.",
47
59
  })
48
60
  .option("mode", {
49
61
  alias: "m",
50
62
  default: readEnvironmentVariable("SBOM_SIGN_MODE") || "replace",
51
63
  choices: ["replace", "signers", "chain"],
52
64
  description:
53
- "Signature mode. Use 'signers' for multi-signing, 'chain' for sequential chaining.",
65
+ "Signature mode. Use 'signers' for multi-signing, 'chain' for sequential chaining. At 2.0 (JSS), 'replace' removes every existing JSS signature, 'signers' appends a root signature (X.590 clause 7.1), and 'chain' countersigns the only root signature (X.590 clause 7.2).",
54
66
  })
55
67
  .option("verify-existing-with", {
56
68
  type: "array",
@@ -149,6 +161,9 @@ if (args.privateKey) {
149
161
 
150
162
  function hasAnySignature(bomJson) {
151
163
  if (bomJson.signature) return true;
164
+ // CycloneDX 2.0 carries JSS signatures in `signatures` lists.
165
+ if (Array.isArray(bomJson.signatures) && bomJson.signatures.length)
166
+ return true;
152
167
  if (
153
168
  Array.isArray(bomJson.components) &&
154
169
  bomJson.components.some((c) => c.signature)
@@ -234,6 +249,189 @@ function checkExistingSignatures(bomJson) {
234
249
  );
235
250
  }
236
251
 
252
+ function signJsfDocument(bomJson) {
253
+ const jsfAlgorithm = args.algorithm || "RS512";
254
+ checkExistingSignatures(bomJson);
255
+ const signedBom = signBom(bomJson, {
256
+ privateKey: privateKeyContent,
257
+ algorithm: jsfAlgorithm,
258
+ embedPublicKey: args.embedPublicKey,
259
+ embedPublicKeyNested: false,
260
+ keyId: args.keyId,
261
+ mode: args.mode,
262
+ signComponents: args.signComponents,
263
+ signServices: args.signServices,
264
+ signAnnotations: args.signAnnotations,
265
+ });
266
+
267
+ const outputPath = args.output || args.input;
268
+ safeWriteSync(outputPath, JSON.stringify(signedBom, null, null));
269
+
270
+ console.log(`Successfully signed BOM and saved to '${outputPath}'`);
271
+ console.log(
272
+ `Mode: ${args.mode} | Algorithm: ${jsfAlgorithm}${args.keyId ? ` | KeyId: ${args.keyId}` : ""}`,
273
+ );
274
+ const rootSignature = signedBom.signature;
275
+ const newEntry =
276
+ rootSignature?.signers?.at(-1) ??
277
+ rootSignature?.chain?.at(-1) ??
278
+ rootSignature;
279
+ if (
280
+ args.embedPublicKey &&
281
+ !String(jsfAlgorithm).startsWith("HS") &&
282
+ newEntry?.publicKey === undefined
283
+ ) {
284
+ console.warn(
285
+ "Warning: this key type has no JWK form, so the public key was not embedded in the signature.",
286
+ );
287
+ }
288
+ return signedBom;
289
+ }
290
+
291
+ // A JSS (X.590) countersignature of the only root entry vouches for that
292
+ // entry and its chain, so every object of the chain must verify (as for a
293
+ // JSF chain). A new root entry covers the same content as the existing ones,
294
+ // so the existing signatures must pass the cdx-verify policy: at least one
295
+ // entry verifies, no entry that names a given key fails, and every
296
+ // countersignature verifies.
297
+ function checkExistingJssSignatures(bomJson) {
298
+ const chain = args.mode === "chain";
299
+ const keys = (args.verifyExistingWith || []).map((keyFile) => {
300
+ if (!safeExistsSync(keyFile)) {
301
+ throw new Error(`Public key file '${keyFile}' not found.`);
302
+ }
303
+ return fs.readFileSync(keyFile, "utf8");
304
+ });
305
+ const verdict = keys.length
306
+ ? verifyJss(bomJson, { publicKeys: keys })
307
+ : undefined;
308
+ const results = verdict
309
+ ? verdict.results
310
+ : bomJson.signatures.map((entry, index) => ({
311
+ path: `signatures[${index}]`,
312
+ algorithm: entry?.algorithm,
313
+ status: "untrusted",
314
+ reason: "no --verify-existing-with key was given",
315
+ }));
316
+ const unverified = results.filter((result) => result.status !== "verified");
317
+ if (chain ? !unverified.length : verdict?.valid) {
318
+ return;
319
+ }
320
+ const someVerified = unverified.length < results.length;
321
+ const details = unverified.map(
322
+ (result) =>
323
+ ` - ${result.path} (${displayValue(result.algorithm)}): ${result.reason || "not verified"}`,
324
+ );
325
+ if (args.allowUnverifiedHistory) {
326
+ console.warn(
327
+ [
328
+ chain
329
+ ? `Warning: countersigning a JSS signature chain with ${unverified.length} unverified signature object(s):`
330
+ : someVerified
331
+ ? `Warning: appending a JSS signature although ${unverified.length} existing signature object(s) failed verification:`
332
+ : `Warning: appending a JSS signature although none of the ${unverified.length} existing signature object(s) verified:`,
333
+ ...details,
334
+ ].join("\n"),
335
+ );
336
+ return;
337
+ }
338
+ throw new Error(
339
+ [
340
+ chain
341
+ ? `the countersignature would vouch for ${unverified.length} existing JSS signature object(s) that could not be verified:`
342
+ : someVerified
343
+ ? "the existing JSS signatures do not pass verification, so cdx-verify would reject the signed document:"
344
+ : `none of the ${unverified.length} existing JSS signature object(s) could be verified, so the content may have changed since it was signed:`,
345
+ ...details,
346
+ "Pass --verify-existing-with <public key> for each earlier signer, or --allow-unverified-history to sign anyway.",
347
+ ].join("\n"),
348
+ );
349
+ }
350
+
351
+ /**
352
+ * Signs a CycloneDX 2.0 document with JSS (ITU-T X.590).
353
+ *
354
+ * - replace: every existing JSS signature is removed (with a warning that
355
+ * names how many), then the root and the nested members are signed;
356
+ * - signers: a root entry is appended. X.590 clause 7.1.2 removes the
357
+ * existing entries before canonicalizing, so they stay valid;
358
+ * - chain: the only root entry is countersigned (X.590 clause 7.2). A JSF
359
+ * chain entry covers every earlier entry, while a JSS countersignature
360
+ * covers only the entry it countersigns, so the two agree only when the
361
+ * root has a single entry; otherwise chain mode is refused.
362
+ *
363
+ * Nested members are signed only when no root signature is kept: signing
364
+ * them changes content the existing root entries cover.
365
+ */
366
+ function signJssDocument(bomJson) {
367
+ if (args.embedPublicKey === false) {
368
+ throw new Error(
369
+ "--no-embed-public-key is not supported for CycloneDX 2.0 (JSS): X.590 clause 6.2.1 requires public_key, public_cert_chain, cert_url or thumbprint in every signature object, and cdx-sign writes public_key.",
370
+ );
371
+ }
372
+ if (args.keyId !== undefined) {
373
+ console.warn(
374
+ "Warning: --key-id is not used for CycloneDX 2.0 (JSS) signatures, which identify the key by public_key.",
375
+ );
376
+ }
377
+ let privateKey;
378
+ try {
379
+ privateKey = crypto.createPrivateKey(privateKeyContent);
380
+ } catch (error) {
381
+ throw new Error(`Unable to load the JSS signing key: ${error.message}`);
382
+ }
383
+ const algorithm = args.algorithm || selectJssAlgorithmForKey(privateKey);
384
+ const rootEntries = Array.isArray(bomJson.signatures)
385
+ ? bomJson.signatures
386
+ : [];
387
+ const appending = args.mode !== "replace" && rootEntries.length > 0;
388
+ const nestedFlags = [
389
+ ["components", args.signComponents],
390
+ ["services", args.signServices],
391
+ ["annotations", args.signAnnotations],
392
+ ];
393
+ if (appending) {
394
+ const requested = nestedFlags.find(([, value]) => value === true);
395
+ if (requested) {
396
+ throw new Error(
397
+ `Cannot sign ${requested[0]} while appending a ${args.mode} JSS signature, because that changes content covered by the existing root signature(s). Sign the root only (for example, --no-sign-${requested[0]}), or use --mode replace.`,
398
+ );
399
+ }
400
+ if (args.mode === "chain" && rootEntries.length !== 1) {
401
+ throw new Error(
402
+ `--mode chain is not supported for JSS when the BOM has ${rootEntries.length} root signatures: an X.590 clause 7.2 countersignature covers only the entry it countersigns, while a chain entry must cover every earlier entry. Use --mode signers to append a root signature.`,
403
+ );
404
+ }
405
+ checkExistingJssSignatures(bomJson);
406
+ }
407
+ // At 2.0 the nested members (inventories, their components, compositions
408
+ // and annotations) are signed together; any --no-sign-* signs the root
409
+ // only.
410
+ const deep = !appending && nestedFlags.every(([, value]) => value !== false);
411
+ const signOptions = { algorithm, privateKey };
412
+ let signedBom;
413
+ let label = args.mode;
414
+ if (args.mode === "replace") {
415
+ const { bom, removed } = removeJssSignatures(bomJson);
416
+ if (removed) {
417
+ console.warn(
418
+ `Replacing ${removed} existing JSS signature(s) (--mode replace). Use --mode signers to keep them.`,
419
+ );
420
+ }
421
+ signedBom = signJssBom(bom, { ...signOptions, deep });
422
+ } else if (appending && args.mode === "chain") {
423
+ signedBom = countersignJss(bomJson, { ...signOptions, index: 0 });
424
+ label = "chain (X.590 clause 7.2 countersignature)";
425
+ } else {
426
+ signedBom = signJssBom(bomJson, { ...signOptions, deep });
427
+ if (args.mode === "chain") {
428
+ label = "chain (first signature)";
429
+ }
430
+ }
431
+ console.log(`Mode: ${label} | Format: JSS | Algorithm: ${algorithm}`);
432
+ return signedBom;
433
+ }
434
+
237
435
  try {
238
436
  const bomJson = parseStrictJson(fs.readFileSync(args.input));
239
437
  if (!isCycloneDxBom(bomJson)) {
@@ -243,39 +441,18 @@ try {
243
441
 
244
442
  let signedBom = bomJson;
245
443
  if (hasPrivateKey && privateKeyContent) {
246
- checkExistingSignatures(bomJson);
247
- signedBom = signBom(bomJson, {
248
- privateKey: privateKeyContent,
249
- algorithm: args.algorithm,
250
- embedPublicKey: args.embedPublicKey,
251
- embedPublicKeyNested: false,
252
- keyId: args.keyId,
253
- mode: args.mode,
254
- signComponents: args.signComponents,
255
- signServices: args.signServices,
256
- signAnnotations: args.signAnnotations,
257
- });
258
-
259
- const outputPath = args.output || args.input;
260
- fs.writeFileSync(outputPath, JSON.stringify(signedBom, null, null));
261
-
262
- console.log(`Successfully signed BOM and saved to '${outputPath}'`);
263
- console.log(
264
- `Mode: ${args.mode} | Algorithm: ${args.algorithm}${args.keyId ? ` | KeyId: ${args.keyId}` : ""}`,
265
- );
266
- const rootSignature = signedBom.signature;
267
- const newEntry =
268
- rootSignature?.signers?.at(-1) ??
269
- rootSignature?.chain?.at(-1) ??
270
- rootSignature;
271
444
  if (
272
- args.embedPublicKey &&
273
- !String(args.algorithm).startsWith("HS") &&
274
- newEntry?.publicKey === undefined
445
+ isCycloneDx20SpecVersion(bomJson.specVersion) ||
446
+ bomJson.specFormat !== undefined
275
447
  ) {
276
- console.warn(
277
- "Warning: this key type has no JWK form, so the public key was not embedded in the signature.",
278
- );
448
+ // A CycloneDX 2.0 document signs with JSS (ITU-T X.590); the JSF
449
+ // signer below is 1.x only.
450
+ signedBom = signJssDocument(bomJson);
451
+ const jssOutputPath = args.output || args.input;
452
+ safeWriteSync(jssOutputPath, JSON.stringify(signedBom, null, null));
453
+ console.log(`Successfully signed BOM and saved to '${jssOutputPath}'`);
454
+ } else {
455
+ signedBom = signJsfDocument(bomJson);
279
456
  }
280
457
  } else {
281
458
  if (!hasAnySignature(bomJson)) {
package/bin/tracebom.js CHANGED
@@ -13,7 +13,12 @@ import {
13
13
  retrieveCdxgenVersion,
14
14
  safeWriteSync,
15
15
  } from "../lib/ecosystems/utils.js";
16
- import { DEFAULT_CDX_SPEC_VERSION } from "../lib/inventory/bomUtils.js";
16
+ import {
17
+ DEFAULT_CDX_SPEC_VERSION,
18
+ isCycloneDx20SpecVersion,
19
+ } from "../lib/inventory/bomUtils.js";
20
+ import { renderCycloneDx20 } from "../lib/inventory/cyclonedx20.js";
21
+ import { hardwareRenderContext } from "../lib/inventory/hbomLoader.js";
17
22
  import { applySpecVersionCompatibility } from "../lib/stages/postgen/specVersionCompat.js";
18
23
 
19
24
  const _yargs = yargs(hideBin(process.argv));
@@ -339,10 +344,20 @@ const options = {
339
344
  process.exit(1);
340
345
  }
341
346
  // tracebom skips postProcess, so the downgrade to an older --spec-version
342
- // (for example dropping the 1.7 algorithmFamily of traced crypto) runs here.
347
+ // (for example dropping the 1.7 algorithmFamily of traced crypto) runs here,
348
+ // and a 2.0 target renders the document with the postgen renderer.
343
349
  applySpecVersionCompatibility(bomJson, options);
350
+ const serviceCount = bomJson?.services?.length || 0;
351
+ const componentCount = bomJson?.components?.length || 0;
352
+ const outputBomJson = isCycloneDx20SpecVersion(options.specVersion)
353
+ ? renderCycloneDx20(bomJson, await hardwareRenderContext(bomJson))
354
+ : bomJson;
344
355
 
345
- const jsonPayload = JSON.stringify(bomJson, null, DEBUG_MODE ? 2 : null);
356
+ const jsonPayload = JSON.stringify(
357
+ outputBomJson,
358
+ null,
359
+ DEBUG_MODE ? 2 : null,
360
+ );
346
361
 
347
362
  if (args.print) {
348
363
  // The document is the payload, so it bypasses the console shim.
@@ -350,9 +365,6 @@ const options = {
350
365
  }
351
366
 
352
367
  safeWriteSync(options.output, jsonPayload);
353
-
354
- const serviceCount = bomJson?.services?.length || 0;
355
- const componentCount = bomJson?.components?.length || 0;
356
368
  const summaryParts = [
357
369
  `SBOM written to ${options.output}`,
358
370
  `${componentCount} component(s)`,
package/bin/validate.js CHANGED
@@ -19,6 +19,7 @@ import process from "node:process";
19
19
  import yargs from "yargs";
20
20
  import { hideBin } from "yargs/helpers";
21
21
 
22
+ import { thoughtLog } from "../lib/core/logger.js";
22
23
  import { installConsoleShim, restoreConsole } from "../lib/core/ui.js";
23
24
  import {
24
25
  dirNameStr,
@@ -32,6 +33,7 @@ import {
32
33
  getNonCycloneDxErrorMessage,
33
34
  isCycloneDxBom,
34
35
  } from "../lib/inventory/bomUtils.js";
36
+ import { formatReadLossNotice } from "../lib/inventory/cyclonedx20.js";
35
37
  import {
36
38
  importProtobomModule,
37
39
  isProtoBomPath,
@@ -80,12 +82,12 @@ const args = _yargs
80
82
  alias: "b",
81
83
  type: "string",
82
84
  description:
83
- "Comma-separated list of compliance benchmark aliases to include in the scorecards (scvs, scvs-l1, scvs-l2, scvs-l3, cra). Defaults to all.",
85
+ "Comma-separated list of compliance benchmark aliases to include in the scorecards (scvs, scvs-l1, scvs-l2, scvs-l3, cra, cisa, cisa-ai). Defaults to all.",
84
86
  })
85
87
  .option("categories", {
86
88
  type: "string",
87
89
  description:
88
- "Comma-separated list of compliance rule categories to evaluate (compliance-scvs, compliance-cra). Defaults to all.",
90
+ "Comma-separated list of compliance rule categories to evaluate (compliance-scvs, compliance-cra, compliance-cisa, compliance-cisa-ai). Defaults to all.",
89
91
  })
90
92
  .option("min-severity", {
91
93
  type: "string",
@@ -323,7 +325,20 @@ try {
323
325
  writeOrPrint(output, args.reportFile);
324
326
 
325
327
  for (const e of report.validationErrors || []) {
326
- console.error(`cdx-validate: ${e.stage} validation threw: ${e.message}`);
328
+ console.error(
329
+ e.stage === "read"
330
+ ? `cdx-validate: ${e.message}`
331
+ : `cdx-validate: ${e.stage} validation threw: ${e.message}`,
332
+ );
333
+ }
334
+ // The deep checks and compliance rules read the 1.x-shaped model of a 2.0
335
+ // input; the losses name the parts of the input they never saw. stderr,
336
+ // because stdout may carry the json or sarif report.
337
+ if (report.readLosses?.length) {
338
+ console.error(formatReadLossNotice(report.readLosses));
339
+ for (const loss of report.readLosses) {
340
+ thoughtLog(`cdx-validate 2.0 loss at ${loss.pointer}: ${loss.reason}`);
341
+ }
327
342
  }
328
343
 
329
344
  const { shouldFail: fail, reason } = shouldFail(report, {
package/bin/verify.js CHANGED
@@ -19,8 +19,10 @@ import {
19
19
  loadVerificationKey,
20
20
  verifyBomSignatures,
21
21
  } from "../lib/helpers/bomSigner.js";
22
+ import { verifyJssBom } from "../lib/helpers/jssSigner.js";
22
23
  import {
23
24
  getNonCycloneDxErrorMessage,
25
+ isCycloneDx20SpecVersion,
24
26
  isCycloneDxBom,
25
27
  } from "../lib/inventory/bomUtils.js";
26
28
  import { isProtoBomPath } from "../lib/inventory/protobomLoader.js";
@@ -45,30 +47,30 @@ const args = _yargs
45
47
  type: "array",
46
48
  string: true,
47
49
  description:
48
- "Trusted public key in PEM format. Repeat to trust several keys; a signature entry verifies when one of them verifies it.",
50
+ "Trusted public key in PEM format. Repeat to trust several keys; a signature entry verifies when one of them verifies it. CycloneDX 2.0 (JSS) documents need at least one: the public_key embedded in a JSS entry is never trusted on its own, and an entry whose public_key is one of these keys must verify with it.",
49
51
  })
50
52
  .option("secret-key", {
51
53
  type: "string",
52
54
  description:
53
- "File holding the shared secret for HMAC (HS256, HS384, HS512) signatures. HMAC signatures are only accepted with this option.",
55
+ "File holding the shared secret for HMAC (HS256, HS384, HS512) signatures. HMAC signatures are only accepted with this option. Not supported for CycloneDX 2.0 (JSS) documents.",
54
56
  })
55
57
  .option("deep", {
56
58
  type: "boolean",
57
59
  default: true,
58
60
  description:
59
- "Strictly verify all nested component, service, and annotation signatures against the provided keys. Pass --no-deep to verify only the root signature.",
61
+ "Strictly verify all nested component, service, and annotation signatures against the provided keys (at 2.0: inventories, their components, compositions, and annotations). Pass --no-deep to verify only the root signature.",
60
62
  })
61
63
  .option("require-all", {
62
64
  type: "boolean",
63
65
  default: false,
64
66
  description:
65
- "Require every entry of a signers or chain signature to verify with one of the keys, instead of at least one.",
67
+ "Require every entry of a signers or chain signature to verify with one of the keys, instead of at least one. At 2.0 every JSS signature object, countersignatures included, must verify with one of the keys, and every key must verify a root signature object.",
66
68
  })
67
69
  .option("allow-nested-only", {
68
70
  type: "boolean",
69
71
  default: false,
70
72
  description:
71
- "Accept a BOM without a root signature when every component, service, and annotation carries a signature that verifies. No signature then covers the other members of the BOM.",
73
+ "Accept a BOM without a root signature when every component, service, and annotation carries a signature that verifies (at 2.0: every inventory, inventory component, composition, and annotation). No signature then covers the other members of the BOM.",
72
74
  })
73
75
  .completion("completion", "Generate bash/zsh completion")
74
76
  .epilogue("for documentation, visit https://cdxgen.github.io/cdxgen")
@@ -159,6 +161,16 @@ if (!isCycloneDxBom(bomJson)) {
159
161
  console.log(getNonCycloneDxErrorMessage(bomJson, "cdx-verify"));
160
162
  process.exit(1);
161
163
  }
164
+ // A CycloneDX 2.0 document carries JSS signatures (ITU-T X.590).
165
+ const isJssBom =
166
+ isCycloneDx20SpecVersion(bomJson.specVersion) ||
167
+ bomJson.specFormat !== undefined;
168
+ if (isJssBom && args.secretKey) {
169
+ console.log(
170
+ "--secret-key is not supported for CycloneDX 2.0 (JSS) signatures: X.590 clause 6.2.2 says HMAC algorithms SHOULD NOT be used, and cdx-verify does not verify them. Pass the signer's --public-key instead.",
171
+ );
172
+ process.exit(1);
173
+ }
162
174
 
163
175
  const verificationKeys = [];
164
176
  for (const keyFile of args.secretKey ? [args.secretKey] : publicKeyFiles) {
@@ -205,6 +217,102 @@ function printRejections(node) {
205
217
  }
206
218
  }
207
219
 
220
+ function describeJssResult(result) {
221
+ const role = result.depth > 0 ? "countersignature" : "signature";
222
+ return `${result.path} ${role} (${displayValue(result.algorithm)})`;
223
+ }
224
+
225
+ function printJssResults(results, prefix = "") {
226
+ for (const result of results) {
227
+ if (result.status === "verified") {
228
+ console.log(
229
+ ` ✓ ${prefix}${describeJssResult(result)} verified with key #${result.keyIndex + 1}: ${publicKeyFiles[result.keyIndex]}`,
230
+ );
231
+ } else {
232
+ console.log(
233
+ ` - ${prefix}${describeJssResult(result)} ${result.status}: ${result.reason || "not verified"}`,
234
+ );
235
+ }
236
+ }
237
+ }
238
+
239
+ if (isJssBom) {
240
+ // JSS verification (X.590 clause 8.1, countersignatures clause 7.2) with
241
+ // the trusted --public-key values only (clause 8.1.3 "fetch it from a
242
+ // trusted source"); the same --deep, --require-all and
243
+ // --allow-nested-only policy as the JSF path below.
244
+ let jssReport;
245
+ try {
246
+ jssReport = verifyJssBom(bomJson, {
247
+ publicKeys: verificationKeys,
248
+ deep: args.deep,
249
+ requireAll: args.requireAll,
250
+ allowNestedOnly: args.allowNestedOnly,
251
+ });
252
+ } catch (error) {
253
+ console.log(`JSS verification failed: ${error.message}`);
254
+ process.exit(1);
255
+ }
256
+ for (const error of jssReport.errors) {
257
+ console.log(error);
258
+ }
259
+ if (args.requireAll && jssReport.root) {
260
+ jssReport.root.keysMatched.forEach((matched, index) => {
261
+ if (!matched) {
262
+ console.log(` - key #${index + 1}: ${publicKeyFiles[index]}`);
263
+ }
264
+ });
265
+ }
266
+ if (jssReport.root) {
267
+ console.log(
268
+ jssReport.root.valid
269
+ ? "BOM JSS signatures:"
270
+ : "BOM signature is invalid!",
271
+ );
272
+ for (const reason of jssReport.root.reasons) {
273
+ console.log(` - ${reason}`);
274
+ }
275
+ printJssResults(jssReport.root.results);
276
+ } else if (!args.allowNestedOnly) {
277
+ console.log(
278
+ "Pass --allow-nested-only to accept a BOM whose inventories, components, compositions, and annotations are all signed.",
279
+ );
280
+ }
281
+ const invalidNested = jssReport.nested.filter((item) => !item.valid);
282
+ for (const item of invalidNested) {
283
+ console.log(`${item.path} signature is invalid!`);
284
+ for (const reason of item.reasons) {
285
+ console.log(` - ${reason}`);
286
+ }
287
+ printJssResults(item.results);
288
+ }
289
+ if (invalidNested.length) {
290
+ console.log("One or more nested signatures are invalid!");
291
+ if (jssReport.root?.valid) {
292
+ console.log(
293
+ "The root signature verifies with this key. Nested signatures belong to the party that created them, so pass --no-deep to verify only the root signature with this key.",
294
+ );
295
+ }
296
+ }
297
+ if (!jssReport.valid) {
298
+ process.exit(1);
299
+ }
300
+ const unverified = [
301
+ ...jssReport.results,
302
+ ...jssReport.nested.flatMap((item) => item.results),
303
+ ].filter((result) => result.status !== "verified");
304
+ if (unverified.length) {
305
+ console.log(
306
+ `Warning: ${unverified.length} JSS signature object(s) did not verify with the given key(s). Pass --require-all to fail in that case.`,
307
+ );
308
+ }
309
+ if (!jssReport.root) {
310
+ console.log(`✓ ${jssReport.nested.length} nested signature(s) are valid!`);
311
+ }
312
+ console.log("JSS signature(s) verified successfully.");
313
+ process.exit(0);
314
+ }
315
+
208
316
  const report = verifyBomSignatures(bomJson, verificationKeys, {
209
317
  deep: args.deep,
210
318
  allowNestedOnly: args.allowNestedOnly,