new file mode 100644
@@ -0,0 +1,197 @@
+################################################################################
+#
+# This file contains various utility functions used to create a SBOM
+# in the JSON CycloneDX format.
+#
+# https://cyclonedx.org/docs/1.5/json/
+#
+################################################################################
+
+# Note: to avoid conflict with <pkg>_VERSION `_SPEC` is added
+CYCLONEDX_VERSION_SPEC = 1.5
+
+#
+# Licenses list helper functions
+# Since licenses in buildroot are comma separated list and the 'make' language
+# uses spaces to create list we need to replace the spaces of licenses name
+# by a character not used in any licenses name.
+#
+# _cyclonedx-licenses-as-list: create a list from the url-encoded comma
+# separeted license list.
+#
+# $(1): an url-encoded comma separeted list
+#
+# Turns "Public%20Domain,%20GPL-2.0" into "Public%20Domain GPL-2.0"
+_cyclonedx-licenses-as-list = $(subst $(comma)%20,$(space),$(1))
+
+# _cyclonedx-license -- create an entry of a cyclonedx component license list
+#
+# For more information on license object see
+# https://cyclonedx.org/docs/1.5/json/#components_items_licenses_oneOf_i0_items_license
+#
+# $(1): a single url-encoded license name
+define _cyclonedx-license
+ {
+ "license": {
+ "name": $(call mk-json-str,$(1))
+ }
+ },
+endef
+
+# _cyclonedx-licenses -- create a licenses list formatted for a CycloneDX
+# component
+#
+# $(1): a comma separated license list
+#
+# KNOWN ISSUE: Licenses name that include a parenthesis with comma inside,
+# will result be misinterpreted as multiple licenses name:
+# - host-util-linux: LGPL-2.1+ (libblkid, libfdisk, libmount)
+define _cyclonedx-licenses
+ $(foreach license,$(call _cyclonedx-licenses-as-list,$(call urlencode,$(1))),
+ $(call urldecode,$(call _cyclonedx-license,$(license)))
+ )
+endef
+
+# Note about patch list: this patch list might not be complete.
+# There is no variable yet that stores the patch list without applying them.
+_cyclonedx-patches-list = $(foreach patchdir,\
+ $(addsuffix /$($(1)_PKGDIR),$(CURDIR)) $(addsuffix /$($(1)_RAWNAME),$(call qstrip,$(BR2_GLOBAL_PATCH_DIR))),\
+ $(wildcard $(addsuffix /*.patch,$(patchdir)))\
+)
+
+# _cyclonedx-patch -- single entry of a patch list.
+# It's required to pass the type of the patch it can be
+# either: unofficial, monkey, backport or cherry-pick.
+# Since there is no information available about each
+# patches, we mark them as "unofficial".
+#
+# $(1): single patch path
+define _cyclonedx-patch
+ {
+ "type": "unofficial",
+ "diff": {
+ "text": {
+ "content": $(call mk-json-str,$(file < $(1)))
+ }
+ }
+ },
+endef
+
+# _cyclonedx-patches -- patch list are stored under the pedigree entry used to
+# document how a component is modified.
+#
+# $(1): patch path list
+define _cyclonedx-patches
+ $(intcmp $(words $(1)),0,,,
+ "pedigree": {\
+ "patches": [\
+ $(foreach patch,$(1),\
+ $(call _cyclonedx-patch,$(patch))\
+ )\
+ ]\
+ }$(comma)\
+ )
+endef
+
+# _cyclonedx-component -- representation of a package for the CycloneDX format
+# - bom-ref: is a unique identifier used to refer to this component in the
+# 'dependencies' section.
+# - type: is a required property since we don't have enough information about
+# the package from its definition CycloneDX spec recommend setting it
+# to 'library'.
+# - properties: is used to add additional information that doesn't fit the
+# current CycloneDX specification.
+# - BR_TYPE: {host|target}
+#
+# $(1): upper-case package name
+#
+# KNWON ISSUE: packages with a custom tarball (linux,uboot,...) will have the
+# 'version' property set to 'custom'.
+define _cyclonedx-component
+ {
+ "bom-ref": $(call mk-json-str,$($(1)_NAME)),
+ "name": $(call mk-json-str,$(if $($(1)_RAWNAME),$($(1)_RAWNAME),$($(1)_NAME))),
+ "type": "library",
+ $(if $($(1)_SOURCE),
+ "version": $(call mk-json-str,$($(1)_DL_VERSION))$(comma)
+ "licenses": [
+ $(call _cyclonedx-licenses,$($(1)_LICENSE)) \
+ ]$(comma)
+ )
+ $(if $($(1)_PURL), \
+ "purl": $(call mk-json-str,$($(1)_PURL))$(comma) \
+ )
+ $(if $($(1)_CPE_ID_VALID), \
+ "cpe": $(call mk-json-str,$($(1)_CPE_ID))$(comma) \
+ )
+ $(call _cyclonedx-patches,$(call _cyclonedx-patches-list,$(1)))
+ "properties": [{
+ "name": "BR_TYPE",
+ "value": $(call mk-json-str,$($(1)_TYPE))
+ }],
+ },
+endef
+
+# _cyclonedx-dependency -- create dependency relationships between components.
+# - ref: reference to a component bom-ref.
+# - dependsOn: array of component bom-ref identifier to create the dependencies.
+#
+# $(1): upper-case package name
+define _cyclonedx-dependency
+ $(if $($(1)_FINAL_RECURSIVE_DEPENDENCIES),
+ {
+ "ref": $(call mk-json-str,$($(1)_NAME))$(comma)
+ "dependsOn": [
+ $(call make-comma-list,$(foreach p,\
+ $($(1)_FINAL_RECURSIVE_DEPENDENCIES), \
+ $(call mk-json-str,$(p))\
+ ))
+ ]
+ }$(comma)
+ )
+endef
+
+# cyclonedx-json -- return a CycloneDX SBOM formatted as a JSON dictionnary.
+# - bomFormat: required field is always "CycloneDX"
+# - specVersion: required field with CycloneDX spec version
+# - version: is used by software that accept CycloneDX SBOM to differentiate
+# the different SBOM. The bigger the number the newer the SBOM is.
+# Here it's set to '1' and it should be incremented when the resulting
+# SBOM is edited later.
+# The CycloneDX spec mentions that an 'uuid' property can also be used to
+# differentiate SBOM but is not included because there is no native command
+# to generate an uuid in buildroot.
+#
+# $(1): packages list
+define cyclonedx-json
+ $(call clean-json,{
+ "bomFormat": "CycloneDX"$(comma)
+ "$$schema": "http://cyclonedx.org/schema/bom-$(CYCLONEDX_VERSION_SPEC).schema.json"$(comma)
+ "specVersion": $(call mk-json-str,$(CYCLONEDX_VERSION_SPEC))$(comma)
+ "version": 1$(comma)
+ "components": [ \
+ $(foreach p,$(1), \
+ $(call _cyclonedx-component,$(call UPPERCASE,$(p))) \
+ ) \
+ ]$(comma)
+ "dependencies": [
+ {
+ "ref": "buildroot"$(comma)\
+ "dependsOn": [$(call make-comma-list,\
+ $(foreach p,$(1),$(call mk-json-str,$(p)))\
+ )]\
+ }$(comma)
+ $(foreach p,$(1),\
+ $(call _cyclonedx-dependency,$(call UPPERCASE,$(p)),$(2)) \
+ ) \
+ ]$(comma)
+ "metadata": {
+ "component": {
+ "bom-ref": "buildroot"$(comma)
+ "name": "buildroot"$(comma)
+ "type": "firmware"$(comma)
+ "version": $(call mk-json-str,$(BR2_VERSION_FULL))
+ }
+ }
+ })
+endef
CycloneDX is a software bill of materials (SBOM) specification. There is a growing need to generate SBOM from buildroot configurations. Right now there are different solutions available for buildroot users `show-info`, `legal-info` and `pkg-stats`. They all generate similar information (`show-info` showing more) but in a format that is specific to buildroot. This is the reason this patch introduces a new SBOM output type for buildroot: CycloneDX. CycloneDX is a format already supported by tools such as https://dependencytrack.org/ that helps track softwares, vulnerabilities, etc ... To match the functionality of `show-info`, buildroot internal packages will also be present in the SBOM with a reduced set of property. Internal packages are defined as packages without `<pkg>_SOURCE` defined. In a future patch more properties can be added to cover the functionality of `show-info`, `legal-info` and `pkg-stats`. The CycloneDX SBOM output as a stripped JSON formatted line as there are already macros available to work with JSON in buildroot. For more information, see https://cyclonedx.org/ and https://cyclonedx.org/docs/1.5/json/ Signed-off-by: Thomas Perale <thomas.perale@mind.be> --- support/misc/cyclonedx.mk | 197 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 support/misc/cyclonedx.mk -- 2.44.0