pull/577/merge
Mark Atwood 2026-09-17 12:02:55 +02:00 committed by GitHub
commit 836f65a818
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 2879 additions and 26 deletions

View File

@ -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 |

View File

@ -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

View File

@ -0,0 +1,462 @@
# 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).
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
### 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.
### 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`
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`).
### 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
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 <board> -- -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
-DZEPHYR_EXTRA_MODULES=/path/to/wolfssl
```
`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 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
```
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).
### 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
ESP-IDF uses CMake and writes `compile_commands.json` to the `build/`
subdirectory automatically. Build your project normally:
```sh
idf.py build
```
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
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
```
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.
### 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`)
> **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 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\<version>\wolfSSL.pdsc
# Linux / macOS (Keil Studio / CMSIS-Toolbox)
~/.arm/Packs/wolfSSL/wolfSSL/<version>/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 .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
`<File><FilePath>` entries under `<Groups>`. 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 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)
```
Usage:
```sh
python3 extract-keil-srcs.py MyProject.uvprojx > wolfssl-srcs.txt
```
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 `<RTE>`. If present and `<component Cvendor="wolfSSL">` is
inside it, you are using the CMSIS Pack (Option A). If wolfssl `.c` files
appear under `<Groups>` 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 `<component Cvendor="wolfSSL">` is
present in the RTE block. For CMSIS Pack mode the installed `.pdsc` must be present at
`~/.arm/Packs/wolfSSL/wolfSSL/<version>/wolfSSL.pdsc`. Requires `python3`.
---
## IAR Embedded Workbench (`.ewp`)
> **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 stores source files as `<file><name>` 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
r"""
Extract .c source paths from an IAR EWARM .ewp project file.
Paths are emitted as absolute paths (resolves $PROJ_DIR$ automatically).
Pass --raw to keep the original $PROJ_DIR$ prefix instead.
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
# 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
```
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 `<excluded>` blocks (per-configuration
exclusions). If you need sources for a specific configuration only, check
`<excluded><configuration>` 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.
### 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
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
```

View File

@ -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 <File>/<FilePath> 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 '/(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
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"
}

View File

@ -0,0 +1,294 @@
#!/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=<target> (e.g. stm32h7, x86_64_efi)
# WOLFBOOT_SIGN=<scheme> (e.g. ECC256, RSA2048, ED25519)
#
# Optional variables:
# WOLFBOOT_HASH=<hash> (default: SHA256)
# WOLFBOOT_EXT_FLASH=<0|1> (default: 0)
# CRA_PYTHON=python3 (Python interpreter with gen-sbom deps)
# CRA_LICENSE_OVERRIDE=<SPDX-id> (e.g. LicenseRef-wolfBoot-Commercial)
# CRA_LICENSE_TEXT=<path> (required when CRA_LICENSE_OVERRIDE is a
# LicenseRef-* id: plain-text license
# embedded in the SBOM)
# CRA_SBOM_OUT_DIR=<path> (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=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)
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
# 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
echo "ERROR: wolfBoot source not found." >&2
echo " Set WOLFBOOT_DIR to your wolfBoot checkout." >&2
exit 1
fi
# 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 [ "$_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}
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 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
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"
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"
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
# 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:
# 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.
if [ "$_no_hash" = "0" ]; then
_srcs_tmp=$(mktemp "${TMPDIR:-/tmp}/wolfboot-sbom-srcs.XXXXXX")
_auto_tempfiles="${_auto_tempfiles:-} $_srcs_tmp"
_saved_makefile_dir="${CRA_SBOM_MAKEFILE_DIR:-}"
_saved_build_dir="${CRA_SBOM_BUILD_DIR:-}"
CRA_SBOM_MAKEFILE_DIR=""
CRA_SBOM_BUILD_DIR=""
_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
# 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")
_auto_tempfiles="${_auto_tempfiles:-} $_srcs_abs_tmp"
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)"
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.
#
# 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")
_auto_tempfiles="${_auto_tempfiles:-} $_defines_tmp"
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; }
_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" \
--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" \
"$@"
echo "SBOM written:"
echo " $CDX_OUT"
echo " $SPDX_OUT"

View File

@ -0,0 +1,231 @@
#!/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=<compiler> (default: cc; set for cross builds)
# CRA_PYTHON=python3 (interpreter with pcpp)
# CRA_LICENSE_OVERRIDE=<SPDX> (e.g. LicenseRef-wolfSSL-Commercial)
# CRA_LICENSE_TEXT=<path> (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=true emit SBOM without an artifact hash (NDA
# customers who cannot share source lists;
# 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
# appends to _cra_auto_tempfiles, so trap both.
_auto_tempfiles=""
_cra_auto_tempfiles=""
trap 'rm -f ${_auto_tempfiles:-} ${_cra_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
# 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:-}
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 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
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
}
echo "==> Embedded path: gen-sbom with CC -dM -E (no user_settings.h)"
# 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 env var onto it.
CRA_SBOM_BUILD_DIR="${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)"
_hash_arg="--srcs-file $_srcs_file"
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" \
${_hash_arg} \
--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=<target-compiler>"
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" \
${_hash_arg} \
--cdx-out "$CDX_OUT" \
--spdx-out "$SPDX_OUT" \
${_license_args}
fi
echo "Done."

View File

@ -0,0 +1,304 @@
#!/bin/sh
# Generate wolfMQTT component SBOM (autotools make sbom, or embedded gen-sbom).
#
# 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_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=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=<SPDX-id> (e.g. LicenseRef-wolfSSL-Commercial)
# CRA_LICENSE_TEXT=<path> (required when CRA_LICENSE_OVERRIDE is LicenseRef-*)
# CRA_SBOM_OUT_DIR=<path> (default: <kit>/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
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:-}
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
_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"
else
make sbom WOLFSSL_DIR="$WOLFSSL_DIR" \
SBOM_LICENSE_OVERRIDE="$CRA_LICENSE_OVERRIDE"
fi
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
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
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
}
# _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
# 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). 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
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
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
}
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
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."

View File

@ -0,0 +1,203 @@
#!/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=<path> (default: $KIT_DIR/auditor-packet/wolfsentry-component)
# CC=<compiler> (default: cc; for -dM -E options dump)
# CRA_WOLFSENTRY_IP_STACK=wolfip|lwip|none
# (default: none; selects which optional IP-stack glue
# 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)
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"}
# CRA_WOLFSENTRY_IP_STACK selects which optional IP stack glue is included.
# Values: wolfip, lwip, none (default: none).
# 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
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
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
_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"
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"
# 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
_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
# 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")
_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
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "ERROR: python3 not found in PATH." >&2
exit 1
fi
# 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 \
--version "$VERSION" \
--supplier "wolfSSL Inc." \
--license-file "$WOLFSENTRY_DIR/LICENSING" \
--options-h "$_defines_h" \
"$@" \
--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."

View File

@ -0,0 +1,370 @@
#!/bin/sh
# 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)
# 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=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=<path> (output directory; default auditor-packet)
# CRA_LICENSE_OVERRIDE=<SPDX-id> (e.g. LicenseRef-wolfSSH-Commercial)
# CRA_LICENSE_TEXT=<path> (required when CRA_LICENSE_OVERRIDE is a
# LicenseRef-* id: plain-text licence embedded
# 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=""
_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
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"}
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
# 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), 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.
# 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"
# 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
# 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
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
# 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"
# 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 -- --srcs-file "$_srcs" --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
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
# (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
})
}
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
# 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. SBOM outputs:"
echo " $CDX_OUT"
echo " $SPDX_OUT"

View File

@ -1,18 +1,53 @@
#!/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=<SPDX-id> (optional: e.g. LicenseRef-wolfSSL-Commercial)
# CRA_LICENSE_TEXT=<path> (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;
# 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)
# 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)
# CRA_SBOM_MAKEFILE_DIR=<path> (embedded: auto-extract srcs via make -n)
# CRA_SBOM_KEIL_PROJECT=<path> (embedded: auto-extract srcs from .uvprojx)
# CRA_SBOM_IAR_PROJECT=<path> (embedded: auto-extract srcs from .ewp)
#
# Optional variables:
# CRA_PYTHON=python3 (interpreter with pcpp; for embedded path)
# CRA_LICENSE_OVERRIDE=<SPDX-id> (e.g. LicenseRef-wolfSSL-Commercial)
# CRA_LICENSE_TEXT=<path> (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
# 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=""
_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
# 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"
@ -102,11 +137,39 @@ _embedded_srcs() {
done
}
_auto_extract_srcs() {
# 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
_auto=$(mktemp "${TMPDIR:-/tmp}/wolfssl-auto-srcs.XXXXXX") || {
echo "ERROR: mktemp failed for the auto-extract source list." >&2
exit 1
}
_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
;;
2)
# No extraction method selected; fall back to the built-in demo list.
;;
*)
# Library already printed an actionable error to stderr.
exit 1
;;
esac
}
_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 +186,88 @@ _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
# 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:
# 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 <<EOF
if [ "${CRA_SBOM_SRCS_ONLY_FROM_FILE:-}" = "true" ]; then
if [ -z "${CRA_SBOM_SRCS_FILE:-}" ]; then
echo "ERROR: CRA_SBOM_SRCS_ONLY_FROM_FILE=true requires CRA_SBOM_SRCS_FILE." >&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 <<EOF
$(_embedded_srcs)
EOF
fi
# Optional caller-supplied source list file (combined with or replacing the demo list).
_srcs_file_args=""
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
_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.
# 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"
@ -147,6 +278,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,7 +286,8 @@ EOF
--user-settings-include "$WOLFSSL_DIR" \
--user-settings-include "$KIT_DIR" \
--user-settings-define WOLFSSL_USER_SETTINGS \
--srcs "$@"
${_srcs_file_args} \
${_srcs_flag} "$@"
return 0
fi
@ -173,7 +306,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" \
@ -187,11 +320,64 @@ 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 "$@"
${_srcs_file_args} \
${_srcs_flag} "$@"
}
_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 -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"
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() {
@ -237,8 +423,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 +437,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 +450,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"

View File

@ -0,0 +1,502 @@
#!/bin/sh
# Generate wolfTPM component SBOMs (autotools make sbom, cmake sbom, or direct gen-sbom).
#
# Mode selection:
# 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)
# 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)
# 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_SBOM_KEIL_PROJECT=<path> (embedded mode: auto-extract srcs from a Keil .uvprojx)
# CRA_SBOM_IAR_PROJECT=<path> (embedded mode: auto-extract srcs from an IAR .ewp)
# CRA_SBOM_MAKEFILE_DIR=<path> (embedded mode: auto-extract srcs via `make -n`)
# CRA_SBOM_NO_HASH=true (embedded mode: emit SBOM without a real artifact
# 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/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=<SPDX-id> (e.g. LicenseRef-wolfTPM-Commercial)
# CRA_LICENSE_TEXT=<path> (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)
# 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"}
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
# Accumulators for temp files; cleaned up on exit. The shared extraction library
# appends to _cra_auto_tempfiles, so trap both.
_auto_tempfiles=""
_cra_auto_tempfiles=""
trap 'rm -f ${_auto_tempfiles:-} ${_cra_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
# 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
set -- "$@" --license-override "$CRA_LICENSE_OVERRIDE"
if [ -n "${CRA_LICENSE_TEXT:-}" ]; then
set -- "$@" --license-text "$CRA_LICENSE_TEXT"
fi
fi
set -- "$@" --srcs-file "$CRA_SBOM_SRCS_FILE"
"$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
# 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") || {
echo "ERROR: mktemp failed for the embedded source list." >&2
exit 1
}
_auto_tempfiles="${_auto_tempfiles:-} $_srcs"
# 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,
# 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_<plat>.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
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
# 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.
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-file "$_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
_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', 'cmake', or 'embedded', 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."

View File

@ -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