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 set(WOLFSSL_DIR "" CACHE PATH
"Path to wolfssl source tree with scripts/gen-sbom") "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. # Derive the version from wolftpm/version.h, NOT from PROJECT_VERSION.
# autotools `make sbom` uses PACKAGE_VERSION, which is sourced from # autotools `make sbom` uses PACKAGE_VERSION, which is sourced from
# version.h. Reading the same header here keeps the cmake and autotools # version.h. Reading the same header here keeps the cmake and autotools
@ -863,6 +872,25 @@ if(BUILD_WOLFTPM_LIB)
set(SBOM_LIB set(SBOM_LIB
"${SBOM_STAGING}/lib/libwolftpm${CMAKE_SHARED_LIBRARY_SUFFIX}") "${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_FILE} is the generated wolftpm/options.h, the same compile-time
# option fingerprint autotools `make sbom` feeds to gen-sbom via # option fingerprint autotools `make sbom` feeds to gen-sbom via
# --options-h. Using it (rather than a raw `cc -dM` dump, which would only # --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 --license-file ${CMAKE_SOURCE_DIR}/LICENSE
--options-h ${OPTION_FILE} --options-h ${OPTION_FILE}
--lib ${SBOM_LIB} --lib ${SBOM_LIB}
--license-override ${SBOM_LICENSE_OVERRIDE}
${_sbom_dep_wolfssl}
--cdx-out ${SBOM_CDX} --cdx-out ${SBOM_CDX}
--spdx-out ${SBOM_SPDX} --spdx-out ${SBOM_SPDX}
# Validate the SPDX JSON and emit the tag-value rendering as a side # 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 \ --error-exitcode=89 --std=c89 \
-I wolftpm src/ hal/ examples -I wolftpm src/ hal/ examples
# SBOM generation (CRA compliance) # SBOM generation (CRA compliance). The recipe is shared across the wolfSSL
SBOM_CDX = wolftpm-$(PACKAGE_VERSION).cdx.json # stack's autotools products in scripts/sbom.am; wolfTPM just declares what it
SBOM_SPDX = wolftpm-$(PACKAGE_VERSION).spdx.json # is (a libwolftpm library that links wolfSSL/wolfCrypt) and includes it.
SBOM_SPDX_TV = wolftpm-$(PACKAGE_VERSION).spdx # WOLFSSL_DIR must point to a wolfssl source tree containing scripts/gen-sbom.
sbomdir = $(datadir)/doc/$(PACKAGE) 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: # wolfTPM is GPLv3-or-later (per the per-file source headers: "either version 3
@if test -z "$(PYTHON3)"; then \ # of the License, or (at your option) any later version") or commercial. Pin
echo ""; \ # the header-accurate SPDX id here so the SBOM is correct regardless of the
echo "ERROR: 'python3' not found in PATH. Cannot generate SBOM."; \ # gen-sbom version's licence detection; commercial licensees can override it
echo ""; \ # (e.g. LicenseRef-wolfSSL-Commercial).
exit 1; \ SBOM_LICENSE_OVERRIDE ?= GPL-3.0-or-later
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)
install-sbom: sbom EXTRA_DIST += scripts/sbom.am
$(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: include scripts/sbom.am
-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)

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 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). 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 ```sh
make sbom WOLFSSL_DIR=/path/to/wolfssl 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 must point to a wolfssl source tree containing `scripts/gen-sbom` (branch
`feat/sbom-embedded`, or `master` once wolfSSL/wolfssl#10343 merges). `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 | Optional overrides:
|------|--------|
| `wolftpm-<version>.cdx.json` | CycloneDX 1.6 | - `SBOM_LICENSE_OVERRIDE` - SPDX expression to use instead of the licence
| `wolftpm-<version>.spdx.json` | SPDX 2.3 JSON | parsed from `LICENSE` (e.g. `LicenseRef-wolfSSL-Commercial` for commercial
| `wolftpm-<version>.spdx` | SPDX 2.3 tag-value | 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 ```sh
make install-sbom # installs to $(datadir)/doc/wolftpm/ make install-sbom # installs to $(datadir)/doc/wolftpm/
make uninstall-sbom 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). For further CRA guidance see [wolfssl/doc/CRA.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/CRA.md).
## Support ## Support

View File

@ -29,16 +29,17 @@ AC_ARG_PROGRAM
AC_CONFIG_HEADERS([src/config.h]) AC_CONFIG_HEADERS([src/config.h])
WOLFTPM_LIBRARY_VERSION_FIRST=17 WOLFTPM_LIBRARY_VERSION=17:0:0
# ^--- current (installed so name: current-age) # | | |
WOLFTPM_LIBRARY_VERSION_SECOND=0 # +------+ | +---+
# ^-- revision # | | |
WOLFTPM_LIBRARY_VERSION_THIRD=0 # current:revision:age
# ^--- age # | | |
WOLFTPM_LIBRARY_VERSION=${WOLFTPM_LIBRARY_VERSION_FIRST}:${WOLFTPM_LIBRARY_VERSION_SECOND}:${WOLFTPM_LIBRARY_VERSION_THIRD} # | | +- increment if source code has changed
AC_SUBST([WOLFTPM_LIBRARY_VERSION_FIRST]) # | | set to zero if [current] or [revision] is incremented
AC_SUBST([WOLFTPM_LIBRARY_VERSION_SECOND]) # | +- increment if interfaces have been added
AC_SUBST([WOLFTPM_LIBRARY_VERSION_THIRD]) # | set to zero if [current] is incremented
# +- increment if interfaces have been removed or changed
AC_SUBST([WOLFTPM_LIBRARY_VERSION]) AC_SUBST([WOLFTPM_LIBRARY_VERSION])
@ -990,9 +991,15 @@ AC_SUBST([AM_CFLAGS])
AC_SUBST([AM_LDFLAGS]) AC_SUBST([AM_LDFLAGS])
AC_SUBST([CPPCHECK]) 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([PYTHON3], [python3])
AC_PATH_PROG([PYSPDXTOOLS], [pyspdxtools]) AC_PATH_PROG([PYSPDXTOOLS], [pyspdxtools])
AC_PATH_PROG([GIT], [git])
AC_SUBST([PYTHON3])
AC_SUBST([PYSPDXTOOLS])
AC_SUBST([GIT])
# FINAL # FINAL
AC_CONFIG_FILES([Makefile]) 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