diff --git a/.github/workflows/cmake-build.yml b/.github/workflows/cmake-build.yml index 0fef6258..94f43767 100644 --- a/.github/workflows/cmake-build.yml +++ b/.github/workflows/cmake-build.yml @@ -5,6 +5,8 @@ on: branches: [ 'master', 'main', 'release/**' ] pull_request: branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] jobs: build: diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 9b3ac250..e570d586 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -5,6 +5,8 @@ on: branches: [ 'master', 'main', 'release/**' ] pull_request: branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/fwtpm-test.yml b/.github/workflows/fwtpm-test.yml index 757149fe..4714d491 100644 --- a/.github/workflows/fwtpm-test.yml +++ b/.github/workflows/fwtpm-test.yml @@ -5,6 +5,8 @@ on: branches: [ 'master', 'main', 'release/**' ] pull_request: branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] jobs: # ---------------------------------------------------------------- diff --git a/.github/workflows/make-test-swtpm.yml b/.github/workflows/make-test-swtpm.yml index e7988e85..eb7c6fcc 100644 --- a/.github/workflows/make-test-swtpm.yml +++ b/.github/workflows/make-test-swtpm.yml @@ -5,6 +5,8 @@ on: branches: [ 'master', 'main', 'release/**' ] pull_request: branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] jobs: build: diff --git a/.github/workflows/multi-compiler.yml b/.github/workflows/multi-compiler.yml index 89f0d0eb..31116558 100644 --- a/.github/workflows/multi-compiler.yml +++ b/.github/workflows/multi-compiler.yml @@ -5,6 +5,8 @@ on: branches: [ 'master', 'main', 'release/**' ] pull_request: branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 00000000..e58d0ff5 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,40 @@ +name: Nightly CI + +# Daily fan-out to every wolfTPM workflow that listens for the +# 'nightly-trigger' repository_dispatch event. Catches upstream wolfSSL +# drift (e.g. header renames, API breaks) within ~24h. +# +# Workflows opt in by adding the listener to their `on:` block: +# repository_dispatch: +# types: [nightly-trigger] +# +# repository_dispatch is API-only — there is no UI button to trigger +# these workflows by hand. Maintainers cannot accidentally fire them. +# Workflows that should never be batch-triggered (e.g. hw-spdm-test on +# the self-hosted Pi runner) simply don't add the listener. + +on: + schedule: + # 02:00 UTC daily (offset from typical top-of-hour congestion). + - cron: '17 2 * * *' + +permissions: + contents: write + +jobs: + fan-out: + name: Dispatch nightly-trigger to all listening workflows + runs-on: ubuntu-latest + if: github.repository == 'wolfSSL/wolfTPM' + + steps: + - name: Send repository_dispatch event + uses: actions/github-script@v7 + with: + script: | + await github.rest.repos.createDispatchEvent({ + owner: context.repo.owner, + repo: context.repo.repo, + event_type: 'nightly-trigger', + }); + core.info('Dispatched nightly-trigger; all listening workflows will fire.'); diff --git a/.github/workflows/pqc-examples.yml b/.github/workflows/pqc-examples.yml index 468a3e1f..183bcfc7 100644 --- a/.github/workflows/pqc-examples.yml +++ b/.github/workflows/pqc-examples.yml @@ -5,6 +5,8 @@ on: branches: [ 'master', 'main', 'release/**' ] pull_request: branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] jobs: pqc-examples: diff --git a/.github/workflows/release-checks.yml b/.github/workflows/release-checks.yml index 8bcc1c9a..ef3f004d 100644 --- a/.github/workflows/release-checks.yml +++ b/.github/workflows/release-checks.yml @@ -11,6 +11,8 @@ on: branches: [ 'master', 'main', 'release/**', 'rel_v*_prep' ] pull_request: branches: [ '**' ] + repository_dispatch: + types: [nightly-trigger] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index 1d66d977..dc747e1c 100644 --- a/.github/workflows/sanitizer.yml +++ b/.github/workflows/sanitizer.yml @@ -5,6 +5,8 @@ on: branches: [ 'master', 'main', 'release/**' ] pull_request: branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/seal-test.yml b/.github/workflows/seal-test.yml index 95e0d34c..9da55af9 100644 --- a/.github/workflows/seal-test.yml +++ b/.github/workflows/seal-test.yml @@ -19,6 +19,8 @@ on: - 'src/tpm2_wrap.c' - 'src/fwtpm/**' - 'wolftpm/tpm2_wrap.h' + repository_dispatch: + types: [nightly-trigger] jobs: seal-test: diff --git a/.github/workflows/win-test.yml b/.github/workflows/win-test.yml index 5b48dcb1..1c73502e 100644 --- a/.github/workflows/win-test.yml +++ b/.github/workflows/win-test.yml @@ -5,6 +5,8 @@ on: branches: [ 'master', 'main', 'release/**' ] pull_request: branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] jobs: build: diff --git a/.github/workflows/wolfssl-versions-pqc.yml b/.github/workflows/wolfssl-versions-pqc.yml new file mode 100644 index 00000000..958a3881 --- /dev/null +++ b/.github/workflows/wolfssl-versions-pqc.yml @@ -0,0 +1,97 @@ +name: wolfSSL Version Matrix (PQC) + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + pqc-build-test: + name: wolfSSL ${{ matrix.wolfssl-version }} + runs-on: ubuntu-latest + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + include: + # Lowest supported: exercises all version-gated workarounds in + # fwtpm_crypto.c (RSA cast, H-set, wc_ForceZero shim). + - wolfssl-version: 'v5.8.0-stable' + wolfssl-ref: 'v5.8.0-stable' + cache-key: 'wolfssl-pqc-v5.8.0-v1' + # Latest stable: workarounds gated off via VERSION_HEX. + - wolfssl-version: 'v5.9.1-stable' + wolfssl-ref: 'v5.9.1-stable' + cache-key: 'wolfssl-pqc-v5.9.1-v1' + # master always rebuilds (no cache) so wolfSSL upstream renames / + # API breaks surface within ~24h on the next scheduled run. + - wolfssl-version: 'master' + wolfssl-ref: 'master' + + steps: + - name: Checkout wolfTPM + uses: actions/checkout@v4 + + - name: Install build deps + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake libtool + + - name: Cache wolfSSL ${{ matrix.wolfssl-version }} + if: matrix.wolfssl-version != 'master' + id: cache-wolfssl + uses: actions/cache@v4 + with: + path: ~/wolfssl-install + key: ${{ matrix.cache-key }} + + - name: Build wolfSSL ${{ matrix.wolfssl-version }} with PQC + if: matrix.wolfssl-version == 'master' || steps.cache-wolfssl.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 --branch ${{ matrix.wolfssl-ref }} \ + https://github.com/wolfSSL/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-wolftpm --enable-pkcallbacks --enable-keygen \ + --enable-dilithium --enable-mlkem --enable-experimental \ + --enable-harden CFLAGS="-DWC_RSA_NO_PADDING" \ + --prefix=$HOME/wolfssl-install + make -j"$(nproc)" + make install + + - name: wolfSSL version info + run: | + grep LIBWOLFSSL_VERSION_STRING $HOME/wolfssl-install/include/wolfssl/version.h + grep LIBWOLFSSL_VERSION_HEX $HOME/wolfssl-install/include/wolfssl/version.h + + - name: Build wolfTPM with v1.85 + fwTPM + run: | + ./autogen.sh + CPPFLAGS="-I$HOME/wolfssl-install/include" \ + LDFLAGS="-L$HOME/wolfssl-install/lib -Wl,-rpath,$HOME/wolfssl-install/lib" \ + ./configure --enable-v185 --enable-fwtpm --enable-debug=verbose + make -j"$(nproc)" + + - name: Run fwtpm_unit.test (PQC KAT block) + run: | + export LD_LIBRARY_PATH=$HOME/wolfssl-install/lib + ./tests/fwtpm_unit.test + + - name: Upload failure logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: wolfssl-versions-${{ matrix.wolfssl-version }}-logs + path: | + config.log + tests/*.log + test-suite.log + retention-days: 5 diff --git a/.github/workflows/zephyr.yml b/.github/workflows/zephyr.yml index 080b4af0..75b3e6f9 100644 --- a/.github/workflows/zephyr.yml +++ b/.github/workflows/zephyr.yml @@ -5,6 +5,8 @@ on: branches: [ 'master', 'main', 'release/**' ] pull_request: branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] jobs: run_test: diff --git a/configure.ac b/configure.ac index 0b39dec3..a00fb7ca 100644 --- a/configure.ac +++ b/configure.ac @@ -714,11 +714,11 @@ else test "x$ENABLED_WOLFCRYPT" = "xyes" then # Probe the actual symbols, not just the headers. wolfSSL ships - # dilithium.h / mlkem.h even without the implementation compiled - # (function decls are gated behind HAVE_DILITHIUM / HAVE_MLKEM - # which only get defined via wolfssl/options.h after the right - # --enable-* flags). Include options.h first so the gate is set - # before the header decls are parsed. + # dilithium.h and wc_mlkem.h even without the implementation + # compiled (function decls are gated behind HAVE_DILITHIUM / + # HAVE_MLKEM which only get defined via wolfssl/options.h after + # the right --enable-* flags). Include options.h first so the + # gate is set before the header decls are parsed. AC_CHECK_DECL([wc_dilithium_init], [WOLFTPM_HAVE_DILITHIUM_FN=yes], [WOLFTPM_HAVE_DILITHIUM_FN=no], @@ -728,7 +728,7 @@ else [WOLFTPM_HAVE_MLKEM_FN=yes], [WOLFTPM_HAVE_MLKEM_FN=no], [[#include - #include ]]) + #include ]]) if test "x$WOLFTPM_HAVE_DILITHIUM_FN" = "xyes" && \ test "x$WOLFTPM_HAVE_MLKEM_FN" = "xyes" then @@ -754,9 +754,22 @@ then [[#include #include ]]) AC_CHECK_DECL([wc_MlKemKey_Init], [], - [AC_MSG_ERROR([--enable-v185/--enable-pqc requires wolfSSL built with --enable-mlkem --enable-experimental])], + [AC_MSG_ERROR([--enable-v185/--enable-pqc requires wolfSSL >= v5.8.0-stable built with --enable-mlkem --enable-experimental])], [[#include - #include ]]) + #include ]]) + + AC_MSG_CHECKING([wolfSSL version >= v5.8.0 for PQC]) + AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ + #include + #if LIBWOLFSSL_VERSION_HEX < 0x05008000 + #error "wolfSSL < v5.8.0" + #endif + int main(void) { return 0; } + ]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AC_MSG_ERROR([wolfTPM PQC requires wolfSSL >= v5.8.0-stable. Please upgrade wolfSSL.])]) + AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_V185" fi AM_CONDITIONAL([BUILD_V185], [test "x$ENABLED_V185" = "xyes"]) diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index 68695992..31d818ca 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -60,8 +60,9 @@ #endif #include #ifdef WOLFTPM_V185 +#include #include -#include +#include #endif /* ================================================================== */ @@ -1003,6 +1004,27 @@ TPM_RC FwDecapsulateMlkem(TPMI_MLKEM_PARAMETER_SET parameterSet, if (rc == 0 && ssSz > sizeof(sharedSecretOut->buffer)) { rc = TPM_RC_SIZE; } +#if LIBWOLFSSL_VERSION_HEX < 0x05009000 + /* Workaround: wolfSSL < v5.9.0 Decapsulate does not compute H (hash of + * public key) when the key was generated from a seed via MakeKey. Encode + * the public key once to trigger H caching before Decapsulate. */ + if (rc == 0) { + word32 pubLen = 0; + wcRet = wc_MlKemKey_PublicKeySize(mlkemKey, &pubLen); + if (wcRet == 0 && pubLen > 0) { + byte tmpPub[WC_ML_KEM_MAX_PUBLIC_KEY_SIZE]; + if (pubLen <= sizeof(tmpPub)) { + wcRet = wc_MlKemKey_EncodePublicKey(mlkemKey, tmpPub, pubLen); + } + else { + wcRet = BUFFER_E; + } + } + if (wcRet != 0) { + rc = TPM_RC_FAILURE; + } + } +#endif if (rc == 0) { wcRet = wc_MlKemKey_Decapsulate(mlkemKey, sharedSecretOut->buffer, ctBuf, ctSize); @@ -3588,8 +3610,11 @@ TPM_RC FwVerifySignatureCore(FWTPM_Object* obj, sig->signature.rsassa.hash); int mgf = FwGetMgfType(sig->signature.rsassa.hash); FWTPM_ALLOC_BUF(decSig, FWTPM_MAX_PUB_BUF); + /* Cast: wc_RsaPSS_VerifyCheck took byte* (non-const) in + * wolfSSL <= v5.8.0; the input buffer is const here. + * v5.8.4+ accepts const byte* and the cast is a no-op. */ wcRc = wc_RsaPSS_VerifyCheck( - sig->signature.rsapss.sig.buffer, + (byte*)(uintptr_t)sig->signature.rsapss.sig.buffer, sig->signature.rsapss.sig.size, decSig, (word32)FWTPM_MAX_PUB_BUF, digest, digestSz, diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 1edafc2d..fd41133d 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -2285,14 +2285,10 @@ static int wolfTPM2_EncryptSecret_RSA(WOLFTPM2_DEV* dev, const WOLFTPM2_KEY* tpm #if defined(WOLFTPM_V185) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \ (defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_KYBER512) || \ defined(WOLFSSL_KYBER768) || defined(WOLFSSL_KYBER1024)) -#include -/* mlkem.h only forward-declares struct MlKemKey; pull the impl header - * for the full struct so callers can stack-allocate. Pattern mirrors - * wolfssl/wolfcrypt/cryptocb.h. */ -#ifdef WOLFSSL_WC_MLKEM - #include -#elif defined(HAVE_LIBOQS) +#if defined(HAVE_LIBOQS) #include +#else + #include #endif /* ML-KEM session-salt path per TCG TPM 2.0 Library v1.85 Part 1 Sec.24 diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index cc83b4f1..573fb9b5 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -2920,7 +2920,7 @@ static void test_fwtpm_mldsa_sequence_roundtrip(void) #include "pqc_kat_vectors.h" #include -#include +#include /* Layer A: wolfCrypt-only verify against NIST ACVP MLDSA-44 pinned vector. */ static void test_fwtpm_mldsa_nist_kat_verify(void) diff --git a/wolftpm/tpm2_types.h b/wolftpm/tpm2_types.h index b715eb41..b9a8c975 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -150,6 +150,16 @@ typedef int64_t INT64; /* The wc_HashFree was added in v3.15.4, so use stub to allow building */ #define wc_HashFree(h, t) (0) #endif + #if defined(LIBWOLFSSL_VERSION_HEX) && LIBWOLFSSL_VERSION_HEX < 0x05008004 + /* wc_ForceZero was first declared in wolfssl/wolfcrypt/memory.h at + * v5.8.4. Mirror the upstream byte-wise volatile zero so older + * wolfSSL releases still build wolfTPM. */ + static WC_INLINE void wc_ForceZero(void* mem, size_t len) + { + volatile byte* z = (volatile byte*)mem; + while (len--) *z++ = 0; + } + #endif #define ENCODING_TYPE_PEM 1 /* CTC_FILETYPE_PEM */ #define ENCODING_TYPE_ASN1 2 /* CTC_FILETYPE_ASN1 */