@cyclonedx/cdxgen 12.4.1 → 12.4.2
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/bin/evinse.js +15 -0
- package/lib/cli/index.js +56 -5
- package/lib/cli/index.poku.js +80 -0
- package/lib/evinser/evinser.js +118 -3
- package/lib/helpers/cbomutils.js +162 -2
- package/lib/helpers/cbomutils.poku.js +100 -0
- package/lib/helpers/ciParsers/githubActions.js +15 -3
- package/lib/helpers/ciParsers/githubActions.poku.js +52 -0
- package/lib/helpers/dosai.js +433 -0
- package/lib/helpers/dosai.poku.js +302 -0
- package/lib/helpers/dosaiParsers.js +103 -0
- package/lib/helpers/utils.js +130 -1
- package/lib/helpers/utils.poku.js +295 -0
- package/lib/stages/postgen/annotator.js +2 -1
- package/lib/stages/postgen/annotator.poku.js +28 -0
- package/package.json +12 -12
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +15 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomUtils.d.ts +1 -3
- package/types/lib/helpers/bomUtils.d.ts.map +1 -1
- package/types/lib/helpers/cbomutils.d.ts +1 -0
- package/types/lib/helpers/cbomutils.d.ts.map +1 -1
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -1
- package/types/lib/helpers/dosai.d.ts +24 -0
- package/types/lib/helpers/dosai.d.ts.map +1 -0
- package/types/lib/helpers/dosaiParsers.d.ts +8 -0
- package/types/lib/helpers/dosaiParsers.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
package/bin/evinse.js
CHANGED
|
@@ -51,15 +51,30 @@ const args = yargs(hideBin(process.argv))
|
|
|
51
51
|
"py",
|
|
52
52
|
"python",
|
|
53
53
|
"android",
|
|
54
|
+
"csharp",
|
|
55
|
+
"cs",
|
|
54
56
|
"c",
|
|
55
57
|
"cpp",
|
|
58
|
+
"dotnet",
|
|
56
59
|
"php",
|
|
57
60
|
"swift",
|
|
58
61
|
"ios",
|
|
59
62
|
"ruby",
|
|
60
63
|
"scala",
|
|
64
|
+
"vb",
|
|
65
|
+
"vbnet",
|
|
66
|
+
"visualbasic",
|
|
67
|
+
"f#",
|
|
68
|
+
"fs",
|
|
69
|
+
"fsharp",
|
|
61
70
|
],
|
|
62
71
|
})
|
|
72
|
+
.option("profile", {
|
|
73
|
+
description:
|
|
74
|
+
"Evidence profile. The research profile enables dosai data-flow and crypto analysis for .NET projects.",
|
|
75
|
+
default: "generic",
|
|
76
|
+
choices: ["generic", "research"],
|
|
77
|
+
})
|
|
63
78
|
.option("db-path", {
|
|
64
79
|
description: "Atom slices DB path. Unused",
|
|
65
80
|
default: undefined,
|
package/lib/cli/index.js
CHANGED
|
@@ -46,7 +46,10 @@ import {
|
|
|
46
46
|
toCycloneDxSpecVersionString,
|
|
47
47
|
} from "../helpers/bomUtils.js";
|
|
48
48
|
import { parseCaxaMetadata } from "../helpers/caxa.js";
|
|
49
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
collectDosaiCryptoComponents,
|
|
51
|
+
collectSourceCryptoComponents,
|
|
52
|
+
} from "../helpers/cbomutils.js";
|
|
50
53
|
import {
|
|
51
54
|
CHROME_EXTENSION_PURL_TYPE,
|
|
52
55
|
collectChromeExtensionsFromPath,
|
|
@@ -58,6 +61,13 @@ import {
|
|
|
58
61
|
mergeServices,
|
|
59
62
|
trimComponents,
|
|
60
63
|
} from "../helpers/depsUtils.js";
|
|
64
|
+
import {
|
|
65
|
+
collectDosaiServicesFromMethods,
|
|
66
|
+
createDosaiMethodsSlice,
|
|
67
|
+
isDosaiDotnetLanguage,
|
|
68
|
+
normalizeDosaiServiceMap,
|
|
69
|
+
readDosaiJsonFile,
|
|
70
|
+
} from "../helpers/dosai.js";
|
|
61
71
|
import { GIT_COMMAND } from "../helpers/envcontext.js";
|
|
62
72
|
import {
|
|
63
73
|
createHbomDocument,
|
|
@@ -246,7 +256,6 @@ import {
|
|
|
246
256
|
enrichOSComponentsWithTrustData,
|
|
247
257
|
executeOsQuery,
|
|
248
258
|
getBinaryBom,
|
|
249
|
-
getDotnetSlices,
|
|
250
259
|
getOSPackages,
|
|
251
260
|
getPluginToolComponents,
|
|
252
261
|
} from "../managers/binary.js";
|
|
@@ -400,6 +409,27 @@ const hasExplicitProjectTypeSelection = (options, baseProjectType) => {
|
|
|
400
409
|
);
|
|
401
410
|
};
|
|
402
411
|
|
|
412
|
+
const hasDotnetProjectIndicators = (src, options = {}) => {
|
|
413
|
+
return Boolean(
|
|
414
|
+
getAllFiles(src, "**/*.{csproj,fsproj,vbproj,sln}", options)?.length,
|
|
415
|
+
);
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const shouldCollectDosaiCrypto = (src, options = {}) => {
|
|
419
|
+
const projectTypes = Array.isArray(options.projectType)
|
|
420
|
+
? options.projectType
|
|
421
|
+
: options.projectType
|
|
422
|
+
? [options.projectType]
|
|
423
|
+
: [];
|
|
424
|
+
if (projectTypes.some((projectType) => isDosaiDotnetLanguage(projectType))) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
if (!projectTypes.length || projectTypes.includes("universal")) {
|
|
428
|
+
return hasDotnetProjectIndicators(src, options);
|
|
429
|
+
}
|
|
430
|
+
return false;
|
|
431
|
+
};
|
|
432
|
+
|
|
403
433
|
const determineParentComponent = (options) => {
|
|
404
434
|
let parentComponent;
|
|
405
435
|
if (options.parentComponent && Object.keys(options.parentComponent).length) {
|
|
@@ -7742,6 +7772,7 @@ export async function createCsharpBom(path, options) {
|
|
|
7742
7772
|
}
|
|
7743
7773
|
}
|
|
7744
7774
|
const pkgNameVersions = {};
|
|
7775
|
+
let services = [];
|
|
7745
7776
|
if (csProjFiles.length) {
|
|
7746
7777
|
manifestFiles = manifestFiles.concat(csProjFiles);
|
|
7747
7778
|
// Parsing csproj is quite error-prone. Some project files may not have versions specified
|
|
@@ -7803,21 +7834,30 @@ export async function createCsharpBom(path, options) {
|
|
|
7803
7834
|
// Perform deep analysis using dosai
|
|
7804
7835
|
if (options.deep) {
|
|
7805
7836
|
const slicesFile = resolve(
|
|
7806
|
-
|
|
7837
|
+
options.depsSlicesFile
|
|
7838
|
+
? join(path, options.depsSlicesFile)
|
|
7839
|
+
: join(getTmpDir(), "dosai.json"),
|
|
7807
7840
|
);
|
|
7808
7841
|
// Create the slices file if it doesn't exist
|
|
7809
7842
|
if (!safeExistsSync(slicesFile)) {
|
|
7810
7843
|
thoughtLog(
|
|
7811
7844
|
"Alright, the next step is to invoke the dosai command to identify evidence of occurrences for various components.",
|
|
7812
7845
|
);
|
|
7813
|
-
const sliceResult =
|
|
7846
|
+
const sliceResult = createDosaiMethodsSlice(
|
|
7847
|
+
resolve(path),
|
|
7848
|
+
resolve(slicesFile),
|
|
7849
|
+
options,
|
|
7850
|
+
);
|
|
7814
7851
|
if (!sliceResult && DEBUG_MODE) {
|
|
7815
7852
|
console.log(
|
|
7816
7853
|
"Slicing with dosai was unsuccessful. Check the errors reported in the logs above.",
|
|
7817
7854
|
);
|
|
7818
7855
|
}
|
|
7819
7856
|
}
|
|
7820
|
-
pkgList = addEvidenceForDotnet(pkgList, slicesFile
|
|
7857
|
+
pkgList = addEvidenceForDotnet(pkgList, slicesFile);
|
|
7858
|
+
const methodsSlice = readDosaiJsonFile(slicesFile);
|
|
7859
|
+
const servicesMap = collectDosaiServicesFromMethods(methodsSlice, {});
|
|
7860
|
+
services = normalizeDosaiServiceMap(servicesMap);
|
|
7821
7861
|
}
|
|
7822
7862
|
}
|
|
7823
7863
|
// Parent dependency tree
|
|
@@ -7843,6 +7883,8 @@ export async function createCsharpBom(path, options) {
|
|
|
7843
7883
|
filename: manifestFiles.join(", "),
|
|
7844
7884
|
dependencies,
|
|
7845
7885
|
parentComponent,
|
|
7886
|
+
services,
|
|
7887
|
+
tools: options.deep ? getPluginToolComponents(["dosai"]) : [],
|
|
7846
7888
|
});
|
|
7847
7889
|
}
|
|
7848
7890
|
|
|
@@ -8354,6 +8396,15 @@ export async function createCryptoCertsBom(path, options) {
|
|
|
8354
8396
|
if (sourceCryptoComponents.length) {
|
|
8355
8397
|
pkgList.push(...sourceCryptoComponents);
|
|
8356
8398
|
}
|
|
8399
|
+
if (shouldCollectDosaiCrypto(path, options)) {
|
|
8400
|
+
const dosaiCryptoComponents = await collectDosaiCryptoComponents(
|
|
8401
|
+
path,
|
|
8402
|
+
options,
|
|
8403
|
+
);
|
|
8404
|
+
if (dosaiCryptoComponents.length) {
|
|
8405
|
+
pkgList.push(...dosaiCryptoComponents);
|
|
8406
|
+
}
|
|
8407
|
+
}
|
|
8357
8408
|
return {
|
|
8358
8409
|
bomJson: {
|
|
8359
8410
|
components: pkgList,
|
package/lib/cli/index.poku.js
CHANGED
|
@@ -286,6 +286,86 @@ describe("CLI tests", () => {
|
|
|
286
286
|
);
|
|
287
287
|
assert.strictEqual(components[0].type, "data");
|
|
288
288
|
});
|
|
289
|
+
|
|
290
|
+
it("does not invoke dosai crypto analysis for non-.NET CBOM scans", async () => {
|
|
291
|
+
const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-cbom-non-dotnet-"));
|
|
292
|
+
const collectDosaiCryptoComponents = sinon.stub().resolves([]);
|
|
293
|
+
try {
|
|
294
|
+
const { createCryptoCertsBom } = await esmock("./index.js", {
|
|
295
|
+
"../helpers/cbomutils.js": {
|
|
296
|
+
collectDosaiCryptoComponents,
|
|
297
|
+
collectSourceCryptoComponents: sinon.stub().resolves([]),
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
await createCryptoCertsBom(tempDir, {
|
|
302
|
+
projectType: ["js"],
|
|
303
|
+
specVersion: 1.7,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
sinon.assert.notCalled(collectDosaiCryptoComponents);
|
|
307
|
+
} finally {
|
|
308
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("invokes dosai crypto analysis for explicit .NET CBOM scans", async () => {
|
|
313
|
+
const tempDir = mkdtempSync(join(tmpdir(), "cdxgen-cbom-dotnet-"));
|
|
314
|
+
const collectDosaiCryptoComponents = sinon.stub().resolves([
|
|
315
|
+
{
|
|
316
|
+
name: "sha-256",
|
|
317
|
+
type: "cryptographic-asset",
|
|
318
|
+
"bom-ref": "crypto/algorithm/sha-256@2.16.840.1.101.3.4.2.1",
|
|
319
|
+
cryptoProperties: {
|
|
320
|
+
assetType: "algorithm",
|
|
321
|
+
oid: "2.16.840.1.101.3.4.2.1",
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
]);
|
|
325
|
+
try {
|
|
326
|
+
const { createCryptoCertsBom } = await esmock("./index.js", {
|
|
327
|
+
"../helpers/cbomutils.js": {
|
|
328
|
+
collectDosaiCryptoComponents,
|
|
329
|
+
collectSourceCryptoComponents: sinon.stub().resolves([]),
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const bomData = await createCryptoCertsBom(tempDir, {
|
|
334
|
+
projectType: ["dotnet"],
|
|
335
|
+
specVersion: 1.7,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
sinon.assert.calledOnce(collectDosaiCryptoComponents);
|
|
339
|
+
assert.strictEqual(bomData.bomJson.components[0].name, "sha-256");
|
|
340
|
+
} finally {
|
|
341
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("invokes dosai crypto analysis when universal scans contain .NET project files", async () => {
|
|
346
|
+
const tempDir = mkdtempSync(
|
|
347
|
+
join(tmpdir(), "cdxgen-cbom-dotnet-indicator-"),
|
|
348
|
+
);
|
|
349
|
+
const collectDosaiCryptoComponents = sinon.stub().resolves([]);
|
|
350
|
+
try {
|
|
351
|
+
writeFileSync(join(tempDir, "app.csproj"), "<Project />");
|
|
352
|
+
const { createCryptoCertsBom } = await esmock("./index.js", {
|
|
353
|
+
"../helpers/cbomutils.js": {
|
|
354
|
+
collectDosaiCryptoComponents,
|
|
355
|
+
collectSourceCryptoComponents: sinon.stub().resolves([]),
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
await createCryptoCertsBom(tempDir, {
|
|
360
|
+
projectType: ["universal"],
|
|
361
|
+
specVersion: 1.7,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
sinon.assert.calledOnce(collectDosaiCryptoComponents);
|
|
365
|
+
} finally {
|
|
366
|
+
rmSync(tempDir, { force: true, recursive: true });
|
|
367
|
+
}
|
|
368
|
+
});
|
|
289
369
|
});
|
|
290
370
|
|
|
291
371
|
describe("distribution filters", () => {
|
package/lib/evinser/evinser.js
CHANGED
|
@@ -4,7 +4,19 @@ import process from "node:process";
|
|
|
4
4
|
|
|
5
5
|
import { PackageURL } from "packageurl-js";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
collectDosaiCryptoComponents,
|
|
9
|
+
findCryptoAlgos,
|
|
10
|
+
} from "../helpers/cbomutils.js";
|
|
11
|
+
import { mergeServices } from "../helpers/depsUtils.js";
|
|
12
|
+
import {
|
|
13
|
+
collectDosaiDataFlowFrames,
|
|
14
|
+
collectDosaiPurlEvidence,
|
|
15
|
+
collectDosaiServicesFromMethods,
|
|
16
|
+
createDosaiDataFlowSlice,
|
|
17
|
+
createDosaiMethodsSlice,
|
|
18
|
+
isDosaiDotnetLanguage,
|
|
19
|
+
} from "../helpers/dosai.js";
|
|
8
20
|
import { parseOccurrenceEvidenceLocation } from "../helpers/evidenceUtils.js";
|
|
9
21
|
import {
|
|
10
22
|
collectGradleDependencies,
|
|
@@ -230,6 +242,8 @@ export async function createSlice(
|
|
|
230
242
|
language = "python";
|
|
231
243
|
} else if (PROJECT_TYPE_ALIASES.scala.includes(language)) {
|
|
232
244
|
language = "scala";
|
|
245
|
+
} else if (isDosaiDotnetLanguage(language)) {
|
|
246
|
+
language = "csharp";
|
|
233
247
|
}
|
|
234
248
|
if (
|
|
235
249
|
PROJECT_TYPE_ALIASES.swift.includes(language) &&
|
|
@@ -270,6 +284,33 @@ export async function createSlice(
|
|
|
270
284
|
}
|
|
271
285
|
return { tempDir: sliceOutputDir, tempDirOwned, slicesFile };
|
|
272
286
|
}
|
|
287
|
+
if (isDosaiDotnetLanguage(language)) {
|
|
288
|
+
console.log(
|
|
289
|
+
`Creating ${sliceType} slice for ${resolve(filePath)} using dosai. Please wait ...`,
|
|
290
|
+
);
|
|
291
|
+
const sliceResult =
|
|
292
|
+
sliceType === "data-flow"
|
|
293
|
+
? createDosaiDataFlowSlice(
|
|
294
|
+
resolve(filePath),
|
|
295
|
+
resolve(slicesFile),
|
|
296
|
+
options,
|
|
297
|
+
)
|
|
298
|
+
: createDosaiMethodsSlice(
|
|
299
|
+
resolve(filePath),
|
|
300
|
+
resolve(slicesFile),
|
|
301
|
+
options,
|
|
302
|
+
);
|
|
303
|
+
if (!sliceResult) {
|
|
304
|
+
console.warn(
|
|
305
|
+
`Unable to generate ${sliceType} slice using dosai. Check if this is a supported .NET project.`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
tempDir: sliceOutputDir,
|
|
310
|
+
tempDirOwned,
|
|
311
|
+
slicesFile,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
273
314
|
console.log(
|
|
274
315
|
`Creating ${sliceType} slice for ${resolve(filePath)}. Please wait ...`,
|
|
275
316
|
);
|
|
@@ -393,6 +434,9 @@ export function purlToLanguage(purl, filePath) {
|
|
|
393
434
|
case "gem":
|
|
394
435
|
language = "ruby";
|
|
395
436
|
break;
|
|
437
|
+
case "nuget":
|
|
438
|
+
language = "csharp";
|
|
439
|
+
break;
|
|
396
440
|
case "generic":
|
|
397
441
|
language = "c";
|
|
398
442
|
}
|
|
@@ -474,6 +518,77 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
474
518
|
// Load any existing purl-location information from the sbom.
|
|
475
519
|
// For eg: cdxgen populates this information for javascript projects
|
|
476
520
|
let { purlLocationMap, purlImportsMap } = initFromSbom(components, language);
|
|
521
|
+
if (isDosaiDotnetLanguage(language)) {
|
|
522
|
+
if (options.profile === "research") {
|
|
523
|
+
options.withDataFlow = true;
|
|
524
|
+
options.includeCrypto = true;
|
|
525
|
+
}
|
|
526
|
+
if (
|
|
527
|
+
options.usagesSlicesFile &&
|
|
528
|
+
usableSlicesFile(options.usagesSlicesFile)
|
|
529
|
+
) {
|
|
530
|
+
usageSlice = JSON.parse(
|
|
531
|
+
fs.readFileSync(options.usagesSlicesFile, "utf-8"),
|
|
532
|
+
);
|
|
533
|
+
usagesSlicesFile = options.usagesSlicesFile;
|
|
534
|
+
} else {
|
|
535
|
+
retMap = await createSlice(language, dirPath, "usages", options);
|
|
536
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
537
|
+
usageSlice = JSON.parse(fs.readFileSync(retMap.slicesFile, "utf-8"));
|
|
538
|
+
usagesSlicesFile = retMap.slicesFile;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (usageSlice && Object.keys(usageSlice).length) {
|
|
542
|
+
const dosaiEvidence = collectDosaiPurlEvidence(usageSlice, components);
|
|
543
|
+
for (const [purl, locations] of Object.entries(
|
|
544
|
+
dosaiEvidence.purlLocationMap,
|
|
545
|
+
)) {
|
|
546
|
+
purlLocationMap[purl] ??= new Set();
|
|
547
|
+
for (const location of locations) {
|
|
548
|
+
purlLocationMap[purl].add(location);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
servicesMap = collectDosaiServicesFromMethods(usageSlice, servicesMap);
|
|
552
|
+
userDefinedTypesMap = {};
|
|
553
|
+
}
|
|
554
|
+
if (options.withDataFlow) {
|
|
555
|
+
if (
|
|
556
|
+
options.dataFlowSlicesFile &&
|
|
557
|
+
safeExistsSync(options.dataFlowSlicesFile)
|
|
558
|
+
) {
|
|
559
|
+
dataFlowSlicesFile = options.dataFlowSlicesFile;
|
|
560
|
+
dataFlowSlice = JSON.parse(
|
|
561
|
+
fs.readFileSync(options.dataFlowSlicesFile, "utf-8"),
|
|
562
|
+
);
|
|
563
|
+
} else {
|
|
564
|
+
retMap = await createSlice(language, dirPath, "data-flow", options);
|
|
565
|
+
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
566
|
+
dataFlowSlicesFile = retMap.slicesFile;
|
|
567
|
+
dataFlowSlice = JSON.parse(
|
|
568
|
+
fs.readFileSync(retMap.slicesFile, "utf-8"),
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (dataFlowSlice && Object.keys(dataFlowSlice).length) {
|
|
573
|
+
dataFlowFrames = collectDosaiDataFlowFrames(dataFlowSlice, components);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (options.includeCrypto) {
|
|
577
|
+
cryptoComponents = await collectDosaiCryptoComponents(dirPath, options);
|
|
578
|
+
}
|
|
579
|
+
return {
|
|
580
|
+
usagesSlicesFile,
|
|
581
|
+
dataFlowSlicesFile,
|
|
582
|
+
purlLocationMap,
|
|
583
|
+
servicesMap,
|
|
584
|
+
dataFlowFrames,
|
|
585
|
+
tempDir: retMap?.tempDir,
|
|
586
|
+
tempDirOwned: retMap?.tempDirOwned,
|
|
587
|
+
userDefinedTypesMap,
|
|
588
|
+
cryptoComponents,
|
|
589
|
+
cryptoGeneratePurls,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
477
592
|
// Do reachables first so that usages slicing can reuse the atom file
|
|
478
593
|
// We need reachables slicing even when trying to infer crypto packages
|
|
479
594
|
if (options.withReachables || options.includeCrypto) {
|
|
@@ -1472,8 +1587,8 @@ export function createEvinseFile(sliceArtefacts, options) {
|
|
|
1472
1587
|
properties: servicesMap[serviceName].properties,
|
|
1473
1588
|
});
|
|
1474
1589
|
}
|
|
1475
|
-
// Add to existing services
|
|
1476
|
-
bomJson.services = (bomJson.services || []
|
|
1590
|
+
// Add to existing services while preserving CycloneDX uniqueItems validity
|
|
1591
|
+
bomJson.services = mergeServices(bomJson.services || [], services);
|
|
1477
1592
|
servicesPresent = true;
|
|
1478
1593
|
}
|
|
1479
1594
|
// Add the crypto components to the components list
|
package/lib/helpers/cbomutils.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { executeOsQuery } from "../managers/binary.js";
|
|
5
5
|
import { detectJsCryptoInventory } from "./analyzer.js";
|
|
6
|
+
import { analyzeDosaiCrypto } from "./dosai.js";
|
|
6
7
|
import {
|
|
7
8
|
createOccurrenceEvidence,
|
|
8
9
|
formatOccurrenceEvidence,
|
|
@@ -239,16 +240,19 @@ function mergeAlgorithmComponentUsage(component, usage, src, options) {
|
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
242
|
if (usage.source) {
|
|
243
|
+
const sourceType =
|
|
244
|
+
usage.source === "dosai" ? undefined : `js-ast:${usage.source}`;
|
|
242
245
|
if (
|
|
246
|
+
sourceType &&
|
|
243
247
|
!properties.some(
|
|
244
248
|
(property) =>
|
|
245
249
|
property.name === "cdx:crypto:sourceType" &&
|
|
246
|
-
property.value ===
|
|
250
|
+
property.value === sourceType,
|
|
247
251
|
)
|
|
248
252
|
) {
|
|
249
253
|
properties.push({
|
|
250
254
|
name: "cdx:crypto:sourceType",
|
|
251
|
-
value:
|
|
255
|
+
value: sourceType,
|
|
252
256
|
});
|
|
253
257
|
}
|
|
254
258
|
}
|
|
@@ -271,6 +275,93 @@ function mergeAlgorithmComponentUsage(component, usage, src, options) {
|
|
|
271
275
|
return component;
|
|
272
276
|
}
|
|
273
277
|
|
|
278
|
+
function normalizeDosaiCryptoNames(cryptoObject) {
|
|
279
|
+
const rawName = cryptoObject?.Name || cryptoObject;
|
|
280
|
+
const names = new Set([rawName]);
|
|
281
|
+
const cleanName = String(rawName || "").trim();
|
|
282
|
+
if (!cleanName) {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
if (cleanName.includes("/")) {
|
|
286
|
+
for (const part of cleanName
|
|
287
|
+
.split("/")
|
|
288
|
+
.map((candidate) => candidate.trim())
|
|
289
|
+
.filter(Boolean)) {
|
|
290
|
+
names.add(part);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (/^SHA-?256$/i.test(cleanName)) {
|
|
294
|
+
names.add("sha-256");
|
|
295
|
+
} else if (/^SHA-?384$/i.test(cleanName)) {
|
|
296
|
+
names.add("sha-384");
|
|
297
|
+
} else if (/^SHA-?512$/i.test(cleanName)) {
|
|
298
|
+
names.add("sha-512");
|
|
299
|
+
} else if (/^SHA-?1$/i.test(cleanName)) {
|
|
300
|
+
names.add("sha-1");
|
|
301
|
+
}
|
|
302
|
+
const context = [
|
|
303
|
+
cryptoObject?.Symbol,
|
|
304
|
+
cryptoObject?.Code,
|
|
305
|
+
cryptoObject?.Algorithm,
|
|
306
|
+
]
|
|
307
|
+
.filter(Boolean)
|
|
308
|
+
.join(" ");
|
|
309
|
+
if (/^SHA-?2$/i.test(cleanName)) {
|
|
310
|
+
if (/SHA-?256/i.test(context)) {
|
|
311
|
+
names.add("sha-256");
|
|
312
|
+
}
|
|
313
|
+
if (/SHA-?384/i.test(context)) {
|
|
314
|
+
names.add("sha-384");
|
|
315
|
+
}
|
|
316
|
+
if (/SHA-?512/i.test(context)) {
|
|
317
|
+
names.add("sha-512");
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return Array.from(names);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function dosaiCryptoUsage(assetOrOperation) {
|
|
324
|
+
const location = assetOrOperation.Location || {};
|
|
325
|
+
return {
|
|
326
|
+
fileName: location.Path || location.FileName,
|
|
327
|
+
lineNumber: location.LineNumber || undefined,
|
|
328
|
+
columnNumber: location.ColumnNumber || undefined,
|
|
329
|
+
primitive: assetOrOperation.Family || assetOrOperation.OperationType,
|
|
330
|
+
source: "dosai",
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function addDosaiProperties(component, dosaiObject, evidenceType) {
|
|
335
|
+
const properties = component.properties || [];
|
|
336
|
+
const addProperty = (name, value) => {
|
|
337
|
+
if (value === undefined || value === null || value === "") {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (
|
|
341
|
+
!properties.some(
|
|
342
|
+
(property) =>
|
|
343
|
+
property.name === name && property.value === String(value),
|
|
344
|
+
)
|
|
345
|
+
) {
|
|
346
|
+
properties.push({ name, value: String(value) });
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
addProperty("cdx:crypto:sourceType", `dosai:${evidenceType}`);
|
|
350
|
+
addProperty("cdx:dosai:crypto:id", dosaiObject.Id);
|
|
351
|
+
addProperty("cdx:dosai:crypto:strength", dosaiObject.Strength);
|
|
352
|
+
addProperty(
|
|
353
|
+
"cdx:dosai:crypto:reachableFromEntryPoint",
|
|
354
|
+
dosaiObject.ReachableFromEntryPoint,
|
|
355
|
+
);
|
|
356
|
+
if (dosaiObject.EntryPointIds?.length) {
|
|
357
|
+
addProperty(
|
|
358
|
+
"cdx:dosai:crypto:entryPointCount",
|
|
359
|
+
dosaiObject.EntryPointIds.length,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
component.properties = properties;
|
|
363
|
+
}
|
|
364
|
+
|
|
274
365
|
export async function collectSourceCryptoComponents(src, options = {}) {
|
|
275
366
|
const inventory = await detectJsCryptoInventory(src, Boolean(options.deep));
|
|
276
367
|
const componentsByRef = new Map();
|
|
@@ -317,6 +408,75 @@ export async function collectSourceCryptoComponents(src, options = {}) {
|
|
|
317
408
|
);
|
|
318
409
|
}
|
|
319
410
|
|
|
411
|
+
export async function collectDosaiCryptoComponents(src, options = {}) {
|
|
412
|
+
const dosaiCrypto = analyzeDosaiCrypto(src, options);
|
|
413
|
+
if (!dosaiCrypto) {
|
|
414
|
+
return [];
|
|
415
|
+
}
|
|
416
|
+
const componentsByRef = new Map();
|
|
417
|
+
const cryptoObjects = [
|
|
418
|
+
...(dosaiCrypto.Assets || []).filter(
|
|
419
|
+
(asset) => asset.AssetType === "algorithm",
|
|
420
|
+
),
|
|
421
|
+
...(dosaiCrypto.Operations || []).map((operation) => ({
|
|
422
|
+
...operation,
|
|
423
|
+
Name: operation.Algorithm,
|
|
424
|
+
Family: operation.OperationType,
|
|
425
|
+
})),
|
|
426
|
+
];
|
|
427
|
+
for (const cryptoObject of cryptoObjects) {
|
|
428
|
+
for (const candidateName of normalizeDosaiCryptoNames(cryptoObject)) {
|
|
429
|
+
const normalizedName = normalizeDetectedCryptoAlgorithmName(
|
|
430
|
+
candidateName,
|
|
431
|
+
"algorithm",
|
|
432
|
+
);
|
|
433
|
+
const algorithmMetadata =
|
|
434
|
+
cbomCryptoOids[normalizedName] || cbomCryptoOids[candidateName];
|
|
435
|
+
if (!algorithmMetadata?.oid) {
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const bomRef = cryptoAlgorithmBomRef(
|
|
439
|
+
normalizedName,
|
|
440
|
+
algorithmMetadata.oid,
|
|
441
|
+
);
|
|
442
|
+
const component = componentsByRef.get(bomRef) || {
|
|
443
|
+
type: "cryptographic-asset",
|
|
444
|
+
name: normalizedName,
|
|
445
|
+
"bom-ref": bomRef,
|
|
446
|
+
description:
|
|
447
|
+
algorithmMetadata.description ||
|
|
448
|
+
"Cryptographic algorithm detected by dosai source analysis",
|
|
449
|
+
cryptoProperties: {
|
|
450
|
+
assetType: "algorithm",
|
|
451
|
+
oid: algorithmMetadata.oid,
|
|
452
|
+
},
|
|
453
|
+
properties: [],
|
|
454
|
+
};
|
|
455
|
+
mergeAlgorithmComponentUsage(
|
|
456
|
+
component,
|
|
457
|
+
dosaiCryptoUsage(cryptoObject),
|
|
458
|
+
src,
|
|
459
|
+
options,
|
|
460
|
+
);
|
|
461
|
+
addDosaiProperties(
|
|
462
|
+
component,
|
|
463
|
+
cryptoObject,
|
|
464
|
+
cryptoObject.OperationType ? "operation" : "asset",
|
|
465
|
+
);
|
|
466
|
+
componentsByRef.set(bomRef, component);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const components = Array.from(componentsByRef.values());
|
|
470
|
+
components.forEach((component) => {
|
|
471
|
+
normalizeCryptoComponentEvidence(component, options);
|
|
472
|
+
});
|
|
473
|
+
return components.sort((left, right) =>
|
|
474
|
+
`${left.name}:${left["bom-ref"]}`.localeCompare(
|
|
475
|
+
`${right.name}:${right["bom-ref"]}`,
|
|
476
|
+
),
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
320
480
|
/**
|
|
321
481
|
* Find crypto algorithm in the given code snippet
|
|
322
482
|
*
|