Skip to content

feat: SBOM generation and OmniBOR build provenance (CRA compliance)#10343

Open
MarkAtwood wants to merge 34 commits into
wolfSSL:masterfrom
MarkAtwood:feat/sbom-bomsh
Open

feat: SBOM generation and OmniBOR build provenance (CRA compliance)#10343
MarkAtwood wants to merge 34 commits into
wolfSSL:masterfrom
MarkAtwood:feat/sbom-bomsh

Conversation

@MarkAtwood
Copy link
Copy Markdown
Contributor

Summary

Adds two complementary supply chain transparency targets to the wolfSSL autotools build:

  • make sbom — generates a Software Bill of Materials (SPDX 2.3 + CycloneDX 1.6) for EU Cyber Resilience Act (CRA) compliance
  • make bomsh — generates an OmniBOR artifact dependency graph (cryptographic source-to-binary traceability) using the Bomsh project
  • When both are run, make bomsh enriches the SPDX document with a PERSISTENT-ID gitoid ExternalRef, bridging component identity and build provenance in a single file

make sbom

Produces three files:

File Format
wolfssl-<version>.cdx.json CycloneDX 1.6 JSON
wolfssl-<version>.spdx.json SPDX 2.3 JSON
wolfssl-<version>.spdx SPDX 2.3 tag-value (validated by pyspdxtools)

SBOM contents: name, version, supplier, license (parsed from LICENSING at generation time), copyright, SHA-256 of installed library, CPE, PURL, download location, build configuration defines, and optional third-party dependency versions (liboqs, libz, libxmss, liblms).

Implementation: scripts/gen-sbom (Python 3, stdlib only) stages a make install into a temp directory, hashes the installed library, generates both formats, removes staging. configure.ac detects python3, pyspdxtools, and git via AC_PATH_PROG. make sbom fails with a clear error if either required tool is missing.

make bomsh

Runs a full clean rebuild under bomtrace3 (patched strace — userspace only, no kernel modifications) to produce an OmniBOR artifact graph in omnibor/. If bomsh_sbom.py is available and wolfssl-<version>.spdx.json exists, annotates the SPDX with a PERSISTENT-ID gitoid ExternalRef.

configure.ac detects bomtrace3, bomsh_create_bom.py, and bomsh_sbom.py via AC_PATH_PROG. The raw logfile and conf file are written to the build directory (not /tmp/) to avoid concurrent-build collisions. All generated files removed by make clean.

Install targets

make install-sbom     # installs SBOM files to $(datadir)/doc/wolfssl/
make install-bomsh    # installs omnibor/ and enriched SPDX to $(datadir)/doc/wolfssl/

Documentation

  • doc/SBOM.md — unified reference covering both targets, combined workflow, all output files, implementation notes
  • doc/CRA.md — product-integrator guide: how to incorporate wolfSSL's SBOM artefacts into a downstream product SBOM (SPDX ExternalDocumentRef, CycloneDX component reference), commercial license guidance, OmniBOR gitoid meaning, auditor handoff checklist, links to OpenSSF CRA and SBOM Everywhere SIG guidance
  • INSTALL: sections 21 and 22
  • README.md: brief SBOM/CRA and OmniBOR/Bomsh sections
  • doc/include.am: SBOM.md and CRA.md added to dist_doc_DATA

Test plan

  • ./autogen.sh && ./configure && make && make sbom — verify three SBOM files produced and SPDX validates
  • make install-sbom / make uninstall-sbom — verify files install and remove cleanly
  • make clean — verify all generated files removed
  • With bomtrace3 and bomsh_create_bom.py in PATH: make sbom && make bomsh — verify omnibor/ produced and omnibor.wolfssl-<ver>.spdx.json contains PERSISTENT-ID gitoid ExternalRef
  • Without bomtrace3 in PATH: make bomsh — verify clear error message, non-zero exit
  • Without prior make sbom: make bomsh — verify OmniBOR graph produced, NOTE printed about missing SPDX, no failure

Copilot AI review requested due to automatic review settings April 28, 2026 21:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds build-integrated supply-chain transparency outputs to the Autotools build: SBOM generation (make sbom) and OmniBOR/Bomsh build provenance tracing (make bomsh) to support CRA-style compliance workflows.

Changes:

  • Add scripts/gen-sbom Python generator for CycloneDX 1.6 + SPDX 2.3 JSON SBOM outputs (plus tag-value conversion via pyspdxtools).
  • Wire new sbom, bomsh, and install/uninstall targets into Autotools (configure.ac, Makefile.am), with tool discovery via AC_PATH_PROG.
  • Add end-user documentation (doc/SBOM.md, doc/CRA.md) and reference it from README.md/INSTALL.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
scripts/gen-sbom New Python SBOM generator producing CycloneDX/SPDX JSON with optional dependency metadata.
Makefile.am Adds sbom/bomsh targets, install/uninstall rules, and clean integration.
configure.ac Detects required tools (python3, pyspdxtools, git, bomtrace3, bomsh scripts) and exports needed vars.
doc/include.am Ships new docs in the dist tarball; cleans generated OmniBOR directory.
doc/SBOM.md Primary documentation for SBOM + OmniBOR workflows and outputs.
doc/CRA.md Downstream integrator guidance for CRA-oriented SBOM/provenance usage.
README.md Adds brief pointers to the new SBOM/CRA and OmniBOR features.
INSTALL Adds new sections documenting make sbom / make bomsh usage and prerequisites.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread doc/SBOM.md Outdated
Comment thread Makefile.am Outdated
Comment thread Makefile.am Outdated
Comment thread Makefile.am Outdated
Comment thread Makefile.am Outdated
Comment thread Makefile.am Outdated
Comment thread Makefile.am Outdated
Comment thread scripts/gen-sbom
@Frauschi
Copy link
Copy Markdown
Contributor

Note that we are in the process of removing liblms and libxmss (see #10292), which will simplify the dependency analysis in the python script.

@sameehj sameehj force-pushed the feat/sbom-bomsh branch from 04008be to 2fff508 Compare May 1, 2026 18:05
@MarkAtwood
Copy link
Copy Markdown
Contributor Author

Three CI failures to address:

1. bomsh integration (linux) — upstream repo restructured
The install step checks for .devcontainer/bomtrace3/ in the omnibor/bomsh repo, which no longer exists. Hit our own error string: bomsh repo layout changed; please update CI. Check the current bomsh repo layout and fix the install step.

2. SBOM integration (linux)liboqs-dev not in apt
E: Unable to locate package liboqs-dev — that package isn't in Ubuntu 24.04's default repos. Either build liboqs from source in CI, use a PPA, or make that step conditional.

3. Windows Build Test (x64)tcp bind failed
Build succeeded (0 errors), runtime fails with wolfSSL error: tcp bind failed. Looks like port contention on the Windows runner. Please check if this also fails on master — if so it's pre-existing and not our bug, and we can ignore it.

PRB-master-job failure appears to be a reporting artifact (no test results found), not a real failure.

@Frauschi
Copy link
Copy Markdown
Contributor

Frauschi commented May 5, 2026

  1. Windows Build Test (x64)tcp bind failed

FYI, this will be resolved with #10387

@sameehj sameehj force-pushed the feat/sbom-bomsh branch 2 times, most recently from c6d9f94 to 39aefe7 Compare May 7, 2026 04:26
Copy link
Copy Markdown

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Choose a reason for hiding this comment

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

Fenrir Automated Review — PR #10343

No scan targets match the changed files in this PR. Review skipped.

@sameehj
Copy link
Copy Markdown
Contributor

sameehj commented May 8, 2026

retest this please

Copy link
Copy Markdown
Contributor Author

@MarkAtwood MarkAtwood left a comment

Choose a reason for hiding this comment

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

Pre-merge self-review

Ran a structured pre-merge review across scripts/gen-sbom, scripts/test_gen_sbom.py, .github/workflows/sbom.yml, Makefile.am, and the docs. Five commits result, each listing its closed findings in the trailer:

test(sbom): expand coverage; CI hard-requires pcpp
fix(sbom): correctness fixes in gen-sbom
build(sbom): Makefile.am hygiene
ci(sbom): pin deps, harden token, partial-clone bomsh
docs(sbom): correct NTIA/SPDX claims; fix broken anchor

Real defects fixed (fix(sbom))

  1. parse_options_h URL corruption. re.split(r'/\*|//', raw)[0] truncated PACKAGE_URL "https://..." at the first // inside the URL, emitting PACKAGE_URL = "https: in the SBOM wolfssl:build:* properties. Replaced with a string-literal-aware state machine.
  2. detect_license missed the canonical FSF preamble. or\s+(any\s+)?later failed on or (at your option) any later version (the FSF GPLv2 preamble used in millions of upstream COPYING files); silently mis-detected as GPL-X.0-only. Broadened with a sentence-bounded filler match.
  3. Empty --lib accepted. A misconfigured build pointing --lib at /dev/null or an empty .a silently emitted the well-known empty-file SHA-256 (e3b0c44…b855) as the wolfSSL component checksum; both SPDX and CDX validators accepted it. Hard-fail gate on size 0 with an actionable error; --srcs zero-byte placeholders get a soft warning (cross-compile setups legitimately use them).

Coverage gaps closed (test(sbom))

+42 tests covering detect_license (had zero tests), sha256_file's chunked-read path (the loop body wasn't exercised by the only existing empty-file vector), all four SBOM document generators (generate_cdx / generate_spdx / cdx_dep_component / spdx_dep_package), and the LicenseRef-without-license-text CLI rejection. Independent oracles throughout — SPDX licence list, hashlib one-shot SHA-256, CDX 1.6 / SPDX 2.3 schema field semantics.

TestParseUserSettings.setUp previously called self.skipTest on missing pcpp. The unit job didn't install pcpp, so 9 standalone-path tests silently skipped on every PR run. Now hard-fails with an actionable install hint, and the unit job installs pcpp==1.30.*.

Build hygiene (build(sbom))

Five Makefile.am improvements — none changes user-facing behaviour for any correct invocation; each closes a specific failure mode:

  • bomsh artifact glob quoted for spaces in abs_builddir
  • SOURCE_DATE_EPOCH only exported when git log -1 --format=%ct returned non-empty (silent shallow-clone case)
  • --license-override / --license-text only passed when set (decouples Makefile from gen-sbom's argparse default)
  • install-bomsh switched from a two-tar pipeline (which masked source-side errors due to POSIX sh's pipeline exit-status semantics) to cp -R src/. dst/
  • uninstall-hook depends on uninstall-sbom + uninstall-bomsh instead of duplicating their bodies

CI hardening (ci(sbom))

pcpp==1.30.* pinned, permissions: contents: read at workflow level (defence-in-depth — workflow does no API writes), git clone --filter=blob:none for the bomsh fetch (saves ~10s per run; pinned SHA makes blob filter safe).

Doc corrections (docs(sbom))

  • README.md previously said "Both produce SPDX 2.3 + CycloneDX 1.6 JSON validated against NTIA minimum elements". Neither path runs an NTIA-minimum-elements checker. Only make sbom runs pyspdxtools (SPDX-spec, not NTIA-specific). Corrected.
  • doc/CRA.md named ntia-conformance-checker as enforcing SPDX §10.1 hasExtractedLicensingInfos; it does not (that is pyspdxtools' job). Corrected with explicit text on what ntia-conformance-checker actually validates so a reader cannot be misled into running a malformed LicenseRef SBOM through the wrong tool and getting a false pass.
  • doc/SBOM.md anchor link #3-make-bomsh resolved to the wrong section; fixed to the GFM auto-anchor for §3.1.

Re the Copilot inline review

The Copilot review raised 8 concerns. 7 are addressed: the NOASSERTION CDX wording in SBOM.md §2.4, make bomshmake sbom rebuild ordering, hardcoded library paths replaced with the WOLFSSL_LIB_DSO_BASENAMES glob, scripts/gen-sbom added to EXTRA_DIST, trap-based staging cleanup, and removing the hardcoded licenseListVersion.

The remaining concern — install-bomsh using a non-INSTALL_DATA copy mechanism — is unchanged in intent (this round uses cp -R rather than the previous tar pipeline for clean error propagation but did not adopt find ... | xargs $(INSTALL_DATA)). The OmniBOR tree is a directory of content-addressed binary objects; using $(INSTALL_DATA) per-file is the more automakey approach but adds complexity for a directory whose layout is meant to be preserved verbatim. Open to revisiting if maintainers prefer.

Acknowledgement

@sameehj — the standalone --user-settings / --srcs entry point, SPDX 2.3 LicenseRef compliance, library-discovery glob, and CI workflow shape this PR from "works on my machine" into a production-ready, embedded-customer-friendly tool. The test infrastructure you built was solid. Thanks.

cc @Frauschi for visibility — make clean removes both the doxygen artefacts and BOMSH_OMNIBORDIR via doc/include.am's clean-local:.

@sameehj sameehj force-pushed the feat/sbom-bomsh branch 2 times, most recently from 525918d to cf0bdd9 Compare May 12, 2026 07:50
@sameehj
Copy link
Copy Markdown
Contributor

sameehj commented May 13, 2026

retest this please

@sameehj
Copy link
Copy Markdown
Contributor

sameehj commented May 14, 2026

retest this please

MarkAtwood and others added 9 commits June 4, 2026 12:21
Adds two complementary supply chain transparency targets to the
wolfSSL autotools build, and documentation covering both as a unified
whole.

Generates a Software Bill of Materials for EU Cyber Resilience Act
(CRA) compliance.  Produces three files in the build directory:

  wolfssl-<version>.cdx.json   CycloneDX 1.6 JSON
  wolfssl-<version>.spdx.json  SPDX 2.3 JSON
  wolfssl-<version>.spdx       SPDX 2.3 tag-value (validated)

The SPDX JSON is validated by pyspdxtools before the tag-value file
is written; make sbom fails if validation fails.

SBOM contents: package name/version, supplier, license (parsed from
LICENSING at generation time, not hardcoded), copyright, SHA-256 of
the installed library, CPE, PURL, download location, and build
configuration defines as a comment.  Third-party dependencies
(liboqs, libz, libxmss, liblms) are included when enabled.

Implementation: scripts/gen-sbom (Python 3, stdlib only) stages a
make install into a temporary directory, hashes the installed
library, generates both SBOM formats, then removes the staging
directory.  configure.ac detects python3, pyspdxtools, and git via
AC_PATH_PROG.

install-sbom / uninstall-sbom targets install the three files to
$(datadir)/doc/wolfssl/.  make clean removes all generated files.

Generates an OmniBOR artifact dependency graph using the Bomsh
project (https://github.com/omnibor/bomsh), providing cryptographic
traceability from every built binary back to the exact set of source
files that produced it.

Runs a full clean rebuild under bomtrace3 (a patched strace, userspace
only — no kernel modifications required).  bomtrace3 intercepts every
compiler execve() syscall and records inputs and outputs; it cannot
post-process an already-built tree, hence the clean rebuild.
bomsh_create_bom.py processes the raw logfile to produce the OmniBOR
artifact objects and metadata in omnibor/.

If bomsh_sbom.py is available and wolfssl-<version>.spdx.json exists
(from make sbom), annotates that SPDX document with a PERSISTENT-ID
gitoid ExternalRef, producing omnibor.wolfssl-<version>.spdx.json.
This enriched SPDX bridges component identity and build provenance in
a single document.

configure.ac detects bomtrace3, bomsh_create_bom.py, and bomsh_sbom.py
via AC_PATH_PROG.  The raw logfile and conf file are written to the
build directory (not /tmp/) to avoid concurrent-build collisions, and
removed by make clean.

install-bomsh / uninstall-bomsh targets install omnibor/ and the
enriched SPDX to $(datadir)/doc/wolfssl/.

doc/SBOM.md: unified reference covering both make sbom and make bomsh
as parts of a single supply chain transparency story — component
identity (what) and build provenance (how) — with a combined workflow
section and full output file reference.

doc/CRA.md: product-integrator guide covering how to incorporate
wolfSSL's SBOM artefacts into a downstream product SBOM (SPDX
ExternalDocumentRef and CycloneDX component reference patterns),
commercial license concluded-field guidance, OmniBOR gitoid meaning,
auditor handoff checklist, and links to OpenSSF CRA and SBOM Everywhere
SIG guidance pages.

INSTALL: sections 21 (make sbom) and 22 (make bomsh).
README.md: brief SBOM/CRA and OmniBOR/Bomsh sections.
doc/include.am: SBOM.md and CRA.md added to dist_doc_DATA.
Glob-match libwolfssl.* across platforms, honour SOURCE_DATE_EPOCH,
stage installs with trap cleanup, and parse options.h for build flags.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Emit hasExtractedLicensingInfos for LicenseRef-* IDs and add
--license-text / SBOM_LICENSE_TEXT to embed the actual licence body.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Cover gen-sbom helpers and add a workflow that validates SPDX/CDX,
NTIA conformance, reproducibility, and the licence-override matrix.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Hard-error on LicenseRef-* without --license-text, route NOASSERTION
via license.name, strip C comments in options.h, NUL-join derived
UUIDs, and detect git worktrees for SOURCE_DATE_EPOCH.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Add tests for the review-driven gen-sbom fixes, tighten file-handle
hygiene in the linux integration job, and add a macOS smoke job for
.dylib detection.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Hoist the shared dynamic-library basenames into a Make variable used
by both `sbom:` and `bomsh:`; add a sha256_file negative test and
freshness checks for the build_timestamp fallback.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Refresh stale Makefile.am comment about SBOM_LICENSE_TEXT, clarify
build_extracted_licensing_infos docstring, and replace a hardcoded
wolfssl-5.9.1.spdx.json check with the wildcard glob used elsewhere.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Drop dead --dep-libxmss/liblms args after PRs wolfSSL#10292/wolfSSL#10293 removed those autoconf vars.
sameehj and others added 25 commits June 4, 2026 12:21
name=falcon+purl=liboqs is unresolvable for OSV/Grype/Trivy; switch to liboqs (HAVE_FALCON stays as build property).
Both live DEP_META entries (libz, liboqs) are pkg-config; the git-describe path was dead.
Adds liboqs+bomsh CI jobs, locks DEP_META shape via 5 new tests, ships test_gen_sbom.py in dist.
Adds --user-settings (pcpp), --srcs (Merkle/OmniBOR), --dep-version,
noise filter with NO_/USE_ carve-out, and pcpp #error fail-fast so
embedded customers can produce a CRA SBOM without autoconf or .a.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Noble lacks liboqs-dev (build 0.12.0 from source); upstream removed
.devcontainer/bomtrace3 (mirror Dockerfile, pin bomsh+strace, mpers).

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
bomtrace3 has no --version (use `-h | grep` smoke check); liboqs default
OQS_USE_OPENSSL=ON drags <openssl/crypto.h> into wolfSSL TUs via falcon.h
and collides with the compat layer (-DOQS_USE_OPENSSL=OFF). Add
upload-artifact@v4 to all four jobs so the OmniBOR graph + enriched SPDX
ship from PR runs.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
…text)

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Closes seven test-coverage gaps surfaced by the wolfssl-1zj
review pass:

  * TestParseUserSettings.setUp now hard-fails on missing
    pcpp instead of silently skipping nine standalone-path
    tests.  CI's unit job installs pcpp before running the
    suite, so the embedded entry point is verified at the
    cheapest gate (1zj.14).

  * TestDetectLicense (10 tests + 2 FSF preamble cases added
    by the gen-sbom commit): pins the SPDX licence ID emitted
    in licenseConcluded / licenseDeclared against the four
    real-world LICENSING shapes plus error paths.  detect_license
    had zero coverage; a regex regression silently flipped the
    SBOM licence tag (1zj.15).

  * TestCliMutualExclusion: covers the --license-override
    LicenseRef-* / --license-text gate at the CLI rejection
    layer (subprocess-based, real argparse).  The placeholder
    fallback in build_extracted_licensing_infos is unreachable
    from main(); this test pins that gate (1zj.17).

  * TestSha256File.test_chunked_read_path_matches_one_shot
    exercises the iter(f.read(65536), b'') loop body (the
    pre-existing empty-file vector skipped it entirely).
    Independent oracle: hashlib's one-shot SHA-256 (1zj.19).

  * TestCdxDepComponent / TestSpdxDepPackage / TestGenerateCdx /
    TestGenerateSpdx (21 tests): cover the four SBOM document
    generators that produce the JSON consumed by vulnerability
    scanners and CRA auditors.  bom-ref derivation
    determinism, dependency-graph integrity, listed-id licence
    block shaping, NOASSERTION fallback, hasExtractedLicensingInfos
    plumbing, hash_kind annotation on the source-merkle path,
    DESCRIBES / DEPENDS_ON SPDX relationships, etc. (1zj.20).

  * test_pcpp_error_directive_is_fatal previously pinned the
    literal substrings 'pcpp' and 'return_code' in the error
    message.  The contract being tested is fail-fast on #error,
    not the wording.  Replace with a length-floor check that
    still catches an empty-message regression (1zj.21).

Closes wolfssl-1zj.14, wolfssl-1zj.15, wolfssl-1zj.17,
wolfssl-1zj.19, wolfssl-1zj.20, wolfssl-1zj.21
Three review-driven correctness fixes in scripts/gen-sbom,
each shipping with its own regression tests:

  * parse_options_h corrupted #define values that contained
    a string literal with '/' characters: 're.split(r"/\\*|//",
    raw, maxsplit=1)[0]' truncated PACKAGE_URL="https://..."
    at the first // inside the URL, so the SBOM emitted
    'PACKAGE_URL = "https:'.  Replace the regex with a
    string-literal-aware state machine (_strip_define_comment)
    so quoted '/' characters survive.  Char literals are not
    handled (autoconf does not emit them, pcpp normalises
    user_settings.h before this code sees the value).
    Adds four regression tests to TestParseOptionsH (1zj.22).

  * --lib accepted /dev/null and emitted the well-known empty-
    file SHA-256 (e3b0c44...b855) as the wolfSSL component
    checksum.  Both SPDX and CDX validators accepted the
    result, so a misconfigured build silently shipped an SBOM
    whose hash matched no compiled wolfSSL artefact.  Hard-fail
    on size==0 --lib, with an actionable error.  Soft-warn on
    zero-byte --srcs entries because cross-compile setups
    legitimately include touch'd placeholders.  Adds
    test_empty_lib_is_rejected and
    test_zero_byte_srcs_warn_but_do_not_fail (1zj.23).

  * detect_license regex 'or\\s+(any\\s+)?later' failed to
    match the canonical FSF GPL preamble phrase 'or (at your
    option) any later version' used verbatim in millions of
    upstream COPYING files: the parenthetical interjected
    between 'or' and 'any later'.  Any LICENSING that copied
    the FSF preamble was silently mis-detected as GPL-X.0-only.
    Broaden to 'or\\s+(?:[^,.;\\n]*?\\s+)?(?:any\\s+)?later'
    -- the negative class keeps the match within a sentence so
    unrelated 'or' / 'later' tokens further down the file do
    not promote a GPLv2-only declaration.  Adds GPLv2 + GPLv3
    canonical-preamble regression guards.  The shipping wolfSSL
    LICENSING still maps to GPL-3.0-only; locked down by
    test_real_wolfssl_licensing_is_gpl3_only (1zj.24).

Also rebinds the test_zero_byte_srcs_warn_but_do_not_fail
tempfile paths before the try-block so the finally clause
references live filenames on the assertion-failure path
instead of leaking renamed tempfiles in /tmp (e8o.2).

Closes wolfssl-1zj.22, wolfssl-1zj.23, wolfssl-1zj.24,
wolfssl-e8o.2
Five small Makefile.am improvements from the wolfssl-1zj
review pass.  None changes user-facing behaviour for any
correct invocation; each closes a specific failure mode that
either silently corrupts an SBOM or leaks state.

  * sbom: lib glob: $(addprefix "$(abs_builddir)/...")
    quotes the prefix so an abs_builddir containing a space
    no longer word-splits the test -f loop.  Matches the
    sbom: target's existing pattern (1zj.7).

  * SOURCE_DATE_EPOCH is only exported when 'git log -1
    --format=%ct' returned a non-empty string.  Previously the
    fallback echo on a shallow clone or corrupt repo silently
    exported SOURCE_DATE_EPOCH='', which gen-sbom then treated
    as 'no SDE' (warning + wallclock fallback) defeating the
    reproducibility intent (1zj.9).

  * --license-override and --license-text are now passed to
    gen-sbom only when their corresponding Make var is set
    ($(if VAR,...)).  The previous unconditional passthrough
    relied on gen-sbom's argparse default '' to treat empty
    as unset; a future gen-sbom change distinguishing those
    cases would have silently mis-emitted SBOMs (1zj.10).

  * install-bomsh replaces the two-tar pipeline with
    cp -R src/. dst/.  POSIX sh masks pipeline source-side
    errors; a permission failure on 'tar cf - .' was followed
    by 'tar xf -' on an empty stream succeeding, leaving a
    silent partial install.  cp -R is a single command whose
    exit code propagates cleanly (1zj.11).

  * uninstall-hook now depends on uninstall-sbom +
    uninstall-bomsh instead of duplicating their bodies.  Both
    targets are .PHONY so the dep edge resolves; the cleanup
    paths stay in lockstep with install-sbom / install-bomsh
    automatically (1zj.12).

(BOMSH_OMNIBORDIR cleanup at make-clean time is handled by
the existing clean-local target in doc/include.am, which the
original PR commit extended.  An earlier review attempted to
add a duplicate clean-local here; that silently overrode the
doc cleanups it relied on.  The duplicate is not present in
this squashed commit.)

Closes wolfssl-1zj.6, wolfssl-1zj.7, wolfssl-1zj.9,
wolfssl-1zj.10, wolfssl-1zj.11, wolfssl-1zj.12,
wolfssl-e8o.1
Three supply-chain hardenings on .github/workflows/sbom.yml:

  * Pin pcpp to ==1.30.* in both jobs that install it
    (unit + standalone), matching the existing pin convention
    used in the same file for spdx-tools, ntia-conformance-
    checker, and cyclonedx-bom.  A pcpp release that changes
    macro-expansion or noise semantics will no longer silently
    shift SBOM build_props on master (1zj.1).

  * Add 'permissions: contents: read' at workflow level.  The
    workflow does no API writes, so the practical risk of the
    default read-write GITHUB_TOKEN on push events is zero
    today; the explicit pin is GitHub's documented hardening
    recommendation for SBOM-producing supply-chain workflows
    so a future step that accidentally introduces a write call
    fails loudly rather than silently mutating the repo (1zj.2).

  * Switch the bomsh clone to git clone --filter=blob:none.
    Full history was being downloaded before 'git checkout
    $BOMSH_SHA'; --depth=1 cannot be combined with a raw SHA,
    so --filter=blob:none is the closest 'shallow' substitute.
    Saves ~10s of CI time per bomsh job run (1zj.3).

Closes wolfssl-1zj.1, wolfssl-1zj.2, wolfssl-1zj.3
Three documentation corrections from the wolfssl-1zj review:

  * README.md previously said 'Both produce SPDX 2.3 + CycloneDX
    1.6 JSON validated against NTIA minimum elements'.  Neither
    entry point runs an NTIA-minimum-elements checker.  The
    autotools 'make sbom' path runs pyspdxtools (SPDX-spec
    validation, not NTIA-specific) and gates the build on it;
    the standalone embedded path runs no validation by default.
    Replace with text saying the SBOMs are intended to satisfy
    NTIA minimum elements, that only the autotools path runs
    SPDX validation, and that neither path runs an NTIA checker
    by default.  Matters because downstream integrators quoting
    this README to auditors must not be misled into claiming
    NTIA validation has been performed (1zj.13).

  * doc/CRA.md previously named ntia-conformance-checker
    alongside pyspdxtools as a tool that 'will reject the SBOM
    otherwise' when a LicenseRef has no hasExtractedLicensingInfos
    block.  ntia-conformance-checker validates the NTIA Minimum
    Elements (supplier, component name, version, unique ID,
    dependency relationships, author, timestamp) -- it does NOT
    enforce SPDX 2.3 \xc2\xa710.1 extracted-text completeness, which is
    a SPDX schema concern enforced by pyspdxtools.  A reader who
    relied on the claim would get a false pass on a malformed
    LicenseRef SBOM.  Drop the misleading mention; add a
    sentence stating what ntia-conformance-checker actually
    checks (1zj.16).

  * doc/SBOM.md anchor link 'See [Prerequisites for make bomsh]
    (#3-make-bomsh)' resolved to the top of section 3 instead
    of the \xc2\xa73.1 prerequisites subsection.  Fix to the GFM
    auto-anchor for '### 3.1 Prerequisites for make bomsh',
    which is wolfSSL#31-prerequisites-for-make-bomsh (1zj.18).

Closes wolfssl-1zj.13, wolfssl-1zj.16, wolfssl-1zj.18
doc/SBOM.md and doc/CRA.md previously had no references to
wolfSSL's vulnerability disclosure process.  CRA Article 10
obliges manufacturers to handle and disclose vulnerabilities,
so a CRA-compliance doc that does not point integrators at the
disclosure path leaves the auditor with an incomplete handoff.

PR wolfSSL#10284 (already on master) published the project's formal
security policy at SECURITY-POLICY.md and a mandatory reporting
template at SECURITY-REPORT-TEMPLATE.md.  The policy explicitly
accommodates ecosystem-coordination embargoes for 'downstream
integrators, certification bodies, or equivalent' -- the exact
audience these CRA-compliance docs are written for.

doc/CRA.md: extends 'What to Give Your Auditor' with a
paragraph on the disclosure process, advisory feed, and the
embargo accommodation; lists SECURITY-POLICY.md and
SECURITY-REPORT-TEMPLATE.md under 'Further Reading'.

doc/SBOM.md: adds a closing paragraph to '5. Using wolfSSL's
artefacts in a product' pointing readers at SECURITY-POLICY.md
when a vulnerability is found in wolfSSL or any SBOM-listed
dependency.

This is congruent with the project's formal security policy
landed by wolfSSL#10284.
…nhosted wolfssl.com URLs

The previous output asserted `https://wolfssl.com/sbom/...` URLs the
project does not serve.  Default `documentNamespace` to a deterministic
`urn:uuid` (SPDX 2.3 §6.5 requires uniqueness, not resolvability) and
add `--document-namespace` / `SBOM_DOCUMENT_NAMESPACE` with RFC 3986
validation; the uuid5 seed is unchanged so derived UUIDs stay byte-stable.
…riched SPDX

CI previously asserted only that a gitoid externalRef exists.  Add
`scripts/bomsh_verify.py` (with 8 synthetic-fixture unit tests) verifying
every gitoid resolves, blobs round-trip their sha1, and the wolfSSL
gitoid matches the built `libwolfssl.*`; pyspdxtools schema-validates the
enriched SPDX and `make bomsh` records the traced artefact in `_bomsh.artefact`.
The bomsh job previously bundled the provenance proof (OmniBOR ADG +
enriched SPDX, valuable long-term) with raw bomtrace3 trace and config
(useful only for triaging the producing run) in one 90-day artefact.
Split into `bomsh-omnibor-<sha>` (90 days) and `bomsh-trace-diag-<sha>`
(14 days) so each class of output gets a sensible retention lifecycle.
`make sbom`'s libtool relink rewrites `src/.libs/lib*.so*` after bomsh
has already gitoid-ed it, breaking check (C) on the previous push.
Capture the gitoid in `make bomsh` BEFORE `make sbom`, persist
`<path>\t<gitoid>` to `_bomsh.artefact`, and have check (C) compare SPDX
vs the saved gitoid (NOTE if on-disk has diverged).  Plus two codespell
typo fixes (`unparseable` → `unparsable`).

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
bomsh_sbom.py hashes -f at call time, so without the pre-install
snapshot it hashes the post-relink bytes and the SPDX externalRef
gitoid stops matching the manifest, failing verifier check (C).

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
-f hashes the file then maps it through bomsh_omnibor_doc_mapping to
the bom_id (a different sha1 than the artefact's gitoid), which never
matches the verifier's manifest.  -g inserts our gitoid verbatim.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Check (C) compared two sha1s that are different by construction:
the ArtifactID we wrote to `_bomsh.artefact` (sha1 of library
bytes) vs. the bom_id `bomsh_sbom.py -f` inserts into the SPDX
(sha1 of the OmniBOR Input Manifest, looked up via
omnibor/metadata/bomsh/bomsh_omnibor_doc_mapping).  Two prior
attempts (be88063 snapshot, efa5977 `-g <ArtifactID>`) each
fixed (C) at the cost of breaking (A): bomsh's
omnibor/objects/ only stores Input Manifests keyed by bom_ids.

Customers walk the ADG from the SPDX gitoid; they need only
(A) "resolves in objects/" and (B) "objects/ self-consistent",
both retained.  (C) was CI-internal hygiene already covered by
the explicit WOLFSSL_LIB_DSO_BASENAMES loop in the recipe.

Drop the manifest, check (C), and its plumbing across
Makefile.am, bomsh_verify.py, test_gen_sbom.py, and sbom.yml.
Restore `bomsh_sbom.py -f <library>` (the bom_id it inserts
resolves in objects/ by construction).

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
- PURL: pkg:generic -> pkg:github/wolfSSL/wolfssl@v<ver> (and
  pkg:generic/zlib -> pkg:github/madler/zlib).  Resolves directly
  in OSV/GHSA/Snyk/Trivy without per-vendor CPE-fallback mapping.
- Always emit wolfssl:sbom:hash-kind, on both autotools and embedded
  paths, so the SHA-256 in checksums[] is no longer ambiguous about
  whether it represents a library binary or a source-set Merkle hash.
- Move structured producer metadata (hash-kind, source-set) out of
  positional key=value slugs in the SPDX package comment field into
  SPDX 2.3 annotations[].
- CycloneDX file sub-component for the linked library on the --lib
  path (SHA-1 + SHA-256), naming the artefact whose hash is reported.
- New externalReferences: GitHub Security Advisories (SPDX + CDX) and
  CDX-side website / issue-tracker / RFC 9116 security.txt.
- GEN_SBOM_TOOL_NAME / GEN_SBOM_VERSION module constants, bumped to
  1.1, single-sourcing the producer identity in CDX and SPDX.
- 8 new unit tests, 4 reshaped for the new shape; 136/136 pass.
- doc/CRA.md customer-facing PURL example follows the new shape.
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.
- .github/workflows/sbom.yml: standalone (no-autotools) job now runs
  ntia-conformance-checker and cyclonedx-bom alongside pyspdxtools,
  matching the integration job.  An NTIA or CDX-1.6 schema regression
  in --user-settings / --srcs handling -- the entry point most
  embedded customers actually invoke -- now fails CI rather than
  needing manual review.
- Update integration-job assertions for the new pkg:github PURL shape
  and pin the GitHub Security Advisories externalRef so a regression
  that drops it fails CI.
- scripts/include.am: add bomsh_verify.py to EXTRA_DIST so a release
  tarball ships the verifier; without this, downstream consumers
  cannot re-verify a `make bomsh` provenance bundle from a tarball.
- .gitignore: /wolfssl-*.{cdx.json,spdx.json,spdx},
  /omnibor.wolfssl-*.spdx.json, /omnibor/, /_sbom_staging/,
  /_bomsh.conf, /bomsh_raw_logfile* -- generated outputs that were
  untracked but not ignored.
Generate CSAF 2.0 advisories and CycloneDX 1.6 VEX from wolfSSL CVE
Program records plus a human-authored VEX overlay (state/justification,
fixed versions, separate FIPS product, reachability hedge). Stdlib-only,
SOURCE_DATE_EPOCH-reproducible, mirroring scripts/gen-sbom.

`make advisory` is a thin wrapper around the same script and is
interchangeable with it: both default to the canonical advisories/ tree
(advisories/records/*.json + advisories/vex-overlay.json) and, with no
record arguments, emit one CSAF + one CycloneDX document per CVE into
advisories/out/ (a build artefact, gitignored). Explicit
--cve-record/--csaf-out/--cdx-vex-out still drive single or bundled
per-release documents.

CSAF 2.0 scores[] carry only CVSS v2/v3 (the schema predates v4); a
v4-only finding is preserved as a note plus aggregate_severity, with the
machine-readable v4 rating in the CycloneDX output. CWE names are
resolved from the official catalogue (cwe-names.json) for CSAF 6.1.11.
The overlay format is documented by advisory-vex-overlay.example.json and
constrained by advisory-vex-overlay.schema.json.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Add scripts/test_gen_advisory.py (record->model logic, CSAF semantic
invariants, reproducibility, batch/default-tree resolution, fail-loud
behaviour) and a CSAF 2.0 conformance gate (scripts/csaf_validate.mjs)
running the strict schema plus all mandatory tests via
@secvisogram/csaf-validator-lib.

advisory.yml runs unit, schema (CycloneDX 1.6 strict + overlay JSON
Schema + reproducibility) and CSAF mandatory-test jobs. Both the schema
and CSAF jobs also exercise the zero-argument default/batch path against
the canonical advisories/ tree, proving `make advisory` and the script
are interchangeable. CVE-2026-5999 is a synthetic CVSS v3.1 fixture that
exercises the CSAF scores[] path the v4-only records do not.

Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
@sameehj sameehj force-pushed the feat/sbom-bomsh branch from 9d49a5d to 8af748b Compare June 5, 2026 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants