Merge 9e6641b807 into fff9e1eac8
commit
836f65a818
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -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."
|
||||
|
|
@ -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."
|
||||
|
|
@ -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."
|
||||
|
|
@ -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"
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue