From 824b3f5cfaee832743efbd0c58b2013418f1ba54 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 11 Jun 2026 17:07:59 -0700 Subject: [PATCH 1/4] Gate wolfCose_EccSignRaw on sign-enable so lean verify-only builds exclude ECC signing --- src/wolfcose.c | 2 ++ src/wolfcose_internal.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/wolfcose.c b/src/wolfcose.c index 0932746..167cccd 100644 --- a/src/wolfcose.c +++ b/src/wolfcose.c @@ -539,6 +539,7 @@ static int wolfCose_HmacCheckKeyLen(int32_t alg, size_t keyLen) /* ----- Internal: ECC DER <-> raw r||s conversion ----- */ #ifdef WOLFCOSE_HAVE_ECDSA +#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, uint8_t* sigBuf, size_t* sigLen, size_t coordSz, WC_RNG* rng, ecc_key* eccKey) @@ -601,6 +602,7 @@ int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, } return ret; } +#endif /* WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN */ int wolfCose_EccVerifyRaw(const uint8_t* sigBuf, size_t sigLen, const uint8_t* hash, size_t hashLen, diff --git a/src/wolfcose_internal.h b/src/wolfcose_internal.h index 8f8d2b9..310f5ba 100644 --- a/src/wolfcose_internal.h +++ b/src/wolfcose_internal.h @@ -292,10 +292,12 @@ WOLFCOSE_LOCAL int wolfCose_HmacType(int32_t alg, int* hmacType); * \param eccKey Caller-owned ECC key with private key. * \return WOLFCOSE_SUCCESS or negative error code. */ +#if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) WOLFCOSE_LOCAL int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, uint8_t* sigBuf, size_t* sigLen, size_t coordSz, WC_RNG* rng, ecc_key* eccKey); +#endif /* WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN */ /** * \brief Verify a raw r||s ECC signature. From 77403e78c051125c218172ac4978819a785aa06b Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 11 Jun 2026 17:17:02 -0700 Subject: [PATCH 2/4] Make ECC raw verify link against NO_ASN verify-only wolfCrypt without mp_int --- src/wolfcose.c | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/wolfcose.c b/src/wolfcose.c index 167cccd..07669ac 100644 --- a/src/wolfcose.c +++ b/src/wolfcose.c @@ -609,10 +609,13 @@ int wolfCose_EccVerifyRaw(const uint8_t* sigBuf, size_t sigLen, size_t coordSz, ecc_key* eccKey, int* verified) { int ret; +#ifndef NO_ASN uint8_t derSig[ECC_MAX_SIG_SIZE]; word32 derSigLen = (word32)sizeof(derSig); +#endif - if ((sigBuf == NULL) || (hash == NULL) || (eccKey == NULL) || (verified == NULL)) { + if ((sigBuf == NULL) || (hash == NULL) || (eccKey == NULL) || + (verified == NULL)) { ret = WOLFCOSE_E_INVALID_ARG; } else if (sigLen != (coordSz * 2u)) { @@ -621,7 +624,20 @@ int wolfCose_EccVerifyRaw(const uint8_t* sigBuf, size_t sigLen, else { *verified = 0; - /* Convert raw r||s to DER */ +#ifdef NO_ASN + /* NO_ASN wolfCrypt (e.g. wolfBoot): wc_ecc_verify_hash consumes the raw + * r||s signature directly, so no DER conversion is needed and the + * sign-side helper wc_ecc_rs_raw_to_sig (gated on ASN) is not required. + * wolfCOSE holds no mp_int itself, keeping the verify path allocation + * free at this layer. */ + INJECT_FAILURE(WOLF_FAIL_ECC_VERIFY, -1, + ret = wc_ecc_verify_hash(sigBuf, (word32)sigLen, hash, + (word32)hashLen, verified, eccKey)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } +#else + /* Convert raw r||s to DER, then verify. */ INJECT_FAILURE(WOLF_FAIL_ECC_RS_TO_SIG, -1, ret = wc_ecc_rs_raw_to_sig(sigBuf, (word32)coordSz, &sigBuf[coordSz], (word32)coordSz, @@ -638,6 +654,7 @@ int wolfCose_EccVerifyRaw(const uint8_t* sigBuf, size_t sigLen, } } (void)wolfCose_ForceZero(derSig, sizeof(derSig)); +#endif } return ret; } From acd83c0291530fb9e75edcf6407b09fc3ee8367a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 12 Jun 2026 08:50:31 -0700 Subject: [PATCH 3/4] Add verify-only ECC CI coverage: NO_ECC_SIGN wolfSSL, no gc-sections so a sign-leak fails to link --- .github/workflows/lean-build.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lean-build.yml b/.github/workflows/lean-build.yml index 20b9a8f..c30b3c2 100644 --- a/.github/workflows/lean-build.yml +++ b/.github/workflows/lean-build.yml @@ -33,10 +33,13 @@ jobs: uses: actions/cache@v4 with: path: ~/wolfssl-lean - key: wolfssl-lean-ecc-v1-${{ steps.wolfssl-rev.outputs.sha }} + key: wolfssl-lean-ecc-verifyonly-v2-${{ steps.wolfssl-rev.outputs.sha }} - # Minimal backend for ES256 verification: ECC + SHA-256 only. - # No keygen and no RNG are needed to verify a COSE_Sign1. + # Verify-only ECC backend: NO_ECC_SIGN compiles ECC signing out entirely, + # mirroring an embedded verify-only target (e.g. wolfBoot). This is the + # config that catches a lean wolfCOSE accidentally pulling in a signing + # helper such as wc_ecc_sign_hash (via wolfCose_EccSignRaw): it links here + # only if the verify path references no sign-side symbols. - name: Build minimal wolfSSL if: steps.cache-wolfssl.outputs.cache-hit != 'true' run: | @@ -45,17 +48,23 @@ jobs: cd wolfssl-lean-src ./autogen.sh ./configure --enable-cryptonly --enable-ecc \ + CFLAGS="-DNO_ECC_SIGN" \ --prefix=$HOME/wolfssl-lean make -j$(nproc) make install + # No --gc-sections here on purpose. Against the NO_ECC_SIGN wolfSSL above, a + # verify path that leaks a signing helper (e.g. wc_ecc_sign_hash via + # wolfCose_EccSignRaw) must fail to link. With dead-code stripping the unused + # helper would be removed and the missing-symbol link error would never fire, + # so the regression would slip through unnoticed. - name: Build and run the lean verify-only example run: | export WOLFSSL_DIR=$HOME/wolfssl-lean export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib make lean-verify \ CFLAGS="-std=c11 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -ffunction-sections -fdata-sections -I./include -isystem $WOLFSSL_DIR/include" \ - LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl -Wl,--gc-sections" + LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" - name: Assert the signing API is absent from a verify-only build run: | From 62316d10449fb403d5f0821030f295331ade8fe7 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Fri, 12 Jun 2026 10:58:35 -0700 Subject: [PATCH 4/4] Add downstream CI building wolfBoot SUIT against this wolfCOSE to catch verify-only regressions --- .../workflows/downstream-wolfboot-suit.yml | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/downstream-wolfboot-suit.yml diff --git a/.github/workflows/downstream-wolfboot-suit.yml b/.github/workflows/downstream-wolfboot-suit.yml new file mode 100644 index 0000000..912a75b --- /dev/null +++ b/.github/workflows/downstream-wolfboot-suit.yml @@ -0,0 +1,67 @@ +# Downstream integration guard: build wolfBoot's SUIT support against THIS +# wolfCOSE and run its tests. wolfBoot is wolfCOSE's flagship lean verify-only +# consumer; the sim build links lean wolfCOSE against a verify-only (NO_ASN, +# NO_ECC_SIGN) wolfCrypt, which is exactly the configuration that surfaces +# verify-only regressions (e.g. a signing helper leaking into a verify build). +# A change to wolfCOSE that breaks wolfBoot fails here instead of in the field. +name: Downstream wolfBoot SUIT + +on: + push: + pull_request: + +jobs: + wolfboot-suit: + name: wolfBoot SUIT lean sim + host test against this wolfCOSE + runs-on: ubuntu-latest + steps: + - name: Checkout wolfCOSE (under test) + uses: actions/checkout@v4 + with: + path: wolfcose-under-test + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential autoconf automake libtool \ + python3 python3-pip + + # wolfBoot carries the SUIT sources + tests. Track the branch until SUIT + # lands on wolfBoot master, then switch this to wolfSSL/wolfBoot. + - name: Checkout wolfBoot (SUIT branch) + wolfSSL submodule + run: | + git clone --depth 1 --branch suit-manifest \ + https://github.com/aidangarske/wolfBoot.git + cd wolfBoot + git submodule update --init --depth 1 lib/wolfssl + rm -rf lib/wolfCOSE + cp -r "$GITHUB_WORKSPACE/wolfcose-under-test" lib/wolfCOSE + + # The critical guard: lean wolfCOSE linked against wolfBoot's verify-only + # wolfCrypt. A verify-only regression in wolfCOSE fails this link. + - name: Build wolfBoot sim WOLFBOOT_SUIT=1 (lean verify-only) + run: | + cd wolfBoot + cp config/examples/sim.config .config + make WOLFBOOT_SUIT=1 SIGN=ECC256 + + - name: Build host wolfSSL (ECC) for the authoring-side host test + run: | + git clone --depth 1 https://github.com/wolfSSL/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-cryptonly --enable-ecc \ + --prefix=$HOME/wolfssl-install + make -j"$(nproc)" + make install + + - name: Host SUIT test (author + verify + install + tamper) + run: | + cd wolfBoot + WOLFSSL_DIR=$HOME/wolfssl-install ./tests/suit_host_test.sh + + - name: Independent cross-check (cbor2 + cryptography) + run: | + pip3 install --quiet cbor2 cryptography + cd wolfBoot + python3 tests/suit_cross_check.py