Skip to content

Commit 3c23492

Browse files
committed
sbom,bomsh: hash the bomsh-traced binary, fix doc/SBOM.md
make bomsh previously left the SHA-256 in checksums[] (post-libtool- relink, hashed by `make sbom`'s private staging install) and the OmniBOR gitoid in externalRefs (pre-relink, traced by bomtrace3) describing two different files inside the same SPDX document. Add SBOM_LIB_OVERRIDE to Makefile.am: the bomsh recipe now discovers the traced library under src/.libs/ and passes it to the nested `make sbom` invocation, so checksums[] hashes the same artefact that bomsh_sbom.py enriches with OmniBOR ExternalRefs. doc/SBOM.md: - §3.5 rewritten to match bomsh_verify.py reality (gates A and B only). The previously documented gate (C) "Artefact correspondence -- gitoid == git-blob hash of libwolfssl.{so,dylib,a}" was never implementable: bomsh attaches the OmniBOR Input Manifest bom_id, not the binary's content gitoid, so that check would always fail. New "Identity of the SHA-256" subsection explains the SBOM_LIB_OVERRIDE binding instead. - §3.2 step ordering corrected (clean before bomtrace3, override plumbing in step 5). - Intro NTIA claim qualified to point at .github/workflows/sbom.yml for the actual validator set. - §2.4 "Third-party deps: none" qualified to mention --with-libz / --with-liboqs and the host C runtime. - §2.4 PURL row updated to the new pkg:github shape.
1 parent 79a8a82 commit 3c23492

2 files changed

Lines changed: 107 additions & 47 deletions

File tree

Makefile.am

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,17 @@ WOLFSSL_LIB_DSO_BASENAMES = \
395395
# their own URL should set this to a URI they
396396
# actually serve (e.g.
397397
# https://example.com/sbom/wolfssl-X.Y.Z.spdx.json).
398+
# SBOM_LIB_OVERRIDE Absolute path to the library artefact whose
399+
# SHA-256 should land in the SBOM, INSTEAD of
400+
# discovering one via a private staging install.
401+
# Set by `make bomsh` so the SBOM hash and the
402+
# OmniBOR enrichment refer to the SAME bomsh-
403+
# traced binary; without this override `make
404+
# sbom` would re-link via `make install` and
405+
# hash a different artefact than `bomsh_sbom.py`
406+
# fingerprints, leaving the SHA-256 in
407+
# `checksums[]` and the gitoid in `externalRefs`
408+
# describing two unrelated files.
398409
sbom:
399410
@if test -z "$(PYTHON3)"; then \
400411
echo ""; \
@@ -412,24 +423,34 @@ sbom:
412423
@rm -rf $(abs_builddir)/_sbom_staging
413424
@set -e; \
414425
trap 'rm -rf $(abs_builddir)/_sbom_staging' EXIT INT TERM HUP; \
415-
$(MAKE) install DESTDIR=$(abs_builddir)/_sbom_staging; \
416-
sbom_lib=""; \
417-
for lib in \
418-
$(addprefix "$(abs_builddir)/_sbom_staging$(libdir)"/,$(WOLFSSL_LIB_DSO_BASENAMES)) \
419-
"$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.dll \
420-
"$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.dll.a \
421-
"$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.lib \
422-
"$(abs_builddir)/_sbom_staging$(libdir)"/wolfssl.lib \
423-
"$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.a; do \
424-
if test -f "$$lib"; then sbom_lib="$$lib"; break; fi; \
425-
done; \
426-
if test -z "$$sbom_lib"; then \
427-
echo ""; \
428-
echo "ERROR: No installed wolfSSL library artifact found for SBOM."; \
429-
echo " Searched in $(abs_builddir)/_sbom_staging$(libdir)"; \
430-
echo " (configure with --enable-shared or --enable-static)"; \
431-
echo ""; \
432-
exit 1; \
426+
if test -n "$(SBOM_LIB_OVERRIDE)"; then \
427+
if test ! -f "$(SBOM_LIB_OVERRIDE)"; then \
428+
echo ""; \
429+
echo "ERROR: SBOM_LIB_OVERRIDE=$(SBOM_LIB_OVERRIDE) does not exist."; \
430+
echo ""; \
431+
exit 1; \
432+
fi; \
433+
sbom_lib="$(SBOM_LIB_OVERRIDE)"; \
434+
else \
435+
$(MAKE) install DESTDIR=$(abs_builddir)/_sbom_staging; \
436+
sbom_lib=""; \
437+
for lib in \
438+
$(addprefix "$(abs_builddir)/_sbom_staging$(libdir)"/,$(WOLFSSL_LIB_DSO_BASENAMES)) \
439+
"$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.dll \
440+
"$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.dll.a \
441+
"$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.lib \
442+
"$(abs_builddir)/_sbom_staging$(libdir)"/wolfssl.lib \
443+
"$(abs_builddir)/_sbom_staging$(libdir)"/libwolfssl.a; do \
444+
if test -f "$$lib"; then sbom_lib="$$lib"; break; fi; \
445+
done; \
446+
if test -z "$$sbom_lib"; then \
447+
echo ""; \
448+
echo "ERROR: No installed wolfSSL library artifact found for SBOM."; \
449+
echo " Searched in $(abs_builddir)/_sbom_staging$(libdir)"; \
450+
echo " (configure with --enable-shared or --enable-static)"; \
451+
echo ""; \
452+
exit 1; \
453+
fi; \
433454
fi; \
434455
echo "SBOM: hashing $$sbom_lib"; \
435456
if test -z "$${SOURCE_DATE_EPOCH:-}" && test -n "$(GIT)" && \
@@ -484,6 +505,17 @@ bomshdir = $(datadir)/doc/$(PACKAGE)
484505
# also what makes the combined workflow correct: `make sbom` writes the SPDX,
485506
# but `make bomsh` issues `make clean` (which removes it via CLEANFILES), so
486507
# the only reliable way to enrich is to regenerate after the traced build.
508+
#
509+
# After the traced rebuild we discover the bomsh-traced library in
510+
# $(abs_builddir)/src/.libs/ and pass it to the nested `make sbom` call as
511+
# SBOM_LIB_OVERRIDE. Without the override `make sbom` would `make install
512+
# DESTDIR=...` into a private tree, which triggers a libtool relink and
513+
# produces a binary whose SHA-256 differs from the one bomtrace3 traced.
514+
# That left the gitoid in `externalRefs` (which IS for the traced binary)
515+
# and the SHA-256 in `checksums[]` (which was NOT) describing two different
516+
# files in the same SPDX document. With the override they describe the
517+
# same artefact, which is the invariant any auditor reading the document
518+
# expects.
487519
bomsh:
488520
@if test -z "$(BOMTRACE3)"; then \
489521
echo ""; \
@@ -503,13 +535,7 @@ bomsh:
503535
@printf 'raw_logfile=%s\n' '$(BOMSH_RAWLOG_BASE)' > '$(BOMSH_CONF)'
504536
$(BOMTRACE3) -c '$(BOMSH_CONF)' $(MAKE)
505537
$(BOMSH_CREATE_BOM) -r '$(BOMSH_RAWLOG)' -b '$(BOMSH_OMNIBORDIR)'
506-
$(MAKE) sbom
507-
@if test -z "$(BOMSH_SBOM)"; then \
508-
echo "NOTE: bomsh_sbom.py not in PATH; skipping SPDX enrichment."; \
509-
echo " The OmniBOR graph in $(BOMSH_OMNIBORDIR) is still produced."; \
510-
exit 0; \
511-
fi; \
512-
bomsh_artifact=""; \
538+
@bomsh_artifact=""; \
513539
for lib in \
514540
$(addprefix "$(abs_builddir)/src/.libs"/,$(WOLFSSL_LIB_DSO_BASENAMES)) \
515541
"$(abs_builddir)/src/.libs/libwolfssl.a" \
@@ -518,7 +544,16 @@ bomsh:
518544
done; \
519545
if test -z "$$bomsh_artifact"; then \
520546
echo "NOTE: no built libwolfssl artifact found in $(abs_builddir)/src/.libs/"; \
521-
echo " OmniBOR graph produced; SPDX enrichment skipped."; \
547+
echo " OmniBOR graph produced; SBOM regeneration + SPDX"; \
548+
echo " enrichment skipped."; \
549+
exit 0; \
550+
fi; \
551+
echo "bomsh: traced binary -> $$bomsh_artifact"; \
552+
$(MAKE) sbom SBOM_LIB_OVERRIDE="$$bomsh_artifact"; \
553+
if test -z "$(BOMSH_SBOM)"; then \
554+
echo "NOTE: bomsh_sbom.py not in PATH; skipping SPDX enrichment."; \
555+
echo " The OmniBOR graph in $(BOMSH_OMNIBORDIR) is still produced."; \
556+
echo " The base SBOM in $(SBOM_SPDX) already hashes the bomsh-traced binary."; \
522557
exit 0; \
523558
fi; \
524559
echo "Enriching SPDX with OmniBOR ExternalRefs (artifact: $$bomsh_artifact)..."; \

doc/SBOM.md

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ covered:
2121
| `python3 scripts/gen-sbom …` (standalone) | Embedded / RTOS customers building with their own Makefile, Keil, IAR, STM32CubeIDE, ESP-IDF, Zephyr, plain CMake, etc. | Any |
2222
| `make sbom` (autotools wrapper) | Linux server / Debian / RPM / Yocto / FIPS-Ready customers running `./configure && make` | Autotools |
2323

24-
Both call the same Python core and produce SBOMs that pass the same SPDX
25-
2.3 / CycloneDX 1.6 / NTIA validators. Pick whichever matches your build
26-
flow.
24+
Both call the same Python core and produce SBOMs that pass SPDX 2.3
25+
(`pyspdxtools`) and CycloneDX 1.6 (`cyclonedx-bom` strict JSON validator)
26+
schema validation. The autotools `make sbom` integration job
27+
additionally runs `ntia-conformance-checker` against NTIA Minimum
28+
Elements 2021; see `.github/workflows/sbom.yml` for the exact set of
29+
validators run on every PR. Pick whichever matches your build flow.
2730

2831
---
2932

@@ -415,9 +418,9 @@ Both formats contain the same information:
415418
| Copyright | `Copyright (C) 2006-<year> wolfSSL Inc.` |
416419
| SHA-256 | hash of the installed `libwolfssl.so.X.Y.Z` |
417420
| CPE | `cpe:2.3:a:wolfssl:wolfssl:<version>:*:*:*:*:*:*:*` |
418-
| PURL | `pkg:generic/wolfssl@<version>` |
421+
| PURL | `pkg:github/wolfSSL/wolfssl@v<version>` (resolves directly in OSV / GHSA / Snyk / Trivy without per-vendor mapping) |
419422
| Download location | `https://github.com/wolfSSL/wolfssl` |
420-
| Third-party deps | none (wolfssl has no runtime dependencies in a default build) |
423+
| Third-party deps | none in a default build; `--with-libz` adds zlib and `--with-liboqs` adds liboqs (recorded as `DEPENDS_ON` packages with their own purl/CPE/supplier). All builds depend transitively on the host C runtime; this is not enumerated as an SBOM component since it is system-supplied and varies per runtime target. |
421424

422425
#### License detection
423426

@@ -578,19 +581,25 @@ Place `bomsh_create_bom.py` (and optionally `bomsh_sbom.py`) from the bomsh
578581

579582
### 3.2 What make bomsh does
580583

581-
1. Writes a build-local `_bomsh.conf` redirecting the raw logfile out of
584+
1. Runs `make clean` to ensure a full rebuild. This is necessary because
585+
`bomtrace3` intercepts syscalls live during compilation and cannot
586+
post-process an already-built tree. This step also removes any prior
587+
`wolfssl-<version>.{cdx,spdx}.json` from a stand-alone `make sbom`,
588+
which is intentional: the document `make bomsh` enriches must come
589+
from the *traced* rebuild, not from a stale pre-trace one.
590+
2. Writes a build-local `_bomsh.conf` redirecting the raw logfile out of
582591
`/tmp/` to the build directory (avoids collisions between concurrent
583592
builds).
584-
2. Runs `make clean` to ensure a full rebuild. This is necessary because
585-
`bomtrace3` intercepts syscalls live during compilation and cannot
586-
post-process an already-built tree.
587593
3. Runs `bomtrace3 -c _bomsh.conf make` — rebuilds wolfSSL under strace
588594
tracing, recording every compiler invocation with its inputs and outputs.
589595
4. Runs `bomsh_create_bom.py` to process the raw logfile and produce the
590596
OmniBOR artifact graph in `omnibor/`.
591-
5. If `bomsh_sbom.py` is available **and** `wolfssl-<version>.spdx.json`
592-
exists (from `make sbom`), annotates that SPDX document with OmniBOR
593-
`ExternalRef` identifiers, producing `omnibor.wolfssl-<version>.spdx.json`.
597+
5. Discovers the bomsh-traced library under `src/.libs/` and runs
598+
`make sbom SBOM_LIB_OVERRIDE=<traced-library>` so the regenerated
599+
SPDX hashes the same binary that bomsh traced (see § 3.5).
600+
6. If `bomsh_sbom.py` is available, annotates the regenerated SPDX
601+
document with OmniBOR `ExternalRef` identifiers, producing
602+
`omnibor.wolfssl-<version>.spdx.json`.
594603

595604
### 3.3 Output files
596605

@@ -632,27 +641,43 @@ The raw logfile (`bomsh_raw_logfile.sha1`) and conf file (`_bomsh.conf`)
632641
are written to the build directory and removed by `make clean`. The
633642
`omnibor/` tree is also removed by `make clean`.
634643

644+
#### Identity of the SHA-256 in the enriched SPDX
645+
646+
`make bomsh` discovers the bomsh-traced library under
647+
`$(abs_builddir)/src/.libs/` and passes it to the nested `make sbom`
648+
invocation as `SBOM_LIB_OVERRIDE`, so the SHA-256 in the SPDX
649+
`checksums[]` is the SHA-256 of the **exact binary that `bomtrace3`
650+
traced**. Without that override `make sbom` would re-link via `make
651+
install DESTDIR=...` and hash a libtool-relinked artefact whose
652+
SHA-256 differs from the traced library, leaving the SHA-256 in
653+
`checksums[]` and the OmniBOR `externalRefs` describing two different
654+
files in the same SPDX document.
655+
635656
#### CI verifiability gates
636657

637-
The bomsh CI job enforces three independent self-consistency properties
658+
The bomsh CI job enforces two independent self-consistency properties
638659
on every PR, in addition to schema validation of the enriched SPDX
639660
through `pyspdxtools`:
640661

641662
1. **Resolvability** — every `gitoid` listed in the SPDX `externalRefs`
642663
resolves to a blob present at `omnibor/objects/<aa>/<rest>`.
643664
2. **Object-store integrity** — every blob in `omnibor/objects/`
644-
round-trips through `sha1(b"blob <len>\0" + content)`, so a corrupt or
645-
truncated object store is caught at PR time, not by a downstream
665+
round-trips through `sha1(b"blob <len>\0" + content)`, so a corrupt
666+
or truncated object store is caught at PR time, not by a downstream
646667
verifier weeks later.
647-
3. **Artefact correspondence** — the `gitoid` recorded against the
648-
`wolfssl` SPDX package equals the git-blob hash of the actual
649-
`libwolfssl.{so,dylib,a}` that `make bomsh` traced. This is what
650-
makes the SBOM a true attestation of the binary that would ship,
651-
rather than a plausible-looking but fictional reference.
652668

653-
If any of these fail, the PR fails — the bomsh provenance bundle that a
669+
If either fails, the PR fails — the bomsh provenance bundle that a
654670
CRA reviewer would download is never published with a broken bridge.
655671

672+
A third gate is **not** implemented: the gitoid that `bomsh_sbom.py`
673+
attaches to the SPDX is the bom_id of the OmniBOR Input Manifest for
674+
the traced artefact, not the git-blob hash of the binary itself. The
675+
two are different by design (the bom_id summarises the build inputs,
676+
not the linked output bytes), so a "gitoid == sha1 of the binary"
677+
check would always fail. What ties the SBOM to the binary today is
678+
the SHA-256 in `checksums[]`, which the `SBOM_LIB_OVERRIDE` plumbing
679+
described above guarantees is the SHA-256 of the bomsh-traced library.
680+
656681
The verifier itself lives at `scripts/bomsh_verify.py` (importable, with
657682
synthetic-fixture unit tests in `scripts/test_gen_sbom.py`). Run it
658683
against any local `make bomsh` output with:

0 commit comments

Comments
 (0)