Skip to content

Commit 2de28e9

Browse files
committed
sbom: switch to vendored wolfGlass driver with real config capture
Replace the hand-rolled gen-sbom Makefile recipe with tools/sbom from wolfGlass 9bdf5b7. Capture wh_settings.h via SBOM_OPTIONS_H (not SBOM_CFLAGS), require WOLFHSM_CFG_DIR, pin an honest .wolfglass-rev, and add a core-only CI canary with min-properties / dep-version checks. Un-ignore tools/sbom/build/ so sbom.mk is not dropped by Build/ on case-insensitive filesystems. Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
1 parent 073f4eb commit 2de28e9

19 files changed

Lines changed: 3251 additions & 196 deletions

.github/workflows/test-sbom.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
name: wolfHSM SBOM Canary
2+
3+
# Core-target only for this pass. posix-server / posix-client / whnvmtool
4+
# SBOM targets are tracked as follow-up and are not exercised here.
5+
#
6+
# Guards:
7+
# * make sbom with an explicit WOLFHSM_CFG_DIR
8+
# * schema-valid CycloneDX 1.6 + SPDX 2.3 whose top-level component is wolfhsm
9+
# * toolchain neutrality: HOSTCC=gcc vs HOSTCC=clang must be byte-identical
10+
#
11+
# No Make-vs-CMake step: wolfHSM has no CMake build.
12+
13+
on:
14+
push:
15+
branches: [ 'master', 'main', 'release/**' ]
16+
pull_request:
17+
branches: [ '*' ]
18+
19+
jobs:
20+
sbom_canary:
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 15
23+
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- name: Trust workspace
28+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
29+
30+
- name: Checkout wolfssl (sibling for include path)
31+
uses: actions/checkout@v4
32+
with:
33+
repository: wolfSSL/wolfssl
34+
path: wolfssl
35+
36+
- name: Install tooling
37+
run: |
38+
sudo apt-get update
39+
sudo apt-get install -y python3 build-essential clang
40+
41+
- name: Verify vendored gen-sbom
42+
run: test -f tools/sbom/gen-sbom
43+
44+
- name: make sbom (core)
45+
run: |
46+
WOLFSSL_DIR=./wolfssl WOLFHSM_CFG_DIR=test/config make sbom
47+
python3 tools/sbom/validate_sbom.py --name-prefix wolfhsm \
48+
--min-properties 50 --require-dep-version wolfssl \
49+
wolfhsm-*.cdx.json wolfhsm-*.spdx.json
50+
51+
- name: Toolchain neutrality (gcc vs clang)
52+
run: |
53+
export SOURCE_DATE_EPOCH=1700000000
54+
export WOLFSSL_DIR=./wolfssl
55+
export WOLFHSM_CFG_DIR=test/config
56+
rm -f wolfhsm-*.cdx.json wolfhsm-*.spdx.json .sbom-wolfhsm-defines.h
57+
make sbom HOSTCC=gcc
58+
cp wolfhsm-*.cdx.json /tmp/gcc.cdx.json
59+
cp wolfhsm-*.spdx.json /tmp/gcc.spdx.json
60+
rm -f wolfhsm-*.cdx.json wolfhsm-*.spdx.json .sbom-wolfhsm-defines.h
61+
make sbom HOSTCC=clang
62+
cp wolfhsm-*.cdx.json /tmp/clang.cdx.json
63+
cp wolfhsm-*.spdx.json /tmp/clang.spdx.json
64+
if ! diff -u /tmp/gcc.cdx.json /tmp/clang.cdx.json \
65+
|| ! diff -u /tmp/gcc.spdx.json /tmp/clang.spdx.json; then
66+
echo "ERROR: gcc and clang produced different SBOMs." >&2
67+
echo "The SBOM must not depend on the host toolchain." >&2
68+
exit 1
69+
fi
70+
# Content sanity: empty --cflags captures also agree across
71+
# toolchains; refuse to call that a pass.
72+
python3 tools/sbom/validate_sbom.py --name-prefix wolfhsm \
73+
--min-properties 50 --require-dep-version wolfssl \
74+
/tmp/gcc.cdx.json /tmp/gcc.spdx.json
75+
echo "neutrality OK: gcc and clang SBOMs are byte-identical"
76+
77+
- name: Upload SBOM artifacts
78+
if: always()
79+
uses: actions/upload-artifact@v4
80+
with:
81+
name: wolfhsm-sboms
82+
path: |
83+
wolfhsm-*.cdx.json
84+
wolfhsm-*.spdx.json
85+
if-no-files-found: warn

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
.DS_Store
2+
# Test/benchmark object dirs. Negate tools/sbom/build/ — bare Build/
3+
# also matches that path on case-insensitive filesystems (macOS).
24
Build/
5+
!tools/sbom/build/
6+
!tools/sbom/build/**
37
*.o
48
*.a
59
*.la
@@ -24,3 +28,10 @@ scan_out/*
2428

2529
# Test output
2630
test-suite.log
31+
32+
# SBOM outputs and wolfGlass temp source lists
33+
*.cdx.json
34+
*.spdx.json
35+
*.spdx
36+
.*-wolfglass-srcs.txt
37+
.sbom-wolfhsm-defines.h

Makefile

Lines changed: 35 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -57,138 +57,40 @@ clean:
5757
make -C tools clean
5858
make -C examples clean
5959

60-
# ---- SBOM generation ----
61-
CC ?= cc
62-
WOLFSSL_DIR ?= ../wolfssl
63-
WOLFHSM_CFG_DIR ?= test/config
64-
PREFIX ?= /usr/local
65-
SBOM_INSTALL_DIR ?= $(PREFIX)/share/doc/wolfhsm
66-
SBOM_VALIDATE ?= yes
67-
SBOM_DEP_WOLFSSL ?= yes
68-
VERSION = $(shell sed -n 's/^. wolfHSM Release v//p' ChangeLog.md | head -1 | cut -d' ' -f1)
69-
SRCS := $(sort $(wildcard src/*.c))
70-
SBOM_CDX = wolfhsm-$(VERSION).cdx.json
71-
SBOM_SPDX = wolfhsm-$(VERSION).spdx.json
72-
SBOM_SPDX_TV = wolfhsm-$(VERSION).spdx
73-
74-
.PHONY: sbom install-sbom uninstall-sbom
75-
76-
sbom:
77-
@if [ -z "$(VERSION)" ]; then \
78-
echo "ERROR: could not parse version from ChangeLog.md." >&2; \
79-
exit 1; \
80-
fi
81-
@if [ -z "$(WOLFSSL_DIR)" ] || [ ! -d "$(WOLFSSL_DIR)" ]; then \
82-
echo "ERROR: WOLFSSL_DIR=$(WOLFSSL_DIR) is not a directory." >&2; \
83-
echo " Set WOLFSSL_DIR to your wolfssl source tree." >&2; \
84-
exit 1; \
85-
fi
86-
@if [ ! -f "$(WOLFSSL_DIR)/scripts/gen-sbom" ]; then \
87-
echo "ERROR: $(WOLFSSL_DIR)/scripts/gen-sbom not found." >&2; \
88-
echo " The sbom target needs a wolfSSL source tree that includes" >&2; \
89-
echo " scripts/gen-sbom (wolfSSL PR #10343, pending a future release)." >&2; \
90-
echo " Set WOLFSSL_DIR to such a tree." >&2; \
91-
exit 1; \
92-
fi
93-
@if [ ! -f "$(WOLFHSM_CFG_DIR)/wolfhsm_cfg.h" ]; then \
94-
echo "ERROR: $(WOLFHSM_CFG_DIR)/wolfhsm_cfg.h not found." >&2; \
95-
echo " Set WOLFHSM_CFG_DIR to the directory holding the" >&2; \
96-
echo " wolfhsm_cfg.h (and user_settings.h) your build uses." >&2; \
97-
exit 1; \
98-
fi
99-
@if [ "$(SBOM_VALIDATE)" != "no" ] && \
100-
! command -v pyspdxtools >/dev/null 2>&1; then \
101-
echo "ERROR: 'pyspdxtools' not found (pip install spdx-tools)." >&2; \
102-
echo " It validates the SPDX output and converts it to" >&2; \
103-
echo " tag-value. Set SBOM_VALIDATE=no to skip both." >&2; \
104-
exit 1; \
105-
fi
106-
@echo "wolfHSM version: $(VERSION)"
107-
@echo "Sources: $(words $(SRCS)) .c files in src/"
108-
@echo "Config: $(WOLFHSM_CFG_DIR)/wolfhsm_cfg.h"
109-
# Effective build config for the SBOM: preprocess wolfhsm/wh_settings.h with
110-
# the same defines and include path the test build compiles under, so the -dM
111-
# dump holds every WOLFHSM_CFG_* option (explicit and defaulted) plus the
112-
# wolfSSL options from user_settings.h — the configuration the library is
113-
# actually built with. Point WOLFHSM_CFG_DIR at the directory holding your
114-
# build's wolfhsm_cfg.h/user_settings.h for an integrator-accurate SBOM.
60+
# ---- SBOM generation (vendored wolfGlass driver) ----
61+
# Version comes from ChangeLog.md: there is no release version macro in the
62+
# public headers (WOLFHSM_CFG_INFOVERSION is a protocol info string, not the
63+
# product release). Parsing ChangeLog.md is a known fragility to fix later.
11564
#
116-
# ponytail: wh_settings.h pulls libc headers (stdint/stdio/strings/stdatomic),
117-
# so ~330 toolchain constants (INT16_MAX, ACCESSPERMS, ...) ride along into
118-
# the SBOM next to the ~175 real config macros. The dump is deliberately NOT
119-
# filtered here: a prefix allowlist would silently drop real options that
120-
# carry no standard prefix (GCM_TABLE_4BIT, FP_MAX_BITS, SINGLE_THREADED).
121-
# The durable fix belongs in gen-sbom's noise filter (wolfSSL PR #10343,
122-
# scripts/gen-sbom _NOISE_MACRO_RE), either of:
123-
# a) provenance filtering: accept a -dD dump and use its #line markers to
124-
# drop macros defined in system headers, or
125-
# b) an --options-baseline flag: subtract a second -dM dump made with the
126-
# same flags minus the -include, plus the libc headers it pulls.
127-
# Once gen-sbom grows that, this recipe needs no change — it already hands
128-
# over the full dump.
129-
@_defines=$$(mktemp "$${TMPDIR:-/tmp}/wolfhsm-defines.XXXXXX") && \
130-
trap 'rm -f "$$_defines"' 0 && \
131-
if ! $(CC) -dM -E -DWOLFHSM_CFG -DWOLFSSL_USER_SETTINGS \
65+
# Config capture MUST use SBOM_OPTIONS_H, not SBOM_CFLAGS. The driver's
66+
# --cflags path keeps only -D tokens and drops -I / -include, so feeding
67+
# `-include wolfhsm/wh_settings.h` via SBOM_CFLAGS produced an empty SBOM
68+
# (two raw -D flags). Capture the expanded header the same way PR #414 did.
69+
WOLFSSL_DIR ?= ../wolfssl
70+
HOSTCC ?= cc
71+
SBOM_NAME := wolfhsm
72+
SBOM_ROOT := $(CURDIR)
73+
SBOM_SRCS := $(sort $(wildcard src/*.c))
74+
SBOM_VERSION := $(shell sed -n 's/^. wolfHSM Release v//p' ChangeLog.md | head -1 | cut -d' ' -f1)
75+
SBOM_LICENSE_FILE := $(CURDIR)/LICENSING
76+
SBOM_DEP_WOLFSSL ?= yes
77+
SBOM_OPTIONS_H := $(CURDIR)/.sbom-wolfhsm-defines.h
78+
79+
ifeq ($(origin WOLFHSM_CFG_DIR),undefined)
80+
$(error WOLFHSM_CFG_DIR is required — point it at the directory \
81+
holding the wolfhsm_cfg.h/user_settings.h your build uses; do \
82+
not assume test/config is a release configuration)
83+
endif
84+
85+
include tools/sbom/build/sbom.mk
86+
87+
# Always re-capture: a stale dump would hide config changes. The driver's
88+
# --cflags path cannot do this — it drops -I/-include.
89+
.PHONY: sbom-defines
90+
sbom-defines:
91+
@echo "SBOM: capturing config via $(HOSTCC) -dM -E -include wolfhsm/wh_settings.h"
92+
@$(HOSTCC) -dM -E -DWOLFHSM_CFG -DWOLFSSL_USER_SETTINGS \
13293
-I. -I$(WOLFHSM_CFG_DIR) -I$(WOLFSSL_DIR) \
133-
-include wolfhsm/wh_settings.h -x c /dev/null >"$$_defines"; then \
134-
echo "ERROR: $(CC) -dM -E on wolfhsm/wh_settings.h failed." >&2; exit 1; \
135-
fi && \
136-
if ! command -v python3 >/dev/null 2>&1; then \
137-
echo "ERROR: python3 not found." >&2; exit 1; \
138-
fi && \
139-
if [ -z "$${SOURCE_DATE_EPOCH:-}" ] && \
140-
command -v git >/dev/null 2>&1 && \
141-
git rev-parse --git-dir >/dev/null 2>&1; then \
142-
sde=$$(git log -1 --format=%ct 2>/dev/null); \
143-
if [ -n "$$sde" ]; then \
144-
SOURCE_DATE_EPOCH="$$sde"; export SOURCE_DATE_EPOCH; \
145-
fi; \
146-
fi && \
147-
dep_args=""; \
148-
if [ "$(SBOM_DEP_WOLFSSL)" != "no" ]; then \
149-
if python3 $(WOLFSSL_DIR)/scripts/gen-sbom --help 2>/dev/null \
150-
| grep -q -- '--dep-wolfssl'; then \
151-
dep_args="--dep-wolfssl yes"; \
152-
wv=$$(sed -n 's/.*LIBWOLFSSL_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \
153-
"$(WOLFSSL_DIR)/wolfssl/version.h" 2>/dev/null); \
154-
if [ -n "$$wv" ]; then \
155-
dep_args="$$dep_args --dep-version wolfssl=$$wv"; \
156-
fi; \
157-
else \
158-
echo "NOTE: this gen-sbom lacks --dep-wolfssl; wolfssl will not"; \
159-
echo " be listed as a dependency component in the SBOM."; \
160-
fi; \
161-
fi && \
162-
python3 $(WOLFSSL_DIR)/scripts/gen-sbom \
163-
--name wolfhsm \
164-
--version $(VERSION) \
165-
--supplier "wolfSSL Inc." \
166-
--license-file LICENSING \
167-
--options-h "$$_defines" \
168-
--srcs $(SRCS) \
169-
$$dep_args \
170-
--cdx-out $(SBOM_CDX) \
171-
--spdx-out $(SBOM_SPDX) && \
172-
if [ "$(SBOM_VALIDATE)" != "no" ]; then \
173-
pyspdxtools --infile $(SBOM_SPDX) --outfile $(SBOM_SPDX_TV) && \
174-
echo "Done: $(SBOM_CDX) $(SBOM_SPDX) $(SBOM_SPDX_TV)"; \
175-
else \
176-
echo "Done: $(SBOM_CDX) $(SBOM_SPDX)"; \
177-
fi
178-
179-
# SBOM install is opt-in (`make install-sbom`), matching the family
180-
# convention in wolfssl's scripts/sbom.am: plain `make` never installs
181-
# SBOM files. wolfHSM's root Makefile has no install/uninstall targets
182-
# to hook, so uninstall-sbom is standalone rather than chained.
183-
install-sbom: sbom
184-
@mkdir -p $(DESTDIR)$(SBOM_INSTALL_DIR)
185-
install -m 0644 $(SBOM_CDX) $(DESTDIR)$(SBOM_INSTALL_DIR)/
186-
install -m 0644 $(SBOM_SPDX) $(DESTDIR)$(SBOM_INSTALL_DIR)/
187-
@if [ -f "$(SBOM_SPDX_TV)" ]; then \
188-
install -m 0644 $(SBOM_SPDX_TV) $(DESTDIR)$(SBOM_INSTALL_DIR)/; \
189-
fi
190-
191-
uninstall-sbom:
192-
-rm -f $(DESTDIR)$(SBOM_INSTALL_DIR)/$(SBOM_CDX) \
193-
$(DESTDIR)$(SBOM_INSTALL_DIR)/$(SBOM_SPDX) \
194-
$(DESTDIR)$(SBOM_INSTALL_DIR)/$(SBOM_SPDX_TV)
94+
-include wolfhsm/wh_settings.h -x c /dev/null > $(SBOM_OPTIONS_H)
95+
96+
sbom: sbom-defines

README.md

Lines changed: 4 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -28,66 +28,7 @@ please refer to the following resources.
2828

2929
## SBOM / EU CRA Compliance
3030

31-
wolfHSM generates a Software Bill of Materials (SBOM) in CycloneDX 1.6 and
32-
SPDX 2.3 formats to support compliance with the EU Cyber Resilience Act (CRA).
33-
34-
Generate both SBOMs with the `sbom` Makefile target:
35-
36-
```sh
37-
make sbom WOLFSSL_DIR=../wolfssl
38-
```
39-
40-
This parses the version from `ChangeLog.md`, collects `src/*.c`, and writes
41-
`wolfhsm-<version>.cdx.json` and `wolfhsm-<version>.spdx.json`, then
42-
validates the SPDX output with `pyspdxtools` and converts it to tag-value
43-
`wolfhsm-<version>.spdx` (skip both with `SBOM_VALIDATE=no`). The SBOM
44-
lists wolfSSL as a dependency component with its version read from
45-
`WOLFSSL_DIR` (disable with `SBOM_DEP_WOLFSSL=no` for a
46-
`WOLFHSM_CFG_NO_CRYPTO` build). Output is reproducible: `SOURCE_DATE_EPOCH`
47-
is honoured and defaults to the last git commit time.
48-
49-
Install the generated files under `$(PREFIX)/share/doc/wolfhsm` (default
50-
`PREFIX=/usr/local`) with `make install-sbom`; remove them with
51-
`make uninstall-sbom`. Plain `make` never generates or installs SBOM files.
52-
53-
The SBOM records the build configuration by preprocessing
54-
`wolfhsm/wh_settings.h` against a config directory. `WOLFHSM_CFG_DIR`
55-
(default: `test/config`) selects the directory holding the `wolfhsm_cfg.h`
56-
and `user_settings.h` your build uses — point it at your port's config so the
57-
recorded `WOLFHSM_CFG_*` and wolfSSL options match the library you ship:
58-
59-
```sh
60-
make sbom WOLFSSL_DIR=../wolfssl WOLFHSM_CFG_DIR=path/to/your/config
61-
```
62-
63-
Note: alongside the real config macros, the dump currently includes libc
64-
constants pulled in by `wh_settings.h`'s system includes; see the comment on
65-
the `sbom` target in `Makefile` for why they are not filtered here and what
66-
the planned gen-sbom fix is.
67-
68-
`WOLFSSL_DIR` must point to a wolfssl source tree containing `scripts/gen-sbom`,
69-
which ships in wolfSSL PR #10343 (pending a future wolfSSL release). If the
70-
script is absent the target fails with a message telling you what is missing.
71-
72-
Requires `python3`, plus `pyspdxtools` (`pip install spdx-tools`) unless
73-
`SBOM_VALIDATE=no`.
74-
75-
To invoke `gen-sbom` directly instead of through the target, run the same
76-
command it runs:
77-
78-
```sh
79-
cc -dM -E -DWOLFHSM_CFG -DWOLFSSL_USER_SETTINGS \
80-
-I. -Itest/config -I$WOLFSSL_DIR \
81-
-include wolfhsm/wh_settings.h -x c /dev/null > wolfhsm-defines.h
82-
python3 $WOLFSSL_DIR/scripts/gen-sbom \
83-
--name wolfhsm \
84-
--version $(sed -n 's/^# wolfHSM Release v\([0-9][0-9.]*\).*/\1/p' ChangeLog.md | head -1) \
85-
--supplier "wolfSSL Inc." \
86-
--license-file LICENSING \
87-
--options-h wolfhsm-defines.h \
88-
--srcs src/*.c \
89-
--cdx-out wolfhsm.cdx.json \
90-
--spdx-out wolfhsm.spdx.json
91-
```
92-
93-
For further CRA guidance see [wolfssl/doc/CRA.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/CRA.md).
31+
- Generate a CycloneDX 1.6 + SPDX 2.3 SBOM for the core library with
32+
`make sbom WOLFSSL_DIR=../wolfssl WOLFHSM_CFG_DIR=/path/to/your/config`
33+
(`WOLFHSM_CFG_DIR` is required). See [docs/SBOM.md](docs/SBOM.md) for
34+
coverage, limitations, and CRA notes.

0 commit comments

Comments
 (0)