Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -770,3 +770,127 @@ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/wolftpm/
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/wolftpm/
DESTINATION include/wolftpm
FILES_MATCHING PATTERN "*.h")


####################################################
# SBOM generation target
####################################################
#
# Usage:
# cmake -B build -DWOLFSSL_DIR=/path/to/wolfssl/source .
# cmake --build build
# cmake --build build --target sbom
#
# WOLFSSL_DIR must point to a wolfssl source tree that contains
# scripts/gen-sbom (available on the feat/sbom-embedded branch).
#
# Outputs written to the build directory:
# wolftpm-<version>.cdx.json (CycloneDX)
# wolftpm-<version>.spdx.json (SPDX JSON)
# wolftpm-<version>.spdx (SPDX tag-value, pyspdxtools validation output)

if(BUILD_WOLFTPM_LIB)
set(WOLFSSL_DIR "" CACHE PATH
"Path to wolfssl source tree with scripts/gen-sbom")

# Derive the version from wolftpm/version.h, NOT from PROJECT_VERSION.
# autotools `make sbom` uses PACKAGE_VERSION, which is sourced from
# version.h. Reading the same header here keeps the cmake and autotools
# SBOMs bit-identical in the version field even if the project() call in
# this file drifts out of sync with the header.
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/wolftpm/version.h"
_sbom_version_line
REGEX "^#define[ \t]+LIBWOLFTPM_VERSION_STRING[ \t]+\"")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 [Low] CMake version.h regex uses [ \t] which does not match a tab in CMake's regex dialect · Logic

The SBOM version is parsed with REGEX "^#define[ \t]+LIBWOLFTPM_VERSION_STRING[ \t]+\"". CMake's regex engine does not interpret \t as a tab escape; inside the bracket expression [ \t] is the literal set {space, backslash, 't'}. It matches today only because the distributed wolftpm/version.h uses space separators. If version.h were ever regenerated with tab separators, file(STRINGS ... REGEX) would return no match, _sbom_version_line would be empty, and the target would abort via the FATAL_ERROR at 807-811. Currently benign — defense-in-depth only.

Fix: Since version.h is generated with single spaces, match [ ]+ explicitly, or embed a literal tab in the character class if tab tolerance is actually desired.

string(REGEX REPLACE
"^#define[ \t]+LIBWOLFTPM_VERSION_STRING[ \t]+\"([^\"]+)\".*" "\\1"
SBOM_VERSION "${_sbom_version_line}")
if(NOT SBOM_VERSION)
message(FATAL_ERROR
"sbom: could not parse LIBWOLFTPM_VERSION_STRING from "
"wolftpm/version.h")
endif()

# Validate the SBOM prerequisites at configure time so the user learns
# what is missing immediately, instead of after a full library build.
#
# These checks must NOT abort configuration of a normal build: someone who
# just wants `cmake -B build && cmake --build build` should never be forced
# to set WOLFSSL_DIR. So when a prerequisite is missing we still define a
# `sbom` target, but one that prints the reason and fails at build time.
# Only when the user explicitly opts in via -DWOLFSSL_DIR=... and that path
# turns out to be wrong do we hard-error at configure time, because at that
# point the user clearly intends to build SBOMs and a typo'd path is a
# mistake worth surfacing right away.
find_program(PYTHON3_CMD python3)
find_program(PYSPDXTOOLS_CMD pyspdxtools)

set(_sbom_error "")
if(WOLFSSL_DIR STREQUAL "")
set(_sbom_error
"WOLFSSL_DIR is not set. Re-run cmake with -DWOLFSSL_DIR=/path/to/wolfssl")
elseif(NOT EXISTS "${WOLFSSL_DIR}/scripts/gen-sbom")
# User opted in with a bad path -> fail configure now, not at build.
message(FATAL_ERROR
"sbom: ${WOLFSSL_DIR}/scripts/gen-sbom not found.\n"
" Check that WOLFSSL_DIR points to a wolfSSL tree with "
"SBOM support.")
elseif(NOT PYTHON3_CMD)
set(_sbom_error "'python3' not found in PATH. Cannot generate SBOM.")
elseif(NOT PYSPDXTOOLS_CMD)
set(_sbom_error
"'pyspdxtools' not found in PATH (install: pip install spdx-tools)")
endif()

if(NOT _sbom_error STREQUAL "")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 [Low] CMake return() used at directory scope to skip the SBOM block; fragile if code is appended · Logic

When an SBOM prerequisite (python3/pyspdxtools/WOLFSSL_DIR) is missing, the code defines a failing fallback sbom target and then calls return(). This block is guarded by if(BUILD_WOLFTPM_LIB) at top-level directory scope (not inside a function), so return() ends processing of the remainder of CMakeLists.txt, not just the SBOM if block. It is harmless today only because the SBOM section is the last block in the file (ends at the endif() on line 896); any install(), target, or export logic added after it would be silently skipped in the common no-SBOM build. Severity views: review=Low/NIT, review-security=Info; strictest kept.

Fix: Avoid return() at directory scope for control flow. Select the fallback vs. real target with if()/else() (or a nested-if scoped to the SBOM section), or move the SBOM logic into a function/included module so the early exit cannot skip future directives.

# Prerequisite missing: keep configuration working, but make the sbom
# target fail loudly if someone actually invokes it.
add_custom_target(sbom
VERBATIM
COMMAND ${CMAKE_COMMAND} -E echo "sbom: ${_sbom_error}"
COMMAND ${CMAKE_COMMAND} -E false
COMMENT "SBOM prerequisites missing")
return()
endif()

set(SBOM_CDX "${CMAKE_BINARY_DIR}/wolftpm-${SBOM_VERSION}.cdx.json")
set(SBOM_SPDX "${CMAKE_BINARY_DIR}/wolftpm-${SBOM_VERSION}.spdx.json")
set(SBOM_SPDX_TV "${CMAKE_BINARY_DIR}/wolftpm-${SBOM_VERSION}.spdx")
set(SBOM_STAGING "${CMAKE_BINARY_DIR}/_sbom_staging")

# Staged install path. install(TARGETS wolftpm) above hardcodes

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔴 [High] CMake sbom target hard-codes Unix shared-library layout; fails on Windows, static, and multi-config builds · Logic

The CMake sbom target computes SBOM_LIB = ${SBOM_STAGING}/lib/libwolftpm${CMAKE_SHARED_LIBRARY_SUFFIX} and passes it to gen-sbom after cmake --install, hard-coding both the lib/ staging subdir and a Unix shared-object name rather than the real target artifact. This diverges from the actual install layout in several supported configurations: (1) With -DBUILD_SHARED_LIBS=OFF (option defaults ON at line 73) the installed artifact is libwolftpm.a under ARCHIVE DESTINATION lib, so --lib points at a .so/.dylib that was never produced. (2) On Windows shared builds, install(TARGETS) uses RUNTIME DESTINATION bin, DLLs go under bin/ and typically drop the lib prefix (wolftpm.dll), so the path never matches. (3) For multi-config generators (Visual Studio, Xcode) the cmake --install omits --config $\<CONFIG>, so building the sbom target can install/hash the wrong or unbuilt configuration. In each case gen-sbom fails with a bare file-not-found. Severity views: review=High/BLOCK, bugs=Medium, review-security=Low; strictest kept.

Fix: Derive the artifact from CMake target metadata via generator expressions ($\<TARGET_FILE_NAME:wolftpm> plus the platform-specific staged destination — bin/ on Windows, lib/ elsewhere), add --config $\<CONFIG> to the install for multi-config generators, and either support static builds explicitly or reject BUILD_SHARED_LIBS=OFF for the sbom target with a clear FATAL_ERROR.

# `LIBRARY DESTINATION lib`, so the .so always lands in lib/, never lib64/.
# ${CMAKE_SHARED_LIBRARY_SUFFIX} resolves to .so on Linux / .dylib on mac.
set(SBOM_LIB
"${SBOM_STAGING}/lib/libwolftpm${CMAKE_SHARED_LIBRARY_SUFFIX}")

# ${OPTION_FILE} is the generated wolftpm/options.h, the same compile-time
# option fingerprint autotools `make sbom` feeds to gen-sbom via
# --options-h. Using it (rather than a raw `cc -dM` dump, which would only
# capture compiler builtins) keeps the cmake SBOM consistent with autotools.
add_custom_target(sbom
VERBATIM
# Stage a clean install so gen-sbom inspects the same artifact layout
# the user would actually ship.
COMMAND ${CMAKE_COMMAND} -E rm -rf ${SBOM_STAGING}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟠 [Medium] CMake sbom target uses cmake -E rm, unavailable on the declared minimum CMake 3.16 · API Contract Violations

The sbom custom target runs ${CMAKE_COMMAND} -E rm -rf ${SBOM_STAGING} twice (lines 874 and 891). The cmake -E rm subcommand was introduced in CMake 3.17, but this file declares cmake_minimum_required(VERSION 3.16) (line 22). On a host with exactly CMake 3.16 — a version the project claims to support — cmake --build build --target sbom fails with cmake -E rm: unknown command before any SBOM is produced. The main build is unaffected; only the new opt-in target breaks.

Fix: Use the 3.16-compatible ${CMAKE_COMMAND} -E remove_directory ${SBOM_STAGING} (handles a missing directory gracefully and matches rm -rf intent), or raise the minimum CMake version for this target path.

COMMAND ${CMAKE_COMMAND} --install ${CMAKE_BINARY_DIR}
--prefix ${SBOM_STAGING}
COMMAND ${PYTHON3_CMD} ${WOLFSSL_DIR}/scripts/gen-sbom
--name wolftpm
--version ${SBOM_VERSION}
--supplier "wolfSSL Inc."
--license-file ${CMAKE_SOURCE_DIR}/LICENSE
--options-h ${OPTION_FILE}
--lib ${SBOM_LIB}
--cdx-out ${SBOM_CDX}
--spdx-out ${SBOM_SPDX}
# Validate the SPDX JSON and emit the tag-value rendering as a side
# effect; a malformed SBOM makes pyspdxtools exit non-zero and fails
# the target.
COMMAND ${PYSPDXTOOLS_CMD} --infile ${SBOM_SPDX}
--outfile ${SBOM_SPDX_TV}
COMMAND ${CMAKE_COMMAND} -E rm -rf ${SBOM_STAGING}
COMMENT "Generating SBOM for wolfTPM ${SBOM_VERSION}")

# gen-sbom reads the staged library, so the library must build first.
add_dependencies(sbom wolftpm)
endif()
67 changes: 67 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,70 @@ cppcheck:
--suppress=invalidPrintfArgType_sint \
--error-exitcode=89 --std=c89 \
-I wolftpm src/ hal/ examples

# SBOM generation (CRA compliance)
SBOM_CDX = wolftpm-$(PACKAGE_VERSION).cdx.json
SBOM_SPDX = wolftpm-$(PACKAGE_VERSION).spdx.json
SBOM_SPDX_TV = wolftpm-$(PACKAGE_VERSION).spdx
sbomdir = $(datadir)/doc/$(PACKAGE)

.PHONY: sbom install-sbom uninstall-sbom

sbom:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟠 [Medium] New SBOM targets have no automated coverage · test

The PR adds non-trivial autotools and CMake SBOM generation paths, but includes no automated smoke coverage for either target; the PR body lists only manual test steps. A lightweight test would have caught the artifact-path and platform assumptions in the new target logic (see the autotools libtool-filename and CMake shared-library-layout findings).

Fix: Add CI or a build-system smoke test that provides a minimal fake WOLFSSL_DIR/scripts/gen-sbom, runs make sbom and cmake --build --target sbom, and asserts the expected output files are produced or that prerequisite errors are clear.

@if test -z "$(PYTHON3)"; then \
echo ""; \
echo "ERROR: 'python3' not found in PATH. Cannot generate SBOM."; \
echo ""; \
exit 1; \
fi
@if test -z "$(PYSPDXTOOLS)"; then \
echo ""; \
echo "ERROR: 'pyspdxtools' not found in PATH. Cannot validate SBOM."; \
echo " Install: pip install spdx-tools"; \
echo ""; \
exit 1; \
fi
@if test -z "$(WOLFSSL_DIR)"; then \
echo ""; \
echo "ERROR: WOLFSSL_DIR is not set. Cannot locate gen-sbom."; \
echo " Set WOLFSSL_DIR to your wolfSSL source tree, e.g.:"; \
echo " make sbom WOLFSSL_DIR=/path/to/wolfssl"; \
echo ""; \
exit 1; \
fi
@if test ! -f "$(WOLFSSL_DIR)/scripts/gen-sbom"; then \
echo ""; \
echo "ERROR: $(WOLFSSL_DIR)/scripts/gen-sbom not found."; \
echo " Check that WOLFSSL_DIR points to a wolfSSL tree with SBOM support."; \
echo ""; \
exit 1; \
fi
rm -rf $(abs_builddir)/_sbom_staging
$(MAKE) install DESTDIR=$(abs_builddir)/_sbom_staging
$(PYTHON3) $(WOLFSSL_DIR)/scripts/gen-sbom \
--name wolftpm \
--version $(PACKAGE_VERSION) \
--supplier "wolfSSL Inc." \
--license-file $(srcdir)/LICENSE \
--options-h $(abs_builddir)/wolftpm/options.h \
--lib $(abs_builddir)/_sbom_staging$(libdir)/libwolftpm.so.@WOLFTPM_LIBRARY_VERSION_FIRST@.@WOLFTPM_LIBRARY_VERSION_SECOND@.@WOLFTPM_LIBRARY_VERSION_THIRD@ \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔴 [High] Autotools sbom target reconstructs the libtool library filename incorrectly; breaks on next version-info bump and on… · Logic

The sbom target points gen-sbom --lib at libwolftpm.so.@WOLFTPM_LIBRARY_VERSION_FIRST@.@WOLFTPM_LIBRARY_VERSION_SECOND@.@WOLFTPM_LIBRARY_VERSION_THIRD@, i.e. libwolftpm.so.\<current>.\<revision>.\<age>, reconstructed from libtool's current:revision:age version-info fields (currently 17:0:0). libtool actually installs the real ELF file as libNAME.so.(current-age).age.revision: the first component should be current-age (not current) and the trailing two are age.revision, swapped relative to this recipe's revision.age. configure.ac's own comments confirm the installed soname is current-age, making the mapping internally inconsistent. It works today only because all three components collapse to 17.0.0 for 17:0:0; the next release with a nonzero revision/age (e.g. 17:1:0) makes libtool install libwolftpm.so.17.0.1 while the recipe looks for libwolftpm.so.17.1.0, so --lib points at a nonexistent file and make sbom fails. The hardcoded .so.X.Y.Z and $(libdir) are also non-portable: macOS installs libwolftpm.\<n>.dylib, static-only builds produce libwolftpm.a, and MinGW installs the DLL runtime under $(bindir) — none of which this path can locate. Severity views across modes: review=High/BLOCK, bugs=Medium, review-security=Low; strictest kept.

Fix: Do not reconstruct the versioned name from version-info components. After the staged install, discover the actual installed artifact under the staged $(libdir)/$(bindir) (glob libwolftpm.so.*, libwolftpm*.dylib, *wolftpm*.dll, or dereference the unversioned libwolftpm.so dev symlink) and fail clearly if none is found. If the versioned name must be kept, compute the first component as current-age and order the trailing components as age.revision.

$(if $(SBOM_LICENSE_OVERRIDE),--license-override $(SBOM_LICENSE_OVERRIDE)) \
$(if $(SBOM_LICENSE_TEXT),--license-text $(SBOM_LICENSE_TEXT)) \
--cdx-out $(abs_builddir)/$(SBOM_CDX) \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔵 [Low] install-sbom mixes absolute and relative artifact paths; only works when run from the build dir · convention

The sbom recipe writes outputs to $(abs_builddir)/$(SBOM_CDX) (absolute) at line 184, while install-sbom installs the bare $(SBOM_CDX), $(SBOM_SPDX), and $(SBOM_SPDX_TV) relative to the current directory at line 192. These only line up when make install-sbom runs from the build directory; VPATH/out-of-tree distcheck-style builds or invoking from a subdir would look in the wrong place.

Fix: Use $(abs_builddir)/$(SBOM_*) consistently in install-sbom (and CLEANFILES) to match where the sbom target actually writes the artifacts.

--spdx-out $(abs_builddir)/$(SBOM_SPDX)
Comment on lines +175 to +185
Comment on lines +179 to +185
rm -rf $(abs_builddir)/_sbom_staging
$(PYSPDXTOOLS) --infile $(abs_builddir)/$(SBOM_SPDX) \
--outfile $(abs_builddir)/$(SBOM_SPDX_TV)

install-sbom: sbom
$(MKDIR_P) $(DESTDIR)$(sbomdir)
$(INSTALL_DATA) $(SBOM_CDX) $(DESTDIR)$(sbomdir)/
$(INSTALL_DATA) $(SBOM_SPDX) $(DESTDIR)$(sbomdir)/
$(INSTALL_DATA) $(SBOM_SPDX_TV) $(DESTDIR)$(sbomdir)/

uninstall-sbom:
-rm -f $(DESTDIR)$(sbomdir)/$(SBOM_CDX)
-rm -f $(DESTDIR)$(sbomdir)/$(SBOM_SPDX)
-rm -f $(DESTDIR)$(sbomdir)/$(SBOM_SPDX_TV)

CLEANFILES += $(SBOM_CDX) $(SBOM_SPDX) $(SBOM_SPDX_TV)
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1122,6 +1122,34 @@ See `./examples/endorsement/get_ek_certs`.
* Inner wrap support for SensitiveToPrivate.
* Add support for IRQ (interrupt line)

## SBOM / EU CRA Compliance

wolfTPM generates a Software Bill of Materials (SBOM) in CycloneDX 1.6 and
SPDX 2.3 formats to support compliance with the EU Cyber Resilience Act (CRA).

```sh
make sbom WOLFSSL_DIR=/path/to/wolfssl
```

Requires `python3` and `pyspdxtools` (`pip install spdx-tools`). `WOLFSSL_DIR`
must point to a wolfssl source tree containing `scripts/gen-sbom` (branch
`feat/sbom-embedded`, or `master` once wolfSSL/wolfssl#10343 merges).

Output files in the build directory:

| File | Format |
|------|--------|
| `wolftpm-<version>.cdx.json` | CycloneDX 1.6 |
| `wolftpm-<version>.spdx.json` | SPDX 2.3 JSON |
| `wolftpm-<version>.spdx` | SPDX 2.3 tag-value |

```sh
make install-sbom # installs to $(datadir)/doc/wolftpm/
make uninstall-sbom
```

For further CRA guidance see [wolfssl/doc/CRA.md](https://github.com/wolfSSL/wolfssl/blob/master/doc/CRA.md).

## Support

Email us at [support@wolfssl.com](mailto:support@wolfssl.com).
25 changes: 14 additions & 11 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,16 @@ AC_ARG_PROGRAM

AC_CONFIG_HEADERS([src/config.h])

WOLFTPM_LIBRARY_VERSION=17:0:0
# | | |
# +------+ | +---+
# | | |
# current:revision:age
# | | |
# | | +- increment if source code has changed
# | | set to zero if [current] or [revision] is incremented
# | +- increment if interfaces have been added
# | set to zero if [current] is incremented
# +- increment if interfaces have been removed or changed
WOLFTPM_LIBRARY_VERSION_FIRST=17
# ^--- current (installed so name: current-age)
WOLFTPM_LIBRARY_VERSION_SECOND=0
# ^-- revision
WOLFTPM_LIBRARY_VERSION_THIRD=0
# ^--- age
WOLFTPM_LIBRARY_VERSION=${WOLFTPM_LIBRARY_VERSION_FIRST}:${WOLFTPM_LIBRARY_VERSION_SECOND}:${WOLFTPM_LIBRARY_VERSION_THIRD}
AC_SUBST([WOLFTPM_LIBRARY_VERSION_FIRST])
AC_SUBST([WOLFTPM_LIBRARY_VERSION_SECOND])
AC_SUBST([WOLFTPM_LIBRARY_VERSION_THIRD])
AC_SUBST([WOLFTPM_LIBRARY_VERSION])


Expand Down Expand Up @@ -991,6 +990,10 @@ AC_SUBST([AM_CFLAGS])
AC_SUBST([AM_LDFLAGS])
AC_SUBST([CPPCHECK])

# SBOM generation
AC_PATH_PROG([PYTHON3], [python3])
AC_PATH_PROG([PYSPDXTOOLS], [pyspdxtools])

# FINAL
AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([wolftpm/version.h])
Expand Down
Loading