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
34 changes: 17 additions & 17 deletions .github/workflows/minimal-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,32 @@ jobs:
matrix:
include:
- name: ECC-only
wolfssl_flags: "--enable-cryptonly --enable-ecc --enable-aesgcm --enable-keygen"
cache_key: wolfssl-ecc-only-v4
wolfssl_flags: "--enable-cryptonly --enable-ecc --enable-aesgcm --enable-keygen --enable-sha384 --enable-sha512 --enable-lowresource --enable-sp-math-all --disable-dh --disable-rsa --disable-aescbc --disable-sha --disable-md5 --disable-chacha --disable-poly1305 --disable-errorstrings"
cache_key: wolfssl-ecc-only-v5
- name: EdDSA-only
wolfssl_flags: "--enable-cryptonly --enable-ed25519 --enable-curve25519 --enable-sha512"
cache_key: wolfssl-eddsa-only-v5
wolfssl_flags: "--enable-cryptonly --enable-ed25519 --enable-curve25519 --enable-sha512 --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings"
cache_key: wolfssl-eddsa-only-v6
- name: Ed448-only
wolfssl_flags: "--enable-cryptonly --enable-ed448 --enable-sha512"
cache_key: wolfssl-ed448-only-v2
wolfssl_flags: "--enable-cryptonly --enable-ed448 --enable-sha512 --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings"
cache_key: wolfssl-ed448-only-v3
- name: AEAD-only (no signing)
wolfssl_flags: "--enable-cryptonly --enable-aesgcm --enable-aesccm --enable-chacha --enable-poly1305"
cache_key: wolfssl-aead-only-v5
wolfssl_flags: "--enable-cryptonly --enable-aesgcm --enable-aesccm --enable-chacha --enable-poly1305 --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings"
cache_key: wolfssl-aead-only-v6
- name: RSA-PSS-only
wolfssl_flags: "--enable-cryptonly --enable-rsapss --enable-keygen --enable-sha384 --enable-sha512"
cache_key: wolfssl-rsa-only-v5
wolfssl_flags: "--enable-cryptonly --enable-rsapss --enable-keygen --enable-sha384 --enable-sha512 --enable-lowresource --disable-dh --disable-errorstrings"
cache_key: wolfssl-rsa-only-v6
- name: PQ (ML-DSA) only
wolfssl_flags: "--enable-cryptonly --enable-mldsa"
wolfssl_flags: "--enable-cryptonly --enable-mldsa --disable-dh --disable-rsa --disable-errorstrings"
cache_key: wolfssl-pq-only-v4
- name: ECDH-ES (key agreement)
wolfssl_flags: "--enable-cryptonly --enable-ecc --enable-aesgcm --enable-keygen --enable-hkdf"
cache_key: wolfssl-ecdh-es-v1
wolfssl_flags: "--enable-cryptonly --enable-ecc --enable-aesgcm --enable-keygen --enable-hkdf --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings"
cache_key: wolfssl-ecdh-es-v2
- name: AES Key Wrap
wolfssl_flags: "--enable-cryptonly --enable-ecc --enable-aesgcm --enable-keygen --enable-aeskeywrap"
cache_key: wolfssl-keywrap-v1
wolfssl_flags: "--enable-cryptonly --enable-ecc --enable-aesgcm --enable-keygen --enable-aeskeywrap --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings"
cache_key: wolfssl-keywrap-v2
- name: MAC-only (HMAC + AES-MAC)
wolfssl_flags: "--enable-cryptonly --enable-sha256 --enable-sha384 --enable-sha512 --enable-aescbc"
cache_key: wolfssl-mac-only-v1
wolfssl_flags: "--enable-cryptonly --enable-sha256 --enable-sha384 --enable-sha512 --enable-aescbc --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings"
cache_key: wolfssl-mac-only-v2

steps:
- uses: actions/checkout@v4
Expand Down
111 changes: 111 additions & 0 deletions .github/workflows/stack-bounds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Stack Bounds

on:
push:
branches: [ 'main', 'release/**' ]
pull_request:
branches: [ '*' ]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
frame-budget:
name: Frame budget (full, worst case)
runs-on: ubuntu-latest
env:
FRAME_BUDGET: 6144
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y autoconf automake libtool

- name: Cache wolfSSL (full)
id: cache-wolfssl
uses: actions/cache@v4
with:
path: ~/wolfssl-full
key: wolfssl-full-stack-v1

- name: Build wolfSSL (full)
if: steps.cache-wolfssl.outputs.cache-hit != 'true'
run: |
cd ~
git clone --depth 1 https://github.com/wolfSSL/wolfssl.git wolfssl-full-src
cd wolfssl-full-src
./autogen.sh
./configure --enable-ecc --enable-ed25519 --enable-ed448 \
--enable-curve25519 --enable-aesgcm --enable-aesccm \
--enable-sha384 --enable-sha512 --enable-keygen \
--enable-rsapss --enable-chacha --enable-poly1305 \
--enable-mldsa --enable-hkdf --enable-aeskeywrap \
--prefix=$HOME/wolfssl-full
make -j$(nproc)
make install

- name: Build wolfCOSE (-Werror=vla, -fstack-usage)
run: |
export WOLFSSL_DIR=$HOME/wolfssl-full
make CFLAGS="-std=c11 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wvla -Werror=vla -fstack-usage -I./include -I$WOLFSSL_DIR/include" \
LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl"

- name: Enforce per-frame budget
run: sh scripts/check_stack_usage.sh "$FRAME_BUDGET"

- name: Negative test — SMALL_FOOTPRINT must reject ML-DSA build
run: |
export WOLFSSL_DIR=$HOME/wolfssl-full
make clean
if make CFLAGS="-std=c11 -Os -DWOLFCOSE_SMALL_FOOTPRINT -I./include -I$WOLFSSL_DIR/include" \
LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" 2>guard.log; then
echo "ERROR: SMALL_FOOTPRINT + ML-DSA should fail to compile"; exit 1
fi
grep -q "WOLFCOSE_SMALL_FOOTPRINT clamps buffers below ML-DSA" guard.log
echo "OK: SMALL_FOOTPRINT guard fired"

small-footprint:
name: SMALL_FOOTPRINT (ECC-only)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y autoconf automake libtool

- name: Cache wolfSSL (ECC-only)
id: cache-wolfssl
uses: actions/cache@v4
with:
path: ~/wolfssl-ecc
key: wolfssl-ecc-smallfp-v2

- name: Build wolfSSL (ECC-only, stripped)
if: steps.cache-wolfssl.outputs.cache-hit != 'true'
run: |
cd ~
git clone --depth 1 https://github.com/wolfSSL/wolfssl.git wolfssl-ecc-src
cd wolfssl-ecc-src
./autogen.sh
./configure --enable-cryptonly --enable-ecc --enable-aesgcm \
--enable-keygen --enable-sha384 --enable-sha512 \
--enable-lowresource --enable-sp-math-all \
--disable-dh --disable-rsa --disable-aescbc \
--disable-sha --disable-md5 --disable-chacha --disable-poly1305 \
--disable-errorstrings --prefix=$HOME/wolfssl-ecc
make -j$(nproc)
make install

- name: Build + test wolfCOSE (-DWOLFCOSE_SMALL_FOOTPRINT)
run: |
export WOLFSSL_DIR=$HOME/wolfssl-ecc
export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib
FLAGS="-std=c11 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wvla -Werror=vla -DWOLFCOSE_SMALL_FOOTPRINT -I./include -I$WOLFSSL_DIR/include"
make CFLAGS="$FLAGS" LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl"
make test CFLAGS="$FLAGS" LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl"
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
CC ?= gcc
AR ?= ar
CFLAGS = -std=c99 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion
CFLAGS += -Wvla -Werror=vla
CFLAGS += -ffunction-sections -fdata-sections
CFLAGS += -fstack-usage
# Match wolfSSL's default (gnu11) struct ABI; -std=c99 alone disables
# HAVE_ANONYMOUS_INLINE_AGGREGATES and shrinks WC_RNG, corrupting the RNG.
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,21 @@ sudo ldconfig

**Algorithms enabled:** ES256, ES384, ES512, AES-GCM-128/192/256

For a smaller wolfCrypt footprint, add `--enable-cryptonly` to drop the TLS
stack and disable the algorithms a Sign1 + Encrypt0 build never uses:

```bash
./configure --enable-cryptonly --enable-ecc --enable-aesgcm \
--enable-sha384 --enable-sha512 --enable-keygen \
--enable-lowresource \
--disable-dh --disable-rsa --disable-aescbc \
--disable-sha --disable-md5 --disable-chacha --disable-poly1305 \
--disable-errorstrings
```

See [Tuning for Constrained Targets](docs/Macros.md#tuning-for-constrained-targets)
for squeezing wolfCrypt further on MCUs.

### Minimal Build (Post-Quantum / ML-DSA only)

For pure post-quantum signing with ML-DSA-44/65/87:
Expand Down
34 changes: 34 additions & 0 deletions docs/Macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ wolfCOSE uses an opt-out design: all features are enabled by default, and you di
| `WOLFCOSE_MAX_SCRATCH_SZ` | Scratch buffer size for Sig_structure/Enc_structure | 512 |
| `WOLFCOSE_PROTECTED_HDR_MAX` | Max protected header size | 64 |
| `WOLFCOSE_CBOR_MAX_DEPTH` | Max CBOR nesting depth | 8 |
| `WOLFCOSE_SMALL_FOOTPRINT` | Clamp all working buffers to the ECC/EdDSA floor | - |

### `WOLFCOSE_SMALL_FOOTPRINT`

One define that clamps the caller working set to the ECC/EdDSA floor for constrained targets: `WOLFCOSE_MAX_SCRATCH_SZ` 512, `WOLFCOSE_MAX_SIG_SZ` 132, `WOLFCOSE_CBOR_MAX_DEPTH` 6, `WOLFCOSE_MAX_MAP_ITEMS` 8. It stays zero-heap and shrinks buffers, not stack frames. An explicit `-D` override of any individual limit takes precedence. Because the clamped buffers cannot hold ML-DSA (or RSA-PSS) outputs, combining `WOLFCOSE_SMALL_FOOTPRINT` with `WOLFSSL_HAVE_MLDSA` (or `WC_RSA_PSS`) is a compile error unless you also raise `WOLFCOSE_MAX_SIG_SZ`/`WOLFCOSE_MAX_SCRATCH_SZ`.

### Tuning for Constrained Targets

Expand All @@ -138,6 +143,35 @@ wolfCOSE uses an opt-out design: all features are enabled by default, and you di
/* #define WOLFCOSE_MAX_SIG_SZ 4627 */
```

#### Squeezing wolfCrypt smaller for MCUs

The buffers above are wolfCOSE's working set; the wolfCrypt backend has its own
size levers. For a constrained microcontroller target, add `--enable-sp-math-all`
to the [Minimal Build](../README.md#minimal-build-ecc--aes-gcm) configure flags
and set the size defines through `CPPFLAGS`. These keep the build zero-heap.

```bash
CPPFLAGS="-DWOLFSSL_SP_SMALL -DWOLFSSL_AES_SMALL_TABLES" \
./configure --enable-cryptonly --enable-ecc --enable-aesgcm \
--enable-sha384 --enable-sha512 --enable-keygen \
--enable-lowresource --enable-sp-math-all \
--disable-dh --disable-rsa --disable-aescbc \
--disable-sha --disable-md5 --disable-chacha --disable-poly1305 \
--disable-errorstrings
```

`--enable-sp-math-all` with `WOLFSSL_SP_SMALL` replaces the large general
big-number math with the compact single-precision implementation, which is
smaller in flash and uses smaller crypto-math stack frames. `WOLFSSL_AES_SMALL_TABLES`
removes the precomputed AES tables, saving about 10 KB of flash at a small speed
cost. Setting these through `CPPFLAGS` keeps wolfSSL's own optimization flags
intact. Build your application with link-time dead-code removal so only the COSE
functions you call are kept:

```bash
-Os -ffunction-sections -fdata-sections -Wl,--gc-sections
```

---

## Example Build Configurations
Expand Down
53 changes: 48 additions & 5 deletions include/wolfcose/wolfcose.h
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,16 @@ extern "C" {
#define WOLFCOSE_E_MAC_FAIL (-9022)
#define WOLFCOSE_E_DETACHED_PAYLOAD (-9023)

/* ----- Configurable limits ----- */
/* ----- Configurable limits -----
* Precedence: explicit -D > WOLFCOSE_SMALL_FOOTPRINT > algorithm default.
* WOLFCOSE_SMALL_FOOTPRINT clamps the caller working set to the ECC/EdDSA
* floor for constrained targets. It stays zero-heap and does not move crypto
* structs off the stack. It shrinks buffers, not frames. See the wolfCOSE
* wiki Memory Model page. */
#ifndef WOLFCOSE_MAX_SCRATCH_SZ
#if defined(WOLFSSL_HAVE_MLDSA)
#if defined(WOLFCOSE_SMALL_FOOTPRINT)
#define WOLFCOSE_MAX_SCRATCH_SZ 512u
#elif defined(WOLFSSL_HAVE_MLDSA)
#define WOLFCOSE_MAX_SCRATCH_SZ 8192u
#else
#define WOLFCOSE_MAX_SCRATCH_SZ 512u
Expand All @@ -240,13 +247,23 @@ extern "C" {
#define WOLFCOSE_PROTECTED_HDR_MAX 64u
#endif
#ifndef WOLFCOSE_CBOR_MAX_DEPTH
#define WOLFCOSE_CBOR_MAX_DEPTH 8u
#if defined(WOLFCOSE_SMALL_FOOTPRINT)
#define WOLFCOSE_CBOR_MAX_DEPTH 6u
#else
#define WOLFCOSE_CBOR_MAX_DEPTH 8u
#endif
#endif
#ifndef WOLFCOSE_MAX_MAP_ITEMS
#define WOLFCOSE_MAX_MAP_ITEMS 16u
#if defined(WOLFCOSE_SMALL_FOOTPRINT)
#define WOLFCOSE_MAX_MAP_ITEMS 8u
#else
#define WOLFCOSE_MAX_MAP_ITEMS 16u
#endif
#endif
#ifndef WOLFCOSE_MAX_SIG_SZ
#if defined(WOLFSSL_HAVE_MLDSA)
#if defined(WOLFCOSE_SMALL_FOOTPRINT)
#define WOLFCOSE_MAX_SIG_SZ 132u
#elif defined(WOLFSSL_HAVE_MLDSA)
#define WOLFCOSE_MAX_SIG_SZ 4627u
#elif defined(WC_RSA_PSS)
#define WOLFCOSE_MAX_SIG_SZ 512u
Expand All @@ -255,6 +272,32 @@ extern "C" {
#endif
#endif

/* WOLFCOSE_SMALL_FOOTPRINT must not silently undersize an enabled algorithm.
* Fire only when the resolved limits are too small, so an explicit -D override
* that raises them is honored. */
#if defined(WOLFCOSE_SMALL_FOOTPRINT) && defined(WOLFSSL_HAVE_MLDSA) && \
((WOLFCOSE_MAX_SIG_SZ < 4627u) || (WOLFCOSE_MAX_SCRATCH_SZ < 8192u))
#error "WOLFCOSE_SMALL_FOOTPRINT clamps buffers below ML-DSA sizes; raise WOLFCOSE_MAX_SIG_SZ (>=4627) and WOLFCOSE_MAX_SCRATCH_SZ (>=8192) to use both."
#endif
#if defined(WOLFCOSE_SMALL_FOOTPRINT) && defined(WC_RSA_PSS) && \
(WOLFCOSE_MAX_SIG_SZ < 512u)
#error "WOLFCOSE_SMALL_FOOTPRINT clamps WOLFCOSE_MAX_SIG_SZ below RSA-PSS sizes; raise WOLFCOSE_MAX_SIG_SZ (>=512) to use both."
#endif

/* Floor checks: an override below the structural minimum is a build error. */
#if WOLFCOSE_MAX_SIG_SZ < 132u
#error "WOLFCOSE_MAX_SIG_SZ below 132 cannot hold an ES256/EdDSA signature"
#endif
#if WOLFCOSE_MAX_SCRATCH_SZ < 256u
#error "WOLFCOSE_MAX_SCRATCH_SZ below 256 is too small for COSE structures"
#endif
#if WOLFCOSE_CBOR_MAX_DEPTH < 4u
#error "WOLFCOSE_CBOR_MAX_DEPTH below 4 cannot parse nested COSE messages"
#endif
#if WOLFCOSE_MAX_MAP_ITEMS < 4u
#error "WOLFCOSE_MAX_MAP_ITEMS below 4 is too small for COSE headers"
#endif

/* ----- CBOR constants (RFC 8949) ----- */

/* Major types (top 3 bits of initial byte) */
Expand Down
32 changes: 32 additions & 0 deletions scripts/check_stack_usage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/sh
# Fail if any wolfCOSE stack frame exceeds the byte budget (default 6144).
# Frames are bounded constants; absence of VLAs/alloca is enforced separately
# by -Werror=vla in the Makefile. Requires a prior build with -fstack-usage.
set -e

BUDGET="${1:-6144}"
SU="src/wolfcose.su src/wolfcose_cbor.su"

for f in $SU; do
if [ ! -f "$f" ]; then
echo "missing $f — build with -fstack-usage first" >&2
exit 2
fi
done

# Flag unbounded frames (qualifier exactly "dynamic", not "dynamic,bounded" or
# "static") regardless of the printed size, plus any frame over budget.
over=$(awk -F'\t' -v b="$BUDGET" '
$3 == "dynamic" { print " " $1 " UNBOUNDED (dynamic)"; next }
$2 + 0 > b { print " " $1 " " $2 " bytes" }
' $SU)

if [ -n "$over" ]; then
echo "FAIL: stack frames over ${BUDGET} bytes:"
echo "$over"
exit 1
fi

echo "PASS: all wolfCOSE stack frames within ${BUDGET} bytes"
echo "Largest frames:"
sort -t " " -k2 -n -r $SU | head -5 | awk -F'\t' '{ print " " $1 " " $2 " bytes" }'
2 changes: 2 additions & 0 deletions src/wolfcose.c
Original file line number Diff line number Diff line change
Expand Up @@ -5624,6 +5624,7 @@ static int wolfCose_IsHmacAlg(int32_t alg)
) ? 1 : 0;
}

#ifdef HAVE_AES_CBC
/**
* Check if algorithm is AES-CBC-MAC based.
*/
Expand All @@ -5634,6 +5635,7 @@ static int wolfCose_IsAesCbcMacAlg(int32_t alg)
(alg == WOLFCOSE_ALG_AES_MAC_128_128) ||
(alg == WOLFCOSE_ALG_AES_MAC_256_128)) ? 1 : 0;
}
#endif /* HAVE_AES_CBC */

#if defined(WOLFCOSE_MAC0_CREATE)
int wc_CoseMac0_Create(const WOLFCOSE_KEY* key, int32_t alg,
Expand Down
7 changes: 3 additions & 4 deletions tests/test_cose.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ static int g_failures = 0;

#define TEST_ASSERT(cond, name) do { \
if (!(cond)) { \
TEST_LOG(" FAIL: %s (line %d)\n", (name), __LINE__); \
(void)printf(" FAIL: %s (line %d)\n", (name), __LINE__); \
g_failures++; \
} else { \
TEST_LOG(" PASS: %s\n", (name)); \
Expand Down Expand Up @@ -3947,8 +3947,7 @@ static int mutate_first_recipient_protected_alg(uint8_t* msg, size_t msgLen,
{
int ret = -1;
WOLFCOSE_CBOR_CTX ctx;
size_t count = 0;
uint64_t tag = 0;
uint64_t count = 0;
const uint8_t* protectedData = NULL;
size_t protectedLen = 0;
size_t protectedOffset = 0u;
Expand All @@ -3959,7 +3958,7 @@ static int mutate_first_recipient_protected_alg(uint8_t* msg, size_t msgLen,

if ((ctx.idx < ctx.bufSz) &&
(wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TAG)) {
ret = wc_CBOR_DecodeTag(&ctx, &tag);
ret = wc_CBOR_DecodeTag(&ctx, &count);
}
else {
ret = 0;
Expand Down
Loading
Loading