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
4 changes: 3 additions & 1 deletion .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ per-app workflows stay untouched.

| Label | Effect |
|-------|--------|
| `ci:<name>` | Run that one job (e.g. `ci:hostap`, `ci:curl`, `ci:static-analysis`). Add several to run several. |
| `ci:<name>` | Run that one job (e.g. `ci:hostap`, `ci:curl`, `ci:static-analysis`, `ci:sbom`). Add several to run several. |
| `ci:all` | Run the whole fan-out (all 43 jobs). |
| (no label) | Nothing runs — a normal PR is unaffected. |

Expand Down Expand Up @@ -176,6 +176,7 @@ exercised, with and without `WOLFPROV_FORCE_FAIL=1`.
| `debian-package.yml` | End-to-end check: builds the wolfprov `.deb`s and confirms they install cleanly on a fresh container and the provider loads. |
| `openssl-version.yml` | Sweeps every upstream `openssl-3.X.Y` release tag — catches breakage from OpenSSL point releases before they hit our matrix defaults. |
| `static-analysis.yml` | cppcheck, clang scan-build, Facebook Infer. Heavy enough that it lives in the nightly fan-out rather than per-PR. |
| `sbom.yml` | Full `make sbom` with the vendored wolfGlass toolkit: SPDX validation, CycloneDX identity, wolfSSL/OpenSSL dependency recording, reproducibility. Builds the OpenSSL + wolfSSL + wolfProvider stack, so it is nightly + `ci:sbom` rather than every PR. |

Sanitizers (ASan+UBSan, TSan) run on every PR/push — see the PR table
above. They're fast enough with caching to gate merges, so they don't
Expand Down Expand Up @@ -384,6 +385,7 @@ can run it on demand:
gh workflow run nightly-osp.yml --ref <branch>
gh workflow run sanitizers.yml --ref <branch>
gh workflow run static-analysis.yml --ref <branch>
gh workflow run sbom.yml --ref <branch>
gh workflow run hostap.yml --ref <branch> # single OSP
```

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/nightly-osp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name: Nightly OSP Suite
# wolfssl stable). Stays dynamic across version bumps.
# Wave 2 = v5.8.4-stable pinned (back-compat line), gated on wave1-done
# with if: always() so a single Wave 1 failure doesn't skip the
# older-line coverage. multi-compiler and static-analysis run once
# older-line coverage. multi-compiler, static-analysis, and sbom run once
# outside the waves.
# nginx-pqc is Wave 1 only: PQC has a v5.9.2-stable floor, so the
# v5.8.4-stable Wave 2 line has no eligible ref to run.
Expand Down Expand Up @@ -169,3 +169,4 @@ jobs:
# === Out-of-wave: not wolfssl-version-split ===
static-analysis: { uses: ./.github/workflows/static-analysis.yml }
multi-compiler: { uses: ./.github/workflows/nightly-multi-compiler.yml }
sbom: { uses: ./.github/workflows/sbom.yml }
4 changes: 4 additions & 0 deletions .github/workflows/pr-osp-select.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ jobs:
needs: select
if: contains(needs.select.outputs.run, ' all ') || contains(needs.select.outputs.run, ' static-analysis ')
uses: ./.github/workflows/static-analysis.yml
sbom:
needs: select
if: contains(needs.select.outputs.run, ' all ') || contains(needs.select.outputs.run, ' sbom ')
uses: ./.github/workflows/sbom.yml
stunnel:
needs: select
if: contains(needs.select.outputs.run, ' all ') || contains(needs.select.outputs.run, ' stunnel ')
Expand Down
207 changes: 207 additions & 0 deletions .github/workflows/sbom.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
name: SBOM Test
Comment thread
aidangarske marked this conversation as resolved.

# Full SBOM generation for wolfProvider using the vendored wolfGlass toolkit
# under tools/sbom/. This job builds the OpenSSL + wolfSSL + wolfProvider
# stack, so it is not a cheap PR check.
#
# Triggers (Aidan, PR #410):
# * Nightly, via nightly-osp.yml (out-of-wave, like static-analysis).
# * On a PR, add the `ci:sbom` label (pr-osp-select.yml).
# * Manual: workflow_dispatch.
#
# A per-PR smoke that only ran `make sbom` + pyspdxtools would still pay the
# full stack build, so it is not cheaper than this job. Use nightly + label.

on:
workflow_call:
inputs:
wolfssl_ref:
description: 'wolfSSL git ref to build and link (not the generator)'
required: false
type: string
default: 'master'
workflow_dispatch:
inputs:
wolfssl_ref:
description: 'wolfSSL git ref to build and link (not the generator)'
required: false
default: 'master'

# run-scoped: a reusable workflow's group keys off the caller, so a shared
# group would let one nightly run cancel another's SBOM job.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.run_id }}
cancel-in-progress: false

# This workflow only reads the repo and uploads artefacts; no API writes.
permissions:
contents: read

jobs:
sbom:
name: wolfProvider SBOM generation (linux)
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Checkout wolfprovider
uses: actions/checkout@v4

- name: Verify vendored wolfGlass generator
run: |
test -f tools/sbom/gen-sbom
test -f tools/sbom/sbom.am
test -f tools/sbom/.wolfglass-rev
python3 -m py_compile tools/sbom/gen-sbom
echo "wolfGlass pin: $(cat tools/sbom/.wolfglass-rev) $(cat tools/sbom/VERSION)"

- name: Install build tooling and SBOM validator (pyspdxtools)
run: |
sudo apt-get update
sudo apt-get install -y build-essential autoconf automake libtool \
pkg-config
python3 -m pip install --user 'spdx-tools==0.8.*'
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

# Build the full stack (OpenSSL + wolfSSL + wolfProvider) from source using
# the project's maintained script. WOLFPROV_SKIP_TEST keeps it a
# build-only run (the SBOM only needs the built libwolfprov artifact).
# This also runs autogen + ./configure in-tree, so `make sbom` picks up the
# AC_PATH_PROG(PYTHON3/PYSPDXTOOLS/GIT) substitutions. wolfSSL defaults to
# master; override with WOLFSSL_TAG / the wolfssl_ref input.
- name: Build wolfProvider stack (openssl + wolfssl + wolfprovider)
env:
WOLFSSL_TAG: ${{ inputs.wolfssl_ref || 'master' }}
run: WOLFPROV_SKIP_TEST=1 ./scripts/build-wolfprovider.sh

# Record the versions of the wolfSSL and OpenSSL that were actually
# linked, so the SBOM's dependency versions match the deployed binaries.
- name: Resolve linked wolfSSL / OpenSSL versions
id: vers
run: |
wv=""
if [ -f wolfssl-source/wolfssl/version.h ]; then
wv=$(sed -n 's/.*LIBWOLFSSL_VERSION_STRING[ \t]*"\([^"]*\)".*/\1/p' \
wolfssl-source/wolfssl/version.h)
fi
ov=""
if [ -x openssl-install/bin/openssl ]; then
# Pass the raw string (may include BUILD_METADATA, e.g.
# 3.5.4+wolfProvider-nonfips). gen-sbom keeps it in versionInfo
# and drops the +suffix from CPE 2.3 and PURL.
ov=$(openssl-install/bin/openssl version | awk '{print $2}')
fi
echo "wolfssl=$wv" >> "$GITHUB_OUTPUT"
echo "openssl=$ov" >> "$GITHUB_OUTPUT"
echo "linked wolfSSL=$wv openssl=$ov"

- name: Generate SBOM
run: |
make sbom \
WOLFSSL_DIR="$GITHUB_WORKSPACE/wolfssl-source" \
SBOM_WOLFSSL_VERSION="${{ steps.vers.outputs.wolfssl }}" \
SBOM_OPENSSL_VERSION="${{ steps.vers.outputs.openssl }}"

- name: Outputs exist and SPDX validates
run: |
ls wolfprovider-*.cdx.json wolfprovider-*.spdx.json wolfprovider-*.spdx
pyspdxtools --infile wolfprovider-*.spdx.json
python3 tools/sbom/validate_sbom.py \
--name-prefix wolfprovider \
--require-dep-version wolfssl \
--require-dep-version openssl \
wolfprovider-*.cdx.json wolfprovider-*.spdx.json

- name: CycloneDX identity and licence
run: |
python3 - <<'PY'
import glob, json
cdx = json.load(open(glob.glob('wolfprovider-*.cdx.json')[0]))
assert cdx['bomFormat'] == 'CycloneDX', cdx.get('bomFormat')
assert cdx['specVersion'] == '1.6', cdx.get('specVersion')
m = cdx['metadata']['component']
assert m['name'] == 'wolfprovider', m['name']
# purl-spec: github namespace and name are lowercased.
assert m['purl'].startswith('pkg:github/wolfssl/wolfprovider@'), m['purl']
# Default override must land as GPL-3.0-or-later (matches source headers).
ids = [l.get('license', {}).get('id') for l in m.get('licenses', [])]
assert 'GPL-3.0-or-later' in ids, ids
# Identity is the hashed library artifact.
assert {h['alg'] for h in m.get('hashes', [])}, 'no component hash'
print('CDX ok:', m['name'], m['purl'], ids)
PY

- name: Assert no host path leak
run: |
if grep -REn '"/(home|Users|root)/' wolfprovider-*.cdx.json \
wolfprovider-*.spdx.json; then
echo "ERROR: absolute host path found in SBOM (scrub failed)." >&2
exit 1
fi
echo "OK: no host path leak."

- name: Reproducible across two runs (SOURCE_DATE_EPOCH)
run: |
rm -f wolfprovider-*.cdx.json wolfprovider-*.spdx.json wolfprovider-*.spdx
SOURCE_DATE_EPOCH=1700000000 make sbom \
WOLFSSL_DIR="$GITHUB_WORKSPACE/wolfssl-source" \
SBOM_WOLFSSL_VERSION="${{ steps.vers.outputs.wolfssl }}" \
SBOM_OPENSSL_VERSION="${{ steps.vers.outputs.openssl }}"
sha256sum wolfprovider-*.cdx.json wolfprovider-*.spdx.json > /tmp/a.sums
rm -f wolfprovider-*.cdx.json wolfprovider-*.spdx.json wolfprovider-*.spdx
SOURCE_DATE_EPOCH=1700000000 make sbom \
WOLFSSL_DIR="$GITHUB_WORKSPACE/wolfssl-source" \
SBOM_WOLFSSL_VERSION="${{ steps.vers.outputs.wolfssl }}" \
SBOM_OPENSSL_VERSION="${{ steps.vers.outputs.openssl }}"
sha256sum wolfprovider-*.cdx.json wolfprovider-*.spdx.json > /tmp/b.sums
diff /tmp/a.sums /tmp/b.sums

- name: wolfssl recorded as a dependency
run: |
python3 - <<'PY'
import glob, json
d = json.load(open(glob.glob('wolfprovider-*.spdx.json')[0]))
assert 'wolfssl' in {p['name'] for p in d['packages']}, \
[p['name'] for p in d['packages']]
rels = [(r['spdxElementId'], r['relationshipType'],
r['relatedSpdxElement']) for r in d['relationships']]
assert ('SPDXRef-Package-wolfprovider', 'DEPENDS_ON',
'SPDXRef-Package-wolfssl') in rels, rels
print('wolfssl dependency ok')
PY

- name: openssl recorded as a dependency
run: |
python3 - <<'PY'
import glob, json
d = json.load(open(glob.glob('wolfprovider-*.spdx.json')[0]))
pkgs = {p['name']: p for p in d['packages']}
assert 'openssl' in pkgs, list(pkgs)
rels = [(r['spdxElementId'], r['relationshipType'],
r['relatedSpdxElement']) for r in d['relationships']]
assert ('SPDXRef-Package-wolfprovider', 'DEPENDS_ON',
'SPDXRef-Package-openssl') in rels, rels
openssl = pkgs['openssl']
# versionInfo keeps BUILD_METADATA when openssl version prints it.
# CPE 2.3 and PURL must not contain a raw '+'.
refs = {r['referenceType']: r['referenceLocator']
for r in openssl.get('externalRefs', [])}
cpe = refs.get('cpe23Type', '')
purl = refs.get('purl', '')
assert cpe.startswith('cpe:2.3:a:openssl:openssl:'), cpe
assert '+' not in cpe, cpe
assert purl.startswith('pkg:github/openssl/openssl@openssl-'), purl
assert '+' not in purl, purl
print('openssl dependency ok:', openssl.get('versionInfo'), cpe, purl)
PY

- name: Upload SBOM artefacts
if: always()
uses: actions/upload-artifact@v4
with:
name: wolfprovider-sbom-${{ github.sha }}
path: |
wolfprovider-*.cdx.json
wolfprovider-*.spdx.json
wolfprovider-*.spdx
if-no-files-found: warn
retention-days: 90
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
*.swo


# SBOM output artefacts (produced by `make sbom`). Built per-release, not source.
/wolfprovider-*.cdx.json
/wolfprovider-*.spdx.json
/wolfprovider-*.spdx
/_sbom_staging/

# Ignore all things produced by autoreconf
/Makefile.in
/aclocal.m4
Expand Down
36 changes: 36 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ noinst_HEADERS =
check_PROGRAMS =
dist_noinst_SCRIPTS =
DISTCLEANFILES =
CLEANFILES =
pkginclude_HEADERS =
EXTRA_DIST =

Expand Down Expand Up @@ -52,3 +53,38 @@ test: check
# The '--with-wolfssl' doesn't get propagated during a distcheck either, but it
# is necessary when they are installed somewhere other than /usr/local.
AM_DISTCHECK_CONFIGURE_FLAGS=CPPFLAGS="-I@abs_top_srcdir@/include" --with-openssl=@OPENSSL_INSTALL_DIR@ --with-wolfssl=@WOLFSSL_INSTALL_DIR@

# SBOM generation (CRA compliance). The recipe is the vendored wolfGlass
# fragment at tools/sbom/sbom.am. wolfProvider declares what it is (a shared
# library that links wolfSSL and OpenSSL) and includes it. gen-sbom is vendored
# next to the fragment, so `make sbom` does not need a wolfSSL source tree.
# Set WOLFSSL_DIR only when you want the SBOM to read the linked wolfSSL
# version from wolfssl/version.h.
SBOM_PKGNAME = wolfprovider
SBOM_LICENSE_FILE = $(srcdir)/COPYING
SBOM_LIB_STEM = libwolfprov
SBOM_DEP_WOLFSSL = yes
SBOM_DEP_OPENSSL = yes

# AC_CONFIG_HEADERS puts our config.h in include/, not the build root, so the
# fragment's default would silently miss every AC_DEFINE feature macro: the
# -include is $(wildcard)-guarded, so a wrong path drops out without an error
# and the SBOM records an empty feature set.
SBOM_CONFIG_H = $(abs_builddir)/include/config.h

# wolfProvider is GPLv3-or-later (per the per-file source headers: "either
# version 3 of the License, or (at your option) any later version") or
# commercial. Pin the header-accurate SPDX id here so the SBOM is correct
# regardless of the gen-sbom version's licence detection; commercial licensees
# can override it (e.g. LicenseRef-wolfSSL-Commercial).
SBOM_LICENSE_OVERRIDE ?= GPL-3.0-or-later

# Automake inlines this include into Makefile. Do not assign SBOM_GEN or
# SBOM_VENDOR_DIR here: Automake -Werror treats a second assignment in
# tools/sbom/sbom.am as fatal and skips Makefile.in. The fragment default
# SBOM_VENDOR_DIR is $(srcdir)/tools/sbom.

EXTRA_DIST += tools/sbom

include tools/sbom/sbom.am

44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,50 @@ Without an enable flag no PQC code is compiled, regardless of what wolfSSL enabl
* ML-DSA (FIPS 204): ML-DSA-44, ML-DSA-65, ML-DSA-87 (signatures, pure mode with empty context per FIPS 204 sec 5.2)


## SBOM / EU CRA Compliance

wolfProvider 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).
The generator is the vendored [wolfGlass](https://github.com/wolfSSL/wolfGlass)
toolkit under `tools/sbom/`. The SBOM records the configured build options,
hashes the built `libwolfprov` library artifact (shared or static; ELF, Mach-O,
or PE), and lists both wolfSSL and OpenSSL as dependencies so vulnerability
scanners can associate wolfSSL and OpenSSL advisories with a wolfProvider
deployment. Output is reproducible: set `SOURCE_DATE_EPOCH` (or build from a
git checkout, which uses the last commit time) and repeated runs are
byte-identical.

```sh
make sbom
```

Requires `python3` and `pyspdxtools` (`pip install spdx-tools`). The generator
is vendored, so you do not need a wolfSSL source tree to run `make sbom`. Set
`WOLFSSL_DIR` only when you want the SBOM to read the linked wolfSSL version
from `wolfssl/version.h`.

Output: `wolfprovider-<version>.cdx.json`, `wolfprovider-<version>.spdx.json`, `wolfprovider-<version>.spdx`

Optional overrides:

- `SBOM_LICENSE_OVERRIDE` - SPDX expression to use instead of the licence
parsed from `COPYING` (e.g. `LicenseRef-wolfSSL-Commercial` for commercial
licensees). Defaults to `GPL-3.0-or-later` (the per-file header licence).
- `SBOM_LICENSE_TEXT` - path to the licence text for any `LicenseRef-*` used in
`SBOM_LICENSE_OVERRIDE` (required by SPDX 2.3).
- `SBOM_WOLFSSL_VERSION` - version recorded for the wolfSSL dependency;
auto-detected from `WOLFSSL_DIR/wolfssl/version.h` (or wolfSSL's `pkg-config`
entry) when unset.
- `SBOM_OPENSSL_VERSION` - version recorded for the OpenSSL dependency;
resolved via OpenSSL's `pkg-config` entry when unset.

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

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

## Support

- [GitHub Issues](https://github.com/wolfssl/wolfProvider/issues)
Expand Down
11 changes: 11 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ USER_CFLAGS="$CFLAGS"
AC_PROG_CC
AC_LANG(C)

# Tools used by the SBOM targets (see tools/sbom/sbom.am `make sbom`). GIT is
# used only to derive SOURCE_DATE_EPOCH for reproducible SBOM output; all three
# are optional and the target reports a clear error when a required one is
# missing.
AC_PATH_PROG([PYTHON3], [python3])
AC_PATH_PROG([PYSPDXTOOLS], [pyspdxtools])
AC_PATH_PROG([GIT], [git])
AC_SUBST([PYTHON3])
AC_SUBST([PYSPDXTOOLS])
AC_SUBST([GIT])

# wolfSSL - check first so its -I/-L paths take precedence over OpenSSL prefix
# which may contain stale wolfSSL headers from a different version
AX_CHECK_WOLFSSL(
Expand Down
Loading
Loading