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
63 changes: 33 additions & 30 deletions .github/workflows/test-sbom.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Wolfboot SBOM Canary

# Exercises every wolfBoot SBOM route so a change to the build system, the
# vendored wolfGlass tooling under tools/sbom/, or an IDE extractor cannot
# silently break SBOM generation:
# vendored wolfssl-compliance-tooling files under tools/sbom/, or an IDE
# extractor cannot silently break SBOM generation:
#
# * Make path (methods 1-4) -> make sbom TARGET=sim
# * CMake path (method 5) -> cmake --build --target sbom
Expand Down Expand Up @@ -32,8 +32,9 @@ name: Wolfboot SBOM Canary
# driver never calls the cross compiler, so the SBOM is produced for a target
# whose toolchain is absent.
#
# The wolfglass_drift job holds tools/sbom/ to the revision it claims, so the
# vendored copy cannot drift from wolfGlass unnoticed.
# The compliance_tooling_drift job holds tools/sbom/ to the revision it
# claims, so the vendored copy cannot drift from wolfssl-compliance-tooling
# unnoticed.

on:
push:
Expand Down Expand Up @@ -338,56 +339,58 @@ jobs:
wolfboot-*.spdx.json
if-no-files-found: warn

# tools/sbom/ is a vendored copy of the wolfGlass share/ set, pinned by
# tools/sbom/.wolfglass-rev. Nothing else proves the copy still matches the
# pin, so a stale copy, a local patch, or an unresolvable revision would all
# pass unnoticed and wolfBoot would ship a different generator from the one
# wolfSSL controls. wolfGlass owns the comparison: tools/wolfglass-sync
# --check diffs share/ against the vendored tree and compares HEAD against
# .wolfglass-rev.
wolfglass_drift:
# tools/sbom/ is a vendored copy of the wolfssl-compliance-tooling share/
# set, pinned by tools/sbom/.wolfssl-compliance-tooling-rev. Nothing else
# proves the copy still matches the pin, so a stale copy, a local patch, or
# an unresolvable revision would all pass unnoticed and wolfBoot would ship
# a different generator from the one wolfSSL controls.
# wolfssl-compliance-tooling owns the comparison:
# tools/wolfssl-compliance-tooling-sync --check diffs share/ against the
# vendored tree and compares HEAD against .wolfssl-compliance-tooling-rev.
compliance_tooling_drift:
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- uses: actions/checkout@v4

- name: Read the pinned wolfGlass revision
- name: Read the pinned wolfssl-compliance-tooling revision
id: pin
run: |
rev="$(tr -d '[:space:]' < tools/sbom/.wolfglass-rev)"
rev="$(tr -d '[:space:]' < tools/sbom/.wolfssl-compliance-tooling-rev)"
if [ -z "$rev" ]; then
echo "ERROR: tools/sbom/.wolfglass-rev is empty." >&2
echo "ERROR: tools/sbom/.wolfssl-compliance-tooling-rev is empty." >&2
exit 1
fi
echo "rev=$rev" >> "$GITHUB_OUTPUT"
echo "Pinned wolfGlass revision: $rev"
echo "Pinned wolfssl-compliance-tooling revision: $rev"

# wolfGlass is public, so this needs no token and the check also runs on a
# fork PR. continue-on-error only defers an unresolvable pin to the step
# below, which can say what went wrong; it never skips the comparison.
- name: Check out wolfGlass at the pinned revision
# wolfssl-compliance-tooling is public, so this needs no token and the
# check also runs on a fork PR. continue-on-error only defers an
# unresolvable pin to the step below, which can say what went wrong; it
# never skips the comparison.
- name: Check out wolfssl-compliance-tooling at the pinned revision
id: fetch
continue-on-error: true
uses: actions/checkout@v4
with:
repository: wolfSSL/wolfGlass
repository: wolfSSL/wolfssl-compliance-tooling
ref: ${{ steps.pin.outputs.rev }}
path: .wolfglass-src
path: .wolfssl-compliance-tooling-src

- name: Drift check against wolfGlass
- name: Drift check against wolfssl-compliance-tooling
env:
PINNED_REV: ${{ steps.pin.outputs.rev }}
run: |
if [ ! -d .wolfglass-src/share ]; then
echo "ERROR: wolfGlass revision $PINNED_REV does not resolve." >&2
echo "tools/sbom/.wolfglass-rev must name a commit that exists in" >&2
echo "wolfSSL/wolfGlass. Re-run tools/wolfglass-sync to re-pin." >&2
if [ ! -d .wolfssl-compliance-tooling-src/share ]; then
echo "ERROR: wolfssl-compliance-tooling revision $PINNED_REV does not resolve." >&2
echo "tools/sbom/.wolfssl-compliance-tooling-rev must name a commit that exists in" >&2
echo "wolfSSL/wolfssl-compliance-tooling. Re-run tools/wolfssl-compliance-tooling-sync to re-pin." >&2
exit 1
fi
git config --global --add safe.directory "$GITHUB_WORKSPACE/.wolfglass-src"
python3 .wolfglass-src/tools/wolfglass-sync \
--check --src .wolfglass-src --dest .
git config --global --add safe.directory "$GITHUB_WORKSPACE/.wolfssl-compliance-tooling-src"
python3 .wolfssl-compliance-tooling-src/tools/wolfssl-compliance-tooling-sync \
--check --src .wolfssl-compliance-tooling-src --dest .

windows_sbom:
runs-on: windows-latest
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ language.settings.xml
/**/build
/**/build-**

# Exception: vendored wolfGlass SBOM integration sources.
# Exception: vendored wolfssl-compliance-tooling SBOM integration sources.
# This directory contains tracked Make/CMake fragments, not generated output.
!/tools/sbom/build/
!/tools/sbom/build/**
Expand Down
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -1065,7 +1065,8 @@ pico-sdk-info: FORCE
# targets that also have an arch.mk path (TI Hercules, Renesas RX, Zynq). It
# extracts the configuration-specific source list from OBJS (fully assembled by
# this point: core wolfBoot + wolfcrypt + HAL) and passes it, together with the
# build CFLAGS, to the vendored wolfGlass driver under tools/sbom/.
# build CFLAGS, to the vendored wolfssl-compliance-tooling driver under
# tools/sbom/.
#
# wolfcrypt sources are compiled directly into the wolfBoot image. They stay
# in the source-set hash, and are also declared as a nested wolfcrypt
Expand Down Expand Up @@ -1181,7 +1182,7 @@ SBOM_HAL_LICENSE_OVERRIDE:=$(SBOM_LICENSE_OVERRIDE)
SBOM_HAL_CDX_OUT:=wolfboot-hal-$(TARGET)-$(WOLFBOOT_VERSION).cdx.json
SBOM_HAL_SPDX_OUT:=wolfboot-hal-$(TARGET)-$(WOLFBOOT_VERSION).spdx.json
SBOM_HAL_GEN:=$(SBOM_GEN)
$(eval $(call wolfglass_sbom_rule,sbom-hal,SBOM_HAL_))
$(eval $(call wolfssl_compliance_tooling_sbom_rule,sbom-hal,SBOM_HAL_))

FORCE:

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ For Visual Studio, the developer command prompt will need to be activated.
* Multiboot2: u32 request-list entries per spec v2.0, zero entries as padding, end-tag termination
* Signing tools: file-backed custom TLV, custom TLV size limit raised to UINT16_MAX, --custom-tlv-pubkey-der, auxiliary algorithms and cert chain/TPM usage, removed the 14-argument limit
* wolfHSM: exposed max verify roots in options.mk, target-independent client build
* SBOM: vendored wolfGlass tooling, SBOMs from every build system and per-target config, CI drift check, registered wolfBoot CPE, identification of the embedded wolfSSL
* SBOM: vendored wolfssl-compliance-tooling files, SBOMs from every build system and per-target config, CI drift check, registered wolfBoot CPE, identification of the embedded wolfSSL
* Removed the obsolete python keytools; all users converted to C
* Added CONTRIBUTING.md covering the contributor agreement and PR process
* Bug fixes and hardening
Expand Down
5 changes: 3 additions & 2 deletions cmake/sbom.cmake
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# cmake/sbom.cmake - wolfBoot wrapper around the vendored wolfGlass CMake helper.
# cmake/sbom.cmake - wolfBoot wrapper around the vendored
# wolfssl-compliance-tooling CMake helper.

if(NOT DEFINED WOLFBOOT_ROOT)
set(WOLFBOOT_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
Expand Down Expand Up @@ -113,4 +114,4 @@ if(DEFINED HOSTCC AND NOT HOSTCC STREQUAL "")
list(APPEND _sbom_args HOSTCC ${HOSTCC})
endif()

wolfglass_add_sbom(${_sbom_args})
wolfssl_compliance_tooling_add_sbom(${_sbom_args})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new helper call also adds install-sbom and uninstall-sbom, but nothing in test-sbom.yml runs them. Could the CMake step run DESTDIR=$PWD/stage cmake --build build-sim --target install-sbom, check both documents land under the stage dir, then run uninstall-sbom and check they're gone?

14 changes: 7 additions & 7 deletions docs/SBOM.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The pieces:

| File | Role |
| --- | --- |
| `tools/sbom/sbom-driver` | Vendored wolfGlass driver (srcs + config → gen-sbom). |
| `tools/sbom/sbom-driver` | Vendored wolfssl-compliance-tooling driver (srcs + config → gen-sbom). |
| `tools/sbom/gen-sbom` | Vendored SBOM generator. |
| `cmake/sbom.cmake` | wolfBoot wrapper around the vendored CMake helper. |
| `tools/sbom/frontends/iar_sbom.py` | Extracts srcs + defines from an IAR `.ewp`. |
Expand All @@ -44,7 +44,7 @@ The pieces:
* `python3`
* A host C compiler. The default is `cc`. To use a different compiler, set
`HOSTCC=...`.
* The vendored wolfGlass SBOM set under `tools/sbom/`.
* The vendored wolfssl-compliance-tooling SBOM set under `tools/sbom/`.

## Limitations

Expand All @@ -64,7 +64,7 @@ Obey these limitations when you make an SBOM.
into the wolfBoot source set, so an advisory against one of those products
does not match the document. The captured configuration does record the
macros (`WOLFBOOT_TPM`, and the equivalents), so the build is still visible.
wolfGlass owns the component table, and this limitation goes away when it
wolfssl-compliance-tooling owns the component table, and this limitation goes away when it
gains the other entries.
- The document records the source set, not each file. There is no per-file
hash, and SPDX reports `filesAnalyzed: false`. Use the source-set Merkle
Expand Down Expand Up @@ -389,11 +389,11 @@ sim configuration and runs a native-Windows scrub test against
artifacts.

The same workflow holds `tools/sbom/` to the revision in
`tools/sbom/.wolfglass-rev`. It checks wolfGlass out at that revision and runs
`tools/wolfglass-sync --check`, which fails if a vendored file differs from
wolfGlass `share/`, or if the pinned revision does not resolve. A local patch to
`tools/sbom/.wolfssl-compliance-tooling-rev`. It checks wolfssl-compliance-tooling out at that revision and runs
`tools/wolfssl-compliance-tooling-sync --check`, which fails if a vendored file differs from
wolfssl-compliance-tooling `share/`, or if the pinned revision does not resolve. A local patch to
the vendored tooling therefore breaks CI instead of shipping quietly: fix
wolfGlass and re-vendor.
wolfssl-compliance-tooling and re-vendor.

## Reproducibility

Expand Down
1 change: 0 additions & 1 deletion tools/sbom/.wolfglass-rev

This file was deleted.

1 change: 1 addition & 0 deletions tools/sbom/.wolfssl-compliance-tooling-rev
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8a96c64f9dc022fa136682a18f23860196de8610
49 changes: 36 additions & 13 deletions tools/sbom/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# share/

This is the only vendorable set. Use `tools/wolfglass-sync` to copy these files
This is the only vendorable set. Use `tools/wolfssl-compliance-tooling-sync` to copy these files
into a product at `tools/sbom/`, together with the pin files (`VERSION` and
`.wolfglass-rev`). Do not copy the `share/` folder name; copy the files.
`.wolfssl-compliance-tooling-rev`). Do not copy the `share/` folder name; copy the files.

## Contents

Expand All @@ -13,9 +13,10 @@ into a product at `tools/sbom/`, together with the pin files (`VERSION` and
| `validate_sbom.py` | Structural validator for CI (`--name-prefix`). |
| `frontends/compdb_sbom.py` | Extractor for any `compile_commands.json`. |
| `frontends/iar_sbom.py` | Extractor for an IAR Embedded Workbench `.ewp`. |
| `frontends/zephyr_sbom.py` | Extractor for a Zephyr module `CMakeLists.txt`. |
| `build/sbom.mk` | Shared plain-Make fragment and `wolfglass_sbom_rule` macro. |
| `build/sbom.cmake` | Shared CMake helper: `wolfglass_add_sbom()`. |
| `frontends/included_sources.py` | Finds the source files a compile command compiles in through `#include` (`compdb_sbom.py --included-sources`). |
| `frontends/zephyr_sbom.py` | Zephyr module, from a west build's `compile_commands.json`. |
| `build/sbom.mk` | Shared plain-Make fragment and `wolfssl_compliance_tooling_sbom_rule` macro. |
| `build/sbom.cmake` | Shared CMake helper: `wolfssl_compliance_tooling_add_sbom()`. |
| `gen-sbom` | The vendored SBOM generator. |
| `sbom.am` | Shared autotools fragment. |

Expand All @@ -28,27 +29,34 @@ Composition (at least one):

- `--srcs-file PATH` — the source files compiled into the artifact (tier E).
- `--lib PATH` — the built library to hash (tier R/L/S).
- `--no-artifact-hash` — record the artifact as-built and do not re-hash. Use it
with `--lib` for a FIPS canister or a kernel module. Never substitute a source
list for a certified artifact.
- `--no-artifact-hash` — record a placeholder checksum when no hashable
artefact exists (a FIPS canister or a kernel module). Do not pass `--lib`
or `--srcs-file` with it. gen-sbom accepts one checksum source. Never
substitute a source list for a certified artifact.

Config (choose one):

- `--cflags="..."` — raw CFLAGS; the driver expands the `-D` tokens through the
host compiler. Use the `=` form so a leading-dash value is not read as a flag.
- `--options-h PATH` — a pre-expanded flat `#define` header, used verbatim.
- `--user-settings PATH` — a `user_settings.h`; the generator captures it.
- `--user-settings PATH` — path to `wolfssl/wolfcrypt/settings.h`. Also pass
`--user-settings-include DIR` (the directory that contains `user_settings.h`)
and `--user-settings-define WOLFSSL_USER_SETTINGS`.
- `--source-only` — no build-config macros (for example a Kconfig-driven build).

Dependency (for linkers and bindings): `--dep-wolfssl`, `--dep-openssl`, and
`--dep-version` are passed through only when the generator supports them.

Copyright: the line reads `Copyright (C) <start>-<build year> <supplier>`.
`--copyright-start` (Make, autotools: `SBOM_COPYRIGHT_START`; CMake:
`COPYRIGHT_START`) sets the first year, which defaults to 2006.

The driver captures macros with the host compiler, so the SBOM is reproducible
across toolchains. It scrubs absolute host paths from the captured macros unless
you pass `--no-scrub`.

The shared driver is product-neutral and calls the vendored `share/gen-sbom` by
default. Pass `--gen-sbom` only when you want to override that copy.
The shared driver is product-neutral and calls the `gen-sbom` file in the same
directory. Pass `--gen-sbom` only when you want to override that copy.

## The manifest contract

Expand All @@ -57,17 +65,32 @@ A product does not copy logic. It describes itself:
- Make: set `SBOM_NAME`, `SBOM_SRCS`, `SBOM_CFLAGS`, and a version
(`SBOM_VERSION`, or `SBOM_VERSION_FILE` + `SBOM_VERSION_MACRO`), then
`include tools/sbom/build/sbom.mk`. For a second target, instantiate
`$(eval $(call wolfglass_sbom_rule,<target>,<prefix>))`.
`$(eval $(call wolfssl_compliance_tooling_sbom_rule,<target>,<prefix>))`.
If the product's configuration lives in a `user_settings.h`, also set
`SBOM_SETTINGS_H` (and `SBOM_INCLUDE_DIRS` if that header needs paths the
CFLAGS do not already carry). `SBOM_CFLAGS` alone records the literal `-D`
set and nothing it derives, which for a gated header means the SBOM
describes a configuration nobody built.
- CMake: `include(tools/sbom/build/sbom.cmake)` and call `wolfglass_add_sbom()`
- CMake: `include(tools/sbom/build/sbom.cmake)` and call `wolfssl_compliance_tooling_add_sbom()`
with `NAME`, `VERSION_FILE`, `VERSION_MACRO`, `TARGETS`, `DEFS`, `LICENSE`.
The same call adds `install-sbom` and `uninstall-sbom`. They copy the
CycloneDX and SPDX files to `CMAKE_INSTALL_FULL_DOCDIR` (or
`<prefix>/share/doc/<NAME>`) and read `DESTDIR` when the target runs.
Pass `CDX_OUT` / `SPDX_OUT`
when the file name is not `<name>-<version>` (wolfBoot's config tag).
wolfTPM writes the tag-value file in a later `sbom` target: pass
`NO_INSTALL` and call `wolfssl_compliance_tooling_add_sbom_install(DEPENDS sbom TV ...)`
after that target exists.
`SBOM_GEN` is the canonical generator override; `GEN_SBOM` remains a legacy
alias for compatibility.
- Autotools: set the `SBOM_*` variables and `include tools/sbom/sbom.am`.
- Zephyr: call `wolfssl_compliance_tooling_add_sbom(TARGETS
${ZEPHYR_CURRENT_LIBRARY} ...)` from the module's own CMakeLists, after its
last `zephyr_library_sources()`, so the SBOM records the source set west
actually built. Without that hook, run `zephyr_sbom.py` on the build's
`compile_commands.json` with `--root` set to the module, and pass
`--user-settings` with the module's settings header to record its
configuration.

Keep only true product knowledge in the product: the route-through script, the
module extractor, and the HAL source selector.
38 changes: 38 additions & 0 deletions tools/sbom/build/install-sbom.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# cmake -P script: install SBOM artifacts.
# DESTDIR is read from the environment when this script runs, so
# DESTDIR=/staging cmake --build . --target install-sbom stages the files.
#
# The CycloneDX and SPDX JSON files must exist. The tag-value file is
# copied only when the caller set its path and the file exists.

if(NOT DEFINED WOLFSSL_COMPLIANCE_TOOLING_INSTALL_DIR OR WOLFSSL_COMPLIANCE_TOOLING_INSTALL_DIR STREQUAL "")
message(FATAL_ERROR "install-sbom.cmake: WOLFSSL_COMPLIANCE_TOOLING_INSTALL_DIR is required")
endif()

include("${CMAKE_CURRENT_LIST_DIR}/sbom-paths.cmake")
wolfssl_compliance_tooling_sbom_resolve_outputs(_cdx _spdx _tv)

# file(INSTALL) applies DESTDIR itself. Prepending it here would stage
# the files twice. file(MAKE_DIRECTORY) does not apply DESTDIR, so the
# directory is created at the staged path.
if(DEFINED ENV{DESTDIR})
set(_destdir "$ENV{DESTDIR}")
else()
set(_destdir "")
endif()
set(_staged "${_destdir}${WOLFSSL_COMPLIANCE_TOOLING_INSTALL_DIR}")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows, file(INSTALL) drops the drive letter from the destination before prepending DESTDIR, but _staged here (and _dest in uninstall-sbom.cmake) is a plain concatenation. With DESTDIR=D:/stage and an install dir of C:/Program Files/wolfboot, the files go to D:/stage/Program Files/wolfboot, while file(MAKE_DIRECTORY) and uninstall use D:/stageC:/Program Files/wolfboot.

Stripping a leading X: from the install dir when DESTDIR is set, in both scripts, would keep them consistent. The MAKE_DIRECTORY call isn't needed anyway, since file(INSTALL) creates the destination.


if(NOT EXISTS "${_cdx}")
message(FATAL_ERROR "install-sbom.cmake: missing CycloneDX file ${_cdx}")
endif()
if(NOT EXISTS "${_spdx}")
message(FATAL_ERROR "install-sbom.cmake: missing SPDX file ${_spdx}")
endif()

file(MAKE_DIRECTORY "${_staged}")
file(INSTALL "${_cdx}" "${_spdx}" DESTINATION "${WOLFSSL_COMPLIANCE_TOOLING_INSTALL_DIR}")
if(NOT _tv STREQUAL "" AND EXISTS "${_tv}")
file(INSTALL "${_tv}" DESTINATION "${WOLFSSL_COMPLIANCE_TOOLING_INSTALL_DIR}")
endif()

message(STATUS "Installed SBOM to ${_staged}")
Loading
Loading