feat: SBOM generation and OmniBOR build provenance (CRA compliance)#10343
feat: SBOM generation and OmniBOR build provenance (CRA compliance)#10343MarkAtwood wants to merge 1 commit intowolfSSL:masterfrom
Conversation
Adds two complementary supply chain transparency targets to the wolfSSL autotools build, and documentation covering both as a unified whole. ## make sbom Generates a Software Bill of Materials for EU Cyber Resilience Act (CRA) compliance. Produces three files in the build directory: wolfssl-<version>.cdx.json CycloneDX 1.6 JSON wolfssl-<version>.spdx.json SPDX 2.3 JSON wolfssl-<version>.spdx SPDX 2.3 tag-value (validated) The SPDX JSON is validated by pyspdxtools before the tag-value file is written; make sbom fails if validation fails. SBOM contents: package name/version, supplier, license (parsed from LICENSING at generation time, not hardcoded), copyright, SHA-256 of the installed library, CPE, PURL, download location, and build configuration defines as a comment. Third-party dependencies (liboqs, libz, libxmss, liblms) are included when enabled. Implementation: scripts/gen-sbom (Python 3, stdlib only) stages a make install into a temporary directory, hashes the installed library, generates both SBOM formats, then removes the staging directory. configure.ac detects python3, pyspdxtools, and git via AC_PATH_PROG. install-sbom / uninstall-sbom targets install the three files to $(datadir)/doc/wolfssl/. make clean removes all generated files. ## make bomsh Generates an OmniBOR artifact dependency graph using the Bomsh project (https://github.com/omnibor/bomsh), providing cryptographic traceability from every built binary back to the exact set of source files that produced it. Runs a full clean rebuild under bomtrace3 (a patched strace, userspace only — no kernel modifications required). bomtrace3 intercepts every compiler execve() syscall and records inputs and outputs; it cannot post-process an already-built tree, hence the clean rebuild. bomsh_create_bom.py processes the raw logfile to produce the OmniBOR artifact objects and metadata in omnibor/. If bomsh_sbom.py is available and wolfssl-<version>.spdx.json exists (from make sbom), annotates that SPDX document with a PERSISTENT-ID gitoid ExternalRef, producing omnibor.wolfssl-<version>.spdx.json. This enriched SPDX bridges component identity and build provenance in a single document. configure.ac detects bomtrace3, bomsh_create_bom.py, and bomsh_sbom.py via AC_PATH_PROG. The raw logfile and conf file are written to the build directory (not /tmp/) to avoid concurrent-build collisions, and removed by make clean. install-bomsh / uninstall-bomsh targets install omnibor/ and the enriched SPDX to $(datadir)/doc/wolfssl/. ## Documentation doc/SBOM.md: unified reference covering both make sbom and make bomsh as parts of a single supply chain transparency story — component identity (what) and build provenance (how) — with a combined workflow section and full output file reference. doc/CRA.md: product-integrator guide covering how to incorporate wolfSSL's SBOM artefacts into a downstream product SBOM (SPDX ExternalDocumentRef and CycloneDX component reference patterns), commercial license concluded-field guidance, OmniBOR gitoid meaning, auditor handoff checklist, and links to OpenSSF CRA and SBOM Everywhere SIG guidance pages. INSTALL: sections 21 (make sbom) and 22 (make bomsh). README.md: brief SBOM/CRA and OmniBOR/Bomsh sections. doc/include.am: SBOM.md and CRA.md added to dist_doc_DATA.
There was a problem hiding this comment.
Pull request overview
Adds build-integrated supply-chain transparency outputs to the Autotools build: SBOM generation (make sbom) and OmniBOR/Bomsh build provenance tracing (make bomsh) to support CRA-style compliance workflows.
Changes:
- Add
scripts/gen-sbomPython generator for CycloneDX 1.6 + SPDX 2.3 JSON SBOM outputs (plus tag-value conversion viapyspdxtools). - Wire new
sbom,bomsh, and install/uninstall targets into Autotools (configure.ac,Makefile.am), with tool discovery viaAC_PATH_PROG. - Add end-user documentation (
doc/SBOM.md,doc/CRA.md) and reference it fromREADME.md/INSTALL.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
scripts/gen-sbom |
New Python SBOM generator producing CycloneDX/SPDX JSON with optional dependency metadata. |
Makefile.am |
Adds sbom/bomsh targets, install/uninstall rules, and clean integration. |
configure.ac |
Detects required tools (python3, pyspdxtools, git, bomtrace3, bomsh scripts) and exports needed vars. |
doc/include.am |
Ships new docs in the dist tarball; cleans generated OmniBOR directory. |
doc/SBOM.md |
Primary documentation for SBOM + OmniBOR workflows and outputs. |
doc/CRA.md |
Downstream integrator guidance for CRA-oriented SBOM/provenance usage. |
README.md |
Adds brief pointers to the new SBOM/CRA and OmniBOR features. |
INSTALL |
Adds new sections documenting make sbom / make bomsh usage and prerequisites. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| tree root (passed via `configure` as `XMSS_ROOT` / `LIBLMS_ROOT`). If the | ||
| source tree has no tags, `git describe` returns the short commit hash, which | ||
| is recorded as-is. If the source tree is unavailable or `git` is not found, | ||
| version is recorded as `NOASSERTION`. |
There was a problem hiding this comment.
This section says that when git is not found or the source tree is unavailable, the dependency version “is recorded as NOASSERTION”. In the current generator, SPDX uses NOASSERTION, but CycloneDX omits the version and purl fields entirely (and prints a warning). Please update the wording to reflect the CycloneDX behavior, or change the generator to emit a consistent placeholder.
| version is recorded as `NOASSERTION`. | |
| SPDX records the version as `NOASSERTION`; CycloneDX omits the `version` and | |
| `purl` fields and emits a warning. |
| $(MKDIR_P) $(DESTDIR)$(bomshdir) | ||
| cp -r '$(BOMSH_OMNIBORDIR)' '$(DESTDIR)$(bomshdir)/omnibor' |
There was a problem hiding this comment.
install-bomsh uses cp -r to install the omnibor/ tree. This bypasses standard install tooling (permissions, verbosity, portability, $(INSTALL) overrides) and can behave differently across platforms. Consider using an Automake-friendly install approach (e.g., create the dir then copy files with $(INSTALL_DATA) via find, or use tar piping) so packaging/install behaves consistently.
| $(MKDIR_P) $(DESTDIR)$(bomshdir) | |
| cp -r '$(BOMSH_OMNIBORDIR)' '$(DESTDIR)$(bomshdir)/omnibor' | |
| $(MKDIR_P) '$(DESTDIR)$(bomshdir)' | |
| $(MKDIR_P) '$(DESTDIR)$(bomshdir)/omnibor' | |
| @if test -d '$(BOMSH_OMNIBORDIR)'; then \ | |
| cd '$(BOMSH_OMNIBORDIR)' && tar cf - . | \ | |
| (cd '$(DESTDIR)$(bomshdir)/omnibor' && tar xpf -); \ | |
| fi |
| $(MAKE) clean | ||
| @printf 'raw_logfile=%s\n' '$(BOMSH_RAWLOG_BASE)' > '$(BOMSH_CONF)' | ||
| $(BOMTRACE3) -c '$(BOMSH_CONF)' $(MAKE) | ||
| $(BOMSH_CREATE_BOM) -r '$(BOMSH_RAWLOG)' -b '$(BOMSH_OMNIBORDIR)' | ||
| @if test -n "$(BOMSH_SBOM)" && test -f '$(abs_builddir)/wolfssl-$(PACKAGE_VERSION).spdx.json'; then \ | ||
| echo "Enriching SPDX with OmniBOR ExternalRefs..."; \ |
There was a problem hiding this comment.
make bomsh starts with $(MAKE) clean), but make clean removes the SBOM outputs (they’re in CLEANFILES). That means even if the user runs make sbom first, the subsequent test -f wolfssl-$(PACKAGE_VERSION).spdx.json will be false and the SPDX enrichment step won’t run. Consider preserving/regenerating the SPDX after the traced rebuild (e.g., run the SBOM generator after the bomtrace3 rebuild, or copy the SPDX aside before clean and restore it).
| $(PYTHON3) $(srcdir)/scripts/gen-sbom \ | ||
| --name $(PACKAGE) \ | ||
| --version $(PACKAGE_VERSION) \ | ||
| --license-file $(srcdir)/LICENSING \ | ||
| --options-h $(abs_builddir)/wolfssl/options.h \ | ||
| --lib $(abs_builddir)/_sbom_staging$(libdir)/libwolfssl.so.$(WOLFSSL_LIBRARY_VERSION_FIRST).$(WOLFSSL_LIBRARY_VERSION_SECOND).$(WOLFSSL_LIBRARY_VERSION_THIRD) \ |
There was a problem hiding this comment.
The SBOM hash input path is hard-coded to libwolfssl.so.<first>.<second>.<third>. This will fail on configurations that disable shared libraries (e.g., --disable-shared, and also when --with-libxmss / --with-liblms forces enable_shared=no in configure.ac). Please detect the installed library artifact dynamically (shared vs static, platform suffixes) or fall back to hashing the static archive when the .so is not present.
| $(PYTHON3) $(srcdir)/scripts/gen-sbom \ | |
| --name $(PACKAGE) \ | |
| --version $(PACKAGE_VERSION) \ | |
| --license-file $(srcdir)/LICENSING \ | |
| --options-h $(abs_builddir)/wolfssl/options.h \ | |
| --lib $(abs_builddir)/_sbom_staging$(libdir)/libwolfssl.so.$(WOLFSSL_LIBRARY_VERSION_FIRST).$(WOLFSSL_LIBRARY_VERSION_SECOND).$(WOLFSSL_LIBRARY_VERSION_THIRD) \ | |
| @sbom_lib=""; \ | |
| for lib in \ | |
| "$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.so.* \ | |
| "$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.so \ | |
| "$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.dylib \ | |
| "$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.*.dylib \ | |
| "$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.dll \ | |
| "$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.dll.a \ | |
| "$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.lib \ | |
| "$(abs_builddir)/_sbom_staging$(libdir)"/wolfssl.lib \ | |
| "$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.a; do \ | |
| if test -f "$$lib"; then \ | |
| sbom_lib="$$lib"; \ | |
| break; \ | |
| fi; \ | |
| done; \ | |
| if test -z "$$sbom_lib"; then \ | |
| echo ""; \ | |
| echo "ERROR: Unable to locate installed wolfSSL library artifact for SBOM generation."; \ | |
| echo " Searched in $(abs_builddir)/_sbom_staging$(libdir) for shared and static library outputs."; \ | |
| echo ""; \ | |
| rm -rf $(abs_builddir)/_sbom_staging; \ | |
| exit 1; \ | |
| fi; \ | |
| $(PYTHON3) $(srcdir)/scripts/gen-sbom \ | |
| --name $(PACKAGE) \ | |
| --version $(PACKAGE_VERSION) \ | |
| --license-file $(srcdir)/LICENSING \ | |
| --options-h $(abs_builddir)/wolfssl/options.h \ | |
| --lib "$$sbom_lib" \ |
| echo "Enriching SPDX with OmniBOR ExternalRefs..."; \ | ||
| $(BOMSH_SBOM) \ | ||
| -b '$(BOMSH_OMNIBORDIR)' \ | ||
| -i '$(abs_builddir)/wolfssl-$(PACKAGE_VERSION).spdx.json' \ | ||
| -f '$(abs_builddir)/src/.libs/libwolfssl.so.$(WOLFSSL_LIBRARY_VERSION_FIRST).$(WOLFSSL_LIBRARY_VERSION_SECOND).$(WOLFSSL_LIBRARY_VERSION_THIRD)' \ | ||
| -s spdx-json \ | ||
| -O '$(abs_builddir)'; \ |
There was a problem hiding this comment.
The Bomsh SPDX enrichment uses a hard-coded shared-library path (src/.libs/libwolfssl.so.<...>). This will fail on static-only builds and on non-ELF platforms (e.g., macOS .dylib). Consider resolving the built artifact path in a platform/config-aware way (or passing the installed artifact from the staging area, similar to make sbom).
| echo "Enriching SPDX with OmniBOR ExternalRefs..."; \ | |
| $(BOMSH_SBOM) \ | |
| -b '$(BOMSH_OMNIBORDIR)' \ | |
| -i '$(abs_builddir)/wolfssl-$(PACKAGE_VERSION).spdx.json' \ | |
| -f '$(abs_builddir)/src/.libs/libwolfssl.so.$(WOLFSSL_LIBRARY_VERSION_FIRST).$(WOLFSSL_LIBRARY_VERSION_SECOND).$(WOLFSSL_LIBRARY_VERSION_THIRD)' \ | |
| -s spdx-json \ | |
| -O '$(abs_builddir)'; \ | |
| bomsh_artifact=""; \ | |
| for candidate in \ | |
| $(abs_builddir)/src/.libs/libwolfssl.so \ | |
| $(abs_builddir)/src/.libs/libwolfssl.so.* \ | |
| $(abs_builddir)/src/.libs/libwolfssl.dylib \ | |
| $(abs_builddir)/src/.libs/libwolfssl.*.dylib \ | |
| $(abs_builddir)/src/.libs/libwolfssl.a \ | |
| $(abs_builddir)/src/libwolfssl.a; do \ | |
| if test -f "$$candidate"; then \ | |
| bomsh_artifact="$$candidate"; \ | |
| break; \ | |
| fi; \ | |
| done; \ | |
| if test -n "$$bomsh_artifact"; then \ | |
| echo "Enriching SPDX with OmniBOR ExternalRefs..."; \ | |
| $(BOMSH_SBOM) \ | |
| -b '$(BOMSH_OMNIBORDIR)' \ | |
| -i '$(abs_builddir)/wolfssl-$(PACKAGE_VERSION).spdx.json' \ | |
| -f "$$bomsh_artifact" \ | |
| -s spdx-json \ | |
| -O '$(abs_builddir)'; \ | |
| else \ | |
| echo "NOTE: unable to locate a built libwolfssl artifact for SPDX enrichment."; \ | |
| fi; \ |
| $(PYTHON3) $(srcdir)/scripts/gen-sbom \ | ||
| --name $(PACKAGE) \ | ||
| --version $(PACKAGE_VERSION) \ | ||
| --license-file $(srcdir)/LICENSING \ | ||
| --options-h $(abs_builddir)/wolfssl/options.h \ | ||
| --lib $(abs_builddir)/_sbom_staging$(libdir)/libwolfssl.so.$(WOLFSSL_LIBRARY_VERSION_FIRST).$(WOLFSSL_LIBRARY_VERSION_SECOND).$(WOLFSSL_LIBRARY_VERSION_THIRD) \ | ||
| --dep-liboqs $(ENABLED_LIBOQS) \ | ||
| --dep-libxmss $(ENABLED_LIBXMSS) \ | ||
| --dep-libxmss-root '$(XMSS_ROOT)' \ | ||
| --dep-liblms $(ENABLED_LIBLMS) \ | ||
| --dep-liblms-root '$(LIBLMS_ROOT)' \ | ||
| --dep-libz $(ENABLED_LIBZ) \ | ||
| --git '$(GIT)' \ | ||
| --cdx-out $(abs_builddir)/$(SBOM_CDX) \ | ||
| --spdx-out $(abs_builddir)/$(SBOM_SPDX) |
There was a problem hiding this comment.
scripts/gen-sbom is invoked from make sbom, but it is not currently listed in scripts/include.am’s EXTRA_DIST/dist_noinst_SCRIPTS. As a result, make dist tarballs are likely to omit the generator and make sbom will fail for users building from release archives. Please add scripts/gen-sbom to the distribution list.
| rm -rf $(abs_builddir)/_sbom_staging | ||
| $(MAKE) install DESTDIR=$(abs_builddir)/_sbom_staging | ||
| $(PYTHON3) $(srcdir)/scripts/gen-sbom \ | ||
| --name $(PACKAGE) \ | ||
| --version $(PACKAGE_VERSION) \ | ||
| --license-file $(srcdir)/LICENSING \ | ||
| --options-h $(abs_builddir)/wolfssl/options.h \ | ||
| --lib $(abs_builddir)/_sbom_staging$(libdir)/libwolfssl.so.$(WOLFSSL_LIBRARY_VERSION_FIRST).$(WOLFSSL_LIBRARY_VERSION_SECOND).$(WOLFSSL_LIBRARY_VERSION_THIRD) \ | ||
| --dep-liboqs $(ENABLED_LIBOQS) \ | ||
| --dep-libxmss $(ENABLED_LIBXMSS) \ | ||
| --dep-libxmss-root '$(XMSS_ROOT)' \ | ||
| --dep-liblms $(ENABLED_LIBLMS) \ | ||
| --dep-liblms-root '$(LIBLMS_ROOT)' \ | ||
| --dep-libz $(ENABLED_LIBZ) \ | ||
| --git '$(GIT)' \ | ||
| --cdx-out $(abs_builddir)/$(SBOM_CDX) \ | ||
| --spdx-out $(abs_builddir)/$(SBOM_SPDX) | ||
| rm -rf $(abs_builddir)/_sbom_staging | ||
| $(PYSPDXTOOLS) --infile $(abs_builddir)/$(SBOM_SPDX) \ |
There was a problem hiding this comment.
If scripts/gen-sbom fails, the _sbom_staging directory is not cleaned up because rm -rf only runs after the generator completes successfully. Consider ensuring staging cleanup happens even on failure (e.g., trap/cleanup handler or chaining cleanup with ||), to avoid leaving large staged installs behind.
| 'creationInfo': { | ||
| 'licenseListVersion': '3.28', | ||
| 'creators': [ | ||
| f'Organization: {supplier}', | ||
| 'Tool: wolfssl-sbom-gen-1.0' | ||
| ], | ||
| 'created': timestamp, | ||
| }, |
There was a problem hiding this comment.
licenseListVersion is hard-coded to 3.28. If a user’s pyspdxtools/license-list data is older, this may cause SPDX validation to fail even when the document is otherwise correct. Consider omitting licenseListVersion (it’s optional in SPDX 2.3) or deriving it from the tool/runtime to keep make sbom robust across environments.
|
Note that we are in the process of removing liblms and libxmss (see #10292), which will simplify the dependency analysis in the python script. |
Summary
Adds two complementary supply chain transparency targets to the wolfSSL autotools build:
make sbom— generates a Software Bill of Materials (SPDX 2.3 + CycloneDX 1.6) for EU Cyber Resilience Act (CRA) compliancemake bomsh— generates an OmniBOR artifact dependency graph (cryptographic source-to-binary traceability) using the Bomsh projectmake bomshenriches the SPDX document with aPERSISTENT-ID gitoidExternalRef, bridging component identity and build provenance in a single filemake sbom
Produces three files:
wolfssl-<version>.cdx.jsonwolfssl-<version>.spdx.jsonwolfssl-<version>.spdxSBOM contents: name, version, supplier, license (parsed from
LICENSINGat generation time), copyright, SHA-256 of installed library, CPE, PURL, download location, build configuration defines, and optional third-party dependency versions (liboqs, libz, libxmss, liblms).Implementation:
scripts/gen-sbom(Python 3, stdlib only) stages amake installinto a temp directory, hashes the installed library, generates both formats, removes staging.configure.acdetectspython3,pyspdxtools, andgitviaAC_PATH_PROG.make sbomfails with a clear error if either required tool is missing.make bomsh
Runs a full clean rebuild under
bomtrace3(patched strace — userspace only, no kernel modifications) to produce an OmniBOR artifact graph inomnibor/. Ifbomsh_sbom.pyis available andwolfssl-<version>.spdx.jsonexists, annotates the SPDX with aPERSISTENT-ID gitoidExternalRef.configure.acdetectsbomtrace3,bomsh_create_bom.py, andbomsh_sbom.pyviaAC_PATH_PROG. The raw logfile and conf file are written to the build directory (not/tmp/) to avoid concurrent-build collisions. All generated files removed bymake clean.Install targets
Documentation
doc/SBOM.md— unified reference covering both targets, combined workflow, all output files, implementation notesdoc/CRA.md— product-integrator guide: how to incorporate wolfSSL's SBOM artefacts into a downstream product SBOM (SPDXExternalDocumentRef, CycloneDX component reference), commercial license guidance, OmniBOR gitoid meaning, auditor handoff checklist, links to OpenSSF CRA and SBOM Everywhere SIG guidanceINSTALL: sections 21 and 22README.md: brief SBOM/CRA and OmniBOR/Bomsh sectionsdoc/include.am:SBOM.mdandCRA.mdadded todist_doc_DATATest plan
./autogen.sh && ./configure && make && make sbom— verify three SBOM files produced and SPDX validatesmake install-sbom/make uninstall-sbom— verify files install and remove cleanlymake clean— verify all generated files removedbomtrace3andbomsh_create_bom.pyin PATH:make sbom && make bomsh— verifyomnibor/produced andomnibor.wolfssl-<ver>.spdx.jsoncontainsPERSISTENT-ID gitoidExternalRefbomtrace3in PATH:make bomsh— verify clear error message, non-zero exitmake sbom:make bomsh— verify OmniBOR graph produced, NOTE printed about missing SPDX, no failure