@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
package/lib/inventory/spdx.js
CHANGED
|
@@ -440,6 +440,150 @@ export function licenseIdFromText(content) {
|
|
|
440
440
|
return undefined;
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
+
// The GNU licenses by the title line their text opens with, and their SPDX
|
|
444
|
+
// ids by version.
|
|
445
|
+
const GNU_LICENSE_TITLES = {
|
|
446
|
+
"GNU AFFERO GENERAL PUBLIC LICENSE": { 3: "AGPL-3.0-only" },
|
|
447
|
+
"GNU LESSER GENERAL PUBLIC LICENSE": {
|
|
448
|
+
3: "LGPL-3.0-only",
|
|
449
|
+
2.1: "LGPL-2.1-only",
|
|
450
|
+
2: "LGPL-2.0-only",
|
|
451
|
+
},
|
|
452
|
+
"GNU LIBRARY GENERAL PUBLIC LICENSE": {
|
|
453
|
+
2.1: "LGPL-2.1-only",
|
|
454
|
+
2: "LGPL-2.0-only",
|
|
455
|
+
},
|
|
456
|
+
"GNU GENERAL PUBLIC LICENSE": {
|
|
457
|
+
3: "GPL-3.0-only",
|
|
458
|
+
2: "GPL-2.0-only",
|
|
459
|
+
1: "GPL-1.0-only",
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
// Phrases that only the grant of one license carries, so finding one anywhere
|
|
464
|
+
// in a notice means that license is granted, not merely mentioned.
|
|
465
|
+
const LICENSE_GRANT_PHRASES = [
|
|
466
|
+
["PERMISSION IS HEREBY GRANTED, FREE OF CHARGE", "MIT"],
|
|
467
|
+
[
|
|
468
|
+
"PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED",
|
|
469
|
+
"ISC",
|
|
470
|
+
],
|
|
471
|
+
[
|
|
472
|
+
"PERMISSION IS GRANTED TO ANYONE TO USE THIS SOFTWARE FOR ANY PURPOSE",
|
|
473
|
+
"Zlib",
|
|
474
|
+
],
|
|
475
|
+
[
|
|
476
|
+
"THIS IS FREE AND UNENCUMBERED SOFTWARE RELEASED INTO THE PUBLIC DOMAIN",
|
|
477
|
+
"Unlicense",
|
|
478
|
+
],
|
|
479
|
+
["BOOST SOFTWARE LICENSE - VERSION 1.0", "BSL-1.0"],
|
|
480
|
+
["MOZILLA PUBLIC LICENSE VERSION 2.0", "MPL-2.0"],
|
|
481
|
+
["MOZILLA PUBLIC LICENSE, VERSION 2.0", "MPL-2.0"],
|
|
482
|
+
["MOZILLA PUBLIC LICENSE, V. 2.0", "MPL-2.0"],
|
|
483
|
+
[
|
|
484
|
+
"CREATIVE COMMONS ATTRIBUTION 4.0 INTERNATIONAL PUBLIC LICENSE",
|
|
485
|
+
"CC-BY-4.0",
|
|
486
|
+
],
|
|
487
|
+
[
|
|
488
|
+
"CREATIVE COMMONS ATTRIBUTION-SHAREALIKE 4.0 INTERNATIONAL PUBLIC LICENSE",
|
|
489
|
+
"CC-BY-SA-4.0",
|
|
490
|
+
],
|
|
491
|
+
["CC0 1.0 UNIVERSAL", "CC0-1.0"],
|
|
492
|
+
];
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Identify every license a notice grants, in the order the notice grants
|
|
496
|
+
* them. A notice can grant several: one LICENSE file holding an MIT grant and
|
|
497
|
+
* an Apache-2.0 grant (gopkg.in/yaml.v3), or a choice of licenses followed by
|
|
498
|
+
* each full text (github.com/spdx/tools-golang). A license text quotes others
|
|
499
|
+
* (the GNU GPL names the GNU Lesser GPL, the MPL names the GNU licenses), so
|
|
500
|
+
* only phrases that a grant carries count: a GNU license is found by its title
|
|
501
|
+
* standing on a line of its own, the others by a sentence of their grant.
|
|
502
|
+
* A notice none of these phrases match falls back to {@link guessLicenseId}.
|
|
503
|
+
*
|
|
504
|
+
* @param {string} content License file contents
|
|
505
|
+
* @returns {string[]} SPDX license ids or expressions; empty when none is
|
|
506
|
+
* recognized
|
|
507
|
+
*/
|
|
508
|
+
export function guessLicenseIds(content) {
|
|
509
|
+
if (typeof content !== "string" || !content.trim()) {
|
|
510
|
+
return [];
|
|
511
|
+
}
|
|
512
|
+
const text = content.replace(/\s+/g, " ").toUpperCase();
|
|
513
|
+
const found = [];
|
|
514
|
+
const add = (at, id) => {
|
|
515
|
+
if (at >= 0 && id) {
|
|
516
|
+
found.push({ at, id });
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
// Walk the lines alongside `text`, so a title line is placed in it.
|
|
520
|
+
let offset = 0;
|
|
521
|
+
for (const line of content.split("\n")) {
|
|
522
|
+
const normalized = line.replace(/\s+/g, " ").trim().toUpperCase();
|
|
523
|
+
const at = normalized ? text.indexOf(normalized, offset) : -1;
|
|
524
|
+
if (at < 0) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
offset = at + normalized.length;
|
|
528
|
+
const ids = GNU_LICENSE_TITLES[normalized];
|
|
529
|
+
if (ids) {
|
|
530
|
+
const version = text
|
|
531
|
+
.slice(offset, offset + 60)
|
|
532
|
+
.match(/VERSION (\d(?:\.\d)?)/)?.[1];
|
|
533
|
+
add(at, ids[version]);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
for (const marker of [
|
|
537
|
+
"APACHE LICENSE VERSION 2.0",
|
|
538
|
+
"APACHE LICENSE, VERSION 2.0",
|
|
539
|
+
"APACHE LICENSE 2.0",
|
|
540
|
+
]) {
|
|
541
|
+
add(
|
|
542
|
+
text.indexOf(marker),
|
|
543
|
+
text.includes("LLVM EXCEPTION")
|
|
544
|
+
? "Apache-2.0 WITH LLVM-exception"
|
|
545
|
+
: "Apache-2.0",
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
for (const [phrase, id] of LICENSE_GRANT_PHRASES) {
|
|
549
|
+
add(text.indexOf(phrase), id);
|
|
550
|
+
}
|
|
551
|
+
// A notice can hold several BSD texts (one per copyright holder), each read
|
|
552
|
+
// up to where the next one starts.
|
|
553
|
+
const bsdPhrase = "REDISTRIBUTION AND USE IN SOURCE AND BINARY FORMS";
|
|
554
|
+
for (
|
|
555
|
+
let bsdAt = text.indexOf(bsdPhrase);
|
|
556
|
+
bsdAt >= 0;
|
|
557
|
+
bsdAt = text.indexOf(bsdPhrase, bsdAt + bsdPhrase.length)
|
|
558
|
+
) {
|
|
559
|
+
const nextAt = text.indexOf(bsdPhrase, bsdAt + bsdPhrase.length);
|
|
560
|
+
const clauses = text.slice(
|
|
561
|
+
bsdAt,
|
|
562
|
+
Math.min(bsdAt + 2000, nextAt < 0 ? text.length : nextAt),
|
|
563
|
+
);
|
|
564
|
+
add(
|
|
565
|
+
bsdAt,
|
|
566
|
+
clauses.includes("ALL ADVERTISING MATERIALS MENTIONING")
|
|
567
|
+
? "BSD-4-Clause"
|
|
568
|
+
: clauses.includes("NEITHER THE NAME") ||
|
|
569
|
+
clauses.includes("THE NAME OF THE AUTHOR MAY NOT BE USED")
|
|
570
|
+
? "BSD-3-Clause"
|
|
571
|
+
: "BSD-2-Clause",
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
const ids = [];
|
|
575
|
+
for (const { id } of found.sort((a, b) => a.at - b.at)) {
|
|
576
|
+
if (!ids.includes(id)) {
|
|
577
|
+
ids.push(id);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (ids.length) {
|
|
581
|
+
return ids;
|
|
582
|
+
}
|
|
583
|
+
const guessed = guessLicenseId(content);
|
|
584
|
+
return guessed ? [guessed] : [];
|
|
585
|
+
}
|
|
586
|
+
|
|
443
587
|
/**
|
|
444
588
|
* Method to guess the spdx license id from license contents
|
|
445
589
|
*
|
|
@@ -195,7 +195,7 @@ export function resolveSaferExecBinary() {
|
|
|
195
195
|
* @param {boolean} [options.denyPersistenceWrites] - Deny writes to auto-execution and persistence locations
|
|
196
196
|
* @param {boolean} [options.privateTmp] - Private tmpfs /tmp and /var/tmp (Linux)
|
|
197
197
|
* @param {string} [options.protectHome] - Home isolation: read-only, tmpfs, or off (Linux)
|
|
198
|
-
* @returns {Promise<{libPaths: string[], httpAccessEntries: Object[], egressEntries?: Object[], dryRunSummary?: Object}>} Collected libraries, HTTP URLs, proxy egress decisions, and the dry-run summary
|
|
198
|
+
* @returns {Promise<{libPaths: string[], httpAccessEntries: Object[], cryptoEntries?: Object[], cryptoComponents?: Object[], cryptoResult?: Object, egressEntries?: Object[], dryRunSummary?: Object}>} Collected libraries, HTTP URLs, crypto observations, proxy egress decisions, and the dry-run summary
|
|
199
199
|
* @throws {Error} When the @cdxgen/safer-exec tracing runtime is not installed
|
|
200
200
|
* (for example after pruning it from a standalone payload), since the traced
|
|
201
201
|
* command could never run and the BOM would silently be empty
|
|
@@ -531,6 +531,9 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
531
531
|
httpAccessEntries: [...collectedUrls, ...urls],
|
|
532
532
|
cryptoEntries: [...collectedCrypto, ...cryptoLogs],
|
|
533
533
|
cryptoComponents,
|
|
534
|
+
// The CRYPTO result from the report channel: the cipher entries carry
|
|
535
|
+
// the certificate chains servers presented (safer-exec ≥ 1.2.0).
|
|
536
|
+
cryptoResult: result?.crypto,
|
|
534
537
|
egressEntries: parseEgressEntries(result.auditLog),
|
|
535
538
|
dryRunSummary,
|
|
536
539
|
};
|
|
@@ -541,6 +544,7 @@ export async function executeAndTrace(commandStr, workingDir, options = {}) {
|
|
|
541
544
|
httpAccessEntries: collectedUrls,
|
|
542
545
|
cryptoEntries: collectedCrypto,
|
|
543
546
|
cryptoComponents,
|
|
547
|
+
cryptoResult: result?.crypto,
|
|
544
548
|
egressEntries: [],
|
|
545
549
|
dryRunSummary,
|
|
546
550
|
};
|
|
@@ -3,6 +3,11 @@ import { join } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { thoughtLog } from "../core/logger.js";
|
|
5
5
|
import { dirNameStr } from "../core/paths.js";
|
|
6
|
+
import {
|
|
7
|
+
CertificateCollector,
|
|
8
|
+
certRecordFromObservedChainEntry,
|
|
9
|
+
toWellFormedText,
|
|
10
|
+
} from "./certificates.js";
|
|
6
11
|
import { applyAlgorithmProperties } from "./cryptoAlgorithmFamily.js";
|
|
7
12
|
|
|
8
13
|
const cryptoOids = JSON.parse(
|
|
@@ -270,6 +275,130 @@ export function normalizeTracedCryptoComponents(cbomComponents) {
|
|
|
270
275
|
return Array.from(byRef.values());
|
|
271
276
|
}
|
|
272
277
|
|
|
278
|
+
// safer-exec walks at most this many certificates of one server's chain
|
|
279
|
+
// (tlsobserve maxChainCerts); a longer array did not come from it, so only the
|
|
280
|
+
// first entries are read.
|
|
281
|
+
const MAX_CHAIN_CERTS = 10;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* The bom-ref normalizeTracedCryptoComponents gives the protocol component of
|
|
285
|
+
* a negotiated TLS version: safer-exec's CBOM names it "TLSv1.2" with the
|
|
286
|
+
* version the prefix leaves ("1.2").
|
|
287
|
+
*
|
|
288
|
+
* @param {string} protocol negotiated protocol name from the crypto result
|
|
289
|
+
* @returns {string} protocol component bom-ref
|
|
290
|
+
*/
|
|
291
|
+
function tracedProtocolBomRef(protocol) {
|
|
292
|
+
const name = String(protocol || "");
|
|
293
|
+
const version = name.startsWith("TLSv") ? name.slice("TLSv".length) : name;
|
|
294
|
+
return `crypto/protocol/${encodeURIComponent(name)}@${encodeURIComponent(version || "unknown")}`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Builds the shared certificate model for the chains servers presented in TLS
|
|
299
|
+
* ≤ 1.2 handshakes, from the `chain` arrays of safer-exec's crypto result
|
|
300
|
+
* (report channel, safer-exec ≥ 1.2.0; earlier releases and TLS 1.3 entries
|
|
301
|
+
* carry no chain and yield nothing). The records go through the shared
|
|
302
|
+
* certificate collector, so bom-refs, properties and dedup match certificates
|
|
303
|
+
* found on disk. A presented chain is untrusted server input: no issuer edges
|
|
304
|
+
* are inferred from it, and the certificate and public-key components link
|
|
305
|
+
* through the collector as every producer's do. Each TLS protocol component
|
|
306
|
+
* provides the certificates and keys presented under it — the edge safer-
|
|
307
|
+
* exec's own CBOM records — which answers which servers presented a key.
|
|
308
|
+
*
|
|
309
|
+
* @param {Object} cryptoResult safer-exec CRYPTO result (`result.crypto`)
|
|
310
|
+
* @param {Set<string>} protocolRefs bom-refs of the protocol components in the BOM;
|
|
311
|
+
* a provides edge is only emitted for a protocol that is actually present
|
|
312
|
+
* @returns {{components: Object[], dependencies: Object[]}} certificate,
|
|
313
|
+
* public-key and algorithm components, and the protocol provides edges
|
|
314
|
+
*/
|
|
315
|
+
export function observedChainComponents(
|
|
316
|
+
cryptoResult,
|
|
317
|
+
protocolRefs = new Set(),
|
|
318
|
+
) {
|
|
319
|
+
const collector = new CertificateCollector();
|
|
320
|
+
// Protocol name -> {certSha256, spkiSha256} pairs in presentation order,
|
|
321
|
+
// leaf first; sorted to refs at the end so the output never depends on the
|
|
322
|
+
// connection order the engine happened to report.
|
|
323
|
+
const chainEntriesByProtocol = new Map();
|
|
324
|
+
// The collector keeps the first record's fields for a certSha256, so two
|
|
325
|
+
// servers presenting one hash with conflicting text would otherwise make
|
|
326
|
+
// the name (and bom-ref) depend on connection order; sort first.
|
|
327
|
+
const ciphers = (
|
|
328
|
+
Array.isArray(cryptoResult?.ciphers) ? cryptoResult.ciphers : []
|
|
329
|
+
)
|
|
330
|
+
.map((cipher) => [JSON.stringify(cipher) ?? "", cipher])
|
|
331
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
|
|
332
|
+
.map(([, cipher]) => cipher);
|
|
333
|
+
for (const cipher of ciphers) {
|
|
334
|
+
const chain = cipher?.chain;
|
|
335
|
+
if (!Array.isArray(chain) || chain.length === 0) {
|
|
336
|
+
// safer-exec ≤ 1.1.2, or a TLS 1.3 handshake (encrypted Certificate
|
|
337
|
+
// message): no chain was observed.
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const host = typeof cipher.host === "string" ? cipher.host : "";
|
|
341
|
+
for (const entry of chain.slice(0, MAX_CHAIN_CERTS)) {
|
|
342
|
+
const record = certRecordFromObservedChainEntry(entry, { host });
|
|
343
|
+
if (record) {
|
|
344
|
+
collector.addRecord(record);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// A protocol name is free text from the report channel; it only reaches a
|
|
348
|
+
// bom-ref through encodeURIComponent, so it must be well-formed.
|
|
349
|
+
const protocol = toWellFormedText(cipher.protocol);
|
|
350
|
+
if (protocol) {
|
|
351
|
+
let entries = chainEntriesByProtocol.get(protocol);
|
|
352
|
+
if (!entries) {
|
|
353
|
+
entries = [];
|
|
354
|
+
chainEntriesByProtocol.set(protocol, entries);
|
|
355
|
+
}
|
|
356
|
+
for (const entry of chain.slice(0, MAX_CHAIN_CERTS)) {
|
|
357
|
+
const certSha256 = String(entry?.certSha256 || "").toLowerCase();
|
|
358
|
+
const spkiSha256 = String(entry?.spkiSha256 || "").toLowerCase();
|
|
359
|
+
if (certSha256.length === 64) {
|
|
360
|
+
entries.push({ certSha256, spkiSha256 });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const { components, dependencies } = collector.build();
|
|
366
|
+
// The collector's components identify their asset by the hash in `version`
|
|
367
|
+
// (certSha256 for certificates, spkiSha256 for public keys).
|
|
368
|
+
const certRefBySha = new Map();
|
|
369
|
+
const keyRefBySpki = new Map();
|
|
370
|
+
for (const component of components) {
|
|
371
|
+
const assetType = component.cryptoProperties?.assetType;
|
|
372
|
+
if (assetType === "certificate") {
|
|
373
|
+
certRefBySha.set(component.version, component["bom-ref"]);
|
|
374
|
+
} else if (assetType === "related-crypto-material") {
|
|
375
|
+
keyRefBySpki.set(component.version, component["bom-ref"]);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const outputDependencies = [...dependencies];
|
|
379
|
+
for (const [protocol, entries] of chainEntriesByProtocol) {
|
|
380
|
+
const protocolRef = tracedProtocolBomRef(protocol);
|
|
381
|
+
if (!protocolRefs.has(protocolRef)) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const provides = [
|
|
385
|
+
...new Set(
|
|
386
|
+
entries.flatMap(({ certSha256, spkiSha256 }) => [
|
|
387
|
+
certRefBySha.get(certSha256),
|
|
388
|
+
keyRefBySpki.get(spkiSha256),
|
|
389
|
+
]),
|
|
390
|
+
),
|
|
391
|
+
]
|
|
392
|
+
.filter((ref) => ref)
|
|
393
|
+
.sort();
|
|
394
|
+
if (provides.length) {
|
|
395
|
+
outputDependencies.push({ ref: protocolRef, provides });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
outputDependencies.sort((left, right) => left.ref.localeCompare(right.ref));
|
|
399
|
+
return { components, dependencies: outputDependencies };
|
|
400
|
+
}
|
|
401
|
+
|
|
273
402
|
/**
|
|
274
403
|
* Maps the crypto libraries safer-exec reported to the paths they were loaded
|
|
275
404
|
* from.
|