From 67ae89da8009cc84458a8d84ad6e531b960eddf7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 2 Sep 2026 15:06:41 -0700 Subject: [PATCH 1/6] Add RFC 9783 PSA attestation token support --- .github/workflows/build-test.yml | 74 + .github/workflows/codeql.yml | 3 + .github/workflows/coverage.yml | 38 +- .github/workflows/interop.yml | 12 +- .github/workflows/lean-build.yml | 54 +- .github/workflows/misra-2012.yml | 19 +- .github/workflows/misra-2023.yml | 13 + .github/workflows/sanitizer.yml | 18 +- .github/workflows/stack-bounds.yml | 6 +- .github/workflows/static-analysis.yml | 27 + .github/workflows/wolfssl-versions.yml | 10 + .gitignore | 4 + ChangeLog.md | 14 + EAT_PSA_TASKS.md | 83 + Makefile | 389 +- README.md | 13 +- docs/API-Reference.md | 130 +- docs/Getting-Started.md | 52 +- docs/Home.md | 2 + docs/Macros.md | 52 +- docs/PSA-EAT.md | 292 ++ docs/Release-Notes.md | 14 + docs/Testing.md | 66 +- docs/_Sidebar.md | 1 + examples/lifecycle_demo.c | 8 +- examples/psa_eat_demo.c | 370 ++ examples/psa_eat_verify_lean.c | 259 + include/wolfcose/eat_psa.h | 328 ++ include/wolfcose/settings.h | 190 +- include/wolfcose/wolfcose.h | 99 +- scripts/check_stack_usage.sh | 2 +- scripts/cmdline-test.sh | 9 + scripts/test_pkg_config.sh | 5 +- src/wolfcose.c | 585 ++- src/wolfcose_cbor.c | 259 +- src/wolfcose_eat_psa.c | 1567 ++++++ src/wolfcose_internal.h | 75 +- tests/config/eat_psa_config/config.h | 9 + tests/config/eat_psa_curves/user_settings.h | 12 + tests/config/eat_psa_min_key/user_settings.h | 12 + .../config/eat_psa_no_sha256/user_settings.h | 18 + tests/interop/t_cose/README.md | 7 + tests/interop/t_cose/interop_tcose.c | 203 +- tests/test_cbor.c | 339 +- tests/test_cose.c | 711 ++- tests/test_eat_psa.c | 4219 +++++++++++++++++ tests/test_eat_psa_curve_gates.c | 29 + tests/test_eat_psa_derived_gate.c | 29 + tests/test_eat_psa_hash_gates.c | 32 + tests/test_eat_psa_limits.c | 282 ++ tests/test_eat_psa_min_key_gates.c | 59 + tests/test_eat_psa_profile_main.c | 22 + tests/test_eat_psa_profiles.c | 675 +++ tests/test_main.c | 4 + tests/test_suite.h | 2 + tools/wolfcose_tool.c | 9 +- 56 files changed, 11278 insertions(+), 537 deletions(-) create mode 100644 EAT_PSA_TASKS.md create mode 100644 docs/PSA-EAT.md create mode 100644 examples/psa_eat_demo.c create mode 100644 examples/psa_eat_verify_lean.c create mode 100644 include/wolfcose/eat_psa.h create mode 100644 src/wolfcose_eat_psa.c create mode 100644 tests/config/eat_psa_config/config.h create mode 100644 tests/config/eat_psa_curves/user_settings.h create mode 100644 tests/config/eat_psa_min_key/user_settings.h create mode 100644 tests/config/eat_psa_no_sha256/user_settings.h create mode 100644 tests/test_eat_psa.c create mode 100644 tests/test_eat_psa_curve_gates.c create mode 100644 tests/test_eat_psa_derived_gate.c create mode 100644 tests/test_eat_psa_hash_gates.c create mode 100644 tests/test_eat_psa_limits.c create mode 100644 tests/test_eat_psa_min_key_gates.c create mode 100644 tests/test_eat_psa_profile_main.c create mode 100644 tests/test_eat_psa_profiles.c diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index e38fbc6..d23c219 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -88,6 +88,62 @@ jobs: make 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 RFC 9783 PSA/EAT conformance tests + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-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 RFC 9783 PSA/EAT device-onboarding example + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make psa-eat-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: Run RFC 9783 PSA/EAT floating-point decoder-safety tests + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-float-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 RFC 9783 PSA/EAT constrained-buffer tests + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-min-buffers-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 RFC 9783 PSA/EAT claim-limit boundary tests + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-claim-limits-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 isolated RFC 9783 PSA/EAT feature-profile tests + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-profile-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: Check PSA/EAT feature-gate matrix + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-config-check 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 zeroize-hook regression test run: | export WOLFSSL_DIR=$HOME/wolfssl-install @@ -112,6 +168,24 @@ jobs: 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 PSA/EAT delegated signing tests + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-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" + make eat-psa-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 generic reduced-algorithm regression + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + export DYLD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make generic-reduced-alg-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 tool round-trip test run: | export WOLFSSL_DIR=$HOME/wolfssl-install diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3ab3837..0c8ebf3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -62,8 +62,11 @@ jobs: - name: Build wolfCOSE run: | export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib make 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" + make eat-psa-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: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index abbcf97..2764b00 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -57,7 +57,7 @@ jobs: run: | export WOLFSSL_DIR=$HOME/wolfssl-install export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib - make coverage-force-failure CC=gcc \ + make eat-psa-coverage-force-failure CC=gcc \ 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" @@ -71,6 +71,7 @@ jobs: # Coverage thresholds: # wolfcose.c: 99% minimum # wolfcose_cbor.c: 100% minimum + # wolfcose_eat_psa.c: 80% minimum (full PSA/EAT profile) FAILED=0 @@ -131,14 +132,45 @@ jobs: echo "" fi + # Parse optional RFC 9783 PSA/EAT parser coverage. This source is + # compiled only by the explicit full profile used above. + EAT_PCT=$(echo "$GCOV_OUTPUT" | grep -A1 "wolfcose_eat_psa.c'" | grep "Lines executed" | sed "s/.*:\([0-9.]*\)%.*/\1/") + EAT_UNCOV=$(grep -c "#####" wolfcose_eat_psa.c.gcov 2>/dev/null) || EAT_UNCOV=0 + + if [ -z "$EAT_PCT" ]; then + FAILED=1 + echo ">>> FAILED: wolfcose_eat_psa.c coverage data is missing! <<<" + echo "" + else + echo "wolfcose_eat_psa.c:" + echo " Coverage: ${EAT_PCT}%" + echo " Threshold: 80%" + echo " Uncovered lines: ${EAT_UNCOV}" + echo "" + + if awk "BEGIN {exit !($EAT_PCT < 80)}"; then + FAILED=1 + echo ">>> FAILED: wolfcose_eat_psa.c coverage is below 80%! <<<" + echo "" + echo "Uncovered lines in wolfcose_eat_psa.c:" + echo "---------------------------------------" + grep -n "#####" wolfcose_eat_psa.c.gcov | head -50 + echo "" + else + echo ">>> PASSED: wolfcose_eat_psa.c meets 80% threshold <<<" + fi + echo "" + fi + echo "==============================================" if [ "$FAILED" -eq 1 ]; then echo "" echo " Looks like you need more tests!" echo "" - echo " Add tests to tests/test_cose.c to cover the" - echo " uncovered lines shown above." + echo " Add tests in the matching test source, for example" + echo " tests/test_eat_psa.c for wolfcose_eat_psa.c, to cover" + echo " the uncovered lines shown above." echo "" echo "==============================================" exit 1 diff --git a/.github/workflows/interop.yml b/.github/workflows/interop.yml index dd6ea74..0d5620d 100644 --- a/.github/workflows/interop.yml +++ b/.github/workflows/interop.yml @@ -23,7 +23,7 @@ jobs: uses: ./.github/workflows/_resolve-wolfssl.yml interop: - name: wolfCOSE <-> t_cose (OpenSSL, wolfSSL ${{ matrix.wolfssl-version }}) + name: wolfCOSE <-> t_cose + PSA/EAT (OpenSSL, wolfSSL ${{ matrix.wolfssl-version }}) needs: discover runs-on: ubuntu-latest strategy: @@ -89,7 +89,15 @@ jobs: make CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -I./include -isystem $WOLFSSL_DIR/include" \ LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" - - name: Run t_cose interop + - name: Verify RFC 9783 Appendix A PSA/EAT vectors + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-test \ + CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -I./include -isystem $WOLFSSL_DIR/include" \ + LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" + + - name: Run t_cose and PSA/EAT profile interop run: | export WOLFSSL_DIR=$HOME/wolfssl-install export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib diff --git a/.github/workflows/lean-build.yml b/.github/workflows/lean-build.yml index c5aea76..65bf1c2 100644 --- a/.github/workflows/lean-build.yml +++ b/.github/workflows/lean-build.yml @@ -33,13 +33,11 @@ jobs: uses: actions/cache@v4 with: path: ~/wolfssl-lean - key: wolfssl-lean-ecc-verifyonly-v2-${{ steps.wolfssl-rev.outputs.sha }} + key: wolfssl-lean-tfm-verifyonly-v3-${{ steps.wolfssl-rev.outputs.sha }} - # 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. + # Verify-only full-#tfm backend: NO_ECC_SIGN compiles ECDSA signing out. + # This catches a verification path that accidentally pulls in a signing + # helper while retaining every algorithm RFC 9783 Section 5.2 requires. - name: Build minimal wolfSSL if: steps.cache-wolfssl.outputs.cache-hit != 'true' run: | @@ -47,7 +45,7 @@ jobs: git clone --depth 1 https://github.com/wolfSSL/wolfssl.git wolfssl-lean-src cd wolfssl-lean-src ./autogen.sh - ./configure --enable-cryptonly --enable-ecc \ + ./configure --enable-cryptonly --enable-ecc --enable-sha384 --enable-sha512 \ CFLAGS="-DNO_ECC_SIGN" \ --prefix=$HOME/wolfssl-lean make -j$(nproc) @@ -77,8 +75,32 @@ jobs: fi echo "OK: verify present, signing absent" + - name: Build and run the lean RFC 9783 PSA/EAT verifier + run: | + export WOLFSSL_DIR=$HOME/wolfssl-lean + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make psa-eat-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" + + - name: Assert PSA/EAT lean verifier has no signing API + run: | + BIN=examples/psa_eat_verify_lean + if nm "$BIN" | grep -E " [tT] wc_Cose(Sign1_Sign|Mac0_Create)\\b"; then + echo "FAIL: creation API present in PSA/EAT verifier"; exit 1 + fi + if ! nm "$BIN" | grep -qE " [tT] wc_CoseEatPsaToken_Verify\\b"; then + echo "FAIL: PSA/EAT verifier API missing"; exit 1 + fi + if nm "$BIN" | grep -qE " [tT] wc_CoseEatPsaToken_(EncodeClaims|CreateSign1|CreateMac0|VerifyByUeid|ForEachComponent)\\b"; then + echo "FAIL: unselected PSA/EAT API linked into lean verifier"; exit 1 + fi + echo "OK: PSA/EAT verification present, signing absent" + - name: Report lean example size - run: size examples/sign1_verify_lean || true + run: | + size examples/sign1_verify_lean || true + size examples/psa_eat_verify_lean || true lean-compile-matrix: name: Lean configs compile clean (strict) @@ -133,6 +155,14 @@ jobs: gcc $SF -DWOLFCOSE_LEAN_VERIFY -c src/wolfcose.c -o /tmp/lean.o echo "== COSE_Mac without COSE_Mac0 ==" gcc $SF -DWOLFCOSE_NO_MAC0 -c src/wolfcose.c -o /tmp/mac-no-mac0.o + echo "== WOLFCOSE_LEAN_VERIFY + RFC 9783 PSA/EAT ==" + gcc $SF -DWOLFCOSE_LEAN_VERIFY -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -DWOLFCOSE_ENABLE_ES384 \ + -DWOLFCOSE_ENABLE_ES512 -DWOLFCOSE_ENABLE_HMAC384 \ + -DWOLFCOSE_ENABLE_HMAC512 \ + -c src/wolfcose_eat_psa.c -o /tmp/eat-lean.o echo "== sign-only ==" gcc $SF -DWOLFCOSE_NO_SIGN1_VERIFY -DWOLFCOSE_NO_ENCRYPT0 -DWOLFCOSE_NO_MAC0 \ -DWOLFCOSE_NO_SIGN -DWOLFCOSE_NO_ENCRYPT -DWOLFCOSE_NO_MAC \ @@ -143,6 +173,14 @@ jobs: gcc $SF -DWOLFCOSE_LEAN_VERIFY_MLDSA -c src/wolfcose.c -o /tmp/mldsav.o echo "All lean configurations compiled clean." + - name: Check PSA/EAT opt-in feature matrix + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + SF="-std=c11 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Werror -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -I./include -isystem $WOLFSSL_DIR/include" + make eat-psa-config-check CFLAGS="$SF" \ + LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" + mldsa: name: Post-quantum ML-DSA lean sign+verify and verify-only runs-on: ubuntu-latest diff --git a/.github/workflows/misra-2012.yml b/.github/workflows/misra-2012.yml index 9313674..3d357b7 100644 --- a/.github/workflows/misra-2012.yml +++ b/.github/workflows/misra-2012.yml @@ -82,8 +82,16 @@ jobs: # a full production build does. Only true user inputs are supplied. cc -std=c99 -Wall -Wextra -Wpedantic -Werror \ -DWOLFCOSE_FLOAT -DWOLFCOSE_ENABLE_EXT_SIGN \ + -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ + -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ -I include -I "$HOME/wolfssl-install/include" \ - -fsyntax-only tests/misra_consumer.c + -fsyntax-only src/wolfcose_eat_psa.c tests/misra_consumer.c set +e cppcheck --addon=misra \ @@ -107,10 +115,19 @@ jobs: -DWOLFSSL_AES_DIRECT \ -DHAVE_HKDF \ -DHAVE_AES_CBC \ + -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ + -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ -DWOLFCOSE_FLOAT \ -DWOLFCOSE_ENABLE_EXT_SIGN \ -I include -I src -I $HOME/wolfssl-install/include \ src/wolfcose.c src/wolfcose_cbor.c \ + src/wolfcose_eat_psa.c \ tests/misra_consumer.c \ 2>&1 | tee misra-report-raw.txt CPPCHECK_STATUS=${PIPESTATUS[0]} diff --git a/.github/workflows/misra-2023.yml b/.github/workflows/misra-2023.yml index ec7e1fb..d4d7900 100644 --- a/.github/workflows/misra-2023.yml +++ b/.github/workflows/misra-2023.yml @@ -102,6 +102,14 @@ jobs: -DHAVE_HKDF -DHAVE_AES_CBC \ -DWOLFCOSE_FLOAT \ -DWOLFCOSE_ENABLE_EXT_SIGN \ + -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ + -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ -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 @@ -221,6 +229,11 @@ jobs: -DWOLFCOSE_SIGN1 -DWOLFCOSE_SIGN1_SIGN -DWOLFCOSE_SIGN1_VERIFY \ -DWOLFCOSE_ENCRYPT0 -DWOLFCOSE_ENCRYPT0_ENCRYPT -DWOLFCOSE_ENCRYPT0_DECRYPT \ -DWOLFCOSE_MAC0 -DWOLFCOSE_MAC0_CREATE -DWOLFCOSE_MAC0_VERIFY \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ + -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ -DWOLFCOSE_SIGN -DWOLFCOSE_SIGN_SIGN -DWOLFCOSE_SIGN_VERIFY \ -DWOLFCOSE_ENCRYPT -DWOLFCOSE_ENCRYPT_ENCRYPT -DWOLFCOSE_ENCRYPT_DECRYPT \ -DWOLFCOSE_MAC -DWOLFCOSE_MAC_CREATE -DWOLFCOSE_MAC_VERIFY \ diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index 48bc592..52cb363 100644 --- a/.github/workflows/sanitizer.yml +++ b/.github/workflows/sanitizer.yml @@ -77,6 +77,13 @@ 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 PSA/EAT conformance tests (ASan) + run: | + export WOLFSSL_DIR=$HOME/wolfssl-asan + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-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 @@ -91,11 +98,20 @@ jobs: 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: Run PSA/EAT delegated signing tests (ASan) + run: | + export WOLFSSL_DIR=$HOME/wolfssl-asan + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make eat-psa-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" + make eat-psa-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 export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib - make coverage-force-failure CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Og -g -fsanitize=address -fno-omit-frame-pointer -I./include -isystem $WOLFSSL_DIR/include" \ + make eat-psa-coverage-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: ECC private-import backend policy (ASan) diff --git a/.github/workflows/stack-bounds.yml b/.github/workflows/stack-bounds.yml index b2fa3b0..fd3e8a7 100644 --- a/.github/workflows/stack-bounds.yml +++ b/.github/workflows/stack-bounds.yml @@ -48,11 +48,13 @@ jobs: make -j$(nproc) make install - - name: Build wolfCOSE (-Werror=vla, -fstack-usage) + - name: Build full PSA/EAT 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" \ + EAT_FLAGS="-DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR" + make CFLAGS="-std=c11 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wvla -Werror=vla -fstack-usage -I./include -I$WOLFSSL_DIR/include $EAT_FLAGS" \ LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" + grep -q "wc_CoseEatPsaToken_Verify" src/wolfcose_eat_psa.su - name: Enforce per-frame budget run: sh scripts/check_stack_usage.sh "$FRAME_BUDGET" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index a12ca88..37163ee 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -65,9 +65,29 @@ jobs: --suppress=unreadVariable:tests/test_cose.c \ --suppress=unusedVariable:tests/test_cose.c \ --inline-suppr \ + -itests/test_eat_psa_curve_gates.c \ + -itests/test_eat_psa_hash_gates.c \ + -itests/test_eat_psa_min_key_gates.c \ -I include -I src -I $HOME/wolfssl-install/include \ src/ tests/ + - name: Run cppcheck on the full PSA/EAT profile + run: | + cppcheck --enable=warning,style,performance,portability \ + --error-exitcode=1 \ + --suppress=missingIncludeSystem \ + --inline-suppr \ + -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ + -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ + -I include -I src -I $HOME/wolfssl-install/include \ + src/wolfcose_eat_psa.c + clang-analyzer: name: Clang Static Analyzer runs-on: ubuntu-latest @@ -108,9 +128,13 @@ jobs: - name: Run scan-build run: | + export LD_LIBRARY_PATH=$HOME/wolfssl-install/lib scan-build --status-bugs -o scan-results \ make CC=clang CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -I./include -isystem $HOME/wolfssl-install/include" \ LDFLAGS="-L$HOME/wolfssl-install/lib -lwolfssl" + scan-build --status-bugs -o scan-results \ + make eat-psa-test CC=clang CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -I./include -isystem $HOME/wolfssl-install/include" \ + LDFLAGS="-L$HOME/wolfssl-install/lib -lwolfssl" - name: Upload scan-build results if: failure() @@ -160,5 +184,8 @@ jobs: - name: Build with -fanalyzer run: | export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib make CC=gcc CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -fanalyzer -Werror -I./include -isystem $WOLFSSL_DIR/include" \ LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" + make eat-psa-test CC=gcc CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -fanalyzer -Werror -I./include -isystem $WOLFSSL_DIR/include" \ + LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" diff --git a/.github/workflows/wolfssl-versions.yml b/.github/workflows/wolfssl-versions.yml index 963d71a..14f32f5 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -171,6 +171,16 @@ jobs: make 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 enabled PSA/EAT tests at the wolfSSL compatibility floor + if: matrix.wolfssl-version == 'v5.8.0-stable' && !matrix.pqc + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + CF="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -I./include -isystem $WOLFSSL_DIR/include" + LF="-L$WOLFSSL_DIR/lib -lwolfssl" + make eat-psa-test CFLAGS="$CF" LDFLAGS="$LF" + make eat-psa-config-check CFLAGS="$CF" LDFLAGS="$LF" + - name: Run tool round-trip test run: | export WOLFSSL_DIR=$HOME/wolfssl-install diff --git a/.gitignore b/.gitignore index fd05704..bfa45e6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ experimental-check.err # Binaries tests/test_wolfcose +tests/test_wolfcose_eat_psa +tests/test_wolfcose_eat_psa_limits tools/wolfcose_tool examples/lifecycle_demo tests/interop/t_cose/interop_tcose @@ -19,6 +21,8 @@ examples/sign1_verify_lean examples/sign1_mldsa examples/ext_sign_demo examples/sign1_verify_mldsa +examples/psa_eat_demo +examples/psa_eat_verify_lean # Editor / OS *.swp diff --git a/ChangeLog.md b/ChangeLog.md index 3c82a8a..d352695 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,6 +13,20 @@ MAC, and key distribution, and standardized post-quantum ML-DSA signatures ## New Feature Additions +* Optional RFC 9783 PSA attestation / EAT Token support. It consumes tagged + current TF-M and legacy PSA IoT profile Sign1 or Mac0 tokens, validates the + required structural claims and nonce, and emits current-profile tokens with + one enabled RFC 9783 Sign1 or Mac0 protection path. Complete RFC 9783 + `#tfm` receiver capability remains a separately derived gate. + Every PSA/EAT profile, envelope, issuer, helper, and non-core algorithm is + explicitly macro-gated and absent from a default archive. The verifier + accepts RFC-required non-preferred definite CBOR while ordinary COSE decode + remains strict. Partial builds reject `#tfm` rather than claiming incomplete + conformance; profile, map-limit, Appendix A TF-M `iat-verifier`, and t_cose + interop coverage are in CI. A device-onboarding example demonstrates current + Sign1 issuance, nonce verification, and software measurement appraisal. See + docs/PSA-EAT.md. + * `wc_CoseSign1_Sign_ex()` can emit untagged COSE_Sign1 messages, and `wc_CoseSign1_SignSize_ex()` reports their exact encoded size without signing or invoking an external signer. diff --git a/EAT_PSA_TASKS.md b/EAT_PSA_TASKS.md new file mode 100644 index 0000000..2c3936d --- /dev/null +++ b/EAT_PSA_TASKS.md @@ -0,0 +1,83 @@ +# PSA and EAT implementation checklist + +This is the working checklist for the RFC 9783 PSA token implementation. +Items move to complete only after their associated code and test evidence are +present. + +## Core protocol + +- [x] Isolate the work from the user's existing dirty worktree. +- [x] Add an optional RFC 8949 variation tolerant decode path while retaining + strict decode as the default COSE API behavior. +- [x] Add tagged COSE Sign1 and Mac0 envelope authentication before claim use. +- [x] Add current `tag:psacertified.org,2023:psa#tfm` claim issuance. +- [x] Add current and `PSA_IOT_PROFILE_1` claim consumption. +- [x] Complete legacy-profile differences, including optional legacy profile, + fixed boot seed, old certification reference, and no-measurements form. +- [x] Enforce direct, attached token boundaries and document certificate-chain + handling. +- [x] Validate all caller-visible API argument and lifetime rules. + +## wolfTrust and PSA integration + +- [x] Provide a raw-token verifier suitable for + `psa_initial_attest_get_token()` output. +- [x] Provide UEID based caller-owned key resolution. +- [x] Verify the delegated/external signer path accepts a NULL RNG and test it + through the PSA issuer wrapper. +- [x] Add a focused PSA client token-acquisition handoff example to the + integration guide without a wolfCOSE PSA-library dependency. + +## Verification and interop + +- [x] Unit-test current Sign1, Mac0, nonce enforcement, UEID resolution, + claim duplicates, and tolerant claims decoding. +- [x] Add RFC 9783 Appendix A Sign1 and Mac0 vectors. +- [x] Add negative envelope tests: untagged/CWT-tagged/detached/x5chain, + unsupported algorithms, malformed and indefinite CBOR. +- [x] Exercise ES256/384/512 and HMAC 256/384/512 where configured. +- [x] Add a verify-only, full `#tfm` receiver build/test with no creation API. +- [x] Extend t_cose wire interop with profile payload coverage in both + directions. +- [x] Run a dedicated Codex-backed Skoll RFC 9783 conformance scan with no + findings at any severity. +- [x] Run RFC 9783's iat-verifier-generated Appendix A vectors in CI and + label them as external static vectors rather than a live tool runner. +- [x] Update coverage instrumentation and thresholds for the new source. +- [x] Run unit, C99, lean, sanitizer, macro-gate, and t_cose interop gates. +- [x] Keep variation-tolerant decoding scoped to COSE verification operations, + without putting profile state in the PSA/EAT public API. +- [x] Enforce RFC 9783 Section 5.2 `#tfm` receiver capability as a derived + full-profile gate; reject partial standard-profile consumption while + allowing an attester to issue with one RFC-permitted protection path. +- [x] Derive ECDSA support from the concrete wolfSSL P-256, P-384, and P-521 + curve configuration, including `ECC_USER_CURVES` and `ECC_MIN_KEY_SZ` + boundary feature-matrix tests. +- [x] Recognize disabled and mixed standardized claim namespaces as profile + errors rather than skipping them as extensions. +- [x] Prove variation-tolerant authenticated decoding for non-preferred claim + maps, byte strings, arrays, unsigned values, and signed values while the + ordinary public CBOR decoder remains strict. +- [x] Prove malformed routing claims are rejected before the UEID key resolver + runs, and that duplicate text labels are rejected in every header map. +- [x] Test the exact and plus-one combined protected/unprotected text-label + tracking boundary. +- [x] Cover receiver-side nonce-length mismatch, component hash boundaries, + every valid lifecycle class, and current certification-reference syntax. +- [x] Test exact and plus-one top-level/component claim-map limits in CI, and + reject every below-minimum resource limit at compile time. +- [x] Compile the enabled PSA/EAT implementation under the strict MISRA C:2023 + compiler-warning profile, not only under clang-tidy. +- [x] Run enabled PSA/EAT conformance and macro-gate tests against the supported + wolfSSL v5.8.0 compatibility floor in the version matrix. + +## Documentation and release readiness + +- [x] Write the RFC 9783 API and integration guide. +- [x] Document security boundary, policy responsibilities, modern versus + legacy behavior, supported algorithms, and unsupported trust models. +- [x] Update README, feature-macro, testing, release notes, and ChangeLog. +- [x] Add a practical current-profile issue, verify, and component-appraisal + onboarding example and run it in CI. +- [x] Final diff review, public-header audit, full validation, and Codex-backed + Skoll remediation to no validated findings above low severity. diff --git a/Makefile b/Makefile index dc8d21a..b85f445 100644 --- a/Makefile +++ b/Makefile @@ -41,14 +41,22 @@ CFLAGS += -fstack-usage # HAVE_ANONYMOUS_INLINE_AGGREGATES and shrinks WC_RNG, corrupting the RNG. CFLAGS += -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 CFLAGS += -I./include $(WOLFSSL_CFLAGS) -CFLAGS += $(EXTRA_CFLAGS) +# Feature-matrix targets pass EXTRA_CFLAGS to recursive makes. GNU make does +# not append ordinary Makefile assignments to command-line CFLAGS, so make the +# opt-in flags survive CI's compiler-profile override as well. +override CFLAGS += $(EXTRA_CFLAGS) LDFLAGS ?= LDLIBS ?= LDLIBS += $(WOLFSSL_LIBS) -# Core library sources (only these go into .a/.so) -SRC = src/wolfcose_cbor.c src/wolfcose.c -OBJ = $(SRC:.c=.o) +# Core library sources. The PSA/EAT translation unit is always present so +# config.h and user-settings feature gates cannot leave declared APIs without +# definitions. Its top-level gate produces no PSA/EAT code or symbols unless +# the caller explicitly enables WOLFCOSE_ENABLE_EAT_PSA. +CORE_SRC = src/wolfcose_cbor.c src/wolfcose.c +EAT_PSA_SRC = src/wolfcose_eat_psa.c +SRC = $(CORE_SRC) $(EAT_PSA_SRC) +OBJ = $(SRC:.c=.o) LIB_A = libwolfcose.a LIB_SO = libwolfcose.so # Make cannot model variable values as dependencies. Compare a saved build @@ -72,8 +80,25 @@ BUILD_CONFIG_CHANGED := FORCE endif # Tests (mirrors two-layer lib architecture) -TEST_SRC = tests/test_cbor.c tests/test_cose.c tests/test_interop.c tests/test_main.c +TEST_SRC = tests/test_cbor.c tests/test_cose.c tests/test_interop.c tests/test_eat_psa.c tests/test_eat_psa_profiles.c tests/test_main.c TEST_BIN = tests/test_wolfcose +EAT_PSA_TEST_BIN = tests/test_wolfcose_eat_psa +EAT_PSA_LIMITS_TEST_BIN = tests/test_wolfcose_eat_psa_limits + +# Full PSA/EAT conformance test profile. Production integrations can select a +# smaller subset by defining only the WOLFCOSE_ENABLE_EAT_PSA_* switches they +# need; see docs/PSA-EAT.md. This variable is overrideable for CI experiments. +EAT_PSA_FULL_FLAGS ?= -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ + -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR + +# Exercise established generic targets against an intentionally reduced +# algorithm set. PSA/EAT-specific targets select EAT_PSA_FULL_FLAGS instead. +REDUCED_ALG_FLAGS = -DWOLFCOSE_NO_ES384 -DWOLFCOSE_NO_ES512 \ + -DWOLFCOSE_NO_HMAC384 -DWOLFCOSE_NO_HMAC512 # Tools (compiled separately, never in core lib) TOOL_SRC = tools/wolfcose_tool.c @@ -86,6 +111,8 @@ ENC_DEMO = examples/encrypt0_demo MAC_DEMO = examples/mac0_demo SIGN1_DEMO = examples/sign1_demo LEANV_DEMO = examples/sign1_verify_lean +EAT_LEANV_DEMO = examples/psa_eat_verify_lean +EAT_DEMO = examples/psa_eat_demo MLDSA_DEMO = examples/sign1_mldsa EXTSIGN_DEMO = examples/ext_sign_demo MLDSAV_DEMO = examples/sign1_verify_mldsa @@ -103,13 +130,13 @@ SCEN_IOTFLEET = examples/scenarios/iot_fleet_config SCEN_SENSOR = examples/scenarios/sensor_attestation SCEN_BROADCAST = examples/scenarios/group_broadcast_mac -.PHONY: all shared test pkg-config-test ecdsa-policy-test rsapss-policy-test zero-alloc-check zeroize-test ecc-import-policy-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 experimental-check clean FORCE +.PHONY: all shared test pkg-config-test ecdsa-policy-test rsapss-policy-test zero-alloc-check zeroize-test ecc-import-policy-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage eat-psa-test eat-psa-float-test eat-psa-min-buffers-test eat-psa-claim-limits-test eat-psa-profile-test eat-psa-config-check eat-psa-ext-sign-test eat-psa-ext-sign-force-failure eat-psa-coverage eat-psa-coverage-force-failure generic-reduced-alg-test tool tool-test cmdline-test demo demos lean-verify psa-eat-lean-verify psa-eat-demo mldsa-demo mldsa-verify comprehensive scenarios interop-tcose c99-check experimental-check clean FORCE # --- Core library --- all: $(LIB_A) $(LIB_A): $(OBJ) - $(AR) rcs $@ $^ + $(AR) rcs $@ $(OBJ) FORCE: @@ -303,6 +330,273 @@ test: pkg-config-test ecdsa-policy-test rsapss-policy-test zero-alloc-check $(LI pkg-config-test: sh scripts/test_pkg_config.sh +# PSA/EAT is intentionally absent from the default API. This target builds its +# complete feature profile directly from source, leaving `make test` a proof +# that an ordinary COSE build has no PSA/EAT API dependency. +eat-psa-test: + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -o $(EAT_PSA_TEST_BIN) \ + $(CORE_SRC) $(EAT_PSA_SRC) $(TEST_SRC) $(LDFLAGS) $(LDLIBS) + ./$(EAT_PSA_TEST_BIN) + +# Exercise the optional float writers at runtime, including the decoder-mode +# safety checks shared by float and double encoders. +eat-psa-float-test: + $(MAKE) clean + $(MAKE) eat-psa-test EXTRA_CFLAGS='-DWOLFCOSE_FLOAT' + +# Validate the current profile with WOLFCOSE_MIN_BUFFERS. The shared claim +# fixture includes every optional current claim, which proves the EAT parser +# does not inherit the smaller COSE-header map limit. +eat-psa-min-buffers-test: + $(CC) $(CFLAGS) -DWOLFCOSE_MIN_BUFFERS $(EAT_PSA_FULL_FLAGS) \ + -o $(EAT_PSA_TEST_BIN) $(CORE_SRC) $(EAT_PSA_SRC) $(TEST_SRC) \ + $(LDFLAGS) $(LDLIBS) + ./$(EAT_PSA_TEST_BIN) + +# Build a tiny, dedicated boundary test with floor-valid limits. It proves an +# exact claim/component map is accepted while each respective +1 declaration +# is rejected before the profile parser traverses attacker-controlled pairs. +eat-psa-claim-limits-test: + $(MAKE) clean + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_TEST_EAT_PSA_LIMITS \ + -DWOLFCOSE_EAT_PSA_MAX_CLAIMS=10u \ + -DWOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS=5u \ + -o $(EAT_PSA_LIMITS_TEST_BIN) $(CORE_SRC) $(EAT_PSA_SRC) \ + tests/test_eat_psa_limits.c $(LDFLAGS) $(LDLIBS) + ./$(EAT_PSA_LIMITS_TEST_BIN) + +# Prove selective configurations at runtime rather than merely compiling their +# source. The partial current attesters may issue #tfm with one enabled +# protection path, but their incomplete receivers must refuse that profile. +# Current/legacy namespace crossings are profile errors; a disabled envelope +# remains WOLFCOSE_E_UNSUPPORTED before crypto. +eat-psa-profile-test: + $(MAKE) clean + $(CC) $(CFLAGS) -DWOLFCOSE_TEST_EAT_PSA_PROFILES \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -o $(EAT_PSA_TEST_BIN) $(CORE_SRC) $(EAT_PSA_SRC) \ + tests/test_eat_psa_profiles.c tests/test_eat_psa_profile_main.c \ + $(LDFLAGS) $(LDLIBS) + ./$(EAT_PSA_TEST_BIN) + $(MAKE) clean + $(CC) $(CFLAGS) -DWOLFCOSE_TEST_EAT_PSA_PROFILES \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -o $(EAT_PSA_TEST_BIN) $(CORE_SRC) $(EAT_PSA_SRC) \ + tests/test_eat_psa_profiles.c tests/test_eat_psa_profile_main.c \ + $(LDFLAGS) $(LDLIBS) + ./$(EAT_PSA_TEST_BIN) + $(MAKE) clean + $(CC) $(CFLAGS) -DWOLFCOSE_TEST_EAT_PSA_PROFILES \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ + -o $(EAT_PSA_TEST_BIN) $(CORE_SRC) $(EAT_PSA_SRC) \ + tests/test_eat_psa_profiles.c tests/test_eat_psa_profile_main.c \ + $(LDFLAGS) $(LDLIBS) + ./$(EAT_PSA_TEST_BIN) + $(MAKE) clean + $(CC) $(CFLAGS) -DWOLFCOSE_TEST_EAT_PSA_PROFILES \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -o $(EAT_PSA_TEST_BIN) $(CORE_SRC) $(EAT_PSA_SRC) \ + tests/test_eat_psa_profiles.c tests/test_eat_psa_profile_main.c \ + $(LDFLAGS) $(LDLIBS) + ./$(EAT_PSA_TEST_BIN) + +# Exercise the independently selectable PSA/EAT profiles, envelopes, and +# issuer helpers. This includes intentionally invalid switches so a typo or +# incomplete feature set fails at compile time rather than shipping a partial +# verifier. The regular full test target covers runtime behavior. +eat-psa-config-check: + $(MAKE) clean + $(MAKE) all + @if nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_"; then \ + echo "FAIL: default library exports PSA/EAT"; exit 1; \ + fi + @echo "PASS: default library has no PSA/EAT symbols" + $(MAKE) test + @echo "PASS: default generic verifiers reject private PSA/EAT decode flags" + $(MAKE) all EXTRA_CFLAGS='$(EAT_PSA_FULL_FLAGS)' + @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_Verify"; then \ + echo "FAIL: enabled library omits PSA/EAT verifier"; exit 1; \ + fi + $(MAKE) all + @if nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_"; then \ + echo "FAIL: enabled-to-default build retained PSA/EAT symbols"; exit 1; \ + fi + $(MAKE) all EXTRA_CFLAGS='-DHAVE_CONFIG_H -I./tests/config/eat_psa_config' + @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_Verify"; then \ + echo "FAIL: config.h-enabled library omits PSA/EAT verifier"; exit 1; \ + fi + $(MAKE) all + @if nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_"; then \ + echo "FAIL: config.h-to-default build retained PSA/EAT symbols"; exit 1; \ + fi + $(CC) $(CFLAGS) -Werror -DWOLFCOSE_LEAN_VERIFY \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror -DWOLFCOSE_LEAN \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror -DWOLFCOSE_LEAN_VERIFY \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror -DWOLFCOSE_LEAN \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_NO_ES384 -DWOLFCOSE_NO_ES512 -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_NO_HMAC384 -DWOLFCOSE_NO_HMAC512 -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_curves \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -fsyntax-only tests/test_eat_psa_curve_gates.c + $(CC) $(CFLAGS) -Werror -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_min_key -DECC_MIN_KEY_SZ=256 \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -fsyntax-only tests/test_eat_psa_min_key_gates.c + $(CC) $(CFLAGS) -Werror -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_min_key -DECC_MIN_KEY_SZ=257 \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -fsyntax-only tests/test_eat_psa_min_key_gates.c + $(CC) $(CFLAGS) -Werror -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_min_key -DECC_MIN_KEY_SZ=384 \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -fsyntax-only tests/test_eat_psa_min_key_gates.c + $(CC) $(CFLAGS) -Werror -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_min_key -DECC_MIN_KEY_SZ=521 \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -fsyntax-only tests/test_eat_psa_min_key_gates.c + $(CC) $(CFLAGS) -Werror -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_min_key -DECC_MIN_KEY_SZ=522 \ + -fsyntax-only tests/test_eat_psa_min_key_gates.c + @if $(CC) $(CFLAGS) -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_min_key -DECC_MIN_KEY_SZ=385 \ + -DWOLFCOSE_ENABLE_ES384 -fsyntax-only \ + tests/test_eat_psa_derived_gate.c >/dev/null 2>&1; then \ + echo "FAIL: 385-bit minimum accepted explicit ES384"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_min_key -DECC_MIN_KEY_SZ=522 \ + -DWOLFCOSE_ENABLE_ES512 -fsyntax-only \ + tests/test_eat_psa_derived_gate.c >/dev/null 2>&1; then \ + echo "FAIL: 522-bit minimum accepted explicit ES512"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_min_key -DECC_MIN_KEY_SZ=522 \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -fsyntax-only \ + tests/test_eat_psa_derived_gate.c >/dev/null 2>&1; then \ + echo "FAIL: 522-bit minimum accepted PSA/EAT Sign1"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_curves -DWOLFCOSE_ENABLE_ES384 \ + -fsyntax-only tests/test_eat_psa_curve_gates.c >/dev/null 2>&1; then \ + echo "FAIL: P-256-only configuration accepted explicit ES384"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_curves -DWOLFCOSE_ENABLE_ES512 \ + -fsyntax-only tests/test_eat_psa_curve_gates.c >/dev/null 2>&1; then \ + echo "FAIL: P-256-only configuration accepted explicit ES512"; exit 1; \ + fi + $(CC) $(CFLAGS) -Werror -DWOLFSSL_USER_SETTINGS \ + -I./tests/config/eat_psa_no_sha256 \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -fsyntax-only tests/test_eat_psa_hash_gates.c + @if $(CC) $(CFLAGS) -Werror \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -DWOLFCOSE_NO_ES384 -DWOLFCOSE_NO_ES512 \ + -DWOLFCOSE_NO_HMAC384 -DWOLFCOSE_NO_HMAC512 \ + -DWOLFCOSE_EAT_PSA_TFM_FULL -fsyntax-only \ + tests/test_eat_psa_derived_gate.c >/dev/null 2>&1; then \ + echo "FAIL: partial receiver accepted forced #tfm capability"; exit 1; \ + fi + $(CC) $(CFLAGS) -Werror -DHAVE_ALL_CURVES -DNO_ECC256 \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -DWOLFCOSE_TEST_NO_ECC256_ALL_CURVES -fsyntax-only \ + tests/test_eat_psa_derived_gate.c + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: top-level PSA/EAT flag accepted without a profile/envelope"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: current profile flag accepted without PSA/EAT"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: legacy profile flag accepted without PSA/EAT"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: Sign1 flag accepted without PSA/EAT"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: Mac0 flag accepted without PSA/EAT"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ + -DWOLFCOSE_NO_ES256 -DWOLFCOSE_NO_ES384 -DWOLFCOSE_NO_ES512 \ + -fsyntax-only $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: Sign1 flag accepted without an ECDSA verifier"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ + -DNO_HMAC -fsyntax-only $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: Mac0 flag accepted without HMAC verification"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: UEID resolver flag accepted without PSA/EAT"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: component iterator flag accepted without PSA/EAT"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_NO_SIGN1_SIGN \ + -fsyntax-only $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: issuer flag accepted without a creation path"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE -fsyntax-only $(EAT_PSA_SRC) \ + >/dev/null 2>&1; then \ + echo "FAIL: issuer flag accepted without current-profile support"; exit 1; \ + fi + @if $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) \ + -DWOLFCOSE_EAT_PSA_MAX_COMPONENTS=0u -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: PSA/EAT accepted zero components"; exit 1; \ + fi + @if $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) \ + -DWOLFCOSE_EAT_PSA_MAX_CLAIMS=9u -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: PSA/EAT accepted fewer than ten token claims"; exit 1; \ + fi + @if $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) \ + -DWOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS=4u -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: PSA/EAT accepted fewer than five component claims"; exit 1; \ + fi + @echo "PASS: PSA/EAT feature-gate matrix" + # --- Zeroize-hook test: asserts secret-scrubbing call sites actually run --- zeroize-test: $(CC) $(CFLAGS) -DWOLFCOSE_TEST_ZEROIZE_HOOK -DWOLFCOSE_TEST_LOG_ENABLE \ @@ -323,26 +617,51 @@ ext-sign-test: -o $(TEST_BIN) $(SRC) $(TEST_SRC) $(LDFLAGS) $(LDLIBS) ./$(TEST_BIN) +eat-psa-ext-sign-test: + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_ENABLE_EXT_SIGN \ + -o $(TEST_BIN) $(SRC) $(TEST_SRC) $(LDFLAGS) $(LDLIBS) + ./$(TEST_BIN) + # --- Coverage --- coverage: clean $(CC) $(CFLAGS) --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose_cbor.c -o src/wolfcose_cbor.o $(CC) $(CFLAGS) --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose.c -o src/wolfcose.o + $(CC) $(CFLAGS) --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose_eat_psa.c -o src/wolfcose_eat_psa.o $(AR) rcs $(LIB_A) $(OBJ) $(CC) $(CFLAGS) --coverage -fprofile-arcs -ftest-coverage -o $(TEST_BIN) $(TEST_SRC) $(LIB_A) $(LDFLAGS) $(LDLIBS) ./$(TEST_BIN) gcov src/*.c +eat-psa-coverage: clean + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose_cbor.c -o src/wolfcose_cbor.o + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose.c -o src/wolfcose.o + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose_eat_psa.c -o src/wolfcose_eat_psa.o + $(AR) rcs $(LIB_A) $(CORE_SRC:.c=.o) $(EAT_PSA_SRC:.c=.o) + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) --coverage -fprofile-arcs -ftest-coverage -o $(TEST_BIN) $(TEST_SRC) $(LIB_A) $(LDFLAGS) $(LDLIBS) + ./$(TEST_BIN) + gcov src/*.c + # --- Coverage with forced failure injection (for testing error paths) --- # See FORCE_FAILURE.md for documentation on this testing mechanism FORCE_FAIL_SRC = tests/force_failure.c coverage-force-failure: clean $(CC) $(CFLAGS) -DWOLFCOSE_FORCE_FAILURE --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose_cbor.c -o src/wolfcose_cbor.o $(CC) $(CFLAGS) -DWOLFCOSE_FORCE_FAILURE --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose.c -o src/wolfcose.o + $(CC) $(CFLAGS) -DWOLFCOSE_FORCE_FAILURE --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose_eat_psa.c -o src/wolfcose_eat_psa.o $(AR) rcs $(LIB_A) $(OBJ) $(CC) $(CFLAGS) -DWOLFCOSE_FORCE_FAILURE --coverage -fprofile-arcs -ftest-coverage -o $(TEST_BIN) $(TEST_SRC) $(FORCE_FAIL_SRC) $(LIB_A) $(LDFLAGS) $(LDLIBS) ./$(TEST_BIN) gcov src/*.c +eat-psa-coverage-force-failure: clean + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_FORCE_FAILURE --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose_cbor.c -o src/wolfcose_cbor.o + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_FORCE_FAILURE --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose.c -o src/wolfcose.o + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_FORCE_FAILURE --coverage -fprofile-arcs -ftest-coverage -c src/wolfcose_eat_psa.c -o src/wolfcose_eat_psa.o + $(AR) rcs $(LIB_A) $(CORE_SRC:.c=.o) $(EAT_PSA_SRC:.c=.o) + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_FORCE_FAILURE --coverage -fprofile-arcs -ftest-coverage -o $(TEST_BIN) $(TEST_SRC) $(FORCE_FAIL_SRC) $(LIB_A) $(LDFLAGS) $(LDLIBS) + ./$(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. @@ -351,6 +670,17 @@ ext-sign-force-failure: clean -o $(TEST_BIN) $(SRC) $(TEST_SRC) $(FORCE_FAIL_SRC) $(LDFLAGS) $(LDLIBS) ./$(TEST_BIN) +eat-psa-ext-sign-force-failure: clean + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_FORCE_FAILURE -DWOLFCOSE_ENABLE_EXT_SIGN \ + -o $(TEST_BIN) $(SRC) $(TEST_SRC) $(FORCE_FAIL_SRC) $(LDFLAGS) $(LDLIBS) + ./$(TEST_BIN) + +generic-reduced-alg-test: + $(MAKE) clean + $(MAKE) ext-sign-test EXTRA_CFLAGS='$(REDUCED_ALG_FLAGS)' + $(MAKE) clean + $(MAKE) coverage-force-failure EXTRA_CFLAGS='$(REDUCED_ALG_FLAGS)' + # --- CLI Tool (compiled out of core lib) --- tool: $(LIB_A) $(CC) $(CFLAGS) -DWOLFCOSE_BUILD_TOOL -o $(TOOL_BIN) $(TOOL_SRC) $(LIB_A) $(LDFLAGS) $(LDLIBS) @@ -395,6 +725,28 @@ lean-verify: @echo "=== Running lean verify-only example ===" ./$(LEANV_DEMO) +# --- Verify-only, full RFC 9783 #tfm PSA/EAT receiver --- +# A standardized #tfm receiver must retain both envelopes and all required +# algorithms. Issuance and optional PSA/EAT helpers remain compiled out. +psa-eat-lean-verify: + $(CC) $(CFLAGS) -DWOLFCOSE_LEAN_VERIFY -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -DWOLFCOSE_ENABLE_ES384 \ + -DWOLFCOSE_ENABLE_ES512 -DWOLFCOSE_ENABLE_HMAC384 \ + -DWOLFCOSE_ENABLE_HMAC512 \ + -o $(EAT_LEANV_DEMO) \ + $(EAT_LEANV_DEMO).c src/wolfcose.c src/wolfcose_cbor.c \ + src/wolfcose_eat_psa.c $(LDFLAGS) $(LDLIBS) + @echo "=== Running verify-only full RFC 9783 PSA/EAT receiver ===" + ./$(EAT_LEANV_DEMO) + +# --- RFC 9783 PSA/EAT device-onboarding example --- +psa-eat-demo: + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -o $(EAT_DEMO) \ + $(EAT_DEMO).c $(CORE_SRC) $(EAT_PSA_SRC) $(LDFLAGS) $(LDLIBS) + @echo "=== Running RFC 9783 PSA/EAT device onboarding ===" + ./$(EAT_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. @@ -461,12 +813,13 @@ INTEROP_DIR = tests/interop/t_cose INTEROP_BIN = $(INTEROP_DIR)/interop_tcose INTEROP_CFLAGS = $(CFLAGS) -std=c99 -I$(TCOSE_DIR)/inc -I$(QCBOR_DIR)/inc -interop-tcose: $(LIB_A) - $(CC) $(INTEROP_CFLAGS) -DT_COSE_USE_OPENSSL_CRYPTO -c $(INTEROP_DIR)/interop_tcose.c -o $(INTEROP_DIR)/interop_tcose.o +interop-tcose: + $(CC) $(INTEROP_CFLAGS) $(EAT_PSA_FULL_FLAGS) -DT_COSE_USE_OPENSSL_CRYPTO -c $(INTEROP_DIR)/interop_tcose.c -o $(INTEROP_DIR)/interop_tcose.o $(CC) -std=c99 -Wall -Wextra -I$(TCOSE_DIR)/inc -I$(QCBOR_DIR)/inc $(TCOSE_CRYPTO_INC) \ -c $(INTEROP_DIR)/interop_key_ossl.c -o $(INTEROP_DIR)/interop_key.o - $(CC) -o $(INTEROP_BIN) $(INTEROP_DIR)/interop_tcose.o $(INTEROP_DIR)/interop_key.o \ - $(LIB_A) $(TCOSE_DIR)/libt_cose.a $(QCBOR_DIR)/libqcbor.a \ + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -o $(INTEROP_BIN) $(CORE_SRC) $(EAT_PSA_SRC) \ + $(INTEROP_DIR)/interop_tcose.o $(INTEROP_DIR)/interop_key.o \ + $(TCOSE_DIR)/libt_cose.a $(QCBOR_DIR)/libqcbor.a \ $(TCOSE_CRYPTO_LIB) $(LDFLAGS) $(LDLIBS) -lm ./$(INTEROP_BIN) @@ -502,6 +855,16 @@ c99-check: -DNO_HMAC -DHAVE_AES_CBC -DWOLFCOSE_ENABLE_AESMAC \ -DWOLFCOSE_NO_RECIPIENTS -DWOLFCOSE_NO_ENCRYPT \ -fsyntax-only src/wolfcose.c + @for cfg in "$(EAT_PSA_FULL_FLAGS)" "$(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_ENABLE_EXT_SIGN"; do \ + for f in src/wolfcose_eat_psa.c tests/test_eat_psa.c $(EAT_LEANV_DEMO).c $(EAT_DEMO).c; do \ + echo " C99 PSA/EAT $$cfg $$f"; \ + $(CC) $(C99_FLAGS) $$cfg -fsyntax-only $$f || exit 1; \ + done; \ + done + $(CC) $(C99_FLAGS) $(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_TEST_EAT_PSA_LIMITS \ + -DWOLFCOSE_EAT_PSA_MAX_CLAIMS=10u \ + -DWOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS=5u -fsyntax-only \ + tests/test_eat_psa_limits.c @echo "PASS: all sources conform to ISO C99 (-pedantic-errors)" # Experimental-feature acknowledgement gate. Proves WOLFCOSE_EXPERIMENTAL guards @@ -535,8 +898,8 @@ experimental-check: # --- Cleanup --- clean: - rm -f $(OBJ) $(TEST_BIN) $(TOOL_BIN) $(DEMO_BIN) $(ENC_DEMO) $(MAC_DEMO) \ - $(EXTSIGN_DEMO) $(SIGN1_DEMO) $(COMP_SIGN) $(COMP_ENCRYPT) $(COMP_MAC) $(COMP_ERRORS) \ + rm -f $(OBJ) $(TEST_BIN) $(EAT_PSA_TEST_BIN) $(EAT_PSA_LIMITS_TEST_BIN) $(TOOL_BIN) $(DEMO_BIN) $(ENC_DEMO) $(MAC_DEMO) \ + $(EXTSIGN_DEMO) $(SIGN1_DEMO) $(LEANV_DEMO) $(EAT_LEANV_DEMO) $(EAT_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) $(BUILD_CONFIG) $(BUILD_CONFIG).tmp src/*.su tests/*.su examples/*.su examples/comprehensive/*.su examples/scenarios/*.su \ diff --git a/README.md b/README.md index 7195da9..5e87fcf 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # wolfCOSE -wolfCOSE is a lightweight C library implementing [CBOR (RFC 8949)](https://www.rfc-editor.org/rfc/rfc8949), [COSE (RFC 9052/9053)](https://www.rfc-editor.org/rfc/rfc9052), and post-quantum [ML-DSA for COSE (RFC 9964)](https://www.rfc-editor.org/rfc/rfc9964) using [wolfSSL](https://www.wolfssl.com/) as the crypto backend. +wolfCOSE is a lightweight C library implementing [CBOR (RFC 8949)](https://www.rfc-editor.org/rfc/rfc8949), [COSE (RFC 9052/9053)](https://www.rfc-editor.org/rfc/rfc9052), post-quantum [ML-DSA for COSE (RFC 9964)](https://www.rfc-editor.org/rfc/rfc9964), and the [PSA Attestation Token profile of EAT (RFC 9783)](https://www.rfc-editor.org/rfc/rfc9783) using [wolfSSL](https://www.wolfssl.com/) as the crypto backend. ## Main Features - **Complete RFC 9052 message set**: all six COSE message types, including multi-signer `COSE_Sign` and multi-recipient `COSE_Encrypt` / `COSE_Mac` - **Post-quantum signing**: ML-DSA (FIPS 204) at all three security levels, with RFC 9964 `COSE_Key` (AKP key type, seed-based private keys) +- **PSA attestation**: optional EAT / PSA Token consumption and current-profile + issuance, with Sign1, Mac0, legacy-token compatibility, and delegated PSA/HSM + signing - **40 algorithms** across signing, encryption, MAC, and key distribution - **Zero dynamic allocation**: heap-allocation-free and non-recursive. Every operation runs on caller-provided buffers within a bounded, target-customizable stack ceiling (nothing on the heap, zero `.data`/`.bss`) @@ -171,6 +174,11 @@ make demo | `make shared` | Build `libwolfcose.so` | | `make test` | Build + run CBOR and COSE unit tests | | `make pkg-config-test` | Verify wolfSSL package discovery and overrides | +| `make eat-psa-test` | Build + run the explicit full RFC 9783 PSA/EAT conformance suite | +| `make eat-psa-min-buffers-test` | Run the full PSA/EAT suite with `WOLFCOSE_MIN_BUFFERS` constrained-target limits | +| `make eat-psa-config-check` | Verify PSA/EAT is absent by default and validate feature-gate combinations | +| `make psa-eat-lean-verify` | Build + run the full `#tfm` verify-only PSA/EAT RFC vector example | +| `make psa-eat-demo` | Issue, verify, and appraise a current RFC 9783 device-onboarding token | | `make tool` | Build CLI tool (`tools/wolfcose_tool`) | | `make tool-test` | Round-trip self-test for all 17 algorithms | | `make demo` | Build + run lifecycle demo (11 algorithms) | @@ -185,6 +193,8 @@ See `examples/` for complete working code: - `lifecycle_demo.c`: full edge-to-cloud workflow - `comprehensive/`: algorithm matrix tests - `scenarios/`: firmware signing, attestation, fleet config +- `psa_eat_demo.c`: RFC 9783 device onboarding with measurement appraisal +- `psa_eat_verify_lean.c`: RFC 9783 current-profile Sign1 verification in a lean build ## CI / Testing @@ -228,6 +238,7 @@ Full documentation is available in the [Wiki](https://github.com/wolfSSL/wolfCOS - [Algorithms](https://github.com/wolfSSL/wolfCOSE/wiki/Algorithms): Complete list of 40 supported algorithms with COSE IDs - [API Reference](https://github.com/wolfSSL/wolfCOSE/wiki/API-Reference): Function signatures, data structures, error codes - [Macros](https://github.com/wolfSSL/wolfCOSE/wiki/Macros): Compile-time configuration, size tuning, and ECDSA nonce policy +- [PSA-EAT](https://github.com/wolfSSL/wolfCOSE/wiki/PSA-EAT): RFC 9783 PSA token profiles, APIs, macros, and security guidance - [Footprint](https://github.com/wolfSSL/wolfCOSE/wiki/Footprint): Size and speed numbers, desktop and on-device - [Testing](https://github.com/wolfSSL/wolfCOSE/wiki/Testing): Test infrastructure, coverage, and failure injection - [MISRA Compliance](https://github.com/wolfSSL/wolfCOSE/wiki/MISRA-Compliance): MISRA C:2012 and C:2023 compliance status and deviation rationale diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 8bf4ed5..b4b0b43 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -12,6 +12,7 @@ Complete API documentation for wolfCOSE (RFC 9052/9053 COSE implementation). - [COSE_Sign API (Multi-Signer)](#cose_sign-api-multi-signer) - [COSE_Encrypt API (Multi-Recipient)](#cose_encrypt-api-multi-recipient) - [COSE_Mac API (Multi-Recipient)](#cose_mac-api-multi-recipient) +- [PSA/EAT API](#psaeat-api) - [CBOR API](#cbor-api) - [Error Codes](#error-codes) @@ -108,13 +109,17 @@ Recipient information for COSE_Encrypt and COSE_Mac multi-recipient messages. ```c typedef struct WOLFCOSE_CBOR_CTX { - uint8_t* buf; /* Buffer pointer */ - size_t bufSz; /* Buffer size */ - size_t idx; /* Current position */ + uint8_t* buf; /* Encode output */ + const uint8_t* cbuf; /* Decode input */ + size_t bufSz; /* Buffer size */ + size_t idx; /* Current position */ } WOLFCOSE_CBOR_CTX; ``` -CBOR encoder/decoder context. +CBOR encoder/decoder context. Use the initializer appropriate to its mode; +the initializers clear the opposite pointer so encode operations cannot write +through decoder input. Variation-tolerant parsing is private to the optional +PSA/EAT verifier and is not stored in caller-owned context state. --- @@ -462,8 +467,10 @@ The decoded `kty`/`crv` must name the attached key type or key type a buffer holds before attaching anything, use [`wc_CoseKey_PeekInfo()`](#wc_cosekey_peekinfo). -Decoding is strict: preferred CBOR only, integer labels only, no duplicate -labels, and `bufSz` must be exactly the encoded length. See +Decoding is strict: preferred CBOR only, no duplicate integer or text labels, +and `bufSz` must be exactly the encoded length. Registered COSE_Key parameters +remain numeric; an unknown text label is treated as a non-critical extension. +See [Getting Started - Strict decoding](Getting-Started.md#strict-decoding-rfc-8949-preferred-serialization). Keys containing the optional `key_ops` label (4) return @@ -527,12 +534,14 @@ if (ret == WOLFCOSE_SUCCESS) { points into `in`, so it stays valid only as long as that buffer does. The same structural checks `wc_CoseKey_Decode()` applies are applied here - -integer labels only, no duplicate labels, `kty` required, no trailing bytes - +no duplicate integer or text labels, `kty` required, and no trailing bytes - so a buffer that peeks successfully will not be rejected by the decoder for -those reasons. Label `-1` is `crv` for EC2/OKP but `k`/`n` for symmetric/RSA -keys; the value is dispatched on its CBOR type, so `crv` stays 0 for the -latter. A `key_ops` label returns `WOLFCOSE_E_UNSUPPORTED`, matching decode. -On any error every field of `info` is cleared. +those reasons. Registered COSE_Key parameters remain numeric and an unknown +text label is treated as a non-critical extension. Label `-1` is `crv` for +EC2/OKP but `k`/`n` for symmetric/RSA keys; the value is dispatched on its +CBOR type, so `crv` stays 0 for the latter. A `key_ops` label returns +`WOLFCOSE_E_UNSUPPORTED`, matching decode. On any error every field of `info` +is cleared. **Returns:** `WOLFCOSE_SUCCESS` or error code @@ -1006,23 +1015,74 @@ pin the same algorithm. --- +## PSA/EAT API + +Available only when `WOLFCOSE_ENABLE_EAT_PSA` and the selected profile and +envelope gates are defined. Include ``. See [[PSA-EAT]] +for configuration and security requirements. + +### wc_CoseEatPsaToken_Verify + +```c +int wc_CoseEatPsaToken_Verify(const WOLFCOSE_KEY* key, + const uint8_t* in, size_t inSz, + const uint8_t* expectedNonce, size_t expectedNonceLen, + uint8_t* scratch, size_t scratchSz, + WOLFCOSE_EAT_PSA_TOKEN* token); +``` + +Authenticates a tagged, attached RFC 9783 current or selected legacy token, +checks profile-required claim structure and the expected nonce, then returns +zero-copy claim spans in `token`. The input buffer must remain unchanged while +the output token is used. `expectedNonce` is required and must be 32, 48, or +64 bytes. + +### Other PSA/EAT entry points + +| Function | Required feature gate | Purpose | +|----------|-----------------------|---------| +| `wc_CoseEatPsaToken_EncodeClaims` | `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` | Encode current-profile claims | +| `wc_CoseEatPsaToken_CreateSign1` | issue plus Sign1 creation | Encode and create a current Sign1 token | +| `wc_CoseEatPsaToken_CreateMac0` | issue plus Mac0 creation | Encode and create a current Mac0 token | +| `wc_CoseEatPsaToken_VerifyByUeid` | `WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER` | Resolve a candidate key from an untrusted UEID, then authenticate the original token | +| `wc_CoseEatPsaToken_ForEachComponent` | `WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR` | Decode authenticated software components one at a time | + +The three writable buffers passed to either creation API (`claimsBuf`, +`scratch`, and `out`) must be pairwise disjoint. Exact or partial overlap is +rejected with `WOLFCOSE_E_INVALID_ARG` before claims are encoded. +`claimsBuf` must additionally be disjoint from the claims structure, component +array, and every nonempty input span. The direct `EncodeClaims` output has the +same input-disjointness requirement; in-place encoding is rejected. + +The raw-key verifier rejects `x5chain`; validate certificates outside this API +before supplying a public key. Verification provides claims for caller policy +appraisal and does not itself authorize a device. + +`wc_CoseEatPsaToken_ForEachComponent()` supplies a component structure that is +valid only for its callback. The structure's span data borrows from the verified +input token and remains valid while that input remains unchanged. + +--- + ## CBOR API Basic CBOR encoding/decoding functions in `wolfcose.h`: ### Context Setup -`WOLFCOSE_CBOR_CTX` carries both a mutable `buf` (encode) and a const `cbuf` -(decode). These set the right one, clear the other, set `bufSz`, and zero -`idx` in a single call: +`WOLFCOSE_CBOR_CTX` carries a mutable `buf` for encode output and a const +`cbuf` for decode input. The initializers set the appropriate pointer, clear +the opposite pointer, set `bufSz`, and zero `idx` in a single call: ```c int wc_CBOR_EncoderInit(WOLFCOSE_CBOR_CTX* ctx, uint8_t* buf, size_t bufSz); int wc_CBOR_DecoderInit(WOLFCOSE_CBOR_CTX* ctx, const uint8_t* buf, size_t bufSz); ``` -Both are `static inline` in the header, so they cost nothing over assigning -the fields by hand. +Use these initializers instead of assigning context fields directly. +Public CBOR decoding always requires RFC 8949 preferred serialization. The +optional PSA/EAT verifier handles RFC 9783's permitted definite-length +variation serialization privately; indefinite-length forms remain unsupported. ```c WOLFCOSE_CBOR_CTX ctx; @@ -1069,9 +1129,14 @@ is `NULL`. | `wc_CBOR_SkipItem(ctx, data, dataLen)` | Skip an item and capture its raw bytes | | `wc_CBOR_PeekType(ctx)` | Peek at next item's major type | -> **Decoding is strict by design.** Every decode entry point requires -> RFC 8949 Section 4.2.1 preferred (shortest-form) arguments and rejects -> indefinite lengths. See [Getting Started - Strict +> **Decoding is strict by default.** Ordinary decode entry points require +> RFC 8949 Section 4.2.1 preferred (shortest-form) arguments and reject +> indefinite lengths. The optional PSA/EAT verifier privately admits the +> non-preferred definite-length forms RFC 9783 requires; it never admits +> indefinite lengths. `wc_CBOR_DecodeHead()`, `wc_CBOR_DecodeLabel()`, +> `wc_CBOR_Skip()`, and `wc_CBOR_SkipItem()` validate every text string they +> encounter, even when merely traversing it, and return +> `WOLFCOSE_E_CBOR_MALFORMED` for invalid UTF-8. See [Getting Started - Strict > decoding](Getting-Started.md#strict-decoding-rfc-8949-preferred-serialization) > before debugging an interop failure. @@ -1108,7 +1173,8 @@ if (ret == WOLFCOSE_SUCCESS) { } ``` -`wc_CBOR_Skip()` is unchanged. On failure the outputs are untouched. +`wc_CBOR_Skip()` and `wc_CBOR_SkipItem()` validate UTF-8 in every text string +they traverse. On failure the capture outputs are untouched. **Returns:** `WOLFCOSE_SUCCESS` or error code @@ -1130,10 +1196,11 @@ int wc_CBOR_LabelIsText(const WOLFCOSE_CBOR_LABEL* label, const uint8_t* text, size_t textLen); ``` -RFC 9052 defines `label = int / tstr`, and real COSE and CTAP2 maps use both -spellings for the same field (`3` vs `"alg"`, `1` vs `"type"`, `2` vs `"id"`). -`wc_CBOR_DecodeLabel()` consumes one item and reports whichever form it found, -so a parser writes the dispatch once instead of duplicating a +RFC 9052 defines `label = int / tstr`. Applications may use either form for +their own map parameters, while registered COSE parameters retain their +specified numeric labels. `wc_CBOR_DecodeLabel()` consumes one item and reports +whichever form it found, so a parser writes the dispatch once instead of +duplicating a `wc_CBOR_PeekType()` branch at every map. Major types 0 and 1 fill `val` with `isText == 0`; major type 3 fills @@ -1158,10 +1225,12 @@ else { } ``` -Note that `wc_CoseKey_Decode()` and the COSE header parsers accept integer -labels only, by design: silently skipping text labels would break their -duplicate-label enforcement. `wc_CBOR_DecodeLabel()` is for caller-written -parsers of protocol maps such as CTAP2. +`wc_CoseKey_Decode()` and the COSE header parsers accept both forms and +bytewise track text-label duplicates, including between protected and +unprotected header buckets. Registered COSE parameters remain numeric: a text +label such as `"alg"` or `"kty"` is an unknown extension, not an alias for +numeric labels 1 or 3. Unknown non-critical extensions are skipped; an +unknown entry listed in numeric `crit` is rejected. **Returns:** `WOLFCOSE_SUCCESS` or error code @@ -1189,6 +1258,10 @@ parsers of protocol maps such as CTAP2. | -9021 | `WOLFCOSE_E_UNSUPPORTED` | Feature not supported | | -9022 | `WOLFCOSE_E_MAC_FAIL` | MAC verification failed | | -9023 | `WOLFCOSE_E_DETACHED_PAYLOAD` | Detached payload required but not provided | +| -9030 | `WOLFCOSE_E_EAT_PSA_CLAIM` | PSA/EAT required claim is malformed, missing, or duplicated (PSA/EAT builds only) | +| -9031 | `WOLFCOSE_E_EAT_PSA_PROFILE` | Token does not match a selected PSA/EAT profile (PSA/EAT builds only) | +| -9032 | `WOLFCOSE_E_EAT_PSA_NONCE` | Authenticated token nonce does not match the expected nonce (PSA/EAT builds only) | +| -9033 | `WOLFCOSE_E_EAT_PSA_KEY` | PSA/EAT key resolution failed (PSA/EAT builds only) | --- @@ -1197,3 +1270,4 @@ parsers of protocol maps such as CTAP2. - [[Getting Started]]: Build instructions and examples - [[Algorithms]]: Supported algorithms - [[Macros]]: Compile-time configuration +- [[PSA-EAT]]: RFC 9783 PSA attestation support diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 8c5d11e..e66a2f7 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -342,6 +342,20 @@ The `examples/` directory contains complete working examples: | `encrypt0_demo.c` | All COSE_Encrypt0 algorithms | | `mac0_demo.c` | All COSE_Mac0 algorithms | | `lifecycle_demo.c` | Full edge-to-cloud workflow | +| `psa_eat_demo.c` | RFC 9783 device onboarding and component appraisal | +| `psa_eat_verify_lean.c` | Verify a fixed RFC 9783 token in a lean receiver | + +Run the complete PSA/EAT onboarding flow with: + +```bash +make psa-eat-demo +``` + +The demo hashes a sample secure-partition firmware image, creates a current +profile COSE_Sign1 token, selects a provisioned public IAK by UEID, proves that +a wrong challenge is rejected, verifies the correct challenge, and appraises +the authenticated lifecycle, device IDs, software measurement, signer ID, and +version against trusted reference values. ### Comprehensive Tests (`examples/comprehensive/`) @@ -364,14 +378,17 @@ The `examples/` directory contains complete working examples: ## Strict Decoding (RFC 8949 Preferred Serialization) -**Read this before filing an interop bug.** wolfCOSE's decoder accepts only -*deterministically encoded* CBOR. This is required by COSE (RFC 9052) and by -CTAP2 canonical CBOR, but it is stricter than most general-purpose CBOR -parsers, so on a device the symptom is usually "my authenticator rejects -requests from client X" rather than an obvious parse bug. +**Read this before filing an interop bug.** wolfCOSE's ordinary decoder APIs +accept only *deterministically encoded* CBOR. This is required by COSE +(RFC 9052) and by CTAP2 canonical CBOR, but it is stricter than most +general-purpose CBOR parsers, so on a device the symptom is usually "my +authenticator rejects requests from client X" rather than an obvious parse +bug. The optional RFC 9783 PSA/EAT verifier is a definite-length +variation-tolerant exception; that profile behavior is not exposed through the +ordinary COSE API. -Two rules apply at every decode entry point - `wc_CBOR_Decode*()`, -`wc_CoseKey_Decode()`, and every `_Verify` / `_Decrypt` function: +The following two rules apply to `wc_CBOR_Decode*()`, `wc_CoseKey_Decode()`, +and the ordinary `_Verify` / `_Decrypt` functions: | Rule | Example rejected input | Error | |------|------------------------|-------| @@ -385,17 +402,22 @@ Related strictness that surprises integrators for the same reason: [`wc_CBOR_SkipItem()`](API-Reference.md#wc_cbor_skipitem) to carve out the exact byte range of an embedded item. - Two-byte simple values below 32 are malformed, per RFC 8949. +- All encountered CBOR text strings are validated as UTF-8. This also applies + to `wc_CBOR_DecodeHead()`, `wc_CBOR_DecodeLabel()`, `wc_CBOR_Skip()`, and + `wc_CBOR_SkipItem()` when they only inspect or traverse the string; invalid + text returns `WOLFCOSE_E_CBOR_MALFORMED`. - EC2 coordinates must be exactly the curve size, with leading zeros preserved (RFC 9053 Section 7.1.1) - a 31-byte P-256 `x` is rejected, not left-padded. - A duplicate label in a header or `COSE_Key` map is rejected. -- `COSE_Key` and COSE header maps accept integer labels only. For your own - protocol maps that mix integer and text labels, use - [`wc_CBOR_DecodeLabel()`](API-Reference.md#wc_cbor_decodelabel). - -None of this is configurable: relaxing it would let a signature or MAC be -recomputed over a re-encoding of the same data, which is the class of bug -deterministic encoding exists to prevent. If a peer emits non-preferred CBOR, -fix the peer - it is not producing valid COSE. +- `COSE_Key` and COSE header maps accept integer and text labels, and reject + duplicate text labels just as they reject duplicate integer labels. Registered + COSE parameters are numeric; an unknown text label is skipped as a + non-critical extension, not treated as an alias such as `"alg"` for label 1. + +None of this is configurable for the ordinary API: relaxing it would let a +signature or MAC be recomputed over a re-encoding of the same data, which is +the class of bug deterministic encoding exists to prevent. The optional +PSA/EAT verifier's variation-tolerant path still rejects indefinite CBOR. ## Cross-Compilation diff --git a/docs/Home.md b/docs/Home.md index 4a58bc8..7fac437 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -17,6 +17,7 @@ It uses [wolfSSL](https://www.wolfssl.com/) as the cryptographic backend and is | Complete RFC 9052 | All six COSE message types — Sign1, Sign, Encrypt0, Encrypt, Mac0, Mac | | Multi-signer / multi-recipient | Full `COSE_Sign`, `COSE_Encrypt`, and `COSE_Mac` support | | Post-quantum signing | ML-DSA (FIPS 204) at all three security levels — first COSE library to ship native PQC | +| PSA attestation | Optional RFC 9783 EAT / PSA token verifier and issuer, including Sign1, Mac0, legacy consumption, and PSA/HSM delegated signing | | Zero dynamic allocation | Heap-allocation-free, non-recursive; caller-provided buffers within a bounded, target-customizable stack ceiling — zero `.data`/`.bss` | | Tiny footprint | 3.5–5.1 KB COSE engine, 5.1–6.8 KB with the built-in CBOR engine (verify-only → sign + verify); 26.2 KB → 34.6 KB total with wolfCrypt — ES256 `COSE_Sign1`, dead-code-eliminated | | 40 algorithms | Signing, encryption, MAC, and key distribution — classical and post-quantum | @@ -33,6 +34,7 @@ It uses [wolfSSL](https://www.wolfssl.com/) as the cryptographic backend and is | [[Algorithms]] | Complete list of supported algorithms with COSE IDs | | [[API Reference]] | Full API documentation for all functions | | [[Macros]] | Configuration macros and compile-time options | +| [[PSA-EAT]] | RFC 9783 PSA attestation profiles, APIs, and integration guidance | | [[Footprint]] | Size and speed numbers, desktop and on-device | | [[Testing]] | Unit tests, coverage, and failure injection | | [[Project Structure]] | Source code layout and file descriptions | diff --git a/docs/Macros.md b/docs/Macros.md index 3d7575f..a5b984b 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -51,15 +51,57 @@ Extension names for `WOLFCOSE_ENABLE_`: `ES384`, `ES512`, `EDDSA`, `ED448`, ` An extension is compiled in when it is explicitly enabled (`WOLFCOSE_ENABLE_`), or — in a non-lean build — when wolfSSL provides the primitive and it is not opted out with `WOLFCOSE_NO_`. Enabling an extension wolfSSL cannot provide is a compile error. The resolved state is exposed internally as read-only `WOLFCOSE_HAVE_` gates (e.g. `WOLFCOSE_HAVE_MLDSA`); sources, tests, and examples compile against those, so you set `WOLFCOSE_ENABLE_*`/`WOLFCOSE_NO_*`, not `WOLFCOSE_HAVE_*`. +## PSA/EAT Attestation Gates + +PSA/EAT support is off in every build, including a non-lean full build. The +default archive does not contain `wc_CoseEatPsaToken_*` symbols or the PSA/EAT +parser. Define the common switch, one or more profiles, and one or more COSE +envelope families deliberately. The three PSA/EAT limit macros below are also +defined and checked only after the common switch resolves to enabled. + +| Define | Description | Default | +|--------|-------------|---------| +| `WOLFCOSE_ENABLE_EAT_PSA` | Common PSA/EAT API and parser | off | +| `WOLFCOSE_ENABLE_EAT_PSA_CURRENT` | RFC 9783 TF-M current profile | off | +| `WOLFCOSE_ENABLE_EAT_PSA_SIGN1` | Tagged Sign1 consumption | off | +| `WOLFCOSE_ENABLE_EAT_PSA_MAC0` | Tagged Mac0 consumption | off | +| `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` | Current-profile claim and token issuance | off | +| `WOLFCOSE_ENABLE_EAT_PSA_LEGACY` | Legacy `PSA_IOT_PROFILE_1` consumption | off | +| `WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER` | UEID-selected key lookup helper | off | +| `WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR` | Zero-copy component callback helper | off | +| `WOLFCOSE_EAT_PSA_TFM_FULL` | Derived: all RFC 9783 `#tfm` receiver algorithms/envelopes are present; do not define manually | derived | +| `WOLFCOSE_EAT_PSA_MAX_COMPONENTS` | Maximum accepted software-component maps | 32 | +| `WOLFCOSE_EAT_PSA_MAX_CLAIMS` | Maximum claim-map entries, including extension claims | 64 | +| `WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS` | Maximum entries in each component map, including extensions | 16 | + +`WOLFCOSE_ENABLE_EAT_PSA_ISSUE` requires the current profile plus an enabled +Sign1 signing or Mac0 creation path. Sign1 consumption requires an enabled +ES256, ES384, or ES512 verifier; Mac0 consumption requires an enabled HMAC +verifier. The normal `WOLFCOSE_ENABLE_ES384`, `WOLFCOSE_ENABLE_ES512`, +`WOLFCOSE_ENABLE_HMAC384`, and `WOLFCOSE_ENABLE_HMAC512` macros choose the +non-core RFC 9783 algorithms in a lean build. `WOLFCOSE_NO_*` macros remove +their branches in a full build. + +RFC 9783 Section 5.2 requires a receiver advertising the standardized +`tag:psacertified.org,2023:psa#tfm` profile to accept tagged Sign1 and Mac0 +tokens with ES256/384/512 and HMAC 256/256, 384/384, and 512/512. wolfCOSE +derives `WOLFCOSE_EAT_PSA_TFM_FULL` only when that complete set is enabled. +That is a receiver-conformance gate only. An attester that enables issuance +and one RFC 9783 Sign1 or Mac0 creation path can issue a correctly labelled +`#tfm` token. A selective current-profile verifier still rejects `#tfm` with +`WOLFCOSE_E_EAT_PSA_PROFILE` and must not advertise full receiver conformance. + +See [[PSA-EAT]] for examples, profile boundaries, and security guidance. + ## Algorithm Gates Per-algorithm opt-outs for the default (non-lean) build. Each also has a `WOLFCOSE_ENABLE_` form for lean opt-in. `ES256`, `AESGCM`, and `HMAC256` form the lean core and stay on unless explicitly opted out. | Opt-out | Algorithm | wolfSSL requirement | |---------|-----------|---------------------| -| `WOLFCOSE_NO_ES256` | ECDSA P-256 (ES256) | `HAVE_ECC` | -| `WOLFCOSE_NO_ES384` | ECDSA P-384 (ES384) | `HAVE_ECC` + `WOLFSSL_SHA384` | -| `WOLFCOSE_NO_ES512` | ECDSA P-521 (ES512) | `HAVE_ECC` + `WOLFSSL_SHA512` | +| `WOLFCOSE_NO_ES256` | ECDSA P-256 (ES256) | `HAVE_ECC`, SHA-256, P-256 not disabled by `NO_ECC256`, and `ECC_MIN_KEY_SZ <= 256` | +| `WOLFCOSE_NO_ES384` | ECDSA P-384 (ES384) | `HAVE_ECC`, `WOLFSSL_SHA384`, `HAVE_ECC384` or `HAVE_ALL_CURVES`, and `ECC_MIN_KEY_SZ <= 384` | +| `WOLFCOSE_NO_ES512` | ECDSA P-521 (ES512) | `HAVE_ECC`, `WOLFSSL_SHA512`, `HAVE_ECC521` or `HAVE_ALL_CURVES`, and `ECC_MIN_KEY_SZ <= 521` | | `WOLFCOSE_NO_EDDSA` | Ed25519 | `HAVE_ED25519` | | `WOLFCOSE_NO_ED448` | Ed448 | `HAVE_ED448` | | `WOLFCOSE_NO_RSAPSS` | RSA-PSS (PS256/384/512) | `WC_RSA_PSS` | @@ -67,7 +109,7 @@ Per-algorithm opt-outs for the default (non-lean) build. Each also has a `WOLFCO | `WOLFCOSE_NO_AESGCM` | AES-GCM | `HAVE_AESGCM` | | `WOLFCOSE_NO_AESCCM` | AES-CCM | `HAVE_AESCCM` | | `WOLFCOSE_NO_CHACHA20` | ChaCha20-Poly1305 | `HAVE_CHACHA` + `HAVE_POLY1305` | -| `WOLFCOSE_NO_HMAC256` | HMAC-SHA256 | HMAC (`NO_HMAC` unset) | +| `WOLFCOSE_NO_HMAC256` | HMAC-SHA256 | HMAC and SHA-256 (`NO_HMAC` and `NO_SHA256` unset) | | `WOLFCOSE_NO_HMAC384` | HMAC-SHA384 | `WOLFSSL_SHA384` | | `WOLFCOSE_NO_HMAC512` | HMAC-SHA512 | `WOLFSSL_SHA512` | | `WOLFCOSE_NO_AESMAC` | AES-CBC-MAC | `HAVE_AES_CBC` | @@ -339,7 +381,7 @@ Post-quantum sign + verify is within ~1 KB of classical ES256 (35.8 vs 34.6 KB), ### `WOLFCOSE_LEAN_VERIFY` — minimal verify-only -The smallest secure on-device profile: COSE_Sign1 verification only, the common case where a device verifies signed firmware or attestation while signing happens off-device on a server or HSM. It implies `WOLFCOSE_LEAN` plus `WOLFCOSE_NO_SIGN1_SIGN` (removing signing and, transitively, the RNG), `WOLFCOSE_NO_ENCRYPT0`, `WOLFCOSE_NO_MAC0`, `WOLFCOSE_NO_KEY_ENCODE`, and `WOLFCOSE_NO_KEY_DECODE`. Full RFC 9052 verification stays: header decode, crit enforcement, duplicate-label detection, and the Sig_structure rebuild. Sign1 verify must stay enabled; the build errors out if it is also disabled. +The smallest secure on-device profile: COSE_Sign1 verification only, the common case where a device verifies signed firmware or attestation while signing happens off-device on a server or HSM. It implies `WOLFCOSE_LEAN` plus `WOLFCOSE_NO_SIGN1_SIGN` (removing signing and, transitively, the RNG), `WOLFCOSE_NO_ENCRYPT0`, `WOLFCOSE_NO_MAC0`, `WOLFCOSE_NO_MAC0_CREATE`, `WOLFCOSE_NO_KEY_ENCODE`, and `WOLFCOSE_NO_KEY_DECODE`. An explicit `WOLFCOSE_ENABLE_EAT_PSA_MAC0` selection retains Mac0 verification but not creation, so a verify-only full RFC 9783 `#tfm` receiver can opt in. Full RFC 9052 verification stays: header decode, crit enforcement, duplicate-label detection, and the Sig_structure rebuild. Sign1 verify must stay enabled; the build errors out if it is also disabled. ```bash make lean-verify # builds + runs examples/sign1_verify_lean.c with the profile diff --git a/docs/PSA-EAT.md b/docs/PSA-EAT.md new file mode 100644 index 0000000..0a01815 --- /dev/null +++ b/docs/PSA-EAT.md @@ -0,0 +1,292 @@ +# PSA Attestation and EAT Tokens + +wolfCOSE can consume and create the PSA Token profile of EAT specified by +[RFC 9783](https://www.rfc-editor.org/rfc/rfc9783). It is an explicit +opt-in: a normal `libwolfcose.a` has neither the PSA/EAT parser nor its public +API symbols. + +The API is designed for a verifier receiving the bytes returned by +`psa_initial_attest_get_token()`, a device onboarding service, a gateway, or a +remote attestation component such as wolfTrust. All storage remains owned by +the caller. Decoded claim spans borrow the authenticated input token. + +## Select only what is needed + +`WOLFCOSE_ENABLE_EAT_PSA` enables the common API. Select at least one profile +and at least one envelope family after it. + +| Define | Effect | Default | +|---|---|---| +| `WOLFCOSE_ENABLE_EAT_PSA` | Common PSA/EAT API and parser support | off | +| `WOLFCOSE_ENABLE_EAT_PSA_CURRENT` | RFC 9783 TF-M profile, `tag:psacertified.org,2023:psa#tfm` | off | +| `WOLFCOSE_ENABLE_EAT_PSA_SIGN1` | Tagged `COSE_Sign1` consumption | off | +| `WOLFCOSE_ENABLE_EAT_PSA_MAC0` | Tagged `COSE_Mac0` consumption | off | +| `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` | Current-profile claim encoding and token creation | off | +| `WOLFCOSE_ENABLE_EAT_PSA_LEGACY` | Legacy `PSA_IOT_PROFILE_1` consumption | off | +| `WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER` | `wc_CoseEatPsaToken_VerifyByUeid()` key lookup helper | off | +| `WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR` | `wc_CoseEatPsaToken_ForEachComponent()` callback helper | off | +| `WOLFCOSE_EAT_PSA_MAX_COMPONENTS` | Maximum accepted software-component maps | 32 | +| `WOLFCOSE_EAT_PSA_MAX_CLAIMS` | Maximum current/legacy claim-map entries, including extensions | 64 | +| `WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS` | Maximum entries in each component map, including extensions | 16 | + +The generic wolfCOSE algorithm gates remain authoritative. Select ES384, +ES512, HMAC384, and HMAC512 with their normal `WOLFCOSE_ENABLE_*` macros in a +lean build, or remove any supported algorithm with its `WOLFCOSE_NO_*` macro. +The PSA/EAT code has no algorithm fallback: a disabled algorithm is rejected +before claims are used. + +Minimal current Sign1 verifier: + +```text +-DWOLFCOSE_LEAN_VERIFY +-DWOLFCOSE_ENABLE_EAT_PSA +-DWOLFCOSE_ENABLE_EAT_PSA_CURRENT +-DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 +``` + +Current Mac0 verifier: + +```text +-DWOLFCOSE_LEAN +-DWOLFCOSE_ENABLE_EAT_PSA +-DWOLFCOSE_ENABLE_EAT_PSA_CURRENT +-DWOLFCOSE_ENABLE_EAT_PSA_MAC0 +``` + +These selective examples are useful COSE building blocks, but they are not +RFC 9783 `#tfm` receivers: Section 5.2 requires a `#tfm` receiver to accept +both envelope types and ES256/384/512 plus HMAC 256/256, 384/384, and +512/512. wolfCOSE therefore derives `WOLFCOSE_EAT_PSA_TFM_FULL` only for that +complete configuration. Without it, the PSA/EAT verifier rejects the +standard `tag:psacertified.org,2023:psa#tfm` value with +`WOLFCOSE_E_EAT_PSA_PROFILE`. This receiver requirement does not constrain an +attester: RFC 9783 permits it to issue `#tfm` with one enabled Table 4 Sign1 +or Mac0 protection algorithm. A reduced verifier must not advertise full +`#tfm` receiver conformance. + +A feature-complete test profile is available through `make eat-psa-test`. It +also enables current issuance, legacy consumption, UEID resolution, and +component iteration. `make eat-psa-config-check` proves the default archive +contains no PSA/EAT symbols and checks minimal, partial, and invalid +configurations. + +`make eat-psa-profile-test` then runs current Sign1-only, current Mac0-only, +legacy Sign1-only, and legacy Mac0-only binaries. It verifies that the two +partial current attesters can issue and generically authenticate `#tfm` with +their enabled envelope, while their incomplete PSA/EAT receivers reject it. +It also verifies that disabled standardized claim namespaces are not silently +skipped and that a disabled envelope returns `WOLFCOSE_E_UNSUPPORTED` before +cryptographic work. + +The claim-map limits are intentionally separate from `WOLFCOSE_MAX_MAP_ITEMS`: +EAT receivers accept extension claims, but must still bound input work while +checking that every standardized and extension key is unique. Set either limit +larger for an expected extension-rich token; the values must be at least 10 +and 5 respectively. + +`make eat-psa-claim-limits-test` recompiles a dedicated test with the minimum +valid ceilings (10 top-level claims and 5 component-map claims). It accepts +the exact boundary and rejects each respective `+1` map before iterating its +entries. + +`WOLFCOSE_EAT_PSA_TFM_FULL` is derived only when a build has current-profile +Sign1 and Mac0 verification plus ES256, ES384, ES512, HMAC256, HMAC384, and +HMAC512. That is the set RFC 9783 requires a TF-M profile receiver to support; +applications do not define this macro themselves; an externally supplied +definition is a configuration error. + +## API + +Include the API only in an enabled build: + +```c +#include +``` + +`wc_CoseEatPsaToken_Verify()` authenticates a tagged, attached COSE token with a +caller-owned `WOLFCOSE_KEY`, validates the selected PSA profile's required +claim shape, and compares the authenticated nonce. It fills a +`WOLFCOSE_EAT_PSA_TOKEN` only on success. + +```c +WOLFCOSE_EAT_PSA_TOKEN token; +int ret = wc_CoseEatPsaToken_Verify(&iak_key, token_bytes, token_size, + challenge, challenge_size, scratch, sizeof(scratch), &token); +if (ret == WOLFCOSE_SUCCESS) { + /* Appraise token.clientId, token.lifecycle, implementationId, and claims. */ +} +``` + +`expectedNonce` is mandatory and must be 32, 48, or 64 bytes. The output +spans, including `nonce`, `ueid`, `implementationId`, and `components`, point +into `token_bytes`; retain that buffer unchanged until appraisal is complete. + +### PSA client handoff + +wolfCOSE deliberately does not link against a PSA client library. A PSA +client obtains opaque bytes from the standard API, then passes them directly +to the verifier with a provisioned IAK key: + +```c +#include + +uint8_t token_bytes[1024u]; /* Select a capacity for the PSA platform. */ +size_t token_size = 0u; +psa_status_t psa_ret; + +psa_ret = psa_initial_attest_get_token_size(sizeof(challenge), &token_size); +if ((psa_ret == PSA_SUCCESS) && (token_size <= sizeof(token_bytes))) { + psa_ret = psa_initial_attest_get_token(challenge, sizeof(challenge), + token_bytes, sizeof(token_bytes), &token_size); +} +if (psa_ret == PSA_SUCCESS) { + ret = wc_CoseEatPsaToken_Verify(&provisioned_iak, token_bytes, token_size, + challenge, sizeof(challenge), scratch, sizeof(scratch), &token); +} +``` + +For a fleet verifier, enable the UEID resolver and map the untrusted UEID to a +candidate provisioned IAK. The token is authenticated only after that lookup. +This keeps the wolfCOSE library portable while allowing a PSA client such as +wolfTrust to use its normal `psa_initial_attest_get_token()` API. + +If the attestation key is selected by device identity, enable +`WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER` and use +`wc_CoseEatPsaToken_VerifyByUeid()`. The resolver sees an untrusted decoded UEID +only to choose a candidate key. wolfCOSE then verifies the original token +before returning any claims, so the resolver must not use that preliminary +UEID for authorization. + +If software-component appraisal is needed, enable +`WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR` and call +`wc_CoseEatPsaToken_ForEachComponent()` after successful verification. It decodes +one component at a time from the authenticated token, avoiding heap storage +for a component list. The callback's component structure is valid only for the +duration of that callback. Its span data borrows from the verified token input +and remains valid while that input remains unchanged. + +For a current-profile issuer, enable `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` and use: + +```c +wc_CoseEatPsaToken_CreateSign1(&issuer_key, WOLFCOSE_ALG_ES256, &claims, + claims_buf, sizeof(claims_buf), scratch, sizeof(scratch), token, + sizeof(token), &token_len, rng); +``` + +`wc_CoseEatPsaToken_CreateMac0()` is available only when both the Mac0 and issue +gates are selected. Issuance always emits the current RFC 9783 profile; +legacy is deliberately consume-only. An attester needs only one enabled +RFC 9783 Table 4 Sign1 or Mac0 creation path to emit the standardized `#tfm` +identifier. `WOLFCOSE_EAT_PSA_TFM_FULL` is deliberately separate: it proves a +receiver has every mandatory envelope and algorithm, so a selective verifier +continues to reject `#tfm` with `WOLFCOSE_E_EAT_PSA_PROFILE`. + +The issuer's `claimsBuf`, `scratch`, and output buffer ranges must be pairwise +disjoint. Either creation function rejects exact or partial overlap with +`WOLFCOSE_E_INVALID_ARG` before encoding claims and clears the output range. +`claimsBuf` is the output of `wc_CoseEatPsaToken_EncodeClaims()`, so it must +also be disjoint from the claims structure, component array, and every +nonempty claim or component span. The direct encoder enforces the same rule +for its output buffer; in-place claim encoding is not supported. + +The Sign1 issuer accepts an external signing key installed with +`wc_CoseKey_SetExtSigner()`. The callback receives the algorithm's digest of +the COSE `Sig_structure`, which maps directly to a PSA +`psa_sign_hash()` implementation. Pass `NULL` for `rng` when the external +signer owns all randomness. This is the intended wolfTrust and secure-element +integration path: wolfCOSE owns the standard claim encoding and COSE envelope, +while the IAK remains inside the PSA service, HSM, or secure partition. + +For wolfTrust, the migration path is to have the Initial Attestation service +populate `WOLFCOSE_EAT_PSA_CLAIMS`, install its existing secure-partition +signing callback, and call `wc_CoseEatPsaToken_CreateSign1()`. The returned bytes +can then be consumed by `wc_CoseEatPsaToken_Verify()` or the UEID resolver without +a second private claim parser. + +`make psa-eat-demo` runs the same application flow without a PSA service. It +measures a sample secure-partition image, issues a current-profile Sign1 token, +selects a provisioned public IAK by UEID, rejects a mismatched challenge, +authenticates the real challenge, and applies an onboarding policy to the +lifecycle, device IDs, signer, version, and software measurement. The example +is in `examples/psa_eat_demo.c`. + +The pre-RFC profile used by older wolfTrust trees (`http://arm.com/psa/2.0.0`) +is intentionally not treated as RFC 9783. A wolfTrust migration replaces its +manual claim encoder with these current-profile claims, uses the RFC 9783 +`tag:psacertified.org,2023:psa#tfm` profile value, and retains its existing +external IAK signer. That gives the PSA service a standards-conformant token +without moving private IAK material out of its secure partition. + +## Profile behavior + +| Capability | Current RFC 9783 TF-M | Legacy PSA IoT Profile 1 | +|---|---|---| +| Profile identifier | `tag:psacertified.org,2023:psa#tfm` | `PSA_IOT_PROFILE_1` | +| Consume | `WOLFCOSE_ENABLE_EAT_PSA_CURRENT` | `WOLFCOSE_ENABLE_EAT_PSA_LEGACY` | +| Issue | yes, with one enabled RFC 9783 Sign1 or Mac0 creation path | no | +| Required nonce | 32, 48, or 64 bytes | 32, 48, or 64 bytes | +| UEID | 33 bytes, first byte `0x01` | same | +| Boot seed | optional, 8 to 32 bytes when present | required, 32 bytes | +| Certification reference | current 13-digit plus version form | old 13-digit form | +| Components | one or more measurement components | components or explicit no-measurements form | + +For the current profile, wolfCOSE requires nonce, UEID, profile, nonzero +client ID, lifecycle, 32-byte implementation ID, and at least one software +component. Each component needs a 32, 48, or 64 byte measurement value and +signer ID. Optional values and unknown EAT claims are retained or skipped as +appropriate without weakening the required-claim checks. Both standardized +current and legacy label namespaces are always recognized: a disabled or mixed +namespace is rejected as `WOLFCOSE_E_EAT_PSA_PROFILE`, not treated as an +extension. + +The RFC 9783 `0x00xx` lifecycle range is structurally valid but represents an +unknown state. Verification retains it; an appraisal policy should normally +reject it for a trust decision. + +## COSE and trust boundary + +RFC 9783 specifies COSE tag 18 (`COSE_Sign1`) and tag 17 (`COSE_Mac0`) for +this profile. wolfCOSE accepts only an explicitly tagged, attached token and +requires its `alg` parameter in the protected header because this API does not +use externally supplied authenticated data. It rejects untagged messages, CWT +tag 61 wrappers, detached payloads, indefinite-length CBOR, unsupported PSA +algorithms, duplicate selected claims, and trailing bytes. + +RFC 9783 requires variation-tolerant reception of definite-length CBOR. +PSA/EAT verification therefore accepts non-preferred but well-formed definite +encodings. Ordinary wolfCOSE decoding remains strict by default. Unknown +non-critical COSE header extensions may use integer or text labels; text labels +are tracked for duplicates across protected and unprotected buckets, but are +not aliases for registered numeric headers. A text extension listed in `crit` +is rejected because this verifier does not implement it. + +The raw-key API rejects `x5chain` headers with `WOLFCOSE_E_UNSUPPORTED`. A +certificate chain is not proof of a trusted IAK until an application has +performed path building, trust-anchor selection, validity checking, and +revocation policy. Do that in a certificate-aware layer, then call the raw-key +API with the validated public key. + +Successful verification is not complete attestation appraisal. The relying +party must apply its own policy to the client ID, lifecycle, UEID, +implementation ID, signer IDs, component measurements, version data, and any +verification-service indicator. It must also retain replay state beyond the +nonce exchange when its protocol requires it. + +Mac0 is available because RFC 9783 defines it, but it uses a shared secret. +Use it only where shared-key provisioning and verifier identity fit the threat +model; Sign1 is normally the better choice for remotely provisioned device +attestation. + +## Validation and interoperability + +`make eat-psa-test` includes the RFC 9783 Appendix A Sign1 and Mac0 vectors, +which the RFC identifies as generated by TF-M's `iat-verifier`, plus current +ES256/384/512 and HMAC256/384/512 round trips, legacy cases, nonce checks, +and malformed-token negatives. `make eat-psa-claim-limits-test` covers exact +and plus-one configured claim limits. The verify-only, full-`#tfm` receiver is +exercised by `make psa-eat-lean-verify`. + +The pinned t_cose and QCBOR CI harness covers COSE wire interoperation in both +directions for current-profile Sign1 and Mac0 payloads. t_cose validates the +envelope produced by wolfCOSE; wolfCOSE validates a t_cose envelope and then +performs the PSA/EAT claim checks. See `tests/interop/t_cose/README.md`. diff --git a/docs/Release-Notes.md b/docs/Release-Notes.md index 2ce136e..4a37583 100644 --- a/docs/Release-Notes.md +++ b/docs/Release-Notes.md @@ -15,6 +15,20 @@ MAC, and key distribution, and standardized post-quantum ML-DSA signatures ### New Feature Additions +- Optional RFC 9783 PSA attestation / EAT Token support. It consumes tagged + current TF-M and legacy PSA IoT profile Sign1 or Mac0 tokens, validates the + required structural claims and nonce, and emits current-profile tokens with + one enabled RFC 9783 Sign1 or Mac0 protection path. Complete RFC 9783 + `#tfm` receiver capability remains a separately derived gate. + Every PSA/EAT profile, envelope, issuer, helper, and non-core algorithm is + explicitly macro-gated and absent from a default archive. The verifier + accepts RFC-required non-preferred definite CBOR while ordinary COSE decode + remains strict. Partial builds reject `#tfm` rather than claiming incomplete + conformance; profile, map-limit, Appendix A TF-M `iat-verifier`, and t_cose + interop coverage are in CI. A device-onboarding example demonstrates current + Sign1 issuance, nonce verification, and software measurement appraisal. See + [[PSA-EAT]]. + - CBOR engine implementing RFC 8949 encode/decode with no external dependency, enforcing deterministic/preferred-encoding rules and rejecting non-preferred or trailing input on decode. diff --git a/docs/Testing.md b/docs/Testing.md index 07790c9..2d4aa93 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,6 +1,6 @@ # Testing -wolfCOSE includes comprehensive testing infrastructure for unit tests, algorithm coverage, code coverage, and failure injection testing. Code coverage is enforced by CI: `wolfcose.c` requires 99% minimum and `wolfcose_cbor.c` requires 100%. These thresholds are validated on every push and PR to ensure coverage doesn't regress. +wolfCOSE includes comprehensive testing infrastructure for unit tests, algorithm coverage, code coverage, and failure injection testing. Code coverage is enforced by CI: `wolfcose.c` requires 99% minimum, `wolfcose_cbor.c` requires 100%, and the explicit full `wolfcose_eat_psa.c` profile requires 80%. These thresholds are validated on every push and PR to ensure coverage doesn't regress. ## Running Tests @@ -16,6 +16,55 @@ This runs the full test suite including: - COSE Sign/Encrypt/Mac multi-party tests - Interoperability tests with COSE Working Group vectors +### PSA/EAT Attestation Tests + +```bash +make eat-psa-test +make eat-psa-float-test +make eat-psa-min-buffers-test +make eat-psa-claim-limits-test +make eat-psa-profile-test +make eat-psa-config-check +make psa-eat-lean-verify +make psa-eat-demo +``` + +`eat-psa-test` builds the explicit full RFC 9783 profile and runs the Appendix +A Sign1 and Mac0 vectors, current and legacy profile cases, current algorithm +round trips, external signing, and malformed-token negatives. The vectors were +generated by TF-M's `iat-verifier` as documented by RFC 9783. + +`eat-psa-min-buffers-test` reruns that full profile with +`WOLFCOSE_MIN_BUFFERS`. It proves that the PSA/EAT claim parser accepts the +complete current-profile fixture even when the ordinary COSE header-map limit +is reduced for constrained targets. + +`eat-psa-float-test` recompiles the full profile with `WOLFCOSE_FLOAT` and +executes both floating-point encoder safety checks against a tolerant decoder +context. + +`eat-psa-claim-limits-test` recompiles a focused full profile with the minimum +valid `WOLFCOSE_EAT_PSA_MAX_CLAIMS` and +`WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS` values. It accepts exact-size maps and +rejects a top-level or component-map declaration one entry over its limit. + +`eat-psa-profile-test` runs separate current Sign1-only, current Mac0-only, +legacy Sign1-only, and legacy Mac0-only binaries. It proves partial current +attesters can issue and generically authenticate `#tfm` with their enabled +envelope, while their incomplete PSA/EAT receivers reject that profile. It +also proves disabled standardized claim namespaces are rejected instead of +skipped, and a structurally valid token using its compiled-out envelope +returns `WOLFCOSE_E_UNSUPPORTED` before cryptographic verification. + +`eat-psa-config-check` proves that the default library has no PSA/EAT symbols, +then compiles minimal current and legacy Sign1/Mac0, issuance-only, and invalid +gate combinations. It also checks the 384, 521, and above-P-521 +`ECC_MIN_KEY_SZ` boundaries and rejects PSA/EAT resource limits below their +structural floors. `psa-eat-lean-verify` runs the RFC Sign1 and Mac0 vectors +against a verify-only, full-`#tfm` receiver build. `psa-eat-demo` runs a complete issue, +challenge verification, and software-component appraisal workflow. +See [[PSA-EAT]]. + ### CLI Tool Tests ```bash @@ -66,6 +115,10 @@ HMAC 256/384/512, and AES-GCM 128/192/256. The bytes on the wire are the only interface; the two APIs are never reconciled. Each primitive class also exercises a tamper case that wolfCOSE must reject. +The same pinned harness also performs current-profile PSA/EAT Sign1 and Mac0 +interoperability. t_cose verifies wolfCOSE's envelope bytes; wolfCOSE verifies +the t_cose envelope and validates the RFC 9783 claims. + t_cose and QCBOR are BSD-3-Clause and are not vendored; the [Interop CI job](../.github/workflows/interop.yml) fetches them at pinned SHAs. See `tests/interop/t_cose/README.md` for the fixed test-key provenance. @@ -78,9 +131,12 @@ SHAs. See `tests/interop/t_cose/README.md` for the fixed test-key provenance. ```bash make coverage +make eat-psa-coverage ``` -This compiles with gcov instrumentation and runs tests, producing coverage reports. +`coverage` keeps the default PSA/EAT-free library configuration. The explicit +`eat-psa-coverage` target enables the full RFC 9783 receiver profile before +collecting the PSA/EAT source coverage report. ### Coverage Targets @@ -88,14 +144,18 @@ This compiles with gcov instrumentation and runs tests, producing coverage repor |-----------|--------| | `wolfcose.c` | 99% minimum | | `wolfcose_cbor.c` | 100% minimum | +| `wolfcose_eat_psa.c` | 80% minimum, full RFC 9783 feature profile | ### Coverage with Failure Injection ```bash make coverage-force-failure +make eat-psa-coverage-force-failure ``` -This enables additional coverage by testing error paths that normally require wolfCrypt internal failures. +The `eat-psa-*` form runs the corresponding full PSA/EAT profile. Both forms +enable additional coverage by testing error paths that normally require +wolfCrypt internal failures. --- diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md index 6683ebe..dff8e42 100644 --- a/docs/_Sidebar.md +++ b/docs/_Sidebar.md @@ -6,6 +6,7 @@ - [[Algorithms]] - [[API Reference]] - [[Macros]] +- [[PSA-EAT]] - [[Footprint]] - [[Testing]] - [[MISRA Compliance]] diff --git a/examples/lifecycle_demo.c b/examples/lifecycle_demo.c index 200c170..bda30dc 100644 --- a/examples/lifecycle_demo.c +++ b/examples/lifecycle_demo.c @@ -79,12 +79,12 @@ static int encode_sensor_payload(uint8_t* payload, size_t payloadSz, int ret; WOLFCOSE_CBOR_CTX cbor; - cbor.buf = payload; - cbor.bufSz = payloadSz; - cbor.idx = 0; + ret = wc_CBOR_EncoderInit(&cbor, payload, payloadSz); /* {"temp": 22, "humidity": 45} */ - ret = wc_CBOR_EncodeMapStart(&cbor, 2); + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&cbor, 2); + } if (ret == 0) { ret = wc_CBOR_EncodeTstr(&cbor, (const uint8_t*)"temp", 4); } diff --git a/examples/psa_eat_demo.c b/examples/psa_eat_demo.c new file mode 100644 index 0000000..786c4fa --- /dev/null +++ b/examples/psa_eat_demo.c @@ -0,0 +1,370 @@ +/* psa_eat_demo.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + * + * wolfCOSE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCOSE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* RFC 9783 device-onboarding example. + * + * Build and run with: + * make psa-eat-demo + * + * The device measures a firmware image and issues a COSE_Sign1 PSA token. + * The verifier authenticates the challenge, checks device policy, and + * appraises the authenticated software component against a reference value. + */ + +#include +#include + +#include +#include +#include +#include + +#ifndef WOLFCOSE_EAT_PSA_TFM_FULL + #error "The onboarding demo requires a complete RFC 9783 receiver" +#endif + +#if !defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) || \ + !defined(WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR) + #error "The onboarding demo requires Sign1 issue and component iteration" +#endif + +#define DEMO_PSA_SECURED_MAJOR 0x3000u + +static const uint8_t kDeviceUeid[33] = { + 0x01, 0xA4, 0x56, 0x51, 0xDB, 0x52, 0x7A, 0x39, + 0x2C, 0xE8, 0x20, 0x23, 0x61, 0x15, 0x6D, 0x2A, + 0x8B, 0x92, 0x5F, 0x01, 0x48, 0x91, 0xC3, 0x37, + 0x6A, 0x73, 0x04, 0xE1, 0x58, 0xB4, 0x2D, 0xC9, + 0x70 +}; + +static const uint8_t kImplementationId[32] = { + 0x90, 0x1C, 0x9A, 0x06, 0x6A, 0xAE, 0xE7, 0x20, + 0xC2, 0xB8, 0x1D, 0xE5, 0xE3, 0x4A, 0x44, 0xF0, + 0x55, 0xB4, 0xB2, 0xD6, 0x12, 0x90, 0x4D, 0x3D, + 0x29, 0x63, 0x15, 0x25, 0x42, 0x93, 0xC7, 0xD1 +}; + +static const uint8_t kSignerId[32] = { + 0x4F, 0xC4, 0xA7, 0x26, 0x8D, 0xC2, 0xA1, 0xD4, + 0x0E, 0x7B, 0x45, 0x90, 0x66, 0x63, 0x21, 0xB3, + 0x7E, 0xA1, 0x25, 0xC1, 0x47, 0x74, 0x8A, 0x19, + 0x28, 0x3A, 0xE1, 0x5C, 0x84, 0x11, 0x36, 0xB0 +}; + +static const uint8_t kApprovedMeasurement[WC_SHA256_DIGEST_SIZE] = { + 0x0B, 0xBE, 0x1B, 0x80, 0xCD, 0x71, 0xAB, 0x92, + 0x09, 0x7D, 0x92, 0xAE, 0x9A, 0x6D, 0x34, 0x00, + 0xFB, 0xCE, 0x4A, 0xF1, 0x20, 0x19, 0x31, 0x0F, + 0xA6, 0xFB, 0x47, 0xD8, 0xD6, 0x22, 0x14, 0x9A +}; + +static const uint8_t kFirmwareImage[] = + "wolfTrust secure partition firmware v1.4.2"; +static const uint8_t kComponentType[] = "PRoT"; +static const uint8_t kComponentVersion[] = "1.4.2"; +static const uint8_t kMeasurementDescription[] = "sha-256"; + +typedef struct DEMO_APPRAISAL_CTX { + const uint8_t* expectedMeasurement; + size_t expectedMeasurementLen; + size_t componentCount; +} DEMO_APPRAISAL_CTX; + +typedef struct DEMO_RESOLVER_CTX { + const WOLFCOSE_KEY* verifyKey; +} DEMO_RESOLVER_CTX; + +static int span_matches(const WOLFCOSE_EAT_PSA_SPAN* span, + const uint8_t* expected, size_t expectedLen) +{ + return ((span != NULL) && (expected != NULL) && + (span->data != NULL) && (span->len == expectedLen) && + (memcmp(span->data, expected, expectedLen) == 0)) ? 1 : 0; +} + +static int appraise_component(void* ctx, + const WOLFCOSE_EAT_PSA_COMPONENT* component) +{ + DEMO_APPRAISAL_CTX* appraisal = (DEMO_APPRAISAL_CTX*)ctx; + int ret = WOLFCOSE_SUCCESS; + + if ((appraisal == NULL) || (component == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if ((span_matches(&component->measurementType, kComponentType, + sizeof(kComponentType) - 1u) == 0) || + (span_matches(&component->version, kComponentVersion, + sizeof(kComponentVersion) - 1u) == 0) || + (span_matches(&component->measurementDesc, + kMeasurementDescription, + sizeof(kMeasurementDescription) - 1u) == 0) || + (span_matches(&component->measurementValue, + appraisal->expectedMeasurement, + appraisal->expectedMeasurementLen) == 0) || + (span_matches(&component->signerId, kSignerId, + sizeof(kSignerId)) == 0)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else { + appraisal->componentCount++; + (void)printf("verifier: accepted %.*s firmware version %.*s\n", + (int)component->measurementType.len, + (const char*)component->measurementType.data, + (int)component->version.len, + (const char*)component->version.data); + } + + return ret; +} + +static int resolve_iak(void* ctx, WOLFCOSE_EAT_PSA_PROFILE profile, + const uint8_t* ueid, size_t ueidLen, int32_t alg, WOLFCOSE_KEY* key) +{ + DEMO_RESOLVER_CTX* resolver = (DEMO_RESOLVER_CTX*)ctx; + int ret = WOLFCOSE_SUCCESS; + + if ((resolver == NULL) || (resolver->verifyKey == NULL) || (key == NULL) || + (profile != WOLFCOSE_EAT_PSA_PROFILE_CURRENT) || + (alg != WOLFCOSE_ALG_ES256) || (ueid == NULL) || + (ueidLen != sizeof(kDeviceUeid)) || + (memcmp(ueid, kDeviceUeid, sizeof(kDeviceUeid)) != 0)) { + ret = WOLFCOSE_E_EAT_PSA_KEY; + } + else { + *key = *resolver->verifyKey; + } + + return ret; +} + +static int appraise_token(const WOLFCOSE_EAT_PSA_TOKEN* token, + const uint8_t* expectedMeasurement, size_t expectedMeasurementLen) +{ + DEMO_APPRAISAL_CTX appraisal; + int ret = WOLFCOSE_SUCCESS; + + if ((token == NULL) || (expectedMeasurement == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if ((token->profile != WOLFCOSE_EAT_PSA_PROFILE_CURRENT) || + (token->protection != WOLFCOSE_EAT_PSA_PROTECTION_SIGN1) || + (token->clientId != -1) || + ((token->lifecycle & 0xFF00u) != DEMO_PSA_SECURED_MAJOR) || + (span_matches(&token->ueid, kDeviceUeid, + sizeof(kDeviceUeid)) == 0) || + (span_matches(&token->implementationId, kImplementationId, + sizeof(kImplementationId)) == 0)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else { + (void)memset(&appraisal, 0, sizeof(appraisal)); + appraisal.expectedMeasurement = expectedMeasurement; + appraisal.expectedMeasurementLen = expectedMeasurementLen; + ret = wc_CoseEatPsaToken_ForEachComponent(token, + appraise_component, &appraisal); + if ((ret == WOLFCOSE_SUCCESS) && + (appraisal.componentCount != 1u)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + } + + return ret; +} + +static void set_claims(WOLFCOSE_EAT_PSA_CLAIMS* claims, + WOLFCOSE_EAT_PSA_COMPONENT* component, + const uint8_t* challenge, const uint8_t* bootSeed, + const uint8_t* measurement) +{ + (void)memset(claims, 0, sizeof(*claims)); + (void)memset(component, 0, sizeof(*component)); + + component->measurementType.data = kComponentType; + component->measurementType.len = sizeof(kComponentType) - 1u; + component->measurementValue.data = measurement; + component->measurementValue.len = WC_SHA256_DIGEST_SIZE; + component->version.data = kComponentVersion; + component->version.len = sizeof(kComponentVersion) - 1u; + component->signerId.data = kSignerId; + component->signerId.len = sizeof(kSignerId); + component->measurementDesc.data = kMeasurementDescription; + component->measurementDesc.len = sizeof(kMeasurementDescription) - 1u; + + claims->nonce.data = challenge; + claims->nonce.len = 32u; + claims->ueid.data = kDeviceUeid; + claims->ueid.len = sizeof(kDeviceUeid); + claims->implementationId.data = kImplementationId; + claims->implementationId.len = sizeof(kImplementationId); + claims->bootSeed.data = bootSeed; + claims->bootSeed.len = 32u; + claims->clientId = -1; + claims->lifecycle = DEMO_PSA_SECURED_MAJOR; + claims->components = component; + claims->componentCount = 1u; +} + +int main(void) +{ + WC_RNG rng; + ecc_key privateIak; + ecc_key publicIak; + WOLFCOSE_KEY signKey; + WOLFCOSE_KEY verifyKey; + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_TOKEN verified; + DEMO_RESOLVER_CTX resolver; + uint8_t challenge[32]; + uint8_t wrongChallenge[32]; + uint8_t bootSeed[32]; + uint8_t measurement[WC_SHA256_DIGEST_SIZE]; + uint8_t publicX[32]; + uint8_t publicY[32]; + uint8_t claimsBuf[768]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tokenBuf[1024]; + word32 publicXLen = (word32)sizeof(publicX); + word32 publicYLen = (word32)sizeof(publicY); + size_t tokenLen = 0u; + int ret; + int rngInitialized = 0; + int privateIakInitialized = 0; + int publicIakInitialized = 0; + int signKeyInitialized = 0; + int verifyKeyInitialized = 0; + + ret = wc_InitRng(&rng); + if (ret == 0) { + rngInitialized = 1; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_RNG_GenerateBlock(&rng, challenge, + (word32)sizeof(challenge)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_RNG_GenerateBlock(&rng, bootSeed, + (word32)sizeof(bootSeed)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_Sha256Hash(kFirmwareImage, + (word32)(sizeof(kFirmwareImage) - 1u), measurement); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_ecc_init(&privateIak); + if (ret == WOLFCOSE_SUCCESS) { + privateIakInitialized = 1; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_ecc_make_key_ex(&rng, 32, &privateIak, ECC_SECP256R1); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_ecc_export_public_raw(&privateIak, publicX, &publicXLen, + publicY, &publicYLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_ecc_init(&publicIak); + if (ret == WOLFCOSE_SUCCESS) { + publicIakInitialized = 1; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_ecc_import_unsigned(&publicIak, publicX, publicY, NULL, + ECC_SECP256R1); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&signKey); + if (ret == WOLFCOSE_SUCCESS) { + signKeyInitialized = 1; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_SetEcc(&signKey, WOLFCOSE_CRV_P256, &privateIak); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&verifyKey); + if (ret == WOLFCOSE_SUCCESS) { + verifyKeyInitialized = 1; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_SetEcc(&verifyKey, WOLFCOSE_CRV_P256, &publicIak); + } + if (ret == WOLFCOSE_SUCCESS) { + set_claims(&claims, &component, challenge, bootSeed, measurement); + ret = wc_CoseEatPsaToken_CreateSign1(&signKey, + WOLFCOSE_ALG_ES256, &claims, claimsBuf, sizeof(claimsBuf), + scratch, sizeof(scratch), tokenBuf, sizeof(tokenBuf), &tokenLen, + &rng); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)printf("device: issued %zu-byte RFC 9783 COSE_Sign1 token\n", + tokenLen); + resolver.verifyKey = &verifyKey; + (void)memcpy(wrongChallenge, challenge, sizeof(wrongChallenge)); + wrongChallenge[0] ^= 0x01u; + ret = wc_CoseEatPsaToken_VerifyByUeid(resolve_iak, &resolver, + tokenBuf, tokenLen, wrongChallenge, sizeof(wrongChallenge), + scratch, sizeof(scratch), &verified); + if (ret == WOLFCOSE_E_EAT_PSA_NONCE) { + (void)printf("verifier: rejected token for the wrong challenge\n"); + ret = WOLFCOSE_SUCCESS; + } + else if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_EAT_PSA_NONCE; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_VerifyByUeid(resolve_iak, &resolver, + tokenBuf, tokenLen, challenge, sizeof(challenge), scratch, + sizeof(scratch), &verified); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = appraise_token(&verified, kApprovedMeasurement, + sizeof(kApprovedMeasurement)); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)printf("onboarding: accepted secured device with one trusted " + "component\n"); + } + else { + (void)printf("onboarding: rejected token (%d)\n", ret); + } + + if (verifyKeyInitialized != 0) { + wc_CoseKey_Free(&verifyKey); + } + if (signKeyInitialized != 0) { + wc_CoseKey_Free(&signKey); + } + if (publicIakInitialized != 0) { + wc_ecc_free(&publicIak); + } + if (privateIakInitialized != 0) { + wc_ecc_free(&privateIak); + } + if (rngInitialized != 0) { + wc_FreeRng(&rng); + } + + return (ret == WOLFCOSE_SUCCESS) ? 0 : 1; +} diff --git a/examples/psa_eat_verify_lean.c b/examples/psa_eat_verify_lean.c new file mode 100644 index 0000000..eb739e9 --- /dev/null +++ b/examples/psa_eat_verify_lean.c @@ -0,0 +1,259 @@ +/* psa_eat_verify_lean.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + * + * wolfCOSE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCOSE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +/* Verify-only full RFC 9783 PSA/EAT receiver. + * + * Build with: + * make psa-eat-lean-verify + * + * A production caller obtains the byte string in kTokenHex from + * psa_initial_attest_get_token(), retains its challenge as the expected nonce, + * resolves the IAK public key from the UEID, and passes the raw token directly + * to wc_CoseEatPsaToken_Verify() or wc_CoseEatPsaToken_VerifyByUeid(). No PSA headers + * or dynamic allocation are required by wolfCOSE. + * + * The fixed inputs are the RFC 9783 Appendix A.1 Sign1 and Mac0 tokens, + * produced by the external TF-M iat-verifier reference implementation. They + * also use non-preferred CBOR lengths, which RFC 9783 requires a PSA token + * receiver to accept. + */ + +#include +#include + +#include +#include + +#ifndef WOLFCOSE_EAT_PSA_TFM_FULL + #error "This RFC 9783 #tfm example requires the complete receiver profile" +#endif + +static const uint8_t kExpectedNonce[32] = { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +static const uint8_t kIakX[32] = { + 0x4E, 0x5E, 0x22, 0x09, 0x9E, 0x3B, 0xCE, 0xB4, + 0x5B, 0x44, 0x6D, 0x13, 0x55, 0xFD, 0x1D, 0xC3, + 0xB5, 0x45, 0x94, 0x7B, 0x6F, 0xD7, 0xC1, 0xC8, + 0x9D, 0x88, 0x67, 0x98, 0xC3, 0x72, 0x6E, 0x8F +}; + +static const uint8_t kIakY[32] = { + 0x80, 0xD7, 0x0B, 0x84, 0x0B, 0x25, 0x6A, 0xAC, + 0x34, 0xA6, 0x2E, 0xDE, 0x10, 0x43, 0x36, 0x4F, + 0x04, 0x40, 0x95, 0xF0, 0x03, 0x47, 0x4B, 0x91, + 0xE0, 0x18, 0x20, 0x92, 0xAF, 0xB1, 0x3F, 0x2E +}; + +static const char kTokenHex[] = + "d28443a10126a0590100a819010058210102020202020202020202020202" + "0202020202020202020202020202020202020219095c5820000000000000" + "00000000000000000000000000000000000000000000000000000a582001" + "010101010101010101010101010101010101010101010101010101010101" + "0119095a1a7fffffff19095b19300019010978217461673a707361636572" + "7469666965642e6f72672c323032333a7073612374666d19010c48000000" + "000000000019095f81a30558200404040404040404040404040404040404" + "040404040404040404040404040404025820030303030303030303030303" + "0303030303030303030303030303030303030303016450526f545840786e" + "937a4c42667af3847399319ca95c7e7dbabdc9b50fdb8de3f6bff4ab82ff" + "80c42140e2a488000219e3e10663193da69c75f52b798ea10b2f7041a90e" + "8e5a"; + +static const uint8_t kMac0Key[64] = { + 0xDE, 0x03, 0x8B, 0x34, 0xAC, 0xA1, 0x25, 0x76, + 0x8C, 0x5E, 0x33, 0x57, 0xAB, 0x8D, 0x06, 0xB3, + 0x67, 0xB9, 0xAB, 0x0D, 0x7E, 0x8B, 0xE1, 0x24, + 0xED, 0xCA, 0x47, 0xFE, 0x03, 0x3A, 0x5B, 0xB7, + 0xA9, 0x3D, 0x30, 0x7F, 0xF2, 0x29, 0xAA, 0x36, + 0xFF, 0x24, 0x6C, 0x12, 0x95, 0x96, 0x4F, 0xAC, + 0xF7, 0x1A, 0xB7, 0xAA, 0x6E, 0xC4, 0xFD, 0x61, + 0x02, 0xB7, 0xB3, 0x98, 0x32, 0x55, 0xAD, 0x92 +}; + +static const char kMac0TokenHex[] = + "d18443a10105a0590100a8190100582101c557bd4fadc83f756fca2cd5ea" + "2dcc8b82159bb4e7453d6a744d4eecd6d0ac6019095c5820000000000000" + "00000000000000000000000000000000000000000000000000000a582001" + "010101010101010101010101010101010101010101010101010101010101" + "0119095a1a7fffffff19095b19300019010978217461673a707361636572" + "7469666965642e6f72672c323032333a7073612374666d19010c48000000" + "000000000019095f81a30558200404040404040404040404040404040404" + "040404040404040404040404040404025820030303030303030303030303" + "0303030303030303030303030303030303030303016450526f545820cf88" + "d330e7a5366a95cf744a4dbf0d50304d405edd8b2530e243eddbd3177820"; + +static int hex_nibble(uint8_t in, uint8_t* out) +{ + int ret = WOLFCOSE_SUCCESS; + + if (out == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if ((in >= (uint8_t)'0') && (in <= (uint8_t)'9')) { + *out = (uint8_t)(in - (uint8_t)'0'); + } + else if ((in >= (uint8_t)'a') && (in <= (uint8_t)'f')) { + *out = (uint8_t)(in - (uint8_t)'a' + 10u); + } + else { + ret = WOLFCOSE_E_INVALID_ARG; + } + + return ret; +} + +static int hex_decode(const char* hex, uint8_t* out, size_t outSz, + size_t* outLen) +{ + int ret = WOLFCOSE_SUCCESS; + size_t i; + size_t hexLen; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((hex == NULL) || (out == NULL) || (outLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + hexLen = strlen(hex); + if (((hexLen & 1u) != 0u) || ((hexLen / 2u) > outSz)) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < (hexLen / 2u)); i++) { + uint8_t high; + uint8_t low; + + ret = hex_nibble((uint8_t)hex[i * 2u], &high); + if (ret == WOLFCOSE_SUCCESS) { + ret = hex_nibble((uint8_t)hex[(i * 2u) + 1u], &low); + } + if (ret == WOLFCOSE_SUCCESS) { + out[i] = (uint8_t)((high << 4) | low); + } + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = hexLen / 2u; + } + } + + return ret; +} + +int main(void) +{ + ecc_key eccKey; + WOLFCOSE_KEY key; + WOLFCOSE_KEY macKey; + WOLFCOSE_EAT_PSA_TOKEN token; + uint8_t rawToken[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + size_t rawTokenLen = 0u; + int ret; + int rc = 1; + int eccInitialized = 0; + int keyInitialized = 0; + int macKeyInitialized = 0; + int sign1Verified = 0; + int mac0Verified = 0; + + ret = hex_decode(kTokenHex, rawToken, sizeof(rawToken), &rawTokenLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_ecc_init(&eccKey); + if (ret == WOLFCOSE_SUCCESS) { + eccInitialized = 1; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_ecc_import_unsigned(&eccKey, (byte*)kIakX, (byte*)kIakY, + NULL, ECC_SECP256R1); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInitialized = 1; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &eccKey); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_Verify(&key, rawToken, rawTokenLen, + kExpectedNonce, sizeof(kExpectedNonce), scratch, sizeof(scratch), + &token); + } + if ((ret == WOLFCOSE_SUCCESS) && + (token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT) && + (token.protection == WOLFCOSE_EAT_PSA_PROTECTION_SIGN1) && + (token.componentCount == 1u)) { + (void)printf("lean PSA/EAT verifier: RFC 9783 Sign1 token verified\n"); + sign1Verified = 1; + } + else { + (void)printf("lean PSA/EAT verifier: Sign1 failed (%d)\n", ret); + } + + if (sign1Verified != 0) { + ret = hex_decode(kMac0TokenHex, rawToken, sizeof(rawToken), + &rawTokenLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&macKey); + if (ret == WOLFCOSE_SUCCESS) { + macKeyInitialized = 1; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_SetSymmetric(&macKey, kMac0Key, sizeof(kMac0Key)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_Verify(&macKey, rawToken, rawTokenLen, + kExpectedNonce, sizeof(kExpectedNonce), scratch, sizeof(scratch), + &token); + } + if ((ret == WOLFCOSE_SUCCESS) && + (token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT) && + (token.protection == WOLFCOSE_EAT_PSA_PROTECTION_MAC0) && + (token.componentCount == 1u)) { + (void)printf("lean PSA/EAT verifier: RFC 9783 Mac0 token verified\n"); + mac0Verified = 1; + } + else { + (void)printf("lean PSA/EAT verifier: Mac0 failed (%d)\n", ret); + } + if ((sign1Verified != 0) && (mac0Verified != 0)) { + rc = 0; + } + + if (macKeyInitialized != 0) { + wc_CoseKey_Free(&macKey); + } + if (keyInitialized != 0) { + wc_CoseKey_Free(&key); + } + if (eccInitialized != 0) { + wc_ecc_free(&eccKey); + } + return rc; +} diff --git a/include/wolfcose/eat_psa.h b/include/wolfcose/eat_psa.h new file mode 100644 index 0000000..603cd1e --- /dev/null +++ b/include/wolfcose/eat_psa.h @@ -0,0 +1,328 @@ +/* eat_psa.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + * + * wolfCOSE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCOSE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef WOLFCOSE_EAT_PSA_H +#define WOLFCOSE_EAT_PSA_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef WOLFCOSE_EAT_PSA + +#define WOLFCOSE_EAT_PSA_PROFILE_TFM \ + "tag:psacertified.org,2023:psa#tfm" +#define WOLFCOSE_EAT_PSA_PROFILE_LEGACY "PSA_IOT_PROFILE_1" + +/* PSA/EAT-specific errors are intentionally absent from the base API when + * this optional feature is not enabled. */ +#define WOLFCOSE_E_EAT_PSA_CLAIM (-9030) +#define WOLFCOSE_E_EAT_PSA_PROFILE (-9031) +#define WOLFCOSE_E_EAT_PSA_NONCE (-9032) +#define WOLFCOSE_E_EAT_PSA_KEY (-9033) + +/* settings.h derives WOLFCOSE_EAT_PSA_TFM_FULL only when the complete RFC + * 9783 Section 5.2 #tfm receiver algorithm and envelope set is enabled. It + * is a receiver-conformance gate; an attester may issue #tfm using one enabled + * RFC 9783 Sign1 or Mac0 protection algorithm. */ + +typedef enum WOLFCOSE_EAT_PSA_PROFILE { + WOLFCOSE_EAT_PSA_PROFILE_NONE = 0, /**< No authenticated profile. */ + WOLFCOSE_EAT_PSA_PROFILE_CURRENT = 1, /**< RFC 9783 TF-M profile. */ + WOLFCOSE_EAT_PSA_PROFILE_OLD = 2 /**< PSA_IOT_PROFILE_1 compatibility. */ +} WOLFCOSE_EAT_PSA_PROFILE; + +typedef enum WOLFCOSE_EAT_PSA_PROTECTION { + WOLFCOSE_EAT_PSA_PROTECTION_NONE = 0, /**< No authenticated envelope. */ + WOLFCOSE_EAT_PSA_PROTECTION_SIGN1 = 1, /**< COSE_Sign1 envelope. */ + WOLFCOSE_EAT_PSA_PROTECTION_MAC0 = 2 /**< COSE_Mac0 envelope. */ +} WOLFCOSE_EAT_PSA_PROTECTION; + +typedef struct WOLFCOSE_EAT_PSA_SPAN { + const uint8_t* data; /**< Borrowed byte or text-string data. */ + size_t len; /**< Data length in bytes. */ +} WOLFCOSE_EAT_PSA_SPAN; + +typedef struct WOLFCOSE_EAT_PSA_COMPONENT { + WOLFCOSE_EAT_PSA_SPAN measurementType; /**< Optional text type. */ + WOLFCOSE_EAT_PSA_SPAN measurementValue; /**< Required 32, 48, or 64-byte hash. */ + WOLFCOSE_EAT_PSA_SPAN version; /**< Optional text version. */ + WOLFCOSE_EAT_PSA_SPAN signerId; /**< Required 32, 48, or 64-byte hash. */ + WOLFCOSE_EAT_PSA_SPAN measurementDesc; /**< Optional text description. */ +} WOLFCOSE_EAT_PSA_COMPONENT; + +typedef struct WOLFCOSE_EAT_PSA_CLAIMS { + WOLFCOSE_EAT_PSA_SPAN nonce; /**< Required 32, 48, or 64-byte challenge. */ + WOLFCOSE_EAT_PSA_SPAN ueid; /**< Required 33-byte UEID starting with 0x01. */ + WOLFCOSE_EAT_PSA_SPAN implementationId; /**< Required 32-byte implementation ID. */ + WOLFCOSE_EAT_PSA_SPAN bootSeed; /**< Optional current-profile 8 to 32-byte seed. */ + WOLFCOSE_EAT_PSA_SPAN certificationReference; /**< Optional 13-digit EAN-13, dash, and five-digit version. */ + WOLFCOSE_EAT_PSA_SPAN verificationServiceIndicator; /**< Optional text VSI. */ + int32_t clientId; /**< Required nonzero signed PSA client ID. */ + uint16_t lifecycle; /**< Required lifecycle; 0x00xx is structurally valid unknown state. */ + const WOLFCOSE_EAT_PSA_COMPONENT* components; /**< Required component array. */ + size_t componentCount; /**< Component count from 1 through configured maximum. */ +} WOLFCOSE_EAT_PSA_CLAIMS; + +typedef struct WOLFCOSE_EAT_PSA_TOKEN { + WOLFCOSE_EAT_PSA_PROFILE profile; /**< Authenticated current or legacy profile. */ + WOLFCOSE_EAT_PSA_PROTECTION protection; /**< Authenticated Sign1 or Mac0 type. */ + WOLFCOSE_EAT_PSA_SPAN nonce; /**< Authenticated challenge. */ + WOLFCOSE_EAT_PSA_SPAN ueid; /**< Authenticated UEID. */ + WOLFCOSE_EAT_PSA_SPAN implementationId; /**< Authenticated implementation ID. */ + WOLFCOSE_EAT_PSA_SPAN bootSeed; /**< Optional authenticated boot seed. */ + WOLFCOSE_EAT_PSA_SPAN certificationReference; /**< Optional EAN-13 + five-digit certification reference. */ + WOLFCOSE_EAT_PSA_SPAN verificationServiceIndicator; /**< Optional VSI. */ + int32_t clientId; /**< Authenticated nonzero PSA client ID. */ + uint16_t lifecycle; /**< Authenticated lifecycle; appraise 0x00xx by policy. */ + WOLFCOSE_EAT_PSA_SPAN components; /**< Borrowed encoded authenticated CBOR component array. */ + size_t componentCount; /**< Authenticated component count. */ + uint8_t noSoftwareMeasurements; /**< Legacy no-measurements assertion. */ +} WOLFCOSE_EAT_PSA_TOKEN; + +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) +/** + * \brief Resolve a candidate key from untrusted routing claims before verification. + * + * The callback must not grant authorization from these untrusted values. It + * initializes or populates \p key for a candidate IAK; wolfCOSE authenticates + * the original token only after this callback returns success. + * + * \param ctx Opaque application context supplied to VerifyByUeid(). + * \param profile Untrusted selected current or legacy profile indication. + * \param ueid Untrusted UEID bytes borrowed from the input token. + * \param ueidLen Length of \p ueid. + * \param alg Untrusted protected COSE algorithm indication. + * \param key Output caller-owned key description to use for verification. + * \return WOLFCOSE_SUCCESS to continue, or an application error returned + * unchanged by VerifyByUeid(). + */ +typedef int (*WOLFCOSE_EAT_PSA_KEY_RESOLVER)(void* ctx, + WOLFCOSE_EAT_PSA_PROFILE profile, + const uint8_t* ueid, size_t ueidLen, int32_t alg, + WOLFCOSE_KEY* key); +#endif + +#if defined(WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR) +/** + * \brief Consume one decoded component from an authenticated token. + * + * \param ctx Opaque application context supplied to ForEachComponent(). + * \param component Decoded component structure valid only during this + * callback. Its span data borrows from the authenticated + * input token and remains valid while that token is retained + * unchanged. + * \return WOLFCOSE_SUCCESS to continue. A nonzero application error stops + * traversal and is returned unchanged by the iterator. + */ +typedef int (*WOLFCOSE_EAT_PSA_COMPONENT_CB)(void* ctx, + const WOLFCOSE_EAT_PSA_COMPONENT* component); +#endif + +/** + * \brief Encode current RFC 9783 PSA claims as a CBOR map. + * + * All input spans borrow caller-owned storage for the duration of this call. + * Optional spans are absent when data is NULL and len is zero. This API emits + * only the current RFC 9783 profile and never emits legacy claims. + * The output range must not overlap the claims structure, component array, or + * any nonempty claim/component span. Exact or partial overlap returns + * WOLFCOSE_E_INVALID_ARG before CBOR encoding starts. + * + * \param claims Current-profile claims to validate and encode. + * \param out Caller-owned CBOR output buffer. + * \param outSz Capacity of \p out in bytes. + * \param outLen Output encoded length. It is zero on every failure. + * An attester needs one enabled RFC 9783 Sign1 or Mac0 creation path to make + * a protected token. WOLFCOSE_EAT_PSA_TFM_FULL applies only to verification + * of the complete standardized receiver profile. + * + * \return WOLFCOSE_SUCCESS, WOLFCOSE_E_EAT_PSA_CLAIM, + * WOLFCOSE_E_CBOR_MALFORMED for invalid UTF-8, + * WOLFCOSE_E_BUFFER_TOO_SMALL, or WOLFCOSE_E_INVALID_ARG. + */ +#if defined(WOLFCOSE_EAT_PSA_ISSUE) +WOLFCOSE_API int wc_CoseEatPsaToken_EncodeClaims( + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* out, size_t outSz, size_t* outLen); +#endif + +#if defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) +/** + * \brief Encode current claims and create an attached COSE_Sign1 PSA token. + * + * The \p claimsBuf, \p scratch, and \p out buffer ranges must be pairwise + * disjoint. The claims structure, component array, and all nonempty input spans + * must also be disjoint from \p claimsBuf. Exact or partial overlap returns + * WOLFCOSE_E_INVALID_ARG before claims are encoded. + * + * \param key Signing key or an external signing callback key. + * \param alg Enabled RFC 9783 ES256, ES384, or ES512 algorithm. + * \param claims Current-profile claims to validate and encode. + * \param claimsBuf Temporary caller-owned claims buffer. + * \param claimsBufSz Capacity of \p claimsBuf in bytes. + * \param scratch Caller-owned COSE signing workspace. + * \param scratchSz Capacity of \p scratch in bytes. + * \param out Caller-owned token output buffer. + * \param outSz Capacity of \p out in bytes. + * \param outLen Output token length, zeroed on every failure. + * \param rng RNG for a local private key. It may be NULL for an external + * signer that owns its randomness. + * \return WOLFCOSE_SUCCESS or a validation, COSE, cryptographic, buffer, + * argument, or unsupported-algorithm error. \p out is cleared and + * \p outLen is zero on failure. + */ +WOLFCOSE_API int wc_CoseEatPsaToken_CreateSign1(WOLFCOSE_KEY* key, int32_t alg, + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* claimsBuf, size_t claimsBufSz, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, size_t* outLen, + WC_RNG* rng); +#endif + +#if defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) +/** + * \brief Encode current claims and create an attached COSE_Mac0 PSA token. + * + * The \p claimsBuf, \p scratch, and \p out buffer ranges must be pairwise + * disjoint. The claims structure, component array, and all nonempty input spans + * must also be disjoint from \p claimsBuf. Exact or partial overlap returns + * WOLFCOSE_E_INVALID_ARG before claims are encoded. + * + * \param key Symmetric MAC key. + * \param alg Enabled RFC 9783 HMAC 256, 384, or 512 algorithm. + * \param claims Current-profile claims to validate and encode. + * \param claimsBuf Temporary caller-owned claims buffer. + * \param claimsBufSz Capacity of \p claimsBuf in bytes. + * \param scratch Caller-owned COSE MAC workspace. + * \param scratchSz Capacity of \p scratch in bytes. + * \param out Caller-owned token output buffer. + * \param outSz Capacity of \p out in bytes. + * \param outLen Output token length, zeroed on every failure. + * \return WOLFCOSE_SUCCESS or a validation, COSE, buffer, argument, or + * unsupported-algorithm error. \p out is cleared and \p outLen is + * zero on failure. + */ +WOLFCOSE_API int wc_CoseEatPsaToken_CreateMac0(const WOLFCOSE_KEY* key, + int32_t alg, const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* claimsBuf, size_t claimsBufSz, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, size_t* outLen); +#endif + +/** + * \brief Verify a current or legacy PSA token with a caller-owned key. + * + * The expected nonce is mandatory. The output borrows from the verified token + * buffer and remains valid while that buffer remains unchanged. The standard + * RFC 9783 #tfm profile is accepted only when settings.h derives + * WOLFCOSE_EAT_PSA_TFM_FULL; otherwise it returns WOLFCOSE_E_EAT_PSA_PROFILE. + * Legacy profile support is consume-only. The result is a verified token, not + * an authorization or appraisal decision. + * + * \param key Caller-owned verification or MAC key. + * \param in Tagged, attached COSE_Sign1 or COSE_Mac0 token bytes. + * \param inSz Length of \p in in bytes. + * \param expectedNonce Required challenge of 32, 48, or 64 bytes. + * \param expectedNonceLen Length of \p expectedNonce. + * \param scratch Caller-owned COSE verification workspace. + * \param scratchSz Capacity of \p scratch in bytes. + * \param token Output token. It is fully zeroed on every failure. + * \return WOLFCOSE_SUCCESS, a COSE verification error, + * WOLFCOSE_E_EAT_PSA_CLAIM, WOLFCOSE_E_EAT_PSA_PROFILE, + * WOLFCOSE_E_EAT_PSA_NONCE, or WOLFCOSE_E_INVALID_ARG. + */ +WOLFCOSE_API int wc_CoseEatPsaToken_Verify(const WOLFCOSE_KEY* key, + const uint8_t* in, size_t inSz, + const uint8_t* expectedNonce, size_t expectedNonceLen, + uint8_t* scratch, size_t scratchSz, + WOLFCOSE_EAT_PSA_TOKEN* token); + +/** + * \brief Verify a PSA token after resolving its key using the untrusted UEID. + * + * The resolver receives routing data before cryptographic verification. It + * must select a candidate key only; policy and authorization decisions belong + * after this function succeeds. + * + * \param resolver Callback that supplies a caller-owned key description. + * \param resolverCtx Opaque callback context, which may be NULL. + * \param in Tagged, attached COSE_Sign1 or COSE_Mac0 token bytes. + * \param inSz Length of \p in in bytes. + * \param expectedNonce Required challenge of 32, 48, or 64 bytes. + * \param expectedNonceLen Length of \p expectedNonce. + * \param scratch Caller-owned COSE verification workspace. + * \param scratchSz Capacity of \p scratch in bytes. + * \param token Output token. It is fully zeroed on every failure. + * \return WOLFCOSE_SUCCESS or the resolver, COSE, claim, profile, nonce, or + * argument error. Resolver errors are returned unchanged. + */ +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) +WOLFCOSE_API int wc_CoseEatPsaToken_VerifyByUeid( + WOLFCOSE_EAT_PSA_KEY_RESOLVER resolver, void* resolverCtx, + const uint8_t* in, size_t inSz, + const uint8_t* expectedNonce, size_t expectedNonceLen, + uint8_t* scratch, size_t scratchSz, + WOLFCOSE_EAT_PSA_TOKEN* token); +#endif + +/** + * \brief Traverse software components in a verified PSA token. + * + * \param token Successfully verified token whose backing input remains valid. + * \param cb Component callback. A nonzero return stops iteration. + * \param cbCtx Opaque callback context, which may be NULL. + * \return WOLFCOSE_SUCCESS, WOLFCOSE_E_EAT_PSA_CLAIM, + * WOLFCOSE_E_INVALID_ARG, or the callback's nonzero return value. + */ +#if defined(WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR) +WOLFCOSE_API int wc_CoseEatPsaToken_ForEachComponent( + const WOLFCOSE_EAT_PSA_TOKEN* token, + WOLFCOSE_EAT_PSA_COMPONENT_CB cb, void* cbCtx); +#endif + +/* Source aliases retain compatibility with the pre-release PSA/EAT branch. + * The wc_CoseEatPsaToken_* names are the exported public API. */ +#if defined(WOLFCOSE_EAT_PSA_ISSUE) + #define wc_EatPsaToken_EncodeClaims wc_CoseEatPsaToken_EncodeClaims +#endif +#if defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) + #define wc_EatPsaToken_CreateSign1 wc_CoseEatPsaToken_CreateSign1 +#endif +#if defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) + #define wc_EatPsaToken_CreateMac0 wc_CoseEatPsaToken_CreateMac0 +#endif +#define wc_EatPsaToken_Verify wc_CoseEatPsaToken_Verify +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) + #define wc_EatPsaToken_VerifyByUeid wc_CoseEatPsaToken_VerifyByUeid +#endif +#if defined(WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR) + #define wc_EatPsaToken_ForEachComponent wc_CoseEatPsaToken_ForEachComponent +#endif + +#endif /* WOLFCOSE_EAT_PSA */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* WOLFCOSE_EAT_PSA_H */ diff --git a/include/wolfcose/settings.h b/include/wolfcose/settings.h index 8886e88..0132712 100644 --- a/include/wolfcose/settings.h +++ b/include/wolfcose/settings.h @@ -59,9 +59,14 @@ extern "C" { #ifndef WOLFCOSE_NO_ENCRYPT0 #define WOLFCOSE_NO_ENCRYPT0 #endif - #ifndef WOLFCOSE_NO_MAC0 + /* A PSA/EAT Mac0 opt-in retains verification only for a full #tfm receiver. */ + #if !defined(WOLFCOSE_NO_MAC0) && \ + !defined(WOLFCOSE_ENABLE_EAT_PSA_MAC0) #define WOLFCOSE_NO_MAC0 #endif + #ifndef WOLFCOSE_NO_MAC0_CREATE + #define WOLFCOSE_NO_MAC0_CREATE + #endif #ifndef WOLFCOSE_NO_KEY_ENCODE #define WOLFCOSE_NO_KEY_ENCODE #endif @@ -136,30 +141,41 @@ extern "C" { /* ----- Signature algorithms ----- */ -/* ES256 — core (on whenever wolfSSL has ECC) */ -#if defined(HAVE_ECC) && !defined(WOLFCOSE_NO_ES256) +/* ES256 — core. ECC_USER_CURVES keeps P-256 unless NO_ECC256 selects it + * out; HAVE_ALL_CURVES is the equivalent all-curves configuration. */ +#if defined(HAVE_ECC) && !defined(NO_SHA256) && !defined(NO_ECC256) && \ + (!defined(ECC_MIN_KEY_SZ) || (ECC_MIN_KEY_SZ <= 256)) && \ + !defined(WOLFCOSE_NO_ES256) #define WOLFCOSE_HAVE_ES256 #endif -/* ES384 — extension */ +/* ES384 — extension. A custom curve build must opt into P-384 explicitly. */ #if defined(WOLFCOSE_ENABLE_ES384) - #if !defined(HAVE_ECC) || !defined(WOLFSSL_SHA384) - #error "WOLFCOSE_ENABLE_ES384 requires wolfSSL HAVE_ECC + WOLFSSL_SHA384" + #if !defined(HAVE_ECC) || !defined(WOLFSSL_SHA384) || \ + (!defined(HAVE_ECC384) && !defined(HAVE_ALL_CURVES)) || \ + (defined(ECC_MIN_KEY_SZ) && (ECC_MIN_KEY_SZ > 384)) + #error "WOLFCOSE_ENABLE_ES384 requires wolfSSL P-384 ECC + WOLFSSL_SHA384" #endif #define WOLFCOSE_HAVE_ES384 #elif !defined(WOLFCOSE_LEAN) && !defined(WOLFCOSE_NO_ES384) && \ - defined(HAVE_ECC) && defined(WOLFSSL_SHA384) + defined(HAVE_ECC) && defined(WOLFSSL_SHA384) && \ + (defined(HAVE_ECC384) || defined(HAVE_ALL_CURVES)) && \ + (!defined(ECC_MIN_KEY_SZ) || (ECC_MIN_KEY_SZ <= 384)) #define WOLFCOSE_HAVE_ES384 #endif -/* ES512 — extension */ +/* ES512 — extension. A custom curve build must opt into P-521 explicitly. */ #if defined(WOLFCOSE_ENABLE_ES512) - #if !defined(HAVE_ECC) || !defined(WOLFSSL_SHA512) - #error "WOLFCOSE_ENABLE_ES512 requires wolfSSL HAVE_ECC + WOLFSSL_SHA512" + #if !defined(HAVE_ECC) || !defined(WOLFSSL_SHA512) || \ + (!defined(HAVE_ECC521) && !defined(HAVE_ALL_CURVES)) || \ + (defined(ECC_MIN_KEY_SZ) && (ECC_MIN_KEY_SZ > 521)) + #error "WOLFCOSE_ENABLE_ES512 requires wolfSSL P-521 ECC + WOLFSSL_SHA512" #endif #define WOLFCOSE_HAVE_ES512 #elif !defined(WOLFCOSE_LEAN) && !defined(WOLFCOSE_NO_ES512) && \ - defined(HAVE_ECC) && defined(WOLFSSL_SHA512) + defined(HAVE_ECC) && defined(WOLFSSL_SHA512) && \ + (defined(HAVE_ECC521) || defined(HAVE_ALL_CURVES)) && \ + (!defined(ECC_MIN_KEY_SZ) || (ECC_MIN_KEY_SZ <= 521)) #define WOLFCOSE_HAVE_ES512 #endif @@ -269,7 +285,8 @@ extern "C" { /* ----- MAC algorithms ----- */ /* HMAC-SHA256 — core */ -#if !defined(NO_HMAC) && !defined(WOLFCOSE_NO_HMAC256) +#if !defined(NO_HMAC) && !defined(NO_SHA256) && \ + !defined(WOLFCOSE_NO_HMAC256) #define WOLFCOSE_HAVE_HMAC256 #endif @@ -522,6 +539,125 @@ extern "C" { #error "WOLFCOSE_NO_CBOR_DECODE conflicts with an enabled decode operation" #endif +/* ----- PSA EAT attestation tokens (RFC 9783) ----- + * + * PSA/EAT is always opt-in. The core current-profile decoder, every envelope + * family, issuance, legacy compatibility, and convenience helper are selected + * independently so an embedded verifier carries only the code it uses. + * Generic WOLFCOSE_ENABLE_ controls remain the algorithm gates. + * + * WOLFCOSE_ENABLE_EAT_PSA common PSA/EAT API and parser + * WOLFCOSE_ENABLE_EAT_PSA_CURRENT RFC 9783 TF-M claim profile + * WOLFCOSE_ENABLE_EAT_PSA_SIGN1 tagged Sign1 consumption + * WOLFCOSE_ENABLE_EAT_PSA_MAC0 tagged Mac0 consumption + * WOLFCOSE_ENABLE_EAT_PSA_ISSUE current-profile issuance + * WOLFCOSE_ENABLE_EAT_PSA_LEGACY PSA_IOT_PROFILE_1 consume + * WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER claim-based key lookup helper + * WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR component traversal helper + */ +#if defined(WOLFCOSE_ENABLE_EAT_PSA) + #define WOLFCOSE_EAT_PSA +#endif + +#if defined(WOLFCOSE_EAT_PSA) && !defined(WOLFCOSE_CBOR_DECODE) + #error "WOLFCOSE_ENABLE_EAT_PSA requires CBOR decoding" +#endif + +#if defined(WOLFCOSE_ENABLE_EAT_PSA_CURRENT) + #if !defined(WOLFCOSE_EAT_PSA) + #error "WOLFCOSE_ENABLE_EAT_PSA_CURRENT requires EAT_PSA" + #endif + #define WOLFCOSE_EAT_PSA_CURRENT +#endif + +#if defined(WOLFCOSE_ENABLE_EAT_PSA_LEGACY) + #if !defined(WOLFCOSE_EAT_PSA) + #error "WOLFCOSE_ENABLE_EAT_PSA_LEGACY requires EAT_PSA" + #endif + #define WOLFCOSE_EAT_PSA_LEGACY +#endif + +#if defined(WOLFCOSE_ENABLE_EAT_PSA_SIGN1) + #if !defined(WOLFCOSE_EAT_PSA) || !defined(WOLFCOSE_SIGN1_VERIFY) || \ + (!defined(WOLFCOSE_HAVE_ES256) && !defined(WOLFCOSE_HAVE_ES384) && \ + !defined(WOLFCOSE_HAVE_ES512)) + #error "WOLFCOSE_ENABLE_EAT_PSA_SIGN1 requires EAT_PSA and an ECDSA COSE Sign1 verifier" + #endif + #define WOLFCOSE_EAT_PSA_SIGN1 +#endif + +#if defined(WOLFCOSE_ENABLE_EAT_PSA_MAC0) + #if !defined(WOLFCOSE_EAT_PSA) || !defined(WOLFCOSE_MAC0_VERIFY) || \ + !defined(WOLFCOSE_HAVE_HMAC) + #error "WOLFCOSE_ENABLE_EAT_PSA_MAC0 requires EAT_PSA and HMAC COSE Mac0 verify" + #endif + #define WOLFCOSE_EAT_PSA_MAC0 +#endif + +#if defined(WOLFCOSE_EAT_PSA) && !defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + !defined(WOLFCOSE_EAT_PSA_LEGACY) + #error "WOLFCOSE_ENABLE_EAT_PSA needs EAT_PSA_CURRENT and/or EAT_PSA_LEGACY" +#endif + +#if defined(WOLFCOSE_EAT_PSA) && !defined(WOLFCOSE_EAT_PSA_SIGN1) && \ + !defined(WOLFCOSE_EAT_PSA_MAC0) + #error "WOLFCOSE_ENABLE_EAT_PSA needs EAT_PSA_SIGN1 and/or EAT_PSA_MAC0" +#endif + +#if defined(WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER) + #if !defined(WOLFCOSE_EAT_PSA) + #error "WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER requires EAT_PSA" + #endif + #define WOLFCOSE_EAT_PSA_UEID_RESOLVER +#endif + +#if defined(WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR) + #if !defined(WOLFCOSE_EAT_PSA) + #error "WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR requires EAT_PSA" + #endif + #define WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR +#endif + +#if defined(WOLFCOSE_ENABLE_EAT_PSA_ISSUE) + #if !defined(WOLFCOSE_EAT_PSA_CURRENT) || !defined(WOLFCOSE_CBOR_ENCODE) + #error "WOLFCOSE_ENABLE_EAT_PSA_ISSUE requires EAT_PSA_CURRENT and CBOR encode" + #endif + #define WOLFCOSE_EAT_PSA_ISSUE +#endif + +#if defined(WOLFCOSE_EAT_PSA_ISSUE) && defined(WOLFCOSE_EAT_PSA_SIGN1) && \ + defined(WOLFCOSE_SIGN1_SIGN) + #define WOLFCOSE_EAT_PSA_SIGN1_ISSUE +#endif + +#if defined(WOLFCOSE_EAT_PSA_ISSUE) && defined(WOLFCOSE_EAT_PSA_MAC0) && \ + defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_HAVE_HMAC) + #define WOLFCOSE_EAT_PSA_MAC0_ISSUE +#endif + +#if defined(WOLFCOSE_EAT_PSA_ISSUE) && \ + !defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) && \ + !defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) + #error "WOLFCOSE_ENABLE_EAT_PSA_ISSUE needs an enabled signing or MAC creation path" +#endif + +/* RFC 9783 Section 5.2 gives the standardized #tfm profile a fixed receiver + * capability floor: both COSE_Sign1 and COSE_Mac0, with ES256/384/512 and + * HMAC 256/256, 384/384, and 512/512. This is intentionally derived rather + * than user-selectable. It gates #tfm receiver conformance only: an attester + * may issue #tfm with one selected RFC-permitted Sign1 or Mac0 algorithm. */ +#if defined(WOLFCOSE_EAT_PSA_TFM_FULL) + #error "WOLFCOSE_EAT_PSA_TFM_FULL is derived; do not define it" +#endif + +#if defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + defined(WOLFCOSE_EAT_PSA_SIGN1) && defined(WOLFCOSE_EAT_PSA_MAC0) && \ + defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_HAVE_ES384) && \ + defined(WOLFCOSE_HAVE_ES512) && defined(WOLFCOSE_HAVE_HMAC256) && \ + defined(WOLFCOSE_HAVE_HMAC384) && defined(WOLFCOSE_HAVE_HMAC512) + #define WOLFCOSE_EAT_PSA_TFM_FULL +#endif + /* ----- Configurable limits (precedence: -D > WOLFCOSE_MIN_BUFFERS > default) ----- * Floors track the largest enabled signature algorithm. See docs/Macros.md. */ #ifndef WOLFCOSE_MAX_SCRATCH_SZ @@ -564,6 +700,25 @@ extern "C" { #endif #endif +#if defined(WOLFCOSE_EAT_PSA) + /* Maximum software-component maps accepted in one PSA token. This bounds + * verification time while leaving decoded component bytes zero-copy. */ + #ifndef WOLFCOSE_EAT_PSA_MAX_COMPONENTS + #define WOLFCOSE_EAT_PSA_MAX_COMPONENTS 32u + #endif + + /* PSA/EAT map keys must be unique, including unknown extension keys. The + * allocation-free duplicate check re-scans earlier entries, so bound both + * maps to retain predictable verifier time. Defaults leave room for + * vendor extensions beyond all standardized claims. */ + #ifndef WOLFCOSE_EAT_PSA_MAX_CLAIMS + #define WOLFCOSE_EAT_PSA_MAX_CLAIMS 64u + #endif + #ifndef WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS + #define WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS 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" @@ -577,6 +732,17 @@ extern "C" { #if WOLFCOSE_MAX_MAP_ITEMS < 4u #error "WOLFCOSE_MAX_MAP_ITEMS below 4 is too small for COSE headers" #endif +#if defined(WOLFCOSE_EAT_PSA) + #if WOLFCOSE_EAT_PSA_MAX_COMPONENTS < 1u + #error "WOLFCOSE_EAT_PSA_MAX_COMPONENTS must permit one component" + #endif + #if WOLFCOSE_EAT_PSA_MAX_CLAIMS < 10u + #error "WOLFCOSE_EAT_PSA_MAX_CLAIMS must permit all current-profile claims" + #endif + #if WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS < 5u + #error "WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS must permit all component claims" + #endif +#endif #if defined(WOLFCOSE_HAVE_MLDSA) && (WOLFCOSE_MAX_SCRATCH_SZ < 4096u) #error "wolfCOSE: ML-DSA enabled but WOLFCOSE_MAX_SCRATCH_SZ too small" diff --git a/include/wolfcose/wolfcose.h b/include/wolfcose/wolfcose.h index 20ab12e..95742d6 100644 --- a/include/wolfcose/wolfcose.h +++ b/include/wolfcose/wolfcose.h @@ -163,8 +163,9 @@ extern "C" { * - HMAC key length: HMAC-256/384/512 require a key of at least 32/48/64 * bytes (RFC 9053 Section 3.1). Shorter keys are rejected with * WOLFCOSE_E_COSE_KEY_TYPE unless WOLFCOSE_ALLOW_SHORT_HMAC_KEY is defined. - * - Strict decode: decoders require preferred (shortest-form) CBOR - * (RFC 8949 Section 4.2.1) across all entry points, and verify/decrypt + * - Strict decode: ordinary decoders require preferred (shortest-form) CBOR + * (RFC 8949 Section 4.2.1). The optional PSA/EAT verifier privately accepts + * non-preferred definite-length forms as RFC 9783 requires. Verify/decrypt * APIs require inSz to be exactly the encoded object length (trailing bytes * are rejected). EC2 coordinates must be exactly the curve size. */ @@ -441,9 +442,9 @@ typedef struct WOLFCOSE_SIGNATURE { /** * \brief Initialize a context for encoding into \p buf. * - * WOLFCOSE_CBOR_CTX carries both an encode pointer (buf) and a decode pointer - * (cbuf); this sets the encode one, clears the decode one, and rewinds idx, so - * a context can never be half-initialized from the wrong direction. + * Sets the mutable encode pointer, clears the const decode pointer, and + * rewinds the cursor. A context initialized for decoding cannot be used to + * encode into its read-only input. * * \param ctx Context to initialize. * \param buf Output buffer. @@ -496,7 +497,8 @@ WOLFCOSE_API int wc_CBOR_EncodeBstr(WOLFCOSE_CBOR_CTX* ctx, * \param ctx Encoder context. * \param str UTF-8 text (not null-terminated requirement). * \param len Length in bytes. - * \return WOLFCOSE_SUCCESS or negative error code. + * \return WOLFCOSE_SUCCESS, WOLFCOSE_E_CBOR_MALFORMED for invalid UTF-8, + * or another negative error code. */ WOLFCOSE_API int wc_CBOR_EncodeTstr(WOLFCOSE_CBOR_CTX* ctx, const uint8_t* str, size_t len); @@ -550,27 +552,33 @@ WOLFCOSE_API int wc_CBOR_EncodeDouble(WOLFCOSE_CBOR_CTX* ctx, double val); * * Guarded by WOLFCOSE_CBOR_DECODE — always needed for verify/decrypt builds. * - * Strictness note (RFC 8949 Section 4.2.1): every decode entry point requires - * preferred, shortest-form argument encoding and rejects indefinite-length - * items. This is what COSE deterministic encoding and CTAP2 canonical CBOR - * require, but it is stricter than a general-purpose CBOR parser: input that - * other decoders accept — 0x1817 for 23, an indefinite-length bstr — is - * rejected here with WOLFCOSE_E_CBOR_MALFORMED or WOLFCOSE_E_UNSUPPORTED. - * See docs/Getting-Started.md, "Strict decoding". + * Strictness note (RFC 8949 Section 4.2.1): the ordinary decode entry points + * require preferred, shortest-form argument encoding and reject + * indefinite-length items. This is what COSE deterministic encoding and CTAP2 + * canonical CBOR require, but it is stricter than a general-purpose CBOR + * parser. The optional PSA/EAT verifier retains its variation-tolerant profile + * handling in private decoder state, so this public context remains ABI-stable + * and ordinary CBOR callers are always strict. Indefinite-length items remain + * unsupported in every mode. Every encountered text string is also validated + * as UTF-8; wc_CBOR_DecodeHead(), wc_CBOR_DecodeLabel(), wc_CBOR_Skip(), and + * wc_CBOR_SkipItem() return WOLFCOSE_E_CBOR_MALFORMED for invalid text even + * when the caller does not request the text value directly. * ----- */ #if defined(WOLFCOSE_CBOR_DECODE) /** - * \brief Initialize a context for decoding from \p buf. + * \brief Initialize a context for ordinary strict CBOR decoding. * * Sets the const decode pointer, clears the mutable encode pointer, and - * rewinds idx. The buffer is never written through this context. - * - * \param ctx Context to initialize. - * \param buf Input buffer. - * \param bufSz Input buffer size. - * \return WOLFCOSE_SUCCESS or WOLFCOSE_E_INVALID_ARG. + * rewinds the cursor. Decoder functions never write through the input. This + * initializer requires RFC 8949 preferred serialization and rejects + * indefinite-length items. + * + * \param ctx Context to initialize for read-only decoding. + * \param buf Non-NULL input buffer that remains valid while decoded. + * \param bufSz Exact size of \p buf in bytes. + * \return WOLFCOSE_SUCCESS or WOLFCOSE_E_INVALID_ARG for a NULL argument. */ static inline int wc_CBOR_DecoderInit(WOLFCOSE_CBOR_CTX* ctx, const uint8_t* buf, size_t bufSz) @@ -591,15 +599,16 @@ static inline int wc_CBOR_DecoderInit(WOLFCOSE_CBOR_CTX* ctx, * \brief Decode a CBOR data item head. Core decoder function. * For bstr/tstr, sets item->data to point into the input buffer. * - * Enforces RFC 8949 Section 4.2.1 preferred serialization: an argument that - * could have been encoded in fewer bytes is WOLFCOSE_E_CBOR_MALFORMED, and an - * indefinite-length item (additional information 31) is - * WOLFCOSE_E_UNSUPPORTED. Deliberately stricter than a general-purpose CBOR - * parser; see the section note above. + * Enforces RFC 8949 Section 4.2.1 preferred serialization. An + * indefinite-length item (additional information 31) is always + * WOLFCOSE_E_UNSUPPORTED. Text strings are UTF-8 validated, including when a + * caller uses this low-level function only to inspect an item. See the section + * note above. * * \param ctx Decoder context (advances idx past the decoded item head + data). * \param item Output: decoded item. - * \return WOLFCOSE_SUCCESS or negative error code. + * \return WOLFCOSE_SUCCESS, WOLFCOSE_E_CBOR_MALFORMED for invalid UTF-8 or + * non-preferred serialization, or another negative error code. */ WOLFCOSE_API int wc_CBOR_DecodeHead(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_ITEM* item); @@ -621,7 +630,7 @@ WOLFCOSE_API int wc_CBOR_DecodeUint(WOLFCOSE_CBOR_CTX* ctx, uint64_t* val); WOLFCOSE_API int wc_CBOR_DecodeInt(WOLFCOSE_CBOR_CTX* ctx, int64_t* val); /** - * \brief Decode a byte string. Zero-copy: *data points into ctx->buf. + * \brief Decode a byte string. Zero-copy: *data points into ctx->cbuf. * \param ctx Decoder context. * \param data Output: pointer into input buffer. * \param dataLen Output: byte string length. @@ -631,11 +640,12 @@ WOLFCOSE_API int wc_CBOR_DecodeBstr(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** data, size_t* dataLen); /** - * \brief Decode a text string. Zero-copy: *str points into ctx->buf. + * \brief Decode a UTF-8 text string. Zero-copy: *str points into ctx->cbuf. * \param ctx Decoder context. * \param str Output: pointer into input buffer. * \param strLen Output: text string length in bytes. - * \return WOLFCOSE_SUCCESS or negative error code. + * \return WOLFCOSE_SUCCESS, WOLFCOSE_E_CBOR_MALFORMED for invalid UTF-8, + * or another negative error code. */ WOLFCOSE_API int wc_CBOR_DecodeTstr(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** str, size_t* strLen); @@ -668,9 +678,11 @@ WOLFCOSE_API int wc_CBOR_DecodeTag(WOLFCOSE_CBOR_CTX* ctx, uint64_t* tag); /** * \brief Skip over a complete CBOR item (including nested arrays/maps). - * Uses iterative traversal with bounded stack depth. + * Uses iterative traversal with bounded stack depth and validates any + * text strings encountered while traversing. * \param ctx Decoder context (idx advances past the skipped item). - * \return WOLFCOSE_SUCCESS or negative error code. + * \return WOLFCOSE_SUCCESS, WOLFCOSE_E_CBOR_MALFORMED for invalid UTF-8, + * or another negative error code. */ WOLFCOSE_API int wc_CBOR_Skip(WOLFCOSE_CBOR_CTX* ctx); @@ -686,8 +698,9 @@ WOLFCOSE_API int wc_CBOR_Skip(WOLFCOSE_CBOR_CTX* ctx); * \param ctx Decoder context (idx advances past the skipped item). * \param data Output: pointer to the first byte of the skipped item. * \param dataLen Output: encoded length of the skipped item. - * \return WOLFCOSE_SUCCESS or negative error code. On failure the outputs are - * untouched and ctx->idx may have advanced, as with wc_CBOR_Skip(). + * \return WOLFCOSE_SUCCESS, WOLFCOSE_E_CBOR_MALFORMED for invalid UTF-8, + * or another negative error code. On failure the outputs are untouched + * and ctx->idx may have advanced, as with wc_CBOR_Skip(). */ WOLFCOSE_API int wc_CBOR_SkipItem(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** data, size_t* dataLen); @@ -695,9 +708,9 @@ WOLFCOSE_API int wc_CBOR_SkipItem(WOLFCOSE_CBOR_CTX* ctx, /** * \brief Decoded CBOR map label: either an integer or a text string. * - * RFC 9052 allows `label = int / tstr`, and real COSE and CTAP2 maps use both - * spellings for the same field (`3` vs `"alg"`, `1` vs `"type"`, `2` vs - * `"id"`). Populated by wc_CBOR_DecodeLabel(); compare with + * RFC 9052 allows `label = int / tstr`. Applications may define either form + * for their own map parameters; registered COSE parameters retain their + * specified numeric labels. Populated by wc_CBOR_DecodeLabel(); compare with * wc_CBOR_LabelIsInt() / wc_CBOR_LabelIsText(). */ typedef struct WOLFCOSE_CBOR_LABEL { @@ -716,7 +729,8 @@ typedef struct WOLFCOSE_CBOR_LABEL { * * \param ctx Decoder context. * \param label Output: decoded label. - * \return WOLFCOSE_SUCCESS or negative error code. + * \return WOLFCOSE_SUCCESS, WOLFCOSE_E_CBOR_MALFORMED for an invalid UTF-8 + * text label, or another negative error code. */ WOLFCOSE_API int wc_CBOR_DecodeLabel(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label); @@ -996,11 +1010,12 @@ typedef struct WOLFCOSE_KEY_INFO { * * Nothing is imported, no key object is needed, and \p in is not modified. * The same structural checks wc_CoseKey_Decode() applies are applied here - * (integer labels only, no duplicate labels, kty required, no trailing - * bytes), so a buffer that peeks successfully will not be rejected by the - * decoder for those reasons. - * Keys containing key_ops are rejected with WOLFCOSE_E_UNSUPPORTED because - * the fixed-size key wrapper cannot retain arbitrary operation identifiers. + * (no duplicate integer or text labels, kty required, no trailing bytes), so + * a buffer that peeks successfully will not be rejected by the decoder for + * those reasons. Registered COSE_Key parameters remain numeric; an unknown + * text label is treated as a non-critical extension. Keys containing key_ops + * are rejected with WOLFCOSE_E_UNSUPPORTED because the fixed-size key wrapper + * cannot retain arbitrary operation identifiers. * * \param in Input CBOR COSE_Key buffer. * \param inSz Input buffer size; must be exactly the encoded length. diff --git a/scripts/check_stack_usage.sh b/scripts/check_stack_usage.sh index f023907..68e80f9 100755 --- a/scripts/check_stack_usage.sh +++ b/scripts/check_stack_usage.sh @@ -5,7 +5,7 @@ set -e BUDGET="${1:-6144}" -SU="src/wolfcose.su src/wolfcose_cbor.su" +SU="src/wolfcose.su src/wolfcose_cbor.su src/wolfcose_eat_psa.su" for f in $SU; do if [ ! -f "$f" ]; then diff --git a/scripts/cmdline-test.sh b/scripts/cmdline-test.sh index 0b10637..6d0d394 100755 --- a/scripts/cmdline-test.sh +++ b/scripts/cmdline-test.sh @@ -154,6 +154,15 @@ else skip "info (ES256)" fi +echo "== info rejects malformed CBOR ==" +IB="$WORK/info-malformed.cbor" +printf '\x1a' > "$IB" +if "$TOOL" info -i "$IB" >/dev/null 2>&1; then + bad "info malformed CBOR rejected" +else + ok "info malformed CBOR rejected" +fi + echo "== Usage errors must exit non-zero ==" if "$TOOL" >/dev/null 2>&1; then bad "no-args exits non-zero"; else ok "no-args exits non-zero"; fi if "$TOOL" boguscmd >/dev/null 2>&1; then bad "bad command exits non-zero"; else ok "bad command exits non-zero"; fi diff --git a/scripts/test_pkg_config.sh b/scripts/test_pkg_config.sh index 94bcbb6..e80343c 100644 --- a/scripts/test_pkg_config.sh +++ b/scripts/test_pkg_config.sh @@ -133,6 +133,7 @@ check_config_rebuild() ( mkdir -p "$config_fixture/src" "$config_fixture/include/wolfcose" cp "$ROOT_DIR/Makefile" "$config_fixture/Makefile" cp "$ROOT_DIR/src/wolfcose_cbor.c" "$ROOT_DIR/src/wolfcose.c" \ + "$ROOT_DIR/src/wolfcose_eat_psa.c" \ "$ROOT_DIR/src/wolfcose_internal.h" "$config_fixture/src/" cp "$ROOT_DIR/include/wolfcose/wolfcose.h" \ "$config_fixture/include/wolfcose/" @@ -163,8 +164,8 @@ check_config_rebuild() ( compiler_args=$(cat "$FAKE_CC_LOG") contains "$compiler_args" '-I/fake/second/include' compiler_count=$(wc -l < "$FAKE_CC_LOG" | tr -d ' ') - if [ "$compiler_count" -ne 4 ]; then - printf 'FAIL: expected configuration change to rebuild both objects\n' >&2 + if [ "$compiler_count" -ne 6 ]; then + printf 'FAIL: expected configuration change to rebuild all objects\n' >&2 exit 1 fi ) diff --git a/src/wolfcose.c b/src/wolfcose.c index 990c3d0..e9ec04f 100644 --- a/src/wolfcose.c +++ b/src/wolfcose.c @@ -1049,6 +1049,7 @@ static void wolfCose_HdrStateInit(WOLFCOSE_HDR_STATE* state) if (state != NULL) { state->labelBits = 0u; state->extraCount = 0u; + state->textCount = 0u; } } @@ -1090,7 +1091,9 @@ static int wolfCose_HdrStateAdd(WOLFCOSE_HDR_STATE* state, int64_t label) if (bit != 0u) { state->labelBits |= bit; } - else if (state->extraCount >= (size_t)WOLFCOSE_MAX_MAP_ITEMS) { + else if ((state->textCount > (size_t)WOLFCOSE_MAX_MAP_ITEMS) || + (state->extraCount >= + ((size_t)WOLFCOSE_MAX_MAP_ITEMS - state->textCount))) { ret = WOLFCOSE_E_CBOR_MALFORMED; } else { @@ -1102,6 +1105,53 @@ static int wolfCose_HdrStateAdd(WOLFCOSE_HDR_STATE* state, int64_t label) return ret; } +static int wolfCose_HdrStateTextContains(const WOLFCOSE_HDR_STATE* state, + const uint8_t* text, size_t textLen) +{ + int found = 0; + + if ((state != NULL) && ((text != NULL) || (textLen == 0u))) { + size_t i; + WOLFCOSE_CBOR_LABEL label; + + label.val = 0; + label.text = text; + label.textLen = textLen; + label.isText = 1u; + for (i = 0u; i < state->textCount; i++) { + if (wc_CBOR_LabelIsText(&label, state->textLabels[i], + state->textLabelLens[i]) != 0) { + found = 1; + break; + } + } + } + + return found; +} + +static int wolfCose_HdrStateTextAdd(WOLFCOSE_HDR_STATE* state, + const uint8_t* text, size_t textLen) +{ + int ret = WOLFCOSE_SUCCESS; + + if ((state == NULL) || ((text == NULL) && (textLen != 0u))) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if ((state->textCount > (size_t)WOLFCOSE_MAX_MAP_ITEMS) || + (state->extraCount >= + ((size_t)WOLFCOSE_MAX_MAP_ITEMS - state->textCount))) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + state->textLabels[state->textCount] = text; + state->textLabelLens[state->textCount] = textLen; + state->textCount++; + } + + return ret; +} + static int wolfCose_HdrStateCheckAndAdd(WOLFCOSE_HDR_STATE* state, int64_t label) { @@ -1117,42 +1167,83 @@ static int wolfCose_HdrStateCheckAndAdd(WOLFCOSE_HDR_STATE* state, return ret; } -static int wolfCose_HdrStateMerge(WOLFCOSE_HDR_STATE* dst, - const WOLFCOSE_HDR_STATE* src) +static int wolfCose_HdrStateContainsLabel(const WOLFCOSE_HDR_STATE* state, + const WOLFCOSE_CBOR_LABEL* label) { - int ret = WOLFCOSE_SUCCESS; + int found = 0; - if ((dst == NULL) || (src == NULL)) { + if (label != NULL) { + if (label->isText != 0u) { + found = wolfCose_HdrStateTextContains(state, label->text, + label->textLen); + } + else { + found = wolfCose_HdrStateContains(state, label->val); + } + } + + return found; +} + +static int wolfCose_HdrStateAddLabel(WOLFCOSE_HDR_STATE* state, + const WOLFCOSE_CBOR_LABEL* label) +{ + int ret; + + if (label == NULL) { ret = WOLFCOSE_E_INVALID_ARG; } + else if (label->isText != 0u) { + ret = wolfCose_HdrStateTextAdd(state, label->text, label->textLen); + } else { - size_t i; - dst->labelBits |= src->labelBits; - for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < src->extraCount); i++) { - ret = wolfCose_HdrStateAdd(dst, src->extraLabels[i]); - } + ret = wolfCose_HdrStateAdd(state, label->val); } return ret; } -/* If the next decoder item is a tstr label, reject it. The implementation - * only supports integer labels, and silently skipping text labels breaks - * duplicate-label enforcement across header and key maps. */ -static int wolfCose_SkipIfTstrLabel(const WOLFCOSE_CBOR_CTX* ctx, int* skipped) +static int wolfCose_HdrStateCheckAndAddLabel(WOLFCOSE_HDR_STATE* state, + const WOLFCOSE_CBOR_LABEL* label) { int ret; - *skipped = 0; - if (ctx->idx >= ctx->bufSz) { - ret = WOLFCOSE_E_CBOR_MALFORMED; + if (label == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (label->isText == 0u) { + ret = wolfCose_HdrStateCheckAndAdd(state, label->val); } - else if (wc_CBOR_PeekType(ctx) == WOLFCOSE_CBOR_TSTR) { + else if (wolfCose_HdrStateContainsLabel(state, label) != 0) { ret = WOLFCOSE_E_CBOR_MALFORMED; } else { - ret = WOLFCOSE_SUCCESS; + ret = wolfCose_HdrStateAddLabel(state, label); } + + return ret; +} + +static int wolfCose_HdrStateMerge(WOLFCOSE_HDR_STATE* dst, + const WOLFCOSE_HDR_STATE* src) +{ + int ret = WOLFCOSE_SUCCESS; + + if ((dst == NULL) || (src == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + size_t i; + dst->labelBits |= src->labelBits; + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < src->extraCount); i++) { + ret = wolfCose_HdrStateAdd(dst, src->extraLabels[i]); + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < src->textCount); i++) { + ret = wolfCose_HdrStateTextAdd(dst, src->textLabels[i], + src->textLabelLens[i]); + } + } + return ret; } @@ -1167,9 +1258,9 @@ int wolfCose_EncodeProtectedHdr(int32_t alg, uint8_t* buf, size_t bufSz, } else { ctx.buf = buf; + ctx.cbuf = NULL; ctx.bufSz = bufSz; ctx.idx = 0; - /* Encode map with 1 entry: {1: alg} */ ret = wc_CBOR_EncodeMapStart(&ctx, 1); if (ret == WOLFCOSE_SUCCESS) { @@ -1185,23 +1276,26 @@ int wolfCose_EncodeProtectedHdr(int32_t alg, uint8_t* buf, size_t bufSz, return ret; } -int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, - WOLFCOSE_HDR* hdr, - WOLFCOSE_HDR_STATE* hdrState) +int wolfCose_DecodeProtectedHdr_ex(const uint8_t* data, size_t dataLen, + WOLFCOSE_HDR* hdr, + WOLFCOSE_HDR_STATE* hdrState, + uint32_t decodeFlags) { int ret; WOLFCOSE_CBOR_CTX ctx; size_t mapCount = 0; size_t i; - int64_t label; + WOLFCOSE_CBOR_LABEL label; int64_t intVal; uint64_t contentTypeVal; uint32_t critLabels = 0u; - int skipped; if ((hdr == NULL) || (hdrState == NULL)) { ret = WOLFCOSE_E_INVALID_ARG; } + else if (WOLFCOSE_COSE_DECODE_FLAGS_VALID(decodeFlags) == 0) { + ret = WOLFCOSE_E_INVALID_ARG; + } else if ((data == NULL) || (dataLen == 0u)) { /* Empty protected header is valid */ wolfCose_HdrStateInit(hdrState); @@ -1209,11 +1303,11 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } else { wolfCose_HdrStateInit(hdrState); - ctx.cbuf = data; - ctx.bufSz = dataLen; - ctx.idx = 0; - - ret = wc_CBOR_DecodeMapStart(&ctx, &mapCount); + ret = wc_CBOR_DecoderInit(&ctx, data, dataLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CBOR_DecodeMapStart_ex(&ctx, &mapCount, + decodeFlags); + } if ((ret == WOLFCOSE_SUCCESS) && (mapCount > (size_t)WOLFCOSE_MAX_MAP_ITEMS)) { ret = WOLFCOSE_E_CBOR_MALFORMED; @@ -1221,23 +1315,20 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < mapCount); i++) { - /* Reject tstr labels: only integer labels are supported. */ - ret = wolfCose_SkipIfTstrLabel(&ctx, &skipped); - if ((ret == WOLFCOSE_SUCCESS) && (skipped == 0)) { - ret = wc_CBOR_DecodeInt(&ctx, &label); - } - + ret = wolfCose_CBOR_DecodeLabel_ex(&ctx, &label, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_HdrStateCheckAndAdd(hdrState, label); + ret = wolfCose_HdrStateCheckAndAddLabel(hdrState, &label); } - if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_HDR_ALG)) { + if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_ALG)) { if ((ctx.idx < ctx.bufSz) && (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TSTR)) { - ret = wc_CBOR_Skip(&ctx); + ret = wolfCose_CBOR_Skip_ex(&ctx, decodeFlags); } else { - ret = wc_CBOR_DecodeInt(&ctx, &intVal); + ret = wolfCose_CBOR_DecodeInt_ex(&ctx, &intVal, + decodeFlags); if ((ret == WOLFCOSE_SUCCESS) && (wolfCose_InInt32Range(intVal) == 0)) { ret = WOLFCOSE_E_COSE_BAD_ALG; @@ -1247,13 +1338,14 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_CRIT)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_CRIT)) { size_t critCount = 0; size_t k; int64_t critLabel; - ret = wc_CBOR_DecodeArrayStart(&ctx, &critCount); + ret = wolfCose_CBOR_DecodeArrayStart_ex(&ctx, &critCount, + decodeFlags); if ((ret == WOLFCOSE_SUCCESS) && ((critCount == 0u) || (critCount > (size_t)WOLFCOSE_MAX_MAP_ITEMS))) { @@ -1265,7 +1357,8 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, ret = WOLFCOSE_E_COSE_BAD_HDR; } else { - ret = wc_CBOR_DecodeInt(&ctx, &critLabel); + ret = wolfCose_CBOR_DecodeInt_ex(&ctx, &critLabel, + decodeFlags); } if (ret == WOLFCOSE_SUCCESS) { /* crit labels limited to ones wolfCOSE processes. */ @@ -1284,14 +1377,15 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_CONTENT_TYPE)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_CONTENT_TYPE)) { if ((ctx.idx < ctx.bufSz) && (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TSTR)) { - ret = wc_CBOR_Skip(&ctx); + ret = wolfCose_CBOR_Skip_ex(&ctx, decodeFlags); } else { - ret = wc_CBOR_DecodeUint(&ctx, &contentTypeVal); + ret = wolfCose_CBOR_DecodeUint_ex(&ctx, &contentTypeVal, + decodeFlags); if ((ret == WOLFCOSE_SUCCESS) && (contentTypeVal > (uint64_t)INT32_MAX)) { ret = WOLFCOSE_E_COSE_BAD_HDR; @@ -1301,43 +1395,53 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, } } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_KID)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_KID)) { /* RFC 9052 Section 3.1: kid may appear in the protected * bucket; populate it the same way the unprotected decoder * does instead of skipping it as unknown. */ const uint8_t* kidData; size_t kidBstrLen; - ret = wc_CBOR_DecodeBstr(&ctx, &kidData, &kidBstrLen); + ret = wolfCose_CBOR_DecodeBstr_ex(&ctx, &kidData, &kidBstrLen, + decodeFlags); if (ret == WOLFCOSE_SUCCESS) { hdr->kid = kidData; hdr->kidLen = kidBstrLen; } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_IV)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_IV)) { const uint8_t* ivData; size_t ivBstrLen; - ret = wc_CBOR_DecodeBstr(&ctx, &ivData, &ivBstrLen); + ret = wolfCose_CBOR_DecodeBstr_ex(&ctx, &ivData, &ivBstrLen, + decodeFlags); if (ret == WOLFCOSE_SUCCESS) { hdr->iv = ivData; hdr->ivLen = ivBstrLen; } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_PARTIAL_IV)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_PARTIAL_IV)) { const uint8_t* pivData; size_t pivBstrLen; - ret = wc_CBOR_DecodeBstr(&ctx, &pivData, &pivBstrLen); + ret = wolfCose_CBOR_DecodeBstr_ex(&ctx, &pivData, &pivBstrLen, + decodeFlags); if (ret == WOLFCOSE_SUCCESS) { hdr->partialIv = pivData; hdr->partialIvLen = pivBstrLen; } } +#if defined(WOLFCOSE_EAT_PSA) + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_X5CHAIN)) { + hdr->flags |= WOLFCOSE_HDR_FLAG_X5CHAIN; + ret = wolfCose_CBOR_Skip_ex(&ctx, decodeFlags); + } +#endif else { if (ret == WOLFCOSE_SUCCESS) { /* Skip unknown header */ - ret = wc_CBOR_Skip(&ctx); + ret = wolfCose_CBOR_Skip_ex(&ctx, decodeFlags); } } } @@ -1361,17 +1465,24 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, return ret; } -int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, - WOLFCOSE_HDR_STATE* hdrState) +int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, + WOLFCOSE_HDR* hdr, + WOLFCOSE_HDR_STATE* hdrState) +{ + return wolfCose_DecodeProtectedHdr_ex(data, dataLen, hdr, hdrState, 0u); +} + +int wolfCose_DecodeUnprotectedHdr_ex(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_HDR* hdr, WOLFCOSE_HDR_STATE* hdrState, uint32_t decodeFlags) { int ret; size_t mapCount = 0; - int64_t label; + WOLFCOSE_CBOR_LABEL label; const uint8_t* bstrData; size_t bstrLen; - int skipped; - if ((ctx == NULL) || (hdr == NULL) || (hdrState == NULL)) { + if ((ctx == NULL) || (hdr == NULL) || (hdrState == NULL) || + (WOLFCOSE_COSE_DECODE_FLAGS_VALID(decodeFlags) == 0)) { ret = WOLFCOSE_E_INVALID_ARG; } else { @@ -1379,7 +1490,7 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, WOLFCOSE_HDR_STATE unprotState; wolfCose_HdrStateInit(&unprotState); - ret = wc_CBOR_DecodeMapStart(ctx, &mapCount); + ret = wolfCose_CBOR_DecodeMapStart_ex(ctx, &mapCount, decodeFlags); if ((ret == WOLFCOSE_SUCCESS) && (mapCount > (size_t)WOLFCOSE_MAX_MAP_ITEMS)) { ret = WOLFCOSE_E_CBOR_MALFORMED; @@ -1387,58 +1498,61 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < mapCount); i++) { - /* Reject tstr labels: only integer labels are supported. */ - ret = wolfCose_SkipIfTstrLabel(ctx, &skipped); - if ((ret == WOLFCOSE_SUCCESS) && (skipped == 0)) { - ret = wc_CBOR_DecodeInt(ctx, &label); - } - + ret = wolfCose_CBOR_DecodeLabel_ex(ctx, &label, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { /* crit MUST live in the protected bucket. */ - if (label == WOLFCOSE_HDR_CRIT) { + if ((label.isText == 0u) && + (label.val == WOLFCOSE_HDR_CRIT)) { ret = WOLFCOSE_E_COSE_BAD_HDR; } - else if ((wolfCose_HdrStateContains(&unprotState, label) != 0) || - (wolfCose_HdrStateContains(hdrState, label) != 0)) { + else if ((wolfCose_HdrStateContainsLabel(&unprotState, + &label) != 0) || + (wolfCose_HdrStateContainsLabel(hdrState, + &label) != 0)) { ret = WOLFCOSE_E_CBOR_MALFORMED; } else { - ret = wolfCose_HdrStateAdd(&unprotState, label); + ret = wolfCose_HdrStateAddLabel(&unprotState, &label); } } - if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_HDR_KID)) { - ret = wc_CBOR_DecodeBstr(ctx, &bstrData, &bstrLen); + if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_KID)) { + ret = wolfCose_CBOR_DecodeBstr_ex(ctx, &bstrData, &bstrLen, + decodeFlags); if (ret == WOLFCOSE_SUCCESS) { hdr->kid = bstrData; hdr->kidLen = bstrLen; } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_IV)) { - ret = wc_CBOR_DecodeBstr(ctx, &bstrData, &bstrLen); + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_IV)) { + ret = wolfCose_CBOR_DecodeBstr_ex(ctx, &bstrData, &bstrLen, + decodeFlags); if (ret == WOLFCOSE_SUCCESS) { hdr->iv = bstrData; hdr->ivLen = bstrLen; } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_PARTIAL_IV)) { - ret = wc_CBOR_DecodeBstr(ctx, &bstrData, &bstrLen); + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_PARTIAL_IV)) { + ret = wolfCose_CBOR_DecodeBstr_ex(ctx, &bstrData, &bstrLen, + decodeFlags); if (ret == WOLFCOSE_SUCCESS) { hdr->partialIv = bstrData; hdr->partialIvLen = bstrLen; } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_ALG)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_ALG)) { if ((ctx->idx < ctx->bufSz) && (wc_CBOR_PeekType(ctx) == WOLFCOSE_CBOR_TSTR)) { - ret = wc_CBOR_Skip(ctx); + ret = wolfCose_CBOR_Skip_ex(ctx, decodeFlags); } else { int64_t algVal; - ret = wc_CBOR_DecodeInt(ctx, &algVal); + ret = wolfCose_CBOR_DecodeInt_ex(ctx, &algVal, + decodeFlags); if ((ret == WOLFCOSE_SUCCESS) && (wolfCose_InInt32Range(algVal) == 0)) { ret = WOLFCOSE_E_COSE_BAD_ALG; @@ -1448,16 +1562,17 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_CONTENT_TYPE)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_CONTENT_TYPE)) { hdr->flags |= WOLFCOSE_HDR_FLAG_CONTENT_TYPE_UNPROTECTED; if ((ctx->idx < ctx->bufSz) && (wc_CBOR_PeekType(ctx) == WOLFCOSE_CBOR_TSTR)) { - ret = wc_CBOR_Skip(ctx); + ret = wolfCose_CBOR_Skip_ex(ctx, decodeFlags); } else { uint64_t contentTypeVal; - ret = wc_CBOR_DecodeUint(ctx, &contentTypeVal); + ret = wolfCose_CBOR_DecodeUint_ex(ctx, &contentTypeVal, + decodeFlags); if ((ret == WOLFCOSE_SUCCESS) && (contentTypeVal > (uint64_t)INT32_MAX)) { ret = WOLFCOSE_E_COSE_BAD_HDR; @@ -1467,9 +1582,16 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } } } +#if defined(WOLFCOSE_EAT_PSA) + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_X5CHAIN)) { + hdr->flags |= WOLFCOSE_HDR_FLAG_X5CHAIN; + ret = wolfCose_CBOR_Skip_ex(ctx, decodeFlags); + } +#endif else { if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_Skip(ctx); + ret = wolfCose_CBOR_Skip_ex(ctx, decodeFlags); } } } @@ -1487,6 +1609,12 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, return ret; } +int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, + WOLFCOSE_HDR_STATE* hdrState) +{ + return wolfCose_DecodeUnprotectedHdr_ex(ctx, hdr, hdrState, 0u); +} + #if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_ENCRYPT_DECRYPT) || \ defined(WOLFCOSE_MAC_VERIFY) /* Decode only the algorithm from an unselected header map. Other labels and @@ -1676,7 +1804,6 @@ static int wolfCose_DecodeSkippedRecipient(WOLFCOSE_CBOR_CTX* ctx, } #endif #endif - /* ----- COSE Key API ----- */ int wc_CoseKey_Init(WOLFCOSE_KEY* key) @@ -3306,7 +3433,7 @@ int wc_CoseKey_PeekInfo(const uint8_t* in, size_t inSz, WOLFCOSE_CBOR_CTX ctx; WOLFCOSE_HDR_STATE keyLabelState; size_t mapCount = 0; - int64_t label; + WOLFCOSE_CBOR_LABEL label; if ((in == NULL) || (inSz == 0u) || (info == NULL)) { ret = WOLFCOSE_E_INVALID_ARG; @@ -3335,20 +3462,14 @@ int wc_CoseKey_PeekInfo(const uint8_t* in, size_t inSz, } for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < mapCount); i++) { - int keySkipped = 0; - - /* RFC 9052: COSE_Key labels follow label = int / tstr; the - * decoder supports the integer form only, so mirror it here. */ - ret = wolfCose_SkipIfTstrLabel(&ctx, &keySkipped); - if ((ret == WOLFCOSE_SUCCESS) && (keySkipped == 0)) { - ret = wc_CBOR_DecodeInt(&ctx, &label); - } + ret = wc_CBOR_DecodeLabel(&ctx, &label); if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_HdrStateCheckAndAdd(&keyLabelState, label); + ret = wolfCose_HdrStateCheckAndAddLabel(&keyLabelState, + &label); } - if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_KTY)) { + if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_KTY)) { uint64_t uval; ret = wc_CBOR_DecodeUint(&ctx, &uval); if ((ret == WOLFCOSE_SUCCESS) && @@ -3359,12 +3480,12 @@ int wc_CoseKey_PeekInfo(const uint8_t* in, size_t inSz, info->kty = (int32_t)uval; } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_KID)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_KID)) { ret = wc_CBOR_DecodeBstr(&ctx, &info->kid, &info->kidLen); } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_ALG)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_ALG)) { int64_t algVal; ret = wc_CBOR_DecodeInt(&ctx, &algVal); if ((ret == WOLFCOSE_SUCCESS) && @@ -3375,12 +3496,12 @@ int wc_CoseKey_PeekInfo(const uint8_t* in, size_t inSz, info->alg = (int32_t)algVal; } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_KEY_OPS)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_KEY_OPS)) { ret = WOLFCOSE_E_UNSUPPORTED; } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_CRV)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_CRV)) { /* -1 is crv for EC2/OKP but k (bstr) for Symmetric and n * (bstr) for RSA, so dispatch on the CBOR type -- kty may * not have been seen yet. */ @@ -3436,7 +3557,7 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) WOLFCOSE_CBOR_CTX ctx; size_t mapCount = 0; size_t i; - int64_t label; + WOLFCOSE_CBOR_LABEL label; uint64_t uval; const uint8_t* bstrData; size_t bstrLen; @@ -3472,6 +3593,7 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) } #endif else { + ctx.buf = NULL; ctx.cbuf = in; ctx.bufSz = inSz; ctx.idx = 0; @@ -3499,20 +3621,15 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) } for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < mapCount); i++) { - int keySkipped = 0; - - /* RFC 9052: COSE_Key labels follow label = int / tstr. */ - ret = wolfCose_SkipIfTstrLabel(&ctx, &keySkipped); - if ((ret == WOLFCOSE_SUCCESS) && (keySkipped == 0)) { - ret = wc_CBOR_DecodeInt(&ctx, &label); - } + ret = wc_CBOR_DecodeLabel(&ctx, &label); if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_HdrStateCheckAndAdd(&keyLabelState, label); + ret = wolfCose_HdrStateCheckAndAddLabel(&keyLabelState, + &label); } - if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_KTY)) { + if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_KTY)) { ret = wc_CBOR_DecodeUint(&ctx, &uval); if ((ret == WOLFCOSE_SUCCESS) && (uval > (uint64_t)INT32_MAX)) { @@ -3522,16 +3639,16 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) key->kty = (int32_t)uval; } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_KID)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_KID)) { ret = wc_CBOR_DecodeBstr(&ctx, &bstrData, &bstrLen); if (ret == WOLFCOSE_SUCCESS) { key->kid = bstrData; key->kidLen = bstrLen; } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_ALG)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_ALG)) { if ((ctx.idx < ctx.bufSz) && (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TSTR)) { ret = WOLFCOSE_E_COSE_BAD_ALG; @@ -3548,12 +3665,12 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) } } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_KEY_OPS)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_KEY_OPS)) { ret = WOLFCOSE_E_UNSUPPORTED; } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_CRV)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_CRV)) { /* -1: crv(uint/negint) for EC2/OKP, k(bstr) for Symmetric, * n(bstr) for RSA (RFC 8230). * Peek at CBOR type so decode is order-independent -- @@ -3582,33 +3699,33 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) } } } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_X)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_X)) { ret = wc_CBOR_DecodeBstr(&ctx, &xData, &xLen); } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_Y)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_Y)) { ret = wc_CBOR_DecodeBstr(&ctx, &yData, &yLen); } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_D)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_D)) { ret = wc_CBOR_DecodeBstr(&ctx, &dData, &dLen); } #ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_RSA_Q)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_RSA_Q)) { ret = wc_CBOR_DecodeBstr(&ctx, &qData, &qLen); } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_RSA_DP)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_RSA_DP)) { ret = wc_CBOR_DecodeBstr(&ctx, &dpData, &dpLen); } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_RSA_DQ)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_RSA_DQ)) { ret = wc_CBOR_DecodeBstr(&ctx, &dqData, &dqLen); } - else if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_KEY_LABEL_RSA_QINV)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_KEY_LABEL_RSA_QINV)) { ret = wc_CBOR_DecodeBstr(&ctx, &qiData, &qiLen); } #endif @@ -4235,6 +4352,7 @@ int wolfCose_BuildToBeSignedMaced( else { size_t arrayLen; ctx.buf = scratch; + ctx.cbuf = NULL; ctx.bufSz = scratchSz; ctx.idx = 0; @@ -4288,6 +4406,7 @@ static int wolfCose_BuildEncStructure( } else { ctx.buf = scratch; + ctx.cbuf = NULL; ctx.bufSz = scratchSz; ctx.idx = 0; @@ -4516,6 +4635,7 @@ static int wolfCose_KdfContextEncode(int32_t contentAlgId, } else { ctx.buf = out; + ctx.cbuf = NULL; ctx.bufSz = outSz; ctx.idx = 0; @@ -5051,7 +5171,7 @@ static int wolfCose_DecodeEphemeralKey(WOLFCOSE_CBOR_CTX* ctx, int ret; size_t mapCount = 0; size_t i; - int64_t label; + WOLFCOSE_CBOR_LABEL label; int haveCrv = 0; int haveX = 0; int haveY = 0; @@ -5060,7 +5180,6 @@ static int wolfCose_DecodeEphemeralKey(WOLFCOSE_CBOR_CTX* ctx, size_t dataLen; int64_t intVal; WOLFCOSE_HDR_STATE ephemState; - int skipped; wolfCose_HdrStateInit(&ephemState); ret = wc_CBOR_DecodeMapStart(ctx, &mapCount); @@ -5071,16 +5190,14 @@ static int wolfCose_DecodeEphemeralKey(WOLFCOSE_CBOR_CTX* ctx, } for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < mapCount); i++) { - ret = wolfCose_SkipIfTstrLabel(ctx, &skipped); - if ((ret == WOLFCOSE_SUCCESS) && (skipped == 0)) { - ret = wc_CBOR_DecodeInt(ctx, &label); - } + ret = wc_CBOR_DecodeLabel(ctx, &label); if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_HdrStateCheckAndAdd(&ephemState, label); + ret = wolfCose_HdrStateCheckAndAddLabel(&ephemState, &label); } - if ((ret == WOLFCOSE_SUCCESS) && (label == 1)) { + if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == 1)) { /* kty - verify it's EC2 */ ret = wc_CBOR_DecodeInt(ctx, &intVal); if ((ret == WOLFCOSE_SUCCESS) && @@ -5091,7 +5208,8 @@ static int wolfCose_DecodeEphemeralKey(WOLFCOSE_CBOR_CTX* ctx, haveKty = 1; } } - else if ((ret == WOLFCOSE_SUCCESS) && (label == -1)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == -1)) { /* crv */ ret = wc_CBOR_DecodeInt(ctx, &intVal); if ((ret == WOLFCOSE_SUCCESS) && @@ -5103,7 +5221,8 @@ static int wolfCose_DecodeEphemeralKey(WOLFCOSE_CBOR_CTX* ctx, haveCrv = 1; } } - else if ((ret == WOLFCOSE_SUCCESS) && (label == -2)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == -2)) { /* x coordinate */ ret = wc_CBOR_DecodeBstr(ctx, &data, &dataLen); if (ret == WOLFCOSE_SUCCESS) { @@ -5117,7 +5236,8 @@ static int wolfCose_DecodeEphemeralKey(WOLFCOSE_CBOR_CTX* ctx, } } } - else if ((ret == WOLFCOSE_SUCCESS) && (label == -3)) { + else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == -3)) { /* y coordinate */ ret = wc_CBOR_DecodeBstr(ctx, &data, &dataLen); if (ret == WOLFCOSE_SUCCESS) { @@ -6102,13 +6222,14 @@ int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, #endif /* WOLFCOSE_SIGN1_SIGN */ #if defined(WOLFCOSE_SIGN1_VERIFY) -int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, +int wolfCose_Sign1_Verify_ex(const WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz, const uint8_t* detachedPayload, size_t detachedLen, const uint8_t* extAad, size_t extAadLen, uint8_t* scratch, size_t scratchSz, WOLFCOSE_HDR* hdr, - const uint8_t** payload, size_t* payloadLen) + const uint8_t** payload, size_t* payloadLen, + uint32_t flags) { int ret = WOLFCOSE_SUCCESS; WOLFCOSE_CBOR_CTX ctx; @@ -6132,6 +6253,10 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, (payload == NULL) || (payloadLen == NULL)) { ret = WOLFCOSE_E_INVALID_ARG; } + if ((ret == WOLFCOSE_SUCCESS) && + (WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) == 0)) { + ret = WOLFCOSE_E_INVALID_ARG; + } #ifdef WOLFCOSE_CHECK_WORD32_LEN if ((ret == WOLFCOSE_SUCCESS) && ((wolfCose_LenFitsWord32(inSz) == 0) || @@ -6145,14 +6270,12 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, if (ret == WOLFCOSE_SUCCESS) { (void)XMEMSET(hdr, 0, sizeof(WOLFCOSE_HDR)); - ctx.cbuf = in; - ctx.bufSz = inSz; - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, in, inSz); /* Optional Tag(18) */ - if ((ctx.idx < ctx.bufSz) && + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx < ctx.bufSz) && (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TAG)) { - ret = wc_CBOR_DecodeTag(&ctx, &tag); + ret = wolfCose_CBOR_DecodeTag_ex(&ctx, &tag, flags); if ((ret == WOLFCOSE_SUCCESS) && (tag != WOLFCOSE_TAG_SIGN1)) { ret = WOLFCOSE_E_COSE_BAD_TAG; } @@ -6161,7 +6284,7 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, /* Array of 4 elements */ if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_DecodeArrayStart(&ctx, &arrayCount); + ret = wolfCose_CBOR_DecodeArrayStart_ex(&ctx, &arrayCount, flags); if ((ret == WOLFCOSE_SUCCESS) && (arrayCount != 4u)) { ret = WOLFCOSE_E_CBOR_MALFORMED; } @@ -6169,13 +6292,14 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, /* 1. Protected headers (bstr) */ if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_DecodeBstr(&ctx, &protectedData, &protectedLen); + ret = wolfCose_CBOR_DecodeBstr_ex(&ctx, &protectedData, &protectedLen, + flags); } /* Parse protected headers */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, - &hdrState); + ret = wolfCose_DecodeProtectedHdr_ex(protectedData, protectedLen, hdr, + &hdrState, flags); if (ret == WOLFCOSE_SUCCESS) { algProtected = wolfCose_HdrStateContains(&hdrState, WOLFCOSE_HDR_ALG); @@ -6184,7 +6308,7 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, /* 2. Unprotected headers (map) */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_DecodeUnprotectedHdr(&ctx, hdr, &hdrState); + ret = wolfCose_DecodeUnprotectedHdr_ex(&ctx, hdr, &hdrState, flags); } /* 3. Payload (bstr or null if detached) */ @@ -6206,7 +6330,8 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, } } else { - ret = wc_CBOR_DecodeBstr(&ctx, &payloadData, &payloadDataLen); + ret = wolfCose_CBOR_DecodeBstr_ex(&ctx, &payloadData, + &payloadDataLen, flags); if (ret == WOLFCOSE_SUCCESS) { verifyPayload = payloadData; verifyPayloadLen = payloadDataLen; @@ -6216,7 +6341,7 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, /* 4. Signature (bstr) */ if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_DecodeBstr(&ctx, &sigData, &sigDataLen); + ret = wolfCose_CBOR_DecodeBstr_ex(&ctx, &sigData, &sigDataLen, flags); } /* RFC 8949 Section 5.3.1: reject trailing data after the COSE object. */ @@ -6495,6 +6620,18 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, return ret; } + +int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, + const uint8_t* in, size_t inSz, + const uint8_t* detachedPayload, size_t detachedLen, + const uint8_t* extAad, size_t extAadLen, + uint8_t* scratch, size_t scratchSz, + WOLFCOSE_HDR* hdr, + const uint8_t** payload, size_t* payloadLen) +{ + return wolfCose_Sign1_Verify_ex(key, in, inSz, detachedPayload, detachedLen, + extAad, extAadLen, scratch, scratchSz, hdr, payload, payloadLen, 0u); +} #endif /* WOLFCOSE_SIGN1_VERIFY */ #endif /* WOLFCOSE_SIGN1 */ @@ -6696,6 +6833,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, /* Start encoding COSE_Sign output */ outCtx.buf = out; + outCtx.cbuf = NULL; outCtx.bufSz = outSz; outCtx.idx = 0; } @@ -7096,6 +7234,7 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, if (ret == WOLFCOSE_SUCCESS) { (void)XMEMSET(hdr, 0, sizeof(WOLFCOSE_HDR)); + ctx.buf = NULL; ctx.cbuf = in; ctx.bufSz = inSz; ctx.idx = 0; @@ -7125,8 +7264,8 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, /* Parse body protected headers */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_DecodeProtectedHdr(bodyProtectedData, bodyProtectedLen, - hdr, &hdrState); + ret = wolfCose_DecodeProtectedHdr_ex(bodyProtectedData, bodyProtectedLen, + hdr, &hdrState, 0u); } /* 2. Body unprotected headers (map) */ @@ -7187,8 +7326,8 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, if (ret == WOLFCOSE_SUCCESS) { (void)XMEMSET(&signerHdr, 0, sizeof(signerHdr)); - ret = wolfCose_DecodeProtectedHdr(signerProtectedData, signerProtectedLen, - &signerHdr, &signerHdrState); + ret = wolfCose_DecodeProtectedHdr_ex(signerProtectedData, signerProtectedLen, + &signerHdr, &signerHdrState, 0u); if (ret == WOLFCOSE_SUCCESS) { signerAlgProtected = wolfCose_HdrStateContains(&signerHdrState, WOLFCOSE_HDR_ALG); @@ -7603,6 +7742,7 @@ int wc_CoseEncrypt0_Encrypt(const WOLFCOSE_KEY* key, int32_t alg, /* Build output COSE_Encrypt0 structure up to ciphertext */ if (ret == WOLFCOSE_SUCCESS) { outCtx.buf = out; + outCtx.cbuf = NULL; outCtx.bufSz = outSz; outCtx.idx = 0; ret = wc_CBOR_EncodeTag(&outCtx, WOLFCOSE_TAG_ENCRYPT0); @@ -7915,6 +8055,7 @@ int wc_CoseEncrypt0_Decrypt(const WOLFCOSE_KEY* key, if (ret == WOLFCOSE_SUCCESS) { (void)XMEMSET(hdr, 0, sizeof(WOLFCOSE_HDR)); + ctx.buf = NULL; ctx.cbuf = in; ctx.bufSz = inSz; ctx.idx = 0; @@ -7943,8 +8084,8 @@ int wc_CoseEncrypt0_Decrypt(const WOLFCOSE_KEY* key, } if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, - &hdrState); + ret = wolfCose_DecodeProtectedHdr_ex(protectedData, protectedLen, hdr, + &hdrState, 0u); if (ret == WOLFCOSE_SUCCESS) { algProtected = wolfCose_HdrStateContains(&hdrState, WOLFCOSE_HDR_ALG); @@ -8602,6 +8743,7 @@ int wc_CoseMac0_Create(const WOLFCOSE_KEY* key, int32_t alg, */ if (ret == WOLFCOSE_SUCCESS) { outCtx.buf = out; + outCtx.cbuf = NULL; outCtx.bufSz = outSz; outCtx.idx = 0; ret = wc_CBOR_EncodeTag(&outCtx, WOLFCOSE_TAG_MAC0); @@ -8666,13 +8808,14 @@ int wc_CoseMac0_Create(const WOLFCOSE_KEY* key, int32_t alg, #endif /* WOLFCOSE_MAC0_CREATE */ #if defined(WOLFCOSE_MAC0_VERIFY) -int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, +int wolfCose_Mac0_Verify_ex(const WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz, const uint8_t* detachedPayload, size_t detachedLen, const uint8_t* extAad, size_t extAadLen, uint8_t* scratch, size_t scratchSz, WOLFCOSE_HDR* hdr, - const uint8_t** payload, size_t* payloadLen) + const uint8_t** payload, size_t* payloadLen, + uint32_t flags) { int ret = WOLFCOSE_SUCCESS; #ifdef WOLFCOSE_HAVE_HMAC @@ -8702,6 +8845,10 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, (payload == NULL) || (payloadLen == NULL)) { ret = WOLFCOSE_E_INVALID_ARG; } + if ((ret == WOLFCOSE_SUCCESS) && + (WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) == 0)) { + ret = WOLFCOSE_E_INVALID_ARG; + } #ifdef WOLFCOSE_CHECK_WORD32_LEN if ((ret == WOLFCOSE_SUCCESS) && ((wolfCose_LenFitsWord32(inSz) == 0) || @@ -8719,14 +8866,12 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, if (ret == WOLFCOSE_SUCCESS) { (void)XMEMSET(hdr, 0, sizeof(WOLFCOSE_HDR)); - ctx.cbuf = in; - ctx.bufSz = inSz; - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, in, inSz); /* Optional Tag(17) */ - if ((ctx.idx < ctx.bufSz) && + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx < ctx.bufSz) && (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TAG)) { - ret = wc_CBOR_DecodeTag(&ctx, &tag); + ret = wolfCose_CBOR_DecodeTag_ex(&ctx, &tag, flags); if ((ret == WOLFCOSE_SUCCESS) && (tag != WOLFCOSE_TAG_MAC0)) { ret = WOLFCOSE_E_COSE_BAD_TAG; } @@ -8735,7 +8880,7 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, /* Array of 4 elements */ if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_DecodeArrayStart(&ctx, &arrayCount); + ret = wolfCose_CBOR_DecodeArrayStart_ex(&ctx, &arrayCount, flags); if ((ret == WOLFCOSE_SUCCESS) && (arrayCount != 4u)) { ret = WOLFCOSE_E_CBOR_MALFORMED; } @@ -8743,13 +8888,14 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, /* 1. Protected headers (bstr) */ if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_DecodeBstr(&ctx, &protectedData, &protectedLen); + ret = wolfCose_CBOR_DecodeBstr_ex(&ctx, &protectedData, &protectedLen, + flags); } /* Parse protected headers */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, - &hdrState); + ret = wolfCose_DecodeProtectedHdr_ex(protectedData, protectedLen, hdr, + &hdrState, flags); if (ret == WOLFCOSE_SUCCESS) { algProtected = wolfCose_HdrStateContains(&hdrState, WOLFCOSE_HDR_ALG); @@ -8758,7 +8904,7 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, /* 2. Unprotected headers (map) */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_DecodeUnprotectedHdr(&ctx, hdr, &hdrState); + ret = wolfCose_DecodeUnprotectedHdr_ex(&ctx, hdr, &hdrState, flags); } /* 3. Payload (bstr or null if detached) */ @@ -8780,7 +8926,8 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, } } else { - ret = wc_CBOR_DecodeBstr(&ctx, &payloadData, &payloadDataLen); + ret = wolfCose_CBOR_DecodeBstr_ex(&ctx, &payloadData, + &payloadDataLen, flags); if (ret == WOLFCOSE_SUCCESS) { verifyPayload = payloadData; verifyPayloadLen = payloadDataLen; @@ -8790,7 +8937,7 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, /* 4. Tag (bstr) */ if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_DecodeBstr(&ctx, &macTag, &macTagLen); + ret = wolfCose_CBOR_DecodeBstr_ex(&ctx, &macTag, &macTagLen, flags); } /* RFC 8949 Section 5.3.1: reject trailing data after the COSE object. */ @@ -8925,6 +9072,18 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, return ret; } + +int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, + const uint8_t* in, size_t inSz, + const uint8_t* detachedPayload, size_t detachedLen, + const uint8_t* extAad, size_t extAadLen, + uint8_t* scratch, size_t scratchSz, + WOLFCOSE_HDR* hdr, + const uint8_t** payload, size_t* payloadLen) +{ + return wolfCose_Mac0_Verify_ex(key, in, inSz, detachedPayload, detachedLen, + extAad, extAadLen, scratch, scratchSz, hdr, payload, payloadLen, 0u); +} #endif /* WOLFCOSE_MAC0_VERIFY */ #endif /* (WOLFCOSE_MAC0 || WOLFCOSE_MAC) && MAC algorithm */ @@ -9247,6 +9406,7 @@ int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, /* Initialize CBOR encoder */ if (ret == WOLFCOSE_SUCCESS) { ctx.buf = out; + ctx.cbuf = NULL; ctx.bufSz = outSz; ctx.idx = 0; @@ -9617,6 +9777,7 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, if (ret == WOLFCOSE_SUCCESS) { (void)XMEMSET(hdr, 0, sizeof(*hdr)); + ctx.buf = NULL; ctx.cbuf = in; ctx.bufSz = inSz; ctx.idx = 0; @@ -9644,8 +9805,8 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, ret = wc_CBOR_DecodeBstr(&ctx, &protectedData, &protectedLen); } if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, - &hdrState); + ret = wolfCose_DecodeProtectedHdr_ex(protectedData, protectedLen, hdr, + &hdrState, 0u); if (ret == WOLFCOSE_SUCCESS) { bodyAlgProtected = wolfCose_HdrStateContains(&hdrState, WOLFCOSE_HDR_ALG); @@ -9676,7 +9837,7 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { ciphertextIsNull = 1; } - ret = wolfCose_CBOR_DecodeHead(&ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(&ctx, &item, 0u); } if (ret == WOLFCOSE_SUCCESS) { @@ -9735,9 +9896,10 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, ret = wc_CBOR_DecodeBstr(&ctx, &recipientProtectedData, &recipientProtectedLen); } if ((ret == WOLFCOSE_SUCCESS) && (recipientProtectedLen > 0u)) { - ret = wolfCose_DecodeProtectedHdr(recipientProtectedData, - recipientProtectedLen, - &recipientHdr, &recipientHdrState); + ret = wolfCose_DecodeProtectedHdr_ex(recipientProtectedData, + recipientProtectedLen, + &recipientHdr, &recipientHdrState, + 0u); } /* [1] recipient unprotected header */ @@ -9755,22 +9917,19 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, } for (j = 0; (ret == WOLFCOSE_SUCCESS) && (j < mapCount); j++) { - int64_t label = 0; - int recipSkipped = 0; + WOLFCOSE_CBOR_LABEL label; - ret = wolfCose_SkipIfTstrLabel(&ctx, &recipSkipped); - if ((ret == WOLFCOSE_SUCCESS) && (recipSkipped == 0)) { - ret = wc_CBOR_DecodeInt(&ctx, &label); - } + ret = wc_CBOR_DecodeLabel(&ctx, &label); /* Reject duplicate labels within the unprotected map and labels * also present in the recipient protected bucket. */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_HdrStateCheckAndAdd(&recipientHdrState, label); + ret = wolfCose_HdrStateCheckAndAddLabel(&recipientHdrState, + &label); } - if ((ret == WOLFCOSE_SUCCESS) && - (label == WOLFCOSE_HDR_EPHEMERAL_KEY)) { + if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && + (label.val == WOLFCOSE_HDR_EPHEMERAL_KEY)) { if (haveEphemKey != 0) { ret = WOLFCOSE_E_CBOR_MALFORMED; } @@ -9908,7 +10067,7 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { recipientValueIsNull = 1; } - ret = wolfCose_CBOR_DecodeHead(&ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(&ctx, &item, 0u); if ((ret == WOLFCOSE_SUCCESS) && (recipientValueIsNull == 0) && (item.majorType != WOLFCOSE_CBOR_BSTR)) { @@ -10379,6 +10538,7 @@ int wc_CoseMac_Create(const WOLFCOSE_RECIPIENT* recipients, /* Initialize CBOR encoder */ if (ret == WOLFCOSE_SUCCESS) { ctx.buf = out; + ctx.cbuf = NULL; ctx.bufSz = outSz; ctx.idx = 0; @@ -10549,6 +10709,7 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, (void)XMEMSET(hdr, 0, sizeof(*hdr)); /* Initialize CBOR decoder */ + ctx.buf = NULL; ctx.cbuf = in; ctx.bufSz = inSz; ctx.idx = 0; @@ -10578,8 +10739,8 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, /* Parse protected header to get algorithm */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, - &hdrState); + ret = wolfCose_DecodeProtectedHdr_ex(protectedData, protectedLen, hdr, + &hdrState, 0u); if (ret == WOLFCOSE_SUCCESS) { bodyAlgProtected = wolfCose_HdrStateContains(&hdrState, WOLFCOSE_HDR_ALG); @@ -10601,7 +10762,7 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { payloadIsNull = 1; } - ret = wolfCose_CBOR_DecodeHead(&ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(&ctx, &item, 0u); } if (ret == WOLFCOSE_SUCCESS) { @@ -10674,8 +10835,8 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, ret = wc_CBOR_DecodeBstr(&ctx, &recipProt, &recipProtLen); if (ret == WOLFCOSE_SUCCESS) { (void)XMEMSET(&recipHdr, 0, sizeof(recipHdr)); - ret = wolfCose_DecodeProtectedHdr(recipProt, recipProtLen, - &recipHdr, &recipState); + ret = wolfCose_DecodeProtectedHdr_ex(recipProt, recipProtLen, + &recipHdr, &recipState, 0u); } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_DecodeUnprotectedHdr(&ctx, &recipHdr, &recipState); @@ -10692,7 +10853,7 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { recipientValueIsNull = 1; } - ret = wolfCose_CBOR_DecodeHead(&ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(&ctx, &item, 0u); } if (ret == WOLFCOSE_SUCCESS) { if (recipientValueIsNull != 0) { diff --git a/src/wolfcose_cbor.c b/src/wolfcose_cbor.c index 898ab7e..b529c9d 100644 --- a/src/wolfcose_cbor.c +++ b/src/wolfcose_cbor.c @@ -31,6 +31,116 @@ #include "wolfcose_internal.h" #include /* memcpy */ +static int wolfCose_CBOR_AllowsNonpreferred(uint32_t decodeFlags) +{ +#if defined(WOLFCOSE_EAT_PSA) + return ((decodeFlags & WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED) != 0u) ? + 1 : 0; +#else + (void)decodeFlags; + return 0; +#endif +} + +static int wolfCose_CBOR_IsEncodeContext(const WOLFCOSE_CBOR_CTX* ctx) +{ + /* buf has always selected encoder mode. Do not inspect cbuf here because + * existing callers may initialize only the original encoder fields. */ + return ((ctx != NULL) && (ctx->buf != NULL)) ? 1 : 0; +} + +/* RFC 8949 Section 3.3 defines a text string as a sequence of Unicode code + * points encoded in UTF-8. Keep this bounded and allocation-free so it is + * usable for ordinary text values, labels, and values skipped by a profile + * parser. */ +static int wolfCose_CBOR_IsUtf8(const uint8_t* data, size_t len) +{ + size_t i = 0u; + int ret = 1; + + if ((data == NULL) && (len > 0u)) { + ret = 0; + } + while ((i < len) && (ret != 0)) { + uint8_t first = data[i++]; + + if (first <= 0x7Fu) { + /* Single-byte ASCII code point. */ + } + else if ((first >= 0xC2u) && (first <= 0xDFu)) { + if (((len - i) < 1u) || ((data[i] & 0xC0u) != 0x80u)) { + ret = 0; + } + else { + i++; + } + } + else if (first == 0xE0u) { + if (((len - i) < 2u) || (data[i] < 0xA0u) || + (data[i] > 0xBFu) || ((data[i + 1u] & 0xC0u) != 0x80u)) { + ret = 0; + } + else { + i += 2u; + } + } + else if (first == 0xEDu) { + if (((len - i) < 2u) || (data[i] < 0x80u) || + (data[i] > 0x9Fu) || ((data[i + 1u] & 0xC0u) != 0x80u)) { + ret = 0; + } + else { + i += 2u; + } + } + else if (((first >= 0xE1u) && (first <= 0xECu)) || + ((first >= 0xEEu) && (first <= 0xEFu))) { + if (((len - i) < 2u) || ((data[i] & 0xC0u) != 0x80u) || + ((data[i + 1u] & 0xC0u) != 0x80u)) { + ret = 0; + } + else { + i += 2u; + } + } + else if (first == 0xF0u) { + if (((len - i) < 3u) || (data[i] < 0x90u) || + (data[i] > 0xBFu) || ((data[i + 1u] & 0xC0u) != 0x80u) || + ((data[i + 2u] & 0xC0u) != 0x80u)) { + ret = 0; + } + else { + i += 3u; + } + } + else if (first == 0xF4u) { + if (((len - i) < 3u) || (data[i] < 0x80u) || + (data[i] > 0x8Fu) || ((data[i + 1u] & 0xC0u) != 0x80u) || + ((data[i + 2u] & 0xC0u) != 0x80u)) { + ret = 0; + } + else { + i += 3u; + } + } + else if ((first >= 0xF1u) && (first <= 0xF3u)) { + if (((len - i) < 3u) || ((data[i] & 0xC0u) != 0x80u) || + ((data[i + 1u] & 0xC0u) != 0x80u) || + ((data[i + 2u] & 0xC0u) != 0x80u)) { + ret = 0; + } + else { + i += 3u; + } + } + else { + ret = 0; + } + } + + return ret; +} + /* WOLFCOSE_CBOR_CTX is public, so a caller can pass a context whose idx has * been advanced past bufSz. Subtraction-based capacity check that cannot wrap * in that case. Returns 1 when at least need bytes remain, 0 otherwise. */ @@ -56,7 +166,7 @@ int wolfCose_CBOR_EncodeHead(WOLFCOSE_CBOR_CTX* ctx, uint8_t majorType, { int ret; - if ((ctx == NULL) || (ctx->buf == NULL)) { + if (wolfCose_CBOR_IsEncodeContext(ctx) == 0) { ret = WOLFCOSE_E_INVALID_ARG; } else { @@ -136,13 +246,15 @@ int wolfCose_CBOR_EncodeHead(WOLFCOSE_CBOR_CTX* ctx, uint8_t majorType, * * For bstr/tstr: advances past the data and sets item->data/dataLen. * ----- */ -int wolfCose_CBOR_DecodeHead(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_ITEM* item) +int wolfCose_CBOR_DecodeHead_ex(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_CBOR_ITEM* item, uint32_t decodeFlags) { int ret; uint8_t ib; uint8_t ai; - if ((ctx == NULL) || (ctx->cbuf == NULL) || (item == NULL)) { + if ((ctx == NULL) || (ctx->cbuf == NULL) || (item == NULL) || + (WOLFCOSE_COSE_DECODE_FLAGS_VALID(decodeFlags) == 0)) { ret = WOLFCOSE_E_INVALID_ARG; } else if (ctx->idx >= ctx->bufSz) { @@ -218,11 +330,10 @@ int wolfCose_CBOR_DecodeHead(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_ITEM* item) ret = WOLFCOSE_E_CBOR_MALFORMED; } - /* RFC 8949 Section 4.2.1 (deterministic encoding, required for COSE): - * the argument must use the shortest additional-info form. This applies - * to every major type except simple/float (where AI 25/26/27 select - * float16/32/64 rather than encode an integer). */ + /* COSE defaults to preferred serialization. Some profiles, including + * PSA attestation, explicitly require a variation-tolerant receiver. */ if ((ret == WOLFCOSE_SUCCESS) && + (wolfCose_CBOR_AllowsNonpreferred(decodeFlags) == 0) && (item->majorType != WOLFCOSE_CBOR_SIMPLE)) { if (((ai == WOLFCOSE_CBOR_AI_1BYTE) && (item->val < 24u)) || ((ai == WOLFCOSE_CBOR_AI_2BYTE) && (item->val < 256u)) || @@ -244,6 +355,11 @@ int wolfCose_CBOR_DecodeHead(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_ITEM* item) item->data = &ctx->cbuf[ctx->idx]; item->dataLen = (size_t)item->val; ctx->idx += (size_t)item->val; + if ((item->majorType == WOLFCOSE_CBOR_TSTR) && + (wolfCose_CBOR_IsUtf8(item->data, item->dataLen) == + 0)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } } } } @@ -321,7 +437,20 @@ int wc_CBOR_EncodeBstr(WOLFCOSE_CBOR_CTX* ctx, const uint8_t* data, int wc_CBOR_EncodeTstr(WOLFCOSE_CBOR_CTX* ctx, const uint8_t* str, size_t len) { - return wolfCose_CBOR_EncodeBytes(ctx, WOLFCOSE_CBOR_TSTR, str, len); + int ret; + + if ((wolfCose_CBOR_IsEncodeContext(ctx) == 0) || + ((str == NULL) && (len > 0u))) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (wolfCose_CBOR_IsUtf8(str, len) == 0) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + ret = wolfCose_CBOR_EncodeBytes(ctx, WOLFCOSE_CBOR_TSTR, str, len); + } + + return ret; } int wc_CBOR_EncodeArrayStart(WOLFCOSE_CBOR_CTX* ctx, size_t count) @@ -346,7 +475,7 @@ static int wolfCose_CBOR_EncodeSimpleVal(WOLFCOSE_CBOR_CTX* ctx, uint8_t val) { int ret; - if ((ctx == NULL) || (ctx->buf == NULL)) { + if (wolfCose_CBOR_IsEncodeContext(ctx) == 0) { ret = WOLFCOSE_E_INVALID_ARG; } else if (wolfCose_CBOR_HasRoom(ctx, 1u) == 0) { @@ -381,7 +510,7 @@ int wc_CBOR_EncodeFloat(WOLFCOSE_CBOR_CTX* ctx, float val) int ret; uint32_t bits; - if ((ctx == NULL) || (ctx->buf == NULL)) { + if (wolfCose_CBOR_IsEncodeContext(ctx) == 0) { ret = WOLFCOSE_E_INVALID_ARG; } else if (wolfCose_CBOR_HasRoom(ctx, 5u) == 0) { @@ -403,7 +532,7 @@ int wc_CBOR_EncodeDouble(WOLFCOSE_CBOR_CTX* ctx, double val) int ret; uint64_t bits; - if ((ctx == NULL) || (ctx->buf == NULL)) { + if (wolfCose_CBOR_IsEncodeContext(ctx) == 0) { ret = WOLFCOSE_E_INVALID_ARG; } else if (wolfCose_CBOR_HasRoom(ctx, 9u) == 0) { @@ -433,10 +562,11 @@ int wc_CBOR_EncodeDouble(WOLFCOSE_CBOR_CTX* ctx, double val) int wc_CBOR_DecodeHead(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_ITEM* item) { - return wolfCose_CBOR_DecodeHead(ctx, item); + return wolfCose_CBOR_DecodeHead_ex(ctx, item, 0u); } -int wc_CBOR_DecodeUint(WOLFCOSE_CBOR_CTX* ctx, uint64_t* val) +int wolfCose_CBOR_DecodeUint_ex(WOLFCOSE_CBOR_CTX* ctx, uint64_t* val, + uint32_t decodeFlags) { int ret; WOLFCOSE_CBOR_ITEM item; @@ -445,7 +575,7 @@ int wc_CBOR_DecodeUint(WOLFCOSE_CBOR_CTX* ctx, uint64_t* val) ret = WOLFCOSE_E_INVALID_ARG; } else { - ret = wolfCose_CBOR_DecodeHead(ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(ctx, &item, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { if (item.majorType != WOLFCOSE_CBOR_UINT) { ret = WOLFCOSE_E_CBOR_TYPE; @@ -458,7 +588,13 @@ int wc_CBOR_DecodeUint(WOLFCOSE_CBOR_CTX* ctx, uint64_t* val) return ret; } -int wc_CBOR_DecodeInt(WOLFCOSE_CBOR_CTX* ctx, int64_t* val) +int wc_CBOR_DecodeUint(WOLFCOSE_CBOR_CTX* ctx, uint64_t* val) +{ + return wolfCose_CBOR_DecodeUint_ex(ctx, val, 0u); +} + +int wolfCose_CBOR_DecodeInt_ex(WOLFCOSE_CBOR_CTX* ctx, int64_t* val, + uint32_t decodeFlags) { int ret; WOLFCOSE_CBOR_ITEM item; @@ -467,7 +603,7 @@ int wc_CBOR_DecodeInt(WOLFCOSE_CBOR_CTX* ctx, int64_t* val) ret = WOLFCOSE_E_INVALID_ARG; } else { - ret = wolfCose_CBOR_DecodeHead(ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(ctx, &item, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { if (item.majorType == WOLFCOSE_CBOR_UINT) { if (item.val > (uint64_t)INT64_MAX) { @@ -494,10 +630,15 @@ int wc_CBOR_DecodeInt(WOLFCOSE_CBOR_CTX* ctx, int64_t* val) return ret; } +int wc_CBOR_DecodeInt(WOLFCOSE_CBOR_CTX* ctx, int64_t* val) +{ + return wolfCose_CBOR_DecodeInt_ex(ctx, val, 0u); +} + /* Shared decode for bstr (major type 2) and tstr (major type 3) */ -static int wolfCose_CBOR_DecodeBytes(WOLFCOSE_CBOR_CTX* ctx, - uint8_t majorType, - const uint8_t** data, size_t* dataLen) +static int wolfCose_CBOR_DecodeBytes_ex(WOLFCOSE_CBOR_CTX* ctx, + uint8_t majorType, const uint8_t** data, size_t* dataLen, + uint32_t decodeFlags) { int ret; WOLFCOSE_CBOR_ITEM item; @@ -506,7 +647,7 @@ static int wolfCose_CBOR_DecodeBytes(WOLFCOSE_CBOR_CTX* ctx, ret = WOLFCOSE_E_INVALID_ARG; } else { - ret = wolfCose_CBOR_DecodeHead(ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(ctx, &item, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { if (item.majorType != majorType) { ret = WOLFCOSE_E_CBOR_TYPE; @@ -520,22 +661,35 @@ static int wolfCose_CBOR_DecodeBytes(WOLFCOSE_CBOR_CTX* ctx, return ret; } +int wolfCose_CBOR_DecodeBstr_ex(WOLFCOSE_CBOR_CTX* ctx, + const uint8_t** data, size_t* dataLen, uint32_t decodeFlags) +{ + return wolfCose_CBOR_DecodeBytes_ex(ctx, WOLFCOSE_CBOR_BSTR, data, + dataLen, decodeFlags); +} + int wc_CBOR_DecodeBstr(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** data, - size_t* dataLen) + size_t* dataLen) { - return wolfCose_CBOR_DecodeBytes(ctx, WOLFCOSE_CBOR_BSTR, data, dataLen); + return wolfCose_CBOR_DecodeBstr_ex(ctx, data, dataLen, 0u); +} + +int wolfCose_CBOR_DecodeTstr_ex(WOLFCOSE_CBOR_CTX* ctx, + const uint8_t** str, size_t* strLen, uint32_t decodeFlags) +{ + return wolfCose_CBOR_DecodeBytes_ex(ctx, WOLFCOSE_CBOR_TSTR, str, + strLen, decodeFlags); } int wc_CBOR_DecodeTstr(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** str, - size_t* strLen) + size_t* strLen) { - return wolfCose_CBOR_DecodeBytes(ctx, WOLFCOSE_CBOR_TSTR, str, strLen); + return wolfCose_CBOR_DecodeTstr_ex(ctx, str, strLen, 0u); } /* Shared decode for array (major type 4) and map (major type 5) */ -static int wolfCose_CBOR_DecodeContainerStart(WOLFCOSE_CBOR_CTX* ctx, - uint8_t majorType, - size_t* count) +static int wolfCose_CBOR_DecodeContainerStart_ex(WOLFCOSE_CBOR_CTX* ctx, + uint8_t majorType, size_t* count, uint32_t decodeFlags) { int ret; WOLFCOSE_CBOR_ITEM item; @@ -544,7 +698,7 @@ static int wolfCose_CBOR_DecodeContainerStart(WOLFCOSE_CBOR_CTX* ctx, ret = WOLFCOSE_E_INVALID_ARG; } else { - ret = wolfCose_CBOR_DecodeHead(ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(ctx, &item, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { if (item.majorType != majorType) { ret = WOLFCOSE_E_CBOR_TYPE; @@ -563,17 +717,32 @@ static int wolfCose_CBOR_DecodeContainerStart(WOLFCOSE_CBOR_CTX* ctx, return ret; } +int wolfCose_CBOR_DecodeArrayStart_ex(WOLFCOSE_CBOR_CTX* ctx, + size_t* count, uint32_t decodeFlags) +{ + return wolfCose_CBOR_DecodeContainerStart_ex(ctx, WOLFCOSE_CBOR_ARRAY, + count, decodeFlags); +} + int wc_CBOR_DecodeArrayStart(WOLFCOSE_CBOR_CTX* ctx, size_t* count) { - return wolfCose_CBOR_DecodeContainerStart(ctx, WOLFCOSE_CBOR_ARRAY, count); + return wolfCose_CBOR_DecodeArrayStart_ex(ctx, count, 0u); +} + +int wolfCose_CBOR_DecodeMapStart_ex(WOLFCOSE_CBOR_CTX* ctx, + size_t* count, uint32_t decodeFlags) +{ + return wolfCose_CBOR_DecodeContainerStart_ex(ctx, WOLFCOSE_CBOR_MAP, + count, decodeFlags); } int wc_CBOR_DecodeMapStart(WOLFCOSE_CBOR_CTX* ctx, size_t* count) { - return wolfCose_CBOR_DecodeContainerStart(ctx, WOLFCOSE_CBOR_MAP, count); + return wolfCose_CBOR_DecodeMapStart_ex(ctx, count, 0u); } -int wc_CBOR_DecodeTag(WOLFCOSE_CBOR_CTX* ctx, uint64_t* tag) +int wolfCose_CBOR_DecodeTag_ex(WOLFCOSE_CBOR_CTX* ctx, uint64_t* tag, + uint32_t decodeFlags) { int ret; WOLFCOSE_CBOR_ITEM item; @@ -582,7 +751,7 @@ int wc_CBOR_DecodeTag(WOLFCOSE_CBOR_CTX* ctx, uint64_t* tag) ret = WOLFCOSE_E_INVALID_ARG; } else { - ret = wolfCose_CBOR_DecodeHead(ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(ctx, &item, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { if (item.majorType != WOLFCOSE_CBOR_TAG) { ret = WOLFCOSE_E_CBOR_TYPE; @@ -595,11 +764,16 @@ int wc_CBOR_DecodeTag(WOLFCOSE_CBOR_CTX* ctx, uint64_t* tag) return ret; } +int wc_CBOR_DecodeTag(WOLFCOSE_CBOR_CTX* ctx, uint64_t* tag) +{ + return wolfCose_CBOR_DecodeTag_ex(ctx, tag, 0u); +} + /* ----- * wc_CBOR_Skip: iterative traversal to skip a complete CBOR item. * Uses a bounded stack (no recursion, MISRA Rule 17.2 compliant). * ----- */ -int wc_CBOR_Skip(WOLFCOSE_CBOR_CTX* ctx) +int wolfCose_CBOR_Skip_ex(WOLFCOSE_CBOR_CTX* ctx, uint32_t decodeFlags) { int ret; WOLFCOSE_CBOR_ITEM item; @@ -615,7 +789,7 @@ int wc_CBOR_Skip(WOLFCOSE_CBOR_CTX* ctx) ret = WOLFCOSE_SUCCESS; while ((remaining > 0u) && (ret == WOLFCOSE_SUCCESS)) { - ret = wolfCose_CBOR_DecodeHead(ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(ctx, &item, decodeFlags); if (ret != WOLFCOSE_SUCCESS) { break; } @@ -677,6 +851,11 @@ int wc_CBOR_Skip(WOLFCOSE_CBOR_CTX* ctx) return ret; } +int wc_CBOR_Skip(WOLFCOSE_CBOR_CTX* ctx) +{ + return wolfCose_CBOR_Skip_ex(ctx, 0u); +} + int wc_CBOR_SkipItem(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** data, size_t* dataLen) { @@ -689,7 +868,7 @@ int wc_CBOR_SkipItem(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** data, else { size_t start = ctx->idx; - ret = wc_CBOR_Skip(ctx); + ret = wolfCose_CBOR_Skip_ex(ctx, 0u); if (ret == WOLFCOSE_SUCCESS) { /* wc_CBOR_Skip only ever advances idx, and never past bufSz. */ *data = &ctx->cbuf[start]; @@ -699,7 +878,8 @@ int wc_CBOR_SkipItem(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** data, return ret; } -int wc_CBOR_DecodeLabel(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label) +int wolfCose_CBOR_DecodeLabel_ex(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_CBOR_LABEL* label, uint32_t decodeFlags) { int ret; WOLFCOSE_CBOR_ITEM item; @@ -708,7 +888,7 @@ int wc_CBOR_DecodeLabel(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label) ret = WOLFCOSE_E_INVALID_ARG; } else { - ret = wolfCose_CBOR_DecodeHead(ctx, &item); + ret = wolfCose_CBOR_DecodeHead_ex(ctx, &item, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { label->val = 0; label->text = NULL; @@ -746,6 +926,11 @@ int wc_CBOR_DecodeLabel(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label) return ret; } +int wc_CBOR_DecodeLabel(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label) +{ + return wolfCose_CBOR_DecodeLabel_ex(ctx, label, 0u); +} + int wc_CBOR_LabelIsInt(const WOLFCOSE_CBOR_LABEL* label, int64_t val) { int match = 0; diff --git a/src/wolfcose_eat_psa.c b/src/wolfcose_eat_psa.c new file mode 100644 index 0000000..f1cf03d --- /dev/null +++ b/src/wolfcose_eat_psa.c @@ -0,0 +1,1567 @@ +/* wolfcose_eat_psa.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + * + * wolfCOSE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfCOSE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFCOSE_EAT_PSA + +#include "wolfcose_internal.h" + +/* RFC 9783 current claim labels. Recognize both standardized namespaces even + * in a selective build so that a disabled or mixed profile is rejected rather + * than silently treated as an unknown extension. */ +#define WOLFCOSE_EAT_PSA_LABEL_NONCE 10 +#define WOLFCOSE_EAT_PSA_LABEL_UEID 256 +#define WOLFCOSE_EAT_PSA_LABEL_PROFILE 265 +#define WOLFCOSE_EAT_PSA_LABEL_BOOT_SEED 268 +#define WOLFCOSE_EAT_PSA_LABEL_CLIENT_ID 2394 +#define WOLFCOSE_EAT_PSA_LABEL_LIFECYCLE 2395 +#define WOLFCOSE_EAT_PSA_LABEL_IMPLEMENTATION 2396 +#define WOLFCOSE_EAT_PSA_LABEL_CERT_REF 2398 +#define WOLFCOSE_EAT_PSA_LABEL_COMPONENTS 2399 +#define WOLFCOSE_EAT_PSA_LABEL_VSI 2400 + +/* RFC 9783 Section 4.6 legacy claim labels. */ +#define WOLFCOSE_EAT_PSA_OLD_LABEL_PROFILE (-75000) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_CLIENT_ID (-75001) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_LIFECYCLE (-75002) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_IMPLEMENTATION (-75003) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_BOOT_SEED (-75004) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_CERT_REF (-75005) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_COMPONENTS (-75006) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_NO_MEASUREMENTS (-75007) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_NONCE (-75008) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_UEID (-75009) +#define WOLFCOSE_EAT_PSA_OLD_LABEL_VSI (-75010) + +#define WOLFCOSE_EAT_PSA_COMPONENT_TYPE 1 +#define WOLFCOSE_EAT_PSA_COMPONENT_VALUE 2 +#define WOLFCOSE_EAT_PSA_COMPONENT_VER 4 +#define WOLFCOSE_EAT_PSA_COMPONENT_SIGNER 5 +#define WOLFCOSE_EAT_PSA_COMPONENT_DESC 6 + +#define WOLFCOSE_EAT_PSA_HAVE_NONCE 0x0001u +#define WOLFCOSE_EAT_PSA_HAVE_UEID 0x0002u +#define WOLFCOSE_EAT_PSA_HAVE_PROFILE 0x0004u +#define WOLFCOSE_EAT_PSA_HAVE_CLIENT_ID 0x0008u +#define WOLFCOSE_EAT_PSA_HAVE_LIFECYCLE 0x0010u +#define WOLFCOSE_EAT_PSA_HAVE_IMPLEMENTATION 0x0020u +#define WOLFCOSE_EAT_PSA_HAVE_COMPONENTS 0x0040u +#define WOLFCOSE_EAT_PSA_HAVE_BOOT_SEED 0x0080u +#define WOLFCOSE_EAT_PSA_HAVE_CERT_REF 0x0100u +#define WOLFCOSE_EAT_PSA_HAVE_VSI 0x0200u +#define WOLFCOSE_EAT_PSA_HAVE_NO_MEASUREMENTS 0x0400u + +#define WOLFCOSE_EAT_PSA_CURRENT_REQUIRED \ + (WOLFCOSE_EAT_PSA_HAVE_NONCE | WOLFCOSE_EAT_PSA_HAVE_UEID | \ + WOLFCOSE_EAT_PSA_HAVE_PROFILE | WOLFCOSE_EAT_PSA_HAVE_CLIENT_ID | \ + WOLFCOSE_EAT_PSA_HAVE_LIFECYCLE | \ + WOLFCOSE_EAT_PSA_HAVE_IMPLEMENTATION | \ + WOLFCOSE_EAT_PSA_HAVE_COMPONENTS) + +#define WOLFCOSE_EAT_PSA_OLD_REQUIRED \ + (WOLFCOSE_EAT_PSA_HAVE_NONCE | WOLFCOSE_EAT_PSA_HAVE_UEID | \ + WOLFCOSE_EAT_PSA_HAVE_CLIENT_ID | WOLFCOSE_EAT_PSA_HAVE_LIFECYCLE | \ + WOLFCOSE_EAT_PSA_HAVE_IMPLEMENTATION | WOLFCOSE_EAT_PSA_HAVE_BOOT_SEED) + +#if defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + (defined(WOLFCOSE_EAT_PSA_TFM_FULL) || defined(WOLFCOSE_EAT_PSA_ISSUE)) +static const uint8_t kEatPsaTfmProfile[] = WOLFCOSE_EAT_PSA_PROFILE_TFM; +#endif +#if defined(WOLFCOSE_EAT_PSA_LEGACY) +static const uint8_t kEatPsaLegacyProfile[] = WOLFCOSE_EAT_PSA_PROFILE_LEGACY; +#endif + +/* RFC 9783 Section 5.1 allows CBOR variation serialization. Keep that + * tolerance scoped to authenticated PSA/EAT parsing; the public CBOR API + * remains strict and carries no profile-specific decode state. */ +#define WOLFCOSE_EAT_PSA_DECODE_FLAGS \ + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED +#define WOLFCOSE_EAT_PSA_DECODE_UINT(ctx, value) \ + wolfCose_CBOR_DecodeUint_ex((ctx), (value), WOLFCOSE_EAT_PSA_DECODE_FLAGS) +#define WOLFCOSE_EAT_PSA_DECODE_INT(ctx, value) \ + wolfCose_CBOR_DecodeInt_ex((ctx), (value), WOLFCOSE_EAT_PSA_DECODE_FLAGS) +#define WOLFCOSE_EAT_PSA_DECODE_BSTR(ctx, data, len) \ + wolfCose_CBOR_DecodeBstr_ex((ctx), (data), (len), \ + WOLFCOSE_EAT_PSA_DECODE_FLAGS) +#define WOLFCOSE_EAT_PSA_DECODE_TSTR(ctx, data, len) \ + wolfCose_CBOR_DecodeTstr_ex((ctx), (data), (len), \ + WOLFCOSE_EAT_PSA_DECODE_FLAGS) +#define WOLFCOSE_EAT_PSA_DECODE_ARRAY(ctx, count) \ + wolfCose_CBOR_DecodeArrayStart_ex((ctx), (count), \ + WOLFCOSE_EAT_PSA_DECODE_FLAGS) +#define WOLFCOSE_EAT_PSA_DECODE_MAP(ctx, count) \ + wolfCose_CBOR_DecodeMapStart_ex((ctx), (count), \ + WOLFCOSE_EAT_PSA_DECODE_FLAGS) +#define WOLFCOSE_EAT_PSA_DECODE_TAG(ctx, tag) \ + wolfCose_CBOR_DecodeTag_ex((ctx), (tag), WOLFCOSE_EAT_PSA_DECODE_FLAGS) +#define WOLFCOSE_EAT_PSA_DECODE_LABEL(ctx, label) \ + wolfCose_CBOR_DecodeLabel_ex((ctx), (label), \ + WOLFCOSE_EAT_PSA_DECODE_FLAGS) +#define WOLFCOSE_EAT_PSA_SKIP(ctx) \ + wolfCose_CBOR_Skip_ex((ctx), WOLFCOSE_EAT_PSA_DECODE_FLAGS) + +#if defined(WOLFCOSE_EAT_PSA_ISSUE) +static int wolfCose_EatPsaBuffersOverlap(const uint8_t* a, size_t aSz, + const uint8_t* b, size_t bSz) +{ + int overlap = 0; + + if ((a != NULL) && (b != NULL) && (aSz != 0u) && (bSz != 0u)) { + uintptr_t aStart = (uintptr_t)(const void*)a; + uintptr_t bStart = (uintptr_t)(const void*)b; + + if (aStart <= bStart) { + overlap = ((bStart - aStart) < (uintptr_t)aSz) ? 1 : 0; + } + else { + overlap = ((aStart - bStart) < (uintptr_t)bSz) ? 1 : 0; + } + } + + return overlap; +} + +static int wolfCose_EatPsaSpanOverlapsBuffer( + const WOLFCOSE_EAT_PSA_SPAN* span, const uint8_t* buffer, + size_t bufferSz) +{ + return ((span != NULL) && + (wolfCose_EatPsaBuffersOverlap(span->data, span->len, + buffer, bufferSz) != 0)) ? 1 : 0; +} + +static int wolfCose_EatPsaComponentOverlapsBuffer( + const WOLFCOSE_EAT_PSA_COMPONENT* component, const uint8_t* buffer, + size_t bufferSz) +{ + int overlap = 0; + + if (component != NULL) { + overlap = + (wolfCose_EatPsaBuffersOverlap( + (const uint8_t*)(const void*)component, sizeof(*component), + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer(&component->measurementType, + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer(&component->measurementValue, + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer(&component->version, + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer(&component->signerId, + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer(&component->measurementDesc, + buffer, bufferSz) != 0); + } + + return overlap; +} + +/* Call only after claim validation has bounded componentCount and checked all + * required pointers. Encoding is deliberately not an in-place operation: + * later map keys and string headers must never overwrite data still to read. */ +static int wolfCose_EatPsaClaimsOverlapBuffer( + const WOLFCOSE_EAT_PSA_CLAIMS* claims, const uint8_t* buffer, + size_t bufferSz) +{ + int overlap = 0; + + if (claims != NULL) { + size_t i; + + overlap = + (wolfCose_EatPsaBuffersOverlap( + (const uint8_t*)(const void*)claims, sizeof(*claims), + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer(&claims->nonce, + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer(&claims->ueid, + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer(&claims->implementationId, + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer(&claims->bootSeed, + buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer( + &claims->certificationReference, buffer, bufferSz) != 0) || + (wolfCose_EatPsaSpanOverlapsBuffer( + &claims->verificationServiceIndicator, buffer, + bufferSz) != 0); + for (i = 0u; (overlap == 0) && (i < claims->componentCount); i++) { + overlap = wolfCose_EatPsaComponentOverlapsBuffer( + &claims->components[i], buffer, bufferSz); + } + } + + return overlap; +} + +static int wolfCose_EatPsaIssueBuffersOverlap( + const uint8_t* claimsBuf, size_t claimsBufSz, + const uint8_t* scratch, size_t scratchSz, + const uint8_t* out, size_t outSz) +{ + return ((wolfCose_EatPsaBuffersOverlap(claimsBuf, claimsBufSz, + scratch, scratchSz) != 0) || + (wolfCose_EatPsaBuffersOverlap(claimsBuf, claimsBufSz, + out, outSz) != 0) || + (wolfCose_EatPsaBuffersOverlap(scratch, scratchSz, + out, outSz) != 0)) ? 1 : 0; +} +#endif /* WOLFCOSE_EAT_PSA_ISSUE */ + +static int wolfCose_EatPsaIsHash(const WOLFCOSE_EAT_PSA_SPAN* span) +{ + int ret = 0; + + if ((span != NULL) && (span->data != NULL) && + ((span->len == 32u) || (span->len == 48u) || (span->len == 64u))) { + ret = 1; + } + + return ret; +} + +#if defined(WOLFCOSE_EAT_PSA_ISSUE) && defined(WOLFCOSE_CBOR_ENCODE) +static int wolfCose_EatPsaIsOptionalSpan(const WOLFCOSE_EAT_PSA_SPAN* span) +{ + int ret = 0; + + if (span != NULL) { + ret = (((span->data == NULL) && (span->len == 0u)) || + (span->data != NULL)) ? 1 : 0; + } + + return ret; +} +#endif /* WOLFCOSE_EAT_PSA_ISSUE && WOLFCOSE_CBOR_ENCODE */ + +static int wolfCose_EatPsaLifecycleValid(uint16_t lifecycle) +{ + uint16_t major = (uint16_t)(lifecycle & 0xFF00u); + + return ((major == 0x0000u) || (major == 0x1000u) || + (major == 0x2000u) || (major == 0x3000u) || + (major == 0x4000u) || (major == 0x5000u) || + (major == 0x6000u)) ? 1 : 0; +} + +static int wolfCose_EatPsaCertRefValid(const WOLFCOSE_EAT_PSA_SPAN* span, + size_t digits, int hasVersion) +{ + int ret = 1; + + if ((span == NULL) || (span->data == NULL) || + (span->len != (digits + ((hasVersion != 0) ? 1u : 0u)))) { + ret = 0; + } + else { + size_t i; + + for (i = 0u; i < span->len; i++) { + if ((hasVersion != 0) && (i == 13u)) { + if (span->data[i] != (uint8_t)'-') { + ret = 0; + } + } + else if ((span->data[i] < (uint8_t)'0') || + (span->data[i] > (uint8_t)'9')) { + ret = 0; + } + } + } + + return ret; +} + +#if defined(WOLFCOSE_EAT_PSA_SIGN1) +static int wolfCose_EatPsaSignAlg(int32_t alg) +{ + int ret = 0; + + switch (alg) { +#if defined(WOLFCOSE_HAVE_ES256) + case WOLFCOSE_ALG_ES256: + ret = 1; + break; +#endif +#if defined(WOLFCOSE_HAVE_ES384) + case WOLFCOSE_ALG_ES384: + ret = 1; + break; +#endif +#if defined(WOLFCOSE_HAVE_ES512) + case WOLFCOSE_ALG_ES512: + ret = 1; + break; +#endif + default: + break; + } + + return ret; +} +#endif /* WOLFCOSE_EAT_PSA_SIGN1 */ + +#if defined(WOLFCOSE_EAT_PSA_MAC0) +static int wolfCose_EatPsaMacAlg(int32_t alg) +{ + int ret = 0; + + switch (alg) { +#if defined(WOLFCOSE_HAVE_HMAC256) + case WOLFCOSE_ALG_HMAC_256_256: + ret = 1; + break; +#endif +#if defined(WOLFCOSE_HAVE_HMAC384) + case WOLFCOSE_ALG_HMAC_384_384: + ret = 1; + break; +#endif +#if defined(WOLFCOSE_HAVE_HMAC512) + case WOLFCOSE_ALG_HMAC_512_512: + ret = 1; + break; +#endif + default: + break; + } + + return ret; +} +#endif /* WOLFCOSE_EAT_PSA_MAC0 */ + +#if defined(WOLFCOSE_EAT_PSA_ISSUE) && defined(WOLFCOSE_CBOR_ENCODE) +static int wolfCose_EatPsaValidateComponent( + const WOLFCOSE_EAT_PSA_COMPONENT* component) +{ + int ret = WOLFCOSE_SUCCESS; + + if ((component == NULL) || + (wolfCose_EatPsaIsHash(&component->measurementValue) == 0) || + (wolfCose_EatPsaIsHash(&component->signerId) == 0) || + (wolfCose_EatPsaIsOptionalSpan(&component->measurementType) == 0) || + (wolfCose_EatPsaIsOptionalSpan(&component->version) == 0) || + (wolfCose_EatPsaIsOptionalSpan(&component->measurementDesc) == 0)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + + return ret; +} + +static int wolfCose_EatPsaValidateClaims( + const WOLFCOSE_EAT_PSA_CLAIMS* claims) +{ + int ret = WOLFCOSE_SUCCESS; + size_t i; + + if ((claims == NULL) || (claims->nonce.data == NULL) || + ((claims->nonce.len != 32u) && (claims->nonce.len != 48u) && + (claims->nonce.len != 64u)) || + (claims->ueid.data == NULL) || (claims->ueid.len != 33u) || + (claims->ueid.data[0] != 0x01u) || + (claims->implementationId.data == NULL) || + (claims->implementationId.len != 32u) || (claims->clientId == 0) || + (wolfCose_EatPsaLifecycleValid(claims->lifecycle) == 0) || + (claims->components == NULL) || (claims->componentCount == 0u) || + (claims->componentCount > WOLFCOSE_EAT_PSA_MAX_COMPONENTS)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if ((ret == WOLFCOSE_SUCCESS) && + ((wolfCose_EatPsaIsOptionalSpan(&claims->bootSeed) == 0) || + ((claims->bootSeed.data != NULL) && + ((claims->bootSeed.len < 8u) || (claims->bootSeed.len > 32u))) || + (wolfCose_EatPsaIsOptionalSpan(&claims->certificationReference) == 0) || + ((claims->certificationReference.data != NULL) && + (wolfCose_EatPsaCertRefValid(&claims->certificationReference, + 18u, 1) == 0)) || + (wolfCose_EatPsaIsOptionalSpan( + &claims->verificationServiceIndicator) == 0))) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < claims->componentCount); + i++) { + ret = wolfCose_EatPsaValidateComponent(&claims->components[i]); + } + + return ret; +} + +static int wolfCose_EatPsaEncodeComponent(WOLFCOSE_CBOR_CTX* ctx, + const WOLFCOSE_EAT_PSA_COMPONENT* component) +{ + int ret = WOLFCOSE_SUCCESS; + size_t count = 2u; + + if ((component->measurementType.data != NULL) || + (component->version.data != NULL) || + (component->measurementDesc.data != NULL)) { + count += (component->measurementType.data != NULL) ? 1u : 0u; + count += (component->version.data != NULL) ? 1u : 0u; + count += (component->measurementDesc.data != NULL) ? 1u : 0u; + } + + ret = wc_CBOR_EncodeMapStart(ctx, count); + if ((ret == WOLFCOSE_SUCCESS) && (component->measurementType.data != NULL)) { + ret = wc_CBOR_EncodeUint(ctx, WOLFCOSE_EAT_PSA_COMPONENT_TYPE); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(ctx, component->measurementType.data, + component->measurementType.len); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, WOLFCOSE_EAT_PSA_COMPONENT_VALUE); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, component->measurementValue.data, + component->measurementValue.len); + } + if ((ret == WOLFCOSE_SUCCESS) && (component->version.data != NULL)) { + ret = wc_CBOR_EncodeUint(ctx, WOLFCOSE_EAT_PSA_COMPONENT_VER); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(ctx, component->version.data, + component->version.len); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, WOLFCOSE_EAT_PSA_COMPONENT_SIGNER); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, component->signerId.data, + component->signerId.len); + } + if ((ret == WOLFCOSE_SUCCESS) && (component->measurementDesc.data != NULL)) { + ret = wc_CBOR_EncodeUint(ctx, WOLFCOSE_EAT_PSA_COMPONENT_DESC); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(ctx, component->measurementDesc.data, + component->measurementDesc.len); + } + } + + return ret; +} +#endif /* WOLFCOSE_EAT_PSA_ISSUE && WOLFCOSE_CBOR_ENCODE */ + +#if defined(WOLFCOSE_EAT_PSA_ISSUE) +int wc_CoseEatPsaToken_EncodeClaims(const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* out, size_t outSz, size_t* outLen) +{ +#if defined(WOLFCOSE_CBOR_ENCODE) + int ret; + WOLFCOSE_CBOR_CTX ctx; + size_t count = 7u; + size_t i; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((out == NULL) || (outLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = wolfCose_EatPsaValidateClaims(claims); + } + if ((ret == WOLFCOSE_SUCCESS) && + (wolfCose_EatPsaClaimsOverlapBuffer(claims, out, outSz) != 0)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + if (ret == WOLFCOSE_SUCCESS) { + count += (claims->bootSeed.data != NULL) ? 1u : 0u; + count += (claims->certificationReference.data != NULL) ? 1u : 0u; + count += (claims->verificationServiceIndicator.data != NULL) ? 1u : 0u; + ret = wc_CBOR_EncoderInit(&ctx, out, outSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, count); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_NONCE); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->nonce.data, claims->nonce.len); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_UEID); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->ueid.data, claims->ueid.len); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_PROFILE); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, kEatPsaTfmProfile, + sizeof(kEatPsaTfmProfile) - 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && (claims->bootSeed.data != NULL)) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_BOOT_SEED); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->bootSeed.data, + claims->bootSeed.len); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_CLIENT_ID); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, (int64_t)claims->clientId); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_LIFECYCLE); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, (uint64_t)claims->lifecycle); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_IMPLEMENTATION); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->implementationId.data, + claims->implementationId.len); + } + if ((ret == WOLFCOSE_SUCCESS) && + (claims->certificationReference.data != NULL)) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_CERT_REF); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, claims->certificationReference.data, + claims->certificationReference.len); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_COMPONENTS); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&ctx, claims->componentCount); + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < claims->componentCount); + i++) { + ret = wolfCose_EatPsaEncodeComponent(&ctx, &claims->components[i]); + } + if ((ret == WOLFCOSE_SUCCESS) && + (claims->verificationServiceIndicator.data != NULL)) { + ret = wc_CBOR_EncodeUint(&ctx, WOLFCOSE_EAT_PSA_LABEL_VSI); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, + claims->verificationServiceIndicator.data, + claims->verificationServiceIndicator.len); + } + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = ctx.idx; + } + + return ret; +#else + (void)claims; + (void)out; + (void)outSz; + (void)outLen; + return WOLFCOSE_E_UNSUPPORTED; +#endif +} +#endif /* WOLFCOSE_EAT_PSA_ISSUE */ + +static int wolfCose_EatPsaLabelsEqual(const WOLFCOSE_CBOR_LABEL* first, + const WOLFCOSE_CBOR_LABEL* second) +{ + int ret = 0; + + if ((first != NULL) && (second != NULL) && + (first->isText == second->isText)) { + if (first->isText != 0u) { + ret = wc_CBOR_LabelIsText(first, second->text, second->textLen); + } + else if (first->val == second->val) { + ret = 1; + } + else { + /* Labels differ. */ + } + } + + return ret; +} + +/* RFC 9783 Section 5.1.1 requires valid CBOR. The profile maps accept + * unknown extension labels, so a fixed seen-bit mask alone cannot reject a + * duplicate unknown key. Re-scan only the earlier pairs in this bounded input + * map. The caller-configurable profile claim limits bound this work without + * requiring allocation or restricting the labels assigned to extensions. */ +static int wolfCose_EatPsaMapHasPriorLabel(const uint8_t* map, size_t mapSz, + size_t before, const WOLFCOSE_CBOR_LABEL* label, int* duplicate) +{ + int ret; + WOLFCOSE_CBOR_CTX scan; + size_t count = 0u; + size_t i; + + if ((map == NULL) || (label == NULL) || (duplicate == NULL) || + (before > mapSz)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + *duplicate = 0; + ret = wc_CBOR_DecoderInit(&scan, map, mapSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_EAT_PSA_DECODE_MAP(&scan, &count); + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count) && + (scan.idx < before); i++) { + WOLFCOSE_CBOR_LABEL prior; + + ret = WOLFCOSE_EAT_PSA_DECODE_LABEL(&scan, &prior); + if ((ret == WOLFCOSE_SUCCESS) && + (wolfCose_EatPsaLabelsEqual(&prior, label) != 0)) { + *duplicate = 1; + break; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_EAT_PSA_SKIP(&scan); + } + } + if ((ret == WOLFCOSE_SUCCESS) && (*duplicate == 0) && + (scan.idx != before)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + + return ret; +} + +static int wolfCose_EatPsaDecodeComponent(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_EAT_PSA_COMPONENT* component) +{ + int ret; + size_t count = 0u; + size_t i; + size_t mapStart = 0u; + uint32_t seen = 0u; + + if ((ctx == NULL) || (component == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + (void)XMEMSET(component, 0, sizeof(*component)); + mapStart = ctx->idx; + ret = WOLFCOSE_EAT_PSA_DECODE_MAP(ctx, &count); + } + if ((ret == WOLFCOSE_SUCCESS) && + (count > WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + WOLFCOSE_CBOR_LABEL label; + size_t labelStart = ctx->idx - mapStart; + int duplicate = 0; + + ret = WOLFCOSE_EAT_PSA_DECODE_LABEL(ctx, &label); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EatPsaMapHasPriorLabel(&ctx->cbuf[mapStart], + ctx->bufSz - mapStart, labelStart, &label, &duplicate); + } + if ((ret == WOLFCOSE_SUCCESS) && (duplicate != 0)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if ((ret == WOLFCOSE_SUCCESS) && (label.isText != 0u)) { + ret = WOLFCOSE_EAT_PSA_SKIP(ctx); + } + else if ((ret == WOLFCOSE_SUCCESS) && + (label.val == WOLFCOSE_EAT_PSA_COMPONENT_TYPE)) { + if ((seen & 0x01u) != 0u) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else { + seen |= 0x01u; + ret = WOLFCOSE_EAT_PSA_DECODE_TSTR(ctx, &component->measurementType.data, + &component->measurementType.len); + } + } + else if ((ret == WOLFCOSE_SUCCESS) && + (label.val == WOLFCOSE_EAT_PSA_COMPONENT_VALUE)) { + if ((seen & 0x02u) != 0u) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else { + seen |= 0x02u; + ret = WOLFCOSE_EAT_PSA_DECODE_BSTR(ctx, &component->measurementValue.data, + &component->measurementValue.len); + } + } + else if ((ret == WOLFCOSE_SUCCESS) && + (label.val == WOLFCOSE_EAT_PSA_COMPONENT_VER)) { + if ((seen & 0x04u) != 0u) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else { + seen |= 0x04u; + ret = WOLFCOSE_EAT_PSA_DECODE_TSTR(ctx, &component->version.data, + &component->version.len); + } + } + else if ((ret == WOLFCOSE_SUCCESS) && + (label.val == WOLFCOSE_EAT_PSA_COMPONENT_SIGNER)) { + if ((seen & 0x08u) != 0u) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else { + seen |= 0x08u; + ret = WOLFCOSE_EAT_PSA_DECODE_BSTR(ctx, &component->signerId.data, + &component->signerId.len); + } + } + else if ((ret == WOLFCOSE_SUCCESS) && + (label.val == WOLFCOSE_EAT_PSA_COMPONENT_DESC)) { + if ((seen & 0x10u) != 0u) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else { + seen |= 0x10u; + ret = WOLFCOSE_EAT_PSA_DECODE_TSTR(ctx, &component->measurementDesc.data, + &component->measurementDesc.len); + } + } + else if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_EAT_PSA_SKIP(ctx); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + ((wolfCose_EatPsaIsHash(&component->measurementValue) == 0) || + (wolfCose_EatPsaIsHash(&component->signerId) == 0))) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + + return ret; +} + +static int wolfCose_EatPsaDecodeComponents(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_EAT_PSA_TOKEN* token) +{ + int ret; + size_t i; + size_t count = 0u; + const uint8_t* start; + WOLFCOSE_EAT_PSA_COMPONENT component; + + if ((ctx == NULL) || (token == NULL) || (ctx->idx >= ctx->bufSz)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + start = &ctx->cbuf[ctx->idx]; + ret = WOLFCOSE_EAT_PSA_DECODE_ARRAY(ctx, &count); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((count == 0u) || (count > WOLFCOSE_EAT_PSA_MAX_COMPONENTS))) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + ret = wolfCose_EatPsaDecodeComponent(ctx, &component); + } + if (ret == WOLFCOSE_SUCCESS) { + token->components.data = start; + token->components.len = (size_t)(&ctx->cbuf[ctx->idx] - start); + token->componentCount = count; + } + + return ret; +} + +static int wolfCose_EatPsaSetProfile(WOLFCOSE_EAT_PSA_TOKEN* token, + const uint8_t* text, size_t textLen, WOLFCOSE_EAT_PSA_PROFILE profile) +{ + int ret = WOLFCOSE_SUCCESS; + const uint8_t* expected = NULL; + size_t expectedLen = 0u; + + if (profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT) { +#if defined(WOLFCOSE_EAT_PSA_CURRENT) && defined(WOLFCOSE_EAT_PSA_TFM_FULL) + expected = kEatPsaTfmProfile; + expectedLen = sizeof(kEatPsaTfmProfile) - 1u; +#else + /* RFC 9783 Section 5.2 requires a #tfm receiver to accept both + * envelopes and every mandatory ESxxx/HMACxxx algorithm. A selective + * build must use a separately named profile, which this standard + * profile API deliberately does not mint or accept. */ + ret = WOLFCOSE_E_EAT_PSA_PROFILE; +#endif + } + else if (profile == WOLFCOSE_EAT_PSA_PROFILE_OLD) { +#if defined(WOLFCOSE_EAT_PSA_LEGACY) + expected = kEatPsaLegacyProfile; + expectedLen = sizeof(kEatPsaLegacyProfile) - 1u; +#else + ret = WOLFCOSE_E_EAT_PSA_PROFILE; +#endif + } + else { + ret = WOLFCOSE_E_EAT_PSA_PROFILE; + } + if (ret == WOLFCOSE_SUCCESS) { + if ((textLen != expectedLen) || + (XMEMCMP(text, expected, textLen) != 0)) { + ret = WOLFCOSE_E_EAT_PSA_PROFILE; + } + else { + token->profile = profile; + } + } + + return ret; +} + +/* Map both standardized claim namespaces. Unknown EAT claims remain skippable, + * but a recognized label from a disabled profile is a profile error. */ +static void wolfCose_EatPsaClaimType(int64_t label, uint32_t* bit, + int* type, uint8_t* legacy) +{ + *bit = 0u; + *type = 0; + *legacy = 0u; + + switch (label) { + case WOLFCOSE_EAT_PSA_LABEL_NONCE: + *bit = WOLFCOSE_EAT_PSA_HAVE_NONCE; + *type = 1; + break; + case WOLFCOSE_EAT_PSA_LABEL_UEID: + *bit = WOLFCOSE_EAT_PSA_HAVE_UEID; + *type = 2; + break; + case WOLFCOSE_EAT_PSA_LABEL_PROFILE: + *bit = WOLFCOSE_EAT_PSA_HAVE_PROFILE; + *type = 3; + break; + case WOLFCOSE_EAT_PSA_LABEL_BOOT_SEED: + *bit = WOLFCOSE_EAT_PSA_HAVE_BOOT_SEED; + *type = 4; + break; + case WOLFCOSE_EAT_PSA_LABEL_CLIENT_ID: + *bit = WOLFCOSE_EAT_PSA_HAVE_CLIENT_ID; + *type = 5; + break; + case WOLFCOSE_EAT_PSA_LABEL_LIFECYCLE: + *bit = WOLFCOSE_EAT_PSA_HAVE_LIFECYCLE; + *type = 6; + break; + case WOLFCOSE_EAT_PSA_LABEL_IMPLEMENTATION: + *bit = WOLFCOSE_EAT_PSA_HAVE_IMPLEMENTATION; + *type = 7; + break; + case WOLFCOSE_EAT_PSA_LABEL_CERT_REF: + *bit = WOLFCOSE_EAT_PSA_HAVE_CERT_REF; + *type = 8; + break; + case WOLFCOSE_EAT_PSA_LABEL_COMPONENTS: + *bit = WOLFCOSE_EAT_PSA_HAVE_COMPONENTS; + *type = 9; + break; + case WOLFCOSE_EAT_PSA_LABEL_VSI: + *bit = WOLFCOSE_EAT_PSA_HAVE_VSI; + *type = 11; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_NONCE: + *bit = WOLFCOSE_EAT_PSA_HAVE_NONCE; + *type = 1; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_UEID: + *bit = WOLFCOSE_EAT_PSA_HAVE_UEID; + *type = 2; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_PROFILE: + *bit = WOLFCOSE_EAT_PSA_HAVE_PROFILE; + *type = 3; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_BOOT_SEED: + *bit = WOLFCOSE_EAT_PSA_HAVE_BOOT_SEED; + *type = 4; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_CLIENT_ID: + *bit = WOLFCOSE_EAT_PSA_HAVE_CLIENT_ID; + *type = 5; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_LIFECYCLE: + *bit = WOLFCOSE_EAT_PSA_HAVE_LIFECYCLE; + *type = 6; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_IMPLEMENTATION: + *bit = WOLFCOSE_EAT_PSA_HAVE_IMPLEMENTATION; + *type = 7; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_CERT_REF: + *bit = WOLFCOSE_EAT_PSA_HAVE_CERT_REF; + *type = 8; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_COMPONENTS: + *bit = WOLFCOSE_EAT_PSA_HAVE_COMPONENTS; + *type = 9; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_NO_MEASUREMENTS: + *bit = WOLFCOSE_EAT_PSA_HAVE_NO_MEASUREMENTS; + *type = 10; + *legacy = 1u; + break; + case WOLFCOSE_EAT_PSA_OLD_LABEL_VSI: + *bit = WOLFCOSE_EAT_PSA_HAVE_VSI; + *type = 11; + *legacy = 1u; + break; + default: + break; + } +} + +static int wolfCose_EatPsaDecodeClaims(const uint8_t* payload, + size_t payloadLen, WOLFCOSE_EAT_PSA_TOKEN* token) +{ + int ret; + WOLFCOSE_CBOR_CTX ctx; + size_t count = 0u; + size_t i; + size_t mapStart = 0u; + uint32_t seen = 0u; + uint8_t currentLabels = 0u; + uint8_t oldLabels = 0u; + + if ((payload == NULL) || (token == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + (void)XMEMSET(token, 0, sizeof(*token)); + ret = wc_CBOR_DecoderInit(&ctx, payload, payloadLen); + } + if (ret == WOLFCOSE_SUCCESS) { + mapStart = ctx.idx; + ret = WOLFCOSE_EAT_PSA_DECODE_MAP(&ctx, &count); + } + if ((ret == WOLFCOSE_SUCCESS) && + (count > WOLFCOSE_EAT_PSA_MAX_CLAIMS)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + WOLFCOSE_CBOR_LABEL label; + size_t labelStart = ctx.idx - mapStart; + uint32_t bit = 0u; + int type = 0; + int duplicate = 0; + int enabled = 0; + uint8_t legacy = 0u; + + ret = WOLFCOSE_EAT_PSA_DECODE_LABEL(&ctx, &label); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EatPsaMapHasPriorLabel(&ctx.cbuf[mapStart], + ctx.bufSz - mapStart, labelStart, &label, &duplicate); + } + if ((ret == WOLFCOSE_SUCCESS) && (duplicate != 0)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if ((ret == WOLFCOSE_SUCCESS) && (label.isText != 0u)) { + ret = WOLFCOSE_EAT_PSA_SKIP(&ctx); + } + else if (ret == WOLFCOSE_SUCCESS) { + wolfCose_EatPsaClaimType(label.val, &bit, &type, &legacy); + if (type == 0) { + ret = WOLFCOSE_EAT_PSA_SKIP(&ctx); + } + else { +#if defined(WOLFCOSE_EAT_PSA_CURRENT) + if (legacy == 0u) { + enabled = 1; + } +#endif +#if defined(WOLFCOSE_EAT_PSA_LEGACY) + if (legacy != 0u) { + enabled = 1; + } +#endif + if (enabled == 0) { + ret = WOLFCOSE_E_EAT_PSA_PROFILE; + } + else if (((legacy != 0u) && (currentLabels != 0u)) || + ((legacy == 0u) && (oldLabels != 0u))) { + ret = WOLFCOSE_E_EAT_PSA_PROFILE; + } + else if ((seen & bit) != 0u) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else { + seen |= bit; + if (legacy != 0u) { + oldLabels = 1u; + } + else { + currentLabels = 1u; + } + + if (type == 1) { + ret = WOLFCOSE_EAT_PSA_DECODE_BSTR(&ctx, + &token->nonce.data, &token->nonce.len); + } + else if (type == 2) { + ret = WOLFCOSE_EAT_PSA_DECODE_BSTR(&ctx, + &token->ueid.data, &token->ueid.len); + } + else if (type == 3) { + const uint8_t* profile; + size_t profileLen; + ret = WOLFCOSE_EAT_PSA_DECODE_TSTR(&ctx, &profile, + &profileLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EatPsaSetProfile(token, profile, + profileLen, (legacy != 0u) ? + WOLFCOSE_EAT_PSA_PROFILE_OLD : + WOLFCOSE_EAT_PSA_PROFILE_CURRENT); + } + } + else if (type == 4) { + ret = WOLFCOSE_EAT_PSA_DECODE_BSTR(&ctx, + &token->bootSeed.data, &token->bootSeed.len); + } + else if (type == 5) { + int64_t clientId; + ret = WOLFCOSE_EAT_PSA_DECODE_INT(&ctx, &clientId); + if ((ret == WOLFCOSE_SUCCESS) && + ((clientId < (-2147483647LL - 1LL)) || + (clientId > 2147483647LL) || (clientId == 0))) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if (ret == WOLFCOSE_SUCCESS) { + token->clientId = (int32_t)clientId; + } + } + else if (type == 6) { + uint64_t lifecycle; + ret = WOLFCOSE_EAT_PSA_DECODE_UINT(&ctx, &lifecycle); + if ((ret == WOLFCOSE_SUCCESS) && + ((lifecycle > 0xFFFFu) || + (wolfCose_EatPsaLifecycleValid( + (uint16_t)lifecycle) == 0))) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if (ret == WOLFCOSE_SUCCESS) { + token->lifecycle = (uint16_t)lifecycle; + } + } + else if (type == 7) { + ret = WOLFCOSE_EAT_PSA_DECODE_BSTR(&ctx, + &token->implementationId.data, + &token->implementationId.len); + } + else if (type == 8) { + ret = WOLFCOSE_EAT_PSA_DECODE_TSTR(&ctx, + &token->certificationReference.data, + &token->certificationReference.len); + } + else if (type == 9) { + ret = wolfCose_EatPsaDecodeComponents(&ctx, token); + } +#if defined(WOLFCOSE_EAT_PSA_LEGACY) + else if (type == 10) { + uint64_t noMeasurements; + + ret = WOLFCOSE_EAT_PSA_DECODE_UINT(&ctx, + &noMeasurements); + if ((ret == WOLFCOSE_SUCCESS) && + (noMeasurements != 1u)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if (ret == WOLFCOSE_SUCCESS) { + token->noSoftwareMeasurements = 1u; + } + } +#endif + else { + ret = WOLFCOSE_EAT_PSA_DECODE_TSTR(&ctx, + &token->verificationServiceIndicator.data, + &token->verificationServiceIndicator.len); + } + } + } + } + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if ((ret == WOLFCOSE_SUCCESS) && + ((currentLabels != 0u) == (oldLabels != 0u))) { + ret = WOLFCOSE_E_EAT_PSA_PROFILE; + } +#if defined(WOLFCOSE_EAT_PSA_CURRENT) + if ((ret == WOLFCOSE_SUCCESS) && (currentLabels != 0u)) { + if ((seen & WOLFCOSE_EAT_PSA_CURRENT_REQUIRED) != + WOLFCOSE_EAT_PSA_CURRENT_REQUIRED) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else if (token->profile != WOLFCOSE_EAT_PSA_PROFILE_CURRENT) { + ret = WOLFCOSE_E_EAT_PSA_PROFILE; + } + } +#endif +#if defined(WOLFCOSE_EAT_PSA_LEGACY) + if ((ret == WOLFCOSE_SUCCESS) && (oldLabels != 0u)) { + if ((seen & WOLFCOSE_EAT_PSA_OLD_REQUIRED) != + WOLFCOSE_EAT_PSA_OLD_REQUIRED) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else if (((seen & WOLFCOSE_EAT_PSA_HAVE_COMPONENTS) != 0u) == + ((seen & WOLFCOSE_EAT_PSA_HAVE_NO_MEASUREMENTS) != 0u)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + else if (token->profile == WOLFCOSE_EAT_PSA_PROFILE_NONE) { + token->profile = WOLFCOSE_EAT_PSA_PROFILE_OLD; + } + else if (token->profile != WOLFCOSE_EAT_PSA_PROFILE_OLD) { + ret = WOLFCOSE_E_EAT_PSA_PROFILE; + } + } +#endif + if ((ret == WOLFCOSE_SUCCESS) && + ((token->nonce.len != 32u) && (token->nonce.len != 48u) && + (token->nonce.len != 64u))) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if ((ret == WOLFCOSE_SUCCESS) && + ((token->ueid.len != 33u) || (token->ueid.data[0] != 0x01u) || + (token->implementationId.len != 32u))) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } +#if defined(WOLFCOSE_EAT_PSA_CURRENT) + if ((ret == WOLFCOSE_SUCCESS) && + (token->profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT) && + (token->bootSeed.data != NULL) && + ((token->bootSeed.len < 8u) || (token->bootSeed.len > 32u))) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } +#endif +#if defined(WOLFCOSE_EAT_PSA_LEGACY) + if ((ret == WOLFCOSE_SUCCESS) && + (token->profile == WOLFCOSE_EAT_PSA_PROFILE_OLD) && + (token->bootSeed.len != 32u)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } +#endif +#if defined(WOLFCOSE_EAT_PSA_CURRENT) + if ((ret == WOLFCOSE_SUCCESS) && + (token->certificationReference.data != NULL) && + (token->profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT) && + (wolfCose_EatPsaCertRefValid(&token->certificationReference, + 18u, 1) == 0)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } +#endif +#if defined(WOLFCOSE_EAT_PSA_LEGACY) + if ((ret == WOLFCOSE_SUCCESS) && + (token->certificationReference.data != NULL) && + (token->profile == WOLFCOSE_EAT_PSA_PROFILE_OLD) && + (wolfCose_EatPsaCertRefValid(&token->certificationReference, + 13u, 0) == 0)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } +#endif + if ((ret != WOLFCOSE_SUCCESS) && (token != NULL)) { + (void)XMEMSET(token, 0, sizeof(*token)); + } + + return ret; +} + +static int wolfCose_EatPsaGetEnvelope(const uint8_t* in, size_t inSz, + uint64_t* tag, const uint8_t** payload, size_t* payloadLen, int32_t* alg) +{ + int ret; + WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_HDR hdr = { 0 }; + WOLFCOSE_HDR_STATE hdrState; + size_t arrayCount = 0u; + const uint8_t* protectedData; + size_t protectedLen; + const uint8_t* ignored; + size_t ignoredLen; + + if ((in == NULL) || (tag == NULL) || (payload == NULL) || + (payloadLen == NULL) || (alg == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = wc_CBOR_DecoderInit(&ctx, in, inSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_EAT_PSA_DECODE_TAG(&ctx, tag); + } + if ((ret == WOLFCOSE_SUCCESS) && (*tag != WOLFCOSE_TAG_SIGN1) && + (*tag != WOLFCOSE_TAG_MAC0)) { + ret = WOLFCOSE_E_COSE_BAD_TAG; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_EAT_PSA_DECODE_ARRAY(&ctx, &arrayCount); + if ((ret == WOLFCOSE_SUCCESS) && (arrayCount != 4u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_EAT_PSA_DECODE_BSTR(&ctx, &protectedData, &protectedLen); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr_ex(protectedData, protectedLen, + &hdr, &hdrState, WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + } + /* COSE requires alg to be authenticated where the construction permits + * it. PSA uses no external AAD, so the only valid location is protected. */ + if ((ret == WOLFCOSE_SUCCESS) && + ((hdrState.labelBits & 0x00000001u) == 0u)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr_ex(&ctx, &hdr, &hdrState, + WOLFCOSE_EAT_PSA_DECODE_FLAGS); + } + /* This API deliberately uses caller-supplied raw verification keys. Do + * not accept an x5chain parameter that would look validated but is not. */ + if ((ret == WOLFCOSE_SUCCESS) && + ((hdr.flags & WOLFCOSE_HDR_FLAG_X5CHAIN) != 0u)) { + ret = WOLFCOSE_E_UNSUPPORTED; + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx < ctx.bufSz) && + (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { + ret = WOLFCOSE_E_DETACHED_PAYLOAD; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_EAT_PSA_DECODE_BSTR(&ctx, payload, payloadLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_EAT_PSA_DECODE_BSTR(&ctx, &ignored, &ignoredLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + *alg = hdr.alg; + } + + return ret; +} + +static int wolfCose_EatPsaCheckNonce(const WOLFCOSE_EAT_PSA_TOKEN* token, + const uint8_t* expectedNonce, size_t expectedNonceLen) +{ + int ret = WOLFCOSE_SUCCESS; + + if ((token == NULL) || (expectedNonce == NULL) || + ((expectedNonceLen != 32u) && (expectedNonceLen != 48u) && + (expectedNonceLen != 64u))) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if ((token->nonce.len != expectedNonceLen) || + (XMEMCMP(token->nonce.data, expectedNonce, expectedNonceLen) != 0)) { + ret = WOLFCOSE_E_EAT_PSA_NONCE; + } + + return ret; +} + +static int wolfCose_EatPsaCheckEnvelopeAlg(uint64_t tag, int32_t alg) +{ + int ret = WOLFCOSE_SUCCESS; + + if (tag == WOLFCOSE_TAG_SIGN1) { +#if defined(WOLFCOSE_EAT_PSA_SIGN1) + if (wolfCose_EatPsaSignAlg(alg) == 0) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } +#else + ret = WOLFCOSE_E_UNSUPPORTED; +#endif + } + else if (tag == WOLFCOSE_TAG_MAC0) { +#if defined(WOLFCOSE_EAT_PSA_MAC0) + if (wolfCose_EatPsaMacAlg(alg) == 0) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } +#else + ret = WOLFCOSE_E_UNSUPPORTED; +#endif + } + else { + ret = WOLFCOSE_E_COSE_BAD_TAG; + } + + return ret; +} + +int wc_CoseEatPsaToken_Verify(const WOLFCOSE_KEY* key, + const uint8_t* in, size_t inSz, + const uint8_t* expectedNonce, size_t expectedNonceLen, + uint8_t* scratch, size_t scratchSz, + WOLFCOSE_EAT_PSA_TOKEN* token) +{ + int ret; + uint64_t tag = 0u; + const uint8_t* payload = NULL; + size_t payloadLen = 0u; + int32_t alg = WOLFCOSE_ALG_UNSET; + WOLFCOSE_HDR hdr = { 0 }; + + if ((key == NULL) || (in == NULL) || (expectedNonce == NULL) || + (scratch == NULL) || (token == NULL) || + ((expectedNonceLen != 32u) && (expectedNonceLen != 48u) && + (expectedNonceLen != 64u))) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + (void)XMEMSET(token, 0, sizeof(*token)); + ret = wolfCose_EatPsaGetEnvelope(in, inSz, &tag, &payload, + &payloadLen, &alg); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EatPsaCheckEnvelopeAlg(tag, alg); + } + if ((ret == WOLFCOSE_SUCCESS) && (tag == WOLFCOSE_TAG_SIGN1)) { +#if defined(WOLFCOSE_EAT_PSA_SIGN1) + ret = wolfCose_Sign1_Verify_ex(key, in, inSz, NULL, 0u, NULL, 0u, + scratch, scratchSz, &hdr, &payload, &payloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); +#else + ret = WOLFCOSE_E_UNSUPPORTED; +#endif + } + if ((ret == WOLFCOSE_SUCCESS) && (tag == WOLFCOSE_TAG_MAC0)) { +#if defined(WOLFCOSE_EAT_PSA_MAC0) + ret = wolfCose_Mac0_Verify_ex(key, in, inSz, NULL, 0u, NULL, 0u, + scratch, scratchSz, &hdr, &payload, &payloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); +#else + ret = WOLFCOSE_E_UNSUPPORTED; +#endif + } + if ((ret == WOLFCOSE_SUCCESS) && ((hdr.flags & WOLFCOSE_HDR_FLAG_DETACHED) != 0u)) { + ret = WOLFCOSE_E_DETACHED_PAYLOAD; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EatPsaDecodeClaims(payload, payloadLen, token); + } + if (ret == WOLFCOSE_SUCCESS) { + token->protection = (tag == WOLFCOSE_TAG_SIGN1) ? + WOLFCOSE_EAT_PSA_PROTECTION_SIGN1 : + WOLFCOSE_EAT_PSA_PROTECTION_MAC0; + ret = wolfCose_EatPsaCheckNonce(token, expectedNonce, expectedNonceLen); + } + if ((ret != WOLFCOSE_SUCCESS) && (token != NULL)) { + (void)XMEMSET(token, 0, sizeof(*token)); + } + + return ret; +} + +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) +int wc_CoseEatPsaToken_VerifyByUeid(WOLFCOSE_EAT_PSA_KEY_RESOLVER resolver, + void* resolverCtx, const uint8_t* in, size_t inSz, + const uint8_t* expectedNonce, size_t expectedNonceLen, + uint8_t* scratch, size_t scratchSz, + WOLFCOSE_EAT_PSA_TOKEN* token) +{ + int ret; + uint64_t tag = 0u; + const uint8_t* payload = NULL; + size_t payloadLen = 0u; + int32_t alg = WOLFCOSE_ALG_UNSET; + WOLFCOSE_EAT_PSA_TOKEN untrusted; + WOLFCOSE_KEY key; + + (void)XMEMSET(&key, 0, sizeof(key)); + + if ((resolver == NULL) || (in == NULL) || (expectedNonce == NULL) || + (scratch == NULL) || (token == NULL) || + ((expectedNonceLen != 32u) && (expectedNonceLen != 48u) && + (expectedNonceLen != 64u))) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + (void)XMEMSET(token, 0, sizeof(*token)); + ret = wolfCose_EatPsaGetEnvelope(in, inSz, &tag, &payload, &payloadLen, + &alg); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EatPsaCheckEnvelopeAlg(tag, alg); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EatPsaDecodeClaims(payload, payloadLen, &untrusted); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&key); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = resolver(resolverCtx, untrusted.profile, untrusted.ueid.data, + untrusted.ueid.len, alg, &key); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_Verify(&key, in, inSz, expectedNonce, + expectedNonceLen, scratch, scratchSz, token); + } + if ((ret != WOLFCOSE_SUCCESS) && (token != NULL)) { + (void)XMEMSET(token, 0, sizeof(*token)); + } + wc_CoseKey_Free(&key); + + return ret; +} +#endif /* WOLFCOSE_EAT_PSA_UEID_RESOLVER */ + +#if defined(WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR) +int wc_CoseEatPsaToken_ForEachComponent(const WOLFCOSE_EAT_PSA_TOKEN* token, + WOLFCOSE_EAT_PSA_COMPONENT_CB cb, void* cbCtx) +{ + int ret; + WOLFCOSE_CBOR_CTX ctx; + size_t count = 0u; + size_t i; + WOLFCOSE_EAT_PSA_COMPONENT component; + + if ((token == NULL) || (cb == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (token->noSoftwareMeasurements != 0u) { + ret = WOLFCOSE_SUCCESS; + } + else if (token->components.data == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = wc_CBOR_DecoderInit(&ctx, token->components.data, + token->components.len); + } + if ((ret == WOLFCOSE_SUCCESS) && + (token->noSoftwareMeasurements == 0u)) { + ret = WOLFCOSE_EAT_PSA_DECODE_ARRAY(&ctx, &count); + } + if ((ret == WOLFCOSE_SUCCESS) && + (token->noSoftwareMeasurements == 0u) && + (count != token->componentCount)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + ret = wolfCose_EatPsaDecodeComponent(&ctx, &component); + if (ret == WOLFCOSE_SUCCESS) { + ret = cb(cbCtx, &component); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + (token->noSoftwareMeasurements == 0u) && (ctx.idx != ctx.bufSz)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + + return ret; +} +#endif /* WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR */ + +#if defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) +int wc_CoseEatPsaToken_CreateSign1(WOLFCOSE_KEY* key, int32_t alg, + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* claimsBuf, size_t claimsBufSz, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, size_t* outLen, + WC_RNG* rng) +{ + int ret; + size_t claimsLen = 0u; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((key == NULL) || (claimsBuf == NULL) || (scratch == NULL) || + (out == NULL) || (outLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (wolfCose_EatPsaIssueBuffersOverlap(claimsBuf, claimsBufSz, + scratch, scratchSz, out, outSz) != 0) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (wolfCose_EatPsaSignAlg(alg) == 0) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else { + ret = wc_CoseEatPsaToken_EncodeClaims(claims, claimsBuf, claimsBufSz, + &claimsLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseSign1_Sign_ex(key, alg, NULL, 0u, claimsBuf, claimsLen, + NULL, 0u, NULL, 0u, scratch, scratchSz, out, outSz, outLen, rng, + 0u); + } + if ((ret != WOLFCOSE_SUCCESS) && (out != NULL)) { + (void)wolfCose_ForceZero(out, outSz); + } + + return ret; +} +#endif /* WOLFCOSE_EAT_PSA_SIGN1_ISSUE */ + +#if defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) +int wc_CoseEatPsaToken_CreateMac0(const WOLFCOSE_KEY* key, int32_t alg, + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* claimsBuf, size_t claimsBufSz, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, size_t* outLen) +{ + int ret; + size_t claimsLen = 0u; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((key == NULL) || (claimsBuf == NULL) || (scratch == NULL) || + (out == NULL) || (outLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (wolfCose_EatPsaIssueBuffersOverlap(claimsBuf, claimsBufSz, + scratch, scratchSz, out, outSz) != 0) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (wolfCose_EatPsaMacAlg(alg) == 0) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else { + ret = wc_CoseEatPsaToken_EncodeClaims(claims, claimsBuf, claimsBufSz, + &claimsLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseMac0_Create(key, alg, NULL, 0u, claimsBuf, claimsLen, + NULL, 0u, NULL, 0u, scratch, scratchSz, out, outSz, outLen); + } + if ((ret != WOLFCOSE_SUCCESS) && (out != NULL)) { + (void)wolfCose_ForceZero(out, outSz); + } + + return ret; +} +#endif /* WOLFCOSE_EAT_PSA_MAC0_ISSUE */ + +#endif /* WOLFCOSE_EAT_PSA */ diff --git a/src/wolfcose_internal.h b/src/wolfcose_internal.h index 6f97dc1..dc39ed8 100644 --- a/src/wolfcose_internal.h +++ b/src/wolfcose_internal.h @@ -122,6 +122,22 @@ static inline uint64_t wolfCose_LoadBE64(const uint8_t* buf) /* ----- Internal CBOR head encode/decode ----- */ +/* RFC 9783 Section 5.1's variation tolerance is a private PSA/EAT decoder + * option. Ordinary public CBOR and COSE APIs always use zero (strict mode). */ +#define WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED 0x0001u + +#if defined(WOLFCOSE_EAT_PSA) + #define WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) \ + (((flags) & ~WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED) == 0u) +#else + #define WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) ((flags) == 0u) +#endif + +#if defined(WOLFCOSE_EAT_PSA) + #define WOLFCOSE_HDR_X5CHAIN 33 + #define WOLFCOSE_HDR_FLAG_X5CHAIN 0x04u +#endif + /** * \brief Encode a CBOR initial byte + argument. * RFC 8949 Section 3.1: initial_byte = (majorType << 5) | additional_info @@ -130,11 +146,29 @@ WOLFCOSE_LOCAL int wolfCose_CBOR_EncodeHead(WOLFCOSE_CBOR_CTX* ctx, uint8_t majorType, uint64_t val); /** - * \brief Decode a CBOR initial byte + argument. Sets item fields. - * For bstr/tstr: item->data points into ctx->buf, item->dataLen set. + * \brief Decode a CBOR initial byte + argument with private profile options. + * For bstr/tstr: item->data points into ctx->cbuf, item->dataLen set. */ -WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeHead(WOLFCOSE_CBOR_CTX* ctx, - WOLFCOSE_CBOR_ITEM* item); +WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeHead_ex(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_CBOR_ITEM* item, uint32_t decodeFlags); +WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeUint_ex(WOLFCOSE_CBOR_CTX* ctx, + uint64_t* val, uint32_t decodeFlags); +WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeInt_ex(WOLFCOSE_CBOR_CTX* ctx, + int64_t* val, uint32_t decodeFlags); +WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeBstr_ex(WOLFCOSE_CBOR_CTX* ctx, + const uint8_t** data, size_t* dataLen, uint32_t decodeFlags); +WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeTstr_ex(WOLFCOSE_CBOR_CTX* ctx, + const uint8_t** str, size_t* strLen, uint32_t decodeFlags); +WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeArrayStart_ex(WOLFCOSE_CBOR_CTX* ctx, + size_t* count, uint32_t decodeFlags); +WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeMapStart_ex(WOLFCOSE_CBOR_CTX* ctx, + size_t* count, uint32_t decodeFlags); +WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeTag_ex(WOLFCOSE_CBOR_CTX* ctx, + uint64_t* tag, uint32_t decodeFlags); +WOLFCOSE_LOCAL int wolfCose_CBOR_Skip_ex(WOLFCOSE_CBOR_CTX* ctx, + uint32_t decodeFlags); +WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeLabel_ex(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_CBOR_LABEL* label, uint32_t decodeFlags); /* ----- RFC 9052 context-string byte arrays (see wolfcose.c) ----- */ WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_SIGNATURE1[10]; @@ -147,7 +181,10 @@ WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_ENCRYPT[7]; typedef struct WOLFCOSE_HDR_STATE { uint32_t labelBits; int64_t extraLabels[WOLFCOSE_MAX_MAP_ITEMS]; + const uint8_t* textLabels[WOLFCOSE_MAX_MAP_ITEMS]; + size_t textLabelLens[WOLFCOSE_MAX_MAP_ITEMS]; size_t extraCount; + size_t textCount; } WOLFCOSE_HDR_STATE; /* ----- COSE internal helpers ----- */ @@ -173,6 +210,12 @@ WOLFCOSE_LOCAL int wolfCose_DecodeProtectedHdr(const uint8_t* data, WOLFCOSE_HDR* hdr, WOLFCOSE_HDR_STATE* hdrState); +WOLFCOSE_LOCAL int wolfCose_DecodeProtectedHdr_ex(const uint8_t* data, + size_t dataLen, + WOLFCOSE_HDR* hdr, + WOLFCOSE_HDR_STATE* hdrState, + uint32_t decodeFlags); + /** * \brief Decode an unprotected header map from the decoder context. * \param ctx Decoder context positioned at the map. @@ -181,6 +224,30 @@ WOLFCOSE_LOCAL int wolfCose_DecodeProtectedHdr(const uint8_t* data, WOLFCOSE_LOCAL int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, WOLFCOSE_HDR_STATE* hdrState); +WOLFCOSE_LOCAL int wolfCose_DecodeUnprotectedHdr_ex(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_HDR* hdr, WOLFCOSE_HDR_STATE* hdrState, uint32_t decodeFlags); + +#if defined(WOLFCOSE_SIGN1_VERIFY) +WOLFCOSE_LOCAL int wolfCose_Sign1_Verify_ex(const WOLFCOSE_KEY* key, + const uint8_t* in, size_t inSz, + const uint8_t* detachedPayload, size_t detachedLen, + const uint8_t* extAad, size_t extAadLen, + uint8_t* scratch, size_t scratchSz, + WOLFCOSE_HDR* hdr, + const uint8_t** payload, size_t* payloadLen, + uint32_t flags); +#endif + +#if defined(WOLFCOSE_MAC0_VERIFY) +WOLFCOSE_LOCAL int wolfCose_Mac0_Verify_ex(const WOLFCOSE_KEY* key, + const uint8_t* in, size_t inSz, + const uint8_t* detachedPayload, size_t detachedLen, + const uint8_t* extAad, size_t extAadLen, + uint8_t* scratch, size_t scratchSz, + WOLFCOSE_HDR* hdr, + const uint8_t** payload, size_t* payloadLen, + uint32_t flags); +#endif /** * \brief Map COSE algorithm ID to wolfCrypt hash type. diff --git a/tests/config/eat_psa_config/config.h b/tests/config/eat_psa_config/config.h new file mode 100644 index 0000000..9b436d7 --- /dev/null +++ b/tests/config/eat_psa_config/config.h @@ -0,0 +1,9 @@ +/* Feature selection supplied through a configure-style config.h. */ +#ifndef WOLFCOSE_TEST_EAT_PSA_CONFIG_H +#define WOLFCOSE_TEST_EAT_PSA_CONFIG_H + +#define WOLFCOSE_ENABLE_EAT_PSA +#define WOLFCOSE_ENABLE_EAT_PSA_CURRENT +#define WOLFCOSE_ENABLE_EAT_PSA_SIGN1 + +#endif /* WOLFCOSE_TEST_EAT_PSA_CONFIG_H */ diff --git a/tests/config/eat_psa_curves/user_settings.h b/tests/config/eat_psa_curves/user_settings.h new file mode 100644 index 0000000..bf2acd0 --- /dev/null +++ b/tests/config/eat_psa_curves/user_settings.h @@ -0,0 +1,12 @@ +/* P-256-only wolfSSL configuration used by the PSA/EAT feature matrix. */ +#ifndef WOLFCOSE_TEST_EAT_PSA_CURVES_H +#define WOLFCOSE_TEST_EAT_PSA_CURVES_H + +#define HAVE_ECC +#define ECC_USER_CURVES +#define ECC_TIMING_RESISTANT +#define NO_RSA +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 + +#endif /* WOLFCOSE_TEST_EAT_PSA_CURVES_H */ diff --git a/tests/config/eat_psa_min_key/user_settings.h b/tests/config/eat_psa_min_key/user_settings.h new file mode 100644 index 0000000..e2063ec --- /dev/null +++ b/tests/config/eat_psa_min_key/user_settings.h @@ -0,0 +1,12 @@ +/* All RFC 9783 ECDSA curves for ECC_MIN_KEY_SZ boundary tests. */ +#ifndef WOLFCOSE_TEST_EAT_PSA_MIN_KEY_H +#define WOLFCOSE_TEST_EAT_PSA_MIN_KEY_H + +#define HAVE_ECC +#define HAVE_ALL_CURVES +#define ECC_TIMING_RESISTANT +#define NO_RSA +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 + +#endif /* WOLFCOSE_TEST_EAT_PSA_MIN_KEY_H */ diff --git a/tests/config/eat_psa_no_sha256/user_settings.h b/tests/config/eat_psa_no_sha256/user_settings.h new file mode 100644 index 0000000..9ff1ac5 --- /dev/null +++ b/tests/config/eat_psa_no_sha256/user_settings.h @@ -0,0 +1,18 @@ +/* SHA-384/512-capable wolfSSL configuration without SHA-256. */ +#ifndef WOLFCOSE_TEST_EAT_PSA_NO_SHA256_H +#define WOLFCOSE_TEST_EAT_PSA_NO_SHA256_H + +#define HAVE_ECC +#define HAVE_ALL_CURVES +#define ECC_TIMING_RESISTANT +#define NO_RSA +#define NO_SHA224 +#define NO_SHA256 +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 +/* This is a compile-only algorithm-derivation fixture. No RNG is needed, and + * wolfSSL 5.8.x otherwise instantiates its SHA-256 Hash-DRBG unconditionally. */ +#define WC_NO_HASHDRBG +#define WC_NO_RNG + +#endif /* WOLFCOSE_TEST_EAT_PSA_NO_SHA256_H */ diff --git a/tests/interop/t_cose/README.md b/tests/interop/t_cose/README.md index 21bf0b0..d36600c 100644 --- a/tests/interop/t_cose/README.md +++ b/tests/interop/t_cose/README.md @@ -14,6 +14,7 @@ performance or feature comparison. | COSE_Sign1 | ES256/384/512, PS256/384/512, EdDSA (Ed25519, Ed448) | | COSE_Mac0 | HMAC 256/384/512 | | COSE_Encrypt0 | AES-GCM 128/192/256 | +| RFC 9783 PSA/EAT | Current TF-M claims in Sign1 ES256 and Mac0 HMAC256 | Each primitive class also runs a negative case — a tampered signature, MAC tag, or AEAD tag — that wolfCOSE must reject. @@ -22,6 +23,12 @@ CBOR byte-for-byte equality is an explicit non-goal: CBOR permits multiple valid encodings, so the suite verifies that each side *reconstructs and validates* the other's output, never that the two producers emit identical bytes. +For the PSA/EAT rows, wolfCOSE builds the current profile claims and each side +authenticates the other's COSE envelope. On the t_cose to wolfCOSE direction, +wolfCOSE additionally performs the RFC 9783 claim and nonce checks. t_cose +does not implement a PSA/EAT profile parser, so its role is intentionally +limited to independent COSE wire validation. + ## Dependencies (not vendored) t_cose and QCBOR are BSD-3-Clause. They are **not** redistributed here; CI fetches diff --git a/tests/interop/t_cose/interop_tcose.c b/tests/interop/t_cose/interop_tcose.c index faec412..6096c5f 100644 --- a/tests/interop/t_cose/interop_tcose.c +++ b/tests/interop/t_cose/interop_tcose.c @@ -11,7 +11,7 @@ * t_cose and QCBOR are fetched at pinned SHAs by CI (BSD-3-Clause, not vendored). */ -#include +#include #include #include #include @@ -50,6 +50,40 @@ static int g_fail = 0; static const unsigned char g_payload[] = "wolfCOSE<->t_cose interop payload"; static const size_t g_payloadLen = sizeof(g_payload) - 1u; +/* A small, valid RFC 9783 current-profile payload. It is deliberately built + * by wolfCOSE and signed by both implementations below, so this test covers + * the profile claims as well as the COSE envelope wire format. */ +static const unsigned char g_psa_nonce[32] = { 0xA1 }; +static const unsigned char g_psa_ueid[33] = { 0x01, 0xA2 }; +static const unsigned char g_psa_implementation_id[32] = { 0xA3 }; +static const unsigned char g_psa_measurement[32] = { 0xA4 }; +static const unsigned char g_psa_signer_id[32] = { 0xA5 }; + +static void psa_claims(WOLFCOSE_EAT_PSA_CLAIMS* claims, + WOLFCOSE_EAT_PSA_COMPONENT* component) +{ + static const unsigned char type[] = "PRoT"; + + memset(claims, 0, sizeof(*claims)); + memset(component, 0, sizeof(*component)); + component->measurementType.data = type; + component->measurementType.len = sizeof(type) - 1u; + component->measurementValue.data = g_psa_measurement; + component->measurementValue.len = sizeof(g_psa_measurement); + component->signerId.data = g_psa_signer_id; + component->signerId.len = sizeof(g_psa_signer_id); + claims->nonce.data = g_psa_nonce; + claims->nonce.len = sizeof(g_psa_nonce); + claims->ueid.data = g_psa_ueid; + claims->ueid.len = sizeof(g_psa_ueid); + claims->implementationId.data = g_psa_implementation_id; + claims->implementationId.len = sizeof(g_psa_implementation_id); + claims->clientId = -1; + claims->lifecycle = 0x3000u; + claims->components = component; + claims->componentCount = 1u; +} + /* ---- wolfCrypt key set (owns the underlying wolfCrypt key for cleanup) ---- */ typedef struct { WOLFCOSE_KEY ck; @@ -273,6 +307,170 @@ static void mac0_case(const char* name, int32_t wc_alg, int32_t tc_alg, size_t k wc_CoseKey_Free(&ck); } +/* ---- RFC 9783 current-profile COSE_Sign1, both directions. ---- */ +static void psa_sign1_case(void) +{ + wc_keyset ks; + struct t_cose_key tk; + struct t_cose_sign1_sign_ctx sctx; + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_TOKEN token; + uint8_t claimsBuf[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t wbuf[1024]; + size_t claimsLen = 0u; + size_t wlen = 0u; + Q_USEFUL_BUF_MAKE_STACK_UB(tbuf, 1024); + struct q_useful_buf_c payload; + struct q_useful_buf_c tmsg = { NULL, 0 }; + enum t_cose_err_t terr; + WC_RNG rng; + int rc; + + printf(" [RFC 9783 PSA/EAT Sign1]\n"); + psa_claims(&claims, &component); + rc = wc_EatPsaToken_EncodeClaims(&claims, claimsBuf, sizeof(claimsBuf), + &claimsLen); + OK(rc == 0 && claimsLen > 0, "wolfCOSE encoded RFC 9783 claims"); + if (rc != 0) return; + payload.ptr = claimsBuf; + payload.len = claimsLen; + if (wc_load(&ks, IT_KEY_P256) != 0) { OK(0, "wolfCrypt key load"); return; } + tk = interop_tcose_load(IT_KEY_P256); + if (tk.key.ptr == NULL) { + OK(0, "t_cose key load"); + wc_free(&ks); + return; + } + if (wc_InitRng(&rng) != 0) { + OK(0, "rng"); + interop_tcose_free(tk); + wc_free(&ks); + return; + } + + /* wolfCOSE issues a PSA token, t_cose verifies the COSE envelope. */ + rc = wc_EatPsaToken_CreateSign1(&ks.ck, WOLFCOSE_ALG_ES256, &claims, + claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), wbuf, + sizeof(wbuf), &wlen, &rng); + OK(rc == 0 && wlen > 0, "wolfCOSE issued PSA COSE_Sign1 (w->t)"); + if (rc == 0) { + struct t_cose_sign1_verify_ctx vctx; + struct q_useful_buf_c sign1 = { wbuf, wlen }; + struct q_useful_buf_c verifiedPayload = { NULL, 0 }; + + t_cose_sign1_verify_init(&vctx, 0); + t_cose_sign1_set_verification_key(&vctx, tk); + terr = t_cose_sign1_verify(&vctx, sign1, &verifiedPayload, NULL); + OK(terr == T_COSE_SUCCESS, + "t_cose verified wolfCOSE PSA token (w->t)"); + OK(terr == T_COSE_SUCCESS && verifiedPayload.len == claimsLen && + memcmp(verifiedPayload.ptr, claimsBuf, claimsLen) == 0, + "PSA claim payload matches (w->t)"); + } + + /* t_cose issues the same claims, wolfCOSE verifies both layers. */ + t_cose_sign1_sign_init(&sctx, 0, T_COSE_ALGORITHM_ES256); + t_cose_sign1_set_signing_key(&sctx, tk, NULL_Q_USEFUL_BUF_C); + terr = t_cose_sign1_sign(&sctx, payload, tbuf, &tmsg); + OK(terr == T_COSE_SUCCESS && tmsg.len > 0, + "t_cose issued PSA COSE_Sign1 (t->w)"); + if (terr == T_COSE_SUCCESS) { + rc = wc_EatPsaToken_Verify(&ks.ck, tmsg.ptr, tmsg.len, g_psa_nonce, + sizeof(g_psa_nonce), scratch, sizeof(scratch), &token); + OK(rc == 0 && token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT && + token.protection == WOLFCOSE_EAT_PSA_PROTECTION_SIGN1, + "wolfCOSE verified t_cose PSA token (t->w)"); + } + + wc_FreeRng(&rng); + interop_tcose_free(tk); + wc_free(&ks); +} + +/* ---- RFC 9783 current-profile COSE_Mac0, both directions. ---- */ +static void psa_mac0_case(void) +{ + WOLFCOSE_KEY ck; + struct t_cose_key tk; + struct t_cose_mac_calculate_ctx cctx; + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_TOKEN token; + struct q_useful_buf_c keyb = { sym_key_64, 32 }; + struct q_useful_buf_c payload; + struct q_useful_buf_c tmsg = { NULL, 0 }; + uint8_t claimsBuf[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t wbuf[1024]; + size_t claimsLen = 0u; + size_t wlen = 0u; + Q_USEFUL_BUF_MAKE_STACK_UB(tbuf, 1024); + enum t_cose_err_t terr; + int rc; + + printf(" [RFC 9783 PSA/EAT Mac0]\n"); + psa_claims(&claims, &component); + rc = wc_EatPsaToken_EncodeClaims(&claims, claimsBuf, sizeof(claimsBuf), + &claimsLen); + OK(rc == 0 && claimsLen > 0, "wolfCOSE encoded RFC 9783 claims"); + if (rc != 0) return; + payload.ptr = claimsBuf; + payload.len = claimsLen; + if (wc_CoseKey_Init(&ck) != 0) { OK(0, "wolfCOSE key"); return; } + if (wc_CoseKey_SetSymmetric(&ck, sym_key_64, 32) != 0) { + OK(0, "wolfCOSE key"); + wc_CoseKey_Free(&ck); + return; + } + if (t_cose_key_init_symmetric(T_COSE_ALGORITHM_HMAC256, keyb, &tk) != + T_COSE_SUCCESS) { + OK(0, "t_cose key"); + wc_CoseKey_Free(&ck); + return; + } + + rc = wc_EatPsaToken_CreateMac0(&ck, WOLFCOSE_ALG_HMAC_256_256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), + wbuf, sizeof(wbuf), &wlen); + OK(rc == 0 && wlen > 0, "wolfCOSE issued PSA COSE_Mac0 (w->t)"); + if (rc == 0) { + struct t_cose_mac_validate_ctx vctx; + struct q_useful_buf_c msg = { wbuf, wlen }; + struct q_useful_buf_c verifiedPayload = { NULL, 0 }; + uint64_t tags[T_COSE_MAX_TAGS_TO_RETURN]; + + t_cose_mac_validate_init(&vctx, T_COSE_OPT_MESSAGE_TYPE_MAC0); + t_cose_mac_set_validate_key(&vctx, tk); + terr = t_cose_mac_validate_msg(&vctx, msg, NULL_Q_USEFUL_BUF_C, + &verifiedPayload, NULL, tags); + OK(terr == T_COSE_SUCCESS, + "t_cose validated wolfCOSE PSA token (w->t)"); + OK(terr == T_COSE_SUCCESS && verifiedPayload.len == claimsLen && + memcmp(verifiedPayload.ptr, claimsBuf, claimsLen) == 0, + "PSA claim payload matches (w->t)"); + } + + t_cose_mac_compute_init(&cctx, T_COSE_OPT_MESSAGE_TYPE_MAC0, + T_COSE_ALGORITHM_HMAC256); + t_cose_mac_set_computing_key(&cctx, tk, NULL_Q_USEFUL_BUF_C); + terr = t_cose_mac_compute(&cctx, NULL_Q_USEFUL_BUF_C, payload, tbuf, + &tmsg); + OK(terr == T_COSE_SUCCESS && tmsg.len > 0, + "t_cose issued PSA COSE_Mac0 (t->w)"); + if (terr == T_COSE_SUCCESS) { + rc = wc_EatPsaToken_Verify(&ck, tmsg.ptr, tmsg.len, g_psa_nonce, + sizeof(g_psa_nonce), scratch, sizeof(scratch), &token); + OK(rc == 0 && token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT && + token.protection == WOLFCOSE_EAT_PSA_PROTECTION_MAC0, + "wolfCOSE verified t_cose PSA token (t->w)"); + } + + t_cose_key_free_symmetric(tk); + wc_CoseKey_Free(&ck); +} + /* ---- COSE_Encrypt0 (AES-GCM) round-trip. IV travels in the message. ---- */ static void enc0_case(const char* name, int32_t wc_alg, int32_t tc_alg, size_t keyLen) { @@ -383,6 +581,9 @@ int main(void) mac0_case("HMAC384", WOLFCOSE_ALG_HMAC_384_384, T_COSE_ALGORITHM_HMAC384, 48); mac0_case("HMAC512", WOLFCOSE_ALG_HMAC_512_512, T_COSE_ALGORITHM_HMAC512, 64); + psa_sign1_case(); + psa_mac0_case(); + enc0_case("A128GCM", WOLFCOSE_ALG_A128GCM, T_COSE_ALGORITHM_A128GCM, 16); enc0_case("A192GCM", WOLFCOSE_ALG_A192GCM, T_COSE_ALGORITHM_A192GCM, 24); enc0_case("A256GCM", WOLFCOSE_ALG_A256GCM, T_COSE_ALGORITHM_A256GCM, 32); diff --git a/tests/test_cbor.c b/tests/test_cbor.c index 84edca9..584d809 100644 --- a/tests/test_cbor.c +++ b/tests/test_cbor.c @@ -37,6 +37,7 @@ #include #include +#include #include #include @@ -69,13 +70,13 @@ static int check_encode_hex(const uint8_t* buf, size_t len, static void test_cbor_encode_vectors(void) { uint8_t buf[64]; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; int ret; printf(" [Encode Vectors]\n"); /* 0 -> 0x00 */ - ctx.buf = buf; ctx.bufSz = sizeof(buf); ctx.idx = 0; + ctx.buf = buf; ctx.cbuf = NULL; ctx.bufSz = sizeof(buf); ctx.idx = 0; ret = wc_CBOR_EncodeUint(&ctx, 0); TEST_ASSERT(ret == 0 && ctx.idx == 1 && buf[0] == 0x00, "uint 0"); @@ -224,7 +225,7 @@ static void test_cbor_encode_vectors(void) /* ----- Decode known vectors ----- */ static void test_cbor_decode_vectors(void) { - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; int ret; uint64_t uval; int64_t ival; @@ -395,7 +396,7 @@ static void test_cbor_decode_vectors(void) static void test_cbor_roundtrip(void) { uint8_t buf[256]; - WOLFCOSE_CBOR_CTX enc, dec; + WOLFCOSE_CBOR_CTX enc = { 0 }, dec = { 0 }; int ret; uint64_t uval; int64_t ival; @@ -407,7 +408,7 @@ static void test_cbor_roundtrip(void) printf(" [Round-trip]\n"); /* Encode a complex structure: Tag(99) [42, -7, h'DEADBEEF', "hello", {}] */ - enc.buf = buf; enc.bufSz = sizeof(buf); enc.idx = 0; + enc.buf = buf; enc.cbuf = NULL; enc.bufSz = sizeof(buf); enc.idx = 0; ret = wc_CBOR_EncodeTag(&enc, 99); if (ret == 0) { ret = wc_CBOR_EncodeArrayStart(&enc, 5); @@ -462,7 +463,7 @@ static void test_cbor_roundtrip(void) static void test_cbor_nested(void) { uint8_t buf[128]; - WOLFCOSE_CBOR_CTX enc, dec; + WOLFCOSE_CBOR_CTX enc = { 0 }, dec = { 0 }; int ret; size_t count; uint64_t uval; @@ -472,7 +473,7 @@ static void test_cbor_nested(void) printf(" [Nested structures]\n"); /* {1: [10, 20], 2: "abc"} */ - enc.buf = buf; enc.bufSz = sizeof(buf); enc.idx = 0; + enc.buf = buf; enc.cbuf = NULL; enc.bufSz = sizeof(buf); enc.idx = 0; ret = wc_CBOR_EncodeMapStart(&enc, 2); if (ret == 0) { ret = wc_CBOR_EncodeUint(&enc, 1); @@ -522,7 +523,7 @@ static void test_cbor_nested(void) static void test_cbor_skip(void) { uint8_t buf[128]; - WOLFCOSE_CBOR_CTX enc, dec; + WOLFCOSE_CBOR_CTX enc = { 0 }, dec = { 0 }; int ret; uint64_t uval; @@ -530,7 +531,7 @@ static void test_cbor_skip(void) /* Encode: [42, {1: "foo", 2: [10, 20, 30]}, 99] * Skip the middle map, then read 99 */ - enc.buf = buf; enc.bufSz = sizeof(buf); enc.idx = 0; + enc.buf = buf; enc.cbuf = NULL; enc.bufSz = sizeof(buf); enc.idx = 0; ret = wc_CBOR_EncodeArrayStart(&enc, 3); if (ret == 0) { ret = wc_CBOR_EncodeUint(&enc, 42); @@ -607,7 +608,7 @@ static void test_cbor_skip_depth(void) * WOLFCOSE_CBOR_MAX_DEPTH (8). wc_CBOR_Skip must reject it instead * of overflowing its stack-local depth tracker. */ uint8_t deep[12]; - WOLFCOSE_CBOR_CTX dec; + WOLFCOSE_CBOR_CTX dec = { 0 }; size_t i; int ret; @@ -631,7 +632,7 @@ static void test_cbor_skip_tainted_count(void) * with value 0xFFFFFFFFFFFFFFFF to exercise both the * `val > bufSz` and `val > SIZE_MAX/2` sanity gates. */ uint8_t tainted[10]; - WOLFCOSE_CBOR_CTX dec; + WOLFCOSE_CBOR_CTX dec = { 0 }; int ret; printf(" [Skip: tainted item count]\n"); @@ -659,7 +660,7 @@ static void test_cbor_decode_simple_not_well_formed(void) /* RFC 8949 Section 3.3: two-byte 0xF8 simple value with arg < 32 is * not well-formed. */ uint8_t badSimple[2]; - WOLFCOSE_CBOR_CTX dec; + WOLFCOSE_CBOR_CTX dec = { 0 }; WOLFCOSE_CBOR_ITEM item; int ret; @@ -680,11 +681,11 @@ static void test_cbor_encode_bstr_null_with_len(void) /* EncodeBstr with NULL data and non-zero length must reject the * call instead of emitting uninitialised buffer contents. */ uint8_t out[32]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; int ret; printf(" [Encode: NULL bstr with non-zero length]\n"); - enc.buf = out; + enc.buf = out; enc.cbuf = NULL; enc.bufSz = sizeof(out); enc.idx = 0; ret = wc_CBOR_EncodeBstr(&enc, NULL, 4); @@ -697,11 +698,11 @@ static void test_cbor_encode_idx_past_bufsz(void) /* WOLFCOSE_CBOR_CTX is public; a corrupted idx near SIZE_MAX must not * wrap the capacity check and write out of bounds. */ uint8_t out[8]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; int ret; printf(" [Encode: idx past bufSz does not wrap]\n"); - enc.buf = out; + enc.buf = out; enc.cbuf = NULL; enc.bufSz = sizeof(out); enc.idx = SIZE_MAX; ret = wc_CBOR_EncodeUint(&enc, 1u); @@ -718,7 +719,7 @@ static void test_cbor_reject_non_preferred(void) { /* RFC 8949 4.2.1: arguments must use the shortest form. Overlong encodings * of small values must be rejected (F-5374). */ - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; uint64_t uval; const uint8_t* data; size_t dataLen; @@ -783,11 +784,201 @@ static void test_cbor_reject_non_preferred(void) TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "reject overlong array len"); } +static void test_cbor_public_decode_strictness(void) +{ + WOLFCOSE_CBOR_CTX ctx = { 0 }; + uint64_t value = 1u; + static const uint8_t nonPreferredZero[] = { 0x18u, 0x00u }; + int ret; + + printf(" [Public decoder strictness]\n"); + ret = wc_CBOR_DecoderInit(&ctx, nonPreferredZero, + sizeof(nonPreferredZero)); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "strict decoder initialises"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeUint(&ctx, &value); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "public decoder rejects non-preferred definite item"); + } +} + +static void test_cbor_utf8_boundaries(void) +{ + static const uint8_t valid2[] = { 0xC2u, 0x80u }; + static const uint8_t valid3E0[] = { 0xE0u, 0xA0u, 0x80u }; + static const uint8_t valid3Ordinary[] = { 0xE1u, 0x80u, 0x80u }; + static const uint8_t valid3Ed[] = { 0xEDu, 0x9Fu, 0xBFu }; + static const uint8_t valid3Ee[] = { 0xEEu, 0x80u, 0x80u }; + static const uint8_t valid4F0[] = { 0xF0u, 0x90u, 0x80u, 0x80u }; + static const uint8_t valid4Ordinary[] = { 0xF1u, 0x80u, 0x80u, 0x80u }; + static const uint8_t valid4F4[] = { 0xF4u, 0x8Fu, 0xBFu, 0xBFu }; + static const uint8_t truncated2[] = { 0xC2u }; + static const uint8_t badContinuation[] = { 0xC2u, 0x41u }; + static const uint8_t overlong2[] = { 0xC0u, 0x80u }; + static const uint8_t truncated3[] = { 0xE0u, 0xA0u }; + static const uint8_t overlong3[] = { 0xE0u, 0x80u, 0x80u }; + static const uint8_t surrogate[] = { 0xEDu, 0xA0u, 0x80u }; + static const uint8_t truncated4[] = { 0xF0u, 0x90u, 0x80u }; + static const uint8_t overlong4[] = { 0xF0u, 0x80u, 0x80u, 0x80u }; + static const uint8_t aboveUnicode[] = { 0xF4u, 0x90u, 0x80u, 0x80u }; + static const uint8_t invalidLead[] = { 0xF5u, 0x80u, 0x80u, 0x80u }; + static const struct { + const char* name; + const uint8_t* bytes; + size_t len; + int valid; + } cases[] = { + { "UTF-8 valid U+0080", valid2, sizeof(valid2), 1 }, + { "UTF-8 valid U+0800", valid3E0, sizeof(valid3E0), 1 }, + { "UTF-8 valid ordinary 3-byte", valid3Ordinary, + sizeof(valid3Ordinary), 1 }, + { "UTF-8 valid before surrogate range", valid3Ed, sizeof(valid3Ed), + 1 }, + { "UTF-8 valid after surrogate range", valid3Ee, sizeof(valid3Ee), + 1 }, + { "UTF-8 valid U+10000", valid4F0, sizeof(valid4F0), 1 }, + { "UTF-8 valid ordinary 4-byte", valid4Ordinary, + sizeof(valid4Ordinary), 1 }, + { "UTF-8 valid U+10FFFF", valid4F4, sizeof(valid4F4), 1 }, + { "UTF-8 truncated 2-byte", truncated2, sizeof(truncated2), 0 }, + { "UTF-8 invalid continuation", badContinuation, + sizeof(badContinuation), 0 }, + { "UTF-8 overlong 2-byte", overlong2, sizeof(overlong2), 0 }, + { "UTF-8 truncated 3-byte", truncated3, sizeof(truncated3), 0 }, + { "UTF-8 overlong 3-byte", overlong3, sizeof(overlong3), 0 }, + { "UTF-8 surrogate", surrogate, sizeof(surrogate), 0 }, + { "UTF-8 truncated 4-byte", truncated4, sizeof(truncated4), 0 }, + { "UTF-8 overlong 4-byte", overlong4, sizeof(overlong4), 0 }, + { "UTF-8 above U+10FFFF", aboveUnicode, sizeof(aboveUnicode), 0 }, + { "UTF-8 invalid leading byte", invalidLead, sizeof(invalidLead), 0 } + }; + WOLFCOSE_CBOR_CTX ctx; + uint8_t encoded[8]; + uint8_t input[8]; + const uint8_t* decoded = NULL; + size_t decodedLen = 0u; + size_t idx; + size_t i; + int ret; + + printf(" [UTF-8 boundaries]\n"); + for (i = 0u; i < (sizeof(cases) / sizeof(cases[0])); i++) { + ret = wc_CBOR_EncoderInit(&ctx, encoded, sizeof(encoded)); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, cases[i].bytes, cases[i].len); + } + if (cases[i].valid != 0) { + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + ctx.idx == (cases[i].len + 1u) && + encoded[0] == (uint8_t)(0x60u + cases[i].len) && + memcmp(&encoded[1], cases[i].bytes, cases[i].len) == 0, + cases[i].name); + } + else { + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, cases[i].name); + } + + input[0] = (uint8_t)(0x60u + cases[i].len); + (void)memcpy(&input[1], cases[i].bytes, cases[i].len); + ret = wc_CBOR_DecoderInit(&ctx, input, cases[i].len + 1u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeTstr(&ctx, &decoded, &decodedLen); + } + if (cases[i].valid != 0) { + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && decodedLen == cases[i].len && + memcmp(decoded, cases[i].bytes, cases[i].len) == 0, + cases[i].name); + } + else { + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, cases[i].name); + } + } + + ret = wc_CBOR_EncoderInit(&ctx, encoded, sizeof(encoded)); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, NULL, 1u); + } + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && ctx.idx == 0u, + "tstr NULL with nonzero length is invalid argument"); + + ret = wc_CBOR_EncoderInit(&ctx, encoded, sizeof(encoded)); + idx = ctx.idx; + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, invalidLead, sizeof(invalidLead)); + } + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED && ctx.idx == idx, + "tstr encoder validates UTF-8 before writing"); +} + +#ifdef WOLFCOSE_FLOAT +static void test_cbor_float_writers(void) +{ + static const uint8_t expectedFloat[] = { + 0xFAu, 0x3Fu, 0x80u, 0x00u, 0x00u + }; + static const uint8_t expectedDouble[] = { + 0xFBu, 0x3Fu, 0xF0u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u + }; + WOLFCOSE_CBOR_CTX ctx; + uint8_t buf[sizeof(expectedDouble)]; + uint8_t shortBuf[sizeof(expectedDouble) - 1u]; + int ret; + + printf(" [Float writers]\n"); + + ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeFloat(&ctx, 1.0f); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && ctx.idx == sizeof(expectedFloat) && + memcmp(buf, expectedFloat, sizeof(expectedFloat)) == 0, + "encode IEEE 754 single precision float"); + + ret = wc_CBOR_EncoderInit(&ctx, shortBuf, sizeof(expectedFloat) - 1u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeFloat(&ctx, 1.0f); + } + TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL && ctx.idx == 0u, + "reject float writer undersized output"); + + ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeDouble(&ctx, 1.0); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && ctx.idx == sizeof(expectedDouble) && + memcmp(buf, expectedDouble, sizeof(expectedDouble)) == 0, + "encode IEEE 754 double precision float"); + + ret = wc_CBOR_EncoderInit(&ctx, shortBuf, sizeof(shortBuf)); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeDouble(&ctx, 1.0); + } + TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL && ctx.idx == 0u, + "reject double writer undersized output"); + + ret = wc_CBOR_DecoderInit(&ctx, buf, sizeof(buf)); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeFloat(&ctx, 1.0f); + } + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "reject float writer on decoder context"); + + ret = wc_CBOR_DecoderInit(&ctx, buf, sizeof(buf)); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeDouble(&ctx, 1.0); + } + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "reject double writer on decoder context"); + +} +#endif /* WOLFCOSE_FLOAT */ + /* ----- Error cases ----- */ static void test_cbor_errors(void) { uint8_t buf[8]; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; int ret; uint64_t uval; const uint8_t* data; @@ -811,12 +1002,12 @@ static void test_cbor_errors(void) TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, "decode null val"); /* Buffer too small for encode */ - ctx.buf = buf; ctx.bufSz = 1; ctx.idx = 0; + ctx.buf = buf; ctx.cbuf = NULL; ctx.bufSz = 1; ctx.idx = 0; ret = wc_CBOR_EncodeUint(&ctx, 1000); /* needs 3 bytes */ TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL, "encode overflow"); /* Buffer too small for bstr data */ - ctx.buf = buf; ctx.bufSz = 3; ctx.idx = 0; + ctx.buf = buf; ctx.cbuf = NULL; ctx.bufSz = 3; ctx.idx = 0; { const uint8_t d[] = {1, 2, 3, 4}; ret = wc_CBOR_EncodeBstr(&ctx, d, 4); /* head=1 + data=4 > 3 */ TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL, "encode bstr overflow"); } @@ -845,6 +1036,26 @@ static void test_cbor_errors(void) ret = wc_CBOR_DecodeTstr(&ctx, &data, &dataLen); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_TYPE, "decode wrong type tstr"); } + /* RFC 8949 text strings must contain valid UTF-8, including when the + * caller reaches them via a profile parser or Skip(). */ + { uint8_t in[] = {0x61u, 0xFFu}; + ctx.cbuf = in; ctx.bufSz = sizeof(in); ctx.idx = 0; + ret = wc_CBOR_DecodeTstr(&ctx, &data, &dataLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "decode rejects invalid UTF-8 text"); + ctx.idx = 0u; + ret = wc_CBOR_DecodeHead(&ctx, &item); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "decode head rejects invalid UTF-8 text"); + ctx.idx = 0u; + ret = wc_CBOR_Skip(&ctx); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "skip rejects invalid UTF-8 text"); + ctx.buf = buf; ctx.cbuf = NULL; ctx.bufSz = sizeof(buf); ctx.idx = 0u; + ret = wc_CBOR_EncodeTstr(&ctx, &in[1], 1u); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "encode rejects invalid UTF-8 text"); } + /* Wrong type: expect array, get uint */ { uint8_t in[] = {0x00}; ctx.cbuf = in; ctx.bufSz = 1; ctx.idx = 0; @@ -905,7 +1116,7 @@ static void test_cbor_errors(void) static void test_cbor_negative_map_keys(void) { uint8_t buf[64]; - WOLFCOSE_CBOR_CTX enc, dec; + WOLFCOSE_CBOR_CTX enc = { 0 }, dec = { 0 }; int ret; int64_t key; uint64_t val; @@ -913,7 +1124,7 @@ static void test_cbor_negative_map_keys(void) printf(" [Negative map keys]\n"); /* {1: 2, -1: 1, -2: h'AA'} -- COSE Key style */ - enc.buf = buf; enc.bufSz = sizeof(buf); enc.idx = 0; + enc.buf = buf; enc.cbuf = NULL; enc.bufSz = sizeof(buf); enc.idx = 0; ret = wc_CBOR_EncodeMapStart(&enc, 3); if (ret == 0) { ret = wc_CBOR_EncodeUint(&enc, 1); @@ -973,7 +1184,7 @@ static void test_cbor_boundary_roundtrip(void) {-4294967296LL, 5u}, {-4294967297LL, 9u}, {INT64_MIN, 9u} }; uint8_t buf[16]; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; uint64_t uval; int64_t ival; int ret; @@ -982,7 +1193,7 @@ static void test_cbor_boundary_roundtrip(void) printf(" [Boundary Round-trip]\n"); for (i = 0; i < (sizeof(uvec) / sizeof(uvec[0])); i++) { - ctx.buf = buf; ctx.bufSz = sizeof(buf); ctx.idx = 0; + ctx.buf = buf; ctx.cbuf = NULL; ctx.bufSz = sizeof(buf); ctx.idx = 0; ret = wc_CBOR_EncodeUint(&ctx, uvec[i].val); TEST_ASSERT(ret == 0 && ctx.idx == uvec[i].len, "uint boundary encode len"); @@ -993,7 +1204,7 @@ static void test_cbor_boundary_roundtrip(void) } for (i = 0; i < (sizeof(ivec) / sizeof(ivec[0])); i++) { - ctx.buf = buf; ctx.bufSz = sizeof(buf); ctx.idx = 0; + ctx.buf = buf; ctx.cbuf = NULL; ctx.bufSz = sizeof(buf); ctx.idx = 0; ret = wc_CBOR_EncodeInt(&ctx, ivec[i].val); TEST_ASSERT(ret == 0 && ctx.idx == ivec[i].len, "negint boundary encode len"); @@ -1006,7 +1217,7 @@ static void test_cbor_boundary_roundtrip(void) static void test_cbor_peektype_bounds(void) { - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; uint8_t buf[1] = {0x40}; printf(" [PeekType bounds]\n"); @@ -1024,32 +1235,56 @@ static void test_cbor_peektype_bounds(void) static void test_cbor_ctx_init(void) { - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; uint8_t buf[16]; uint64_t uval = 0; int ret; printf(" [Context initialisers]\n"); - /* Poison every field so a partial init is visible. */ - ctx.buf = (uint8_t*)0x1; - ctx.cbuf = (const uint8_t*)0x1; + /* Preserve the committed four-field public ABI. */ + TEST_ASSERT(offsetof(WOLFCOSE_CBOR_CTX, buf) == 0u && + offsetof(WOLFCOSE_CBOR_CTX, cbuf) == sizeof(uint8_t*) && + offsetof(WOLFCOSE_CBOR_CTX, bufSz) == + (2u * sizeof(uint8_t*)) && + offsetof(WOLFCOSE_CBOR_CTX, idx) == + ((2u * sizeof(uint8_t*)) + sizeof(size_t)) && + sizeof(WOLFCOSE_CBOR_CTX) == + ((2u * sizeof(uint8_t*)) + (2u * sizeof(size_t))), + "CBOR context retains committed ABI layout"); + + /* Poison every public field so a partial init is visible. */ + ctx.buf = (uint8_t*)0x1; ctx.cbuf = NULL; + ctx.cbuf = (const uint8_t*)0x2; ctx.bufSz = 0xDEAD; ctx.idx = 0xBEEF; ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); TEST_ASSERT(ret == 0 && ctx.buf == buf && ctx.cbuf == NULL && ctx.bufSz == sizeof(buf) && ctx.idx == 0, - "encoder init sets encode side only"); + "encoder initializer resets context"); ret = wc_CBOR_EncodeUint(&ctx, 1000); TEST_ASSERT(ret == 0 && ctx.idx == 3, "encoder init usable"); ret = wc_CBOR_DecoderInit(&ctx, buf, ctx.idx); - TEST_ASSERT(ret == 0 && ctx.cbuf == buf && ctx.buf == NULL && + TEST_ASSERT(ret == 0 && ctx.buf == NULL && ctx.cbuf == buf && ctx.bufSz == 3 && ctx.idx == 0, - "decoder init sets decode side only"); + "decoder initializer resets context"); ret = wc_CBOR_DecodeUint(&ctx, &uval); TEST_ASSERT(ret == 0 && uval == 1000, "decoder init usable"); + ret = wc_CBOR_EncodeUint(&ctx, 1u); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "encoder rejects decoder context"); + + /* Preserve source compatibility with callers that manually initialize + * the established encoder fields and leave stale decode state behind. */ + ctx.buf = buf; + ctx.cbuf = &buf[1]; + ctx.bufSz = sizeof(buf); + ctx.idx = 0u; + ret = wc_CBOR_EncodeUint(&ctx, 23u); + TEST_ASSERT(ret == 0 && ctx.idx == 1u && buf[0] == 0x17u, + "legacy manual encoder context remains usable"); TEST_ASSERT(wc_CBOR_EncoderInit(NULL, buf, sizeof(buf)) == WOLFCOSE_E_INVALID_ARG, "encoder init null ctx"); @@ -1063,7 +1298,7 @@ static void test_cbor_ctx_init(void) static void test_cbor_skip_item(void) { - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; uint8_t buf[64]; const uint8_t* item = NULL; size_t itemLen = 0; @@ -1112,7 +1347,7 @@ static void test_cbor_skip_item(void) /* The captured bytes must parse standalone. */ /* empty-brace-scan: allow - test-local temporary scope */ { - WOLFCOSE_CBOR_CTX sub; + WOLFCOSE_CBOR_CTX sub = { 0 }; size_t subCount = 0; (void)wc_CBOR_DecoderInit(&sub, item, itemLen); @@ -1141,11 +1376,26 @@ static void test_cbor_skip_item(void) TEST_ASSERT(ret != 0 && item == NULL && itemLen == 0, "skipitem truncated input fails"); } + + /* Invalid UTF-8 is rejected without publishing a partial capture. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + static const uint8_t invalidUtf8[] = {0x61u, 0xFFu}; + const uint8_t* sentinel = &invalidUtf8[1]; + + item = sentinel; + itemLen = 17u; + (void)wc_CBOR_DecoderInit(&ctx, invalidUtf8, sizeof(invalidUtf8)); + ret = wc_CBOR_SkipItem(&ctx, &item, &itemLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED && + item == sentinel && itemLen == 17u, + "skipitem rejects invalid UTF-8 without output"); + } } static void test_cbor_decode_label(void) { - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; WOLFCOSE_CBOR_LABEL label; uint8_t buf[64]; static const uint8_t algText[] = "alg"; @@ -1255,6 +1505,18 @@ static void test_cbor_decode_label(void) TEST_ASSERT(ret == WOLFCOSE_E_CBOR_TYPE, "bstr rejected as label"); } + /* A malformed text label is rejected before a label is published. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + static const uint8_t invalidUtf8[] = {0x61u, 0xFFu}; + + (void)memset(&label, 0xA5, sizeof(label)); + (void)wc_CBOR_DecoderInit(&ctx, invalidUtf8, sizeof(invalidUtf8)); + ret = wc_CBOR_DecodeLabel(&ctx, &label); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "label rejects invalid UTF-8 text"); + } + TEST_ASSERT(wc_CBOR_DecodeLabel(NULL, &label) == WOLFCOSE_E_INVALID_ARG, "label null ctx"); TEST_ASSERT(wc_CBOR_DecodeLabel(&ctx, NULL) == WOLFCOSE_E_INVALID_ARG, @@ -1284,6 +1546,11 @@ int test_cbor(void) test_cbor_encode_bstr_null_with_len(); test_cbor_encode_idx_past_bufsz(); test_cbor_reject_non_preferred(); + test_cbor_public_decode_strictness(); + test_cbor_utf8_boundaries(); +#ifdef WOLFCOSE_FLOAT + test_cbor_float_writers(); +#endif test_cbor_errors(); test_cbor_negative_map_keys(); diff --git a/tests/test_cose.c b/tests/test_cose.c index c1c3fd9..e38e851 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -5012,7 +5012,7 @@ static void test_cose_key_ed25519_public_only(void) { /* Build a minimal OKP key with only x (public) */ uint8_t pubBuf[256]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; uint8_t xBuf[32]; word32 xSz = sizeof(xBuf); ed25519_key edKey3; @@ -5020,7 +5020,7 @@ static void test_cose_key_ed25519_public_only(void) wc_ed25519_init(&edKey3); wc_ed25519_export_public(&edKey, xBuf, &xSz); - enc.buf = pubBuf; enc.bufSz = sizeof(pubBuf); enc.idx = 0; + enc.buf = pubBuf; enc.cbuf = NULL; enc.bufSz = sizeof(pubBuf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 3); wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_OKP); @@ -5053,7 +5053,7 @@ static void test_cose_key_ed25519_public_only(void) static size_t typeconf_key_blob(uint8_t* out, size_t outSz, int64_t kty, int64_t crv, size_t partLen) { - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; uint8_t part[64]; if (partLen > sizeof(part)) { @@ -5061,7 +5061,7 @@ static size_t typeconf_key_blob(uint8_t* out, size_t outSz, int64_t kty, } (void)XMEMSET(part, 0x41, sizeof(part)); - enc.buf = out; + enc.buf = out; enc.cbuf = NULL; enc.bufSz = outSz; enc.idx = 0; @@ -5097,7 +5097,7 @@ static void test_cose_key_decode_type_confusion(void) wc_MlDsaKey dlKey; #endif static const uint8_t symmData[32] = { 0x5au }; - WOLFCOSE_CBOR_CTX symmEnc; + WOLFCOSE_CBOR_CTX symmEnc = { 0 }; uint8_t blob[256]; size_t blobLen; int ret; @@ -5267,7 +5267,7 @@ static void test_cose_key_decode_type_confusion(void) /* Matching symmetric decode through the setter still imports. RFC 9053 * carries the symmetric value in k (label -1), not in x/d. */ - symmEnc.buf = blob; + symmEnc.buf = blob; symmEnc.cbuf = NULL; symmEnc.bufSz = sizeof(blob); symmEnc.idx = 0; (void)wc_CBOR_EncodeMapStart(&symmEnc, 2); @@ -5311,7 +5311,7 @@ static void test_cose_key_ed448_public_only(void) ed448_key edKey, edKey2; WC_RNG rng; uint8_t pubBuf[256]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; uint8_t xBuf[57]; word32 xSz = sizeof(xBuf); int ret; @@ -5325,7 +5325,7 @@ static void test_cose_key_ed448_public_only(void) wc_ed448_export_public(&edKey, xBuf, &xSz); /* Build a public-only OKP key (no d label) */ - enc.buf = pubBuf; enc.bufSz = sizeof(pubBuf); enc.idx = 0; + enc.buf = pubBuf; enc.cbuf = NULL; enc.bufSz = sizeof(pubBuf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 3); wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_OKP); @@ -5365,8 +5365,8 @@ static void test_cose_key_decode_private_only(void) word32 ySz = sizeof(yBuf); uint8_t keyBuf[128]; uint8_t fullKeyBuf[256]; - WOLFCOSE_CBOR_CTX enc; - WOLFCOSE_CBOR_CTX fullEnc; + WOLFCOSE_CBOR_CTX enc = { 0 }; + WOLFCOSE_CBOR_CTX fullEnc = { 0 }; #ifndef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED uint8_t hash[32]; uint8_t sig[80]; @@ -5399,7 +5399,7 @@ static void test_cose_key_decode_private_only(void) "ec private import export public"); /* Build {kty: EC2, crv: P-256, d: <32>} with no x/y. */ - enc.buf = keyBuf; enc.bufSz = sizeof(keyBuf); enc.idx = 0; + enc.buf = keyBuf; enc.cbuf = NULL; enc.bufSz = sizeof(keyBuf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 3); wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_EC2); @@ -5796,7 +5796,7 @@ static void test_cose_key_decode_private_only(void) uint8_t dBuf[ED25519_KEY_SIZE]; word32 dSz = sizeof(dBuf); uint8_t keyBuf[128]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; uint8_t msg[16]; uint8_t sig[ED25519_SIG_SIZE]; word32 sigLen = sizeof(sig); @@ -5820,7 +5820,7 @@ static void test_cose_key_decode_private_only(void) TEST_ASSERT(ret == 0 && dSz == sizeof(dBuf), "ed priv-only export d"); /* Build {kty: OKP, crv: Ed25519, d: <32>} with no x. */ - enc.buf = keyBuf; enc.bufSz = sizeof(keyBuf); enc.idx = 0; + enc.buf = keyBuf; enc.cbuf = NULL; enc.bufSz = sizeof(keyBuf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 3); wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_OKP); @@ -5858,7 +5858,7 @@ static void test_cose_key_decode_private_only(void) uint8_t dBuf[ED448_KEY_SIZE]; word32 dSz = sizeof(dBuf); uint8_t keyBuf[160]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; uint8_t msg[16]; uint8_t sig[ED448_SIG_SIZE]; word32 sigLen = sizeof(sig); @@ -5882,7 +5882,7 @@ static void test_cose_key_decode_private_only(void) TEST_ASSERT(ret == 0 && dSz == sizeof(dBuf), "ed448 priv-only export d"); /* Build {kty: OKP, crv: Ed448, d: <57>} with no x. */ - enc.buf = keyBuf; enc.bufSz = sizeof(keyBuf); enc.idx = 0; + enc.buf = keyBuf; enc.cbuf = NULL; enc.bufSz = sizeof(keyBuf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 3); wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_OKP); @@ -5926,7 +5926,7 @@ static void test_cose_key_rsa_public_decode(void) word32 nSz = sizeof(nBuf); word32 eSz = sizeof(eBuf); uint8_t keyBuf[400]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; int ret; TEST_LOG(" [Key RSA public-only decode]\n"); @@ -5943,7 +5943,7 @@ static void test_cose_key_rsa_public_decode(void) TEST_ASSERT(ret == 0, "rsa pub flatten"); /* Build {kty: RSA, -1: n, -2: e} with no private components. */ - enc.buf = keyBuf; enc.bufSz = sizeof(keyBuf); enc.idx = 0; + enc.buf = keyBuf; enc.cbuf = NULL; enc.bufSz = sizeof(keyBuf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 3); wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_RSA); @@ -5972,7 +5972,7 @@ static void test_cose_key_mldsa_public_only(void) wc_MlDsaKey dlKey, dlKey2; WC_RNG rng; uint8_t pubBuf[2048]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; uint8_t xBuf[1312]; /* ML-DSA-44 pub key size */ word32 xSz = sizeof(xBuf); int ret; @@ -5987,7 +5987,7 @@ static void test_cose_key_mldsa_public_only(void) wc_MlDsaKey_ExportPubRaw(&dlKey, xBuf, &xSz); /* Build a public-only AKP key (RFC 9964): kty=AKP, required alg, pub(-1) */ - enc.buf = pubBuf; enc.bufSz = sizeof(pubBuf); enc.idx = 0; + enc.buf = pubBuf; enc.cbuf = NULL; enc.bufSz = sizeof(pubBuf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 3); wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_AKP); @@ -6051,7 +6051,7 @@ static void test_cose_key_mldsa_negative(void) uint8_t buf[2048]; uint8_t outBuf[8192]; size_t outLen; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; int ret; #if !defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) && \ !defined(WOLFSSL_MLDSA_VERIFY_ONLY) @@ -6100,7 +6100,7 @@ static void test_cose_key_mldsa_negative(void) "dl public-only decode has no private"); /* Decode: AKP private key with no pub is rejected. */ - enc.buf = buf; enc.bufSz = sizeof(buf); enc.idx = 0; + enc.buf = buf; enc.cbuf = NULL; enc.bufSz = sizeof(buf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 3); wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_AKP); @@ -6280,7 +6280,7 @@ static void test_cose_key_ecc_public_only(void) ecc_key eccKey, eccKey2; WC_RNG rng; uint8_t pubBuf[256]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; uint8_t xBuf[32], yBuf[32]; word32 xLen = sizeof(xBuf), yLen = sizeof(yBuf); int ret; @@ -6294,7 +6294,7 @@ static void test_cose_key_ecc_public_only(void) wc_ecc_export_public_raw(&eccKey, xBuf, &xLen, yBuf, &yLen); /* Build a public-only EC2 key (no d label) */ - enc.buf = pubBuf; enc.bufSz = sizeof(pubBuf); enc.idx = 0; + enc.buf = pubBuf; enc.cbuf = NULL; enc.bufSz = sizeof(pubBuf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 4); wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_EC2); @@ -6322,7 +6322,7 @@ static void test_cose_key_decode_optional_labels(void) { WOLFCOSE_KEY key; uint8_t buf[128]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; const uint8_t kidVal[] = "sensor-01"; const uint8_t symmKey[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; int ret; @@ -6330,7 +6330,7 @@ static void test_cose_key_decode_optional_labels(void) TEST_LOG(" [Key Decode Optional Labels]\n"); /* Build a symmetric key with kid(2), alg(3), and an unknown label(99) */ - enc.buf = buf; enc.bufSz = sizeof(buf); enc.idx = 0; + enc.buf = buf; enc.cbuf = NULL; enc.bufSz = sizeof(buf); enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 5); /* kty = 4 (Symmetric) */ @@ -7252,7 +7252,7 @@ static void test_cose_key_encode_rsa_short_d(void) { RsaKey rsaKey; WOLFCOSE_KEY key; - WOLFCOSE_CBOR_CTX dec; + WOLFCOSE_CBOR_CTX dec = { 0 }; uint8_t out[2048]; const uint8_t* dBytes = NULL; size_t outLen = 0; @@ -7405,7 +7405,7 @@ static void test_cose_key_peek_info(void) /* Unknown labels are skipped, not fatal. */ /* empty-brace-scan: allow - test-local temporary scope */ { - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; (void)wc_CBOR_EncoderInit(&enc, buf, sizeof(buf)); (void)wc_CBOR_EncodeMapStart(&enc, 3); @@ -7427,7 +7427,7 @@ static void test_cose_key_peek_info(void) /* Error cases */ /* empty-brace-scan: allow - test-local temporary scope */ { - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; static const uint8_t notAMap[] = {0x01}; TEST_ASSERT(wc_CoseKey_PeekInfo(NULL, 4, &info) == @@ -7473,7 +7473,67 @@ static void test_cose_key_peek_info(void) (void)wc_CBOR_EncodeTstr(&enc, (const uint8_t*)"kty", 3); (void)wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_EC2); TEST_ASSERT(wc_CoseKey_PeekInfo(buf, enc.idx, &info) == - WOLFCOSE_E_CBOR_MALFORMED, "peek text label rejected"); + WOLFCOSE_E_COSE_BAD_HDR, + "peek does not treat text label as numeric kty"); + + /* Unknown text labels are valid COSE_Key extensions. */ + (void)wc_CBOR_EncoderInit(&enc, buf, sizeof(buf)); + (void)wc_CBOR_EncodeMapStart(&enc, 2); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); + (void)wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_EC2); + (void)wc_CBOR_EncodeTstr(&enc, (const uint8_t*)"vendor", 6); + (void)wc_CBOR_EncodeUint(&enc, 0u); + ret = wc_CoseKey_PeekInfo(buf, enc.idx, &info); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && info.kty == WOLFCOSE_KTY_EC2, + "peek skips unknown text-label key extension"); + + /* Exercise a complete key through both public parsers. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY key; + static const uint8_t symBytes[16] = {0}; + + (void)wc_CBOR_EncoderInit(&enc, buf, sizeof(buf)); + (void)wc_CBOR_EncodeMapStart(&enc, 3); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); + (void)wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_SYMMETRIC); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_K); + (void)wc_CBOR_EncodeBstr(&enc, symBytes, sizeof(symBytes)); + (void)wc_CBOR_EncodeTstr(&enc, (const uint8_t*)"vendor", 6); + (void)wc_CBOR_EncodeUint(&enc, 0u); + + ret = wc_CoseKey_PeekInfo(buf, enc.idx, &info); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + info.kty == WOLFCOSE_KTY_SYMMETRIC, + "peek accepts complete key with text extension"); + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + key.kty == WOLFCOSE_KTY_SYMMETRIC && + key.key.symm.keyLen == sizeof(symBytes), + "decode accepts complete key with text extension"); + wc_CoseKey_Free(&key); + + (void)wc_CBOR_EncoderInit(&enc, buf, sizeof(buf)); + (void)wc_CBOR_EncodeMapStart(&enc, 4); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); + (void)wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_SYMMETRIC); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_K); + (void)wc_CBOR_EncodeBstr(&enc, symBytes, sizeof(symBytes)); + (void)wc_CBOR_EncodeTstr(&enc, (const uint8_t*)"vendor", 6); + (void)wc_CBOR_EncodeUint(&enc, 0u); + (void)wc_CBOR_EncodeTstr(&enc, (const uint8_t*)"vendor", 6); + (void)wc_CBOR_EncodeUint(&enc, 1u); + + ret = wc_CoseKey_PeekInfo(buf, enc.idx, &info); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "peek rejects duplicate text extension"); + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "decode rejects duplicate text extension"); + wc_CoseKey_Free(&key); + } } } @@ -7483,7 +7543,7 @@ static void test_cose_key_peek_info(void) static void test_cose_key_peek_info_alg(void) { WOLFCOSE_KEY_INFO info; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; uint8_t buf[64]; int ret; @@ -8287,7 +8347,7 @@ static void test_cose_mac0_aes_cbc_mac_detached(void) static int mac0_tag_len(const uint8_t* msg, size_t msgLen, size_t* tagLen) { - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; const uint8_t* p; size_t n; uint64_t t; @@ -9262,7 +9322,7 @@ static int mutate_first_recipient_protected_alg(uint8_t* msg, size_t msgLen, uint8_t algByte) { int ret = -1; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; uint64_t tagVal = 0; size_t count = 0; const uint8_t* protectedData = NULL; @@ -10081,6 +10141,48 @@ static void test_cose_secret_zeroize(void) #endif /* ZEROIZE_HOOK && ECDH_ES_DIRECT && ES256 && HKDF */ #if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(WOLFCOSE_HAVE_ES256) && defined(HAVE_HKDF) +static int test_cose_add_vendor_entries(const uint8_t* in, size_t inLen, + size_t mapPos, size_t entryCount, uint8_t* out, size_t outSz, + size_t* outLen) +{ + static const uint8_t entry[] = { + 0x66u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u + }; + size_t insertLen; + size_t i; + int ret = WOLFCOSE_SUCCESS; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((in == NULL) || (out == NULL) || (outLen == NULL) || + (mapPos >= inLen) || (entryCount == 0u) || (entryCount > 2u)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (((in[mapPos] & 0xE0u) != 0xA0u) || + ((in[mapPos] & 0x1Fu) > (23u - entryCount))) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else if ((outSz < inLen) || + (entryCount > ((outSz - inLen) / sizeof(entry)))) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + else { + insertLen = entryCount * sizeof(entry); + (void)memcpy(out, in, mapPos); + out[mapPos] = (uint8_t)(in[mapPos] + (uint8_t)entryCount); + for (i = 0u; i < entryCount; i++) { + (void)memcpy(&out[mapPos + 1u + (i * sizeof(entry))], entry, + sizeof(entry)); + } + (void)memcpy(&out[mapPos + 1u + insertLen], &in[mapPos + 1u], + inLen - mapPos - 1u); + *outLen = inLen + insertLen; + } + + return ret; +} + /** * Test ECDH-ES (Ephemeral-Static) encryption and decryption. * - Encrypt with recipient's EC public key @@ -10281,6 +10383,122 @@ static void test_cose_encrypt_ecdh_es_ephemeral_crv_narrowing(void) (void)wc_FreeRng(&rng); } +static void test_cose_encrypt_ecdh_es_text_extensions(void) +{ + WOLFCOSE_KEY recipientKey; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + ecc_key recipientEcc; + WC_RNG rng; + uint8_t out[1024]; + uint8_t modified[1056]; + uint8_t scratch[1024]; + uint8_t plaintext[128]; + uint8_t iv[12]; + const uint8_t payload[] = "ECDH-ES text extensions"; + size_t outLen = 0u; + size_t modifiedLen = 0u; + size_t plaintextLen = 0u; + size_t recipientMapPos = 0u; + size_t ephemMapPos = 0u; + size_t i; + int found = 0; + int ret; + static const uint8_t anchor[] = { + 0xA1u, 0x20u, 0xA4u, 0x01u, 0x02u, 0x20u, 0x01u, 0x21u + }; + + TEST_LOG(" [Encrypt ECDH-ES text extensions]\n"); + + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "ecdh text rng"); + ret = wc_ecc_init(&recipientEcc); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "ecdh text ecc init"); + ret = wc_ecc_make_key(&rng, 32, &recipientEcc); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "ecdh text keygen"); + + (void)wc_CoseKey_Init(&recipientKey); + ret = wc_CoseKey_SetEcc(&recipientKey, WOLFCOSE_CRV_P256, &recipientEcc); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "ecdh text set key"); + recipientKey.hasPrivate = 0u; + recipient.algId = WOLFCOSE_ALG_ECDH_ES_HKDF_256; + recipient.key = &recipientKey; + recipient.kid = NULL; + recipient.kidLen = 0u; + + ret = wc_RNG_GenerateBlock(&rng, iv, sizeof(iv)); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "ecdh text iv"); + ret = wc_CoseEncrypt_Encrypt(&recipient, 1u, WOLFCOSE_ALG_A128GCM, + iv, sizeof(iv), payload, sizeof(payload) - 1u, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &outLen, &rng); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "ecdh text encrypt"); + + for (i = 0u; (found == 0) && (outLen >= sizeof(anchor)) && + (i <= (outLen - sizeof(anchor))); i++) { + if (memcmp(&out[i], anchor, sizeof(anchor)) == 0) { + recipientMapPos = i; + ephemMapPos = i + 2u; + found = 1; + } + } + TEST_ASSERT(found == 1, "ecdh text maps located"); + recipientKey.hasPrivate = 1u; + + if (found == 1) { + ret = test_cose_add_vendor_entries(out, outLen, recipientMapPos, 1u, + modified, sizeof(modified), &modifiedLen); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, modified, + modifiedLen, NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, plaintext, sizeof(plaintext), &plaintextLen); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + plaintextLen == (sizeof(payload) - 1u) && + memcmp(plaintext, payload, plaintextLen) == 0, + "ECDH recipient accepts unknown text extension"); + + ret = test_cose_add_vendor_entries(out, outLen, recipientMapPos, 2u, + modified, sizeof(modified), &modifiedLen); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, modified, + modifiedLen, NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, plaintext, sizeof(plaintext), &plaintextLen); + } + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "ECDH recipient rejects duplicate text extension"); + + ret = test_cose_add_vendor_entries(out, outLen, ephemMapPos, 1u, + modified, sizeof(modified), &modifiedLen); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, modified, + modifiedLen, NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, plaintext, sizeof(plaintext), &plaintextLen); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + plaintextLen == (sizeof(payload) - 1u) && + memcmp(plaintext, payload, plaintextLen) == 0, + "ECDH ephemeral key accepts unknown text extension"); + + ret = test_cose_add_vendor_entries(out, outLen, ephemMapPos, 2u, + modified, sizeof(modified), &modifiedLen); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, modified, + modifiedLen, NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, plaintext, sizeof(plaintext), &plaintextLen); + } + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "ECDH ephemeral key rejects duplicate text extension"); + } + + wc_CoseKey_Free(&recipientKey); + (void)wc_ecc_free(&recipientEcc); + (void)wc_FreeRng(&rng); +} + static void test_cose_encrypt_ecdh_es_malformed_ephemeral_point(void) { WOLFCOSE_KEY recipientKey; @@ -12416,7 +12634,7 @@ static void test_cose_mac_multi_recipient_direct_empty_protected(void) { WOLFCOSE_KEY key1, key2; WOLFCOSE_RECIPIENT recipients[2]; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; int ret; uint8_t out[512]; size_t outLen = 0; @@ -15283,7 +15501,7 @@ static void test_cose_mac0_key_sizes(void) /* Test CBOR encoding edge cases for higher coverage */ static void test_cbor_edge_cases(void) { - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; uint8_t buf[256]; int ret; uint64_t u64Val; @@ -15294,50 +15512,56 @@ static void test_cbor_edge_cases(void) TEST_LOG(" [CBOR Edge Cases]\n"); /* Test encoding/decoding large uint (> 255) */ - ctx.buf = buf; - ctx.bufSz = sizeof(buf); - ctx.idx = 0; + ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "init uint encoder"); ret = wc_CBOR_EncodeUint(&ctx, 1000); /* > 255, needs 2 bytes */ TEST_ASSERT(ret == 0, "encode uint 1000"); - ctx.cbuf = buf; - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "init uint decoder"); ret = wc_CBOR_DecodeUint(&ctx, &u64Val); TEST_ASSERT(ret == 0, "decode uint 1000"); TEST_ASSERT(u64Val == 1000, "uint 1000 value"); /* Test encoding/decoding 4-byte uint */ - ctx.idx = 0; + ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "reinit uint encoder"); ret = wc_CBOR_EncodeUint(&ctx, 100000); /* needs 4 bytes */ TEST_ASSERT(ret == 0, "encode uint 100000"); - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "reinit uint decoder"); ret = wc_CBOR_DecodeUint(&ctx, &u64Val); TEST_ASSERT(ret == 0, "decode uint 100000"); TEST_ASSERT(u64Val == 100000, "uint 100000 value"); /* Test negative integer encoding */ - ctx.idx = 0; + ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "reinit int encoder"); ret = wc_CBOR_EncodeInt(&ctx, -100); TEST_ASSERT(ret == 0, "encode int -100"); - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "reinit int decoder"); ret = wc_CBOR_DecodeInt(&ctx, &i64Val); TEST_ASSERT(ret == 0, "decode int -100"); TEST_ASSERT(i64Val == -100, "int -100 value"); /* Test large negative integer */ - ctx.idx = 0; + ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "reinit negative-int encoder"); ret = wc_CBOR_EncodeInt(&ctx, -1000); TEST_ASSERT(ret == 0, "encode int -1000"); - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "reinit negative-int decoder"); ret = wc_CBOR_DecodeInt(&ctx, &i64Val); TEST_ASSERT(ret == 0, "decode int -1000"); TEST_ASSERT(i64Val == -1000, "int -1000 value"); /* Test bstr boundary (24 bytes) */ - ctx.idx = 0; + ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "reinit bstr encoder"); ret = wc_CBOR_EncodeBstr(&ctx, buf, 24); TEST_ASSERT(ret == 0, "encode bstr 24"); @@ -15346,8 +15570,8 @@ static void test_cbor_edge_cases(void) { uint8_t largeBuf[512]; const uint8_t bigData[260] = {0}; - WOLFCOSE_CBOR_CTX bigCtx; - bigCtx.buf = largeBuf; + WOLFCOSE_CBOR_CTX bigCtx = { 0 }; + bigCtx.buf = largeBuf; bigCtx.cbuf = NULL; bigCtx.bufSz = sizeof(largeBuf); bigCtx.idx = 0; ret = wc_CBOR_EncodeBstr(&bigCtx, bigData, 256); @@ -15367,8 +15591,8 @@ static void test_cbor_edge_cases(void) ret = wc_CBOR_EncodeBstr(&ctx, testBytes, sizeof(testBytes)); TEST_ASSERT(ret == 0, "encode map val bstr"); - ctx.cbuf = buf; - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "init map decoder"); ret = wc_CBOR_DecodeMapStart(&ctx, &count); TEST_ASSERT(ret == 0, "decode map start"); TEST_ASSERT(count == 2, "map count 2"); @@ -15380,8 +15604,8 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t tiny[2]; - WOLFCOSE_CBOR_CTX tinyCtx; - tinyCtx.buf = tiny; + WOLFCOSE_CBOR_CTX tinyCtx = { 0 }; + tinyCtx.buf = tiny; tinyCtx.cbuf = NULL; tinyCtx.bufSz = sizeof(tiny); tinyCtx.idx = 0; ret = wc_CBOR_EncodeUint(&tinyCtx, 0xFFFFFFFFu); /* needs 5 bytes */ @@ -15392,8 +15616,8 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t tiny[4]; - WOLFCOSE_CBOR_CTX tinyCtx; - tinyCtx.buf = tiny; + WOLFCOSE_CBOR_CTX tinyCtx = { 0 }; + tinyCtx.buf = tiny; tinyCtx.cbuf = NULL; tinyCtx.bufSz = sizeof(tiny); tinyCtx.idx = 0; ret = wc_CBOR_EncodeUint(&tinyCtx, 0xFFFFFFFFFFFFFFFFULL); @@ -15405,8 +15629,8 @@ static void test_cbor_edge_cases(void) { uint8_t tiny[5]; uint8_t data[10] = {0}; - WOLFCOSE_CBOR_CTX tinyCtx; - tinyCtx.buf = tiny; + WOLFCOSE_CBOR_CTX tinyCtx = { 0 }; + tinyCtx.buf = tiny; tinyCtx.cbuf = NULL; tinyCtx.bufSz = sizeof(tiny); tinyCtx.idx = 0; ret = wc_CBOR_EncodeBstr(&tinyCtx, data, sizeof(data)); @@ -15472,7 +15696,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t empty[1] = {0}; - WOLFCOSE_CBOR_CTX emptyCtx; + WOLFCOSE_CBOR_CTX emptyCtx = { 0 }; emptyCtx.cbuf = empty; emptyCtx.bufSz = 0; /* Empty buffer */ emptyCtx.idx = 0; @@ -15484,7 +15708,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t truncated[] = {0x19, 0x01}; /* uint16 header, only 1 data byte */ - WOLFCOSE_CBOR_CTX truncCtx; + WOLFCOSE_CBOR_CTX truncCtx = { 0 }; truncCtx.cbuf = truncated; truncCtx.bufSz = sizeof(truncated); truncCtx.idx = 0; @@ -15496,7 +15720,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t truncated[] = {0x1A, 0x01, 0x02}; /* uint32 header, only 2 data bytes */ - WOLFCOSE_CBOR_CTX truncCtx; + WOLFCOSE_CBOR_CTX truncCtx = { 0 }; truncCtx.cbuf = truncated; truncCtx.bufSz = sizeof(truncated); truncCtx.idx = 0; @@ -15508,7 +15732,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t truncated[] = {0x1B, 0x01, 0x02, 0x03, 0x04}; /* uint64 header, only 4 data bytes */ - WOLFCOSE_CBOR_CTX truncCtx; + WOLFCOSE_CBOR_CTX truncCtx = { 0 }; truncCtx.cbuf = truncated; truncCtx.bufSz = sizeof(truncated); truncCtx.idx = 0; @@ -15520,7 +15744,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t reserved[] = {0x1C}; /* AI=28 is reserved */ - WOLFCOSE_CBOR_CTX resCtx; + WOLFCOSE_CBOR_CTX resCtx = { 0 }; resCtx.cbuf = reserved; resCtx.bufSz = sizeof(reserved); resCtx.idx = 0; @@ -15532,7 +15756,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t indef[] = {0x5F}; /* bstr indefinite */ - WOLFCOSE_CBOR_CTX indefCtx; + WOLFCOSE_CBOR_CTX indefCtx = { 0 }; indefCtx.cbuf = indef; indefCtx.bufSz = sizeof(indef); indefCtx.idx = 0; @@ -15546,7 +15770,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t truncBstr[] = {0x45, 'a', 'b'}; /* bstr of 5 bytes, only 2 provided */ - WOLFCOSE_CBOR_CTX truncCtx; + WOLFCOSE_CBOR_CTX truncCtx = { 0 }; truncCtx.cbuf = truncBstr; truncCtx.bufSz = sizeof(truncBstr); truncCtx.idx = 0; @@ -15563,7 +15787,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t bstr[] = {0x43, 'a', 'b', 'c'}; /* bstr of 3 bytes */ - WOLFCOSE_CBOR_CTX bstrCtx; + WOLFCOSE_CBOR_CTX bstrCtx = { 0 }; bstrCtx.cbuf = bstr; bstrCtx.bufSz = sizeof(bstr); bstrCtx.idx = 0; @@ -15575,7 +15799,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t uintData[] = {0x18, 0x64}; /* uint 100 */ - WOLFCOSE_CBOR_CTX uintCtx; + WOLFCOSE_CBOR_CTX uintCtx = { 0 }; uintCtx.cbuf = uintData; uintCtx.bufSz = sizeof(uintData); uintCtx.idx = 0; @@ -15589,7 +15813,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t bstr[] = {0x43, 'a', 'b', 'c'}; - WOLFCOSE_CBOR_CTX bstrCtx; + WOLFCOSE_CBOR_CTX bstrCtx = { 0 }; bstrCtx.cbuf = bstr; bstrCtx.bufSz = sizeof(bstr); bstrCtx.idx = 0; @@ -15601,7 +15825,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t arr[] = {0x82, 0x01, 0x02}; /* array of 2 elements */ - WOLFCOSE_CBOR_CTX arrCtx; + WOLFCOSE_CBOR_CTX arrCtx = { 0 }; arrCtx.cbuf = arr; arrCtx.bufSz = sizeof(arr); arrCtx.idx = 0; @@ -15613,7 +15837,7 @@ static void test_cbor_edge_cases(void) /* empty-brace-scan: allow - test-local temporary scope */ { uint8_t bstr[] = {0x43, 'a', 'b', 'c'}; - WOLFCOSE_CBOR_CTX bstrCtx; + WOLFCOSE_CBOR_CTX bstrCtx = { 0 }; bstrCtx.cbuf = bstr; bstrCtx.bufSz = sizeof(bstr); bstrCtx.idx = 0; @@ -15629,7 +15853,7 @@ static void test_cbor_edge_cases(void) { /* Encode 0x8000000000000000 (> INT64_MAX) */ uint8_t bigUint[] = {0x1B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - WOLFCOSE_CBOR_CTX bigCtx; + WOLFCOSE_CBOR_CTX bigCtx = { 0 }; bigCtx.cbuf = bigUint; bigCtx.bufSz = sizeof(bigUint); bigCtx.idx = 0; @@ -15642,7 +15866,7 @@ static void test_cbor_edge_cases(void) { /* CBOR negative: -1 - 0x8000000000000000 would overflow */ uint8_t bigNeg[] = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - WOLFCOSE_CBOR_CTX bigCtx; + WOLFCOSE_CBOR_CTX bigCtx = { 0 }; bigCtx.cbuf = bigNeg; bigCtx.bufSz = sizeof(bigNeg); bigCtx.idx = 0; @@ -15656,19 +15880,20 @@ static void test_cbor_edge_cases(void) { uint64_t tag; /* Encode a tag and decode it */ - ctx.idx = 0; + ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "reinit tag encoder"); ret = wc_CBOR_EncodeTag(&ctx, 18); /* COSE_Sign1 tag */ TEST_ASSERT(ret == 0, "encode tag 18"); - ctx.cbuf = buf; - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "init tag decoder"); ret = wc_CBOR_DecodeTag(&ctx, &tag); TEST_ASSERT(ret == 0, "decode tag"); TEST_ASSERT(tag == 18, "tag value 18"); /* Tag with wrong type */ uint8_t notTag[] = {0x01}; /* uint 1 */ - WOLFCOSE_CBOR_CTX notTagCtx; + WOLFCOSE_CBOR_CTX notTagCtx = { 0 }; notTagCtx.cbuf = notTag; notTagCtx.bufSz = sizeof(notTag); notTagCtx.idx = 0; @@ -15688,7 +15913,8 @@ static void test_cbor_edge_cases(void) TEST_LOG(" [CBOR Encode Boundaries]\n"); /* Encode value 23 (max single-byte) */ - ctx.idx = 0; + ret = wc_CBOR_EncoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "reinit boundary encoder"); ret = wc_CBOR_EncodeUint(&ctx, 23); TEST_ASSERT(ret == 0, "encode uint 23"); @@ -15746,8 +15972,8 @@ static void test_cbor_edge_cases(void) /* Simple value encode with buffer too small */ /* empty-brace-scan: allow - test-local temporary scope */ { - WOLFCOSE_CBOR_CTX tinyCtx; - tinyCtx.buf = buf; /* Use valid buf but 0 size */ + WOLFCOSE_CBOR_CTX tinyCtx = { 0 }; + tinyCtx.buf = buf; tinyCtx.cbuf = NULL; /* Use valid buf but 0 size */ tinyCtx.bufSz = 0; tinyCtx.idx = 0; ret = wc_CBOR_EncodeTrue(&tinyCtx); @@ -15767,8 +15993,8 @@ static void test_cbor_edge_cases(void) ret = wc_CBOR_EncodeTstr(&ctx, helloTstr, sizeof(helloTstr)); TEST_ASSERT(ret == 0, "encode tstr"); - ctx.cbuf = buf; - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, buf, sizeof(buf)); + TEST_ASSERT(ret == 0, "init tstr decoder"); ret = wc_CBOR_DecodeTstr(&ctx, &str, &strLen); TEST_ASSERT(ret == 0, "decode tstr"); TEST_ASSERT(strLen == 5, "tstr len"); @@ -15776,7 +16002,7 @@ static void test_cbor_edge_cases(void) /* Type mismatch: decode bstr as tstr */ uint8_t bstr[] = {0x43, 'a', 'b', 'c'}; /* bstr */ - WOLFCOSE_CBOR_CTX bstrCtx; + WOLFCOSE_CBOR_CTX bstrCtx = { 0 }; bstrCtx.cbuf = bstr; bstrCtx.bufSz = sizeof(bstr); bstrCtx.idx = 0; @@ -15788,8 +16014,8 @@ static void test_cbor_edge_cases(void) TEST_LOG(" [CBOR NULL Buffer]\n"); /* empty-brace-scan: allow - test-local temporary scope */ { - WOLFCOSE_CBOR_CTX nullBufCtx; - nullBufCtx.buf = NULL; + WOLFCOSE_CBOR_CTX nullBufCtx = { 0 }; + nullBufCtx.buf = NULL; nullBufCtx.cbuf = NULL; nullBufCtx.bufSz = 256; nullBufCtx.idx = 0; @@ -16285,7 +16511,7 @@ static void test_cose_encrypt_direct_empty_protected(void) WOLFCOSE_KEY key; WOLFCOSE_RECIPIENT recipient; WOLFCOSE_HDR hdr; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; WC_RNG rng; int ret; int rngInited = 0; @@ -16715,6 +16941,38 @@ static void test_cose_encrypt_multi_per_recipient(void) } #endif /* WOLFCOSE_ENCRYPT && WOLFCOSE_HAVE_AESGCM */ +#if !defined(WOLFCOSE_EAT_PSA) && \ + (defined(WOLFCOSE_SIGN1_VERIFY) || defined(WOLFCOSE_MAC0_VERIFY)) +static void test_cose_default_rejects_profile_decode_flags(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_HDR hdr; + const uint8_t input[] = {0x00u}; + const uint8_t* payload = NULL; + uint8_t scratch[1] = {0u}; + size_t payloadLen = 0u; + int ret; + + TEST_LOG(" [Default build: private profile decode flags]\n"); + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&hdr, 0, sizeof(hdr)); +#if defined(WOLFCOSE_SIGN1_VERIFY) + ret = wolfCose_Sign1_Verify_ex(&key, input, sizeof(input), NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &payload, &payloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "default Sign1 verifier rejects private PSA/EAT decode flag"); +#endif +#if defined(WOLFCOSE_MAC0_VERIFY) + ret = wolfCose_Mac0_Verify_ex(&key, input, sizeof(input), NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &payload, &payloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "default Mac0 verifier rejects private PSA/EAT decode flag"); +#endif +} +#endif + static void test_cose_protected_hdr_content_type(void) { int ret; @@ -16798,13 +17056,40 @@ static void test_cose_protected_hdr_tstr_label(void) WOLFCOSE_HDR_STATE hdrState; /* {1: -7, "x": 0} : alg ES256, plus an unknown tstr label */ uint8_t tstrLabel[] = {0xA2u, 0x01u, 0x26u, 0x61u, 'x', 0x00u}; + /* Same map, with tstr length one encoded non-preferred. */ + uint8_t nonPreferred[] = { + 0xA2u, 0x01u, 0x26u, 0x78u, 0x01u, 'x', 0x00u + }; + uint8_t duplicateTstr[] = { + 0xA2u, 0x61u, 'x', 0x00u, 0x61u, 'x', 0x01u + }; TEST_LOG(" [Protected Header: tstr-labeled entry]\n"); XMEMSET(&hdr, 0, sizeof(hdr)); ret = wolfCose_DecodeProtectedHdr(tstrLabel, sizeof(tstrLabel), &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && hdr.alg == WOLFCOSE_ALG_ES256, + "DecodeProtectedHdr skips unknown preferred tstr label"); + + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(nonPreferred, sizeof(nonPreferred), + &hdr, &hdrState); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, - "DecodeProtectedHdr rejects tstr labels"); + "strict protected header rejects non-preferred tstr label"); +#if defined(WOLFCOSE_EAT_PSA) + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr_ex(nonPreferred, sizeof(nonPreferred), + &hdr, &hdrState, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && hdr.alg == WOLFCOSE_ALG_ES256, + "PSA/EAT header accepts non-preferred tstr label"); +#endif + + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(duplicateTstr, sizeof(duplicateTstr), + &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "DecodeProtectedHdr rejects duplicate tstr labels"); } static void test_cose_protected_hdr_dup_label(void) @@ -16850,6 +17135,10 @@ static void test_cose_protected_hdr_crit(void) uint8_t critMissing[] = {0xA2u, 0x01u, 0x26u, 0x02u, 0x81u, 0x05u}; /* {1: -7, 2: []} : crit is an empty array -> RFC 9052 rejects */ uint8_t critEmpty[] = {0xA2u, 0x01u, 0x26u, 0x02u, 0x80u}; + /* {1: -7, 2: ["alg"]} : numeric-label crit cannot contain a tstr */ + uint8_t critText[] = { + 0xA2u, 0x01u, 0x26u, 0x02u, 0x81u, 0x63u, 'a', 'l', 'g' + }; TEST_LOG(" [Protected Header: crit]\n"); XMEMSET(&hdr, 0, sizeof(hdr)); @@ -16875,6 +17164,20 @@ static void test_cose_protected_hdr_crit(void) &hdrState); TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, "DecodeProtectedHdr crit empty array"); + + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(critText, sizeof(critText), &hdr, + &hdrState); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "DecodeProtectedHdr numeric crit rejects text label"); + +#if defined(WOLFCOSE_EAT_PSA) + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr_ex(critText, sizeof(critText), &hdr, + &hdrState, WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "tolerant DecodeProtectedHdr numeric crit rejects text label"); +#endif } static void test_cose_cross_bucket_dup(void) @@ -16882,7 +17185,7 @@ static void test_cose_cross_bucket_dup(void) int ret; WOLFCOSE_HDR hdr; WOLFCOSE_HDR_STATE hdrState; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; uint8_t protAlg[] = {0xA1u, 0x01u, 0x26u}; uint8_t unprotAlg[] = {0xA1u, 0x01u, 0x26u}; @@ -16906,7 +17209,7 @@ static void test_cose_crit_in_unprotected(void) int ret; WOLFCOSE_HDR hdr; WOLFCOSE_HDR_STATE hdrState; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; /* {2: [1]} : crit in unprotected bucket - RFC 9052 forbids this. */ uint8_t critUnprot[] = {0xA1u, 0x02u, 0x81u, 0x01u}; @@ -16926,7 +17229,7 @@ static void test_cose_iv_partial_iv(void) int ret; WOLFCOSE_HDR hdr; WOLFCOSE_HDR_STATE hdrState; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; /* {5: h'01', 6: h'02'} : IV and Partial IV both present */ uint8_t ivPiv[] = {0xA2u, 0x05u, 0x41u, 0x01u, 0x06u, 0x41u, 0x02u}; /* {5: h'01020304'} : IV only (protected, valid) */ @@ -18191,7 +18494,7 @@ static void test_cose_decode_tstr_alg_values(void) int ret; WOLFCOSE_HDR hdr; WOLFCOSE_HDR_STATE hdrState; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; /* Protected hdr {1: "X"} — tstr alg */ uint8_t protTstrAlg[] = {0xA1u, 0x01u, 0x61u, 'X'}; /* Unprotected hdr {1: "X"} */ @@ -18215,34 +18518,237 @@ static void test_cose_decode_tstr_alg_values(void) "DecodeUnprotectedHdr tolerates tstr alg"); } +static int test_cose_encode_text_extension_map(uint8_t firstLabel, + size_t count, uint8_t* out, size_t outSz, size_t* outLen) +{ + WOLFCOSE_CBOR_CTX ctx; + size_t i; + int ret = WOLFCOSE_E_INVALID_ARG; + + if (outLen != NULL) { + *outLen = 0u; + ret = wc_CBOR_EncoderInit(&ctx, out, outSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, count); + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + uint8_t label = (uint8_t)(firstLabel + (uint8_t)i); + + ret = wc_CBOR_EncodeTstr(&ctx, &label, 1u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 0u); + } + } + if ((ret == WOLFCOSE_SUCCESS) && (outLen != NULL)) { + *outLen = ctx.idx; + } + + return ret; +} + +static int test_cose_encode_int_extension_map(int64_t firstLabel, + size_t count, uint8_t* out, size_t outSz, size_t* outLen) +{ + WOLFCOSE_CBOR_CTX ctx; + size_t i; + int ret = WOLFCOSE_E_INVALID_ARG; + + if (outLen != NULL) { + *outLen = 0u; + ret = wc_CBOR_EncoderInit(&ctx, out, outSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, count); + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + ret = wc_CBOR_EncodeInt(&ctx, firstLabel + (int64_t)i); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 0u); + } + } + if ((ret == WOLFCOSE_SUCCESS) && (outLen != NULL)) { + *outLen = ctx.idx; + } + + return ret; +} + static void test_cose_decode_unprotected_tstr_label(void) { - /* Cover the tstr-skip + dup-detection paths in - * wolfCose_DecodeUnprotectedHdr that the protected-hdr test - * exercised on the other side. */ int ret; WOLFCOSE_HDR hdr; WOLFCOSE_HDR_STATE hdrState; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; /* {1: -7, "x": 0} */ uint8_t tstrLabel[] = {0xA2u, 0x01u, 0x26u, 0x61u, 'x', 0x00u}; - - TEST_LOG(" [DecodeUnprotectedHdr: tstr label skipped]\n"); + uint8_t protectedLabel[] = { + 0xA2u, 0x01u, 0x26u, 0x61u, 'x', 0x00u + }; + uint8_t duplicateLabel[] = { + 0xA1u, 0x61u, 'x', 0x00u + }; + uint8_t protectedCapacity[128]; + uint8_t unprotectedCapacity[128]; + size_t protectedCapacityLen = 0u; + size_t unprotectedCapacityLen = 0u; + size_t protectedCount = (size_t)WOLFCOSE_MAX_MAP_ITEMS / 2u; + size_t unprotectedCount = + (size_t)WOLFCOSE_MAX_MAP_ITEMS - protectedCount; + + TEST_LOG(" [DecodeUnprotectedHdr: tstr label]\n"); memset(&hdr, 0, sizeof(hdr)); memset(&hdrState, 0, sizeof(hdrState)); ctx.cbuf = tstrLabel; ctx.bufSz = sizeof(tstrLabel); ctx.idx = 0; ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && hdr.alg == WOLFCOSE_ALG_ES256, + "DecodeUnprotectedHdr skips unknown tstr label"); + + memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedLabel, sizeof(protectedLabel), + &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "DecodeProtectedHdr records tstr label for cross-map duplicate"); + ctx.cbuf = duplicateLabel; + ctx.bufSz = sizeof(duplicateLabel); + ctx.idx = 0; + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "DecodeUnprotectedHdr rejects protected tstr duplicate"); + + ret = test_cose_encode_text_extension_map((uint8_t)'A', protectedCount, + protectedCapacity, sizeof(protectedCapacity), &protectedCapacityLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = test_cose_encode_text_extension_map((uint8_t)'a', + unprotectedCount, unprotectedCapacity, + sizeof(unprotectedCapacity), &unprotectedCapacityLen); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedCapacity, + protectedCapacityLen, &hdr, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecoderInit(&ctx, unprotectedCapacity, + unprotectedCapacityLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + hdrState.textCount == (size_t)WOLFCOSE_MAX_MAP_ITEMS, + "combined protected text-label capacity succeeds at exact limit"); + + ret = test_cose_encode_text_extension_map((uint8_t)'a', + unprotectedCount + 1u, unprotectedCapacity, + sizeof(unprotectedCapacity), &unprotectedCapacityLen); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedCapacity, + protectedCapacityLen, &hdr, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecoderInit(&ctx, unprotectedCapacity, + unprotectedCapacityLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + } + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "combined protected text-label capacity rejects one over limit"); + + ret = test_cose_encode_int_extension_map(100, protectedCount, + protectedCapacity, sizeof(protectedCapacity), &protectedCapacityLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = test_cose_encode_text_extension_map((uint8_t)'a', + unprotectedCount, unprotectedCapacity, + sizeof(unprotectedCapacity), &unprotectedCapacityLen); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedCapacity, + protectedCapacityLen, &hdr, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecoderInit(&ctx, unprotectedCapacity, + unprotectedCapacityLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + hdrState.extraCount == protectedCount && + hdrState.textCount == unprotectedCount, + "combined integer-before-text capacity succeeds at exact limit"); + + ret = test_cose_encode_text_extension_map((uint8_t)'a', + unprotectedCount + 1u, unprotectedCapacity, + sizeof(unprotectedCapacity), &unprotectedCapacityLen); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedCapacity, + protectedCapacityLen, &hdr, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecoderInit(&ctx, unprotectedCapacity, + unprotectedCapacityLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + } + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "combined integer-before-text capacity rejects one over limit"); + + ret = test_cose_encode_text_extension_map((uint8_t)'A', protectedCount, + protectedCapacity, sizeof(protectedCapacity), &protectedCapacityLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = test_cose_encode_int_extension_map(100, unprotectedCount, + unprotectedCapacity, sizeof(unprotectedCapacity), + &unprotectedCapacityLen); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedCapacity, + protectedCapacityLen, &hdr, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecoderInit(&ctx, unprotectedCapacity, + unprotectedCapacityLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + hdrState.textCount == protectedCount && + hdrState.extraCount == unprotectedCount, + "combined text-before-integer capacity succeeds at exact limit"); + + ret = test_cose_encode_int_extension_map(100, unprotectedCount + 1u, + unprotectedCapacity, sizeof(unprotectedCapacity), + &unprotectedCapacityLen); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedCapacity, + protectedCapacityLen, &hdr, &hdrState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecoderInit(&ctx, unprotectedCapacity, + unprotectedCapacityLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + } TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, - "DecodeUnprotectedHdr rejects tstr label"); + "combined text-before-integer capacity rejects one over limit"); } static void test_cose_key_decode_tstr_alg_rejected(void) { WOLFCOSE_KEY key; uint8_t buf[128]; - WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX enc = { 0 }; const uint8_t symmKey[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; static const uint8_t hs256Tstr[] = { 0x48u, 0x53u, 0x32u, 0x35u, 0x36u @@ -18251,7 +18757,7 @@ static void test_cose_key_decode_tstr_alg_rejected(void) TEST_LOG(" [COSE_Key decode tstr alg rejected]\n"); - enc.buf = buf; + enc.buf = buf; enc.cbuf = NULL; enc.bufSz = sizeof(buf); enc.idx = 0; ret = wc_CBOR_EncodeMapStart(&enc, 3); @@ -19483,7 +19989,7 @@ static void test_internal_helpers(void) TEST_LOG(" [Header Decode Edge Cases]\n"); /* empty-brace-scan: allow - test-local temporary scope */ { - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; WOLFCOSE_HDR hdr; WOLFCOSE_HDR_STATE hdrState; @@ -23225,7 +23731,7 @@ static void test_ecdh_es_multi_recipient_decrypt_rejected(void) WOLFCOSE_HDR hdr; ecc_key recipientEcc; WC_RNG rng; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; int ret; size_t outerCount = 0; size_t recipPos = 0; @@ -23314,7 +23820,7 @@ static void test_ecdh_es_recipient_protected_bound(void) WOLFCOSE_HDR hdr; ecc_key recipientEcc; WC_RNG rng; - WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_CBOR_CTX ctx = { 0 }; int ret; size_t n = 0; size_t i; @@ -23603,6 +24109,10 @@ int test_cose(void) /* Internal helper tests */ test_wolfcose_force_zero(); +#if !defined(WOLFCOSE_EAT_PSA) && \ + (defined(WOLFCOSE_SIGN1_VERIFY) || defined(WOLFCOSE_MAC0_VERIFY)) + test_cose_default_rejects_profile_decode_flags(); +#endif #if defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_SIGN1_SIGN) test_cose_sign1_size_and_untagged(); #endif @@ -23822,6 +24332,7 @@ int test_cose(void) test_cose_encrypt_ecdh_es_kid_and_alg_pin(); test_cose_encrypt_ecdh_es_ephemeral_crv_narrowing(); test_cose_encrypt_ecdh_es_malformed_ephemeral_point(); + test_cose_encrypt_ecdh_es_text_extensions(); test_cose_encrypt_ecdh_es_hkdf_256(); test_cose_encrypt_ecdh_es_long_recipient_protected(); test_cose_encrypt_ecdh_es_wrong_key(); diff --git a/tests/test_eat_psa.c b/tests/test_eat_psa.c new file mode 100644 index 0000000..552e93e --- /dev/null +++ b/tests/test_eat_psa.c @@ -0,0 +1,4219 @@ +/* test_eat_psa.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + */ + +#include +#include + +#include +#include +#ifdef HAVE_ECC + #include +#endif + +#include "../src/wolfcose_internal.h" /* Test delegated-signing seam. */ +#include "test_suite.h" + +static int g_failures = 0; + +#define TEST_ASSERT(cond, name) do { \ + if (!(cond)) { \ + (void)printf(" FAIL: %s (line %d)\n", name, __LINE__); \ + g_failures++; \ + } \ +} while (0) + +#if defined(WOLFCOSE_EAT_PSA) && \ + defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + defined(WOLFCOSE_EAT_PSA_SIGN1) && \ + defined(WOLFCOSE_EAT_PSA_MAC0) && \ + defined(WOLFCOSE_EAT_PSA_ISSUE) && \ + defined(WOLFCOSE_EAT_PSA_LEGACY) && \ + defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) && \ + defined(WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR) && \ + defined(WOLFCOSE_EAT_PSA_TFM_FULL) + #define WOLFCOSE_TEST_EAT_PSA_FULL +#endif + +#if defined(WOLFCOSE_TEST_EAT_PSA_FULL) && \ + (defined(WOLFCOSE_HAVE_ES256) || defined(WOLFCOSE_HAVE_HMAC256)) + +static const uint8_t kNonce[32] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F +}; + +static const uint8_t kUeid[33] = { + 0x01, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F +}; + +static const uint8_t kImplementationId[32] = { + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F +}; + +static const uint8_t kMeasurement[32] = { + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F +}; + +static const uint8_t kSignerId[32] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F +}; + +static const uint8_t kBootSeed[32] = { + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, + 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF +}; + +#if defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256) +static const uint8_t kMac0TestKey[32] = { 0 }; +#endif + +static const uint8_t kOversizedBootSeed[33] = { 0xC0u }; + +static const uint8_t kInvalidCurrentCertRef[] = "1234567890123_12345"; + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +static const uint8_t kLegacyBootSeed[32] = { + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, + 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF +}; +#endif + +/* RFC 9783 Appendix A uses this nonce in both external-producer vectors. */ +static const uint8_t kRfc9783Nonce[32] = { + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +#if defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) +static const uint8_t kRfc9783Sign1KeyX[32] = { + 0x4E, 0x5E, 0x22, 0x09, 0x9E, 0x3B, 0xCE, 0xB4, + 0x5B, 0x44, 0x6D, 0x13, 0x55, 0xFD, 0x1D, 0xC3, + 0xB5, 0x45, 0x94, 0x7B, 0x6F, 0xD7, 0xC1, 0xC8, + 0x9D, 0x88, 0x67, 0x98, 0xC3, 0x72, 0x6E, 0x8F +}; + +static const uint8_t kRfc9783Sign1KeyY[32] = { + 0x80, 0xD7, 0x0B, 0x84, 0x0B, 0x25, 0x6A, 0xAC, + 0x34, 0xA6, 0x2E, 0xDE, 0x10, 0x43, 0x36, 0x4F, + 0x04, 0x40, 0x95, 0xF0, 0x03, 0x47, 0x4B, 0x91, + 0xE0, 0x18, 0x20, 0x92, 0xAF, 0xB1, 0x3F, 0x2E +}; + +static const char kRfc9783Sign1Hex[] = + "d28443a10126a0590100a819010058210102020202020202020202020202" + "0202020202020202020202020202020202020219095c5820000000000000" + "00000000000000000000000000000000000000000000000000000a582001" + "010101010101010101010101010101010101010101010101010101010101" + "0119095a1a7fffffff19095b19300019010978217461673a707361636572" + "7469666965642e6f72672c323032333a7073612374666d19010c48000000" + "000000000019095f81a30558200404040404040404040404040404040404" + "040404040404040404040404040404025820030303030303030303030303" + "0303030303030303030303030303030303030303016450526f545840786e" + "937a4c42667af3847399319ca95c7e7dbabdc9b50fdb8de3f6bff4ab82ff" + "80c42140e2a488000219e3e10663193da69c75f52b798ea10b2f7041a90e" + "8e5a"; +#endif /* WOLFCOSE_SIGN1_VERIFY && WOLFCOSE_HAVE_ES256 */ + +#if defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256) +static const uint8_t kRfc9783Mac0Key[64] = { + 0xDE, 0x03, 0x8B, 0x34, 0xAC, 0xA1, 0x25, 0x76, + 0x8C, 0x5E, 0x33, 0x57, 0xAB, 0x8D, 0x06, 0xB3, + 0x67, 0xB9, 0xAB, 0x0D, 0x7E, 0x8B, 0xE1, 0x24, + 0xED, 0xCA, 0x47, 0xFE, 0x03, 0x3A, 0x5B, 0xB7, + 0xA9, 0x3D, 0x30, 0x7F, 0xF2, 0x29, 0xAA, 0x36, + 0xFF, 0x24, 0x6C, 0x12, 0x95, 0x96, 0x4F, 0xAC, + 0xF7, 0x1A, 0xB7, 0xAA, 0x6E, 0xC4, 0xFD, 0x61, + 0x02, 0xB7, 0xB3, 0x98, 0x32, 0x55, 0xAD, 0x92 +}; + +static const char kRfc9783Mac0Hex[] = + "d18443a10105a0590100a8190100582101c557bd4fadc83f756fca2cd5ea" + "2dcc8b82159bb4e7453d6a744d4eecd6d0ac6019095c5820000000000000" + "00000000000000000000000000000000000000000000000000000a582001" + "010101010101010101010101010101010101010101010101010101010101" + "0119095a1a7fffffff19095b19300019010978217461673a707361636572" + "7469666965642e6f72672c323032333a7073612374666d19010c48000000" + "000000000019095f81a30558200404040404040404040404040404040404" + "040404040404040404040404040404025820030303030303030303030303" + "0303030303030303030303030303030303030303016450526f545820cf88" + "d330e7a5366a95cf744a4dbf0d50304d405edd8b2530e243eddbd3177820"; +#endif /* WOLFCOSE_MAC0_VERIFY && WOLFCOSE_HAVE_HMAC256 */ + +#if (defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256)) || \ + (defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256)) +static int test_eat_psa_hex_nibble(uint8_t c, uint8_t* value) +{ + int ret = WOLFCOSE_SUCCESS; + + if (value == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if ((c >= (uint8_t)'0') && (c <= (uint8_t)'9')) { + *value = (uint8_t)(c - (uint8_t)'0'); + } + else if ((c >= (uint8_t)'a') && (c <= (uint8_t)'f')) { + *value = (uint8_t)(c - (uint8_t)'a' + 10u); + } + else if ((c >= (uint8_t)'A') && (c <= (uint8_t)'F')) { + *value = (uint8_t)(c - (uint8_t)'A' + 10u); + } + else { + ret = WOLFCOSE_E_INVALID_ARG; + } + + return ret; +} + +static int test_eat_psa_hex_decode(const char* hex, uint8_t* out, + size_t outSz, size_t* outLen) +{ + int ret = WOLFCOSE_SUCCESS; + size_t hexLen; + size_t i; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((hex == NULL) || (out == NULL) || (outLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + hexLen = strlen(hex); + if (((hexLen & 1u) != 0u) || ((hexLen / 2u) > outSz)) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < (hexLen / 2u)); i++) { + uint8_t high; + uint8_t low; + + ret = test_eat_psa_hex_nibble((uint8_t)hex[2u * i], &high); + if (ret == WOLFCOSE_SUCCESS) { + ret = test_eat_psa_hex_nibble((uint8_t)hex[(2u * i) + 1u], + &low); + } + if (ret == WOLFCOSE_SUCCESS) { + out[i] = (uint8_t)((high << 4) | low); + } + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = hexLen / 2u; + } + } + + return ret; +} +#endif + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +#define TEST_EAT_PSA_OMIT_NONCE 0x01u +#define TEST_EAT_PSA_OMIT_UEID 0x02u +#define TEST_EAT_PSA_OMIT_PROFILE 0x04u +#define TEST_EAT_PSA_OMIT_CLIENT_ID 0x08u +#define TEST_EAT_PSA_OMIT_LIFECYCLE 0x10u +#define TEST_EAT_PSA_OMIT_IMPLEMENTATION 0x20u +#define TEST_EAT_PSA_OMIT_COMPONENTS 0x40u +#define TEST_EAT_PSA_UNKNOWN_TEXT 0x80u +#define TEST_EAT_PSA_DUP_UNKNOWN_CLAIM 0x100u +#define TEST_EAT_PSA_DUP_UNKNOWN_TEXT 0x200u + +#define TEST_EAT_PSA_COMPONENT_OMIT_VALUE 0x01u +#define TEST_EAT_PSA_COMPONENT_OMIT_SIGNER 0x02u +#define TEST_EAT_PSA_COMPONENT_DUP_VALUE 0x04u +#define TEST_EAT_PSA_COMPONENT_UNKNOWN_TEXT 0x08u +#define TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_TEXT 0x10u +#define TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_CLAIM 0x20u + +static int test_eat_psa_encode_raw_component_ex(WOLFCOSE_CBOR_CTX* ctx, + const WOLFCOSE_EAT_PSA_COMPONENT* component, uint32_t flags) +{ + static const uint8_t unknownLabel[] = "vendor-component"; + static const uint8_t duplicateLabel[] = "vendor-duplicate"; + int ret; + size_t count = 2u; + + count -= ((flags & TEST_EAT_PSA_COMPONENT_OMIT_VALUE) != 0u) ? 1u : 0u; + count -= ((flags & TEST_EAT_PSA_COMPONENT_OMIT_SIGNER) != 0u) ? 1u : 0u; + count += ((flags & TEST_EAT_PSA_COMPONENT_DUP_VALUE) != 0u) ? 1u : 0u; + count += ((flags & TEST_EAT_PSA_COMPONENT_UNKNOWN_TEXT) != 0u) ? 1u : 0u; + count += ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_TEXT) != 0u) ? + 2u : 0u; + count += ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_CLAIM) != 0u) ? + 2u : 0u; + ret = wc_CBOR_EncodeMapStart(ctx, count); + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_OMIT_VALUE) == 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 2u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_OMIT_VALUE) == 0u)) { + ret = wc_CBOR_EncodeBstr(ctx, component->measurementValue.data, + component->measurementValue.len); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_VALUE) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 2u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_VALUE) != 0u)) { + ret = wc_CBOR_EncodeBstr(ctx, component->measurementValue.data, + component->measurementValue.len); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_OMIT_SIGNER) == 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 5u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_OMIT_SIGNER) == 0u)) { + ret = wc_CBOR_EncodeBstr(ctx, component->signerId.data, + component->signerId.len); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeTstr(ctx, unknownLabel, sizeof(unknownLabel) - 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeTstr(ctx, duplicateLabel, + sizeof(duplicateLabel) - 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeTstr(ctx, duplicateLabel, + sizeof(duplicateLabel) - 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 2u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_CLAIM) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 99u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_CLAIM) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_CLAIM) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 99u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_CLAIM) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 2u); + } + + return ret; +} + +/* Encode a signed-input fixture without issuer validation. This lets the + * verifier tests exercise malformed but authenticated peer tokens. */ +static int test_eat_psa_encode_current_raw_ex( + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + const uint8_t* profile, size_t profileLen, uint32_t omit, + size_t unknownClaimCount, int addLegacyNoMeasurements, + uint32_t componentFlags, size_t componentCount, + uint8_t* out, size_t outSz, size_t* outLen) +{ + static const uint8_t unknownLabel[] = "vendor-claim"; + static const uint8_t duplicateLabel[] = "vendor-duplicate"; + WOLFCOSE_CBOR_CTX ctx; + size_t count = 7u; + size_t i; + int ret; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((claims == NULL) || (profile == NULL) || (out == NULL) || + (outLen == NULL) || (claims->components == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + count -= ((omit & TEST_EAT_PSA_OMIT_NONCE) != 0u) ? 1u : 0u; + count -= ((omit & TEST_EAT_PSA_OMIT_UEID) != 0u) ? 1u : 0u; + count -= ((omit & TEST_EAT_PSA_OMIT_PROFILE) != 0u) ? 1u : 0u; + count -= ((omit & TEST_EAT_PSA_OMIT_CLIENT_ID) != 0u) ? 1u : 0u; + count -= ((omit & TEST_EAT_PSA_OMIT_LIFECYCLE) != 0u) ? 1u : 0u; + count -= ((omit & TEST_EAT_PSA_OMIT_IMPLEMENTATION) != 0u) ? 1u : 0u; + count -= ((omit & TEST_EAT_PSA_OMIT_COMPONENTS) != 0u) ? 1u : 0u; + count += unknownClaimCount; + count += (addLegacyNoMeasurements != 0) ? 1u : 0u; + count += (claims->bootSeed.data != NULL) ? 1u : 0u; + count += (claims->certificationReference.data != NULL) ? 1u : 0u; + count += (claims->verificationServiceIndicator.data != NULL) ? 1u : 0u; + count += ((omit & TEST_EAT_PSA_UNKNOWN_TEXT) != 0u) ? 1u : 0u; + count += ((omit & TEST_EAT_PSA_DUP_UNKNOWN_TEXT) != 0u) ? 2u : 0u; + ret = wc_CBOR_EncoderInit(&ctx, out, outSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, count); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_OMIT_NONCE) == 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 10u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->nonce.data, claims->nonce.len); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_OMIT_UEID) == 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 256u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->ueid.data, claims->ueid.len); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_OMIT_PROFILE) == 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 265u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, profile, profileLen); + } + } + if ((ret == WOLFCOSE_SUCCESS) && (claims->bootSeed.data != NULL)) { + ret = wc_CBOR_EncodeUint(&ctx, 268u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->bootSeed.data, + claims->bootSeed.len); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_OMIT_CLIENT_ID) == 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 2394u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, claims->clientId); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_OMIT_LIFECYCLE) == 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 2395u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, claims->lifecycle); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_OMIT_IMPLEMENTATION) == 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 2396u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->implementationId.data, + claims->implementationId.len); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + (claims->certificationReference.data != NULL)) { + ret = wc_CBOR_EncodeUint(&ctx, 2398u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, claims->certificationReference.data, + claims->certificationReference.len); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_OMIT_COMPONENTS) == 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 2399u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&ctx, componentCount); + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && + (i < componentCount); i++) { + ret = test_eat_psa_encode_raw_component_ex(&ctx, + &claims->components[i], componentFlags); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + (claims->verificationServiceIndicator.data != NULL)) { + ret = wc_CBOR_EncodeUint(&ctx, 2400u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, + claims->verificationServiceIndicator.data, + claims->verificationServiceIndicator.len); + } + } + if ((ret == WOLFCOSE_SUCCESS) && (addLegacyNoMeasurements != 0)) { + ret = wc_CBOR_EncodeInt(&ctx, -75007); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 1u); + } + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < unknownClaimCount); i++) { + ret = wc_CBOR_EncodeUint(&ctx, 5000u + + (((omit & TEST_EAT_PSA_DUP_UNKNOWN_CLAIM) != 0u) && (i > 0u) ? + 0u : i)); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 1u); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeTstr(&ctx, unknownLabel, sizeof(unknownLabel) - 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_DUP_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeTstr(&ctx, duplicateLabel, + sizeof(duplicateLabel) - 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_DUP_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_DUP_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeTstr(&ctx, duplicateLabel, + sizeof(duplicateLabel) - 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_DUP_UNKNOWN_TEXT) != 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 2u); + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = ctx.idx; + } + + return ret; +} + +static int test_eat_psa_sign1_payload(WOLFCOSE_KEY* key, WC_RNG* rng, + const uint8_t* payload, size_t payloadLen, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, size_t* outLen) +{ + return wc_CoseSign1_Sign(key, WOLFCOSE_ALG_ES256, NULL, 0u, + payload, payloadLen, NULL, 0u, NULL, 0u, scratch, scratchSz, + out, outSz, outLen, rng); +} + +static int test_eat_psa_verify_raw_current_ex(WOLFCOSE_KEY* key, WC_RNG* rng, + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + const uint8_t* profile, size_t profileLen, uint32_t omit, + size_t unknownClaimCount, int addLegacyNoMeasurements, + uint32_t componentFlags, size_t componentCount, + uint8_t* scratch, size_t scratchSz, WOLFCOSE_EAT_PSA_TOKEN* token) +{ + WOLFCOSE_EAT_PSA_TOKEN localToken; + static uint8_t payload[4096]; + static uint8_t signedToken[4608]; + size_t payloadLen = 0u; + size_t signedTokenLen = 0u; + int ret; + + ret = test_eat_psa_encode_current_raw_ex(claims, profile, profileLen, + omit, unknownClaimCount, addLegacyNoMeasurements, componentFlags, + componentCount, payload, sizeof(payload), &payloadLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = test_eat_psa_sign1_payload(key, rng, payload, payloadLen, + scratch, scratchSz, signedToken, sizeof(signedToken), + &signedTokenLen); + } + if (ret == WOLFCOSE_SUCCESS) { + if (token == NULL) { + token = &localToken; + } + (void)memset(token, 0xA5, sizeof(*token)); + ret = wc_CoseEatPsaToken_Verify(key, signedToken, signedTokenLen, + kNonce, sizeof(kNonce), scratch, scratchSz, token); + } + + return ret; +} + +static int test_eat_psa_verify_raw_current(WOLFCOSE_KEY* key, WC_RNG* rng, + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + const uint8_t* profile, size_t profileLen, uint32_t omit, + size_t unknownClaimCount, int addLegacyNoMeasurements, + uint8_t* scratch, size_t scratchSz) +{ + return test_eat_psa_verify_raw_current_ex(key, rng, claims, profile, + profileLen, omit, unknownClaimCount, addLegacyNoMeasurements, 0u, + (claims != NULL) ? claims->componentCount : 0u, scratch, scratchSz, + NULL); +} +#endif + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +/* The issuer encodes lifecycle as uint16. Retaining that width makes zero a + * non-preferred but valid CBOR representation, which tests claim validation. */ +static int test_eat_psa_set_lifecycle(uint8_t* payload, size_t payloadLen, + uint16_t lifecycle) +{ + int ret = WOLFCOSE_E_EAT_PSA_CLAIM; + size_t i; + + if (payload == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + for (i = 0u; (i + 5u) < payloadLen; i++) { + if ((payload[i] == 0x19u) && (payload[i + 1u] == 0x09u) && + (payload[i + 2u] == 0x5Bu) && (payload[i + 3u] == 0x19u)) { + payload[i + 4u] = (uint8_t)(lifecycle >> 8); + payload[i + 5u] = (uint8_t)lifecycle; + ret = WOLFCOSE_SUCCESS; + break; + } + } + } + + return ret; +} +#endif + +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) && \ + ((defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256)) || \ + (defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256))) +typedef struct EAT_PSA_RESOLVER_CTX { + const WOLFCOSE_KEY* key; + int called; + int fail; + WOLFCOSE_EAT_PSA_PROFILE receivedProfile; + int32_t receivedAlg; +} EAT_PSA_RESOLVER_CTX; +#endif + +#if defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) +typedef struct EAT_PSA_COMPONENT_CTX { + int count; + int valid; + int fail; + size_t expectedHashLen; + const uint8_t* expectedVersion; + size_t expectedVersionLen; +} EAT_PSA_COMPONENT_CTX; +#endif + +#if (defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256)) || \ + (defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_MAC0_VERIFY) && \ + defined(WOLFCOSE_HAVE_HMAC256)) +static void test_eat_psa_claims(WOLFCOSE_EAT_PSA_CLAIMS* claims, + WOLFCOSE_EAT_PSA_COMPONENT* component) +{ + static const uint8_t type[] = "PRoT"; + static const uint8_t version[] = "1.2.3"; + static const uint8_t desc[] = "sha-256"; + static const uint8_t certRef[] = "1234567890123-12345"; + static const uint8_t vsi[] = "https://verifier.example/psa"; + + (void)memset(claims, 0, sizeof(*claims)); + (void)memset(component, 0, sizeof(*component)); + component->measurementType.data = type; + component->measurementType.len = sizeof(type) - 1u; + component->measurementValue.data = kMeasurement; + component->measurementValue.len = sizeof(kMeasurement); + component->version.data = version; + component->version.len = sizeof(version) - 1u; + component->signerId.data = kSignerId; + component->signerId.len = sizeof(kSignerId); + component->measurementDesc.data = desc; + component->measurementDesc.len = sizeof(desc) - 1u; + claims->nonce.data = kNonce; + claims->nonce.len = sizeof(kNonce); + claims->ueid.data = kUeid; + claims->ueid.len = sizeof(kUeid); + claims->implementationId.data = kImplementationId; + claims->implementationId.len = sizeof(kImplementationId); + claims->bootSeed.data = kBootSeed; + claims->bootSeed.len = sizeof(kBootSeed); + claims->certificationReference.data = certRef; + claims->certificationReference.len = sizeof(certRef) - 1u; + claims->verificationServiceIndicator.data = vsi; + claims->verificationServiceIndicator.len = sizeof(vsi) - 1u; + claims->clientId = -1; + claims->lifecycle = 0x3000u; + claims->components = component; + claims->componentCount = 1u; +} +#endif + +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) && \ + ((defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256)) || \ + (defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256))) +static int test_eat_psa_resolve(void* ctx, + WOLFCOSE_EAT_PSA_PROFILE profile, + const uint8_t* ueid, size_t ueidLen, int32_t alg, + WOLFCOSE_KEY* key) +{ + EAT_PSA_RESOLVER_CTX* resolver = (EAT_PSA_RESOLVER_CTX*)ctx; + + if ((resolver == NULL) || (key == NULL) || + (profile == WOLFCOSE_EAT_PSA_PROFILE_NONE) || + (ueid == NULL) || (ueidLen != sizeof(kUeid)) || + (memcmp(ueid, kUeid, sizeof(kUeid)) != 0) || + ((alg != WOLFCOSE_ALG_ES256) && + (alg != WOLFCOSE_ALG_HMAC_256_256))) { + return WOLFCOSE_E_EAT_PSA_KEY; + } + resolver->receivedProfile = profile; + resolver->receivedAlg = alg; + resolver->called++; + if (resolver->fail != 0) { + return WOLFCOSE_E_EAT_PSA_KEY; + } + *key = *resolver->key; + return WOLFCOSE_SUCCESS; +} +#endif + +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) && \ + defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256) +static int test_eat_psa_resolve_without_ctx(void* ctx, + WOLFCOSE_EAT_PSA_PROFILE profile, + const uint8_t* ueid, size_t ueidLen, int32_t alg, + WOLFCOSE_KEY* key) +{ + if ((ctx != NULL) || (key == NULL) || + (profile != WOLFCOSE_EAT_PSA_PROFILE_CURRENT) || + (ueid == NULL) || (ueidLen != sizeof(kUeid)) || + (memcmp(ueid, kUeid, sizeof(kUeid)) != 0) || + (alg != WOLFCOSE_ALG_HMAC_256_256)) { + return WOLFCOSE_E_EAT_PSA_KEY; + } + + return wc_CoseKey_SetSymmetric(key, kMac0TestKey, + sizeof(kMac0TestKey)); +} +#endif + +#if defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) +static int test_eat_psa_component(void* ctx, + const WOLFCOSE_EAT_PSA_COMPONENT* component) +{ + EAT_PSA_COMPONENT_CTX* componentCtx = (EAT_PSA_COMPONENT_CTX*)ctx; + + if ((componentCtx == NULL) || (component == NULL) || + ((component->measurementValue.len != 32u) && + (component->measurementValue.len != 48u) && + (component->measurementValue.len != 64u)) || + ((component->signerId.len != 32u) && + (component->signerId.len != 48u) && + (component->signerId.len != 64u)) || + ((componentCtx->expectedHashLen != 0u) && + ((component->measurementValue.len != componentCtx->expectedHashLen) || + (component->signerId.len != componentCtx->expectedHashLen))) || + ((componentCtx->expectedVersion != NULL) && + ((component->version.len != componentCtx->expectedVersionLen) || + (component->version.data == NULL) || + (memcmp(component->version.data, componentCtx->expectedVersion, + componentCtx->expectedVersionLen) != 0)))) { + return WOLFCOSE_E_EAT_PSA_CLAIM; + } + componentCtx->count++; + componentCtx->valid = 1; + return (componentCtx->fail != 0) ? WOLFCOSE_E_EAT_PSA_CLAIM : + WOLFCOSE_SUCCESS; +} + +static int test_eat_psa_component_without_ctx(void* ctx, + const WOLFCOSE_EAT_PSA_COMPONENT* component) +{ + if ((ctx != NULL) || (component == NULL) || + (component->measurementValue.data == NULL) || + (component->measurementValue.len != sizeof(kMeasurement)) || + (memcmp(component->measurementValue.data, kMeasurement, + sizeof(kMeasurement)) != 0)) { + return WOLFCOSE_E_EAT_PSA_CLAIM; + } + + return WOLFCOSE_SUCCESS; +} +#endif + +static int test_eat_psa_token_is_zero( + const WOLFCOSE_EAT_PSA_TOKEN* token); + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +static void test_eat_psa_nonce_and_hash_lengths(WOLFCOSE_KEY* key, WC_RNG* rng) +{ + static const size_t lengths[] = { 32u, 48u, 64u }; + static uint8_t nonce[64]; + static uint8_t hash[64]; + static uint8_t wrongNonce[64]; + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_TOKEN token; + EAT_PSA_COMPONENT_CTX componentCtx; + uint8_t claimsBuf[1024]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tokenBuf[1536]; + size_t i; + size_t j; + size_t tokenLen; + int ret; + + (void)printf(" [RFC 9783 nonce and component hash lengths]\n"); + for (i = 0u; i < (sizeof(lengths) / sizeof(lengths[0])); i++) { + for (j = 0u; j < lengths[i]; j++) { + nonce[j] = (uint8_t)(0x20u + j); + hash[j] = (uint8_t)(0x80u + j); + } + test_eat_psa_claims(&claims, &component); + claims.nonce.data = nonce; + claims.nonce.len = lengths[i]; + component.measurementValue.data = hash; + component.measurementValue.len = lengths[i]; + component.signerId.data = hash; + component.signerId.len = lengths[i]; + tokenLen = 0u; + ret = wc_CoseEatPsaToken_CreateSign1(key, WOLFCOSE_ALG_ES256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), + tokenBuf, sizeof(tokenBuf), &tokenLen, rng); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && tokenLen > 0u, + "issue PSA token with supported nonce and component hash length"); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&token, 0, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, tokenBuf, tokenLen, nonce, + lengths[i], scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && token.nonce.len == lengths[i], + "verify PSA token with supported nonce length"); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&componentCtx, 0, sizeof(componentCtx)); + componentCtx.expectedHashLen = lengths[i]; + componentCtx.expectedVersion = component.version.data; + componentCtx.expectedVersionLen = component.version.len; + ret = wc_CoseEatPsaToken_ForEachComponent(&token, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && componentCtx.count == 1 && + componentCtx.valid != 0, + "iterate complete supported component hash length and version"); + } + if (tokenLen > 0u) { + (void)memcpy(wrongNonce, nonce, lengths[i]); + wrongNonce[0] ^= 0xFFu; + ret = wc_CoseEatPsaToken_Verify(key, tokenBuf, tokenLen, + wrongNonce, lengths[i], scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_NONCE, + "reject same-length mismatched PSA nonce"); + } + if ((tokenLen > 0u) && (lengths[i] == 32u)) { + for (j = 0u; j < 48u; j++) { + wrongNonce[j] = (uint8_t)(0x40u + j); + } + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, tokenBuf, tokenLen, + wrongNonce, 48u, scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_NONCE && + test_eat_psa_token_is_zero(&token) != 0, + "reject valid but different expected nonce length"); + } + } +} +#endif + +static int test_eat_psa_token_is_zero(const WOLFCOSE_EAT_PSA_TOKEN* token) +{ + WOLFCOSE_EAT_PSA_TOKEN zeroToken; + + (void)memset(&zeroToken, 0, sizeof(zeroToken)); + return ((token != NULL) && + (memcmp(token, &zeroToken, sizeof(zeroToken)) == 0)) ? 1 : 0; +} + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +static int test_eat_psa_replace_once(const uint8_t* in, size_t inLen, + const uint8_t* find, size_t findLen, + const uint8_t* replacement, size_t replacementLen, + uint8_t* out, size_t outSz, size_t* outLen) +{ + size_t i; + int ret = WOLFCOSE_E_INVALID_ARG; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((in == NULL) || (find == NULL) || (replacement == NULL) || + (out == NULL) || (outLen == NULL) || (findLen == 0u) || + (findLen > inLen) || (replacementLen == 0u)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if ((outSz < inLen) || + ((replacementLen > findLen) && + ((replacementLen - findLen) > (outSz - inLen)))) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + else { + for (i = 0u; i <= (inLen - findLen); i++) { + if (memcmp(&in[i], find, findLen) == 0) { + (void)memcpy(out, in, i); + (void)memcpy(&out[i], replacement, replacementLen); + (void)memcpy(&out[i + replacementLen], &in[i + findLen], + inLen - i - findLen); + *outLen = inLen - findLen + replacementLen; + ret = WOLFCOSE_SUCCESS; + break; + } + } + } + + return ret; +} + +static void test_eat_psa_nonpreferred_claim_form(WOLFCOSE_KEY* key, + WC_RNG* rng, const uint8_t* payload, size_t payloadLen, + const uint8_t* find, size_t findLen, + const uint8_t* replacement, size_t replacementLen, + const char* strictName, const char* acceptName) +{ + WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_EAT_PSA_TOKEN token; + uint8_t variant[1024]; + uint8_t signedToken[1536]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + size_t variantLen = 0u; + size_t signedTokenLen = 0u; + int ret; + + ret = test_eat_psa_replace_once(payload, payloadLen, find, findLen, + replacement, replacementLen, variant, sizeof(variant), &variantLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "construct non-preferred authenticated claim form"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecoderInit(&ctx, variant, variantLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_Skip(&ctx); + } + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, strictName); + + ret = test_eat_psa_sign1_payload(key, rng, variant, variantLen, scratch, + sizeof(scratch), signedToken, sizeof(signedToken), &signedTokenLen); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, signedToken, signedTokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT && + token.clientId == -1 && token.lifecycle == 0x3000u, + acceptName); +} + +static void test_eat_psa_nonpreferred_claim_forms(WOLFCOSE_KEY* key, + WC_RNG* rng, const uint8_t* payload, size_t payloadLen) +{ + static const uint8_t findMap[] = { 0xAAu }; + static const uint8_t replaceMap[] = { 0xB8u, 0x0Au }; + static const uint8_t findNonce[] = { 0x0Au, 0x58u, 0x20u }; + static const uint8_t replaceNonce[] = { + 0x0Au, 0x59u, 0x00u, 0x20u + }; + static const uint8_t findComponents[] = { + 0x19u, 0x09u, 0x5Fu, 0x81u + }; + static const uint8_t replaceComponents[] = { + 0x19u, 0x09u, 0x5Fu, 0x98u, 0x01u + }; + static const uint8_t findLifecycle[] = { + 0x19u, 0x09u, 0x5Bu, 0x19u, 0x30u, 0x00u + }; + static const uint8_t replaceLifecycle[] = { + 0x19u, 0x09u, 0x5Bu, 0x1Au, 0x00u, 0x00u, 0x30u, 0x00u + }; + static const uint8_t findClientId[] = { + 0x19u, 0x09u, 0x5Au, 0x20u + }; + static const uint8_t replaceClientId[] = { + 0x19u, 0x09u, 0x5Au, 0x38u, 0x00u + }; + + (void)printf(" [RFC 9783 non-preferred authenticated claims]\n"); + test_eat_psa_nonpreferred_claim_form(key, rng, payload, payloadLen, + findMap, sizeof(findMap), replaceMap, sizeof(replaceMap), + "strict decoder rejects non-preferred claim map length", + "PSA verifier accepts non-preferred claim map length"); + test_eat_psa_nonpreferred_claim_form(key, rng, payload, payloadLen, + findNonce, sizeof(findNonce), replaceNonce, sizeof(replaceNonce), + "strict decoder rejects non-preferred nonce bstr length", + "PSA verifier accepts non-preferred nonce bstr length"); + test_eat_psa_nonpreferred_claim_form(key, rng, payload, payloadLen, + findComponents, sizeof(findComponents), replaceComponents, + sizeof(replaceComponents), + "strict decoder rejects non-preferred component array length", + "PSA verifier accepts non-preferred component array length"); + test_eat_psa_nonpreferred_claim_form(key, rng, payload, payloadLen, + findLifecycle, sizeof(findLifecycle), replaceLifecycle, + sizeof(replaceLifecycle), + "strict decoder rejects non-preferred lifecycle value", + "PSA verifier accepts non-preferred lifecycle value"); + test_eat_psa_nonpreferred_claim_form(key, rng, payload, payloadLen, + findClientId, sizeof(findClientId), replaceClientId, + sizeof(replaceClientId), + "strict decoder rejects non-preferred signed client ID", + "PSA verifier accepts non-preferred signed client ID"); +} +#endif + +static int test_eat_psa_bytes_are_zero(const uint8_t* buf, size_t len) +{ + size_t i; + int ret = 1; + + if (buf == NULL) { + ret = 0; + } + for (i = 0u; (i < len) && (ret != 0); i++) { + if (buf[i] != 0u) { + ret = 0; + } + } + + return ret; +} + +#if defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) +/* Exercise the PSA/EAT envelope pre-parser directly through its public + * verifier entry point. Every failure must leave application-facing claims + * cleared because these inputs are unauthenticated. */ +static void test_eat_psa_expect_envelope_failure(const WOLFCOSE_KEY* key, + const uint8_t* in, size_t inSz, uint8_t* scratch, size_t scratchSz, + int expected, const char* name) +{ + WOLFCOSE_EAT_PSA_TOKEN token; + int ret; + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, in, inSz, kNonce, sizeof(kNonce), + scratch, scratchSz, &token); + TEST_ASSERT(ret == expected && test_eat_psa_token_is_zero(&token) != 0, + name); +} +#endif + +#if defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) +static void test_eat_psa_verify_argument_guards(const WOLFCOSE_KEY* key, + const uint8_t* in, size_t inSz, uint8_t* scratch, size_t scratchSz) +{ + WOLFCOSE_EAT_PSA_TOKEN token; + int ret; + + (void)printf(" [RFC 9783 verifier argument contracts]\n"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(NULL, in, inSz, kNonce, sizeof(kNonce), + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject NULL PSA verification key and clear token output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, NULL, inSz, kNonce, sizeof(kNonce), + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject NULL PSA token input and clear token output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, in, inSz, NULL, sizeof(kNonce), + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject NULL expected PSA nonce and clear token output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, in, inSz, kNonce, sizeof(kNonce), + NULL, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject NULL PSA verification scratch and clear token output"); + + ret = wc_CoseEatPsaToken_Verify(key, in, inSz, kNonce, sizeof(kNonce), + scratch, scratchSz, NULL); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "reject NULL PSA token output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, in, inSz, kNonce, 31u, scratch, + scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject unsupported expected PSA nonce length and clear token output"); + +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_VerifyByUeid(NULL, NULL, in, inSz, kNonce, + sizeof(kNonce), scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject NULL PSA UEID resolver and clear token output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_VerifyByUeid(test_eat_psa_resolve, NULL, NULL, + inSz, kNonce, sizeof(kNonce), scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject NULL UEID-resolver token input and clear token output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_VerifyByUeid(test_eat_psa_resolve, NULL, in, + inSz, NULL, sizeof(kNonce), scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject NULL UEID-resolver nonce and clear token output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_VerifyByUeid(test_eat_psa_resolve, NULL, in, + inSz, kNonce, sizeof(kNonce), NULL, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject NULL UEID-resolver scratch and clear token output"); + + ret = wc_CoseEatPsaToken_VerifyByUeid(test_eat_psa_resolve, NULL, in, + inSz, kNonce, sizeof(kNonce), scratch, scratchSz, NULL); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "reject NULL UEID-resolver token output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_VerifyByUeid(test_eat_psa_resolve, NULL, in, + inSz, kNonce, 31u, scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && + test_eat_psa_token_is_zero(&token) != 0, + "reject unsupported UEID-resolver nonce length and clear token output"); +#endif +} +#endif + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +/* Construct a valid Sign1 whose inner protected-header map uses a + * non-preferred definite-length encoding. This distinguishes propagation of + * the decode option into the inner header parser from accepting only a + * non-preferred outer tag. */ +static void test_eat_psa_nonpreferred_protected_sign1(WOLFCOSE_KEY* key, + WC_RNG* rng) +{ + static const uint8_t protectedHdr[] = { + 0xB8u, 0x01u, 0x01u, 0x26u + }; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t digest[32]; + uint8_t signature[64]; + uint8_t message[256]; + WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_HDR hdr; + const uint8_t* verifiedPayload = NULL; + size_t verifiedPayloadLen = 0u; + size_t structureLen = 0u; + size_t signatureLen = sizeof(signature); + size_t messageLen = 0u; + int ret; + int testRet; + + ret = wolfCose_BuildToBeSignedMaced(WOLFCOSE_CTX_SIGNATURE1, + sizeof(WOLFCOSE_CTX_SIGNATURE1), protectedHdr, sizeof(protectedHdr), + NULL, 0u, NULL, 0u, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &structureLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_Hash(WC_SHA256, scratch, (word32)structureLen, digest, + (word32)sizeof(digest)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EccSignRaw(digest, sizeof(digest), signature, + &signatureLen, 32u, WC_HASH_TYPE_SHA256, rng, key->key.ecc); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncoderInit(&ctx, message, sizeof(message)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTag(&ctx, WOLFCOSE_TAG_SIGN1); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 4u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, protectedHdr, sizeof(protectedHdr)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, 0u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kNonce, sizeof(kNonce)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, signature, signatureLen); + } + if (ret == WOLFCOSE_SUCCESS) { + messageLen = ctx.idx; + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "construct Sign1 with non-preferred protected header"); + if (ret == WOLFCOSE_SUCCESS) { + testRet = wc_CoseSign1_Verify(key, message, messageLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &verifiedPayload, + &verifiedPayloadLen); + TEST_ASSERT(testRet != WOLFCOSE_SUCCESS, + "strict Sign1 rejects non-preferred protected header"); + testRet = wolfCose_Sign1_Verify_ex(key, message, messageLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &verifiedPayload, + &verifiedPayloadLen, WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + verifiedPayloadLen == sizeof(kNonce), + "Sign1 decode-option API accepts non-preferred protected header"); + } + (void)wolfCose_ForceZero(digest, sizeof(digest)); + (void)wolfCose_ForceZero(signature, sizeof(signature)); +} +#endif + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +/* The unprotected map is outside Sig_structure. Replacing its preferred empty + * map header keeps the signature valid while testing decode-flag propagation + * through the unprotected-header parser. */ +static void test_eat_psa_nonpreferred_unprotected_sign1(WOLFCOSE_KEY* key, + WC_RNG* rng) +{ + static const uint8_t expectedPrefix[] = { + 0xD2u, 0x84u, 0x43u, 0xA1u, 0x01u, 0x26u, 0xA0u + }; + static const uint8_t textUnprotected[] = { + 0xA1u, 0x66u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u + }; + static const uint8_t nonPreferredTextUnprotected[] = { + 0xA1u, 0x78u, 0x06u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u + }; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t message[256]; + uint8_t nonPreferred[266]; + WOLFCOSE_HDR hdr; + const uint8_t* verifiedPayload = NULL; + size_t verifiedPayloadLen = 0u; + size_t messageLen = 0u; + size_t nonPreferredLen = 0u; + int ret; + int testRet; + + ret = wc_CoseSign1_Sign(key, WOLFCOSE_ALG_ES256, NULL, 0u, kNonce, + sizeof(kNonce), NULL, 0u, NULL, 0u, scratch, sizeof(scratch), message, + sizeof(message), &messageLen, rng); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "construct Sign1 with preferred empty unprotected header"); + if ((ret == WOLFCOSE_SUCCESS) && + ((messageLen < sizeof(expectedPrefix)) || + (memcmp(message, expectedPrefix, sizeof(expectedPrefix)) != 0))) { + TEST_ASSERT(0, "expected deterministic Sign1 header layout"); + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memcpy(nonPreferred, message, sizeof(expectedPrefix) - 1u); + nonPreferred[sizeof(expectedPrefix) - 1u] = 0xB8u; + nonPreferred[sizeof(expectedPrefix)] = 0x00u; + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) + 1u], + &message[sizeof(expectedPrefix)], + messageLen - sizeof(expectedPrefix)); + nonPreferredLen = messageLen + 1u; + + testRet = wc_CoseSign1_Verify(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen); + TEST_ASSERT(testRet != WOLFCOSE_SUCCESS, + "strict Sign1 rejects non-preferred unprotected header"); + + testRet = wolfCose_Sign1_Verify_ex(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + hdr.alg == WOLFCOSE_ALG_ES256 && + verifiedPayloadLen == sizeof(kNonce) && + memcmp(verifiedPayload, kNonce, sizeof(kNonce)) == 0, + "Sign1 decode-option API accepts non-preferred unprotected header"); + + (void)memcpy(nonPreferred, message, sizeof(expectedPrefix) - 1u); + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) - 1u], + textUnprotected, sizeof(textUnprotected)); + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) - 1u + + sizeof(textUnprotected)], &message[sizeof(expectedPrefix)], + messageLen - sizeof(expectedPrefix)); + nonPreferredLen = messageLen - 1u + sizeof(textUnprotected); + testRet = wc_CoseSign1_Verify(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + verifiedPayloadLen == sizeof(kNonce), + "Sign1 accepts authenticated preferred tstr header extension"); + + (void)memcpy(nonPreferred, message, sizeof(expectedPrefix) - 1u); + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) - 1u], + nonPreferredTextUnprotected, sizeof(nonPreferredTextUnprotected)); + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) - 1u + + sizeof(nonPreferredTextUnprotected)], &message[sizeof(expectedPrefix)], + messageLen - sizeof(expectedPrefix)); + nonPreferredLen = messageLen - 1u + sizeof(nonPreferredTextUnprotected); + testRet = wc_CoseSign1_Verify(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen); + TEST_ASSERT(testRet != WOLFCOSE_SUCCESS, + "strict Sign1 rejects non-preferred tstr header extension"); + testRet = wolfCose_Sign1_Verify_ex(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + verifiedPayloadLen == sizeof(kNonce), + "Sign1 decode-option API accepts non-preferred tstr header extension"); + } +} +#endif + +#if defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256) +/* Same inner-header propagation test for Mac0. The construction is manually + * MACed so its protected bytes stay intentionally non-preferred. */ +static void test_eat_psa_nonpreferred_protected_mac0(const WOLFCOSE_KEY* key) +{ + static const uint8_t protectedHdr[] = { + 0xB8u, 0x01u, 0x01u, 0x05u + }; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tag[32]; + uint8_t message[256]; + WOLFCOSE_CBOR_CTX ctx; + WOLFCOSE_HDR hdr; + const uint8_t* verifiedPayload = NULL; + size_t verifiedPayloadLen = 0u; + size_t structureLen = 0u; + size_t messageLen = 0u; + Hmac hmac; + int hmacInited = 0; + int ret; + int testRet; + + ret = wolfCose_BuildToBeSignedMaced(WOLFCOSE_CTX_MAC0, + sizeof(WOLFCOSE_CTX_MAC0), protectedHdr, sizeof(protectedHdr), NULL, + 0u, NULL, 0u, kNonce, sizeof(kNonce), scratch, sizeof(scratch), + &structureLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + else { + hmacInited = 1; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_HmacSetKey(&hmac, WC_SHA256, key->key.symm.key, + (word32)key->key.symm.keyLen); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_HmacUpdate(&hmac, scratch, (word32)structureLen); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_HmacFinal(&hmac, tag); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncoderInit(&ctx, message, sizeof(message)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTag(&ctx, WOLFCOSE_TAG_MAC0); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 4u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, protectedHdr, sizeof(protectedHdr)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, 0u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kNonce, sizeof(kNonce)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, tag, sizeof(tag)); + } + if (ret == WOLFCOSE_SUCCESS) { + messageLen = ctx.idx; + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "construct Mac0 with non-preferred protected header"); + if (ret == WOLFCOSE_SUCCESS) { + testRet = wc_CoseMac0_Verify(key, message, messageLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &verifiedPayload, + &verifiedPayloadLen); + TEST_ASSERT(testRet != WOLFCOSE_SUCCESS, + "strict Mac0 rejects non-preferred protected header"); + testRet = wolfCose_Mac0_Verify_ex(key, message, messageLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &verifiedPayload, + &verifiedPayloadLen, WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + verifiedPayloadLen == sizeof(kNonce), + "Mac0 decode-option API accepts non-preferred protected header"); + } + if (hmacInited != 0) { + (void)wc_HmacFree(&hmac); + } + (void)wolfCose_ForceZero(tag, sizeof(tag)); +} +#endif + +#if defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_MAC0_VERIFY) && \ + defined(WOLFCOSE_HAVE_HMAC256) +/* This mirrors the Sign1 fixture above for the Mac0 unprotected-header path. */ +static void test_eat_psa_nonpreferred_unprotected_mac0(const WOLFCOSE_KEY* key) +{ + static const uint8_t expectedPrefix[] = { + 0xD1u, 0x84u, 0x43u, 0xA1u, 0x01u, 0x05u, 0xA0u + }; + static const uint8_t textUnprotected[] = { + 0xA1u, 0x66u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u + }; + static const uint8_t nonPreferredTextUnprotected[] = { + 0xA1u, 0x78u, 0x06u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u + }; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t message[256]; + uint8_t nonPreferred[266]; + WOLFCOSE_HDR hdr; + const uint8_t* verifiedPayload = NULL; + size_t verifiedPayloadLen = 0u; + size_t messageLen = 0u; + size_t nonPreferredLen = 0u; + int ret; + int testRet; + + ret = wc_CoseMac0_Create(key, WOLFCOSE_ALG_HMAC_256_256, NULL, 0u, + kNonce, sizeof(kNonce), NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + message, sizeof(message), &messageLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "construct Mac0 with preferred empty unprotected header"); + if ((ret == WOLFCOSE_SUCCESS) && + ((messageLen < sizeof(expectedPrefix)) || + (memcmp(message, expectedPrefix, sizeof(expectedPrefix)) != 0))) { + TEST_ASSERT(0, "expected deterministic Mac0 header layout"); + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memcpy(nonPreferred, message, sizeof(expectedPrefix) - 1u); + nonPreferred[sizeof(expectedPrefix) - 1u] = 0xB8u; + nonPreferred[sizeof(expectedPrefix)] = 0x00u; + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) + 1u], + &message[sizeof(expectedPrefix)], + messageLen - sizeof(expectedPrefix)); + nonPreferredLen = messageLen + 1u; + + testRet = wc_CoseMac0_Verify(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen); + TEST_ASSERT(testRet != WOLFCOSE_SUCCESS, + "strict Mac0 rejects non-preferred unprotected header"); + + testRet = wolfCose_Mac0_Verify_ex(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + hdr.alg == WOLFCOSE_ALG_HMAC_256_256 && + verifiedPayloadLen == sizeof(kNonce) && + memcmp(verifiedPayload, kNonce, sizeof(kNonce)) == 0, + "Mac0 decode-option API accepts non-preferred unprotected header"); + + (void)memcpy(nonPreferred, message, sizeof(expectedPrefix) - 1u); + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) - 1u], + textUnprotected, sizeof(textUnprotected)); + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) - 1u + + sizeof(textUnprotected)], &message[sizeof(expectedPrefix)], + messageLen - sizeof(expectedPrefix)); + nonPreferredLen = messageLen - 1u + sizeof(textUnprotected); + testRet = wc_CoseMac0_Verify(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + verifiedPayloadLen == sizeof(kNonce), + "Mac0 accepts authenticated preferred tstr header extension"); + + (void)memcpy(nonPreferred, message, sizeof(expectedPrefix) - 1u); + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) - 1u], + nonPreferredTextUnprotected, sizeof(nonPreferredTextUnprotected)); + (void)memcpy(&nonPreferred[sizeof(expectedPrefix) - 1u + + sizeof(nonPreferredTextUnprotected)], &message[sizeof(expectedPrefix)], + messageLen - sizeof(expectedPrefix)); + nonPreferredLen = messageLen - 1u + sizeof(nonPreferredTextUnprotected); + testRet = wc_CoseMac0_Verify(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen); + TEST_ASSERT(testRet != WOLFCOSE_SUCCESS, + "strict Mac0 rejects non-preferred tstr header extension"); + testRet = wolfCose_Mac0_Verify_ex(key, nonPreferred, nonPreferredLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &verifiedPayload, &verifiedPayloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + verifiedPayloadLen == sizeof(kNonce), + "Mac0 decode-option API accepts non-preferred tstr header extension"); + } +} +#endif + +#if defined(WOLFCOSE_EXT_SIGN) && defined(WOLFCOSE_SIGN1_SIGN) && \ + defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) +typedef struct EAT_PSA_EXT_SIGN_CTX { + WC_RNG* rng; + ecc_key* key; + int called; + int fail; +} EAT_PSA_EXT_SIGN_CTX; + +static int test_eat_psa_ext_sign_cb(void* cbCtx, int32_t alg, + const uint8_t* tbs, size_t tbsSz, uint8_t* sig, size_t sigSz, + size_t* sigLen) +{ + EAT_PSA_EXT_SIGN_CTX* ctx = (EAT_PSA_EXT_SIGN_CTX*)cbCtx; + enum wc_HashType hashType = WC_HASH_TYPE_NONE; + int ret; + + if ((ctx == NULL) || (sigLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ctx->called++; + if (ctx->fail != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + else { + *sigLen = sigSz; + ret = wolfCose_AlgToHashType(alg, &hashType); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EccSignRaw(tbs, tbsSz, sig, sigLen, 32u, + hashType, ctx->rng, ctx->key); + } + } + } + + return ret; +} +#endif + +#if (defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256)) || \ + (defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_MAC0_VERIFY) && \ + defined(WOLFCOSE_HAVE_HMAC256)) +static int test_eat_psa_encode_legacy(const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* out, size_t outSz, size_t* outLen, int omitProfile, + int noMeasurements, int incompatibleProfile) +{ + int ret; + WOLFCOSE_CBOR_CTX ctx; + static const uint8_t profile[] = WOLFCOSE_EAT_PSA_PROFILE_LEGACY; + static const uint8_t wrongProfile[] = "not-a-legacy-profile"; + const uint8_t* profileText = (incompatibleProfile != 0) ? wrongProfile : + profile; + size_t profileLen = (incompatibleProfile != 0) ? + (sizeof(wrongProfile) - 1u) : (sizeof(profile) - 1u); + size_t count = (omitProfile != 0) ? 7u : 8u; + + if (claims->verificationServiceIndicator.data != NULL) { + count++; + } + + ret = wc_CBOR_EncoderInit(&ctx, out, outSz); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, count); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75008); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->nonce.data, claims->nonce.len); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75009); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->ueid.data, claims->ueid.len); + } + if ((ret == WOLFCOSE_SUCCESS) && (omitProfile == 0)) { + ret = wc_CBOR_EncodeInt(&ctx, -75000); + } + if ((ret == WOLFCOSE_SUCCESS) && (omitProfile == 0)) { + ret = wc_CBOR_EncodeTstr(&ctx, profileText, profileLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75001); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, claims->clientId); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75002); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, claims->lifecycle); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75003); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->implementationId.data, + claims->implementationId.len); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75004); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->bootSeed.data, + claims->bootSeed.len); + } + if ((ret == WOLFCOSE_SUCCESS) && + (claims->verificationServiceIndicator.data != NULL)) { + ret = wc_CBOR_EncodeInt(&ctx, -75010); + } + if ((ret == WOLFCOSE_SUCCESS) && + (claims->verificationServiceIndicator.data != NULL)) { + ret = wc_CBOR_EncodeTstr(&ctx, + claims->verificationServiceIndicator.data, + claims->verificationServiceIndicator.len); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements != 0)) { + ret = wc_CBOR_EncodeInt(&ctx, -75007); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements != 0)) { + ret = wc_CBOR_EncodeUint(&ctx, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements == 0)) { + ret = wc_CBOR_EncodeInt(&ctx, -75006); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements == 0)) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements == 0)) { + ret = wc_CBOR_EncodeMapStart(&ctx, 3u); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements == 0)) { + ret = wc_CBOR_EncodeUint(&ctx, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements == 0)) { + ret = wc_CBOR_EncodeTstr(&ctx, claims->components[0].measurementType.data, + claims->components[0].measurementType.len); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements == 0)) { + ret = wc_CBOR_EncodeUint(&ctx, 2u); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements == 0)) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->components[0].measurementValue.data, + claims->components[0].measurementValue.len); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements == 0)) { + ret = wc_CBOR_EncodeUint(&ctx, 5u); + } + if ((ret == WOLFCOSE_SUCCESS) && (noMeasurements == 0)) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->components[0].signerId.data, + claims->components[0].signerId.len); + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = ctx.idx; + } + + return ret; +} +#endif + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +/* Legacy-only malformed fixtures: all structures are signed before the public + * verifier sees them, so these exercise the receiver rather than issuer-side + * validation. */ +static int test_eat_psa_encode_legacy_raw( + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + int includeProfile, int includeComponents, int includeNoMeasurements, + uint64_t noMeasurementsValue, const WOLFCOSE_EAT_PSA_SPAN* certification, + uint8_t* out, size_t outSz, size_t* outLen) +{ + static const uint8_t profile[] = WOLFCOSE_EAT_PSA_PROFILE_LEGACY; + WOLFCOSE_CBOR_CTX ctx; + size_t count = 6u; + int ret; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((claims == NULL) || (out == NULL) || (outLen == NULL) || + (claims->components == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + count += (includeProfile != 0) ? 1u : 0u; + count += (includeComponents != 0) ? 1u : 0u; + count += (includeNoMeasurements != 0) ? 1u : 0u; + count += ((certification != NULL) && + (certification->data != NULL)) ? 1u : 0u; + ret = wc_CBOR_EncoderInit(&ctx, out, outSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, count); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75008); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->nonce.data, claims->nonce.len); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75009); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->ueid.data, claims->ueid.len); + } + if ((ret == WOLFCOSE_SUCCESS) && (includeProfile != 0)) { + ret = wc_CBOR_EncodeInt(&ctx, -75000); + } + if ((ret == WOLFCOSE_SUCCESS) && (includeProfile != 0)) { + ret = wc_CBOR_EncodeTstr(&ctx, profile, sizeof(profile) - 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75001); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, claims->clientId); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75002); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, claims->lifecycle); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75003); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->implementationId.data, + claims->implementationId.len); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75004); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, claims->bootSeed.data, + claims->bootSeed.len); + } + if ((ret == WOLFCOSE_SUCCESS) && (certification != NULL) && + (certification->data != NULL)) { + ret = wc_CBOR_EncodeInt(&ctx, -75005); + } + if ((ret == WOLFCOSE_SUCCESS) && (certification != NULL) && + (certification->data != NULL)) { + ret = wc_CBOR_EncodeTstr(&ctx, certification->data, + certification->len); + } + if ((ret == WOLFCOSE_SUCCESS) && (includeComponents != 0)) { + ret = wc_CBOR_EncodeInt(&ctx, -75006); + } + if ((ret == WOLFCOSE_SUCCESS) && (includeComponents != 0)) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 1u); + } + if ((ret == WOLFCOSE_SUCCESS) && (includeComponents != 0)) { + ret = test_eat_psa_encode_raw_component_ex(&ctx, claims->components, + 0u); + } + if ((ret == WOLFCOSE_SUCCESS) && (includeNoMeasurements != 0)) { + ret = wc_CBOR_EncodeInt(&ctx, -75007); + } + if ((ret == WOLFCOSE_SUCCESS) && (includeNoMeasurements != 0)) { + ret = wc_CBOR_EncodeUint(&ctx, noMeasurementsValue); + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = ctx.idx; + } + + return ret; +} + +static int test_eat_psa_verify_legacy_raw(WOLFCOSE_KEY* key, WC_RNG* rng, + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + int includeProfile, int includeComponents, int includeNoMeasurements, + uint64_t noMeasurementsValue, const WOLFCOSE_EAT_PSA_SPAN* certification, + uint8_t* scratch, size_t scratchSz, WOLFCOSE_EAT_PSA_TOKEN* token) +{ + static uint8_t payload[1024]; + static uint8_t signedToken[1536]; + size_t payloadLen = 0u; + size_t signedTokenLen = 0u; + int ret; + + if (token == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = test_eat_psa_encode_legacy_raw(claims, includeProfile, + includeComponents, includeNoMeasurements, noMeasurementsValue, + certification, payload, sizeof(payload), &payloadLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = test_eat_psa_sign1_payload(key, rng, payload, payloadLen, + scratch, scratchSz, signedToken, sizeof(signedToken), + &signedTokenLen); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(token, 0xA5, sizeof(*token)); + ret = wc_CoseEatPsaToken_Verify(key, signedToken, signedTokenLen, + kNonce, sizeof(kNonce), scratch, scratchSz, token); + } + + return ret; +} +#endif + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +static void test_eat_psa_claim_rejections(WOLFCOSE_KEY* key, WC_RNG* rng, + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* scratch, size_t scratchSz) +{ + static const uint8_t currentProfile[] = WOLFCOSE_EAT_PSA_PROFILE_TFM; + static const uint8_t wrongProfile[] = "not-a-psa-profile"; + static WOLFCOSE_EAT_PSA_COMPONENT overComponents[ + WOLFCOSE_EAT_PSA_MAX_COMPONENTS + 1u]; + static uint8_t oversizedScratch[4096]; + WOLFCOSE_EAT_PSA_CLAIMS caseClaims; + WOLFCOSE_EAT_PSA_COMPONENT caseComponent; + WOLFCOSE_EAT_PSA_TOKEN token; + EAT_PSA_COMPONENT_CTX componentCtx; + uint8_t badUeid[sizeof(kUeid)]; + uint8_t invalidUtf8Payload[1024]; + uint8_t invalidUtf8Token[1536]; + size_t invalidUtf8PayloadLen = 0u; + size_t invalidUtf8TokenLen = 0u; + size_t i; + int found; + int ret; + int testRet; + + (void)printf(" [RFC 9783 authenticated claim rejection matrix]\n"); + + ret = test_eat_psa_verify_raw_current(key, rng, claims, currentProfile, + sizeof(currentProfile) - 1u, TEST_EAT_PSA_OMIT_COMPONENTS, 0u, 0, + scratch, scratchSz); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject signed token missing required current claim"); + + caseClaims = *claims; + caseClaims.clientId = 0; + ret = test_eat_psa_verify_raw_current(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, scratch, + scratchSz); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject signed zero current client ID"); + + caseClaims = *claims; + caseClaims.nonce.len = 31u; + ret = test_eat_psa_verify_raw_current(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, scratch, + scratchSz); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject signed invalid current nonce length"); + + (void)memcpy(badUeid, kUeid, sizeof(badUeid)); + badUeid[0] = 0x02u; + caseClaims = *claims; + caseClaims.ueid.data = badUeid; + ret = test_eat_psa_verify_raw_current(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, scratch, + scratchSz); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject signed malformed UEID"); + + caseClaims = *claims; + caseClaims.implementationId.len = 31u; + ret = test_eat_psa_verify_raw_current(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, scratch, + scratchSz); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject signed malformed implementation ID"); + + caseClaims = *claims; + caseClaims.bootSeed.len = 7u; + ret = test_eat_psa_verify_raw_current(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, scratch, + scratchSz); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject signed current boot seed below profile minimum"); + + caseClaims = *claims; + caseClaims.bootSeed.data = kOversizedBootSeed; + caseClaims.bootSeed.len = sizeof(kOversizedBootSeed); + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, 0u, + caseClaims.componentCount, scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject signed current boot seed above profile maximum and clear output"); + + caseClaims = *claims; + caseClaims.certificationReference.len--; + ret = test_eat_psa_verify_raw_current(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, scratch, + scratchSz); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject signed malformed current certification reference"); + + caseClaims = *claims; + caseClaims.certificationReference.data = kInvalidCurrentCertRef; + caseClaims.certificationReference.len = + sizeof(kInvalidCurrentCertRef) - 1u; + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, 0u, + caseClaims.componentCount, scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject signed current certification reference separator"); + + ret = test_eat_psa_verify_raw_current(key, rng, claims, wrongProfile, + sizeof(wrongProfile) - 1u, 0u, 0u, 0, scratch, scratchSz); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_PROFILE, + "reject signed mismatched current profile"); + + ret = test_eat_psa_verify_raw_current(key, rng, claims, currentProfile, + sizeof(currentProfile) - 1u, 0u, 0u, 1, scratch, scratchSz); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_PROFILE, + "reject signed mixed current and legacy labels"); + + ret = test_eat_psa_verify_raw_current(key, rng, claims, currentProfile, + sizeof(currentProfile) - 1u, 0u, 17u, 0, scratch, scratchSz); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "accept signed unknown current claims beyond COSE map limit"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, + TEST_EAT_PSA_UNKNOWN_TEXT, 0u, 0, + TEST_EAT_PSA_COMPONENT_UNKNOWN_TEXT, claims->componentCount, scratch, + scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && token.componentCount == 1u, + "accept unknown text labels in PSA claims and components"); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&componentCtx, 0, sizeof(componentCtx)); + ret = wc_CoseEatPsaToken_ForEachComponent(&token, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && componentCtx.count == 1 && + componentCtx.valid != 0, + "iterate known component after unknown text labels"); + } + + /* Build a valid authenticated claim map, then corrupt a text-string byte + * after CBOR encoding. The sender helper correctly refuses this text; + * the receiver must also reject a malicious peer token. */ + testRet = test_eat_psa_encode_current_raw_ex(claims, currentProfile, + sizeof(currentProfile) - 1u, 0u, 0u, 0, 0u, + claims->componentCount, invalidUtf8Payload, + sizeof(invalidUtf8Payload), &invalidUtf8PayloadLen); + if (testRet == WOLFCOSE_SUCCESS) { + found = 0; + for (i = 0u; (i + (sizeof(currentProfile) - 1u)) <= + invalidUtf8PayloadLen; i++) { + if (memcmp(&invalidUtf8Payload[i], currentProfile, + sizeof(currentProfile) - 1u) == 0) { + invalidUtf8Payload[i] = 0xFFu; + found = 1; + break; + } + } + if (found == 0) { + testRet = WOLFCOSE_E_INVALID_ARG; + } + } + if (testRet == WOLFCOSE_SUCCESS) { + testRet = test_eat_psa_sign1_payload(key, rng, invalidUtf8Payload, + invalidUtf8PayloadLen, scratch, scratchSz, invalidUtf8Token, + sizeof(invalidUtf8Token), &invalidUtf8TokenLen); + } + if (testRet == WOLFCOSE_SUCCESS) { + (void)memset(&token, 0xA5, sizeof(token)); + testRet = wc_CoseEatPsaToken_Verify(key, invalidUtf8Token, + invalidUtf8TokenLen, kNonce, sizeof(kNonce), scratch, scratchSz, + &token); + } + TEST_ASSERT(testRet == WOLFCOSE_E_CBOR_MALFORMED && + test_eat_psa_token_is_zero(&token) != 0, + "reject signed invalid UTF-8 claim and clear output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, + TEST_EAT_PSA_DUP_UNKNOWN_CLAIM, 2u, 0, 0u, + claims->componentCount, scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject duplicate unknown numeric PSA claim and clear output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, + TEST_EAT_PSA_DUP_UNKNOWN_TEXT, 0u, 0, 0u, + claims->componentCount, scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject duplicate unknown text PSA claim and clear output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, + TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_TEXT, claims->componentCount, + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject duplicate unknown component text label and clear output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, + TEST_EAT_PSA_COMPONENT_DUP_UNKNOWN_CLAIM, claims->componentCount, + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject duplicate unknown numeric component claim and clear output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, + TEST_EAT_PSA_COMPONENT_DUP_VALUE, 1u, scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject duplicate authenticated component field and clear output"); + + caseClaims = *claims; + caseComponent = claims->components[0]; + caseClaims.components = &caseComponent; + caseComponent.measurementValue.len = 31u; + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, 0u, 1u, + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject authenticated component measurement hash length"); + + caseComponent = claims->components[0]; + caseComponent.signerId.len = 31u; + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, 0u, 1u, + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject authenticated component signer hash length"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, + TEST_EAT_PSA_COMPONENT_OMIT_VALUE, 1u, scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject component missing measurement value and clear output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, + TEST_EAT_PSA_COMPONENT_OMIT_SIGNER, 1u, scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject component missing signer ID and clear output"); + + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, 0u, 0u, + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject empty authenticated component array and clear output"); + + caseClaims = *claims; + for (i = 0u; i < (WOLFCOSE_EAT_PSA_MAX_COMPONENTS + 1u); i++) { + overComponents[i] = claims->components[0]; + } + caseClaims.components = overComponents; + (void)memset(&token, 0xA5, sizeof(token)); + ret = test_eat_psa_verify_raw_current_ex(key, rng, &caseClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, 0u, + WOLFCOSE_EAT_PSA_MAX_COMPONENTS + 1u, oversizedScratch, + sizeof(oversizedScratch), &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject component array beyond configured maximum and clear output"); +} +#endif + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +static void test_eat_psa_expect_issue_claim_failure( + const WOLFCOSE_EAT_PSA_CLAIMS* claims, uint8_t* out, size_t outSz, + const char* name) +{ + size_t outLen = 17u; + int ret; + + ret = wc_CoseEatPsaToken_EncodeClaims(claims, out, outSz, &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && outLen == 0u, name); +} + +#define TEST_EAT_PSA_INPUT_SPAN_COUNT 11u + +static WOLFCOSE_EAT_PSA_SPAN* test_eat_psa_input_span( + WOLFCOSE_EAT_PSA_CLAIMS* claims, + WOLFCOSE_EAT_PSA_COMPONENT* component, size_t index) +{ + WOLFCOSE_EAT_PSA_SPAN* span = NULL; + + switch (index) { + case 0u: + span = &claims->nonce; + break; + case 1u: + span = &claims->ueid; + break; + case 2u: + span = &claims->implementationId; + break; + case 3u: + span = &claims->bootSeed; + break; + case 4u: + span = &claims->certificationReference; + break; + case 5u: + span = &claims->verificationServiceIndicator; + break; + case 6u: + span = &component->measurementType; + break; + case 7u: + span = &component->measurementValue; + break; + case 8u: + span = &component->version; + break; + case 9u: + span = &component->signerId; + break; + case 10u: + span = &component->measurementDesc; + break; + default: + break; + } + + return span; +} + +static int test_eat_psa_alias_input_span(WOLFCOSE_EAT_PSA_CLAIMS* claims, + WOLFCOSE_EAT_PSA_COMPONENT* component, size_t index, + uint8_t* storage, size_t storageSz) +{ + WOLFCOSE_EAT_PSA_SPAN* span; + int ret = 0; + + test_eat_psa_claims(claims, component); + span = test_eat_psa_input_span(claims, component, index); + if ((span != NULL) && (span->data != NULL) && (span->len > 1u) && + (span->len <= storageSz)) { + (void)memcpy(storage, span->data, span->len); + span->data = storage; + ret = 1; + } + + return ret; +} + +static int test_eat_psa_encode_overlap_rejected( + const WOLFCOSE_EAT_PSA_CLAIMS* claims, uint8_t* out, size_t outSz) +{ + size_t outLen = 17u; + int ret; + + ret = wc_CoseEatPsaToken_EncodeClaims(claims, out, outSz, &outLen); + return ((ret == WOLFCOSE_E_INVALID_ARG) && (outLen == 0u)) ? 1 : 0; +} + +static int test_eat_psa_sign1_overlap_rejected(WOLFCOSE_KEY* key, + WC_RNG* rng, const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* claimsBuf, size_t claimsBufSz, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz) +{ + size_t outLen = 17u; + int ret; + + (void)memset(out, 0xA5, outSz); + ret = wc_CoseEatPsaToken_CreateSign1(key, WOLFCOSE_ALG_ES256, claims, + claimsBuf, claimsBufSz, scratch, scratchSz, out, outSz, &outLen, rng); + return ((ret == WOLFCOSE_E_INVALID_ARG) && (outLen == 0u) && + (test_eat_psa_bytes_are_zero(out, outSz) != 0)) ? 1 : 0; +} + +static void test_eat_psa_expect_sign1_overlap(WOLFCOSE_KEY* key, + WC_RNG* rng, const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* claimsBuf, size_t claimsBufSz, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, const char* name) +{ + TEST_ASSERT(test_eat_psa_sign1_overlap_rejected(key, rng, claims, + claimsBuf, claimsBufSz, scratch, scratchSz, out, + outSz) != 0, + name); +} + +static void test_eat_psa_issue_boundaries(WOLFCOSE_KEY* key, WC_RNG* rng) +{ + static const uint8_t invalidUtf8[] = { 0xFFu }; + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_COMPONENT components[WOLFCOSE_EAT_PSA_MAX_COMPONENTS + 1u]; + union { + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + uint8_t bytes[2048]; + } inputStorage; + uint8_t badUeid[sizeof(kUeid)]; + uint8_t claimsBuf[8192]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t out[1024]; + uint8_t overlap[2048]; + size_t outLen = 17u; + size_t i; + int ret; + int spanChecks; + + (void)printf(" [RFC 9783 issuer boundary contracts]\n"); + test_eat_psa_claims(&claims, &component); + + ret = wc_CoseEatPsaToken_EncodeClaims(NULL, claimsBuf, + sizeof(claimsBuf), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && outLen == 0u, + "reject NULL claims and clear encoded length"); + + outLen = 17u; + ret = wc_CoseEatPsaToken_EncodeClaims(&claims, NULL, 0u, &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && outLen == 0u, + "reject NULL claims output and clear encoded length"); + + ret = wc_CoseEatPsaToken_EncodeClaims(&claims, claimsBuf, + sizeof(claimsBuf), NULL); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "reject NULL encoded-length output"); + + outLen = 17u; + ret = wc_CoseEatPsaToken_EncodeClaims(&claims, claimsBuf, 1u, &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL && outLen == 0u, + "reject one-byte-short claims output"); + + test_eat_psa_claims(&claims, &component); + inputStorage.claims = claims; + TEST_ASSERT(test_eat_psa_encode_overlap_rejected(&inputStorage.claims, + inputStorage.bytes, sizeof(inputStorage.bytes)) != 0, + "direct encoder rejects exact claims-structure overlap"); + test_eat_psa_claims(&claims, &component); + inputStorage.claims = claims; + TEST_ASSERT(test_eat_psa_encode_overlap_rejected(&inputStorage.claims, + &inputStorage.bytes[sizeof(inputStorage.claims) / 2u], + sizeof(inputStorage.bytes) - + (sizeof(inputStorage.claims) / 2u)) != 0, + "direct encoder rejects partial claims-structure overlap"); + test_eat_psa_claims(&claims, &component); + inputStorage.claims = claims; + TEST_ASSERT(test_eat_psa_sign1_overlap_rejected(key, rng, + &inputStorage.claims, inputStorage.bytes, + sizeof(inputStorage.bytes), scratch, sizeof(scratch), out, + sizeof(out)) != 0, + "Sign1 issuer rejects exact claims-structure overlap"); + test_eat_psa_claims(&claims, &component); + inputStorage.claims = claims; + TEST_ASSERT(test_eat_psa_sign1_overlap_rejected(key, rng, + &inputStorage.claims, + &inputStorage.bytes[sizeof(inputStorage.claims) / 2u], + sizeof(inputStorage.bytes) - + (sizeof(inputStorage.claims) / 2u), + scratch, sizeof(scratch), out, sizeof(out)) != 0, + "Sign1 issuer rejects partial claims-structure overlap"); + + test_eat_psa_claims(&claims, &component); + inputStorage.component = component; + claims.components = &inputStorage.component; + TEST_ASSERT(test_eat_psa_encode_overlap_rejected(&claims, + inputStorage.bytes, sizeof(inputStorage.bytes)) != 0, + "direct encoder rejects exact component-array overlap"); + test_eat_psa_claims(&claims, &component); + inputStorage.component = component; + claims.components = &inputStorage.component; + TEST_ASSERT(test_eat_psa_encode_overlap_rejected(&claims, + &inputStorage.bytes[sizeof(inputStorage.component) / 2u], + sizeof(inputStorage.bytes) - + (sizeof(inputStorage.component) / 2u)) != 0, + "direct encoder rejects partial component-array overlap"); + test_eat_psa_claims(&claims, &component); + inputStorage.component = component; + claims.components = &inputStorage.component; + TEST_ASSERT(test_eat_psa_sign1_overlap_rejected(key, rng, &claims, + inputStorage.bytes, sizeof(inputStorage.bytes), scratch, + sizeof(scratch), out, sizeof(out)) != 0, + "Sign1 issuer rejects exact component-array overlap"); + test_eat_psa_claims(&claims, &component); + inputStorage.component = component; + claims.components = &inputStorage.component; + TEST_ASSERT(test_eat_psa_sign1_overlap_rejected(key, rng, &claims, + &inputStorage.bytes[sizeof(inputStorage.component) / 2u], + sizeof(inputStorage.bytes) - + (sizeof(inputStorage.component) / 2u), + scratch, sizeof(scratch), out, sizeof(out)) != 0, + "Sign1 issuer rejects partial component-array overlap"); + + spanChecks = 1; + for (i = 0u; i < TEST_EAT_PSA_INPUT_SPAN_COUNT; i++) { + if ((test_eat_psa_alias_input_span(&claims, &component, i, + inputStorage.bytes, sizeof(inputStorage.bytes)) == 0) || + (test_eat_psa_encode_overlap_rejected(&claims, + inputStorage.bytes, sizeof(inputStorage.bytes)) == 0)) { + spanChecks = 0; + } + if ((test_eat_psa_alias_input_span(&claims, &component, i, + inputStorage.bytes, sizeof(inputStorage.bytes)) == 0) || + (test_eat_psa_encode_overlap_rejected(&claims, + &inputStorage.bytes[1], sizeof(inputStorage.bytes) - 1u) == + 0)) { + spanChecks = 0; + } + if ((test_eat_psa_alias_input_span(&claims, &component, i, + inputStorage.bytes, sizeof(inputStorage.bytes)) == 0) || + (test_eat_psa_sign1_overlap_rejected(key, rng, &claims, + inputStorage.bytes, sizeof(inputStorage.bytes), scratch, + sizeof(scratch), out, sizeof(out)) == 0)) { + spanChecks = 0; + } + if ((test_eat_psa_alias_input_span(&claims, &component, i, + inputStorage.bytes, sizeof(inputStorage.bytes)) == 0) || + (test_eat_psa_sign1_overlap_rejected(key, rng, &claims, + &inputStorage.bytes[1], sizeof(inputStorage.bytes) - 1u, + scratch, sizeof(scratch), out, sizeof(out)) == 0)) { + spanChecks = 0; + } + } + TEST_ASSERT(spanChecks != 0, + "direct encoder and Sign1 reject exact/partial overlap for every input span"); + + test_eat_psa_claims(&claims, &component); + claims.nonce.data = NULL; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer nonce with NULL data"); + + test_eat_psa_claims(&claims, &component); + claims.nonce.len = 31u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer nonce with unsupported length"); + + test_eat_psa_claims(&claims, &component); + claims.ueid.data = NULL; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer UEID with NULL data"); + + test_eat_psa_claims(&claims, &component); + claims.ueid.len--; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer UEID with wrong length"); + + (void)memcpy(badUeid, kUeid, sizeof(badUeid)); + badUeid[0] = 0x02u; + test_eat_psa_claims(&claims, &component); + claims.ueid.data = badUeid; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer UEID with wrong type byte"); + + test_eat_psa_claims(&claims, &component); + claims.implementationId.data = NULL; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer implementation ID with NULL data"); + + test_eat_psa_claims(&claims, &component); + claims.implementationId.len--; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer implementation ID with wrong length"); + + test_eat_psa_claims(&claims, &component); + claims.clientId = 0; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject zero issuer client ID"); + + test_eat_psa_claims(&claims, &component); + claims.lifecycle = 0x0100u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer lifecycle outside PSA state ranges"); + + test_eat_psa_claims(&claims, &component); + claims.components = NULL; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer NULL component array"); + + test_eat_psa_claims(&claims, &component); + claims.bootSeed.data = NULL; + claims.bootSeed.len = 8u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer optional boot seed with NULL data"); + + test_eat_psa_claims(&claims, &component); + claims.bootSeed.len = 7u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer boot seed below profile minimum"); + + test_eat_psa_claims(&claims, &component); + claims.bootSeed.data = kOversizedBootSeed; + claims.bootSeed.len = sizeof(kOversizedBootSeed); + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer boot seed above profile maximum"); + + test_eat_psa_claims(&claims, &component); + claims.certificationReference.data = NULL; + claims.certificationReference.len = 1u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), + "reject issuer certification reference with NULL data"); + + test_eat_psa_claims(&claims, &component); + claims.certificationReference.len--; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject malformed issuer certification reference"); + + test_eat_psa_claims(&claims, &component); + claims.certificationReference.data = kInvalidCurrentCertRef; + claims.certificationReference.len = sizeof(kInvalidCurrentCertRef) - 1u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), + "reject issuer certification reference with invalid separator"); + + test_eat_psa_claims(&claims, &component); + claims.verificationServiceIndicator.data = NULL; + claims.verificationServiceIndicator.len = 1u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer VSI with NULL data"); + + test_eat_psa_claims(&claims, &component); + component.measurementValue.data = NULL; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer component hash with NULL data"); + + test_eat_psa_claims(&claims, &component); + component.measurementValue.len = 31u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer component hash with wrong length"); + + test_eat_psa_claims(&claims, &component); + component.signerId.data = NULL; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer signer ID with NULL data"); + + test_eat_psa_claims(&claims, &component); + component.signerId.len = 31u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), "reject issuer signer ID with wrong length"); + + test_eat_psa_claims(&claims, &component); + component.measurementType.data = NULL; + component.measurementType.len = 1u; + test_eat_psa_expect_issue_claim_failure(&claims, claimsBuf, + sizeof(claimsBuf), + "reject issuer optional component text with NULL data"); + + test_eat_psa_claims(&claims, &component); + claims.componentCount = 0u; + outLen = 17u; + ret = wc_CoseEatPsaToken_EncodeClaims(&claims, claimsBuf, + sizeof(claimsBuf), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && outLen == 0u, + "reject zero software components"); + + test_eat_psa_claims(&claims, &component); + for (i = 0u; i < (WOLFCOSE_EAT_PSA_MAX_COMPONENTS + 1u); i++) { + components[i] = component; + } + claims.components = components; + claims.componentCount = WOLFCOSE_EAT_PSA_MAX_COMPONENTS; + ret = wc_CoseEatPsaToken_EncodeClaims(&claims, claimsBuf, + sizeof(claimsBuf), &outLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && outLen > 0u, + "encode exactly the configured component maximum"); + + claims.componentCount = WOLFCOSE_EAT_PSA_MAX_COMPONENTS + 1u; + outLen = 17u; + ret = wc_CoseEatPsaToken_EncodeClaims(&claims, claimsBuf, + sizeof(claimsBuf), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM && outLen == 0u, + "reject one component beyond configured maximum"); + + test_eat_psa_claims(&claims, &component); + component.version.data = invalidUtf8; + component.version.len = sizeof(invalidUtf8); + outLen = 17u; + ret = wc_CoseEatPsaToken_EncodeClaims(&claims, claimsBuf, + sizeof(claimsBuf), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED && outLen == 0u, + "reject invalid UTF-8 issuer text claim"); + + test_eat_psa_claims(&claims, &component); + (void)memset(out, 0xA5, sizeof(out)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateSign1(NULL, WOLFCOSE_ALG_ES256, &claims, + claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), out, + sizeof(out), &outLen, rng); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && outLen == 0u && + test_eat_psa_bytes_are_zero(out, sizeof(out)) != 0, + "clear Sign1 output after invalid arguments"); + + for (i = 0u; i < 5u; i++) { + const WOLFCOSE_EAT_PSA_CLAIMS* argClaims = + (i == 0u) ? NULL : &claims; + uint8_t* argClaimsBuf = (i == 1u) ? NULL : claimsBuf; + uint8_t* argScratch = (i == 2u) ? NULL : scratch; + uint8_t* argOut = (i == 3u) ? NULL : out; + size_t* argOutLen = (i == 4u) ? NULL : &outLen; + int expected = (i == 0u) ? WOLFCOSE_E_EAT_PSA_CLAIM : + WOLFCOSE_E_INVALID_ARG; + + (void)memset(out, 0xA5, sizeof(out)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateSign1(key, WOLFCOSE_ALG_ES256, + argClaims, argClaimsBuf, sizeof(claimsBuf), argScratch, + sizeof(scratch), argOut, sizeof(out), argOutLen, rng); + TEST_ASSERT(ret == expected && + ((argOutLen == NULL) || (outLen == 0u)) && + ((argOut == NULL) || + (test_eat_psa_bytes_are_zero(out, sizeof(out)) != 0)), + "Sign1 wrapper rejects each missing required pointer"); + } + + test_eat_psa_expect_sign1_overlap(key, rng, &claims, + overlap, 512u, scratch, sizeof(scratch), overlap, 1024u, + "Sign1 rejects exact claims/output overlap"); + test_eat_psa_expect_sign1_overlap(key, rng, &claims, + &overlap[256], 512u, scratch, sizeof(scratch), overlap, 1024u, + "Sign1 rejects partial claims/output overlap"); + test_eat_psa_expect_sign1_overlap(key, rng, &claims, + overlap, 512u, overlap, 1024u, out, sizeof(out), + "Sign1 rejects exact claims/scratch overlap"); + test_eat_psa_expect_sign1_overlap(key, rng, &claims, + overlap, 512u, &overlap[256], 1024u, out, sizeof(out), + "Sign1 rejects partial claims/scratch overlap"); + test_eat_psa_expect_sign1_overlap(key, rng, &claims, + claimsBuf, sizeof(claimsBuf), overlap, 1024u, overlap, 1024u, + "Sign1 rejects exact scratch/output overlap"); + test_eat_psa_expect_sign1_overlap(key, rng, &claims, + claimsBuf, sizeof(claimsBuf), overlap, 1024u, &overlap[256], 1024u, + "Sign1 rejects partial scratch/output overlap"); + + (void)memset(out, 0xA5, sizeof(out)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateSign1(key, WOLFCOSE_ALG_EDDSA, &claims, + claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), out, + sizeof(out), &outLen, rng); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG && outLen == 0u && + test_eat_psa_bytes_are_zero(out, sizeof(out)) != 0, + "clear Sign1 output after unsupported PSA algorithm"); + + (void)memset(out, 0xA5, sizeof(out)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateSign1(key, WOLFCOSE_ALG_ES256, &claims, + claimsBuf, 1u, scratch, sizeof(scratch), out, sizeof(out), &outLen, + rng); + TEST_ASSERT(ret != WOLFCOSE_SUCCESS && outLen == 0u && + test_eat_psa_bytes_are_zero(out, sizeof(out)) != 0, + "clear Sign1 output after claims-buffer failure"); + + (void)memset(out, 0xA5, sizeof(out)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateSign1(key, WOLFCOSE_ALG_ES256, &claims, + claimsBuf, sizeof(claimsBuf), scratch, 1u, out, sizeof(out), &outLen, + rng); + TEST_ASSERT(ret != WOLFCOSE_SUCCESS && outLen == 0u && + test_eat_psa_bytes_are_zero(out, sizeof(out)) != 0, + "clear Sign1 output after scratch-buffer failure"); + + (void)memset(out, 0xA5, sizeof(out)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateSign1(key, WOLFCOSE_ALG_ES256, &claims, + claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), out, 1u, + &outLen, rng); + TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL && outLen == 0u && + test_eat_psa_bytes_are_zero(out, 1u) != 0, + "clear Sign1 output after token-buffer failure"); +} +#endif + +#if defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) +static void test_eat_psa_rfc9783_sign1(void) +{ + WOLFCOSE_EAT_PSA_TOKEN token; + EAT_PSA_COMPONENT_CTX componentCtx; + WOLFCOSE_KEY key; + WOLFCOSE_HDR hdr; + WOLFCOSE_HDR_STATE hdrState; + ecc_key ecc; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tokenBuf[512]; + uint8_t nonPreferred[513]; + uint8_t cwtTagged[514]; + const uint8_t* genericPayload = NULL; + size_t genericPayloadLen = 0u; + size_t tokenLen = 0u; + int ret; + int testRet; + int eccInited = 0; + int keyInited = 0; + + (void)printf(" [RFC 9783 Appendix A Sign1]\n"); + { + static const uint8_t protectedX5chain[] = { + 0xA2u, 0x01u, 0x26u, 0x18u, 0x21u, 0x40u + }; + + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedX5chain, + sizeof(protectedX5chain), &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + ((hdr.flags & WOLFCOSE_HDR_FLAG_X5CHAIN) != 0u), + "detect x5chain in protected COSE headers"); + } + ret = test_eat_psa_hex_decode(kRfc9783Sign1Hex, tokenBuf, + sizeof(tokenBuf), &tokenLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && tokenLen > 0u, + "decode RFC 9783 Sign1 vector"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_ecc_init(&ecc); + if (ret == 0) { + eccInited = 1; + ret = wc_ecc_import_unsigned(&ecc, kRfc9783Sign1KeyX, + kRfc9783Sign1KeyY, NULL, ECC_SECP256R1); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &ecc); + key.hasPrivate = 0u; + } + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "import RFC 9783 Sign1 key"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, + kRfc9783Nonce, sizeof(kRfc9783Nonce), scratch, sizeof(scratch), + &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "verify iat-verifier RFC 9783 Sign1 vector"); + } + if (ret == WOLFCOSE_SUCCESS) { + TEST_ASSERT(token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT && + token.protection == WOLFCOSE_EAT_PSA_PROTECTION_SIGN1 && + token.componentCount == 1u && token.bootSeed.len == 8u, + "parse RFC 9783 Sign1 claims"); + (void)memset(&componentCtx, 0, sizeof(componentCtx)); + ret = wc_EatPsaToken_ForEachComponent(&token, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && componentCtx.count == 1 && + componentCtx.valid != 0, + "parse RFC 9783 Sign1 component"); + } + /* RFC 9783 requires PSA receivers to accept non-preferred CBOR, while + * normal COSE verification retains its strict-by-default behavior. */ + if (ret == WOLFCOSE_SUCCESS) { + nonPreferred[0] = 0xD8u; + nonPreferred[1] = 0x12u; + (void)memcpy(&nonPreferred[2], &tokenBuf[1], tokenLen - 1u); + ret = wc_CoseSign1_Verify(&key, nonPreferred, tokenLen + 1u, NULL, + 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, &genericPayload, + &genericPayloadLen); + TEST_ASSERT(ret != WOLFCOSE_SUCCESS, + "generic COSE remains strict for non-preferred tag"); + testRet = wolfCose_Sign1_Verify_ex(&key, nonPreferred, tokenLen + 1u, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &genericPayload, &genericPayloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "Sign1 decode-option API accepts non-preferred tag"); + testRet = wolfCose_Sign1_Verify_ex(&key, tokenBuf, tokenLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &genericPayload, + &genericPayloadLen, 0x80000000u); + TEST_ASSERT(testRet == WOLFCOSE_E_INVALID_ARG, + "Sign1 decode-option API rejects unknown flag"); + ret = wc_EatPsaToken_Verify(&key, nonPreferred, tokenLen + 1u, + kRfc9783Nonce, sizeof(kRfc9783Nonce), scratch, sizeof(scratch), + &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "PSA verifier accepts non-preferred tag"); + } + if (ret == WOLFCOSE_SUCCESS) { + cwtTagged[0] = 0xD8u; + cwtTagged[1] = 0x3Du; + (void)memcpy(&cwtTagged[2], tokenBuf, tokenLen); + ret = wc_EatPsaToken_Verify(&key, cwtTagged, tokenLen + 2u, + kRfc9783Nonce, sizeof(kRfc9783Nonce), scratch, sizeof(scratch), + &token); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_TAG, + "reject CWT tag around PSA token"); + } + + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } + if (eccInited != 0) { + (void)wc_ecc_free(&ecc); + } +} +#endif /* WOLFCOSE_SIGN1_VERIFY && WOLFCOSE_HAVE_ES256 */ + +#if defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256) +static void test_eat_psa_rfc9783_mac0(void) +{ + WOLFCOSE_EAT_PSA_TOKEN token; + WOLFCOSE_KEY key; + WOLFCOSE_HDR hdr; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tokenBuf[512]; + uint8_t nonPreferred[513]; + const uint8_t* genericPayload = NULL; + size_t genericPayloadLen = 0u; + size_t tokenLen = 0u; + int ret; + int testRet; + int keyInited = 0; + + (void)printf(" [RFC 9783 Appendix A Mac0]\n"); + ret = test_eat_psa_hex_decode(kRfc9783Mac0Hex, tokenBuf, + sizeof(tokenBuf), &tokenLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && tokenLen > 0u, + "decode RFC 9783 Mac0 vector"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetSymmetric(&key, kRfc9783Mac0Key, + sizeof(kRfc9783Mac0Key)); + } + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "import RFC 9783 Mac0 key"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, + kRfc9783Nonce, sizeof(kRfc9783Nonce), scratch, sizeof(scratch), + &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "verify iat-verifier RFC 9783 Mac0 vector"); + } + if (ret == WOLFCOSE_SUCCESS) { + TEST_ASSERT(token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT && + token.protection == WOLFCOSE_EAT_PSA_PROTECTION_MAC0 && + token.componentCount == 1u, + "parse RFC 9783 Mac0 claims"); + } + if (ret == WOLFCOSE_SUCCESS) { + nonPreferred[0] = 0xD8u; + nonPreferred[1] = 0x11u; + (void)memcpy(&nonPreferred[2], &tokenBuf[1], tokenLen - 1u); + testRet = wc_CoseMac0_Verify(&key, nonPreferred, tokenLen + 1u, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &genericPayload, &genericPayloadLen); + TEST_ASSERT(testRet != WOLFCOSE_SUCCESS, + "generic Mac0 remains strict for non-preferred tag"); + testRet = wolfCose_Mac0_Verify_ex(&key, nonPreferred, tokenLen + 1u, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + &genericPayload, &genericPayloadLen, + WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "Mac0 decode-option API accepts non-preferred tag"); + testRet = wolfCose_Mac0_Verify_ex(&key, tokenBuf, tokenLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &genericPayload, + &genericPayloadLen, 0x80000000u); + TEST_ASSERT(testRet == WOLFCOSE_E_INVALID_ARG, + "Mac0 decode-option API rejects unknown flag"); + } + + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } +} +#endif /* WOLFCOSE_MAC0_VERIFY && WOLFCOSE_HAVE_HMAC256 */ + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +static void test_eat_psa_sign1(void) +{ + static const uint8_t currentProfile[] = WOLFCOSE_EAT_PSA_PROFILE_TFM; + static const uint16_t lifecycleClasses[] = { + 0x1000u, 0x4000u, 0x5000u, 0x6000u + }; + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_CLAIMS malformedClaims; + WOLFCOSE_EAT_PSA_CLAIMS multiClaims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_COMPONENT multiComponents[2]; + WOLFCOSE_EAT_PSA_TOKEN token; + WOLFCOSE_EAT_PSA_TOKEN iteratorToken; + WOLFCOSE_KEY key; + EAT_PSA_RESOLVER_CTX resolver; + EAT_PSA_COMPONENT_CTX componentCtx; + ecc_key ecc; + WC_RNG rng; + uint8_t claimsBuf[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tokenBuf[1024]; + uint8_t unprotectedAlgToken[1024]; + uint8_t malformedUeid[sizeof(kUeid)]; + uint8_t malformedPayload[512]; + uint8_t malformedToken[1024]; + uint8_t payload[512]; + uint8_t lifecyclePayload[512]; + uint8_t variantPayload[513]; + uint8_t duplicatePayload[600]; + uint8_t legacyToken[1024]; + size_t tokenLen = 0u; + size_t payloadLen = 0u; + size_t variantLen = 0u; + size_t duplicateLen = 0u; + size_t legacyLen = 0u; + size_t auxTokenLen = 0u; + size_t malformedPayloadLen = 0u; + size_t malformedTokenLen = 0u; + size_t i; + int ret; + int testRet; + int rngInited = 0; + int eccInited = 0; + int keyInited = 0; + + (void)printf(" [RFC 9783 Sign1]\n"); + test_eat_psa_claims(&claims, &component); + claims.certificationReference.len--; + ret = wc_EatPsaToken_EncodeClaims(&claims, claimsBuf, sizeof(claimsBuf), + &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject malformed current certification reference"); + claims.certificationReference.len++; + claims.lifecycle = 0x0000u; + ret = wc_EatPsaToken_EncodeClaims(&claims, claimsBuf, sizeof(claimsBuf), + &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "accept structurally valid unknown PSA lifecycle lower boundary"); + claims.lifecycle = 0x00FFu; + ret = wc_EatPsaToken_EncodeClaims(&claims, claimsBuf, sizeof(claimsBuf), + &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "accept structurally valid unknown PSA lifecycle upper boundary"); + claims.lifecycle = 0x2000u; + ret = wc_EatPsaToken_EncodeClaims(&claims, claimsBuf, sizeof(claimsBuf), + &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "accept PSA RoT provisioning lifecycle lower boundary"); + claims.lifecycle = 0x20FFu; + ret = wc_EatPsaToken_EncodeClaims(&claims, claimsBuf, sizeof(claimsBuf), + &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "accept PSA RoT provisioning lifecycle upper boundary"); + claims.lifecycle = 0x0100u; + ret = wc_EatPsaToken_EncodeClaims(&claims, claimsBuf, sizeof(claimsBuf), + &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject PSA lifecycle outside RFC 9783 state ranges"); + claims.lifecycle = 0x3000u; + ret = wc_InitRng(&rng); + if (ret == 0) { + rngInited = 1; + ret = wc_ecc_init(&ecc); + } + if (ret == 0) { + eccInited = 1; + ret = wc_ecc_make_key_ex(&rng, 32, &ecc, ECC_SECP256R1); + } + if (ret == 0) { + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + } + } + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &ecc); + } + if (ret == WOLFCOSE_SUCCESS) { + test_eat_psa_issue_boundaries(&key, &rng); + } + if (ret == WOLFCOSE_SUCCESS) { + for (i = 0u; i < (sizeof(lifecycleClasses) / + sizeof(lifecycleClasses[0])); i++) { + claims.lifecycle = lifecycleClasses[i]; + auxTokenLen = 0u; + testRet = wc_CoseEatPsaToken_CreateSign1(&key, + WOLFCOSE_ALG_ES256, &claims, claimsBuf, sizeof(claimsBuf), + scratch, sizeof(scratch), legacyToken, sizeof(legacyToken), + &auxTokenLen, &rng); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseEatPsaToken_Verify(&key, legacyToken, + auxTokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + } + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.lifecycle == lifecycleClasses[i], + "issue and verify accepted PSA lifecycle class"); + } + claims.lifecycle = 0x3000u; + } + if (ret == 0) { + ret = wc_EatPsaToken_CreateSign1(&key, WOLFCOSE_ALG_ES256, &claims, + claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), tokenBuf, + sizeof(tokenBuf), &tokenLen, &rng); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && tokenLen > 0u, + "create current Sign1 token"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "verify current Sign1 token"); + TEST_ASSERT(token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT, + "current profile"); + TEST_ASSERT(token.clientId == -1 && token.lifecycle == 0x3000u, + "current scalar claims"); + TEST_ASSERT(token.certificationReference.len == + claims.certificationReference.len && + token.verificationServiceIndicator.len == + claims.verificationServiceIndicator.len, + "current optional claims"); + (void)memset(&componentCtx, 0, sizeof(componentCtx)); + ret = wc_EatPsaToken_ForEachComponent(&token, test_eat_psa_component, + &componentCtx); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && componentCtx.count == 1 && + componentCtx.valid != 0, "component iteration"); + testRet = wc_CoseEatPsaToken_ForEachComponent(&token, + test_eat_psa_component_without_ctx, NULL); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "component iterator permits NULL callback context"); + testRet = wc_CoseEatPsaToken_ForEachComponent(NULL, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(testRet == WOLFCOSE_E_INVALID_ARG, + "component iterator rejects NULL token"); + testRet = wc_CoseEatPsaToken_ForEachComponent(&token, NULL, + &componentCtx); + TEST_ASSERT(testRet == WOLFCOSE_E_INVALID_ARG, + "component iterator rejects NULL callback"); + iteratorToken = token; + iteratorToken.components.data = NULL; + testRet = wc_CoseEatPsaToken_ForEachComponent(&iteratorToken, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(testRet == WOLFCOSE_E_INVALID_ARG, + "component iterator rejects absent component data"); + test_eat_psa_verify_argument_guards(&key, tokenBuf, tokenLen, scratch, + sizeof(scratch)); + } + if (ret == WOLFCOSE_SUCCESS) { + testRet = wc_CoseEatPsaToken_EncodeClaims(&claims, payload, + sizeof(payload), &payloadLen); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "encode canonical claims for variation-tolerance tests"); + if (testRet == WOLFCOSE_SUCCESS) { + test_eat_psa_nonpreferred_claim_forms(&key, &rng, payload, + payloadLen); + } + } + if (ret == WOLFCOSE_SUCCESS) { + test_eat_psa_nonpreferred_protected_sign1(&key, &rng); + test_eat_psa_nonpreferred_unprotected_sign1(&key, &rng); + } + if (ret == WOLFCOSE_SUCCESS) { + static const uint8_t textUnprotected[] = { + 0xA1u, 0x66u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u + }; + static const uint8_t contentTypeUnprotected[] = { + 0xA1u, 0x03u, 0x00u + }; + static const uint8_t nonPreferredTextUnprotected[] = { + 0xA1u, 0x78u, 0x06u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u + }; + static const uint8_t duplicateTextUnprotected[] = { + 0xA2u, + 0x66u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u, + 0x66u, 'v', 'e', 'n', 'd', 'o', 'r', 0x01u + }; + + if ((tokenLen > 7u) && (tokenBuf[0] == 0xD2u) && + (tokenBuf[1] == 0x84u) && (tokenBuf[2] == 0x43u) && + (tokenBuf[3] == 0xA1u) && (tokenBuf[4] == 0x01u) && + (tokenBuf[5] == 0x26u) && (tokenBuf[6] == 0xA0u) && + (tokenLen + sizeof(duplicateTextUnprotected) - 1u <= + sizeof(unprotectedAlgToken))) { + (void)memcpy(unprotectedAlgToken, tokenBuf, 6u); + (void)memcpy(&unprotectedAlgToken[6], textUnprotected, + sizeof(textUnprotected)); + (void)memcpy(&unprotectedAlgToken[6u + sizeof(textUnprotected)], + &tokenBuf[7], tokenLen - 7u); + auxTokenLen = tokenLen + sizeof(textUnprotected) - 1u; + testRet = wc_EatPsaToken_Verify(&key, unprotectedAlgToken, + auxTokenLen, kNonce, sizeof(kNonce), scratch, sizeof(scratch), + &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT, + "PSA Sign1 accepts preferred tstr header extension"); + + (void)memcpy(unprotectedAlgToken, tokenBuf, 6u); + (void)memcpy(&unprotectedAlgToken[6], contentTypeUnprotected, + sizeof(contentTypeUnprotected)); + (void)memcpy(&unprotectedAlgToken[6u + + sizeof(contentTypeUnprotected)], &tokenBuf[7], tokenLen - 7u); + auxTokenLen = tokenLen + sizeof(contentTypeUnprotected) - 1u; + testRet = wc_EatPsaToken_Verify(&key, unprotectedAlgToken, + auxTokenLen, kNonce, sizeof(kNonce), scratch, sizeof(scratch), + &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT, + "PSA Sign1 accepts unprotected content type"); + + (void)memcpy(unprotectedAlgToken, tokenBuf, 6u); + (void)memcpy(&unprotectedAlgToken[6], nonPreferredTextUnprotected, + sizeof(nonPreferredTextUnprotected)); + (void)memcpy(&unprotectedAlgToken[6u + + sizeof(nonPreferredTextUnprotected)], &tokenBuf[7], + tokenLen - 7u); + auxTokenLen = tokenLen + sizeof(nonPreferredTextUnprotected) - 1u; + testRet = wc_EatPsaToken_Verify(&key, unprotectedAlgToken, + auxTokenLen, kNonce, sizeof(kNonce), scratch, sizeof(scratch), + &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT, + "PSA Sign1 accepts non-preferred tstr header extension"); + + (void)memcpy(unprotectedAlgToken, tokenBuf, 6u); + (void)memcpy(&unprotectedAlgToken[6], duplicateTextUnprotected, + sizeof(duplicateTextUnprotected)); + (void)memcpy(&unprotectedAlgToken[6u + + sizeof(duplicateTextUnprotected)], &tokenBuf[7], + tokenLen - 7u); + auxTokenLen = tokenLen + sizeof(duplicateTextUnprotected) - 1u; + (void)memset(&token, 0xA5, sizeof(token)); + testRet = wc_CoseEatPsaToken_Verify(&key, unprotectedAlgToken, + auxTokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_CBOR_MALFORMED && + test_eat_psa_token_is_zero(&token) != 0, + "reject duplicate text labels in one unprotected map"); + } + else { + TEST_ASSERT(0, "expected deterministic PSA Sign1 header layout"); + } + } + if (ret == WOLFCOSE_SUCCESS) { + static const uint8_t wrongArrayCount[] = { + 0xD2u, 0x83u, 0x40u, 0xA0u, 0x40u + }; + static const uint8_t wrongProtectedType[] = { + 0xD2u, 0x84u, 0x00u, 0xA0u, 0x40u, 0x40u + }; + static const uint8_t wrongPayloadType[] = { + 0xD2u, 0x84u, 0x43u, 0xA1u, 0x01u, 0x26u, + 0xA0u, 0x00u, 0x40u + }; + static const uint8_t wrongAuthType[] = { + 0xD2u, 0x84u, 0x43u, 0xA1u, 0x01u, 0x26u, + 0xA0u, 0x40u, 0x00u + }; + + test_eat_psa_expect_envelope_failure(&key, wrongArrayCount, + sizeof(wrongArrayCount), scratch, sizeof(scratch), + WOLFCOSE_E_CBOR_MALFORMED, + "reject PSA envelope with wrong array count and clear output"); + test_eat_psa_expect_envelope_failure(&key, wrongProtectedType, + sizeof(wrongProtectedType), scratch, sizeof(scratch), + WOLFCOSE_E_CBOR_TYPE, + "reject PSA envelope with non-bstr protected header and clear output"); + test_eat_psa_expect_envelope_failure(&key, wrongPayloadType, + sizeof(wrongPayloadType), scratch, sizeof(scratch), + WOLFCOSE_E_CBOR_TYPE, + "reject PSA envelope with non-bstr payload and clear output"); + test_eat_psa_expect_envelope_failure(&key, wrongAuthType, + sizeof(wrongAuthType), scratch, sizeof(scratch), + WOLFCOSE_E_CBOR_TYPE, + "reject PSA envelope with non-bstr authentication data and clear output"); + if (tokenLen < sizeof(unprotectedAlgToken)) { + (void)memcpy(unprotectedAlgToken, tokenBuf, tokenLen); + unprotectedAlgToken[tokenLen] = 0x00u; + test_eat_psa_expect_envelope_failure(&key, unprotectedAlgToken, + tokenLen + 1u, scratch, sizeof(scratch), + WOLFCOSE_E_CBOR_MALFORMED, + "reject PSA envelope trailing data and clear output"); + } + else { + TEST_ASSERT(0, "room for PSA envelope trailing-data test"); + } + } + if (ret == WOLFCOSE_SUCCESS) { + test_eat_psa_nonce_and_hash_lengths(&key, &rng); + } + if (ret == WOLFCOSE_SUCCESS) { + test_eat_psa_claim_rejections(&key, &rng, &claims, scratch, + sizeof(scratch)); + } + if (ret == WOLFCOSE_SUCCESS) { + multiClaims = claims; + multiComponents[0] = component; + multiComponents[1] = component; + multiClaims.components = multiComponents; + multiClaims.componentCount = 2u; + testRet = wc_CoseEatPsaToken_CreateSign1(&key, WOLFCOSE_ALG_ES256, + &multiClaims, claimsBuf, sizeof(claimsBuf), scratch, + sizeof(scratch), legacyToken, sizeof(legacyToken), &auxTokenLen, + &rng); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseEatPsaToken_Verify(&key, legacyToken, + auxTokenLen, kNonce, sizeof(kNonce), scratch, sizeof(scratch), + &token); + } + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && token.componentCount == 2u, + "verify token with multiple components"); + if (testRet == WOLFCOSE_SUCCESS) { + (void)memset(&componentCtx, 0, sizeof(componentCtx)); + componentCtx.fail = 1; + testRet = wc_CoseEatPsaToken_ForEachComponent(&token, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM && + componentCtx.count == 1, + "propagate component callback failure once"); + (void)memset(&componentCtx, 0, sizeof(componentCtx)); + token.componentCount++; + testRet = wc_CoseEatPsaToken_ForEachComponent(&token, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM && + componentCtx.count == 0, + "reject component count mismatch"); + token.componentCount--; + (void)memset(&componentCtx, 0, sizeof(componentCtx)); + token.components.len--; + testRet = wc_CoseEatPsaToken_ForEachComponent(&token, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(testRet != WOLFCOSE_SUCCESS && + componentCtx.count < 2, + "reject truncated component data"); + token.components.len++; + if (token.components.len < sizeof(variantPayload)) { + (void)memcpy(variantPayload, token.components.data, + token.components.len); + variantPayload[token.components.len] = 0x00u; + iteratorToken = token; + iteratorToken.components.data = variantPayload; + iteratorToken.components.len++; + (void)memset(&componentCtx, 0, sizeof(componentCtx)); + testRet = wc_CoseEatPsaToken_ForEachComponent(&iteratorToken, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM && + componentCtx.count == 2, + "reject bytes trailing the component array"); + } + else { + TEST_ASSERT(0, "room for trailing component-array byte"); + } + } + } + if ((ret == WOLFCOSE_SUCCESS) && (tokenLen > 1u)) { + testRet = wc_EatPsaToken_Verify(&key, &tokenBuf[1], tokenLen - 1u, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet != WOLFCOSE_SUCCESS, "reject untagged PSA token"); + } + if (ret == WOLFCOSE_SUCCESS) { + if ((tokenLen > 7u) && (tokenBuf[0] == 0xD2u) && + (tokenBuf[1] == 0x84u) && (tokenBuf[6] == 0xA0u)) { + (void)memcpy(unprotectedAlgToken, tokenBuf, 2u); + unprotectedAlgToken[2] = 0x40u; + unprotectedAlgToken[3] = 0xA1u; + unprotectedAlgToken[4] = 0x01u; + unprotectedAlgToken[5] = 0x26u; + (void)memcpy(&unprotectedAlgToken[6], &tokenBuf[7], + tokenLen - 7u); + auxTokenLen = tokenLen - 1u; + testRet = wc_EatPsaToken_Verify(&key, unprotectedAlgToken, + auxTokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_COSE_BAD_HDR, + "reject unauthenticated COSE algorithm"); + } + else { + TEST_ASSERT(0, "expected deterministic Sign1 header layout"); + } + } + if (ret == WOLFCOSE_SUCCESS) { + resolver.key = &key; + resolver.called = 0; + resolver.fail = 0; + resolver.receivedProfile = WOLFCOSE_EAT_PSA_PROFILE_NONE; + resolver.receivedAlg = WOLFCOSE_ALG_UNSET; + ret = wc_EatPsaToken_VerifyByUeid(test_eat_psa_resolve, &resolver, + tokenBuf, tokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && resolver.called == 1 && + resolver.receivedProfile == + WOLFCOSE_EAT_PSA_PROFILE_CURRENT && + resolver.receivedAlg == WOLFCOSE_ALG_ES256, + "resolve current Sign1 key by UEID and COSE algorithm"); + testRet = wc_EatPsaToken_VerifyByUeid(test_eat_psa_resolve, &resolver, + tokenBuf, tokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), NULL); + TEST_ASSERT(testRet == WOLFCOSE_E_INVALID_ARG, + "reject NULL UEID resolver output"); + (void)memset(&token, 0xA5, sizeof(token)); + resolver.fail = 1; + testRet = wc_EatPsaToken_VerifyByUeid(test_eat_psa_resolve, &resolver, + tokenBuf, tokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_KEY && + test_eat_psa_token_is_zero(&token) != 0, + "propagate resolver failure and clear token output"); + resolver.fail = 0; + + (void)memcpy(unprotectedAlgToken, tokenBuf, tokenLen); + unprotectedAlgToken[tokenLen - 1u] ^= 0xFFu; + (void)memset(&token, 0xA5, sizeof(token)); + resolver.called = 0; + testRet = wc_EatPsaToken_VerifyByUeid(test_eat_psa_resolve, &resolver, + unprotectedAlgToken, tokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_COSE_SIG_FAIL && + resolver.called == 1 && + test_eat_psa_token_is_zero(&token) != 0, + "resolve Sign1 key before rejecting tampered signature and clear output"); + + (void)memcpy(malformedUeid, kUeid, sizeof(malformedUeid)); + malformedUeid[0] = 0x02u; + malformedClaims = claims; + malformedClaims.ueid.data = malformedUeid; + testRet = test_eat_psa_encode_current_raw_ex(&malformedClaims, + currentProfile, sizeof(currentProfile) - 1u, 0u, 0u, 0, 0u, + malformedClaims.componentCount, malformedPayload, + sizeof(malformedPayload), &malformedPayloadLen); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = test_eat_psa_sign1_payload(&key, &rng, + malformedPayload, malformedPayloadLen, scratch, + sizeof(scratch), malformedToken, sizeof(malformedToken), + &malformedTokenLen); + } + resolver.called = 0; + (void)memset(&token, 0xA5, sizeof(token)); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseEatPsaToken_VerifyByUeid( + test_eat_psa_resolve, &resolver, malformedToken, + malformedTokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + } + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM && + resolver.called == 0 && + test_eat_psa_token_is_zero(&token) != 0, + "reject malformed routing UEID before invoking resolver"); + } + if (ret == WOLFCOSE_SUCCESS) { + if ((tokenLen > 7u) && (tokenBuf[0] == 0xD2u) && + (tokenBuf[1] == 0x84u) && (tokenBuf[6] == 0xA0u)) { + (void)memcpy(legacyToken, tokenBuf, 6u); + legacyToken[6] = 0xA1u; + legacyToken[7] = 0x18u; + legacyToken[8] = 0x21u; + legacyToken[9] = 0x40u; + (void)memcpy(&legacyToken[10], &tokenBuf[7], tokenLen - 7u); + auxTokenLen = tokenLen + 3u; + testRet = wc_EatPsaToken_Verify(&key, legacyToken, auxTokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_UNSUPPORTED, + "reject x5chain without a certificate validator"); + } + else { + TEST_ASSERT(0, "expected deterministic Sign1 header layout"); + } + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memcpy(legacyToken, tokenBuf, tokenLen); + legacyToken[5] = 0x27u; /* EdDSA, which is not a TFM PSA algorithm. */ + testRet = wc_EatPsaToken_Verify(&key, legacyToken, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_COSE_BAD_ALG, + "reject non-TFM PSA algorithm before cryptographic verification"); + } + if (ret == WOLFCOSE_SUCCESS) { + testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, + NULL, 0u, kNonce, sizeof(kNonce), NULL, 0u, scratch, + sizeof(scratch), legacyToken, sizeof(legacyToken), &auxTokenLen, + &rng); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "create detached Sign1 for PSA rejection test"); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_EatPsaToken_Verify(&key, legacyToken, auxTokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_DETACHED_PAYLOAD, + "reject detached PSA token"); + } + } + if (ret == WOLFCOSE_SUCCESS) { + static const uint8_t indefiniteClaims[] = { 0xBFu, 0xFFu }; + + testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, + indefiniteClaims, sizeof(indefiniteClaims), NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), legacyToken, sizeof(legacyToken), + &auxTokenLen, &rng); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "create indefinite CBOR PSA rejection token"); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_EatPsaToken_Verify(&key, legacyToken, auxTokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_UNSUPPORTED, + "reject indefinite-length PSA claims"); + } + } + if (ret == WOLFCOSE_SUCCESS) { + uint8_t wrongNonce[sizeof(kNonce)]; + (void)memcpy(wrongNonce, kNonce, sizeof(wrongNonce)); + wrongNonce[0] ^= 0xFFu; + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, wrongNonce, + sizeof(wrongNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_NONCE && + test_eat_psa_token_is_zero(&token) != 0, + "reject wrong nonce and clear token output"); + testRet = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, NULL, 0u, + scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_INVALID_ARG, + "reject invalid expected nonce before verification"); + testRet = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), NULL); + TEST_ASSERT(testRet == WOLFCOSE_E_INVALID_ARG, + "reject NULL verifier output"); + ret = wc_EatPsaToken_EncodeClaims(&claims, payload, sizeof(payload), + &payloadLen); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memcpy(lifecyclePayload, payload, payloadLen); + testRet = test_eat_psa_set_lifecycle(lifecyclePayload, payloadLen, + 0x00FFu); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "locate current lifecycle claim"); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, + lifecyclePayload, payloadLen, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), legacyToken, sizeof(legacyToken), + &auxTokenLen, &rng); + } + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "create signed unknown lifecycle token"); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_EatPsaToken_Verify(&key, legacyToken, auxTokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && token.lifecycle == 0x00FFu, + "retain unknown lifecycle for application policy"); + } + + { + static const uint16_t provisioningLifecycles[] = { + 0x2000u, 0x20FFu + }; + + for (i = 0u; i < (sizeof(provisioningLifecycles) / + sizeof(provisioningLifecycles[0])); i++) { + (void)memcpy(lifecyclePayload, payload, payloadLen); + testRet = test_eat_psa_set_lifecycle(lifecyclePayload, + payloadLen, provisioningLifecycles[i]); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, + NULL, 0u, lifecyclePayload, payloadLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), legacyToken, + sizeof(legacyToken), &auxTokenLen, &rng); + } + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_EatPsaToken_Verify(&key, legacyToken, + auxTokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + } + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.lifecycle == provisioningLifecycles[i], + "accept signed PSA RoT provisioning lifecycle boundaries"); + } + } + (void)memcpy(lifecyclePayload, payload, payloadLen); + testRet = test_eat_psa_set_lifecycle(lifecyclePayload, payloadLen, + 0x0100u); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, + lifecyclePayload, payloadLen, NULL, 0u, NULL, 0u, scratch, + sizeof(scratch), legacyToken, sizeof(legacyToken), + &auxTokenLen, &rng); + } + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseEatPsaToken_Verify(&key, legacyToken, auxTokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + } + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject signed lifecycle outside RFC 9783 state ranges"); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)memcpy(variantPayload, payload, 1u); + variantPayload[1] = 0x18u; + variantPayload[2] = 0x0Au; + (void)memcpy(&variantPayload[3], &payload[2], payloadLen - 2u); + variantLen = payloadLen + 1u; + ret = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, + variantPayload, variantLen, NULL, 0u, NULL, 0u, scratch, + sizeof(scratch), tokenBuf, sizeof(tokenBuf), &tokenLen, &rng); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "accept non-preferred claim label"); + } + if (ret == WOLFCOSE_SUCCESS) { + if ((payload[0] < 0xa0u) || (payload[0] >= 0xb7u)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + /* Add a second copy of the first current-profile nonce claim. */ + duplicatePayload[0] = (uint8_t)(payload[0] + 1u); + (void)memcpy(&duplicatePayload[1], &payload[1], payloadLen - 1u); + (void)memcpy(&duplicatePayload[payloadLen], &payload[1], 35u); + duplicateLen = payloadLen + 35u; + ret = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, + duplicatePayload, duplicateLen, NULL, 0u, NULL, 0u, scratch, + sizeof(scratch), tokenBuf, sizeof(tokenBuf), &tokenLen, &rng); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject duplicate PSA claim"); + claims.bootSeed.data = kLegacyBootSeed; + claims.bootSeed.len = sizeof(kLegacyBootSeed); + ret = test_eat_psa_encode_legacy(&claims, payload, sizeof(payload), + &payloadLen, 0, 0, 0); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, payload, + payloadLen, NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + legacyToken, sizeof(legacyToken), &legacyLen, &rng); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&key, legacyToken, legacyLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_OLD && + token.verificationServiceIndicator.len == + claims.verificationServiceIndicator.len && + memcmp(token.verificationServiceIndicator.data, + claims.verificationServiceIndicator.data, + claims.verificationServiceIndicator.len) == 0, + "consume legacy PSA token with verification service indicator"); + } + if (ret == WOLFCOSE_SUCCESS) { + static const uint8_t validCert[] = "1234567890123"; + static const uint8_t shortCert[] = "123456789012"; + static const uint8_t nonDigitCert[] = "123456789012x"; + WOLFCOSE_EAT_PSA_SPAN certification; + + certification.data = validCert; + certification.len = sizeof(validCert) - 1u; + testRet = test_eat_psa_verify_legacy_raw(&key, &rng, &claims, 1, 1, + 0, 0u, &certification, scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.certificationReference.len == certification.len, + "accept valid legacy certification reference"); + + certification.data = shortCert; + certification.len = sizeof(shortCert) - 1u; + testRet = test_eat_psa_verify_legacy_raw(&key, &rng, &claims, 1, 1, + 0, 0u, &certification, scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject short legacy certification reference"); + + certification.data = nonDigitCert; + certification.len = sizeof(nonDigitCert) - 1u; + testRet = test_eat_psa_verify_legacy_raw(&key, &rng, &claims, 1, 1, + 0, 0u, &certification, scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject nonnumeric legacy certification reference"); + + testRet = test_eat_psa_verify_legacy_raw(&key, &rng, &claims, 1, 0, + 1, 2u, NULL, scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject legacy no-measurements value other than one"); + + testRet = test_eat_psa_verify_legacy_raw(&key, &rng, &claims, 1, 1, + 1, 1u, NULL, scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject legacy token with components and no-measurements"); + + testRet = test_eat_psa_verify_legacy_raw(&key, &rng, &claims, 1, 0, + 0, 0u, NULL, scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM && + test_eat_psa_token_is_zero(&token) != 0, + "reject legacy token without components or no-measurements"); + } + if (ret == WOLFCOSE_SUCCESS) { + testRet = test_eat_psa_encode_legacy(&claims, payload, + sizeof(payload), &payloadLen, 0, 0, 1); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, + payload, payloadLen, NULL, 0u, NULL, 0u, scratch, + sizeof(scratch), legacyToken, sizeof(legacyToken), &legacyLen, + &rng); + } + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseEatPsaToken_Verify(&key, legacyToken, legacyLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + } + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_PROFILE, + "reject signed incompatible legacy profile"); + } + if (ret == WOLFCOSE_SUCCESS) { + testRet = test_eat_psa_encode_legacy(&claims, payload, + sizeof(payload), &payloadLen, 1, 1, 0); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "encode legacy token without profile and measurements"); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, + payload, payloadLen, NULL, 0u, NULL, 0u, scratch, + sizeof(scratch), legacyToken, sizeof(legacyToken), &legacyLen, + &rng); + } + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS, + "sign legacy token without profile and measurements"); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_EatPsaToken_Verify(&key, legacyToken, legacyLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + } + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_OLD && + token.noSoftwareMeasurements != 0u && + token.componentCount == 0u, + "consume legacy no-measurements token"); + if (testRet == WOLFCOSE_SUCCESS) { + (void)memset(&componentCtx, 0, sizeof(componentCtx)); + testRet = wc_EatPsaToken_ForEachComponent(&token, + test_eat_psa_component, &componentCtx); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && componentCtx.count == 0, + "iterate legacy no-measurements token"); + } + } + if (ret == WOLFCOSE_SUCCESS) { + claims.bootSeed.len--; + testRet = test_eat_psa_encode_legacy(&claims, payload, + sizeof(payload), &payloadLen, 0, 0, 0); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, + payload, payloadLen, NULL, 0u, NULL, 0u, scratch, + sizeof(scratch), legacyToken, sizeof(legacyToken), &legacyLen, + &rng); + } + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_EatPsaToken_Verify(&key, legacyToken, legacyLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + } + TEST_ASSERT(testRet == WOLFCOSE_E_EAT_PSA_CLAIM, + "reject legacy boot seed that is not 32 bytes"); + claims.bootSeed.len++; + } + + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } + if (eccInited != 0) { + (void)wc_ecc_free(&ecc); + } + if (rngInited != 0) { + (void)wc_FreeRng(&rng); + } +} +#endif + +#if defined(WOLFCOSE_EXT_SIGN) && defined(WOLFCOSE_SIGN1_SIGN) && \ + defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) +static void test_eat_psa_delegated_signer(void) +{ + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_TOKEN token; + WOLFCOSE_KEY signKey; + WOLFCOSE_KEY verifyKey; + EAT_PSA_EXT_SIGN_CTX signCtx; + ecc_key ecc; + WC_RNG rng; + uint8_t claimsBuf[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tokenBuf[1024]; + size_t tokenLen = 0u; + int ret; + int rngInited = 0; + int eccInited = 0; + int signKeyInited = 0; + int verifyKeyInited = 0; + + (void)printf(" [RFC 9783 delegated Sign1]\n"); + test_eat_psa_claims(&claims, &component); + (void)memset(&signCtx, 0, sizeof(signCtx)); + ret = wc_InitRng(&rng); + if (ret == WOLFCOSE_SUCCESS) { + rngInited = 1; + ret = wc_ecc_init(&ecc); + } + if (ret == WOLFCOSE_SUCCESS) { + eccInited = 1; + ret = wc_ecc_make_key_ex(&rng, 32, &ecc, ECC_SECP256R1); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&signKey); + if (ret == WOLFCOSE_SUCCESS) { + signKeyInited = 1; + signKey.kty = WOLFCOSE_KTY_EC2; + signKey.crv = WOLFCOSE_CRV_P256; + signCtx.rng = &rng; + signCtx.key = &ecc; + ret = wc_CoseKey_SetExtSigner(&signKey, test_eat_psa_ext_sign_cb, + &signCtx); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&verifyKey); + if (ret == WOLFCOSE_SUCCESS) { + verifyKeyInited = 1; + ret = wc_CoseKey_SetEcc(&verifyKey, WOLFCOSE_CRV_P256, &ecc); + } + } + if (ret == WOLFCOSE_SUCCESS) { + /* The key-handle signer owns its entropy. This is the PSA client + * handoff path, so the wolfCOSE RNG argument remains NULL. */ + ret = wc_EatPsaToken_CreateSign1(&signKey, WOLFCOSE_ALG_ES256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), + tokenBuf, sizeof(tokenBuf), &tokenLen, NULL); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && tokenLen > 0u && + signCtx.called == 1, + "create PSA token with delegated signer and NULL RNG"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&verifyKey, tokenBuf, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "verify delegated PSA token"); + } + if (ret == WOLFCOSE_SUCCESS) { + signCtx.fail = 1; + (void)memset(tokenBuf, 0xA5, sizeof(tokenBuf)); + tokenLen = 17u; + ret = wc_EatPsaToken_CreateSign1(&signKey, WOLFCOSE_ALG_ES256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), + tokenBuf, sizeof(tokenBuf), &tokenLen, NULL); + TEST_ASSERT(ret == WOLFCOSE_E_CRYPTO && signCtx.called == 2 && + tokenLen == 0u && test_eat_psa_bytes_are_zero(tokenBuf, + sizeof(tokenBuf)) != 0, + "propagate delegated PSA signer failure and clear token output"); + } + + if (verifyKeyInited != 0) { + wc_CoseKey_Free(&verifyKey); + } + if (signKeyInited != 0) { + wc_CoseKey_Free(&signKey); + } + if (eccInited != 0) { + (void)wc_ecc_free(&ecc); + } + if (rngInited != 0) { + (void)wc_FreeRng(&rng); + } +} +#endif + +#if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) && \ + (defined(WOLFCOSE_HAVE_ES384) || defined(WOLFCOSE_HAVE_ES512)) +static void test_eat_psa_sign_alg(int32_t alg, int keySize, int curve, + int32_t coseCurve, const char* name) +{ + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_TOKEN token; + WOLFCOSE_KEY key; + ecc_key ecc; + WC_RNG rng; + uint8_t claimsBuf[768]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tokenBuf[1300]; + size_t tokenLen = 0u; + int ret; + int rngInited = 0; + int eccInited = 0; + int keyInited = 0; + + (void)printf(" [RFC 9783 Sign1 %s]\n", name); + test_eat_psa_claims(&claims, &component); + ret = wc_InitRng(&rng); + if (ret == WOLFCOSE_SUCCESS) { + rngInited = 1; + ret = wc_ecc_init(&ecc); + } + if (ret == WOLFCOSE_SUCCESS) { + eccInited = 1; + ret = wc_ecc_make_key_ex(&rng, keySize, &ecc, curve); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetEcc(&key, coseCurve, &ecc); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_CreateSign1(&key, alg, &claims, claimsBuf, + sizeof(claimsBuf), scratch, sizeof(scratch), tokenBuf, + sizeof(tokenBuf), &tokenLen, &rng); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && tokenLen > 0u, + "create current TFM Sign1 token"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + token.protection == WOLFCOSE_EAT_PSA_PROTECTION_SIGN1, + "verify current TFM Sign1 token"); + } + + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } + if (eccInited != 0) { + (void)wc_ecc_free(&ecc); + } + if (rngInited != 0) { + (void)wc_FreeRng(&rng); + } +} +#endif + +#if defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_MAC0_VERIFY) && \ + defined(WOLFCOSE_HAVE_HMAC256) +static int test_eat_psa_mac0_overlap_rejected(const WOLFCOSE_KEY* key, + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* claimsBuf, size_t claimsBufSz, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz) +{ + size_t outLen = 17u; + int ret; + + (void)memset(out, 0xA5, outSz); + ret = wc_CoseEatPsaToken_CreateMac0(key, WOLFCOSE_ALG_HMAC_256_256, + claims, claimsBuf, claimsBufSz, scratch, scratchSz, out, outSz, + &outLen); + return ((ret == WOLFCOSE_E_INVALID_ARG) && (outLen == 0u) && + (test_eat_psa_bytes_are_zero(out, outSz) != 0)) ? 1 : 0; +} + +static void test_eat_psa_expect_mac0_overlap(const WOLFCOSE_KEY* key, + const WOLFCOSE_EAT_PSA_CLAIMS* claims, + uint8_t* claimsBuf, size_t claimsBufSz, + uint8_t* scratch, size_t scratchSz, + uint8_t* out, size_t outSz, const char* name) +{ + TEST_ASSERT(test_eat_psa_mac0_overlap_rejected(key, claims, claimsBuf, + claimsBufSz, scratch, scratchSz, out, outSz) != 0, + name); +} + +static void test_eat_psa_mac0(void) +{ + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_TOKEN token; + WOLFCOSE_KEY key; + union { + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + uint8_t bytes[2048]; + } inputStorage; + uint8_t claimsBuf[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tokenBuf[1024]; + uint8_t modifiedToken[1024]; + uint8_t legacyPayload[512]; + uint8_t legacyToken[1024]; + uint8_t overlap[2048]; + size_t tokenLen = 0u; + size_t legacyPayloadLen = 0u; + size_t legacyTokenLen = 0u; + size_t outLen = 17u; + size_t argCase; + size_t i; + int ret; + int testRet; + int keyInited = 0; + int spanChecks; +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) + EAT_PSA_RESOLVER_CTX resolver; +#endif + + (void)printf(" [RFC 9783 Mac0]\n"); + test_eat_psa_claims(&claims, &component); + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetSymmetric(&key, kMac0TestKey, + sizeof(kMac0TestKey)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_CreateMac0(&key, WOLFCOSE_ALG_HMAC_256_256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), + tokenBuf, sizeof(tokenBuf), &tokenLen); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && tokenLen > 0u, + "create current Mac0 token"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT, + "verify current Mac0 token"); + } + if (ret == WOLFCOSE_SUCCESS) { + test_eat_psa_claims(&claims, &component); + inputStorage.claims = claims; + TEST_ASSERT(test_eat_psa_mac0_overlap_rejected(&key, + &inputStorage.claims, inputStorage.bytes, + sizeof(inputStorage.bytes), scratch, sizeof(scratch), + modifiedToken, sizeof(modifiedToken)) != 0, + "Mac0 issuer rejects exact claims-structure overlap"); + test_eat_psa_claims(&claims, &component); + inputStorage.claims = claims; + TEST_ASSERT(test_eat_psa_mac0_overlap_rejected(&key, + &inputStorage.claims, + &inputStorage.bytes[sizeof(inputStorage.claims) / 2u], + sizeof(inputStorage.bytes) - + (sizeof(inputStorage.claims) / 2u), + scratch, sizeof(scratch), modifiedToken, + sizeof(modifiedToken)) != 0, + "Mac0 issuer rejects partial claims-structure overlap"); + + test_eat_psa_claims(&claims, &component); + inputStorage.component = component; + claims.components = &inputStorage.component; + TEST_ASSERT(test_eat_psa_mac0_overlap_rejected(&key, &claims, + inputStorage.bytes, sizeof(inputStorage.bytes), + scratch, sizeof(scratch), modifiedToken, + sizeof(modifiedToken)) != 0, + "Mac0 issuer rejects exact component-array overlap"); + test_eat_psa_claims(&claims, &component); + inputStorage.component = component; + claims.components = &inputStorage.component; + TEST_ASSERT(test_eat_psa_mac0_overlap_rejected(&key, &claims, + &inputStorage.bytes[ + sizeof(inputStorage.component) / 2u], + sizeof(inputStorage.bytes) - + (sizeof(inputStorage.component) / 2u), + scratch, sizeof(scratch), modifiedToken, + sizeof(modifiedToken)) != 0, + "Mac0 issuer rejects partial component-array overlap"); + + spanChecks = 1; + for (i = 0u; i < TEST_EAT_PSA_INPUT_SPAN_COUNT; i++) { + if ((test_eat_psa_alias_input_span(&claims, &component, i, + inputStorage.bytes, sizeof(inputStorage.bytes)) == 0) || + (test_eat_psa_mac0_overlap_rejected(&key, &claims, + inputStorage.bytes, sizeof(inputStorage.bytes), scratch, + sizeof(scratch), modifiedToken, sizeof(modifiedToken)) == + 0)) { + spanChecks = 0; + } + if ((test_eat_psa_alias_input_span(&claims, &component, i, + inputStorage.bytes, sizeof(inputStorage.bytes)) == 0) || + (test_eat_psa_mac0_overlap_rejected(&key, &claims, + &inputStorage.bytes[1], sizeof(inputStorage.bytes) - 1u, + scratch, sizeof(scratch), modifiedToken, + sizeof(modifiedToken)) == 0)) { + spanChecks = 0; + } + } + TEST_ASSERT(spanChecks != 0, + "Mac0 rejects exact/partial overlap for every input span"); + + test_eat_psa_claims(&claims, &component); + test_eat_psa_expect_mac0_overlap(&key, &claims, + overlap, 512u, scratch, sizeof(scratch), overlap, 1024u, + "Mac0 rejects exact claims/output overlap"); + test_eat_psa_expect_mac0_overlap(&key, &claims, + &overlap[256], 512u, scratch, sizeof(scratch), overlap, 1024u, + "Mac0 rejects partial claims/output overlap"); + test_eat_psa_expect_mac0_overlap(&key, &claims, + overlap, 512u, overlap, 1024u, modifiedToken, + sizeof(modifiedToken), "Mac0 rejects exact claims/scratch overlap"); + test_eat_psa_expect_mac0_overlap(&key, &claims, + overlap, 512u, &overlap[256], 1024u, modifiedToken, + sizeof(modifiedToken), + "Mac0 rejects partial claims/scratch overlap"); + test_eat_psa_expect_mac0_overlap(&key, &claims, + claimsBuf, sizeof(claimsBuf), overlap, 1024u, overlap, 1024u, + "Mac0 rejects exact scratch/output overlap"); + test_eat_psa_expect_mac0_overlap(&key, &claims, + claimsBuf, sizeof(claimsBuf), overlap, 1024u, &overlap[256], + 1024u, "Mac0 rejects partial scratch/output overlap"); + } + if (ret == WOLFCOSE_SUCCESS) { + test_eat_psa_nonpreferred_protected_mac0(&key); + test_eat_psa_nonpreferred_unprotected_mac0(&key); + } + if (ret == WOLFCOSE_SUCCESS) { + static const uint8_t textUnprotected[] = { + 0xA1u, 0x66u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u + }; + static const uint8_t contentTypeUnprotected[] = { + 0xA1u, 0x03u, 0x00u + }; + static const uint8_t nonPreferredTextUnprotected[] = { + 0xA1u, 0x78u, 0x06u, 'v', 'e', 'n', 'd', 'o', 'r', 0x00u + }; + + if ((tokenLen > 6u) && (tokenBuf[0] == 0xD1u) && + (tokenBuf[1] == 0x84u) && (tokenBuf[2] == 0x43u) && + (tokenBuf[3] == 0xA1u) && (tokenBuf[4] == 0x01u) && + (tokenBuf[5] == 0x05u) && (tokenBuf[6] == 0xA0u) && + (tokenLen + sizeof(nonPreferredTextUnprotected) - 1u <= + sizeof(modifiedToken))) { + (void)memcpy(modifiedToken, tokenBuf, 6u); + (void)memcpy(&modifiedToken[6], textUnprotected, + sizeof(textUnprotected)); + (void)memcpy(&modifiedToken[6u + sizeof(textUnprotected)], + &tokenBuf[7], tokenLen - 7u); + legacyTokenLen = tokenLen + sizeof(textUnprotected) - 1u; + testRet = wc_EatPsaToken_Verify(&key, modifiedToken, legacyTokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT, + "PSA Mac0 accepts preferred tstr header extension"); + + (void)memcpy(modifiedToken, tokenBuf, 6u); + (void)memcpy(&modifiedToken[6], contentTypeUnprotected, + sizeof(contentTypeUnprotected)); + (void)memcpy(&modifiedToken[6u + sizeof(contentTypeUnprotected)], + &tokenBuf[7], tokenLen - 7u); + legacyTokenLen = tokenLen + sizeof(contentTypeUnprotected) - 1u; + testRet = wc_EatPsaToken_Verify(&key, modifiedToken, + legacyTokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT, + "PSA Mac0 accepts unprotected content type"); + + (void)memcpy(modifiedToken, tokenBuf, 6u); + (void)memcpy(&modifiedToken[6], nonPreferredTextUnprotected, + sizeof(nonPreferredTextUnprotected)); + (void)memcpy(&modifiedToken[6u + + sizeof(nonPreferredTextUnprotected)], &tokenBuf[7], + tokenLen - 7u); + legacyTokenLen = tokenLen + sizeof(nonPreferredTextUnprotected) - 1u; + testRet = wc_EatPsaToken_Verify(&key, modifiedToken, legacyTokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT, + "PSA Mac0 accepts non-preferred tstr header extension"); + } + else { + TEST_ASSERT(0, "expected deterministic PSA Mac0 header layout"); + } + } +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) + if (ret == WOLFCOSE_SUCCESS) { + resolver.key = &key; + resolver.called = 0; + resolver.fail = 0; + resolver.receivedProfile = WOLFCOSE_EAT_PSA_PROFILE_NONE; + resolver.receivedAlg = WOLFCOSE_ALG_UNSET; + testRet = wc_EatPsaToken_VerifyByUeid(test_eat_psa_resolve, + &resolver, tokenBuf, tokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && resolver.called == 1 && + resolver.receivedProfile == + WOLFCOSE_EAT_PSA_PROFILE_CURRENT && + resolver.receivedAlg == WOLFCOSE_ALG_HMAC_256_256, + "resolve current Mac0 key by UEID and COSE algorithm"); + + testRet = wc_EatPsaToken_VerifyByUeid( + test_eat_psa_resolve_without_ctx, NULL, tokenBuf, tokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_CURRENT, + "UEID resolver permits NULL callback context"); + + (void)memcpy(modifiedToken, tokenBuf, tokenLen); + modifiedToken[tokenLen - 1u] ^= 0xFFu; + (void)memset(&token, 0xA5, sizeof(token)); + resolver.called = 0; + testRet = wc_EatPsaToken_VerifyByUeid(test_eat_psa_resolve, + &resolver, modifiedToken, tokenLen, kNonce, sizeof(kNonce), + scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_E_MAC_FAIL && resolver.called == 1 && + test_eat_psa_token_is_zero(&token) != 0, + "resolve Mac0 key before rejecting tampered tag and clear output"); + } +#endif + if (ret == WOLFCOSE_SUCCESS) { + claims.bootSeed.data = kBootSeed; + claims.bootSeed.len = sizeof(kBootSeed); + testRet = test_eat_psa_encode_legacy(&claims, legacyPayload, + sizeof(legacyPayload), &legacyPayloadLen, 0, 0, 0); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, + NULL, 0u, legacyPayload, legacyPayloadLen, NULL, 0u, NULL, + 0u, scratch, sizeof(scratch), legacyToken, sizeof(legacyToken), + &legacyTokenLen); + } + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseEatPsaToken_Verify(&key, legacyToken, + legacyTokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); + } + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_OLD && + token.protection == WOLFCOSE_EAT_PSA_PROTECTION_MAC0, + "consume legacy PSA Mac0 token"); +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) + if (testRet == WOLFCOSE_SUCCESS) { + resolver.called = 0; + resolver.receivedProfile = WOLFCOSE_EAT_PSA_PROFILE_NONE; + resolver.receivedAlg = WOLFCOSE_ALG_UNSET; + testRet = wc_EatPsaToken_VerifyByUeid(test_eat_psa_resolve, + &resolver, legacyToken, legacyTokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && resolver.called == 1 && + resolver.receivedProfile == + WOLFCOSE_EAT_PSA_PROFILE_OLD && + resolver.receivedAlg == WOLFCOSE_ALG_HMAC_256_256, + "resolve legacy Mac0 key by UEID and COSE algorithm"); + } +#endif + } + if (ret == WOLFCOSE_SUCCESS) { + if ((tokenLen > 6u) && (tokenBuf[0] == 0xD1u) && + (tokenBuf[1] == 0x84u) && (tokenBuf[2] == 0x43u) && + (tokenBuf[3] == 0xA1u) && (tokenBuf[4] == 0x01u) && + (tokenBuf[5] == 0x05u)) { + (void)memcpy(modifiedToken, tokenBuf, tokenLen); + modifiedToken[5] = 0x27u; + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(&key, modifiedToken, tokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG && + test_eat_psa_token_is_zero(&token) != 0, + "reject unsupported PSA Mac0 algorithm before MAC verification"); + } + else { + TEST_ASSERT(0, "expected deterministic Mac0 protected header"); + } + } + if (ret == WOLFCOSE_E_COSE_BAD_ALG) { + (void)memcpy(modifiedToken, tokenBuf, tokenLen); + modifiedToken[tokenLen - 1u] ^= 0xFFu; + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(&key, modifiedToken, tokenLen, + kNonce, sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_E_MAC_FAIL && + test_eat_psa_token_is_zero(&token) != 0, + "reject tampered PSA Mac0 tag and clear token output"); + } + (void)memset(modifiedToken, 0xA5, sizeof(modifiedToken)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateMac0(NULL, WOLFCOSE_ALG_HMAC_256_256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), + modifiedToken, sizeof(modifiedToken), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG && outLen == 0u && + test_eat_psa_bytes_are_zero(modifiedToken, + sizeof(modifiedToken)) != 0, + "clear Mac0 output after invalid arguments"); + for (argCase = 0u; argCase < 5u; argCase++) { + const WOLFCOSE_EAT_PSA_CLAIMS* argClaims = + (argCase == 0u) ? NULL : &claims; + uint8_t* argClaimsBuf = (argCase == 1u) ? NULL : claimsBuf; + uint8_t* argScratch = (argCase == 2u) ? NULL : scratch; + uint8_t* argOut = (argCase == 3u) ? NULL : modifiedToken; + size_t* argOutLen = (argCase == 4u) ? NULL : &outLen; + int expected = (argCase == 0u) ? WOLFCOSE_E_EAT_PSA_CLAIM : + WOLFCOSE_E_INVALID_ARG; + + (void)memset(modifiedToken, 0xA5, sizeof(modifiedToken)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateMac0(&key, + WOLFCOSE_ALG_HMAC_256_256, argClaims, argClaimsBuf, + sizeof(claimsBuf), argScratch, sizeof(scratch), argOut, + sizeof(modifiedToken), argOutLen); + TEST_ASSERT(ret == expected && + ((argOutLen == NULL) || (outLen == 0u)) && + ((argOut == NULL) || (test_eat_psa_bytes_are_zero( + modifiedToken, sizeof(modifiedToken)) != 0)), + "Mac0 wrapper rejects each missing required pointer"); + } + (void)memset(modifiedToken, 0xA5, sizeof(modifiedToken)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateMac0(&key, WOLFCOSE_ALG_ES256, &claims, + claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), modifiedToken, + sizeof(modifiedToken), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG && outLen == 0u && + test_eat_psa_bytes_are_zero(modifiedToken, + sizeof(modifiedToken)) != 0, + "clear Mac0 output after unsupported algorithm"); + (void)memset(modifiedToken, 0xA5, sizeof(modifiedToken)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateMac0(&key, WOLFCOSE_ALG_HMAC_256_256, + &claims, claimsBuf, 1u, scratch, sizeof(scratch), modifiedToken, + sizeof(modifiedToken), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL && outLen == 0u && + test_eat_psa_bytes_are_zero(modifiedToken, + sizeof(modifiedToken)) != 0, + "clear Mac0 output after claims-buffer failure"); + (void)memset(modifiedToken, 0xA5, sizeof(modifiedToken)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateMac0(&key, WOLFCOSE_ALG_HMAC_256_256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, 1u, modifiedToken, + sizeof(modifiedToken), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL && outLen == 0u && + test_eat_psa_bytes_are_zero(modifiedToken, + sizeof(modifiedToken)) != 0, + "clear Mac0 output after scratch-buffer failure"); + (void)memset(modifiedToken, 0xA5, sizeof(modifiedToken)); + outLen = 17u; + ret = wc_CoseEatPsaToken_CreateMac0(&key, WOLFCOSE_ALG_HMAC_256_256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), + modifiedToken, 1u, &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL && outLen == 0u && + test_eat_psa_bytes_are_zero(modifiedToken, 1u) != 0, + "clear Mac0 output after token-buffer failure"); + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } +} +#endif /* WOLFCOSE_MAC0_CREATE && WOLFCOSE_MAC0_VERIFY && HMAC256 */ + +#if defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_MAC0_VERIFY) && \ + defined(WOLFCOSE_HAVE_HMAC256) && \ + (defined(WOLFCOSE_HAVE_HMAC384) || defined(WOLFCOSE_HAVE_HMAC512)) +static void test_eat_psa_mac_alg(int32_t alg, size_t keyLen, const char* name) +{ + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_EAT_PSA_TOKEN token; + WOLFCOSE_KEY key; + uint8_t hmacKey[64] = { 0 }; + uint8_t claimsBuf[768]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t tokenBuf[1024]; + size_t tokenLen = 0u; + int ret; + int keyInited = 0; + + (void)printf(" [RFC 9783 Mac0 %s]\n", name); + test_eat_psa_claims(&claims, &component); + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetSymmetric(&key, hmacKey, keyLen); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_CreateMac0(&key, alg, &claims, claimsBuf, + sizeof(claimsBuf), scratch, sizeof(scratch), tokenBuf, + sizeof(tokenBuf), &tokenLen); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && tokenLen > 0u, + "create current TFM Mac0 token"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_EatPsaToken_Verify(&key, tokenBuf, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + token.protection == WOLFCOSE_EAT_PSA_PROTECTION_MAC0, + "verify current TFM Mac0 token"); + } + + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } +} +#endif + +#endif /* WOLFCOSE_EAT_PSA and test crypto */ + +int test_eat_psa(void) +{ + g_failures = 0; +#if defined(WOLFCOSE_TEST_EAT_PSA_FULL) +#if defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) + test_eat_psa_rfc9783_sign1(); + #if defined(WOLFCOSE_SIGN1_SIGN) + test_eat_psa_sign1(); + #if defined(WOLFCOSE_EXT_SIGN) + test_eat_psa_delegated_signer(); + #endif + #endif + #if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_HAVE_ES384) + test_eat_psa_sign_alg(WOLFCOSE_ALG_ES384, 48, ECC_SECP384R1, + WOLFCOSE_CRV_P384, "ES384"); + #endif + #if defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_HAVE_ES512) + test_eat_psa_sign_alg(WOLFCOSE_ALG_ES512, 66, ECC_SECP521R1, + WOLFCOSE_CRV_P521, "ES512"); + #endif + #endif + #if defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256) + test_eat_psa_rfc9783_mac0(); + #if defined(WOLFCOSE_MAC0_CREATE) + test_eat_psa_mac0(); + #if defined(WOLFCOSE_HAVE_HMAC384) + test_eat_psa_mac_alg(WOLFCOSE_ALG_HMAC_384_384, 48u, "HMAC384/384"); + #endif + #if defined(WOLFCOSE_HAVE_HMAC512) + test_eat_psa_mac_alg(WOLFCOSE_ALG_HMAC_512_512, 64u, "HMAC512/512"); + #endif + #endif + #endif +#else + (void)printf(" PSA/EAT unavailable in this build\n"); +#endif + return g_failures; +} diff --git a/tests/test_eat_psa_curve_gates.c b/tests/test_eat_psa_curve_gates.c new file mode 100644 index 0000000..1671c42 --- /dev/null +++ b/tests/test_eat_psa_curve_gates.c @@ -0,0 +1,29 @@ +/* test_eat_psa_curve_gates.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + */ + +#include + +#if !defined(WOLFCOSE_EAT_PSA) + #error "PSA/EAT feature matrix must select PSA/EAT" +#endif + +#if !defined(WOLFCOSE_HAVE_ES256) + #error "P-256-only wolfSSL configuration must retain ES256" +#endif + +#if defined(WOLFCOSE_HAVE_ES384) || defined(WOLFCOSE_HAVE_ES512) + #error "P-256-only wolfSSL configuration must not expose ES384 or ES512" +#endif + +#if defined(WOLFCOSE_EAT_PSA_TFM_FULL) + #error "P-256-only receiver must not claim RFC 9783 #tfm conformance" +#endif + +int test_eat_psa_curve_gates(void) +{ + return 0; +} diff --git a/tests/test_eat_psa_derived_gate.c b/tests/test_eat_psa_derived_gate.c new file mode 100644 index 0000000..cf2e62d --- /dev/null +++ b/tests/test_eat_psa_derived_gate.c @@ -0,0 +1,29 @@ +/* test_eat_psa_derived_gate.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + */ + +/* The feature-matrix target compiles this translation unit with a partial + * receiver and a caller-supplied WOLFCOSE_EAT_PSA_TFM_FULL. It must fail: + * #tfm capability is derived by settings.h, never caller selectable. + * + * A second target combines HAVE_ALL_CURVES with an explicit NO_ECC256. The + * explicit P-256 exclusion must win, so neither ES256 nor the complete #tfm + * receiver capability may be derived. */ +#include + +#if defined(WOLFCOSE_TEST_NO_ECC256_ALL_CURVES) + #if defined(WOLFCOSE_HAVE_ES256) + #error "NO_ECC256 must disable ES256 even with HAVE_ALL_CURVES" + #endif + #if defined(WOLFCOSE_EAT_PSA_TFM_FULL) + #error "NO_ECC256 must prevent complete #tfm receiver capability" + #endif +#endif + +int test_eat_psa_derived_gate(void) +{ + return 0; +} diff --git a/tests/test_eat_psa_hash_gates.c b/tests/test_eat_psa_hash_gates.c new file mode 100644 index 0000000..18e98a3 --- /dev/null +++ b/tests/test_eat_psa_hash_gates.c @@ -0,0 +1,32 @@ +/* test_eat_psa_hash_gates.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + */ + +/* Compile this with NO_SHA256 and the current Sign1/Mac0 feature selection. + * RFC 9783 Section 5.2 requires both ES256 and HMAC-256, so this configuration + * must never advertise the standardized #tfm receiver profile. */ +#include + +#if !defined(WOLFCOSE_EAT_PSA) + #error "PSA/EAT feature matrix must select PSA/EAT" +#endif + +#if defined(WOLFCOSE_HAVE_ES256) + #error "NO_SHA256 must remove ES256 support" +#endif + +#if defined(WOLFCOSE_HAVE_HMAC256) + #error "NO_SHA256 must remove HMAC-256 support" +#endif + +#if defined(WOLFCOSE_EAT_PSA_TFM_FULL) + #error "NO_SHA256 receiver must not claim RFC 9783 #tfm conformance" +#endif + +int test_eat_psa_hash_gates(void) +{ + return 0; +} diff --git a/tests/test_eat_psa_limits.c b/tests/test_eat_psa_limits.c new file mode 100644 index 0000000..7b7627e --- /dev/null +++ b/tests/test_eat_psa_limits.c @@ -0,0 +1,282 @@ +/* test_eat_psa_limits.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + */ + +/* Exercise the claim-map ceilings with deliberately small, independently + * compiled limits. The normal RFC 9783 suite uses production defaults, while + * this test proves that each boundary check is reached before parser work can + * grow beyond the configured bound. */ + +#include +#include + +#include + +#if defined(WOLFCOSE_TEST_EAT_PSA_LIMITS) && defined(WOLFCOSE_EAT_PSA) + +#if !defined(WOLFCOSE_EAT_PSA_TFM_FULL) + #error "claim-limit test requires the full RFC 9783 #tfm receiver profile" +#endif + +#define EAT_LIMITS_OUTER_REQUIRED 7u +#define EAT_LIMITS_COMPONENT_REQUIRED 2u + +enum test_eat_psa_limit_case { + TEST_EAT_PSA_LIMIT_EXACT = 0, + TEST_EAT_PSA_LIMIT_TOP_PLUS_ONE = 1, + TEST_EAT_PSA_LIMIT_COMPONENT_PLUS_ONE = 2 +}; + +static const uint8_t kLimitsNonce[32] = { 0x11u }; +static const uint8_t kLimitsUeid[33] = { 0x01u, 0x22u }; +static const uint8_t kLimitsImplementationId[32] = { 0x33u }; +static const uint8_t kLimitsMeasurement[32] = { 0x55u }; +static const uint8_t kLimitsSignerId[32] = { 0x66u }; +static const uint8_t kLimitsProfile[] = WOLFCOSE_EAT_PSA_PROFILE_TFM; +static int g_limit_failures = 0; + +#define LIMIT_ASSERT(cond, name) do { \ + if (!(cond)) { \ + (void)printf(" FAIL: %s (line %d)\n", name, __LINE__); \ + g_limit_failures++; \ + } \ +} while (0) + +static int test_eat_psa_limits_token_is_zero( + const WOLFCOSE_EAT_PSA_TOKEN* token) +{ + WOLFCOSE_EAT_PSA_TOKEN zero; + + (void)memset(&zero, 0, sizeof(zero)); + return ((token != NULL) && + (memcmp(token, &zero, sizeof(zero)) == 0)) ? 1 : 0; +} + +static int test_eat_psa_limits_encode_component(WOLFCOSE_CBOR_CTX* ctx, + int overLimit) +{ + int ret; + size_t i; + size_t count = overLimit != 0 ? + WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS + 1u : + WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS; + + ret = wc_CBOR_EncodeMapStart(ctx, count); + if (overLimit != 0) { + /* The generic CBOR container check needs one remaining byte per + * declared pair. The EAT component ceiling is tested before parsing + * any pair, so these values intentionally need not form valid pairs. */ + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + ret = wc_CBOR_EncodeUint(ctx, 0u); + } + } + else { + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, 2u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, kLimitsMeasurement, + sizeof(kLimitsMeasurement)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, 5u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, kLimitsSignerId, + sizeof(kLimitsSignerId)); + } + for (i = EAT_LIMITS_COMPONENT_REQUIRED; + (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + ret = wc_CBOR_EncodeUint(ctx, 70000u + (uint64_t)i); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, (uint64_t)i); + } + } + } + + return ret; +} + +static int test_eat_psa_limits_encode_current(WOLFCOSE_CBOR_CTX* ctx, + int componentOverLimit) +{ + int ret; + size_t i; + + ret = wc_CBOR_EncodeUint(ctx, 10u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, kLimitsNonce, sizeof(kLimitsNonce)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, 256u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, kLimitsUeid, sizeof(kLimitsUeid)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, 265u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(ctx, kLimitsProfile, + sizeof(kLimitsProfile) - 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, 2394u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(ctx, -1); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, 2395u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, 0x3000u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, 2396u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(ctx, kLimitsImplementationId, + sizeof(kLimitsImplementationId)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, 2399u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(ctx, 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = test_eat_psa_limits_encode_component(ctx, componentOverLimit); + } + for (i = EAT_LIMITS_OUTER_REQUIRED; + (ret == WOLFCOSE_SUCCESS) && + (i < WOLFCOSE_EAT_PSA_MAX_CLAIMS); i++) { + ret = wc_CBOR_EncodeUint(ctx, 80000u + (uint64_t)i); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(ctx, (uint64_t)i); + } + } + + return ret; +} + +static int test_eat_psa_limits_encode_payload( + enum test_eat_psa_limit_case which, uint8_t* out, size_t outSz, + size_t* outLen) +{ + WOLFCOSE_CBOR_CTX ctx; + int ret; + size_t i; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((out == NULL) || (outLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = wc_CBOR_EncoderInit(&ctx, out, outSz); + } + if ((ret == WOLFCOSE_SUCCESS) && + (which == TEST_EAT_PSA_LIMIT_TOP_PLUS_ONE)) { + ret = wc_CBOR_EncodeMapStart(&ctx, + WOLFCOSE_EAT_PSA_MAX_CLAIMS + 1u); + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && + (i < (WOLFCOSE_EAT_PSA_MAX_CLAIMS + 1u)); i++) { + ret = wc_CBOR_EncodeUint(&ctx, 0u); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + (which != TEST_EAT_PSA_LIMIT_TOP_PLUS_ONE)) { + ret = wc_CBOR_EncodeMapStart(&ctx, WOLFCOSE_EAT_PSA_MAX_CLAIMS); + } + if ((ret == WOLFCOSE_SUCCESS) && + (which != TEST_EAT_PSA_LIMIT_TOP_PLUS_ONE)) { + ret = test_eat_psa_limits_encode_current(&ctx, + which == TEST_EAT_PSA_LIMIT_COMPONENT_PLUS_ONE ? 1 : 0); + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = ctx.idx; + } + + return ret; +} + +static void test_eat_psa_limits_run_case(const WOLFCOSE_KEY* key, + enum test_eat_psa_limit_case which, int expected, const char* name) +{ + WOLFCOSE_EAT_PSA_TOKEN token; + uint8_t payload[1024]; + uint8_t message[1536]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + size_t payloadLen = 0u; + size_t messageLen = 0u; + int ret; + + ret = test_eat_psa_limits_encode_payload(which, payload, sizeof(payload), + &payloadLen); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseMac0_Create(key, WOLFCOSE_ALG_HMAC_256_256, NULL, 0u, + payload, payloadLen, NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + message, sizeof(message), &messageLen); + } + LIMIT_ASSERT(ret == WOLFCOSE_SUCCESS && messageLen > 0u, name); + if (ret == WOLFCOSE_SUCCESS) { + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, message, messageLen, + kLimitsNonce, sizeof(kLimitsNonce), scratch, sizeof(scratch), + &token); + LIMIT_ASSERT(ret == expected, name); + if (expected == WOLFCOSE_SUCCESS) { + LIMIT_ASSERT(token.componentCount == 1u, name); + } + else { + LIMIT_ASSERT(test_eat_psa_limits_token_is_zero(&token) != 0, + name); + } + } +} + +int main(void) +{ + WOLFCOSE_KEY key; + uint8_t hmacKey[32] = { 0xA5u }; + int ret; + int keyInited = 0; + + (void)printf("=== wolfCOSE PSA/EAT Claim-Limit Tests ===\n\n"); + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetSymmetric(&key, hmacKey, sizeof(hmacKey)); + } + LIMIT_ASSERT(ret == WOLFCOSE_SUCCESS, "initialize HMAC key"); + if (ret == WOLFCOSE_SUCCESS) { + test_eat_psa_limits_run_case(&key, TEST_EAT_PSA_LIMIT_EXACT, + WOLFCOSE_SUCCESS, "accept exact claim and component limits"); + test_eat_psa_limits_run_case(&key, TEST_EAT_PSA_LIMIT_TOP_PLUS_ONE, + WOLFCOSE_E_EAT_PSA_CLAIM, "reject claim-map limit plus one"); + test_eat_psa_limits_run_case(&key, + TEST_EAT_PSA_LIMIT_COMPONENT_PLUS_ONE, WOLFCOSE_E_EAT_PSA_CLAIM, + "reject component-map limit plus one"); + } + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } + + (void)printf("\n=== Results: %s ===\n", + g_limit_failures == 0 ? "ALL PASSED" : "FAILURES"); + return g_limit_failures == 0 ? 0 : 1; +} + +#else + +int main(void) +{ + return 0; +} + +#endif /* WOLFCOSE_TEST_EAT_PSA_LIMITS && WOLFCOSE_EAT_PSA */ diff --git a/tests/test_eat_psa_min_key_gates.c b/tests/test_eat_psa_min_key_gates.c new file mode 100644 index 0000000..1dedb63 --- /dev/null +++ b/tests/test_eat_psa_min_key_gates.c @@ -0,0 +1,59 @@ +/* test_eat_psa_min_key_gates.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + */ + +#include + +#if !defined(ECC_MIN_KEY_SZ) + #error "ECC_MIN_KEY_SZ boundary test needs an explicit minimum" +#endif + +#if ECC_MIN_KEY_SZ == 256 + #if !defined(WOLFCOSE_HAVE_ES256) || !defined(WOLFCOSE_HAVE_ES384) || \ + !defined(WOLFCOSE_HAVE_ES512) + #error "256-bit minimum must retain ES256, ES384, and ES512" + #endif + #if !defined(WOLFCOSE_EAT_PSA_TFM_FULL) + #error "256-bit minimum must retain complete #tfm capability" + #endif +#elif ECC_MIN_KEY_SZ == 257 + #if defined(WOLFCOSE_HAVE_ES256) + #error "257-bit minimum must disable ES256" + #endif + #if !defined(WOLFCOSE_HAVE_ES384) || !defined(WOLFCOSE_HAVE_ES512) + #error "257-bit minimum must retain ES384 and ES512" + #endif +#elif ECC_MIN_KEY_SZ == 384 + #if defined(WOLFCOSE_HAVE_ES256) + #error "384-bit minimum must disable ES256" + #endif + #if !defined(WOLFCOSE_HAVE_ES384) || !defined(WOLFCOSE_HAVE_ES512) + #error "384-bit minimum must retain ES384 and ES512" + #endif +#elif ECC_MIN_KEY_SZ == 521 + #if defined(WOLFCOSE_HAVE_ES256) || defined(WOLFCOSE_HAVE_ES384) + #error "521-bit minimum must disable ES256 and ES384" + #endif + #if !defined(WOLFCOSE_HAVE_ES512) + #error "521-bit minimum must retain ES512" + #endif +#elif ECC_MIN_KEY_SZ == 522 + #if defined(WOLFCOSE_HAVE_ES256) || defined(WOLFCOSE_HAVE_ES384) || \ + defined(WOLFCOSE_HAVE_ES512) + #error "minimum above P-521 must disable every ECDSA algorithm" + #endif +#else + #error "unexpected ECC_MIN_KEY_SZ boundary" +#endif + +#if (ECC_MIN_KEY_SZ != 256) && defined(WOLFCOSE_EAT_PSA_TFM_FULL) + #error "restricted ECC minimum must prevent complete #tfm capability" +#endif + +int test_eat_psa_min_key_gates(void) +{ + return 0; +} diff --git a/tests/test_eat_psa_profile_main.c b/tests/test_eat_psa_profile_main.c new file mode 100644 index 0000000..c44aa61 --- /dev/null +++ b/tests/test_eat_psa_profile_main.c @@ -0,0 +1,22 @@ +/* test_eat_psa_profile_main.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + */ + +#include + +#include "test_suite.h" + +int main(void) +{ + int failures; + + (void)printf("=== wolfCOSE PSA/EAT Feature Profile Tests ===\n\n"); + failures = test_eat_psa_profiles(); + (void)printf("\n=== Results: %s ===\n", + (failures == 0) ? "ALL PASSED" : "FAILURES"); + + return (failures == 0) ? 0 : 1; +} diff --git a/tests/test_eat_psa_profiles.c b/tests/test_eat_psa_profiles.c new file mode 100644 index 0000000..cf7518d --- /dev/null +++ b/tests/test_eat_psa_profiles.c @@ -0,0 +1,675 @@ +/* test_eat_psa_profiles.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfCOSE. + */ + +/* Runtime coverage for deliberately small PSA/EAT builds. The normal + * conformance suite enables every feature; this companion verifies that an + * application can retain just one envelope/profile and that a disabled + * envelope is refused before any cryptographic operation. */ + +#include +#include + +#include +#include +#ifdef HAVE_ECC + #include +#endif + +#include "test_suite.h" + +#if defined(WOLFCOSE_TEST_EAT_PSA_PROFILES) && defined(WOLFCOSE_EAT_PSA) + +static int g_profile_failures = 0; + +#define PROFILE_ASSERT(cond, name) do { \ + if (!(cond)) { \ + (void)printf(" FAIL: %s (line %d)\n", name, __LINE__); \ + g_profile_failures++; \ + } \ +} while (0) + +static const uint8_t kProfileNonce[32] = { 0x11u }; +static const uint8_t kProfileUeid[33] = { 0x01u, 0x22u }; +static const uint8_t kProfileImplementationId[32] = { 0x33u }; +static const uint8_t kProfileMeasurement[32] = { 0x55u }; +static const uint8_t kProfileSignerId[32] = { 0x66u }; +static const uint8_t kProfileBootSeed[32] = { 0x44u }; + +static int test_eat_psa_profile_token_is_zero( + const WOLFCOSE_EAT_PSA_TOKEN* token) +{ + WOLFCOSE_EAT_PSA_TOKEN zero; + + (void)memset(&zero, 0, sizeof(zero)); + return ((token != NULL) && + (memcmp(token, &zero, sizeof(zero)) == 0)) ? 1 : 0; +} + +static void test_eat_psa_profile_expect_error( + const WOLFCOSE_KEY* key, const uint8_t* message, size_t messageLen, + uint8_t* scratch, size_t scratchSz, int expected, const char* name) +{ + WOLFCOSE_EAT_PSA_TOKEN token; + int ret; + + (void)memset(&token, 0xA5, sizeof(token)); + ret = wc_CoseEatPsaToken_Verify(key, message, messageLen, kProfileNonce, + sizeof(kProfileNonce), scratch, scratchSz, &token); + PROFILE_ASSERT(ret == expected && + test_eat_psa_profile_token_is_zero(&token) != 0, + name); +} + +#if defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + defined(WOLFCOSE_EAT_PSA_ISSUE) +static void test_eat_psa_profile_current_claims( + WOLFCOSE_EAT_PSA_CLAIMS* claims, WOLFCOSE_EAT_PSA_COMPONENT* component) +{ + (void)memset(claims, 0, sizeof(*claims)); + (void)memset(component, 0, sizeof(*component)); + component->measurementValue.data = kProfileMeasurement; + component->measurementValue.len = sizeof(kProfileMeasurement); + component->signerId.data = kProfileSignerId; + component->signerId.len = sizeof(kProfileSignerId); + claims->nonce.data = kProfileNonce; + claims->nonce.len = sizeof(kProfileNonce); + claims->ueid.data = kProfileUeid; + claims->ueid.len = sizeof(kProfileUeid); + claims->implementationId.data = kProfileImplementationId; + claims->implementationId.len = sizeof(kProfileImplementationId); + claims->clientId = -1; + claims->lifecycle = 0x3000u; + claims->components = component; + claims->componentCount = 1u; +} +#endif + +/* Build authenticated payloads with the public CBOR encoder so the legacy-only + * profile test can distinguish a disabled current profile from a bad + * signature. */ +#if defined(WOLFCOSE_EAT_PSA_LEGACY) && defined(WOLFCOSE_CBOR_ENCODE) && \ + ((defined(WOLFCOSE_EAT_PSA_SIGN1) && \ + defined(WOLFCOSE_SIGN1_SIGN)) || \ + (defined(WOLFCOSE_EAT_PSA_MAC0) && defined(WOLFCOSE_MAC0_CREATE))) +static int test_eat_psa_encode_current_tfm(uint8_t* out, size_t outSz, + size_t* outLen) +{ + static const uint8_t profile[] = WOLFCOSE_EAT_PSA_PROFILE_TFM; + WOLFCOSE_CBOR_CTX ctx; + int ret; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((out == NULL) || (outLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = wc_CBOR_EncoderInit(&ctx, out, outSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, 7u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 10u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileNonce, sizeof(kProfileNonce)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 256u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileUeid, sizeof(kProfileUeid)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 265u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&ctx, profile, sizeof(profile) - 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 2394u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -1); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 2395u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 0x3000u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 2396u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileImplementationId, + sizeof(kProfileImplementationId)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 2399u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, 2u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 2u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileMeasurement, + sizeof(kProfileMeasurement)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 5u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileSignerId, + sizeof(kProfileSignerId)); + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = ctx.idx; + } + + return ret; +} +#endif + +static int test_eat_psa_encode_legacy_profile(uint8_t* out, size_t outSz, + size_t* outLen) +{ + WOLFCOSE_CBOR_CTX ctx; + int ret; + + if (outLen != NULL) { + *outLen = 0u; + } + if ((out == NULL) || (outLen == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = wc_CBOR_EncoderInit(&ctx, out, outSz); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, 7u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75008); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileNonce, sizeof(kProfileNonce)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75009); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileUeid, sizeof(kProfileUeid)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75001); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -1); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75002); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 0x3000u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75003); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileImplementationId, + sizeof(kProfileImplementationId)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75004); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileBootSeed, + sizeof(kProfileBootSeed)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, -75006); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&ctx, 2u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 2u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileMeasurement, + sizeof(kProfileMeasurement)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeUint(&ctx, 5u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, kProfileSignerId, + sizeof(kProfileSignerId)); + } + if (ret == WOLFCOSE_SUCCESS) { + *outLen = ctx.idx; + } + + return ret; +} + +#if defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) && \ + defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) +static void test_eat_psa_current_sign1_only(void) +{ + static const uint8_t disabledMac0[] = { + 0xD1u, 0x84u, 0x43u, 0xA1u, 0x01u, 0x05u, + 0xA0u, 0x40u, 0x40u + }; + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_KEY key; + WOLFCOSE_HDR hdr = { 0 }; + ecc_key ecc; + WC_RNG rng; + uint8_t claimsBuf[512]; + uint8_t payload[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t message[1024]; + const uint8_t* genericPayload = NULL; + size_t genericPayloadLen = 0u; + size_t payloadLen = 0u; + size_t messageLen = 0u; + int ret; + int testRet; + int rngInited = 0; + int eccInited = 0; + int keyInited = 0; + int setupOk = 0; + + (void)printf(" [current Sign1-only runtime profile]\n"); + test_eat_psa_profile_current_claims(&claims, &component); + ret = wc_InitRng(&rng); + if (ret == 0) { + rngInited = 1; + ret = wc_ecc_init(&ecc); + } + if (ret == 0) { + eccInited = 1; + ret = wc_ecc_make_key_ex(&rng, 32, &ecc, ECC_SECP256R1); + } + if (ret == 0) { + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &ecc); + } + } + if (ret == WOLFCOSE_SUCCESS) { + setupOk = 1; + } + PROFILE_ASSERT(setupOk != 0, "initialize Sign1 profile key"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_EncodeClaims(&claims, payload, + sizeof(payload), &payloadLen); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && payloadLen > 0u, + "encode #tfm claims in Sign1-only sender build"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_CreateSign1(&key, WOLFCOSE_ALG_ES256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), + message, sizeof(message), &messageLen, &rng); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && messageLen > 0u, + "issue #tfm Sign1 in Sign1-only sender build"); + if ((messageLen > 0u) && (setupOk != 0)) { + testRet = wc_CoseSign1_Verify(&key, message, messageLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &genericPayload, + &genericPayloadLen); + PROFILE_ASSERT(testRet == WOLFCOSE_SUCCESS && + genericPayloadLen == payloadLen && + memcmp(genericPayload, payload, payloadLen) == 0, + "verify Sign1-only sender token with generic COSE"); + test_eat_psa_profile_expect_error(&key, message, messageLen, + scratch, sizeof(scratch), WOLFCOSE_E_EAT_PSA_PROFILE, + "reject #tfm without every RFC 9783 envelope and algorithm"); + } + + ret = test_eat_psa_encode_legacy_profile(payload, sizeof(payload), + &payloadLen); + if ((ret == WOLFCOSE_SUCCESS) && (setupOk != 0)) { + ret = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, payload, + payloadLen, NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + message, sizeof(message), &messageLen, &rng); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && messageLen > 0u, + "construct authenticated legacy Sign1 for disabled-profile rejection"); + if ((ret == WOLFCOSE_SUCCESS) && (messageLen > 0u) && (setupOk != 0)) { + test_eat_psa_profile_expect_error(&key, message, messageLen, + scratch, sizeof(scratch), WOLFCOSE_E_EAT_PSA_PROFILE, + "reject recognized legacy labels when legacy support is disabled"); + } + if (setupOk != 0) { + test_eat_psa_profile_expect_error(&key, disabledMac0, + sizeof(disabledMac0), scratch, sizeof(scratch), + WOLFCOSE_E_UNSUPPORTED, + "reject Mac0 when compiled out of Sign1-only build"); + } + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } + if (eccInited != 0) { + (void)wc_ecc_free(&ecc); + } + if (rngInited != 0) { + (void)wc_FreeRng(&rng); + } +} +#endif + +#if defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) && \ + defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256) +static void test_eat_psa_current_mac0_only(void) +{ + static const uint8_t disabledSign1[] = { + 0xD2u, 0x84u, 0x43u, 0xA1u, 0x01u, 0x26u, + 0xA0u, 0x40u, 0x40u + }; + WOLFCOSE_EAT_PSA_CLAIMS claims; + WOLFCOSE_EAT_PSA_COMPONENT component; + WOLFCOSE_KEY key; + WOLFCOSE_HDR hdr = { 0 }; + uint8_t hmacKey[32] = { 0 }; + uint8_t claimsBuf[512]; + uint8_t payload[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t message[1024]; + const uint8_t* genericPayload = NULL; + size_t genericPayloadLen = 0u; + size_t payloadLen = 0u; + size_t messageLen = 0u; + int ret; + int testRet; + int keyInited = 0; + int setupOk = 0; + + (void)printf(" [current Mac0-only runtime profile]\n"); + test_eat_psa_profile_current_claims(&claims, &component); + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetSymmetric(&key, hmacKey, sizeof(hmacKey)); + } + if (ret == WOLFCOSE_SUCCESS) { + setupOk = 1; + } + PROFILE_ASSERT(setupOk != 0, "initialize Mac0 profile key"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_EncodeClaims(&claims, payload, + sizeof(payload), &payloadLen); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && payloadLen > 0u, + "encode #tfm claims in Mac0-only sender build"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_CreateMac0(&key, + WOLFCOSE_ALG_HMAC_256_256, &claims, claimsBuf, + sizeof(claimsBuf), scratch, sizeof(scratch), message, + sizeof(message), &messageLen); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && messageLen > 0u, + "issue #tfm Mac0 in Mac0-only sender build"); + if ((messageLen > 0u) && (setupOk != 0)) { + testRet = wc_CoseMac0_Verify(&key, message, messageLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), &hdr, &genericPayload, + &genericPayloadLen); + PROFILE_ASSERT(testRet == WOLFCOSE_SUCCESS && + genericPayloadLen == payloadLen && + memcmp(genericPayload, payload, payloadLen) == 0, + "verify Mac0-only sender token with generic COSE"); + test_eat_psa_profile_expect_error(&key, message, messageLen, + scratch, sizeof(scratch), WOLFCOSE_E_EAT_PSA_PROFILE, + "reject #tfm without every RFC 9783 envelope and algorithm"); + } + + ret = test_eat_psa_encode_legacy_profile(payload, sizeof(payload), + &payloadLen); + if ((ret == WOLFCOSE_SUCCESS) && (setupOk != 0)) { + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, NULL, 0u, + payload, payloadLen, NULL, 0u, NULL, 0u, scratch, + sizeof(scratch), message, sizeof(message), &messageLen); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && messageLen > 0u, + "construct authenticated legacy Mac0 for disabled-profile rejection"); + if ((ret == WOLFCOSE_SUCCESS) && (messageLen > 0u) && (setupOk != 0)) { + test_eat_psa_profile_expect_error(&key, message, messageLen, + scratch, sizeof(scratch), WOLFCOSE_E_EAT_PSA_PROFILE, + "reject recognized legacy labels when legacy support is disabled"); + } + if (setupOk != 0) { + test_eat_psa_profile_expect_error(&key, disabledSign1, + sizeof(disabledSign1), scratch, sizeof(scratch), + WOLFCOSE_E_UNSUPPORTED, + "reject Sign1 when compiled out of Mac0-only build"); + } + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } +} +#endif + +#if defined(WOLFCOSE_EAT_PSA_LEGACY) && \ + defined(WOLFCOSE_EAT_PSA_SIGN1) && \ + defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) +static void test_eat_psa_legacy_sign1_only(void) +{ + WOLFCOSE_EAT_PSA_TOKEN token; + WOLFCOSE_KEY key; + ecc_key ecc; + WC_RNG rng; + uint8_t payload[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t message[1024]; + size_t payloadLen = 0u; + size_t messageLen = 0u; + int ret; + int rngInited = 0; + int eccInited = 0; + int keyInited = 0; + int setupOk = 0; + + (void)printf(" [legacy Sign1-only runtime profile]\n"); + ret = wc_InitRng(&rng); + if (ret == 0) { + rngInited = 1; + ret = wc_ecc_init(&ecc); + } + if (ret == 0) { + eccInited = 1; + ret = wc_ecc_make_key_ex(&rng, 32, &ecc, ECC_SECP256R1); + } + if (ret == 0) { + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &ecc); + } + } + if (ret == WOLFCOSE_SUCCESS) { + setupOk = 1; + } + PROFILE_ASSERT(setupOk != 0, "initialize legacy Sign1 profile key"); + if (ret == WOLFCOSE_SUCCESS) { + ret = test_eat_psa_encode_legacy_profile(payload, sizeof(payload), + &payloadLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (setupOk != 0)) { + ret = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, payload, + payloadLen, NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + message, sizeof(message), &messageLen, &rng); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && messageLen > 0u, + "construct legacy Sign1 in legacy-only build"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_Verify(&key, message, messageLen, + kProfileNonce, sizeof(kProfileNonce), scratch, sizeof(scratch), + &token); + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_OLD && + token.protection == WOLFCOSE_EAT_PSA_PROTECTION_SIGN1, + "verify legacy Sign1 in legacy-only build"); + } + + ret = test_eat_psa_encode_current_tfm(payload, sizeof(payload), + &payloadLen); + if ((ret == WOLFCOSE_SUCCESS) && (setupOk != 0)) { + ret = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, NULL, 0u, payload, + payloadLen, NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + message, sizeof(message), &messageLen, &rng); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && messageLen > 0u, + "construct authenticated #tfm Sign1 for disabled-profile rejection"); + if ((ret == WOLFCOSE_SUCCESS) && (messageLen > 0u) && (setupOk != 0)) { + test_eat_psa_profile_expect_error(&key, message, messageLen, + scratch, sizeof(scratch), WOLFCOSE_E_EAT_PSA_PROFILE, + "reject recognized current labels when current support is disabled"); + } + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } + if (eccInited != 0) { + (void)wc_ecc_free(&ecc); + } + if (rngInited != 0) { + (void)wc_FreeRng(&rng); + } +} +#endif + +#if defined(WOLFCOSE_EAT_PSA_LEGACY) && \ + defined(WOLFCOSE_EAT_PSA_MAC0) && \ + defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_MAC0_VERIFY) && \ + defined(WOLFCOSE_HAVE_HMAC256) +static void test_eat_psa_legacy_mac0_only(void) +{ + static const uint8_t disabledSign1[] = { + 0xD2u, 0x84u, 0x43u, 0xA1u, 0x01u, 0x26u, + 0xA0u, 0x40u, 0x40u + }; + WOLFCOSE_EAT_PSA_TOKEN token; + WOLFCOSE_KEY key; + uint8_t hmacKey[32] = { 0 }; + uint8_t payload[512]; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t message[1024]; + size_t payloadLen = 0u; + size_t messageLen = 0u; + int ret; + int keyInited = 0; + int setupOk = 0; + + (void)printf(" [legacy Mac0-only runtime profile]\n"); + ret = wc_CoseKey_Init(&key); + if (ret == WOLFCOSE_SUCCESS) { + keyInited = 1; + ret = wc_CoseKey_SetSymmetric(&key, hmacKey, sizeof(hmacKey)); + } + if (ret == WOLFCOSE_SUCCESS) { + setupOk = 1; + } + PROFILE_ASSERT(setupOk != 0, "initialize legacy Mac0 profile key"); + if (ret == WOLFCOSE_SUCCESS) { + ret = test_eat_psa_encode_legacy_profile(payload, sizeof(payload), + &payloadLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (setupOk != 0)) { + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, NULL, 0u, + payload, payloadLen, NULL, 0u, NULL, 0u, scratch, + sizeof(scratch), message, sizeof(message), &messageLen); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && messageLen > 0u, + "construct legacy Mac0 in legacy-only build"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_Verify(&key, message, messageLen, + kProfileNonce, sizeof(kProfileNonce), scratch, sizeof(scratch), + &token); + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && + token.profile == WOLFCOSE_EAT_PSA_PROFILE_OLD && + token.protection == WOLFCOSE_EAT_PSA_PROTECTION_MAC0, + "verify legacy Mac0 in legacy-only build"); + } + + ret = test_eat_psa_encode_current_tfm(payload, sizeof(payload), + &payloadLen); + if ((ret == WOLFCOSE_SUCCESS) && (setupOk != 0)) { + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, NULL, 0u, + payload, payloadLen, NULL, 0u, NULL, 0u, scratch, + sizeof(scratch), message, sizeof(message), &messageLen); + } + PROFILE_ASSERT(ret == WOLFCOSE_SUCCESS && messageLen > 0u, + "construct authenticated #tfm Mac0 for disabled-profile rejection"); + if ((ret == WOLFCOSE_SUCCESS) && (messageLen > 0u) && (setupOk != 0)) { + test_eat_psa_profile_expect_error(&key, message, messageLen, + scratch, sizeof(scratch), WOLFCOSE_E_EAT_PSA_PROFILE, + "reject recognized current labels when current support is disabled"); + } + if (setupOk != 0) { + test_eat_psa_profile_expect_error(&key, disabledSign1, + sizeof(disabledSign1), scratch, sizeof(scratch), + WOLFCOSE_E_UNSUPPORTED, + "reject Sign1 when compiled out of legacy Mac0-only build"); + } + if (keyInited != 0) { + wc_CoseKey_Free(&key); + } +} +#endif + +#endif /* WOLFCOSE_TEST_EAT_PSA_PROFILES && WOLFCOSE_EAT_PSA */ + +int test_eat_psa_profiles(void) +{ +#if defined(WOLFCOSE_TEST_EAT_PSA_PROFILES) && defined(WOLFCOSE_EAT_PSA) + g_profile_failures = 0; + (void)printf(" [PSA/EAT independent feature-profile tests]\n"); + + #if defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) && \ + defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) + test_eat_psa_current_sign1_only(); + #endif + #if defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) && \ + defined(WOLFCOSE_MAC0_VERIFY) && defined(WOLFCOSE_HAVE_HMAC256) + test_eat_psa_current_mac0_only(); + #endif + #if defined(WOLFCOSE_EAT_PSA_LEGACY) && \ + defined(WOLFCOSE_EAT_PSA_SIGN1) && \ + defined(WOLFCOSE_SIGN1_SIGN) && defined(WOLFCOSE_SIGN1_VERIFY) && \ + defined(WOLFCOSE_HAVE_ES256) + test_eat_psa_legacy_sign1_only(); + #endif + #if defined(WOLFCOSE_EAT_PSA_LEGACY) && \ + defined(WOLFCOSE_EAT_PSA_MAC0) && \ + defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_MAC0_VERIFY) && \ + defined(WOLFCOSE_HAVE_HMAC256) + test_eat_psa_legacy_mac0_only(); + #endif + + return g_profile_failures; +#else + return 0; +#endif +} diff --git a/tests/test_main.c b/tests/test_main.c index 76bacc6..cbf1b32 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -41,6 +41,10 @@ int main(void) printf("\n--- Interoperability Tests ---\n"); failures += test_interop(); + printf("\n--- PSA/EAT Tests ---\n"); + failures += test_eat_psa(); + failures += test_eat_psa_profiles(); + printf("\n=== Results: %s ===\n", (failures == 0) ? "ALL PASSED" : "FAILURES"); diff --git a/tests/test_suite.h b/tests/test_suite.h index bbdd548..5705653 100644 --- a/tests/test_suite.h +++ b/tests/test_suite.h @@ -26,5 +26,7 @@ int test_cbor(void); int test_cose(void); int test_interop(void); +int test_eat_psa(void); +int test_eat_psa_profiles(void); #endif /* WOLFCOSE_TEST_SUITE_H */ diff --git a/tools/wolfcose_tool.c b/tools/wolfcose_tool.c index f58ad81..1daae25 100644 --- a/tools/wolfcose_tool.c +++ b/tools/wolfcose_tool.c @@ -1011,16 +1011,17 @@ static int tool_info(const char* inPath) printf("COSE message: %zu bytes\n", msgLen); - ctx.buf = msgBuf; - ctx.bufSz = msgLen; - ctx.idx = 0; + ret = wc_CBOR_DecoderInit(&ctx, msgBuf, msgLen); + if (ret != WOLFCOSE_SUCCESS) { + return EXIT_CRYPTO; + } while (ctx.idx < ctx.bufSz) { size_t pos = ctx.idx; ret = wc_CBOR_DecodeHead(&ctx, &item); if (ret != 0) { printf(" [decode error at offset %zu: %d]\n", pos, ret); - break; + return EXIT_CRYPTO; } for (i = 0; i < (size_t)indent; i++) printf(" "); From ea77b385d8640037b0586b1533fec8c13dc98500 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 2 Sep 2026 20:20:10 -0700 Subject: [PATCH 2/6] Tighten PSA EAT feature gates --- .github/workflows/misra-2012.yml | 4 + .github/workflows/misra-2023.yml | 4 + .github/workflows/stack-bounds.yml | 2 +- .github/workflows/static-analysis.yml | 2 + ChangeLog.md | 17 +++-- EAT_PSA_TASKS.md | 83 --------------------- Makefile | 93 ++++++++++++++++++++++- docs/API-Reference.md | 36 ++++----- docs/Getting-Started.md | 19 ++--- docs/Macros.md | 61 +++++++++++---- docs/PSA-EAT.md | 48 +++++++++--- include/wolfcose/eat_psa.h | 12 ++- include/wolfcose/settings.h | 102 ++++++++++++++++---------- include/wolfcose/wolfcose.h | 13 ++-- scripts/test_pkg_config.sh | 19 ++++- src/wolfcose.c | 26 ++++++- src/wolfcose_cbor.c | 14 +++- src/wolfcose_eat_psa.c | 17 ++++- src/wolfcose_internal.h | 20 ++++- tests/test_cose.c | 28 ++++++- 20 files changed, 417 insertions(+), 203 deletions(-) delete mode 100644 EAT_PSA_TASKS.md diff --git a/.github/workflows/misra-2012.yml b/.github/workflows/misra-2012.yml index 3d357b7..1930e01 100644 --- a/.github/workflows/misra-2012.yml +++ b/.github/workflows/misra-2012.yml @@ -87,6 +87,8 @@ jobs: -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE \ -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ @@ -120,6 +122,8 @@ jobs: -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE \ -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ diff --git a/.github/workflows/misra-2023.yml b/.github/workflows/misra-2023.yml index d4d7900..5d6abd1 100644 --- a/.github/workflows/misra-2023.yml +++ b/.github/workflows/misra-2023.yml @@ -107,6 +107,8 @@ jobs: -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE \ -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ @@ -232,6 +234,8 @@ jobs: -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE \ -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ -DWOLFCOSE_SIGN -DWOLFCOSE_SIGN_SIGN -DWOLFCOSE_SIGN_VERIFY \ diff --git a/.github/workflows/stack-bounds.yml b/.github/workflows/stack-bounds.yml index fd3e8a7..0bdd089 100644 --- a/.github/workflows/stack-bounds.yml +++ b/.github/workflows/stack-bounds.yml @@ -51,7 +51,7 @@ jobs: - name: Build full PSA/EAT wolfCOSE (-Werror=vla, -fstack-usage) run: | export WOLFSSL_DIR=$HOME/wolfssl-full - EAT_FLAGS="-DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR" + EAT_FLAGS="-DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR" make CFLAGS="-std=c11 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wvla -Werror=vla -fstack-usage -I./include -I$WOLFSSL_DIR/include $EAT_FLAGS" \ LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" grep -q "wc_CoseEatPsaToken_Verify" src/wolfcose_eat_psa.su diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 37163ee..b6e1f18 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -82,6 +82,8 @@ jobs: -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE \ -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR \ diff --git a/ChangeLog.md b/ChangeLog.md index d352695..bf1b586 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,14 +18,15 @@ MAC, and key distribution, and standardized post-quantum ML-DSA signatures required structural claims and nonce, and emits current-profile tokens with one enabled RFC 9783 Sign1 or Mac0 protection path. Complete RFC 9783 `#tfm` receiver capability remains a separately derived gate. - Every PSA/EAT profile, envelope, issuer, helper, and non-core algorithm is - explicitly macro-gated and absent from a default archive. The verifier - accepts RFC-required non-preferred definite CBOR while ordinary COSE decode - remains strict. Partial builds reject `#tfm` rather than claiming incomplete - conformance; profile, map-limit, Appendix A TF-M `iat-verifier`, and t_cose - interop coverage are in CI. A device-onboarding example demonstrates current - Sign1 issuance, nonce verification, and software measurement appraisal. See - docs/PSA-EAT.md. + Every PSA/EAT profile, consume or issue envelope, helper, and non-core + algorithm is explicitly macro-gated and absent from a default archive. The + verifier accepts RFC-required non-preferred definite CBOR while ordinary + COSE decode remains strict. Its bounded text-label tracking is separately + selectable for generic COSE parsing and otherwise remains off. Partial + builds reject `#tfm` rather than claiming incomplete conformance; profile, + map-limit, Appendix A TF-M `iat-verifier`, and t_cose interop coverage are in + CI. A device-onboarding example demonstrates current Sign1 issuance, nonce + verification, and software measurement appraisal. See docs/PSA-EAT.md. * `wc_CoseSign1_Sign_ex()` can emit untagged COSE_Sign1 messages, and `wc_CoseSign1_SignSize_ex()` reports their exact encoded size without diff --git a/EAT_PSA_TASKS.md b/EAT_PSA_TASKS.md deleted file mode 100644 index 2c3936d..0000000 --- a/EAT_PSA_TASKS.md +++ /dev/null @@ -1,83 +0,0 @@ -# PSA and EAT implementation checklist - -This is the working checklist for the RFC 9783 PSA token implementation. -Items move to complete only after their associated code and test evidence are -present. - -## Core protocol - -- [x] Isolate the work from the user's existing dirty worktree. -- [x] Add an optional RFC 8949 variation tolerant decode path while retaining - strict decode as the default COSE API behavior. -- [x] Add tagged COSE Sign1 and Mac0 envelope authentication before claim use. -- [x] Add current `tag:psacertified.org,2023:psa#tfm` claim issuance. -- [x] Add current and `PSA_IOT_PROFILE_1` claim consumption. -- [x] Complete legacy-profile differences, including optional legacy profile, - fixed boot seed, old certification reference, and no-measurements form. -- [x] Enforce direct, attached token boundaries and document certificate-chain - handling. -- [x] Validate all caller-visible API argument and lifetime rules. - -## wolfTrust and PSA integration - -- [x] Provide a raw-token verifier suitable for - `psa_initial_attest_get_token()` output. -- [x] Provide UEID based caller-owned key resolution. -- [x] Verify the delegated/external signer path accepts a NULL RNG and test it - through the PSA issuer wrapper. -- [x] Add a focused PSA client token-acquisition handoff example to the - integration guide without a wolfCOSE PSA-library dependency. - -## Verification and interop - -- [x] Unit-test current Sign1, Mac0, nonce enforcement, UEID resolution, - claim duplicates, and tolerant claims decoding. -- [x] Add RFC 9783 Appendix A Sign1 and Mac0 vectors. -- [x] Add negative envelope tests: untagged/CWT-tagged/detached/x5chain, - unsupported algorithms, malformed and indefinite CBOR. -- [x] Exercise ES256/384/512 and HMAC 256/384/512 where configured. -- [x] Add a verify-only, full `#tfm` receiver build/test with no creation API. -- [x] Extend t_cose wire interop with profile payload coverage in both - directions. -- [x] Run a dedicated Codex-backed Skoll RFC 9783 conformance scan with no - findings at any severity. -- [x] Run RFC 9783's iat-verifier-generated Appendix A vectors in CI and - label them as external static vectors rather than a live tool runner. -- [x] Update coverage instrumentation and thresholds for the new source. -- [x] Run unit, C99, lean, sanitizer, macro-gate, and t_cose interop gates. -- [x] Keep variation-tolerant decoding scoped to COSE verification operations, - without putting profile state in the PSA/EAT public API. -- [x] Enforce RFC 9783 Section 5.2 `#tfm` receiver capability as a derived - full-profile gate; reject partial standard-profile consumption while - allowing an attester to issue with one RFC-permitted protection path. -- [x] Derive ECDSA support from the concrete wolfSSL P-256, P-384, and P-521 - curve configuration, including `ECC_USER_CURVES` and `ECC_MIN_KEY_SZ` - boundary feature-matrix tests. -- [x] Recognize disabled and mixed standardized claim namespaces as profile - errors rather than skipping them as extensions. -- [x] Prove variation-tolerant authenticated decoding for non-preferred claim - maps, byte strings, arrays, unsigned values, and signed values while the - ordinary public CBOR decoder remains strict. -- [x] Prove malformed routing claims are rejected before the UEID key resolver - runs, and that duplicate text labels are rejected in every header map. -- [x] Test the exact and plus-one combined protected/unprotected text-label - tracking boundary. -- [x] Cover receiver-side nonce-length mismatch, component hash boundaries, - every valid lifecycle class, and current certification-reference syntax. -- [x] Test exact and plus-one top-level/component claim-map limits in CI, and - reject every below-minimum resource limit at compile time. -- [x] Compile the enabled PSA/EAT implementation under the strict MISRA C:2023 - compiler-warning profile, not only under clang-tidy. -- [x] Run enabled PSA/EAT conformance and macro-gate tests against the supported - wolfSSL v5.8.0 compatibility floor in the version matrix. - -## Documentation and release readiness - -- [x] Write the RFC 9783 API and integration guide. -- [x] Document security boundary, policy responsibilities, modern versus - legacy behavior, supported algorithms, and unsupported trust models. -- [x] Update README, feature-macro, testing, release notes, and ChangeLog. -- [x] Add a practical current-profile issue, verify, and component-appraisal - onboarding example and run it in CI. -- [x] Final diff review, public-header audit, full validation, and Codex-backed - Skoll remediation to no validated findings above low severity. diff --git a/Makefile b/Makefile index b85f445..35def2e 100644 --- a/Makefile +++ b/Makefile @@ -92,9 +92,17 @@ EAT_PSA_FULL_FLAGS ?= -DWOLFCOSE_ENABLE_EAT_PSA \ -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE \ -DWOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER \ -DWOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR +# Remove every lean-core decode path. Issuer-only matrix builds use this to +# prove that PSA/EAT claim and envelope creation do not retain CBOR decoding. +EAT_PSA_NO_DECODE_FLAGS = -DWOLFCOSE_NO_SIGN1_VERIFY \ + -DWOLFCOSE_NO_ENCRYPT0_DECRYPT -DWOLFCOSE_NO_MAC0_VERIFY \ + -DWOLFCOSE_NO_KEY_DECODE -DWOLFCOSE_NO_CBOR_DECODE + # Exercise established generic targets against an intentionally reduced # algorithm set. PSA/EAT-specific targets select EAT_PSA_FULL_FLAGS instead. REDUCED_ALG_FLAGS = -DWOLFCOSE_NO_ES384 -DWOLFCOSE_NO_ES512 \ @@ -151,6 +159,8 @@ shared: $(OBJ) src/%.o: src/%.c src/wolfcose_internal.h include/wolfcose/wolfcose.h $(BUILD_CONFIG_CHANGED) $(BUILD_CONFIG) $(CC) $(CFLAGS) -c $< -o $@ +src/wolfcose_eat_psa.o: include/wolfcose/eat_psa.h + # --- Tests --- # Keep this synthetic policy probe independent of profiles used by the build # under test. It must exercise the local ECDSA Sign1 signing path even when the @@ -375,6 +385,7 @@ eat-psa-profile-test: $(CC) $(CFLAGS) -DWOLFCOSE_TEST_EAT_PSA_PROFILES \ -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE \ -o $(EAT_PSA_TEST_BIN) $(CORE_SRC) $(EAT_PSA_SRC) \ tests/test_eat_psa_profiles.c tests/test_eat_psa_profile_main.c \ $(LDFLAGS) $(LDLIBS) @@ -383,6 +394,7 @@ eat-psa-profile-test: $(CC) $(CFLAGS) -DWOLFCOSE_TEST_EAT_PSA_PROFILES \ -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE \ -o $(EAT_PSA_TEST_BIN) $(CORE_SRC) $(EAT_PSA_SRC) \ tests/test_eat_psa_profiles.c tests/test_eat_psa_profile_main.c \ $(LDFLAGS) $(LDLIBS) @@ -417,6 +429,8 @@ eat-psa-config-check: @echo "PASS: default library has no PSA/EAT symbols" $(MAKE) test @echo "PASS: default generic verifiers reject private PSA/EAT decode flags" + $(MAKE) test EXTRA_CFLAGS='-DWOLFCOSE_ENABLE_COSE_TEXT_LABELS' + @echo "PASS: generic COSE text-label extension is independently selectable" $(MAKE) all EXTRA_CFLAGS='$(EAT_PSA_FULL_FLAGS)' @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_Verify"; then \ echo "FAIL: enabled library omits PSA/EAT verifier"; exit 1; \ @@ -433,6 +447,36 @@ eat-psa-config-check: @if nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_"; then \ echo "FAIL: config.h-to-default build retained PSA/EAT symbols"; exit 1; \ fi + $(MAKE) all EXTRA_CFLAGS='-DWOLFCOSE_LEAN $(EAT_PSA_NO_DECODE_FLAGS) \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE' + @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_EncodeClaims"; then \ + echo "FAIL: claim-only issuer omits claim encoder"; exit 1; \ + fi + @if nm $(LIB_A) | grep -E -q "wc_CoseEatPsaToken_(Verify|Create)"; then \ + echo "FAIL: claim-only issuer contains a verifier or envelope creator"; exit 1; \ + fi + $(MAKE) all EXTRA_CFLAGS='-DWOLFCOSE_LEAN $(EAT_PSA_NO_DECODE_FLAGS) \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE' + @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_CreateSign1"; then \ + echo "FAIL: Sign1-only issuer omits Sign1 creator"; exit 1; \ + fi + @if nm $(LIB_A) | grep -E -q "wc_CoseEatPsaToken_(Verify|CreateMac0)"; then \ + echo "FAIL: Sign1-only issuer contains verifier or Mac0 creator"; exit 1; \ + fi + $(MAKE) all EXTRA_CFLAGS='-DWOLFCOSE_LEAN $(EAT_PSA_NO_DECODE_FLAGS) \ + -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE' + @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_CreateMac0"; then \ + echo "FAIL: Mac0-only issuer omits Mac0 creator"; exit 1; \ + fi + @if nm $(LIB_A) | grep -E -q "wc_CoseEatPsaToken_(Verify|CreateSign1)"; then \ + echo "FAIL: Mac0-only issuer contains verifier or Sign1 creator"; exit 1; \ + fi + $(MAKE) all $(CC) $(CFLAGS) -Werror -DWOLFCOSE_LEAN_VERIFY \ -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -fsyntax-only $(EAT_PSA_SRC) @@ -448,11 +492,33 @@ eat-psa-config-check: $(CC) $(CFLAGS) -Werror \ -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE \ -DWOLFCOSE_NO_ES384 -DWOLFCOSE_NO_ES512 -fsyntax-only $(EAT_PSA_SRC) $(CC) $(CFLAGS) -Werror \ -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE \ -DWOLFCOSE_NO_HMAC384 -DWOLFCOSE_NO_HMAC512 -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror -Werror=unused-function -DWOLFCOSE_LEAN \ + -DWOLFCOSE_NO_ES256 -DWOLFCOSE_ENABLE_ES384 \ + -DWOLFCOSE_NO_SIGN1_VERIFY -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror -Werror=unused-function -DWOLFCOSE_LEAN \ + -DWOLFCOSE_NO_ES256 -DWOLFCOSE_ENABLE_ES512 \ + -DWOLFCOSE_NO_SIGN1_VERIFY -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror -Werror=unused-function -DWOLFCOSE_LEAN \ + -DWOLFCOSE_NO_HMAC256 -DWOLFCOSE_ENABLE_HMAC384 \ + -DWOLFCOSE_NO_MAC0_VERIFY -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE -fsyntax-only $(EAT_PSA_SRC) + $(CC) $(CFLAGS) -Werror -Werror=unused-function -DWOLFCOSE_LEAN \ + -DWOLFCOSE_NO_HMAC256 -DWOLFCOSE_ENABLE_HMAC512 \ + -DWOLFCOSE_NO_MAC0_VERIFY -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE -fsyntax-only $(EAT_PSA_SRC) $(CC) $(CFLAGS) -Werror -DWOLFSSL_USER_SETTINGS \ -I./tests/config/eat_psa_curves \ -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ @@ -549,6 +615,23 @@ eat-psa-config-check: $(EAT_PSA_SRC) >/dev/null 2>&1; then \ echo "FAIL: Mac0 flag accepted without PSA/EAT"; exit 1; \ fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: PSA/EAT accepted a profile without an operation"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: Sign1 issuer accepted without common issuance"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE -fsyntax-only \ + $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: Mac0 issuer accepted without common issuance"; exit 1; \ + fi @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ -DWOLFCOSE_NO_ES256 -DWOLFCOSE_NO_ES384 -DWOLFCOSE_NO_ES512 \ @@ -570,9 +653,15 @@ eat-psa-config-check: fi @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ - -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_NO_SIGN1_SIGN \ + -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE -DWOLFCOSE_NO_SIGN1_SIGN \ + -fsyntax-only $(EAT_PSA_SRC) >/dev/null 2>&1; then \ + echo "FAIL: Sign1 issuer accepted without a creation path"; exit 1; \ + fi + @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ + -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ + -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE -DWOLFCOSE_NO_MAC0_CREATE \ -fsyntax-only $(EAT_PSA_SRC) >/dev/null 2>&1; then \ - echo "FAIL: issuer flag accepted without a creation path"; exit 1; \ + echo "FAIL: Mac0 issuer accepted without a creation path"; exit 1; \ fi @if $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EAT_PSA \ -DWOLFCOSE_ENABLE_EAT_PSA_LEGACY -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 \ diff --git a/docs/API-Reference.md b/docs/API-Reference.md index b4b0b43..cf77b5c 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -467,10 +467,11 @@ The decoded `kty`/`crv` must name the attached key type or key type a buffer holds before attaching anything, use [`wc_CoseKey_PeekInfo()`](#wc_cosekey_peekinfo). -Decoding is strict: preferred CBOR only, no duplicate integer or text labels, -and `bufSz` must be exactly the encoded length. Registered COSE_Key parameters -remain numeric; an unknown text label is treated as a non-critical extension. -See +Decoding is strict: preferred CBOR only, no duplicate integer labels, and +`bufSz` must be exactly the encoded length. With +`WOLFCOSE_ENABLE_COSE_TEXT_LABELS`, unknown text labels are accepted as +non-critical extensions and checked for duplicates. Registered COSE_Key +parameters remain numeric. See [Getting Started - Strict decoding](Getting-Started.md#strict-decoding-rfc-8949-preferred-serialization). Keys containing the optional `key_ops` label (4) return @@ -533,11 +534,11 @@ if (ret == WOLFCOSE_SUCCESS) { `in` is not modified and nothing is consumed, so the call is repeatable. `kid` points into `in`, so it stays valid only as long as that buffer does. -The same structural checks `wc_CoseKey_Decode()` applies are applied here - -no duplicate integer or text labels, `kty` required, and no trailing bytes - -so a buffer that peeks successfully will not be rejected by the decoder for -those reasons. Registered COSE_Key parameters remain numeric and an unknown -text label is treated as a non-critical extension. Label `-1` is `crv` for +The same structural checks `wc_CoseKey_Decode()` applies are applied here: +no duplicate integer labels, `kty` required, and no trailing bytes. With +`WOLFCOSE_ENABLE_COSE_TEXT_LABELS`, unknown text labels are also accepted and +checked for duplicates. Registered COSE_Key parameters remain numeric. Label +`-1` is `crv` for EC2/OKP but `k`/`n` for symmetric/RSA keys; the value is dispatched on its CBOR type, so `crv` stays 0 for the latter. A `key_ops` label returns `WOLFCOSE_E_UNSUPPORTED`, matching decode. On any error every field of `info` @@ -1042,8 +1043,8 @@ the output token is used. `expectedNonce` is required and must be 32, 48, or | Function | Required feature gate | Purpose | |----------|-----------------------|---------| | `wc_CoseEatPsaToken_EncodeClaims` | `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` | Encode current-profile claims | -| `wc_CoseEatPsaToken_CreateSign1` | issue plus Sign1 creation | Encode and create a current Sign1 token | -| `wc_CoseEatPsaToken_CreateMac0` | issue plus Mac0 creation | Encode and create a current Mac0 token | +| `wc_CoseEatPsaToken_CreateSign1` | `WOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE` plus common issue | Encode and create a current Sign1 token | +| `wc_CoseEatPsaToken_CreateMac0` | `WOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE` plus common issue | Encode and create a current Mac0 token | | `wc_CoseEatPsaToken_VerifyByUeid` | `WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER` | Resolve a candidate key from an untrusted UEID, then authenticate the original token | | `wc_CoseEatPsaToken_ForEachComponent` | `WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR` | Decode authenticated software components one at a time | @@ -1225,12 +1226,13 @@ else { } ``` -`wc_CoseKey_Decode()` and the COSE header parsers accept both forms and -bytewise track text-label duplicates, including between protected and -unprotected header buckets. Registered COSE parameters remain numeric: a text -label such as `"alg"` or `"kty"` is an unknown extension, not an alias for -numeric labels 1 or 3. Unknown non-critical extensions are skipped; an -unknown entry listed in numeric `crit` is rejected. +With `WOLFCOSE_ENABLE_COSE_TEXT_LABELS`, `wc_CoseKey_Decode()` and the COSE +header parsers accept both forms and bytewise track text-label duplicates, +including between protected and unprotected header buckets. Registered COSE +parameters remain numeric: a text label such as `"alg"` or `"kty"` is an +unknown extension, not an alias for numeric labels 1 or 3. Unknown +non-critical extensions are skipped; an unknown entry listed in numeric +`crit` is rejected. Without the gate, these generic parsers reject text labels. **Returns:** `WOLFCOSE_SUCCESS` or error code diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index e66a2f7..1d5aea1 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -409,15 +409,16 @@ Related strictness that surprises integrators for the same reason: - EC2 coordinates must be exactly the curve size, with leading zeros preserved (RFC 9053 Section 7.1.1) - a 31-byte P-256 `x` is rejected, not left-padded. - A duplicate label in a header or `COSE_Key` map is rejected. -- `COSE_Key` and COSE header maps accept integer and text labels, and reject - duplicate text labels just as they reject duplicate integer labels. Registered - COSE parameters are numeric; an unknown text label is skipped as a - non-critical extension, not treated as an alias such as `"alg"` for label 1. - -None of this is configurable for the ordinary API: relaxing it would let a -signature or MAC be recomputed over a re-encoding of the same data, which is -the class of bug deterministic encoding exists to prevent. The optional -PSA/EAT verifier's variation-tolerant path still rejects indefinite CBOR. +- With `WOLFCOSE_ENABLE_COSE_TEXT_LABELS`, `COSE_Key` and COSE header maps + accept unknown text labels and reject their duplicates. Registered COSE + parameters remain numeric; `"alg"` is not an alias for integer label 1. + The option is off by default and is implied by PSA/EAT verification. + +The strict decoding rules are not configurable for the ordinary API: relaxing +them would let a signature or MAC be recomputed over a re-encoding of the same +data. The text-label option expands accepted label types without relaxing CBOR +encoding rules. The optional PSA/EAT verifier's variation-tolerant path still +rejects indefinite CBOR. ## Cross-Compilation diff --git a/docs/Macros.md b/docs/Macros.md index a5b984b..0d4a516 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -51,36 +51,69 @@ Extension names for `WOLFCOSE_ENABLE_`: `ES384`, `ES512`, `EDDSA`, `ED448`, ` An extension is compiled in when it is explicitly enabled (`WOLFCOSE_ENABLE_`), or — in a non-lean build — when wolfSSL provides the primitive and it is not opted out with `WOLFCOSE_NO_`. Enabling an extension wolfSSL cannot provide is a compile error. The resolved state is exposed internally as read-only `WOLFCOSE_HAVE_` gates (e.g. `WOLFCOSE_HAVE_MLDSA`); sources, tests, and examples compile against those, so you set `WOLFCOSE_ENABLE_*`/`WOLFCOSE_NO_*`, not `WOLFCOSE_HAVE_*`. +## COSE Text-Label Extensions + +`WOLFCOSE_ENABLE_COSE_TEXT_LABELS` enables unknown text-string labels in COSE +header and `COSE_Key` maps. The decoder skips them as non-critical extensions +and tracks them for duplicate detection. It is off by default because the +tracking arrays add bounded parser state. PSA/EAT verification enables this +capability automatically for variation-tolerant reception; issuer-only builds +do not. The public `wc_CBOR_DecodeLabel()` helper remains available without +this gate for applications that decode their own maps. + ## PSA/EAT Attestation Gates PSA/EAT support is off in every build, including a non-lean full build. The default archive does not contain `wc_CoseEatPsaToken_*` symbols or the PSA/EAT parser. Define the common switch, one or more profiles, and one or more COSE -envelope families deliberately. The three PSA/EAT limit macros below are also -defined and checked only after the common switch resolves to enabled. +envelope families deliberately. The component-count limit applies to issuers +and verifiers. The two claim-map limits exist only in verifier builds. | Define | Description | Default | |--------|-------------|---------| -| `WOLFCOSE_ENABLE_EAT_PSA` | Common PSA/EAT API and parser | off | +| `WOLFCOSE_ENABLE_EAT_PSA` | Common PSA/EAT API and types | off | | `WOLFCOSE_ENABLE_EAT_PSA_CURRENT` | RFC 9783 TF-M current profile | off | | `WOLFCOSE_ENABLE_EAT_PSA_SIGN1` | Tagged Sign1 consumption | off | | `WOLFCOSE_ENABLE_EAT_PSA_MAC0` | Tagged Mac0 consumption | off | -| `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` | Current-profile claim and token issuance | off | +| `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` | Current-profile claim encoding | off | +| `WOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE` | Tagged Sign1 token issuance | off | +| `WOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE` | Tagged Mac0 token issuance | off | | `WOLFCOSE_ENABLE_EAT_PSA_LEGACY` | Legacy `PSA_IOT_PROFILE_1` consumption | off | | `WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER` | UEID-selected key lookup helper | off | | `WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR` | Zero-copy component callback helper | off | +| `WOLFCOSE_ENABLE_COSE_TEXT_LABELS` | Generic COSE text-label extensions; implied by PSA/EAT verification | off | | `WOLFCOSE_EAT_PSA_TFM_FULL` | Derived: all RFC 9783 `#tfm` receiver algorithms/envelopes are present; do not define manually | derived | | `WOLFCOSE_EAT_PSA_MAX_COMPONENTS` | Maximum accepted software-component maps | 32 | -| `WOLFCOSE_EAT_PSA_MAX_CLAIMS` | Maximum claim-map entries, including extension claims | 64 | -| `WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS` | Maximum entries in each component map, including extensions | 16 | - -`WOLFCOSE_ENABLE_EAT_PSA_ISSUE` requires the current profile plus an enabled -Sign1 signing or Mac0 creation path. Sign1 consumption requires an enabled -ES256, ES384, or ES512 verifier; Mac0 consumption requires an enabled HMAC -verifier. The normal `WOLFCOSE_ENABLE_ES384`, `WOLFCOSE_ENABLE_ES512`, -`WOLFCOSE_ENABLE_HMAC384`, and `WOLFCOSE_ENABLE_HMAC512` macros choose the -non-core RFC 9783 algorithms in a lean build. `WOLFCOSE_NO_*` macros remove -their branches in a full build. +| `WOLFCOSE_EAT_PSA_MAX_CLAIMS` | Verifier-only maximum claim-map entries, including extensions | 64 | +| `WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS` | Verifier-only maximum entries in each component map | 16 | + +`WOLFCOSE_ENABLE_EAT_PSA_ISSUE` enables the claim encoder and requires the +current profile. Add `WOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE` and/or +`WOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE` for a protected token creator. These +issuer gates are independent of the Sign1 and Mac0 consumption gates, so an +attester-only build does not export `wc_CoseEatPsaToken_Verify()`. +It may also disable all generic verify/decrypt operations, key decoding, and +`WOLFCOSE_CBOR_DECODE`; `make eat-psa-config-check` compiles both issuer +envelopes in exactly that encode-only configuration. + +The generic operation and algorithm gates remain authoritative: + +| PSA/EAT path | Required generic operation | Algorithm selection | +|--------------|----------------------------|---------------------| +| Sign1 consume | `WOLFCOSE_SIGN1_VERIFY` | ES256, ES384, and/or ES512 | +| Sign1 issue | `WOLFCOSE_SIGN1_SIGN` | ES256, ES384, and/or ES512 | +| Mac0 consume | `WOLFCOSE_MAC0_VERIFY` | HMAC256, HMAC384, and/or HMAC512 | +| Mac0 issue | `WOLFCOSE_MAC0_CREATE` | HMAC256, HMAC384, and/or HMAC512 | + +Use `WOLFCOSE_NO_SIGN1_SIGN`, `WOLFCOSE_NO_SIGN1_VERIFY`, +`WOLFCOSE_NO_MAC0_CREATE`, or `WOLFCOSE_NO_MAC0_VERIFY` to remove a generic +operation. ES256 and HMAC256 are lean-core algorithms and can be removed with +`WOLFCOSE_NO_ES256` and `WOLFCOSE_NO_HMAC256`. Select ES384, ES512, HMAC384, +and HMAC512 in a lean build with `WOLFCOSE_ENABLE_ES384`, +`WOLFCOSE_ENABLE_ES512`, `WOLFCOSE_ENABLE_HMAC384`, and +`WOLFCOSE_ENABLE_HMAC512`; use the corresponding `WOLFCOSE_NO_*` macros in a +full build. A selected PSA/EAT path that has no compatible generic operation +or algorithm is a compile-time configuration error. RFC 9783 Section 5.2 requires a receiver advertising the standardized `tag:psacertified.org,2023:psa#tfm` profile to accept tagged Sign1 and Mac0 diff --git a/docs/PSA-EAT.md b/docs/PSA-EAT.md index 0a01815..ee65a6f 100644 --- a/docs/PSA-EAT.md +++ b/docs/PSA-EAT.md @@ -13,21 +13,23 @@ the caller. Decoded claim spans borrow the authenticated input token. ## Select only what is needed `WOLFCOSE_ENABLE_EAT_PSA` enables the common API. Select at least one profile -and at least one envelope family after it. +and at least one consume or issue operation after it. | Define | Effect | Default | |---|---|---| -| `WOLFCOSE_ENABLE_EAT_PSA` | Common PSA/EAT API and parser support | off | +| `WOLFCOSE_ENABLE_EAT_PSA` | Common PSA/EAT API and types | off | | `WOLFCOSE_ENABLE_EAT_PSA_CURRENT` | RFC 9783 TF-M profile, `tag:psacertified.org,2023:psa#tfm` | off | | `WOLFCOSE_ENABLE_EAT_PSA_SIGN1` | Tagged `COSE_Sign1` consumption | off | | `WOLFCOSE_ENABLE_EAT_PSA_MAC0` | Tagged `COSE_Mac0` consumption | off | -| `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` | Current-profile claim encoding and token creation | off | +| `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` | Current-profile claim encoding | off | +| `WOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE` | Tagged `COSE_Sign1` token creation | off | +| `WOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE` | Tagged `COSE_Mac0` token creation | off | | `WOLFCOSE_ENABLE_EAT_PSA_LEGACY` | Legacy `PSA_IOT_PROFILE_1` consumption | off | | `WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER` | `wc_CoseEatPsaToken_VerifyByUeid()` key lookup helper | off | | `WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR` | `wc_CoseEatPsaToken_ForEachComponent()` callback helper | off | | `WOLFCOSE_EAT_PSA_MAX_COMPONENTS` | Maximum accepted software-component maps | 32 | -| `WOLFCOSE_EAT_PSA_MAX_CLAIMS` | Maximum current/legacy claim-map entries, including extensions | 64 | -| `WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS` | Maximum entries in each component map, including extensions | 16 | +| `WOLFCOSE_EAT_PSA_MAX_CLAIMS` | Verifier-only maximum current/legacy claim-map entries | 64 | +| `WOLFCOSE_EAT_PSA_MAX_COMPONENT_CLAIMS` | Verifier-only maximum entries in each component map | 16 | The generic wolfCOSE algorithm gates remain authoritative. Select ES384, ES512, HMAC384, and HMAC512 with their normal `WOLFCOSE_ENABLE_*` macros in a @@ -35,6 +37,12 @@ lean build, or remove any supported algorithm with its `WOLFCOSE_NO_*` macro. The PSA/EAT code has no algorithm fallback: a disabled algorithm is rejected before claims are used. +The consume and issue directions are independent. An attester can select the +common issue gate and one issue envelope without selecting either consumption +gate. Such a build contains no PSA/EAT verifier symbol. A claim-encoder-only +build can omit both issue-envelope gates. Conversely, a verifier can omit the +common issue gate and every issuance API. + Minimal current Sign1 verifier: ```text @@ -53,6 +61,25 @@ Current Mac0 verifier: -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 ``` +Current ES256 Sign1 issuer without the PSA/EAT verifier: + +```text +-DWOLFCOSE_LEAN +-DWOLFCOSE_NO_SIGN1_VERIFY +-DWOLFCOSE_NO_ENCRYPT0_DECRYPT +-DWOLFCOSE_NO_MAC0_VERIFY +-DWOLFCOSE_NO_KEY_DECODE +-DWOLFCOSE_NO_CBOR_DECODE +-DWOLFCOSE_ENABLE_EAT_PSA +-DWOLFCOSE_ENABLE_EAT_PSA_CURRENT +-DWOLFCOSE_ENABLE_EAT_PSA_ISSUE +-DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE +``` + +Replace the last gate with `WOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE` for an +HMAC256 Mac0 issuer. The same decode opt-outs apply. The algorithm gates can +then replace ES256 or HMAC256 independently as described in [[Macros]]. + These selective examples are useful COSE building blocks, but they are not RFC 9783 `#tfm` receivers: Section 5.2 requires a `#tfm` receiver to accept both envelope types and ES256/384/512 plus HMAC 256/256, 384/384, and @@ -165,7 +192,9 @@ for a component list. The callback's component structure is valid only for the duration of that callback. Its span data borrows from the verified token input and remains valid while that input remains unchanged. -For a current-profile issuer, enable `WOLFCOSE_ENABLE_EAT_PSA_ISSUE` and use: +For a current-profile Sign1 issuer, enable +`WOLFCOSE_ENABLE_EAT_PSA_ISSUE` and +`WOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE`, then use: ```c wc_CoseEatPsaToken_CreateSign1(&issuer_key, WOLFCOSE_ALG_ES256, &claims, @@ -173,8 +202,8 @@ wc_CoseEatPsaToken_CreateSign1(&issuer_key, WOLFCOSE_ALG_ES256, &claims, sizeof(token), &token_len, rng); ``` -`wc_CoseEatPsaToken_CreateMac0()` is available only when both the Mac0 and issue -gates are selected. Issuance always emits the current RFC 9783 profile; +`wc_CoseEatPsaToken_CreateMac0()` is available only when the common issue and +Mac0 issue gates are selected. Issuance always emits the current RFC 9783 profile; legacy is deliberately consume-only. An attester needs only one enabled RFC 9783 Table 4 Sign1 or Mac0 creation path to emit the standardized `#tfm` identifier. `WOLFCOSE_EAT_PSA_TFM_FULL` is deliberately separate: it proves a @@ -258,7 +287,8 @@ encodings. Ordinary wolfCOSE decoding remains strict by default. Unknown non-critical COSE header extensions may use integer or text labels; text labels are tracked for duplicates across protected and unprotected buckets, but are not aliases for registered numeric headers. A text extension listed in `crit` -is rejected because this verifier does not implement it. +is rejected because this verifier does not implement it. Verification implies +the otherwise optional `WOLFCOSE_ENABLE_COSE_TEXT_LABELS` capability. The raw-key API rejects `x5chain` headers with `WOLFCOSE_E_UNSUPPORTED`. A certificate chain is not proof of a trusted IAK until an application has diff --git a/include/wolfcose/eat_psa.h b/include/wolfcose/eat_psa.h index 603cd1e..5b8ca44 100644 --- a/include/wolfcose/eat_psa.h +++ b/include/wolfcose/eat_psa.h @@ -75,7 +75,8 @@ typedef struct WOLFCOSE_EAT_PSA_CLAIMS { WOLFCOSE_EAT_PSA_SPAN ueid; /**< Required 33-byte UEID starting with 0x01. */ WOLFCOSE_EAT_PSA_SPAN implementationId; /**< Required 32-byte implementation ID. */ WOLFCOSE_EAT_PSA_SPAN bootSeed; /**< Optional current-profile 8 to 32-byte seed. */ - WOLFCOSE_EAT_PSA_SPAN certificationReference; /**< Optional 13-digit EAN-13, dash, and five-digit version. */ + /** Optional 13-digit EAN-13, dash, and five-digit version. */ + WOLFCOSE_EAT_PSA_SPAN certificationReference; WOLFCOSE_EAT_PSA_SPAN verificationServiceIndicator; /**< Optional text VSI. */ int32_t clientId; /**< Required nonzero signed PSA client ID. */ uint16_t lifecycle; /**< Required lifecycle; 0x00xx is structurally valid unknown state. */ @@ -90,7 +91,8 @@ typedef struct WOLFCOSE_EAT_PSA_TOKEN { WOLFCOSE_EAT_PSA_SPAN ueid; /**< Authenticated UEID. */ WOLFCOSE_EAT_PSA_SPAN implementationId; /**< Authenticated implementation ID. */ WOLFCOSE_EAT_PSA_SPAN bootSeed; /**< Optional authenticated boot seed. */ - WOLFCOSE_EAT_PSA_SPAN certificationReference; /**< Optional EAN-13 + five-digit certification reference. */ + /** Optional EAN-13 and five-digit certification reference. */ + WOLFCOSE_EAT_PSA_SPAN certificationReference; WOLFCOSE_EAT_PSA_SPAN verificationServiceIndicator; /**< Optional VSI. */ int32_t clientId; /**< Authenticated nonzero PSA client ID. */ uint16_t lifecycle; /**< Authenticated lifecycle; appraise 0x00xx by policy. */ @@ -251,11 +253,13 @@ WOLFCOSE_API int wc_CoseEatPsaToken_CreateMac0(const WOLFCOSE_KEY* key, * WOLFCOSE_E_EAT_PSA_CLAIM, WOLFCOSE_E_EAT_PSA_PROFILE, * WOLFCOSE_E_EAT_PSA_NONCE, or WOLFCOSE_E_INVALID_ARG. */ +#if defined(WOLFCOSE_EAT_PSA_VERIFY) WOLFCOSE_API int wc_CoseEatPsaToken_Verify(const WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz, const uint8_t* expectedNonce, size_t expectedNonceLen, uint8_t* scratch, size_t scratchSz, WOLFCOSE_EAT_PSA_TOKEN* token); +#endif /** * \brief Verify a PSA token after resolving its key using the untrusted UEID. @@ -311,7 +315,9 @@ WOLFCOSE_API int wc_CoseEatPsaToken_ForEachComponent( #if defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) #define wc_EatPsaToken_CreateMac0 wc_CoseEatPsaToken_CreateMac0 #endif -#define wc_EatPsaToken_Verify wc_CoseEatPsaToken_Verify +#if defined(WOLFCOSE_EAT_PSA_VERIFY) + #define wc_EatPsaToken_Verify wc_CoseEatPsaToken_Verify +#endif #if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) #define wc_EatPsaToken_VerifyByUeid wc_CoseEatPsaToken_VerifyByUeid #endif diff --git a/include/wolfcose/settings.h b/include/wolfcose/settings.h index 0132712..6da0908 100644 --- a/include/wolfcose/settings.h +++ b/include/wolfcose/settings.h @@ -546,11 +546,13 @@ extern "C" { * independently so an embedded verifier carries only the code it uses. * Generic WOLFCOSE_ENABLE_ controls remain the algorithm gates. * - * WOLFCOSE_ENABLE_EAT_PSA common PSA/EAT API and parser + * WOLFCOSE_ENABLE_EAT_PSA common PSA/EAT API and types * WOLFCOSE_ENABLE_EAT_PSA_CURRENT RFC 9783 TF-M claim profile * WOLFCOSE_ENABLE_EAT_PSA_SIGN1 tagged Sign1 consumption * WOLFCOSE_ENABLE_EAT_PSA_MAC0 tagged Mac0 consumption - * WOLFCOSE_ENABLE_EAT_PSA_ISSUE current-profile issuance + * WOLFCOSE_ENABLE_EAT_PSA_ISSUE current claim encoding + * WOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE tagged Sign1 issuance + * WOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE tagged Mac0 issuance * WOLFCOSE_ENABLE_EAT_PSA_LEGACY PSA_IOT_PROFILE_1 consume * WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER claim-based key lookup helper * WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR component traversal helper @@ -559,10 +561,6 @@ extern "C" { #define WOLFCOSE_EAT_PSA #endif -#if defined(WOLFCOSE_EAT_PSA) && !defined(WOLFCOSE_CBOR_DECODE) - #error "WOLFCOSE_ENABLE_EAT_PSA requires CBOR decoding" -#endif - #if defined(WOLFCOSE_ENABLE_EAT_PSA_CURRENT) #if !defined(WOLFCOSE_EAT_PSA) #error "WOLFCOSE_ENABLE_EAT_PSA_CURRENT requires EAT_PSA" @@ -577,8 +575,16 @@ extern "C" { #define WOLFCOSE_EAT_PSA_LEGACY #endif +#if defined(WOLFCOSE_ENABLE_EAT_PSA_ISSUE) + #if !defined(WOLFCOSE_EAT_PSA_CURRENT) || !defined(WOLFCOSE_CBOR_ENCODE) + #error "WOLFCOSE_ENABLE_EAT_PSA_ISSUE requires EAT_PSA_CURRENT and CBOR encode" + #endif + #define WOLFCOSE_EAT_PSA_ISSUE +#endif + #if defined(WOLFCOSE_ENABLE_EAT_PSA_SIGN1) #if !defined(WOLFCOSE_EAT_PSA) || !defined(WOLFCOSE_SIGN1_VERIFY) || \ + !defined(WOLFCOSE_CBOR_DECODE) || \ (!defined(WOLFCOSE_HAVE_ES256) && !defined(WOLFCOSE_HAVE_ES384) && \ !defined(WOLFCOSE_HAVE_ES512)) #error "WOLFCOSE_ENABLE_EAT_PSA_SIGN1 requires EAT_PSA and an ECDSA COSE Sign1 verifier" @@ -588,57 +594,73 @@ extern "C" { #if defined(WOLFCOSE_ENABLE_EAT_PSA_MAC0) #if !defined(WOLFCOSE_EAT_PSA) || !defined(WOLFCOSE_MAC0_VERIFY) || \ - !defined(WOLFCOSE_HAVE_HMAC) + !defined(WOLFCOSE_CBOR_DECODE) || !defined(WOLFCOSE_HAVE_HMAC) #error "WOLFCOSE_ENABLE_EAT_PSA_MAC0 requires EAT_PSA and HMAC COSE Mac0 verify" #endif #define WOLFCOSE_EAT_PSA_MAC0 #endif -#if defined(WOLFCOSE_EAT_PSA) && !defined(WOLFCOSE_EAT_PSA_CURRENT) && \ - !defined(WOLFCOSE_EAT_PSA_LEGACY) - #error "WOLFCOSE_ENABLE_EAT_PSA needs EAT_PSA_CURRENT and/or EAT_PSA_LEGACY" +#if defined(WOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE) + #if !defined(WOLFCOSE_EAT_PSA_ISSUE) || \ + !defined(WOLFCOSE_SIGN1_SIGN) || \ + (!defined(WOLFCOSE_HAVE_ES256) && !defined(WOLFCOSE_HAVE_ES384) && \ + !defined(WOLFCOSE_HAVE_ES512)) + #error "EAT_PSA_SIGN1_ISSUE needs EAT_PSA_ISSUE and ECDSA Sign1 signing" + #endif + #define WOLFCOSE_EAT_PSA_SIGN1_ISSUE #endif -#if defined(WOLFCOSE_EAT_PSA) && !defined(WOLFCOSE_EAT_PSA_SIGN1) && \ - !defined(WOLFCOSE_EAT_PSA_MAC0) - #error "WOLFCOSE_ENABLE_EAT_PSA needs EAT_PSA_SIGN1 and/or EAT_PSA_MAC0" +#if defined(WOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE) + #if !defined(WOLFCOSE_EAT_PSA_ISSUE) || \ + !defined(WOLFCOSE_MAC0_CREATE) || !defined(WOLFCOSE_HAVE_HMAC) + #error "EAT_PSA_MAC0_ISSUE needs EAT_PSA_ISSUE and HMAC Mac0 creation" + #endif + #define WOLFCOSE_EAT_PSA_MAC0_ISSUE #endif -#if defined(WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER) - #if !defined(WOLFCOSE_EAT_PSA) - #error "WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER requires EAT_PSA" - #endif - #define WOLFCOSE_EAT_PSA_UEID_RESOLVER +#if defined(WOLFCOSE_EAT_PSA_SIGN1) || defined(WOLFCOSE_EAT_PSA_MAC0) + #define WOLFCOSE_EAT_PSA_VERIFY #endif -#if defined(WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR) - #if !defined(WOLFCOSE_EAT_PSA) - #error "WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR requires EAT_PSA" +/* Text-string COSE labels need additional duplicate-tracking state. Keep the + * generic extension opt-in, while enabling it automatically for variation- + * tolerant PSA/EAT verification. */ +#if defined(WOLFCOSE_ENABLE_COSE_TEXT_LABELS) + #if !defined(WOLFCOSE_CBOR_DECODE) + #error "WOLFCOSE_ENABLE_COSE_TEXT_LABELS requires CBOR decode" #endif - #define WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR + #define WOLFCOSE_COSE_TEXT_LABELS +#elif defined(WOLFCOSE_EAT_PSA_VERIFY) + #define WOLFCOSE_COSE_TEXT_LABELS #endif -#if defined(WOLFCOSE_ENABLE_EAT_PSA_ISSUE) - #if !defined(WOLFCOSE_EAT_PSA_CURRENT) || !defined(WOLFCOSE_CBOR_ENCODE) - #error "WOLFCOSE_ENABLE_EAT_PSA_ISSUE requires EAT_PSA_CURRENT and CBOR encode" - #endif - #define WOLFCOSE_EAT_PSA_ISSUE +#if defined(WOLFCOSE_EAT_PSA) && !defined(WOLFCOSE_EAT_PSA_CURRENT) && \ + !defined(WOLFCOSE_EAT_PSA_LEGACY) + #error "WOLFCOSE_ENABLE_EAT_PSA needs EAT_PSA_CURRENT and/or EAT_PSA_LEGACY" #endif -#if defined(WOLFCOSE_EAT_PSA_ISSUE) && defined(WOLFCOSE_EAT_PSA_SIGN1) && \ - defined(WOLFCOSE_SIGN1_SIGN) - #define WOLFCOSE_EAT_PSA_SIGN1_ISSUE +#if defined(WOLFCOSE_EAT_PSA) && !defined(WOLFCOSE_EAT_PSA_VERIFY) && \ + !defined(WOLFCOSE_EAT_PSA_ISSUE) + #error "WOLFCOSE_ENABLE_EAT_PSA needs a verifier and/or issuer operation" #endif -#if defined(WOLFCOSE_EAT_PSA_ISSUE) && defined(WOLFCOSE_EAT_PSA_MAC0) && \ - defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_HAVE_HMAC) - #define WOLFCOSE_EAT_PSA_MAC0_ISSUE +#if defined(WOLFCOSE_EAT_PSA_LEGACY) && \ + !defined(WOLFCOSE_EAT_PSA_VERIFY) + #error "WOLFCOSE_ENABLE_EAT_PSA_LEGACY is consume-only and needs a verifier" #endif -#if defined(WOLFCOSE_EAT_PSA_ISSUE) && \ - !defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) && \ - !defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) - #error "WOLFCOSE_ENABLE_EAT_PSA_ISSUE needs an enabled signing or MAC creation path" +#if defined(WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER) + #if !defined(WOLFCOSE_EAT_PSA_VERIFY) + #error "WOLFCOSE_ENABLE_EAT_PSA_UEID_RESOLVER requires an EAT_PSA verifier" + #endif + #define WOLFCOSE_EAT_PSA_UEID_RESOLVER +#endif + +#if defined(WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR) + #if !defined(WOLFCOSE_EAT_PSA_VERIFY) + #error "WOLFCOSE_ENABLE_EAT_PSA_COMPONENT_ITERATOR requires an EAT_PSA verifier" + #endif + #define WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR #endif /* RFC 9783 Section 5.2 gives the standardized #tfm profile a fixed receiver @@ -702,11 +724,13 @@ extern "C" { #if defined(WOLFCOSE_EAT_PSA) /* Maximum software-component maps accepted in one PSA token. This bounds - * verification time while leaving decoded component bytes zero-copy. */ + * verification time and issuer input size. */ #ifndef WOLFCOSE_EAT_PSA_MAX_COMPONENTS #define WOLFCOSE_EAT_PSA_MAX_COMPONENTS 32u #endif +#endif +#if defined(WOLFCOSE_EAT_PSA_VERIFY) /* PSA/EAT map keys must be unique, including unknown extension keys. The * allocation-free duplicate check re-scans earlier entries, so bound both * maps to retain predictable verifier time. Defaults leave room for @@ -736,6 +760,8 @@ extern "C" { #if WOLFCOSE_EAT_PSA_MAX_COMPONENTS < 1u #error "WOLFCOSE_EAT_PSA_MAX_COMPONENTS must permit one component" #endif +#endif +#if defined(WOLFCOSE_EAT_PSA_VERIFY) #if WOLFCOSE_EAT_PSA_MAX_CLAIMS < 10u #error "WOLFCOSE_EAT_PSA_MAX_CLAIMS must permit all current-profile claims" #endif diff --git a/include/wolfcose/wolfcose.h b/include/wolfcose/wolfcose.h index 95742d6..1dd659c 100644 --- a/include/wolfcose/wolfcose.h +++ b/include/wolfcose/wolfcose.h @@ -1010,12 +1010,13 @@ typedef struct WOLFCOSE_KEY_INFO { * * Nothing is imported, no key object is needed, and \p in is not modified. * The same structural checks wc_CoseKey_Decode() applies are applied here - * (no duplicate integer or text labels, kty required, no trailing bytes), so - * a buffer that peeks successfully will not be rejected by the decoder for - * those reasons. Registered COSE_Key parameters remain numeric; an unknown - * text label is treated as a non-critical extension. Keys containing key_ops - * are rejected with WOLFCOSE_E_UNSUPPORTED because the fixed-size key wrapper - * cannot retain arbitrary operation identifiers. + * (no duplicate integer labels, kty required, no trailing bytes), so a buffer + * that peeks successfully will not be rejected by the decoder for those + * reasons. WOLFCOSE_ENABLE_COSE_TEXT_LABELS accepts unknown text labels as + * non-critical extensions and checks them for duplicates. Registered + * COSE_Key parameters remain numeric. Keys containing key_ops are rejected + * with WOLFCOSE_E_UNSUPPORTED because the fixed-size key wrapper cannot + * retain arbitrary operation identifiers. * * \param in Input CBOR COSE_Key buffer. * \param inSz Input buffer size; must be exactly the encoded length. diff --git a/scripts/test_pkg_config.sh b/scripts/test_pkg_config.sh index e80343c..b232449 100644 --- a/scripts/test_pkg_config.sh +++ b/scripts/test_pkg_config.sh @@ -136,6 +136,7 @@ check_config_rebuild() ( "$ROOT_DIR/src/wolfcose_eat_psa.c" \ "$ROOT_DIR/src/wolfcose_internal.h" "$config_fixture/src/" cp "$ROOT_DIR/include/wolfcose/wolfcose.h" \ + "$ROOT_DIR/include/wolfcose/eat_psa.h" \ "$config_fixture/include/wolfcose/" FAKE_CC_LOG="$config_fixture/compiler.log" @@ -161,13 +162,27 @@ check_config_rebuild() ( 'WOLFSSL_LIBS=-L/fake/second/lib -lfakesecond' \ libwolfcose.a + # Some supported file systems expose only one-second timestamp precision. + sleep 1 + touch "$config_fixture/include/wolfcose/eat_psa.h" + run_config_make \ + "CC=sh $SCRIPT_PATH --fake-cc" \ + "AR=sh $SCRIPT_PATH --fake-ar" \ + 'PKG_CONFIG=false' \ + 'WOLFSSL_CFLAGS=-I/fake/second/include' \ + 'WOLFSSL_LIBS=-L/fake/second/lib -lfakesecond' \ + libwolfcose.a + compiler_args=$(cat "$FAKE_CC_LOG") contains "$compiler_args" '-I/fake/second/include' compiler_count=$(wc -l < "$FAKE_CC_LOG" | tr -d ' ') - if [ "$compiler_count" -ne 6 ]; then - printf 'FAIL: expected configuration change to rebuild all objects\n' >&2 + if [ "$compiler_count" -ne 7 ]; then + printf 'FAIL: expected 7 config/header compiles, got %s\n' \ + "$compiler_count" >&2 exit 1 fi + last_compile=$(tail -n 1 "$FAKE_CC_LOG") + contains "$last_compile" 'src/wolfcose_eat_psa.c' ) pkg_config_output=$(run_make tool \ diff --git a/src/wolfcose.c b/src/wolfcose.c index e9ec04f..1361eb7 100644 --- a/src/wolfcose.c +++ b/src/wolfcose.c @@ -1010,6 +1010,7 @@ int wolfCose_EccVerifyRaw(const uint8_t* sigBuf, size_t sigLen, /* ----- Internal: Protected/Unprotected header encode/decode ----- */ +#if defined(WOLFCOSE_CBOR_DECODE) /* COSE algorithm, key type, and curve identifiers are stored in int32_t * fields. Reject decoded CBOR integers that do not fit before narrowing so a * non-representable value cannot alias a valid identifier. */ @@ -1049,7 +1050,9 @@ static void wolfCose_HdrStateInit(WOLFCOSE_HDR_STATE* state) if (state != NULL) { state->labelBits = 0u; state->extraCount = 0u; +#if defined(WOLFCOSE_COSE_TEXT_LABELS) state->textCount = 0u; +#endif } } @@ -1091,9 +1094,13 @@ static int wolfCose_HdrStateAdd(WOLFCOSE_HDR_STATE* state, int64_t label) if (bit != 0u) { state->labelBits |= bit; } +#if defined(WOLFCOSE_COSE_TEXT_LABELS) else if ((state->textCount > (size_t)WOLFCOSE_MAX_MAP_ITEMS) || (state->extraCount >= ((size_t)WOLFCOSE_MAX_MAP_ITEMS - state->textCount))) { +#else + else if (state->extraCount >= (size_t)WOLFCOSE_MAX_MAP_ITEMS) { +#endif ret = WOLFCOSE_E_CBOR_MALFORMED; } else { @@ -1105,6 +1112,7 @@ static int wolfCose_HdrStateAdd(WOLFCOSE_HDR_STATE* state, int64_t label) return ret; } +#if defined(WOLFCOSE_COSE_TEXT_LABELS) static int wolfCose_HdrStateTextContains(const WOLFCOSE_HDR_STATE* state, const uint8_t* text, size_t textLen) { @@ -1151,6 +1159,7 @@ static int wolfCose_HdrStateTextAdd(WOLFCOSE_HDR_STATE* state, return ret; } +#endif static int wolfCose_HdrStateCheckAndAdd(WOLFCOSE_HDR_STATE* state, int64_t label) @@ -1174,8 +1183,10 @@ static int wolfCose_HdrStateContainsLabel(const WOLFCOSE_HDR_STATE* state, if (label != NULL) { if (label->isText != 0u) { +#if defined(WOLFCOSE_COSE_TEXT_LABELS) found = wolfCose_HdrStateTextContains(state, label->text, label->textLen); +#endif } else { found = wolfCose_HdrStateContains(state, label->val); @@ -1194,7 +1205,11 @@ static int wolfCose_HdrStateAddLabel(WOLFCOSE_HDR_STATE* state, ret = WOLFCOSE_E_INVALID_ARG; } else if (label->isText != 0u) { +#if defined(WOLFCOSE_COSE_TEXT_LABELS) ret = wolfCose_HdrStateTextAdd(state, label->text, label->textLen); +#else + ret = WOLFCOSE_E_CBOR_MALFORMED; +#endif } else { ret = wolfCose_HdrStateAdd(state, label->val); @@ -1238,15 +1253,19 @@ static int wolfCose_HdrStateMerge(WOLFCOSE_HDR_STATE* dst, for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < src->extraCount); i++) { ret = wolfCose_HdrStateAdd(dst, src->extraLabels[i]); } +#if defined(WOLFCOSE_COSE_TEXT_LABELS) for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < src->textCount); i++) { ret = wolfCose_HdrStateTextAdd(dst, src->textLabels[i], src->textLabelLens[i]); } +#endif } return ret; } +#endif /* WOLFCOSE_CBOR_DECODE */ +#if defined(WOLFCOSE_CBOR_ENCODE) int wolfCose_EncodeProtectedHdr(int32_t alg, uint8_t* buf, size_t bufSz, size_t* outLen) { @@ -1275,7 +1294,9 @@ int wolfCose_EncodeProtectedHdr(int32_t alg, uint8_t* buf, size_t bufSz, } return ret; } +#endif /* WOLFCOSE_CBOR_ENCODE */ +#if defined(WOLFCOSE_CBOR_DECODE) int wolfCose_DecodeProtectedHdr_ex(const uint8_t* data, size_t dataLen, WOLFCOSE_HDR* hdr, WOLFCOSE_HDR_STATE* hdrState, @@ -1431,7 +1452,7 @@ int wolfCose_DecodeProtectedHdr_ex(const uint8_t* data, size_t dataLen, hdr->partialIvLen = pivBstrLen; } } -#if defined(WOLFCOSE_EAT_PSA) +#if defined(WOLFCOSE_EAT_PSA_VERIFY) else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && (label.val == WOLFCOSE_HDR_X5CHAIN)) { hdr->flags |= WOLFCOSE_HDR_FLAG_X5CHAIN; @@ -1582,7 +1603,7 @@ int wolfCose_DecodeUnprotectedHdr_ex(WOLFCOSE_CBOR_CTX* ctx, } } } -#if defined(WOLFCOSE_EAT_PSA) +#if defined(WOLFCOSE_EAT_PSA_VERIFY) else if ((ret == WOLFCOSE_SUCCESS) && (label.isText == 0u) && (label.val == WOLFCOSE_HDR_X5CHAIN)) { hdr->flags |= WOLFCOSE_HDR_FLAG_X5CHAIN; @@ -1614,6 +1635,7 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, { return wolfCose_DecodeUnprotectedHdr_ex(ctx, hdr, hdrState, 0u); } +#endif /* WOLFCOSE_CBOR_DECODE */ #if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_ENCRYPT_DECRYPT) || \ defined(WOLFCOSE_MAC_VERIFY) diff --git a/src/wolfcose_cbor.c b/src/wolfcose_cbor.c index b529c9d..574228d 100644 --- a/src/wolfcose_cbor.c +++ b/src/wolfcose_cbor.c @@ -31,9 +31,10 @@ #include "wolfcose_internal.h" #include /* memcpy */ +#if defined(WOLFCOSE_CBOR_DECODE) static int wolfCose_CBOR_AllowsNonpreferred(uint32_t decodeFlags) { -#if defined(WOLFCOSE_EAT_PSA) +#if defined(WOLFCOSE_EAT_PSA_VERIFY) return ((decodeFlags & WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED) != 0u) ? 1 : 0; #else @@ -41,14 +42,18 @@ static int wolfCose_CBOR_AllowsNonpreferred(uint32_t decodeFlags) return 0; #endif } +#endif +#if defined(WOLFCOSE_CBOR_ENCODE) static int wolfCose_CBOR_IsEncodeContext(const WOLFCOSE_CBOR_CTX* ctx) { /* buf has always selected encoder mode. Do not inspect cbuf here because * existing callers may initialize only the original encoder fields. */ return ((ctx != NULL) && (ctx->buf != NULL)) ? 1 : 0; } +#endif +#if defined(WOLFCOSE_CBOR_ENCODE) || defined(WOLFCOSE_CBOR_DECODE) /* RFC 8949 Section 3.3 defines a text string as a sequence of Unicode code * points encoded in UTF-8. Keep this bounded and allocation-free so it is * usable for ordinary text values, labels, and values skipped by a profile @@ -140,7 +145,9 @@ static int wolfCose_CBOR_IsUtf8(const uint8_t* data, size_t len) return ret; } +#endif +#if defined(WOLFCOSE_CBOR_ENCODE) /* WOLFCOSE_CBOR_CTX is public, so a caller can pass a context whose idx has * been advanced past bufSz. Subtraction-based capacity check that cannot wrap * in that case. Returns 1 when at least need bytes remain, 0 otherwise. */ @@ -149,7 +156,9 @@ static int wolfCose_CBOR_HasRoom(const WOLFCOSE_CBOR_CTX* ctx, size_t need) return ((ctx->idx <= ctx->bufSz) && (need <= (ctx->bufSz - ctx->idx))) ? 1 : 0; } +#endif +#if defined(WOLFCOSE_CBOR_ENCODE) /* ----- * Internal: CBOR head encoder * @@ -235,7 +244,9 @@ int wolfCose_CBOR_EncodeHead(WOLFCOSE_CBOR_CTX* ctx, uint8_t majorType, } return ret; } +#endif /* WOLFCOSE_CBOR_ENCODE */ +#if defined(WOLFCOSE_CBOR_DECODE) /* ----- * Internal: CBOR head decoder * @@ -366,6 +377,7 @@ int wolfCose_CBOR_DecodeHead_ex(WOLFCOSE_CBOR_CTX* ctx, } return ret; } +#endif /* WOLFCOSE_CBOR_DECODE */ /* ----- * Public Encode API diff --git a/src/wolfcose_eat_psa.c b/src/wolfcose_eat_psa.c index f1cf03d..6d1f04d 100644 --- a/src/wolfcose_eat_psa.c +++ b/src/wolfcose_eat_psa.c @@ -216,6 +216,8 @@ static int wolfCose_EatPsaClaimsOverlapBuffer( return overlap; } +#if defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) || \ + defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) static int wolfCose_EatPsaIssueBuffersOverlap( const uint8_t* claimsBuf, size_t claimsBufSz, const uint8_t* scratch, size_t scratchSz, @@ -228,6 +230,7 @@ static int wolfCose_EatPsaIssueBuffersOverlap( (wolfCose_EatPsaBuffersOverlap(scratch, scratchSz, out, outSz) != 0)) ? 1 : 0; } +#endif #endif /* WOLFCOSE_EAT_PSA_ISSUE */ static int wolfCose_EatPsaIsHash(const WOLFCOSE_EAT_PSA_SPAN* span) @@ -294,7 +297,8 @@ static int wolfCose_EatPsaCertRefValid(const WOLFCOSE_EAT_PSA_SPAN* span, return ret; } -#if defined(WOLFCOSE_EAT_PSA_SIGN1) +#if defined(WOLFCOSE_EAT_PSA_SIGN1) || \ + defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) static int wolfCose_EatPsaSignAlg(int32_t alg) { int ret = 0; @@ -321,9 +325,10 @@ static int wolfCose_EatPsaSignAlg(int32_t alg) return ret; } -#endif /* WOLFCOSE_EAT_PSA_SIGN1 */ +#endif /* WOLFCOSE_EAT_PSA_SIGN1 || WOLFCOSE_EAT_PSA_SIGN1_ISSUE */ -#if defined(WOLFCOSE_EAT_PSA_MAC0) +#if defined(WOLFCOSE_EAT_PSA_MAC0) || \ + defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) static int wolfCose_EatPsaMacAlg(int32_t alg) { int ret = 0; @@ -350,7 +355,7 @@ static int wolfCose_EatPsaMacAlg(int32_t alg) return ret; } -#endif /* WOLFCOSE_EAT_PSA_MAC0 */ +#endif /* WOLFCOSE_EAT_PSA_MAC0 || WOLFCOSE_EAT_PSA_MAC0_ISSUE */ #if defined(WOLFCOSE_EAT_PSA_ISSUE) && defined(WOLFCOSE_CBOR_ENCODE) static int wolfCose_EatPsaValidateComponent( @@ -582,6 +587,8 @@ int wc_CoseEatPsaToken_EncodeClaims(const WOLFCOSE_EAT_PSA_CLAIMS* claims, } #endif /* WOLFCOSE_EAT_PSA_ISSUE */ +#if defined(WOLFCOSE_EAT_PSA_VERIFY) + static int wolfCose_EatPsaLabelsEqual(const WOLFCOSE_CBOR_LABEL* first, const WOLFCOSE_CBOR_LABEL* second) { @@ -1482,6 +1489,8 @@ int wc_CoseEatPsaToken_ForEachComponent(const WOLFCOSE_EAT_PSA_TOKEN* token, } #endif /* WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR */ +#endif /* WOLFCOSE_EAT_PSA_VERIFY */ + #if defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) int wc_CoseEatPsaToken_CreateSign1(WOLFCOSE_KEY* key, int32_t alg, const WOLFCOSE_EAT_PSA_CLAIMS* claims, diff --git a/src/wolfcose_internal.h b/src/wolfcose_internal.h index dc39ed8..696bd2e 100644 --- a/src/wolfcose_internal.h +++ b/src/wolfcose_internal.h @@ -122,29 +122,34 @@ static inline uint64_t wolfCose_LoadBE64(const uint8_t* buf) /* ----- Internal CBOR head encode/decode ----- */ +#if defined(WOLFCOSE_CBOR_DECODE) /* RFC 9783 Section 5.1's variation tolerance is a private PSA/EAT decoder * option. Ordinary public CBOR and COSE APIs always use zero (strict mode). */ #define WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED 0x0001u -#if defined(WOLFCOSE_EAT_PSA) +#if defined(WOLFCOSE_EAT_PSA_VERIFY) #define WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) \ (((flags) & ~WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED) == 0u) #else #define WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) ((flags) == 0u) #endif -#if defined(WOLFCOSE_EAT_PSA) +#if defined(WOLFCOSE_EAT_PSA_VERIFY) #define WOLFCOSE_HDR_X5CHAIN 33 #define WOLFCOSE_HDR_FLAG_X5CHAIN 0x04u #endif +#endif /* WOLFCOSE_CBOR_DECODE */ +#if defined(WOLFCOSE_CBOR_ENCODE) /** * \brief Encode a CBOR initial byte + argument. * RFC 8949 Section 3.1: initial_byte = (majorType << 5) | additional_info */ WOLFCOSE_LOCAL int wolfCose_CBOR_EncodeHead(WOLFCOSE_CBOR_CTX* ctx, uint8_t majorType, uint64_t val); +#endif +#if defined(WOLFCOSE_CBOR_DECODE) /** * \brief Decode a CBOR initial byte + argument with private profile options. * For bstr/tstr: item->data points into ctx->cbuf, item->dataLen set. @@ -169,6 +174,7 @@ WOLFCOSE_LOCAL int wolfCose_CBOR_Skip_ex(WOLFCOSE_CBOR_CTX* ctx, uint32_t decodeFlags); WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeLabel_ex(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label, uint32_t decodeFlags); +#endif /* ----- RFC 9052 context-string byte arrays (see wolfcose.c) ----- */ WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_SIGNATURE1[10]; @@ -178,17 +184,24 @@ WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_MAC[3]; WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_ENCRYPT0[8]; WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_ENCRYPT[7]; +#if defined(WOLFCOSE_CBOR_DECODE) typedef struct WOLFCOSE_HDR_STATE { uint32_t labelBits; int64_t extraLabels[WOLFCOSE_MAX_MAP_ITEMS]; +#if defined(WOLFCOSE_COSE_TEXT_LABELS) const uint8_t* textLabels[WOLFCOSE_MAX_MAP_ITEMS]; size_t textLabelLens[WOLFCOSE_MAX_MAP_ITEMS]; +#endif size_t extraCount; +#if defined(WOLFCOSE_COSE_TEXT_LABELS) size_t textCount; +#endif } WOLFCOSE_HDR_STATE; +#endif /* ----- COSE internal helpers ----- */ +#if defined(WOLFCOSE_CBOR_ENCODE) /** * \brief Encode a protected header map: {1: alg} as a bstr. * \param alg Algorithm identifier. @@ -198,7 +211,9 @@ typedef struct WOLFCOSE_HDR_STATE { */ WOLFCOSE_LOCAL int wolfCose_EncodeProtectedHdr(int32_t alg, uint8_t* buf, size_t bufSz, size_t* outLen); +#endif +#if defined(WOLFCOSE_CBOR_DECODE) /** * \brief Decode a protected header bstr (containing a CBOR map). * \param data Raw bstr content. @@ -248,6 +263,7 @@ WOLFCOSE_LOCAL int wolfCose_Mac0_Verify_ex(const WOLFCOSE_KEY* key, const uint8_t** payload, size_t* payloadLen, uint32_t flags); #endif +#endif /* WOLFCOSE_CBOR_DECODE */ /** * \brief Map COSE algorithm ID to wolfCrypt hash type. diff --git a/tests/test_cose.c b/tests/test_cose.c index e38e851..4a10601 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -7467,11 +7467,12 @@ static void test_cose_key_peek_info(void) TEST_ASSERT(wc_CoseKey_PeekInfo(buf, enc.idx, &info) == WOLFCOSE_E_CBOR_MALFORMED, "peek trailing bytes"); - /* Text label */ + /* A text label named "kty" is never the registered integer kty. */ (void)wc_CBOR_EncoderInit(&enc, buf, sizeof(buf)); (void)wc_CBOR_EncodeMapStart(&enc, 1); (void)wc_CBOR_EncodeTstr(&enc, (const uint8_t*)"kty", 3); (void)wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_EC2); +#if defined(WOLFCOSE_COSE_TEXT_LABELS) TEST_ASSERT(wc_CoseKey_PeekInfo(buf, enc.idx, &info) == WOLFCOSE_E_COSE_BAD_HDR, "peek does not treat text label as numeric kty"); @@ -7534,6 +7535,11 @@ static void test_cose_key_peek_info(void) "decode rejects duplicate text extension"); wc_CoseKey_Free(&key); } +#else + TEST_ASSERT(wc_CoseKey_PeekInfo(buf, enc.idx, &info) == + WOLFCOSE_E_CBOR_MALFORMED, + "peek rejects text labels when support is disabled"); +#endif } } @@ -10140,7 +10146,9 @@ static void test_cose_secret_zeroize(void) } #endif /* ZEROIZE_HOOK && ECDH_ES_DIRECT && ES256 && HKDF */ -#if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(WOLFCOSE_HAVE_ES256) && defined(HAVE_HKDF) +#if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(WOLFCOSE_HAVE_ES256) && \ + defined(HAVE_HKDF) +#if defined(WOLFCOSE_COSE_TEXT_LABELS) static int test_cose_add_vendor_entries(const uint8_t* in, size_t inLen, size_t mapPos, size_t entryCount, uint8_t* out, size_t outSz, size_t* outLen) @@ -10182,6 +10190,7 @@ static int test_cose_add_vendor_entries(const uint8_t* in, size_t inLen, return ret; } +#endif /* WOLFCOSE_COSE_TEXT_LABELS */ /** * Test ECDH-ES (Ephemeral-Static) encryption and decryption. @@ -10383,6 +10392,7 @@ static void test_cose_encrypt_ecdh_es_ephemeral_crv_narrowing(void) (void)wc_FreeRng(&rng); } +#if defined(WOLFCOSE_COSE_TEXT_LABELS) static void test_cose_encrypt_ecdh_es_text_extensions(void) { WOLFCOSE_KEY recipientKey; @@ -10498,6 +10508,7 @@ static void test_cose_encrypt_ecdh_es_text_extensions(void) (void)wc_ecc_free(&recipientEcc); (void)wc_FreeRng(&rng); } +#endif /* WOLFCOSE_COSE_TEXT_LABELS */ static void test_cose_encrypt_ecdh_es_malformed_ephemeral_point(void) { @@ -17056,6 +17067,7 @@ static void test_cose_protected_hdr_tstr_label(void) WOLFCOSE_HDR_STATE hdrState; /* {1: -7, "x": 0} : alg ES256, plus an unknown tstr label */ uint8_t tstrLabel[] = {0xA2u, 0x01u, 0x26u, 0x61u, 'x', 0x00u}; +#if defined(WOLFCOSE_COSE_TEXT_LABELS) /* Same map, with tstr length one encoded non-preferred. */ uint8_t nonPreferred[] = { 0xA2u, 0x01u, 0x26u, 0x78u, 0x01u, 'x', 0x00u @@ -17063,11 +17075,13 @@ static void test_cose_protected_hdr_tstr_label(void) uint8_t duplicateTstr[] = { 0xA2u, 0x61u, 'x', 0x00u, 0x61u, 'x', 0x01u }; +#endif TEST_LOG(" [Protected Header: tstr-labeled entry]\n"); XMEMSET(&hdr, 0, sizeof(hdr)); ret = wolfCose_DecodeProtectedHdr(tstrLabel, sizeof(tstrLabel), &hdr, &hdrState); +#if defined(WOLFCOSE_COSE_TEXT_LABELS) TEST_ASSERT(ret == WOLFCOSE_SUCCESS && hdr.alg == WOLFCOSE_ALG_ES256, "DecodeProtectedHdr skips unknown preferred tstr label"); @@ -17090,6 +17104,10 @@ static void test_cose_protected_hdr_tstr_label(void) &hdr, &hdrState); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "DecodeProtectedHdr rejects duplicate tstr labels"); +#else + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "DecodeProtectedHdr rejects disabled tstr labels"); +#endif } static void test_cose_protected_hdr_dup_label(void) @@ -18518,6 +18536,7 @@ static void test_cose_decode_tstr_alg_values(void) "DecodeUnprotectedHdr tolerates tstr alg"); } +#if defined(WOLFCOSE_COSE_TEXT_LABELS) static int test_cose_encode_text_extension_map(uint8_t firstLabel, size_t count, uint8_t* out, size_t outSz, size_t* outLen) { @@ -18743,6 +18762,7 @@ static void test_cose_decode_unprotected_tstr_label(void) TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "combined text-before-integer capacity rejects one over limit"); } +#endif /* WOLFCOSE_COSE_TEXT_LABELS */ static void test_cose_key_decode_tstr_alg_rejected(void) { @@ -24332,7 +24352,9 @@ int test_cose(void) test_cose_encrypt_ecdh_es_kid_and_alg_pin(); test_cose_encrypt_ecdh_es_ephemeral_crv_narrowing(); test_cose_encrypt_ecdh_es_malformed_ephemeral_point(); +#if defined(WOLFCOSE_COSE_TEXT_LABELS) test_cose_encrypt_ecdh_es_text_extensions(); +#endif test_cose_encrypt_ecdh_es_hkdf_256(); test_cose_encrypt_ecdh_es_long_recipient_protected(); test_cose_encrypt_ecdh_es_wrong_key(); @@ -24541,7 +24563,9 @@ int test_cose(void) defined(WOLFCOSE_HAVE_ES256) && defined(WOLFSSL_KEY_GEN) test_cose_sign_multi_wrong_kty_for_pss(); #endif +#if defined(WOLFCOSE_COSE_TEXT_LABELS) test_cose_decode_unprotected_tstr_label(); +#endif test_cose_sigsize_known_algs(); test_cose_decode_tstr_alg_values(); test_cose_key_decode_tstr_alg_rejected(); From 06962af9c510108d84e876d76c3fd03e0ca5ffe3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 3 Sep 2026 09:50:12 -0700 Subject: [PATCH 3/6] Enforce full PSA EAT source coverage --- .github/workflows/coverage.yml | 10 +++++----- docs/Testing.md | 9 +++++++-- tests/test_eat_psa.c | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 2764b00..1c982c2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -71,7 +71,7 @@ jobs: # Coverage thresholds: # wolfcose.c: 99% minimum # wolfcose_cbor.c: 100% minimum - # wolfcose_eat_psa.c: 80% minimum (full PSA/EAT profile) + # wolfcose_eat_psa.c: 100% minimum (full PSA/EAT profile) FAILED=0 @@ -144,20 +144,20 @@ jobs: else echo "wolfcose_eat_psa.c:" echo " Coverage: ${EAT_PCT}%" - echo " Threshold: 80%" + echo " Threshold: 100%" echo " Uncovered lines: ${EAT_UNCOV}" echo "" - if awk "BEGIN {exit !($EAT_PCT < 80)}"; then + if awk "BEGIN {exit !($EAT_PCT < 100)}"; then FAILED=1 - echo ">>> FAILED: wolfcose_eat_psa.c coverage is below 80%! <<<" + echo ">>> FAILED: wolfcose_eat_psa.c coverage is below 100%! <<<" echo "" echo "Uncovered lines in wolfcose_eat_psa.c:" echo "---------------------------------------" grep -n "#####" wolfcose_eat_psa.c.gcov | head -50 echo "" else - echo ">>> PASSED: wolfcose_eat_psa.c meets 80% threshold <<<" + echo ">>> PASSED: wolfcose_eat_psa.c meets 100% threshold <<<" fi echo "" fi diff --git a/docs/Testing.md b/docs/Testing.md index 2d4aa93..35ee4c0 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,6 +1,11 @@ # Testing -wolfCOSE includes comprehensive testing infrastructure for unit tests, algorithm coverage, code coverage, and failure injection testing. Code coverage is enforced by CI: `wolfcose.c` requires 99% minimum, `wolfcose_cbor.c` requires 100%, and the explicit full `wolfcose_eat_psa.c` profile requires 80%. These thresholds are validated on every push and PR to ensure coverage doesn't regress. +wolfCOSE includes comprehensive testing infrastructure for unit tests, +algorithm coverage, code coverage, and failure injection testing. Code +coverage is enforced by CI: `wolfcose.c` requires 99% minimum, while +`wolfcose_cbor.c` and the explicit full `wolfcose_eat_psa.c` profile require +100%. These thresholds are validated on every push and PR to ensure coverage +doesn't regress. ## Running Tests @@ -144,7 +149,7 @@ collecting the PSA/EAT source coverage report. |-----------|--------| | `wolfcose.c` | 99% minimum | | `wolfcose_cbor.c` | 100% minimum | -| `wolfcose_eat_psa.c` | 80% minimum, full RFC 9783 feature profile | +| `wolfcose_eat_psa.c` | 100% minimum, full RFC 9783 feature profile | ### Coverage with Failure Injection diff --git a/tests/test_eat_psa.c b/tests/test_eat_psa.c index 552e93e..2ef2f5f 100644 --- a/tests/test_eat_psa.c +++ b/tests/test_eat_psa.c @@ -2206,9 +2206,15 @@ static void test_eat_psa_expect_sign1_overlap(WOLFCOSE_KEY* key, static void test_eat_psa_issue_boundaries(WOLFCOSE_KEY* key, WC_RNG* rng) { static const uint8_t invalidUtf8[] = { 0xFFu }; + static const char* componentClaimTests[] = { + "issue and verify component with version only", + "issue and verify component with description only", + "issue and verify component with required claims only" + }; WOLFCOSE_EAT_PSA_CLAIMS claims; WOLFCOSE_EAT_PSA_COMPONENT component; WOLFCOSE_EAT_PSA_COMPONENT components[WOLFCOSE_EAT_PSA_MAX_COMPONENTS + 1u]; + WOLFCOSE_EAT_PSA_TOKEN token; union { WOLFCOSE_EAT_PSA_CLAIMS claims; WOLFCOSE_EAT_PSA_COMPONENT component; @@ -2220,6 +2226,7 @@ static void test_eat_psa_issue_boundaries(WOLFCOSE_KEY* key, WC_RNG* rng) uint8_t out[1024]; uint8_t overlap[2048]; size_t outLen = 17u; + size_t tokenLen; size_t i; int ret; int spanChecks; @@ -2461,6 +2468,31 @@ static void test_eat_psa_issue_boundaries(WOLFCOSE_KEY* key, WC_RNG* rng) sizeof(claimsBuf), "reject issuer optional component text with NULL data"); + for (i = 0u; i < (sizeof(componentClaimTests) / + sizeof(componentClaimTests[0])); i++) { + test_eat_psa_claims(&claims, &component); + component.measurementType.data = NULL; + component.measurementType.len = 0u; + if (i != 0u) { + component.version.data = NULL; + component.version.len = 0u; + } + if (i != 1u) { + component.measurementDesc.data = NULL; + component.measurementDesc.len = 0u; + } + tokenLen = 0u; + ret = wc_CoseEatPsaToken_CreateSign1(key, WOLFCOSE_ALG_ES256, + &claims, claimsBuf, sizeof(claimsBuf), scratch, sizeof(scratch), + out, sizeof(out), &tokenLen, rng); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseEatPsaToken_Verify(key, out, tokenLen, kNonce, + sizeof(kNonce), scratch, sizeof(scratch), &token); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && token.componentCount == 1u, + componentClaimTests[i]); + } + test_eat_psa_claims(&claims, &component); claims.componentCount = 0u; outLen = 17u; From 3b9f9155968b1235e1992020a6c696a56cc1c56e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 3 Sep 2026 09:58:05 -0700 Subject: [PATCH 4/6] Remove PSA EAT release note entries --- ChangeLog.md | 15 --------------- docs/Release-Notes.md | 14 -------------- 2 files changed, 29 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index bf1b586..3c82a8a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,21 +13,6 @@ MAC, and key distribution, and standardized post-quantum ML-DSA signatures ## New Feature Additions -* Optional RFC 9783 PSA attestation / EAT Token support. It consumes tagged - current TF-M and legacy PSA IoT profile Sign1 or Mac0 tokens, validates the - required structural claims and nonce, and emits current-profile tokens with - one enabled RFC 9783 Sign1 or Mac0 protection path. Complete RFC 9783 - `#tfm` receiver capability remains a separately derived gate. - Every PSA/EAT profile, consume or issue envelope, helper, and non-core - algorithm is explicitly macro-gated and absent from a default archive. The - verifier accepts RFC-required non-preferred definite CBOR while ordinary - COSE decode remains strict. Its bounded text-label tracking is separately - selectable for generic COSE parsing and otherwise remains off. Partial - builds reject `#tfm` rather than claiming incomplete conformance; profile, - map-limit, Appendix A TF-M `iat-verifier`, and t_cose interop coverage are in - CI. A device-onboarding example demonstrates current Sign1 issuance, nonce - verification, and software measurement appraisal. See docs/PSA-EAT.md. - * `wc_CoseSign1_Sign_ex()` can emit untagged COSE_Sign1 messages, and `wc_CoseSign1_SignSize_ex()` reports their exact encoded size without signing or invoking an external signer. diff --git a/docs/Release-Notes.md b/docs/Release-Notes.md index 4a37583..2ce136e 100644 --- a/docs/Release-Notes.md +++ b/docs/Release-Notes.md @@ -15,20 +15,6 @@ MAC, and key distribution, and standardized post-quantum ML-DSA signatures ### New Feature Additions -- Optional RFC 9783 PSA attestation / EAT Token support. It consumes tagged - current TF-M and legacy PSA IoT profile Sign1 or Mac0 tokens, validates the - required structural claims and nonce, and emits current-profile tokens with - one enabled RFC 9783 Sign1 or Mac0 protection path. Complete RFC 9783 - `#tfm` receiver capability remains a separately derived gate. - Every PSA/EAT profile, envelope, issuer, helper, and non-core algorithm is - explicitly macro-gated and absent from a default archive. The verifier - accepts RFC-required non-preferred definite CBOR while ordinary COSE decode - remains strict. Partial builds reject `#tfm` rather than claiming incomplete - conformance; profile, map-limit, Appendix A TF-M `iat-verifier`, and t_cose - interop coverage are in CI. A device-onboarding example demonstrates current - Sign1 issuance, nonce verification, and software measurement appraisal. See - [[PSA-EAT]]. - - CBOR engine implementing RFC 8949 encode/decode with no external dependency, enforcing deterministic/preferred-encoding rules and rejecting non-preferred or trailing input on decode. From 2e4fd0c1bdde6c9f0fa44980e6402f80a30e5478 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 3 Sep 2026 11:00:16 -0700 Subject: [PATCH 5/6] Fix PSA EAT CI failures --- Makefile | 34 ++++++------- include/wolfcose/eat_psa.h | 2 +- include/wolfcose/settings.h | 6 +-- scripts/misra-deviations.json | 11 +++++ src/wolfcose_cbor.c | 4 +- src/wolfcose_eat_psa.c | 93 +++++++++++++++++++++++++---------- src/wolfcose_internal.h | 5 +- tests/misra_consumer.c | 40 ++++++++++++++- tests/test_cose.c | 2 - tests/test_eat_psa.c | 68 ++++++++++++------------- 10 files changed, 175 insertions(+), 90 deletions(-) diff --git a/Makefile b/Makefile index 35def2e..e63fd19 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ EAT_PSA_LIMITS_TEST_BIN = tests/test_wolfcose_eat_psa_limits # Full PSA/EAT conformance test profile. Production integrations can select a # smaller subset by defining only the WOLFCOSE_ENABLE_EAT_PSA_* switches they -# need; see docs/PSA-EAT.md. This variable is overrideable for CI experiments. +# need; see docs/PSA-EAT.md. This variable is overridable for CI experiments. EAT_PSA_FULL_FLAGS ?= -DWOLFCOSE_ENABLE_EAT_PSA \ -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1 -DWOLFCOSE_ENABLE_EAT_PSA_MAC0 \ @@ -288,16 +288,16 @@ ecdsa-policy-test: rsapss-policy-test: $(CC) $(CFLAGS) -Werror=unused-function -fsyntax-only \ -DWOLFCOSE_NO_SIGN1 -DWOLFCOSE_NO_SIGN src/wolfcose.c - $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H \ + $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H -DWC_NO_HARDEN \ -DWC_RSA_PSS -DWOLFCOSE_NO_KEY_ENCODE \ -DWOLFCOSE_ENABLE_RSAPSS src/wolfcose.c - $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H \ + $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H -DWC_NO_HARDEN \ -DWC_RSA_PSS -DWOLFCOSE_LEAN_VERIFY \ -DWOLFCOSE_ENABLE_RSAPSS src/wolfcose.c @set -e; \ log_file=$$(mktemp "$${TMPDIR:-/tmp}/wolfcose-rsapss.XXXXXX"); \ trap 'rm -f "$$log_file"' 0 1 2 3 15; \ - if $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H \ + if $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H -DWC_NO_HARDEN \ -UHAVE_ECC -UWOLFSSL_EXPORT_INT \ -DWC_RSA_PSS -DWOLFCOSE_ENABLE_RSAPSS \ -DWOLFSSL_RSA_VERIFY_ONLY -DWOLFCOSE_LEAN_VERIFY \ @@ -307,7 +307,7 @@ rsapss-policy-test: fi; \ grep -q "RSA-PSS key validation requires WOLFSSL_EXPORT_INT" \ "$$log_file" - $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H \ + $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H -DWC_NO_HARDEN \ -UHAVE_ECC -UWOLFSSL_EXPORT_INT \ -DWC_RSA_PSS -DWOLFCOSE_ENABLE_RSAPSS \ -DWOLFSSL_RSA_VERIFY_ONLY -DWOLFSSL_EXPORT_INT \ @@ -316,7 +316,7 @@ rsapss-policy-test: log_file=$$(mktemp "$${TMPDIR:-/tmp}/wolfcose-rsapss.XXXXXX"); \ trap 'rm -f "$$log_file"' 0 1 2 3 15; \ for backend in WOLF_CRYPTO_CB WOLFSSL_MICROCHIP_TA100; do \ - if $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H \ + if $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H -DWC_NO_HARDEN \ -UHAVE_ECC -UWOLFSSL_EXPORT_INT \ -DWC_RSA_PSS -DWOLFCOSE_ENABLE_RSAPSS \ -DWOLFSSL_RSA_VERIFY_ONLY -D$$backend \ @@ -423,7 +423,7 @@ eat-psa-profile-test: eat-psa-config-check: $(MAKE) clean $(MAKE) all - @if nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_"; then \ + @if nm $(LIB_A) | grep "wc_CoseEatPsaToken_" >/dev/null; then \ echo "FAIL: default library exports PSA/EAT"; exit 1; \ fi @echo "PASS: default library has no PSA/EAT symbols" @@ -432,48 +432,48 @@ eat-psa-config-check: $(MAKE) test EXTRA_CFLAGS='-DWOLFCOSE_ENABLE_COSE_TEXT_LABELS' @echo "PASS: generic COSE text-label extension is independently selectable" $(MAKE) all EXTRA_CFLAGS='$(EAT_PSA_FULL_FLAGS)' - @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_Verify"; then \ + @if ! nm $(LIB_A) | grep "wc_CoseEatPsaToken_Verify" >/dev/null; then \ echo "FAIL: enabled library omits PSA/EAT verifier"; exit 1; \ fi $(MAKE) all - @if nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_"; then \ + @if nm $(LIB_A) | grep "wc_CoseEatPsaToken_" >/dev/null; then \ echo "FAIL: enabled-to-default build retained PSA/EAT symbols"; exit 1; \ fi $(MAKE) all EXTRA_CFLAGS='-DHAVE_CONFIG_H -I./tests/config/eat_psa_config' - @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_Verify"; then \ + @if ! nm $(LIB_A) | grep "wc_CoseEatPsaToken_Verify" >/dev/null; then \ echo "FAIL: config.h-enabled library omits PSA/EAT verifier"; exit 1; \ fi $(MAKE) all - @if nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_"; then \ + @if nm $(LIB_A) | grep "wc_CoseEatPsaToken_" >/dev/null; then \ echo "FAIL: config.h-to-default build retained PSA/EAT symbols"; exit 1; \ fi $(MAKE) all EXTRA_CFLAGS='-DWOLFCOSE_LEAN $(EAT_PSA_NO_DECODE_FLAGS) \ -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE' - @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_EncodeClaims"; then \ + @if ! nm $(LIB_A) | grep "wc_CoseEatPsaToken_EncodeClaims" >/dev/null; then \ echo "FAIL: claim-only issuer omits claim encoder"; exit 1; \ fi - @if nm $(LIB_A) | grep -E -q "wc_CoseEatPsaToken_(Verify|Create)"; then \ + @if nm $(LIB_A) | grep -E "wc_CoseEatPsaToken_(Verify|Create)" >/dev/null; then \ echo "FAIL: claim-only issuer contains a verifier or envelope creator"; exit 1; \ fi $(MAKE) all EXTRA_CFLAGS='-DWOLFCOSE_LEAN $(EAT_PSA_NO_DECODE_FLAGS) \ -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ -DWOLFCOSE_ENABLE_EAT_PSA_SIGN1_ISSUE' - @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_CreateSign1"; then \ + @if ! nm $(LIB_A) | grep "wc_CoseEatPsaToken_CreateSign1" >/dev/null; then \ echo "FAIL: Sign1-only issuer omits Sign1 creator"; exit 1; \ fi - @if nm $(LIB_A) | grep -E -q "wc_CoseEatPsaToken_(Verify|CreateMac0)"; then \ + @if nm $(LIB_A) | grep -E "wc_CoseEatPsaToken_(Verify|CreateMac0)" >/dev/null; then \ echo "FAIL: Sign1-only issuer contains verifier or Mac0 creator"; exit 1; \ fi $(MAKE) all EXTRA_CFLAGS='-DWOLFCOSE_LEAN $(EAT_PSA_NO_DECODE_FLAGS) \ -DWOLFCOSE_ENABLE_EAT_PSA -DWOLFCOSE_ENABLE_EAT_PSA_CURRENT \ -DWOLFCOSE_ENABLE_EAT_PSA_ISSUE \ -DWOLFCOSE_ENABLE_EAT_PSA_MAC0_ISSUE' - @if ! nm $(LIB_A) | grep -q "wc_CoseEatPsaToken_CreateMac0"; then \ + @if ! nm $(LIB_A) | grep "wc_CoseEatPsaToken_CreateMac0" >/dev/null; then \ echo "FAIL: Mac0-only issuer omits Mac0 creator"; exit 1; \ fi - @if nm $(LIB_A) | grep -E -q "wc_CoseEatPsaToken_(Verify|CreateSign1)"; then \ + @if nm $(LIB_A) | grep -E "wc_CoseEatPsaToken_(Verify|CreateSign1)" >/dev/null; then \ echo "FAIL: Mac0-only issuer contains verifier or Sign1 creator"; exit 1; \ fi $(MAKE) all diff --git a/include/wolfcose/eat_psa.h b/include/wolfcose/eat_psa.h index 5b8ca44..b083548 100644 --- a/include/wolfcose/eat_psa.h +++ b/include/wolfcose/eat_psa.h @@ -30,7 +30,7 @@ extern "C" { #ifdef WOLFCOSE_EAT_PSA #define WOLFCOSE_EAT_PSA_PROFILE_TFM \ - "tag:psacertified.org,2023:psa#tfm" + "tag:psacertified.org,2023:psa\x23" "tfm" #define WOLFCOSE_EAT_PSA_PROFILE_LEGACY "PSA_IOT_PROFILE_1" /* PSA/EAT-specific errors are intentionally absent from the base API when diff --git a/include/wolfcose/settings.h b/include/wolfcose/settings.h index 6da0908..58a86fd 100644 --- a/include/wolfcose/settings.h +++ b/include/wolfcose/settings.h @@ -143,9 +143,9 @@ extern "C" { /* ES256 — core. ECC_USER_CURVES keeps P-256 unless NO_ECC256 selects it * out; HAVE_ALL_CURVES is the equivalent all-curves configuration. */ -#if defined(HAVE_ECC) && !defined(NO_SHA256) && !defined(NO_ECC256) && \ - (!defined(ECC_MIN_KEY_SZ) || (ECC_MIN_KEY_SZ <= 256)) && \ - !defined(WOLFCOSE_NO_ES256) +#if !defined(WOLFCOSE_NO_ES256) && defined(HAVE_ECC) && \ + !defined(NO_SHA256) && !defined(NO_ECC256) && \ + (!defined(ECC_MIN_KEY_SZ) || (ECC_MIN_KEY_SZ <= 256)) #define WOLFCOSE_HAVE_ES256 #endif diff --git a/scripts/misra-deviations.json b/scripts/misra-deviations.json index 3a6dfc2..4e2bcc8 100644 --- a/scripts/misra-deviations.json +++ b/scripts/misra-deviations.json @@ -11,6 +11,17 @@ ], "expected": 1 }, + { + "id": "D-11.4-001", + "rule": "11.4", + "path": "src/wolfcose_eat_psa.c", + "symbol": "wolfCose_EatPsaBuffersOverlap", + "anchors": [ + " uintptr_t aStart = (uintptr_t)a;", + " uintptr_t bStart = (uintptr_t)b;" + ], + "expected": 2 + }, { "id": "D-19.2-001", "rule": "19.2", diff --git a/src/wolfcose_cbor.c b/src/wolfcose_cbor.c index 574228d..4166c7a 100644 --- a/src/wolfcose_cbor.c +++ b/src/wolfcose_cbor.c @@ -67,7 +67,9 @@ static int wolfCose_CBOR_IsUtf8(const uint8_t* data, size_t len) ret = 0; } while ((i < len) && (ret != 0)) { - uint8_t first = data[i++]; + uint8_t first = data[i]; + + i++; if (first <= 0x7Fu) { /* Single-byte ASCII code point. */ diff --git a/src/wolfcose_eat_psa.c b/src/wolfcose_eat_psa.c index 6d1f04d..57c759b 100644 --- a/src/wolfcose_eat_psa.c +++ b/src/wolfcose_eat_psa.c @@ -89,10 +89,6 @@ (defined(WOLFCOSE_EAT_PSA_TFM_FULL) || defined(WOLFCOSE_EAT_PSA_ISSUE)) static const uint8_t kEatPsaTfmProfile[] = WOLFCOSE_EAT_PSA_PROFILE_TFM; #endif -#if defined(WOLFCOSE_EAT_PSA_LEGACY) -static const uint8_t kEatPsaLegacyProfile[] = WOLFCOSE_EAT_PSA_PROFILE_LEGACY; -#endif - /* RFC 9783 Section 5.1 allows CBOR variation serialization. Keep that * tolerance scoped to authenticated PSA/EAT parsing; the public CBOR API * remains strict and carries no profile-specific decode state. */ @@ -122,6 +118,21 @@ static const uint8_t kEatPsaLegacyProfile[] = WOLFCOSE_EAT_PSA_PROFILE_LEGACY; #define WOLFCOSE_EAT_PSA_SKIP(ctx) \ wolfCose_CBOR_Skip_ex((ctx), WOLFCOSE_EAT_PSA_DECODE_FLAGS) +#if defined(WOLFCOSE_EAT_PSA_VERIFY) +static int wolfCose_EatPsaConstantCompare(const uint8_t* a, const uint8_t* b, + size_t length) +{ + size_t i; + volatile unsigned int result = 0u; + + for (i = 0u; i < length; i++) { + result |= (unsigned int)a[i] ^ (unsigned int)b[i]; + } + + return (int)result; +} +#endif + #if defined(WOLFCOSE_EAT_PSA_ISSUE) static int wolfCose_EatPsaBuffersOverlap(const uint8_t* a, size_t aSz, const uint8_t* b, size_t bSz) @@ -129,14 +140,21 @@ static int wolfCose_EatPsaBuffersOverlap(const uint8_t* a, size_t aSz, int overlap = 0; if ((a != NULL) && (b != NULL) && (aSz != 0u) && (bSz != 0u)) { - uintptr_t aStart = (uintptr_t)(const void*)a; - uintptr_t bStart = (uintptr_t)(const void*)b; - - if (aStart <= bStart) { - overlap = ((bStart - aStart) < (uintptr_t)aSz) ? 1 : 0; + /* uintptr_t is used intentionally: relational comparison of pointers + * to unrelated caller-owned objects is not defined by ISO C. */ + uintptr_t aStart = (uintptr_t)a; + uintptr_t bStart = (uintptr_t)b; + + if ((aStart <= bStart) && + ((bStart - aStart) < (uintptr_t)aSz)) { + overlap = 1; + } + else if ((aStart > bStart) && + ((aStart - bStart) < (uintptr_t)bSz)) { + overlap = 1; } else { - overlap = ((aStart - bStart) < (uintptr_t)bSz) ? 1 : 0; + /* The nonempty buffer ranges are disjoint. */ } } @@ -159,9 +177,9 @@ static int wolfCose_EatPsaComponentOverlapsBuffer( int overlap = 0; if (component != NULL) { - overlap = + overlap = ( (wolfCose_EatPsaBuffersOverlap( - (const uint8_t*)(const void*)component, sizeof(*component), + (const uint8_t*)component, sizeof(*component), buffer, bufferSz) != 0) || (wolfCose_EatPsaSpanOverlapsBuffer(&component->measurementType, buffer, bufferSz) != 0) || @@ -172,7 +190,7 @@ static int wolfCose_EatPsaComponentOverlapsBuffer( (wolfCose_EatPsaSpanOverlapsBuffer(&component->signerId, buffer, bufferSz) != 0) || (wolfCose_EatPsaSpanOverlapsBuffer(&component->measurementDesc, - buffer, bufferSz) != 0); + buffer, bufferSz) != 0)) ? 1 : 0; } return overlap; @@ -190,9 +208,9 @@ static int wolfCose_EatPsaClaimsOverlapBuffer( if (claims != NULL) { size_t i; - overlap = + overlap = ( (wolfCose_EatPsaBuffersOverlap( - (const uint8_t*)(const void*)claims, sizeof(*claims), + (const uint8_t*)claims, sizeof(*claims), buffer, bufferSz) != 0) || (wolfCose_EatPsaSpanOverlapsBuffer(&claims->nonce, buffer, bufferSz) != 0) || @@ -206,7 +224,7 @@ static int wolfCose_EatPsaClaimsOverlapBuffer( &claims->certificationReference, buffer, bufferSz) != 0) || (wolfCose_EatPsaSpanOverlapsBuffer( &claims->verificationServiceIndicator, buffer, - bufferSz) != 0); + bufferSz) != 0)) ? 1 : 0; for (i = 0u; (overlap == 0) && (i < claims->componentCount); i++) { overlap = wolfCose_EatPsaComponentOverlapsBuffer( &claims->components[i], buffer, bufferSz); @@ -250,9 +268,10 @@ static int wolfCose_EatPsaIsOptionalSpan(const WOLFCOSE_EAT_PSA_SPAN* span) { int ret = 0; - if (span != NULL) { - ret = (((span->data == NULL) && (span->len == 0u)) || - (span->data != NULL)) ? 1 : 0; + if ((span != NULL) && + (((span->data == NULL) && (span->len == 0u)) || + (span->data != NULL))) { + ret = 1; } return ret; @@ -291,6 +310,9 @@ static int wolfCose_EatPsaCertRefValid(const WOLFCOSE_EAT_PSA_SPAN* span, (span->data[i] > (uint8_t)'9')) { ret = 0; } + else { + /* The character is a valid decimal digit. */ + } } } @@ -751,6 +773,9 @@ static int wolfCose_EatPsaDecodeComponent(WOLFCOSE_CBOR_CTX* ctx, else if (ret == WOLFCOSE_SUCCESS) { ret = WOLFCOSE_EAT_PSA_SKIP(ctx); } + else { + /* Preserve the error produced while decoding the component. */ + } } if ((ret == WOLFCOSE_SUCCESS) && ((wolfCose_EatPsaIsHash(&component->measurementValue) == 0) || @@ -767,6 +792,7 @@ static int wolfCose_EatPsaDecodeComponents(WOLFCOSE_CBOR_CTX* ctx, int ret; size_t i; size_t count = 0u; + size_t startIdx = 0u; const uint8_t* start; WOLFCOSE_EAT_PSA_COMPONENT component; @@ -774,7 +800,8 @@ static int wolfCose_EatPsaDecodeComponents(WOLFCOSE_CBOR_CTX* ctx, ret = WOLFCOSE_E_INVALID_ARG; } else { - start = &ctx->cbuf[ctx->idx]; + startIdx = ctx->idx; + start = &ctx->cbuf[startIdx]; ret = WOLFCOSE_EAT_PSA_DECODE_ARRAY(ctx, &count); } if ((ret == WOLFCOSE_SUCCESS) && @@ -786,7 +813,7 @@ static int wolfCose_EatPsaDecodeComponents(WOLFCOSE_CBOR_CTX* ctx, } if (ret == WOLFCOSE_SUCCESS) { token->components.data = start; - token->components.len = (size_t)(&ctx->cbuf[ctx->idx] - start); + token->components.len = ctx->idx - startIdx; token->componentCount = count; } @@ -814,8 +841,11 @@ static int wolfCose_EatPsaSetProfile(WOLFCOSE_EAT_PSA_TOKEN* token, } else if (profile == WOLFCOSE_EAT_PSA_PROFILE_OLD) { #if defined(WOLFCOSE_EAT_PSA_LEGACY) - expected = kEatPsaLegacyProfile; - expectedLen = sizeof(kEatPsaLegacyProfile) - 1u; + static const uint8_t legacyProfile[] = + WOLFCOSE_EAT_PSA_PROFILE_LEGACY; + + expected = legacyProfile; + expectedLen = sizeof(legacyProfile) - 1u; #else ret = WOLFCOSE_E_EAT_PSA_PROFILE; #endif @@ -825,7 +855,7 @@ static int wolfCose_EatPsaSetProfile(WOLFCOSE_EAT_PSA_TOKEN* token, } if (ret == WOLFCOSE_SUCCESS) { if ((textLen != expectedLen) || - (XMEMCMP(text, expected, textLen) != 0)) { + (wolfCose_EatPsaConstantCompare(text, expected, textLen) != 0)) { ret = WOLFCOSE_E_EAT_PSA_PROFILE; } else { @@ -1113,6 +1143,9 @@ static int wolfCose_EatPsaDecodeClaims(const uint8_t* payload, } } } + else { + /* Preserve the error produced while decoding the map label. */ + } } if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { ret = WOLFCOSE_E_EAT_PSA_CLAIM; @@ -1130,6 +1163,9 @@ static int wolfCose_EatPsaDecodeClaims(const uint8_t* payload, else if (token->profile != WOLFCOSE_EAT_PSA_PROFILE_CURRENT) { ret = WOLFCOSE_E_EAT_PSA_PROFILE; } + else { + /* All required current-profile claims were decoded. */ + } } #endif #if defined(WOLFCOSE_EAT_PSA_LEGACY) @@ -1148,6 +1184,9 @@ static int wolfCose_EatPsaDecodeClaims(const uint8_t* payload, else if (token->profile != WOLFCOSE_EAT_PSA_PROFILE_OLD) { ret = WOLFCOSE_E_EAT_PSA_PROFILE; } + else { + /* All required legacy-profile claims were decoded. */ + } } #endif if ((ret == WOLFCOSE_SUCCESS) && @@ -1288,9 +1327,13 @@ static int wolfCose_EatPsaCheckNonce(const WOLFCOSE_EAT_PSA_TOKEN* token, ret = WOLFCOSE_E_INVALID_ARG; } else if ((token->nonce.len != expectedNonceLen) || - (XMEMCMP(token->nonce.data, expectedNonce, expectedNonceLen) != 0)) { + (wolfCose_EatPsaConstantCompare(token->nonce.data, expectedNonce, + expectedNonceLen) != 0)) { ret = WOLFCOSE_E_EAT_PSA_NONCE; } + else { + /* The authenticated nonce matches the caller's challenge. */ + } return ret; } diff --git a/src/wolfcose_internal.h b/src/wolfcose_internal.h index 696bd2e..08d2e00 100644 --- a/src/wolfcose_internal.h +++ b/src/wolfcose_internal.h @@ -129,9 +129,10 @@ static inline uint64_t wolfCose_LoadBE64(const uint8_t* buf) #if defined(WOLFCOSE_EAT_PSA_VERIFY) #define WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) \ - (((flags) & ~WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED) == 0u) + ((((flags) & ~WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED) == 0u) ? 1 : 0) #else - #define WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) ((flags) == 0u) + #define WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) \ + (((flags) == 0u) ? 1 : 0) #endif #if defined(WOLFCOSE_EAT_PSA_VERIFY) diff --git a/tests/misra_consumer.c b/tests/misra_consumer.c index ca25de1..6b49e1c 100644 --- a/tests/misra_consumer.c +++ b/tests/misra_consumer.c @@ -6,6 +6,7 @@ */ #include +#include #define WOLFCOSE_MISRA_USE_API(name) total += sizeof(&(name)) @@ -113,6 +114,30 @@ static size_t wolfCose_MisraUsePublicApis(void) #ifdef WOLFCOSE_MAC_VERIFY WOLFCOSE_MISRA_USE_API(wc_CoseMac_Verify); #endif +#ifdef WOLFCOSE_EAT_PSA_ISSUE + WOLFCOSE_MISRA_USE_API(wc_CoseEatPsaToken_EncodeClaims); + WOLFCOSE_MISRA_USE_API(wc_EatPsaToken_EncodeClaims); +#endif +#ifdef WOLFCOSE_EAT_PSA_SIGN1_ISSUE + WOLFCOSE_MISRA_USE_API(wc_CoseEatPsaToken_CreateSign1); + WOLFCOSE_MISRA_USE_API(wc_EatPsaToken_CreateSign1); +#endif +#ifdef WOLFCOSE_EAT_PSA_MAC0_ISSUE + WOLFCOSE_MISRA_USE_API(wc_CoseEatPsaToken_CreateMac0); + WOLFCOSE_MISRA_USE_API(wc_EatPsaToken_CreateMac0); +#endif +#ifdef WOLFCOSE_EAT_PSA_VERIFY + WOLFCOSE_MISRA_USE_API(wc_CoseEatPsaToken_Verify); + WOLFCOSE_MISRA_USE_API(wc_EatPsaToken_Verify); +#endif +#ifdef WOLFCOSE_EAT_PSA_UEID_RESOLVER + WOLFCOSE_MISRA_USE_API(wc_CoseEatPsaToken_VerifyByUeid); + WOLFCOSE_MISRA_USE_API(wc_EatPsaToken_VerifyByUeid); +#endif +#ifdef WOLFCOSE_EAT_PSA_COMPONENT_ITERATOR + WOLFCOSE_MISRA_USE_API(wc_CoseEatPsaToken_ForEachComponent); + WOLFCOSE_MISRA_USE_API(wc_EatPsaToken_ForEachComponent); +#endif return total; } @@ -137,7 +162,13 @@ int main(void) WOLFCOSE_CRV_ML_DSA_44, WOLFCOSE_CRV_ML_DSA_65, WOLFCOSE_CRV_ML_DSA_87, - (int32_t)LIBWOLFCOSE_VERSION_HEX + (int32_t)LIBWOLFCOSE_VERSION_HEX, +#ifdef WOLFCOSE_EAT_PSA + WOLFCOSE_E_EAT_PSA_CLAIM, + WOLFCOSE_E_EAT_PSA_PROFILE, + WOLFCOSE_E_EAT_PSA_NONCE, + WOLFCOSE_E_EAT_PSA_KEY +#endif }; size_t used = wolfCose_MisraUsePublicApis(); @@ -146,6 +177,10 @@ int main(void) used = 0u; } used += sizeof(LIBWOLFCOSE_VERSION_STRING); +#ifdef WOLFCOSE_EAT_PSA + used += sizeof(WOLFCOSE_EAT_PSA_PROFILE_TFM); + used += sizeof(WOLFCOSE_EAT_PSA_PROFILE_LEGACY); +#endif #ifdef WOLFCOSE_RECIPIENTS used += 1u; #endif @@ -156,5 +191,6 @@ int main(void) used += 1u; #endif - return (used == 0u) ? 1 : 0; + (void)used; + return 0; } diff --git a/tests/test_cose.c b/tests/test_cose.c index 4a10601..f4fad13 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -4342,7 +4342,6 @@ static void test_cose_mac_multi_per_recipient(void) TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, "mac rejects later mixed recipient mode"); out[algOffsets[1]] = 0x25u; /* direct */ - ret = WOLFCOSE_SUCCESS; out[algOffsets[0]] = 0x22u; /* A128KW */ ret = wc_CoseMac_Verify(&recipients[1], 1, out, outLen, NULL, 0, NULL, 0, scratch, sizeof(scratch), @@ -16940,7 +16939,6 @@ static void test_cose_encrypt_multi_per_recipient(void) TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, "encrypt rejects later mixed recipient mode"); out[algOffsets[1]] = 0x25u; /* direct */ - ret = WOLFCOSE_SUCCESS; out[algOffsets[0]] = 0x22u; /* A128KW */ ret = wc_CoseEncrypt_Decrypt(&recipients[1], 1, out, outLen, NULL, 0, NULL, 0, scratch, sizeof(scratch), &hdr, diff --git a/tests/test_eat_psa.c b/tests/test_eat_psa.c index 2ef2f5f..52f10a8 100644 --- a/tests/test_eat_psa.c +++ b/tests/test_eat_psa.c @@ -2620,6 +2620,9 @@ static void test_eat_psa_issue_boundaries(WOLFCOSE_KEY* key, WC_RNG* rng) #if defined(WOLFCOSE_SIGN1_VERIFY) && defined(WOLFCOSE_HAVE_ES256) static void test_eat_psa_rfc9783_sign1(void) { + static const uint8_t protectedX5chain[] = { + 0xA2u, 0x01u, 0x26u, 0x18u, 0x21u, 0x40u + }; WOLFCOSE_EAT_PSA_TOKEN token; EAT_PSA_COMPONENT_CTX componentCtx; WOLFCOSE_KEY key; @@ -2639,18 +2642,12 @@ static void test_eat_psa_rfc9783_sign1(void) int keyInited = 0; (void)printf(" [RFC 9783 Appendix A Sign1]\n"); - { - static const uint8_t protectedX5chain[] = { - 0xA2u, 0x01u, 0x26u, 0x18u, 0x21u, 0x40u - }; - - (void)memset(&hdr, 0, sizeof(hdr)); - ret = wolfCose_DecodeProtectedHdr(protectedX5chain, - sizeof(protectedX5chain), &hdr, &hdrState); - TEST_ASSERT(ret == WOLFCOSE_SUCCESS && - ((hdr.flags & WOLFCOSE_HDR_FLAG_X5CHAIN) != 0u), - "detect x5chain in protected COSE headers"); - } + (void)memset(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(protectedX5chain, + sizeof(protectedX5chain), &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + ((hdr.flags & WOLFCOSE_HDR_FLAG_X5CHAIN) != 0u), + "detect x5chain in protected COSE headers"); ret = test_eat_psa_hex_decode(kRfc9783Sign1Hex, tokenBuf, sizeof(tokenBuf), &tokenLen); TEST_ASSERT(ret == WOLFCOSE_SUCCESS && tokenLen > 0u, @@ -2818,6 +2815,9 @@ static void test_eat_psa_sign1(void) static const uint16_t lifecycleClasses[] = { 0x1000u, 0x4000u, 0x5000u, 0x6000u }; + static const uint16_t provisioningLifecycles[] = { + 0x2000u, 0x20FFu + }; WOLFCOSE_EAT_PSA_CLAIMS claims; WOLFCOSE_EAT_PSA_CLAIMS malformedClaims; WOLFCOSE_EAT_PSA_CLAIMS multiClaims; @@ -3377,31 +3377,25 @@ static void test_eat_psa_sign1(void) "retain unknown lifecycle for application policy"); } - { - static const uint16_t provisioningLifecycles[] = { - 0x2000u, 0x20FFu - }; - - for (i = 0u; i < (sizeof(provisioningLifecycles) / - sizeof(provisioningLifecycles[0])); i++) { - (void)memcpy(lifecyclePayload, payload, payloadLen); - testRet = test_eat_psa_set_lifecycle(lifecyclePayload, - payloadLen, provisioningLifecycles[i]); - if (testRet == WOLFCOSE_SUCCESS) { - testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, - NULL, 0u, lifecyclePayload, payloadLen, NULL, 0u, - NULL, 0u, scratch, sizeof(scratch), legacyToken, - sizeof(legacyToken), &auxTokenLen, &rng); - } - if (testRet == WOLFCOSE_SUCCESS) { - testRet = wc_EatPsaToken_Verify(&key, legacyToken, - auxTokenLen, kNonce, sizeof(kNonce), scratch, - sizeof(scratch), &token); - } - TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && - token.lifecycle == provisioningLifecycles[i], - "accept signed PSA RoT provisioning lifecycle boundaries"); + for (i = 0u; i < (sizeof(provisioningLifecycles) / + sizeof(provisioningLifecycles[0])); i++) { + (void)memcpy(lifecyclePayload, payload, payloadLen); + testRet = test_eat_psa_set_lifecycle(lifecyclePayload, + payloadLen, provisioningLifecycles[i]); + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES256, + NULL, 0u, lifecyclePayload, payloadLen, NULL, 0u, + NULL, 0u, scratch, sizeof(scratch), legacyToken, + sizeof(legacyToken), &auxTokenLen, &rng); + } + if (testRet == WOLFCOSE_SUCCESS) { + testRet = wc_EatPsaToken_Verify(&key, legacyToken, + auxTokenLen, kNonce, sizeof(kNonce), scratch, + sizeof(scratch), &token); } + TEST_ASSERT(testRet == WOLFCOSE_SUCCESS && + token.lifecycle == provisioningLifecycles[i], + "accept signed PSA RoT provisioning lifecycle boundaries"); } (void)memcpy(lifecyclePayload, payload, payloadLen); testRet = test_eat_psa_set_lifecycle(lifecyclePayload, payloadLen, @@ -4082,7 +4076,7 @@ static void test_eat_psa_mac0(void) TEST_ASSERT(0, "expected deterministic Mac0 protected header"); } } - if (ret == WOLFCOSE_E_COSE_BAD_ALG) { + if ((ret == WOLFCOSE_E_COSE_BAD_ALG) && (tokenLen > 0u)) { (void)memcpy(modifiedToken, tokenBuf, tokenLen); modifiedToken[tokenLen - 1u] ^= 0xFFu; (void)memset(&token, 0xA5, sizeof(token)); From 738d78e54e78b368a423b40f91fb08d79fa26993 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 3 Sep 2026 11:10:06 -0700 Subject: [PATCH 6/6] Fix remaining macOS and MISRA checks --- Makefile | 2 +- src/wolfcose_eat_psa.c | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index e63fd19..3bad78b 100644 --- a/Makefile +++ b/Makefile @@ -143,7 +143,7 @@ SCEN_BROADCAST = examples/scenarios/group_broadcast_mac # --- Core library --- all: $(LIB_A) -$(LIB_A): $(OBJ) +$(LIB_A): $(OBJ) $(BUILD_CONFIG_CHANGED) $(AR) rcs $@ $(OBJ) FORCE: diff --git a/src/wolfcose_eat_psa.c b/src/wolfcose_eat_psa.c index 57c759b..991da50 100644 --- a/src/wolfcose_eat_psa.c +++ b/src/wolfcose_eat_psa.c @@ -176,8 +176,7 @@ static int wolfCose_EatPsaComponentOverlapsBuffer( { int overlap = 0; - if (component != NULL) { - overlap = ( + if ((component != NULL) && ( (wolfCose_EatPsaBuffersOverlap( (const uint8_t*)component, sizeof(*component), buffer, bufferSz) != 0) || @@ -190,7 +189,8 @@ static int wolfCose_EatPsaComponentOverlapsBuffer( (wolfCose_EatPsaSpanOverlapsBuffer(&component->signerId, buffer, bufferSz) != 0) || (wolfCose_EatPsaSpanOverlapsBuffer(&component->measurementDesc, - buffer, bufferSz) != 0)) ? 1 : 0; + buffer, bufferSz) != 0))) { + overlap = 1; } return overlap; @@ -208,7 +208,7 @@ static int wolfCose_EatPsaClaimsOverlapBuffer( if (claims != NULL) { size_t i; - overlap = ( + if ( (wolfCose_EatPsaBuffersOverlap( (const uint8_t*)claims, sizeof(*claims), buffer, bufferSz) != 0) || @@ -224,7 +224,9 @@ static int wolfCose_EatPsaClaimsOverlapBuffer( &claims->certificationReference, buffer, bufferSz) != 0) || (wolfCose_EatPsaSpanOverlapsBuffer( &claims->verificationServiceIndicator, buffer, - bufferSz) != 0)) ? 1 : 0; + bufferSz) != 0)) { + overlap = 1; + } for (i = 0u; (overlap == 0) && (i < claims->componentCount); i++) { overlap = wolfCose_EatPsaComponentOverlapsBuffer( &claims->components[i], buffer, bufferSz);