Skip to content
Merged
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
38 changes: 19 additions & 19 deletions .github/workflows/minimal-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,35 +20,35 @@ 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
- name: Lean core (WOLFCOSE_LEAN, minimal wolfSSL)
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
cose_flags: "-DWOLFCOSE_LEAN"

steps:
Expand Down
109 changes: 109 additions & 0 deletions .github/workflows/stack-bounds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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: SMALL_FOOTPRINT + ML-DSA builds and tests (algorithm-aware floor)
run: |
export WOLFSSL_DIR=$HOME/wolfssl-full
export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib
make clean
FLAGS="-std=c11 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -DWOLFCOSE_MIN_BUFFERS -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"

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_MIN_BUFFERS)
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_MIN_BUFFERS -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
25 changes: 25 additions & 0 deletions docs/Macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,19 @@ Resolved internally as read-only `WOLFCOSE_KEY_WRAP`, `WOLFCOSE_ECDH`, and `WOLF
| `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_MIN_BUFFERS` | Trim the working set to the minimum that fits the enabled algorithms | - |

### `WOLFCOSE_MIN_BUFFERS`

One define that trims the caller working set to the minimum that still fits the enabled algorithms. It tightens the CBOR parsing limits (`WOLFCOSE_CBOR_MAX_DEPTH` 8→6, `WOLFCOSE_MAX_MAP_ITEMS` 16→8) and keeps the algorithm-driven signature/scratch floors, which track the largest enabled signature algorithm:

| Enabled signature algorithm | `WOLFCOSE_MAX_SIG_SZ` | `WOLFCOSE_MAX_SCRATCH_SZ` |
|---|---|---|
| ES256/384/512, EdDSA (Ed25519/Ed448) | 132 | 512 |
| RSA-PSS (PS256/384/512) | 512 | 512 |
| ML-DSA-44/65/87 | 4627 | 8192 |

Because the floor follows the algorithm, `WOLFCOSE_MIN_BUFFERS` stays valid with any algorithm — ML-DSA and RSA-PSS simply use that algorithm's floor rather than the ECC floor (ML-DSA-87's 4627-byte signature is the largest wolfCOSE supports). It stays zero-heap and shrinks buffers, not stack frames. An explicit `-D` override of any individual limit takes precedence.

### Tuning for Constrained Targets

Expand All @@ -170,6 +183,18 @@ Resolved internally as read-only `WOLFCOSE_KEY_WRAP`, `WOLFCOSE_ECDH`, and `WOLF
/* #define WOLFCOSE_MAX_SIG_SZ 4627 */
```

#### Tuning the wolfCrypt backend

The limits above are wolfCOSE's working set. Shrinking the wolfCrypt backend
itself — big-number math, AES tables, flash and stack footprint — is a wolfSSL
build concern, not a wolfCOSE one. See the
[wolfSSL Tuning Guide](https://www.wolfssl.com/documentation/manuals/wolfssl-tuning-guide/index.html)
and the [wolfSSL Manual](https://www.wolfssl.com/documentation/manuals/wolfssl/)
for the relevant options (e.g. `--enable-sp-math-all`, `WOLFSSL_SP_SMALL`,
`WOLFSSL_AES_SMALL_TABLES`). Build your application with
`-ffunction-sections -fdata-sections -Wl,--gc-sections` so only the COSE
functions you call are linked.

---

## Example Build Configurations
Expand Down
9 changes: 9 additions & 0 deletions docs/Testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ wolfCOSE runs the following CI checks on every push and pull request:
4. **Scenario Examples**: Real-world workflow tests
5. **Tool Tests**: CLI round-trip tests (17 algorithms)

### Memory and Stack Bounds

wolfCOSE is zero-heap (no `malloc`/`XMALLOC` on any path) and bounded-stack, both enforced in CI:

- **Bounded stack**: built with `-fstack-usage`, then `scripts/check_stack_usage.sh` fails the build if any wolfCOSE frame exceeds 6144 bytes or is `dynamic` (unbounded); `-Werror=vla` bans VLAs/`alloca`.
- **Zero heap**: sources, tests, tools, and examples are grepped for allocator calls.
- **`WOLFCOSE_MIN_BUFFERS`**: constrained-target profile that shrinks the caller working buffers (not the library frames) — see [[Macros]].
- **Minimal Build matrix**: builds and tests against single-purpose minimal wolfCrypt configs (ECC-only, EdDSA-only, AEAD-only, MAC-only, …) plus a `WOLFCOSE_LEAN` core build.

### Static Analysis

| Tool | Purpose |
Expand Down
43 changes: 33 additions & 10 deletions include/wolfcose/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,23 +376,15 @@ extern "C" {
#error "WOLFCOSE_NO_CBOR_DECODE conflicts with an enabled decode operation"
#endif

/* === Configurable limits === */
/* === Configurable limits (precedence: -D > WOLFCOSE_MIN_BUFFERS > default) ===
* Floors track the largest enabled signature algorithm. See docs/Macros.md. */
#ifndef WOLFCOSE_MAX_SCRATCH_SZ
#if defined(WOLFCOSE_HAVE_MLDSA)
#define WOLFCOSE_MAX_SCRATCH_SZ 8192u
#else
#define WOLFCOSE_MAX_SCRATCH_SZ 512u
#endif
#endif
#ifndef WOLFCOSE_PROTECTED_HDR_MAX
#define WOLFCOSE_PROTECTED_HDR_MAX 64u
#endif
#ifndef WOLFCOSE_CBOR_MAX_DEPTH
#define WOLFCOSE_CBOR_MAX_DEPTH 8u
#endif
#ifndef WOLFCOSE_MAX_MAP_ITEMS
#define WOLFCOSE_MAX_MAP_ITEMS 16u
#endif
#ifndef WOLFCOSE_MAX_SIG_SZ
#if defined(WOLFCOSE_HAVE_MLDSA)
#define WOLFCOSE_MAX_SIG_SZ 4627u
Expand All @@ -402,6 +394,37 @@ extern "C" {
#define WOLFCOSE_MAX_SIG_SZ 132u
#endif
#endif
#ifndef WOLFCOSE_PROTECTED_HDR_MAX
#define WOLFCOSE_PROTECTED_HDR_MAX 64u
#endif
#ifndef WOLFCOSE_CBOR_MAX_DEPTH
#if defined(WOLFCOSE_MIN_BUFFERS)
#define WOLFCOSE_CBOR_MAX_DEPTH 6u
#else
#define WOLFCOSE_CBOR_MAX_DEPTH 8u
#endif
#endif
#ifndef WOLFCOSE_MAX_MAP_ITEMS
#if defined(WOLFCOSE_MIN_BUFFERS)
#define WOLFCOSE_MAX_MAP_ITEMS 8u
#else
#define WOLFCOSE_MAX_MAP_ITEMS 16u
#endif
#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

#if defined(WOLFCOSE_HAVE_MLDSA) && (WOLFCOSE_MAX_SCRATCH_SZ < 4096u)
#error "wolfCOSE: ML-DSA enabled but WOLFCOSE_MAX_SCRATCH_SZ too small"
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 @@ -5776,6 +5776,7 @@ static int wolfCose_IsHmacAlg(int32_t alg)
) ? 1 : 0;
}

#ifdef HAVE_AES_CBC
/**
* Check if algorithm is AES-CBC-MAC based.
*/
Expand All @@ -5786,6 +5787,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 @@ -80,7 +80,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 @@ -4287,8 +4287,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;
Expand All @@ -4299,7 +4298,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