diff --git a/.github/workflows/lean-build.yml b/.github/workflows/lean-build.yml index c30b3c2..c5aea76 100644 --- a/.github/workflows/lean-build.yml +++ b/.github/workflows/lean-build.yml @@ -131,6 +131,8 @@ jobs: gcc $SF -DWOLFCOSE_LEAN -c src/wolfcose.c -o /tmp/leanall.o echo "== WOLFCOSE_LEAN_VERIFY ==" gcc $SF -DWOLFCOSE_LEAN_VERIFY -c src/wolfcose.c -o /tmp/lean.o + echo "== COSE_Mac without COSE_Mac0 ==" + gcc $SF -DWOLFCOSE_NO_MAC0 -c src/wolfcose.c -o /tmp/mac-no-mac0.o echo "== sign-only ==" gcc $SF -DWOLFCOSE_NO_SIGN1_VERIFY -DWOLFCOSE_NO_ENCRYPT0 -DWOLFCOSE_NO_MAC0 \ -DWOLFCOSE_NO_SIGN -DWOLFCOSE_NO_ENCRYPT -DWOLFCOSE_NO_MAC \ diff --git a/.github/workflows/minimal-build.yml b/.github/workflows/minimal-build.yml index f6992f4..d935ef1 100644 --- a/.github/workflows/minimal-build.yml +++ b/.github/workflows/minimal-build.yml @@ -22,9 +22,19 @@ jobs: - name: ECC-only wolfssl_flags: "--enable-cryptonly --enable-ecc --enable-aesgcm --enable-keygen --enable-sha384 --enable-sha512 --enable-lowresource --enable-sp-math-all --disable-dh --disable-rsa --disable-aescbc --disable-sha --disable-md5 --disable-chacha --disable-poly1305 --disable-errorstrings" cache_key: wolfssl-ecc-only-v5 + - name: Deterministic ECDSA + wolfssl_flags: "--enable-cryptonly --enable-ecc --enable-aesgcm --enable-keygen --enable-sha384 --enable-sha512 --enable-lowresource --enable-sp-math-all --disable-dh --disable-rsa --disable-aescbc --disable-sha --disable-md5 --disable-chacha --disable-poly1305 --disable-errorstrings" + wolfssl_cppflags: "-DWOLFSSL_ECDSA_DETERMINISTIC_K -DWOLFSSL_NO_MALLOC" + cache_key: wolfssl-ecc-deterministic-v2 + cose_flags: "-DWOLFCOSE_ENABLE_DETERMINISTIC_ECDSA" + - name: ECC fastmath ALT with bigint + wolfssl_flags: "--enable-cryptonly --enable-ecc --enable-aesgcm --enable-keygen --enable-sha384 --enable-sha512 --enable-fastmath --enable-lowresource --enable-asynccrypt --disable-dh --disable-rsa --disable-sha --disable-md5 --disable-chacha --disable-poly1305 --disable-errorstrings --disable-shared --enable-static" + cache_key: wolfssl-ecc-fast-alt-bigint-v1 + compile_only: true - name: EdDSA-only - wolfssl_flags: "--enable-cryptonly --enable-ed25519 --enable-curve25519 --enable-sha512 --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings" - cache_key: wolfssl-eddsa-only-v6 + wolfssl_flags: "--enable-cryptonly --disable-ecc --enable-ed25519 --enable-curve25519 --enable-sha512 --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings" + cache_key: wolfssl-eddsa-only-v7 + skip_tool_test: true - name: Ed448-only wolfssl_flags: "--enable-cryptonly --enable-ed448 --enable-sha512 --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings" cache_key: wolfssl-ed448-only-v3 @@ -32,8 +42,9 @@ jobs: wolfssl_flags: "--enable-cryptonly --enable-aesgcm --enable-aesccm --enable-chacha --enable-poly1305 --enable-lowresource --disable-dh --disable-rsa --disable-errorstrings" cache_key: wolfssl-aead-only-v6 - name: RSA-PSS-only - wolfssl_flags: "--enable-cryptonly --enable-rsapss --enable-keygen --enable-sha384 --enable-sha512 --enable-lowresource --disable-dh --disable-errorstrings" - cache_key: wolfssl-rsa-only-v6 + wolfssl_flags: "--enable-cryptonly --enable-rsapss --enable-keygen --enable-sha384 --enable-sha512 --enable-lowresource --disable-ecc --disable-dh --disable-errorstrings" + cache_key: wolfssl-rsa-only-v7 + skip_tool_test: true - name: PQ (ML-DSA) only wolfssl_flags: "--enable-cryptonly --enable-mldsa --disable-dh --disable-rsa --disable-errorstrings" cache_key: wolfssl-pq-only-v4 @@ -77,18 +88,21 @@ jobs: git clone --depth 1 https://github.com/wolfSSL/wolfssl.git wolfssl-minimal-src cd wolfssl-minimal-src ./autogen.sh + export CPPFLAGS="${{ matrix.wolfssl_cppflags }}" ./configure ${{ matrix.wolfssl_flags }} \ --prefix=$HOME/wolfssl-minimal make -j$(nproc) make install - name: Build wolfCOSE + if: matrix.compile_only != true run: | export WOLFSSL_DIR=$HOME/wolfssl-minimal make CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion ${{ matrix.cose_flags }} -I./include -isystem $WOLFSSL_DIR/include" \ LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" - name: Run unit tests + if: matrix.compile_only != true run: | export WOLFSSL_DIR=$HOME/wolfssl-minimal export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib @@ -96,8 +110,25 @@ jobs: LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" - name: Run tool round-trip test + if: matrix.compile_only != true && matrix.skip_tool_test != true run: | export WOLFSSL_DIR=$HOME/wolfssl-minimal export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib make tool-test CFLAGS="-std=c99 -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 -Os -Wall -Wextra -Wpedantic -Wshadow -Wconversion ${{ matrix.cose_flags }} -I./include -isystem $WOLFSSL_DIR/include" \ LDFLAGS="-L$WOLFSSL_DIR/lib -lwolfssl" + + - name: Compile wolfCOSE policy profile + if: matrix.compile_only == true + run: | + export WOLFSSL_DIR=$HOME/wolfssl-minimal + for macro in ALT_ECC_SIZE USE_FAST_MATH HAVE_WOLF_BIGINT; do + if ! grep -Eq "^#define[[:space:]]+$macro([[:space:]]|$)" \ + "$WOLFSSL_DIR/include/wolfssl/options.h"; then + echo "::error::Expected $macro in wolfSSL options.h" + exit 1 + fi + done + cc -std=c99 -pedantic-errors -Werror -Wall -Wextra -Wshadow \ + -Wconversion -Wvla -DHAVE_ANONYMOUS_INLINE_AGGREGATES=1 \ + -I./include -isystem $WOLFSSL_DIR/include -fsyntax-only \ + src/wolfcose.c tests/test_cose.c diff --git a/.github/workflows/misra-2012.yml b/.github/workflows/misra-2012.yml index c9c21d0..9313674 100644 --- a/.github/workflows/misra-2012.yml +++ b/.github/workflows/misra-2012.yml @@ -17,7 +17,10 @@ concurrency: jobs: misra-cppcheck: name: MISRA C 2012 (cppcheck) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + + env: + WOLFSSL_REF: ac01707f552c611fbd135cc723b2682b3e7f80f2 permissions: issues: write @@ -29,23 +32,28 @@ jobs: run: | sudo apt-get update sudo apt-get install -y cppcheck autoconf automake libtool + CPPCHECK_VERSION=$(cppcheck --version) + if [ "$CPPCHECK_VERSION" != "Cppcheck 2.13.0" ]; then + echo "::error::expected Cppcheck 2.13.0, got $CPPCHECK_VERSION" + exit 1 + fi - - name: Resolve wolfSSL master commit - id: wolfssl-rev - run: echo "sha=$(git ls-remote https://github.com/wolfSSL/wolfssl.git HEAD | cut -f1)" >> "$GITHUB_OUTPUT" - name: Cache wolfSSL id: cache-wolfssl uses: actions/cache@v4 with: path: ~/wolfssl-install - key: wolfssl-ubuntu-latest-v3-${{ steps.wolfssl-rev.outputs.sha }} + key: wolfssl-ubuntu-24.04-v4-${{ env.WOLFSSL_REF }} - name: Build wolfSSL if: steps.cache-wolfssl.outputs.cache-hit != 'true' run: | cd ~ - git clone --depth 1 https://github.com/wolfSSL/wolfssl.git + git init wolfssl cd wolfssl + git remote add origin https://github.com/wolfSSL/wolfssl.git + git fetch --depth 1 origin "$WOLFSSL_REF" + git checkout --detach FETCH_HEAD ./autogen.sh ./configure --enable-ecc --enable-ed25519 --enable-ed448 \ --enable-curve25519 --enable-aesgcm --enable-aesccm \ @@ -57,8 +65,9 @@ jobs: make install - name: Run MISRA C 2012 check + id: misra-analysis run: | - # Define every wolfCOSE and wolfSSL macro so cppcheck checks the full + # Define wolfSSL capability inputs so cppcheck checks the full # code path instead of enumerating thousands of wolfSSL platform #ifdef # combinations (which takes 3+ hours with --force). # @@ -69,59 +78,20 @@ jobs: # Hashing: SHA-384, SHA-512 # Key distribution: AES Key Wrap, HKDF, ECDH # - # wolfCOSE message type gates: - # Sign1, Encrypt0, Mac0, Sign, Encrypt, Mac - # - # wolfCOSE feature gates: - # Recipients, Key Wrap, ECDH, CBOR, Key encode/decode, Float - # - # MISRA C:2012 Deviations (documented at - # https://github.com/wolfSSL/wolfCOSE/wiki/MISRA-Compliance): - # - # Rule 2.5 — Feature-gate macros (WOLFCOSE_SIGN1 etc.) are defined - # conditionally; cppcheck false-positive when both parent - # and child -D flags are passed on the command line. - # - # Rule 8.7 — wolfCose_SigSize is WOLFCOSE_LOCAL (not static) because - # it is called from tests/test_cose.c, which cppcheck does - # not scan. cppcheck only sees src/ and thinks the function - # is used in one translation unit. - # - # Rule 11.8 — wolfSSL's ECC/ECDH APIs (wc_ecc_set_rng, - # wc_ecc_shared_secret, wolfCose_EccSignRaw) take - # non-const ecc_key* even for read-like operations. - # wolfCOSE passes keys through the WOLFCOSE_KEY union, - # which cppcheck flags as discarding const. - # - # Rule 19.2 — Tagged union in WOLFCOSE_KEY is a fundamental design - # choice for multi-algorithm key support. The kty field - # discriminates the active union member. - # - # Rule 21.15 — XMEMMOVE (memmove) is used intentionally for - # overlapping memory regions when right-justifying - # ECC r/s signature components. cppcheck flags the - # overlap as a potential issue but memmove handles - # it correctly by design. - # - # Rule 11.5 — wolfCose_ForceZero takes void* so it can accept any - # buffer type, then casts internally to volatile - # unsigned char* for byte-wise secure zeroing. The - # void*->byte cast is the standard pattern for - # generic memory APIs. - # + # Let settings.h derive wolfCOSE's internal feature macros exactly as + # a full production build does. Only true user inputs are supplied. + cc -std=c99 -Wall -Wextra -Wpedantic -Werror \ + -DWOLFCOSE_FLOAT -DWOLFCOSE_ENABLE_EXT_SIGN \ + -I include -I "$HOME/wolfssl-install/include" \ + -fsyntax-only tests/misra_consumer.c + + set +e cppcheck --addon=misra \ --enable=all \ - --suppress=misra-config \ --suppress=unmatchedSuppression \ --suppress=missingIncludeSystem \ --suppress=unusedFunction \ --suppress=*:$HOME/wolfssl-install/include/* \ - --suppress=misra-c2012-2.5 \ - --suppress=misra-c2012-8.7 \ - --suppress=misra-c2012-11.5 \ - --suppress=misra-c2012-11.8 \ - --suppress=misra-c2012-19.2 \ - --suppress=misra-c2012-21.15 \ -DHAVE_ECC \ -DHAVE_ED25519 \ -DHAVE_ED448 \ @@ -137,48 +107,72 @@ jobs: -DWOLFSSL_AES_DIRECT \ -DHAVE_HKDF \ -DHAVE_AES_CBC \ - -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_SIGN \ - -DWOLFCOSE_SIGN_SIGN \ - -DWOLFCOSE_SIGN_VERIFY \ - -DWOLFCOSE_ENCRYPT \ - -DWOLFCOSE_ENCRYPT_ENCRYPT \ - -DWOLFCOSE_ENCRYPT_DECRYPT \ - -DWOLFCOSE_MAC \ - -DWOLFCOSE_MAC_CREATE \ - -DWOLFCOSE_MAC_VERIFY \ - -DWOLFCOSE_RECIPIENTS \ - -DWOLFCOSE_KEY_WRAP \ - -DWOLFCOSE_ECDH \ - -DWOLFCOSE_ECDH_WRAP \ - -DWOLFCOSE_CBOR_ENCODE \ - -DWOLFCOSE_CBOR_DECODE \ - -DWOLFCOSE_KEY_ENCODE \ - -DWOLFCOSE_KEY_DECODE \ -DWOLFCOSE_FLOAT \ -DWOLFCOSE_ENABLE_EXT_SIGN \ -I include -I src -I $HOME/wolfssl-install/include \ - src/ include/ \ - 2>&1 | tee misra-report-raw.txt || true - # Check for cppcheck tool errors (addon not found, parse errors, etc.) + src/wolfcose.c src/wolfcose_cbor.c \ + tests/misra_consumer.c \ + 2>&1 | tee misra-report-raw.txt + CPPCHECK_STATUS=${PIPESTATUS[0]} + set -e + if [ "$CPPCHECK_STATUS" -ne 0 ]; then + echo "::error::cppcheck failed with status $CPPCHECK_STATUS" + exit "$CPPCHECK_STATUS" + fi + # Keep only MISRA findings in wolfCOSE code. Generic cppcheck findings + # are enforced by static-analysis.yml. + grep -E '^(src/|include/wolfcose/).*\[misra-c2012-[0-9]+\.[0-9]+\]' \ + misra-report-raw.txt > misra-report-all.txt || true + grep -E '^tests/misra_consumer\.c:.*\[misra-c2012-[0-9]+\.[0-9]+\]' \ + misra-report-raw.txt > misra-report-consumer.txt || true + # A broken analyzer must never be mistaken for a clean report. if grep -q 'error:.*\[preprocessorErrorDirective\]\|Failed to execute addon\|Bailing out' misra-report-raw.txt; then - echo "::warning::cppcheck encountered tool errors — review raw report" + echo "::error::cppcheck encountered tool errors; review raw report" + exit 1 + fi + + set +e + python3 scripts/classify_misra.py \ + --input misra-report-all.txt \ + --unresolved misra-report.txt \ + --deviated misra-report-deviated.txt \ + --limitations misra-report-limitations.txt + CLASSIFIER_STATUS=$? + set -e + echo "$CLASSIFIER_STATUS" > misra-classifier-status.txt + if [ "$CLASSIFIER_STATUS" -gt 1 ]; then + echo "::error::MISRA classifier failed with status $CLASSIFIER_STATUS" + exit "$CLASSIFIER_STATUS" + fi + if [ -s misra-report-consumer.txt ]; then + echo "::error::MISRA consumer harness has violations" + cat misra-report-consumer.txt + exit 1 + fi + if [ "$CLASSIFIER_STATUS" -ne 0 ]; then + exit "$CLASSIFIER_STATUS" fi - # Filter out wolfSSL header violations — only keep wolfCOSE code - grep -E '^(src/|include/wolfcose/)' misra-report-raw.txt > misra-report.txt || true - name: MISRA report summary if: always() run: | echo "## MISRA C 2012 Report" >> $GITHUB_STEP_SUMMARY + if [ ! -f misra-classifier-status.txt ]; then + echo "**MISRA analysis did not reach classification.**" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + CLASSIFIER_STATUS=$(cat misra-classifier-status.txt) + if [ "$CLASSIFIER_STATUS" != "0" ] && \ + [ "$CLASSIFIER_STATUS" != "1" ]; then + echo "**MISRA classifier failed with status ${CLASSIFIER_STATUS}.**" >> $GITHUB_STEP_SUMMARY + exit 1 + fi + CONSUMER=$(wc -l < misra-report-consumer.txt) + echo "Analyzer-consumer violations: ${CONSUMER}." >> $GITHUB_STEP_SUMMARY + if [ "$CONSUMER" -ne 0 ]; then + echo "**MISRA consumer analysis failed.**" >> $GITHUB_STEP_SUMMARY + exit 1 + fi if [ -s misra-report.txt ]; then TOTAL=$(wc -l < misra-report.txt) echo "**Total violations: ${TOTAL}**" >> $GITHUB_STEP_SUMMARY @@ -189,8 +183,13 @@ jobs: echo "... (${TOTAL} total, showing first 50)" >> $GITHUB_STEP_SUMMARY fi echo '```' >> $GITHUB_STEP_SUMMARY + exit 1 else - echo "No MISRA violations found." >> $GITHUB_STEP_SUMMARY + DEVIATED=$(wc -l < misra-report-deviated.txt) + LIMITATIONS=$(wc -l < misra-report-limitations.txt) + echo "No undeviated MISRA violations found." >> $GITHUB_STEP_SUMMARY + echo "Approved deviations observed: ${DEVIATED}." >> $GITHUB_STEP_SUMMARY + echo "Analyzer limitations resolved with source evidence: ${LIMITATIONS}." >> $GITHUB_STEP_SUMMARY fi - name: Upload MISRA report @@ -198,7 +197,14 @@ jobs: uses: actions/upload-artifact@v4 with: name: misra-compliance-report - path: misra-report.txt + path: | + misra-report-raw.txt + misra-report-all.txt + misra-report.txt + misra-report-deviated.txt + misra-report-limitations.txt + misra-report-consumer.txt + misra-classifier-status.txt - name: Report results via GitHub Issue if: always() && github.event_name == 'push' @@ -211,6 +217,55 @@ jobs: let body = ''; let hasViolations = false; + let deviationCount = 0; + + let classifierStatus = ''; + try { + classifierStatus = fs.readFileSync( + 'misra-classifier-status.txt', 'utf8').trim(); + } catch (e) { + core.setFailed( + 'MISRA classification did not complete; issue left unchanged'); + return; + } + if ((classifierStatus !== '0') && (classifierStatus !== '1')) { + core.setFailed( + `MISRA classifier failed with status ${classifierStatus}; ` + + 'issue left unchanged'); + return; + } + + try { + const deviations = fs.readFileSync('misra-report-deviated.txt', 'utf8').trim(); + deviationCount = deviations.length > 0 ? deviations.split('\n').length : 0; + } catch (e) { + core.setFailed( + 'MISRA deviation report is missing; issue left unchanged'); + return; + } + + try { + const consumerReport = fs.readFileSync( + 'misra-report-consumer.txt', 'utf8').trim(); + if (consumerReport.length > 0) { + core.setFailed( + 'MISRA consumer analysis failed; issue left unchanged'); + return; + } + } catch (e) { + core.setFailed( + 'MISRA consumer report is missing; issue left unchanged'); + return; + } + + try { + fs.readFileSync('misra-report-limitations.txt', 'utf8'); + } catch (e) { + core.setFailed( + 'MISRA analyzer-limitations report is missing; ' + + 'issue left unchanged'); + return; + } try { const report = fs.readFileSync('misra-report.txt', 'utf8').trim(); @@ -229,7 +284,8 @@ jobs: body += `*Updated: ${new Date().toISOString()}*`; } } catch (e) { - // No report file means cppcheck didn't produce output + core.setFailed('MISRA report is missing; issue left unchanged'); + return; } // Find existing issue @@ -263,7 +319,7 @@ jobs: core.info('Created new MISRA report issue'); } } else { - // No violations — close the issue if it exists + // No undeviated violations; close the issue if it exists. if (existing && existing.state === 'open') { await github.rest.issues.update({ owner: context.repo.owner, @@ -276,9 +332,9 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: existing.number, - body: `No MISRA violations found. [Workflow run](${runUrl})\n\n*${new Date().toISOString()}*` + body: `No undeviated MISRA violations. Approved deviations observed: ${deviationCount}. [Workflow run](${runUrl})\n\n*${new Date().toISOString()}*` }); - core.info(`Closed issue #${existing.number} — no violations`); + core.info(`Closed issue #${existing.number}: no undeviated violations`); } } diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index 59d89a9..48bc592 100644 --- a/.github/workflows/sanitizer.yml +++ b/.github/workflows/sanitizer.yml @@ -97,3 +97,10 @@ jobs: 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" \ LDFLAGS="-fsanitize=address -L$WOLFSSL_DIR/lib -lwolfssl" + + - name: ECC private-import backend policy (ASan) + run: | + export WOLFSSL_DIR=$HOME/wolfssl-asan + export LD_LIBRARY_PATH=$WOLFSSL_DIR/lib + make ecc-import-policy-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" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 23db83d..a12ca88 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -15,6 +15,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Enforce zero dynamic allocation + run: make zero-alloc-check + - name: Install cppcheck run: | sudo apt-get update diff --git a/.github/workflows/wolfssl-versions.yml b/.github/workflows/wolfssl-versions.yml index 74f98bc..963d71a 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -16,12 +16,14 @@ permissions: jobs: # Resolve the latest -stable wolfSSL tag at run time so we don't have to - # bump this workflow every release. Floor (v5.8.0) and master are fixed: - # v5.8.0 is the first release with the public wc_ForceZero symbol and the - # current set of FIPS-204 final ML-DSA APIs wolfCOSE depends on; master - # surfaces upstream drift on the nightly run. Master is tested both classic - # and with ML-DSA/PQC; latest-stable gains an explicit PQC row automatically - # once it is newer than v5.9.1-stable (i.e. when 5.9.2 ships). + # bump this workflow every release. The base floor (v5.8.0), AES Key Wrap + # floor (v5.9.0), and master are fixed: + # v5.8.0 is the first release with the public wc_ForceZero symbol. AES Key + # Wrap is disabled on that row because its constant-time integrity check + # requires wolfSSL 5.9.0 or later. Master surfaces upstream drift on the + # nightly run. Master is tested both classic and with ML-DSA/PQC; + # latest-stable gains an explicit PQC row automatically once it is newer + # than v5.9.1-stable (i.e. when 5.9.2 ships). discover-versions: name: Resolve wolfSSL version matrix runs-on: ubuntu-latest @@ -60,7 +62,8 @@ jobs: # non-PQC build is still exercised on the same wolfSSL. MATRIX=$(jq -nc --arg latest "$LATEST" --argjson latest_pqc "$LATEST_PQC" '{ include: ([ - {"wolfssl-version":"v5.8.0-stable","wolfssl-ref":"v5.8.0-stable","cache-key":"wolfssl-nopqc-v5.8.0-v1","pqc":false}, + {"wolfssl-version":"v5.8.0-stable","wolfssl-ref":"v5.8.0-stable","cache-key":"wolfssl-nopqc-v5.8.0-v2","pqc":false}, + {"wolfssl-version":"v5.9.0-stable","wolfssl-ref":"v5.9.0-stable","cache-key":"wolfssl-nopqc-v5.9.0-v1","pqc":false}, {"wolfssl-version":$latest,"wolfssl-ref":$latest,"cache-key":("wolfssl-nopqc-" + $latest + "-v1"),"pqc":false}, {"wolfssl-version":"master","wolfssl-ref":"master","cache-key":"","pqc":false}, {"wolfssl-version":"master","wolfssl-ref":"master","cache-key":"","pqc":true} @@ -111,11 +114,16 @@ jobs: else PQC_FLAGS="" fi + if [ "${{ matrix.wolfssl-version }}" = "v5.8.0-stable" ]; then + AESWRAP_FLAGS="" + else + AESWRAP_FLAGS="--enable-aeskeywrap" + fi ./configure --enable-ecc --enable-ed25519 --enable-ed448 \ --enable-curve25519 --enable-curve448 \ --enable-aesgcm --enable-aesccm --enable-aescbc \ --enable-sha384 --enable-sha512 \ - --enable-keygen --enable-hkdf --enable-aeskeywrap \ + --enable-keygen --enable-hkdf $AESWRAP_FLAGS \ --enable-chacha --enable-poly1305 \ $PQC_FLAGS --enable-rsapss \ --prefix=$HOME/wolfssl-install @@ -128,6 +136,34 @@ jobs: 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" + - name: Verify legacy AES Key Wrap rejection + if: matrix.wolfssl-version == 'v5.8.0-stable' + run: | + export WOLFSSL_DIR=$HOME/wolfssl-install + POLICY_LOG=/tmp/wolfcose-aeswrap-policy.log + POLICY_BASE_FLAGS="-std=c99 -x c -fsyntax-only -Wno-error -DWOLFSSL_NO_OPTIONS_H -DHAVE_AES_KEYWRAP -DWOLFSSL_AES_DIRECT -I./include -isystem $WOLFSSL_DIR/include" + if cc $POLICY_BASE_FLAGS -DWOLFCOSE_ENABLE_AESWRAP \ + include/wolfcose/settings.h >"$POLICY_LOG" 2>&1; then + echo "::error::wolfSSL 5.8.0 unexpectedly enabled AES Key Wrap" + exit 1 + fi + if ! grep -q "AES Key Wrap requires wolfSSL 5.9.0 or later" "$POLICY_LOG"; then + cat "$POLICY_LOG" + echo "::error::legacy AES Key Wrap failed for an unexpected reason" + exit 1 + fi + if ! cc $POLICY_BASE_FLAGS -DWOLFCOSE_NO_AESWRAP \ + include/wolfcose/settings.h >/dev/null 2>&1; then + echo "::error::WOLFCOSE_NO_AESWRAP did not disable legacy Key Wrap" + exit 1 + fi + if ! cc $POLICY_BASE_FLAGS -DWOLFCOSE_ENABLE_AESWRAP \ + -DWOLFCOSE_NO_RECIPIENTS \ + include/wolfcose/settings.h >/dev/null 2>&1; then + echo "::error::disabled recipients unexpectedly require new AES Key Wrap" + exit 1 + fi + - name: Run unit tests run: | export WOLFSSL_DIR=$HOME/wolfssl-install diff --git a/Makefile b/Makefile index 27173ea..455dac4 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ SCEN_IOTFLEET = examples/scenarios/iot_fleet_config SCEN_SENSOR = examples/scenarios/sensor_attestation SCEN_BROADCAST = examples/scenarios/group_broadcast_mac -.PHONY: all shared test zeroize-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage tool tool-test cmdline-test demo demos lean-verify mldsa-demo mldsa-verify comprehensive scenarios interop-tcose c99-check clean +.PHONY: all shared test ecdsa-policy-test rsapss-policy-test zero-alloc-check zeroize-test ecc-import-policy-test ext-sign-test ext-sign-demo ext-sign-force-failure coverage tool tool-test cmdline-test demo demos lean-verify mldsa-demo mldsa-verify comprehensive scenarios interop-tcose c99-check clean # --- Core library --- all: $(LIB_A) @@ -81,7 +81,178 @@ src/%.o: src/%.c src/wolfcose_internal.h include/wolfcose/wolfcose.h $(CC) $(CFLAGS) -c $< -o $@ # --- Tests --- -test: $(LIB_A) +# 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 +# caller is testing a no-ECDSA or verify-only configuration. +ECDSA_POLICY_OPTS ?= -include wolfssl/options.h +ECDSA_POLICY_BASE_FLAGS = $(CFLAGS) -x c -fsyntax-only -Wno-error \ + $(ECDSA_POLICY_OPTS) -DHAVE_ECC \ + -UWOLFCOSE_ENABLE_DETERMINISTIC_ECDSA \ + -UWOLFCOSE_NO_ES256 -UWOLFCOSE_NO_SIGN1 \ + -UWOLFCOSE_NO_SIGN1_SIGN -UWOLFCOSE_LEAN_VERIFY \ + -UWOLFCOSE_LEAN_VERIFY_MLDSA \ + -UWOLFCOSE_LEAN_MLDSA +ECDSA_POLICY_NO_SUPPORT_FLAGS = $(CFLAGS) -x c -fsyntax-only -Wno-error \ + -DWOLFSSL_NO_OPTIONS_H -DHAVE_ECC \ + -UWOLFSSL_ECDSA_DETERMINISTIC_K \ + -UWOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT \ + -UWOLFCOSE_NO_ES256 -UWOLFCOSE_NO_SIGN1 \ + -UWOLFCOSE_NO_SIGN1_SIGN \ + -UWOLFCOSE_LEAN_VERIFY \ + -UWOLFCOSE_LEAN_VERIFY_MLDSA \ + -UWOLFCOSE_LEAN_MLDSA +ECDSA_POLICY_FLAGS = $(ECDSA_POLICY_BASE_FLAGS) \ + -DWOLFSSL_ECDSA_DETERMINISTIC_K \ + -DWOLFCOSE_ENABLE_DETERMINISTIC_ECDSA +ECDSA_POLICY_HEADER = include/wolfcose/settings.h + +ecdsa-policy-test: + @set -e; \ + log_file=$$(mktemp "$${TMPDIR:-/tmp}/wolfcose-ecdsa.XXXXXX"); \ + trap 'rm -f "$$log_file"' 0 1 2 3 15; \ + if ! $(CC) $(ECDSA_POLICY_BASE_FLAGS) $(ECDSA_POLICY_HEADER) \ + >"$$log_file" 2>&1; then \ + cat "$$log_file"; \ + echo "FAIL: default ECDSA policy rejected"; \ + exit 1; \ + fi; \ + if $(CC) $(ECDSA_POLICY_NO_SUPPORT_FLAGS) \ + -DWOLFCOSE_ENABLE_DETERMINISTIC_ECDSA \ + $(ECDSA_POLICY_HEADER) >"$$log_file" 2>&1; then \ + echo "FAIL: deterministic ECDSA accepted without wolfSSL support"; \ + exit 1; \ + fi; \ + if ! grep -Fq \ + "WOLFCOSE_ENABLE_DETERMINISTIC_ECDSA requires wolfSSL support" \ + "$$log_file"; then \ + cat "$$log_file"; \ + echo "FAIL: deterministic ECDSA probe failed unexpectedly"; \ + exit 1; \ + fi; \ + if ! $(CC) $(ECDSA_POLICY_FLAGS) $(ECDSA_POLICY_HEADER) \ + >"$$log_file" 2>&1; then \ + cat "$$log_file"; \ + echo "FAIL: ECDSA policy probe environment unusable"; \ + exit 1; \ + fi; \ + for backend in \ + "-DWOLF_CRYPTO_CB -DWOLF_CRYPTO_CB_FIND" \ + "-DWOLF_CRYPTO_CB -DWOLF_CRYPTO_CB_ONLY_ECC" \ + "-DWOLFSSL_STM32_PKA" \ + "-DWOLFSSL_ATECC508A" \ + "-DWOLFSSL_ATECC608A" \ + "-DWOLFSSL_MICROCHIP_TA100" \ + "-DPLUTON_CRYPTO_ECC" \ + "-DWOLFSSL_CRYPTOCELL" \ + "-DWOLFSSL_SILABS_SE_ACCEL" \ + "-DWOLFSSL_KCAPI_ECC" \ + "-DWOLFSSL_SE050" \ + "-DWOLFSSL_ASYNC_CRYPT -DWC_ASYNC_ENABLE_ECC -DHAVE_CAVIUM -DHAVE_CAVIUM_V" \ + "-DWOLFSSL_ASYNC_CRYPT -DWC_ASYNC_ENABLE_ECC -DHAVE_INTEL_QA"; do \ + if $(CC) $(ECDSA_POLICY_FLAGS) $$backend $(ECDSA_POLICY_HEADER) \ + >"$$log_file" 2>&1; then \ + echo "FAIL: unverified ECDSA backend accepted: $$backend"; \ + exit 1; \ + fi; \ + if ! grep -Fq \ + "ECDSA backend does not support deterministic signing" \ + "$$log_file"; then \ + cat "$$log_file"; \ + echo "FAIL: ECDSA backend probe failed unexpectedly: $$backend"; \ + exit 1; \ + fi; \ + if ! $(CC) $(ECDSA_POLICY_BASE_FLAGS) $$backend \ + $(ECDSA_POLICY_HEADER) \ + >"$$log_file" 2>&1; then \ + cat "$$log_file"; \ + echo "FAIL: default ECDSA backend rejected: $$backend"; \ + exit 1; \ + fi; \ + done; \ + if ! $(CC) $(ECDSA_POLICY_FLAGS) -DWOLFSSL_XILINX_CRYPT_VERSAL \ + $(ECDSA_POLICY_HEADER) >"$$log_file" 2>&1; then \ + cat "$$log_file"; \ + echo "FAIL: deterministic Xilinx Versal ECDSA backend rejected"; \ + exit 1; \ + fi; \ + if ! $(CC) $(ECDSA_POLICY_FLAGS) -DWOLFSSL_STM32_PKA \ + -DWC_STM32_PKA_VERIFY_ONLY $(ECDSA_POLICY_HEADER) \ + >"$$log_file" 2>&1; then \ + cat "$$log_file"; \ + echo "FAIL: STM32 PKA verify-only backend rejected"; \ + exit 1; \ + fi; \ + if ! $(CC) $(ECDSA_POLICY_FLAGS) -DWOLF_CRYPTO_CB \ + $(ECDSA_POLICY_HEADER) >"$$log_file" 2>&1; then \ + cat "$$log_file"; \ + echo "FAIL: software-fallback crypto callback rejected"; \ + exit 1; \ + fi; \ + if ! $(CC) $(ECDSA_POLICY_FLAGS) -DWOLF_CRYPTO_CB \ + -DWOLFCOSE_LEAN_VERIFY $(ECDSA_POLICY_HEADER) \ + >"$$log_file" 2>&1; then \ + cat "$$log_file"; \ + echo "FAIL: ECDSA offload rejected in a verification-only build"; \ + exit 1; \ + fi; \ + if ! $(CC) $(ECDSA_POLICY_FLAGS) -DWOLFSSL_SE050 \ + -DWOLFSSL_SE050_ONLY_KEY_ID $(ECDSA_POLICY_HEADER) \ + >"$$log_file" 2>&1; then \ + cat "$$log_file"; \ + echo "FAIL: deterministic ECDSA rejected for SE050 software keys"; \ + exit 1; \ + fi; \ + echo "PASS: optional ECDSA nonce policy enforced" + +rsapss-policy-test: + $(CC) $(CFLAGS) -Werror=unused-function -fsyntax-only \ + -DWOLFCOSE_NO_SIGN1 -DWOLFCOSE_NO_SIGN src/wolfcose.c + $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H \ + -DWC_RSA_PSS -DWOLFCOSE_NO_KEY_ENCODE \ + -DWOLFCOSE_ENABLE_RSAPSS src/wolfcose.c + $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H \ + -DWC_RSA_PSS -DWOLFCOSE_LEAN_VERIFY \ + -DWOLFCOSE_ENABLE_RSAPSS src/wolfcose.c + @set -e; \ + log_file=$$(mktemp "$${TMPDIR:-/tmp}/wolfcose-rsapss.XXXXXX"); \ + trap 'rm -f "$$log_file"' 0 1 2 3 15; \ + if $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H \ + -UHAVE_ECC -UWOLFSSL_EXPORT_INT \ + -DWC_RSA_PSS -DWOLFCOSE_ENABLE_RSAPSS \ + -DWOLFSSL_RSA_VERIFY_ONLY -DWOLFCOSE_LEAN_VERIFY \ + src/wolfcose.c >"$$log_file" 2>&1; then \ + echo "FAIL: unsupported RSA verify-only policy compiled"; \ + exit 1; \ + fi; \ + grep -q "RSA-PSS key validation requires WOLFSSL_EXPORT_INT" \ + "$$log_file" + $(CC) $(CFLAGS) -x c -fsyntax-only -DWOLFSSL_NO_OPTIONS_H \ + -UHAVE_ECC -UWOLFSSL_EXPORT_INT \ + -DWC_RSA_PSS -DWOLFCOSE_ENABLE_RSAPSS \ + -DWOLFSSL_RSA_VERIFY_ONLY -DWOLFSSL_EXPORT_INT \ + -DWOLFCOSE_LEAN_VERIFY src/wolfcose.c + @set -e; \ + 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 \ + -UHAVE_ECC -UWOLFSSL_EXPORT_INT \ + -DWC_RSA_PSS -DWOLFCOSE_ENABLE_RSAPSS \ + -DWOLFSSL_RSA_VERIFY_ONLY -D$$backend \ + -DWOLFCOSE_LEAN_VERIFY include/wolfcose/settings.h \ + >"$$log_file" 2>&1; then \ + echo "FAIL: mixed RSA verify-only policy compiled: $$backend"; \ + exit 1; \ + fi; \ + grep -q "RSA-PSS key validation requires WOLFSSL_EXPORT_INT" \ + "$$log_file"; \ + done + @echo "PASS: RSA-PSS operation guards compile cleanly" + +zero-alloc-check: + sh scripts/check_zero_alloc.sh + +test: ecdsa-policy-test rsapss-policy-test zero-alloc-check $(LIB_A) $(CC) $(CFLAGS) -o $(TEST_BIN) $(TEST_SRC) $(LIB_A) $(LDFLAGS) ./$(TEST_BIN) @@ -91,6 +262,14 @@ zeroize-test: -o $(TEST_BIN) $(SRC) $(TEST_SRC) $(LDFLAGS) ./$(TEST_BIN) +# --- ECC private-import backend policy --- +# Exercise the fail-closed path without requiring a hardware SDK/device. +ecc-import-policy-test: + $(CC) $(CFLAGS) -DWOLFCOSE_FORCE_FAILURE \ + -DWOLFCOSE_TEST_NONTRANSACTIONAL_ECC_IMPORT \ + -o $(TEST_BIN) $(SRC) $(TEST_SRC) $(FORCE_FAIL_SRC) $(LDFLAGS) + ./$(TEST_BIN) + # --- Delegated signing seam test: exercises the ext-sign callback --- ext-sign-test: $(CC) $(CFLAGS) -DWOLFCOSE_ENABLE_EXT_SIGN \ @@ -270,6 +449,10 @@ c99-check: $(CC) $(C99_FLAGS) $$cfg -fsyntax-only $$f || exit 1; \ done; \ done + @$(CC) $(C99_FLAGS) -Werror=unused-function -Werror=unused-parameter \ + -DNO_HMAC -DHAVE_AES_CBC -DWOLFCOSE_ENABLE_AESMAC \ + -DWOLFCOSE_NO_RECIPIENTS -DWOLFCOSE_NO_ENCRYPT \ + -fsyntax-only src/wolfcose.c @echo "PASS: all sources conform to ISO C99 (-pedantic-errors)" # --- Cleanup --- diff --git a/README.md b/README.md index fcb2b6d..73c4f7d 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,10 @@ wolfCOSE has implemented all RFC 9052 messages both single-actor and multi-actor ## Prerequisites (wolfSSL) -wolfCOSE requires [wolfSSL](https://www.wolfssl.com/) as its crypto backend. **Minimum supported version: v5.8.0-stable** (first release with the public `wc_ForceZero` symbol). Post-quantum signing uses the canonical FIPS 204 `wc_MlDsaKey` API, which lands in wolfSSL **after v5.9.1-stable**; building wolfCOSE against v5.8.0–v5.9.1 works for everything except ML-DSA. Older 5.x releases can technically be supported but require source-level changes; contact [wolfSSL](https://www.wolfssl.com/contact/) for commercial support. +wolfCOSE requires [wolfSSL](https://www.wolfssl.com/) as its crypto backend. +**Minimum supported version: v5.8.0-stable**. Some optional algorithms require +newer releases; see [Getting Started](docs/Getting-Started.md#prerequisites) for +feature-specific dependency floors. Choose a build configuration based on the algorithms you need. @@ -189,7 +192,7 @@ Full documentation is available in the [Wiki](https://github.com/wolfSSL/wolfCOS - [Message Types](https://github.com/wolfSSL/wolfCOSE/wiki/Message-Types): All six RFC 9052 messages (Sign1, Sign, Encrypt0, Encrypt, Mac0, Mac) with code samples - [Algorithms](https://github.com/wolfSSL/wolfCOSE/wiki/Algorithms): Complete list of 40 supported algorithms with COSE IDs - [API Reference](https://github.com/wolfSSL/wolfCOSE/wiki/API-Reference): Function signatures, data structures, error codes -- [Macros](https://github.com/wolfSSL/wolfCOSE/wiki/Macros): Compile-time configuration options +- [Macros](https://github.com/wolfSSL/wolfCOSE/wiki/Macros): Compile-time configuration, size tuning, and ECDSA nonce policy - [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 44b9016..8bf4ed5 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -48,16 +48,29 @@ Pointer-based key structure (~48 bytes). Caller owns underlying wolfCrypt keys. ```c typedef struct WOLFCOSE_HDR { - int32_t alg; /* Algorithm from protected header */ + int32_t alg; /* Algorithm from either header bucket */ const uint8_t* kid; /* Key ID (zero-copy pointer) */ size_t kidLen; const uint8_t* iv; /* IV from unprotected header */ size_t ivLen; + const uint8_t* partialIv; /* Partial IV from unprotected header */ + size_t partialIvLen; + int32_t contentType; /* Content type from either header bucket */ uint8_t flags; /* WOLFCOSE_HDR_FLAG_* */ } WOLFCOSE_HDR; ``` -Parsed COSE header information. +Parsed COSE header information. `alg` and `contentType` may come from either +the protected or unprotected header bucket; `kid`, `iv`, and `partialIv` are +unprotected metadata. `WOLFCOSE_HDR_FLAG_CONTENT_TYPE_UNPROTECTED` is set when +the unprotected bucket contains the content-type label. Integer values are +stored in `contentType`; text-string values are accepted but not retained. +wolfCOSE pins an unprotected algorithm to the supplied key where cryptographic +policy requires it, but applications must not treat the other returned fields +as authenticated policy unless they independently pin them. + +All message APIs require the external AAD pointer to be non-NULL when its +length is non-zero. --- @@ -453,6 +466,17 @@ Decoding is strict: preferred CBOR only, integer labels only, no duplicate labels, and `bufSz` must be exactly the encoded length. See [Getting Started - Strict decoding](Getting-Started.md#strict-decoding-rfc-8949-preferred-serialization). +Keys containing the optional `key_ops` label (4) return +`WOLFCOSE_E_UNSUPPORTED` before any key material is imported because the +fixed-size `WOLFCOSE_KEY` wrapper cannot retain arbitrary operation arrays. + +An attached `ecc_key` that will receive private EC2 material must be freshly +initialized, with no existing curve or key material. Reusing a populated ECC +object returns `WOLFCOSE_E_INVALID_ARG` without replacing its key. Free and +initialize the object again before decoding another private EC2 key. This +precondition lets wolfCOSE roll back a failed software import without losing +caller-owned wolfCrypt configuration. Backends or math layouts that cannot be +rolled back safely return `WOLFCOSE_E_UNSUPPORTED` before private import. --- @@ -507,7 +531,8 @@ 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. On any error every field of `info` is cleared. +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 @@ -536,7 +561,7 @@ Create a COSE_Sign1 message (single signer). **Parameters:** | Name | Description | |------|-------------| -| `key` | Signing key (must have private key) | +| `key` | Signing key with private material, or an external signing callback when enabled | | `alg` | Algorithm: `WOLFCOSE_ALG_ES256`, `WOLFCOSE_ALG_ES384`, `WOLFCOSE_ALG_ES512`, `WOLFCOSE_ALG_EDDSA`, etc. | | `kid`, `kidLen` | Optional key identifier | | `payload`, `payloadLen` | Payload to include in message (or NULL for detached) | @@ -544,7 +569,7 @@ Create a COSE_Sign1 message (single signer). | `extAad`, `extAadLen` | External additional authenticated data | | `scratch`, `scratchSz` | Scratch buffer (min `WOLFCOSE_MAX_SCRATCH_SZ`) | | `out`, `outSz`, `outLen` | Output buffer and length | -| `rng` | Random number generator for ECDSA | +| `rng` | Random number generator for local signing; may be NULL with an external signing callback | **Returns:** `WOLFCOSE_SUCCESS` or error code @@ -842,6 +867,11 @@ int wc_CoseEncrypt_Encrypt( Create a COSE_Encrypt message for multiple recipients. +Direct recipients are encoded with the mandatory unprotected `{1: -6}` +algorithm header and an empty bstr ciphertext item. This adds two bytes per +direct recipient compared with the former empty-map encoding; callers using +fixed output buffers must leave room for it. + **Parameters:** | Name | Description | |------|-------------| @@ -877,6 +907,13 @@ int wc_CoseEncrypt_Decrypt( Decrypt a COSE_Encrypt message as a specific recipient. +Every recipient must declare its key-management algorithm. Direct encryption +algorithms may coexist with each other, direct key agreement requires one +recipient, and key-transport algorithms may coexist with each other. Direct +recipients must carry an empty bstr or null ciphertext item. If the body +algorithm is only in the unprotected header, +`recipient->key->alg` must pin the same algorithm. + **Parameters:** | Name | Description | |------|-------------| @@ -911,6 +948,10 @@ int wc_CoseMac_Create( Create a COSE_Mac message for multiple recipients. +Each direct recipient is encoded with the mandatory unprotected `{1: -6}` +algorithm header and an empty bstr ciphertext item. This adds two bytes per +recipient compared with the former empty-map encoding. + **Parameters:** | Name | Description | |------|-------------| @@ -944,6 +985,11 @@ int wc_CoseMac_Verify( Verify a COSE_Mac message as a specific recipient. +Every recipient must declare `WOLFCOSE_ALG_DIRECT`, sibling recipients must use +direct mode, and the recipient ciphertext item must be an empty bstr or null. +If the body algorithm is only in the unprotected header, `recipient->key->alg` must +pin the same algorithm. + **Parameters:** | Name | Description | |------|-------------| diff --git a/docs/Algorithms.md b/docs/Algorithms.md index acd42c1..bc24d96 100644 --- a/docs/Algorithms.md +++ b/docs/Algorithms.md @@ -80,6 +80,8 @@ Used with COSE_Encrypt and COSE_Mac for multi-recipient messages: | ECDH-ES+A192KW | -30 | `HAVE_ECC && HAVE_HKDF && HAVE_AES_KEYWRAP` | ECDH + Key Wrap | | ECDH-ES+A256KW | -31 | `HAVE_ECC && HAVE_HKDF && HAVE_AES_KEYWRAP` | ECDH + Key Wrap | +AES Key Wrap-based algorithms also require wolfSSL 5.9.0 or later. + ## Key Types | COSE kty | Value | Guard | Algorithms | @@ -153,6 +155,10 @@ wolfCOSE defines these constants in `wolfcose.h`: #define WOLFCOSE_ALG_A128KW (-3) #define WOLFCOSE_ALG_A192KW (-4) #define WOLFCOSE_ALG_A256KW (-5) +#define WOLFCOSE_ALG_DIRECT_HKDF_SHA_256 (-10) +#define WOLFCOSE_ALG_DIRECT_HKDF_SHA_512 (-11) +#define WOLFCOSE_ALG_DIRECT_HKDF_AES_128 (-12) +#define WOLFCOSE_ALG_DIRECT_HKDF_AES_256 (-13) #define WOLFCOSE_ALG_ECDH_ES_HKDF_256 (-25) #define WOLFCOSE_ALG_ECDH_ES_HKDF_512 (-26) diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 4379929..4725413 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -6,7 +6,12 @@ This guide covers prerequisites, building wolfCOSE, and basic usage examples. ### wolfSSL Installation -wolfCOSE requires wolfSSL 5.x with the appropriate algorithms enabled. Here's a full-featured build: +wolfCOSE requires wolfSSL 5.8.0 or later with the appropriate algorithms +enabled. AES Key Wrap requires wolfSSL 5.9.0 or later because that release +uses a constant-time integrity comparison during unwrap. ML-DSA requires a +wolfSSL release newer than 5.9.1. + +Here is a full-featured build using a release that meets those feature floors: ```bash cd wolfssl @@ -52,7 +57,7 @@ You can enable only the algorithms you need: | AES-CCM encryption | `--enable-aesccm` | | ChaCha20-Poly1305 | `--enable-chacha --enable-poly1305` | | ECDH-ES key agreement | `--enable-ecc --enable-hkdf` | -| AES Key Wrap | `--enable-aeskeywrap` | +| AES Key Wrap | `--enable-aeskeywrap` (wolfSSL 5.9.0+) | | RSA-PSS signing | `--enable-rsapss --enable-keygen` | | ML-DSA (post-quantum) | `--enable-mldsa` | | AES-MAC | `--enable-aescbc` | diff --git a/docs/MISRA-Compliance.md b/docs/MISRA-Compliance.md index e9cc50f..4851d8d 100644 --- a/docs/MISRA-Compliance.md +++ b/docs/MISRA-Compliance.md @@ -63,42 +63,33 @@ MISRA C:2023 is essentially MISRA C:2012 plus Amendments 1-4. Since free tooling | Data Flow | Low (~30%) | Medium (~50%) | 100% | | Std Lib Safety | Low (~20%) | Medium (~60%) | 100% | -## Known Deviations in wolfCOSE - -wolfCOSE has the following documented MISRA C:2012 deviations. Each is suppressed in CI via `cppcheck --suppress` and justified below. - -### Rule 2.5: Unused Macro Definitions - -**Location:** `include/wolfcose/wolfcose.h` (feature gate macros) - -**Justification:** wolfCOSE feature-gate macros (`WOLFCOSE_SIGN1`, `WOLFCOSE_SIGN1_SIGN`, etc.) are defined conditionally via `#if !defined(NO_X) && !defined(X)` guards. When the CI passes both parent and child `-D` flags on the command line (e.g., `-DWOLFCOSE_SIGN1 -DWOLFCOSE_SIGN1_SIGN`), the child macro is pre-defined and the header guard skips the `#define`. cppcheck sees the guarded-away `#define` as "unused" — a false positive caused by the CI passing explicit flags for cppcheck path coverage. - -### Rule 8.7: Could Be Defined with Internal Linkage - -**Location:** `src/wolfcose.c:wolfCose_SigSize()` - -**Justification:** `wolfCose_SigSize()` is declared `WOLFCOSE_LOCAL` in `wolfcose_internal.h` and called from `tests/test_cose.c` for unit testing. cppcheck only scans `src/` and `include/`, so it does not see the test file usage and incorrectly reports the function could be `static`. Making it `static` would break the test suite. - -### Rule 11.8: Cast Removes Const Qualification - -**Location:** `src/wolfcose.c` (ECC/ECDH wolfSSL API calls) - -**Justification:** wolfSSL's ECC APIs (`wc_ecc_set_rng`, `wc_ecc_shared_secret`, `wc_ecc_sign_hash`, `wc_ecc_verify_hash`) take non-const `ecc_key*` parameters even for operations that do not modify the key data. wolfCOSE's public API correctly uses `const` qualifiers on key parameters (e.g., `const WOLFCOSE_KEY* key`) to communicate that keys are not modified. However, when passing the internal `ecc_key*` from a const `WOLFCOSE_KEY` to wolfSSL, cppcheck flags the implicit const-to-non-const conversion through the union member access chain. This is a wolfSSL API design limitation that wolfCOSE preserves const-correctness at its own API boundary but cannot enforce it through wolfSSL's non-const function signatures. - -### Rule 19.2: Union Type Used - -**Location:** `include/wolfcose/wolfcose.h:WOLFCOSE_KEY` struct (lines 443-464) - -**Justification:** The `WOLFCOSE_KEY` struct uses a tagged union to hold pointers to wolfCrypt key types (`ecc_key*`, `ed25519_key*`, `ed448_key*`, `RsaKey*`, `wc_MlDsaKey*`, symmetric key bytes). The `kty` field discriminates the active union member. This is the standard C pattern for polymorphic types and is fundamental to supporting a multi-algorithm COSE library without dynamic allocation. Replacing the union would require either: -- `void*` pointers (less type-safe, violates Rule 11.5) -- Separate functions per algorithm (massive API bloat) -- Dynamic allocation (violates the zero-allocation design constraint) - -### Rule 21.15: Overlapping Memory in Copy - -**Location:** `src/wolfcose.c:wolfCose_EccSignRaw()` (XMEMMOVE calls) - -**Justification:** `XMEMMOVE` (`memmove`) is used intentionally for overlapping memory regions when right-justifying ECC r/s signature components within a single buffer. The signature buffer contains `r||s` where `r` or `s` may be shorter than the coordinate size and needs to be shifted right with zero-padding. `memmove` is the correct function for this — it handles overlapping source and destination by design. cppcheck flags the overlap as a potential issue, but this is the intended behavior per RFC 8152 Section 8.1. +## MISRA C:2012 Rule Enforcement + +The MISRA C:2012 workflow does not suppress any project rule IDs. It retains +the raw analyzer output, separately reports approved deviations and proven +analyzer limitations, and fails every undeviated finding in `src/` or +`include/wolfcose/`. The remaining cppcheck suppressions only exclude generic +analyzer diagnostics and findings inside third-party wolfSSL headers. + +The analyzer includes `tests/misra_consumer.c` as a downstream translation +unit. This gives public APIs and public constants a real external use for Rules +8.7 and 2.5 without linking analyzer-only code into the library. For Rule 2.5, +cppcheck does not count a macro used only by an `#if` or `#ifdef`; the report +classifier accepts that limitation only after finding the exact macro in a +tracked conditional preprocessing directive. + +### Approved deviations + +| ID | Rule | Location | Rationale and validation | +|----|------|----------|--------------------------| +| D-11.5-001 | 11.5 | `wolfCose_ForceZero` | Converting the caller's object pointer to a volatile character pointer is the standard C mechanism for securely erasing its object representation. Character access is alignment-safe and the function allocates no memory. `make zeroize-test`, `make zero-alloc-check`, and `make c99-check` validate the implementation. | +| D-19.2-001 | 19.2 | `WOLFCOSE_KEY.key` | The public, discriminated union preserves the established ABI and embedded-memory footprint. `kty` and `attachedType` govern member access. Replacing it with a structure would break ABI and increase RAM. The full tests exercise the supported members, and CI anchors the deviation to the exact union boundaries. | + +`scripts/misra-deviations.json` identifies only these source locations. The +classifier also pins a hash of the public union declaration. It fails if an +expected diagnostic disappears, an anchor or approved declaration changes, or +an additional finding of either rule appears, so a deviation cannot silently +broaden or become stale. ## MISRA C:2023 Deviations (clang-tidy) @@ -108,7 +99,7 @@ The following clang-tidy checks are suppressed in the MISRA 2023 workflow. GCC s **Location:** `src/wolfcose.c` (algorithm dispatch switches) -**Justification:** Different COSE algorithms intentionally map to the same wolfCrypt value. For example, ES512 and EdDSA both use `WC_HASH_TYPE_SHA512`, and A128GCM/A192GCM share the same nonce length. The switch branches are not bugs — they represent distinct algorithm IDs with identical cryptographic parameters. +**Justification:** Different COSE algorithms intentionally map to the same wolfCrypt value. For example, ES512 and EdDSA both use `WC_HASH_TYPE_SHA512`, and A128GCM/A192GCM share the same nonce length. The switch branches are not bugs; they represent distinct algorithm IDs with identical cryptographic parameters. ### bugprone-easily-swappable-parameters: Adjacent Parameters of Similar Types @@ -120,25 +111,25 @@ The following clang-tidy checks are suppressed in the MISRA 2023 workflow. GCC s `examples/` and `tests/` are runnable demonstration and test programs, not part of the shippable library, and are held to the same style rules where it keeps them useful as reference implementations. They are clean of the rules the library observes (no `goto`, fixed-length-coordinate checks, const-qualified literal payloads, braced statement bodies, unsigned size arithmetic, explicit precedence). The remaining deviations are inherent to runnable demos: -### Rule 21.6 — Standard I/O (and 17.7 on its return value) +### Rule 21.6: Standard I/O (and 17.7 on its return value) **Location:** `examples/`, `tests/`. **Justification:** Demos and test harnesses print human-readable status and PASS/FAIL to the console with `printf`/`fprintf`; the ignored return value (Rule 17.7) is part of the same console-output use. A real integration replaces this one call with a platform output routine. Library code under `src/` uses no standard I/O. -### Rule 15.5 — Multiple return / single point of exit +### Rule 15.5: Multiple return / single point of exit **Location:** `examples/`, `tests/`. **Justification:** Demonstration code uses early returns for linear, readable top-to-bottom flow. The library itself observes single-exit with cascading `if (ret == 0)` and a single `return`. -### Rule 2.5 — Unused macro definitions +### Rule 2.5: Unused macro definitions **Location:** `examples/`, `tests/`. **Justification:** Same false positive as the library: CI passes explicit `-D` feature flags so cppcheck checks one code path, which makes the guarded-away feature `#define`s look unused. -### Rules 8.6 / 5.9 / 8.9 — External/internal identifier definitions +### Rules 8.6 / 5.9 / 8.9: External/internal identifier definitions **Location:** `examples/`. diff --git a/docs/Macros.md b/docs/Macros.md index ef5a4a8..056d14c 100644 --- a/docs/Macros.md +++ b/docs/Macros.md @@ -36,6 +36,63 @@ Per-algorithm opt-outs for the default (non-lean) build. Each also has a `WOLFCO | `WOLFCOSE_NO_HMAC512` | HMAC-SHA512 | `WOLFSSL_SHA512` | | `WOLFCOSE_NO_AESMAC` | AES-CBC-MAC | `HAVE_AES_CBC` | +RSA-PSS operations enforce RFC 8230's minimum 2048-bit modulus. A minimal +wolfSSL RSA verify-only build must define `WOLFSSL_EXPORT_INT` so wolfCOSE can +inspect the exact modulus width; builds that also enable ECC already expose +the required wolfSSL integer-export API. + +### ECDSA Nonce Policy + +Local ES256, ES384, and ES512 signing uses wolfSSL's configured nonce policy by +default. Deterministic ECDSA is an optional hardening feature because its RFC +6979 and HMAC support increases the linked footprint and requires a specially +built wolfSSL. When enabled, wolfCOSE selects SHA-256, SHA-384, or SHA-512 to +match the COSE algorithm before every signature, then restores the caller's +deterministic-mode and hash settings. + +| Define | Description | Default | +|--------|-------------|---------| +| `WOLFCOSE_ENABLE_DETERMINISTIC_ECDSA` | Require RFC 6979 deterministic local ECDSA signing | off | + +The feature requires wolfSSL built with `WOLFSSL_ECDSA_DETERMINISTIC_K` or +`WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT`. wolfSSL has no dedicated configure +switch for these defines, so enable one in wolfSSL's `CPPFLAGS`, then enable the +wolfCOSE feature when building: + +```bash +cd wolfssl +CPPFLAGS="-DWOLFSSL_ECDSA_DETERMINISTIC_K -DWOLFSSL_NO_MALLOC" \ + ./configure +make && sudo make install + +cd ../wolfcose +make EXTRA_CFLAGS="-DWOLFCOSE_ENABLE_DETERMINISTIC_ECDSA" +``` + +`WOLFSSL_NO_MALLOC` keeps the wolfCrypt deterministic-nonce helper on its +preallocated path. It is not required to enable deterministic signing, but it +preserves zero-heap operation across the wolfCOSE and wolfCrypt layers. + +Verification-only builds do not need the option. A delegated signing callback +chooses its own nonce policy. For a `WOLFSSL_USER_SETTINGS` test build, override +the policy probe include, for example with +`ECDSA_POLICY_OPTS='-DWOLFSSL_USER_SETTINGS -Ipath/to/settings'`. + +wolfCOSE also rejects local signing through wolfSSL dispatchers that do not +consume the key's deterministic nonce state: crypto callbacks that always +search or require callback-only ECC, STM32 PKA signing, ATECC508A/608A, +Microchip TA100, Pluton, CryptoCell, Silicon Labs SE acceleration, KCAPI, +SE050, and Cavium/Intel async ECC when the feature is enabled. A plain +crypto-callback build may use the software fallback, but a device-bound key is +rejected at runtime. The Xilinx Versal path remains permitted because wolfSSL +passes its RFC 6979-derived nonce to that hardware signer. + +Without `WOLFSSL_NO_MALLOC`, wolfSSL may allocate a temporary deterministic +nonce object per signature. In an ES256-only x86_64 macOS link, deterministic +support increased text and constants by approximately 7.1 KB. The exact cost +is platform and wolfSSL-configuration dependent, which is why the feature is +off by default. + ## Message Type Gates ### COSE_Sign1 (Single Signer) @@ -116,7 +173,7 @@ Per-algorithm opt-outs for the default (non-lean) build. Each also has a `WOLFCO | `WOLFCOSE_ENABLE_AESWRAP` | Opt in AES Key Wrap under `WOLFCOSE_LEAN` | - | | `WOLFCOSE_ENABLE_ECDH_ES` | Opt in ECDH-ES under `WOLFCOSE_LEAN` | - | -Resolved internally as read-only `WOLFCOSE_KEY_WRAP`, `WOLFCOSE_ECDH`, and `WOLFCOSE_ECDH_WRAP` gates. Requires the matching wolfSSL feature (`HAVE_AES_KEYWRAP`; `HAVE_ECC` + `HAVE_HKDF` for ECDH-ES) and at least one multi-recipient message type enabled. +Resolved internally as read-only `WOLFCOSE_KEY_WRAP`, `WOLFCOSE_ECDH`, and `WOLFCOSE_ECDH_WRAP` gates. Requires the matching wolfSSL feature (`HAVE_AES_KEYWRAP`; `HAVE_ECC` + `HAVE_HKDF` for ECDH-ES) and at least one multi-recipient message type enabled. AES Key Wrap also requires wolfSSL 5.9.0 or later; older releases used a comparison whose timing behavior depended on the compiler and `XMEMCMP` configuration. Define `WOLFCOSE_NO_AESWRAP` when building the otherwise supported wolfSSL 5.8.x series. --- @@ -309,6 +366,7 @@ wolfCOSE requires these wolfSSL features for full functionality: | wolfSSL Define | wolfCOSE Feature | |----------------|------------------| | `HAVE_ECC` | ECDSA signing (ES256/ES384/ES512), ECDH key agreement | +| `WOLFSSL_ECDSA_DETERMINISTIC_K` or `WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT` | Optional deterministic local ECDSA signing | | `HAVE_ED25519` | EdDSA signing (Ed25519) | | `HAVE_ED448` | EdDSA signing (Ed448) | | `WOLFSSL_HAVE_MLDSA` | ML-DSA post-quantum signing | diff --git a/docs/Release-Notes.md b/docs/Release-Notes.md index 03cb5fa..2ce136e 100644 --- a/docs/Release-Notes.md +++ b/docs/Release-Notes.md @@ -53,11 +53,24 @@ MAC, and key distribution, and standardized post-quantum ML-DSA signatures private RSA key round-trips reliably against strict RSA decoders. - `COSE_Mac` emits an empty protected header for direct-key recipients, matching the COSE structure other implementations expect on the wire. +- Multi-recipient Encrypt and MAC now require an explicit recipient algorithm, + enforce the RFC recipient-mode combinations, and require an empty bstr or + null for a direct recipient's ciphertext item. Direct encoders emit + `{1: -6}` in each recipient's unprotected map, adding two bytes per direct + recipient. +- RSA-PSS operations reject keys below the RFC 8230 minimum of 2048 bits. +- AES-CCM algorithms using a 13-byte nonce reject payloads above 65535 bytes, + matching the algorithm's two-byte message-length field. +- `wc_CoseKey_Decode` and `wc_CoseKey_PeekInfo` reject the optional `key_ops` + label with `WOLFCOSE_E_UNSUPPORTED`; operation arrays are not retained by + the fixed-size key wrapper. - `COSE_Key` emits preferred (shortest) CBOR length for the RSA `n` and `d` byte strings, keeping serialized keys deterministic. ### Improvements/Optimizations +- Optional RFC 6979 deterministic ECDSA signing through + `WOLFCOSE_ENABLE_DETERMINISTIC_ECDSA`. - Minimal footprint: an ES256 `COSE_Sign1` build is ~5.1 KB verify-only and ~6.8 KB sign + verify for the wolfCOSE COSE + CBOR engine. See [[Footprint]]. - MISRA C:2012 and C:2023 checked. See [[MISRA Compliance]]. diff --git a/examples/scenarios/group_broadcast_mac.c b/examples/scenarios/group_broadcast_mac.c index 7203d69..19ac171 100644 --- a/examples/scenarios/group_broadcast_mac.c +++ b/examples/scenarios/group_broadcast_mac.c @@ -374,7 +374,7 @@ int main(void) } /* Each subscriber verifies */ - for (i = 0; i < NUM_SUBSCRIBERS && ret == 0; i++) { + for (i = 0; (i < NUM_SUBSCRIBERS) && (ret == 0); i++) { ret = subscriber_verify_broadcast(i, macMsg, macMsgLen); if (ret == 0) { printf("\n"); diff --git a/examples/scenarios/iot_fleet_config.c b/examples/scenarios/iot_fleet_config.c index 36ebf30..2d15f7c 100644 --- a/examples/scenarios/iot_fleet_config.c +++ b/examples/scenarios/iot_fleet_config.c @@ -312,7 +312,7 @@ int main(void) } /* Each device decrypts */ - for (i = 0; i < NUM_DEVICES && ret == 0; i++) { + for (i = 0; (i < NUM_DEVICES) && (ret == 0); i++) { ret = device_decrypt_config(i, encryptedMsg, encryptedLen, plaintext, sizeof(plaintext), &plaintextLen); diff --git a/examples/sign1_mldsa.c b/examples/sign1_mldsa.c index 13c1367..f3f7494 100644 --- a/examples/sign1_mldsa.c +++ b/examples/sign1_mldsa.c @@ -92,9 +92,9 @@ int main(void) gScratch, sizeof(gScratch), &hdr, &payload, &payloadLen); } - if (ret == WOLFCOSE_SUCCESS && - payloadLen == (sizeof(PAYLOAD) - 1) && payload != NULL && - memcmp(payload, PAYLOAD, payloadLen) == 0) { + if ((ret == WOLFCOSE_SUCCESS) && + (payloadLen == (sizeof(PAYLOAD) - 1)) && (payload != NULL) && + (memcmp(payload, PAYLOAD, payloadLen) == 0)) { (void)printf("ML-DSA-44 verify: OK, payload = \"%.*s\"\n", (int)payloadLen, payload); rc = 0; diff --git a/examples/sign1_verify_mldsa.c b/examples/sign1_verify_mldsa.c index 5a2bfbf..2d53351 100644 --- a/examples/sign1_verify_mldsa.c +++ b/examples/sign1_verify_mldsa.c @@ -72,9 +72,10 @@ int main(void) &payload, &payloadLen); } - if (ret == WOLFCOSE_SUCCESS && - payloadLen == (sizeof(EXPECTED_PAYLOAD) - 1) && payload != NULL && - memcmp(payload, EXPECTED_PAYLOAD, payloadLen) == 0) { + if ((ret == WOLFCOSE_SUCCESS) && + (payloadLen == (sizeof(EXPECTED_PAYLOAD) - 1)) && + (payload != NULL) && + (memcmp(payload, EXPECTED_PAYLOAD, payloadLen) == 0)) { (void)printf("lean ML-DSA verify-only: COSE_Sign1 ML-DSA-44 verified, " "payload = \"%.*s\"\n", (int)payloadLen, payload); rc = 0; diff --git a/include/wolfcose/settings.h b/include/wolfcose/settings.h index 78119ef..052fd36 100644 --- a/include/wolfcose/settings.h +++ b/include/wolfcose/settings.h @@ -38,6 +38,7 @@ #define WOLFCOSE_SETTINGS_H #include +#include #ifdef __cplusplus extern "C" { @@ -354,6 +355,49 @@ extern "C" { #endif #endif +/* Exact enforcement of RFC 8230's 2048-bit RSA-PSS minimum needs access to + * the modulus at the byte boundary. Backend-enabled builds may also carry + * software keys, so verify-only builds must export the modulus. */ +#if defined(WOLFCOSE_HAVE_RSAPSS) && \ + defined(WOLFSSL_RSA_VERIFY_ONLY) && !defined(HAVE_ECC) && \ + !defined(WOLFSSL_EXPORT_INT) && \ + (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY)) + #error "wolfCOSE RSA-PSS key validation requires WOLFSSL_EXPORT_INT" +#endif + +/* Optional RFC 6979 deterministic ECDSA signing. */ +#if defined(WOLFCOSE_ENABLE_DETERMINISTIC_ECDSA) && \ + defined(WOLFCOSE_HAVE_ECDSA) && \ + (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN)) && \ + !defined(WOLFCOSE_HAVE_DETERMINISTIC_ECDSA) + #if !defined(WOLFSSL_ECDSA_DETERMINISTIC_K) && \ + !defined(WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT) + #error "WOLFCOSE_ENABLE_DETERMINISTIC_ECDSA requires wolfSSL support" + #endif + + #define WOLFCOSE_HAVE_DETERMINISTIC_ECDSA + + /* These wolfSSL sign dispatchers do not consume the key's deterministic + * nonce state. Fail closed rather than silently bypassing the policy. The + * Xilinx Versal path is not listed because it passes the derived nonce to + * its hardware signer. */ + #if (defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_FIND)) || \ + defined(WOLF_CRYPTO_CB_ONLY_ECC) || \ + (defined(WOLFSSL_STM32_PKA) && \ + !defined(WC_STM32_PKA_VERIFY_ONLY)) || \ + defined(WOLFSSL_ATECC508A) || defined(WOLFSSL_ATECC608A) || \ + defined(WOLFSSL_MICROCHIP_TA100) || defined(PLUTON_CRYPTO_ECC) || \ + defined(WOLFSSL_CRYPTOCELL) || defined(WOLFSSL_SILABS_SE_ACCEL) || \ + defined(WOLFSSL_KCAPI_ECC) || \ + (defined(WOLFSSL_SE050) && \ + !defined(WOLFSSL_SE050_ONLY_KEY_ID)) || \ + (defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_ECC) && \ + (defined(HAVE_CAVIUM_V) || defined(HAVE_INTEL_QA))) + #error "ECDSA backend does not support deterministic signing" + #endif +#endif + /* COSE_Encrypt multi-recipient — extension */ #if defined(WOLFCOSE_ENABLE_ENCRYPT) #define WOLFCOSE_ENCRYPT_WANT @@ -398,6 +442,12 @@ extern "C" { defined(HAVE_AES_KEYWRAP) #define WOLFCOSE_WANT_KEY_WRAP #endif +#if defined(WOLFCOSE_WANT_KEY_WRAP) && \ + (defined(WOLFCOSE_ENCRYPT) || defined(WOLFCOSE_MAC)) && \ + !defined(WOLFCOSE_NO_RECIPIENTS) && \ + (LIBWOLFSSL_VERSION_HEX < 0x05009000) + #error "wolfCOSE AES Key Wrap requires wolfSSL 5.9.0 or later" +#endif /* ECDH-ES — extension */ #if defined(WOLFCOSE_ENABLE_ECDH_ES) diff --git a/include/wolfcose/wolfcose.h b/include/wolfcose/wolfcose.h index df19349..20ab12e 100644 --- a/include/wolfcose/wolfcose.h +++ b/include/wolfcose/wolfcose.h @@ -210,6 +210,10 @@ extern "C" { #define WOLFCOSE_ALG_A192KW (-4) /* AES-192 Key Wrap */ #define WOLFCOSE_ALG_A256KW (-5) /* AES-256 Key Wrap */ #define WOLFCOSE_ALG_DIRECT (-6) /* Direct use of CEK */ +#define WOLFCOSE_ALG_DIRECT_HKDF_SHA_256 (-10) /* Direct + HKDF-SHA-256 */ +#define WOLFCOSE_ALG_DIRECT_HKDF_SHA_512 (-11) /* Direct + HKDF-SHA-512 */ +#define WOLFCOSE_ALG_DIRECT_HKDF_AES_128 (-12) /* Direct + HKDF-AES-128 */ +#define WOLFCOSE_ALG_DIRECT_HKDF_AES_256 (-13) /* Direct + HKDF-AES-256 */ #define WOLFCOSE_ALG_ECDH_ES_HKDF_256 (-25) /* ECDH-ES + HKDF-256 */ #define WOLFCOSE_ALG_ECDH_ES_HKDF_512 (-26) /* ECDH-ES + HKDF-512 */ #define WOLFCOSE_ALG_ECDH_SS_HKDF_256 (-27) /* ECDH-SS + HKDF-256 */ @@ -258,6 +262,7 @@ extern "C" { #define WOLFCOSE_KEY_LABEL_KTY 1 #define WOLFCOSE_KEY_LABEL_KID 2 #define WOLFCOSE_KEY_LABEL_ALG 3 +#define WOLFCOSE_KEY_LABEL_KEY_OPS 4 #define WOLFCOSE_KEY_LABEL_CRV (-1) #define WOLFCOSE_KEY_LABEL_X (-2) #define WOLFCOSE_KEY_LABEL_Y (-3) @@ -313,12 +318,14 @@ typedef struct WOLFCOSE_HDR { size_t ivLen; /**< IV length */ const uint8_t* partialIv; /**< Partial IV pointer */ size_t partialIvLen; /**< Partial IV length */ - int32_t contentType; /**< Content type, 0 if absent */ + int32_t contentType; /**< Content type from either header bucket */ uint8_t flags; /**< Header flags (see WOLFCOSE_HDR_FLAG_*) */ } WOLFCOSE_HDR; /** \brief Flag indicating payload is detached (RFC 9052 Section 2) */ #define WOLFCOSE_HDR_FLAG_DETACHED 0x01u +/** \brief Flag indicating an unprotected content-type label was present */ +#define WOLFCOSE_HDR_FLAG_CONTENT_TYPE_UNPROTECTED 0x02u /** * \brief Caller-supplied signature callback (RFC 9052 Section 4.4). @@ -992,6 +999,8 @@ typedef struct WOLFCOSE_KEY_INFO { * (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. * * \param in Input CBOR COSE_Key buffer. * \param inSz Input buffer size; must be exactly the encoded length. @@ -1009,6 +1018,14 @@ WOLFCOSE_API int wc_CoseKey_PeekInfo(const uint8_t* in, size_t inSz, * assigning key.* directly records no type and imports nothing. * A decoded kty/crv that does not match the attached type returns * WOLFCOSE_E_COSE_KEY_TYPE before any import runs. + * Keys containing key_ops return WOLFCOSE_E_UNSUPPORTED before any + * key material is imported. + * An attached ECC object receiving private EC2 material must be + * freshly initialized, with no existing curve or key material. + * When wolfCrypt's private importer uses an unsupported math layout + * or a non-transactional callback or hardware backend, private EC2 + * decode returns WOLFCOSE_E_UNSUPPORTED before importing key + * material. * \param key Key structure (should be initialized, with wolfCrypt key * attached for asymmetric types). * \param in Input CBOR buffer. @@ -1022,11 +1039,15 @@ WOLFCOSE_API int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, /* ----- COSE_Sign1 API (RFC 9052 Section 4.3) ----- */ +/* All message APIs require extAadLen to be zero when extAad is NULL. */ + #if defined(WOLFCOSE_SIGN1_SIGN) /** * \brief Sign a payload producing a COSE_Sign1 message (RFC 9052 Section 4.3). * - * \param key WOLFCOSE_KEY with hasPrivate=1. Caller retains ownership. + * \param key WOLFCOSE_KEY with hasPrivate=1, or an external + * signing callback when enabled. Caller retains + * ownership. * \param alg Algorithm identifier (WOLFCOSE_ALG_ES256, etc). * \param kid Key ID to include in unprotected headers (NULL if none). * \param kidLen Key ID length. @@ -1042,7 +1063,8 @@ WOLFCOSE_API int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, * \param out Output buffer. * \param outSz Output buffer size. * \param outLen Output: bytes written to out. - * \param rng Initialized WC_RNG. + * \param rng Initialized WC_RNG for local signing; may be NULL + * when an external signing callback is configured. * \return WOLFCOSE_SUCCESS or negative error code. */ WOLFCOSE_API int wc_CoseSign1_Sign(WOLFCOSE_KEY* key, int32_t alg, @@ -1338,9 +1360,11 @@ WOLFCOSE_API int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, * Creates a COSE_Encrypt structure: * COSE_Encrypt = [Headers, ciphertext, recipients : [+ COSE_recipient]] * - * Currently supports direct key mode where the content encryption key (CEK) - * is the same for all recipients (pre-shared). The recipients array contains - * header-only entries identifying which key is used. + * Depending on build configuration, recipient key management supports direct, + * AES Key Wrap, and ECDH-ES direct key agreement. Direct mode uses a + * pre-shared content encryption key (CEK). AES Key Wrap generates one CEK and + * wraps it separately for each recipient. ECDH-ES derives the CEK for one + * recipient and puts the ephemeral public key in the unprotected header. * * \param recipients Array of WOLFCOSE_RECIPIENT with keys. * \param recipientCount Number of recipients (must be >= 1). @@ -1361,7 +1385,8 @@ WOLFCOSE_API int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, * \param out Output buffer. * \param outSz Output buffer size. * \param outLen Output: bytes written to out. - * \param rng Initialized WC_RNG (for future CEK generation). + * \param rng Initialized WC_RNG for AES Key Wrap or ECDH-ES; + * may be NULL for direct mode. * \return WOLFCOSE_SUCCESS or negative error code. */ WOLFCOSE_API int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, @@ -1381,6 +1406,12 @@ WOLFCOSE_API int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, * \brief Decrypt a COSE_Encrypt message. * * Decrypts using the key from the specified recipient entry. + * Every on-wire recipient must declare its key-management algorithm, all + * top-level sibling recipients must use an RFC-compatible key-distribution + * mode, and a direct recipient's ciphertext item must be an empty bstr or + * null. Selecting a recipient that contains nested recipients is unsupported. + * An unprotected body algorithm is accepted only when recipient->key->alg + * pins the same value. * * \param recipient WOLFCOSE_RECIPIENT with decryption key. * \param recipientIndex 0-based index of recipient to use. @@ -1453,6 +1484,11 @@ WOLFCOSE_API int wc_CoseMac_Create(const WOLFCOSE_RECIPIENT* recipients, * \brief Verify a COSE_Mac message. * * Verifies using the key from the specified recipient entry. + * Every on-wire recipient must declare a direct key-distribution algorithm, + * all top-level sibling recipients must use direct mode, and the recipient + * ciphertext item must be an empty bstr or null. Selecting a recipient that + * contains nested recipients is unsupported. An unprotected body algorithm is + * accepted only when recipient->key->alg pins the same value. * * \param recipient WOLFCOSE_RECIPIENT with MAC key. * \param recipientIndex 0-based index of recipient to use. diff --git a/scripts/check_house_style.sh b/scripts/check_house_style.sh index 2914b22..a6acc74 100755 --- a/scripts/check_house_style.sh +++ b/scripts/check_house_style.sh @@ -38,6 +38,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" +# Examples follow the documented explicit-precedence style: each operand in a +# logical-AND condition is parenthesized independently. +git grep -nE '^[[:space:]]*if \([^()].*&&' -- $PATHS | \ + grep '^examples/' | \ + report "parenthesize each && operand in example if conditions" +git grep -nE '^[[:space:]]*for \([^;]*; [^()].*&&' -- $PATHS | \ + grep '^examples/' | \ + report "parenthesize each && operand in example loop conditions" + if [ "$status" -ne 0 ]; then printf '\nHouse-style check failed.\n' exit 1 diff --git a/scripts/check_zero_alloc.sh b/scripts/check_zero_alloc.sh new file mode 100644 index 0000000..c4c674c --- /dev/null +++ b/scripts/check_zero_alloc.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +set -eu + +scan_status=0 +allocators='malloc|calloc|realloc|free|strdup|alloca|aligned_alloc' +allocators="${allocators}|reallocarray|valloc|XMALLOC|XCALLOC|XREALLOC|XFREE" +allocator_call="(^|[^[:alnum:]_])(${allocators})[[:space:]]*[(]" +allocator_ref="([=(&,{]|return[[:space:]]+)[[:space:]]*" +allocator_ref="${allocator_ref}(&[[:space:]]*)?(${allocators})[[:space:]]*[,;})]" +grep -REn --include='*.c' --include='*.h' \ + -e "$allocator_call" -e "$allocator_ref" \ + src include tests tools examples || scan_status=$? + +if [ "$scan_status" -eq 0 ]; then + echo "FAIL: dynamic allocation call found" + exit 1 +fi + +if [ "$scan_status" -ne 1 ]; then + echo "FAIL: dynamic allocation scan failed" + exit "$scan_status" +fi + +echo "PASS: no dynamic allocation calls" diff --git a/scripts/classify_misra.py b/scripts/classify_misra.py new file mode 100644 index 0000000..9ac6f90 --- /dev/null +++ b/scripts/classify_misra.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 +"""Classify cppcheck MISRA output without suppressing any rule.""" + +import argparse +import hashlib +import json +from pathlib import Path +import re +import sys + + +DIAGNOSTIC_RE = re.compile( + r"^(?P[^:]+):(?P[0-9]+):(?P[0-9]+):.*" + r"\[misra-c2012-(?P[0-9]+\.[0-9]+)\]$" +) +DEFINE_RE = re.compile(r"^\s*#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)\b") + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--input", required=True) + parser.add_argument("--unresolved", required=True) + parser.add_argument("--deviated", required=True) + parser.add_argument("--limitations", required=True) + return parser.parse_args() + + +def source_lines(root, relpath): + return (root / relpath).read_text(encoding="utf-8").splitlines() + + +def find_anchor_lines(root, deviation): + lines = source_lines(root, deviation["path"]) + symbol_re = re.compile(r"\b" + re.escape(deviation["symbol"]) + r"\b") + if not any(symbol_re.search(line) for line in lines): + raise ValueError(f"{deviation['id']}: symbol not found") + found = [] + for anchor in deviation["anchors"]: + matches = [ + idx + 1 for idx, line in enumerate(lines) + if line.strip() == anchor.strip() + ] + if len(matches) != 1: + raise ValueError( + f"{deviation['id']}: anchor must occur once: {anchor!r}" + ) + found.append(matches[0]) + if len(found) != deviation["expected"]: + raise ValueError(f"{deviation['id']}: expected count does not match anchors") + if "range_sha256" in deviation: + first = min(found) - 1 + last = max(found) + source_range = "\n".join(lines[first:last]) + "\n" + digest = hashlib.sha256(source_range.encode("utf-8")).hexdigest() + if digest != deviation["range_sha256"]: + raise ValueError(f"{deviation['id']}: approved source range changed") + return found + + +def macro_has_directive_use(root, macro, definition_path, definition_line): + directive_re = re.compile(r"^\s*#\s*(?:if|ifdef|ifndef|elif)\b") + token_re = re.compile(r"\b" + re.escape(macro) + r"\b") + + for base in ("include", "src", "tests", "examples"): + for path in (root / base).rglob("*"): + if path.suffix not in (".c", ".h"): + continue + relpath = path.relative_to(root).as_posix() + for lineno, line in enumerate( + path.read_text(encoding="utf-8").splitlines(), start=1 + ): + if (relpath == definition_path) and (lineno == definition_line): + continue + if ( + (relpath == definition_path) + and (lineno + 1 == definition_line) + and re.match( + r"^\s*#\s*ifndef\s+" + re.escape(macro) + r"\s*$", + line, + ) + ): + continue + if directive_re.match(line) and token_re.search(line): + return f"{relpath}:{lineno}" + return None + + +def write_report(path, lines): + text = "".join(line + "\n" for line in lines) + Path(path).write_text(text, encoding="utf-8") + + +def main(): + args = parse_args() + # Remove stale results before any validation that can raise. The workflow + # records this process's exit status separately and never treats empty + # reports from an aborted classification as a clean result. + write_report(args.unresolved, []) + write_report(args.deviated, []) + write_report(args.limitations, []) + + root = Path(__file__).resolve().parent.parent + manifest_path = root / "scripts" / "misra-deviations.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + if manifest.get("schema") != 1: + raise ValueError("unsupported MISRA deviation manifest schema") + + deviation_locations = {} + expected_deviations = set() + for deviation in manifest["deviations"]: + for line in find_anchor_lines(root, deviation): + key = (deviation["path"], line, deviation["rule"]) + if key in deviation_locations: + raise ValueError(f"duplicate deviation location: {key}") + deviation_locations[key] = deviation["id"] + expected_deviations.add(key) + + unresolved = [] + deviated = [] + limitations = [] + observed_deviations = set() + + for raw_line in Path(args.input).read_text(encoding="utf-8").splitlines(): + match = DIAGNOSTIC_RE.match(raw_line) + if match is None: + unresolved.append(raw_line) + continue + + relpath = match.group("path") + lineno = int(match.group("line")) + rule = match.group("rule") + key = (relpath, lineno, rule) + if key in deviation_locations: + deviated.append(f"{deviation_locations[key]}: {raw_line}") + observed_deviations.add(key) + continue + + if rule == "2.5": + lines = source_lines(root, relpath) + if (lineno <= 0) or (lineno > len(lines)): + unresolved.append(raw_line) + continue + define_match = DEFINE_RE.match(lines[lineno - 1]) + if define_match is not None: + macro = define_match.group(1) + evidence = macro_has_directive_use(root, macro, relpath, lineno) + if evidence is not None: + limitations.append(f"{raw_line} [directive use: {evidence}]") + continue + + unresolved.append(raw_line) + + stale = sorted(expected_deviations - observed_deviations) + if stale: + for relpath, lineno, rule in stale: + unresolved.append( + f"{relpath}:{lineno}:0: stale approved deviation " + f"[misra-c2012-{rule}]" + ) + + write_report(args.unresolved, unresolved) + write_report(args.deviated, deviated) + write_report(args.limitations, limitations) + return 1 if unresolved else 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except (OSError, ValueError, KeyError, json.JSONDecodeError) as error: + print(f"MISRA classifier error: {error}", file=sys.stderr) + sys.exit(2) diff --git a/scripts/misra-deviations.json b/scripts/misra-deviations.json new file mode 100644 index 0000000..3a6dfc2 --- /dev/null +++ b/scripts/misra-deviations.json @@ -0,0 +1,27 @@ +{ + "schema": 1, + "deviations": [ + { + "id": "D-11.5-001", + "rule": "11.5", + "path": "src/wolfcose.c", + "symbol": "wolfCose_ForceZero", + "anchors": [ + "volatile unsigned char* p = (volatile unsigned char*)mem;" + ], + "expected": 1 + }, + { + "id": "D-19.2-001", + "rule": "19.2", + "path": "include/wolfcose/wolfcose.h", + "symbol": "WOLFCOSE_KEY", + "anchors": [ + " union {", + " } key;" + ], + "expected": 2, + "range_sha256": "5ea3030e1870253ae4805cb405bf516807cac046c242467fe45648727321b261" + } + ] +} diff --git a/src/wolfcose.c b/src/wolfcose.c index e448af2..990c3d0 100644 --- a/src/wolfcose.c +++ b/src/wolfcose.c @@ -94,6 +94,245 @@ WOLFCOSE_LOCAL void wolfCose_ForceZero(void* mem, size_t len) #endif } +#if defined(WOLFCOSE_HAVE_MLDSA) && defined(WOLFCOSE_KEY_DECODE) && \ + !defined(WOLFSSL_MLDSA_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLDSA_ASSIGN_KEY) +/* Clear a failed software import without freeing the caller-owned key object. + * SetParams releases dynamic key buffers and caches while preserving heap, + * device and private-key identifier configuration. Wipe the private buffer + * here as well so cleanup does not depend on allocator behavior; static key + * buffers need it because wolfCrypt deliberately retains their storage. */ +static void wolfCose_MlDsaImportRollback(wc_MlDsaKey* key, byte level) +{ +#if defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) + if ((key->k != NULL) && (key->kSz > 0u)) { + (void)wolfCose_ForceZero(key->k, (size_t)key->kSz); + } +#else + (void)wolfCose_ForceZero(key->k, sizeof(key->k)); +#endif +#if defined(WC_MLDSA_FIXED_ARRAY) && \ + defined(WC_MLDSA_CACHE_PRIV_VECTORS) + (void)wolfCose_ForceZero(key->s1, sizeof(key->s1)); + (void)wolfCose_ForceZero(key->s2, sizeof(key->s2)); + (void)wolfCose_ForceZero(key->t0, sizeof(key->t0)); +#endif +#ifndef USE_INTEL_SPEEDUP + wc_Shake256_Free(&key->shake); +#endif + (void)wolfCose_ForceZero(&key->shake, sizeof(key->shake)); + (void)wc_MlDsaKey_SetParams(key, level); +} +#endif /* MLDSA && KEY_DECODE && !NO_MAKE_KEY && !ASSIGN_KEY */ + +#if defined(HAVE_ECC) && defined(WOLFCOSE_KEY_DECODE) +/* Clear initialized multiprecision storage without releasing it. wolfSSL's + * MP cleanup symbols are not exported by every supported build. */ +#if !defined(ALT_ECC_SIZE) && !defined(WOLFSSL_SP_MATH_ALL) && \ + !defined(WOLFSSL_SP_MATH) && !defined(USE_FAST_MATH) +static void wolfCose_MpClearDigits(mp_digit* digits, size_t digitCount) +{ + if ((digits != NULL) && (digitCount > 0u) && + (digitCount <= ((size_t)-1 / sizeof(digits[0])))) { + (void)wolfCose_ForceZero(digits, + digitCount * sizeof(digits[0])); + } +} +#endif + +#ifdef ALT_ECC_SIZE +/* ALT_ECC_SIZE points ecc_key members at smaller alt_fp_int objects. Access + * that real layout directly so a full-size mp_int view cannot run past it. */ +static int wolfCose_AltMpValueIsZero(const alt_fp_int* value) +{ + int isZero = 0; + + if ((value != NULL) && ((size_t)value->used == 0u)) { + isZero = 1; + } + + return isZero; +} + +static void wolfCose_AltMpClearValue(alt_fp_int* value) +{ + if (value != NULL) { + (void)wolfCose_ForceZero(value->dp, sizeof(value->dp)); +#if defined(HAVE_WOLF_BIGINT) && !defined(USE_FAST_MATH) + if ((value->raw.buf != NULL) && (value->raw.len > 0u)) { + (void)wolfCose_ForceZero(value->raw.buf, value->raw.len); + } +#endif + value->used = 0; +#if defined(USE_FAST_MATH) || defined(WOLFSSL_SP_INT_NEGATIVE) + value->sign = MP_ZPOS; +#endif + } +} +#else +static int wolfCose_MpValueIsZero(const mp_int* value) +{ + int isZero = 0; + + if ((value != NULL) && ((size_t)value->used == 0u)) { + isZero = 1; + } + + return isZero; +} + +static void wolfCose_MpClearValue(mp_int* value) +{ + if (value != NULL) { +#if defined(WOLFSSL_SP_MATH_ALL) || defined(WOLFSSL_SP_MATH) || \ + defined(USE_FAST_MATH) + (void)wolfCose_ForceZero(value->dp, sizeof(value->dp)); +#else + if (value->alloc > 0) { + wolfCose_MpClearDigits(value->dp, (size_t)value->alloc); + } +#endif +#ifdef HAVE_WOLF_BIGINT + if ((value->raw.buf != NULL) && (value->raw.len > 0u)) { + (void)wolfCose_ForceZero(value->raw.buf, value->raw.len); + } +#endif + value->used = 0; +#if defined(USE_FAST_MATH) || defined(USE_INTEGER_HEAP_MATH) || \ + defined(WOLFSSL_SP_INT_NEGATIVE) + value->sign = MP_ZPOS; +#endif + } +} +#endif /* ALT_ECC_SIZE */ + +static int wolfCose_EccPrivateValuesAreZero(const ecc_key* ecc) +{ + int areZero; + +#ifdef ALT_ECC_SIZE + areZero = wolfCose_AltMpValueIsZero(&ecc->ka[0]); + #ifdef WOLFSSL_ECC_BLIND_K + if (wolfCose_AltMpValueIsZero(&ecc->kba[0]) == 0) { + areZero = 0; + } + if (wolfCose_AltMpValueIsZero(&ecc->kua[0]) == 0) { + areZero = 0; + } + #endif +#else + areZero = wolfCose_MpValueIsZero(ecc->k); + #ifdef WOLFSSL_ECC_BLIND_K + if (wolfCose_MpValueIsZero(ecc->kb) == 0) { + areZero = 0; + } + if (wolfCose_MpValueIsZero(ecc->ku) == 0) { + areZero = 0; + } + #endif +#endif + + return areZero; +} + +static void wolfCose_EccClearPrivateValues(ecc_key* ecc) +{ +#ifdef ALT_ECC_SIZE + wolfCose_AltMpClearValue(&ecc->ka[0]); + #ifdef WOLFSSL_ECC_BLIND_K + wolfCose_AltMpClearValue(&ecc->kba[0]); + wolfCose_AltMpClearValue(&ecc->kua[0]); + #endif +#else + wolfCose_MpClearValue(ecc->k); + #ifdef WOLFSSL_ECC_BLIND_K + wolfCose_MpClearValue(ecc->kb); + wolfCose_MpClearValue(ecc->ku); + #endif +#endif +} + +static int wolfCose_EccPrivateImportSupported(const ecc_key* ecc) +{ + int supported = 1; + + (void)ecc; + +#ifdef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED + supported = 0; +#else + #if defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_SETKEY) + if (ecc->devId != INVALID_DEVID) { + supported = 0; + } + #endif +#endif + + return supported; +} + +typedef struct WOLFCOSE_ECC_IMPORT_STATE { + int type; + int idx; + int state; + const ecc_set_type* dp; +} WOLFCOSE_ECC_IMPORT_STATE; + +static int wolfCose_EccPrivateImportBegin(ecc_key* ecc, + WOLFCOSE_ECC_IMPORT_STATE* saved) +{ + int ret = WOLFCOSE_SUCCESS; + + saved->type = ecc->type; + saved->idx = ecc->idx; + saved->state = ecc->state; + saved->dp = ecc->dp; + + /* wolfCrypt has no transactional ECC-key replacement API. Require an + * initialized but empty destination so a failed import cannot overwrite + * caller-owned key or custom-curve state. */ + if ((saved->type != 0) || (saved->idx != 0) || (saved->state != 0) || + (saved->dp != NULL) +#ifdef WOLFSSL_CUSTOM_CURVES + || (ecc->deallocSet != 0) +#endif + ) { + ret = WOLFCOSE_E_INVALID_ARG; + } +#ifdef ALT_ECC_SIZE + else if (((const void*)ecc->k != (const void*)&ecc->ka[0]) + #ifdef WOLFSSL_ECC_BLIND_K + || ((const void*)ecc->kb != (const void*)&ecc->kba[0]) + || ((const void*)ecc->ku != (const void*)&ecc->kua[0]) + #endif + ) { + ret = WOLFCOSE_E_INVALID_ARG; + } +#endif + else if (wolfCose_EccPrivateValuesAreZero(ecc) == 0) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (wolfCose_EccPrivateImportSupported(ecc) == 0) { + ret = WOLFCOSE_E_UNSUPPORTED; + } + else { + /* No action required */ + } + + return ret; +} + +static void wolfCose_EccPrivateImportRollback(ecc_key* ecc, + const WOLFCOSE_ECC_IMPORT_STATE* saved) +{ + wolfCose_EccClearPrivateValues(ecc); + ecc->type = saved->type; + ecc->idx = saved->idx; + ecc->state = saved->state; + ecc->dp = saved->dp; +} +#endif /* HAVE_ECC && WOLFCOSE_KEY_DECODE */ + /* On a failed verify/decrypt, clear the header so unauthenticated metadata is * not exposed to callers that inspect hdr without gating on the return code. */ #if defined(WOLFCOSE_SIGN1_VERIFY) || defined(WOLFCOSE_SIGN_VERIFY) || \ @@ -322,6 +561,47 @@ int wolfCose_CrvToWcCurve(int32_t crv, int* wcCrv) } return ret; } + +static int wolfCose_EccKeyCheckCurve(int32_t crv, ecc_key* eccKey) +{ + int ret = WOLFCOSE_SUCCESS; + int expectedCrv = ECC_CURVE_INVALID; + int actualSz = 0; + size_t expectedSz = 0; + + if (eccKey == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CrvToWcCurve(crv, &expectedCrv); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CrvKeySize(crv, &expectedSz); + } + if (ret == WOLFCOSE_SUCCESS) { + actualSz = wc_ecc_size(eccKey); + /* An initialized, empty key may be attached as a decode target. */ + if (actualSz != 0) { + if ((actualSz < 0) || ((size_t)actualSz != expectedSz)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + } + } + if ((ret == WOLFCOSE_SUCCESS) && (actualSz != 0) && + (eccKey->dp != NULL)) { + int actualCrv = wc_ecc_get_curve_id(eccKey->idx); + + if (actualCrv == (int)ECC_CURVE_INVALID) { + actualCrv = wc_ecc_get_curve_id_from_dp_params(eccKey->dp); + } + if ((actualCrv == (int)ECC_CURVE_INVALID) || + (actualCrv != expectedCrv)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + } + + return ret; +} #endif /* ----- Internal: AEAD dispatch helpers (AES-GCM, ChaCha20-Poly1305, AES-CCM) ----- */ @@ -459,6 +739,32 @@ int wolfCose_AeadTagLen(int32_t alg, size_t* tagLen) return ret; } +#if defined(WOLFCOSE_HAVE_AESCCM) && \ + (defined(WOLFCOSE_ENCRYPT0_ENCRYPT) || \ + defined(WOLFCOSE_ENCRYPT0_DECRYPT) || \ + defined(WOLFCOSE_ENCRYPT_ENCRYPT) || \ + defined(WOLFCOSE_ENCRYPT_DECRYPT)) +static int wolfCose_AeadCheckPayloadLen(int32_t alg, size_t payloadLen) +{ + int ret = WOLFCOSE_SUCCESS; + + switch (alg) { + case WOLFCOSE_ALG_AES_CCM_16_64_128: /* fall through */ + case WOLFCOSE_ALG_AES_CCM_16_64_256: /* fall through */ + case WOLFCOSE_ALG_AES_CCM_16_128_128: /* fall through */ + case WOLFCOSE_ALG_AES_CCM_16_128_256: + if (payloadLen > (size_t)UINT16_MAX) { + ret = WOLFCOSE_E_INVALID_ARG; + } + break; + default: + break; + } + + return ret; +} +#endif + /* ----- Internal: HMAC helpers ----- */ #if defined(WOLFCOSE_HAVE_HMAC) @@ -530,6 +836,12 @@ static int wolfCose_HmacCheckKeyLen(int32_t alg, size_t keyLen) #else (void)alg; (void)keyLen; +#endif +#if defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) + if ((ret == WOLFCOSE_SUCCESS) && + (keyLen > (size_t)0xFFFFFFFFUL)) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } #endif return ret; } @@ -542,13 +854,18 @@ static int wolfCose_HmacCheckKeyLen(int32_t alg, size_t keyLen) #if defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN_SIGN) int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, uint8_t* sigBuf, size_t* sigLen, - size_t coordSz, WC_RNG* rng, ecc_key* eccKey) + size_t coordSz, enum wc_HashType hashType, + WC_RNG* rng, ecc_key* eccKey) { int ret; uint8_t derSig[ECC_MAX_SIG_SIZE]; word32 derSigLen = (word32)sizeof(derSig); word32 rLen; word32 sLen; +#ifdef WOLFCOSE_HAVE_DETERMINISTIC_ECDSA + byte savedDeterministic = 0u; + enum wc_HashType savedHashType = WC_HASH_TYPE_NONE; +#endif if ((hash == NULL) || (sigBuf == NULL) || (sigLen == NULL) || (rng == NULL) || (eccKey == NULL)) { @@ -558,14 +875,41 @@ int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, ret = WOLFCOSE_E_BUFFER_TOO_SMALL; } else { +#ifdef WOLFCOSE_HAVE_DETERMINISTIC_ECDSA + savedDeterministic = (byte)eccKey->deterministic; + savedHashType = eccKey->hashType; +#if defined(WOLF_CRYPTO_CB) && !defined(WOLF_CRYPTO_CB_FIND) + if (eccKey->devId != INVALID_DEVID) { + ret = WOLFCOSE_E_UNSUPPORTED; + } + else +#endif +#if defined(WOLFSSL_SE050) && defined(WOLFSSL_SE050_ONLY_KEY_ID) + if (eccKey->keyIdSet != 0) { + ret = WOLFCOSE_E_UNSUPPORTED; + } + else +#endif + { + ret = wc_ecc_set_deterministic_ex(eccKey, 1u, hashType); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } +#else + (void)hashType; + ret = WOLFCOSE_SUCCESS; +#endif /* Sign producing DER-encoded signature */ - INJECT_FAILURE(WOLF_FAIL_ECC_SIGN, -1, - ret = wc_ecc_sign_hash(hash, (word32)hashLen, derSig, &derSigLen, - rng, eccKey)); - if (ret != 0) { - ret = WOLFCOSE_E_CRYPTO; + if (ret == WOLFCOSE_SUCCESS) { + INJECT_FAILURE(WOLF_FAIL_ECC_SIGN, -1, + ret = wc_ecc_sign_hash(hash, (word32)hashLen, derSig, + &derSigLen, rng, eccKey)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } } - else { + if (ret == WOLFCOSE_SUCCESS) { /* Extract raw r and s from DER */ rLen = (word32)coordSz; sLen = (word32)coordSz; @@ -599,6 +943,10 @@ int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, } } (void)wolfCose_ForceZero(derSig, sizeof(derSig)); +#ifdef WOLFCOSE_HAVE_DETERMINISTIC_ECDSA + eccKey->deterministic = savedDeterministic; + eccKey->hashType = savedHashType; +#endif } return ret; } @@ -847,6 +1195,7 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, size_t i; int64_t label; int64_t intVal; + uint64_t contentTypeVal; uint32_t critLabels = 0u; int skipped; @@ -942,13 +1291,13 @@ int wolfCose_DecodeProtectedHdr(const uint8_t* data, size_t dataLen, ret = wc_CBOR_Skip(&ctx); } else { - ret = wc_CBOR_DecodeInt(&ctx, &intVal); + ret = wc_CBOR_DecodeUint(&ctx, &contentTypeVal); if ((ret == WOLFCOSE_SUCCESS) && - (wolfCose_InInt32Range(intVal) == 0)) { + (contentTypeVal > (uint64_t)INT32_MAX)) { ret = WOLFCOSE_E_COSE_BAD_HDR; } if (ret == WOLFCOSE_SUCCESS) { - hdr->contentType = (int32_t)intVal; + hdr->contentType = (int32_t)contentTypeVal; } } } @@ -1099,6 +1448,25 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, } } } + else if ((ret == WOLFCOSE_SUCCESS) && + (label == WOLFCOSE_HDR_CONTENT_TYPE)) { + hdr->flags |= WOLFCOSE_HDR_FLAG_CONTENT_TYPE_UNPROTECTED; + if ((ctx->idx < ctx->bufSz) && + (wc_CBOR_PeekType(ctx) == WOLFCOSE_CBOR_TSTR)) { + ret = wc_CBOR_Skip(ctx); + } + else { + uint64_t contentTypeVal; + ret = wc_CBOR_DecodeUint(ctx, &contentTypeVal); + if ((ret == WOLFCOSE_SUCCESS) && + (contentTypeVal > (uint64_t)INT32_MAX)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + if (ret == WOLFCOSE_SUCCESS) { + hdr->contentType = (int32_t)contentTypeVal; + } + } + } else { if (ret == WOLFCOSE_SUCCESS) { ret = wc_CBOR_Skip(ctx); @@ -1112,12 +1480,202 @@ int wolfCose_DecodeUnprotectedHdr(WOLFCOSE_CBOR_CTX* ctx, WOLFCOSE_HDR* hdr, ret = WOLFCOSE_E_COSE_BAD_HDR; } - if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_HdrStateMerge(hdrState, &unprotState); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_HdrStateMerge(hdrState, &unprotState); + } + } + return ret; +} + +#if defined(WOLFCOSE_SIGN_VERIFY) || defined(WOLFCOSE_ENCRYPT_DECRYPT) || \ + defined(WOLFCOSE_MAC_VERIFY) +/* Decode only the algorithm from an unselected header map. Other labels and + * values are intentionally left to the application that selected the entry. */ +static int wolfCose_DecodeSkippedHdrAlg(WOLFCOSE_CBOR_CTX* ctx, + int32_t* alg, int* algFound) +{ + int ret; + size_t mapCount = 0u; + size_t i; + + if ((ctx == NULL) || (alg == NULL) || (algFound == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + ret = wc_CBOR_DecodeMapStart(ctx, &mapCount); + } + if ((ret == WOLFCOSE_SUCCESS) && (mapCount > ctx->bufSz)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < mapCount); i++) { + WOLFCOSE_CBOR_LABEL label; + + ret = wc_CBOR_DecodeLabel(ctx, &label); + if ((ret == WOLFCOSE_SUCCESS) && + (wc_CBOR_LabelIsInt(&label, WOLFCOSE_HDR_ALG) != 0)) { + if (*algFound != 0) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + int64_t algVal; + + *algFound = 1; + if ((ctx->idx < ctx->bufSz) && + (wc_CBOR_PeekType(ctx) == WOLFCOSE_CBOR_TSTR)) { + ret = wc_CBOR_Skip(ctx); + } + else { + ret = wc_CBOR_DecodeInt(ctx, &algVal); + if ((ret == WOLFCOSE_SUCCESS) && + (wolfCose_InInt32Range(algVal) == 0)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + if (ret == WOLFCOSE_SUCCESS) { + *alg = (int32_t)algVal; + } + } + } + } + else if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_Skip(ctx); + } + else { + /* No action required */ + } + } + + return ret; +} + +/* Decode the three fields shared by COSE_Signature and COSE_recipient. */ +static int wolfCose_DecodeSkippedHeaderEntry(WOLFCOSE_CBOR_CTX* ctx, + size_t maxArrayCount, size_t* arrayCount, int32_t* alg) +{ + int ret; + const uint8_t* protectedData = NULL; + size_t protectedLen = 0u; + int algFound = 0; + + if ((ctx == NULL) || (arrayCount == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + *arrayCount = 0u; + if (alg != NULL) { + *alg = WOLFCOSE_ALG_UNSET; + } + ret = wc_CBOR_DecodeArrayStart(ctx, arrayCount); + } + if ((ret == WOLFCOSE_SUCCESS) && + ((*arrayCount < 3u) || (*arrayCount > maxArrayCount))) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if ((ret == WOLFCOSE_SUCCESS) && (alg == NULL)) { + size_t i; + + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < 3u); i++) { + ret = wc_CBOR_Skip(ctx); + } + } + if ((ret == WOLFCOSE_SUCCESS) && (alg != NULL)) { + ret = wc_CBOR_DecodeBstr(ctx, &protectedData, &protectedLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (alg != NULL) && + (protectedLen > 0u)) { + WOLFCOSE_CBOR_CTX protectedCtx; + + (void)XMEMSET(&protectedCtx, 0, sizeof(protectedCtx)); + protectedCtx.cbuf = protectedData; + protectedCtx.bufSz = protectedLen; + ret = wolfCose_DecodeSkippedHdrAlg(&protectedCtx, alg, &algFound); + if ((ret == WOLFCOSE_SUCCESS) && + (protectedCtx.idx != protectedCtx.bufSz)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + } + if ((ret == WOLFCOSE_SUCCESS) && (alg != NULL)) { + ret = wolfCose_DecodeSkippedHdrAlg(ctx, alg, &algFound); + } + if ((ret == WOLFCOSE_SUCCESS) && (alg != NULL)) { + ret = wc_CBOR_Skip(ctx); + } + + return ret; +} + +#if defined(WOLFCOSE_SIGN_VERIFY) +/* A COSE_Signature has exactly three fields. */ +static int wolfCose_DecodeSkippedSignature(WOLFCOSE_CBOR_CTX* ctx) +{ + size_t arrayCount = 0u; + + return wolfCose_DecodeSkippedHeaderEntry(ctx, 3u, &arrayCount, NULL); +} +#endif + +#if defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) +/* Structurally validate one non-selected COSE_recipient and every nested + * recipient. Use an explicit bounded stack to avoid recursive C calls. */ +static int wolfCose_DecodeSkippedRecipient(WOLFCOSE_CBOR_CTX* ctx, + int32_t* recipientAlg) +{ + int ret; + size_t remaining = 1u; + size_t stack[WOLFCOSE_CBOR_MAX_DEPTH]; + unsigned int depth = 0u; + int firstRecipient = 1; + + if ((ctx == NULL) || (recipientAlg == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + *recipientAlg = WOLFCOSE_ALG_UNSET; + ret = WOLFCOSE_SUCCESS; + } + + while ((ret == WOLFCOSE_SUCCESS) && (remaining > 0u)) { + size_t arrayCount = 0u; + int32_t decodedAlg = WOLFCOSE_ALG_UNSET; + + ret = wolfCose_DecodeSkippedHeaderEntry(ctx, 4u, &arrayCount, + &decodedAlg); + remaining--; + if ((ret == WOLFCOSE_SUCCESS) && (firstRecipient != 0)) { + *recipientAlg = decodedAlg; + firstRecipient = 0; + } + + if ((ret == WOLFCOSE_SUCCESS) && (arrayCount == 4u)) { + size_t nestedCount = 0u; + + ret = wc_CBOR_DecodeArrayStart(ctx, &nestedCount); + if ((ret == WOLFCOSE_SUCCESS) && + ((nestedCount == 0u) || (nestedCount > ctx->bufSz))) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if ((ret == WOLFCOSE_SUCCESS) && (depth >= + (unsigned int)WOLFCOSE_CBOR_MAX_DEPTH)) { + ret = WOLFCOSE_E_CBOR_DEPTH; + } + if (ret == WOLFCOSE_SUCCESS) { + stack[depth] = remaining; + depth++; + remaining = nestedCount; + } + } + + while ((ret == WOLFCOSE_SUCCESS) && (remaining == 0u) && + (depth > 0u)) { + depth--; + remaining = stack[depth]; } } + return ret; } +#endif +#endif /* ----- COSE Key API ----- */ @@ -1158,6 +1716,9 @@ int wc_CoseKey_SetEcc(WOLFCOSE_KEY* key, int32_t crv, ecc_key* eccKey) (crv != WOLFCOSE_CRV_P521)) { ret = WOLFCOSE_E_INVALID_ARG; } + else if (wolfCose_EccKeyCheckCurve(crv, eccKey) != WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_INVALID_ARG; + } else { key->kty = WOLFCOSE_KTY_EC2; key->crv = crv; @@ -1307,6 +1868,14 @@ int wc_CoseKey_SetRsa(WOLFCOSE_KEY* key, RsaKey* rsaKey) } #endif /* WOLFCOSE_HAVE_RSAPSS */ +#ifdef WOLFCOSE_HAVE_RSAPSS +/* Widest RSA public exponent wolfCOSE emits and the RFC 8230 RSA-PSS + * minimum modulus width. These are shared by COSE_Key encoding and message + * operations, including builds that disable COSE_Key encoding. */ +#define WOLFCOSE_RSA_E_MAX_SZ 8u +#define WOLFCOSE_RSA_PSS_MIN_SZ 256u +#endif + int wc_CoseKey_SetSymmetric(WOLFCOSE_KEY* key, const uint8_t* data, size_t dataLen) { @@ -1497,11 +2066,6 @@ static int wolfCose_EncodeKeyOptionalFields(WOLFCOSE_CBOR_CTX* ctx, } #ifdef WOLFCOSE_HAVE_RSAPSS -/* Widest RSA public exponent wolfCOSE emits. Both wc_CoseKey_Encode_ex() and - * wc_CoseKey_EncodeSize_ex() read e through a buffer this size, so the size - * query fails on an oversized exponent exactly where the encoder does. */ -#define WOLFCOSE_RSA_E_MAX_SZ 8u - /* Measure the RSA public exponent without mp_unsigned_bin_size(): that is * declared MP_API, which wolfSSL exports only when built with * WOLFSSL_PUBLIC_MP, so calling it here fails to link (undefined reference @@ -1722,6 +2286,13 @@ int wc_CoseKey_Encode_ex(WOLFCOSE_KEY* key, uint8_t* out, size_t outSz, if (ret != 0) { ret = WOLFCOSE_E_CRYPTO; } + else if (((size_t)xLen != coordSz) || + ((size_t)yLen != coordSz)) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + /* No action required. */ + } } if (ret == WOLFCOSE_SUCCESS) { emitPriv = wolfCose_KeyEmitsPrivate(key, flags); @@ -1733,6 +2304,12 @@ int wc_CoseKey_Encode_ex(WOLFCOSE_KEY* key, uint8_t* out, size_t outSz, if (ret != 0) { ret = WOLFCOSE_E_CRYPTO; } + else if ((size_t)dLen != coordSz) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + /* No action required. */ + } } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_EncodeEc2Map(&ctx, key, xBuf, (size_t)xLen, @@ -2405,15 +2982,20 @@ int wc_CoseKey_EncodeSize_ex(const WOLFCOSE_KEY* key, size_t* outLen, #ifdef HAVE_ECC if (key->kty == WOLFCOSE_KTY_EC2) { + ecc_key* eccKey = key->key.ecc; size_t coordSz = 0u; int emitPriv; - if (key->key.ecc == NULL) { + if (eccKey == NULL) { ret = WOLFCOSE_E_INVALID_ARG; } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_CrvKeySize(key->crv, &coordSz); } + if ((ret == WOLFCOSE_SUCCESS) && + (wc_ecc_size(eccKey) != (int)coordSz)) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } emitPriv = (ret == WOLFCOSE_SUCCESS) ? wolfCose_KeyEmitsPrivate(key, flags) : 0; if (ret == WOLFCOSE_SUCCESS) { @@ -2443,18 +3025,19 @@ int wc_CoseKey_EncodeSize_ex(const WOLFCOSE_KEY* key, size_t* outLen, #endif /* HAVE_ECC */ #ifdef WOLFCOSE_HAVE_RSAPSS if (key->kty == WOLFCOSE_KTY_RSA) { + RsaKey* rsaKey = key->key.rsa; size_t itemLen = 0u; size_t modSz = 0u; size_t eSz = 0u; int rsaPriv = 0; int rsaEncSz; - if (key->key.rsa == NULL) { + if (rsaKey == NULL) { ret = WOLFCOSE_E_INVALID_ARG; } if (ret == WOLFCOSE_SUCCESS) { /* Same value wc_RsaFlattenPublicKey() reports for n. */ - rsaEncSz = wc_RsaEncryptSize(key->key.rsa); + rsaEncSz = wc_RsaEncryptSize(rsaKey); if (rsaEncSz <= 0) { ret = WOLFCOSE_E_CRYPTO; } @@ -2463,7 +3046,7 @@ int wc_CoseKey_EncodeSize_ex(const WOLFCOSE_KEY* key, size_t* outLen, } } if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_RsaExponentSize(key->key.rsa, &eSz); + ret = wolfCose_RsaExponentSize(rsaKey, &eSz); } #ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY if ((ret == WOLFCOSE_SUCCESS) && @@ -2471,7 +3054,7 @@ int wc_CoseKey_EncodeSize_ex(const WOLFCOSE_KEY* key, size_t* outLen, rsaPriv = 1; #ifdef WOLF_CRYPTO_CB /* Device-backed keys have no local CRT to export. */ - if (key->key.rsa->devId != INVALID_DEVID) { + if (rsaKey->devId != INVALID_DEVID) { rsaPriv = 0; } #endif @@ -2520,11 +3103,12 @@ int wc_CoseKey_EncodeSize_ex(const WOLFCOSE_KEY* key, size_t* outLen, #endif /* WOLFCOSE_HAVE_RSAPSS */ #ifdef WOLFCOSE_HAVE_MLDSA if (key->kty == WOLFCOSE_KTY_AKP) { + wc_MlDsaKey* mldsaKey = key->key.mldsa; size_t itemLen = 0u; size_t pubSz = 0u; int emitPriv = 0; - if (key->key.mldsa == NULL) { + if (mldsaKey == NULL) { ret = WOLFCOSE_E_INVALID_ARG; } /* Read the FIPS 204 public key length from the key itself, not @@ -2540,7 +3124,7 @@ int wc_CoseKey_EncodeSize_ex(const WOLFCOSE_KEY* key, size_t* outLen, /* RFC 9964: alg is REQUIRED for AKP keys. */ ret = WOLFCOSE_E_COSE_BAD_ALG; } - else if (wc_MlDsaKey_GetPubLen(key->key.mldsa, + else if (wc_MlDsaKey_GetPubLen(mldsaKey, &dlPubLen) != 0) { ret = WOLFCOSE_E_CRYPTO; } @@ -2662,6 +3246,10 @@ int wc_CoseKey_EncodeSize_ex(const WOLFCOSE_KEY* key, size_t* outLen, #endif /* WOLFCOSE_KEY_ENCODE */ #if defined(WOLFCOSE_KEY_DECODE) +#if defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) +static int wolfCose_LenFitsWord32(size_t n); +#endif + /* Decoded kty/crv select the importer, so they must name the type the caller * attached; a non-NULL key.* union member cannot tell them apart. */ static int wolfCose_KeyAttachedTypeCheck(const WOLFCOSE_KEY* key) @@ -2787,6 +3375,10 @@ int wc_CoseKey_PeekInfo(const uint8_t* in, size_t inSz, info->alg = (int32_t)algVal; } } + else if ((ret == WOLFCOSE_SUCCESS) && + (label == WOLFCOSE_KEY_LABEL_KEY_OPS)) { + ret = WOLFCOSE_E_UNSUPPORTED; + } else if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_KEY_LABEL_CRV)) { /* -1 is crv for EC2/OKP but k (bstr) for Symmetric and n @@ -2956,6 +3548,10 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) } } } + else if ((ret == WOLFCOSE_SUCCESS) && + (label == WOLFCOSE_KEY_LABEL_KEY_OPS)) { + ret = WOLFCOSE_E_UNSUPPORTED; + } else if ((ret == WOLFCOSE_SUCCESS) && (label == WOLFCOSE_KEY_LABEL_CRV)) { /* -1: crv(uint/negint) for EC2/OKP, k(bstr) for Symmetric, @@ -3035,6 +3631,36 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) ret = WOLFCOSE_E_CBOR_MALFORMED; } +#if defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) + /* wolfCrypt key import APIs take word32 lengths. Reject every parsed + * component before any conversion can truncate it. */ + if ((ret == WOLFCOSE_SUCCESS) && + ((wolfCose_LenFitsWord32(nLen) == 0) || + (wolfCose_LenFitsWord32(xLen) == 0) || + (wolfCose_LenFitsWord32(yLen) == 0) || + (wolfCose_LenFitsWord32(dLen) == 0))) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY + if ((ret == WOLFCOSE_SUCCESS) && + ((wolfCose_LenFitsWord32(qLen) == 0) || + (wolfCose_LenFitsWord32(dpLen) == 0) || + (wolfCose_LenFitsWord32(dqLen) == 0) || + (wolfCose_LenFitsWord32(qiLen) == 0))) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } +#endif +#endif + + /* An EC2 key must contain either a complete public point or a private + * scalar. Validate this independently of whether a wolfCrypt key is + * attached for import. */ + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_EC2) && + (((xData == NULL) || (yData == NULL)) && (dData == NULL))) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + #ifdef HAVE_ECC /* RFC 9053 Section 7.1.1: EC2 coordinates are fixed length with * leading zeros preserved. Reject any present coordinate that does not @@ -3055,6 +3681,36 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) ret = wolfCose_KeyAttachedTypeCheck(key); } + /* RFC 9964 requires alg to select an ML-DSA parameter set. */ + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_AKP) && + (key->alg != WOLFCOSE_ALG_ML_DSA_44) && + (key->alg != WOLFCOSE_ALG_ML_DSA_65) && + (key->alg != WOLFCOSE_ALG_ML_DSA_87)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + /* RFC 9964 requires pub for both public and private AKP keys. */ + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_AKP) && (nData == NULL)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + /* RFC 9964 encodes an ML-DSA private key as a 32-byte seed. */ + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_AKP) && (xData != NULL) && + (xLen != WOLFCOSE_MLDSA_SEED_SZ)) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } +#ifdef WOLFSSL_MLDSA_NO_MAKE_KEY + /* This wolfSSL configuration has no private-seed expansion API. + * Metadata-only decode can still validate the representation, while + * an attached key cannot import it. */ + if ((ret == WOLFCOSE_SUCCESS) && + (key->kty == WOLFCOSE_KTY_AKP) && (xData != NULL) && + (key->attachedType == WOLFCOSE_ATT_MLDSA)) { + ret = WOLFCOSE_E_UNSUPPORTED; + } +#endif + /* Import key data into wolfCrypt key structs */ if (ret == WOLFCOSE_SUCCESS) { #ifdef HAVE_ECC @@ -3065,15 +3721,31 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) * private EC2 key, so accept {kty, crv, d} alone. */ if (dData != NULL) { int wcCrv = 0; - ret = wolfCose_CrvToWcCurve(key->crv, &wcCrv); + WOLFCOSE_ECC_IMPORT_STATE importState; + + ret = wolfCose_EccPrivateImportBegin(key->key.ecc, + &importState); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CrvToWcCurve(key->crv, &wcCrv); + } if (ret == WOLFCOSE_SUCCESS) { INJECT_FAILURE(WOLF_FAIL_ECC_IMPORT_X963, -1, ret = wc_ecc_import_private_key_ex(dData, (word32)dLen, NULL, 0, key->key.ecc, wcCrv)); +#ifdef WOLFCOSE_FORCE_FAILURE + if (ret == 0) { + INJECT_FAILURE( + WOLF_FAIL_ECC_IMPORT_PRIVATE_POST, -1, + ret = WOLFCOSE_SUCCESS); + } +#endif if (ret == 0) { key->hasPrivate = 1; } else { + wolfCose_EccPrivateImportRollback(key->key.ecc, + &importState); + key->hasPrivate = 0; ret = WOLFCOSE_E_CRYPTO; } } @@ -3085,7 +3757,15 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) else { int wcCrv; size_t coordSz = 0; - ret = wolfCose_CrvToWcCurve(key->crv, &wcCrv); + WOLFCOSE_ECC_IMPORT_STATE importState; + + if (dData != NULL) { + ret = wolfCose_EccPrivateImportBegin(key->key.ecc, + &importState); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_CrvToWcCurve(key->crv, &wcCrv); + } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_CrvKeySize(key->crv, &coordSz); } @@ -3111,9 +3791,21 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) key->key.ecc, tmpX, tmpY, tmpD, wcCrv)); (void)wolfCose_ForceZero(tmpD, sizeof(tmpD)); +#ifdef WOLFCOSE_FORCE_FAILURE + if (ret == 0) { + INJECT_FAILURE( + WOLF_FAIL_ECC_IMPORT_PRIVATE_POST, -1, + ret = WOLFCOSE_SUCCESS); + } +#endif if (ret == 0) { key->hasPrivate = 1; } + else { + wolfCose_EccPrivateImportRollback( + key->key.ecc, &importState); + key->hasPrivate = 0; + } } else { INJECT_FAILURE(WOLF_FAIL_ECC_IMPORT_X963, -1, @@ -3123,7 +3815,8 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) } } if ((ret != WOLFCOSE_SUCCESS) && - (ret != WOLFCOSE_E_INVALID_ARG)) { + (ret != WOLFCOSE_E_INVALID_ARG) && + (ret != WOLFCOSE_E_UNSUPPORTED)) { ret = WOLFCOSE_E_CRYPTO; } } @@ -3181,8 +3874,10 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) * 32-byte seed in xData. The ML-DSA level comes from alg. */ const uint8_t* akpPub = nData; size_t akpPubLen = nLen; +#ifndef WOLFSSL_MLDSA_NO_MAKE_KEY const uint8_t* akpSeed = xData; size_t akpSeedLen = xLen; +#endif byte dlLevel; if (key->alg == WOLFCOSE_ALG_ML_DSA_44) { @@ -3215,12 +3910,19 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) if (ret != 0) { ret = WOLFCOSE_E_CRYPTO; } - else if (akpSeed != NULL) { - /* Private key: the seed is authoritative and fully - * determines the keypair, so the (RFC-required, already - * present) pub is not cross-checked against it -- doing - * so would need a multi-KB stack copy of the derived - * public key, which the bounded-stack design avoids. */ + if (ret == WOLFCOSE_SUCCESS) { + /* Apply the parameter-set-specific public key import + * checks before deriving a private key from its seed. */ + INJECT_FAILURE(WOLF_FAIL_MLDSA_IMPORT_PUB, -1, + ret = wc_MlDsaKey_ImportPubRaw( + key->key.mldsa, akpPub, + (word32)akpPubLen)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } + } +#ifndef WOLFSSL_MLDSA_NO_MAKE_KEY + if ((ret == WOLFCOSE_SUCCESS) && (akpSeed != NULL)) { if (akpSeedLen != WOLFCOSE_MLDSA_SEED_SZ) { ret = WOLFCOSE_E_COSE_BAD_HDR; } @@ -3228,7 +3930,17 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) INJECT_FAILURE(WOLF_FAIL_MLDSA_IMPORT_PRIV, -1, ret = wc_MlDsaKey_MakeKeyFromSeed( key->key.mldsa, akpSeed)); - if (ret == 0) { + if ((ret == 0) && + (XMEMCMP(key->key.mldsa->p, akpPub, + akpPubLen) != 0)) { + wolfCose_MlDsaImportRollback( + key->key.mldsa, dlLevel); + key->hasPrivate = 0u; + key->mldsaSeed = NULL; + key->mldsaSeedLen = 0u; + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + else if (ret == 0) { key->hasPrivate = 1; /* Retain the seed (zero-copy into the input, * like kid) so a decode->encode round-trip can @@ -3236,15 +3948,17 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) key->mldsaSeed = akpSeed; key->mldsaSeedLen = akpSeedLen; } - else { ret = WOLFCOSE_E_CRYPTO; } + else { + wolfCose_MlDsaImportRollback( + key->key.mldsa, dlLevel); + key->hasPrivate = 0u; + key->mldsaSeed = NULL; + key->mldsaSeedLen = 0u; + ret = WOLFCOSE_E_CRYPTO; + } } } - else { - INJECT_FAILURE(WOLF_FAIL_MLDSA_IMPORT_PUB, -1, - ret = wc_MlDsaKey_ImportPubRaw( - key->key.mldsa, akpPub, (word32)akpPubLen)); - if (ret != 0) { ret = WOLFCOSE_E_CRYPTO; } - } +#endif } } else @@ -3271,8 +3985,17 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) ret = wc_ed25519_import_private_only(dData, (word32)dLen, key->key.ed25519)); if (ret == 0) { - ret = wc_ed25519_make_public(key->key.ed25519, - key->key.ed25519->p, ED25519_PUB_KEY_SIZE); + INJECT_FAILURE(WOLF_FAIL_ED25519_MAKE_PUB, -1, + ret = wc_ed25519_make_public( + key->key.ed25519, + key->key.ed25519->p, + ED25519_PUB_KEY_SIZE)); + if (ret != 0) { + (void)wolfCose_ForceZero( + key->key.ed25519->k, + ED25519_KEY_SIZE); + key->key.ed25519->privKeySet = 0; + } } } if (ret == 0) { key->hasPrivate = 1; } @@ -3301,8 +4024,16 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) ret = wc_ed448_import_private_only(dData, (word32)dLen, key->key.ed448)); if (ret == 0) { - ret = wc_ed448_make_public(key->key.ed448, - key->key.ed448->p, ED448_PUB_KEY_SIZE); + INJECT_FAILURE(WOLF_FAIL_ED448_MAKE_PUB, -1, + ret = wc_ed448_make_public( + key->key.ed448, + key->key.ed448->p, + ED448_PUB_KEY_SIZE)); + if (ret != 0) { + (void)wolfCose_ForceZero( + key->key.ed448->k, ED448_KEY_SIZE); + key->key.ed448->privKeySet = 0; + } } } if (ret == 0) { key->hasPrivate = 1; } @@ -3324,9 +4055,8 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) #endif /* WOLFCOSE_HAVE_EDDSA || WOLFCOSE_HAVE_ED448 */ if (key->kty == WOLFCOSE_KTY_SYMMETRIC) { /* nData holds the symmetric k value (parsed from label -1). - * Reject the message when the mandatory k parameter is - * absent so callers cannot end up with an empty key. */ - if (nData == NULL) { + * Reject a missing or empty mandatory k parameter. */ + if ((nData == NULL) || (nLen == 0u)) { ret = WOLFCOSE_E_COSE_BAD_HDR; } else { @@ -3349,6 +4079,99 @@ int wc_CoseKey_Decode(WOLFCOSE_KEY* key, const uint8_t* in, size_t inSz) #endif /* WOLFCOSE_KEY_DECODE */ /* ----- Internal: RSA-PSS hash-to-MGF mapping ----- */ +#if defined(WOLFCOSE_HAVE_RSAPSS) && \ + (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ + defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY)) +/* RFC 8230 Section 6.1 requires RSA-PSS keys of at least 2048 bits. */ +static int wolfCose_RsaPssCheckKey(const WOLFCOSE_KEY* key, + size_t* modulusLen) +{ + int ret = WOLFCOSE_SUCCESS; + + if (key == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if ((key->kty != WOLFCOSE_KTY_RSA) || + (key->attachedType != WOLFCOSE_ATT_RSA) || + (key->key.rsa == NULL)) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + RsaKey* rsaKey = key->key.rsa; + int modulusSz = wc_RsaEncryptSize(rsaKey); + int opaqueKey = 0; + +#ifdef WOLF_CRYPTO_CB + if (rsaKey->devId != INVALID_DEVID) { + opaqueKey = 1; + } +#endif +#ifdef WOLFSSL_MICROCHIP_TA100 + if ((rsaKey->rKeyH != 0u) || (rsaKey->uKeyH != 0u)) { + opaqueKey = 1; + } +#endif + if (modulusSz < (int)WOLFCOSE_RSA_PSS_MIN_SZ) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else if (modulusSz == (int)WOLFCOSE_RSA_PSS_MIN_SZ) { + int modulusMaterialized = 0; + uint8_t modulus[WOLFCOSE_RSA_PSS_MIN_SZ] = {0}; + word32 modulusLen32 = (word32)sizeof(modulus); +#if !defined(HAVE_ECC) && !defined(WOLFSSL_EXPORT_INT) && \ + !defined(WOLFSSL_RSA_VERIFY_ONLY) + word32 exponentLen = (word32)sizeof(modulus); +#endif +#if defined(HAVE_ECC) || defined(WOLFSSL_EXPORT_INT) + int modulusExportRet = wc_export_int(&rsaKey->n, modulus, + &modulusLen32, (word32)sizeof(modulus), + WC_TYPE_UNSIGNED_BIN); +#elif defined(WOLFSSL_RSA_VERIFY_ONLY) + int modulusExportRet = -1; +#else + /* The modulus output overwrites the unused exponent output. */ + int modulusExportRet = wc_RsaFlattenPublicKey(rsaKey, + modulus, &exponentLen, modulus, &modulusLen32); +#endif + if (modulusExportRet == 0) { + if (modulusLen32 > (word32)sizeof(modulus)) { + modulusMaterialized = 1; + } + else { + size_t i = 0u; + + for (; i < (size_t)modulusLen32; i++) { + if (modulus[i] != 0u) { + modulusMaterialized = 1; + } + } + } + } + if (modulusMaterialized != 0) { + if ((modulusLen32 != (word32)sizeof(modulus)) || + ((modulus[0] & 0x80u) == 0u)) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + } + else if (opaqueKey == 0) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + /* The backend-reported size is authoritative for an opaque + * key whose modulus is unavailable to the caller. */ + } + } + else { + /* A wider modulus is above the required minimum. */ + } + if ((ret == WOLFCOSE_SUCCESS) && (modulusLen != NULL)) { + *modulusLen = (size_t)modulusSz; + } + } + return ret; +} +#endif /* WOLFCOSE_HAVE_RSAPSS && RSA-PSS operations */ + #if defined(WOLFCOSE_HAVE_RSAPSS) && \ (defined(WOLFCOSE_SIGN1_SIGN) || defined(WOLFCOSE_SIGN1_VERIFY) || \ defined(WOLFCOSE_SIGN_SIGN) || defined(WOLFCOSE_SIGN_VERIFY)) @@ -3430,8 +4253,7 @@ int wolfCose_BuildToBeSignedMaced( ret = wc_CBOR_EncodeBstr(&ctx, signProtected, signProtectedLen); } if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_EncodeBstr(&ctx, extAad, - (extAad != NULL) ? extAadLen : 0u); + ret = wc_CBOR_EncodeBstr(&ctx, extAad, extAadLen); } if (ret == WOLFCOSE_SUCCESS) { ret = wc_CBOR_EncodeBstr(&ctx, payload, payloadLen); @@ -3478,8 +4300,7 @@ static int wolfCose_BuildEncStructure( ret = wc_CBOR_EncodeBstr(&ctx, bodyProtected, bodyProtectedLen); } if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_EncodeBstr(&ctx, extAad, - (extAad != NULL) ? extAadLen : 0u); + ret = wc_CBOR_EncodeBstr(&ctx, extAad, extAadLen); } if (ret == WOLFCOSE_SUCCESS) { *structLen = ctx.idx; @@ -3667,11 +4488,14 @@ static int wolfCose_IsKeyWrapAlg(int32_t alg) /** * Build COSE_KDF_Context for ECDH key derivation (RFC 9053 Section 5.2). * - * Simplified version: PartyUInfo and PartyVInfo are empty arrays. - * SuppPubInfo contains only keyDataLength and empty protected header. + * PartyUInfo and PartyVInfo are each [nil, nil, nil]. SuppPubInfo contains + * keyDataLength and the caller-supplied recipient protected header, encoded + * as {1: algId} by the ECDH-ES callers. * * \param contentAlgId Content encryption algorithm for derived key * \param keyDataLengthBits Key length in bits + * \param recipientProtected Encoded {1: algId} recipient protected header + * \param recipientProtectedLen Recipient protected header length * \param out Output buffer * \param outSz Output buffer size * \param outLen Output: bytes written @@ -3814,6 +4638,11 @@ static int wolfCose_EcdhEsDirect(int32_t alg, ret = WOLFCOSE_E_COSE_KEY_TYPE; } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EccKeyCheckCurve(recipientPub->crv, + recipientPub->key.ecc); + } + /* Determine hash type from algorithm */ if (ret == WOLFCOSE_SUCCESS) { if (alg == WOLFCOSE_ALG_ECDH_ES_HKDF_256) { @@ -3951,6 +4780,10 @@ static int wolfCose_EcdhEsDirect(int32_t alg, * \param ephemPubLen Coordinate length * \param contentAlgId Content encryption algorithm * \param cekLenBytes Required CEK length in bytes + * \param recipientProtected Encoded recipient protected header + * \param recipientProtectedLen Recipient protected header length + * \param kdfContext Scratch buffer for the encoded KDF context + * \param kdfContextSz KDF context scratch buffer size * \param cekOut Output: derived CEK * \param cekOutSz CEK buffer size * \return WOLFCOSE_SUCCESS or error code @@ -3964,6 +4797,8 @@ static int wolfCose_EcdhEsDirectRecv(int32_t alg, size_t cekLenBytes, const uint8_t* recipientProtected, size_t recipientProtectedLen, + uint8_t* kdfContext, + size_t kdfContextSz, uint8_t* cekOut, size_t cekOutSz) { int ret = WOLFCOSE_SUCCESS; @@ -3971,7 +4806,6 @@ static int wolfCose_EcdhEsDirectRecv(int32_t alg, int ephemInited = 0; uint8_t sharedSecret[66]; word32 sharedSecretLen = sizeof(sharedSecret); - uint8_t kdfContext[64]; size_t kdfContextLen = 0; int hashType = 0; int wcCurve = 0; @@ -3982,7 +4816,7 @@ static int wolfCose_EcdhEsDirectRecv(int32_t alg, /* Parameter validation */ if ((recipientKey == NULL) || (ephemPubX == NULL) || (ephemPubY == NULL) || - (cekOut == NULL)) { + (kdfContext == NULL) || (cekOut == NULL)) { ret = WOLFCOSE_E_INVALID_ARG; } @@ -3997,6 +4831,11 @@ static int wolfCose_EcdhEsDirectRecv(int32_t alg, ret = WOLFCOSE_E_COSE_KEY_TYPE; } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EccKeyCheckCurve(recipientKey->crv, + recipientKey->key.ecc); + } + /* Determine hash type from algorithm */ if (ret == WOLFCOSE_SUCCESS) { if (alg == WOLFCOSE_ALG_ECDH_ES_HKDF_256) { @@ -4070,6 +4909,12 @@ static int wolfCose_EcdhEsDirectRecv(int32_t alg, if (eccRet != 0) { ret = WOLFCOSE_E_CRYPTO; } + else { + eccRet = wc_ecc_check_key(&ephemPub); + if (eccRet != 0) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } + } } } @@ -4100,7 +4945,7 @@ static int wolfCose_EcdhEsDirectRecv(int32_t alg, ret = wolfCose_KdfContextEncode(contentAlgId, cekLenBytes * 8u, recipientProtected, recipientProtectedLen, - kdfContext, sizeof(kdfContext), + kdfContext, kdfContextSz, &kdfContextLen); } @@ -4317,10 +5162,61 @@ static int wolfCose_DecodeEphemeralKey(WOLFCOSE_CBOR_CTX* ctx, #endif /* WOLFCOSE_ECDH_ES_DIRECT && HAVE_ECC && HAVE_HKDF */ +#if defined(WOLFCOSE_ENCRYPT_DECRYPT) || defined(WOLFCOSE_MAC_VERIFY) +#define WOLFCOSE_RECIP_MODE_DIRECT_ENCRYPTION 1 +#define WOLFCOSE_RECIP_MODE_DIRECT_AGREEMENT 2 +#define WOLFCOSE_RECIP_MODE_KEY_TRANSPORT 3 + +/* Enforce the recipient combinations allowed by RFC 9052 Section 8.5. */ +static int wolfCose_UpdateRecipientMode(int32_t alg, int* commonMode) +{ + int ret = WOLFCOSE_SUCCESS; + int mode = 0; + + if (commonMode == NULL) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else if (alg == WOLFCOSE_ALG_UNSET) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else if ((alg == WOLFCOSE_ALG_DIRECT) || + (alg == WOLFCOSE_ALG_DIRECT_HKDF_SHA_256) || + (alg == WOLFCOSE_ALG_DIRECT_HKDF_SHA_512) || + (alg == WOLFCOSE_ALG_DIRECT_HKDF_AES_128) || + (alg == WOLFCOSE_ALG_DIRECT_HKDF_AES_256)) { + mode = WOLFCOSE_RECIP_MODE_DIRECT_ENCRYPTION; + } + else if ((alg == WOLFCOSE_ALG_ECDH_ES_HKDF_256) || + (alg == WOLFCOSE_ALG_ECDH_ES_HKDF_512) || + (alg == WOLFCOSE_ALG_ECDH_SS_HKDF_256) || + (alg == WOLFCOSE_ALG_ECDH_SS_HKDF_512)) { + mode = WOLFCOSE_RECIP_MODE_DIRECT_AGREEMENT; + } + else { + mode = WOLFCOSE_RECIP_MODE_KEY_TRANSPORT; + } + + if ((ret == WOLFCOSE_SUCCESS) && (*commonMode == 0)) { + *commonMode = mode; + } + else if ((ret == WOLFCOSE_SUCCESS) && + ((*commonMode != mode) || + (mode == WOLFCOSE_RECIP_MODE_DIRECT_AGREEMENT))) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else { + /* No action required */ + } + + return ret; +} +#endif + /* Only meaningful where size_t can exceed word32; on smaller-or-equal size_t * platforms the cast cannot truncate, so the guard and helper are omitted (and * the condition would otherwise be a compile-time constant). */ -#if (defined(WOLFCOSE_SIGN1) || defined(WOLFCOSE_SIGN) || \ +#if (defined(WOLFCOSE_KEY_DECODE) || defined(WOLFCOSE_SIGN1) || \ + defined(WOLFCOSE_SIGN) || \ defined(WOLFCOSE_MAC0) || defined(WOLFCOSE_MAC) || \ defined(WOLFCOSE_ENCRYPT0) || defined(WOLFCOSE_ENCRYPT)) && \ defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) @@ -4447,27 +5343,7 @@ static int wolfCose_SignSigLen(const WOLFCOSE_KEY* key, int32_t alg, case WOLFCOSE_ALG_PS512: #endif { - if ((key == NULL) || (key->kty != WOLFCOSE_KTY_RSA) || - (key->attachedType != WOLFCOSE_ATT_RSA) || - (key->key.rsa == NULL)) { - if (key == NULL) { - ret = WOLFCOSE_E_INVALID_ARG; - } - else { - ret = WOLFCOSE_E_COSE_KEY_TYPE; - } - } - else { - int modSz = wc_RsaEncryptSize(key->key.rsa); - - if (modSz <= 0) { - ret = WOLFCOSE_E_COSE_KEY_TYPE; - } - else { - *expSigLen = (size_t)modSz; - ret = WOLFCOSE_SUCCESS; - } - } + ret = wolfCose_RsaPssCheckKey(key, expSigLen); } break; #endif @@ -5015,6 +5891,10 @@ int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, } } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EccKeyCheckCurve(key->crv, key->key.ecc); + } + if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_AlgToHashType(alg, &hashType); } @@ -5043,7 +5923,7 @@ int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, size_t rawSigLen = sizeof(sigBuf); ret = wolfCose_EccSignRaw(hashBuf, (size_t)digestSz, sigBuf, &rawSigLen, coordSz, - rng, key->key.ecc); + hashType, rng, key->key.ecc); if (ret == WOLFCOSE_SUCCESS) { sigSz = rawSigLen; } @@ -5058,9 +5938,7 @@ int wc_CoseSign1_Sign_ex(WOLFCOSE_KEY* key, int32_t alg, int digestSz = 0; int mgf = 0; - if ((key->kty != WOLFCOSE_KTY_RSA) || (key->key.rsa == NULL)) { - ret = WOLFCOSE_E_COSE_KEY_TYPE; - } + ret = wolfCose_RsaPssCheckKey(key, NULL); if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_AlgToHashType(alg, &hashType); @@ -5248,6 +6126,7 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, WOLFCOSE_HDR_STATE hdrState; const uint8_t* verifyPayload = NULL; size_t verifyPayloadLen = 0; + int algProtected = 0; if ((key == NULL) || (in == NULL) || (scratch == NULL) || (hdr == NULL) || (payload == NULL) || (payloadLen == NULL)) { @@ -5297,6 +6176,10 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, &hdrState); + if (ret == WOLFCOSE_SUCCESS) { + algProtected = wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_ALG); + } } /* 2. Unprotected headers (map) */ @@ -5350,6 +6233,10 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, (key->alg != WOLFCOSE_ALG_UNSET) && (key->alg != alg)) { ret = WOLFCOSE_E_COSE_BAD_ALG; } + if ((ret == WOLFCOSE_SUCCESS) && + (key->alg == WOLFCOSE_ALG_UNSET) && (algProtected == 0)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } if (ret == WOLFCOSE_SUCCESS) { /* Rebuild Sig_structure in scratch using appropriate payload */ @@ -5368,16 +6255,23 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, } #ifdef WOLFCOSE_HAVE_EDDSA if ((ret == WOLFCOSE_SUCCESS) && (key->crv == WOLFCOSE_CRV_ED25519)) { - if (key->key.ed25519 == NULL) { + if (key->attachedType != WOLFCOSE_ATT_ED25519) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } else { - INJECT_FAILURE(WOLF_FAIL_ED25519_VERIFY, -1, - ret = wc_ed25519_verify_msg(sigData, (word32)sigDataLen, - scratch, (word32)sigStructLen, - &verified, key->key.ed25519)); - if (ret != 0) { - ret = WOLFCOSE_E_CRYPTO; + ed25519_key* ed25519Key = key->key.ed25519; + + if (ed25519Key == NULL) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + INJECT_FAILURE(WOLF_FAIL_ED25519_VERIFY, -1, + ret = wc_ed25519_verify_msg(sigData, + (word32)sigDataLen, scratch, + (word32)sigStructLen, &verified, ed25519Key)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } } } } @@ -5385,17 +6279,24 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, #endif #ifdef WOLFCOSE_HAVE_ED448 if ((ret == WOLFCOSE_SUCCESS) && (key->crv == WOLFCOSE_CRV_ED448)) { - if (key->key.ed448 == NULL) { + if (key->attachedType != WOLFCOSE_ATT_ED448) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } else { - INJECT_FAILURE(WOLF_FAIL_ED448_VERIFY, -1, - ret = wc_ed448_verify_msg(sigData, (word32)sigDataLen, - scratch, (word32)sigStructLen, - &verified, key->key.ed448, - NULL, 0)); - if (ret != 0) { - ret = WOLFCOSE_E_CRYPTO; + ed448_key* ed448Key = key->key.ed448; + + if (ed448Key == NULL) { + ret = WOLFCOSE_E_COSE_KEY_TYPE; + } + else { + INJECT_FAILURE(WOLF_FAIL_ED448_VERIFY, -1, + ret = wc_ed448_verify_msg(sigData, + (word32)sigDataLen, scratch, + (word32)sigStructLen, &verified, ed448Key, + NULL, 0)); + if (ret != 0) { + ret = WOLFCOSE_E_CRYPTO; + } } } } @@ -5417,6 +6318,7 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, if ((ret == WOLFCOSE_SUCCESS) && ((alg == WOLFCOSE_ALG_ES256) || (alg == WOLFCOSE_ALG_ES384) || (alg == WOLFCOSE_ALG_ES512))) { + ecc_key* eccKey = NULL; int verified = 0; size_t coordSz = 0; enum wc_HashType hashType = WC_HASH_TYPE_NONE; @@ -5441,6 +6343,10 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, ret = WOLFCOSE_E_COSE_BAD_ALG; } } + if (ret == WOLFCOSE_SUCCESS) { + eccKey = key->key.ecc; + ret = wolfCose_EccKeyCheckCurve(key->crv, eccKey); + } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_AlgToHashType(alg, &hashType); } @@ -5464,7 +6370,7 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_EccVerifyRaw(sigData, sigDataLen, hashBuf, (size_t)digestSz, - coordSz, key->key.ecc, &verified); + coordSz, eccKey, &verified); } if ((ret == WOLFCOSE_SUCCESS) && (verified != 1)) { ret = WOLFCOSE_E_COSE_SIG_FAIL; @@ -5476,14 +6382,14 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, if ((ret == WOLFCOSE_SUCCESS) && ((alg == WOLFCOSE_ALG_PS256) || (alg == WOLFCOSE_ALG_PS384) || (alg == WOLFCOSE_ALG_PS512))) { + RsaKey* rsaKey = NULL; enum wc_HashType hashType = WC_HASH_TYPE_NONE; int digestSz = 0; int mgf = 0; - if ((key->kty != WOLFCOSE_KTY_RSA) || (key->key.rsa == NULL)) { - ret = WOLFCOSE_E_COSE_KEY_TYPE; - } + ret = wolfCose_RsaPssCheckKey(key, NULL); if (ret == WOLFCOSE_SUCCESS) { + rsaKey = key->key.rsa; ret = wolfCose_AlgToHashType(alg, &hashType); } if (ret == WOLFCOSE_SUCCESS) { @@ -5517,7 +6423,7 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, ret = wc_RsaPSS_VerifyCheck(scratch, (word32)sigDataLen, scratch, (word32)scratchSz, hashBuf, (word32)digestSz, - hashType, mgf, key->key.rsa)); + hashType, mgf, rsaKey)); if (ret < 0) { ret = WOLFCOSE_E_COSE_SIG_FAIL; } @@ -5532,6 +6438,7 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, if ((ret == WOLFCOSE_SUCCESS) && ((alg == WOLFCOSE_ALG_ML_DSA_44) || (alg == WOLFCOSE_ALG_ML_DSA_65) || (alg == WOLFCOSE_ALG_ML_DSA_87))) { + wc_MlDsaKey* mldsaKey = NULL; int verified = 0; /* RFC 9964: AKP key whose level matches the algorithm. */ @@ -5539,9 +6446,10 @@ int wc_CoseSign1_Verify(const WOLFCOSE_KEY* key, ret = wolfCose_MlDsaCheckKey(key, alg); } if (ret == WOLFCOSE_SUCCESS) { + mldsaKey = key->key.mldsa; INJECT_FAILURE(WOLF_FAIL_MLDSA_VERIFY, -1, ret = wc_MlDsaKey_VerifyCtx( - key->key.mldsa, + mldsaKey, sigData, (word32)sigDataLen, NULL, 0, scratch, (word32)sigStructLen, @@ -5701,16 +6609,18 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, /* Fail-fast key/alg checks before any hashing. */ for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < signerCount); i++) { - if ((signers[i].key == NULL) || - (wolfCose_KeyCanSign(signers[i].key) == 0)) { + WOLFCOSE_KEY* signerKey = signers[i].key; + + if ((signerKey == NULL) || + (wolfCose_KeyCanSign(signerKey) == 0)) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } else if (((signers[i].kid != NULL) && (signers[i].kidLen == 0u)) || ((signers[i].kid == NULL) && (signers[i].kidLen != 0u))) { ret = WOLFCOSE_E_INVALID_ARG; } - else if ((signers[i].key->alg != WOLFCOSE_ALG_UNSET) && - (signers[i].key->alg != signers[i].algId)) { + else if ((signerKey->alg != WOLFCOSE_ALG_UNSET) && + (signerKey->alg != signers[i].algId)) { ret = WOLFCOSE_E_COSE_BAD_ALG; } #if defined(WOLFCOSE_EXT_SIGN) @@ -5718,7 +6628,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, * bindings below describe something that is not here. Matches the * order wc_CoseSign1_Sign uses, where the ext-sign branch precedes * the per-algorithm ones. */ - else if (signers[i].key->signCb != NULL) { + else if (signerKey->signCb != NULL) { /* No action required */ } #endif @@ -5736,10 +6646,14 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, else { expectedCrv = WOLFCOSE_CRV_P521; } - if (signers[i].key->kty != WOLFCOSE_KTY_EC2) { + if (signerKey->kty != WOLFCOSE_KTY_EC2) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } - else if (signers[i].key->crv != expectedCrv) { + else if (signerKey->crv != expectedCrv) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else if (wolfCose_EccKeyCheckCurve(signerKey->crv, + signerKey->key.ecc) != WOLFCOSE_SUCCESS) { ret = WOLFCOSE_E_COSE_BAD_ALG; } else { @@ -5749,7 +6663,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, #endif #if defined(WOLFCOSE_HAVE_EDDSA) || defined(WOLFCOSE_HAVE_ED448) else if ((signers[i].algId == WOLFCOSE_ALG_EDDSA) && - (signers[i].key->kty != WOLFCOSE_KTY_OKP)) { + (signerKey->kty != WOLFCOSE_KTY_OKP)) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } #endif @@ -5757,7 +6671,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, else if (((signers[i].algId == WOLFCOSE_ALG_PS256) || (signers[i].algId == WOLFCOSE_ALG_PS384) || (signers[i].algId == WOLFCOSE_ALG_PS512)) && - (signers[i].key->kty != WOLFCOSE_KTY_RSA)) { + (signerKey->kty != WOLFCOSE_KTY_RSA)) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } #endif @@ -5766,7 +6680,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, else if (((signers[i].algId == WOLFCOSE_ALG_ML_DSA_44) || (signers[i].algId == WOLFCOSE_ALG_ML_DSA_65) || (signers[i].algId == WOLFCOSE_ALG_ML_DSA_87)) && - (wolfCose_MlDsaCheckKey(signers[i].key, signers[i].algId) + (wolfCose_MlDsaCheckKey(signerKey, signers[i].algId) != WOLFCOSE_SUCCESS)) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } @@ -5823,6 +6737,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, /* Create each COSE_Signature */ for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < signerCount); i++) { const WOLFCOSE_SIGNATURE* signer = &signers[i]; + WOLFCOSE_KEY* signerKey = signer->key; enum wc_HashType hashType = WC_HASH_TYPE_NONE; size_t hashLen = 0; const uint8_t* sigPtr = sigBuf; @@ -5835,7 +6750,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, * without a pre-hash so the hash type lookup is skipped. */ if ((ret == WOLFCOSE_SUCCESS) && #if defined(WOLFCOSE_EXT_SIGN) - (signer->key->signCb == NULL) && + (signerKey->signCb == NULL) && #endif (signer->algId != WOLFCOSE_ALG_ML_DSA_44) && (signer->algId != WOLFCOSE_ALG_ML_DSA_65) && @@ -5866,7 +6781,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, * does its own hashing inside wolfCose_ExtSign. */ if ((ret == WOLFCOSE_SUCCESS) && #if defined(WOLFCOSE_EXT_SIGN) - (signer->key->signCb == NULL) && + (signerKey->signCb == NULL) && #endif (signer->algId != WOLFCOSE_ALG_EDDSA) && (signer->algId != WOLFCOSE_ALG_ML_DSA_44) && @@ -5888,7 +6803,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, /* Sign the hash */ #if defined(WOLFCOSE_EXT_SIGN) - if ((ret == WOLFCOSE_SUCCESS) && (signer->key->signCb != NULL)) { + if ((ret == WOLFCOSE_SUCCESS) && (signerKey->signCb != NULL)) { size_t extSigLen = 0; size_t sigOff = 0; int extPreHash = 0; @@ -5903,7 +6818,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, } } if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_ExtSign(signer->key, signer->algId, + ret = wolfCose_ExtSign(signerKey, signer->algId, scratch, sigStructLen, &scratch[sigOff], scratchSz - sigOff, &extSigLen); @@ -5921,12 +6836,13 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, (signer->algId == WOLFCOSE_ALG_ES384) || (signer->algId == WOLFCOSE_ALG_ES512))) { size_t coordSz = 0; - ret = wolfCose_CrvKeySize(signer->key->crv, &coordSz); + ret = wolfCose_CrvKeySize(signerKey->crv, &coordSz); if (ret == WOLFCOSE_SUCCESS) { sigSz = coordSz * 2u; ret = wolfCose_EccSignRaw(hashBuf, hashLen, sigBuf, &sigSz, coordSz, - rng, signer->key->key.ecc); + hashType, rng, + signerKey->key.ecc); } } else @@ -5936,14 +6852,14 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, (signer->algId == WOLFCOSE_ALG_EDDSA)) { word32 edSigSz = (word32)sizeof(sigBuf); #ifdef WOLFCOSE_HAVE_EDDSA - if (signer->key->crv == WOLFCOSE_CRV_ED25519) { - if (signer->key->key.ed25519 == NULL) { + if (signerKey->crv == WOLFCOSE_CRV_ED25519) { + if (signerKey->key.ed25519 == NULL) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } else { ret = wc_ed25519_sign_msg(scratch, (word32)sigStructLen, sigBuf, &edSigSz, - signer->key->key.ed25519); + signerKey->key.ed25519); if (ret != 0) { ret = WOLFCOSE_E_CRYPTO; } @@ -5955,14 +6871,14 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, else #endif #ifdef WOLFCOSE_HAVE_ED448 - if (signer->key->crv == WOLFCOSE_CRV_ED448) { - if (signer->key->key.ed448 == NULL) { + if (signerKey->crv == WOLFCOSE_CRV_ED448) { + if (signerKey->key.ed448 == NULL) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } else { ret = wc_ed448_sign_msg(scratch, (word32)sigStructLen, sigBuf, &edSigSz, - signer->key->key.ed448, NULL, 0); + signerKey->key.ed448, NULL, 0); if (ret != 0) { ret = WOLFCOSE_E_CRYPTO; } @@ -5985,9 +6901,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, (signer->algId == WOLFCOSE_ALG_PS384) || (signer->algId == WOLFCOSE_ALG_PS512))) { int mgf = 0; - if (signer->key->key.rsa == NULL) { - ret = WOLFCOSE_E_COSE_KEY_TYPE; - } + ret = wolfCose_RsaPssCheckKey(signerKey, NULL); if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_HashToMgf(hashType, &mgf); } @@ -5998,7 +6912,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, ret = wc_RsaPSS_Sign_ex(hashBuf, (word32)hashLen, scratch, rsaSigLen, hashType, mgf, (int)hashLen, - signer->key->key.rsa, rng); + signerKey->key.rsa, rng); if (ret <= 0) { ret = WOLFCOSE_E_CRYPTO; } @@ -6019,7 +6933,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, size_t expectedSigSz = 0; /* RFC 9964: AKP key whose level matches the algorithm. */ if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_MlDsaCheckKey(signer->key, signer->algId); + ret = wolfCose_MlDsaCheckKey(signerKey, signer->algId); } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_SigSize(signer->algId, &expectedSigSz); @@ -6032,7 +6946,7 @@ int wc_CoseSign_Sign(const WOLFCOSE_SIGNATURE* signers, size_t signerCount, if (ret == WOLFCOSE_SUCCESS) { word32 dlSigLen = (word32)expectedSigSz; ret = wc_MlDsaKey_SignCtx( - signer->key->key.mldsa, NULL, 0, + signerKey->key.mldsa, NULL, 0, &scratch[sigStructLen], &dlSigLen, scratch, (word32)sigStructLen, rng); if (ret != 0) { @@ -6163,6 +7077,7 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, WOLFCOSE_HDR signerHdr; WOLFCOSE_HDR_STATE hdrState; WOLFCOSE_HDR_STATE signerHdrState; + int signerAlgProtected = 0; if ((verifyKey == NULL) || (in == NULL) || (scratch == NULL) || (hdr == NULL) || (payload == NULL) || (payloadLen == NULL)) { @@ -6254,7 +7169,7 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, /* Skip to the requested signer */ for (i = 0; (i < signerIndex) && (ret == WOLFCOSE_SUCCESS); i++) { - ret = wc_CBOR_Skip(&ctx); + ret = wolfCose_DecodeSkippedSignature(&ctx); } /* Parse the target COSE_Signature: [protected, unprotected, signature] */ @@ -6275,23 +7190,34 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, ret = wolfCose_DecodeProtectedHdr(signerProtectedData, signerProtectedLen, &signerHdr, &signerHdrState); if (ret == WOLFCOSE_SUCCESS) { - alg = signerHdr.alg; + signerAlgProtected = wolfCose_HdrStateContains(&signerHdrState, + WOLFCOSE_HDR_ALG); } } - /* Honour the verifyKey->alg pin. */ - if ((ret == WOLFCOSE_SUCCESS) && - (verifyKey->alg != WOLFCOSE_ALG_UNSET) && - (verifyKey->alg != alg)) { - ret = WOLFCOSE_E_COSE_BAD_ALG; - } - /* Signer unprotected headers. Decode with duplicate-label tracking so a * malformed signer header (repeated labels, or labels also present in the * signer protected bucket) is rejected. */ if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_DecodeUnprotectedHdr(&ctx, &signerHdr, &signerHdrState); } + if (ret == WOLFCOSE_SUCCESS) { + alg = signerHdr.alg; + } + + /* An unprotected alg is safe only when constrained by key policy. */ + if ((ret == WOLFCOSE_SUCCESS) && + (verifyKey->alg == WOLFCOSE_ALG_UNSET) && + (signerAlgProtected == 0)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + + /* Honour the verifyKey->alg pin. */ + if ((ret == WOLFCOSE_SUCCESS) && + (verifyKey->alg != WOLFCOSE_ALG_UNSET) && + (verifyKey->alg != alg)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } /* Signature */ if (ret == WOLFCOSE_SUCCESS) { @@ -6301,7 +7227,7 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, /* Skip remaining signers, then reject trailing data (RFC 8949 5.3.1). */ for (i = signerIndex + 1u; (i < signatureCount) && (ret == WOLFCOSE_SUCCESS); i++) { - ret = wc_CBOR_Skip(&ctx); + ret = wolfCose_DecodeSkippedSignature(&ctx); } if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { ret = WOLFCOSE_E_CBOR_MALFORMED; @@ -6355,6 +7281,7 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, if ((ret == WOLFCOSE_SUCCESS) && ((alg == WOLFCOSE_ALG_ES256) || (alg == WOLFCOSE_ALG_ES384) || (alg == WOLFCOSE_ALG_ES512))) { + ecc_key* eccKey = NULL; int verified = 0; size_t coordSz = 0; int32_t expectedCrv; @@ -6373,13 +7300,18 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, if ((ret == WOLFCOSE_SUCCESS) && (verifyKey->crv != expectedCrv)) { ret = WOLFCOSE_E_COSE_BAD_ALG; } + if (ret == WOLFCOSE_SUCCESS) { + eccKey = verifyKey->key.ecc; + ret = wolfCose_EccKeyCheckCurve(verifyKey->crv, + eccKey); + } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_CrvKeySize(verifyKey->crv, &coordSz); } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_EccVerifyRaw(signature, signatureLen, hashBuf, hashLen, coordSz, - verifyKey->key.ecc, &verified); + eccKey, &verified); } if ((ret == WOLFCOSE_SUCCESS) && (verified != 1)) { ret = WOLFCOSE_E_COSE_SIG_FAIL; @@ -6396,13 +7328,15 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, #ifdef WOLFCOSE_HAVE_EDDSA if ((ret == WOLFCOSE_SUCCESS) && (verifyKey->crv == WOLFCOSE_CRV_ED25519)) { - if (verifyKey->key.ed25519 == NULL) { + ed25519_key* ed25519Key = verifyKey->key.ed25519; + + if (ed25519Key == NULL) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } else { ret = wc_ed25519_verify_msg(signature, (word32)signatureLen, scratch, (word32)sigStructLen, - &verified, verifyKey->key.ed25519); + &verified, ed25519Key); if (ret != 0) { ret = WOLFCOSE_E_CRYPTO; } @@ -6413,13 +7347,15 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, #ifdef WOLFCOSE_HAVE_ED448 if ((ret == WOLFCOSE_SUCCESS) && (verifyKey->crv == WOLFCOSE_CRV_ED448)) { - if (verifyKey->key.ed448 == NULL) { + ed448_key* ed448Key = verifyKey->key.ed448; + + if (ed448Key == NULL) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } else { ret = wc_ed448_verify_msg(signature, (word32)signatureLen, scratch, (word32)sigStructLen, - &verified, verifyKey->key.ed448, + &verified, ed448Key, NULL, 0); if (ret != 0) { ret = WOLFCOSE_E_CRYPTO; @@ -6444,12 +7380,11 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, if ((ret == WOLFCOSE_SUCCESS) && ((alg == WOLFCOSE_ALG_PS256) || (alg == WOLFCOSE_ALG_PS384) || (alg == WOLFCOSE_ALG_PS512))) { + RsaKey* rsaKey = NULL; int mgf = 0; - if ((verifyKey->kty != WOLFCOSE_KTY_RSA) || - (verifyKey->key.rsa == NULL)) { - ret = WOLFCOSE_E_COSE_KEY_TYPE; - } + ret = wolfCose_RsaPssCheckKey(verifyKey, NULL); if (ret == WOLFCOSE_SUCCESS) { + rsaKey = verifyKey->key.rsa; ret = wolfCose_HashToMgf(hashType, &mgf); } if (ret == WOLFCOSE_SUCCESS) { @@ -6462,7 +7397,7 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, ret = wc_RsaPSS_VerifyCheck(scratch, (word32)signatureLen, scratch, (word32)scratchSz, hashBuf, (word32)hashLen, - hashType, mgf, verifyKey->key.rsa); + hashType, mgf, rsaKey); if (ret < 0) { ret = WOLFCOSE_E_COSE_SIG_FAIL; } @@ -6477,14 +7412,16 @@ int wc_CoseSign_Verify(const WOLFCOSE_KEY* verifyKey, if ((ret == WOLFCOSE_SUCCESS) && ((alg == WOLFCOSE_ALG_ML_DSA_44) || (alg == WOLFCOSE_ALG_ML_DSA_65) || (alg == WOLFCOSE_ALG_ML_DSA_87))) { + wc_MlDsaKey* mldsaKey = NULL; int verified = 0; /* RFC 9964: AKP key whose level matches the algorithm. */ if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_MlDsaCheckKey(verifyKey, alg); } if (ret == WOLFCOSE_SUCCESS) { + mldsaKey = verifyKey->key.mldsa; ret = wc_MlDsaKey_VerifyCtx( - verifyKey->key.mldsa, + mldsaKey, signature, (word32)signatureLen, NULL, 0, scratch, (word32)sigStructLen, @@ -6629,6 +7566,12 @@ int wc_CoseEncrypt0_Encrypt(const WOLFCOSE_KEY* key, int32_t alg, ret = wolfCose_AeadTagLen(alg, &aeadTagLen); } +#ifdef WOLFCOSE_HAVE_AESCCM + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_AeadCheckPayloadLen(alg, payloadLen); + } +#endif + /* For detached mode, need detachedLen output and sufficient buffer */ if ((ret == WOLFCOSE_SUCCESS) && (isDetached != 0) && ((detachedLen == NULL) || (detachedSz < (payloadLen + aeadTagLen)))) { @@ -6947,6 +7890,7 @@ int wc_CoseEncrypt0_Decrypt(const WOLFCOSE_KEY* key, size_t aeadTagLen = 0; size_t payloadSz = 0; int32_t alg = 0; + int algProtected = 0; if ((key == NULL) || (in == NULL) || (scratch == NULL) || (hdr == NULL) || (plaintext == NULL) || (plaintextLen == NULL)) { @@ -7001,6 +7945,10 @@ int wc_CoseEncrypt0_Decrypt(const WOLFCOSE_KEY* key, if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, &hdrState); + if (ret == WOLFCOSE_SUCCESS) { + algProtected = wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_ALG); + } } /* 2. Unprotected headers */ @@ -7043,6 +7991,10 @@ int wc_CoseEncrypt0_Decrypt(const WOLFCOSE_KEY* key, (key->alg != WOLFCOSE_ALG_UNSET) && (key->alg != alg)) { ret = WOLFCOSE_E_COSE_BAD_ALG; } + if ((ret == WOLFCOSE_SUCCESS) && + (key->alg == WOLFCOSE_ALG_UNSET) && (algProtected == 0)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_AeadKeyLen(alg, &aeadKeyLen); @@ -7063,7 +8015,13 @@ int wc_CoseEncrypt0_Decrypt(const WOLFCOSE_KEY* key, /* Payload size = ciphertext minus tag */ if (ret == WOLFCOSE_SUCCESS) { payloadSz = ciphertextLen - aeadTagLen; - if (payloadSz > plaintextSz) { +#ifdef WOLFCOSE_HAVE_AESCCM + if (wolfCose_AeadCheckPayloadLen(alg, payloadSz) != + WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } +#endif + if ((ret == WOLFCOSE_SUCCESS) && (payloadSz > plaintextSz)) { ret = WOLFCOSE_E_BUFFER_TOO_SMALL; } } @@ -7216,8 +8174,10 @@ int wc_CoseEncrypt0_Decrypt(const WOLFCOSE_KEY* key, * Supports HMAC (RFC 9053 Section 3.1) and AES-CBC-MAC (RFC 9053 Section 3.2) * ----- */ -#if defined(WOLFCOSE_MAC0) && (defined(WOLFCOSE_HAVE_HMAC) || defined(WOLFCOSE_HAVE_AESMAC)) +#if (defined(WOLFCOSE_MAC0) || defined(WOLFCOSE_MAC)) && \ + (defined(WOLFCOSE_HAVE_HMAC) || defined(WOLFCOSE_HAVE_AESMAC)) +#if defined(WOLFCOSE_MAC0) /** * Build the MAC_structure for COSE_Mac0 (wrapper for unified builder): * ["MAC0", body_protected, external_aad, payload] @@ -7240,6 +8200,7 @@ static int wolfCose_BuildMacStructure(const uint8_t* protectedHdr, payload, payloadLen, scratch, scratchSz, structLen); } +#endif /** * Get MAC tag size for a COSE MAC algorithm (HMAC or AES-CBC-MAC). @@ -7428,23 +8389,34 @@ static int wolfCose_AesCbcMac(const uint8_t* key, size_t keyLen, } #endif /* WOLFCOSE_HAVE_AESMAC */ +#ifdef WOLFCOSE_HAVE_HMAC /** * Check if algorithm is HMAC-based. */ static int wolfCose_IsHmacAlg(int32_t alg) { - return ((0) + int isHmac = 0; + + switch (alg) { #ifdef WOLFCOSE_HAVE_HMAC256 - || (alg == WOLFCOSE_ALG_HMAC_256_256) + case WOLFCOSE_ALG_HMAC_256_256: #endif #ifdef WOLFCOSE_HAVE_HMAC384 - || (alg == WOLFCOSE_ALG_HMAC_384_384) + case WOLFCOSE_ALG_HMAC_384_384: #endif #ifdef WOLFCOSE_HAVE_HMAC512 - || (alg == WOLFCOSE_ALG_HMAC_512_512) + case WOLFCOSE_ALG_HMAC_512_512: #endif - ) ? 1 : 0; + isHmac = 1; + break; + default: + /* No action required. */ + break; + } + + return isHmac; } +#endif /* WOLFCOSE_HAVE_HMAC */ #ifdef WOLFCOSE_HAVE_AESMAC /** @@ -7724,6 +8696,7 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, WOLFCOSE_HDR_STATE hdrState; const uint8_t* verifyPayload = NULL; size_t verifyPayloadLen = 0; + int algProtected = 0; if ((key == NULL) || (in == NULL) || (scratch == NULL) || (hdr == NULL) || (payload == NULL) || (payloadLen == NULL)) { @@ -7777,6 +8750,10 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, &hdrState); + if (ret == WOLFCOSE_SUCCESS) { + algProtected = wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_ALG); + } } /* 2. Unprotected headers (map) */ @@ -7828,6 +8805,10 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, ret = WOLFCOSE_E_COSE_BAD_ALG; } } + if ((ret == WOLFCOSE_SUCCESS) && + (key->alg == WOLFCOSE_ALG_UNSET) && (algProtected == 0)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_MacTagSize(alg, &expectedTagSz); } @@ -7946,7 +8927,7 @@ int wc_CoseMac0_Verify(const WOLFCOSE_KEY* key, } #endif /* WOLFCOSE_MAC0_VERIFY */ -#endif /* WOLFCOSE_MAC0 && (WOLFCOSE_HAVE_HMAC || WOLFCOSE_HAVE_AESMAC) */ +#endif /* (WOLFCOSE_MAC0 || WOLFCOSE_MAC) && MAC algorithm */ /* ----- COSE_Encrypt Multi-Recipient API (RFC 9052 Section 5.1) ----- */ @@ -7997,11 +8978,12 @@ static int wolfCose_ValidateRecipientKeyAlg(const WOLFCOSE_KEY* key, * wc_CoseEncrypt_Encrypt - Create a COSE_Encrypt message (RFC 9052 Section 5.1) * * Structure: [Headers, ciphertext, recipients: [+ COSE_recipient]] - * Each COSE_recipient: [Headers, wrapped_cek] + * Each COSE_recipient: [protected, unprotected, ciphertext] * - * For simplicity, this implementation uses direct key (no key wrap): - * - The content encryption key (CEK) is pre-shared or derived externally - * - Recipients array contains header-only entries with no wrapped key + * Recipient key management supports direct, AES Key Wrap, and ECDH-ES direct + * modes when enabled. Direct uses a pre-shared CEK. AES Key Wrap generates one + * CEK and wraps it separately for each recipient. ECDH-ES derives the CEK for + * one recipient and carries the ephemeral public key in its unprotected header. */ int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, size_t recipientCount, @@ -8100,6 +9082,12 @@ int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, ret = wolfCose_AeadTagLen(contentAlgId, &aeadTagLen); } +#ifdef WOLFCOSE_HAVE_AESCCM + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_AeadCheckPayloadLen(contentAlgId, encryptPayloadLen); + } +#endif + /* Validate nonce length matches algorithm spec */ if (ret == WOLFCOSE_SUCCESS) { size_t expectedNonceLen; @@ -8130,7 +9118,9 @@ int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, ret = WOLFCOSE_E_COSE_BAD_ALG; } else { - recipientCrv = recipients[0].key->crv; + WOLFCOSE_KEY* recipientKey = recipients[0].key; + + recipientCrv = recipientKey->crv; /* Pre-encode recipient protected hdr for KDF context. */ ret = wolfCose_EncodeProtectedHdr(recipients[0].algId, @@ -8141,7 +9131,7 @@ int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_EcdhEsDirect( recipients[0].algId, - recipients[0].key, + recipientKey, contentAlgId, keyLen, recipientProtectedBuf, recipientProtectedLen, @@ -8221,9 +9211,7 @@ int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < recipientCount); i++) { /* Direct mode requires an explicit WOLFCOSE_ALG_DIRECT so a * zero-initialized (WOLFCOSE_ALG_UNSET) algId cannot silently - * select the direct-CEK construction. The decrypt path still - * accepts an empty (UNSET) recipient header, which is the on-wire - * representation of a direct recipient. */ + * select the direct-CEK construction. */ if (recipients[i].algId != WOLFCOSE_ALG_DIRECT) { ret = WOLFCOSE_E_COSE_BAD_ALG; } @@ -8415,8 +9403,7 @@ int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, /* Encode recipient protected header. RFC 9053 Section 6.1: the direct * key algorithm uses a zero-length protected header, so treat an * explicit WOLFCOSE_ALG_DIRECT the same as the unset direct case. */ - if ((recipients[i].algId != WOLFCOSE_ALG_UNSET) && - (recipients[i].algId != WOLFCOSE_ALG_DIRECT)) { + if (recipients[i].algId != WOLFCOSE_ALG_DIRECT) { ret = wolfCose_EncodeProtectedHdr(recipients[i].algId, recipientProtectedBuf, sizeof(recipientProtectedBuf), &recipientProtectedLen); @@ -8465,18 +9452,36 @@ int wc_CoseEncrypt_Encrypt(const WOLFCOSE_RECIPIENT* recipients, } else #endif - if ((recipients[i].kid != NULL) && (recipients[i].kidLen > 0u)) { - ret = wc_CBOR_EncodeMapStart(&ctx, 1u); - if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_KID); + if (ret == WOLFCOSE_SUCCESS) { + size_t mapEntries = 0u; + + if (recipients[i].algId == WOLFCOSE_ALG_DIRECT) { + mapEntries++; } - if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_EncodeBstr(&ctx, recipients[i].kid, - recipients[i].kidLen); + if ((recipients[i].kid != NULL) && + (recipients[i].kidLen > 0u)) { + mapEntries++; + } + ret = wc_CBOR_EncodeMapStart(&ctx, mapEntries); + if ((ret == WOLFCOSE_SUCCESS) && + (recipients[i].algId == WOLFCOSE_ALG_DIRECT)) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_ALG); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_ALG_DIRECT); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + (recipients[i].kid != NULL) && + (recipients[i].kidLen > 0u)) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_KID); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&ctx, recipients[i].kid, + recipients[i].kidLen); + } } - } else { - /* Empty map */ - ret = wc_CBOR_EncodeMapStart(&ctx, 0u); + } + else { + /* Encoding stopped after an earlier error. */ } } @@ -8553,6 +9558,7 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, size_t protectedLen = 0; const uint8_t* ciphertext = NULL; size_t ciphertextLen = 0; + int ciphertextIsNull = 0; size_t encStructLen = 0; size_t recipientsCount = 0; size_t i; @@ -8568,8 +9574,11 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, const uint8_t* recipientProtectedData = NULL; size_t recipientProtectedLen = 0; int32_t recipientAlgId = 0; + int recipientMode = 0; + WOLFCOSE_HDR recipientHdr; WOLFCOSE_HDR_STATE hdrState; WOLFCOSE_HDR_STATE recipientHdrState; + int bodyAlgProtected = 0; #if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && defined(HAVE_HKDF) uint8_t cek[32]; uint8_t ephemPubX[66]; @@ -8637,15 +9646,19 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, &hdrState); - } - if (ret == WOLFCOSE_SUCCESS) { - alg = hdr->alg; + if (ret == WOLFCOSE_SUCCESS) { + bodyAlgProtected = wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_ALG); + } } /* [1] unprotected header */ if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_DecodeUnprotectedHdr(&ctx, hdr, &hdrState); } + if (ret == WOLFCOSE_SUCCESS) { + alg = hdr->alg; + } /* Validate IV */ if (ret == WOLFCOSE_SUCCESS) { @@ -8658,11 +9671,16 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, /* [2] ciphertext */ if (ret == WOLFCOSE_SUCCESS) { + ciphertextIsNull = 0; + if ((ctx.idx < ctx.bufSz) && + (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { + ciphertextIsNull = 1; + } ret = wolfCose_CBOR_DecodeHead(&ctx, &item); } if (ret == WOLFCOSE_SUCCESS) { - if ((item.majorType == WOLFCOSE_CBOR_SIMPLE) && (item.val == 22u)) { + if (ciphertextIsNull != 0) { if (detachedCt == NULL) { hdr->flags |= WOLFCOSE_HDR_FLAG_DETACHED; ret = WOLFCOSE_E_DETACHED_PAYLOAD; @@ -8692,7 +9710,12 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, /* Skip to requested recipient */ for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < recipientIndex); i++) { - ret = wc_CBOR_Skip(&ctx); + int32_t skippedAlg = WOLFCOSE_ALG_UNSET; + + ret = wolfCose_DecodeSkippedRecipient(&ctx, &skippedAlg); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_UpdateRecipientMode(skippedAlg, &recipientMode); + } } /* Parse recipient array */ @@ -8707,78 +9730,20 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, * unprotected decode below can run cross-bucket duplicate checks even when * the protected bucket is an empty bstr. */ if (ret == WOLFCOSE_SUCCESS) { + (void)XMEMSET(&recipientHdr, 0, sizeof(recipientHdr)); wolfCose_HdrStateInit(&recipientHdrState); ret = wc_CBOR_DecodeBstr(&ctx, &recipientProtectedData, &recipientProtectedLen); } if ((ret == WOLFCOSE_SUCCESS) && (recipientProtectedLen > 0u)) { - WOLFCOSE_HDR recipientHdr; - (void)XMEMSET(&recipientHdr, 0, sizeof(recipientHdr)); ret = wolfCose_DecodeProtectedHdr(recipientProtectedData, recipientProtectedLen, &recipientHdr, &recipientHdrState); - if (ret == WOLFCOSE_SUCCESS) { - recipientAlgId = recipientHdr.alg; - } - } - if (ret == WOLFCOSE_SUCCESS) { - ret = wolfCose_ValidateRecipientKeyAlg(recipient->key, recipientAlgId, - alg); - } - - /* Classify the recipient key-management algorithm. Only direct, ECDH-ES - * direct, and AES key wrap are supported; reject anything else instead of - * silently treating it as direct-key decryption. */ - if (ret == WOLFCOSE_SUCCESS) { - int recipModeOk = 0; - if ((recipientAlgId == WOLFCOSE_ALG_UNSET) || - (recipientAlgId == WOLFCOSE_ALG_DIRECT)) { - recipModeOk = 1; - } -#if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && defined(HAVE_HKDF) - else if (wolfCose_IsEcdhEsDirectAlg(recipientAlgId) != 0) { - recipModeOk = 1; - } -#endif -#if defined(WOLFCOSE_KEY_WRAP) - else if (wolfCose_IsKeyWrapAlg(recipientAlgId) != 0) { - recipModeOk = 1; - } -#endif - else { - /* No action required */ - } - if (recipModeOk == 0) { - ret = WOLFCOSE_E_COSE_BAD_ALG; - } - } - - /* Enforce the caller's recipient->algId policy when set. A message in - * implicit direct mode has no recipient alg, so normalize it to direct. */ - if ((ret == WOLFCOSE_SUCCESS) && - (recipient->algId != WOLFCOSE_ALG_UNSET)) { - int32_t gotAlg = recipientAlgId; - if (gotAlg == WOLFCOSE_ALG_UNSET) { - gotAlg = WOLFCOSE_ALG_DIRECT; - } - if (recipient->algId != gotAlg) { - ret = WOLFCOSE_E_COSE_BAD_ALG; - } - } - -#if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && defined(HAVE_HKDF) - /* RFC 9052 Section 8.5.5: direct key agreement carries exactly one - * recipient. */ - if ((ret == WOLFCOSE_SUCCESS) && - (wolfCose_IsEcdhEsDirectAlg(recipientAlgId) != 0) && - ((recipientsCount != 1u) || (recipientIndex != 0u))) { - ret = WOLFCOSE_E_COSE_BAD_HDR; } -#endif /* [1] recipient unprotected header */ #if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && defined(HAVE_HKDF) if ((ret == WOLFCOSE_SUCCESS) && - (wolfCose_IsEcdhEsDirectAlg(recipientAlgId) != 0)) { + (wolfCose_IsEcdhEsDirectAlg(recipientHdr.alg) != 0)) { size_t mapCount = 0; size_t j; @@ -8842,15 +9807,86 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, if (ret == WOLFCOSE_SUCCESS) { /* Decode the recipient unprotected map with duplicate-label tracking * (within the map and against the recipient protected bucket). */ - WOLFCOSE_HDR recipUnprotHdr; - (void)XMEMSET(&recipUnprotHdr, 0, sizeof(recipUnprotHdr)); - ret = wolfCose_DecodeUnprotectedHdr(&ctx, &recipUnprotHdr, + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &recipientHdr, &recipientHdrState); } else { /* No action required */ } + if (ret == WOLFCOSE_SUCCESS) { + recipientAlgId = recipientHdr.alg; + ret = wolfCose_ValidateRecipientKeyAlg(recipient->key, recipientAlgId, + alg); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_UpdateRecipientMode(recipientAlgId, &recipientMode); + } + + /* Classify the recipient key-management algorithm. Only direct, ECDH-ES + * direct, and AES key wrap are supported; reject anything else instead of + * silently treating it as direct-key decryption. */ + if (ret == WOLFCOSE_SUCCESS) { + int recipModeOk = 0; + if (recipientAlgId == WOLFCOSE_ALG_DIRECT) { + recipModeOk = 1; + } +#if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && defined(HAVE_HKDF) + else if (wolfCose_IsEcdhEsDirectAlg(recipientAlgId) != 0) { + recipModeOk = 1; + } +#endif +#if defined(WOLFCOSE_KEY_WRAP) + else if (wolfCose_IsKeyWrapAlg(recipientAlgId) != 0) { + recipModeOk = 1; + } +#endif + else { + /* No action required */ + } + if (recipModeOk == 0) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + } + +#if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && \ + defined(HAVE_HKDF) + /* The ECDH parser must have consumed an ephemeral key before the merged + * unprotected alg can classify this recipient as ECDH-ES. */ + if ((ret == WOLFCOSE_SUCCESS) && + (wolfCose_IsEcdhEsDirectAlg(recipientAlgId) != 0) && + (useEcdhEs == 0)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } +#endif + + /* Enforce the caller's recipient->algId policy when set. */ + if ((ret == WOLFCOSE_SUCCESS) && + (recipient->algId != WOLFCOSE_ALG_UNSET)) { + if (recipient->algId != recipientAlgId) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + } + + /* An unprotected content algorithm is safe only when direct-key policy + * independently pins the same algorithm. Other recipient modes pin their + * key-management algorithm, not the content algorithm. */ + if ((ret == WOLFCOSE_SUCCESS) && (bodyAlgProtected == 0) && + ((recipientAlgId != WOLFCOSE_ALG_DIRECT) || + (recipient->key == NULL) || (recipient->key->alg != alg))) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + +#if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && defined(HAVE_HKDF) + /* RFC 9052 Section 8.5.5: direct key agreement carries exactly one + * recipient. */ + if ((ret == WOLFCOSE_SUCCESS) && + (wolfCose_IsEcdhEsDirectAlg(recipientAlgId) != 0) && + ((recipientsCount != 1u) || (recipientIndex != 0u))) { + ret = WOLFCOSE_E_COSE_BAD_HDR; + } +#endif + /* [2] wrapped CEK */ #if defined(WOLFCOSE_KEY_WRAP) if ((ret == WOLFCOSE_SUCCESS) && @@ -8866,7 +9902,25 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, else #endif if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_Skip(&ctx); + int recipientValueIsNull = 0; + + if ((ctx.idx < ctx.bufSz) && + (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { + recipientValueIsNull = 1; + } + ret = wolfCose_CBOR_DecodeHead(&ctx, &item); + if ((ret == WOLFCOSE_SUCCESS) && + (recipientValueIsNull == 0) && + (item.majorType != WOLFCOSE_CBOR_BSTR)) { + ret = WOLFCOSE_E_CBOR_TYPE; + } + else if ((ret == WOLFCOSE_SUCCESS) && + (recipientValueIsNull == 0) && (item.dataLen != 0u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + /* No action required */ + } } else { /* No action required */ @@ -8875,7 +9929,12 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, /* Skip remaining recipients, then reject trailing data (RFC 8949 5.3.1). */ for (i = recipientIndex + 1u; (ret == WOLFCOSE_SUCCESS) && (i < recipientsCount); i++) { - ret = wc_CBOR_Skip(&ctx); + int32_t skippedAlg = WOLFCOSE_ALG_UNSET; + + ret = wolfCose_DecodeSkippedRecipient(&ctx, &skippedAlg); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_UpdateRecipientMode(skippedAlg, &recipientMode); + } } if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { ret = WOLFCOSE_E_CBOR_MALFORMED; @@ -8892,10 +9951,12 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, /* Derive/validate decryption key */ #if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && defined(HAVE_HKDF) if ((ret == WOLFCOSE_SUCCESS) && (useEcdhEs != 0)) { - if ((recipient->key == NULL) || - (recipient->key->kty != WOLFCOSE_KTY_EC2) || - (recipient->key->key.ecc == NULL) || - (recipient->key->hasPrivate != 1u)) { + WOLFCOSE_KEY* recipientKey = recipient->key; + + if ((recipientKey == NULL) || + (recipientKey->kty != WOLFCOSE_KTY_EC2) || + (recipientKey->key.ecc == NULL) || + (recipientKey->hasPrivate != 1u)) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } /* X and Y coordinates for the ephemeral key must have matching @@ -8906,10 +9967,11 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, } if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_EcdhEsDirectRecv( - recipientAlgId, recipient->key, + recipientAlgId, recipientKey, ephemPubX, ephemPubY, ephemPubXLen, alg, keyLen, recipientProtectedData, recipientProtectedLen, + scratch, scratchSz, cek, sizeof(cek)); } if (ret == WOLFCOSE_SUCCESS) { @@ -8960,7 +10022,13 @@ int wc_CoseEncrypt_Decrypt(const WOLFCOSE_RECIPIENT* recipient, } if (ret == WOLFCOSE_SUCCESS) { payloadLen = ciphertextLen - aeadTagLen; - if (payloadLen > plaintextSz) { +#ifdef WOLFCOSE_HAVE_AESCCM + if (wolfCose_AeadCheckPayloadLen(alg, payloadLen) != + WOLFCOSE_SUCCESS) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } +#endif + if ((ret == WOLFCOSE_SUCCESS) && (payloadLen > plaintextSz)) { ret = WOLFCOSE_E_CBOR_OVERFLOW; } } @@ -9366,21 +10434,27 @@ int wc_CoseMac_Create(const WOLFCOSE_RECIPIENT* recipients, ret = wc_CBOR_EncodeBstr(&ctx, NULL, 0); } - /* [1] unprotected header map (with kid if present) */ + /* [1] unprotected header map. The direct algorithm is mandatory and + * belongs in the unprotected bucket (RFC 9053 Section 6.1). */ if (ret == WOLFCOSE_SUCCESS) { - if ((recipients[i].kid != NULL) && (recipients[i].kidLen > 0u)) { - ret = wc_CBOR_EncodeMapStart(&ctx, 1u); - if (ret == WOLFCOSE_SUCCESS) { - ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_KID); - } + ret = wc_CBOR_EncodeMapStart(&ctx, + ((recipients[i].kid != NULL) && + (recipients[i].kidLen > 0u)) ? 2u : 1u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_ALG); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_ALG_DIRECT); + } + if ((ret == WOLFCOSE_SUCCESS) && + (recipients[i].kid != NULL) && + (recipients[i].kidLen > 0u)) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_KID); if (ret == WOLFCOSE_SUCCESS) { ret = wc_CBOR_EncodeBstr(&ctx, recipients[i].kid, - recipients[i].kidLen); + recipients[i].kidLen); } } - else { - ret = wc_CBOR_EncodeMapStart(&ctx, 0u); - } } /* [2] wrapped key (empty for direct key) */ @@ -9437,10 +10511,12 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, size_t i; int32_t alg = 0; int32_t recipientAlgId = WOLFCOSE_ALG_UNSET; + int recipientMode = 0; size_t macStructLen = 0; size_t expectedTagLen = 0; uint8_t computedTag[WC_MAX_DIGEST_SIZE]; WOLFCOSE_HDR_STATE hdrState; + int bodyAlgProtected = 0; #ifdef WOLFCOSE_HAVE_HMAC Hmac hmac; int hashType = 0; @@ -9448,6 +10524,9 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, #endif const uint8_t* verifyPayload = NULL; size_t verifyPayloadLen = 0; + int payloadIsNull = 0; + int recipientValueIsNull = 0; + size_t recipientValueLen = 0; /* Parameter validation */ if ((recipient == NULL) || (in == NULL) || (inSz == 0u) || @@ -9501,23 +10580,32 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_DecodeProtectedHdr(protectedData, protectedLen, hdr, &hdrState); - } - if (ret == WOLFCOSE_SUCCESS) { - alg = hdr->alg; + if (ret == WOLFCOSE_SUCCESS) { + bodyAlgProtected = wolfCose_HdrStateContains(&hdrState, + WOLFCOSE_HDR_ALG); + } } /* [1] Decode unprotected header */ if (ret == WOLFCOSE_SUCCESS) { ret = wolfCose_DecodeUnprotectedHdr(&ctx, hdr, &hdrState); } + if (ret == WOLFCOSE_SUCCESS) { + alg = hdr->alg; + } /* [2] Decode payload */ if (ret == WOLFCOSE_SUCCESS) { + payloadIsNull = 0; + if ((ctx.idx < ctx.bufSz) && + (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { + payloadIsNull = 1; + } ret = wolfCose_CBOR_DecodeHead(&ctx, &item); } if (ret == WOLFCOSE_SUCCESS) { - if ((item.majorType == WOLFCOSE_CBOR_SIMPLE) && (item.val == 22u)) { + if (payloadIsNull != 0) { /* Null - detached payload */ if (detachedPayload == NULL) { hdr->flags |= WOLFCOSE_HDR_FLAG_DETACHED; @@ -9559,7 +10647,12 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, /* Skip to the requested recipient */ for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < recipientIndex); i++) { - ret = wc_CBOR_Skip(&ctx); + int32_t skippedAlg = WOLFCOSE_ALG_UNSET; + + ret = wolfCose_DecodeSkippedRecipient(&ctx, &skippedAlg); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_UpdateRecipientMode(skippedAlg, &recipientMode); + } } /* Parse the selected COSE_recipient: [protected, unprotected, ciphertext]. @@ -9589,18 +10682,24 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, } if (ret == WOLFCOSE_SUCCESS) { recipientAlgId = recipHdr.alg; + ret = wolfCose_UpdateRecipientMode(recipientAlgId, + &recipientMode); } - /* ciphertext: bstr (wrapped key) or nil (direct). */ + /* Parse the recipient ciphertext before classifying its algorithm. */ if (ret == WOLFCOSE_SUCCESS) { + recipientValueIsNull = 0; + if ((ctx.idx < ctx.bufSz) && + (ctx.cbuf[ctx.idx] == WOLFCOSE_CBOR_NULL)) { + recipientValueIsNull = 1; + } ret = wolfCose_CBOR_DecodeHead(&ctx, &item); } if (ret == WOLFCOSE_SUCCESS) { - if ((item.majorType == WOLFCOSE_CBOR_SIMPLE) && - (item.val == 22u)) { - /* nil - direct MAC, no wrapped key */ + if (recipientValueIsNull != 0) { + /* Validate after recipient algorithm classification. */ } else if (item.majorType == WOLFCOSE_CBOR_BSTR) { - /* bstr - wrapped key present (unused with provided key) */ + recipientValueLen = item.dataLen; } else { ret = WOLFCOSE_E_CBOR_TYPE; @@ -9611,7 +10710,12 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, /* Skip remaining recipients, then reject trailing data (RFC 8949 5.3.1). */ for (i = recipientIndex + 1u; (ret == WOLFCOSE_SUCCESS) && (i < recipientsCount); i++) { - ret = wc_CBOR_Skip(&ctx); + int32_t skippedAlg = WOLFCOSE_ALG_UNSET; + + ret = wolfCose_DecodeSkippedRecipient(&ctx, &skippedAlg); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_UpdateRecipientMode(skippedAlg, &recipientMode); + } } if ((ret == WOLFCOSE_SUCCESS) && (ctx.idx != ctx.bufSz)) { ret = WOLFCOSE_E_CBOR_MALFORMED; @@ -9623,29 +10727,42 @@ int wc_CoseMac_Verify(const WOLFCOSE_RECIPIENT* recipient, (recipient->key->kty != WOLFCOSE_KTY_SYMMETRIC))) { ret = WOLFCOSE_E_COSE_KEY_TYPE; } + /* An unprotected alg is safe only when constrained by key policy. */ + if ((ret == WOLFCOSE_SUCCESS) && + (recipient->key->alg == WOLFCOSE_ALG_UNSET) && + (bodyAlgProtected == 0)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } if ((ret == WOLFCOSE_SUCCESS) && (recipient->key->alg != WOLFCOSE_ALG_UNSET) && (recipient->key->alg != alg)) { ret = WOLFCOSE_E_COSE_BAD_ALG; } - /* COSE_Mac is direct-keyed here: the only supported recipient modes are an - * absent (UNSET) or explicit WOLFCOSE_ALG_DIRECT alg. A recipient that - * advertises a key-distribution mode is not silently accepted. */ - if ((ret == WOLFCOSE_SUCCESS) && - (recipientAlgId != WOLFCOSE_ALG_UNSET) && - (recipientAlgId != WOLFCOSE_ALG_DIRECT)) { - ret = WOLFCOSE_E_UNSUPPORTED; + /* COSE_Mac is direct-keyed here and the recipient algorithm is mandatory. */ + if (ret == WOLFCOSE_SUCCESS) { + if (recipientAlgId == WOLFCOSE_ALG_UNSET) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + else if (recipientAlgId != WOLFCOSE_ALG_DIRECT) { + ret = WOLFCOSE_E_UNSUPPORTED; + } + else { + /* No action required */ + } + } + if (ret == WOLFCOSE_SUCCESS) { + if ((recipientValueIsNull == 0) && (recipientValueLen != 0u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + else { + /* No action required */ + } } - /* Enforce the caller's recipient->algId policy when set, normalizing an - * absent recipient alg to direct (matches wc_CoseEncrypt_Decrypt). */ + /* Enforce the caller's recipient->algId policy when set. */ if ((ret == WOLFCOSE_SUCCESS) && (recipient->algId != WOLFCOSE_ALG_UNSET)) { - int32_t gotAlg = recipientAlgId; - if (gotAlg == WOLFCOSE_ALG_UNSET) { - gotAlg = WOLFCOSE_ALG_DIRECT; - } - if (recipient->algId != gotAlg) { + if (recipient->algId != recipientAlgId) { ret = WOLFCOSE_E_COSE_BAD_ALG; } } diff --git a/src/wolfcose_cbor.c b/src/wolfcose_cbor.c index c4cd7b8..898ab7e 100644 --- a/src/wolfcose_cbor.c +++ b/src/wolfcose_cbor.c @@ -388,7 +388,7 @@ int wc_CBOR_EncodeFloat(WOLFCOSE_CBOR_CTX* ctx, float val) ret = WOLFCOSE_E_BUFFER_TOO_SMALL; } else { - (void)XMEMCPY(&bits, &val, sizeof(bits)); + (void)XMEMCPY((void*)&bits, (const void*)&val, sizeof(bits)); ctx->buf[ctx->idx] = (uint8_t)((WOLFCOSE_CBOR_SIMPLE << 5) | WOLFCOSE_CBOR_AI_FLOAT32); wolfCose_StoreBE32(&ctx->buf[ctx->idx + 1u], bits); @@ -410,7 +410,7 @@ int wc_CBOR_EncodeDouble(WOLFCOSE_CBOR_CTX* ctx, double val) ret = WOLFCOSE_E_BUFFER_TOO_SMALL; } else { - (void)XMEMCPY(&bits, &val, sizeof(bits)); + (void)XMEMCPY((void*)&bits, (const void*)&val, sizeof(bits)); ctx->buf[ctx->idx] = (uint8_t)((WOLFCOSE_CBOR_SIMPLE << 5) | WOLFCOSE_CBOR_AI_FLOAT64); wolfCose_StoreBE64(&ctx->buf[ctx->idx + 1u], bits); @@ -773,12 +773,12 @@ int wc_CBOR_LabelIsText(const WOLFCOSE_CBOR_LABEL* label, const uint8_t* text, * difference. Same OR-accumulate shape as the MAC tag compare in * wolfcose.c; volatile keeps the loop from becoming an early * exit. */ - volatile unsigned int diff = 0; + volatile uint32_t diff = 0u; size_t i; for (i = 0u; i < textLen; i++) { - diff |= (unsigned int)label->text[i] ^ - (unsigned int)text[i]; + diff |= ((uint32_t)label->text[i] ^ + (uint32_t)text[i]); } if (diff == 0u) { match = 1; diff --git a/src/wolfcose_internal.h b/src/wolfcose_internal.h index f6b9bad..6f97dc1 100644 --- a/src/wolfcose_internal.h +++ b/src/wolfcose_internal.h @@ -34,6 +34,23 @@ #include #endif +/* ECC private-material import is unavailable when wolfCrypt uses an + * incompatible math layout or cannot roll back accepted or provisioned key + * material. */ +#if defined(HAVE_ECC) && defined(WOLFCOSE_KEY_DECODE) && \ + (defined(WOLFSSL_CRYPTOCELL) || defined(WOLFSSL_QNX_CAAM) || \ + defined(WOLFSSL_IMXRT1170_CAAM) || \ + defined(WOLFSSL_SILABS_SE_ACCEL) || \ + defined(WOLFSSL_MAXQ10XX_CRYPTO) || \ + (defined(ALT_ECC_SIZE) && defined(USE_FAST_MATH) && \ + defined(HAVE_WOLF_BIGINT)) || \ + (defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_SETKEY) && \ + defined(WOLF_CRYPTO_CB_FIND)) || \ + (defined(WOLFCOSE_FORCE_FAILURE) && \ + defined(WOLFCOSE_TEST_NONTRANSACTIONAL_ECC_IMPORT))) + #define WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED +#endif + #ifdef __cplusplus extern "C" { #endif @@ -288,6 +305,7 @@ WOLFCOSE_LOCAL int wolfCose_HmacType(int32_t alg, int* hmacType); * \param sigBuf Output: raw r||s signature. * \param sigLen In/Out: buffer size / bytes written. * \param coordSz Coordinate size for this curve (e.g., 32 for P-256). + * \param hashType Hash type for optional deterministic k generation. * \param rng Initialized WC_RNG. * \param eccKey Caller-owned ECC key with private key. * \return WOLFCOSE_SUCCESS or negative error code. @@ -296,6 +314,7 @@ WOLFCOSE_LOCAL int wolfCose_HmacType(int32_t alg, int* hmacType); WOLFCOSE_LOCAL int wolfCose_EccSignRaw(const uint8_t* hash, size_t hashLen, uint8_t* sigBuf, size_t* sigLen, size_t coordSz, + enum wc_HashType hashType, WC_RNG* rng, ecc_key* eccKey); #endif /* WOLFCOSE_SIGN1_SIGN || WOLFCOSE_SIGN_SIGN */ diff --git a/tests/force_failure.h b/tests/force_failure.h index 2b86e36..8ee4602 100644 --- a/tests/force_failure.h +++ b/tests/force_failure.h @@ -47,6 +47,7 @@ typedef enum { WOLF_FAIL_ECC_VERIFY, /* wc_ecc_verify_hash */ WOLF_FAIL_ECC_EXPORT_X963, /* wc_ecc_export_x963 */ WOLF_FAIL_ECC_IMPORT_X963, /* wc_ecc_import_x963 */ + WOLF_FAIL_ECC_IMPORT_PRIVATE_POST, /* after private-only ECC import */ WOLF_FAIL_ECC_EXPORT_PRIVATE, /* wc_ecc_export_private_only */ /* RSA failures */ @@ -64,12 +65,14 @@ typedef enum { WOLF_FAIL_ED25519_EXPORT_PRIV, /* wc_ed25519_export_private_only */ WOLF_FAIL_ED25519_IMPORT_PUB, /* wc_ed25519_import_public */ WOLF_FAIL_ED25519_IMPORT_PRIV, /* wc_ed25519_import_private_key */ + WOLF_FAIL_ED25519_MAKE_PUB, /* wc_ed25519_make_public */ WOLF_FAIL_ED448_SIGN, /* wc_ed448_sign_msg */ WOLF_FAIL_ED448_VERIFY, /* wc_ed448_verify_msg */ WOLF_FAIL_ED448_EXPORT_PUB, /* wc_ed448_export_public */ WOLF_FAIL_ED448_EXPORT_PRIV, /* wc_ed448_export_private_only */ WOLF_FAIL_ED448_IMPORT_PUB, /* wc_ed448_import_public */ WOLF_FAIL_ED448_IMPORT_PRIV, /* wc_ed448_import_private_key */ + WOLF_FAIL_ED448_MAKE_PUB, /* wc_ed448_make_public */ /* ML-DSA failures */ WOLF_FAIL_MLDSA_SIGN, /* wc_MlDsaKey_SignCtx */ diff --git a/tests/misra_consumer.c b/tests/misra_consumer.c new file mode 100644 index 0000000..ca25de1 --- /dev/null +++ b/tests/misra_consumer.c @@ -0,0 +1,160 @@ +/* Analyzer-only consumer for whole-program MISRA checks. + * + * This translation unit models a downstream application. It is parsed by + * cppcheck but is never linked or shipped. Public symbols are referenced so + * Rule 8.7 is evaluated at the library boundary instead of one source file. + */ + +#include + +#define WOLFCOSE_MISRA_USE_API(name) total += sizeof(&(name)) + +static size_t wolfCose_MisraUsePublicApis(void) +{ + size_t total = 0u; + + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeUint); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeInt); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeBstr); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeTstr); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeArrayStart); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeMapStart); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeTag); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeTrue); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeFalse); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeNull); +#ifdef WOLFCOSE_FLOAT + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeFloat); + WOLFCOSE_MISRA_USE_API(wc_CBOR_EncodeDouble); +#endif + WOLFCOSE_MISRA_USE_API(wc_CBOR_DecodeHead); + WOLFCOSE_MISRA_USE_API(wc_CBOR_DecodeUint); + WOLFCOSE_MISRA_USE_API(wc_CBOR_DecodeInt); + WOLFCOSE_MISRA_USE_API(wc_CBOR_DecodeBstr); + WOLFCOSE_MISRA_USE_API(wc_CBOR_DecodeTstr); + WOLFCOSE_MISRA_USE_API(wc_CBOR_DecodeArrayStart); + WOLFCOSE_MISRA_USE_API(wc_CBOR_DecodeMapStart); + WOLFCOSE_MISRA_USE_API(wc_CBOR_DecodeTag); + WOLFCOSE_MISRA_USE_API(wc_CBOR_Skip); + WOLFCOSE_MISRA_USE_API(wc_CBOR_SkipItem); + WOLFCOSE_MISRA_USE_API(wc_CBOR_DecodeLabel); + WOLFCOSE_MISRA_USE_API(wc_CBOR_LabelIsInt); + WOLFCOSE_MISRA_USE_API(wc_CBOR_LabelIsText); + WOLFCOSE_MISRA_USE_API(wc_CoseKey_Init); + WOLFCOSE_MISRA_USE_API(wc_CoseKey_Free); +#ifdef HAVE_ECC + WOLFCOSE_MISRA_USE_API(wc_CoseKey_SetEcc); +#endif +#ifdef WOLFCOSE_HAVE_EDDSA + WOLFCOSE_MISRA_USE_API(wc_CoseKey_SetEd25519); +#endif +#ifdef WOLFCOSE_HAVE_ED448 + WOLFCOSE_MISRA_USE_API(wc_CoseKey_SetEd448); +#endif +#ifdef WOLFCOSE_HAVE_MLDSA + WOLFCOSE_MISRA_USE_API(wc_CoseKey_SetMlDsa); + WOLFCOSE_MISRA_USE_API(wc_CoseKey_SetMlDsa_ex); +#endif +#ifdef WOLFCOSE_HAVE_RSAPSS + WOLFCOSE_MISRA_USE_API(wc_CoseKey_SetRsa); +#endif + WOLFCOSE_MISRA_USE_API(wc_CoseKey_SetSymmetric); +#ifdef WOLFCOSE_EXT_SIGN + WOLFCOSE_MISRA_USE_API(wc_CoseKey_SetExtSigner); +#endif +#ifdef WOLFCOSE_KEY_ENCODE + WOLFCOSE_MISRA_USE_API(wc_CoseKey_Encode); + WOLFCOSE_MISRA_USE_API(wc_CoseKey_Encode_ex); +#ifdef HAVE_ECC + WOLFCOSE_MISRA_USE_API(wc_CoseKey_EncodeEccRaw); +#endif + WOLFCOSE_MISRA_USE_API(wc_CoseKey_EncodeSize); + WOLFCOSE_MISRA_USE_API(wc_CoseKey_EncodeSize_ex); +#endif +#ifdef WOLFCOSE_KEY_DECODE + WOLFCOSE_MISRA_USE_API(wc_CoseKey_PeekInfo); + WOLFCOSE_MISRA_USE_API(wc_CoseKey_Decode); +#endif +#ifdef WOLFCOSE_SIGN1_SIGN + WOLFCOSE_MISRA_USE_API(wc_CoseSign1_Sign); + WOLFCOSE_MISRA_USE_API(wc_CoseSign1_Sign_ex); + WOLFCOSE_MISRA_USE_API(wc_CoseSign1_SignSize_ex); +#endif +#ifdef WOLFCOSE_SIGN1_VERIFY + WOLFCOSE_MISRA_USE_API(wc_CoseSign1_Verify); +#endif +#ifdef WOLFCOSE_ENCRYPT0_ENCRYPT + WOLFCOSE_MISRA_USE_API(wc_CoseEncrypt0_Encrypt); +#endif +#ifdef WOLFCOSE_ENCRYPT0_DECRYPT + WOLFCOSE_MISRA_USE_API(wc_CoseEncrypt0_Decrypt); +#endif +#ifdef WOLFCOSE_MAC0_CREATE + WOLFCOSE_MISRA_USE_API(wc_CoseMac0_Create); +#endif +#ifdef WOLFCOSE_MAC0_VERIFY + WOLFCOSE_MISRA_USE_API(wc_CoseMac0_Verify); +#endif +#ifdef WOLFCOSE_SIGN_SIGN + WOLFCOSE_MISRA_USE_API(wc_CoseSign_Sign); +#endif +#ifdef WOLFCOSE_SIGN_VERIFY + WOLFCOSE_MISRA_USE_API(wc_CoseSign_Verify); +#endif +#ifdef WOLFCOSE_ENCRYPT_ENCRYPT + WOLFCOSE_MISRA_USE_API(wc_CoseEncrypt_Encrypt); +#endif +#ifdef WOLFCOSE_ENCRYPT_DECRYPT + WOLFCOSE_MISRA_USE_API(wc_CoseEncrypt_Decrypt); +#endif +#ifdef WOLFCOSE_MAC_CREATE + WOLFCOSE_MISRA_USE_API(wc_CoseMac_Create); +#endif +#ifdef WOLFCOSE_MAC_VERIFY + WOLFCOSE_MISRA_USE_API(wc_CoseMac_Verify); +#endif + + return total; +} + +int main(void) +{ + /* These public compatibility and reserved constants are intentionally not + * consumed by the library itself. Expanding them here models application + * use. */ + static const volatile int32_t publicConstants[] = { + WOLFCOSE_E_COSE_MAC_FAIL, + (int32_t)WOLFCOSE_CBOR_BREAK, + (int32_t)WOLFCOSE_CBOR_AI_FLOAT16, + WOLFCOSE_ALG_HMAC256, + WOLFCOSE_ALG_HMAC384, + WOLFCOSE_ALG_HMAC512, + WOLFCOSE_ALG_ECDH_SS_HKDF_256, + WOLFCOSE_ALG_ECDH_SS_HKDF_512, + WOLFCOSE_ALG_ECDH_ES_A128KW, + WOLFCOSE_ALG_ECDH_ES_A192KW, + WOLFCOSE_ALG_ECDH_ES_A256KW, + WOLFCOSE_CRV_ML_DSA_44, + WOLFCOSE_CRV_ML_DSA_65, + WOLFCOSE_CRV_ML_DSA_87, + (int32_t)LIBWOLFCOSE_VERSION_HEX + }; + size_t used = wolfCose_MisraUsePublicApis(); + + used += sizeof(publicConstants); + if (publicConstants[0] == INT32_MIN) { + used = 0u; + } + used += sizeof(LIBWOLFCOSE_VERSION_STRING); +#ifdef WOLFCOSE_RECIPIENTS + used += 1u; +#endif +#ifdef WOLFCOSE_ECDH + used += 1u; +#endif +#ifdef WOLFCOSE_ECDH_WRAP + used += 1u; +#endif + + return (used == 0u) ? 1 : 0; +} diff --git a/tests/test_cbor.c b/tests/test_cbor.c index 12dda63..84edca9 100644 --- a/tests/test_cbor.c +++ b/tests/test_cbor.c @@ -729,6 +729,10 @@ static void test_cbor_reject_non_preferred(void) uint8_t u2[] = {0x19, 0x00, 0x17}; /* uint 23 overlong */ uint8_t u4[] = {0x1A, 0x00, 0x00, 0x00, 0x18}; /* uint 24 overlong */ uint8_t u8[] = {0x1B, 0,0,0,0, 0,0,0x01,0x00}; /* uint 256 overlong */ + uint8_t u1Max[] = {0x18, 0x17}; /* uint 23 overlong */ + uint8_t u2Max[] = {0x19, 0x00, 0xFF}; /* uint 255 overlong */ + uint8_t u4Max[] = {0x1A, 0x00, 0x00, 0xFF, 0xFF}; /* uint 65535 overlong */ + uint8_t u8Max[] = {0x1B, 0,0,0,0, 0xFF,0xFF,0xFF,0xFF}; uint8_t bs[] = {0x58, 0x00}; /* empty bstr overlong */ uint8_t arr[] = {0x98, 0x00}; /* array(0) overlong */ @@ -750,6 +754,26 @@ static void test_cbor_reject_non_preferred(void) ret = wc_CBOR_DecodeUint(&ctx, &uval); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "reject overlong uint 256"); + ctx.cbuf = u1Max; ctx.bufSz = sizeof(u1Max); ctx.idx = 0; + ret = wc_CBOR_DecodeUint(&ctx, &uval); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "reject max overlong 1-byte uint"); + + ctx.cbuf = u2Max; ctx.bufSz = sizeof(u2Max); ctx.idx = 0; + ret = wc_CBOR_DecodeUint(&ctx, &uval); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "reject max overlong 2-byte uint"); + + ctx.cbuf = u4Max; ctx.bufSz = sizeof(u4Max); ctx.idx = 0; + ret = wc_CBOR_DecodeUint(&ctx, &uval); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "reject max overlong 4-byte uint"); + + ctx.cbuf = u8Max; ctx.bufSz = sizeof(u8Max); ctx.idx = 0; + ret = wc_CBOR_DecodeUint(&ctx, &uval); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "reject max overlong 8-byte uint"); + ctx.cbuf = bs; ctx.bufSz = sizeof(bs); ctx.idx = 0; ret = wc_CBOR_DecodeBstr(&ctx, &data, &dataLen); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "reject overlong bstr len"); diff --git a/tests/test_cose.c b/tests/test_cose.c index 376df10..c1c3fd9 100644 --- a/tests/test_cose.c +++ b/tests/test_cose.c @@ -60,6 +60,10 @@ #ifdef WOLFCOSE_HAVE_RSAPSS #include #endif +#if defined(WOLFCOSE_HAVE_RSAPSS) && defined(WOLFSSL_KEY_GEN) + #define USE_CERT_BUFFERS_1024 + #include +#endif #ifdef WOLFCOSE_TEST_LOG_ENABLE #include #endif @@ -68,6 +72,13 @@ #include "force_failure.h" #endif +#if defined(HAVE_ECC) && defined(WOLFCOSE_KEY_DECODE) && \ + defined(ALT_ECC_SIZE) && defined(USE_FAST_MATH) && \ + defined(HAVE_WOLF_BIGINT) && \ + !defined(WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED) + #error "fast ALT ECC with bigint requires fail-closed private import" +#endif + static int g_failures = 0; #ifdef WOLFCOSE_TEST_LOG_ENABLE @@ -87,6 +98,105 @@ static int g_failures = 0; } \ } while (0) +#if (defined(WOLFCOSE_MAC) && defined(WOLFCOSE_HAVE_HMAC256)) || \ + (defined(WOLFCOSE_ENCRYPT) && defined(WOLFCOSE_HAVE_AESGCM)) +/* Locate the one-byte direct-alg value in a generated recipient header. */ +static int find_recipient_direct_alg(const uint8_t* msg, size_t msgLen, + size_t fieldsBeforeRecipients, size_t recipientIndex, size_t* algOffset) +{ + int ret = WOLFCOSE_SUCCESS; + WOLFCOSE_CBOR_CTX ctx; + uint64_t tag = 0u; + size_t count = 0u; + size_t i; + const uint8_t* protectedData = NULL; + size_t protectedLen = 0u; + int found = 0; + + if ((msg == NULL) || (algOffset == NULL)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + if (ret == WOLFCOSE_SUCCESS) { + (void)XMEMSET(&ctx, 0, sizeof(ctx)); + ctx.cbuf = msg; + ctx.bufSz = msgLen; + if ((ctx.idx < ctx.bufSz) && + (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TAG)) { + ret = wc_CBOR_DecodeTag(&ctx, &tag); + } + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + if ((ret == WOLFCOSE_SUCCESS) && + (count != (fieldsBeforeRecipients + 1u))) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && + (i < fieldsBeforeRecipients); i++) { + ret = wc_CBOR_Skip(&ctx); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + if ((ret == WOLFCOSE_SUCCESS) && (recipientIndex >= count)) { + ret = WOLFCOSE_E_INVALID_ARG; + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < recipientIndex); i++) { + ret = wc_CBOR_Skip(&ctx); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + if ((ret == WOLFCOSE_SUCCESS) && (count != 3u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeBstr(&ctx, &protectedData, &protectedLen); + } + if ((ret == WOLFCOSE_SUCCESS) && (protectedLen != 0u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeMapStart(&ctx, &count); + } + for (i = 0u; (ret == WOLFCOSE_SUCCESS) && (i < count); i++) { + int64_t label = 0; + + ret = wc_CBOR_DecodeInt(&ctx, &label); + if ((ret == WOLFCOSE_SUCCESS) && + (label == WOLFCOSE_HDR_ALG)) { + int64_t alg = 0; + size_t offset = ctx.idx; + + ret = wc_CBOR_DecodeInt(&ctx, &alg); + if ((ret == WOLFCOSE_SUCCESS) && + ((alg != WOLFCOSE_ALG_DIRECT) || + (ctx.idx != (offset + 1u)))) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == WOLFCOSE_SUCCESS) { + *algOffset = offset; + found = 1; + } + } + else if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_Skip(&ctx); + } + else { + /* No action required */ + } + } + if ((ret == WOLFCOSE_SUCCESS) && (found == 0)) { + ret = WOLFCOSE_E_COSE_BAD_ALG; + } + + (void)tag; + (void)protectedData; + return ret; +} +#endif + /* ----- Internal helper tests ----- */ static void test_wolfcose_force_zero(void) { @@ -231,9 +341,16 @@ static void test_cose_key_ecc(void) ret = wc_CoseKey_SetEcc(&key2, WOLFCOSE_CRV_P256, &eccKey2); TEST_ASSERT(ret == 0, "key2 set ecc"); ret = wc_CoseKey_Decode(&key2, cbuf, cLen); +#ifdef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED && + key2.kty == WOLFCOSE_KTY_EC2 && + key2.crv == WOLFCOSE_CRV_P256 && key2.hasPrivate == 0, + "key ecc decode backend rejected"); +#else TEST_ASSERT(ret == 0 && key2.kty == WOLFCOSE_KTY_EC2 && key2.crv == WOLFCOSE_CRV_P256 && key2.hasPrivate == 1, "key ecc decode"); +#endif (void)wc_ecc_free(&eccKey2); } @@ -382,6 +499,116 @@ static void test_cose_key_symmetric(void) wc_CoseKey_Free(&key); } +#if defined(WOLFCOSE_KEY_DECODE) +static void test_cose_key_operations(void) +{ + static const uint8_t encodedKey[] = { + 0xA3u, + 0x01u, 0x04u, + 0x04u, 0x81u, 0x0Au, + 0x20u, 0x58u, 0x20u, + 0x00u, 0x01u, 0x02u, 0x03u, 0x04u, 0x05u, 0x06u, 0x07u, + 0x08u, 0x09u, 0x0Au, 0x0Bu, 0x0Cu, 0x0Du, 0x0Eu, 0x0Fu, + 0x10u, 0x11u, 0x12u, 0x13u, 0x14u, 0x15u, 0x16u, 0x17u, + 0x18u, 0x19u, 0x1Au, 0x1Bu, 0x1Cu, 0x1Du, 0x1Eu, 0x1Fu + }; + static const uint8_t materialFirst[] = { + 0xA3u, + 0x01u, 0x04u, + 0x20u, 0x58u, 0x20u, + 0x00u, 0x01u, 0x02u, 0x03u, 0x04u, 0x05u, 0x06u, 0x07u, + 0x08u, 0x09u, 0x0Au, 0x0Bu, 0x0Cu, 0x0Du, 0x0Eu, 0x0Fu, + 0x10u, 0x11u, 0x12u, 0x13u, 0x14u, 0x15u, 0x16u, 0x17u, + 0x18u, 0x19u, 0x1Au, 0x1Bu, 0x1Cu, 0x1Du, 0x1Eu, 0x1Fu, + 0x04u, 0x81u, 0x0Au + }; + WOLFCOSE_KEY key; + WOLFCOSE_KEY_INFO info; + int ret; + + TEST_LOG(" [Key Operations]\n"); + + ret = wc_CoseKey_Init(&key); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "key ops init"); + ret = wc_CoseKey_Decode(&key, encodedKey, sizeof(encodedKey)); + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED && key.hasPrivate == 0u && + key.key.symm.key == NULL && key.key.symm.keyLen == 0u, + "key ops rejected before import"); + + ret = wc_CoseKey_PeekInfo(encodedKey, sizeof(encodedKey), &info); + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED && info.kty == 0 && + info.alg == WOLFCOSE_ALG_UNSET && info.kid == NULL, + "key ops peek rejected and cleared"); + + ret = wc_CoseKey_Init(&key); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "key ops second init"); + ret = wc_CoseKey_Decode(&key, materialFirst, sizeof(materialFirst)); + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED && key.hasPrivate == 0u && + key.key.symm.key == NULL && key.key.symm.keyLen == 0u, + "key ops reject material before operation policy"); +} + +static void test_cose_key_akp_alg_metadata(void) +{ + static const uint8_t missingAlg[] = { + 0xA2u, 0x01u, 0x07u, 0x20u, 0x41u, 0x00u + }; + static const uint8_t wrongAlg[] = { + 0xA3u, 0x01u, 0x07u, 0x03u, 0x26u, + 0x20u, 0x41u, 0x00u + }; + WOLFCOSE_KEY key; + int ret; + + TEST_LOG(" [Key AKP algorithm metadata]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, missingAlg, sizeof(missingAlg)); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "AKP metadata rejects missing algorithm"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, wrongAlg, sizeof(wrongAlg)); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "AKP metadata rejects non-ML-DSA algorithm"); +} + +static void test_cose_key_akp_public_metadata(void) +{ + static const uint8_t missingPublic[] = { + 0xA2u, 0x01u, 0x07u, 0x03u, 0x38u, 0x2Fu + }; + WOLFCOSE_KEY key; + int ret; + + TEST_LOG(" [Key AKP public metadata]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, missingPublic, sizeof(missingPublic)); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "AKP metadata rejects missing public key"); +} + +static void test_cose_key_akp_seed_metadata(void) +{ + static const uint8_t shortSeed[] = { + 0xA4u, 0x01u, 0x07u, 0x03u, 0x38u, 0x2Fu, + 0x20u, 0x41u, 0x00u, 0x21u, 0x50u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u + }; + WOLFCOSE_KEY key; + int ret; + + TEST_LOG(" [Key AKP seed metadata]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, shortSeed, sizeof(shortSeed)); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "AKP metadata rejects wrong seed length"); +} +#endif + /* ----- COSE_Sign1 tests ----- */ #ifdef WOLFCOSE_HAVE_ES256 static void test_cose_sign1_ecc(const char* label, int32_t alg, int32_t crv, @@ -399,6 +626,11 @@ static void test_cose_sign1_ecc(const char* label, int32_t alg, int32_t crv, uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; uint8_t out[512]; size_t outLen = 0; +#ifdef WOLFCOSE_HAVE_DETERMINISTIC_ECDSA + uint8_t deterministicOut[512]; + size_t deterministicOutLen = 0; + enum wc_HashType expectedHashType = WC_HASH_TYPE_NONE; +#endif size_t sizedLen = 0; const uint8_t* decPayload = NULL; size_t decPayloadLen = 0; @@ -425,6 +657,10 @@ static void test_cose_sign1_ecc(const char* label, int32_t alg, int32_t crv, if (ret == 0) { ret = wc_CoseKey_Init(&signKey); TEST_ASSERT(ret == 0, "sign key init"); +#ifdef WOLFCOSE_HAVE_DETERMINISTIC_ECDSA + eccKey.deterministic = 0u; + eccKey.hashType = WC_HASH_TYPE_SHA512; +#endif if (ret == 0) { ret = wc_CoseKey_SetEcc(&signKey, crv, &eccKey); TEST_ASSERT(ret == 0, "sign key set ecc"); @@ -450,6 +686,35 @@ static void test_cose_sign1_ecc(const char* label, int32_t alg, int32_t crv, } } +#ifdef WOLFCOSE_HAVE_DETERMINISTIC_ECDSA + if (ret == 0) { + ret = wolfCose_AlgToHashType(alg, &expectedHashType); + TEST_ASSERT(ret == 0, "sign1 deterministic hash mapping"); + } + if (ret == 0) { + TEST_ASSERT(eccKey.deterministic == 0u, + "sign1 restores caller deterministic mode"); + TEST_ASSERT(eccKey.hashType == WC_HASH_TYPE_SHA512, + "sign1 restores caller deterministic hash"); + + ret = wc_CoseSign1_Sign(&signKey, alg, + kid, sizeof(kid) - 1, + payload, sizeof(payload) - 1, + NULL, 0, NULL, 0, + scratch, sizeof(scratch), deterministicOut, + sizeof(deterministicOut), &deterministicOutLen, &rng); + TEST_ASSERT(ret == 0, "sign1 deterministic repeat sign"); + TEST_ASSERT(ret != 0 || + (deterministicOutLen == outLen && + memcmp(deterministicOut, out, outLen) == 0), + "sign1 deterministic repeat output"); + TEST_ASSERT(eccKey.deterministic == 0u, + "sign1 repeat restores deterministic mode"); + TEST_ASSERT(eccKey.hashType == WC_HASH_TYPE_SHA512, + "sign1 repeat restores deterministic hash"); + } +#endif + if (ret == 0) { /* Verify with same key (through a const pointer: the verify API only * reads the key, so its parameter is const-qualified). */ @@ -551,13 +816,16 @@ static int test_ext_sign_cb(void* cbCtx, int32_t alg, uint8_t* sig, size_t sigSz, size_t* sigLen) { test_ext_ctx* ctx = (test_ext_ctx*)cbCtx; + enum wc_HashType hashType = WC_HASH_TYPE_NONE; int ret; - (void)alg; ctx->called++; *sigLen = sigSz; - ret = wolfCose_EccSignRaw(tbs, tbsSz, sig, sigLen, ctx->coordSz, - ctx->rng, ctx->key); + ret = wolfCose_AlgToHashType(alg, &hashType); + if (ret == WOLFCOSE_SUCCESS) { + ret = wolfCose_EccSignRaw(tbs, tbsSz, sig, sigLen, ctx->coordSz, + hashType, ctx->rng, ctx->key); + } return (ret == 0) ? 0 : -1; } @@ -1659,6 +1927,18 @@ static void test_cose_sign1_eddsa(void) TEST_ASSERT(hdr.alg == WOLFCOSE_ALG_EDDSA, "sign1 eddsa hdr alg"); } + if (ret == 0) { + WOLFCOSE_KEY wrongTypeKey = signKey; + int wrongRet; + + wrongTypeKey.attachedType = WOLFCOSE_ATT_NONE; + wrongRet = wc_CoseSign1_Verify(&wrongTypeKey, out, outLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(wrongRet == WOLFCOSE_E_COSE_KEY_TYPE, + "sign1 eddsa attachedType mismatch rejected"); + } + if (ret == 0) { /* Wrong key should fail */ ed25519_key edWrong; @@ -1756,6 +2036,18 @@ static void test_cose_sign1_ed448(void) TEST_ASSERT(hdr.alg == WOLFCOSE_ALG_EDDSA, "sign1 ed448 hdr alg"); } + if (ret == 0) { + WOLFCOSE_KEY wrongTypeKey = signKey; + int wrongRet; + + wrongTypeKey.attachedType = WOLFCOSE_ATT_NONE; + wrongRet = wc_CoseSign1_Verify(&wrongTypeKey, out, outLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(wrongRet == WOLFCOSE_E_COSE_KEY_TYPE, + "sign1 ed448 attachedType mismatch rejected"); + } + if (ret == 0) { /* Wrong key should fail */ ed448_key edWrong; @@ -2115,6 +2407,16 @@ static void test_cose_encrypt0_with_aad(void) scratch, sizeof(scratch), &hdr, plaintext, sizeof(plaintext), &plaintextLen); TEST_ASSERT(ret != 0, "enc0 missing aad fails"); + + ret = wc_CoseEncrypt0_Encrypt(&key, WOLFCOSE_ALG_A128GCM, + iv, sizeof(iv), + payload, sizeof(payload) - 1, + NULL, 0u, NULL, + NULL, 1u, + scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "enc0 null aad with length rejected"); } #endif /* WOLFCOSE_HAVE_AESGCM */ @@ -2469,6 +2771,116 @@ static void test_cose_aes_ccm_all_params(void) "ccm sweep multi roundtrip"); } } + +#define TEST_CCM_L2_TOO_LONG 65536u +#define TEST_CCM_L2_TAG_LEN 16u +#define TEST_CCM_L2_OUT_SZ 65792u + +static uint8_t testCcmL2Payload[TEST_CCM_L2_TOO_LONG]; +static uint8_t testCcmL2Output[TEST_CCM_L2_OUT_SZ]; + +static void test_cose_aes_ccm_l2_payload_limit(void) +{ + static const uint8_t enc0Detached[] = { + 0xD0u, 0x83u, 0x44u, 0xA1u, 0x01u, 0x18u, 0x1Eu, + 0xA1u, 0x05u, 0x4Du, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0xF6u + }; +#if defined(WOLFCOSE_ENCRYPT) + static const uint8_t encryptDetached[] = { + 0xD8u, 0x60u, 0x84u, + 0x44u, 0xA1u, 0x01u, 0x18u, 0x1Eu, + 0xA1u, 0x05u, 0x4Du, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0xF6u, + 0x81u, 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u + }; + WOLFCOSE_RECIPIENT recipient; +#endif + WOLFCOSE_KEY key; + WOLFCOSE_HDR hdr; + uint8_t keyBytes[16] = {0}; + uint8_t iv[13] = {0}; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t out[64]; + size_t outLen = 0; + size_t detachedLen = 0; + size_t plaintextLen = 0; + int ret; + + TEST_LOG(" [AES-CCM L=2 payload limit]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyBytes, sizeof(keyBytes)); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "ccm L=2 key set"); + +#if defined(WOLFCOSE_ENCRYPT0_ENCRYPT) + ret = wc_CoseEncrypt0_Encrypt(&key, + WOLFCOSE_ALG_AES_CCM_16_128_128, + iv, sizeof(iv), testCcmL2Payload, UINT16_MAX, + testCcmL2Output, sizeof(testCcmL2Output), &detachedLen, + NULL, 0u, scratch, sizeof(scratch), out, sizeof(out), &outLen); + TEST_ASSERT((ret == WOLFCOSE_SUCCESS) && + (detachedLen == ((size_t)UINT16_MAX + TEST_CCM_L2_TAG_LEN)), + "encrypt0 accepts maximum CCM L=2 payload"); + + ret = wc_CoseEncrypt0_Encrypt(&key, + WOLFCOSE_ALG_AES_CCM_64_128_128, + iv, 7u, testCcmL2Payload, sizeof(testCcmL2Payload), + testCcmL2Output, sizeof(testCcmL2Output), &detachedLen, + NULL, 0u, scratch, sizeof(scratch), out, sizeof(out), &outLen); + TEST_ASSERT((ret == WOLFCOSE_SUCCESS) && + (detachedLen == (TEST_CCM_L2_TOO_LONG + + TEST_CCM_L2_TAG_LEN)), + "encrypt0 accepts payload above CCM L=2 limit for L=8"); + + ret = wc_CoseEncrypt0_Encrypt(&key, + WOLFCOSE_ALG_AES_CCM_16_128_128, + iv, sizeof(iv), testCcmL2Payload, sizeof(testCcmL2Payload), + testCcmL2Output, sizeof(testCcmL2Output), &detachedLen, + NULL, 0u, scratch, sizeof(scratch), out, sizeof(out), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "encrypt0 rejects CCM L=2 oversized payload"); +#endif + +#if defined(WOLFCOSE_ENCRYPT0_DECRYPT) + ret = wc_CoseEncrypt0_Decrypt(&key, + enc0Detached, sizeof(enc0Detached), + testCcmL2Output, TEST_CCM_L2_TOO_LONG + TEST_CCM_L2_TAG_LEN, + NULL, 0u, scratch, sizeof(scratch), &hdr, + testCcmL2Payload, sizeof(testCcmL2Payload), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "encrypt0 rejects CCM L=2 oversized ciphertext"); +#endif + +#if defined(WOLFCOSE_ENCRYPT) + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0u; + + ret = wc_CoseEncrypt_Encrypt(&recipient, 1u, + WOLFCOSE_ALG_AES_CCM_16_128_128, + iv, sizeof(iv), testCcmL2Payload, sizeof(testCcmL2Payload), + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + testCcmL2Output, sizeof(testCcmL2Output), &outLen, NULL); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "encrypt rejects CCM L=2 oversized payload"); + + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, + encryptDetached, sizeof(encryptDetached), + testCcmL2Output, TEST_CCM_L2_TOO_LONG + TEST_CCM_L2_TAG_LEN, + NULL, 0u, scratch, sizeof(scratch), &hdr, + testCcmL2Payload, sizeof(testCcmL2Payload), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "encrypt rejects CCM L=2 oversized ciphertext"); +#endif + + wc_CoseKey_Free(&key); +} #endif /* WOLFCOSE_HAVE_AESCCM */ /* ----- COSE_Sign1 RSA-PSS tests ----- */ @@ -2579,83 +2991,418 @@ static void test_cose_sign1_pss(const char* label, int32_t alg) (void)wc_FreeRng(&rng); } } -#endif /* WOLFCOSE_HAVE_RSAPSS && WOLFSSL_KEY_GEN */ -/* ----- COSE_Sign1 ML-DSA tests ----- */ -#ifdef WOLFCOSE_HAVE_MLDSA -static void test_cose_sign1_ml_dsa(const char* label, int32_t alg, byte level) +static void test_cose_rsa_pss_minimum_key_size(void) { - WOLFCOSE_KEY signKey; - wc_MlDsaKey dlKey; + WOLFCOSE_KEY key; + RsaKey rsaKey; + RsaKey boundaryKey; WC_RNG rng; - int ret = 0; - int rngInited = 0; - int dlInited = 0; - uint8_t payload[] = "ML-DSA payload"; - uint8_t scratch[8192]; - uint8_t out[8192]; - size_t outLen = 0; - size_t sizedLen = 0; - const uint8_t* decPayload = NULL; - size_t decPayloadLen = 0; WOLFCOSE_HDR hdr; + WOLFCOSE_CBOR_CTX enc; + uint8_t protectedBuf[8]; + uint8_t signature[128] = {0}; + uint8_t boundaryModulus[256] = {0}; + const uint8_t rsaExponent[] = {0x01, 0x00, 0x01}; + const uint8_t largeExponent[] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }; + uint8_t payload[] = "RSA-PSS minimum key size"; + uint8_t scratch[1024]; + uint8_t out[1024]; + uint8_t sign1Msg[256]; + size_t protectedLen = 0u; + size_t outLen = 0u; + size_t sign1MsgLen = 0u; + size_t sizedLen = 0u; + word32 idx = 0u; + const uint8_t* decPayload = NULL; + size_t decPayloadLen = 0u; + int ret; + int keyInit = 0; + int boundaryInit = 0; + int rsaInit = 0; + int rngInit = 0; +#ifdef WOLFCOSE_SIGN + WOLFCOSE_SIGNATURE signer; + uint8_t signMsg[256]; + size_t signMsgLen = 0u; +#endif - (void)label; - TEST_LOG(" [Sign1 %s]\n", label); + TEST_LOG(" [RSA-PSS minimum key size]\n"); - ret = wc_InitRng(&rng); - if (ret != 0) { TEST_ASSERT(0, "rng init"); } + ret = wc_CoseKey_Init(&key); + TEST_ASSERT(ret == 0, "rsa minimum COSE key init"); if (ret == 0) { - rngInited = 1; + keyInit = 1; } - if (ret == 0) { - ret = wc_MlDsaKey_Init(&dlKey, NULL, INVALID_DEVID); - if (ret != 0) { TEST_ASSERT(0, "dl init"); } + ret = wc_InitRsaKey(&rsaKey, NULL); + TEST_ASSERT(ret == 0, "rsa minimum key init"); if (ret == 0) { - dlInited = 1; + rsaInit = 1; } } - if (ret == 0) { - ret = wc_MlDsaKey_SetParams(&dlKey, level); - if (ret != 0) { TEST_ASSERT(0, "dl set level"); } + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == 0, "rsa minimum rng init"); + if (ret == 0) { + rngInit = 1; + } } - if (ret == 0) { - ret = wc_MlDsaKey_MakeKey(&dlKey, &rng); - if (ret != 0) { TEST_ASSERT(0, "dl keygen"); } + ret = wc_RsaPrivateKeyDecode(client_key_der_1024, &idx, &rsaKey, + sizeof_client_key_der_1024); + TEST_ASSERT(ret == 0, "rsa minimum weak key import"); } - if (ret == 0) { - (void)wc_CoseKey_Init(&signKey); - (void)wc_CoseKey_SetMlDsa(&signKey, alg, &dlKey); + ret = wc_CoseKey_SetRsa(&key, &rsaKey); + TEST_ASSERT(ret == 0, "rsa minimum COSE key set"); + } - ret = wc_CoseSign1_SignSize_ex(NULL, alg, 0u, + if (ret == 0) { + ret = wc_CoseSign1_SignSize_ex(&key, WOLFCOSE_ALG_PS256, 0u, sizeof(payload) - 1u, 0u, 0u, &sizedLen); - TEST_ASSERT(ret == 0, "sign1 ml-dsa size"); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "sign1 pss size rejects 1024-bit key"); - /* Sign */ - if (ret == 0) { - ret = wc_CoseSign1_Sign(&signKey, alg, - NULL, 0, - payload, sizeof(payload) - 1, - NULL, 0, /* detachedPayload, detachedLen */ - NULL, 0, /* extAad, extAadLen */ - scratch, sizeof(scratch), - out, sizeof(out), &outLen, &rng); - TEST_ASSERT(ret == 0 && outLen > 0, "sign1 ml-dsa sign"); - TEST_ASSERT(outLen == sizedLen, "sign1 ml-dsa exact size"); - } + ret = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_PS256, + NULL, 0u, payload, sizeof(payload) - 1u, + NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &outLen, &rng); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "sign1 pss rejects 1024-bit key"); } + ret = wc_CBOR_EncoderInit(&enc, protectedBuf, sizeof(protectedBuf)); if (ret == 0) { - /* Verify */ - ret = wc_CoseSign1_Verify(&signKey, out, outLen, - NULL, 0, /* detachedPayload, detachedLen */ - NULL, 0, /* extAad, extAadLen */ - scratch, sizeof(scratch), - &hdr, &decPayload, &decPayloadLen); + ret = wc_CBOR_EncodeMapStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeUint(&enc, WOLFCOSE_HDR_ALG); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_PS256); + } + if (ret == 0) { + protectedLen = enc.idx; + ret = wc_CBOR_EncoderInit(&enc, sign1Msg, sizeof(sign1Msg)); + } + if (ret == 0) { + ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_SIGN1); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 4u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, protectedBuf, protectedLen); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, payload, sizeof(payload) - 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, signature, sizeof(signature)); + } + TEST_ASSERT(ret == 0, "rsa minimum Sign1 fixture encode"); + if (ret == 0) { + sign1MsgLen = enc.idx; + ret = wc_CoseSign1_Verify(&key, sign1Msg, sign1MsgLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "sign1 pss verify rejects 1024-bit key"); + } + +#ifdef WOLFCOSE_SIGN + signer.algId = WOLFCOSE_ALG_PS256; + signer.key = &key; + signer.kid = NULL; + signer.kidLen = 0u; + ret = wc_CoseSign_Sign(&signer, 1u, + payload, sizeof(payload) - 1u, NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), out, sizeof(out), &outLen, &rng); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "multi pss sign rejects 1024-bit key"); + + ret = wc_CBOR_EncoderInit(&enc, signMsg, sizeof(signMsg)); + if (ret == 0) { + ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_SIGN); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 4u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, payload, sizeof(payload) - 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 3u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, protectedBuf, protectedLen); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, signature, sizeof(signature)); + } + TEST_ASSERT(ret == 0, "rsa minimum Sign fixture encode"); + if (ret == 0) { + signMsgLen = enc.idx; + ret = wc_CoseSign_Verify(&key, 0u, signMsg, signMsgLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "multi pss verify rejects 1024-bit key"); + } +#endif + + ret = WOLFCOSE_SUCCESS; + if (ret == 0) { + ret = wc_InitRsaKey(&boundaryKey, NULL); + TEST_ASSERT(ret == 0, "rsa boundary key init"); + if (ret == 0) { + boundaryInit = 1; + } + } + if (ret == 0) { + boundaryModulus[0] = 0x7fu; + boundaryModulus[sizeof(boundaryModulus) - 1u] = 0x01u; + ret = wc_RsaPublicKeyDecodeRaw(boundaryModulus, + (word32)sizeof(boundaryModulus), rsaExponent, + (word32)sizeof(rsaExponent), &boundaryKey); + TEST_ASSERT(ret == 0, "rsa 2047-bit boundary key import"); + } + if (ret == 0) { + ret = wc_CoseKey_SetRsa(&key, &boundaryKey); + TEST_ASSERT(ret == 0, "rsa 2047-bit COSE key set"); + } + if (ret == 0) { + ret = wc_CoseSign1_SignSize_ex(&key, WOLFCOSE_ALG_PS256, 0u, + sizeof(payload) - 1u, 0u, 0u, &sizedLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "sign1 pss size rejects 2047-bit key"); + } + + if (boundaryInit != 0) { + (void)wc_FreeRsaKey(&boundaryKey); + boundaryInit = 0; + } + ret = wc_InitRsaKey(&boundaryKey, NULL); + TEST_ASSERT(ret == 0, "rsa large-exponent key init"); + if (ret == 0) { + boundaryInit = 1; + boundaryModulus[0] = 0x80u; + ret = wc_RsaPublicKeyDecodeRaw(boundaryModulus, + (word32)sizeof(boundaryModulus), largeExponent, + (word32)sizeof(largeExponent), &boundaryKey); + TEST_ASSERT(ret == 0, "rsa large-exponent key import"); + } + if (ret == 0) { + ret = wc_CoseKey_SetRsa(&key, &boundaryKey); + TEST_ASSERT(ret == 0, "rsa large-exponent COSE key set"); + } + if (ret == 0) { + ret = wc_CoseSign1_SignSize_ex(&key, WOLFCOSE_ALG_PS256, 0u, + sizeof(payload) - 1u, 0u, 0u, &sizedLen); + TEST_ASSERT((ret == 0) && (sizedLen > 0u), + "sign1 pss size accepts 2048-bit large-exponent key"); + } + + if (keyInit != 0) { + (void)wc_CoseKey_Free(&key); + } + if (boundaryInit != 0) { + (void)wc_FreeRsaKey(&boundaryKey); + } + if (rsaInit != 0) { + (void)wc_FreeRsaKey(&rsaKey); + } + if (rngInit != 0) { + (void)wc_FreeRng(&rng); + } +} +#endif /* WOLFCOSE_HAVE_RSAPSS && WOLFSSL_KEY_GEN */ + +#if defined(WOLFCOSE_HAVE_RSAPSS) && defined(WOLFCOSE_SIGN1_SIGN) && \ + (defined(WOLF_CRYPTO_CB) || defined(WOLFSSL_MICROCHIP_TA100)) +static void test_cose_rsa_pss_opaque_key_size(void) +{ + WOLFCOSE_KEY key; + RsaKey rsaKey; + RsaKey boundaryKey; + uint8_t boundaryModulus[256] = {0}; + const uint8_t rsaExponent[] = {0x01, 0x00, 0x01}; + size_t sizedLen = 0u; + int ret; + int rsaInit = 0; + int boundaryInit = 0; + + TEST_LOG(" [RSA-PSS opaque key size]\n"); + + ret = wc_CoseKey_Init(&key); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "opaque rsa COSE key init"); +#ifdef WOLF_CRYPTO_CB + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_InitRsaKey_ex(&rsaKey, NULL, 1); + } +#else + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_InitRsaKey(&rsaKey, NULL); + } +#endif + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "opaque rsa key init"); + if (ret == WOLFCOSE_SUCCESS) { + rsaInit = 1; + } +#ifdef WOLFSSL_MICROCHIP_TA100 + if (ret == WOLFCOSE_SUCCESS) { + rsaKey.uKeyH = 1u; + } +#endif + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_SetRsa(&key, &rsaKey); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "opaque rsa COSE key set"); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseSign1_SignSize_ex(&key, WOLFCOSE_ALG_PS256, 0u, + 1u, 0u, 0u, &sizedLen); + TEST_ASSERT((ret == WOLFCOSE_SUCCESS) && (sizedLen > 0u), + "sign1 pss size accepts opaque 2048-bit key"); + } + +#ifdef WOLF_CRYPTO_CB + ret = wc_InitRsaKey_ex(&boundaryKey, NULL, 1); +#else + ret = wc_InitRsaKey(&boundaryKey, NULL); +#endif + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "callback rsa boundary key init"); + if (ret == WOLFCOSE_SUCCESS) { + boundaryInit = 1; + } +#ifdef WOLFSSL_MICROCHIP_TA100 + if (ret == WOLFCOSE_SUCCESS) { + boundaryKey.uKeyH = 1u; + } +#endif + if (ret == WOLFCOSE_SUCCESS) { + boundaryModulus[0] = 0x7fu; + boundaryModulus[sizeof(boundaryModulus) - 1u] = 0x01u; + ret = wc_RsaPublicKeyDecodeRaw(boundaryModulus, + (word32)sizeof(boundaryModulus), rsaExponent, + (word32)sizeof(rsaExponent), &boundaryKey); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "callback rsa boundary key import"); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseKey_SetRsa(&key, &boundaryKey); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "callback rsa boundary COSE key set"); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseSign1_SignSize_ex(&key, WOLFCOSE_ALG_PS256, 0u, + 1u, 0u, 0u, &sizedLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "sign1 pss size rejects materialized 2047-bit key"); + } + + (void)wc_CoseKey_Free(&key); + if (rsaInit != 0) { + (void)wc_FreeRsaKey(&rsaKey); + } + if (boundaryInit != 0) { + (void)wc_FreeRsaKey(&boundaryKey); + } +} +#endif + +/* ----- COSE_Sign1 ML-DSA tests ----- */ +#ifdef WOLFCOSE_HAVE_MLDSA +static void test_cose_sign1_ml_dsa(const char* label, int32_t alg, byte level) +{ + WOLFCOSE_KEY signKey; + wc_MlDsaKey dlKey; + WC_RNG rng; + int ret = 0; + int rngInited = 0; + int dlInited = 0; + uint8_t payload[] = "ML-DSA payload"; + uint8_t scratch[8192]; + uint8_t out[8192]; + size_t outLen = 0; + size_t sizedLen = 0; + const uint8_t* decPayload = NULL; + size_t decPayloadLen = 0; + WOLFCOSE_HDR hdr; + + (void)label; + TEST_LOG(" [Sign1 %s]\n", label); + + ret = wc_InitRng(&rng); + if (ret != 0) { TEST_ASSERT(0, "rng init"); } + if (ret == 0) { + rngInited = 1; + } + + if (ret == 0) { + ret = wc_MlDsaKey_Init(&dlKey, NULL, INVALID_DEVID); + if (ret != 0) { TEST_ASSERT(0, "dl init"); } + if (ret == 0) { + dlInited = 1; + } + } + + if (ret == 0) { + ret = wc_MlDsaKey_SetParams(&dlKey, level); + if (ret != 0) { TEST_ASSERT(0, "dl set level"); } + } + + if (ret == 0) { + ret = wc_MlDsaKey_MakeKey(&dlKey, &rng); + if (ret != 0) { TEST_ASSERT(0, "dl keygen"); } + } + + if (ret == 0) { + (void)wc_CoseKey_Init(&signKey); + (void)wc_CoseKey_SetMlDsa(&signKey, alg, &dlKey); + + ret = wc_CoseSign1_SignSize_ex(NULL, alg, 0u, + sizeof(payload) - 1u, 0u, 0u, &sizedLen); + TEST_ASSERT(ret == 0, "sign1 ml-dsa size"); + + /* Sign */ + if (ret == 0) { + ret = wc_CoseSign1_Sign(&signKey, alg, + NULL, 0, + payload, sizeof(payload) - 1, + NULL, 0, /* detachedPayload, detachedLen */ + NULL, 0, /* extAad, extAadLen */ + scratch, sizeof(scratch), + out, sizeof(out), &outLen, &rng); + TEST_ASSERT(ret == 0 && outLen > 0, "sign1 ml-dsa sign"); + TEST_ASSERT(outLen == sizedLen, "sign1 ml-dsa exact size"); + } + } + + if (ret == 0) { + /* Verify */ + ret = wc_CoseSign1_Verify(&signKey, out, outLen, + NULL, 0, /* detachedPayload, detachedLen */ + NULL, 0, /* extAad, extAadLen */ + scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); TEST_ASSERT(ret == 0, "sign1 ml-dsa verify"); TEST_ASSERT(decPayloadLen == sizeof(payload) - 1 && memcmp(decPayload, payload, decPayloadLen) == 0, @@ -3158,40 +3905,212 @@ static void test_cose_key_mldsa(const char* label, int32_t alg, /* ----- COSE_Mac0 tests ----- */ #ifdef WOLFCOSE_HAVE_HMAC256 -static void test_cose_mac0_hmac256(void) +static int test_cose_reencode_mac_tag(const uint8_t* in, size_t inLen, + size_t tagLen, uint8_t* out, size_t outSz, size_t* outLen) { - WOLFCOSE_KEY key; - uint8_t keyData[32] = { - 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, 0x20 - }; - uint8_t payload[] = "COSE_Mac0 test payload"; - uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; - uint8_t out[512]; - size_t outLen = 0; - const uint8_t* decPayload = NULL; - size_t decPayloadLen = 0; - WOLFCOSE_HDR hdr; - int ret; - - TEST_LOG(" [Mac0 HMAC-256/256]\n"); + int ret = WOLFCOSE_SUCCESS; + WOLFCOSE_CBOR_CTX dec; + WOLFCOSE_CBOR_CTX enc; + uint64_t coseTag = 0; + size_t arrayCount = 0; + size_t tagOffset = 0; + size_t suffixOffset = 0; + size_t suffixLen = 0; + const uint8_t* originalTag = NULL; + size_t originalTagLen = 0; + size_t copyLen = 0; + size_t i; + uint8_t tag[WC_MAX_DIGEST_SIZE + 1u]; - (void)wc_CoseKey_Init(&key); - (void)wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + if ((in == NULL) || (out == NULL) || (outLen == NULL) || + (tagLen > sizeof(tag))) { + ret = WOLFCOSE_E_INVALID_ARG; + } + else { + *outLen = 0; + dec.cbuf = in; + dec.bufSz = inLen; + dec.idx = 0; + + if ((dec.idx < dec.bufSz) && + (wc_CBOR_PeekType(&dec) == WOLFCOSE_CBOR_TAG)) { + ret = wc_CBOR_DecodeTag(&dec, &coseTag); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_DecodeArrayStart(&dec, &arrayCount); + } + if ((ret == WOLFCOSE_SUCCESS) && (arrayCount < 4u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < 3u); i++) { + ret = wc_CBOR_Skip(&dec); + } + if (ret == WOLFCOSE_SUCCESS) { + tagOffset = dec.idx; + ret = wc_CBOR_DecodeBstr(&dec, &originalTag, + &originalTagLen); + } - /* Create Mac0 */ - ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, - NULL, 0, /* kid, kidLen */ - payload, sizeof(payload) - 1, - NULL, 0, /* detachedPayload, detachedLen */ - NULL, 0, /* extAad, extAadLen */ - scratch, sizeof(scratch), - out, sizeof(out), &outLen); - TEST_ASSERT(ret == 0 && outLen > 0, "mac0 hmac256 create"); + if (ret == WOLFCOSE_SUCCESS) { + suffixOffset = dec.idx; + suffixLen = inLen - suffixOffset; + copyLen = originalTagLen; + if (copyLen > tagLen) { + copyLen = tagLen; + } + if (copyLen > 0u) { + (void)memcpy(tag, originalTag, copyLen); + } + if (tagLen > copyLen) { + (void)memset(&tag[copyLen], 0xA5, tagLen - copyLen); + } - /* Verify */ + if (tagOffset > outSz) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + else { + (void)memcpy(out, in, tagOffset); + enc.buf = out; + enc.bufSz = outSz; + enc.idx = tagOffset; + ret = wc_CBOR_EncodeBstr(&enc, tag, tagLen); + } + } + if ((ret == WOLFCOSE_SUCCESS) && + (suffixLen > (outSz - enc.idx))) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + if (ret == WOLFCOSE_SUCCESS) { + if (suffixLen > 0u) { + (void)memcpy(&out[enc.idx], &in[suffixOffset], suffixLen); + } + enc.idx += suffixLen; + *outLen = enc.idx; + } + } + + return ret; +} + +static void test_cose_mac_wrong_tag_lengths(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_HDR hdr; + const uint8_t keyData[32] = {0}; + const uint8_t payload[] = "wrong tag length"; + const uint8_t* decoded = NULL; + size_t decodedLen = 0; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t out[256]; + size_t outLen = 0; + uint8_t malformed[272]; + size_t malformedLen = 0; + static const size_t wrongTagLens[2] = { + 8u, WC_SHA256_DIGEST_SIZE + 1u + }; + size_t i; + int ret; + + TEST_LOG(" [MAC well-formed wrong tag lengths]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "wrong tag length key set"); + + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, + NULL, 0, payload, sizeof(payload) - 1u, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + out, sizeof(out), &outLen); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "wrong tag length Mac0 create"); + + for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < 2u); i++) { + ret = test_cose_reencode_mac_tag(out, outLen, wrongTagLens[i], + malformed, sizeof(malformed), &malformedLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "wrong tag length Mac0 re-encode"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseMac0_Verify(&key, malformed, malformedLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &decoded, &decodedLen); + TEST_ASSERT(ret == WOLFCOSE_E_MAC_FAIL, + (i == 0u) ? "Mac0 rejects short tag" : + "Mac0 rejects long tag"); + ret = WOLFCOSE_SUCCESS; + } + } + +#ifdef WOLFCOSE_MAC + if (ret == WOLFCOSE_SUCCESS) { + WOLFCOSE_RECIPIENT recipient; + + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0; + ret = wc_CoseMac_Create(&recipient, 1, + WOLFCOSE_ALG_HMAC_256_256, + payload, sizeof(payload) - 1u, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "wrong tag length Mac create"); + + for (i = 0; (ret == WOLFCOSE_SUCCESS) && (i < 2u); i++) { + ret = test_cose_reencode_mac_tag(out, outLen, wrongTagLens[i], + malformed, sizeof(malformed), &malformedLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "wrong tag length Mac re-encode"); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CoseMac_Verify(&recipient, 0, malformed, + malformedLen, NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, &decoded, &decodedLen); + TEST_ASSERT(ret == WOLFCOSE_E_MAC_FAIL, + (i == 0u) ? "Mac rejects short tag" : + "Mac rejects long tag"); + ret = WOLFCOSE_SUCCESS; + } + } + } +#endif + + wc_CoseKey_Free(&key); +} + +static void test_cose_mac0_hmac256(void) +{ + WOLFCOSE_KEY key; + uint8_t keyData[32] = { + 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, 0x20 + }; + uint8_t payload[] = "COSE_Mac0 test payload"; + uint8_t scratch[WOLFCOSE_MAX_SCRATCH_SZ]; + uint8_t out[512]; + size_t outLen = 0; + const uint8_t* decPayload = NULL; + size_t decPayloadLen = 0; + WOLFCOSE_HDR hdr; + int ret; + + TEST_LOG(" [Mac0 HMAC-256/256]\n"); + + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + + /* Create Mac0 */ + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, + NULL, 0, /* kid, kidLen */ + payload, sizeof(payload) - 1, + NULL, 0, /* detachedPayload, detachedLen */ + NULL, 0, /* extAad, extAadLen */ + scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0 && outLen > 0, "mac0 hmac256 create"); + + /* Verify */ ret = wc_CoseMac0_Verify(&key, out, outLen, NULL, 0, /* detachedPayload, detachedLen */ NULL, 0, /* extAad, extAadLen */ @@ -3367,6 +4286,7 @@ static void test_cose_mac_multi_per_recipient(void) size_t outLen = 0; int ret; size_t r; + size_t algOffsets[2] = {0u, 0u}; const uint8_t payload[] = "multi recipient mac"; TEST_LOG(" [Mac multi per-recipient roundtrip]\n"); @@ -3386,6 +4306,12 @@ static void test_cose_mac_multi_per_recipient(void) scratch, sizeof(scratch), out, sizeof(out), &outLen); TEST_ASSERT(ret == 0, "multi mac create"); + for (r = 0u; (ret == 0) && (r < 2u); r++) { + ret = find_recipient_direct_alg(out, outLen, 4u, r, + &algOffsets[r]); + TEST_ASSERT(ret == 0, "locate direct mac recipient alg"); + } + /* Every encoded recipient must verify. */ for (r = 0; (ret == 0) && (r < 2u); r++) { memset(&hdr, 0, sizeof(hdr)); @@ -3397,6 +4323,34 @@ static void test_cose_mac_multi_per_recipient(void) TEST_ASSERT(decPayloadLen == sizeof(payload) - 1, "multi recipient mac payload len"); } + + if (ret == 0) { + out[algOffsets[1]] = 0x29u; /* direct + HKDF-SHA-256 */ + ret = wc_CoseMac_Verify(&recipients[0], 0, out, outLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "mac allows direct encryption sibling modes"); + out[algOffsets[1]] = 0x25u; /* direct */ + } + + if (ret == 0) { + out[algOffsets[1]] = 0x22u; /* A128KW */ + ret = wc_CoseMac_Verify(&recipients[0], 0, out, outLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + 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), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "mac rejects earlier mixed recipient mode"); + out[algOffsets[0]] = 0x25u; /* direct */ + } } /** @@ -4259,8 +5213,13 @@ static void test_cose_key_decode_type_confusion(void) (void)wc_CoseKey_Init(&key); (void)wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &eccKey); ret = wc_CoseKey_Decode(&key, blob, blobLen); +#ifdef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED, + "ecc private-only backend policy applies after type gate"); +#else TEST_ASSERT((ret == WOLFCOSE_SUCCESS) || (ret == WOLFCOSE_E_CRYPTO), "ecc attached accepts a different EC2 curve"); +#endif (void)wc_ecc_free(&eccKey); #endif /* HAVE_ECC */ @@ -4400,18 +5359,28 @@ static void test_cose_key_decode_private_only(void) WC_RNG rng; uint8_t dBuf[32]; word32 dSz = sizeof(dBuf); + uint8_t xBuf[32]; + uint8_t yBuf[32]; + word32 xSz = sizeof(xBuf); + word32 ySz = sizeof(yBuf); uint8_t keyBuf[128]; + uint8_t fullKeyBuf[256]; WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX fullEnc; +#ifndef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED uint8_t hash[32]; uint8_t sig[80]; word32 sigLen = sizeof(sig); int verifyStatus = 0; +#endif int ret; +#ifndef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED size_t i; for (i = 0; i < sizeof(hash); i++) { hash[i] = (uint8_t)i; } +#endif ret = wc_InitRng(&rng); TEST_ASSERT(ret == 0, "ec priv-only rng init"); @@ -4423,6 +5392,11 @@ static void test_cose_key_decode_private_only(void) TEST_ASSERT(ret == 0, "ec priv-only keygen"); ret = wc_ecc_export_private_only(&eccKey, dBuf, &dSz); TEST_ASSERT(ret == 0 && dSz == sizeof(dBuf), "ec priv-only export d"); + if (ret == 0) { + ret = wc_ecc_export_public_raw(&eccKey, xBuf, &xSz, yBuf, &ySz); + } + TEST_ASSERT(ret == 0 && xSz == sizeof(xBuf) && ySz == sizeof(yBuf), + "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; @@ -4434,9 +5408,31 @@ static void test_cose_key_decode_private_only(void) wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_D); wc_CBOR_EncodeBstr(&enc, dBuf, dSz); + /* Build {kty, crv, x, y, d} to exercise the full private import. */ + fullEnc.buf = fullKeyBuf; + fullEnc.bufSz = sizeof(fullKeyBuf); + fullEnc.idx = 0; + wc_CBOR_EncodeMapStart(&fullEnc, 5); + wc_CBOR_EncodeInt(&fullEnc, WOLFCOSE_KEY_LABEL_KTY); + wc_CBOR_EncodeUint(&fullEnc, WOLFCOSE_KTY_EC2); + wc_CBOR_EncodeInt(&fullEnc, WOLFCOSE_KEY_LABEL_CRV); + wc_CBOR_EncodeUint(&fullEnc, WOLFCOSE_CRV_P256); + wc_CBOR_EncodeInt(&fullEnc, WOLFCOSE_KEY_LABEL_X); + wc_CBOR_EncodeBstr(&fullEnc, xBuf, xSz); + wc_CBOR_EncodeInt(&fullEnc, WOLFCOSE_KEY_LABEL_Y); + wc_CBOR_EncodeBstr(&fullEnc, yBuf, ySz); + wc_CBOR_EncodeInt(&fullEnc, WOLFCOSE_KEY_LABEL_D); + wc_CBOR_EncodeBstr(&fullEnc, dBuf, dSz); + (void)wc_CoseKey_Init(&key2); (void)wc_CoseKey_SetEcc(&key2, WOLFCOSE_CRV_P256, &eccKey2); ret = wc_CoseKey_Decode(&key2, keyBuf, enc.idx); +#ifdef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED, + "ec2 private-only backend rejected before import"); + TEST_ASSERT((key2.hasPrivate == 0) && (mp_iszero(eccKey2.k) != 0), + "ec2 rejected backend imports no scalar"); +#else TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "ec2 private-only {kty,crv,d} accepted"); TEST_ASSERT(key2.hasPrivate == 1, "ec2 private-only has private"); @@ -4449,6 +5445,341 @@ static void test_cose_key_decode_private_only(void) &verifyStatus, &eccKey); TEST_ASSERT(ret == 0 && verifyStatus == 1, "ec2 private-only signature verifies"); +#endif + +#ifdef WOLFCOSE_FORCE_FAILURE +#ifndef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED + /* A failed import must clear the scalar and leave the key reusable. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY failedKey; + ecc_key failedEcc; + uint8_t exposedD[32]; + word32 exposedDSz = sizeof(exposedD); + const void* failedHeap; + int failedType; + int failedIdx; + int failedState; + const ecc_set_type* failedDp; +#if defined(HAVE_WOLF_BIGINT) && \ + (!defined(ALT_ECC_SIZE) || !defined(USE_FAST_MATH)) + size_t rawIdx; + int rawCleared = 1; +#endif +#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) + void* failedDevCtx; + int failedDevId; +#endif + + ret = wc_ecc_init(&failedEcc); + TEST_ASSERT(ret == 0, "ec failed-import key init"); + failedHeap = failedEcc.heap; + failedType = failedEcc.type; + failedIdx = failedEcc.idx; + failedState = failedEcc.state; + failedDp = failedEcc.dp; + ret = wc_ecc_set_flags(&failedEcc, WC_ECC_FLAG_COFACTOR); + TEST_ASSERT(ret == 0, "ec failed-import flags set"); + ret = wc_ecc_set_rng(&failedEcc, &rng); + TEST_ASSERT(ret == 0, "ec failed-import rng set"); +#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) + failedDevCtx = (void*)&failedKey; + failedDevId = failedEcc.devId; + failedEcc.devCtx = failedDevCtx; +#endif +#ifdef WOLF_PRIVATE_KEY_ID + failedEcc.id[0] = 0xa5u; + failedEcc.idLen = 1; + failedEcc.label[0] = 'k'; + failedEcc.labelLen = 1; +#endif +#if defined(WOLFSSL_ECDSA_DETERMINISTIC_K) || \ + defined(WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT) + failedEcc.deterministic = 1u; + failedEcc.hashType = WC_HASH_TYPE_SHA256; +#endif + (void)wc_CoseKey_Init(&failedKey); + (void)wc_CoseKey_SetEcc(&failedKey, WOLFCOSE_CRV_P256, + &failedEcc); + +#if defined(WOLFSSL_SP_MATH_ALL) || defined(WOLFSSL_SP_MATH) + /* SP import overwrites only used digits. Leave a marker in the + * initialized tail so the rollback must scrub full capacity. */ + #ifdef ALT_ECC_SIZE + TEST_ASSERT(failedEcc.ka[0].size > 0u, + "ec failed-import alt SP storage initialized"); + failedEcc.ka[0].dp[failedEcc.ka[0].size - 1u] = (mp_digit)0xa5u; + #else + TEST_ASSERT(failedEcc.k->size > 0u, + "ec failed-import SP storage initialized"); + failedEcc.k->dp[failedEcc.k->size - 1u] = (mp_digit)0xa5u; + #endif +#endif + + /* Import succeeds first; the post hook then converts that success + * into an error so rollback sees real imported key state. */ + wolfForceFailure_Set(WOLF_FAIL_ECC_IMPORT_PRIVATE_POST); + ret = wc_CoseKey_Decode(&failedKey, keyBuf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_CRYPTO, + "ec2 failed private-only import rejected"); + TEST_ASSERT(wolfForceFailure_Get() == WOLF_FAIL_NONE, + "ec2 post-import failure hook reached"); + TEST_ASSERT(mp_iszero(failedEcc.k) != 0, + "ec2 failed private-only import zeroes scalar"); +#if defined(WOLFSSL_SP_MATH_ALL) || defined(WOLFSSL_SP_MATH) + #ifdef ALT_ECC_SIZE + TEST_ASSERT(failedEcc.ka[0].dp[ + failedEcc.ka[0].size - 1u] == (mp_digit)0, + "ec2 failed import scrubs alt SP capacity"); + #else + TEST_ASSERT(failedEcc.k->dp[ + failedEcc.k->size - 1u] == (mp_digit)0, + "ec2 failed import scrubs SP capacity"); + #endif +#endif +#if defined(HAVE_WOLF_BIGINT) && \ + (!defined(ALT_ECC_SIZE) || !defined(USE_FAST_MATH)) + #ifdef ALT_ECC_SIZE + for (rawIdx = 0; rawIdx < failedEcc.ka[0].raw.len; rawIdx++) { + if (failedEcc.ka[0].raw.buf[rawIdx] != 0u) { + #else + for (rawIdx = 0; rawIdx < failedEcc.k->raw.len; rawIdx++) { + if (failedEcc.k->raw.buf[rawIdx] != 0u) { + #endif + rawCleared = 0; + } + } + TEST_ASSERT(rawCleared == 1, + "ec2 failed private-only import zeroes raw scalar"); +#endif + ret = wc_ecc_export_private_only(&failedEcc, exposedD, + &exposedDSz); + TEST_ASSERT(ret != 0, + "ec2 failed private-only import clears scalar"); + TEST_ASSERT(failedKey.hasPrivate == 0, + "ec2 failed private-only import leaves wrapper public"); + TEST_ASSERT(failedEcc.heap == failedHeap, + "ec2 failed private-only import preserves heap"); + TEST_ASSERT(failedEcc.flags == WC_ECC_FLAG_COFACTOR, + "ec2 failed private-only import preserves flags"); + TEST_ASSERT((failedEcc.type == failedType) && + (failedEcc.idx == failedIdx) && + (failedEcc.state == failedState) && + (failedEcc.dp == failedDp), + "ec2 failed private-only import restores key state"); +#ifdef ECC_TIMING_RESISTANT + TEST_ASSERT(failedEcc.rng == &rng, + "ec2 failed private-only import preserves rng"); +#endif +#ifdef WOLFSSL_ECC_BLIND_K + TEST_ASSERT((mp_iszero(failedEcc.kb) != 0) && + (mp_iszero(failedEcc.ku) != 0), + "ec2 failed private-only import clears blinding state"); +#endif +#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) + TEST_ASSERT(failedEcc.devCtx == failedDevCtx, + "ec2 failed private-only import preserves device ctx"); + TEST_ASSERT(failedEcc.devId == failedDevId, + "ec2 failed private-only import preserves device id"); +#endif +#ifdef WOLF_PRIVATE_KEY_ID + TEST_ASSERT((failedEcc.idLen == 1) && + (failedEcc.id[0] == 0xa5u), + "ec2 failed private-only import preserves key id"); + TEST_ASSERT((failedEcc.labelLen == 1) && + (failedEcc.label[0] == 'k'), + "ec2 failed private-only import preserves key label"); +#endif +#if defined(WOLFSSL_ECDSA_DETERMINISTIC_K) || \ + defined(WOLFSSL_ECDSA_DETERMINISTIC_K_VARIANT) + TEST_ASSERT((failedEcc.deterministic == 1u) && + (failedEcc.hashType == WC_HASH_TYPE_SHA256), + "ec2 failed private-only import preserves signing mode"); +#endif + + wolfForceFailure_Set(WOLF_FAIL_ECC_IMPORT_PRIVATE_POST); + ret = wc_CoseKey_Decode(&failedKey, fullKeyBuf, fullEnc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_CRYPTO, + "ec2 failed full private import rejected"); + TEST_ASSERT(wolfForceFailure_Get() == WOLF_FAIL_NONE, + "ec2 full post-import failure hook reached"); + TEST_ASSERT((mp_iszero(failedEcc.k) != 0) && + (failedKey.hasPrivate == 0), + "ec2 failed full private import clears scalar"); + TEST_ASSERT((failedEcc.type == failedType) && + (failedEcc.idx == failedIdx) && + (failedEcc.state == failedState) && + (failedEcc.dp == failedDp), + "ec2 failed full private import restores key state"); + +#if defined(PLUTON_CRYPTO_ECC) || defined(WOLF_CRYPTO_CB) + failedEcc.devCtx = NULL; +#endif + ret = wc_CoseKey_Decode(&failedKey, keyBuf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "ec2 key reusable after failed private-only import"); + TEST_ASSERT(failedKey.hasPrivate == 1, + "ec2 reused key has private"); + (void)wc_ecc_free(&failedEcc); + } +#else + /* Non-transactional backends must be rejected before the import and + * therefore before the post-import failure hook can be consumed. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY backendKey; + ecc_key backendEcc; + int backendType; + int backendIdx; + int backendState; + const ecc_set_type* backendDp; + + ret = wc_ecc_init(&backendEcc); + TEST_ASSERT(ret == 0, "ec backend-reject key init"); + backendType = backendEcc.type; + backendIdx = backendEcc.idx; + backendState = backendEcc.state; + backendDp = backendEcc.dp; + (void)wc_CoseKey_Init(&backendKey); + (void)wc_CoseKey_SetEcc(&backendKey, WOLFCOSE_CRV_P256, + &backendEcc); + wolfForceFailure_Set(WOLF_FAIL_ECC_IMPORT_PRIVATE_POST); + ret = wc_CoseKey_Decode(&backendKey, keyBuf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED, + "ec2 non-transactional backend rejected"); + TEST_ASSERT(wolfForceFailure_Get() == + WOLF_FAIL_ECC_IMPORT_PRIVATE_POST, + "ec2 backend rejection occurs before import"); + wolfForceFailure_Clear(); + TEST_ASSERT((mp_iszero(backendEcc.k) != 0) && + (backendEcc.type == backendType) && + (backendEcc.idx == backendIdx) && + (backendEcc.state == backendState) && + (backendEcc.dp == backendDp), + "ec2 backend rejection preserves empty key"); + + wolfForceFailure_Set(WOLF_FAIL_ECC_IMPORT_PRIVATE_POST); + ret = wc_CoseKey_Decode(&backendKey, fullKeyBuf, fullEnc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED, + "ec2 full private backend rejected"); + TEST_ASSERT(wolfForceFailure_Get() == + WOLF_FAIL_ECC_IMPORT_PRIVATE_POST, + "ec2 full backend rejection occurs before import"); + wolfForceFailure_Clear(); + (void)wc_ecc_free(&backendEcc); + } +#endif /* WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED */ + +#if !defined(WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED) && \ + (((defined(WOLF_CRYPTO_CB) && defined(WOLF_CRYPTO_CB_SETKEY)) && \ + !defined(WOLF_CRYPTO_CB_FIND)) || \ + defined(WOLFSSL_MAXQ10XX_CRYPTO)) + /* A configured callback/device would make the import + * non-transactional even when software fallback is otherwise safe. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY deviceKey; + ecc_key deviceEcc; + int savedDevId; + + ret = wc_ecc_init(&deviceEcc); + TEST_ASSERT(ret == 0, "ec device-reject key init"); + savedDevId = deviceEcc.devId; + deviceEcc.devId = 1; + (void)wc_CoseKey_Init(&deviceKey); + (void)wc_CoseKey_SetEcc(&deviceKey, WOLFCOSE_CRV_P256, + &deviceEcc); + wolfForceFailure_Set(WOLF_FAIL_ECC_IMPORT_PRIVATE_POST); + ret = wc_CoseKey_Decode(&deviceKey, keyBuf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED, + "ec2 configured device backend rejected"); + TEST_ASSERT(wolfForceFailure_Get() == + WOLF_FAIL_ECC_IMPORT_PRIVATE_POST, + "ec2 device rejection occurs before import"); + wolfForceFailure_Clear(); + TEST_ASSERT(mp_iszero(deviceEcc.k) != 0, + "ec2 device rejection imports no scalar"); + deviceEcc.devId = savedDevId; + (void)wc_ecc_free(&deviceEcc); + } +#endif + + /* Do not risk replacing an existing private key non-transactionally. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY occupiedKey; + uint8_t occupiedD[32]; + word32 occupiedDSz = sizeof(occupiedD); + + (void)wc_CoseKey_Init(&occupiedKey); + (void)wc_CoseKey_SetEcc(&occupiedKey, WOLFCOSE_CRV_P256, + &eccKey); + wolfForceFailure_Set(WOLF_FAIL_ECC_IMPORT_X963); + ret = wc_CoseKey_Decode(&occupiedKey, keyBuf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "ec2 private-only import rejects occupied key"); + wolfForceFailure_Clear(); + ret = wc_ecc_export_private_only(&eccKey, occupiedD, + &occupiedDSz); + TEST_ASSERT((ret == 0) && (occupiedDSz == dSz) && + (XMEMCMP(occupiedD, dBuf, dSz) == 0), + "ec2 rejected import preserves existing private key"); + } + + /* Existing curve configuration is also caller-owned state. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY configuredKey; + ecc_key configuredEcc; + const ecc_set_type* configuredDp = &ecc_sets[0]; + + ret = wc_ecc_init(&configuredEcc); + TEST_ASSERT(ret == 0, "ec configured key init"); + (void)wc_CoseKey_Init(&configuredKey); + ret = wc_CoseKey_SetEcc(&configuredKey, WOLFCOSE_CRV_P256, + &configuredEcc); + TEST_ASSERT(ret == 0, "ec configured COSE key attach"); + configuredEcc.idx = ECC_CUSTOM_IDX; + configuredEcc.dp = configuredDp; + wolfForceFailure_Set(WOLF_FAIL_ECC_IMPORT_X963); + ret = wc_CoseKey_Decode(&configuredKey, keyBuf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "ec2 private-only import rejects configured curve"); + wolfForceFailure_Clear(); + TEST_ASSERT((configuredEcc.idx == ECC_CUSTOM_IDX) && + (configuredEcc.dp == configuredDp), + "ec2 rejected import preserves configured curve"); + configuredEcc.idx = 0; + configuredEcc.dp = NULL; + (void)wc_ecc_free(&configuredEcc); + } + +#ifdef ALT_ECC_SIZE + /* ALT members must still point at their initialized inline storage. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY layoutKey; + ecc_key layoutEcc; + + ret = wc_ecc_init(&layoutEcc); + TEST_ASSERT(ret == 0, "ec alt-layout key init"); + layoutEcc.k = NULL; + (void)wc_CoseKey_Init(&layoutKey); + (void)wc_CoseKey_SetEcc(&layoutKey, WOLFCOSE_CRV_P256, + &layoutEcc); + wolfForceFailure_Set(WOLF_FAIL_ECC_IMPORT_PRIVATE_POST); + ret = wc_CoseKey_Decode(&layoutKey, keyBuf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "ec2 private-only rejects invalid alt layout"); + TEST_ASSERT(wolfForceFailure_Get() == + WOLF_FAIL_ECC_IMPORT_PRIVATE_POST, + "ec2 alt-layout rejection occurs before import"); + wolfForceFailure_Clear(); + layoutEcc.k = (mp_int*)&layoutEcc.ka[0]; + (void)wc_ecc_free(&layoutEcc); + } +#endif +#endif (void)wc_ecc_free(&eccKey); (void)wc_ecc_free(&eccKey2); @@ -4712,7 +6043,7 @@ static void test_cose_key_mldsa_public_only(void) static void test_cose_key_mldsa_negative(void) { WOLFCOSE_KEY key; - wc_MlDsaKey dlKey, dlKey2; + wc_MlDsaKey dlKey, dlKey2, dlMismatch; WC_RNG rng; uint8_t seed[WOLFCOSE_MLDSA_SEED_SZ]; uint8_t pubBuf[2048]; @@ -4722,12 +6053,25 @@ static void test_cose_key_mldsa_negative(void) size_t outLen; WOLFCOSE_CBOR_CTX enc; int ret; +#if !defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) && \ + !defined(WOLFSSL_MLDSA_VERIFY_ONLY) + size_t i; +#endif + int privateCleared = 1; TEST_LOG(" [Key ML-DSA negative]\n"); wc_InitRng(&rng); wc_MlDsaKey_Init(&dlKey, NULL, INVALID_DEVID); wc_MlDsaKey_Init(&dlKey2, NULL, INVALID_DEVID); + wc_MlDsaKey_Init(&dlMismatch, (void*)&key, INVALID_DEVID); +#ifdef WOLF_CRYPTO_CB + dlMismatch.devCtx = (void*)&dlKey; +#endif +#ifdef WOLF_PRIVATE_KEY_ID + dlMismatch.id[0] = 0xa5u; + dlMismatch.idLen = 1; +#endif wc_MlDsaKey_SetParams(&dlKey, WC_ML_DSA_44); wc_RNG_GenerateBlock(&rng, seed, (word32)sizeof(seed)); wc_MlDsaKey_MakeKeyFromSeed(&dlKey, seed); @@ -4765,6 +6109,12 @@ static void test_cose_key_mldsa_negative(void) wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PRIV); wc_CBOR_EncodeBstr(&enc, seed, sizeof(seed)); (void)wc_CoseKey_Init(&key); + + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "metadata-only dl key missing pub rejected"); + + (void)wc_CoseKey_Init(&key); (void)wc_CoseKey_SetMlDsa(&key, WOLFCOSE_ALG_ML_DSA_44, &dlKey2); ret = wc_CoseKey_Decode(&key, buf, enc.idx); TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, "dl decode rejects missing pub"); @@ -4777,10 +6127,30 @@ static void test_cose_key_mldsa_negative(void) wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PUB); wc_CBOR_EncodeBstr(&enc, pubBuf, (size_t)pubSz); (void)wc_CoseKey_Init(&key); + + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "metadata-only dl key missing alg rejected"); + + (void)wc_CoseKey_Init(&key); (void)wc_CoseKey_SetMlDsa(&key, WOLFCOSE_ALG_ML_DSA_44, &dlKey2); ret = wc_CoseKey_Decode(&key, buf, enc.idx); TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, "dl decode rejects missing alg"); + /* Decode: metadata-only AKP key with a non-ML-DSA alg is rejected. */ + enc.idx = 0; + wc_CBOR_EncodeMapStart(&enc, 3); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); + wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_AKP); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_ALG); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_ES256); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PUB); + wc_CBOR_EncodeBstr(&enc, pubBuf, (size_t)pubSz); + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "metadata-only dl key unsupported alg rejected"); + /* Decode: AKP private key with a wrong-length seed is rejected. */ enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 4); @@ -4793,11 +6163,93 @@ static void test_cose_key_mldsa_negative(void) wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PRIV); wc_CBOR_EncodeBstr(&enc, seed, (size_t)16); (void)wc_CoseKey_Init(&key); + + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "metadata-only dl key wrong seed length rejected"); + + (void)wc_CoseKey_Init(&key); (void)wc_CoseKey_SetMlDsa(&key, WOLFCOSE_ALG_ML_DSA_44, &dlKey2); ret = wc_CoseKey_Decode(&key, buf, enc.idx); TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, "dl decode rejects wrong seed length"); + /* Decode: a private AKP key with a short public parameter is rejected. */ + enc.idx = 0; + wc_CBOR_EncodeMapStart(&enc, 4); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); + wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_AKP); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_ALG); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_ML_DSA_44); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PUB); + wc_CBOR_EncodeBstr(&enc, pubBuf, (size_t)1); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PRIV); + wc_CBOR_EncodeBstr(&enc, seed, sizeof(seed)); + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetMlDsa(&key, WOLFCOSE_ALG_ML_DSA_44, &dlKey2); + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_CRYPTO, + "dl private decode rejects short pub"); + + /* Decode: pub must match the public key derived from the private seed. */ + pubBuf[0] ^= 1u; + enc.idx = 0; + wc_CBOR_EncodeMapStart(&enc, 4); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); + wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_AKP); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_ALG); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_ML_DSA_44); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PUB); + wc_CBOR_EncodeBstr(&enc, pubBuf, (size_t)pubSz); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PRIV); + wc_CBOR_EncodeBstr(&enc, seed, sizeof(seed)); + pubBuf[0] ^= 1u; + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetMlDsa(&key, WOLFCOSE_ALG_ML_DSA_44, &dlMismatch); + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "dl private decode rejects mismatched pub and seed"); + TEST_ASSERT((key.attachedType == WOLFCOSE_ATT_MLDSA) && + (key.key.mldsa == &dlMismatch), + "dl mismatch preserves attached key"); + TEST_ASSERT(dlMismatch.heap == (void*)&key, + "dl mismatch preserves heap hint"); +#ifdef WOLF_CRYPTO_CB + TEST_ASSERT(dlMismatch.devCtx == (void*)&dlKey, + "dl mismatch preserves device context"); +#endif +#ifdef WOLF_PRIVATE_KEY_ID + TEST_ASSERT((dlMismatch.idLen == 1) && (dlMismatch.id[0] == 0xa5u), + "dl mismatch preserves private key identifier"); +#endif +#if defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) + privateCleared = (dlMismatch.k == NULL) ? 1 : 0; +#elif !defined(WOLFSSL_MLDSA_VERIFY_ONLY) + for (i = 0u; i < sizeof(dlMismatch.k); i++) { + if (dlMismatch.k[i] != 0u) { + privateCleared = 0; + } + } +#endif + TEST_ASSERT((privateCleared == 1) && (dlMismatch.prvKeySet == 0u), + "dl mismatch clears derived private key"); + + /* The same attached object remains usable for a valid decode. */ + enc.idx = 0; + wc_CBOR_EncodeMapStart(&enc, 4); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); + wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_AKP); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_ALG); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_ML_DSA_44); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PUB); + wc_CBOR_EncodeBstr(&enc, pubBuf, (size_t)pubSz); + wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_PRIV); + wc_CBOR_EncodeBstr(&enc, seed, sizeof(seed)); + ret = wc_CoseKey_Decode(&key, buf, enc.idx); + TEST_ASSERT((ret == WOLFCOSE_SUCCESS) && (key.hasPrivate == 1u) && + (key.key.mldsa == &dlMismatch), + "dl mismatch key is reusable"); + /* Decode: AKP key carrying a crv is rejected (AKP has no crv). */ enc.idx = 0; wc_CBOR_EncodeMapStart(&enc, 3); @@ -4815,6 +6267,7 @@ static void test_cose_key_mldsa_negative(void) wc_CoseKey_Free(&key); (void)wc_MlDsaKey_Free(&dlKey); (void)wc_MlDsaKey_Free(&dlKey2); + (void)wc_MlDsaKey_Free(&dlMismatch); (void)wc_FreeRng(&rng); } #endif /* WOLFCOSE_HAVE_MLDSA */ @@ -5204,29 +6657,85 @@ static void test_cose_key_encode_ecc_raw_curves(void) } } #endif /* WOLFCOSE_HAVE_ES384 || WOLFCOSE_HAVE_ES512 */ -#endif /* WOLFCOSE_HAVE_ES256 */ -/* The size query must be exact, not an upper bound, for every key type the - * build supports. Compares against what the encoder actually writes. */ -static void test_cose_key_encode_size_exact(void) +#ifdef WOLFCOSE_HAVE_ES384 +static void test_cose_key_encode_ecc_curve_mismatch(void) { - uint8_t out[4096]; - size_t outLen = 0; - size_t sized = 0; + WOLFCOSE_KEY key; + ecc_key eccKey; + WC_RNG rng; + uint8_t out[256]; + size_t outLen = 0u; int ret; - TEST_LOG(" [Key EncodeSize Exact]\n"); + TEST_LOG(" [Key Encode ECC Curve Mismatch]\n"); - /* empty-brace-scan: allow - test-local temporary scope */ - { - WOLFCOSE_KEY symKey; - static const uint8_t symBytes[32] = {0}; - static const uint8_t symKid[] = "sym-1"; + (void)wc_CoseKey_Init(&key); + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == 0, "curve mismatch rng init"); + if (ret != 0) { + return; + } + ret = wc_ecc_init(&eccKey); + TEST_ASSERT(ret == 0, "curve mismatch ecc init"); + if (ret != 0) { + (void)wc_FreeRng(&rng); + return; + } + ret = wc_ecc_make_key(&rng, 48, &eccKey); + TEST_ASSERT(ret == 0, "curve mismatch keygen"); + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P384, &eccKey); + } + TEST_ASSERT(ret == 0, "curve mismatch key attach"); - (void)wc_CoseKey_Init(&symKey); - (void)wc_CoseKey_SetSymmetric(&symKey, symBytes, sizeof(symBytes)); - symKey.kid = symKid; - symKey.kidLen = sizeof(symKid) - 1u; + if (ret == 0) { + /* The setter now rejects mismatches. Mutate the public metadata to + * retain direct coverage of the encoder's independent defense. */ + key.crv = WOLFCOSE_CRV_P256; + ret = wc_CoseKey_EncodeSize_ex(&key, &outLen, + WOLFCOSE_KEY_PUBLIC_ONLY); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "curve mismatch size rejected"); + outLen = 0u; + ret = wc_CoseKey_Encode_ex(&key, out, sizeof(out), &outLen, + WOLFCOSE_KEY_PUBLIC_ONLY); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "curve mismatch public encode rejected"); + outLen = 0u; + ret = wc_CoseKey_Encode(&key, out, sizeof(out), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "curve mismatch private encode rejected"); + } + + wc_CoseKey_Free(&key); + (void)wc_ecc_free(&eccKey); + (void)wc_FreeRng(&rng); +} +#endif /* WOLFCOSE_HAVE_ES384 */ +#endif /* WOLFCOSE_HAVE_ES256 */ + +/* The size query must be exact, not an upper bound, for every key type the + * build supports. Compares against what the encoder actually writes. */ +static void test_cose_key_encode_size_exact(void) +{ + uint8_t out[4096]; + size_t outLen = 0; + size_t sized = 0; + int ret; + + TEST_LOG(" [Key EncodeSize Exact]\n"); + + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY symKey; + static const uint8_t symBytes[32] = {0}; + static const uint8_t symKid[] = "sym-1"; + + (void)wc_CoseKey_Init(&symKey); + (void)wc_CoseKey_SetSymmetric(&symKey, symBytes, sizeof(symBytes)); + symKey.kid = symKid; + symKey.kidLen = sizeof(symKid) - 1u; symKey.alg = WOLFCOSE_ALG_HMAC_256_256; ret = wc_CoseKey_Encode(&symKey, out, sizeof(out), &outLen); @@ -5543,8 +7052,13 @@ static void test_cose_key_encode_public_only_types(void) "pubtype rsa full encode"); ret = wc_CoseKey_Encode_ex(&key, pub, sizeof(pub), &pubLen, WOLFCOSE_KEY_PUBLIC_ONLY); +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY TEST_ASSERT(ret == 0 && pubLen < fullLen, "pubtype rsa public is shorter"); +#else + TEST_ASSERT(ret == 0 && pubLen == fullLen, + "pubtype rsa is already public-only"); +#endif (void)wc_InitRsaKey(&rsaPub, NULL); (void)wc_CoseKey_Init(&decKey); @@ -7218,6 +8732,110 @@ static void test_cose_sign_verify_key_alg_mismatch(void) (void)wc_FreeRng(&rng); } +static void test_cose_sign_verify_unprotected_alg(void) +{ + WOLFCOSE_KEY key; + ecc_key eccKey; + WC_RNG rng; + WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_HDR hdr; + const uint8_t payloadData[] = "unprotected signer alg"; + const uint8_t* payload = NULL; + size_t payloadLen = 0; + uint8_t emptyProtected = 0; + uint8_t sigStruct[128]; + size_t sigStructLen = 0; + uint8_t hash[WC_SHA256_DIGEST_SIZE]; + uint8_t signature[64]; + size_t signatureLen = sizeof(signature); + uint8_t msg[256]; + uint8_t scratch[256]; + int ret; + + TEST_LOG(" [Sign_Verify unprotected signer alg]\n"); + + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == 0, "unprotected signer alg rng"); + ret = wc_ecc_init(&eccKey); + TEST_ASSERT(ret == 0, "unprotected signer alg ecc init"); + ret = wc_ecc_make_key(&rng, 32, &eccKey); + TEST_ASSERT(ret == 0, "unprotected signer alg keygen"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &eccKey); + TEST_ASSERT(ret == 0, "unprotected signer alg key set"); + key.alg = WOLFCOSE_ALG_ES256; + + ret = wolfCose_BuildToBeSignedMaced( + WOLFCOSE_CTX_SIGNATURE, sizeof(WOLFCOSE_CTX_SIGNATURE), + NULL, 0, &emptyProtected, 0, NULL, 0, + payloadData, sizeof(payloadData) - 1, + sigStruct, sizeof(sigStruct), &sigStructLen); + TEST_ASSERT(ret == 0, "unprotected signer alg structure"); + ret = wc_Hash(WC_HASH_TYPE_SHA256, sigStruct, (word32)sigStructLen, + hash, sizeof(hash)); + TEST_ASSERT(ret == 0, "unprotected signer alg hash"); + ret = wolfCose_EccSignRaw(hash, sizeof(hash), signature, &signatureLen, + 32u, WC_HASH_TYPE_SHA256, &rng, &eccKey); + TEST_ASSERT(ret == 0, "unprotected signer alg signature"); + + enc.buf = msg; + enc.bufSz = sizeof(msg); + enc.idx = 0; + ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_SIGN); + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 4u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 0u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, payloadData, + sizeof(payloadData) - 1); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 3u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_ES256); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, signature, signatureLen); + } + TEST_ASSERT(ret == 0, "unprotected signer alg message"); + + ret = wc_CoseSign_Verify(&key, 0, msg, enc.idx, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == 0, "pinned unprotected signer alg accepted"); + + key.alg = WOLFCOSE_ALG_UNSET; + ret = wc_CoseSign_Verify(&key, 0, msg, enc.idx, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "unpinned unprotected signer alg rejected"); + + wc_CoseKey_Free(&key); + (void)wc_ecc_free(&eccKey); + (void)wc_FreeRng(&rng); +} + static void test_cose_encrypt0_decrypt_key_alg_mismatch(void) { WOLFCOSE_KEY encKey; @@ -8027,6 +9645,150 @@ static void test_cose_encrypt_direct_key_alg_pin_roundtrip(void) wc_CoseKey_Free(&key); } +static void test_cose_encrypt_unprotected_body_alg(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX aadEnc; + Aes aes; + const uint8_t keyData[16] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F + }; + const uint8_t iv[WOLFCOSE_AES_GCM_NONCE_SZ] = { + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B + }; + const uint8_t payloadData[] = "unprotected body alg"; + uint8_t ciphertext[sizeof(payloadData) - 1u + WOLFCOSE_AES_GCM_TAG_SZ]; + uint8_t aad[32]; + uint8_t msg[256]; + uint8_t scratch[256]; + uint8_t plaintext[64]; + size_t plaintextLen = 0u; + int aesInited = 0; + int ret; + + TEST_LOG(" [Encrypt unprotected body alg]\n"); + + (void)XMEMSET(&aadEnc, 0, sizeof(aadEnc)); + aadEnc.buf = aad; + aadEnc.bufSz = sizeof(aad); + ret = wc_CBOR_EncodeArrayStart(&aadEnc, 3u); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTstr(&aadEnc, WOLFCOSE_CTX_ENCRYPT, + sizeof(WOLFCOSE_CTX_ENCRYPT)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&aadEnc, NULL, 0u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&aadEnc, NULL, 0u); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "unprotected body alg Enc_structure"); + + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (ret == 0) { + aesInited = 1; + ret = wc_AesGcmSetKey(&aes, keyData, sizeof(keyData)); + } + } + if (ret == 0) { + ret = wc_AesGcmEncrypt(&aes, ciphertext, payloadData, + (word32)(sizeof(payloadData) - 1u), iv, (word32)sizeof(iv), + &ciphertext[sizeof(payloadData) - 1u], + (word32)WOLFCOSE_AES_GCM_TAG_SZ, aad, (word32)aadEnc.idx); + } + TEST_ASSERT(ret == 0, "unprotected body alg encrypt"); + if (aesInited != 0) { + (void)wc_AesFree(&aes); + } + + (void)XMEMSET(&enc, 0, sizeof(enc)); + enc.buf = msg; + enc.bufSz = sizeof(msg); + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_ENCRYPT); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&enc, 4u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&enc, 2u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_A128GCM); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_IV); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&enc, iv, sizeof(iv)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&enc, ciphertext, sizeof(ciphertext)); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&enc, 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeArrayStart(&enc, 3u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeMapStart(&enc, 1u); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_DIRECT); + } + if (ret == WOLFCOSE_SUCCESS) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); + } + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "unprotected body alg message"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, "unprotected body alg key set"); + key.alg = WOLFCOSE_ALG_A128GCM; + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0u; + + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, msg, enc.idx, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "pinned unprotected body alg accepted"); + TEST_ASSERT((plaintextLen == (sizeof(payloadData) - 1u)) && + (XMEMCMP(plaintext, payloadData, plaintextLen) == 0), + "unprotected body alg payload"); + + key.alg = WOLFCOSE_ALG_UNSET; + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, msg, enc.idx, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "unpinned unprotected body alg rejected"); + + wc_CoseKey_Free(&key); +} + static void test_cose_encrypt_unset_alg_rejected(void) { WOLFCOSE_KEY key; @@ -8519,10 +10281,97 @@ static void test_cose_encrypt_ecdh_es_ephemeral_crv_narrowing(void) (void)wc_FreeRng(&rng); } +static void test_cose_encrypt_ecdh_es_malformed_ephemeral_point(void) +{ + WOLFCOSE_KEY recipientKey; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + ecc_key recipientEcc; + WC_RNG rng; + int ret; + uint8_t out[1024]; + size_t outLen = 0u; + uint8_t scratch[1024]; + uint8_t plaintext[128]; + size_t plaintextLen = 0u; + const uint8_t payload[] = "ECDH-ES malformed ephemeral point"; + uint8_t iv[12]; + size_t i; + size_t xPos = 0u; + int found = 0; + static const uint8_t xAnchor[] = { + 0xA4u, 0x01u, 0x02u, 0x20u, 0x01u, 0x21u, 0x58u, 0x20u + }; + + TEST_LOG(" [Encrypt ECDH-ES malformed ephemeral point]\n"); + + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == 0, "ecdh-es-point rng"); + ret = wc_ecc_init(&recipientEcc); + TEST_ASSERT(ret == 0, "ecdh-es-point ecc init"); + ret = wc_ecc_make_key(&rng, 32, &recipientEcc); + TEST_ASSERT(ret == 0, "ecdh-es-point keygen"); + + (void)wc_CoseKey_Init(&recipientKey); + ret = wc_CoseKey_SetEcc(&recipientKey, WOLFCOSE_CRV_P256, &recipientEcc); + TEST_ASSERT(ret == 0, "ecdh-es-point 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 == 0, "ecdh-es-point 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 == 0, "ecdh-es-point encrypt"); + + for (i = 0u; (found == 0) && (outLen >= sizeof(xAnchor)) && + (i <= (outLen - sizeof(xAnchor))); i++) { + if (XMEMCMP(&out[i], xAnchor, sizeof(xAnchor)) == 0) { + xPos = i + sizeof(xAnchor); + found = 1; + } + } + TEST_ASSERT((found == 1) && ((xPos + 32u) <= outLen), + "ecdh-es-point x coordinate located"); + + if ((found == 1) && ((xPos + 32u) <= outLen)) { + (void)XMEMSET(&out[xPos], 0, 32u); + recipientKey.hasPrivate = 1u; + (void)XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wc_CoseEncrypt_Decrypt( + &recipient, 0u, + out, outLen, + NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), + &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "ecdh-es malformed point rejected"); + } + + wc_CoseKey_Free(&recipientKey); + (void)wc_ecc_free(&recipientEcc); + (void)wc_FreeRng(&rng); +} + static void test_cose_encrypt_ecdh_es_hkdf_256(void) { WOLFCOSE_KEY recipientKey; + WOLFCOSE_KEY mismatchKey; WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_RECIPIENT mismatchRecipient; WOLFCOSE_HDR hdr; ecc_key recipientEcc; WC_RNG rng; @@ -8564,6 +10413,23 @@ static void test_cose_encrypt_ecdh_es_hkdf_256(void) ret = wc_RNG_GenerateBlock(&rng, iv, sizeof(iv)); TEST_ASSERT(ret == 0, "ecdh-es generate iv"); + mismatchKey = recipientKey; + mismatchKey.crv = WOLFCOSE_CRV_P384; + mismatchRecipient = recipient; + mismatchRecipient.key = &mismatchKey; + ret = wc_CoseEncrypt_Encrypt( + &mismatchRecipient, 1, + WOLFCOSE_ALG_A128GCM, + iv, sizeof(iv), + payload, sizeof(payload) - 1, + NULL, 0, + NULL, 0, + scratch, sizeof(scratch), + out, sizeof(out), &outLen, + &rng); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "ecdh-es rejects mismatched recipient curve"); + /* Encrypt */ ret = wc_CoseEncrypt_Encrypt( &recipient, 1, @@ -8604,6 +10470,320 @@ static void test_cose_encrypt_ecdh_es_hkdf_256(void) (void)wc_FreeRng(&rng); } +static void test_cose_encrypt_ecdh_es_long_recipient_protected(void) +{ + WOLFCOSE_KEY recipientKey; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + WOLFCOSE_CBOR_CTX ctx; + ecc_key recipientEcc; + ecc_key ephemeralEcc; + WC_RNG rng; + Aes aes; + int ret; + int rngInited = 0; + int recipientInited = 0; + int ephemeralInited = 0; + int aesInited = 0; + word32 xLen; + word32 yLen; + word32 sharedSecretLen; + uint8_t x[32]; + uint8_t y[32]; + uint8_t sharedSecret[32]; + uint8_t cek[16]; + uint8_t recipientProtected[80]; + size_t recipientProtectedLen = 0u; + uint8_t kdfContext[96]; + size_t kdfContextLen = 0u; + uint8_t bodyProtected[8]; + size_t bodyProtectedLen = 0u; + uint8_t aad[32]; + size_t aadLen = 0u; + uint8_t ciphertext[64]; + uint8_t msg[384]; + size_t msgLen = 0u; + uint8_t scratch[256]; + uint8_t plaintext[64]; + size_t plaintextLen = 0u; + const uint8_t payload[] = "ECDH-ES long protected header"; + const uint8_t iv[WOLFCOSE_AES_GCM_NONCE_SZ] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B + }; + const uint8_t kid[48] = { 0xA5 }; + + TEST_LOG(" [Encrypt ECDH-ES long recipient protected header]\n"); + + ret = wc_InitRng(&rng); + if (ret == 0) { + rngInited = 1; + ret = wc_ecc_init(&recipientEcc); + } + if (ret == 0) { + recipientInited = 1; + ret = wc_ecc_make_key(&rng, 32, &recipientEcc); + } + if (ret == 0) { + ret = wc_ecc_init(&ephemeralEcc); + } + if (ret == 0) { + ephemeralInited = 1; + ret = wc_ecc_make_key(&rng, 32, &ephemeralEcc); + } + if (ret == 0) { + ret = wc_ecc_set_rng(&recipientEcc, &rng); + } + if (ret == 0) { + ret = wc_ecc_set_rng(&ephemeralEcc, &rng); + } + TEST_ASSERT(ret == 0, "ecdh-es long protected key setup"); + + xLen = (word32)sizeof(x); + yLen = (word32)sizeof(y); + if (ret == 0) { + ret = wc_ecc_export_public_raw(&ephemeralEcc, x, &xLen, y, &yLen); + } + sharedSecretLen = (word32)sizeof(sharedSecret); + if (ret == 0) { + ret = wc_ecc_shared_secret(&ephemeralEcc, &recipientEcc, + sharedSecret, &sharedSecretLen); + } + + (void)XMEMSET(&ctx, 0, sizeof(ctx)); + ctx.buf = recipientProtected; + ctx.bufSz = sizeof(recipientProtected); + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&ctx, 2u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_ALG); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_ALG_ECDH_ES_HKDF_256); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_KID); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, kid, sizeof(kid)); + } + if (ret == 0) { + recipientProtectedLen = ctx.idx; + } + + (void)XMEMSET(&ctx, 0, sizeof(ctx)); + ctx.buf = kdfContext; + ctx.bufSz = sizeof(kdfContext); + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 4u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_ALG_A128GCM); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 3u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeNull(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_EncodeNull(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_EncodeNull(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 3u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeNull(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_EncodeNull(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_EncodeNull(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 2u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeUint(&ctx, 128u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, recipientProtected, + recipientProtectedLen); + } + if (ret == 0) { + kdfContextLen = ctx.idx; + ret = wc_HKDF(WC_SHA256, sharedSecret, sharedSecretLen, + NULL, 0u, kdfContext, (word32)kdfContextLen, + cek, (word32)sizeof(cek)); + } + TEST_ASSERT((ret == 0) && (kdfContextLen > 64u), + "ecdh-es long protected KDF context exceeds 64 bytes"); + + (void)XMEMSET(&ctx, 0, sizeof(ctx)); + ctx.buf = bodyProtected; + ctx.bufSz = sizeof(bodyProtected); + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&ctx, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_ALG); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_ALG_A128GCM); + } + if (ret == 0) { + bodyProtectedLen = ctx.idx; + } + + (void)XMEMSET(&ctx, 0, sizeof(ctx)); + ctx.buf = aad; + ctx.bufSz = sizeof(aad); + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 3u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeTstr(&ctx, WOLFCOSE_CTX_ENCRYPT, + sizeof(WOLFCOSE_CTX_ENCRYPT)); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, bodyProtected, bodyProtectedLen); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, NULL, 0u); + } + if (ret == 0) { + aadLen = ctx.idx; + ret = wc_AesInit(&aes, NULL, INVALID_DEVID); + } + if (ret == 0) { + aesInited = 1; + ret = wc_AesGcmSetKey(&aes, cek, (word32)sizeof(cek)); + } + if (ret == 0) { + ret = wc_AesGcmEncrypt(&aes, ciphertext, payload, + (word32)(sizeof(payload) - 1u), iv, (word32)sizeof(iv), + &ciphertext[sizeof(payload) - 1u], WOLFCOSE_AES_GCM_TAG_SZ, + aad, (word32)aadLen); + } + + (void)XMEMSET(&ctx, 0, sizeof(ctx)); + ctx.buf = msg; + ctx.bufSz = sizeof(msg); + if (ret == 0) { + ret = wc_CBOR_EncodeTag(&ctx, WOLFCOSE_TAG_ENCRYPT); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 4u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, bodyProtected, bodyProtectedLen); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&ctx, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_IV); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, iv, sizeof(iv)); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, ciphertext, + sizeof(payload) - 1u + + WOLFCOSE_AES_GCM_TAG_SZ); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&ctx, 3u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, recipientProtected, + recipientProtectedLen); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&ctx, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_HDR_EPHEMERAL_KEY); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&ctx, 4u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, 1); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_KTY_EC2); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, -1); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, WOLFCOSE_CRV_P256); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, -2); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, x, (size_t)xLen); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&ctx, -3); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, y, (size_t)yLen); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&ctx, NULL, 0u); + } + if (ret == 0) { + msgLen = ctx.idx; + } + TEST_ASSERT(ret == 0, "ecdh-es long protected peer message encode"); + + (void)wc_CoseKey_Init(&recipientKey); + if (ret == 0) { + ret = wc_CoseKey_SetEcc(&recipientKey, WOLFCOSE_CRV_P256, + &recipientEcc); + } + if (ret == 0) { + recipientKey.hasPrivate = 1u; + recipient.algId = WOLFCOSE_ALG_ECDH_ES_HKDF_256; + recipient.key = &recipientKey; + recipient.kid = NULL; + recipient.kidLen = 0u; + (void)XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, msg, msgLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + } + TEST_ASSERT(ret == 0, "ecdh-es long protected peer decrypt"); + TEST_ASSERT((ret == 0) && (plaintextLen == (sizeof(payload) - 1u)) && + (XMEMCMP(plaintext, payload, plaintextLen) == 0), + "ecdh-es long protected peer plaintext"); + + wc_CoseKey_Free(&recipientKey); + if (aesInited != 0) { + (void)wc_AesFree(&aes); + } + if (ephemeralInited != 0) { + (void)wc_ecc_free(&ephemeralEcc); + } + if (recipientInited != 0) { + (void)wc_ecc_free(&recipientEcc); + } + if (rngInited != 0) { + (void)wc_FreeRng(&rng); + } +} + /** * Test ECDH-ES with wrong key fails decryption. */ @@ -8704,6 +10884,7 @@ static void test_cose_encrypt_ecdh_es_p384(void) WOLFCOSE_RECIPIENT recipient; WOLFCOSE_HDR hdr; ecc_key recipientEcc; + ecc_key mismatchedEcc; WC_RNG rng; int ret; uint8_t out[512]; @@ -8727,6 +10908,11 @@ static void test_cose_encrypt_ecdh_es_p384(void) ret = wc_ecc_make_key(&rng, 48, &recipientEcc); TEST_ASSERT(ret == 0, "ecdh-es p384 make key"); + ret = wc_ecc_init(&mismatchedEcc); + TEST_ASSERT(ret == 0, "ecdh-es mismatched ecc init"); + ret = wc_ecc_make_key(&rng, 32, &mismatchedEcc); + TEST_ASSERT(ret == 0, "ecdh-es mismatched make key"); + /* Set up recipient's public key for encryption */ (void)wc_CoseKey_Init(&recipientKey); ret = wc_CoseKey_SetEcc(&recipientKey, WOLFCOSE_CRV_P384, &recipientEcc); @@ -8756,6 +10942,26 @@ static void test_cose_encrypt_ecdh_es_p384(void) &rng); TEST_ASSERT(ret == 0, "ecdh-es p384 encrypt"); + ret = wc_CoseKey_SetEcc(&recipientKey, WOLFCOSE_CRV_P256, + &mismatchedEcc); + TEST_ASSERT(ret == 0, "ecdh-es set mismatched key"); + recipientKey.crv = WOLFCOSE_CRV_P384; + memset(&hdr, 0, sizeof(hdr)); + ret = wc_CoseEncrypt_Decrypt( + &recipient, 0, + out, outLen, + NULL, 0, + NULL, 0, + scratch, sizeof(scratch), + &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "ecdh-es decrypt rejects mismatched recipient curve"); + + ret = wc_CoseKey_SetEcc(&recipientKey, WOLFCOSE_CRV_P384, + &recipientEcc); + TEST_ASSERT(ret == 0, "ecdh-es restore p384 key"); + /* Set up recipient with private key for decryption */ recipientKey.hasPrivate = 1; @@ -8776,6 +10982,7 @@ static void test_cose_encrypt_ecdh_es_p384(void) /* Clean up */ wc_CoseKey_Free(&recipientKey); (void)wc_ecc_free(&recipientEcc); + (void)wc_ecc_free(&mismatchedEcc); (void)wc_FreeRng(&rng); } @@ -8981,9 +11188,136 @@ static void test_cose_encrypt_a128kw(void) plaintext, sizeof(plaintext), &plaintextLen); TEST_ASSERT(ret != 0, "a128kw wrong kek fails"); - /* Clean up */ + /* Clean up */ + wc_CoseKey_Free(&kek); + wc_CoseKey_Free(&wrongKek); + (void)wc_FreeRng(&rng); +} + +static void test_cose_encrypt_a128kw_unprotected_alg(void) +{ + WOLFCOSE_KEY kek; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + WOLFCOSE_CBOR_CTX ctx; + WC_RNG rng; + const uint8_t kekData[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F + }; + const uint8_t payload[] = "A128KW unprotected algorithm"; + uint8_t iv[12] = {0}; + uint8_t scratch[256]; + uint8_t out[512]; + uint8_t conformant[512]; + uint8_t plaintext[128]; + size_t outLen = 0u; + size_t conformantLen = 0u; + size_t plaintextLen = 0u; + size_t count = 0u; + size_t recipientHdrStart = 0u; + size_t recipientSuffix = 0u; + const uint8_t* protectedData = NULL; + size_t protectedLen = 0u; + uint64_t tag = 0u; + int ret; + + TEST_LOG(" [Encrypt A128KW unprotected recipient algorithm]\n"); + + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == 0, "a128kw unprotected rng init"); + (void)wc_CoseKey_Init(&kek); + ret = wc_CoseKey_SetSymmetric(&kek, kekData, sizeof(kekData)); + TEST_ASSERT(ret == 0, "a128kw unprotected set kek"); + recipient.algId = WOLFCOSE_ALG_A128KW; + recipient.key = &kek; + recipient.kid = NULL; + recipient.kidLen = 0u; + + 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 == 0, "a128kw unprotected base encrypt"); + + (void)XMEMSET(&ctx, 0, sizeof(ctx)); + ctx.cbuf = out; + ctx.bufSz = outLen; + if ((ret == 0) && (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TAG)) { + ret = wc_CBOR_DecodeTag(&ctx, &tag); + } + if (ret == 0) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + if ((ret == 0) && (count != 4u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == 0) { + ret = wc_CBOR_Skip(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_Skip(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_Skip(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + if ((ret == 0) && (count != 1u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == 0) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + if ((ret == 0) && (count != 3u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == 0) { + recipientHdrStart = ctx.idx; + ret = wc_CBOR_DecodeBstr(&ctx, &protectedData, &protectedLen); + } + if ((ret == 0) && ((protectedLen != 3u) || + (protectedData[0] != 0xA1u) || (protectedData[1] != 0x01u) || + (protectedData[2] != 0x22u))) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == 0) { + ret = wc_CBOR_DecodeMapStart(&ctx, &count); + } + if ((ret == 0) && (count != 0u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == 0) { + recipientSuffix = ctx.idx; + conformantLen = recipientHdrStart + 4u + (outLen - recipientSuffix); + if (conformantLen > sizeof(conformant)) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + } + TEST_ASSERT(ret == 0, "a128kw unprotected locate recipient headers"); + if (ret == 0) { + (void)XMEMCPY(conformant, out, recipientHdrStart); + conformant[recipientHdrStart] = 0x40u; + conformant[recipientHdrStart + 1u] = 0xA1u; + conformant[recipientHdrStart + 2u] = 0x01u; + conformant[recipientHdrStart + 3u] = 0x22u; + (void)XMEMCPY(&conformant[recipientHdrStart + 4u], + &out[recipientSuffix], outLen - recipientSuffix); + + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, + conformant, conformantLen, + NULL, 0u, NULL, 0u, + scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == 0, "a128kw unprotected decrypt"); + TEST_ASSERT((plaintextLen == (sizeof(payload) - 1u)) && + (XMEMCMP(plaintext, payload, plaintextLen) == 0), + "a128kw unprotected payload"); + } + wc_CoseKey_Free(&kek); - wc_CoseKey_Free(&wrongKek); (void)wc_FreeRng(&rng); } @@ -9066,11 +11400,20 @@ static void test_cose_encrypt_a128kw_multi_recipient(void) int ret; uint8_t out[512]; size_t outLen; + uint8_t mixed[512]; + size_t mixedLen = 0u; uint8_t scratch[512]; uint8_t plain1[64]; uint8_t plain2[64]; size_t plain1Len = 0; size_t plain2Len = 0; + WOLFCOSE_CBOR_CTX ctx; + uint64_t tag = 0u; + size_t count = 0u; + const uint8_t* recipientProtected = NULL; + size_t recipientProtectedLen = 0u; + size_t protectedHeadOffset = 0u; + size_t protectedDataOffset = 0u; const uint8_t payload[] = "Multi-KW payload"; static const uint8_t kwR0Kid[] = { 'k', 'w', '-', 'r', '0' }; static const uint8_t kwR1Kid[] = { 'k', 'w', '-', 'r', '1' }; @@ -9145,6 +11488,90 @@ static void test_cose_encrypt_a128kw_multi_recipient(void) TEST_ASSERT(memcmp(plain1, plain2, plain1Len) == 0, "kw-multi same plaintext"); + /* A selected AES-KW recipient may coexist with an unselected transport + * algorithm that wolfCOSE does not implement. Change recipient 1 from + * A128KW (-3) to RSAES-OAEP w/ SHA-256 (-41) without touching recipient 0. + */ + if (ret == 0) { + (void)XMEMSET(&ctx, 0, sizeof(ctx)); + ctx.cbuf = out; + ctx.bufSz = outLen; + if (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TAG) { + ret = wc_CBOR_DecodeTag(&ctx, &tag); + } + } + if (ret == 0) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + for (count = 0u; (ret == 0) && (count < 3u); count++) { + ret = wc_CBOR_Skip(&ctx); + } + if (ret == 0) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + if ((ret == 0) && (count == 2u)) { + ret = wc_CBOR_Skip(&ctx); + } + else if (ret == 0) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == 0) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &count); + } + if ((ret == 0) && (count == 3u)) { + protectedHeadOffset = ctx.idx; + ret = wc_CBOR_DecodeBstr(&ctx, &recipientProtected, + &recipientProtectedLen); + } + else if (ret == 0) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if ((ret == 0) && (recipientProtectedLen == 3u) && + (recipientProtected[0] == 0xA1u) && + (recipientProtected[1] == 0x01u) && + (recipientProtected[2] == 0x22u) && + (outLen < sizeof(mixed))) { + protectedDataOffset = (size_t)(recipientProtected - out); + (void)XMEMCPY(mixed, out, outLen); + (void)XMEMMOVE(&mixed[protectedDataOffset + 4u], + &mixed[protectedDataOffset + 3u], + outLen - (protectedDataOffset + 3u)); + mixed[protectedHeadOffset] = 0x44u; + mixed[protectedDataOffset + 2u] = 0x38u; + mixed[protectedDataOffset + 3u] = 0x28u; + mixedLen = outLen + 1u; + } + else if (ret == 0) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + TEST_ASSERT(ret == 0, "kw-multi patch unsupported transport sibling"); + + if (ret == 0) { + plain1Len = 0u; + ret = wc_CoseEncrypt_Decrypt( + &recipients[0], 0, mixed, mixedLen, + NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, + plain1, sizeof(plain1), &plain1Len); + TEST_ASSERT(ret == 0, + "kw-multi ignores unsupported transport sibling"); + TEST_ASSERT((plain1Len == sizeof(payload) - 1u) && + (XMEMCMP(plain1, payload, plain1Len) == 0), + "kw-multi transport sibling plaintext"); + } + + if (ret == 0) { + mixed[protectedDataOffset + 3u] = 0x1Au; /* ECDH-SS + HKDF-256 */ + plain1Len = 0u; + ret = wc_CoseEncrypt_Decrypt( + &recipients[0], 0, mixed, mixedLen, + NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, + plain1, sizeof(plain1), &plain1Len); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "kw-multi rejects direct key-agreement sibling"); + } + /* Crossing the KEK index must fail because the wrapped CEK at * index 0 was wrapped under kek1, not kek2. */ cross.algId = WOLFCOSE_ALG_A128KW; @@ -9567,9 +11994,8 @@ static void test_cose_mac_multi_recipient(void) /** * wc_CoseMac_Verify must enforce the caller's recipient->algId policy against - * the on-wire recipient alg (direct-keyed messages have no recipient alg, so it - * normalizes to WOLFCOSE_ALG_DIRECT). A caller demanding a non-direct mode must - * not silently verify a direct-keyed message. + * the mandatory on-wire recipient alg. A caller demanding a non-direct mode + * must not silently verify a direct-keyed message. */ static void test_cose_mac_verify_algid_policy(void) { @@ -9619,14 +12045,369 @@ static void test_cose_mac_verify_algid_policy(void) &hdr, &decPayload, &decPayloadLen); TEST_ASSERT(ret == 0, "mac verify accepts explicit direct algId"); - /* Unset policy imposes no recipient-alg requirement. */ - recipient.algId = WOLFCOSE_ALG_UNSET; - memset(&hdr, 0, sizeof(hdr)); - ret = wc_CoseMac_Verify(&recipient, 0, out, outLen, - NULL, 0, NULL, 0, - scratch, sizeof(scratch), - &hdr, &decPayload, &decPayloadLen); - TEST_ASSERT(ret == 0, "mac verify accepts unset algId"); + /* Unset policy imposes no recipient-alg requirement. */ + recipient.algId = WOLFCOSE_ALG_UNSET; + memset(&hdr, 0, sizeof(hdr)); + ret = wc_CoseMac_Verify(&recipient, 0, out, outLen, + NULL, 0, NULL, 0, + scratch, sizeof(scratch), + &hdr, &decPayload, &decPayloadLen); + TEST_ASSERT(ret == 0, "mac verify accepts unset algId"); + + wc_CoseKey_Free(&key); +} + +static void test_cose_mac_verify_unprotected_body_alg(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_HDR hdr; + Hmac hmac; + const uint8_t keyData[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 + }; + const uint8_t payloadData[] = "unprotected body alg"; + const uint8_t* payload = NULL; + size_t payloadLen = 0; + uint8_t macStruct[128]; + size_t macStructLen = 0; + uint8_t tag[WC_SHA256_DIGEST_SIZE]; + uint8_t msg[256]; + uint8_t scratch[256]; + int ret; + + TEST_LOG(" [Mac_Verify unprotected body alg]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + TEST_ASSERT(ret == 0, "unprotected body alg key set"); + key.alg = WOLFCOSE_ALG_HMAC_256_256; + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0; + + ret = wolfCose_BuildToBeSignedMaced( + WOLFCOSE_CTX_MAC, sizeof(WOLFCOSE_CTX_MAC), + NULL, 0, NULL, 0, NULL, 0, + payloadData, sizeof(payloadData) - 1, + macStruct, sizeof(macStruct), &macStructLen); + TEST_ASSERT(ret == 0, "unprotected body alg structure"); + ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + TEST_ASSERT(ret == 0, "unprotected body alg hmac init"); + if (ret == 0) { + ret = wc_HmacSetKey(&hmac, WC_SHA256, keyData, sizeof(keyData)); + } + if (ret == 0) { + ret = wc_HmacUpdate(&hmac, macStruct, (word32)macStructLen); + } + if (ret == 0) { + ret = wc_HmacFinal(&hmac, tag); + } + TEST_ASSERT(ret == 0, "unprotected body alg tag"); + (void)wc_HmacFree(&hmac); + + enc.buf = msg; + enc.bufSz = sizeof(msg); + enc.idx = 0; + ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_MAC); + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 5u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_HMAC_256_256); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, payloadData, + sizeof(payloadData) - 1); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, tag, sizeof(tag)); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 3u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_DIRECT); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0); + } + TEST_ASSERT(ret == 0, "unprotected body alg message"); + + ret = wc_CoseMac_Verify(&recipient, 0, msg, enc.idx, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == 0, "pinned unprotected body alg accepted"); + + key.alg = WOLFCOSE_ALG_UNSET; + ret = wc_CoseMac_Verify(&recipient, 0, msg, enc.idx, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "unpinned unprotected body alg rejected"); + + wc_CoseKey_Free(&key); +} + +static void test_cose_mac_rejects_float_payload(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_HDR hdr; + Hmac hmac; + const uint8_t keyData[32] = {0}; + const uint8_t detached[] = "detached float payload"; + const uint8_t* payload = NULL; + size_t payloadLen = 0; + uint8_t protectedBuf[WOLFCOSE_PROTECTED_HDR_MAX]; + size_t protectedLen = 0; + uint8_t macStruct[128]; + size_t macStructLen = 0; + uint8_t tag[WC_SHA256_DIGEST_SIZE]; + uint8_t msg[256]; + uint8_t scratch[256]; + static const uint8_t floatPayloads[3][9] = { + {0xF9u, 0x00u, 0x16u}, + {0xFAu, 0x00u, 0x00u, 0x00u, 0x16u}, + {0xFBu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x16u} + }; + static const size_t floatLens[3] = {3u, 5u, 9u}; + size_t i; + int ret; + + TEST_LOG(" [Mac_Verify rejects float payload]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + TEST_ASSERT(ret == 0, "float payload key set"); + key.alg = WOLFCOSE_ALG_HMAC_256_256; + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0; + + ret = wolfCose_EncodeProtectedHdr(WOLFCOSE_ALG_HMAC_256_256, + protectedBuf, sizeof(protectedBuf), &protectedLen); + TEST_ASSERT(ret == 0, "float payload protected header"); + ret = wolfCose_BuildToBeSignedMaced( + WOLFCOSE_CTX_MAC, sizeof(WOLFCOSE_CTX_MAC), + protectedBuf, protectedLen, NULL, 0, NULL, 0, + detached, sizeof(detached) - 1, + macStruct, sizeof(macStruct), &macStructLen); + TEST_ASSERT(ret == 0, "float payload MAC structure"); + ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + TEST_ASSERT(ret == 0, "float payload hmac init"); + if (ret == 0) { + ret = wc_HmacSetKey(&hmac, WC_SHA256, keyData, sizeof(keyData)); + } + if (ret == 0) { + ret = wc_HmacUpdate(&hmac, macStruct, (word32)macStructLen); + } + if (ret == 0) { + ret = wc_HmacFinal(&hmac, tag); + } + TEST_ASSERT(ret == 0, "float payload tag"); + (void)wc_HmacFree(&hmac); + + for (i = 0; i < 3u; i++) { + enc.buf = msg; + enc.bufSz = sizeof(msg); + enc.idx = 0; + ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_MAC); + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 5u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, protectedBuf, protectedLen); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 0u); + } + if ((ret == 0) && ((enc.idx + floatLens[i]) <= enc.bufSz)) { + (void)memcpy(&msg[enc.idx], floatPayloads[i], floatLens[i]); + enc.idx += floatLens[i]; + } + else if (ret == 0) { + ret = WOLFCOSE_E_BUFFER_TOO_SMALL; + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, tag, sizeof(tag)); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeArrayStart(&enc, 3u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0); + } + if (ret == 0) { + ret = wc_CBOR_EncodeMapStart(&enc, 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); + } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_DIRECT); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, NULL, 0); + } + TEST_ASSERT(ret == 0, "float payload message"); + + ret = wc_CoseMac_Verify(&recipient, 0, msg, enc.idx, + detached, sizeof(detached) - 1, NULL, 0, + scratch, sizeof(scratch), &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_TYPE, + "Mac_Verify rejects float payload"); + } + + wc_CoseKey_Free(&key); +} + +static void test_cose_mac_rejects_float_recipient_ciphertext(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + const uint8_t keyData[32] = {0}; + const uint8_t payloadData[] = "float recipient ciphertext"; + const uint8_t* payload = NULL; + size_t payloadLen = 0; + uint8_t out[256]; + size_t outLen = 0; + uint8_t msg[272]; + size_t msgLen; + uint8_t scratch[256]; + static const uint8_t floatValues[3][9] = { + {0xF9u, 0x00u, 0x16u}, + {0xFAu, 0x00u, 0x00u, 0x00u, 0x16u}, + {0xFBu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x16u} + }; + static const size_t floatLens[3] = {3u, 5u, 9u}; + size_t i; + int ret; + + TEST_LOG(" [Mac_Verify rejects float recipient ciphertext]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + TEST_ASSERT(ret == 0, "float recipient key set"); + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0; + + ret = wc_CoseMac_Create(&recipient, 1, WOLFCOSE_ALG_HMAC_256_256, + payloadData, sizeof(payloadData) - 1, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0, "float recipient base message"); + TEST_ASSERT((outLen > 0u) && (out[outLen - 1u] == 0x40u), + "float recipient ciphertext is empty bstr"); + if ((ret != 0) || (outLen == 0u) || (out[outLen - 1u] != 0x40u)) { + wc_CoseKey_Free(&key); + return; + } + + for (i = 0; i < 3u; i++) { + (void)memcpy(msg, out, outLen - 1u); + (void)memcpy(&msg[outLen - 1u], floatValues[i], floatLens[i]); + msgLen = (outLen - 1u) + floatLens[i]; + + ret = wc_CoseMac_Verify(&recipient, 0, msg, msgLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_TYPE, + "Mac_Verify rejects float recipient ciphertext"); + } + + wc_CoseKey_Free(&key); +} + +static void test_cose_mac_rejects_nonempty_recipient_ciphertext(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + const uint8_t keyData[32] = {0}; + const uint8_t payloadData[] = "nonempty recipient ciphertext"; + const uint8_t* payload = NULL; + size_t payloadLen = 0; + uint8_t out[256]; + size_t outLen = 0; + uint8_t msg[257]; + uint8_t scratch[256]; + int ret; + + TEST_LOG(" [Mac_Verify rejects nonempty recipient ciphertext]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + TEST_ASSERT(ret == 0, "nonempty recipient key set"); + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0; + + ret = wc_CoseMac_Create(&recipient, 1, WOLFCOSE_ALG_HMAC_256_256, + payloadData, sizeof(payloadData) - 1, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == 0, "nonempty recipient base message"); + TEST_ASSERT((outLen > 0u) && (out[outLen - 1u] == 0x40u), + "nonempty recipient ciphertext starts empty"); + if ((ret != 0) || (outLen == 0u) || (out[outLen - 1u] != 0x40u)) { + wc_CoseKey_Free(&key); + return; + } + + (void)memcpy(msg, out, outLen - 1u); + msg[outLen - 1u] = 0x41u; + msg[outLen] = 0xA5u; + + ret = wc_CoseMac_Verify(&recipient, 0, msg, outLen + 1u, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "Mac_Verify rejects nonempty recipient ciphertext"); + + (void)memcpy(msg, out, outLen); + msg[outLen - 1u] = WOLFCOSE_CBOR_NULL; + + ret = wc_CoseMac_Verify(&recipient, 0, msg, outLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "Mac_Verify accepts null recipient ciphertext"); wc_CoseKey_Free(&key); } @@ -9697,13 +12478,22 @@ static void test_cose_mac_multi_recipient_direct_empty_protected(void) TEST_ASSERT(ret == 0 && recipCount == 2, "mac recipients array"); for (i = 0; i < recipCount; i++) { + WOLFCOSE_HDR recipHdr; + WOLFCOSE_HDR_STATE recipState; + ret = wc_CBOR_DecodeArrayStart(&ctx, &arrCount); TEST_ASSERT(ret == 0 && arrCount == 3, "recipient array"); ret = wc_CBOR_DecodeBstr(&ctx, &prot, &protLen); TEST_ASSERT(ret == 0, "recipient protected decode"); TEST_ASSERT(protLen == 0, "direct recipient protected empty"); - ret = wc_CBOR_Skip(&ctx); + (void)XMEMSET(&recipHdr, 0, sizeof(recipHdr)); + ret = wolfCose_DecodeProtectedHdr(prot, protLen, &recipHdr, + &recipState); + TEST_ASSERT(ret == 0, "recipient protected header"); + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &recipHdr, &recipState); TEST_ASSERT(ret == 0, "recipient unprotected"); + TEST_ASSERT(recipHdr.alg == WOLFCOSE_ALG_DIRECT, + "direct recipient unprotected alg"); ret = wc_CBOR_Skip(&ctx); TEST_ASSERT(ret == 0, "recipient cek"); } @@ -12139,6 +14929,12 @@ static void test_cose_error_paths(void) wc_ecc_init(&eccKey); (void)wc_CoseKey_Init(&decodedKey); + + ret = wc_CoseKey_Decode(&decodedKey, eccNoCoords, sizeof(eccNoCoords)); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "metadata-only ecc key missing material rejected"); + + (void)wc_CoseKey_Init(&decodedKey); (void)wc_CoseKey_SetEcc(&decodedKey, WOLFCOSE_CRV_P256, &eccKey); ret = wc_CoseKey_Decode(&decodedKey, eccNoCoords, sizeof(eccNoCoords)); @@ -13092,6 +15888,75 @@ static void test_cose_oversized_int_narrowing(void) "CoseKey_Decode rejects oversized kty"); } +#if defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) && \ + (defined(WOLFCOSE_HAVE_EDDSA) || \ + defined(WOLFCOSE_HAVE_RSA_PRIVATE_KEY)) +static void test_cose_key_word32_overflow_guard(void) +{ + WOLFCOSE_KEY key; +#ifdef WOLFCOSE_HAVE_EDDSA + ed25519_key decodedKey; + size_t hugeLen = (size_t)0xFFFFFFFFUL + 33u; +#endif + int ret; +#ifdef WOLFCOSE_HAVE_EDDSA + uint8_t keyData[15u + ED25519_PUB_KEY_SIZE] = { + 0xA3u, /* map(3) */ + 0x01u, 0x01u, /* kty: OKP */ + 0x20u, 0x06u, /* crv: Ed25519 */ + 0x21u, /* x */ + 0x5Bu, /* bstr with eight-byte length */ + 0x00u, 0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x00u, 0x20u, + /* Valid Ed25519 public key prefix. */ + 0xD7u, 0x5Au, 0x98u, 0x01u, 0x82u, 0xB1u, 0x0Au, 0xB7u, + 0xD5u, 0x4Bu, 0xFEu, 0xD3u, 0xC9u, 0x64u, 0x07u, 0x3Au, + 0x0Eu, 0xE1u, 0x72u, 0xF3u, 0xDAu, 0xA6u, 0x23u, 0x25u, + 0xAFu, 0x02u, 0x1Au, 0x68u, 0xF7u, 0x07u, 0x51u, 0x1Au + }; +#endif +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY + /* {1: RSA, -1: h'01', -2: h'03', -5: h''}. + * Only the first q byte is physically present. Decode uses the synthetic + * claimed size for bounds accounting and must reject q before import. */ + uint8_t rsaKeyData[20] = { + 0xA4u, + 0x01u, 0x03u, + 0x20u, 0x41u, 0x01u, + 0x21u, 0x41u, 0x03u, + 0x24u, 0x5Bu, + 0x00u, 0x00u, 0x00u, 0x01u, 0x00u, 0x00u, 0x00u, 0x01u, + 0x00u + }; +#endif + + TEST_LOG(" [COSE_Key word32 length guard]\n"); + +#ifdef WOLFCOSE_HAVE_EDDSA + ret = wc_ed25519_init(&decodedKey); + TEST_ASSERT(ret == 0, "key length guard destination init"); + if (ret == 0) { + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetEd25519(&key, &decodedKey); + + /* The declared bstr is larger than word32 but truncates to the valid + * public-key prefix size. The guard must reject it before import; no + * byte beyond the supplied prefix is accessed. */ + ret = wc_CoseKey_Decode(&key, keyData, 15u + hugeLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "key decode rejects oversized bstr component"); + (void)wc_ed25519_free(&decodedKey); + } +#endif +#ifdef WOLFCOSE_HAVE_RSA_PRIVATE_KEY + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, rsaKeyData, 19u + + ((size_t)0xFFFFFFFFUL + 2u)); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "key decode rejects oversized RSA CRT component"); +#endif +} +#endif /* 64-bit size_t and an import with word32 component lengths */ + #if defined(WOLFCOSE_SIGN) && defined(WOLFCOSE_HAVE_ES256) static void test_cose_sign_dup_signer_unprot_hdr(void) { @@ -13113,6 +15978,14 @@ static void test_cose_sign_dup_signer_unprot_hdr(void) 0xA2u, 0x04u, 0x41u, 0x01u, 0x04u, 0x41u, 0x02u, 0x42u, 0x00u, 0x00u }; + uint8_t unselectedMsg[] = { + 0x84u, 0x40u, 0xA0u, 0x41u, 0x78u, 0x82u, + 0x83u, 0x43u, 0xA1u, 0x01u, 0x26u, 0xA0u, + 0x42u, 0x00u, 0x00u, + 0x83u, 0x43u, 0xA1u, 0x01u, 0x26u, + 0xA2u, 0x04u, 0x41u, 0x01u, 0x04u, 0x41u, 0x02u, + 0x42u, 0x00u, 0x00u + }; TEST_LOG(" [Sign multi dup signer unprotected label]\n"); @@ -13134,6 +16007,12 @@ static void test_cose_sign_dup_signer_unprot_hdr(void) &hdr, &payload, &payloadLen); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "dup signer unprotected label rejected"); + + ret = wc_CoseSign_Verify(&key, 0, unselectedMsg, + sizeof(unselectedMsg), NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_SIG_FAIL, + "dup label in unselected signer ignored"); } if (eccInited != 0) { (void)wc_ecc_free(&eccKey); } @@ -13163,6 +16042,42 @@ static void test_cose_mac_dup_recipient_unprot_hdr(void) 0xA2u, 0x04u, 0x41u, 0x01u, 0x04u, 0x41u, 0x02u, 0xF6u }; + uint8_t unselectedMsg[] = { + 0x85u, 0x43u, 0xA1u, 0x01u, 0x05u, 0xA0u, 0x41u, 0x78u, + 0x58u, 0x20u, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x82u, + 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x83u, 0x40u, + 0xA3u, 0x01u, 0x25u, + 0x04u, 0x41u, 0x01u, 0x04u, 0x41u, 0x02u, + 0x40u + }; + /* Valid four-element recipients appear on both sides of the selected + * recipient. Their nested direct recipients must also be decoded. */ + uint8_t nestedSiblings[] = { + 0x85u, 0x43u, 0xA1u, 0x01u, 0x05u, 0xA0u, 0x41u, 0x78u, + 0x58u, 0x20u, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x83u, + 0x84u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x81u, 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x84u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x81u, 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u + }; + uint8_t tstrSibling[] = { + 0x85u, 0x43u, 0xA1u, 0x01u, 0x05u, 0xA0u, 0x41u, 0x78u, + 0x58u, 0x20u, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x82u, + 0x83u, 0x40u, 0xA2u, 0x61u, 0x78u, 0x00u, + 0x01u, 0x25u, 0x40u, + 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u + }; TEST_LOG(" [Mac multi dup recipient unprotected label]\n"); @@ -13179,6 +16094,24 @@ static void test_cose_mac_dup_recipient_unprot_hdr(void) &hdr, &payload, &payloadLen); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "dup recipient unprotected label rejected (mac)"); + + ret = wc_CoseMac_Verify(&recipient, 0, unselectedMsg, + sizeof(unselectedMsg), NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_MAC_FAIL, + "dup label in unselected recipient ignored (mac)"); + + ret = wc_CoseMac_Verify(&recipient, 1, nestedSiblings, + sizeof(nestedSiblings), NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_MAC_FAIL, + "nested unselected recipients accepted (mac)"); + + ret = wc_CoseMac_Verify(&recipient, 1, tstrSibling, + sizeof(tstrSibling), NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_MAC_FAIL, + "tstr field in unselected recipient accepted (mac)"); } /** @@ -13209,6 +16142,13 @@ static void test_cose_mac_verify_rejects_keydist_recipient(void) 0xA0u, 0xF6u }; + uint8_t missingAlg[] = { + 0x85u, 0x43u, 0xA1u, 0x01u, 0x05u, 0xA0u, 0x41u, 0x78u, + 0x58u, 0x20u, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x81u, 0x83u, 0x40u, 0xA0u, 0x40u + }; TEST_LOG(" [Mac verify rejects key-distribution recipient]\n"); @@ -13226,6 +16166,14 @@ static void test_cose_mac_verify_rejects_keydist_recipient(void) TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED, "mac verify rejects key-distribution recipient alg"); + recipient.algId = WOLFCOSE_ALG_DIRECT; + ret = wc_CoseMac_Verify(&recipient, 0, missingAlg, sizeof(missingAlg), + NULL, 0, NULL, 0, + scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "mac verify rejects missing recipient alg"); + wc_CoseKey_Free(&key); } #endif /* WOLFCOSE_MAC && WOLFCOSE_HAVE_HMAC256 */ @@ -13254,6 +16202,45 @@ static void test_cose_encrypt_dup_recipient_unprot_hdr(void) 0xA2u, 0x04u, 0x41u, 0x01u, 0x04u, 0x41u, 0x02u, 0xF6u }; + uint8_t unselectedMsg[] = { + 0x84u, 0x43u, 0xA1u, 0x01u, 0x01u, + 0xA1u, 0x05u, 0x4Cu, + 0,0,0,0,0,0,0,0,0,0,0,0, + 0x50u, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x82u, + 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x83u, 0x40u, + 0xA3u, 0x01u, 0x25u, + 0x04u, 0x41u, 0x01u, 0x04u, 0x41u, 0x02u, + 0x40u + }; + /* Valid four-element recipients appear on both sides of the selected + * recipient. Their nested direct recipients must also be decoded. */ + uint8_t nestedSiblings[] = { + 0x84u, 0x43u, 0xA1u, 0x01u, 0x01u, + 0xA1u, 0x05u, 0x4Cu, + 0,0,0,0,0,0,0,0,0,0,0,0, + 0x50u, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x83u, + 0x84u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x81u, 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x84u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u, + 0x81u, 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u + }; + uint8_t tstrSibling[] = { + 0x84u, 0x43u, 0xA1u, 0x01u, 0x01u, + 0xA1u, 0x05u, 0x4Cu, + 0,0,0,0,0,0,0,0,0,0,0,0, + 0x50u, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x82u, + 0x83u, 0x40u, 0xA2u, 0x61u, 0x78u, 0x00u, + 0x01u, 0x25u, 0x40u, + 0x83u, 0x40u, 0xA1u, 0x01u, 0x25u, 0x40u + }; TEST_LOG(" [Encrypt multi dup recipient unprotected label]\n"); @@ -13270,6 +16257,27 @@ static void test_cose_encrypt_dup_recipient_unprot_hdr(void) &hdr, plaintext, sizeof(plaintext), &plaintextLen); TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, "dup recipient unprotected label rejected (encrypt)"); + + ret = wc_CoseEncrypt_Decrypt(&recipient, 0, unselectedMsg, + sizeof(unselectedMsg), NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_DECRYPT_FAIL, + "dup label in unselected recipient ignored (encrypt)"); + + ret = wc_CoseEncrypt_Decrypt(&recipient, 1, nestedSiblings, + sizeof(nestedSiblings), NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_DECRYPT_FAIL, + "nested unselected recipients accepted (encrypt)"); + + ret = wc_CoseEncrypt_Decrypt(&recipient, 1, tstrSibling, + sizeof(tstrSibling), NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_DECRYPT_FAIL, + "tstr field in unselected recipient accepted (encrypt)"); } static void test_cose_encrypt_direct_empty_protected(void) @@ -13292,6 +16300,10 @@ static void test_cose_encrypt_direct_empty_protected(void) size_t i; size_t n = 0; uint64_t tag = 0; + const uint8_t* recipProt = NULL; + size_t recipProtLen = 0; + WOLFCOSE_HDR recipHdr; + WOLFCOSE_HDR_STATE recipState; TEST_LOG(" [Encrypt direct alg empty protected]\n"); @@ -13335,6 +16347,19 @@ static void test_cose_encrypt_direct_empty_protected(void) } TEST_ASSERT((ret == 0) && (out[ctx.idx] == 0x40u), "direct recipient protected is empty bstr"); + if (ret == 0) { + ret = wc_CBOR_DecodeBstr(&ctx, &recipProt, &recipProtLen); + } + (void)XMEMSET(&recipHdr, 0, sizeof(recipHdr)); + if (ret == 0) { + ret = wolfCose_DecodeProtectedHdr(recipProt, recipProtLen, + &recipHdr, &recipState); + } + if (ret == 0) { + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &recipHdr, &recipState); + } + TEST_ASSERT((ret == 0) && (recipHdr.alg == WOLFCOSE_ALG_DIRECT), + "direct recipient alg is unprotected"); } if (ret == 0) { @@ -13377,6 +16402,19 @@ static void test_cose_encrypt_recipient_alg_checks(void) 0x50u, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0x81u, 0x83u, 0x40u, 0xA0u, 0xF6u }; +#if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && \ + defined(HAVE_HKDF) + /* ECDH-ES declared only in the recipient unprotected bucket, without an + * ephemeral key. It must not fall through to direct symmetric decrypt. */ + uint8_t unprotectedEcdh[] = { + 0x84u, 0x43u, 0xA1u, 0x01u, 0x01u, + 0xA1u, 0x05u, 0x4Cu, 0,0,0,0,0,0,0,0,0,0,0,0, + 0x50u, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0x81u, 0x83u, 0x40u, + 0xA1u, 0x01u, 0x38u, 0x18u, /* {alg: ECDH-ES + HKDF-256} */ + 0x40u + }; +#endif TEST_LOG(" [Encrypt recipient alg checks]\n"); @@ -13407,6 +16445,200 @@ static void test_cose_encrypt_recipient_alg_checks(void) plaintext, sizeof(plaintext), &plaintextLen); TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, "recipient algId policy enforced"); + + recipient.algId = WOLFCOSE_ALG_DIRECT; + ret = wc_CoseEncrypt_Decrypt(&recipient, 0, direct, sizeof(direct), + NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "missing recipient alg rejected"); + +#if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_ECC) && \ + defined(HAVE_HKDF) + recipient.algId = WOLFCOSE_ALG_UNSET; + ret = wc_CoseEncrypt_Decrypt(&recipient, 0, unprotectedEcdh, + sizeof(unprotectedEcdh), NULL, 0, NULL, 0, + scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "unparsed unprotected ECDH recipient rejected"); +#endif +} + +static void test_cose_encrypt_direct_recipient_value(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + uint8_t cek[16] = {0}; + uint8_t iv[12] = {0}; + uint8_t scratch[512]; + uint8_t out[256]; + uint8_t msg[258]; + uint8_t plaintext[64]; + size_t outLen = 0u; + size_t plaintextLen = 0u; + const uint8_t payload[] = "direct recipient value"; + int ret; + + TEST_LOG(" [Encrypt direct recipient transported-key value]\n"); + + (void)wc_CoseKey_Init(&key); + (void)wc_CoseKey_SetSymmetric(&key, cek, sizeof(cek)); + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0u; + + 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, NULL); + TEST_ASSERT(ret == 0, "direct value base encrypt"); + TEST_ASSERT((outLen > 0u) && (out[outLen - 1u] == 0x40u), + "direct value base empty bstr"); + if ((ret != 0) || (outLen == 0u) || (out[outLen - 1u] != 0x40u)) { + wc_CoseKey_Free(&key); + return; + } + + (void)XMEMCPY(msg, out, outLen); + msg[outLen - 1u] = WOLFCOSE_CBOR_NULL; + plaintextLen = 0u; + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, msg, outLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT((ret == WOLFCOSE_SUCCESS) && + (plaintextLen == (sizeof(payload) - 1u)) && + (XMEMCMP(plaintext, payload, plaintextLen) == 0), + "direct value accepts null"); + + (void)XMEMCPY(msg, out, outLen); + msg[outLen - 1u] = 0x00u; + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, msg, outLen, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_TYPE, + "direct value rejects integer"); + + (void)XMEMCPY(msg, out, outLen); + msg[outLen - 1u] = 0x41u; + msg[outLen] = 0xA5u; + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, msg, outLen + 1u, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_MALFORMED, + "direct value rejects nonempty bstr"); + + wc_CoseKey_Free(&key); +} + +static void test_cose_encrypt_rejects_float_ciphertext(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + WOLFCOSE_CBOR_CTX ctx; + const uint8_t cek[16] = {0}; + const uint8_t iv[12] = {0}; + const uint8_t payload[] = "float ciphertext"; + const uint8_t* protectedData = NULL; + const uint8_t* detachedCt = NULL; + size_t protectedLen = 0u; + size_t detachedCtLen = 0u; + uint8_t scratch[512]; + uint8_t out[256]; + uint8_t msg[272]; + uint8_t plaintext[64]; + size_t outLen = 0u; + size_t plaintextLen = 0u; + size_t arrayCount = 0u; + size_t ciphertextStart = 0u; + size_t ciphertextEnd = 0u; + size_t suffixLen = 0u; + uint64_t tag = 0u; + static const uint8_t floatValues[3][9] = { + {0xF9u, 0x00u, 0x16u}, + {0xFAu, 0x00u, 0x00u, 0x00u, 0x16u}, + {0xFBu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x16u} + }; + static const size_t floatLens[3] = {3u, 5u, 9u}; + size_t i; + int ret; + + TEST_LOG(" [Encrypt rejects float ciphertext]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, cek, sizeof(cek)); + TEST_ASSERT(ret == 0, "float ciphertext key set"); + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0u; + + 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, NULL); + TEST_ASSERT(ret == 0, "float ciphertext base encrypt"); + + ctx.cbuf = out; + ctx.bufSz = outLen; + ctx.idx = 0u; + if ((ret == 0) && (wc_CBOR_PeekType(&ctx) == WOLFCOSE_CBOR_TAG)) { + ret = wc_CBOR_DecodeTag(&ctx, &tag); + } + if (ret == 0) { + ret = wc_CBOR_DecodeArrayStart(&ctx, &arrayCount); + } + if ((ret == 0) && (arrayCount != 4u)) { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + if (ret == 0) { + ret = wc_CBOR_DecodeBstr(&ctx, &protectedData, &protectedLen); + } + if (ret == 0) { + ret = wc_CBOR_Skip(&ctx); + } + if (ret == 0) { + ciphertextStart = ctx.idx; + ret = wc_CBOR_DecodeBstr(&ctx, &detachedCt, &detachedCtLen); + ciphertextEnd = ctx.idx; + } + if ((ret == 0) && (ciphertextEnd <= outLen)) { + suffixLen = outLen - ciphertextEnd; + } + else { + ret = WOLFCOSE_E_CBOR_MALFORMED; + } + TEST_ASSERT(ret == 0, "float ciphertext locate body"); + if (ret != 0) { + wc_CoseKey_Free(&key); + return; + } + + for (i = 0u; i < 3u; i++) { + size_t msgLen; + + (void)XMEMCPY(msg, out, ciphertextStart); + (void)XMEMCPY(&msg[ciphertextStart], floatValues[i], floatLens[i]); + (void)XMEMCPY(&msg[ciphertextStart + floatLens[i]], + &out[ciphertextEnd], suffixLen); + msgLen = ciphertextStart + floatLens[i] + suffixLen; + plaintextLen = 0u; + + ret = wc_CoseEncrypt_Decrypt(&recipient, 0u, msg, msgLen, + detachedCt, detachedCtLen, NULL, 0u, + scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_CBOR_TYPE, + "Encrypt rejects float ciphertext"); + } + + wc_CoseKey_Free(&key); } static void test_cose_encrypt_multi_per_recipient(void) @@ -13423,6 +16655,7 @@ static void test_cose_encrypt_multi_per_recipient(void) size_t plaintextLen; int ret; size_t r; + size_t algOffsets[2] = {0u, 0u}; const uint8_t payload[] = "multi recipient direct"; TEST_LOG(" [Encrypt multi per-recipient roundtrip]\n"); @@ -13442,6 +16675,12 @@ static void test_cose_encrypt_multi_per_recipient(void) scratch, sizeof(scratch), out, sizeof(out), &outLen, NULL); TEST_ASSERT(ret == 0, "multi encrypt create"); + for (r = 0u; (ret == 0) && (r < 2u); r++) { + ret = find_recipient_direct_alg(out, outLen, 3u, r, + &algOffsets[r]); + TEST_ASSERT(ret == 0, "locate direct encrypt recipient alg"); + } + /* Every encoded recipient must decrypt to the original plaintext. */ for (r = 0; (ret == 0) && (r < 2u); r++) { memset(&hdr, 0, sizeof(hdr)); @@ -13455,6 +16694,24 @@ static void test_cose_encrypt_multi_per_recipient(void) (memcmp(plaintext, payload, plaintextLen) == 0), "multi recipient payload match"); } + + if (ret == 0) { + out[algOffsets[1]] = 0x22u; /* A128KW */ + ret = wc_CoseEncrypt_Decrypt(&recipients[0], 0, out, outLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), &hdr, + plaintext, sizeof(plaintext), &plaintextLen); + 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, + plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "encrypt rejects earlier mixed recipient mode"); + out[algOffsets[0]] = 0x25u; /* direct */ + } } #endif /* WOLFCOSE_ENCRYPT && WOLFCOSE_HAVE_AESGCM */ @@ -13463,23 +16720,75 @@ static void test_cose_protected_hdr_content_type(void) int ret; WOLFCOSE_HDR hdr; WOLFCOSE_HDR_STATE hdrState; + WOLFCOSE_CBOR_CTX ctx; uint8_t ctHdr[] = {0xA1u, 0x03u, 0x18u, 0x32u}; /* {3: 50} */ uint8_t ctTstr[] = {0xA1u, 0x03u, 0x69u, 'a','p','p','l','i','c','a','t','e'}; + uint8_t ctNegative[] = {0xA1u, 0x03u, 0x20u}; /* {3: -1} */ TEST_LOG(" [Protected Header: content-type]\n"); XMEMSET(&hdr, 0, sizeof(hdr)); ret = wolfCose_DecodeProtectedHdr(ctHdr, sizeof(ctHdr), &hdr, &hdrState); TEST_ASSERT(ret == WOLFCOSE_SUCCESS, - "DecodeProtectedHdr content-type uint"); - TEST_ASSERT(hdr.contentType == 50, - "DecodeProtectedHdr stores content-type"); + "DecodeProtectedHdr content-type uint"); + TEST_ASSERT(hdr.contentType == 50, + "DecodeProtectedHdr stores content-type"); + TEST_ASSERT((hdr.flags & + WOLFCOSE_HDR_FLAG_CONTENT_TYPE_UNPROTECTED) == 0u, + "protected content-type marked authenticated"); + + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(ctTstr, sizeof(ctTstr), &hdr, + &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "DecodeProtectedHdr tolerates tstr content-type"); + + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(ctNegative, sizeof(ctNegative), &hdr, + &hdrState); + TEST_ASSERT(ret != WOLFCOSE_SUCCESS, + "DecodeProtectedHdr rejects negative content-type"); + + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(NULL, 0, &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "initialize empty protected header state"); + ctx.cbuf = ctHdr; + ctx.bufSz = sizeof(ctHdr); + ctx.idx = 0; + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "DecodeUnprotectedHdr content-type uint"); + TEST_ASSERT(hdr.contentType == 50, + "DecodeUnprotectedHdr stores content-type"); + TEST_ASSERT((hdr.flags & + WOLFCOSE_HDR_FLAG_CONTENT_TYPE_UNPROTECTED) != 0u, + "unprotected content-type provenance retained"); + + XMEMSET(&hdr, 0, sizeof(hdr)); + ret = wolfCose_DecodeProtectedHdr(NULL, 0, &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "reinitialize empty protected header state"); + ctx.cbuf = ctTstr; + ctx.bufSz = sizeof(ctTstr); + ctx.idx = 0; + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + TEST_ASSERT(ret == WOLFCOSE_SUCCESS, + "DecodeUnprotectedHdr tolerates tstr content-type"); + TEST_ASSERT((hdr.flags & + WOLFCOSE_HDR_FLAG_CONTENT_TYPE_UNPROTECTED) != 0u, + "unprotected tstr content-type provenance retained"); XMEMSET(&hdr, 0, sizeof(hdr)); - ret = wolfCose_DecodeProtectedHdr(ctTstr, sizeof(ctTstr), &hdr, - &hdrState); + ret = wolfCose_DecodeProtectedHdr(NULL, 0, &hdr, &hdrState); TEST_ASSERT(ret == WOLFCOSE_SUCCESS, - "DecodeProtectedHdr tolerates tstr content-type"); + "reinitialize protected state for invalid content-type"); + ctx.cbuf = ctNegative; + ctx.bufSz = sizeof(ctNegative); + ctx.idx = 0; + ret = wolfCose_DecodeUnprotectedHdr(&ctx, &hdr, &hdrState); + TEST_ASSERT(ret != WOLFCOSE_SUCCESS, + "DecodeUnprotectedHdr rejects negative content-type"); } static void test_cose_protected_hdr_tstr_label(void) @@ -13686,9 +16995,27 @@ static void test_cose_sign1_alg_curve_mismatch(void) TEST_ASSERT(ret == 0, "ecc keygen P-256"); (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P384, &eccKey); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "reject P-256 key declared as P-384"); + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &eccKey); TEST_ASSERT(ret == 0, "set ECC key P-256"); + /* Do not trust a declaration changed after key attachment. */ + key.crv = WOLFCOSE_CRV_P384; + ret = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES384, + NULL, 0, + payload, sizeof(payload) - 1, + NULL, 0, + NULL, 0, + scratch, sizeof(scratch), + out, sizeof(out), &outLen, + &rng); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "Sign1 rejects mismatched attached ECC curve"); + + key.crv = WOLFCOSE_CRV_P256; /* Ask for ES384 with a P-256 key -> bad alg */ ret = wc_CoseSign1_Sign(&key, WOLFCOSE_ALG_ES384, NULL, 0, @@ -14171,6 +17498,16 @@ static void test_cose_build_sig_structure_context(void) TEST_ASSERT(sizeof(WOLFCOSE_CTX_MAC) == 3u && memcmp(WOLFCOSE_CTX_MAC, "MAC", 3) == 0, "MAC context constant bytes"); + + ret = wolfCose_BuildToBeSignedMaced( + WOLFCOSE_CTX_SIGNATURE1, sizeof(WOLFCOSE_CTX_SIGNATURE1), + protectedHdr, sizeof(protectedHdr), + NULL, 0u, + NULL, 1u, + payload, sizeof(payload), + scratch, sizeof(scratch), &structLen); + TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, + "auth structure null aad with length rejected"); } /* ----- Coverage boost: exercise multi-signer / multi-recipient paths @@ -14573,7 +17910,12 @@ static void test_cose_key_kid_alg_roundtrip(void) (void)wc_CoseKey_Init(&dstKey); (void)wc_CoseKey_SetEcc(&dstKey, WOLFCOSE_CRV_P256, &dstEcc); ret = wc_CoseKey_Decode(&dstKey, encoded, encodedLen); +#ifdef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED, + "key kidAlg private backend rejected"); +#else TEST_ASSERT(ret == 0, "key kidAlg decode"); +#endif TEST_ASSERT(dstKey.alg == WOLFCOSE_ALG_ES256, "key kidAlg alg preserved"); TEST_ASSERT(dstKey.kidLen == sizeof(kid) - 1, @@ -15130,6 +18472,86 @@ static void test_cose_mac0_verify_short_key_rejected(void) } #endif +#if defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) && \ + defined(WOLFCOSE_HAVE_HMAC256) && \ + defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_MAC0_VERIFY) && \ + defined(WOLFCOSE_MAC_CREATE) && defined(WOLFCOSE_MAC_VERIFY) +static void test_cose_hmac_oversized_key_rejected(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_RECIPIENT recipient; + WOLFCOSE_HDR hdr; + uint8_t keyData[32] = {0}; + const uint8_t payloadData[] = "oversized HMAC key"; + const uint8_t* payload = NULL; + size_t payloadLen = 0; + uint8_t mac0[256]; + size_t mac0Len = 0; + uint8_t mac[256]; + size_t macLen = 0; + uint8_t out[256]; + size_t outLen = 0; + uint8_t scratch[256]; + int ret; + + TEST_LOG(" [HMAC oversized key rejected]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + TEST_ASSERT(ret == 0, "oversized HMAC base key set"); + recipient.algId = WOLFCOSE_ALG_DIRECT; + recipient.key = &key; + recipient.kid = NULL; + recipient.kidLen = 0; + + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, + NULL, 0, payloadData, sizeof(payloadData) - 1, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + mac0, sizeof(mac0), &mac0Len); + TEST_ASSERT(ret == 0, "oversized HMAC Mac0 baseline"); + ret = wc_CoseMac_Create(&recipient, 1, WOLFCOSE_ALG_HMAC_256_256, + payloadData, sizeof(payloadData) - 1, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + mac, sizeof(mac), &macLen); + TEST_ASSERT(ret == 0, "oversized HMAC Mac baseline"); + if ((ret != 0) || (mac0Len == 0u) || (macLen == 0u)) { + wc_CoseKey_Free(&key); + return; + } + + key.key.symm.keyLen = (size_t)UINT32_MAX + 1u; + + ret = wc_CoseMac0_Create(&key, WOLFCOSE_ALG_HMAC_256_256, + NULL, 0, payloadData, sizeof(payloadData) - 1, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "Mac0_Create rejects oversized HMAC key"); + + ret = wc_CoseMac0_Verify(&key, mac0, mac0Len, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "Mac0_Verify rejects oversized HMAC key"); + + outLen = 0; + ret = wc_CoseMac_Create(&recipient, 1, WOLFCOSE_ALG_HMAC_256_256, + payloadData, sizeof(payloadData) - 1, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + out, sizeof(out), &outLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "Mac_Create rejects oversized HMAC key"); + + ret = wc_CoseMac_Verify(&recipient, 0, mac, macLen, + NULL, 0, NULL, 0, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_KEY_TYPE, + "Mac_Verify rejects oversized HMAC key"); + + wc_CoseKey_Free(&key); +} +#endif + #if defined(WOLFCOSE_HAVE_HMAC256) && defined(WOLFCOSE_MAC0_CREATE) static void test_cose_mac0_create_key_alg_mismatch(void) { @@ -15163,6 +18585,88 @@ static void test_cose_mac0_create_key_alg_mismatch(void) } #endif +#if defined(WOLFCOSE_HAVE_HMAC256) && defined(WOLFCOSE_MAC0_VERIFY) +static void test_cose_mac0_verify_unprotected_alg(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_HDR hdr; + Hmac hmac; + const uint8_t keyData[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 + }; + const uint8_t payloadData[] = "unprotected Mac0 alg"; + const uint8_t* payload = NULL; + size_t payloadLen = 0u; + uint8_t macStruct[128]; + size_t macStructLen = 0u; + uint8_t tag[WC_SHA256_DIGEST_SIZE]; + uint8_t msg[256]; + uint8_t scratch[256]; + int ret; + + TEST_LOG(" [Mac0_Verify unprotected alg]\n"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + TEST_ASSERT(ret == 0, "unprotected Mac0 alg key set"); + key.alg = WOLFCOSE_ALG_HMAC_256_256; + + ret = wolfCose_BuildToBeSignedMaced( + WOLFCOSE_CTX_MAC0, sizeof(WOLFCOSE_CTX_MAC0), + NULL, 0u, NULL, 0u, NULL, 0u, + payloadData, sizeof(payloadData) - 1u, + macStruct, sizeof(macStruct), &macStructLen); + TEST_ASSERT(ret == 0, "unprotected Mac0 alg structure"); + ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + TEST_ASSERT(ret == 0, "unprotected Mac0 alg hmac init"); + if (ret == 0) { + ret = wc_HmacSetKey(&hmac, WC_SHA256, keyData, sizeof(keyData)); + } + if (ret == 0) { + ret = wc_HmacUpdate(&hmac, macStruct, (word32)macStructLen); + } + if (ret == 0) { ret = wc_HmacFinal(&hmac, tag); } + TEST_ASSERT(ret == 0, "unprotected Mac0 alg tag"); + (void)wc_HmacFree(&hmac); + + enc.buf = msg; + enc.bufSz = sizeof(msg); + enc.idx = 0u; + if (ret == 0) { ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_MAC0); } + if (ret == 0) { ret = wc_CBOR_EncodeArrayStart(&enc, 4u); } + if (ret == 0) { ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); } + if (ret == 0) { ret = wc_CBOR_EncodeMapStart(&enc, 1u); } + if (ret == 0) { ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); } + if (ret == 0) { + ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_HMAC_256_256); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, payloadData, + sizeof(payloadData) - 1u); + } + if (ret == 0) { ret = wc_CBOR_EncodeBstr(&enc, tag, sizeof(tag)); } + TEST_ASSERT(ret == 0, "unprotected Mac0 alg message"); + + ret = wc_CoseMac0_Verify(&key, msg, enc.idx, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == 0, "pinned unprotected Mac0 alg accepted"); + + key.alg = WOLFCOSE_ALG_UNSET; + ret = wc_CoseMac0_Verify(&key, msg, enc.idx, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "unpinned unprotected Mac0 alg rejected"); + + wc_CoseKey_Free(&key); +} +#endif + #if defined(WOLFCOSE_HAVE_AESGCM) && defined(WOLFCOSE_ENCRYPT0_ENCRYPT) static void test_cose_encrypt0_key_alg_mismatch(void) { @@ -15197,6 +18701,102 @@ static void test_cose_encrypt0_key_alg_mismatch(void) } #endif +#if defined(WOLFCOSE_HAVE_AESGCM) && defined(WOLFCOSE_ENCRYPT0_DECRYPT) +static void test_cose_encrypt0_decrypt_unprotected_alg(void) +{ + WOLFCOSE_KEY key; + WOLFCOSE_HDR hdr; + WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_CBOR_CTX aadEnc; + Aes aes; + const uint8_t keyData[16] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + }; + const uint8_t iv[WOLFCOSE_AES_GCM_NONCE_SZ] = { + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, + 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B + }; + const uint8_t payloadData[] = "unprotected Encrypt0 alg"; + uint8_t ciphertext[sizeof(payloadData) - 1u + WOLFCOSE_AES_GCM_TAG_SZ]; + uint8_t aad[32]; + uint8_t msg[256]; + uint8_t scratch[256]; + uint8_t plaintext[64]; + size_t plaintextLen = 0u; + int aesInited = 0; + int ret; + + TEST_LOG(" [Encrypt0_Decrypt unprotected alg]\n"); + + aadEnc.buf = aad; + aadEnc.bufSz = sizeof(aad); + aadEnc.idx = 0u; + ret = wc_CBOR_EncodeArrayStart(&aadEnc, 3u); + if (ret == 0) { + ret = wc_CBOR_EncodeTstr(&aadEnc, WOLFCOSE_CTX_ENCRYPT0, + sizeof(WOLFCOSE_CTX_ENCRYPT0)); + } + if (ret == 0) { ret = wc_CBOR_EncodeBstr(&aadEnc, NULL, 0u); } + if (ret == 0) { ret = wc_CBOR_EncodeBstr(&aadEnc, NULL, 0u); } + TEST_ASSERT(ret == 0, "unprotected Encrypt0 alg Enc_structure"); + + if (ret == 0) { + ret = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (ret == 0) { + aesInited = 1; + ret = wc_AesGcmSetKey(&aes, keyData, sizeof(keyData)); + } + } + if (ret == 0) { + ret = wc_AesGcmEncrypt(&aes, ciphertext, payloadData, + (word32)(sizeof(payloadData) - 1u), iv, (word32)sizeof(iv), + &ciphertext[sizeof(payloadData) - 1u], + (word32)WOLFCOSE_AES_GCM_TAG_SZ, aad, (word32)aadEnc.idx); + } + TEST_ASSERT(ret == 0, "unprotected Encrypt0 alg encrypt"); + if (aesInited != 0) { (void)wc_AesFree(&aes); } + + enc.buf = msg; + enc.bufSz = sizeof(msg); + enc.idx = 0u; + if (ret == 0) { ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_ENCRYPT0); } + if (ret == 0) { ret = wc_CBOR_EncodeArrayStart(&enc, 3u); } + if (ret == 0) { ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); } + if (ret == 0) { ret = wc_CBOR_EncodeMapStart(&enc, 2u); } + if (ret == 0) { ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); } + if (ret == 0) { ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_A128GCM); } + if (ret == 0) { ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_IV); } + if (ret == 0) { ret = wc_CBOR_EncodeBstr(&enc, iv, sizeof(iv)); } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, ciphertext, sizeof(ciphertext)); + } + TEST_ASSERT(ret == 0, "unprotected Encrypt0 alg message"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetSymmetric(&key, keyData, sizeof(keyData)); + TEST_ASSERT(ret == 0, "unprotected Encrypt0 alg key set"); + key.alg = WOLFCOSE_ALG_A128GCM; + + ret = wc_CoseEncrypt0_Decrypt(&key, msg, enc.idx, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == 0, "pinned unprotected Encrypt0 alg accepted"); + TEST_ASSERT((plaintextLen == (sizeof(payloadData) - 1u)) && + (XMEMCMP(plaintext, payloadData, plaintextLen) == 0), + "unprotected Encrypt0 alg payload"); + + key.alg = WOLFCOSE_ALG_UNSET; + ret = wc_CoseEncrypt0_Decrypt(&key, msg, enc.idx, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, plaintext, sizeof(plaintext), &plaintextLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "unpinned unprotected Encrypt0 alg rejected"); + + wc_CoseKey_Free(&key); +} +#endif + #if defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_SIGN1_SIGN) static void test_cose_sign1_key_alg_mismatch(void) { @@ -15297,6 +18897,87 @@ static void test_cose_sign1_verify_key_alg_mismatch(void) (void)wc_FreeRng(&rng); } +static void test_cose_sign1_verify_unprotected_alg(void) +{ + WOLFCOSE_KEY key; + ecc_key eccKey; + WC_RNG rng; + WOLFCOSE_CBOR_CTX enc; + WOLFCOSE_HDR hdr; + const uint8_t payloadData[] = "unprotected Sign1 alg"; + const uint8_t* payload = NULL; + size_t payloadLen = 0u; + uint8_t sigStruct[128]; + size_t sigStructLen = 0u; + uint8_t hash[WC_SHA256_DIGEST_SIZE]; + uint8_t signature[64]; + size_t signatureLen = sizeof(signature); + uint8_t msg[256]; + uint8_t scratch[256]; + int ret; + + TEST_LOG(" [Sign1_Verify unprotected alg]\n"); + + ret = wc_InitRng(&rng); + TEST_ASSERT(ret == 0, "unprotected Sign1 alg rng"); + ret = wc_ecc_init(&eccKey); + TEST_ASSERT(ret == 0, "unprotected Sign1 alg ecc init"); + ret = wc_ecc_make_key(&rng, 32, &eccKey); + TEST_ASSERT(ret == 0, "unprotected Sign1 alg keygen"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_SetEcc(&key, WOLFCOSE_CRV_P256, &eccKey); + TEST_ASSERT(ret == 0, "unprotected Sign1 alg key set"); + key.alg = WOLFCOSE_ALG_ES256; + + ret = wolfCose_BuildToBeSignedMaced( + WOLFCOSE_CTX_SIGNATURE1, sizeof(WOLFCOSE_CTX_SIGNATURE1), + NULL, 0u, NULL, 0u, NULL, 0u, + payloadData, sizeof(payloadData) - 1u, + sigStruct, sizeof(sigStruct), &sigStructLen); + TEST_ASSERT(ret == 0, "unprotected Sign1 alg structure"); + ret = wc_Hash(WC_HASH_TYPE_SHA256, sigStruct, (word32)sigStructLen, + hash, sizeof(hash)); + TEST_ASSERT(ret == 0, "unprotected Sign1 alg hash"); + ret = wolfCose_EccSignRaw(hash, sizeof(hash), signature, &signatureLen, + 32u, WC_HASH_TYPE_SHA256, &rng, &eccKey); + TEST_ASSERT(ret == 0, "unprotected Sign1 alg signature"); + + enc.buf = msg; + enc.bufSz = sizeof(msg); + enc.idx = 0u; + ret = wc_CBOR_EncodeTag(&enc, WOLFCOSE_TAG_SIGN1); + if (ret == 0) { ret = wc_CBOR_EncodeArrayStart(&enc, 4u); } + if (ret == 0) { ret = wc_CBOR_EncodeBstr(&enc, NULL, 0u); } + if (ret == 0) { ret = wc_CBOR_EncodeMapStart(&enc, 1u); } + if (ret == 0) { ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_HDR_ALG); } + if (ret == 0) { ret = wc_CBOR_EncodeInt(&enc, WOLFCOSE_ALG_ES256); } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, payloadData, + sizeof(payloadData) - 1u); + } + if (ret == 0) { + ret = wc_CBOR_EncodeBstr(&enc, signature, signatureLen); + } + TEST_ASSERT(ret == 0, "unprotected Sign1 alg message"); + + ret = wc_CoseSign1_Verify(&key, msg, enc.idx, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == 0, "pinned unprotected Sign1 alg accepted"); + + key.alg = WOLFCOSE_ALG_UNSET; + ret = wc_CoseSign1_Verify(&key, msg, enc.idx, + NULL, 0u, NULL, 0u, scratch, sizeof(scratch), + &hdr, &payload, &payloadLen); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_ALG, + "unpinned unprotected Sign1 alg rejected"); + + wc_CoseKey_Free(&key); + (void)wc_ecc_free(&eccKey); + (void)wc_FreeRng(&rng); +} + static void test_cose_sign1_both_payloads(void) { WOLFCOSE_KEY key; @@ -15449,6 +19130,8 @@ static void test_cose_key_decode_symmetric_missing_k(void) int ret; /* {1: 4} -> kty=Symmetric but no k label (-1). */ uint8_t noK[] = {0xA1u, 0x01u, 0x04u}; + /* {1: 4, -1: h''} -> kty=Symmetric with an empty k value. */ + uint8_t emptyK[] = {0xA2u, 0x01u, 0x04u, 0x20u, 0x40u}; TEST_LOG(" [CoseKey_Decode symmetric without k]\n"); @@ -15456,6 +19139,11 @@ static void test_cose_key_decode_symmetric_missing_k(void) ret = wc_CoseKey_Decode(&key, noK, sizeof(noK)); TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, "CoseKey_Decode rejects symmetric w/o k"); + + (void)wc_CoseKey_Init(&key); + ret = wc_CoseKey_Decode(&key, emptyK, sizeof(emptyK)); + TEST_ASSERT(ret == WOLFCOSE_E_COSE_BAD_HDR, + "CoseKey_Decode rejects empty symmetric k"); } #if defined(WOLFCOSE_HAVE_ES256) @@ -15726,19 +19414,22 @@ static void test_internal_helpers(void) ecc_key dummyKey; /* EccSignRaw with NULL parameters */ - ret = wolfCose_EccSignRaw(NULL, 32, sigBuf, &sigLen, 32, NULL, NULL); + ret = wolfCose_EccSignRaw(NULL, 32, sigBuf, &sigLen, 32, + WC_HASH_TYPE_SHA256, NULL, NULL); TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, "EccSignRaw NULL hash"); - ret = wolfCose_EccSignRaw(hash, 32, NULL, &sigLen, 32, NULL, NULL); + ret = wolfCose_EccSignRaw(hash, 32, NULL, &sigLen, 32, + WC_HASH_TYPE_SHA256, NULL, NULL); TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, "EccSignRaw NULL sigBuf"); - ret = wolfCose_EccSignRaw(hash, 32, sigBuf, NULL, 32, NULL, NULL); + ret = wolfCose_EccSignRaw(hash, 32, sigBuf, NULL, 32, + WC_HASH_TYPE_SHA256, NULL, NULL); TEST_ASSERT(ret == WOLFCOSE_E_INVALID_ARG, "EccSignRaw NULL sigLen"); /* EccSignRaw with buffer too small */ sigLen = 10; /* Too small for 64-byte sig */ - ret = wolfCose_EccSignRaw(hash, 32, sigBuf, &sigLen, 32, &dummyRng, - &dummyKey); + ret = wolfCose_EccSignRaw(hash, 32, sigBuf, &sigLen, 32, + WC_HASH_TYPE_SHA256, &dummyRng, &dummyKey); TEST_ASSERT(ret == WOLFCOSE_E_BUFFER_TOO_SMALL, "EccSignRaw buf small"); /* EccVerifyRaw with NULL parameters */ @@ -16163,6 +19854,48 @@ static void test_force_failure_crypto(void) (void)wc_ed25519_free(&edKey); wc_CoseKey_Free(&key); } + + /* Verify private-only import is rolled back if public derivation fails. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY decodedKey; + ed25519_key decodedEdKey; + WOLFCOSE_CBOR_CTX enc; + uint8_t keyBuf[128]; + uint8_t seed[ED25519_KEY_SIZE] = {1u}; + size_t i; + int cleared = 1; + + enc.buf = keyBuf; + enc.bufSz = sizeof(keyBuf); + enc.idx = 0; + (void)wc_CBOR_EncodeMapStart(&enc, 3); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); + (void)wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_OKP); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_CRV); + (void)wc_CBOR_EncodeUint(&enc, WOLFCOSE_CRV_ED25519); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_D); + (void)wc_CBOR_EncodeBstr(&enc, seed, sizeof(seed)); + + (void)wc_ed25519_init(&decodedEdKey); + (void)wc_CoseKey_Init(&decodedKey); + (void)wc_CoseKey_SetEd25519(&decodedKey, &decodedEdKey); + wolfForceFailure_Set(WOLF_FAIL_ED25519_MAKE_PUB); + ret = wc_CoseKey_Decode(&decodedKey, keyBuf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_CRYPTO, + "Ed25519 make public forced failure"); + for (i = 0; i < ED25519_KEY_SIZE; i++) { + if (decodedEdKey.k[i] != 0u) { + cleared = 0; + } + } + TEST_ASSERT(cleared == 1, "Ed25519 failed derivation clears seed"); + TEST_ASSERT(decodedEdKey.privKeySet == 0, + "Ed25519 failed derivation clears private flag"); + TEST_ASSERT(decodedKey.hasPrivate == 0, + "Ed25519 failed derivation leaves wrapper public"); + (void)wc_ed25519_free(&decodedEdKey); + } #endif /* WOLFCOSE_HAVE_EDDSA */ #if defined(WOLFCOSE_HAVE_RSAPSS) && defined(WOLFSSL_KEY_GEN) @@ -16436,6 +20169,48 @@ static void test_force_failure_crypto(void) (void)wc_ed448_free(&edKey); wc_CoseKey_Free(&key); } + + /* Verify private-only import is rolled back if public derivation fails. */ + /* empty-brace-scan: allow - test-local temporary scope */ + { + WOLFCOSE_KEY decodedKey; + ed448_key decodedEdKey; + WOLFCOSE_CBOR_CTX enc; + uint8_t keyBuf[128]; + uint8_t seed[ED448_KEY_SIZE] = {1u}; + size_t i; + int cleared = 1; + + enc.buf = keyBuf; + enc.bufSz = sizeof(keyBuf); + enc.idx = 0; + (void)wc_CBOR_EncodeMapStart(&enc, 3); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_KTY); + (void)wc_CBOR_EncodeUint(&enc, WOLFCOSE_KTY_OKP); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_CRV); + (void)wc_CBOR_EncodeUint(&enc, WOLFCOSE_CRV_ED448); + (void)wc_CBOR_EncodeInt(&enc, WOLFCOSE_KEY_LABEL_D); + (void)wc_CBOR_EncodeBstr(&enc, seed, sizeof(seed)); + + (void)wc_ed448_init(&decodedEdKey); + (void)wc_CoseKey_Init(&decodedKey); + (void)wc_CoseKey_SetEd448(&decodedKey, &decodedEdKey); + wolfForceFailure_Set(WOLF_FAIL_ED448_MAKE_PUB); + ret = wc_CoseKey_Decode(&decodedKey, keyBuf, enc.idx); + TEST_ASSERT(ret == WOLFCOSE_E_CRYPTO, + "Ed448 make public forced failure"); + for (i = 0; i < ED448_KEY_SIZE; i++) { + if (decodedEdKey.k[i] != 0u) { + cleared = 0; + } + } + TEST_ASSERT(cleared == 1, "Ed448 failed derivation clears seed"); + TEST_ASSERT(decodedEdKey.privKeySet == 0, + "Ed448 failed derivation clears private flag"); + TEST_ASSERT(decodedKey.hasPrivate == 0, + "Ed448 failed derivation leaves wrapper public"); + (void)wc_ed448_free(&decodedEdKey); + } #endif /* WOLFCOSE_HAVE_ED448 */ #if defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(HAVE_HKDF) @@ -16513,7 +20288,15 @@ static void test_force_failure_crypto(void) &decodedEccKey); wolfForceFailure_Set(WOLF_FAIL_ECC_IMPORT_X963); ret = wc_CoseKey_Decode(&decodedKey, keyBuf, keyLen); +#ifdef WOLFCOSE_ECC_PRIVATE_IMPORT_ALWAYS_UNSUPPORTED + TEST_ASSERT(ret == WOLFCOSE_E_UNSUPPORTED, + "ECC import backend rejected before failure hook"); + TEST_ASSERT(wolfForceFailure_Get() == WOLF_FAIL_ECC_IMPORT_X963, + "ECC backend rejection preserves failure hook"); + wolfForceFailure_Clear(); +#else TEST_ASSERT(ret == WOLFCOSE_E_CRYPTO, "ECC import forced failure"); +#endif (void)wc_ecc_free(&decodedEccKey); } } @@ -16603,6 +20386,11 @@ static void test_force_failure_crypto(void) uint8_t keyBuf[8192]; uint8_t impSeed[WOLFCOSE_MLDSA_SEED_SZ]; size_t keyLen; + wc_Shake emptyShake = {0}; +#if !defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) + size_t i; +#endif + int privateCleared = 1; WOLFCOSE_KEY decodedKey; wc_MlDsaKey decodedDlKey; @@ -16625,14 +20413,67 @@ static void test_force_failure_crypto(void) ret = wc_CoseKey_Encode(&key, keyBuf, sizeof(keyBuf), &keyLen); if (ret == 0) { /* Test ML-DSA import failure - must pre-allocate internal key */ - wc_MlDsaKey_Init(&decodedDlKey, NULL, INVALID_DEVID); + wc_MlDsaKey_Init(&decodedDlKey, (void*)&decodedKey, + INVALID_DEVID); +#ifdef WOLF_CRYPTO_CB + decodedDlKey.devCtx = (void*)&dlKey; +#endif +#ifdef WOLF_PRIVATE_KEY_ID + decodedDlKey.id[0] = 0x5au; + decodedDlKey.idLen = 1; +#endif wc_MlDsaKey_SetParams(&decodedDlKey, WC_ML_DSA_44); - (void)wc_CoseKey_Init(&decodedKey); - (void)wc_CoseKey_SetMlDsa(&decodedKey, WOLFCOSE_ALG_ML_DSA_44, - &decodedDlKey); - wolfForceFailure_Set(WOLF_FAIL_MLDSA_IMPORT_PRIV); - ret = wc_CoseKey_Decode(&decodedKey, keyBuf, keyLen); - TEST_ASSERT(ret == WOLFCOSE_E_CRYPTO, "ML-DSA import priv forced failure"); + ret = wc_MlDsaKey_MakeKeyFromSeed(&decodedDlKey, impSeed); + TEST_ASSERT(ret == 0, "ML-DSA rollback key setup"); + if (ret == 0) { + (void)wc_CoseKey_Init(&decodedKey); + (void)wc_CoseKey_SetMlDsa(&decodedKey, + WOLFCOSE_ALG_ML_DSA_44, &decodedDlKey); + wolfForceFailure_Set(WOLF_FAIL_MLDSA_IMPORT_PRIV); + ret = wc_CoseKey_Decode(&decodedKey, keyBuf, keyLen); + TEST_ASSERT(ret == WOLFCOSE_E_CRYPTO, + "ML-DSA import priv forced failure"); + TEST_ASSERT(XMEMCMP(&decodedDlKey.shake, &emptyShake, + sizeof(emptyShake)) == 0, + "ML-DSA failed decode clears SHAKE state"); +#if defined(WOLFSSL_MLDSA_DYNAMIC_KEYS) + privateCleared = (decodedDlKey.k == NULL) ? 1 : 0; +#else + for (i = 0u; i < sizeof(decodedDlKey.k); i++) { + if (decodedDlKey.k[i] != 0u) { + privateCleared = 0; + } + } +#endif + TEST_ASSERT(privateCleared == 1, + "ML-DSA failed decode clears private key"); + TEST_ASSERT(decodedDlKey.prvKeySet == 0u, + "ML-DSA failed decode clears private flag"); + TEST_ASSERT((decodedKey.hasPrivate == 0u) && + (decodedKey.mldsaSeed == NULL) && + (decodedKey.mldsaSeedLen == 0u), + "ML-DSA failed decode clears wrapper state"); + TEST_ASSERT((decodedKey.attachedType == + WOLFCOSE_ATT_MLDSA) && + (decodedKey.key.mldsa == &decodedDlKey), + "ML-DSA failed decode preserves attachment"); + TEST_ASSERT(decodedDlKey.heap == (void*)&decodedKey, + "ML-DSA failed decode preserves heap hint"); +#ifdef WOLF_CRYPTO_CB + TEST_ASSERT(decodedDlKey.devCtx == (void*)&dlKey, + "ML-DSA failed decode preserves device context"); +#endif +#ifdef WOLF_PRIVATE_KEY_ID + TEST_ASSERT((decodedDlKey.idLen == 1) && + (decodedDlKey.id[0] == 0x5au), + "ML-DSA failed decode preserves key identifier"); +#endif + ret = wc_CoseKey_Decode(&decodedKey, keyBuf, keyLen); + TEST_ASSERT((ret == WOLFCOSE_SUCCESS) && + (decodedKey.hasPrivate == 1u) && + (decodedKey.key.mldsa == &decodedDlKey), + "ML-DSA failed decode key is reusable"); + } (void)wc_MlDsaKey_Free(&decodedDlKey); } } @@ -19775,6 +23616,9 @@ int test_cose(void) #if defined(WOLFCOSE_HAVE_ES384) || defined(WOLFCOSE_HAVE_ES512) test_cose_key_encode_ecc_raw_curves(); #endif +#ifdef WOLFCOSE_HAVE_ES384 + test_cose_key_encode_ecc_curve_mismatch(); +#endif #endif test_cose_key_encode_size_exact(); #if defined(WOLFCOSE_HAVE_EDDSA) || defined(WOLFCOSE_HAVE_ED448) || \ @@ -19791,6 +23635,12 @@ int test_cose(void) test_cose_key_ed25519(); #endif test_cose_key_symmetric(); +#if defined(WOLFCOSE_KEY_DECODE) + test_cose_key_operations(); + test_cose_key_akp_alg_metadata(); + test_cose_key_akp_public_metadata(); + test_cose_key_akp_seed_metadata(); +#endif #if defined(WOLFCOSE_HAVE_RSAPSS) && defined(WOLFSSL_KEY_GEN) test_cose_key_rsa(); test_cose_key_rsa_scratch_scrubbed(); @@ -19870,6 +23720,7 @@ int test_cose(void) #ifdef WOLFCOSE_HAVE_AESCCM test_cose_encrypt0_aes_ccm(); test_cose_aes_ccm_all_params(); + test_cose_aes_ccm_l2_payload_limit(); #endif /* RSA-PSS signature tests */ @@ -19877,6 +23728,11 @@ int test_cose(void) test_cose_sign1_pss("PS256", WOLFCOSE_ALG_PS256); test_cose_sign1_pss("PS384", WOLFCOSE_ALG_PS384); test_cose_sign1_pss("PS512", WOLFCOSE_ALG_PS512); + test_cose_rsa_pss_minimum_key_size(); +#endif +#if defined(WOLFCOSE_HAVE_RSAPSS) && defined(WOLFCOSE_SIGN1_SIGN) && \ + (defined(WOLF_CRYPTO_CB) || defined(WOLFSSL_MICROCHIP_TA100)) + test_cose_rsa_pss_opaque_key_size(); #endif /* ML-DSA signature tests */ @@ -19889,6 +23745,7 @@ int test_cose(void) /* Mac0 basic tests */ #if defined(WOLFCOSE_HAVE_HMAC256) + test_cose_mac_wrong_tag_lengths(); test_cose_mac0_hmac256(); test_cose_mac0_short_hmac_key(); #ifdef WOLFCOSE_MAC @@ -19940,6 +23797,7 @@ int test_cose(void) test_cose_sign_ml_dsa_level_mismatch(); #endif test_cose_sign_verify_key_alg_mismatch(); + test_cose_sign_verify_unprotected_alg(); test_cose_encrypt0_decrypt_key_alg_mismatch(); test_cose_mac0_verify_key_alg_mismatch(); test_cose_sign_with_aad(); @@ -19956,13 +23814,16 @@ int test_cose(void) test_cose_encrypt_with_aad(); test_cose_encrypt_a256gcm(); test_cose_encrypt_direct_key_alg_pin_roundtrip(); + test_cose_encrypt_unprotected_body_alg(); test_cose_encrypt_unset_alg_rejected(); test_cose_encrypt_direct_alg_id_key_alg_roundtrip(); test_cose_encrypt_direct_multi_key_alg_mismatch(); #if defined(WOLFCOSE_ECDH_ES_DIRECT) && defined(WOLFCOSE_HAVE_ES256) && defined(HAVE_HKDF) test_cose_encrypt_ecdh_es_kid_and_alg_pin(); test_cose_encrypt_ecdh_es_ephemeral_crv_narrowing(); + test_cose_encrypt_ecdh_es_malformed_ephemeral_point(); test_cose_encrypt_ecdh_es_hkdf_256(); + test_cose_encrypt_ecdh_es_long_recipient_protected(); test_cose_encrypt_ecdh_es_wrong_key(); test_cose_encrypt_ecdh_es_p384(); test_cose_encrypt_ecdh_es_wrong_key_type(); @@ -19971,6 +23832,7 @@ int test_cose(void) #endif #endif test_cose_encrypt_a128kw(); + test_cose_encrypt_a128kw_unprotected_alg(); test_cose_encrypt_a128kw_multi_recipient(); test_cose_encrypt_a192kw(); test_cose_encrypt_a256kw(); @@ -19986,6 +23848,10 @@ int test_cose(void) #if defined(WOLFCOSE_MAC) && defined(WOLFCOSE_HAVE_HMAC256) test_cose_mac_multi_recipient(); test_cose_mac_verify_algid_policy(); + test_cose_mac_verify_unprotected_body_alg(); + test_cose_mac_rejects_float_payload(); + test_cose_mac_rejects_float_recipient_ciphertext(); + test_cose_mac_rejects_nonempty_recipient_ciphertext(); test_cose_mac_multi_recipient_direct_empty_protected(); test_cose_mac_multi_recipient_key_alg_mismatch(); test_cose_mac_with_aad(); @@ -20079,6 +23945,11 @@ int test_cose(void) test_cose_protected_hdr_trailing(); test_cose_protected_hdr_kid(); test_cose_oversized_int_narrowing(); +#if defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) && \ + (defined(WOLFCOSE_HAVE_EDDSA) || \ + defined(WOLFCOSE_HAVE_RSA_PRIVATE_KEY)) + test_cose_key_word32_overflow_guard(); +#endif #if defined(WOLFCOSE_SIGN) && defined(WOLFCOSE_HAVE_ES256) test_cose_sign_dup_signer_unprot_hdr(); #endif @@ -20090,6 +23961,8 @@ int test_cose(void) test_cose_encrypt_dup_recipient_unprot_hdr(); test_cose_encrypt_direct_empty_protected(); test_cose_encrypt_recipient_alg_checks(); + test_cose_encrypt_direct_recipient_value(); + test_cose_encrypt_rejects_float_ciphertext(); test_cose_encrypt_multi_per_recipient(); #endif test_cose_protected_hdr_content_type(); @@ -20181,12 +24054,25 @@ int test_cose(void) defined(WOLFCOSE_MAC0_VERIFY) test_cose_mac0_verify_short_key_rejected(); #endif +#if defined(WOLFCOSE_HAVE_HMAC256) && defined(WOLFCOSE_MAC0_VERIFY) + test_cose_mac0_verify_unprotected_alg(); +#endif +#if defined(SIZE_MAX) && (SIZE_MAX > 0xFFFFFFFFUL) && \ + defined(WOLFCOSE_HAVE_HMAC256) && \ + defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_MAC0_VERIFY) && \ + defined(WOLFCOSE_MAC_CREATE) && defined(WOLFCOSE_MAC_VERIFY) + test_cose_hmac_oversized_key_rejected(); +#endif #if defined(WOLFCOSE_HAVE_AESGCM) && defined(WOLFCOSE_ENCRYPT0_ENCRYPT) test_cose_encrypt0_key_alg_mismatch(); #endif +#if defined(WOLFCOSE_HAVE_AESGCM) && defined(WOLFCOSE_ENCRYPT0_DECRYPT) + test_cose_encrypt0_decrypt_unprotected_alg(); +#endif #if defined(WOLFCOSE_HAVE_ES256) && defined(WOLFCOSE_SIGN1_SIGN) test_cose_sign1_key_alg_mismatch(); test_cose_sign1_verify_key_alg_mismatch(); + test_cose_sign1_verify_unprotected_alg(); test_cose_sign1_both_payloads(); #endif #if defined(WOLFCOSE_MAC0_CREATE) && defined(WOLFCOSE_HAVE_HMAC256)