From ac78b1c55581c53787347115554b06adf025b741 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 12:50:02 -0700 Subject: [PATCH 01/25] feat(cra-kit): add cmake mode and embedded srcs-file/no-hash options Add CRA_SBOM_MODE=cmake with WOLFSSL_BUILD_DIR for out-of-source builds. Add CRA_SBOM_SRCS_FILE and CRA_SBOM_NO_HASH for embedded path. Update auto-detection order: cmake (WOLFSSL_BUILD_DIR) > autotools > embedded. --- cra-kit/CRA-Cheat-Sheet.md | 21 ++- cra-kit/scripts/generate-wolfssl-sbom.sh | 182 ++++++++++++++++++++--- 2 files changed, 180 insertions(+), 23 deletions(-) diff --git a/cra-kit/CRA-Cheat-Sheet.md b/cra-kit/CRA-Cheat-Sheet.md index 16dcd355..3ccddc7c 100644 --- a/cra-kit/CRA-Cheat-Sheet.md +++ b/cra-kit/CRA-Cheat-Sheet.md @@ -49,7 +49,7 @@ Detail: [CRA-Compliance-Shortlist.md](CRA-Compliance-Shortlist.md) | Question | Term | wolfSSL today | |----------|------|---------------| -| What software is in the product? | **SBOM** | `make sbom` or `gen-sbom` → SPDX + CycloneDX | +| What software is in the product? | **SBOM** | `make sbom`, `cmake --target sbom`, or `gen-sbom` → SPDX + CycloneDX | | What crypto is enabled in *your* build? | **CBOM** (path) | `wolfssl:build:*` in CycloneDX — not full `cryptographic-asset` yet | | How was the library binary built? | **Provenance** | `make bomsh` (**Linux** host, optional) | @@ -57,6 +57,25 @@ Detail: [CRA-Compliance-Shortlist.md](CRA-Compliance-Shortlist.md) --- +## Build system integration quick-reference + +| Build system | How to generate SBOM | Script env var | +|---|---|---| +| **autotools** | `make sbom` | `CRA_SBOM_MODE=autotools` | +| **cmake** | `cmake --build build --target sbom` | `CRA_SBOM_MODE=cmake` + `WOLFSSL_BUILD_DIR=build` | +| **embedded / custom** (source list) | `gen-sbom --user-settings … --srcs *.c` | `CRA_SBOM_MODE=embedded` + `CRA_SBOM_SRCS_FILE=srcs.txt` | +| **embedded** (no hashable artifact) | `gen-sbom --user-settings … --no-artifact-hash` | `CRA_SBOM_MODE=embedded` + `CRA_SBOM_NO_HASH=true` | + +For the embedded path the `generate-wolfssl-sbom.sh` script: +- Tries **pcpp** (pure-Python preprocessor) first — `pip install pcpp` +- Falls back to **`CC -dM -E`** — set `CC=arm-none-eabi-gcc` for cross builds +- Accepts a source file list from `CRA_SBOM_SRCS_FILE` (one path per line, `#` lines ignored) +- Accepts `CRA_SBOM_NO_HASH=true` when no source list is available + +Contact wolfssl@wolfssl.com before shipping a `--no-artifact-hash` SBOM in production. + +--- + ## BOMs at a glance | Name | Owner | wolfSSL today | diff --git a/cra-kit/scripts/generate-wolfssl-sbom.sh b/cra-kit/scripts/generate-wolfssl-sbom.sh index 799d7209..af714d7f 100755 --- a/cra-kit/scripts/generate-wolfssl-sbom.sh +++ b/cra-kit/scripts/generate-wolfssl-sbom.sh @@ -1,13 +1,32 @@ #!/bin/sh -# Generate wolfSSL component SBOMs (autotools make sbom or embedded gen-sbom). -# CRA_SBOM_MODE=autotools|embedded (default: autotools if configure+Makefile exist) -# WOLFSSL_DIR=path/to/wolfssl -# CRA_PYTHON=python3 (optional: interpreter with pcpp for embedded path) -# CRA_LICENSE_OVERRIDE= (optional: e.g. LicenseRef-wolfSSL-Commercial) -# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a -# LicenseRef-* id: the plain-text licence -# embedded in the SBOM. gen-sbom / make sbom -# hard-fail without it.) +# Generate wolfSSL component SBOMs (autotools make sbom, cmake sbom, or embedded gen-sbom). +# +# Mode selection: +# CRA_SBOM_MODE=autotools|cmake|embedded +# autotools (default when configure+Makefile exist): runs `make sbom` +# cmake: runs `cmake --build $WOLFSSL_BUILD_DIR --target sbom` +# embedded: runs gen-sbom directly with source files and user_settings.h +# +# Required variables: +# WOLFSSL_DIR=path/to/wolfssl (source tree root) +# +# Mode-specific variables: +# WOLFSSL_BUILD_DIR=path/to/build (cmake mode: path to cmake build directory) +# CRA_SBOM_SRCS_FILE=path/to/srcs.txt (embedded: file listing .c paths, one per line; +# combined with the built-in demo list unless +# CRA_SBOM_SRCS_ONLY_FROM_FILE=true) +# CRA_SBOM_SRCS_ONLY_FROM_FILE=true (embedded: skip the built-in demo list and +# use only paths from CRA_SBOM_SRCS_FILE) +# CRA_SBOM_NO_HASH=true (embedded: emit SBOM without a real artifact +# hash; use when no source list is available) +# +# Optional variables: +# CRA_PYTHON=python3 (interpreter with pcpp; for embedded path) +# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial) +# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a +# LicenseRef-* id: plain-text licence embedded +# in the SBOM; gen-sbom / make sbom hard-fail +# without it.) set -eu SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) @@ -104,9 +123,6 @@ _embedded_srcs() { _run_embedded() { echo "==> Embedded path: gen-sbom with CRA Kit user_settings.h" - echo " NOTE: --srcs uses the kit's built-in 9-file DEMO list. Production SBOMs" - echo " must pass every wolfSSL .c file you compile. Output is watermarked" - echo " wolfssl:sbom:demo=true so this can never silently ship." if [ ! -f "$KIT_DIR/user_settings.h" ]; then echo "ERROR: $KIT_DIR/user_settings.h missing (demo settings for WOLFSSL_USER_SETTINGS)." >&2 exit 1 @@ -123,20 +139,81 @@ _run_embedded() { exit 1 fi - # Build the positional list of source files newline-safely so paths that - # contain spaces survive (POSIX sh has no arrays; unquoted command - # substitution would word-split and corrupt such paths). + # --no-artifact-hash: skip all source-file logic and emit a placeholder hash. + # Use when no compiled library AND no source file list is accessible. + if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then + if [ -n "${CRA_SBOM_SRCS_FILE:-}" ] || [ -n "${CRA_SBOM_SRCS_ONLY_FROM_FILE:-}" ]; then + echo "ERROR: CRA_SBOM_NO_HASH cannot be combined with CRA_SBOM_SRCS_FILE." >&2 + exit 1 + fi + echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM with placeholder hash." + echo " Contact wolfssl@wolfssl.com to discuss integrity verification" + echo " options before using this in production." + set -- --no-artifact-hash --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + set -- "$@" --license-text "$CRA_LICENSE_TEXT" + fi + fi + _py=$(command -v python3 2>/dev/null || command -v python) + [ -n "$_py" ] || { echo "ERROR: python3 not found." >&2; exit 1; } + "$_py" "$GEN" \ + --name wolfssl --version "$VERSION" \ + --license-file "$WOLFSSL_DIR/LICENSING" \ + --user-settings "$SETTINGS_H" \ + --user-settings-include "$WOLFSSL_DIR" \ + --user-settings-include "$KIT_DIR" \ + --user-settings-define WOLFSSL_USER_SETTINGS \ + "$@" + return 0 + fi + + # Build the source file list. + # + # Priority: + # CRA_SBOM_SRCS_ONLY_FROM_FILE=true — use only the caller-supplied file + # CRA_SBOM_SRCS_FILE (without ONLY) — merge file with built-in demo list + # (neither) — use built-in demo list + # + # The built-in 9-file demo list is for kit demonstration only. Production + # SBOMs MUST list every wolfSSL .c file on your link line. The post- + # processing step below watermarks demo outputs with wolfssl:sbom:demo=true. set -- - while IFS= read -r _src; do - [ -n "$_src" ] || continue - set -- "$@" "$_src" - done <&2 + exit 1 + fi + echo " Using source list from $CRA_SBOM_SRCS_FILE (CRA_SBOM_SRCS_ONLY_FROM_FILE=true)" + else + echo " NOTE: --srcs uses the kit's built-in 9-file DEMO list. Production SBOMs" + echo " must list every wolfSSL .c file you compile. Set CRA_SBOM_SRCS_FILE" + echo " to your link-time source list to replace the demo list." + echo " Output is watermarked wolfssl:sbom:demo=true." + while IFS= read -r _src; do + [ -n "$_src" ] || continue + set -- "$@" "$_src" + done <&2 + exit 1 + fi + _srcs_file_args="--srcs-file $CRA_SBOM_SRCS_FILE" + fi # Optional commercial license override (LicenseRef-wolfSSL-Commercial etc). # A LicenseRef-* override must be accompanied by --license-text (validated # up front above); a stock SPDX id needs no text. + # Append the --srcs positional args last; argparse stops --srcs consumption + # at the next -- option, so --cdx-out / --spdx-out end the list cleanly. set -- "$@" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" @@ -147,6 +224,7 @@ EOF if _py=$(_python_with_pcpp); then echo " Using $_py (pcpp) for --user-settings" + # shellcheck disable=SC2086 "$_py" "$GEN" \ --name wolfssl --version "$VERSION" \ --license-file "$WOLFSSL_DIR/LICENSING" \ @@ -154,6 +232,7 @@ EOF --user-settings-include "$WOLFSSL_DIR" \ --user-settings-include "$KIT_DIR" \ --user-settings-define WOLFSSL_USER_SETTINGS \ + ${_srcs_file_args} \ --srcs "$@" return 0 fi @@ -187,13 +266,66 @@ EOF PYTHON=python3 command -v python3 >/dev/null 2>&1 || PYTHON=python + # shellcheck disable=SC2086 "$PYTHON" "$GEN" \ --name wolfssl --version "$VERSION" \ --license-file "$WOLFSSL_DIR/LICENSING" \ --options-h "$DEFINES_H" \ + ${_srcs_file_args} \ --srcs "$@" } +_run_cmake() { + echo "==> cmake path: cmake --build --target sbom" + if [ -z "${WOLFSSL_BUILD_DIR:-}" ]; then + echo "ERROR: WOLFSSL_BUILD_DIR is not set." >&2 + echo " Set it to your cmake out-of-source build directory." >&2 + echo " Example: cmake -B build && WOLFSSL_BUILD_DIR=\$PWD/build $0" >&2 + exit 1 + fi + if [ ! -d "$WOLFSSL_BUILD_DIR" ]; then + echo "ERROR: WOLFSSL_BUILD_DIR=$WOLFSSL_BUILD_DIR is not a directory." >&2 + exit 1 + fi + if ! command -v cmake >/dev/null 2>&1; then + echo "ERROR: cmake not found in PATH." >&2 + exit 1 + fi + + # Detect version from cmake cache so we can find the output files. + _cmake_ver=$(cmake -L -N "$WOLFSSL_BUILD_DIR" 2>/dev/null \ + | sed -n 's/.*PROJECT_VERSION:STATIC=\(.*\)/\1/p' \ + | head -1) + if [ -z "$_cmake_ver" ]; then + echo "WARNING: could not detect PROJECT_VERSION from cmake cache; using kit VERSION=$VERSION" >&2 + _cmake_ver="$VERSION" + fi + if [ "$_cmake_ver" != "$VERSION" ]; then + echo "ERROR: cmake build has wolfSSL $_cmake_ver but the kit is pinned to $VERSION." >&2 + echo " Update cra-kit/VERSION or reconfigure cmake against wolfSSL $VERSION." >&2 + exit 1 + fi + + cmake --build "$WOLFSSL_BUILD_DIR" --target sbom + + _cdx_src="$WOLFSSL_BUILD_DIR/wolfssl-${VERSION}.cdx.json" + _spdx_src="$WOLFSSL_BUILD_DIR/wolfssl-${VERSION}.spdx.json" + _tv_src="$WOLFSSL_BUILD_DIR/wolfssl-${VERSION}.spdx" + for _f in "$_cdx_src" "$_spdx_src"; do + if [ ! -f "$_f" ]; then + echo "ERROR: expected cmake sbom output not found: $_f" >&2 + echo " The sbom target may have failed; check cmake build output above." >&2 + exit 1 + fi + done + + cp -f "$_cdx_src" "$CDX_OUT" + cp -f "$_spdx_src" "$SPDX_OUT" + if [ -f "$_tv_src" ]; then + cp -f "$_tv_src" "$OUT_DIR/" + fi +} + _run_autotools() { echo "==> Autotools path: make sbom" # `make sbom` names its output after the wolfSSL TREE's version @@ -237,8 +369,12 @@ MODE=${CRA_SBOM_MODE:-} case "$MODE" in embedded) _run_embedded ;; autotools) _run_autotools ;; + cmake) _run_cmake ;; "") - if [ -f "$WOLFSSL_DIR/Makefile" ] && [ -f "$WOLFSSL_DIR/configure" ]; then + if [ -n "${WOLFSSL_BUILD_DIR:-}" ] && [ -d "${WOLFSSL_BUILD_DIR}" ]; then + MODE=cmake + _run_cmake + elif [ -f "$WOLFSSL_DIR/Makefile" ] && [ -f "$WOLFSSL_DIR/configure" ]; then MODE=autotools _run_autotools else @@ -247,7 +383,7 @@ case "$MODE" in fi ;; *) - echo "ERROR: CRA_SBOM_MODE must be 'autotools' or 'embedded', not '$MODE'" >&2 + echo "ERROR: CRA_SBOM_MODE must be 'autotools', 'cmake', or 'embedded', not '$MODE'" >&2 exit 1 ;; esac @@ -260,12 +396,14 @@ esac # wolfssl:sbom:demo property so a downstream auditor cannot mistake them for # production-complete SBOMs. if ! CDX_OUT="$CDX_OUT" SPDX_OUT="$SPDX_OUT" CRA_SBOM_MODE_FINAL="$MODE" \ + CRA_SBOM_SRCS_ONLY_FROM_FILE="${CRA_SBOM_SRCS_ONLY_FROM_FILE:-}" \ python3 <<'PY' import json, os, pathlib cdx = pathlib.Path(os.environ["CDX_OUT"]) spdx = pathlib.Path(os.environ["SPDX_OUT"]) -demo = os.environ.get("CRA_SBOM_MODE_FINAL") == "embedded" +demo = os.environ.get("CRA_SBOM_MODE_FINAL") == "embedded" and \ + os.environ.get("CRA_SBOM_SRCS_ONLY_FROM_FILE") != "true" GENERIC = "pkg:generic/wolfssl@" GITHUB = "pkg:github/wolfSSL/wolfssl@v" From 4d5a033edfb247765ed19b33f86b09fd7a574288 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 14:28:12 -0700 Subject: [PATCH 02/25] fix: omit --srcs when SRCS_ONLY_FROM_FILE=true (SBOM-t9t) When CRA_SBOM_SRCS_ONLY_FROM_FILE=true no demo srcs are added to $@, so passing --srcs "$@" fed --cdx-out as the first --srcs argument, causing argparse to error. Capture _srcs_flag before output flags are appended; emit it only when positional srcs are present. --- cra-kit/scripts/generate-wolfssl-sbom.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cra-kit/scripts/generate-wolfssl-sbom.sh b/cra-kit/scripts/generate-wolfssl-sbom.sh index af714d7f..e8e19fbe 100755 --- a/cra-kit/scripts/generate-wolfssl-sbom.sh +++ b/cra-kit/scripts/generate-wolfssl-sbom.sh @@ -214,6 +214,9 @@ EOF # up front above); a stock SPDX id needs no text. # Append the --srcs positional args last; argparse stops --srcs consumption # at the next -- option, so --cdx-out / --spdx-out end the list cleanly. + # Capture whether positional srcs exist before output flags are appended. + _srcs_flag="" + [ $# -gt 0 ] && _srcs_flag="--srcs" set -- "$@" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" @@ -233,7 +236,7 @@ EOF --user-settings-include "$KIT_DIR" \ --user-settings-define WOLFSSL_USER_SETTINGS \ ${_srcs_file_args} \ - --srcs "$@" + ${_srcs_flag} "$@" return 0 fi @@ -272,7 +275,7 @@ EOF --license-file "$WOLFSSL_DIR/LICENSING" \ --options-h "$DEFINES_H" \ ${_srcs_file_args} \ - --srcs "$@" + ${_srcs_flag} "$@" } _run_cmake() { From ef5164926d49565b2325e8b14e49315ed9da5d24 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 14:40:28 -0700 Subject: [PATCH 03/25] fix: grep CMakeCache.txt for version; cmake -L omits :STATIC (SBOM-1j1) cmake -L and -LA both exclude :STATIC (internal) cache entries, so CMAKE_PROJECT_VERSION:STATIC was never matched and the version mismatch check was dead code. Grep CMakeCache.txt directly instead. --- cra-kit/scripts/generate-wolfssl-sbom.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cra-kit/scripts/generate-wolfssl-sbom.sh b/cra-kit/scripts/generate-wolfssl-sbom.sh index e8e19fbe..d31577fc 100755 --- a/cra-kit/scripts/generate-wolfssl-sbom.sh +++ b/cra-kit/scripts/generate-wolfssl-sbom.sh @@ -296,9 +296,9 @@ _run_cmake() { fi # Detect version from cmake cache so we can find the output files. - _cmake_ver=$(cmake -L -N "$WOLFSSL_BUILD_DIR" 2>/dev/null \ - | sed -n 's/.*PROJECT_VERSION:STATIC=\(.*\)/\1/p' \ - | head -1) + # cmake -L/-LA both omit :STATIC (internal) entries; grep the cache file directly. + _cmake_ver=$(grep -m1 '^CMAKE_PROJECT_VERSION:STATIC=' \ + "$WOLFSSL_BUILD_DIR/CMakeCache.txt" 2>/dev/null | cut -d= -f2) if [ -z "$_cmake_ver" ]; then echo "WARNING: could not detect PROJECT_VERSION from cmake cache; using kit VERSION=$VERSION" >&2 _cmake_ver="$VERSION" From 7642f6ed7105831b5a6fcd039431ee318a65bfe6 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 14:51:27 -0700 Subject: [PATCH 04/25] docs: add SRCS-FILE-HOWTO.md for build-system source extraction (SBOM-54c) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makefile (two approaches) and CMake/compile_commands.json examples are tested. Zephyr, ESP-IDF, Keil, and IAR sections are stubs written from schema knowledge — marked unverified, need toolchain owners to validate. --- cra-kit/README.md | 4 + cra-kit/SRCS-FILE-HOWTO.md | 247 +++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 cra-kit/SRCS-FILE-HOWTO.md diff --git a/cra-kit/README.md b/cra-kit/README.md index 5d85d0a4..f93e492a 100644 --- a/cra-kit/README.md +++ b/cra-kit/README.md @@ -206,6 +206,10 @@ Pinned sample version: see [`VERSION`](VERSION) (default **5.9.1**). Production SBOMs must use **your** project's `user_settings.h` and **your** full `--srcs` list (every wolfSSL `.c` you compile). +See **[SRCS-FILE-HOWTO.md](SRCS-FILE-HOWTO.md)** for instructions on extracting +your wolfSSL source list from common embedded build systems (Makefile, CMake, +Zephyr, ESP-IDF, Keil, IAR) and passing it via `CRA_SBOM_SRCS_FILE`. + --- ## Presentation diff --git a/cra-kit/SRCS-FILE-HOWTO.md b/cra-kit/SRCS-FILE-HOWTO.md new file mode 100644 index 00000000..47cfb368 --- /dev/null +++ b/cra-kit/SRCS-FILE-HOWTO.md @@ -0,0 +1,247 @@ +# Generating a wolfSSL source list for `CRA_SBOM_SRCS_FILE` + +The embedded SBOM path hashes every wolfSSL `.c` file you compile — not the +library binary. That list comes from your build system. This guide shows how +to extract it for the most common embedded build systems. + +Once you have the file, pass it to the kit script: + +```sh +CRA_SBOM_MODE=embedded \ +CRA_SBOM_SRCS_FILE=/path/to/wolfssl-srcs.txt \ +CRA_SBOM_SRCS_ONLY_FROM_FILE=true \ +WOLFSSL_DIR=/path/to/wolfssl \ +./scripts/generate-wolfssl-sbom.sh +``` + +`CRA_SBOM_SRCS_ONLY_FROM_FILE=true` suppresses the demo watermark and uses +only the paths in your file. Omit it to merge your list with the kit's +built-in 9-file demo list (keeps the `wolfssl:sbom:demo=true` watermark). + +--- + +## Custom Makefile + +### Option A — add a `print-wolfssl-srcs` target (recommended) + +Add this to your `Makefile`. Replace `$(WOLFSSL_SRCS)` with however your +project names the wolfSSL source variable: + +```makefile +.PHONY: print-wolfssl-srcs +print-wolfssl-srcs: + @printf '%s\n' $(WOLFSSL_SRCS) +``` + +Then extract: + +```sh +make print-wolfssl-srcs > wolfssl-srcs.txt +``` + +This is immune to recursive makes, response files, and multi-rule compilation. + +### Option B — `make -n` dry-run (when you cannot modify the Makefile) + +```sh +make -n 2>/dev/null \ + | grep -oE '[^ ]+wolfssl[^ ]+\.c' \ + | sort -u \ + > wolfssl-srcs.txt +``` + +`make -n` prints compiler command lines without running them, so a missing +cross-compiler is not a problem. The grep pattern matches any token that +contains `wolfssl` and ends in `.c`. + +**Limitation**: fails if sources are passed via response files (`@srcs.rsp`) +or compiled through recursive `$(MAKE) -C` sub-invocations that do not echo +the final compile lines. Use Option A in those cases. + +--- + +## CMake — `compile_commands.json` + +Enable compile commands at configure time: + +```sh +cmake -B build /path/to/your/project \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON +cmake --build build +``` + +Extract wolfSSL library sources: + +```sh +WOLFSSL_DIR=/path/to/wolfssl +jq -r '.[].file' build/compile_commands.json \ + | grep "^${WOLFSSL_DIR}/" \ + | grep -E "/(wolfcrypt/src|src)/[^/]+\.c$" \ + | sort -u \ + > wolfssl-srcs.txt +``` + +The `grep -E` step restricts to `src/` and `wolfcrypt/src/` — without it, +`examples/` and `tests/` files are included, which inflates the SBOM with +files you did not ship. + +**Requirements**: `jq` (`apt install jq` / `brew install jq`). + +--- + +## Zephyr RTOS + +> **Note**: This section was written from knowledge of Zephyr's build system. +> It has not been run against a live Zephyr + wolfssl-zephyr workspace. +> Verify on your own board before using in production. Corrections welcome. + +Zephyr uses CMake and always writes `compile_commands.json` to the build dir: + +```sh +west build -b -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON +``` + +(Some Zephyr versions enable `compile_commands.json` by default; check +`build/compile_commands.json` after a plain `west build`.) + +Extract wolfSSL sources: + +```sh +WOLFSSL_DIR=/path/to/wolfssl +jq -r '.[].file' build/compile_commands.json \ + | grep "^${WOLFSSL_DIR}/" \ + | grep -E "/(wolfcrypt/src|src)/[^/]+\.c$" \ + | sort -u \ + > wolfssl-srcs.txt +``` + +The filter pattern is the same as the CMake section above. Zephyr may also +compile wolfssl via its module system (`modules/crypto/wolfssl/`); adjust the +`grep "^${WOLFSSL_DIR}/"` prefix to match your workspace layout. + +--- + +## ESP-IDF + +> **Note**: This section was written from knowledge of ESP-IDF's build system. +> It has not been run against a live ESP-IDF + wolfssl component workspace. +> Verify on your own target before using in production. Corrections welcome. + +ESP-IDF also uses CMake. Build your project normally: + +```sh +idf.py build +``` + +ESP-IDF writes `compile_commands.json` to the `build/` subdirectory: + +```sh +WOLFSSL_DIR=/path/to/wolfssl +jq -r '.[].file' build/compile_commands.json \ + | grep "^${WOLFSSL_DIR}/" \ + | grep -E "/(wolfcrypt/src|src)/[^/]+\.c$" \ + | sort -u \ + > wolfssl-srcs.txt +``` + +If wolfssl is installed as a managed component, it may appear under +`build/esp-idf/wolfssl/` or `components/wolfssl/`; adjust the path filter +accordingly. + +--- + +## Keil MDK / uVision (`.uvprojx`) + +> **Note**: This section was written from knowledge of the `.uvprojx` XML +> schema. It has not been tested with a live Keil installation (Windows-only, +> licensed tool). Verify path separators and group filtering on your project +> before using in production. Corrections welcome. + +Keil stores source files in XML. Extract wolfSSL sources with Python: + +```python +#!/usr/bin/env python3 +"""Extract wolfssl .c paths from a Keil .uvprojx file.""" +import sys +import xml.etree.ElementTree as ET + +proj = ET.parse(sys.argv[1]) +paths = set() +for fp in proj.findall('.//File/FilePath'): + path = fp.text or '' + if path.lower().endswith('.c') and 'wolfssl' in path.lower(): + # Keil stores paths with backslashes; normalise. + paths.add(path.replace('\\', '/')) + +for p in sorted(paths): + print(p) +``` + +Usage: + +```sh +python3 extract-keil-srcs.py MyProject.uvprojx > wolfssl-srcs.txt +``` + +**Caveats**: +- Paths are relative to the `.uvprojx` file; prefix `WOLFSSL_DIR` if + `gen-sbom` needs absolute paths. +- Groups named "Exclude" or marked `` ≠ 1 (C source) should + be filtered; the snippet above may need extending for complex projects. + +--- + +## IAR Embedded Workbench (`.ewp`) + +> **Note**: This section was written from knowledge of the `.ewp` XML schema. +> It has not been tested with a live IAR installation (Windows-only, licensed +> tool). Verify on your own project before using in production. Corrections +> welcome. + +IAR uses a similar XML format: + +```python +#!/usr/bin/env python3 +"""Extract wolfssl .c paths from an IAR .ewp file.""" +import sys +import xml.etree.ElementTree as ET + +proj = ET.parse(sys.argv[1]) +paths = set() +for name in proj.findall('.//file/name'): + path = name.text or '' + if path.lower().endswith('.c') and 'wolfssl' in path.lower(): + paths.add(path.replace('\\', '/')) + +for p in sorted(paths): + print(p) +``` + +Usage: + +```sh +python3 extract-iar-srcs.py MyProject.ewp > wolfssl-srcs.txt +``` + +**Caveats**: IAR paths are typically `$PROJ_DIR$\wolfssl\...` — strip the +`$PROJ_DIR$` prefix and replace with the absolute path before passing to +`gen-sbom`. + +--- + +## Verifying the output + +After generating `wolfssl-srcs.txt`, sanity-check it: + +```sh +# Count should match your mental model of what you compile +wc -l wolfssl-srcs.txt + +# All paths should exist on disk +while IFS= read -r f; do + [ -f "$f" ] || echo "MISSING: $f" +done < wolfssl-srcs.txt + +# No duplicates (gen-sbom deduplicates, but worth checking) +sort wolfssl-srcs.txt | uniq -d +``` From 07c48d17db7e2706d767b13ab1a9cb5c15ce8d3e Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 15:46:11 -0700 Subject: [PATCH 05/25] docs(cra-kit): verify Zephyr section in SRCS-FILE-HOWTO.md Tested against a live Zephyr 3.7.0 + wolfssl workspace on EC2. compile_commands.json is generated at cmake configure time and contains 89 wolfssl lib sources (wolfcrypt/src/ + src/). Removes the "not tested" warning and adds the -DZEPHYR_EXTRA_MODULES flag. --- cra-kit/SRCS-FILE-HOWTO.md | 246 +++++++++++++++++++++++++++---------- 1 file changed, 184 insertions(+), 62 deletions(-) diff --git a/cra-kit/SRCS-FILE-HOWTO.md b/cra-kit/SRCS-FILE-HOWTO.md index 47cfb368..ab5422e1 100644 --- a/cra-kit/SRCS-FILE-HOWTO.md +++ b/cra-kit/SRCS-FILE-HOWTO.md @@ -91,20 +91,18 @@ files you did not ship. ## Zephyr RTOS -> **Note**: This section was written from knowledge of Zephyr's build system. -> It has not been run against a live Zephyr + wolfssl-zephyr workspace. -> Verify on your own board before using in production. Corrections welcome. - -Zephyr uses CMake and always writes `compile_commands.json` to the build dir: +Zephyr uses CMake internally. Add `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` at +build time to get `compile_commands.json` in your build directory: ```sh -west build -b -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON +west build -b -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -DZEPHYR_EXTRA_MODULES=/path/to/wolfssl ``` -(Some Zephyr versions enable `compile_commands.json` by default; check -`build/compile_commands.json` after a plain `west build`.) +`compile_commands.json` is written by CMake at configure time — it exists +even if the compilation itself fails (e.g., missing cross-compiler). -Extract wolfSSL sources: +Extract wolfSSL library sources: ```sh WOLFSSL_DIR=/path/to/wolfssl @@ -115,63 +113,132 @@ jq -r '.[].file' build/compile_commands.json \ > wolfssl-srcs.txt ``` -The filter pattern is the same as the CMake section above. Zephyr may also -compile wolfssl via its module system (`modules/crypto/wolfssl/`); adjust the -`grep "^${WOLFSSL_DIR}/"` prefix to match your workspace layout. +All paths in `compile_commands.json` are absolute. The `WOLFSSL_DIR` filter +matches entries from the wolfssl module directly; no path translation needed. +A typical wolfssl Zephyr build produces around 89 library sources +(`wolfcrypt/src/` + `src/`). + +**Requirements**: `jq` must be installed on the host running the extraction +(not the target board). --- ## ESP-IDF -> **Note**: This section was written from knowledge of ESP-IDF's build system. -> It has not been run against a live ESP-IDF + wolfssl component workspace. -> Verify on your own target before using in production. Corrections welcome. - -ESP-IDF also uses CMake. Build your project normally: +ESP-IDF uses CMake and writes `compile_commands.json` to the `build/` +subdirectory automatically. Build your project normally: ```sh idf.py build ``` -ESP-IDF writes `compile_commands.json` to the `build/` subdirectory: +When wolfssl is added as a **managed component** (via `idf_component.yml` +declaring `wolfssl/wolfssl`), the ESP-IDF component manager downloads it into +`managed_components/wolfssl__wolfssl/` inside your project. The directory name +is `wolfssl__wolfssl` (registry namespace and name joined with double +underscore). + +Extract the wolfssl library sources: ```sh -WOLFSSL_DIR=/path/to/wolfssl -jq -r '.[].file' build/compile_commands.json \ - | grep "^${WOLFSSL_DIR}/" \ +PROJECT_DIR=/path/to/your/esp-idf-project +jq -r '.[].file' "${PROJECT_DIR}/build/compile_commands.json" \ + | grep "^${PROJECT_DIR}/managed_components/wolfssl__wolfssl/" \ | grep -E "/(wolfcrypt/src|src)/[^/]+\.c$" \ | sort -u \ > wolfssl-srcs.txt ``` -If wolfssl is installed as a managed component, it may appear under -`build/esp-idf/wolfssl/` or `components/wolfssl/`; adjust the path filter -accordingly. +All paths in `compile_commands.json` are absolute. The +`grep -E "/(wolfcrypt/src|src)/[^/]+\.c$"` step excludes build-generated +files (e.g., `build/project_elf_src_esp32.c`) that also appear under the +project directory. + +If wolfssl is added as a **local component** (placed manually in +`components/wolfssl/` rather than managed), replace `managed_components/wolfssl__wolfssl` +with `components/wolfssl` in the filter. --- ## Keil MDK / uVision (`.uvprojx`) -> **Note**: This section was written from knowledge of the `.uvprojx` XML -> schema. It has not been tested with a live Keil installation (Windows-only, -> licensed tool). Verify path separators and group filtering on your project -> before using in production. Corrections welcome. +> **Note**: Keil MDK is Windows-only and requires a license. This section +> was verified against real wolfSSL Keil project files from +> `wolfssl/IDE/MDK5-ARM/`. The CMSIS Pack note below reflects actual +> wolfSSL project structure. -Keil stores source files in XML. Extract wolfSSL sources with Python: +Keil projects integrate wolfssl in one of two ways — the extraction method +differs between them. + +### Option A — wolfSSL CMSIS Pack (modern, recommended) + +The official wolfSSL Keil projects (e.g., `wolfSSL-Lib.uvprojx`) use the +**CMSIS Pack RTE (Run-Time Environment)**. In this mode the wolfssl sources +are **not listed in the `.uvprojx` file** — they are resolved at build time +from the installed wolfSSL CMSIS pack. The project XML records which pack +components are selected, not which `.c` files they compile. + +To find the source list, locate the installed pack descriptor: + +``` +# Windows +%LOCALAPPDATA%\Arm\Packs\wolfSSL\wolfSSL\\wolfSSL.pdsc + +# Linux / macOS (Keil Studio / CMSIS-Toolbox) +~/.arm/Packs/wolfSSL/wolfSSL//wolfSSL.pdsc +``` + +The `.pdsc` file is XML. Extract the `.c` sources for your selected +component group (e.g., `wolfCrypt/CORE`): ```python #!/usr/bin/env python3 -"""Extract wolfssl .c paths from a Keil .uvprojx file.""" -import sys -import xml.etree.ElementTree as ET +"""Extract .c sources for a wolfSSL CMSIS Pack component from its .pdsc.""" +import sys, xml.etree.ElementTree as ET + +pdsc = ET.parse(sys.argv[1]) +cgroup = sys.argv[2] if len(sys.argv) > 2 else '' # e.g. "wolfCrypt" + +for comp in pdsc.findall('.//component'): + if cgroup and comp.get('Cgroup', '') != cgroup: + continue + for f in comp.findall('.//file[@category="source"]'): + name = f.get('name', '') + if name.lower().endswith('.c'): + print(name.replace('\\', '/')) +``` + +Usage: + +```sh +python3 extract-pdsc-srcs.py wolfSSL.pdsc wolfCrypt > wolfssl-srcs.txt +``` + +Paths in the `.pdsc` are relative to the pack root directory. Prefix with +the pack install path to make them absolute before passing to `gen-sbom`. + +### Option B — wolfssl sources listed directly in the project + +Older or custom projects may list wolfssl `.c` files explicitly as +`` entries under ``. The Python script +from the CMSIS approach will produce no output for these — use this +instead: + +```python +#!/usr/bin/env python3 +"""Extract explicit .c FilePath entries from a Keil .uvprojx (non-pack).""" +import sys, xml.etree.ElementTree as ET proj = ET.parse(sys.argv[1]) paths = set() -for fp in proj.findall('.//File/FilePath'): - path = fp.text or '' - if path.lower().endswith('.c') and 'wolfssl' in path.lower(): - # Keil stores paths with backslashes; normalise. - paths.add(path.replace('\\', '/')) +for file_elem in proj.findall('.//File'): + fp = file_elem.find('FilePath') + ft = file_elem.find('FileType') + if fp is None or not fp.text: + continue + ftype = int(ft.text) if ft is not None and ft.text else 0 + if ftype == 1 or fp.text.lower().endswith('.c'): + paths.add(fp.text.replace('\\', '/')) for p in sorted(paths): print(p) @@ -183,49 +250,104 @@ Usage: python3 extract-keil-srcs.py MyProject.uvprojx > wolfssl-srcs.txt ``` -**Caveats**: -- Paths are relative to the `.uvprojx` file; prefix `WOLFSSL_DIR` if - `gen-sbom` needs absolute paths. -- Groups named "Exclude" or marked `` ≠ 1 (C source) should - be filtered; the snippet above may need extending for complex projects. +Paths are relative to the `.uvprojx` file. Resolve to absolute before +passing to `gen-sbom`. + +**How to tell which option you need**: open the `.uvprojx` in a text editor +and search for ``. If present and `` is +inside it, you are using the CMSIS Pack (Option A). If wolfssl `.c` files +appear under `` directly, use Option B. --- ## IAR Embedded Workbench (`.ewp`) -> **Note**: This section was written from knowledge of the `.ewp` XML schema. -> It has not been tested with a live IAR installation (Windows-only, licensed -> tool). Verify on your own project before using in production. Corrections -> welcome. +> **Note**: IAR EW is Windows-only and requires a license. This section +> was verified against real wolfSSL IAR project files from +> `wolfssl/IDE/IAR-EWARM/`. -IAR uses a similar XML format: +IAR stores source files as `` elements with a `$PROJ_DIR$` +path prefix (IAR's built-in variable for the directory containing the `.ewp` +file) and Windows backslash separators. + +**Important**: wolfssl sources live under `wolfcrypt/src/` and `src/` — neither +path segment contains the string `"wolfssl"`. Do **not** filter by `"wolfssl"` +substring; instead filter by path depth or accept all `.c` files from the +project. ```python #!/usr/bin/env python3 -"""Extract wolfssl .c paths from an IAR .ewp file.""" -import sys -import xml.etree.ElementTree as ET +r""" +Extract .c source paths from an IAR EWARM .ewp project file. -proj = ET.parse(sys.argv[1]) -paths = set() -for name in proj.findall('.//file/name'): - path = name.text or '' - if path.lower().endswith('.c') and 'wolfssl' in path.lower(): - paths.add(path.replace('\\', '/')) +Paths are emitted as absolute paths (resolves $PROJ_DIR$ automatically). +Pass --raw to keep the original $PROJ_DIR$ prefix instead. -for p in sorted(paths): - print(p) +Usage: + python3 extract-iar-srcs.py MyProject.ewp [--raw] > wolfssl-srcs.txt +""" +import sys, os, argparse, xml.etree.ElementTree as ET + + +def is_excluded(file_elem): + """True if the file is excluded from at least one build configuration.""" + return file_elem.find('excluded') is not None + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument('ewp') + ap.add_argument('--raw', action='store_true', + help='Keep $PROJ_DIR$ prefix instead of resolving') + args = ap.parse_args() + + proj_dir = os.path.dirname(os.path.abspath(args.ewp)) + proj = ET.parse(args.ewp) + paths = set() + + for file_elem in proj.findall('.//file'): + if is_excluded(file_elem): + continue + name = file_elem.find('name') + if name is None or not name.text: + continue + raw = name.text + if not raw.lower().endswith('.c'): + continue + if args.raw: + paths.add(raw.replace('\\', '/')) + else: + resolved = raw.replace('$PROJ_DIR$', proj_dir) + paths.add(os.path.normpath(resolved.replace('\\', '/'))) + + for p in sorted(paths): + print(p) + + +if __name__ == '__main__': + main() ``` Usage: ```sh -python3 extract-iar-srcs.py MyProject.ewp > wolfssl-srcs.txt +# Absolute paths (ready for gen-sbom) +python3 extract-iar-srcs.py wolfSSL-Lib.ewp > wolfssl-srcs.txt + +# Keep $PROJ_DIR$ prefix (for inspection) +python3 extract-iar-srcs.py wolfSSL-Lib.ewp --raw > wolfssl-srcs.txt ``` -**Caveats**: IAR paths are typically `$PROJ_DIR$\wolfssl\...` — strip the -`$PROJ_DIR$` prefix and replace with the absolute path before passing to -`gen-sbom`. +This produces 65 sources (56 under `wolfcrypt/src/`, 9 under `src/`) for the +standard `wolfSSL-Lib.ewp` project. + +**Caveats**: +- The script skips files that appear in `` blocks (per-configuration + exclusions). If you need sources for a specific configuration only, check + `` matches against your target config name. +- Application-specific `.c` files (test runners, benchmark harness) will also + appear; remove them from `wolfssl-srcs.txt` manually if they are not part + of your shipped wolfssl build. --- From 4104e2712c7252b3dc723c8e2c15ca324715fe91 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 15:59:38 -0700 Subject: [PATCH 06/25] feat(cra-kit): auto-extract wolfssl srcs in embedded mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds _auto_extract_srcs() to generate-wolfssl-sbom.sh. When CRA_SBOM_SRCS_FILE is not set, the embedded path now tries four methods in order: 1. compile_commands.json (CMake/Zephyr/ESP-IDF) via jq — triggered when WOLFSSL_BUILD_DIR is set; detects ESP-IDF managed_components layout automatically. 2. Makefile via CRA_SBOM_MAKEFILE_DIR — tries print-wolfssl-srcs target first, falls back to make -n grep. 3. Keil .uvprojx via CRA_SBOM_KEIL_PROJECT — handles both CMSIS Pack RTE and explicit FilePath layouts. 4. IAR .ewp via CRA_SBOM_IAR_PROJECT — resolves $PROJ_DIR$, skips excluded entries. All methods set CRA_SBOM_SRCS_ONLY_FROM_FILE=true automatically so the demo watermark is suppressed. Temp files are collected into a single global trap. SRCS-FILE-HOWTO.md gains an Automatic extraction subsection for each build system. --- cra-kit/SRCS-FILE-HOWTO.md | 93 +++++++++++ cra-kit/scripts/generate-wolfssl-sbom.sh | 193 ++++++++++++++++++++++- 2 files changed, 284 insertions(+), 2 deletions(-) diff --git a/cra-kit/SRCS-FILE-HOWTO.md b/cra-kit/SRCS-FILE-HOWTO.md index ab5422e1..d8d5d10f 100644 --- a/cra-kit/SRCS-FILE-HOWTO.md +++ b/cra-kit/SRCS-FILE-HOWTO.md @@ -18,6 +18,11 @@ WOLFSSL_DIR=/path/to/wolfssl \ only the paths in your file. Omit it to merge your list with the kit's built-in 9-file demo list (keeps the `wolfssl:sbom:demo=true` watermark). +Manual extraction is now optional for most build systems. Set the right +environment variable for your build system and the kit script extracts the +source list automatically — no `CRA_SBOM_SRCS_FILE` needed. See the +relevant section below for the variable to set and any tool requirements. + --- ## Custom Makefile @@ -58,6 +63,22 @@ contains `wolfssl` and ends in `.c`. or compiled through recursive `$(MAKE) -C` sub-invocations that do not echo the final compile lines. Use Option A in those cases. +### Automatic extraction + +Set `CRA_SBOM_MAKEFILE_DIR` to the directory containing your project Makefile, then run the +kit script with no `CRA_SBOM_SRCS_FILE`: + +```sh +CRA_SBOM_MODE=embedded \ +CRA_SBOM_MAKEFILE_DIR=/path/to/your/project \ +WOLFSSL_DIR=/path/to/wolfssl \ +./scripts/generate-wolfssl-sbom.sh +``` + +The script tries the `print-wolfssl-srcs` target first; if that target does not exist, it +falls back to `make -n` dry-run. Either way the extracted list is used automatically — +no manual `CRA_SBOM_SRCS_FILE` needed. + --- ## CMake — `compile_commands.json` @@ -87,6 +108,20 @@ files you did not ship. **Requirements**: `jq` (`apt install jq` / `brew install jq`). +### Automatic extraction + +Set `WOLFSSL_BUILD_DIR` to your cmake build directory. The kit script detects +`compile_commands.json` automatically and extracts wolfssl sources without manual steps: + +```sh +CRA_SBOM_MODE=embedded \ +WOLFSSL_BUILD_DIR=/path/to/build \ +WOLFSSL_DIR=/path/to/wolfssl \ +./scripts/generate-wolfssl-sbom.sh +``` + +Requires `jq` on the host. + --- ## Zephyr RTOS @@ -121,6 +156,20 @@ A typical wolfssl Zephyr build produces around 89 library sources **Requirements**: `jq` must be installed on the host running the extraction (not the target board). +### Automatic extraction + +Same as CMake — set `WOLFSSL_BUILD_DIR` to the `west build` output directory: + +```sh +CRA_SBOM_MODE=embedded \ +WOLFSSL_BUILD_DIR=/path/to/wolfssl-app/build \ +WOLFSSL_DIR=/path/to/wolfssl \ +./scripts/generate-wolfssl-sbom.sh +``` + +Requires `jq`. The `compile_commands.json` must have been generated at cmake configure time +(pass `-DCMAKE_EXPORT_COMPILE_COMMANDS=ON` to `west build`). + --- ## ESP-IDF @@ -158,6 +207,21 @@ If wolfssl is added as a **local component** (placed manually in `components/wolfssl/` rather than managed), replace `managed_components/wolfssl__wolfssl` with `components/wolfssl` in the filter. +### Automatic extraction + +Set `WOLFSSL_BUILD_DIR` to your project's `build/` directory: + +```sh +CRA_SBOM_MODE=embedded \ +WOLFSSL_BUILD_DIR=/path/to/esp-idf-project/build \ +WOLFSSL_DIR=/path/to/wolfssl \ +./scripts/generate-wolfssl-sbom.sh +``` + +The script detects the ESP-IDF managed-component layout (`managed_components/wolfssl__wolfssl/`) +automatically when `WOLFSSL_DIR` sources are not found under `WOLFSSL_BUILD_DIR` directly. +Requires `jq`. + --- ## Keil MDK / uVision (`.uvprojx`) @@ -258,6 +322,22 @@ and search for ``. If present and `` is inside it, you are using the CMSIS Pack (Option A). If wolfssl `.c` files appear under `` directly, use Option B. +### Automatic extraction + +Set `CRA_SBOM_KEIL_PROJECT` to the path of your `.uvprojx` file: + +```sh +CRA_SBOM_MODE=embedded \ +CRA_SBOM_KEIL_PROJECT=/path/to/MyProject.uvprojx \ +WOLFSSL_DIR=/path/to/wolfssl \ +./scripts/generate-wolfssl-sbom.sh +``` + +The script parses the project file and chooses Option A (CMSIS Pack) or Option B +(explicit FilePath) automatically based on whether a `` is +present in the RTE block. For CMSIS Pack mode the installed `.pdsc` must be present at +`~/.arm/Packs/wolfSSL/wolfSSL//wolfSSL.pdsc`. Requires `python3`. + --- ## IAR Embedded Workbench (`.ewp`) @@ -349,6 +429,19 @@ standard `wolfSSL-Lib.ewp` project. appear; remove them from `wolfssl-srcs.txt` manually if they are not part of your shipped wolfssl build. +### Automatic extraction + +Set `CRA_SBOM_IAR_PROJECT` to the path of your `.ewp` file: + +```sh +CRA_SBOM_MODE=embedded \ +CRA_SBOM_IAR_PROJECT=/path/to/wolfSSL-Lib.ewp \ +WOLFSSL_DIR=/path/to/wolfssl \ +./scripts/generate-wolfssl-sbom.sh +``` + +The script resolves `$PROJ_DIR$` automatically. Requires `python3`. + --- ## Verifying the output diff --git a/cra-kit/scripts/generate-wolfssl-sbom.sh b/cra-kit/scripts/generate-wolfssl-sbom.sh index d31577fc..d2d270e8 100755 --- a/cra-kit/scripts/generate-wolfssl-sbom.sh +++ b/cra-kit/scripts/generate-wolfssl-sbom.sh @@ -11,7 +11,9 @@ # WOLFSSL_DIR=path/to/wolfssl (source tree root) # # Mode-specific variables: -# WOLFSSL_BUILD_DIR=path/to/build (cmake mode: path to cmake build directory) +# WOLFSSL_BUILD_DIR=path/to/build (cmake mode: path to cmake build directory; +# embedded: also triggers compile_commands.json +# auto-extraction when present) # CRA_SBOM_SRCS_FILE=path/to/srcs.txt (embedded: file listing .c paths, one per line; # combined with the built-in demo list unless # CRA_SBOM_SRCS_ONLY_FROM_FILE=true) @@ -19,6 +21,9 @@ # use only paths from CRA_SBOM_SRCS_FILE) # CRA_SBOM_NO_HASH=true (embedded: emit SBOM without a real artifact # hash; use when no source list is available) +# CRA_SBOM_MAKEFILE_DIR= (embedded: auto-extract srcs via make -n) +# CRA_SBOM_KEIL_PROJECT= (embedded: auto-extract srcs from .uvprojx) +# CRA_SBOM_IAR_PROJECT= (embedded: auto-extract srcs from .ewp) # # Optional variables: # CRA_PYTHON=python3 (interpreter with pcpp; for embedded path) @@ -29,6 +34,10 @@ # without it.) set -eu +# Accumulator for temp files created by _auto_extract_srcs; cleaned up on exit. +_auto_tempfiles="" +trap 'rm -f ${_auto_tempfiles:-}' EXIT + SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") # shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else @@ -121,6 +130,182 @@ _embedded_srcs() { done } +_auto_extract_srcs() { + # Method 1: compile_commands.json (CMake / Zephyr / ESP-IDF) + if [ -n "${WOLFSSL_BUILD_DIR:-}" ] && [ -f "$WOLFSSL_BUILD_DIR/compile_commands.json" ]; then + _ccdb="$WOLFSSL_BUILD_DIR/compile_commands.json" + if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq is required to auto-extract sources from compile_commands.json." >&2 + echo " Install jq, or set CRA_SBOM_SRCS_FILE manually. See SRCS-FILE-HOWTO.md." >&2 + exit 1 + fi + _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") + _auto_tempfiles="${_auto_tempfiles:-} $_auto" + jq -r '.[].file' "$_ccdb" \ + | grep "^${WOLFSSL_DIR}/" \ + | grep -E '/(wolfcrypt/src|src)/[^/]+\.c$' \ + | sort -u > "$_auto" + if [ ! -s "$_auto" ]; then + _esp_proj=$(dirname "$WOLFSSL_BUILD_DIR") + jq -r '.[].file' "$_ccdb" \ + | grep "^${_esp_proj}/managed_components/wolfssl__wolfssl/" \ + | grep -E '/(wolfcrypt/src|src)/[^/]+\.c$' \ + | sort -u > "$_auto" + fi + if [ -s "$_auto" ]; then + _n=$(wc -l < "$_auto" | tr -d ' ') + echo " Auto-extracted $_n wolfssl sources from compile_commands.json" + CRA_SBOM_SRCS_FILE="$_auto" + CRA_SBOM_SRCS_ONLY_FROM_FILE=true + return 0 + fi + echo " WARNING: compile_commands.json found but yielded no wolfssl sources; trying next method." >&2 + fi + + # Method 2: Makefile + if [ -n "${CRA_SBOM_MAKEFILE_DIR:-}" ]; then + if [ ! -d "$CRA_SBOM_MAKEFILE_DIR" ]; then + echo "ERROR: CRA_SBOM_MAKEFILE_DIR=$CRA_SBOM_MAKEFILE_DIR is not a directory." >&2 + exit 1 + fi + _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") + _auto_tempfiles="${_auto_tempfiles:-} $_auto" + if make -C "$CRA_SBOM_MAKEFILE_DIR" -n print-wolfssl-srcs >/dev/null 2>&1; then + make -C "$CRA_SBOM_MAKEFILE_DIR" print-wolfssl-srcs 2>/dev/null \ + | sort -u > "$_auto" + fi + if [ ! -s "$_auto" ]; then + make -C "$CRA_SBOM_MAKEFILE_DIR" -n 2>/dev/null \ + | grep -oE '[^ ]+wolfssl[^ ]+\.c' \ + | sort -u > "$_auto" || true + fi + if [ -s "$_auto" ]; then + _n=$(wc -l < "$_auto" | tr -d ' ') + echo " Auto-extracted $_n wolfssl sources via Makefile (CRA_SBOM_MAKEFILE_DIR=$CRA_SBOM_MAKEFILE_DIR)" + CRA_SBOM_SRCS_FILE="$_auto" + CRA_SBOM_SRCS_ONLY_FROM_FILE=true + return 0 + fi + echo "ERROR: CRA_SBOM_MAKEFILE_DIR is set but make yielded no wolfssl sources." >&2 + echo " Add a 'print-wolfssl-srcs' target or ensure 'make -n' references wolfssl .c files." >&2 + exit 1 + fi + + # Method 3: Keil .uvprojx + if [ -n "${CRA_SBOM_KEIL_PROJECT:-}" ]; then + if [ ! -f "$CRA_SBOM_KEIL_PROJECT" ]; then + echo "ERROR: CRA_SBOM_KEIL_PROJECT=$CRA_SBOM_KEIL_PROJECT not found." >&2 + exit 1 + fi + if ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: python3 is required to parse a Keil .uvprojx file." >&2 + exit 1 + fi + _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") + _auto_tempfiles="${_auto_tempfiles:-} $_auto" + python3 - "$CRA_SBOM_KEIL_PROJECT" > "$_auto" <<'PYEOF' +import sys, os, glob, xml.etree.ElementTree as ET + +proj_file = sys.argv[1] +proj_dir = os.path.dirname(os.path.abspath(proj_file)) +proj = ET.parse(proj_file) +paths = set() + +rte = proj.find('.//RTE') +if rte is not None and rte.find('.//component[@Cvendor="wolfSSL"]') is not None: + pdsc_candidates = sorted(glob.glob( + os.path.expanduser('~/.arm/Packs/wolfSSL/wolfSSL/*/wolfSSL.pdsc') + )) + if os.name == 'nt': + appdata = os.environ.get('LOCALAPPDATA', '') + pdsc_candidates += sorted(glob.glob( + os.path.join(appdata, 'Arm', 'Packs', 'wolfSSL', 'wolfSSL', '*', 'wolfSSL.pdsc') + )) + if pdsc_candidates: + pdsc_file = pdsc_candidates[-1] + pack_dir = os.path.dirname(pdsc_file) + pdsc = ET.parse(pdsc_file) + for f in pdsc.findall('.//file[@category="source"]'): + name = f.get('name', '') + if name.lower().endswith('.c'): + paths.add(os.path.normpath(os.path.join(pack_dir, name.replace('\\', '/')))) +else: + for file_elem in proj.findall('.//File'): + fp = file_elem.find('FilePath') + ft = file_elem.find('FileType') + if fp is None or not fp.text: + continue + ftype = int(ft.text) if ft is not None and ft.text else 0 + if ftype == 1 or fp.text.lower().endswith('.c'): + abs_path = os.path.normpath( + os.path.join(proj_dir, fp.text.replace('\\', '/')) + ) + paths.add(abs_path) + +for p in sorted(paths): + print(p) +PYEOF + if [ -s "$_auto" ]; then + _n=$(wc -l < "$_auto" | tr -d ' ') + echo " Auto-extracted $_n wolfssl sources from Keil project" + CRA_SBOM_SRCS_FILE="$_auto" + CRA_SBOM_SRCS_ONLY_FROM_FILE=true + return 0 + fi + echo "ERROR: CRA_SBOM_KEIL_PROJECT is set but no sources were extracted from $CRA_SBOM_KEIL_PROJECT." >&2 + exit 1 + fi + + # Method 4: IAR .ewp + if [ -n "${CRA_SBOM_IAR_PROJECT:-}" ]; then + if [ ! -f "$CRA_SBOM_IAR_PROJECT" ]; then + echo "ERROR: CRA_SBOM_IAR_PROJECT=$CRA_SBOM_IAR_PROJECT not found." >&2 + exit 1 + fi + if ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: python3 is required to parse an IAR .ewp file." >&2 + exit 1 + fi + _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") + _auto_tempfiles="${_auto_tempfiles:-} $_auto" + python3 - "$CRA_SBOM_IAR_PROJECT" > "$_auto" <<'PYEOF' +import sys, os, xml.etree.ElementTree as ET + +def is_excluded(file_elem): + return file_elem.find('excluded') is not None + +proj_file = sys.argv[1] +proj_dir = os.path.dirname(os.path.abspath(proj_file)) +proj = ET.parse(proj_file) +paths = set() + +for file_elem in proj.findall('.//file'): + if is_excluded(file_elem): + continue + name_elem = file_elem.find('name') + if name_elem is None or not name_elem.text: + continue + raw = name_elem.text + if not raw.lower().endswith('.c'): + continue + resolved = raw.replace('$PROJ_DIR$', proj_dir) + paths.add(os.path.normpath(resolved.replace('\\', '/'))) + +for p in sorted(paths): + print(p) +PYEOF + if [ -s "$_auto" ]; then + _n=$(wc -l < "$_auto" | tr -d ' ') + echo " Auto-extracted $_n wolfssl sources from IAR project" + CRA_SBOM_SRCS_FILE="$_auto" + CRA_SBOM_SRCS_ONLY_FROM_FILE=true + return 0 + fi + echo "ERROR: CRA_SBOM_IAR_PROJECT is set but no sources were extracted from $CRA_SBOM_IAR_PROJECT." >&2 + exit 1 + fi +} + _run_embedded() { echo "==> Embedded path: gen-sbom with CRA Kit user_settings.h" if [ ! -f "$KIT_DIR/user_settings.h" ]; then @@ -169,6 +354,10 @@ _run_embedded() { return 0 fi + # Auto-extract if caller didn't supply CRA_SBOM_SRCS_FILE + [ -z "${CRA_SBOM_SRCS_FILE:-}" ] && [ "${CRA_SBOM_NO_HASH:-}" != "true" ] && \ + _auto_extract_srcs + # Build the source file list. # # Priority: @@ -255,7 +444,7 @@ EOF # Clean up the temp defines file on every exit path, including a failing # generator run (it previously leaked the file under `set -e` if the # final gen-sbom invocation failed before the manual `rm -f`). - trap 'rm -f "$DEFINES_H"' EXIT + _auto_tempfiles="${_auto_tempfiles:-} $DEFINES_H" CC=${CC:-cc} if ! "$CC" -dM -E \ -I"$WOLFSSL_DIR" \ From c77b4a5ac708862f81f77a37315355e98f38b3b5 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 16:12:03 -0700 Subject: [PATCH 07/25] fix(cra-kit): fix two auto-extract bugs found in testing Makefile: add --no-print-directory to suppress make's Entering/Leaving directory banners, which leaked into the source list when the Makefile directory path contained "wolfssl". Keil CMSIS Pack: when the wolfSSL pack .pdsc is not installed locally (~/.arm/Packs/wolfSSL/), fall back to enumerating wolfcrypt/src/*.c and src/*.c from WOLFSSL_DIR. The full library is what the pack ships. --- cra-kit/scripts/generate-wolfssl-sbom.sh | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/cra-kit/scripts/generate-wolfssl-sbom.sh b/cra-kit/scripts/generate-wolfssl-sbom.sh index d2d270e8..7d72336e 100755 --- a/cra-kit/scripts/generate-wolfssl-sbom.sh +++ b/cra-kit/scripts/generate-wolfssl-sbom.sh @@ -170,8 +170,8 @@ _auto_extract_srcs() { fi _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") _auto_tempfiles="${_auto_tempfiles:-} $_auto" - if make -C "$CRA_SBOM_MAKEFILE_DIR" -n print-wolfssl-srcs >/dev/null 2>&1; then - make -C "$CRA_SBOM_MAKEFILE_DIR" print-wolfssl-srcs 2>/dev/null \ + if make --no-print-directory -C "$CRA_SBOM_MAKEFILE_DIR" -n print-wolfssl-srcs >/dev/null 2>&1; then + make --no-print-directory -C "$CRA_SBOM_MAKEFILE_DIR" print-wolfssl-srcs 2>/dev/null \ | sort -u > "$_auto" fi if [ ! -s "$_auto" ]; then @@ -203,16 +203,18 @@ _auto_extract_srcs() { fi _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") _auto_tempfiles="${_auto_tempfiles:-} $_auto" - python3 - "$CRA_SBOM_KEIL_PROJECT" > "$_auto" <<'PYEOF' + python3 - "$CRA_SBOM_KEIL_PROJECT" "$WOLFSSL_DIR" > "$_auto" <<'PYEOF' import sys, os, glob, xml.etree.ElementTree as ET proj_file = sys.argv[1] +wolfssl_dir = sys.argv[2] if len(sys.argv) > 2 else '' proj_dir = os.path.dirname(os.path.abspath(proj_file)) proj = ET.parse(proj_file) paths = set() rte = proj.find('.//RTE') if rte is not None and rte.find('.//component[@Cvendor="wolfSSL"]') is not None: + # CMSIS Pack RTE: sources come from the installed pack .pdsc pdsc_candidates = sorted(glob.glob( os.path.expanduser('~/.arm/Packs/wolfSSL/wolfSSL/*/wolfSSL.pdsc') )) @@ -229,6 +231,15 @@ if rte is not None and rte.find('.//component[@Cvendor="wolfSSL"]') is not None: name = f.get('name', '') if name.lower().endswith('.c'): paths.add(os.path.normpath(os.path.join(pack_dir, name.replace('\\', '/')))) + elif wolfssl_dir and os.path.isdir(wolfssl_dir): + # Pack not installed locally; enumerate sources directly from WOLFSSL_DIR. + # The CMSIS Pack contains the full wolfssl library (wolfcrypt/src/ + src/). + for subdir in ('wolfcrypt/src', 'src'): + d = os.path.join(wolfssl_dir, subdir) + if os.path.isdir(d): + for name in os.listdir(d): + if name.endswith('.c'): + paths.add(os.path.join(d, name)) else: for file_elem in proj.findall('.//File'): fp = file_elem.find('FilePath') From 8b558f3d5acb4a07e14a64c65c8d2fb492317abe Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 18:37:53 -0700 Subject: [PATCH 08/25] feat: add CRA Kit scripts for 6 wolfSSL products Generate auditor-ready SBOM packages for wolfSSH, wolfTPM, wolfMQTT, wolfHSM, wolfsentry, and wolfBoot. Each script mirrors the wolfssl SBOM workflow. --- cra-kit/scripts/generate-wolfboot-sbom.sh | 230 +++++++++++++++++ cra-kit/scripts/generate-wolfhsm-sbom.sh | 192 ++++++++++++++ cra-kit/scripts/generate-wolfmqtt-sbom.sh | 119 +++++++++ cra-kit/scripts/generate-wolfsentry-sbom.sh | 151 +++++++++++ cra-kit/scripts/generate-wolfssh-sbom.sh | 164 ++++++++++++ cra-kit/scripts/generate-wolftpm-sbom.sh | 272 ++++++++++++++++++++ 6 files changed, 1128 insertions(+) create mode 100755 cra-kit/scripts/generate-wolfboot-sbom.sh create mode 100755 cra-kit/scripts/generate-wolfhsm-sbom.sh create mode 100755 cra-kit/scripts/generate-wolfmqtt-sbom.sh create mode 100755 cra-kit/scripts/generate-wolfsentry-sbom.sh create mode 100755 cra-kit/scripts/generate-wolfssh-sbom.sh create mode 100755 cra-kit/scripts/generate-wolftpm-sbom.sh diff --git a/cra-kit/scripts/generate-wolfboot-sbom.sh b/cra-kit/scripts/generate-wolfboot-sbom.sh new file mode 100755 index 00000000..2a5f1d3f --- /dev/null +++ b/cra-kit/scripts/generate-wolfboot-sbom.sh @@ -0,0 +1,230 @@ +#!/bin/sh +# Generate wolfBoot component SBOMs via gen-sbom. +# +# wolfBoot is build-configuration-specific: its compiled source list depends +# on TARGET, SIGN, HASH, and EXT_FLASH. This script runs `make -n` against +# the wolfBoot tree to extract the exact set of .c files for the requested +# configuration, then calls gen-sbom directly. +# +# wolfcrypt sources are compiled directly into the wolfBoot image (there is +# no separate wolfssl shared library). They appear in OBJS alongside core +# wolfBoot sources and are therefore included as wolfBoot's own component +# sources, not as a separate dependency. +# +# Required variables: +# WOLFBOOT_DIR=path/to/wolfBoot (source tree root) +# WOLFBOOT_TARGET= (e.g. stm32h7, x86_64_efi) +# WOLFBOOT_SIGN= (e.g. ECC256, RSA2048, ED25519) +# +# Optional variables: +# WOLFBOOT_HASH= (default: SHA256) +# WOLFBOOT_EXT_FLASH=<0|1> (default: 0) +# CRA_PYTHON=python3 (Python interpreter with gen-sbom deps) +# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfBoot-Commercial) +# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a +# LicenseRef-* id: plain-text license +# embedded in the SBOM) +# CRA_SBOM_OUT_DIR= (override output directory) +set -eu + +SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +KIT_DIR=$(dirname "$SCRIPT_DIR") + +# Default wolfBoot directory: sibling of the wolfssl-examples checkout. +# shellcheck disable=SC2015 +WOLFBOOT_DIR=${WOLFBOOT_DIR:-$(cd "$KIT_DIR/../../wolfBoot" 2>/dev/null && pwd || true)} + +if [ -z "${WOLFBOOT_DIR:-}" ] || [ ! -d "$WOLFBOOT_DIR" ]; then + echo "ERROR: wolfBoot source not found." >&2 + echo " Set WOLFBOOT_DIR to your wolfBoot checkout." >&2 + exit 1 +fi + +if [ -z "${WOLFBOOT_TARGET:-}" ]; then + echo "ERROR: WOLFBOOT_TARGET is not set." >&2 + echo " Example: WOLFBOOT_TARGET=stm32h7 $0" >&2 + exit 1 +fi + +if [ -z "${WOLFBOOT_SIGN:-}" ]; then + echo "ERROR: WOLFBOOT_SIGN is not set." >&2 + echo " Example: WOLFBOOT_SIGN=ECC256 $0" >&2 + exit 1 +fi + +WOLFBOOT_HASH=${WOLFBOOT_HASH:-SHA256} +WOLFBOOT_EXT_FLASH=${WOLFBOOT_EXT_FLASH:-0} + +OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfboot-component"} + +# gen-sbom lives inside the wolfssl submodule under wolfBoot. +GEN_SBOM="$WOLFBOOT_DIR/lib/wolfssl/scripts/gen-sbom" +if [ ! -f "$GEN_SBOM" ]; then + echo "ERROR: gen-sbom not found at $GEN_SBOM" >&2 + echo " Ensure the wolfssl submodule is initialized:" >&2 + echo " git -C \"$WOLFBOOT_DIR\" submodule update --init lib/wolfssl" >&2 + exit 1 +fi + +# Extract version from wolfBoot's version header. +VERSION=$(sed -n \ + 's/.*LIBWOLFBOOT_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$WOLFBOOT_DIR/include/wolfboot/version.h") +if [ -z "$VERSION" ]; then + echo "ERROR: could not detect wolfBoot version from $WOLFBOOT_DIR/include/wolfboot/version.h" >&2 + exit 1 +fi + +mkdir -p "$OUT_DIR" +CDX_OUT="$OUT_DIR/wolfboot-${VERSION}.cdx.json" +SPDX_OUT="$OUT_DIR/wolfboot-${VERSION}.spdx.json" + +echo "wolfBoot tree: $WOLFBOOT_DIR" +echo "Configuration: TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN HASH=$WOLFBOOT_HASH EXT_FLASH=$WOLFBOOT_EXT_FLASH" +echo "Version: $VERSION" +echo "Outputs: $CDX_OUT" +echo " $SPDX_OUT" +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + echo "License override: $CRA_LICENSE_OVERRIDE" +fi + +# A LicenseRef-* override requires the license text to be embedded in the SBOM +# (SPDX 2.3 §10.1). gen-sbom hard-fails without it; catch the omission here. +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + case "$CRA_LICENSE_OVERRIDE" in + LicenseRef-*) + if [ -z "${CRA_LICENSE_TEXT:-}" ]; then + echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2 + echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the license text to be" >&2 + echo " embedded for any LicenseRef-* used in licenseConcluded/licenseDeclared." >&2 + echo " Re-run with CRA_LICENSE_TEXT=/path/to/wolfboot-license.txt" >&2 + exit 1 + fi + if [ ! -f "$CRA_LICENSE_TEXT" ]; then + echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2 + exit 1 + fi + ;; + esac +fi + +# Canonicalize CRA_LICENSE_TEXT to an absolute path. +if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then + CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT") +fi + +# Extract the configuration-specific source list via `make -n`. +# +# `make -n TARGET=... SIGN=...` prints every command make would run without +# executing them. The compiler invocations include every .c file on the +# wolfBoot link line, including both core wolfBoot sources and wolfcrypt +# files compiled inline. grep -oE pulls out every .c argument; sort -u +# deduplicates. +echo "Extracting source list via make -n TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN ..." + +_srcs_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-srcs.XXXXXX") +trap 'rm -f "$_srcs_tmp"' EXIT + +make --no-print-directory \ + -C "$WOLFBOOT_DIR" \ + -n \ + TARGET="$WOLFBOOT_TARGET" \ + SIGN="$WOLFBOOT_SIGN" \ + HASH="$WOLFBOOT_HASH" \ + EXT_FLASH="$WOLFBOOT_EXT_FLASH" \ + 2>/dev/null \ + | grep -oE '[^ ]+\.c' \ + | grep -v '\.h' \ + | sort -u > "$_srcs_tmp" || true + +if [ ! -s "$_srcs_tmp" ]; then + echo "ERROR: make -n yielded no .c source files for TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN." >&2 + echo " Check that TARGET and SIGN are valid for this wolfBoot tree." >&2 + exit 1 +fi + +_n=$(wc -l < "$_srcs_tmp" | tr -d ' ') +echo " Extracted $_n source files" + +# Resolve paths to absolute: make -n emits relative paths; gen-sbom needs to +# open the files to compute gitoid hashes. +_srcs_abs_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-srcs-abs.XXXXXX") +trap 'rm -f "$_srcs_tmp" "$_srcs_abs_tmp"' EXIT + +while IFS= read -r _src; do + [ -n "$_src" ] || continue + # Paths from make -n are relative to wolfBoot root. + if [ "${_src#/}" = "$_src" ]; then + _abs="$WOLFBOOT_DIR/$_src" + else + _abs="$_src" + fi + # Skip paths that do not exist on disk (generated files, stubs, etc.). + if [ -f "$_abs" ]; then + echo "$_abs" + fi +done < "$_srcs_tmp" > "$_srcs_abs_tmp" + +if [ ! -s "$_srcs_abs_tmp" ]; then + echo "ERROR: no source files from make -n exist on disk." >&2 + echo " Verify WOLFBOOT_DIR=$WOLFBOOT_DIR and submodules are initialized." >&2 + exit 1 +fi + +_n_abs=$(wc -l < "$_srcs_abs_tmp" | tr -d ' ') +echo " Resolved $_n_abs paths ($(( _n - _n_abs )) non-existent skipped)" + +# Preprocess build settings for gen-sbom --options-h. +# +# wolfBoot has no options.h (it is not an autotools project). We use +# cc -dM -E on the host with wolfBoot's include dirs to produce a flat +# #define file that gen-sbom can parse for algorithm enablement. +_defines_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-defines.XXXXXX") +trap 'rm -f "$_srcs_tmp" "$_srcs_abs_tmp" "$_defines_tmp"' EXIT + +CC=${CC:-cc} +echo " Preprocessing build settings via $CC -dM -E ..." +if ! "$CC" -dM -E \ + -I"$WOLFBOOT_DIR/include" \ + -I"$WOLFBOOT_DIR/lib/wolfssl" \ + -I"$WOLFBOOT_DIR/lib/wolfssl/wolfcrypt/src" \ + -DWOLFSSL_USER_SETTINGS \ + -x c /dev/null > "$_defines_tmp" 2>/dev/null; then + echo "ERROR: $CC -dM -E failed; install a host C compiler or set CC." >&2 + exit 1 +fi + +# Build gen-sbom argument list. +_PYTHON=${CRA_PYTHON:-python3} +command -v "$_PYTHON" >/dev/null 2>&1 || \ + { echo "ERROR: $_PYTHON not found. Set CRA_PYTHON to your Python interpreter." >&2; exit 1; } + +# Read absolute source paths into positional parameters. +set -- +while IFS= read -r _src; do + [ -n "$_src" ] || continue + set -- "$@" "$_src" +done < "$_srcs_abs_tmp" + +_license_override=${CRA_LICENSE_OVERRIDE:-GPL-3.0-only} +set -- "$@" \ + --cdx-out "$CDX_OUT" \ + --spdx-out "$SPDX_OUT" \ + --license-override "$_license_override" + +if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + set -- "$@" --license-text "$CRA_LICENSE_TEXT" +fi + +echo "==> Running gen-sbom ..." +"$_PYTHON" "$GEN_SBOM" \ + --name wolfboot \ + --version "$VERSION" \ + --supplier "wolfSSL Inc." \ + --license-file "$WOLFBOOT_DIR/LICENSE" \ + --options-h "$_defines_tmp" \ + --srcs "$@" + +echo "SBOM written:" +echo " $CDX_OUT" +echo " $SPDX_OUT" diff --git a/cra-kit/scripts/generate-wolfhsm-sbom.sh b/cra-kit/scripts/generate-wolfhsm-sbom.sh new file mode 100755 index 00000000..0fee3883 --- /dev/null +++ b/cra-kit/scripts/generate-wolfhsm-sbom.sh @@ -0,0 +1,192 @@ +#!/bin/sh +# Generate wolfHSM component SBOM (embedded gen-sbom path). +# +# wolfHSM is a Makefile-only library with no autotools configure step. +# This script always uses the embedded gen-sbom path: it enumerates +# wolfHSM sources directly from the source tree and derives compile-time +# defines via CC -dM -E (or pcpp when available). +# +# Required variables: +# WOLFSSL_DIR=path/to/wolfssl (source tree root; must contain scripts/gen-sbom) +# WOLFHSM_DIR=path/to/wolfHSM (source tree root) +# +# Optional variables: +# CC= (default: cc; set for cross builds) +# CRA_PYTHON=python3 (interpreter with pcpp) +# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial) +# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is LicenseRef-*) +set -eu + +# Accumulator for temp files; cleaned up on exit. +_auto_tempfiles="" +trap 'rm -f ${_auto_tempfiles:-}' EXIT + +SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +KIT_DIR=$(dirname "$SCRIPT_DIR") + +# Locate WOLFSSL_DIR (default: sibling of wolfssl-examples). +# shellcheck disable=SC2015 +WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} +# WOLFHSM_DIR has no sensible default; must be explicit. +WOLFHSM_DIR=${WOLFHSM_DIR:-} + +if [ -z "${WOLFSSL_DIR:-}" ] || [ ! -d "$WOLFSSL_DIR" ]; then + echo "ERROR: wolfSSL source not found." >&2 + echo " Set WOLFSSL_DIR to your wolfssl checkout." >&2 + exit 1 +fi + +if [ -z "${WOLFHSM_DIR:-}" ] || [ ! -d "$WOLFHSM_DIR" ]; then + echo "ERROR: WOLFHSM_DIR is not set or not a directory." >&2 + echo " Set WOLFHSM_DIR to your wolfHSM source tree." >&2 + exit 1 +fi + +GEN="$WOLFSSL_DIR/scripts/gen-sbom" +if [ ! -f "$GEN" ]; then + echo "ERROR: $GEN not found (need wolfSSL with SBOM support)." >&2 + exit 1 +fi + +# Parse version from ChangeLog.md: first line matching "## wolfHSM Release vX.Y.Z" +VERSION=$(sed -n 's/^# wolfHSM Release v\([0-9][0-9.]*\).*/\1/p' \ + "$WOLFHSM_DIR/ChangeLog.md" 2>/dev/null | head -1) +if [ -z "$VERSION" ]; then + echo "ERROR: could not parse version from $WOLFHSM_DIR/ChangeLog.md." >&2 + exit 1 +fi + +OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfhsm-component"} +mkdir -p "$OUT_DIR" +CDX_OUT="$OUT_DIR/wolfhsm-${VERSION}.cdx.json" +SPDX_OUT="$OUT_DIR/wolfhsm-${VERSION}.spdx.json" + +echo "wolfHSM tree: $WOLFHSM_DIR" +echo "wolfSSL tree: $WOLFSSL_DIR" +echo "Version: $VERSION" +echo "Outputs: $CDX_OUT" +echo " $SPDX_OUT" +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + echo "License override: $CRA_LICENSE_OVERRIDE" +fi + +# A LicenseRef-* override requires the licence text to be embedded (SPDX 2.3 §10.1). +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + case "$CRA_LICENSE_OVERRIDE" in + LicenseRef-*) + if [ -z "${CRA_LICENSE_TEXT:-}" ]; then + echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2 + echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the licence text" >&2 + echo " to be embedded. Re-run with CRA_LICENSE_TEXT=/path/to/license.txt." >&2 + exit 1 + fi + if [ ! -f "$CRA_LICENSE_TEXT" ]; then + echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2 + exit 1 + fi + ;; + esac +fi + +# Canonicalize CRA_LICENSE_TEXT to absolute path (subshells resolve relative paths +# against their own CWD; gen-sbom may be invoked from a different directory). +if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then + CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT") + echo "License text: $CRA_LICENSE_TEXT" +fi + +# Pick a Python that can `import pcpp`. +_python_with_pcpp() { + for py in ${CRA_PYTHON:-} python3 python; do + [ -n "$py" ] || continue + if command -v "$py" >/dev/null 2>&1 && \ + "$py" -c "import pcpp" 2>/dev/null; then + echo "$py" + return 0 + fi + done + return 1 +} + +# Enumerate wolfHSM sources: all *.c directly under src/ (no recursion). +_wolfhsm_srcs() { + find "$WOLFHSM_DIR/src" -maxdepth 1 -name "*.c" | sort +} + +echo "==> Embedded path: gen-sbom with CC -dM -E (no user_settings.h)" + +# Write collected source paths to a temp file for --srcs-file. +_srcs_file=$(mktemp "${TMPDIR:-/tmp}/wolfhsm-srcs.XXXXXX") +_auto_tempfiles="${_auto_tempfiles:-} $_srcs_file" +_wolfhsm_srcs > "$_srcs_file" +_n=$(wc -l < "$_srcs_file" | tr -d ' ') +echo " Sources: $_n .c files from $WOLFHSM_DIR/src/" +if [ ! -s "$_srcs_file" ]; then + echo "ERROR: no .c files found under $WOLFHSM_DIR/src/." >&2 + exit 1 +fi + +# Build license-override args. +_license_args="" +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + _license_args="--license-override $CRA_LICENSE_OVERRIDE" + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + _license_args="$_license_args --license-text $CRA_LICENSE_TEXT" + fi +fi + +if _py=$(_python_with_pcpp); then + echo " Using $_py (pcpp) for --user-settings" + # wolfHSM has no user_settings.h equivalent; pass settings.h from wolfssl + # so gen-sbom has a preprocessable configuration source. The include path + # covers wolfHSM headers and the wolfssl tree. + SETTINGS_H="$WOLFSSL_DIR/wolfssl/wolfcrypt/settings.h" + if [ ! -f "$SETTINGS_H" ]; then + echo "ERROR: $SETTINGS_H not found." >&2 + exit 1 + fi + # shellcheck disable=SC2086 + "$_py" "$GEN" \ + --name wolfhsm \ + --version "$VERSION" \ + --supplier "wolfSSL Inc." \ + --license-file "$WOLFHSM_DIR/LICENSING" \ + --user-settings "$SETTINGS_H" \ + --user-settings-include "$WOLFHSM_DIR" \ + --user-settings-include "$WOLFSSL_DIR" \ + --srcs-file "$_srcs_file" \ + --cdx-out "$CDX_OUT" \ + --spdx-out "$SPDX_OUT" \ + ${_license_args} +else + echo "NOTE: pcpp not found; using CC -dM -E -> --options-h" + echo " Install pcpp: python3 -m pip install pcpp" + echo " For cross builds: set CC=" + + CC=${CC:-cc} + _defines=$(mktemp "${TMPDIR:-/tmp}/wolfhsm-defines.XXXXXX") + _auto_tempfiles="${_auto_tempfiles:-} $_defines" + if ! "$CC" -dM -E \ + -I"$WOLFHSM_DIR" \ + -I"$WOLFSSL_DIR" \ + -x c /dev/null >"$_defines" 2>/dev/null; then + echo "ERROR: $CC -dM -E failed; install pcpp or set CC to your cross-compiler." >&2 + exit 1 + fi + + _python=python3 + command -v python3 >/dev/null 2>&1 || _python=python + # shellcheck disable=SC2086 + "$_python" "$GEN" \ + --name wolfhsm \ + --version "$VERSION" \ + --supplier "wolfSSL Inc." \ + --license-file "$WOLFHSM_DIR/LICENSING" \ + --options-h "$_defines" \ + --srcs-file "$_srcs_file" \ + --cdx-out "$CDX_OUT" \ + --spdx-out "$SPDX_OUT" \ + ${_license_args} +fi + +echo "Done." diff --git a/cra-kit/scripts/generate-wolfmqtt-sbom.sh b/cra-kit/scripts/generate-wolfmqtt-sbom.sh new file mode 100755 index 00000000..5dd4ec3d --- /dev/null +++ b/cra-kit/scripts/generate-wolfmqtt-sbom.sh @@ -0,0 +1,119 @@ +#!/bin/sh +# Generate wolfMQTT component SBOM (autotools make sbom path). +# +# wolfMQTT uses autotools. This script runs `make sbom` inside the wolfMQTT +# source tree and copies the resulting SBOM files to the auditor packet. +# +# Required variables: +# WOLFSSL_DIR=path/to/wolfssl (source tree root; must contain scripts/gen-sbom) +# WOLFMQTT_DIR=path/to/wolfMQTT (source tree root) +# +# Optional variables: +# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial) +# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is LicenseRef-*) +# CRA_SBOM_OUT_DIR= (default: /auditor-packet/wolfmqtt-component) +set -eu + +SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +KIT_DIR=$(dirname "$SCRIPT_DIR") + +# shellcheck disable=SC2015 +WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} +WOLFMQTT_DIR=${WOLFMQTT_DIR:-} + +if [ -z "${WOLFSSL_DIR:-}" ] || [ ! -d "$WOLFSSL_DIR" ]; then + echo "ERROR: wolfSSL source not found." >&2 + echo " Set WOLFSSL_DIR to your wolfssl checkout (contains scripts/gen-sbom)." >&2 + exit 1 +fi + +if [ -z "${WOLFMQTT_DIR:-}" ] || [ ! -d "$WOLFMQTT_DIR" ]; then + echo "ERROR: WOLFMQTT_DIR is not set or not a directory." >&2 + echo " Set WOLFMQTT_DIR to your wolfMQTT source tree." >&2 + exit 1 +fi + +GEN="$WOLFSSL_DIR/scripts/gen-sbom" +if [ ! -f "$GEN" ]; then + echo "ERROR: $GEN not found (need wolfSSL with SBOM support)." >&2 + exit 1 +fi + +# Parse version from wolfmqtt/version.h. +VERSION=$(sed -n \ + 's/.*LIBWOLFMQTT_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$WOLFMQTT_DIR/wolfmqtt/version.h" 2>/dev/null || true) +if [ -z "$VERSION" ]; then + echo "ERROR: could not parse version from $WOLFMQTT_DIR/wolfmqtt/version.h." >&2 + exit 1 +fi + +OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfmqtt-component"} +mkdir -p "$OUT_DIR" +CDX_OUT="$OUT_DIR/wolfmqtt-${VERSION}.cdx.json" +SPDX_OUT="$OUT_DIR/wolfmqtt-${VERSION}.spdx.json" + +echo "wolfMQTT tree: $WOLFMQTT_DIR" +echo "wolfSSL tree: $WOLFSSL_DIR" +echo "Version: $VERSION" +echo "Outputs: $CDX_OUT" +echo " $SPDX_OUT" +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + echo "License override: $CRA_LICENSE_OVERRIDE" +fi + +# A LicenseRef-* override requires the licence text to be embedded (SPDX 2.3 §10.1). +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + case "$CRA_LICENSE_OVERRIDE" in + LicenseRef-*) + if [ -z "${CRA_LICENSE_TEXT:-}" ]; then + echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2 + echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the licence text" >&2 + echo " to be embedded. Re-run with CRA_LICENSE_TEXT=/path/to/license.txt." >&2 + exit 1 + fi + if [ ! -f "$CRA_LICENSE_TEXT" ]; then + echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2 + exit 1 + fi + ;; + esac +fi + +# Canonicalize CRA_LICENSE_TEXT to an absolute path: make sbom runs inside a +# subshell `cd "$WOLFMQTT_DIR"`, where a relative path would resolve against +# the wolfMQTT tree rather than the caller's CWD. +if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then + CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT") + echo "License text: $CRA_LICENSE_TEXT" +fi + +echo "==> Autotools path: make sbom" + +# Detect whether the wolfMQTT tree is already configured; run ./configure first +# if no Makefile is present. +(cd "$WOLFMQTT_DIR" && { + if [ ! -f Makefile ]; then + echo " Running ./configure first (WOLFSSL_DIR=$WOLFSSL_DIR)..." + ./configure --with-wolfssl="$WOLFSSL_DIR" + fi + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ + SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" \ + SBOM_LICENSE_TEXT="$CRA_LICENSE_TEXT" + else + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ + SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" + fi + else + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" + fi + cp -f "wolfmqtt-${VERSION}.cdx.json" "$CDX_OUT" + cp -f "wolfmqtt-${VERSION}.spdx.json" "$SPDX_OUT" + if [ -f "wolfmqtt-${VERSION}.spdx" ]; then + cp -f "wolfmqtt-${VERSION}.spdx" "$OUT_DIR/" + fi +}) + +echo "Done." diff --git a/cra-kit/scripts/generate-wolfsentry-sbom.sh b/cra-kit/scripts/generate-wolfsentry-sbom.sh new file mode 100755 index 00000000..a2aaf8c5 --- /dev/null +++ b/cra-kit/scripts/generate-wolfsentry-sbom.sh @@ -0,0 +1,151 @@ +#!/bin/sh +# Generate wolfSentry component SBOMs via gen-sbom. +# +# Required variables: +# WOLFSENTRY_DIR=path/to/wolfsentry (source tree root) +# +# gen-sbom location (one required): +# WOLFSSL_DIR=path/to/wolfssl (gen-sbom taken from scripts/gen-sbom) +# CRA_GEN_SBOM=path/to/gen-sbom (direct path; overrides WOLFSSL_DIR) +# +# Optional variables: +# CRA_SBOM_OUT_DIR= (default: $KIT_DIR/auditor-packet/wolfsentry-component) +# CC= (default: cc; for -dM -E options dump) +set -eu + +SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +KIT_DIR=$(dirname "$SCRIPT_DIR") + +# shellcheck disable=SC2015 +WOLFSENTRY_DIR=${WOLFSENTRY_DIR:-$(cd "$KIT_DIR/../../wolfsentry" 2>/dev/null && pwd || true)} +OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfsentry-component"} + +if [ -z "${WOLFSENTRY_DIR:-}" ] || [ ! -d "$WOLFSENTRY_DIR" ]; then + echo "ERROR: wolfSentry source not found." >&2 + echo " Set WOLFSENTRY_DIR to your wolfsentry checkout (sibling of wolfssl-examples)." >&2 + exit 1 +fi + +# Resolve gen-sbom: CRA_GEN_SBOM takes precedence, then WOLFSSL_DIR. +if [ -n "${CRA_GEN_SBOM:-}" ]; then + GEN_SBOM="$CRA_GEN_SBOM" +elif [ -n "${WOLFSSL_DIR:-}" ]; then + GEN_SBOM="$WOLFSSL_DIR/scripts/gen-sbom" +else + echo "ERROR: gen-sbom location not specified." >&2 + echo " Set WOLFSSL_DIR (path to wolfssl repo) or CRA_GEN_SBOM (direct path to gen-sbom)." >&2 + exit 1 +fi + +if [ ! -f "$GEN_SBOM" ]; then + echo "ERROR: gen-sbom not found: $GEN_SBOM" >&2 + exit 1 +fi + +# Extract version from wolfsentry/wolfsentry.h macros. +HEADER="$WOLFSENTRY_DIR/wolfsentry/wolfsentry.h" +if [ ! -f "$HEADER" ]; then + echo "ERROR: version header not found: $HEADER" >&2 + exit 1 +fi +VERSION=$(awk ' + /WOLFSENTRY_VERSION_MAJOR/ { maj=$3 } + /WOLFSENTRY_VERSION_MINOR/ { min=$3 } + /WOLFSENTRY_VERSION_TINY/ { tiny=$3 } + END { print maj"."min"."tiny } +' "$HEADER") +if [ -z "$VERSION" ] || [ "$VERSION" = ".." ]; then + echo "ERROR: could not extract version from $HEADER" >&2 + exit 1 +fi + +mkdir -p "$OUT_DIR" +CDX_OUT="$OUT_DIR/wolfsentry-${VERSION}.cdx.json" +SPDX_OUT="$OUT_DIR/wolfsentry-${VERSION}.spdx.json" + +echo "wolfSentry tree: $WOLFSENTRY_DIR" +echo "Version: $VERSION" +echo "gen-sbom: $GEN_SBOM" +echo "Outputs: $CDX_OUT" +echo " $SPDX_OUT" + +# Enumerate all .c sources from $WOLFSENTRY_DIR/src/ (the directory the Makefile +# compiles from; conditional sources like json/ and lwip/ are subdirs of src/). +SRCS=$(find "$WOLFSENTRY_DIR/src" -name "*.c" | sort) +if [ -z "$SRCS" ]; then + echo "ERROR: no .c files found under $WOLFSENTRY_DIR/src/" >&2 + exit 1 +fi +_n=$(echo "$SRCS" | wc -l | tr -d ' ') +echo "Sources: $_n .c files from $WOLFSENTRY_DIR/src/" + +# Dump compiler defines for --options-h (no user_settings.h; wolfsentry is +# configured via Makefile flags, not a settings header). +CC=${CC:-cc} +_defines_h=$(mktemp "${TMPDIR:-/tmp}/wolfsentry-defines.XXXXXX") +trap 'rm -f "$_defines_h"' EXIT +if ! "$CC" -dM -E -I"$WOLFSENTRY_DIR" -x c /dev/null >"$_defines_h" 2>/dev/null; then + echo "ERROR: $CC -dM -E failed; set CC to an available compiler." >&2 + exit 1 +fi + +if ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: python3 not found in PATH." >&2 + exit 1 +fi + +# Build --srcs argument list from the source enumeration. +# shellcheck disable=SC2086 +set -- $SRCS + +python3 "$GEN_SBOM" \ + --name wolfsentry \ + --version "$VERSION" \ + --supplier "wolfSSL Inc." \ + --license-file "$WOLFSENTRY_DIR/LICENSING" \ + --options-h "$_defines_h" \ + --srcs "$@" \ + --cdx-out "$CDX_OUT" \ + --spdx-out "$SPDX_OUT" + +# Post-process: rewrite pkg:generic/wolfsentry@X -> pkg:github/wolfSSL/wolfsentry@vX +# gen-sbom emits pkg:generic/{name}@{version} for non-wolfssl names; the canonical +# PURL for wolfsentry is the GitHub package form. +if ! CDX_OUT="$CDX_OUT" SPDX_OUT="$SPDX_OUT" VERSION="$VERSION" \ +python3 <<'PY' +import json, os, pathlib + +cdx = pathlib.Path(os.environ["CDX_OUT"]) +spdx = pathlib.Path(os.environ["SPDX_OUT"]) +ver = os.environ["VERSION"] + +GENERIC = "pkg:generic/wolfsentry@" +GITHUB = "pkg:github/wolfSSL/wolfsentry@v" + +def fix(s): + if isinstance(s, str) and s.startswith(GENERIC): + return GITHUB + s[len(GENERIC):] + return s + +if cdx.exists(): + d = json.loads(cdx.read_text()) + comp = d.get("metadata", {}).get("component", {}) + comp["purl"] = fix(comp.get("purl", "")) + cdx.write_text(json.dumps(d, indent=2) + "\n") + print(f"Post-processed {cdx.name}: PURL -> {comp['purl']}") + +if spdx.exists(): + d = json.loads(spdx.read_text()) + for pkg in d.get("packages", []): + for ref in pkg.get("externalRefs", []): + if ref.get("referenceType") == "purl": + ref["referenceLocator"] = fix(ref.get("referenceLocator", "")) + spdx.write_text(json.dumps(d, indent=2) + "\n") + print(f"Post-processed {spdx.name}: PURL canonicalized") +PY +then + echo "ERROR: PURL post-processing failed; SBOM may carry pkg:generic PURLs." >&2 + exit 1 +fi + +echo "Done." diff --git a/cra-kit/scripts/generate-wolfssh-sbom.sh b/cra-kit/scripts/generate-wolfssh-sbom.sh new file mode 100755 index 00000000..4c5730da --- /dev/null +++ b/cra-kit/scripts/generate-wolfssh-sbom.sh @@ -0,0 +1,164 @@ +#!/bin/sh +# Generate wolfSSH component SBOM (autotools make sbom). +# +# Required variables: +# WOLFSSL_DIR=path/to/wolfssl (source tree root; provides gen-sbom) +# WOLFSSH_DIR=path/to/wolfssh (source tree root) +# +# Optional variables: +# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSH-Commercial) +# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a +# LicenseRef-* id: plain-text licence embedded +# in the SBOM; make sbom hard-fails without it.) +set -eu + +SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +KIT_DIR=$(dirname "$SCRIPT_DIR") +# shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else +WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} +WOLFSSH_DIR=${WOLFSSH_DIR:-$(cd "$KIT_DIR/../../wolfSSH" 2>/dev/null && pwd || true)} +OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfssh-component"} + +if [ -z "${WOLFSSL_DIR:-}" ] || [ ! -d "$WOLFSSL_DIR" ]; then + echo "ERROR: wolfSSL source not found." >&2 + echo " Set WOLFSSL_DIR to your wolfssl checkout (sibling of wolfssl-examples)." >&2 + exit 1 +fi + +if [ -z "${WOLFSSH_DIR:-}" ] || [ ! -d "$WOLFSSH_DIR" ]; then + echo "ERROR: wolfSSH source not found." >&2 + echo " Set WOLFSSH_DIR to your wolfSSH checkout (sibling of wolfssl-examples)." >&2 + exit 1 +fi + +VERSION=$(sed -n \ + 's/.*LIBWOLFSSH_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$WOLFSSH_DIR/wolfssh/version.h" 2>/dev/null || true) +if [ -z "$VERSION" ]; then + echo "ERROR: could not extract LIBWOLFSSH_VERSION_STRING from $WOLFSSH_DIR/wolfssh/version.h" >&2 + exit 1 +fi + +mkdir -p "$OUT_DIR" +CDX_OUT="$OUT_DIR/wolfssh-${VERSION}.cdx.json" +SPDX_OUT="$OUT_DIR/wolfssh-${VERSION}.spdx.json" + +echo "wolfSSL tree: $WOLFSSL_DIR" +echo "wolfSSH tree: $WOLFSSH_DIR" +echo "Outputs: $CDX_OUT" +echo " $SPDX_OUT" +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + echo "License override: $CRA_LICENSE_OVERRIDE" +fi + +# A LicenseRef-* override (e.g. the commercial license) requires the actual +# licence text to be embedded in the SBOM (SPDX 2.3 §10.1). Both gen-sbom and +# `make sbom` hard-fail without it, so catch the omission here with an +# actionable message instead of letting the run die deep in the generator. +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + case "$CRA_LICENSE_OVERRIDE" in + LicenseRef-*) + if [ -z "${CRA_LICENSE_TEXT:-}" ]; then + echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2 + echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the licence text to be" >&2 + echo " embedded for any LicenseRef-* used in licenseConcluded/licenseDeclared." >&2 + echo " Re-run with CRA_LICENSE_TEXT=/path/to/wolfssh-commercial-license.txt." >&2 + exit 1 + fi + if [ ! -f "$CRA_LICENSE_TEXT" ]; then + echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2 + exit 1 + fi + ;; + esac +fi + +# Canonicalize CRA_LICENSE_TEXT to an absolute path: make sbom runs inside a +# `cd "$WOLFSSH_DIR"` subshell, where a relative path would resolve against the +# wolfSSH tree rather than the caller's CWD. +if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then + CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT") + echo "License text: $CRA_LICENSE_TEXT" +fi + +_run_autotools() { + echo "==> Autotools path: make sbom" + # `make sbom` names its output after the wolfSSH TREE's version + # (PACKAGE_VERSION). Detect mismatches early so the cp below doesn't fail + # with a cryptic "No such file or directory" under `set -eu`. + _tree_ver=$(sed -n \ + 's/.*LIBWOLFSSH_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$WOLFSSH_DIR/wolfssh/version.h" 2>/dev/null || true) + if [ -n "$_tree_ver" ] && [ "$_tree_ver" != "$VERSION" ]; then + echo "ERROR: wolfSSH tree is version $_tree_ver but expected $VERSION." >&2 + exit 1 + fi + (cd "$WOLFSSH_DIR" && { + if [ ! -f Makefile ]; then + echo " Running ./configure first..." + ./configure --with-wolfssl="$WOLFSSL_DIR" + fi + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ + SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" \ + SBOM_LICENSE_TEXT="$CRA_LICENSE_TEXT" + else + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ + SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" + fi + else + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" + fi + cp -f "wolfssh-${VERSION}.cdx.json" "$CDX_OUT" + cp -f "wolfssh-${VERSION}.spdx.json" "$SPDX_OUT" + if [ -f "wolfssh-${VERSION}.spdx" ]; then + cp -f "wolfssh-${VERSION}.spdx" "$OUT_DIR/" + fi + }) +} + +_run_autotools + +# ---- Post-process: defensive PURL canonicalization ---- +# Current gen-sbom already emits pkg:github/wolfSSL/wolfSSH@vX natively for +# known component names; the rewrite below is a defensive no-op kept only for +# older generator versions that may emit pkg:generic/wolfssh@X. +if ! CDX_OUT="$CDX_OUT" SPDX_OUT="$SPDX_OUT" \ + python3 <<'PY' +import json, os, pathlib + +cdx = pathlib.Path(os.environ["CDX_OUT"]) +spdx = pathlib.Path(os.environ["SPDX_OUT"]) + +GENERIC = "pkg:generic/wolfssh@" +GITHUB = "pkg:github/wolfSSL/wolfSSH@v" + +def canonicalize_purl(s): + if isinstance(s, str) and s.startswith(GENERIC): + return GITHUB + s[len(GENERIC):] + return s + +if cdx.exists(): + d = json.loads(cdx.read_text()) + comp = d.get("metadata", {}).get("component", {}) + comp["purl"] = canonicalize_purl(comp.get("purl", "")) + cdx.write_text(json.dumps(d, indent=2) + "\n") + print(f"Post-processed {cdx.name}") + +if spdx.exists(): + d = json.loads(spdx.read_text()) + for pkg in d.get("packages", []): + for ref in pkg.get("externalRefs", []): + if ref.get("referenceType") == "purl": + ref["referenceLocator"] = canonicalize_purl(ref.get("referenceLocator", "")) + spdx.write_text(json.dumps(d, indent=2) + "\n") + print(f"Post-processed {spdx.name}") +PY +then + echo "ERROR: post-process failed (PURL canonicalization incomplete)." >&2 + echo " The emitted SBOM may carry pkg:generic PURLs; not trusting it." >&2 + exit 1 +fi + +echo "Done." diff --git a/cra-kit/scripts/generate-wolftpm-sbom.sh b/cra-kit/scripts/generate-wolftpm-sbom.sh new file mode 100755 index 00000000..96af1cea --- /dev/null +++ b/cra-kit/scripts/generate-wolftpm-sbom.sh @@ -0,0 +1,272 @@ +#!/bin/sh +# Generate wolfTPM component SBOMs (autotools make sbom, cmake sbom, or direct gen-sbom). +# +# Mode selection: +# CRA_SBOM_MODE=autotools|cmake +# autotools (default when configure+Makefile exist): runs `make sbom` +# cmake: auto-extracts sources from compile_commands.json and runs gen-sbom directly +# +# Required variables: +# WOLFSSL_DIR=path/to/wolfssl (source tree root; provides gen-sbom) +# WOLFTPM_DIR=path/to/wolftpm (source tree root) +# +# Mode-specific variables: +# WOLFTPM_BUILD_DIR=path/to/build (cmake mode: path to cmake build directory; +# triggers compile_commands.json auto-extraction) +# +# Optional variables: +# CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfTPM-Commercial) +# CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a +# LicenseRef-* id: plain-text licence embedded +# in the SBOM; gen-sbom / make sbom hard-fail +# without it.) +set -eu + +SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +KIT_DIR=$(dirname "$SCRIPT_DIR") +# shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else +WOLFTPM_DIR=${WOLFTPM_DIR:-$(cd "$KIT_DIR/../../wolftpm" 2>/dev/null && pwd || true)} +WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} +OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolftpm-component"} + +if [ -z "${WOLFTPM_DIR:-}" ] || [ ! -d "$WOLFTPM_DIR" ]; then + echo "ERROR: wolfTPM source not found." >&2 + echo " Set WOLFTPM_DIR to your wolftpm checkout." >&2 + exit 1 +fi + +if [ -z "${WOLFSSL_DIR:-}" ] || [ ! -d "$WOLFSSL_DIR" ]; then + echo "ERROR: wolfSSL source not found (needed for gen-sbom)." >&2 + echo " Set WOLFSSL_DIR to your wolfssl checkout." >&2 + exit 1 +fi + +GEN="$WOLFSSL_DIR/scripts/gen-sbom" +if [ ! -f "$GEN" ]; then + echo "ERROR: $GEN not found (need wolfSSL with SBOM support)." >&2 + exit 1 +fi + +VERSION=$(sed -n \ + 's/.*LIBWOLFTPM_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$WOLFTPM_DIR/wolftpm/version.h" 2>/dev/null || true) +if [ -z "$VERSION" ]; then + echo "ERROR: could not extract version from $WOLFTPM_DIR/wolftpm/version.h" >&2 + exit 1 +fi + +mkdir -p "$OUT_DIR" +CDX_OUT="$OUT_DIR/wolftpm-${VERSION}.cdx.json" +SPDX_OUT="$OUT_DIR/wolftpm-${VERSION}.spdx.json" + +echo "wolfTPM tree: $WOLFTPM_DIR" +echo "wolfSSL tree: $WOLFSSL_DIR" +echo "Outputs: $CDX_OUT" +echo " $SPDX_OUT" +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + echo "License override: $CRA_LICENSE_OVERRIDE" +fi + +# A LicenseRef-* override requires the actual licence text to be embedded +# in the SBOM (SPDX 2.3 §10.1). Catch the omission early. +if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + case "$CRA_LICENSE_OVERRIDE" in + LicenseRef-*) + if [ -z "${CRA_LICENSE_TEXT:-}" ]; then + echo "ERROR: CRA_LICENSE_OVERRIDE=$CRA_LICENSE_OVERRIDE is a LicenseRef-* identifier," >&2 + echo " but CRA_LICENSE_TEXT is not set. SPDX 2.3 requires the licence text to be" >&2 + echo " embedded for any LicenseRef-* used in licenseConcluded/licenseDeclared." >&2 + echo " Re-run with CRA_LICENSE_TEXT=/path/to/wolftpm-commercial-license.txt" >&2 + exit 1 + fi + if [ ! -f "$CRA_LICENSE_TEXT" ]; then + echo "ERROR: CRA_LICENSE_TEXT=$CRA_LICENSE_TEXT not found." >&2 + exit 1 + fi + ;; + esac +fi + +# Canonicalize CRA_LICENSE_TEXT to an absolute path: the autotools path runs +# `make sbom` inside a `cd "$WOLFTPM_DIR"` subshell, where a relative path would +# otherwise resolve against the wolfTPM tree rather than the caller's CWD. +if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then + CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT") + echo "License text: $CRA_LICENSE_TEXT" +fi + +# Accumulator for temp files; cleaned up on exit. +_auto_tempfiles="" +trap 'rm -f ${_auto_tempfiles:-}' EXIT + +_auto_extract_srcs() { + # Extract wolftpm sources from compile_commands.json (CMake build). + if [ -n "${WOLFTPM_BUILD_DIR:-}" ] && [ -f "$WOLFTPM_BUILD_DIR/compile_commands.json" ]; then + _ccdb="$WOLFTPM_BUILD_DIR/compile_commands.json" + if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq is required to auto-extract sources from compile_commands.json." >&2 + echo " Install jq, or set CRA_SBOM_SRCS_FILE manually." >&2 + exit 1 + fi + _auto=$(mktemp "${TMPDIR:-/tmp}/wolftpm-auto-srcs.XXXXXX") + _auto_tempfiles="${_auto_tempfiles:-} $_auto" + jq -r '.[].file' "$_ccdb" \ + | grep "^${WOLFTPM_DIR}/" \ + | grep -E '/src/[^/]+\.c$' \ + | sort -u > "$_auto" + if [ -s "$_auto" ]; then + _n=$(wc -l < "$_auto" | tr -d ' ') + echo " Auto-extracted $_n wolftpm sources from compile_commands.json" + CRA_SBOM_SRCS_FILE="$_auto" + return 0 + fi + echo " WARNING: compile_commands.json found but yielded no wolftpm sources." >&2 + fi +} + +_run_autotools() { + echo "==> Autotools path: make sbom" + _tree_ver=$(sed -n \ + 's/.*LIBWOLFTPM_VERSION_STRING[[:space:]]*"\([^"]*\)".*/\1/p' \ + "$WOLFTPM_DIR/wolftpm/version.h" 2>/dev/null || true) + if [ -n "$_tree_ver" ] && [ "$_tree_ver" != "$VERSION" ]; then + echo "ERROR: wolfTPM tree is version $_tree_ver but detected version is $VERSION." >&2 + exit 1 + fi + (cd "$WOLFTPM_DIR" && { + if [ ! -f Makefile ]; then + echo " Running ./configure first..." + ./configure + fi + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ + SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" \ + SBOM_LICENSE_TEXT="$CRA_LICENSE_TEXT" + else + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ + SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" + fi + else + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" + fi + cp -f "wolftpm-${VERSION}.cdx.json" "$CDX_OUT" + cp -f "wolftpm-${VERSION}.spdx.json" "$SPDX_OUT" + if [ -f "wolftpm-${VERSION}.spdx" ]; then + cp -f "wolftpm-${VERSION}.spdx" "$OUT_DIR/" + fi + }) +} + +_run_cmake() { + echo "==> cmake path: gen-sbom with compile_commands.json source extraction" + if [ -z "${WOLFTPM_BUILD_DIR:-}" ]; then + echo "ERROR: WOLFTPM_BUILD_DIR is not set." >&2 + echo " Set it to your cmake out-of-source build directory." >&2 + echo " Example: cmake -B build && WOLFTPM_BUILD_DIR=\$PWD/build $0" >&2 + exit 1 + fi + if [ ! -d "$WOLFTPM_BUILD_DIR" ]; then + echo "ERROR: WOLFTPM_BUILD_DIR=$WOLFTPM_BUILD_DIR is not a directory." >&2 + exit 1 + fi + + CRA_SBOM_SRCS_FILE="" + _auto_extract_srcs + + if [ -z "${CRA_SBOM_SRCS_FILE:-}" ]; then + echo "ERROR: could not extract wolftpm sources from compile_commands.json." >&2 + echo " Reconfigure cmake with -DCMAKE_EXPORT_COMPILE_COMMANDS=ON, or" >&2 + echo " switch to autotools mode (CRA_SBOM_MODE=autotools)." >&2 + exit 1 + fi + + PYTHON3=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true) + if [ -z "$PYTHON3" ]; then + echo "ERROR: python3 not found in PATH." >&2 + exit 1 + fi + + set -- \ + --name wolftpm \ + --version "$VERSION" \ + --supplier "wolfSSL Inc." \ + --license-file "$WOLFTPM_DIR/LICENSE" \ + --srcs-file "$CRA_SBOM_SRCS_FILE" \ + --cdx-out "$CDX_OUT" \ + --spdx-out "$SPDX_OUT" + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + set -- "$@" --license-text "$CRA_LICENSE_TEXT" + fi + fi + "$PYTHON3" "$GEN" "$@" +} + +MODE=${CRA_SBOM_MODE:-} +case "$MODE" in + autotools) _run_autotools ;; + cmake) _run_cmake ;; + "") + if [ -n "${WOLFTPM_BUILD_DIR:-}" ] && [ -d "${WOLFTPM_BUILD_DIR}" ]; then + MODE=cmake + _run_cmake + elif [ -f "$WOLFTPM_DIR/Makefile" ] && [ -f "$WOLFTPM_DIR/configure" ]; then + MODE=autotools + _run_autotools + else + echo "ERROR: could not detect build mode." >&2 + echo " Set CRA_SBOM_MODE=autotools or cmake, and ensure the build" >&2 + echo " directory exists (WOLFTPM_BUILD_DIR) or configure has been run" >&2 + echo " in WOLFTPM_DIR." >&2 + exit 1 + fi + ;; + *) + echo "ERROR: CRA_SBOM_MODE must be 'autotools' or 'cmake', not '$MODE'" >&2 + exit 1 + ;; +esac + +# ---- Post-process: PURL canonicalization ---- +# gen-sbom emits pkg:generic/wolftpm@X by default; rewrite to the canonical +# pkg:github/wolfSSL/wolfTPM@vX form expected in the auditor packet. +if ! CDX_OUT="$CDX_OUT" SPDX_OUT="$SPDX_OUT" \ + python3 <<'PY' +import json, os, pathlib + +cdx = pathlib.Path(os.environ["CDX_OUT"]) +spdx = pathlib.Path(os.environ["SPDX_OUT"]) + +GENERIC = "pkg:generic/wolftpm@" +GITHUB = "pkg:github/wolfSSL/wolfTPM@v" + +def canonicalize_purl(s): + if isinstance(s, str) and s.startswith(GENERIC): + return GITHUB + s[len(GENERIC):] + return s + +if cdx.exists(): + d = json.loads(cdx.read_text()) + comp = d.get("metadata", {}).get("component", {}) + comp["purl"] = canonicalize_purl(comp.get("purl", "")) + cdx.write_text(json.dumps(d, indent=2) + "\n") + print(f"Post-processed {cdx.name}") + +if spdx.exists(): + d = json.loads(spdx.read_text()) + for pkg in d.get("packages", []): + for ref in pkg.get("externalRefs", []): + if ref.get("referenceType") == "purl": + ref["referenceLocator"] = canonicalize_purl(ref.get("referenceLocator", "")) + spdx.write_text(json.dumps(d, indent=2) + "\n") + print(f"Post-processed {spdx.name}") +PY +then + echo "ERROR: post-process failed (PURL canonicalization incomplete)." >&2 + echo " The emitted SBOM may carry pkg:generic PURLs; not trusting it." >&2 + exit 1 +fi + +echo "Done." From c3ed5fad30df33baa4865f04c1e4ac1bea8063a8 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:00:41 -0700 Subject: [PATCH 09/25] feat: add embedded mode to generate-wolfssh-sbom.sh --- cra-kit/scripts/generate-wolfssh-sbom.sh | 228 ++++++++++++++++++++++- 1 file changed, 224 insertions(+), 4 deletions(-) diff --git a/cra-kit/scripts/generate-wolfssh-sbom.sh b/cra-kit/scripts/generate-wolfssh-sbom.sh index 4c5730da..b094750c 100755 --- a/cra-kit/scripts/generate-wolfssh-sbom.sh +++ b/cra-kit/scripts/generate-wolfssh-sbom.sh @@ -1,17 +1,43 @@ #!/bin/sh -# Generate wolfSSH component SBOM (autotools make sbom). +# Generate wolfSSH component SBOM (autotools make sbom, or embedded gen-sbom). +# +# Mode selection: +# CRA_SBOM_MODE=autotools|embedded (default: autotools) +# autotools: builds libwolfssh.so and runs `make sbom` +# embedded: hashes wolfSSH source files directly (no .so is produced when +# wolfSSH is compiled into firmware), via wolfSSL's gen-sbom. # # Required variables: # WOLFSSL_DIR=path/to/wolfssl (source tree root; provides gen-sbom) # WOLFSSH_DIR=path/to/wolfssh (source tree root) # +# Embedded-mode variables: +# CRA_SBOM_BUILD_DIR=path/to/build (embedded: dir containing +# compile_commands.json for CMake / ESP-IDF +# / Zephyr builds, used to extract the exact +# wolfSSH .c files on the link line) +# CRA_SBOM_SRCS_FILE=path/to/srcs.txt (embedded: explicit list of wolfSSH .c +# paths, one per line; takes priority over +# every other source-resolution method) +# # Optional variables: +# CRA_SBOM_OUT_DIR= (output directory; default auditor-packet) # CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSH-Commercial) # CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is a # LicenseRef-* id: plain-text licence embedded -# in the SBOM; make sbom hard-fails without it.) +# in the SBOM; make sbom / gen-sbom hard-fail +# without it.) +# POSIX sh (script is #!/bin/sh and run via `sh`); dash has no `set -o pipefail`, +# so we use `set -eu` like the rest of the kit. Pipelines that must not mask a +# failed first stage are checked explicitly instead. set -eu +# Accumulator for temp files (embedded source extraction); cleaned up on exit. +# Why: mktemp temp files must not leak if any later command fails under set -e; +# a single EXIT trap removes them on every exit path including errors. +_auto_tempfiles="" +trap 'rm -f ${_auto_tempfiles:-}' EXIT + SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") # shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else @@ -81,6 +107,190 @@ if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then echo "License text: $CRA_LICENSE_TEXT" fi +# Pick a Python that can `import pcpp` (pip may target a different python3 than +# the one first on PATH). pcpp lets gen-sbom walk settings.h without a compiler. +_python_with_pcpp() { + for py in ${CRA_PYTHON:-} python3 python; do + [ -n "$py" ] || continue + if command -v "$py" >/dev/null 2>&1 && \ + "$py" -c "import pcpp" 2>/dev/null; then + echo "$py" + return 0 + fi + done + return 1 +} + +# Resolve the wolfSSH source list for embedded mode into the file named by $1. +# +# Source set rule: ONLY wolfSSH protocol sources (${WOLFSSH_DIR}/src/*.c) are +# product-owned and belong in this component. The wolfcrypt / wolfSSL sources +# that wolfSSH links against are NOT included here — they are a separate +# component covered by generate-wolfssl-sbom.sh and referenced as a dependency. +# Mixing them in would double-count the crypto library across two SBOMs. +# +# Priority order (first match wins): +# 1. CRA_SBOM_SRCS_FILE — explicit user list beats anything inferred, because +# the user knows their exact link line; we must not +# second-guess it. +# 2. compile_commands.json at CRA_SBOM_BUILD_DIR — accurate per-build extraction +# for CMake / ESP-IDF / Zephyr. +# 3. Default: all ${WOLFSSH_DIR}/src/*.c sorted — the "all sources" fallback for +# toolchains (IAR, Keil, MPLAB) that produce no +# compile_commands.json and where we cannot infer the +# exact subset compiled. Listing all sources is the +# safe over-approximation: it never omits a file that +# shipped. +_resolve_wolfssh_srcs() { + _out="$1" + + # 1. Explicit user-provided list (verbatim). + if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then + if [ ! -f "$CRA_SBOM_SRCS_FILE" ]; then + echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE not found." >&2 + exit 1 + fi + if [ ! -s "$CRA_SBOM_SRCS_FILE" ]; then + echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE is empty." >&2 + exit 1 + fi + grep -v '^[[:space:]]*$' "$CRA_SBOM_SRCS_FILE" > "$_out" || { + echo "ERROR: failed to read CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE." >&2 + exit 1 + } + echo " Using explicit source list from $CRA_SBOM_SRCS_FILE" + return 0 + fi + + # 2. compile_commands.json (CMake / ESP-IDF / Zephyr). + if [ -n "${CRA_SBOM_BUILD_DIR:-}" ] && \ + [ -f "$CRA_SBOM_BUILD_DIR/compile_commands.json" ]; then + _ccdb="$CRA_SBOM_BUILD_DIR/compile_commands.json" + if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq is required to extract sources from compile_commands.json." >&2 + echo " Install jq, or set CRA_SBOM_SRCS_FILE manually." >&2 + exit 1 + fi + # Restrict to wolfSSH's own src/*.c; exclude any wolfcrypt/wolfssl entries + # that share the build (they belong to the wolfssl component). + jq -r '.[].file' "$_ccdb" \ + | grep "^${WOLFSSH_DIR}/src/" \ + | grep -E '/src/[^/]+\.c$' \ + | sort -u > "$_out" || { + echo "ERROR: failed to parse $_ccdb with jq." >&2 + exit 1 + } + if [ -s "$_out" ]; then + _n=$(wc -l < "$_out" | tr -d ' ') + echo " Extracted $_n wolfSSH sources from compile_commands.json" + return 0 + fi + echo " WARNING: compile_commands.json found but yielded no wolfSSH" >&2 + echo " src/ sources; falling back to all src/*.c." >&2 + fi + + # 3. Default fallback: every wolfSSH src/*.c. A POSIX glob expands in sorted + # order; guard the no-match case where the pattern stays literal. + : > "$_out" + for _c in "$WOLFSSH_DIR"/src/*.c; do + [ -f "$_c" ] || continue + printf '%s\n' "$_c" >> "$_out" + done + if [ ! -s "$_out" ]; then + echo "ERROR: no wolfSSH sources found in $WOLFSSH_DIR/src/*.c." >&2 + exit 1 + fi + _n=$(wc -l < "$_out" | tr -d ' ') + echo " Using all $_n wolfSSH sources from $WOLFSSH_DIR/src/*.c (default)" + return 0 +} + +_run_embedded() { + echo "==> Embedded path: gen-sbom hashing wolfSSH source files" + + GEN="$WOLFSSL_DIR/scripts/gen-sbom" + if [ ! -f "$GEN" ]; then + echo "ERROR: $GEN not found (need a wolfSSL tree with SBOM support)." >&2 + exit 1 + fi + + # gen-sbom's embedded entry point walks wolfSSL's settings.h to resolve the + # build config. wolfSSH headers pull in wolfSSL headers, so we reuse the same + # settings.h + kit user_settings.h the wolfssl embedded path uses. + SETTINGS_H="$WOLFSSL_DIR/wolfssl/wolfcrypt/settings.h" + if [ ! -f "$SETTINGS_H" ]; then + echo "ERROR: $SETTINGS_H not found." >&2 + exit 1 + fi + if [ ! -f "$KIT_DIR/user_settings.h" ]; then + echo "ERROR: $KIT_DIR/user_settings.h missing (demo WOLFSSL_USER_SETTINGS)." >&2 + exit 1 + fi + + if ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: python3 not found in PATH (required by gen-sbom)." >&2 + exit 1 + fi + + # Resolve the source list into a temp file (cleaned up by the EXIT trap). + _srcs=$(mktemp "${TMPDIR:-/tmp}/wolfssh-srcs.XXXXXX") || { + echo "ERROR: mktemp failed for the source-list temp file." >&2 + exit 1 + } + _auto_tempfiles="${_auto_tempfiles:-} $_srcs" + _resolve_wolfssh_srcs "$_srcs" + + # Build the positional --srcs argument list from the resolved file. + set -- + while IFS= read -r _src; do + [ -n "$_src" ] || continue + if [ ! -f "$_src" ]; then + echo "ERROR: source file does not exist: $_src" >&2 + exit 1 + fi + set -- "$@" "$_src" + done < "$_srcs" + if [ $# -eq 0 ]; then + echo "ERROR: resolved source list is empty." >&2 + exit 1 + fi + + # Prepend --srcs, then append the trailing options. argparse stops --srcs + # consumption at the next -- option, so --cdx-out / --spdx-out end it cleanly. + set -- --srcs "$@" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + set -- "$@" --license-text "$CRA_LICENSE_TEXT" + fi + fi + + if _py=$(_python_with_pcpp); then + echo " Using $_py (pcpp) for --user-settings" + else + echo "ERROR: no python3/python with pcpp installed (required for embedded mode)." >&2 + echo " Install it on the same interpreter: python3 -m pip install pcpp" >&2 + exit 1 + fi + + "$_py" "$GEN" \ + --name wolfssh --version "$VERSION" \ + --license-file "$WOLFSSH_DIR/LICENSING" \ + --user-settings "$SETTINGS_H" \ + --user-settings-include "$WOLFSSL_DIR" \ + --user-settings-include "$KIT_DIR" \ + --user-settings-define WOLFSSL_USER_SETTINGS \ + "$@" + + # Verify gen-sbom actually produced both outputs and they are non-empty. + for _f in "$CDX_OUT" "$SPDX_OUT"; do + if [ ! -s "$_f" ]; then + echo "ERROR: expected output missing or empty: $_f" >&2 + exit 1 + fi + done +} + _run_autotools() { echo "==> Autotools path: make sbom" # `make sbom` names its output after the wolfSSH TREE's version @@ -118,7 +328,15 @@ _run_autotools() { }) } -_run_autotools +MODE=${CRA_SBOM_MODE:-autotools} +case "$MODE" in + embedded) _run_embedded ;; + autotools) _run_autotools ;; + *) + echo "ERROR: CRA_SBOM_MODE must be 'autotools' or 'embedded', not '$MODE'" >&2 + exit 1 + ;; +esac # ---- Post-process: defensive PURL canonicalization ---- # Current gen-sbom already emits pkg:github/wolfSSL/wolfSSH@vX natively for @@ -161,4 +379,6 @@ then exit 1 fi -echo "Done." +echo "Done. SBOM outputs:" +echo " $CDX_OUT" +echo " $SPDX_OUT" From 950398f3cf2def0e60a7e26dcecab91c7d3b2d7d Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:01:03 -0700 Subject: [PATCH 10/25] feat: add embedded mode to generate-wolfmqtt-sbom.sh --- cra-kit/scripts/generate-wolfmqtt-sbom.sh | 226 +++++++++++++++++++--- 1 file changed, 202 insertions(+), 24 deletions(-) diff --git a/cra-kit/scripts/generate-wolfmqtt-sbom.sh b/cra-kit/scripts/generate-wolfmqtt-sbom.sh index 5dd4ec3d..08866dc1 100755 --- a/cra-kit/scripts/generate-wolfmqtt-sbom.sh +++ b/cra-kit/scripts/generate-wolfmqtt-sbom.sh @@ -1,18 +1,33 @@ #!/bin/sh -# Generate wolfMQTT component SBOM (autotools make sbom path). +# Generate wolfMQTT component SBOM (autotools make sbom, or embedded gen-sbom). # -# wolfMQTT uses autotools. This script runs `make sbom` inside the wolfMQTT -# source tree and copies the resulting SBOM files to the auditor packet. +# Mode selection: +# CRA_SBOM_MODE=autotools|embedded (default: autotools) +# autotools: runs `make sbom` inside the wolfMQTT tree +# embedded: runs gen-sbom directly over the MQTT protocol sources, hashing +# them with an OmniBOR gitoid Merkle hash. Use this when wolfMQTT +# is compiled into firmware (ESP-IDF, Arduino, STM32, bare-metal) +# and there is no .so/.a to hash. # # Required variables: # WOLFSSL_DIR=path/to/wolfssl (source tree root; must contain scripts/gen-sbom) # WOLFMQTT_DIR=path/to/wolfMQTT (source tree root) # +# Embedded-mode variables (CRA_SBOM_MODE=embedded): +# CRA_SBOM_SRCS_FILE=path/to/srcs.txt (explicit .c list, one path per line; +# used verbatim — highest priority) +# CRA_SBOM_BUILD_DIR=path/to/build (CMake/ESP-IDF build dir; sources are +# read from its compile_commands.json) +# # Optional variables: # CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial) # CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is LicenseRef-*) # CRA_SBOM_OUT_DIR= (default: /auditor-packet/wolfmqtt-component) set -eu +# Enable pipefail when the shell supports it (bash/ksh/some dash builds). +# Plain POSIX sh may not; tolerate its absence so the script still runs. +# shellcheck disable=SC3040 +(set -o pipefail) 2>/dev/null && set -o pipefail || true SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") @@ -88,32 +103,195 @@ if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then echo "License text: $CRA_LICENSE_TEXT" fi -echo "==> Autotools path: make sbom" +_run_autotools() { + echo "==> Autotools path: make sbom" -# Detect whether the wolfMQTT tree is already configured; run ./configure first -# if no Makefile is present. -(cd "$WOLFMQTT_DIR" && { - if [ ! -f Makefile ]; then - echo " Running ./configure first (WOLFSSL_DIR=$WOLFSSL_DIR)..." - ./configure --with-wolfssl="$WOLFSSL_DIR" - fi - if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then - if [ -n "${CRA_LICENSE_TEXT:-}" ]; then - make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ - SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" \ - SBOM_LICENSE_TEXT="$CRA_LICENSE_TEXT" + # Detect whether the wolfMQTT tree is already configured; run ./configure first + # if no Makefile is present. + (cd "$WOLFMQTT_DIR" && { + if [ ! -f Makefile ]; then + echo " Running ./configure first (WOLFSSL_DIR=$WOLFSSL_DIR)..." + ./configure --with-wolfssl="$WOLFSSL_DIR" + fi + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ + SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" \ + SBOM_LICENSE_TEXT="$CRA_LICENSE_TEXT" + else + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ + SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" + fi else - make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \ - SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE" + make sbom WOLFSSL_DIR="$WOLFSSL_DIR" + fi + cp -f "wolfmqtt-${VERSION}.cdx.json" "$CDX_OUT" + cp -f "wolfmqtt-${VERSION}.spdx.json" "$SPDX_OUT" + if [ -f "wolfmqtt-${VERSION}.spdx" ]; then + cp -f "wolfmqtt-${VERSION}.spdx" "$OUT_DIR/" + fi + }) +} + +_run_embedded() { + echo "==> Embedded path: gen-sbom over MQTT protocol sources" + + # wolfMQTT's MQTT protocol implementation lives entirely in src/mqtt_*.c. + # Unlike wolfTPM there are no platform-specific HAL files to exclude and no + # host-only sources, so the mqtt_*.c glob is the correct default source set. + # + # wolfcrypt/wolfSSL crypto sources and wolfSSL TLS sources are deliberately + # NOT hashed here: they are a separate component with their own SBOM, + # produced by generate-wolfssl-sbom.sh. Mixing them in would double-count + # the crypto component and misattribute its provenance to wolfMQTT. + if [ ! -d "$WOLFMQTT_DIR/src" ]; then + echo "ERROR: $WOLFMQTT_DIR/src not found; cannot locate MQTT sources." >&2 + exit 1 + fi + + GEN="$WOLFSSL_DIR/scripts/gen-sbom" + if [ ! -f "$GEN" ]; then + echo "ERROR: $GEN not found (need wolfSSL with SBOM support)." >&2 + exit 1 + fi + + PY=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true) + if [ -z "$PY" ]; then + echo "ERROR: python3 (or python) not found; required to run gen-sbom." >&2 + exit 1 + fi + + # wolfMQTT ships its own licence file; detect the SPDX id from it (not from + # the wolfSSL tree, which carries a different LICENSING file). + LICENSE_FILE="" + for _lf in "$WOLFMQTT_DIR/LICENSE" "$WOLFMQTT_DIR/COPYING" "$WOLFMQTT_DIR/LICENSING"; do + if [ -f "$_lf" ]; then + LICENSE_FILE="$_lf" + break + fi + done + if [ -z "$LICENSE_FILE" ]; then + echo "ERROR: no LICENSE/COPYING/LICENSING file found in $WOLFMQTT_DIR." >&2 + exit 1 + fi + + # Resolve the source list. Priority order: + # 1. CRA_SBOM_SRCS_FILE — the caller knows their exact build better than any + # heuristic, so an explicit list always wins and is used verbatim. + # 2. compile_commands.json under CRA_SBOM_BUILD_DIR — ESP-IDF and other + # CMake-based builds emit this, the most common embedded path with + # build-system support; filter it to WOLFMQTT_DIR sources only. + # 3. Default glob of src/mqtt_*.c — documented fallback for Arduino and + # bare-metal builds that produce no compile_commands.json. + SRCS_LIST=$(mktemp "${TMPDIR:-/tmp}/wolfmqtt-srcs.XXXXXX") || { + echo "ERROR: mktemp failed for the source-list temp file." >&2 + exit 1 + } + # gen-sbom requires exactly one of --options-h / --user-settings to source + # its build properties. Those describe wolfSSL's crypto/TLS configuration, + # which belongs to the wolfssl component's SBOM, not wolfMQTT's. Feed an + # empty options file so gen-sbom records no (and thus no misattributed) + # build defines for the wolfMQTT component. + EMPTY_OPTS=$(mktemp "${TMPDIR:-/tmp}/wolfmqtt-opts.XXXXXX") || { + echo "ERROR: mktemp failed for the empty options temp file." >&2 + exit 1 + } + trap 'rm -f "$SRCS_LIST" "$EMPTY_OPTS"' EXIT + + if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then + if [ ! -f "$CRA_SBOM_SRCS_FILE" ]; then + echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE not found." >&2 + exit 1 + fi + echo " Source list: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE (verbatim)" + grep -v '^[[:space:]]*$' "$CRA_SBOM_SRCS_FILE" > "$SRCS_LIST" || { + echo "ERROR: failed to read CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE." >&2 + exit 1 + } + elif [ -n "${CRA_SBOM_BUILD_DIR:-}" ] && [ -f "${CRA_SBOM_BUILD_DIR}/compile_commands.json" ]; then + _ccdb="${CRA_SBOM_BUILD_DIR}/compile_commands.json" + if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq is required to read sources from $_ccdb." >&2 + echo " Install jq, or set CRA_SBOM_SRCS_FILE manually." >&2 + exit 1 + fi + echo " Source list: $_ccdb (filtered to WOLFMQTT_DIR)" + jq -r '.[].file' "$_ccdb" \ + | grep "^${WOLFMQTT_DIR}/" \ + | grep -E '/src/mqtt_[^/]+\.c$' \ + | sort -u > "$SRCS_LIST" || { + echo "ERROR: failed to extract sources from $_ccdb." >&2 + exit 1 + } + if [ ! -s "$SRCS_LIST" ]; then + echo "ERROR: $_ccdb yielded no wolfMQTT sources under $WOLFMQTT_DIR/src." >&2 + echo " Check CRA_SBOM_BUILD_DIR, or set CRA_SBOM_SRCS_FILE manually." >&2 + exit 1 fi else - make sbom WOLFSSL_DIR="$WOLFSSL_DIR" + echo " Source list: default glob $WOLFMQTT_DIR/src/mqtt_*.c" + for _f in "$WOLFMQTT_DIR"/src/mqtt_*.c; do + [ -f "$_f" ] && echo "$_f" + done | sort -u > "$SRCS_LIST" || { + echo "ERROR: failed to enumerate $WOLFMQTT_DIR/src/mqtt_*.c." >&2 + exit 1 + } fi - cp -f "wolfmqtt-${VERSION}.cdx.json" "$CDX_OUT" - cp -f "wolfmqtt-${VERSION}.spdx.json" "$SPDX_OUT" - if [ -f "wolfmqtt-${VERSION}.spdx" ]; then - cp -f "wolfmqtt-${VERSION}.spdx" "$OUT_DIR/" + + if [ ! -s "$SRCS_LIST" ]; then + echo "ERROR: no MQTT source files found to hash." >&2 + exit 1 fi -}) + + # Pass the resolved sources positionally to gen-sbom's --srcs (it takes a + # space-separated list; argparse stops consuming at the next -- option, so + # --cdx-out/--spdx-out terminate the list cleanly). + set -- + while IFS= read -r _src; do + [ -n "$_src" ] || continue + if [ ! -f "$_src" ]; then + echo "ERROR: listed source not found: $_src" >&2 + exit 1 + fi + set -- "$@" "$_src" + done < "$SRCS_LIST" + + _count=$# + set -- --srcs "$@" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + set -- "$@" --license-text "$CRA_LICENSE_TEXT" + fi + fi + + "$PY" "$GEN" \ + --name wolfmqtt --version "$VERSION" \ + --license-file "$LICENSE_FILE" \ + --options-h "$EMPTY_OPTS" \ + "$@" || { + echo "ERROR: gen-sbom failed." >&2 + exit 1 + } + + echo "NOTE: hashed ${_count} source file(s)" +} + +case "${CRA_SBOM_MODE:-autotools}" in + autotools) _run_autotools ;; + embedded) _run_embedded ;; + *) + echo "ERROR: unknown CRA_SBOM_MODE='${CRA_SBOM_MODE:-}' (expected 'autotools' or 'embedded')" >&2 + exit 1 + ;; +esac + +# Verify the generator actually produced non-empty SBOM files. +for _out in "$CDX_OUT" "$SPDX_OUT"; do + if [ ! -s "$_out" ]; then + echo "ERROR: expected SBOM output missing or empty: $_out" >&2 + exit 1 + fi +done echo "Done." From 53d6c3746774ab07fa8b57a5d259ca24baa5bbcb Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:01:27 -0700 Subject: [PATCH 11/25] feat: add embedded mode to generate-wolftpm-sbom.sh --- cra-kit/scripts/generate-wolftpm-sbom.sh | 164 ++++++++++++++++++++++- 1 file changed, 162 insertions(+), 2 deletions(-) diff --git a/cra-kit/scripts/generate-wolftpm-sbom.sh b/cra-kit/scripts/generate-wolftpm-sbom.sh index 96af1cea..97cc2e39 100755 --- a/cra-kit/scripts/generate-wolftpm-sbom.sh +++ b/cra-kit/scripts/generate-wolftpm-sbom.sh @@ -2,9 +2,11 @@ # Generate wolfTPM component SBOMs (autotools make sbom, cmake sbom, or direct gen-sbom). # # Mode selection: -# CRA_SBOM_MODE=autotools|cmake +# CRA_SBOM_MODE=autotools|cmake|embedded # autotools (default when configure+Makefile exist): runs `make sbom` # cmake: auto-extracts sources from compile_commands.json and runs gen-sbom directly +# embedded: hashes the wolfTPM core sources + one platform HAL file directly +# (for firmware builds that compile wolfTPM in, with no .so to hash) # # Required variables: # WOLFSSL_DIR=path/to/wolfssl (source tree root; provides gen-sbom) @@ -13,6 +15,17 @@ # Mode-specific variables: # WOLFTPM_BUILD_DIR=path/to/build (cmake mode: path to cmake build directory; # triggers compile_commands.json auto-extraction) +# CRA_TPM_HAL=st|espressif|microchip|atmel|zephyr|xilinx|uboot|barebox|qnx|mmio|infineon +# (embedded mode: selects the single platform HAL +# file hal/tpm_io_${CRA_TPM_HAL}.c. Required for a +# complete embedded SBOM; if unset, no HAL file is +# hashed and a warning is emitted.) +# CRA_SBOM_SRCS_FILE=path/to/srcs.txt (embedded mode: explicit source list, one .c path +# per line; takes priority over all auto-detection. +# The caller owns correctness of this list.) +# CRA_TPM_OPTIONS_H=path/to/options.h (embedded mode: flat #define build-config header for +# feature enumeration; defaults to +# $WOLFTPM_DIR/wolftpm/options.h) # # Optional variables: # CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfTPM-Commercial) @@ -204,10 +217,157 @@ _run_cmake() { "$PYTHON3" "$GEN" "$@" } +_run_embedded() { + echo "==> Embedded path: hash wolfTPM core sources + one platform HAL" + + GEN_PY=$(command -v python3 2>/dev/null || command -v python 2>/dev/null || true) + if [ -z "$GEN_PY" ]; then + echo "ERROR: python3 not found in PATH (needed to run gen-sbom)." >&2 + exit 1 + fi + + if [ ! -d "$WOLFTPM_DIR/src" ]; then + echo "ERROR: $WOLFTPM_DIR/src not found; not a wolfTPM source tree?" >&2 + exit 1 + fi + + # gen-sbom needs a build-config header to enumerate enabled features for the + # SBOM (it requires exactly one of --options-h / --user-settings). For wolfTPM + # the committed wolftpm/options.h is a flat #define file in the same shape the + # autotools `make sbom` path feeds via --options-h, so reuse it; callers whose + # firmware uses a different config can point CRA_TPM_OPTIONS_H at their header. + OPTIONS_H=${CRA_TPM_OPTIONS_H:-"$WOLFTPM_DIR/wolftpm/options.h"} + if [ ! -f "$OPTIONS_H" ]; then + echo "ERROR: build-config header not found: $OPTIONS_H" >&2 + echo " Run ./configure in WOLFTPM_DIR to generate wolftpm/options.h," >&2 + echo " or set CRA_TPM_OPTIONS_H to your firmware's flat #define header." >&2 + exit 1 + fi + + # Source list, one .c path per line. + _srcs=$(mktemp "${TMPDIR:-/tmp}/wolftpm-embedded-srcs.XXXXXX") || { + echo "ERROR: mktemp failed for the embedded source list." >&2 + exit 1 + } + _auto_tempfiles="${_auto_tempfiles:-} $_srcs" + + # CRA_SBOM_SRCS_FILE takes priority over all auto-detection: when the caller + # supplies an explicit list they have already reconciled it against their exact + # firmware link line (core + the correct HAL + any tpm2_* they actually build), + # so second-guessing it here could only drop or add files they did not intend. + if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then + if [ ! -f "$CRA_SBOM_SRCS_FILE" ]; then + echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE not found." >&2 + exit 1 + fi + echo " Using caller-supplied source list: $CRA_SBOM_SRCS_FILE" + cp -f "$CRA_SBOM_SRCS_FILE" "$_srcs" || { + echo "ERROR: could not read CRA_SBOM_SRCS_FILE." >&2 + exit 1 + } + else + # Core sources: every src/tpm2*.c EXCEPT the host-only transports below. + # The excluded files (Linux /dev/tpm0, Windows TBS, swtpm simulator) target + # a full OS and will not compile or link on a bare-metal/RTOS firmware build, + # so including them would misrepresent what is actually in the firmware. + for _f in "$WOLFTPM_DIR"/src/tpm2*.c; do + [ -f "$_f" ] || continue + case "$(basename "$_f")" in + tpm2_linux.c|tpm2_winapi.c|tpm2_swtpm.c) continue ;; + esac + echo "$_f" >> "$_srcs" || { + echo "ERROR: failed writing core source to list." >&2 + exit 1 + } + done + + # Dispatcher: always part of the HAL layer. + if [ -f "$WOLFTPM_DIR/hal/tpm_io.c" ]; then + echo "$WOLFTPM_DIR/hal/tpm_io.c" >> "$_srcs" || { + echo "ERROR: failed writing dispatcher to list." >&2 + exit 1 + } + fi + + # Platform HAL: exactly one tpm_io_.c belongs in a given firmware. + # Which one is the caller's responsibility — only they know the target board. + # Picking the wrong HAL (or all of them) would produce an SBOM that does not + # match the shipped firmware, so we hash exactly the one named by CRA_TPM_HAL + # and refuse to guess: an unset CRA_TPM_HAL yields a warning and no HAL file. + if [ -n "${CRA_TPM_HAL:-}" ]; then + _hal="$WOLFTPM_DIR/hal/tpm_io_${CRA_TPM_HAL}.c" + if [ ! -f "$_hal" ]; then + echo "ERROR: CRA_TPM_HAL=$CRA_TPM_HAL but $_hal does not exist." >&2 + echo " Available HALs:" >&2 + for _h in "$WOLFTPM_DIR"/hal/tpm_io_*.c; do + [ -f "$_h" ] || continue + _b=$(basename "$_h"); _b=${_b#tpm_io_}; _b=${_b%.c} + echo " $_b" >&2 + done + exit 1 + fi + echo "$_hal" >> "$_srcs" || { + echo "ERROR: failed writing HAL source to list." >&2 + exit 1 + } + echo " HAL: tpm_io_${CRA_TPM_HAL}.c" + else + echo "WARNING: CRA_TPM_HAL not set; HAL source excluded from SBOM. Set CRA_TPM_HAL=st|espressif|..." >&2 + fi + fi + + if [ ! -s "$_srcs" ]; then + echo "ERROR: no source files collected for the embedded SBOM." >&2 + exit 1 + fi + + # wolfcrypt/wolfssl sources are intentionally NOT hashed here: they are a + # separate component covered by generate-wolfssl-sbom.sh (embedded mode), and + # the wolfSSL SBOM is referenced as a dependency rather than duplicated. + + _n=$(wc -l < "$_srcs" | tr -d ' ') + echo "NOTE: hashed $_n source file(s)" + + # gen-sbom takes the source list as a positional --srcs vector (exactly one + # of --lib / --srcs is accepted). Build the option vector first, then append + # the collected paths after --srcs so they bind as that argument's nargs list. + set -- \ + --name wolftpm \ + --version "$VERSION" \ + --supplier "wolfSSL Inc." \ + --license-file "$WOLFTPM_DIR/LICENSE" \ + --options-h "$OPTIONS_H" \ + --cdx-out "$CDX_OUT" \ + --spdx-out "$SPDX_OUT" + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + set -- "$@" --license-text "$CRA_LICENSE_TEXT" + fi + fi + set -- "$@" --srcs + while IFS= read -r _src; do + [ -n "$_src" ] || continue + set -- "$@" "$_src" + done < "$_srcs" + "$GEN_PY" "$GEN" "$@" || { + echo "ERROR: gen-sbom failed in embedded mode." >&2 + exit 1 + } + + for _out in "$CDX_OUT" "$SPDX_OUT"; do + if [ ! -s "$_out" ]; then + echo "ERROR: expected output $_out is missing or empty." >&2 + exit 1 + fi + done +} + MODE=${CRA_SBOM_MODE:-} case "$MODE" in autotools) _run_autotools ;; cmake) _run_cmake ;; + embedded) _run_embedded ;; "") if [ -n "${WOLFTPM_BUILD_DIR:-}" ] && [ -d "${WOLFTPM_BUILD_DIR}" ]; then MODE=cmake @@ -224,7 +384,7 @@ case "$MODE" in fi ;; *) - echo "ERROR: CRA_SBOM_MODE must be 'autotools' or 'cmake', not '$MODE'" >&2 + echo "ERROR: CRA_SBOM_MODE must be 'autotools', 'cmake', or 'embedded', not '$MODE'" >&2 exit 1 ;; esac From 5f909c67135ae1a0a0a604788f44c112ae52d633 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:16:12 -0700 Subject: [PATCH 12/25] refactor: extract shared source-extraction lib _cra-sbom-extract.sh --- cra-kit/scripts/_cra-sbom-extract.sh | 271 +++++++++++++++++++++++ cra-kit/scripts/generate-wolfssl-sbom.sh | 211 +++--------------- 2 files changed, 301 insertions(+), 181 deletions(-) create mode 100644 cra-kit/scripts/_cra-sbom-extract.sh diff --git a/cra-kit/scripts/_cra-sbom-extract.sh b/cra-kit/scripts/_cra-sbom-extract.sh new file mode 100644 index 00000000..591f720f --- /dev/null +++ b/cra-kit/scripts/_cra-sbom-extract.sh @@ -0,0 +1,271 @@ +#!/bin/sh +# _cra-sbom-extract.sh — shared source-extraction helper for CRA Kit SBOM scripts. +# +# Source this file; do not execute it directly. It provides one function, +# _cra_extract_srcs, that product SBOM scripts (generate-wolfssl-sbom.sh, +# generate-wolfssh-sbom.sh, ...) call to auto-detect the list of .c files that +# went into an embedded build. +# +# _cra_extract_srcs PRODUCT_DIR PRODUCT_NAME OUT_FILE +# +# Tries each extraction method in priority order. On success, writes the +# sorted, de-duplicated list of .c paths to OUT_FILE and returns 0. If a +# method is selected (its env var is set) but yields no sources, prints an +# error to stderr and returns 1. If no extraction env var is set at all, +# returns 2 so the caller can fall back to its default source glob. +# +# Methods, in priority order: +# 1. CRA_SBOM_SRCS_FILE — copy verbatim to OUT_FILE (safety net; callers +# normally handle this themselves before calling) +# 2. CRA_SBOM_KEIL_PROJECT — parse .uvprojx, filter to PRODUCT_DIR +# 3. CRA_SBOM_IAR_PROJECT — parse .ewp, filter to PRODUCT_DIR +# 4. CRA_SBOM_MAKEFILE_DIR — `make -n`, grep for PRODUCT_DIR/...*.c +# 5. CRA_SBOM_BUILD_DIR — jq filter compile_commands.json to PRODUCT_DIR +# none set — return 2 +# +# PRODUCT_DIR: absolute path to the product source tree; used to filter paths. +# PRODUCT_NAME: short name for log messages and the wolfssl CMSIS-Pack special +# case (e.g. "wolfssl", "wolfssh", "wolftpm"). +# OUT_FILE: path the caller has already created (mktemp) for the result. +# +# The function appends any temp files it creates to _cra_auto_tempfiles so the +# caller's EXIT trap can clean them up. CRA_SBOM_NO_HASH is honoured by callers, +# not here: a caller that sees CRA_SBOM_NO_HASH=true should skip hashing (and +# thus skip this function) entirely. + +_cra_extract_srcs() { + _cra_product_dir="$1" + _cra_product_name="$2" + _cra_out_file="$3" + + if [ -z "$_cra_product_dir" ] || [ -z "$_cra_product_name" ] || [ -z "$_cra_out_file" ]; then + echo "ERROR: _cra_extract_srcs requires PRODUCT_DIR PRODUCT_NAME OUT_FILE." >&2 + return 1 + fi + + # Method 1: explicit source list file. Callers usually handle this before + # calling us, but honour it here too so the function is safe to call blindly. + if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then + if [ ! -f "$CRA_SBOM_SRCS_FILE" ]; then + echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE not found." >&2 + return 1 + fi + sort -u "$CRA_SBOM_SRCS_FILE" > "$_cra_out_file" || { + echo "ERROR: failed to read CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE." >&2 + return 1 + } + if [ ! -s "$_cra_out_file" ]; then + echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE is empty." >&2 + return 1 + fi + _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ') + echo " Using $_cra_n sources from CRA_SBOM_SRCS_FILE" + return 0 + fi + + # Method 2: Keil .uvprojx + if [ -n "${CRA_SBOM_KEIL_PROJECT:-}" ]; then + if [ ! -f "$CRA_SBOM_KEIL_PROJECT" ]; then + echo "ERROR: CRA_SBOM_KEIL_PROJECT=$CRA_SBOM_KEIL_PROJECT not found." >&2 + return 1 + fi + if ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: python3 is required to parse a Keil .uvprojx file." >&2 + return 1 + fi + # The CMSIS Pack RTE lookup only applies when PRODUCT_NAME=wolfssl: + # wolfSSL ships a CMSIS Pack whose sources live in the installed pack, + # not in the project. Other products have no CMSIS pack, so the parser + # skips that path and enumerates / entries directly. + python3 - "$CRA_SBOM_KEIL_PROJECT" "$_cra_product_dir" "$_cra_product_name" \ + > "$_cra_out_file" <<'PYEOF' || { +import sys, os, glob, xml.etree.ElementTree as ET + +proj_file = sys.argv[1] +product_dir = sys.argv[2] if len(sys.argv) > 2 else '' +product_name = sys.argv[3] if len(sys.argv) > 3 else '' +proj_dir = os.path.dirname(os.path.abspath(proj_file)) +proj = ET.parse(proj_file) +paths = set() + +rte = proj.find('.//RTE') +# CMSIS Pack RTE special case: wolfSSL only. +if (product_name == 'wolfssl' and rte is not None + and rte.find('.//component[@Cvendor="wolfSSL"]') is not None): + pdsc_candidates = sorted(glob.glob( + os.path.expanduser('~/.arm/Packs/wolfSSL/wolfSSL/*/wolfSSL.pdsc') + )) + if os.name == 'nt': + appdata = os.environ.get('LOCALAPPDATA', '') + pdsc_candidates += sorted(glob.glob( + os.path.join(appdata, 'Arm', 'Packs', 'wolfSSL', 'wolfSSL', '*', 'wolfSSL.pdsc') + )) + if pdsc_candidates: + pdsc_file = pdsc_candidates[-1] + pack_dir = os.path.dirname(pdsc_file) + pdsc = ET.parse(pdsc_file) + for f in pdsc.findall('.//file[@category="source"]'): + name = f.get('name', '') + if name.lower().endswith('.c'): + paths.add(os.path.normpath(os.path.join(pack_dir, name.replace('\\', '/')))) + elif product_dir and os.path.isdir(product_dir): + # Pack not installed locally; enumerate sources from PRODUCT_DIR. + for subdir in ('wolfcrypt/src', 'src'): + d = os.path.join(product_dir, subdir) + if os.path.isdir(d): + for name in os.listdir(d): + if name.endswith('.c'): + paths.add(os.path.join(d, name)) +else: + for file_elem in proj.findall('.//File'): + fp = file_elem.find('FilePath') + ft = file_elem.find('FileType') + if fp is None or not fp.text: + continue + ftype = int(ft.text) if ft is not None and ft.text else 0 + if ftype == 1 or fp.text.lower().endswith('.c'): + abs_path = os.path.normpath( + os.path.join(proj_dir, fp.text.replace('\\', '/')) + ) + paths.add(abs_path) + +for p in sorted(paths): + print(p) +PYEOF + echo "ERROR: failed to parse Keil project $CRA_SBOM_KEIL_PROJECT." >&2 + return 1 + } + # wolfssl's CMSIS-Pack branch legitimately emits paths under the installed + # pack dir (~/.arm/Packs/...), which lie OUTSIDE PRODUCT_DIR; filtering + # would wrongly drop them. The wolfssl parser already constrains its + # output to wolfssl sources, so skip the PRODUCT_DIR filter for wolfssl. + if [ "$_cra_product_name" != "wolfssl" ]; then + _cra_filter_to_product "$_cra_out_file" "$_cra_product_dir" + fi + if [ ! -s "$_cra_out_file" ]; then + echo "ERROR: CRA_SBOM_KEIL_PROJECT is set but no $_cra_product_name sources were extracted from $CRA_SBOM_KEIL_PROJECT." >&2 + return 1 + fi + _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ') + echo " Auto-extracted $_cra_n $_cra_product_name sources from Keil project" + return 0 + fi + + # Method 3: IAR .ewp + if [ -n "${CRA_SBOM_IAR_PROJECT:-}" ]; then + if [ ! -f "$CRA_SBOM_IAR_PROJECT" ]; then + echo "ERROR: CRA_SBOM_IAR_PROJECT=$CRA_SBOM_IAR_PROJECT not found." >&2 + return 1 + fi + if ! command -v python3 >/dev/null 2>&1; then + echo "ERROR: python3 is required to parse an IAR .ewp file." >&2 + return 1 + fi + python3 - "$CRA_SBOM_IAR_PROJECT" > "$_cra_out_file" <<'PYEOF' || { +import sys, os, xml.etree.ElementTree as ET + +def is_excluded(file_elem): + return file_elem.find('excluded') is not None + +proj_file = sys.argv[1] +proj_dir = os.path.dirname(os.path.abspath(proj_file)) +proj = ET.parse(proj_file) +paths = set() + +for file_elem in proj.findall('.//file'): + if is_excluded(file_elem): + continue + name_elem = file_elem.find('name') + if name_elem is None or not name_elem.text: + continue + raw = name_elem.text + if not raw.lower().endswith('.c'): + continue + resolved = raw.replace('$PROJ_DIR$', proj_dir) + paths.add(os.path.normpath(resolved.replace('\\', '/'))) + +for p in sorted(paths): + print(p) +PYEOF + echo "ERROR: failed to parse IAR project $CRA_SBOM_IAR_PROJECT." >&2 + return 1 + } + # Preserve wolfssl's original behaviour (all project .c, unfiltered); other + # products filter to PRODUCT_DIR to drop demo/BSP sources from the project. + if [ "$_cra_product_name" != "wolfssl" ]; then + _cra_filter_to_product "$_cra_out_file" "$_cra_product_dir" + fi + if [ ! -s "$_cra_out_file" ]; then + echo "ERROR: CRA_SBOM_IAR_PROJECT is set but no $_cra_product_name sources were extracted from $CRA_SBOM_IAR_PROJECT." >&2 + return 1 + fi + _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ') + echo " Auto-extracted $_cra_n $_cra_product_name sources from IAR project" + return 0 + fi + + # Method 4: Makefile dry-run + if [ -n "${CRA_SBOM_MAKEFILE_DIR:-}" ]; then + if [ ! -d "$CRA_SBOM_MAKEFILE_DIR" ]; then + echo "ERROR: CRA_SBOM_MAKEFILE_DIR=$CRA_SBOM_MAKEFILE_DIR is not a directory." >&2 + return 1 + fi + if ! command -v make >/dev/null 2>&1; then + echo "ERROR: make is required to auto-extract sources from CRA_SBOM_MAKEFILE_DIR." >&2 + return 1 + fi + # `make -n` (dry run) emits the compile commands; pull out any .c path + # that lives under PRODUCT_DIR. grep -F on the dir keeps the pattern + # literal (PRODUCT_DIR may contain regex metacharacters). + make -C "$CRA_SBOM_MAKEFILE_DIR" -n 2>/dev/null \ + | grep -oE '[^ ]+\.c' \ + | grep -F "$_cra_product_dir/" \ + | sort -u > "$_cra_out_file" || true + if [ ! -s "$_cra_out_file" ]; then + echo "ERROR: CRA_SBOM_MAKEFILE_DIR is set but make yielded no $_cra_product_name sources." >&2 + echo " Ensure 'make -n' in $CRA_SBOM_MAKEFILE_DIR references .c files under $_cra_product_dir." >&2 + return 1 + fi + _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ') + echo " Auto-extracted $_cra_n $_cra_product_name sources via Makefile (CRA_SBOM_MAKEFILE_DIR=$CRA_SBOM_MAKEFILE_DIR)" + return 0 + fi + + # Method 5: compile_commands.json (CMake / Zephyr / ESP-IDF) + if [ -n "${CRA_SBOM_BUILD_DIR:-}" ] && [ -f "$CRA_SBOM_BUILD_DIR/compile_commands.json" ]; then + if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq is required to auto-extract sources from compile_commands.json." >&2 + echo " Install jq, or set CRA_SBOM_SRCS_FILE manually. See SRCS-FILE-HOWTO.md." >&2 + return 1 + fi + jq -r '.[].file' "$CRA_SBOM_BUILD_DIR/compile_commands.json" \ + | grep -F "$_cra_product_dir/" \ + | grep -E '\.c$' \ + | sort -u > "$_cra_out_file" || true + if [ ! -s "$_cra_out_file" ]; then + echo "ERROR: compile_commands.json in $CRA_SBOM_BUILD_DIR yielded no $_cra_product_name sources under $_cra_product_dir." >&2 + return 1 + fi + _cra_n=$(wc -l < "$_cra_out_file" | tr -d ' ') + echo " Auto-extracted $_cra_n $_cra_product_name sources from compile_commands.json" + return 0 + fi + + # No extraction method selected — caller should use its default glob. + return 2 +} + +# _cra_filter_to_product OUT_FILE PRODUCT_DIR +# In-place filter: keep only lines that are .c paths under PRODUCT_DIR. +# grep -F keeps PRODUCT_DIR literal (it may contain regex metacharacters). +_cra_filter_to_product() { + _cra_f="$1" + _cra_dir="$2" + _cra_tmp=$(mktemp "${TMPDIR:-/tmp}/cra-filter.XXXXXX") || { + echo "ERROR: mktemp failed while filtering sources." >&2 + return 1 + } + _cra_auto_tempfiles="${_cra_auto_tempfiles:-} $_cra_tmp" + grep -F "$_cra_dir/" "$_cra_f" | grep -E '\.c$' | sort -u > "$_cra_tmp" || true + mv -f "$_cra_tmp" "$_cra_f" +} diff --git a/cra-kit/scripts/generate-wolfssl-sbom.sh b/cra-kit/scripts/generate-wolfssl-sbom.sh index 7d72336e..57c74698 100755 --- a/cra-kit/scripts/generate-wolfssl-sbom.sh +++ b/cra-kit/scripts/generate-wolfssl-sbom.sh @@ -34,11 +34,16 @@ # without it.) set -eu -# Accumulator for temp files created by _auto_extract_srcs; cleaned up on exit. +# Accumulator for temp files created by _auto_extract_srcs / the shared +# extraction library; cleaned up on exit. The library appends to +# _cra_auto_tempfiles, so trap both. _auto_tempfiles="" -trap 'rm -f ${_auto_tempfiles:-}' EXIT +_cra_auto_tempfiles="" +trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +# shellcheck source=_cra-sbom-extract.sh disable=SC1091 +. "$SCRIPT_DIR/_cra-sbom-extract.sh" KIT_DIR=$(dirname "$SCRIPT_DIR") # shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} @@ -131,190 +136,34 @@ _embedded_srcs() { } _auto_extract_srcs() { - # Method 1: compile_commands.json (CMake / Zephyr / ESP-IDF) - if [ -n "${WOLFSSL_BUILD_DIR:-}" ] && [ -f "$WOLFSSL_BUILD_DIR/compile_commands.json" ]; then - _ccdb="$WOLFSSL_BUILD_DIR/compile_commands.json" - if ! command -v jq >/dev/null 2>&1; then - echo "ERROR: jq is required to auto-extract sources from compile_commands.json." >&2 - echo " Install jq, or set CRA_SBOM_SRCS_FILE manually. See SRCS-FILE-HOWTO.md." >&2 - exit 1 - fi - _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") - _auto_tempfiles="${_auto_tempfiles:-} $_auto" - jq -r '.[].file' "$_ccdb" \ - | grep "^${WOLFSSL_DIR}/" \ - | grep -E '/(wolfcrypt/src|src)/[^/]+\.c$' \ - | sort -u > "$_auto" - if [ ! -s "$_auto" ]; then - _esp_proj=$(dirname "$WOLFSSL_BUILD_DIR") - jq -r '.[].file' "$_ccdb" \ - | grep "^${_esp_proj}/managed_components/wolfssl__wolfssl/" \ - | grep -E '/(wolfcrypt/src|src)/[^/]+\.c$' \ - | sort -u > "$_auto" - fi - if [ -s "$_auto" ]; then - _n=$(wc -l < "$_auto" | tr -d ' ') - echo " Auto-extracted $_n wolfssl sources from compile_commands.json" - CRA_SBOM_SRCS_FILE="$_auto" - CRA_SBOM_SRCS_ONLY_FROM_FILE=true - return 0 - fi - echo " WARNING: compile_commands.json found but yielded no wolfssl sources; trying next method." >&2 + # Delegate to the shared extraction library. The library reads + # CRA_SBOM_BUILD_DIR for compile_commands.json; map WOLFSSL_BUILD_DIR onto it + # so the embedded cmake/Zephyr/ESP-IDF auto-extraction keeps working. + if [ -n "${WOLFSSL_BUILD_DIR:-}" ]; then + CRA_SBOM_BUILD_DIR=${CRA_SBOM_BUILD_DIR:-$WOLFSSL_BUILD_DIR} fi - - # Method 2: Makefile - if [ -n "${CRA_SBOM_MAKEFILE_DIR:-}" ]; then - if [ ! -d "$CRA_SBOM_MAKEFILE_DIR" ]; then - echo "ERROR: CRA_SBOM_MAKEFILE_DIR=$CRA_SBOM_MAKEFILE_DIR is not a directory." >&2 - exit 1 - fi - _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") - _auto_tempfiles="${_auto_tempfiles:-} $_auto" - if make --no-print-directory -C "$CRA_SBOM_MAKEFILE_DIR" -n print-wolfssl-srcs >/dev/null 2>&1; then - make --no-print-directory -C "$CRA_SBOM_MAKEFILE_DIR" print-wolfssl-srcs 2>/dev/null \ - | sort -u > "$_auto" - fi - if [ ! -s "$_auto" ]; then - make -C "$CRA_SBOM_MAKEFILE_DIR" -n 2>/dev/null \ - | grep -oE '[^ ]+wolfssl[^ ]+\.c' \ - | sort -u > "$_auto" || true - fi - if [ -s "$_auto" ]; then - _n=$(wc -l < "$_auto" | tr -d ' ') - echo " Auto-extracted $_n wolfssl sources via Makefile (CRA_SBOM_MAKEFILE_DIR=$CRA_SBOM_MAKEFILE_DIR)" - CRA_SBOM_SRCS_FILE="$_auto" - CRA_SBOM_SRCS_ONLY_FROM_FILE=true - return 0 - fi - echo "ERROR: CRA_SBOM_MAKEFILE_DIR is set but make yielded no wolfssl sources." >&2 - echo " Add a 'print-wolfssl-srcs' target or ensure 'make -n' references wolfssl .c files." >&2 + _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") || { + echo "ERROR: mktemp failed for the auto-extract source list." >&2 exit 1 - fi - - # Method 3: Keil .uvprojx - if [ -n "${CRA_SBOM_KEIL_PROJECT:-}" ]; then - if [ ! -f "$CRA_SBOM_KEIL_PROJECT" ]; then - echo "ERROR: CRA_SBOM_KEIL_PROJECT=$CRA_SBOM_KEIL_PROJECT not found." >&2 - exit 1 - fi - if ! command -v python3 >/dev/null 2>&1; then - echo "ERROR: python3 is required to parse a Keil .uvprojx file." >&2 - exit 1 - fi - _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") - _auto_tempfiles="${_auto_tempfiles:-} $_auto" - python3 - "$CRA_SBOM_KEIL_PROJECT" "$WOLFSSL_DIR" > "$_auto" <<'PYEOF' -import sys, os, glob, xml.etree.ElementTree as ET - -proj_file = sys.argv[1] -wolfssl_dir = sys.argv[2] if len(sys.argv) > 2 else '' -proj_dir = os.path.dirname(os.path.abspath(proj_file)) -proj = ET.parse(proj_file) -paths = set() - -rte = proj.find('.//RTE') -if rte is not None and rte.find('.//component[@Cvendor="wolfSSL"]') is not None: - # CMSIS Pack RTE: sources come from the installed pack .pdsc - pdsc_candidates = sorted(glob.glob( - os.path.expanduser('~/.arm/Packs/wolfSSL/wolfSSL/*/wolfSSL.pdsc') - )) - if os.name == 'nt': - appdata = os.environ.get('LOCALAPPDATA', '') - pdsc_candidates += sorted(glob.glob( - os.path.join(appdata, 'Arm', 'Packs', 'wolfSSL', 'wolfSSL', '*', 'wolfSSL.pdsc') - )) - if pdsc_candidates: - pdsc_file = pdsc_candidates[-1] - pack_dir = os.path.dirname(pdsc_file) - pdsc = ET.parse(pdsc_file) - for f in pdsc.findall('.//file[@category="source"]'): - name = f.get('name', '') - if name.lower().endswith('.c'): - paths.add(os.path.normpath(os.path.join(pack_dir, name.replace('\\', '/')))) - elif wolfssl_dir and os.path.isdir(wolfssl_dir): - # Pack not installed locally; enumerate sources directly from WOLFSSL_DIR. - # The CMSIS Pack contains the full wolfssl library (wolfcrypt/src/ + src/). - for subdir in ('wolfcrypt/src', 'src'): - d = os.path.join(wolfssl_dir, subdir) - if os.path.isdir(d): - for name in os.listdir(d): - if name.endswith('.c'): - paths.add(os.path.join(d, name)) -else: - for file_elem in proj.findall('.//File'): - fp = file_elem.find('FilePath') - ft = file_elem.find('FileType') - if fp is None or not fp.text: - continue - ftype = int(ft.text) if ft is not None and ft.text else 0 - if ftype == 1 or fp.text.lower().endswith('.c'): - abs_path = os.path.normpath( - os.path.join(proj_dir, fp.text.replace('\\', '/')) - ) - paths.add(abs_path) - -for p in sorted(paths): - print(p) -PYEOF - if [ -s "$_auto" ]; then - _n=$(wc -l < "$_auto" | tr -d ' ') - echo " Auto-extracted $_n wolfssl sources from Keil project" + } + _auto_tempfiles="${_auto_tempfiles:-} $_auto" + # `|| _rc=$?` keeps `set -e` from aborting on the library's non-zero returns + # (1 = error, 2 = no method) so we can dispatch on the code below. + _rc=0 + _cra_extract_srcs "$WOLFSSL_DIR" "wolfssl" "$_auto" || _rc=$? + case "$_rc" in + 0) CRA_SBOM_SRCS_FILE="$_auto" CRA_SBOM_SRCS_ONLY_FROM_FILE=true - return 0 - fi - echo "ERROR: CRA_SBOM_KEIL_PROJECT is set but no sources were extracted from $CRA_SBOM_KEIL_PROJECT." >&2 - exit 1 - fi - - # Method 4: IAR .ewp - if [ -n "${CRA_SBOM_IAR_PROJECT:-}" ]; then - if [ ! -f "$CRA_SBOM_IAR_PROJECT" ]; then - echo "ERROR: CRA_SBOM_IAR_PROJECT=$CRA_SBOM_IAR_PROJECT not found." >&2 + ;; + 2) + # No extraction method selected; fall back to the built-in demo list. + ;; + *) + # Library already printed an actionable error to stderr. exit 1 - fi - if ! command -v python3 >/dev/null 2>&1; then - echo "ERROR: python3 is required to parse an IAR .ewp file." >&2 - exit 1 - fi - _auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") - _auto_tempfiles="${_auto_tempfiles:-} $_auto" - python3 - "$CRA_SBOM_IAR_PROJECT" > "$_auto" <<'PYEOF' -import sys, os, xml.etree.ElementTree as ET - -def is_excluded(file_elem): - return file_elem.find('excluded') is not None - -proj_file = sys.argv[1] -proj_dir = os.path.dirname(os.path.abspath(proj_file)) -proj = ET.parse(proj_file) -paths = set() - -for file_elem in proj.findall('.//file'): - if is_excluded(file_elem): - continue - name_elem = file_elem.find('name') - if name_elem is None or not name_elem.text: - continue - raw = name_elem.text - if not raw.lower().endswith('.c'): - continue - resolved = raw.replace('$PROJ_DIR$', proj_dir) - paths.add(os.path.normpath(resolved.replace('\\', '/'))) - -for p in sorted(paths): - print(p) -PYEOF - if [ -s "$_auto" ]; then - _n=$(wc -l < "$_auto" | tr -d ' ') - echo " Auto-extracted $_n wolfssl sources from IAR project" - CRA_SBOM_SRCS_FILE="$_auto" - CRA_SBOM_SRCS_ONLY_FROM_FILE=true - return 0 - fi - echo "ERROR: CRA_SBOM_IAR_PROJECT is set but no sources were extracted from $CRA_SBOM_IAR_PROJECT." >&2 - exit 1 - fi + ;; + esac } _run_embedded() { From 0b410f70c148c55a100d5e43550242ee7828816b Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:20:43 -0700 Subject: [PATCH 13/25] feat: add Keil/IAR/Makefile/NO_HASH handlers to generate-wolftpm-sbom.sh --- cra-kit/scripts/generate-wolftpm-sbom.sh | 103 ++++++++++++++++++----- 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/cra-kit/scripts/generate-wolftpm-sbom.sh b/cra-kit/scripts/generate-wolftpm-sbom.sh index 97cc2e39..6e20cae3 100755 --- a/cra-kit/scripts/generate-wolftpm-sbom.sh +++ b/cra-kit/scripts/generate-wolftpm-sbom.sh @@ -23,6 +23,11 @@ # CRA_SBOM_SRCS_FILE=path/to/srcs.txt (embedded mode: explicit source list, one .c path # per line; takes priority over all auto-detection. # The caller owns correctness of this list.) +# CRA_SBOM_KEIL_PROJECT= (embedded mode: auto-extract srcs from a Keil .uvprojx) +# CRA_SBOM_IAR_PROJECT= (embedded mode: auto-extract srcs from an IAR .ewp) +# CRA_SBOM_MAKEFILE_DIR= (embedded mode: auto-extract srcs via `make -n`) +# CRA_SBOM_NO_HASH=true (embedded mode: emit SBOM without a real artifact +# hash; use when no source list is available) # CRA_TPM_OPTIONS_H=path/to/options.h (embedded mode: flat #define build-config header for # feature enumeration; defaults to # $WOLFTPM_DIR/wolftpm/options.h) @@ -36,6 +41,8 @@ set -eu SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) +# shellcheck source=_cra-sbom-extract.sh disable=SC1091 +. "$SCRIPT_DIR/_cra-sbom-extract.sh" KIT_DIR=$(dirname "$SCRIPT_DIR") # shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else WOLFTPM_DIR=${WOLFTPM_DIR:-$(cd "$KIT_DIR/../../wolftpm" 2>/dev/null && pwd || true)} @@ -108,9 +115,11 @@ if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then echo "License text: $CRA_LICENSE_TEXT" fi -# Accumulator for temp files; cleaned up on exit. +# Accumulators for temp files; cleaned up on exit. The shared extraction library +# appends to _cra_auto_tempfiles, so trap both. _auto_tempfiles="" -trap 'rm -f ${_auto_tempfiles:-}' EXIT +_cra_auto_tempfiles="" +trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT _auto_extract_srcs() { # Extract wolftpm sources from compile_commands.json (CMake build). @@ -244,6 +253,45 @@ _run_embedded() { exit 1 fi + # --no-artifact-hash: skip all source-file logic and emit a placeholder hash. + # Use when no compiled library AND no source file list is accessible. The + # options.h header is still required so the SBOM enumerates enabled features. + if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then + if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then + echo "ERROR: CRA_SBOM_NO_HASH cannot be combined with CRA_SBOM_SRCS_FILE." >&2 + exit 1 + fi + echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM with placeholder hash." + echo " Contact wolfssl@wolfssl.com to discuss integrity verification" + echo " options before using this in production." + set -- \ + --name wolftpm \ + --version "$VERSION" \ + --supplier "wolfSSL Inc." \ + --license-file "$WOLFTPM_DIR/LICENSE" \ + --options-h "$OPTIONS_H" \ + --no-artifact-hash \ + --cdx-out "$CDX_OUT" \ + --spdx-out "$SPDX_OUT" + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + set -- "$@" --license-text "$CRA_LICENSE_TEXT" + fi + fi + "$GEN_PY" "$GEN" "$@" || { + echo "ERROR: gen-sbom failed in embedded NO_HASH mode." >&2 + exit 1 + } + for _out in "$CDX_OUT" "$SPDX_OUT"; do + if [ ! -s "$_out" ]; then + echo "ERROR: expected output $_out is missing or empty." >&2 + exit 1 + fi + done + return 0 + fi + # Source list, one .c path per line. _srcs=$(mktemp "${TMPDIR:-/tmp}/wolftpm-embedded-srcs.XXXXXX") || { echo "ERROR: mktemp failed for the embedded source list." >&2 @@ -251,21 +299,27 @@ _run_embedded() { } _auto_tempfiles="${_auto_tempfiles:-} $_srcs" - # CRA_SBOM_SRCS_FILE takes priority over all auto-detection: when the caller - # supplies an explicit list they have already reconciled it against their exact - # firmware link line (core + the correct HAL + any tpm2_* they actually build), - # so second-guessing it here could only drop or add files they did not intend. - if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then - if [ ! -f "$CRA_SBOM_SRCS_FILE" ]; then - echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE not found." >&2 - exit 1 - fi - echo " Using caller-supplied source list: $CRA_SBOM_SRCS_FILE" - cp -f "$CRA_SBOM_SRCS_FILE" "$_srcs" || { - echo "ERROR: could not read CRA_SBOM_SRCS_FILE." >&2 - exit 1 - } - else + # Resolve the source list. The shared library handles CRA_SBOM_SRCS_FILE, + # Keil/IAR/Makefile, and compile_commands.json extraction; it returns: + # 0 = a build-system method produced the list (trust it verbatim) + # 2 = no method active (fall back to the default glob + HAL selection) + # 1 = a method was selected but failed (library already explained why) + # `|| _cra_rc=$?` keeps `set -e` from aborting on the non-zero returns. + _cra_rc=0 + _cra_extract_srcs "$WOLFTPM_DIR" "wolftpm" "$_srcs" || _cra_rc=$? + + if [ "$_cra_rc" -eq 0 ]; then + # A build-system method (Keil/IAR/Makefile/compile_commands) produced the + # list. Trust it: the build system already selected the correct single HAL + # and excluded the host-only transports (tpm2_linux.c, tpm2_winapi.c, + # tpm2_swtpm.c). Do NOT apply CRA_TPM_HAL on top — that would double-count + # the HAL or, if CRA_TPM_HAL disagrees with the build, conflict with it. + _n=$(wc -l < "$_srcs" | tr -d ' ') + echo "NOTE: hashed $_n source file(s) (from build system)" + elif [ "$_cra_rc" -eq 2 ]; then + # No extraction method active: build the default source list ourselves, + # selecting the single platform HAL via CRA_TPM_HAL. + # # Core sources: every src/tpm2*.c EXCEPT the host-only transports below. # The excluded files (Linux /dev/tpm0, Windows TBS, swtpm simulator) target # a full OS and will not compile or link on a bare-metal/RTOS firmware build, @@ -314,10 +368,16 @@ _run_embedded() { else echo "WARNING: CRA_TPM_HAL not set; HAL source excluded from SBOM. Set CRA_TPM_HAL=st|espressif|..." >&2 fi - fi - if [ ! -s "$_srcs" ]; then - echo "ERROR: no source files collected for the embedded SBOM." >&2 + if [ ! -s "$_srcs" ]; then + echo "ERROR: no source files collected for the embedded SBOM." >&2 + exit 1 + fi + + _n=$(wc -l < "$_srcs" | tr -d ' ') + echo "NOTE: hashed $_n source file(s)" + else + # Library selected a method but it failed; it already printed the reason. exit 1 fi @@ -325,9 +385,6 @@ _run_embedded() { # separate component covered by generate-wolfssl-sbom.sh (embedded mode), and # the wolfSSL SBOM is referenced as a dependency rather than duplicated. - _n=$(wc -l < "$_srcs" | tr -d ' ') - echo "NOTE: hashed $_n source file(s)" - # gen-sbom takes the source list as a positional --srcs vector (exactly one # of --lib / --srcs is accepted). Build the option vector first, then append # the collected paths after --srcs so they bind as that argument's nargs list. From 7b260edab1dc439c9d3ecd437fc4410f2e852756 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:22:32 -0700 Subject: [PATCH 14/25] feat: add Keil/IAR/Makefile handlers to generate-wolfssh-sbom.sh Wire in shared _cra-sbom-extract.sh for Keil/IAR/Makefile/compile_commands.json source extraction. NO_HASH deferred to SBOM-cgz (gen-sbom lacks --no-artifact-hash). --- cra-kit/scripts/generate-wolfssh-sbom.sh | 88 +++++++++--------------- 1 file changed, 33 insertions(+), 55 deletions(-) diff --git a/cra-kit/scripts/generate-wolfssh-sbom.sh b/cra-kit/scripts/generate-wolfssh-sbom.sh index b094750c..d54f9d3f 100755 --- a/cra-kit/scripts/generate-wolfssh-sbom.sh +++ b/cra-kit/scripts/generate-wolfssh-sbom.sh @@ -19,6 +19,10 @@ # CRA_SBOM_SRCS_FILE=path/to/srcs.txt (embedded: explicit list of wolfSSH .c # paths, one per line; takes priority over # every other source-resolution method) +# CRA_SBOM_KEIL_PROJECT=path (embedded: auto-extract from Keil .uvprojx) +# CRA_SBOM_IAR_PROJECT=path (embedded: auto-extract from IAR .ewp) +# CRA_SBOM_MAKEFILE_DIR=path (embedded: auto-extract via make -n dry-run) +# CRA_SBOM_NO_HASH — not yet supported; blocked on SBOM-cgz (gen-sbom --no-artifact-hash) # # Optional variables: # CRA_SBOM_OUT_DIR= (output directory; default auditor-packet) @@ -36,10 +40,14 @@ set -eu # Why: mktemp temp files must not leak if any later command fails under set -e; # a single EXIT trap removes them on every exit path including errors. _auto_tempfiles="" -trap 'rm -f ${_auto_tempfiles:-}' EXIT +# _cra_auto_tempfiles is populated by _cra-sbom-extract.sh's helpers; clean both. +trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") + +# shared extraction methods (Keil, IAR, Makefile, compile_commands.json) +. "$SCRIPT_DIR/_cra-sbom-extract.sh" # shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} WOLFSSH_DIR=${WOLFSSH_DIR:-$(cd "$KIT_DIR/../../wolfSSH" 2>/dev/null && pwd || true)} @@ -129,68 +137,36 @@ _python_with_pcpp() { # component covered by generate-wolfssl-sbom.sh and referenced as a dependency. # Mixing them in would double-count the crypto library across two SBOMs. # -# Priority order (first match wins): +# Priority order (first match wins), all but the last handled by +# _cra_extract_srcs from _cra-sbom-extract.sh: # 1. CRA_SBOM_SRCS_FILE — explicit user list beats anything inferred, because -# the user knows their exact link line; we must not -# second-guess it. -# 2. compile_commands.json at CRA_SBOM_BUILD_DIR — accurate per-build extraction -# for CMake / ESP-IDF / Zephyr. -# 3. Default: all ${WOLFSSH_DIR}/src/*.c sorted — the "all sources" fallback for -# toolchains (IAR, Keil, MPLAB) that produce no -# compile_commands.json and where we cannot infer the -# exact subset compiled. Listing all sources is the -# safe over-approximation: it never omits a file that +# the user knows their exact link line. +# 2. CRA_SBOM_KEIL_PROJECT — parse Keil .uvprojx, filter to WOLFSSH_DIR. +# 3. CRA_SBOM_IAR_PROJECT — parse IAR .ewp, filter to WOLFSSH_DIR. +# 4. CRA_SBOM_MAKEFILE_DIR — `make -n` dry-run, filter to WOLFSSH_DIR. +# 5. compile_commands.json at CRA_SBOM_BUILD_DIR — per-build extraction for +# CMake / ESP-IDF / Zephyr. +# 6. Default: all ${WOLFSSH_DIR}/src/*.c sorted — the "all sources" fallback +# for toolchains where we cannot infer the exact +# subset compiled. Listing all sources is the safe +# over-approximation: it never omits a file that # shipped. _resolve_wolfssh_srcs() { _out="$1" - # 1. Explicit user-provided list (verbatim). - if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then - if [ ! -f "$CRA_SBOM_SRCS_FILE" ]; then - echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE not found." >&2 - exit 1 - fi - if [ ! -s "$CRA_SBOM_SRCS_FILE" ]; then - echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE is empty." >&2 - exit 1 - fi - grep -v '^[[:space:]]*$' "$CRA_SBOM_SRCS_FILE" > "$_out" || { - echo "ERROR: failed to read CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE." >&2 - exit 1 - } - echo " Using explicit source list from $CRA_SBOM_SRCS_FILE" + # Try every env-var-driven extraction method (SRCS_FILE, Keil, IAR, + # Makefile, compile_commands.json). rc=2 means none was set: fall back to + # the wolfSSH default glob below. + _cra_rc=0 + _cra_extract_srcs "$WOLFSSH_DIR" "wolfssh" "$_out" || _cra_rc=$? + if [ "$_cra_rc" -eq 0 ]; then return 0 + elif [ "$_cra_rc" -ne 2 ]; then + exit 1 # _cra_extract_srcs already printed the error fi - # 2. compile_commands.json (CMake / ESP-IDF / Zephyr). - if [ -n "${CRA_SBOM_BUILD_DIR:-}" ] && \ - [ -f "$CRA_SBOM_BUILD_DIR/compile_commands.json" ]; then - _ccdb="$CRA_SBOM_BUILD_DIR/compile_commands.json" - if ! command -v jq >/dev/null 2>&1; then - echo "ERROR: jq is required to extract sources from compile_commands.json." >&2 - echo " Install jq, or set CRA_SBOM_SRCS_FILE manually." >&2 - exit 1 - fi - # Restrict to wolfSSH's own src/*.c; exclude any wolfcrypt/wolfssl entries - # that share the build (they belong to the wolfssl component). - jq -r '.[].file' "$_ccdb" \ - | grep "^${WOLFSSH_DIR}/src/" \ - | grep -E '/src/[^/]+\.c$' \ - | sort -u > "$_out" || { - echo "ERROR: failed to parse $_ccdb with jq." >&2 - exit 1 - } - if [ -s "$_out" ]; then - _n=$(wc -l < "$_out" | tr -d ' ') - echo " Extracted $_n wolfSSH sources from compile_commands.json" - return 0 - fi - echo " WARNING: compile_commands.json found but yielded no wolfSSH" >&2 - echo " src/ sources; falling back to all src/*.c." >&2 - fi - - # 3. Default fallback: every wolfSSH src/*.c. A POSIX glob expands in sorted - # order; guard the no-match case where the pattern stays literal. + # No extraction env var set: every wolfSSH src/*.c. A POSIX glob expands in + # sorted order; guard the no-match case where the pattern stays literal. : > "$_out" for _c in "$WOLFSSH_DIR"/src/*.c; do [ -f "$_c" ] || continue @@ -241,6 +217,8 @@ _run_embedded() { _resolve_wolfssh_srcs "$_srcs" # Build the positional --srcs argument list from the resolved file. + # ponytail: gen-sbom lacks --srcs-file; pass list as positional args + # ceiling: ARG_MAX; upgrade path: SBOM-cgz adds --srcs-file to gen-sbom set -- while IFS= read -r _src; do [ -n "$_src" ] || continue From f54370fc8585be31f35d589617eff3d581ef6ce4 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:23:48 -0700 Subject: [PATCH 15/25] fix: replace broken --srcs-file/--no-artifact-hash with positional --srcs in wolftpm cmake+embedded modes --- cra-kit/scripts/generate-wolftpm-sbom.sh | 48 +++++------------------- 1 file changed, 9 insertions(+), 39 deletions(-) diff --git a/cra-kit/scripts/generate-wolftpm-sbom.sh b/cra-kit/scripts/generate-wolftpm-sbom.sh index 6e20cae3..e791118f 100755 --- a/cra-kit/scripts/generate-wolftpm-sbom.sh +++ b/cra-kit/scripts/generate-wolftpm-sbom.sh @@ -214,7 +214,6 @@ _run_cmake() { --version "$VERSION" \ --supplier "wolfSSL Inc." \ --license-file "$WOLFTPM_DIR/LICENSE" \ - --srcs-file "$CRA_SBOM_SRCS_FILE" \ --cdx-out "$CDX_OUT" \ --spdx-out "$SPDX_OUT" if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then @@ -223,6 +222,13 @@ _run_cmake() { set -- "$@" --license-text "$CRA_LICENSE_TEXT" fi fi + # ponytail: gen-sbom lacks --srcs-file; pass list as positional args after --srcs + # ceiling: ARG_MAX on very large source trees; upgrade path: SBOM-cgz + set -- "$@" --srcs + while IFS= read -r _src; do + [ -n "$_src" ] || continue + set -- "$@" "$_src" + done < "$CRA_SBOM_SRCS_FILE" "$PYTHON3" "$GEN" "$@" } @@ -253,44 +259,8 @@ _run_embedded() { exit 1 fi - # --no-artifact-hash: skip all source-file logic and emit a placeholder hash. - # Use when no compiled library AND no source file list is accessible. The - # options.h header is still required so the SBOM enumerates enabled features. - if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then - if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then - echo "ERROR: CRA_SBOM_NO_HASH cannot be combined with CRA_SBOM_SRCS_FILE." >&2 - exit 1 - fi - echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM with placeholder hash." - echo " Contact wolfssl@wolfssl.com to discuss integrity verification" - echo " options before using this in production." - set -- \ - --name wolftpm \ - --version "$VERSION" \ - --supplier "wolfSSL Inc." \ - --license-file "$WOLFTPM_DIR/LICENSE" \ - --options-h "$OPTIONS_H" \ - --no-artifact-hash \ - --cdx-out "$CDX_OUT" \ - --spdx-out "$SPDX_OUT" - if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then - set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" - if [ -n "${CRA_LICENSE_TEXT:-}" ]; then - set -- "$@" --license-text "$CRA_LICENSE_TEXT" - fi - fi - "$GEN_PY" "$GEN" "$@" || { - echo "ERROR: gen-sbom failed in embedded NO_HASH mode." >&2 - exit 1 - } - for _out in "$CDX_OUT" "$SPDX_OUT"; do - if [ ! -s "$_out" ]; then - echo "ERROR: expected output $_out is missing or empty." >&2 - exit 1 - fi - done - return 0 - fi + # CRA_SBOM_NO_HASH is not yet supported: gen-sbom requires --lib or --srcs and + # has no --no-artifact-hash flag. Blocked on SBOM-cgz. # Source list, one .c path per line. _srcs=$(mktemp "${TMPDIR:-/tmp}/wolftpm-embedded-srcs.XXXXXX") || { From 234bf26001240caa25019f221fe87dbb48a773f3 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:24:04 -0700 Subject: [PATCH 16/25] feat: add Keil/IAR/Makefile/compile_commands handlers to generate-wolfhsm-sbom.sh Fix broken --srcs-file call (gen-sbom only supports positional --srcs). NO_HASH deferred to SBOM-cgz. --- cra-kit/scripts/generate-wolfhsm-sbom.sh | 56 +++++++++++++++++------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/cra-kit/scripts/generate-wolfhsm-sbom.sh b/cra-kit/scripts/generate-wolfhsm-sbom.sh index 0fee3883..ff98e33e 100755 --- a/cra-kit/scripts/generate-wolfhsm-sbom.sh +++ b/cra-kit/scripts/generate-wolfhsm-sbom.sh @@ -15,11 +15,21 @@ # CRA_PYTHON=python3 (interpreter with pcpp) # CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial) # CRA_LICENSE_TEXT= (required when CRA_LICENSE_OVERRIDE is LicenseRef-*) +# WOLFHSM_BUILD_DIR=path auto-extract from compile_commands.json +# CRA_SBOM_SRCS_FILE=path explicit .c file list, one per line +# CRA_SBOM_KEIL_PROJECT=path auto-extract from Keil .uvprojx +# CRA_SBOM_IAR_PROJECT=path auto-extract from IAR .ewp +# CRA_SBOM_MAKEFILE_DIR=path auto-extract via make -n dry-run +# CRA_SBOM_NO_HASH — not yet supported; blocked on SBOM-cgz (gen-sbom --no-artifact-hash) set -eu -# Accumulator for temp files; cleaned up on exit. +. "$(dirname "$0")/_cra-sbom-extract.sh" + +# Accumulator for temp files; cleaned up on exit. The shared extraction library +# appends to _cra_auto_tempfiles, so trap both. _auto_tempfiles="" -trap 'rm -f ${_auto_tempfiles:-}' EXIT +_cra_auto_tempfiles="" +trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") @@ -108,24 +118,40 @@ _python_with_pcpp() { return 1 } -# Enumerate wolfHSM sources: all *.c directly under src/ (no recursion). -_wolfhsm_srcs() { - find "$WOLFHSM_DIR/src" -maxdepth 1 -name "*.c" | sort -} - echo "==> Embedded path: gen-sbom with CC -dM -E (no user_settings.h)" -# Write collected source paths to a temp file for --srcs-file. _srcs_file=$(mktemp "${TMPDIR:-/tmp}/wolfhsm-srcs.XXXXXX") _auto_tempfiles="${_auto_tempfiles:-} $_srcs_file" -_wolfhsm_srcs > "$_srcs_file" -_n=$(wc -l < "$_srcs_file" | tr -d ' ') -echo " Sources: $_n .c files from $WOLFHSM_DIR/src/" -if [ ! -s "$_srcs_file" ]; then - echo "ERROR: no .c files found under $WOLFHSM_DIR/src/." >&2 + +# Allow WOLFHSM_BUILD_DIR to feed compile_commands.json extraction. The shared +# library reads CRA_SBOM_BUILD_DIR; map our wolfHSM-style env var onto it. +CRA_SBOM_BUILD_DIR="${WOLFHSM_BUILD_DIR:-}" + +_cra_rc=0 +_cra_extract_srcs "$WOLFHSM_DIR" "wolfhsm" "$_srcs_file" || _cra_rc=$? + +if [ "$_cra_rc" -eq 2 ]; then + # No extraction method active: enumerate all wolfHSM C sources via find. + # find is used (not glob) because wolfHSM sources span subdirectories. + find "$WOLFHSM_DIR/src" -name "*.c" | sort > "$_srcs_file" || { + echo "ERROR: find failed on $WOLFHSM_DIR/src" >&2; exit 1 + } + _n=$(wc -l < "$_srcs_file" | tr -d ' ') + echo " Source list: find $WOLFHSM_DIR/src -name '*.c' ($_n files)" +elif [ "$_cra_rc" -ne 0 ]; then exit 1 fi +if [ ! -s "$_srcs_file" ]; then + echo "ERROR: no wolfHSM sources found in $WOLFHSM_DIR/src" >&2 + exit 1 +fi +_n=$(wc -l < "$_srcs_file" | tr -d ' ') +echo "NOTE: hashed $_n source file(s)" + +# ponytail: gen-sbom lacks --srcs-file; pass list as positional args +# ceiling: ARG_MAX; upgrade path: SBOM-cgz adds --srcs-file to gen-sbom + # Build license-override args. _license_args="" if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then @@ -154,7 +180,7 @@ if _py=$(_python_with_pcpp); then --user-settings "$SETTINGS_H" \ --user-settings-include "$WOLFHSM_DIR" \ --user-settings-include "$WOLFSSL_DIR" \ - --srcs-file "$_srcs_file" \ + --srcs $(cat "$_srcs_file") \ --cdx-out "$CDX_OUT" \ --spdx-out "$SPDX_OUT" \ ${_license_args} @@ -183,7 +209,7 @@ else --supplier "wolfSSL Inc." \ --license-file "$WOLFHSM_DIR/LICENSING" \ --options-h "$_defines" \ - --srcs-file "$_srcs_file" \ + --srcs $(cat "$_srcs_file") \ --cdx-out "$CDX_OUT" \ --spdx-out "$SPDX_OUT" \ ${_license_args} From a108cdd5889d024ba1b7735e06b50f3a83b58e8e Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:24:11 -0700 Subject: [PATCH 17/25] feat: add Keil/IAR/Makefile handlers to generate-wolfmqtt-sbom.sh --- cra-kit/scripts/generate-wolfmqtt-sbom.sh | 75 +++++++++-------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/cra-kit/scripts/generate-wolfmqtt-sbom.sh b/cra-kit/scripts/generate-wolfmqtt-sbom.sh index 08866dc1..93cf45a3 100755 --- a/cra-kit/scripts/generate-wolfmqtt-sbom.sh +++ b/cra-kit/scripts/generate-wolfmqtt-sbom.sh @@ -16,8 +16,12 @@ # Embedded-mode variables (CRA_SBOM_MODE=embedded): # CRA_SBOM_SRCS_FILE=path/to/srcs.txt (explicit .c list, one path per line; # used verbatim — highest priority) +# CRA_SBOM_KEIL_PROJECT=path/to/x.uvprojx (parse Keil project for .c sources) +# CRA_SBOM_IAR_PROJECT=path/to/x.ewp (parse IAR project for .c sources) +# CRA_SBOM_MAKEFILE_DIR=path/to/dir (run `make -n` to extract .c sources) # CRA_SBOM_BUILD_DIR=path/to/build (CMake/ESP-IDF build dir; sources are # read from its compile_commands.json) +# CRA_SBOM_NO_HASH — not yet supported; blocked on SBOM-cgz # # Optional variables: # CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial) @@ -32,6 +36,9 @@ set -eu SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") +# Shared source-extraction helper (Keil/IAR/Makefile/compile_commands.json). +. "$SCRIPT_DIR/_cra-sbom-extract.sh" + # shellcheck disable=SC2015 WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} WOLFMQTT_DIR=${WOLFMQTT_DIR:-} @@ -175,14 +182,6 @@ _run_embedded() { exit 1 fi - # Resolve the source list. Priority order: - # 1. CRA_SBOM_SRCS_FILE — the caller knows their exact build better than any - # heuristic, so an explicit list always wins and is used verbatim. - # 2. compile_commands.json under CRA_SBOM_BUILD_DIR — ESP-IDF and other - # CMake-based builds emit this, the most common embedded path with - # build-system support; filter it to WOLFMQTT_DIR sources only. - # 3. Default glob of src/mqtt_*.c — documented fallback for Arduino and - # bare-metal builds that produce no compile_commands.json. SRCS_LIST=$(mktemp "${TMPDIR:-/tmp}/wolfmqtt-srcs.XXXXXX") || { echo "ERROR: mktemp failed for the source-list temp file." >&2 exit 1 @@ -196,46 +195,29 @@ _run_embedded() { echo "ERROR: mktemp failed for the empty options temp file." >&2 exit 1 } - trap 'rm -f "$SRCS_LIST" "$EMPTY_OPTS"' EXIT + # _cra_auto_tempfiles collects any temp files the shared extractor creates; + # initialise it so the EXIT trap is safe under `set -u` even when the + # extractor adds nothing (e.g. the default-glob path). + _cra_auto_tempfiles="" + trap 'rm -f "$SRCS_LIST" "$EMPTY_OPTS" $_cra_auto_tempfiles' EXIT - if [ -n "${CRA_SBOM_SRCS_FILE:-}" ]; then - if [ ! -f "$CRA_SBOM_SRCS_FILE" ]; then - echo "ERROR: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE not found." >&2 - exit 1 - fi - echo " Source list: CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE (verbatim)" - grep -v '^[[:space:]]*$' "$CRA_SBOM_SRCS_FILE" > "$SRCS_LIST" || { - echo "ERROR: failed to read CRA_SBOM_SRCS_FILE=$CRA_SBOM_SRCS_FILE." >&2 - exit 1 - } - elif [ -n "${CRA_SBOM_BUILD_DIR:-}" ] && [ -f "${CRA_SBOM_BUILD_DIR}/compile_commands.json" ]; then - _ccdb="${CRA_SBOM_BUILD_DIR}/compile_commands.json" - if ! command -v jq >/dev/null 2>&1; then - echo "ERROR: jq is required to read sources from $_ccdb." >&2 - echo " Install jq, or set CRA_SBOM_SRCS_FILE manually." >&2 - exit 1 - fi - echo " Source list: $_ccdb (filtered to WOLFMQTT_DIR)" - jq -r '.[].file' "$_ccdb" \ - | grep "^${WOLFMQTT_DIR}/" \ - | grep -E '/src/mqtt_[^/]+\.c$' \ - | sort -u > "$SRCS_LIST" || { - echo "ERROR: failed to extract sources from $_ccdb." >&2 - exit 1 - } - if [ ! -s "$SRCS_LIST" ]; then - echo "ERROR: $_ccdb yielded no wolfMQTT sources under $WOLFMQTT_DIR/src." >&2 - echo " Check CRA_SBOM_BUILD_DIR, or set CRA_SBOM_SRCS_FILE manually." >&2 - exit 1 - fi - else + # Resolve the source list via the shared extractor (CRA_SBOM_SRCS_FILE, + # Keil/IAR projects, Makefile dry-run, or compile_commands.json). It returns + # 2 when no extraction method is selected, in which case we fall back to the + # default mqtt_*.c glob. + _cra_rc=0 + _cra_extract_srcs "$WOLFMQTT_DIR" "wolfmqtt" "$SRCS_LIST" || _cra_rc=$? + + if [ "$_cra_rc" -eq 2 ]; then + # No extraction method active: use default glob (all src/mqtt_*.c sorted). + # Why: MQTT has no HAL split, so the full source set is the right default + # for bare-metal builds without a build-system-extractable source list. echo " Source list: default glob $WOLFMQTT_DIR/src/mqtt_*.c" - for _f in "$WOLFMQTT_DIR"/src/mqtt_*.c; do - [ -f "$_f" ] && echo "$_f" - done | sort -u > "$SRCS_LIST" || { - echo "ERROR: failed to enumerate $WOLFMQTT_DIR/src/mqtt_*.c." >&2 - exit 1 - } + for _c in "$WOLFMQTT_DIR"/src/mqtt_*.c; do + [ -f "$_c" ] && echo "$_c" + done | sort > "$SRCS_LIST" + elif [ "$_cra_rc" -ne 0 ]; then + exit 1 fi if [ ! -s "$SRCS_LIST" ]; then @@ -246,6 +228,7 @@ _run_embedded() { # Pass the resolved sources positionally to gen-sbom's --srcs (it takes a # space-separated list; argparse stops consuming at the next -- option, so # --cdx-out/--spdx-out terminate the list cleanly). + # ponytail: gen-sbom lacks --srcs-file; upgrade path: SBOM-cgz set -- while IFS= read -r _src; do [ -n "$_src" ] || continue From b907915855b03a37a54b689a4c10c53ab40d96ee Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:28:09 -0700 Subject: [PATCH 18/25] feat: add Keil/IAR/NO_HASH handlers to generate-wolfboot-sbom.sh --- cra-kit/scripts/generate-wolfboot-sbom.sh | 100 ++++++++++++++++------ 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/cra-kit/scripts/generate-wolfboot-sbom.sh b/cra-kit/scripts/generate-wolfboot-sbom.sh index 2a5f1d3f..6a354f29 100755 --- a/cra-kit/scripts/generate-wolfboot-sbom.sh +++ b/cra-kit/scripts/generate-wolfboot-sbom.sh @@ -25,11 +25,24 @@ # LicenseRef-* id: plain-text license # embedded in the SBOM) # CRA_SBOM_OUT_DIR= (override output directory) +# CRA_SBOM_SRCS_FILE=path explicit .c file list (overrides make -n) +# CRA_SBOM_KEIL_PROJECT=path auto-extract from Keil .uvprojx (overrides make -n) +# CRA_SBOM_IAR_PROJECT=path auto-extract from IAR .ewp (overrides make -n) +# CRA_SBOM_NO_HASH — not yet supported; blocked on SBOM-cgz (gen-sbom --no-artifact-hash) set -eu SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") +# Shared source-extraction helper. It appends temp files it creates to +# _cra_auto_tempfiles, so the EXIT trap below cleans both lists. +_auto_tempfiles="" +_cra_auto_tempfiles="" +trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT + +# shellcheck source=_cra-sbom-extract.sh disable=SC1091 +. "$SCRIPT_DIR/_cra-sbom-extract.sh" + # Default wolfBoot directory: sibling of the wolfssl-examples checkout. # shellcheck disable=SC2015 WOLFBOOT_DIR=${WOLFBOOT_DIR:-$(cd "$KIT_DIR/../../wolfBoot" 2>/dev/null && pwd || true)} @@ -113,43 +126,72 @@ if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT") fi -# Extract the configuration-specific source list via `make -n`. +# Extract the configuration-specific source list. # -# `make -n TARGET=... SIGN=...` prints every command make would run without -# executing them. The compiler invocations include every .c file on the -# wolfBoot link line, including both core wolfBoot sources and wolfcrypt -# files compiled inline. grep -oE pulls out every .c argument; sort -u -# deduplicates. -echo "Extracting source list via make -n TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN ..." - +# Priority order: +# 1. CRA_SBOM_SRCS_FILE explicit .c list +# 2. CRA_SBOM_KEIL_PROJECT Keil .uvprojx +# 3. CRA_SBOM_IAR_PROJECT IAR .ewp +# 4. make -n TARGET/SIGN wolfBoot's product-specific default (below) +# +# The shared helper handles 1-3. wolfBoot's Makefile path is product-specific +# (driven by TARGET/SIGN/HASH/EXT_FLASH, not the generic CRA_SBOM_MAKEFILE_DIR / +# compile_commands.json handlers), so we blank those two env vars before calling +# the helper and run our own make -n below when no IDE project is set. _srcs_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-srcs.XXXXXX") -trap 'rm -f "$_srcs_tmp"' EXIT +_auto_tempfiles="${_auto_tempfiles:-} $_srcs_tmp" -make --no-print-directory \ - -C "$WOLFBOOT_DIR" \ - -n \ - TARGET="$WOLFBOOT_TARGET" \ - SIGN="$WOLFBOOT_SIGN" \ - HASH="$WOLFBOOT_HASH" \ - EXT_FLASH="$WOLFBOOT_EXT_FLASH" \ - 2>/dev/null \ - | grep -oE '[^ ]+\.c' \ - | grep -v '\.h' \ - | sort -u > "$_srcs_tmp" || true +_saved_makefile_dir="${CRA_SBOM_MAKEFILE_DIR:-}" +_saved_build_dir="${CRA_SBOM_BUILD_DIR:-}" +CRA_SBOM_MAKEFILE_DIR="" +CRA_SBOM_BUILD_DIR="" -if [ ! -s "$_srcs_tmp" ]; then - echo "ERROR: make -n yielded no .c source files for TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN." >&2 - echo " Check that TARGET and SIGN are valid for this wolfBoot tree." >&2 +_cra_rc=0 +_cra_extract_srcs "$WOLFBOOT_DIR" "wolfboot" "$_srcs_tmp" || _cra_rc=$? + +CRA_SBOM_MAKEFILE_DIR="$_saved_makefile_dir" +CRA_SBOM_BUILD_DIR="$_saved_build_dir" + +if [ "$_cra_rc" -eq 0 ]; then + _n=$(wc -l < "$_srcs_tmp" | tr -d ' ') + echo " Extracted $_n source files (from IDE project / source list)" +elif [ "$_cra_rc" -eq 2 ]; then + # No IDE project set: use wolfBoot's product-specific make -n extraction. + # + # `make -n TARGET=... SIGN=...` prints every command make would run without + # executing them. The compiler invocations include every .c file on the + # wolfBoot link line, including both core wolfBoot sources and wolfcrypt + # files compiled inline. grep -oE pulls out every .c argument; sort -u + # deduplicates. + echo "Extracting source list via make -n TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN ..." + make --no-print-directory \ + -C "$WOLFBOOT_DIR" \ + -n \ + TARGET="$WOLFBOOT_TARGET" \ + SIGN="$WOLFBOOT_SIGN" \ + HASH="$WOLFBOOT_HASH" \ + EXT_FLASH="$WOLFBOOT_EXT_FLASH" \ + 2>/dev/null \ + | grep -oE '[^ ]+\.c' \ + | grep -v '\.h' \ + | sort -u > "$_srcs_tmp" || true + + if [ ! -s "$_srcs_tmp" ]; then + echo "ERROR: make -n yielded no .c source files for TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN." >&2 + echo " Check that TARGET and SIGN are valid for this wolfBoot tree." >&2 + exit 1 + fi + + _n=$(wc -l < "$_srcs_tmp" | tr -d ' ') + echo " Extracted $_n source files" +else exit 1 fi -_n=$(wc -l < "$_srcs_tmp" | tr -d ' ') -echo " Extracted $_n source files" - # Resolve paths to absolute: make -n emits relative paths; gen-sbom needs to # open the files to compute gitoid hashes. _srcs_abs_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-srcs-abs.XXXXXX") -trap 'rm -f "$_srcs_tmp" "$_srcs_abs_tmp"' EXIT +_auto_tempfiles="${_auto_tempfiles:-} $_srcs_abs_tmp" while IFS= read -r _src; do [ -n "$_src" ] || continue @@ -180,7 +222,7 @@ echo " Resolved $_n_abs paths ($(( _n - _n_abs )) non-existent skipped)" # cc -dM -E on the host with wolfBoot's include dirs to produce a flat # #define file that gen-sbom can parse for algorithm enablement. _defines_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-defines.XXXXXX") -trap 'rm -f "$_srcs_tmp" "$_srcs_abs_tmp" "$_defines_tmp"' EXIT +_auto_tempfiles="${_auto_tempfiles:-} $_defines_tmp" CC=${CC:-cc} echo " Preprocessing build settings via $CC -dM -E ..." @@ -200,6 +242,8 @@ command -v "$_PYTHON" >/dev/null 2>&1 || \ { echo "ERROR: $_PYTHON not found. Set CRA_PYTHON to your Python interpreter." >&2; exit 1; } # Read absolute source paths into positional parameters. +# ponytail: gen-sbom lacks --srcs-file; pass list as positional args +# ceiling: ARG_MAX; upgrade path: SBOM-cgz adds --srcs-file to gen-sbom set -- while IFS= read -r _src; do [ -n "$_src" ] || continue From b6bd384fb6a7851d283b4b61fbc57ef3f95150f0 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:29:21 -0700 Subject: [PATCH 19/25] fix: allow GEN_SBOM env override in generate-wolfboot-sbom.sh --- cra-kit/scripts/generate-wolfboot-sbom.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cra-kit/scripts/generate-wolfboot-sbom.sh b/cra-kit/scripts/generate-wolfboot-sbom.sh index 6a354f29..09a9427b 100755 --- a/cra-kit/scripts/generate-wolfboot-sbom.sh +++ b/cra-kit/scripts/generate-wolfboot-sbom.sh @@ -70,8 +70,8 @@ WOLFBOOT_EXT_FLASH=${WOLFBOOT_EXT_FLASH:-0} OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfboot-component"} -# gen-sbom lives inside the wolfssl submodule under wolfBoot. -GEN_SBOM="$WOLFBOOT_DIR/lib/wolfssl/scripts/gen-sbom" +# gen-sbom lives inside the wolfssl submodule under wolfBoot; GEN_SBOM env var overrides. +GEN_SBOM="${GEN_SBOM:-$WOLFBOOT_DIR/lib/wolfssl/scripts/gen-sbom}" if [ ! -f "$GEN_SBOM" ]; then echo "ERROR: gen-sbom not found at $GEN_SBOM" >&2 echo " Ensure the wolfssl submodule is initialized:" >&2 From 44bb6f9de711a6772e508e4ddf6747048f70c430 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:34:11 -0700 Subject: [PATCH 20/25] fix: add CRA_WOLFSENTRY_IP_STACK to resolve duplicate basename in wolfsentry SBOM --- cra-kit/scripts/generate-wolfsentry-sbom.sh | 36 +++++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/cra-kit/scripts/generate-wolfsentry-sbom.sh b/cra-kit/scripts/generate-wolfsentry-sbom.sh index a2aaf8c5..b6ea142a 100755 --- a/cra-kit/scripts/generate-wolfsentry-sbom.sh +++ b/cra-kit/scripts/generate-wolfsentry-sbom.sh @@ -11,6 +11,10 @@ # Optional variables: # CRA_SBOM_OUT_DIR= (default: $KIT_DIR/auditor-packet/wolfsentry-component) # CC= (default: cc; for -dM -E options dump) +# CRA_WOLFSENTRY_IP_STACK=wolfip|lwip|none +# (default: none; selects which optional IP-stack glue +# to include; wolfip/ and lwip/ have identical basenames +# so exactly one can be included per SBOM) set -eu SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) @@ -20,6 +24,18 @@ KIT_DIR=$(dirname "$SCRIPT_DIR") WOLFSENTRY_DIR=${WOLFSENTRY_DIR:-$(cd "$KIT_DIR/../../wolfsentry" 2>/dev/null && pwd || true)} OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfsentry-component"} +# CRA_WOLFSENTRY_IP_STACK selects which optional IP stack glue is included. +# Values: wolfip, lwip, none (default: none). +# Why default none: wolfip/ and lwip/ both contain packet_filter_glue.c; +# including both breaks gen-sbom's unique-basename requirement and would +# misrepresent the firmware (only one is compiled per build). +CRA_WOLFSENTRY_IP_STACK="${CRA_WOLFSENTRY_IP_STACK:-none}" + +case "$CRA_WOLFSENTRY_IP_STACK" in + wolfip|lwip|none) ;; + *) echo "ERROR: CRA_WOLFSENTRY_IP_STACK must be wolfip, lwip, or none (got: $CRA_WOLFSENTRY_IP_STACK)" >&2; exit 1 ;; +esac + if [ -z "${WOLFSENTRY_DIR:-}" ] || [ ! -d "$WOLFSENTRY_DIR" ]; then echo "ERROR: wolfSentry source not found." >&2 echo " Set WOLFSENTRY_DIR to your wolfsentry checkout (sibling of wolfssl-examples)." >&2 @@ -69,13 +85,27 @@ echo "gen-sbom: $GEN_SBOM" echo "Outputs: $CDX_OUT" echo " $SPDX_OUT" -# Enumerate all .c sources from $WOLFSENTRY_DIR/src/ (the directory the Makefile -# compiles from; conditional sources like json/ and lwip/ are subdirs of src/). -SRCS=$(find "$WOLFSENTRY_DIR/src" -name "*.c" | sort) +# Core sources: all .c files except the two IP stack subdirs (which have +# duplicate basenames and must be selected individually via CRA_WOLFSENTRY_IP_STACK). +SRCS=$(find "$WOLFSENTRY_DIR/src" -name "*.c" \ + ! -path "*/wolfip/*" \ + ! -path "*/lwip/*" \ + | sort) if [ -z "$SRCS" ]; then echo "ERROR: no .c files found under $WOLFSENTRY_DIR/src/" >&2 exit 1 fi + +# Optionally add the selected IP stack. +if [ "$CRA_WOLFSENTRY_IP_STACK" != "none" ]; then + SRCS="$SRCS +$(find "$WOLFSENTRY_DIR/src/$CRA_WOLFSENTRY_IP_STACK" -name "*.c" | sort)" + echo " IP stack: $CRA_WOLFSENTRY_IP_STACK" +else + echo " NOTE: CRA_WOLFSENTRY_IP_STACK not set; IP glue excluded from SBOM." + echo " Set CRA_WOLFSENTRY_IP_STACK=wolfip or lwip to include it." +fi + _n=$(echo "$SRCS" | wc -l | tr -d ' ') echo "Sources: $_n .c files from $WOLFSENTRY_DIR/src/" From ac8c33114f7f1094c45ed95d65d22594149ee3d7 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:44:53 -0700 Subject: [PATCH 21/25] feat: wire --srcs-file and CRA_SBOM_NO_HASH into all product SBOM scripts --- cra-kit/scripts/generate-wolfboot-sbom.sh | 58 +++++++++----- cra-kit/scripts/generate-wolfhsm-sbom.sh | 66 +++++++++------- cra-kit/scripts/generate-wolfmqtt-sbom.sh | 88 ++++++++++++--------- cra-kit/scripts/generate-wolfsentry-sbom.sh | 82 +++++++++++-------- cra-kit/scripts/generate-wolfssh-sbom.sh | 54 +++++++------ cra-kit/scripts/generate-wolftpm-sbom.sh | 56 ++++++++----- 6 files changed, 243 insertions(+), 161 deletions(-) diff --git a/cra-kit/scripts/generate-wolfboot-sbom.sh b/cra-kit/scripts/generate-wolfboot-sbom.sh index 09a9427b..dcc8849d 100755 --- a/cra-kit/scripts/generate-wolfboot-sbom.sh +++ b/cra-kit/scripts/generate-wolfboot-sbom.sh @@ -28,7 +28,10 @@ # CRA_SBOM_SRCS_FILE=path explicit .c file list (overrides make -n) # CRA_SBOM_KEIL_PROJECT=path auto-extract from Keil .uvprojx (overrides make -n) # CRA_SBOM_IAR_PROJECT=path auto-extract from IAR .ewp (overrides make -n) -# CRA_SBOM_NO_HASH — not yet supported; blocked on SBOM-cgz (gen-sbom --no-artifact-hash) +# CRA_SBOM_NO_HASH=true emit SBOM without an artifact hash, skipping +# the source list — for NDA customers who cannot +# share source lists; WARNING: not suitable for +# production compliance set -eu SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) @@ -53,16 +56,26 @@ if [ -z "${WOLFBOOT_DIR:-}" ] || [ ! -d "$WOLFBOOT_DIR" ]; then exit 1 fi -if [ -z "${WOLFBOOT_TARGET:-}" ]; then - echo "ERROR: WOLFBOOT_TARGET is not set." >&2 - echo " Example: WOLFBOOT_TARGET=stm32h7 $0" >&2 - exit 1 +# CRA_SBOM_NO_HASH skips the make -n source extraction entirely, so TARGET/SIGN +# (which only drive that extraction) are not required in that mode. +if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then + _no_hash=1 +else + _no_hash=0 fi -if [ -z "${WOLFBOOT_SIGN:-}" ]; then - echo "ERROR: WOLFBOOT_SIGN is not set." >&2 - echo " Example: WOLFBOOT_SIGN=ECC256 $0" >&2 - exit 1 +if [ "$_no_hash" = "0" ]; then + if [ -z "${WOLFBOOT_TARGET:-}" ]; then + echo "ERROR: WOLFBOOT_TARGET is not set." >&2 + echo " Example: WOLFBOOT_TARGET=stm32h7 $0" >&2 + exit 1 + fi + + if [ -z "${WOLFBOOT_SIGN:-}" ]; then + echo "ERROR: WOLFBOOT_SIGN is not set." >&2 + echo " Example: WOLFBOOT_SIGN=ECC256 $0" >&2 + exit 1 + fi fi WOLFBOOT_HASH=${WOLFBOOT_HASH:-SHA256} @@ -93,7 +106,9 @@ CDX_OUT="$OUT_DIR/wolfboot-${VERSION}.cdx.json" SPDX_OUT="$OUT_DIR/wolfboot-${VERSION}.spdx.json" echo "wolfBoot tree: $WOLFBOOT_DIR" -echo "Configuration: TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN HASH=$WOLFBOOT_HASH EXT_FLASH=$WOLFBOOT_EXT_FLASH" +if [ "$_no_hash" = "0" ]; then + echo "Configuration: TARGET=$WOLFBOOT_TARGET SIGN=$WOLFBOOT_SIGN HASH=$WOLFBOOT_HASH EXT_FLASH=$WOLFBOOT_EXT_FLASH" +fi echo "Version: $VERSION" echo "Outputs: $CDX_OUT" echo " $SPDX_OUT" @@ -126,6 +141,8 @@ if [ -n "${CRA_LICENSE_TEXT:-}" ] && [ -f "$CRA_LICENSE_TEXT" ]; then CRA_LICENSE_TEXT=$(CDPATH='' cd -- "$(dirname -- "$CRA_LICENSE_TEXT")" && pwd)/$(basename -- "$CRA_LICENSE_TEXT") fi +# CRA_SBOM_NO_HASH (resolved to $_no_hash above) emits a placeholder checksum +# and skips the source list entirely (NDA customers who cannot share sources). # Extract the configuration-specific source list. # # Priority order: @@ -138,6 +155,7 @@ fi # (driven by TARGET/SIGN/HASH/EXT_FLASH, not the generic CRA_SBOM_MAKEFILE_DIR / # compile_commands.json handlers), so we blank those two env vars before calling # the helper and run our own make -n below when no IDE project is set. +if [ "$_no_hash" = "0" ]; then _srcs_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-srcs.XXXXXX") _auto_tempfiles="${_auto_tempfiles:-} $_srcs_tmp" @@ -215,6 +233,10 @@ fi _n_abs=$(wc -l < "$_srcs_abs_tmp" | tr -d ' ') echo " Resolved $_n_abs paths ($(( _n - _n_abs )) non-existent skipped)" +else + echo "==> CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash." + echo " WARNING: not suitable for production CRA compliance." >&2 +fi # Preprocess build settings for gen-sbom --options-h. # @@ -241,16 +263,12 @@ _PYTHON=${CRA_PYTHON:-python3} command -v "$_PYTHON" >/dev/null 2>&1 || \ { echo "ERROR: $_PYTHON not found. Set CRA_PYTHON to your Python interpreter." >&2; exit 1; } -# Read absolute source paths into positional parameters. -# ponytail: gen-sbom lacks --srcs-file; pass list as positional args -# ceiling: ARG_MAX; upgrade path: SBOM-cgz adds --srcs-file to gen-sbom -set -- -while IFS= read -r _src; do - [ -n "$_src" ] || continue - set -- "$@" "$_src" -done < "$_srcs_abs_tmp" - _license_override=${CRA_LICENSE_OVERRIDE:-GPL-3.0-only} +if [ "$_no_hash" = "1" ]; then + set -- --no-artifact-hash +else + set -- --srcs-file "$_srcs_abs_tmp" +fi set -- "$@" \ --cdx-out "$CDX_OUT" \ --spdx-out "$SPDX_OUT" \ @@ -267,7 +285,7 @@ echo "==> Running gen-sbom ..." --supplier "wolfSSL Inc." \ --license-file "$WOLFBOOT_DIR/LICENSE" \ --options-h "$_defines_tmp" \ - --srcs "$@" + "$@" echo "SBOM written:" echo " $CDX_OUT" diff --git a/cra-kit/scripts/generate-wolfhsm-sbom.sh b/cra-kit/scripts/generate-wolfhsm-sbom.sh index ff98e33e..3fa5e927 100755 --- a/cra-kit/scripts/generate-wolfhsm-sbom.sh +++ b/cra-kit/scripts/generate-wolfhsm-sbom.sh @@ -20,7 +20,9 @@ # CRA_SBOM_KEIL_PROJECT=path auto-extract from Keil .uvprojx # CRA_SBOM_IAR_PROJECT=path auto-extract from IAR .ewp # CRA_SBOM_MAKEFILE_DIR=path auto-extract via make -n dry-run -# CRA_SBOM_NO_HASH — not yet supported; blocked on SBOM-cgz (gen-sbom --no-artifact-hash) +# CRA_SBOM_NO_HASH=true emit SBOM without an artifact hash (NDA +# customers who cannot share source lists; +# WARNING: not suitable for production compliance) set -eu . "$(dirname "$0")/_cra-sbom-extract.sh" @@ -120,38 +122,44 @@ _python_with_pcpp() { echo "==> Embedded path: gen-sbom with CC -dM -E (no user_settings.h)" -_srcs_file=$(mktemp "${TMPDIR:-/tmp}/wolfhsm-srcs.XXXXXX") -_auto_tempfiles="${_auto_tempfiles:-} $_srcs_file" +# CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list +# entirely (for NDA customers who cannot share source lists). +if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then + echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash." + echo " WARNING: not suitable for production CRA compliance." >&2 + _hash_arg="--no-artifact-hash" +else + _srcs_file=$(mktemp "${TMPDIR:-/tmp}/wolfhsm-srcs.XXXXXX") + _auto_tempfiles="${_auto_tempfiles:-} $_srcs_file" -# Allow WOLFHSM_BUILD_DIR to feed compile_commands.json extraction. The shared -# library reads CRA_SBOM_BUILD_DIR; map our wolfHSM-style env var onto it. -CRA_SBOM_BUILD_DIR="${WOLFHSM_BUILD_DIR:-}" + # Allow WOLFHSM_BUILD_DIR to feed compile_commands.json extraction. The + # shared library reads CRA_SBOM_BUILD_DIR; map our env var onto it. + CRA_SBOM_BUILD_DIR="${WOLFHSM_BUILD_DIR:-}" -_cra_rc=0 -_cra_extract_srcs "$WOLFHSM_DIR" "wolfhsm" "$_srcs_file" || _cra_rc=$? + _cra_rc=0 + _cra_extract_srcs "$WOLFHSM_DIR" "wolfhsm" "$_srcs_file" || _cra_rc=$? -if [ "$_cra_rc" -eq 2 ]; then - # No extraction method active: enumerate all wolfHSM C sources via find. - # find is used (not glob) because wolfHSM sources span subdirectories. - find "$WOLFHSM_DIR/src" -name "*.c" | sort > "$_srcs_file" || { - echo "ERROR: find failed on $WOLFHSM_DIR/src" >&2; exit 1 - } + if [ "$_cra_rc" -eq 2 ]; then + # No extraction method active: enumerate all wolfHSM C sources via find. + # find is used (not glob) because wolfHSM sources span subdirectories. + find "$WOLFHSM_DIR/src" -name "*.c" | sort > "$_srcs_file" || { + echo "ERROR: find failed on $WOLFHSM_DIR/src" >&2; exit 1 + } + _n=$(wc -l < "$_srcs_file" | tr -d ' ') + echo " Source list: find $WOLFHSM_DIR/src -name '*.c' ($_n files)" + elif [ "$_cra_rc" -ne 0 ]; then + exit 1 + fi + + if [ ! -s "$_srcs_file" ]; then + echo "ERROR: no wolfHSM sources found in $WOLFHSM_DIR/src" >&2 + exit 1 + fi _n=$(wc -l < "$_srcs_file" | tr -d ' ') - echo " Source list: find $WOLFHSM_DIR/src -name '*.c' ($_n files)" -elif [ "$_cra_rc" -ne 0 ]; then - exit 1 + echo "NOTE: hashed $_n source file(s)" + _hash_arg="--srcs-file $_srcs_file" fi -if [ ! -s "$_srcs_file" ]; then - echo "ERROR: no wolfHSM sources found in $WOLFHSM_DIR/src" >&2 - exit 1 -fi -_n=$(wc -l < "$_srcs_file" | tr -d ' ') -echo "NOTE: hashed $_n source file(s)" - -# ponytail: gen-sbom lacks --srcs-file; pass list as positional args -# ceiling: ARG_MAX; upgrade path: SBOM-cgz adds --srcs-file to gen-sbom - # Build license-override args. _license_args="" if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then @@ -180,7 +188,7 @@ if _py=$(_python_with_pcpp); then --user-settings "$SETTINGS_H" \ --user-settings-include "$WOLFHSM_DIR" \ --user-settings-include "$WOLFSSL_DIR" \ - --srcs $(cat "$_srcs_file") \ + ${_hash_arg} \ --cdx-out "$CDX_OUT" \ --spdx-out "$SPDX_OUT" \ ${_license_args} @@ -209,7 +217,7 @@ else --supplier "wolfSSL Inc." \ --license-file "$WOLFHSM_DIR/LICENSING" \ --options-h "$_defines" \ - --srcs $(cat "$_srcs_file") \ + ${_hash_arg} \ --cdx-out "$CDX_OUT" \ --spdx-out "$SPDX_OUT" \ ${_license_args} diff --git a/cra-kit/scripts/generate-wolfmqtt-sbom.sh b/cra-kit/scripts/generate-wolfmqtt-sbom.sh index 93cf45a3..cfae983a 100755 --- a/cra-kit/scripts/generate-wolfmqtt-sbom.sh +++ b/cra-kit/scripts/generate-wolfmqtt-sbom.sh @@ -21,7 +21,10 @@ # CRA_SBOM_MAKEFILE_DIR=path/to/dir (run `make -n` to extract .c sources) # CRA_SBOM_BUILD_DIR=path/to/build (CMake/ESP-IDF build dir; sources are # read from its compile_commands.json) -# CRA_SBOM_NO_HASH — not yet supported; blocked on SBOM-cgz +# CRA_SBOM_NO_HASH=true (emit SBOM without an artifact hash, +# skipping the source list — for NDA +# customers who cannot share source lists; +# WARNING: not suitable for production compliance) # # Optional variables: # CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfSSL-Commercial) @@ -201,46 +204,51 @@ _run_embedded() { _cra_auto_tempfiles="" trap 'rm -f "$SRCS_LIST" "$EMPTY_OPTS" $_cra_auto_tempfiles' EXIT - # Resolve the source list via the shared extractor (CRA_SBOM_SRCS_FILE, - # Keil/IAR projects, Makefile dry-run, or compile_commands.json). It returns - # 2 when no extraction method is selected, in which case we fall back to the - # default mqtt_*.c glob. - _cra_rc=0 - _cra_extract_srcs "$WOLFMQTT_DIR" "wolfmqtt" "$SRCS_LIST" || _cra_rc=$? + # CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list + # entirely (for NDA customers who cannot share source lists). + if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then + echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash." + echo " WARNING: not suitable for production CRA compliance." >&2 + _count=0 + set -- --no-artifact-hash --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" + else + # Resolve the source list via the shared extractor (CRA_SBOM_SRCS_FILE, + # Keil/IAR projects, Makefile dry-run, or compile_commands.json). It + # returns 2 when no extraction method is selected, in which case we + # fall back to the default mqtt_*.c glob. + _cra_rc=0 + _cra_extract_srcs "$WOLFMQTT_DIR" "wolfmqtt" "$SRCS_LIST" || _cra_rc=$? - if [ "$_cra_rc" -eq 2 ]; then - # No extraction method active: use default glob (all src/mqtt_*.c sorted). - # Why: MQTT has no HAL split, so the full source set is the right default - # for bare-metal builds without a build-system-extractable source list. - echo " Source list: default glob $WOLFMQTT_DIR/src/mqtt_*.c" - for _c in "$WOLFMQTT_DIR"/src/mqtt_*.c; do - [ -f "$_c" ] && echo "$_c" - done | sort > "$SRCS_LIST" - elif [ "$_cra_rc" -ne 0 ]; then - exit 1 - fi - - if [ ! -s "$SRCS_LIST" ]; then - echo "ERROR: no MQTT source files found to hash." >&2 - exit 1 - fi - - # Pass the resolved sources positionally to gen-sbom's --srcs (it takes a - # space-separated list; argparse stops consuming at the next -- option, so - # --cdx-out/--spdx-out terminate the list cleanly). - # ponytail: gen-sbom lacks --srcs-file; upgrade path: SBOM-cgz - set -- - while IFS= read -r _src; do - [ -n "$_src" ] || continue - if [ ! -f "$_src" ]; then - echo "ERROR: listed source not found: $_src" >&2 + if [ "$_cra_rc" -eq 2 ]; then + # No extraction method active: use default glob (all src/mqtt_*.c + # sorted). MQTT has no HAL split, so the full source set is the + # right default for bare-metal builds without an extractable list. + echo " Source list: default glob $WOLFMQTT_DIR/src/mqtt_*.c" + for _c in "$WOLFMQTT_DIR"/src/mqtt_*.c; do + [ -f "$_c" ] && echo "$_c" + done | sort > "$SRCS_LIST" + elif [ "$_cra_rc" -ne 0 ]; then exit 1 fi - set -- "$@" "$_src" - done < "$SRCS_LIST" - _count=$# - set -- --srcs "$@" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" + if [ ! -s "$SRCS_LIST" ]; then + echo "ERROR: no MQTT source files found to hash." >&2 + exit 1 + fi + + # Validate every resolved path exists before handing the file to gen-sbom. + _count=0 + while IFS= read -r _src; do + [ -n "$_src" ] || continue + if [ ! -f "$_src" ]; then + echo "ERROR: listed source not found: $_src" >&2 + exit 1 + fi + _count=$((_count + 1)) + done < "$SRCS_LIST" + + set -- --srcs-file "$SRCS_LIST" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" + fi if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" if [ -n "${CRA_LICENSE_TEXT:-}" ]; then @@ -257,7 +265,11 @@ _run_embedded() { exit 1 } - echo "NOTE: hashed ${_count} source file(s)" + if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then + echo "NOTE: artifact hash omitted (CRA_SBOM_NO_HASH)" + else + echo "NOTE: hashed ${_count} source file(s)" + fi } case "${CRA_SBOM_MODE:-autotools}" in diff --git a/cra-kit/scripts/generate-wolfsentry-sbom.sh b/cra-kit/scripts/generate-wolfsentry-sbom.sh index b6ea142a..c8f32e25 100755 --- a/cra-kit/scripts/generate-wolfsentry-sbom.sh +++ b/cra-kit/scripts/generate-wolfsentry-sbom.sh @@ -13,8 +13,11 @@ # CC= (default: cc; for -dM -E options dump) # CRA_WOLFSENTRY_IP_STACK=wolfip|lwip|none # (default: none; selects which optional IP-stack glue -# to include; wolfip/ and lwip/ have identical basenames -# so exactly one can be included per SBOM) +# to include in the firmware source set) +# CRA_SBOM_NO_HASH=true emit SBOM without an artifact hash, skipping the +# source list — for NDA customers who cannot share +# source lists; WARNING: not suitable for production +# compliance set -eu SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) @@ -26,9 +29,11 @@ OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfsentry-component"} # CRA_WOLFSENTRY_IP_STACK selects which optional IP stack glue is included. # Values: wolfip, lwip, none (default: none). -# Why default none: wolfip/ and lwip/ both contain packet_filter_glue.c; -# including both breaks gen-sbom's unique-basename requirement and would -# misrepresent the firmware (only one is compiled per build). +# Why default none / exactly one: wolfip/ and lwip/ both contain +# packet_filter_glue.c, and a firmware build compiles exactly one IP stack; +# including both would misrepresent what is actually in the firmware. +# (gen-sbom keys its Merkle hash on relative path, not basename, so the two +# same-named files no longer collide -- but a build still uses only one.) CRA_WOLFSENTRY_IP_STACK="${CRA_WOLFSENTRY_IP_STACK:-none}" case "$CRA_WOLFSENTRY_IP_STACK" in @@ -85,35 +90,45 @@ echo "gen-sbom: $GEN_SBOM" echo "Outputs: $CDX_OUT" echo " $SPDX_OUT" -# Core sources: all .c files except the two IP stack subdirs (which have -# duplicate basenames and must be selected individually via CRA_WOLFSENTRY_IP_STACK). -SRCS=$(find "$WOLFSENTRY_DIR/src" -name "*.c" \ - ! -path "*/wolfip/*" \ - ! -path "*/lwip/*" \ - | sort) -if [ -z "$SRCS" ]; then - echo "ERROR: no .c files found under $WOLFSENTRY_DIR/src/" >&2 - exit 1 -fi - -# Optionally add the selected IP stack. -if [ "$CRA_WOLFSENTRY_IP_STACK" != "none" ]; then - SRCS="$SRCS -$(find "$WOLFSENTRY_DIR/src/$CRA_WOLFSENTRY_IP_STACK" -name "*.c" | sort)" - echo " IP stack: $CRA_WOLFSENTRY_IP_STACK" +# CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list +# entirely (for NDA customers who cannot share source lists). +if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then + _no_hash=1 + echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash." + echo " WARNING: not suitable for production CRA compliance." >&2 else - echo " NOTE: CRA_WOLFSENTRY_IP_STACK not set; IP glue excluded from SBOM." - echo " Set CRA_WOLFSENTRY_IP_STACK=wolfip or lwip to include it." -fi + _no_hash=0 + # Core sources: all .c files except the two IP stack subdirs, which are + # selected individually via CRA_WOLFSENTRY_IP_STACK (a build compiles one). + SRCS=$(find "$WOLFSENTRY_DIR/src" -name "*.c" \ + ! -path "*/wolfip/*" \ + ! -path "*/lwip/*" \ + | sort) + if [ -z "$SRCS" ]; then + echo "ERROR: no .c files found under $WOLFSENTRY_DIR/src/" >&2 + exit 1 + fi -_n=$(echo "$SRCS" | wc -l | tr -d ' ') -echo "Sources: $_n .c files from $WOLFSENTRY_DIR/src/" + # Optionally add the selected IP stack. + if [ "$CRA_WOLFSENTRY_IP_STACK" != "none" ]; then + SRCS="$SRCS +$(find "$WOLFSENTRY_DIR/src/$CRA_WOLFSENTRY_IP_STACK" -name "*.c" | sort)" + echo " IP stack: $CRA_WOLFSENTRY_IP_STACK" + else + echo " NOTE: CRA_WOLFSENTRY_IP_STACK not set; IP glue excluded from SBOM." + echo " Set CRA_WOLFSENTRY_IP_STACK=wolfip or lwip to include it." + fi + + _n=$(echo "$SRCS" | wc -l | tr -d ' ') + echo "Sources: $_n .c files from $WOLFSENTRY_DIR/src/" +fi # Dump compiler defines for --options-h (no user_settings.h; wolfsentry is # configured via Makefile flags, not a settings header). CC=${CC:-cc} _defines_h=$(mktemp "${TMPDIR:-/tmp}/wolfsentry-defines.XXXXXX") -trap 'rm -f "$_defines_h"' EXIT +_srcs_file=$(mktemp "${TMPDIR:-/tmp}/wolfsentry-srcs.XXXXXX") +trap 'rm -f "$_defines_h" "$_srcs_file"' EXIT if ! "$CC" -dM -E -I"$WOLFSENTRY_DIR" -x c /dev/null >"$_defines_h" 2>/dev/null; then echo "ERROR: $CC -dM -E failed; set CC to an available compiler." >&2 exit 1 @@ -124,9 +139,14 @@ if ! command -v python3 >/dev/null 2>&1; then exit 1 fi -# Build --srcs argument list from the source enumeration. -# shellcheck disable=SC2086 -set -- $SRCS +# Build the component-checksum argument: either a placeholder (NO_HASH) or the +# enumerated source list written one path per line for gen-sbom's --srcs-file. +if [ "$_no_hash" = "1" ]; then + set -- --no-artifact-hash +else + printf '%s\n' "$SRCS" > "$_srcs_file" + set -- --srcs-file "$_srcs_file" +fi python3 "$GEN_SBOM" \ --name wolfsentry \ @@ -134,7 +154,7 @@ python3 "$GEN_SBOM" \ --supplier "wolfSSL Inc." \ --license-file "$WOLFSENTRY_DIR/LICENSING" \ --options-h "$_defines_h" \ - --srcs "$@" \ + "$@" \ --cdx-out "$CDX_OUT" \ --spdx-out "$SPDX_OUT" diff --git a/cra-kit/scripts/generate-wolfssh-sbom.sh b/cra-kit/scripts/generate-wolfssh-sbom.sh index d54f9d3f..e1234bcb 100755 --- a/cra-kit/scripts/generate-wolfssh-sbom.sh +++ b/cra-kit/scripts/generate-wolfssh-sbom.sh @@ -22,7 +22,10 @@ # CRA_SBOM_KEIL_PROJECT=path (embedded: auto-extract from Keil .uvprojx) # CRA_SBOM_IAR_PROJECT=path (embedded: auto-extract from IAR .ewp) # CRA_SBOM_MAKEFILE_DIR=path (embedded: auto-extract via make -n dry-run) -# CRA_SBOM_NO_HASH — not yet supported; blocked on SBOM-cgz (gen-sbom --no-artifact-hash) +# CRA_SBOM_NO_HASH=true (embedded: emit SBOM without an artifact +# hash, skipping the source list — for NDA +# customers who cannot share source lists; +# WARNING: not suitable for production compliance) # # Optional variables: # CRA_SBOM_OUT_DIR= (output directory; default auditor-packet) @@ -208,34 +211,35 @@ _run_embedded() { exit 1 fi - # Resolve the source list into a temp file (cleaned up by the EXIT trap). - _srcs=$(mktemp "${TMPDIR:-/tmp}/wolfssh-srcs.XXXXXX") || { - echo "ERROR: mktemp failed for the source-list temp file." >&2 - exit 1 - } - _auto_tempfiles="${_auto_tempfiles:-} $_srcs" - _resolve_wolfssh_srcs "$_srcs" + # CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list + # entirely (for NDA customers who cannot share source lists). + if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then + echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash." + echo " WARNING: not suitable for production CRA compliance." >&2 + set -- --no-artifact-hash --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" + else + # Resolve the source list into a temp file (cleaned up by the EXIT trap). + _srcs=$(mktemp "${TMPDIR:-/tmp}/wolfssh-srcs.XXXXXX") || { + echo "ERROR: mktemp failed for the source-list temp file." >&2 + exit 1 + } + _auto_tempfiles="${_auto_tempfiles:-} $_srcs" + _resolve_wolfssh_srcs "$_srcs" - # Build the positional --srcs argument list from the resolved file. - # ponytail: gen-sbom lacks --srcs-file; pass list as positional args - # ceiling: ARG_MAX; upgrade path: SBOM-cgz adds --srcs-file to gen-sbom - set -- - while IFS= read -r _src; do - [ -n "$_src" ] || continue - if [ ! -f "$_src" ]; then - echo "ERROR: source file does not exist: $_src" >&2 + # Validate every resolved path exists before handing the file to gen-sbom. + while IFS= read -r _src; do + [ -n "$_src" ] || continue + if [ ! -f "$_src" ]; then + echo "ERROR: source file does not exist: $_src" >&2 + exit 1 + fi + done < "$_srcs" + if [ ! -s "$_srcs" ]; then + echo "ERROR: resolved source list is empty." >&2 exit 1 fi - set -- "$@" "$_src" - done < "$_srcs" - if [ $# -eq 0 ]; then - echo "ERROR: resolved source list is empty." >&2 - exit 1 + set -- --srcs-file "$_srcs" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" fi - - # Prepend --srcs, then append the trailing options. argparse stops --srcs - # consumption at the next -- option, so --cdx-out / --spdx-out end it cleanly. - set -- --srcs "$@" --cdx-out "$CDX_OUT" --spdx-out "$SPDX_OUT" if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" if [ -n "${CRA_LICENSE_TEXT:-}" ]; then diff --git a/cra-kit/scripts/generate-wolftpm-sbom.sh b/cra-kit/scripts/generate-wolftpm-sbom.sh index e791118f..075b74d7 100755 --- a/cra-kit/scripts/generate-wolftpm-sbom.sh +++ b/cra-kit/scripts/generate-wolftpm-sbom.sh @@ -27,7 +27,9 @@ # CRA_SBOM_IAR_PROJECT= (embedded mode: auto-extract srcs from an IAR .ewp) # CRA_SBOM_MAKEFILE_DIR= (embedded mode: auto-extract srcs via `make -n`) # CRA_SBOM_NO_HASH=true (embedded mode: emit SBOM without a real artifact -# hash; use when no source list is available) +# hash, skipping the source list — for NDA customers +# who cannot share source lists; WARNING: not +# suitable for production compliance) # CRA_TPM_OPTIONS_H=path/to/options.h (embedded mode: flat #define build-config header for # feature enumeration; defaults to # $WOLFTPM_DIR/wolftpm/options.h) @@ -222,13 +224,7 @@ _run_cmake() { set -- "$@" --license-text "$CRA_LICENSE_TEXT" fi fi - # ponytail: gen-sbom lacks --srcs-file; pass list as positional args after --srcs - # ceiling: ARG_MAX on very large source trees; upgrade path: SBOM-cgz - set -- "$@" --srcs - while IFS= read -r _src; do - [ -n "$_src" ] || continue - set -- "$@" "$_src" - done < "$CRA_SBOM_SRCS_FILE" + set -- "$@" --srcs-file "$CRA_SBOM_SRCS_FILE" "$PYTHON3" "$GEN" "$@" } @@ -259,8 +255,39 @@ _run_embedded() { exit 1 fi - # CRA_SBOM_NO_HASH is not yet supported: gen-sbom requires --lib or --srcs and - # has no --no-artifact-hash flag. Blocked on SBOM-cgz. + # CRA_SBOM_NO_HASH emits a placeholder checksum and skips the source list + # entirely (for NDA customers who cannot share source lists). OPTIONS_H is + # still required so the SBOM records the enabled-feature build properties. + if [ "${CRA_SBOM_NO_HASH:-}" = "true" ] || [ "${CRA_SBOM_NO_HASH:-}" = "1" ]; then + echo " NOTE: CRA_SBOM_NO_HASH=true: emitting SBOM without artifact hash." + echo " WARNING: not suitable for production CRA compliance." >&2 + set -- \ + --name wolftpm \ + --version "$VERSION" \ + --supplier "wolfSSL Inc." \ + --license-file "$WOLFTPM_DIR/LICENSE" \ + --options-h "$OPTIONS_H" \ + --no-artifact-hash \ + --cdx-out "$CDX_OUT" \ + --spdx-out "$SPDX_OUT" + if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then + set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE" + if [ -n "${CRA_LICENSE_TEXT:-}" ]; then + set -- "$@" --license-text "$CRA_LICENSE_TEXT" + fi + fi + "$GEN_PY" "$GEN" "$@" || { + echo "ERROR: gen-sbom failed in embedded mode." >&2 + exit 1 + } + for _out in "$CDX_OUT" "$SPDX_OUT"; do + if [ ! -s "$_out" ]; then + echo "ERROR: expected output $_out is missing or empty." >&2 + exit 1 + fi + done + return 0 + fi # Source list, one .c path per line. _srcs=$(mktemp "${TMPDIR:-/tmp}/wolftpm-embedded-srcs.XXXXXX") || { @@ -355,9 +382,6 @@ _run_embedded() { # separate component covered by generate-wolfssl-sbom.sh (embedded mode), and # the wolfSSL SBOM is referenced as a dependency rather than duplicated. - # gen-sbom takes the source list as a positional --srcs vector (exactly one - # of --lib / --srcs is accepted). Build the option vector first, then append - # the collected paths after --srcs so they bind as that argument's nargs list. set -- \ --name wolftpm \ --version "$VERSION" \ @@ -372,11 +396,7 @@ _run_embedded() { set -- "$@" --license-text "$CRA_LICENSE_TEXT" fi fi - set -- "$@" --srcs - while IFS= read -r _src; do - [ -n "$_src" ] || continue - set -- "$@" "$_src" - done < "$_srcs" + set -- "$@" --srcs-file "$_srcs" "$GEN_PY" "$GEN" "$@" || { echo "ERROR: gen-sbom failed in embedded mode." >&2 exit 1 From 0032a3a33ebc3c1064178a39ea31c90ebdbbc951 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:53:07 -0700 Subject: [PATCH 22/25] fix: parse wolfsentry version from MAJOR/MINOR/TINY macros --- cra-kit/scripts/generate-wolfsentry-sbom.sh | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cra-kit/scripts/generate-wolfsentry-sbom.sh b/cra-kit/scripts/generate-wolfsentry-sbom.sh index c8f32e25..8acbed98 100755 --- a/cra-kit/scripts/generate-wolfsentry-sbom.sh +++ b/cra-kit/scripts/generate-wolfsentry-sbom.sh @@ -69,16 +69,17 @@ if [ ! -f "$HEADER" ]; then echo "ERROR: version header not found: $HEADER" >&2 exit 1 fi -VERSION=$(awk ' - /WOLFSENTRY_VERSION_MAJOR/ { maj=$3 } - /WOLFSENTRY_VERSION_MINOR/ { min=$3 } - /WOLFSENTRY_VERSION_TINY/ { tiny=$3 } - END { print maj"."min"."tiny } -' "$HEADER") -if [ -z "$VERSION" ] || [ "$VERSION" = ".." ]; then - echo "ERROR: could not extract version from $HEADER" >&2 - exit 1 -fi +_extract_ver() { + grep -E "^#define[[:space:]]+$1[[:space:]]+[0-9]+" "$HEADER" | awk '{print $3}' +} +_major=$(_extract_ver WOLFSENTRY_VERSION_MAJOR) +_minor=$(_extract_ver WOLFSENTRY_VERSION_MINOR) +_tiny=$(_extract_ver WOLFSENTRY_VERSION_TINY) +VERSION="${_major}.${_minor}.${_tiny}" +case "$VERSION" in + [0-9]*.[0-9]*.[0-9]*) ;; + *) echo "ERROR: could not parse wolfsentry version from $HEADER (got: '$VERSION')" >&2; exit 1 ;; +esac mkdir -p "$OUT_DIR" CDX_OUT="$OUT_DIR/wolfsentry-${VERSION}.cdx.json" From d318dd27093f3d525c7d7e6b3b909a4642ca9739 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 22 Jun 2026 19:54:06 -0700 Subject: [PATCH 23/25] fix: add --options-h to wolfTPM cmake SBOM mode --- cra-kit/scripts/generate-wolftpm-sbom.sh | 26 +++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/cra-kit/scripts/generate-wolftpm-sbom.sh b/cra-kit/scripts/generate-wolftpm-sbom.sh index 075b74d7..265f16c5 100755 --- a/cra-kit/scripts/generate-wolftpm-sbom.sh +++ b/cra-kit/scripts/generate-wolftpm-sbom.sh @@ -30,9 +30,11 @@ # hash, skipping the source list — for NDA customers # who cannot share source lists; WARNING: not # suitable for production compliance) -# CRA_TPM_OPTIONS_H=path/to/options.h (embedded mode: flat #define build-config header for -# feature enumeration; defaults to -# $WOLFTPM_DIR/wolftpm/options.h) +# CRA_TPM_OPTIONS_H=path/to/options.h (embedded/cmake mode: flat #define build-config header +# for feature enumeration. Embedded mode defaults to +# $WOLFTPM_DIR/wolftpm/options.h; cmake mode prefers the +# cmake-generated $WOLFTPM_BUILD_DIR/wolftpm/options.h, +# then falls back to the source-tree copy.) # # Optional variables: # CRA_LICENSE_OVERRIDE= (e.g. LicenseRef-wolfTPM-Commercial) @@ -211,11 +213,29 @@ _run_cmake() { exit 1 fi + # gen-sbom requires exactly one of --options-h / --user-settings to enumerate + # enabled features. Prefer the cmake-generated header (reflects the actual + # build config) over the source-tree template; CRA_TPM_OPTIONS_H overrides both. + _options_h="${CRA_TPM_OPTIONS_H:-}" + if [ -z "$_options_h" ] && [ -n "${WOLFTPM_BUILD_DIR:-}" ] && \ + [ -f "$WOLFTPM_BUILD_DIR/wolftpm/options.h" ]; then + _options_h="$WOLFTPM_BUILD_DIR/wolftpm/options.h" + fi + if [ -z "$_options_h" ] && [ -f "$WOLFTPM_DIR/wolftpm/options.h" ]; then + _options_h="$WOLFTPM_DIR/wolftpm/options.h" + fi + if [ -z "$_options_h" ]; then + echo "ERROR: no wolftpm/options.h found." >&2 + echo " Run cmake to generate it, or set CRA_TPM_OPTIONS_H." >&2 + exit 1 + fi + set -- \ --name wolftpm \ --version "$VERSION" \ --supplier "wolfSSL Inc." \ --license-file "$WOLFTPM_DIR/LICENSE" \ + --options-h "$_options_h" \ --cdx-out "$CDX_OUT" \ --spdx-out "$SPDX_OUT" if [ -n "${CRA_LICENSE_OVERRIDE:-}" ]; then From 9a226cd41d2905b76e9625f009082f8b22d2243b Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Mon, 6 Jul 2026 19:04:30 -0700 Subject: [PATCH 24/25] fix(cra-kit): clean shellcheck warnings in sbom scripts generate-wolfssh: add missing _cra_auto_tempfiles init (SC2154). generate-wolfhsm: make CRA_SBOM_BUILD_DIR self-referential so shellcheck sees the sourced-helper use (SC2034). Rewrite pipefail probe as if-then; annotate the sourced-helper (SC1091) and intentional cd-fallback (SC2015) lines. shellcheck cra-kit/scripts/*.sh now exits 0. --- cra-kit/scripts/generate-wolfboot-sbom.sh | 2 ++ cra-kit/scripts/generate-wolfhsm-sbom.sh | 4 +++- cra-kit/scripts/generate-wolfmqtt-sbom.sh | 4 +++- cra-kit/scripts/generate-wolfsentry-sbom.sh | 1 + cra-kit/scripts/generate-wolfssh-sbom.sh | 4 ++++ cra-kit/scripts/generate-wolfssl-sbom.sh | 2 ++ cra-kit/scripts/generate-wolftpm-sbom.sh | 3 +++ 7 files changed, 18 insertions(+), 2 deletions(-) diff --git a/cra-kit/scripts/generate-wolfboot-sbom.sh b/cra-kit/scripts/generate-wolfboot-sbom.sh index dcc8849d..1207688f 100755 --- a/cra-kit/scripts/generate-wolfboot-sbom.sh +++ b/cra-kit/scripts/generate-wolfboot-sbom.sh @@ -44,10 +44,12 @@ _cra_auto_tempfiles="" trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT # shellcheck source=_cra-sbom-extract.sh disable=SC1091 +# shellcheck disable=SC1091 # sourced helper, resolved at runtime . "$SCRIPT_DIR/_cra-sbom-extract.sh" # Default wolfBoot directory: sibling of the wolfssl-examples checkout. # shellcheck disable=SC2015 +# shellcheck disable=SC2015 # fallback to unset on cd failure is intentional WOLFBOOT_DIR=${WOLFBOOT_DIR:-$(cd "$KIT_DIR/../../wolfBoot" 2>/dev/null && pwd || true)} if [ -z "${WOLFBOOT_DIR:-}" ] || [ ! -d "$WOLFBOOT_DIR" ]; then diff --git a/cra-kit/scripts/generate-wolfhsm-sbom.sh b/cra-kit/scripts/generate-wolfhsm-sbom.sh index 3fa5e927..325a323d 100755 --- a/cra-kit/scripts/generate-wolfhsm-sbom.sh +++ b/cra-kit/scripts/generate-wolfhsm-sbom.sh @@ -25,6 +25,7 @@ # WARNING: not suitable for production compliance) set -eu +# shellcheck disable=SC1091 # sourced helper, resolved at runtime . "$(dirname "$0")/_cra-sbom-extract.sh" # Accumulator for temp files; cleaned up on exit. The shared extraction library @@ -38,6 +39,7 @@ KIT_DIR=$(dirname "$SCRIPT_DIR") # Locate WOLFSSL_DIR (default: sibling of wolfssl-examples). # shellcheck disable=SC2015 +# shellcheck disable=SC2015 # fallback to unset on cd failure is intentional WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} # WOLFHSM_DIR has no sensible default; must be explicit. WOLFHSM_DIR=${WOLFHSM_DIR:-} @@ -134,7 +136,7 @@ else # Allow WOLFHSM_BUILD_DIR to feed compile_commands.json extraction. The # shared library reads CRA_SBOM_BUILD_DIR; map our env var onto it. - CRA_SBOM_BUILD_DIR="${WOLFHSM_BUILD_DIR:-}" + CRA_SBOM_BUILD_DIR="${CRA_SBOM_BUILD_DIR:-${WOLFHSM_BUILD_DIR:-}}" _cra_rc=0 _cra_extract_srcs "$WOLFHSM_DIR" "wolfhsm" "$_srcs_file" || _cra_rc=$? diff --git a/cra-kit/scripts/generate-wolfmqtt-sbom.sh b/cra-kit/scripts/generate-wolfmqtt-sbom.sh index cfae983a..a99ed2f9 100755 --- a/cra-kit/scripts/generate-wolfmqtt-sbom.sh +++ b/cra-kit/scripts/generate-wolfmqtt-sbom.sh @@ -34,15 +34,17 @@ set -eu # Enable pipefail when the shell supports it (bash/ksh/some dash builds). # Plain POSIX sh may not; tolerate its absence so the script still runs. # shellcheck disable=SC3040 -(set -o pipefail) 2>/dev/null && set -o pipefail || true +if (set -o pipefail) 2>/dev/null; then set -o pipefail; fi SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") # Shared source-extraction helper (Keil/IAR/Makefile/compile_commands.json). +# shellcheck disable=SC1091 # sourced helper, resolved at runtime . "$SCRIPT_DIR/_cra-sbom-extract.sh" # shellcheck disable=SC2015 +# shellcheck disable=SC2015 # fallback to unset on cd failure is intentional WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} WOLFMQTT_DIR=${WOLFMQTT_DIR:-} diff --git a/cra-kit/scripts/generate-wolfsentry-sbom.sh b/cra-kit/scripts/generate-wolfsentry-sbom.sh index 8acbed98..2a458058 100755 --- a/cra-kit/scripts/generate-wolfsentry-sbom.sh +++ b/cra-kit/scripts/generate-wolfsentry-sbom.sh @@ -24,6 +24,7 @@ SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") # shellcheck disable=SC2015 +# shellcheck disable=SC2015 # fallback to unset on cd failure is intentional WOLFSENTRY_DIR=${WOLFSENTRY_DIR:-$(cd "$KIT_DIR/../../wolfsentry" 2>/dev/null && pwd || true)} OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfsentry-component"} diff --git a/cra-kit/scripts/generate-wolfssh-sbom.sh b/cra-kit/scripts/generate-wolfssh-sbom.sh index e1234bcb..c05b6a03 100755 --- a/cra-kit/scripts/generate-wolfssh-sbom.sh +++ b/cra-kit/scripts/generate-wolfssh-sbom.sh @@ -43,6 +43,7 @@ set -eu # Why: mktemp temp files must not leak if any later command fails under set -e; # a single EXIT trap removes them on every exit path including errors. _auto_tempfiles="" +_cra_auto_tempfiles="" # _cra_auto_tempfiles is populated by _cra-sbom-extract.sh's helpers; clean both. trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT @@ -50,9 +51,12 @@ SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) KIT_DIR=$(dirname "$SCRIPT_DIR") # shared extraction methods (Keil, IAR, Makefile, compile_commands.json) +# shellcheck disable=SC1091 # sourced helper, resolved at runtime . "$SCRIPT_DIR/_cra-sbom-extract.sh" # shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else +# shellcheck disable=SC2015 # fallback to unset on cd failure is intentional WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} +# shellcheck disable=SC2015 # fallback to unset on cd failure is intentional WOLFSSH_DIR=${WOLFSSH_DIR:-$(cd "$KIT_DIR/../../wolfSSH" 2>/dev/null && pwd || true)} OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfssh-component"} diff --git a/cra-kit/scripts/generate-wolfssl-sbom.sh b/cra-kit/scripts/generate-wolfssl-sbom.sh index 57c74698..293cd92b 100755 --- a/cra-kit/scripts/generate-wolfssl-sbom.sh +++ b/cra-kit/scripts/generate-wolfssl-sbom.sh @@ -43,9 +43,11 @@ trap 'rm -f ${_auto_tempfiles:-} ${_cra_auto_tempfiles:-}' EXIT SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) # shellcheck source=_cra-sbom-extract.sh disable=SC1091 +# shellcheck disable=SC1091 # sourced helper, resolved at runtime . "$SCRIPT_DIR/_cra-sbom-extract.sh" KIT_DIR=$(dirname "$SCRIPT_DIR") # shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else +# shellcheck disable=SC2015 # fallback to unset on cd failure is intentional WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolfssl-component"} VERSION_FILE="$KIT_DIR/VERSION" diff --git a/cra-kit/scripts/generate-wolftpm-sbom.sh b/cra-kit/scripts/generate-wolftpm-sbom.sh index 265f16c5..9f85960f 100755 --- a/cra-kit/scripts/generate-wolftpm-sbom.sh +++ b/cra-kit/scripts/generate-wolftpm-sbom.sh @@ -46,10 +46,13 @@ set -eu SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) # shellcheck source=_cra-sbom-extract.sh disable=SC1091 +# shellcheck disable=SC1091 # sourced helper, resolved at runtime . "$SCRIPT_DIR/_cra-sbom-extract.sh" KIT_DIR=$(dirname "$SCRIPT_DIR") # shellcheck disable=SC2015 # `|| true` is a deliberate set -e guard, not if-then-else +# shellcheck disable=SC2015 # fallback to unset on cd failure is intentional WOLFTPM_DIR=${WOLFTPM_DIR:-$(cd "$KIT_DIR/../../wolftpm" 2>/dev/null && pwd || true)} +# shellcheck disable=SC2015 # fallback to unset on cd failure is intentional WOLFSSL_DIR=${WOLFSSL_DIR:-$(cd "$KIT_DIR/../../wolfssl" 2>/dev/null && pwd || true)} OUT_DIR=${CRA_SBOM_OUT_DIR:-"$KIT_DIR/auditor-packet/wolftpm-component"} From 9e6641b807e25850f454562317bff8cbbedd3b8d Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Tue, 14 Jul 2026 16:42:03 -0700 Subject: [PATCH 25/25] fix(cra-kit): review findings on sbom scripts --- cra-kit/scripts/_cra-sbom-extract.sh | 2 +- cra-kit/scripts/generate-wolfhsm-sbom.sh | 7 +++++-- cra-kit/scripts/generate-wolfmqtt-sbom.sh | 10 ++++++++++ cra-kit/user_settings.h | 1 + 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cra-kit/scripts/_cra-sbom-extract.sh b/cra-kit/scripts/_cra-sbom-extract.sh index 591f720f..a36262dd 100644 --- a/cra-kit/scripts/_cra-sbom-extract.sh +++ b/cra-kit/scripts/_cra-sbom-extract.sh @@ -240,7 +240,7 @@ PYEOF fi jq -r '.[].file' "$CRA_SBOM_BUILD_DIR/compile_commands.json" \ | grep -F "$_cra_product_dir/" \ - | grep -E '\.c$' \ + | grep -E '/(wolfcrypt/src|src)/[^/]+\.c$' \ | sort -u > "$_cra_out_file" || true if [ ! -s "$_cra_out_file" ]; then echo "ERROR: compile_commands.json in $CRA_SBOM_BUILD_DIR yielded no $_cra_product_name sources under $_cra_product_dir." >&2 diff --git a/cra-kit/scripts/generate-wolfhsm-sbom.sh b/cra-kit/scripts/generate-wolfhsm-sbom.sh index 325a323d..9c44ac05 100755 --- a/cra-kit/scripts/generate-wolfhsm-sbom.sh +++ b/cra-kit/scripts/generate-wolfhsm-sbom.sh @@ -62,8 +62,11 @@ if [ ! -f "$GEN" ]; then exit 1 fi -# Parse version from ChangeLog.md: first line matching "## wolfHSM Release vX.Y.Z" -VERSION=$(sed -n 's/^# wolfHSM Release v\([0-9][0-9.]*\).*/\1/p' \ +# Parse version from ChangeLog.md: first heading matching "wolfHSM Release vX.Y.Z". +# Current ChangeLog.md uses an H1 ("# wolfHSM Release v1.4.0"); tolerate one or +# two leading '#', surrounding whitespace, and an optional 'v' so a changelog +# style change does not silently break the parse. +VERSION=$(sed -n 's/^#\{1,2\}[[:space:]]*wolfHSM Release[[:space:]]*v\{0,1\}\([0-9][0-9.]*\).*/\1/p' \ "$WOLFHSM_DIR/ChangeLog.md" 2>/dev/null | head -1) if [ -z "$VERSION" ]; then echo "ERROR: could not parse version from $WOLFHSM_DIR/ChangeLog.md." >&2 diff --git a/cra-kit/scripts/generate-wolfmqtt-sbom.sh b/cra-kit/scripts/generate-wolfmqtt-sbom.sh index a99ed2f9..0343aeff 100755 --- a/cra-kit/scripts/generate-wolfmqtt-sbom.sh +++ b/cra-kit/scripts/generate-wolfmqtt-sbom.sh @@ -137,6 +137,16 @@ _run_autotools() { else make sbom WOLFSSL_DIR="$WOLFSSL_DIR" fi + # make sbom names artifacts after configure.ac's PACKAGE_VERSION; if + # that ever skews from wolfmqtt/version.h (our $VERSION), fail with an + # explanation instead of a cryptic cp "No such file" under set -eu. + if [ ! -f "wolfmqtt-${VERSION}.cdx.json" ]; then + echo "ERROR: make sbom did not produce wolfmqtt-${VERSION}.cdx.json." >&2 + echo " wolfmqtt/version.h says $VERSION but the autotools" >&2 + echo " PACKAGE_VERSION (which names make sbom outputs) differs:" >&2 + ls wolfmqtt-*.cdx.json >&2 2>/dev/null || true + exit 1 + fi cp -f "wolfmqtt-${VERSION}.cdx.json" "$CDX_OUT" cp -f "wolfmqtt-${VERSION}.spdx.json" "$SPDX_OUT" if [ -f "wolfmqtt-${VERSION}.spdx" ]; then diff --git a/cra-kit/user_settings.h b/cra-kit/user_settings.h index 94ebd570..3df3d779 100644 --- a/cra-kit/user_settings.h +++ b/cra-kit/user_settings.h @@ -1,4 +1,5 @@ /* Demo user_settings.h for CRA Kit embedded SBOM generation. + * Illustrative only, NOT a security-hardened production configuration. * Production: replace with your project's user_settings.h (or point gen-sbom at it). */ #ifndef CRA_KIT_USER_SETTINGS_H #define CRA_KIT_USER_SETTINGS_H