diff --git a/meta/classes/create-spdx.bbclass b/meta/classes/create-spdx.bbclass index 349ecfe6abc4d36ddecf8754c19f8b43d5ee7960..4b4eba833401c19900f8d0b4eb489f2bb4010a2b 100644 --- a/meta/classes/create-spdx.bbclass +++ b/meta/classes/create-spdx.bbclass @@ -12,6 +12,8 @@ CVE_VERSION ??= "${PV}" SPDXDIR ??= "${WORKDIR}/spdx" SPDXDEPLOY = "${SPDXDIR}/deploy" SPDXWORK = "${SPDXDIR}/work" +SPDXIMAGEWORK = "${SPDXDIR}/image-work" +SPDXSDKWORK = "${SPDXDIR}/sdk-work" SPDX_TOOL_NAME ??= "oe-spdx-creator" SPDX_TOOL_VERSION ??= "1.0" @@ -28,6 +30,8 @@ SPDX_PRETTY ??= "0" SPDX_LICENSES ??= "${COREBASE}/meta/files/spdx-licenses.json" +SPDX_CUSTOM_ANNOTATION_VARS ??= "" + SPDX_ORG ??= "OpenEmbedded ()" SPDX_SUPPLIER ??= "Organization: ${SPDX_ORG}" SPDX_SUPPLIER[doc] = "The SPDX PackageSupplier field for SPDX packages created from \ @@ -375,7 +379,7 @@ def collect_dep_recipes(d, doc, spdx_recipe): return dep_recipes collect_dep_recipes[vardepsexclude] += "BB_TASKDEPDATA" - +collect_dep_recipes[vardeps] += "DEPENDS" def collect_dep_sources(d, dep_recipes): import oe.sbom @@ -401,6 +405,54 @@ def collect_dep_sources(d, dep_recipes): return sources +def add_download_packages(d, doc, recipe): + import os.path + from bb.fetch2 import decodeurl, CHECKSUM_LIST + import bb.process + import oe.spdx + import oe.sbom + + for download_idx, src_uri in enumerate(d.getVar('SRC_URI').split()): + f = bb.fetch2.FetchData(src_uri, d) + + for name in f.names: + package = oe.spdx.SPDXPackage() + package.name = "%s-source-%d" % (d.getVar("PN"), download_idx + 1) + package.SPDXID = oe.sbom.get_download_spdxid(d, download_idx + 1) + + if f.type == "file": + continue + + uri = f.type + proto = getattr(f, "proto", None) + if proto is not None: + uri = uri + "+" + proto + uri = uri + "://" + f.host + f.path + + if f.method.supports_srcrev(): + uri = uri + "@" + f.revisions[name] + + if f.method.supports_checksum(f): + for checksum_id in CHECKSUM_LIST: + if checksum_id.upper() not in oe.spdx.SPDXPackage.ALLOWED_CHECKSUMS: + continue + + expected_checksum = getattr(f, "%s_expected" % checksum_id) + if expected_checksum is None: + continue + + c = oe.spdx.SPDXChecksum() + c.algorithm = checksum_id.upper() + c.checksumValue = expected_checksum + package.checksums.append(c) + + package.downloadLocation = uri + doc.packages.append(package) + doc.add_relationship(doc, "DESCRIBES", package) + # In the future, we might be able to do more fancy dependencies, + # but this should be sufficient for now + doc.add_relationship(package, "BUILD_DEPENDENCY_OF", recipe) + python do_create_spdx() { from datetime import datetime, timezone import oe.sbom @@ -453,14 +505,6 @@ python do_create_spdx() { if bb.data.inherits_class("native", d) or bb.data.inherits_class("cross", d): recipe.annotations.append(create_annotation(d, "isNative")) - for s in d.getVar('SRC_URI').split(): - if not s.startswith("file://"): - s = s.split(';')[0] - recipe.downloadLocation = s - break - else: - recipe.downloadLocation = "NOASSERTION" - homepage = d.getVar("HOMEPAGE") if homepage: recipe.homepage = homepage @@ -477,6 +521,10 @@ python do_create_spdx() { if description: recipe.description = description + if d.getVar("SPDX_CUSTOM_ANNOTATION_VARS"): + for var in d.getVar('SPDX_CUSTOM_ANNOTATION_VARS').split(): + recipe.annotations.append(create_annotation(d, var + "=" + d.getVar(var))) + # Some CVEs may be patched during the build process without incrementing the version number, # so querying for CVEs based on the CPE id can lead to false positives. To account for this, # save the CVEs fixed by patches to source information field in the SPDX. @@ -498,6 +546,8 @@ python do_create_spdx() { doc.packages.append(recipe) doc.add_relationship(doc, "DESCRIBES", recipe) + add_download_packages(d, doc, recipe) + if process_sources(d) and include_sources: recipe_archive = deploy_dir_spdx / "recipes" / (doc.name + ".tar.zst") with optional_tarfile(recipe_archive, archive_sources) as archive: @@ -819,10 +869,12 @@ def spdx_get_src(d): d.setVar("WORKDIR", workdir) do_rootfs[recrdeptask] += "do_create_spdx do_create_runtime_spdx" +do_rootfs[cleandirs] += "${SPDXIMAGEWORK}" ROOTFS_POSTUNINSTALL_COMMAND =+ "image_combine_spdx ; " do_populate_sdk[recrdeptask] += "do_create_spdx do_create_runtime_spdx" +do_populate_sdk[cleandirs] += "${SPDXSDKWORK}" POPULATE_SDK_POST_HOST_COMMAND:append:task-populate-sdk = " sdk_host_combine_spdx; " POPULATE_SDK_POST_TARGET_COMMAND:append:task-populate-sdk = " sdk_target_combine_spdx; " @@ -838,7 +890,7 @@ python image_combine_spdx() { img_spdxid = oe.sbom.get_image_spdxid(image_name) packages = image_list_installed_packages(d) - combine_spdx(d, image_name, imgdeploydir, img_spdxid, packages) + combine_spdx(d, image_name, imgdeploydir, img_spdxid, packages, Path(d.getVar("SPDXIMAGEWORK"))) def make_image_link(target_path, suffix): if image_link_name: @@ -846,12 +898,8 @@ python image_combine_spdx() { if link != target_path: link.symlink_to(os.path.relpath(target_path, link.parent)) - image_spdx_path = imgdeploydir / (image_name + ".spdx.json") - make_image_link(image_spdx_path, ".spdx.json") spdx_tar_path = imgdeploydir / (image_name + ".spdx.tar.zst") make_image_link(spdx_tar_path, ".spdx.tar.zst") - spdx_index_path = imgdeploydir / (image_name + ".spdx.index.json") - make_image_link(spdx_index_path, ".spdx.index.json") } python sdk_host_combine_spdx() { @@ -871,9 +919,9 @@ def sdk_combine_spdx(d, sdk_type): sdk_deploydir = Path(d.getVar("SDKDEPLOYDIR")) sdk_spdxid = oe.sbom.get_sdk_spdxid(sdk_name) sdk_packages = sdk_list_installed_packages(d, sdk_type == "target") - combine_spdx(d, sdk_name, sdk_deploydir, sdk_spdxid, sdk_packages) + combine_spdx(d, sdk_name, sdk_deploydir, sdk_spdxid, sdk_packages, Path(d.getVar('SPDXSDKWORK'))) -def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages): +def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages, spdx_workdir): import os import oe.spdx import oe.sbom @@ -942,7 +990,7 @@ def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages): comment="Runtime dependencies for %s" % name ) - image_spdx_path = rootfs_deploydir / (rootfs_name + ".spdx.json") + image_spdx_path = spdx_workdir / (rootfs_name + ".spdx.json") with image_spdx_path.open("wb") as f: doc.to_json(f, sort_keys=True, indent=get_json_indent(d)) @@ -1018,7 +1066,3 @@ def combine_spdx(d, rootfs_name, rootfs_deploydir, rootfs_spdxid, packages): info.gname = "root" tar.addfile(info, fileobj=index_str) - - spdx_index_path = rootfs_deploydir / (rootfs_name + ".spdx.index.json") - with spdx_index_path.open("w") as f: - json.dump(index, f, sort_keys=True, indent=get_json_indent(d)) diff --git a/meta/lib/oe/sbom.py b/meta/lib/oe/sbom.py index 52bf51440e9a4e1101d971253c8242affaf0948b..e1c132bc908988138d54ae93719f9561a63bdd39 100644 --- a/meta/lib/oe/sbom.py +++ b/meta/lib/oe/sbom.py @@ -12,6 +12,10 @@ def get_recipe_spdxid(d): return "SPDXRef-%s-%s" % ("Recipe", d.getVar("PN")) +def get_download_spdxid(d, idx): + return "SPDXRef-Download-%s-%d" % (d.getVar("PN"), idx) + + def get_package_spdxid(pkg): return "SPDXRef-Package-%s" % pkg diff --git a/meta/lib/oe/spdx.py b/meta/lib/oe/spdx.py index 6d56ed90df29b3926cf9c97d105be6bfa78aef0f..29d1db235c51f835679b53b6d3c4bc3796e9caa0 100644 --- a/meta/lib/oe/spdx.py +++ b/meta/lib/oe/spdx.py @@ -214,6 +214,18 @@ class SPDXPackageVerificationCode(SPDXObject): class SPDXPackage(SPDXObject): + ALLOWED_CHECKSUMS = [ + "SHA1", + "SHA224", + "SHA256", + "SHA384", + "SHA512", + "MD2", + "MD4", + "MD5", + "MD6", + ] + name = _String() SPDXID = _String() versionInfo = _String() @@ -232,6 +244,7 @@ class SPDXPackage(SPDXObject): hasFiles = _StringList() packageFileName = _String() annotations = _ObjectList(SPDXAnnotation) + checksums = _ObjectList(SPDXChecksum) class SPDXFile(SPDXObject):