diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 49b78186ee..4e2b3fcd95 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -655,102 +655,20 @@ jobs: print('simple SPDX override: ok') PY - # ---- liboqs / Falcon dep entry --------------------------------------- - # Without this, every code path that emits a dep package - pkg-config - # lookup, supplier/purl/license construction, deterministic UUID - # derivation for deps - is uncovered by CI. A future rename or shape - # break in DEP_META['liboqs'] would silently land. + # ---- Falcon (native) build-property coverage -------------------------- + # liboqs was removed from wolfSSL (Falcon is now provided natively by + # wolfCrypt), so there is no external dependency package to record; this + # exercises the SBOM build-property capture with an algorithm enabled. - - name: Install liboqs (provides liboqs.pc for --with-liboqs) - # Ubuntu noble (24.04) does not ship liboqs-dev in its archive - # (Debian sid has 0.7.x; Ubuntu only has unsupported PPAs). Build - # from a pinned upstream commit so this job stays deterministic across - # runs - any future liboqs API/ABI break shows up here, not in - # production builds. Pinning matters: SBOM correctness assertions - # below check purl shape, and an unpinned 'main' would silently - # change what pkg-config reports as the version string. - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - cmake ninja-build libssl-dev - # Pin to the exact commit the 0.12.0 tag resolves to (a tag is - # mutable, a SHA is not) so this provenance workflow's own build - # inputs are immutable. --filter=blob:none keeps the commit graph - # so `git checkout ` works without a full blob download. - git clone --filter=blob:none \ - https://github.com/open-quantum-safe/liboqs /tmp/liboqs - git -C /tmp/liboqs checkout f4b96220e4bd208895172acc4fedb5a191d9f5b1 # 0.12.0 - # -DOQS_USE_OPENSSL=OFF is load-bearing: without it, liboqs's - # installed common.h pulls (system) into every - # TU that includes . wolfssl/wolfcrypt/falcon.h - # includes , so once --enable-falcon is on, every - # wolfSSL TU that pulls falcon.h also pulls system OpenSSL, - # which collides with wolfssl/openssl/ssl.h under -Werror - # (CRYPTO_UNLOCK, sk_num, OPENSSL_malloc_init, ... all redefined). - # OFF makes liboqs use its bundled SHA/randombytes (the #else - # branches in oqs/common.h), keeping the build hermetic. - cmake -S /tmp/liboqs -B /tmp/liboqs/build -GNinja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_INSTALL_PREFIX=/usr/local \ - -DBUILD_SHARED_LIBS=ON \ - -DOQS_BUILD_ONLY_LIB=ON \ - -DOQS_DIST_BUILD=OFF \ - -DOQS_USE_OPENSSL=OFF - cmake --build /tmp/liboqs/build --parallel "$(nproc)" - sudo cmake --install /tmp/liboqs/build - sudo ldconfig - # /usr/local/lib/pkgconfig is on pkg-config's compiled-in path - # on Ubuntu, but export via $GITHUB_ENV so a future image change - # cannot silently break --with-liboqs autodetection. ${VAR:+:$VAR} - # avoids a trailing colon when PKG_CONFIG_PATH is unset. - echo "PKG_CONFIG_PATH=/usr/local/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" \ - >> "$GITHUB_ENV" - - - name: Verify liboqs.pc visible to pkg-config - # Separate step so the $GITHUB_ENV write above has taken effect - # for this shell; an in-step call would only be exercising the - # compiled-in default path, not the export. - run: pkg-config --modversion liboqs - - - name: Configure with --with-liboqs --enable-falcon + - name: Configure with --enable-falcon (native) run: | make distclean autoreconf -ivf - ./configure --enable-shared --enable-experimental \ - --with-liboqs --enable-falcon + ./configure --enable-shared --enable-experimental --enable-falcon - - name: Build + generate SBOM with liboqs enabled + - name: Build + generate SBOM with Falcon enabled run: make sbom - - name: liboqs dep entry resolves to a CVE-trackable identifier - # The point of recording liboqs (rather than `falcon`) is that - # OSV / Grype / Trivy / Dependency-Track key vulnerability - # records off purl + name. These assertions guard the contract - # that pulled the entry away from the algorithm name. - run: | - python3 - <<'PY' - import glob, json, re, sys - with open(glob.glob('wolfssl-*.spdx.json')[0]) as f: - d = json.load(f) - pkgs = {p['name']: p for p in d['packages']} - assert 'liboqs' in pkgs, list(pkgs) - assert 'falcon' not in pkgs, "algorithm name leaked as a dep package" - liboqs = pkgs['liboqs'] - assert liboqs['supplier'] == 'Organization: Open Quantum Safe', \ - liboqs['supplier'] - refs = {r['referenceType']: r['referenceLocator'] - for r in liboqs.get('externalRefs', [])} - assert 'purl' in refs, refs - assert re.match(r'pkg:github/open-quantum-safe/liboqs@', refs['purl']), \ - refs['purl'] - # CycloneDX side: same package + version present. - with open(glob.glob('wolfssl-*.cdx.json')[0]) as f: - cdx = json.load(f) - deps = {c['name']: c for c in cdx.get('components', [])} - assert 'liboqs' in deps, list(deps) - print('liboqs dep entry: ok ->', refs['purl']) - PY - - name: HAVE_FALCON algorithm flag is captured as a build property # Algorithm visibility moved out of the dep entry; this verifies # it is still preserved (just somewhere honest). @@ -811,7 +729,7 @@ jobs: # a downstream packager, or the next maintainer triaging a regression # can download them straight from the run summary instead of replaying # the full job locally. `if: always()` so a failed assertion above - # (license matrix, NTIA, CDX schema, liboqs dep entry, ...) still ships + # (license matrix, NTIA, CDX schema, Falcon build prop, ...) still ships # the bytes it failed on. The last `make sbom` invocation in this job # is the simple SPDX override step, but the path matches every wolfssl # SPDX/CDX in $PWD - if any are present at job end they will be picked diff --git a/Makefile.am b/Makefile.am index c28fb9c578..62b62042c3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -499,12 +499,12 @@ WOLFSSL_LIB_DSO_BASENAMES = \ # https://example.com/sbom/wolfssl-X.Y.Z.spdx.json). # SBOM_DEP_VERSIONS Space-separated KEY=VERSION list forwarded to # gen-sbom as repeated --dep-version flags (KEY is -# one of the known deps, e.g. liboqs / libz). Use +# one of the known deps, e.g. libz / openssl). Use # this on build/packaging hosts that lack the dep's # pkg-config .pc file, where version detection would # otherwise fall back to NOASSERTION (SPDX) / an # omitted version+purl (CycloneDX). Example: -# make sbom SBOM_DEP_VERSIONS='liboqs=0.10.0 libz=1.3.1'. +# make sbom SBOM_DEP_VERSIONS='libz=1.3.1 openssl=3.5.0'. # SBOM_LIB_OVERRIDE Absolute path to the library artefact whose # SHA-256 should land in the SBOM, INSTEAD of # discovering one via a private staging install. @@ -581,7 +581,6 @@ sbom: --options-h $(abs_builddir)/wolfssl/options.h \ --lib "$$sbom_lib" \ --dep-libz "$(ENABLED_LIBZ)" \ - --dep-liboqs "$(ENABLED_LIBOQS)" \ $(foreach dv,$(SBOM_DEP_VERSIONS),--dep-version '$(dv)') \ --cdx-out $(abs_builddir)/$(SBOM_CDX) \ --spdx-out $(abs_builddir)/$(SBOM_SPDX); \ diff --git a/configure.ac b/configure.ac index 78f28db579..bf7eedf287 100644 --- a/configure.ac +++ b/configure.ac @@ -13183,7 +13183,6 @@ AC_PATH_PROG([PYTHON3], [python3]) AC_PATH_PROG([PYSPDXTOOLS], [pyspdxtools]) AC_PATH_PROG([GIT], [git]) AC_SUBST([ENABLED_LIBZ]) -AC_SUBST([ENABLED_LIBOQS]) # Bomsh (OmniBOR build artifact tracing + SBOM enrichment) AC_PATH_PROG([BOMTRACE3], [bomtrace3]) diff --git a/doc/SBOM.md b/doc/SBOM.md index 519a2be7a8..32c7de41b7 100644 --- a/doc/SBOM.md +++ b/doc/SBOM.md @@ -81,7 +81,6 @@ Optional flags: |---|---| | `--supplier "Acme Inc."` | Override the default `wolfSSL Inc.` (rare) | | `--dep-libz yes` | If your build links `libz` | -| `--dep-liboqs yes` | If your build links `liboqs` | | `--dep-version libz=1.3.1` | Explicit dep version when `pkg-config` is unavailable (typical cross-compile) | | `--license-override LicenseRef-wolfSSL-Commercial` | If you are a commercial licensee, not GPL | | `--license-text /path/to/commercial-license.txt` | Required when `--license-override` is a `LicenseRef-*` | @@ -458,7 +457,7 @@ Both formats contain the same information: | CPE | `cpe:2.3:a:wolfssl:wolfssl::*:*:*:*:*:*:*` | | PURL | `pkg:github/wolfSSL/wolfssl@v` (resolves directly in OSV / GHSA / Snyk / Trivy without per-vendor mapping) | | Download location | `https://github.com/wolfSSL/wolfssl` | -| Third-party deps | none in a default build; `--with-libz` adds zlib and `--with-liboqs` adds liboqs (recorded as `DEPENDS_ON` packages with their own purl/CPE/supplier). All builds depend transitively on the host C runtime; this is not enumerated as an SBOM component since it is system-supplied and varies per runtime target. | +| Third-party deps | none in a default build; `--with-libz` adds zlib (recorded as a `DEPENDS_ON` package with its own purl/CPE/supplier). All builds depend transitively on the host C runtime; this is not enumerated as an SBOM component since it is system-supplied and varies per runtime target. | #### License detection @@ -519,14 +518,14 @@ make sbom \ #### External dependency version detection -The optional external dependencies wolfSSL can link against (`libz` and -`liboqs`) are both installed packages and are queried via -`pkg-config --modversion` at SBOM generation time. The SBOM records each -linked library by its package name (`zlib`, `liboqs`) so that downstream -vulnerability scanners (OSV, Grype, Trivy, Dependency-Track) match CVEs -against the right component. Algorithm enablement (e.g. Falcon, which is -reachable only via liboqs) is captured separately as build properties -(`wolfssl:build:HAVE_FALCON` etc.) parsed from `wolfssl/options.h`. +The optional external dependency wolfSSL can link against (`libz`) is an +installed package and is queried via `pkg-config --modversion` at SBOM +generation time. The SBOM records the linked library by its package name +(`zlib`) so that downstream vulnerability scanners (OSV, Grype, Trivy, +Dependency-Track) match CVEs against the right component. Algorithm +enablement (e.g. Falcon, now provided natively by wolfCrypt) is captured +separately as build properties (`wolfssl:build:HAVE_FALCON` etc.) parsed +from `wolfssl/options.h`. If pkg-config does not report a version (the package is not installed, or its `.pc` file is missing): @@ -543,7 +542,7 @@ that lacks the dependency's `.pc` file can still record the version instead of `NOASSERTION`: ```sh -make sbom SBOM_DEP_VERSIONS='liboqs=0.10.0 libz=1.3.1' +make sbom SBOM_DEP_VERSIONS='libz=1.3.1' ``` ### 2.5 Validating the SBOM manually diff --git a/scripts/gen-sbom b/scripts/gen-sbom index f90b3f450e..9225d7c34b 100755 --- a/scripts/gen-sbom +++ b/scripts/gen-sbom @@ -122,19 +122,6 @@ DEP_META = { 'pkgconfig': 'wolfssl', 'purl': lambda v: f'pkg:github/wolfSSL/wolfssl@v{v}', }, - # liboqs is the only PQ external dependency wolfSSL still links against - # after upstream PR #10293 collapsed the rest of the PQ surface into - # native wolfCrypt. Today, --enable-falcon strictly implies --with-liboqs - # (configure.ac enforces both directions), so a build that links liboqs - # is precisely a build that exposed Falcon. - 'liboqs': { - 'name': 'liboqs', - 'supplier': 'Open Quantum Safe', - 'license': 'MIT', - 'download': 'https://github.com/open-quantum-safe/liboqs', - 'pkgconfig': 'liboqs', - 'purl': lambda v: f'pkg:github/open-quantum-safe/liboqs@{v}', - }, 'libz': { 'name': 'zlib', 'supplier': 'Jean-loup Gailly and Mark Adler', @@ -1078,7 +1065,7 @@ def _resolve_dep_versions(enabled_deps, overrides): value instead of each re-invoking pkg-config. Caching the result (including None) means a later dep_version() lookup short-circuits on the membership check rather than re-shelling to `pkg-config --modversion`, so - a default --with-libz --with-liboqs build calls pkg-config once per dep + a default --with-libz build calls pkg-config once per dep (not once per dep per output format) and the two documents can never disagree if pkg-config output were ever non-deterministic.""" for key in enabled_deps: @@ -1185,10 +1172,6 @@ def main(): 'openssl.pc.') parser.add_argument('--dep-libz', default='no', help='yes if built with --with-libz') - parser.add_argument('--dep-liboqs', default='no', - help='yes if built with --with-liboqs (the package ' - 'wolfSSL links against; --enable-falcon implies ' - 'this in any legal configuration)') parser.add_argument('--dep-version', action='append', default=[], metavar='KEY=VERSION', help='Override pkg-config version detection for a ' @@ -1257,7 +1240,6 @@ def main(): ('wolfssl', args.dep_wolfssl), ('openssl', args.dep_openssl), ('libz', args.dep_libz), - ('liboqs', args.dep_liboqs), ] if flag.lower() == 'yes' ] diff --git a/scripts/test_gen_sbom.py b/scripts/test_gen_sbom.py index 3a38ad9e2b..d46752f83e 100644 --- a/scripts/test_gen_sbom.py +++ b/scripts/test_gen_sbom.py @@ -830,9 +830,9 @@ class TestDepMetaShape(unittest.TestCase): # wolfssl is tracked so downstream wolfSSL-stack products (wolfSSH, # wolfMQTT, ...) can declare it via --dep-wolfssl; openssl so the # OpenSSL-compat products (wolfProvider, wolfEngine) can declare it via - # --dep-openssl; libz/liboqs are wolfSSL's own optional linked deps. + # --dep-openssl; libz is wolfSSL's own optional linked dep. self.assertEqual(set(gs.DEP_META.keys()), - {'wolfssl', 'openssl', 'libz', 'liboqs'}) + {'wolfssl', 'openssl', 'libz'}) def test_wolfssl_dep_entry_describes_the_linked_artefact(self): wolfssl = gs.DEP_META['wolfssl'] @@ -861,22 +861,14 @@ class TestDepMetaShape(unittest.TestCase): openssl['purl']('3.5.0'), 'pkg:github/openssl/openssl@openssl-3.5.0') - def test_liboqs_entry_describes_the_linked_artefact(self): - liboqs = gs.DEP_META['liboqs'] - self.assertEqual(liboqs['name'], 'liboqs') - self.assertEqual(liboqs['supplier'], 'Open Quantum Safe') - self.assertEqual(liboqs['pkgconfig'], 'liboqs') - self.assertEqual( - liboqs['purl']('0.10.0'), - 'pkg:github/open-quantum-safe/liboqs@0.10.0') - def test_no_stale_dep_keys(self): # `falcon` is an algorithm, not a linked package; it must not # appear as a dep entry (algorithm enablement lives in - # build_props parsed from options.h). `libxmss` and `liblms` - # were removed upstream; their re-appearance here would - # silently emit unresolvable identifiers in the SBOM. - for stale in ('falcon', 'libxmss', 'liblms', 'xmss', 'lms'): + # build_props parsed from options.h). `liboqs`, `libxmss` and + # `liblms` were removed upstream (Falcon is now native wolfCrypt, + # no liboqs); their re-appearance here would silently emit + # unresolvable identifiers in the SBOM. + for stale in ('falcon', 'liboqs', 'libxmss', 'liblms', 'xmss', 'lms'): self.assertNotIn(stale, gs.DEP_META) @@ -895,22 +887,21 @@ class TestEnabledDepsCli(unittest.TestCase): capture_output=True, text=True ) - def test_dep_liboqs_is_accepted(self): + def test_dep_flags_are_accepted(self): result = self._run('--help') self.assertEqual(result.returncode, 0, result.stderr) - self.assertIn('--dep-liboqs', result.stdout) self.assertIn('--dep-libz', result.stdout) self.assertIn('--dep-wolfssl', result.stdout) self.assertIn('--dep-openssl', result.stdout) def test_removed_flags_are_rejected(self): - # Each of these was either renamed (--dep-falcon -> --dep-liboqs) - # or removed entirely (--dep-libxmss/--dep-liblms with upstream - # removal of the libraries). argparse should reject them as - # unrecognised, not silently accept them. We pass the full set - # of required args (against /dev/null sentinels) so argparse - # progresses to the unknown-flag check; we never want - # gen-sbom to actually generate anything in this test. + # Each of these was removed: --dep-falcon/--dep-libxmss/--dep-liblms + # (the libraries were removed upstream) and --dep-liboqs (liboqs support + # was removed; Falcon is now provided natively by wolfCrypt). argparse + # should reject them as unrecognised, not silently accept them. We pass + # the full set of required args (against /dev/null sentinels) so argparse + # progresses to the unknown-flag check; we never want gen-sbom to + # actually generate anything in this test. required = [ '--name', 'wolfssl', '--version', '0.0.0-test', @@ -920,9 +911,9 @@ class TestEnabledDepsCli(unittest.TestCase): '--cdx-out', '/dev/null', '--spdx-out', '/dev/null', ] - for stale_flag in ('--dep-falcon', '--dep-libxmss', '--dep-liblms', - '--dep-libxmss-root', '--dep-liblms-root', - '--git'): + for stale_flag in ('--dep-falcon', '--dep-liboqs', '--dep-libxmss', + '--dep-liblms', '--dep-libxmss-root', + '--dep-liblms-root', '--git'): result = self._run(*required, stale_flag, 'no') self.assertNotEqual(result.returncode, 0, f"{stale_flag!r} unexpectedly accepted") @@ -1313,7 +1304,7 @@ class TestDepVersionOverride(unittest.TestCase): self.assertEqual(gs.dep_version('libz'), '1.0.0') self.assertEqual(gs.dep_version('libz', {}), '1.0.0') self.assertEqual( - gs.dep_version('libz', {'liboqs': '0.0'}), '1.0.0') + gs.dep_version('libz', {'openssl': '0.0'}), '1.0.0') finally: gs.pkgconfig_version = original @@ -1327,16 +1318,16 @@ class TestDepVersionOverride(unittest.TestCase): def test_parse_overrides_accepts_known_keys(self): out = gs._parse_dep_version_overrides([ - 'libz=1.3.1', 'liboqs=0.10.0', + 'libz=1.3.1', 'openssl=3.5.0', ]) - self.assertEqual(out, {'libz': '1.3.1', 'liboqs': '0.10.0'}) + self.assertEqual(out, {'libz': '1.3.1', 'openssl': '3.5.0'}) class TestResolveDepVersionsSingleShot(unittest.TestCase): """Each enabled dependency's version must be resolved exactly once (in main, via _resolve_dep_versions), not once per output format. Without the precompute, generate_cdx and generate_spdx each call dep_version() - independently, so a default --with-libz --with-liboqs build would shell + independently, so a build linking libz + openssl would shell out to `pkg-config --modversion` four times (2 deps x CDX+SPDX) instead of twice -- and the two documents could disagree if pkg-config were ever non-deterministic. These tests lock that single-resolution behaviour in.""" @@ -1346,14 +1337,14 @@ class TestResolveDepVersionsSingleShot(unittest.TestCase): original = gs.pkgconfig_version try: gs.pkgconfig_version = lambda pkg: (calls.append(pkg), '1.2.3')[1] - overrides = gs._resolve_dep_versions(['libz', 'liboqs'], {}) + overrides = gs._resolve_dep_versions(['libz', 'openssl'], {}) self.assertEqual(len(calls), 2) self.assertEqual(overrides['libz'], '1.2.3') - self.assertEqual(overrides['liboqs'], '1.2.3') + self.assertEqual(overrides['openssl'], '1.2.3') # The emitters reuse the cached value: a later dep_version() for # an already-resolved key must not re-invoke pkg-config. gs.dep_version('libz', overrides) - gs.dep_version('liboqs', overrides) + gs.dep_version('openssl', overrides) self.assertEqual(len(calls), 2) finally: gs.pkgconfig_version = original @@ -1374,11 +1365,11 @@ class TestResolveDepVersionsSingleShot(unittest.TestCase): original = gs.pkgconfig_version try: gs.pkgconfig_version = lambda pkg: (calls.append(pkg), None)[1] - overrides = gs._resolve_dep_versions(['liboqs'], {}) - self.assertIn('liboqs', overrides) - self.assertIsNone(overrides['liboqs']) + overrides = gs._resolve_dep_versions(['openssl'], {}) + self.assertIn('openssl', overrides) + self.assertIsNone(overrides['openssl']) # A cached None must short-circuit later lookups too. - gs.dep_version('liboqs', overrides) + gs.dep_version('openssl', overrides) self.assertEqual(len(calls), 1) finally: gs.pkgconfig_version = original @@ -1740,7 +1731,7 @@ class TestCdxDepComponent(unittest.TestCase): def test_returns_bomref_and_component(self): # Stub pkgconfig_version so the test does not depend on the - # build host having libz / liboqs installed. + # build host having libz / openssl installed. original = gs.pkgconfig_version try: gs.pkgconfig_version = lambda *_a, **_k: '1.3.1' @@ -1812,7 +1803,7 @@ class TestSpdxDepPackage(unittest.TestCase): original = gs.pkgconfig_version try: gs.pkgconfig_version = lambda *_a, **_k: '0.10.0' - spdx_id, pkg = gs.spdx_dep_package('liboqs') + spdx_id, pkg = gs.spdx_dep_package('openssl') finally: gs.pkgconfig_version = original self.assertTrue(spdx_id.startswith('SPDXRef-Package-')) @@ -1825,7 +1816,7 @@ class TestSpdxDepPackage(unittest.TestCase): _re.match(r'\ASPDXRef-[A-Za-z0-9.-]+\Z', spdx_id), f'invalid SPDXID shape: {spdx_id!r}') self.assertEqual(pkg['SPDXID'], spdx_id) - self.assertEqual(pkg['name'], 'liboqs') + self.assertEqual(pkg['name'], 'openssl') self.assertEqual(pkg['versionInfo'], '0.10.0') self.assertEqual(pkg['filesAnalyzed'], False) # Both license fields must agree; SPDX validators accept @@ -1840,7 +1831,7 @@ class TestSpdxDepPackage(unittest.TestCase): original = gs.pkgconfig_version try: gs.pkgconfig_version = lambda *_a, **_k: None - _, pkg = gs.spdx_dep_package('liboqs') + _, pkg = gs.spdx_dep_package('openssl') finally: gs.pkgconfig_version = original self.assertEqual(pkg['versionInfo'], 'NOASSERTION') @@ -1853,7 +1844,7 @@ class TestSpdxDepPackage(unittest.TestCase): original = gs.pkgconfig_version try: gs.pkgconfig_version = lambda *_a, **_k: '0.10.0' - _, pkg = gs.spdx_dep_package('liboqs') + _, pkg = gs.spdx_dep_package('openssl') finally: gs.pkgconfig_version = original purl_refs = [ @@ -1861,7 +1852,7 @@ class TestSpdxDepPackage(unittest.TestCase): if r.get('referenceType') == 'purl' ] self.assertEqual(len(purl_refs), 1) - self.assertIn('liboqs', purl_refs[0]['referenceLocator']) + self.assertIn('openssl', purl_refs[0]['referenceLocator']) self.assertIn('0.10.0', purl_refs[0]['referenceLocator'])