Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions .github/workflows/test-sbom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: wolfHSM SBOM Canary

# Core-target only for this pass. posix-server / posix-client / whnvmtool
# SBOM targets are tracked as follow-up and are not exercised here.
#
# Guards:
# * make sbom with an explicit WOLFHSM_CFG_DIR
# * schema-valid CycloneDX 1.6 + SPDX 2.3 whose top-level component is wolfhsm
# * toolchain neutrality: HOSTCC=gcc vs HOSTCC=clang must be byte-identical
#
# No Make-vs-CMake step: wolfHSM has no CMake build.

on:
push:
branches: [ 'master', 'main', 'release/**' ]
pull_request:
branches: [ '*' ]

jobs:
sbom_canary:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- uses: actions/checkout@v4

- name: Trust workspace
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: Checkout wolfssl (sibling for include path)
uses: actions/checkout@v4
with:
repository: wolfSSL/wolfssl
path: wolfssl

- name: Install tooling
run: |
sudo apt-get update
sudo apt-get install -y python3 build-essential clang

- name: Verify vendored gen-sbom
run: test -f tools/sbom/gen-sbom

- name: make sbom (core)
run: |
WOLFSSL_DIR=./wolfssl WOLFHSM_CFG_DIR=test/config make sbom
python3 tools/sbom/validate_sbom.py --name-prefix wolfhsm \
--min-properties 50 --require-dep-version wolfssl \
wolfhsm-*.cdx.json wolfhsm-*.spdx.json

- name: Toolchain neutrality (gcc vs clang)
run: |
export SOURCE_DATE_EPOCH=1700000000
export WOLFSSL_DIR=./wolfssl
export WOLFHSM_CFG_DIR=test/config
rm -f wolfhsm-*.cdx.json wolfhsm-*.spdx.json .sbom-wolfhsm-defines.h
make sbom HOSTCC=gcc
cp wolfhsm-*.cdx.json /tmp/gcc.cdx.json
cp wolfhsm-*.spdx.json /tmp/gcc.spdx.json
rm -f wolfhsm-*.cdx.json wolfhsm-*.spdx.json .sbom-wolfhsm-defines.h
make sbom HOSTCC=clang
cp wolfhsm-*.cdx.json /tmp/clang.cdx.json
cp wolfhsm-*.spdx.json /tmp/clang.spdx.json
if ! diff -u /tmp/gcc.cdx.json /tmp/clang.cdx.json \
|| ! diff -u /tmp/gcc.spdx.json /tmp/clang.spdx.json; then
echo "ERROR: gcc and clang produced different SBOMs." >&2
echo "The SBOM must not depend on the host toolchain." >&2
exit 1
fi
# Content sanity: empty --cflags captures also agree across
# toolchains; refuse to call that a pass.
python3 tools/sbom/validate_sbom.py --name-prefix wolfhsm \
--min-properties 50 --require-dep-version wolfssl \
/tmp/gcc.cdx.json /tmp/gcc.spdx.json
echo "neutrality OK: gcc and clang SBOMs are byte-identical"

- name: Upload SBOM artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: wolfhsm-sboms
path: |
wolfhsm-*.cdx.json
wolfhsm-*.spdx.json
if-no-files-found: warn
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
.DS_Store
# Test/benchmark object dirs. Negate tools/sbom/build/ — bare Build/
# also matches that path on case-insensitive filesystems (macOS).
Build/
!tools/sbom/build/
!tools/sbom/build/**
*.o
*.a
*.la
Expand All @@ -24,3 +28,10 @@ scan_out/*

# Test output
test-suite.log

# SBOM outputs and wolfGlass temp source lists
*.cdx.json
*.spdx.json
*.spdx
.*-wolfglass-srcs.txt
.sbom-wolfhsm-defines.h
67 changes: 67 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,70 @@ clean:
make -C benchmark clean
make -C tools clean
make -C examples clean

# ---- SBOM generation (vendored wolfGlass driver) ----
# Version comes from ChangeLog.md: there is no release version macro in the
# public headers (WOLFHSM_CFG_INFOVERSION is a protocol info string, not the
# product release). Parsing ChangeLog.md is a known fragility to fix later.
#
# Config capture MUST use SBOM_OPTIONS_H, not SBOM_CFLAGS. The driver's
# --cflags path keeps only -D tokens and drops -I / -include, so feeding
# `-include wolfhsm/wh_settings.h` via SBOM_CFLAGS produced an empty SBOM
# (two raw -D flags). Capture the expanded header the same way PR #414 did.
WOLFSSL_DIR ?= ../wolfssl
HOSTCC ?= cc
SBOM_NAME := wolfhsm
SBOM_ROOT := $(CURDIR)
SBOM_SRCS := $(sort $(wildcard src/*.c))
SBOM_VERSION := $(shell sed -n 's/^. wolfHSM Release v//p' ChangeLog.md | head -1 | cut -d' ' -f1)
SBOM_LICENSE_FILE := $(CURDIR)/LICENSING
SBOM_DEP_WOLFSSL ?= yes
SBOM_OPTIONS_H := $(CURDIR)/.sbom-wolfhsm-defines.h

# Enforce only when an SBOM goal was actually requested. A bare $(error) here
# is evaluated while the Makefile is read, so it aborts every goal in the tree
# — `make scan`, `make clean`, `make test` — not just the SBOM ones.
ifneq (,$(filter sbom sbom-defines,$(MAKECMDGOALS)))
ifeq ($(origin WOLFHSM_CFG_DIR),undefined)
$(error WOLFHSM_CFG_DIR is required — point it at the directory \
holding the wolfhsm_cfg.h/user_settings.h your build uses; do \
not assume test/config is a release configuration)
endif
endif

include tools/sbom/build/sbom.mk

# Keep only project configuration macros. A raw -dM dump also carries the host
# compiler's own builtins, and gen-sbom's scrub drops __-prefixed names but not
# system macros whose *values* name them (ATOMIC_BOOL_LOCK_FREE expands to
# __GCC_ATOMIC_BOOL_LOCK_FREE under gcc, __CLANG_ATOMIC_BOOL_LOCK_FREE under
# clang), which made the SBOM toolchain-dependent.
#
# Object-like macros only: the trailing [[:space:]] excludes function-like ones,
# whose bodies differ cosmetically between the two preprocessors ("idx ##VAR"
# vs "idx##VAR"). A macro body is an implementation detail, not build config.
#
# The \# is required: an unescaped # would start a Make comment and silently
# truncate this pattern to "^", which matches every line and filters nothing.
SBOM_DEFINE_RE := ^\#define (WOLFHSM|WOLFSSL|WOLFCRYPT|WOLF|WC_|HAVE_|NO_|OPENSSL|USE_|SIZEOF_|FP_|TFM_|SP_|ECC_|RSA_|AES_|SHA|CURVE|ED25519|ED448|HKDF|KEEP_|SMALL_|BIG_|NDEBUG|DEBUG)[A-Za-z0-9_]*[[:space:]]

# Always re-capture: a stale dump would hide config changes. The driver's
# --cflags path cannot do this — it drops -I/-include.
.PHONY: sbom-defines
sbom-defines:
@echo "SBOM: capturing config via $(HOSTCC) -dM -E -include wolfhsm/wh_settings.h"
@$(HOSTCC) -dM -E -DWOLFHSM_CFG -DWOLFSSL_USER_SETTINGS \
-I. -I$(WOLFHSM_CFG_DIR) -I$(WOLFSSL_DIR) \
-include wolfhsm/wh_settings.h -x c /dev/null \
| LC_ALL=C grep -E '$(SBOM_DEFINE_RE)' \
| LC_ALL=C sort > $(SBOM_OPTIONS_H)
@if [ ! -s $(SBOM_OPTIONS_H) ]; then \
echo "ERROR: captured no configuration macros from" >&2; \
echo " WOLFHSM_CFG_DIR=$(WOLFHSM_CFG_DIR)" >&2; \
echo " Check that it holds the wolfhsm_cfg.h/user_settings.h" >&2; \
echo " your build uses." >&2; \
rm -f $(SBOM_OPTIONS_H); \
exit 1; \
fi

sbom: sbom-defines
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ please refer to the following resources.
- [wolfHSM Manual](https://www.wolfssl.com/documentation/manuals/wolfhsm/index.html)
- [wolfHSM API Reference](https://www.wolfssl.com/documentation/manuals/wolfhsm/appendix01.html)
- [wolfHSM Examples](https://github.com/wolfSSL/wolfHSM/tree/main/examples)
- [wolfHSM SBOM Generation](docs/SBOM.md) — `make sbom`, CycloneDX/SPDX output, and EU CRA notes
71 changes: 71 additions & 0 deletions docs/SBOM-FOLLOWUPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# SBOM follow-up issues (drafts)

These were prepared for `wolfSSL/wolfHSM` as Step 8 of the wolfGlass
migration. They were not filed automatically — create them when ready
(for example with `gh issue create --repo wolfSSL/wolfHSM`).

---

## 1. Add wolfGlass sbom target to examples/posix/wh_posix_server/Makefile

Follow-up from the wolfGlass SBOM migration (PR #414 lineage): add a
`sbom` Make target to `examples/posix/wh_posix_server/Makefile` using
the vendored `tools/sbom/build/sbom.mk` driver (same pattern as the
core root `Makefile`).

Core-only pass covers `src/*.c` via root `make sbom`. posix-server is
intentionally not marked ✅ until both (a) the target runs locally and
(b) CI covers it.

See `docs/SBOM.md` coverage table.

---

## 2. Add wolfGlass sbom target to examples/posix/wh_posix_client/Makefile

Same as (1) for `examples/posix/wh_posix_client/Makefile`.

---

## 3. Add wolfGlass sbom target to tools/whnvmtool/Makefile

Same as (1) for `tools/whnvmtool/Makefile`.

---

## 4. Confirm whether restricted vendor ports need out-of-tree SBOM support

Ask port owners directly (do not infer from public stubs):

- `port/infineon/tc3xx`
- `port/renesas/rh850f1km`
- `port/stmicro/spc58nn`
- `port/stmicro/SR6`
- `port/microchip/pic32cz`
- `port/ti/tda4vh`

Build files are not public. If a private port has a real build, it may
still need an out-of-tree wolfGlass front end. Owners: reply yes/no +
front end (Make / CMake / IAR / compdb / other) per port.

---

## 5. Replace the SBOM_DEFINE_RE prefix allowlist with a sourced macro list

`SBOM_DEFINE_RE` in the root `Makefile` filters the `-dM` dump to macro
names matching a hand-maintained prefix allowlist. This is what makes the
SBOM toolchain-neutral (a raw dump embeds `__GCC_*` vs `__CLANG_*`), but it
has two known costs:

* A future configuration macro with a novel prefix is dropped silently.
The `--min-properties 50` canary threshold will not notice one missing
macro.
* Alignment/codegen helpers that happen to match (`ALIGN16`,
`ASSERT_SAVED_VECTOR_REGISTERS`) are excluded by design — they are code
helpers, not build configuration — but that boundary is a judgement call
encoded in a regex rather than declared anywhere.

Better: derive the recorded set from the settings headers themselves
(the macros `wh_settings.h` and `user_settings.h` actually define), so the
list tracks the configuration surface instead of a prefix guess. This
likely belongs in the shared wolfGlass driver, not per-product Makefiles.
113 changes: 113 additions & 0 deletions docs/SBOM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# wolfHSM SBOM Generation

wolfHSM can emit a Software Bill of Materials (SBOM) in **CycloneDX 1.6** and
**SPDX 2.3** JSON. An SBOM is one of the software-transparency artifacts useful
towards EU Cyber Resilience Act (CRA) obligations; it does not by itself make a
product CRA compliant.

## One engine

There is a single SBOM engine. The Make front end feeds it the source list and
the build configuration, then calls the vendored wolfGlass driver:

```
make sbom ─► tools/sbom/sbom-driver ─► tools/sbom/gen-sbom ─► *.cdx.json + *.spdx.json
```

| File | Role |
| --- | --- |
| `tools/sbom/sbom-driver` | Vendored wolfGlass driver (srcs + config → gen-sbom). |
| `tools/sbom/gen-sbom` | Vendored SBOM generator. |
| `tools/sbom/build/sbom.mk` | Shared plain-Make fragment. |
| `tools/sbom/validate_sbom.py` | Structural sanity check used by CI. |

## Prerequisites

* `python3`
* A host C compiler (`HOSTCC`, default `cc`)
* The vendored wolfGlass SBOM set under `tools/sbom/`
* `WOLFHSM_CFG_DIR` — **required**. Point it at the directory that holds the
`wolfhsm_cfg.h` and `user_settings.h` your build uses. There is no default;
`test/config` is a test harness config, not a release configuration.
* `WOLFSSL_DIR` — path to a wolfssl source tree used on the include path
(default `../wolfssl`)

## Generate the core SBOM

```sh
make sbom WOLFSSL_DIR=../wolfssl WOLFHSM_CFG_DIR=/path/to/your/config
```

Outputs: `wolfhsm-<version>.cdx.json` and `wolfhsm-<version>.spdx.json`.

The Make target first runs `$(HOSTCC) -dM -E ... -include wolfhsm/wh_settings.h`
and feeds that dump to the driver as `--options-h` (`SBOM_OPTIONS_H`). Do
**not** use `SBOM_CFLAGS` for this product: the driver's `--cflags` path keeps
only `-D` tokens and drops `-I` / `-include`, which yields an empty config
record (two raw defines) instead of the real `WOLFHSM_CFG_*` / wolfSSL option
set.

That dump is filtered to project configuration macros (`SBOM_DEFINE_RE` in the
root `Makefile`) before it reaches the driver, and only object-like macros are
kept. A raw `-dM` dump also carries the host compiler's own builtins, which
makes the SBOM depend on whether you built with gcc or clang — the SBOM records
*your build configuration*, not your compiler's internals. The
`make sbom HOSTCC=gcc` vs `HOSTCC=clang` byte-identity check in
`.github/workflows/test-sbom.yml` guards this.

Version is parsed from `ChangeLog.md` (for example `# wolfHSM Release v1.4.0`).
There is no product release version macro in the public headers today
(`WOLFHSM_CFG_INFOVERSION` is a protocol info string). Relying on ChangeLog
parsing is a known fragility to replace with a header macro later.

Useful overrides: `HOSTCC`, `SBOM_GEN`, `CRA_PYTHON`, `SBOM_DEP_WOLFSSL=no`
(for a `WOLFHSM_CFG_NO_CRYPTO` build). With `SBOM_DEP_WOLFSSL=yes` (default),
`sbom.mk` reads `$(WOLFSSL_DIR)/wolfssl/version.h` for the dependency version.

Validate locally (also guards empty captures and missing dep versions):

```sh
python3 tools/sbom/validate_sbom.py --name-prefix wolfhsm \
--min-properties 50 --require-dep-version wolfssl \
wolfhsm-*.cdx.json wolfhsm-*.spdx.json
```

## Coverage

| Target | Status | How | Notes |
| --- | --- | --- | --- |
| Core library (`src/*.c`) | Covered | `make sbom` | Config via `WOLFHSM_CFG_DIR` + `wh_settings.h` |
| `examples/posix/wh_posix_server` | **Not yet** | follow-up | Needs its own `sbom` target in that Makefile |
| `examples/posix/wh_posix_client` | **Not yet** | follow-up | Needs its own `sbom` target in that Makefile |
| `tools/whnvmtool` | **Not yet** | follow-up | Needs its own `sbom` target in that Makefile |
| `port/posix` (HAL sources alone) | **Not yet** | follow-up | May be folded into posix example targets |
| `port/infineon/tc3xx` | Out of scope | — | Restricted vendor stub; build files not public |
| `port/renesas/rh850f1km` | Out of scope | — | Restricted vendor stub; build files not public |
| `port/stmicro/spc58nn` | Out of scope | — | Restricted vendor stub; build files not public |
| `port/stmicro/SR6` | Out of scope | — | Restricted vendor stub; build files not public |
| `port/microchip/pic32cz` | Out of scope | — | Restricted vendor stub; build files not public |
| `port/ti/tda4vh` | Out of scope | — | Restricted vendor stub; build files not public |

Restricted ports: ask port owners whether private IDE/build tooling should get
out-of-tree SBOM support. Do not infer from the public stubs. Draft issue text
for the three Makefile follow-ups and the port-owner question lives in
[SBOM-FOLLOWUPS.md](SBOM-FOLLOWUPS.md) (not filed yet).

wolfHSM has no CMake, autotools, or public IAR/compdb front end in this tree.
Do not invent a CMake SBOM path for parity with other products.

## CI

`.github/workflows/test-sbom.yml` runs the core canary: `make sbom` against
`test/config`, validates both documents with `--min-properties 50` and
`--require-dep-version wolfssl` (so an empty `--cflags` capture cannot pass),
and checks toolchain neutrality (`HOSTCC=gcc` vs `HOSTCC=clang` with a fixed
`SOURCE_DATE_EPOCH`).

A coverage cell is only ✅ when (a) the target runs locally and (b) CI is green
for it. Do not mark ✅ on “should work” alone.

## Further reading

* [wolfssl/doc/CRA.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/CRA.md)
* Vendored toolkit pin: `tools/sbom/VERSION` and `tools/sbom/.wolfglass-rev`
1 change: 1 addition & 0 deletions tools/sbom/.wolfglass-rev
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9bdf5b7133c603659ea34b9a552c0bf2a962a8c0
Loading
Loading