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
16 changes: 16 additions & 0 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,22 @@ jobs:
make zeroize-test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" \
LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl"

- name: Run delegated signing test
run: |
export WOLFSSL_DIR=$HOME/wolfssl-install
export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib
export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib
make ext-sign-test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" \
LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl"

- name: Run delegated signing forced-failure test
run: |
export WOLFSSL_DIR=$HOME/wolfssl-install
export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib
export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib
make ext-sign-force-failure CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" \
LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl"

- name: Run tool round-trip test
run: |
export WOLFSSL_DIR=$HOME/wolfssl-install
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ jobs:
make demos CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" \
LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl"

- name: Build and run delegated signing example
run: |
export WOLFSSL_DIR=$HOME/wolfssl-install
export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib
make ext-sign-demo CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" \
LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl"

- name: Binary size audit
run: |
export WOLFSSL_DIR=$HOME/wolfssl-install
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/misra-2012.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ jobs:
-DWOLFCOSE_KEY_ENCODE \
-DWOLFCOSE_KEY_DECODE \
-DWOLFCOSE_FLOAT \
-DWOLFCOSE_ENABLE_EXT_SIGN \
-I include -I src -I $HOME/wolfssl-install/include \
src/ include/ \
2>&1 | tee misra-report-raw.txt || true
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/misra-2023.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ jobs:
-DHAVE_AES_KEYWRAP -DWOLFSSL_AES_DIRECT \
-DHAVE_HKDF -DHAVE_AES_CBC \
-DWOLFCOSE_FLOAT \
-DWOLFCOSE_ENABLE_EXT_SIGN \
-I./include -isystem $WOLFSSL_DIR/include"
for f in src/*.c; do
gcc $MISRA_FLAGS -c "$f" -o /dev/null 2>&1 | tee -a compiler-warnings.txt || true
Expand Down Expand Up @@ -228,6 +229,7 @@ jobs:
-DWOLFCOSE_CBOR_ENCODE -DWOLFCOSE_CBOR_DECODE \
-DWOLFCOSE_KEY_ENCODE -DWOLFCOSE_KEY_DECODE \
-DWOLFCOSE_FLOAT \
-DWOLFCOSE_ENABLE_EXT_SIGN \
> clang-tidy-report.txt 2>&1 || true

- name: clang-tidy summary
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/sanitizer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,20 @@ jobs:
make test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Og -g -fsanitize=address -fno-omit-frame-pointer -I./include -isystem $WOLFSSL_DIR/include" \
LDFLAGS="-fsanitize=address -L$WOLFSSL_DIR/lib -lwolfssl"

- name: Run delegated signing tests (ASan)
run: |
export WOLFSSL_DIR=$HOME/wolfssl-asan
export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib
make ext-sign-test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Og -g -fsanitize=address -fno-omit-frame-pointer -I./include -isystem $WOLFSSL_DIR/include" \
LDFLAGS="-fsanitize=address -L$WOLFSSL_DIR/lib -lwolfssl"

- name: Run delegated signing forced-failure tests (ASan)
run: |
export WOLFSSL_DIR=$HOME/wolfssl-asan
export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib
make ext-sign-force-failure CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Og -g -fsanitize=address -fno-omit-frame-pointer -I./include -isystem $WOLFSSL_DIR/include" \
LDFLAGS="-fsanitize=address -L$WOLFSSL_DIR/lib -lwolfssl"

- name: Forced-failure coverage (ASan)
run: |
export WOLFSSL_DIR=$HOME/wolfssl-asan
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Build artifacts
*.o
*.o.tmp
*.a
*.so
*.su
*.dSYM/

# Binaries
tests/test_wolfcose
Expand All @@ -13,6 +15,7 @@ tests/interop/t_cose/QCBOR/
tests/interop/t_cose/t_cose/
examples/sign1_verify_lean
examples/sign1_mldsa
examples/ext_sign_demo
examples/sign1_verify_mldsa

# Editor / OS
Expand Down
42 changes: 35 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ MAC_DEMO = examples/mac0_demo
SIGN1_DEMO = examples/sign1_demo
LEANV_DEMO = examples/sign1_verify_lean
MLDSA_DEMO = examples/sign1_mldsa
EXTSIGN_DEMO = examples/ext_sign_demo
MLDSAV_DEMO = examples/sign1_verify_mldsa

# Comprehensive tests (CI)
Expand All @@ -64,7 +65,7 @@ SCEN_IOTFLEET = examples/scenarios/iot_fleet_config
SCEN_SENSOR = examples/scenarios/sensor_attestation
SCEN_BROADCAST = examples/scenarios/group_broadcast_mac

.PHONY: all shared test zeroize-test coverage tool tool-test cmdline-test demo demos lean-verify mldsa-demo mldsa-verify comprehensive scenarios interop-tcose c99-check clean
.PHONY: all shared test zeroize-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage tool tool-test cmdline-test demo demos lean-verify mldsa-demo mldsa-verify comprehensive scenarios interop-tcose c99-check clean

# --- Core library ---
all: $(LIB_A)
Expand All @@ -90,6 +91,12 @@ zeroize-test:
-o $(TEST_BIN) $(SRC) $(TEST_SRC) $(LDFLAGS)
./$(TEST_BIN)

# --- Delegated signing seam test: exercises the ext-sign callback ---
ext-sign-test:
$(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EXT_SIGN \
-o $(TEST_BIN) $(SRC) $(TEST_SRC) $(LDFLAGS)
./$(TEST_BIN)

# --- Coverage ---
coverage: clean
$(CC) $(CFLAGS) --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose_cbor.c -o src/wolfcose_cbor.o
Expand All @@ -110,6 +117,14 @@ coverage-force-failure: clean
./$(TEST_BIN)
gcov src/*.c

# --- Forced-failure coverage of the delegated seam ---
# WOLF_FAIL_EXT_SIGN lives behind both WOLFCOSE_FORCE_FAILURE and
# WOLFCOSE_ENABLE_EXT_SIGN, so it is unreachable unless both are set.
ext-sign-force-failure: clean
$(CC) $(CFLAGS) -DWOLFCOSE_FORCE_FAILURE -DWOLFCOSE_ENABLE_EXT_SIGN \
-o $(TEST_BIN) $(SRC) $(TEST_SRC) $(FORCE_FAIL_SRC) $(LDFLAGS)
./$(TEST_BIN)

# --- CLI Tool (compiled out of core lib) ---
tool: $(LIB_A)
$(CC) $(CFLAGS) -DWOLFCOSE_BUILD_TOOL -o $(TOOL_BIN) $(TOOL_SRC) $(LIB_A) $(LDFLAGS)
Expand Down Expand Up @@ -154,6 +169,15 @@ lean-verify:
@echo "=== Running lean verify-only example ==="
./$(LEANV_DEMO)

# --- Delegated signing example (WOLFCOSE_ENABLE_EXT_SIGN) ---
# Built from sources with the opt-in macro, since the prebuilt library does
# not carry the seam.
ext-sign-demo:
$(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EXT_SIGN -o $(EXTSIGN_DEMO) \
$(EXTSIGN_DEMO).c src/wolfcose.c src/wolfcose_cbor.c $(LDFLAGS)
@echo "=== Running delegated signing example ==="
./$(EXTSIGN_DEMO)

# --- Post-quantum ML-DSA lean sign + verify (WOLFCOSE_LEAN_MLDSA) ---
# Requires wolfSSL built with ML-DSA (./configure --enable-dilithium).
mldsa-demo:
Expand Down Expand Up @@ -232,10 +256,12 @@ C99_SRC = $(SRC) $(TEST_SRC) $(TOOL_SRC) $(DEMO_SRC) \
$(ENC_DEMO).c $(MAC_DEMO).c $(SIGN1_DEMO).c \
$(COMP_SIGN).c $(COMP_ENCRYPT).c $(COMP_MAC).c $(COMP_ERRORS).c \
$(SCEN_FIRMWARE).c $(SCEN_MULTIPARTY).c $(SCEN_IOTFLEET).c \
$(SCEN_SENSOR).c $(SCEN_BROADCAST).c
# Default features plus the opt-in WOLFCOSE_FLOAT paths, so the gate judges
# every conditionally-compiled translation unit, not just the default subset.
C99_CONFIGS = "" "-DWOLFCOSE_FLOAT"
$(SCEN_SENSOR).c $(SCEN_BROADCAST).c $(EXTSIGN_DEMO).c
# Default features plus the opt-in paths (WOLFCOSE_FLOAT, delegated signing,
# and delegated signing without EdDSA), so the gate judges every
# conditionally-compiled translation unit, not just the default subset.
C99_CONFIGS = "" "-DWOLFCOSE_FLOAT" "-DWOLFCOSE_ENABLE_EXT_SIGN" \
"-DWOLFCOSE_ENABLE_EXT_SIGN -DWOLFCOSE_NO_EDDSA -DWOLFCOSE_NO_ED448"

c99-check:
@for cfg in $(C99_CONFIGS); do \
Expand All @@ -249,8 +275,10 @@ c99-check:
# --- Cleanup ---
clean:
rm -f $(OBJ) $(TEST_BIN) $(TOOL_BIN) $(DEMO_BIN) $(ENC_DEMO) $(MAC_DEMO) \
$(SIGN1_DEMO) $(COMP_SIGN) $(COMP_ENCRYPT) $(COMP_MAC) $(COMP_ERRORS) \
$(EXTSIGN_DEMO) $(SIGN1_DEMO) $(COMP_SIGN) $(COMP_ENCRYPT) $(COMP_MAC) $(COMP_ERRORS) \
$(SCEN_FIRMWARE) $(SCEN_MULTIPARTY) $(SCEN_IOTFLEET) $(SCEN_SENSOR) $(SCEN_BROADCAST) \
$(INTEROP_DIR)/*.o $(INTEROP_DIR)/*.su $(INTEROP_BIN) \
$(LIB_A) $(LIB_SO) src/*.su tests/*.su examples/comprehensive/*.su examples/scenarios/*.su \
$(LIB_A) $(LIB_SO) src/*.su tests/*.su examples/*.su examples/comprehensive/*.su examples/scenarios/*.su \
src/*.gcno src/*.gcda tests/*.gcno tests/*.gcda *.gcov
rm -rf tests/*.dSYM tools/*.dSYM examples/*.dSYM \
examples/comprehensive/*.dSYM examples/scenarios/*.dSYM
35 changes: 35 additions & 0 deletions docs/Macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,41 @@ Resolved internally as read-only `WOLFCOSE_KEY_WRAP`, `WOLFCOSE_ECDH`, and `WOLF

---

## Delegated Signing

| Define | Description | Default |
|--------|-------------|---------|
| `WOLFCOSE_ENABLE_EXT_SIGN` | Opt in `wc_CoseKey_SetExtSigner()`, which delegates signing to a caller-supplied callback | off |

Off in every build unless explicitly enabled, lean or not. Resolved internally as the read-only `WOLFCOSE_EXT_SIGN` gate.

Intended for keys held outside wolfCOSE — an HSM, a secure element, or a TrustZone secure partition.

What the callback receives depends on the algorithm, and getting this wrong produces a well-formed message no verifier accepts:

| Algorithm | `tbs` holds |
|---|---|
| ES256/384/512 | the **digest** of the `Sig_structure` — sign with a sign-hash primitive (`psa_sign_hash`, `CKM_ECDSA`) and return fixed-width `r \|\| s` (RFC 9053 sec. 2.1), **not** a DER `SEQUENCE` |
| PS256/384/512 | the **digest** — sign with RSASSA-PSS, MGF1 over the same SHA-2 as the algorithm, salt length equal to the digest length (RFC 8230 sec. 2) |
| EdDSA, Ed448, ML-DSA | the **`Sig_structure` itself** — sign it with a sign-message primitive |

It returns the raw COSE signature; wolfCOSE checks the returned length against the algorithm but performs no key operation itself. No RNG is needed.

A delegated key needs no local *private* key, but it must declare enough for wolfCOSE to know the expected signature length: ES* and ML-DSA need nothing beyond `alg`; EdDSA needs `kty`/`crv`; PS* needs `kty` plus a local `RsaKey` attached via `wc_CoseKey_SetRsa()` for its modulus size.

Pass a NULL callback to detach. Attaching local key material with `wc_CoseKey_SetEcc()` and friends detaches implicitly, so always call `wc_CoseKey_SetExtSigner()` last. `wc_CoseKey_Decode()` is rejected on a key that has a signer attached, rather than silently importing private material and signing locally with it.

For a key that has no local wolfCrypt object at all, set `kty` (and `crv` for EdDSA) on the `WOLFCOSE_KEY` directly before attaching the signer — there is no setter for declaring a key type without attaching one.

Two limits worth knowing before designing around this:

- It does not remove the local algorithm. `WOLFCOSE_ENABLE_EXT_SIGN` still requires a signing operation, which requires at least one signature algorithm compiled in, so a build with no local signature primitive is rejected at compile time and `WOLFCOSE_LEAN_VERIFY`/`WOLFCOSE_LEAN_VERIFY_MLDSA` cannot be combined with it. Delegating ML-DSA likewise needs local ML-DSA compiled in, which raises the `WOLFCOSE_MAX_SCRATCH_SZ` default to 8192 bytes and enforces a 4096-byte minimum.
- Scratch must hold the `Sig_structure`, which embeds the payload, and delegated signing needs at least as much again for the signature:
- **ES\*, PS\*** pre-hash, so the signature reuses the `Sig_structure` space: `scratchSz >= max(Sig_structure, signature)`. Local ECDSA signs into a stack buffer and needs only the `Sig_structure`, so a small-payload ES256 case can need more scratch delegated than local.
- **EdDSA, Ed448, ML-DSA** sign the structure in place, so the signature goes after it: `scratchSz >= Sig_structure + signature`. Delegated Ed25519 needs 64 bytes more than the local path, Ed448 114.

---

## CBOR Layer Gates

| Define | Description | Default |
Expand Down
Loading
Loading