From 824b3f5cfaee832743efbd0c58b2013418f1ba54 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 11 Jun 2026 17:07:59 -0700 Subject: [PATCH 1/3] 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/3] 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/3] 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: |