diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 9078a61a..321f24a4 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -89,6 +89,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 @@ -113,6 +169,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 3ab38377..0c8ebf3c 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 3420659c..05daa61c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -61,7 +61,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" @@ -109,14 +109,45 @@ jobs: echo "" done + # 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: 100%" + echo " Uncovered lines: ${EAT_UNCOV}" + echo "" + + if awk "BEGIN {exit !($EAT_PCT < 100)}"; then + FAILED=1 + 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 100% 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 c2dc9321..2790da95 100644 --- a/.github/workflows/interop.yml +++ b/.github/workflows/interop.yml @@ -23,7 +23,7 @@ jobs: uses: ./.github/workflows/_resolve-wolfssl.yml interop: - name: COSE interoperability (OpenSSL, wolfSSL ${{ matrix.wolfssl-version }}) + name: COSE and PSA/EAT interoperability (OpenSSL, wolfSSL ${{ matrix.wolfssl-version }}) needs: discover runs-on: ubuntu-latest # All external peers share this job and its wolfSSL build. Keeping them @@ -112,7 +112,15 @@ jobs: make WOLFSSL_CFLAGS="-isystem $WOLFSSL_DIR/include" \ WOLFSSL_LIBS="-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 32b3ee0a..107601ee 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) @@ -142,6 +164,13 @@ jobs: build_cfg "default (full)" build_cfg "WOLFCOSE_LEAN (sign+verify)" -DWOLFCOSE_LEAN build_cfg "WOLFCOSE_LEAN_VERIFY" -DWOLFCOSE_LEAN_VERIFY + build_cfg "WOLFCOSE_LEAN_VERIFY + RFC 9783 PSA/EAT" \ + -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 build_cfg "COSE_Mac without COSE_Mac0" -DWOLFCOSE_NO_MAC0 build_cfg "sign-only" -DWOLFCOSE_NO_SIGN1_VERIFY -DWOLFCOSE_NO_ENCRYPT0 \ -DWOLFCOSE_NO_MAC0 -DWOLFCOSE_NO_SIGN -DWOLFCOSE_NO_ENCRYPT \ @@ -150,6 +179,14 @@ jobs: build_cfg "WOLFCOSE_LEAN_VERIFY_MLDSA (verify-only)" -DWOLFCOSE_LEAN_VERIFY_MLDSA 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 79b8914f..c970e1ea 100644 --- a/.github/workflows/misra-2012.yml +++ b/.github/workflows/misra-2012.yml @@ -82,8 +82,18 @@ 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_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 \ -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 \ @@ -108,6 +118,16 @@ 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_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 \ -DWOLFCOSE_FLOAT \ -DWOLFCOSE_ENABLE_EXT_SIGN \ -I include -I src -I $HOME/wolfssl-install/include \ diff --git a/.github/workflows/misra-2023.yml b/.github/workflows/misra-2023.yml index 4d407262..58155dcd 100644 --- a/.github/workflows/misra-2023.yml +++ b/.github/workflows/misra-2023.yml @@ -102,6 +102,16 @@ 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_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 \ -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 +231,13 @@ 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_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 \ -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 48bc592d..52cb3635 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 b2fa3b0c..0bdd089e 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_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 - 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 75cdf39c..5bce9376 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -65,9 +65,31 @@ 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_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 \ + -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 +130,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 +186,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 40b2b464..c3a83149 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -181,6 +181,16 @@ jobs: make test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion $WOLFCOSE_COMPAT_FLAGS -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 $WOLFCOSE_COMPAT_FLAGS -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 f732f2e7..037c079b 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 @@ -27,6 +29,8 @@ examples/ext_sign_demo examples/sign1_verify_mldsa examples/sign1_lms examples/sign1_verify_lms +examples/psa_eat_demo +examples/psa_eat_verify_lean # Editor / OS *.swp diff --git a/Makefile b/Makefile index 6d2be2a8..22cd2e5c 100644 --- a/Makefile +++ b/Makefile @@ -41,13 +41,16 @@ 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 \ +CORE_SRC = src/wolfcose_cbor.c \ src/wolfcose_util.c \ src/wolfcose_alg.c \ src/wolfcose_ecc.c \ @@ -62,6 +65,8 @@ SRC = src/wolfcose_cbor.c \ src/wolfcose_mac0.c \ src/wolfcose_encrypt.c \ src/wolfcose_mac.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 @@ -88,8 +93,34 @@ endif # Tests (mirrors two-layer lib architecture) TEST_SRC = tests/test_cbor.c tests/test_cose.c tests/test_interop.c \ tests/test_cose_examples.c tests/test_psa_attestation.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 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 \ + -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 \ + -DWOLFCOSE_NO_HMAC384 -DWOLFCOSE_NO_HMAC512 # Tools (compiled separately, never in core lib) TOOL_SRC = tools/wolfcose_tool.c @@ -102,6 +133,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 @@ -121,14 +154,14 @@ 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 countersign-config-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 lms-demo lms-verify comprehensive scenarios interop-tcose tcose-upstream interop-go-cose interop-python-cwt interop-rust-coset c99-check c99-check-lms experimental-check clean FORCE +.PHONY: all shared test pkg-config-test ecdsa-policy-test rsapss-policy-test countersign-config-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 lms-demo lms-verify comprehensive scenarios interop-tcose tcose-upstream interop-go-cose interop-python-cwt interop-rust-coset c99-check c99-check-lms experimental-check clean FORCE # --- Core library --- all: $(LIB_A) -$(LIB_A): $(OBJ) +$(LIB_A): $(OBJ) $(BUILD_CONFIG_CHANGED) rm -f $@ - $(AR) rcs $@ $^ + $(AR) rcs $@ $(OBJ) FORCE: @@ -143,6 +176,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 @@ -274,16 +309,16 @@ ecdsa-policy-test: rsapss-policy-test: $(CC) $(CFLAGS) -Werror=unused-function -fsyntax-only \ -DWOLFCOSE_NO_SIGN1 -DWOLFCOSE_NO_SIGN $(SRC) - $(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) - $(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) @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 \ @@ -293,7 +328,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 \ @@ -302,7 +337,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 \ @@ -372,6 +407,350 @@ 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 \ + -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) + ./$(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 \ + -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) + ./$(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 "wc_CoseEatPsaToken_" >/dev/null; 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 "wc_CoseEatPsaToken_Verify" >/dev/null; then \ + echo "FAIL: enabled library omits PSA/EAT verifier"; exit 1; \ + fi + $(MAKE) all + @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 "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 "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 "wc_CoseEatPsaToken_EncodeClaims" >/dev/null; then \ + echo "FAIL: claim-only issuer omits claim encoder"; exit 1; \ + fi + @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 "wc_CoseEatPsaToken_CreateSign1" >/dev/null; then \ + echo "FAIL: Sign1-only issuer omits Sign1 creator"; exit 1; \ + fi + @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 "wc_CoseEatPsaToken_CreateMac0" >/dev/null; then \ + echo "FAIL: Mac0-only issuer omits Mac0 creator"; exit 1; \ + fi + @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 + $(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_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 \ + -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 -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 \ + -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_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: 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 \ + -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 \ @@ -392,6 +771,11 @@ 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 @set -e; for f in $(SRC); do \ @@ -403,6 +787,16 @@ coverage: clean ./$(TEST_BIN) gcov src/*.c +eat-psa-coverage: clean + @set -e; for f in $(SRC); do \ + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) --coverage -fprofile-arcs -ftest-coverage -c $$f -o $${f%.c}.o; \ + done + rm -f $(LIB_A) + $(AR) rcs $(LIB_A) $(OBJ) + $(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 @@ -416,6 +810,16 @@ coverage-force-failure: clean ./$(TEST_BIN) gcov src/*.c +eat-psa-coverage-force-failure: clean + @set -e; for f in $(SRC); do \ + $(CC) $(CFLAGS) $(EAT_PSA_FULL_FLAGS) -DWOLFCOSE_FORCE_FAILURE --coverage -fprofile-arcs -ftest-coverage -c $$f -o $${f%.c}.o; \ + done + rm -f $(LIB_A) + $(AR) rcs $(LIB_A) $(OBJ) + $(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. @@ -424,6 +828,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) @@ -468,6 +883,27 @@ 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) $(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. @@ -549,12 +985,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) @@ -675,6 +1112,16 @@ c99-check: -DNO_HMAC -DHAVE_AES_CBC -DWOLFCOSE_ENABLE_AESMAC \ -DWOLFCOSE_NO_RECIPIENTS -DWOLFCOSE_NO_ENCRYPT \ -fsyntax-only $(SRC) + @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)" # Strict C99 gate for the LMS-gated branches and profiles. Needs an @@ -728,7 +1175,9 @@ experimental-check: # --- Cleanup --- clean: rm -f $(OBJ) $(TEST_BIN) $(TOOL_BIN) $(DEMO_BIN) $(ENC_DEMO) $(MAC_DEMO) \ - $(EXTSIGN_DEMO) $(SIGN1_DEMO) $(MLDSA_DEMO) $(MLDSAV_DEMO) $(LMS_DEMO) $(LMSV_DEMO) \ + $(EAT_PSA_TEST_BIN) $(EAT_PSA_LIMITS_TEST_BIN) \ + $(EXTSIGN_DEMO) $(SIGN1_DEMO) $(LEANV_DEMO) $(EAT_LEANV_DEMO) \ + $(EAT_DEMO) $(MLDSA_DEMO) $(MLDSAV_DEMO) $(LMS_DEMO) $(LMSV_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) \ diff --git a/README.md b/README.md index 4536203a..a89400c6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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), [COSE countersignatures (RFC 9338)](https://www.rfc-editor.org/rfc/rfc9338), and post-quantum [ML-DSA for COSE (RFC 9964)](https://www.rfc-editor.org/rfc/rfc9964) and [HSS/LMS for COSE (RFC 8778)](https://www.rfc-editor.org/rfc/rfc8778) 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), [COSE countersignatures (RFC 9338)](https://www.rfc-editor.org/rfc/rfc9338), post-quantum [ML-DSA for COSE (RFC 9964)](https://www.rfc-editor.org/rfc/rfc9964), [HSS/LMS for COSE (RFC 8778)](https://www.rfc-editor.org/rfc/rfc8778), 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 @@ -13,6 +13,9 @@ wolfCOSE is a lightweight C library implementing [CBOR (RFC 8949)](https://www.r - **Post-quantum signing**: ML-DSA (FIPS 204) at all three security levels, with RFC 9964 `COSE_Key` (AKP key type, seed-based private keys) - **Stateful hash-based signing**: HSS/LMS (RFC 8778, NIST SP 800-208) with `COSE_Key` type 5 — the CNSA 2.0 algorithm for firmware and software signing, verify-only friendly for constrained devices - **41 algorithms** across signing, encryption, MAC, and key distribution +- **PSA attestation**: optional EAT / PSA Token consumption and current-profile + issuance, with Sign1, Mac0, legacy-token compatibility, and delegated PSA/HSM + signing - **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`) - **Tiny footprint**: ES256 `COSE_Sign1` wolfCOSE (COSE + CBOR engine) **~5.1 KB** verify-only and **~6.8 KB** sign + verify. @@ -147,6 +150,12 @@ make demo | `make all` | Build `libwolfcose.a` (core library only) | | `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) | @@ -161,6 +170,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 @@ -204,6 +215,7 @@ Full documentation is available in the [Wiki](https://github.com/wolfSSL/wolfCOS - [Algorithms](https://github.com/wolfSSL/wolfCOSE/wiki/Algorithms): Complete list of 41 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 c647efd2..9f8a4e85 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -14,6 +14,7 @@ countersignature 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) @@ -132,13 +133,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. --- @@ -518,7 +523,8 @@ 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 +labels, and `bufSz` must be exactly the encoded length. COSE_Key text labels +are rejected even though COSE header maps permit text labels. See [Getting Started - Strict decoding](Getting-Started.md#strict-decoding-rfc-8949-preferred-serialization). Keys containing the optional `key_ops` label (4) return @@ -579,13 +585,13 @@ 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 - -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. 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. +The same structural checks `wc_CoseKey_Decode()` applies are applied here: +integer labels only, no duplicates, `kty` required, and no trailing bytes. +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 @@ -1153,23 +1159,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` | `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 | + +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; @@ -1216,9 +1273,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. @@ -1255,7 +1317,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 @@ -1277,15 +1340,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 CBOR head. Major types 0 and 1 fill `val` -with `isText == 0`; major type 3 consumes the text and fills -`text`/`textLen` with `isText == 1` and no copy. A byte string is fully -consumed and rejected with `WOLFCOSE_E_CBOR_TYPE`. For a tag, array, or map, -only the head is consumed. Use `wc_CBOR_Skip()` instead when the complete -unsupported item must be skipped. -A parser can therefore write 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 type 3 validates UTF-8 and, when valid, fills `text`/`textLen` with @@ -1311,12 +1370,13 @@ else { } ``` -COSE header parsers accept both integer and text labels and enforce duplicate -labels within and across the protected and unprotected buckets. Unknown, -non-critical text-labeled parameters are preserved in the encoded message and -ignored during processing. `wc_CoseKey_Decode()` accepts the integer labels -defined for COSE_Key. `wc_CBOR_DecodeLabel()` is also available for -caller-written protocol parsers such as CTAP2. +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"` is an unknown extension, not an alias for numeric label +1. Unknown non-critical extensions are skipped; an unknown entry listed in +numeric `crit` is rejected. `wc_CoseKey_Decode()` and +`wc_CoseKey_PeekInfo()` always require integer labels. **Returns:** `WOLFCOSE_SUCCESS` or error code @@ -1344,6 +1404,10 @@ caller-written protocol parsers 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) | --- @@ -1352,3 +1416,4 @@ caller-written protocol parsers 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 044c6c00..316494b8 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -355,6 +355,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/`) @@ -377,14 +391,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 | |------|------------------------|-------| @@ -398,19 +415,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` maps accept the registered integer labels. COSE header maps accept - both integer and text labels, retain unknown non-critical parameters in the - encoded message, and reject duplicates within or across header buckets. For - caller-written protocol maps, 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 header maps accept unknown text labels and reject their duplicates. + Registered COSE parameters remain numeric; `"alg"` is not an alias for + integer label 1. `COSE_Key` maps always require integer labels. + +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/Home.md b/docs/Home.md index b61bb550..d63df944 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 | | 41 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 b56ab72e..58b2681e 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -51,15 +51,79 @@ 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 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 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 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_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` | 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 +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` | @@ -68,7 +132,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` | @@ -365,7 +429,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 00000000..8205b1aa --- /dev/null +++ b/docs/PSA-EAT.md @@ -0,0 +1,321 @@ +# 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 consume or issue operation after it. + +| Define | Effect | Default | +|---|---|---| +| `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 | 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` | 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 +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 +-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 +``` + +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 +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 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, + claims_buf, sizeof(claims_buf), scratch, sizeof(scratch), token, + sizeof(token), &token_len, rng); +``` + +`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 +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/Testing.md b/docs/Testing.md index a8ffabd6..2fa958cc 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -1,6 +1,9 @@ # Testing -wolfCOSE includes comprehensive testing infrastructure for unit tests, algorithm coverage, code coverage, and failure injection testing. Code coverage is enforced by CI: every wolfCOSE source file requires 100% line coverage. 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. CI requires +100% line coverage for every wolfCOSE source file. These thresholds are +validated on every push and PR to ensure coverage does not regress. ## Running Tests @@ -16,6 +19,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 +118,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. @@ -161,9 +217,12 @@ with wolfCOSE CBOR APIs. ```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 @@ -175,9 +234,12 @@ This compiles with gcov instrumentation and runs tests, producing coverage repor ```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 6683ebea..dff8e42e 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 200c170a..bda30dcb 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 00000000..e05ae872 --- /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) && + (XMEMCMP(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)) || + (XMEMCMP(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)XMEMSET(&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)XMEMSET(claims, 0, sizeof(*claims)); + (void)XMEMSET(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)XMEMCPY(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 00000000..85eca6fe --- /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 = XSTRLEN(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 00000000..b0835488 --- /dev/null +++ b/include/wolfcose/eat_psa.h @@ -0,0 +1,334 @@ +/* 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\x23" "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. */ + /** 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. */ + 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. */ + /** 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. */ + 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. + */ +#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. + * + * 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 +#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 +#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 548a5094..17e4b67e 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 @@ -178,30 +183,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(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 -/* 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 @@ -359,7 +375,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 @@ -658,6 +675,140 @@ 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 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 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 + */ +#if defined(WOLFCOSE_ENABLE_EAT_PSA) + #define WOLFCOSE_EAT_PSA +#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_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" + #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_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_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_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_EAT_PSA_SIGN1) || defined(WOLFCOSE_EAT_PSA_MAC0) + #define WOLFCOSE_EAT_PSA_VERIFY +#endif + +/* RFC 9052 permits integer and text-string labels in COSE header maps. */ +#if defined(WOLFCOSE_CBOR_DECODE) + #define WOLFCOSE_COSE_TEXT_LABELS +#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_VERIFY) && \ + !defined(WOLFCOSE_EAT_PSA_ISSUE) + #error "WOLFCOSE_ENABLE_EAT_PSA needs a verifier and/or issuer operation" +#endif + +#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_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 + * 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 @@ -716,6 +867,27 @@ extern "C" { #endif #endif +#if defined(WOLFCOSE_EAT_PSA) + /* Maximum software-component maps accepted in one PSA token. This bounds + * 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 + * 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" @@ -729,6 +901,19 @@ 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 +#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 + #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 f77435c2..5aa185c5 100644 --- a/include/wolfcose/wolfcose.h +++ b/include/wolfcose/wolfcose.h @@ -174,8 +174,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. */ @@ -474,9 +475,9 @@ typedef struct WOLFCOSE_COUNTERSIGNATURE0 { /** * \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. @@ -529,7 +530,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); @@ -583,27 +585,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. + * 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. - * \param buf Input buffer. - * \param bufSz Input buffer size. - * \return WOLFCOSE_SUCCESS or WOLFCOSE_E_INVALID_ARG. + * \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) @@ -624,15 +632,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); @@ -654,7 +663,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. @@ -664,11 +673,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); @@ -701,9 +711,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); @@ -719,8 +731,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); @@ -728,9 +741,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 { @@ -751,7 +764,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); @@ -1052,11 +1066,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 labels, kty required, no trailing bytes), so a buffer + * that peeks successfully will not be rejected by the decoder for those + * reasons. COSE_Key labels must be integers; text labels are rejected even + * though COSE header maps permit them. 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_house_style.sh b/scripts/check_house_style.sh index a6acc74b..0bcb53bc 100755 --- a/scripts/check_house_style.sh +++ b/scripts/check_house_style.sh @@ -9,6 +9,7 @@ status=0 PATHS='*.c *.h :(exclude)lightpanda/' +LIB_PATHS='src include' tab=$(printf '\t') report() { @@ -38,6 +39,15 @@ git grep -nE "$tab" -- $PATHS | report "tabs are banned in C sources; use spaces # No trailing whitespace. git grep -nE ' +$' -- $PATHS | report "trailing whitespace" +# Library code uses wolfSSL portability wrappers for memory and string calls. +git grep -nE \ + -e '(^|[^[:alnum:]_])(memcpy|memset|memmove|memcmp)[[:space:]]*\(' \ + -e '(^|[^[:alnum:]_])(malloc|calloc|realloc|free)[[:space:]]*\(' \ + -e '(^|[^[:alnum:]_])(strcpy|strncpy|strcat|strncat)[[:space:]]*\(' \ + -e '(^|[^[:alnum:]_])(strlen|strcmp|strncmp)[[:space:]]*\(' \ + -- $LIB_PATHS | \ + report "raw memory/string calls are banned in library code; use wolfSSL X* wrappers" + # Examples follow the documented explicit-precedence style: each operand in a # logical-AND condition is parenthesized independently. git grep -nE '^[[:space:]]*if \([^()].*&&' -- $PATHS | \ diff --git a/scripts/cmdline-test.sh b/scripts/cmdline-test.sh index ac69889a..4d23d6cd 100755 --- a/scripts/cmdline-test.sh +++ b/scripts/cmdline-test.sh @@ -190,6 +190,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/misra-deviations.json b/scripts/misra-deviations.json index 4b2f66f2..fdbc8a72 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/scripts/test_pkg_config.sh b/scripts/test_pkg_config.sh index 99623e97..25b98792 100644 --- a/scripts/test_pkg_config.sh +++ b/scripts/test_pkg_config.sh @@ -135,6 +135,7 @@ check_config_rebuild() ( cp "$ROOT_DIR"/src/*.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" @@ -160,15 +161,29 @@ 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 ' ') src_count=$(ls "$config_fixture/src"/*.c | wc -l | tr -d ' ') - expected_count=$((2 * src_count)) + expected_count=$((2 * src_count + 1)) if [ "$compiler_count" -ne "$expected_count" ]; then - printf 'FAIL: expected configuration change to rebuild both objects\n' >&2 + printf 'FAIL: expected %s config/header compiles, got %s\n' \ + "$expected_count" "$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_cbor.c b/src/wolfcose_cbor.c index 4a4575e5..a38fc527 100644 --- a/src/wolfcose_cbor.c +++ b/src/wolfcose_cbor.c @@ -29,8 +29,127 @@ #endif #include "wolfcose_internal.h" -#include /* memcpy */ +#include /* XMEMCPY */ +#if defined(WOLFCOSE_CBOR_DECODE) +static int wolfCose_CBOR_AllowsNonpreferred(uint32_t decodeFlags) +{ +#if defined(WOLFCOSE_EAT_PSA_VERIFY) + return ((decodeFlags & WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED) != 0u) ? + 1 : 0; +#else + (void)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 + * 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]; + + 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; +} +#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. */ @@ -39,7 +158,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 * @@ -56,7 +177,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 { @@ -125,7 +246,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 * @@ -136,13 +259,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 +343,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,12 +368,18 @@ 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; + } } } } } return ret; } +#endif /* WOLFCOSE_CBOR_DECODE */ /* ----- * Public Encode API @@ -321,7 +451,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 +489,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 +524,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 +546,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) { @@ -431,12 +574,19 @@ int wc_CBOR_EncodeDouble(WOLFCOSE_CBOR_CTX* ctx, double val) #if defined(WOLFCOSE_CBOR_DECODE) +int wolfCose_CBOR_DecodeHead(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_CBOR_ITEM* item) +{ + return wolfCose_CBOR_DecodeHead_ex(ctx, item, 0u); +} + 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 +595,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 +608,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 +623,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 +650,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 +667,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 +681,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_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(ctx, WOLFCOSE_CBOR_BSTR, data, dataLen); + 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 +718,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; @@ -567,17 +741,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; @@ -586,7 +775,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; @@ -599,11 +788,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; @@ -619,7 +813,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; } @@ -681,6 +875,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) { @@ -693,7 +892,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]; @@ -703,74 +902,8 @@ int wc_CBOR_SkipItem(WOLFCOSE_CBOR_CTX* ctx, const uint8_t** data, return ret; } -static int wolfCose_CBOR_IsUtf8Continuation(uint8_t value) -{ - return ((value >= 0x80u) && (value <= 0xBFu)) ? 1 : 0; -} - -static int wolfCose_CBOR_IsValidUtf8(const uint8_t* text, size_t textLen) -{ - int valid = 1; - size_t i = 0u; - - if ((text == NULL) && (textLen > 0u)) { - valid = 0; - } - - while ((valid != 0) && (i < textLen)) { - uint8_t first = text[i]; - size_t remaining = textLen - i; - - if (first <= 0x7Fu) { - i++; - } - else if ((first >= 0xC2u) && (first <= 0xDFu)) { - if ((remaining < 2u) || - (wolfCose_CBOR_IsUtf8Continuation(text[i + 1u]) == 0)) { - valid = 0; - } - else { - i += 2u; - } - } - else if ((first >= 0xE0u) && (first <= 0xEFu)) { - if ((remaining < 3u) || - (wolfCose_CBOR_IsUtf8Continuation(text[i + 1u]) == 0) || - (wolfCose_CBOR_IsUtf8Continuation(text[i + 2u]) == 0)) { - valid = 0; - } - else if (((first == 0xE0u) && (text[i + 1u] < 0xA0u)) || - ((first == 0xEDu) && (text[i + 1u] > 0x9Fu))) { - valid = 0; - } - else { - i += 3u; - } - } - else if ((first >= 0xF0u) && (first <= 0xF4u)) { - if ((remaining < 4u) || - (wolfCose_CBOR_IsUtf8Continuation(text[i + 1u]) == 0) || - (wolfCose_CBOR_IsUtf8Continuation(text[i + 2u]) == 0) || - (wolfCose_CBOR_IsUtf8Continuation(text[i + 3u]) == 0)) { - valid = 0; - } - else if (((first == 0xF0u) && (text[i + 1u] < 0x90u)) || - ((first == 0xF4u) && (text[i + 1u] > 0x8Fu))) { - valid = 0; - } - else { - i += 4u; - } - } - else { - valid = 0; - } - } - - return valid; -} - -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; @@ -779,7 +912,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; @@ -804,14 +937,10 @@ int wc_CBOR_DecodeLabel(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_CBOR_LABEL* label) } } else if (item.majorType == WOLFCOSE_CBOR_TSTR) { - if (wolfCose_CBOR_IsValidUtf8(item.data, item.dataLen) == 0) { - ret = WOLFCOSE_E_CBOR_MALFORMED; - } - else { - label->text = item.data; - label->textLen = item.dataLen; - label->isText = 1u; - } + /* DecodeHead has already validated the text string. */ + label->text = item.data; + label->textLen = item.dataLen; + label->isText = 1u; } else { /* RFC 9052: label = int / tstr, nothing else. */ @@ -822,6 +951,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 00000000..520bc772 --- /dev/null +++ b/src/wolfcose_eat_psa.c @@ -0,0 +1,1699 @@ +/* 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" +#include /* XMEMCPY */ + +/* 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 +/* 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_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) +{ + int overlap = 0; + + if ((a != NULL) && (b != NULL) && (aSz != 0u) && (bSz != 0u)) { + /* 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 { + /* The nonempty buffer ranges are disjoint. */ + } + } + + 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) && ( + (wolfCose_EatPsaBuffersOverlap( + (const uint8_t*)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))) { + overlap = 1; + } + + 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; + + if ( + (wolfCose_EatPsaBuffersOverlap( + (const uint8_t*)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)) { + overlap = 1; + } + for (i = 0u; (overlap == 0) && (i < claims->componentCount); i++) { + overlap = wolfCose_EatPsaComponentOverlapsBuffer( + &claims->components[i], buffer, bufferSz); + } + } + + 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, + 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 +#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) && + (((span->data == NULL) && (span->len == 0u)) || + (span->data != NULL))) { + ret = 1; + } + + 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; + } + else { + /* The character is a valid decimal digit. */ + } + } + } + + return ret; +} + +#if defined(WOLFCOSE_EAT_PSA_SIGN1) || \ + defined(WOLFCOSE_EAT_PSA_SIGN1_ISSUE) +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 || WOLFCOSE_EAT_PSA_SIGN1_ISSUE */ + +#if defined(WOLFCOSE_EAT_PSA_MAC0) || \ + defined(WOLFCOSE_EAT_PSA_MAC0_ISSUE) +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 || WOLFCOSE_EAT_PSA_MAC0_ISSUE */ + +#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 */ + +#if defined(WOLFCOSE_EAT_PSA_VERIFY) + +typedef struct WOLFCOSE_EAT_PSA_LABEL { + uint64_t val; + const uint8_t* text; + size_t textLen; + uint8_t majorType; +} WOLFCOSE_EAT_PSA_LABEL; + +static int wolfCose_EatPsaDecodeLabel(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_EAT_PSA_LABEL* label) +{ + int ret; + WOLFCOSE_CBOR_ITEM item; + + if ((ctx == NULL) || (label == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = wolfCose_CBOR_DecodeHead_ex(ctx, &item, + WOLFCOSE_EAT_PSA_DECODE_FLAGS); + } + if (ret == WOLFCOSE_SUCCESS) { + (void)XMEMSET(label, 0, sizeof(*label)); + label->majorType = item.majorType; + if ((item.majorType == WOLFCOSE_CBOR_UINT) || + (item.majorType == WOLFCOSE_CBOR_NEGINT)) { + label->val = item.val; + } + else if (item.majorType == WOLFCOSE_CBOR_TSTR) { + label->text = item.data; + label->textLen = item.dataLen; + } + else { + ret = WOLFCOSE_E_CBOR_TYPE; + } + } + + return ret; +} + +static int wolfCose_EatPsaLabelToInt(const WOLFCOSE_EAT_PSA_LABEL* label, + int64_t* val) +{ + int ret = 0; + + if ((label != NULL) && (val != NULL) && + (label->val <= (uint64_t)INT64_MAX)) { + if (label->majorType == WOLFCOSE_CBOR_UINT) { + *val = (int64_t)label->val; + ret = 1; + } + else if (label->majorType == WOLFCOSE_CBOR_NEGINT) { + *val = -1 - (int64_t)label->val; + ret = 1; + } + else { + /* The label is a text string. */ + } + } + + return ret; +} + +static int wolfCose_EatPsaLabelsEqual(const WOLFCOSE_EAT_PSA_LABEL* first, + const WOLFCOSE_EAT_PSA_LABEL* second) +{ + int ret = 0; + + if ((first != NULL) && (second != NULL) && + (first->majorType == second->majorType)) { + if (first->majorType == WOLFCOSE_CBOR_TSTR) { + if ((first->textLen == second->textLen) && + ((first->textLen == 0u) || + (wolfCose_EatPsaConstantCompare(first->text, second->text, + first->textLen) == 0))) { + ret = 1; + } + } + 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_EAT_PSA_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_EAT_PSA_LABEL prior; + + ret = wolfCose_EatPsaDecodeLabel(&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_EAT_PSA_LABEL label; + size_t labelStart = ctx->idx - mapStart; + int64_t labelVal = 0; + int labelIsInt = 0; + int duplicate = 0; + + ret = wolfCose_EatPsaDecodeLabel(ctx, &label); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EatPsaMapHasPriorLabel(&ctx->cbuf[mapStart], + ctx->bufSz - mapStart, labelStart, &label, &duplicate); + labelIsInt = wolfCose_EatPsaLabelToInt(&label, &labelVal); + } + if ((ret == WOLFCOSE_SUCCESS) && (duplicate != 0)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if ((ret == WOLFCOSE_SUCCESS) && (labelIsInt == 0)) { + ret = WOLFCOSE_EAT_PSA_SKIP(ctx); + } + else if ((ret == WOLFCOSE_SUCCESS) && + (labelVal == 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) && + (labelVal == 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) && + (labelVal == 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) && + (labelVal == 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) && + (labelVal == 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); + } + else { + /* Preserve the error produced while decoding the component. */ + } + } + 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; + size_t startIdx = 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 { + startIdx = ctx->idx; + start = &ctx->cbuf[startIdx]; + 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 = ctx->idx - startIdx; + 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) + static const uint8_t legacyProfile[] = + WOLFCOSE_EAT_PSA_PROFILE_LEGACY; + + expected = legacyProfile; + expectedLen = sizeof(legacyProfile) - 1u; +#else + ret = WOLFCOSE_E_EAT_PSA_PROFILE; +#endif + } + else { + ret = WOLFCOSE_E_EAT_PSA_PROFILE; + } + if (ret == WOLFCOSE_SUCCESS) { + if ((textLen != expectedLen) || + (wolfCose_EatPsaConstantCompare(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_EAT_PSA_LABEL label; + size_t labelStart = ctx.idx - mapStart; + int64_t labelVal = 0; + int labelIsInt = 0; + uint32_t bit = 0u; + int type = 0; + int duplicate = 0; + int enabled = 0; + uint8_t legacy = 0u; + + ret = wolfCose_EatPsaDecodeLabel(&ctx, &label); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EatPsaMapHasPriorLabel(&ctx.cbuf[mapStart], + ctx.bufSz - mapStart, labelStart, &label, &duplicate); + labelIsInt = wolfCose_EatPsaLabelToInt(&label, &labelVal); + } + if ((ret == WOLFCOSE_SUCCESS) && (duplicate != 0)) { + ret = WOLFCOSE_E_EAT_PSA_CLAIM; + } + if ((ret == WOLFCOSE_SUCCESS) && (labelIsInt == 0)) { + ret = WOLFCOSE_EAT_PSA_SKIP(&ctx); + } + else if (ret == WOLFCOSE_SUCCESS) { + wolfCose_EatPsaClaimType(labelVal, &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); + } + } + } + } + else { + /* Preserve the error produced while decoding the map label. */ + } + } + 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; + } + else { + /* All required current-profile claims were decoded. */ + } + } +#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; + } + else { + /* All required legacy-profile claims were decoded. */ + } + } +#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) || + (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; +} + +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 }; + uint8_t expectedNonceCopy[64] = { 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)XMEMCPY(expectedNonceCopy, expectedNonce, expectedNonceLen); + (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, expectedNonceCopy, + expectedNonceLen); + } + if ((ret != WOLFCOSE_SUCCESS) && (token != NULL)) { + (void)XMEMSET(token, 0, sizeof(*token)); + } + wolfCose_ForceZero(expectedNonceCopy, sizeof(expectedNonceCopy)); + + 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; + uint8_t expectedNonceCopy[64] = { 0 }; + + (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)XMEMCPY(expectedNonceCopy, expectedNonce, expectedNonceLen); + (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, expectedNonceCopy, + expectedNonceLen, scratch, scratchSz, token); + } + if ((ret != WOLFCOSE_SUCCESS) && (token != NULL)) { + (void)XMEMSET(token, 0, sizeof(*token)); + } + wc_CoseKey_Free(&key); + wolfCose_ForceZero(expectedNonceCopy, sizeof(expectedNonceCopy)); + + 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 */ + +#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, + 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_hdr.c b/src/wolfcose_hdr.c index 0647060b..58eff324 100644 --- a/src/wolfcose_hdr.c +++ b/src/wolfcose_hdr.c @@ -48,6 +48,7 @@ /* ----- 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. */ @@ -88,7 +89,9 @@ void wolfCose_HdrStateInit(WOLFCOSE_HDR_STATE* state) if (state != NULL) { state->labelBits = 0u; state->extraIntegerCount = 0u; +#if defined(WOLFCOSE_COSE_TEXT_LABELS) state->extraTextCount = 0u; +#endif } } @@ -158,6 +161,7 @@ int wolfCose_HdrStateCheckAndAdd(WOLFCOSE_HDR_STATE* state, return ret; } +#if defined(WOLFCOSE_COSE_TEXT_LABELS) static int wolfCose_TextLabelEquals(const uint8_t* encoded, const WOLFCOSE_CBOR_LABEL* label) { @@ -208,6 +212,7 @@ static int wolfCose_TextLabelEquals(const uint8_t* encoded, return equal; } +#endif int wolfCose_HdrStateContainsLabel(const WOLFCOSE_HDR_STATE* state, const WOLFCOSE_CBOR_LABEL* label) @@ -219,6 +224,7 @@ int wolfCose_HdrStateContainsLabel(const WOLFCOSE_HDR_STATE* state, found = wolfCose_HdrStateContains(state, label->val); } else { +#if defined(WOLFCOSE_COSE_TEXT_LABELS) size_t i; for (i = 0u; i < state->extraTextCount; i++) { @@ -228,6 +234,7 @@ int wolfCose_HdrStateContainsLabel(const WOLFCOSE_HDR_STATE* state, break; } } +#endif } } @@ -245,6 +252,7 @@ int wolfCose_HdrStateAddLabel(WOLFCOSE_HDR_STATE* state, else if (label->isText == 0u) { ret = wolfCose_HdrStateAdd(state, label->val); } +#if defined(WOLFCOSE_COSE_TEXT_LABELS) else if (encodedLabel == NULL) { ret = WOLFCOSE_E_INVALID_ARG; } @@ -256,6 +264,12 @@ int wolfCose_HdrStateAddLabel(WOLFCOSE_HDR_STATE* state, state->extraTextLabels[state->extraTextCount] = encodedLabel; state->extraTextCount++; } +#else + else { + (void)encodedLabel; + ret = WOLFCOSE_E_CBOR_MALFORMED; + } +#endif return ret; } @@ -299,6 +313,9 @@ int wolfCose_SkipIfTstrLabel(const WOLFCOSE_CBOR_CTX* ctx, int* skipped) } #endif +#endif /* WOLFCOSE_CBOR_DECODE */ + +#if defined(WOLFCOSE_CBOR_ENCODE) int wolfCose_EncodeProtectedHdr(int32_t alg, uint8_t* buf, size_t bufSz, size_t* outLen) { @@ -327,10 +344,13 @@ int wolfCose_EncodeProtectedHdr(int32_t alg, uint8_t* buf, size_t bufSz, } return ret; } +#endif /* WOLFCOSE_CBOR_ENCODE */ -int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, - WOLFCOSE_HDR* hdr, - WOLFCOSE_HDR_STATE* hdrState) +#if defined(WOLFCOSE_CBOR_DECODE) +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; @@ -344,6 +364,9 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, 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)) { ret = WOLFCOSE_E_INVALID_ARG; } @@ -356,11 +379,11 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, uint8_t contentTypeUnderstood = 0u; 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; @@ -373,7 +396,7 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, if ((ctx.cbuf != NULL) && (ctx.idx < ctx.bufSz)) { encodedLabel = &ctx.cbuf[ctx.idx]; } - ret = wc_CBOR_DecodeLabel(&ctx, &label); + ret = wolfCose_CBOR_DecodeLabel_ex(&ctx, &label, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_HdrStateCheckAndAddLabel(hdrState, &label, encodedLabel); @@ -383,10 +406,11 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_ALG) != 0)) { 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; @@ -403,7 +427,8 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, 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))) { @@ -415,7 +440,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. */ @@ -439,10 +465,11 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, WOLFCOSE_HDR_CONTENT_TYPE) != 0)) { 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; @@ -460,7 +487,8 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, * 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; @@ -470,7 +498,8 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_IV) != 0)) { 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; @@ -481,16 +510,24 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, WOLFCOSE_HDR_PARTIAL_IV) != 0)) { 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_VERIFY) + else if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_X5CHAIN) != 0)) { + 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); } } } @@ -533,8 +570,15 @@ 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; @@ -542,7 +586,8 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, const uint8_t* bstrData; size_t bstrLen; - 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 { @@ -550,7 +595,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; @@ -563,7 +608,7 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, if ((ctx->cbuf != NULL) && (ctx->idx < ctx->bufSz)) { encodedLabel = &ctx->cbuf[ctx->idx]; } - ret = wc_CBOR_DecodeLabel(ctx, &label); + ret = wolfCose_CBOR_DecodeLabel_ex(ctx, &label, decodeFlags); if (ret == WOLFCOSE_SUCCESS) { /* crit MUST live in the protected bucket. */ if (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_CRIT) != 0) { @@ -583,7 +628,8 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, if ((ret == WOLFCOSE_SUCCESS) && (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_KID) != 0)) { - ret = wc_CBOR_DecodeBstr(ctx, &bstrData, &bstrLen); + ret = wolfCose_CBOR_DecodeBstr_ex(ctx, &bstrData, &bstrLen, + decodeFlags); if (ret == WOLFCOSE_SUCCESS) { hdr->kid = bstrData; hdr->kidLen = bstrLen; @@ -591,7 +637,8 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } else if ((ret == WOLFCOSE_SUCCESS) && (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_IV) != 0)) { - ret = wc_CBOR_DecodeBstr(ctx, &bstrData, &bstrLen); + ret = wolfCose_CBOR_DecodeBstr_ex(ctx, &bstrData, &bstrLen, + decodeFlags); if (ret == WOLFCOSE_SUCCESS) { hdr->iv = bstrData; hdr->ivLen = bstrLen; @@ -600,7 +647,8 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, else if ((ret == WOLFCOSE_SUCCESS) && (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_PARTIAL_IV) != 0)) { - ret = wc_CBOR_DecodeBstr(ctx, &bstrData, &bstrLen); + ret = wolfCose_CBOR_DecodeBstr_ex(ctx, &bstrData, &bstrLen, + decodeFlags); if (ret == WOLFCOSE_SUCCESS) { hdr->partialIv = bstrData; hdr->partialIvLen = bstrLen; @@ -610,11 +658,12 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_ALG) != 0)) { 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; @@ -630,11 +679,12 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, 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; @@ -644,9 +694,16 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } } } +#if defined(WOLFCOSE_EAT_PSA_VERIFY) + else if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_X5CHAIN) != 0)) { + 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); } } } @@ -667,6 +724,13 @@ 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); +} +#endif /* WOLFCOSE_CBOR_DECODE */ + #if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_ENCRYPT_DECRYPT) || \ defined(WOLFCOSE_MAC_VERIFY) || defined(WOLFCOSE_COUNTERSIGN) /* Decode only the algorithm from an unselected header map. Other labels and diff --git a/src/wolfcose_internal.h b/src/wolfcose_internal.h index dbc7e95b..af522e86 100644 --- a/src/wolfcose_internal.h +++ b/src/wolfcose_internal.h @@ -125,19 +125,62 @@ 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_VERIFY) + #define WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) \ + ((((flags) & ~WOLFCOSE_COSE_DECODE_ALLOW_NONPREFERRED) == 0u) ? 1 : 0) +#else + #define WOLFCOSE_COSE_DECODE_FLAGS_VALID(flags) \ + (((flags) == 0u) ? 1 : 0) +#endif + +#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. Sets item fields. + * \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_ex(WOLFCOSE_CBOR_CTX* ctx, + WOLFCOSE_CBOR_ITEM* item, uint32_t decodeFlags); WOLFCOSE_LOCAL int wolfCose_CBOR_DecodeHead(WOLFCOSE_CBOR_CTX* ctx, - WOLFCOSE_CBOR_ITEM* item); + WOLFCOSE_CBOR_ITEM* item); +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); +#endif /* ----- RFC 9052 context-string byte arrays (see wolfcose.c) ----- */ WOLFCOSE_LOCAL extern const uint8_t WOLFCOSE_CTX_SIGNATURE1[10]; @@ -147,18 +190,25 @@ 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) #define WOLFCOSE_MAX_HEADER_LABELS WOLFCOSE_MAX_MAP_ITEMS typedef struct WOLFCOSE_HDR_STATE { uint32_t labelBits; int64_t extraIntegerLabels[WOLFCOSE_MAX_HEADER_LABELS]; +#if defined(WOLFCOSE_COSE_TEXT_LABELS) const uint8_t* extraTextLabels[WOLFCOSE_MAX_HEADER_LABELS]; +#endif size_t extraIntegerCount; +#if defined(WOLFCOSE_COSE_TEXT_LABELS) size_t extraTextCount; +#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. @@ -168,7 +218,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. @@ -180,6 +232,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. @@ -190,6 +248,31 @@ 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 +#endif /* WOLFCOSE_CBOR_DECODE */ /** * \brief Map COSE algorithm ID to wolfCrypt hash type. @@ -436,6 +519,7 @@ WOLFCOSE_LOCAL int wolfCose_HashToMgf(enum wc_HashType hashType, WOLFCOSE_MGF_ID* mgf); #endif +#if defined(WOLFCOSE_CBOR_DECODE) /* InInt32Range -- defined in wolfcose_hdr.c */ WOLFCOSE_LOCAL int wolfCose_InInt32Range(int64_t val); @@ -460,6 +544,7 @@ WOLFCOSE_LOCAL int wolfCose_HdrStateContainsLabel(const WOLFCOSE_HDR_STATE* stat /* HdrStateAddLabel -- defined in wolfcose_hdr.c */ WOLFCOSE_LOCAL int wolfCose_HdrStateAddLabel(WOLFCOSE_HDR_STATE* state, const WOLFCOSE_CBOR_LABEL* label, const uint8_t* encodedLabel); +#endif /* WOLFCOSE_CBOR_DECODE */ #if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_ENCRYPT_DECRYPT) || \ diff --git a/src/wolfcose_mac0.c b/src/wolfcose_mac0.c index 2c7032d7..a34ae8e0 100644 --- a/src/wolfcose_mac0.c +++ b/src/wolfcose_mac0.c @@ -545,13 +545,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 @@ -581,6 +582,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) || @@ -598,14 +603,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; } @@ -614,7 +617,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; } @@ -622,13 +625,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); @@ -637,7 +641,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) */ @@ -659,7 +663,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; @@ -669,7 +674,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. */ @@ -804,6 +809,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 */ diff --git a/src/wolfcose_sign1.c b/src/wolfcose_sign1.c index 3e078c99..bd774aca 100644 --- a/src/wolfcose_sign1.c +++ b/src/wolfcose_sign1.c @@ -1055,13 +1055,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; @@ -1085,6 +1086,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) || @@ -1098,14 +1103,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; } @@ -1114,7 +1117,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; } @@ -1122,13 +1125,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); @@ -1137,7 +1141,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) */ @@ -1159,7 +1163,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; @@ -1169,7 +1174,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. */ @@ -1477,6 +1482,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 */ diff --git a/tests/config/eat_psa_config/config.h b/tests/config/eat_psa_config/config.h new file mode 100644 index 00000000..9b436d79 --- /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 00000000..bf2acd09 --- /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 00000000..e2063ecb --- /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 00000000..9ff1ac50 --- /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 61e4c9b9..fceb02b6 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 faec412f..6096c5f5 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/misra_consumer.c b/tests/misra_consumer.c index bf83383c..451e8e62 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)) @@ -124,6 +125,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; } @@ -149,7 +174,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(); @@ -158,6 +189,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 @@ -168,5 +203,6 @@ int main(void) used += 1u; #endif - return (used == 0u) ? 1 : 0; + (void)used; + return 0; } diff --git a/tests/test_cbor.c b/tests/test_cbor.c index 893ba5d9..cbaf8351 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"; @@ -1294,6 +1544,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, @@ -1323,6 +1585,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 34452e82..8dec7533 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -304,7 +304,7 @@ static void test_cose_countersign_ecdsa_curves(void) } TEST_ASSERT(ret == 0, "ES512 countersign key"); if (ret == 0) { - ret = test_cose_countersign_roundtrip(&signKey, &signKey, + (void)test_cose_countersign_roundtrip(&signKey, &signKey, WOLFCOSE_ALG_ES512, &rng, "ES512"); wc_CoseKey_Free(&signKey); } @@ -5822,7 +5822,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), @@ -6492,7 +6491,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; @@ -6500,7 +6499,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); @@ -6533,7 +6532,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)) { @@ -6541,7 +6540,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; @@ -6577,7 +6576,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; @@ -6747,7 +6746,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); @@ -6791,7 +6790,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; @@ -6805,7 +6804,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); @@ -6845,8 +6844,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]; @@ -6879,7 +6878,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); @@ -7276,7 +7275,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); @@ -7300,7 +7299,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); @@ -7338,7 +7337,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); @@ -7362,7 +7361,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); @@ -7406,7 +7405,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"); @@ -7423,7 +7422,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); @@ -7452,7 +7451,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; @@ -7467,7 +7466,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); @@ -7531,7 +7530,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) @@ -7580,7 +7579,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); @@ -7760,7 +7759,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; @@ -7774,7 +7773,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); @@ -7802,7 +7801,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; @@ -7810,7 +7809,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) */ @@ -8732,7 +8731,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; @@ -8885,7 +8884,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); @@ -8907,7 +8906,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) == @@ -8947,13 +8946,70 @@ 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); TEST_ASSERT(wc_CoseKey_PeekInfo(buf, enc.idx, &info) == - WOLFCOSE_E_CBOR_MALFORMED, "peek text label rejected"); + WOLFCOSE_E_CBOR_MALFORMED, + "peek rejects text label in key map"); + + /* Text labels are not accepted in COSE_Key maps. */ + (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_E_CBOR_MALFORMED, + "peek rejects 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_E_CBOR_MALFORMED, + "peek rejects complete key with text extension"); + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "decode rejects 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); + } } } @@ -8963,7 +9019,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; @@ -9767,7 +9823,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; @@ -10742,7 +10798,7 @@ static int mutate_first_recipient_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; @@ -11579,7 +11635,52 @@ 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) +{ + 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; +} +#endif /* WOLFCOSE_COSE_TEXT_LABELS */ + /** * Test ECDH-ES (Ephemeral-Static) encryption and decryption. * - Encrypt with recipient's EC public key @@ -11780,6 +11881,122 @@ 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; + 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_E_CBOR_MALFORMED, + "ECDH ephemeral key rejects 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); +} +#endif /* WOLFCOSE_COSE_TEXT_LABELS */ + static void test_cose_encrypt_ecdh_es_malformed_ephemeral_point(void) { WOLFCOSE_KEY recipientKey; @@ -13946,7 +14163,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; @@ -16813,7 +17030,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; @@ -16824,50 +17041,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"); @@ -16876,8 +17099,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); @@ -16897,8 +17120,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"); @@ -16910,8 +17133,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 */ @@ -16922,8 +17145,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); @@ -16935,8 +17158,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)); @@ -17002,7 +17225,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; @@ -17014,7 +17237,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; @@ -17026,7 +17249,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; @@ -17038,7 +17261,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; @@ -17050,7 +17273,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; @@ -17062,7 +17285,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; @@ -17076,7 +17299,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; @@ -17093,7 +17316,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; @@ -17105,7 +17328,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; @@ -17119,7 +17342,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; @@ -17131,7 +17354,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; @@ -17143,7 +17366,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; @@ -17159,7 +17382,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; @@ -17172,7 +17395,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; @@ -17186,19 +17409,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; @@ -17218,7 +17442,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"); @@ -17276,8 +17501,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); @@ -17297,8 +17522,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"); @@ -17306,7 +17531,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; @@ -17318,8 +17543,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; @@ -17351,10 +17576,16 @@ static void test_cose_protected_hdr_empty_map(void) &hdrState); TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "DecodeProtectedHdr accepts serialized empty map"); +#if defined(WOLFCOSE_COSE_TEXT_LABELS) TEST_ASSERT((hdrState.labelBits == 0u) && (hdrState.extraIntegerCount == 0u) && (hdrState.extraTextCount == 0u), "DecodeProtectedHdr empty map state"); +#else + TEST_ASSERT((hdrState.labelBits == 0u) && + (hdrState.extraIntegerCount == 0u), + "DecodeProtectedHdr empty map state"); +#endif } static void test_cose_protected_hdr_trailing(void) @@ -17836,7 +18067,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; @@ -18252,7 +18483,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, @@ -18264,6 +18494,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; @@ -18345,25 +18607,46 @@ static void test_cose_protected_hdr_tstr_label(void) int ret; WOLFCOSE_HDR hdr; 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) WOLFCOSE_CBOR_CTX enc; uint8_t labelData[256]; uint8_t dupBoundary[600]; static const size_t boundaryLen[] = {0u, 23u, 24u, 255u, 256u}; size_t i; - /* {1: -7, "x": 0} : alg ES256, plus an unknown tstr label */ - uint8_t tstrLabel[] = {0xA2u, 0x01u, 0x26u, 0x61u, 'x', 0x00u}; /* {"x": 0, "x": 1} */ uint8_t dupTstrLabel[] = { 0xA2u, 0x61u, 'x', 0x00u, 0x61u, 'x', 0x01u }; + /* Same map, with tstr length one encoded non-preferred. */ + uint8_t nonPreferred[] = { + 0xA2u, 0x01u, 0x26u, 0x78u, 0x01u, 'x', 0x00u + }; +#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 preserves unknown tstr label"); + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(nonPreferred, sizeof(nonPreferred), + &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "strict protected header rejects non-preferred tstr label"); +#if defined(WOLFCOSE_EAT_PSA_VERIFY) + 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(dupTstrLabel, sizeof(dupTstrLabel), &hdr, &hdrState); @@ -18396,6 +18679,10 @@ static void test_cose_protected_hdr_tstr_label(void) TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "reject duplicate tstr label at CBOR length boundary"); } +#else + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "DecodeProtectedHdr rejects disabled tstr labels"); +#endif } static void test_cose_protected_hdr_dup_label(void) @@ -18445,6 +18732,10 @@ static void test_cose_protected_hdr_crit(void) uint8_t critTstrContentType[] = { 0xA2u, 0x02u, 0x81u, 0x03u, 0x03u, 0x61u, 'x' }; + /* {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)); @@ -18476,6 +18767,20 @@ static void test_cose_protected_hdr_crit(void) sizeof(critTstrContentType), &hdr, &hdrState); TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, "DecodeProtectedHdr rejects critical tstr content-type"); + + 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_VERIFY) + 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) @@ -18483,7 +18788,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}; @@ -18507,7 +18812,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}; @@ -18527,7 +18832,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) */ @@ -19792,7 +20097,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"} */ @@ -19816,12 +20121,13 @@ static void test_cose_decode_tstr_alg_values(void) "DecodeUnprotectedHdr tolerates tstr alg"); } +#if defined(WOLFCOSE_COSE_TEXT_LABELS) static void test_cose_decode_unprotected_tstr_label(void) { 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}; /* {"x": 0} */ @@ -19853,12 +20159,13 @@ static void test_cose_decode_unprotected_tstr_label(void) TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "DecodeUnprotectedHdr rejects cross-bucket tstr duplicate"); } +#endif /* WOLFCOSE_COSE_TEXT_LABELS */ 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 @@ -19867,7 +20174,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); @@ -21119,7 +21426,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; @@ -24991,7 +25298,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; @@ -25080,7 +25387,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; @@ -25849,10 +26156,12 @@ static void test_cose_countersignatures(void) 0xD8u, 0x62u, 0x84u, 0x40u, 0xA0u, 0x42u, 0x01u, 0x02u, 0x81u, 0x83u, 0x40u, 0xA0u, 0x41u, 0x03u }; +#if defined(WOLFCOSE_COSE_TEXT_LABELS) static const uint8_t textHeaderTarget[] = { 0xD2u, 0x84u, 0x44u, 0xA1u, 0x61u, 'x', 0x00u, 0xA1u, 0x61u, 'y', 0x01u, 0x41u, 0x01u, 0x41u, 0x02u }; +#endif static const uint8_t duplicateHeaderTarget[] = { 0xD2u, 0x84u, 0x40u, 0xA2u, 0x01u, 0x26u, 0x01u, 0x26u, 0x41u, 0x01u, 0x41u, 0x02u @@ -25931,7 +26240,9 @@ static void test_cose_countersignatures(void) { encryptTarget, sizeof(encryptTarget) }, { macTarget, sizeof(macTarget) }, { signTarget, sizeof(signTarget) }, +#if defined(WOLFCOSE_COSE_TEXT_LABELS) { textHeaderTarget, sizeof(textHeaderTarget) }, +#endif { protectedNestedCounterTarget, sizeof(protectedNestedCounterTarget) } }; @@ -26701,6 +27012,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 @@ -26959,6 +27274,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(); @@ -27167,7 +27485,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(); diff --git a/tests/test_eat_psa.c b/tests/test_eat_psa.c new file mode 100644 index 00000000..2c514ec3 --- /dev/null +++ b/tests/test_eat_psa.c @@ -0,0 +1,4316 @@ +/* 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 = XSTRLEN(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_UNKNOWN_UINT64 0x400u + +#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 +#define TEST_EAT_PSA_COMPONENT_UNKNOWN_UINT64 0x40u + +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; + count += ((flags & TEST_EAT_PSA_COMPONENT_UNKNOWN_UINT64) != 0u) ? + 1u : 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); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_UNKNOWN_UINT64) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, UINT64_MAX); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((flags & TEST_EAT_PSA_COMPONENT_UNKNOWN_UINT64) != 0u)) { + ret = wc_CBOR_EncodeUint(ctx, 1u); + } + + 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; + count += ((omit & TEST_EAT_PSA_UNKNOWN_UINT64) != 0u) ? 1u : 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) && + ((omit & TEST_EAT_PSA_UNKNOWN_UINT64) != 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, UINT64_MAX); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((omit & TEST_EAT_PSA_UNKNOWN_UINT64) != 0u)) { + ret = wc_CBOR_EncodeUint(&ctx, 1u); + } + 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)XMEMSET(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)XMEMSET(claims, 0, sizeof(*claims)); + (void)XMEMSET(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)) || + (XMEMCMP(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)) || + (XMEMCMP(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) || + (XMEMCMP(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)) || + (XMEMCMP(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)XMEMSET(&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)XMEMSET(&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)XMEMCPY(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)XMEMSET(&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)XMEMSET(&zeroToken, 0, sizeof(zeroToken)); + return ((token != NULL) && + (XMEMCMP(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 (XMEMCMP(&in[i], find, findLen) == 0) { + (void)XMEMCPY(out, in, i); + (void)XMEMCPY(&out[i], replacement, replacementLen); + (void)XMEMCPY(&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)XMEMSET(&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)XMEMSET(&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; +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) + EAT_PSA_RESOLVER_CTX resolver; +#endif + int ret; + + (void)printf(" [RFC 9783 verifier argument contracts]\n"); + + (void)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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"); + + (void)XMEMCPY(scratch, kNonce, sizeof(kNonce)); + ret = wc_CoseEatPsaToken_Verify(key, in, inSz, scratch, sizeof(kNonce), + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + token.nonce.len == sizeof(kNonce) && + XMEMCMP(token.nonce.data, kNonce, sizeof(kNonce)) == 0, + "preserve expected nonce that aliases verification scratch"); + + (void)XMEMCPY(&token, kNonce, sizeof(kNonce)); + ret = wc_CoseEatPsaToken_Verify(key, in, inSz, + (const uint8_t*)&token, sizeof(kNonce), scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && + token.nonce.len == sizeof(kNonce) && + XMEMCMP(token.nonce.data, kNonce, sizeof(kNonce)) == 0, + "preserve expected nonce that aliases verification output"); + +#if defined(WOLFCOSE_EAT_PSA_UEID_RESOLVER) + resolver.key = key; + resolver.called = 0; + resolver.fail = 0; + resolver.receivedProfile = WOLFCOSE_EAT_PSA_PROFILE_NONE; + resolver.receivedAlg = WOLFCOSE_ALG_UNSET; + (void)XMEMCPY(scratch, kNonce, sizeof(kNonce)); + ret = wc_CoseEatPsaToken_VerifyByUeid(test_eat_psa_resolve, &resolver, + in, inSz, scratch, sizeof(kNonce), scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && resolver.called == 1 && + token.nonce.len == sizeof(kNonce) && + XMEMCMP(token.nonce.data, kNonce, sizeof(kNonce)) == 0, + "preserve UEID-resolver nonce that aliases verification scratch"); + + resolver.called = 0; + (void)XMEMCPY(&token, kNonce, sizeof(kNonce)); + ret = wc_CoseEatPsaToken_VerifyByUeid(test_eat_psa_resolve, &resolver, + in, inSz, (const uint8_t*)&token, sizeof(kNonce), scratch, scratchSz, + &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && resolver.called == 1 && + token.nonce.len == sizeof(kNonce) && + XMEMCMP(token.nonce.data, kNonce, sizeof(kNonce)) == 0, + "preserve UEID-resolver nonce that aliases verification output"); + + (void)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)) || + (XMEMCMP(message, expectedPrefix, sizeof(expectedPrefix)) != 0))) { + TEST_ASSERT(0, "expected deterministic Sign1 header layout"); + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + (void)XMEMCPY(nonPreferred, message, sizeof(expectedPrefix) - 1u); + nonPreferred[sizeof(expectedPrefix) - 1u] = 0xB8u; + nonPreferred[sizeof(expectedPrefix)] = 0x00u; + (void)XMEMCPY(&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) && + XMEMCMP(verifiedPayload, kNonce, sizeof(kNonce)) == 0, + "Sign1 decode-option API accepts non-preferred unprotected header"); + + (void)XMEMCPY(nonPreferred, message, sizeof(expectedPrefix) - 1u); + (void)XMEMCPY(&nonPreferred[sizeof(expectedPrefix) - 1u], + textUnprotected, sizeof(textUnprotected)); + (void)XMEMCPY(&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)XMEMCPY(nonPreferred, message, sizeof(expectedPrefix) - 1u); + (void)XMEMCPY(&nonPreferred[sizeof(expectedPrefix) - 1u], + nonPreferredTextUnprotected, sizeof(nonPreferredTextUnprotected)); + (void)XMEMCPY(&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)) || + (XMEMCMP(message, expectedPrefix, sizeof(expectedPrefix)) != 0))) { + TEST_ASSERT(0, "expected deterministic Mac0 header layout"); + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + (void)XMEMCPY(nonPreferred, message, sizeof(expectedPrefix) - 1u); + nonPreferred[sizeof(expectedPrefix) - 1u] = 0xB8u; + nonPreferred[sizeof(expectedPrefix)] = 0x00u; + (void)XMEMCPY(&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) && + XMEMCMP(verifiedPayload, kNonce, sizeof(kNonce)) == 0, + "Mac0 decode-option API accepts non-preferred unprotected header"); + + (void)XMEMCPY(nonPreferred, message, sizeof(expectedPrefix) - 1u); + (void)XMEMCPY(&nonPreferred[sizeof(expectedPrefix) - 1u], + textUnprotected, sizeof(textUnprotected)); + (void)XMEMCPY(&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)XMEMCPY(nonPreferred, message, sizeof(expectedPrefix) - 1u); + (void)XMEMCPY(&nonPreferred[sizeof(expectedPrefix) - 1u], + nonPreferredTextUnprotected, sizeof(nonPreferredTextUnprotected)); + (void)XMEMCPY(&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)XMEMSET(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)XMEMCPY(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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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"); + } + + ret = test_eat_psa_verify_raw_current_ex(key, rng, claims, + currentProfile, sizeof(currentProfile) - 1u, + TEST_EAT_PSA_UNKNOWN_UINT64, 0u, 0, + TEST_EAT_PSA_COMPONENT_UNKNOWN_UINT64, claims->componentCount, + scratch, scratchSz, &token); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS && token.componentCount == 1u, + "accept full-width unknown integer claims and components"); + + /* 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 (XMEMCMP(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMCPY(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)XMEMSET(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 }; + 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; + 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 tokenLen; + 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)XMEMCPY(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"); + + 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; + 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)XMEMSET(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)XMEMSET(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)XMEMSET(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)XMEMSET(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)XMEMSET(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)XMEMSET(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) +{ + static const uint8_t protectedX5chain[] = { + 0xA2u, 0x01u, 0x26u, 0x18u, 0x21u, 0x40u + }; + 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"); + (void)XMEMSET(&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)XMEMSET(&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)XMEMCPY(&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)XMEMCPY(&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)XMEMCPY(&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 + }; + static const uint16_t provisioningLifecycles[] = { + 0x2000u, 0x20FFu + }; + 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)XMEMSET(&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)XMEMCPY(unprotectedAlgToken, tokenBuf, 6u); + (void)XMEMCPY(&unprotectedAlgToken[6], textUnprotected, + sizeof(textUnprotected)); + (void)XMEMCPY(&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)XMEMCPY(unprotectedAlgToken, tokenBuf, 6u); + (void)XMEMCPY(&unprotectedAlgToken[6], contentTypeUnprotected, + sizeof(contentTypeUnprotected)); + (void)XMEMCPY(&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)XMEMCPY(unprotectedAlgToken, tokenBuf, 6u); + (void)XMEMCPY(&unprotectedAlgToken[6], nonPreferredTextUnprotected, + sizeof(nonPreferredTextUnprotected)); + (void)XMEMCPY(&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)XMEMCPY(unprotectedAlgToken, tokenBuf, 6u); + (void)XMEMCPY(&unprotectedAlgToken[6], duplicateTextUnprotected, + sizeof(duplicateTextUnprotected)); + (void)XMEMCPY(&unprotectedAlgToken[6u + + sizeof(duplicateTextUnprotected)], &tokenBuf[7], + tokenLen - 7u); + auxTokenLen = tokenLen + sizeof(duplicateTextUnprotected) - 1u; + (void)XMEMSET(&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)XMEMCPY(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)XMEMSET(&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)XMEMSET(&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)XMEMSET(&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)XMEMCPY(variantPayload, token.components.data, + token.components.len); + variantPayload[token.components.len] = 0x00u; + iteratorToken = token; + iteratorToken.components.data = variantPayload; + iteratorToken.components.len++; + (void)XMEMSET(&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)XMEMCPY(unprotectedAlgToken, tokenBuf, 2u); + unprotectedAlgToken[2] = 0x40u; + unprotectedAlgToken[3] = 0xA1u; + unprotectedAlgToken[4] = 0x01u; + unprotectedAlgToken[5] = 0x26u; + (void)XMEMCPY(&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)XMEMSET(&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)XMEMCPY(unprotectedAlgToken, tokenBuf, tokenLen); + unprotectedAlgToken[tokenLen - 1u] ^= 0xFFu; + (void)XMEMSET(&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)XMEMCPY(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)XMEMSET(&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)XMEMCPY(legacyToken, tokenBuf, 6u); + legacyToken[6] = 0xA1u; + legacyToken[7] = 0x18u; + legacyToken[8] = 0x21u; + legacyToken[9] = 0x40u; + (void)XMEMCPY(&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)XMEMCPY(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)XMEMCPY(wrongNonce, kNonce, sizeof(wrongNonce)); + wrongNonce[0] ^= 0xFFu; + (void)XMEMSET(&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)XMEMCPY(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"); + } + + for (i = 0u; i < (sizeof(provisioningLifecycles) / + sizeof(provisioningLifecycles[0])); i++) { + (void)XMEMCPY(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)XMEMCPY(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)XMEMCPY(variantPayload, payload, 1u); + variantPayload[1] = 0x18u; + variantPayload[2] = 0x0Au; + (void)XMEMCPY(&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)XMEMCPY(&duplicatePayload[1], &payload[1], payloadLen - 1u); + (void)XMEMCPY(&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 && + XMEMCMP(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)XMEMSET(&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)XMEMSET(&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)XMEMSET(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)XMEMSET(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)XMEMCPY(modifiedToken, tokenBuf, 6u); + (void)XMEMCPY(&modifiedToken[6], textUnprotected, + sizeof(textUnprotected)); + (void)XMEMCPY(&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)XMEMCPY(modifiedToken, tokenBuf, 6u); + (void)XMEMCPY(&modifiedToken[6], contentTypeUnprotected, + sizeof(contentTypeUnprotected)); + (void)XMEMCPY(&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)XMEMCPY(modifiedToken, tokenBuf, 6u); + (void)XMEMCPY(&modifiedToken[6], nonPreferredTextUnprotected, + sizeof(nonPreferredTextUnprotected)); + (void)XMEMCPY(&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)XMEMCPY(modifiedToken, tokenBuf, tokenLen); + modifiedToken[tokenLen - 1u] ^= 0xFFu; + (void)XMEMSET(&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)XMEMCPY(modifiedToken, tokenBuf, tokenLen); + modifiedToken[5] = 0x27u; + (void)XMEMSET(&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) && (tokenLen > 0u)) { + (void)XMEMCPY(modifiedToken, tokenBuf, tokenLen); + modifiedToken[tokenLen - 1u] ^= 0xFFu; + (void)XMEMSET(&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)XMEMSET(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)XMEMSET(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)XMEMSET(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)XMEMSET(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)XMEMSET(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)XMEMSET(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 00000000..1671c42b --- /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 00000000..cf2e62dd --- /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 00000000..18e98a36 --- /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 00000000..300e780b --- /dev/null +++ b/tests/test_eat_psa_limits.c @@ -0,0 +1,281 @@ +/* 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)XMEMSET(&zero, 0, sizeof(zero)); + return ((token != NULL) && + (XMEMCMP(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) { + /* Satisfy the generic CBOR container check with minimal pairs. The EAT + * component ceiling is tested before any pair is parsed. */ + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < (2u * 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 < (2u * (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)XMEMSET(&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 00000000..1dedb63e --- /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 00000000..c44aa610 --- /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 00000000..ecc00f78 --- /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)XMEMSET(&zero, 0, sizeof(zero)); + return ((token != NULL) && + (XMEMCMP(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)XMEMSET(&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)XMEMSET(claims, 0, sizeof(*claims)); + (void)XMEMSET(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 && + XMEMCMP(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 && + XMEMCMP(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 b9c5938f..618b9fec 100644 --- a/tests/test_main.c +++ b/tests/test_main.c @@ -47,6 +47,10 @@ int main(void) printf("\n--- PSA Attestation Token Tests ---\n"); failures += test_psa_attestation(); + 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 ffe19913..ee429c77 100644 --- a/tests/test_suite.h +++ b/tests/test_suite.h @@ -28,5 +28,7 @@ int test_cose(void); int test_interop(void); int test_cose_examples(void); int test_psa_attestation(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 224cbba3..2f19ee48 100644 --- a/tools/wolfcose_tool.c +++ b/tools/wolfcose_tool.c @@ -1297,16 +1297,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(" ");