SBOM: adopt shared sbom.am fragment; bring CMake target to parity

Autotools: replace the inline make sbom recipe with the shared
scripts/sbom.am fragment (declare wolftpm identity, LICENSE, wolfSSL
dependency, and wolftpm/options.h as the feature-macro source). Add GIT
discovery + AC_SUBST for reproducible SOURCE_DATE_EPOCH, and revert the
WOLFTPM_LIBRARY_VERSION_{FIRST,SECOND,THIRD} split that only fed the old
hardcoded --lib path (the fragment discovers the artifact by glob).

CMake: pin the SBOM licence to GPL-3.0-or-later (header-accurate; matches
autotools SBOM_LICENSE_OVERRIDE) and record wolfSSL as a dependency via
--dep-wolfssl, capability-gated on gen-sbom --help so older gen-sbom
versions still produce a valid (dependency-less) SBOM.

Pin the license default to GPL-3.0-or-later to match the source headers
("or (at your option) any later version"), fixing the SBOM licence field.

Add a CI workflow that builds wolfSSL + wolfTPM and asserts SBOM identity,
licence, options capture, reproducibility, and the wolfSSL dependency.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
pull/536/head
Sameeh Jubran 2026-07-10 14:19:24 +03:00
parent 6f86a048dd
commit fc511714d3
6 changed files with 469 additions and 79 deletions

175
.github/workflows/sbom.yml vendored 100644
View File

@ -0,0 +1,175 @@
name: SBOM Test
on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]
workflow_dispatch:
inputs:
wolfssl_ref:
description: 'wolfssl git ref that provides scripts/gen-sbom'
# TODO: switch back to 'master' once wolfSSL/wolfssl#10343 merges.
default: 'refs/pull/10343/head'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# This workflow only reads the repo and uploads artefacts; no API writes.
permissions:
contents: read
jobs:
sbom:
name: wolfTPM SBOM generation (linux)
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout wolftpm
uses: actions/checkout@v4
with:
path: wolftpm
# wolfTPM links wolfSSL/wolfCrypt (RNG, parameter encryption, PK
# callbacks), so its SBOM records wolfSSL as a dependency. wolfSSL is
# built + installed here so wolfTPM has a library to link, and the same
# source tree (scripts/gen-sbom + wolfssl/version.h) is passed to
# `make sbom` via WOLFSSL_DIR -- so the recorded wolfSSL dependency
# version matches the linked one. gen-sbom is not yet on wolfssl master,
# so default to the open PR head that carries it (wolfSSL/wolfssl#10343)
# so CI actually exercises `make sbom` instead of silently skipping.
# TODO: switch the fallback back to 'master' once #10343 merges.
- name: Checkout wolfssl (gen-sbom + library source)
uses: actions/checkout@v4
with:
repository: wolfSSL/wolfssl
ref: ${{ github.event.inputs.wolfssl_ref || 'refs/pull/10343/head' }}
path: wolfssl
- name: Install build tooling and SBOM validator (pyspdxtools)
run: |
sudo apt-get update
sudo apt-get install -y build-essential autoconf automake libtool \
pkg-config
python3 -m pip install --user 'spdx-tools==0.8.*'
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Build and install wolfssl
working-directory: wolfssl
run: |
autoreconf -ivf
./configure --enable-wolftpm --enable-pkcallbacks \
--prefix="$GITHUB_WORKSPACE/wolfssl-install"
make -j"$(nproc)"
make install
# gen-sbom lives in wolfssl and may not be on the checked-out ref yet (the
# wolfSSL SBOM change can land separately). Gate on its presence and on
# --dep-wolfssl so this workflow is safe against a gen-sbom that predates
# it.
- name: Detect gen-sbom availability and capabilities
id: gate
run: |
GS="$GITHUB_WORKSPACE/wolfssl/scripts/gen-sbom"
if [ ! -f "$GS" ]; then
echo "have=no" >> "$GITHUB_OUTPUT"
echo "::notice::wolfssl scripts/gen-sbom not present on this ref; skipping SBOM generation."
exit 0
fi
echo "have=yes" >> "$GITHUB_OUTPUT"
if python3 "$GS" --help 2>/dev/null | grep -q -- '--dep-wolfssl'; then
echo "dep_wolfssl=yes" >> "$GITHUB_OUTPUT"
else
echo "dep_wolfssl=no" >> "$GITHUB_OUTPUT"
echo "::notice::gen-sbom on this ref has no --dep-wolfssl; the wolfssl dependency + name-derived identity assertions will be skipped."
fi
- name: Configure and build wolftpm
if: steps.gate.outputs.have == 'yes'
working-directory: wolftpm
run: |
autoreconf -ivf
./configure --enable-swtpm --disable-fwtpm --disable-examples \
--with-wolfcrypt="$GITHUB_WORKSPACE/wolfssl-install"
make -j"$(nproc)"
- name: Generate SBOM
if: steps.gate.outputs.have == 'yes'
working-directory: wolftpm
run: make sbom WOLFSSL_DIR="$GITHUB_WORKSPACE/wolfssl"
- name: Outputs exist and SPDX validates
if: steps.gate.outputs.have == 'yes'
working-directory: wolftpm
run: |
ls wolftpm-*.cdx.json wolftpm-*.spdx.json wolftpm-*.spdx
pyspdxtools --infile wolftpm-*.spdx.json
- name: CycloneDX identity, licence, and captured options
if: steps.gate.outputs.have == 'yes'
working-directory: wolftpm
run: |
python3 - <<'PY'
import glob, json
cdx = json.load(open(glob.glob('wolftpm-*.cdx.json')[0]))
assert cdx['bomFormat'] == 'CycloneDX', cdx.get('bomFormat')
assert cdx['specVersion'] == '1.6', cdx.get('specVersion')
m = cdx['metadata']['component']
assert m['name'] == 'wolftpm', m['name']
assert m['purl'].startswith('pkg:github/wolfSSL/wolftpm@'), m['purl']
# Default override must land as GPL-3.0-or-later (matches source headers).
ids = [l.get('license', {}).get('id') for l in m.get('licenses', [])]
assert 'GPL-3.0-or-later' in ids, ids
# Identity is the hashed library artifact.
assert {h['alg'] for h in m.get('hashes', [])}, 'no component hash'
# SBOM_OPTIONS_H must have been parsed: wolfTPM records its feature
# macros in wolftpm/options.h, so the SBOM's build properties must be
# populated.
props = [p for p in m.get('properties', [])
if p.get('name', '').startswith('wolfssl:build:')]
assert props, 'no wolfssl:build:* properties captured from options.h'
print('CDX ok:', m['name'], m['purl'], ids, f'{len(props)} build props')
PY
- name: Reproducible across two runs (SOURCE_DATE_EPOCH)
if: steps.gate.outputs.have == 'yes'
working-directory: wolftpm
run: |
rm -f wolftpm-*.cdx.json wolftpm-*.spdx.json wolftpm-*.spdx
SOURCE_DATE_EPOCH=1700000000 make sbom \
WOLFSSL_DIR="$GITHUB_WORKSPACE/wolfssl"
sha256sum wolftpm-*.cdx.json wolftpm-*.spdx.json > /tmp/a.sums
rm -f wolftpm-*.cdx.json wolftpm-*.spdx.json wolftpm-*.spdx
SOURCE_DATE_EPOCH=1700000000 make sbom \
WOLFSSL_DIR="$GITHUB_WORKSPACE/wolfssl"
sha256sum wolftpm-*.cdx.json wolftpm-*.spdx.json > /tmp/b.sums
diff /tmp/a.sums /tmp/b.sums
- name: wolfssl recorded as a dependency
if: steps.gate.outputs.have == 'yes' && steps.gate.outputs.dep_wolfssl == 'yes'
working-directory: wolftpm
run: |
python3 - <<'PY'
import glob, json
d = json.load(open(glob.glob('wolftpm-*.spdx.json')[0]))
assert 'wolfssl' in {p['name'] for p in d['packages']}, \
[p['name'] for p in d['packages']]
rels = [(r['spdxElementId'], r['relationshipType'],
r['relatedSpdxElement']) for r in d['relationships']]
assert ('SPDXRef-Package-wolftpm', 'DEPENDS_ON',
'SPDXRef-Package-wolfssl') in rels, rels
print('wolfssl dependency ok')
PY
- name: Upload SBOM artefacts
if: always() && steps.gate.outputs.have == 'yes'
uses: actions/upload-artifact@v4
with:
name: wolftpm-sbom-${{ github.sha }}
path: |
wolftpm/wolftpm-*.cdx.json
wolftpm/wolftpm-*.spdx.json
wolftpm/wolftpm-*.spdx
if-no-files-found: warn
retention-days: 90

View File

@ -793,6 +793,15 @@ if(BUILD_WOLFTPM_LIB)
set(WOLFSSL_DIR "" CACHE PATH
"Path to wolfssl source tree with scripts/gen-sbom")
# wolfTPM is GPLv3-or-later (per the per-file source headers: "either
# version 3 of the License, or (at your option) any later version") or
# commercial. Pin the header-accurate SPDX id so the SBOM is correct
# regardless of the gen-sbom version's licence detection; commercial
# licensees can override it (e.g. LicenseRef-wolfSSL-Commercial). This
# mirrors SBOM_LICENSE_OVERRIDE in the autotools build.
set(SBOM_LICENSE_OVERRIDE "GPL-3.0-or-later" CACHE STRING
"SPDX licence expression recorded in the SBOM")
# Derive the version from wolftpm/version.h, NOT from PROJECT_VERSION.
# autotools `make sbom` uses PACKAGE_VERSION, which is sourced from
# version.h. Reading the same header here keeps the cmake and autotools
@ -863,6 +872,25 @@ if(BUILD_WOLFTPM_LIB)
set(SBOM_LIB
"${SBOM_STAGING}/lib/libwolftpm${CMAKE_SHARED_LIBRARY_SUFFIX}")
# wolfTPM links wolfSSL/wolfCrypt, so record wolfSSL as a dependency in the
# SBOM (matches the autotools SBOM_DEP_WOLFSSL=yes). Recording it needs the
# gen-sbom from wolfSSL/wolfssl#10343; older gen-sbom rejects unknown flags,
# so probe --help and only pass --dep-wolfssl when supported. Against an
# older gen-sbom the SBOM is still valid but omits the wolfSSL entry.
set(_sbom_dep_wolfssl "")
execute_process(
COMMAND ${PYTHON3_CMD} ${WOLFSSL_DIR}/scripts/gen-sbom --help
OUTPUT_VARIABLE _sbom_help
ERROR_QUIET)
if(_sbom_help MATCHES "--dep-wolfssl")
set(_sbom_dep_wolfssl --dep-wolfssl yes)
else()
message(WARNING
"sbom: ${WOLFSSL_DIR}/scripts/gen-sbom has no --dep-wolfssl; "
"the SBOM will omit the wolfSSL dependency entry. Use the gen-sbom "
"from wolfSSL/wolfssl#10343 (or master once merged) to record it.")
endif()
# ${OPTION_FILE} is the generated wolftpm/options.h, the same compile-time
# option fingerprint autotools `make sbom` feeds to gen-sbom via
# --options-h. Using it (rather than a raw `cc -dM` dump, which would only
@ -881,6 +909,8 @@ if(BUILD_WOLFTPM_LIB)
--license-file ${CMAKE_SOURCE_DIR}/LICENSE
--options-h ${OPTION_FILE}
--lib ${SBOM_LIB}
--license-override ${SBOM_LICENSE_OVERRIDE}
${_sbom_dep_wolfssl}
--cdx-out ${SBOM_CDX}
--spdx-out ${SBOM_SPDX}
# Validate the SPDX JSON and emit the tag-value rendering as a side

View File

@ -133,69 +133,25 @@ cppcheck:
--error-exitcode=89 --std=c89 \
-I wolftpm src/ hal/ examples
# SBOM generation (CRA compliance)
SBOM_CDX = wolftpm-$(PACKAGE_VERSION).cdx.json
SBOM_SPDX = wolftpm-$(PACKAGE_VERSION).spdx.json
SBOM_SPDX_TV = wolftpm-$(PACKAGE_VERSION).spdx
sbomdir = $(datadir)/doc/$(PACKAGE)
# SBOM generation (CRA compliance). The recipe is shared across the wolfSSL
# stack's autotools products in scripts/sbom.am; wolfTPM just declares what it
# is (a libwolftpm library that links wolfSSL/wolfCrypt) and includes it.
# WOLFSSL_DIR must point to a wolfssl source tree containing scripts/gen-sbom.
SBOM_PKGNAME = wolftpm
SBOM_LICENSE_FILE = $(srcdir)/LICENSE
SBOM_DEP_WOLFSSL = yes
.PHONY: sbom install-sbom uninstall-sbom
# wolfTPM records its feature macros in its own generated options header, so
# point gen-sbom at that header rather than at the compiler-derived defaults.
SBOM_OPTIONS_H = $(abs_builddir)/wolftpm/options.h
sbom:
@if test -z "$(PYTHON3)"; then \
echo ""; \
echo "ERROR: 'python3' not found in PATH. Cannot generate SBOM."; \
echo ""; \
exit 1; \
fi
@if test -z "$(PYSPDXTOOLS)"; then \
echo ""; \
echo "ERROR: 'pyspdxtools' not found in PATH. Cannot validate SBOM."; \
echo " Install: pip install spdx-tools"; \
echo ""; \
exit 1; \
fi
@if test -z "$(WOLFSSL_DIR)"; then \
echo ""; \
echo "ERROR: WOLFSSL_DIR is not set. Cannot locate gen-sbom."; \
echo " Set WOLFSSL_DIR to your wolfSSL source tree, e.g.:"; \
echo " make sbom WOLFSSL_DIR=/path/to/wolfssl"; \
echo ""; \
exit 1; \
fi
@if test ! -f "$(WOLFSSL_DIR)/scripts/gen-sbom"; then \
echo ""; \
echo "ERROR: $(WOLFSSL_DIR)/scripts/gen-sbom not found."; \
echo " Check that WOLFSSL_DIR points to a wolfSSL tree with SBOM support."; \
echo ""; \
exit 1; \
fi
rm -rf $(abs_builddir)/_sbom_staging
$(MAKE) install DESTDIR=$(abs_builddir)/_sbom_staging
$(PYTHON3) $(WOLFSSL_DIR)/scripts/gen-sbom \
--name wolftpm \
--version $(PACKAGE_VERSION) \
--supplier "wolfSSL Inc." \
--license-file $(srcdir)/LICENSE \
--options-h $(abs_builddir)/wolftpm/options.h \
--lib $(abs_builddir)/_sbom_staging$(libdir)/libwolftpm.so.@WOLFTPM_LIBRARY_VERSION_FIRST@.@WOLFTPM_LIBRARY_VERSION_SECOND@.@WOLFTPM_LIBRARY_VERSION_THIRD@ \
$(if $(SBOM_LICENSE_OVERRIDE),--license-override $(SBOM_LICENSE_OVERRIDE)) \
$(if $(SBOM_LICENSE_TEXT),--license-text $(SBOM_LICENSE_TEXT)) \
--cdx-out $(abs_builddir)/$(SBOM_CDX) \
--spdx-out $(abs_builddir)/$(SBOM_SPDX)
rm -rf $(abs_builddir)/_sbom_staging
$(PYSPDXTOOLS) --infile $(abs_builddir)/$(SBOM_SPDX) \
--outfile $(abs_builddir)/$(SBOM_SPDX_TV)
# wolfTPM is GPLv3-or-later (per the per-file source headers: "either version 3
# of the License, or (at your option) any later version") or commercial. Pin
# the header-accurate SPDX id here so the SBOM is correct regardless of the
# gen-sbom version's licence detection; commercial licensees can override it
# (e.g. LicenseRef-wolfSSL-Commercial).
SBOM_LICENSE_OVERRIDE ?= GPL-3.0-or-later
install-sbom: sbom
$(MKDIR_P) $(DESTDIR)$(sbomdir)
$(INSTALL_DATA) $(SBOM_CDX) $(DESTDIR)$(sbomdir)/
$(INSTALL_DATA) $(SBOM_SPDX) $(DESTDIR)$(sbomdir)/
$(INSTALL_DATA) $(SBOM_SPDX_TV) $(DESTDIR)$(sbomdir)/
EXTRA_DIST += scripts/sbom.am
uninstall-sbom:
-rm -f $(DESTDIR)$(sbomdir)/$(SBOM_CDX)
-rm -f $(DESTDIR)$(sbomdir)/$(SBOM_SPDX)
-rm -f $(DESTDIR)$(sbomdir)/$(SBOM_SPDX_TV)
CLEANFILES += $(SBOM_CDX) $(SBOM_SPDX) $(SBOM_SPDX_TV)
include scripts/sbom.am

View File

@ -1126,6 +1126,12 @@ See `./examples/endorsement/get_ek_certs`.
wolfTPM generates a Software Bill of Materials (SBOM) in CycloneDX 1.6 and
SPDX 2.3 formats to support compliance with the EU Cyber Resilience Act (CRA).
The SBOM records the configured build options (from `wolftpm/options.h`),
hashes the built `libwolftpm` library artifact (shared or static; ELF, Mach-O,
or PE), and (with a sufficiently new `gen-sbom`) lists wolfSSL as a dependency
so vulnerability scanners can associate wolfSSL advisories with a wolfTPM
deployment. Output is reproducible: set `SOURCE_DATE_EPOCH` (or build from a git
checkout, which uses the last commit time) and repeated runs are byte-identical.
```sh
make sbom WOLFSSL_DIR=/path/to/wolfssl
@ -1135,19 +1141,29 @@ Requires `python3` and `pyspdxtools` (`pip install spdx-tools`). `WOLFSSL_DIR`
must point to a wolfssl source tree containing `scripts/gen-sbom` (branch
`feat/sbom-embedded`, or `master` once wolfSSL/wolfssl#10343 merges).
Output files in the build directory:
Output: `wolftpm-<version>.cdx.json`, `wolftpm-<version>.spdx.json`, `wolftpm-<version>.spdx`
| File | Format |
|------|--------|
| `wolftpm-<version>.cdx.json` | CycloneDX 1.6 |
| `wolftpm-<version>.spdx.json` | SPDX 2.3 JSON |
| `wolftpm-<version>.spdx` | SPDX 2.3 tag-value |
Optional overrides:
- `SBOM_LICENSE_OVERRIDE` - SPDX expression to use instead of the licence
parsed from `LICENSE` (e.g. `LicenseRef-wolfSSL-Commercial` for commercial
licensees). Defaults to `GPL-3.0-or-later` (the per-file header licence).
- `SBOM_LICENSE_TEXT` - path to the licence text for any `LicenseRef-*` used in
`SBOM_LICENSE_OVERRIDE` (required by SPDX 2.3).
- `SBOM_WOLFSSL_VERSION` - version recorded for the wolfSSL dependency;
auto-detected from `WOLFSSL_DIR/wolfssl/version.h` (or wolfSSL's `pkg-config`
entry) when unset.
```sh
make install-sbom # installs to $(datadir)/doc/wolftpm/
make uninstall-sbom
```
Note: recording wolfSSL as a dependency and emitting wolfTPM-specific project
URLs require the `gen-sbom` from wolfSSL/wolfssl#10343. Against an older
`gen-sbom`, `make sbom` still succeeds and produces a valid SBOM, but omits the
wolfSSL dependency entry and inherits wolfSSL's project URLs.
For further CRA guidance see [wolfssl/doc/CRA.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/CRA.md).
## Support

View File

@ -29,16 +29,17 @@ AC_ARG_PROGRAM
AC_CONFIG_HEADERS([src/config.h])
WOLFTPM_LIBRARY_VERSION_FIRST=17
# ^--- current (installed so name: current-age)
WOLFTPM_LIBRARY_VERSION_SECOND=0
# ^-- revision
WOLFTPM_LIBRARY_VERSION_THIRD=0
# ^--- age
WOLFTPM_LIBRARY_VERSION=${WOLFTPM_LIBRARY_VERSION_FIRST}:${WOLFTPM_LIBRARY_VERSION_SECOND}:${WOLFTPM_LIBRARY_VERSION_THIRD}
AC_SUBST([WOLFTPM_LIBRARY_VERSION_FIRST])
AC_SUBST([WOLFTPM_LIBRARY_VERSION_SECOND])
AC_SUBST([WOLFTPM_LIBRARY_VERSION_THIRD])
WOLFTPM_LIBRARY_VERSION=17:0:0
# | | |
# +------+ | +---+
# | | |
# current:revision:age
# | | |
# | | +- increment if source code has changed
# | | set to zero if [current] or [revision] is incremented
# | +- increment if interfaces have been added
# | set to zero if [current] is incremented
# +- increment if interfaces have been removed or changed
AC_SUBST([WOLFTPM_LIBRARY_VERSION])
@ -990,9 +991,15 @@ AC_SUBST([AM_CFLAGS])
AC_SUBST([AM_LDFLAGS])
AC_SUBST([CPPCHECK])
# SBOM generation
# Tools used by the SBOM targets (see scripts/sbom.am `make sbom`). GIT is used
# only to derive SOURCE_DATE_EPOCH for reproducible SBOM output; all three are
# optional and the target reports a clear error when a required one is missing.
AC_PATH_PROG([PYTHON3], [python3])
AC_PATH_PROG([PYSPDXTOOLS], [pyspdxtools])
AC_PATH_PROG([GIT], [git])
AC_SUBST([PYTHON3])
AC_SUBST([PYSPDXTOOLS])
AC_SUBST([GIT])
# FINAL
AC_CONFIG_FILES([Makefile])

206
scripts/sbom.am 100644
View File

@ -0,0 +1,206 @@
# scripts/sbom.am - shared Automake recipe for CRA-compliant SBOM generation.
#
# One generator (gen-sbom) does the work; each product just describes itself and
# includes this fragment. It is deliberately product-agnostic: a Makefile.am
# sets a few variables (below) and does `include scripts/sbom.am` to get the
# `sbom`, `install-sbom` and `uninstall-sbom` targets.
#
# The canonical copy lives in the wolfSSL repository (scripts/sbom.am); keep
# product copies in sync. gen-sbom is taken from a vendored scripts/gen-sbom
# if a product ships one (used automatically), otherwise from a wolfSSL source
# tree via WOLFSSL_DIR. wolfSSH uses the WOLFSSL_DIR route; vendoring gen-sbom
# for fully offline tarball builds can be added later with no change here.
#
# ---------------------------------------------------------------------------
# The including Makefile.am MUST set, before `include scripts/sbom.am`:
# SBOM_PKGNAME Product name recorded in the SBOM (e.g. wolfssh). Drives
# the output filenames and gen-sbom --name.
# SBOM_LICENSE_FILE Path to the product's LICENSING file
# (e.g. $(srcdir)/LICENSING).
#
# Optional (defaults shown):
# SBOM_OPTIONS_H Path to a product-generated options header (e.g.
# wolfMQTT's $(builddir)/wolfmqtt/options.h) that records
# the enabled build macros. Set this for products whose
# feature flags are NOT in config.h (no AC_DEFINE); when
# unset the recipe derives the macros from the compiler +
# config.h. Default: unset.
# SBOM_ARTIFACT lib | bin - which build output to hash. Default: lib.
# SBOM_LIB_STEM Library basename w/o extension. Default: lib$(SBOM_PKGNAME).
# SBOM_BIN_NAME Program name when SBOM_ARTIFACT = bin. Default: $(SBOM_PKGNAME).
# SBOM_DEP_WOLFSSL yes | no - record wolfSSL as a dependency. Default: no.
# SBOM_DEP_OPENSSL yes | no - record OpenSSL as a dependency (wolfProvider /
# wolfEngine). Default: no.
# SBOM_LICENSE_OVERRIDE SPDX expression to record instead of the licence
# detected from SBOM_LICENSE_FILE.
# SBOM_LICENSE_TEXT Path to licence text for any LicenseRef-* used in
# SBOM_LICENSE_OVERRIDE (required by SPDX 2.3).
# SBOM_WOLFSSL_VERSION Version recorded for the wolfSSL dependency;
# auto-detected from WOLFSSL_DIR/wolfssl/version.h when unset.
# SBOM_OPENSSL_VERSION Version recorded for the OpenSSL dependency;
# gen-sbom resolves it via pkg-config when unset.
#
# The wolfSSL/OpenSSL dependency flags are feature-detected against gen-sbom
# --help, so a product wired for them still produces a valid SBOM (with a NOTE)
# against a gen-sbom that predates the flag.
#
# gen-sbom is located at $(srcdir)/scripts/gen-sbom if vendored, else at
# $(WOLFSSL_DIR)/scripts/gen-sbom. python3, pyspdxtools and git come from
# configure (AC_PATH_PROG); git is used only to derive SOURCE_DATE_EPOCH.
# ---------------------------------------------------------------------------
SBOM_ARTIFACT ?= lib
SBOM_LIB_STEM ?= lib$(SBOM_PKGNAME)
SBOM_BIN_NAME ?= $(SBOM_PKGNAME)
SBOM_DEP_WOLFSSL ?= no
SBOM_DEP_OPENSSL ?= no
SBOM_CDX = $(SBOM_PKGNAME)-$(PACKAGE_VERSION).cdx.json
SBOM_SPDX = $(SBOM_PKGNAME)-$(PACKAGE_VERSION).spdx.json
SBOM_SPDX_TV = $(SBOM_PKGNAME)-$(PACKAGE_VERSION).spdx
sbomdir = $(datadir)/doc/$(PACKAGE)
# Prefer a vendored gen-sbom; fall back to an external wolfSSL source tree.
# The fallback is $(wildcard)-guarded and only consulted when WOLFSSL_DIR is
# set, so an unset WOLFSSL_DIR leaves SBOM_GEN empty (and the sbom recipe's
# `test -f` prints the "set WOLFSSL_DIR" error) rather than resolving to an
# absolute /scripts/gen-sbom that could run an unrelated host script.
SBOM_GEN = $(firstword $(wildcard $(srcdir)/scripts/gen-sbom) \
$(if $(WOLFSSL_DIR),$(wildcard $(WOLFSSL_DIR)/scripts/gen-sbom)))
# Library artifact search order (versioned first) covering ELF, Mach-O and PE.
# Windows import libs (.lib) come with and without the "lib" prefix.
SBOM_LIB_GLOBS = \
$(SBOM_LIB_STEM).so.[0-9]* \
$(SBOM_LIB_STEM).so \
$(SBOM_LIB_STEM).[0-9]*.dylib \
$(SBOM_LIB_STEM).dylib \
$(SBOM_LIB_STEM).dll \
$(SBOM_LIB_STEM).dll.a \
$(SBOM_LIB_STEM).lib \
$(SBOM_PKGNAME).lib \
$(SBOM_LIB_STEM).a
# Automake requires CLEANFILES to be initialised with `=` before `+=`; the
# including Makefile.am must declare `CLEANFILES =` (typically in its primaries
# init block) before `include scripts/sbom.am`.
CLEANFILES += $(SBOM_CDX) $(SBOM_SPDX) $(SBOM_SPDX_TV)
.PHONY: sbom install-sbom uninstall-sbom
# Stage a `make install` into a private tree, discover the installed artifact
# (shared/static library or program; ELF/Mach-O/PE), hash it, capture the
# configured build macros (AM_CPPFLAGS + config.h), generate SPDX+CDX, validate
# the SPDX, then convert to tag-value. The staging tree and temp defines file
# are removed unconditionally via `trap`, even on failure. SOURCE_DATE_EPOCH is
# honoured for reproducible output (defaults to the last git commit time).
sbom:
@test -n "$(PYTHON3)" || { \
echo "ERROR: 'python3' not found in PATH. Cannot generate SBOM."; \
exit 1; }
@test -n "$(PYSPDXTOOLS)" || { \
echo "ERROR: 'pyspdxtools' not found (pip install spdx-tools)."; \
exit 1; }
@test -f "$(SBOM_GEN)" || { \
echo "ERROR: gen-sbom not found. Vendor scripts/gen-sbom, or re-run:"; \
echo " make sbom WOLFSSL_DIR=/path/to/wolfssl"; \
exit 1; }
@rm -rf $(abs_builddir)/_sbom_staging
@set -e; \
_defines=`mktemp $(abs_builddir)/_sbom_defines.XXXXXX`; \
trap 'rm -rf $(abs_builddir)/_sbom_staging "$$_defines"' EXIT INT TERM HUP; \
$(MAKE) install DESTDIR=$(abs_builddir)/_sbom_staging; \
sbom_art=""; \
if test "$(SBOM_ARTIFACT)" = bin; then \
for art in \
"$(abs_builddir)/_sbom_staging$(bindir)/$(SBOM_BIN_NAME)" \
"$(abs_builddir)/_sbom_staging$(bindir)/$(SBOM_BIN_NAME)".exe; do \
if test -f "$$art"; then sbom_art="$$art"; break; fi; \
done; \
else \
for art in \
$(addprefix "$(abs_builddir)/_sbom_staging$(libdir)"/,$(SBOM_LIB_GLOBS)) \
$(addprefix "$(abs_builddir)/_sbom_staging$(bindir)"/,$(SBOM_LIB_STEM).dll $(SBOM_PKGNAME).dll); do \
if test -f "$$art"; then sbom_art="$$art"; break; fi; \
done; \
fi; \
if test -z "$$sbom_art"; then \
echo ""; \
echo "ERROR: no installed $(SBOM_PKGNAME) artifact found for SBOM."; \
echo " (configure with --enable-shared or --enable-static)"; \
echo ""; \
exit 1; \
fi; \
echo "SBOM: hashing $$sbom_art"; \
opts_h="$(SBOM_OPTIONS_H)"; \
if test -z "$$opts_h"; then \
opts_h="$$_defines"; \
$(CC) -dM -E $(DEFAULT_INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
$(if $(wildcard $(abs_builddir)/config.h),-include $(abs_builddir)/config.h) \
-x c /dev/null > "$$_defines"; \
fi; \
if test -z "$${SOURCE_DATE_EPOCH:-}" && test -n "$(GIT)" && \
$(GIT) -C "$(srcdir)" rev-parse --git-dir >/dev/null 2>&1; then \
sde=`$(GIT) -C "$(srcdir)" log -1 --format=%ct 2>/dev/null`; \
if test -n "$$sde"; then SOURCE_DATE_EPOCH="$$sde"; export SOURCE_DATE_EPOCH; fi; \
fi; \
dep_args=""; \
if test "$(SBOM_DEP_WOLFSSL)" = yes; then \
if $(PYTHON3) "$(SBOM_GEN)" --help 2>/dev/null \
| $(GREP) -q -- '--dep-wolfssl'; then \
dep_args="$$dep_args --dep-wolfssl yes"; \
wv="$(SBOM_WOLFSSL_VERSION)"; \
if test -z "$$wv" && test -f "$(WOLFSSL_DIR)/wolfssl/version.h"; then \
wv=`sed -n 's/.*LIBWOLFSSL_VERSION_STRING[ \t]*"\([^"]*\)".*/\1/p' \
"$(WOLFSSL_DIR)/wolfssl/version.h"`; \
fi; \
if test -n "$$wv"; then \
dep_args="$$dep_args --dep-version wolfssl=$$wv"; \
fi; \
else \
echo "NOTE: this gen-sbom has no --dep-wolfssl support, so the SBOM"; \
echo " will not list wolfssl as a dependency component. That"; \
echo " support is added by wolfSSL/wolfssl#10343; until it merges"; \
echo " to wolfssl master, point WOLFSSL_DIR at that PR's branch"; \
echo " to enable it. The generated SBOM is valid either way."; \
fi; \
fi; \
if test "$(SBOM_DEP_OPENSSL)" = yes; then \
if $(PYTHON3) "$(SBOM_GEN)" --help 2>/dev/null \
| $(GREP) -q -- '--dep-openssl'; then \
dep_args="$$dep_args --dep-openssl yes"; \
if test -n "$(SBOM_OPENSSL_VERSION)"; then \
dep_args="$$dep_args --dep-version openssl=$(SBOM_OPENSSL_VERSION)"; \
fi; \
else \
echo "NOTE: this gen-sbom has no --dep-openssl support; openssl will"; \
echo " not be listed as a dependency component."; \
fi; \
fi; \
$(PYTHON3) "$(SBOM_GEN)" \
--name $(SBOM_PKGNAME) \
--version $(PACKAGE_VERSION) \
--supplier "wolfSSL Inc." \
--license-file $(SBOM_LICENSE_FILE) \
--options-h "$$opts_h" \
--lib "$$sbom_art" \
$$dep_args \
$(if $(SBOM_LICENSE_OVERRIDE),--license-override '$(SBOM_LICENSE_OVERRIDE)') \
$(if $(SBOM_LICENSE_TEXT),--license-text '$(SBOM_LICENSE_TEXT)') \
--cdx-out $(abs_builddir)/$(SBOM_CDX) \
--spdx-out $(abs_builddir)/$(SBOM_SPDX); \
$(PYSPDXTOOLS) --infile $(abs_builddir)/$(SBOM_SPDX) \
--outfile $(abs_builddir)/$(SBOM_SPDX_TV)
install-sbom: sbom
$(MKDIR_P) $(DESTDIR)$(sbomdir)
$(INSTALL_DATA) $(SBOM_CDX) $(DESTDIR)$(sbomdir)/
$(INSTALL_DATA) $(SBOM_SPDX) $(DESTDIR)$(sbomdir)/
$(INSTALL_DATA) $(SBOM_SPDX_TV) $(DESTDIR)$(sbomdir)/
uninstall-sbom:
-rm -f $(DESTDIR)$(sbomdir)/$(SBOM_CDX)
-rm -f $(DESTDIR)$(sbomdir)/$(SBOM_SPDX)
-rm -f $(DESTDIR)$(sbomdir)/$(SBOM_SPDX_TV)
uninstall-hook: uninstall-sbom