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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ system it ships) while all of them converge on the same engine.

| Frontend | Build system | What it extracts |
|---|---|---|
| `sbom.am` | Autotools | Stages a private `make install`, discovers the installed library/binary, hashes it; config from `AM_CPPFLAGS`/`config.h`. |
| `sbom.am` | Autotools | Stages a private `make install`, discovers the installed library/binary, hashes it; config from `AM_CPPFLAGS`/`config.h`, or `SBOM_USER_SETTINGS`. |
| `build/sbom.cmake` | CMake | Same shape, called as `wolfssl_compliance_tooling_add_sbom()` with `NAME`/`TARGETS`/`DEFS`. Also adds `install-sbom` and `uninstall-sbom`. |
| `build/sbom.mk` | Plain Make | Product sets `SBOM_NAME`/`SBOM_SRCS`/`SBOM_CFLAGS` (plus `SBOM_SETTINGS_H` when a `user_settings.h` derives the config), includes the fragment. |
| `frontends/compdb_sbom.py` | Any `compile_commands.json` | The universal fallback — TI CCS, MPLAB X, Renesas e2studio, or anything wrappable with `bear -- make ...`. |
Expand Down
10 changes: 9 additions & 1 deletion share/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,17 @@ Config (choose one):
- `--options-h PATH` — a pre-expanded flat `#define` header, used verbatim.
- `--user-settings PATH` — path to `wolfssl/wolfcrypt/settings.h`. Also pass
`--user-settings-include DIR` (the directory that contains `user_settings.h`)
and `--user-settings-define WOLFSSL_USER_SETTINGS`.
and `--user-settings-define WOLFSSL_USER_SETTINGS`. The autotools fragment
takes `SBOM_USER_SETTINGS`, `SBOM_USER_SETTINGS_INCLUDES` and
`SBOM_USER_SETTINGS_DEFINES`.
- `--source-only` — no build-config macros (for example a Kconfig-driven build).

A product that shares one `user_settings.h` with wolfSSL captures wolfSSL's
macros along with its own. Pass `--macro-prefix PREFIX` (repeatable; Make and
autotools `SBOM_MACRO_PREFIX`, CMake `MACRO_PREFIX`) to record only the macros
whose name starts with that prefix, for example `WOLFCERT_`. `--crypto-only auto`
still reads the full capture.

Dependency (for linkers and bindings): `--dep-wolfssl`, `--dep-openssl`, and
`--dep-version` are passed through only when the generator supports them.

Expand Down
7 changes: 6 additions & 1 deletion share/build/sbom.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
# them must contain user_settings.h.
# USER_SETTINGS_DEFINE <NAME[=VALUE]>... Predefines for that preprocess.
# Include WOLFSSL_USER_SETTINGS.
# MACRO_PREFIX <prefix>... Record only the captured macros whose name
# starts with one of these prefixes, e.g. WOLFCERT_.
# TV_OUT <path> SPDX tag-value file, when a later step writes one.
# INSTALL_DIR <dir> Doc directory for install-sbom. Default:
# CMAKE_INSTALL_FULL_DOCDIR, else
Expand Down Expand Up @@ -111,7 +113,7 @@ function(wolfssl_compliance_tooling_add_sbom)
LICENSE_OVERRIDE LICENSE_TEXT
INSTALL_DIR INSTALL_DEPENDS INSTALL_TARGET UNINSTALL_TARGET)
set(_multi TARGETS DEFS DEP_VERSION INCLUDE_DIRS
USER_SETTINGS_INCLUDE USER_SETTINGS_DEFINE)
USER_SETTINGS_INCLUDE USER_SETTINGS_DEFINE MACRO_PREFIX)
cmake_parse_arguments(SB "${_opts}" "${_one}" "${_multi}" ${ARGN})

if(NOT SB_NAME)
Expand Down Expand Up @@ -274,6 +276,9 @@ function(wolfssl_compliance_tooling_add_sbom)
if(NOT "${SB_CRYPTO_ONLY}" STREQUAL "")
list(APPEND _cmd --crypto-only "${SB_CRYPTO_ONLY}")
endif()
foreach(_pfx IN LISTS SB_MACRO_PREFIX)
list(APPEND _cmd --macro-prefix ${_pfx})
endforeach()
foreach(_dv IN LISTS SB_DEP_VERSION)
list(APPEND _cmd --dep-version ${_dv})
endforeach()
Expand Down
3 changes: 3 additions & 0 deletions share/build/sbom.mk
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
# Predefines for that preprocess, space-separated.
# Include WOLFSSL_USER_SETTINGS.
# SBOM_SOURCE_ONLY = 1 Source-inventory SBOM with no build-config macros.
# SBOM_MACRO_PREFIX Record only the captured macros whose name starts
# with one of these prefixes, space-separated.
#
# Config, strongly recommended alongside SBOM_CFLAGS:
# SBOM_SETTINGS_H Settings header -include'd during the capture, so the
Expand Down Expand Up @@ -214,6 +216,7 @@ $(1): $($(2)PREREQS)
$(if $($(2)DEP_LIBZ),--dep-libz "$($(2)DEP_LIBZ)") \
$(if $($(2)DOCUMENT_NAMESPACE),--document-namespace "$($(2)DOCUMENT_NAMESPACE)") \
$(if $($(2)CRYPTO_ONLY),--crypto-only "$($(2)CRYPTO_ONLY)") \
$(foreach p,$($(2)MACRO_PREFIX),--macro-prefix "$(p)") \
$$$$dep_ver \
$(if $(or $($(2)GEN),$(GEN_SBOM)),--gen-sbom "$(or $($(2)GEN),$(GEN_SBOM))") \
$(if $($(2)CDX_OUT),--cdx-out "$($(2)CDX_OUT)") \
Expand Down
67 changes: 63 additions & 4 deletions share/gen-sbom
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ def resolve_crypto_only(mode, build_props):
"""Return (is_crypto_only, basis) for --crypto-only auto|yes|no.

basis is 'declared' when the operator stated it, 'captured' when it was
read out of the configuration macros this SBOM records, and 'unknown'
read out of the captured configuration macros (before any --macro-prefix
filter, so the deciding macro may not be recorded), and 'unknown'
when there are no captured macros to read (a --source-only front end
such as Zephyr, where nothing in the inputs can settle the question).

Expand Down Expand Up @@ -283,7 +284,8 @@ def config_identity(lib_hash, license_id, build_props, enabled_deps,
dep_versions, *, supplier=None, component_type=None,
license_text=None, hash_kind=None, hash_source=None,
file_names=None, wolfssl_subset=None, subset_basis=None,
copyright_start=DEFAULT_COPYRIGHT_START):
copyright_start=DEFAULT_COPYRIGHT_START,
macro_prefixes=None):
"""Stable digest of the build configuration behind one name+version.

Folded into the SBOM serialNumber / SPDX documentNamespace so two builds
Expand Down Expand Up @@ -333,6 +335,8 @@ def config_identity(lib_hash, license_id, build_props, enabled_deps,
# Folded only when set, so default documents keep their serialNumber.
if copyright_start != DEFAULT_COPYRIGHT_START:
_field('copyright_start', str(copyright_start))
if macro_prefixes:
_field('macro_prefix', ','.join(macro_prefixes))
return h.hexdigest()


Expand Down Expand Up @@ -1007,6 +1011,15 @@ def parse_user_settings(settings_h_path, include_dirs, predefines):
return sorted(defines.items())


def filter_macro_prefix(build_props, prefixes):
"""Keep only the (name, value) pairs whose name starts with a prefix.

An empty prefix list keeps everything."""
if not prefixes:
return build_props
return [(k, v) for k, v in build_props if k.startswith(tuple(prefixes))]


def gitoid_blob_sha256(path):
"""Compute the OmniBOR / git SHA-256 gitoid for a single file.

Expand Down Expand Up @@ -1208,7 +1221,8 @@ def generate_cdx(name, version, supplier, license_id, license_text, lib_hash,
dep_version_overrides=None, hash_kind='library-binary',
hash_source='lib', srcs_basenames=None, file_entries=None,
component_type='library', wolfssl_subset=None,
subset_basis=None, copyright_start=DEFAULT_COPYRIGHT_START):
subset_basis=None, copyright_start=DEFAULT_COPYRIGHT_START,
macro_prefixes=None):
bom_ref = derived_uuid(name, version, 'package')
urls = project_urls(name)

Expand Down Expand Up @@ -1242,6 +1256,11 @@ def generate_cdx(name, version, supplier, license_id, license_text, lib_hash,
{'name': f'wolfssl:build:{k}', 'value': v}
for k, v in build_props
]
if macro_prefixes:
properties.append({
'name': 'wolfssl:sbom:macro-prefix',
'value': ','.join(macro_prefixes),
})
# Document what the SHA-256 in `hashes` represents, on every entry
# point. Without this property an auditor reading the SBOM has to
# guess whether the SHA-256 is over a library binary, a source-set
Expand Down Expand Up @@ -1380,7 +1399,8 @@ def generate_spdx(name, version, supplier, license_id, license_text, lib_hash,
hash_source='lib', srcs_basenames=None,
document_namespace=None, file_entries=None,
component_type='library', wolfssl_subset=None,
subset_basis=None, copyright_start=DEFAULT_COPYRIGHT_START):
subset_basis=None, copyright_start=DEFAULT_COPYRIGHT_START,
macro_prefixes=None):
build_defines = ', '.join(k for k, _ in build_props)
# Hash-kind / source-set / bomsh-traced-binary information used to
# be stuffed into the package `comment` as `key=value` slugs, which
Expand Down Expand Up @@ -1410,6 +1430,8 @@ def generate_spdx(name, version, supplier, license_id, license_text, lib_hash,
_annotate(f'wolfssl:sbom:no-artifact-hash-note={_NO_HASH_NOTE}')
if srcs_basenames:
_annotate('wolfssl:sbom:source-set=' + ','.join(srcs_basenames))
if macro_prefixes:
_annotate('wolfssl:sbom:macro-prefix=' + ','.join(macro_prefixes))
if wolfssl_subset:
_annotate(f'wolfssl:sbom:wolfssl-subset={wolfssl_subset}')
_annotate('wolfssl:sbom:wolfssl-subset-basis='
Expand Down Expand Up @@ -1703,6 +1725,15 @@ def main():
'on the compiler command line. At minimum '
'pass `WOLFSSL_USER_SETTINGS` so settings.h '
'pulls in user_settings.h.')
parser.add_argument('--macro-prefix', action='append', default=[],
metavar='PREFIX',
help='Record only the build-configuration macros '
'whose name starts with PREFIX (repeatable), '
'e.g. WOLFCERT_ for a product that shares one '
'user_settings.h with wolfSSL. Applies to '
'--options-h and --user-settings alike. '
'--crypto-only auto still reads the full '
'capture.')
# Component checksum source: pick exactly one.
parser.add_argument('--lib',
help='Path to the wolfSSL library artifact '
Expand Down Expand Up @@ -1809,6 +1840,14 @@ def main():
"compiled into your firmware (OmniBOR gitoid Merkle hash).\n"
" --no-artifact-hash: record a placeholder when no "
"hashable artefact exists (ROM/HSM/binary-only).")
if any(not pfx.strip() for pfx in args.macro_prefix):
sys.exit("ERROR: --macro-prefix must not be empty.")
if any(pfx != pfx.strip() for pfx in args.macro_prefix):
sys.exit("ERROR: --macro-prefix must not start or end with "
"whitespace.")
if any(',' in pfx for pfx in args.macro_prefix):
sys.exit("ERROR: --macro-prefix takes one prefix; repeat the option "
"for more.")

# SPDX 2.3 §6.5 requires documentNamespace to be a unique absolute URI
# per RFC 3986. `make sbom` runs pyspdxtools afterwards and would
Expand Down Expand Up @@ -1881,6 +1920,23 @@ def main():
crypto_only, subset_basis = resolve_crypto_only(args.crypto_only,
build_props)
wolfssl_subset = WOLFSSL_SUBSET_CRYPTO_ONLY if crypto_only else None
macro_prefixes = sorted(set(args.macro_prefix))
captured = build_props
build_props = filter_macro_prefix(build_props, macro_prefixes)
if macro_prefixes and not captured:
print(f"WARNING: --macro-prefix {','.join(macro_prefixes)} has no "
"captured macros to filter.", file=sys.stderr)
elif macro_prefixes and not build_props:
print(
f"WARNING: --macro-prefix {','.join(macro_prefixes)} matched none "
f"of the {len(captured)} captured macros; the SBOM records no "
"build configuration.",
file=sys.stderr)
elif captured:
for pfx in macro_prefixes:
if not filter_macro_prefix(captured, [pfx]):
print(f"WARNING: --macro-prefix {pfx} matched none of the "
f"{len(captured)} captured macros.", file=sys.stderr)
if subset_basis == 'unknown' and 'wolfssl' in enabled_deps:
# A front end that captures no macros (--source-only) cannot answer
# the question either way. Say so rather than defaulting silently to
Expand Down Expand Up @@ -1981,6 +2037,7 @@ def main():
wolfssl_subset=wolfssl_subset,
subset_basis=subset_basis,
copyright_start=args.copyright_start,
macro_prefixes=macro_prefixes,
)
serial = derived_uuid(args.name, args.version, 'serial', config_id)
doc_ns_uuid = derived_uuid(args.name, args.version, 'document', config_id)
Expand All @@ -1997,6 +2054,7 @@ def main():
wolfssl_subset=wolfssl_subset,
subset_basis=subset_basis,
copyright_start=args.copyright_start,
macro_prefixes=macro_prefixes,
)
spdx = generate_spdx(
args.name, args.version, args.supplier,
Expand All @@ -2011,6 +2069,7 @@ def main():
wolfssl_subset=wolfssl_subset,
subset_basis=subset_basis,
copyright_start=args.copyright_start,
macro_prefixes=macro_prefixes,
)

try:
Expand Down
13 changes: 13 additions & 0 deletions share/sbom-driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ def main():
"user_settings.h.")
grp.add_argument("--source-only", action="store_true",
help="Source-inventory SBOM with no build-config macros.")
ap.add_argument("--macro-prefix", action="append", default=[],
metavar="PREFIX",
help="Record only configuration macros whose name starts "
"with PREFIX (repeatable). Passed through only if "
"the generator supports it.")

# Identity.
ap.add_argument("--name", required=True, help="Package name in the SBOM.")
Expand Down Expand Up @@ -486,6 +491,14 @@ def main():
if args.dep_version and gen_sbom_supports(args.python, gen_sbom, "--dep-version"):
for dv in args.dep_version:
cmd += ["--dep-version", dv]
if args.macro_prefix:
if gen_sbom_supports(args.python, gen_sbom, "--macro-prefix"):
for prefix in args.macro_prefix:
cmd += ["--macro-prefix", prefix]
else:
print("NOTE: this gen-sbom has no --macro-prefix support; "
"every captured macro will be recorded.",
file=sys.stderr)
if args.crypto_only and gen_sbom_supports(
args.python, gen_sbom, "--crypto-only"):
cmd += ["--crypto-only", args.crypto_only]
Expand Down
71 changes: 58 additions & 13 deletions share/sbom.am
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@
# feature flags are NOT in config.h (no AC_DEFINE); when
# unset the recipe derives the macros from the compiler +
# config.h. Default: unset.
# SBOM_USER_SETTINGS Path to wolfssl/wolfcrypt/settings.h, for a product
# configured by a user_settings.h instead of configure.
# gen-sbom walks it through pcpp. Also set
# SBOM_USER_SETTINGS_INCLUDES (one directory must hold
# user_settings.h) and SBOM_USER_SETTINGS_DEFINES
# (include WOLFSSL_USER_SETTINGS), both space-separated.
# Mutually exclusive with SBOM_OPTIONS_H. Default: unset.
# SBOM_MACRO_PREFIX Space-separated macro name prefixes. When set, only
# the captured macros starting with one of them are
# recorded, e.g. WOLFCERT_ for a product that shares its
# user_settings.h with wolfSSL. Default: unset.
# SBOM_ARTIFACT lib | bin - which build output to hash. Default: lib.
# SBOM_LIB_STEM Library basename w/o extension. Default: lib$(SBOM_PKGNAME).
# SBOM_BIN_NAME Program name when SBOM_ARTIFACT = bin. Default: $(SBOM_PKGNAME).
Expand Down Expand Up @@ -85,8 +96,8 @@
# (AC_PATH_PROG); git is used only to derive SOURCE_DATE_EPOCH.
#
# NOTE: this fragment requires GNU make. It uses GNU conditional assignment
# (?=) and the GNU make functions $(wildcard), $(if), $(firstword) and
# $(addprefix); under a non-GNU make the SBOM targets will not work.
# (?=) and the GNU make functions $(wildcard), $(if), $(firstword), $(strip)
# and $(addprefix); under a non-GNU make the SBOM targets will not work.
# ---------------------------------------------------------------------------

SBOM_ARTIFACT ?= lib
Expand Down Expand Up @@ -144,15 +155,15 @@ CLEANFILES += $(SBOM_CDX) $(SBOM_SPDX) $(SBOM_SPDX_TV)

# Stage a `make install` into a private tree, discover the installed artifact
# (shared/static library or program; ELF/Mach-O/PE), hash it, capture the
# configured build macros (from SBOM_OPTIONS_H if set, else the -D/-U/-I tokens
# of AM_CPPFLAGS/AM_CFLAGS/CFLAGS + config.h, expanded through the HOST compiler
# SBOM_HOSTCC so the SBOM is reproducible across cross toolchains; some products
# carry their feature -D flags in AM_CFLAGS rather than AM_CPPFLAGS, and some
# outside config.h entirely),
# generate SPDX+CDX, validate
# the SPDX, then convert to tag-value. The staging tree and temp defines file
# are removed unconditionally via `trap`, even on failure. SOURCE_DATE_EPOCH is
# honoured for reproducible output (defaults to the last git commit time).
# configured build macros (from SBOM_USER_SETTINGS or SBOM_OPTIONS_H if set,
# else the -D/-U/-I tokens of AM_CPPFLAGS/AM_CFLAGS/CFLAGS + config.h,
# expanded through the HOST compiler SBOM_HOSTCC so the SBOM is reproducible
# across cross toolchains; some products carry their feature -D flags in
# AM_CFLAGS rather than AM_CPPFLAGS, and some outside config.h entirely),
# generate SPDX+CDX, validate the SPDX, then convert to tag-value. The
# staging tree and temp defines file are removed unconditionally via `trap`,
# even on failure. SOURCE_DATE_EPOCH is honoured for reproducible output
# (defaults to the last git commit time).
sbom:
@test -n "$(PYTHON3)" || { \
echo "ERROR: 'python3' not found in PATH. Cannot generate SBOM."; \
Expand Down Expand Up @@ -202,7 +213,29 @@ sbom:
fi; \
echo "SBOM: hashing $$sbom_art"; \
opts_h="$(SBOM_OPTIONS_H)"; \
if test -z "$$opts_h"; then \
set --; \
if test -n "$(SBOM_USER_SETTINGS)"; then \
if test -n "$$opts_h"; then \
echo "ERROR: set SBOM_USER_SETTINGS or SBOM_OPTIONS_H, not both."; \
exit 1; \
fi; \
if test -z "$(strip $(SBOM_USER_SETTINGS_INCLUDES))"; then \
echo "ERROR: SBOM_USER_SETTINGS needs SBOM_USER_SETTINGS_INCLUDES."; \
exit 1; \
fi; \
case " $(SBOM_USER_SETTINGS_DEFINES) " in \
*" WOLFSSL_USER_SETTINGS "*|*" WOLFSSL_USER_SETTINGS="*) ;; \
*) echo "ERROR: SBOM_USER_SETTINGS_DEFINES must include WOLFSSL_USER_SETTINGS."; \
exit 1;; \
esac; \
set -- --user-settings "$(SBOM_USER_SETTINGS)"; \
for d in $(SBOM_USER_SETTINGS_INCLUDES); do \
set -- "$$@" --user-settings-include "$$d"; \
done; \
for d in $(SBOM_USER_SETTINGS_DEFINES); do \
set -- "$$@" --user-settings-define "$$d"; \
done; \
elif test -z "$$opts_h"; then \
opts_h="$$_defines"; \
sbom_cpp=""; \
for f in $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS); do \
Expand All @@ -212,6 +245,18 @@ sbom:
$(if $(wildcard $(SBOM_CONFIG_H)),-include $(SBOM_CONFIG_H)) \
-x c /dev/null > "$$_defines"; \
fi; \
if test $$# -eq 0; then set -- --options-h "$$opts_h"; fi; \
if test -n "$(SBOM_MACRO_PREFIX)"; then \
if $(PYTHON3) "$(SBOM_GEN)" --help 2>/dev/null \
| $(GREP) -q -- '--macro-prefix'; then \
for p in $(SBOM_MACRO_PREFIX); do \
set -- "$$@" --macro-prefix "$$p"; \
done; \
else \
echo "NOTE: this gen-sbom has no --macro-prefix support; every"; \
echo " captured macro will be recorded."; \
fi; \
fi; \
if test -z "$${SOURCE_DATE_EPOCH:-}" && test -n "$(GIT)" && \
$(GIT) -C "$(srcdir)" rev-parse --git-dir >/dev/null 2>&1; then \
sde=`$(GIT) -C "$(srcdir)" log -1 --format=%ct 2>/dev/null`; \
Expand Down Expand Up @@ -291,7 +336,7 @@ sbom:
--version $(PACKAGE_VERSION) \
--supplier "wolfSSL Inc." \
--license-file $(SBOM_LICENSE_FILE) \
--options-h "$$opts_h" \
"$$@" \
--lib "$$sbom_art" \
$$dep_args \
$${ns_arg:+$$ns_arg '$(SBOM_DOCUMENT_NAMESPACE)'} \
Expand Down
Loading
Loading