From cdd1221c87ec8c413baf280649465f6d2ed7b48a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 12:55:17 -0700 Subject: [PATCH 01/27] Import SPDM core from wolfTPM 0f639565 --- .github/workflows/build-test.yml | 27 +- .github/workflows/codeql.yml | 2 +- .github/workflows/compiler-warnings.yml | 4 +- .github/workflows/memory-check.yml | 2 +- .github/workflows/multi-compiler.yml | 2 +- .github/workflows/spdm-emu-pqc-test.yml | 364 -- .github/workflows/spdm-emu-test.yml | 146 - .github/workflows/static-analysis.yml | 2 +- .github/workflows/wolfssl-versions.yml | 4 +- .github/workflows/wolftpm-downstream.yml | 185 + Makefile.am | 36 +- config.h.in | 18 +- configure.ac | 181 +- examples/spdm_demo.c | 605 --- examples/spdm_test.sh | 277 -- scripts/wolftpm-overlay.sh | 40 + src/spdm_chunk.c | 209 - src/spdm_context.c | 643 ++-- src/spdm_crypto.c | 341 +- src/spdm_internal.h | 570 +-- src/spdm_kdf.c | 299 +- src/spdm_msg.c | 1998 ++-------- src/spdm_psk.c | 430 +++ src/spdm_responder.c | 1289 +++++++ src/spdm_secured.c | 639 ++-- src/spdm_session.c | 761 +--- src/spdm_tcg.c | 726 ++++ src/spdm_transcript.c | 45 +- src/vendor/spdm_nations.c | 182 + src/vendor/spdm_nuvoton.c | 120 + test/test_spdm.c | 223 -- test/unit_test.c | 4425 +++++++++++----------- wolfspdm/spdm.h | 613 +-- wolfspdm/spdm_error.h | 22 +- wolfspdm/spdm_nations.h | 91 + wolfspdm/spdm_nuvoton.h | 76 + wolfspdm/spdm_psk.h | 91 + wolfspdm/spdm_responder.h | 112 + wolfspdm/spdm_tcg.h | 178 + wolfspdm/spdm_types.h | 419 +- 40 files changed, 7275 insertions(+), 9122 deletions(-) delete mode 100644 .github/workflows/spdm-emu-pqc-test.yml delete mode 100644 .github/workflows/spdm-emu-test.yml create mode 100644 .github/workflows/wolftpm-downstream.yml delete mode 100644 examples/spdm_demo.c delete mode 100755 examples/spdm_test.sh create mode 100755 scripts/wolftpm-overlay.sh delete mode 100644 src/spdm_chunk.c create mode 100644 src/spdm_psk.c create mode 100644 src/spdm_responder.c create mode 100644 src/spdm_tcg.c create mode 100644 src/vendor/spdm_nations.c create mode 100644 src/vendor/spdm_nuvoton.c delete mode 100644 test/test_spdm.c create mode 100644 wolfspdm/spdm_nations.h create mode 100644 wolfspdm/spdm_nuvoton.h create mode 100644 wolfspdm/spdm_psk.h create mode 100644 wolfspdm/spdm_responder.h create mode 100644 wolfspdm/spdm_tcg.h diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index cad8543..9d17499 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -52,7 +52,7 @@ jobs: - name: Configure run: | - ./configure --with-wolfssl=$HOME/wolfssl-install \ + ./configure --with-wolfssl=$HOME/wolfssl-install --enable-nuvoton --enable-nations --enable-responder \ ${{ matrix.debug == 'yes' && '--enable-debug' || '' }} \ ${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }} @@ -73,21 +73,25 @@ jobs: test/*.log config.log - chunk-config: - name: chunking / ${{ matrix.variant }} + feature-config: + name: features / ${{ matrix.variant }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - - variant: default + - variant: core configure: '' - - variant: disabled - configure: '--disable-chunking' - - variant: no-secured - configure: 'CPPFLAGS=-DWOLFSPDM_CHUNK_NO_SECURED' - - variant: small-mtu - configure: 'CPPFLAGS=-DWOLFSPDM_CHUNK_BUF_SIZE=1024' + - variant: tcg + configure: '--enable-tcg' + - variant: tcg-psk + configure: '--enable-tcg --enable-psk' + - variant: nuvoton + configure: '--enable-nuvoton' + - variant: nations + configure: '--enable-nations' + - variant: tcg-responder + configure: '--enable-tcg --enable-responder' steps: - uses: actions/checkout@v4 @@ -123,9 +127,6 @@ jobs: - name: Configure run: ./configure --with-wolfssl=$HOME/wolfssl-install ${{ matrix.configure }} - - name: Verify chunking status - run: grep -E 'Chunking:' config.log || true - - name: Build run: make -j$(nproc) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a1d29b8..aad2708 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -56,7 +56,7 @@ jobs: - name: Build run: | ./autogen.sh - ./configure --with-wolfssl=$HOME/wolfssl-install + ./configure --with-wolfssl=$HOME/wolfssl-install --enable-nuvoton --enable-nations --enable-responder make -j$(nproc) - name: Perform CodeQL Analysis diff --git a/.github/workflows/compiler-warnings.yml b/.github/workflows/compiler-warnings.yml index 431b3e8..145be26 100644 --- a/.github/workflows/compiler-warnings.yml +++ b/.github/workflows/compiler-warnings.yml @@ -44,7 +44,7 @@ jobs: - name: Build with strict warnings run: | ./autogen.sh - ./configure --with-wolfssl=$HOME/wolfssl-install + ./configure --with-wolfssl=$HOME/wolfssl-install --enable-nuvoton --enable-nations --enable-responder make -j$(nproc) CFLAGS="-Wall -Wextra -Wpedantic -Werror -Wconversion -Wshadow" clang: @@ -82,7 +82,7 @@ jobs: - name: Build with clang run: | ./autogen.sh - CC=clang ./configure --with-wolfssl=$HOME/wolfssl-install + CC=clang ./configure --with-wolfssl=$HOME/wolfssl-install --enable-nuvoton --enable-nations --enable-responder make -j$(nproc) CFLAGS="-Wall -Wextra -Werror" - name: Run unit tests diff --git a/.github/workflows/memory-check.yml b/.github/workflows/memory-check.yml index 8e7ac47..71e6307 100644 --- a/.github/workflows/memory-check.yml +++ b/.github/workflows/memory-check.yml @@ -48,7 +48,7 @@ jobs: - name: Build with debug run: | ./autogen.sh - ./configure --with-wolfssl=$HOME/wolfssl-install --enable-debug \ + ./configure --with-wolfssl=$HOME/wolfssl-install --enable-nuvoton --enable-nations --enable-responder --enable-debug \ ${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }} make -j$(nproc) make -j$(nproc) check TESTS= diff --git a/.github/workflows/multi-compiler.yml b/.github/workflows/multi-compiler.yml index 502b2ca..5755d0e 100644 --- a/.github/workflows/multi-compiler.yml +++ b/.github/workflows/multi-compiler.yml @@ -59,7 +59,7 @@ jobs: - name: Build wolfSPDM with ${{ matrix.cc }} run: | ./autogen.sh - CC=${{ matrix.cc }} ./configure --with-wolfssl=$HOME/wolfssl-install + CC=${{ matrix.cc }} ./configure --with-wolfssl=$HOME/wolfssl-install --enable-nuvoton --enable-nations --enable-responder make -j$(nproc) CFLAGS="-Wall -Wextra -Werror" - name: Run unit tests diff --git a/.github/workflows/spdm-emu-pqc-test.yml b/.github/workflows/spdm-emu-pqc-test.yml deleted file mode 100644 index dbb10b6..0000000 --- a/.github/workflows/spdm-emu-pqc-test.yml +++ /dev/null @@ -1,364 +0,0 @@ -name: SPDM Emulator PQC Test - -# Post-quantum interop (ML-DSA signing + ML-KEM key exchange, and a full-PQ -# combination), mirroring the classical SPDM Emulator Integration Test matrix -# (ubuntu 22.04/24.04 x64 + 24.04 aarch64, each with static and dynamic memory). -# The wc_MlDsaKey context API wolfSPDM verifies with lands post-v5.9.1-stable, -# so this job pins wolfSSL master. libspdm's ML-DSA/ML-KEM are only in its -# OpenSSL backend (the mbedtls backend stubs them out), so spdm-emu is built -# with CRYPTO=openssl. - -on: - push: - branches: [ 'main', 'release/**' ] - pull_request: - branches: [ '*' ] - repository_dispatch: - types: [nightly-trigger] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - pqc-emu-test: - name: ${{ matrix.os }} (${{ matrix.arch }}) / dynamic-mem=${{ matrix.dynamic-mem }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-22.04 - arch: x64 - dynamic-mem: 'no' - - os: ubuntu-22.04 - arch: x64 - dynamic-mem: 'yes' - - os: ubuntu-24.04 - arch: x64 - dynamic-mem: 'no' - - os: ubuntu-24.04 - arch: x64 - dynamic-mem: 'yes' - - os: ubuntu-24.04-arm - arch: aarch64 - dynamic-mem: 'no' - - os: ubuntu-24.04-arm - arch: aarch64 - dynamic-mem: 'yes' - runs-on: ${{ matrix.os }} - timeout-minutes: 40 - steps: - - uses: actions/checkout@v4 - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y autoconf automake libtool cmake - - - name: Compute cache period - id: cache-period - run: echo "biweekly=$(( $(date +%s) / 1296000 ))" >> $GITHUB_OUTPUT - - # --- wolfSSL master with ML-DSA (rebuilt to track upstream drift) --- - - name: Build wolfSSL master (--enable-mldsa --enable-mlkem) - run: | - cd ~ - git clone --depth 1 --branch master https://github.com/wolfSSL/wolfssl.git - cd wolfssl - ./autogen.sh - ./configure --enable-ecc --enable-sha384 --enable-aesgcm \ - --enable-hkdf --enable-sp --enable-mldsa --enable-mlkem \ - --prefix=$HOME/wolfssl-install - make -j"$(nproc)" - make install - grep LIBWOLFSSL_VERSION_STRING $HOME/wolfssl-install/include/wolfssl/version.h - - # --- ML-KEM without ML-DSA: a real config (ML-KEM/Kyber is commonly - # enabled for TLS hybrid KEX while ML-DSA is not). Exercises the - # ML-KEM-only WOLFSPDM_CTX_STATIC_SIZE budget and unit tests, which - # the combined build below does not. Cleaned up before the full build. --- - - name: Build + test wolfSPDM ML-KEM-only (--disable-mldsa --enable-mlkem) - run: | - ./autogen.sh - ./configure --with-wolfssl=$HOME/wolfssl-install \ - --disable-mldsa --enable-mlkem \ - ${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }} - make -j"$(nproc)" - make check - make distclean - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib - - # --- wolfSPDM with ML-DSA + ML-KEM asserted on, static or dynamic memory --- - - name: Build and install wolfSPDM (--enable-mldsa --enable-mlkem) - run: | - ./autogen.sh - ./configure --with-wolfssl=$HOME/wolfssl-install \ - --prefix=$HOME/wolfspdm-install --enable-mldsa --enable-mlkem \ - ${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }} - make -j"$(nproc)" - make install - - - name: Run unit tests (includes ML-DSA verify + ML-KEM decap) - run: make check - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib - - # --- spdm-emu with OpenSSL backend (ML-DSA), cached per OS/arch --- - # Cache the whole build tree, not just build/bin: the OpenSSL-backed - # responder depends on the bundled OpenSSL libraries/providers built - # elsewhere under build/, so a build/bin-only cache restores a binary - # that fails mid-handshake. The full tree makes a restored build behave - # identically to a fresh one. - - name: Cache spdm-emu (openssl) - id: cache-spdm-emu - uses: actions/cache@v4 - with: - path: ~/spdm-emu/build - key: spdm-emu-pqc-openssl-v4-${{ matrix.os }}-${{ matrix.arch }}-${{ steps.cache-period.outputs.biweekly }} - - - name: Build spdm-emu (CRYPTO=openssl) - if: steps.cache-spdm-emu.outputs.cache-hit != 'true' - run: | - cd ~ - git clone --depth 1 --recurse-submodules https://github.com/DMTF/spdm-emu.git - cd spdm-emu - mkdir build && cd build - cmake -DARCH=${{ matrix.arch }} -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=openssl .. - make copy_sample_key - make -j"$(nproc)" spdm_responder_emu - - # --- ML-DSA interop: responder offers only ML-DSA, requester verifies. - # ML-DSA-44/65 fit spdm-emu's DataTransferSize (4608 B) in one message; - # ML-DSA-87 (sig 4627 B) exceeds it and the responder chunks the - # response, exercising CHUNK_GET reassembly on both the cleartext - # (KEY_EXCHANGE/CHALLENGE) and secured (MEASUREMENTS) paths. - - name: ML-DSA 44/65/87 session + measurements + challenge - run: | - export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib - export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin - DEMO=./examples/spdm_demo - FAILURES="" - - # Wait until the responder is accepting connections on port 2323. - wait_for_port() { - local i - for i in $(seq 1 50); do - if ss -ltn 2>/dev/null | grep -q ':2323 '; then return 0; fi - sleep 0.2 - done - return 1 - } - - # $1 ML_DSA_xx (responder), $2 cert dir, $3 demo label, then demo args. - # The OpenSSL-backed ML-DSA responder is slower to become ready than - # the mbedtls/ECDSA one, and it stops after a failed connection, so - # retry the whole case (restarting the responder) a few times. - # Records (does NOT exit on) failures so every ML-DSA size and flow is - # exercised and reported, not masked by the first failure. - run_case() { - local alg="$1" certdir="$2" label="$3"; shift 3 - export SPDM_EMU_CERT_DIR="$certdir" - echo "::group::$alg $label" - local attempt rc=1 emu - for attempt in 1 2 3; do - ( cd "$SPDM_EMU_PATH" && ./spdm_responder_emu --ver 1.4 \ - --hash SHA_384 --asym NONE --pqc_asym "$alg" \ - --dhe SECP_384_R1 --aead AES_256_GCM \ - >/tmp/pqc_emu_${alg}_${label}.log 2>&1 ) & - emu=$! - if wait_for_port; then - sleep 1 - if "$DEMO" "$@" --ver 1.4 --debug; then rc=0; else rc=$?; fi - else - rc=1 - fi - kill $emu 2>/dev/null || true - wait $emu 2>/dev/null || true - pkill -f spdm_responder_emu 2>/dev/null || true - [ $rc -eq 0 ] && break - echo "--- responder log (attempt $attempt) ---" - cat /tmp/pqc_emu_${alg}_${label}.log || true - echo "attempt $attempt for $alg $label failed (rc=$rc), retrying" - sleep 1 - done - echo "::endgroup::" - if [ $rc -ne 0 ]; then - echo "::error::$alg $label failed after retries (rc=$rc)" - FAILURES="$FAILURES $alg/$label" - else - echo "$alg $label: OK" - fi - } - - for spec in "ML_DSA_44 mldsa44" "ML_DSA_65 mldsa65" "ML_DSA_87 mldsa87"; do - set -- $spec - alg="$1"; dir="$2" - run_case "$alg" "$dir" session --emu - run_case "$alg" "$dir" meas --meas - run_case "$alg" "$dir" challenge --challenge - done - - if [ -n "$FAILURES" ]; then - echo "::error::ML-DSA interop failures:$FAILURES" - exit 1 - fi - echo "All ML-DSA 44/65/87 interop cases passed." - - # --- ML-KEM interop: responder offers only ML-KEM (--dhe NONE) with ECDSA - # signing, so the handshake performs ML-KEM key exchange in isolation. - # The requester forces KEM-only advertisement with --kex. ek (<=1568 B) - # and ciphertext c (<=1568 B) fit one message under the responder's - # DataTransferSize, so this tests the KEM path without chunking. - - name: ML-KEM 512/768/1024 session + measurements + challenge - run: | - export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib - export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin - export SPDM_EMU_CERT_DIR=ecp384 - DEMO=./examples/spdm_demo - FAILURES="" - - wait_for_port() { - local i - for i in $(seq 1 50); do - if ss -ltn 2>/dev/null | grep -q ':2323 '; then return 0; fi - sleep 0.2 - done - return 1 - } - - # $1 emu KEM name, $2 demo --kex name, $3 label, then demo args. - run_kem() { - local kem="$1" kex="$2" label="$3"; shift 3 - echo "::group::$kem $label" - local attempt rc=1 emu - for attempt in 1 2 3; do - ( cd "$SPDM_EMU_PATH" && ./spdm_responder_emu --ver 1.4 \ - --hash SHA_384 --asym ECDSA_P384 --pqc_asym NONE \ - --dhe NONE --kem "$kem" --aead AES_256_GCM \ - >/tmp/pqc_emu_${kem}_${label}.log 2>&1 ) & - emu=$! - if wait_for_port; then - sleep 1 - if "$DEMO" "$@" --ver 1.4 --kex "$kex" --debug; then rc=0; else rc=$?; fi - else - rc=1 - fi - kill $emu 2>/dev/null || true - wait $emu 2>/dev/null || true - pkill -f spdm_responder_emu 2>/dev/null || true - [ $rc -eq 0 ] && break - echo "--- responder log (attempt $attempt) ---" - cat /tmp/pqc_emu_${kem}_${label}.log || true - echo "attempt $attempt for $kem $label failed (rc=$rc), retrying" - sleep 1 - done - echo "::endgroup::" - if [ $rc -ne 0 ]; then - echo "::error::$kem $label failed after retries (rc=$rc)" - FAILURES="$FAILURES $kem/$label" - else - echo "$kem $label: OK" - fi - } - - for spec in "ML_KEM_512 mlkem512" "ML_KEM_768 mlkem768" \ - "ML_KEM_1024 mlkem1024"; do - set -- $spec - kem="$1"; kex="$2" - run_kem "$kem" "$kex" session --emu - run_kem "$kem" "$kex" meas --meas - run_kem "$kem" "$kex" challenge --challenge - done - - if [ -n "$FAILURES" ]; then - echo "::error::ML-KEM interop failures:$FAILURES" - exit 1 - fi - echo "All ML-KEM 512/768/1024 interop cases passed." - - # --- Full post-quantum handshake: ML-KEM-768 key exchange + ML-DSA - # signing together, no classical asymmetric crypto. ML-DSA-65 fits one - # message; ML-DSA-87 (sig 4627 B) + ciphertext c (1088 B) exceeds the - # DataTransferSize, so this case also exercises CHUNK_GET reassembly - - # ML-KEM + ML-DSA + chunking in a single handshake. - - name: Full PQ (ML-KEM-768 + ML-DSA 65/87) session + measurements + challenge - run: | - export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib - export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin - DEMO=./examples/spdm_demo - FAILURES="" - - wait_for_port() { - local i - for i in $(seq 1 50); do - if ss -ltn 2>/dev/null | grep -q ':2323 '; then return 0; fi - sleep 0.2 - done - return 1 - } - - # $1 pqc_asym (ML_DSA_xx), $2 cert dir, $3 label, then demo args. KEM is - # ML-KEM-768 throughout; the requester forces it with --kex mlkem768. - run_pq() { - local pqc="$1" certdir="$2" label="$3"; shift 3 - export SPDM_EMU_CERT_DIR="$certdir" - echo "::group::$pqc+ML_KEM_768 $label" - local attempt rc=1 emu - for attempt in 1 2 3; do - ( cd "$SPDM_EMU_PATH" && ./spdm_responder_emu --ver 1.4 \ - --hash SHA_384 --asym NONE --pqc_asym "$pqc" \ - --dhe NONE --kem ML_KEM_768 --aead AES_256_GCM \ - >/tmp/pqc_emu_${pqc}_kem_${label}.log 2>&1 ) & - emu=$! - if wait_for_port; then - sleep 1 - if "$DEMO" "$@" --ver 1.4 --kex mlkem768 --debug; then rc=0; else rc=$?; fi - else - rc=1 - fi - kill $emu 2>/dev/null || true - wait $emu 2>/dev/null || true - pkill -f spdm_responder_emu 2>/dev/null || true - [ $rc -eq 0 ] && break - echo "--- responder log (attempt $attempt) ---" - cat /tmp/pqc_emu_${pqc}_kem_${label}.log || true - echo "attempt $attempt for $pqc+KEM $label failed (rc=$rc), retrying" - sleep 1 - done - echo "::endgroup::" - if [ $rc -ne 0 ]; then - echo "::error::$pqc+ML_KEM_768 $label failed after retries (rc=$rc)" - FAILURES="$FAILURES $pqc+kem/$label" - else - echo "$pqc+ML_KEM_768 $label: OK" - fi - } - - for spec in "ML_DSA_65 mldsa65" "ML_DSA_87 mldsa87"; do - set -- $spec - pqc="$1"; dir="$2" - run_pq "$pqc" "$dir" session --emu - run_pq "$pqc" "$dir" meas --meas - run_pq "$pqc" "$dir" challenge --challenge - done - - if [ -n "$FAILURES" ]; then - echo "::error::Full-PQ interop failures:$FAILURES" - exit 1 - fi - echo "All full-PQ (ML-KEM + ML-DSA) interop cases passed." - - - name: Upload logs on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: spdm-emu-pqc-logs-${{ matrix.os }}-${{ matrix.arch }}-dynmem-${{ matrix.dynamic-mem }} - path: | - config.log - test/*.log - /tmp/pqc_emu_*.log - retention-days: 5 diff --git a/.github/workflows/spdm-emu-test.yml b/.github/workflows/spdm-emu-test.yml deleted file mode 100644 index d36a3bb..0000000 --- a/.github/workflows/spdm-emu-test.yml +++ /dev/null @@ -1,146 +0,0 @@ -name: SPDM Emulator Integration Test - -on: - push: - branches: [ 'main', 'release/**' ] - pull_request: - branches: [ '*' ] - repository_dispatch: - types: [nightly-trigger] - -jobs: - spdm-emu-test: - name: ${{ matrix.os }} (${{ matrix.arch }}) / dynamic-mem=${{ matrix.dynamic-mem }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-22.04 - arch: x64 - dynamic-mem: 'no' - - os: ubuntu-22.04 - arch: x64 - dynamic-mem: 'yes' - - os: ubuntu-24.04 - arch: x64 - dynamic-mem: 'no' - - os: ubuntu-24.04 - arch: x64 - dynamic-mem: 'yes' - - os: ubuntu-24.04-arm - arch: aarch64 - dynamic-mem: 'no' - - os: ubuntu-24.04-arm - arch: aarch64 - dynamic-mem: 'yes' - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y autoconf automake libtool cmake libmbedtls-dev - - # Cache period rotates every ~15 days so dependencies stay fresh - - name: Compute cache period - id: cache-period - run: echo "biweekly=$(( $(date +%s) / 1296000 ))" >> $GITHUB_OUTPUT - - # --- wolfSSL (cached) --- - - name: Cache wolfSSL - id: cache-wolfssl - uses: actions/cache@v4 - with: - path: ~/wolfssl-install - key: wolfssl-spdm-${{ matrix.os }}-${{ steps.cache-period.outputs.biweekly }} - - - name: Build wolfSSL - if: steps.cache-wolfssl.outputs.cache-hit != 'true' - run: | - cd ~ - git clone --depth 1 https://github.com/wolfSSL/wolfssl.git - cd wolfssl - ./autogen.sh - ./configure --enable-wolftpm --enable-ecc --enable-sha384 \ - --enable-aesgcm --enable-hkdf --enable-sp \ - --prefix=$HOME/wolfssl-install - make -j$(nproc) - make install - - # --- wolfSPDM (always rebuilt - this is what we're testing) --- - - name: Build and install wolfSPDM - run: | - ./autogen.sh - ./configure --with-wolfssl=$HOME/wolfssl-install \ - --prefix=$HOME/wolfspdm-install \ - ${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }} - make -j$(nproc) - make install - - - name: Run unit tests - run: make check - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib - - # --- spdm-emu (cached) --- - - name: Cache spdm-emu - id: cache-spdm-emu - uses: actions/cache@v4 - with: - path: ~/spdm-emu/build/bin - key: spdm-emu-${{ matrix.os }}-${{ steps.cache-period.outputs.biweekly }} - - - name: Build spdm-emu - if: steps.cache-spdm-emu.outputs.cache-hit != 'true' - run: | - cd ~ - git clone --depth 1 --recursive https://github.com/DMTF/spdm-emu.git - cd spdm-emu - mkdir build && cd build - cmake -DARCH=${{ matrix.arch }} -DTOOLCHAIN=GCC \ - -DTARGET=Release -DCRYPTO=mbedtls .. - make copy_sample_key - make -j$(nproc) - - # --- Demo smoke (help text / arg parsing, no emulator) --- - - name: spdm_demo CLI smoke (no emulator) - run: | - export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib - ./examples/spdm_demo --help 2>&1 | head -20 || true - - # --- Legacy smoke test (test/test_spdm) — single session against emu --- - - name: test/test_spdm smoke (one session) - run: | - export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib - export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin - # Start emulator in background, run the legacy smoke, then kill it - (cd "$SPDM_EMU_PATH" && ./spdm_responder_emu --ver 1.2 \ - --hash SHA_384 --asym ECDSA_P384 \ - --dhe SECP_384_R1 --aead AES_256_GCM \ - >/tmp/test_spdm_emu.log 2>&1) & - EMU_PID=$! - sleep 2 - ./test/test_spdm - RC=$? - kill $EMU_PID 2>/dev/null || true - wait $EMU_PID 2>/dev/null || true - exit $RC - - # --- Full integration matrix (18 tests: 6 scenarios x SPDM 1.2/1.3/1.4) --- - - name: Run SPDM emulator tests (18-test matrix) - run: | - export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib - export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin - ./examples/spdm_test.sh - - - name: Upload logs on failure - if: failure() - uses: actions/upload-artifact@v4 - with: - name: spdm-emu-test-logs-${{ matrix.os }}-${{ matrix.arch }}-dynmem-${{ matrix.dynamic-mem }} - path: | - config.log - test/*.log - /tmp/spdm_emu_*.log - /tmp/test_spdm_emu.log diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 0d05dd6..253f0b0 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -88,7 +88,7 @@ jobs: - name: Configure run: | ./autogen.sh - ./configure --with-wolfssl=$HOME/wolfssl-install + ./configure --with-wolfssl=$HOME/wolfssl-install --enable-nuvoton --enable-nations --enable-responder - name: Run scan-build run: scan-build --status-bugs -o scan-results make -j$(nproc) diff --git a/.github/workflows/wolfssl-versions.yml b/.github/workflows/wolfssl-versions.yml index 737a568..5a421fe 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -115,7 +115,7 @@ jobs: MLDSA_FLAG="" if [ "${{ matrix.pqc }}" = "true" ]; then MLDSA_FLAG="--enable-mldsa"; fi ./autogen.sh - ./configure --with-wolfssl=$HOME/wolfssl-install $MLDSA_FLAG + ./configure --with-wolfssl=$HOME/wolfssl-install --enable-nuvoton --enable-nations --enable-responder $MLDSA_FLAG make -j"$(nproc)" - name: Run unit tests @@ -129,7 +129,7 @@ jobs: if [ "${{ matrix.pqc }}" = "true" ]; then MLDSA_FLAG="--enable-mldsa"; fi make distclean || true ./autogen.sh - ./configure --with-wolfssl=$HOME/wolfssl-install --enable-dynamic-mem $MLDSA_FLAG + ./configure --with-wolfssl=$HOME/wolfssl-install --enable-nuvoton --enable-nations --enable-responder --enable-dynamic-mem $MLDSA_FLAG make -j"$(nproc)" - name: Run unit tests (dynamic-mem) diff --git a/.github/workflows/wolftpm-downstream.yml b/.github/workflows/wolftpm-downstream.yml new file mode 100644 index 0000000..2b333ab --- /dev/null +++ b/.github/workflows/wolftpm-downstream.yml @@ -0,0 +1,185 @@ +name: wolfTPM downstream + +# Builds wolfTPM master with this wolfSPDM tree in place of its embedded SPDM +# sources and runs wolfTPM's SPDM test suite, so no change can land in +# wolfSPDM that breaks wolfTPM. + +on: + push: + branches: [ 'main', 'wolftpm-core', 'release/**' ] + pull_request: + branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: build / ${{ matrix.name }} + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - name: spdm-base-only + config: --enable-spdm --disable-fwtpm --disable-tcg --disable-psk + - name: spdm-tcg-only + config: --enable-fwtpm --enable-spdm --enable-tcg --disable-psk + - name: spdm-both + config: --enable-fwtpm --enable-spdm --enable-tcg --enable-psk + - name: spdm-nuvoton + config: --enable-fwtpm --enable-spdm --enable-nuvoton + reject: nations + - name: spdm-nations + config: --enable-fwtpm --enable-spdm --enable-nations + reject: nuvoton + - name: spdm-full-vendors + config: --enable-fwtpm --enable-spdm --enable-tcg --enable-psk --enable-nuvoton --enable-nations + - name: spdm-requester-only + config: --enable-spdm --enable-nuvoton --enable-nations + - name: spdm-debug-on + config: --enable-fwtpm --enable-spdm --enable-tcg --enable-psk --enable-debug + - name: spdm-no-getenv + config: --enable-fwtpm --enable-spdm --enable-tcg --enable-psk CFLAGS=-DNO_GETENV + - name: spdm-nuvoton-smallstack + config: --enable-spdm --enable-nuvoton --enable-smallstack --disable-fwtpm + - name: spdm-nations-smallstack + config: --enable-spdm --enable-nations --enable-smallstack --disable-fwtpm + - name: spdm-nations-debug + config: --enable-spdm --enable-nations --enable-debug --disable-fwtpm + - name: spdm-nuvoton-autodisable + config: --enable-spdm --enable-nuvoton --enable-debug + - name: spdm-nations-autodisable + config: --enable-spdm --enable-nations --enable-debug + + steps: + - name: Checkout wolfTPM + uses: actions/checkout@v4 + with: + repository: wolfSSL/wolfTPM + ref: master + persist-credentials: false + + - name: Checkout wolfSPDM + uses: actions/checkout@v4 + with: + path: wolfspdm-src + persist-credentials: false + + - name: Setup wolfSSL + uses: ./.github/actions/setup-wolfssl + with: + configure-flags: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-aescfb + cflags: -DWC_RSA_NO_PADDING + prefix: $HOME/wolfssl-install + + - name: Overlay wolfSPDM onto wolfTPM + run: ./wolfspdm-src/scripts/wolftpm-overlay.sh . + + - name: Build wolfTPM (${{ matrix.name }}) + run: | + ./autogen.sh + ./configure ${{ matrix.config }} --with-wolfcrypt=$HOME/wolfssl-install + make -j"$(nproc)" + + - name: Run SPDM unit tests + env: + LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }} + run: ./src/spdm/unit_test + + - name: Test unavailable vendor rejection + if: matrix.reject != '' + env: + LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }} + run: | + set -eo pipefail + if [ "${{ matrix.reject }}" = "nations" ]; then + expected="Nations adapter is not available in this build" + else + expected="Nuvoton adapter is not available in this build" + fi + if output=$(./examples/spdm/spdm_ctrl \ + "--vendor=${{ matrix.reject }}" --status 2>&1); then + echo "Expected unavailable vendor rejection" + exit 1 + fi + grep -F "$expected" <<< "$output" + + e2e: + name: e2e / ${{ matrix.mode }} + runs-on: ubuntu-latest + timeout-minutes: 25 + strategy: + fail-fast: false + matrix: + mode: [ fwtpm-tcg, fwtpm-psk ] + + steps: + - name: Checkout wolfTPM + uses: actions/checkout@v4 + with: + repository: wolfSSL/wolfTPM + ref: master + persist-credentials: false + + - name: Checkout wolfSPDM + uses: actions/checkout@v4 + with: + path: wolfspdm-src + persist-credentials: false + + - name: Setup wolfSSL + uses: ./.github/actions/setup-wolfssl + with: + configure-flags: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-aescfb + cflags: -DWC_RSA_NO_PADDING + prefix: $HOME/wolfssl-install + + - name: Overlay wolfSPDM onto wolfTPM + run: ./wolfspdm-src/scripts/wolftpm-overlay.sh . + + - name: Build wolfTPM with full SPDM and vendor support + run: | + ./autogen.sh + ./configure --enable-fwtpm --enable-spdm \ + --enable-tcg --enable-psk \ + --enable-nuvoton --enable-nations \ + --enable-debug --enable-swtpm \ + --with-wolfcrypt=$HOME/wolfssl-install + make -j"$(nproc)" + + - name: Run wolfTPM make check + env: + LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }} + run: | + set -eo pipefail + make check 2>&1 | tee make-check-${{ matrix.mode }}.log + + - name: Run spdm_test.sh ${{ matrix.mode }} + env: + LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }} + run: | + set -eo pipefail + ./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl ${{ matrix.mode }} 2>&1 \ + | tee spdm-${{ matrix.mode }}.log + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: wolftpm-downstream-${{ matrix.mode }} + path: | + *.log + config.log + test-suite.log + tests/*.log + src/spdm/*.log + /tmp/fwtpm_spdm_test.* + retention-days: 14 diff --git a/Makefile.am b/Makefile.am index fb18744..d9e62f1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4,7 +4,6 @@ lib_LTLIBRARIES = libwolfspdm.la libwolfspdm_la_SOURCES = \ src/spdm_context.c \ - src/spdm_chunk.c \ src/spdm_crypto.c \ src/spdm_kdf.c \ src/spdm_msg.c \ @@ -12,6 +11,22 @@ libwolfspdm_la_SOURCES = \ src/spdm_session.c \ src/spdm_transcript.c +if BUILD_TCG +libwolfspdm_la_SOURCES += src/spdm_tcg.c +endif +if BUILD_PSK +libwolfspdm_la_SOURCES += src/spdm_psk.c +endif +if BUILD_NUVOTON +libwolfspdm_la_SOURCES += src/vendor/spdm_nuvoton.c +endif +if BUILD_NATIONS +libwolfspdm_la_SOURCES += src/vendor/spdm_nations.c +endif +if BUILD_RESPONDER +libwolfspdm_la_SOURCES += src/spdm_responder.c +endif + libwolfspdm_la_CPPFLAGS = -I$(srcdir)/wolfspdm -I$(srcdir)/src libwolfspdm_la_LIBADD = -lwolfssl @@ -30,33 +45,28 @@ nobase_include_HEADERS = \ wolfspdm/spdm.h \ wolfspdm/spdm_types.h \ wolfspdm/spdm_error.h \ + wolfspdm/spdm_tcg.h \ + wolfspdm/spdm_psk.h \ + wolfspdm/spdm_nuvoton.h \ + wolfspdm/spdm_nations.h \ + wolfspdm/spdm_responder.h \ wolfspdm/options.h # Test programs -check_PROGRAMS = test/unit_test test/test_spdm +check_PROGRAMS = test/unit_test test_unit_test_SOURCES = test/unit_test.c test_unit_test_CPPFLAGS = -I$(srcdir)/wolfspdm -I$(srcdir)/src test_unit_test_LDADD = libwolfspdm.la -lwolfssl -test_test_spdm_SOURCES = test/test_spdm.c -test_test_spdm_CPPFLAGS = -I$(srcdir)/wolfspdm -test_test_spdm_LDADD = libwolfspdm.la -lwolfssl - TESTS = test/unit_test -# Example programs (built but not installed) -noinst_PROGRAMS = examples/spdm_demo - -examples_spdm_demo_SOURCES = examples/spdm_demo.c -examples_spdm_demo_CPPFLAGS = -I$(srcdir)/wolfspdm -examples_spdm_demo_LDADD = libwolfspdm.la -lwolfssl - # pkgconfig pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = wolfspdm.pc EXTRA_DIST = \ + src/spdm_internal.h \ autogen.sh \ README.md \ LICENSE \ diff --git a/config.h.in b/config.h.in index 3d4d91c..50e585f 100644 --- a/config.h.in +++ b/config.h.in @@ -71,14 +71,20 @@ /* Enable dynamic memory allocation */ #undef WOLFSPDM_DYNAMIC_MEMORY -/* Disable SPDM message chunking */ -#undef WOLFSPDM_NO_CHUNK +/* Enable Nations SPDM vendor commands */ +#undef WOLFSPDM_NATIONS -/* Disable ML-DSA support */ -#undef WOLFSPDM_NO_MLDSA +/* Enable Nuvoton SPDM vendor commands */ +#undef WOLFSPDM_NUVOTON -/* Disable ML-KEM support */ -#undef WOLFSPDM_NO_MLKEM +/* Enable SPDM PSK mode */ +#undef WOLFSPDM_PSK + +/* Enable the SPDM responder */ +#undef WOLFSPDM_RESPONDER + +/* Enable the TCG SPDM binding */ +#undef WOLFSPDM_TCG /* Define for Solaris 2.5.1 so the uint32_t typedef from , , or is not used. If the typedef were allowed, the diff --git a/configure.ac b/configure.ac index 29ea00a..8fafcef 100644 --- a/configure.ac +++ b/configure.ac @@ -80,131 +80,66 @@ if test "x$enable_dynamic_mem" = "xyes"; then AC_DEFINE([WOLFSPDM_DYNAMIC_MEMORY], [1], [Enable dynamic memory allocation]) fi -# Large-response chunking (DSP0274 Sec. 10.27 CHUNK_GET). Default: enabled. -# Lets the requester reassemble responses the responder splits because they -# exceed DataTransferSize (needed for ML-DSA-87 over the wire). -AC_ARG_ENABLE([chunking], - [AS_HELP_STRING([--disable-chunking], [Disable SPDM message chunking (CHUNK_GET)])], - [enable_chunking=$enableval], - [enable_chunking=yes]) - -if test "x$enable_chunking" = "xno"; then - AC_DEFINE([WOLFSPDM_NO_CHUNK], [1], [Disable SPDM message chunking]) - chunking_status=disabled -else - chunking_status=enabled +# TCG SPDM binding (TPM transport). Nuvoton/Nations imply it. +AC_ARG_ENABLE([tcg], + [AS_HELP_STRING([--enable-tcg], [Enable the TCG SPDM binding (default: disabled)])], + [enable_tcg=$enableval], + [enable_tcg=no]) + +AC_ARG_ENABLE([psk], + [AS_HELP_STRING([--enable-psk], [Enable SPDM PSK_EXCHANGE/PSK_FINISH (default: disabled)])], + [enable_psk=$enableval], + [enable_psk=no]) + +AC_ARG_ENABLE([nuvoton], + [AS_HELP_STRING([--enable-nuvoton], [Enable Nuvoton NPCT75x SPDM vendor commands (default: disabled)])], + [enable_nuvoton=$enableval], + [enable_nuvoton=no]) + +AC_ARG_ENABLE([nations], + [AS_HELP_STRING([--enable-nations], [Enable Nations NS350 SPDM vendor commands (default: disabled)])], + [enable_nations=$enableval], + [enable_nations=no]) + +AC_ARG_ENABLE([responder], + [AS_HELP_STRING([--enable-responder], [Enable the SPDM responder (default: disabled)])], + [enable_responder=$enableval], + [enable_responder=no]) + +if test "x$enable_nuvoton" = "xyes" || test "x$enable_nations" = "xyes"; then + enable_tcg=yes fi - -# ML-DSA (FIPS 204) post-quantum signatures (DSP0274 1.4). Default: auto-follow -# the linked wolfSSL - on when it reports WOLFSSL_HAVE_MLDSA, off otherwise. -AC_ARG_ENABLE([mldsa], - [AS_HELP_STRING([--disable-mldsa], [Disable ML-DSA support even if wolfSSL has it])], - [enable_mldsa=$enableval], - [enable_mldsa=auto]) - -AC_MSG_CHECKING([whether wolfSSL provides ML-DSA]) -AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ - #include - #include - #ifndef WOLFSSL_HAVE_MLDSA - #error "no mldsa" - #endif - int main(void) { return 0; } -]])], -[have_wolfssl_mldsa=yes], -[have_wolfssl_mldsa=no]) -AC_MSG_RESULT([$have_wolfssl_mldsa]) - -# wolfSPDM uses the context-based wc_MlDsaKey_* verification API (FIPS 204 -# ML-DSA.Verify with a context). That API lands post-v5.9.1-stable; older -# wolfSSL releases ship only the legacy ML-DSA interface and report the same -# LIBWOLFSSL_VERSION_HEX as wolfSSL master, so we capability-test for the API -# instead of gating on a version number. -have_mldsa_api=no -if test "x$enable_mldsa" != "xno" && test "x$have_wolfssl_mldsa" = "xyes"; then - AC_MSG_CHECKING([for the wc_MlDsaKey context API]) - AC_LINK_IFELSE([AC_LANG_PROGRAM([[ - #include - #include - #include - ]], [[ - MlDsaKey k; int res = 0; word32 idx = 0; - (void)wc_MlDsaKey_VerifyCtx(&k, 0, 0, 0, 0, 0, 0, &res); - (void)wc_MlDsaKey_PublicKeyDecode(&k, 0, 0, &idx); - ]])], - [have_mldsa_api=yes], - [have_mldsa_api=no]) - AC_MSG_RESULT([$have_mldsa_api]) +if test "x$enable_nations" = "xyes"; then + enable_psk=yes fi - -if test "x$enable_mldsa" = "xno"; then - AC_DEFINE([WOLFSPDM_NO_MLDSA], [1], [Disable ML-DSA support]) - mldsa_status=disabled -elif test "x$have_mldsa_api" = "xyes"; then - mldsa_status=enabled -elif test "x$enable_mldsa" = "xyes"; then - AC_MSG_ERROR([--enable-mldsa requires a wolfSSL with the wc_MlDsaKey context API (wolfSSL master or a release that ships it, built with --enable-mldsa). Use --disable-mldsa or update wolfSSL.]) -else - AC_DEFINE([WOLFSPDM_NO_MLDSA], [1], [Disable ML-DSA support]) - mldsa_status=disabled +if test "x$enable_psk" = "xyes" && test "x$enable_tcg" != "xyes"; then + AC_MSG_ERROR([--enable-psk requires --enable-tcg (PSK handshake uses TCG SPDM binding framing)]) fi - -# ML-KEM (FIPS 203) post-quantum key exchange (DSP0274 1.4). Default: auto-follow -# the linked wolfSSL - on when it reports WOLFSSL_HAVE_MLKEM, off otherwise. -AC_ARG_ENABLE([mlkem], - [AS_HELP_STRING([--disable-mlkem], [Disable ML-KEM support even if wolfSSL has it])], - [enable_mlkem=$enableval], - [enable_mlkem=auto]) - -AC_MSG_CHECKING([whether wolfSSL provides ML-KEM]) -AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ - #include - #include - #ifndef WOLFSSL_HAVE_MLKEM - #error "no mlkem" - #endif - int main(void) { return 0; } -]])], -[have_wolfssl_mlkem=yes], -[have_wolfssl_mlkem=no]) -AC_MSG_RESULT([$have_wolfssl_mlkem]) - -# wolfSPDM uses the wc_MlKemKey_* API (keygen, encapsulation-key encode, and -# decapsulation). Capability-test for it rather than gating on a version number. -have_mlkem_api=no -if test "x$enable_mlkem" != "xno" && test "x$have_wolfssl_mlkem" = "xyes"; then - AC_MSG_CHECKING([for the wc_MlKemKey API]) - AC_LINK_IFELSE([AC_LANG_PROGRAM([[ - #include - #include - #include - ]], [[ - MlKemKey k; word32 len = 0; - (void)wc_MlKemKey_Init(&k, 0, 0, 0); - (void)wc_MlKemKey_MakeKey(&k, 0); - (void)wc_MlKemKey_EncodePublicKey(&k, 0, 0); - (void)wc_MlKemKey_PublicKeySize(&k, &len); - (void)wc_MlKemKey_CipherTextSize(&k, &len); - (void)wc_MlKemKey_SharedSecretSize(&k, &len); - (void)wc_MlKemKey_Decapsulate(&k, 0, 0, 0); - (void)wc_MlKemKey_Free(&k); - ]])], - [have_mlkem_api=yes], - [have_mlkem_api=no]) - AC_MSG_RESULT([$have_mlkem_api]) +if test "x$enable_responder" = "xyes" && test "x$enable_tcg" != "xyes"; then + AC_MSG_ERROR([--enable-responder requires --enable-tcg]) fi -if test "x$enable_mlkem" = "xno"; then - AC_DEFINE([WOLFSPDM_NO_MLKEM], [1], [Disable ML-KEM support]) - mlkem_status=disabled -elif test "x$have_mlkem_api" = "xyes"; then - mlkem_status=enabled -elif test "x$enable_mlkem" = "xyes"; then - AC_MSG_ERROR([--enable-mlkem requires a wolfSSL with the wc_MlKemKey API, built with --enable-mlkem. Use --disable-mlkem or update wolfSSL.]) -else - AC_DEFINE([WOLFSPDM_NO_MLKEM], [1], [Disable ML-KEM support]) - mlkem_status=disabled +if test "x$enable_tcg" = "xyes"; then + AC_DEFINE([WOLFSPDM_TCG], [1], [Enable the TCG SPDM binding]) fi +if test "x$enable_psk" = "xyes"; then + AC_DEFINE([WOLFSPDM_PSK], [1], [Enable SPDM PSK mode]) +fi +if test "x$enable_nuvoton" = "xyes"; then + AC_DEFINE([WOLFSPDM_NUVOTON], [1], [Enable Nuvoton SPDM vendor commands]) +fi +if test "x$enable_nations" = "xyes"; then + AC_DEFINE([WOLFSPDM_NATIONS], [1], [Enable Nations SPDM vendor commands]) +fi +if test "x$enable_responder" = "xyes"; then + AC_DEFINE([WOLFSPDM_RESPONDER], [1], [Enable the SPDM responder]) +fi + +AM_CONDITIONAL([BUILD_TCG], [test "x$enable_tcg" = "xyes"]) +AM_CONDITIONAL([BUILD_PSK], [test "x$enable_psk" = "xyes"]) +AM_CONDITIONAL([BUILD_NUVOTON], [test "x$enable_nuvoton" = "xyes"]) +AM_CONDITIONAL([BUILD_NATIONS], [test "x$enable_nations" = "xyes"]) +AM_CONDITIONAL([BUILD_RESPONDER], [test "x$enable_responder" = "xyes"]) # Output files AC_CONFIG_FILES([Makefile wolfspdm.pc]) @@ -215,8 +150,10 @@ echo "wolfSPDM configuration summary:" echo " Version: $PACKAGE_VERSION" echo " Debug: $enable_debug" echo " Dynamic mem: $enable_dynamic_mem" -echo " Chunking: $chunking_status" -echo " ML-DSA: $mldsa_status" -echo " ML-KEM: $mlkem_status" +echo " TCG: $enable_tcg" +echo " PSK: $enable_psk" +echo " Nuvoton: $enable_nuvoton" +echo " Nations: $enable_nations" +echo " Responder: $enable_responder" echo " wolfSSL: ${WOLFSSL_DIR:-system}" echo "" diff --git a/examples/spdm_demo.c b/examples/spdm_demo.c deleted file mode 100644 index a8181f9..0000000 --- a/examples/spdm_demo.c +++ /dev/null @@ -1,605 +0,0 @@ -/* spdm_demo.c - * - * wolfSPDM emulator demo - drives spdm-emu over TCP/MCTP for end-to-end - * testing of session, measurements, challenge, heartbeat, and key update. - * - * Usage: - * spdm_demo --emu [--ver 1.2|1.3|1.4] - * spdm_demo --meas [--no-sig] [--ver ...] - * spdm_demo --challenge [--ver ...] - * spdm_demo --heartbeat [--ver ...] - * spdm_demo --key-update [--ver ...] - * - * Picks up the spdm-emu install dir from $SPDM_EMU_PATH (used to find the - * ca.cert.der for --challenge). - */ - -#include -#include -#include -#include -#include -#include -#include - -#ifdef __linux__ -#include -#include -#include -#include -#include -#include -#define HAS_SOCKET 1 -#endif - -#define EMU_HOST "127.0.0.1" -#define EMU_PORT 2323 - -#ifdef HAS_SOCKET - -typedef struct { - int sockFd; -} TCP_CTX; - -static TCP_CTX g_tcpCtx = { -1 }; - -/* A secured SPDM record starts with the 4-byte session ID; a plain SPDM - * message starts with a version byte. Look up the live session ID from - * the wolfSPDM context: before KEY_EXCHANGE_RSP it's 0, after it matches - * the first 4 bytes of every secured record. Robust against non-default - * reqSessionId picks, unlike a buf[0] range check. */ -static int is_secured_spdm(WOLFSPDM_CTX* ctx, const byte* buf, word32 sz) -{ - word32 sid; - word32 b0; - if (sz < 4) return 0; - sid = wolfSPDM_GetSessionId(ctx); - if (sid == 0) return 0; - b0 = (word32)buf[0] | ((word32)buf[1] << 8) | - ((word32)buf[2] << 16) | ((word32)buf[3] << 24); - return b0 == sid; -} - -/* send_all / recv_all: loop until the full count is transferred or a hard - * error occurs. TCP send/recv may return short on a busy / interrupted - * socket; MSG_WAITALL handles most recv cases but is advisory only, and - * send() must always be looped. */ -static int send_all(int fd, const void* buf, size_t len) -{ - const byte* p = (const byte*)buf; - size_t left = len; - while (left > 0) { - ssize_t n = send(fd, p, left, 0); - if (n < 0) { - if (errno == EINTR) continue; - return -1; - } - if (n == 0) return -1; - p += (size_t)n; - left -= (size_t)n; - } - return 0; -} - -static int recv_all(int fd, void* buf, size_t len) -{ - byte* p = (byte*)buf; - size_t left = len; - while (left > 0) { - ssize_t n = recv(fd, p, left, 0); - if (n < 0) { - if (errno == EINTR) continue; - return -1; - } - if (n == 0) return -1; /* peer closed */ - p += (size_t)n; - left -= (size_t)n; - } - return 0; -} - -/* MCTP transport I/O callback for spdm-emu (--trans MCTP, the default) */ -static int tcp_io_callback(WOLFSPDM_CTX* ctx, - const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz, - void* userCtx) -{ - TCP_CTX* tcpCtx = (TCP_CTX*)userCtx; - byte sendBuf[4096]; - byte recvHdr[12]; - byte mctpHdr; - word32 payloadSz, respSize; - - if (tcpCtx == NULL || tcpCtx->sockFd < 0) { - return -1; - } - - /* Bound txSz first so the +1/+12 additions can't overflow word32. */ - if (txSz > sizeof(sendBuf) - 13) { - return -1; - } - payloadSz = 1 + txSz; - - /* Socket header: command(4,BE) + transport_type(4,BE) + size(4,BE) */ - sendBuf[0] = 0x00; sendBuf[1] = 0x00; sendBuf[2] = 0x00; sendBuf[3] = 0x01; - sendBuf[4] = 0x00; sendBuf[5] = 0x00; sendBuf[6] = 0x00; sendBuf[7] = 0x01; - sendBuf[8] = (byte)(payloadSz >> 24); - sendBuf[9] = (byte)(payloadSz >> 16); - sendBuf[10] = (byte)(payloadSz >> 8); - sendBuf[11] = (byte)(payloadSz & 0xFF); - - /* MCTP message type: 0x05 = SPDM, 0x06 = Secured SPDM. */ - sendBuf[12] = is_secured_spdm(ctx, txBuf, txSz) ? 0x06 : 0x05; - - if (txSz > 0) { - memcpy(sendBuf + 13, txBuf, txSz); - } - - if (send_all(tcpCtx->sockFd, sendBuf, (size_t)(12 + payloadSz)) != 0) { - return -1; - } - - if (recv_all(tcpCtx->sockFd, recvHdr, sizeof(recvHdr)) != 0) { - return -1; - } - - respSize = ((word32)recvHdr[8] << 24) | ((word32)recvHdr[9] << 16) | - ((word32)recvHdr[10] << 8) | (word32)recvHdr[11]; - - if (respSize < 1 || respSize - 1 > *rxSz) { - return -1; - } - - /* Skip MCTP header byte */ - if (recv_all(tcpCtx->sockFd, &mctpHdr, 1) != 0) return -1; - (void)mctpHdr; - - *rxSz = respSize - 1; - if (*rxSz > 0) { - if (recv_all(tcpCtx->sockFd, rxBuf, (size_t)*rxSz) != 0) return -1; - } - return 0; -} - -static int tcp_connect(const char* host, int port) -{ - int sockFd; - struct sockaddr_in addr; - int optVal = 1; - - sockFd = socket(AF_INET, SOCK_STREAM, 0); - if (sockFd < 0) return -1; - - setsockopt(sockFd, IPPROTO_TCP, TCP_NODELAY, &optVal, sizeof(optVal)); - - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons((uint16_t)port); - if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { - close(sockFd); - return -1; - } - if (connect(sockFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - close(sockFd); - return -1; - } - g_tcpCtx.sockFd = sockFd; - return 0; -} - -static void tcp_disconnect(void) -{ - if (g_tcpCtx.sockFd >= 0) { - close(g_tcpCtx.sockFd); - g_tcpCtx.sockFd = -1; - } -} - -/* Load DER from file. Returns malloc'd buffer; caller frees. */ -static byte* load_der(const char* path, word32* outSz) -{ - FILE* f = fopen(path, "rb"); - long sz; - byte* buf; - size_t r; - - if (f == NULL) return NULL; - if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } - sz = ftell(f); - if (sz <= 0) { fclose(f); return NULL; } - rewind(f); - - buf = (byte*)malloc((size_t)sz); - if (buf == NULL) { fclose(f); return NULL; } - - r = fread(buf, 1, (size_t)sz, f); - fclose(f); - if (r != (size_t)sz) { free(buf); return NULL; } - - *outSz = (word32)sz; - return buf; -} - -/* Static-mode buffer for the SPDM context; sized by the public header. */ -static byte g_ctxBuf[WOLFSPDM_CTX_STATIC_SIZE]; -#define CTX_BUF_SIZE ((int)sizeof(g_ctxBuf)) - -enum { - MODE_SESSION = 1, /* --emu */ - MODE_MEAS, /* --meas */ - MODE_CHALLENGE, /* --challenge */ - MODE_HEARTBEAT, /* --heartbeat */ - MODE_KEY_UPDATE /* --key-update */ -}; - -static void usage(const char* argv0) -{ - fprintf(stderr, - "Usage: %s {--emu|--meas|--challenge|--heartbeat|--key-update}\n" - " [--no-sig] [--ver 1.2|1.3|1.4]\n" - " [--kex ecdhe|mlkem512|mlkem768|mlkem1024] [--debug]\n" - "\n" - "Env:\n" - " SPDM_EMU_PATH path to spdm-emu build/bin/ (used for trusted CA\n" - " lookup in --challenge mode)\n" - " SPDM_EMU_CERT_DIR cert subdir (ecp384 default, mldsa65, ...)\n", - argv0); -} - -/* Map "1.2"/"1.3"/"1.4" -> 0x12/0x13/0x14. Returns 0 on parse error. */ -static byte parse_version(const char* s) -{ - if (s == NULL) return 0; - if (strcmp(s, "1.2") == 0) return SPDM_VERSION_12; - if (strcmp(s, "1.3") == 0) return SPDM_VERSION_13; - if (strcmp(s, "1.4") == 0) return SPDM_VERSION_14; - return 0; -} - -/* Sanitize SPDM_EMU_PATH before joining a fixed suffix and handing it to - * fopen(): reject NULL, oversized, traversal-bearing, or non-printable - * input, then canonicalize with realpath() so the value used by fopen is - * a resolved filesystem path, not raw env data. This is a demo, but - * CodeQL flags concatenated env-vars in path expressions and the fix is - * also defensive against a malicious shell environment. */ -static int sanitize_emu_path(const char* emuPath, char* outReal, size_t outSz) -{ - size_t len, i; - char resolved[PATH_MAX]; - - if (emuPath == NULL) return -1; - len = strlen(emuPath); - if (len == 0 || len > PATH_MAX - 32) return -1; - for (i = 0; i < len; i++) { - unsigned char c = (unsigned char)emuPath[i]; - if (c < 0x20 || c == 0x7F) return -1; /* no control chars */ - } - - /* POSIX realpath(path, resolved): resolved must point to a buffer of - * PATH_MAX bytes. (Avoid the GNU realpath(path, NULL) extension.) - * realpath() canonicalizes any ../ segments so the resolved path is - * the actual filesystem location used by fopen(), which is what - * CodeQL's "uncontrolled data in path expression" rule asks for. */ - if (realpath(emuPath, resolved) == NULL) return -1; - len = strlen(resolved); - if (len >= outSz) return -1; - memcpy(outReal, resolved, len + 1); - return 0; -} - -static int load_trusted_ca(WOLFSPDM_CTX* ctx) -{ - /* Cert subdir matches the responder's selected algorithm. Only a fixed set - * of spdm-emu directory names is accepted; the env value is mapped to the - * matching string literal so no caller-controlled data reaches the fopen() - * path below (avoids path traversal). */ - static const char* const allowedCertDirs[] = { - "ecp256", "ecp384", "ecp521", "mldsa44", "mldsa65", "mldsa87" - }; - const char* emuPath = getenv("SPDM_EMU_PATH"); - const char* certDir = getenv("SPDM_EMU_CERT_DIR"); - const char* safeDir = NULL; - char realEmu[PATH_MAX]; - char path[PATH_MAX]; - byte* der; - word32 derSz; - int rc; - int n; - unsigned int i; - - if (emuPath == NULL) { - fprintf(stderr, "ERROR: SPDM_EMU_PATH not set; cannot locate " - "ca.cert.der for --challenge\n"); - return -1; - } - if (sanitize_emu_path(emuPath, realEmu, sizeof(realEmu)) != 0) { - fprintf(stderr, "ERROR: SPDM_EMU_PATH is not a valid directory path\n"); - return -1; - } - if (certDir == NULL || certDir[0] == '\0') { - certDir = "ecp384"; /* default: ECDSA P-384 */ - } - for (i = 0; i < sizeof(allowedCertDirs) / sizeof(allowedCertDirs[0]); i++) { - if (strcmp(certDir, allowedCertDirs[i]) == 0) { - safeDir = allowedCertDirs[i]; - break; - } - } - if (safeDir == NULL) { - fprintf(stderr, "ERROR: unsupported SPDM_EMU_CERT_DIR '%s'\n", certDir); - return -1; - } - n = snprintf(path, sizeof(path), "%s/%s/ca.cert.der", realEmu, safeDir); - if (n < 0 || (size_t)n >= sizeof(path)) { - fprintf(stderr, "ERROR: certificate path too long\n"); - return -1; - } - - der = load_der(path, &derSz); - if (der == NULL) { - fprintf(stderr, "ERROR: cannot read %s\n", path); - return -1; - } - rc = wolfSPDM_SetTrustedCAs(ctx, der, derSz); - free(der); - if (rc != WOLFSPDM_SUCCESS) { - fprintf(stderr, "ERROR: wolfSPDM_SetTrustedCAs: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - return -1; - } - return 0; -} - -static int do_session(WOLFSPDM_CTX* ctx) -{ - int rc = wolfSPDM_Connect(ctx); - if (rc != WOLFSPDM_SUCCESS) { - fprintf(stderr, "wolfSPDM_Connect: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - return rc; - } - printf("Session established (id=0x%08x, version=0x%02x)\n", - wolfSPDM_GetSessionId(ctx), - wolfSPDM_GetNegotiatedVersion(ctx)); - return WOLFSPDM_SUCCESS; -} - -static int do_meas(WOLFSPDM_CTX* ctx, int withSig) -{ - int rc; - - rc = do_session(ctx); - if (rc != WOLFSPDM_SUCCESS) return rc; - - rc = wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, withSig); - if (withSig) { - if (rc != WOLFSPDM_SUCCESS) { - fprintf(stderr, "GetMeasurements (signed): %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - return rc; - } - printf("Signed measurements verified (%d blocks)\n", - wolfSPDM_GetMeasurementCount(ctx)); - } - else { - /* Unsigned: NOT_VERIFIED is the expected success return */ - if (rc != WOLFSPDM_SUCCESS && rc != WOLFSPDM_E_MEAS_NOT_VERIFIED) { - fprintf(stderr, "GetMeasurements (unsigned): %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - return rc; - } - printf("Unsigned measurements received (%d blocks)\n", - wolfSPDM_GetMeasurementCount(ctx)); - rc = WOLFSPDM_SUCCESS; - } - return rc; -} - -static int do_challenge(WOLFSPDM_CTX* ctx) -{ - int rc; - - /* Sessionless: walk through GET_VERSION -> CAPABILITIES -> ALGORITHMS -> - * GET_DIGESTS -> GET_CERTIFICATE, then CHALLENGE. We don't call - * wolfSPDM_Connect() because that does KEY_EXCHANGE + FINISH. */ - rc = wolfSPDM_GetVersion(ctx); - if (rc != WOLFSPDM_SUCCESS) goto done; - rc = wolfSPDM_GetCapabilities(ctx); - if (rc != WOLFSPDM_SUCCESS) goto done; - rc = wolfSPDM_NegotiateAlgorithms(ctx); - if (rc != WOLFSPDM_SUCCESS) goto done; - rc = wolfSPDM_GetDigests(ctx); - if (rc != WOLFSPDM_SUCCESS) goto done; - rc = wolfSPDM_GetCertificate(ctx, 0); - if (rc != WOLFSPDM_SUCCESS) goto done; - - rc = load_trusted_ca(ctx); - if (rc != 0) { rc = WOLFSPDM_E_INVALID_ARG; goto done; } - - /* wolfSPDM_Challenge internally validates the cert chain against the - * loaded CAs when flags.hasTrustedCAs is set. */ - rc = wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_ALL); - if (rc == WOLFSPDM_SUCCESS) { - printf("Challenge succeeded (signature verified)\n"); - } -done: - if (rc != WOLFSPDM_SUCCESS) { - fprintf(stderr, "Challenge flow failed: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - } - return rc; -} - -static int do_heartbeat(WOLFSPDM_CTX* ctx) -{ - int rc = do_session(ctx); - if (rc != WOLFSPDM_SUCCESS) return rc; - rc = wolfSPDM_Heartbeat(ctx); - if (rc == WOLFSPDM_SUCCESS) { - printf("Heartbeat ACK received\n"); - } - else { - fprintf(stderr, "Heartbeat: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - } - return rc; -} - -static int do_key_update(WOLFSPDM_CTX* ctx) -{ - int rc = do_session(ctx); - if (rc != WOLFSPDM_SUCCESS) return rc; - rc = wolfSPDM_KeyUpdate(ctx, 1); /* rotate both directions */ - if (rc == WOLFSPDM_SUCCESS) { - printf("Key update succeeded\n"); - } - else { - fprintf(stderr, "KeyUpdate: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - } - return rc; -} - -int main(int argc, char* argv[]) -{ - static const struct option longOpts[] = { - { "emu", no_argument, 0, 'e' }, - { "meas", no_argument, 0, 'm' }, - { "no-sig", no_argument, 0, 'n' }, - { "challenge", no_argument, 0, 'c' }, - { "heartbeat", no_argument, 0, 'b' }, - { "key-update", no_argument, 0, 'k' }, - { "ver", required_argument, 0, 'v' }, - { "kex", required_argument, 0, 'K' }, - { "debug", no_argument, 0, 'd' }, - { "help", no_argument, 0, 'h' }, - { 0, 0, 0, 0 } - }; - int mode = 0; - int withSig = 1; - int debug = 0; - byte maxVer = 0; - int kexEcdheOnly = 0; - word16 kexKemOnly = 0; - int opt; - int rc; - WOLFSPDM_CTX* ctx = (WOLFSPDM_CTX*)g_ctxBuf; - - while ((opt = getopt_long(argc, argv, "emncbkv:hd", longOpts, NULL)) != -1) { - switch (opt) { - case 'e': mode = MODE_SESSION; break; - case 'm': mode = MODE_MEAS; break; - case 'n': withSig = 0; break; - case 'c': mode = MODE_CHALLENGE; break; - case 'b': mode = MODE_HEARTBEAT; break; - case 'k': mode = MODE_KEY_UPDATE; break; - case 'd': debug = 1; break; - case 'v': - maxVer = parse_version(optarg); - if (maxVer == 0) { - fprintf(stderr, "Invalid --ver %s (expected 1.2/1.3/1.4)\n", - optarg); - return 1; - } - break; - case 'K': - if (strcmp(optarg, "ecdhe") == 0) { - kexEcdheOnly = 1; - } - else if (strcmp(optarg, "mlkem512") == 0) { - kexKemOnly = SPDM_KEM_ALGO_ML_KEM_512; - } - else if (strcmp(optarg, "mlkem768") == 0) { - kexKemOnly = SPDM_KEM_ALGO_ML_KEM_768; - } - else if (strcmp(optarg, "mlkem1024") == 0) { - kexKemOnly = SPDM_KEM_ALGO_ML_KEM_1024; - } - else { - fprintf(stderr, "Invalid --kex %s (expected ecdhe/" - "mlkem512/mlkem768/mlkem1024)\n", optarg); - return 1; - } - break; - case 'h': usage(argv[0]); return 0; - default: usage(argv[0]); return 1; - } - } - if (mode == 0) { usage(argv[0]); return 1; } - - if (wolfSPDM_GetCtxSize() > CTX_BUF_SIZE) { - fprintf(stderr, "ERROR: CTX_BUF_SIZE too small (%d needed)\n", - wolfSPDM_GetCtxSize()); - return 1; - } - - if (tcp_connect(EMU_HOST, EMU_PORT) < 0) { - fprintf(stderr, "ERROR: cannot connect to %s:%d (is spdm-emu running?)\n", - EMU_HOST, EMU_PORT); - return 1; - } - - rc = wolfSPDM_InitStatic(ctx, CTX_BUF_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - fprintf(stderr, "wolfSPDM_InitStatic: %s\n", - wolfSPDM_GetErrorString(rc)); - tcp_disconnect(); - return 1; - } - - wolfSPDM_SetIO(ctx, tcp_io_callback, &g_tcpCtx); - - if (debug) { - wolfSPDM_SetDebug(ctx, 1); - } - - /* Demo runs against the DMTF spdm-emu, which uses self-signed test - * certs. Explicitly opt in to operating without a trust anchor so the - * default fail-closed behavior doesn't refuse the handshake. Real - * deployments should call wolfSPDM_SetTrustedCAs instead. */ - wolfSPDM_AllowUntrustedCerts(ctx, 1); - - if (maxVer != 0) { - rc = wolfSPDM_SetMaxVersion(ctx, maxVer); - if (rc != WOLFSPDM_SUCCESS) { - fprintf(stderr, "wolfSPDM_SetMaxVersion: %s\n", - wolfSPDM_GetErrorString(rc)); - goto done; - } - } - - if (kexEcdheOnly || kexKemOnly != 0) { - rc = wolfSPDM_SetKeyExchangePref(ctx, kexEcdheOnly ? 1 : 0, kexKemOnly); - if (rc != WOLFSPDM_SUCCESS) { - fprintf(stderr, "wolfSPDM_SetKeyExchangePref: %s\n", - wolfSPDM_GetErrorString(rc)); - goto done; - } - } - - switch (mode) { - case MODE_SESSION: rc = do_session(ctx); break; - case MODE_MEAS: rc = do_meas(ctx, withSig); break; - case MODE_CHALLENGE: rc = do_challenge(ctx); break; - case MODE_HEARTBEAT: rc = do_heartbeat(ctx); break; - case MODE_KEY_UPDATE: rc = do_key_update(ctx); break; - default: rc = -1; break; - } - - if (rc == WOLFSPDM_SUCCESS && wolfSPDM_IsConnected(ctx)) { - wolfSPDM_Disconnect(ctx); - } - -done: - wolfSPDM_Free(ctx); - tcp_disconnect(); - return (rc == WOLFSPDM_SUCCESS) ? 0 : 1; -} - -#else /* !HAS_SOCKET */ -int main(void) -{ - fprintf(stderr, "spdm_demo: socket support unavailable on this platform\n"); - return 1; -} -#endif diff --git a/examples/spdm_test.sh b/examples/spdm_test.sh deleted file mode 100755 index 4008777..0000000 --- a/examples/spdm_test.sh +++ /dev/null @@ -1,277 +0,0 @@ -#!/bin/bash -# -# spdm_test.sh - SPDM emulator test script -# -# Tests SPDM protocol with libspdm emulator (session + measurements + challenge -# + heartbeat + key update) across SPDM versions 1.2, 1.3, and 1.4. -# -# Usage: -# ./spdm_test.sh # Run emulator tests -# ./spdm_test.sh [path-to-spdm_demo] # Custom spdm_demo path -# - -SPDM_DEMO="./examples/spdm_demo" -PASS=0 -FAIL=0 -TOTAL=0 -EMU_PID="" -EMU_LOG="/tmp/spdm_emu_$$.log" - -# Colors (if terminal supports it) -if [ -t 1 ]; then - GREEN='\033[0;32m' - RED='\033[0;31m' - YELLOW='\033[0;33m' - NC='\033[0m' -else - GREEN='' - RED='' - YELLOW='' - NC='' -fi - -usage() { - echo "Usage: $0 [path-to-spdm_demo]" - echo "" - echo "Runs SPDM emulator tests (session, measurements, challenge," - echo "heartbeat, key update) across SPDM versions 1.2, 1.3, and 1.4." - echo "" - echo "Expects spdm_responder_emu to be found via:" - echo " 1. SPDM_EMU_PATH environment variable" - echo " 2. ../spdm-emu/build/bin/ (cloned next to wolfSPDM)" - echo " 3. spdm_responder_emu in PATH" -} - -# Parse arguments -for arg in "$@"; do - case "$arg" in - -h|--help) - usage - exit 0 - ;; - *) - # Treat as path to spdm_demo - SPDM_DEMO="$arg" - ;; - esac -done - -# Find spdm_responder_emu -find_emu() { - # 1. Check SPDM_EMU_PATH - if [ -n "$SPDM_EMU_PATH" ]; then - if [ -x "$SPDM_EMU_PATH/spdm_responder_emu" ]; then - EMU_DIR="$SPDM_EMU_PATH" - EMU_BIN="$SPDM_EMU_PATH/spdm_responder_emu" - return 0 - elif [ -x "$SPDM_EMU_PATH" ]; then - EMU_DIR="$(dirname "$SPDM_EMU_PATH")" - EMU_BIN="$SPDM_EMU_PATH" - return 0 - fi - fi - - # 2. Check common relative paths - for dir in \ - "../spdm-emu/build/bin" \ - "../../spdm-emu/build/bin" \ - "$HOME/spdm-emu/build/bin"; do - if [ -x "$dir/spdm_responder_emu" ]; then - EMU_DIR="$dir" - EMU_BIN="$dir/spdm_responder_emu" - return 0 - fi - done - - # 3. Check PATH - if command -v spdm_responder_emu >/dev/null 2>&1; then - EMU_BIN="$(command -v spdm_responder_emu)" - EMU_DIR="$(dirname "$EMU_BIN")" - return 0 - fi - - return 1 -} - -# Start the emulator (must run from its bin dir for cert files) -# Usage: start_emu [version] -start_emu() { - local ver="${1:-1.2}" - echo " Starting spdm_responder_emu (SPDM $ver)..." - - # Reap any emulator we started ourselves earlier (do NOT kill unrelated - # spdm_responder_emu instances - a developer may have one in another - # shell). Only the previous $EMU_PID is fair game. - if [ -n "$EMU_PID" ] && kill -0 "$EMU_PID" 2>/dev/null; then - kill -9 "$EMU_PID" 2>/dev/null - wait "$EMU_PID" 2>/dev/null - EMU_PID="" - sleep 1 - fi - - # If port 2323 is still occupied, it isn't ours - surface that clearly - # rather than kicking the unrelated holder off the port. Try ss, then - # netstat, then lsof - skip the check (with a warning) if none exist. - if command -v ss >/dev/null 2>&1; then - if ss -tlnp 2>/dev/null | grep -q ":2323 "; then - echo -e " ${RED}ERROR: Port 2323 already in use by another process${NC}" - ss -tlnp 2>/dev/null | grep ":2323 " - return 1 - fi - elif command -v netstat >/dev/null 2>&1; then - if netstat -tlnp 2>/dev/null | grep -q ":2323 "; then - echo -e " ${RED}ERROR: Port 2323 already in use by another process${NC}" - netstat -tlnp 2>/dev/null | grep ":2323 " - return 1 - fi - elif command -v lsof >/dev/null 2>&1; then - if lsof -iTCP:2323 -sTCP:LISTEN >/dev/null 2>&1; then - echo -e " ${RED}ERROR: Port 2323 already in use by another process${NC}" - lsof -iTCP:2323 -sTCP:LISTEN - return 1 - fi - else - echo -e " ${YELLOW}WARNING: ss/netstat/lsof unavailable - skipping port-in-use check${NC}" - fi - - # Verify cert/key files exist in EMU_DIR (spdm-emu uses lowercase 'ecp384') - if [ ! -d "$EMU_DIR/ecp384" ] && [ ! -d "$EMU_DIR/EcP384" ]; then - echo -e " ${YELLOW}WARNING: Certificate files may be missing in $EMU_DIR${NC}" - echo " Run 'make copy_sample_key' in the spdm-emu build directory" - fi - - (cd "$EMU_DIR" && ./spdm_responder_emu --ver "$ver" \ - --hash SHA_384 --asym ECDSA_P384 \ - --dhe SECP_384_R1 --aead AES_256_GCM >"$EMU_LOG" 2>&1) & - EMU_PID=$! - sleep 2 - - # Verify it started - if ! kill -0 "$EMU_PID" 2>/dev/null; then - echo -e " ${RED}ERROR: Emulator failed to start${NC}" - if [ -s "$EMU_LOG" ]; then - echo " Emulator output:" - sed 's/^/ /' "$EMU_LOG" | head -20 - fi - EMU_PID="" - return 1 - fi - return 0 -} - -# Stop the emulator -stop_emu() { - if [ -n "$EMU_PID" ]; then - kill "$EMU_PID" 2>/dev/null - wait "$EMU_PID" 2>/dev/null - EMU_PID="" - fi -} - -# Cleanup on exit -cleanup() { - stop_emu - rm -f "$EMU_LOG" -} -trap cleanup EXIT - -# Run a test (start/stop emulator around each test) -# Usage: run_test -run_test() { - local name="$1" - local emu_ver="$2" - shift 2 - - TOTAL=$((TOTAL + 1)) - echo "[$TOTAL] $name" - - if ! start_emu "$emu_ver"; then - echo -e " ${RED}FAIL (emulator start)${NC}" - FAIL=$((FAIL + 1)) - echo "" - return 1 - fi - - if "$@"; then - echo -e " ${GREEN}PASS${NC}" - PASS=$((PASS + 1)) - else - echo -e " ${RED}FAIL${NC}" - FAIL=$((FAIL + 1)) - fi - - stop_emu - sleep 1 # Let port release - echo "" -} - -# Check spdm_demo exists -if [ ! -x "$SPDM_DEMO" ]; then - echo "Error: $SPDM_DEMO not found or not executable" - usage - exit 1 -fi - -# ========================================================================== -# Emulator Tests -# ========================================================================== -echo "=== SPDM Emulator Tests ===" - -if ! find_emu; then - echo -e "${RED}ERROR: spdm_responder_emu not found${NC}" - echo "" - echo "Set SPDM_EMU_PATH or clone spdm-emu next to wolfSPDM:" - echo " git clone https://github.com/DMTF/spdm-emu.git ../spdm-emu" - echo " cd ../spdm-emu && mkdir build && cd build" - echo " cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=mbedtls .." - echo " make copy_sample_key && make" - exit 1 -fi - -echo "Using emulator: $EMU_BIN" -echo "Using demo: $SPDM_DEMO" -echo "" - -# Test each SPDM version (1.2, 1.3, 1.4) against the emulator -for VER in 1.2 1.3 1.4; do - echo "--- SPDM $VER ---" - - # Session establishment - run_test "Session (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --emu --ver "$VER" - - # Session + signed measurements - run_test "Signed measurements (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --meas --ver "$VER" - - # Session + unsigned measurements - run_test "Unsigned measurements (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --meas --no-sig --ver "$VER" - - # Challenge authentication (sessionless) - run_test "Challenge (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --challenge --ver "$VER" - - # Session + heartbeat - run_test "Heartbeat (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --emu --heartbeat --ver "$VER" - - # Session + key update - run_test "Key update (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --emu --key-update --ver "$VER" - - echo "" -done - -# ========================================================================== -# Summary -# ========================================================================== -echo "=== Results ===" -echo "Total: $TOTAL Passed: $PASS Failed: $FAIL" -if [ $FAIL -eq 0 ]; then - echo -e "${GREEN}ALL TESTS PASSED${NC}" - exit 0 -else - echo -e "${RED}$FAIL TEST(S) FAILED${NC}" - exit 1 -fi diff --git a/scripts/wolftpm-overlay.sh b/scripts/wolftpm-overlay.sh new file mode 100755 index 0000000..0ebdcbe --- /dev/null +++ b/scripts/wolftpm-overlay.sh @@ -0,0 +1,40 @@ +#!/bin/sh +# Replace a wolfTPM checkout's embedded SPDM sources with this wolfSPDM tree +# so wolfTPM's own SPDM tests run against it. +set -e + +usage() { + echo "usage: $0 " >&2 + exit 1 +} + +[ -n "$1" ] || usage +TPM=$1 +SPDM=$(cd "$(dirname "$0")/.." && pwd) + +[ -d "$TPM/src/spdm" ] && [ -d "$TPM/wolftpm/spdm" ] || { + echo "error: $TPM does not look like a wolfTPM tree with src/spdm" >&2 + exit 1 +} + +rm -f "$TPM"/src/spdm/spdm_*.c "$TPM"/src/spdm/spdm_internal.h +cp "$SPDM"/src/spdm_*.c "$SPDM"/src/vendor/spdm_*.c \ + "$SPDM"/src/spdm_internal.h "$TPM"/src/spdm/ +cp "$SPDM"/test/unit_test.c "$TPM"/src/spdm/unit_test.c + +mkdir -p "$TPM"/wolfspdm +cp "$SPDM"/wolfspdm/spdm*.h "$TPM"/wolfspdm/ +for h in "$SPDM"/wolfspdm/spdm*.h; do + b=$(basename "$h") + printf '#include \n' "$b" > "$TPM/wolftpm/spdm/$b" +done + +cat >> "$TPM/wolftpm/spdm/spdm_nations.h" <<'EOF' +#ifdef WOLFSPDM_NATIONS + #define TPM_CC_Nations_SpdmIdentityKeySet (0x20000708) + #define TPM_PT_VENDOR_NATIONS_FIPS_SL2 (TPM_PT_VENDOR + 11) + #define TPM_PT_VENDOR_NATIONS_IDENTITY_KEY (TPM_PT_VENDOR + 12) +#endif +EOF + +echo "wolfSPDM overlaid onto $TPM" diff --git a/src/spdm_chunk.c b/src/spdm_chunk.c deleted file mode 100644 index a4b2029..0000000 --- a/src/spdm_chunk.c +++ /dev/null @@ -1,209 +0,0 @@ -/* spdm_chunk.c - * - * Copyright (C) 2006-2025 wolfSSL Inc. - * - * This file is part of wolfSPDM. - * - * wolfSPDM is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * wolfSPDM is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA - */ - -#include "spdm_internal.h" - -#ifdef WOLFSPDM_HAVE_CHUNK - -/* DSP0274 Sec. 10.27.2: CHUNK_GET request. - * header: Param1 = Reserved, Param2 = Handle - * ChunkSeqNo: u16 for SPDM < 1.4, u32 for >= 1.4 */ -int wolfSPDM_BuildChunkGet(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - byte handle, word32 seqNo) -{ - word32 need = (ctx != NULL && ctx->spdmVersion >= SPDM_VERSION_14) ? 8 : 6; - - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, need); - - buf[0] = ctx->spdmVersion; - buf[1] = SPDM_CHUNK_GET; - buf[2] = 0x00; /* Param1 reserved */ - buf[3] = handle; /* Param2 = Handle */ - if (ctx->spdmVersion >= SPDM_VERSION_14) { - SPDM_Set32LE(&buf[4], seqNo); - } - else { - SPDM_Set16LE(&buf[4], (word16)seqNo); - } - *bufSz = need; - return WOLFSPDM_SUCCESS; -} - -/* Cleartext transport for one CHUNK_GET -> CHUNK_RESPONSE into ctx->chunkBuf. - * Uses the bare callback so the SendReceive chunk hook does not re-enter. */ -static int wolfSPDM_ChunkXferCleartext(WOLFSPDM_CTX* ctx, - const byte* tx, word32 txSz, word32* rxSz) -{ - *rxSz = (word32)sizeof(ctx->chunkBuf); - return wolfSPDM_SendReceiveRaw(ctx, tx, txSz, ctx->chunkBuf, rxSz); -} - -#ifndef WOLFSPDM_CHUNK_NO_SECURED -/* Secured (in-session) transport: encrypt CHUNK_GET, send, decrypt the - * CHUNK_RESPONSE into ctx->chunkBuf. The on-stack encrypted buffers scale with - * the MTU knob WOLFSPDM_CHUNK_BUF_SIZE. */ -static int wolfSPDM_ChunkXferSecured(WOLFSPDM_CTX* ctx, - const byte* tx, word32 txSz, word32* rxSz) -{ - byte enc[64]; /* CHUNK_GET is <= 8 B + AEAD */ - byte encRx[WOLFSPDM_CHUNK_BUF_SIZE + 64]; /* encrypted CHUNK_RESPONSE */ - word32 encSz = sizeof(enc); - word32 encRxSz = sizeof(encRx); - int rc = wolfSPDM_EncryptInternal(ctx, tx, txSz, enc, &encSz); - - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_SendReceiveRaw(ctx, enc, encSz, encRx, &encRxSz); - } - if (rc == WOLFSPDM_SUCCESS) { - *rxSz = (word32)sizeof(ctx->chunkBuf); - rc = wolfSPDM_DecryptInternal(ctx, encRx, encRxSz, ctx->chunkBuf, rxSz); - } - return rc; -} -#endif /* !WOLFSPDM_CHUNK_NO_SECURED */ - -/* Reassemble a large response the responder split (DSP0274 Sec. 10.27.2). - * The triggering ERROR(LargeResponse) carried the Handle; this drives the - * CHUNK_GET loop, copying each chunk into the caller's outBuf until LastChunk. - * - * CHUNK_RESPONSE layout (data offsets are version-independent): - * [0..3] header (Param1 = attributes, Param2 = Handle) - * [4..7] ChunkSeqNo (u32 for 1.4; u16 + u16 reserved for < 1.4) - * [8..11] ChunkSize (u32) - * [12..15] LargeMessageSize (u32, only when ChunkSeqNo == 0) - * [12 or 16 ..] chunk bytes */ -int wolfSPDM_ReassembleLargeResponse(WOLFSPDM_CTX* ctx, int secured, - byte handle, byte* outBuf, word32 outBufSz, word32* outSz) -{ - byte txBuf[8]; - word32 txSz; - word32 rxSz = 0; - word32 seq = 0; - word32 off = 0; - word32 total = 0; - word32 chunkSize; - word32 dataOff; - word32 seqEcho; - word32 minSz; - byte attrs; - int last = 0; - int rc; - - if (ctx == NULL || outBuf == NULL || outSz == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - /* The < 1.4 CHUNK_GET is 6 bytes; zero the buffer so the trailing two bytes - * are deterministic rather than stale stack. */ - XMEMSET(txBuf, 0, sizeof(txBuf)); - - while (!last) { - if (seq >= WOLFSPDM_CHUNK_MAX_CHUNKS) { - wolfSPDM_DebugPrint(ctx, "CHUNK: exceeded max chunks (%u)\n", - (unsigned)WOLFSPDM_CHUNK_MAX_CHUNKS); - return WOLFSPDM_E_CHUNK; - } - - txSz = sizeof(txBuf); - rc = wolfSPDM_BuildChunkGet(ctx, txBuf, &txSz, handle, seq); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - if (secured) { -#ifndef WOLFSPDM_CHUNK_NO_SECURED - rc = wolfSPDM_ChunkXferSecured(ctx, txBuf, txSz, &rxSz); -#else - return WOLFSPDM_E_CHUNK; /* secured chunking compiled out */ -#endif - } - else { - rc = wolfSPDM_ChunkXferCleartext(ctx, txBuf, txSz, &rxSz); - } - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* A mid-stream ERROR (e.g. the responder aborting the transfer) is a - * valid 4-byte response; surface its code before the CHUNK_RESPONSE - * length checks. */ - if (rxSz >= 4 && ctx->chunkBuf[1] == SPDM_ERROR) { - ctx->lastPeerErrorCode = ctx->chunkBuf[2]; - wolfSPDM_DebugPrint(ctx, "CHUNK: responder ERROR 0x%02x\n", - ctx->chunkBuf[2]); - return WOLFSPDM_E_PEER_ERROR; - } - /* Minimum CHUNK_RESPONSE: header(4)+seq(4)+size(4); the first chunk - * (seq 0) also carries LargeMessageSize, so require 4 more before - * reading it. */ - minSz = (seq == 0) ? 16u : 12u; - if (rxSz < minSz) { - return WOLFSPDM_E_CHUNK; - } - if (ctx->chunkBuf[1] != SPDM_CHUNK_RESPONSE || - ctx->chunkBuf[3] != handle) { - return WOLFSPDM_E_CHUNK; - } - attrs = ctx->chunkBuf[2]; - seqEcho = (ctx->spdmVersion >= SPDM_VERSION_14) - ? SPDM_Get32LE(&ctx->chunkBuf[4]) - : (word32)SPDM_Get16LE(&ctx->chunkBuf[4]); - if (seqEcho != seq) { - return WOLFSPDM_E_CHUNK; - } - - chunkSize = SPDM_Get32LE(&ctx->chunkBuf[8]); - dataOff = 12; - if (seq == 0) { - total = SPDM_Get32LE(&ctx->chunkBuf[12]); - dataOff = 16; - if (total == 0 || total > outBufSz) { - wolfSPDM_DebugPrint(ctx, - "CHUNK: LargeMessageSize %u exceeds buffer %u\n", - (unsigned)total, (unsigned)outBufSz); - return WOLFSPDM_E_BUFFER_SMALL; - } - } - - /* chunkSize is fully responder-controlled. Validate with subtraction so - * an oversized value cannot wrap an addition: dataOff <= rxSz (minSz) - * and off <= total hold by construction, so the differences are safe. */ - if (chunkSize == 0 || - chunkSize > rxSz - dataOff || - chunkSize > total - off) { - return WOLFSPDM_E_CHUNK; - } - XMEMCPY(outBuf + off, &ctx->chunkBuf[dataOff], chunkSize); - off += chunkSize; - - last = (attrs & SPDM_CHUNK_LAST_CHUNK) != 0; - seq++; - } - - if (off != total) { - return WOLFSPDM_E_CHUNK; - } - *outSz = total; - wolfSPDM_DebugPrint(ctx, "CHUNK: reassembled %u bytes in %u chunk(s)\n", - (unsigned)total, (unsigned)seq); - return WOLFSPDM_SUCCESS; -} - -#endif /* WOLFSPDM_HAVE_CHUNK */ diff --git a/src/spdm_context.c b/src/spdm_context.c index 490c31e..af34fa6 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -1,6 +1,6 @@ /* spdm_context.c * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -19,47 +19,25 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + #include "spdm_internal.h" #include #include -/* --- Context Management --- */ - -/* Wipe every long-lived session-key field. Used by Disconnect, ConnectStandard - * reset, and anywhere derived material from a prior session must not leak - * into the next. */ -static void wolfSPDM_WipeSessionKeys(WOLFSPDM_CTX* ctx) -{ - wc_ForceZero(ctx->sharedSecret, sizeof(ctx->sharedSecret)); - wc_ForceZero(ctx->handshakeSecret, sizeof(ctx->handshakeSecret)); - wc_ForceZero(ctx->reqHsSecret, sizeof(ctx->reqHsSecret)); - wc_ForceZero(ctx->rspHsSecret, sizeof(ctx->rspHsSecret)); - wc_ForceZero(ctx->reqFinishedKey, sizeof(ctx->reqFinishedKey)); - wc_ForceZero(ctx->rspFinishedKey, sizeof(ctx->rspFinishedKey)); - wc_ForceZero(ctx->reqDataKey, sizeof(ctx->reqDataKey)); - wc_ForceZero(ctx->rspDataKey, sizeof(ctx->rspDataKey)); - wc_ForceZero(ctx->reqDataIv, sizeof(ctx->reqDataIv)); - wc_ForceZero(ctx->rspDataIv, sizeof(ctx->rspDataIv)); - wc_ForceZero(ctx->reqAppSecret, sizeof(ctx->reqAppSecret)); - wc_ForceZero(ctx->rspAppSecret, sizeof(ctx->rspAppSecret)); - wc_ForceZero(ctx->th1, sizeof(ctx->th1)); - ctx->sharedSecretSz = 0; -} +/* ----- Context Management ----- */ int wolfSPDM_Init(WOLFSPDM_CTX* ctx) { int rc; - word16 sid; if (ctx == NULL) { return WOLFSPDM_E_INVALID_ARG; } - /* Clean slate - do NOT read any fields before this (could be garbage). - * Callers must wolfSPDM_Free before re-initializing an existing ctx; - * skipping that step leaks the wolfCrypt RNG/ECC/SHA states opened by - * the prior Init. We cannot reliably detect that from inside Init - * (the flag byte is itself part of the garbage we are about to wipe). */ + /* Clean slate, dont read fields before this */ XMEMSET(ctx, 0, sizeof(WOLFSPDM_CTX)); ctx->state = WOLFSPDM_STATE_INIT; @@ -70,31 +48,11 @@ int wolfSPDM_Init(WOLFSPDM_CTX* ctx) } ctx->flags.rngInitialized = 1; - /* Set default requester capabilities */ - ctx->reqCaps = WOLFSPDM_DEFAULT_REQ_CAPS; - - /* Key-exchange advertisement: DHE always; ML-KEM (all sets, at 1.4) - * dual-stack alongside it by default. wolfSPDM_SetKeyExchangePref overrides. */ - ctx->kexAdvDhe = 1; -#ifdef WOLFSPDM_HAVE_MLKEM - ctx->kexAdvKem = (word16)(SPDM_KEM_ALGO_ML_KEM_512 | - SPDM_KEM_ALGO_ML_KEM_768 | - SPDM_KEM_ALGO_ML_KEM_1024); -#endif - - /* Pick a random, non-reserved ReqSessionID (DSP0277 reserves 0x0000 and - * 0xFFFF). Callers needing determinism can override via - * wolfSPDM_SetRequesterSessionId. */ - do { - if (wc_RNG_GenerateBlock(&ctx->rng, (byte*)&sid, sizeof(sid)) != 0) { - sid = 0x0001; /* RNG failed; fall back to legacy default */ - break; - } - } while (sid == 0x0000 || sid == 0xFFFF); - ctx->reqSessionId = sid; + /* Set default session ID (0x0001 is valid; 0x0000/0xFFFF are reserved) */ + ctx->reqSessionId = 0x0001; ctx->flags.initialized = 1; - /* isDynamic remains 0 - only wolfSPDM_New sets it */ + /* isDynamic remains 0, only wolfSPDM_New sets it */ return WOLFSPDM_SUCCESS; } @@ -122,18 +80,13 @@ WOLFSPDM_CTX* wolfSPDM_New(void) void wolfSPDM_Free(WOLFSPDM_CTX* ctx) { -#ifdef WOLFSPDM_DYNAMIC_MEMORY int wasDynamic; -#endif if (ctx == NULL) { return; } -#ifdef WOLFSPDM_DYNAMIC_MEMORY - /* Capture before wc_ForceZero wipes ctx->flags. */ wasDynamic = ctx->flags.isDynamic; -#endif /* Free RNG */ if (ctx->flags.rngInitialized) { @@ -142,22 +95,9 @@ void wolfSPDM_Free(WOLFSPDM_CTX* ctx) /* Free ephemeral key */ if (ctx->flags.ephemeralKeyInit) { - wolfSPDM_FreeEphemeralKey(ctx); - } - - /* Free responder public key (used for measurement/challenge verification) */ - if (ctx->flags.hasResponderPubKey) { - wolfSPDM_FreeResponderPubKey(ctx); + wc_ecc_free(&ctx->ephemeralKey); } -#ifndef NO_WOLFSPDM_CHALLENGE - /* Free M1/M2 challenge hash if still initialized */ - if (ctx->flags.m1m2HashInit) { - wc_Sha384Free(&ctx->m1m2Hash); - ctx->flags.m1m2HashInit = 0; - } -#endif - /* Zero entire struct (covers all sensitive key material) */ wc_ForceZero(ctx, sizeof(WOLFSPDM_CTX)); @@ -165,6 +105,8 @@ void wolfSPDM_Free(WOLFSPDM_CTX* ctx) if (wasDynamic) { XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); } +#else + (void)wasDynamic; #endif } @@ -173,18 +115,6 @@ int wolfSPDM_GetCtxSize(void) return (int)sizeof(WOLFSPDM_CTX); } -/* Catch struct growth past the public WOLFSPDM_CTX_STATIC_SIZE at compile - * time rather than at wolfSPDM_InitStatic runtime. Negative array size if - * the static buffer is no longer sufficient. */ -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L -_Static_assert(sizeof(struct WOLFSPDM_CTX) <= WOLFSPDM_CTX_STATIC_SIZE, - "WOLFSPDM_CTX_STATIC_SIZE must be >= sizeof(struct WOLFSPDM_CTX); " - "bump the public macro in wolfspdm/spdm.h"); -#else -typedef char wolfSPDM_ctx_static_size_check - [(sizeof(struct WOLFSPDM_CTX) <= WOLFSPDM_CTX_STATIC_SIZE) ? 1 : -1]; -#endif - int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size) { if (ctx == NULL) { @@ -198,7 +128,7 @@ int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size) return wolfSPDM_Init(ctx); } -/* --- Configuration --- */ +/* ----- Configuration ----- */ int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx) { @@ -212,113 +142,121 @@ int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx) return WOLFSPDM_SUCCESS; } -int wolfSPDM_SetTrustedCAs(WOLFSPDM_CTX* ctx, const byte* derCerts, - word32 derCertsSz) +int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz) { - if (ctx == NULL || derCerts == NULL || derCertsSz == 0) { + if (ctx == NULL || pubKey == NULL) { return WOLFSPDM_E_INVALID_ARG; } - if (derCertsSz > WOLFSPDM_MAX_TRUSTED_CA) { - return WOLFSPDM_E_BUFFER_SMALL; + if (pubKeySz != WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_INVALID_ARG; } - XMEMCPY(ctx->trustedCAs, derCerts, derCertsSz); - ctx->trustedCAsSz = derCertsSz; - ctx->flags.hasTrustedCAs = 1; + XMEMCPY(ctx->rspPubKey, pubKey, pubKeySz); + ctx->rspPubKeyLen = pubKeySz; + ctx->flags.hasRspPubKey = 1; return WOLFSPDM_SUCCESS; } -void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable) +int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, + const byte* privKey, word32 privKeySz, + const byte* pubKey, word32 pubKeySz) { - if (ctx != NULL) { - ctx->flags.debug = enable ? 1 : 0; + if (ctx == NULL || privKey == NULL || pubKey == NULL) { + return WOLFSPDM_E_INVALID_ARG; } -} -byte wolfSPDM_GetLastPeerError(WOLFSPDM_CTX* ctx) -{ - return (ctx != NULL) ? ctx->lastPeerErrorCode : 0; -} + if (privKeySz != WOLFSPDM_ECC_KEY_SIZE || + pubKeySz != WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } -/* Backwards-compat: the old function name from before the rename to - * wolfSPDM_GetNegotiatedVersion. Kept so binaries already linked against - * the old symbol still resolve. */ -byte wolfSPDM_GetVersion_Negotiated(WOLFSPDM_CTX* ctx) -{ - return wolfSPDM_GetNegotiatedVersion(ctx); + XMEMCPY(ctx->reqPrivKey, privKey, privKeySz); + ctx->reqPrivKeyLen = privKeySz; + XMEMCPY(ctx->reqPubKey, pubKey, pubKeySz); + ctx->flags.hasReqKeyPair = 1; + + return WOLFSPDM_SUCCESS; } -int wolfSPDM_SetRequesterSessionId(WOLFSPDM_CTX* ctx, word16 reqSessionId) +#ifdef WOLFSPDM_TCG +int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz) { - if (ctx == NULL) { + if (ctx == NULL || tpmtPub == NULL) { return WOLFSPDM_E_INVALID_ARG; } - /* DSP0277: 0x0000 and 0xFFFF are reserved and shall not appear on wire. */ - if (reqSessionId == 0x0000 || reqSessionId == 0xFFFF) { + if (tpmtPubSz > sizeof(ctx->reqPubKeyTPMT)) { return WOLFSPDM_E_INVALID_ARG; } - ctx->reqSessionId = reqSessionId; + XMEMCPY(ctx->reqPubKeyTPMT, tpmtPub, tpmtPubSz); + ctx->reqPubKeyTPMTLen = tpmtPubSz; return WOLFSPDM_SUCCESS; } +#endif /* WOLFSPDM_TCG */ -int wolfSPDM_AllowUntrustedCerts(WOLFSPDM_CTX* ctx, int allow) +/* wolfSPDM_SetPSK moved to spdm_psk.c */ + +void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable) { - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; + if (ctx != NULL) { + ctx->flags.debug = (enable != 0); } - ctx->flags.allowUntrustedCert = allow ? 1 : 0; - return WOLFSPDM_SUCCESS; } -int wolfSPDM_SetMaxVersion(WOLFSPDM_CTX* ctx, byte maxVersion) +int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode) { if (ctx == NULL) { return WOLFSPDM_E_INVALID_ARG; } - /* 0 means reset to compile-time default */ - if (maxVersion == 0) { - ctx->maxVersion = 0; +#if !defined(WOLFSPDM_NUVOTON) && !defined(WOLFSPDM_NATIONS) && \ + !defined(WOLFSPDM_PSK) + (void)mode; +#endif + +#ifdef WOLFSPDM_NUVOTON + if (mode == WOLFSPDM_MODE_NUVOTON) { + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ctx->connectionHandle = WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT; + ctx->fipsIndicator = WOLFSPDM_NUVOTON_FIPS_DEFAULT; return WOLFSPDM_SUCCESS; } - - /* Validate range. WOLFSPDM_MAX_SPDM_VERSION is the build-time ceiling - * and is authoritative: the runtime setter cannot raise it. */ - if (maxVersion < WOLFSPDM_MIN_SPDM_VERSION || - maxVersion > WOLFSPDM_MAX_SPDM_VERSION) { - return WOLFSPDM_E_INVALID_ARG; +#endif +#ifdef WOLFSPDM_NATIONS + if (mode == WOLFSPDM_MODE_NATIONS) { + ctx->mode = WOLFSPDM_MODE_NATIONS; + ctx->connectionHandle = 0; + /* Default to NON_FIPS; overridden by auto-detect if FIPS configured */ + ctx->fipsIndicator = WOLFSPDM_FIPS_NON_FIPS; + return WOLFSPDM_SUCCESS; } +#endif +#ifdef WOLFSPDM_PSK + /* Spec-pure PSK mode - DSP0274 handshake. Available whenever the PSK + * feature is built, independent of any vendor adapter. */ + if (mode == WOLFSPDM_MODE_NATIONS_PSK) { + ctx->mode = WOLFSPDM_MODE_NATIONS_PSK; + ctx->connectionHandle = 0; + ctx->fipsIndicator = WOLFSPDM_FIPS_NON_FIPS; + return WOLFSPDM_SUCCESS; + } +#endif - ctx->maxVersion = maxVersion; - return WOLFSPDM_SUCCESS; + return WOLFSPDM_E_INVALID_ARG; /* Unsupported mode */ } -int wolfSPDM_SetKeyExchangePref(WOLFSPDM_CTX* ctx, int advDhe, word16 kemMask) +WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx) { if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; + return WOLFSPDM_MODE_AUTO; } - if (advDhe == 0 && kemMask == 0) { - return WOLFSPDM_E_INVALID_ARG; /* must advertise at least one method */ - } - if ((kemMask & ~(word16)(SPDM_KEM_ALGO_ML_KEM_512 | - SPDM_KEM_ALGO_ML_KEM_768 | - SPDM_KEM_ALGO_ML_KEM_1024)) != 0) { - return WOLFSPDM_E_INVALID_ARG; /* undefined ML-KEM bit(s) */ - } -#ifndef WOLFSPDM_HAVE_MLKEM - if (kemMask != 0) { - return WOLFSPDM_E_INVALID_ARG; /* ML-KEM not built in */ - } -#endif - ctx->kexAdvDhe = (byte)(advDhe != 0); - ctx->kexAdvKem = kemMask; - return WOLFSPDM_SUCCESS; + return ctx->mode; } -/* --- Session Status --- */ +/* ----- Session Status ----- */ int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx) { @@ -330,14 +268,7 @@ int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx) word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx) { - /* Return the negotiated session ID once KEY_EXCHANGE_RSP has set it - * (I/O callbacks need it between KEY_EXCHANGE and FINISH to tag the - * encrypted FINISH record). Restrict the exposure window to states - * where the value is actually meaningful: from KEY_EX through CONNECTED - * / MEASURED. Pre-KEY_EX or in the error state, return 0 so callers - * that test "GetSessionId() != 0" don't see a stale or transitional id. */ - if (ctx == NULL || ctx->state < WOLFSPDM_STATE_KEY_EX || - ctx->state == WOLFSPDM_STATE_ERROR) { + if (ctx == NULL || ctx->state != WOLFSPDM_STATE_CONNECTED) { return 0; } return ctx->sessionId; @@ -351,105 +282,25 @@ byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx) return ctx->spdmVersion; } -/* --- Session Establishment - Connect (Full Handshake) --- */ - -/* Standard SPDM 1.2 connection flow (for libspdm emulator, etc.) */ -static int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx) +#ifdef WOLFSPDM_TCG +word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx) { - int rc; - int slot; - int i; - - /* Reset state for new connection. Drop any cached responder public - * key from a prior attempt - GetCertificate's guard otherwise skips - * re-extraction, and KEY_EXCHANGE_RSP signature verification would - * then run against the stale key from the previous responder. Also - * clear sessionId / seqNums so a partial prior attempt can't leak - * state into the new handshake. */ - if (ctx->flags.hasResponderPubKey) { - wolfSPDM_FreeResponderPubKey(ctx); - ctx->flags.hasResponderPubKey = 0; - } - /* Wipe derived key material from any prior session before starting a - * fresh handshake. If this new handshake fails before - * wolfSPDM_DeriveHandshakeKeys overwrites the fields, the prior - * session's secrets must not linger in the context. */ - wolfSPDM_WipeSessionKeys(ctx); - ctx->state = WOLFSPDM_STATE_INIT; - ctx->sessionId = 0; - /* Preserve caller-set reqSessionId from Init / SetRequesterSessionId. */ - ctx->rspSessionId = 0; - ctx->reqSeqNum = 0; - ctx->rspSeqNum = 0; - ctx->lastPeerErrorCode = 0; - ctx->slotMask = 0; - ctx->currentSlotId = 0; -#ifndef NO_WOLFSPDM_MEAS - /* Drop stale measurement state from a prior connect so reconnect- - * without-disconnect doesn't surface old blocks. */ - ctx->measBlockCount = 0; - ctx->measSignatureSize = 0; - ctx->flags.hasMeasurements = 0; -#endif - wolfSPDM_TranscriptReset(ctx); - - SPDM_CONNECT_STEP(ctx, "Step 1: GET_VERSION\n", - wolfSPDM_GetVersion(ctx)); - SPDM_CONNECT_STEP(ctx, "Step 2: GET_CAPABILITIES\n", - wolfSPDM_GetCapabilities(ctx)); - SPDM_CONNECT_STEP(ctx, "Step 3: NEGOTIATE_ALGORITHMS\n", - wolfSPDM_NegotiateAlgorithms(ctx)); - SPDM_CONNECT_STEP(ctx, "Step 4: GET_DIGESTS\n", - wolfSPDM_GetDigests(ctx)); - - /* DSP0274 Sec. 10.5: pick the lowest-numbered slot the responder said - * is populated (DIGESTS Param1 SlotMask). Fall back to slot 0 if the - * responder did not report a mask, matching the prior behavior. */ - slot = 0; - if (ctx->slotMask != 0) { - for (i = 0; i < 8; i++) { - if (ctx->slotMask & (1 << i)) { - slot = i; - break; - } - } + if (ctx == NULL) { + return 0; } - SPDM_CONNECT_STEP(ctx, "Step 5: GET_CERTIFICATE\n", - wolfSPDM_GetCertificate(ctx, slot)); + return ctx->connectionHandle; +} - /* Validate certificate chain if trusted CAs are loaded. GetCertificate - * already guarantees flags.hasResponderPubKey is set on success (returns - * an error otherwise), so we only need to gate on the CA-bundle. Fail - * closed by default: refuse to derive session keys against an - * unauthenticated responder unless the caller has explicitly opted - * into untrusted operation via wolfSPDM_AllowUntrustedCerts. */ - if (ctx->flags.hasTrustedCAs) { - SPDM_CONNECT_STEP(ctx, "Validating certificate chain\n", - wolfSPDM_ValidateCertChain(ctx)); - } - else if (!ctx->flags.allowUntrustedCert) { - wolfSPDM_DebugPrint(ctx, - "Refusing handshake: no trust anchor configured; call " - "wolfSPDM_SetTrustedCAs or wolfSPDM_AllowUntrustedCerts\n"); - ctx->state = WOLFSPDM_STATE_ERROR; - return WOLFSPDM_E_CERT_FAIL; - } - else { - wolfSPDM_DebugPrint(ctx, - "Warning: No trusted CAs loaded - chain not validated\n"); +word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return 0; } - - SPDM_CONNECT_STEP(ctx, "Step 6: KEY_EXCHANGE\n", - wolfSPDM_KeyExchange(ctx)); - SPDM_CONNECT_STEP(ctx, "Step 7: FINISH\n", - wolfSPDM_Finish(ctx)); - - ctx->state = WOLFSPDM_STATE_CONNECTED; - wolfSPDM_DebugPrint(ctx, "SPDM Session Established! SessionID=0x%08x\n", - ctx->sessionId); - - return WOLFSPDM_SUCCESS; + return ctx->fipsIndicator; } +#endif + +/* ----- Session Establishment - Connect (Full Handshake) ----- */ int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) { @@ -465,75 +316,91 @@ int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) return WOLFSPDM_E_IO_FAIL; } - return wolfSPDM_ConnectStandard(ctx); +#ifdef WOLFSPDM_TCG + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS) { + return wolfSPDM_ConnectTCG(ctx); + } +#endif +#ifdef WOLFSPDM_PSK + if (ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + return wolfSPDM_ConnectPsk(ctx); + } +#endif + + return WOLFSPDM_E_INVALID_ARG; /* Standard mode not available */ } int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) { - int rc = WOLFSPDM_SUCCESS; + int rc; byte txBuf[8]; byte rxBuf[16]; /* END_SESSION_ACK: 4 bytes */ word32 txSz, rxSz; - int sendEndSession; if (ctx == NULL) { return WOLFSPDM_E_INVALID_ARG; } - /* Only send END_SESSION when we actually have a connected secured - * channel. For partial-handshake failures (state below CONNECTED) we - * still want to wipe locally derived material on the way out. */ - sendEndSession = (ctx->state == WOLFSPDM_STATE_CONNECTED); + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } - if (sendEndSession) { - txSz = sizeof(txBuf); - rc = wolfSPDM_BuildEndSession(ctx, txBuf, &txSz); - if (rc == WOLFSPDM_SUCCESS) { - rxSz = sizeof(rxBuf); - rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + /* Build END_SESSION */ + txSz = sizeof(txBuf); + rc = wolfSPDM_BuildEndSession(ctx, txBuf, &txSz); + if (rc == WOLFSPDM_SUCCESS) { + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + if (rxSz < 4) { + rc = WOLFSPDM_E_BUFFER_SMALL; + } + else if (wolfSPDM_CheckError(rxBuf, rxSz, NULL)) { + rc = WOLFSPDM_E_PEER_ERROR; + } + else if (rxSz != 4 || rxBuf[0] != ctx->spdmVersion || + rxBuf[1] != SPDM_END_SESSION_ACK || + rxBuf[2] != 0 || rxBuf[3] != 0) { + rc = WOLFSPDM_E_PEER_ERROR; } } - /* Reset state regardless of result. Free the cached responder public - * key so the next Connect re-extracts it from the (potentially new) - * responder's certificate chain - otherwise KEY_EXCHANGE_RSP signature - * verification on the reconnect would run against the old key. */ - if (ctx->flags.hasResponderPubKey) { - wolfSPDM_FreeResponderPubKey(ctx); - ctx->flags.hasResponderPubKey = 0; - } - /* Wipe every long-lived session secret so disconnected contexts cannot - * be recovered for the duration before wolfSPDM_Free or a fresh - * Connect overwrites them. */ - wolfSPDM_WipeSessionKeys(ctx); + /* Reset session state and wipe session-scoped keys; configured identity + * keys remain for a later connection */ ctx->state = WOLFSPDM_STATE_INIT; ctx->sessionId = 0; - ctx->rspSessionId = 0; ctx->reqSeqNum = 0; ctx->rspSeqNum = 0; - ctx->lastPeerErrorCode = 0; - ctx->slotMask = 0; - ctx->currentSlotId = 0; -#ifndef NO_WOLFSPDM_MEAS - /* Drop stale measurement state so callers can't accidentally read - * blocks from the previous session after a reconnect. */ - ctx->measBlockCount = 0; - ctx->measSignatureSize = 0; - ctx->flags.hasMeasurements = 0; -#endif - - /* If we never had a session, the caller did not request a real - * Disconnect; surface that distinction without masking it as a - * successful teardown. */ - if (!sendEndSession) { - return WOLFSPDM_E_NOT_CONNECTED; + /* App data keys */ + wc_ForceZero(ctx->reqDataKey, sizeof(ctx->reqDataKey)); + wc_ForceZero(ctx->rspDataKey, sizeof(ctx->rspDataKey)); + wc_ForceZero(ctx->reqDataIv, sizeof(ctx->reqDataIv)); + wc_ForceZero(ctx->rspDataIv, sizeof(ctx->rspDataIv)); + /* Handshake keys */ + wc_ForceZero(ctx->reqHsSecret, sizeof(ctx->reqHsSecret)); + wc_ForceZero(ctx->rspHsSecret, sizeof(ctx->rspHsSecret)); + wc_ForceZero(ctx->reqFinishedKey, sizeof(ctx->reqFinishedKey)); + wc_ForceZero(ctx->rspFinishedKey, sizeof(ctx->rspFinishedKey)); + /* Secrets and hashes */ + wc_ForceZero(ctx->handshakeSecret, sizeof(ctx->handshakeSecret)); + wc_ForceZero(ctx->sharedSecret, sizeof(ctx->sharedSecret)); + ctx->sharedSecretSz = 0; + wc_ForceZero(ctx->th1, sizeof(ctx->th1)); + wc_ForceZero(ctx->th2, sizeof(ctx->th2)); + /* Free ephemeral ECC key */ + if (ctx->flags.ephemeralKeyInit) { + wc_ecc_free(&ctx->ephemeralKey); + ctx->flags.ephemeralKeyInit = 0; } + return rc; } -/* --- I/O Helper --- */ +/* ----- I/O Helper ----- */ -int wolfSPDM_SendReceiveRaw(WOLFSPDM_CTX* ctx, +int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz) { @@ -543,6 +410,100 @@ int wolfSPDM_SendReceiveRaw(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_IO_FAIL; } +#ifdef WOLFSPDM_TCG + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + /* Wrap messages with TCG SPDM + * headers; I/O sends TCG-framed messages. */ + byte tcgTx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + byte tcgRx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + word32 tcgRxSz = sizeof(tcgRx); + int tcgTxSz; + word32 msgSize; + word32 payloadSz; + word16 tag; + + /* Detect message type: SPDM version byte 0x10-0x1F = clear message. + * Secured records start with SessionID (LE, typically 0x01 0x00...), + * which is never in the SPDM version range. */ + if (txSz > 0 && txBuf[0] >= 0x10 && txBuf[0] <= 0x1F) { + /* Clear SPDM message - wrap with TCG clear header (0x8101) */ + tcgTxSz = wolfSPDM_BuildTcgClearMessage(ctx, txBuf, txSz, + tcgTx, sizeof(tcgTx)); + } else { + /* Secured record - prepend TCG secured header (0x8201) */ + word32 totalSz; + if (txSz > sizeof(tcgTx) - WOLFSPDM_TCG_HEADER_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + totalSz = WOLFSPDM_TCG_HEADER_SIZE + txSz; + wolfSPDM_WriteTcgHeader(tcgTx, WOLFSPDM_TCG_TAG_SECURED, + totalSz, ctx->connectionHandle, ctx->fipsIndicator); + XMEMCPY(tcgTx + WOLFSPDM_TCG_HEADER_SIZE, txBuf, txSz); + tcgTxSz = (int)totalSz; + } + + if (tcgTxSz < 0) { + return tcgTxSz; + } + + wolfSPDM_DebugHex(ctx, "TCG TX", tcgTx, (word32)tcgTxSz); + + /* Send/receive via I/O callback (raw transport) */ + rc = ctx->ioCb(ctx, tcgTx, (word32)tcgTxSz, tcgRx, &tcgRxSz, + ctx->ioUserCtx); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "TCG I/O failed: %d\n", rc); + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugHex(ctx, "TCG RX", tcgRx, tcgRxSz); + + /* Strip TCG binding header from response */ + if (tcgRxSz < WOLFSPDM_TCG_HEADER_SIZE) { + wolfSPDM_DebugPrint(ctx, "SendReceive: response too short (%u)\n", + tcgRxSz); + return WOLFSPDM_E_BUFFER_SMALL; + } + + tag = SPDM_Get16BE(tcgRx); + if (tag != WOLFSPDM_TCG_TAG_CLEAR && tag != WOLFSPDM_TCG_TAG_SECURED) { + wolfSPDM_DebugPrint(ctx, "SendReceive: unexpected TCG tag " + "0x%04x\n", tag); + return WOLFSPDM_E_PEER_ERROR; + } + + /* Capture FIPS indicator from response if non-zero */ + tag = SPDM_Get16BE(tcgRx + 10); + if (tag != 0) { + ctx->fipsIndicator = tag; + } + + /* Extract payload (everything after 16-byte TCG header) */ + msgSize = SPDM_Get32BE(tcgRx + 2); + + if (msgSize < WOLFSPDM_TCG_HEADER_SIZE || msgSize > tcgRxSz) { + wolfSPDM_DebugPrint(ctx, "SendReceive: TCG size %u invalid " + "(min=%u, received=%u)\n", msgSize, + WOLFSPDM_TCG_HEADER_SIZE, tcgRxSz); + return WOLFSPDM_E_BUFFER_SMALL; + } + + payloadSz = msgSize - WOLFSPDM_TCG_HEADER_SIZE; + if (payloadSz > *rxSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(rxBuf, tcgRx + WOLFSPDM_TCG_HEADER_SIZE, payloadSz); + *rxSz = payloadSz; + + return WOLFSPDM_SUCCESS; + } +#endif /* WOLFSPDM_TCG */ + rc = ctx->ioCb(ctx, txBuf, txSz, rxBuf, rxSz, ctx->ioUserCtx); if (rc != 0) { return WOLFSPDM_E_IO_FAIL; @@ -551,32 +512,8 @@ int wolfSPDM_SendReceiveRaw(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } -int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, - const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz) -{ -#ifdef WOLFSPDM_HAVE_CHUNK - word32 cap = (rxSz != NULL) ? *rxSz : 0; - byte handle = 0; -#endif - int rc = wolfSPDM_SendReceiveRaw(ctx, txBuf, txSz, rxBuf, rxSz); - -#ifdef WOLFSPDM_HAVE_CHUNK - /* Transparently reassemble a cleartext response the responder chunked - * (ERROR(LargeResponse)); the caller then parses the logical message. Only - * when the responder actually negotiated CHUNK_CAP, so a non-conformant - * LargeResponse fails fast rather than driving a doomed CHUNK_GET loop. */ - if (rc == WOLFSPDM_SUCCESS && rxSz != NULL && - (ctx->rspCaps & SPDM_CAP_CHUNK_CAP) != 0 && - wolfSPDM_IsLargeResponse(rxBuf, *rxSz, &handle)) { - rc = wolfSPDM_ReassembleLargeResponse(ctx, 0, handle, rxBuf, cap, rxSz); - } -#endif - return rc; -} - -/* --- Debug Utilities --- */ - +/* ----- Debug Utilities ----- */ +#ifdef WOLFSPDM_DEBUG void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) { va_list args; @@ -611,59 +548,9 @@ void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, printf("\n"); fflush(stdout); } +#endif -/* --- Measurement Accessors --- */ - -#ifndef NO_WOLFSPDM_MEAS - -int wolfSPDM_GetMeasurementCount(WOLFSPDM_CTX* ctx) -{ - if (ctx == NULL || !ctx->flags.hasMeasurements) { - return 0; - } - return (int)ctx->measBlockCount; -} - -int wolfSPDM_GetMeasurementBlock(WOLFSPDM_CTX* ctx, int blockIdx, - byte* measIndex, byte* measType, byte* value, word32* valueSz) -{ - const WOLFSPDM_MEAS_BLOCK* blk; - - if (ctx == NULL || !ctx->flags.hasMeasurements) { - return WOLFSPDM_E_INVALID_ARG; - } - if (blockIdx < 0 || blockIdx >= (int)ctx->measBlockCount) { - return WOLFSPDM_E_INVALID_ARG; - } - if (valueSz == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - blk = &ctx->measBlocks[blockIdx]; - - if (measIndex != NULL) { - *measIndex = blk->index; - } - if (measType != NULL) { - *measType = blk->dmtfType; - } - - if (value != NULL) { - word32 copySize = blk->valueSize; - if (copySize > *valueSz) { - copySize = *valueSz; - } - XMEMCPY(value, blk->value, copySize); - } - *valueSz = blk->valueSize; - - return WOLFSPDM_SUCCESS; -} - -#endif /* !NO_WOLFSPDM_MEAS */ - -/* --- Error String --- */ - +/* ----- Error String ----- */ const char* wolfSPDM_GetErrorString(int error) { switch (error) { @@ -683,18 +570,12 @@ const char* wolfSPDM_GetErrorString(int error) case WOLFSPDM_E_NOT_CONNECTED: return "Not connected"; case WOLFSPDM_E_ALREADY_INIT: return "Already initialized"; case WOLFSPDM_E_NO_MEMORY: return "Memory allocation failed"; - case WOLFSPDM_E_CERT_FAIL: return "Certificate error"; - case WOLFSPDM_E_CAPS_MISMATCH: return "Capability mismatch"; - case WOLFSPDM_E_ALGO_MISMATCH: return "Algorithm mismatch"; case WOLFSPDM_E_SESSION_INVALID: return "Invalid session"; case WOLFSPDM_E_KEY_EXCHANGE: return "Key exchange failed"; - case WOLFSPDM_E_MEASUREMENT: return "Measurement retrieval failed"; - case WOLFSPDM_E_MEAS_NOT_VERIFIED: return "Measurements not signature-verified"; - case WOLFSPDM_E_MEAS_SIG_FAIL: return "Measurement signature verification failed"; - case WOLFSPDM_E_CERT_PARSE: return "Failed to parse responder certificate"; - case WOLFSPDM_E_CHALLENGE: return "Challenge authentication failed"; - case WOLFSPDM_E_KEY_UPDATE: return "Key update failed"; - case WOLFSPDM_E_CHUNK: return "Large-response chunking failed"; + case WOLFSPDM_E_NOT_AVAILABLE: return "Feature not compiled in"; + case WOLFSPDM_E_FRAMING: return "Framing violation"; + case WOLFSPDM_E_NOT_IMPL: return "Not implemented"; default: return "Unknown error"; } } + diff --git a/src/spdm_crypto.c b/src/spdm_crypto.c index d7555f6..58cc303 100644 --- a/src/spdm_crypto.c +++ b/src/spdm_crypto.c @@ -1,6 +1,6 @@ /* spdm_crypto.c * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -19,27 +19,23 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + #include "spdm_internal.h" -/* Left-pad a buffer in-place to targetSz with leading zeros. - * Returns WOLFSPDM_E_CRYPTO_FAIL if currentSz exceeds targetSz - that - * would indicate a wolfCrypt routine wrote more bytes than the protocol - * allows (P-384 should never produce more than 48 raw bytes); silently - * truncating would hide an internal-invariant violation. */ -static int wolfSPDM_LeftPadToSize(byte* buf, word32 currentSz, word32 targetSz) +/* Left-pad a buffer in-place to targetSz with leading zeros */ +static void wolfSPDM_LeftPadToSize(byte* buf, word32 currentSz, word32 targetSz) { - if (currentSz > targetSz) { - return WOLFSPDM_E_CRYPTO_FAIL; - } if (currentSz < targetSz) { word32 padLen = targetSz - currentSz; XMEMMOVE(buf + padLen, buf, currentSz); XMEMSET(buf, 0, padLen); } - return WOLFSPDM_SUCCESS; } -/* --- Random Number Generation --- */ +/* ----- Random Number Generation ----- */ int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz) { @@ -61,7 +57,7 @@ int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz) return WOLFSPDM_SUCCESS; } -/* --- ECDHE Key Generation (P-384) --- */ +/* ----- ECDHE Key Generation (P-384) ----- */ int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx) { @@ -77,22 +73,28 @@ int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx) /* Free existing key if any */ if (ctx->flags.ephemeralKeyInit) { - wolfSPDM_FreeEphemeralKey(ctx); + wc_ecc_free(&ctx->ephemeralKey); ctx->flags.ephemeralKeyInit = 0; } - ctx->kexType = WOLFSPDM_KEX_ECDHE; /* Initialize new key */ - rc = wc_ecc_init(&ctx->ephemeralKey.ecc); + rc = wc_ecc_init(&ctx->ephemeralKey); if (rc != 0) { return WOLFSPDM_E_CRYPTO_FAIL; } /* Generate P-384 key pair */ - rc = wc_ecc_make_key(&ctx->rng, WOLFSPDM_ECC_KEY_SIZE, - &ctx->ephemeralKey.ecc); + rc = wc_ecc_make_key(&ctx->rng, WOLFSPDM_ECC_KEY_SIZE, &ctx->ephemeralKey); if (rc != 0) { - wc_ecc_free(&ctx->ephemeralKey.ecc); + wc_ecc_free(&ctx->ephemeralKey); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + /* Attach RNG so timing-resistant scalar-mul inside wc_ecc_shared_secret + * doesn't fail with MISSING_RNG_E in builds that enable hardening. */ + rc = wc_ecc_set_rng(&ctx->ephemeralKey, &ctx->rng); + if (rc != 0) { + wc_ecc_free(&ctx->ephemeralKey); return WOLFSPDM_E_CRYPTO_FAIL; } @@ -122,37 +124,27 @@ int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_BUFFER_SMALL; } - rc = wc_ecc_export_public_raw(&ctx->ephemeralKey.ecc, + rc = wc_ecc_export_public_raw(&ctx->ephemeralKey, pubKeyX, pubKeyXSz, pubKeyY, pubKeyYSz); if (rc != 0) { return WOLFSPDM_E_CRYPTO_FAIL; } /* Left-pad coordinates to full size (wolfSSL may strip leading zeros) */ - rc = wolfSPDM_LeftPadToSize(pubKeyX, *pubKeyXSz, WOLFSPDM_ECC_KEY_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } + wolfSPDM_LeftPadToSize(pubKeyX, *pubKeyXSz, WOLFSPDM_ECC_KEY_SIZE); *pubKeyXSz = WOLFSPDM_ECC_KEY_SIZE; - rc = wolfSPDM_LeftPadToSize(pubKeyY, *pubKeyYSz, WOLFSPDM_ECC_KEY_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } + wolfSPDM_LeftPadToSize(pubKeyY, *pubKeyYSz, WOLFSPDM_ECC_KEY_SIZE); *pubKeyYSz = WOLFSPDM_ECC_KEY_SIZE; return WOLFSPDM_SUCCESS; } -/* --- ECDH Shared Secret Computation --- */ +/* ----- ECDH Shared Secret Computation ----- */ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, const byte* peerPubKeyX, const byte* peerPubKeyY) { ecc_key peerKey; - byte scratch[WOLFSPDM_ECC_KEY_SIZE]; - word32 retSz; - word32 pad; - word32 i; int rc; int peerKeyInit = 0; @@ -164,57 +156,42 @@ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_BAD_STATE; } - /* Initialize peer key structure */ rc = wc_ecc_init(&peerKey); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - peerKeyInit = 1; - - /* Import peer's public key */ - rc = wc_ecc_import_unsigned(&peerKey, - peerPubKeyX, peerPubKeyY, - NULL, /* No private key */ - ECC_SECP384R1); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "Failed to import peer public key: %d\n", rc); - goto cleanup; + if (rc == 0) { + peerKeyInit = 1; + rc = wc_ecc_import_unsigned(&peerKey, peerPubKeyX, peerPubKeyY, + NULL, ECC_SECP384R1); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Failed to import peer public key: %d\n", rc); + } + } + /* Validate peer's public key is on the curve (prevents invalid-curve attacks) */ + if (rc == 0) { + rc = wc_ecc_check_key(&peerKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Peer public key invalid (not on curve): %d\n", rc); + } } - /* Compute ECDH shared secret */ - ctx->sharedSecretSz = sizeof(ctx->sharedSecret); - rc = wc_ecc_shared_secret(&ctx->ephemeralKey.ecc, &peerKey, - ctx->sharedSecret, &ctx->sharedSecretSz); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "ECDH shared_secret failed: %d\n", rc); - goto cleanup; + if (rc == 0) { + ctx->sharedSecretSz = sizeof(ctx->sharedSecret); + rc = wc_ecc_shared_secret(&ctx->ephemeralKey, &peerKey, + ctx->sharedSecret, &ctx->sharedSecretSz); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "ECDH shared_secret failed: %d\n", rc); + } + } + if (rc == 0) { + wolfSPDM_LeftPadToSize(ctx->sharedSecret, ctx->sharedSecretSz, + WOLFSPDM_ECC_KEY_SIZE); + ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; + wolfSPDM_DebugPrint(ctx, "ECDH shared secret computed (%u bytes)\n", + ctx->sharedSecretSz); + } else { + wc_ForceZero(ctx->sharedSecret, sizeof(ctx->sharedSecret)); + ctx->sharedSecretSz = 0; } - /* Zero-pad the X-coordinate to the full curve size in a way that does - * not branch on the secret's leading-zero count: always touch every - * byte of a scratch buffer so the memory-access pattern is independent - * of how many high-order zero bytes wolfCrypt stripped. The underlying - * wc_ecc_shared_secret length is itself a function of the secret X - * coordinate; this routine just keeps the wolfSPDM-level work uniform. */ - retSz = ctx->sharedSecretSz; - if (retSz > WOLFSPDM_ECC_KEY_SIZE) { - rc = WOLFSPDM_E_CRYPTO_FAIL; - goto cleanup; - } - pad = WOLFSPDM_ECC_KEY_SIZE - retSz; - for (i = 0; i < WOLFSPDM_ECC_KEY_SIZE; i++) { - scratch[i] = (i < pad) ? (byte)0 : ctx->sharedSecret[i - pad]; - } - XMEMCPY(ctx->sharedSecret, scratch, WOLFSPDM_ECC_KEY_SIZE); - wc_ForceZero(scratch, sizeof(scratch)); - ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; - - wolfSPDM_DebugPrint(ctx, "ECDH shared secret computed (%u bytes)\n", - ctx->sharedSecretSz); - - rc = 0; - -cleanup: if (peerKeyInit) { wc_ecc_free(&peerKey); } @@ -222,101 +199,183 @@ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } -#ifdef WOLFSPDM_HAVE_MLKEM -/* Map the negotiated SPDM KEM selection to the wolfSSL ML-KEM parameter set. */ -static int wolfSPDM_MlKemType(word16 kemAlgSel, int* type) +/* ----- ECDSA Signature Verification (P-384) ----- */ + +int wolfSPDM_ExtractEccPoint(const byte* pubKey, word32 pubKeySz, + const byte** pubKeyX, const byte** pubKeyY) { - switch (kemAlgSel) { - case SPDM_KEM_ALGO_ML_KEM_512: *type = WC_ML_KEM_512; break; - case SPDM_KEM_ALGO_ML_KEM_768: *type = WC_ML_KEM_768; break; - case SPDM_KEM_ALGO_ML_KEM_1024: *type = WC_ML_KEM_1024; break; - default: return WOLFSPDM_E_ALGO_MISMATCH; + if (pubKey == NULL || pubKeyX == NULL || pubKeyY == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (pubKeySz == WOLFSPDM_ECC_POINT_SIZE) { + *pubKeyX = pubKey; + *pubKeyY = pubKey + WOLFSPDM_ECC_KEY_SIZE; } + else if (pubKeySz >= WOLFSPDM_ECC_POINT_SIZE + 4) { + word32 pointOffset = pubKeySz - (WOLFSPDM_ECC_POINT_SIZE + 4); + if (SPDM_Get16BE(pubKey + pointOffset) != + WOLFSPDM_ECC_KEY_SIZE || + SPDM_Get16BE(pubKey + pointOffset + 2 + + WOLFSPDM_ECC_KEY_SIZE) != WOLFSPDM_ECC_KEY_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + *pubKeyX = pubKey + pointOffset + 2; + *pubKeyY = pubKey + pointOffset + 4 + WOLFSPDM_ECC_KEY_SIZE; + } + else { + return WOLFSPDM_E_INVALID_ARG; + } + return WOLFSPDM_SUCCESS; } -/* Generate the ephemeral ML-KEM key pair for KEY_EXCHANGE and export the - * encapsulation key ek into ekOut. The decapsulation key dk stays in - * ctx->ephemeralKey.mlkem for wolfSPDM_MlKemDecapsulate. */ -int wolfSPDM_GenerateMlKemKey(WOLFSPDM_CTX* ctx, byte* ekOut, word32* ekOutSz) +int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, + const byte* sig, word32 sigSz) { - int type; - word32 ekSz; + ecc_key verifyKey; int rc; - - if (ctx == NULL || ekOut == NULL || ekOutSz == NULL) { + int keyInit = 0; + byte derSig[ECC_MAX_SIG_SIZE]; + word32 derSigSz = sizeof(derSig); + int verified = 0; + const byte* pubKeyX; + const byte* pubKeyY; + + if (ctx == NULL || hash == NULL || sig == NULL) { return WOLFSPDM_E_INVALID_ARG; } - if (!ctx->flags.rngInitialized) { + + if (!ctx->flags.hasRspPubKey || ctx->rspPubKeyLen < WOLFSPDM_ECC_POINT_SIZE) { + wolfSPDM_DebugPrint(ctx, "No responder public key for verification\n"); return WOLFSPDM_E_BAD_STATE; } - rc = wolfSPDM_MlKemType(ctx->kemAlgSel, &type); + + if (sigSz != WOLFSPDM_ECC_SIG_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wolfSPDM_ExtractEccPoint(ctx->rspPubKey, ctx->rspPubKeyLen, + &pubKeyX, &pubKeyY); if (rc != WOLFSPDM_SUCCESS) { return rc; } - if (ctx->flags.ephemeralKeyInit) { - wolfSPDM_FreeEphemeralKey(ctx); - ctx->flags.ephemeralKeyInit = 0; + rc = wc_ecc_init(&verifyKey); + if (rc == 0) { + keyInit = 1; + rc = wc_ecc_import_unsigned(&verifyKey, pubKeyX, pubKeyY, + NULL, ECC_SECP384R1); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Failed to import rsp pub key for verify: %d\n", rc); + } } - ctx->kexType = WOLFSPDM_KEX_MLKEM; - - rc = wc_MlKemKey_Init(&ctx->ephemeralKey.mlkem, type, NULL, INVALID_DEVID); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; + if (rc == 0) { + rc = wc_ecc_check_key(&verifyKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Responder pub key invalid (not on curve): %d\n", rc); + } } - rc = wc_MlKemKey_MakeKey(&ctx->ephemeralKey.mlkem, &ctx->rng); - if (rc != 0) { - wc_MlKemKey_Free(&ctx->ephemeralKey.mlkem); - return WOLFSPDM_E_CRYPTO_FAIL; + /* Convert raw R||S signature to DER format for wolfCrypt */ + if (rc == 0) { + rc = wc_ecc_rs_raw_to_sig(sig, WOLFSPDM_ECC_KEY_SIZE, + sig + WOLFSPDM_ECC_KEY_SIZE, WOLFSPDM_ECC_KEY_SIZE, + derSig, &derSigSz); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_rs_raw_to_sig failed: %d\n", rc); + } } - ctx->flags.ephemeralKeyInit = 1; - - rc = wc_MlKemKey_PublicKeySize(&ctx->ephemeralKey.mlkem, &ekSz); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; + if (rc == 0) { + rc = wc_ecc_verify_hash(derSig, derSigSz, hash, hashSz, + &verified, &verifyKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_verify_hash failed: %d\n", rc); + } } - if (ekSz > *ekOutSz) { - return WOLFSPDM_E_BUFFER_SMALL; + if (rc == 0 && !verified) { + wolfSPDM_DebugPrint(ctx, "Responder signature verification FAILED\n"); + rc = -1; } - rc = wc_MlKemKey_EncodePublicKey(&ctx->ephemeralKey.mlkem, ekOut, ekSz); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; + if (rc == 0) { + wolfSPDM_DebugPrint(ctx, "Responder signature VERIFIED OK\n"); } - *ekOutSz = ekSz; - wolfSPDM_DebugPrint(ctx, "Generated ML-KEM ephemeral key (ek %u bytes)\n", - ekSz); - return WOLFSPDM_SUCCESS; + if (keyInit) { + wc_ecc_free(&verifyKey); + } + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_BAD_SIGNATURE; } -/* Decapsulate the responder's ciphertext c into ctx->sharedSecret (K'). */ -int wolfSPDM_MlKemDecapsulate(WOLFSPDM_CTX* ctx, const byte* ct, word32 ctSz) +/* ----- ECDSA Signing (P-384) ----- */ + +int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, + byte* sig, word32* sigSz) { - word32 ssSz; + ecc_key sigKey; int rc; + int keyInit = 0; + byte derSig[ECC_MAX_SIG_SIZE]; + word32 derSigSz = sizeof(derSig); + word32 rLen, sLen; - if (ctx == NULL || ct == NULL) { + if (ctx == NULL || hash == NULL || sig == NULL || sigSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } - if (!ctx->flags.ephemeralKeyInit || - ctx->kexType != WOLFSPDM_KEX_MLKEM) { + + if (!ctx->flags.hasReqKeyPair || ctx->reqPrivKeyLen == 0) { + wolfSPDM_DebugPrint(ctx, "No requester key pair for signing\n"); return WOLFSPDM_E_BAD_STATE; } - rc = wc_MlKemKey_SharedSecretSize(&ctx->ephemeralKey.mlkem, &ssSz); - if (rc != 0 || ssSz > sizeof(ctx->sharedSecret)) { - return WOLFSPDM_E_CRYPTO_FAIL; + if (*sigSz < WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; } - rc = wc_MlKemKey_Decapsulate(&ctx->ephemeralKey.mlkem, ctx->sharedSecret, - ct, ctSz); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "ML-KEM decapsulate failed: %d\n", rc); - return WOLFSPDM_E_CRYPTO_FAIL; + + rc = wc_ecc_init(&sigKey); + if (rc == 0) { + keyInit = 1; + rc = wc_ecc_import_unsigned(&sigKey, + ctx->reqPubKey, + ctx->reqPubKey + WOLFSPDM_ECC_KEY_SIZE, + ctx->reqPrivKey, + ECC_SECP384R1); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_import_unsigned failed: %d\n", rc); + } + } else { + wolfSPDM_DebugPrint(ctx, "wc_ecc_init failed: %d\n", rc); + } + if (rc == 0) { + rc = wc_ecc_sign_hash(hash, hashSz, derSig, &derSigSz, + &ctx->rng, &sigKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_sign_hash failed: %d\n", rc); + } + } + /* Convert DER signature to raw R||S format (96 bytes for P-384) */ + if (rc == 0) { + rLen = WOLFSPDM_ECC_KEY_SIZE; + sLen = WOLFSPDM_ECC_KEY_SIZE; + rc = wc_ecc_sig_to_rs(derSig, derSigSz, sig, &rLen, + sig + WOLFSPDM_ECC_KEY_SIZE, &sLen); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_sig_to_rs failed: %d\n", rc); + } + } + if (rc == 0) { + wolfSPDM_LeftPadToSize(sig, rLen, WOLFSPDM_ECC_KEY_SIZE); + wolfSPDM_LeftPadToSize(sig + WOLFSPDM_ECC_KEY_SIZE, sLen, + WOLFSPDM_ECC_KEY_SIZE); + *sigSz = WOLFSPDM_ECC_POINT_SIZE; + wolfSPDM_DebugPrint(ctx, "Signed hash with P-384 key (sig=%u bytes)\n", + *sigSz); + } + + if (keyInit) { + wc_ecc_free(&sigKey); } - ctx->sharedSecretSz = ssSz; - wolfSPDM_DebugPrint(ctx, "ML-KEM shared secret derived (%u bytes)\n", ssSz); - return WOLFSPDM_SUCCESS; + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } -#endif /* WOLFSPDM_HAVE_MLKEM */ + diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 19a4e64..340640d 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -1,6 +1,6 @@ /* spdm_internal.h * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -27,18 +27,17 @@ #include #endif -/* wolfSSL options MUST be included first */ -#ifndef WOLFSSL_USER_SETTINGS - #include -#endif -#include - #include #include #include -/* wolfCrypt includes */ -#include +/* wolfCrypt includes - verify required algorithms */ +#ifndef HAVE_ECC + #error "wolfSPDM requires ECC (--enable-ecc in wolfSSL)" +#endif +#ifndef WOLFSSL_SHA384 + #error "wolfSPDM requires SHA-384 (--enable-sha384 in wolfSSL)" +#endif #include #include #include @@ -46,16 +45,10 @@ #include #include #include -#include -#ifdef WOLFSPDM_HAVE_MLDSA - #include -#endif -#ifdef WOLFSPDM_HAVE_MLKEM - #include -#endif +#include #if defined(LIBWOLFSSL_VERSION_HEX) && LIBWOLFSSL_VERSION_HEX < 0x05008004 -/* wc_ForceZero added in wolfSSL v5.8.4; provide a stub for older releases. */ +/* wc_ForceZero added in wolfSSL v5.8.4 */ static WC_INLINE void wc_ForceZero(void* mem, word32 len) { volatile byte* z = (volatile byte*)mem; @@ -65,134 +58,90 @@ static WC_INLINE void wc_ForceZero(void* mem, word32 len) } #endif -/* Constant-time byte comparison: returns 0 iff a==b for the full length. - * Used for MAC/HMAC equality so we don't leak match position via timing. */ -static WC_INLINE int wolfSPDM_ConstCompare(const byte* a, const byte* b, - word32 len) -{ - byte diff = 0; - word32 i; - for (i = 0; i < len; i++) { - diff |= (byte)(a[i] ^ b[i]); - } - return diff; -} - #ifdef __cplusplus extern "C" { #endif -/* --- State Machine Constants --- */ +/* ----- State Machine Constants ----- */ #define WOLFSPDM_STATE_INIT 0 /* Initial state */ #define WOLFSPDM_STATE_VERSION 1 /* GET_VERSION complete */ -#define WOLFSPDM_STATE_CAPS 2 /* GET_CAPABILITIES complete */ -#define WOLFSPDM_STATE_ALGO 3 /* NEGOTIATE_ALGORITHMS complete */ -#define WOLFSPDM_STATE_DIGESTS 4 /* GET_DIGESTS complete */ -#define WOLFSPDM_STATE_CERT 5 /* GET_CERTIFICATE complete */ -#define WOLFSPDM_STATE_KEY_EX 6 /* KEY_EXCHANGE complete */ -#define WOLFSPDM_STATE_FINISH 7 /* FINISH complete */ -#define WOLFSPDM_STATE_CONNECTED 8 /* Session established */ -#define WOLFSPDM_STATE_ERROR 9 /* Error state */ -#ifndef NO_WOLFSPDM_MEAS -#define WOLFSPDM_STATE_MEASURED 10 /* Measurements retrieved */ -#endif +#define WOLFSPDM_STATE_CERT 2 /* GET_CERTIFICATE / GET_PUB_KEY complete */ +#define WOLFSPDM_STATE_KEY_EX 3 /* KEY_EXCHANGE complete */ +#define WOLFSPDM_STATE_FINISH 4 /* FINISH complete */ +#define WOLFSPDM_STATE_CONNECTED 5 /* Session established */ +#define WOLFSPDM_STATE_ERROR 6 /* Error state */ + +/* ----- Supported SPDM Version Range ----- */ -/* SPDM version bounds. Override with -DWOLFSPDM_MIN/MAX_SPDM_VERSION at - * compile time. The runtime wolfSPDM_SetMaxVersion clamps against these. */ +/* Maximum SPDM version we support. Supports SPDM 1.2 through 1.4. + * Override with -DWOLFSPDM_MAX_SPDM_VERSION at compile time to cap + * at a lower version. */ #ifndef WOLFSPDM_MAX_SPDM_VERSION #define WOLFSPDM_MAX_SPDM_VERSION SPDM_VERSION_14 #endif + +/* Minimum SPDM version we require. Our key derivation uses BinConcat + * format ("spdm1.2 " prefix) which is a 1.2+ feature. SPDM 1.1 uses + * a different HKDF label format and would require separate key + * derivation code. Override at compile time if 1.1 support is added. */ #ifndef WOLFSPDM_MIN_SPDM_VERSION #define WOLFSPDM_MIN_SPDM_VERSION SPDM_VERSION_12 #endif -/* --- Measurement Block Structure --- */ - -#ifndef NO_WOLFSPDM_MEAS -typedef struct WOLFSPDM_MEAS_BLOCK { - byte index; /* SPDM measurement index (1-based) */ - byte measurementSpec; /* Measurement specification (1=DMTF) */ - byte dmtfType; /* DMTFSpecMeasurementValueType */ - word16 valueSize; /* Actual value size in bytes */ - byte value[WOLFSPDM_MAX_MEAS_VALUE_SIZE]; /* Measurement value (digest/raw) */ -} WOLFSPDM_MEAS_BLOCK; -#endif /* !NO_WOLFSPDM_MEAS */ - -/* --- Internal Context Structure --- */ +/* ----- Internal Context Structure ----- */ struct WOLFSPDM_CTX { /* State machine */ int state; - /* Boolean flags - packed into a small bit-field struct (one 4-byte - * unsigned int holding 9 booleans, vs. 9 separate ints = 36 bytes). - * Use unsigned int (not byte) since C11 Sec. 6.7.2.1 only guarantees - * bit-field support for _Bool, signed int, and unsigned int - this - * keeps the struct portable under -Wpedantic -Werror. */ - struct { - unsigned int debug : 1; - unsigned int initialized : 1; - unsigned int isDynamic : 1; /* Set by wolfSPDM_New(), checked by Free */ - unsigned int rngInitialized : 1; - unsigned int ephemeralKeyInit : 1; - unsigned int hasMeasurements : 1; - unsigned int hasResponderPubKey : 1; - unsigned int hasTrustedCAs : 1; - unsigned int m1m2HashInit : 1; - unsigned int allowUntrustedCert : 1; /* Explicit opt-in for missing trust anchor */ - } flags; + /* Protocol mode */ + WOLFSPDM_MODE mode; /* I/O callback */ WOLFSPDM_IO_CB ioCb; void* ioUserCtx; +#ifdef WOLFSPDM_TCG + /* TCG binding fields (shared by Nuvoton + Nations) */ + word32 connectionHandle; /* Connection handle (usually 0) */ + word16 fipsIndicator; /* FIPS service indicator */ + + /* Host's public key in TPMT_PUBLIC format */ + byte reqPubKeyTPMT[WOLFSPDM_PUBKEY_BUF_SZ / 2]; /* TPMT_PUBLIC (~120 bytes) */ + word32 reqPubKeyTPMTLen; +#endif + +#ifdef WOLFSPDM_PSK + /* PSK fields */ + byte psk[WOLFSPDM_PSK_MAX_SIZE]; + word32 pskSz; /* pskSz > 0 means PSK is set */ + byte pskHint[WOLFSPDM_PSK_HINT_MAX]; + word32 pskHintSz; +#endif + /* Random number generator */ WC_RNG rng; /* Negotiated parameters */ byte maxVersion; /* Runtime max version cap (0 = use compile-time default) */ byte spdmVersion; /* Negotiated SPDM version */ - byte lastPeerErrorCode; /* Last SPDM_ERROR Param1 from responder (0 = none) */ - word32 rspCaps; /* Responder capabilities */ - word32 reqCaps; /* Our (requester) capabilities */ - byte ctExponent; /* DSP0274 Table 12: CT = 2^CTExponent us */ - word32 dataTransferSize; /* SPDM 1.2+ max per-fragment payload */ - word32 maxSpdmMsgSize; /* SPDM 1.2+ max full SPDM message size */ - byte slotMask; /* DIGESTS Param1: bit i = slot i populated */ - byte currentSlotId; /* Slot the most recent GET_CERTIFICATE used */ - - /* Ephemeral key generated for KEY_EXCHANGE. kexType records the negotiated - * key-exchange method; only one union member is ever live. The ML-KEM key - * also holds the decapsulation key dk between request and response. */ - byte kexType; /* WOLFSPDM_KEX_ECDHE|_MLKEM */ - word16 kemAlgSel; /* Selected SPDM_KEM_ALGO_* (0 if ECDHE) */ - byte kexAdvDhe; /* Advertise the DHE group (default 1) */ - word16 kexAdvKem; /* ML-KEM mask to advertise (0 = none) */ - union { - ecc_key ecc; /* ECDHE secp384r1 (Algorithm Set B) */ -#ifdef WOLFSPDM_HAVE_MLKEM - MlKemKey mlkem; /* ML-KEM (DSP0274 1.4) */ -#endif - } ephemeralKey; - /* Key-exchange shared secret: ECDH P-384 X-coordinate (48 bytes) or an - * ML-KEM decapsulated shared secret (32 bytes); sharedSecretSz tracks which. */ + /* Ephemeral ECDHE key (generated for KEY_EXCHANGE) */ + ecc_key ephemeralKey; + + /* ECDH shared secret (P-384 X-coordinate = 48 bytes) */ byte sharedSecret[WOLFSPDM_ECC_KEY_SIZE]; word32 sharedSecretSz; /* Transcript hash for TH1/TH2 computation */ byte transcript[WOLFSPDM_MAX_TRANSCRIPT]; word32 transcriptLen; - word32 vcaLen; /* VCA transcript size (after ALGORITHMS, used by measurement sig) */ - - /* Certificate chain buffer for Ct computation */ - byte certChain[WOLFSPDM_MAX_CERT_CHAIN]; - word32 certChainLen; /* Computed hashes */ byte certChainHash[WOLFSPDM_HASH_SIZE]; /* Ct = Hash(cert_chain) */ byte th1[WOLFSPDM_HASH_SIZE]; /* TH1 after KEY_EXCHANGE_RSP */ + byte th2[WOLFSPDM_HASH_SIZE]; /* TH2 after FINISH */ /* Derived keys */ byte handshakeSecret[WOLFSPDM_HASH_SIZE]; @@ -216,91 +165,32 @@ struct WOLFSPDM_CTX { word16 rspSessionId; /* Responder's session ID */ word32 sessionId; /* Combined: reqSessionId | (rspSessionId << 16) */ -#ifndef NO_WOLFSPDM_MEAS - /* Measurement data */ - WOLFSPDM_MEAS_BLOCK measBlocks[WOLFSPDM_MAX_MEAS_BLOCKS]; - word32 measBlockCount; - byte measNonce[32]; /* Nonce for signed measurements */ - byte measSummaryHash[WOLFSPDM_HASH_SIZE]; /* Summary hash from response */ - byte measSignature[WOLFSPDM_MAX_SIG_SIZE]; /* Captured signature (ECDSA/ML-DSA) */ - word32 measSignatureSize; /* 0 if unsigned, else SigLen */ - -#ifndef NO_WOLFSPDM_MEAS_VERIFY - /* Saved GET_MEASUREMENTS request for L1/L2 transcript */ - byte measReqMsg[48]; /* Saved request (max 37 bytes) */ - word32 measReqMsgSz; -#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ -#endif /* !NO_WOLFSPDM_MEAS */ - - /* Responder identity for signature verification (measurements + challenge). - * asymType records which family the responder selected during - * NEGOTIATE_ALGORITHMS; only one key in the union is ever live. */ - byte asymType; /* WOLFSPDM_ASYM_ECDSA|_MLDSA */ - word32 pqcAsymSel; /* Selected PqcAsymSel (0=ECDSA) */ - union { - ecc_key ecc; /* ECDSA P-384 (Algorithm Set B) */ -#ifdef WOLFSPDM_HAVE_MLDSA - MlDsaKey mldsa; /* ML-DSA (DSP0274 1.4) */ -#endif - } responderPubKey; /* Extracted from cert chain leaf */ - - /* Certificate chain validation */ - byte trustedCAs[WOLFSPDM_MAX_TRUSTED_CA]; /* DER-encoded root CA */ - word32 trustedCAsSz; - -#ifndef NO_WOLFSPDM_CHALLENGE - /* Challenge authentication */ - byte challengeNonce[32]; /* Saved nonce from CHALLENGE request */ - byte challengeReqCtx[8]; /* RequesterContext sent (1.3+) */ - byte challengeMeasHashType; /* MeasurementSummaryHashType from req */ - byte challengeSlotId; /* SlotID sent (for echo verification) */ - - /* Running M1/M2 hash for CHALLENGE_AUTH signature verification. - * Per DSP0274, M1/M2 = A || B || C where: - * A = VCA (GET_VERSION..ALGORITHMS) - * B = GET_DIGESTS + DIGESTS + GET_CERTIFICATE + CERTIFICATE (all chunks) - * C = CHALLENGE + CHALLENGE_AUTH (before sig) - * This hash accumulates A+B during NegAlgo/GetDigests/GetCertificate, - * then C is added in VerifyChallengeAuthSig. */ - wc_Sha384 m1m2Hash; -#endif + /* Responder's identity public key (for cert-less mode like Nuvoton) */ + byte rspPubKey[WOLFSPDM_PUBKEY_BUF_SZ / 2]; /* pinned raw X||Y */ + word32 rspPubKeyLen; - /* Key update state - app secrets for re-derivation */ - byte reqAppSecret[WOLFSPDM_HASH_SIZE]; /* 48 bytes */ - byte rspAppSecret[WOLFSPDM_HASH_SIZE]; /* 48 bytes */ + /* Mutual auth fields from KEY_EXCHANGE_RSP */ + byte mutAuthRequested; /* MutAuthRequested from KEY_EXCHANGE_RSP */ + byte reqSlotIdParam; /* ReqSlotIDParam from KEY_EXCHANGE_RSP */ -#ifdef WOLFSPDM_HAVE_CHUNK - /* Single reused transport buffer for CHUNK_GET/CHUNK_RESPONSE (the MTU). - * Zero-allocation: one fixed buffer holds one CHUNK_RESPONSE message. */ - byte chunkBuf[WOLFSPDM_CHUNK_BUF_SIZE]; -#endif -}; + /* Requester's identity key pair (for mutual auth) */ + byte reqPrivKey[WOLFSPDM_ECC_KEY_SIZE]; + word32 reqPrivKeyLen; + byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; -/* Free whichever responder verify key is live (union member by asymType). */ -static WC_INLINE void wolfSPDM_FreeResponderPubKey(WOLFSPDM_CTX* ctx) -{ -#ifdef WOLFSPDM_HAVE_MLDSA - if (ctx->asymType == WOLFSPDM_ASYM_MLDSA) { - wc_MlDsaKey_Free(&ctx->responderPubKey.mldsa); - return; - } -#endif - wc_ecc_free(&ctx->responderPubKey.ecc); -} - -/* Free whichever ephemeral key is live (union member by kexType). */ -static WC_INLINE void wolfSPDM_FreeEphemeralKey(WOLFSPDM_CTX* ctx) -{ -#ifdef WOLFSPDM_HAVE_MLKEM - if (ctx->kexType == WOLFSPDM_KEX_MLKEM) { - wc_MlKemKey_Free(&ctx->ephemeralKey.mlkem); - return; - } -#endif - wc_ecc_free(&ctx->ephemeralKey.ecc); -} + /* Boolean flag bit field (at end for better struct packing) */ + struct { + unsigned int debug : 1; + unsigned int initialized : 1; + unsigned int isDynamic : 1; /* Set by wolfSPDM_New(), checked by Free */ + unsigned int rngInitialized : 1; + unsigned int ephemeralKeyInit : 1; + unsigned int hasRspPubKey : 1; + unsigned int hasReqKeyPair : 1; + } flags; +}; -/* --- Byte-Order Helpers --- */ +/* ----- Byte-Order Helpers ----- */ static WC_INLINE void SPDM_Set16LE(byte* buf, word16 val) { buf[0] = (byte)(val & 0xFF); buf[1] = (byte)(val >> 8); @@ -343,16 +233,32 @@ static WC_INLINE word64 SPDM_Get64LE(const byte* buf) { ((word64)buf[6] << 48) | ((word64)buf[7] << 56); } -/* Build IV: BaseIV XOR zero-extended sequence number (DSP0277) */ +/* ----- Write TCG SPDM Binding header ----- */ +/* tag(2/BE) + size(4/BE) + + * connHandle(4/BE) + fips(2/BE) + reserved(4) */ +#ifdef WOLFSPDM_TCG +static WC_INLINE void wolfSPDM_WriteTcgHeader(byte* buf, word16 tag, + word32 totalSz, word32 connHandle, word16 fips) +{ + SPDM_Set16BE(buf, tag); + SPDM_Set32BE(buf + 2, totalSz); + SPDM_Set32BE(buf + 6, connHandle); + SPDM_Set16BE(buf + 10, fips); + XMEMSET(buf + 12, 0, 4); /* Reserved */ +} +#endif + +/* ----- Build IV ----- */ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, word64 seqNum) { + byte seq[8]; int i; XMEMCPY(iv, baseIv, WOLFSPDM_AEAD_IV_SIZE); - iv[0] ^= (byte)(seqNum & 0xFF); - iv[1] ^= (byte)((seqNum >> 8) & 0xFF); + SPDM_Set64LE(seq, seqNum); + for (i = 0; i < 8; i++) iv[i] ^= seq[i]; } -/* --- Connect Step Macro --- */ +/* ----- Connect Step Macro ----- */ #define SPDM_CONNECT_STEP(ctx, msg, func) do { \ wolfSPDM_DebugPrint(ctx, msg); \ @@ -360,7 +266,7 @@ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, if (rc != WOLFSPDM_SUCCESS) { ctx->state = WOLFSPDM_STATE_ERROR; return rc; } \ } while (0) -/* --- Argument Validation Macros --- */ +/* ----- Argument Validation Macros ----- */ #define SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, minSz) \ do { \ @@ -368,30 +274,21 @@ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, return WOLFSPDM_E_INVALID_ARG; \ if (*(bufSz) < (minSz)) \ return WOLFSPDM_E_BUFFER_SMALL; \ - } while (0) + } while(0) -/* Validate parser inputs. The 4-byte SPDM header (version + code + Param1 + - * Param2) must always be present. A response shorter than minSz that turns - * out to be SPDM_ERROR is *not* rejected here so the matching - * SPDM_CHECK_RESPONSE call can surface it as WOLFSPDM_E_PEER_ERROR. */ -#define SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, minSz) \ +#define SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, minSz) \ do { \ - if ((ctx) == NULL || (buf) == NULL) \ - return WOLFSPDM_E_INVALID_ARG; \ - if ((bufSz) < 4) \ - return WOLFSPDM_E_INVALID_ARG; \ - if ((bufSz) < (minSz) && (buf)[1] != SPDM_ERROR) \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) < (minSz)) \ return WOLFSPDM_E_INVALID_ARG; \ - } while (0) + } while(0) -/* --- Response Code Check Macro --- */ +/* ----- Response Code Check Macro ----- */ #define SPDM_CHECK_RESPONSE(ctx, buf, bufSz, expected, fallbackErr) \ do { \ if ((buf)[1] != (expected)) { \ int _ec; \ if (wolfSPDM_CheckError((buf), (bufSz), &_ec)) { \ - (ctx)->lastPeerErrorCode = (byte)_ec; \ wolfSPDM_DebugPrint((ctx), "SPDM error: 0x%02x\n", _ec); \ return WOLFSPDM_E_PEER_ERROR; \ } \ @@ -399,253 +296,92 @@ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, } \ } while (0) -/* --- Internal Function Declarations - Transcript --- */ - -/* Reset transcript buffer */ -void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); - -/* Add data to transcript */ -int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); - -/* Add data to certificate chain buffer */ -int wolfSPDM_CertChainAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); +/* ----- Internal Function Declarations - Transcript ----- */ -/* Compute hash of current transcript */ -int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); - -/* Compute Ct = Hash(certificate_chain) */ -int wolfSPDM_ComputeCertChainHash(WOLFSPDM_CTX* ctx); - -/* SHA-384 hash helper: Hash(d1 || d2 || d3), pass NULL/0 for unused buffers */ -int wolfSPDM_Sha384Hash(byte* out, +WOLFSPDM_API void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); +WOLFSPDM_API int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); +WOLFSPDM_API int wolfSPDM_Sha384Hash(byte* out, const byte* d1, word32 d1Sz, const byte* d2, word32 d2Sz, const byte* d3, word32 d3Sz); -/* --- Internal Function Declarations - Crypto --- */ +/* ----- Internal Function Declarations - Crypto ----- */ -/* Generate ephemeral P-384 key for ECDHE */ -int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); - -/* Export ephemeral public key (X||Y) */ -int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, +WOLFSPDM_API int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, byte* pubKeyX, word32* pubKeyXSz, byte* pubKeyY, word32* pubKeyYSz); - -/* Compute ECDH shared secret from responder's public key */ -int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, +WOLFSPDM_API int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, const byte* peerPubKeyX, const byte* peerPubKeyY); - -#ifdef WOLFSPDM_HAVE_MLKEM -/* Generate the ephemeral ML-KEM key pair and export the encapsulation key ek */ -int wolfSPDM_GenerateMlKemKey(WOLFSPDM_CTX* ctx, byte* ekOut, word32* ekOutSz); - -/* Decapsulate the responder's ciphertext c into ctx->sharedSecret (K') */ -int wolfSPDM_MlKemDecapsulate(WOLFSPDM_CTX* ctx, const byte* ct, word32 ctSz); -#endif - -/* Generate random bytes */ -int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); - -/* --- Internal Function Declarations - Key Derivation --- */ - -/* Derive all keys from shared secret and TH1 */ -int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); - -/* Derive application data keys from MasterSecret and TH2_final */ -int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx); - -/* HKDF-Expand with SPDM BinConcat format (uses version-specific prefix) */ -int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, +WOLFSPDM_API int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); +WOLFSPDM_API int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, + byte* sig, word32* sigSz); +WOLFSPDM_TEST_API int wolfSPDM_ExtractEccPoint(const byte* pubKey, + word32 pubKeySz, const byte** pubKeyX, const byte** pubKeyY); +WOLFSPDM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, + const byte* hash, word32 hashSz, + const byte* sig, word32 sigSz); + +/* ----- Internal Function Declarations - Key Derivation ----- */ + +WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); +WOLFSPDM_API int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, const byte* th1Hash); +WOLFSPDM_API int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, const char* label, const byte* context, word32 contextSz, byte* out, word32 outSz); - -/* Compute HMAC for VerifyData */ -int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, +WOLFSPDM_API int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, byte* verifyData); +/* Cross-TU helper, not a shipped API - WOLFSPDM_LOCAL keeps it out of the + * shared-library export table. spdm_internal.h is private to wolfSPDM. */ +WOLFSPDM_LOCAL int wolfSPDM_BuildSignedHash(byte spdmVersion, + const char* contextStr, word32 contextStrLen, + const byte* inputDigest, byte* outputDigest); -/* --- Internal Function Declarations - Message Building --- */ - -/* Build GET_VERSION request */ -int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); - -/* Build GET_CAPABILITIES request */ -int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build NEGOTIATE_ALGORITHMS request */ -int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build GET_DIGESTS request */ -int wolfSPDM_BuildGetDigests(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build GET_CERTIFICATE request */ -int wolfSPDM_BuildGetCertificate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - int slotId, word16 offset, word16 length); - -/* Build KEY_EXCHANGE request */ -int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build FINISH request */ -int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build END_SESSION request */ -int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* --- Internal Function Declarations - Message Parsing --- */ - -/* Parse VERSION response */ -int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -/* Parse CAPABILITIES response */ -int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -/* Parse ALGORITHMS response */ -int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -/* Parse DIGESTS response */ -int wolfSPDM_ParseDigests(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +/* ----- Internal Function Declarations - Message Building ----- */ -/* Parse CERTIFICATE response */ -int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz, - word16* portionLen, word16* remainderLen); +WOLFSPDM_API int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); +WOLFSPDM_API int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFSPDM_API int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFSPDM_API int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +/* PSK message builders/parsers declared in spdm_psk.h */ -/* Parse KEY_EXCHANGE_RSP */ -int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +/* ----- Internal Function Declarations - Message Parsing ----- */ -/* Parse FINISH_RSP (after decryption) */ -int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); -/* Check for ERROR response */ -int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); +/* ----- Internal Function Declarations - Secured Messaging ----- */ -/* --- Internal Function Declarations - Secured Messaging --- */ - -/* Encrypt plaintext using session keys */ -int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, +WOLFSPDM_API int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, const byte* plain, word32 plainSz, byte* enc, word32* encSz); - -/* Decrypt ciphertext using session keys */ -int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, +WOLFSPDM_API int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, const byte* enc, word32 encSz, byte* plain, word32* plainSz); -/* --- Internal Utility Functions --- */ +/* ----- Internal Utility Functions ----- */ -/* Send message via I/O callback and receive response. SendReceive transparently - * reassembles a chunked (LargeResponse) reply; SendReceiveRaw is the bare - * callback used by the chunk loop and the secured transport to avoid re-entry. */ -int wolfSPDM_SendReceiveRaw(WOLFSPDM_CTX* ctx, - const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz); -int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, +WOLFSPDM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); -#ifdef WOLFSPDM_HAVE_CHUNK -/* True if buf is an ERROR(LargeResponse); writes the 1-byte Handle. */ -static WC_INLINE int wolfSPDM_IsLargeResponse(const byte* buf, word32 bufSz, - byte* handle) -{ - if (buf == NULL || bufSz < 5) { - return 0; - } - if (buf[1] == SPDM_ERROR && buf[2] == SPDM_ERROR_LARGE_RESPONSE) { - if (handle != NULL) { - *handle = buf[4]; /* ExtendedErrorData byte 0 = Handle (Table 68) */ - } - return 1; - } - return 0; -} - -/* Build a CHUNK_GET request for (handle, seqNo). */ -int wolfSPDM_BuildChunkGet(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - byte handle, word32 seqNo); - -/* Reassemble a large response the responder split. The triggering - * ERROR(LargeResponse) was already received; this drives the CHUNK_GET loop and - * fills outBuf with the logical message. secured selects the encrypted - * (session) transport. */ -int wolfSPDM_ReassembleLargeResponse(WOLFSPDM_CTX* ctx, int secured, - byte handle, byte* outBuf, word32 outBufSz, word32* outSz); -#endif /* WOLFSPDM_HAVE_CHUNK */ - -/* Debug print (if enabled) */ -void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) +#ifdef WOLFSPDM_DEBUG +WOLFSPDM_API void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) #ifdef __GNUC__ __attribute__((format(printf, 2, 3))) #endif ; -/* Hex dump for debugging */ -void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, +WOLFSPDM_API void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, const byte* data, word32 len); - -/* --- Internal Function Declarations - Measurements --- */ - -#ifndef NO_WOLFSPDM_MEAS -/* Build GET_MEASUREMENTS request */ -int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - byte operation, byte requestSig); - -/* Parse MEASUREMENTS response */ -int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -#ifndef NO_WOLFSPDM_MEAS_VERIFY -/* Verify measurement signature (L1/L2 transcript) */ -int wolfSPDM_VerifyMeasurementSig(WOLFSPDM_CTX* ctx, - const byte* rspBuf, word32 rspBufSz, - const byte* reqMsg, word32 reqMsgSz); -#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ -#endif /* !NO_WOLFSPDM_MEAS */ - -/* --- Internal Function Declarations - Certificate Chain Validation --- */ - -/* Extract responder's public key from certificate chain leaf cert */ -int wolfSPDM_ExtractResponderPubKey(WOLFSPDM_CTX* ctx); - -/* Validate certificate chain using trusted CAs and extract public key */ -int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx); - -/* --- Internal Function Declarations - Challenge --- */ - -#ifndef NO_WOLFSPDM_CHALLENGE -/* Build CHALLENGE request */ -int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - int slotId, byte measHashType); - -/* Parse CHALLENGE_AUTH response */ -int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, const byte* buf, - word32 bufSz, word32* sigOffset); - -/* Verify CHALLENGE_AUTH signature */ -int wolfSPDM_VerifyChallengeAuthSig(WOLFSPDM_CTX* ctx, - const byte* rspBuf, word32 rspBufSz, - const byte* reqMsg, word32 reqMsgSz, word32 sigOffset); -#endif /* !NO_WOLFSPDM_CHALLENGE */ - -/* --- Internal Function Declarations - Heartbeat --- */ - -/* Build HEARTBEAT request */ -int wolfSPDM_BuildHeartbeat(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Parse HEARTBEAT_ACK response */ -int wolfSPDM_ParseHeartbeatAck(WOLFSPDM_CTX* ctx, const byte* buf, - word32 bufSz); - -/* --- Internal Function Declarations - Key Update --- */ - -/* Build KEY_UPDATE request */ -int wolfSPDM_BuildKeyUpdate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - byte operation, byte* tag); - -/* Parse KEY_UPDATE_ACK response */ -int wolfSPDM_ParseKeyUpdateAck(WOLFSPDM_CTX* ctx, const byte* buf, - word32 bufSz, byte operation, byte tag); - -/* Derive updated keys from saved app secrets */ -int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, int updateAll); +#else +#define wolfSPDM_DebugPrint(ctx, fmt, ...) do { (void)(ctx); (void)fmt; } while(0) +#define wolfSPDM_DebugHex(ctx, label, data, len) do { (void)(ctx); (void)(label); (void)(data); (void)(len); } while(0) +#endif #ifdef __cplusplus } diff --git a/src/spdm_kdf.c b/src/spdm_kdf.c index bef4c8e..10a5220 100644 --- a/src/spdm_kdf.c +++ b/src/spdm_kdf.c @@ -1,6 +1,6 @@ /* spdm_kdf.c * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -19,24 +19,14 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + #include "spdm_internal.h" -/* - * SPDM Key Derivation (DSP0277) - * - * SPDM uses HKDF with a BinConcat info format different from TLS 1.3: - * info = Length (2 bytes, LE) || "spdm1.2 " || Label || Context - * - * Key hierarchy: - * HandshakeSecret = HKDF-Extract(salt=zeros, IKM=sharedSecret) - * reqHsSecret = HKDF-Expand(HS, "req hs data" || TH1, 48) - * rspHsSecret = HKDF-Expand(HS, "rsp hs data" || TH1, 48) - * reqFinishedKey = HKDF-Expand(reqHsSecret, "finished", 48) - * rspFinishedKey = HKDF-Expand(rspHsSecret, "finished", 48) - * reqDataKey = HKDF-Expand(reqHsSecret, "key", 32) - * reqDataIV = HKDF-Expand(reqHsSecret, "iv", 12) - * (same pattern for rsp keys) - */ +/* SPDM key derivation (DSP0277): HKDF with + * info = Length(2,LE) || "spdm1.2 " || Label || Context. */ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, const char* label, const byte* context, word32 contextSz, @@ -44,24 +34,14 @@ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secret { byte info[128]; word32 infoLen = 0; - const char* prefix; word32 labelLen; + const char* prefix; int rc; if (secret == NULL || label == NULL || out == NULL) { return WOLFSPDM_E_INVALID_ARG; } - /* Defense-in-depth bound check: 2 (outLen) + 8 (version prefix) + - * strlen(label) + contextSz must fit into info[128]. Reject before any - * XMEMCPY rather than relying on every caller to stay within bounds. */ - labelLen = (word32)XSTRLEN(label); - if (labelLen > sizeof(info) || - contextSz > sizeof(info) || - 2 + SPDM_BIN_CONCAT_PREFIX_LEN + labelLen + contextSz > sizeof(info)) { - return WOLFSPDM_E_BUFFER_SMALL; - } - /* Select version-specific prefix */ if (spdmVersion >= 0x14) { prefix = SPDM_BIN_CONCAT_PREFIX_14; /* "spdm1.4 " */ @@ -71,10 +51,19 @@ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secret prefix = SPDM_BIN_CONCAT_PREFIX_12; /* "spdm1.2 " */ } - /* BinConcat format: Length (2 LE) || "spdmX.Y " || Label || Context */ + /* BinConcat format: Length (2 LE) || "spdmX.Y " || Label || Context + * Note: SPDM spec references TLS 1.3 (BE), but Nuvoton uses LE. + * The ResponderVerifyData match proves LE is correct for this TPM. */ info[infoLen++] = (byte)(outSz & 0xFF); info[infoLen++] = (byte)((outSz >> 8) & 0xFF); + labelLen = (word32)XSTRLEN(label); + + /* Bounds check: 2 + prefix(8) + label + context must fit in info[128] */ + if (2 + SPDM_BIN_CONCAT_PREFIX_LEN + labelLen + contextSz > sizeof(info)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(info + infoLen, prefix, SPDM_BIN_CONCAT_PREFIX_LEN); infoLen += SPDM_BIN_CONCAT_PREFIX_LEN; @@ -88,11 +77,6 @@ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secret rc = wc_HKDF_Expand(WC_SHA384, secret, secretSz, info, infoLen, out, outSz); - /* info embeds the context bytes (TH1/TH2 transcript hash for handshake - * derivations). Wipe before returning so the assembled label does not - * linger on the stack. */ - wc_ForceZero(info, sizeof(info)); - return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } @@ -125,6 +109,7 @@ int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, rc = wc_HmacFinal(&hmac, verifyData); wc_HmacFree(&hmac); + wc_ForceZero(&hmac, sizeof(hmac)); return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } @@ -145,6 +130,60 @@ static int wolfSPDM_DeriveKeyIvPair(byte spdmVersion, const byte* secret, iv, WOLFSPDM_AEAD_IV_SIZE); } +/* Shared post-Extract: derive HS secrets, finished keys, and data keys from + * ctx->handshakeSecret. Called by both ECDHE and PSK key derivation. */ +int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, + const byte* th1Hash) +{ + int rc; + + /* reqHsSecret = HKDF-Expand(HS, "req hs data" || TH1, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_REQ_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, + ctx->reqHsSecret, WOLFSPDM_HASH_SIZE); + if (rc == WOLFSPDM_SUCCESS) { + /* rspHsSecret = HKDF-Expand(HS, "rsp hs data" || TH1, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_RSP_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, + ctx->rspHsSecret, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Finished keys (used for VerifyData HMAC) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->reqHsSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, + ctx->reqFinishedKey, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->rspHsSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, + ctx->rspFinishedKey, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Data encryption keys + IVs (AES-256-GCM) */ + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->reqHsSecret, + ctx->reqDataKey, ctx->reqDataIv); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->rspHsSecret, + ctx->rspDataKey, ctx->rspDataIv); + } + + if (rc != WOLFSPDM_SUCCESS) { + /* wipe any partially derived material so it cannot carry into a + * reused ctx */ + wc_ForceZero(ctx->reqHsSecret, sizeof(ctx->reqHsSecret)); + wc_ForceZero(ctx->rspHsSecret, sizeof(ctx->rspHsSecret)); + wc_ForceZero(ctx->reqFinishedKey, sizeof(ctx->reqFinishedKey)); + wc_ForceZero(ctx->rspFinishedKey, sizeof(ctx->rspFinishedKey)); + wc_ForceZero(ctx->reqDataKey, sizeof(ctx->reqDataKey)); + wc_ForceZero(ctx->rspDataKey, sizeof(ctx->rspDataKey)); + wc_ForceZero(ctx->reqDataIv, sizeof(ctx->reqDataIv)); + wc_ForceZero(ctx->rspDataIv, sizeof(ctx->rspDataIv)); + } + + return rc; +} + int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash) { byte salt[WOLFSPDM_HASH_SIZE]; @@ -165,46 +204,11 @@ int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash) return WOLFSPDM_E_CRYPTO_FAIL; } - /* reqHsSecret = HKDF-Expand(HS, "req hs data" || TH1, 48) */ - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, - WOLFSPDM_HASH_SIZE, SPDM_LABEL_REQ_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, - ctx->reqHsSecret, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - /* rspHsSecret = HKDF-Expand(HS, "rsp hs data" || TH1, 48) */ - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, - WOLFSPDM_HASH_SIZE, SPDM_LABEL_RSP_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, - ctx->rspHsSecret, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - /* Finished keys (used for VerifyData HMAC) */ - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->reqHsSecret, - WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, - ctx->reqFinishedKey, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->rspHsSecret, - WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, - ctx->rspFinishedKey, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Data encryption keys + IVs (AES-256-GCM) */ - rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->reqHsSecret, - ctx->reqDataKey, ctx->reqDataIv); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - return wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->rspHsSecret, - ctx->rspDataKey, ctx->rspDataIv); + return wolfSPDM_DeriveFromHandshakeSecret(ctx, th1Hash); } +/* PSK key derivation moved to spdm_psk.c */ + int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) { byte th2Hash[WOLFSPDM_HASH_SIZE]; @@ -212,7 +216,6 @@ int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) byte masterSecret[WOLFSPDM_HASH_SIZE]; byte reqAppSecret[WOLFSPDM_HASH_SIZE]; byte rspAppSecret[WOLFSPDM_HASH_SIZE]; - byte zeroIkm[WOLFSPDM_HASH_SIZE]; int rc; if (ctx == NULL) { @@ -221,111 +224,61 @@ int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) /* Compute TH2_final = Hash(full transcript including FINISH + FINISH_RSP) */ rc = wolfSPDM_TranscriptHash(ctx, th2Hash); - if (rc != WOLFSPDM_SUCCESS) goto exit; + if (rc == WOLFSPDM_SUCCESS) { + /* salt = HKDF-Expand(HandshakeSecret, BinConcat("derived"), 48) + * Per DSP0277: "derived" label has NO context (unlike TLS 1.3 which + * uses Hash("")). libspdm confirms: bin_concat("derived", context=NULL) + */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, "derived", NULL, 0, + salt, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + byte zeroIkm[WOLFSPDM_HASH_SIZE]; + + /* MasterSecret = HKDF-Extract(salt, 0^hashSize) */ + XMEMSET(zeroIkm, 0, sizeof(zeroIkm)); + rc = wc_HKDF_Extract(WC_SHA384, salt, WOLFSPDM_HASH_SIZE, + zeroIkm, WOLFSPDM_HASH_SIZE, masterSecret); + if (rc != 0) { + rc = WOLFSPDM_E_CRYPTO_FAIL; + } + } + if (rc == WOLFSPDM_SUCCESS) { + /* reqAppSecret = HKDF-Expand(MasterSecret, "req app data" || TH2, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, masterSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_REQ_DATA, th2Hash, + WOLFSPDM_HASH_SIZE, reqAppSecret, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* rspAppSecret = HKDF-Expand(MasterSecret, "rsp app data" || TH2, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, masterSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_RSP_DATA, th2Hash, + WOLFSPDM_HASH_SIZE, rspAppSecret, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Derive new encryption keys + IVs from app data secrets */ + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, reqAppSecret, + ctx->reqDataKey, ctx->reqDataIv); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, rspAppSecret, + ctx->rspDataKey, ctx->rspDataIv); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Reset sequence numbers for application phase */ + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + wolfSPDM_DebugPrint(ctx, "App data keys derived, seq nums reset to 0\n"); + } - /* salt = HKDF-Expand(HandshakeSecret, BinConcat("derived"), 48) - * Per DSP0277: "derived" label has NO context (unlike TLS 1.3 which uses Hash("")) - * libspdm confirms: bin_concat("derived", context=NULL) */ - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, - WOLFSPDM_HASH_SIZE, "derived", NULL, 0, - salt, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) goto exit; - - /* MasterSecret = HKDF-Extract(salt, 0^hashSize) */ - XMEMSET(zeroIkm, 0, sizeof(zeroIkm)); - rc = wc_HKDF_Extract(WC_SHA384, salt, WOLFSPDM_HASH_SIZE, - zeroIkm, WOLFSPDM_HASH_SIZE, masterSecret); - if (rc != 0) { rc = WOLFSPDM_E_CRYPTO_FAIL; goto exit; } - - /* reqAppSecret = HKDF-Expand(MasterSecret, "req app data" || TH2, 48) */ - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, masterSecret, - WOLFSPDM_HASH_SIZE, SPDM_LABEL_REQ_DATA, th2Hash, WOLFSPDM_HASH_SIZE, - reqAppSecret, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) goto exit; - - /* rspAppSecret = HKDF-Expand(MasterSecret, "rsp app data" || TH2, 48) */ - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, masterSecret, - WOLFSPDM_HASH_SIZE, SPDM_LABEL_RSP_DATA, th2Hash, WOLFSPDM_HASH_SIZE, - rspAppSecret, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) goto exit; - - /* Save app secrets for KEY_UPDATE re-derivation */ - XMEMCPY(ctx->reqAppSecret, reqAppSecret, WOLFSPDM_HASH_SIZE); - XMEMCPY(ctx->rspAppSecret, rspAppSecret, WOLFSPDM_HASH_SIZE); - - /* Derive new encryption keys + IVs from app data secrets */ - rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, reqAppSecret, - ctx->reqDataKey, ctx->reqDataIv); - if (rc != WOLFSPDM_SUCCESS) goto exit; - - rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, rspAppSecret, - ctx->rspDataKey, ctx->rspDataIv); - if (rc != WOLFSPDM_SUCCESS) goto exit; - - /* Reset sequence numbers for application phase */ - ctx->reqSeqNum = 0; - ctx->rspSeqNum = 0; - - wolfSPDM_DebugPrint(ctx, "App data keys derived, seq nums reset to 0\n"); - -exit: - /* Wipe transient secret material from the stack. ctx-resident copies - * are still live and will be zeroed when wolfSPDM_Free runs. */ - wc_ForceZero(salt, sizeof(salt)); + /* Always zero sensitive intermediate key material */ wc_ForceZero(masterSecret, sizeof(masterSecret)); wc_ForceZero(reqAppSecret, sizeof(reqAppSecret)); wc_ForceZero(rspAppSecret, sizeof(rspAppSecret)); + wc_ForceZero(salt, sizeof(salt)); wc_ForceZero(th2Hash, sizeof(th2Hash)); - wc_ForceZero(zeroIkm, sizeof(zeroIkm)); - return rc; -} - -/* --- Key Update Re-derivation (DSP0277) --- */ - -int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, int updateAll) -{ - byte newReqAppSecret[WOLFSPDM_HASH_SIZE]; - byte newRspAppSecret[WOLFSPDM_HASH_SIZE]; - int rc; - - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - /* Per DSP0277: KEY_UPDATE uses "traffic upd" label with NO context. - * info = outLen(2 LE) || "spdm1.2 " || "traffic upd" */ - - /* Always update requester key */ - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->reqAppSecret, - WOLFSPDM_HASH_SIZE, SPDM_LABEL_UPDATE, NULL, 0, - newReqAppSecret, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) goto exit; - - rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, newReqAppSecret, - ctx->reqDataKey, ctx->reqDataIv); - if (rc != WOLFSPDM_SUCCESS) goto exit; - - /* Save new requester secret for future updates */ - XMEMCPY(ctx->reqAppSecret, newReqAppSecret, WOLFSPDM_HASH_SIZE); - - /* Optionally update responder key */ - if (updateAll) { - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->rspAppSecret, - WOLFSPDM_HASH_SIZE, SPDM_LABEL_UPDATE, NULL, 0, - newRspAppSecret, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) goto exit; - rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, newRspAppSecret, - ctx->rspDataKey, ctx->rspDataIv); - if (rc != WOLFSPDM_SUCCESS) goto exit; - - /* Save new responder secret for future updates */ - XMEMCPY(ctx->rspAppSecret, newRspAppSecret, WOLFSPDM_HASH_SIZE); - } - -exit: - /* Wipe transient secret material from the stack. */ - wc_ForceZero(newReqAppSecret, sizeof(newReqAppSecret)); - wc_ForceZero(newRspAppSecret, sizeof(newRspAppSecret)); return rc; } + diff --git a/src/spdm_msg.c b/src/spdm_msg.c index d6c5072..3607ee9 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -1,6 +1,6 @@ /* spdm_msg.c * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -19,8 +19,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + #include "spdm_internal.h" -#include int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) { @@ -28,7 +31,7 @@ int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) if (buf == NULL || bufSz == NULL || *bufSz < 4) return WOLFSPDM_E_BUFFER_SMALL; - /* Per SPDM spec, GET_VERSION always uses version 0x10 */ + /* Per SPDM spec, GET_VERSION always uses version 1.0 */ buf[0] = SPDM_VERSION_10; buf[1] = SPDM_GET_VERSION; buf[2] = 0x00; @@ -38,121 +41,6 @@ int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) return WOLFSPDM_SUCCESS; } -int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) -{ - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 20); - - XMEMSET(buf, 0, 20); - buf[0] = ctx->spdmVersion; /* Use negotiated version */ - buf[1] = SPDM_GET_CAPABILITIES; - buf[2] = 0x00; - buf[3] = 0x00; - /* CTExponent and reserved at offsets 4-7 */ - - /* Requester flags (4 bytes LE) */ - SPDM_Set32LE(&buf[8], ctx->reqCaps); - - /* DataTransferSize (4 LE): with chunking we advertise the MTU so the - * responder splits anything larger (which we reassemble); without it we - * advertise the full message size. */ -#ifdef WOLFSPDM_HAVE_CHUNK - SPDM_Set32LE(&buf[12], WOLFSPDM_CHUNK_BUF_SIZE); -#else - SPDM_Set32LE(&buf[12], WOLFSPDM_MAX_MSG_SIZE); -#endif - /* MaxSPDMmsgSize (4 LE): largest logical (reassembled) message we accept */ - SPDM_Set32LE(&buf[16], WOLFSPDM_MAX_MSG_SIZE); - - *bufSz = 20; - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) -{ - word32 off; - byte numStructs = 0; - int advDhe; -#ifdef WOLFSPDM_HAVE_MLKEM - int advKem; -#endif - - /* Fixed header (32 bytes) + up to 5 AlgStructs (4 bytes each) = 52. */ - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 52); - -#ifdef WOLFSPDM_HAVE_MLKEM - advDhe = (ctx->kexAdvDhe != 0); - advKem = (ctx->spdmVersion >= SPDM_VERSION_14 && ctx->kexAdvKem != 0); - if (!advDhe && !advKem) { - /* The caller forced KEM-only (kexAdvDhe == 0) but ML-KEM cannot be - * advertised at the negotiated version (< 1.4). Fail rather than - * silently re-enable DHE and downgrade the caller's PQC-only intent. */ - wolfSPDM_DebugPrint(ctx, - "NEGOTIATE: KEM-only requested but unavailable at version 0x%02x\n", - ctx->spdmVersion); - return WOLFSPDM_E_ALGO_MISMATCH; - } -#else - advDhe = 1; /* DHE is the only key-exchange method without ML-KEM */ -#endif - - XMEMSET(buf, 0, 52); - buf[0] = ctx->spdmVersion; /* Use negotiated version */ - buf[1] = SPDM_NEGOTIATE_ALGORITHMS; - buf[6] = 0x01; /* MeasurementSpecification = DMTF */ - buf[7] = 0x02; /* OtherParamsSupport = MULTI_KEY_CONN */ - - /* BaseAsymAlgo: ECDSA P-384 (bit 7) */ - buf[8] = 0x80; buf[9] = 0x00; buf[10] = 0x00; buf[11] = 0x00; - /* BaseHashAlgo: SHA-384 (bit 1) */ - buf[12] = 0x02; buf[13] = 0x00; buf[14] = 0x00; buf[15] = 0x00; - -#ifdef WOLFSPDM_HAVE_MLDSA - /* DSP0274 1.4 Table 19: PqcAsymAlgo (8-byte field at offset 16). Advertise - * ML-DSA-44/65/87 alongside ECDSA (dual-stack); the responder selects - * exactly one across BaseAsymAlgo and PqcAsymAlgo. Bytes 16-31 are - * reserved-zero before 1.4, so only emit the selection there. */ - if (ctx->spdmVersion >= SPDM_VERSION_14) { - buf[16] = (byte)(SPDM_PQC_ASYM_ALGO_ML_DSA_44 | - SPDM_PQC_ASYM_ALGO_ML_DSA_65 | - SPDM_PQC_ASYM_ALGO_ML_DSA_87); - } -#endif - - /* AlgStruct tables start at offset 32, emitted at a running offset so the - * key-exchange methods can be advertised independently (DHE, ML-KEM, or - * both). */ - off = 32; - if (advDhe) { - buf[off] = SPDM_ALG_TYPE_DHE; buf[off + 1] = 0x20; - SPDM_Set16LE(&buf[off + 2], SPDM_DHE_ALGO_SECP384R1); - off += 4; numStructs++; - } - buf[off] = SPDM_ALG_TYPE_AEAD; buf[off + 1] = 0x20; - SPDM_Set16LE(&buf[off + 2], SPDM_AEAD_ALGO_AES_256_GCM); - off += 4; numStructs++; - buf[off] = SPDM_ALG_TYPE_REQ_BASE_ASYM; buf[off + 1] = 0x20; - buf[off + 2] = 0x0F; - off += 4; numStructs++; - buf[off] = SPDM_ALG_TYPE_KEY_SCHEDULE; buf[off + 1] = 0x20; - buf[off + 2] = 0x01; - off += 4; numStructs++; -#ifdef WOLFSPDM_HAVE_MLKEM - /* DSP0274 1.4 Table 24 KEMAlg struct (AlgType 0x07). AlgCount 0x20 = - * 2-byte mask. No hybrid in 1.4: the responder selects DHE or a KEM. */ - if (advKem) { - buf[off] = SPDM_ALG_TYPE_KEM; buf[off + 1] = 0x20; - SPDM_Set16LE(&buf[off + 2], ctx->kexAdvKem); - off += 4; numStructs++; - } -#endif - - buf[2] = numStructs; /* NumAlgoStructTables */ - buf[4] = (byte)off; buf[5] = 0; /* Length */ - - *bufSz = off; - return WOLFSPDM_SUCCESS; -} - static int wolfSPDM_BuildSimpleMsg(WOLFSPDM_CTX* ctx, byte msgCode, byte* buf, word32* bufSz) { @@ -165,26 +53,17 @@ static int wolfSPDM_BuildSimpleMsg(WOLFSPDM_CTX* ctx, byte msgCode, return WOLFSPDM_SUCCESS; } -int wolfSPDM_BuildGetDigests(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) -{ - return wolfSPDM_BuildSimpleMsg(ctx, SPDM_GET_DIGESTS, buf, bufSz); -} - -int wolfSPDM_BuildGetCertificate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - int slotId, word16 offset, word16 length) -{ - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 8); - - buf[0] = ctx->spdmVersion; /* Use negotiated version */ - buf[1] = SPDM_GET_CERTIFICATE; - buf[2] = (byte)(slotId & 0x0F); - buf[3] = 0x00; - SPDM_Set16LE(&buf[4], offset); - SPDM_Set16LE(&buf[6], length); - *bufSz = 8; - - return WOLFSPDM_SUCCESS; -} +/* KEY_EXCHANGE request size: 8-byte header, 32-byte RandomData, and two ECC + * coordinates, plus a config-specific OpaqueData block. Keep + * WOLFSPDM_KEYEX_OPAQUE_SZ in sync with the OpaqueData written below. */ +#define WOLFSPDM_KEYEX_FIXED_SZ (40 + 2 * WOLFSPDM_ECC_KEY_SIZE) +#ifdef WOLFSPDM_NUVOTON + #define WOLFSPDM_KEYEX_OPAQUE_SZ 14 +#elif defined(WOLFSPDM_NATIONS) + #define WOLFSPDM_KEYEX_OPAQUE_SZ 2 +#else + #define WOLFSPDM_KEYEX_OPAQUE_SZ 22 +#endif int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { @@ -193,303 +72,149 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) byte pubKeyY[WOLFSPDM_ECC_KEY_SIZE]; word32 pubKeyXSz = sizeof(pubKeyX); word32 pubKeyYSz = sizeof(pubKeyY); -#ifdef WOLFSPDM_HAVE_MLKEM - word32 ekSz = 0; -#endif int rc; - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 180); + /* Require exactly the encoded request size */ + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, + WOLFSPDM_KEYEX_FIXED_SZ + WOLFSPDM_KEYEX_OPAQUE_SZ); - XMEMSET(buf, 0, *bufSz); + rc = wolfSPDM_GenerateEphemeralKey(ctx); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &pubKeyXSz, + pubKeyY, &pubKeyYSz); - /* Use negotiated SPDM version (not hardcoded 1.2) */ - buf[offset++] = ctx->spdmVersion; - buf[offset++] = SPDM_KEY_EXCHANGE; - buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ - /* SlotIDParam: authenticate the slot selected during GET_CERTIFICATE. - * Hard-coding 0 would break responders whose DIGESTS SlotMask omits - * slot 0 (the requester would then KEY_EXCHANGE against a different - * or empty slot than the one whose chain it just fetched). */ - buf[offset++] = (byte)(ctx->currentSlotId & 0x0F); + if (rc == WOLFSPDM_SUCCESS) { + XMEMSET(buf, 0, *bufSz); + + /* Use negotiated SPDM version (not hardcoded 1.2) */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_KEY_EXCHANGE; + buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ +#ifdef WOLFSPDM_TCG + buf[offset++] = 0xFF; /* SlotID = 0xFF (no cert, use provisioned public key) */ +#else + buf[offset++] = 0x00; /* SlotID = 0 (certificate slot 0) */ +#endif - /* ReqSessionID (2 LE) */ - buf[offset++] = (byte)(ctx->reqSessionId & 0xFF); - buf[offset++] = (byte)((ctx->reqSessionId >> 8) & 0xFF); + /* ReqSessionID (2 LE) */ + buf[offset++] = (byte)(ctx->reqSessionId & 0xFF); + buf[offset++] = (byte)((ctx->reqSessionId >> 8) & 0xFF); - buf[offset++] = 0x00; /* SessionPolicy */ - buf[offset++] = 0x00; /* Reserved */ + buf[offset++] = 0x00; /* SessionPolicy */ + buf[offset++] = 0x00; /* Reserved */ - /* RandomData (32 bytes) */ - rc = wolfSPDM_GetRandom(ctx, &buf[offset], WOLFSPDM_RANDOM_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - offset += WOLFSPDM_RANDOM_SIZE; - - /* ExchangeData: the negotiated method's public value (DSP0274 1.4 Table 77). - * ML-KEM sends the encapsulation key ek; ECDHE sends X || Y. Every branch - * below (incl. the unrecognized-kexType else) assigns rc. */ -#ifdef WOLFSPDM_HAVE_MLKEM - if (ctx->kexType == WOLFSPDM_KEX_MLKEM) { - /* Reserve the trailing OpaqueData block so handing the remaining buffer - * to GenerateMlKemKey (which only bounds the ek) cannot leave the - * OpaqueData write below to overrun. */ - if (*bufSz < offset + WOLFSPDM_KEX_OPAQUE_LEN) { - rc = WOLFSPDM_E_BUFFER_SMALL; - } - else { - ekSz = *bufSz - offset - WOLFSPDM_KEX_OPAQUE_LEN; - rc = wolfSPDM_GenerateMlKemKey(ctx, &buf[offset], &ekSz); - if (rc == WOLFSPDM_SUCCESS) { - offset += ekSz; - } - } - } - else -#endif - if (ctx->kexType == WOLFSPDM_KEX_ECDHE) { - rc = wolfSPDM_GenerateEphemeralKey(ctx); - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &pubKeyXSz, - pubKeyY, &pubKeyYSz); - } + /* RandomData (32 bytes) */ + rc = wolfSPDM_GetRandom(ctx, &buf[offset], WOLFSPDM_RANDOM_SIZE); if (rc == WOLFSPDM_SUCCESS) { + offset += WOLFSPDM_RANDOM_SIZE; + + /* ExchangeData: X || Y */ XMEMCPY(&buf[offset], pubKeyX, WOLFSPDM_ECC_KEY_SIZE); offset += WOLFSPDM_ECC_KEY_SIZE; XMEMCPY(&buf[offset], pubKeyY, WOLFSPDM_ECC_KEY_SIZE); offset += WOLFSPDM_ECC_KEY_SIZE; - } - } - else { - rc = WOLFSPDM_E_BAD_STATE; /* unrecognized kexType: fail closed */ - } - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* OpaqueData for secured message version negotiation. DSP0277 v1.2 is - * the current spec, defining secured-message versions 1.0, 1.1, 1.2. - * Higher SPDM control versions reuse the 1.2 secured-message format; - * there is no DSP0277 1.3 or 1.4. OpaqueLength must be a multiple of - * 4 per DSP0274 - the 20-byte fixed block satisfies that. */ - buf[offset++] = 0x14; /* OpaqueLength = 20 */ - buf[offset++] = 0x00; - buf[offset++] = 0x01; buf[offset++] = 0x00; /* TotalElements */ - buf[offset++] = 0x00; buf[offset++] = 0x00; /* Reserved */ - buf[offset++] = 0x00; buf[offset++] = 0x00; - buf[offset++] = 0x09; buf[offset++] = 0x00; /* DataSize */ - buf[offset++] = 0x01; /* Registry ID = DMTF */ - buf[offset++] = 0x01; /* VendorLen */ - buf[offset++] = 0x03; buf[offset++] = 0x00; /* VersionCount */ - buf[offset++] = 0x10; buf[offset++] = 0x00; /* 1.0 */ - buf[offset++] = 0x11; buf[offset++] = 0x00; /* 1.1 */ - buf[offset++] = 0x12; buf[offset++] = 0x00; /* 1.2 */ - buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding to mult of 4 */ - - *bufSz = offset; - return WOLFSPDM_SUCCESS; -} -/* --- Shared Signing Helpers --- */ + /* OpaqueData for secured message version negotiation */ +#ifdef WOLFSPDM_NUVOTON + /* Nuvoton vendor format: 12 bytes */ + buf[offset++] = 0x0c; buf[offset++] = 0x00; + buf[offset++] = 0x00; buf[offset++] = 0x00; + buf[offset++] = 0x05; buf[offset++] = 0x00; + buf[offset++] = 0x01; buf[offset++] = 0x01; + buf[offset++] = 0x01; buf[offset++] = 0x00; + buf[offset++] = 0x10; buf[offset++] = 0x00; + buf[offset++] = 0x00; buf[offset++] = 0x00; +#elif defined(WOLFSPDM_NATIONS) + /* Empty OpaqueData — Nations only accepts OpaqueLength=0 */ + buf[offset++] = 0x00; buf[offset++] = 0x00; +#else + /* Standard SPDM 1.2+ OpaqueData format: 20 bytes */ + buf[offset++] = 0x14; /* OpaqueLength = 20 */ + buf[offset++] = 0x00; + buf[offset++] = 0x01; buf[offset++] = 0x00; /* TotalElements */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Reserved */ + buf[offset++] = 0x00; buf[offset++] = 0x00; + buf[offset++] = 0x09; buf[offset++] = 0x00; /* DataSize */ + buf[offset++] = 0x01; /* Registry ID */ + buf[offset++] = 0x01; /* VendorLen */ + buf[offset++] = 0x03; buf[offset++] = 0x00; /* VersionCount */ + buf[offset++] = 0x10; buf[offset++] = 0x00; /* 1.0 */ + buf[offset++] = 0x11; buf[offset++] = 0x00; /* 1.1 */ + buf[offset++] = 0x12; buf[offset++] = 0x00; /* 1.2 */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding */ +#endif -/* Size of the negotiated signature field (DSP0274 1.4 Table 19 SigLen). */ -static word32 wolfSPDM_GetSigSize(const WOLFSPDM_CTX* ctx) -{ -#ifdef WOLFSPDM_HAVE_MLDSA - if (ctx->asymType == WOLFSPDM_ASYM_MLDSA) { - if (ctx->pqcAsymSel == SPDM_PQC_ASYM_ALGO_ML_DSA_44) { - return WOLFSPDM_MLDSA44_SIG_SIZE; - } - if (ctx->pqcAsymSel == SPDM_PQC_ASYM_ALGO_ML_DSA_87) { - return WOLFSPDM_MLDSA87_SIG_SIZE; + *bufSz = offset; } - return WOLFSPDM_MLDSA65_SIG_SIZE; } -#else - (void)ctx; -#endif - return WOLFSPDM_ECC_SIG_SIZE; + + return rc; } -/* Assemble the SPDM 1.2+ data_to_be_signed message M per DSP0274 Sec. 15: - * M = combined_spdm_prefix || message_hash - * combined_spdm_prefix = "dmtf-spdm-v1.X.*" x4 (64) || zero_pad || spdm_context - * (100 bytes total) - * message_hash = inputDigest (Hash of data_to_be_signed, 48 bytes SHA-384) - * contextStr already carries the "responder-"/"requester-" spdm_context prefix. - * outMsg must hold >= 148 bytes. */ -static int wolfSPDM_BuildSignedMsg(byte spdmVersion, +/* ----- Shared Signing Helpers ----- */ + +/* Build SPDM 1.2+ signed hash per DSP0274: + * M = combined_spdm_prefix || zero_pad || context_str || inputDigest + * outputDigest = Hash(M) + * + * combined_spdm_prefix = "dmtf-spdm-v1.X.*" x4 = 64 bytes + * zero_pad = (36 - contextStrLen) bytes of 0x00 + * context_str = signing context string (variable length, max 36) */ +int wolfSPDM_BuildSignedHash(byte spdmVersion, const char* contextStr, word32 contextStrLen, - const byte* inputDigest, byte* outMsg, word32* outMsgLen) + const byte* inputDigest, byte* outputDigest) { + byte signMsg[200]; /* 64 + 36 + 48 = 148 bytes max */ word32 signMsgLen = 0; word32 zeroPadLen; byte majorVer, minorVer; - int i; - - /* Reject overlong context strings before computing zeroPadLen (which - * is 36 - contextStrLen and would underflow). */ - if (contextStr == NULL || contextStrLen > 36 || - outMsg == NULL || outMsgLen == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } + int i, rc; majorVer = (byte)('0' + ((spdmVersion >> 4) & 0xF)); minorVer = (byte)('0' + (spdmVersion & 0xF)); - /* spdm_prefix: "dmtf-spdm-v1.X.*" x4 = 64 bytes */ + /* combined_spdm_prefix: "dmtf-spdm-v1.X.*" x4 = 64 bytes */ for (i = 0; i < 4; i++) { - XMEMCPY(&outMsg[signMsgLen], "dmtf-spdm-v1.2.*", 16); - outMsg[signMsgLen + 11] = majorVer; - outMsg[signMsgLen + 13] = minorVer; - outMsg[signMsgLen + 15] = '*'; + XMEMCPY(&signMsg[signMsgLen], "dmtf-spdm-v1.2.*", 16); + signMsg[signMsgLen + 11] = majorVer; + signMsg[signMsgLen + 13] = minorVer; + signMsg[signMsgLen + 15] = '*'; signMsgLen += 16; } - /* Zero padding: 36 - contextStrLen bytes (combined prefix is 100 bytes) */ + /* Zero padding: 36 - contextStrLen bytes */ + if (contextStrLen > 36) { + return WOLFSPDM_E_INVALID_ARG; + } zeroPadLen = 36 - contextStrLen; - XMEMSET(&outMsg[signMsgLen], 0x00, zeroPadLen); + XMEMSET(&signMsg[signMsgLen], 0x00, zeroPadLen); signMsgLen += zeroPadLen; - /* spdm_context string */ - XMEMCPY(&outMsg[signMsgLen], contextStr, contextStrLen); + /* Signing context string */ + XMEMCPY(&signMsg[signMsgLen], contextStr, contextStrLen); signMsgLen += contextStrLen; - /* message_hash */ - XMEMCPY(&outMsg[signMsgLen], inputDigest, WOLFSPDM_HASH_SIZE); + /* Input digest */ + XMEMCPY(&signMsg[signMsgLen], inputDigest, WOLFSPDM_HASH_SIZE); signMsgLen += WOLFSPDM_HASH_SIZE; - *outMsgLen = signMsgLen; - return WOLFSPDM_SUCCESS; -} - -/* Build the pre-hashed signing input used by RSA/ECDSA: outputDigest = Hash(M). - * ECDSA hashes M internally, so verify_hash takes Hash(M). */ -static int wolfSPDM_BuildSignedHash(byte spdmVersion, - const char* contextStr, word32 contextStrLen, - const byte* inputDigest, byte* outputDigest) -{ - byte signMsg[200]; /* 64 + 36 + 48 = 148 bytes max */ - word32 signMsgLen = 0; - int rc; - - rc = wolfSPDM_BuildSignedMsg(spdmVersion, contextStr, contextStrLen, - inputDigest, signMsg, &signMsgLen); - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_Sha384Hash(outputDigest, signMsg, signMsgLen, - NULL, 0, NULL, 0); - } - /* signMsg embeds the inputDigest (a transcript-state hash). Wipe it - * before returning so the assembled signing input does not linger on - * the stack frame. */ - wc_ForceZero(signMsg, sizeof(signMsg)); - return rc; -} - -/* Verify an SPDM ECDSA signature (raw r||s format) against a digest - * using the responder's public key stored in ctx. */ -static int wolfSPDM_VerifyEccSig(WOLFSPDM_CTX* ctx, - const byte* sigRaw, word32 sigRawSz, - const byte* digest, word32 digestSz) -{ - byte derSig[256]; - word32 derSigSz = sizeof(derSig); - const byte* sigR = sigRaw; - const byte* sigS = sigRaw + (sigRawSz / 2); - int verified = 0; - int rc; - - rc = wc_ecc_rs_raw_to_sig(sigR, sigRawSz / 2, - sigS, sigRawSz / 2, derSig, &derSigSz); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "ECC rs_raw_to_sig failed: %d\n", rc); - return WOLFSPDM_E_CRYPTO_FAIL; - } - - rc = wc_ecc_verify_hash(derSig, derSigSz, digest, digestSz, - &verified, &ctx->responderPubKey.ecc); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "ECC verify_hash failed: %d\n", rc); - /* Internal wolfCrypt failure (memory pressure, missing curve, etc.) - * - distinguish from an actual bad signature so the caller doesn't - * impeach the responder identity over a transient infra issue. */ - return WOLFSPDM_E_CRYPTO_FAIL; - } - - /* verified == 1 means the signature is good; 0 means tamper/wrong key. */ - return (verified == 1) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_BAD_SIGNATURE; -} - -/* ECDSA signing tail: hash M, then verify the raw r||s signature. */ -static int wolfSPDM_VerifyEcdsaSigned(WOLFSPDM_CTX* ctx, - const char* contextStr, word32 contextStrLen, - byte* digest, const byte* sig, word32 sigSz) -{ - int rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, contextStr, - contextStrLen, digest, digest); - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_VerifyEccSig(ctx, sig, sigSz, digest, WOLFSPDM_HASH_SIZE); - } - return rc; -} - -#ifdef WOLFSPDM_HAVE_MLDSA -/* Verify an SPDM ML-DSA signature against message_hash using the responder's - * ML-DSA public key. Per DSP0274 1.4 Sec. 15.5, SPDM uses Algorithm 2 (pure - * ML-DSA.Sign), NOT the pre-hash variant: M = combined_spdm_prefix || - * message_hash, and the ML-DSA ctx parameter is spdm_context (contextStr). */ -static int wolfSPDM_VerifyMlDsaSig(WOLFSPDM_CTX* ctx, - const char* contextStr, word32 contextStrLen, - const byte* messageHash, const byte* sig, word32 sigSz) -{ - byte signMsg[200]; /* combined_spdm_prefix(100) + message_hash(48) = 148 */ - word32 signMsgLen = 0; - int verified = 0; - int rc; - - rc = wolfSPDM_BuildSignedMsg(ctx->spdmVersion, contextStr, contextStrLen, - messageHash, signMsg, &signMsgLen); - if (rc == WOLFSPDM_SUCCESS) { - rc = wc_MlDsaKey_VerifyCtx(&ctx->responderPubKey.mldsa, sig, sigSz, - (const byte*)contextStr, (byte)contextStrLen, - signMsg, signMsgLen, &verified); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "ML-DSA VerifyCtx failed: %d\n", rc); - rc = WOLFSPDM_E_CRYPTO_FAIL; - } - else { - rc = (verified == 1) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_BAD_SIGNATURE; - } - } - /* signMsg embeds message_hash (transcript-state). Wipe before return. */ - wc_ForceZero(signMsg, sizeof(signMsg)); - return rc; -} -#endif /* WOLFSPDM_HAVE_MLDSA */ + /* Hash M */ + rc = wolfSPDM_Sha384Hash(outputDigest, signMsg, signMsgLen, + NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) return rc; -/* Verify an SPDM signature over message_hash using whichever asymmetric family - * the responder selected in NEGOTIATE_ALGORITHMS. digest holds message_hash; - * the ECDSA path overwrites it with Hash(M). Returns the raw verify rc. */ -static int wolfSPDM_VerifySig(WOLFSPDM_CTX* ctx, - const char* contextStr, word32 contextStrLen, - byte* digest, const byte* sig, word32 sigSz) -{ -#ifdef WOLFSPDM_HAVE_MLDSA - if (ctx->asymType == WOLFSPDM_ASYM_MLDSA) { - return wolfSPDM_VerifyMlDsaSig(ctx, contextStr, contextStrLen, - digest, sig, sigSz); - } -#endif - return wolfSPDM_VerifyEcdsaSigned(ctx, contextStr, contextStrLen, - digest, sig, sigSz); + return WOLFSPDM_SUCCESS; } int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { byte th2Hash[WOLFSPDM_HASH_SIZE]; byte verifyData[WOLFSPDM_HASH_SIZE]; + byte signature[WOLFSPDM_ECC_POINT_SIZE]; /* 96 bytes for P-384 */ + word32 sigSz = sizeof(signature); word32 offset = 4; /* Start after header */ word32 minSz; + int mutualAuth = 0; int rc; /* Check arguments first before any ctx dereference */ @@ -497,18 +222,36 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) return WOLFSPDM_E_INVALID_ARG; } - /* Check buffer size: header(4) + [OpaqueLength(2) for 1.4+] + HMAC(48) */ + /* Mutual auth is enabled when the responder requested it (MutAuthRequested + * bit 0) AND we have a requester key pair to sign with */ + if ((ctx->mutAuthRequested & 0x01) && ctx->flags.hasReqKeyPair) { + mutualAuth = 1; + wolfSPDM_DebugPrint(ctx, "FINISH: Mutual auth ENABLED " + "(MutAuth=0x%02x ReqSlot=0x%02x)\n", + ctx->mutAuthRequested, ctx->reqSlotIdParam); + } + + /* Check buffer size: header(4) + [OpaqueLength(2) for 1.4+] + + * [signature(96) for mutual auth] + HMAC(48) */ minSz = 4 + WOLFSPDM_HASH_SIZE; /* header + HMAC */ if (ctx->spdmVersion >= SPDM_VERSION_14) minSz += 2; /* OpaqueLength */ + if (mutualAuth) + minSz += WOLFSPDM_ECC_POINT_SIZE; /* Signature */ if (*bufSz < minSz) return WOLFSPDM_E_BUFFER_SMALL; - /* Build FINISH header (mutual auth not supported in standard requester) */ + /* Build FINISH header */ buf[0] = ctx->spdmVersion; buf[1] = SPDM_FINISH; - buf[2] = 0x00; /* Param1: No signature */ - buf[3] = 0x00; /* Param2: SlotID = 0 when no signature */ + if (mutualAuth) { + buf[2] = 0x01; /* Param1: Signature field is included */ + /* Param2: For PUB_KEY_ID mode, shall be 0xFF per DSP0274 */ + buf[3] = 0xFF; + } else { + buf[2] = 0x00; /* Param1: No signature */ + buf[3] = 0x00; /* Param2: SlotID = 0 when no signature */ + } /* SPDM 1.4 adds OpaqueLength(2) + OpaqueData(var) after header */ if (ctx->spdmVersion >= SPDM_VERSION_14) { @@ -516,43 +259,65 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) buf[offset++] = 0x00; } - /* Add FINISH header (+ OpaqueLength for 1.4) to transcript for TH2 */ - rc = wolfSPDM_TranscriptAdd(ctx, buf, offset); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; - } + rc = WOLFSPDM_SUCCESS; - /* TH2 = Hash(transcript with FINISH header) */ - rc = wolfSPDM_TranscriptHash(ctx, th2Hash); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + /* Mutual auth: add Hash(Cm_requester) to transcript between message_k + * and FINISH header. For PUB_KEY_ID mode, Cm = SHA-384(TPMT_PUBLIC) + * of the requester's public key (matching how Ct is computed for + * responder per TCG SPDM binding). */ +#ifdef WOLFSPDM_TCG + if (rc == WOLFSPDM_SUCCESS && mutualAuth && ctx->reqPubKeyTPMTLen > 0) { + byte cmHash[WOLFSPDM_HASH_SIZE]; + rc = wolfSPDM_Sha384Hash(cmHash, ctx->reqPubKeyTPMT, + ctx->reqPubKeyTPMTLen, NULL, 0, NULL, 0); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, cmHash, WOLFSPDM_HASH_SIZE); } +#endif - /* RequesterVerifyData = HMAC(reqFinishedKey, TH2) where TH2 is the - * transcript hash through the FINISH header. */ - rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, verifyData); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + /* Add FINISH header to transcript, compute TH2 */ + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, buf, offset); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + if (rc == WOLFSPDM_SUCCESS) + XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); + + /* Mutual auth: sign TH2, add signature to transcript, recompute TH2 */ + if (rc == WOLFSPDM_SUCCESS && mutualAuth) { + byte signMsgHash[WOLFSPDM_HASH_SIZE]; + + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "requester-finish signing", 24, th2Hash, signMsgHash); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SignHash(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, + signature, &sigSz); + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&buf[offset], signature, WOLFSPDM_ECC_POINT_SIZE); + offset += WOLFSPDM_ECC_POINT_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, signature, + WOLFSPDM_ECC_POINT_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); } - XMEMCPY(&buf[offset], verifyData, WOLFSPDM_HASH_SIZE); - offset += WOLFSPDM_HASH_SIZE; - - /* Add RequesterVerifyData to transcript for TH2_final (app data key derivation) */ - rc = wolfSPDM_TranscriptAdd(ctx, verifyData, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + /* RequesterVerifyData = HMAC(reqFinishedKey, TH2) */ + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, + verifyData); + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&buf[offset], verifyData, WOLFSPDM_HASH_SIZE); + offset += WOLFSPDM_HASH_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, verifyData, WOLFSPDM_HASH_SIZE); } + if (rc == WOLFSPDM_SUCCESS) + *bufSz = offset; - *bufSz = offset; - rc = WOLFSPDM_SUCCESS; - -cleanup: - /* th2Hash is the FINISH transcript-state digest; verifyData is the - * requester FINISH MAC keyed with reqFinishedKey. Both must not linger - * on the stack frame after this call returns. */ + /* Always zero sensitive stack buffers */ wc_ForceZero(th2Hash, sizeof(th2Hash)); wc_ForceZero(verifyData, sizeof(verifyData)); + wc_ForceZero(signature, sizeof(signature)); return rc; } @@ -580,24 +345,25 @@ int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode) int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { word16 entryCount; - word16 maxEntries; + word32 i; byte highestVersion = 0; /* No version found yet */ byte maxVer; - word32 i; - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 6); + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 6); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_VERSION, WOLFSPDM_E_VERSION_MISMATCH); - /* Parse VERSION response: - * Offset 4-5: VersionNumberEntryCount (LE) + /* VersionNumberEntryCount is the one-byte field at offset 5 (byte 4 + * reserved) per DSP0274; older wolfTPM responders placed it at + * offset 4, so fall back to that when offset 5 is zero. * Offset 6+: VersionNumberEntry array (2 bytes each, LE) */ - entryCount = SPDM_Get16LE(&buf[4]); + entryCount = buf[5]; + if (entryCount == 0) { + entryCount = buf[4]; + } - /* Cap entryCount to what actually fits in the buffer to prevent - * overflow on exotic compilers where i*2 could wrap */ - maxEntries = (word16)((bufSz - 6) / 2); - if (entryCount > maxEntries) { - entryCount = maxEntries; + /* Reject a truncated entry list instead of negotiating from a subset */ + if ((word32)6 + (word32)entryCount * 2 > bufSz) { + return WOLFSPDM_E_VERSION_MISMATCH; } /* Find highest mutually supported version. @@ -605,7 +371,7 @@ int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) * that both sides support. We support WOLFSPDM_MIN_SPDM_VERSION * through WOLFSPDM_MAX_SPDM_VERSION (or ctx->maxVersion if set). */ maxVer = (ctx->maxVersion != 0) ? ctx->maxVersion - : WOLFSPDM_MAX_SPDM_VERSION; + : WOLFSPDM_MAX_SPDM_VERSION; for (i = 0; i < entryCount; i++) { /* Each entry is 2 bytes; high byte (offset +1) is Major.Minor */ byte ver = buf[6 + i * 2 + 1]; @@ -630,537 +396,126 @@ int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) return WOLFSPDM_SUCCESS; } -int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) -{ - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 12); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CAPABILITIES, WOLFSPDM_E_CAPS_MISMATCH); - - /* DSP0274 Table 12: CAPABILITIES response layout - * buf[4]: CTExponent - * buf[8-11]: Flags (rspCaps) - * buf[12-15]: DataTransferSize (SPDM 1.2+) - * buf[16-19]: MaxSPDMmsgSize (SPDM 1.2+) - * The 1.2+ fields are populated when the response carries them; pre-1.2 - * leaves them as 0 and downstream paths fall back to fixed defaults. */ - ctx->ctExponent = buf[4]; - ctx->rspCaps = SPDM_Get32LE(&buf[8]); - if (ctx->spdmVersion >= SPDM_VERSION_12 && bufSz >= 20) { - ctx->dataTransferSize = SPDM_Get32LE(&buf[12]); - ctx->maxSpdmMsgSize = SPDM_Get32LE(&buf[16]); - } - ctx->state = WOLFSPDM_STATE_CAPS; - - wolfSPDM_DebugPrint(ctx, "Responder caps: 0x%08x\n", ctx->rspCaps); - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) -{ - word32 baseAsymAlgo; - word32 baseHashAlgo; - word32 pqcAsymSel; - word16 declaredLen; - byte numAlgs; - byte extAsymCount; - byte extHashCount; - byte ai; - word32 algStart; - word32 off; - int dheOk = 0; - int aeadOk = 0; - int ksOk = 0; - int kemOk = 0; - - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 36); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_ALGORITHMS, WOLFSPDM_E_ALGO_MISMATCH); - - /* DSP0274 Table 18: Length (offset 4-5, LE) is the entire response size - * including header. Reject responses whose declared length does not - * match the received buffer. */ - declaredLen = SPDM_Get16LE(&buf[4]); - if (declaredLen != bufSz) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: declared Length %u != bufSz %u\n", - declaredLen, bufSz); - return WOLFSPDM_E_ALGO_MISMATCH; - } - - /* DSP0274 Table 18 offsets 6-7: MeasurementSpecificationSel and - * OtherParamsSel. We advertise DMTF (0x01) and OpaqueDataFormat1 (0x02) - * in NEGOTIATE_ALGORITHMS; reject responders that select a different - * bit or zero. */ - if (buf[6] != 0x01) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: MeasurementSpecificationSel != DMTF (0x%02x)\n", - buf[6]); - return WOLFSPDM_E_ALGO_MISMATCH; - } - if (ctx->spdmVersion >= SPDM_VERSION_12 && buf[7] != 0x02) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: OtherParamsSel != OpaqueDataFormat1 (0x%02x)\n", - buf[7]); - return WOLFSPDM_E_ALGO_MISMATCH; - } - - /* Validate negotiated algorithms match Algorithm Set B. - * ALGORITHMS response layout (DSP0274 Table 18): - * Offset 8-11: MeasurementHashAlgo (4 LE) - * Offset 12-15: BaseAsymSel (4 LE) - * Offset 16-19: BaseHashSel (4 LE) - * Note: Response has MeasurementHashAlgo before BaseAsymSel, - * unlike the request which has BaseAsymAlgo at offset 8. */ - baseAsymAlgo = SPDM_Get32LE(&buf[12]); - baseHashAlgo = SPDM_Get32LE(&buf[16]); - - /* Defensive: a second NEGOTIATE_ALGORITHMS via the fine-grained API could - * flip asymType while a responder key from a prior round is still live, - * which would later free the wrong union member. Drop any live key first - * so the union member always matches the asymType set below. */ - if (ctx->flags.hasResponderPubKey) { - wolfSPDM_FreeResponderPubKey(ctx); - ctx->flags.hasResponderPubKey = 0; - } - - /* Same hazard for the ephemeral key union: a prior handshake may have left a - * key live whose union member is named by the OLD ctx->kexType. Free it now, - * before kexType is reassigned below, so the free dispatches on the matching - * member (otherwise a reconnect that switches DHE<->ML-KEM would type-confuse - * the free in wolfSPDM_FreeEphemeralKey). */ - if (ctx->flags.ephemeralKeyInit) { - wolfSPDM_FreeEphemeralKey(ctx); - ctx->flags.ephemeralKeyInit = 0; - } - - /* DSP0274 1.4 Table 20: PqcAsymSel (offset 20). Present from 1.4; earlier - * versions leave these bytes reserved-zero. The spec caps the combined - * bit count of BaseAsymSel and PqcAsymSel at one, so exactly one of the - * two fields carries the selected signature algorithm. */ - pqcAsymSel = 0; - if (ctx->spdmVersion >= SPDM_VERSION_14) { - pqcAsymSel = SPDM_Get32LE(&buf[20]); - } - - if (pqcAsymSel != 0) { -#ifdef WOLFSPDM_HAVE_MLDSA - if (baseAsymAlgo != 0) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: BaseAsymSel and PqcAsymSel both set " - "(0x%08x/0x%08x)\n", baseAsymAlgo, pqcAsymSel); - return WOLFSPDM_E_ALGO_MISMATCH; - } - if (pqcAsymSel != SPDM_PQC_ASYM_ALGO_ML_DSA_44 && - pqcAsymSel != SPDM_PQC_ASYM_ALGO_ML_DSA_65 && - pqcAsymSel != SPDM_PQC_ASYM_ALGO_ML_DSA_87) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: unsupported PqcAsymSel (0x%08x)\n", pqcAsymSel); - return WOLFSPDM_E_ALGO_MISMATCH; - } - ctx->asymType = WOLFSPDM_ASYM_MLDSA; - ctx->pqcAsymSel = pqcAsymSel; -#else - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: PqcAsymSel set but ML-DSA not built in (0x%08x)\n", - pqcAsymSel); - return WOLFSPDM_E_ALGO_MISMATCH; -#endif - } - else { - /* Per DSP0274 Table 18, BaseAsymSel carries the responder's SELECTED - * algorithm - exactly one bit. Strict equality enforces Algorithm - * Set B rather than accepting any superset. */ - if (baseAsymAlgo != SPDM_ASYM_ALGO_ECDSA_P384) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: BaseAsymSel != ECDSA_P384 (0x%08x)\n", - baseAsymAlgo); - return WOLFSPDM_E_ALGO_MISMATCH; - } - ctx->asymType = WOLFSPDM_ASYM_ECDSA; - ctx->pqcAsymSel = 0; - } - if (baseHashAlgo != SPDM_HASH_ALGO_SHA_384) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: BaseHashSel != SHA_384 (0x%08x)\n", baseHashAlgo); - return WOLFSPDM_E_ALGO_MISMATCH; - } - - /* AlgStruct tables follow the fixed-size response header. Walk them - * and confirm DHE, AEAD, and KeySchedule selections are Algorithm - * Set B (SECP_384_R1 / AES_256_GCM / SPDM). Require all three to be - * present and match - a responder offering AlgStructCount=0 must not - * bypass the Set-B contract. Layout per DSP0274 Table 18: - * each struct: AlgType(1) | AlgCount(1) | AlgSupported(2 LE) | ext... - * - * DSP0274 Table 18: ExtAsymSelCount (buf[32]) + ExtHashSelCount - * (buf[33]) push the AlgStruct array past the fixed 36 bytes. - * Skip both ExtAsym/ExtHash tables (each entry is 4 bytes) before - * walking AlgStructs. */ - numAlgs = buf[2]; /* Param1 = AlgStructCount */ - extAsymCount = (bufSz >= 33) ? buf[32] : 0; - extHashCount = (bufSz >= 34) ? buf[33] : 0; - algStart = (word32)36 + - (word32)extAsymCount * 4 + (word32)extHashCount * 4; - if (algStart > bufSz) { - return WOLFSPDM_E_ALGO_MISMATCH; - } - off = algStart; - for (ai = 0; ai < numAlgs && off + 4 <= bufSz; ai++) { - byte algType = buf[off]; - byte algCount = buf[off + 1]; - word16 algSel = SPDM_Get16LE(&buf[off + 2]); - /* Per DSP0274 Table 16: AlgCount low nibble = ExtAlgCount - * (each ExtAlg is 4 bytes); high nibble = fixed-size marker - * (= 2 in current spec). Use the LOW nibble for extLen. */ - word32 extLen = ((word32)(algCount & 0x0F)) * 4; - switch (algType) { - case SPDM_ALG_TYPE_DHE: - /* algSel == 0 means the responder did not pick a DHE group - * (it selected a KEM instead); only a non-zero value must be - * the supported group. */ - if (algSel == SPDM_DHE_ALGO_SECP384R1) { - dheOk = 1; - } - else if (algSel != 0) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: DHE not SECP_384_R1 (0x%04x)\n", algSel); - return WOLFSPDM_E_ALGO_MISMATCH; - } - break; -#ifdef WOLFSPDM_HAVE_MLKEM - case SPDM_ALG_TYPE_KEM: - /* DSP0274 1.4 Table 24: the responder selected one ML-KEM set - * (or 0 if it picked DHE instead). */ - if (algSel == SPDM_KEM_ALGO_ML_KEM_512 || - algSel == SPDM_KEM_ALGO_ML_KEM_768 || - algSel == SPDM_KEM_ALGO_ML_KEM_1024) { - kemOk = 1; - ctx->kemAlgSel = algSel; - } - else if (algSel != 0) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: unsupported KEM (0x%04x)\n", algSel); - return WOLFSPDM_E_ALGO_MISMATCH; - } - break; -#endif - case SPDM_ALG_TYPE_AEAD: - if (algSel != SPDM_AEAD_ALGO_AES_256_GCM) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: AEAD not AES_256_GCM (0x%04x)\n", algSel); - return WOLFSPDM_E_ALGO_MISMATCH; - } - aeadOk = 1; - break; - case SPDM_ALG_TYPE_KEY_SCHEDULE: - if (algSel != SPDM_KEY_SCHEDULE_SPDM) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: KeySchedule not SPDM (0x%04x)\n", algSel); - return WOLFSPDM_E_ALGO_MISMATCH; - } - ksOk = 1; - break; - default: break; - } - off += 4 + extLen; - } - if (!aeadOk || !ksOk) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: missing required AlgStruct(s) aead=%d ks=%d\n", - aeadOk, ksOk); - return WOLFSPDM_E_ALGO_MISMATCH; - } - /* Exactly one key-exchange method: a DHE group or a KEM, never both and - * never neither (DSP0274 1.4: no hybrid). */ - if (dheOk + kemOk != 1) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: key-exchange must be exactly one dhe=%d kem=%d\n", - dheOk, kemOk); - return WOLFSPDM_E_ALGO_MISMATCH; - } - ctx->kexType = kemOk ? WOLFSPDM_KEX_MLKEM : WOLFSPDM_KEX_ECDHE; - if (!kemOk) { - ctx->kemAlgSel = 0; /* no stale KEM selection on the ECDHE branch */ - } - - wolfSPDM_DebugPrint(ctx, "ALGORITHMS: BaseAsym=0x%08x BaseHash=0x%08x\n", - baseAsymAlgo, baseHashAlgo); - - ctx->state = WOLFSPDM_STATE_ALGO; - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_ParseDigests(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) -{ - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 4); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_DIGESTS, WOLFSPDM_E_CERT_FAIL); - - /* DSP0274 Sec. 10.5: Param1 = SlotMask, bit i = slot i populated. - * Stash it so GetCertificate can pick a populated slot rather than - * blindly requesting slot 0. */ - ctx->slotMask = buf[2]; - ctx->state = WOLFSPDM_STATE_DIGESTS; - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz, - word16* portionLen, word16* remainderLen) -{ - if (portionLen == NULL || remainderLen == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - /* Use the shared parse-or-error helper so a 4-byte SPDM_ERROR is allowed - * to fall through to SPDM_CHECK_RESPONSE, which surfaces it as - * WOLFSPDM_E_PEER_ERROR and stashes the responder error code. */ - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 8); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CERTIFICATE, WOLFSPDM_E_CERT_FAIL); - - /* DSP0274 Sec. 10.6: Param1[3:0] echoes the SlotID the requester asked - * for. A responder returning a different slot's chain could trick us - * into validating the wrong identity. */ - if ((buf[2] & 0x0F) != (ctx->currentSlotId & 0x0F)) { - wolfSPDM_DebugPrint(ctx, - "CERTIFICATE: SlotID echo mismatch (got %u, expected %u)\n", - buf[2] & 0x0F, ctx->currentSlotId & 0x0F); - return WOLFSPDM_E_CERT_FAIL; - } - - *portionLen = SPDM_Get16LE(&buf[4]); - *remainderLen = SPDM_Get16LE(&buf[6]); - - /* Reject truncated chunks - returning success here would let GetCertificate - * advance offset by a portionLen that was never actually delivered, and - * eventually advance state with a partial chain. */ - if (*portionLen > 0) { - int rc; - if (bufSz < (word32)(8 + *portionLen)) { - return WOLFSPDM_E_BUFFER_SMALL; - } - rc = wolfSPDM_CertChainAdd(ctx, buf + 8, *portionLen); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - } - - if (*remainderLen == 0) { - ctx->state = WOLFSPDM_STATE_CERT; - } - - return WOLFSPDM_SUCCESS; -} - -#ifdef WOLFSPDM_HAVE_MLKEM -/* Ciphertext size of the negotiated ML-KEM set (the KEY_EXCHANGE_RSP - * ExchangeData length). The ephemeral key is live between request and - * response, so query it directly. Returns 0 on failure. */ -static word32 wolfSPDM_GetKemCtSize(WOLFSPDM_CTX* ctx) -{ - word32 ctSz = 0; - if (wc_MlKemKey_CipherTextSize(&ctx->ephemeralKey.mlkem, &ctSz) != 0) { - return 0; - } - return ctSz; -} -#endif - int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { - static const char sigCtx[] = "responder-key_exchange_rsp signing"; word16 opaqueLen; - word32 exDataLen; - word32 opaqueLenOff; word32 sigOffset; word32 keRspPartialLen; byte peerPubKeyX[WOLFSPDM_ECC_KEY_SIZE]; byte peerPubKeyY[WOLFSPDM_ECC_KEY_SIZE]; + byte th1SigHash[WOLFSPDM_HASH_SIZE]; + byte signMsgHash[WOLFSPDM_HASH_SIZE]; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; const byte* signature; const byte* rspVerifyData; - byte expectedHmac[WOLFSPDM_HASH_SIZE]; - byte th1Partial[WOLFSPDM_HASH_SIZE]; - word32 sigSize; int rc; - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 140); + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 140); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_KEY_EXCHANGE_RSP, WOLFSPDM_E_KEY_EXCHANGE); - /* Defensive: the new in-parser signature verification dereferences - * ctx->responderPubKey. Internal callers (wolfSPDM_KeyExchange) gate - * on hasResponderPubKey, but enforce it here too so any future direct - * caller fails cleanly instead of dereferencing an uninitialized key. */ - if (!ctx->flags.hasResponderPubKey) { - return WOLFSPDM_E_BAD_STATE; - } + ctx->rspSessionId = SPDM_Get16LE(&buf[4]); + ctx->sessionId = (word32)ctx->reqSessionId | ((word32)ctx->rspSessionId << 16); - sigSize = wolfSPDM_GetSigSize(ctx); + /* Parse MutAuthRequested and ReqSlotIDParam (offsets 6-7) */ + ctx->mutAuthRequested = buf[6]; + ctx->reqSlotIdParam = buf[7]; + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: MutAuth=0x%02x ReqSlotID=0x%02x\n", + ctx->mutAuthRequested, ctx->reqSlotIdParam); - /* MutAuthRequested (offset 6) per DSP0274 Table 35. We don't implement - * the requester-signed FINISH path; refuse before committing sessionId - * so a rejected handshake doesn't leak partial session state into ctx. */ - if (buf[6] != 0) { - wolfSPDM_DebugPrint(ctx, "Responder requested mutual auth (%02x); " - "not supported in this build\n", buf[6]); - return WOLFSPDM_E_KEY_EXCHANGE; - } + /* Extract responder's ephemeral public key (offset 40 = 4+2+1+1+32) */ + XMEMCPY(peerPubKeyX, &buf[40], WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(peerPubKeyY, &buf[88], WOLFSPDM_ECC_KEY_SIZE); - /* Compute and validate the layout BEFORE committing any ctx fields so - * a truncated response doesn't leak partial sessionId/peer-key state. - * ExchangeData (offset 40) is ECDHE X||Y (96) or the ML-KEM ciphertext c; - * OpaqueData/signature/ResponderVerifyData follow it. */ - exDataLen = WOLFSPDM_ECC_POINT_SIZE; -#ifdef WOLFSPDM_HAVE_MLKEM - if (ctx->kexType == WOLFSPDM_KEX_MLKEM) { - /* GetKemCtSize reads the ephemeral ML-KEM key; require it live, mirroring - * the hasResponderPubKey guard above. */ - if (!ctx->flags.ephemeralKeyInit) { - return WOLFSPDM_E_BAD_STATE; - } - exDataLen = wolfSPDM_GetKemCtSize(ctx); - if (exDataLen == 0 || exDataLen > WOLFSPDM_MAX_KEM_CT_SIZE) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - } -#endif - opaqueLenOff = 40 + exDataLen; - if (bufSz < opaqueLenOff + 2u) { - return WOLFSPDM_E_BUFFER_SMALL; - } - opaqueLen = SPDM_Get16LE(&buf[opaqueLenOff]); - sigOffset = opaqueLenOff + 2u + opaqueLen; + /* OpaqueLen at offset 136 */ + opaqueLen = SPDM_Get16LE(&buf[136]); + sigOffset = 138 + opaqueLen; keRspPartialLen = sigOffset; - if (bufSz < sigOffset + sigSize + WOLFSPDM_HASH_SIZE) { + if (bufSz < sigOffset + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE) { return WOLFSPDM_E_BUFFER_SMALL; } - /* Now safe to commit session state. */ - ctx->rspSessionId = SPDM_Get16LE(&buf[4]); - ctx->sessionId = (word32)ctx->reqSessionId | ((word32)ctx->rspSessionId << 16); - signature = buf + sigOffset; - rspVerifyData = buf + sigOffset + sigSize; + rspVerifyData = buf + sigOffset + WOLFSPDM_ECC_SIG_SIZE; /* Add KEY_EXCHANGE_RSP partial (without sig/verify) to transcript */ rc = wolfSPDM_TranscriptAdd(ctx, buf, keRspPartialLen); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; - } - /* Verify responder signature per DSP0274 Sec 14: signature is over the - * partial transcript hash with context "responder-key_exchange_rsp - * signing", using the negotiated asym family (ECDSA or ML-DSA). - * wolfSPDM_KeyExchange refuses to proceed without a parsed cert chain, - * so hasResponderPubKey is always true here. */ - rc = wolfSPDM_TranscriptHash(ctx, th1Partial); + /* Verify responder signature over TH1 (DSP0274). Responder public key + * must be provisioned before KEY_EXCHANGE. */ + if (rc == WOLFSPDM_SUCCESS && !ctx->flags.hasRspPubKey) { + wolfSPDM_DebugPrint(ctx, "No responder public key set\n"); + rc = WOLFSPDM_E_BAD_STATE; + } if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_VerifySig(ctx, sigCtx, (word32)(sizeof(sigCtx) - 1), - th1Partial, signature, sigSize); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, - "KEY_EXCHANGE_RSP signature verification failed (rc=%d)\n", - rc); - } - else { - wolfSPDM_DebugPrint(ctx, - "KEY_EXCHANGE_RSP signature verified\n"); - } + rc = wolfSPDM_TranscriptHash(ctx, th1SigHash); } - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "responder-key_exchange_rsp signing", 34, + th1SigHash, signMsgHash); } - - /* Add signature to transcript (TH1 includes signature) */ - rc = wolfSPDM_TranscriptAdd(ctx, signature, sigSize); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_VerifySignature(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, + signature, WOLFSPDM_ECC_SIG_SIZE); + if (rc != WOLFSPDM_SUCCESS) + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP signature INVALID\n"); } - - /* Derive the key-exchange shared secret: ML-KEM decapsulation of the - * responder's ciphertext c (ExchangeData), or ECDH from the peer point. */ - rc = WOLFSPDM_SUCCESS; -#ifdef WOLFSPDM_HAVE_MLKEM - if (ctx->kexType == WOLFSPDM_KEX_MLKEM) { - rc = wolfSPDM_MlKemDecapsulate(ctx, &buf[40], exDataLen); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_SIG_SIZE); } - else -#endif - if (ctx->kexType == WOLFSPDM_KEX_ECDHE) { - /* ExchangeData is the responder's ephemeral point X||Y at offset 40. */ - XMEMCPY(peerPubKeyX, &buf[40], WOLFSPDM_ECC_KEY_SIZE); - XMEMCPY(peerPubKeyY, &buf[88], WOLFSPDM_ECC_KEY_SIZE); + if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_ComputeSharedSecret(ctx, peerPubKeyX, peerPubKeyY); } - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, ctx->th1); } - - /* Compute TH1 = Hash(transcript including signature) */ - rc = wolfSPDM_TranscriptHash(ctx, ctx->th1); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveHandshakeKeys(ctx, ctx->th1); } - /* Derive all session keys */ - rc = wolfSPDM_DeriveHandshakeKeys(ctx, ctx->th1); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, ctx->th1, expectedHmac); } - - /* Verify ResponderVerifyData = HMAC(rspFinishedKey, TH1) */ - rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, ctx->th1, expectedHmac); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + word32 i; + volatile int diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { + diff |= expectedHmac[i] ^ rspVerifyData[i]; + } + if (diff != 0) { + wolfSPDM_DebugPrint(ctx, "ResponderVerifyData MISMATCH\n"); + rc = WOLFSPDM_E_BAD_HMAC; + } } - - /* Constant-time compare to avoid leaking HMAC bytes via timing. */ - if (wolfSPDM_ConstCompare(expectedHmac, rspVerifyData, - WOLFSPDM_HASH_SIZE) != 0) { - wolfSPDM_DebugPrint(ctx, "ResponderVerifyData MISMATCH\n"); - rc = WOLFSPDM_E_BAD_HMAC; - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "ResponderVerifyData VERIFIED OK\n"); + rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); } - wolfSPDM_DebugPrint(ctx, "ResponderVerifyData VERIFIED OK\n"); - - /* Add ResponderVerifyData to transcript (per SPDM spec, always included) */ - rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_KEY_EX; } - ctx->state = WOLFSPDM_STATE_KEY_EX; - rc = WOLFSPDM_SUCCESS; - -cleanup: - /* expectedHmac is derived from rspFinishedKey; wipe regardless of path. - * th1Partial is intermediate handshake material - wipe it too so it does - * not linger on the stack frame. */ wc_ForceZero(expectedHmac, sizeof(expectedHmac)); - wc_ForceZero(peerPubKeyX, sizeof(peerPubKeyX)); - wc_ForceZero(peerPubKeyY, sizeof(peerPubKeyY)); - wc_ForceZero(th1Partial, sizeof(th1Partial)); + wc_ForceZero(th1SigHash, sizeof(th1SigHash)); + wc_ForceZero(signMsgHash, sizeof(signMsgHash)); return rc; } int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); if (buf[1] == SPDM_FINISH_RSP) { int addRc; word32 rspMsgLen = 4; - /* SPDM 1.4 adds OpaqueLength(2) + OpaqueData(var) to FINISH_RSP. - * Cap accepted OpaqueData size to keep wolfSPDM_Finish's decBuf - * footprint bounded. Per DSP0274 the field is u16 (theoretical - * 65535) but real responders keep it small. */ + /* SPDM 1.4 adds OpaqueLength(2) + OpaqueData(var) to FINISH_RSP */ if (ctx->spdmVersion >= SPDM_VERSION_14) { word16 opaqueLen; if (bufSz < 6) { return WOLFSPDM_E_BUFFER_SMALL; } opaqueLen = SPDM_Get16LE(&buf[4]); - if (opaqueLen > 256) { - wolfSPDM_DebugPrint(ctx, - "FINISH_RSP: OpaqueLength %u exceeds 256B cap\n", opaqueLen); - return WOLFSPDM_E_BUFFER_SMALL; - } rspMsgLen = 4 + 2 + opaqueLen; if (bufSz < rspMsgLen) { return WOLFSPDM_E_BUFFER_SMALL; @@ -1185,834 +540,5 @@ int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) return WOLFSPDM_E_BAD_STATE; } -/* --- Measurement Message Building and Parsing --- */ +/* PSK message builders/parsers moved to spdm_psk.c */ -#ifndef NO_WOLFSPDM_MEAS - -int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - byte operation, byte requestSig) -{ - word32 offset = 0; - word32 minSz; - - if (ctx == NULL || buf == NULL || bufSz == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - /* Size: 4 header + (requestSig ? 32 nonce + 1 slotId : 0) - * SPDM 1.3+ adds RequesterContext(8) always. OpaqueDataLength is NOT - * part of GET_MEASUREMENTS request per DSP0274 Table 51 / libspdm. */ - minSz = 4; - if (requestSig) { - minSz += 32 + 1; /* Nonce + SlotIDParam */ - } - if (ctx->spdmVersion >= SPDM_VERSION_13) { - minSz += 8; /* RequesterContext (always for 1.3+) */ - } - if (*bufSz < minSz) - return WOLFSPDM_E_BUFFER_SMALL; - - buf[offset++] = ctx->spdmVersion; - buf[offset++] = SPDM_GET_MEASUREMENTS; - /* Param1: bit 0 = signature requested */ - buf[offset++] = requestSig ? SPDM_MEAS_REQUEST_SIG_BIT : 0x00; - /* Param2: MeasurementOperation */ - buf[offset++] = operation; - - if (requestSig) { - /* Nonce (32 bytes) */ - int rc = wolfSPDM_GetRandom(ctx, &buf[offset], 32); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - XMEMCPY(ctx->measNonce, &buf[offset], 32); - offset += 32; - - /* SlotIDParam: the slot whose certificate authenticates the - * measurement signature. Must match the slot chosen during - * GET_CERTIFICATE (ctx->currentSlotId) so the responder signs - * with the key whose chain we hold. */ - buf[offset++] = (byte)(ctx->currentSlotId & 0x0F); - } - - /* DSP0274 v1.3.0 Table 50 / v1.4.0 Table 49: RequesterContext (8 bytes) - * is appended to GET_MEASUREMENTS for SPDM 1.3 and above, REGARDLESS of - * whether a signature was requested. The MEASUREMENTS response echoes - * these bytes back between OpaqueData and Signature; ParseMeasurements - * skips over them. The signature already covers RequesterContext, so - * we don't separately verify the echo here. */ - if (ctx->spdmVersion >= SPDM_VERSION_13) { - int rc = wolfSPDM_GetRandom(ctx, &buf[offset], 8); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - offset += 8; - } - - *bufSz = offset; - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) -{ - word32 offset; - byte numBlocks; - word32 recordLen; - word32 recordEnd; - word32 blockIdx; - word32 sigSize; - - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 8); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_MEASUREMENTS, WOLFSPDM_E_MEASUREMENT); - - /* DSP0274: Param2[3:0] echoes the SlotID the requester sent. The - * requester picks ctx->currentSlotId for signed requests, so reject - * any other slot value. */ - if ((buf[3] & 0x0F) != (ctx->currentSlotId & 0x0F)) { - wolfSPDM_DebugPrint(ctx, - "MEASUREMENTS: SlotID echo mismatch (got %u expected %u)\n", - buf[3] & 0x0F, ctx->currentSlotId & 0x0F); - return WOLFSPDM_E_MEASUREMENT; - } - - numBlocks = buf[4]; - /* MeasurementRecordLength: 3 bytes LE at offset 5..7 */ - recordLen = (word32)buf[5] | ((word32)buf[6] << 8) | ((word32)buf[7] << 16); - - wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: numBlocks=%u, recordLen=%u\n", - numBlocks, recordLen); - - /* Validate record fits in buffer */ - if (8 + recordLen > bufSz) { - wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: recordLen %u exceeds bufSz %u\n", - recordLen, bufSz); - return WOLFSPDM_E_MEASUREMENT; - } - - recordEnd = 8 + recordLen; - offset = 8; /* Start of measurement record */ - ctx->measBlockCount = 0; - - /* Parse each measurement block */ - for (blockIdx = 0; blockIdx < numBlocks; blockIdx++) { - word16 measSize; - - /* Check block header fits */ - if (offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE > recordEnd) { - wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: block %u header truncated\n", - blockIdx); - return WOLFSPDM_E_MEASUREMENT; - } - - /* Read block header: Index(1) + MeasSpec(1) + MeasSize(2 LE) */ - measSize = SPDM_Get16LE(&buf[offset + 2]); - - /* Check block data fits */ - if (offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE + measSize > recordEnd) { - wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: block %u data truncated\n", - blockIdx); - return WOLFSPDM_E_MEASUREMENT; - } - - /* Store if we have room */ - if (ctx->measBlockCount < WOLFSPDM_MAX_MEAS_BLOCKS) { - WOLFSPDM_MEAS_BLOCK* blk = &ctx->measBlocks[ctx->measBlockCount]; - blk->index = buf[offset]; - blk->measurementSpec = buf[offset + 1]; - - /* Parse DMTF measurement value if MeasSpec==1 and size >= 3 */ - if (blk->measurementSpec == 0x01 && measSize >= 3) { - word16 valueSize; - word16 copySize; - - blk->dmtfType = buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE]; - valueSize = (word16)( - buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE + 1] | - (buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE + 2] << 8)); - - /* Validate valueSize against measSize */ - if (valueSize > measSize - 3) { - wolfSPDM_DebugPrint(ctx, - "MEASUREMENTS: block %u valueSize %u > measSize-3 %u\n", - blockIdx, valueSize, measSize - 3); - return WOLFSPDM_E_MEASUREMENT; - } - - /* Truncate if value exceeds our buffer */ - copySize = valueSize; - if (copySize > WOLFSPDM_MAX_MEAS_VALUE_SIZE) { - copySize = WOLFSPDM_MAX_MEAS_VALUE_SIZE; - } - blk->valueSize = copySize; - XMEMCPY(blk->value, - &buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE + 3], copySize); - } - else { - /* Non-DMTF or too small: store raw */ - word16 copySize = measSize; - blk->dmtfType = 0; - if (copySize > WOLFSPDM_MAX_MEAS_VALUE_SIZE) { - copySize = WOLFSPDM_MAX_MEAS_VALUE_SIZE; - } - blk->valueSize = copySize; - if (copySize > 0) { - XMEMCPY(blk->value, - &buf[offset + WOLFSPDM_MEAS_BLOCK_HDR_SIZE], copySize); - } - } - - ctx->measBlockCount++; - } - else { - wolfSPDM_DebugPrint(ctx, - "MEASUREMENTS: block %u exceeds MAX_MEAS_BLOCKS (%u), skipping\n", - blockIdx, WOLFSPDM_MAX_MEAS_BLOCKS); - } - - offset += WOLFSPDM_MEAS_BLOCK_HDR_SIZE + measSize; - } - - /* After measurement record: Nonce(32) + OpaqueDataLength(2) + OpaqueData - * + [RequesterContext(8) for 1.3+] + Signature(96). Nonce/Sig only - * appear when signature was requested. Distinguish the two cases by - * whether the response carries ANY tail bytes: - * - offset == bufSz: unsigned request, no tail. Accepted. - * - bufSz > offset: signed request - the FULL tail must be present; - * a partial tail is a truncated/malformed response. */ - ctx->measSignatureSize = 0; - - if (offset == bufSz) { - /* Unsigned measurement response - no tail expected. */ - } - else if (offset + 32 + 2 > bufSz) { - wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: signed tail truncated\n"); - return WOLFSPDM_E_MEASUREMENT; - } - else { - /* Nonce (32 bytes) - skip, we already have our own in ctx->measNonce */ - offset += 32; - - /* OpaqueDataLength (2 LE) */ - word16 opaqueLen = SPDM_Get16LE(&buf[offset]); - offset += 2; - - /* Skip opaque data */ - if (offset + opaqueLen > bufSz) { - wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: opaque data truncated\n"); - return WOLFSPDM_E_MEASUREMENT; - } - offset += opaqueLen; - - /* DSP0274 1.3+ Table 50 mandates an 8-byte RequesterContext echo - * between OpaqueData and Signature. Require room for the 8 bytes - * but NOT for the signature - unsigned measurements omit the sig - * tail and must still parse. The Signature copy below remains - * conditional on its own bufSz check. */ - if (ctx->spdmVersion >= SPDM_VERSION_13) { - if (offset + 8 > bufSz) { - wolfSPDM_DebugPrint(ctx, - "MEASUREMENTS: 1.3+ response missing RequesterContext\n"); - return WOLFSPDM_E_MEASUREMENT; - } - offset += 8; - } - - /* Signature (if present). Size is the negotiated SigLen (ECDSA or - * ML-DSA); the stored copy bounds at WOLFSPDM_MAX_SIG_SIZE. */ - sigSize = wolfSPDM_GetSigSize(ctx); - if (offset + sigSize <= bufSz) { - XMEMCPY(ctx->measSignature, &buf[offset], sigSize); - ctx->measSignatureSize = sigSize; - } - } - - ctx->flags.hasMeasurements = 1; - wolfSPDM_DebugPrint(ctx, "MEASUREMENTS: parsed %u blocks\n", - ctx->measBlockCount); - - return WOLFSPDM_SUCCESS; -} - -#ifndef NO_WOLFSPDM_MEAS_VERIFY - -/* Shared tail: BuildSignedHash -> VerifyEccSig -> debug print -> return */ -static int wolfSPDM_VerifySignedDigest(WOLFSPDM_CTX* ctx, - const char* contextStr, word32 contextStrLen, - byte* digest, /* in: message_hash (ECDSA path overwrites it) */ - const byte* sig, word32 sigSz, - const char* passMsg, const char* failMsg, int failErr) -{ - int rc = wolfSPDM_VerifySig(ctx, contextStr, contextStrLen, - digest, sig, sigSz); - - if (rc == WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "%s\n", passMsg); - return WOLFSPDM_SUCCESS; - } - wolfSPDM_DebugPrint(ctx, "%s\n", failMsg); - /* Preserve CRYPTO_FAIL (transient infra) vs BAD_SIGNATURE (peer-level - * violation) - only the latter gets mapped to the caller's domain - * error code (MEAS_SIG_FAIL / CHALLENGE). */ - return (rc == WOLFSPDM_E_BAD_SIGNATURE) ? failErr : rc; -} - -int wolfSPDM_VerifyMeasurementSig(WOLFSPDM_CTX* ctx, - const byte* rspBuf, word32 rspBufSz, - const byte* reqMsg, word32 reqMsgSz) -{ - byte digest[WOLFSPDM_HASH_SIZE]; - word32 sigOffset; - word32 sigSize; - int rc; - - if (ctx == NULL || rspBuf == NULL || reqMsg == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (!ctx->flags.hasResponderPubKey) { - return WOLFSPDM_E_MEAS_NOT_VERIFIED; - } - - /* Signature is the last SigLen bytes of the response (ECDSA or ML-DSA) */ - sigSize = wolfSPDM_GetSigSize(ctx); - if (rspBufSz < sigSize) { - return WOLFSPDM_E_MEASUREMENT; - } - sigOffset = rspBufSz - sigSize; - - /* Compute L1||L2 hash per DSP0274 Section 10.11.1: - * L1/L2 = VCA || GET_MEASUREMENTS_request || MEASUREMENTS_response(before sig) */ - rc = wolfSPDM_Sha384Hash(digest, - ctx->transcript, ctx->vcaLen, - reqMsg, reqMsgSz, - rspBuf, sigOffset); - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_VerifySignedDigest(ctx, - "responder-measurements signing", 30, digest, - rspBuf + sigOffset, sigSize, - "Measurement signature VERIFIED", - "Measurement signature INVALID", - WOLFSPDM_E_MEAS_SIG_FAIL); - } - wc_ForceZero(digest, sizeof(digest)); - return rc; -} - -#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ -#endif /* !NO_WOLFSPDM_MEAS */ - -/* --- Responder Public Key Extraction --- - * Extract responder's ECC P-384 public key from the leaf certificate in the - * SPDM certificate chain. Used by both measurement signature verification - * and CHALLENGE authentication, so it lives outside measurement guards. */ - -/* Helper: find leaf cert in SPDM cert chain buffer. - * SPDM cert chain header: Length(2 LE) + Reserved(2) + RootHash(48) = 52 bytes - * After header: concatenated DER certificates, leaf is the last one. */ -static int wolfSPDM_FindLeafCert(const byte* certChain, word32 certChainLen, - const byte** leafCert, word32* leafCertSz) -{ - const byte* certDer; - word32 certDerSz; - word32 pos; - const byte* lastCert; - word32 lastCertSz; - - if (certChainLen <= 52) { - return WOLFSPDM_E_CERT_PARSE; - } - - certDer = certChain + 52; - certDerSz = certChainLen - 52; - lastCert = certDer; - lastCertSz = certDerSz; - pos = 0; - - while (pos < certDerSz) { - word32 certLen; - word32 hdrLen; - - if (certDer[pos] != 0x30) { - break; - } - - if (pos + 1 >= certDerSz) break; - - if (certDer[pos + 1] < 0x80) { - certLen = certDer[pos + 1]; - hdrLen = 2; - } - else if (certDer[pos + 1] == 0x81) { - if (pos + 2 >= certDerSz) break; - certLen = certDer[pos + 2]; - hdrLen = 3; - } - else if (certDer[pos + 1] == 0x82) { - if (pos + 3 >= certDerSz) break; - certLen = ((word32)certDer[pos + 2] << 8) | certDer[pos + 3]; - hdrLen = 4; - } - else if (certDer[pos + 1] == 0x83) { - if (pos + 4 >= certDerSz) break; - certLen = ((word32)certDer[pos + 2] << 16) | - ((word32)certDer[pos + 3] << 8) | certDer[pos + 4]; - hdrLen = 5; - } - else { - break; - } - - if (pos + hdrLen + certLen > certDerSz) break; - - lastCert = certDer + pos; - lastCertSz = hdrLen + certLen; - pos += hdrLen + certLen; - } - - *leafCert = lastCert; - *leafCertSz = lastCertSz; - return WOLFSPDM_SUCCESS; -} - -/* Import the ECDSA P-384 public key from the parsed leaf cert. */ -static int wolfSPDM_ImportEccPubKey(WOLFSPDM_CTX* ctx, DecodedCert* cert) -{ - word32 idx = 0; - int rc = wc_ecc_init(&ctx->responderPubKey.ecc); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - rc = wc_EccPublicKeyDecode(cert->publicKey, &idx, - &ctx->responderPubKey.ecc, cert->pubKeySize); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "ECC public key decode failed: %d\n", rc); - wc_ecc_free(&ctx->responderPubKey.ecc); - return WOLFSPDM_E_CERT_PARSE; - } - wolfSPDM_DebugPrint(ctx, "Extracted responder ECC P-384 public key\n"); - return WOLFSPDM_SUCCESS; -} - -#ifdef WOLFSPDM_HAVE_MLDSA -/* Import the ML-DSA public key from the parsed leaf cert. The parameter set - * is pinned from the negotiated PqcAsymSel, so a cert whose AlgorithmIdentifier - * OID names a different level is rejected by the decoder. */ -static int wolfSPDM_ImportMlDsaPubKey(WOLFSPDM_CTX* ctx, DecodedCert* cert) -{ - word32 idx = 0; - byte level; - int rc; - - if (ctx->pqcAsymSel == SPDM_PQC_ASYM_ALGO_ML_DSA_44) { - level = WC_ML_DSA_44; - } - else if (ctx->pqcAsymSel == SPDM_PQC_ASYM_ALGO_ML_DSA_87) { - level = WC_ML_DSA_87; - } - else { - level = WC_ML_DSA_65; - } - - rc = wc_MlDsaKey_Init(&ctx->responderPubKey.mldsa, NULL, INVALID_DEVID); - if (rc == 0) { - rc = wc_MlDsaKey_SetParams(&ctx->responderPubKey.mldsa, level); - } - if (rc != 0) { - wc_MlDsaKey_Free(&ctx->responderPubKey.mldsa); - return WOLFSPDM_E_CRYPTO_FAIL; - } - - rc = wc_MlDsaKey_PublicKeyDecode(&ctx->responderPubKey.mldsa, - cert->publicKey, cert->pubKeySize, &idx); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "ML-DSA public key decode failed: %d\n", rc); - wc_MlDsaKey_Free(&ctx->responderPubKey.mldsa); - return WOLFSPDM_E_CERT_PARSE; - } - wolfSPDM_DebugPrint(ctx, "Extracted responder ML-DSA public key (level %u)\n", - level); - return WOLFSPDM_SUCCESS; -} -#endif /* WOLFSPDM_HAVE_MLDSA */ - -int wolfSPDM_ExtractResponderPubKey(WOLFSPDM_CTX* ctx) -{ - DecodedCert cert; - const byte* leafCert; - word32 leafCertSz; - int rc; - - if (ctx == NULL || ctx->certChainLen == 0) { - return WOLFSPDM_E_CERT_PARSE; - } - - /* Find the leaf (last) certificate in the SPDM cert chain */ - rc = wolfSPDM_FindLeafCert(ctx->certChain, ctx->certChainLen, - &leafCert, &leafCertSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "Certificate chain too short for header\n"); - return rc; - } - - /* Parse the leaf certificate */ - wc_InitDecodedCert(&cert, leafCert, leafCertSz, NULL); - rc = wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "Certificate parse failed: %d\n", rc); - wc_FreeDecodedCert(&cert); - return WOLFSPDM_E_CERT_PARSE; - } - - /* Import the responder verify key for whichever family was negotiated. */ -#ifdef WOLFSPDM_HAVE_MLDSA - if (ctx->asymType == WOLFSPDM_ASYM_MLDSA) { - rc = wolfSPDM_ImportMlDsaPubKey(ctx, &cert); - } - else { - rc = wolfSPDM_ImportEccPubKey(ctx, &cert); - } -#else - rc = wolfSPDM_ImportEccPubKey(ctx, &cert); -#endif - - wc_FreeDecodedCert(&cert); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - ctx->flags.hasResponderPubKey = 1; - return WOLFSPDM_SUCCESS; -} - -/* --- Certificate Chain Validation --- */ - -int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) -{ - byte caHash[WOLFSPDM_HASH_SIZE]; - const byte* chainRootHash; - int rc; - - if (ctx == NULL || ctx->certChainLen == 0) { - return WOLFSPDM_E_CERT_PARSE; - } - - if (!ctx->flags.hasTrustedCAs) { - return WOLFSPDM_E_CERT_PARSE; - } - - /* SPDM cert chain header: Length(2 LE) + Reserved(2) + RootHash(48) */ - if (ctx->certChainLen <= 52) { - return WOLFSPDM_E_CERT_PARSE; - } - - /* Validate the root hash against our trusted CA */ - rc = wolfSPDM_Sha384Hash(caHash, ctx->trustedCAs, ctx->trustedCAsSz, - NULL, 0, NULL, 0); - if (rc != WOLFSPDM_SUCCESS) return rc; - - chainRootHash = ctx->certChain + 4; /* Skip Length(2) + Reserved(2) */ - if (XMEMCMP(caHash, chainRootHash, WOLFSPDM_HASH_SIZE) != 0) { - wolfSPDM_DebugPrint(ctx, - "Root cert hash mismatch - chain not from trusted CA\n"); - return WOLFSPDM_E_CERT_PARSE; - } - - wolfSPDM_DebugPrint(ctx, "Root certificate hash VERIFIED against trusted CA\n"); - - /* Extract public key from the leaf cert. GetCertificate already - * ran ExtractResponderPubKey, so skip the re-init - calling - * wc_ecc_init on an already-initialized key leaks the previous - * key's internal allocations. */ - if (!ctx->flags.hasResponderPubKey) { - rc = wolfSPDM_ExtractResponderPubKey(ctx); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - } - - wolfSPDM_DebugPrint(ctx, "Certificate chain validated\n"); - return WOLFSPDM_SUCCESS; -} - -/* --- Challenge Authentication (DSP0274 Section 10.8) --- */ - -#ifndef NO_WOLFSPDM_CHALLENGE - -int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - int slotId, byte measHashType) -{ - word32 offset = 0; - word32 minSz; - int rc; - - if (ctx == NULL || buf == NULL || bufSz == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - /* SPDM 1.3+ adds RequesterContext(8) per DSP0274 Table 46 */ - minSz = 36; - if (ctx->spdmVersion >= SPDM_VERSION_13) - minSz += 8; /* RequesterContext */ - if (*bufSz < minSz) - return WOLFSPDM_E_BUFFER_SMALL; - - buf[offset++] = ctx->spdmVersion; - buf[offset++] = SPDM_CHALLENGE; - buf[offset++] = (byte)(slotId & 0x0F); - buf[offset++] = measHashType; - - /* Save measHashType + slotId for ParseChallengeAuth echo check */ - ctx->challengeMeasHashType = measHashType; - ctx->challengeSlotId = (byte)(slotId & 0x0F); - - /* Nonce (32 bytes random) */ - rc = wolfSPDM_GetRandom(ctx, &buf[offset], 32); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - XMEMCPY(ctx->challengeNonce, &buf[offset], 32); - offset += 32; - - /* SPDM 1.3+ adds RequesterContext(8) per DSP0274 Table 46. - * Save it so ParseChallengeAuth can verify the responder echoed it. - * Note: OpaqueDataLength is NOT part of the CHALLENGE request. */ - if (ctx->spdmVersion >= SPDM_VERSION_13) { - rc = wolfSPDM_GetRandom(ctx, &buf[offset], 8); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - XMEMCPY(ctx->challengeReqCtx, &buf[offset], 8); - offset += 8; - } - - *bufSz = offset; - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, const byte* buf, - word32 bufSz, word32* sigOffset) -{ - word32 offset; - word16 opaqueLen; - - if (ctx == NULL || buf == NULL || sigOffset == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - /* Minimum size: 4 hdr + 48 certChainHash + 48 nonce + 48 measSummary - * + 2 opaqueLen + 96 sig = 246 bytes (with meas hash) */ - if (bufSz < 4) { - return WOLFSPDM_E_CHALLENGE; - } - - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CHALLENGE_AUTH, WOLFSPDM_E_CHALLENGE); - - /* DSP0274 Sec. 10.8: Param1[3:0] echoes the requested SlotID. - * BuildChallenge saved the slot it sent in ctx->challengeSlotId; - * reject a responder that authenticates a different slot. */ - if ((buf[2] & 0x0F) != (ctx->challengeSlotId & 0x0F)) { - wolfSPDM_DebugPrint(ctx, - "CHALLENGE_AUTH: SlotID echo mismatch (got %u expected %u)\n", - buf[2] & 0x0F, ctx->challengeSlotId & 0x0F); - return WOLFSPDM_E_CHALLENGE; - } - - offset = 4; - - /* CertChainHash (H bytes, 48 for SHA-384) */ - if (offset + WOLFSPDM_HASH_SIZE > bufSz) { - wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH: too short for CertChainHash\n"); - return WOLFSPDM_E_CHALLENGE; - } - /* Verify cert chain hash matches what we computed */ - if (XMEMCMP(&buf[offset], ctx->certChainHash, WOLFSPDM_HASH_SIZE) != 0) { - wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH: CertChainHash mismatch\n"); - return WOLFSPDM_E_CHALLENGE; - } - offset += WOLFSPDM_HASH_SIZE; - - /* Nonce (32 bytes per DSP0274) */ - if (offset + 32 > bufSz) { - wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH: too short for Nonce\n"); - return WOLFSPDM_E_CHALLENGE; - } - offset += 32; - - /* MeasurementSummaryHash (H bytes if requested, 0 bytes if type=NONE) */ - if (ctx->challengeMeasHashType != SPDM_MEAS_SUMMARY_HASH_NONE) { - if (offset + WOLFSPDM_HASH_SIZE > bufSz) { - wolfSPDM_DebugPrint(ctx, - "CHALLENGE_AUTH: too short for MeasurementSummaryHash\n"); - return WOLFSPDM_E_CHALLENGE; - } - offset += WOLFSPDM_HASH_SIZE; - } - - /* OpaqueDataLength (2 LE) */ - if (offset + 2 > bufSz) { - return WOLFSPDM_E_CHALLENGE; - } - opaqueLen = SPDM_Get16LE(&buf[offset]); - offset += 2; - - /* Skip opaque data */ - if (offset + opaqueLen > bufSz) { - return WOLFSPDM_E_CHALLENGE; - } - offset += opaqueLen; - - /* SPDM 1.3+ adds RequesterContext (8 bytes echoed from request). - * Per DSP0274, this comes AFTER OpaqueData and BEFORE Signature. - * Verify the responder echoed the same value we sent - the signature - * already covers it, so a tampered value would fail signature check, - * but the explicit echo check catches responder-side bugs / cached - * stale responses early. */ - if (ctx->spdmVersion >= SPDM_VERSION_13) { - if (offset + 8 > bufSz) { - wolfSPDM_DebugPrint(ctx, - "CHALLENGE_AUTH: too short for RequesterContext\n"); - return WOLFSPDM_E_CHALLENGE; - } - if (XMEMCMP(&buf[offset], ctx->challengeReqCtx, 8) != 0) { - wolfSPDM_DebugPrint(ctx, - "CHALLENGE_AUTH: RequesterContext echo mismatch\n"); - return WOLFSPDM_E_CHALLENGE; - } - offset += 8; - } - - /* Signature starts here (ECDSA or ML-DSA SigLen) */ - if (offset + wolfSPDM_GetSigSize(ctx) > bufSz) { - wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH: no room for signature\n"); - return WOLFSPDM_E_CHALLENGE; - } - - *sigOffset = offset; - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_VerifyChallengeAuthSig(WOLFSPDM_CTX* ctx, - const byte* rspBuf, word32 rspBufSz, - const byte* reqMsg, word32 reqMsgSz, word32 sigOffset) -{ - byte digest[WOLFSPDM_HASH_SIZE]; - int rc; - - (void)rspBufSz; - - if (ctx == NULL || rspBuf == NULL || reqMsg == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (!ctx->flags.hasResponderPubKey) { - return WOLFSPDM_E_CHALLENGE; - } - - /* Build M1/M2 hash per DSP0274 Section 10.8.3: - * A+B are already accumulated in ctx->m1m2Hash. Now add C and finalize. */ - if (!ctx->flags.m1m2HashInit) { - wolfSPDM_DebugPrint(ctx, "CHALLENGE: M1/M2 hash not initialized\n"); - return WOLFSPDM_E_CHALLENGE; - } - - /* Add C: CHALLENGE request + CHALLENGE_AUTH response (before sig). - * If a step fails, free the hash state and clear the init flag so a - * retry rebuilds from scratch instead of using a partially-updated hash. */ - rc = wc_Sha384Update(&ctx->m1m2Hash, reqMsg, reqMsgSz); - if (rc != 0) { - wc_Sha384Free(&ctx->m1m2Hash); - ctx->flags.m1m2HashInit = 0; - return WOLFSPDM_E_CRYPTO_FAIL; - } - rc = wc_Sha384Update(&ctx->m1m2Hash, rspBuf, sigOffset); - if (rc != 0) { - wc_Sha384Free(&ctx->m1m2Hash); - ctx->flags.m1m2HashInit = 0; - return WOLFSPDM_E_CRYPTO_FAIL; - } - - /* Finalize M1/M2 hash */ - rc = wc_Sha384Final(&ctx->m1m2Hash, digest); - ctx->flags.m1m2HashInit = 0; /* Hash consumed regardless */ - if (rc != 0) { - wc_ForceZero(digest, sizeof(digest)); - return WOLFSPDM_E_CRYPTO_FAIL; - } - - rc = wolfSPDM_VerifySignedDigest(ctx, - "responder-challenge_auth signing", 32, digest, - rspBuf + sigOffset, wolfSPDM_GetSigSize(ctx), - "CHALLENGE_AUTH signature VERIFIED", - "CHALLENGE_AUTH signature INVALID", - WOLFSPDM_E_CHALLENGE); - wc_ForceZero(digest, sizeof(digest)); - return rc; -} - -#endif /* !NO_WOLFSPDM_CHALLENGE */ - -/* --- Heartbeat (DSP0274 Section 10.10) --- */ - -int wolfSPDM_BuildHeartbeat(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) -{ - return wolfSPDM_BuildSimpleMsg(ctx, SPDM_HEARTBEAT, buf, bufSz); -} - -int wolfSPDM_ParseHeartbeatAck(WOLFSPDM_CTX* ctx, const byte* buf, - word32 bufSz) -{ - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 4); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_HEARTBEAT_ACK, WOLFSPDM_E_BAD_STATE); - - wolfSPDM_DebugPrint(ctx, "HEARTBEAT_ACK received\n"); - return WOLFSPDM_SUCCESS; -} - -/* --- Key Update (DSP0274 Section 10.9) --- */ - -int wolfSPDM_BuildKeyUpdate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - byte operation, byte* tag) -{ - int rc; - - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 4); - if (tag == NULL) - return WOLFSPDM_E_INVALID_ARG; - - /* Generate random tag for request/response matching */ - rc = wolfSPDM_GetRandom(ctx, tag, 1); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - buf[0] = ctx->spdmVersion; - buf[1] = SPDM_KEY_UPDATE; - buf[2] = operation; - buf[3] = *tag; - *bufSz = 4; - - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_ParseKeyUpdateAck(WOLFSPDM_CTX* ctx, const byte* buf, - word32 bufSz, byte operation, byte tag) -{ - SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 4); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_KEY_UPDATE_ACK, WOLFSPDM_E_KEY_UPDATE); - - /* Verify echoed operation and tag */ - if (buf[2] != operation) { - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE_ACK: operation mismatch: 0x%02x != 0x%02x\n", - buf[2], operation); - return WOLFSPDM_E_KEY_UPDATE; - } - - if (buf[3] != tag) { - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE_ACK: tag mismatch: 0x%02x != 0x%02x\n", - buf[3], tag); - return WOLFSPDM_E_KEY_UPDATE; - } - - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE_ACK received\n"); - return WOLFSPDM_SUCCESS; -} diff --git a/src/spdm_psk.c b/src/spdm_psk.c new file mode 100644 index 0000000..c0d11d3 --- /dev/null +++ b/src/spdm_psk.c @@ -0,0 +1,430 @@ +/* spdm_psk.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +/* Shared SPDM PSK protocol code used by Nations (and future Infineon). */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_PSK + +#include +#include + +/* ----- PSK Context Setup ----- */ + +int wolfSPDM_SetPSK(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz) +{ + if (ctx == NULL || psk == NULL || pskSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + if (pskSz > WOLFSPDM_PSK_MAX_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + if (hint != NULL && hintSz > WOLFSPDM_PSK_HINT_MAX) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMCPY(ctx->psk, psk, pskSz); + ctx->pskSz = pskSz; + + if (hint != NULL && hintSz > 0) { + XMEMCPY(ctx->pskHint, hint, hintSz); + ctx->pskHintSz = hintSz; + } else { + XMEMSET(ctx->pskHint, 0, sizeof(ctx->pskHint)); + ctx->pskHintSz = 0; + } + + return WOLFSPDM_SUCCESS; +} + +/* ----- PSK Message Builders/Parsers ----- */ + +int wolfSPDM_BuildPskExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + word32 offset = 0; + int rc; + + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 48); + + if (ctx->pskSz == 0) { + return WOLFSPDM_E_BAD_STATE; + } + + XMEMSET(buf, 0, *bufSz); + + /* Header */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_PSK_EXCHANGE; + buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ + buf[offset++] = 0x00; /* Param2 = Reserved */ + + /* ReqSessionID (2 LE) */ + SPDM_Set16LE(&buf[offset], ctx->reqSessionId); + offset += 2; + + /* PSKHintLength (2 LE) */ + SPDM_Set16LE(&buf[offset], (word16)ctx->pskHintSz); + offset += 2; + + /* RequesterContextLength (2 LE) = 32 */ + SPDM_Set16LE(&buf[offset], WOLFSPDM_RANDOM_SIZE); + offset += 2; + + /* OpaqueDataLength (2 LE) = 0 */ + SPDM_Set16LE(&buf[offset], 0); + offset += 2; + + /* PSKHint */ + if (ctx->pskHintSz > 0) { + if (offset + ctx->pskHintSz > *bufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(&buf[offset], ctx->pskHint, ctx->pskHintSz); + offset += ctx->pskHintSz; + } + + /* RequesterContext (32 random bytes) */ + if (offset + WOLFSPDM_RANDOM_SIZE > *bufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + rc = wolfSPDM_GetRandom(ctx, &buf[offset], WOLFSPDM_RANDOM_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + offset += WOLFSPDM_RANDOM_SIZE; + + /* OpaqueData - none */ + + *bufSz = offset; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz) +{ + word16 rspContextLen, opaqueLen; + word32 verifyOffset; + word32 rspPartialLen; + byte th1Hash[WOLFSPDM_HASH_SIZE]; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + const byte* rspVerifyData; + int rc; + + /* Minimum: header(4) + RspSessionID(2) + Reserved(1) + RspContextLen(2) + + * OpaqueLen(2) + VerifyData(48) = 59 */ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 59); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_PSK_EXCHANGE_RSP, + WOLFSPDM_E_KEY_EXCHANGE); + + /* Per SPDM 1.3 DSP0274 Table 65: + * [4-5] RspSessionID, [6] MutAuthRequested, [7] ReqSlotIDParam, + * [8-9] RspContextLength, [10-11] OpaqueDataLength */ + ctx->rspSessionId = SPDM_Get16LE(&buf[4]); + ctx->sessionId = (word32)ctx->reqSessionId | + ((word32)ctx->rspSessionId << 16); + + rspContextLen = SPDM_Get16LE(&buf[8]); + opaqueLen = SPDM_Get16LE(&buf[10]); + + verifyOffset = 12 + rspContextLen + opaqueLen; + rspPartialLen = verifyOffset; + + if (bufSz < verifyOffset + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + rspVerifyData = buf + verifyOffset; + + /* Add PSK_EXCHANGE_RSP (without VerifyData) to transcript */ + rc = wolfSPDM_TranscriptAdd(ctx, buf, rspPartialLen); + + /* Compute TH1 and derive handshake keys from PSK BEFORE verifying */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th1Hash); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->th1, th1Hash, WOLFSPDM_HASH_SIZE); + rc = wolfSPDM_DeriveHandshakeKeysPsk(ctx, th1Hash); + } + + /* Verify ResponderVerifyData = HMAC(rspFinishedKey, TH1) */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, th1Hash, + expectedHmac); + } + if (rc == WOLFSPDM_SUCCESS) { + word32 i; + volatile int diff = 0; + wolfSPDM_DebugHex(ctx, "Expected HMAC", expectedHmac, + WOLFSPDM_HASH_SIZE); + wolfSPDM_DebugHex(ctx, "Received HMAC", rspVerifyData, + WOLFSPDM_HASH_SIZE); + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { + diff |= expectedHmac[i] ^ rspVerifyData[i]; + } + if (diff != 0) { + wolfSPDM_DebugPrint(ctx, "PSK ResponderVerifyData MISMATCH\n"); + rc = WOLFSPDM_E_BAD_HMAC; + } + } + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "PSK ResponderVerifyData VERIFIED OK\n"); + rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_KEY_EX; + } + + wc_ForceZero(expectedHmac, sizeof(expectedHmac)); + wc_ForceZero(th1Hash, sizeof(th1Hash)); + return rc; +} + +int wolfSPDM_BuildPskFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + byte th2Hash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + word32 offset = 0; + int rc; + + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* PSK_FINISH = header(4) + VerifyData(48) = 52 bytes */ + if (*bufSz < 4 + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Header */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_PSK_FINISH; + buf[offset++] = 0x00; /* Param1 */ + buf[offset++] = 0x00; /* Param2 */ + + /* Add PSK_FINISH header to transcript, compute TH2 */ + rc = wolfSPDM_TranscriptAdd(ctx, buf, offset); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); + } + + /* RequesterVerifyData = HMAC(reqFinishedKey, TH2) */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, + verifyData); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&buf[offset], verifyData, WOLFSPDM_HASH_SIZE); + offset += WOLFSPDM_HASH_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, verifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + *bufSz = offset; + } + + wc_ForceZero(th2Hash, sizeof(th2Hash)); + wc_ForceZero(verifyData, sizeof(verifyData)); + return rc; +} + +int wolfSPDM_ParsePskFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + + if (buf[1] == SPDM_PSK_FINISH_RSP) { + int addRc = wolfSPDM_TranscriptAdd(ctx, buf, 4); + if (addRc != WOLFSPDM_SUCCESS) { + return addRc; + } + ctx->state = WOLFSPDM_STATE_FINISH; + wolfSPDM_DebugPrint(ctx, "PSK_FINISH_RSP received\n"); + return WOLFSPDM_SUCCESS; + } + + if (buf[1] == SPDM_ERROR) { + wolfSPDM_DebugPrint(ctx, "PSK_FINISH error: 0x%02x\n", buf[2]); + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_E_BAD_STATE; +} + +/* ----- PSK Key Derivation ----- */ + +int wolfSPDM_DeriveHandshakeKeysPsk(WOLFSPDM_CTX* ctx, const byte* th1Hash) +{ + byte salt[WOLFSPDM_HASH_SIZE]; + int rc; + + if (ctx == NULL || th1Hash == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (ctx->pskSz == 0) { + return WOLFSPDM_E_BAD_STATE; + } + + /* PSK mode: Salt_0 = 0xFF-filled (per TCG PSK specification). */ + XMEMSET(salt, 0xFF, sizeof(salt)); + + /* HandshakeSecret = HKDF-Extract(0xFF-salt, PSK) */ + rc = wc_HKDF_Extract(WC_SHA384, salt, sizeof(salt), + ctx->psk, ctx->pskSz, ctx->handshakeSecret); + if (rc != 0) { + wc_ForceZero(ctx->psk, sizeof(ctx->psk)); + ctx->pskSz = 0; + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wolfSPDM_DeriveFromHandshakeSecret(ctx, th1Hash); + + /* Zero PSK immediately after key derivation */ + wc_ForceZero(ctx->psk, sizeof(ctx->psk)); + ctx->pskSz = 0; + + return rc; +} + +/* ----- Shared PSK Connection Flow ----- */ + +/* GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> + * PSK_EXCHANGE -> PSK_FINISH -> app key derivation */ +int wolfSPDM_ConnectPsk(WOLFSPDM_CTX* ctx) +{ + int rc; + byte txBuf[128]; + byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte finBuf[64]; + byte encBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte decBuf[64]; + word32 txSz; + word32 rxSz; + word32 finSz; + word32 encSz; + word32 decSz; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->pskSz == 0) { + wolfSPDM_DebugPrint(ctx, "PSK: No PSK set\n"); + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugPrint(ctx, "PSK: Starting SPDM connection\n"); + + ctx->state = WOLFSPDM_STATE_INIT; + wolfSPDM_TranscriptReset(ctx); + + /* Step 1: GET_VERSION */ + wolfSPDM_DebugPrint(ctx, "PSK Step 1: GET_VERSION\n"); + rc = wolfSPDM_GetVersion(ctx); + + /* Steps 2-3: GET_CAPABILITIES + NEGOTIATE_ALGORITHMS + * Not mandatory for PSK mode per TCG PC Client PSK spec. + * NS350 supports direct GET_VERSION -> PSK_EXCHANGE. */ + + /* Step 2: PSK_EXCHANGE / PSK_EXCHANGE_RSP */ + if (rc == WOLFSPDM_SUCCESS) { + txSz = sizeof(txBuf); + rxSz = sizeof(rxBuf); + + wolfSPDM_DebugPrint(ctx, "PSK Step 4: PSK_EXCHANGE\n"); + rc = wolfSPDM_BuildPskExchange(ctx, txBuf, &txSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParsePskExchangeRsp(ctx, rxBuf, rxSz); + } + + /* Step 5: PSK_FINISH / PSK_FINISH_RSP (encrypted) */ + if (rc == WOLFSPDM_SUCCESS) { + finSz = sizeof(finBuf); + encSz = sizeof(encBuf); + rxSz = sizeof(rxBuf); + decSz = sizeof(decBuf); + + wolfSPDM_DebugPrint(ctx, "PSK Step 5: PSK_FINISH\n"); + rc = wolfSPDM_BuildPskFinish(ctx, finBuf, &finSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_EncryptInternal(ctx, finBuf, finSz, encBuf, &encSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParsePskFinishRsp(ctx, decBuf, decSz); + } + + /* Derive application data keys */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveAppDataKeys(ctx); + } + + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_CONNECTED; + wolfSPDM_DebugPrint(ctx, "PSK: SPDM Session Established! " + "SessionID=0x%08x\n", ctx->sessionId); + } + else { + ctx->state = WOLFSPDM_STATE_ERROR; + } + + /* Always zero sensitive stack buffers */ + wc_ForceZero(txBuf, sizeof(txBuf)); + wc_ForceZero(rxBuf, sizeof(rxBuf)); + wc_ForceZero(finBuf, sizeof(finBuf)); + wc_ForceZero(encBuf, sizeof(encBuf)); + wc_ForceZero(decBuf, sizeof(decBuf)); + + return rc; +} + +#endif /* WOLFSPDM_PSK */ + diff --git a/src/spdm_responder.c b/src/spdm_responder.c new file mode 100644 index 0000000..b94e707 --- /dev/null +++ b/src/spdm_responder.c @@ -0,0 +1,1289 @@ +/* spdm_responder.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_RESPONDER + +#include +/* spdm_tcg.h is included unconditionally - the transport framing + * constants (TAG_CLEAR/SECURED, HEADER_SIZE) are needed by the + * responder's frame-discrimination check even in PSK-only builds. */ +#include + +struct WOLFSPDM_RESP_CTX { + WOLFSPDM_CTX ctx; + + struct { + unsigned int useTcg : 1; + unsigned int usePsk : 1; + unsigned int hasIdKey : 1; + unsigned int initialized : 1; + unsigned int spdmOnlyLock : 1; /* SPDMONLY lock: plaintext TPM + * rejected with TPM_RC_DISABLED */ + unsigned int pskProvisioned : 1; /* PSK_SET / PSK_CLR vendor state */ + unsigned int clearAuthSet : 1; /* a ClearAuth digest is stored */ + unsigned int sessionAsym : 1; /* session came from KEY_EXCHANGE */ + unsigned int pendingAsym : 1; /* KEY_EX reached via KEY_EXCHANGE */ + } flags; + + /* SHA-384(ClearAuth) stored on PSK_SET, verified on PSK_CLR. */ + byte clearAuthDigest[WOLFSPDM_HASH_SIZE]; + + byte idPrivKey[WOLFSPDM_ECC_KEY_SIZE]; + word32 idPrivKeyLen; + byte idPubKey[WOLFSPDM_ECC_POINT_SIZE]; + word32 idPubKeyLen; + + /* Persistent PSK store. wolfSPDM_DeriveHandshakeKeysPsk wipes + * ctx->psk after each derivation; we reload from here on every + * PSK_EXCHANGE so the responder can serve multiple sessions. */ + byte pskStore[WOLFSPDM_PSK_MAX_SIZE]; + word32 pskStoreSz; + byte pskHintStore[WOLFSPDM_PSK_HINT_MAX]; + word32 pskHintStoreSz; + + WOLFSPDM_RESP_TPM_CB tpmCb; + void* tpmCbUserCtx; + + /* Per-context working buffers. Previously file-scope `static` - + * moved here so each ctx is independently reentrant. */ + byte secureInPlain[WOLFSPDM_MAX_TPM_MSG_SIZE]; + byte secureOutPlain[WOLFSPDM_MAX_TPM_MSG_SIZE]; + byte vdInPayload[WOLFSPDM_MAX_TPM_MSG_SIZE]; + byte vdOutPayload[WOLFSPDM_MAX_TPM_MSG_SIZE]; +}; + +/* Compile-time guarantee that the public static-size macro is large + * enough for the actual struct. If this fires, raise the +1024 slack + * in WOLFSPDM_RESP_CTX_STATIC_SIZE in spdm_responder.h. */ +typedef char wolfSPDM_resp_ctx_size_check_[ + (sizeof(struct WOLFSPDM_RESP_CTX) <= WOLFSPDM_RESP_CTX_STATIC_SIZE) + ? 1 : -1]; + +int wolfSPDM_RespInit(WOLFSPDM_RESP_CTX* ctx) +{ + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMSET(ctx, 0, sizeof(*ctx)); + + rc = wolfSPDM_Init(&ctx->ctx); + if (rc == WOLFSPDM_SUCCESS) { + ctx->flags.initialized = 1; + } + + return rc; +} + +void wolfSPDM_RespFree(WOLFSPDM_RESP_CTX* ctx) +{ + if (ctx == NULL) { + return; + } + wc_ForceZero(ctx->idPrivKey, sizeof(ctx->idPrivKey)); + wc_ForceZero(ctx->pskStore, sizeof(ctx->pskStore)); + wc_ForceZero(ctx->clearAuthDigest, sizeof(ctx->clearAuthDigest)); + wolfSPDM_Free(&ctx->ctx); + XMEMSET(ctx, 0, sizeof(*ctx)); +} + +int wolfSPDM_RespGetCtxSize(void) +{ + return (int)sizeof(struct WOLFSPDM_RESP_CTX); +} + +int wolfSPDM_RespSetMode(WOLFSPDM_RESP_CTX* ctx, int useTcg, int usePsk) +{ + if (ctx == NULL || !ctx->flags.initialized) { + return WOLFSPDM_E_INVALID_ARG; + } + if (!useTcg && !usePsk) { + return WOLFSPDM_E_INVALID_ARG; + } +#ifndef WOLFSPDM_TCG + if (useTcg) { + return WOLFSPDM_E_NOT_AVAILABLE; + } +#endif +#ifndef WOLFSPDM_PSK + if (usePsk) { + return WOLFSPDM_E_NOT_AVAILABLE; + } +#endif + ctx->flags.useTcg = (useTcg != 0); + ctx->flags.usePsk = (usePsk != 0); + /* Pick a mode so encrypt/decrypt use the 14-byte TCG AAD format. */ + ctx->ctx.mode = usePsk ? WOLFSPDM_MODE_NATIONS_PSK : WOLFSPDM_MODE_NUVOTON; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_RespSetPSK(WOLFSPDM_RESP_CTX* ctx, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz) +{ +#ifndef WOLFSPDM_PSK + (void)ctx; + (void)psk; + (void)pskSz; + (void)hint; + (void)hintSz; + return WOLFSPDM_E_NOT_AVAILABLE; +#else + int rc; + if (ctx == NULL || !ctx->flags.initialized) { + return WOLFSPDM_E_INVALID_ARG; + } + if (psk == NULL || pskSz == 0 || pskSz > sizeof(ctx->pskStore)) { + return WOLFSPDM_E_INVALID_ARG; + } + /* Commit the inner context first so a rejected PSK leaves no partially + * provisioned responder state */ + rc = wolfSPDM_SetPSK(&ctx->ctx, psk, pskSz, hint, hintSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + XMEMCPY(ctx->pskStore, psk, pskSz); + ctx->pskStoreSz = pskSz; + if (hint != NULL && hintSz > 0 && hintSz <= sizeof(ctx->pskHintStore)) { + XMEMCPY(ctx->pskHintStore, hint, hintSz); + ctx->pskHintStoreSz = hintSz; + } + else { + ctx->pskHintStoreSz = 0; + } + ctx->flags.pskProvisioned = 1; + return WOLFSPDM_SUCCESS; +#endif +} + +int wolfSPDM_RespSetIdentityKey(WOLFSPDM_RESP_CTX* ctx, + const byte* privKey, word32 privSz, + const byte* pubKey, word32 pubSz) +{ + if (ctx == NULL || !ctx->flags.initialized || + privKey == NULL || pubKey == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (privSz != WOLFSPDM_ECC_KEY_SIZE || + pubSz != WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + /* Rotating the key mid-session would attribute that session to a key it + * never negotiated with. */ + if (ctx->ctx.state != WOLFSPDM_STATE_INIT) { + return WOLFSPDM_E_BAD_STATE; + } + XMEMCPY(ctx->idPrivKey, privKey, privSz); + ctx->idPrivKeyLen = privSz; + XMEMCPY(ctx->idPubKey, pubKey, pubSz); + ctx->idPubKeyLen = pubSz; + ctx->flags.hasIdKey = 1; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_RespSetTpmCallback(WOLFSPDM_RESP_CTX* ctx, + WOLFSPDM_RESP_TPM_CB cb, void* userCtx) +{ + if (ctx == NULL || !ctx->flags.initialized) { + return WOLFSPDM_E_INVALID_ARG; + } + ctx->tpmCb = cb; + ctx->tpmCbUserCtx = userCtx; + return WOLFSPDM_SUCCESS; +} + +void wolfSPDM_RespSetDebug(WOLFSPDM_RESP_CTX* ctx, int enable) +{ + if (ctx == NULL) { + return; + } + wolfSPDM_SetDebug(&ctx->ctx, enable); +} + +int wolfSPDM_RespIsLocked(const WOLFSPDM_RESP_CTX* ctx) +{ + return (ctx != NULL && ctx->flags.spdmOnlyLock) ? 1 : 0; +} + +int wolfSPDM_RespIsSessionActive(const WOLFSPDM_RESP_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return (ctx->ctx.state == WOLFSPDM_STATE_CONNECTED && + ctx->ctx.sessionId != 0) ? 1 : 0; +} + +word32 wolfSPDM_RespGetIdentityKey(const WOLFSPDM_RESP_CTX* ctx, + const byte** idPub) +{ + if (ctx == NULL || idPub == NULL || !ctx->flags.hasIdKey || + !ctx->flags.sessionAsym) { + return 0; + } + *idPub = ctx->idPubKey; + return ctx->idPubKeyLen; +} + +void wolfSPDM_RespReset(WOLFSPDM_RESP_CTX* ctx) +{ + if (ctx == NULL) { + return; + } + /* Identity key, PSK, mode flags survive; only session-scoped material + * is wiped. */ + wc_ForceZero(ctx->ctx.reqDataKey, sizeof(ctx->ctx.reqDataKey)); + wc_ForceZero(ctx->ctx.rspDataKey, sizeof(ctx->ctx.rspDataKey)); + wc_ForceZero(ctx->ctx.reqDataIv, sizeof(ctx->ctx.reqDataIv)); + wc_ForceZero(ctx->ctx.rspDataIv, sizeof(ctx->ctx.rspDataIv)); + wc_ForceZero(ctx->ctx.handshakeSecret, + sizeof(ctx->ctx.handshakeSecret)); + wc_ForceZero(ctx->ctx.reqHsSecret, sizeof(ctx->ctx.reqHsSecret)); + wc_ForceZero(ctx->ctx.rspHsSecret, sizeof(ctx->ctx.rspHsSecret)); + wc_ForceZero(ctx->ctx.reqFinishedKey, + sizeof(ctx->ctx.reqFinishedKey)); + wc_ForceZero(ctx->ctx.rspFinishedKey, + sizeof(ctx->ctx.rspFinishedKey)); + wc_ForceZero(ctx->ctx.sharedSecret, sizeof(ctx->ctx.sharedSecret)); + ctx->ctx.sharedSecretSz = 0; + ctx->ctx.reqSeqNum = 0; + ctx->ctx.rspSeqNum = 0; + ctx->ctx.sessionId = 0; + ctx->ctx.state = WOLFSPDM_STATE_INIT; + ctx->flags.sessionAsym = 0; + ctx->flags.pendingAsym = 0; +} + +#ifdef WOLFSPDM_TCG + +#define WOLFSPDM_GET_CAPABILITIES 0xE1 +#define WOLFSPDM_CAPABILITIES 0x61 +#define WOLFSPDM_NEGOTIATE_ALGORITHMS 0xE3 +#define WOLFSPDM_ALGORITHMS 0x63 + +static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, byte* out, word32* outSz, int fromSecured, + char* vdCodeOut); +static int RespBuildKeyExchangeRsp(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, byte* out, word32* outSz); +static int RespHandleFinish(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, byte* out, word32* outSz); + +static int RespBuildErrorClear(WOLFSPDM_CTX* ctx, byte errCode, + byte errData, byte* out, word32* outSz) +{ + if (*outSz < 4) { + return WOLFSPDM_E_BUFFER_SMALL; + } + out[0] = (ctx->spdmVersion != 0) ? ctx->spdmVersion : SPDM_VERSION_10; + out[1] = SPDM_ERROR; + out[2] = errCode; + out[3] = errData; + *outSz = 4; + return WOLFSPDM_SUCCESS; +} + +/* Always paired: swap -> call existing req-side helper -> swap-back. */ +static void RespSwapDataDir(WOLFSPDM_CTX* ctx) +{ + byte tmpKey[WOLFSPDM_AEAD_KEY_SIZE]; + byte tmpIv[WOLFSPDM_AEAD_IV_SIZE]; + word64 tmpSeq; + + XMEMCPY(tmpKey, ctx->reqDataKey, sizeof(tmpKey)); + XMEMCPY(ctx->reqDataKey, ctx->rspDataKey, sizeof(tmpKey)); + XMEMCPY(ctx->rspDataKey, tmpKey, sizeof(tmpKey)); + + XMEMCPY(tmpIv, ctx->reqDataIv, sizeof(tmpIv)); + XMEMCPY(ctx->reqDataIv, ctx->rspDataIv, sizeof(tmpIv)); + XMEMCPY(ctx->rspDataIv, tmpIv, sizeof(tmpIv)); + + tmpSeq = ctx->reqSeqNum; + ctx->reqSeqNum = ctx->rspSeqNum; + ctx->rspSeqNum = tmpSeq; + + wc_ForceZero(tmpKey, sizeof(tmpKey)); + wc_ForceZero(tmpIv, sizeof(tmpIv)); +} + +static int RespEncrypt(WOLFSPDM_CTX* ctx, + const byte* plain, word32 plainSz, byte* enc, word32* encSz) +{ + int rc; + RespSwapDataDir(ctx); + rc = wolfSPDM_EncryptInternal(ctx, plain, plainSz, enc, encSz); + RespSwapDataDir(ctx); + return rc; +} + +static int RespDecrypt(WOLFSPDM_CTX* ctx, + const byte* enc, word32 encSz, byte* plain, word32* plainSz) +{ + int rc; + RespSwapDataDir(ctx); + rc = wolfSPDM_DecryptInternal(ctx, enc, encSz, plain, plainSz); + RespSwapDataDir(ctx); + return rc; +} + +/* The responder FINISH path has no 1.4 OpaqueData handling, so it tops out + * at 1.3 even when the requester build allows 1.4. */ +#if WOLFSPDM_MAX_SPDM_VERSION > SPDM_VERSION_13 +#define WOLFSPDM_RESP_MAX_VERSION SPDM_VERSION_13 +#else +#define WOLFSPDM_RESP_MAX_VERSION WOLFSPDM_MAX_SPDM_VERSION +#endif +#if WOLFSPDM_MIN_SPDM_VERSION > WOLFSPDM_RESP_MAX_VERSION +#error "SPDM responder version range is empty" +#endif +#define WOLFSPDM_RESP_VERSION_COUNT \ + (WOLFSPDM_RESP_MAX_VERSION - WOLFSPDM_MIN_SPDM_VERSION + 1) + +static int RespBuildVersion(WOLFSPDM_CTX* ctx, + const byte* req, word32 reqSz, + byte* out, word32* outSz) +{ + word32 off; + byte ver; + + (void)req; + (void)reqSz; + if (*outSz < 6 + 2 * WOLFSPDM_RESP_VERSION_COUNT) { + return WOLFSPDM_E_BUFFER_SMALL; + } + off = 0; + out[off++] = SPDM_VERSION_10; + out[off++] = SPDM_VERSION; + out[off++] = 0x00; + out[off++] = 0x00; + /* VersionNumberEntryCount (LE) at offset 4. */ + out[off++] = WOLFSPDM_RESP_VERSION_COUNT; + out[off++] = 0x00; + /* Entries: 2 bytes each, byte+1 holds the version (Major<<4 | Minor). */ + for (ver = WOLFSPDM_MIN_SPDM_VERSION; ver <= WOLFSPDM_RESP_MAX_VERSION; + ver++) { + out[off++] = 0x00; + out[off++] = ver; + } + *outSz = off; + /* The requester picks from the advertised set; its next request + * carries the selection (see RespSelectVersion). */ + ctx->spdmVersion = 0; + ctx->state = WOLFSPDM_STATE_VERSION; + return WOLFSPDM_SUCCESS; +} + +/* Adopt the version the requester selects on its first request after + * VERSION and pin it for the rest of the connection. Before any VERSION + * exchange only pre-negotiation vendor commands (GET_STS_, PSK_SET_) may + * pass, and they select nothing. */ +static int RespSelectVersion(WOLFSPDM_CTX* ctx, byte reqVer, int isVendor) +{ + if (ctx->spdmVersion != 0) { + return (reqVer == ctx->spdmVersion) ? WOLFSPDM_SUCCESS : + WOLFSPDM_E_VERSION_MISMATCH; + } + if (ctx->state != WOLFSPDM_STATE_VERSION) { + return isVendor ? WOLFSPDM_SUCCESS : WOLFSPDM_E_BAD_STATE; + } + if (reqVer < WOLFSPDM_MIN_SPDM_VERSION || + reqVer > WOLFSPDM_RESP_MAX_VERSION) { + return WOLFSPDM_E_VERSION_MISMATCH; + } + ctx->spdmVersion = reqVer; + return WOLFSPDM_SUCCESS; +} + +/* Flags: ENCRYPT/MAC/KEY_EX_CAP always; PSK_CAP iff pskEnabled. */ +static int RespBuildCapabilities(WOLFSPDM_CTX* ctx, int pskEnabled, + const byte* req, word32 reqSz, + byte* out, word32* outSz) +{ + word32 off; + word32 flags; + + (void)req; + (void)reqSz; + if (*outSz < 20) { + return WOLFSPDM_E_BUFFER_SMALL; + } + flags = 0x000193C0UL; + if (pskEnabled) { + flags |= 0x00000400UL; + } + off = 0; + out[off++] = ctx->spdmVersion; + out[off++] = WOLFSPDM_CAPABILITIES; + out[off++] = 0x00; + out[off++] = 0x00; + out[off++] = 0x00; + out[off++] = 0x1F; + out[off++] = 0x00; + out[off++] = 0x00; + SPDM_Set32LE(out + off, flags); + off += 4; + out[off++] = 0xC0; out[off++] = 0x07; out[off++] = 0x00; out[off++] = 0x00; + out[off++] = 0xC0; out[off++] = 0x07; out[off++] = 0x00; out[off++] = 0x00; + *outSz = off; + return WOLFSPDM_SUCCESS; +} + +/* Algorithm Set B (P-384/SHA-384/AES-256-GCM) is the only set we support. */ +static int RespBuildAlgorithms(WOLFSPDM_CTX* ctx, + const byte* req, word32 reqSz, + byte* out, word32* outSz) +{ + word32 off; + + (void)req; + (void)reqSz; + if (*outSz < 52) { + return WOLFSPDM_E_BUFFER_SMALL; + } + off = 0; + out[off++] = ctx->spdmVersion; + out[off++] = WOLFSPDM_ALGORITHMS; + out[off++] = 0x04; + out[off++] = 0x00; + out[off++] = 0x34; out[off++] = 0x00; + out[off++] = 0x00; out[off++] = 0x02; + XMEMSET(out + off, 0, 4); off += 4; + out[off++] = 0x80; out[off++] = 0x00; out[off++] = 0x00; out[off++] = 0x00; + out[off++] = 0x02; out[off++] = 0x00; out[off++] = 0x00; out[off++] = 0x00; + XMEMSET(out + off, 0, 12); off += 12; + out[off++] = 0x00; out[off++] = 0x00; out[off++] = 0x00; out[off++] = 0x00; + out[off++] = 0x02; out[off++] = 0x20; out[off++] = 0x10; out[off++] = 0x00; + out[off++] = 0x03; out[off++] = 0x20; out[off++] = 0x02; out[off++] = 0x00; + out[off++] = 0x04; out[off++] = 0x20; out[off++] = 0x80; out[off++] = 0x00; + out[off++] = 0x05; out[off++] = 0x20; out[off++] = 0x01; out[off++] = 0x00; + *outSz = off; + return WOLFSPDM_SUCCESS; +} + +#ifdef WOLFSPDM_PSK +/* Transcript add splits across key derivation, matching the requester. */ +static int RespBuildPskExchangeRsp(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, + byte* out, word32* outSz) +{ + WOLFSPDM_CTX* ctx = &rctx->ctx; + word16 reqContextLen; + word16 reqHintLen; + word16 reqOpaqueLen; + word32 off; + word32 partialLen; + byte th1Hash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + int rc; + + if (inSz < 12 || rctx->pskStoreSz == 0) { + return WOLFSPDM_E_BAD_STATE; + } + if (*outSz < 12u + 32u + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + /* Reload PSK from the persistent store - the requester-side helper + * zeroes ctx->psk after derivation. */ + XMEMCPY(ctx->psk, rctx->pskStore, rctx->pskStoreSz); + ctx->pskSz = rctx->pskStoreSz; + if (rctx->pskHintStoreSz > 0) { + XMEMCPY(ctx->pskHint, rctx->pskHintStore, rctx->pskHintStoreSz); + ctx->pskHintSz = rctx->pskHintStoreSz; + } + + ctx->reqSessionId = SPDM_Get16LE(&in[4]); + reqHintLen = SPDM_Get16LE(&in[6]); + reqContextLen = SPDM_Get16LE(&in[8]); + reqOpaqueLen = SPDM_Get16LE(&in[10]); + /* Every declared variable-length field must fit within the request */ + if ((word32)12 + reqHintLen + reqContextLen + reqOpaqueLen > inSz) { + return WOLFSPDM_E_FRAMING; + } + + ctx->rspSessionId = 0xFFFE; + ctx->sessionId = (word32)ctx->reqSessionId | + ((word32)ctx->rspSessionId << 16); + + off = 0; + out[off++] = ctx->spdmVersion; + out[off++] = SPDM_PSK_EXCHANGE_RSP; + out[off++] = 0x00; + out[off++] = 0x00; + SPDM_Set16LE(&out[off], ctx->rspSessionId); off += 2; + out[off++] = 0x00; + out[off++] = 0x00; + SPDM_Set16LE(&out[off], 32); off += 2; + SPDM_Set16LE(&out[off], 0); off += 2; + rc = wolfSPDM_GetRandom(ctx, &out[off], 32); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + off += 32; + partialLen = off; + + rc = wolfSPDM_TranscriptAdd(ctx, out, partialLen); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th1Hash); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->th1, th1Hash, WOLFSPDM_HASH_SIZE); + rc = wolfSPDM_DeriveHandshakeKeysPsk(ctx, th1Hash); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, th1Hash, + verifyData); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&out[off], verifyData, WOLFSPDM_HASH_SIZE); + off += WOLFSPDM_HASH_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, verifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + *outSz = off; + ctx->state = WOLFSPDM_STATE_KEY_EX; + rctx->flags.pendingAsym = 0; + } + + wc_ForceZero(verifyData, sizeof(verifyData)); + wc_ForceZero(th1Hash, sizeof(th1Hash)); + return rc; +} +#endif /* WOLFSPDM_PSK */ + +static int RespDispatchClear(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, + byte* out, word32* outSz) +{ + WOLFSPDM_CTX* ctx = &rctx->ctx; + byte code; + int rc; + int handlerManagesTranscript = 0; + char vdCode[WOLFSPDM_VDCODE_LEN + 1]; + + if (inSz < 2) { + return WOLFSPDM_E_FRAMING; + } + code = in[1]; + XMEMSET(vdCode, 0, sizeof(vdCode)); + + if (code == SPDM_GET_VERSION) { + wolfSPDM_TranscriptReset(ctx); + wolfSPDM_RespReset(rctx); + } + else { + rc = RespSelectVersion(ctx, in[0], + code == SPDM_VENDOR_DEFINED_REQUEST); + if (rc == WOLFSPDM_E_BAD_STATE) { + return RespBuildErrorClear(ctx, + SPDM_ERROR_UNEXPECTED_REQUEST, 0, out, outSz); + } + if (rc != WOLFSPDM_SUCCESS) { + return RespBuildErrorClear(ctx, + SPDM_ERROR_MAJOR_VERSION_MISMATCH, 0, out, outSz); + } + } + + /* VENDOR_DEFINED bytes don't go into the SPDM transcript - the + * requester's wolfSPDM_TCG_VendorCmdClear doesn't add them, so the + * responder mustn't either. GET_PUBK contributes via Ct = SHA-384 + * of its response payload, added separately below. */ + if (code != SPDM_VENDOR_DEFINED_REQUEST) { + rc = wolfSPDM_TranscriptAdd(ctx, in, inSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + } + + switch (code) { + case SPDM_GET_VERSION: + rc = RespBuildVersion(ctx, in, inSz, out, outSz); + break; + case WOLFSPDM_GET_CAPABILITIES: + rc = RespBuildCapabilities(ctx, rctx->flags.usePsk, + in, inSz, out, outSz); + break; + case WOLFSPDM_NEGOTIATE_ALGORITHMS: + rc = RespBuildAlgorithms(ctx, in, inSz, out, outSz); + break; +#ifdef WOLFSPDM_PSK + case SPDM_PSK_EXCHANGE: + rc = RespBuildPskExchangeRsp(rctx, in, inSz, out, outSz); + handlerManagesTranscript = 1; + break; +#endif + case SPDM_KEY_EXCHANGE: + rc = RespBuildKeyExchangeRsp(rctx, in, inSz, out, outSz); + handlerManagesTranscript = 1; + break; + case SPDM_VENDOR_DEFINED_REQUEST: + rc = RespHandleVendorDefined(rctx, in, inSz, out, outSz, 0, + vdCode); + handlerManagesTranscript = 1; + /* For GET_PUBK specifically, mirror what the requester does: + * add Ct = SHA-384(rspPubKey) to the transcript. Keyed on the + * parsed VdCode, whose wire offset varies with vendorIdLen. */ + if (rc == WOLFSPDM_SUCCESS && + XMEMCMP(vdCode, WOLFSPDM_VDCODE_GET_PUBK, + WOLFSPDM_VDCODE_LEN) == 0) { + byte ct[WOLFSPDM_HASH_SIZE]; + int hrc = wolfSPDM_Sha384Hash(ct, + rctx->idPubKey, rctx->idPubKeyLen, NULL, 0, NULL, 0); + if (hrc == WOLFSPDM_SUCCESS) { + hrc = wolfSPDM_TranscriptAdd(ctx, ct, WOLFSPDM_HASH_SIZE); + } + wc_ForceZero(ct, sizeof(ct)); + if (hrc != WOLFSPDM_SUCCESS) { + rc = hrc; + } + } + break; + default: + return RespBuildErrorClear(ctx, + SPDM_ERROR_UNSUPPORTED_REQUEST, code, out, outSz); + } + + if (rc == WOLFSPDM_SUCCESS && !handlerManagesTranscript) { + rc = wolfSPDM_TranscriptAdd(ctx, out, *outSz); + } + return rc; +} + +#define WOLFSPDM_VENDOR_DEFINED_RSP 0x7E + +/* KEY_EXCHANGE (clear) -> KEY_EXCHANGE_RSP (clear). Mirror of + * wolfSPDM_BuildKeyExchange / wolfSPDM_ParseKeyExchangeRsp flipped to the + * responder side: receive requester pubkey, generate own ephemeral key, + * compute shared secret, sign TH1 with the identity key, derive handshake + * keys, emit ResponderVerifyData. */ +static int RespBuildKeyExchangeRsp(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, + byte* out, word32* outSz) +{ + WOLFSPDM_CTX* ctx = &rctx->ctx; + byte peerPubKeyX[WOLFSPDM_ECC_KEY_SIZE]; + byte peerPubKeyY[WOLFSPDM_ECC_KEY_SIZE]; + byte myPubX[WOLFSPDM_ECC_KEY_SIZE]; + byte myPubY[WOLFSPDM_ECC_KEY_SIZE]; + word32 myPubXSz = sizeof(myPubX); + word32 myPubYSz = sizeof(myPubY); + byte th1[WOLFSPDM_HASH_SIZE]; + byte signMsgHash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + byte savedReqPriv[WOLFSPDM_ECC_KEY_SIZE]; + byte savedReqPub[WOLFSPDM_ECC_POINT_SIZE]; + word32 savedReqPrivLen; + byte savedHasReqKeyPair; + word32 sigSz = WOLFSPDM_ECC_SIG_SIZE; + word32 off; + word32 partialLen; + int rc; + + if (inSz < 136 || !rctx->flags.hasIdKey) { + return WOLFSPDM_E_BAD_STATE; + } + if (*outSz < 138u + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + ctx->reqSessionId = SPDM_Get16LE(&in[4]); + XMEMCPY(peerPubKeyX, &in[40], WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(peerPubKeyY, &in[88], WOLFSPDM_ECC_KEY_SIZE); + + ctx->rspSessionId = 0xFFFE; + ctx->sessionId = (word32)ctx->reqSessionId | + ((word32)ctx->rspSessionId << 16); + + off = 0; + out[off++] = ctx->spdmVersion; + out[off++] = SPDM_KEY_EXCHANGE_RSP; + out[off++] = 0x00; + out[off++] = 0x00; + SPDM_Set16LE(&out[off], ctx->rspSessionId); off += 2; + out[off++] = 0x00; /* MutAuthRequested */ + out[off++] = 0x00; /* SlotID */ + + rc = wolfSPDM_GetRandom(ctx, &out[off], WOLFSPDM_RANDOM_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + off += WOLFSPDM_RANDOM_SIZE; + + rc = wolfSPDM_GenerateEphemeralKey(ctx); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ExportEphemeralPubKey(ctx, + myPubX, &myPubXSz, myPubY, &myPubYSz); + } + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + XMEMCPY(&out[off], myPubX, WOLFSPDM_ECC_KEY_SIZE); off += WOLFSPDM_ECC_KEY_SIZE; + XMEMCPY(&out[off], myPubY, WOLFSPDM_ECC_KEY_SIZE); off += WOLFSPDM_ECC_KEY_SIZE; + SPDM_Set16LE(&out[off], 0); off += 2; /* OpaqueLength = 0 */ + + partialLen = off; + + rc = wolfSPDM_TranscriptAdd(ctx, out, partialLen); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th1); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "responder-key_exchange_rsp signing", 34, th1, signMsgHash); + } + if (rc == WOLFSPDM_SUCCESS) { + /* wolfSPDM_SignHash signs with ctx->reqPrivKey/reqPubKey. Swap the + * identity key in temporarily; restore on exit. */ + XMEMCPY(savedReqPriv, ctx->reqPrivKey, WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(savedReqPub, ctx->reqPubKey, WOLFSPDM_ECC_POINT_SIZE); + savedReqPrivLen = ctx->reqPrivKeyLen; + savedHasReqKeyPair = ctx->flags.hasReqKeyPair; + XMEMCPY(ctx->reqPrivKey, rctx->idPrivKey, WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(ctx->reqPubKey, rctx->idPubKey, WOLFSPDM_ECC_POINT_SIZE); + ctx->reqPrivKeyLen = WOLFSPDM_ECC_KEY_SIZE; + ctx->flags.hasReqKeyPair = 1; + rc = wolfSPDM_SignHash(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, + &out[off], &sigSz); + XMEMCPY(ctx->reqPrivKey, savedReqPriv, WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(ctx->reqPubKey, savedReqPub, WOLFSPDM_ECC_POINT_SIZE); + ctx->reqPrivKeyLen = savedReqPrivLen; + ctx->flags.hasReqKeyPair = savedHasReqKeyPair; + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, &out[off], WOLFSPDM_ECC_SIG_SIZE); + off += WOLFSPDM_ECC_SIG_SIZE; + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeSharedSecret(ctx, peerPubKeyX, peerPubKeyY); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, ctx->th1); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveHandshakeKeys(ctx, ctx->th1); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, ctx->th1, + verifyData); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&out[off], verifyData, WOLFSPDM_HASH_SIZE); + off += WOLFSPDM_HASH_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, verifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + *outSz = off; + ctx->state = WOLFSPDM_STATE_KEY_EX; + rctx->flags.pendingAsym = 1; + } + + wc_ForceZero(savedReqPriv, sizeof(savedReqPriv)); + wc_ForceZero(verifyData, sizeof(verifyData)); + wc_ForceZero(signMsgHash, sizeof(signMsgHash)); + wc_ForceZero(th1, sizeof(th1)); + return rc; +} + +/* FINISH (encrypted) -> FINISH_RSP (encrypted). Verifies the requester's + * HMAC over TH2 with reqFinishedKey, then emits a 4-byte FINISH_RSP. App + * keys derive after encryption (handled in the secured dispatcher). */ +static int RespHandleFinish(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, byte* out, word32* outSz) +{ + WOLFSPDM_CTX* ctx = &rctx->ctx; + byte th2[WOLFSPDM_HASH_SIZE]; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + int rc; + word32 i; + volatile int diff = 0; + + if (inSz < 4u + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_FRAMING; + } + if (*outSz < 4) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + rc = wolfSPDM_TranscriptAdd(ctx, in, 4); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th2); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->th2, th2, WOLFSPDM_HASH_SIZE); + rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2, + expectedHmac); + } + if (rc == WOLFSPDM_SUCCESS) { + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { + diff |= expectedHmac[i] ^ in[4 + i]; + } + if (diff != 0) { + rc = WOLFSPDM_E_BAD_HMAC; + } + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, in + 4, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + out[0] = ctx->spdmVersion; + out[1] = SPDM_FINISH_RSP; + out[2] = 0x00; + out[3] = 0x00; + *outSz = 4; + rc = wolfSPDM_TranscriptAdd(ctx, out, 4); + } + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_CONNECTED; + rctx->flags.sessionAsym = 1; + } + + wc_ForceZero(expectedHmac, sizeof(expectedHmac)); + wc_ForceZero(th2, sizeof(th2)); + return rc; +} + +#ifdef WOLFSPDM_PSK +static int RespHandlePskFinish(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, byte* out, word32* outSz) +{ + WOLFSPDM_CTX* ctx = &rctx->ctx; + byte th2Hash[WOLFSPDM_HASH_SIZE]; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + int rc; + word32 i; + volatile int diff = 0; + + if (inSz < 4u + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_FRAMING; + } + if (*outSz < 4) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + rc = wolfSPDM_TranscriptAdd(ctx, in, 4); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); + rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, + expectedHmac); + } + if (rc == WOLFSPDM_SUCCESS) { + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { + diff |= expectedHmac[i] ^ in[4 + i]; + } + if (diff != 0) { + rc = WOLFSPDM_E_BAD_HMAC; + } + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, in + 4, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + out[0] = ctx->spdmVersion; + out[1] = SPDM_PSK_FINISH_RSP; + out[2] = 0x00; + out[3] = 0x00; + *outSz = 4; + rc = wolfSPDM_TranscriptAdd(ctx, out, 4); + } + /* App-key derivation runs in the caller AFTER PSK_FINISH_RSP is + * encrypted with the still-current handshake keys (otherwise the + * requester decrypts with handshake keys but we wrote with app keys). */ + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_CONNECTED; + rctx->flags.sessionAsym = 0; + } + + wc_ForceZero(expectedHmac, sizeof(expectedHmac)); + wc_ForceZero(th2Hash, sizeof(th2Hash)); + return rc; +} +#endif /* WOLFSPDM_PSK */ + +static int RespBuildEndSessionAck(WOLFSPDM_CTX* ctx, + const byte* in, word32 inSz, byte* out, word32* outSz) +{ + (void)in; + (void)inSz; + if (*outSz < 4) { + return WOLFSPDM_E_BUFFER_SMALL; + } + out[0] = ctx->spdmVersion; + out[1] = SPDM_END_SESSION_ACK; + out[2] = 0x00; + out[3] = 0x00; + *outSz = 4; + return WOLFSPDM_SUCCESS; +} + +static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, byte* out, word32* outSz, int fromSecured, + char* vdCodeOut) +{ + WOLFSPDM_CTX* ctx = &rctx->ctx; + char vdCode[WOLFSPDM_VDCODE_LEN + 1]; + /* TPM2_CMD payloads carry large TPM commands (CreatePrimary RSA ~400 + * bytes), so buffers are sized to MAX_MSG_SIZE. Storage lives in the + * per-context struct so concurrent ctxs don't share working memory. */ + byte* payload = rctx->vdInPayload; + byte* respPayload = rctx->vdOutPayload; + word32 payloadSz; + word32 respPayloadSz = 0; + word32 totalSz; + word32 off; + int rc; + + payloadSz = WOLFSPDM_MAX_TPM_MSG_SIZE; + rc = wolfSPDM_ParseVendorDefined(in, inSz, vdCode, payload, &payloadSz); + if (rc < 0) { + return rc; + } + if (vdCodeOut != NULL) { + XMEMCPY(vdCodeOut, vdCode, WOLFSPDM_VDCODE_LEN + 1); + } + + /* TPM2_CMD, GIVE_PUB and SPDMONLY are only ever sent inside a secured + * message; honouring them from a clear frame would defeat the + * bus-snooping defence. GET_PUBK / GET_STS_ / PSK_* are pre-session by + * design and stay reachable in the clear. */ + if (!fromSecured && + (XSTRCMP(vdCode, WOLFSPDM_VDCODE_TPM2_CMD) == 0 || + XSTRCMP(vdCode, WOLFSPDM_VDCODE_GIVE_PUB) == 0 || + XSTRCMP(vdCode, WOLFSPDM_VDCODE_SPDMONLY) == 0)) { + return WOLFSPDM_E_BAD_STATE; + } + + if (XSTRCMP(vdCode, WOLFSPDM_VDCODE_TPM2_CMD) == 0) { + /* Reserve the VENDOR_DEFINED_RSP wrapper overhead + * (1+1+1+1+2+1+2 = 9 fixed bytes + vdCode) so the TPM callback + * cannot return more data than will fit inside the response + * envelope. Otherwise the wrapper below silently returns + * E_BUFFER_SMALL on the largest TPM responses. */ + word32 tpmRespCap = WOLFSPDM_MAX_TPM_MSG_SIZE + - (9 + WOLFSPDM_VDCODE_LEN); + if (rctx->tpmCb == NULL) { + return WOLFSPDM_E_BAD_STATE; + } + rc = rctx->tpmCb(rctx->tpmCbUserCtx, payload, payloadSz, + respPayload, tpmRespCap, &respPayloadSz); + if (rc != 0) { + return WOLFSPDM_E_IO_FAIL; + } + } +#ifdef WOLFSPDM_TCG + else if (XSTRCMP(vdCode, WOLFSPDM_VDCODE_GET_PUBK) == 0) { + if (!rctx->flags.hasIdKey) { + return WOLFSPDM_E_BAD_STATE; + } + if (rctx->idPubKeyLen > WOLFSPDM_MAX_MSG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(respPayload, rctx->idPubKey, rctx->idPubKeyLen); + respPayloadSz = rctx->idPubKeyLen; + } + else if (XSTRCMP(vdCode, WOLFSPDM_VDCODE_GIVE_PUB) == 0) { + if (payloadSz > sizeof(ctx->reqPubKeyTPMT)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(ctx->reqPubKeyTPMT, payload, payloadSz); + ctx->reqPubKeyTPMTLen = payloadSz; + respPayloadSz = 0; + } +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + /* GET_STS_ / SPDMONLY are vendor-format adapters. Nuvoton and Nations + * share the same vdcode strings and a compatible 4-byte status layout + * - byte[2] is "Reserved" on Nuvoton and "PSKSet" on Nations (Nuvoton + * never sets PSK, so a zero here is correct in either mode). */ + else if (XSTRCMP(vdCode, WOLFSPDM_VDCODE_GET_STS) == 0) { + respPayload[0] = 0x00; + respPayload[1] = 0x04; + respPayload[2] = rctx->flags.pskProvisioned ? 0x01 : 0x00; + respPayload[3] = rctx->flags.spdmOnlyLock ? 0x01 : 0x00; + respPayloadSz = 4; + } + else if (XSTRCMP(vdCode, WOLFSPDM_VDCODE_SPDMONLY) == 0) { + if (payloadSz >= 1 && payload[0] == WOLFSPDM_SPDMONLY_LOCK) { + rctx->flags.spdmOnlyLock = 1; + } + else { + rctx->flags.spdmOnlyLock = 0; + } + respPayloadSz = 0; + } +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#ifdef WOLFSPDM_NATIONS + /* PSK_SET_ / PSK_CLR_ are Nations-proprietary PSK provisioning carried + * over SPDM VENDOR_DEFINED. The DSP0274 spec leaves PSK delivery to the + * implementation; we adopt Nations' NSING format here. */ + else if (XSTRCMP(vdCode, "PSK_SET_") == 0) { + /* Payload: PSK + SHA-384(ClearAuth). */ + const word32 pskLen = (word32)sizeof(rctx->pskStore); + if (payloadSz != pskLen + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + /* Once a ClearAuth is registered, replacing the PSK requires + * PSK_CLR_ first, or that check is trivially skipped. A PSK set by + * configuration has no ClearAuth, so it may still be provisioned. */ + if (rctx->flags.clearAuthSet) { + return WOLFSPDM_E_BAD_STATE; + } + XMEMCPY(rctx->pskStore, payload, pskLen); + rctx->pskStoreSz = pskLen; + XMEMCPY(rctx->clearAuthDigest, payload + pskLen, WOLFSPDM_HASH_SIZE); + rctx->flags.pskProvisioned = 1; + rctx->flags.clearAuthSet = 1; + /* Mirror into ctx->psk so the next PSK_EXCHANGE can use it. */ + XMEMCPY(ctx->psk, rctx->pskStore, rctx->pskStoreSz); + ctx->pskSz = rctx->pskStoreSz; + respPayloadSz = 0; + } + else if (XSTRCMP(vdCode, "PSK_CLR_") == 0) { + /* Payload: ClearAuth(32 raw bytes). Verify SHA-384 matches stored. */ + byte digest[WOLFSPDM_HASH_SIZE]; + volatile int diff = 0; + word32 i; + if (payloadSz != 32 || !rctx->flags.pskProvisioned) { + return WOLFSPDM_E_INVALID_ARG; + } + rc = wolfSPDM_Sha384Hash(digest, payload, payloadSz, + NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + /* Constant-time compare, matching the FINISH HMAC paths. */ + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { + diff |= digest[i] ^ rctx->clearAuthDigest[i]; + } + wc_ForceZero(digest, sizeof(digest)); + if (diff != 0) { + return WOLFSPDM_E_BAD_HMAC; + } + wc_ForceZero(rctx->pskStore, sizeof(rctx->pskStore)); + wc_ForceZero(rctx->clearAuthDigest, sizeof(rctx->clearAuthDigest)); + rctx->pskStoreSz = 0; + rctx->flags.pskProvisioned = 0; + rctx->flags.clearAuthSet = 0; + wc_ForceZero(ctx->psk, sizeof(ctx->psk)); + ctx->pskSz = 0; + respPayloadSz = 0; + } +#endif /* WOLFSPDM_NATIONS */ +#endif /* WOLFSPDM_TCG */ + else { + return WOLFSPDM_E_NOT_AVAILABLE; + } + + /* Build VENDOR_DEFINED_RSP frame (response code 0x7E). */ + totalSz = 1 + 1 + 1 + 1 + 2 + 1 + 2 + WOLFSPDM_VDCODE_LEN + respPayloadSz; + if (*outSz < totalSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + off = 0; + out[off++] = ctx->spdmVersion; + out[off++] = WOLFSPDM_VENDOR_DEFINED_RSP; + out[off++] = 0x00; + out[off++] = 0x00; + SPDM_Set16LE(out + off, 0x0001); off += 2; + out[off++] = 0x00; + SPDM_Set16LE(out + off, (word16)(WOLFSPDM_VDCODE_LEN + respPayloadSz)); + off += 2; + XMEMCPY(out + off, vdCode, WOLFSPDM_VDCODE_LEN); + off += WOLFSPDM_VDCODE_LEN; + if (respPayloadSz > 0) { + XMEMCPY(out + off, respPayload, respPayloadSz); + off += respPayloadSz; + } + *outSz = off; + return WOLFSPDM_SUCCESS; +} + +static int RespDispatchSecured(WOLFSPDM_RESP_CTX* rctx, + const byte* securedIn, word32 securedInSz, + byte* securedOut, word32* securedOutSz) +{ + byte* plain = rctx->secureInPlain; + byte* respPlain = rctx->secureOutPlain; + WOLFSPDM_CTX* ctx = &rctx->ctx; + word32 plainSz; + word32 respPlainSz; + byte code; + int rc; + int sessionEnded = 0; + int derivedAppKeys = 0; + + /* KEY_EX has handshake traffic keys; CONNECTED has application traffic + * keys. In every other state, decryption would use unestablished key + * material (zeroed by initialization and reset). */ + if ((ctx->state != WOLFSPDM_STATE_KEY_EX && + ctx->state != WOLFSPDM_STATE_CONNECTED) || ctx->sessionId == 0) { + return WOLFSPDM_E_BAD_STATE; + } + + plainSz = WOLFSPDM_MAX_MSG_SIZE; + rc = RespDecrypt(ctx, securedIn, securedInSz, plain, &plainSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + if (plainSz < 2) { + return WOLFSPDM_E_FRAMING; + } + code = plain[1]; + + respPlainSz = WOLFSPDM_MAX_MSG_SIZE; + switch (code) { + /* A finish must match the exchange that opened KEY_EX and cannot run + * again once connected, or a PSK peer could relabel its session as + * identity-key authenticated with a plain FINISH. */ +#ifdef WOLFSPDM_PSK + case SPDM_PSK_FINISH: + if (ctx->state != WOLFSPDM_STATE_KEY_EX || + rctx->flags.pendingAsym) { + return WOLFSPDM_E_BAD_STATE; + } + rc = RespHandlePskFinish(rctx, plain, plainSz, + respPlain, &respPlainSz); + derivedAppKeys = (rc == WOLFSPDM_SUCCESS) ? 1 : 0; + break; +#endif + case SPDM_FINISH: + if (ctx->state != WOLFSPDM_STATE_KEY_EX || + !rctx->flags.pendingAsym) { + return WOLFSPDM_E_BAD_STATE; + } + rc = RespHandleFinish(rctx, plain, plainSz, + respPlain, &respPlainSz); + derivedAppKeys = (rc == WOLFSPDM_SUCCESS) ? 1 : 0; + break; + case SPDM_END_SESSION: + rc = RespBuildEndSessionAck(ctx, plain, plainSz, + respPlain, &respPlainSz); + sessionEnded = 1; + break; + case SPDM_VENDOR_DEFINED_REQUEST: + rc = RespHandleVendorDefined(rctx, plain, plainSz, + respPlain, &respPlainSz, 1, NULL); + break; + default: + rc = RespBuildErrorClear(ctx, + SPDM_ERROR_UNSUPPORTED_REQUEST, code, + respPlain, &respPlainSz); + break; + } + + if (rc == WOLFSPDM_SUCCESS) { + rc = RespEncrypt(ctx, respPlain, respPlainSz, + securedOut, securedOutSz); + } + if (rc == WOLFSPDM_SUCCESS && derivedAppKeys) { + rc = wolfSPDM_DeriveAppDataKeys(ctx); + } + + if (sessionEnded && rc == WOLFSPDM_SUCCESS) { + wolfSPDM_RespReset(rctx); + } + return rc; +} + +#endif /* WOLFSPDM_TCG */ + +int wolfSPDM_RespHandleMessage(WOLFSPDM_RESP_CTX* ctx, + const byte* inBuf, word32 inSz, + byte* outBuf, word32* outSz) +{ +#ifdef WOLFSPDM_TCG + word16 tag; + word32 msgSize; + word32 payloadSz; + word32 spdmOutSz; + word32 totalSz; + int rc; +#endif + + if (ctx == NULL || inBuf == NULL || outBuf == NULL || outSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + if (!ctx->flags.useTcg && !ctx->flags.usePsk) { + return WOLFSPDM_E_BAD_STATE; + } + + /* Bus-snooping defence: only TCG clear (0x8101) or secured (0x8201) + * frames are accepted; anything else is rejected here. */ + if (inSz < WOLFSPDM_TCG_HEADER_SIZE) { + return WOLFSPDM_E_FRAMING; + } + +#ifndef WOLFSPDM_TCG + (void)inBuf; + return WOLFSPDM_E_NOT_AVAILABLE; +#else + tag = SPDM_Get16BE(inBuf); + if (tag != WOLFSPDM_TCG_TAG_CLEAR && tag != WOLFSPDM_TCG_TAG_SECURED) { + return WOLFSPDM_E_FRAMING; + } + msgSize = SPDM_Get32BE(inBuf + 2); + if (msgSize < WOLFSPDM_TCG_HEADER_SIZE || msgSize > inSz) { + return WOLFSPDM_E_FRAMING; + } + ctx->ctx.connectionHandle = SPDM_Get32BE(inBuf + 6); + ctx->ctx.fipsIndicator = SPDM_Get16BE(inBuf + 10); + payloadSz = msgSize - WOLFSPDM_TCG_HEADER_SIZE; + + if (*outSz < WOLFSPDM_TCG_HEADER_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + spdmOutSz = *outSz - WOLFSPDM_TCG_HEADER_SIZE; + + if (tag == WOLFSPDM_TCG_TAG_CLEAR) { + rc = RespDispatchClear(ctx, + inBuf + WOLFSPDM_TCG_HEADER_SIZE, payloadSz, + outBuf + WOLFSPDM_TCG_HEADER_SIZE, &spdmOutSz); + } + else { + rc = RespDispatchSecured(ctx, + inBuf + WOLFSPDM_TCG_HEADER_SIZE, payloadSz, + outBuf + WOLFSPDM_TCG_HEADER_SIZE, &spdmOutSz); + } + + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + totalSz = WOLFSPDM_TCG_HEADER_SIZE + spdmOutSz; + wolfSPDM_WriteTcgHeader(outBuf, tag, totalSz, + ctx->ctx.connectionHandle, ctx->ctx.fipsIndicator); + *outSz = totalSz; + return WOLFSPDM_SUCCESS; +#endif /* WOLFSPDM_TCG */ +} + +#endif /* WOLFSPDM_RESPONDER */ diff --git a/src/spdm_secured.c b/src/spdm_secured.c index 28774bc..04614b3 100644 --- a/src/spdm_secured.c +++ b/src/spdm_secured.c @@ -1,6 +1,6 @@ /* spdm_secured.c * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -19,6 +19,10 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + #include "spdm_internal.h" /* @@ -26,119 +30,149 @@ * * MCTP transport: * Header/AAD: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) = 8 bytes - * IV XOR: Leftmost 2 bytes (bytes 0-1) with 2-byte LE sequence number + * IV XOR: Leftmost 2 bytes (bytes 0-1) with 2-byte LE sequence number (DSP0277) + * + * Nuvoton TCG binding (Rev 1.11): + * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes + * IV XOR: Leftmost 8 bytes (bytes 0-7) with 8-byte LE sequence number (DSP0277 1.2) + * Plaintext: AppDataLength(2 LE) + SPDM msg + RandomData (pad to 16) * * Full message: Header || Ciphertext || Tag (16) */ - int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, const byte* plain, word32 plainSz, byte* enc, word32* encSz) { Aes aes; byte iv[WOLFSPDM_AEAD_IV_SIZE]; - byte aad[8]; + byte aad[16]; /* Up to 14 bytes for TCG format */ byte plainBuf[WOLFSPDM_MAX_MSG_SIZE + 16]; byte tag[WOLFSPDM_AEAD_TAG_SIZE]; - word16 appDataLen; - word16 encDataLen; word32 plainBufSz; word16 recordLen; word32 hdrSz; word32 aadSz; + int aesInit = 0; int rc; if (ctx == NULL || plain == NULL || enc == NULL || encSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } - - /* Defense-in-depth: the public wolfSPDM_EncryptMessage wrapper exposes - * this function with caller-supplied plainSz. plainBuf holds - * AppDataLen(2) + MCTPheader(1) + plaintext, so bound plainSz against - * the buffer size minus those 3 prefix bytes. */ - if (plainSz > sizeof(plainBuf) - 3) { + if (plainSz > WOLFSPDM_MAX_MSG_SIZE) { return WOLFSPDM_E_BUFFER_SMALL; } - /* DSP0277 Sec. 11.3: the sequence number shall not wrap. The wire field is - * 16-bit and wolfSPDM_BuildIV mixes only the low 16 bits into the AES-GCM - * IV, so a wrap would reuse an IV under the same key. Refuse to encrypt - * once the counter reaches 0x10000 - caller must wolfSPDM_KeyUpdate. */ - if (ctx->reqSeqNum > 0xFFFF) { - return WOLFSPDM_E_SEQUENCE; - } - - /* MCTP format (per DSP0277): - * Plaintext: AppDataLen(2 LE) + MCTP header(0x05) + SPDM message - * Header: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) = 8 bytes - * AAD = Header - */ - appDataLen = (word16)(1 + plainSz); - encDataLen = (word16)(2 + appDataLen); - - plainBufSz = encDataLen; - recordLen = (word16)(encDataLen + WOLFSPDM_AEAD_TAG_SIZE); - hdrSz = 8; /* 4 + 2 + 2 */ - - if (*encSz < hdrSz + recordLen) { - return WOLFSPDM_E_BUFFER_SMALL; +#ifdef WOLFSPDM_TCG + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + /* Nuvoton TCG binding format per Rev 1.11 spec page 25: + * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes + * IV XOR: Leftmost 8 bytes (bytes 0-7) with 8-byte LE sequence number + */ + word16 appDataLen = (word16)plainSz; + + word16 unpadded = (word16)(2 + appDataLen); + word16 padLen = (word16)((16 - (unpadded % 16)) % 16); + word16 encPayloadSz = (word16)(unpadded + padLen); + + plainBufSz = encPayloadSz; + /* Length field = ciphertext + MAC + * (per Nuvoton spec page 25: Length=160=144+16) */ + recordLen = (word16)(encPayloadSz + WOLFSPDM_AEAD_TAG_SIZE); + hdrSz = 14; /* 4 + 8 + 2 (TCG binding format) */ + + if (*encSz < hdrSz + plainBufSz + WOLFSPDM_AEAD_TAG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Build plaintext: AppDataLength(2 LE) || SPDM message || RandomData */ + SPDM_Set16LE(plainBuf, appDataLen); + XMEMCPY(&plainBuf[2], plain, plainSz); + /* Fill RandomData with actual random bytes per Nuvoton spec */ + if (padLen > 0) { + rc = wolfSPDM_GetRandom(ctx, &plainBuf[unpadded], padLen); + if (rc != WOLFSPDM_SUCCESS) { + wc_ForceZero(plainBuf, sizeof(plainBuf)); + return rc; + } + } + + /* Build header/AAD: SessionID(4 LE) + SeqNum(8 LE) + + * Length(2 LE) = 14 bytes */ + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set64LE(&enc[4], ctx->reqSeqNum); + SPDM_Set16LE(&enc[12], recordLen); + + aadSz = 14; + XMEMCPY(aad, enc, aadSz); + } else +#endif + { + /* MCTP format (per DSP0277): + * Plaintext: AppDataLen(2 LE) + MCTP header(0x05) + SPDM message + * Header: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) = 8 bytes + * AAD = Header + */ + word16 appDataLen = (word16)(1 + plainSz); + word16 encDataLen = (word16)(2 + appDataLen); + + /* MCTP carries a 16-bit sequence number; fail rather than let the wire + * value and the 64-bit IV counter diverge past 0xFFFF */ + if (ctx->reqSeqNum > 0xFFFF) { + return WOLFSPDM_E_BAD_STATE; + } + + plainBufSz = encDataLen; + recordLen = (word16)(encDataLen + WOLFSPDM_AEAD_TAG_SIZE); + hdrSz = 8; /* 4 + 2 + 2 */ + + if (*encSz < hdrSz + recordLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Build plaintext: AppDataLen(2 LE) || MCTP header(0x05) || SPDM msg */ + SPDM_Set16LE(plainBuf, appDataLen); + plainBuf[2] = MCTP_MESSAGE_TYPE_SPDM; + XMEMCPY(&plainBuf[3], plain, plainSz); + + /* Build header/AAD: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) */ + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set16LE(&enc[4], (word16)ctx->reqSeqNum); + SPDM_Set16LE(&enc[6], recordLen); + + aadSz = 8; + XMEMCPY(aad, enc, aadSz); } - /* Build plaintext: AppDataLen(2 LE) || MCTP header(0x05) || SPDM msg */ - SPDM_Set16LE(plainBuf, appDataLen); - plainBuf[2] = MCTP_MESSAGE_TYPE_SPDM; - XMEMCPY(&plainBuf[3], plain, plainSz); - - /* Build header/AAD: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) */ - SPDM_Set32LE(&enc[0], ctx->sessionId); - SPDM_Set16LE(&enc[4], (word16)ctx->reqSeqNum); - SPDM_Set16LE(&enc[6], recordLen); - - aadSz = 8; - XMEMCPY(aad, enc, aadSz); - /* Build IV: BaseIV XOR sequence number (DSP0277) */ wolfSPDM_BuildIV(iv, ctx->reqDataIv, ctx->reqSeqNum); + /* AES-GCM encrypt — cascade with single cleanup */ rc = wc_AesInit(&aes, NULL, INVALID_DEVID); - if (rc != 0) { - /* wc_AesInit failed: do NOT touch aes (don't call wc_AesFree on - * an uninitialized object). Caller-side cleanup below is guarded - * by aesInit. */ - wc_ForceZero(plainBuf, sizeof(plainBuf)); - return WOLFSPDM_E_CRYPTO_FAIL; + if (rc == 0) { + aesInit = 1; + rc = wc_AesGcmSetKey(&aes, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); } - rc = wc_AesGcmSetKey(&aes, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); - if (rc != 0) { - rc = WOLFSPDM_E_CRYPTO_FAIL; - goto exit; + if (rc == 0) { + rc = wc_AesGcmEncrypt(&aes, &enc[hdrSz], plainBuf, plainBufSz, + iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); } - - /* Encrypt directly into output buffer (enc + hdrSz) to avoid a copy */ - rc = wc_AesGcmEncrypt(&aes, &enc[hdrSz], plainBuf, plainBufSz, - iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); - if (rc != 0) { - rc = WOLFSPDM_E_CRYPTO_FAIL; - goto exit; + if (aesInit) { + wc_AesFree(&aes); } - XMEMCPY(&enc[hdrSz + plainBufSz], tag, WOLFSPDM_AEAD_TAG_SIZE); - *encSz = hdrSz + plainBufSz + WOLFSPDM_AEAD_TAG_SIZE; - - ctx->reqSeqNum++; - - wolfSPDM_DebugPrint(ctx, "Encrypted %u bytes -> %u bytes (seq=%llu)\n", - plainSz, *encSz, (unsigned long long)(ctx->reqSeqNum - 1)); + if (rc == 0) { + XMEMCPY(&enc[hdrSz + plainBufSz], tag, WOLFSPDM_AEAD_TAG_SIZE); + *encSz = hdrSz + plainBufSz + WOLFSPDM_AEAD_TAG_SIZE; + ctx->reqSeqNum++; + wolfSPDM_DebugPrint(ctx, "Encrypted %u bytes -> %u bytes (seq=%llu)\n", + plainSz, *encSz, (unsigned long long)(ctx->reqSeqNum - 1)); + } - rc = WOLFSPDM_SUCCESS; -exit: - /* aes was initialized (we jumped here past the init check); safe to free. */ - wc_AesFree(&aes); - /* Wipe the plaintext buffer so the outgoing payload doesn't linger - * on the stack frame after this call returns. */ wc_ForceZero(plainBuf, sizeof(plainBuf)); - return rc; + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, @@ -147,333 +181,196 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, { Aes aes; byte iv[WOLFSPDM_AEAD_IV_SIZE]; - byte aad[8]; + byte aad[16]; byte decrypted[WOLFSPDM_MAX_MSG_SIZE + 16]; const byte* ciphertext; const byte* tag; - word32 rspSessionId; - word16 rspSeqNum; - word16 rspLen; - word16 cipherLen; + word32 cipherLen; word16 appDataLen; - word32 hdrSz = 8; - word32 aadSz = 8; + word32 hdrSz; + word32 aadSz; + int aesInit = 0; + int ret; int rc; if (ctx == NULL || enc == NULL || plain == NULL || plainSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } - /* DSP0277 Sec. 11.3: refuse to decrypt past wire-counter exhaustion (matches - * the encrypt-side cap; prevents AES-GCM IV reuse). Caller must - * wolfSPDM_KeyUpdate before the responder's seqNum reaches 0x10000. */ - if (ctx->rspSeqNum > 0xFFFF) { - return WOLFSPDM_E_SEQUENCE; - } - - /* MCTP format */ - if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - /* Parse header: SessionID(4) + SeqNum(2) + Length(2) */ - rspSessionId = SPDM_Get32LE(&enc[0]); - rspSeqNum = SPDM_Get16LE(&enc[4]); - rspLen = SPDM_Get16LE(&enc[6]); - - if (rspSessionId != ctx->sessionId) { - wolfSPDM_DebugPrint(ctx, "Session ID mismatch: 0x%08x != 0x%08x\n", - rspSessionId, ctx->sessionId); - return WOLFSPDM_E_SESSION_INVALID; - } - - /* Validate sequence number matches expected (DSP0277 replay protection). - * The wire field is 16-bit. Casting the 64-bit counter to word16 already - * truncates, matching what the encrypt side wrote. */ - if (rspSeqNum != (word16)ctx->rspSeqNum) { - wolfSPDM_DebugPrint(ctx, "Sequence number mismatch: %u != %llu\n", - (unsigned)rspSeqNum, (unsigned long long)ctx->rspSeqNum); - return WOLFSPDM_E_SEQUENCE; - } - - if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < (word32)(hdrSz + rspLen)) { - return WOLFSPDM_E_BUFFER_SMALL; - } - /* DSP0277: the Length field SHALL equal the exact length of the - * encrypted payload. Reject over-received records with extra - * unauthenticated trailing bytes. */ - if (encSz != (word32)(hdrSz + rspLen)) { - wolfSPDM_DebugPrint(ctx, - "Secured msg: encSz %u != hdrSz+rspLen %u\n", - encSz, (unsigned)(hdrSz + rspLen)); - return WOLFSPDM_E_BUFFER_SMALL; - } - - cipherLen = (word16)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); - /* Defense-in-depth: the decrypted[] stack buffer is the upper bound on - * what wc_AesGcmDecrypt may write. Reject anything larger before the - * AEAD call so a wire-supplied rspLen cannot overflow the buffer. */ - if (cipherLen > sizeof(decrypted)) { - return WOLFSPDM_E_BUFFER_SMALL; - } - ciphertext = enc + hdrSz; - tag = enc + hdrSz + cipherLen; - - XMEMCPY(aad, enc, aadSz); - - /* Build IV: BaseIV XOR sequence number (DSP0277) */ - wolfSPDM_BuildIV(iv, ctx->rspDataIv, (word64)rspSeqNum); - + /* ----- Transport-specific header parsing ----- */ + +#ifdef WOLFSPDM_TCG + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + word64 rspSeqNum64; + word32 rspSessionId; + word16 rspLen; + hdrSz = 14; + aadSz = 14; + + if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) + return WOLFSPDM_E_BUFFER_SMALL; + + rspSessionId = SPDM_Get32LE(&enc[0]); + rspSeqNum64 = SPDM_Get64LE(&enc[4]); + rspLen = SPDM_Get16LE(&enc[12]); + + if (rspSessionId != ctx->sessionId) { + wolfSPDM_DebugPrint(ctx, "Session ID mismatch: 0x%08x != 0x%08x\n", + rspSessionId, ctx->sessionId); + return WOLFSPDM_E_SESSION_INVALID; + } + if (rspSeqNum64 != ctx->rspSeqNum) { + wolfSPDM_DebugPrint(ctx, "Seq mismatch: %llu != %llu\n", + (unsigned long long)rspSeqNum64, + (unsigned long long)ctx->rspSeqNum); + return WOLFSPDM_E_SEQUENCE; + } + if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < hdrSz + rspLen) + return WOLFSPDM_E_BUFFER_SMALL; + + cipherLen = (word32)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + if (cipherLen > sizeof(decrypted)) + return WOLFSPDM_E_BUFFER_SMALL; + + ciphertext = enc + hdrSz; + tag = enc + hdrSz + cipherLen; + XMEMCPY(aad, enc, aadSz); + wolfSPDM_BuildIV(iv, ctx->rspDataIv, rspSeqNum64); + } else +#endif + { + word32 rspSessionId; + word16 rspSeqNum, rspLen; + hdrSz = 8; + aadSz = 8; + + if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) + return WOLFSPDM_E_BUFFER_SMALL; + + rspSessionId = SPDM_Get32LE(&enc[0]); + rspSeqNum = SPDM_Get16LE(&enc[4]); + rspLen = SPDM_Get16LE(&enc[6]); + + if (rspSessionId != ctx->sessionId) { + wolfSPDM_DebugPrint(ctx, "Session ID mismatch: 0x%08x != 0x%08x\n", + rspSessionId, ctx->sessionId); + return WOLFSPDM_E_SESSION_INVALID; + } + if ((word64)rspSeqNum != ctx->rspSeqNum) { + wolfSPDM_DebugPrint(ctx, "Seq mismatch: %u != %llu\n", + rspSeqNum, (unsigned long long)ctx->rspSeqNum); + return WOLFSPDM_E_SEQUENCE; + } + if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < (word32)(hdrSz + rspLen)) + return WOLFSPDM_E_BUFFER_SMALL; + + cipherLen = (word32)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + if (cipherLen > sizeof(decrypted)) + return WOLFSPDM_E_BUFFER_SMALL; + + ciphertext = enc + hdrSz; + tag = enc + hdrSz + cipherLen; + XMEMCPY(aad, enc, aadSz); + wolfSPDM_BuildIV(iv, ctx->rspDataIv, (word64)rspSeqNum); + } + + /* ----- AES-GCM decrypt (shared for both transports) ----- */ + + ret = WOLFSPDM_E_CRYPTO_FAIL; rc = wc_AesInit(&aes, NULL, INVALID_DEVID); - if (rc != 0) { - /* wc_AesInit failed: aes is not safe to wc_AesFree. Wipe stack - * decrypted buffer and bail without touching aes. */ - wc_ForceZero(decrypted, sizeof(decrypted)); - return WOLFSPDM_E_CRYPTO_FAIL; - } - rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); - if (rc != 0) { - rc = WOLFSPDM_E_CRYPTO_FAIL; - goto exit; - } - - rc = wc_AesGcmDecrypt(&aes, decrypted, ciphertext, cipherLen, - iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "AES-GCM decrypt failed: %d\n", rc); - rc = WOLFSPDM_E_DECRYPT_FAIL; - goto exit; - } - - /* Need at least AppDataLen(2) + MCTP(1) bytes in the decrypted output. */ - if (cipherLen < 3) { - rc = WOLFSPDM_E_BUFFER_SMALL; - goto exit; - } - - /* Parse decrypted: AppDataLen (2) || MCTP (1) || SPDM msg */ - appDataLen = SPDM_Get16LE(decrypted); - - if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen)) { - rc = WOLFSPDM_E_BUFFER_SMALL; - goto exit; - } - - /* Validate the inner MCTP type byte matches what the encrypt side - * writes - catches responder-side framing bugs early. */ - if (decrypted[2] != MCTP_MESSAGE_TYPE_SPDM) { - wolfSPDM_DebugPrint(ctx, "Inner MCTP type mismatch: 0x%02x\n", - decrypted[2]); - rc = WOLFSPDM_E_DECRYPT_FAIL; - goto exit; - } - - /* Skip MCTP header, copy SPDM message */ - if (*plainSz < (word32)(appDataLen - 1)) { - rc = WOLFSPDM_E_BUFFER_SMALL; - goto exit; - } - - XMEMCPY(plain, &decrypted[3], appDataLen - 1); - *plainSz = appDataLen - 1; - - ctx->rspSeqNum++; - - wolfSPDM_DebugPrint(ctx, "Decrypted %u bytes -> %u bytes (seq=%u)\n", - encSz, *plainSz, rspSeqNum); - - rc = WOLFSPDM_SUCCESS; -exit: - /* aes was initialized (we jumped here past the init check); safe to free. */ - wc_AesFree(&aes); - /* Wipe the decrypted plaintext so secured-channel payloads don't - * linger on the stack frame after this call returns. */ - wc_ForceZero(decrypted, sizeof(decrypted)); - return rc; -} - -/* Public wrappers must only operate after FINISH has installed the - * application-phase AEAD keys. Allow STATE_FINISH (DeriveAppDataKeys has - * just run) through STATE_MEASURED, but reject STATE_KEY_EX (handshake - * keys still in place) and below. */ -static int wolfSPDM_AppPhaseStateOk(const WOLFSPDM_CTX* ctx) -{ - if (ctx == NULL) { - return 0; - } - if (ctx->state == WOLFSPDM_STATE_FINISH || - ctx->state == WOLFSPDM_STATE_CONNECTED) { - return 1; - } -#ifndef NO_WOLFSPDM_MEAS - if (ctx->state == WOLFSPDM_STATE_MEASURED) { - return 1; - } + if (rc == 0) { + aesInit = 1; + rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + } + if (rc == 0) { + rc = wc_AesGcmDecrypt(&aes, decrypted, ciphertext, cipherLen, + iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, + aad, aadSz); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "AES-GCM decrypt failed: %d\n", rc); + ret = WOLFSPDM_E_DECRYPT_FAIL; + } + else { + /* Record is authenticated (tag verified) so the peer has advanced; + * advance now. A forged record fails the tag and never reaches + * here, and a later payload parse error stays fatal without + * desyncing the sequence. */ + ctx->rspSeqNum++; + } + } + if (aesInit) { + wc_AesFree(&aes); + } + + /* ----- Parse decrypted payload ----- */ + + if (rc == 0 && cipherLen < 2) { + /* authenticated record too short to hold the application length */ + ret = WOLFSPDM_E_BUFFER_SMALL; + } + else if (rc == 0) { + appDataLen = SPDM_Get16LE(decrypted); +#ifdef WOLFSPDM_TCG + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + /* TCG binding: AppDataLen(2) || SPDM msg || RandomData */ + if (cipherLen < (word32)(2 + appDataLen) || + *plainSz < appDataLen) { + ret = WOLFSPDM_E_BUFFER_SMALL; + } else { + XMEMCPY(plain, &decrypted[2], appDataLen); + *plainSz = appDataLen; + ret = WOLFSPDM_SUCCESS; + } + } else #endif - return 0; -} - -#ifndef WOLFSPDM_LEAN -int wolfSPDM_EncryptMessage(WOLFSPDM_CTX* ctx, - const byte* plain, word32 plainSz, - byte* enc, word32* encSz) -{ - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; + { + /* MCTP: AppDataLen(2) || MCTP(1) || SPDM msg */ + if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen) || + *plainSz < (word32)(appDataLen - 1)) { + ret = WOLFSPDM_E_BUFFER_SMALL; + } else { + XMEMCPY(plain, &decrypted[3], appDataLen - 1); + *plainSz = appDataLen - 1; + ret = WOLFSPDM_SUCCESS; + } + } } - if (!wolfSPDM_AppPhaseStateOk(ctx)) { - return WOLFSPDM_E_NOT_CONNECTED; + if (ret == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "Decrypted %u bytes -> %u bytes\n", + encSz, *plainSz); } - return wolfSPDM_EncryptInternal(ctx, plain, plainSz, enc, encSz); -} - -int wolfSPDM_DecryptMessage(WOLFSPDM_CTX* ctx, - const byte* enc, word32 encSz, - byte* plain, word32* plainSz) -{ - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (!wolfSPDM_AppPhaseStateOk(ctx)) { - return WOLFSPDM_E_NOT_CONNECTED; - } - - return wolfSPDM_DecryptInternal(ctx, enc, encSz, plain, plainSz); + wc_ForceZero(decrypted, sizeof(decrypted)); + return ret; } -#endif /* !WOLFSPDM_LEAN */ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz) { - byte encBuf[WOLFSPDM_MAX_MSG_SIZE + 48]; - byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + 48]; + byte encBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; word32 encSz = sizeof(encBuf); word32 rxSz = sizeof(rxBuf); -#if defined(WOLFSPDM_HAVE_CHUNK) && !defined(WOLFSPDM_CHUNK_NO_SECURED) - word32 cap = (rspSz != NULL) ? *rspSz : 0; - byte handle = 0; -#endif int rc; if (ctx == NULL || cmdPlain == NULL || rspPlain == NULL || rspSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } - /* Match the EncryptMessage/DecryptMessage state guard so callers can't - * encrypt application data with handshake or zeroed keys. */ - if (!wolfSPDM_AppPhaseStateOk(ctx)) { - return WOLFSPDM_E_NOT_CONNECTED; - } - rc = wolfSPDM_EncryptInternal(ctx, cmdPlain, cmdSz, encBuf, &encSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); } - - /* Raw transport here: the cleartext chunk hook in wolfSPDM_SendReceive must - * not run on the encrypted record. Chunking of the decrypted plaintext is - * handled below. */ - rc = wolfSPDM_SendReceiveRaw(ctx, encBuf, encSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); } - rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); - -#if defined(WOLFSPDM_HAVE_CHUNK) && !defined(WOLFSPDM_CHUNK_NO_SECURED) - /* Reassemble a secured response the responder chunked (only when CHUNK_CAP - * was negotiated). */ - if (rc == WOLFSPDM_SUCCESS && - (ctx->rspCaps & SPDM_CAP_CHUNK_CAP) != 0 && - wolfSPDM_IsLargeResponse(rspPlain, *rspSz, &handle)) { - rc = wolfSPDM_ReassembleLargeResponse(ctx, 1, handle, rspPlain, cap, - rspSz); - } -#endif return rc; } -/* --- Application Data Transfer --- */ - -#ifndef WOLFSPDM_LEAN -int wolfSPDM_SendData(WOLFSPDM_CTX* ctx, const byte* data, word32 dataSz) -{ - byte encBuf[WOLFSPDM_MAX_MSG_SIZE + 48]; - byte rxBuf[16]; - word32 encSz = sizeof(encBuf); - word32 rxSz; - int rc; - - if (ctx == NULL || data == NULL || dataSz == 0) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (ctx->state != WOLFSPDM_STATE_CONNECTED -#ifndef NO_WOLFSPDM_MEAS - && ctx->state != WOLFSPDM_STATE_MEASURED -#endif - ) { - return WOLFSPDM_E_NOT_CONNECTED; - } - - /* Max payload: leave room for AEAD overhead */ - if (dataSz > WOLFSPDM_MAX_MSG_SIZE - 64) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - /* Encrypt the application data */ - rc = wolfSPDM_EncryptInternal(ctx, data, dataSz, encBuf, &encSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Send via I/O callback (no response expected for send-only) */ - if (ctx->ioCb == NULL) { - return WOLFSPDM_E_IO_FAIL; - } - - rxSz = sizeof(rxBuf); - rc = ctx->ioCb(ctx, encBuf, encSz, rxBuf, &rxSz, ctx->ioUserCtx); - if (rc != 0) { - return WOLFSPDM_E_IO_FAIL; - } - - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_ReceiveData(WOLFSPDM_CTX* ctx, byte* data, word32* dataSz) -{ - byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + 48]; - word32 rxSz = sizeof(rxBuf); - int rc; - - if (ctx == NULL || data == NULL || dataSz == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (ctx->state != WOLFSPDM_STATE_CONNECTED -#ifndef NO_WOLFSPDM_MEAS - && ctx->state != WOLFSPDM_STATE_MEASURED -#endif - ) { - return WOLFSPDM_E_NOT_CONNECTED; - } - - if (ctx->ioCb == NULL) { - return WOLFSPDM_E_IO_FAIL; - } - - /* Receive via I/O callback (NULL tx to indicate receive-only) */ - rc = ctx->ioCb(ctx, NULL, 0, rxBuf, &rxSz, ctx->ioUserCtx); - if (rc != 0) { - return WOLFSPDM_E_IO_FAIL; - } - - /* Decrypt the received data */ - return wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, data, dataSz); -} -#endif /* !WOLFSPDM_LEAN */ diff --git a/src/spdm_session.c b/src/spdm_session.c index 0b45424..30e6af1 100644 --- a/src/spdm_session.c +++ b/src/spdm_session.c @@ -1,6 +1,6 @@ /* spdm_session.c * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -19,53 +19,39 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + #include "spdm_internal.h" /* Callback types for build/parse functions */ typedef int (*wolfSPDM_BuildFn)(WOLFSPDM_CTX*, byte*, word32*); typedef int (*wolfSPDM_ParseFn)(WOLFSPDM_CTX*, const byte*, word32); -/* Exchange helper: build -> transcript(tx) -> sendrecv -> transcript(rx) -> parse. - * Snapshot transcriptLen on entry and roll back if anything after the - * first TranscriptAdd fails - otherwise a transient failure would leave a - * partial TX/RX pair committed and corrupt TH1/TH2 on retry. */ +/* Exchange helper: build -> transcript(tx) -> sendrecv -> transcript(rx) -> parse */ static int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, wolfSPDM_BuildFn buildFn, wolfSPDM_ParseFn parseFn, byte* txBuf, word32 txBufSz, byte* rxBuf, word32 rxBufSz) { word32 txSz = txBufSz; word32 rxSz = rxBufSz; - word32 transcriptSnapshot; int rc; - /* Not every adapter (e.g. BuildGetVersion's) validates ctx, so guard - * here to keep wolfSPDM_GetVersion(NULL) and similar paths from - * dereferencing a NULL context. */ - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - rc = buildFn(ctx, txBuf, &txSz); - if (rc != WOLFSPDM_SUCCESS) return rc; - - transcriptSnapshot = ctx->transcriptLen; - - rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); - if (rc != WOLFSPDM_SUCCESS) goto rollback; - - rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) goto rollback; - - rc = wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); - if (rc != WOLFSPDM_SUCCESS) goto rollback; - - rc = parseFn(ctx, rxBuf, rxSz); - if (rc != WOLFSPDM_SUCCESS) goto rollback; - - return WOLFSPDM_SUCCESS; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = parseFn(ctx, rxBuf, rxSz); + } -rollback: - ctx->transcriptLen = transcriptSnapshot; return rc; } @@ -80,719 +66,94 @@ static int wolfSPDM_BuildGetVersionAdapter(WOLFSPDM_CTX* ctx, byte* buf, int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx) { byte txBuf[8]; - byte rxBuf[64]; /* VERSION: 4 hdr + 2 count + up to ~29 entries * 2 */ + byte rxBuf[32]; /* VERSION: 4 hdr + 2 count + up to 8 entries * 2 = 22 */ return wolfSPDM_ExchangeMsg(ctx, wolfSPDM_BuildGetVersionAdapter, wolfSPDM_ParseVersion, txBuf, sizeof(txBuf), rxBuf, sizeof(rxBuf)); } -int wolfSPDM_GetCapabilities(WOLFSPDM_CTX* ctx) -{ - byte txBuf[24]; /* GET_CAPABILITIES: 20 bytes */ - byte rxBuf[40]; /* CAPABILITIES: 20-36 bytes */ - - return wolfSPDM_ExchangeMsg(ctx, wolfSPDM_BuildGetCapabilities, - wolfSPDM_ParseCapabilities, txBuf, sizeof(txBuf), rxBuf, sizeof(rxBuf)); -} - -int wolfSPDM_NegotiateAlgorithms(WOLFSPDM_CTX* ctx) -{ - byte txBuf[52]; /* NEGOTIATE_ALGORITHMS: 48 B, or 52 with the KEM struct */ - byte rxBuf[80]; /* ALGORITHMS: ~56 bytes with struct tables */ - int rc; -#ifndef NO_WOLFSPDM_CHALLENGE - int hashRc; -#endif - - rc = wolfSPDM_ExchangeMsg(ctx, wolfSPDM_BuildNegotiateAlgorithms, - wolfSPDM_ParseAlgorithms, txBuf, sizeof(txBuf), rxBuf, sizeof(rxBuf)); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Save VCA transcript length (GET_VERSION through ALGORITHMS). - * Used by measurement signature verification per DSP0274. */ - ctx->vcaLen = ctx->transcriptLen; - -#ifndef NO_WOLFSPDM_CHALLENGE - /* Initialize M1/M2 running hash for potential CHALLENGE auth. - * Start with VCA (A portion of the M1/M2 transcript per DSP0274). - * Free a stale hash from a prior call so wc_InitSha384 doesn't - * leak whatever wolfCrypt allocated previously. - * Non-fatal: challenge just won't work if this fails. */ - if (ctx->flags.m1m2HashInit) { - wc_Sha384Free(&ctx->m1m2Hash); - ctx->flags.m1m2HashInit = 0; - } - hashRc = wc_InitSha384(&ctx->m1m2Hash); - if (hashRc == 0) { - hashRc = wc_Sha384Update(&ctx->m1m2Hash, ctx->transcript, - ctx->vcaLen); - if (hashRc == 0) { - ctx->flags.m1m2HashInit = 1; - } - else { - wc_Sha384Free(&ctx->m1m2Hash); - } - } -#endif - - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx) +int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) { - byte txBuf[8]; - byte rxBuf[256]; + byte txBuf[WOLFSPDM_KEY_EX_TX_SZ]; + byte rxBuf[WOLFSPDM_KEY_EX_RX_SZ]; word32 txSz = sizeof(txBuf); word32 rxSz = sizeof(rxBuf); int rc; - rc = wolfSPDM_BuildGetDigests(ctx, txBuf, &txSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Note: GET_DIGESTS/DIGESTS are NOT added to main transcript for TH1, - * but ARE needed for CHALLENGE M1/M2 (the "B" portion per DSP0274). */ - rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - -#ifndef NO_WOLFSPDM_CHALLENGE - /* Feed GET_DIGESTS request + DIGESTS response to M1/M2 challenge hash */ - if (ctx->flags.m1m2HashInit) { - wc_Sha384Update(&ctx->m1m2Hash, txBuf, txSz); - wc_Sha384Update(&ctx->m1m2Hash, rxBuf, rxSz); - } -#endif - - return wolfSPDM_ParseDigests(ctx, rxBuf, rxSz); -} - -int wolfSPDM_GetCertificate(WOLFSPDM_CTX* ctx, int slotId) -{ - byte txBuf[16]; - byte rxBuf[1040]; /* 8 hdr + up to 1024 cert data per chunk */ - word32 txSz; - word32 rxSz; - word16 offset = 0; - word16 portionLen; - word16 remainderLen = 1; - word16 chunkLen; - word32 iterations = 0; - /* (WOLFSPDM_MAX_CERT_CHAIN / 1) + slack: every progressing chunk delivers - * at least 1 byte, so the chain itself bounds the loop. The extra slack - * absorbs any responder that returns smaller-than-requested chunks. */ - const word32 maxIterations = WOLFSPDM_MAX_CERT_CHAIN + 16; - int rc; - - /* DSP0274 Sec. 10.3: per-fragment Length must not exceed the responder's - * negotiated DataTransferSize. Our chunk buffer also caps at 1024. */ - chunkLen = 1024; - if (ctx->dataTransferSize != 0 && ctx->dataTransferSize < chunkLen) { - chunkLen = (word16)ctx->dataTransferSize; + rc = wolfSPDM_BuildKeyExchange(ctx, txBuf, &txSz); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); } - - ctx->currentSlotId = (byte)(slotId & 0x0F); - - while (remainderLen > 0) { - if (++iterations > maxIterations) { - wolfSPDM_DebugPrint(ctx, - "GET_CERTIFICATE: iteration cap reached; aborting\n"); - return WOLFSPDM_E_CERT_FAIL; - } - - txSz = sizeof(txBuf); - rc = wolfSPDM_BuildGetCertificate(ctx, txBuf, &txSz, slotId, offset, - chunkLen); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - rxSz = sizeof(rxBuf); + if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - -#ifndef NO_WOLFSPDM_CHALLENGE - /* Feed each GET_CERTIFICATE/CERTIFICATE chunk to M1/M2 challenge hash */ - if (ctx->flags.m1m2HashInit) { - wc_Sha384Update(&ctx->m1m2Hash, txBuf, txSz); - wc_Sha384Update(&ctx->m1m2Hash, rxBuf, rxSz); - } -#endif - - rc = wolfSPDM_ParseCertificate(ctx, rxBuf, rxSz, &portionLen, &remainderLen); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Forward-progress guard: a responder reporting portionLen=0 with - * remainderLen>0 is non-compliant and would spin this loop. Per - * DSP0274 each non-final chunk shall deliver some data. */ - if (portionLen == 0 && remainderLen > 0) { - wolfSPDM_DebugPrint(ctx, - "GET_CERTIFICATE: responder returned portionLen=0 with " - "remainder=%u\n", remainderLen); - return WOLFSPDM_E_CERT_FAIL; - } - - offset += portionLen; - wolfSPDM_DebugPrint(ctx, "Certificate: offset=%u, portion=%u, remainder=%u\n", - offset, portionLen, remainderLen); - } - - /* Compute Ct = Hash(certificate_chain) and add to transcript */ - rc = wolfSPDM_ComputeCertChainHash(ctx); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - rc = wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Auto-extract responder public key from leaf cert. Required by every - * downstream signature check (KEY_EXCHANGE_RSP, MEASUREMENTS, CHALLENGE). - * Fail hard: a chain we couldn't bind to a public key gives us no - * identity assurance, so refusing the session is the safe default. */ - if (!ctx->flags.hasResponderPubKey) { - int keyRc = wolfSPDM_ExtractResponderPubKey(ctx); - if (keyRc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, - "Could not extract responder public key (%d)\n", keyRc); - return WOLFSPDM_E_CERT_PARSE; + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE: SendReceive failed: %d\n", rc); } } - - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) -{ - byte txBuf[WOLFSPDM_KEX_REQ_BUF]; /* KEY_EXCHANGE: ~158 B / ML-KEM ek */ - byte rxBuf[WOLFSPDM_SIG_RSP_BUF]; /* KEY_EXCHANGE_RSP (ECDSA ~302 / ML-DSA) */ - word32 txSz = sizeof(txBuf); - word32 rxSz = sizeof(rxBuf); - int rc; - - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: received %u bytes\n", rxSz); + rc = wolfSPDM_ParseKeyExchangeRsp(ctx, rxBuf, rxSz); } - /* Refuse KEY_EXCHANGE without an extracted responder public key: - * the responder's ECDSA signature would silently skip otherwise, - * leaving only the HMAC ResponderVerifyData (which proves the peer - * derived the same DHE secret but not its long-term identity). */ - if (!ctx->flags.hasResponderPubKey) { - wolfSPDM_DebugPrint(ctx, - "KEY_EXCHANGE refused: GET_CERTIFICATE must run first\n"); - return WOLFSPDM_E_BAD_STATE; - } - - /* DSP0274 Sec. 10.13.5: HANDSHAKE_IN_THE_CLEAR is only entered when - * the requester also opts in (KEY_EXCHANGE Param1 bit set). wolfSPDM - * never opts in, so the encrypted FINISH_RSP path always applies and - * a responder merely advertising HANDSHAKE_IN_THE_CLEAR is fine. The - * separate ResponderVerifyData-in-the-clear parsing path is therefore - * intentionally unimplemented. */ - - rc = wolfSPDM_BuildKeyExchange(ctx, txBuf, &txSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* An ML-KEM encapsulation key makes this request large (up to ~1.6 KB for - * ML-KEM-1024), unlike the ~158-byte ECDHE request. wolfSPDM implements - * CHUNK_GET (response reassembly) but not CHUNK_SEND (request - * fragmentation), so if the request exceeds the responder's advertised - * DataTransferSize, fail fast with a clear error rather than transmit a - * non-conformant oversized request (DSP0274 Sec. 10.27). */ - if (ctx->dataTransferSize != 0 && txSz > ctx->dataTransferSize) { - wolfSPDM_DebugPrint(ctx, - "KEY_EXCHANGE %u B exceeds responder DataTransferSize %u " - "(no CHUNK_SEND)\n", - (unsigned)txSz, (unsigned)ctx->dataTransferSize); - return WOLFSPDM_E_BUFFER_SMALL; - } - - rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE: SendReceive failed: %d\n", rc); - return rc; - } - - wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: received %u bytes\n", rxSz); - - /* ParseKeyExchangeRsp handles transcript updates and key derivation */ - return wolfSPDM_ParseKeyExchangeRsp(ctx, rxBuf, rxSz); + return rc; } int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) { - /* Cap stack pressure for embedded callers: 1.4 FINISH_RSP carries a - * u16 OpaqueLength in theory, but spec-aligned responders keep it - * small. ParseFinishRsp enforces FINISH_RSP_MAX_OPAQUE so we know the - * decrypted size up front. */ - byte finishBuf[64]; /* FINISH: 4 hdr + 2 OpaqueLen (1.4) + 48 HMAC = 54 */ - byte encBuf[256]; /* Encrypted: hdr(14) + padded(160) + tag(16) = 190 max */ - byte rxBuf[768]; /* Encrypted FINISH_RSP: hdr + ciphertext + tag */ - byte decBuf[512]; /* Decrypted: 4 hdr + 2 OpaqueLen + up to ~500B OpaqueData */ + byte finishBuf[WOLFSPDM_FINISH_BUF_SZ]; + byte encBuf[WOLFSPDM_VENDOR_BUF_SZ]; + byte rxBuf[128]; /* Encrypted FINISH_RSP: ~94 bytes max */ + byte decBuf[64]; /* Decrypted FINISH_RSP: 4 hdr + 48 verify = 52 */ word32 finishSz = sizeof(finishBuf); word32 encSz = sizeof(encBuf); word32 rxSz = sizeof(rxBuf); word32 decSz = sizeof(decBuf); int rc; - rc = wolfSPDM_BuildFinish(ctx, finishBuf, &finishSz); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + /* FINISH is only valid after a successful KEY_EXCHANGE; otherwise the + * session keys are unestablished (zero-entropy). */ + if (ctx == NULL || ctx->state < WOLFSPDM_STATE_KEY_EX) { + return WOLFSPDM_E_BAD_STATE; } + rc = wolfSPDM_BuildFinish(ctx, finishBuf, &finishSz); + /* FINISH must be sent encrypted (HANDSHAKE_IN_THE_CLEAR not negotiated) */ - /* FINISH is encrypted; use the raw transport so the cleartext chunk hook - * in wolfSPDM_SendReceive does not inspect the encrypted record. */ - rc = wolfSPDM_EncryptInternal(ctx, finishBuf, finishSz, encBuf, &encSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "FINISH encrypt failed: %d\n", rc); - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_EncryptInternal(ctx, finishBuf, finishSz, encBuf, + &encSz); } - - rc = wolfSPDM_SendReceiveRaw(ctx, encBuf, encSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "FINISH SendReceive failed: %d\n", rc); - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); } - /* Classify the response: an encrypted record's first 4 bytes are the - * session ID we just negotiated. Anything else is an unencrypted SPDM - * message (typically an SPDM_ERROR from the peer). Compare against the - * session id explicitly rather than relying on the version-byte range - * heuristic, which can collide if reqSessionId's low byte falls in - * 0x10-0x1F. */ - if (rxSz >= 4 && SPDM_Get32LE(rxBuf) != ctx->sessionId) { - if (rxBuf[1] == SPDM_ERROR) { - ctx->lastPeerErrorCode = rxBuf[2]; - wolfSPDM_DebugPrint(ctx, "FINISH: peer returned SPDM ERROR 0x%02x\n", - rxBuf[2]); - rc = WOLFSPDM_E_PEER_ERROR; - goto cleanup; + /* Check for unencrypted SPDM error response */ + if (rc == WOLFSPDM_SUCCESS && + rxSz >= 2 && rxBuf[0] >= 0x10 && rxBuf[0] <= 0x1F) { + #ifdef WOLFSPDM_DEBUG + if (rxBuf[1] == 0x7F) { + byte errCode = (rxSz >= 3) ? rxBuf[2] : 0xFF; + wolfSPDM_DebugPrint(ctx, "FINISH: SPDM ERROR 0x%02x\n", errCode); } - wolfSPDM_DebugPrint(ctx, "FINISH: unexpected response code 0x%02x\n", - rxBuf[1]); + #endif rc = WOLFSPDM_E_PEER_ERROR; - goto cleanup; } - rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "FINISH decrypt failed: %d\n", rc); - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); } - - rc = wolfSPDM_ParseFinishRsp(ctx, decBuf, decSz); - if (rc != WOLFSPDM_SUCCESS) { - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseFinishRsp(ctx, decBuf, decSz); } /* Derive application data keys (transition from handshake to app phase) */ - rc = wolfSPDM_DeriveAppDataKeys(ctx); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "App data key derivation failed: %d\n", rc); - goto cleanup; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveAppDataKeys(ctx); } - rc = WOLFSPDM_SUCCESS; -cleanup: - /* finishBuf holds the requester VerifyData MAC; decBuf holds decrypted - * FINISH_RSP including the responder VerifyData MAC. Wipe both so the - * FINISH-stage authentication material does not linger on the stack. */ + /* Always zero sensitive stack buffers */ wc_ForceZero(finishBuf, sizeof(finishBuf)); wc_ForceZero(decBuf, sizeof(decBuf)); return rc; } -/* --- Measurements (Device Attestation) --- */ - -#ifndef NO_WOLFSPDM_MEAS - -int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, byte measOperation, - int requestSignature) -{ - byte txBuf[48]; /* GET_MEASUREMENTS: max 37 bytes (with sig request) */ - byte rxBuf[WOLFSPDM_MAX_MSG_SIZE]; - word32 txSz = sizeof(txBuf); - word32 rxSz = sizeof(rxBuf); - int errCode; - int rc; - - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - /* Refuse to fetch measurements over an unencrypted channel: device - * attestation content is sensitive, and the public API is intended for - * post-session use. Allow STATE_FINISH (intermediate, but session keys - * have been derived), CONNECTED, and MEASURED. */ - if (ctx->state < WOLFSPDM_STATE_FINISH) { - wolfSPDM_DebugPrint(ctx, - "GET_MEASUREMENTS: refusing in state %d (need >= FINISH)\n", - ctx->state); - return WOLFSPDM_E_NOT_CONNECTED; - } - - /* Build GET_MEASUREMENTS request */ - rc = wolfSPDM_BuildGetMeasurements(ctx, txBuf, &txSz, - measOperation, (byte)requestSignature); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - -#ifndef NO_WOLFSPDM_MEAS_VERIFY - /* Save request message for L1 transcript (signature verification) */ - if (txSz <= sizeof(ctx->measReqMsg)) { - XMEMCPY(ctx->measReqMsg, txBuf, txSz); - ctx->measReqMsgSz = txSz; - } -#endif - - /* Send over the secured channel; the state guard above already ensures - * session keys are installed. */ - rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "GET_MEASUREMENTS exchange failed: %d\n", rc); - return rc; - } - - /* Check for SPDM_ERROR before parsing - SPDM error responses are only - * 4 bytes, which would be rejected by ParseMeasurements's minimum-size - * check (8 bytes) as WOLFSPDM_E_INVALID_ARG. Catch it here so the - * caller sees the more accurate PEER_ERROR. Stash the responder's - * error code so callers can retrieve it via wolfSPDM_GetLastPeerError - * (e.g. back off on BUSY, abort on UNSUPPORTED_REQUEST). */ - errCode = 0; - if (wolfSPDM_CheckError(rxBuf, rxSz, &errCode)) { - ctx->lastPeerErrorCode = (byte)errCode; - wolfSPDM_DebugPrint(ctx, - "GET_MEASUREMENTS: responder error 0x%02x\n", errCode); - return WOLFSPDM_E_PEER_ERROR; - } - - /* Parse the response */ - rc = wolfSPDM_ParseMeasurements(ctx, rxBuf, rxSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - -#ifndef NO_WOLFSPDM_MEAS_VERIFY - /* Verify signature if requested and signature was captured */ - if (requestSignature && ctx->measSignatureSize > 0) { - if (!ctx->flags.hasResponderPubKey) { - wolfSPDM_DebugPrint(ctx, - "No responder public key - cannot verify signature\n"); - return WOLFSPDM_E_MEAS_NOT_VERIFIED; - } - - rc = wolfSPDM_VerifyMeasurementSig(ctx, rxBuf, rxSz, - ctx->measReqMsg, ctx->measReqMsgSz); - if (rc != WOLFSPDM_SUCCESS) { - /* Pass through CRYPTO_FAIL vs MEAS_SIG_FAIL distinction. */ - return rc; - } - - ctx->state = WOLFSPDM_STATE_MEASURED; - return WOLFSPDM_SUCCESS; /* Verified! */ - } -#else - (void)requestSignature; -#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ - - /* DSP0274: when the caller did not request a signature, treat the - * retrieval as success. Reserve WOLFSPDM_E_MEAS_NOT_VERIFIED for the - * case where verification was requested but cannot be performed (no - * responder public key, or build compiled without verify support). */ - ctx->state = WOLFSPDM_STATE_MEASURED; - if (requestSignature) { - return WOLFSPDM_E_MEAS_NOT_VERIFIED; - } - return WOLFSPDM_SUCCESS; -} - -#endif /* !NO_WOLFSPDM_MEAS */ - -/* --- Challenge Authentication (Sessionless Attestation) --- */ - -#ifndef NO_WOLFSPDM_CHALLENGE - -int wolfSPDM_Challenge(WOLFSPDM_CTX* ctx, int slotId, byte measHashType) -{ - byte txBuf[48]; /* CHALLENGE: 36 bytes (1.2) or 44 bytes (1.3+) */ - byte rxBuf[WOLFSPDM_SIG_RSP_BUF]; /* CHALLENGE_AUTH (ECDSA ~300 / ML-DSA) */ - word32 txSz = sizeof(txBuf); - word32 rxSz = sizeof(rxBuf); - word32 sigOffset = 0; - int rc; - - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - /* Need cert chain for signature verification */ - if (ctx->state < WOLFSPDM_STATE_CERT) { - return WOLFSPDM_E_BAD_STATE; - } - - if (!ctx->flags.hasResponderPubKey) { - wolfSPDM_DebugPrint(ctx, - "CHALLENGE: No responder public key for verification\n"); - return WOLFSPDM_E_CHALLENGE; - } - - /* If trusted CAs are loaded, anchor the responder's leaf cert against - * the trust store before CHALLENGE issues. Otherwise the caller is - * trusting whatever leaf cert the responder shipped. */ - if (ctx->flags.hasTrustedCAs) { - rc = wolfSPDM_ValidateCertChain(ctx); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, - "CHALLENGE: cert chain validation failed (%d)\n", rc); - return rc; - } - } - - /* Build CHALLENGE request */ - rc = wolfSPDM_BuildChallenge(ctx, txBuf, &txSz, slotId, measHashType); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - wolfSPDM_DebugPrint(ctx, "Sending CHALLENGE (slot=%d, measHash=0x%02x)\n", - slotId, measHashType); - - /* Cleartext exchange (no session needed) */ - rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "CHALLENGE: SendReceive failed: %d\n", rc); - return rc; - } - - /* Parse CHALLENGE_AUTH response */ - rc = wolfSPDM_ParseChallengeAuth(ctx, rxBuf, rxSz, &sigOffset); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Verify signature */ - rc = wolfSPDM_VerifyChallengeAuthSig(ctx, rxBuf, rxSz, - txBuf, txSz, sigOffset); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - wolfSPDM_DebugPrint(ctx, "CHALLENGE authentication PASSED\n"); - return WOLFSPDM_SUCCESS; -} - -#endif /* !NO_WOLFSPDM_CHALLENGE */ - -/* --- Heartbeat (Session Keep-Alive) --- */ - -int wolfSPDM_Heartbeat(WOLFSPDM_CTX* ctx) -{ - byte txBuf[8]; - byte rxBuf[32]; - word32 txSz = sizeof(txBuf); - word32 rxSz = sizeof(rxBuf); - int rc; - - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (ctx->state != WOLFSPDM_STATE_CONNECTED -#ifndef NO_WOLFSPDM_MEAS - && ctx->state != WOLFSPDM_STATE_MEASURED -#endif - ) { - return WOLFSPDM_E_NOT_CONNECTED; - } - - rc = wolfSPDM_BuildHeartbeat(ctx, txBuf, &txSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Must be sent over encrypted channel */ - rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "HEARTBEAT: SecuredExchange failed: %d\n", rc); - return rc; - } - - return wolfSPDM_ParseHeartbeatAck(ctx, rxBuf, rxSz); -} - -/* --- Key Update (Session Key Rotation) --- */ - -int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll) -{ - byte txBuf[8]; - byte rxBuf[32]; - byte encBuf[64]; - byte rawRxBuf[64]; - /* Snapshot the request-side keying material so a failed ACK decrypt - * can roll the session back to the pre-update state instead of - * leaving requester and responder permanently desynchronised. The - * responder side is only mutated when updateAll is set, so the rsp - * snapshot is only relevant in that branch. */ - byte savedReqDataKey[WOLFSPDM_AEAD_KEY_SIZE]; - byte savedReqDataIv[WOLFSPDM_AEAD_IV_SIZE]; - byte savedReqAppSecret[WOLFSPDM_HASH_SIZE]; - byte savedRspDataKey[WOLFSPDM_AEAD_KEY_SIZE]; - byte savedRspDataIv[WOLFSPDM_AEAD_IV_SIZE]; - byte savedRspAppSecret[WOLFSPDM_HASH_SIZE]; - word64 savedReqSeqNum; - word64 savedRspSeqNum; - word32 txSz, rxSz, encSz, rawRxSz; - byte tag, tag2; - byte operation; - int rc; - - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (ctx->state != WOLFSPDM_STATE_CONNECTED -#ifndef NO_WOLFSPDM_MEAS - && ctx->state != WOLFSPDM_STATE_MEASURED -#endif - ) { - return WOLFSPDM_E_NOT_CONNECTED; - } - - operation = updateAll ? SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS - : SPDM_KEY_UPDATE_OP_UPDATE_KEY; - - /* Step 1: Send KEY_UPDATE encrypted with CURRENT req key */ - txSz = sizeof(txBuf); - rc = wolfSPDM_BuildKeyUpdate(ctx, txBuf, &txSz, operation, &tag); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - wolfSPDM_DebugPrint(ctx, "Sending KEY_UPDATE\n"); - - encSz = sizeof(encBuf); - rawRxSz = sizeof(rawRxBuf); - savedReqSeqNum = ctx->reqSeqNum; - savedRspSeqNum = ctx->rspSeqNum; - - XMEMCPY(savedReqDataKey, ctx->reqDataKey, sizeof(savedReqDataKey)); - XMEMCPY(savedReqDataIv, ctx->reqDataIv, sizeof(savedReqDataIv)); - XMEMCPY(savedReqAppSecret, ctx->reqAppSecret, sizeof(savedReqAppSecret)); - XMEMCPY(savedRspDataKey, ctx->rspDataKey, sizeof(savedRspDataKey)); - XMEMCPY(savedRspDataIv, ctx->rspDataIv, sizeof(savedRspDataIv)); - XMEMCPY(savedRspAppSecret, ctx->rspAppSecret, sizeof(savedRspAppSecret)); - - /* Encrypt with current req key */ - rc = wolfSPDM_EncryptInternal(ctx, txBuf, txSz, encBuf, &encSz); - if (rc != WOLFSPDM_SUCCESS) { - goto kupd_cleanup; - } - - /* Send and receive raw (don't decrypt yet) */ - rc = wolfSPDM_SendReceiveRaw(ctx, encBuf, encSz, rawRxBuf, &rawRxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: SendReceive failed: %d\n", rc); - goto kupd_cleanup; - } - - /* Step 2: Derive new keys BEFORE decrypting ACK. - * The responder derives new keys upon receiving KEY_UPDATE and - * encrypts the ACK with the NEW response key. */ - rc = wolfSPDM_DeriveUpdatedKeys(ctx, updateAll); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: DeriveUpdatedKeys failed: %d\n", rc); - goto kupd_cleanup; - } - /* Per DSP0277 Sec 11: reset only the seqNum for directions whose - * keys actually rotated. updateAll=0 (UpdateKey) only rotates the - * requester's send-direction; the responder keeps incrementing its - * old rspSeqNum until UpdateAll happens. */ - ctx->reqSeqNum = 0; - if (updateAll) { - ctx->rspSeqNum = 0; - } - - /* Decrypt ACK with new rsp key. If this fails, roll the session - * back to the pre-update keys / seqNums - otherwise a single failed - * ACK leaves the requester and responder permanently desynchronised - * (DoS). */ - rxSz = sizeof(rxBuf); - rc = wolfSPDM_DecryptInternal(ctx, rawRxBuf, rawRxSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, - "KEY_UPDATE: ACK decrypt failed (%d); rolling keys back\n", rc); - XMEMCPY(ctx->reqDataKey, savedReqDataKey, sizeof(savedReqDataKey)); - XMEMCPY(ctx->reqDataIv, savedReqDataIv, sizeof(savedReqDataIv)); - XMEMCPY(ctx->reqAppSecret, savedReqAppSecret, - sizeof(savedReqAppSecret)); - if (updateAll) { - XMEMCPY(ctx->rspDataKey, savedRspDataKey, sizeof(savedRspDataKey)); - XMEMCPY(ctx->rspDataIv, savedRspDataIv, sizeof(savedRspDataIv)); - XMEMCPY(ctx->rspAppSecret, savedRspAppSecret, - sizeof(savedRspAppSecret)); - } - ctx->reqSeqNum = savedReqSeqNum; - ctx->rspSeqNum = savedRspSeqNum; - } - -kupd_cleanup: - wc_ForceZero(savedReqDataKey, sizeof(savedReqDataKey)); - wc_ForceZero(savedReqDataIv, sizeof(savedReqDataIv)); - wc_ForceZero(savedReqAppSecret, sizeof(savedReqAppSecret)); - wc_ForceZero(savedRspDataKey, sizeof(savedRspDataKey)); - wc_ForceZero(savedRspDataIv, sizeof(savedRspDataIv)); - wc_ForceZero(savedRspAppSecret, sizeof(savedRspAppSecret)); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - rc = wolfSPDM_ParseKeyUpdateAck(ctx, rxBuf, rxSz, operation, tag); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Step 3: Verify new key works (send VERIFY_NEW_KEY with new keys) */ - txSz = sizeof(txBuf); - rc = wolfSPDM_BuildKeyUpdate(ctx, txBuf, &txSz, - SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY, &tag2); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - rxSz = sizeof(rxBuf); - rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: VerifyNewKey exchange failed: %d\n", rc); - return rc; - } - - rc = wolfSPDM_ParseKeyUpdateAck(ctx, rxBuf, rxSz, - SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY, tag2); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE completed, new keys active\n"); - return WOLFSPDM_SUCCESS; -} diff --git a/src/spdm_tcg.c b/src/spdm_tcg.c new file mode 100644 index 0000000..a459485 --- /dev/null +++ b/src/spdm_tcg.c @@ -0,0 +1,726 @@ +/* spdm_tcg.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +/* Shared TCG SPDM code used by both Nuvoton and Nations Technology TPMs. */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_TCG + +#include + +/* ----- Vendor Command Helpers ----- */ + +int wolfSPDM_TCG_VendorCmdClear(WOLFSPDM_CTX* ctx, const char* vdCode, + const byte* payload, word32 payloadSz, WOLFSPDM_VENDOR_RSP* rsp) +{ + byte spdmMsg[WOLFSPDM_VENDOR_BUF_SZ]; + int spdmMsgSz; + byte rxBuf[WOLFSPDM_VENDOR_RX_SZ]; + word32 rxSz; + int rc; + byte ver; + + ver = ctx->spdmVersion ? ctx->spdmVersion : SPDM_VERSION_13; + spdmMsgSz = wolfSPDM_BuildVendorDefined(ver, vdCode, payload, + payloadSz, spdmMsg, sizeof(spdmMsg)); + if (spdmMsgSz < 0) { + return spdmMsgSz; + } + + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SendReceive(ctx, spdmMsg, (word32)spdmMsgSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + if (rxSz >= 4 && rxBuf[1] == SPDM_ERROR) { + wolfSPDM_DebugPrint(ctx, "%s: SPDM ERROR 0x%02x 0x%02x\n", + vdCode, rxBuf[2], rxBuf[3]); + return WOLFSPDM_E_PEER_ERROR; + } + + if (rsp != NULL) { + rsp->payloadSz = sizeof(rsp->payload); + XMEMSET(rsp->vdCode, 0, sizeof(rsp->vdCode)); + rc = wolfSPDM_ParseVendorDefined(rxBuf, rxSz, + rsp->vdCode, rsp->payload, &rsp->payloadSz); + if (rc < 0) { + return rc; + } + /* Validate response VdCode matches the request */ + if (XMEMCMP(rsp->vdCode, vdCode, WOLFSPDM_VDCODE_LEN) != 0) { + wolfSPDM_DebugPrint(ctx, "%s: unexpected VdCode '%.8s'\n", + vdCode, rsp->vdCode); + return WOLFSPDM_E_PEER_ERROR; + } + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_TCG_VendorCmdSecured(WOLFSPDM_CTX* ctx, const char* vdCode, + const byte* payload, word32 payloadSz) +{ + byte spdmMsg[WOLFSPDM_VENDOR_BUF_SZ]; + int spdmMsgSz; + byte decBuf[WOLFSPDM_VENDOR_BUF_SZ]; + word32 decSz = 0; + int rc; + byte ver; + + ver = ctx->spdmVersion ? ctx->spdmVersion : SPDM_VERSION_13; + spdmMsgSz = wolfSPDM_BuildVendorDefined(ver, vdCode, payload, + payloadSz, spdmMsg, sizeof(spdmMsg)); + if (spdmMsgSz < 0) { + rc = spdmMsgSz; + } + else { + decSz = sizeof(decBuf); + rc = wolfSPDM_SecuredExchange(ctx, spdmMsg, (word32)spdmMsgSz, + decBuf, &decSz); + } + + if (rc == WOLFSPDM_SUCCESS && decSz >= 4 && decBuf[1] == SPDM_ERROR) { + wolfSPDM_DebugPrint(ctx, "%s: SPDM ERROR 0x%02x 0x%02x\n", + vdCode, decBuf[2], decBuf[3]); + rc = WOLFSPDM_E_PEER_ERROR; + } + + /* Always zero sensitive stack buffers */ + wc_ForceZero(spdmMsg, sizeof(spdmMsg)); + wc_ForceZero(decBuf, sizeof(decBuf)); + + return rc; +} + +/* ----- TCG SPDM Binding Message Framing ----- */ + +int wolfSPDM_BuildTcgClearMessage( + WOLFSPDM_CTX* ctx, + const byte* spdmPayload, word32 spdmPayloadSz, + byte* outBuf, word32 outBufSz) +{ + word32 totalSz; + + if (ctx == NULL || spdmPayload == NULL || outBuf == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + totalSz = WOLFSPDM_TCG_HEADER_SIZE + spdmPayloadSz; + + if (outBufSz < totalSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + wolfSPDM_WriteTcgHeader(outBuf, WOLFSPDM_TCG_TAG_CLEAR, totalSz, + ctx->connectionHandle, ctx->fipsIndicator); + XMEMCPY(outBuf + WOLFSPDM_TCG_HEADER_SIZE, spdmPayload, spdmPayloadSz); + + return (int)totalSz; +} + +int wolfSPDM_ParseTcgClearMessage( + const byte* inBuf, word32 inBufSz, + byte* spdmPayload, word32* spdmPayloadSz, + WOLFSPDM_TCG_CLEAR_HDR* hdr) +{ + word16 tag; + word32 msgSize; + word32 payloadSz; + + if (inBuf == NULL || spdmPayload == NULL || spdmPayloadSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (inBufSz < WOLFSPDM_TCG_HEADER_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + tag = SPDM_Get16BE(inBuf); + if (tag != WOLFSPDM_TCG_TAG_CLEAR) { + return WOLFSPDM_E_PEER_ERROR; + } + + msgSize = SPDM_Get32BE(inBuf + 2); + if (msgSize < WOLFSPDM_TCG_HEADER_SIZE || msgSize > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + payloadSz = msgSize - WOLFSPDM_TCG_HEADER_SIZE; + if (*spdmPayloadSz < payloadSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + if (hdr != NULL) { + hdr->tag = tag; + hdr->size = msgSize; + hdr->connectionHandle = SPDM_Get32BE(inBuf + 6); + hdr->fipsIndicator = SPDM_Get16BE(inBuf + 10); + hdr->reserved = SPDM_Get32BE(inBuf + 12); + } + + XMEMCPY(spdmPayload, inBuf + WOLFSPDM_TCG_HEADER_SIZE, payloadSz); + *spdmPayloadSz = payloadSz; + + return (int)payloadSz; +} + +/* ----- SPDM Vendor Defined Message Helpers ----- */ + +int wolfSPDM_BuildVendorDefined( + byte spdmVersion, + const char* vdCode, + const byte* payload, word32 payloadSz, + byte* outBuf, word32 outBufSz) +{ + word32 totalSz; + word32 offset = 0; + + if (vdCode == NULL || outBuf == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (payload == NULL && payloadSz != 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Reject a payload that would overflow the 16-bit request-length field */ + if (payloadSz > (word32)(0xFFFF - WOLFSPDM_VDCODE_LEN)) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* SPDM VENDOR_DEFINED_REQUEST format: + * SPDMVersion(1) + reqRspCode(1) + param1(1) + param2(1) + + * standardId(2/LE) + vendorIdLen(1) + reqLength(2/LE) + + * vdCode(8) + payload */ + totalSz = 1 + 1 + 1 + 1 + 2 + 1 + 2 + WOLFSPDM_VDCODE_LEN + payloadSz; + + if (outBufSz < totalSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + outBuf[offset++] = spdmVersion; + outBuf[offset++] = SPDM_VENDOR_DEFINED_REQUEST; + outBuf[offset++] = 0x00; + outBuf[offset++] = 0x00; + /* Standard ID (0x0001 = TCG, little-endian) */ + SPDM_Set16LE(outBuf + offset, 0x0001); + offset += 2; + /* Vendor ID Length (0 for TCG) */ + outBuf[offset++] = 0x00; + /* Request Length (vdCode + payload, little-endian) */ + SPDM_Set16LE(outBuf + offset, (word16)(WOLFSPDM_VDCODE_LEN + payloadSz)); + offset += 2; + /* VdCode (8-byte ASCII) */ + XMEMCPY(outBuf + offset, vdCode, WOLFSPDM_VDCODE_LEN); + offset += WOLFSPDM_VDCODE_LEN; + /* Payload */ + if (payload != NULL && payloadSz > 0) { + XMEMCPY(outBuf + offset, payload, payloadSz); + offset += payloadSz; + } + + return (int)offset; +} + +int wolfSPDM_ParseVendorDefined( + const byte* inBuf, word32 inBufSz, + char* vdCode, + byte* payload, word32* payloadSz) +{ + word32 offset = 0; + word16 reqLength; + word32 dataLen; + byte vendorIdLen; + + if (inBuf == NULL || vdCode == NULL || payload == NULL || + payloadSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Minimum: version(1) + code(1) + param1(1) + param2(1) + stdId(2/LE) + + * vidLen(1) + reqLen(2/LE) + vdCode(8) = 17 */ + if (inBufSz < 17) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + offset += 1; /* SPDM version */ + offset += 3; /* request/response code + params */ + offset += 2; /* standard ID */ + vendorIdLen = inBuf[offset]; + offset += 1 + vendorIdLen; + + if (offset + 2 > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + reqLength = SPDM_Get16LE(inBuf + offset); + offset += 2; + + if (reqLength < WOLFSPDM_VDCODE_LEN) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + if (offset + reqLength > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(vdCode, inBuf + offset, WOLFSPDM_VDCODE_LEN); + vdCode[WOLFSPDM_VDCODE_LEN] = '\0'; + offset += WOLFSPDM_VDCODE_LEN; + + dataLen = reqLength - WOLFSPDM_VDCODE_LEN; + if (*payloadSz < dataLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + if (dataLen > 0) { + XMEMCPY(payload, inBuf + offset, dataLen); + } + *payloadSz = dataLen; + + return (int)dataLen; +} + +/* ----- Shared TCG SPDM Functions ----- */ + +static int wolfSPDM_TCG_CheckPubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz) +{ + const byte* pubKeyX; + const byte* pubKeyY; + int rc; + + if (!ctx->flags.hasRspPubKey) { + return WOLFSPDM_SUCCESS; + } + if (ctx->rspPubKeyLen != WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_BAD_STATE; + } + + rc = wolfSPDM_ExtractEccPoint(pubKey, pubKeySz, &pubKeyX, &pubKeyY); + if (rc != WOLFSPDM_SUCCESS) { + return WOLFSPDM_E_PEER_ERROR; + } + + if (XMEMCMP(pubKeyX, ctx->rspPubKey, WOLFSPDM_ECC_KEY_SIZE) != 0 || + XMEMCMP(pubKeyY, ctx->rspPubKey + WOLFSPDM_ECC_KEY_SIZE, + WOLFSPDM_ECC_KEY_SIZE) != 0) { + wolfSPDM_DebugPrint(ctx, "GET_PUBK: Responder key mismatch\n"); + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_TCG_GetPubKey( + WOLFSPDM_CTX* ctx, + byte* pubKey, word32* pubKeySz) +{ + WOLFSPDM_VENDOR_RSP rsp; + int rc; + + if (ctx == NULL || pubKey == NULL || pubKeySz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + wolfSPDM_DebugPrint(ctx, "TCG: GET_PUBK\n"); + + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_PUBK, + NULL, 0, &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + if (XMEMCMP(rsp.vdCode, WOLFSPDM_VDCODE_GET_PUBK, + WOLFSPDM_VDCODE_LEN) != 0) { + wolfSPDM_DebugPrint(ctx, "GET_PUBK: Unexpected VdCode '%.8s'\n", + rsp.vdCode); + return WOLFSPDM_E_PEER_ERROR; + } + + wolfSPDM_DebugPrint(ctx, "GET_PUBK: Got TPMT_PUBLIC (%u bytes)\n", + rsp.payloadSz); + + rc = wolfSPDM_TCG_CheckPubKey(ctx, rsp.payload, rsp.payloadSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + if (*pubKeySz < rsp.payloadSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(pubKey, rsp.payload, rsp.payloadSz); + *pubKeySz = rsp.payloadSz; + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_TCG_GivePubKey( + WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz) +{ + int rc; + + if (ctx == NULL || pubKey == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state < WOLFSPDM_STATE_KEY_EX) { + return WOLFSPDM_E_BAD_STATE; + } + + wolfSPDM_DebugPrint(ctx, "TCG: GIVE_PUB (%u bytes)\n", pubKeySz); + + rc = wolfSPDM_TCG_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_GIVE_PUB, + pubKey, pubKeySz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GIVE_PUB: SecuredExchange failed %d\n", rc); + return rc; + } + + wolfSPDM_DebugPrint(ctx, "GIVE_PUB: Success\n"); + return WOLFSPDM_SUCCESS; +} + +/* ----- Shared GET_CAPABILITIES + NEGOTIATE_ALGORITHMS ----- */ + +#define WOLFSPDM_TCG_CAPABILITIES_RSP 0x61 +#define WOLFSPDM_TCG_ALGORITHMS_RSP 0x63 +#define WOLFSPDM_TCG_CAPABILITIES_RSP_SZ 20 +#define WOLFSPDM_TCG_ALGORITHMS_RSP_SZ 52 +#define WOLFSPDM_TCG_MIN_DATA_TRANSFER_SZ 42 + +#define WOLFSPDM_TCG_CAP_ENCRYPT 0x00000040UL +#define WOLFSPDM_TCG_CAP_MAC 0x00000080UL +#define WOLFSPDM_TCG_CAP_KEY_EX 0x00000200UL +#define WOLFSPDM_TCG_CAP_PSK 0x00000400UL +#define WOLFSPDM_TCG_CAP_PSK_WITH_CONTEXT 0x00000800UL +#define WOLFSPDM_TCG_CAP_PSK_MASK 0x00000C00UL +#define WOLFSPDM_TCG_CAP_PUB_KEY_ID 0x00010000UL + +static int wolfSPDM_TCG_CheckResponse(WOLFSPDM_CTX* ctx, const byte* rsp, + word32 rspSz, word32 minRspSz, byte expectedCode) +{ + int errorCode; + + if (rspSz < 4) { + return WOLFSPDM_E_BUFFER_SMALL; + } + if (wolfSPDM_CheckError(rsp, rspSz, &errorCode)) { + wolfSPDM_DebugPrint(ctx, "SPDM error: 0x%02x\n", errorCode); + return WOLFSPDM_E_PEER_ERROR; + } + if (rspSz < minRspSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + if (rsp[0] != ctx->spdmVersion) { + return WOLFSPDM_E_VERSION_MISMATCH; + } + if (rsp[1] != expectedCode) { + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_SUCCESS; +} + +static int wolfSPDM_TCG_CheckCapabilities(WOLFSPDM_CTX* ctx, + const byte* rsp, word32 rspSz, word32 capsFlags) +{ + word32 requiredFlags; + word32 rspFlags; + word32 dataTransferSz; + word32 maxSpdmMsgSz; + int rc; + + rc = wolfSPDM_TCG_CheckResponse(ctx, rsp, rspSz, + WOLFSPDM_TCG_CAPABILITIES_RSP_SZ, WOLFSPDM_TCG_CAPABILITIES_RSP); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + if (rspSz != WOLFSPDM_TCG_CAPABILITIES_RSP_SZ) { + return WOLFSPDM_E_PEER_ERROR; + } + + requiredFlags = WOLFSPDM_TCG_CAP_ENCRYPT | WOLFSPDM_TCG_CAP_MAC | + WOLFSPDM_TCG_CAP_PUB_KEY_ID; + if ((capsFlags & WOLFSPDM_TCG_CAP_PSK) == 0) { + requiredFlags |= WOLFSPDM_TCG_CAP_KEY_EX; + } + + rspFlags = SPDM_Get32LE(rsp + 8); + dataTransferSz = SPDM_Get32LE(rsp + 12); + maxSpdmMsgSz = SPDM_Get32LE(rsp + 16); + if ((rspFlags & requiredFlags) != requiredFlags || + ((capsFlags & WOLFSPDM_TCG_CAP_PSK) != 0 && + (rspFlags & WOLFSPDM_TCG_CAP_PSK_MASK) != WOLFSPDM_TCG_CAP_PSK && + (rspFlags & WOLFSPDM_TCG_CAP_PSK_MASK) != + WOLFSPDM_TCG_CAP_PSK_WITH_CONTEXT) || + dataTransferSz < WOLFSPDM_TCG_MIN_DATA_TRANSFER_SZ || + maxSpdmMsgSz < dataTransferSz) { + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_SUCCESS; +} + +static int wolfSPDM_TCG_CheckAlgorithms(WOLFSPDM_CTX* ctx, + const byte* rsp, word32 rspSz) +{ + static const byte expectedAlgStructs[16] = { + 0x02, 0x20, 0x10, 0x00, + 0x03, 0x20, 0x02, 0x00, + 0x04, 0x20, 0x80, 0x00, + 0x05, 0x20, 0x01, 0x00 + }; + int rc; + + rc = wolfSPDM_TCG_CheckResponse(ctx, rsp, rspSz, + WOLFSPDM_TCG_ALGORITHMS_RSP_SZ, WOLFSPDM_TCG_ALGORITHMS_RSP); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + if (rspSz != WOLFSPDM_TCG_ALGORITHMS_RSP_SZ || + SPDM_Get16LE(rsp + 4) != rspSz || rsp[2] != 4 || rsp[3] != 0 || + rsp[6] != 0 || rsp[7] != 0x02 || + SPDM_Get32LE(rsp + 8) != 0 || + SPDM_Get32LE(rsp + 12) != 0x00000080UL || + SPDM_Get32LE(rsp + 16) != 0x00000002UL || + SPDM_Get32LE(rsp + 20) != 0 || SPDM_Get32LE(rsp + 24) != 0 || + SPDM_Get32LE(rsp + 28) != 0 || rsp[32] != 0 || rsp[33] != 0 || + rsp[34] != 0 || rsp[35] != 0 || + XMEMCMP(rsp + 36, expectedAlgStructs, + sizeof(expectedAlgStructs)) != 0) { + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_TCG_GetCapabilities(WOLFSPDM_CTX* ctx, word32 capsFlags) +{ + byte capsReq[20]; + byte capsRsp[64]; + word32 capsRspSz = sizeof(capsRsp); + word32 off = 0; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + capsReq[off++] = ctx->spdmVersion; + capsReq[off++] = 0xE1; /* GET_CAPABILITIES */ + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + /* Reserved(1) + CTExponent(1) + Reserved(2) */ + capsReq[off++] = 0x00; capsReq[off++] = 0x1F; + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + /* Flags (4 bytes LE) */ + SPDM_Set32LE(capsReq + off, capsFlags); + off += 4; + /* DataTransferSize */ + capsReq[off++] = 0xC0; capsReq[off++] = 0x07; + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + /* MaxSPDMmsgSize */ + capsReq[off++] = 0xC0; capsReq[off++] = 0x07; + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + + wolfSPDM_DebugPrint(ctx, "TCG: GET_CAPABILITIES\n"); + rc = wolfSPDM_TranscriptAdd(ctx, capsReq, off); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SendReceive(ctx, capsReq, off, capsRsp, &capsRspSz); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TCG_CheckCapabilities(ctx, capsRsp, capsRspSz, + capsFlags); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, capsRsp, capsRspSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + } + return rc; +} + +int wolfSPDM_TCG_NegotiateAlgorithms(WOLFSPDM_CTX* ctx) +{ + /* Algorithm Set B: P-384/SHA-384/AES-256-GCM */ + byte algReq[48]; + byte algRsp[128]; + word32 algRspSz = sizeof(algRsp); + word32 off = 0; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + algReq[off++] = ctx->spdmVersion; + algReq[off++] = 0xE3; /* NEGOTIATE_ALGORITHMS */ + algReq[off++] = 0x04; /* Param1: NumAlgStructs = 4 */ + algReq[off++] = 0x00; + algReq[off++] = 0x30; algReq[off++] = 0x00; /* Length = 48 */ + algReq[off++] = 0x00; algReq[off++] = 0x02; /* MeasurementSpec + Reserved */ + /* BaseAsymAlgo: ECDSA_ECC_NIST_P384 */ + algReq[off++] = 0x80; algReq[off++] = 0x00; + algReq[off++] = 0x00; algReq[off++] = 0x00; + /* BaseHashAlgo: SHA_384 */ + algReq[off++] = 0x02; algReq[off++] = 0x00; + algReq[off++] = 0x00; algReq[off++] = 0x00; + /* Reserved (16 bytes) */ + XMEMSET(&algReq[off], 0, 16); off += 16; + /* AlgStruct[0]: DHE = SECP_384_R1 */ + algReq[off++] = 0x02; algReq[off++] = 0x20; + algReq[off++] = 0x10; algReq[off++] = 0x00; + /* AlgStruct[1]: AEAD = AES_256_GCM */ + algReq[off++] = 0x03; algReq[off++] = 0x20; + algReq[off++] = 0x02; algReq[off++] = 0x00; + /* AlgStruct[2]: ReqBaseAsymAlg = ECDSA_P384 */ + algReq[off++] = 0x04; algReq[off++] = 0x20; + algReq[off++] = 0x80; algReq[off++] = 0x00; + /* AlgStruct[3]: KeySchedule = SPDM */ + algReq[off++] = 0x05; algReq[off++] = 0x20; + algReq[off++] = 0x01; algReq[off++] = 0x00; + + wolfSPDM_DebugPrint(ctx, "TCG: NEGOTIATE_ALGORITHMS\n"); + rc = wolfSPDM_TranscriptAdd(ctx, algReq, off); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SendReceive(ctx, algReq, off, algRsp, &algRspSz); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TCG_CheckAlgorithms(ctx, algRsp, algRspSz); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, algRsp, algRspSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + } + return rc; +} + +/* ----- TCG SPDM Connection Flow ----- */ + +/* GET_VERSION -> [GET_CAPS -> NEG_ALGO] -> GET_PUBK -> KEY_EXCHANGE -> + * GIVE_PUB -> FINISH */ +int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx) +{ + int rc; + byte pubKey[WOLFSPDM_PUBKEY_BUF_SZ]; + word32 pubKeySz; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + + if (!ctx->flags.hasRspPubKey || + ctx->rspPubKeyLen != WOLFSPDM_ECC_POINT_SIZE) { + wolfSPDM_DebugPrint(ctx, + "TCG: Trusted responder public key is not configured\n"); + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugPrint(ctx, "TCG: Starting SPDM connection\n"); + + ctx->state = WOLFSPDM_STATE_INIT; + wolfSPDM_TranscriptReset(ctx); + + /* Step 1: GET_VERSION */ + SPDM_CONNECT_STEP(ctx, "TCG Step 1: GET_VERSION\n", + wolfSPDM_GetVersion(ctx)); + + /* Steps 2-3: GET_CAPABILITIES + NEGOTIATE_ALGORITHMS. + * TCG SPDM Binding mandates these before GET_PUB_KEY. Nuvoton silicon + * uses a simplified flow that skips them, so gate at runtime by mode. */ + if (ctx->mode != WOLFSPDM_MODE_NUVOTON) { + SPDM_CONNECT_STEP(ctx, "TCG Step 2: GET_CAPABILITIES\n", + wolfSPDM_TCG_GetCapabilities(ctx, WOLFSPDM_TCG_CAPS_FLAGS_DEFAULT)); + SPDM_CONNECT_STEP(ctx, "TCG Step 3: NEGOTIATE_ALGORITHMS\n", + wolfSPDM_TCG_NegotiateAlgorithms(ctx)); + } + + /* Step 4: GET_PUBK */ + wolfSPDM_DebugPrint(ctx, "TCG Step 4: GET_PUBK\n"); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GET_PUBK failed: %d\n", rc); + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + ctx->state = WOLFSPDM_STATE_CERT; + + /* Compute Ct from the fetched wire object after its public point has + * matched the separately configured responder key. */ + wolfSPDM_DebugPrint(ctx, "TCG: Computing Ct = SHA-384(GET_PUBK[%u])\n", + pubKeySz); + rc = wolfSPDM_Sha384Hash(ctx->certChainHash, + pubKey, pubKeySz, NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, + WOLFSPDM_HASH_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + + /* Step 5: KEY_EXCHANGE */ + SPDM_CONNECT_STEP(ctx, "TCG Step 5: KEY_EXCHANGE\n", + wolfSPDM_KeyExchange(ctx)); + + /* Step 6: GIVE_PUB (secured) */ + if (ctx->flags.hasReqKeyPair && ctx->reqPubKeyTPMTLen > 0) { + wolfSPDM_DebugPrint(ctx, "TCG Step 6: GIVE_PUB\n"); + rc = wolfSPDM_TCG_GivePubKey(ctx, ctx->reqPubKeyTPMT, + ctx->reqPubKeyTPMTLen); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GIVE_PUB failed: %d\n", rc); + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } else { + wolfSPDM_DebugPrint(ctx, + "TCG Step 6: GIVE_PUB (skipped, no host key)\n"); + } + + /* Step 7: FINISH */ + SPDM_CONNECT_STEP(ctx, "TCG Step 7: FINISH\n", + wolfSPDM_Finish(ctx)); + + ctx->state = WOLFSPDM_STATE_CONNECTED; + wolfSPDM_DebugPrint(ctx, "TCG: SPDM Session Established! " + "SessionID=0x%08x\n", ctx->sessionId); + + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_TCG */ + diff --git a/src/spdm_transcript.c b/src/spdm_transcript.c index 1aae728..66ea411 100644 --- a/src/spdm_transcript.c +++ b/src/spdm_transcript.c @@ -1,6 +1,6 @@ /* spdm_transcript.c * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -19,9 +19,13 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + #include "spdm_internal.h" -/* --- Transcript Management --- +/* ----- Transcript Management ----- * VCA = GET_VERSION || VERSION || GET_CAPS || CAPS || NEG_ALGO || ALGO * Ct = Hash(certificate_chain) * TH1 = Hash(VCA || Ct || KEY_EXCHANGE || KEY_EXCHANGE_RSP_partial || Signature) @@ -36,11 +40,9 @@ void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx) XMEMSET(ctx->transcript, 0, sizeof(ctx->transcript)); ctx->transcriptLen = 0; - XMEMSET(ctx->certChain, 0, sizeof(ctx->certChain)); - ctx->certChainLen = 0; - XMEMSET(ctx->certChainHash, 0, sizeof(ctx->certChainHash)); XMEMSET(ctx->th1, 0, sizeof(ctx->th1)); + XMEMSET(ctx->th2, 0, sizeof(ctx->th2)); } int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len) @@ -49,7 +51,8 @@ int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len) return WOLFSPDM_E_INVALID_ARG; } - if (ctx->transcriptLen + len > WOLFSPDM_MAX_TRANSCRIPT) { + if (ctx->transcriptLen > WOLFSPDM_MAX_TRANSCRIPT || + len > WOLFSPDM_MAX_TRANSCRIPT - ctx->transcriptLen) { return WOLFSPDM_E_BUFFER_SMALL; } @@ -62,22 +65,6 @@ int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len) return WOLFSPDM_SUCCESS; } -int wolfSPDM_CertChainAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len) -{ - if (ctx == NULL || data == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (ctx->certChainLen + len > WOLFSPDM_MAX_CERT_CHAIN) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - XMEMCPY(ctx->certChain + ctx->certChainLen, data, len); - ctx->certChainLen += len; - - return WOLFSPDM_SUCCESS; -} - int wolfSPDM_Sha384Hash(byte* out, const byte* d1, word32 d1Sz, const byte* d2, word32 d2Sz, @@ -114,17 +101,3 @@ int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash) NULL, 0, NULL, 0); } -int wolfSPDM_ComputeCertChainHash(WOLFSPDM_CTX* ctx) -{ - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - if (ctx->certChainLen == 0) { - XMEMSET(ctx->certChainHash, 0, sizeof(ctx->certChainHash)); - return WOLFSPDM_SUCCESS; - } - - wolfSPDM_DebugPrint(ctx, "Ct = Hash(cert_chain[%u])\n", ctx->certChainLen); - return wolfSPDM_Sha384Hash(ctx->certChainHash, - ctx->certChain, ctx->certChainLen, NULL, 0, NULL, 0); -} diff --git a/src/vendor/spdm_nations.c b/src/vendor/spdm_nations.c new file mode 100644 index 0000000..4375c25 --- /dev/null +++ b/src/vendor/spdm_nations.c @@ -0,0 +1,182 @@ +/* spdm_nations.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +/* Nations Technology NS350 SPDM Functions + * + * PSK-mode vendor commands and PSK connection flow. + * Identity key mode uses shared TCG code in spdm_tcg.c. + */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_NATIONS + +#include + +/* ----- Nations PSK-Mode Vendor Commands ----- */ + +int wolfSPDM_Nations_GetStatus(WOLFSPDM_CTX* ctx, + WOLFSPDM_NATIONS_STATUS* status) +{ + WOLFSPDM_VENDOR_RSP rsp; + int rc; + + if (ctx == NULL || status == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMSET(status, 0, sizeof(*status)); + + wolfSPDM_DebugPrint(ctx, "Nations: GET_STS_\n"); + + /* NS350 accepts GET_STATUS with no payload (Type field omitted) */ + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_STS, + NULL, 0, &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugHex(ctx, "GET_STS_ payload", rsp.payload, rsp.payloadSz); + + /* Per TCG spec Table 15 — GET_STATUS_RSP payload: + * [0] SpecMajorVersion, [1] SpecMinorVersion, + * [2] PSKSet (00=NO, 01=YES), + * [3] SPDMOnly (00=DISABLED, 01=ENABLED, 81=PENDING_DISABLE) */ + if (rsp.payloadSz >= 4) { + status->spdmEnabled = 1; + status->pskProvisioned = (rsp.payload[2] != 0); + status->spdmOnlyLocked = (rsp.payload[3] != 0); + wolfSPDM_DebugPrint(ctx, "GET_STS_: v%u.%u PSK=%s SPDMOnly=0x%02x\n", + rsp.payload[0], rsp.payload[1], + status->pskProvisioned ? "YES" : "NO", + rsp.payload[3]); + } + else { + return WOLFSPDM_E_FRAMING; + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock) +{ + byte param[1]; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + param[0] = lock ? WOLFSPDM_SPDMONLY_LOCK : WOLFSPDM_SPDMONLY_UNLOCK; + + wolfSPDM_DebugPrint(ctx, "Nations: SPDMONLY %s\n", + lock ? "LOCK" : "UNLOCK"); + + rc = wolfSPDM_TCG_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_SPDMONLY, + param, sizeof(param)); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "SPDMONLY: Success (Lock=%u)\n", param[0]); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_PskSet(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz) +{ + int rc; + + if (ctx == NULL || psk == NULL || pskSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + wolfSPDM_DebugPrint(ctx, "Nations: PSK_SET_ (%u bytes)\n", pskSz); + + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_NATIONS_VDCODE_PSK_SET, + psk, pskSz, NULL); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "PSK_SET_ failed: %d\n", rc); + return rc; + } + + wolfSPDM_DebugPrint(ctx, "PSK_SET_: Success\n"); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_PskClear(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz) +{ + int rc; + + if (ctx == NULL || clearAuth == NULL || clearAuthSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + wolfSPDM_DebugPrint(ctx, "Nations: PSK_CLR_ (auth=%u bytes)\n", + clearAuthSz); + + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_NATIONS_VDCODE_PSK_CLEAR, + clearAuth, clearAuthSz, NULL); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "PSK_CLR_ failed: %d\n", rc); + return rc; + } + + wolfSPDM_DebugPrint(ctx, "PSK_CLR_: Success\n"); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz) +{ + int rc; + + if (ctx == NULL || clearAuth == NULL || clearAuthSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Full VCA: GET_VERSION + GET_CAPABILITIES + NEGOTIATE_ALGORITHMS */ + rc = wolfSPDM_GetVersion(ctx); + if (rc != WOLFSPDM_SUCCESS) return rc; + + rc = wolfSPDM_TCG_GetCapabilities(ctx, WOLFSPDM_TCG_CAPS_FLAGS_PSK); + if (rc != WOLFSPDM_SUCCESS) return rc; + + rc = wolfSPDM_TCG_NegotiateAlgorithms(ctx); + if (rc != WOLFSPDM_SUCCESS) return rc; + + return wolfSPDM_Nations_PskClear(ctx, clearAuth, clearAuthSz); +} + +/* PSK connection flow moved to spdm_psk.c (wolfSPDM_ConnectPsk). + * wolfSPDM_ConnectNationsPsk is a backward-compat alias in spdm_psk.h. */ + +#endif /* WOLFSPDM_NATIONS */ + diff --git a/src/vendor/spdm_nuvoton.c b/src/vendor/spdm_nuvoton.c new file mode 100644 index 0000000..b9870ca --- /dev/null +++ b/src/vendor/spdm_nuvoton.c @@ -0,0 +1,120 @@ +/* spdm_nuvoton.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +/* Nuvoton-specific SPDM functions (GetStatus, SetOnlyMode). */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_NUVOTON + +#include + +int wolfSPDM_Nuvoton_GetStatus( + WOLFSPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status) +{ + WOLFSPDM_VENDOR_RSP rsp; + byte statusType[4] = {0x00, 0x00, 0x00, 0x00}; + int rc; + + if (ctx == NULL || status == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMSET(status, 0, sizeof(*status)); + + wolfSPDM_DebugPrint(ctx, "Nuvoton: GET_STS_\n"); + + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_STS, + statusType, sizeof(statusType), &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "GET_STS_: VdCode='%.8s', %u bytes\n", + rsp.vdCode, rsp.payloadSz); + + /* Parse status fields per Nuvoton spec page 9: + * Byte 0: SpecVersionMajor (0 for SPDM 1.x) + * Byte 1: SpecVersionMinor (1 = SPDM 1.1, 3 = SPDM 1.3) + * Byte 2: Reserved + * Byte 3: SPDMOnly lock state (0 = unlocked, 1 = locked) */ + if (rsp.payloadSz >= 4) { + byte specMajor = rsp.payload[0]; + byte specMinor = rsp.payload[1]; + byte spdmOnly = rsp.payload[3]; + + status->specVersionMajor = specMajor; + status->specVersionMinor = specMinor; + status->spdmOnlyLocked = (spdmOnly != 0); + status->spdmEnabled = 1; + status->sessionActive = 0; + + wolfSPDM_DebugPrint(ctx, "GET_STS_: SpecVersion=%u.%u, SPDMOnly=%s\n", + specMajor, specMinor, spdmOnly ? "LOCKED" : "unlocked"); + } else if (rsp.payloadSz >= 1) { + status->spdmOnlyLocked = (rsp.payload[0] != 0); + status->spdmEnabled = 1; + wolfSPDM_DebugPrint(ctx, "GET_STS_: SPDMOnly=%s (minimal response)\n", + status->spdmOnlyLocked ? "LOCKED" : "unlocked"); + } + else { + return WOLFSPDM_E_FRAMING; + } + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nuvoton_SetOnlyMode( + WOLFSPDM_CTX* ctx, + int lock) +{ + byte param[1]; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + param[0] = lock ? WOLFSPDM_SPDMONLY_LOCK : WOLFSPDM_SPDMONLY_UNLOCK; + + wolfSPDM_DebugPrint(ctx, "Nuvoton: SPDMONLY %s\n", + lock ? "LOCK" : "UNLOCK"); + + rc = wolfSPDM_TCG_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_SPDMONLY, + param, sizeof(param)); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "SPDMONLY: Success\n"); + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON */ + diff --git a/test/test_spdm.c b/test/test_spdm.c deleted file mode 100644 index 7352aff..0000000 --- a/test/test_spdm.c +++ /dev/null @@ -1,223 +0,0 @@ -/* test_spdm.c - * - * Basic test for wolfSPDM library. - * Tests against libspdm emulator (spdm_responder_emu --trans TCP) - */ - -#include -#include -#include -#include -#include - -#ifdef __linux__ -#include -#include -#include -#include -#include -#include -#define HAS_SOCKET 1 -#endif - -#define EMU_HOST "127.0.0.1" -#define EMU_PORT 2323 - -#ifdef HAS_SOCKET -typedef struct { - int sockFd; -} TCP_CTX; - -static TCP_CTX g_tcpCtx = { -1 }; - -/* Tell secured (post-KEY_EXCHANGE_RSP) records from plaintext handshake by - * matching the leading 4 bytes against the live session ID, the same way - * examples/spdm_demo.c does it. Using a static flag here would miss FINISH - * (encrypted, but before WOLFSPDM_STATE_CONNECTED). */ -static int is_secured_spdm(WOLFSPDM_CTX* ctx, const byte* buf, word32 sz) -{ - word32 sid, b0; - if (sz < 4) return 0; - sid = wolfSPDM_GetSessionId(ctx); - if (sid == 0) return 0; - b0 = (word32)buf[0] | ((word32)buf[1] << 8) | - ((word32)buf[2] << 16) | ((word32)buf[3] << 24); - return b0 == sid; -} - -/* MCTP transport I/O callback for libspdm emulator */ -static int tcp_io_callback(WOLFSPDM_CTX* ctx, - const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz, - void* userCtx) -{ - TCP_CTX* tcpCtx = (TCP_CTX*)userCtx; - byte sendBuf[512]; - byte recvHdr[12]; - byte mctpHdr; - ssize_t sent, recvd; - word32 payloadSz, respSize; - - if (tcpCtx == NULL || tcpCtx->sockFd < 0) { - return -1; - } - - /* Payload = MCTP header (1) + SPDM message */ - payloadSz = 1 + txSz; - - if (12 + payloadSz > sizeof(sendBuf)) { - return -1; - } - - /* Socket header: command(4,BE) + transport_type(4,BE) + size(4,BE) */ - sendBuf[0] = 0x00; sendBuf[1] = 0x00; sendBuf[2] = 0x00; sendBuf[3] = 0x01; - sendBuf[4] = 0x00; sendBuf[5] = 0x00; sendBuf[6] = 0x00; sendBuf[7] = 0x01; - sendBuf[8] = (byte)(payloadSz >> 24); - sendBuf[9] = (byte)(payloadSz >> 16); - sendBuf[10] = (byte)(payloadSz >> 8); - sendBuf[11] = (byte)(payloadSz & 0xFF); - - /* MCTP message type: secured (0x06) once the tx buffer starts with the - * live session ID, otherwise plaintext SPDM (0x05). */ - sendBuf[12] = is_secured_spdm(ctx, txBuf, txSz) ? 0x06 : 0x05; - - if (txSz > 0) { - memcpy(sendBuf + 13, txBuf, txSz); - } - - sent = send(tcpCtx->sockFd, sendBuf, 12 + payloadSz, 0); - if (sent != (ssize_t)(12 + payloadSz)) { - return -1; - } - - recvd = recv(tcpCtx->sockFd, recvHdr, 12, MSG_WAITALL); - if (recvd != 12) { - return -1; - } - - respSize = ((word32)recvHdr[8] << 24) | ((word32)recvHdr[9] << 16) | - ((word32)recvHdr[10] << 8) | (word32)recvHdr[11]; - - if (respSize < 1 || respSize - 1 > *rxSz) { - return -1; - } - - /* Skip MCTP header */ - recvd = recv(tcpCtx->sockFd, &mctpHdr, 1, MSG_WAITALL); - if (recvd != 1) return -1; - (void)mctpHdr; - - *rxSz = respSize - 1; - if (*rxSz > 0) { - recvd = recv(tcpCtx->sockFd, rxBuf, *rxSz, MSG_WAITALL); - if (recvd != (ssize_t)*rxSz) return -1; - } - - return 0; -} - -static int tcp_connect(const char* host, int port) -{ - int sockFd; - struct sockaddr_in addr; - int optVal = 1; - - sockFd = socket(AF_INET, SOCK_STREAM, 0); - if (sockFd < 0) return -1; - - setsockopt(sockFd, IPPROTO_TCP, TCP_NODELAY, &optVal, sizeof(optVal)); - - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons((uint16_t)port); - if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { - close(sockFd); - return -1; - } - - if (connect(sockFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - close(sockFd); - return -1; - } - - g_tcpCtx.sockFd = sockFd; - return 0; -} - -static void tcp_disconnect(void) -{ - if (g_tcpCtx.sockFd >= 0) { - close(g_tcpCtx.sockFd); - g_tcpCtx.sockFd = -1; - } -} - -/* Static context buffer sized by the public header so wolfSSL configs that - * grow internal struct sizes (e.g. sp-math/ecc variants in CI) still fit. */ -#define CTX_BUF_SIZE WOLFSPDM_CTX_STATIC_SIZE -static byte g_ctxBuf[CTX_BUF_SIZE]; - -int main(int argc, char* argv[]) -{ - WOLFSPDM_CTX* ctx = (WOLFSPDM_CTX*)g_ctxBuf; - int rc; - - (void)argc; - (void)argv; - - printf("wolfSPDM Test - Connecting to %s:%d\n", EMU_HOST, EMU_PORT); - - if (wolfSPDM_GetCtxSize() > CTX_BUF_SIZE) { - printf("ERROR: CTX_BUF_SIZE too small (%d needed)\n", - wolfSPDM_GetCtxSize()); - return 1; - } - - if (tcp_connect(EMU_HOST, EMU_PORT) < 0) { - printf("ERROR: Cannot connect to emulator.\n"); - printf("Start the emulator first:\n"); - printf(" ./spdm_responder_emu --trans TCP\n"); - return 1; - } - - rc = wolfSPDM_InitStatic(ctx, CTX_BUF_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - printf("ERROR: wolfSPDM_InitStatic failed: %s\n", - wolfSPDM_GetErrorString(rc)); - tcp_disconnect(); - return 1; - } - - wolfSPDM_SetDebug(ctx, 1); - wolfSPDM_SetIO(ctx, tcp_io_callback, &g_tcpCtx); - - /* Smoke test runs against spdm-emu with self-signed test certs; the - * library now refuses handshakes without a trust anchor unless the - * caller explicitly opts out. */ - wolfSPDM_AllowUntrustedCerts(ctx, 1); - - printf("\nEstablishing SPDM session...\n\n"); - rc = wolfSPDM_Connect(ctx); - - if (rc == WOLFSPDM_SUCCESS) { - printf("\n===========================================\n"); - printf(" SUCCESS: SPDM Session Established!\n"); - printf(" SessionID: 0x%08x\n", wolfSPDM_GetSessionId(ctx)); - printf("===========================================\n"); - } else { - printf("\nERROR: wolfSPDM_Connect failed: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - } - - wolfSPDM_Free(ctx); - tcp_disconnect(); - - return (rc == WOLFSPDM_SUCCESS) ? 0 : 1; -} -#else -int main(void) -{ - printf("Socket support not available\n"); - return 1; -} -#endif diff --git a/test/unit_test.c b/test/unit_test.c index 7aeb3dd..ebb563f 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1,8 +1,22 @@ /* unit_test.c * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * - * Unit tests for wolfSPDM library functions. + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ #ifdef HAVE_CONFIG_H @@ -10,7 +24,10 @@ #endif #include -#include "../src/spdm_internal.h" +#ifdef WOLFSPDM_RESPONDER + #include +#endif +#include "spdm_internal.h" #include #include #include @@ -35,10 +52,6 @@ static int g_testsFailed = 0; printf(" FAIL %s:%d: %s returned %d\n", __FILE__, __LINE__, #expr, _r); \ g_testsFailed++; return -1; } } while(0) -#define ASSERT_FAIL(expr) do { int _r = (expr); if (_r == 0) { \ - printf(" FAIL %s:%d: %s should have failed\n", __FILE__, __LINE__, #expr); \ - g_testsFailed++; return -1; } } while(0) - #define ASSERT_EQ(a, b, msg) TEST_ASSERT((a) == (b), msg) #define ASSERT_NE(a, b, msg) TEST_ASSERT((a) != (b), msg) @@ -64,9 +77,7 @@ static int dummy_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, return -1; } -/* ========================================================================== */ -/* Context Tests */ -/* ========================================================================== */ +/* ----- Context Tests ----- */ #ifdef WOLFSPDM_DYNAMIC_MEMORY static int test_context_new_free(void) @@ -94,7 +105,6 @@ static int test_context_init(void) printf("test_context_init...\n"); ASSERT_EQ(ctx->flags.initialized, 1, "Not marked initialized"); ASSERT_EQ(ctx->flags.rngInitialized, 1, "RNG not initialized"); - ASSERT_EQ(ctx->reqCaps, WOLFSPDM_DEFAULT_REQ_CAPS, "Default caps wrong"); TEST_CTX_FREE(); TEST_PASS(); @@ -107,8 +117,10 @@ static int test_context_static_alloc(void) printf("test_context_static_alloc...\n"); - ASSERT_EQ(wolfSPDM_GetCtxSize(), (int)sizeof(WOLFSPDM_CTX), "GetCtxSize mismatch"); - ASSERT_EQ(wolfSPDM_InitStatic(ctx, 10), WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); + ASSERT_EQ(wolfSPDM_GetCtxSize(), (int)sizeof(WOLFSPDM_CTX), + "GetCtxSize mismatch"); + ASSERT_EQ(wolfSPDM_InitStatic(ctx, 10), WOLFSPDM_E_BUFFER_SMALL, + "Should fail on small buffer"); ASSERT_SUCCESS(wolfSPDM_InitStatic(ctx, sizeof(buffer))); ASSERT_EQ(ctx->flags.initialized, 1, "Static ctx not initialized"); @@ -126,15 +138,14 @@ static int test_context_set_io(void) ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, dummy_io_cb, &dummy)); ASSERT_EQ(ctx->ioCb, dummy_io_cb, "IO callback not set"); ASSERT_EQ(ctx->ioUserCtx, &dummy, "User context not set"); - ASSERT_EQ(wolfSPDM_SetIO(ctx, NULL, NULL), WOLFSPDM_E_INVALID_ARG, "NULL callback should fail"); + ASSERT_EQ(wolfSPDM_SetIO(ctx, NULL, NULL), WOLFSPDM_E_INVALID_ARG, + "NULL callback should fail"); TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* Transcript Tests */ -/* ========================================================================== */ +/* ----- Transcript Tests ----- */ static int test_transcript_add_reset(void) { @@ -171,32 +182,14 @@ static int test_transcript_hash(void) wolfSPDM_TranscriptAdd(ctx, data, sizeof(data) - 1); ASSERT_SUCCESS(wolfSPDM_TranscriptHash(ctx, hash)); XMEMSET(zeros, 0, sizeof(zeros)); - ASSERT_NE(memcmp(hash, zeros, WOLFSPDM_HASH_SIZE), 0, "Hash should be non-zero"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -static int test_certchain_hash(void) -{ - byte certData[] = {0x30, 0x82, 0x01, 0x00, 0xAA, 0xBB, 0xCC, 0xDD}; - byte zeros[WOLFSPDM_HASH_SIZE]; - TEST_CTX_SETUP(); - - printf("test_certchain_hash...\n"); - ASSERT_SUCCESS(wolfSPDM_CertChainAdd(ctx, certData, sizeof(certData))); - ASSERT_EQ(ctx->certChainLen, sizeof(certData), "CertChain len wrong"); - ASSERT_SUCCESS(wolfSPDM_ComputeCertChainHash(ctx)); - XMEMSET(zeros, 0, sizeof(zeros)); - ASSERT_NE(memcmp(ctx->certChainHash, zeros, WOLFSPDM_HASH_SIZE), 0, "Ct should be non-zero"); + ASSERT_NE(memcmp(hash, zeros, WOLFSPDM_HASH_SIZE), 0, + "Hash should be non-zero"); TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* Crypto Tests */ -/* ========================================================================== */ +/* ----- Crypto Tests ----- */ static int test_random_generation(void) { @@ -206,7 +199,8 @@ static int test_random_generation(void) printf("test_random_generation...\n"); ASSERT_SUCCESS(wolfSPDM_GetRandom(ctx, buf1, sizeof(buf1))); ASSERT_SUCCESS(wolfSPDM_GetRandom(ctx, buf2, sizeof(buf2))); - ASSERT_NE(memcmp(buf1, buf2, sizeof(buf1)), 0, "Random outputs should differ"); + ASSERT_NE(memcmp(buf1, buf2, sizeof(buf1)), 0, + "Random outputs should differ"); TEST_CTX_FREE(); TEST_PASS(); @@ -228,15 +222,14 @@ static int test_ephemeral_key_generation(void) ASSERT_EQ(xSz, WOLFSPDM_ECC_KEY_SIZE, "X coordinate wrong size"); ASSERT_EQ(ySz, WOLFSPDM_ECC_KEY_SIZE, "Y coordinate wrong size"); XMEMSET(zeros, 0, sizeof(zeros)); - ASSERT_NE(memcmp(pubKeyX, zeros, WOLFSPDM_ECC_KEY_SIZE), 0, "Public key X should be non-zero"); + ASSERT_NE(memcmp(pubKeyX, zeros, WOLFSPDM_ECC_KEY_SIZE), 0, + "Public key X should be non-zero"); TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* KDF Tests */ -/* ========================================================================== */ +/* ----- KDF Tests ----- */ static int test_hkdf_expand_label(void) { @@ -253,7 +246,8 @@ static int test_hkdf_expand_label(void) ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(0x13, secret, sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), output, sizeof(output))); XMEMSET(zeros, 0, sizeof(zeros)); - ASSERT_NE(memcmp(output, zeros, sizeof(output)), 0, "HKDF output should be non-zero"); + ASSERT_NE(memcmp(output, zeros, sizeof(output)), 0, + "HKDF output should be non-zero"); TEST_PASS(); } @@ -272,14 +266,13 @@ static int test_compute_verify_data(void) ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKey, thHash, verifyData)); XMEMSET(zeros, 0, sizeof(zeros)); - ASSERT_NE(memcmp(verifyData, zeros, WOLFSPDM_HASH_SIZE), 0, "VerifyData should be non-zero"); + ASSERT_NE(memcmp(verifyData, zeros, WOLFSPDM_HASH_SIZE), 0, + "VerifyData should be non-zero"); TEST_PASS(); } -/* ========================================================================== */ -/* Message Builder Tests */ -/* ========================================================================== */ +/* ----- Message Builder Tests ----- */ static int test_build_get_version(void) { @@ -290,2694 +283,2507 @@ static int test_build_get_version(void) ASSERT_SUCCESS(wolfSPDM_BuildGetVersion(buf, &bufSz)); ASSERT_EQ(bufSz, 4, "GET_VERSION should be 4 bytes"); - ASSERT_EQ(buf[0], SPDM_VERSION_10, "Version should be 0x10"); ASSERT_EQ(buf[1], SPDM_GET_VERSION, "Code should be 0x84"); bufSz = 2; - ASSERT_EQ(wolfSPDM_BuildGetVersion(buf, &bufSz), WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); + ASSERT_EQ(wolfSPDM_BuildGetVersion(buf, &bufSz), WOLFSPDM_E_BUFFER_SMALL, + "Should fail on small buffer"); TEST_PASS(); } -static int test_get_version_null_ctx(void) +static int test_build_end_session(void) { - /* wolfSPDM_GetVersion routes through the shared ExchangeMsg helper, - * which dereferences ctx for transcript snapshotting. The - * BuildGetVersion adapter ignores ctx, so without an explicit guard - * a NULL caller would crash inside the helper. Match the rest of - * the public API and return WOLFSPDM_E_INVALID_ARG instead. */ - printf("test_get_version_null_ctx...\n"); + byte buf[16]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); - ASSERT_EQ(wolfSPDM_GetVersion(NULL), WOLFSPDM_E_INVALID_ARG, - "GetVersion(NULL) must return INVALID_ARG, not crash"); + printf("test_build_end_session...\n"); + ASSERT_SUCCESS(wolfSPDM_BuildEndSession(ctx, buf, &bufSz)); + ASSERT_EQ(bufSz, 4, "END_SESSION should be 4 bytes"); + ASSERT_EQ(buf[1], SPDM_END_SESSION, "END_SESSION code mismatch"); + TEST_CTX_FREE(); TEST_PASS(); } -static int test_build_get_capabilities(void) +/* ----- Error Check Tests ----- */ + +static int test_check_error(void) { - byte buf[32]; - word32 bufSz = sizeof(buf); - TEST_CTX_SETUP_V12(); + byte errorMsg[] = {0x12, SPDM_ERROR, 0x06, 0x00}; + byte okMsg[] = {0x12, SPDM_VERSION, 0x00, 0x00}; + int errorCode = 0; - printf("test_build_get_capabilities...\n"); - ASSERT_SUCCESS(wolfSPDM_BuildGetCapabilities(ctx, buf, &bufSz)); - ASSERT_EQ(bufSz, 20, "GET_CAPABILITIES should be 20 bytes"); - ASSERT_EQ(buf[0], SPDM_VERSION_12, "Version should be 0x12"); - ASSERT_EQ(buf[1], SPDM_GET_CAPABILITIES, "Code should be 0xE1"); + printf("test_check_error...\n"); + + TEST_ASSERT(wolfSPDM_CheckError(errorMsg, sizeof(errorMsg), &errorCode) == 1, + "Should detect error"); + TEST_ASSERT(errorCode == SPDM_ERROR_DECRYPT_ERROR, "Error code wrong"); + + TEST_ASSERT(wolfSPDM_CheckError(okMsg, sizeof(okMsg), NULL) == 0, + "Should not detect error on OK message"); - TEST_CTX_FREE(); TEST_PASS(); } -static int test_build_negotiate_algorithms(void) +static int test_error_strings(void) { - byte buf[64]; - word32 bufSz = sizeof(buf); - TEST_CTX_SETUP_V12(); + printf("test_error_strings...\n"); - printf("test_build_negotiate_algorithms...\n"); - ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, buf, &bufSz)); - ASSERT_EQ(bufSz, 48, "NEGOTIATE_ALGORITHMS should be 48 bytes"); - ASSERT_EQ(buf[1], SPDM_NEGOTIATE_ALGORITHMS, "Code should be 0xE3"); + TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_SUCCESS), "Success") == 0, + "SUCCESS string wrong"); + TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_E_INVALID_ARG), + "Invalid argument") == 0, "INVALID_ARG string wrong"); + TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_E_CRYPTO_FAIL), + "Crypto operation failed") == 0, "CRYPTO_FAIL string wrong"); - TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_algorithms_set_b_enforcement(void) -{ - /* Synthesise a minimal ALGORITHMS response and verify each Set-B - * constraint (BaseAsym=ECDSA-P384, BaseHash=SHA-384, DHE=SECP_384_R1, - * AEAD=AES_256_GCM, KeySchedule=SPDM) is enforced. */ - byte rsp[64]; - TEST_CTX_SETUP_V12(); - - printf("test_parse_algorithms_set_b_enforcement...\n"); +/* ----- Multi-Version Tests ----- */ - /* Build a response with Set-B selections. */ - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_12; - rsp[1] = SPDM_ALGORITHMS; - rsp[2] = 4; /* Param1 = AlgStructCount */ - /* Length field (offset 4-5, LE) must match received bufSz (52). */ - SPDM_Set16LE(&rsp[4], 52); - rsp[6] = 0x01; /* MeasurementSpecificationSel = DMTF */ - rsp[7] = 0x02; /* OtherParamsSel = OpaqueDataFormat1 */ - /* BaseAsymSel = ECDSA_P384 (bit 7) at offset 12 */ - rsp[12] = SPDM_ASYM_ALGO_ECDSA_P384 & 0xFF; - rsp[13] = (SPDM_ASYM_ALGO_ECDSA_P384 >> 8) & 0xFF; - /* BaseHashSel = SHA_384 (bit 1) at offset 16 */ - rsp[16] = SPDM_HASH_ALGO_SHA_384; - /* AlgStruct table starts at offset 36, each entry 4 bytes: - * AlgType(1) + AlgCount(1=0x20) + AlgSel(2 LE) */ - rsp[36] = 2; rsp[37] = 0x20; rsp[38] = 0x10; rsp[39] = 0x00; /* DHE SECP_384_R1 */ - rsp[40] = 3; rsp[41] = 0x20; rsp[42] = 0x02; rsp[43] = 0x00; /* AEAD AES_256_GCM */ - rsp[44] = 4; rsp[45] = 0x20; rsp[46] = 0x0F; rsp[47] = 0x00; /* ReqBaseAsym */ - rsp[48] = 5; rsp[49] = 0x20; rsp[50] = 0x01; rsp[51] = 0x00; /* KeySchedule SPDM */ +static int test_kdf_version_prefix(void) +{ + byte secret[48]; + byte context[48]; + byte out12[32], out13[32], out14[32]; - ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, 52)); + printf("test_kdf_version_prefix...\n"); - /* Tamper AEAD selection to AES_128_GCM (bit 0) - must be rejected. */ - rsp[42] = 0x01; - ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, 52), - WOLFSPDM_E_ALGO_MISMATCH, "Non-AES-256-GCM AEAD must fail Set-B"); - rsp[42] = 0x02; + memset(secret, 0x5A, sizeof(secret)); + memset(context, 0x00, sizeof(context)); - /* Tamper DHE to SECP_256_R1 (bit 3) - must be rejected. */ - rsp[38] = 0x08; - ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, 52), - WOLFSPDM_E_ALGO_MISMATCH, "Non-SECP_384_R1 DHE must fail Set-B"); - rsp[38] = 0x10; + ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(SPDM_VERSION_12, secret, + sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), + out12, sizeof(out12))); + ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(SPDM_VERSION_13, secret, + sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), + out13, sizeof(out13))); + ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(SPDM_VERSION_14, secret, + sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), + out14, sizeof(out14))); - /* Tamper KeySchedule to 0 - must be rejected. */ - rsp[50] = 0x00; - ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, 52), - WOLFSPDM_E_ALGO_MISMATCH, "Non-SPDM KeySchedule must fail Set-B"); + /* All three outputs should differ due to different BinConcat prefixes */ + ASSERT_NE(memcmp(out12, out13, sizeof(out12)), 0, + "1.2 and 1.3 outputs should differ"); + ASSERT_NE(memcmp(out13, out14, sizeof(out13)), 0, + "1.3 and 1.4 outputs should differ"); + ASSERT_NE(memcmp(out12, out14, sizeof(out12)), 0, + "1.2 and 1.4 outputs should differ"); - TEST_CTX_FREE(); TEST_PASS(); } -#ifdef WOLFSPDM_HAVE_MLDSA -static int test_negotiate_algorithms_pqc_build(void) +static int test_hmac_mismatch_negative(void) { - byte buf[64]; - word32 bufSz; - TEST_CTX_SETUP(); + byte finishedKeyA[WOLFSPDM_HASH_SIZE]; + byte finishedKeyB[WOLFSPDM_HASH_SIZE]; + byte thHash[WOLFSPDM_HASH_SIZE]; + byte verifyA[WOLFSPDM_HASH_SIZE]; + byte verifyB[WOLFSPDM_HASH_SIZE]; - printf("test_negotiate_algorithms_pqc_build...\n"); + printf("test_hmac_mismatch_negative...\n"); - /* SPDM 1.4: PqcAsymAlgo (offset 16) advertises ML-DSA-44|65|87 = 0x07. */ - ctx->spdmVersion = SPDM_VERSION_14; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, buf, &bufSz)); - ASSERT_EQ(buf[16], - (SPDM_PQC_ASYM_ALGO_ML_DSA_44 | SPDM_PQC_ASYM_ALGO_ML_DSA_65 | - SPDM_PQC_ASYM_ALGO_ML_DSA_87), - "1.4 PqcAsymAlgo must advertise ML-DSA 44/65/87 at offset 16"); - ASSERT_EQ(buf[8], 0x80, "BaseAsymAlgo ECDSA P-384 still advertised"); + memset(finishedKeyA, 0xAB, sizeof(finishedKeyA)); + memset(finishedKeyB, 0xAC, sizeof(finishedKeyB)); /* Differs by 1 bit */ + memset(thHash, 0xCD, sizeof(thHash)); - /* SPDM 1.2: offset 16 is reserved-zero (no PqcAsymAlgo). */ - ctx->spdmVersion = SPDM_VERSION_12; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, buf, &bufSz)); - ASSERT_EQ(buf[16], 0x00, "1.2 must leave offset 16 reserved-zero"); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKeyA, thHash, verifyA)); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKeyB, thHash, verifyB)); - TEST_CTX_FREE(); - TEST_PASS(); -} + /* Single-bit change in key must produce different verify data */ + ASSERT_NE(memcmp(verifyA, verifyB, WOLFSPDM_HASH_SIZE), 0, + "Different keys should produce different verify data"); -/* Build a minimal SPDM 1.4 ALGORITHMS response with the given BaseAsymSel - * (offset 12) and PqcAsymSel (offset 20). Returns total length (52). */ -static word32 build_algorithms_14(byte* rsp, word32 baseAsymSel, - word32 pqcAsymSel) -{ - XMEMSET(rsp, 0, 52); - rsp[0] = SPDM_VERSION_14; - rsp[1] = SPDM_ALGORITHMS; - rsp[2] = 4; /* AlgStructCount */ - SPDM_Set16LE(&rsp[4], 52); - rsp[6] = 0x01; /* MeasurementSpecificationSel = DMTF */ - rsp[7] = 0x02; /* OtherParamsSel = OpaqueDataFormat1 */ - SPDM_Set32LE(&rsp[12], baseAsymSel); - rsp[16] = SPDM_HASH_ALGO_SHA_384; - SPDM_Set32LE(&rsp[20], pqcAsymSel); - rsp[36] = 2; rsp[37] = 0x20; rsp[38] = 0x10; /* DHE SECP_384_R1 */ - rsp[40] = 3; rsp[41] = 0x20; rsp[42] = 0x02; /* AEAD AES_256_GCM */ - rsp[44] = 4; rsp[45] = 0x20; rsp[46] = 0x0F; /* ReqBaseAsym */ - rsp[48] = 5; rsp[49] = 0x20; rsp[50] = 0x01; /* KeySchedule SPDM */ - return 52; + TEST_PASS(); } -static int test_parse_algorithms_pqc_select(void) +static int test_transcript_overflow(void) { - byte rsp[64]; - word32 levels[3]; - int i; + byte chunk[256]; + word32 i, needed; TEST_CTX_SETUP(); - levels[0] = SPDM_PQC_ASYM_ALGO_ML_DSA_44; - levels[1] = SPDM_PQC_ASYM_ALGO_ML_DSA_65; - levels[2] = SPDM_PQC_ASYM_ALGO_ML_DSA_87; + printf("test_transcript_overflow...\n"); - printf("test_parse_algorithms_pqc_select...\n"); - ctx->spdmVersion = SPDM_VERSION_14; + memset(chunk, 0x42, sizeof(chunk)); - /* Each ML-DSA level (44/65/87) with BaseAsymSel = 0: select ML-DSA. */ - for (i = 0; i < 3; i++) { - build_algorithms_14(rsp, 0, levels[i]); - ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, 52)); - ASSERT_EQ(ctx->asymType, WOLFSPDM_ASYM_MLDSA, "asymType must be ML-DSA"); - ASSERT_EQ(ctx->pqcAsymSel, levels[i], "pqcAsymSel must echo level"); + /* Fill transcript to capacity */ + needed = WOLFSPDM_MAX_TRANSCRIPT / sizeof(chunk); + for (i = 0; i < needed; i++) { + ASSERT_SUCCESS(wolfSPDM_TranscriptAdd(ctx, chunk, sizeof(chunk))); } + ASSERT_EQ(ctx->transcriptLen, (word32)(needed * sizeof(chunk)), + "Transcript should be full"); + + /* Next add should fail with BUFFER_SMALL */ + ASSERT_EQ(wolfSPDM_TranscriptAdd(ctx, chunk, sizeof(chunk)), + WOLFSPDM_E_BUFFER_SMALL, "Overflow should return BUFFER_SMALL"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_version_fallback(void) +{ + /* Fake VERSION response with versions 1.0, 1.1, 1.2, 1.3 */ + byte rsp[] = { + 0x10, SPDM_VERSION, 0x00, 0x00, /* header */ + 0x04, 0x00, /* entryCount = 4 */ + 0x00, 0x10, /* 1.0 */ + 0x00, 0x11, /* 1.1 */ + 0x00, 0x12, /* 1.2 */ + 0x00, 0x13 /* 1.3 */ + }; + TEST_CTX_SETUP(); - /* Both BaseAsymSel and PqcAsymSel set: spec caps combined bits at one. */ - build_algorithms_14(rsp, SPDM_ASYM_ALGO_ECDSA_P384, - SPDM_PQC_ASYM_ALGO_ML_DSA_65); - ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, 52), - WOLFSPDM_E_ALGO_MISMATCH, "both Base+Pqc selected must fail"); + printf("test_version_fallback...\n"); - /* Unsupported PqcAsymSel bit (e.g. an SLH-DSA bit) must be rejected. */ - build_algorithms_14(rsp, 0, 0x00000008); - ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, 52), - WOLFSPDM_E_ALGO_MISMATCH, "unsupported PqcAsymSel must fail"); + /* With no maxVersion set, should select 1.3 (highest mutual) */ + ASSERT_SUCCESS(wolfSPDM_ParseVersion(ctx, rsp, sizeof(rsp))); + ASSERT_EQ(ctx->spdmVersion, SPDM_VERSION_13, + "Should select 1.3 as highest mutual"); - /* No PqcAsymSel: classic ECDSA path still works. */ - build_algorithms_14(rsp, SPDM_ASYM_ALGO_ECDSA_P384, 0); - ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, 52)); - ASSERT_EQ(ctx->asymType, WOLFSPDM_ASYM_ECDSA, "asymType must be ECDSA"); + /* Reset state and set maxVersion to 1.2 */ + ctx->state = WOLFSPDM_STATE_INIT; + ctx->spdmVersion = 0; + ctx->maxVersion = SPDM_VERSION_12; + ASSERT_SUCCESS(wolfSPDM_ParseVersion(ctx, rsp, sizeof(rsp))); + ASSERT_EQ(ctx->spdmVersion, SPDM_VERSION_12, + "Should fall back to 1.2 with maxVersion cap"); TEST_CTX_FREE(); TEST_PASS(); } -#ifndef NO_WOLFSPDM_MEAS_VERIFY -/* Mirror wolfSPDM's data_to_be_signed construction (DSP0274 Sec. 15): - * M = combined_spdm_prefix(100) || message_hash(48), so the test signs exactly - * what wolfSPDM_VerifyMlDsaSig assembles and verifies. */ -static word32 build_signed_msg(byte version, const char* ctxStr, - word32 ctxLen, const byte* msgHash, byte* out) +/* ----- Session State Tests ----- */ + +static int test_session_state(void) { - word32 n = 0; - word32 pad; - byte maj = (byte)('0' + ((version >> 4) & 0xF)); - byte min = (byte)('0' + (version & 0xF)); - int i; - for (i = 0; i < 4; i++) { - XMEMCPY(&out[n], "dmtf-spdm-v1.2.*", 16); - out[n + 11] = maj; out[n + 13] = min; out[n + 15] = '*'; - n += 16; - } - pad = 36 - ctxLen; - XMEMSET(&out[n], 0, pad); n += pad; - XMEMCPY(&out[n], ctxStr, ctxLen); n += ctxLen; - XMEMCPY(&out[n], msgHash, WOLFSPDM_HASH_SIZE); n += WOLFSPDM_HASH_SIZE; - return n; -} - -/* Sign an SPDM "measurements" message with a fresh ML-DSA key at the given - * level and confirm wolfSPDM_VerifyMeasurementSig accepts it (and rejects a - * tampered copy). Exercises GetSigSize, BuildSignedMsg, and VerifyCtx for one - * parameter set. Returns 0 on pass. */ -static int mldsa_verify_one(byte level, word32 pqcSel, word32 expSigLen) -{ - static const char measCtx[] = "responder-measurements signing"; - WC_RNG rng; - byte reqMsg[8]; - byte rspBuf[64 + WOLFSPDM_MLDSA87_SIG_SIZE]; - byte msgHash[WOLFSPDM_HASH_SIZE]; - byte signMsg[200]; - word32 signMsgLen; - word32 sigLen; - word32 bodyLen = 16; - int rc; TEST_CTX_SETUP(); - ctx->spdmVersion = SPDM_VERSION_14; - ctx->asymType = WOLFSPDM_ASYM_MLDSA; - ctx->pqcAsymSel = pqcSel; - ctx->vcaLen = 0; - - ASSERT_EQ(wc_InitRng(&rng), 0, "InitRng"); - ASSERT_EQ(wc_MlDsaKey_Init(&ctx->responderPubKey.mldsa, NULL, INVALID_DEVID), - 0, "MlDsaKey_Init"); - ASSERT_EQ(wc_MlDsaKey_SetParams(&ctx->responderPubKey.mldsa, level), - 0, "SetParams"); - ASSERT_EQ(wc_MlDsaKey_MakeKey(&ctx->responderPubKey.mldsa, &rng), 0, - "MakeKey"); - ctx->flags.hasResponderPubKey = 1; - - XMEMSET(reqMsg, 0xA5, sizeof(reqMsg)); - XMEMSET(rspBuf, 0x5A, bodyLen); - - /* message_hash = SHA384(VCA(empty) || reqMsg || signed_body) */ - ASSERT_EQ(wolfSPDM_Sha384Hash(msgHash, NULL, 0, reqMsg, sizeof(reqMsg), - rspBuf, bodyLen), 0, "hash"); - signMsgLen = build_signed_msg(SPDM_VERSION_14, measCtx, - (word32)(sizeof(measCtx) - 1), msgHash, signMsg); - - sigLen = (word32)(sizeof(rspBuf) - bodyLen); - rc = wc_MlDsaKey_SignCtx(&ctx->responderPubKey.mldsa, - (const byte*)measCtx, (byte)(sizeof(measCtx) - 1), - &rspBuf[bodyLen], &sigLen, signMsg, signMsgLen, &rng); - ASSERT_EQ(rc, 0, "SignCtx"); - ASSERT_EQ(sigLen, expSigLen, "ML-DSA SigLen must match level"); - - /* Good signature verifies. */ - ASSERT_SUCCESS(wolfSPDM_VerifyMeasurementSig(ctx, rspBuf, bodyLen + sigLen, - reqMsg, sizeof(reqMsg))); - - /* Tamper a signed-body byte -> verification must fail. */ - rspBuf[0] ^= 0xFF; - ASSERT_FAIL(wolfSPDM_VerifyMeasurementSig(ctx, rspBuf, bodyLen + sigLen, - reqMsg, sizeof(reqMsg))); - - wc_FreeRng(&rng); + printf("test_session_state...\n"); + ASSERT_EQ(wolfSPDM_IsConnected(ctx), 0, "Should not be connected"); + ASSERT_EQ(wolfSPDM_GetSessionId(ctx), 0, "SessionId should be 0"); + + /* Simulate connected state */ + ctx->state = WOLFSPDM_STATE_CONNECTED; + ctx->sessionId = 0xAABBCCDD; + ctx->spdmVersion = SPDM_VERSION_12; + ASSERT_EQ(wolfSPDM_IsConnected(ctx), 1, "Should be connected"); + ASSERT_EQ(wolfSPDM_GetSessionId(ctx), (word32)0xAABBCCDD, "SessionId wrong"); + ASSERT_EQ(wolfSPDM_GetNegotiatedVersion(ctx), SPDM_VERSION_12, "Version wrong"); + TEST_CTX_FREE(); - return 0; + TEST_PASS(); } -static int test_mldsa_measurement_verify(void) +/* ----- Security Tests ----- */ + +/* Test Fix 1: MITM rejection — a KEY_EXCHANGE_RSP with a forged signature + * (signed by an attacker's key, not the real responder) must be rejected. */ +static int test_mitm_signature_rejected(void) { - printf("test_mldsa_measurement_verify (ML-DSA 44/65/87)...\n"); - if (mldsa_verify_one(WC_ML_DSA_44, SPDM_PQC_ASYM_ALGO_ML_DSA_44, - WOLFSPDM_MLDSA44_SIG_SIZE) != 0) { - return -1; + ecc_key realKey, attackerKey; + byte realPubX[WOLFSPDM_ECC_KEY_SIZE], realPubY[WOLFSPDM_ECC_KEY_SIZE]; + byte atkPubX[WOLFSPDM_ECC_KEY_SIZE], atkPubY[WOLFSPDM_ECC_KEY_SIZE]; + word32 xSz, ySz; + byte rspPubKey[WOLFSPDM_ECC_POINT_SIZE]; + byte keRsp[300]; /* KEY_EXCHANGE_RSP: 282 bytes with opaqueLen=0 */ + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_mitm_signature_rejected...\n"); + + /* Generate "real responder" key and "attacker" key */ + wc_ecc_init(&realKey); + wc_ecc_init(&attackerKey); + wc_ecc_make_key(&ctx->rng, WOLFSPDM_ECC_KEY_SIZE, &realKey); + wc_ecc_make_key(&ctx->rng, WOLFSPDM_ECC_KEY_SIZE, &attackerKey); + + /* Export real responder public key and set it on ctx */ + xSz = ySz = WOLFSPDM_ECC_KEY_SIZE; + wc_ecc_export_public_raw(&realKey, realPubX, &xSz, realPubY, &ySz); + memcpy(rspPubKey, realPubX, WOLFSPDM_ECC_KEY_SIZE); + memcpy(rspPubKey + WOLFSPDM_ECC_KEY_SIZE, realPubY, WOLFSPDM_ECC_KEY_SIZE); + wolfSPDM_SetResponderPubKey(ctx, rspPubKey, WOLFSPDM_ECC_POINT_SIZE); + + /* Export attacker's ephemeral public key */ + xSz = ySz = WOLFSPDM_ECC_KEY_SIZE; + wc_ecc_export_public_raw(&attackerKey, atkPubX, &xSz, atkPubY, &ySz); + + /* Generate our ephemeral key (needed for ECDH later) */ + ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); + + /* Build a fake KEY_EXCHANGE_RSP: + * [0]=ver, [1]=0x64, [2-3]=params, [4-5]=rspSessionId, + * [6-7]=mutAuth, [8-39]=random, [40-87]=pubX, [88-135]=pubY, + * [136-137]=opaqueLen=0, [138-233]=signature, [234-281]=verifyData */ + memset(keRsp, 0, sizeof(keRsp)); + keRsp[0] = SPDM_VERSION_12; + keRsp[1] = SPDM_KEY_EXCHANGE_RSP; + SPDM_Set16LE(&keRsp[4], 0x0002); /* rspSessionId */ + wolfSPDM_GetRandom(ctx, &keRsp[8], 32); /* random */ + memcpy(&keRsp[40], atkPubX, WOLFSPDM_ECC_KEY_SIZE); + memcpy(&keRsp[88], atkPubY, WOLFSPDM_ECC_KEY_SIZE); + SPDM_Set16LE(&keRsp[136], 0); /* opaqueLen = 0 */ + /* Signature at [138]: fill with garbage (attacker can't sign with real key) */ + wolfSPDM_GetRandom(ctx, &keRsp[138], WOLFSPDM_ECC_SIG_SIZE); + /* VerifyData at [234]: garbage */ + memset(&keRsp[234], 0xAA, WOLFSPDM_HASH_SIZE); + + /* Parse should reject: signature doesn't match real responder's key */ + rc = wolfSPDM_ParseKeyExchangeRsp(ctx, keRsp, 282); + ASSERT_EQ(rc, WOLFSPDM_E_BAD_SIGNATURE, "MITM forged sig must be rejected"); + + wc_ecc_free(&realKey); + wc_ecc_free(&attackerKey); + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* Drive wolfSPDM_ParseKeyExchangeRsp past the signature check and exercise + * the ResponderVerifyData HMAC compare. The fixture reuses a single P-384 + * key pair as both requester and responder identity so the test can + * produce a signature that the parse path will accept; everything after + * (ECDH, KDF, HMAC) then runs on real inputs. */ +static int test_key_exchange_rsp_hmac_check(void) +{ + byte keRsp[300]; + const word32 keRspLen = 282; + const word32 keRspPartialLen = 138; + ecc_key ltKey; + ecc_key respEphem; + ecc_key ourPubKey; + byte ltPriv[48], ltPubX[48], ltPubY[48], ltPub[96]; + word32 ltPrivSz = 48, ltPubXSz = 48, ltPubYSz = 48; + byte respPubX[48], respPubY[48]; + word32 respPubXSz = 48, respPubYSz = 48; + byte ourPubX[48], ourPubY[48]; + word32 ourXSz = 48, ourYSz = 48; + byte sharedSecret[64]; + word32 sharedSz = sizeof(sharedSecret); + byte signMsg[160]; + word32 signMsgLen = 0; + byte th1SigHash[WOLFSPDM_HASH_SIZE]; + byte signMsgHash[WOLFSPDM_HASH_SIZE]; + byte th1[WOLFSPDM_HASH_SIZE]; + byte sigRaw[WOLFSPDM_ECC_SIG_SIZE]; + word32 sigRawSz = WOLFSPDM_ECC_SIG_SIZE; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + const char* ctxStr = "responder-key_exchange_rsp signing"; + const word32 ctxStrLen = 34; + word32 zeroPadLen; + int i, rc; + WOLFSPDM_CTX helperBuf; + WOLFSPDM_CTX* helper = &helperBuf; + TEST_CTX_SETUP_V12(); + + printf("test_key_exchange_rsp_hmac_check...\n"); + + /* Long-term P-384 key, shared between requester (for test signing) + * and responder (for parse verification) */ + ASSERT_SUCCESS(wc_ecc_init(<Key)); + ASSERT_SUCCESS(wc_ecc_make_key(&ctx->rng, 48, <Key)); + ASSERT_SUCCESS(wc_ecc_export_private_only(<Key, ltPriv, <PrivSz)); + ASSERT_SUCCESS(wc_ecc_export_public_raw(<Key, + ltPubX, <PubXSz, ltPubY, <PubYSz)); + if (ltPrivSz < 48) { + XMEMMOVE(ltPriv + (48 - ltPrivSz), ltPriv, ltPrivSz); + XMEMSET(ltPriv, 0, 48 - ltPrivSz); } - if (mldsa_verify_one(WC_ML_DSA_65, SPDM_PQC_ASYM_ALGO_ML_DSA_65, - WOLFSPDM_MLDSA65_SIG_SIZE) != 0) { - return -1; + if (ltPubXSz < 48) { + XMEMMOVE(ltPubX + (48 - ltPubXSz), ltPubX, ltPubXSz); + XMEMSET(ltPubX, 0, 48 - ltPubXSz); } - if (mldsa_verify_one(WC_ML_DSA_87, SPDM_PQC_ASYM_ALGO_ML_DSA_87, - WOLFSPDM_MLDSA87_SIG_SIZE) != 0) { - return -1; + if (ltPubYSz < 48) { + XMEMMOVE(ltPubY + (48 - ltPubYSz), ltPubY, ltPubYSz); + XMEMSET(ltPubY, 0, 48 - ltPubYSz); + } + XMEMCPY(ltPub, ltPubX, 48); + XMEMCPY(ltPub + 48, ltPubY, 48); + ASSERT_SUCCESS(wolfSPDM_SetRequesterKeyPair(ctx, ltPriv, 48, ltPub, 96)); + ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(ctx, ltPub, 96)); + + /* Our ephemeral ECDH key (requester side). Some wolfSSL builds + * (e.g. ECC_TIMING_RESISTANT) require an RNG on the ECDH private + * key for blinding; ensure one is attached for wc_ecc_shared_secret. */ + ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); + ASSERT_SUCCESS(wc_ecc_set_rng(&ctx->ephemeralKey, &ctx->rng)); + ASSERT_SUCCESS(wolfSPDM_ExportEphemeralPubKey(ctx, + ourPubX, &ourXSz, ourPubY, &ourYSz)); + + /* Responder ephemeral ECDH key (simulated responder side) */ + ASSERT_SUCCESS(wc_ecc_init(&respEphem)); + ASSERT_SUCCESS(wc_ecc_make_key(&ctx->rng, 48, &respEphem)); + ASSERT_SUCCESS(wc_ecc_set_rng(&respEphem, &ctx->rng)); + ASSERT_SUCCESS(wc_ecc_export_public_raw(&respEphem, + respPubX, &respPubXSz, respPubY, &respPubYSz)); + if (respPubXSz < 48) { + XMEMMOVE(respPubX + (48 - respPubXSz), respPubX, respPubXSz); + XMEMSET(respPubX, 0, 48 - respPubXSz); + } + if (respPubYSz < 48) { + XMEMMOVE(respPubY + (48 - respPubYSz), respPubY, respPubYSz); + XMEMSET(respPubY, 0, 48 - respPubYSz); + } + + /* Build partial KE_RSP (bytes 0..137) */ + XMEMSET(keRsp, 0, sizeof(keRsp)); + keRsp[0] = SPDM_VERSION_12; + keRsp[1] = SPDM_KEY_EXCHANGE_RSP; + SPDM_Set16LE(&keRsp[4], 0x1234); + XMEMSET(&keRsp[8], 0x5A, 32); + XMEMCPY(&keRsp[40], respPubX, 48); + XMEMCPY(&keRsp[88], respPubY, 48); + SPDM_Set16LE(&keRsp[136], 0); + + /* th1SigHash = Hash(transcript + partial KE_RSP); transcript starts empty */ + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(th1SigHash, + keRsp, keRspPartialLen, NULL, 0, NULL, 0)); + + /* Replicate wolfSPDM_BuildSignedHash for SPDM 1.2 over th1SigHash */ + signMsgLen = 0; + for (i = 0; i < 4; i++) { + XMEMCPY(&signMsg[signMsgLen], "dmtf-spdm-v1.2.*", 16); + signMsgLen += 16; + } + zeroPadLen = 36 - ctxStrLen; + XMEMSET(&signMsg[signMsgLen], 0, zeroPadLen); + signMsgLen += zeroPadLen; + XMEMCPY(&signMsg[signMsgLen], ctxStr, ctxStrLen); + signMsgLen += ctxStrLen; + XMEMCPY(&signMsg[signMsgLen], th1SigHash, WOLFSPDM_HASH_SIZE); + signMsgLen += WOLFSPDM_HASH_SIZE; + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(signMsgHash, + signMsg, signMsgLen, NULL, 0, NULL, 0)); + + /* Sign with long-term key; wolfSPDM_SignHash pads R||S to 96 bytes */ + sigRawSz = WOLFSPDM_ECC_SIG_SIZE; + ASSERT_SUCCESS(wolfSPDM_SignHash(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, + sigRaw, &sigRawSz)); + XMEMCPY(&keRsp[138], sigRaw, WOLFSPDM_ECC_SIG_SIZE); + + /* TH1 = Hash(partial || signature) */ + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(th1, + keRsp, keRspPartialLen + WOLFSPDM_ECC_SIG_SIZE, NULL, 0, NULL, 0)); + + /* Shared secret from responder ephemeral and our public key (mirrors + * ECDH(our_priv, resp_pub) that parse will compute on ctx) */ + ASSERT_SUCCESS(wc_ecc_init(&ourPubKey)); + ASSERT_SUCCESS(wc_ecc_import_unsigned(&ourPubKey, + ourPubX, ourPubY, NULL, ECC_SECP384R1)); + ASSERT_SUCCESS(wc_ecc_shared_secret(&respEphem, &ourPubKey, + sharedSecret, &sharedSz)); + wc_ecc_free(&ourPubKey); + if (sharedSz < 48) { + XMEMMOVE(sharedSecret + (48 - sharedSz), sharedSecret, sharedSz); + XMEMSET(sharedSecret, 0, 48 - sharedSz); } + sharedSz = 48; + + /* Derive rspFinishedKey via a throwaway helper ctx */ + ASSERT_SUCCESS(wolfSPDM_Init(helper)); + helper->spdmVersion = SPDM_VERSION_12; + XMEMCPY(helper->sharedSecret, sharedSecret, 48); + helper->sharedSecretSz = 48; + ASSERT_SUCCESS(wolfSPDM_DeriveHandshakeKeys(helper, th1)); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData( + helper->rspFinishedKey, th1, expectedHmac)); + wolfSPDM_Free(helper); + + /* Positive: valid HMAC must succeed and advance state to KEY_EX */ + XMEMCPY(&keRsp[234], expectedHmac, WOLFSPDM_HASH_SIZE); + rc = wolfSPDM_ParseKeyExchangeRsp(ctx, keRsp, keRspLen); + ASSERT_EQ(rc, WOLFSPDM_SUCCESS, "valid HMAC should succeed"); + ASSERT_EQ(ctx->state, WOLFSPDM_STATE_KEY_EX, + "state should advance to KEY_EX on valid parse"); + + /* Negative: a single bit flip in rspVerifyData must be rejected. + * Reset transcript/state only; keep ephemeral key so ECDH reproduces. */ + wolfSPDM_TranscriptReset(ctx); + ctx->state = WOLFSPDM_STATE_INIT; + keRsp[234] ^= 0x01; + rc = wolfSPDM_ParseKeyExchangeRsp(ctx, keRsp, keRspLen); + ASSERT_EQ(rc, WOLFSPDM_E_BAD_HMAC, + "flipped rspVerifyData byte must return BAD_HMAC"); + + wc_ecc_free(<Key); + wc_ecc_free(&respEphem); + TEST_CTX_FREE(); TEST_PASS(); } -#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ -/* The KEY_EXCHANGE_RSP / CHALLENGE_AUTH parsers derive the signature offset - * from wolfSPDM_GetSigSize(ctx). These confirm the ML-DSA SigLen (not the - * 96-byte ECDSA size) drives the buffer-bound check: a response only large - * enough for an ECDSA signature is rejected once ML-DSA is negotiated. */ -static int test_key_exchange_rsp_mldsa_sigsize(void) +/* Test Fix 4: Invalid curve point must be rejected by ComputeSharedSecret */ +static int test_invalid_curve_point(void) { - byte buf[300]; - TEST_CTX_SETUP(); + byte badX[WOLFSPDM_ECC_KEY_SIZE]; + byte badY[WOLFSPDM_ECC_KEY_SIZE]; + byte zeros[WOLFSPDM_ECC_KEY_SIZE]; + int rc; + TEST_CTX_SETUP_V12(); - printf("test_key_exchange_rsp_mldsa_sigsize...\n"); - ctx->spdmVersion = SPDM_VERSION_14; - ctx->asymType = WOLFSPDM_ASYM_MLDSA; - ctx->pqcAsymSel = SPDM_PQC_ASYM_ALGO_ML_DSA_65; - ASSERT_EQ(wc_MlDsaKey_Init(&ctx->responderPubKey.mldsa, NULL, INVALID_DEVID), - 0, "MlDsaKey_Init"); - ctx->flags.hasResponderPubKey = 1; + printf("test_invalid_curve_point...\n"); - XMEMSET(buf, 0, sizeof(buf)); - buf[0] = SPDM_VERSION_14; - buf[1] = SPDM_KEY_EXCHANGE_RSP; - /* buf[6] MutAuth = 0; opaqueLen at 136-137 = 0 -> sigOffset = 138. - * 282 fits an ECDSA sig (138+96+48) but not ML-DSA-65 (138+3309+48). */ - ASSERT_EQ(wolfSPDM_ParseKeyExchangeRsp(ctx, buf, 282), - WOLFSPDM_E_BUFFER_SMALL, "ML-DSA-65 KEY_EXCHANGE_RSP size guard"); + ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); + + /* Point (1, 1) is not on P-384 */ + memset(badX, 0, sizeof(badX)); + memset(badY, 0, sizeof(badY)); + badX[WOLFSPDM_ECC_KEY_SIZE - 1] = 0x01; + badY[WOLFSPDM_ECC_KEY_SIZE - 1] = 0x01; + + rc = wolfSPDM_ComputeSharedSecret(ctx, badX, badY); + ASSERT_EQ(rc, WOLFSPDM_E_CRYPTO_FAIL, "Off-curve point must be rejected"); + + /* Verify shared secret was zeroed on failure */ + memset(zeros, 0, sizeof(zeros)); + ASSERT_EQ(memcmp(ctx->sharedSecret, zeros, sizeof(ctx->sharedSecret)), 0, + "sharedSecret must be zeroed on failure"); + ASSERT_EQ(ctx->sharedSecretSz, 0, "sharedSecretSz must be 0 on failure"); TEST_CTX_FREE(); TEST_PASS(); } -#ifndef NO_WOLFSPDM_CHALLENGE -static int test_challenge_auth_mldsa_sigsize(void) +static int test_extract_ecc_point(void) { - byte buf[200]; - word32 sigOff = 0; - TEST_CTX_SETUP(); + byte point[WOLFSPDM_ECC_POINT_SIZE]; + const byte* pointX = NULL; + const byte* pointY = NULL; - printf("test_challenge_auth_mldsa_sigsize...\n"); - ctx->spdmVersion = SPDM_VERSION_12; - ctx->asymType = WOLFSPDM_ASYM_MLDSA; - ctx->pqcAsymSel = SPDM_PQC_ASYM_ALGO_ML_DSA_65; - ctx->challengeSlotId = 0; - ctx->challengeMeasHashType = SPDM_MEAS_SUMMARY_HASH_NONE; + printf("test_extract_ecc_point...\n"); - XMEMSET(buf, 0, sizeof(buf)); - buf[0] = SPDM_VERSION_12; - buf[1] = SPDM_CHALLENGE_AUTH; - buf[2] = 0; /* SlotID echo */ - XMEMSET(&buf[4], 0xCC, WOLFSPDM_HASH_SIZE); /* CertChainHash */ - XMEMSET(ctx->certChainHash, 0xCC, WOLFSPDM_HASH_SIZE); - /* Fixed tail ends at 4+48+32+2(opaqueLen=0) = 86; 182 fits an ECDSA sig - * but not ML-DSA-65 (86+3309), so the sig-room check must reject it. */ - ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, buf, 182, &sigOff), - WOLFSPDM_E_CHALLENGE, "ML-DSA-65 CHALLENGE_AUTH size guard"); + XMEMSET(point, 0xA5, sizeof(point)); + ASSERT_EQ(wolfSPDM_ExtractEccPoint(NULL, sizeof(point), &pointX, + &pointY), WOLFSPDM_E_INVALID_ARG, "NULL public key must fail"); + ASSERT_EQ(wolfSPDM_ExtractEccPoint(point, sizeof(point), NULL, + &pointY), WOLFSPDM_E_INVALID_ARG, "NULL X output must fail"); + ASSERT_EQ(wolfSPDM_ExtractEccPoint(point, sizeof(point), &pointX, + NULL), WOLFSPDM_E_INVALID_ARG, "NULL Y output must fail"); + ASSERT_EQ(wolfSPDM_ExtractEccPoint(point, sizeof(point) - 1, &pointX, + &pointY), WOLFSPDM_E_INVALID_ARG, "short point must fail"); + ASSERT_SUCCESS(wolfSPDM_ExtractEccPoint(point, sizeof(point), &pointX, + &pointY)); + TEST_ASSERT(pointX == point, "raw point X offset mismatch"); + TEST_ASSERT(pointY == point + WOLFSPDM_ECC_KEY_SIZE, + "raw point Y offset mismatch"); - TEST_CTX_FREE(); TEST_PASS(); } -#endif /* !NO_WOLFSPDM_CHALLENGE */ -#endif /* WOLFSPDM_HAVE_MLDSA */ - -#ifdef WOLFSPDM_HAVE_MLKEM -static int test_negotiate_algorithms_kem_build(void) -{ - byte buf[64]; - word32 bufSz; - TEST_CTX_SETUP(); - printf("test_negotiate_algorithms_kem_build...\n"); - - /* SPDM 1.4: a 5th KEMAlg struct (AlgType 0x07) at offset 48 advertises - * ML-KEM 512|768|1024 = 0x07, NumAlgoStructTables = 5, Length = 52. */ - ctx->spdmVersion = SPDM_VERSION_14; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, buf, &bufSz)); - ASSERT_EQ(bufSz, 52, "1.4 NEGOTIATE_ALGORITHMS with KEM is 52 bytes"); - ASSERT_EQ(buf[2], 0x05, "NumAlgoStructTables = 5"); - ASSERT_EQ(buf[48], SPDM_ALG_TYPE_KEM, "KEMAlg AlgType 0x07 at offset 48"); - ASSERT_EQ(buf[50], - (SPDM_KEM_ALGO_ML_KEM_512 | SPDM_KEM_ALGO_ML_KEM_768 | - SPDM_KEM_ALGO_ML_KEM_1024), - "KEMAlg advertises ML-KEM 512/768/1024"); - ASSERT_EQ(buf[32], SPDM_ALG_TYPE_DHE, "DHE struct still present (dual-stack)"); - - /* SPDM 1.2: no KEMAlg struct, message stays 48 bytes. */ - ctx->spdmVersion = SPDM_VERSION_12; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, buf, &bufSz)); - ASSERT_EQ(bufSz, 48, "1.2 NEGOTIATE_ALGORITHMS stays 48 bytes"); - ASSERT_EQ(buf[2], 0x04, "1.2 NumAlgoStructTables = 4"); - - /* KEM-only preference below 1.4 must fail rather than silently advertise - * DHE (no security downgrade of an explicit PQC-only request). */ - ASSERT_SUCCESS(wolfSPDM_SetKeyExchangePref(ctx, 0, SPDM_KEM_ALGO_ML_KEM_768)); - ctx->spdmVersion = SPDM_VERSION_12; - bufSz = sizeof(buf); - ASSERT_EQ(wolfSPDM_BuildNegotiateAlgorithms(ctx, buf, &bufSz), - WOLFSPDM_E_ALGO_MISMATCH, "KEM-only below 1.4 must not downgrade to DHE"); - /* At 1.4 the same preference advertises a KEM-only request: DHE struct - * dropped, so 4 structs (AEAD, ReqBaseAsym, KeySchedule, KEM) and no DHE. */ - ctx->spdmVersion = SPDM_VERSION_14; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, buf, &bufSz)); - ASSERT_EQ(buf[2], 0x04, "KEM-only: 4 structs (no DHE)"); - ASSERT_EQ(buf[32], SPDM_ALG_TYPE_AEAD, "KEM-only: DHE dropped, AEAD first"); - ASSERT_EQ(buf[44], SPDM_ALG_TYPE_KEM, "KEM-only: KEMAlg struct present"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -/* Build a 1.4 ALGORITHMS response with a 5th KEMAlg struct. dheSel is the DHE - * selection (0 = not selected) and kemSel the ML-KEM selection; the caller picks - * the mutual-exclusivity case. Returns total length (56). */ -static word32 build_algorithms_14_kem(byte* rsp, word16 dheSel, word16 kemSel) -{ - XMEMSET(rsp, 0, 56); - rsp[0] = SPDM_VERSION_14; - rsp[1] = SPDM_ALGORITHMS; - rsp[2] = 5; /* AlgStructCount = 5 */ - SPDM_Set16LE(&rsp[4], 56); - rsp[6] = 0x01; - rsp[7] = 0x02; - SPDM_Set32LE(&rsp[12], SPDM_ASYM_ALGO_ECDSA_P384); - rsp[16] = SPDM_HASH_ALGO_SHA_384; - rsp[36] = 2; rsp[37] = 0x20; SPDM_Set16LE(&rsp[38], dheSel); /* DHE */ - rsp[40] = 3; rsp[41] = 0x20; rsp[42] = 0x02; /* AEAD */ - rsp[44] = 4; rsp[45] = 0x20; rsp[46] = 0x0F; /* ReqBaseAsym */ - rsp[48] = 5; rsp[49] = 0x20; rsp[50] = 0x01; /* KeySchedule */ - rsp[52] = SPDM_ALG_TYPE_KEM; rsp[53] = 0x20; /* KEMAlg */ - SPDM_Set16LE(&rsp[54], kemSel); - return 56; -} - -static int test_parse_algorithms_kem_select(void) -{ - byte rsp[72]; - word32 len; - TEST_CTX_SETUP(); +#ifdef WOLFSPDM_TCG +#define SPDM_TEST_VENDOR_DEFINED_RSP 0x7E - printf("test_parse_algorithms_kem_select...\n"); +typedef struct TCG_GET_PUB_KEY_IO_CTX { + const byte* pubKey; + word32 pubKeySz; +} TCG_GET_PUB_KEY_IO_CTX; - /* Responder selects ML-KEM-768 (DHE = 0): kexType becomes MLKEM. */ - len = build_algorithms_14_kem(rsp, 0, SPDM_KEM_ALGO_ML_KEM_768); - ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, len)); - ASSERT_EQ(ctx->kexType, WOLFSPDM_KEX_MLKEM, "kexType MLKEM on KEM select"); - ASSERT_EQ(ctx->kemAlgSel, SPDM_KEM_ALGO_ML_KEM_768, "kemAlgSel = ML-KEM-768"); +static int tcg_get_pub_key_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, + word32 txSz, byte* rxBuf, word32* rxSz, void* userCtx) +{ + TCG_GET_PUB_KEY_IO_CTX* ioCtx = (TCG_GET_PUB_KEY_IO_CTX*)userCtx; + byte spdmRsp[WOLFSPDM_VENDOR_BUF_SZ]; + word32 totalSz; + int spdmRspSz; - /* Responder selects DHE (KEM = 0): kexType stays ECDHE. */ - len = build_algorithms_14_kem(rsp, SPDM_DHE_ALGO_SECP384R1, 0); - ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, len)); - ASSERT_EQ(ctx->kexType, WOLFSPDM_KEX_ECDHE, "kexType ECDHE on DHE select"); + (void)txBuf; + (void)txSz; - /* Both DHE and KEM selected: no hybrid in 1.4, must be rejected. */ - len = build_algorithms_14_kem(rsp, SPDM_DHE_ALGO_SECP384R1, - SPDM_KEM_ALGO_ML_KEM_768); - ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, len), - WOLFSPDM_E_ALGO_MISMATCH, "DHE+KEM (hybrid) must be rejected"); + if (ctx == NULL || rxBuf == NULL || rxSz == NULL || ioCtx == NULL) { + return -1; + } - /* Neither selected: must be rejected. */ - len = build_algorithms_14_kem(rsp, 0, 0); - ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, len), - WOLFSPDM_E_ALGO_MISMATCH, "no key-exchange method must be rejected"); + spdmRspSz = wolfSPDM_BuildVendorDefined(SPDM_VERSION_13, + WOLFSPDM_VDCODE_GET_PUBK, ioCtx->pubKey, ioCtx->pubKeySz, + spdmRsp, sizeof(spdmRsp)); + if (spdmRspSz < 0) { + return -1; + } + spdmRsp[1] = SPDM_TEST_VENDOR_DEFINED_RSP; - /* Unsupported KEM bit: must be rejected. */ - len = build_algorithms_14_kem(rsp, 0, 0x0008); - ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, len), - WOLFSPDM_E_ALGO_MISMATCH, "unsupported KEM must be rejected"); + totalSz = WOLFSPDM_TCG_HEADER_SIZE + (word32)spdmRspSz; + if (*rxSz < totalSz) { + return -1; + } + wolfSPDM_WriteTcgHeader(rxBuf, WOLFSPDM_TCG_TAG_CLEAR, totalSz, + ctx->connectionHandle, ctx->fipsIndicator); + XMEMCPY(rxBuf + WOLFSPDM_TCG_HEADER_SIZE, spdmRsp, + (word32)spdmRspSz); + *rxSz = totalSz; - TEST_CTX_FREE(); - TEST_PASS(); + return 0; } -static int test_mlkem_decapsulate(void) +static int test_tcg_get_pub_key_preserves_pin(void) { - byte ek[WOLFSPDM_MLKEM768_EK_SIZE]; - byte ct[WOLFSPDM_MLKEM768_CT_SIZE]; - byte ssResponder[WOLFSPDM_KEM_SS_SIZE]; - word32 ekSz = sizeof(ek); - MlKemKey rspKey; - int rspKeyInit = 0; + byte trustedKey[WOLFSPDM_ECC_POINT_SIZE]; + byte differentKey[WOLFSPDM_ECC_POINT_SIZE]; + byte tpmtPublic[120]; + byte pubKey[sizeof(tpmtPublic)]; + word32 pubKeySz; int rc; + TCG_GET_PUB_KEY_IO_CTX ioCtx; TEST_CTX_SETUP(); - printf("test_mlkem_decapsulate...\n"); - - /* Requester generates the ephemeral ML-KEM-768 key and exports ek. */ - ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; - ASSERT_SUCCESS(wolfSPDM_GenerateMlKemKey(ctx, ek, &ekSz)); - ASSERT_EQ(ekSz, WOLFSPDM_MLKEM768_EK_SIZE, "ML-KEM-768 ek is 1184 bytes"); - ASSERT_EQ(ctx->kexType, WOLFSPDM_KEX_MLKEM, "kexType set to MLKEM"); - - /* Responder side: import ek, encapsulate -> ciphertext c + shared secret K. */ - ASSERT_EQ(wc_MlKemKey_Init(&rspKey, WC_ML_KEM_768, NULL, INVALID_DEVID), 0, - "responder MlKemKey_Init"); - rspKeyInit = 1; - ASSERT_EQ(wc_MlKemKey_DecodePublicKey(&rspKey, ek, ekSz), 0, - "responder decode ek"); - ASSERT_EQ(wc_MlKemKey_Encapsulate(&rspKey, ct, ssResponder, &ctx->rng), 0, - "responder encapsulate"); - - /* Requester decapsulates c -> K'; K' must equal the responder's K. */ - rc = wolfSPDM_MlKemDecapsulate(ctx, ct, sizeof(ct)); - ASSERT_SUCCESS(rc); - ASSERT_EQ(ctx->sharedSecretSz, WOLFSPDM_KEM_SS_SIZE, - "decapsulated secret is 32 bytes"); - ASSERT_EQ(XMEMCMP(ctx->sharedSecret, ssResponder, WOLFSPDM_KEM_SS_SIZE), 0, - "K' (decapsulation) equals K (encapsulation)"); + printf("test_tcg_get_pub_key_preserves_pin...\n"); + + XMEMSET(trustedKey, 0xA5, sizeof(trustedKey)); + XMEMSET(differentKey, 0x5A, sizeof(differentKey)); + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, tcg_get_pub_key_io_cb, &ioCtx)); + ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(ctx, trustedKey, + sizeof(trustedKey))); + + ioCtx.pubKey = differentKey; + ioCtx.pubKeySz = sizeof(differentKey); + XMEMSET(pubKey, 0xCC, sizeof(pubKey)); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, + "mismatched responder key must be rejected"); + ASSERT_EQ(ctx->rspPubKeyLen, sizeof(trustedKey), + "configured key length changed"); + TEST_ASSERT(XMEMCMP(ctx->rspPubKey, trustedKey, sizeof(trustedKey)) == 0, + "configured responder key changed"); + + ioCtx.pubKey = trustedKey; + ioCtx.pubKeySz = sizeof(trustedKey); + pubKeySz = sizeof(pubKey); + ASSERT_SUCCESS(wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz)); + ASSERT_EQ(pubKeySz, sizeof(trustedKey), "raw key size mismatch"); + TEST_ASSERT(XMEMCMP(pubKey, trustedKey, sizeof(trustedKey)) == 0, + "raw key output mismatch"); + + XMEMSET(tpmtPublic, 0, sizeof(tpmtPublic)); + SPDM_Set16BE(tpmtPublic + 20, WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(tpmtPublic + 22, trustedKey, WOLFSPDM_ECC_KEY_SIZE); + SPDM_Set16BE(tpmtPublic + 70, WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(tpmtPublic + 72, trustedKey + WOLFSPDM_ECC_KEY_SIZE, + WOLFSPDM_ECC_KEY_SIZE); + ioCtx.pubKey = tpmtPublic; + ioCtx.pubKeySz = sizeof(tpmtPublic); + pubKeySz = sizeof(pubKey); + ASSERT_SUCCESS(wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz)); + ASSERT_EQ(pubKeySz, sizeof(tpmtPublic), "TPMT_PUBLIC size mismatch"); + TEST_ASSERT(XMEMCMP(pubKey, tpmtPublic, sizeof(tpmtPublic)) == 0, + "TPMT_PUBLIC output mismatch"); + TEST_ASSERT(XMEMCMP(ctx->rspPubKey, trustedKey, sizeof(trustedKey)) == 0, + "TPMT_PUBLIC response replaced configured key"); + + SPDM_Set16BE(tpmtPublic + 20, WOLFSPDM_ECC_KEY_SIZE - 1); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, + "malformed TPMT_PUBLIC X size must be rejected"); + + SPDM_Set16BE(tpmtPublic + 20, WOLFSPDM_ECC_KEY_SIZE); + SPDM_Set16BE(tpmtPublic + 70, WOLFSPDM_ECC_KEY_SIZE - 1); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, + "malformed TPMT_PUBLIC Y size must be rejected"); + + ioCtx.pubKeySz = WOLFSPDM_ECC_POINT_SIZE + 1; + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, + "truncated TPMT_PUBLIC point must be rejected"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_tcg_get_pub_key_discovery_is_not_trusted(void) +{ + byte discoveredKey[WOLFSPDM_ECC_POINT_SIZE]; + byte pubKey[WOLFSPDM_ECC_POINT_SIZE]; + word32 pubKeySz = sizeof(pubKey); + TCG_GET_PUB_KEY_IO_CTX ioCtx; + TEST_CTX_SETUP(); + + printf("test_tcg_get_pub_key_discovery_is_not_trusted...\n"); + + XMEMSET(discoveredKey, 0x3C, sizeof(discoveredKey)); + ioCtx.pubKey = discoveredKey; + ioCtx.pubKeySz = sizeof(discoveredKey); + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, tcg_get_pub_key_io_cb, &ioCtx)); + ASSERT_SUCCESS(wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz)); + TEST_ASSERT(XMEMCMP(pubKey, discoveredKey, sizeof(discoveredKey)) == 0, + "discovered key output mismatch"); + ASSERT_EQ(ctx->flags.hasRspPubKey, 0, + "cleartext discovery must not configure trust"); + ASSERT_EQ(ctx->rspPubKeyLen, 0, + "cleartext discovery must not store responder key"); - if (rspKeyInit) { - wc_MlKemKey_Free(&rspKey); - } TEST_CTX_FREE(); TEST_PASS(); } -/* Reconnect on a reused context switching key-exchange method must free the - * prior ephemeral key while ctx->kexType still names its union member (no - * type-confused free). Run under valgrind in CI to catch a mismatched free. */ -static int test_kex_reconnect_method_switch(void) +static int test_tcg_connect_requires_responder_key(void) { - byte ek[WOLFSPDM_MLKEM768_EK_SIZE]; - byte rsp[72]; - word32 ekSz = sizeof(ek); - word32 len; + int rc; TEST_CTX_SETUP(); - printf("test_kex_reconnect_method_switch...\n"); - ctx->spdmVersion = SPDM_VERSION_14; + printf("test_tcg_connect_requires_responder_key...\n"); - /* Round 1 leaves a live ML-KEM ephemeral key. */ - ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; - ASSERT_SUCCESS(wolfSPDM_GenerateMlKemKey(ctx, ek, &ekSz)); - ASSERT_EQ(ctx->flags.ephemeralKeyInit, 1, "ML-KEM key live"); - ASSERT_EQ(ctx->kexType, WOLFSPDM_KEX_MLKEM, "kexType MLKEM"); - - /* Reconnect negotiates ECDHE: ParseAlgorithms frees the live ML-KEM key - * (still matching kexType) before flipping to ECDHE. */ - len = build_algorithms_14_kem(rsp, SPDM_DHE_ALGO_SECP384R1, 0); - ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, len)); - ASSERT_EQ(ctx->kexType, WOLFSPDM_KEX_ECDHE, "switched to ECDHE"); - ASSERT_EQ(ctx->flags.ephemeralKeyInit, 0, "stale ML-KEM key freed"); - - /* And the reverse: a live ECDHE key, then a reconnect negotiating ML-KEM. */ - ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); - ASSERT_EQ(ctx->flags.ephemeralKeyInit, 1, "ECDHE key live"); - len = build_algorithms_14_kem(rsp, 0, SPDM_KEM_ALGO_ML_KEM_768); - ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, len)); - ASSERT_EQ(ctx->kexType, WOLFSPDM_KEX_MLKEM, "switched to ML-KEM"); - ASSERT_EQ(ctx->flags.ephemeralKeyInit, 0, "stale ECDHE key freed"); + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, dummy_io_cb, NULL)); + rc = wolfSPDM_ConnectTCG(ctx); + ASSERT_EQ(rc, WOLFSPDM_E_BAD_STATE, + "identity connection must require a responder key"); TEST_CTX_FREE(); TEST_PASS(); } -/* KEY_EXCHANGE with ML-KEM places the encapsulation key ek as ExchangeData at - * offset 40; confirm it decodes as a valid ML-KEM-768 public key. */ -static int test_build_key_exchange_mlkem(void) +/* I/O callback that returns a TCG response with msgSize < TCG_HEADER_SIZE */ +static int tcg_underflow_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, void* userCtx) +{ + (void)ctx; (void)txBuf; (void)txSz; (void)userCtx; + /* Return a 20-byte TCG response with msgSize field = 5 (< 16) */ + if (*rxSz < 20) return -1; + memset(rxBuf, 0, 20); + SPDM_Set16BE(rxBuf, 0x8101); /* tag: clear SPDM */ + SPDM_Set32BE(rxBuf + 2, 5); /* msgSize = 5 (underflow!) */ + *rxSz = 20; + return 0; +} + +static int test_tcg_underflow(void) { - byte buf[WOLFSPDM_KEX_REQ_BUF]; - word32 bufSz = sizeof(buf); - MlKemKey check; - int checkInit = 0; + byte txBuf[32]; + byte rxBuf[32]; + word32 rxSz = sizeof(rxBuf); + int rc; TEST_CTX_SETUP(); - printf("test_build_key_exchange_mlkem...\n"); - ctx->spdmVersion = SPDM_VERSION_14; - ctx->kexType = WOLFSPDM_KEX_MLKEM; - ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; + printf("test_tcg_underflow...\n"); + + /* wolfSPDM_SetMode is vendor-gated; set field directly so the TCG path + * runs in vendor-neutral builds too. */ +#ifdef WOLFSPDM_NUVOTON + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NUVOTON); +#elif defined(WOLFSPDM_NATIONS) + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS); +#else + ctx->mode = WOLFSPDM_MODE_NUVOTON; +#endif + wolfSPDM_SetIO(ctx, tcg_underflow_io_cb, NULL); + + txBuf[0] = 0x10; + txBuf[1] = SPDM_GET_VERSION; + txBuf[2] = 0x00; + txBuf[3] = 0x00; + + rc = wolfSPDM_SendReceive(ctx, txBuf, 4, rxBuf, &rxSz); + ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, + "msgSize < 16 must return BUFFER_SMALL"); - ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); - ASSERT_EQ(buf[1], SPDM_KEY_EXCHANGE, "KEY_EXCHANGE code"); - /* offset 40 = 4 hdr + 2 sessionId + 2 policy/rsvd + 32 random. */ - ASSERT_EQ(wc_MlKemKey_Init(&check, WC_ML_KEM_768, NULL, INVALID_DEVID), 0, - "MlKemKey_Init"); - checkInit = 1; - ASSERT_EQ(wc_MlKemKey_DecodePublicKey(&check, &buf[40], - WOLFSPDM_MLKEM768_EK_SIZE), 0, "ek decodes at offset 40"); - /* 40 fixed + 1184 ek + 22 OpaqueData block. */ - ASSERT_EQ(bufSz, 40u + WOLFSPDM_MLKEM768_EK_SIZE + 22u, - "ML-KEM KEY_EXCHANGE total size"); - - if (checkInit) { - wc_MlKemKey_Free(&check); - } TEST_CTX_FREE(); TEST_PASS(); } +#endif /* WOLFSPDM_TCG */ -/* An ML-KEM KEY_EXCHANGE request larger than the responder's DataTransferSize - * must fail fast (no CHUNK_SEND), not emit a non-conformant oversized message. */ -static int test_key_exchange_mlkem_exceeds_dts(void) +#ifdef WOLFSPDM_NATIONS +static int test_nations_mode(void) { + int rc; TEST_CTX_SETUP(); - printf("test_key_exchange_mlkem_exceeds_dts...\n"); - ctx->spdmVersion = SPDM_VERSION_14; - ctx->kexType = WOLFSPDM_KEX_MLKEM; - ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_1024; /* ek 1568 -> request ~1630 B */ - ctx->flags.hasResponderPubKey = 1; /* pass the precondition */ - ctx->dataTransferSize = 512; /* smaller than the request */ + printf("test_nations_mode...\n"); + + /* Test Nations mode can be set */ + rc = wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS); + ASSERT_SUCCESS(rc); + ASSERT_EQ(wolfSPDM_GetMode(ctx), WOLFSPDM_MODE_NATIONS, + "Mode should be NATIONS"); + + /* Verify TCG fields initialized */ + ASSERT_EQ(wolfSPDM_GetConnectionHandle(ctx), 0, + "connectionHandle should be 0"); + ASSERT_EQ(wolfSPDM_GetFipsIndicator(ctx), WOLFSPDM_FIPS_NON_FIPS, + "fipsIndicator should be NON_FIPS"); - ASSERT_EQ(wolfSPDM_KeyExchange(ctx), WOLFSPDM_E_BUFFER_SMALL, - "oversized ML-KEM KEY_EXCHANGE rejected before send"); + /* Test Nations PSK mode can be set */ + rc = wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS_PSK); + ASSERT_SUCCESS(rc); + ASSERT_EQ(wolfSPDM_GetMode(ctx), WOLFSPDM_MODE_NATIONS_PSK, + "Mode should be NATIONS_PSK"); TEST_CTX_FREE(); TEST_PASS(); } -/* KEY_EXCHANGE_RSP parsing for ML-KEM must locate OpaqueData/signature after a - * ciphertext-sized ExchangeData, not the 96-byte ECDHE point. A buffer that - * stops between the two offsets distinguishes them. */ -static int test_parse_key_exchange_rsp_mlkem_offset(void) +static int test_nations_psk_set(void) { - byte ek[WOLFSPDM_MLKEM768_EK_SIZE]; - byte rsp[1100]; - word32 ekSz = sizeof(ek); + int rc; + byte psk[48]; + byte hint[] = "test_hint"; TEST_CTX_SETUP(); - printf("test_parse_key_exchange_rsp_mlkem_offset...\n"); - ctx->spdmVersion = SPDM_VERSION_14; - ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; - ASSERT_SUCCESS(wolfSPDM_GenerateMlKemKey(ctx, ek, &ekSz)); - ctx->flags.hasResponderPubKey = 1; + printf("test_nations_psk_set...\n"); - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_14; - rsp[1] = SPDM_KEY_EXCHANGE_RSP; - rsp[6] = 0; /* no mutual auth */ + memset(psk, 0xAB, sizeof(psk)); - /* With the ML-KEM-768 ciphertext (1088), OpaqueLength sits at offset - * 40+1088 = 1128, so a 1000-byte buffer fails the length check. If the parse - * wrongly used the 96-byte ECDHE size (OpaqueLength at 136) it would read - * past 1000 instead of returning here. */ - ASSERT_EQ(wolfSPDM_ParseKeyExchangeRsp(ctx, rsp, 1000), - WOLFSPDM_E_BUFFER_SMALL, - "ML-KEM RSP uses ciphertext-sized ExchangeData offset"); + /* NULL args */ + rc = wolfSPDM_SetPSK(NULL, psk, sizeof(psk), NULL, 0); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "NULL ctx should fail"); + rc = wolfSPDM_SetPSK(ctx, NULL, sizeof(psk), NULL, 0); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "NULL psk should fail"); + rc = wolfSPDM_SetPSK(ctx, psk, 0, NULL, 0); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "Zero pskSz should fail"); + + /* Valid PSK without hint */ + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0); + ASSERT_SUCCESS(rc); + ASSERT_EQ(ctx->pskSz, sizeof(psk), "pskSz should be 48"); + ASSERT_EQ(ctx->pskHintSz, 0, "hintSz should be 0"); + + /* Valid PSK with hint */ + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), hint, sizeof(hint) - 1); + ASSERT_SUCCESS(rc); + ASSERT_EQ(ctx->pskHintSz, sizeof(hint) - 1, "hintSz mismatch"); TEST_CTX_FREE(); TEST_PASS(); } -/* Error/guard paths: the public preference API and the ML-KEM helpers must - * fail closed on invalid input and bad state. */ -static int test_mlkem_error_paths(void) +static int test_nations_psk_kdf(void) { - byte ek[WOLFSPDM_MLKEM768_EK_SIZE]; - byte small[64]; - byte req[WOLFSPDM_KEX_REQ_BUF]; - byte ct[WOLFSPDM_MLKEM768_CT_SIZE]; - word32 sz; - TEST_CTX_SETUP(); - - printf("test_mlkem_error_paths...\n"); - ctx->spdmVersion = SPDM_VERSION_14; - - /* wolfSPDM_SetKeyExchangePref validation. */ - ASSERT_EQ(wolfSPDM_SetKeyExchangePref(NULL, 1, 0), WOLFSPDM_E_INVALID_ARG, - "SetKeyExchangePref NULL ctx"); - ASSERT_EQ(wolfSPDM_SetKeyExchangePref(ctx, 0, 0), WOLFSPDM_E_INVALID_ARG, - "SetKeyExchangePref no methods"); - ASSERT_EQ(wolfSPDM_SetKeyExchangePref(ctx, 0, 0x0008), - WOLFSPDM_E_INVALID_ARG, "SetKeyExchangePref undefined KEM bit"); - ASSERT_SUCCESS(wolfSPDM_SetKeyExchangePref(ctx, 1, - SPDM_KEM_ALGO_ML_KEM_768)); - - /* GenerateMlKemKey: NULL, unknown KEM selection, ek output too small. */ - sz = sizeof(ek); - ASSERT_EQ(wolfSPDM_GenerateMlKemKey(NULL, ek, &sz), WOLFSPDM_E_INVALID_ARG, - "GenerateMlKemKey NULL ctx"); - ctx->kemAlgSel = 0; /* not a valid ML-KEM selection */ - sz = sizeof(ek); - ASSERT_EQ(wolfSPDM_GenerateMlKemKey(ctx, ek, &sz), WOLFSPDM_E_ALGO_MISMATCH, - "GenerateMlKemKey unknown KEM set"); - ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; - sz = sizeof(small); /* 64 < 1184 */ - ASSERT_EQ(wolfSPDM_GenerateMlKemKey(ctx, small, &sz), - WOLFSPDM_E_BUFFER_SMALL, "GenerateMlKemKey ek buffer too small"); - - /* MlKemDecapsulate: NULL, and no live ephemeral key / wrong kexType. */ - ASSERT_EQ(wolfSPDM_MlKemDecapsulate(ctx, NULL, 0), WOLFSPDM_E_INVALID_ARG, - "MlKemDecapsulate NULL ct"); - ctx->flags.ephemeralKeyInit = 0; - ctx->kexType = WOLFSPDM_KEX_MLKEM; - ASSERT_EQ(wolfSPDM_MlKemDecapsulate(ctx, ct, sizeof(ct)), - WOLFSPDM_E_BAD_STATE, "MlKemDecapsulate no live key"); - - /* BuildKeyExchange: unrecognized kexType fails closed; ML-KEM request that - * does not fit the caller buffer is rejected. */ - ctx->kexType = (byte)0xEE; - sz = sizeof(req); - ASSERT_EQ(wolfSPDM_BuildKeyExchange(ctx, req, &sz), WOLFSPDM_E_BAD_STATE, - "BuildKeyExchange unknown kexType"); - ctx->kexType = WOLFSPDM_KEX_MLKEM; - ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; - sz = 500; /* >= 180 arg check, < 40 + 1184 ek + 22 */ - ASSERT_EQ(wolfSPDM_BuildKeyExchange(ctx, req, &sz), WOLFSPDM_E_BUFFER_SMALL, - "BuildKeyExchange ML-KEM request exceeds buffer"); - - TEST_CTX_FREE(); - TEST_PASS(); -} -#endif /* WOLFSPDM_HAVE_MLKEM */ - -#ifdef WOLFSPDM_HAVE_CHUNK -#define CHUNK_TEST_MAX 4627 /* ML-DSA-87 SigLen — largest we reassemble */ -#define CHUNK_NONE 0xFFFFFFFFu -static byte g_chunkLarge[CHUNK_TEST_MAX]; -static word32 g_chunkTotal; -static word32 g_chunkPer; -/* Adversarial-injection knobs (CHUNK_NONE = off) for malformed-responder tests */ -static word32 g_chunkBadSizeSeq; /* inject oversized ChunkSize at this seq */ -static word32 g_chunkErrSeq; /* return SPDM_ERROR at this seq */ -static int g_chunkBadHandle; /* echo a wrong Handle on chunk 0 */ -static int g_chunkNeverLast; /* never set LastChunk (MAX_CHUNKS guard) */ -static int g_chunkShortLast; /* LastChunk on chunk 0 but off < total */ -static int g_chunkTrigger; /* answer a non-CHUNK_GET with ERROR(LargeResp) */ -static int g_chunkSecured; /* encrypt each CHUNK_RESPONSE (secured path) */ -static word32 g_chunkSecuredSeq; /* mock-side seq counter for the secured path */ -static int g_chunkSecTrigger; /* first secured reply is ERROR(LargeResponse) */ -static int g_chunkSecTrigDone; /* the trigger ERROR has been delivered */ - -/* Mock I/O: answer each CHUNK_GET(seq) with the matching synthetic - * CHUNK_RESPONSE chunk of g_chunkLarge (version-correct layout), splitting - * g_chunkTotal bytes into g_chunkPer-sized chunks. Honors the g_chunk* knobs. */ -static int chunk_mock_io(WOLFSPDM_CTX* ctx, const byte* tx, word32 txSz, - byte* rx, word32* rxSz, void* user) -{ - byte resp[CHUNK_TEST_MAX + 16]; - word32 seq, off, csz, dataOff, respSz; - word32 savedReq; - byte handle; - int rc; - (void)txSz; (void)user; - - if (g_chunkSecured) { - /* tx is an encrypted CHUNK_GET; drive seq from a local counter rather - * than decoding it, and echo the fixed handle. */ - if (g_chunkSecTrigger && !g_chunkSecTrigDone) { - /* First secured reply is an encrypted ERROR(LargeResponse) so the - * SecuredExchange transparent hook fires. */ - g_chunkSecTrigDone = 1; - resp[0] = (ctx != NULL) ? ctx->spdmVersion : SPDM_VERSION_14; - resp[1] = SPDM_ERROR; - resp[2] = SPDM_ERROR_LARGE_RESPONSE; - resp[3] = 0; - resp[4] = 0x42; /* ExtendedErrorData: Handle */ - savedReq = ctx->reqSeqNum; - ctx->reqSeqNum = ctx->rspSeqNum; - rc = wolfSPDM_EncryptInternal(ctx, resp, 5, rx, rxSz); - ctx->reqSeqNum = savedReq; - return (rc == WOLFSPDM_SUCCESS) ? 0 : -1; - } - seq = g_chunkSecuredSeq++; - handle = 0x42; - } - else { - if (tx[1] != SPDM_CHUNK_GET) { - /* Trigger path: an arbitrary request gets ERROR(LargeResponse) so - * the SendReceive hook drives reassembly. */ - if (g_chunkTrigger) { - rx[0] = SPDM_VERSION_14; - rx[1] = SPDM_ERROR; - rx[2] = SPDM_ERROR_LARGE_RESPONSE; - rx[3] = 0; - rx[4] = 0x42; /* ExtendedErrorData: Handle */ - *rxSz = 5; - return 0; - } - return -1; - } - handle = tx[3]; - seq = (ctx != NULL && ctx->spdmVersion >= SPDM_VERSION_14) - ? SPDM_Get32LE(&tx[4]) - : (word32)SPDM_Get16LE(&tx[4]); - if (seq == g_chunkErrSeq) { - rx[0] = SPDM_VERSION_14; - rx[1] = SPDM_ERROR; - rx[2] = SPDM_ERROR_UNSPECIFIED; - rx[3] = 0; - *rxSz = 4; - return 0; - } - } - - off = seq * g_chunkPer; - csz = g_chunkTotal - off; - if (csz > g_chunkPer) { - csz = g_chunkPer; - } - resp[0] = (ctx != NULL) ? ctx->spdmVersion : SPDM_VERSION_14; - resp[1] = SPDM_CHUNK_RESPONSE; - resp[2] = (off + csz >= g_chunkTotal) ? SPDM_CHUNK_LAST_CHUNK : 0; - if (g_chunkNeverLast) { - resp[2] = 0; /* force the MAX_CHUNKS guard */ - } - if (g_chunkShortLast && seq == 0) { - resp[2] = SPDM_CHUNK_LAST_CHUNK; /* claim last while off < total */ - } - resp[3] = (g_chunkBadHandle && seq == 0) ? (byte)(handle ^ 0xFF) : handle; - SPDM_Set32LE(&resp[4], seq); - SPDM_Set32LE(&resp[8], (seq == g_chunkBadSizeSeq) ? 0xFFFFFFF8u : csz); - if (seq == 0) { - SPDM_Set32LE(&resp[12], g_chunkTotal); - dataOff = 16; - } - else { - dataOff = 12; - } - XMEMCPY(&resp[dataOff], &g_chunkLarge[off], csz); - respSz = dataOff + csz; - - if (g_chunkSecured) { - /* Encrypt the CHUNK_RESPONSE so DecryptInternal round-trips it. The test - * installs symmetric req/rsp keys, so encrypting at the seq the decrypt - * side expects (rspSeqNum) yields a record it accepts; restore the req - * counter the secured transport advanced on its CHUNK_GET. */ - savedReq = ctx->reqSeqNum; - ctx->reqSeqNum = ctx->rspSeqNum; - rc = wolfSPDM_EncryptInternal(ctx, resp, respSz, rx, rxSz); - ctx->reqSeqNum = savedReq; - return (rc == WOLFSPDM_SUCCESS) ? 0 : -1; - } - XMEMCPY(rx, resp, respSz); - *rxSz = respSz; - return 0; -} - -/* Reset the mock to well-formed behavior. */ -static void chunk_mock_reset(word32 total, word32 per) -{ - word32 i; - g_chunkTotal = total; - g_chunkPer = per; - g_chunkBadSizeSeq = CHUNK_NONE; - g_chunkErrSeq = CHUNK_NONE; - g_chunkBadHandle = 0; - g_chunkNeverLast = 0; - g_chunkShortLast = 0; - g_chunkTrigger = 0; - g_chunkSecured = 0; - g_chunkSecuredSeq = 0; - g_chunkSecTrigger = 0; - g_chunkSecTrigDone = 0; - for (i = 0; i < total; i++) { - g_chunkLarge[i] = (byte)((i * 7u + 1u) & 0xFF); - } -} - -/* Reassemble a `total`-byte message split into `per`-byte chunks and verify the - * bytes round-trip. Returns 0 on pass. */ -static int chunk_reassemble_one(WOLFSPDM_CTX* ctx, word32 total, word32 per) -{ - byte out[CHUNK_TEST_MAX]; - word32 outSz = 0; int rc; - - chunk_mock_reset(total, per); - rc = wolfSPDM_ReassembleLargeResponse(ctx, 0, 0x42, out, sizeof(out), &outSz); - if (rc != WOLFSPDM_SUCCESS || outSz != total || - XMEMCMP(out, g_chunkLarge, total) != 0) { - return -1; - } - return 0; -} - -static int test_chunk_reassemble(void) -{ - byte cg[8]; - byte small[64]; - byte out[CHUNK_TEST_MAX]; - byte req[4]; - word32 cgSz = sizeof(cg); - word32 outSz = 0; - word32 rsz; - TEST_CTX_SETUP(); - - printf("test_chunk_reassemble...\n"); - ctx->spdmVersion = SPDM_VERSION_14; - ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, chunk_mock_io, NULL)); - - /* CHUNK_GET wire format (1.4): 8 bytes, code 0x86, Param2=handle, u32 seq. */ - ASSERT_SUCCESS(wolfSPDM_BuildChunkGet(ctx, cg, &cgSz, 0x42, 3)); - ASSERT_EQ(cgSz, 8, "1.4 CHUNK_GET is 8 bytes"); - ASSERT_EQ(cg[1], SPDM_CHUNK_GET, "CHUNK_GET code 0x86"); - ASSERT_EQ(cg[3], 0x42, "Param2 = Handle"); - ASSERT_EQ(SPDM_Get32LE(&cg[4]), 3, "ChunkSeqNo u32"); - - /* Reassemble across realistic sizes (incl. ML-DSA 44/65/87 SigLens) and - * varied chunk sizes — exercises single-chunk, many-chunk, and a final - * short chunk. */ - ASSERT_EQ(chunk_reassemble_one(ctx, 350, 100), 0, "350 B / 100"); - ASSERT_EQ(chunk_reassemble_one(ctx, 100, 100), 0, "single chunk"); - ASSERT_EQ(chunk_reassemble_one(ctx, 2420, 1000), 0, "ML-DSA-44 SigLen"); - ASSERT_EQ(chunk_reassemble_one(ctx, 3309, 1000), 0, "ML-DSA-65 SigLen"); - ASSERT_EQ(chunk_reassemble_one(ctx, 4627, 1000), 0, "ML-DSA-87 SigLen"); - ASSERT_EQ(chunk_reassemble_one(ctx, 4627, 512), 0, "ML-DSA-87, small MTU"); - - /* SPDM < 1.4 uses a u16 ChunkSeqNo; the synthetic 1.4 layout is compatible - * (seq low bytes + zero reserved), so reassembly must still succeed. */ - ctx->spdmVersion = SPDM_VERSION_12; - ASSERT_EQ(chunk_reassemble_one(ctx, 2420, 1000), 0, "SPDM 1.2 u16 seqNo"); - ctx->spdmVersion = SPDM_VERSION_14; - - /* --- Adversarial responders (every CHUNK_RESPONSE byte is untrusted) --- */ - - /* Oversized ChunkSize (~UINT32_MAX) on the FIRST chunk — must be rejected - * with no copy (this is the integer-overflow case). */ - chunk_mock_reset(2420, 1000); - g_chunkBadSizeSeq = 0; - ASSERT_EQ(wolfSPDM_ReassembleLargeResponse(ctx, 0, 0x42, out, sizeof(out), - &outSz), WOLFSPDM_E_CHUNK, "oversized ChunkSize (seq 0) rejected"); - - /* Oversized ChunkSize on a LATER chunk (off > 0) — the overflow path that - * wraps off+chunkSize; must be rejected. */ - chunk_mock_reset(2420, 1000); - g_chunkBadSizeSeq = 1; - ASSERT_EQ(wolfSPDM_ReassembleLargeResponse(ctx, 0, 0x42, out, sizeof(out), - &outSz), WOLFSPDM_E_CHUNK, "oversized ChunkSize (seq>0) rejected"); - - /* Mismatched Handle echo. */ - chunk_mock_reset(2420, 1000); - g_chunkBadHandle = 1; - ASSERT_EQ(wolfSPDM_ReassembleLargeResponse(ctx, 0, 0x42, out, sizeof(out), - &outSz), WOLFSPDM_E_CHUNK, "wrong Handle rejected"); - - /* Mid-stream SPDM_ERROR surfaces as a peer error. */ - chunk_mock_reset(2420, 1000); - g_chunkErrSeq = 1; - ASSERT_EQ(wolfSPDM_ReassembleLargeResponse(ctx, 0, 0x42, out, sizeof(out), - &outSz), WOLFSPDM_E_PEER_ERROR, "mid-stream ERROR surfaced"); - - /* LargeMessageSize exceeds the output buffer. */ - chunk_mock_reset(CHUNK_TEST_MAX, 512); - ASSERT_EQ(wolfSPDM_ReassembleLargeResponse(ctx, 0, 0x42, small, - sizeof(small), &outSz), WOLFSPDM_E_BUFFER_SMALL, - "oversized message rejected"); - - /* Responder claims LastChunk before delivering the full message. */ - chunk_mock_reset(1000, 100); - g_chunkShortLast = 1; - ASSERT_EQ(wolfSPDM_ReassembleLargeResponse(ctx, 0, 0x42, out, sizeof(out), - &outSz), WOLFSPDM_E_CHUNK, "short final chunk rejected"); - - /* Never-ending stream hits the WOLFSPDM_CHUNK_MAX_CHUNKS guard. */ - chunk_mock_reset(CHUNK_TEST_MAX, 16); - g_chunkNeverLast = 1; - ASSERT_EQ(wolfSPDM_ReassembleLargeResponse(ctx, 0, 0x42, out, sizeof(out), - &outSz), WOLFSPDM_E_CHUNK, "max-chunks guard"); - - /* Transparent trigger: wolfSPDM_SendReceive sees ERROR(LargeResponse) and - * reassembles. Requires the peer to have negotiated CHUNK_CAP. */ - chunk_mock_reset(2420, 1000); - g_chunkTrigger = 1; - ctx->rspCaps |= SPDM_CAP_CHUNK_CAP; - req[0] = SPDM_VERSION_14; - req[1] = SPDM_GET_MEASUREMENTS; - req[2] = 0; - req[3] = 0; - rsz = sizeof(out); - ASSERT_SUCCESS(wolfSPDM_SendReceive(ctx, req, sizeof(req), out, &rsz)); - ASSERT_EQ(rsz, 2420, "SendReceive hook reassembled size"); - ASSERT_EQ(XMEMCMP(out, g_chunkLarge, 2420), 0, - "SendReceive hook reassembled bytes"); - - /* Same ERROR(LargeResponse) with CHUNK_CAP NOT negotiated: the hook must - * not fire; the raw ERROR is returned for the caller to handle. */ - chunk_mock_reset(2420, 1000); - g_chunkTrigger = 1; - ctx->rspCaps &= ~(word32)SPDM_CAP_CHUNK_CAP; - rsz = sizeof(out); - ASSERT_SUCCESS(wolfSPDM_SendReceive(ctx, req, sizeof(req), out, &rsz)); - ASSERT_EQ(out[1], SPDM_ERROR, "no CHUNK_CAP: raw ERROR returned"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -/* Drive the secured (in-session) reassembly path: each CHUNK_GET is encrypted - * and each CHUNK_RESPONSE decrypted through wolfSPDM_ChunkXferSecured. A loopback - * mock with symmetric req/rsp keys stands in for the responder. */ -static int test_chunk_reassemble_secured(void) -{ - byte out[CHUNK_TEST_MAX]; - byte cmd[4]; - word32 outSz = 0; - int i; + byte psk[48]; + byte th1[WOLFSPDM_HASH_SIZE]; + byte zeros[WOLFSPDM_PSK_MAX_SIZE]; TEST_CTX_SETUP_V12(); - printf("test_chunk_reassemble_secured...\n"); - ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, chunk_mock_io, NULL)); - ctx->state = WOLFSPDM_STATE_CONNECTED; - ctx->sessionId = 0xCAFEBABE; - for (i = 0; i < WOLFSPDM_AEAD_KEY_SIZE; i++) { - ctx->reqDataKey[i] = (byte)(i + 1); - ctx->rspDataKey[i] = (byte)(i + 1); - } - for (i = 0; i < WOLFSPDM_AEAD_IV_SIZE; i++) { - ctx->reqDataIv[i] = (byte)(0x40 + i); - ctx->rspDataIv[i] = (byte)(0x40 + i); - } - ctx->reqSeqNum = 0; - ctx->rspSeqNum = 0; - - chunk_mock_reset(3309, 800); /* ML-DSA-65 SigLen over several chunks */ - g_chunkSecured = 1; - -#ifndef WOLFSPDM_CHUNK_NO_SECURED - ASSERT_SUCCESS(wolfSPDM_ReassembleLargeResponse(ctx, 1, 0x42, out, - sizeof(out), &outSz)); - ASSERT_EQ(outSz, 3309, "secured reassembled size"); - ASSERT_EQ(XMEMCMP(out, g_chunkLarge, 3309), 0, "secured reassembled bytes"); + printf("test_nations_psk_kdf...\n"); - /* End-to-end through wolfSPDM_SecuredExchange: the decrypted reply is an - * ERROR(LargeResponse) and the transparent hook reassembles it (exercises - * the cap-capture and CHUNK_CAP-gate glue). */ - ctx->reqSeqNum = 0; - ctx->rspSeqNum = 0; - chunk_mock_reset(2420, 700); - g_chunkSecured = 1; - g_chunkSecTrigger = 1; - ctx->rspCaps |= SPDM_CAP_CHUNK_CAP; - cmd[0] = SPDM_VERSION_12; - cmd[1] = SPDM_GET_MEASUREMENTS; - cmd[2] = 0; - cmd[3] = 0; - outSz = sizeof(out); - ASSERT_SUCCESS(wolfSPDM_SecuredExchange(ctx, cmd, sizeof(cmd), out, &outSz)); - ASSERT_EQ(outSz, 2420, "SecuredExchange hook reassembled size"); - ASSERT_EQ(XMEMCMP(out, g_chunkLarge, 2420), 0, - "SecuredExchange hook reassembled bytes"); -#else - (void)cmd; - ASSERT_EQ(wolfSPDM_ReassembleLargeResponse(ctx, 1, 0x42, out, sizeof(out), - &outSz), WOLFSPDM_E_CHUNK, "secured path compiled out returns E_CHUNK"); -#endif - - TEST_CTX_FREE(); - TEST_PASS(); -} -#endif /* WOLFSPDM_HAVE_CHUNK */ + memset(psk, 0xCD, sizeof(psk)); + memset(th1, 0xEF, sizeof(th1)); + memset(zeros, 0, sizeof(zeros)); -static int test_build_get_digests(void) -{ - byte buf[16]; - word32 bufSz = sizeof(buf); - TEST_CTX_SETUP_V12(); + /* Set PSK */ + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0); + ASSERT_SUCCESS(rc); - printf("test_build_get_digests...\n"); - ASSERT_SUCCESS(wolfSPDM_BuildGetDigests(ctx, buf, &bufSz)); - ASSERT_EQ(bufSz, 4, "GET_DIGESTS should be 4 bytes"); - ASSERT_EQ(buf[1], SPDM_GET_DIGESTS, "Code should be 0x81"); + /* Derive handshake keys from PSK */ + rc = wolfSPDM_DeriveHandshakeKeysPsk(ctx, th1); + ASSERT_SUCCESS(rc); - TEST_CTX_FREE(); - TEST_PASS(); -} + /* Verify PSK was scrubbed */ + ASSERT_EQ(ctx->pskSz, 0, "pskSz should be 0 after derivation"); + ASSERT_EQ(memcmp(ctx->psk, zeros, WOLFSPDM_PSK_MAX_SIZE), 0, + "PSK not zeroed after derivation"); -static int test_build_get_certificate(void) -{ - byte buf[16]; - word32 bufSz = sizeof(buf); - TEST_CTX_SETUP_V12(); + /* Verify handshake secret was derived (non-zero) */ + ASSERT_NE(memcmp(ctx->handshakeSecret, zeros, sizeof(ctx->handshakeSecret)), 0, + "handshakeSecret should be non-zero"); - printf("test_build_get_certificate...\n"); - ASSERT_SUCCESS(wolfSPDM_BuildGetCertificate(ctx, buf, &bufSz, 0, 0, 1024)); - ASSERT_EQ(bufSz, 8, "GET_CERTIFICATE should be 8 bytes"); - ASSERT_EQ(buf[1], SPDM_GET_CERTIFICATE, "Code should be 0x82"); - ASSERT_EQ(buf[2], 0x00, "SlotID should be 0"); - TEST_ASSERT(buf[6] == 0x00 && buf[7] == 0x04, "Length should be 1024"); + /* Verify finished keys were derived (non-zero) */ + ASSERT_NE(memcmp(ctx->reqFinishedKey, zeros, sizeof(ctx->reqFinishedKey)), 0, + "reqFinishedKey should be non-zero"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_build_end_session(void) +static int test_nations_psk_message_format(void) { - byte buf[16]; + int rc; + byte psk[48]; + byte buf[128]; word32 bufSz = sizeof(buf); TEST_CTX_SETUP_V12(); - printf("test_build_end_session...\n"); - ASSERT_SUCCESS(wolfSPDM_BuildEndSession(ctx, buf, &bufSz)); - ASSERT_EQ(bufSz, 4, "END_SESSION should be 4 bytes"); - ASSERT_EQ(buf[1], SPDM_END_SESSION, "Code should be 0xEA"); + printf("test_nations_psk_message_format...\n"); - TEST_CTX_FREE(); - TEST_PASS(); -} - -static int test_parse_finish_rsp_14_opaque_length(void) -{ - /* SPDM 1.4 FINISH_RSP: header(4) + OpaqueLength(2 LE) + OpaqueData(var). - * Exercise both the happy path (correctly consuming the variable-length - * tail into the transcript) and the size-guard (truncated buffer must - * return BUFFER_SMALL). */ - byte rsp[64]; - word32 startTranscriptLen; - TEST_CTX_SETUP(); - - printf("test_parse_finish_rsp_14_opaque_length...\n"); + memset(psk, 0xAA, sizeof(psk)); + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0); + ASSERT_SUCCESS(rc); - ctx->spdmVersion = SPDM_VERSION_14; + /* Build PSK_EXCHANGE */ + rc = wolfSPDM_BuildPskExchange(ctx, buf, &bufSz); + ASSERT_SUCCESS(rc); - /* Empty OpaqueData (opaqueLen=0): rspMsgLen=6, fits in 4-byte header - * tail. The transcript should advance by 6 bytes. */ - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_14; - rsp[1] = SPDM_FINISH_RSP; - /* OpaqueLength = 0 at offset 4..5 (already zeroed). */ - startTranscriptLen = ctx->transcriptLen; - ASSERT_SUCCESS(wolfSPDM_ParseFinishRsp(ctx, rsp, 6)); - ASSERT_EQ(ctx->transcriptLen - startTranscriptLen, (word32)6, - "Transcript should grow by 6 (hdr+OpaqueLen) for empty opaque"); + /* Verify header */ + ASSERT_EQ(buf[0], SPDM_VERSION_12, "Version should be 0x12"); + ASSERT_EQ(buf[1], SPDM_PSK_EXCHANGE, "Code should be PSK_EXCHANGE"); - /* Non-zero OpaqueData (5 bytes) - rspMsgLen = 4+2+5 = 11. */ - ctx->transcriptLen = 0; - rsp[4] = 0x05; rsp[5] = 0x00; - rsp[6] = 'h'; rsp[7] = 'e'; rsp[8] = 'l'; rsp[9] = 'l'; rsp[10] = 'o'; - ctx->state = WOLFSPDM_STATE_FINISH; /* reset for re-parse */ - ASSERT_SUCCESS(wolfSPDM_ParseFinishRsp(ctx, rsp, 11)); - ASSERT_EQ(ctx->transcriptLen, (word32)11, - "Transcript should grow by hdr+OpaqueLen+OpaqueData = 11"); + /* ReqSessionID at offset 4-5 */ + ASSERT_EQ(SPDM_Get16LE(&buf[4]), ctx->reqSessionId, + "ReqSessionID mismatch"); - /* Truncated: 5 bytes (header + 1 byte of OpaqueLength) must fail. */ - ASSERT_EQ(wolfSPDM_ParseFinishRsp(ctx, rsp, 5), WOLFSPDM_E_BUFFER_SMALL, - "1.4 FINISH_RSP with truncated OpaqueLength must fail"); + /* PSKHintLength at offset 6-7 should be 0 (no hint) */ + ASSERT_EQ(SPDM_Get16LE(&buf[6]), 0, "PSKHintLen should be 0"); - /* Truncated: 9 bytes (claimed opaqueLen=5 but only 3 bytes follow). */ - ASSERT_EQ(wolfSPDM_ParseFinishRsp(ctx, rsp, 9), WOLFSPDM_E_BUFFER_SMALL, - "1.4 FINISH_RSP with truncated OpaqueData must fail"); + /* RequesterContextLength at offset 8-9 should be 32 */ + ASSERT_EQ(SPDM_Get16LE(&buf[8]), WOLFSPDM_RANDOM_SIZE, + "ReqCtxLen should be 32"); TEST_CTX_FREE(); TEST_PASS(); } +#endif /* WOLFSPDM_NATIONS */ -static int test_build_finish_opaque_length_14(void) +static int test_decrypt_overflow(void) { - /* SPDM 1.4 adds OpaqueLength(2) to FINISH at offset 4. Verify both the - * grown size requirement and that the field is actually written. */ - byte buf[128]; - byte tinyBuf[53]; - word32 bufSz; - word32 tinySz; - int i; + /* Static to avoid 4KB+ on stack; cipherLen must exceed + * sizeof(decrypted) = WOLFSPDM_MAX_MSG_SIZE + 16 = 4112 */ + static byte enc[4140]; + byte plain[64]; + word32 plainSz = sizeof(plain); + int rc; TEST_CTX_SETUP_V12(); - printf("test_build_finish_opaque_length_14...\n"); + printf("test_decrypt_overflow...\n"); - /* Populate reqFinishedKey so the HMAC step doesn't fault. The HMAC - * value itself isn't validated here - we're checking the header. */ - for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { - ctx->reqFinishedKey[i] = (byte)i; - } + ctx->sessionId = 0x00010001; + ctx->rspSeqNum = 0; + memset(ctx->rspDataKey, 0x42, sizeof(ctx->rspDataKey)); + memset(ctx->rspDataIv, 0x42, sizeof(ctx->rspDataIv)); - /* 1.4: header(4) + OpaqueLen(2) = 0x0000 + HMAC(48) = 54 bytes. */ - ctx->spdmVersion = SPDM_VERSION_14; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildFinish(ctx, buf, &bufSz)); - ASSERT_EQ(bufSz, 54, "1.4 FINISH should be 54 bytes"); - ASSERT_EQ(buf[1], SPDM_FINISH, "Code should be SPDM_FINISH"); - ASSERT_EQ(buf[4], 0x00, "OpaqueLength byte 0 should be 0"); - ASSERT_EQ(buf[5], 0x00, "OpaqueLength byte 1 should be 0"); + /* MCTP header: rspLen=4130 -> cipherLen=4114 > 4112 = overflow guard */ + memset(enc, 0, sizeof(enc)); + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set16LE(&enc[4], 0x0000); + SPDM_Set16LE(&enc[6], 4130); - /* 1.4 size guard: 53 bytes must be refused. */ - tinySz = sizeof(tinyBuf); - ASSERT_EQ(wolfSPDM_BuildFinish(ctx, tinyBuf, &tinySz), - WOLFSPDM_E_BUFFER_SMALL, - "1.4 FINISH should refuse 53 bytes (needs 54)"); + rc = wolfSPDM_DecryptInternal(ctx, enc, 4138, plain, &plainSz); + ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, "Overflow cipherLen must be caught"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_build_key_exchange_opaque_data(void) +static int test_oob_read_error(void) { - /* OpaqueData carries the SecuredMessage version negotiation. DSP0277 - * only defines versions 1.0, 1.1, 1.2, so the block is the same length - * regardless of the negotiated SPDM control version. */ - byte buf[256]; - word32 bufSz; - word16 opaqueLen; - word32 opaqueOffset = 4 + 2 + 1 + 1 + 32 + 96; /* hdr + ResSI + pol + res + RND + ExchData */ + byte shortErr[2] = {0x12, SPDM_ERROR}; + byte fullErr[4] = {0x12, SPDM_ERROR, 0x06, 0x00}; + int rc; TEST_CTX_SETUP_V12(); - printf("test_build_key_exchange_opaque_data...\n"); + printf("test_oob_read_error...\n"); - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); - opaqueLen = (word16)(buf[opaqueOffset] | ((word16)buf[opaqueOffset + 1] << 8)); - ASSERT_EQ(opaqueLen, 20, "OpaqueLength should be 20 bytes"); - /* SecuredMessageVersions block at offset+12: 0x10 0x00 0x11 0x00 0x12 0x00 */ - ASSERT_EQ(buf[opaqueOffset + 14], 0x10, "Version[0] should be 0x10 (1.0)"); - ASSERT_EQ(buf[opaqueOffset + 16], 0x11, "Version[1] should be 0x11 (1.1)"); - ASSERT_EQ(buf[opaqueOffset + 18], 0x12, "Version[2] should be 0x12 (1.2)"); - - /* SPDM 1.4 reuses the same 1.2 secured-message format. */ - ctx->spdmVersion = SPDM_VERSION_14; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); - opaqueLen = (word16)(buf[opaqueOffset] | ((word16)buf[opaqueOffset + 1] << 8)); - ASSERT_EQ(opaqueLen, 20, "1.4 OpaqueLength should still be 20"); + rc = wolfSPDM_ParseFinishRsp(ctx, fullErr, sizeof(fullErr)); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, "Should return peer error"); + + rc = wolfSPDM_ParseFinishRsp(ctx, shortErr, sizeof(shortErr)); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "Short buffer should fail"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_build_key_exchange_slot(void) +static int test_constant_time_hmac(void) { - /* When ConnectStandard selects a non-zero cert slot (DIGESTS SlotMask), - * KEY_EXCHANGE must authenticate that slot. Hard-coding 0 would ask - * the responder to sign with a different (possibly empty) slot's - * key than the chain the requester just fetched. */ - byte buf[256]; - word32 bufSz; - TEST_CTX_SETUP_V12(); - - printf("test_build_key_exchange_slot...\n"); + byte finishedKey[WOLFSPDM_HASH_SIZE]; + byte thHash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + byte fakeVerify[WOLFSPDM_HASH_SIZE]; + byte goodVerify[WOLFSPDM_HASH_SIZE]; + word32 i; + int diff; - ctx->currentSlotId = 2; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); - /* Header: ver, code, measSummary, slotIDParam */ - ASSERT_EQ(buf[3] & 0x0F, 2, "SlotIDParam should echo currentSlotId"); + printf("test_constant_time_hmac...\n"); - TEST_CTX_FREE(); - TEST_PASS(); -} + memset(finishedKey, 0xAB, sizeof(finishedKey)); + memset(thHash, 0xCD, sizeof(thHash)); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKey, thHash, verifyData)); -static int test_key_exchange_requires_cert(void) -{ - /* wolfSPDM_KeyExchange must refuse to proceed without an extracted - * responder public key (signature verification would otherwise be - * skipped, regressing MITM resistance to HMAC-only). */ - TEST_CTX_SETUP_V12(); + /* 1-byte difference must be detected */ + memcpy(fakeVerify, verifyData, sizeof(fakeVerify)); + fakeVerify[WOLFSPDM_HASH_SIZE - 1] ^= 0x01; - printf("test_key_exchange_requires_cert...\n"); + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ fakeVerify[i]; + ASSERT_NE(diff, 0, "Should detect 1-byte diff"); - /* hasResponderPubKey is 0 by default; KeyExchange must reject. */ - ASSERT_EQ(wolfSPDM_KeyExchange(ctx), WOLFSPDM_E_BAD_STATE, - "KeyExchange without cert must return BAD_STATE"); + /* Identical must pass */ + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKey, thHash, goodVerify)); + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ goodVerify[i]; + ASSERT_EQ(diff, 0, "Identical data should match"); - TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_key_exchange_rsp_mutual_auth_refused(void) +static int test_setdebug_truncation(void) { - /* Responder sets MutAuthRequested (offset 6) - the parser must refuse - * before committing ctx->sessionId so a rejected handshake doesn't - * leak partial state. */ - byte rsp[240]; /* full KEY_EXCHANGE_RSP minimum + sig + hmac */ - TEST_CTX_SETUP_V12(); - - printf("test_parse_key_exchange_rsp_mutual_auth_refused...\n"); + TEST_CTX_SETUP(); - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_12; - rsp[1] = SPDM_KEY_EXCHANGE_RSP; - rsp[6] = 0x01; /* MutAuthRequested - we don't support it */ - ASSERT_EQ(wc_ecc_init(&ctx->responderPubKey.ecc), 0, "ecc_init"); - ctx->flags.hasResponderPubKey = 1; - ctx->sessionId = 0; - ASSERT_EQ(wolfSPDM_ParseKeyExchangeRsp(ctx, rsp, sizeof(rsp)), - WOLFSPDM_E_KEY_EXCHANGE, "MutAuthRequested must be refused"); - ASSERT_EQ(ctx->sessionId, (word32)0, - "sessionId must not be committed on mutual-auth refusal"); + printf("test_setdebug_truncation...\n"); - TEST_CTX_FREE(); - TEST_PASS(); -} + wolfSPDM_SetDebug(ctx, 2); + ASSERT_EQ(ctx->flags.debug, 1, "debug=2 should be 1"); -static int test_parse_key_exchange_rsp_too_short(void) -{ - /* Verify ParseKeyExchangeRsp's size guard runs before the signature - * verification path. A truncated response (no room for the 96-byte - * signature) must be rejected up front rather than reaching the - * verifier with garbage bytes. The actual signature-verify failure - * path is exercised end-to-end by the integration tests against - * spdm-emu (a bad signature there returns WOLFSPDM_E_BAD_SIGNATURE). */ - byte rsp[140]; /* below the sigOffset(138) + sig(96) minimum */ - int rc; - TEST_CTX_SETUP_V12(); + wolfSPDM_SetDebug(ctx, 0); + ASSERT_EQ(ctx->flags.debug, 0, "debug=0 should be 0"); - printf("test_parse_key_exchange_rsp_too_short...\n"); - - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_12; - rsp[1] = SPDM_KEY_EXCHANGE_RSP; - /* Properly init the ecc_key the flag references; wolfSPDM_Free's - * wc_ecc_free should run against a wolfCrypt-initialized state, not - * a zeroed-but-never-initialized struct. */ - ASSERT_EQ(wc_ecc_init(&ctx->responderPubKey.ecc), 0, "ecc_init"); - ctx->flags.hasResponderPubKey = 1; - rc = wolfSPDM_ParseKeyExchangeRsp(ctx, rsp, sizeof(rsp)); - ASSERT_NE(rc, WOLFSPDM_SUCCESS, - "Truncated KEY_EXCHANGE_RSP must not succeed"); + wolfSPDM_SetDebug(ctx, 255); + ASSERT_EQ(ctx->flags.debug, 1, "debug=255 should be 1"); TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* Error Check Tests */ -/* ========================================================================== */ - -static int test_check_error(void) +static int test_key_zeroing(void) { - byte errorMsg[] = {0x12, SPDM_ERROR, 0x06, 0x00}; - byte okMsg[] = {0x12, SPDM_VERSION, 0x00, 0x00}; - int errorCode = 0; + byte zeros[WOLFSPDM_HASH_SIZE]; + byte zeroKey[WOLFSPDM_AEAD_KEY_SIZE]; + byte zeroIv[WOLFSPDM_AEAD_IV_SIZE]; + TEST_CTX_SETUP_V12(); - printf("test_check_error...\n"); + printf("test_key_zeroing...\n"); + + memset(zeros, 0, sizeof(zeros)); + memset(zeroKey, 0, sizeof(zeroKey)); + memset(zeroIv, 0, sizeof(zeroIv)); + + /* Fill key material with non-zero data */ + memset(ctx->reqDataKey, 0xAA, sizeof(ctx->reqDataKey)); + memset(ctx->rspDataKey, 0xBB, sizeof(ctx->rspDataKey)); + memset(ctx->reqDataIv, 0xCC, sizeof(ctx->reqDataIv)); + memset(ctx->rspDataIv, 0xDD, sizeof(ctx->rspDataIv)); + memset(ctx->reqHsSecret, 0x11, sizeof(ctx->reqHsSecret)); + memset(ctx->rspHsSecret, 0x22, sizeof(ctx->rspHsSecret)); + memset(ctx->reqFinishedKey, 0x33, sizeof(ctx->reqFinishedKey)); + memset(ctx->rspFinishedKey, 0x44, sizeof(ctx->rspFinishedKey)); + memset(ctx->handshakeSecret, 0x55, sizeof(ctx->handshakeSecret)); + memset(ctx->sharedSecret, 0x66, sizeof(ctx->sharedSecret)); + memset(ctx->th1, 0x77, sizeof(ctx->th1)); + memset(ctx->th2, 0x88, sizeof(ctx->th2)); + ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; - TEST_ASSERT(wolfSPDM_CheckError(errorMsg, sizeof(errorMsg), &errorCode) == 1, - "Should detect error"); - TEST_ASSERT(errorCode == SPDM_ERROR_DECRYPT_ERROR, "Error code wrong"); + ctx->state = WOLFSPDM_STATE_CONNECTED; + ctx->sessionId = 0x00010001; + ctx->ioCb = dummy_io_cb; - TEST_ASSERT(wolfSPDM_CheckError(okMsg, sizeof(okMsg), NULL) == 0, - "Should not detect error on OK message"); + wolfSPDM_Disconnect(ctx); - TEST_PASS(); -} - -static int test_error_strings(void) -{ - printf("test_error_strings...\n"); - - TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_SUCCESS), "Success") == 0, - "SUCCESS string wrong"); - TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_E_INVALID_ARG), - "Invalid argument") == 0, "INVALID_ARG string wrong"); - TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_E_CRYPTO_FAIL), - "Crypto operation failed") == 0, "CRYPTO_FAIL string wrong"); + ASSERT_EQ(memcmp(ctx->reqDataKey, zeroKey, sizeof(ctx->reqDataKey)), 0, + "reqDataKey not zeroed"); + ASSERT_EQ(memcmp(ctx->rspDataKey, zeroKey, sizeof(ctx->rspDataKey)), 0, + "rspDataKey not zeroed"); + ASSERT_EQ(memcmp(ctx->reqDataIv, zeroIv, sizeof(ctx->reqDataIv)), 0, + "reqDataIv not zeroed"); + ASSERT_EQ(memcmp(ctx->rspDataIv, zeroIv, sizeof(ctx->rspDataIv)), 0, + "rspDataIv not zeroed"); + ASSERT_EQ(memcmp(ctx->reqHsSecret, zeros, sizeof(ctx->reqHsSecret)), 0, + "reqHsSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->rspHsSecret, zeros, sizeof(ctx->rspHsSecret)), 0, + "rspHsSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->reqFinishedKey, zeros, sizeof(ctx->reqFinishedKey)), 0, + "reqFinishedKey not zeroed"); + ASSERT_EQ(memcmp(ctx->rspFinishedKey, zeros, sizeof(ctx->rspFinishedKey)), 0, + "rspFinishedKey not zeroed"); + ASSERT_EQ(memcmp(ctx->handshakeSecret, zeros, sizeof(ctx->handshakeSecret)), 0, + "handshakeSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->sharedSecret, zeros, sizeof(ctx->sharedSecret)), 0, + "sharedSecret not zeroed"); + ASSERT_EQ(ctx->sharedSecretSz, 0, "sharedSecretSz not zeroed"); + ASSERT_EQ(memcmp(ctx->th1, zeros, sizeof(ctx->th1)), 0, + "th1 not zeroed"); + ASSERT_EQ(memcmp(ctx->th2, zeros, sizeof(ctx->th2)), 0, + "th2 not zeroed"); + wolfSPDM_Init(ctx); + TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* Measurement Tests */ -/* ========================================================================== */ +/* ===== NEW COVERAGE TESTS ===== */ -#ifndef NO_WOLFSPDM_MEAS +/* ----- Group A: Public API Coverage ----- */ -static int test_build_get_measurements(void) +static int test_set_requester_key_pair(void) { - byte buf[64]; - byte zeros[32]; - word32 bufSz; - TEST_CTX_SETUP_V12(); - - printf("test_build_get_measurements...\n"); - - /* Build without signature */ - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, buf, &bufSz, SPDM_MEAS_OPERATION_ALL, 0)); - ASSERT_EQ(bufSz, 4, "Without sig should be 4 bytes"); - ASSERT_EQ(buf[1], SPDM_GET_MEASUREMENTS, "Code should be 0xE0"); - ASSERT_EQ(buf[2], 0x00, "Param1 should be 0 (no sig)"); - - /* Build with signature */ - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, buf, &bufSz, SPDM_MEAS_OPERATION_ALL, 1)); - ASSERT_EQ(bufSz, 37, "With sig should be 37 bytes"); - ASSERT_EQ(buf[2], SPDM_MEAS_REQUEST_SIG_BIT, "Sig bit should be set"); - XMEMSET(zeros, 0, sizeof(zeros)); - ASSERT_NE(memcmp(&buf[4], zeros, 32), 0, "Nonce should be non-zero"); - ASSERT_EQ(memcmp(ctx->measNonce, &buf[4], 32), 0, "Nonce should match context"); + byte privKey[48], pubKey[96]; + TEST_CTX_SETUP(); + printf("test_set_requester_key_pair...\n"); + XMEMSET(privKey, 0xAA, sizeof(privKey)); + XMEMSET(pubKey, 0xBB, sizeof(pubKey)); - /* DSP0274 v1.3.0 Table 50 / v1.4.0 Table 49: RequesterContext is - * appended for 1.3+ regardless of whether signature was requested. */ - ctx->spdmVersion = SPDM_VERSION_13; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, buf, &bufSz, SPDM_MEAS_OPERATION_ALL, 0)); - ASSERT_EQ(bufSz, 12, "1.3 unsigned should be 4 + 8 = 12 bytes"); + /* NULL args */ + TEST_ASSERT(wolfSPDM_SetRequesterKeyPair(NULL, privKey, 48, pubKey, 96) + != WOLFSPDM_SUCCESS, "NULL ctx should fail"); + TEST_ASSERT(wolfSPDM_SetRequesterKeyPair(ctx, NULL, 48, pubKey, 96) + != WOLFSPDM_SUCCESS, "NULL privKey should fail"); + TEST_ASSERT(wolfSPDM_SetRequesterKeyPair(ctx, privKey, 48, NULL, 96) + != WOLFSPDM_SUCCESS, "NULL pubKey should fail"); - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, buf, &bufSz, SPDM_MEAS_OPERATION_ALL, 1)); - ASSERT_EQ(bufSz, 45, "1.3 signed should be 4 + 32 + 1 + 8 = 45 bytes"); + /* Valid call */ + ASSERT_SUCCESS(wolfSPDM_SetRequesterKeyPair(ctx, privKey, 48, pubKey, 96)); + ASSERT_EQ(ctx->flags.hasReqKeyPair, 1, "hasReqKeyPair not set"); + ASSERT_EQ(ctx->reqPrivKeyLen, 48, "privKey len wrong"); + TEST_ASSERT(memcmp(ctx->reqPrivKey, privKey, 48) == 0, "privKey mismatch"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_build_get_measurements_slot(void) +static int test_connect_null_args(void) { - /* DSP0274: signed GET_MEASUREMENTS carries the SlotID whose - * certificate authenticates the response. When ConnectStandard picks - * a non-zero slot (lowest populated bit in DIGESTS SlotMask), the - * build path must echo that selection rather than hard-coding 0. */ - byte buf[64]; - word32 bufSz; - TEST_CTX_SETUP_V12(); - - printf("test_build_get_measurements_slot...\n"); + TEST_CTX_SETUP(); + printf("test_connect_null_args...\n"); - ctx->currentSlotId = 3; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, buf, &bufSz, - SPDM_MEAS_OPERATION_ALL, 1)); - /* Layout (1.2 signed): hdr(4) + nonce(32) + slot(1) = 37 bytes. - * SlotIDParam follows the 32-byte nonce, so buf[36]. */ - ASSERT_EQ(bufSz, 37, "1.2 signed length wrong"); - ASSERT_EQ(buf[36] & 0x0F, 3, "SlotIDParam should echo currentSlotId"); + TEST_ASSERT(wolfSPDM_Connect(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + /* No ioCb set */ + TEST_ASSERT(wolfSPDM_Connect(ctx) != WOLFSPDM_SUCCESS, + "No IO should fail"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_measurement_accessors(void) +static int test_get_version_no_io(void) { - byte measIdx, measType; - byte value[64]; - word32 valueSz; TEST_CTX_SETUP(); - - printf("test_measurement_accessors...\n"); - ASSERT_EQ(wolfSPDM_GetMeasurementCount(ctx), 0, "Count should be 0 before measurements"); - - /* Manually populate 2 test blocks */ - ctx->flags.hasMeasurements = 1; - ctx->measBlockCount = 2; - ctx->measBlocks[0].index = 1; - ctx->measBlocks[0].dmtfType = SPDM_MEAS_VALUE_TYPE_IMMUTABLE_ROM; - ctx->measBlocks[0].valueSize = 4; - ctx->measBlocks[0].value[0] = 0xAA; ctx->measBlocks[0].value[1] = 0xBB; - ctx->measBlocks[0].value[2] = 0xCC; ctx->measBlocks[0].value[3] = 0xDD; - ctx->measBlocks[1].index = 2; - ctx->measBlocks[1].dmtfType = SPDM_MEAS_VALUE_TYPE_MUTABLE_FW; - ctx->measBlocks[1].valueSize = 2; - ctx->measBlocks[1].value[0] = 0x11; ctx->measBlocks[1].value[1] = 0x22; - - ASSERT_EQ(wolfSPDM_GetMeasurementCount(ctx), 2, "Count should be 2"); - - /* Get block 0 */ - valueSz = sizeof(value); - ASSERT_SUCCESS(wolfSPDM_GetMeasurementBlock(ctx, 0, &measIdx, &measType, value, &valueSz)); - ASSERT_EQ(measIdx, 1, "Block 0 index should be 1"); - ASSERT_EQ(measType, SPDM_MEAS_VALUE_TYPE_IMMUTABLE_ROM, "Block 0 type wrong"); - ASSERT_EQ(valueSz, 4, "Block 0 size wrong"); - ASSERT_EQ(value[0], 0xAA, "Block 0 value wrong"); - - /* Get block 1 */ - valueSz = sizeof(value); - ASSERT_SUCCESS(wolfSPDM_GetMeasurementBlock(ctx, 1, &measIdx, &measType, value, &valueSz)); - ASSERT_EQ(measIdx, 2, "Block 1 index should be 2"); - - /* Out of range */ - valueSz = sizeof(value); - ASSERT_FAIL(wolfSPDM_GetMeasurementBlock(ctx, 2, &measIdx, &measType, value, &valueSz)); - ASSERT_FAIL(wolfSPDM_GetMeasurementBlock(ctx, -1, &measIdx, &measType, value, &valueSz)); - + printf("test_get_version_no_io...\n"); + TEST_ASSERT(wolfSPDM_GetVersion(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + TEST_ASSERT(wolfSPDM_GetVersion(ctx) != WOLFSPDM_SUCCESS, + "No IO should fail"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_measurements(void) +static int test_key_exchange_no_io(void) { TEST_CTX_SETUP(); - /* Fake MEASUREMENTS response: 2 blocks, recordLen=20 */ - byte rsp[] = { - 0x12, 0x60, 0x00, 0x00, /* header */ - 0x02, /* numBlocks */ - 0x14, 0x00, 0x00, /* recordLen = 20 LE */ - /* Block 1: Index=1, Spec=1, Size=7, DMTF Type=0x00, ValSize=4 */ - 0x01, 0x01, 0x07, 0x00, 0x00, 0x04, 0x00, 0xAA, 0xBB, 0xCC, 0xDD, - /* Block 2: Index=2, Spec=1, Size=5, DMTF Type=0x01, ValSize=2 */ - 0x02, 0x01, 0x05, 0x00, 0x01, 0x02, 0x00, 0x11, 0x22 - }; - - printf("test_parse_measurements...\n"); - - ASSERT_SUCCESS(wolfSPDM_ParseMeasurements(ctx, rsp, sizeof(rsp))); - ASSERT_EQ(ctx->measBlockCount, 2, "Should have 2 blocks"); - ASSERT_EQ(ctx->flags.hasMeasurements, 1, "hasMeasurements should be set"); - ASSERT_EQ(ctx->measBlocks[0].index, 1, "Block 0 index wrong"); - ASSERT_EQ(ctx->measBlocks[0].dmtfType, 0x00, "Block 0 type wrong"); - ASSERT_EQ(ctx->measBlocks[0].valueSize, 4, "Block 0 valueSize wrong"); - ASSERT_EQ(ctx->measBlocks[0].value[0], 0xAA, "Block 0 value[0] wrong"); - ASSERT_EQ(ctx->measBlocks[1].index, 2, "Block 1 index wrong"); - ASSERT_EQ(ctx->measBlocks[1].valueSize, 2, "Block 1 valueSize wrong"); - - /* Test truncated buffer */ - ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, rsp, 5)); - + printf("test_key_exchange_no_io...\n"); + TEST_ASSERT(wolfSPDM_KeyExchange(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + TEST_ASSERT(wolfSPDM_KeyExchange(ctx) != WOLFSPDM_SUCCESS, + "No IO should fail"); TEST_CTX_FREE(); TEST_PASS(); } -#ifndef NO_WOLFSPDM_MEAS_VERIFY - -static int test_measurement_sig_verification(void) +static int test_finish_no_io(void) { - ecc_key sigKey; - WC_RNG rng; - /* Construct a minimal GET_MEASUREMENTS request (L1) */ - byte reqMsg[] = { - 0x12, 0xE0, 0x01, 0xFF, /* version, GET_MEASUREMENTS, sig bit, all */ - /* 32 bytes nonce */ - 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, - 0x00 /* SlotID */ - }; - /* Construct a MEASUREMENTS response (L2) WITHOUT signature - * We'll append signature after signing */ - byte rspBase[] = { - 0x12, 0x60, 0x00, 0x00, /* header */ - 0x01, /* numBlocks=1 */ - 0x0B, 0x00, 0x00, /* recordLen=11 */ - /* Block 1 */ - 0x01, 0x01, 0x07, 0x00, /* Index=1, Spec=1, Size=7 */ - 0x00, 0x04, 0x00, /* Type=0, ValueSize=4 */ - 0xAA, 0xBB, 0xCC, 0xDD, /* Value */ - /* Nonce (32 bytes) */ - 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, - /* OpaqueDataLength = 0 */ - 0x00, 0x00 - }; - byte rspBuf[256]; /* rspBase + 96 byte signature */ - word32 rspBufSz; - wc_Sha384 sha, sha2; - byte digest[WOLFSPDM_HASH_SIZE]; - byte derSig[256]; - word32 derSigSz = sizeof(derSig); - byte rawR[WOLFSPDM_ECC_KEY_SIZE]; - byte rawS[WOLFSPDM_ECC_KEY_SIZE]; - word32 rSz = sizeof(rawR); - word32 sSz = sizeof(rawS); - byte pubDer[256]; - word32 pubDerSz; - word32 idx; - byte signMsg[200]; - word32 signMsgLen; - byte sigRaw[WOLFSPDM_ECC_SIG_SIZE]; - int rc; - int i; - TEST_CTX_SETUP_V12(); - - printf("test_measurement_sig_verification...\n"); - - /* Generate ECC P-384 keypair for testing */ - rc = wc_InitRng(&rng); - TEST_ASSERT(rc == 0, "wc_InitRng failed"); - rc = wc_ecc_init(&sigKey); - TEST_ASSERT(rc == 0, "wc_ecc_init failed"); - rc = wc_ecc_make_key(&rng, 48, &sigKey); - TEST_ASSERT(rc == 0, "wc_ecc_make_key failed"); - - /* Copy public key into context for verification */ - rc = wc_ecc_init(&ctx->responderPubKey.ecc); - TEST_ASSERT(rc == 0, "wc_ecc_init responderPubKey failed"); - - /* Export/import just the public key */ - pubDerSz = sizeof(pubDer); - idx = 0; - rc = wc_EccPublicKeyToDer(&sigKey, pubDer, pubDerSz, 1); - TEST_ASSERT(rc > 0, "EccPublicKeyToDer failed"); - pubDerSz = (word32)rc; - rc = wc_EccPublicKeyDecode(pubDer, &idx, &ctx->responderPubKey.ecc, - pubDerSz); - TEST_ASSERT(rc == 0, "EccPublicKeyDecode failed"); - ctx->flags.hasResponderPubKey = 1; - - /* Build the response buffer (rspBase + signature) */ - XMEMCPY(rspBuf, rspBase, sizeof(rspBase)); - rspBufSz = sizeof(rspBase); - - /* Compute Hash(L1||L2) where L2 = rspBase (before signature), - * then build M = prefix||pad||context||hash, then Hash(M). */ - #define TEST_CONTEXT_STR "responder-measurements signing" - #define TEST_PREFIX_SIZE 16 - #define TEST_CONTEXT_STR_SIZE 30 /* strlen, no null terminator */ - #define TEST_ZERO_PAD_SIZE (36 - TEST_CONTEXT_STR_SIZE) - signMsgLen = 0; - - /* L1||L2 hash */ - rc = wc_InitSha384(&sha); - TEST_ASSERT(rc == 0, "InitSha384 failed"); - wc_Sha384Update(&sha, reqMsg, sizeof(reqMsg)); - wc_Sha384Update(&sha, rspBuf, rspBufSz); - wc_Sha384Final(&sha, digest); - wc_Sha384Free(&sha); - - /* Build M */ - for (i = 0; i < 4; i++) { - XMEMCPY(&signMsg[signMsgLen], "dmtf-spdm-v1.2.*", TEST_PREFIX_SIZE); - signMsgLen += TEST_PREFIX_SIZE; - } - XMEMSET(&signMsg[signMsgLen], 0x00, TEST_ZERO_PAD_SIZE); - signMsgLen += TEST_ZERO_PAD_SIZE; - XMEMCPY(&signMsg[signMsgLen], TEST_CONTEXT_STR, TEST_CONTEXT_STR_SIZE); - signMsgLen += TEST_CONTEXT_STR_SIZE; - XMEMCPY(&signMsg[signMsgLen], digest, WOLFSPDM_HASH_SIZE); - signMsgLen += WOLFSPDM_HASH_SIZE; - - /* Hash(M) */ - rc = wc_InitSha384(&sha2); - TEST_ASSERT(rc == 0, "InitSha384 for M failed"); - wc_Sha384Update(&sha2, signMsg, signMsgLen); - wc_Sha384Final(&sha2, digest); - wc_Sha384Free(&sha2); - - /* Sign Hash(M) with our test key (DER format) */ - rc = wc_ecc_sign_hash(digest, WOLFSPDM_HASH_SIZE, derSig, &derSigSz, - &rng, &sigKey); - TEST_ASSERT(rc == 0, "ecc_sign_hash failed"); - - /* Convert DER signature to raw r||s for SPDM */ - rc = wc_ecc_sig_to_rs(derSig, derSigSz, rawR, &rSz, rawS, &sSz); - TEST_ASSERT(rc == 0, "ecc_sig_to_rs failed"); - - /* Pad r and s to 48 bytes each (P-384) */ - XMEMSET(sigRaw, 0, sizeof(sigRaw)); - /* Right-align r and s in their 48-byte fields */ - XMEMCPY(sigRaw + (48 - rSz), rawR, rSz); - XMEMCPY(sigRaw + 48 + (48 - sSz), rawS, sSz); - XMEMCPY(rspBuf + rspBufSz, sigRaw, WOLFSPDM_ECC_SIG_SIZE); - rspBufSz += WOLFSPDM_ECC_SIG_SIZE; - - /* Test 1: Valid signature should verify */ - rc = wolfSPDM_VerifyMeasurementSig(ctx, rspBuf, rspBufSz, - reqMsg, sizeof(reqMsg)); - TEST_ASSERT(rc == WOLFSPDM_SUCCESS, - "Valid signature should verify"); - - /* Test 2: Corrupt one signature byte -> should fail */ - rspBuf[rspBufSz - 10] ^= 0xFF; - rc = wolfSPDM_VerifyMeasurementSig(ctx, rspBuf, rspBufSz, - reqMsg, sizeof(reqMsg)); - TEST_ASSERT(rc == WOLFSPDM_E_MEAS_SIG_FAIL, - "Corrupted sig should fail"); - rspBuf[rspBufSz - 10] ^= 0xFF; /* Restore */ - - /* Test 3: Corrupt one measurement byte -> should fail */ - rspBuf[15] ^= 0xFF; /* Corrupt a measurement value byte */ - rc = wolfSPDM_VerifyMeasurementSig(ctx, rspBuf, rspBufSz, - reqMsg, sizeof(reqMsg)); - TEST_ASSERT(rc == WOLFSPDM_E_MEAS_SIG_FAIL, - "Corrupted measurement should fail"); - - wc_ecc_free(&sigKey); - wc_FreeRng(&rng); + TEST_CTX_SETUP(); + printf("test_finish_no_io...\n"); + TEST_ASSERT(wolfSPDM_Finish(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + TEST_ASSERT(wolfSPDM_Finish(ctx) != WOLFSPDM_SUCCESS, + "No session should fail"); TEST_CTX_FREE(); TEST_PASS(); } -#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ -#endif /* !NO_WOLFSPDM_MEAS */ - -/* ========================================================================== */ -/* Certificate Chain Validation Tests */ -/* ========================================================================== */ - -static int test_set_trusted_cas(void) +static int test_secured_exchange_null_args(void) { - byte fakeCa[] = {0x30, 0x82, 0x01, 0x00, 0xAA, 0xBB, 0xCC, 0xDD}; + byte cmd[4] = {0}, rsp[64]; + word32 rspSz = sizeof(rsp); TEST_CTX_SETUP(); + printf("test_secured_exchange_null_args...\n"); - printf("test_set_trusted_cas...\n"); - ASSERT_FAIL(wolfSPDM_SetTrustedCAs(NULL, fakeCa, sizeof(fakeCa))); - ASSERT_FAIL(wolfSPDM_SetTrustedCAs(ctx, NULL, sizeof(fakeCa))); - ASSERT_FAIL(wolfSPDM_SetTrustedCAs(ctx, fakeCa, 0)); - ASSERT_SUCCESS(wolfSPDM_SetTrustedCAs(ctx, fakeCa, sizeof(fakeCa))); - ASSERT_EQ(ctx->flags.hasTrustedCAs, 1, "hasTrustedCAs not set"); - ASSERT_EQ(ctx->trustedCAsSz, sizeof(fakeCa), "Size mismatch"); - ASSERT_EQ(memcmp(ctx->trustedCAs, fakeCa, sizeof(fakeCa)), 0, "Data mismatch"); + TEST_ASSERT(wolfSPDM_SecuredExchange(NULL, cmd, 4, rsp, &rspSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_SecuredExchange(ctx, NULL, 4, rsp, &rspSz) + != WOLFSPDM_SUCCESS, "NULL cmd"); + TEST_ASSERT(wolfSPDM_SecuredExchange(ctx, cmd, 4, NULL, &rspSz) + != WOLFSPDM_SUCCESS, "NULL rsp"); + TEST_ASSERT(wolfSPDM_SecuredExchange(ctx, cmd, 4, rsp, NULL) + != WOLFSPDM_SUCCESS, "NULL rspSz"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_validate_cert_chain_no_cas(void) +static int test_disconnect_states(void) { TEST_CTX_SETUP(); - - printf("test_validate_cert_chain_no_cas...\n"); - - ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_PARSE, "Should fail without trusted CAs"); - + printf("test_disconnect_states...\n"); + /* Not connected should still succeed (cleanup is safe) */ + wolfSPDM_Disconnect(ctx); + wolfSPDM_Disconnect(NULL); /* Should not crash */ TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* Challenge Tests */ -/* ========================================================================== */ +/* ----- Group B: TCG Message Framing ----- */ -#ifndef NO_WOLFSPDM_CHALLENGE +#ifdef WOLFSPDM_TCG -static int test_build_challenge(void) +static int test_build_tcg_clear_message(void) { - byte buf[64]; - byte zeros[32]; - word32 bufSz; - TEST_CTX_SETUP_V12(); + byte outBuf[64]; + int rc; + TEST_CTX_SETUP(); + printf("test_build_tcg_clear_message...\n"); - printf("test_build_challenge...\n"); + ctx->connectionHandle = 0; + ctx->fipsIndicator = 0; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildChallenge(ctx, buf, &bufSz, 0, SPDM_MEAS_SUMMARY_HASH_NONE)); - ASSERT_EQ(bufSz, 36, "CHALLENGE should be 36 bytes"); - ASSERT_EQ(buf[1], SPDM_CHALLENGE, "Code should be 0x83"); - ASSERT_EQ(buf[3], SPDM_MEAS_SUMMARY_HASH_NONE, "MeasHashType wrong"); - XMEMSET(zeros, 0, sizeof(zeros)); - ASSERT_NE(memcmp(&buf[4], zeros, 32), 0, "Nonce should be non-zero"); - ASSERT_EQ(memcmp(ctx->challengeNonce, &buf[4], 32), 0, "Nonce should match context"); - - /* Test with different slot and meas hash type */ - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildChallenge(ctx, buf, &bufSz, 3, SPDM_MEAS_SUMMARY_HASH_ALL)); - ASSERT_EQ(buf[2], 0x03, "SlotID should be 3"); - - /* SPDM 1.3+ adds an 8-byte RequesterContext at the end. */ - ctx->spdmVersion = SPDM_VERSION_13; - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildChallenge(ctx, buf, &bufSz, 0, SPDM_MEAS_SUMMARY_HASH_NONE)); - ASSERT_EQ(bufSz, 44, "1.3 CHALLENGE should be 36 + 8 = 44 bytes"); - ASSERT_EQ(memcmp(ctx->challengeReqCtx, &buf[36], 8), 0, - "ReqCtx should follow nonce"); + /* NULL args */ + TEST_ASSERT(wolfSPDM_BuildTcgClearMessage(NULL, (byte*)"AB", 2, outBuf, + sizeof(outBuf)) < 0, "NULL ctx"); + TEST_ASSERT(wolfSPDM_BuildTcgClearMessage(ctx, NULL, 2, outBuf, + sizeof(outBuf)) < 0, "NULL payload"); + TEST_ASSERT(wolfSPDM_BuildTcgClearMessage(ctx, (byte*)"AB", 2, NULL, + sizeof(outBuf)) < 0, "NULL outBuf"); /* Buffer too small */ - ctx->spdmVersion = SPDM_VERSION_12; - bufSz = 10; - ASSERT_EQ(wolfSPDM_BuildChallenge(ctx, buf, &bufSz, 0, SPDM_MEAS_SUMMARY_HASH_NONE), - WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); + TEST_ASSERT(wolfSPDM_BuildTcgClearMessage(ctx, (byte*)"AB", 2, outBuf, + 4) < 0, "small buffer"); + + /* Valid build: 16 header + 4 payload = 20 bytes */ + rc = wolfSPDM_BuildTcgClearMessage(ctx, (byte*)"TEST", 4, outBuf, + sizeof(outBuf)); + TEST_ASSERT(rc == 20, "expected 20 bytes"); + /* Tag at [0-1] should be 0x8101 big-endian */ + TEST_ASSERT(outBuf[0] == 0x81 && outBuf[1] == 0x01, "wrong tag"); + /* Payload at offset 16 */ + TEST_ASSERT(memcmp(outBuf + 16, "TEST", 4) == 0, "payload mismatch"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_challenge_auth(void) +static int test_parse_tcg_clear_message(void) { - /* Fake CHALLENGE_AUTH: hdr(4) + CertHash(48) + Nonce(32) + OpaqueLen(2) + Sig(96) = 182 */ - byte rsp[182]; - word32 sigOffset = 0; - TEST_CTX_SETUP_V12(); - - printf("test_parse_challenge_auth...\n"); - - ctx->challengeMeasHashType = SPDM_MEAS_SUMMARY_HASH_NONE; + byte buf[32], payload[16]; + word32 payloadSz = sizeof(payload); + WOLFSPDM_TCG_CLEAR_HDR hdr; + int built; + int parsed; + TEST_CTX_SETUP(); + printf("test_parse_tcg_clear_message...\n"); - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_12; - rsp[1] = SPDM_CHALLENGE_AUTH; - XMEMSET(&rsp[4], 0xAA, WOLFSPDM_HASH_SIZE); - XMEMCPY(ctx->certChainHash, &rsp[4], WOLFSPDM_HASH_SIZE); - XMEMSET(&rsp[52], 0xBB, 32); - XMEMSET(&rsp[86], 0xCC, WOLFSPDM_ECC_SIG_SIZE); + /* Build a valid message first */ + ctx->connectionHandle = 0; + ctx->fipsIndicator = 0; + built = wolfSPDM_BuildTcgClearMessage(ctx, (byte*)"ABCD", 4, buf, + sizeof(buf)); + TEST_ASSERT(built == 20, "build failed"); - ASSERT_SUCCESS(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset)); - ASSERT_EQ(sigOffset, 86, "Signature offset should be 86"); + /* NULL args */ + TEST_ASSERT(wolfSPDM_ParseTcgClearMessage(NULL, 20, payload, &payloadSz, + &hdr) != WOLFSPDM_SUCCESS, "NULL inBuf"); + TEST_ASSERT(wolfSPDM_ParseTcgClearMessage(buf, 20, NULL, &payloadSz, + &hdr) != WOLFSPDM_SUCCESS, "NULL payload"); - /* Wrong response code */ - rsp[1] = 0xFF; - ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset), - WOLFSPDM_E_CHALLENGE, "Wrong code should fail"); - rsp[1] = SPDM_CHALLENGE_AUTH; + /* Short buffer */ + TEST_ASSERT(wolfSPDM_ParseTcgClearMessage(buf, 8, payload, &payloadSz, + &hdr) != WOLFSPDM_SUCCESS, "short buffer"); - /* CertChainHash mismatch */ - ctx->certChainHash[0] = 0x00; - ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset), - WOLFSPDM_E_CHALLENGE, "Hash mismatch should fail"); + /* Valid parse (returns payload size on success) */ + payloadSz = sizeof(payload); + parsed = wolfSPDM_ParseTcgClearMessage(buf, 20, payload, + &payloadSz, &hdr); + TEST_ASSERT(parsed >= 0, "parse failed"); + ASSERT_EQ(payloadSz, 4, "payload size wrong"); + TEST_ASSERT(memcmp(payload, "ABCD", 4) == 0, "payload mismatch"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_challenge_auth_slot_echo(void) +static int test_build_vendor_defined(void) { - /* DSP0274 Sec. 10.8: Param1[3:0] echoes the requested SlotID. Public - * API accepts slots 0..7, so a CHALLENGE for slot 3 must accept a - * matching CHALLENGE_AUTH and reject any other slot value. */ - byte rsp[182]; - word32 sigOffset = 0; - TEST_CTX_SETUP_V12(); - - printf("test_parse_challenge_auth_slot_echo...\n"); - - ctx->challengeMeasHashType = SPDM_MEAS_SUMMARY_HASH_NONE; - ctx->challengeSlotId = 3; /* What BuildChallenge(...,3,...) would set */ - - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_12; - rsp[1] = SPDM_CHALLENGE_AUTH; - rsp[2] = 0x03; /* Param1 echoes slot 3 */ - XMEMSET(&rsp[4], 0xAA, WOLFSPDM_HASH_SIZE); - XMEMCPY(ctx->certChainHash, &rsp[4], WOLFSPDM_HASH_SIZE); - XMEMSET(&rsp[52], 0xBB, 32); - XMEMSET(&rsp[86], 0xCC, WOLFSPDM_ECC_SIG_SIZE); + byte outBuf[64]; + int rc; + printf("test_build_vendor_defined...\n"); - /* Matching echo must be accepted. */ - ASSERT_SUCCESS( - wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset)); + /* NULL args */ + TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, NULL, + (byte*)"X", 1, outBuf, sizeof(outBuf)) < 0, "NULL vdCode"); + TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", + (byte*)"X", 1, NULL, sizeof(outBuf)) < 0, "NULL outBuf"); - /* Wrong echo (slot 0 when slot 3 was requested) must be refused. */ - rsp[2] = 0x00; - ASSERT_EQ( - wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset), - WOLFSPDM_E_CHALLENGE, "SlotID echo mismatch must be rejected"); + /* Buffer too small */ + TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", + (byte*)"X", 1, outBuf, 4) < 0, "small buffer"); + + /* Valid build with payload */ + rc = wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", + (byte*)"ABCD", 4, outBuf, sizeof(outBuf)); + TEST_ASSERT(rc > 0, "build failed"); + ASSERT_EQ(outBuf[0], SPDM_VERSION_12, "wrong version"); + TEST_ASSERT(outBuf[1] == 0xFE || outBuf[1] == 0x7E, + "wrong opcode"); + + /* Build with no payload */ + rc = wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "GET_PUBK", + NULL, 0, outBuf, sizeof(outBuf)); + TEST_ASSERT(rc > 0, "no-payload build failed"); + + TEST_PASS(); +} + +static int test_parse_vendor_defined(void) +{ + byte outBuf[64], payload[32]; + char vdCode[9]; + word32 payloadSz; + int built; + int parsed; + printf("test_parse_vendor_defined...\n"); + + /* Build, then parse back */ + built = wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", + (byte*)"HELLO", 5, outBuf, sizeof(outBuf)); + TEST_ASSERT(built > 0, "build failed"); + + payloadSz = sizeof(payload); + parsed = wolfSPDM_ParseVendorDefined(outBuf, (word32)built, vdCode, + payload, &payloadSz); + TEST_ASSERT(parsed >= 0, "parse failed"); + TEST_ASSERT(memcmp(vdCode, "TPM2_CMD", 8) == 0, "vdCode mismatch"); + ASSERT_EQ(payloadSz, 5, "payload size wrong"); + TEST_ASSERT(memcmp(payload, "HELLO", 5) == 0, "payload mismatch"); + + /* NULL args */ + TEST_ASSERT(wolfSPDM_ParseVendorDefined(NULL, (word32)built, vdCode, + payload, &payloadSz) != WOLFSPDM_SUCCESS, "NULL inBuf"); + + /* Short buffer */ + payloadSz = sizeof(payload); + TEST_ASSERT(wolfSPDM_ParseVendorDefined(outBuf, 4, vdCode, + payload, &payloadSz) != WOLFSPDM_SUCCESS, "short buffer"); + + TEST_PASS(); +} + +static int test_vendor_defined_roundtrip(void) +{ + static const char* codes[] = {"GET_PUBK", "GIVE_PUB", "TPM2_CMD", + "GET_STS_", "SPDMONLY"}; + byte outBuf[64], payload[32]; + char vdCode[9]; + word32 payloadSz; + int i, built; + int parsed; + printf("test_vendor_defined_roundtrip...\n"); + + for (i = 0; i < 5; i++) { + byte testData[4] = {(byte)i, 0x11, 0x22, 0x33}; + built = wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, codes[i], + testData, 4, outBuf, sizeof(outBuf)); + TEST_ASSERT(built > 0, "build failed"); + payloadSz = sizeof(payload); + parsed = wolfSPDM_ParseVendorDefined(outBuf, (word32)built, + vdCode, payload, &payloadSz); + TEST_ASSERT(parsed >= 0, "parse failed"); + TEST_ASSERT(memcmp(vdCode, codes[i], 8) == 0, "vdCode mismatch"); + ASSERT_EQ(payloadSz, 4, "payload size"); + TEST_ASSERT(memcmp(payload, testData, 4) == 0, "payload mismatch"); + } - TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_challenge_auth_reqctx_echo(void) +static int test_tcg_get_pub_key_null_args(void) { - /* SPDM 1.3+: CHALLENGE_AUTH carries an 8-byte echo of the - * RequesterContext from the request. wolfSPDM_ParseChallengeAuth - * must compare it against ctx->challengeReqCtx and reject mismatches. */ - byte rsp[190]; /* 1.3 layout: hdr(4) + cert(48) + nonce(32) + opaqueLen(2) + reqCtx(8) + sig(96) */ - word32 sigOffset = 0; - int i; + byte pubKey[256]; + word32 pubKeySz = sizeof(pubKey); TEST_CTX_SETUP(); - - printf("test_parse_challenge_auth_reqctx_echo...\n"); - - ctx->spdmVersion = SPDM_VERSION_13; - ctx->challengeMeasHashType = SPDM_MEAS_SUMMARY_HASH_NONE; - - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_13; - rsp[1] = SPDM_CHALLENGE_AUTH; - XMEMSET(&rsp[4], 0xAA, WOLFSPDM_HASH_SIZE); /* CertHash */ - XMEMCPY(ctx->certChainHash, &rsp[4], WOLFSPDM_HASH_SIZE); - XMEMSET(&rsp[52], 0xBB, 32); /* Nonce */ - /* OpaqueLen at offset 84..85 = 0 (already memset) */ - /* RequesterContext echo at offset 86..93 */ - for (i = 0; i < 8; i++) { - rsp[86 + i] = (byte)(0xE0 + i); - ctx->challengeReqCtx[i] = (byte)(0xE0 + i); - } - /* Signature at offset 94..189 */ - XMEMSET(&rsp[94], 0xCC, WOLFSPDM_ECC_SIG_SIZE); - - ASSERT_SUCCESS( - wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset)); - ASSERT_EQ(sigOffset, 94, "Signature offset should be 94 (1.3+)"); - - /* Flip a single echo byte; parser must reject. */ - rsp[86] ^= 0x01; - ASSERT_EQ( - wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset), - WOLFSPDM_E_CHALLENGE, "Tampered ReqCtx echo must be refused"); - + printf("test_tcg_get_pub_key_null_args...\n"); + TEST_ASSERT(wolfSPDM_TCG_GetPubKey(NULL, pubKey, &pubKeySz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_TCG_GetPubKey(ctx, NULL, &pubKeySz) + != WOLFSPDM_SUCCESS, "NULL pubKey"); + TEST_ASSERT(wolfSPDM_TCG_GetPubKey(ctx, pubKey, NULL) + != WOLFSPDM_SUCCESS, "NULL pubKeySz"); TEST_CTX_FREE(); TEST_PASS(); } -#endif /* !NO_WOLFSPDM_CHALLENGE */ - -/* ========================================================================== */ -/* Heartbeat Tests */ -/* ========================================================================== */ - -static int test_build_heartbeat(void) +static int test_tcg_give_pub_key_null_args(void) { - byte buf[16]; - word32 bufSz; - TEST_CTX_SETUP_V12(); - - printf("test_build_heartbeat...\n"); - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildHeartbeat(ctx, buf, &bufSz)); - ASSERT_EQ(bufSz, 4, "HEARTBEAT should be 4 bytes"); - ASSERT_EQ(buf[1], SPDM_HEARTBEAT, "Code should be 0xE8"); - - bufSz = 2; - ASSERT_EQ(wolfSPDM_BuildHeartbeat(ctx, buf, &bufSz), WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); - + byte pubKey[128]; + TEST_CTX_SETUP(); + printf("test_tcg_give_pub_key_null_args...\n"); + XMEMSET(pubKey, 0xAA, sizeof(pubKey)); + TEST_ASSERT(wolfSPDM_TCG_GivePubKey(NULL, pubKey, 120) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_TCG_GivePubKey(ctx, NULL, 120) + != WOLFSPDM_SUCCESS, "NULL pubKey"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_heartbeat_ack(void) +static int test_set_requester_key_tpmt(void) { - byte ack[] = {0x12, SPDM_HEARTBEAT_ACK, 0x00, 0x00}; - byte err[] = {0x12, SPDM_ERROR, 0x01, 0x00}; + byte tpmt[128]; TEST_CTX_SETUP(); + printf("test_set_requester_key_tpmt...\n"); + XMEMSET(tpmt, 0x55, sizeof(tpmt)); - printf("test_parse_heartbeat_ack...\n"); - ASSERT_SUCCESS(wolfSPDM_ParseHeartbeatAck(ctx, ack, sizeof(ack))); - ASSERT_EQ(wolfSPDM_ParseHeartbeatAck(ctx, err, sizeof(err)), WOLFSPDM_E_PEER_ERROR, "Error should return PEER_ERROR"); + TEST_ASSERT(wolfSPDM_SetRequesterKeyTPMT(NULL, tpmt, 120) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_SetRequesterKeyTPMT(ctx, NULL, 120) + != WOLFSPDM_SUCCESS, "NULL tpmtPub"); + + /* Valid 120-byte TPMT */ + ASSERT_SUCCESS(wolfSPDM_SetRequesterKeyTPMT(ctx, tpmt, 120)); + ASSERT_EQ(ctx->reqPubKeyTPMTLen, 120, "tpmt len wrong"); + TEST_ASSERT(memcmp(ctx->reqPubKeyTPMT, tpmt, 120) == 0, "tpmt mismatch"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_heartbeat_state_check(void) +static int test_connect_tcg_null_args(void) { TEST_CTX_SETUP(); - - printf("test_heartbeat_state_check...\n"); - ASSERT_EQ(wolfSPDM_Heartbeat(ctx), WOLFSPDM_E_NOT_CONNECTED, "Heartbeat should fail when not connected"); - + printf("test_connect_tcg_null_args...\n"); + TEST_ASSERT(wolfSPDM_ConnectTCG(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + /* No IO set */ + TEST_ASSERT(wolfSPDM_ConnectTCG(ctx) != WOLFSPDM_SUCCESS, + "No IO should fail"); TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* Key Update Tests */ -/* ========================================================================== */ - -static int test_build_key_update(void) -{ - byte buf[16]; - word32 bufSz; - byte tag = 0; - TEST_CTX_SETUP_V12(); +#endif /* WOLFSPDM_TCG */ - printf("test_build_key_update...\n"); - bufSz = sizeof(buf); - ASSERT_SUCCESS(wolfSPDM_BuildKeyUpdate(ctx, buf, &bufSz, SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, &tag)); - ASSERT_EQ(bufSz, 4, "KEY_UPDATE should be 4 bytes"); - ASSERT_EQ(buf[1], SPDM_KEY_UPDATE, "Code should be 0xE9"); - ASSERT_EQ(buf[2], SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, "Operation should be UpdateAllKeys"); - ASSERT_EQ(buf[3], tag, "Tag should match returned value"); +/* ----- Group C: Nuvoton ----- */ - bufSz = 2; - ASSERT_EQ(wolfSPDM_BuildKeyUpdate(ctx, buf, &bufSz, SPDM_KEY_UPDATE_OP_UPDATE_KEY, &tag), - WOLFSPDM_E_BUFFER_SMALL, "Should fail on small buffer"); +#ifdef WOLFSPDM_NUVOTON +static int test_nuvoton_get_status_null_args(void) +{ + WOLFSPDM_NUVOTON_STATUS status; + TEST_CTX_SETUP(); + printf("test_nuvoton_get_status_null_args...\n"); + TEST_ASSERT(wolfSPDM_Nuvoton_GetStatus(NULL, &status) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nuvoton_GetStatus(ctx, NULL) + != WOLFSPDM_SUCCESS, "NULL status"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_key_update_ack(void) +static int test_nuvoton_set_only_mode_null_args(void) { - byte ack[] = {0x12, SPDM_KEY_UPDATE_ACK, 0x02, 0x42}; TEST_CTX_SETUP(); - - printf("test_parse_key_update_ack...\n"); - ASSERT_SUCCESS(wolfSPDM_ParseKeyUpdateAck(ctx, ack, sizeof(ack), SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, 0x42)); - ASSERT_EQ(wolfSPDM_ParseKeyUpdateAck(ctx, ack, sizeof(ack), SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, 0xFF), - WOLFSPDM_E_KEY_UPDATE, "Mismatched tag should fail"); - ASSERT_EQ(wolfSPDM_ParseKeyUpdateAck(ctx, ack, sizeof(ack), SPDM_KEY_UPDATE_OP_UPDATE_KEY, 0x42), - WOLFSPDM_E_KEY_UPDATE, "Mismatched op should fail"); - + printf("test_nuvoton_set_only_mode_null_args...\n"); + TEST_ASSERT(wolfSPDM_Nuvoton_SetOnlyMode(NULL, 1) + != WOLFSPDM_SUCCESS, "NULL ctx"); + /* Not connected */ + TEST_ASSERT(wolfSPDM_Nuvoton_SetOnlyMode(ctx, 1) + != WOLFSPDM_SUCCESS, "not connected"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_derive_updated_keys(void) -{ - byte origReqKey[WOLFSPDM_AEAD_KEY_SIZE]; - byte origRspKey[WOLFSPDM_AEAD_KEY_SIZE]; - TEST_CTX_SETUP_V12(); +#endif /* WOLFSPDM_NUVOTON */ - printf("test_derive_updated_keys...\n"); - XMEMSET(ctx->reqAppSecret, 0x5A, WOLFSPDM_HASH_SIZE); - XMEMSET(ctx->rspAppSecret, 0xA5, WOLFSPDM_HASH_SIZE); - XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); - XMEMSET(ctx->rspDataKey, 0x22, WOLFSPDM_AEAD_KEY_SIZE); - XMEMCPY(origReqKey, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); - XMEMCPY(origRspKey, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); +/* ----- Group D: Nations ----- */ - /* Update all keys */ - ASSERT_SUCCESS(wolfSPDM_DeriveUpdatedKeys(ctx, 1)); - ASSERT_NE(memcmp(ctx->reqDataKey, origReqKey, WOLFSPDM_AEAD_KEY_SIZE), 0, "Req key should change"); - ASSERT_NE(memcmp(ctx->rspDataKey, origRspKey, WOLFSPDM_AEAD_KEY_SIZE), 0, "Rsp key should change"); - - /* Update requester only */ - XMEMCPY(origReqKey, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); - XMEMCPY(origRspKey, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); - ASSERT_SUCCESS(wolfSPDM_DeriveUpdatedKeys(ctx, 0)); - ASSERT_NE(memcmp(ctx->reqDataKey, origReqKey, WOLFSPDM_AEAD_KEY_SIZE), 0, "Req key should change"); - ASSERT_EQ(memcmp(ctx->rspDataKey, origRspKey, WOLFSPDM_AEAD_KEY_SIZE), 0, "Rsp key should NOT change"); - - TEST_CTX_FREE(); - TEST_PASS(); -} +#ifdef WOLFSPDM_NATIONS -static int test_key_update_state_check(void) +static int test_nations_get_status_null_args(void) { + WOLFSPDM_NATIONS_STATUS status; TEST_CTX_SETUP(); - - printf("test_key_update_state_check...\n"); - ASSERT_EQ(wolfSPDM_KeyUpdate(ctx, 1), WOLFSPDM_E_NOT_CONNECTED, "KeyUpdate should fail when not connected"); - + printf("test_nations_get_status_null_args...\n"); + TEST_ASSERT(wolfSPDM_Nations_GetStatus(NULL, &status) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_GetStatus(ctx, NULL) + != WOLFSPDM_SUCCESS, "NULL status"); TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* Multi-Version Tests */ -/* ========================================================================== */ - -static int test_kdf_version_prefix(void) +static int test_nations_set_only_mode_null_args(void) { - byte secret[48]; - byte context[48]; - byte out12[32], out13[32], out14[32]; - - printf("test_kdf_version_prefix...\n"); - - memset(secret, 0x5A, sizeof(secret)); - memset(context, 0x00, sizeof(context)); - - ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(SPDM_VERSION_12, secret, - sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), - out12, sizeof(out12))); - ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(SPDM_VERSION_13, secret, - sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), - out13, sizeof(out13))); - ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(SPDM_VERSION_14, secret, - sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), - out14, sizeof(out14))); - - /* All three outputs should differ due to different BinConcat prefixes */ - ASSERT_NE(memcmp(out12, out13, sizeof(out12)), 0, - "1.2 and 1.3 outputs should differ"); - ASSERT_NE(memcmp(out13, out14, sizeof(out13)), 0, - "1.3 and 1.4 outputs should differ"); - ASSERT_NE(memcmp(out12, out14, sizeof(out12)), 0, - "1.2 and 1.4 outputs should differ"); - + TEST_CTX_SETUP(); + printf("test_nations_set_only_mode_null_args...\n"); + TEST_ASSERT(wolfSPDM_Nations_SetOnlyMode(NULL, 1) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_SetOnlyMode(ctx, 1) + != WOLFSPDM_SUCCESS, "not connected"); + TEST_CTX_FREE(); TEST_PASS(); -} - -static int test_hmac_mismatch_negative(void) -{ - byte finishedKeyA[WOLFSPDM_HASH_SIZE]; - byte finishedKeyB[WOLFSPDM_HASH_SIZE]; - byte thHash[WOLFSPDM_HASH_SIZE]; - byte verifyA[WOLFSPDM_HASH_SIZE]; - byte verifyB[WOLFSPDM_HASH_SIZE]; - - printf("test_hmac_mismatch_negative...\n"); - - memset(finishedKeyA, 0xAB, sizeof(finishedKeyA)); - memset(finishedKeyB, 0xAC, sizeof(finishedKeyB)); /* Differs by 1 bit */ - memset(thHash, 0xCD, sizeof(thHash)); - - ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKeyA, thHash, verifyA)); - ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKeyB, thHash, verifyB)); - - /* Single-bit change in key must produce different verify data */ - ASSERT_NE(memcmp(verifyA, verifyB, WOLFSPDM_HASH_SIZE), 0, - "Different keys should produce different verify data"); +} +static int test_nations_psk_set_null_args(void) +{ + byte psk[64]; + TEST_CTX_SETUP(); + printf("test_nations_psk_set_null_args...\n"); + XMEMSET(psk, 0xAA, sizeof(psk)); + TEST_ASSERT(wolfSPDM_Nations_PskSet(NULL, psk, 64) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_PskSet(ctx, NULL, 64) + != WOLFSPDM_SUCCESS, "NULL psk"); + TEST_CTX_FREE(); TEST_PASS(); } -static int test_transcript_overflow(void) +static int test_nations_psk_clear_null_args(void) { - byte chunk[256]; - word32 i, needed; + byte auth[32]; TEST_CTX_SETUP(); + printf("test_nations_psk_clear_null_args...\n"); + XMEMSET(auth, 0xBB, sizeof(auth)); + TEST_ASSERT(wolfSPDM_Nations_PskClear(NULL, auth, 32) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_PskClear(ctx, NULL, 32) + != WOLFSPDM_SUCCESS, "NULL auth"); + TEST_CTX_FREE(); + TEST_PASS(); +} - printf("test_transcript_overflow...\n"); +static int test_nations_psk_clear_vca_null_args(void) +{ + byte auth[32]; + TEST_CTX_SETUP(); + printf("test_nations_psk_clear_vca_null_args...\n"); + XMEMSET(auth, 0xCC, sizeof(auth)); + TEST_ASSERT(wolfSPDM_Nations_PskClearWithVCA(NULL, auth, 32) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_PskClearWithVCA(ctx, NULL, 32) + != WOLFSPDM_SUCCESS, "NULL auth"); + TEST_CTX_FREE(); + TEST_PASS(); +} - memset(chunk, 0x42, sizeof(chunk)); +#endif /* WOLFSPDM_NATIONS */ - /* Fill transcript to capacity */ - needed = WOLFSPDM_MAX_TRANSCRIPT / sizeof(chunk); - for (i = 0; i < needed; i++) { - ASSERT_SUCCESS(wolfSPDM_TranscriptAdd(ctx, chunk, sizeof(chunk))); - } - ASSERT_EQ(ctx->transcriptLen, (word32)(needed * sizeof(chunk)), - "Transcript should be full"); +/* ----- Group E: PSK Messages ----- */ - /* Next add should fail with BUFFER_SMALL */ - ASSERT_EQ(wolfSPDM_TranscriptAdd(ctx, chunk, sizeof(chunk)), - WOLFSPDM_E_BUFFER_SMALL, "Overflow should return BUFFER_SMALL"); +#ifdef WOLFSPDM_PSK +static int test_parse_psk_exchange_rsp_null_args(void) +{ + byte buf[64]; + TEST_CTX_SETUP_V12(); + printf("test_parse_psk_exchange_rsp_null_args...\n"); + XMEMSET(buf, 0, sizeof(buf)); + TEST_ASSERT(wolfSPDM_ParsePskExchangeRsp(NULL, buf, sizeof(buf)) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_ParsePskExchangeRsp(ctx, NULL, sizeof(buf)) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_ParsePskExchangeRsp(ctx, buf, 4) + != WOLFSPDM_SUCCESS, "short buf"); TEST_CTX_FREE(); TEST_PASS(); } -#ifndef NO_WOLFSPDM_MEAS - -/* Mock I/O callback that returns a fixed SPDM_ERROR response. */ -static int error_io_cb(WOLFSPDM_CTX* ctx, - const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz, void* userCtx) +/* Drive wolfSPDM_ParsePskExchangeRsp through key derivation to exercise + * the PSK ResponderVerifyData HMAC compare. Previously only NULL/short- + * buffer paths were covered, so mutations of the `if (diff != 0)` block + * or of `diff |= ...` → `diff &= ...` survived every test. */ +static int test_parse_psk_exchange_rsp_hmac_check(void) { - (void)ctx; (void)txBuf; (void)txSz; (void)userCtx; - if (*rxSz < 4) return -1; - rxBuf[0] = SPDM_VERSION_12; - rxBuf[1] = SPDM_ERROR; - rxBuf[2] = SPDM_ERROR_INVALID_REQUEST; /* param1 = error code */ - rxBuf[3] = 0x00; /* param2 */ - *rxSz = 4; - return 0; + byte pskRsp[64]; + const word32 pskRspLen = 60; /* 12-byte partial + 48 HMAC */ + const word32 pskRspPartialLen = 12; + byte psk[48]; + byte th1[WOLFSPDM_HASH_SIZE]; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + int rc; + WOLFSPDM_CTX helperBuf; + WOLFSPDM_CTX* helper = &helperBuf; + TEST_CTX_SETUP_V12(); + + printf("test_parse_psk_exchange_rsp_hmac_check...\n"); + + XMEMSET(psk, 0xA5, sizeof(psk)); + + /* Build PSK_EXCHANGE_RSP partial (12 bytes): rspContextLen=0, opaqueLen=0 */ + XMEMSET(pskRsp, 0, sizeof(pskRsp)); + pskRsp[0] = SPDM_VERSION_12; + pskRsp[1] = SPDM_PSK_EXCHANGE_RSP; + SPDM_Set16LE(&pskRsp[4], 0x1234); /* RspSessionID */ + SPDM_Set16LE(&pskRsp[8], 0); /* RspContextLength */ + SPDM_Set16LE(&pskRsp[10], 0); /* OpaqueDataLength */ + + /* TH1 = Hash(transcript + partial); transcript starts empty */ + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(th1, + pskRsp, pskRspPartialLen, NULL, 0, NULL, 0)); + + /* Derive rspFinishedKey on a throwaway helper ctx */ + ASSERT_SUCCESS(wolfSPDM_Init(helper)); + helper->spdmVersion = SPDM_VERSION_12; + ASSERT_SUCCESS(wolfSPDM_SetPSK(helper, psk, sizeof(psk), NULL, 0)); + ASSERT_SUCCESS(wolfSPDM_DeriveHandshakeKeysPsk(helper, th1)); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData( + helper->rspFinishedKey, th1, expectedHmac)); + wolfSPDM_Free(helper); + + /* Positive: correct HMAC must succeed and advance state to KEY_EX */ + XMEMCPY(&pskRsp[12], expectedHmac, WOLFSPDM_HASH_SIZE); + ASSERT_SUCCESS(wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0)); + rc = wolfSPDM_ParsePskExchangeRsp(ctx, pskRsp, pskRspLen); + ASSERT_EQ(rc, WOLFSPDM_SUCCESS, "valid PSK HMAC should succeed"); + ASSERT_EQ(ctx->state, WOLFSPDM_STATE_KEY_EX, + "state should advance to KEY_EX on valid PSK parse"); + + /* Negative: flip one byte — must return BAD_HMAC. + * Parse scrubs ctx->psk after derivation, so re-set it; also reset + * transcript because the successful parse appended 60 bytes. */ + wolfSPDM_TranscriptReset(ctx); + ctx->state = WOLFSPDM_STATE_INIT; + ASSERT_SUCCESS(wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0)); + pskRsp[12] ^= 0x01; + rc = wolfSPDM_ParsePskExchangeRsp(ctx, pskRsp, pskRspLen); + ASSERT_EQ(rc, WOLFSPDM_E_BAD_HMAC, + "flipped PSK rspVerifyData byte must return BAD_HMAC"); + + TEST_CTX_FREE(); + TEST_PASS(); } -/* Captures the txBuf the wolfSPDM_SecuredExchange layer hands us, so a - * test can prove the encrypt path ran (txBuf will start with the - * sessionId, not an SPDM version byte). Returns "session terminated" - * via a peer error so the caller doesn't try to parse a bogus response. */ -typedef struct { byte first; int hit; } SECURED_PROBE; -static int secured_probe_io_cb(WOLFSPDM_CTX* ctx, - const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz, void* userCtx) +static int test_build_psk_finish_null_args(void) { - SECURED_PROBE* p = (SECURED_PROBE*)userCtx; - (void)ctx; - if (p != NULL && txSz > 0 && txBuf != NULL) { - p->first = txBuf[0]; - p->hit = 1; - } - /* Respond with a 4-byte SPDM_ERROR so the caller surfaces PEER_ERROR. */ - if (*rxSz < 4) return -1; - rxBuf[0] = SPDM_VERSION_12; - rxBuf[1] = SPDM_ERROR; - rxBuf[2] = SPDM_ERROR_INVALID_REQUEST; - rxBuf[3] = 0x00; - *rxSz = 4; - return 0; + byte buf[128]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_psk_finish_null_args...\n"); + TEST_ASSERT(wolfSPDM_BuildPskFinish(NULL, buf, &bufSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_BuildPskFinish(ctx, NULL, &bufSz) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_BuildPskFinish(ctx, buf, NULL) + != WOLFSPDM_SUCCESS, "NULL bufSz"); + bufSz = 4; + TEST_ASSERT(wolfSPDM_BuildPskFinish(ctx, buf, &bufSz) + != WOLFSPDM_SUCCESS, "small buffer"); + TEST_CTX_FREE(); + TEST_PASS(); } -static int test_get_measurements_uses_secured_when_measured(void) +static int test_build_psk_finish_format(void) { - /* After the first GetMeasurements completes, ctx->state advances to - * WOLFSPDM_STATE_MEASURED. The dispatch must keep using the encrypted - * (SecuredExchange) path on subsequent calls rather than falling back - * to cleartext. We assert this by capturing the first txBuf byte: - * - plain SPDM: version byte (0x10-0x1F) - * - secured: low byte of sessionId (we plant 0xAB) */ - SECURED_PROBE probe = { 0, 0 }; - int i; + byte buf[128]; + word32 bufSz = sizeof(buf); TEST_CTX_SETUP_V12(); + printf("test_build_psk_finish_format...\n"); - printf("test_get_measurements_uses_secured_when_measured...\n"); - - ctx->state = WOLFSPDM_STATE_MEASURED; - ctx->sessionId = 0xCAFEBAAB; /* LSB = 0xAB, distinct from 0x10..0x1F */ - for (i = 0; i < WOLFSPDM_AEAD_KEY_SIZE; i++) { - ctx->reqDataKey[i] = (byte)(i + 7); - ctx->rspDataKey[i] = (byte)(i + 7); - } - for (i = 0; i < WOLFSPDM_AEAD_IV_SIZE; i++) { - ctx->reqDataIv[i] = (byte)(0x60 + i); - ctx->rspDataIv[i] = (byte)(0x60 + i); - } - ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, secured_probe_io_cb, &probe)); + /* Fill reqFinishedKey with test data */ + XMEMSET(ctx->reqFinishedKey, 0x5A, WOLFSPDM_HASH_SIZE); + /* Need some transcript data for HMAC */ + wolfSPDM_TranscriptAdd(ctx, (byte*)"test transcript data", 20); - /* Drive GetMeasurements. The mocked I/O returns SPDM_ERROR, which is - * fine - we only care that the secured path was taken. */ - (void)wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 0); - ASSERT_EQ(probe.hit, 1, "I/O callback should have been invoked"); - ASSERT_EQ(probe.first, 0xAB, - "STATE_MEASURED dispatch must use SecuredExchange (sessionId byte)"); + ASSERT_SUCCESS(wolfSPDM_BuildPskFinish(ctx, buf, &bufSz)); + ASSERT_EQ(buf[0], SPDM_VERSION_12, "wrong version"); + ASSERT_EQ(buf[1], 0xE7, "wrong opcode (PSK_FINISH)"); + ASSERT_EQ(bufSz, 52, "expected 4 header + 48 HMAC"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_get_measurements_peer_error(void) +static int test_parse_psk_finish_rsp(void) { + byte buf[8]; TEST_CTX_SETUP_V12(); + printf("test_parse_psk_finish_rsp...\n"); - printf("test_get_measurements_peer_error...\n"); + /* NULL args */ + TEST_ASSERT(wolfSPDM_ParsePskFinishRsp(NULL, buf, 4) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_ParsePskFinishRsp(ctx, NULL, 4) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_ParsePskFinishRsp(ctx, buf, 2) + != WOLFSPDM_SUCCESS, "short buf"); - /* GET_MEASUREMENTS now requires >= STATE_FINISH (post-handshake) so it - * cannot be issued in the clear. Setting state to ALGO must therefore - * be refused with NOT_CONNECTED rather than reaching the I/O callback. */ - ctx->state = WOLFSPDM_STATE_ALGO; - ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, error_io_cb, NULL)); + /* Valid PSK_FINISH_RSP */ + buf[0] = SPDM_VERSION_12; + buf[1] = 0x67; /* PSK_FINISH_RSP */ + buf[2] = 0x00; + buf[3] = 0x00; + ASSERT_SUCCESS(wolfSPDM_ParsePskFinishRsp(ctx, buf, 4)); - ASSERT_EQ( - wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 0), - WOLFSPDM_E_NOT_CONNECTED, - "GET_MEASUREMENTS pre-FINISH must be refused"); + /* Error response */ + buf[1] = 0x7F; /* SPDM_ERROR */ + buf[2] = 0x01; + TEST_ASSERT(wolfSPDM_ParsePskFinishRsp(ctx, buf, 4) + != WOLFSPDM_SUCCESS, "error not detected"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_measurements_13_requester_context(void) +static int test_connect_psk_null_args(void) { - /* Exercise the SPDM 1.3+ branch in ParseMeasurements that requires - * the 8-byte RequesterContext echo between OpaqueData and Signature. - * - * Layout for SIGNED 1.3+: hdr(4) + NumBlocks(1) + RecLen(3) + Record(N) - * + Nonce(32) + OpaqueLen(2) + RequesterContext(8) - * + Signature(96). - * We use a 0-block record (8 hdr) so the parser jumps straight to the - * post-record tail. */ - byte rsp[8 + 32 + 2 + 8 + WOLFSPDM_ECC_SIG_SIZE]; - byte rspShort[8 + 32 + 2 + 4]; /* truncated: ReqCtx missing */ TEST_CTX_SETUP(); + printf("test_connect_psk_null_args...\n"); + TEST_ASSERT(wolfSPDM_ConnectPsk(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + /* No PSK set, no IO */ + TEST_ASSERT(wolfSPDM_ConnectPsk(ctx) != WOLFSPDM_SUCCESS, + "No PSK/IO should fail"); + TEST_CTX_FREE(); + TEST_PASS(); +} - printf("test_parse_measurements_13_requester_context...\n"); +#endif /* WOLFSPDM_PSK */ - ctx->spdmVersion = SPDM_VERSION_13; +/* ----- Group F: Internal Crypto ----- */ - /* Valid response: 0 blocks, RecLen=0, Nonce(32), OpaqueLen=0, ReqCtx(8), Sig(96). */ - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_13; - rsp[1] = SPDM_MEASUREMENTS; - /* NumBlocks=0 at buf[4]; RecLen=0 at buf[5..7]; nothing else needs writing. */ - ASSERT_SUCCESS(wolfSPDM_ParseMeasurements(ctx, rsp, sizeof(rsp))); - ASSERT_EQ(ctx->measSignatureSize, WOLFSPDM_ECC_SIG_SIZE, - "Signature should be captured"); +static int test_sha384_hash(void) +{ + byte hash[48], hash2[48]; + printf("test_sha384_hash...\n"); - /* Missing RequesterContext (only 4 bytes after OpaqueLen) - must fail. */ - XMEMSET(rspShort, 0, sizeof(rspShort)); - rspShort[0] = SPDM_VERSION_13; - rspShort[1] = SPDM_MEASUREMENTS; - ASSERT_EQ(wolfSPDM_ParseMeasurements(ctx, rspShort, sizeof(rspShort)), - WOLFSPDM_E_MEASUREMENT, - "1.3+ response missing RequesterContext must be refused"); + /* Single block */ + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(hash, (byte*)"abc", 3, + NULL, 0, NULL, 0)); + /* Result should be non-zero */ + TEST_ASSERT(hash[0] != 0 || hash[1] != 0, "hash is zero"); + + /* Multi-block should produce same result as single */ + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(hash2, (byte*)"a", 1, + (byte*)"b", 1, (byte*)"c", 1)); + TEST_ASSERT(memcmp(hash, hash2, 48) == 0, + "split hash should match single"); - TEST_CTX_FREE(); TEST_PASS(); } -static int test_parse_measurements_negative(void) +static int test_export_ephemeral_pub_key(void) { - byte truncated[] = {0x12, 0x60, 0x00, 0x00, 0x01}; - byte wrongCode[] = {0x12, 0xFF, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00}; + byte pubKeyX[48], pubKeyY[48]; + word32 xSz = sizeof(pubKeyX), ySz = sizeof(pubKeyY); TEST_CTX_SETUP(); + printf("test_export_ephemeral_pub_key...\n"); - printf("test_parse_measurements_negative...\n"); - - /* Truncated buffer */ - ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, truncated, sizeof(truncated))); + /* NULL args */ + TEST_ASSERT(wolfSPDM_ExportEphemeralPubKey(NULL, pubKeyX, &xSz, + pubKeyY, &ySz) != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_ExportEphemeralPubKey(ctx, NULL, &xSz, + pubKeyY, &ySz) != WOLFSPDM_SUCCESS, "NULL pubKeyX"); - /* Wrong response code */ - ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, wrongCode, sizeof(wrongCode))); + /* No key generated yet */ + TEST_ASSERT(wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &xSz, + pubKeyY, &ySz) != WOLFSPDM_SUCCESS, "no key should fail"); - /* NULL inputs */ - ASSERT_FAIL(wolfSPDM_ParseMeasurements(NULL, truncated, sizeof(truncated))); - ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, NULL, sizeof(truncated))); - - /* Zero length */ - ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, truncated, 0)); + /* Generate key, then export */ + ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); + xSz = sizeof(pubKeyX); + ySz = sizeof(pubKeyY); + ASSERT_SUCCESS(wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &xSz, + pubKeyY, &ySz)); + ASSERT_EQ(xSz, 48, "X size"); + ASSERT_EQ(ySz, 48, "Y size"); TEST_CTX_FREE(); TEST_PASS(); } -#endif /* !NO_WOLFSPDM_MEAS */ -static int test_version_fallback(void) +static int test_sign_hash_null_args(void) { - /* Fake VERSION response with versions 1.0, 1.1, 1.2, 1.3 */ - byte rsp[] = { - 0x10, SPDM_VERSION, 0x00, 0x00, /* header */ - 0x04, 0x00, /* entryCount = 4 */ - 0x00, 0x10, /* 1.0 */ - 0x00, 0x11, /* 1.1 */ - 0x00, 0x12, /* 1.2 */ - 0x00, 0x13 /* 1.3 */ - }; - byte rsp14[] = { - 0x10, SPDM_VERSION, 0x00, 0x00, - 0x05, 0x00, - 0x00, 0x10, - 0x00, 0x11, - 0x00, 0x12, - 0x00, 0x13, - 0x00, 0x14 - }; + byte hash[48], sig[128]; + word32 sigSz = sizeof(sig); TEST_CTX_SETUP(); + printf("test_sign_hash_null_args...\n"); + XMEMSET(hash, 0xAA, sizeof(hash)); - printf("test_version_fallback...\n"); + TEST_ASSERT(wolfSPDM_SignHash(NULL, hash, 48, sig, &sigSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_SignHash(ctx, NULL, 48, sig, &sigSz) + != WOLFSPDM_SUCCESS, "NULL hash"); + TEST_ASSERT(wolfSPDM_SignHash(ctx, hash, 48, NULL, &sigSz) + != WOLFSPDM_SUCCESS, "NULL sig"); + TEST_ASSERT(wolfSPDM_SignHash(ctx, hash, 48, sig, NULL) + != WOLFSPDM_SUCCESS, "NULL sigSz"); - /* With no maxVersion set, should select 1.3 (highest mutual) */ - ASSERT_SUCCESS(wolfSPDM_ParseVersion(ctx, rsp, sizeof(rsp))); - ASSERT_EQ(ctx->spdmVersion, SPDM_VERSION_13, - "Should select 1.3 as highest mutual"); + TEST_CTX_FREE(); + TEST_PASS(); +} - /* Reset state and set maxVersion to 1.2 */ - ctx->state = WOLFSPDM_STATE_INIT; - ctx->spdmVersion = 0; - ctx->maxVersion = SPDM_VERSION_12; - ASSERT_SUCCESS(wolfSPDM_ParseVersion(ctx, rsp, sizeof(rsp))); - ASSERT_EQ(ctx->spdmVersion, SPDM_VERSION_12, - "Should fall back to 1.2 with maxVersion cap"); +static int test_verify_signature_null_args(void) +{ + byte hash[48], sig[96]; + TEST_CTX_SETUP(); + printf("test_verify_signature_null_args...\n"); + XMEMSET(hash, 0xAA, sizeof(hash)); + XMEMSET(sig, 0xBB, sizeof(sig)); - /* A responder advertising 1.4 should be selected when we allow it. */ - ctx->state = WOLFSPDM_STATE_INIT; - ctx->spdmVersion = 0; - ctx->maxVersion = 0; /* compile-time default = 1.4 */ - ASSERT_SUCCESS(wolfSPDM_ParseVersion(ctx, rsp14, sizeof(rsp14))); - ASSERT_EQ(ctx->spdmVersion, SPDM_VERSION_14, - "Should select 1.4 when offered and allowed"); + TEST_ASSERT(wolfSPDM_VerifySignature(NULL, hash, 48, sig, 96) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_VerifySignature(ctx, NULL, 48, sig, 96) + != WOLFSPDM_SUCCESS, "NULL hash"); + TEST_ASSERT(wolfSPDM_VerifySignature(ctx, hash, 48, NULL, 96) + != WOLFSPDM_SUCCESS, "NULL sig"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_set_max_version(void) +static int test_sign_verify_roundtrip(void) { + byte hash[48], sig[128]; + word32 sigSz = sizeof(sig); + byte privKey[48], pubKeyX[48], pubKeyY[48], pubKey[96]; + word32 privSz = 48, xSz = 48, ySz = 48; + ecc_key ecKey; TEST_CTX_SETUP(); + printf("test_sign_verify_roundtrip...\n"); + + /* Generate a P-384 key pair */ + ASSERT_SUCCESS(wc_ecc_init(&ecKey)); + ASSERT_SUCCESS(wc_ecc_make_key(&ctx->rng, 48, &ecKey)); + ASSERT_SUCCESS(wc_ecc_export_private_only(&ecKey, privKey, &privSz)); + ASSERT_SUCCESS(wc_ecc_export_public_raw(&ecKey, pubKeyX, &xSz, + pubKeyY, &ySz)); + wc_ecc_free(&ecKey); - printf("test_set_max_version...\n"); + XMEMCPY(pubKey, pubKeyX, 48); + XMEMCPY(pubKey + 48, pubKeyY, 48); - /* Valid versions */ - ASSERT_SUCCESS(wolfSPDM_SetMaxVersion(ctx, SPDM_VERSION_12)); - ASSERT_EQ(ctx->maxVersion, SPDM_VERSION_12, "maxVersion should be 0x12"); - ASSERT_SUCCESS(wolfSPDM_SetMaxVersion(ctx, SPDM_VERSION_14)); - ASSERT_EQ(ctx->maxVersion, SPDM_VERSION_14, "maxVersion should be 0x14"); + /* Set requester key pair for signing */ + ASSERT_SUCCESS(wolfSPDM_SetRequesterKeyPair(ctx, privKey, 48, pubKey, 96)); + /* Set responder pub key for verification */ + ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(ctx, pubKey, 96)); - /* Reset to default */ - ASSERT_SUCCESS(wolfSPDM_SetMaxVersion(ctx, 0)); - ASSERT_EQ(ctx->maxVersion, 0, "maxVersion should be 0 (default)"); + /* Sign */ + XMEMSET(hash, 0x42, sizeof(hash)); + ASSERT_SUCCESS(wolfSPDM_SignHash(ctx, hash, 48, sig, &sigSz)); + ASSERT_EQ(sigSz, 96, "sig should be 96 bytes"); - /* Invalid: too low */ - ASSERT_FAIL(wolfSPDM_SetMaxVersion(ctx, 0x11)); - /* Invalid: too high */ - ASSERT_FAIL(wolfSPDM_SetMaxVersion(ctx, 0x15)); - /* NULL ctx */ - ASSERT_FAIL(wolfSPDM_SetMaxVersion(NULL, SPDM_VERSION_12)); + /* Verify */ + ASSERT_SUCCESS(wolfSPDM_VerifySignature(ctx, hash, 48, sig, sigSz)); + /* Flip a bit - should fail */ + sig[10] ^= 0x01; + TEST_ASSERT(wolfSPDM_VerifySignature(ctx, hash, 48, sig, sigSz) + != WOLFSPDM_SUCCESS, "flipped sig should fail"); + + wc_ForceZero(privKey, sizeof(privKey)); TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* Sequence Number Wrap Tests */ -/* ========================================================================== */ +/* ----- Group G: Internal KDF ----- */ -static int test_sequence_number_mismatch(void) +static int test_derive_handshake_keys(void) { - /* DSP0277 Sec. 11 mandates strict monotonic sequence numbers on the - * receive side. Plant a record whose wire seqNum doesn't match the - * locally expected counter and confirm DecryptInternal refuses it. */ - byte plain[] = "hello-spdm"; - byte enc[256]; - byte dec[256]; - word32 encSz = sizeof(enc); - word32 decSz = sizeof(dec); - int i; + byte th1[48]; + byte zeros[48]; TEST_CTX_SETUP_V12(); + printf("test_derive_handshake_keys...\n"); - printf("test_sequence_number_mismatch...\n"); + XMEMSET(th1, 0xAB, sizeof(th1)); + XMEMSET(zeros, 0, sizeof(zeros)); - ctx->state = WOLFSPDM_STATE_CONNECTED; - ctx->sessionId = 0xCAFEBABE; - for (i = 0; i < WOLFSPDM_AEAD_KEY_SIZE; i++) { - ctx->reqDataKey[i] = (byte)(i + 1); - ctx->rspDataKey[i] = (byte)(i + 1); - } - for (i = 0; i < WOLFSPDM_AEAD_IV_SIZE; i++) { - ctx->reqDataIv[i] = (byte)(0x40 + i); - ctx->rspDataIv[i] = (byte)(0x40 + i); - } + /* NULL args */ + TEST_ASSERT(wolfSPDM_DeriveHandshakeKeys(NULL, th1) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_DeriveHandshakeKeys(ctx, NULL) + != WOLFSPDM_SUCCESS, "NULL th1"); - /* Encrypt at req seq = 5 (wire seqNum encoded as 5). */ - ctx->reqSeqNum = 5; - ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, sizeof(plain), - enc, &encSz)); + /* Set up shared secret */ + XMEMSET(ctx->sharedSecret, 0x5A, WOLFSPDM_ECC_KEY_SIZE); + ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; - /* Decrypt-side expects seq = 7, not 5 - must reject. */ - ctx->rspSeqNum = 7; - ASSERT_EQ(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz), - WOLFSPDM_E_SEQUENCE, "seqNum mismatch must return SEQUENCE error"); + ASSERT_SUCCESS(wolfSPDM_DeriveHandshakeKeys(ctx, th1)); - /* Sanity: when seq matches, decrypt succeeds. */ - ctx->rspSeqNum = 5; - decSz = sizeof(dec); - ASSERT_SUCCESS(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz)); - ASSERT_EQ(memcmp(dec, plain, sizeof(plain)), 0, "Decrypt payload mismatch"); + /* Verify derived keys are non-zero */ + TEST_ASSERT(memcmp(ctx->handshakeSecret, zeros, 48) != 0, + "handshakeSecret is zero"); + TEST_ASSERT(memcmp(ctx->reqHsSecret, zeros, 48) != 0, + "reqHsSecret is zero"); + TEST_ASSERT(memcmp(ctx->rspHsSecret, zeros, 48) != 0, + "rspHsSecret is zero"); + TEST_ASSERT(memcmp(ctx->reqDataKey, zeros, 32) != 0, + "reqDataKey is zero"); + TEST_ASSERT(memcmp(ctx->rspDataKey, zeros, 32) != 0, + "rspDataKey is zero"); + /* req and rsp keys should differ */ + TEST_ASSERT(memcmp(ctx->reqDataKey, ctx->rspDataKey, 32) != 0, + "req/rsp keys should differ"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_sequence_number_wrap(void) +static int test_derive_from_handshake_secret(void) { - byte plain[] = "hello-spdm"; - byte enc[256]; - byte dec[256]; - word32 encSz; - word32 decSz; - int i; + byte th1[48]; + byte zeros[48]; TEST_CTX_SETUP_V12(); + printf("test_derive_from_handshake_secret...\n"); - printf("test_sequence_number_wrap...\n"); + XMEMSET(th1, 0xCD, sizeof(th1)); + XMEMSET(zeros, 0, sizeof(zeros)); + XMEMSET(ctx->handshakeSecret, 0x5A, WOLFSPDM_HASH_SIZE); - /* Zero the encrypt buffer so a future change that reads before the - * seq-check fails would be visible rather than reading stack garbage. */ - XMEMSET(enc, 0, sizeof(enc)); + ASSERT_SUCCESS(wolfSPDM_DeriveFromHandshakeSecret(ctx, th1)); + TEST_ASSERT(memcmp(ctx->reqHsSecret, zeros, 48) != 0, + "reqHsSecret is zero"); + TEST_ASSERT(memcmp(ctx->reqFinishedKey, zeros, 48) != 0, + "reqFinishedKey is zero"); + TEST_ASSERT(memcmp(ctx->rspFinishedKey, zeros, 48) != 0, + "rspFinishedKey is zero"); - /* Set up a fake session: loopback (req keys == rsp keys). */ - ctx->state = WOLFSPDM_STATE_CONNECTED; - ctx->sessionId = 0xDEADBEEF; - for (i = 0; i < WOLFSPDM_AEAD_KEY_SIZE; i++) { - ctx->reqDataKey[i] = (byte)i; - ctx->rspDataKey[i] = (byte)i; - } - for (i = 0; i < WOLFSPDM_AEAD_IV_SIZE; i++) { - ctx->reqDataIv[i] = (byte)(0x20 + i); - ctx->rspDataIv[i] = (byte)(0x20 + i); - } + TEST_CTX_FREE(); + TEST_PASS(); +} - /* DSP0277 Sec. 11.3: wire seqNum is 16-bit and shall not wrap. wolfSPDM_BuildIV - * mixes only the low 16 bits into the AES-GCM IV, so any wrap would reuse - * an IV under the same key. Encrypt/decrypt must refuse to proceed past - * the 16-bit boundary; caller is expected to wolfSPDM_KeyUpdate first. */ +static int test_derive_app_data_keys(void) +{ + byte zeros[48]; + TEST_CTX_SETUP_V12(); + printf("test_derive_app_data_keys...\n"); - /* Counter planted just past the 16-bit wire boundary: encrypt must - * refuse rather than reuse an IV. */ - ctx->reqSeqNum = 0x10000; - encSz = sizeof(enc); - ASSERT_EQ(wolfSPDM_EncryptInternal(ctx, plain, sizeof(plain), enc, &encSz), - WOLFSPDM_E_SEQUENCE, "Encrypt past seq=0xFFFF must be refused"); + XMEMSET(zeros, 0, sizeof(zeros)); + TEST_ASSERT(wolfSPDM_DeriveAppDataKeys(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx"); - /* Decrypt-side cap mirrors the encrypt side. Use a separate output - * buffer so a future change that writes before checking seqNum would - * be caught instead of silently corrupting the input. */ - ctx->rspSeqNum = 0x10000; - decSz = sizeof(dec); - ASSERT_EQ(wolfSPDM_DecryptInternal(ctx, enc, sizeof(enc), dec, &decSz), - WOLFSPDM_E_SEQUENCE, "Decrypt past seq=0xFFFF must be refused"); + /* Set up handshake secret and transcript */ + XMEMSET(ctx->handshakeSecret, 0x5A, WOLFSPDM_HASH_SIZE); + wolfSPDM_TranscriptAdd(ctx, (byte*)"test data for th2", 17); + ctx->reqSeqNum = 99; + ctx->rspSeqNum = 99; - /* A counter just inside the limit still works. */ - ctx->reqSeqNum = 0xFFFF; - encSz = sizeof(enc); - ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, sizeof(plain), - enc, &encSz)); + ASSERT_SUCCESS(wolfSPDM_DeriveAppDataKeys(ctx)); + TEST_ASSERT(memcmp(ctx->reqDataKey, zeros, 32) != 0, + "reqDataKey is zero"); + TEST_ASSERT(memcmp(ctx->rspDataKey, zeros, 32) != 0, + "rspDataKey is zero"); + ASSERT_EQ(ctx->reqSeqNum, 0, "reqSeqNum not reset"); + ASSERT_EQ(ctx->rspSeqNum, 0, "rspSeqNum not reset"); TEST_CTX_FREE(); TEST_PASS(); } -/* ========================================================================== */ -/* Session State Tests */ -/* ========================================================================== */ +/* ----- Group H: Internal Message Building ----- */ -static int test_session_state(void) +static int test_build_key_exchange_null_args(void) { - TEST_CTX_SETUP(); - - printf("test_session_state...\n"); - ASSERT_EQ(wolfSPDM_IsConnected(ctx), 0, "Should not be connected"); - ASSERT_EQ(wolfSPDM_GetSessionId(ctx), 0, "SessionId should be 0"); - - /* Mid-handshake (KEY_EXCHANGE done, FINISH pending): IsConnected is - * still 0 but sessionId IS available so the I/O callback can tag - * the encrypted FINISH record. */ - ctx->state = WOLFSPDM_STATE_KEY_EX; - ctx->sessionId = 0x12345678; - ASSERT_EQ(wolfSPDM_IsConnected(ctx), 0, - "IsConnected should remain 0 mid-handshake"); - ASSERT_EQ(wolfSPDM_GetSessionId(ctx), (word32)0x12345678, - "GetSessionId must return value before STATE_CONNECTED"); + byte buf[256]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_key_exchange_null_args...\n"); - /* Simulate connected state */ - ctx->state = WOLFSPDM_STATE_CONNECTED; - ctx->sessionId = 0xAABBCCDD; - ctx->spdmVersion = SPDM_VERSION_12; - ASSERT_EQ(wolfSPDM_IsConnected(ctx), 1, "Should be connected"); - ASSERT_EQ(wolfSPDM_GetSessionId(ctx), (word32)0xAABBCCDD, "SessionId wrong"); - ASSERT_EQ(wolfSPDM_GetNegotiatedVersion(ctx), SPDM_VERSION_12, "Version wrong"); + TEST_ASSERT(wolfSPDM_BuildKeyExchange(NULL, buf, &bufSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_BuildKeyExchange(ctx, NULL, &bufSz) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_BuildKeyExchange(ctx, buf, NULL) + != WOLFSPDM_SUCCESS, "NULL bufSz"); + bufSz = 4; + TEST_ASSERT(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz) + != WOLFSPDM_SUCCESS, "small buffer"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_const_compare(void) +static int test_build_key_exchange_format(void) { - /* wolfSPDM_ConstCompare must return non-zero for any single-byte - * difference and 0 only for equal buffers. Catches the |= -> &= - * accumulator mutation that the HMAC compare relies on. */ - byte a[16], b[16]; - int i; + byte buf[256]; + word32 bufSz = sizeof(buf); + byte zeros[48]; + TEST_CTX_SETUP_V12(); + printf("test_build_key_exchange_format...\n"); - printf("test_const_compare...\n"); + XMEMSET(zeros, 0, sizeof(zeros)); + ctx->reqSessionId = 0x0001; - for (i = 0; i < 16; i++) { a[i] = (byte)i; b[i] = (byte)i; } - ASSERT_EQ(wolfSPDM_ConstCompare(a, b, 16), 0, - "Equal buffers must compare equal"); + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); + ASSERT_EQ(buf[0], SPDM_VERSION_12, "wrong version"); + ASSERT_EQ(buf[1], 0xE4, "wrong opcode (KEY_EXCHANGE)"); + TEST_ASSERT(bufSz > 100, "message too small"); + /* Ephemeral key should be generated */ + ASSERT_EQ(ctx->flags.ephemeralKeyInit, 1, "ephemeral key not init"); - /* Differ only at index 0 */ - b[0] ^= 0xFF; - ASSERT_NE(wolfSPDM_ConstCompare(a, b, 16), 0, "Diff at index 0"); - b[0] = a[0]; + TEST_CTX_FREE(); + TEST_PASS(); +} - /* Differ only at the last index */ - b[15] ^= 0x01; - ASSERT_NE(wolfSPDM_ConstCompare(a, b, 16), 0, "Diff at last index"); - b[15] = a[15]; +static int test_build_finish_null_args(void) +{ + byte buf[256]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_finish_null_args...\n"); - /* Asymmetric pair {0x01, 0x00} vs {0x00, 0x01} - a |=-to-&= mutation - * would falsely report equal here. */ - a[0] = 0x01; a[1] = 0x00; - b[0] = 0x00; b[1] = 0x01; - ASSERT_NE(wolfSPDM_ConstCompare(a, b, 2), 0, - "Asymmetric pair must compare unequal"); + TEST_ASSERT(wolfSPDM_BuildFinish(NULL, buf, &bufSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_BuildFinish(ctx, NULL, &bufSz) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_BuildFinish(ctx, buf, NULL) + != WOLFSPDM_SUCCESS, "NULL bufSz"); - g_testsPassed++; - return 0; + TEST_CTX_FREE(); + TEST_PASS(); } -static int test_build_iv_byte_positions(void) +static int test_build_finish_format(void) { - /* wolfSPDM_BuildIV XORs the low 2 bytes of seqNum into iv[0]/iv[1]. - * Pin byte positions so swap/offset mutations are caught. */ - byte baseIv[WOLFSPDM_AEAD_IV_SIZE]; - byte iv[WOLFSPDM_AEAD_IV_SIZE]; - word32 i; + byte buf[256]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_finish_format...\n"); - printf("test_build_iv_byte_positions...\n"); + ctx->mutAuthRequested = 0; /* No mutual auth */ + XMEMSET(ctx->reqFinishedKey, 0x5A, WOLFSPDM_HASH_SIZE); + wolfSPDM_TranscriptAdd(ctx, (byte*)"test transcript", 15); - XMEMSET(baseIv, 0, sizeof(baseIv)); - wolfSPDM_BuildIV(iv, baseIv, (word64)0x1234); + ASSERT_SUCCESS(wolfSPDM_BuildFinish(ctx, buf, &bufSz)); + ASSERT_EQ(buf[0], SPDM_VERSION_12, "wrong version"); + ASSERT_EQ(buf[1], 0xE5, "wrong opcode (FINISH)"); + ASSERT_EQ(buf[2], 0, "sigIncluded should be 0"); + ASSERT_EQ(bufSz, 52, "expected 4 header + 48 HMAC"); - ASSERT_EQ(iv[0], (byte)0x34, "iv[0] must hold low byte of seqNum"); - ASSERT_EQ(iv[1], (byte)0x12, "iv[1] must hold high byte of seqNum"); - for (i = 2; i < WOLFSPDM_AEAD_IV_SIZE; i++) { - ASSERT_EQ(iv[i], (byte)0, "iv past byte 1 must not be touched"); - } - g_testsPassed++; - return 0; + TEST_CTX_FREE(); + TEST_PASS(); } -static int test_decrypt_session_id_mismatch(void) +/* ----- Group I: Internal Encrypt/Decrypt ----- */ + +static int test_encrypt_internal_null_args(void) { - /* Encrypt at one sessionId, then change ctx->sessionId and assert - * decrypt refuses with WOLFSPDM_E_SESSION_INVALID. */ - byte plain[] = "x"; + byte plain[16] = {0}; byte enc[256]; - byte dec[256]; + word32 encSz = sizeof(enc); + TEST_CTX_SETUP_V12(); + printf("test_encrypt_internal_null_args...\n"); + + TEST_ASSERT(wolfSPDM_EncryptInternal(NULL, plain, 16, enc, &encSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_EncryptInternal(ctx, NULL, 16, enc, &encSz) + != WOLFSPDM_SUCCESS, "NULL plain"); + TEST_ASSERT(wolfSPDM_EncryptInternal(ctx, plain, 16, NULL, &encSz) + != WOLFSPDM_SUCCESS, "NULL enc"); + TEST_ASSERT(wolfSPDM_EncryptInternal(ctx, plain, 16, enc, NULL) + != WOLFSPDM_SUCCESS, "NULL encSz"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_encrypt_decrypt_roundtrip(void) +{ + byte plain[16] = "Hello SPDM test!"; + static byte enc[512]; + static byte dec[256]; word32 encSz = sizeof(enc); word32 decSz = sizeof(dec); - int i; TEST_CTX_SETUP_V12(); + printf("test_encrypt_decrypt_roundtrip...\n"); + + /* Set up session keys (same for req/rsp for self-roundtrip) */ + ctx->sessionId = 0x00020001; + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->reqDataIv, 0x22, WOLFSPDM_AEAD_IV_SIZE); + XMEMSET(ctx->rspDataIv, 0x22, WOLFSPDM_AEAD_IV_SIZE); - printf("test_decrypt_session_id_mismatch...\n"); + /* Encrypt */ + ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, 16, enc, &encSz)); + TEST_ASSERT(encSz > 16, "encrypted should be larger"); - ctx->state = WOLFSPDM_STATE_CONNECTED; - ctx->sessionId = 0xAAAAAAAA; - for (i = 0; i < WOLFSPDM_AEAD_KEY_SIZE; i++) { - ctx->reqDataKey[i] = (byte)i; - ctx->rspDataKey[i] = (byte)i; - } - for (i = 0; i < WOLFSPDM_AEAD_IV_SIZE; i++) { - ctx->reqDataIv[i] = (byte)(0x20 + i); - ctx->rspDataIv[i] = (byte)(0x20 + i); - } - ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, sizeof(plain), - enc, &encSz)); + /* Reset rsp seq to match what was encrypted (req incremented to 1) */ + ctx->rspSeqNum = 0; - ctx->sessionId = 0xBBBBBBBB; - ASSERT_EQ(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz), - WOLFSPDM_E_SESSION_INVALID, - "Decrypt with mismatched sessionId must refuse"); + /* Decrypt */ + ASSERT_SUCCESS(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz)); + ASSERT_EQ(decSz, 16, "decrypted size mismatch"); + TEST_ASSERT(memcmp(dec, plain, 16) == 0, "plaintext mismatch"); TEST_CTX_FREE(); TEST_PASS(); } -static int test_encrypt_decrypt_empty_payload(void) +#ifdef WOLFSPDM_TCG +static int test_encrypt_decrypt_roundtrip_tcg(void) { - /* DSP0277 boundary: plainSz=0 should round-trip cleanly through the - * MCTP path. */ - byte plain[1]; - byte enc[64]; - byte dec[64]; + byte plain[16] = "TCG encrypt tst!"; + static byte enc[512]; + static byte dec[256]; word32 encSz = sizeof(enc); word32 decSz = sizeof(dec); - int i; TEST_CTX_SETUP_V12(); + printf("test_encrypt_decrypt_roundtrip_tcg...\n"); - printf("test_encrypt_decrypt_empty_payload...\n"); + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS); + ctx->sessionId = 0x00020001; + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + XMEMSET(ctx->reqDataKey, 0x33, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataKey, 0x33, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->reqDataIv, 0x44, WOLFSPDM_AEAD_IV_SIZE); + XMEMSET(ctx->rspDataIv, 0x44, WOLFSPDM_AEAD_IV_SIZE); - ctx->state = WOLFSPDM_STATE_CONNECTED; - ctx->sessionId = 0x12345678; - for (i = 0; i < WOLFSPDM_AEAD_KEY_SIZE; i++) { - ctx->reqDataKey[i] = (byte)(0x40 + i); - ctx->rspDataKey[i] = (byte)(0x40 + i); - } - for (i = 0; i < WOLFSPDM_AEAD_IV_SIZE; i++) { - ctx->reqDataIv[i] = (byte)(0x80 + i); - ctx->rspDataIv[i] = (byte)(0x80 + i); - } + ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, 16, enc, &encSz)); + TEST_ASSERT(encSz > 16, "encrypted should be larger"); - ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, 0, enc, &encSz)); + ctx->rspSeqNum = 0; ASSERT_SUCCESS(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz)); - ASSERT_EQ(decSz, (word32)0, "Decrypted size must be 0 for empty payload"); + ASSERT_EQ(decSz, 16, "decrypted size mismatch"); + TEST_ASSERT(memcmp(dec, plain, 16) == 0, "plaintext mismatch"); TEST_CTX_FREE(); TEST_PASS(); } +#endif /* WOLFSPDM_TCG */ -static int test_parse_version_edge_cases(void) +#ifdef WOLFSPDM_RESPONDER + +static int g_tpmCbInvocations = 0; +static byte g_tpmCbLastCmd[256]; +static word32 g_tpmCbLastCmdSz; + +static int responder_tpm_stub(void* userCtx, + const byte* cmd, word32 cmdSz, + byte* resp, word32 respBufSz, word32* respSz) { - /* (a) entryCount = 0 -> MISMATCH - * (b) all entries below WOLFSPDM_MIN_SPDM_VERSION -> MISMATCH */ - byte rsp[64]; - TEST_CTX_SETUP(); + (void)userCtx; + if (respBufSz < 10) { + return -1; + } + g_tpmCbInvocations++; + if (cmdSz <= sizeof(g_tpmCbLastCmd)) { + XMEMCPY(g_tpmCbLastCmd, cmd, cmdSz); + g_tpmCbLastCmdSz = cmdSz; + } + /* Return a fixed TPM2 TPM_ST_NO_SESSIONS / size=10 / TPM_RC_SUCCESS reply. */ + resp[0] = 0x80; resp[1] = 0x01; + resp[2] = 0x00; resp[3] = 0x00; resp[4] = 0x00; resp[5] = 0x0A; + resp[6] = 0x00; resp[7] = 0x00; resp[8] = 0x00; resp[9] = 0x00; + *respSz = 10; + return 0; +} + +static int test_responder_init_free(void) +{ + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + + printf("test_responder_init_free...\n"); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); + wolfSPDM_RespFree(rctx); + /* Idempotent: re-free should be safe. */ + wolfSPDM_RespFree(rctx); + wolfSPDM_RespFree(NULL); + TEST_PASS(); +} - printf("test_parse_version_edge_cases...\n"); +static int test_responder_setmode_rejects_both_off(void) +{ + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + int rc; - /* (a) Zero entries */ - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_12; - rsp[1] = SPDM_VERSION; - SPDM_Set16LE(&rsp[4], 0); - ASSERT_EQ(wolfSPDM_ParseVersion(ctx, rsp, 6), - WOLFSPDM_E_VERSION_MISMATCH, "entryCount=0 must fail"); + printf("test_responder_setmode_rejects_both_off...\n"); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); + rc = wolfSPDM_RespSetMode(rctx, 0, 0); + TEST_ASSERT(rc != WOLFSPDM_SUCCESS, "Both modes off must error"); + wolfSPDM_RespFree(rctx); + TEST_PASS(); +} - /* (b) Two entries, both below the 1.2 minimum (1.0 and 1.1) */ - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_12; - rsp[1] = SPDM_VERSION; - SPDM_Set16LE(&rsp[4], 2); - rsp[7] = 0x10; - rsp[9] = 0x11; - ASSERT_EQ(wolfSPDM_ParseVersion(ctx, rsp, 10), - WOLFSPDM_E_VERSION_MISMATCH, "All-below-min must fail"); +/* Plaintext-bypass regression. With SPDM mode active, a raw TPM2 frame + * (tag 0x8001, the swtpm-mssim plaintext path) must be rejected with + * WOLFSPDM_E_FRAMING and never reach the dispatcher. */ +static int test_responder_no_plaintext_bypass(void) +{ + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + /* Use a buffer >= TCG header (16) so the test exercises the tag + * comparison, not just the length check. */ + byte rawTpm[24]; + byte outBuf[64]; + word32 outSz = sizeof(outBuf); + int rc; - TEST_CTX_FREE(); + printf("test_responder_no_plaintext_bypass...\n"); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); +#ifdef WOLFSPDM_TCG + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 1, 0)); +#else + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 0, 1)); +#endif + g_tpmCbInvocations = 0; + ASSERT_SUCCESS(wolfSPDM_RespSetTpmCallback(rctx, responder_tpm_stub, NULL)); + + /* Raw TPM2 frame: tag 0x8001 (TPM_ST_NO_SESSIONS), size 0x18, + * remainder zero. Length passes the 16-byte gate so the responder + * proceeds to the tag check and rejects on tag != 0x8101/0x8201. */ + XMEMSET(rawTpm, 0, sizeof(rawTpm)); + rawTpm[0] = 0x80; rawTpm[1] = 0x01; + rawTpm[5] = 0x18; + outSz = sizeof(outBuf); + rc = wolfSPDM_RespHandleMessage(rctx, rawTpm, sizeof(rawTpm), + outBuf, &outSz); + TEST_ASSERT(rc == WOLFSPDM_E_FRAMING, + "Tagged TPM2 frame must be rejected by tag check with E_FRAMING"); + ASSERT_EQ(g_tpmCbInvocations, 0, + "TPM callback must NOT have been invoked"); + wolfSPDM_RespFree(rctx); + TEST_PASS(); +} + +#ifdef WOLFSPDM_TCG +/* A TCG *clear* frame (tag 0x8101) carrying VENDOR_DEFINED TPM2_CMD passes + * the tag check, so it must be refused by the vendor handler instead. */ +static int test_responder_no_clear_tpm2_cmd(void) +{ + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + byte tpmCmd[12]; + byte spdmMsg[64]; + byte frame[128]; + byte outBuf[256]; + word32 outSz = sizeof(outBuf); + int spdmMsgSz; + int rc; + int v; + + printf("test_responder_no_clear_tpm2_cmd...\n"); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 1, 0)); + g_tpmCbInvocations = 0; + ASSERT_SUCCESS(wolfSPDM_RespSetTpmCallback(rctx, responder_tpm_stub, NULL)); + + /* TPM2_Startup(SU_CLEAR) */ + XMEMSET(tpmCmd, 0, sizeof(tpmCmd)); + tpmCmd[0] = 0x80; tpmCmd[1] = 0x01; + tpmCmd[5] = 0x0C; + tpmCmd[8] = 0x01; tpmCmd[9] = 0x44; + + /* Every vendor code gated to secured frames must be refused here. */ + for (v = 0; v < 3; v++) { + const char* vd = (v == 0) ? WOLFSPDM_VDCODE_TPM2_CMD : + (v == 1) ? WOLFSPDM_VDCODE_GIVE_PUB : + WOLFSPDM_VDCODE_SPDMONLY; + spdmMsgSz = wolfSPDM_BuildVendorDefined(SPDM_VERSION_13, + vd, tpmCmd, (word32)sizeof(tpmCmd), spdmMsg, sizeof(spdmMsg)); + TEST_ASSERT(spdmMsgSz > 0, "BuildVendorDefined failed"); + + XMEMSET(frame, 0, sizeof(frame)); + frame[0] = 0x81; frame[1] = 0x01; /* WOLFSPDM_TCG_TAG_CLEAR */ + frame[2] = 0; frame[3] = 0; + frame[4] = (byte)(((word32)spdmMsgSz + WOLFSPDM_TCG_HEADER_SIZE) >> 8); + frame[5] = (byte)((word32)spdmMsgSz + WOLFSPDM_TCG_HEADER_SIZE); + XMEMCPY(frame + WOLFSPDM_TCG_HEADER_SIZE, spdmMsg, + (size_t)spdmMsgSz); + + outSz = sizeof(outBuf); + rc = wolfSPDM_RespHandleMessage(rctx, frame, + (word32)spdmMsgSz + WOLFSPDM_TCG_HEADER_SIZE, outBuf, &outSz); + TEST_ASSERT(rc != WOLFSPDM_SUCCESS, + "Clear-frame secured-only vendor code must not succeed"); + ASSERT_EQ(g_tpmCbInvocations, 0, + "TPM callback must NOT run for a clear frame"); + /* SPDMONLY must not have been able to change the lock state. */ + ASSERT_EQ(wolfSPDM_RespIsLocked(rctx), 0, + "clear frame must not alter SPDM-only lock"); + } + wolfSPDM_RespFree(rctx); TEST_PASS(); } -static int test_parse_algorithms_zero_numalgs(void) +/* Secured records are valid only after key exchange establishes session + * traffic state. Initialization and reset must both reject them before + * attempting to parse the record. */ +static int test_responder_secured_requires_session(void) { - /* numAlgs=0 must trip the !dheOk || !aeadOk || !ksOk guard. */ - byte rsp[64]; - TEST_CTX_SETUP_V12(); + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + byte frame[WOLFSPDM_TCG_HEADER_SIZE + 1]; + byte outBuf[64]; + word32 outSz; + int rc; + + printf("test_responder_secured_requires_session...\n"); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 1, 0)); + g_tpmCbInvocations = 0; + ASSERT_SUCCESS(wolfSPDM_RespSetTpmCallback(rctx, responder_tpm_stub, + NULL)); + + XMEMSET(frame, 0, sizeof(frame)); + wolfSPDM_WriteTcgHeader(frame, WOLFSPDM_TCG_TAG_SECURED, + sizeof(frame), 0, 0); + + outSz = sizeof(outBuf); + rc = wolfSPDM_RespHandleMessage(rctx, frame, sizeof(frame), outBuf, + &outSz); + ASSERT_EQ(rc, WOLFSPDM_E_BAD_STATE, + "secured record without a session must return BAD_STATE"); + ASSERT_EQ(g_tpmCbInvocations, 0, + "record without a session must not reach the TPM callback"); + + /* Reset must leave the responder in a sessionless state. */ + wolfSPDM_RespReset(rctx); + outSz = sizeof(outBuf); + rc = wolfSPDM_RespHandleMessage(rctx, frame, sizeof(frame), outBuf, + &outSz); + ASSERT_EQ(rc, WOLFSPDM_E_BAD_STATE, + "secured record after reset must return BAD_STATE"); + ASSERT_EQ(g_tpmCbInvocations, 0, + "reset record must not reach the TPM callback"); + + wolfSPDM_RespFree(rctx); + TEST_PASS(); +} + +#ifdef WOLFSPDM_NATIONS +/* Send one vendor-defined command in a TCG clear frame. */ +static int resp_send_clear_vd(WOLFSPDM_RESP_CTX* rctx, const char* vdCode, + const byte* payload, word32 payloadSz, byte* out, word32* outSz) +{ + byte spdmMsg[256]; + byte frame[320]; + int spdmMsgSz; + + spdmMsgSz = wolfSPDM_BuildVendorDefined(SPDM_VERSION_13, vdCode, + payload, payloadSz, spdmMsg, sizeof(spdmMsg)); + if (spdmMsgSz <= 0) { + return spdmMsgSz; + } + XMEMSET(frame, 0, sizeof(frame)); + frame[0] = 0x81; frame[1] = 0x01; + frame[4] = (byte)(((word32)spdmMsgSz + WOLFSPDM_TCG_HEADER_SIZE) >> 8); + frame[5] = (byte)((word32)spdmMsgSz + WOLFSPDM_TCG_HEADER_SIZE); + XMEMCPY(frame + WOLFSPDM_TCG_HEADER_SIZE, spdmMsg, (size_t)spdmMsgSz); + return wolfSPDM_RespHandleMessage(rctx, frame, + (word32)spdmMsgSz + WOLFSPDM_TCG_HEADER_SIZE, out, outSz); +} + +/* A provisioned PSK may only be replaced after an authenticated PSK_CLR_. */ +static int test_responder_psk_replace_guard(void) +{ + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + byte setPayload[WOLFSPDM_PSK_MAX_SIZE + WOLFSPDM_HASH_SIZE]; + byte clearAuth[32]; + byte outBuf[256]; + word32 outSz; + int rc; - printf("test_parse_algorithms_zero_numalgs...\n"); + printf("test_responder_psk_replace_guard...\n"); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 1, 1)); - XMEMSET(rsp, 0, sizeof(rsp)); - rsp[0] = SPDM_VERSION_12; - rsp[1] = SPDM_ALGORITHMS; - rsp[2] = 0; /* numAlgs = 0 */ - SPDM_Set16LE(&rsp[4], 36); - rsp[6] = 0x01; - rsp[7] = 0x02; - rsp[12] = SPDM_ASYM_ALGO_ECDSA_P384 & 0xFF; - rsp[13] = (SPDM_ASYM_ALGO_ECDSA_P384 >> 8) & 0xFF; - rsp[16] = SPDM_HASH_ALGO_SHA_384; + XMEMSET(clearAuth, 0xC1, sizeof(clearAuth)); + XMEMSET(setPayload, 0xA5, WOLFSPDM_PSK_MAX_SIZE); + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(setPayload + WOLFSPDM_PSK_MAX_SIZE, + clearAuth, sizeof(clearAuth), NULL, 0, NULL, 0)); - ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, 36), - WOLFSPDM_E_ALGO_MISMATCH, "numAlgs=0 must fail Set-B"); + /* First provisioning succeeds. */ + outSz = sizeof(outBuf); + rc = resp_send_clear_vd(rctx, WOLFSPDM_NATIONS_VDCODE_PSK_SET, + setPayload, (word32)sizeof(setPayload), outBuf, &outSz); + ASSERT_SUCCESS(rc); - TEST_CTX_FREE(); + /* A second PSK_SET_ must be refused while one is provisioned. */ + outSz = sizeof(outBuf); + rc = resp_send_clear_vd(rctx, WOLFSPDM_NATIONS_VDCODE_PSK_SET, + setPayload, (word32)sizeof(setPayload), outBuf, &outSz); + TEST_ASSERT(rc != WOLFSPDM_SUCCESS, + "replacing a provisioned PSK must be refused"); + + /* A clear with the wrong ClearAuth leaves it provisioned. */ + XMEMSET(clearAuth, 0x00, sizeof(clearAuth)); + outSz = sizeof(outBuf); + rc = resp_send_clear_vd(rctx, WOLFSPDM_NATIONS_VDCODE_PSK_CLEAR, + clearAuth, (word32)sizeof(clearAuth), outBuf, &outSz); + TEST_ASSERT(rc != WOLFSPDM_SUCCESS, "wrong ClearAuth must fail"); + + outSz = sizeof(outBuf); + rc = resp_send_clear_vd(rctx, WOLFSPDM_NATIONS_VDCODE_PSK_SET, + setPayload, (word32)sizeof(setPayload), outBuf, &outSz); + TEST_ASSERT(rc != WOLFSPDM_SUCCESS, + "PSK must remain provisioned after a failed clear"); + + /* The correct ClearAuth releases it and re-provisioning works. */ + XMEMSET(clearAuth, 0xC1, sizeof(clearAuth)); + outSz = sizeof(outBuf); + rc = resp_send_clear_vd(rctx, WOLFSPDM_NATIONS_VDCODE_PSK_CLEAR, + clearAuth, (word32)sizeof(clearAuth), outBuf, &outSz); + ASSERT_SUCCESS(rc); + + outSz = sizeof(outBuf); + rc = resp_send_clear_vd(rctx, WOLFSPDM_NATIONS_VDCODE_PSK_SET, + setPayload, (word32)sizeof(setPayload), outBuf, &outSz); + ASSERT_SUCCESS(rc); + + wolfSPDM_RespFree(rctx); TEST_PASS(); } +#endif /* WOLFSPDM_NATIONS */ +#endif /* WOLFSPDM_TCG */ -static int test_parse_capabilities_full(void) -{ - /* CAPABILITIES response per DSP0274 Table 12: - * 0: SPDMVersion - * 1: RequestResponseCode (0x61 = CAPABILITIES) - * 2: Param1 3: Param2 - * 4: CTExponent - * 5-7: Reserved - * 8-11: Flags (rspCaps) - * 12-15: DataTransferSize (SPDM 1.2+) - * 16-19: MaxSPDMmsgSize (SPDM 1.2+) - * - * Parser must populate ctExponent, rspCaps, dataTransferSize, and - * maxSpdmMsgSize so the requester can honor the responder's negotiated - * limits for subsequent fragmented commands (e.g. GET_CERTIFICATE). */ - byte msg[20]; - TEST_CTX_SETUP_V12(); +#ifdef WOLFSPDM_TCG + +/* In-process I/O glue: route the requester's outbound TCG frame to the + * responder's HandleMessage and copy the response back. */ +static int requester_to_responder_iocb(WOLFSPDM_CTX* spdmCtx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + void* userCtx) +{ + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)userCtx; + (void)spdmCtx; + return wolfSPDM_RespHandleMessage(rctx, txBuf, txSz, rxBuf, rxSz); +} + +/* Pinned identity handshake + tunneled TPM2_CMD + END_SESSION, end-to-end. */ +static int test_responder_identity_roundtrip(void) +{ + WOLFSPDM_CTX req; + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + ecc_key idKey; + byte idPriv[WOLFSPDM_ECC_KEY_SIZE]; + byte idPubX[WOLFSPDM_ECC_KEY_SIZE]; + byte idPubY[WOLFSPDM_ECC_KEY_SIZE]; + byte idPub[WOLFSPDM_ECC_POINT_SIZE]; + byte cmd[10]; + word32 idPrivSz = sizeof(idPriv); + word32 idPubXSz = sizeof(idPubX); + word32 idPubYSz = sizeof(idPubY); + int rc; - printf("test_parse_capabilities_full...\n"); + printf("test_responder_identity_roundtrip...\n"); + + ASSERT_SUCCESS(wolfSPDM_Init(&req)); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); + ASSERT_SUCCESS(wc_ecc_init(&idKey)); + ASSERT_SUCCESS(wc_ecc_make_key(&req.rng, WOLFSPDM_ECC_KEY_SIZE, + &idKey)); + ASSERT_SUCCESS(wc_ecc_export_private_only(&idKey, idPriv, &idPrivSz)); + ASSERT_SUCCESS(wc_ecc_export_public_raw(&idKey, idPubX, &idPubXSz, + idPubY, &idPubYSz)); + wc_ecc_free(&idKey); + ASSERT_EQ(idPrivSz, WOLFSPDM_ECC_KEY_SIZE, + "identity private-key size mismatch"); + ASSERT_EQ(idPubXSz, WOLFSPDM_ECC_KEY_SIZE, + "identity public X size mismatch"); + ASSERT_EQ(idPubYSz, WOLFSPDM_ECC_KEY_SIZE, + "identity public Y size mismatch"); + XMEMCPY(idPub, idPubX, sizeof(idPubX)); + XMEMCPY(idPub + sizeof(idPubX), idPubY, sizeof(idPubY)); + + /* Use the simplified Nuvoton TCG flow without requiring a vendor adapter + * in this loopback-only unit build. */ + req.mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(&req, idPub, + sizeof(idPub))); + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 1, 0)); + ASSERT_SUCCESS(wolfSPDM_RespSetIdentityKey(rctx, idPriv, + sizeof(idPriv), idPub, sizeof(idPub))); + g_tpmCbInvocations = 0; + ASSERT_SUCCESS(wolfSPDM_RespSetTpmCallback(rctx, responder_tpm_stub, + NULL)); + ASSERT_SUCCESS(wolfSPDM_SetIO(&req, requester_to_responder_iocb, rctx)); + + rc = wolfSPDM_ConnectTCG(&req); + TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "identity ConnectTCG failed"); + ASSERT_EQ(wolfSPDM_IsConnected(&req), 1, "Requester not connected"); + + XMEMSET(cmd, 0, sizeof(cmd)); + cmd[0] = 0x80; cmd[1] = 0x01; + cmd[5] = 0x0A; + cmd[8] = 0x01; cmd[9] = 0x44; + rc = wolfSPDM_TCG_VendorCmdSecured(&req, WOLFSPDM_VDCODE_TPM2_CMD, + cmd, sizeof(cmd)); + TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "TPM2_CMD passthrough failed"); + ASSERT_EQ(g_tpmCbInvocations, 1, "TPM stub must have run once"); + + ASSERT_SUCCESS(wolfSPDM_Disconnect(&req)); + + wc_ForceZero(idPriv, sizeof(idPriv)); + wolfSPDM_RespFree(rctx); + wolfSPDM_Free(&req); + TEST_PASS(); +} + +#ifdef WOLFSPDM_PSK +/* PSK handshake + tunneled TPM2_CMD round-trip + END_SESSION, end-to-end. */ +static int test_responder_psk_roundtrip(void) +{ + static const byte testPsk[64] = { + 0xdb,0xc2,0x19,0x22,0x91,0xd8,0x07,0x74, + 0x24,0x41,0xb9,0x63,0xf6,0x71,0x28,0x41, + 0xf7,0x69,0x7e,0x2e,0x39,0xc4,0x59,0x31, + 0xf3,0xab,0xc5,0x36,0x58,0xc8,0xb9,0x33, + 0x8b,0xd3,0x56,0x1c,0xab,0x5d,0x90,0xcf, + 0x9e,0x49,0x32,0x95,0xbb,0x5b,0xd6,0xb2, + 0xc4,0x55,0xe0,0xfd,0x19,0x39,0x2e,0x0c, + 0xe4,0xf3,0x43,0x3c,0xbc,0xfc,0x70,0x47 + }; + WOLFSPDM_CTX req; + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + byte cmd[10]; + byte frame[WOLFSPDM_TCG_HEADER_SIZE + 1]; + byte outBuf[64]; + word32 outSz; + int rc; - XMEMSET(msg, 0, sizeof(msg)); - msg[0] = SPDM_VERSION_12; - msg[1] = SPDM_CAPABILITIES; - msg[4] = 0x0A; /* CTExponent = 10 */ - SPDM_Set32LE(&msg[8], 0x00012345); /* Flags / rspCaps */ - SPDM_Set32LE(&msg[12], 0x00001000); /* DataTransferSize = 4096 */ - SPDM_Set32LE(&msg[16], 0x00010000); /* MaxSPDMmsgSize = 64 KiB */ + printf("test_responder_psk_roundtrip...\n"); - ASSERT_SUCCESS(wolfSPDM_ParseCapabilities(ctx, msg, sizeof(msg))); + ASSERT_SUCCESS(wolfSPDM_Init(&req)); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); - ASSERT_EQ(ctx->rspCaps, (word32)0x00012345, "rspCaps not parsed"); - ASSERT_EQ(ctx->ctExponent, (byte)0x0A, "CTExponent not parsed"); - ASSERT_EQ(ctx->dataTransferSize, (word32)0x00001000, - "DataTransferSize not parsed"); - ASSERT_EQ(ctx->maxSpdmMsgSize, (word32)0x00010000, - "MaxSPDMmsgSize not parsed"); + /* Bypass wolfSPDM_SetMode (vendor-gated) - set field directly so the + * test runs in a vendor-neutral build. */ + req.mode = WOLFSPDM_MODE_NATIONS_PSK; + ASSERT_SUCCESS(wolfSPDM_SetPSK(&req, testPsk, sizeof(testPsk), NULL, 0)); + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 0, 1)); + ASSERT_SUCCESS(wolfSPDM_RespSetPSK(rctx, testPsk, sizeof(testPsk), + NULL, 0)); - TEST_CTX_FREE(); + g_tpmCbInvocations = 0; + ASSERT_SUCCESS(wolfSPDM_RespSetTpmCallback(rctx, responder_tpm_stub, + NULL)); + ASSERT_SUCCESS(wolfSPDM_SetIO(&req, requester_to_responder_iocb, rctx)); + + rc = wolfSPDM_Connect(&req); + TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "PSK Connect failed"); + ASSERT_EQ(wolfSPDM_IsConnected(&req), 1, "Requester not connected"); + + /* Tunnel a fake TPM2_Startup command via the VENDOR_DEFINED "TPM2_CMD" + * vendor message - same wrapping the real Nuvoton/Nations requester uses. */ + XMEMSET(cmd, 0, sizeof(cmd)); + cmd[0] = 0x80; cmd[1] = 0x01; + cmd[5] = 0x0A; + cmd[8] = 0x01; cmd[9] = 0x44; + rc = wolfSPDM_TCG_VendorCmdSecured(&req, WOLFSPDM_VDCODE_TPM2_CMD, + cmd, sizeof(cmd)); + TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "TPM2_CMD passthrough failed"); + ASSERT_EQ(g_tpmCbInvocations, 1, "TPM stub must have run once"); + + ASSERT_SUCCESS(wolfSPDM_Disconnect(&req)); + + /* Session teardown must reject any later secured record until a new + * handshake establishes fresh traffic keys. */ + XMEMSET(frame, 0, sizeof(frame)); + wolfSPDM_WriteTcgHeader(frame, WOLFSPDM_TCG_TAG_SECURED, + sizeof(frame), 0, 0); + outSz = sizeof(outBuf); + rc = wolfSPDM_RespHandleMessage(rctx, frame, sizeof(frame), outBuf, + &outSz); + ASSERT_EQ(rc, WOLFSPDM_E_BAD_STATE, + "secured record after session teardown must return BAD_STATE"); + ASSERT_EQ(g_tpmCbInvocations, 1, + "sessionless record must not reach the TPM callback"); + + wolfSPDM_RespFree(rctx); + wolfSPDM_Free(&req); TEST_PASS(); } +#endif /* WOLFSPDM_PSK */ -/* ========================================================================== */ -/* Main */ -/* ========================================================================== */ +#endif /* WOLFSPDM_TCG */ + +#endif /* WOLFSPDM_RESPONDER */ + +/* ----- Main ----- */ int main(void) { @@ -2996,7 +2802,6 @@ int main(void) /* Transcript tests */ test_transcript_add_reset(); test_transcript_hash(); - test_certchain_hash(); /* Crypto tests */ test_random_generation(); @@ -3008,112 +2813,128 @@ int main(void) /* Message builder tests */ test_build_get_version(); - test_get_version_null_ctx(); - test_build_get_capabilities(); - test_build_negotiate_algorithms(); - test_parse_algorithms_set_b_enforcement(); -#ifdef WOLFSPDM_HAVE_MLDSA - test_negotiate_algorithms_pqc_build(); - test_parse_algorithms_pqc_select(); -#ifndef NO_WOLFSPDM_MEAS_VERIFY - test_mldsa_measurement_verify(); -#endif - test_key_exchange_rsp_mldsa_sigsize(); -#ifndef NO_WOLFSPDM_CHALLENGE - test_challenge_auth_mldsa_sigsize(); -#endif -#endif -#ifdef WOLFSPDM_HAVE_MLKEM - test_negotiate_algorithms_kem_build(); - test_parse_algorithms_kem_select(); - test_mlkem_decapsulate(); - test_kex_reconnect_method_switch(); - test_build_key_exchange_mlkem(); - test_key_exchange_mlkem_exceeds_dts(); - test_parse_key_exchange_rsp_mlkem_offset(); - test_mlkem_error_paths(); -#endif -#ifdef WOLFSPDM_HAVE_CHUNK - test_chunk_reassemble(); - test_chunk_reassemble_secured(); -#endif - test_build_get_digests(); - test_build_get_certificate(); - test_build_key_exchange_opaque_data(); - test_build_key_exchange_slot(); - test_build_finish_opaque_length_14(); - test_parse_finish_rsp_14_opaque_length(); - test_key_exchange_requires_cert(); - test_parse_key_exchange_rsp_too_short(); - test_parse_key_exchange_rsp_mutual_auth_refused(); test_build_end_session(); /* Error tests */ test_check_error(); test_error_strings(); - /* Measurement tests */ -#ifndef NO_WOLFSPDM_MEAS - test_build_get_measurements(); - test_build_get_measurements_slot(); - test_measurement_accessors(); - test_parse_measurements(); -#ifndef NO_WOLFSPDM_MEAS_VERIFY - test_measurement_sig_verification(); -#endif -#endif - - /* Certificate chain validation tests */ - test_set_trusted_cas(); - test_validate_cert_chain_no_cas(); - - /* Challenge tests */ -#ifndef NO_WOLFSPDM_CHALLENGE - test_build_challenge(); - test_parse_challenge_auth(); - test_parse_challenge_auth_slot_echo(); - test_parse_challenge_auth_reqctx_echo(); -#endif - - /* Heartbeat tests */ - test_build_heartbeat(); - test_parse_heartbeat_ack(); - test_heartbeat_state_check(); - - /* Key update tests */ - test_build_key_update(); - test_parse_key_update_ack(); - test_derive_updated_keys(); - test_key_update_state_check(); - /* Multi-version tests */ test_kdf_version_prefix(); test_hmac_mismatch_negative(); test_transcript_overflow(); -#ifndef NO_WOLFSPDM_MEAS - test_parse_measurements_negative(); - test_parse_measurements_13_requester_context(); - test_get_measurements_peer_error(); - test_get_measurements_uses_secured_when_measured(); -#endif test_version_fallback(); - test_set_max_version(); - test_sequence_number_wrap(); - test_sequence_number_mismatch(); /* Session state tests */ test_session_state(); - /* CAPABILITIES full-field parsing */ - test_parse_capabilities_full(); + /* Security tests */ + test_mitm_signature_rejected(); + test_key_exchange_rsp_hmac_check(); + test_invalid_curve_point(); + test_extract_ecc_point(); +#ifdef WOLFSPDM_TCG + test_tcg_get_pub_key_preserves_pin(); + test_tcg_get_pub_key_discovery_is_not_trusted(); + test_tcg_connect_requires_responder_key(); + test_tcg_underflow(); +#endif +#ifdef WOLFSPDM_NATIONS + test_nations_mode(); + test_nations_psk_set(); + test_nations_psk_kdf(); + test_nations_psk_message_format(); +#endif + test_decrypt_overflow(); + test_oob_read_error(); + test_constant_time_hmac(); + test_setdebug_truncation(); + test_key_zeroing(); + + /* ----- NEW COVERAGE TESTS ----- */ + + /* Public API coverage */ + test_set_requester_key_pair(); + test_connect_null_args(); + test_get_version_no_io(); + test_key_exchange_no_io(); + test_finish_no_io(); + test_secured_exchange_null_args(); + test_disconnect_states(); + +#ifdef WOLFSPDM_TCG + /* TCG message framing */ + test_build_tcg_clear_message(); + test_parse_tcg_clear_message(); + test_build_vendor_defined(); + test_parse_vendor_defined(); + test_vendor_defined_roundtrip(); + test_tcg_get_pub_key_null_args(); + test_tcg_give_pub_key_null_args(); + test_set_requester_key_tpmt(); + test_connect_tcg_null_args(); +#endif +#ifdef WOLFSPDM_NUVOTON + test_nuvoton_get_status_null_args(); + test_nuvoton_set_only_mode_null_args(); +#endif +#ifdef WOLFSPDM_NATIONS + test_nations_get_status_null_args(); + test_nations_set_only_mode_null_args(); + test_nations_psk_set_null_args(); + test_nations_psk_clear_null_args(); + test_nations_psk_clear_vca_null_args(); +#endif +#ifdef WOLFSPDM_PSK + test_parse_psk_exchange_rsp_null_args(); + test_parse_psk_exchange_rsp_hmac_check(); + test_build_psk_finish_null_args(); + test_build_psk_finish_format(); + test_parse_psk_finish_rsp(); + test_connect_psk_null_args(); +#endif + + /* Internal crypto */ + test_sha384_hash(); + test_export_ephemeral_pub_key(); + test_sign_hash_null_args(); + test_verify_signature_null_args(); + test_sign_verify_roundtrip(); + + /* Internal KDF */ + test_derive_handshake_keys(); + test_derive_from_handshake_secret(); + test_derive_app_data_keys(); + + /* Internal message building */ + test_build_key_exchange_null_args(); + test_build_key_exchange_format(); + test_build_finish_null_args(); + test_build_finish_format(); + + /* Internal encrypt/decrypt */ + test_encrypt_internal_null_args(); + test_encrypt_decrypt_roundtrip(); +#ifdef WOLFSPDM_TCG + test_encrypt_decrypt_roundtrip_tcg(); +#endif - /* Crypto / wire primitives */ - test_const_compare(); - test_build_iv_byte_positions(); - test_decrypt_session_id_mismatch(); - test_encrypt_decrypt_empty_payload(); - test_parse_version_edge_cases(); - test_parse_algorithms_zero_numalgs(); +#ifdef WOLFSPDM_RESPONDER + test_responder_init_free(); + test_responder_setmode_rejects_both_off(); + test_responder_no_plaintext_bypass(); +#ifdef WOLFSPDM_TCG + test_responder_no_clear_tpm2_cmd(); + test_responder_secured_requires_session(); +#ifdef WOLFSPDM_NATIONS + test_responder_psk_replace_guard(); +#endif + test_responder_identity_roundtrip(); +#endif +#if defined(WOLFSPDM_PSK) && defined(WOLFSPDM_TCG) + test_responder_psk_roundtrip(); +#endif +#endif printf("\n===========================================\n"); printf("Results: %d passed, %d failed\n", g_testsPassed, g_testsFailed); diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index 4678714..b066a66 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -1,6 +1,6 @@ /* spdm.h * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -22,113 +22,65 @@ #ifndef WOLFSPDM_SPDM_H #define WOLFSPDM_SPDM_H -/* Include build options (WOLFSPDM_DYNAMIC_MEMORY, etc.) - * Generated from config.h during build; installed alongside this header. */ -#ifndef HAVE_CONFIG_H - #include -#endif - #include #include -/* Feature detection macros - external projects (e.g. wolfTPM) can check these - * to conditionally compile against optional wolfSPDM APIs. */ -#ifndef NO_WOLFSPDM_MEAS -#define WOLFSPDM_HAS_MEASUREMENTS -#endif -#ifndef NO_WOLFSPDM_CHALLENGE -#define WOLFSPDM_HAS_CHALLENGE -#endif -#define WOLFSPDM_HAS_HEARTBEAT -#define WOLFSPDM_HAS_KEY_UPDATE - #ifdef __cplusplus extern "C" { #endif -/* wolfSPDM implements the standard SPDM 1.2+ protocol per DMTF DSP0274/DSP0277. - * Flow: GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> - * GET_DIGESTS -> GET_CERTIFICATE -> KEY_EXCHANGE -> FINISH - * Use with: libspdm emulator, standard SPDM responders */ +/* Protocol mode: TCG binding + vendor commands. + * For standard SPDM (emulator, measurements, challenge), see wolfSPDM standalone. */ +typedef enum { + WOLFSPDM_MODE_AUTO = 0, + WOLFSPDM_MODE_NUVOTON = 1, + WOLFSPDM_MODE_NATIONS = 2, + WOLFSPDM_MODE_NATIONS_PSK = 3 +} WOLFSPDM_MODE; + +/* wolfSPDM: Lightweight SPDM requester using wolfCrypt. + * Algorithm Set B fixed: P-384/SHA-384/AES-256-GCM. + * + * Usage (static, zero-malloc): + * WOLFSPDM_CTX ctx; + * wolfSPDM_Init(&ctx); + * wolfSPDM_SetIO(&ctx, callback, userPtr); + * wolfSPDM_SetResponderPubKey(&ctx, trustedPubKey, trustedPubKeySz); + * wolfSPDM_Connect(&ctx); + * wolfSPDM_SecuredExchange(&ctx, ...); + * wolfSPDM_Disconnect(&ctx); + * wolfSPDM_Free(&ctx); + * + * Dynamic (requires --enable-dynamic-mem): + * ctx = wolfSPDM_New(); + * // ... same as above ... + * wolfSPDM_Free(ctx); + * + * WOLFSPDM_CTX is ~22KB. Use static global on small-stack systems. + * SecuredExchange call chain uses ~20KB stack for message buffers. */ + +/* Compile-time buffer size for static allocation (32KB, runtime-verified) */ +#define WOLFSPDM_CTX_STATIC_SIZE 32768 -/* --- wolfSPDM Overview --- - * - * wolfSPDM is a lightweight SPDM (Security Protocol and Data Model) - * implementation using wolfCrypt for all cryptographic operations. - * - * Key Features: - * - Requester-only (initiator) implementation - * - Algorithm Set B fixed: P-384/SHA-384/AES-256-GCM - * - Full transcript tracking for proper TH1/TH2 computation - * - Supports SPDM 1.2, 1.3, and 1.4 - * - Compatible with libspdm emulator for testing - * - No external dependencies beyond wolfCrypt - * - * Typical Usage: - * - * Static (default, zero-malloc): - * WOLFSPDM_CTX ctx; - * wolfSPDM_Init(&ctx); - * wolfSPDM_SetIO(&ctx, callback, userPtr); - * wolfSPDM_Connect(&ctx); - * wolfSPDM_SecuredExchange(&ctx, ...); - * wolfSPDM_Disconnect(&ctx); - * wolfSPDM_Free(&ctx); - * - * Dynamic (opt-in, requires --enable-dynamic-mem): - * ctx = wolfSPDM_New(); // Allocates and fully initializes - * wolfSPDM_SetIO(ctx, callback, userPtr); - * wolfSPDM_Connect(ctx); - * wolfSPDM_SecuredExchange(ctx, ...); - * wolfSPDM_Disconnect(ctx); - * wolfSPDM_Free(ctx); // Frees the allocation - * - * Note: WOLFSPDM_CTX is approximately 22KB. On embedded systems with - * small stacks, declare it as a static global rather than a local variable. */ - -/* Compile-time size for static allocation of WOLFSPDM_CTX. - * Use this when you need a buffer large enough to hold WOLFSPDM_CTX - * without access to the struct definition (e.g., in wolfTPM). - * Classical (Algorithm Set B) struct size: ~31.3 KB, rounded to 32 KB. - * With ML-DSA the larger PQC buffers (sigs, cert chains) and the ML-DSA verify - * key push it to ~67 KB, rounded to 72 KB. wolfSPDM_InitStatic() verifies at - * runtime that the provided buffer is large enough (WOLFSPDM_E_BUFFER_SMALL); - * a compile-time _Static_assert in spdm_context.c also guards this value. */ -#if defined(WOLFSPDM_HAVE_MLDSA) -#define WOLFSPDM_CTX_STATIC_SIZE 73728 /* 72KB - fits CTX with ML-DSA buffers */ -#elif defined(WOLFSPDM_HAVE_MLKEM) -/* ML-KEM (no ML-DSA): the ephemeralKey union holds an MlKemKey (larger than - * ecc_key, and larger still with WOLFSSL_MLKEM_CACHE_A) instead of the classical - * key, so allow extra headroom over the 32KB classical budget. */ -#define WOLFSPDM_CTX_STATIC_SIZE 49152 /* 48KB */ -#else -#define WOLFSPDM_CTX_STATIC_SIZE 32768 /* 32KB - fits CTX with cert validation + challenge + key update fields */ -#endif - -/* Forward declaration */ struct WOLFSPDM_CTX; typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; -/* --- I/O Callback --- - * - * The I/O callback is called by wolfSPDM to send and receive raw SPDM - * messages. The transport layer (SPI, I2C, TCP, etc.) is handled externally. - * - * Parameters: - * ctx - wolfSPDM context - * txBuf - Data to transmit (raw SPDM message, no transport headers) - * txSz - Size of transmit data - * rxBuf - Buffer to receive response - * rxSz - [in] Size of receive buffer, [out] Actual received size - * userCtx - User context pointer from wolfSPDM_SetIO() - * - * Returns: - * 0 on success, negative on error - * - * Notes: - * - For MCTP transport, the callback should handle MCTP encapsulation - * - For secured messages (after KEY_EXCHANGE), the callback receives - * already-encrypted data including the session header */ +#ifdef WOLFSPDM_TCG + #include +#endif +#ifdef WOLFSPDM_NUVOTON + #include +#endif +#ifdef WOLFSPDM_NATIONS + #include +#endif +#ifdef WOLFSPDM_PSK + #include +#endif + +/* I/O callback: transport-agnostic send/receive. + * Returns 0 on success, negative on error. + * rxSz: [in] buffer size, [out] actual received size. */ typedef int (*WOLFSPDM_IO_CB)( WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, @@ -136,472 +88,55 @@ typedef int (*WOLFSPDM_IO_CB)( void* userCtx ); -/* --- Context Management --- */ - -/** - * Initialize a wolfSPDM context for use. - * Zeroes the context and initializes all internal state. - * Works on stack, static, or dynamically-allocated contexts. - * Must be called before wolfSPDM_Connect(). - * - * Call wolfSPDM_Free() before re-initializing to avoid leaking the RNG. - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ +/* Context management */ WOLFSPDM_API int wolfSPDM_Init(WOLFSPDM_CTX* ctx); - #ifdef WOLFSPDM_DYNAMIC_MEMORY -/** - * Allocate and fully initialize a new wolfSPDM context. - * No separate wolfSPDM_Init() call needed. - * Requires --enable-dynamic-mem at configure time. - * - * @return Pointer to new context, or NULL on failure. - */ WOLFSPDM_API WOLFSPDM_CTX* wolfSPDM_New(void); #endif - -/** - * Free a wolfSPDM context and all associated resources. - * Safe for both stack-allocated and dynamically-allocated contexts. - * Zeroes all sensitive key material before returning. - * - * @param ctx The wolfSPDM context to free. - */ WOLFSPDM_API void wolfSPDM_Free(WOLFSPDM_CTX* ctx); - -/** - * Get the size of the WOLFSPDM_CTX structure. - * Useful for static allocation. - * - * @return Size in bytes. - */ WOLFSPDM_API int wolfSPDM_GetCtxSize(void); - -/** - * Initialize a statically-allocated context with size check. - * Verifies the buffer is large enough, then calls wolfSPDM_Init(). - * - * @param ctx Pointer to pre-allocated memory of at least wolfSPDM_GetCtxSize(). - * @param size Size of the provided buffer. - * @return WOLFSPDM_SUCCESS or negative error code. - */ WOLFSPDM_API int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size); -/* --- Configuration --- */ - -/** - * Set the I/O callback for sending/receiving SPDM messages. - * - * @param ctx The wolfSPDM context. - * @param ioCb The I/O callback function. - * @param userCtx User context pointer passed to callback. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx); - -/** - * Set the maximum SPDM version to negotiate. - * Caps the version selected during GET_VERSION exchange. - * Must be called before wolfSPDM_Connect(). - * - * @param ctx The wolfSPDM context. - * @param maxVersion Maximum version (e.g., SPDM_VERSION_12, SPDM_VERSION_14). - * Must be in range 0x12-0x14. Use 0 to reset to compile-time default. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_SetMaxVersion(WOLFSPDM_CTX* ctx, byte maxVersion); - -/** - * Choose which key-exchange methods NEGOTIATE_ALGORITHMS advertises (SPDM 1.4). - * Default is dual-stack: the DHE group and all ML-KEM sets, letting the responder - * select. Pass advDhe=0 with a single SPDM_KEM_ALGO_ML_KEM_* in kemMask to force - * the responder onto a specific ML-KEM set (e.g. for PQC-only interop). ML-KEM is - * only advertised at SPDM 1.4+ and when built with ML-KEM support. - * - * @param ctx The wolfSPDM context. - * @param advDhe Non-zero to advertise the classical DHE group. - * @param kemMask Bit mask of SPDM_KEM_ALGO_ML_KEM_* sets to advertise (0 = none). - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_SetKeyExchangePref(WOLFSPDM_CTX* ctx, int advDhe, - word16 kemMask); - -/** - * Pin the requester session ID used during KEY_EXCHANGE. - * Default behavior is to draw a random non-reserved value during Connect(). - * Use this only for deterministic test setups; the reserved values 0x0000 - * and 0xFFFF are rejected. - * - * @param ctx The wolfSPDM context. - * @param reqSessionId Caller-chosen ReqSessionID (1..0xFFFE). - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_SetRequesterSessionId(WOLFSPDM_CTX* ctx, - word16 reqSessionId); - -/** - * Opt in to operating without a configured trust anchor. - * Without this call (and without wolfSPDM_SetTrustedCAs), wolfSPDM_Connect - * refuses to complete the handshake against an unauthenticated responder - * certificate chain. Intended for emulator / development use only. - * - * @param ctx The wolfSPDM context. - * @param allow Non-zero to permit handshakes without a trust anchor. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_AllowUntrustedCerts(WOLFSPDM_CTX* ctx, int allow); - -/* --- Session Establishment --- */ - -/** - * Establish an SPDM session (full handshake). - * Performs: GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> - * GET_DIGESTS -> GET_CERTIFICATE -> KEY_EXCHANGE -> FINISH - * - * After successful completion, use wolfSPDM_SecuredExchange() for - * encrypted communication. - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ +/* Configuration */ +WOLFSPDM_API int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, + void* userCtx); +WOLFSPDM_API int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode); +WOLFSPDM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); +/* Pin the responder key for cert-less operation (96 bytes P-384 X||Y). + * Required before a TCG identity-key connection. */ +WOLFSPDM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz); +/* Set requester key pair for mutual auth (privKey=48, pubKey=96 bytes) */ +WOLFSPDM_API int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, + const byte* privKey, word32 privKeySz, + const byte* pubKey, word32 pubKeySz); + +/* Session establishment */ WOLFSPDM_API int wolfSPDM_Connect(WOLFSPDM_CTX* ctx); - -/** - * Check if an SPDM session is established. - * - * @param ctx The wolfSPDM context. - * @return 1 if connected, 0 if not. - */ WOLFSPDM_API int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx); - -/** - * End the SPDM session gracefully. - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ WOLFSPDM_API int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx); -/* --- Individual Handshake Steps (for fine-grained control) --- */ - -/** - * Send GET_VERSION and receive VERSION response. - * First step in SPDM handshake (VCA part 1). - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ +/* Individual handshake steps (for fine-grained control) */ WOLFSPDM_API int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx); - -/** - * Send GET_CAPABILITIES and receive CAPABILITIES response. - * Second step in SPDM handshake (VCA part 2). - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_GetCapabilities(WOLFSPDM_CTX* ctx); - -/** - * Send NEGOTIATE_ALGORITHMS and receive ALGORITHMS response. - * Third step in SPDM handshake (VCA part 3). - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_NegotiateAlgorithms(WOLFSPDM_CTX* ctx); - -/** - * Send GET_DIGESTS and receive DIGESTS response. - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx); - -/** - * Send GET_CERTIFICATE and receive full certificate chain. - * May require multiple requests for large chains. - * - * @param ctx The wolfSPDM context. - * @param slotId Certificate slot (0-7, typically 0). - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_GetCertificate(WOLFSPDM_CTX* ctx, int slotId); - -/** - * Send KEY_EXCHANGE and receive KEY_EXCHANGE_RSP. - * Performs ECDHE key exchange and derives handshake keys. - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ WOLFSPDM_API int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx); - -/** - * Send FINISH and receive FINISH_RSP (encrypted). - * Completes the handshake and establishes the secure session. - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ WOLFSPDM_API int wolfSPDM_Finish(WOLFSPDM_CTX* ctx); -/* --- Secured Messaging --- */ - -#ifndef WOLFSPDM_LEAN -/** - * Encrypt a message for sending over the established session. - * - * @param ctx The wolfSPDM context. - * @param plain Plaintext message to encrypt. - * @param plainSz Size of plaintext. - * @param enc Buffer for encrypted output (includes header and tag). - * @param encSz [in] Size of enc buffer, [out] Actual encrypted size. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_EncryptMessage(WOLFSPDM_CTX* ctx, - const byte* plain, word32 plainSz, - byte* enc, word32* encSz); - -/** - * Decrypt a message received over the established session. - * - * @param ctx The wolfSPDM context. - * @param enc Encrypted message (includes header and tag). - * @param encSz Size of encrypted message. - * @param plain Buffer for decrypted output. - * @param plainSz [in] Size of plain buffer, [out] Actual decrypted size. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_DecryptMessage(WOLFSPDM_CTX* ctx, - const byte* enc, word32 encSz, - byte* plain, word32* plainSz); -#endif /* !WOLFSPDM_LEAN */ - -/** - * Perform a secured message exchange (encrypt, send, receive, decrypt). - * Convenience function combining encrypt, I/O, and decrypt. - * - * @param ctx The wolfSPDM context. - * @param cmdPlain Plaintext command to send. - * @param cmdSz Size of command. - * @param rspPlain Buffer for plaintext response. - * @param rspSz [in] Size of response buffer, [out] Actual response size. - * @return WOLFSPDM_SUCCESS or negative error code. - */ +/* Secured messaging: encrypt, send, receive, decrypt in one call */ WOLFSPDM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz); -#ifndef NO_WOLFSPDM_MEAS -/* --- Measurements (Device Attestation) --- - * - * When requestSignature=1 (and NO_WOLFSPDM_MEAS_VERIFY is NOT defined): - * Retrieves measurements with a cryptographic signature from the responder, - * then verifies the signature using the responder's certificate (retrieved - * during wolfSPDM_Connect). Returns WOLFSPDM_SUCCESS if verification passes. - * Returns WOLFSPDM_E_MEAS_SIG_FAIL if the signature is invalid. - * - * When requestSignature=0: - * Retrieves measurements WITHOUT a signature. Returns WOLFSPDM_SUCCESS on - * retrieval; the call is treated as informational only and the blocks - * must not be used for security-critical decisions. - * - * If compiled with NO_WOLFSPDM_MEAS_VERIFY, signature verification is - * unavailable. In that build a signed request (requestSignature=1) returns - * WOLFSPDM_E_MEAS_NOT_VERIFIED (the signature bytes are still captured in - * the context); unsigned requests still return WOLFSPDM_SUCCESS. - * - * Contexts are NOT thread-safe; do not call from multiple threads. */ - -/** - * Retrieve measurements from the SPDM responder. - * - * @param ctx The wolfSPDM context. - * @param measOperation SPDM_MEAS_OPERATION_ALL (0xFF) or specific index. - * @param requestSignature 1 to request signed measurements, 0 for unsigned. - * @return WOLFSPDM_SUCCESS on retrieval (unsigned) or successful signature - * verification (signed). WOLFSPDM_E_MEAS_SIG_FAIL when a signed - * response fails verification. WOLFSPDM_E_MEAS_NOT_VERIFIED only - * when a signed request is made in a build without verification - * support. Other negative error codes on protocol/transport errors. - */ -WOLFSPDM_API int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, byte measOperation, - int requestSignature); - -/** - * Get the number of measurement blocks retrieved. - * - * @param ctx The wolfSPDM context. - * @return Number of measurement blocks, or 0 if none. - */ -WOLFSPDM_API int wolfSPDM_GetMeasurementCount(WOLFSPDM_CTX* ctx); - -/** - * Get a specific measurement block by index. - * - * @param ctx The wolfSPDM context. - * @param blockIdx Index into retrieved blocks (0-based). - * @param measIndex [out] SPDM measurement index (1-based). - * @param measType [out] DMTFSpecMeasurementValueType. - * @param value [out] Buffer for measurement value. - * @param valueSz [in] Size of value buffer, [out] Actual value size. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_GetMeasurementBlock(WOLFSPDM_CTX* ctx, int blockIdx, - byte* measIndex, byte* measType, byte* value, word32* valueSz); -#endif /* !NO_WOLFSPDM_MEAS */ - -#ifndef WOLFSPDM_LEAN -/* --- Application Data Transfer --- - * - * Send/receive application data over an established SPDM session. - * Max payload per call: WOLFSPDM_MAX_MSG_SIZE minus AEAD overhead (~4000 bytes). - * These are message-oriented (no partial reads/writes). - * Contexts are NOT thread-safe; do not call from multiple threads. */ - -/** - * Send application data over an established SPDM session. - * - * @param ctx The wolfSPDM context (must be connected). - * @param data Data to send. - * @param dataSz Size of data. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_SendData(WOLFSPDM_CTX* ctx, const byte* data, word32 dataSz); - -/** - * Receive application data over an established SPDM session. - * - * @param ctx The wolfSPDM context (must be connected). - * @param data Buffer for received data. - * @param dataSz [in] Size of buffer, [out] Actual data size. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_ReceiveData(WOLFSPDM_CTX* ctx, byte* data, word32* dataSz); -#endif /* !WOLFSPDM_LEAN */ - -/* --- Session Information --- */ - -/** - * Get the current session ID. - * - * The session ID is allocated by KEY_EXCHANGE_RSP and remains valid for - * the rest of the handshake (FINISH) and the application phase. This - * returns the value as soon as KEY_EXCHANGE_RSP sets it, not only after - * wolfSPDM_IsConnected() goes true - I/O callbacks need it between - * KEY_EXCHANGE and FINISH to distinguish secured records. - * - * NOTE: a non-zero return does NOT imply the session is established. Use - * wolfSPDM_IsConnected() to test for completion of the handshake. - * - * @param ctx The wolfSPDM context. - * @return Session ID (combined reqSessionId | rspSessionId << 16), or 0 - * before KEY_EXCHANGE_RSP has run, or after the handshake errored. - */ +/* Session info */ WOLFSPDM_API word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx); - -/** - * Get negotiated SPDM version. - * - * @param ctx The wolfSPDM context. - * @return Version (e.g., 0x12 for SPDM 1.2), or 0 if not negotiated. - */ WOLFSPDM_API byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx); +#ifdef WOLFSPDM_TCG +WOLFSPDM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); +WOLFSPDM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); +#endif -/** - * Get the responder's last SPDM_ERROR code (Param1). - * - * Set by APIs that receive an SPDM_ERROR response from the responder - * (e.g. wolfSPDM_GetMeasurements returning WOLFSPDM_E_PEER_ERROR). - * Callers can branch on this code to back off on BUSY, abort on - * UNSUPPORTED_REQUEST, retry on REQUEST_RESYNCH, etc. - * - * @param ctx The wolfSPDM context. - * @return Last responder error code, or 0 if none has been received. - */ -WOLFSPDM_API byte wolfSPDM_GetLastPeerError(WOLFSPDM_CTX* ctx); - -/* Backwards-compat for the original spelling. Exported as a real symbol - * so binaries previously linked against the old name keep loading. New - * code should use wolfSPDM_GetNegotiatedVersion directly. */ -WOLFSPDM_API byte wolfSPDM_GetVersion_Negotiated(WOLFSPDM_CTX* ctx); - -/* --- Certificate Chain Validation --- */ - -/** - * Load the trusted root CA certificate for certificate chain validation. - * When set, wolfSPDM_Connect() / wolfSPDM_Challenge() will validate the - * responder's certificate chain by comparing SHA-384 of the supplied - * cert against the chain's RootHash. Only a single CA cert is supported - * - the buffer is hashed as one DER blob, not parsed as a list. - * Without this, only the public key is extracted (no chain anchor). - * - * @param ctx The wolfSPDM context. - * @param derCerts DER-encoded CA certificate (single cert, not a chain). - * @param derCertsSz Size of DER certificate data. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_SetTrustedCAs(WOLFSPDM_CTX* ctx, const byte* derCerts, - word32 derCertsSz); - -#ifndef NO_WOLFSPDM_CHALLENGE -/* --- Challenge Authentication (Sessionless Attestation) --- */ - -/** - * Perform CHALLENGE/CHALLENGE_AUTH exchange for sessionless attestation. - * Requires state >= WOLFSPDM_STATE_CERT (cert chain must be retrieved). - * Typical flow: GET_VERSION -> GET_CAPS -> NEGOTIATE_ALGO -> GET_DIGESTS - * -> GET_CERTIFICATE -> CHALLENGE - * - * @param ctx The wolfSPDM context. - * @param slotId Certificate slot (0-7, typically 0). - * @param measHashType Measurement summary hash type: - * SPDM_MEAS_SUMMARY_HASH_NONE (0x00), - * SPDM_MEAS_SUMMARY_HASH_TCB (0x01), or - * SPDM_MEAS_SUMMARY_HASH_ALL (0xFF). - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_Challenge(WOLFSPDM_CTX* ctx, int slotId, byte measHashType); -#endif /* !NO_WOLFSPDM_CHALLENGE */ - -/* --- Session Keep-Alive --- */ - -/** - * Send HEARTBEAT and receive HEARTBEAT_ACK. - * Must be in an established session (CONNECTED or MEASURED state). - * Sent over the encrypted channel. - * - * @param ctx The wolfSPDM context. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_Heartbeat(WOLFSPDM_CTX* ctx); - -/* --- Key Update (Session Key Rotation) --- */ - -/** - * Perform KEY_UPDATE to rotate session encryption keys. - * Must be in an established session (CONNECTED or MEASURED state). - * Follows up with VERIFY_NEW_KEY to confirm the new keys work. - * - * @param ctx The wolfSPDM context. - * @param updateAll 0 = rotate requester key only, - * 1 = rotate both requester and responder keys. - * @return WOLFSPDM_SUCCESS or negative error code. - */ -WOLFSPDM_API int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll); - -/* --- Debug/Utility --- */ +/* wolfSPDM_SetPSK declared in spdm_psk.h */ -/** - * Enable or disable debug output. - * - * @param ctx The wolfSPDM context. - * @param enable Non-zero to enable, 0 to disable. - */ +/* Debug */ WOLFSPDM_API void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable); #ifdef __cplusplus diff --git a/wolfspdm/spdm_error.h b/wolfspdm/spdm_error.h index b9b52ef..56eb6b5 100644 --- a/wolfspdm/spdm_error.h +++ b/wolfspdm/spdm_error.h @@ -1,6 +1,6 @@ /* spdm_error.h * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -22,7 +22,6 @@ #ifndef WOLFSPDM_ERROR_H #define WOLFSPDM_ERROR_H -/* Pull in WOLFSPDM_API visibility macro so this header is self-contained. */ #include #ifdef __cplusplus @@ -47,18 +46,13 @@ enum WOLFSPDM_ERROR { WOLFSPDM_E_NOT_CONNECTED = -13, /* Session not established */ WOLFSPDM_E_ALREADY_INIT = -14, /* Context already initialized */ WOLFSPDM_E_NO_MEMORY = -15, /* Memory allocation failed */ - WOLFSPDM_E_CERT_FAIL = -16, /* Certificate processing failed */ - WOLFSPDM_E_CAPS_MISMATCH = -17, /* Capability negotiation failed */ - WOLFSPDM_E_ALGO_MISMATCH = -18, /* Algorithm negotiation failed */ - WOLFSPDM_E_SESSION_INVALID = -19, /* Session ID invalid or mismatch */ - WOLFSPDM_E_KEY_EXCHANGE = -20, /* Key exchange failed */ - WOLFSPDM_E_MEASUREMENT = -21, /* Measurement retrieval/parsing failed */ - WOLFSPDM_E_MEAS_NOT_VERIFIED = -22, /* Measurements retrieved but not signature-verified */ - WOLFSPDM_E_MEAS_SIG_FAIL = -23, /* Measurement signature verification failed */ - WOLFSPDM_E_CERT_PARSE = -24, /* Failed to parse responder certificate */ - WOLFSPDM_E_CHALLENGE = -25, /* Challenge authentication failed */ - WOLFSPDM_E_KEY_UPDATE = -26, /* Key update failed */ - WOLFSPDM_E_CHUNK = -27, /* Large-response chunking failed */ + WOLFSPDM_E_SESSION_INVALID = -16, /* Session ID invalid or mismatch */ + WOLFSPDM_E_KEY_EXCHANGE = -17, /* Key exchange failed */ + WOLFSPDM_E_NOT_AVAILABLE = -18, /* Feature/mode not compiled in */ + WOLFSPDM_E_FRAMING = -19, /* Frame did not parse (e.g. plaintext + * TPM2 sent while SPDM mode active - + * bus-snooping defence) */ + WOLFSPDM_E_NOT_IMPL = -20, /* Handler not yet implemented */ }; /* Get human-readable error string */ diff --git a/wolfspdm/spdm_nations.h b/wolfspdm/spdm_nations.h new file mode 100644 index 0000000..bd84975 --- /dev/null +++ b/wolfspdm/spdm_nations.h @@ -0,0 +1,91 @@ +/* spdm_nations.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Nations Technology NS350 TPM SPDM Support + * + * Two SPDM modes (mutually exclusive): + * + * 1. Identity key mode — TCG "TPM Communication over SPDM" + * - Uses shared TCG binding code (spdm_tcg.c) + * - GET_PUB_KEY, GIVE_PUB_KEY, TPM_CMD vendor commands + * - Algorithm Set B (P-384/SHA-384/AES-256-GCM) + * + * 2. PSK mode — PSK_EXCHANGE/PSK_FINISH + * - GET_STATUS, SPDM_ONLY, PSK_SET, PSK_CLEAR vendor commands + * - Same Algorithm Set B + * + * Reference: NS350 Datasheet Rev 2.06 Section 4.5.8 + */ + +#ifndef WOLFSPDM_NATIONS_H +#define WOLFSPDM_NATIONS_H + +#ifdef WOLFSPDM_NATIONS + +/* Include shared TCG declarations */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- Nations-Only Vendor-Defined Commands ----- */ + +#define WOLFSPDM_NATIONS_VDCODE_PSK_SET "PSK_SET_" +#define WOLFSPDM_NATIONS_VDCODE_PSK_CLEAR "PSK_CLR_" + +/* ----- Nations SPDM Status ----- */ + +/* GET_STATUS_RSP fields per TCG spec Table 15 */ +typedef struct WOLFSPDM_NATIONS_STATUS { + unsigned int spdmEnabled : 1; + unsigned int sessionActive : 1; + unsigned int spdmOnlyLocked : 1; + unsigned int spdmOnlyPending : 1; + unsigned int pskProvisioned : 1; + unsigned int identityKeyProvisioned : 1; +} WOLFSPDM_NATIONS_STATUS; + +/* ----- Nations PSK-Mode SPDM Functions ----- */ + +WOLFSPDM_API int wolfSPDM_Nations_GetStatus(WOLFSPDM_CTX* ctx, + WOLFSPDM_NATIONS_STATUS* status); + +WOLFSPDM_API int wolfSPDM_Nations_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock); + +WOLFSPDM_API int wolfSPDM_Nations_PskSet(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz); + +WOLFSPDM_API int wolfSPDM_Nations_PskClear(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz); + +WOLFSPDM_API int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz); + +/* wolfSPDM_ConnectNationsPsk is an alias for wolfSPDM_ConnectPsk (spdm_psk.h) */ + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_NATIONS */ + +#endif /* WOLFSPDM_NATIONS_H */ diff --git a/wolfspdm/spdm_nuvoton.h b/wolfspdm/spdm_nuvoton.h new file mode 100644 index 0000000..bdf4ff6 --- /dev/null +++ b/wolfspdm/spdm_nuvoton.h @@ -0,0 +1,76 @@ +/* spdm_nuvoton.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Nuvoton TPM SPDM Support + * + * Nuvoton-specific SPDM functions (GetStatus, SetOnlyMode). + * Shared TCG code is in spdm_tcg.h / spdm_tcg.c. + * + * The Nuvoton NPCT75x TPM uses a simplified SPDM flow: + * GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH + * + * Notable differences from standard SPDM: + * - No GET_CAPABILITIES or NEGOTIATE_ALGORITHMS (Algorithm Set B is fixed) + * - Uses vendor-defined commands for identity key exchange + * - TCG binding headers wrap all SPDM messages + * + * Reference: Nuvoton SPDM Guidance Rev 1.11 + */ + +#ifndef WOLFSPDM_NUVOTON_H +#define WOLFSPDM_NUVOTON_H + +/* Include shared TCG declarations */ +#include + +#ifdef WOLFSPDM_NUVOTON + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- Nuvoton SPDM Status ----- */ + +typedef struct WOLFSPDM_NUVOTON_STATUS { + int spdmEnabled; + int sessionActive; + int spdmOnlyLocked; + byte specVersionMajor; + byte specVersionMinor; +} WOLFSPDM_NUVOTON_STATUS; + +/* ----- Nuvoton-Only Functions ----- */ + +WOLFSPDM_API int wolfSPDM_Nuvoton_GetStatus( + WOLFSPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status); + +WOLFSPDM_API int wolfSPDM_Nuvoton_SetOnlyMode( + WOLFSPDM_CTX* ctx, + int lock); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFSPDM_NUVOTON_H */ diff --git a/wolfspdm/spdm_psk.h b/wolfspdm/spdm_psk.h new file mode 100644 index 0000000..2f1dcd9 --- /dev/null +++ b/wolfspdm/spdm_psk.h @@ -0,0 +1,91 @@ +/* spdm_psk.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Shared SPDM PSK Support (DSP0274 1.2+) + * + * Standard SPDM PSK protocol: + * - PSK_EXCHANGE / PSK_EXCHANGE_RSP + * - PSK_FINISH / PSK_FINISH_RSP + * - PSK key derivation (HKDF-Extract with PSK) + * - Shared PSK connection flow + * + * Vendor-specific PSK provisioning commands (PSK_SET, PSK_CLEAR, etc.) + * remain in the vendor files (spdm_nations.c, etc.). + */ + +#ifndef WOLFSPDM_PSK_H +#define WOLFSPDM_PSK_H + +#include + +#ifdef WOLFSPDM_PSK + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- PSK Context Setup ----- */ + +WOLFSPDM_API int wolfSPDM_SetPSK(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz); + +/* ----- PSK Message Builders/Parsers ----- */ + +WOLFSPDM_API int wolfSPDM_BuildPskExchange(WOLFSPDM_CTX* ctx, + byte* buf, word32* bufSz); + +WOLFSPDM_API int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz); + +WOLFSPDM_API int wolfSPDM_BuildPskFinish(WOLFSPDM_CTX* ctx, + byte* buf, word32* bufSz); + +WOLFSPDM_API int wolfSPDM_ParsePskFinishRsp(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz); + +/* ----- PSK Key Derivation ----- */ + +WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeysPsk(WOLFSPDM_CTX* ctx, + const byte* th1Hash); + +/* ----- Shared PSK Connection Flow ----- */ + +/** + * Perform PSK SPDM connection. + * GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> + * PSK_EXCHANGE -> PSK_FINISH -> app key derivation. + * + * @param ctx wolfSPDM context (must have PSK set via wolfSPDM_SetPSK) + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_ConnectPsk(WOLFSPDM_CTX* ctx); + +/* Backward compatibility */ +#define wolfSPDM_ConnectNationsPsk wolfSPDM_ConnectPsk + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_PSK */ + +#endif /* WOLFSPDM_PSK_H */ diff --git a/wolfspdm/spdm_responder.h b/wolfspdm/spdm_responder.h new file mode 100644 index 0000000..5e5550b --- /dev/null +++ b/wolfspdm/spdm_responder.h @@ -0,0 +1,112 @@ +/* spdm_responder.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* SPDM responder. Lives above fwtpm's transport HAL and reuses wolfSPDM + * crypto + framing helpers, flipped to answer requester-driven messages. */ + +#ifndef WOLFSPDM_RESPONDER_H +#define WOLFSPDM_RESPONDER_H + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifdef WOLFSPDM_RESPONDER + +#ifdef __cplusplus +extern "C" { +#endif + +struct WOLFSPDM_RESP_CTX; +typedef struct WOLFSPDM_RESP_CTX WOLFSPDM_RESP_CTX; + +/* Dispatcher for tunneled TPM2_CMD payloads. fwtpm_server wires this to + * FWTPM_ProcessCommand; unit tests wire a stub. */ +typedef int (*WOLFSPDM_RESP_TPM_CB)(void* userCtx, + const byte* cmd, word32 cmdSz, + byte* resp, word32 respBufSz, word32* respSz); + +WOLFSPDM_API int wolfSPDM_RespInit(WOLFSPDM_RESP_CTX* ctx); +WOLFSPDM_API void wolfSPDM_RespFree(WOLFSPDM_RESP_CTX* ctx); + +/* The tunnel buffers stay at WOLFSPDM_MAX_MSG_SIZE: the secured path + * (encrypt, transport and requester buffers) is capped there too, so a + * larger TPM response could not be delivered even if staged here. */ +#define WOLFSPDM_MAX_TPM_MSG_SIZE WOLFSPDM_MAX_MSG_SIZE + +/* RESP_CTX embeds the requester CTX + four MAX_MSG_SIZE working buffers + * (4 * 4096) + identity/PSK material + tpm callback + flags. Static buffer + * is sized for that worst case; spdm_responder.c has a compile-time assert + * so it can't silently undersize. */ +#define WOLFSPDM_RESP_CTX_STATIC_SIZE \ + (WOLFSPDM_CTX_STATIC_SIZE + (4 * WOLFSPDM_MAX_MSG_SIZE) + 1024) +WOLFSPDM_API int wolfSPDM_RespGetCtxSize(void); + +WOLFSPDM_API int wolfSPDM_RespSetMode(WOLFSPDM_RESP_CTX* ctx, + int useTcg, int usePsk); + +WOLFSPDM_API int wolfSPDM_RespSetPSK(WOLFSPDM_RESP_CTX* ctx, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz); + +/* privKey: 48 bytes (P-384 scalar). pubKey: 96 bytes (X||Y, big-endian). + * Rejected with WOLFSPDM_E_BAD_STATE while a session is negotiating or + * connected; reset the responder first. */ +WOLFSPDM_API int wolfSPDM_RespSetIdentityKey(WOLFSPDM_RESP_CTX* ctx, + const byte* privKey, word32 privSz, + const byte* pubKey, word32 pubSz); + +WOLFSPDM_API int wolfSPDM_RespSetTpmCallback(WOLFSPDM_RESP_CTX* ctx, + WOLFSPDM_RESP_TPM_CB cb, void* userCtx); + +WOLFSPDM_API void wolfSPDM_RespSetDebug(WOLFSPDM_RESP_CTX* ctx, int enable); + +/* Returns WOLFSPDM_E_FRAMING on a non-TCG inbound frame. Callers MUST drop + * the connection on E_FRAMING; never fall through to the TPM parser. */ +WOLFSPDM_API int wolfSPDM_RespHandleMessage(WOLFSPDM_RESP_CTX* ctx, + const byte* inBuf, word32 inSz, + byte* outBuf, word32* outSz); + +WOLFSPDM_API void wolfSPDM_RespReset(WOLFSPDM_RESP_CTX* ctx); + +/* SPDMONLY lock: when 1, the transport must reject plaintext TPM frames. + * Toggled by the requester via SPDMONLY vendor command. */ +WOLFSPDM_API int wolfSPDM_RespIsLocked(const WOLFSPDM_RESP_CTX* ctx); + +/* Returns 1 when a secured SPDM session is established. */ +WOLFSPDM_API int wolfSPDM_RespIsSessionActive(const WOLFSPDM_RESP_CTX* ctx); + +/* On success, points idPub at the responder's own SPDM identity key (raw + * P-384 X||Y) and returns its length. Returns 0 when there is no identity + * key or the active session did not authenticate with it (PSK sessions). + * The requester's key is never exposed: no requester mutual-auth is done. */ +WOLFSPDM_API word32 wolfSPDM_RespGetIdentityKey(const WOLFSPDM_RESP_CTX* ctx, + const byte** idPub); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_RESPONDER */ + +#endif /* WOLFSPDM_RESPONDER_H */ diff --git a/wolfspdm/spdm_tcg.h b/wolfspdm/spdm_tcg.h new file mode 100644 index 0000000..54eef41 --- /dev/null +++ b/wolfspdm/spdm_tcg.h @@ -0,0 +1,178 @@ +/* spdm_tcg.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Shared TCG SPDM Binding Support + * + * This header provides shared TCG SPDM functionality used by both + * Nuvoton and Nations Technology TPMs: + * - TCG SPDM Binding message framing (per TCG SPDM Binding Spec v1.0) + * - Vendor-defined command helpers + * - Identity key exchange (GET_PUBK, GIVE_PUB) + * - GET_CAPABILITIES + NEGOTIATE_ALGORITHMS + * - TCG SPDM connection flow + */ + +#ifndef WOLFSPDM_TCG_H +#define WOLFSPDM_TCG_H + +#include + +/* Transport framing constants - needed by any SPDM-over-TPM build, + * not just the TCG cert handshake. */ + +/* Message Tags */ +#define WOLFSPDM_TCG_TAG_CLEAR 0x8101 /* Clear (unencrypted) message */ +#define WOLFSPDM_TCG_TAG_SECURED 0x8201 /* Secured (encrypted) message */ + +/* Header Sizes */ +#define WOLFSPDM_TCG_HEADER_SIZE 16 /* TCG binding header size */ + +#ifdef WOLFSPDM_TCG + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) ----- */ + +/* FIPS Service Indicator */ +#define WOLFSPDM_FIPS_NON_FIPS 0x00 +#define WOLFSPDM_FIPS_APPROVED 0x01 + +/* ----- TCG Vendor-Defined Command Codes (shared) ----- */ + +/* 8-byte ASCII vendor codes for SPDM VENDOR_DEFINED messages */ +#define WOLFSPDM_VDCODE_LEN 8 + +#define WOLFSPDM_VDCODE_TPM2_CMD "TPM2_CMD" /* TPM command over SPDM */ + +/* Locality-aware TPM command VdCodes (TCG spec Table 11, optional). + * Response to all TPM_CMD_L* is TPM_RSP with VdCode "TPM2_CMD". + * Not currently used -- our code sends TPM2_CMD for all localities. */ +#define WOLFSPDM_VDCODE_TPM2CMD0 "TPM2CMD0" /* TPM_CMD_L0: locality 0 */ +#define WOLFSPDM_VDCODE_TPM2CMD1 "TPM2CMD1" /* TPM_CMD_L1: locality 1 */ +#define WOLFSPDM_VDCODE_TPM2CMD2 "TPM2CMD2" /* TPM_CMD_L2: locality 2 */ +#define WOLFSPDM_VDCODE_TPM2CMD3 "TPM2CMD3" /* TPM_CMD_L3: locality 3 */ +#define WOLFSPDM_VDCODE_TPM2CMD4 "TPM2CMD4" /* TPM_CMD_L4: locality 4 */ +#define WOLFSPDM_VDCODE_GET_PUBK "GET_PUBK" /* Get TPM's identity key */ +#define WOLFSPDM_VDCODE_GIVE_PUB "GIVE_PUB" /* Give host's identity key */ +#define WOLFSPDM_VDCODE_GET_STS "GET_STS_" /* Get SPDM status */ +#define WOLFSPDM_VDCODE_SPDMONLY "SPDMONLY" /* Lock/unlock SPDM-only */ + +/* SPDMONLY command parameters */ +#define WOLFSPDM_SPDMONLY_LOCK 0x01 +#define WOLFSPDM_SPDMONLY_UNLOCK 0x00 + +/* ----- TCG Binding Header Structures ----- */ + +/* Clear message header (tag 0x8101) + * Layout: tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + + * fipsIndicator(2/BE) + reserved(4) = 16 bytes */ +typedef struct WOLFSPDM_TCG_CLEAR_HDR { + word16 tag; + word32 size; + word32 connectionHandle; + word16 fipsIndicator; + word32 reserved; +} WOLFSPDM_TCG_CLEAR_HDR; + +/* ----- Vendor Command Response Container ----- */ + +typedef struct { + char vdCode[WOLFSPDM_VDCODE_LEN + 1]; + byte payload[WOLFSPDM_VENDOR_BUF_SZ]; + word32 payloadSz; +} WOLFSPDM_VENDOR_RSP; + +/* ----- Vendor Command Helpers ----- */ + +WOLFSPDM_API int wolfSPDM_TCG_VendorCmdClear(WOLFSPDM_CTX* ctx, + const char* vdCode, const byte* payload, word32 payloadSz, + WOLFSPDM_VENDOR_RSP* rsp); + +WOLFSPDM_API int wolfSPDM_TCG_VendorCmdSecured(WOLFSPDM_CTX* ctx, + const char* vdCode, const byte* payload, word32 payloadSz); + +/* ----- TCG Binding Message Framing ----- */ + +WOLFSPDM_API int wolfSPDM_BuildTcgClearMessage( + WOLFSPDM_CTX* ctx, + const byte* spdmPayload, word32 spdmPayloadSz, + byte* outBuf, word32 outBufSz); + +WOLFSPDM_API int wolfSPDM_ParseTcgClearMessage( + const byte* inBuf, word32 inBufSz, + byte* spdmPayload, word32* spdmPayloadSz, + WOLFSPDM_TCG_CLEAR_HDR* hdr); + +/* ----- Vendor-Defined Message Helpers ----- */ + +WOLFSPDM_API int wolfSPDM_BuildVendorDefined( + byte spdmVersion, const char* vdCode, + const byte* payload, word32 payloadSz, + byte* outBuf, word32 outBufSz); + +WOLFSPDM_API int wolfSPDM_ParseVendorDefined( + const byte* inBuf, word32 inBufSz, char* vdCode, + byte* payload, word32* payloadSz); + +/* ----- Shared TCG SPDM Functions ----- */ + +/* Cleartext key discovery. The result is never installed as trusted state. + * If a responder key is pinned, the discovered public point must match it. */ +WOLFSPDM_API int wolfSPDM_TCG_GetPubKey(WOLFSPDM_CTX* ctx, + byte* pubKey, word32* pubKeySz); + +WOLFSPDM_API int wolfSPDM_TCG_GivePubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz); + +WOLFSPDM_API int wolfSPDM_TCG_GetCapabilities(WOLFSPDM_CTX* ctx, + word32 capsFlags); + +WOLFSPDM_API int wolfSPDM_TCG_NegotiateAlgorithms(WOLFSPDM_CTX* ctx); + +WOLFSPDM_API int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz); + +WOLFSPDM_API int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx); + +/* Backward compatibility aliases */ +#define wolfSPDM_ConnectNuvoton wolfSPDM_ConnectTCG +#define wolfSPDM_Nuvoton_GetPubKey wolfSPDM_TCG_GetPubKey +#define wolfSPDM_Nuvoton_GivePubKey wolfSPDM_TCG_GivePubKey + +/* ----- TCG Context Defaults ----- */ + +#define WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT 0 +#define WOLFSPDM_NUVOTON_FIPS_DEFAULT WOLFSPDM_FIPS_NON_FIPS + +/* Default capabilities flags (identity key mode, no PSK_CAP) */ +#define WOLFSPDM_TCG_CAPS_FLAGS_DEFAULT 0x000193C0UL +/* Capabilities flags with PSK_CAP (bit 10) set */ +#define WOLFSPDM_TCG_CAPS_FLAGS_PSK 0x000197C0UL + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_TCG */ + +#endif /* WOLFSPDM_TCG_H */ diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index d564d76..385e4f1 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -1,6 +1,6 @@ /* spdm_types.h * - * Copyright (C) 2006-2025 wolfSSL Inc. + * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSPDM. * @@ -28,13 +28,42 @@ #endif #include -/* Visibility: when built as part of wolfTPM, use WOLFTPM_API for export */ -#ifdef BUILDING_WOLFTPM +#if !defined(HAVE_CONFIG_H) && !defined(WOLFTPM_SPDM) && \ + !defined(WOLFSPDM_USER_SETTINGS) + #include +#endif + +/* wolfTPM's configure spells these switches with its own prefix */ +#if defined(WOLFTPM_SPDM_TCG) && !defined(WOLFSPDM_TCG) + #define WOLFSPDM_TCG +#endif +#if defined(WOLFTPM_SPDM_PSK) && !defined(WOLFSPDM_PSK) + #define WOLFSPDM_PSK +#endif +#if defined(WOLFTPM_SPDM_RESPONDER) && !defined(WOLFSPDM_RESPONDER) + #define WOLFSPDM_RESPONDER +#endif +#if defined(DEBUG_WOLFTPM) && !defined(WOLFSPDM_DEBUG) + #define WOLFSPDM_DEBUG +#endif +#if defined(WOLFTPM_SMALL_STACK) && !defined(WOLFSPDM_DYNAMIC_MEMORY) + #define WOLFSPDM_DYNAMIC_MEMORY +#endif + +#if defined(BUILDING_WOLFTPM) || defined(WOLFTPM_SPDM) #include - #define WOLFSPDM_API WOLFTPM_API + #define WOLFSPDM_API WOLFTPM_API + #define WOLFSPDM_LOCAL WOLFTPM_LOCAL + #define WOLFSPDM_TEST_API WOLFTPM_TEST_API #else #ifndef WOLFSPDM_API - #define WOLFSPDM_API + #define WOLFSPDM_API + #endif + #ifndef WOLFSPDM_LOCAL + #define WOLFSPDM_LOCAL + #endif + #ifndef WOLFSPDM_TEST_API + #define WOLFSPDM_TEST_API WOLFSPDM_API #endif #endif @@ -47,85 +76,28 @@ extern "C" { #include #endif -/* ML-DSA (FIPS 204) support follows wolfSSL: auto-on when the linked wolfSSL - * reports WOLFSSL_HAVE_MLDSA. Define WOLFSPDM_NO_MLDSA to force it off. - * wolfSPDM's configure capability-tests for the wc_MlDsaKey context API and - * defines WOLFSPDM_NO_MLDSA when only the legacy ML-DSA interface is present. - * Non-autoconf consumers (e.g. wolfTPM embedding) linking a pre-context-API - * wolfSSL that still reports WOLFSSL_HAVE_MLDSA must define WOLFSPDM_NO_MLDSA - * themselves to avoid a compile break. */ -#if defined(WOLFSSL_HAVE_MLDSA) && !defined(WOLFSPDM_NO_MLDSA) - #ifndef WOLFSPDM_HAVE_MLDSA - #define WOLFSPDM_HAVE_MLDSA - #endif -#endif - -/* ML-KEM (FIPS 203) key exchange follows the linked wolfSSL: on when it - * reports WOLFSSL_HAVE_MLKEM unless WOLFSPDM_NO_MLKEM is defined. */ -#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSPDM_NO_MLKEM) - #ifndef WOLFSPDM_HAVE_MLKEM - #define WOLFSPDM_HAVE_MLKEM - #endif -#endif - -/* --- SPDM Protocol Constants (DMTF DSP0274 / DSP0277) --- */ +/* ----- SPDM Protocol Constants (DMTF DSP0274 / DSP0277) ----- */ -/* SPDM Version Numbers */ -#define SPDM_VERSION_10 0x10 /* SPDM 1.0 (for GET_VERSION) */ -#define SPDM_VERSION_11 0x11 /* SPDM 1.1 */ +/* SPDM Version Numbers (used in version negotiation and key derivation) */ +#define SPDM_VERSION_10 0x10 /* GET_VERSION always uses 1.0 */ #define SPDM_VERSION_12 0x12 /* SPDM 1.2 */ #define SPDM_VERSION_13 0x13 /* SPDM 1.3 */ #define SPDM_VERSION_14 0x14 /* SPDM 1.4 */ -/* SPDM Message Header Size */ -#define SPDM_HEADER_SIZE 4 /* Version + Code + Param1 + Param2 */ - -/* SPDM Request Codes (sent by requester) */ +/* SPDM Request Codes (used by this implementation) */ #define SPDM_GET_VERSION 0x84 -#define SPDM_GET_CAPABILITIES 0xE1 -#define SPDM_NEGOTIATE_ALGORITHMS 0xE3 -#define SPDM_GET_DIGESTS 0x81 -#define SPDM_GET_CERTIFICATE 0x82 -#define SPDM_CHALLENGE 0x83 -#define SPDM_GET_MEASUREMENTS 0xE0 #define SPDM_KEY_EXCHANGE 0xE4 #define SPDM_FINISH 0xE5 -#define SPDM_PSK_EXCHANGE 0xE6 -#define SPDM_PSK_FINISH 0xE7 -#define SPDM_HEARTBEAT 0xE8 -#define SPDM_KEY_UPDATE 0xE9 -#ifndef SPDM_END_SESSION -#define SPDM_END_SESSION 0xEA -#endif -#define SPDM_CHUNK_SEND 0x85 /* Large request chunking (unused) */ -#define SPDM_CHUNK_GET 0x86 /* Large response chunking (CHUNK_GET) */ -#define SPDM_VENDOR_DEFINED 0xFF +#define SPDM_END_SESSION 0xEC +#define SPDM_VENDOR_DEFINED_REQUEST 0xFE -/* SPDM Response Codes (sent by responder) */ +/* SPDM Response Codes (used by this implementation) */ #define SPDM_VERSION 0x04 -#define SPDM_CAPABILITIES 0x61 -#define SPDM_ALGORITHMS 0x63 -#define SPDM_DIGESTS 0x01 -#define SPDM_CERTIFICATE 0x02 -#define SPDM_CHALLENGE_AUTH 0x03 -#define SPDM_MEASUREMENTS 0x60 #define SPDM_KEY_EXCHANGE_RSP 0x64 #define SPDM_FINISH_RSP 0x65 -#define SPDM_PSK_EXCHANGE_RSP 0x66 -#define SPDM_PSK_FINISH_RSP 0x67 -#define SPDM_HEARTBEAT_ACK 0x68 -#define SPDM_KEY_UPDATE_ACK 0x69 -#ifndef SPDM_END_SESSION_ACK -#define SPDM_END_SESSION_ACK 0x6A -#endif -#define SPDM_CHUNK_SEND_ACK 0x05 /* CHUNK_SEND response (unused) */ -#define SPDM_CHUNK_RESPONSE 0x06 /* CHUNK_GET response (DSP0274 10.27.2) */ -#define SPDM_VENDOR_DEFINED_RSP 0x7E +#define SPDM_END_SESSION_ACK 0x6C #define SPDM_ERROR 0x7F -/* CHUNK_RESPONSE Param1 (Response Attributes) bit (DSP0274 Table 102) */ -#define SPDM_CHUNK_LAST_CHUNK 0x01 /* This chunk is the last one */ - /* SPDM Error Codes (in Param1 of ERROR response) */ #define SPDM_ERROR_INVALID_REQUEST 0x01 #define SPDM_ERROR_BUSY 0x03 @@ -146,54 +118,8 @@ extern "C" { #define SPDM_ERROR_RESPONSE_NOT_READY 0x42 #define SPDM_ERROR_REQUEST_RESYNCH 0x43 -/* --- Algorithm Set B (FIPS 140-3 Level 3 compliant) --- - * This implementation ONLY supports Algorithm Set B for simplicity. */ - -/* Hash Algorithms */ -#define SPDM_HASH_ALGO_SHA_384 0x00000002 /* TPM_ALG_SHA384 */ - -/* Asymmetric Signature Algorithms */ -#define SPDM_ASYM_ALGO_ECDSA_P384 0x00000080 /* ECDSA-ECC_NIST_P384 */ - -/* PQC Asymmetric Signature Algorithms (DSP0274 1.4 Table 19 PqcAsymAlgo / - * Table 20 PqcAsymSel). Byte 0 bit mask; one selected, mutually exclusive - * with BaseAsymSel. */ -#define SPDM_PQC_ASYM_ALGO_ML_DSA_44 0x00000001 /* ML-DSA-44, SigLen 2420 */ -#define SPDM_PQC_ASYM_ALGO_ML_DSA_65 0x00000002 /* ML-DSA-65, SigLen 3309 */ -#define SPDM_PQC_ASYM_ALGO_ML_DSA_87 0x00000004 /* ML-DSA-87, SigLen 4627 */ - -/* DHE (Diffie-Hellman Ephemeral) Algorithms */ -#define SPDM_DHE_ALGO_SECP384R1 0x0010 /* secp384r1 */ - -/* KEM Algorithms (DSP0274 1.4 Table 24 KEMAlg AlgSupported). Byte 0 bit mask; - * one selected, mutually exclusive with a DHE group (no hybrid in 1.4). */ -#define SPDM_KEM_ALGO_ML_KEM_512 0x0001 /* ML-KEM-512 */ -#define SPDM_KEM_ALGO_ML_KEM_768 0x0002 /* ML-KEM-768 */ -#define SPDM_KEM_ALGO_ML_KEM_1024 0x0004 /* ML-KEM-1024 */ - -/* AEAD Algorithms */ -#define SPDM_AEAD_ALGO_AES_256_GCM 0x0002 /* AES-256-GCM */ - -/* Key Schedule (SPDM 1.2) */ -#define SPDM_KEY_SCHEDULE_SPDM 0x0001 /* Standard SPDM key schedule */ - -/* ALGORITHMS AlgStruct AlgType values (DSP0274 Sec. 10.4 Table 16) */ -#define SPDM_ALG_TYPE_DHE 2 -#define SPDM_ALG_TYPE_AEAD 3 -#define SPDM_ALG_TYPE_REQ_BASE_ASYM 4 -#define SPDM_ALG_TYPE_KEY_SCHEDULE 5 -#define SPDM_ALG_TYPE_REQ_PQC_ASYM 6 /* DSP0274 1.4 Table 21 ReqPqcAsymAlg */ -#define SPDM_ALG_TYPE_KEM 7 /* DSP0274 1.4 KEM (ML-KEM) */ - -/* Which asymmetric family the responder selected (ctx->asymType) */ -#define WOLFSPDM_ASYM_ECDSA 0 /* BaseAsymSel = ECDSA P-384 */ -#define WOLFSPDM_ASYM_MLDSA 1 /* PqcAsymSel = ML-DSA */ - -/* Which key-exchange method the responder selected (ctx->kexType) */ -#define WOLFSPDM_KEX_ECDHE 0 /* DHE group = secp384r1 */ -#define WOLFSPDM_KEX_MLKEM 1 /* KEM = ML-KEM */ - -/* Algorithm Set B Fixed Parameters */ +/* Algorithm Set B Fixed Parameters (FIPS 140-3 Level 3 compliant) + * P-384 ECDSA/ECDH, SHA-384, AES-256-GCM, HKDF */ #define WOLFSPDM_HASH_SIZE 48 /* SHA-384 output size */ #define WOLFSPDM_ECC_KEY_SIZE 48 /* P-384 coordinate size */ #define WOLFSPDM_ECC_POINT_SIZE (2 * WOLFSPDM_ECC_KEY_SIZE) /* P-384 X||Y */ @@ -201,239 +127,80 @@ extern "C" { #define WOLFSPDM_AEAD_KEY_SIZE 32 /* AES-256 key size */ #define WOLFSPDM_AEAD_IV_SIZE 12 /* AES-GCM IV size */ #define WOLFSPDM_AEAD_TAG_SIZE 16 /* AES-GCM tag size */ -#define WOLFSPDM_HMAC_SIZE 48 /* HMAC-SHA384 output size */ +#define WOLFSPDM_AEAD_OVERHEAD 48 /* Max AEAD record overhead (hdr+pad+tag) */ -#ifdef WOLFSPDM_HAVE_MLDSA -/* ML-DSA signature sizes (DSP0274 1.4 Table 19; FIPS 204). The largest sig we - * may need to verify bounds receive/transcript buffers below. */ -#define WOLFSPDM_MLDSA44_SIG_SIZE 2420 -#define WOLFSPDM_MLDSA65_SIG_SIZE 3309 -#define WOLFSPDM_MLDSA87_SIG_SIZE 4627 -#define WOLFSPDM_MAX_SIG_SIZE WOLFSPDM_MLDSA87_SIG_SIZE -#else -#define WOLFSPDM_MAX_SIG_SIZE WOLFSPDM_ECC_SIG_SIZE -#endif +/* ----- Buffer/Message Size Limits ----- */ -#ifdef WOLFSPDM_HAVE_MLKEM -/* ML-KEM sizes (FIPS 203). The KEY_EXCHANGE ExchangeData carries the - * encapsulation key ek; the KEY_EXCHANGE_RSP ExchangeData carries the ciphertext - * c; decapsulation yields a 32-byte shared secret. */ -#define WOLFSPDM_MLKEM512_EK_SIZE 800 -#define WOLFSPDM_MLKEM512_CT_SIZE 768 -#define WOLFSPDM_MLKEM768_EK_SIZE 1184 -#define WOLFSPDM_MLKEM768_CT_SIZE 1088 -#define WOLFSPDM_MLKEM1024_EK_SIZE 1568 -#define WOLFSPDM_MLKEM1024_CT_SIZE 1568 -#define WOLFSPDM_KEM_SS_SIZE 32 -#define WOLFSPDM_MAX_KEM_EK_SIZE WOLFSPDM_MLKEM1024_EK_SIZE -#define WOLFSPDM_MAX_KEM_CT_SIZE WOLFSPDM_MLKEM1024_CT_SIZE -/* Fixed OpaqueData block wolfSPDM_BuildKeyExchange appends after ExchangeData - * (2-byte OpaqueLength + 20-byte secured-message-version block). */ -#define WOLFSPDM_KEX_OPAQUE_LEN 22 -/* KEY_EXCHANGE request buffer: fixed fields + the largest ek + OpaqueData. */ -#define WOLFSPDM_KEX_REQ_BUF (96 + WOLFSPDM_MAX_KEM_EK_SIZE) -#else -#define WOLFSPDM_KEX_REQ_BUF 192 -#endif +#define WOLFSPDM_MAX_MSG_SIZE 4096 /* Maximum SPDM message size */ +#define WOLFSPDM_MAX_TRANSCRIPT 4096 /* Maximum transcript buffer */ +#define WOLFSPDM_RANDOM_SIZE 32 /* Random data in KEY_EXCHANGE */ -/* Receive-buffer size for the signature-bearing responses (KEY_EXCHANGE_RSP, - * CHALLENGE_AUTH). Sized for fixed fields + a small OpaqueData block + the - * negotiated SigLen + HMAC, NOT the full advertised DataTransferSize: like the - * pre-ML-DSA design (4096 advertised vs a 384-byte buffer), this assumes real - * responders keep OpaqueData in these two responses small. These are on-stack - * buffers in wolfSPDM_KeyExchange / wolfSPDM_Challenge, so the ML-DSA value - * (~5.3 KB) is the per-call stack cost on constrained targets. */ -#if defined(WOLFSPDM_HAVE_MLDSA) && defined(WOLFSPDM_HAVE_MLKEM) -/* ML-KEM ciphertext c (up to 1568 B) replaces the 96-byte ECDHE point in the - * KEY_EXCHANGE_RSP ExchangeData, so add headroom for it alongside the SigLen. */ -#define WOLFSPDM_SIG_RSP_BUF (640 + WOLFSPDM_MAX_SIG_SIZE + \ - WOLFSPDM_MAX_KEM_CT_SIZE) -#elif defined(WOLFSPDM_HAVE_MLDSA) -#define WOLFSPDM_SIG_RSP_BUF (640 + WOLFSPDM_MAX_SIG_SIZE) -#elif defined(WOLFSPDM_HAVE_MLKEM) -#define WOLFSPDM_SIG_RSP_BUF (640 + WOLFSPDM_MAX_SIG_SIZE + \ - WOLFSPDM_MAX_KEM_CT_SIZE) -#else -#define WOLFSPDM_SIG_RSP_BUF 512 -#endif +/* ----- MCTP Transport Constants ----- */ -/* --- Capability Flags (per DSP0274) --- */ +#define MCTP_MESSAGE_TYPE_SPDM 0x05 /* SPDM over MCTP */ -/* Requester Capabilities (GET_CAPABILITIES flags) */ -#define SPDM_CAP_CERT_CAP 0x00000002 /* Certificate support */ -#define SPDM_CAP_CHAL_CAP 0x00000004 /* Challenge support */ -#define SPDM_CAP_MEAS_CAP_NO_SIG 0x00000008 /* Measurements without sig */ -#define SPDM_CAP_MEAS_CAP_SIG 0x00000010 /* Measurements with sig */ -#define SPDM_CAP_MEAS_FRESH_CAP 0x00000020 /* Fresh measurements */ -#define SPDM_CAP_ENCRYPT_CAP 0x00000040 /* Encryption support */ -#define SPDM_CAP_MAC_CAP 0x00000080 /* MAC support */ -#define SPDM_CAP_MUT_AUTH_CAP 0x00000100 /* Mutual auth support */ -#define SPDM_CAP_KEY_EX_CAP 0x00000200 /* Key exchange support */ -#define SPDM_CAP_PSK_CAP_NOHB 0x00000400 /* PSK without heartbeat */ -#define SPDM_CAP_PSK_CAP_HB 0x00000800 /* PSK with heartbeat */ -#define SPDM_CAP_ENCAP_CAP 0x00001000 /* Encapsulated request */ -#define SPDM_CAP_HBEAT_CAP 0x00002000 /* Heartbeat support */ -#define SPDM_CAP_KEY_UPD_CAP 0x00004000 /* Key update support */ -#define SPDM_CAP_HANDSHAKE_ITC 0x00008000 /* Handshake in the clear */ -#define SPDM_CAP_PUB_KEY_ID_CAP 0x00010000 /* Public key ID */ -#define SPDM_CAP_CHUNK_CAP 0x00020000 /* Large SPDM message chunking */ +/* ----- Key Derivation Labels (SPDM 1.2 per DSP0277) ----- */ -/* Chunking (DSP0274 Sec. 10.27): CHUNK_GET reassembly of large responses. - * Pure-wolfSPDM (no wolfSSL dependency), default on; WOLFSPDM_NO_CHUNK (or - * --disable-chunking) compiles it out entirely. */ -#if !defined(WOLFSPDM_NO_CHUNK) && !defined(WOLFSPDM_HAVE_CHUNK) - #define WOLFSPDM_HAVE_CHUNK -#endif -#ifdef WOLFSPDM_HAVE_CHUNK -/* MTU: advertised DataTransferSize and size of the single reused transport - * buffer. Lower it for constrained devices (smaller buffer, more round-trips). */ -#ifndef WOLFSPDM_CHUNK_BUF_SIZE -#define WOLFSPDM_CHUNK_BUF_SIZE 4096 -#endif -/* Must hold a CHUNK_RESPONSE header (16 B) plus useful payload. */ -#if WOLFSPDM_CHUNK_BUF_SIZE < 64 -#error "WOLFSPDM_CHUNK_BUF_SIZE must be at least 64" -#endif -/* Reassembly loop guard (max chunks per large message). */ -#ifndef WOLFSPDM_CHUNK_MAX_CHUNKS -#define WOLFSPDM_CHUNK_MAX_CHUNKS 64 -#endif -#define WOLFSPDM_CHUNK_CAP_BIT SPDM_CAP_CHUNK_CAP -#else -#define WOLFSPDM_CHUNK_CAP_BIT 0 -#endif +#define SPDM_BIN_CONCAT_PREFIX_12 "spdm1.2 " +#define SPDM_BIN_CONCAT_PREFIX_13 "spdm1.3 " +#define SPDM_BIN_CONCAT_PREFIX_14 "spdm1.4 " +#define SPDM_BIN_CONCAT_PREFIX_LEN 8 -/* Default requester capabilities for Algorithm Set B session */ -/* DSP0274 Table 11: CERT_CAP and CHAL_CAP are responder-only bits. - * wolfSPDM is a pure requester and never serves certs or challenges, so - * those bits are intentionally absent from the default. */ -#define WOLFSPDM_DEFAULT_REQ_CAPS (SPDM_CAP_ENCRYPT_CAP | SPDM_CAP_MAC_CAP | \ - SPDM_CAP_KEY_EX_CAP | SPDM_CAP_HBEAT_CAP | \ - SPDM_CAP_KEY_UPD_CAP | WOLFSPDM_CHUNK_CAP_BIT) +#define SPDM_LABEL_REQ_HS_DATA "req hs data" +#define SPDM_LABEL_RSP_HS_DATA "rsp hs data" +#define SPDM_LABEL_REQ_DATA "req app data" +#define SPDM_LABEL_RSP_DATA "rsp app data" +#define SPDM_LABEL_FINISHED "finished" +#define SPDM_LABEL_KEY "key" +#define SPDM_LABEL_IV "iv" -/* --- Buffer/Message Size Limits --- */ +/* ----- Buffer Size Macros (overridable) ----- */ -/* ML-DSA payloads (multi-KB sigs, pubkeys, cert chains) need larger buffers - * than Algorithm Set B. Defaults grow when ML-DSA is built in so ML-DSA-65 - * fits a single message; all three are overridable with -D. ML-DSA-87 and - * very large chains rely on the chunking engine. */ -#ifndef WOLFSPDM_MAX_MSG_SIZE -#ifdef WOLFSPDM_HAVE_MLDSA -#define WOLFSPDM_MAX_MSG_SIZE 8192 /* Maximum SPDM message size */ -#else -#define WOLFSPDM_MAX_MSG_SIZE 4096 -#endif -#endif -/* The secured chunk path decrypts each CHUNK_RESPONSE through a - * WOLFSPDM_MAX_MSG_SIZE-bounded stage buffer, so a chunk's plaintext (up to the - * advertised MTU) cannot exceed it. */ -#if defined(WOLFSPDM_HAVE_CHUNK) && !defined(WOLFSPDM_CHUNK_NO_SECURED) && \ - (WOLFSPDM_CHUNK_BUF_SIZE > WOLFSPDM_MAX_MSG_SIZE) -#error "WOLFSPDM_CHUNK_BUF_SIZE must be <= WOLFSPDM_MAX_MSG_SIZE for the secured chunk path" -#endif -#ifndef WOLFSPDM_MAX_CERT_CHAIN -#ifdef WOLFSPDM_HAVE_MLDSA -/* A full ML-DSA-65 responder cert chain (spdm-emu) is ~16.8 KB; ML-DSA-87 and - * alias chains run larger. 24 KB fits the common chains in one buffer. */ -#define WOLFSPDM_MAX_CERT_CHAIN 24576 /* Maximum certificate chain size */ -#else -#define WOLFSPDM_MAX_CERT_CHAIN 4096 +#ifndef WOLFSPDM_KEY_EX_TX_SZ +#define WOLFSPDM_KEY_EX_TX_SZ 192 /* KEY_EXCHANGE request (~158 bytes) */ #endif +#ifndef WOLFSPDM_KEY_EX_RX_SZ +#define WOLFSPDM_KEY_EX_RX_SZ 384 /* KEY_EXCHANGE_RSP (~302 bytes) */ #endif -/* trustedCAs holds a single root CA cert, not a full chain, so it stays small - * even when ML-DSA grows the chain buffer. */ -#ifndef WOLFSPDM_MAX_TRUSTED_CA -#ifdef WOLFSPDM_HAVE_MLDSA -#define WOLFSPDM_MAX_TRUSTED_CA 8192 -#else -#define WOLFSPDM_MAX_TRUSTED_CA 4096 +#ifndef WOLFSPDM_FINISH_BUF_SZ +#define WOLFSPDM_FINISH_BUF_SZ 152 /* FINISH mutual auth (~148 bytes) */ #endif +#ifndef WOLFSPDM_VENDOR_BUF_SZ +#define WOLFSPDM_VENDOR_BUF_SZ 256 /* Vendor command message/payload */ #endif -#ifndef WOLFSPDM_MAX_TRANSCRIPT -#if defined(WOLFSPDM_HAVE_MLDSA) -#define WOLFSPDM_MAX_TRANSCRIPT 16384 /* Maximum transcript buffer */ -#elif defined(WOLFSPDM_HAVE_MLKEM) -/* ML-KEM (no ML-DSA): an ML-KEM-1024 handshake transcript (ek 1568 + ciphertext - * 1568 + the fixed messages) approaches 4 KB, so allow extra headroom. */ -#define WOLFSPDM_MAX_TRANSCRIPT 8192 -#else -#define WOLFSPDM_MAX_TRANSCRIPT 4096 +#ifndef WOLFSPDM_VENDOR_RX_SZ +#define WOLFSPDM_VENDOR_RX_SZ 512 /* Vendor response buffer */ #endif +#ifndef WOLFSPDM_PUBKEY_BUF_SZ +#define WOLFSPDM_PUBKEY_BUF_SZ 256 /* Public key buffer */ #endif -#define WOLFSPDM_RANDOM_SIZE 32 /* Random data in KEY_EXCHANGE */ -/* --- MCTP Transport Constants (for TCP/socket transport) --- */ +/* ----- TCG Build Option ----- */ -#define MCTP_MESSAGE_TYPE_SPDM 0x05 /* SPDM over MCTP */ -#define MCTP_MESSAGE_TYPE_SECURED 0x06 /* Secured SPDM over MCTP */ - -/* Socket protocol for libspdm emulator */ -#ifndef SOCKET_TRANSPORT_TYPE_MCTP -#define SOCKET_TRANSPORT_TYPE_MCTP 0x00000001 -#endif -#ifndef SOCKET_TRANSPORT_TYPE_TCP -#define SOCKET_TRANSPORT_TYPE_TCP 0x00000003 +/* Nuvoton or Nations enables TCG SPDM binding; future chips can set directly */ +#if (defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS)) && \ + !defined(WOLFSPDM_TCG) + #define WOLFSPDM_TCG #endif -#ifndef SOCKET_SPDM_COMMAND_NORMAL -#define SOCKET_SPDM_COMMAND_NORMAL 0x00000001 -#endif - -#ifndef NO_WOLFSPDM_MEAS -/* --- Measurement Constants (DSP0274 Section 10.11) --- */ - -/* MeasurementSummaryHashType (Param1 of GET_MEASUREMENTS) */ -#define SPDM_MEAS_SUMMARY_HASH_NONE 0x00 -#define SPDM_MEAS_SUMMARY_HASH_TCB 0x01 -#define SPDM_MEAS_SUMMARY_HASH_ALL 0xFF -/* MeasurementOperation (Param2 of GET_MEASUREMENTS) */ -#define SPDM_MEAS_OPERATION_TOTAL_NUMBER 0x00 -#define SPDM_MEAS_OPERATION_ALL 0xFF +/* ----- PSK Build Option ----- */ -/* Request signature bit in Param1 */ -#define SPDM_MEAS_REQUEST_SIG_BIT 0x01 - -/* DMTFSpecMeasurementValueType (DSP0274 Table 22) */ -#define SPDM_MEAS_VALUE_TYPE_IMMUTABLE_ROM 0x00 -#define SPDM_MEAS_VALUE_TYPE_MUTABLE_FW 0x01 -#define SPDM_MEAS_VALUE_TYPE_HW_CONFIG 0x02 -#define SPDM_MEAS_VALUE_TYPE_FW_CONFIG 0x03 -#define SPDM_MEAS_VALUE_TYPE_MEAS_MANIFEST 0x04 -#define SPDM_MEAS_VALUE_TYPE_VERSION 0x05 -#define SPDM_MEAS_VALUE_TYPE_RAW_BIT 0x80 /* Bit 7: raw vs digest */ - -/* Configurable limits (override with -D at compile time) */ -#ifndef WOLFSPDM_MAX_MEAS_BLOCKS -#define WOLFSPDM_MAX_MEAS_BLOCKS 16 -#endif -#ifndef WOLFSPDM_MAX_MEAS_VALUE_SIZE -#define WOLFSPDM_MAX_MEAS_VALUE_SIZE 64 /* Fits SHA-512; SHA-384 uses 48 */ +/* Nations build enables PSK by default; can also be set independently */ +#if defined(WOLFSPDM_NATIONS) && !defined(WOLFSPDM_PSK) + #define WOLFSPDM_PSK #endif -#define WOLFSPDM_MEAS_BLOCK_HDR_SIZE 4 /* Index(1) + MeasSpec(1) + Size(2 LE) */ -#endif /* !NO_WOLFSPDM_MEAS */ - -/* --- Key Derivation Labels (SPDM 1.2 per DSP0277) --- */ - -#define SPDM_BIN_CONCAT_PREFIX_12 "spdm1.2 " -#define SPDM_BIN_CONCAT_PREFIX_13 "spdm1.3 " -#define SPDM_BIN_CONCAT_PREFIX_14 "spdm1.4 " -#define SPDM_BIN_CONCAT_PREFIX_LEN 8 +/* ----- PSK Message Codes (SPDM 1.2+ DSP0274) ----- */ -#define SPDM_LABEL_REQ_HS_DATA "req hs data" -#define SPDM_LABEL_RSP_HS_DATA "rsp hs data" -#define SPDM_LABEL_REQ_DATA "req app data" -#define SPDM_LABEL_RSP_DATA "rsp app data" -#define SPDM_LABEL_FINISHED "finished" -#define SPDM_LABEL_KEY "key" -#define SPDM_LABEL_IV "iv" -#define SPDM_LABEL_UPDATE "traffic upd" +#define SPDM_PSK_EXCHANGE 0xE6 +#define SPDM_PSK_EXCHANGE_RSP 0x66 +#define SPDM_PSK_FINISH 0xE7 +#define SPDM_PSK_FINISH_RSP 0x67 -/* KEY_UPDATE Operations (DSP0274 Section 10.9) */ -#define SPDM_KEY_UPDATE_OP_UPDATE_KEY 1 -#define SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS 2 -#define SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY 3 +/* ----- PSK Size Limits ----- */ +#define WOLFSPDM_PSK_MAX_SIZE 64 /* Max PSK size (Nations NS350) */ +#define WOLFSPDM_PSK_HINT_MAX 32 /* Max PSK hint size */ #ifdef __cplusplus } #endif From f6ebdcd906da2b16f1e451354e0dbd5555695bbe Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 13:12:14 -0700 Subject: [PATCH 02/27] Select vendor KEY_EXCHANGE format at runtime and tighten response checks --- src/spdm_context.c | 7 +-- src/spdm_internal.h | 8 ++++ src/spdm_msg.c | 108 ++++++++++++++++++++++++-------------------- src/spdm_secured.c | 17 +++---- test/unit_test.c | 86 +++++++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 63 deletions(-) diff --git a/src/spdm_context.c b/src/spdm_context.c index af34fa6..30670c7 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -27,6 +27,9 @@ #include #include +typedef char wolfSPDM_ctx_size_check_[ + (sizeof(struct WOLFSPDM_CTX) <= WOLFSPDM_CTX_STATIC_SIZE) ? 1 : -1]; + /* ----- Context Management ----- */ int wolfSPDM_Init(WOLFSPDM_CTX* ctx) @@ -411,9 +414,7 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, } #ifdef WOLFSPDM_TCG - if (ctx->mode == WOLFSPDM_MODE_NUVOTON || - ctx->mode == WOLFSPDM_MODE_NATIONS || - ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + if (wolfSPDM_IsTcgMode(ctx)) { /* Wrap messages with TCG SPDM * headers; I/O sends TCG-framed messages. */ byte tcgTx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 340640d..46ca2d4 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -190,6 +190,14 @@ struct WOLFSPDM_CTX { } flags; }; +/* The vendor modes select TCG binding framing and pinned-key identity */ +static WC_INLINE int wolfSPDM_IsTcgMode(const WOLFSPDM_CTX* ctx) +{ + return ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK; +} + /* ----- Byte-Order Helpers ----- */ static WC_INLINE void SPDM_Set16LE(byte* buf, word16 val) { diff --git a/src/spdm_msg.c b/src/spdm_msg.c index 3607ee9..6609508 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -53,18 +53,48 @@ static int wolfSPDM_BuildSimpleMsg(WOLFSPDM_CTX* ctx, byte msgCode, return WOLFSPDM_SUCCESS; } -/* KEY_EXCHANGE request size: 8-byte header, 32-byte RandomData, and two ECC - * coordinates, plus a config-specific OpaqueData block. Keep - * WOLFSPDM_KEYEX_OPAQUE_SZ in sync with the OpaqueData written below. */ +/* KEY_EXCHANGE request size: 8-byte header, 32-byte RandomData and two ECC + * coordinates, followed by the mode's OpaqueData block. */ #define WOLFSPDM_KEYEX_FIXED_SZ (40 + 2 * WOLFSPDM_ECC_KEY_SIZE) + +/* Standard SPDM 1.2+ secured message version list: OpaqueLength(2) + 20 */ +static const byte kexOpaqueStd[] = { + 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x01, + 0x03, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x00, 0x00 +}; #ifdef WOLFSPDM_NUVOTON - #define WOLFSPDM_KEYEX_OPAQUE_SZ 14 -#elif defined(WOLFSPDM_NATIONS) - #define WOLFSPDM_KEYEX_OPAQUE_SZ 2 -#else - #define WOLFSPDM_KEYEX_OPAQUE_SZ 22 +static const byte kexOpaqueNuvoton[] = { + 0x0c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x01, 0x01, 0x00, 0x10, 0x00, + 0x00, 0x00 +}; +#endif +#ifdef WOLFSPDM_NATIONS +/* Nations only accepts OpaqueLength=0 */ +static const byte kexOpaqueNations[] = { 0x00, 0x00 }; #endif +static void wolfSPDM_KeyExOpaque(const WOLFSPDM_CTX* ctx, + const byte** opaque, word32* opaqueSz) +{ + *opaque = kexOpaqueStd; + *opaqueSz = (word32)sizeof(kexOpaqueStd); +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + *opaque = kexOpaqueNuvoton; + *opaqueSz = (word32)sizeof(kexOpaqueNuvoton); + } +#endif +#ifdef WOLFSPDM_NATIONS + if (ctx->mode == WOLFSPDM_MODE_NATIONS) { + *opaque = kexOpaqueNations; + *opaqueSz = (word32)sizeof(kexOpaqueNations); + } +#endif +#if !defined(WOLFSPDM_NUVOTON) && !defined(WOLFSPDM_NATIONS) + (void)ctx; +#endif +} + int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { word32 offset = 0; @@ -72,11 +102,17 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) byte pubKeyY[WOLFSPDM_ECC_KEY_SIZE]; word32 pubKeyXSz = sizeof(pubKeyX); word32 pubKeyYSz = sizeof(pubKeyY); + const byte* opaque; + word32 opaqueSz; int rc; + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + wolfSPDM_KeyExOpaque(ctx, &opaque, &opaqueSz); + /* Require exactly the encoded request size */ - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, - WOLFSPDM_KEYEX_FIXED_SZ + WOLFSPDM_KEYEX_OPAQUE_SZ); + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, WOLFSPDM_KEYEX_FIXED_SZ + opaqueSz); rc = wolfSPDM_GenerateEphemeralKey(ctx); if (rc == WOLFSPDM_SUCCESS) @@ -90,11 +126,8 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) buf[offset++] = ctx->spdmVersion; buf[offset++] = SPDM_KEY_EXCHANGE; buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ -#ifdef WOLFSPDM_TCG - buf[offset++] = 0xFF; /* SlotID = 0xFF (no cert, use provisioned public key) */ -#else - buf[offset++] = 0x00; /* SlotID = 0 (certificate slot 0) */ -#endif + /* SlotID: 0xFF = provisioned public key (TCG), else cert slot 0 */ + buf[offset++] = wolfSPDM_IsTcgMode(ctx) ? 0xFF : 0x00; /* ReqSessionID (2 LE) */ buf[offset++] = (byte)(ctx->reqSessionId & 0xFF); @@ -115,34 +148,8 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) offset += WOLFSPDM_ECC_KEY_SIZE; /* OpaqueData for secured message version negotiation */ -#ifdef WOLFSPDM_NUVOTON - /* Nuvoton vendor format: 12 bytes */ - buf[offset++] = 0x0c; buf[offset++] = 0x00; - buf[offset++] = 0x00; buf[offset++] = 0x00; - buf[offset++] = 0x05; buf[offset++] = 0x00; - buf[offset++] = 0x01; buf[offset++] = 0x01; - buf[offset++] = 0x01; buf[offset++] = 0x00; - buf[offset++] = 0x10; buf[offset++] = 0x00; - buf[offset++] = 0x00; buf[offset++] = 0x00; -#elif defined(WOLFSPDM_NATIONS) - /* Empty OpaqueData — Nations only accepts OpaqueLength=0 */ - buf[offset++] = 0x00; buf[offset++] = 0x00; -#else - /* Standard SPDM 1.2+ OpaqueData format: 20 bytes */ - buf[offset++] = 0x14; /* OpaqueLength = 20 */ - buf[offset++] = 0x00; - buf[offset++] = 0x01; buf[offset++] = 0x00; /* TotalElements */ - buf[offset++] = 0x00; buf[offset++] = 0x00; /* Reserved */ - buf[offset++] = 0x00; buf[offset++] = 0x00; - buf[offset++] = 0x09; buf[offset++] = 0x00; /* DataSize */ - buf[offset++] = 0x01; /* Registry ID */ - buf[offset++] = 0x01; /* VendorLen */ - buf[offset++] = 0x03; buf[offset++] = 0x00; /* VersionCount */ - buf[offset++] = 0x10; buf[offset++] = 0x00; /* 1.0 */ - buf[offset++] = 0x11; buf[offset++] = 0x00; /* 1.1 */ - buf[offset++] = 0x12; buf[offset++] = 0x00; /* 1.2 */ - buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding */ -#endif + XMEMCPY(&buf[offset], opaque, opaqueSz); + offset += opaqueSz; *bufSz = offset; } @@ -413,14 +420,10 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 140); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_KEY_EXCHANGE_RSP, WOLFSPDM_E_KEY_EXCHANGE); - ctx->rspSessionId = SPDM_Get16LE(&buf[4]); - ctx->sessionId = (word32)ctx->reqSessionId | ((word32)ctx->rspSessionId << 16); - - /* Parse MutAuthRequested and ReqSlotIDParam (offsets 6-7) */ - ctx->mutAuthRequested = buf[6]; - ctx->reqSlotIdParam = buf[7]; + /* RspSessionID (4-5), MutAuthRequested (6), ReqSlotIDParam (7) are + * committed to ctx only after the signature and HMAC verify */ wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: MutAuth=0x%02x ReqSlotID=0x%02x\n", - ctx->mutAuthRequested, ctx->reqSlotIdParam); + buf[6], buf[7]); /* Extract responder's ephemeral public key (offset 40 = 4+2+1+1+32) */ XMEMCPY(peerPubKeyX, &buf[40], WOLFSPDM_ECC_KEY_SIZE); @@ -492,6 +495,11 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); } if (rc == WOLFSPDM_SUCCESS) { + ctx->rspSessionId = SPDM_Get16LE(&buf[4]); + ctx->sessionId = (word32)ctx->reqSessionId | + ((word32)ctx->rspSessionId << 16); + ctx->mutAuthRequested = buf[6]; + ctx->reqSlotIdParam = buf[7]; ctx->state = WOLFSPDM_STATE_KEY_EX; } diff --git a/src/spdm_secured.c b/src/spdm_secured.c index 04614b3..be7c4ea 100644 --- a/src/spdm_secured.c +++ b/src/spdm_secured.c @@ -64,9 +64,7 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, } #ifdef WOLFSPDM_TCG - if (ctx->mode == WOLFSPDM_MODE_NUVOTON || - ctx->mode == WOLFSPDM_MODE_NATIONS || - ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + if (wolfSPDM_IsTcgMode(ctx)) { /* Nuvoton TCG binding format per Rev 1.11 spec page 25: * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes * IV XOR: Leftmost 8 bytes (bytes 0-7) with 8-byte LE sequence number @@ -200,9 +198,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, /* ----- Transport-specific header parsing ----- */ #ifdef WOLFSPDM_TCG - if (ctx->mode == WOLFSPDM_MODE_NUVOTON || - ctx->mode == WOLFSPDM_MODE_NATIONS || - ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + if (wolfSPDM_IsTcgMode(ctx)) { word64 rspSeqNum64; word32 rspSessionId; word16 rspLen; @@ -263,7 +259,8 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, rspSeqNum, (unsigned long long)ctx->rspSeqNum); return WOLFSPDM_E_SEQUENCE; } - if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < (word32)(hdrSz + rspLen)) + /* DSP0277: Length covers exactly the rest of the record */ + if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz != (word32)(hdrSz + rspLen)) return WOLFSPDM_E_BUFFER_SMALL; cipherLen = (word32)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); @@ -313,9 +310,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, else if (rc == 0) { appDataLen = SPDM_Get16LE(decrypted); #ifdef WOLFSPDM_TCG - if (ctx->mode == WOLFSPDM_MODE_NUVOTON || - ctx->mode == WOLFSPDM_MODE_NATIONS || - ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + if (wolfSPDM_IsTcgMode(ctx)) { /* TCG binding: AppDataLen(2) || SPDM msg || RandomData */ if (cipherLen < (word32)(2 + appDataLen) || *plainSz < appDataLen) { @@ -332,6 +327,8 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen) || *plainSz < (word32)(appDataLen - 1)) { ret = WOLFSPDM_E_BUFFER_SMALL; + } else if (decrypted[2] != MCTP_MESSAGE_TYPE_SPDM) { + ret = WOLFSPDM_E_DECRYPT_FAIL; } else { XMEMCPY(plain, &decrypted[3], appDataLen - 1); *plainSz = appDataLen - 1; diff --git a/test/unit_test.c b/test/unit_test.c index ebb563f..98c80f7 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -536,6 +536,8 @@ static int test_mitm_signature_rejected(void) /* Parse should reject: signature doesn't match real responder's key */ rc = wolfSPDM_ParseKeyExchangeRsp(ctx, keRsp, 282); ASSERT_EQ(rc, WOLFSPDM_E_BAD_SIGNATURE, "MITM forged sig must be rejected"); + ASSERT_EQ(ctx->sessionId, (word32)0, + "forged response must not set the session ID"); wc_ecc_free(&realKey); wc_ecc_free(&attackerKey); @@ -2199,6 +2201,82 @@ static int test_build_key_exchange_format(void) TEST_PASS(); } +static int test_build_key_exchange_mode_opaque(void) +{ + byte buf[256]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_key_exchange_mode_opaque...\n"); + + /* Standard mode: cert slot 0 and the 20-byte version list */ + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); + ASSERT_EQ(buf[3], 0x00, "standard SlotID must be 0"); + ASSERT_EQ(bufSz, (word32)(136 + 22), "standard KEY_EXCHANGE size"); + ASSERT_EQ(buf[136], 0x14, "standard OpaqueLength must be 20"); + +#ifdef WOLFSPDM_NUVOTON + ASSERT_SUCCESS(wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NUVOTON)); + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); + ASSERT_EQ(buf[3], 0xFF, "Nuvoton SlotID must be 0xFF"); + ASSERT_EQ(bufSz, (word32)(136 + 14), "Nuvoton KEY_EXCHANGE size"); + ASSERT_EQ(buf[136], 0x0C, "Nuvoton OpaqueLength must be 12"); +#endif +#ifdef WOLFSPDM_NATIONS + ASSERT_SUCCESS(wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS)); + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); + ASSERT_EQ(buf[3], 0xFF, "Nations SlotID must be 0xFF"); + ASSERT_EQ(bufSz, (word32)(136 + 2), "Nations KEY_EXCHANGE size"); + ASSERT_EQ(buf[136] | buf[137], 0, "Nations OpaqueLength must be 0"); +#endif + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_decrypt_rejects_wrong_mctp_type(void) +{ + /* An authenticated record whose inner MCTP type is not SPDM must be + * rejected, and the sequence still advances past the verified tag */ + Aes aes; + byte inner[4]; + byte rec[8 + sizeof(inner) + WOLFSPDM_AEAD_TAG_SIZE]; + byte iv[WOLFSPDM_AEAD_IV_SIZE]; + byte dec[64]; + word32 decSz = sizeof(dec); + TEST_CTX_SETUP_V12(); + printf("test_decrypt_rejects_wrong_mctp_type...\n"); + + ctx->sessionId = 0x11223344; + XMEMSET(ctx->rspDataKey, 0x33, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataIv, 0x44, WOLFSPDM_AEAD_IV_SIZE); + + SPDM_Set16LE(inner, 2); + inner[2] = 0x06; + inner[3] = SPDM_VERSION_12; + SPDM_Set32LE(&rec[0], ctx->sessionId); + SPDM_Set16LE(&rec[4], 0); + SPDM_Set16LE(&rec[6], (word16)(sizeof(inner) + WOLFSPDM_AEAD_TAG_SIZE)); + wolfSPDM_BuildIV(iv, ctx->rspDataIv, 0); + + ASSERT_SUCCESS(wc_AesInit(&aes, NULL, INVALID_DEVID)); + ASSERT_SUCCESS(wc_AesGcmSetKey(&aes, ctx->rspDataKey, + WOLFSPDM_AEAD_KEY_SIZE)); + ASSERT_SUCCESS(wc_AesGcmEncrypt(&aes, &rec[8], inner, sizeof(inner), + iv, sizeof(iv), &rec[8 + sizeof(inner)], WOLFSPDM_AEAD_TAG_SIZE, + rec, 8)); + wc_AesFree(&aes); + + ASSERT_EQ(wolfSPDM_DecryptInternal(ctx, rec, sizeof(rec), dec, &decSz), + WOLFSPDM_E_DECRYPT_FAIL, "wrong inner MCTP type must fail"); + ASSERT_EQ(ctx->rspSeqNum, (word64)1, + "authenticated record must advance rspSeqNum"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + static int test_build_finish_null_args(void) { byte buf[256]; @@ -2287,6 +2365,12 @@ static int test_encrypt_decrypt_roundtrip(void) /* Reset rsp seq to match what was encrypted (req incremented to 1) */ ctx->rspSeqNum = 0; + /* Trailing bytes past the record Length must be rejected */ + enc[encSz] = 0x00; + ASSERT_EQ(wolfSPDM_DecryptInternal(ctx, enc, encSz + 1, dec, &decSz), + WOLFSPDM_E_BUFFER_SMALL, "trailing byte must be rejected"); + decSz = sizeof(dec); + /* Decrypt */ ASSERT_SUCCESS(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz)); ASSERT_EQ(decSz, 16, "decrypted size mismatch"); @@ -2909,12 +2993,14 @@ int main(void) /* Internal message building */ test_build_key_exchange_null_args(); test_build_key_exchange_format(); + test_build_key_exchange_mode_opaque(); test_build_finish_null_args(); test_build_finish_format(); /* Internal encrypt/decrypt */ test_encrypt_internal_null_args(); test_encrypt_decrypt_roundtrip(); + test_decrypt_rejects_wrong_mctp_type(); #ifdef WOLFSPDM_TCG test_encrypt_decrypt_roundtrip_tcg(); #endif From 5dd275524edc4777f9238c1f9fccf0ee4f9f13d7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 13:38:57 -0700 Subject: [PATCH 03/27] Add the standard certificate requester on the wolfTPM core --- .github/workflows/spdm-emu-test.yml | 127 +++++ .github/workflows/static-analysis.yml | 1 + Makefile.am | 14 +- config.h.in | 3 + configure.ac | 12 + examples/spdm_demo.c | 645 ++++++++++++++++++++++++++ examples/spdm_test.sh | 283 +++++++++++ src/spdm_context.c | 36 +- src/spdm_internal.h | 44 ++ src/spdm_msg.c | 12 +- src/spdm_session.c | 6 +- src/spdm_standard.c | 591 +++++++++++++++++++++++ test/test_certs.h | 208 +++++++++ test/unit_test.c | 244 +++++++++- wolfspdm/spdm.h | 19 + wolfspdm/spdm_error.h | 4 + wolfspdm/spdm_types.h | 60 +++ 17 files changed, 2299 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/spdm-emu-test.yml create mode 100644 examples/spdm_demo.c create mode 100755 examples/spdm_test.sh create mode 100644 src/spdm_standard.c create mode 100644 test/test_certs.h diff --git a/.github/workflows/spdm-emu-test.yml b/.github/workflows/spdm-emu-test.yml new file mode 100644 index 0000000..95e65c0 --- /dev/null +++ b/.github/workflows/spdm-emu-test.yml @@ -0,0 +1,127 @@ +name: SPDM Emulator Integration Test + +on: + push: + branches: [ 'main', 'wolftpm-core', 'release/**' ] + pull_request: + branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] + +jobs: + spdm-emu-test: + name: ${{ matrix.os }} (${{ matrix.arch }}) / dynamic-mem=${{ matrix.dynamic-mem }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + arch: x64 + dynamic-mem: 'no' + - os: ubuntu-22.04 + arch: x64 + dynamic-mem: 'yes' + - os: ubuntu-24.04 + arch: x64 + dynamic-mem: 'no' + - os: ubuntu-24.04 + arch: x64 + dynamic-mem: 'yes' + - os: ubuntu-24.04-arm + arch: aarch64 + dynamic-mem: 'no' + - os: ubuntu-24.04-arm + arch: aarch64 + dynamic-mem: 'yes' + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake libtool cmake libmbedtls-dev + + # Cache period rotates every ~15 days so dependencies stay fresh + - name: Compute cache period + id: cache-period + run: echo "biweekly=$(( $(date +%s) / 1296000 ))" >> $GITHUB_OUTPUT + + # --- wolfSSL (cached) --- + - name: Cache wolfSSL + id: cache-wolfssl + uses: actions/cache@v4 + with: + path: ~/wolfssl-install + key: wolfssl-spdm-${{ matrix.os }}-${{ steps.cache-period.outputs.biweekly }} + + - name: Build wolfSSL + if: steps.cache-wolfssl.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 https://github.com/wolfSSL/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-wolftpm --enable-ecc --enable-sha384 \ + --enable-aesgcm --enable-hkdf --enable-sp \ + --prefix=$HOME/wolfssl-install + make -j$(nproc) + make install + + # --- wolfSPDM (always rebuilt - this is what we're testing) --- + - name: Build and install wolfSPDM + run: | + ./autogen.sh + ./configure --with-wolfssl=$HOME/wolfssl-install \ + --prefix=$HOME/wolfspdm-install \ + ${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }} + make -j$(nproc) + make install + + - name: Run unit tests + run: make check + env: + LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib + + # --- spdm-emu (cached) --- + - name: Cache spdm-emu + id: cache-spdm-emu + uses: actions/cache@v4 + with: + path: ~/spdm-emu/build/bin + key: spdm-emu-${{ matrix.os }}-${{ steps.cache-period.outputs.biweekly }} + + - name: Build spdm-emu + if: steps.cache-spdm-emu.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 --recursive https://github.com/DMTF/spdm-emu.git + cd spdm-emu + mkdir build && cd build + cmake -DARCH=${{ matrix.arch }} -DTOOLCHAIN=GCC \ + -DTARGET=Release -DCRYPTO=mbedtls .. + make copy_sample_key + make -j$(nproc) + + # --- Demo smoke (help text / arg parsing, no emulator) --- + - name: spdm_demo CLI smoke (no emulator) + run: | + export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib + ./examples/spdm_demo --help 2>&1 | head -20 || true + + # --- Integration matrix (scenarios x SPDM 1.2/1.3/1.4; unbuilt ones skip) --- + - name: Run SPDM emulator tests + run: | + export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib + export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin + ./examples/spdm_test.sh + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: spdm-emu-test-logs-${{ matrix.os }}-${{ matrix.arch }}-dynmem-${{ matrix.dynamic-mem }} + path: | + config.log + test/*.log + /tmp/spdm_emu_*.log diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 253f0b0..783069a 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -48,6 +48,7 @@ jobs: --error-exitcode=1 \ --suppress=missingIncludeSystem \ --suppress=constParameterPointer \ + --suppress=constParameterCallback \ --suppress=knownConditionTrueFalse \ --inline-suppr \ -I wolfspdm -I src -I $HOME/wolfssl-install/include \ diff --git a/Makefile.am b/Makefile.am index d9e62f1..41aa36d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -11,6 +11,9 @@ libwolfspdm_la_SOURCES = \ src/spdm_session.c \ src/spdm_transcript.c +if BUILD_CERT +libwolfspdm_la_SOURCES += src/spdm_standard.c +endif if BUILD_TCG libwolfspdm_la_SOURCES += src/spdm_tcg.c endif @@ -55,18 +58,27 @@ nobase_include_HEADERS = \ # Test programs check_PROGRAMS = test/unit_test -test_unit_test_SOURCES = test/unit_test.c +test_unit_test_SOURCES = test/unit_test.c test/test_certs.h test_unit_test_CPPFLAGS = -I$(srcdir)/wolfspdm -I$(srcdir)/src test_unit_test_LDADD = libwolfspdm.la -lwolfssl TESTS = test/unit_test +# spdm-emu driver (standard certificate flow over TCP/MCTP) +if BUILD_CERT +noinst_PROGRAMS = examples/spdm_demo +examples_spdm_demo_SOURCES = examples/spdm_demo.c +examples_spdm_demo_CPPFLAGS = -I$(srcdir)/wolfspdm +examples_spdm_demo_LDADD = libwolfspdm.la -lwolfssl +endif + # pkgconfig pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = wolfspdm.pc EXTRA_DIST = \ src/spdm_internal.h \ + examples/spdm_test.sh \ autogen.sh \ README.md \ LICENSE \ diff --git a/config.h.in b/config.h.in index 50e585f..8ef8c89 100644 --- a/config.h.in +++ b/config.h.in @@ -74,6 +74,9 @@ /* Enable Nations SPDM vendor commands */ #undef WOLFSPDM_NATIONS +/* Disable the standard certificate requester */ +#undef WOLFSPDM_NO_CERT + /* Enable Nuvoton SPDM vendor commands */ #undef WOLFSPDM_NUVOTON diff --git a/configure.ac b/configure.ac index 8fafcef..30a3e79 100644 --- a/configure.ac +++ b/configure.ac @@ -80,6 +80,17 @@ if test "x$enable_dynamic_mem" = "xyes"; then AC_DEFINE([WOLFSPDM_DYNAMIC_MEMORY], [1], [Enable dynamic memory allocation]) fi +# Standard certificate-based requester (DSP0274 flow for non-TPM responders) +AC_ARG_ENABLE([cert], + [AS_HELP_STRING([--disable-cert], [Disable the standard certificate-based requester (default: enabled)])], + [enable_cert=$enableval], + [enable_cert=yes]) + +if test "x$enable_cert" = "xno"; then + AC_DEFINE([WOLFSPDM_NO_CERT], [1], [Disable the standard certificate requester]) +fi +AM_CONDITIONAL([BUILD_CERT], [test "x$enable_cert" = "xyes"]) + # TCG SPDM binding (TPM transport). Nuvoton/Nations imply it. AC_ARG_ENABLE([tcg], [AS_HELP_STRING([--enable-tcg], [Enable the TCG SPDM binding (default: disabled)])], @@ -150,6 +161,7 @@ echo "wolfSPDM configuration summary:" echo " Version: $PACKAGE_VERSION" echo " Debug: $enable_debug" echo " Dynamic mem: $enable_dynamic_mem" +echo " Standard: $enable_cert" echo " TCG: $enable_tcg" echo " PSK: $enable_psk" echo " Nuvoton: $enable_nuvoton" diff --git a/examples/spdm_demo.c b/examples/spdm_demo.c new file mode 100644 index 0000000..bc8d1b8 --- /dev/null +++ b/examples/spdm_demo.c @@ -0,0 +1,645 @@ +/* spdm_demo.c + * + * wolfSPDM emulator demo - drives spdm-emu over TCP/MCTP for end-to-end + * testing of session, measurements, challenge, heartbeat, and key update. + * + * Usage: + * spdm_demo --emu [--ver 1.2|1.3|1.4] + * spdm_demo --meas [--no-sig] [--ver ...] + * spdm_demo --challenge [--ver ...] + * spdm_demo --heartbeat [--ver ...] + * spdm_demo --key-update [--ver ...] + * + * Picks up the spdm-emu install dir from $SPDM_EMU_PATH (used to find the + * ca.cert.der for --challenge). + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#define HAS_SOCKET 1 +#endif + +#define EMU_HOST "127.0.0.1" +#define EMU_PORT 2323 + +#ifdef HAS_SOCKET + +typedef struct { + int sockFd; +} TCP_CTX; + +static TCP_CTX g_tcpCtx = { -1 }; + +/* A secured SPDM record starts with the 4-byte session ID; a plain SPDM + * message starts with a version byte. Look up the live session ID from + * the wolfSPDM context: before KEY_EXCHANGE_RSP it's 0, after it matches + * the first 4 bytes of every secured record. Robust against non-default + * reqSessionId picks, unlike a buf[0] range check. */ +static int is_secured_spdm(WOLFSPDM_CTX* ctx, const byte* buf, word32 sz) +{ + word32 sid; + word32 b0; + if (sz < 4) return 0; + sid = wolfSPDM_GetSessionId(ctx); + if (sid == 0) return 0; + b0 = (word32)buf[0] | ((word32)buf[1] << 8) | + ((word32)buf[2] << 16) | ((word32)buf[3] << 24); + return b0 == sid; +} + +/* send_all / recv_all: loop until the full count is transferred or a hard + * error occurs. TCP send/recv may return short on a busy / interrupted + * socket; MSG_WAITALL handles most recv cases but is advisory only, and + * send() must always be looped. */ +static int send_all(int fd, const void* buf, size_t len) +{ + const byte* p = (const byte*)buf; + size_t left = len; + while (left > 0) { + ssize_t n = send(fd, p, left, 0); + if (n < 0) { + if (errno == EINTR) continue; + return -1; + } + if (n == 0) return -1; + p += (size_t)n; + left -= (size_t)n; + } + return 0; +} + +static int recv_all(int fd, void* buf, size_t len) +{ + byte* p = (byte*)buf; + size_t left = len; + while (left > 0) { + ssize_t n = recv(fd, p, left, 0); + if (n < 0) { + if (errno == EINTR) continue; + return -1; + } + if (n == 0) return -1; /* peer closed */ + p += (size_t)n; + left -= (size_t)n; + } + return 0; +} + +/* MCTP transport I/O callback for spdm-emu (--trans MCTP, the default) */ +static int tcp_io_callback(WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + void* userCtx) +{ + TCP_CTX* tcpCtx = (TCP_CTX*)userCtx; + byte sendBuf[4096]; + byte recvHdr[12]; + byte mctpHdr; + word32 payloadSz, respSize; + + if (tcpCtx == NULL || tcpCtx->sockFd < 0) { + return -1; + } + + /* Bound txSz first so the +1/+12 additions can't overflow word32. */ + if (txSz > sizeof(sendBuf) - 13) { + return -1; + } + payloadSz = 1 + txSz; + + /* Socket header: command(4,BE) + transport_type(4,BE) + size(4,BE) */ + sendBuf[0] = 0x00; sendBuf[1] = 0x00; sendBuf[2] = 0x00; sendBuf[3] = 0x01; + sendBuf[4] = 0x00; sendBuf[5] = 0x00; sendBuf[6] = 0x00; sendBuf[7] = 0x01; + sendBuf[8] = (byte)(payloadSz >> 24); + sendBuf[9] = (byte)(payloadSz >> 16); + sendBuf[10] = (byte)(payloadSz >> 8); + sendBuf[11] = (byte)(payloadSz & 0xFF); + + /* MCTP message type: 0x05 = SPDM, 0x06 = Secured SPDM. */ + sendBuf[12] = is_secured_spdm(ctx, txBuf, txSz) ? 0x06 : 0x05; + + if (txSz > 0) { + memcpy(sendBuf + 13, txBuf, txSz); + } + + if (send_all(tcpCtx->sockFd, sendBuf, (size_t)(12 + payloadSz)) != 0) { + return -1; + } + + if (recv_all(tcpCtx->sockFd, recvHdr, sizeof(recvHdr)) != 0) { + return -1; + } + + respSize = ((word32)recvHdr[8] << 24) | ((word32)recvHdr[9] << 16) | + ((word32)recvHdr[10] << 8) | (word32)recvHdr[11]; + + if (respSize < 1 || respSize - 1 > *rxSz) { + return -1; + } + + /* Skip MCTP header byte */ + if (recv_all(tcpCtx->sockFd, &mctpHdr, 1) != 0) return -1; + (void)mctpHdr; + + *rxSz = respSize - 1; + if (*rxSz > 0) { + if (recv_all(tcpCtx->sockFd, rxBuf, (size_t)*rxSz) != 0) return -1; + } + return 0; +} + +static int tcp_connect(const char* host, int port) +{ + int sockFd; + struct sockaddr_in addr; + int optVal = 1; + + sockFd = socket(AF_INET, SOCK_STREAM, 0); + if (sockFd < 0) return -1; + + setsockopt(sockFd, IPPROTO_TCP, TCP_NODELAY, &optVal, sizeof(optVal)); + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons((uint16_t)port); + if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { + close(sockFd); + return -1; + } + if (connect(sockFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + close(sockFd); + return -1; + } + g_tcpCtx.sockFd = sockFd; + return 0; +} + +static void tcp_disconnect(void) +{ + if (g_tcpCtx.sockFd >= 0) { + close(g_tcpCtx.sockFd); + g_tcpCtx.sockFd = -1; + } +} + +/* Load DER from file. Returns malloc'd buffer; caller frees. */ +static byte* load_der(const char* path, word32* outSz) +{ + FILE* f = fopen(path, "rb"); + long sz; + byte* buf; + size_t r; + + if (f == NULL) return NULL; + if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } + sz = ftell(f); + if (sz <= 0) { fclose(f); return NULL; } + rewind(f); + + buf = (byte*)malloc((size_t)sz); + if (buf == NULL) { fclose(f); return NULL; } + + r = fread(buf, 1, (size_t)sz, f); + fclose(f); + if (r != (size_t)sz) { free(buf); return NULL; } + + *outSz = (word32)sz; + return buf; +} + +/* Static-mode buffer for the SPDM context; sized by the public header. */ +static byte g_ctxBuf[WOLFSPDM_CTX_STATIC_SIZE]; +#define CTX_BUF_SIZE ((int)sizeof(g_ctxBuf)) + +enum { + MODE_SESSION = 1, /* --emu */ + MODE_MEAS, /* --meas */ + MODE_CHALLENGE, /* --challenge */ + MODE_HEARTBEAT, /* --heartbeat */ + MODE_KEY_UPDATE /* --key-update */ +}; + +static void usage(const char* argv0) +{ + fprintf(stderr, + "Usage: %s {--emu|--meas|--challenge|--heartbeat|--key-update}\n" + " [--no-sig] [--ver 1.2|1.3|1.4]\n" + " [--kex ecdhe|mlkem512|mlkem768|mlkem1024] [--debug]\n" + "\n" + "Env:\n" + " SPDM_EMU_PATH path to spdm-emu build/bin/ (used for trusted CA\n" + " lookup in --challenge mode)\n" + " SPDM_EMU_CERT_DIR cert subdir (ecp384 default, mldsa65, ...)\n", + argv0); +} + +/* Map "1.2"/"1.3"/"1.4" -> 0x12/0x13/0x14. Returns 0 on parse error. */ +static byte parse_version(const char* s) +{ + if (s == NULL) return 0; + if (strcmp(s, "1.2") == 0) return SPDM_VERSION_12; + if (strcmp(s, "1.3") == 0) return SPDM_VERSION_13; + if (strcmp(s, "1.4") == 0) return SPDM_VERSION_14; + return 0; +} + +/* Sanitize SPDM_EMU_PATH before joining a fixed suffix and handing it to + * fopen(): reject NULL, oversized, traversal-bearing, or non-printable + * input, then canonicalize with realpath() so the value used by fopen is + * a resolved filesystem path, not raw env data. This is a demo, but + * CodeQL flags concatenated env-vars in path expressions and the fix is + * also defensive against a malicious shell environment. */ +static int sanitize_emu_path(const char* emuPath, char* outReal, size_t outSz) +{ + size_t len, i; + char resolved[PATH_MAX]; + + if (emuPath == NULL) return -1; + len = strlen(emuPath); + if (len == 0 || len > PATH_MAX - 32) return -1; + for (i = 0; i < len; i++) { + unsigned char c = (unsigned char)emuPath[i]; + if (c < 0x20 || c == 0x7F) return -1; /* no control chars */ + } + + /* POSIX realpath(path, resolved): resolved must point to a buffer of + * PATH_MAX bytes. (Avoid the GNU realpath(path, NULL) extension.) + * realpath() canonicalizes any ../ segments so the resolved path is + * the actual filesystem location used by fopen(), which is what + * CodeQL's "uncontrolled data in path expression" rule asks for. */ + if (realpath(emuPath, resolved) == NULL) return -1; + len = strlen(resolved); + if (len >= outSz) return -1; + memcpy(outReal, resolved, len + 1); + return 0; +} + +static int load_trusted_ca(WOLFSPDM_CTX* ctx) +{ + /* Cert subdir matches the responder's selected algorithm. Only a fixed set + * of spdm-emu directory names is accepted; the env value is mapped to the + * matching string literal so no caller-controlled data reaches the fopen() + * path below (avoids path traversal). */ + static const char* const allowedCertDirs[] = { + "ecp256", "ecp384", "ecp521", "mldsa44", "mldsa65", "mldsa87" + }; + const char* emuPath = getenv("SPDM_EMU_PATH"); + const char* certDir = getenv("SPDM_EMU_CERT_DIR"); + const char* safeDir = NULL; + char realEmu[PATH_MAX]; + char path[PATH_MAX]; + byte* der; + word32 derSz; + int rc; + int n; + unsigned int i; + + if (emuPath == NULL) { + fprintf(stderr, "ERROR: SPDM_EMU_PATH not set; cannot locate " + "ca.cert.der for --challenge\n"); + return -1; + } + if (sanitize_emu_path(emuPath, realEmu, sizeof(realEmu)) != 0) { + fprintf(stderr, "ERROR: SPDM_EMU_PATH is not a valid directory path\n"); + return -1; + } + if (certDir == NULL || certDir[0] == '\0') { + certDir = "ecp384"; /* default: ECDSA P-384 */ + } + for (i = 0; i < sizeof(allowedCertDirs) / sizeof(allowedCertDirs[0]); i++) { + if (strcmp(certDir, allowedCertDirs[i]) == 0) { + safeDir = allowedCertDirs[i]; + break; + } + } + if (safeDir == NULL) { + fprintf(stderr, "ERROR: unsupported SPDM_EMU_CERT_DIR '%s'\n", certDir); + return -1; + } + n = snprintf(path, sizeof(path), "%s/%s/ca.cert.der", realEmu, safeDir); + if (n < 0 || (size_t)n >= sizeof(path)) { + fprintf(stderr, "ERROR: certificate path too long\n"); + return -1; + } + + der = load_der(path, &derSz); + if (der == NULL) { + fprintf(stderr, "ERROR: cannot read %s\n", path); + return -1; + } + rc = wolfSPDM_SetTrustedCAs(ctx, der, derSz); + free(der); + if (rc != WOLFSPDM_SUCCESS) { + fprintf(stderr, "ERROR: wolfSPDM_SetTrustedCAs: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return -1; + } + return 0; +} + +static int do_session(WOLFSPDM_CTX* ctx) +{ + int rc = wolfSPDM_Connect(ctx); + if (rc != WOLFSPDM_SUCCESS) { + fprintf(stderr, "wolfSPDM_Connect: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return rc; + } + printf("Session established (id=0x%08x, version=0x%02x)\n", + wolfSPDM_GetSessionId(ctx), + wolfSPDM_GetNegotiatedVersion(ctx)); + return WOLFSPDM_SUCCESS; +} + +#ifdef WOLFSPDM_HAS_MEASUREMENTS +static int do_meas(WOLFSPDM_CTX* ctx, int withSig) +{ + int rc; + + rc = do_session(ctx); + if (rc != WOLFSPDM_SUCCESS) return rc; + + rc = wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, withSig); + if (withSig) { + if (rc != WOLFSPDM_SUCCESS) { + fprintf(stderr, "GetMeasurements (signed): %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return rc; + } + printf("Signed measurements verified (%d blocks)\n", + wolfSPDM_GetMeasurementCount(ctx)); + } + else { + /* Unsigned: NOT_VERIFIED is the expected success return */ + if (rc != WOLFSPDM_SUCCESS && rc != WOLFSPDM_E_MEAS_NOT_VERIFIED) { + fprintf(stderr, "GetMeasurements (unsigned): %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return rc; + } + printf("Unsigned measurements received (%d blocks)\n", + wolfSPDM_GetMeasurementCount(ctx)); + rc = WOLFSPDM_SUCCESS; + } + return rc; +} + +#endif + +#ifdef WOLFSPDM_HAS_CHALLENGE +static int do_challenge(WOLFSPDM_CTX* ctx) +{ + int rc; + + /* Sessionless: walk through GET_VERSION -> CAPABILITIES -> ALGORITHMS -> + * GET_DIGESTS -> GET_CERTIFICATE, then CHALLENGE. We don't call + * wolfSPDM_Connect() because that does KEY_EXCHANGE + FINISH. */ + rc = wolfSPDM_GetVersion(ctx); + if (rc != WOLFSPDM_SUCCESS) goto done; + rc = wolfSPDM_GetCapabilities(ctx); + if (rc != WOLFSPDM_SUCCESS) goto done; + rc = wolfSPDM_NegotiateAlgorithms(ctx); + if (rc != WOLFSPDM_SUCCESS) goto done; + rc = wolfSPDM_GetDigests(ctx); + if (rc != WOLFSPDM_SUCCESS) goto done; + rc = wolfSPDM_GetCertificate(ctx, 0); + if (rc != WOLFSPDM_SUCCESS) goto done; + + rc = load_trusted_ca(ctx); + if (rc != 0) { rc = WOLFSPDM_E_INVALID_ARG; goto done; } + + /* wolfSPDM_Challenge internally validates the cert chain against the + * loaded CAs when flags.hasTrustedCAs is set. */ + rc = wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_ALL); + if (rc == WOLFSPDM_SUCCESS) { + printf("Challenge succeeded (signature verified)\n"); + } +done: + if (rc != WOLFSPDM_SUCCESS) { + fprintf(stderr, "Challenge flow failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + return rc; +} + +#endif + +#ifdef WOLFSPDM_HAS_HEARTBEAT +static int do_heartbeat(WOLFSPDM_CTX* ctx) +{ + int rc = do_session(ctx); + if (rc != WOLFSPDM_SUCCESS) return rc; + rc = wolfSPDM_Heartbeat(ctx); + if (rc == WOLFSPDM_SUCCESS) { + printf("Heartbeat ACK received\n"); + } + else { + fprintf(stderr, "Heartbeat: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + return rc; +} + +#endif + +#ifdef WOLFSPDM_HAS_KEY_UPDATE +static int do_key_update(WOLFSPDM_CTX* ctx) +{ + int rc = do_session(ctx); + if (rc != WOLFSPDM_SUCCESS) return rc; + rc = wolfSPDM_KeyUpdate(ctx, 1); /* rotate both directions */ + if (rc == WOLFSPDM_SUCCESS) { + printf("Key update succeeded\n"); + } + else { + fprintf(stderr, "KeyUpdate: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + return rc; +} + +#endif + +int main(int argc, char* argv[]) +{ + static const struct option longOpts[] = { + { "emu", no_argument, 0, 'e' }, + { "meas", no_argument, 0, 'm' }, + { "no-sig", no_argument, 0, 'n' }, + { "challenge", no_argument, 0, 'c' }, + { "heartbeat", no_argument, 0, 'b' }, + { "key-update", no_argument, 0, 'k' }, + { "ver", required_argument, 0, 'v' }, + { "kex", required_argument, 0, 'K' }, + { "debug", no_argument, 0, 'd' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + int mode = 0; + int withSig = 1; + int debug = 0; + byte maxVer = 0; + int kexEcdheOnly = 0; + word16 kexKemOnly = 0; + int opt; + int rc; + WOLFSPDM_CTX* ctx = (WOLFSPDM_CTX*)g_ctxBuf; + + while ((opt = getopt_long(argc, argv, "emncbkv:hd", longOpts, NULL)) != -1) { + switch (opt) { + case 'e': mode = MODE_SESSION; break; + case 'm': mode = MODE_MEAS; break; + case 'n': withSig = 0; break; + case 'c': mode = MODE_CHALLENGE; break; + case 'b': mode = MODE_HEARTBEAT; break; + case 'k': mode = MODE_KEY_UPDATE; break; + case 'd': debug = 1; break; + case 'v': + maxVer = parse_version(optarg); + if (maxVer == 0) { + fprintf(stderr, "Invalid --ver %s (expected 1.2/1.3/1.4)\n", + optarg); + return 1; + } + break; + case 'K': +#ifdef WOLFSPDM_HAVE_MLKEM + if (strcmp(optarg, "ecdhe") == 0) { + kexEcdheOnly = 1; + } + else if (strcmp(optarg, "mlkem512") == 0) { + kexKemOnly = SPDM_KEM_ALGO_ML_KEM_512; + } + else if (strcmp(optarg, "mlkem768") == 0) { + kexKemOnly = SPDM_KEM_ALGO_ML_KEM_768; + } + else if (strcmp(optarg, "mlkem1024") == 0) { + kexKemOnly = SPDM_KEM_ALGO_ML_KEM_1024; + } + else { + fprintf(stderr, "Invalid --kex %s (expected ecdhe/" + "mlkem512/mlkem768/mlkem1024)\n", optarg); + return 1; + } + break; +#else + fprintf(stderr, "--kex needs ML-KEM support in wolfSPDM\n"); + return 77; +#endif + case 'h': usage(argv[0]); return 0; + default: usage(argv[0]); return 1; + } + } + if (mode == 0) { usage(argv[0]); return 1; } + + if (wolfSPDM_GetCtxSize() > CTX_BUF_SIZE) { + fprintf(stderr, "ERROR: CTX_BUF_SIZE too small (%d needed)\n", + wolfSPDM_GetCtxSize()); + return 1; + } + + if (tcp_connect(EMU_HOST, EMU_PORT) < 0) { + fprintf(stderr, "ERROR: cannot connect to %s:%d (is spdm-emu running?)\n", + EMU_HOST, EMU_PORT); + return 1; + } + + rc = wolfSPDM_InitStatic(ctx, CTX_BUF_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + fprintf(stderr, "wolfSPDM_InitStatic: %s\n", + wolfSPDM_GetErrorString(rc)); + tcp_disconnect(); + return 1; + } + + wolfSPDM_SetIO(ctx, tcp_io_callback, &g_tcpCtx); + + if (debug) { + wolfSPDM_SetDebug(ctx, 1); + } + + /* Demo runs against the DMTF spdm-emu, which uses self-signed test + * certs. Explicitly opt in to operating without a trust anchor so the + * default fail-closed behavior doesn't refuse the handshake. Real + * deployments should call wolfSPDM_SetTrustedCAs instead. */ + wolfSPDM_AllowUntrustedCerts(ctx, 1); + + if (maxVer != 0) { + rc = wolfSPDM_SetMaxVersion(ctx, maxVer); + if (rc != WOLFSPDM_SUCCESS) { + fprintf(stderr, "wolfSPDM_SetMaxVersion: %s\n", + wolfSPDM_GetErrorString(rc)); + goto done; + } + } + +#ifdef WOLFSPDM_HAVE_MLKEM + if (kexEcdheOnly || kexKemOnly != 0) { + rc = wolfSPDM_SetKeyExchangePref(ctx, kexEcdheOnly ? 1 : 0, kexKemOnly); + if (rc != WOLFSPDM_SUCCESS) { + fprintf(stderr, "wolfSPDM_SetKeyExchangePref: %s\n", + wolfSPDM_GetErrorString(rc)); + goto done; + } + } +#else + (void)kexEcdheOnly; + (void)kexKemOnly; +#endif +#ifndef WOLFSPDM_HAS_MEASUREMENTS + (void)withSig; +#endif + + switch (mode) { + case MODE_SESSION: rc = do_session(ctx); break; +#ifdef WOLFSPDM_HAS_MEASUREMENTS + case MODE_MEAS: rc = do_meas(ctx, withSig); break; +#endif +#ifdef WOLFSPDM_HAS_CHALLENGE + case MODE_CHALLENGE: rc = do_challenge(ctx); break; +#endif +#ifdef WOLFSPDM_HAS_HEARTBEAT + case MODE_HEARTBEAT: rc = do_heartbeat(ctx); break; +#endif +#ifdef WOLFSPDM_HAS_KEY_UPDATE + case MODE_KEY_UPDATE: rc = do_key_update(ctx); break; +#endif + default: + fprintf(stderr, "Scenario not built into this wolfSPDM\n"); + rc = WOLFSPDM_E_NOT_AVAILABLE; + break; + } + + if (rc == WOLFSPDM_SUCCESS && wolfSPDM_IsConnected(ctx)) { + wolfSPDM_Disconnect(ctx); + } + +done: + wolfSPDM_Free(ctx); + tcp_disconnect(); + /* 77 = scenario skipped (automake convention) */ + if (rc == WOLFSPDM_E_NOT_AVAILABLE) { + return 77; + } + return (rc == WOLFSPDM_SUCCESS) ? 0 : 1; +} + +#else /* !HAS_SOCKET */ +int main(void) +{ + fprintf(stderr, "spdm_demo: socket support unavailable on this platform\n"); + return 1; +} +#endif diff --git a/examples/spdm_test.sh b/examples/spdm_test.sh new file mode 100755 index 0000000..5291d87 --- /dev/null +++ b/examples/spdm_test.sh @@ -0,0 +1,283 @@ +#!/bin/bash +# +# spdm_test.sh - SPDM emulator test script +# +# Tests SPDM protocol with libspdm emulator (session + measurements + challenge +# + heartbeat + key update) across SPDM versions 1.2, 1.3, and 1.4. +# +# Usage: +# ./spdm_test.sh # Run emulator tests +# ./spdm_test.sh [path-to-spdm_demo] # Custom spdm_demo path +# + +SPDM_DEMO="./examples/spdm_demo" +PASS=0 +FAIL=0 +SKIP=0 +TOTAL=0 +EMU_PID="" +EMU_LOG="/tmp/spdm_emu_$$.log" + +# Colors (if terminal supports it) +if [ -t 1 ]; then + GREEN='\033[0;32m' + RED='\033[0;31m' + YELLOW='\033[0;33m' + NC='\033[0m' +else + GREEN='' + RED='' + YELLOW='' + NC='' +fi + +usage() { + echo "Usage: $0 [path-to-spdm_demo]" + echo "" + echo "Runs SPDM emulator tests (session, measurements, challenge," + echo "heartbeat, key update) across SPDM versions 1.2, 1.3, and 1.4." + echo "" + echo "Expects spdm_responder_emu to be found via:" + echo " 1. SPDM_EMU_PATH environment variable" + echo " 2. ../spdm-emu/build/bin/ (cloned next to wolfSPDM)" + echo " 3. spdm_responder_emu in PATH" +} + +# Parse arguments +for arg in "$@"; do + case "$arg" in + -h|--help) + usage + exit 0 + ;; + *) + # Treat as path to spdm_demo + SPDM_DEMO="$arg" + ;; + esac +done + +# Find spdm_responder_emu +find_emu() { + # 1. Check SPDM_EMU_PATH + if [ -n "$SPDM_EMU_PATH" ]; then + if [ -x "$SPDM_EMU_PATH/spdm_responder_emu" ]; then + EMU_DIR="$SPDM_EMU_PATH" + EMU_BIN="$SPDM_EMU_PATH/spdm_responder_emu" + return 0 + elif [ -x "$SPDM_EMU_PATH" ]; then + EMU_DIR="$(dirname "$SPDM_EMU_PATH")" + EMU_BIN="$SPDM_EMU_PATH" + return 0 + fi + fi + + # 2. Check common relative paths + for dir in \ + "../spdm-emu/build/bin" \ + "../../spdm-emu/build/bin" \ + "$HOME/spdm-emu/build/bin"; do + if [ -x "$dir/spdm_responder_emu" ]; then + EMU_DIR="$dir" + EMU_BIN="$dir/spdm_responder_emu" + return 0 + fi + done + + # 3. Check PATH + if command -v spdm_responder_emu >/dev/null 2>&1; then + EMU_BIN="$(command -v spdm_responder_emu)" + EMU_DIR="$(dirname "$EMU_BIN")" + return 0 + fi + + return 1 +} + +# Start the emulator (must run from its bin dir for cert files) +# Usage: start_emu [version] +start_emu() { + local ver="${1:-1.2}" + echo " Starting spdm_responder_emu (SPDM $ver)..." + + # Reap any emulator we started ourselves earlier (do NOT kill unrelated + # spdm_responder_emu instances - a developer may have one in another + # shell). Only the previous $EMU_PID is fair game. + if [ -n "$EMU_PID" ] && kill -0 "$EMU_PID" 2>/dev/null; then + kill -9 "$EMU_PID" 2>/dev/null + wait "$EMU_PID" 2>/dev/null + EMU_PID="" + sleep 1 + fi + + # If port 2323 is still occupied, it isn't ours - surface that clearly + # rather than kicking the unrelated holder off the port. Try ss, then + # netstat, then lsof - skip the check (with a warning) if none exist. + if command -v ss >/dev/null 2>&1; then + if ss -tlnp 2>/dev/null | grep -q ":2323 "; then + echo -e " ${RED}ERROR: Port 2323 already in use by another process${NC}" + ss -tlnp 2>/dev/null | grep ":2323 " + return 1 + fi + elif command -v netstat >/dev/null 2>&1; then + if netstat -tlnp 2>/dev/null | grep -q ":2323 "; then + echo -e " ${RED}ERROR: Port 2323 already in use by another process${NC}" + netstat -tlnp 2>/dev/null | grep ":2323 " + return 1 + fi + elif command -v lsof >/dev/null 2>&1; then + if lsof -iTCP:2323 -sTCP:LISTEN >/dev/null 2>&1; then + echo -e " ${RED}ERROR: Port 2323 already in use by another process${NC}" + lsof -iTCP:2323 -sTCP:LISTEN + return 1 + fi + else + echo -e " ${YELLOW}WARNING: ss/netstat/lsof unavailable - skipping port-in-use check${NC}" + fi + + # Verify cert/key files exist in EMU_DIR (spdm-emu uses lowercase 'ecp384') + if [ ! -d "$EMU_DIR/ecp384" ] && [ ! -d "$EMU_DIR/EcP384" ]; then + echo -e " ${YELLOW}WARNING: Certificate files may be missing in $EMU_DIR${NC}" + echo " Run 'make copy_sample_key' in the spdm-emu build directory" + fi + + (cd "$EMU_DIR" && ./spdm_responder_emu --ver "$ver" \ + --hash SHA_384 --asym ECDSA_P384 \ + --dhe SECP_384_R1 --aead AES_256_GCM >"$EMU_LOG" 2>&1) & + EMU_PID=$! + sleep 2 + + # Verify it started + if ! kill -0 "$EMU_PID" 2>/dev/null; then + echo -e " ${RED}ERROR: Emulator failed to start${NC}" + if [ -s "$EMU_LOG" ]; then + echo " Emulator output:" + sed 's/^/ /' "$EMU_LOG" | head -20 + fi + EMU_PID="" + return 1 + fi + return 0 +} + +# Stop the emulator +stop_emu() { + if [ -n "$EMU_PID" ]; then + kill "$EMU_PID" 2>/dev/null + wait "$EMU_PID" 2>/dev/null + EMU_PID="" + fi +} + +# Cleanup on exit +cleanup() { + stop_emu + rm -f "$EMU_LOG" +} +trap cleanup EXIT + +# Run a test (start/stop emulator around each test) +# Usage: run_test +run_test() { + local name="$1" + local emu_ver="$2" + shift 2 + + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + + if ! start_emu "$emu_ver"; then + echo -e " ${RED}FAIL (emulator start)${NC}" + FAIL=$((FAIL + 1)) + echo "" + return 1 + fi + + "$@" + local rc=$? + if [ $rc -eq 0 ]; then + echo -e " ${GREEN}PASS${NC}" + PASS=$((PASS + 1)) + elif [ $rc -eq 77 ]; then + echo -e " ${YELLOW}SKIP (not built)${NC}" + SKIP=$((SKIP + 1)) + else + echo -e " ${RED}FAIL${NC}" + FAIL=$((FAIL + 1)) + fi + + stop_emu + sleep 1 # Let port release + echo "" +} + +# Check spdm_demo exists +if [ ! -x "$SPDM_DEMO" ]; then + echo "Error: $SPDM_DEMO not found or not executable" + usage + exit 1 +fi + +# ========================================================================== +# Emulator Tests +# ========================================================================== +echo "=== SPDM Emulator Tests ===" + +if ! find_emu; then + echo -e "${RED}ERROR: spdm_responder_emu not found${NC}" + echo "" + echo "Set SPDM_EMU_PATH or clone spdm-emu next to wolfSPDM:" + echo " git clone https://github.com/DMTF/spdm-emu.git ../spdm-emu" + echo " cd ../spdm-emu && mkdir build && cd build" + echo " cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=mbedtls .." + echo " make copy_sample_key && make" + exit 1 +fi + +echo "Using emulator: $EMU_BIN" +echo "Using demo: $SPDM_DEMO" +echo "" + +# Test each SPDM version (1.2, 1.3, 1.4) against the emulator +for VER in 1.2 1.3 1.4; do + echo "--- SPDM $VER ---" + + # Session establishment + run_test "Session (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --emu --ver "$VER" + + # Session + signed measurements + run_test "Signed measurements (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --meas --ver "$VER" + + # Session + unsigned measurements + run_test "Unsigned measurements (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --meas --no-sig --ver "$VER" + + # Challenge authentication (sessionless) + run_test "Challenge (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --challenge --ver "$VER" + + # Session + heartbeat + run_test "Heartbeat (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --emu --heartbeat --ver "$VER" + + # Session + key update + run_test "Key update (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --emu --key-update --ver "$VER" + + echo "" +done + +# ========================================================================== +# Summary +# ========================================================================== +echo "=== Results ===" +echo "Total: $TOTAL Passed: $PASS Skipped: $SKIP Failed: $FAIL" +if [ $FAIL -eq 0 ]; then + echo -e "${GREEN}ALL TESTS PASSED${NC}" + exit 0 +else + echo -e "${RED}$FAIL TEST(S) FAILED${NC}" + exit 1 +fi diff --git a/src/spdm_context.c b/src/spdm_context.c index 30670c7..6ef7b9b 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -159,6 +159,9 @@ int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, XMEMCPY(ctx->rspPubKey, pubKey, pubKeySz); ctx->rspPubKeyLen = pubKeySz; ctx->flags.hasRspPubKey = 1; +#ifndef WOLFSPDM_NO_CERT + ctx->flags.rspKeyFromCert = 0; +#endif return WOLFSPDM_SUCCESS; } @@ -202,6 +205,24 @@ int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, /* wolfSPDM_SetPSK moved to spdm_psk.c */ +int wolfSPDM_SetMaxVersion(WOLFSPDM_CTX* ctx, byte maxVersion) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (maxVersion != 0 && (maxVersion < WOLFSPDM_MIN_SPDM_VERSION || + maxVersion > WOLFSPDM_MAX_SPDM_VERSION)) { + return WOLFSPDM_E_INVALID_ARG; + } + ctx->maxVersion = maxVersion; + return WOLFSPDM_SUCCESS; +} + +byte wolfSPDM_GetLastPeerError(WOLFSPDM_CTX* ctx) +{ + return (ctx == NULL) ? 0 : ctx->lastPeerErrorCode; +} + void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable) { if (ctx != NULL) { @@ -269,9 +290,11 @@ int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx) return (ctx->state == WOLFSPDM_STATE_CONNECTED) ? 1 : 0; } +/* Valid from KEY_EXCHANGE_RSP on: transports need it to frame FINISH */ word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx) { - if (ctx == NULL || ctx->state != WOLFSPDM_STATE_CONNECTED) { + if (ctx == NULL || ctx->state < WOLFSPDM_STATE_KEY_EX || + ctx->state == WOLFSPDM_STATE_ERROR) { return 0; } return ctx->sessionId; @@ -330,8 +353,13 @@ int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) return wolfSPDM_ConnectPsk(ctx); } #endif +#ifndef WOLFSPDM_NO_CERT + if (ctx->mode == WOLFSPDM_MODE_AUTO) { + return wolfSPDM_ConnectStandard(ctx); + } +#endif - return WOLFSPDM_E_INVALID_ARG; /* Standard mode not available */ + return WOLFSPDM_E_INVALID_ARG; } int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) @@ -576,6 +604,10 @@ const char* wolfSPDM_GetErrorString(int error) case WOLFSPDM_E_NOT_AVAILABLE: return "Feature not compiled in"; case WOLFSPDM_E_FRAMING: return "Framing violation"; case WOLFSPDM_E_NOT_IMPL: return "Not implemented"; + case WOLFSPDM_E_CERT_FAIL: return "Certificate check failed"; + case WOLFSPDM_E_CAPS_MISMATCH: return "Capability mismatch"; + case WOLFSPDM_E_ALGO_MISMATCH: return "Algorithm mismatch"; + case WOLFSPDM_E_CERT_PARSE: return "Certificate parse failed"; default: return "Unknown error"; } } diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 46ca2d4..a34b0f5 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -126,6 +126,7 @@ struct WOLFSPDM_CTX { /* Negotiated parameters */ byte maxVersion; /* Runtime max version cap (0 = use compile-time default) */ byte spdmVersion; /* Negotiated SPDM version */ + byte lastPeerErrorCode; /* Last SPDM ERROR Param1 (0 = none) */ /* Ephemeral ECDHE key (generated for KEY_EXCHANGE) */ ecc_key ephemeralKey; @@ -178,6 +179,19 @@ struct WOLFSPDM_CTX { word32 reqPrivKeyLen; byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; +#ifndef WOLFSPDM_NO_CERT + /* Standard requester: negotiated limits and responder cert chain */ + word32 rspCaps; + word32 dataTransferSize; + word32 maxSpdmMsgSize; + word32 certChainLen; + word32 trustedCASz; + byte slotMask; + byte currentSlotId; + byte certChain[WOLFSPDM_MAX_CERT_CHAIN]; + byte trustedCA[WOLFSPDM_MAX_TRUSTED_CA]; +#endif + /* Boolean flag bit field (at end for better struct packing) */ struct { unsigned int debug : 1; @@ -187,6 +201,10 @@ struct WOLFSPDM_CTX { unsigned int ephemeralKeyInit : 1; unsigned int hasRspPubKey : 1; unsigned int hasReqKeyPair : 1; +#ifndef WOLFSPDM_NO_CERT + unsigned int allowUntrustedCert : 1; + unsigned int rspKeyFromCert : 1; +#endif } flags; }; @@ -297,6 +315,7 @@ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, if ((buf)[1] != (expected)) { \ int _ec; \ if (wolfSPDM_CheckError((buf), (bufSz), &_ec)) { \ + (ctx)->lastPeerErrorCode = (byte)_ec; \ wolfSPDM_DebugPrint((ctx), "SPDM error: 0x%02x\n", _ec); \ return WOLFSPDM_E_PEER_ERROR; \ } \ @@ -373,6 +392,31 @@ WOLFSPDM_API int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, /* ----- Internal Utility Functions ----- */ +typedef int (*wolfSPDM_BuildFn)(WOLFSPDM_CTX*, byte*, word32*); +typedef int (*wolfSPDM_ParseFn)(WOLFSPDM_CTX*, const byte*, word32); + +/* build -> transcript(tx) -> send/receive -> transcript(rx) -> parse */ +WOLFSPDM_LOCAL int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, + wolfSPDM_BuildFn buildFn, wolfSPDM_ParseFn parseFn, + byte* txBuf, word32 txBufSz, byte* rxBuf, word32 rxBufSz); + +#ifndef WOLFSPDM_NO_CERT +WOLFSPDM_TEST_API int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, + byte* buf, word32* bufSz); +WOLFSPDM_TEST_API int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz); +WOLFSPDM_TEST_API int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, + byte* buf, word32* bufSz); +WOLFSPDM_TEST_API int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz); +WOLFSPDM_TEST_API int wolfSPDM_ParseDigests(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz); +WOLFSPDM_TEST_API int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz, word16* portionLen, word16* remainderLen); +WOLFSPDM_TEST_API int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx); +WOLFSPDM_LOCAL int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx); +#endif + WOLFSPDM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); diff --git a/src/spdm_msg.c b/src/spdm_msg.c index 6609508..d7f3d0d 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -126,8 +126,12 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) buf[offset++] = ctx->spdmVersion; buf[offset++] = SPDM_KEY_EXCHANGE; buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ - /* SlotID: 0xFF = provisioned public key (TCG), else cert slot 0 */ + /* SlotID: 0xFF = provisioned public key (TCG), else the cert slot */ +#ifndef WOLFSPDM_NO_CERT + buf[offset++] = wolfSPDM_IsTcgMode(ctx) ? 0xFF : ctx->currentSlotId; +#else buf[offset++] = wolfSPDM_IsTcgMode(ctx) ? 0xFF : 0x00; +#endif /* ReqSessionID (2 LE) */ buf[offset++] = (byte)(ctx->reqSessionId & 0xFF); @@ -420,6 +424,12 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 140); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_KEY_EXCHANGE_RSP, WOLFSPDM_E_KEY_EXCHANGE); + /* Only the TCG binding carries a requester identity (GIVE_PUB) */ + if (!wolfSPDM_IsTcgMode(ctx) && buf[6] != 0) { + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: mutual auth unsupported\n"); + return WOLFSPDM_E_KEY_EXCHANGE; + } + /* RspSessionID (4-5), MutAuthRequested (6), ReqSlotIDParam (7) are * committed to ctx only after the signature and HMAC verify */ wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: MutAuth=0x%02x ReqSlotID=0x%02x\n", diff --git a/src/spdm_session.c b/src/spdm_session.c index 30e6af1..37f9dfd 100644 --- a/src/spdm_session.c +++ b/src/spdm_session.c @@ -25,12 +25,8 @@ #include "spdm_internal.h" -/* Callback types for build/parse functions */ -typedef int (*wolfSPDM_BuildFn)(WOLFSPDM_CTX*, byte*, word32*); -typedef int (*wolfSPDM_ParseFn)(WOLFSPDM_CTX*, const byte*, word32); - /* Exchange helper: build -> transcript(tx) -> sendrecv -> transcript(rx) -> parse */ -static int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, +int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, wolfSPDM_BuildFn buildFn, wolfSPDM_ParseFn parseFn, byte* txBuf, word32 txBufSz, byte* rxBuf, word32 rxBufSz) { diff --git a/src/spdm_standard.c b/src/spdm_standard.c new file mode 100644 index 0000000..0b1fe98 --- /dev/null +++ b/src/spdm_standard.c @@ -0,0 +1,591 @@ +/* spdm_standard.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "spdm_internal.h" + +#ifndef WOLFSPDM_NO_CERT + +#include + +/* Largest certificate portion requested per GET_CERTIFICATE */ +#define WOLFSPDM_CERT_PORTION_SZ 1024 + +/* ----- VCA: GET_CAPABILITIES / NEGOTIATE_ALGORITHMS ----- */ + +int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 20); + + XMEMSET(buf, 0, 20); + buf[0] = ctx->spdmVersion; + buf[1] = SPDM_GET_CAPABILITIES; + SPDM_Set32LE(&buf[8], WOLFSPDM_REQ_CAPS); + SPDM_Set32LE(&buf[12], WOLFSPDM_MAX_MSG_SIZE); /* DataTransferSize */ + SPDM_Set32LE(&buf[16], WOLFSPDM_MAX_MSG_SIZE); /* MaxSPDMmsgSize */ + *bufSz = 20; + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz) +{ + const word32 required = SPDM_CAP_CERT_CAP | SPDM_CAP_ENCRYPT_CAP | + SPDM_CAP_MAC_CAP | SPDM_CAP_KEY_EX_CAP; + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CAPABILITIES, + WOLFSPDM_E_CAPS_MISMATCH); + + if (bufSz < 20 || buf[0] != ctx->spdmVersion) { + return WOLFSPDM_E_CAPS_MISMATCH; + } + + ctx->rspCaps = SPDM_Get32LE(&buf[8]); + ctx->dataTransferSize = SPDM_Get32LE(&buf[12]); + ctx->maxSpdmMsgSize = SPDM_Get32LE(&buf[16]); + + /* DSP0274: MinDataTransferSize is 42 */ + if ((ctx->rspCaps & required) != required || + ctx->dataTransferSize < 42 || + ctx->maxSpdmMsgSize < ctx->dataTransferSize) { + wolfSPDM_DebugPrint(ctx, "CAPABILITIES rejected: caps=0x%08x " + "dts=%u max=%u\n", ctx->rspCaps, ctx->dataTransferSize, + ctx->maxSpdmMsgSize); + return WOLFSPDM_E_CAPS_MISMATCH; + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, byte* buf, + word32* bufSz) +{ + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 48); + + XMEMSET(buf, 0, 48); + buf[0] = ctx->spdmVersion; + buf[1] = SPDM_NEGOTIATE_ALGORITHMS; + buf[2] = 4; /* AlgStruct count */ + SPDM_Set16LE(&buf[4], 48); /* Length */ + buf[6] = 0x01; /* MeasurementSpecification = DMTF */ + buf[7] = 0x02; /* OtherParams = OpaqueDataFmt1 */ + SPDM_Set32LE(&buf[8], SPDM_ASYM_ALGO_ECDSA_P384); + SPDM_Set32LE(&buf[12], SPDM_HASH_ALGO_SHA_384); + + buf[32] = SPDM_ALG_TYPE_DHE; + buf[33] = 0x20; + SPDM_Set16LE(&buf[34], SPDM_DHE_ALGO_SECP384R1); + buf[36] = SPDM_ALG_TYPE_AEAD; + buf[37] = 0x20; + SPDM_Set16LE(&buf[38], SPDM_AEAD_ALGO_AES_256_GCM); + buf[40] = SPDM_ALG_TYPE_REQ_BASE_ASYM; + buf[41] = 0x20; + SPDM_Set16LE(&buf[42], (word16)SPDM_ASYM_ALGO_ECDSA_P384); + buf[44] = SPDM_ALG_TYPE_KEY_SCHEDULE; + buf[45] = 0x20; + SPDM_Set16LE(&buf[46], SPDM_KEY_SCHEDULE_SPDM); + *bufSz = 48; + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + word32 off; + byte numAlgs; + byte i; + int dheOk = 0; + int aeadOk = 0; + int ksOk = 0; + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_ALGORITHMS, + WOLFSPDM_E_ALGO_MISMATCH); + + if (bufSz < 36 || buf[0] != ctx->spdmVersion || + SPDM_Get16LE(&buf[4]) != bufSz) { + return WOLFSPDM_E_ALGO_MISMATCH; + } + /* MeasurementSpecificationSel (none or DMTF), OtherParamsSel */ + if (buf[6] > 0x01 || buf[7] != 0x02) { + return WOLFSPDM_E_ALGO_MISMATCH; + } + if (SPDM_Get32LE(&buf[12]) != SPDM_ASYM_ALGO_ECDSA_P384 || + SPDM_Get32LE(&buf[16]) != SPDM_HASH_ALGO_SHA_384) { + return WOLFSPDM_E_ALGO_MISMATCH; + } + + /* AlgStructs follow the ExtAsymSel and ExtHashSel tables */ + numAlgs = buf[2]; + off = 36 + (word32)buf[32] * 4 + (word32)buf[33] * 4; + for (i = 0; i < numAlgs; i++) { + word16 algSel; + word32 extLen; + + if (off > bufSz || bufSz - off < 4) { + return WOLFSPDM_E_ALGO_MISMATCH; + } + algSel = SPDM_Get16LE(&buf[off + 2]); + extLen = (word32)(buf[off + 1] & 0x0F) * 4; + switch (buf[off]) { + case SPDM_ALG_TYPE_DHE: + dheOk = (algSel == SPDM_DHE_ALGO_SECP384R1); + break; + case SPDM_ALG_TYPE_AEAD: + aeadOk = (algSel == SPDM_AEAD_ALGO_AES_256_GCM); + break; + case SPDM_ALG_TYPE_KEY_SCHEDULE: + ksOk = (algSel == SPDM_KEY_SCHEDULE_SPDM); + break; + default: + break; + } + off += 4 + extLen; + } + if (!dheOk || !aeadOk || !ksOk) { + wolfSPDM_DebugPrint(ctx, "ALGORITHMS: not Algorithm Set B " + "(dhe=%d aead=%d ks=%d)\n", dheOk, aeadOk, ksOk); + return WOLFSPDM_E_ALGO_MISMATCH; + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_GetCapabilities(WOLFSPDM_CTX* ctx) +{ + byte txBuf[20]; + byte rxBuf[64]; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + return wolfSPDM_ExchangeMsg(ctx, wolfSPDM_BuildGetCapabilities, + wolfSPDM_ParseCapabilities, txBuf, sizeof(txBuf), rxBuf, sizeof(rxBuf)); +} + +int wolfSPDM_NegotiateAlgorithms(WOLFSPDM_CTX* ctx) +{ + byte txBuf[48]; + byte rxBuf[128]; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + return wolfSPDM_ExchangeMsg(ctx, wolfSPDM_BuildNegotiateAlgorithms, + wolfSPDM_ParseAlgorithms, txBuf, sizeof(txBuf), rxBuf, sizeof(rxBuf)); +} + +/* ----- GET_DIGESTS / GET_CERTIFICATE (not part of the TH transcript) ----- */ + +int wolfSPDM_ParseDigests(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_DIGESTS, WOLFSPDM_E_CERT_FAIL); + + /* Param2 is the (provisioned) slot mask in SPDM 1.2 and 1.3 */ + ctx->slotMask = buf[3]; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx) +{ + byte txBuf[4]; + byte rxBuf[512]; + word32 rxSz = sizeof(rxBuf); + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + txBuf[0] = ctx->spdmVersion; + txBuf[1] = SPDM_GET_DIGESTS; + txBuf[2] = 0x00; + txBuf[3] = 0x00; + + rc = wolfSPDM_SendReceive(ctx, txBuf, sizeof(txBuf), rxBuf, &rxSz); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseDigests(ctx, rxBuf, rxSz); + } + return rc; +} + +int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz, word16* portionLen, word16* remainderLen) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + if (portionLen == NULL || remainderLen == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CERTIFICATE, + WOLFSPDM_E_CERT_FAIL); + + /* Param1[3:0] must echo the requested slot */ + if (bufSz < 8 || (buf[2] & 0x0F) != ctx->currentSlotId) { + return WOLFSPDM_E_CERT_FAIL; + } + + *portionLen = SPDM_Get16LE(&buf[4]); + *remainderLen = SPDM_Get16LE(&buf[6]); + if (bufSz - 8 < *portionLen || + *portionLen > WOLFSPDM_MAX_CERT_CHAIN - ctx->certChainLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(ctx->certChain + ctx->certChainLen, buf + 8, *portionLen); + ctx->certChainLen += *portionLen; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_GetCertificate(WOLFSPDM_CTX* ctx, int slotId) +{ + byte txBuf[8]; + byte rxBuf[8 + WOLFSPDM_CERT_PORTION_SZ]; + word32 rxSz; + word16 offset = 0; + word16 portionLen = 0; + word16 remainderLen = 1; + word16 reqLen = WOLFSPDM_CERT_PORTION_SZ; + int rc = WOLFSPDM_SUCCESS; + + if (ctx == NULL || slotId < 0 || slotId > 7) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* The CERTIFICATE response (8-byte header + portion) must fit the + * responder's DataTransferSize */ + if (ctx->dataTransferSize != 0 && + ctx->dataTransferSize < (word32)reqLen + 8) { + reqLen = (word16)(ctx->dataTransferSize - 8); + } + ctx->currentSlotId = (byte)slotId; + ctx->certChainLen = 0; + + while (rc == WOLFSPDM_SUCCESS && remainderLen > 0) { + txBuf[0] = ctx->spdmVersion; + txBuf[1] = SPDM_GET_CERTIFICATE; + txBuf[2] = (byte)slotId; + txBuf[3] = 0x00; + SPDM_Set16LE(&txBuf[4], offset); + SPDM_Set16LE(&txBuf[6], reqLen); + + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SendReceive(ctx, txBuf, sizeof(txBuf), rxBuf, &rxSz); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseCertificate(ctx, rxBuf, rxSz, &portionLen, + &remainderLen); + } + /* Every non-final portion must make progress */ + if (rc == WOLFSPDM_SUCCESS && portionLen == 0 && remainderLen > 0) { + rc = WOLFSPDM_E_CERT_FAIL; + } + offset = (word16)(offset + portionLen); + } + + if (rc == WOLFSPDM_SUCCESS && + (ctx->certChainLen <= WOLFSPDM_CERT_CHAIN_HDR_SZ || + SPDM_Get16LE(ctx->certChain) != ctx->certChainLen)) { + rc = WOLFSPDM_E_CERT_FAIL; + } + + /* Ct = Hash(certificate chain) is part of TH */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_Sha384Hash(ctx->certChainHash, ctx->certChain, + ctx->certChainLen, NULL, 0, NULL, 0); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, + WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_CERT; + } + return rc; +} + +/* ----- Certificate chain verification ----- */ + +/* Returns the full length of the DER SEQUENCE at der, or 0 if malformed */ +static word32 wolfSPDM_DerSeqLen(const byte* der, word32 derSz) +{ + word32 len = 0; + word32 hdr = 2; + + if (derSz < 2 || der[0] != 0x30) { + return 0; + } + if (der[1] < 0x80) { + len = der[1]; + } + else { + word32 n = der[1] & 0x7FU; + word32 i; + + if (n == 0 || n > 3 || derSz < 2 + n) { + return 0; + } + for (i = 0; i < n; i++) { + len = (len << 8) | der[2 + i]; + } + hdr += n; + } + if (len > derSz - hdr) { + return 0; + } + return hdr + len; +} + +/* Load the ECC public key of a certificate into an initialized key */ +static int wolfSPDM_CertPubKey(const byte* der, word32 derSz, ecc_key* key, + int* isCA) +{ + DecodedCert cert; + word32 idx = 0; + int rc; + + wc_InitDecodedCert(&cert, der, derSz, NULL); + rc = wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL); + if (rc == 0 && cert.keyOID != ECDSAk) { + rc = -1; + } + if (rc == 0) { + rc = wc_EccPublicKeyDecode(cert.publicKey, &idx, key, + cert.pubKeySize); + } + if (rc == 0) { + *isCA = cert.isCA; + } + wc_FreeDecodedCert(&cert); + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CERT_PARSE; +} + +/* Verify that the certificate at der is signed by issuer (ECDSA-SHA384) */ +static int wolfSPDM_CertSignedBy(const byte* der, word32 derSz, + ecc_key* issuer) +{ + DecodedCert cert; + byte hash[WOLFSPDM_HASH_SIZE]; + int verified = 0; + int rc; + + wc_InitDecodedCert(&cert, der, derSz, NULL); + rc = wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL); + if (rc == 0 && (cert.signatureOID != CTC_SHA384wECDSA || + cert.sigIndex <= cert.certBegin)) { + rc = -1; + } + if (rc == 0) { + rc = wolfSPDM_Sha384Hash(hash, cert.source + cert.certBegin, + cert.sigIndex - cert.certBegin, NULL, 0, NULL, 0); + } + if (rc == 0) { + rc = wc_ecc_verify_hash(cert.signature, cert.sigLength, hash, + sizeof(hash), &verified, issuer); + } + wc_FreeDecodedCert(&cert); + + return (rc == 0 && verified == 1) ? WOLFSPDM_SUCCESS : + WOLFSPDM_E_CERT_FAIL; +} + +/* Walk the retrieved chain: each certificate must be signed by the one + * before it, the first by the trusted root when one is set. The leaf's + * P-384 key becomes the responder key, or must match a pinned key. */ +int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) +{ + ecc_key key; + byte leaf[WOLFSPDM_ECC_POINT_SIZE]; + word32 xSz = WOLFSPDM_ECC_KEY_SIZE; + word32 ySz = WOLFSPDM_ECC_KEY_SIZE; + word32 pos = WOLFSPDM_CERT_CHAIN_HDR_SZ; + int keyInit = 0; + int isCA = 0; + int anchored = 0; + int rc = WOLFSPDM_SUCCESS; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (ctx->certChainLen <= WOLFSPDM_CERT_CHAIN_HDR_SZ) { + return WOLFSPDM_E_CERT_FAIL; + } + + /* RootHash in the chain header must name the configured root */ + if (ctx->trustedCASz > 0) { + byte hash[WOLFSPDM_HASH_SIZE]; + + rc = wolfSPDM_Sha384Hash(hash, ctx->trustedCA, ctx->trustedCASz, + NULL, 0, NULL, 0); + if (rc == WOLFSPDM_SUCCESS && + XMEMCMP(hash, ctx->certChain + 4, WOLFSPDM_HASH_SIZE) != 0) { + rc = WOLFSPDM_E_CERT_FAIL; + } + if (rc == WOLFSPDM_SUCCESS) { + rc = (wc_ecc_init(&key) == 0) ? WOLFSPDM_SUCCESS : + WOLFSPDM_E_CRYPTO_FAIL; + } + if (rc == WOLFSPDM_SUCCESS) { + keyInit = 1; + rc = wolfSPDM_CertPubKey(ctx->trustedCA, ctx->trustedCASz, &key, + &isCA); + } + anchored = 1; + } + + while (rc == WOLFSPDM_SUCCESS && pos < ctx->certChainLen) { + word32 certSz = wolfSPDM_DerSeqLen(ctx->certChain + pos, + ctx->certChainLen - pos); + if (certSz == 0) { + rc = WOLFSPDM_E_CERT_PARSE; + } + if (rc == WOLFSPDM_SUCCESS && keyInit) { + rc = isCA ? wolfSPDM_CertSignedBy(ctx->certChain + pos, certSz, + &key) : WOLFSPDM_E_CERT_FAIL; + } + if (keyInit) { + wc_ecc_free(&key); + keyInit = 0; + } + if (rc == WOLFSPDM_SUCCESS) { + rc = (wc_ecc_init(&key) == 0) ? WOLFSPDM_SUCCESS : + WOLFSPDM_E_CRYPTO_FAIL; + } + if (rc == WOLFSPDM_SUCCESS) { + keyInit = 1; + rc = wolfSPDM_CertPubKey(ctx->certChain + pos, certSz, &key, + &isCA); + } + pos += certSz; + } + + /* The leaf must carry a P-384 key (Algorithm Set B) */ + if (rc == WOLFSPDM_SUCCESS && + (!keyInit || wc_ecc_get_curve_id(key.idx) != ECC_SECP384R1 || + wc_ecc_export_public_raw(&key, leaf, &xSz, + leaf + WOLFSPDM_ECC_KEY_SIZE, &ySz) != 0 || + xSz != WOLFSPDM_ECC_KEY_SIZE || ySz != WOLFSPDM_ECC_KEY_SIZE)) { + rc = WOLFSPDM_E_CERT_PARSE; + } + if (keyInit) { + wc_ecc_free(&key); + } + + if (rc == WOLFSPDM_SUCCESS && ctx->flags.hasRspPubKey && + !ctx->flags.rspKeyFromCert) { + if (ctx->rspPubKeyLen != WOLFSPDM_ECC_POINT_SIZE || + XMEMCMP(ctx->rspPubKey, leaf, WOLFSPDM_ECC_POINT_SIZE) != 0) { + wolfSPDM_DebugPrint(ctx, "Leaf key does not match pinned key\n"); + rc = WOLFSPDM_E_CERT_FAIL; + } + anchored = 1; + } + else if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->rspPubKey, leaf, WOLFSPDM_ECC_POINT_SIZE); + ctx->rspPubKeyLen = WOLFSPDM_ECC_POINT_SIZE; + ctx->flags.hasRspPubKey = 1; + ctx->flags.rspKeyFromCert = 1; + } + + if (rc == WOLFSPDM_SUCCESS && !anchored && + !ctx->flags.allowUntrustedCert) { + wolfSPDM_DebugPrint(ctx, "No trust anchor: set a root CA, pin the " + "responder key, or allow untrusted certificates\n"); + rc = WOLFSPDM_E_CERT_FAIL; + } + + return rc; +} + +/* ----- Configuration and connect ----- */ + +int wolfSPDM_SetTrustedCAs(WOLFSPDM_CTX* ctx, const byte* derCerts, + word32 derCertsSz) +{ + if (ctx == NULL || derCerts == NULL || derCertsSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + if (derCertsSz > sizeof(ctx->trustedCA)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(ctx->trustedCA, derCerts, derCertsSz); + ctx->trustedCASz = derCertsSz; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_AllowUntrustedCerts(WOLFSPDM_CTX* ctx, int allow) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + ctx->flags.allowUntrustedCert = (allow != 0); + return WOLFSPDM_SUCCESS; +} + +/* GET_VERSION -> CAPS -> ALGO -> DIGESTS -> CERTIFICATE -> KEY_EXCHANGE -> + * FINISH */ +int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx) +{ + int slot = 0; + int rc; + + /* A key taken from a previous responder's chain is not a trust anchor */ + if (ctx->flags.rspKeyFromCert) { + ctx->flags.hasRspPubKey = 0; + ctx->flags.rspKeyFromCert = 0; + ctx->rspPubKeyLen = 0; + } + ctx->state = WOLFSPDM_STATE_INIT; + ctx->lastPeerErrorCode = 0; + wolfSPDM_TranscriptReset(ctx); + + SPDM_CONNECT_STEP(ctx, "GET_VERSION\n", wolfSPDM_GetVersion(ctx)); + SPDM_CONNECT_STEP(ctx, "GET_CAPABILITIES\n", + wolfSPDM_GetCapabilities(ctx)); + SPDM_CONNECT_STEP(ctx, "NEGOTIATE_ALGORITHMS\n", + wolfSPDM_NegotiateAlgorithms(ctx)); + SPDM_CONNECT_STEP(ctx, "GET_DIGESTS\n", wolfSPDM_GetDigests(ctx)); + + /* Lowest populated slot, slot 0 if the responder reports none */ + while (slot < 7 && ctx->slotMask != 0 && + (ctx->slotMask & (1 << slot)) == 0) { + slot++; + } + SPDM_CONNECT_STEP(ctx, "GET_CERTIFICATE\n", + wolfSPDM_GetCertificate(ctx, slot)); + SPDM_CONNECT_STEP(ctx, "Validating certificate chain\n", + wolfSPDM_ValidateCertChain(ctx)); + SPDM_CONNECT_STEP(ctx, "KEY_EXCHANGE\n", wolfSPDM_KeyExchange(ctx)); + SPDM_CONNECT_STEP(ctx, "FINISH\n", wolfSPDM_Finish(ctx)); + + ctx->state = WOLFSPDM_STATE_CONNECTED; + wolfSPDM_DebugPrint(ctx, "SPDM session established, SessionID=0x%08x\n", + ctx->sessionId); + return WOLFSPDM_SUCCESS; +} + +#endif /* !WOLFSPDM_NO_CERT */ diff --git a/test/test_certs.h b/test/test_certs.h new file mode 100644 index 0000000..62b9170 --- /dev/null +++ b/test/test_certs.h @@ -0,0 +1,208 @@ +/* test_certs.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* DMTF libspdm ECP384 sample certificates (libspdm unit_test/sample_key) */ + +#ifndef WOLFSPDM_TEST_CERTS_H +#define WOLFSPDM_TEST_CERTS_H + +static const byte test_ca_cert_der[] = { + 0x30, 0x82, 0x01, 0xd4, 0x30, 0x82, 0x01, 0x5a, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x14, 0x4e, 0x2c, 0xc9, 0x29, 0x56, 0xcc, 0x9f, 0x61, 0xab, + 0xb6, 0xe0, 0x9b, 0x51, 0xdb, 0xe6, 0x6f, 0x91, 0x83, 0x7a, 0xe9, 0x30, + 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, + 0x21, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x16, + 0x44, 0x4d, 0x54, 0x46, 0x20, 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, 0x6d, + 0x20, 0x45, 0x43, 0x50, 0x33, 0x38, 0x34, 0x20, 0x43, 0x41, 0x30, 0x1e, + 0x17, 0x0d, 0x32, 0x36, 0x30, 0x36, 0x32, 0x33, 0x30, 0x32, 0x35, 0x38, + 0x33, 0x39, 0x5a, 0x17, 0x0d, 0x33, 0x36, 0x30, 0x36, 0x32, 0x30, 0x30, + 0x32, 0x35, 0x38, 0x33, 0x39, 0x5a, 0x30, 0x21, 0x31, 0x1f, 0x30, 0x1d, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x16, 0x44, 0x4d, 0x54, 0x46, 0x20, + 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, 0x6d, 0x20, 0x45, 0x43, 0x50, 0x33, + 0x38, 0x34, 0x20, 0x43, 0x41, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, + 0x22, 0x03, 0x62, 0x00, 0x04, 0x08, 0x14, 0xc6, 0x90, 0x33, 0xb3, 0xec, + 0xf1, 0xe3, 0xc9, 0x1c, 0xb1, 0x07, 0xfb, 0xe5, 0x8f, 0xa2, 0x86, 0x23, + 0xc1, 0x09, 0xa9, 0xd2, 0xf6, 0x05, 0xa0, 0x83, 0x87, 0xa4, 0x3d, 0xf0, + 0xc0, 0xfe, 0xba, 0x62, 0x7a, 0x05, 0x14, 0x7f, 0xe4, 0xd6, 0x5c, 0x71, + 0xf1, 0xc5, 0xf2, 0x31, 0xdd, 0xf4, 0xf5, 0x90, 0xa9, 0x84, 0x5b, 0x35, + 0xce, 0x4f, 0xe0, 0x3f, 0xe9, 0xbe, 0xe6, 0xbf, 0x3e, 0x7b, 0xb3, 0x6f, + 0x6a, 0xc9, 0x22, 0x0f, 0x74, 0xac, 0xae, 0xc3, 0x74, 0xe3, 0xff, 0x6c, + 0x1d, 0x86, 0x51, 0xda, 0xf7, 0x1a, 0x71, 0x4b, 0x44, 0x51, 0x9b, 0x99, + 0x36, 0x02, 0x55, 0xa4, 0x9f, 0xa3, 0x53, 0x30, 0x51, 0x30, 0x1d, 0x06, + 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x4d, 0xa0, 0xa6, 0xe8, + 0xbe, 0x96, 0x0b, 0x1b, 0x82, 0x6a, 0x0d, 0x19, 0x69, 0x22, 0xeb, 0x12, + 0x0e, 0x8c, 0x72, 0xdb, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, + 0x18, 0x30, 0x16, 0x80, 0x14, 0x4d, 0xa0, 0xa6, 0xe8, 0xbe, 0x96, 0x0b, + 0x1b, 0x82, 0x6a, 0x0d, 0x19, 0x69, 0x22, 0xeb, 0x12, 0x0e, 0x8c, 0x72, + 0xdb, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, + 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x68, 0x00, 0x30, 0x65, 0x02, + 0x31, 0x00, 0xa3, 0xaa, 0x37, 0x07, 0x8d, 0x10, 0xf9, 0x42, 0x64, 0x22, + 0x6b, 0x58, 0xeb, 0x8b, 0x75, 0x9a, 0x97, 0x29, 0xe3, 0x79, 0x2f, 0x63, + 0x20, 0x67, 0x37, 0xf2, 0xe3, 0xd1, 0x42, 0x10, 0x3d, 0xff, 0xe8, 0xc2, + 0x41, 0x15, 0xa0, 0xb2, 0x23, 0x09, 0x09, 0xfc, 0x45, 0x39, 0x5a, 0x60, + 0x22, 0x49, 0x02, 0x30, 0x4e, 0x06, 0x77, 0xf4, 0x5d, 0xb3, 0x6a, 0x86, + 0xb0, 0x01, 0xa5, 0xf7, 0x8d, 0xb1, 0x1f, 0x9f, 0xfb, 0xd2, 0x7d, 0x4d, + 0x27, 0x2d, 0x83, 0xe5, 0x60, 0xea, 0xe8, 0x59, 0xfe, 0x5b, 0xb2, 0x0e, + 0x43, 0x71, 0x38, 0x4a, 0xed, 0x60, 0x34, 0x40, 0xac, 0x7b, 0xd3, 0x58, + 0x33, 0x04, 0x16, 0x69 +}; + +/* root || intermediate || responder leaf */ +static const byte test_rsp_chain_der[] = { + 0x30, 0x82, 0x01, 0xd4, 0x30, 0x82, 0x01, 0x5a, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x14, 0x4e, 0x2c, 0xc9, 0x29, 0x56, 0xcc, 0x9f, 0x61, 0xab, + 0xb6, 0xe0, 0x9b, 0x51, 0xdb, 0xe6, 0x6f, 0x91, 0x83, 0x7a, 0xe9, 0x30, + 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, + 0x21, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x16, + 0x44, 0x4d, 0x54, 0x46, 0x20, 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, 0x6d, + 0x20, 0x45, 0x43, 0x50, 0x33, 0x38, 0x34, 0x20, 0x43, 0x41, 0x30, 0x1e, + 0x17, 0x0d, 0x32, 0x36, 0x30, 0x36, 0x32, 0x33, 0x30, 0x32, 0x35, 0x38, + 0x33, 0x39, 0x5a, 0x17, 0x0d, 0x33, 0x36, 0x30, 0x36, 0x32, 0x30, 0x30, + 0x32, 0x35, 0x38, 0x33, 0x39, 0x5a, 0x30, 0x21, 0x31, 0x1f, 0x30, 0x1d, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x16, 0x44, 0x4d, 0x54, 0x46, 0x20, + 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, 0x6d, 0x20, 0x45, 0x43, 0x50, 0x33, + 0x38, 0x34, 0x20, 0x43, 0x41, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, + 0x22, 0x03, 0x62, 0x00, 0x04, 0x08, 0x14, 0xc6, 0x90, 0x33, 0xb3, 0xec, + 0xf1, 0xe3, 0xc9, 0x1c, 0xb1, 0x07, 0xfb, 0xe5, 0x8f, 0xa2, 0x86, 0x23, + 0xc1, 0x09, 0xa9, 0xd2, 0xf6, 0x05, 0xa0, 0x83, 0x87, 0xa4, 0x3d, 0xf0, + 0xc0, 0xfe, 0xba, 0x62, 0x7a, 0x05, 0x14, 0x7f, 0xe4, 0xd6, 0x5c, 0x71, + 0xf1, 0xc5, 0xf2, 0x31, 0xdd, 0xf4, 0xf5, 0x90, 0xa9, 0x84, 0x5b, 0x35, + 0xce, 0x4f, 0xe0, 0x3f, 0xe9, 0xbe, 0xe6, 0xbf, 0x3e, 0x7b, 0xb3, 0x6f, + 0x6a, 0xc9, 0x22, 0x0f, 0x74, 0xac, 0xae, 0xc3, 0x74, 0xe3, 0xff, 0x6c, + 0x1d, 0x86, 0x51, 0xda, 0xf7, 0x1a, 0x71, 0x4b, 0x44, 0x51, 0x9b, 0x99, + 0x36, 0x02, 0x55, 0xa4, 0x9f, 0xa3, 0x53, 0x30, 0x51, 0x30, 0x1d, 0x06, + 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x4d, 0xa0, 0xa6, 0xe8, + 0xbe, 0x96, 0x0b, 0x1b, 0x82, 0x6a, 0x0d, 0x19, 0x69, 0x22, 0xeb, 0x12, + 0x0e, 0x8c, 0x72, 0xdb, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, + 0x18, 0x30, 0x16, 0x80, 0x14, 0x4d, 0xa0, 0xa6, 0xe8, 0xbe, 0x96, 0x0b, + 0x1b, 0x82, 0x6a, 0x0d, 0x19, 0x69, 0x22, 0xeb, 0x12, 0x0e, 0x8c, 0x72, + 0xdb, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, + 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x68, 0x00, 0x30, 0x65, 0x02, + 0x31, 0x00, 0xa3, 0xaa, 0x37, 0x07, 0x8d, 0x10, 0xf9, 0x42, 0x64, 0x22, + 0x6b, 0x58, 0xeb, 0x8b, 0x75, 0x9a, 0x97, 0x29, 0xe3, 0x79, 0x2f, 0x63, + 0x20, 0x67, 0x37, 0xf2, 0xe3, 0xd1, 0x42, 0x10, 0x3d, 0xff, 0xe8, 0xc2, + 0x41, 0x15, 0xa0, 0xb2, 0x23, 0x09, 0x09, 0xfc, 0x45, 0x39, 0x5a, 0x60, + 0x22, 0x49, 0x02, 0x30, 0x4e, 0x06, 0x77, 0xf4, 0x5d, 0xb3, 0x6a, 0x86, + 0xb0, 0x01, 0xa5, 0xf7, 0x8d, 0xb1, 0x1f, 0x9f, 0xfb, 0xd2, 0x7d, 0x4d, + 0x27, 0x2d, 0x83, 0xe5, 0x60, 0xea, 0xe8, 0x59, 0xfe, 0x5b, 0xb2, 0x0e, + 0x43, 0x71, 0x38, 0x4a, 0xed, 0x60, 0x34, 0x40, 0xac, 0x7b, 0xd3, 0x58, + 0x33, 0x04, 0x16, 0x69, 0x30, 0x82, 0x01, 0xfc, 0x30, 0x82, 0x01, 0x82, + 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x01, 0x30, 0x0a, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x30, 0x21, 0x31, 0x1f, + 0x30, 0x1d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x16, 0x44, 0x4d, 0x54, + 0x46, 0x20, 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, 0x6d, 0x20, 0x45, 0x43, + 0x50, 0x33, 0x38, 0x34, 0x20, 0x43, 0x41, 0x30, 0x1e, 0x17, 0x0d, 0x32, + 0x36, 0x30, 0x36, 0x32, 0x33, 0x30, 0x32, 0x35, 0x38, 0x33, 0x39, 0x5a, + 0x17, 0x0d, 0x33, 0x36, 0x30, 0x36, 0x32, 0x30, 0x30, 0x32, 0x35, 0x38, + 0x33, 0x39, 0x5a, 0x30, 0x30, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x0c, 0x25, 0x44, 0x4d, 0x54, 0x46, 0x20, 0x6c, 0x69, 0x62, + 0x73, 0x70, 0x64, 0x6d, 0x20, 0x45, 0x43, 0x50, 0x33, 0x38, 0x34, 0x20, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, + 0x20, 0x63, 0x65, 0x72, 0x74, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, + 0x22, 0x03, 0x62, 0x00, 0x04, 0x41, 0x48, 0xeb, 0x59, 0xd9, 0x00, 0x94, + 0x60, 0x16, 0x14, 0x65, 0x06, 0xdf, 0x8e, 0x43, 0x08, 0xd5, 0xaa, 0x72, + 0x79, 0xed, 0xd3, 0x79, 0x0b, 0xfa, 0x81, 0x7c, 0x1b, 0x13, 0x3c, 0xe8, + 0xc7, 0xe5, 0x7c, 0xe0, 0xd4, 0xea, 0x14, 0x29, 0xa0, 0x10, 0xf4, 0x03, + 0x8a, 0x37, 0x47, 0xc6, 0xbf, 0x43, 0x60, 0x4c, 0x1c, 0xf6, 0x67, 0x05, + 0x8e, 0xb4, 0x9f, 0xc6, 0xc2, 0x12, 0x52, 0xac, 0x0a, 0x4f, 0x25, 0x38, + 0x1e, 0xf7, 0x99, 0x49, 0xf6, 0x6c, 0x61, 0xe8, 0xee, 0x1a, 0xc5, 0x4f, + 0xdd, 0x88, 0x6d, 0xf1, 0xb1, 0xdc, 0x59, 0xce, 0x74, 0x88, 0x8d, 0xed, + 0x6f, 0xfa, 0xda, 0x72, 0x3e, 0xa3, 0x7f, 0x30, 0x7d, 0x30, 0x0c, 0x06, + 0x03, 0x55, 0x1d, 0x13, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, + 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0xfe, + 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x9f, + 0x29, 0xc1, 0x22, 0xb9, 0x85, 0x2c, 0xd6, 0x8a, 0xad, 0x63, 0x1f, 0x7f, + 0x20, 0x95, 0xb4, 0x03, 0x6e, 0x5c, 0x09, 0x30, 0x20, 0x06, 0x03, 0x55, + 0x1d, 0x25, 0x01, 0x01, 0xff, 0x04, 0x16, 0x30, 0x14, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, + 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, + 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x4d, 0xa0, 0xa6, 0xe8, 0xbe, 0x96, + 0x0b, 0x1b, 0x82, 0x6a, 0x0d, 0x19, 0x69, 0x22, 0xeb, 0x12, 0x0e, 0x8c, + 0x72, 0xdb, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, + 0x03, 0x03, 0x03, 0x68, 0x00, 0x30, 0x65, 0x02, 0x30, 0x17, 0x63, 0x16, + 0x8b, 0x5a, 0x2f, 0x16, 0x19, 0x44, 0x72, 0x60, 0x6a, 0x51, 0x00, 0x44, + 0xad, 0xbd, 0x8b, 0x8f, 0xc7, 0x7d, 0xee, 0xec, 0x1a, 0x48, 0x3d, 0x33, + 0x43, 0xd7, 0x10, 0x57, 0x51, 0xcd, 0x13, 0xca, 0xcb, 0x5b, 0x4f, 0x39, + 0x1a, 0xc8, 0x02, 0xcb, 0xdd, 0x26, 0x9f, 0x5d, 0x67, 0x02, 0x31, 0x00, + 0xf3, 0xf9, 0x3b, 0x80, 0xf8, 0xae, 0x86, 0xfe, 0xb5, 0x37, 0xe4, 0x04, + 0xe9, 0x72, 0xda, 0xbd, 0x83, 0xfb, 0xb0, 0x32, 0xbb, 0xe3, 0x76, 0x71, + 0x3a, 0x2e, 0xf6, 0x85, 0xbe, 0xdb, 0x59, 0xa1, 0x92, 0xd1, 0x3c, 0xff, + 0xca, 0x7b, 0xee, 0xdf, 0x5e, 0x94, 0x5c, 0x7a, 0x41, 0x37, 0x1a, 0xca, + 0x30, 0x82, 0x02, 0x67, 0x30, 0x82, 0x01, 0xed, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x01, 0x03, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, + 0x3d, 0x04, 0x03, 0x03, 0x30, 0x30, 0x31, 0x2e, 0x30, 0x2c, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x25, 0x44, 0x4d, 0x54, 0x46, 0x20, 0x6c, 0x69, + 0x62, 0x73, 0x70, 0x64, 0x6d, 0x20, 0x45, 0x43, 0x50, 0x33, 0x38, 0x34, + 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, + 0x65, 0x20, 0x63, 0x65, 0x72, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x36, + 0x30, 0x36, 0x32, 0x33, 0x30, 0x32, 0x35, 0x38, 0x33, 0x39, 0x5a, 0x17, + 0x0d, 0x33, 0x36, 0x30, 0x36, 0x32, 0x30, 0x30, 0x32, 0x35, 0x38, 0x33, + 0x39, 0x5a, 0x30, 0x2d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x0c, 0x22, 0x44, 0x4d, 0x54, 0x46, 0x20, 0x6c, 0x69, 0x62, 0x73, + 0x70, 0x64, 0x6d, 0x20, 0x45, 0x43, 0x50, 0x33, 0x38, 0x34, 0x20, 0x72, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x63, 0x65, 0x72, + 0x74, 0x30, 0x76, 0x30, 0x10, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62, 0x00, + 0x04, 0xa7, 0xfc, 0x87, 0x24, 0xbe, 0x89, 0x4b, 0xeb, 0x29, 0x13, 0xd4, + 0x85, 0xc9, 0x68, 0xbc, 0x46, 0x5f, 0x17, 0x60, 0x1f, 0x5a, 0xf3, 0x4e, + 0xb8, 0x94, 0xdc, 0xcc, 0x39, 0x82, 0x27, 0x51, 0x0f, 0x68, 0x83, 0x9a, + 0xe1, 0x45, 0xd1, 0x9f, 0xef, 0x76, 0x95, 0xd3, 0x00, 0xcb, 0x13, 0x77, + 0x31, 0x12, 0x9c, 0x33, 0xd0, 0x36, 0xdc, 0xa2, 0x61, 0x9e, 0xaf, 0xcb, + 0x19, 0x36, 0x1c, 0xc8, 0x55, 0x1f, 0x53, 0xb9, 0x7f, 0x33, 0x60, 0x8b, + 0x56, 0x9d, 0xd6, 0x2b, 0x00, 0x77, 0x36, 0x0c, 0x72, 0xc0, 0x68, 0x81, + 0x82, 0x56, 0x34, 0x41, 0x0e, 0x12, 0x26, 0xee, 0xa3, 0xa4, 0xe4, 0x85, + 0x3d, 0xa3, 0x81, 0xdd, 0x30, 0x81, 0xda, 0x30, 0x0c, 0x06, 0x03, 0x55, + 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0b, 0x06, + 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x05, 0xe0, 0x30, 0x1d, + 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xec, 0xaa, 0x6a, + 0xa1, 0x85, 0x9c, 0xa3, 0xc2, 0x4e, 0xbd, 0x20, 0xd8, 0x96, 0xcf, 0x94, + 0x73, 0xc5, 0x80, 0x51, 0x9c, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x11, + 0x04, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, + 0x01, 0x83, 0x1c, 0x82, 0x12, 0x01, 0xa0, 0x18, 0x0c, 0x16, 0x41, 0x43, + 0x4d, 0x45, 0x3a, 0x57, 0x49, 0x44, 0x47, 0x45, 0x54, 0x3a, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x30, 0x2a, 0x06, 0x03, + 0x55, 0x1d, 0x25, 0x01, 0x01, 0xff, 0x04, 0x20, 0x30, 0x1e, 0x06, 0x08, + 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, + 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, + 0x05, 0x07, 0x03, 0x09, 0x30, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, + 0x01, 0x83, 0x1c, 0x82, 0x12, 0x06, 0x04, 0x10, 0x30, 0x0e, 0x30, 0x0c, + 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1c, 0x82, 0x12, 0x02, + 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, + 0x14, 0x9f, 0x29, 0xc1, 0x22, 0xb9, 0x85, 0x2c, 0xd6, 0x8a, 0xad, 0x63, + 0x1f, 0x7f, 0x20, 0x95, 0xb4, 0x03, 0x6e, 0x5c, 0x09, 0x30, 0x0a, 0x06, + 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x03, 0x03, 0x68, 0x00, + 0x30, 0x65, 0x02, 0x31, 0x00, 0x99, 0xf3, 0xc4, 0x66, 0xec, 0xdb, 0x65, + 0x42, 0x85, 0xd0, 0x4b, 0x4b, 0xa9, 0xfe, 0x88, 0xcb, 0xf2, 0x7a, 0xbd, + 0x4b, 0xee, 0xdc, 0xa9, 0x95, 0xa6, 0x7e, 0x8b, 0xce, 0x36, 0xc0, 0x9f, + 0xc0, 0x03, 0x06, 0xd9, 0x28, 0xd9, 0xe2, 0xd6, 0x30, 0x8e, 0x0b, 0xf9, + 0x87, 0x03, 0x94, 0xad, 0xe1, 0x02, 0x30, 0x47, 0x16, 0xc3, 0x38, 0x22, + 0xfa, 0xb2, 0xe1, 0x68, 0x0e, 0x40, 0x58, 0x18, 0x39, 0x48, 0x3c, 0x92, + 0x35, 0x10, 0xbb, 0xee, 0x51, 0xf7, 0xaa, 0x86, 0x6a, 0xcd, 0xd2, 0x60, + 0x33, 0x66, 0xbe, 0x30, 0x3e, 0xee, 0x2b, 0x55, 0x1f, 0xd6, 0xa8, 0x94, + 0x09, 0x35, 0x44, 0x9b, 0xfe, 0x06, 0x6d +}; + +#endif /* WOLFSPDM_TEST_CERTS_H */ diff --git a/test/unit_test.c b/test/unit_test.c index 98c80f7..6759542 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -28,6 +28,9 @@ #include #endif #include "spdm_internal.h" +#ifndef WOLFSPDM_NO_CERT + #include "test_certs.h" +#endif #include #include #include @@ -467,9 +470,16 @@ static int test_session_state(void) ASSERT_EQ(wolfSPDM_IsConnected(ctx), 0, "Should not be connected"); ASSERT_EQ(wolfSPDM_GetSessionId(ctx), 0, "SessionId should be 0"); + /* The ID is visible from KEY_EXCHANGE on, but not after an error */ + ctx->sessionId = 0xAABBCCDD; + ctx->state = WOLFSPDM_STATE_KEY_EX; + ASSERT_EQ(wolfSPDM_GetSessionId(ctx), (word32)0xAABBCCDD, + "SessionId must be visible before FINISH"); + ctx->state = WOLFSPDM_STATE_ERROR; + ASSERT_EQ(wolfSPDM_GetSessionId(ctx), 0, "SessionId hidden on error"); + /* Simulate connected state */ ctx->state = WOLFSPDM_STATE_CONNECTED; - ctx->sessionId = 0xAABBCCDD; ctx->spdmVersion = SPDM_VERSION_12; ASSERT_EQ(wolfSPDM_IsConnected(ctx), 1, "Should be connected"); ASSERT_EQ(wolfSPDM_GetSessionId(ctx), (word32)0xAABBCCDD, "SessionId wrong"); @@ -2869,6 +2879,231 @@ static int test_responder_psk_roundtrip(void) /* ----- Main ----- */ +#ifndef WOLFSPDM_NO_CERT +/* ----- Standard (certificate) requester ----- */ + +static int test_parse_capabilities(void) +{ + byte rsp[20]; + TEST_CTX_SETUP_V12(); + printf("test_parse_capabilities...\n"); + + XMEMSET(rsp, 0, sizeof(rsp)); + rsp[0] = SPDM_VERSION_12; + rsp[1] = SPDM_CAPABILITIES; + SPDM_Set32LE(&rsp[8], SPDM_CAP_CERT_CAP | SPDM_CAP_ENCRYPT_CAP | + SPDM_CAP_MAC_CAP | SPDM_CAP_KEY_EX_CAP); + SPDM_Set32LE(&rsp[12], 1024); + SPDM_Set32LE(&rsp[16], 4096); + ASSERT_SUCCESS(wolfSPDM_ParseCapabilities(ctx, rsp, sizeof(rsp))); + ASSERT_EQ(ctx->dataTransferSize, (word32)1024, "DataTransferSize"); + + /* Responder without KEY_EX_CAP cannot open a session */ + SPDM_Set32LE(&rsp[8], SPDM_CAP_CERT_CAP | SPDM_CAP_ENCRYPT_CAP | + SPDM_CAP_MAC_CAP); + ASSERT_EQ(wolfSPDM_ParseCapabilities(ctx, rsp, sizeof(rsp)), + WOLFSPDM_E_CAPS_MISMATCH, "missing KEY_EX_CAP must fail"); + + /* DataTransferSize below the DSP0274 minimum of 42 */ + SPDM_Set32LE(&rsp[8], SPDM_CAP_CERT_CAP | SPDM_CAP_ENCRYPT_CAP | + SPDM_CAP_MAC_CAP | SPDM_CAP_KEY_EX_CAP); + SPDM_Set32LE(&rsp[12], 41); + ASSERT_EQ(wolfSPDM_ParseCapabilities(ctx, rsp, sizeof(rsp)), + WOLFSPDM_E_CAPS_MISMATCH, "DataTransferSize < 42 must fail"); + + /* Version must echo the negotiated version */ + SPDM_Set32LE(&rsp[12], 1024); + rsp[0] = SPDM_VERSION_13; + ASSERT_EQ(wolfSPDM_ParseCapabilities(ctx, rsp, sizeof(rsp)), + WOLFSPDM_E_CAPS_MISMATCH, "version mismatch must fail"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_negotiate_algorithms_roundtrip(void) +{ + byte req[48]; + byte rsp[52]; + word32 reqSz = sizeof(req); + TEST_CTX_SETUP_V12(); + printf("test_negotiate_algorithms_roundtrip...\n"); + + ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, req, &reqSz)); + ASSERT_EQ(reqSz, (word32)48, "NEGOTIATE_ALGORITHMS size"); + + /* Algorithm Set B selection with DHE, AEAD, ReqBaseAsym, KeySchedule */ + XMEMSET(rsp, 0, sizeof(rsp)); + rsp[0] = SPDM_VERSION_12; + rsp[1] = SPDM_ALGORITHMS; + rsp[2] = 4; + SPDM_Set16LE(&rsp[4], sizeof(rsp)); + rsp[6] = 0x01; + rsp[7] = 0x02; + SPDM_Set32LE(&rsp[12], SPDM_ASYM_ALGO_ECDSA_P384); + SPDM_Set32LE(&rsp[16], SPDM_HASH_ALGO_SHA_384); + XMEMCPY(&rsp[36], &req[32], 16); + ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, sizeof(rsp))); + + /* Declared Length must equal the received size */ + SPDM_Set16LE(&rsp[4], sizeof(rsp) - 1); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, sizeof(rsp)), + WOLFSPDM_E_ALGO_MISMATCH, "Length mismatch must fail"); + SPDM_Set16LE(&rsp[4], sizeof(rsp)); + + /* A responder that selects a different AEAD is rejected */ + SPDM_Set16LE(&rsp[42], 0x0001); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, sizeof(rsp)), + WOLFSPDM_E_ALGO_MISMATCH, "non Set B AEAD must fail"); + SPDM_Set16LE(&rsp[42], SPDM_AEAD_ALGO_AES_256_GCM); + + /* AlgStruct count past the end of the response */ + rsp[2] = 5; + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, sizeof(rsp)), + WOLFSPDM_E_ALGO_MISMATCH, "truncated AlgStructs must fail"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_parse_certificate(void) +{ + byte rsp[8 + 16]; + word16 portion = 0; + word16 remainder = 0; + TEST_CTX_SETUP_V12(); + printf("test_parse_certificate...\n"); + + XMEMSET(rsp, 0xA5, sizeof(rsp)); + rsp[0] = SPDM_VERSION_12; + rsp[1] = SPDM_CERTIFICATE; + rsp[2] = 0; + rsp[3] = 0; + SPDM_Set16LE(&rsp[4], 16); + SPDM_Set16LE(&rsp[6], 100); + ctx->currentSlotId = 0; + ASSERT_SUCCESS(wolfSPDM_ParseCertificate(ctx, rsp, sizeof(rsp), + &portion, &remainder)); + ASSERT_EQ(portion, 16, "portion length"); + ASSERT_EQ(remainder, 100, "remainder length"); + ASSERT_EQ(ctx->certChainLen, (word32)16, "chain grew by portion"); + + /* Slot echo must match the requested slot */ + ctx->currentSlotId = 1; + ASSERT_EQ(wolfSPDM_ParseCertificate(ctx, rsp, sizeof(rsp), &portion, + &remainder), WOLFSPDM_E_CERT_FAIL, "slot echo mismatch must fail"); + + /* Portion longer than the received data */ + ctx->currentSlotId = 0; + SPDM_Set16LE(&rsp[4], 17); + ASSERT_EQ(wolfSPDM_ParseCertificate(ctx, rsp, sizeof(rsp), &portion, + &remainder), WOLFSPDM_E_BUFFER_SMALL, "truncated portion must fail"); + + /* Chain buffer cannot overflow */ + SPDM_Set16LE(&rsp[4], 16); + ctx->certChainLen = WOLFSPDM_MAX_CERT_CHAIN - 8; + ASSERT_EQ(wolfSPDM_ParseCertificate(ctx, rsp, sizeof(rsp), &portion, + &remainder), WOLFSPDM_E_BUFFER_SMALL, "chain overflow must fail"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_mutual_auth_rejected_in_standard_mode(void) +{ + byte rsp[282]; + TEST_CTX_SETUP_V12(); + printf("test_mutual_auth_rejected_in_standard_mode...\n"); + + XMEMSET(rsp, 0, sizeof(rsp)); + rsp[0] = SPDM_VERSION_12; + rsp[1] = SPDM_KEY_EXCHANGE_RSP; + rsp[6] = 0x01; /* MutAuthRequested */ + ASSERT_EQ(wolfSPDM_ParseKeyExchangeRsp(ctx, rsp, sizeof(rsp)), + WOLFSPDM_E_KEY_EXCHANGE, "standard mode must reject MutAuth"); + ASSERT_EQ(ctx->sessionId, (word32)0, "no session state committed"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +/* SPDM cert chain from the sample: header, RootHash(root), certificates */ +static int test_load_sample_chain(WOLFSPDM_CTX* ctx) +{ + word32 total = WOLFSPDM_CERT_CHAIN_HDR_SZ + + (word32)sizeof(test_rsp_chain_der); + + if (total > WOLFSPDM_MAX_CERT_CHAIN) { + return -1; + } + SPDM_Set16LE(ctx->certChain, (word16)total); + ctx->certChain[2] = 0; + ctx->certChain[3] = 0; + if (wolfSPDM_Sha384Hash(ctx->certChain + 4, test_ca_cert_der, + sizeof(test_ca_cert_der), NULL, 0, NULL, 0) != 0) { + return -1; + } + XMEMCPY(ctx->certChain + WOLFSPDM_CERT_CHAIN_HDR_SZ, test_rsp_chain_der, + sizeof(test_rsp_chain_der)); + ctx->certChainLen = total; + return 0; +} + +static int test_validate_cert_chain(void) +{ + byte leafKey[WOLFSPDM_ECC_POINT_SIZE]; + TEST_CTX_SETUP_V12(); + printf("test_validate_cert_chain...\n"); + + /* No trust anchor: refused */ + ASSERT_SUCCESS(test_load_sample_chain(ctx)); + ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_FAIL, + "chain without a trust anchor must fail"); + + /* Root CA anchor: every signature verifies, leaf key installed */ + ASSERT_SUCCESS(wolfSPDM_SetTrustedCAs(ctx, test_ca_cert_der, + sizeof(test_ca_cert_der))); + ASSERT_SUCCESS(wolfSPDM_ValidateCertChain(ctx)); + ASSERT_EQ(ctx->flags.hasRspPubKey, 1, "leaf key installed"); + ASSERT_EQ(ctx->flags.rspKeyFromCert, 1, "key marked as cert derived"); + XMEMCPY(leafKey, ctx->rspPubKey, sizeof(leafKey)); + + /* A forged leaf signature breaks the chain */ + ctx->certChain[ctx->certChainLen - 2] ^= 0x01; + ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_FAIL, + "forged leaf signature must fail"); + ctx->certChain[ctx->certChainLen - 2] ^= 0x01; + + /* RootHash naming another root is refused */ + ctx->certChain[4] ^= 0x01; + ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_FAIL, + "RootHash mismatch must fail"); + TEST_CTX_FREE(); + + /* A pinned responder key anchors the chain without a root CA */ + wolfSPDM_Init(ctx); + ctx->spdmVersion = SPDM_VERSION_12; + ASSERT_SUCCESS(test_load_sample_chain(ctx)); + ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(ctx, leafKey, + sizeof(leafKey))); + ASSERT_SUCCESS(wolfSPDM_ValidateCertChain(ctx)); + ASSERT_EQ(ctx->flags.rspKeyFromCert, 0, "pinned key stays the anchor"); + ctx->rspPubKey[10] ^= 0x01; + ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_FAIL, + "leaf not matching the pinned key must fail"); + TEST_CTX_FREE(); + + /* Explicit opt-in accepts an unanchored chain */ + wolfSPDM_Init(ctx); + ctx->spdmVersion = SPDM_VERSION_12; + ASSERT_SUCCESS(test_load_sample_chain(ctx)); + ASSERT_SUCCESS(wolfSPDM_AllowUntrustedCerts(ctx, 1)); + ASSERT_SUCCESS(wolfSPDM_ValidateCertChain(ctx)); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSPDM_NO_CERT */ + int main(void) { printf("===========================================\n"); @@ -3001,6 +3236,13 @@ int main(void) test_encrypt_internal_null_args(); test_encrypt_decrypt_roundtrip(); test_decrypt_rejects_wrong_mctp_type(); +#ifndef WOLFSPDM_NO_CERT + test_parse_capabilities(); + test_negotiate_algorithms_roundtrip(); + test_parse_certificate(); + test_mutual_auth_rejected_in_standard_mode(); + test_validate_cert_chain(); +#endif #ifdef WOLFSPDM_TCG test_encrypt_decrypt_roundtrip_tcg(); #endif diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index b066a66..717b4c4 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -110,6 +110,17 @@ WOLFSPDM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, WOLFSPDM_API int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, const byte* privKey, word32 privKeySz, const byte* pubKey, word32 pubKeySz); +/* Cap the negotiated version (0x12-0x14, 0 = build default) */ +WOLFSPDM_API int wolfSPDM_SetMaxVersion(WOLFSPDM_CTX* ctx, byte maxVersion); + +#ifndef WOLFSPDM_NO_CERT +/* Standard (certificate) mode, used by Connect when no vendor mode is set. + * The trust anchor is one root CA (DER), a pinned responder key, or an + * explicit opt-in to untrusted certificates. */ +WOLFSPDM_API int wolfSPDM_SetTrustedCAs(WOLFSPDM_CTX* ctx, + const byte* derCerts, word32 derCertsSz); +WOLFSPDM_API int wolfSPDM_AllowUntrustedCerts(WOLFSPDM_CTX* ctx, int allow); +#endif /* Session establishment */ WOLFSPDM_API int wolfSPDM_Connect(WOLFSPDM_CTX* ctx); @@ -118,6 +129,12 @@ WOLFSPDM_API int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx); /* Individual handshake steps (for fine-grained control) */ WOLFSPDM_API int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx); +#ifndef WOLFSPDM_NO_CERT +WOLFSPDM_API int wolfSPDM_GetCapabilities(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_NegotiateAlgorithms(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_GetCertificate(WOLFSPDM_CTX* ctx, int slotId); +#endif WOLFSPDM_API int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx); WOLFSPDM_API int wolfSPDM_Finish(WOLFSPDM_CTX* ctx); @@ -129,6 +146,8 @@ WOLFSPDM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, /* Session info */ WOLFSPDM_API word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx); WOLFSPDM_API byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx); +/* Param1 of the last SPDM ERROR from the responder, 0 if none */ +WOLFSPDM_API byte wolfSPDM_GetLastPeerError(WOLFSPDM_CTX* ctx); #ifdef WOLFSPDM_TCG WOLFSPDM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); WOLFSPDM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); diff --git a/wolfspdm/spdm_error.h b/wolfspdm/spdm_error.h index 56eb6b5..6c80d9a 100644 --- a/wolfspdm/spdm_error.h +++ b/wolfspdm/spdm_error.h @@ -53,6 +53,10 @@ enum WOLFSPDM_ERROR { * TPM2 sent while SPDM mode active - * bus-snooping defence) */ WOLFSPDM_E_NOT_IMPL = -20, /* Handler not yet implemented */ + WOLFSPDM_E_CERT_FAIL = -21, /* Certificate retrieval or chain check failed */ + WOLFSPDM_E_CAPS_MISMATCH = -22, /* Capability negotiation failed */ + WOLFSPDM_E_ALGO_MISMATCH = -23, /* Algorithm negotiation failed */ + WOLFSPDM_E_CERT_PARSE = -24, /* Certificate could not be parsed */ }; /* Get human-readable error string */ diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 385e4f1..7e4e5fa 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -175,6 +175,66 @@ extern "C" { #define WOLFSPDM_PUBKEY_BUF_SZ 256 /* Public key buffer */ #endif +/* ----- TPM Build Profile ----- */ + +/* Built inside wolfTPM: the TPM only speaks the TCG binding */ +#if defined(WOLFTPM_SPDM) && !defined(WOLFSPDM_PROFILE_TPM) + #define WOLFSPDM_PROFILE_TPM +#endif +#if defined(WOLFSPDM_PROFILE_TPM) && !defined(WOLFSPDM_NO_CERT) + #define WOLFSPDM_NO_CERT +#endif +#if defined(NO_ASN) && !defined(WOLFSPDM_NO_CERT) + #define WOLFSPDM_NO_CERT +#endif + +#ifndef WOLFSPDM_NO_CERT +/* ----- Standard (certificate) Requester, DSP0274 ----- */ + +#define SPDM_GET_DIGESTS 0x81 +#define SPDM_GET_CERTIFICATE 0x82 +#define SPDM_GET_CAPABILITIES 0xE1 +#define SPDM_NEGOTIATE_ALGORITHMS 0xE3 +#define SPDM_DIGESTS 0x01 +#define SPDM_CERTIFICATE 0x02 +#define SPDM_CAPABILITIES 0x61 +#define SPDM_ALGORITHMS 0x63 + +/* CAPABILITIES flags */ +#define SPDM_CAP_CERT_CAP 0x00000002 +#define SPDM_CAP_ENCRYPT_CAP 0x00000040 +#define SPDM_CAP_MAC_CAP 0x00000080 +#define SPDM_CAP_KEY_EX_CAP 0x00000200 + +#ifndef WOLFSPDM_REQ_CAPS +#define WOLFSPDM_REQ_CAPS (SPDM_CAP_ENCRYPT_CAP | SPDM_CAP_MAC_CAP | \ + SPDM_CAP_KEY_EX_CAP) +#endif + +/* Algorithm Set B selections */ +#define SPDM_HASH_ALGO_SHA_384 0x00000002 +#define SPDM_ASYM_ALGO_ECDSA_P384 0x00000080 +#define SPDM_DHE_ALGO_SECP384R1 0x0010 +#define SPDM_AEAD_ALGO_AES_256_GCM 0x0002 +#define SPDM_KEY_SCHEDULE_SPDM 0x0001 + +/* ALGORITHMS AlgStruct AlgType values (DSP0274 Table 16) */ +#define SPDM_ALG_TYPE_DHE 2 +#define SPDM_ALG_TYPE_AEAD 3 +#define SPDM_ALG_TYPE_REQ_BASE_ASYM 4 +#define SPDM_ALG_TYPE_KEY_SCHEDULE 5 + +/* SPDM cert chain header: Length(2) + Reserved(2) + RootHash(48) */ +#define WOLFSPDM_CERT_CHAIN_HDR_SZ (4 + WOLFSPDM_HASH_SIZE) + +#ifndef WOLFSPDM_MAX_CERT_CHAIN +#define WOLFSPDM_MAX_CERT_CHAIN 4096 +#endif +#ifndef WOLFSPDM_MAX_TRUSTED_CA +#define WOLFSPDM_MAX_TRUSTED_CA 2048 +#endif +#endif /* !WOLFSPDM_NO_CERT */ + /* ----- TCG Build Option ----- */ /* Nuvoton or Nations enables TCG SPDM binding; future chips can set directly */ From 29afc3f580ebd193f3d270a751e79346d609fb64 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 13:56:26 -0700 Subject: [PATCH 04/27] Run wolfTPM's own SPDM unit tests in the downstream job --- scripts/wolftpm-overlay.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/wolftpm-overlay.sh b/scripts/wolftpm-overlay.sh index 0ebdcbe..cac8bcc 100755 --- a/scripts/wolftpm-overlay.sh +++ b/scripts/wolftpm-overlay.sh @@ -1,6 +1,7 @@ #!/bin/sh -# Replace a wolfTPM checkout's embedded SPDM sources with this wolfSPDM tree -# so wolfTPM's own SPDM tests run against it. +# Replace a wolfTPM checkout's embedded SPDM sources with this wolfSPDM tree. +# wolfTPM keeps its own src/spdm/unit_test.c, so its regression tests run +# against this code. set -e usage() { @@ -20,7 +21,6 @@ SPDM=$(cd "$(dirname "$0")/.." && pwd) rm -f "$TPM"/src/spdm/spdm_*.c "$TPM"/src/spdm/spdm_internal.h cp "$SPDM"/src/spdm_*.c "$SPDM"/src/vendor/spdm_*.c \ "$SPDM"/src/spdm_internal.h "$TPM"/src/spdm/ -cp "$SPDM"/test/unit_test.c "$TPM"/src/spdm/unit_test.c mkdir -p "$TPM"/wolfspdm cp "$SPDM"/wolfspdm/spdm*.h "$TPM"/wolfspdm/ From 2843d481bc3947411127e4101046815cd52b003b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 14:32:54 -0700 Subject: [PATCH 05/27] Add HEARTBEAT and KEY_UPDATE on the wolfTPM core --- .github/workflows/build-test.yml | 4 + README.md | 1 + config.h.in | 6 + configure.ac | 20 +++ examples/spdm_demo.c | 3 + src/spdm_context.c | 5 + src/spdm_internal.h | 19 +++ src/spdm_kdf.c | 43 +++++ src/spdm_msg.c | 55 +++++++ src/spdm_session.c | 117 ++++++++++++++ test/unit_test.c | 268 +++++++++++++++++++++++++++++++ wolfspdm/spdm.h | 10 ++ wolfspdm/spdm_error.h | 1 + wolfspdm/spdm_types.h | 34 +++- 14 files changed, 585 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 9d17499..35ec35c 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -92,6 +92,10 @@ jobs: configure: '--enable-nations' - variant: tcg-responder configure: '--enable-tcg --enable-responder' + - variant: no-session-ext + configure: '--disable-heartbeat --disable-key-update' + - variant: lean + configure: '--disable-cert --disable-heartbeat --disable-key-update' steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 2cdf0f3..b42b680 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ make check | `--enable-dynamic-mem` | Use heap allocation for `WOLFSPDM_CTX` (default: static) | | `--disable-mldsa` / `--disable-mlkem` | Force off ML-DSA signatures / ML-KEM key exchange (default: auto-follow wolfSSL) | | `--disable-chunking` | Compile out SPDM 1.2 message chunking (default: enabled) | +| `--disable-heartbeat` / `--disable-key-update` | Compile out HEARTBEAT / KEY_UPDATE (default: enabled) | | `--with-wolfssl=PATH` | wolfSSL installation path | ### Memory Modes diff --git a/config.h.in b/config.h.in index 8ef8c89..b927961 100644 --- a/config.h.in +++ b/config.h.in @@ -77,6 +77,12 @@ /* Disable the standard certificate requester */ #undef WOLFSPDM_NO_CERT +/* Disable HEARTBEAT */ +#undef WOLFSPDM_NO_HEARTBEAT + +/* Disable KEY_UPDATE */ +#undef WOLFSPDM_NO_KEY_UPDATE + /* Enable Nuvoton SPDM vendor commands */ #undef WOLFSPDM_NUVOTON diff --git a/configure.ac b/configure.ac index 30a3e79..adcc137 100644 --- a/configure.ac +++ b/configure.ac @@ -91,6 +91,24 @@ if test "x$enable_cert" = "xno"; then fi AM_CONDITIONAL([BUILD_CERT], [test "x$enable_cert" = "xyes"]) +AC_ARG_ENABLE([heartbeat], + [AS_HELP_STRING([--disable-heartbeat], [Disable HEARTBEAT session keep-alive (default: enabled)])], + [enable_heartbeat=$enableval], + [enable_heartbeat=yes]) + +if test "x$enable_heartbeat" = "xno"; then + AC_DEFINE([WOLFSPDM_NO_HEARTBEAT], [1], [Disable HEARTBEAT]) +fi + +AC_ARG_ENABLE([key-update], + [AS_HELP_STRING([--disable-key-update], [Disable KEY_UPDATE session key rotation (default: enabled)])], + [enable_key_update=$enableval], + [enable_key_update=yes]) + +if test "x$enable_key_update" = "xno"; then + AC_DEFINE([WOLFSPDM_NO_KEY_UPDATE], [1], [Disable KEY_UPDATE]) +fi + # TCG SPDM binding (TPM transport). Nuvoton/Nations imply it. AC_ARG_ENABLE([tcg], [AS_HELP_STRING([--enable-tcg], [Enable the TCG SPDM binding (default: disabled)])], @@ -162,6 +180,8 @@ echo " Version: $PACKAGE_VERSION" echo " Debug: $enable_debug" echo " Dynamic mem: $enable_dynamic_mem" echo " Standard: $enable_cert" +echo " Heartbeat: $enable_heartbeat" +echo " Key update: $enable_key_update" echo " TCG: $enable_tcg" echo " PSK: $enable_psk" echo " Nuvoton: $enable_nuvoton" diff --git a/examples/spdm_demo.c b/examples/spdm_demo.c index bc8d1b8..6d5487d 100644 --- a/examples/spdm_demo.c +++ b/examples/spdm_demo.c @@ -14,6 +14,9 @@ * ca.cert.der for --challenge). */ +#ifdef HAVE_CONFIG_H + #include +#endif #include #include #include diff --git a/src/spdm_context.c b/src/spdm_context.c index 6ef7b9b..28fc1fa 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -409,6 +409,10 @@ int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) wc_ForceZero(ctx->rspDataKey, sizeof(ctx->rspDataKey)); wc_ForceZero(ctx->reqDataIv, sizeof(ctx->reqDataIv)); wc_ForceZero(ctx->rspDataIv, sizeof(ctx->rspDataIv)); +#ifndef WOLFSPDM_NO_KEY_UPDATE + wc_ForceZero(ctx->reqAppSecret, sizeof(ctx->reqAppSecret)); + wc_ForceZero(ctx->rspAppSecret, sizeof(ctx->rspAppSecret)); +#endif /* Handshake keys */ wc_ForceZero(ctx->reqHsSecret, sizeof(ctx->reqHsSecret)); wc_ForceZero(ctx->rspHsSecret, sizeof(ctx->rspHsSecret)); @@ -608,6 +612,7 @@ const char* wolfSPDM_GetErrorString(int error) case WOLFSPDM_E_CAPS_MISMATCH: return "Capability mismatch"; case WOLFSPDM_E_ALGO_MISMATCH: return "Algorithm mismatch"; case WOLFSPDM_E_CERT_PARSE: return "Certificate parse failed"; + case WOLFSPDM_E_KEY_UPDATE: return "Key update failed"; default: return "Unknown error"; } } diff --git a/src/spdm_internal.h b/src/spdm_internal.h index a34b0f5..3bc6535 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -156,6 +156,10 @@ struct WOLFSPDM_CTX { byte rspDataKey[WOLFSPDM_AEAD_KEY_SIZE]; /* Incoming decryption key */ byte reqDataIv[WOLFSPDM_AEAD_IV_SIZE]; /* Base IV for outgoing */ byte rspDataIv[WOLFSPDM_AEAD_IV_SIZE]; /* Base IV for incoming */ +#ifndef WOLFSPDM_NO_KEY_UPDATE + byte reqAppSecret[WOLFSPDM_HASH_SIZE]; + byte rspAppSecret[WOLFSPDM_HASH_SIZE]; +#endif /* Sequence numbers for IV generation */ word64 reqSeqNum; /* Outgoing message sequence */ @@ -417,6 +421,21 @@ WOLFSPDM_TEST_API int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx); WOLFSPDM_LOCAL int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx); #endif +#ifndef WOLFSPDM_NO_HEARTBEAT +WOLFSPDM_TEST_API int wolfSPDM_BuildHeartbeat(WOLFSPDM_CTX* ctx, + byte* buf, word32* bufSz); +WOLFSPDM_TEST_API int wolfSPDM_ParseHeartbeatAck(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz); +#endif +#ifndef WOLFSPDM_NO_KEY_UPDATE +WOLFSPDM_TEST_API int wolfSPDM_BuildKeyUpdate(WOLFSPDM_CTX* ctx, + byte* buf, word32* bufSz, byte operation, byte* tag); +WOLFSPDM_TEST_API int wolfSPDM_ParseKeyUpdateAck(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz, byte operation, byte tag); +WOLFSPDM_TEST_API int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, + int updateAll); +#endif + WOLFSPDM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); diff --git a/src/spdm_kdf.c b/src/spdm_kdf.c index 10a5220..4291837 100644 --- a/src/spdm_kdf.c +++ b/src/spdm_kdf.c @@ -266,6 +266,10 @@ int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) ctx->rspDataKey, ctx->rspDataIv); } if (rc == WOLFSPDM_SUCCESS) { + #ifndef WOLFSPDM_NO_KEY_UPDATE + XMEMCPY(ctx->reqAppSecret, reqAppSecret, WOLFSPDM_HASH_SIZE); + XMEMCPY(ctx->rspAppSecret, rspAppSecret, WOLFSPDM_HASH_SIZE); + #endif /* Reset sequence numbers for application phase */ ctx->reqSeqNum = 0; ctx->rspSeqNum = 0; @@ -282,3 +286,42 @@ int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) return rc; } +#ifndef WOLFSPDM_NO_KEY_UPDATE +/* Next-generation secret per DSP0277: "traffic upd" label, no context */ +static int wolfSPDM_UpdateDirection(byte spdmVersion, byte* appSecret, + byte* key, byte* iv) +{ + byte next[WOLFSPDM_HASH_SIZE]; + int rc; + + rc = wolfSPDM_HkdfExpandLabel(spdmVersion, appSecret, WOLFSPDM_HASH_SIZE, + SPDM_LABEL_UPDATE, NULL, 0, next, WOLFSPDM_HASH_SIZE); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveKeyIvPair(spdmVersion, next, key, iv); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(appSecret, next, WOLFSPDM_HASH_SIZE); + } + + wc_ForceZero(next, sizeof(next)); + return rc; +} + +int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, int updateAll) +{ + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wolfSPDM_UpdateDirection(ctx->spdmVersion, ctx->reqAppSecret, + ctx->reqDataKey, ctx->reqDataIv); + if (rc == WOLFSPDM_SUCCESS && updateAll) { + rc = wolfSPDM_UpdateDirection(ctx->spdmVersion, ctx->rspAppSecret, + ctx->rspDataKey, ctx->rspDataIv); + } + + return rc; +} +#endif /* !WOLFSPDM_NO_KEY_UPDATE */ diff --git a/src/spdm_msg.c b/src/spdm_msg.c index d7f3d0d..a8165d4 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -560,3 +560,58 @@ int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) /* PSK message builders/parsers moved to spdm_psk.c */ +#ifndef WOLFSPDM_NO_HEARTBEAT +int wolfSPDM_BuildHeartbeat(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + return wolfSPDM_BuildSimpleMsg(ctx, SPDM_HEARTBEAT, buf, bufSz); +} + +int wolfSPDM_ParseHeartbeatAck(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_HEARTBEAT_ACK, + WOLFSPDM_E_PEER_ERROR); + if (bufSz != 4 || buf[0] != ctx->spdmVersion) { + return WOLFSPDM_E_PEER_ERROR; + } + return WOLFSPDM_SUCCESS; +} +#endif /* !WOLFSPDM_NO_HEARTBEAT */ + +#ifndef WOLFSPDM_NO_KEY_UPDATE +int wolfSPDM_BuildKeyUpdate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + byte operation, byte* tag) +{ + int rc; + + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 4); + if (tag == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wolfSPDM_GetRandom(ctx, tag, 1); + if (rc == WOLFSPDM_SUCCESS) { + buf[0] = ctx->spdmVersion; + buf[1] = SPDM_KEY_UPDATE; + buf[2] = operation; + buf[3] = *tag; + *bufSz = 4; + } + return rc; +} + +int wolfSPDM_ParseKeyUpdateAck(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz, byte operation, byte tag) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_KEY_UPDATE_ACK, + WOLFSPDM_E_KEY_UPDATE); + if (bufSz != 4 || buf[0] != ctx->spdmVersion || buf[2] != operation || + buf[3] != tag) { + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE_ACK mismatch\n"); + return WOLFSPDM_E_KEY_UPDATE; + } + return WOLFSPDM_SUCCESS; +} +#endif /* !WOLFSPDM_NO_KEY_UPDATE */ diff --git a/src/spdm_session.c b/src/spdm_session.c index 37f9dfd..93a0577 100644 --- a/src/spdm_session.c +++ b/src/spdm_session.c @@ -153,3 +153,120 @@ int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) return rc; } +#if !defined(WOLFSPDM_NO_HEARTBEAT) || !defined(WOLFSPDM_NO_KEY_UPDATE) +/* Standard mode checks the responder's CAPABILITIES; TCG profiles fix them */ +static int wolfSPDM_CheckSessionCap(const WOLFSPDM_CTX* ctx, word32 cap) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } +#ifndef WOLFSPDM_NO_CERT + if (!wolfSPDM_IsTcgMode(ctx) && (ctx->rspCaps & cap) == 0) { + return WOLFSPDM_E_CAPS_MISMATCH; + } +#else + (void)cap; +#endif + return WOLFSPDM_SUCCESS; +} +#endif + +#ifndef WOLFSPDM_NO_HEARTBEAT +int wolfSPDM_Heartbeat(WOLFSPDM_CTX* ctx) +{ + byte txBuf[4]; + byte rxBuf[32]; + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + int rc; + + rc = wolfSPDM_CheckSessionCap(ctx, SPDM_CAP_HBEAT_CAP); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildHeartbeat(ctx, txBuf, &txSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseHeartbeatAck(ctx, rxBuf, rxSz); + } + return rc; +} +#endif /* !WOLFSPDM_NO_HEARTBEAT */ + +#ifndef WOLFSPDM_NO_KEY_UPDATE +int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll) +{ + byte txBuf[4]; + byte rxBuf[32]; + byte encBuf[64]; + byte rawBuf[64]; + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + word32 encSz = sizeof(encBuf); + word32 rawSz = sizeof(rawBuf); + byte op; + byte tag = 0; + int rotated = 0; + int rc; + + op = updateAll ? SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS : + SPDM_KEY_UPDATE_OP_UPDATE_KEY; + + rc = wolfSPDM_CheckSessionCap(ctx, SPDM_CAP_KEY_UPD_CAP); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildKeyUpdate(ctx, txBuf, &txSz, op, &tag); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_EncryptInternal(ctx, txBuf, txSz, encBuf, &encSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rawBuf, &rawSz); + } + /* A rejection arrives under the current keys, an UpdateAllKeys ACK under + * the new ones, so keys rotate only once the response proves it */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rawBuf, rawSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS && updateAll) { + rotated = 1; + rc = wolfSPDM_DeriveUpdatedKeys(ctx, 1); + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + rxSz = sizeof(rxBuf); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rawBuf, rawSz, rxBuf, + &rxSz); + } + } + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseKeyUpdateAck(ctx, rxBuf, rxSz, op, tag); + } + if (rc == WOLFSPDM_SUCCESS && updateAll && !rotated) { + rc = WOLFSPDM_E_KEY_UPDATE; + } + if (rc == WOLFSPDM_SUCCESS && !updateAll) { + rc = wolfSPDM_DeriveUpdatedKeys(ctx, 0); + ctx->reqSeqNum = 0; + } + + if (rc == WOLFSPDM_SUCCESS) { + txSz = sizeof(txBuf); + rc = wolfSPDM_BuildKeyUpdate(ctx, txBuf, &txSz, + SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY, &tag); + } + if (rc == WOLFSPDM_SUCCESS) { + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseKeyUpdateAck(ctx, rxBuf, rxSz, + SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY, tag); + } + + return rc; +} +#endif /* !WOLFSPDM_NO_KEY_UPDATE */ diff --git a/test/unit_test.c b/test/unit_test.c index 6759542..7b868a1 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -3104,6 +3104,266 @@ static int test_validate_cert_chain(void) } #endif /* !WOLFSPDM_NO_CERT */ +#ifndef WOLFSPDM_NO_HEARTBEAT +static int test_heartbeat_msgs(void) +{ + byte buf[16]; + byte ack[] = {0x12, SPDM_HEARTBEAT_ACK, 0x00, 0x00}; + byte err[] = {0x12, SPDM_ERROR, SPDM_ERROR_BUSY, 0x00}; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + + printf("test_heartbeat_msgs...\n"); + ASSERT_SUCCESS(wolfSPDM_BuildHeartbeat(ctx, buf, &bufSz)); + ASSERT_EQ(bufSz, 4, "HEARTBEAT should be 4 bytes"); + ASSERT_EQ(buf[1], SPDM_HEARTBEAT, "Code should be 0xE8"); + bufSz = 2; + ASSERT_EQ(wolfSPDM_BuildHeartbeat(ctx, buf, &bufSz), + WOLFSPDM_E_BUFFER_SMALL, "Small buffer should fail"); + + ASSERT_SUCCESS(wolfSPDM_ParseHeartbeatAck(ctx, ack, sizeof(ack))); + ASSERT_EQ(wolfSPDM_ParseHeartbeatAck(ctx, err, sizeof(err)), + WOLFSPDM_E_PEER_ERROR, "ERROR should return PEER_ERROR"); + ASSERT_EQ(wolfSPDM_GetLastPeerError(ctx), SPDM_ERROR_BUSY, + "Peer error code should be recorded"); + ack[0] = 0x13; + ASSERT_EQ(wolfSPDM_ParseHeartbeatAck(ctx, ack, sizeof(ack)), + WOLFSPDM_E_PEER_ERROR, "Version mismatch should fail"); + ASSERT_EQ(wolfSPDM_Heartbeat(ctx), WOLFSPDM_E_NOT_CONNECTED, + "Heartbeat needs a session"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSPDM_NO_HEARTBEAT */ + +#ifndef WOLFSPDM_NO_KEY_UPDATE +static int test_key_update_msgs(void) +{ + byte buf[16]; + byte ack[] = {0x12, SPDM_KEY_UPDATE_ACK, + SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, 0x42}; + word32 bufSz = sizeof(buf); + byte tag = 0; + TEST_CTX_SETUP_V12(); + + printf("test_key_update_msgs...\n"); + ASSERT_SUCCESS(wolfSPDM_BuildKeyUpdate(ctx, buf, &bufSz, + SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, &tag)); + ASSERT_EQ(bufSz, 4, "KEY_UPDATE should be 4 bytes"); + ASSERT_EQ(buf[1], SPDM_KEY_UPDATE, "Code should be 0xE9"); + ASSERT_EQ(buf[2], SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, "Operation"); + ASSERT_EQ(buf[3], tag, "Tag should match returned value"); + bufSz = 2; + ASSERT_EQ(wolfSPDM_BuildKeyUpdate(ctx, buf, &bufSz, + SPDM_KEY_UPDATE_OP_UPDATE_KEY, &tag), WOLFSPDM_E_BUFFER_SMALL, + "Small buffer should fail"); + + ASSERT_SUCCESS(wolfSPDM_ParseKeyUpdateAck(ctx, ack, sizeof(ack), + SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, 0x42)); + ASSERT_EQ(wolfSPDM_ParseKeyUpdateAck(ctx, ack, sizeof(ack), + SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS, 0xFF), WOLFSPDM_E_KEY_UPDATE, + "Mismatched tag should fail"); + ASSERT_EQ(wolfSPDM_ParseKeyUpdateAck(ctx, ack, sizeof(ack), + SPDM_KEY_UPDATE_OP_UPDATE_KEY, 0x42), WOLFSPDM_E_KEY_UPDATE, + "Mismatched operation should fail"); + ASSERT_EQ(wolfSPDM_KeyUpdate(ctx, 1), WOLFSPDM_E_NOT_CONNECTED, + "KeyUpdate needs a session"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_derive_updated_keys(void) +{ + byte origReqKey[WOLFSPDM_AEAD_KEY_SIZE]; + byte origRspKey[WOLFSPDM_AEAD_KEY_SIZE]; + TEST_CTX_SETUP_V12(); + + printf("test_derive_updated_keys...\n"); + XMEMSET(ctx->reqAppSecret, 0x5A, WOLFSPDM_HASH_SIZE); + XMEMSET(ctx->rspAppSecret, 0xA5, WOLFSPDM_HASH_SIZE); + XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataKey, 0x22, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(origReqKey, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(origRspKey, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + + ASSERT_SUCCESS(wolfSPDM_DeriveUpdatedKeys(ctx, 1)); + ASSERT_NE(memcmp(ctx->reqDataKey, origReqKey, WOLFSPDM_AEAD_KEY_SIZE), 0, + "Req key should change"); + ASSERT_NE(memcmp(ctx->rspDataKey, origRspKey, WOLFSPDM_AEAD_KEY_SIZE), 0, + "Rsp key should change"); + + XMEMCPY(origReqKey, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(origRspKey, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + ASSERT_SUCCESS(wolfSPDM_DeriveUpdatedKeys(ctx, 0)); + ASSERT_NE(memcmp(ctx->reqDataKey, origReqKey, WOLFSPDM_AEAD_KEY_SIZE), 0, + "Req key should change"); + ASSERT_EQ(memcmp(ctx->rspDataKey, origRspKey, WOLFSPDM_AEAD_KEY_SIZE), 0, + "Rsp key should not change"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* Loopback responder: a mirrored context that answers secured requests */ +static WOLFSPDM_CTX g_peer; +static int g_peerRejects; + +static void test_swap(byte* a, byte* b, word32 sz) +{ + word32 i; + for (i = 0; i < sz; i++) { + byte t = a[i]; + a[i] = b[i]; + b[i] = t; + } +} + +/* Swap the peer's directions so DeriveUpdatedKeys(peer, 0) rotates the + * requester-to-responder key */ +static void test_peer_swap_dirs(WOLFSPDM_CTX* p) +{ + test_swap(p->reqDataKey, p->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + test_swap(p->reqDataIv, p->rspDataIv, WOLFSPDM_AEAD_IV_SIZE); + test_swap(p->reqAppSecret, p->rspAppSecret, WOLFSPDM_HASH_SIZE); +} + +static int test_peer_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, void* userCtx) +{ + WOLFSPDM_CTX* p = (WOLFSPDM_CTX*)userCtx; + byte req[32]; + byte rsp[4]; + word32 reqSz = sizeof(req); + + (void)ctx; + if (wolfSPDM_DecryptInternal(p, txBuf, txSz, req, &reqSz) != 0 || + reqSz < 4) { + return -1; + } + rsp[0] = req[0]; + rsp[2] = req[2]; + rsp[3] = req[3]; + if (g_peerRejects) { + rsp[1] = SPDM_ERROR; + rsp[2] = SPDM_ERROR_BUSY; + rsp[3] = 0; + } + else if (req[1] == SPDM_KEY_UPDATE) { + rsp[1] = SPDM_KEY_UPDATE_ACK; + if (req[2] == SPDM_KEY_UPDATE_OP_UPDATE_KEY) { + test_peer_swap_dirs(p); + wolfSPDM_DeriveUpdatedKeys(p, 0); + test_peer_swap_dirs(p); + p->rspSeqNum = 0; + } + else if (req[2] == SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS) { + wolfSPDM_DeriveUpdatedKeys(p, 1); + p->reqSeqNum = 0; + p->rspSeqNum = 0; + } + } +#ifndef WOLFSPDM_NO_HEARTBEAT + else if (req[1] == SPDM_HEARTBEAT) { + rsp[1] = SPDM_HEARTBEAT_ACK; + rsp[2] = 0; + rsp[3] = 0; + } +#endif + else { + return -1; + } + return wolfSPDM_EncryptInternal(p, rsp, sizeof(rsp), rxBuf, rxSz); +} + +static void test_session_loopback(WOLFSPDM_CTX* ctx) +{ + WOLFSPDM_CTX* p = &g_peer; + + ctx->spdmVersion = SPDM_VERSION_12; + ctx->sessionId = 0x00020001; + ctx->state = WOLFSPDM_STATE_CONNECTED; + XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataKey, 0x22, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->reqDataIv, 0x33, WOLFSPDM_AEAD_IV_SIZE); + XMEMSET(ctx->rspDataIv, 0x44, WOLFSPDM_AEAD_IV_SIZE); + XMEMSET(ctx->reqAppSecret, 0x55, WOLFSPDM_HASH_SIZE); + XMEMSET(ctx->rspAppSecret, 0x66, WOLFSPDM_HASH_SIZE); +#ifndef WOLFSPDM_NO_CERT + ctx->rspCaps = SPDM_CAP_HBEAT_CAP | SPDM_CAP_KEY_UPD_CAP; +#endif + + wolfSPDM_Init(p); + p->spdmVersion = ctx->spdmVersion; + p->sessionId = ctx->sessionId; + XMEMCPY(p->reqDataKey, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(p->rspDataKey, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(p->reqDataIv, ctx->rspDataIv, WOLFSPDM_AEAD_IV_SIZE); + XMEMCPY(p->rspDataIv, ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE); + XMEMCPY(p->reqAppSecret, ctx->rspAppSecret, WOLFSPDM_HASH_SIZE); + XMEMCPY(p->rspAppSecret, ctx->reqAppSecret, WOLFSPDM_HASH_SIZE); + g_peerRejects = 0; + wolfSPDM_SetIO(ctx, test_peer_io_cb, p); +} + +static int test_key_update_loopback(void) +{ + byte reqKey[WOLFSPDM_AEAD_KEY_SIZE]; + byte rspKey[WOLFSPDM_AEAD_KEY_SIZE]; + word64 reqSeq; + TEST_CTX_SETUP(); + + printf("test_key_update_loopback...\n"); + test_session_loopback(ctx); +#ifndef WOLFSPDM_NO_HEARTBEAT + ASSERT_SUCCESS(wolfSPDM_Heartbeat(ctx)); +#endif + + XMEMCPY(reqKey, ctx->reqDataKey, sizeof(reqKey)); + XMEMCPY(rspKey, ctx->rspDataKey, sizeof(rspKey)); + ASSERT_SUCCESS(wolfSPDM_KeyUpdate(ctx, 0)); + ASSERT_NE(memcmp(reqKey, ctx->reqDataKey, sizeof(reqKey)), 0, + "UpdateKey should rotate the request key"); + ASSERT_EQ(memcmp(rspKey, ctx->rspDataKey, sizeof(rspKey)), 0, + "UpdateKey should keep the response key"); + + XMEMCPY(reqKey, ctx->reqDataKey, sizeof(reqKey)); + ASSERT_SUCCESS(wolfSPDM_KeyUpdate(ctx, 1)); + ASSERT_NE(memcmp(reqKey, ctx->reqDataKey, sizeof(reqKey)), 0, + "UpdateAllKeys should rotate the request key"); + ASSERT_NE(memcmp(rspKey, ctx->rspDataKey, sizeof(rspKey)), 0, + "UpdateAllKeys should rotate the response key"); + + /* A rejected update keeps the keys and never reuses a sequence number */ + g_peerRejects = 1; + XMEMCPY(reqKey, ctx->reqDataKey, sizeof(reqKey)); + XMEMCPY(rspKey, ctx->rspDataKey, sizeof(rspKey)); + reqSeq = ctx->reqSeqNum; + ASSERT_EQ(wolfSPDM_KeyUpdate(ctx, 1), WOLFSPDM_E_PEER_ERROR, + "Rejected UpdateAllKeys should fail"); + ASSERT_EQ(ctx->reqSeqNum, reqSeq + 1, "Sequence must advance"); + ASSERT_EQ(wolfSPDM_KeyUpdate(ctx, 0), WOLFSPDM_E_PEER_ERROR, + "Rejected UpdateKey should fail"); + ASSERT_EQ(memcmp(reqKey, ctx->reqDataKey, sizeof(reqKey)), 0, + "Rejected update should keep the request key"); + ASSERT_EQ(memcmp(rspKey, ctx->rspDataKey, sizeof(rspKey)), 0, + "Rejected update should keep the response key"); + g_peerRejects = 0; + ASSERT_SUCCESS(wolfSPDM_KeyUpdate(ctx, 1)); + +#ifndef WOLFSPDM_NO_CERT + ctx->rspCaps = 0; + ASSERT_EQ(wolfSPDM_KeyUpdate(ctx, 1), WOLFSPDM_E_CAPS_MISMATCH, + "Responder without KEY_UPD_CAP should be refused"); +#endif + + wolfSPDM_Free(&g_peer); + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSPDM_NO_KEY_UPDATE */ + int main(void) { printf("===========================================\n"); @@ -3246,6 +3506,14 @@ int main(void) #ifdef WOLFSPDM_TCG test_encrypt_decrypt_roundtrip_tcg(); #endif +#ifndef WOLFSPDM_NO_HEARTBEAT + test_heartbeat_msgs(); +#endif +#ifndef WOLFSPDM_NO_KEY_UPDATE + test_key_update_msgs(); + test_derive_updated_keys(); + test_key_update_loopback(); +#endif #ifdef WOLFSPDM_RESPONDER test_responder_init_free(); diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index 717b4c4..4074508 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -143,6 +143,16 @@ WOLFSPDM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz); +#ifndef WOLFSPDM_NO_HEARTBEAT +#define WOLFSPDM_HAS_HEARTBEAT +WOLFSPDM_API int wolfSPDM_Heartbeat(WOLFSPDM_CTX* ctx); +#endif +#ifndef WOLFSPDM_NO_KEY_UPDATE +#define WOLFSPDM_HAS_KEY_UPDATE +/* Rotate the requester key, or both directions when updateAll is set */ +WOLFSPDM_API int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll); +#endif + /* Session info */ WOLFSPDM_API word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx); WOLFSPDM_API byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx); diff --git a/wolfspdm/spdm_error.h b/wolfspdm/spdm_error.h index 6c80d9a..9d739db 100644 --- a/wolfspdm/spdm_error.h +++ b/wolfspdm/spdm_error.h @@ -57,6 +57,7 @@ enum WOLFSPDM_ERROR { WOLFSPDM_E_CAPS_MISMATCH = -22, /* Capability negotiation failed */ WOLFSPDM_E_ALGO_MISMATCH = -23, /* Algorithm negotiation failed */ WOLFSPDM_E_CERT_PARSE = -24, /* Certificate could not be parsed */ + WOLFSPDM_E_KEY_UPDATE = -25, /* Key update failed */ }; /* Get human-readable error string */ diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 7e4e5fa..d655354 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -187,6 +187,37 @@ extern "C" { #if defined(NO_ASN) && !defined(WOLFSPDM_NO_CERT) #define WOLFSPDM_NO_CERT #endif +#if defined(WOLFSPDM_PROFILE_TPM) && !defined(WOLFSPDM_NO_HEARTBEAT) + #define WOLFSPDM_NO_HEARTBEAT +#endif +#if defined(WOLFSPDM_PROFILE_TPM) && !defined(WOLFSPDM_NO_KEY_UPDATE) + #define WOLFSPDM_NO_KEY_UPDATE +#endif + +/* ----- Session Keep-Alive and Key Rotation ----- */ + +#define SPDM_CAP_HBEAT_CAP 0x00002000 +#define SPDM_CAP_KEY_UPD_CAP 0x00004000 + +#ifndef WOLFSPDM_NO_HEARTBEAT +#define SPDM_HEARTBEAT 0xE8 +#define SPDM_HEARTBEAT_ACK 0x68 +#define WOLFSPDM_HBEAT_REQ_CAP SPDM_CAP_HBEAT_CAP +#else +#define WOLFSPDM_HBEAT_REQ_CAP 0 +#endif + +#ifndef WOLFSPDM_NO_KEY_UPDATE +#define SPDM_KEY_UPDATE 0xE9 +#define SPDM_KEY_UPDATE_ACK 0x69 +#define SPDM_KEY_UPDATE_OP_UPDATE_KEY 1 +#define SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS 2 +#define SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY 3 +#define SPDM_LABEL_UPDATE "traffic upd" +#define WOLFSPDM_KEY_UPD_REQ_CAP SPDM_CAP_KEY_UPD_CAP +#else +#define WOLFSPDM_KEY_UPD_REQ_CAP 0 +#endif #ifndef WOLFSPDM_NO_CERT /* ----- Standard (certificate) Requester, DSP0274 ----- */ @@ -208,7 +239,8 @@ extern "C" { #ifndef WOLFSPDM_REQ_CAPS #define WOLFSPDM_REQ_CAPS (SPDM_CAP_ENCRYPT_CAP | SPDM_CAP_MAC_CAP | \ - SPDM_CAP_KEY_EX_CAP) + SPDM_CAP_KEY_EX_CAP | WOLFSPDM_HBEAT_REQ_CAP | \ + WOLFSPDM_KEY_UPD_REQ_CAP) #endif /* Algorithm Set B selections */ From 8b36cfa5e45f4bd168388fa584c0727f206c9c95 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 15:00:37 -0700 Subject: [PATCH 06/27] Add GET_MEASUREMENTS and CHALLENGE on the wolfTPM core --- .github/workflows/build-test.yml | 2 + Makefile.am | 2 +- README.md | 1 + config.h.in | 6 + configure.ac | 20 ++ examples/spdm_demo.c | 4 +- src/spdm_attest.c | 448 +++++++++++++++++++++++++++++ src/spdm_context.c | 5 + src/spdm_internal.h | 38 +++ src/spdm_secured.c | 6 + src/spdm_session.c | 6 + src/spdm_standard.c | 24 +- test/test_certs.h | 18 ++ test/unit_test.c | 479 +++++++++++++++++++++++++++++-- wolfspdm/spdm.h | 18 ++ wolfspdm/spdm_error.h | 2 + wolfspdm/spdm_types.h | 33 +++ 17 files changed, 1084 insertions(+), 28 deletions(-) create mode 100644 src/spdm_attest.c diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 35ec35c..2b14c8b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -94,6 +94,8 @@ jobs: configure: '--enable-tcg --enable-responder' - variant: no-session-ext configure: '--disable-heartbeat --disable-key-update' + - variant: no-attest + configure: '--disable-meas --disable-challenge' - variant: lean configure: '--disable-cert --disable-heartbeat --disable-key-update' diff --git a/Makefile.am b/Makefile.am index 41aa36d..d8e5539 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,7 @@ libwolfspdm_la_SOURCES = \ src/spdm_transcript.c if BUILD_CERT -libwolfspdm_la_SOURCES += src/spdm_standard.c +libwolfspdm_la_SOURCES += src/spdm_standard.c src/spdm_attest.c endif if BUILD_TCG libwolfspdm_la_SOURCES += src/spdm_tcg.c diff --git a/README.md b/README.md index b42b680..5aadd29 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ make check | `--enable-dynamic-mem` | Use heap allocation for `WOLFSPDM_CTX` (default: static) | | `--disable-mldsa` / `--disable-mlkem` | Force off ML-DSA signatures / ML-KEM key exchange (default: auto-follow wolfSSL) | | `--disable-chunking` | Compile out SPDM 1.2 message chunking (default: enabled) | +| `--disable-meas` / `--disable-challenge` | Compile out GET_MEASUREMENTS / CHALLENGE (default: enabled) | | `--disable-heartbeat` / `--disable-key-update` | Compile out HEARTBEAT / KEY_UPDATE (default: enabled) | | `--with-wolfssl=PATH` | wolfSSL installation path | diff --git a/config.h.in b/config.h.in index b927961..8c3343b 100644 --- a/config.h.in +++ b/config.h.in @@ -77,12 +77,18 @@ /* Disable the standard certificate requester */ #undef WOLFSPDM_NO_CERT +/* Disable CHALLENGE */ +#undef WOLFSPDM_NO_CHALLENGE + /* Disable HEARTBEAT */ #undef WOLFSPDM_NO_HEARTBEAT /* Disable KEY_UPDATE */ #undef WOLFSPDM_NO_KEY_UPDATE +/* Disable GET_MEASUREMENTS */ +#undef WOLFSPDM_NO_MEAS + /* Enable Nuvoton SPDM vendor commands */ #undef WOLFSPDM_NUVOTON diff --git a/configure.ac b/configure.ac index adcc137..9ba33fe 100644 --- a/configure.ac +++ b/configure.ac @@ -100,6 +100,24 @@ if test "x$enable_heartbeat" = "xno"; then AC_DEFINE([WOLFSPDM_NO_HEARTBEAT], [1], [Disable HEARTBEAT]) fi +AC_ARG_ENABLE([meas], + [AS_HELP_STRING([--disable-meas], [Disable GET_MEASUREMENTS attestation (default: enabled)])], + [enable_meas=$enableval], + [enable_meas=yes]) + +if test "x$enable_meas" = "xno"; then + AC_DEFINE([WOLFSPDM_NO_MEAS], [1], [Disable GET_MEASUREMENTS]) +fi + +AC_ARG_ENABLE([challenge], + [AS_HELP_STRING([--disable-challenge], [Disable CHALLENGE attestation (default: enabled)])], + [enable_challenge=$enableval], + [enable_challenge=yes]) + +if test "x$enable_challenge" = "xno"; then + AC_DEFINE([WOLFSPDM_NO_CHALLENGE], [1], [Disable CHALLENGE]) +fi + AC_ARG_ENABLE([key-update], [AS_HELP_STRING([--disable-key-update], [Disable KEY_UPDATE session key rotation (default: enabled)])], [enable_key_update=$enableval], @@ -180,6 +198,8 @@ echo " Version: $PACKAGE_VERSION" echo " Debug: $enable_debug" echo " Dynamic mem: $enable_dynamic_mem" echo " Standard: $enable_cert" +echo " Meas: $enable_meas" +echo " Challenge: $enable_challenge" echo " Heartbeat: $enable_heartbeat" echo " Key update: $enable_key_update" echo " TCG: $enable_tcg" diff --git a/examples/spdm_demo.c b/examples/spdm_demo.c index 6d5487d..d3b3e19 100644 --- a/examples/spdm_demo.c +++ b/examples/spdm_demo.c @@ -386,15 +386,13 @@ static int do_meas(WOLFSPDM_CTX* ctx, int withSig) wolfSPDM_GetMeasurementCount(ctx)); } else { - /* Unsigned: NOT_VERIFIED is the expected success return */ - if (rc != WOLFSPDM_SUCCESS && rc != WOLFSPDM_E_MEAS_NOT_VERIFIED) { + if (rc != WOLFSPDM_SUCCESS) { fprintf(stderr, "GetMeasurements (unsigned): %s (%d)\n", wolfSPDM_GetErrorString(rc), rc); return rc; } printf("Unsigned measurements received (%d blocks)\n", wolfSPDM_GetMeasurementCount(ctx)); - rc = WOLFSPDM_SUCCESS; } return rc; } diff --git a/src/spdm_attest.c b/src/spdm_attest.c new file mode 100644 index 0000000..56f3efd --- /dev/null +++ b/src/spdm_attest.c @@ -0,0 +1,448 @@ +/* spdm_attest.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "spdm_internal.h" + +#if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) + +#define SPDM_NONCE_SZ 32 +#define SPDM_REQ_CONTEXT_SZ 8 + +/* OpaqueLength, room for OpaqueData, RequesterContext and the signature */ +#define WOLFSPDM_ATTEST_TAIL_SZ (2 + 512 + SPDM_REQ_CONTEXT_SZ + \ + WOLFSPDM_ECC_SIG_SIZE) + +/* 1.3+ requests end with a RequesterContext the response echoes */ +static word32 wolfSPDM_ReqContextSz(const WOLFSPDM_CTX* ctx) +{ + return (ctx->spdmVersion >= SPDM_VERSION_13) ? SPDM_REQ_CONTEXT_SZ : 0; +} + +/* (Re)start a running transcript hash at the VCA */ +static int wolfSPDM_RunStart(const WOLFSPDM_CTX* ctx, wc_Sha384* sha, + byte* state) +{ + int rc; + + if (*state != WOLFSPDM_RUN_NONE) { + wc_Sha384Free(sha); + *state = WOLFSPDM_RUN_NONE; + } + rc = wc_InitSha384(sha); + if (rc == 0) { + *state = WOLFSPDM_RUN_LIVE; + rc = wc_Sha384Update(sha, ctx->transcript, ctx->vcaLen); + } + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +static int wolfSPDM_RunAdd(wc_Sha384* sha, const byte* req, word32 reqSz, + const byte* rsp, word32 rspSz) +{ + int rc; + + rc = wc_Sha384Update(sha, req, reqSz); + if (rc == 0) { + rc = wc_Sha384Update(sha, rsp, rspSz); + } + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +/* Close the run with this exchange (response up to its signature) and verify + * the responder signature over the SPDM signing digest */ +static int wolfSPDM_RunVerify(WOLFSPDM_CTX* ctx, wc_Sha384* sha, byte* state, + const char* label, word32 labelSz, const byte* req, word32 reqSz, + const byte* rsp, word32 sigOff) +{ + byte digest[WOLFSPDM_HASH_SIZE]; + byte signHash[WOLFSPDM_HASH_SIZE]; + int rc; + + rc = wolfSPDM_RunAdd(sha, req, reqSz, rsp, sigOff); + if (rc == WOLFSPDM_SUCCESS && wc_Sha384Final(sha, digest) != 0) { + rc = WOLFSPDM_E_CRYPTO_FAIL; + } + wc_Sha384Free(sha); + *state = WOLFSPDM_RUN_NONE; + + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, label, labelSz, + digest, signHash); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_VerifySignature(ctx, signHash, WOLFSPDM_HASH_SIZE, + rsp + sigOff, WOLFSPDM_ECC_SIG_SIZE); + } + return rc; +} + +/* OpaqueLength, OpaqueData, the RequesterContext echo, then exactly sigSz + * signature bytes; returns 1 when well formed */ +static int wolfSPDM_ParseTail(const WOLFSPDM_CTX* ctx, const byte* req, + word32 reqSz, const byte* buf, word32 bufSz, word32 off, word32 sigSz, + word32* sigOff) +{ + word32 ctxSz = wolfSPDM_ReqContextSz(ctx); + + if (off + 2 > bufSz) { + return 0; + } + off += 2 + (word32)SPDM_Get16LE(&buf[off]); + if (off > bufSz || bufSz - off != ctxSz + sigSz || reqSz < ctxSz || + XMEMCMP(&buf[off], req + reqSz - ctxSz, ctxSz) != 0) { + return 0; + } + *sigOff = off + ctxSz; + return 1; +} + +void wolfSPDM_AttestFree(WOLFSPDM_CTX* ctx) +{ +#ifndef WOLFSPDM_NO_MEAS + if (ctx->l1l2State != WOLFSPDM_RUN_NONE) { + wc_Sha384Free(&ctx->l1l2Hash); + ctx->l1l2State = WOLFSPDM_RUN_NONE; + } +#endif +#ifndef WOLFSPDM_NO_CHALLENGE + if (ctx->m1State != WOLFSPDM_RUN_NONE) { + wc_Sha384Free(&ctx->m1Hash); + ctx->m1State = WOLFSPDM_RUN_NONE; + } +#endif +} + +/* ----- GET_MEASUREMENTS ----- */ + +#ifndef WOLFSPDM_NO_MEAS + +#define WOLFSPDM_MEAS_RSP_SZ (8 + WOLFSPDM_MAX_MEAS_RECORD + \ + SPDM_NONCE_SZ + WOLFSPDM_ATTEST_TAIL_SZ) + +int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, + word32* bufSz, byte operation, int requestSig) +{ + word32 ctxSz; + word32 sz; + int rc = WOLFSPDM_SUCCESS; + + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + ctxSz = wolfSPDM_ReqContextSz(ctx); + sz = 4 + (requestSig ? SPDM_NONCE_SZ + 1 : 0) + ctxSz; + if (*bufSz < sz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + buf[0] = ctx->spdmVersion; + buf[1] = SPDM_GET_MEASUREMENTS; + buf[2] = requestSig ? SPDM_MEAS_REQUEST_SIG_BIT : 0x00; + buf[3] = operation; + if (requestSig) { + rc = wolfSPDM_GetRandom(ctx, &buf[4], SPDM_NONCE_SZ); + buf[4 + SPDM_NONCE_SZ] = ctx->currentSlotId; + } + if (rc == WOLFSPDM_SUCCESS && ctxSz > 0) { + rc = wolfSPDM_GetRandom(ctx, &buf[sz - ctxSz], ctxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + *bufSz = sz; + } + return rc; +} + +int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* req, + word32 reqSz, const byte* buf, word32 bufSz, word32* sigOff) +{ + word32 recEnd; + word32 off = 8; + word32 i; + int signedReq; + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 8); + if (req == NULL || reqSz < 4 || sigOff == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_MEASUREMENTS, + WOLFSPDM_E_MEASUREMENT); + + signedReq = (req[2] & SPDM_MEAS_REQUEST_SIG_BIT) != 0; + recEnd = 8 + ((word32)buf[5] | ((word32)buf[6] << 8) | + ((word32)buf[7] << 16)); + if (buf[0] != ctx->spdmVersion || recEnd > bufSz || + (signedReq && (buf[3] & 0x0F) != ctx->currentSlotId)) { + return WOLFSPDM_E_MEASUREMENT; + } + + /* NumberOfBlocks blocks must exactly fill MeasurementRecordLength */ + for (i = 0; i < buf[4] && off + WOLFSPDM_MEAS_BLOCK_HDR_SZ <= recEnd; + i++) { + off += WOLFSPDM_MEAS_BLOCK_HDR_SZ + SPDM_Get16LE(&buf[off + 2]); + } + if (i != buf[4] || off != recEnd || + !wolfSPDM_ParseTail(ctx, req, reqSz, buf, bufSz, + recEnd + SPDM_NONCE_SZ, + signedReq ? WOLFSPDM_ECC_SIG_SIZE : 0, sigOff)) { + return WOLFSPDM_E_MEASUREMENT; + } + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, byte measOperation, + int requestSignature) +{ + byte req[4 + SPDM_NONCE_SZ + 1 + SPDM_REQ_CONTEXT_SZ]; + byte rsp[WOLFSPDM_MEAS_RSP_SZ]; + word32 reqSz = sizeof(req); + word32 rspSz = sizeof(rsp); + word32 sigOff = 0; + word32 recLen = 0; + word32 cap = SPDM_CAP_MEAS_CAP_SIG; + int rc = WOLFSPDM_SUCCESS; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + if (!requestSignature) { + cap |= SPDM_CAP_MEAS_CAP_NO_SIG; + } + if ((ctx->rspCaps & cap) == 0) { + return WOLFSPDM_E_CAPS_MISMATCH; + } + ctx->measBlockCount = 0; + ctx->measRecordLen = 0; + + /* L1/L2 spans consecutive GET_MEASUREMENTS; any other request resets */ + if (ctx->l1l2State != WOLFSPDM_RUN_OPEN) { + rc = wolfSPDM_RunStart(ctx, &ctx->l1l2Hash, &ctx->l1l2State); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildGetMeasurements(ctx, req, &reqSz, measOperation, + requestSignature); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SecuredExchange(ctx, req, reqSz, rsp, &rspSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseMeasurements(ctx, req, reqSz, rsp, rspSz, &sigOff); + } + if (rc == WOLFSPDM_SUCCESS && requestSignature) { + rc = wolfSPDM_RunVerify(ctx, &ctx->l1l2Hash, &ctx->l1l2State, + "responder-measurements signing", 30, req, reqSz, rsp, sigOff); + } + else if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_RunAdd(&ctx->l1l2Hash, req, reqSz, rsp, rspSz); + if (rc == WOLFSPDM_SUCCESS) { + ctx->l1l2State = WOLFSPDM_RUN_OPEN; + } + } + + if (rc == WOLFSPDM_SUCCESS) { + recLen = (word32)rsp[5] | ((word32)rsp[6] << 8) | + ((word32)rsp[7] << 16); + if (recLen > sizeof(ctx->measRecord)) { + rc = WOLFSPDM_E_BUFFER_SMALL; + } + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->measRecord, &rsp[8], recLen); + ctx->measRecordLen = recLen; + ctx->measBlockCount = rsp[4]; + } + return rc; +} + +int wolfSPDM_GetMeasurementCount(WOLFSPDM_CTX* ctx) +{ + return (ctx == NULL) ? 0 : (int)ctx->measBlockCount; +} + +int wolfSPDM_GetMeasurementBlock(WOLFSPDM_CTX* ctx, int blockIdx, + byte* measIndex, byte* measType, byte* value, word32* valueSz) +{ + const byte* blk; + word32 off = 0; + word32 len; + int i; + + if (ctx == NULL || measIndex == NULL || measType == NULL || + value == NULL || valueSz == NULL || blockIdx < 0 || + blockIdx >= (int)ctx->measBlockCount) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* The stored record was validated block by block when received */ + for (i = 0; i < blockIdx; i++) { + off += WOLFSPDM_MEAS_BLOCK_HDR_SZ + + SPDM_Get16LE(&ctx->measRecord[off + 2]); + } + blk = &ctx->measRecord[off]; + *measIndex = blk[0]; + *measType = 0; + len = SPDM_Get16LE(&blk[2]); + if (blk[1] == SPDM_MEAS_SPEC_DMTF && len >= 3 && + SPDM_Get16LE(&blk[WOLFSPDM_MEAS_BLOCK_HDR_SZ + 1]) <= len - 3) { + /* DMTF value: Type(1) + ValueSize(2) + Value */ + *measType = blk[WOLFSPDM_MEAS_BLOCK_HDR_SZ]; + len = SPDM_Get16LE(&blk[WOLFSPDM_MEAS_BLOCK_HDR_SZ + 1]); + blk += 3; + } + blk += WOLFSPDM_MEAS_BLOCK_HDR_SZ; + + if (len > *valueSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(value, blk, len); + *valueSz = len; + return WOLFSPDM_SUCCESS; +} + +#endif /* !WOLFSPDM_NO_MEAS */ + +/* ----- CHALLENGE ----- */ + +#ifndef WOLFSPDM_NO_CHALLENGE + +#define WOLFSPDM_CHAL_RSP_SZ (4 + 2 * WOLFSPDM_HASH_SIZE + SPDM_NONCE_SZ + \ + WOLFSPDM_ATTEST_TAIL_SZ) + +int wolfSPDM_M1Start(WOLFSPDM_CTX* ctx) +{ + return wolfSPDM_RunStart(ctx, &ctx->m1Hash, &ctx->m1State); +} + +int wolfSPDM_M1Add(WOLFSPDM_CTX* ctx, const byte* req, word32 reqSz, + const byte* rsp, word32 rspSz) +{ + if (ctx->m1State == WOLFSPDM_RUN_NONE) { + return WOLFSPDM_SUCCESS; + } + return wolfSPDM_RunAdd(&ctx->m1Hash, req, reqSz, rsp, rspSz); +} + +int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + int slotId, byte measHashType) +{ + word32 sz; + int rc; + + if (ctx == NULL || buf == NULL || bufSz == NULL || slotId < 0 || + slotId > 7) { + return WOLFSPDM_E_INVALID_ARG; + } + sz = 4 + SPDM_NONCE_SZ + wolfSPDM_ReqContextSz(ctx); + if (*bufSz < sz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + buf[0] = ctx->spdmVersion; + buf[1] = SPDM_CHALLENGE; + buf[2] = (byte)slotId; + buf[3] = measHashType; + /* Nonce, then the 1.3+ RequesterContext */ + rc = wolfSPDM_GetRandom(ctx, &buf[4], sz - 4); + if (rc == WOLFSPDM_SUCCESS) { + *bufSz = sz; + } + return rc; +} + +int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, const byte* req, + word32 reqSz, const byte* buf, word32 bufSz, word32* sigOff) +{ + word32 off = 4 + WOLFSPDM_HASH_SIZE + SPDM_NONCE_SZ; + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + if (req == NULL || reqSz < 4 || sigOff == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CHALLENGE_AUTH, + WOLFSPDM_E_CHALLENGE); + + if (req[3] != SPDM_MEAS_SUMMARY_HASH_NONE) { + off += WOLFSPDM_HASH_SIZE; + } + if (buf[0] != ctx->spdmVersion || (buf[2] & 0x0F) != req[2] || + bufSz < off || + XMEMCMP(&buf[4], ctx->certChainHash, WOLFSPDM_HASH_SIZE) != 0 || + !wolfSPDM_ParseTail(ctx, req, reqSz, buf, bufSz, off, + WOLFSPDM_ECC_SIG_SIZE, sigOff)) { + return WOLFSPDM_E_CHALLENGE; + } + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Challenge(WOLFSPDM_CTX* ctx, int slotId, byte measHashType) +{ + byte req[4 + SPDM_NONCE_SZ + SPDM_REQ_CONTEXT_SZ]; + byte rsp[WOLFSPDM_CHAL_RSP_SZ]; + word32 reqSz = sizeof(req); + word32 rspSz = sizeof(rsp); + word32 sigOff = 0; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + /* M1 covers the chain fetched for this slot */ + if (ctx->state < WOLFSPDM_STATE_CERT || + ctx->state == WOLFSPDM_STATE_ERROR || + ctx->m1State == WOLFSPDM_RUN_NONE || + slotId != (int)ctx->currentSlotId) { + return WOLFSPDM_E_BAD_STATE; + } + if ((ctx->rspCaps & SPDM_CAP_CHAL_CAP) == 0) { + return WOLFSPDM_E_CAPS_MISMATCH; + } + + rc = wolfSPDM_ValidateCertChain(ctx); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildChallenge(ctx, req, &reqSz, slotId, measHashType); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, req, reqSz, rsp, &rspSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseChallengeAuth(ctx, req, reqSz, rsp, rspSz, + &sigOff); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_RunVerify(ctx, &ctx->m1Hash, &ctx->m1State, + "responder-challenge_auth signing", 32, req, reqSz, rsp, sigOff); + } + /* After CHALLENGE_AUTH the next M1 is VCA plus its own messages */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_M1Start(ctx); + } + return rc; +} + +#endif /* !WOLFSPDM_NO_CHALLENGE */ + +#endif /* !WOLFSPDM_NO_MEAS || !WOLFSPDM_NO_CHALLENGE */ diff --git a/src/spdm_context.c b/src/spdm_context.c index 28fc1fa..10cb29a 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -100,6 +100,9 @@ void wolfSPDM_Free(WOLFSPDM_CTX* ctx) if (ctx->flags.ephemeralKeyInit) { wc_ecc_free(&ctx->ephemeralKey); } +#if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) + wolfSPDM_AttestFree(ctx); +#endif /* Zero entire struct (covers all sensitive key material) */ wc_ForceZero(ctx, sizeof(WOLFSPDM_CTX)); @@ -613,6 +616,8 @@ const char* wolfSPDM_GetErrorString(int error) case WOLFSPDM_E_ALGO_MISMATCH: return "Algorithm mismatch"; case WOLFSPDM_E_CERT_PARSE: return "Certificate parse failed"; case WOLFSPDM_E_KEY_UPDATE: return "Key update failed"; + case WOLFSPDM_E_MEASUREMENT: return "Measurement response invalid"; + case WOLFSPDM_E_CHALLENGE: return "Challenge response invalid"; default: return "Unknown error"; } } diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 3bc6535..09c4d67 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -186,6 +186,7 @@ struct WOLFSPDM_CTX { #ifndef WOLFSPDM_NO_CERT /* Standard requester: negotiated limits and responder cert chain */ word32 rspCaps; + word32 vcaLen; /* transcript length after ALGORITHMS */ word32 dataTransferSize; word32 maxSpdmMsgSize; word32 certChainLen; @@ -195,6 +196,17 @@ struct WOLFSPDM_CTX { byte certChain[WOLFSPDM_MAX_CERT_CHAIN]; byte trustedCA[WOLFSPDM_MAX_TRUSTED_CA]; #endif +#ifndef WOLFSPDM_NO_MEAS + wc_Sha384 l1l2Hash; /* running L1/L2 over unsigned measurements */ + word32 measRecordLen; + byte measBlockCount; + byte l1l2State; /* WOLFSPDM_RUN_* */ + byte measRecord[WOLFSPDM_MAX_MEAS_RECORD]; +#endif +#ifndef WOLFSPDM_NO_CHALLENGE + wc_Sha384 m1Hash; /* running M1: VCA, DIGESTS, CERTIFICATE */ + byte m1State; /* WOLFSPDM_RUN_* */ +#endif /* Boolean flag bit field (at end for better struct packing) */ struct { @@ -209,6 +221,7 @@ struct WOLFSPDM_CTX { unsigned int allowUntrustedCert : 1; unsigned int rspKeyFromCert : 1; #endif + } flags; }; @@ -421,6 +434,31 @@ WOLFSPDM_TEST_API int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx); WOLFSPDM_LOCAL int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx); #endif +#if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) +/* Running transcript hash states */ +#define WOLFSPDM_RUN_NONE 0 +#define WOLFSPDM_RUN_LIVE 1 /* initialized, not extendable */ +#define WOLFSPDM_RUN_OPEN 2 /* extendable by the next message */ +WOLFSPDM_LOCAL void wolfSPDM_AttestFree(WOLFSPDM_CTX* ctx); +#endif +#ifndef WOLFSPDM_NO_MEAS +WOLFSPDM_TEST_API int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, + byte* buf, word32* bufSz, byte operation, int requestSig); +WOLFSPDM_TEST_API int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, + const byte* req, word32 reqSz, const byte* buf, word32 bufSz, + word32* sigOff); +#endif +#ifndef WOLFSPDM_NO_CHALLENGE +WOLFSPDM_LOCAL int wolfSPDM_M1Start(WOLFSPDM_CTX* ctx); +WOLFSPDM_LOCAL int wolfSPDM_M1Add(WOLFSPDM_CTX* ctx, const byte* req, + word32 reqSz, const byte* rsp, word32 rspSz); +WOLFSPDM_TEST_API int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, + word32* bufSz, int slotId, byte measHashType); +WOLFSPDM_TEST_API int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, + const byte* req, word32 reqSz, const byte* buf, word32 bufSz, + word32* sigOff); +#endif + #ifndef WOLFSPDM_NO_HEARTBEAT WOLFSPDM_TEST_API int wolfSPDM_BuildHeartbeat(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); diff --git a/src/spdm_secured.c b/src/spdm_secured.c index be7c4ea..fc03e63 100644 --- a/src/spdm_secured.c +++ b/src/spdm_secured.c @@ -359,6 +359,12 @@ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, if (ctx == NULL || cmdPlain == NULL || rspPlain == NULL || rspSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } +#ifndef WOLFSPDM_NO_MEAS + /* Only back-to-back GET_MEASUREMENTS extend L1/L2 */ + if (ctx->l1l2State == WOLFSPDM_RUN_OPEN) { + ctx->l1l2State = WOLFSPDM_RUN_LIVE; + } +#endif rc = wolfSPDM_EncryptInternal(ctx, cmdPlain, cmdSz, encBuf, &encSz); if (rc == WOLFSPDM_SUCCESS) { diff --git a/src/spdm_session.c b/src/spdm_session.c index 93a0577..461c463 100644 --- a/src/spdm_session.c +++ b/src/spdm_session.c @@ -77,6 +77,12 @@ int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) int rc; rc = wolfSPDM_BuildKeyExchange(ctx, txBuf, &txSz); +#ifndef WOLFSPDM_NO_CHALLENGE + /* KEY_EXCHANGE drops DIGESTS and CERTIFICATE from M1 */ + if (rc == WOLFSPDM_SUCCESS && ctx->m1State != WOLFSPDM_RUN_NONE) { + rc = wolfSPDM_M1Start(ctx); + } +#endif if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); } diff --git a/src/spdm_standard.c b/src/spdm_standard.c index 0b1fe98..fbb884a 100644 --- a/src/spdm_standard.c +++ b/src/spdm_standard.c @@ -190,12 +190,20 @@ int wolfSPDM_NegotiateAlgorithms(WOLFSPDM_CTX* ctx) { byte txBuf[48]; byte rxBuf[128]; + int rc; if (ctx == NULL) { return WOLFSPDM_E_INVALID_ARG; } - return wolfSPDM_ExchangeMsg(ctx, wolfSPDM_BuildNegotiateAlgorithms, + rc = wolfSPDM_ExchangeMsg(ctx, wolfSPDM_BuildNegotiateAlgorithms, wolfSPDM_ParseAlgorithms, txBuf, sizeof(txBuf), rxBuf, sizeof(rxBuf)); + if (rc == WOLFSPDM_SUCCESS) { + ctx->vcaLen = ctx->transcriptLen; + #ifndef WOLFSPDM_NO_CHALLENGE + rc = wolfSPDM_M1Start(ctx); + #endif + } + return rc; } /* ----- GET_DIGESTS / GET_CERTIFICATE (not part of the TH transcript) ----- */ @@ -230,6 +238,11 @@ int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx) if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_ParseDigests(ctx, rxBuf, rxSz); } +#ifndef WOLFSPDM_NO_CHALLENGE + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_M1Add(ctx, txBuf, sizeof(txBuf), rxBuf, rxSz); + } +#endif return rc; } @@ -298,6 +311,12 @@ int wolfSPDM_GetCertificate(WOLFSPDM_CTX* ctx, int slotId) rc = wolfSPDM_ParseCertificate(ctx, rxBuf, rxSz, &portionLen, &remainderLen); } + #ifndef WOLFSPDM_NO_CHALLENGE + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_M1Add(ctx, txBuf, sizeof(txBuf), rxBuf, + 8u + portionLen); + } + #endif /* Every non-final portion must make progress */ if (rc == WOLFSPDM_SUCCESS && portionLen == 0 && remainderLen > 0) { rc = WOLFSPDM_E_CERT_FAIL; @@ -562,6 +581,9 @@ int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx) ctx->state = WOLFSPDM_STATE_INIT; ctx->lastPeerErrorCode = 0; wolfSPDM_TranscriptReset(ctx); +#if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) + wolfSPDM_AttestFree(ctx); +#endif SPDM_CONNECT_STEP(ctx, "GET_VERSION\n", wolfSPDM_GetVersion(ctx)); SPDM_CONNECT_STEP(ctx, "GET_CAPABILITIES\n", diff --git a/test/test_certs.h b/test/test_certs.h index 62b9170..d232d68 100644 --- a/test/test_certs.h +++ b/test/test_certs.h @@ -205,4 +205,22 @@ static const byte test_rsp_chain_der[] = { 0x09, 0x35, 0x44, 0x9b, 0xfe, 0x06, 0x6d }; +/* Leaf key of test_rsp_chain_der (end_responder.key), raw P-384 */ +static const byte test_rsp_leaf_priv[] = { + 0x9c, 0x4e, 0x7e, 0x9f, 0x83, 0x28, 0x42, 0xd8, 0xc3, 0x01, 0xfb, 0x57, + 0x32, 0x09, 0xa5, 0xbb, 0x83, 0x6f, 0x93, 0xf3, 0xfe, 0xa3, 0x08, 0x8e, + 0xb7, 0xf9, 0x30, 0x30, 0xb9, 0x35, 0x26, 0x33, 0x05, 0x18, 0x7b, 0x05, + 0xbf, 0xb9, 0xf7, 0x03, 0x81, 0x16, 0xd1, 0x93, 0xfb, 0xd7, 0x02, 0x56 +}; +static const byte test_rsp_leaf_pub[] = { + 0xa7, 0xfc, 0x87, 0x24, 0xbe, 0x89, 0x4b, 0xeb, 0x29, 0x13, 0xd4, 0x85, + 0xc9, 0x68, 0xbc, 0x46, 0x5f, 0x17, 0x60, 0x1f, 0x5a, 0xf3, 0x4e, 0xb8, + 0x94, 0xdc, 0xcc, 0x39, 0x82, 0x27, 0x51, 0x0f, 0x68, 0x83, 0x9a, 0xe1, + 0x45, 0xd1, 0x9f, 0xef, 0x76, 0x95, 0xd3, 0x00, 0xcb, 0x13, 0x77, 0x31, + 0x12, 0x9c, 0x33, 0xd0, 0x36, 0xdc, 0xa2, 0x61, 0x9e, 0xaf, 0xcb, 0x19, + 0x36, 0x1c, 0xc8, 0x55, 0x1f, 0x53, 0xb9, 0x7f, 0x33, 0x60, 0x8b, 0x56, + 0x9d, 0xd6, 0x2b, 0x00, 0x77, 0x36, 0x0c, 0x72, 0xc0, 0x68, 0x81, 0x82, + 0x56, 0x34, 0x41, 0x0e, 0x12, 0x26, 0xee, 0xa3, 0xa4, 0xe4, 0x85, 0x3d +}; + #endif /* WOLFSPDM_TEST_CERTS_H */ diff --git a/test/unit_test.c b/test/unit_test.c index 7b868a1..9d7f42f 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -3206,10 +3206,15 @@ static int test_derive_updated_keys(void) TEST_PASS(); } -/* Loopback responder: a mirrored context that answers secured requests */ +#endif /* !WOLFSPDM_NO_KEY_UPDATE */ + +#if !defined(WOLFSPDM_NO_KEY_UPDATE) || !defined(WOLFSPDM_NO_MEAS) || \ + !defined(WOLFSPDM_NO_CHALLENGE) +/* Loopback responder: a mirrored context that answers requests */ static WOLFSPDM_CTX g_peer; static int g_peerRejects; +#ifndef WOLFSPDM_NO_KEY_UPDATE static void test_swap(byte* a, byte* b, word32 sz) { word32 i; @@ -3229,52 +3234,234 @@ static void test_peer_swap_dirs(WOLFSPDM_CTX* p) test_swap(p->reqAppSecret, p->rspAppSecret, WOLFSPDM_HASH_SIZE); } +static void test_peer_key_update(WOLFSPDM_CTX* p, byte op) +{ + if (op == SPDM_KEY_UPDATE_OP_UPDATE_KEY) { + test_peer_swap_dirs(p); + wolfSPDM_DeriveUpdatedKeys(p, 0); + test_peer_swap_dirs(p); + p->rspSeqNum = 0; + } + else if (op == SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS) { + wolfSPDM_DeriveUpdatedKeys(p, 1); + p->reqSeqNum = 0; + p->rspSeqNum = 0; + } +} +#endif /* !WOLFSPDM_NO_KEY_UPDATE */ + +#if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) +static const byte test_vca[] = { + 0x10, 0x84, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00 +}; +static wc_Sha384 g_peerRun; +static int g_peerRunOpen; +static int g_peerTamper; + +static void test_peer_run_reset(void) +{ + if (g_peerRunOpen) { + wc_Sha384Free(&g_peerRun); + g_peerRunOpen = 0; + } +} + +/* Extend the peer's running L1/L2 or M1, started at the VCA */ +static int test_peer_run_add(const byte* req, word32 reqSz, const byte* rsp, + word32 rspSz) +{ + int rc = 0; + + if (!g_peerRunOpen) { + rc = wc_InitSha384(&g_peerRun); + if (rc == 0) { + g_peerRunOpen = 1; + rc = wc_Sha384Update(&g_peerRun, test_vca, sizeof(test_vca)); + } + } + if (rc == 0) { + rc = wc_Sha384Update(&g_peerRun, req, reqSz); + } + if (rc == 0) { + rc = wc_Sha384Update(&g_peerRun, rsp, rspSz); + } + return rc; +} + +/* Close the run with this exchange and append the leaf-key signature */ +static int test_peer_sign(const char* label, word32 labelSz, const byte* req, + word32 reqSz, byte* rsp, word32* rspSz) +{ + byte digest[WOLFSPDM_HASH_SIZE]; + word32 sigSz = WOLFSPDM_ECC_SIG_SIZE; + int rc; + + rc = test_peer_run_add(req, reqSz, rsp, *rspSz); + if (rc == 0) { + rc = wc_Sha384Final(&g_peerRun, digest); + } + test_peer_run_reset(); + if (rc == 0) { + rc = wolfSPDM_BuildSignedHash(g_peer.spdmVersion, label, labelSz, + digest, digest); + } + if (rc == 0) { + rc = wolfSPDM_SignHash(&g_peer, digest, sizeof(digest), + rsp + *rspSz, &sigSz); + } + if (rc == 0) { + if (g_peerTamper) { + rsp[*rspSz] ^= 0x01; + } + *rspSz += sigSz; + } + return rc; +} + +/* Nonce(32) of 0x5A, optional zero summary hash, empty OpaqueData and the + * 1.3+ RequesterContext echo */ +static word32 test_rsp_tail(const byte* req, word32 reqSz, byte* rsp, + word32 n, word32 summarySz) +{ + XMEMSET(rsp + n, 0x5A, 32); + n += 32; + XMEMSET(rsp + n, 0, summarySz + 2); + n += summarySz + 2; + if (req[0] >= SPDM_VERSION_13) { + XMEMCPY(rsp + n, req + reqSz - 8, 8); + n += 8; + } + return n; +} +#endif + +#ifndef WOLFSPDM_NO_MEAS +/* One DMTF block: index 1, type 1, value DE AD BE EF */ +static const byte test_meas_block[] = { + 0x01, SPDM_MEAS_SPEC_DMTF, 0x07, 0x00, 0x01, 0x04, 0x00, + 0xDE, 0xAD, 0xBE, 0xEF +}; + +/* Unsigned MEASUREMENTS for req; TOTAL_NUMBER carries no blocks */ +static word32 test_meas_rsp(const byte* req, word32 reqSz, byte* rsp) +{ + int all = (req[3] != SPDM_MEAS_OPERATION_TOTAL_NUMBER); + word32 n = 8; + + rsp[0] = req[0]; + rsp[1] = SPDM_MEASUREMENTS; + rsp[2] = all ? 0 : 1; + rsp[3] = 0; + rsp[4] = all ? 1 : 0; + rsp[5] = all ? (byte)sizeof(test_meas_block) : 0; + rsp[6] = 0; + rsp[7] = 0; + if (all) { + XMEMCPY(rsp + n, test_meas_block, sizeof(test_meas_block)); + n += (word32)sizeof(test_meas_block); + } + return test_rsp_tail(req, reqSz, rsp, n, 0); +} +#endif + +#ifndef WOLFSPDM_NO_CHALLENGE +/* CHALLENGE_AUTH for req up to its signature */ +static word32 test_chal_rsp(const WOLFSPDM_CTX* ctx, const byte* req, + word32 reqSz, byte* rsp) +{ + rsp[0] = req[0]; + rsp[1] = SPDM_CHALLENGE_AUTH; + rsp[2] = req[2]; + rsp[3] = 0x01; + XMEMCPY(rsp + 4, ctx->certChainHash, WOLFSPDM_HASH_SIZE); + return test_rsp_tail(req, reqSz, rsp, 4 + WOLFSPDM_HASH_SIZE, + (req[3] != SPDM_MEAS_SUMMARY_HASH_NONE) ? WOLFSPDM_HASH_SIZE : 0); +} +#endif + static int test_peer_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz, void* userCtx) { WOLFSPDM_CTX* p = (WOLFSPDM_CTX*)userCtx; - byte req[32]; - byte rsp[4]; + byte req[64]; + byte rsp[512]; word32 reqSz = sizeof(req); + word32 rspSz = 4; + int clear = (txSz > 0 && txBuf[0] >= 0x10 && txBuf[0] <= 0x1F); + int rc = 0; (void)ctx; - if (wolfSPDM_DecryptInternal(p, txBuf, txSz, req, &reqSz) != 0 || - reqSz < 4) { + if (clear && txSz <= sizeof(req)) { + XMEMCPY(req, txBuf, txSz); + reqSz = txSz; + } + else if (clear || + wolfSPDM_DecryptInternal(p, txBuf, txSz, req, &reqSz) != 0) { + return -1; + } + if (reqSz < 4) { return -1; } +#ifndef WOLFSPDM_NO_MEAS + if (req[1] != SPDM_GET_MEASUREMENTS) { + test_peer_run_reset(); + } +#endif + rsp[0] = req[0]; - rsp[2] = req[2]; - rsp[3] = req[3]; + rsp[1] = SPDM_ERROR; + rsp[2] = SPDM_ERROR_UNSUPPORTED_REQUEST; + rsp[3] = 0; if (g_peerRejects) { - rsp[1] = SPDM_ERROR; rsp[2] = SPDM_ERROR_BUSY; - rsp[3] = 0; } +#ifndef WOLFSPDM_NO_KEY_UPDATE else if (req[1] == SPDM_KEY_UPDATE) { rsp[1] = SPDM_KEY_UPDATE_ACK; - if (req[2] == SPDM_KEY_UPDATE_OP_UPDATE_KEY) { - test_peer_swap_dirs(p); - wolfSPDM_DeriveUpdatedKeys(p, 0); - test_peer_swap_dirs(p); - p->rspSeqNum = 0; - } - else if (req[2] == SPDM_KEY_UPDATE_OP_UPDATE_ALL_KEYS) { - wolfSPDM_DeriveUpdatedKeys(p, 1); - p->reqSeqNum = 0; - p->rspSeqNum = 0; - } + rsp[2] = req[2]; + rsp[3] = req[3]; + test_peer_key_update(p, req[2]); } +#endif #ifndef WOLFSPDM_NO_HEARTBEAT else if (req[1] == SPDM_HEARTBEAT) { rsp[1] = SPDM_HEARTBEAT_ACK; rsp[2] = 0; - rsp[3] = 0; } #endif - else { +#ifndef WOLFSPDM_NO_MEAS + else if (req[1] == SPDM_GET_MEASUREMENTS) { + rspSz = test_meas_rsp(req, reqSz, rsp); + if (req[2] & SPDM_MEAS_REQUEST_SIG_BIT) { + rc = test_peer_sign("responder-measurements signing", 30, + req, reqSz, rsp, &rspSz); + } + else { + rc = test_peer_run_add(req, reqSz, rsp, rspSz); + } + } +#endif +#ifndef WOLFSPDM_NO_CHALLENGE + else if (req[1] == SPDM_CHALLENGE) { + /* No DIGESTS/CERTIFICATE crossed the loopback, so M1 is VCA + C */ + test_peer_run_reset(); + rspSz = test_chal_rsp(ctx, req, reqSz, rsp); + rc = test_peer_sign("responder-challenge_auth signing", 32, + req, reqSz, rsp, &rspSz); + } +#endif + if (rc != 0) { return -1; } - return wolfSPDM_EncryptInternal(p, rsp, sizeof(rsp), rxBuf, rxSz); + if (clear) { + if (rspSz > *rxSz) { + return -1; + } + XMEMCPY(rxBuf, rsp, rspSz); + *rxSz = rspSz; + return 0; + } + return wolfSPDM_EncryptInternal(p, rsp, rspSz, rxBuf, rxSz); } static void test_session_loopback(WOLFSPDM_CTX* ctx) @@ -3288,8 +3475,10 @@ static void test_session_loopback(WOLFSPDM_CTX* ctx) XMEMSET(ctx->rspDataKey, 0x22, WOLFSPDM_AEAD_KEY_SIZE); XMEMSET(ctx->reqDataIv, 0x33, WOLFSPDM_AEAD_IV_SIZE); XMEMSET(ctx->rspDataIv, 0x44, WOLFSPDM_AEAD_IV_SIZE); +#ifndef WOLFSPDM_NO_KEY_UPDATE XMEMSET(ctx->reqAppSecret, 0x55, WOLFSPDM_HASH_SIZE); XMEMSET(ctx->rspAppSecret, 0x66, WOLFSPDM_HASH_SIZE); +#endif #ifndef WOLFSPDM_NO_CERT ctx->rspCaps = SPDM_CAP_HBEAT_CAP | SPDM_CAP_KEY_UPD_CAP; #endif @@ -3301,11 +3490,35 @@ static void test_session_loopback(WOLFSPDM_CTX* ctx) XMEMCPY(p->rspDataKey, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); XMEMCPY(p->reqDataIv, ctx->rspDataIv, WOLFSPDM_AEAD_IV_SIZE); XMEMCPY(p->rspDataIv, ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE); +#ifndef WOLFSPDM_NO_KEY_UPDATE XMEMCPY(p->reqAppSecret, ctx->rspAppSecret, WOLFSPDM_HASH_SIZE); XMEMCPY(p->rspAppSecret, ctx->reqAppSecret, WOLFSPDM_HASH_SIZE); +#endif +#if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) + /* The peer signs with the sample leaf key over the shared VCA */ + XMEMCPY(ctx->transcript, test_vca, sizeof(test_vca)); + ctx->transcriptLen = (word32)sizeof(test_vca); + ctx->vcaLen = ctx->transcriptLen; + wolfSPDM_SetResponderPubKey(ctx, test_rsp_leaf_pub, + sizeof(test_rsp_leaf_pub)); + wolfSPDM_SetRequesterKeyPair(p, test_rsp_leaf_priv, + sizeof(test_rsp_leaf_priv), test_rsp_leaf_pub, + sizeof(test_rsp_leaf_pub)); + g_peerRunOpen = 0; + g_peerTamper = 0; +#endif +#ifndef WOLFSPDM_NO_MEAS + ctx->rspCaps |= SPDM_CAP_MEAS_CAP_SIG; +#endif +#ifndef WOLFSPDM_NO_CHALLENGE + ctx->rspCaps |= SPDM_CAP_CHAL_CAP; +#endif g_peerRejects = 0; wolfSPDM_SetIO(ctx, test_peer_io_cb, p); } +#endif /* loopback */ + +#ifndef WOLFSPDM_NO_KEY_UPDATE static int test_key_update_loopback(void) { @@ -3364,6 +3577,218 @@ static int test_key_update_loopback(void) } #endif /* !WOLFSPDM_NO_KEY_UPDATE */ +#ifndef WOLFSPDM_NO_MEAS +static int test_measurements_msgs(void) +{ + byte req[64]; + byte rsp[256]; + byte err[] = {0x12, SPDM_ERROR, SPDM_ERROR_BUSY, 0, 0, 0, 0, 0}; + word32 reqSz = sizeof(req); + word32 n; + word32 sigOff = 0; + TEST_CTX_SETUP_V12(); + + printf("test_measurements_msgs...\n"); + ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, req, &reqSz, + SPDM_MEAS_OPERATION_ALL, 1)); + ASSERT_EQ(reqSz, 37, "1.2 signed request is 37 bytes"); + ASSERT_EQ(req[2], SPDM_MEAS_REQUEST_SIG_BIT, "Signature bit"); + ctx->spdmVersion = SPDM_VERSION_13; + reqSz = sizeof(req); + ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, req, &reqSz, + SPDM_MEAS_OPERATION_ALL, 0)); + ASSERT_EQ(reqSz, 12, "1.3 unsigned request carries RequesterContext"); + reqSz = 44; + ASSERT_EQ(wolfSPDM_BuildGetMeasurements(ctx, req, &reqSz, + SPDM_MEAS_OPERATION_ALL, 1), WOLFSPDM_E_BUFFER_SMALL, + "1.3 signed request needs 45 bytes"); + + /* 1.3 unsigned response echoing the RequesterContext */ + reqSz = sizeof(req); + ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, req, &reqSz, + SPDM_MEAS_OPERATION_ALL, 0)); + n = test_meas_rsp(req, reqSz, rsp); + ASSERT_SUCCESS(wolfSPDM_ParseMeasurements(ctx, req, reqSz, rsp, n, + &sigOff)); + ASSERT_EQ(sigOff, n, "Unsigned response has no signature"); + rsp[n] = 0; + ASSERT_EQ(wolfSPDM_ParseMeasurements(ctx, req, reqSz, rsp, n + 1, + &sigOff), WOLFSPDM_E_MEASUREMENT, "Trailing byte"); + rsp[n - 1] ^= 0xFF; + ASSERT_EQ(wolfSPDM_ParseMeasurements(ctx, req, reqSz, rsp, n, &sigOff), + WOLFSPDM_E_MEASUREMENT, "RequesterContext echo mismatch"); + rsp[n - 1] ^= 0xFF; + rsp[4] = 2; + ASSERT_EQ(wolfSPDM_ParseMeasurements(ctx, req, reqSz, rsp, n, &sigOff), + WOLFSPDM_E_MEASUREMENT, "Block count must fill the record"); + rsp[4] = 1; + rsp[6] = 0x10; + ASSERT_EQ(wolfSPDM_ParseMeasurements(ctx, req, reqSz, rsp, n, &sigOff), + WOLFSPDM_E_MEASUREMENT, "Record longer than the response"); + rsp[6] = 0; + ASSERT_EQ(wolfSPDM_ParseMeasurements(ctx, req, reqSz, rsp, n - 1, + &sigOff), WOLFSPDM_E_MEASUREMENT, "Truncated tail"); + ASSERT_EQ(wolfSPDM_ParseMeasurements(ctx, req, reqSz, err, sizeof(err), + &sigOff), WOLFSPDM_E_PEER_ERROR, "ERROR response"); + ASSERT_EQ(wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 0), + WOLFSPDM_E_NOT_CONNECTED, "Measurements need a session"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_measurements_loopback(void) +{ + static const byte expect[] = {0xDE, 0xAD, 0xBE, 0xEF}; + byte idx = 0; + byte type = 0; + byte val[16]; + word32 valSz = sizeof(val); + TEST_CTX_SETUP(); + + printf("test_measurements_loopback...\n"); + test_session_loopback(ctx); + + ASSERT_SUCCESS(wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 1)); + ASSERT_EQ(wolfSPDM_GetMeasurementCount(ctx), 1, "One block"); + ASSERT_SUCCESS(wolfSPDM_GetMeasurementBlock(ctx, 0, &idx, &type, val, + &valSz)); + ASSERT_EQ(idx, 1, "Block index"); + ASSERT_EQ(type, 1, "DMTF value type"); + ASSERT_EQ(valSz, 4, "Value size"); + ASSERT_EQ(memcmp(val, expect, sizeof(expect)), 0, "Value"); + valSz = 2; + ASSERT_EQ(wolfSPDM_GetMeasurementBlock(ctx, 0, &idx, &type, val, &valSz), + WOLFSPDM_E_BUFFER_SMALL, "Small value buffer"); + valSz = sizeof(val); + ASSERT_EQ(wolfSPDM_GetMeasurementBlock(ctx, 1, &idx, &type, val, &valSz), + WOLFSPDM_E_INVALID_ARG, "Block out of range"); + + /* Unsigned exchanges before a signed one are part of its L1/L2 */ + ASSERT_SUCCESS(wolfSPDM_GetMeasurements(ctx, + SPDM_MEAS_OPERATION_TOTAL_NUMBER, 0)); + ASSERT_EQ(wolfSPDM_GetMeasurementCount(ctx), 0, "TOTAL_NUMBER has none"); + ASSERT_SUCCESS(wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 0)); + ASSERT_SUCCESS(wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 1)); +#ifndef WOLFSPDM_NO_HEARTBEAT + /* Any other request restarts L1/L2 on both sides */ + ASSERT_SUCCESS(wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 0)); + ASSERT_SUCCESS(wolfSPDM_Heartbeat(ctx)); + ASSERT_SUCCESS(wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 1)); +#endif + + g_peerTamper = 1; + ASSERT_EQ(wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 1), + WOLFSPDM_E_BAD_SIGNATURE, "Tampered signature"); + ASSERT_EQ(wolfSPDM_GetMeasurementCount(ctx), 0, + "A failed fetch exposes no blocks"); + g_peerTamper = 0; + ASSERT_SUCCESS(wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 1)); + + ctx->rspCaps &= ~(word32)SPDM_CAP_MEAS_CAP_SIG; + ASSERT_EQ(wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, 1), + WOLFSPDM_E_CAPS_MISMATCH, "Responder without MEAS_CAP"); + + wolfSPDM_Free(&g_peer); + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSPDM_NO_MEAS */ + +#ifndef WOLFSPDM_NO_CHALLENGE +static int test_challenge_msgs(void) +{ + byte req[64]; + byte rsp[256]; + word32 reqSz = sizeof(req); + word32 n; + word32 sigOff = 0; + TEST_CTX_SETUP_V12(); + + printf("test_challenge_msgs...\n"); + ASSERT_SUCCESS(wolfSPDM_BuildChallenge(ctx, req, &reqSz, 0, + SPDM_MEAS_SUMMARY_HASH_NONE)); + ASSERT_EQ(reqSz, 36, "1.2 CHALLENGE is 36 bytes"); + reqSz = sizeof(req); + ASSERT_EQ(wolfSPDM_BuildChallenge(ctx, req, &reqSz, 8, + SPDM_MEAS_SUMMARY_HASH_NONE), WOLFSPDM_E_INVALID_ARG, "Slot 8"); + ctx->spdmVersion = SPDM_VERSION_13; + reqSz = 43; + ASSERT_EQ(wolfSPDM_BuildChallenge(ctx, req, &reqSz, 0, + SPDM_MEAS_SUMMARY_HASH_NONE), WOLFSPDM_E_BUFFER_SMALL, + "1.3 CHALLENGE needs 44 bytes"); + + reqSz = sizeof(req); + ASSERT_SUCCESS(wolfSPDM_BuildChallenge(ctx, req, &reqSz, 0, + SPDM_MEAS_SUMMARY_HASH_TCB)); + XMEMSET(ctx->certChainHash, 0xC7, WOLFSPDM_HASH_SIZE); + n = test_chal_rsp(ctx, req, reqSz, rsp); + XMEMSET(rsp + n, 0, WOLFSPDM_ECC_SIG_SIZE); + ASSERT_SUCCESS(wolfSPDM_ParseChallengeAuth(ctx, req, reqSz, rsp, + n + WOLFSPDM_ECC_SIG_SIZE, &sigOff)); + ASSERT_EQ(sigOff, n, "Signature follows the RequesterContext"); + ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, req, reqSz, rsp, n, &sigOff), + WOLFSPDM_E_CHALLENGE, "Missing signature"); + rsp[n - 1] ^= 0xFF; + ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, req, reqSz, rsp, + n + WOLFSPDM_ECC_SIG_SIZE, &sigOff), WOLFSPDM_E_CHALLENGE, + "RequesterContext echo mismatch"); + rsp[n - 1] ^= 0xFF; + rsp[2] = 1; + ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, req, reqSz, rsp, + n + WOLFSPDM_ECC_SIG_SIZE, &sigOff), WOLFSPDM_E_CHALLENGE, + "Slot echo mismatch"); + rsp[2] = 0; + rsp[4] ^= 0x01; + ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, req, reqSz, rsp, + n + WOLFSPDM_ECC_SIG_SIZE, &sigOff), WOLFSPDM_E_CHALLENGE, + "CertChainHash mismatch"); + rsp[4] ^= 0x01; + req[3] = SPDM_MEAS_SUMMARY_HASH_NONE; + ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, req, reqSz, rsp, + n + WOLFSPDM_ECC_SIG_SIZE, &sigOff), WOLFSPDM_E_CHALLENGE, + "Summary hash present without being requested"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_challenge_loopback(void) +{ + TEST_CTX_SETUP(); + + printf("test_challenge_loopback...\n"); + test_session_loopback(ctx); + ASSERT_SUCCESS(test_load_sample_chain(ctx)); + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(ctx->certChainHash, ctx->certChain, + ctx->certChainLen, NULL, 0, NULL, 0)); + ASSERT_SUCCESS(wolfSPDM_SetTrustedCAs(ctx, test_ca_cert_der, + sizeof(test_ca_cert_der))); + ASSERT_EQ(wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_NONE), + WOLFSPDM_E_BAD_STATE, "M1 starts at NEGOTIATE_ALGORITHMS"); + ASSERT_SUCCESS(wolfSPDM_M1Start(ctx)); + + ASSERT_SUCCESS(wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_NONE)); + ASSERT_SUCCESS(wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_ALL)); + ASSERT_EQ(wolfSPDM_Challenge(ctx, 1, SPDM_MEAS_SUMMARY_HASH_NONE), + WOLFSPDM_E_BAD_STATE, "No chain held for slot 1"); + + g_peerTamper = 1; + ASSERT_EQ(wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_NONE), + WOLFSPDM_E_BAD_SIGNATURE, "Tampered signature"); + g_peerTamper = 0; + + ASSERT_SUCCESS(wolfSPDM_M1Start(ctx)); + ctx->rspCaps &= ~(word32)SPDM_CAP_CHAL_CAP; + ASSERT_EQ(wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_NONE), + WOLFSPDM_E_CAPS_MISMATCH, "Responder without CHAL_CAP"); + + wolfSPDM_Free(&g_peer); + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSPDM_NO_CHALLENGE */ + int main(void) { printf("===========================================\n"); @@ -3514,6 +3939,14 @@ int main(void) test_derive_updated_keys(); test_key_update_loopback(); #endif +#ifndef WOLFSPDM_NO_MEAS + test_measurements_msgs(); + test_measurements_loopback(); +#endif +#ifndef WOLFSPDM_NO_CHALLENGE + test_challenge_msgs(); + test_challenge_loopback(); +#endif #ifdef WOLFSPDM_RESPONDER test_responder_init_free(); diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index 4074508..3273c8d 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -143,6 +143,24 @@ WOLFSPDM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz); +#ifndef WOLFSPDM_NO_MEAS +#define WOLFSPDM_HAS_MEASUREMENTS +/* Fetch measurements over the session; a signed request is verified against + * the responder key before the blocks are exposed */ +WOLFSPDM_API int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, + byte measOperation, int requestSignature); +WOLFSPDM_API int wolfSPDM_GetMeasurementCount(WOLFSPDM_CTX* ctx); +/* valueSz is in/out; measType is the DMTF value type, 0 for raw blocks */ +WOLFSPDM_API int wolfSPDM_GetMeasurementBlock(WOLFSPDM_CTX* ctx, int blockIdx, + byte* measIndex, byte* measType, byte* value, word32* valueSz); +#endif +#ifndef WOLFSPDM_NO_CHALLENGE +#define WOLFSPDM_HAS_CHALLENGE +/* Sessionless CHALLENGE after GET_CERTIFICATE for slotId; validates the + * chain and verifies CHALLENGE_AUTH over M1 */ +WOLFSPDM_API int wolfSPDM_Challenge(WOLFSPDM_CTX* ctx, int slotId, + byte measHashType); +#endif #ifndef WOLFSPDM_NO_HEARTBEAT #define WOLFSPDM_HAS_HEARTBEAT WOLFSPDM_API int wolfSPDM_Heartbeat(WOLFSPDM_CTX* ctx); diff --git a/wolfspdm/spdm_error.h b/wolfspdm/spdm_error.h index 9d739db..e0f6ef5 100644 --- a/wolfspdm/spdm_error.h +++ b/wolfspdm/spdm_error.h @@ -58,6 +58,8 @@ enum WOLFSPDM_ERROR { WOLFSPDM_E_ALGO_MISMATCH = -23, /* Algorithm negotiation failed */ WOLFSPDM_E_CERT_PARSE = -24, /* Certificate could not be parsed */ WOLFSPDM_E_KEY_UPDATE = -25, /* Key update failed */ + WOLFSPDM_E_MEASUREMENT = -26, /* Malformed MEASUREMENTS response */ + WOLFSPDM_E_CHALLENGE = -27, /* Malformed or mismatched CHALLENGE_AUTH */ }; /* Get human-readable error string */ diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index d655354..73eea10 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -193,6 +193,13 @@ extern "C" { #if defined(WOLFSPDM_PROFILE_TPM) && !defined(WOLFSPDM_NO_KEY_UPDATE) #define WOLFSPDM_NO_KEY_UPDATE #endif +/* Attestation needs the certificate flow (VCA transcript, chain hash) */ +#if defined(WOLFSPDM_NO_CERT) && !defined(WOLFSPDM_NO_MEAS) + #define WOLFSPDM_NO_MEAS +#endif +#if defined(WOLFSPDM_NO_CERT) && !defined(WOLFSPDM_NO_CHALLENGE) + #define WOLFSPDM_NO_CHALLENGE +#endif /* ----- Session Keep-Alive and Key Rotation ----- */ @@ -267,6 +274,32 @@ extern "C" { #endif #endif /* !WOLFSPDM_NO_CERT */ +/* ----- Attestation: measurements and challenge ----- */ + +#ifndef WOLFSPDM_NO_MEAS +#define SPDM_GET_MEASUREMENTS 0xE0 +#define SPDM_MEASUREMENTS 0x60 +#define SPDM_CAP_MEAS_CAP_NO_SIG 0x00000008 +#define SPDM_CAP_MEAS_CAP_SIG 0x00000010 +#define SPDM_MEAS_REQUEST_SIG_BIT 0x01 +#define SPDM_MEAS_OPERATION_TOTAL_NUMBER 0x00 +#define SPDM_MEAS_OPERATION_ALL 0xFF +#define SPDM_MEAS_SPEC_DMTF 0x01 +#define WOLFSPDM_MEAS_BLOCK_HDR_SZ 4 /* Index + MeasSpec + Size(2) */ +#ifndef WOLFSPDM_MAX_MEAS_RECORD +#define WOLFSPDM_MAX_MEAS_RECORD 1024 +#endif +#endif /* !WOLFSPDM_NO_MEAS */ + +#ifndef WOLFSPDM_NO_CHALLENGE +#define SPDM_CHALLENGE 0x83 +#define SPDM_CHALLENGE_AUTH 0x03 +#define SPDM_CAP_CHAL_CAP 0x00000004 +#define SPDM_MEAS_SUMMARY_HASH_NONE 0x00 +#define SPDM_MEAS_SUMMARY_HASH_TCB 0x01 +#define SPDM_MEAS_SUMMARY_HASH_ALL 0xFF +#endif /* !WOLFSPDM_NO_CHALLENGE */ + /* ----- TCG Build Option ----- */ /* Nuvoton or Nations enables TCG SPDM binding; future chips can set directly */ From 999920894343c571f89d2abbcd137a9cff69e93d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 19:35:38 -0700 Subject: [PATCH 07/27] Add CHUNK_SEND and CHUNK_GET large message chunking --- .github/workflows/build-test.yml | 2 + .github/workflows/spdm-emu-test.yml | 97 ++++++ Makefile.am | 3 +- README.md | 3 +- config.h.in | 3 + configure.ac | 10 + examples/spdm_test.sh | 6 +- src/spdm_attest.c | 2 +- src/spdm_chunk.c | 327 +++++++++++++++++++ src/spdm_context.c | 1 + src/spdm_internal.h | 26 ++ src/spdm_secured.c | 41 ++- src/spdm_session.c | 53 +-- src/spdm_standard.c | 17 +- test/unit_test.c | 479 ++++++++++++++++++++++++++-- wolfspdm/spdm_error.h | 1 + wolfspdm/spdm_types.h | 55 +++- 17 files changed, 1052 insertions(+), 74 deletions(-) create mode 100644 src/spdm_chunk.c diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 2b14c8b..5b80291 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -96,6 +96,8 @@ jobs: configure: '--disable-heartbeat --disable-key-update' - variant: no-attest configure: '--disable-meas --disable-challenge' + - variant: no-chunking + configure: '--disable-chunking' - variant: lean configure: '--disable-cert --disable-heartbeat --disable-key-update' diff --git a/.github/workflows/spdm-emu-test.yml b/.github/workflows/spdm-emu-test.yml index 95e65c0..248011a 100644 --- a/.github/workflows/spdm-emu-test.yml +++ b/.github/workflows/spdm-emu-test.yml @@ -125,3 +125,100 @@ jobs: config.log test/*.log /tmp/spdm_emu_*.log + + # CHUNK_SEND / CHUNK_GET: a small DataTransferSize on either side + spdm-emu-chunk: + name: chunking / wolfSPDM ${{ matrix.lib }} vs spdm-emu ${{ matrix.emu }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - lib: dts64 + emu: stock + - lib: default + emu: dts42 + - lib: dts64 + emu: dts42 + env: + EMU_CAPS: CACHE,CERT,CHAL,MEAS_SIG,MEAS_FRESH,ENCRYPT,MAC,MUT_AUTH,KEY_EX,PSK_WITH_CONTEXT,ENCAP,HBEAT,KEY_UPD,HANDSHAKE_IN_CLEAR,SET_CERT,CSR,MULTI_KEY_NEG,GET_KEY_PAIR_INFO,SET_KEY_PAIR_INFO,LARGE_RESP,CHUNK + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake libtool cmake libmbedtls-dev + + - name: Compute cache period + id: cache-period + run: echo "biweekly=$(( $(date +%s) / 1296000 ))" >> $GITHUB_OUTPUT + + - name: Cache wolfSSL + id: cache-wolfssl + uses: actions/cache@v4 + with: + path: ~/wolfssl-install + key: wolfssl-spdm-ubuntu-24.04-${{ steps.cache-period.outputs.biweekly }} + + - name: Build wolfSSL + if: steps.cache-wolfssl.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 https://github.com/wolfSSL/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-wolftpm --enable-ecc --enable-sha384 \ + --enable-aesgcm --enable-hkdf --enable-sp \ + --prefix=$HOME/wolfssl-install + make -j$(nproc) + make install + + - name: Build wolfSPDM + run: | + ./autogen.sh + ./configure --with-wolfssl=$HOME/wolfssl-install \ + CFLAGS="-O2 ${{ matrix.lib == 'dts64' && '-DWOLFSPDM_DATA_TRANSFER_SIZE=64' || '' }}" + make -j$(nproc) + + - name: Run unit tests + run: LD_LIBRARY_PATH=$HOME/wolfssl-install/lib make check + + # The stock build shares the main job's cache; dts42 shrinks the + # responder's DataTransferSize to the 42-byte minimum + - name: Cache spdm-emu + id: cache-spdm-emu + uses: actions/cache@v4 + with: + path: ~/${{ matrix.emu == 'dts42' && 'spdm-emu-dts42' || 'spdm-emu' }}/build/bin + key: ${{ matrix.emu == 'dts42' && 'spdm-emu-dts42' || 'spdm-emu' }}-ubuntu-24.04-${{ steps.cache-period.outputs.biweekly }} + + - name: Build spdm-emu + if: steps.cache-spdm-emu.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 --recursive https://github.com/DMTF/spdm-emu.git \ + ${{ matrix.emu == 'dts42' && 'spdm-emu-dts42' || 'spdm-emu' }} + cd ${{ matrix.emu == 'dts42' && 'spdm-emu-dts42' || 'spdm-emu' }} + mkdir build && cd build + cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=mbedtls \ + -DCMAKE_C_FLAGS="${{ matrix.emu == 'dts42' && '-DLIBSPDM_RECEIVER_BUFFER_SIZE=170' || '' }}" .. + make copy_sample_key + make -j$(nproc) + + - name: Run SPDM emulator tests with chunking + run: | + export LD_LIBRARY_PATH=$HOME/wolfssl-install/lib:${{ github.workspace }}/.libs + export SPDM_EMU_PATH=$HOME/${{ matrix.emu == 'dts42' && 'spdm-emu-dts42' || 'spdm-emu' }}/build/bin + export SPDM_EMU_ARGS="--cap $EMU_CAPS" + ./examples/spdm_test.sh + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: spdm-emu-chunk-${{ matrix.lib }}-${{ matrix.emu }} + path: | + config.log + test/*.log + /tmp/spdm_emu_*.log diff --git a/Makefile.am b/Makefile.am index d8e5539..fdad72c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,8 @@ libwolfspdm_la_SOURCES = \ src/spdm_transcript.c if BUILD_CERT -libwolfspdm_la_SOURCES += src/spdm_standard.c src/spdm_attest.c +libwolfspdm_la_SOURCES += src/spdm_standard.c src/spdm_attest.c \ + src/spdm_chunk.c endif if BUILD_TCG libwolfspdm_la_SOURCES += src/spdm_tcg.c diff --git a/README.md b/README.md index 5aadd29..8474b01 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,11 @@ make check | `--enable-debug` | Debug output with `-g -O0` (default: `-O2`) | | `--enable-dynamic-mem` | Use heap allocation for `WOLFSPDM_CTX` (default: static) | | `--disable-mldsa` / `--disable-mlkem` | Force off ML-DSA signatures / ML-KEM key exchange (default: auto-follow wolfSSL) | -| `--disable-chunking` | Compile out SPDM 1.2 message chunking (default: enabled) | +| `--disable-chunking` | Compile out CHUNK_SEND/CHUNK_GET large message chunking (default: enabled) | | `--disable-meas` / `--disable-challenge` | Compile out GET_MEASUREMENTS / CHALLENGE (default: enabled) | | `--disable-heartbeat` / `--disable-key-update` | Compile out HEARTBEAT / KEY_UPDATE (default: enabled) | | `--with-wolfssl=PATH` | wolfSSL installation path | +| `CFLAGS=-DWOLFSPDM_DATA_TRANSFER_SIZE=N` | Largest single SPDM message, 42 to 4096 (default 4096). Smaller values shrink the per-message transport buffers; larger messages then travel in CHUNK_SEND/CHUNK_GET pieces when the responder supports chunking | ### Memory Modes diff --git a/config.h.in b/config.h.in index 8c3343b..c50557d 100644 --- a/config.h.in +++ b/config.h.in @@ -80,6 +80,9 @@ /* Disable CHALLENGE */ #undef WOLFSPDM_NO_CHALLENGE +/* Disable large message chunking */ +#undef WOLFSPDM_NO_CHUNK + /* Disable HEARTBEAT */ #undef WOLFSPDM_NO_HEARTBEAT diff --git a/configure.ac b/configure.ac index 9ba33fe..ba5b045 100644 --- a/configure.ac +++ b/configure.ac @@ -100,6 +100,15 @@ if test "x$enable_heartbeat" = "xno"; then AC_DEFINE([WOLFSPDM_NO_HEARTBEAT], [1], [Disable HEARTBEAT]) fi +AC_ARG_ENABLE([chunking], + [AS_HELP_STRING([--disable-chunking], [Disable CHUNK_SEND/CHUNK_GET large message chunking (default: enabled)])], + [enable_chunking=$enableval], + [enable_chunking=yes]) + +if test "x$enable_chunking" = "xno"; then + AC_DEFINE([WOLFSPDM_NO_CHUNK], [1], [Disable large message chunking]) +fi + AC_ARG_ENABLE([meas], [AS_HELP_STRING([--disable-meas], [Disable GET_MEASUREMENTS attestation (default: enabled)])], [enable_meas=$enableval], @@ -198,6 +207,7 @@ echo " Version: $PACKAGE_VERSION" echo " Debug: $enable_debug" echo " Dynamic mem: $enable_dynamic_mem" echo " Standard: $enable_cert" +echo " Chunking: $enable_chunking" echo " Meas: $enable_meas" echo " Challenge: $enable_challenge" echo " Heartbeat: $enable_heartbeat" diff --git a/examples/spdm_test.sh b/examples/spdm_test.sh index 5291d87..c1d65d2 100755 --- a/examples/spdm_test.sh +++ b/examples/spdm_test.sh @@ -41,6 +41,8 @@ usage() { echo " 1. SPDM_EMU_PATH environment variable" echo " 2. ../spdm-emu/build/bin/ (cloned next to wolfSPDM)" echo " 3. spdm_responder_emu in PATH" + echo "" + echo "SPDM_EMU_ARGS adds responder options, e.g. --cap ...,CHUNK" } # Parse arguments @@ -141,9 +143,11 @@ start_emu() { echo " Run 'make copy_sample_key' in the spdm-emu build directory" fi + # SPDM_EMU_ARGS adds responder options, e.g. --cap ...,CHUNK (cd "$EMU_DIR" && ./spdm_responder_emu --ver "$ver" \ --hash SHA_384 --asym ECDSA_P384 \ - --dhe SECP_384_R1 --aead AES_256_GCM >"$EMU_LOG" 2>&1) & + --dhe SECP_384_R1 --aead AES_256_GCM $SPDM_EMU_ARGS \ + >"$EMU_LOG" 2>&1) & EMU_PID=$! sleep 2 diff --git a/src/spdm_attest.c b/src/spdm_attest.c index 56f3efd..c23d7c9 100644 --- a/src/spdm_attest.c +++ b/src/spdm_attest.c @@ -426,7 +426,7 @@ int wolfSPDM_Challenge(WOLFSPDM_CTX* ctx, int slotId, byte measHashType) rc = wolfSPDM_BuildChallenge(ctx, req, &reqSz, slotId, measHashType); } if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_SendReceive(ctx, req, reqSz, rsp, &rspSz); + rc = wolfSPDM_ClearExchange(ctx, req, reqSz, rsp, &rspSz); } if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_ParseChallengeAuth(ctx, req, reqSz, rsp, rspSz, diff --git a/src/spdm_chunk.c b/src/spdm_chunk.c new file mode 100644 index 0000000..11a6ef7 --- /dev/null +++ b/src/spdm_chunk.c @@ -0,0 +1,327 @@ +/* spdm_chunk.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include "spdm_internal.h" + +#ifndef WOLFSPDM_NO_CHUNK + +/* CHUNK_SEND and CHUNK_RESPONSE: header(4), ChunkSeqNo (u16 + reserved before + * 1.4, u32 from 1.4), ChunkSize(4); the first chunk adds LargeMessageSize(4) */ +#define SPDM_CHUNK_HDR_SZ 12 +#define SPDM_CHUNK_FIRST_HDR_SZ (SPDM_CHUNK_HDR_SZ + 4) + +static word32 wolfSPDM_ChunkSeqSz(const WOLFSPDM_CTX* ctx) +{ + return (ctx->spdmVersion >= SPDM_VERSION_14) ? 4 : 2; +} + +static void wolfSPDM_ChunkSetSeq(const WOLFSPDM_CTX* ctx, byte* p, word32 seq) +{ + if (ctx->spdmVersion >= SPDM_VERSION_14) { + SPDM_Set32LE(p, seq); + } + else { + SPDM_Set16LE(p, (word16)seq); + } +} + +static word32 wolfSPDM_ChunkGetSeq(const WOLFSPDM_CTX* ctx, const byte* p) +{ + if (ctx->spdmVersion >= SPDM_VERSION_14) { + return SPDM_Get32LE(p); + } + return SPDM_Get16LE(p); +} + +/* ChunkSeqNo is 16 bits before 1.4 and shall not wrap */ +static int wolfSPDM_ChunkSeqOk(const WOLFSPDM_CTX* ctx, word32 seq) +{ + return ctx->spdmVersion >= SPDM_VERSION_14 || seq <= 0xFFFF; +} + +/* A response that is an SPDM ERROR; records its code */ +static int wolfSPDM_ChunkPeerError(WOLFSPDM_CTX* ctx, const byte* msg, + word32 msgSz) +{ + if (msgSz >= 4 && msg[1] == SPDM_ERROR) { + ctx->lastPeerErrorCode = msg[2]; + wolfSPDM_DebugPrint(ctx, "CHUNK: SPDM error 0x%02x\n", msg[2]); + return 1; + } + return 0; +} + +static int wolfSPDM_ChunkXfer(WOLFSPDM_CTX* ctx, int secured, + const byte* req, word32 reqSz, byte* rsp, word32* rspSz) +{ + if (secured) { + return wolfSPDM_SecuredXfer(ctx, req, reqSz, rsp, rspSz); + } + return wolfSPDM_SendReceive(ctx, req, reqSz, rsp, rspSz); +} + +static void wolfSPDM_ChunkCopyOut(const byte* src, word32 srcSz, byte* rsp, + word32 cap, word32* rspSz, int* rc) +{ + if (srcSz > cap) { + *rc = WOLFSPDM_E_BUFFER_SMALL; + } + else { + XMEMCPY(rsp, src, srcSz); + *rspSz = srcSz; + } +} + +/* Send a request the responder cannot take in one message; the last + * CHUNK_SEND_ACK carries the response, or an ERROR(LargeResponse) stands in + * for it when the two would not fit together */ +static int wolfSPDM_ChunkSend(WOLFSPDM_CTX* ctx, int secured, word32 limit, + const byte* req, word32 reqSz, byte* rsp, word32* rspSz) +{ + byte msg[WOLFSPDM_DATA_TRANSFER_SIZE]; + byte ack[WOLFSPDM_DATA_TRANSFER_SIZE]; + word32 ackHdr = 4 + wolfSPDM_ChunkSeqSz(ctx); + word32 cap = *rspSz; + word32 sent = 0; + word32 seq = 0; + word32 ackSz; + byte handle = ctx->chunkHandle++; + int done = 0; + int rc = WOLFSPDM_SUCCESS; + + while (rc == WOLFSPDM_SUCCESS && !done) { + word32 hdr = SPDM_CHUNK_HDR_SZ; + word32 take; + + if (!wolfSPDM_ChunkSeqOk(ctx, seq)) { + rc = WOLFSPDM_E_CHUNK; + break; + } + XMEMSET(msg, 0, SPDM_CHUNK_FIRST_HDR_SZ); + msg[0] = ctx->spdmVersion; + msg[1] = SPDM_CHUNK_SEND; + msg[3] = handle; + wolfSPDM_ChunkSetSeq(ctx, &msg[4], seq); + if (seq == 0) { + SPDM_Set32LE(&msg[hdr], reqSz); + hdr += 4; + } + take = limit - hdr; + if (take > reqSz - sent) { + take = reqSz - sent; + } + SPDM_Set32LE(&msg[8], take); + XMEMCPY(&msg[hdr], req + sent, take); + sent += take; + if (sent == reqSz) { + msg[2] = SPDM_CHUNK_LAST_CHUNK; + } + + ackSz = sizeof(ack); + rc = wolfSPDM_ChunkXfer(ctx, secured, msg, hdr + take, ack, &ackSz); + if (rc != WOLFSPDM_SUCCESS) { + break; + } + + if (ackSz >= 5 && ack[0] == ctx->spdmVersion && + ack[1] == SPDM_ERROR && + ack[2] == SPDM_ERROR_LARGE_RESPONSE && sent == reqSz) { + /* Handed back for the CHUNK_GET that follows */ + wolfSPDM_ChunkCopyOut(ack, ackSz, rsp, cap, rspSz, &rc); + done = 1; + } + else if (wolfSPDM_ChunkPeerError(ctx, ack, ackSz)) { + rc = WOLFSPDM_E_PEER_ERROR; + } + else if (ackSz < ackHdr || ack[0] != ctx->spdmVersion || + ack[1] != SPDM_CHUNK_SEND_ACK || ack[3] != handle || + wolfSPDM_ChunkGetSeq(ctx, &ack[4]) != seq) { + rc = WOLFSPDM_E_CHUNK; + } + else if (ack[2] & SPDM_CHUNK_EARLY_ERROR) { + /* The responder rejected the request before the last chunk */ + if (!wolfSPDM_ChunkPeerError(ctx, ack + ackHdr, ackSz - ackHdr)) { + rc = WOLFSPDM_E_CHUNK; + } + else { + rc = WOLFSPDM_E_PEER_ERROR; + } + } + else if (sent < reqSz) { + /* Only the last acknowledgement carries a response */ + if (ackSz != ackHdr) { + rc = WOLFSPDM_E_CHUNK; + } + seq++; + } + else { + wolfSPDM_ChunkCopyOut(ack + ackHdr, ackSz - ackHdr, rsp, cap, + rspSz, &rc); + done = 1; + } + } + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "CHUNK: sent %u bytes in %u chunks\n", reqSz, + seq + 1); + } + + wc_ForceZero(msg, sizeof(msg)); + wc_ForceZero(ack, sizeof(ack)); + return rc; +} + +/* Reassemble a response the responder split after ERROR(LargeResponse) */ +static int wolfSPDM_ChunkGet(WOLFSPDM_CTX* ctx, int secured, byte handle, + byte* rsp, word32 cap, word32* rspSz) +{ + byte req[8]; + byte msg[WOLFSPDM_DATA_TRANSFER_SIZE]; + word32 reqSz = 4 + wolfSPDM_ChunkSeqSz(ctx); + word32 msgSz; + word32 total = 0; + word32 off = 0; + word32 seq = 0; + int last = 0; + int rc = WOLFSPDM_SUCCESS; + + /* No larger than the MaxSPDMmsgSize we advertised */ + if (cap > WOLFSPDM_MAX_MSG_SIZE) { + cap = WOLFSPDM_MAX_MSG_SIZE; + } + req[0] = ctx->spdmVersion; + req[1] = SPDM_CHUNK_GET; + req[2] = 0x00; + req[3] = handle; + + while (rc == WOLFSPDM_SUCCESS && !last) { + word32 hdr = (seq == 0) ? SPDM_CHUNK_FIRST_HDR_SZ : SPDM_CHUNK_HDR_SZ; + word32 size; + + if (!wolfSPDM_ChunkSeqOk(ctx, seq)) { + rc = WOLFSPDM_E_CHUNK; + break; + } + wolfSPDM_ChunkSetSeq(ctx, &req[4], seq); + msgSz = sizeof(msg); + rc = wolfSPDM_ChunkXfer(ctx, secured, req, reqSz, msg, &msgSz); + if (rc != WOLFSPDM_SUCCESS) { + break; + } + if (wolfSPDM_ChunkPeerError(ctx, msg, msgSz)) { + rc = WOLFSPDM_E_PEER_ERROR; + break; + } + + if (msgSz < hdr || msg[0] != ctx->spdmVersion || + msg[1] != SPDM_CHUNK_RESPONSE || msg[3] != handle || + wolfSPDM_ChunkGetSeq(ctx, &msg[4]) != seq) { + rc = WOLFSPDM_E_CHUNK; + break; + } + if (seq == 0) { + total = SPDM_Get32LE(&msg[SPDM_CHUNK_HDR_SZ]); + if (total == 0) { + rc = WOLFSPDM_E_CHUNK; + break; + } + if (total > cap) { + rc = WOLFSPDM_E_BUFFER_SMALL; + break; + } + } + + /* Each CHUNK_RESPONSE is exactly its header and a non-empty chunk, + * and the one flagged last completes the message */ + size = SPDM_Get32LE(&msg[8]); + last = (msg[2] & SPDM_CHUNK_LAST_CHUNK) != 0; + if (size == 0 || size != msgSz - hdr || size > total - off) { + rc = WOLFSPDM_E_CHUNK; + break; + } + XMEMCPY(rsp + off, &msg[hdr], size); + off += size; + if (last != (off == total)) { + rc = WOLFSPDM_E_CHUNK; + break; + } + seq++; + } + + if (rc == WOLFSPDM_SUCCESS) { + *rspSz = total; + wolfSPDM_DebugPrint(ctx, "CHUNK: reassembled %u bytes in %u chunks\n", + total, seq); + } + wc_ForceZero(msg, sizeof(msg)); + return rc; +} + +int wolfSPDM_ChunkExchange(WOLFSPDM_CTX* ctx, int secured, + const byte* req, word32 reqSz, byte* rsp, word32* rspSz) +{ + word32 limit; + word32 cap; + int rc; + + if (ctx == NULL || req == NULL || rsp == NULL || rspSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + cap = *rspSz; + + /* Each message fits both the responder's DataTransferSize and ours */ + limit = ctx->dataTransferSize; + if (limit == 0 || limit > WOLFSPDM_DATA_TRANSFER_SIZE) { + limit = WOLFSPDM_DATA_TRANSFER_SIZE; + } + + if (reqSz <= limit) { + rc = wolfSPDM_ChunkXfer(ctx, secured, req, reqSz, rsp, rspSz); + } + else if (ctx->maxSpdmMsgSize != 0 && reqSz > ctx->maxSpdmMsgSize) { + rc = WOLFSPDM_E_BUFFER_SMALL; + } + else { + rc = wolfSPDM_ChunkSend(ctx, secured, limit, req, reqSz, rsp, rspSz); + } + + /* ERROR(LargeResponse): ExtendedErrorData is the chunk Handle */ + if (rc == WOLFSPDM_SUCCESS && *rspSz >= 5 && + rsp[0] == ctx->spdmVersion && rsp[1] == SPDM_ERROR && + rsp[2] == SPDM_ERROR_LARGE_RESPONSE) { + rc = wolfSPDM_ChunkGet(ctx, secured, rsp[4], rsp, cap, rspSz); + } + return rc; +} + +int wolfSPDM_ClearExchange(WOLFSPDM_CTX* ctx, const byte* req, word32 reqSz, + byte* rsp, word32* rspSz) +{ + if (ctx != NULL && wolfSPDM_ChunkOn(ctx)) { + return wolfSPDM_ChunkExchange(ctx, 0, req, reqSz, rsp, rspSz); + } + return wolfSPDM_SendReceive(ctx, req, reqSz, rsp, rspSz); +} + +#endif /* !WOLFSPDM_NO_CHUNK */ diff --git a/src/spdm_context.c b/src/spdm_context.c index 10cb29a..fbb535f 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -618,6 +618,7 @@ const char* wolfSPDM_GetErrorString(int error) case WOLFSPDM_E_KEY_UPDATE: return "Key update failed"; case WOLFSPDM_E_MEASUREMENT: return "Measurement response invalid"; case WOLFSPDM_E_CHALLENGE: return "Challenge response invalid"; + case WOLFSPDM_E_CHUNK: return "Chunked transfer failed"; default: return "Unknown error"; } } diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 09c4d67..315cd9d 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -193,6 +193,9 @@ struct WOLFSPDM_CTX { word32 trustedCASz; byte slotMask; byte currentSlotId; +#ifndef WOLFSPDM_NO_CHUNK + byte chunkHandle; /* next CHUNK_SEND handle */ +#endif byte certChain[WOLFSPDM_MAX_CERT_CHAIN]; byte trustedCA[WOLFSPDM_MAX_TRUSTED_CA]; #endif @@ -233,6 +236,15 @@ static WC_INLINE int wolfSPDM_IsTcgMode(const WOLFSPDM_CTX* ctx) ctx->mode == WOLFSPDM_MODE_NATIONS_PSK; } +#ifndef WOLFSPDM_NO_CHUNK +/* Chunking applies once both sides set CHUNK_CAP; TCG profiles never chunk */ +static WC_INLINE int wolfSPDM_ChunkOn(const WOLFSPDM_CTX* ctx) +{ + return !wolfSPDM_IsTcgMode(ctx) && + (ctx->rspCaps & SPDM_CAP_CHUNK_CAP) != 0; +} +#endif + /* ----- Byte-Order Helpers ----- */ static WC_INLINE void SPDM_Set16LE(byte* buf, word16 val) { @@ -477,6 +489,20 @@ WOLFSPDM_TEST_API int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, WOLFSPDM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); +/* One secured message each way, never chunked */ +WOLFSPDM_LOCAL int wolfSPDM_SecuredXfer(WOLFSPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz); + +#ifndef WOLFSPDM_NO_CHUNK +/* Request/response that chunks with CHUNK_SEND and CHUNK_GET as needed */ +WOLFSPDM_TEST_API int wolfSPDM_ChunkExchange(WOLFSPDM_CTX* ctx, int secured, + const byte* req, word32 reqSz, byte* rsp, word32* rspSz); +WOLFSPDM_LOCAL int wolfSPDM_ClearExchange(WOLFSPDM_CTX* ctx, + const byte* req, word32 reqSz, byte* rsp, word32* rspSz); +#else +#define wolfSPDM_ClearExchange(ctx, req, reqSz, rsp, rspSz) \ + wolfSPDM_SendReceive(ctx, req, reqSz, rsp, rspSz) +#endif #ifdef WOLFSPDM_DEBUG WOLFSPDM_API void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) diff --git a/src/spdm_secured.c b/src/spdm_secured.c index fc03e63..e23fef1 100644 --- a/src/spdm_secured.c +++ b/src/spdm_secured.c @@ -47,7 +47,7 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, Aes aes; byte iv[WOLFSPDM_AEAD_IV_SIZE]; byte aad[16]; /* Up to 14 bytes for TCG format */ - byte plainBuf[WOLFSPDM_MAX_MSG_SIZE + 16]; + byte plainBuf[WOLFSPDM_XFER_MSG_SIZE + 16]; byte tag[WOLFSPDM_AEAD_TAG_SIZE]; word32 plainBufSz; word16 recordLen; @@ -59,7 +59,7 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, if (ctx == NULL || plain == NULL || enc == NULL || encSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } - if (plainSz > WOLFSPDM_MAX_MSG_SIZE) { + if (plainSz > WOLFSPDM_XFER_MSG_SIZE) { return WOLFSPDM_E_BUFFER_SMALL; } @@ -180,7 +180,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, Aes aes; byte iv[WOLFSPDM_AEAD_IV_SIZE]; byte aad[16]; - byte decrypted[WOLFSPDM_MAX_MSG_SIZE + 16]; + byte decrypted[WOLFSPDM_XFER_MSG_SIZE + WOLFSPDM_SECURED_PAD]; const byte* ciphertext; const byte* tag; word32 cipherLen; @@ -346,16 +346,31 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, return ret; } -int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, +int wolfSPDM_SecuredXfer(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz) { - byte encBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; - byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte encBuf[WOLFSPDM_XFER_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte rxBuf[WOLFSPDM_XFER_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; word32 encSz = sizeof(encBuf); word32 rxSz = sizeof(rxBuf); int rc; + rc = wolfSPDM_EncryptInternal(ctx, cmdPlain, cmdSz, encBuf, &encSz); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); + } + + return rc; +} + +int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz) +{ if (ctx == NULL || cmdPlain == NULL || rspPlain == NULL || rspSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } @@ -365,15 +380,13 @@ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, ctx->l1l2State = WOLFSPDM_RUN_LIVE; } #endif - - rc = wolfSPDM_EncryptInternal(ctx, cmdPlain, cmdSz, encBuf, &encSz); - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); - } - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); +#ifndef WOLFSPDM_NO_CHUNK + if (wolfSPDM_ChunkOn(ctx)) { + return wolfSPDM_ChunkExchange(ctx, 1, cmdPlain, cmdSz, rspPlain, + rspSz); } +#endif - return rc; + return wolfSPDM_SecuredXfer(ctx, cmdPlain, cmdSz, rspPlain, rspSz); } diff --git a/src/spdm_session.c b/src/spdm_session.c index 461c463..3517441 100644 --- a/src/spdm_session.c +++ b/src/spdm_session.c @@ -39,7 +39,7 @@ int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); } if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + rc = wolfSPDM_ClearExchange(ctx, txBuf, txSz, rxBuf, &rxSz); } if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); @@ -87,7 +87,7 @@ int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); } if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + rc = wolfSPDM_ClearExchange(ctx, txBuf, txSz, rxBuf, &rxSz); if (rc != WOLFSPDM_SUCCESS) { wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE: SendReceive failed: %d\n", rc); } @@ -100,31 +100,24 @@ int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) return rc; } -int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) +/* FINISH must be sent encrypted (HANDSHAKE_IN_THE_CLEAR not negotiated) */ +static int wolfSPDM_FinishXfer(WOLFSPDM_CTX* ctx, const byte* finishBuf, + word32 finishSz, byte* decBuf, word32* decSz) { - byte finishBuf[WOLFSPDM_FINISH_BUF_SZ]; byte encBuf[WOLFSPDM_VENDOR_BUF_SZ]; byte rxBuf[128]; /* Encrypted FINISH_RSP: ~94 bytes max */ - byte decBuf[64]; /* Decrypted FINISH_RSP: 4 hdr + 48 verify = 52 */ - word32 finishSz = sizeof(finishBuf); word32 encSz = sizeof(encBuf); word32 rxSz = sizeof(rxBuf); - word32 decSz = sizeof(decBuf); int rc; - /* FINISH is only valid after a successful KEY_EXCHANGE; otherwise the - * session keys are unestablished (zero-entropy). */ - if (ctx == NULL || ctx->state < WOLFSPDM_STATE_KEY_EX) { - return WOLFSPDM_E_BAD_STATE; +#ifndef WOLFSPDM_NO_CHUNK + if (wolfSPDM_ChunkOn(ctx)) { + return wolfSPDM_ChunkExchange(ctx, 1, finishBuf, finishSz, decBuf, + decSz); } +#endif - rc = wolfSPDM_BuildFinish(ctx, finishBuf, &finishSz); - - /* FINISH must be sent encrypted (HANDSHAKE_IN_THE_CLEAR not negotiated) */ - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_EncryptInternal(ctx, finishBuf, finishSz, encBuf, - &encSz); - } + rc = wolfSPDM_EncryptInternal(ctx, finishBuf, finishSz, encBuf, &encSz); if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); } @@ -142,7 +135,29 @@ int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) } if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, decSz); + } + + return rc; +} + +int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) +{ + byte finishBuf[WOLFSPDM_FINISH_BUF_SZ]; + byte decBuf[64]; /* Decrypted FINISH_RSP: 4 hdr + 48 verify = 52 */ + word32 finishSz = sizeof(finishBuf); + word32 decSz = sizeof(decBuf); + int rc; + + /* FINISH is only valid after a successful KEY_EXCHANGE; otherwise the + * session keys are unestablished (zero-entropy). */ + if (ctx == NULL || ctx->state < WOLFSPDM_STATE_KEY_EX) { + return WOLFSPDM_E_BAD_STATE; + } + + rc = wolfSPDM_BuildFinish(ctx, finishBuf, &finishSz); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_FinishXfer(ctx, finishBuf, finishSz, decBuf, &decSz); } if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_ParseFinishRsp(ctx, decBuf, decSz); diff --git a/src/spdm_standard.c b/src/spdm_standard.c index fbb884a..d085f0e 100644 --- a/src/spdm_standard.c +++ b/src/spdm_standard.c @@ -29,8 +29,13 @@ #include -/* Largest certificate portion requested per GET_CERTIFICATE */ -#define WOLFSPDM_CERT_PORTION_SZ 1024 +/* Largest certificate portion requested per GET_CERTIFICATE; the response + * fits our DataTransferSize so certificates are never chunked */ +#if WOLFSPDM_DATA_TRANSFER_SIZE < 1024 + 8 + #define WOLFSPDM_CERT_PORTION_SZ (WOLFSPDM_DATA_TRANSFER_SIZE - 8) +#else + #define WOLFSPDM_CERT_PORTION_SZ 1024 +#endif /* ----- VCA: GET_CAPABILITIES / NEGOTIATE_ALGORITHMS ----- */ @@ -42,7 +47,7 @@ int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) buf[0] = ctx->spdmVersion; buf[1] = SPDM_GET_CAPABILITIES; SPDM_Set32LE(&buf[8], WOLFSPDM_REQ_CAPS); - SPDM_Set32LE(&buf[12], WOLFSPDM_MAX_MSG_SIZE); /* DataTransferSize */ + SPDM_Set32LE(&buf[12], WOLFSPDM_DATA_TRANSFER_SIZE); SPDM_Set32LE(&buf[16], WOLFSPDM_MAX_MSG_SIZE); /* MaxSPDMmsgSize */ *bufSz = 20; @@ -234,7 +239,7 @@ int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx) txBuf[2] = 0x00; txBuf[3] = 0x00; - rc = wolfSPDM_SendReceive(ctx, txBuf, sizeof(txBuf), rxBuf, &rxSz); + rc = wolfSPDM_ClearExchange(ctx, txBuf, sizeof(txBuf), rxBuf, &rxSz); if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_ParseDigests(ctx, rxBuf, rxSz); } @@ -306,7 +311,7 @@ int wolfSPDM_GetCertificate(WOLFSPDM_CTX* ctx, int slotId) SPDM_Set16LE(&txBuf[6], reqLen); rxSz = sizeof(rxBuf); - rc = wolfSPDM_SendReceive(ctx, txBuf, sizeof(txBuf), rxBuf, &rxSz); + rc = wolfSPDM_ClearExchange(ctx, txBuf, sizeof(txBuf), rxBuf, &rxSz); if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_ParseCertificate(ctx, rxBuf, rxSz, &portionLen, &remainderLen); @@ -580,6 +585,8 @@ int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx) } ctx->state = WOLFSPDM_STATE_INIT; ctx->lastPeerErrorCode = 0; + /* No chunking or other peer capability until CAPABILITIES says so */ + ctx->rspCaps = 0; wolfSPDM_TranscriptReset(ctx); #if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) wolfSPDM_AttestFree(ctx); diff --git a/test/unit_test.c b/test/unit_test.c index 9d7f42f..63c5f32 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1145,8 +1145,9 @@ static int test_nations_psk_message_format(void) static int test_decrypt_overflow(void) { /* Static to avoid 4KB+ on stack; cipherLen must exceed - * sizeof(decrypted) = WOLFSPDM_MAX_MSG_SIZE + 16 = 4112 */ - static byte enc[4140]; + * sizeof(decrypted) = WOLFSPDM_XFER_MSG_SIZE + WOLFSPDM_SECURED_PAD */ + static byte enc[8 + WOLFSPDM_XFER_MSG_SIZE + WOLFSPDM_SECURED_PAD + 2 + + WOLFSPDM_AEAD_TAG_SIZE]; byte plain[64]; word32 plainSz = sizeof(plain); int rc; @@ -1159,13 +1160,13 @@ static int test_decrypt_overflow(void) memset(ctx->rspDataKey, 0x42, sizeof(ctx->rspDataKey)); memset(ctx->rspDataIv, 0x42, sizeof(ctx->rspDataIv)); - /* MCTP header: rspLen=4130 -> cipherLen=4114 > 4112 = overflow guard */ + /* MCTP header: cipherLen two bytes past the decrypt buffer */ memset(enc, 0, sizeof(enc)); SPDM_Set32LE(&enc[0], ctx->sessionId); SPDM_Set16LE(&enc[4], 0x0000); - SPDM_Set16LE(&enc[6], 4130); + SPDM_Set16LE(&enc[6], (word16)(sizeof(enc) - 8)); - rc = wolfSPDM_DecryptInternal(ctx, enc, 4138, plain, &plainSz); + rc = wolfSPDM_DecryptInternal(ctx, enc, sizeof(enc), plain, &plainSz); ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, "Overflow cipherLen must be caught"); TEST_CTX_FREE(); @@ -2245,6 +2246,52 @@ static int test_build_key_exchange_mode_opaque(void) TEST_PASS(); } +/* libspdm pads MCTP records with up to 32 random bytes; a full-size message + * with the most padding must still decrypt */ +static int test_decrypt_mctp_random_padding(void) +{ + static byte inner[3 + WOLFSPDM_XFER_MSG_SIZE + 32]; + static byte rec[8 + sizeof(inner) + WOLFSPDM_AEAD_TAG_SIZE]; + static byte dec[WOLFSPDM_XFER_MSG_SIZE]; + Aes aes; + byte iv[WOLFSPDM_AEAD_IV_SIZE]; + word32 decSz = sizeof(dec); + word32 i; + TEST_CTX_SETUP_V12(); + printf("test_decrypt_mctp_random_padding...\n"); + + ctx->sessionId = 0x11223344; + XMEMSET(ctx->rspDataKey, 0x33, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataIv, 0x44, WOLFSPDM_AEAD_IV_SIZE); + + for (i = 0; i < sizeof(inner); i++) { + inner[i] = (byte)(i * 3); + } + SPDM_Set16LE(inner, (word16)(1 + WOLFSPDM_XFER_MSG_SIZE)); + inner[2] = MCTP_MESSAGE_TYPE_SPDM; + inner[3] = SPDM_VERSION_12; + SPDM_Set32LE(&rec[0], ctx->sessionId); + SPDM_Set16LE(&rec[4], 0); + SPDM_Set16LE(&rec[6], (word16)(sizeof(inner) + WOLFSPDM_AEAD_TAG_SIZE)); + wolfSPDM_BuildIV(iv, ctx->rspDataIv, 0); + + ASSERT_SUCCESS(wc_AesInit(&aes, NULL, INVALID_DEVID)); + ASSERT_SUCCESS(wc_AesGcmSetKey(&aes, ctx->rspDataKey, + WOLFSPDM_AEAD_KEY_SIZE)); + ASSERT_SUCCESS(wc_AesGcmEncrypt(&aes, &rec[8], inner, sizeof(inner), + iv, sizeof(iv), &rec[8 + sizeof(inner)], WOLFSPDM_AEAD_TAG_SIZE, + rec, 8)); + wc_AesFree(&aes); + + ASSERT_SUCCESS(wolfSPDM_DecryptInternal(ctx, rec, sizeof(rec), dec, + &decSz)); + ASSERT_EQ(decSz, WOLFSPDM_XFER_MSG_SIZE, "whole message recovered"); + ASSERT_EQ(memcmp(dec, inner + 3, decSz), 0, "message matches"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + static int test_decrypt_rejects_wrong_mctp_type(void) { /* An authenticated record whose inner MCTP type is not SPDM must be @@ -3209,7 +3256,7 @@ static int test_derive_updated_keys(void) #endif /* !WOLFSPDM_NO_KEY_UPDATE */ #if !defined(WOLFSPDM_NO_KEY_UPDATE) || !defined(WOLFSPDM_NO_MEAS) || \ - !defined(WOLFSPDM_NO_CHALLENGE) + !defined(WOLFSPDM_NO_CHALLENGE) || !defined(WOLFSPDM_NO_CHUNK) /* Loopback responder: a mirrored context that answers requests */ static WOLFSPDM_CTX g_peer; static int g_peerRejects; @@ -3379,29 +3426,14 @@ static word32 test_chal_rsp(const WOLFSPDM_CTX* ctx, const byte* req, } #endif -static int test_peer_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz, void* userCtx) +/* The peer's answer to one complete request */ +static int test_peer_dispatch(WOLFSPDM_CTX* ctx, WOLFSPDM_CTX* p, + const byte* req, word32 reqSz, byte* rsp, word32* rspSz) { - WOLFSPDM_CTX* p = (WOLFSPDM_CTX*)userCtx; - byte req[64]; - byte rsp[512]; - word32 reqSz = sizeof(req); - word32 rspSz = 4; - int clear = (txSz > 0 && txBuf[0] >= 0x10 && txBuf[0] <= 0x1F); int rc = 0; (void)ctx; - if (clear && txSz <= sizeof(req)) { - XMEMCPY(req, txBuf, txSz); - reqSz = txSz; - } - else if (clear || - wolfSPDM_DecryptInternal(p, txBuf, txSz, req, &reqSz) != 0) { - return -1; - } - if (reqSz < 4) { - return -1; - } + (void)p; #ifndef WOLFSPDM_NO_MEAS if (req[1] != SPDM_GET_MEASUREMENTS) { test_peer_run_reset(); @@ -3412,9 +3444,16 @@ static int test_peer_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, rsp[1] = SPDM_ERROR; rsp[2] = SPDM_ERROR_UNSUPPORTED_REQUEST; rsp[3] = 0; + *rspSz = 4; if (g_peerRejects) { rsp[2] = SPDM_ERROR_BUSY; } + else if (req[1] == SPDM_VENDOR_DEFINED_REQUEST) { + /* Echo sized by the request, to drive chunking */ + rsp[1] = 0x7E; + XMEMCPY(rsp + 2, req + 2, reqSz - 2); + *rspSz = reqSz; + } #ifndef WOLFSPDM_NO_KEY_UPDATE else if (req[1] == SPDM_KEY_UPDATE) { rsp[1] = SPDM_KEY_UPDATE_ACK; @@ -3431,13 +3470,13 @@ static int test_peer_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, #endif #ifndef WOLFSPDM_NO_MEAS else if (req[1] == SPDM_GET_MEASUREMENTS) { - rspSz = test_meas_rsp(req, reqSz, rsp); + *rspSz = test_meas_rsp(req, reqSz, rsp); if (req[2] & SPDM_MEAS_REQUEST_SIG_BIT) { rc = test_peer_sign("responder-measurements signing", 30, - req, reqSz, rsp, &rspSz); + req, reqSz, rsp, rspSz); } else { - rc = test_peer_run_add(req, reqSz, rsp, rspSz); + rc = test_peer_run_add(req, reqSz, rsp, *rspSz); } } #endif @@ -3445,10 +3484,249 @@ static int test_peer_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, else if (req[1] == SPDM_CHALLENGE) { /* No DIGESTS/CERTIFICATE crossed the loopback, so M1 is VCA + C */ test_peer_run_reset(); - rspSz = test_chal_rsp(ctx, req, reqSz, rsp); + *rspSz = test_chal_rsp(ctx, req, reqSz, rsp); rc = test_peer_sign("responder-challenge_auth signing", 32, - req, reqSz, rsp, &rspSz); + req, reqSz, rsp, rspSz); + } +#endif + return rc; +} + +#ifndef WOLFSPDM_NO_CHUNK +/* Responder side of CHUNK_SEND and CHUNK_GET, with injectable faults */ +enum { + PEER_FAULT_NONE = 0, + PEER_FAULT_GET_HANDLE, + PEER_FAULT_GET_SEQ, + PEER_FAULT_GET_SIZE, + PEER_FAULT_GET_NO_LAST, + PEER_FAULT_GET_EARLY_LAST, + PEER_FAULT_GET_ERROR, + PEER_FAULT_GET_TOO_BIG, + PEER_FAULT_ACK_SEQ, + PEER_FAULT_ACK_EARLY_ERROR, + PEER_FAULT_ACK_EXTRA +}; +static byte g_peerLarge[1024]; /* response served by CHUNK_GET */ +static word32 g_peerLargeSz; +static word32 g_peerLargeOff; +static word32 g_peerGetSeq; +static byte g_peerGetHandle; +static byte g_peerReq[1024]; /* request reassembled from CHUNK_SEND */ +static word32 g_peerReqSz; +static word32 g_peerSendSeq; +static word32 g_peerDts; /* the peer's DataTransferSize */ +static word32 g_peerChunkMax; /* nonzero: always CHUNK_GET, this big */ +static int g_peerFault; + +static void test_peer_chunk_reset(void) +{ + g_peerLargeSz = 0; + g_peerLargeOff = 0; + g_peerGetSeq = 0; + g_peerReqSz = 0; + g_peerSendSeq = 0; + g_peerDts = sizeof(g_peerReq); + g_peerChunkMax = 0; + g_peerFault = PEER_FAULT_NONE; +} + +static word32 test_seq_sz(const byte* msg) +{ + return (msg[0] >= SPDM_VERSION_14) ? 4 : 2; +} + +static word32 test_get_seq(const byte* msg) +{ + return (test_seq_sz(msg) == 4) ? SPDM_Get32LE(msg + 4) : + SPDM_Get16LE(msg + 4); +} + +static void test_set_seq(byte* msg, word32 seq) +{ + if (test_seq_sz(msg) == 4) { + SPDM_Set32LE(msg + 4, seq); + } + else { + SPDM_Set16LE(msg + 4, (word16)seq); + } +} + +static int test_peer_chunk_get(const byte* req, byte* rsp, word32* rspSz) +{ + word32 seq = test_get_seq(req); + word32 hdr = (seq == 0) ? 16 : 12; + word32 room = WOLFSPDM_DATA_TRANSFER_SIZE - hdr; + word32 size = g_peerLargeSz - g_peerLargeOff; + + if (req[3] != g_peerGetHandle || seq != g_peerGetSeq || + g_peerLargeOff >= g_peerLargeSz) { + return -1; + } + if (g_peerFault == PEER_FAULT_GET_ERROR && seq == 1) { + rsp[0] = req[0]; + rsp[1] = SPDM_ERROR; + rsp[2] = SPDM_ERROR_UNSPECIFIED; + rsp[3] = 0; + *rspSz = 4; + return 0; + } + /* Never past the requester's DataTransferSize */ + if (g_peerChunkMax != 0 && g_peerChunkMax < room) { + room = g_peerChunkMax; + } + if (size > room) { + size = room; + } + XMEMSET(rsp, 0, hdr); + rsp[0] = req[0]; + rsp[1] = SPDM_CHUNK_RESPONSE; + rsp[3] = g_peerGetHandle; + test_set_seq(rsp, seq); + SPDM_Set32LE(rsp + 8, size); + if (seq == 0) { + SPDM_Set32LE(rsp + 12, g_peerLargeSz); + } + XMEMCPY(rsp + hdr, g_peerLarge + g_peerLargeOff, size); + g_peerLargeOff += size; + g_peerGetSeq++; + if (g_peerLargeOff == g_peerLargeSz) { + rsp[2] = SPDM_CHUNK_LAST_CHUNK; + } + *rspSz = hdr + size; + + if (g_peerFault == PEER_FAULT_GET_HANDLE) { + rsp[3] ^= 0x01; + } + else if (g_peerFault == PEER_FAULT_GET_SEQ) { + test_set_seq(rsp, seq + 1); + } + else if (g_peerFault == PEER_FAULT_GET_SIZE) { + SPDM_Set32LE(rsp + 8, size + 1); + } + else if (g_peerFault == PEER_FAULT_GET_NO_LAST) { + rsp[2] = 0; + } + else if (g_peerFault == PEER_FAULT_GET_EARLY_LAST) { + rsp[2] = SPDM_CHUNK_LAST_CHUNK; + } + else if (g_peerFault == PEER_FAULT_GET_TOO_BIG && seq == 0) { + SPDM_Set32LE(rsp + 12, WOLFSPDM_MAX_MSG_SIZE + 1); + } + return 0; +} + +/* Take one request message: collect CHUNK_SEND pieces, serve CHUNK_GET, and + * park responses too large for the requester behind ERROR(LargeResponse) */ +static int test_peer_chunk(WOLFSPDM_CTX* ctx, WOLFSPDM_CTX* p, + const byte* req, word32 reqSz, byte* rsp, word32* rspSz) +{ + byte out[1024]; + word32 outSz = 0; + word32 prefix = 0; + int rc; + + if (req[1] == SPDM_CHUNK_GET) { + return test_peer_chunk_get(req, rsp, rspSz); + } + if (req[1] == SPDM_CHUNK_SEND) { + word32 seq = test_get_seq(req); + word32 hdr = (seq == 0) ? 16 : 12; + word32 size = SPDM_Get32LE(req + 8); + + if (reqSz > g_peerDts || seq != g_peerSendSeq || hdr + size != reqSz || + g_peerReqSz + size > sizeof(g_peerReq)) { + return -1; + } + XMEMCPY(g_peerReq + g_peerReqSz, req + hdr, size); + g_peerReqSz += size; + g_peerSendSeq++; + + rsp[0] = req[0]; + rsp[1] = SPDM_CHUNK_SEND_ACK; + rsp[2] = 0; + rsp[3] = req[3]; + test_set_seq(rsp, (g_peerFault == PEER_FAULT_ACK_SEQ) ? seq + 1 : seq); + prefix = 4 + test_seq_sz(req); + if (g_peerFault == PEER_FAULT_ACK_EARLY_ERROR) { + rsp[2] = SPDM_CHUNK_EARLY_ERROR; + rsp[prefix] = req[0]; + rsp[prefix + 1] = SPDM_ERROR; + rsp[prefix + 2] = SPDM_ERROR_INVALID_REQUEST; + rsp[prefix + 3] = 0; + *rspSz = prefix + 4; + return 0; + } + if ((req[2] & SPDM_CHUNK_LAST_CHUNK) == 0) { + *rspSz = prefix; + if (g_peerFault == PEER_FAULT_ACK_EXTRA) { + rsp[prefix] = 0; + (*rspSz)++; + } + return 0; + } + /* Last piece: answer the reassembled request */ + req = g_peerReq; + reqSz = g_peerReqSz; + g_peerReqSz = 0; + g_peerSendSeq = 0; + } + + rc = test_peer_dispatch(ctx, p, req, reqSz, out, &outSz); + if (rc != 0) { + return rc; + } + if (g_peerChunkMax == 0 && prefix + outSz <= WOLFSPDM_DATA_TRANSFER_SIZE) { + XMEMCPY(rsp + prefix, out, outSz); + *rspSz = prefix + outSz; + return 0; + } + rsp[0] = out[0]; + rsp[1] = SPDM_ERROR; + rsp[3] = 0; + if ((ctx->rspCaps & SPDM_CAP_CHUNK_CAP) == 0) { + rsp[2] = SPDM_ERROR_RESPONSE_TOO_LARGE; + *rspSz = 4; + return 0; + } + XMEMCPY(g_peerLarge, out, outSz); + g_peerLargeSz = outSz; + g_peerLargeOff = 0; + g_peerGetSeq = 0; + g_peerGetHandle++; + rsp[2] = SPDM_ERROR_LARGE_RESPONSE; + rsp[4] = g_peerGetHandle; + *rspSz = 5; + return 0; +} +#endif /* !WOLFSPDM_NO_CHUNK */ + +static int test_peer_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, void* userCtx) +{ + WOLFSPDM_CTX* p = (WOLFSPDM_CTX*)userCtx; + byte req[1024]; + byte rsp[1100]; + word32 reqSz = sizeof(req); + word32 rspSz = 0; + int clear = (txSz > 0 && txBuf[0] >= 0x10 && txBuf[0] <= 0x1F); + int rc; + + if (clear && txSz <= sizeof(req)) { + XMEMCPY(req, txBuf, txSz); + reqSz = txSz; + } + else if (clear || + wolfSPDM_DecryptInternal(p, txBuf, txSz, req, &reqSz) != 0) { + return -1; } + if (reqSz < 4) { + return -1; + } +#ifndef WOLFSPDM_NO_CHUNK + rc = test_peer_chunk(ctx, p, req, reqSz, rsp, &rspSz); +#else + rc = test_peer_dispatch(ctx, p, req, reqSz, rsp, &rspSz); #endif if (rc != 0) { return -1; @@ -3512,6 +3790,13 @@ static void test_session_loopback(WOLFSPDM_CTX* ctx) #endif #ifndef WOLFSPDM_NO_CHALLENGE ctx->rspCaps |= SPDM_CAP_CHAL_CAP; +#endif +#ifndef WOLFSPDM_NO_CHUNK + /* Chunk whenever a response outgrows our DataTransferSize */ + ctx->rspCaps |= SPDM_CAP_CHUNK_CAP; + ctx->dataTransferSize = 0; + ctx->maxSpdmMsgSize = 0; + test_peer_chunk_reset(); #endif g_peerRejects = 0; wolfSPDM_SetIO(ctx, test_peer_io_cb, p); @@ -3789,6 +4074,136 @@ static int test_challenge_loopback(void) } #endif /* !WOLFSPDM_NO_CHALLENGE */ +#ifndef WOLFSPDM_NO_CHUNK +/* Send a len-byte VENDOR_DEFINED request the peer echoes; -100 on a bad echo */ +static int test_chunk_echo(WOLFSPDM_CTX* ctx, int secured, word32 len) +{ + byte req[600]; + byte rsp[700]; + word32 rspSz = sizeof(rsp); + word32 i; + int rc; + + req[0] = ctx->spdmVersion; + req[1] = SPDM_VENDOR_DEFINED_REQUEST; + for (i = 2; i < len; i++) { + req[i] = (byte)(i * 7); + } + if (secured) { + rc = wolfSPDM_SecuredExchange(ctx, req, len, rsp, &rspSz); + } + else { + rc = wolfSPDM_ClearExchange(ctx, req, len, rsp, &rspSz); + } + if (rc == 0 && (rspSz != len || rsp[1] != 0x7E || + memcmp(rsp + 2, req + 2, len - 2) != 0)) { + rc = -100; + } + return rc; +} + +static int test_chunk_fault(WOLFSPDM_CTX* ctx, int fault) +{ + int rc; + + test_peer_chunk_reset(); + g_peerDts = 48; + g_peerChunkMax = 40; + g_peerFault = fault; + rc = test_chunk_echo(ctx, 0, 300); + test_peer_chunk_reset(); + g_peerDts = 48; + return rc; +} + +static int test_chunk_transfers(void) +{ + byte req[300]; + byte rsp[64]; + word32 rspSz = sizeof(rsp); + TEST_CTX_SETUP(); + + printf("test_chunk_transfers...\n"); + test_session_loopback(ctx); + ctx->dataTransferSize = 48; + ctx->maxSpdmMsgSize = 4096; + g_peerDts = 48; + + /* CHUNK_SEND, response in the last CHUNK_SEND_ACK when it fits */ + ASSERT_SUCCESS(test_chunk_echo(ctx, 0, 300)); + ASSERT_SUCCESS(test_chunk_echo(ctx, 1, 300)); + ASSERT_SUCCESS(test_chunk_echo(ctx, 1, 48)); + + /* Responses fetched with CHUNK_GET, after CHUNK_SEND or on their own */ + g_peerChunkMax = 40; + ASSERT_SUCCESS(test_chunk_echo(ctx, 0, 300)); + ASSERT_SUCCESS(test_chunk_echo(ctx, 1, 300)); + ASSERT_SUCCESS(test_chunk_echo(ctx, 0, 20)); + ASSERT_SUCCESS(test_chunk_echo(ctx, 1, 20)); + + /* 1.4 carries 32-bit chunk sequence numbers */ + ctx->spdmVersion = SPDM_VERSION_14; + g_peer.spdmVersion = SPDM_VERSION_14; + ASSERT_SUCCESS(test_chunk_echo(ctx, 0, 300)); + ASSERT_SUCCESS(test_chunk_echo(ctx, 1, 300)); + ctx->spdmVersion = SPDM_VERSION_12; + g_peer.spdmVersion = SPDM_VERSION_12; + + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_GET_HANDLE), WOLFSPDM_E_CHUNK, + "CHUNK_RESPONSE with another handle"); + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_GET_SEQ), WOLFSPDM_E_CHUNK, + "CHUNK_RESPONSE out of sequence"); + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_GET_SIZE), WOLFSPDM_E_CHUNK, + "ChunkSize past the message"); + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_GET_NO_LAST), WOLFSPDM_E_CHUNK, + "Complete message without LastChunk"); + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_GET_EARLY_LAST), + WOLFSPDM_E_CHUNK, "LastChunk before the message is complete"); + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_GET_ERROR), + WOLFSPDM_E_PEER_ERROR, "ERROR during CHUNK_GET"); + ASSERT_EQ(wolfSPDM_GetLastPeerError(ctx), SPDM_ERROR_UNSPECIFIED, + "ERROR code recorded"); + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_GET_TOO_BIG), + WOLFSPDM_E_BUFFER_SMALL, "LargeMessageSize above MaxSPDMmsgSize"); + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_ACK_SEQ), WOLFSPDM_E_CHUNK, + "CHUNK_SEND_ACK out of sequence"); + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_ACK_EARLY_ERROR), + WOLFSPDM_E_PEER_ERROR, "Early error in CHUNK_SEND_ACK"); + ASSERT_EQ(wolfSPDM_GetLastPeerError(ctx), SPDM_ERROR_INVALID_REQUEST, + "Early error code recorded"); + ASSERT_EQ(test_chunk_fault(ctx, PEER_FAULT_ACK_EXTRA), WOLFSPDM_E_CHUNK, + "Response data in a non-final CHUNK_SEND_ACK"); + + /* The reassembled response must fit the caller */ + g_peerChunkMax = 40; + XMEMSET(req, 0x33, sizeof(req)); + req[0] = ctx->spdmVersion; + req[1] = SPDM_VENDOR_DEFINED_REQUEST; + ASSERT_EQ(wolfSPDM_ChunkExchange(ctx, 0, req, sizeof(req), rsp, &rspSz), + WOLFSPDM_E_BUFFER_SMALL, "Response larger than the caller buffer"); + test_peer_chunk_reset(); + g_peerDts = 48; + + /* Requests above the responder's MaxSPDMmsgSize are not sent */ + ctx->maxSpdmMsgSize = 100; + ASSERT_EQ(test_chunk_echo(ctx, 0, 300), WOLFSPDM_E_BUFFER_SMALL, + "Request above MaxSPDMmsgSize"); + ctx->maxSpdmMsgSize = 4096; + +#if WOLFSPDM_DATA_TRANSFER_SIZE >= 300 + /* Without CHUNK_CAP every message goes whole */ + ctx->rspCaps &= ~(word32)SPDM_CAP_CHUNK_CAP; + g_peerDts = sizeof(g_peerReq); + ASSERT_SUCCESS(test_chunk_echo(ctx, 0, 300)); + ASSERT_SUCCESS(test_chunk_echo(ctx, 1, 300)); +#endif + + wolfSPDM_Free(&g_peer); + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSPDM_NO_CHUNK */ + int main(void) { printf("===========================================\n"); @@ -3921,6 +4336,7 @@ int main(void) test_encrypt_internal_null_args(); test_encrypt_decrypt_roundtrip(); test_decrypt_rejects_wrong_mctp_type(); + test_decrypt_mctp_random_padding(); #ifndef WOLFSPDM_NO_CERT test_parse_capabilities(); test_negotiate_algorithms_roundtrip(); @@ -3947,6 +4363,9 @@ int main(void) test_challenge_msgs(); test_challenge_loopback(); #endif +#ifndef WOLFSPDM_NO_CHUNK + test_chunk_transfers(); +#endif #ifdef WOLFSPDM_RESPONDER test_responder_init_free(); diff --git a/wolfspdm/spdm_error.h b/wolfspdm/spdm_error.h index e0f6ef5..c43281c 100644 --- a/wolfspdm/spdm_error.h +++ b/wolfspdm/spdm_error.h @@ -60,6 +60,7 @@ enum WOLFSPDM_ERROR { WOLFSPDM_E_KEY_UPDATE = -25, /* Key update failed */ WOLFSPDM_E_MEASUREMENT = -26, /* Malformed MEASUREMENTS response */ WOLFSPDM_E_CHALLENGE = -27, /* Malformed or mismatched CHALLENGE_AUTH */ + WOLFSPDM_E_CHUNK = -28, /* Chunked transfer failed */ }; /* Get human-readable error string */ diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 73eea10..82fbda8 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -127,7 +127,9 @@ extern "C" { #define WOLFSPDM_AEAD_KEY_SIZE 32 /* AES-256 key size */ #define WOLFSPDM_AEAD_IV_SIZE 12 /* AES-GCM IV size */ #define WOLFSPDM_AEAD_TAG_SIZE 16 /* AES-GCM tag size */ -#define WOLFSPDM_AEAD_OVERHEAD 48 /* Max AEAD record overhead (hdr+pad+tag) */ +/* Secured record bytes around a message: up to 32 of header and tag plus + * WOLFSPDM_SECURED_PAD of AppDataLength, MCTP type and padding */ +#define WOLFSPDM_AEAD_OVERHEAD (32 + WOLFSPDM_SECURED_PAD) /* ----- Buffer/Message Size Limits ----- */ @@ -187,6 +189,13 @@ extern "C" { #if defined(NO_ASN) && !defined(WOLFSPDM_NO_CERT) #define WOLFSPDM_NO_CERT #endif +/* MCTP records may carry up to 32 random bytes (DSP0277); the TCG binding + * only pads to 16, and wolfTPM speaks nothing else */ +#ifdef WOLFSPDM_PROFILE_TPM + #define WOLFSPDM_SECURED_PAD 16 +#else + #define WOLFSPDM_SECURED_PAD 48 +#endif #if defined(WOLFSPDM_PROFILE_TPM) && !defined(WOLFSPDM_NO_HEARTBEAT) #define WOLFSPDM_NO_HEARTBEAT #endif @@ -200,6 +209,10 @@ extern "C" { #if defined(WOLFSPDM_NO_CERT) && !defined(WOLFSPDM_NO_CHALLENGE) #define WOLFSPDM_NO_CHALLENGE #endif +/* Chunking is negotiated in CAPABILITIES */ +#if defined(WOLFSPDM_NO_CERT) && !defined(WOLFSPDM_NO_CHUNK) + #define WOLFSPDM_NO_CHUNK +#endif /* ----- Session Keep-Alive and Key Rotation ----- */ @@ -247,7 +260,7 @@ extern "C" { #ifndef WOLFSPDM_REQ_CAPS #define WOLFSPDM_REQ_CAPS (SPDM_CAP_ENCRYPT_CAP | SPDM_CAP_MAC_CAP | \ SPDM_CAP_KEY_EX_CAP | WOLFSPDM_HBEAT_REQ_CAP | \ - WOLFSPDM_KEY_UPD_REQ_CAP) + WOLFSPDM_KEY_UPD_REQ_CAP | WOLFSPDM_CHUNK_REQ_CAP) #endif /* Algorithm Set B selections */ @@ -300,6 +313,37 @@ extern "C" { #define SPDM_MEAS_SUMMARY_HASH_ALL 0xFF #endif /* !WOLFSPDM_NO_CHALLENGE */ +/* ----- Large message chunking (CHUNK_SEND / CHUNK_GET) ----- */ + +#define SPDM_MIN_DATA_TRANSFER_SIZE 42 + +/* Largest single message sent or received. MaxSPDMmsgSize stays + * WOLFSPDM_MAX_MSG_SIZE, so anything smaller relies on chunking. */ +#ifndef WOLFSPDM_DATA_TRANSFER_SIZE +#define WOLFSPDM_DATA_TRANSFER_SIZE WOLFSPDM_MAX_MSG_SIZE +#endif +#if WOLFSPDM_DATA_TRANSFER_SIZE < SPDM_MIN_DATA_TRANSFER_SIZE || \ + WOLFSPDM_DATA_TRANSFER_SIZE > WOLFSPDM_MAX_MSG_SIZE + #error "WOLFSPDM_DATA_TRANSFER_SIZE must be 42 to WOLFSPDM_MAX_MSG_SIZE" +#endif +#if defined(WOLFSPDM_NO_CHUNK) && \ + WOLFSPDM_DATA_TRANSFER_SIZE != WOLFSPDM_MAX_MSG_SIZE + #error "WOLFSPDM_DATA_TRANSFER_SIZE below WOLFSPDM_MAX_MSG_SIZE needs chunking" +#endif + +#ifndef WOLFSPDM_NO_CHUNK +#define SPDM_CHUNK_SEND 0x85 +#define SPDM_CHUNK_GET 0x86 +#define SPDM_CHUNK_SEND_ACK 0x05 +#define SPDM_CHUNK_RESPONSE 0x06 +#define SPDM_CAP_CHUNK_CAP 0x00020000 +#define SPDM_CHUNK_LAST_CHUNK 0x01 /* CHUNK_SEND, CHUNK_RESPONSE */ +#define SPDM_CHUNK_EARLY_ERROR 0x01 /* CHUNK_SEND_ACK */ +#define WOLFSPDM_CHUNK_REQ_CAP SPDM_CAP_CHUNK_CAP +#else +#define WOLFSPDM_CHUNK_REQ_CAP 0 +#endif + /* ----- TCG Build Option ----- */ /* Nuvoton or Nations enables TCG SPDM binding; future chips can set directly */ @@ -308,6 +352,13 @@ extern "C" { #define WOLFSPDM_TCG #endif +/* Single-message buffers; the TCG binding is never chunked */ +#ifdef WOLFSPDM_TCG + #define WOLFSPDM_XFER_MSG_SIZE WOLFSPDM_MAX_MSG_SIZE +#else + #define WOLFSPDM_XFER_MSG_SIZE WOLFSPDM_DATA_TRANSFER_SIZE +#endif + /* ----- PSK Build Option ----- */ /* Nations build enables PSK by default; can also be set independently */ From a8dece048385b3394199c4c2d79624619b116443 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 20:40:34 -0700 Subject: [PATCH 08/27] Compile out the TPM side in standalone builds and MCTP in pure TCG builds --- .github/workflows/build-test.yml | 26 ++++++ .github/workflows/wolftpm-downstream.yml | 10 +++ README.md | 2 + config.h.in | 3 + configure.ac | 24 ++++++ src/spdm_context.c | 2 + src/spdm_crypto.c | 3 +- src/spdm_internal.h | 13 ++- src/spdm_msg.c | 18 ++++ src/spdm_secured.c | 12 +++ test/unit_test.c | 101 +++++++++++++++++++---- wolfspdm/spdm.h | 4 +- wolfspdm/spdm_types.h | 15 ++++ 13 files changed, 214 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 5b80291..1d12afb 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -76,12 +76,18 @@ jobs: feature-config: name: features / ${{ matrix.variant }} runs-on: ubuntu-latest + env: + # Standalone builds carry none of the TPM side, pure TCG builds none of + # the standard requester + TPM_SYMS: 'wolfSPDM_(TCG_.*|Nuvoton_.*|Nations_.*|Resp[A-Z].*|.*Psk.*|SetPSK|ConnectTCG|.*TcgClear.*|.*VendorDefined|SetRequesterKey.*|SignHash|GetConnectionHandle|GetFipsIndicator)' + STD_SYMS: 'wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange)' strategy: fail-fast: false matrix: include: - variant: core configure: '' + forbid: tpm - variant: tcg configure: '--enable-tcg' - variant: tcg-psk @@ -92,14 +98,21 @@ jobs: configure: '--enable-nations' - variant: tcg-responder configure: '--enable-tcg --enable-responder' + - variant: tcg-pure + configure: '--enable-nuvoton --enable-nations --enable-responder --disable-mctp' + forbid: std - variant: no-session-ext configure: '--disable-heartbeat --disable-key-update' + forbid: tpm - variant: no-attest configure: '--disable-meas --disable-challenge' + forbid: tpm - variant: no-chunking configure: '--disable-chunking' + forbid: tpm - variant: lean configure: '--disable-cert --disable-heartbeat --disable-key-update' + forbid: tpm steps: - uses: actions/checkout@v4 @@ -143,6 +156,19 @@ jobs: env: LD_LIBRARY_PATH: ${{ github.workspace }}/src/.libs:$HOME/wolfssl-install/lib + - name: Check the other side is compiled out + if: matrix.forbid != '' + run: | + case "${{ matrix.forbid }}" in + tpm) re="$TPM_SYMS" ;; + std) re="$STD_SYMS" ;; + esac + bad=$(nm --defined-only .libs/libwolfspdm.so | awk '{print $3}' | + grep -E "^${re}$" || true) + if [ -n "$bad" ]; then + echo "Unexpected symbols:"; echo "$bad"; exit 1 + fi + - name: Upload test logs on failure if: failure() uses: actions/upload-artifact@v4 diff --git a/.github/workflows/wolftpm-downstream.yml b/.github/workflows/wolftpm-downstream.yml index 2b333ab..b883f57 100644 --- a/.github/workflows/wolftpm-downstream.yml +++ b/.github/workflows/wolftpm-downstream.yml @@ -94,6 +94,16 @@ jobs: LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }} run: ./src/spdm/unit_test + # The TPM profile compiles none of the standard requester + - name: Check the standard requester is compiled out + run: | + re='wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange|.*Heartbeat.*|.*KeyUpdate.*|DeriveUpdatedKeys)' + bad=$(nm --defined-only src/.libs/libwolftpm.so | awk '{print $3}' | + grep -E "^${re}$" || true) + if [ -n "$bad" ]; then + echo "Unexpected symbols:"; echo "$bad"; exit 1 + fi + - name: Test unavailable vendor rejection if: matrix.reject != '' env: diff --git a/README.md b/README.md index 8474b01..34a7e86 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,8 @@ make check | `--disable-chunking` | Compile out CHUNK_SEND/CHUNK_GET large message chunking (default: enabled) | | `--disable-meas` / `--disable-challenge` | Compile out GET_MEASUREMENTS / CHALLENGE (default: enabled) | | `--disable-heartbeat` / `--disable-key-update` | Compile out HEARTBEAT / KEY_UPDATE (default: enabled) | +| `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-psk` / `--enable-responder` | TPM side: TCG SPDM binding, vendor commands, PSK and the responder (default: all disabled, so a standalone build carries none of it) | +| `--disable-mctp` | Pure TCG build: drops MCTP secured messages and the whole standard requester (needs `--enable-tcg` or a vendor) | | `--with-wolfssl=PATH` | wolfSSL installation path | | `CFLAGS=-DWOLFSPDM_DATA_TRANSFER_SIZE=N` | Largest single SPDM message, 42 to 4096 (default 4096). Smaller values shrink the per-message transport buffers; larger messages then travel in CHUNK_SEND/CHUNK_GET pieces when the responder supports chunking | diff --git a/config.h.in b/config.h.in index c50557d..e2072da 100644 --- a/config.h.in +++ b/config.h.in @@ -89,6 +89,9 @@ /* Disable KEY_UPDATE */ #undef WOLFSPDM_NO_KEY_UPDATE +/* Drop MCTP secured messages */ +#undef WOLFSPDM_NO_MCTP + /* Disable GET_MEASUREMENTS */ #undef WOLFSPDM_NO_MEAS diff --git a/configure.ac b/configure.ac index ba5b045..ea7af5e 100644 --- a/configure.ac +++ b/configure.ac @@ -80,12 +80,22 @@ if test "x$enable_dynamic_mem" = "xyes"; then AC_DEFINE([WOLFSPDM_DYNAMIC_MEMORY], [1], [Enable dynamic memory allocation]) fi +# MCTP secured messages carry the standard requester; a pure TCG build drops both +AC_ARG_ENABLE([mctp], + [AS_HELP_STRING([--disable-mctp], [Drop MCTP secured messages and the standard requester for a pure TCG build (default: enabled)])], + [enable_mctp=$enableval], + [enable_mctp=yes]) + # Standard certificate-based requester (DSP0274 flow for non-TPM responders) AC_ARG_ENABLE([cert], [AS_HELP_STRING([--disable-cert], [Disable the standard certificate-based requester (default: enabled)])], [enable_cert=$enableval], [enable_cert=yes]) +if test "x$enable_mctp" = "xno"; then + enable_cert=no +fi + if test "x$enable_cert" = "xno"; then AC_DEFINE([WOLFSPDM_NO_CERT], [1], [Disable the standard certificate requester]) fi @@ -174,6 +184,12 @@ fi if test "x$enable_responder" = "xyes" && test "x$enable_tcg" != "xyes"; then AC_MSG_ERROR([--enable-responder requires --enable-tcg]) fi +if test "x$enable_mctp" = "xno"; then + if test "x$enable_tcg" != "xyes"; then + AC_MSG_ERROR([--disable-mctp requires --enable-tcg or a TCG vendor]) + fi + AC_DEFINE([WOLFSPDM_NO_MCTP], [1], [Drop MCTP secured messages]) +fi if test "x$enable_tcg" = "xyes"; then AC_DEFINE([WOLFSPDM_TCG], [1], [Enable the TCG SPDM binding]) @@ -201,11 +217,19 @@ AM_CONDITIONAL([BUILD_RESPONDER], [test "x$enable_responder" = "xyes"]) AC_CONFIG_FILES([Makefile wolfspdm.pc]) AC_OUTPUT +# These ride on the certificate flow and are compiled out without it +if test "x$enable_cert" = "xno"; then + enable_chunking=no + enable_meas=no + enable_challenge=no +fi + echo "" echo "wolfSPDM configuration summary:" echo " Version: $PACKAGE_VERSION" echo " Debug: $enable_debug" echo " Dynamic mem: $enable_dynamic_mem" +echo " MCTP: $enable_mctp" echo " Standard: $enable_cert" echo " Chunking: $enable_chunking" echo " Meas: $enable_meas" diff --git a/src/spdm_context.c b/src/spdm_context.c index fbb535f..e21de2a 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -169,6 +169,7 @@ int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } +#ifdef WOLFSPDM_MUTUAL_AUTH int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, const byte* privKey, word32 privKeySz, const byte* pubKey, word32 pubKeySz) @@ -189,6 +190,7 @@ int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } +#endif #ifdef WOLFSPDM_TCG int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, diff --git a/src/spdm_crypto.c b/src/spdm_crypto.c index 58cc303..2651098 100644 --- a/src/spdm_crypto.c +++ b/src/spdm_crypto.c @@ -309,6 +309,7 @@ int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, /* ----- ECDSA Signing (P-384) ----- */ +#ifdef WOLFSPDM_MUTUAL_AUTH int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, byte* sig, word32* sigSz) { @@ -378,4 +379,4 @@ int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } - +#endif /* WOLFSPDM_MUTUAL_AUTH */ diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 315cd9d..8582386 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -178,10 +178,12 @@ struct WOLFSPDM_CTX { byte mutAuthRequested; /* MutAuthRequested from KEY_EXCHANGE_RSP */ byte reqSlotIdParam; /* ReqSlotIDParam from KEY_EXCHANGE_RSP */ - /* Requester's identity key pair (for mutual auth) */ +#ifdef WOLFSPDM_MUTUAL_AUTH + /* Requester's identity key pair (TCG GIVE_PUB mutual auth) */ byte reqPrivKey[WOLFSPDM_ECC_KEY_SIZE]; word32 reqPrivKeyLen; byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; +#endif #ifndef WOLFSPDM_NO_CERT /* Standard requester: negotiated limits and responder cert chain */ @@ -219,7 +221,9 @@ struct WOLFSPDM_CTX { unsigned int rngInitialized : 1; unsigned int ephemeralKeyInit : 1; unsigned int hasRspPubKey : 1; +#ifdef WOLFSPDM_MUTUAL_AUTH unsigned int hasReqKeyPair : 1; +#endif #ifndef WOLFSPDM_NO_CERT unsigned int allowUntrustedCert : 1; unsigned int rspKeyFromCert : 1; @@ -231,9 +235,14 @@ struct WOLFSPDM_CTX { /* The vendor modes select TCG binding framing and pinned-key identity */ static WC_INLINE int wolfSPDM_IsTcgMode(const WOLFSPDM_CTX* ctx) { +#ifdef WOLFSPDM_TCG return ctx->mode == WOLFSPDM_MODE_NUVOTON || ctx->mode == WOLFSPDM_MODE_NATIONS || ctx->mode == WOLFSPDM_MODE_NATIONS_PSK; +#else + (void)ctx; + return 0; +#endif } #ifndef WOLFSPDM_NO_CHUNK @@ -371,8 +380,10 @@ WOLFSPDM_API int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, WOLFSPDM_API int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, const byte* peerPubKeyX, const byte* peerPubKeyY); WOLFSPDM_API int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); +#ifdef WOLFSPDM_MUTUAL_AUTH WOLFSPDM_API int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, byte* sig, word32* sigSz); +#endif WOLFSPDM_TEST_API int wolfSPDM_ExtractEccPoint(const byte* pubKey, word32 pubKeySz, const byte** pubKeyX, const byte** pubKeyY); WOLFSPDM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, diff --git a/src/spdm_msg.c b/src/spdm_msg.c index a8165d4..d9d26ba 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -57,11 +57,13 @@ static int wolfSPDM_BuildSimpleMsg(WOLFSPDM_CTX* ctx, byte msgCode, * coordinates, followed by the mode's OpaqueData block. */ #define WOLFSPDM_KEYEX_FIXED_SZ (40 + 2 * WOLFSPDM_ECC_KEY_SIZE) +#ifndef WOLFSPDM_NO_MCTP /* Standard SPDM 1.2+ secured message version list: OpaqueLength(2) + 20 */ static const byte kexOpaqueStd[] = { 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x01, 0x03, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x00, 0x00 }; +#endif #ifdef WOLFSPDM_NUVOTON static const byte kexOpaqueNuvoton[] = { 0x0c, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x01, 0x01, 0x00, 0x10, 0x00, @@ -76,8 +78,13 @@ static const byte kexOpaqueNations[] = { 0x00, 0x00 }; static void wolfSPDM_KeyExOpaque(const WOLFSPDM_CTX* ctx, const byte** opaque, word32* opaqueSz) { +#ifndef WOLFSPDM_NO_MCTP *opaque = kexOpaqueStd; *opaqueSz = (word32)sizeof(kexOpaqueStd); +#else + *opaque = NULL; + *opaqueSz = 0; +#endif #ifdef WOLFSPDM_NUVOTON if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { *opaque = kexOpaqueNuvoton; @@ -110,6 +117,9 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) return WOLFSPDM_E_INVALID_ARG; } wolfSPDM_KeyExOpaque(ctx, &opaque, &opaqueSz); + if (opaque == NULL) { + return WOLFSPDM_E_NOT_AVAILABLE; + } /* Require exactly the encoded request size */ SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, WOLFSPDM_KEYEX_FIXED_SZ + opaqueSz); @@ -221,8 +231,10 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { byte th2Hash[WOLFSPDM_HASH_SIZE]; byte verifyData[WOLFSPDM_HASH_SIZE]; +#ifdef WOLFSPDM_MUTUAL_AUTH byte signature[WOLFSPDM_ECC_POINT_SIZE]; /* 96 bytes for P-384 */ word32 sigSz = sizeof(signature); +#endif word32 offset = 4; /* Start after header */ word32 minSz; int mutualAuth = 0; @@ -233,6 +245,7 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) return WOLFSPDM_E_INVALID_ARG; } +#ifdef WOLFSPDM_MUTUAL_AUTH /* Mutual auth is enabled when the responder requested it (MutAuthRequested * bit 0) AND we have a requester key pair to sign with */ if ((ctx->mutAuthRequested & 0x01) && ctx->flags.hasReqKeyPair) { @@ -241,6 +254,7 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) "(MutAuth=0x%02x ReqSlot=0x%02x)\n", ctx->mutAuthRequested, ctx->reqSlotIdParam); } +#endif /* Check buffer size: header(4) + [OpaqueLength(2) for 1.4+] + * [signature(96) for mutual auth] + HMAC(48) */ @@ -294,6 +308,7 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) if (rc == WOLFSPDM_SUCCESS) XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); +#ifdef WOLFSPDM_MUTUAL_AUTH /* Mutual auth: sign TH2, add signature to transcript, recompute TH2 */ if (rc == WOLFSPDM_SUCCESS && mutualAuth) { byte signMsgHash[WOLFSPDM_HASH_SIZE]; @@ -312,6 +327,7 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) if (rc == WOLFSPDM_SUCCESS) rc = wolfSPDM_TranscriptHash(ctx, th2Hash); } +#endif /* RequesterVerifyData = HMAC(reqFinishedKey, TH2) */ if (rc == WOLFSPDM_SUCCESS) @@ -328,7 +344,9 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) /* Always zero sensitive stack buffers */ wc_ForceZero(th2Hash, sizeof(th2Hash)); wc_ForceZero(verifyData, sizeof(verifyData)); +#ifdef WOLFSPDM_MUTUAL_AUTH wc_ForceZero(signature, sizeof(signature)); +#endif return rc; } diff --git a/src/spdm_secured.c b/src/spdm_secured.c index e23fef1..f69e748 100644 --- a/src/spdm_secured.c +++ b/src/spdm_secured.c @@ -108,6 +108,9 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, } else #endif { +#ifdef WOLFSPDM_NO_MCTP + return WOLFSPDM_E_NOT_AVAILABLE; +#else /* MCTP format (per DSP0277): * Plaintext: AppDataLen(2 LE) + MCTP header(0x05) + SPDM message * Header: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) = 8 bytes @@ -142,6 +145,7 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, aadSz = 8; XMEMCPY(aad, enc, aadSz); +#endif } /* Build IV: BaseIV XOR sequence number (DSP0277) */ @@ -237,6 +241,9 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, } else #endif { +#ifdef WOLFSPDM_NO_MCTP + return WOLFSPDM_E_NOT_AVAILABLE; +#else word32 rspSessionId; word16 rspSeqNum, rspLen; hdrSz = 8; @@ -271,6 +278,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, tag = enc + hdrSz + cipherLen; XMEMCPY(aad, enc, aadSz); wolfSPDM_BuildIV(iv, ctx->rspDataIv, (word64)rspSeqNum); +#endif } /* ----- AES-GCM decrypt (shared for both transports) ----- */ @@ -323,6 +331,9 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, } else #endif { +#ifdef WOLFSPDM_NO_MCTP + ret = WOLFSPDM_E_NOT_AVAILABLE; +#else /* MCTP: AppDataLen(2) || MCTP(1) || SPDM msg */ if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen) || *plainSz < (word32)(appDataLen - 1)) { @@ -334,6 +345,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, *plainSz = appDataLen - 1; ret = WOLFSPDM_SUCCESS; } +#endif } } diff --git a/test/unit_test.c b/test/unit_test.c index 63c5f32..8c64393 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -80,6 +80,40 @@ static int dummy_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, return -1; } +/* Raw r||s ECDSA P-384 signature, as a responder produces it */ +static int test_ecc_sign(WC_RNG* rng, const byte* priv, const byte* pub, + const byte* hash, byte* sig) +{ + ecc_key key; + byte der[ECC_MAX_SIG_SIZE]; + byte r[WOLFSPDM_ECC_KEY_SIZE]; + byte s[WOLFSPDM_ECC_KEY_SIZE]; + word32 derSz = sizeof(der); + word32 rSz = sizeof(r); + word32 sSz = sizeof(s); + int rc; + + rc = wc_ecc_init(&key); + if (rc == 0) { + rc = wc_ecc_import_unsigned(&key, pub, pub + WOLFSPDM_ECC_KEY_SIZE, + priv, ECC_SECP384R1); + } + if (rc == 0) { + rc = wc_ecc_sign_hash(hash, WOLFSPDM_HASH_SIZE, der, &derSz, rng, + &key); + } + if (rc == 0) { + rc = wc_ecc_sig_to_rs(der, derSz, r, &rSz, s, &sSz); + } + if (rc == 0) { + XMEMSET(sig, 0, WOLFSPDM_ECC_SIG_SIZE); + XMEMCPY(sig + WOLFSPDM_ECC_KEY_SIZE - rSz, r, rSz); + XMEMCPY(sig + WOLFSPDM_ECC_SIG_SIZE - sSz, s, sSz); + } + wc_ecc_free(&key); + return rc; +} + /* ----- Context Tests ----- */ #ifdef WOLFSPDM_DYNAMIC_MEMORY @@ -582,7 +616,6 @@ static int test_key_exchange_rsp_hmac_check(void) byte signMsgHash[WOLFSPDM_HASH_SIZE]; byte th1[WOLFSPDM_HASH_SIZE]; byte sigRaw[WOLFSPDM_ECC_SIG_SIZE]; - word32 sigRawSz = WOLFSPDM_ECC_SIG_SIZE; byte expectedHmac[WOLFSPDM_HASH_SIZE]; const char* ctxStr = "responder-key_exchange_rsp signing"; const word32 ctxStrLen = 34; @@ -615,7 +648,6 @@ static int test_key_exchange_rsp_hmac_check(void) } XMEMCPY(ltPub, ltPubX, 48); XMEMCPY(ltPub + 48, ltPubY, 48); - ASSERT_SUCCESS(wolfSPDM_SetRequesterKeyPair(ctx, ltPriv, 48, ltPub, 96)); ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(ctx, ltPub, 96)); /* Our ephemeral ECDH key (requester side). Some wolfSSL builds @@ -671,10 +703,9 @@ static int test_key_exchange_rsp_hmac_check(void) ASSERT_SUCCESS(wolfSPDM_Sha384Hash(signMsgHash, signMsg, signMsgLen, NULL, 0, NULL, 0)); - /* Sign with long-term key; wolfSPDM_SignHash pads R||S to 96 bytes */ - sigRawSz = WOLFSPDM_ECC_SIG_SIZE; - ASSERT_SUCCESS(wolfSPDM_SignHash(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, - sigRaw, &sigRawSz)); + /* Sign with the long-term key as R||S, 96 bytes */ + ASSERT_SUCCESS(test_ecc_sign(&ctx->rng, ltPriv, ltPub, signMsgHash, + sigRaw)); XMEMCPY(&keRsp[138], sigRaw, WOLFSPDM_ECC_SIG_SIZE); /* TH1 = Hash(partial || signature) */ @@ -1142,6 +1173,7 @@ static int test_nations_psk_message_format(void) } #endif /* WOLFSPDM_NATIONS */ +#ifndef WOLFSPDM_NO_MCTP static int test_decrypt_overflow(void) { /* Static to avoid 4KB+ on stack; cipherLen must exceed @@ -1172,6 +1204,7 @@ static int test_decrypt_overflow(void) TEST_CTX_FREE(); TEST_PASS(); } +#endif static int test_oob_read_error(void) { @@ -1315,6 +1348,7 @@ static int test_key_zeroing(void) /* ----- Group A: Public API Coverage ----- */ +#ifdef WOLFSPDM_MUTUAL_AUTH static int test_set_requester_key_pair(void) { byte privKey[48], pubKey[96]; @@ -1340,6 +1374,7 @@ static int test_set_requester_key_pair(void) TEST_CTX_FREE(); TEST_PASS(); } +#endif static int test_connect_null_args(void) { @@ -1989,6 +2024,7 @@ static int test_export_ephemeral_pub_key(void) TEST_PASS(); } +#ifdef WOLFSPDM_MUTUAL_AUTH static int test_sign_hash_null_args(void) { byte hash[48], sig[128]; @@ -2009,6 +2045,7 @@ static int test_sign_hash_null_args(void) TEST_CTX_FREE(); TEST_PASS(); } +#endif static int test_verify_signature_null_args(void) { @@ -2029,6 +2066,7 @@ static int test_verify_signature_null_args(void) TEST_PASS(); } +#ifdef WOLFSPDM_MUTUAL_AUTH static int test_sign_verify_roundtrip(void) { byte hash[48], sig[128]; @@ -2072,6 +2110,7 @@ static int test_sign_verify_roundtrip(void) TEST_CTX_FREE(); TEST_PASS(); } +#endif /* ----- Group G: Internal KDF ----- */ @@ -2190,6 +2229,7 @@ static int test_build_key_exchange_null_args(void) TEST_PASS(); } +#ifndef WOLFSPDM_NO_MCTP static int test_build_key_exchange_format(void) { byte buf[256]; @@ -2211,6 +2251,7 @@ static int test_build_key_exchange_format(void) TEST_CTX_FREE(); TEST_PASS(); } +#endif static int test_build_key_exchange_mode_opaque(void) { @@ -2219,11 +2260,16 @@ static int test_build_key_exchange_mode_opaque(void) TEST_CTX_SETUP_V12(); printf("test_build_key_exchange_mode_opaque...\n"); +#ifndef WOLFSPDM_NO_MCTP /* Standard mode: cert slot 0 and the 20-byte version list */ ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); ASSERT_EQ(buf[3], 0x00, "standard SlotID must be 0"); ASSERT_EQ(bufSz, (word32)(136 + 22), "standard KEY_EXCHANGE size"); ASSERT_EQ(buf[136], 0x14, "standard OpaqueLength must be 20"); +#else + ASSERT_EQ(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz), + WOLFSPDM_E_NOT_AVAILABLE, "no standard KEY_EXCHANGE without MCTP"); +#endif #ifdef WOLFSPDM_NUVOTON ASSERT_SUCCESS(wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NUVOTON)); @@ -2248,6 +2294,7 @@ static int test_build_key_exchange_mode_opaque(void) /* libspdm pads MCTP records with up to 32 random bytes; a full-size message * with the most padding must still decrypt */ +#ifndef WOLFSPDM_NO_MCTP static int test_decrypt_mctp_random_padding(void) { static byte inner[3 + WOLFSPDM_XFER_MSG_SIZE + 32]; @@ -2291,7 +2338,9 @@ static int test_decrypt_mctp_random_padding(void) TEST_CTX_FREE(); TEST_PASS(); } +#endif +#ifndef WOLFSPDM_NO_MCTP static int test_decrypt_rejects_wrong_mctp_type(void) { /* An authenticated record whose inner MCTP type is not SPDM must be @@ -2333,6 +2382,7 @@ static int test_decrypt_rejects_wrong_mctp_type(void) TEST_CTX_FREE(); TEST_PASS(); } +#endif static int test_build_finish_null_args(void) { @@ -2396,6 +2446,7 @@ static int test_encrypt_internal_null_args(void) TEST_PASS(); } +#ifndef WOLFSPDM_NO_MCTP static int test_encrypt_decrypt_roundtrip(void) { byte plain[16] = "Hello SPDM test!"; @@ -2436,6 +2487,7 @@ static int test_encrypt_decrypt_roundtrip(void) TEST_CTX_FREE(); TEST_PASS(); } +#endif #ifdef WOLFSPDM_TCG static int test_encrypt_decrypt_roundtrip_tcg(void) @@ -3255,9 +3307,10 @@ static int test_derive_updated_keys(void) #endif /* !WOLFSPDM_NO_KEY_UPDATE */ -#if !defined(WOLFSPDM_NO_KEY_UPDATE) || !defined(WOLFSPDM_NO_MEAS) || \ - !defined(WOLFSPDM_NO_CHALLENGE) || !defined(WOLFSPDM_NO_CHUNK) -/* Loopback responder: a mirrored context that answers requests */ +#if (!defined(WOLFSPDM_NO_KEY_UPDATE) || !defined(WOLFSPDM_NO_MEAS) || \ + !defined(WOLFSPDM_NO_CHALLENGE) || !defined(WOLFSPDM_NO_CHUNK)) && \ + !defined(WOLFSPDM_NO_MCTP) +/* Loopback responder: a mirrored context that answers requests over MCTP */ static WOLFSPDM_CTX g_peer; static int g_peerRejects; @@ -3340,7 +3393,6 @@ static int test_peer_sign(const char* label, word32 labelSz, const byte* req, word32 reqSz, byte* rsp, word32* rspSz) { byte digest[WOLFSPDM_HASH_SIZE]; - word32 sigSz = WOLFSPDM_ECC_SIG_SIZE; int rc; rc = test_peer_run_add(req, reqSz, rsp, *rspSz); @@ -3353,14 +3405,14 @@ static int test_peer_sign(const char* label, word32 labelSz, const byte* req, digest, digest); } if (rc == 0) { - rc = wolfSPDM_SignHash(&g_peer, digest, sizeof(digest), - rsp + *rspSz, &sigSz); + rc = test_ecc_sign(&g_peer.rng, test_rsp_leaf_priv, + test_rsp_leaf_pub, digest, rsp + *rspSz); } if (rc == 0) { if (g_peerTamper) { rsp[*rspSz] ^= 0x01; } - *rspSz += sigSz; + *rspSz += WOLFSPDM_ECC_SIG_SIZE; } return rc; } @@ -3779,9 +3831,6 @@ static void test_session_loopback(WOLFSPDM_CTX* ctx) ctx->vcaLen = ctx->transcriptLen; wolfSPDM_SetResponderPubKey(ctx, test_rsp_leaf_pub, sizeof(test_rsp_leaf_pub)); - wolfSPDM_SetRequesterKeyPair(p, test_rsp_leaf_priv, - sizeof(test_rsp_leaf_priv), test_rsp_leaf_pub, - sizeof(test_rsp_leaf_pub)); g_peerRunOpen = 0; g_peerTamper = 0; #endif @@ -3805,6 +3854,7 @@ static void test_session_loopback(WOLFSPDM_CTX* ctx) #ifndef WOLFSPDM_NO_KEY_UPDATE +#ifndef WOLFSPDM_NO_MCTP static int test_key_update_loopback(void) { byte reqKey[WOLFSPDM_AEAD_KEY_SIZE]; @@ -3860,6 +3910,7 @@ static int test_key_update_loopback(void) TEST_CTX_FREE(); TEST_PASS(); } +#endif #endif /* !WOLFSPDM_NO_KEY_UPDATE */ #ifndef WOLFSPDM_NO_MEAS @@ -4264,7 +4315,9 @@ int main(void) test_nations_psk_kdf(); test_nations_psk_message_format(); #endif +#ifndef WOLFSPDM_NO_MCTP test_decrypt_overflow(); +#endif test_oob_read_error(); test_constant_time_hmac(); test_setdebug_truncation(); @@ -4273,7 +4326,9 @@ int main(void) /* ----- NEW COVERAGE TESTS ----- */ /* Public API coverage */ +#ifdef WOLFSPDM_MUTUAL_AUTH test_set_requester_key_pair(); +#endif test_connect_null_args(); test_get_version_no_io(); test_key_exchange_no_io(); @@ -4316,9 +4371,13 @@ int main(void) /* Internal crypto */ test_sha384_hash(); test_export_ephemeral_pub_key(); +#ifdef WOLFSPDM_MUTUAL_AUTH test_sign_hash_null_args(); +#endif test_verify_signature_null_args(); +#ifdef WOLFSPDM_MUTUAL_AUTH test_sign_verify_roundtrip(); +#endif /* Internal KDF */ test_derive_handshake_keys(); @@ -4327,16 +4386,24 @@ int main(void) /* Internal message building */ test_build_key_exchange_null_args(); +#ifndef WOLFSPDM_NO_MCTP test_build_key_exchange_format(); +#endif test_build_key_exchange_mode_opaque(); test_build_finish_null_args(); test_build_finish_format(); /* Internal encrypt/decrypt */ test_encrypt_internal_null_args(); +#ifndef WOLFSPDM_NO_MCTP test_encrypt_decrypt_roundtrip(); +#endif +#ifndef WOLFSPDM_NO_MCTP test_decrypt_rejects_wrong_mctp_type(); +#endif +#ifndef WOLFSPDM_NO_MCTP test_decrypt_mctp_random_padding(); +#endif #ifndef WOLFSPDM_NO_CERT test_parse_capabilities(); test_negotiate_algorithms_roundtrip(); @@ -4353,8 +4420,10 @@ int main(void) #ifndef WOLFSPDM_NO_KEY_UPDATE test_key_update_msgs(); test_derive_updated_keys(); +#ifndef WOLFSPDM_NO_MCTP test_key_update_loopback(); #endif +#endif #ifndef WOLFSPDM_NO_MEAS test_measurements_msgs(); test_measurements_loopback(); diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index 3273c8d..bfd321b 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -106,10 +106,12 @@ WOLFSPDM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); * Required before a TCG identity-key connection. */ WOLFSPDM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, const byte* pubKey, word32 pubKeySz); -/* Set requester key pair for mutual auth (privKey=48, pubKey=96 bytes) */ +#ifdef WOLFSPDM_MUTUAL_AUTH +/* Set requester key pair for TCG mutual auth (privKey=48, pubKey=96 bytes) */ WOLFSPDM_API int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, const byte* privKey, word32 privKeySz, const byte* pubKey, word32 pubKeySz); +#endif /* Cap the negotiated version (0x12-0x14, 0 = build default) */ WOLFSPDM_API int wolfSPDM_SetMaxVersion(WOLFSPDM_CTX* ctx, byte maxVersion); diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 82fbda8..876b069 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -189,6 +189,10 @@ extern "C" { #if defined(NO_ASN) && !defined(WOLFSPDM_NO_CERT) #define WOLFSPDM_NO_CERT #endif +/* A pure TCG build drops MCTP secured messages and the standard requester */ +#if defined(WOLFSPDM_NO_MCTP) && !defined(WOLFSPDM_NO_CERT) + #define WOLFSPDM_NO_CERT +#endif /* MCTP records may carry up to 32 random bytes (DSP0277); the TCG binding * only pads to 16, and wolfTPM speaks nothing else */ #ifdef WOLFSPDM_PROFILE_TPM @@ -352,6 +356,17 @@ extern "C" { #define WOLFSPDM_TCG #endif +#if defined(WOLFSPDM_NO_MCTP) && !defined(WOLFSPDM_TCG) + #error "WOLFSPDM_NO_MCTP leaves no transport without the TCG binding" +#endif + +/* Requester identity key for TCG GIVE_PUB mutual auth; wolfTPM builds keep + * the API whether or not the binding is compiled */ +#if (defined(WOLFSPDM_TCG) || defined(WOLFSPDM_PROFILE_TPM)) && \ + !defined(WOLFSPDM_MUTUAL_AUTH) + #define WOLFSPDM_MUTUAL_AUTH +#endif + /* Single-message buffers; the TCG binding is never chunked */ #ifdef WOLFSPDM_TCG #define WOLFSPDM_XFER_MSG_SIZE WOLFSPDM_MAX_MSG_SIZE From 66100f7dbdcf90fc1f3cbc3b6cc48603acbe6c4d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 20:54:01 -0700 Subject: [PATCH 09/27] Run every CI workflow on wolftpm-core and fix what GCC and codespell flag --- .github/workflows/build-test.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/codespell.yml | 2 +- .github/workflows/compiler-warnings.yml | 2 +- .github/workflows/empty-brace-scan.yml | 2 +- .github/workflows/memory-check.yml | 2 +- .github/workflows/multi-compiler.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/wolfssl-versions.yml | 2 +- src/spdm_attest.c | 14 +++++++------- src/spdm_psk.c | 2 +- src/spdm_responder.c | 2 +- 12 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 1d12afb..04173b5 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -2,7 +2,7 @@ name: Build and Test on: push: - branches: [ 'main', 'release/**' ] + branches: [ 'main', 'wolftpm-core', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index aad2708..a9fba61 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,7 +2,7 @@ name: CodeQL Security on: push: - branches: [ 'main', 'release/**' ] + branches: [ 'main', 'wolftpm-core', 'release/**' ] pull_request: branches: [ '*' ] schedule: diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 5f19b64..257581b 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -5,7 +5,7 @@ name: Codespell test # START OF COMMON SECTION on: push: - branches: [ 'main', 'release/**' ] + branches: [ 'main', 'wolftpm-core', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/compiler-warnings.yml b/.github/workflows/compiler-warnings.yml index 145be26..4a25266 100644 --- a/.github/workflows/compiler-warnings.yml +++ b/.github/workflows/compiler-warnings.yml @@ -2,7 +2,7 @@ name: Compiler Warnings on: push: - branches: [ 'main', 'release/**' ] + branches: [ 'main', 'wolftpm-core', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/empty-brace-scan.yml b/.github/workflows/empty-brace-scan.yml index a0fb6f9..85e78d4 100644 --- a/.github/workflows/empty-brace-scan.yml +++ b/.github/workflows/empty-brace-scan.yml @@ -2,7 +2,7 @@ name: Empty Brace Scope Scan on: push: - branches: [ 'main', 'release/**' ] + branches: [ 'main', 'wolftpm-core', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/memory-check.yml b/.github/workflows/memory-check.yml index 71e6307..b3ea538 100644 --- a/.github/workflows/memory-check.yml +++ b/.github/workflows/memory-check.yml @@ -2,7 +2,7 @@ name: Memory Check on: push: - branches: [ 'main', 'release/**' ] + branches: [ 'main', 'wolftpm-core', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/multi-compiler.yml b/.github/workflows/multi-compiler.yml index 5755d0e..f551b71 100644 --- a/.github/workflows/multi-compiler.yml +++ b/.github/workflows/multi-compiler.yml @@ -2,7 +2,7 @@ name: Multiple Compilers on: push: - branches: [ 'main', 'release/**' ] + branches: [ 'main', 'wolftpm-core', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 783069a..928ff1e 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -2,7 +2,7 @@ name: Static Analysis on: push: - branches: [ 'main', 'release/**' ] + branches: [ 'main', 'wolftpm-core', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/wolfssl-versions.yml b/.github/workflows/wolfssl-versions.yml index 5a421fe..80b66f7 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -2,7 +2,7 @@ name: wolfSSL Version Matrix on: push: - branches: [ 'main', 'release/**' ] + branches: [ 'main', 'wolftpm-core', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/src/spdm_attest.c b/src/spdm_attest.c index c23d7c9..130c096 100644 --- a/src/spdm_attest.c +++ b/src/spdm_attest.c @@ -152,7 +152,7 @@ int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, return WOLFSPDM_E_INVALID_ARG; } ctxSz = wolfSPDM_ReqContextSz(ctx); - sz = 4 + (requestSig ? SPDM_NONCE_SZ + 1 : 0) + ctxSz; + sz = 4 + (requestSig ? (word32)SPDM_NONCE_SZ + 1 : 0) + ctxSz; if (*bufSz < sz) { return WOLFSPDM_E_BUFFER_SMALL; } @@ -177,7 +177,7 @@ int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* req, word32 reqSz, const byte* buf, word32 bufSz, word32* sigOff) { - word32 recEnd; + word32 recordEnd; word32 off = 8; word32 i; int signedReq; @@ -190,21 +190,21 @@ int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* req, WOLFSPDM_E_MEASUREMENT); signedReq = (req[2] & SPDM_MEAS_REQUEST_SIG_BIT) != 0; - recEnd = 8 + ((word32)buf[5] | ((word32)buf[6] << 8) | + recordEnd = 8 + ((word32)buf[5] | ((word32)buf[6] << 8) | ((word32)buf[7] << 16)); - if (buf[0] != ctx->spdmVersion || recEnd > bufSz || + if (buf[0] != ctx->spdmVersion || recordEnd > bufSz || (signedReq && (buf[3] & 0x0F) != ctx->currentSlotId)) { return WOLFSPDM_E_MEASUREMENT; } /* NumberOfBlocks blocks must exactly fill MeasurementRecordLength */ - for (i = 0; i < buf[4] && off + WOLFSPDM_MEAS_BLOCK_HDR_SZ <= recEnd; + for (i = 0; i < buf[4] && off + WOLFSPDM_MEAS_BLOCK_HDR_SZ <= recordEnd; i++) { off += WOLFSPDM_MEAS_BLOCK_HDR_SZ + SPDM_Get16LE(&buf[off + 2]); } - if (i != buf[4] || off != recEnd || + if (i != buf[4] || off != recordEnd || !wolfSPDM_ParseTail(ctx, req, reqSz, buf, bufSz, - recEnd + SPDM_NONCE_SZ, + recordEnd + SPDM_NONCE_SZ, signedReq ? WOLFSPDM_ECC_SIG_SIZE : 0, sigOff)) { return WOLFSPDM_E_MEASUREMENT; } diff --git a/src/spdm_psk.c b/src/spdm_psk.c index c0d11d3..cacaf0a 100644 --- a/src/spdm_psk.c +++ b/src/spdm_psk.c @@ -151,7 +151,7 @@ int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, rspContextLen = SPDM_Get16LE(&buf[8]); opaqueLen = SPDM_Get16LE(&buf[10]); - verifyOffset = 12 + rspContextLen + opaqueLen; + verifyOffset = 12 + (word32)rspContextLen + opaqueLen; rspPartialLen = verifyOffset; if (bufSz < verifyOffset + WOLFSPDM_HASH_SIZE) { diff --git a/src/spdm_responder.c b/src/spdm_responder.c index b94e707..d883689 100644 --- a/src/spdm_responder.c +++ b/src/spdm_responder.c @@ -772,7 +772,7 @@ static int RespBuildKeyExchangeRsp(WOLFSPDM_RESP_CTX* rctx, XMEMCPY(ctx->reqPrivKey, savedReqPriv, WOLFSPDM_ECC_KEY_SIZE); XMEMCPY(ctx->reqPubKey, savedReqPub, WOLFSPDM_ECC_POINT_SIZE); ctx->reqPrivKeyLen = savedReqPrivLen; - ctx->flags.hasReqKeyPair = savedHasReqKeyPair; + ctx->flags.hasReqKeyPair = savedHasReqKeyPair ? 1 : 0; } if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_TranscriptAdd(ctx, &out[off], WOLFSPDM_ECC_SIG_SIZE); From 414326cc559b113eb8e4901d8e70c26363dc46b4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 21:07:03 -0700 Subject: [PATCH 10/27] Suppress cppcheck on the wolfCrypt requirement guards --- src/spdm_internal.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 8582386..6fa9482 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -33,9 +33,11 @@ /* wolfCrypt includes - verify required algorithms */ #ifndef HAVE_ECC + /* cppcheck-suppress preprocessorErrorDirective */ #error "wolfSPDM requires ECC (--enable-ecc in wolfSSL)" #endif #ifndef WOLFSSL_SHA384 + /* cppcheck-suppress preprocessorErrorDirective */ #error "wolfSPDM requires SHA-384 (--enable-sha384 in wolfSSL)" #endif #include From 8cc0b82f06fd4cfeb66a1f50cea0eaad01162519 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 25 Sep 2026 21:29:50 -0700 Subject: [PATCH 11/27] Add MCTP application data and secured message API on the wolfTPM core --- .github/workflows/build-test.yml | 7 +- .github/workflows/wolftpm-downstream.yml | 2 +- README.md | 9 +- config.h.in | 3 + configure.ac | 13 ++ examples/spdm_demo.c | 88 ++++++++--- examples/spdm_test.sh | 10 +- src/spdm_secured.c | 139 +++++++++++++++-- test/unit_test.c | 186 +++++++++++++++++++++++ wolfspdm/spdm.h | 19 +++ wolfspdm/spdm_types.h | 5 + 11 files changed, 445 insertions(+), 36 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 04173b5..4940d93 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -80,7 +80,7 @@ jobs: # Standalone builds carry none of the TPM side, pure TCG builds none of # the standard requester TPM_SYMS: 'wolfSPDM_(TCG_.*|Nuvoton_.*|Nations_.*|Resp[A-Z].*|.*Psk.*|SetPSK|ConnectTCG|.*TcgClear.*|.*VendorDefined|SetRequesterKey.*|SignHash|GetConnectionHandle|GetFipsIndicator)' - STD_SYMS: 'wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange)' + STD_SYMS: 'wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange|SendData|ReceiveData|EncryptMessage|DecryptMessage)' strategy: fail-fast: false matrix: @@ -110,8 +110,11 @@ jobs: - variant: no-chunking configure: '--disable-chunking' forbid: tpm + - variant: no-app-data + configure: '--disable-app-data' + forbid: tpm - variant: lean - configure: '--disable-cert --disable-heartbeat --disable-key-update' + configure: '--disable-cert --disable-heartbeat --disable-key-update --disable-app-data' forbid: tpm steps: diff --git a/.github/workflows/wolftpm-downstream.yml b/.github/workflows/wolftpm-downstream.yml index b883f57..6b55982 100644 --- a/.github/workflows/wolftpm-downstream.yml +++ b/.github/workflows/wolftpm-downstream.yml @@ -97,7 +97,7 @@ jobs: # The TPM profile compiles none of the standard requester - name: Check the standard requester is compiled out run: | - re='wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange|.*Heartbeat.*|.*KeyUpdate.*|DeriveUpdatedKeys)' + re='wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange|.*Heartbeat.*|.*KeyUpdate.*|DeriveUpdatedKeys|SendData|ReceiveData|EncryptMessage|DecryptMessage)' bad=$(nm --defined-only src/.libs/libwolftpm.so | awk '{print $3}' | grep -E "^${re}$" || true) if [ -n "$bad" ]; then diff --git a/README.md b/README.md index 34a7e86..ff38d4a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ wolfSPDM is a lightweight C library implementing [SPDM 1.2 / 1.3 / 1.4](https:// - **Optional `--enable-dynamic-mem`** for heap-allocated contexts on small-stack platforms - **Full session lifecycle:** key exchange, finish, encrypted messaging, heartbeat keep-alive, key update - **Device attestation:** signed / unsigned `GET_MEASUREMENTS`, sessionless `CHALLENGE_AUTH`, certificate-chain validation against trusted root CAs -- **Compatible with DMTF spdm-emu** for interoperability testing (18-test matrix across 1.2 / 1.3 / 1.4) +- **Compatible with DMTF spdm-emu** for interoperability testing (21-test matrix across 1.2 / 1.3 / 1.4) - **Path to FIPS 140-3** via wolfCrypt FIPS Certificate #4718 (sole crypto dependency) ## Supported Operations (RFC / DSP0274) @@ -21,7 +21,7 @@ wolfSPDM is a lightweight C library implementing [SPDM 1.2 / 1.3 / 1.4](https:// | Operation | DSP0274 | wolfSPDM API | |---|---|---| | Session establishment | Sec. 10.7 | `wolfSPDM_Connect`, `wolfSPDM_KeyExchange`, `wolfSPDM_Finish` | -| Encrypted application data | DSP0277 | `wolfSPDM_SecuredExchange`, `wolfSPDM_SendData`, `wolfSPDM_ReceiveData` | +| Encrypted application data | DSP0277 | `wolfSPDM_SecuredExchange`, `wolfSPDM_SendData`, `wolfSPDM_ReceiveData`, `wolfSPDM_EncryptMessage`, `wolfSPDM_DecryptMessage` | | Measurements (signed/unsigned) | Sec. 10.11 | `wolfSPDM_GetMeasurements`, `wolfSPDM_GetMeasurementBlock` | | Challenge authentication (sessionless) | Sec. 10.8 | `wolfSPDM_Challenge` | | Session keep-alive | Sec. 10.10 | `wolfSPDM_Heartbeat` | @@ -66,6 +66,7 @@ make check | `--disable-chunking` | Compile out CHUNK_SEND/CHUNK_GET large message chunking (default: enabled) | | `--disable-meas` / `--disable-challenge` | Compile out GET_MEASUREMENTS / CHALLENGE (default: enabled) | | `--disable-heartbeat` / `--disable-key-update` | Compile out HEARTBEAT / KEY_UPDATE (default: enabled) | +| `--disable-app-data` | Compile out `SendData`/`ReceiveData` MCTP application messages and `Encrypt`/`DecryptMessage` (default: enabled) | | `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-psk` / `--enable-responder` | TPM side: TCG SPDM binding, vendor commands, PSK and the responder (default: all disabled, so a standalone build carries none of it) | | `--disable-mctp` | Pure TCG build: drops MCTP secured messages and the whole standard requester (needs `--enable-tcg` or a vendor) | | `--with-wolfssl=PATH` | wolfSSL installation path | @@ -106,12 +107,12 @@ cd spdm-emu && mkdir build && cd build cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=mbedtls .. make copy_sample_key && make -# Run the 18-test integration matrix from this repo +# Run the 21-test integration matrix from this repo export SPDM_EMU_PATH=../spdm-emu/build/bin ./examples/spdm_test.sh ``` -The driver starts/stops `spdm_responder_emu` per test and runs six scenarios — Session, Signed Measurements, Unsigned Measurements, Challenge, Heartbeat, Key Update — across SPDM 1.2, 1.3, and 1.4 (18 tests total). +The driver starts/stops `spdm_responder_emu` per test and runs seven scenarios — Session, Signed Measurements, Unsigned Measurements, Challenge, Heartbeat, Key Update, Application Data (PLDM GetTID) — across SPDM 1.2, 1.3, and 1.4 (21 tests total). ## Relationship to wolfTPM's SPDM diff --git a/config.h.in b/config.h.in index e2072da..c0cb00d 100644 --- a/config.h.in +++ b/config.h.in @@ -74,6 +74,9 @@ /* Enable Nations SPDM vendor commands */ #undef WOLFSPDM_NATIONS +/* Disable the application data API */ +#undef WOLFSPDM_NO_APP_DATA + /* Disable the standard certificate requester */ #undef WOLFSPDM_NO_CERT diff --git a/configure.ac b/configure.ac index ea7af5e..2dd2254 100644 --- a/configure.ac +++ b/configure.ac @@ -110,6 +110,18 @@ if test "x$enable_heartbeat" = "xno"; then AC_DEFINE([WOLFSPDM_NO_HEARTBEAT], [1], [Disable HEARTBEAT]) fi +AC_ARG_ENABLE([app-data], + [AS_HELP_STRING([--disable-app-data], [Disable the MCTP application data API (default: enabled)])], + [enable_app_data=$enableval], + [enable_app_data=yes]) + +if test "x$enable_mctp" = "xno"; then + enable_app_data=no +fi +if test "x$enable_app_data" = "xno"; then + AC_DEFINE([WOLFSPDM_NO_APP_DATA], [1], [Disable the application data API]) +fi + AC_ARG_ENABLE([chunking], [AS_HELP_STRING([--disable-chunking], [Disable CHUNK_SEND/CHUNK_GET large message chunking (default: enabled)])], [enable_chunking=$enableval], @@ -231,6 +243,7 @@ echo " Debug: $enable_debug" echo " Dynamic mem: $enable_dynamic_mem" echo " MCTP: $enable_mctp" echo " Standard: $enable_cert" +echo " App data: $enable_app_data" echo " Chunking: $enable_chunking" echo " Meas: $enable_meas" echo " Challenge: $enable_challenge" diff --git a/examples/spdm_demo.c b/examples/spdm_demo.c index d3b3e19..480b013 100644 --- a/examples/spdm_demo.c +++ b/examples/spdm_demo.c @@ -1,7 +1,8 @@ /* spdm_demo.c * * wolfSPDM emulator demo - drives spdm-emu over TCP/MCTP for end-to-end - * testing of session, measurements, challenge, heartbeat, and key update. + * testing of session, measurements, challenge, heartbeat, key update, and + * application data. * * Usage: * spdm_demo --emu [--ver 1.2|1.3|1.4] @@ -9,6 +10,7 @@ * spdm_demo --challenge [--ver ...] * spdm_demo --heartbeat [--ver ...] * spdm_demo --key-update [--ver ...] + * spdm_demo --app-data [--ver ...] * * Picks up the spdm-emu install dir from $SPDM_EMU_PATH (used to find the * ca.cert.der for --challenge). @@ -121,25 +123,37 @@ static int tcp_io_callback(WOLFSPDM_CTX* ctx, if (txSz > sizeof(sendBuf) - 13) { return -1; } - payloadSz = 1 + txSz; - /* Socket header: command(4,BE) + transport_type(4,BE) + size(4,BE) */ - sendBuf[0] = 0x00; sendBuf[1] = 0x00; sendBuf[2] = 0x00; sendBuf[3] = 0x01; - sendBuf[4] = 0x00; sendBuf[5] = 0x00; sendBuf[6] = 0x00; sendBuf[7] = 0x01; - sendBuf[8] = (byte)(payloadSz >> 24); - sendBuf[9] = (byte)(payloadSz >> 16); - sendBuf[10] = (byte)(payloadSz >> 8); - sendBuf[11] = (byte)(payloadSz & 0xFF); + /* wolfSPDM_ReceiveData passes no request: only read the next message */ + if (txBuf != NULL) { + payloadSz = 1 + txSz; - /* MCTP message type: 0x05 = SPDM, 0x06 = Secured SPDM. */ - sendBuf[12] = is_secured_spdm(ctx, txBuf, txSz) ? 0x06 : 0x05; + /* Socket header: command(4,BE) + transport_type(4,BE) + size(4,BE) */ + sendBuf[0] = 0x00; sendBuf[1] = 0x00; sendBuf[2] = 0x00; + sendBuf[3] = 0x01; + sendBuf[4] = 0x00; sendBuf[5] = 0x00; sendBuf[6] = 0x00; + sendBuf[7] = 0x01; + sendBuf[8] = (byte)(payloadSz >> 24); + sendBuf[9] = (byte)(payloadSz >> 16); + sendBuf[10] = (byte)(payloadSz >> 8); + sendBuf[11] = (byte)(payloadSz & 0xFF); - if (txSz > 0) { - memcpy(sendBuf + 13, txBuf, txSz); + /* MCTP message type: 0x05 = SPDM, 0x06 = Secured SPDM. */ + sendBuf[12] = is_secured_spdm(ctx, txBuf, txSz) ? 0x06 : 0x05; + + if (txSz > 0) { + memcpy(sendBuf + 13, txBuf, txSz); + } + + if (send_all(tcpCtx->sockFd, sendBuf, (size_t)(12 + payloadSz)) != 0) { + return -1; + } } - if (send_all(tcpCtx->sockFd, sendBuf, (size_t)(12 + payloadSz)) != 0) { - return -1; + /* wolfSPDM_SendData passes no receive buffer: the reply waits for + * wolfSPDM_ReceiveData */ + if (rxBuf == NULL) { + return 0; } if (recv_all(tcpCtx->sockFd, recvHdr, sizeof(recvHdr)) != 0) { @@ -232,13 +246,15 @@ enum { MODE_MEAS, /* --meas */ MODE_CHALLENGE, /* --challenge */ MODE_HEARTBEAT, /* --heartbeat */ - MODE_KEY_UPDATE /* --key-update */ + MODE_KEY_UPDATE, /* --key-update */ + MODE_APP_DATA /* --app-data */ }; static void usage(const char* argv0) { fprintf(stderr, - "Usage: %s {--emu|--meas|--challenge|--heartbeat|--key-update}\n" + "Usage: %s {--emu|--meas|--challenge|--heartbeat|--key-update|\n" + " --app-data}\n" " [--no-sig] [--ver 1.2|1.3|1.4]\n" " [--kex ecdhe|mlkem512|mlkem768|mlkem1024] [--debug]\n" "\n" @@ -473,6 +489,37 @@ static int do_key_update(WOLFSPDM_CTX* ctx) #endif +#ifdef WOLFSPDM_HAS_APP_DATA +/* PLDM GetTID as an MCTP application message; spdm-emu answers TID 1 */ +static int do_app_data(WOLFSPDM_CTX* ctx) +{ + static const byte getTid[] = { 0x01, 0x80, 0x00, 0x02 }; + byte rsp[64]; + word32 rspSz = sizeof(rsp); + int rc = do_session(ctx); + if (rc != WOLFSPDM_SUCCESS) return rc; + + rc = wolfSPDM_SendData(ctx, getTid, sizeof(getTid)); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ReceiveData(ctx, rsp, &rspSz); + } + if (rc != WOLFSPDM_SUCCESS) { + fprintf(stderr, "App data: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return rc; + } + /* MCTP type, PLDM header (3), completion code, TID */ + if (rspSz != 6 || rsp[0] != getTid[0] || rsp[2] != getTid[2] || + rsp[3] != getTid[3] || rsp[4] != 0x00) { + fprintf(stderr, "App data: unexpected PLDM GetTID response\n"); + return WOLFSPDM_E_FRAMING; + } + printf("PLDM GetTID over the session: TID %u\n", rsp[5]); + return WOLFSPDM_SUCCESS; +} + +#endif + int main(int argc, char* argv[]) { static const struct option longOpts[] = { @@ -482,6 +529,7 @@ int main(int argc, char* argv[]) { "challenge", no_argument, 0, 'c' }, { "heartbeat", no_argument, 0, 'b' }, { "key-update", no_argument, 0, 'k' }, + { "app-data", no_argument, 0, 'a' }, { "ver", required_argument, 0, 'v' }, { "kex", required_argument, 0, 'K' }, { "debug", no_argument, 0, 'd' }, @@ -498,7 +546,7 @@ int main(int argc, char* argv[]) int rc; WOLFSPDM_CTX* ctx = (WOLFSPDM_CTX*)g_ctxBuf; - while ((opt = getopt_long(argc, argv, "emncbkv:hd", longOpts, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, "emncbkav:hd", longOpts, NULL)) != -1) { switch (opt) { case 'e': mode = MODE_SESSION; break; case 'm': mode = MODE_MEAS; break; @@ -506,6 +554,7 @@ int main(int argc, char* argv[]) case 'c': mode = MODE_CHALLENGE; break; case 'b': mode = MODE_HEARTBEAT; break; case 'k': mode = MODE_KEY_UPDATE; break; + case 'a': mode = MODE_APP_DATA; break; case 'd': debug = 1; break; case 'v': maxVer = parse_version(optarg); @@ -616,6 +665,9 @@ int main(int argc, char* argv[]) #endif #ifdef WOLFSPDM_HAS_KEY_UPDATE case MODE_KEY_UPDATE: rc = do_key_update(ctx); break; +#endif +#ifdef WOLFSPDM_HAS_APP_DATA + case MODE_APP_DATA: rc = do_app_data(ctx); break; #endif default: fprintf(stderr, "Scenario not built into this wolfSPDM\n"); diff --git a/examples/spdm_test.sh b/examples/spdm_test.sh index c1d65d2..c788e9d 100755 --- a/examples/spdm_test.sh +++ b/examples/spdm_test.sh @@ -3,7 +3,8 @@ # spdm_test.sh - SPDM emulator test script # # Tests SPDM protocol with libspdm emulator (session + measurements + challenge -# + heartbeat + key update) across SPDM versions 1.2, 1.3, and 1.4. +# + heartbeat + key update + application data) across SPDM versions 1.2, 1.3, +# and 1.4. # # Usage: # ./spdm_test.sh # Run emulator tests @@ -35,7 +36,8 @@ usage() { echo "Usage: $0 [path-to-spdm_demo]" echo "" echo "Runs SPDM emulator tests (session, measurements, challenge," - echo "heartbeat, key update) across SPDM versions 1.2, 1.3, and 1.4." + echo "heartbeat, key update, application data) across SPDM versions 1.2," + echo "1.3, and 1.4." echo "" echo "Expects spdm_responder_emu to be found via:" echo " 1. SPDM_EMU_PATH environment variable" @@ -270,6 +272,10 @@ for VER in 1.2 1.3 1.4; do run_test "Key update (SPDM $VER)" "$VER" \ "$SPDM_DEMO" --emu --key-update --ver "$VER" + # Session + PLDM GetTID as an MCTP application message + run_test "Application data (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --app-data --ver "$VER" + echo "" done diff --git a/src/spdm_secured.c b/src/spdm_secured.c index f69e748..aa68324 100644 --- a/src/spdm_secured.c +++ b/src/spdm_secured.c @@ -40,7 +40,8 @@ * Full message: Header || Ciphertext || Tag (16) */ -int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, +/* appMsg: plain is an MCTP application message carrying its own type byte */ +static int wolfSPDM_EncryptRecord(WOLFSPDM_CTX* ctx, int appMsg, const byte* plain, word32 plainSz, byte* enc, word32* encSz) { @@ -109,6 +110,7 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, #endif { #ifdef WOLFSPDM_NO_MCTP + (void)appMsg; return WOLFSPDM_E_NOT_AVAILABLE; #else /* MCTP format (per DSP0277): @@ -116,7 +118,7 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, * Header: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) = 8 bytes * AAD = Header */ - word16 appDataLen = (word16)(1 + plainSz); + word16 appDataLen = (word16)(appMsg ? plainSz : 1 + plainSz); word16 encDataLen = (word16)(2 + appDataLen); /* MCTP carries a 16-bit sequence number; fail rather than let the wire @@ -135,8 +137,12 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, /* Build plaintext: AppDataLen(2 LE) || MCTP header(0x05) || SPDM msg */ SPDM_Set16LE(plainBuf, appDataLen); - plainBuf[2] = MCTP_MESSAGE_TYPE_SPDM; - XMEMCPY(&plainBuf[3], plain, plainSz); + if (appMsg) { + XMEMCPY(&plainBuf[2], plain, plainSz); + } else { + plainBuf[2] = MCTP_MESSAGE_TYPE_SPDM; + XMEMCPY(&plainBuf[3], plain, plainSz); + } /* Build header/AAD: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) */ SPDM_Set32LE(&enc[0], ctx->sessionId); @@ -177,7 +183,15 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } -int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, +int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, + const byte* plain, word32 plainSz, + byte* enc, word32* encSz) +{ + return wolfSPDM_EncryptRecord(ctx, 0, plain, plainSz, enc, encSz); +} + +/* appMsg: return the MCTP application message with its type byte */ +static int wolfSPDM_DecryptRecord(WOLFSPDM_CTX* ctx, int appMsg, const byte* enc, word32 encSz, byte* plain, word32* plainSz) { @@ -242,6 +256,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, #endif { #ifdef WOLFSPDM_NO_MCTP + (void)appMsg; return WOLFSPDM_E_NOT_AVAILABLE; #else word32 rspSessionId; @@ -335,14 +350,15 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, ret = WOLFSPDM_E_NOT_AVAILABLE; #else /* MCTP: AppDataLen(2) || MCTP(1) || SPDM msg */ + word32 typeSz = appMsg ? 0 : 1; if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen) || - *plainSz < (word32)(appDataLen - 1)) { + *plainSz < (word32)(appDataLen - typeSz)) { ret = WOLFSPDM_E_BUFFER_SMALL; - } else if (decrypted[2] != MCTP_MESSAGE_TYPE_SPDM) { + } else if (!appMsg && decrypted[2] != MCTP_MESSAGE_TYPE_SPDM) { ret = WOLFSPDM_E_DECRYPT_FAIL; } else { - XMEMCPY(plain, &decrypted[3], appDataLen - 1); - *plainSz = appDataLen - 1; + XMEMCPY(plain, &decrypted[2 + typeSz], appDataLen - typeSz); + *plainSz = appDataLen - typeSz; ret = WOLFSPDM_SUCCESS; } #endif @@ -358,6 +374,13 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, return ret; } +int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, + const byte* enc, word32 encSz, + byte* plain, word32* plainSz) +{ + return wolfSPDM_DecryptRecord(ctx, 0, enc, encSz, plain, plainSz); +} + int wolfSPDM_SecuredXfer(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz) @@ -402,3 +425,101 @@ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, return wolfSPDM_SecuredXfer(ctx, cmdPlain, cmdSz, rspPlain, rspSz); } + +#ifndef WOLFSPDM_NO_APP_DATA +int wolfSPDM_EncryptMessage(WOLFSPDM_CTX* ctx, + const byte* plain, word32 plainSz, byte* enc, word32* encSz) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + return wolfSPDM_EncryptRecord(ctx, 0, plain, plainSz, enc, encSz); +} + +int wolfSPDM_DecryptMessage(WOLFSPDM_CTX* ctx, + const byte* enc, word32 encSz, byte* plain, word32* plainSz) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + return wolfSPDM_DecryptRecord(ctx, 0, enc, encSz, plain, plainSz); +} + +static int wolfSPDM_AppDataReady(const WOLFSPDM_CTX* ctx) +{ + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + /* The TCG binding only carries SPDM request/response pairs */ + if (wolfSPDM_IsTcgMode(ctx)) { + return WOLFSPDM_E_NOT_AVAILABLE; + } + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_SendData(WOLFSPDM_CTX* ctx, const byte* data, word32 dataSz) +{ + byte encBuf[WOLFSPDM_XFER_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + word32 encSz = sizeof(encBuf); + word32 rxSz = 0; + int rc; + + if (ctx == NULL || data == NULL || dataSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + /* Type 0x05 would reach the peer as an SPDM request */ + if (data[0] == MCTP_MESSAGE_TYPE_SPDM) { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wolfSPDM_AppDataReady(ctx); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_EncryptRecord(ctx, 1, data, dataSz, encBuf, &encSz); + } + if (rc == WOLFSPDM_SUCCESS && + ctx->ioCb(ctx, encBuf, encSz, NULL, &rxSz, ctx->ioUserCtx) != 0) { + rc = WOLFSPDM_E_IO_FAIL; + } + + return rc; +} + +int wolfSPDM_ReceiveData(WOLFSPDM_CTX* ctx, byte* data, word32* dataSz) +{ + byte rxBuf[WOLFSPDM_XFER_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + word32 rxSz = sizeof(rxBuf); + int rc; + + if (ctx == NULL || data == NULL || dataSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wolfSPDM_AppDataReady(ctx); + if (rc == WOLFSPDM_SUCCESS && + (ctx->ioCb(ctx, NULL, 0, rxBuf, &rxSz, ctx->ioUserCtx) != 0 || + rxSz > sizeof(rxBuf))) { + rc = WOLFSPDM_E_IO_FAIL; + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptRecord(ctx, 1, rxBuf, rxSz, data, dataSz); + } + /* The responder answers undeliverable application data in SPDM */ + if (rc == WOLFSPDM_SUCCESS && data[0] == MCTP_MESSAGE_TYPE_SPDM) { + if (*dataSz >= 4 && data[2] == SPDM_ERROR) { + ctx->lastPeerErrorCode = data[3]; + } + rc = WOLFSPDM_E_PEER_ERROR; + } + + return rc; +} +#endif /* !WOLFSPDM_NO_APP_DATA */ diff --git a/test/unit_test.c b/test/unit_test.c index 8c64393..5826a3a 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -2522,6 +2522,189 @@ static int test_encrypt_decrypt_roundtrip_tcg(void) } #endif /* WOLFSPDM_TCG */ +#ifndef WOLFSPDM_NO_APP_DATA +/* One-slot mailbox standing in for a message-oriented MCTP link */ +static byte g_mbox[WOLFSPDM_XFER_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; +static word32 g_mboxSz; + +static int test_mbox_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, void* userCtx) +{ + (void)ctx; + (void)userCtx; + if (txBuf != NULL) { + /* SendData never waits for a reply */ + if (rxBuf != NULL || rxSz == NULL || *rxSz != 0 || + txSz > sizeof(g_mbox)) { + return -1; + } + XMEMCPY(g_mbox, txBuf, txSz); + g_mboxSz = txSz; + return 0; + } + if (txSz != 0 || rxBuf == NULL || g_mboxSz == 0 || g_mboxSz > *rxSz) { + return -1; + } + XMEMCPY(rxBuf, g_mbox, g_mboxSz); + *rxSz = g_mboxSz; + g_mboxSz = 0; + return 0; +} + +/* Two connected contexts with mirrored keys sharing the mailbox */ +static void test_app_data_pair(WOLFSPDM_CTX* ctx, WOLFSPDM_CTX* p) +{ + wolfSPDM_Init(p); + ctx->spdmVersion = SPDM_VERSION_12; + p->spdmVersion = SPDM_VERSION_12; + ctx->sessionId = 0x00020001; + p->sessionId = 0x00020001; + ctx->state = WOLFSPDM_STATE_CONNECTED; + p->state = WOLFSPDM_STATE_CONNECTED; + XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataKey, 0x22, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->reqDataIv, 0x33, WOLFSPDM_AEAD_IV_SIZE); + XMEMSET(ctx->rspDataIv, 0x44, WOLFSPDM_AEAD_IV_SIZE); + XMEMCPY(p->reqDataKey, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(p->rspDataKey, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + XMEMCPY(p->reqDataIv, ctx->rspDataIv, WOLFSPDM_AEAD_IV_SIZE); + XMEMCPY(p->rspDataIv, ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE); + wolfSPDM_SetIO(ctx, test_mbox_io_cb, NULL); + wolfSPDM_SetIO(p, test_mbox_io_cb, NULL); + g_mboxSz = 0; +} + +static int test_app_data(void) +{ + /* PLDM GetTID and the reply spdm-emu gives it */ + static const byte getTid[] = { 0x01, 0x80, 0x00, 0x02 }; + static const byte tidRsp[] = { 0x01, 0x00, 0x00, 0x02, 0x00, 0x01 }; + static const byte spdmErr[] = { SPDM_VERSION_12, SPDM_ERROR, + SPDM_ERROR_UNSUPPORTED_REQUEST, 0x00 }; + static const byte asSpdm[] = { MCTP_MESSAGE_TYPE_SPDM, SPDM_VERSION_12, + SPDM_ERROR, 0x00, 0x00 }; + static WOLFSPDM_CTX peer; + static byte big[WOLFSPDM_XFER_MSG_SIZE + 1]; + byte buf[64]; + byte enc[128]; + word32 bufSz = sizeof(buf); + word32 encSz = sizeof(enc); + TEST_CTX_SETUP(); + + printf("test_app_data...\n"); + XMEMSET(enc, 0, sizeof(enc)); + ASSERT_EQ(wolfSPDM_SendData(NULL, getTid, sizeof(getTid)), + WOLFSPDM_E_INVALID_ARG, "NULL ctx"); + ASSERT_EQ(wolfSPDM_SendData(ctx, NULL, sizeof(getTid)), + WOLFSPDM_E_INVALID_ARG, "NULL data"); + ASSERT_EQ(wolfSPDM_ReceiveData(ctx, NULL, &bufSz), + WOLFSPDM_E_INVALID_ARG, "NULL data"); + ASSERT_EQ(wolfSPDM_ReceiveData(ctx, buf, NULL), + WOLFSPDM_E_INVALID_ARG, "NULL size"); + ASSERT_EQ(wolfSPDM_EncryptMessage(NULL, spdmErr, sizeof(spdmErr), enc, + &encSz), WOLFSPDM_E_INVALID_ARG, "NULL ctx"); + ASSERT_EQ(wolfSPDM_DecryptMessage(NULL, enc, 32, buf, &bufSz), + WOLFSPDM_E_INVALID_ARG, "NULL ctx"); + + /* Nothing flows before the session is up */ + wolfSPDM_SetIO(ctx, test_mbox_io_cb, NULL); + g_mboxSz = 0; + ASSERT_EQ(wolfSPDM_SendData(ctx, getTid, sizeof(getTid)), + WOLFSPDM_E_NOT_CONNECTED, "SendData before connect"); + ASSERT_EQ(wolfSPDM_ReceiveData(ctx, buf, &bufSz), + WOLFSPDM_E_NOT_CONNECTED, "ReceiveData before connect"); + ASSERT_EQ(wolfSPDM_EncryptMessage(ctx, spdmErr, sizeof(spdmErr), enc, + &encSz), WOLFSPDM_E_NOT_CONNECTED, "EncryptMessage before connect"); + ASSERT_EQ(wolfSPDM_DecryptMessage(ctx, enc, 32, buf, &bufSz), + WOLFSPDM_E_NOT_CONNECTED, "DecryptMessage before connect"); + ASSERT_EQ(g_mboxSz, 0, "Nothing sent before connect"); + + test_app_data_pair(ctx, &peer); + + /* The application message keeps its own MCTP type inside the record */ + ASSERT_SUCCESS(wolfSPDM_SendData(ctx, getTid, sizeof(getTid))); + ASSERT_EQ(g_mboxSz, 8 + 2 + sizeof(getTid) + WOLFSPDM_AEAD_TAG_SIZE, + "Record carries no extra type byte"); + ASSERT_EQ(SPDM_Get32LE(g_mbox), 0x00020001, "Session ID in the header"); + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_ReceiveData(&peer, buf, &bufSz)); + ASSERT_EQ(bufSz, sizeof(getTid), "Request size"); + ASSERT_EQ(memcmp(buf, getTid, sizeof(getTid)), 0, "Request bytes"); + + ASSERT_SUCCESS(wolfSPDM_SendData(&peer, tidRsp, sizeof(tidRsp))); + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_ReceiveData(ctx, buf, &bufSz)); + ASSERT_EQ(bufSz, sizeof(tidRsp), "Response size"); + ASSERT_EQ(memcmp(buf, tidRsp, sizeof(tidRsp)), 0, "Response bytes"); + ASSERT_EQ(ctx->reqSeqNum, 1, "Request sequence advanced"); + ASSERT_EQ(ctx->rspSeqNum, 1, "Response sequence advanced"); + + /* SPDM messages sealed for a caller-driven transport */ + encSz = sizeof(enc); + ASSERT_SUCCESS(wolfSPDM_EncryptMessage(ctx, spdmErr, sizeof(spdmErr), + enc, &encSz)); + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_DecryptMessage(&peer, enc, encSz, buf, &bufSz)); + ASSERT_EQ(bufSz, sizeof(spdmErr), "SPDM message size"); + ASSERT_EQ(memcmp(buf, spdmErr, sizeof(spdmErr)), 0, "SPDM message bytes"); + + /* An SPDM ERROR in place of application data */ + encSz = sizeof(g_mbox); + ASSERT_SUCCESS(wolfSPDM_EncryptMessage(&peer, spdmErr, sizeof(spdmErr), + g_mbox, &encSz)); + g_mboxSz = encSz; + bufSz = sizeof(buf); + ASSERT_EQ(wolfSPDM_ReceiveData(ctx, buf, &bufSz), WOLFSPDM_E_PEER_ERROR, + "SPDM ERROR surfaces as a peer error"); + ASSERT_EQ(wolfSPDM_GetLastPeerError(ctx), SPDM_ERROR_UNSUPPORTED_REQUEST, + "Peer error code recorded"); + + /* DecryptMessage only opens SPDM messages */ + ASSERT_SUCCESS(wolfSPDM_SendData(&peer, tidRsp, sizeof(tidRsp))); + bufSz = sizeof(buf); + ASSERT_EQ(wolfSPDM_DecryptMessage(ctx, g_mbox, g_mboxSz, buf, &bufSz), + WOLFSPDM_E_DECRYPT_FAIL, "Application message is not SPDM"); + g_mboxSz = 0; + + ASSERT_EQ(wolfSPDM_SendData(ctx, asSpdm, sizeof(asSpdm)), + WOLFSPDM_E_INVALID_ARG, "MCTP type 0x05 is reserved for SPDM"); + ASSERT_EQ(wolfSPDM_SendData(ctx, getTid, 0), WOLFSPDM_E_INVALID_ARG, + "Empty message"); + big[0] = 0x01; + ASSERT_EQ(wolfSPDM_SendData(ctx, big, sizeof(big)), + WOLFSPDM_E_BUFFER_SMALL, "Oversized message"); + ASSERT_EQ(g_mboxSz, 0, "Rejected messages are not sent"); + bufSz = sizeof(buf); + ASSERT_EQ(wolfSPDM_ReceiveData(ctx, buf, &bufSz), WOLFSPDM_E_IO_FAIL, + "Empty link"); + + ASSERT_SUCCESS(wolfSPDM_SendData(&peer, tidRsp, sizeof(tidRsp))); + bufSz = sizeof(tidRsp) - 1; + ASSERT_EQ(wolfSPDM_ReceiveData(ctx, buf, &bufSz), WOLFSPDM_E_BUFFER_SMALL, + "Short output buffer"); + + ASSERT_SUCCESS(wolfSPDM_SendData(&peer, tidRsp, sizeof(tidRsp))); + g_mbox[10] ^= 0x01; + bufSz = sizeof(buf); + ASSERT_EQ(wolfSPDM_ReceiveData(ctx, buf, &bufSz), WOLFSPDM_E_DECRYPT_FAIL, + "Tampered record"); + +#ifdef WOLFSPDM_TCG + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_EQ(wolfSPDM_SendData(ctx, getTid, sizeof(getTid)), + WOLFSPDM_E_NOT_AVAILABLE, "No application messages over TCG"); + ctx->mode = WOLFSPDM_MODE_AUTO; +#endif + ctx->ioCb = NULL; + ASSERT_EQ(wolfSPDM_SendData(ctx, getTid, sizeof(getTid)), + WOLFSPDM_E_IO_FAIL, "No transport"); + + wolfSPDM_Free(&peer); + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSPDM_NO_APP_DATA */ + #ifdef WOLFSPDM_RESPONDER static int g_tpmCbInvocations = 0; @@ -4414,6 +4597,9 @@ int main(void) #ifdef WOLFSPDM_TCG test_encrypt_decrypt_roundtrip_tcg(); #endif +#ifndef WOLFSPDM_NO_APP_DATA + test_app_data(); +#endif #ifndef WOLFSPDM_NO_HEARTBEAT test_heartbeat_msgs(); #endif diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index bfd321b..79cefe8 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -145,6 +145,25 @@ WOLFSPDM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz); +#ifndef WOLFSPDM_NO_APP_DATA +#define WOLFSPDM_HAS_APP_DATA +/* Seal or open one SPDM message as a secured record for a caller-driven + * transport; at most WOLFSPDM_XFER_MSG_SIZE bytes of message */ +WOLFSPDM_API int wolfSPDM_EncryptMessage(WOLFSPDM_CTX* ctx, + const byte* plain, word32 plainSz, byte* enc, word32* encSz); +WOLFSPDM_API int wolfSPDM_DecryptMessage(WOLFSPDM_CTX* ctx, + const byte* enc, word32 encSz, byte* plain, word32* plainSz); +/* One MCTP application message per call, starting with its own MCTP message + * type (0x01 for PLDM, never 0x05). SendData calls the I/O callback with + * rxBuf NULL and *rxSz 0 (send only); ReceiveData calls it with txBuf NULL + * and txSz 0 (receive only). An SPDM ERROR in place of application data + * returns WOLFSPDM_E_PEER_ERROR. */ +WOLFSPDM_API int wolfSPDM_SendData(WOLFSPDM_CTX* ctx, const byte* data, + word32 dataSz); +WOLFSPDM_API int wolfSPDM_ReceiveData(WOLFSPDM_CTX* ctx, byte* data, + word32* dataSz); +#endif + #ifndef WOLFSPDM_NO_MEAS #define WOLFSPDM_HAS_MEASUREMENTS /* Fetch measurements over the session; a signed request is verified against diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 876b069..dfc401c 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -206,6 +206,11 @@ extern "C" { #if defined(WOLFSPDM_PROFILE_TPM) && !defined(WOLFSPDM_NO_KEY_UPDATE) #define WOLFSPDM_NO_KEY_UPDATE #endif +/* Application messages ride MCTP; WOLFSPDM_LEAN is the older spelling */ +#if (defined(WOLFSPDM_PROFILE_TPM) || defined(WOLFSPDM_NO_MCTP) || \ + defined(WOLFSPDM_LEAN)) && !defined(WOLFSPDM_NO_APP_DATA) + #define WOLFSPDM_NO_APP_DATA +#endif /* Attestation needs the certificate flow (VCA transcript, chain hash) */ #if defined(WOLFSPDM_NO_CERT) && !defined(WOLFSPDM_NO_MEAS) #define WOLFSPDM_NO_MEAS From 0d50ae9fe3d0d1ff68db6485a6683dfb8a0c14a1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 09:16:22 -0700 Subject: [PATCH 12/27] Add ML-DSA and ML-KEM on the wolfTPM core and restore main's remaining API --- .github/workflows/build-test.yml | 2 +- .github/workflows/spdm-emu-pqc-test.yml | 367 +++++++ .github/workflows/wolftpm-downstream.yml | 2 +- Makefile.am | 2 +- config.h.in | 6 + configure.ac | 78 ++ src/spdm_attest.c | 16 +- src/spdm_chunk.c | 15 +- src/spdm_context.c | 37 +- src/spdm_crypto.c | 205 +++- src/spdm_internal.h | 139 ++- src/spdm_msg.c | 263 +++-- src/spdm_standard.c | 398 ++++++-- test/test_certs_mldsa.h | 1165 ++++++++++++++++++++++ test/unit_test.c | 491 ++++++++- wolfspdm/spdm.h | 28 +- wolfspdm/spdm_types.h | 93 +- 17 files changed, 3063 insertions(+), 244 deletions(-) create mode 100644 .github/workflows/spdm-emu-pqc-test.yml create mode 100644 test/test_certs_mldsa.h diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 4940d93..58a151e 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -80,7 +80,7 @@ jobs: # Standalone builds carry none of the TPM side, pure TCG builds none of # the standard requester TPM_SYMS: 'wolfSPDM_(TCG_.*|Nuvoton_.*|Nations_.*|Resp[A-Z].*|.*Psk.*|SetPSK|ConnectTCG|.*TcgClear.*|.*VendorDefined|SetRequesterKey.*|SignHash|GetConnectionHandle|GetFipsIndicator)' - STD_SYMS: 'wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange|SendData|ReceiveData|EncryptMessage|DecryptMessage)' + STD_SYMS: 'wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange|SendData|ReceiveData|EncryptMessage|DecryptMessage|.*MlDsa.*|.*MlKem.*|SetKeyExchangePref)' strategy: fail-fast: false matrix: diff --git a/.github/workflows/spdm-emu-pqc-test.yml b/.github/workflows/spdm-emu-pqc-test.yml new file mode 100644 index 0000000..5c854c4 --- /dev/null +++ b/.github/workflows/spdm-emu-pqc-test.yml @@ -0,0 +1,367 @@ +name: SPDM Emulator PQC Test + +# Post-quantum interop (ML-DSA signing + ML-KEM key exchange, and a full-PQ +# combination), mirroring the classical SPDM Emulator Integration Test matrix +# (ubuntu 22.04/24.04 x64 + 24.04 aarch64, each with static and dynamic memory). +# The wc_MlDsaKey context API wolfSPDM verifies with lands post-v5.9.1-stable, +# so this job pins wolfSSL master. libspdm's ML-DSA/ML-KEM are only in its +# OpenSSL backend (the mbedtls backend stubs them out), so spdm-emu is built +# with CRYPTO=openssl. + +on: + push: + branches: [ 'main', 'wolftpm-core', 'release/**' ] + pull_request: + branches: [ '*' ] + repository_dispatch: + types: [nightly-trigger] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + pqc-emu-test: + name: ${{ matrix.os }} (${{ matrix.arch }}) / dynamic-mem=${{ matrix.dynamic-mem }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + arch: x64 + dynamic-mem: 'no' + - os: ubuntu-22.04 + arch: x64 + dynamic-mem: 'yes' + - os: ubuntu-24.04 + arch: x64 + dynamic-mem: 'no' + - os: ubuntu-24.04 + arch: x64 + dynamic-mem: 'yes' + - os: ubuntu-24.04-arm + arch: aarch64 + dynamic-mem: 'no' + - os: ubuntu-24.04-arm + arch: aarch64 + dynamic-mem: 'yes' + runs-on: ${{ matrix.os }} + timeout-minutes: 40 + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y autoconf automake libtool cmake + + - name: Compute cache period + id: cache-period + run: echo "biweekly=$(( $(date +%s) / 1296000 ))" >> $GITHUB_OUTPUT + + # --- wolfSSL master with ML-DSA (rebuilt to track upstream drift) --- + - name: Build wolfSSL master (--enable-mldsa --enable-mlkem) + run: | + cd ~ + git clone --depth 1 --branch master https://github.com/wolfSSL/wolfssl.git + cd wolfssl + ./autogen.sh + ./configure --enable-ecc --enable-sha384 --enable-aesgcm \ + --enable-hkdf --enable-sp --enable-mldsa --enable-mlkem \ + --prefix=$HOME/wolfssl-install + make -j"$(nproc)" + make install + grep LIBWOLFSSL_VERSION_STRING $HOME/wolfssl-install/include/wolfssl/version.h + + # --- ML-KEM without ML-DSA: a real config (ML-KEM/Kyber is commonly + # enabled for TLS hybrid KEX while ML-DSA is not). Exercises the + # ML-KEM-only WOLFSPDM_CTX_STATIC_SIZE budget and unit tests, which + # the combined build below does not. Cleaned up before the full build. --- + - name: Build + test wolfSPDM ML-KEM-only (--disable-mldsa --enable-mlkem) + run: | + ./autogen.sh + ./configure --with-wolfssl=$HOME/wolfssl-install \ + --disable-mldsa --enable-mlkem \ + ${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }} + make -j"$(nproc)" + make check + make distclean + env: + LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib + + # --- wolfSPDM with ML-DSA + ML-KEM asserted on, static or dynamic memory --- + - name: Build and install wolfSPDM (--enable-mldsa --enable-mlkem) + run: | + ./autogen.sh + ./configure --with-wolfssl=$HOME/wolfssl-install \ + --prefix=$HOME/wolfspdm-install --enable-mldsa --enable-mlkem \ + ${{ matrix.dynamic-mem == 'yes' && '--enable-dynamic-mem' || '' }} + make -j"$(nproc)" + make install + + - name: Run unit tests (includes ML-DSA verify + ML-KEM decap) + run: make check + env: + LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib + + # --- spdm-emu with OpenSSL backend (ML-DSA), cached per OS/arch --- + # Cache the whole build tree, not just build/bin: the OpenSSL-backed + # responder depends on the bundled OpenSSL libraries/providers built + # elsewhere under build/, so a build/bin-only cache restores a binary + # that fails mid-handshake. The full tree makes a restored build behave + # identically to a fresh one. + - name: Cache spdm-emu (openssl) + id: cache-spdm-emu + uses: actions/cache@v4 + with: + path: ~/spdm-emu/build + key: spdm-emu-pqc-openssl-v4-${{ matrix.os }}-${{ matrix.arch }}-${{ steps.cache-period.outputs.biweekly }} + + - name: Build spdm-emu (CRYPTO=openssl) + if: steps.cache-spdm-emu.outputs.cache-hit != 'true' + run: | + cd ~ + git clone --depth 1 --recurse-submodules https://github.com/DMTF/spdm-emu.git + cd spdm-emu + mkdir build && cd build + cmake -DARCH=${{ matrix.arch }} -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=openssl .. + make copy_sample_key + make -j"$(nproc)" spdm_responder_emu + + # --- ML-DSA interop: responder offers only ML-DSA, requester verifies. + # ML-DSA-44/65 fit spdm-emu's DataTransferSize (4608 B) in one message; + # ML-DSA-87 (sig 4627 B) exceeds it and the responder chunks the + # response, exercising CHUNK_GET reassembly on both the cleartext + # (KEY_EXCHANGE/CHALLENGE) and secured (MEASUREMENTS) paths. + - name: ML-DSA 44/65/87 session + measurements + challenge + run: | + export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib + export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin + DEMO=./examples/spdm_demo + FAILURES="" + + # Wait until the responder is accepting connections on port 2323. + wait_for_port() { + local i + for i in $(seq 1 50); do + if ss -ltn 2>/dev/null | grep -q ':2323 '; then return 0; fi + sleep 0.2 + done + return 1 + } + + # $1 ML_DSA_xx (responder), $2 cert dir, $3 demo label, then demo args. + # The OpenSSL-backed ML-DSA responder is slower to become ready than + # the mbedtls/ECDSA one, and it stops after a failed connection, so + # retry the whole case (restarting the responder) a few times. + # Records (does NOT exit on) failures so every ML-DSA size and flow is + # exercised and reported, not masked by the first failure. + run_case() { + local alg="$1" certdir="$2" label="$3"; shift 3 + export SPDM_EMU_CERT_DIR="$certdir" + echo "::group::$alg $label" + local attempt rc=1 emu + for attempt in 1 2 3; do + ( cd "$SPDM_EMU_PATH" && ./spdm_responder_emu --ver 1.4 \ + --hash SHA_384 --asym NONE --pqc_asym "$alg" \ + --dhe SECP_384_R1 --aead AES_256_GCM \ + >/tmp/pqc_emu_${alg}_${label}.log 2>&1 ) & + emu=$! + if wait_for_port; then + sleep 1 + if "$DEMO" "$@" --ver 1.4 --debug; then rc=0; else rc=$?; fi + else + rc=1 + fi + kill $emu 2>/dev/null || true + wait $emu 2>/dev/null || true + pkill -f spdm_responder_emu 2>/dev/null || true + [ $rc -eq 0 ] && break + echo "--- responder log (attempt $attempt) ---" + cat /tmp/pqc_emu_${alg}_${label}.log || true + echo "attempt $attempt for $alg $label failed (rc=$rc), retrying" + sleep 1 + done + echo "::endgroup::" + if [ $rc -ne 0 ]; then + echo "::error::$alg $label failed after retries (rc=$rc)" + FAILURES="$FAILURES $alg/$label" + else + echo "$alg $label: OK" + fi + } + + for spec in "ML_DSA_44 mldsa44" "ML_DSA_65 mldsa65" "ML_DSA_87 mldsa87"; do + set -- $spec + alg="$1"; dir="$2" + run_case "$alg" "$dir" session --emu + run_case "$alg" "$dir" meas --meas + run_case "$alg" "$dir" challenge --challenge + done + + if [ -n "$FAILURES" ]; then + echo "::error::ML-DSA interop failures:$FAILURES" + exit 1 + fi + echo "All ML-DSA 44/65/87 interop cases passed." + + # --- ML-KEM interop: responder offers only ML-KEM (--dhe NONE) with ECDSA + # signing, so the handshake performs ML-KEM key exchange in isolation. + # The requester forces KEM-only advertisement with --kex. ek (<=1568 B) + # and ciphertext c (<=1568 B) fit one message under the responder's + # DataTransferSize, so this tests the KEM path without chunking. + - name: ML-KEM 512/768/1024 session + measurements + challenge + run: | + export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib + export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin + export SPDM_EMU_CERT_DIR=ecp384 + DEMO=./examples/spdm_demo + FAILURES="" + + wait_for_port() { + local i + for i in $(seq 1 50); do + if ss -ltn 2>/dev/null | grep -q ':2323 '; then return 0; fi + sleep 0.2 + done + return 1 + } + + # $1 emu KEM name, $2 demo --kex name, $3 label, then demo args. + run_kem() { + local kem="$1" kex="$2" label="$3"; shift 3 + echo "::group::$kem $label" + local attempt rc=1 emu + for attempt in 1 2 3; do + ( cd "$SPDM_EMU_PATH" && ./spdm_responder_emu --ver 1.4 \ + --hash SHA_384 --asym ECDSA_P384 --pqc_asym NONE \ + --dhe NONE --kem "$kem" --aead AES_256_GCM \ + >/tmp/pqc_emu_${kem}_${label}.log 2>&1 ) & + emu=$! + if wait_for_port; then + sleep 1 + if "$DEMO" "$@" --ver 1.4 --kex "$kex" --debug; then rc=0; else rc=$?; fi + else + rc=1 + fi + kill $emu 2>/dev/null || true + wait $emu 2>/dev/null || true + pkill -f spdm_responder_emu 2>/dev/null || true + [ $rc -eq 0 ] && break + echo "--- responder log (attempt $attempt) ---" + cat /tmp/pqc_emu_${kem}_${label}.log || true + echo "attempt $attempt for $kem $label failed (rc=$rc), retrying" + sleep 1 + done + echo "::endgroup::" + if [ $rc -ne 0 ]; then + echo "::error::$kem $label failed after retries (rc=$rc)" + FAILURES="$FAILURES $kem/$label" + else + echo "$kem $label: OK" + fi + } + + for spec in "ML_KEM_512 mlkem512" "ML_KEM_768 mlkem768" \ + "ML_KEM_1024 mlkem1024"; do + set -- $spec + kem="$1"; kex="$2" + run_kem "$kem" "$kex" session --emu + run_kem "$kem" "$kex" meas --meas + run_kem "$kem" "$kex" challenge --challenge + done + + if [ -n "$FAILURES" ]; then + echo "::error::ML-KEM interop failures:$FAILURES" + exit 1 + fi + echo "All ML-KEM 512/768/1024 interop cases passed." + + # --- Full post-quantum handshake: ML-KEM-768 key exchange + ML-DSA + # signing together, no classical asymmetric crypto. ML-DSA-65 fits one + # message; ML-DSA-87 (sig 4627 B) + ciphertext c (1088 B) exceeds the + # DataTransferSize, so this case also exercises CHUNK_GET reassembly - + # ML-KEM + ML-DSA + chunking in a single handshake. + - name: Full PQ (ML-KEM-768 + ML-DSA 65/87) session, attestation, key update, app data + run: | + export LD_LIBRARY_PATH=$HOME/wolfspdm-install/lib:$HOME/wolfssl-install/lib + export SPDM_EMU_PATH=$HOME/spdm-emu/build/bin + DEMO=./examples/spdm_demo + FAILURES="" + + wait_for_port() { + local i + for i in $(seq 1 50); do + if ss -ltn 2>/dev/null | grep -q ':2323 '; then return 0; fi + sleep 0.2 + done + return 1 + } + + # $1 pqc_asym (ML_DSA_xx), $2 cert dir, $3 label, then demo args. KEM is + # ML-KEM-768 throughout; the requester forces it with --kex mlkem768. + run_pq() { + local pqc="$1" certdir="$2" label="$3"; shift 3 + export SPDM_EMU_CERT_DIR="$certdir" + echo "::group::$pqc+ML_KEM_768 $label" + local attempt rc=1 emu + for attempt in 1 2 3; do + ( cd "$SPDM_EMU_PATH" && ./spdm_responder_emu --ver 1.4 \ + --hash SHA_384 --asym NONE --pqc_asym "$pqc" \ + --dhe NONE --kem ML_KEM_768 --aead AES_256_GCM \ + >/tmp/pqc_emu_${pqc}_kem_${label}.log 2>&1 ) & + emu=$! + if wait_for_port; then + sleep 1 + if "$DEMO" "$@" --ver 1.4 --kex mlkem768 --debug; then rc=0; else rc=$?; fi + else + rc=1 + fi + kill $emu 2>/dev/null || true + wait $emu 2>/dev/null || true + pkill -f spdm_responder_emu 2>/dev/null || true + [ $rc -eq 0 ] && break + echo "--- responder log (attempt $attempt) ---" + cat /tmp/pqc_emu_${pqc}_kem_${label}.log || true + echo "attempt $attempt for $pqc+KEM $label failed (rc=$rc), retrying" + sleep 1 + done + echo "::endgroup::" + if [ $rc -ne 0 ]; then + echo "::error::$pqc+ML_KEM_768 $label failed after retries (rc=$rc)" + FAILURES="$FAILURES $pqc+kem/$label" + else + echo "$pqc+ML_KEM_768 $label: OK" + fi + } + + for spec in "ML_DSA_65 mldsa65" "ML_DSA_87 mldsa87"; do + set -- $spec + pqc="$1"; dir="$2" + run_pq "$pqc" "$dir" session --emu + run_pq "$pqc" "$dir" meas --meas + run_pq "$pqc" "$dir" challenge --challenge + run_pq "$pqc" "$dir" heartbeat --heartbeat + run_pq "$pqc" "$dir" keyupdate --key-update + run_pq "$pqc" "$dir" appdata --app-data + done + + if [ -n "$FAILURES" ]; then + echo "::error::Full-PQ interop failures:$FAILURES" + exit 1 + fi + echo "All full-PQ (ML-KEM + ML-DSA) interop cases passed." + + - name: Upload logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: spdm-emu-pqc-logs-${{ matrix.os }}-${{ matrix.arch }}-dynmem-${{ matrix.dynamic-mem }} + path: | + config.log + test/*.log + /tmp/pqc_emu_*.log + retention-days: 5 diff --git a/.github/workflows/wolftpm-downstream.yml b/.github/workflows/wolftpm-downstream.yml index 6b55982..afffe1b 100644 --- a/.github/workflows/wolftpm-downstream.yml +++ b/.github/workflows/wolftpm-downstream.yml @@ -97,7 +97,7 @@ jobs: # The TPM profile compiles none of the standard requester - name: Check the standard requester is compiled out run: | - re='wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange|.*Heartbeat.*|.*KeyUpdate.*|DeriveUpdatedKeys|SendData|ReceiveData|EncryptMessage|DecryptMessage)' + re='wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange|.*Heartbeat.*|.*KeyUpdate.*|DeriveUpdatedKeys|SendData|ReceiveData|EncryptMessage|DecryptMessage|.*MlDsa.*|.*MlKem.*|SetKeyExchangePref)' bad=$(nm --defined-only src/.libs/libwolftpm.so | awk '{print $3}' | grep -E "^${re}$" || true) if [ -n "$bad" ]; then diff --git a/Makefile.am b/Makefile.am index fdad72c..edb2ecf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -59,7 +59,7 @@ nobase_include_HEADERS = \ # Test programs check_PROGRAMS = test/unit_test -test_unit_test_SOURCES = test/unit_test.c test/test_certs.h +test_unit_test_SOURCES = test/unit_test.c test/test_certs.h test/test_certs_mldsa.h test_unit_test_CPPFLAGS = -I$(srcdir)/wolfspdm -I$(srcdir)/src test_unit_test_LDADD = libwolfspdm.la -lwolfssl diff --git a/config.h.in b/config.h.in index c0cb00d..80db462 100644 --- a/config.h.in +++ b/config.h.in @@ -98,6 +98,12 @@ /* Disable GET_MEASUREMENTS */ #undef WOLFSPDM_NO_MEAS +/* Disable ML-DSA signatures */ +#undef WOLFSPDM_NO_MLDSA + +/* Disable ML-KEM key exchange */ +#undef WOLFSPDM_NO_MLKEM + /* Enable Nuvoton SPDM vendor commands */ #undef WOLFSPDM_NUVOTON diff --git a/configure.ac b/configure.ac index 2dd2254..f8fd4d5 100644 --- a/configure.ac +++ b/configure.ac @@ -122,6 +122,82 @@ if test "x$enable_app_data" = "xno"; then AC_DEFINE([WOLFSPDM_NO_APP_DATA], [1], [Disable the application data API]) fi +# ML-DSA (FIPS 204) and ML-KEM (FIPS 203) from DSP0274 1.4 follow the linked +# wolfSSL by default; both ride the certificate flow +AC_ARG_ENABLE([mldsa], + [AS_HELP_STRING([--disable-mldsa], [Disable ML-DSA signatures even if wolfSSL has them (default: auto)])], + [enable_mldsa=$enableval], + [enable_mldsa=auto]) +AC_ARG_ENABLE([mlkem], + [AS_HELP_STRING([--disable-mlkem], [Disable ML-KEM key exchange even if wolfSSL has it (default: auto)])], + [enable_mlkem=$enableval], + [enable_mlkem=auto]) + +have_mldsa_api=no +if test "x$enable_mldsa" != "xno" && test "x$enable_cert" = "xyes"; then + AC_MSG_CHECKING([for the wolfSSL wc_MlDsaKey context API]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include + #include + #ifndef WOLFSSL_HAVE_MLDSA + #error "no ML-DSA" + #endif + #include + #include + ]], [[ + MlDsaKey k; int res = 0; word32 oid = ML_DSA_44k + CTC_ML_DSA_87; + (void)wc_MlDsaKey_SetParams(&k, WC_ML_DSA_65); + (void)wc_MlDsaKey_ImportPubRaw(&k, 0, 0); + (void)wc_MlDsaKey_VerifyCtx(&k, 0, 0, 0, 0, 0, 0, &res); + (void)oid; + ]])], + [have_mldsa_api=yes], + [have_mldsa_api=no]) + AC_MSG_RESULT([$have_mldsa_api]) +fi +if test "x$have_mldsa_api" = "xyes"; then + enable_mldsa=yes +elif test "x$enable_mldsa" = "xyes"; then + AC_MSG_ERROR([--enable-mldsa needs the certificate requester and a wolfSSL built with --enable-mldsa that has the wc_MlDsaKey context API]) +else + enable_mldsa=no + AC_DEFINE([WOLFSPDM_NO_MLDSA], [1], [Disable ML-DSA signatures]) +fi + +have_mlkem_api=no +if test "x$enable_mlkem" != "xno" && test "x$enable_cert" = "xyes"; then + AC_MSG_CHECKING([for the wolfSSL wc_MlKemKey API]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[ + #include + #include + #ifndef WOLFSSL_HAVE_MLKEM + #error "no ML-KEM" + #endif + #include + ]], [[ + MlKemKey k; word32 len = 0; + (void)wc_MlKemKey_Init(&k, WC_ML_KEM_768, 0, 0); + (void)wc_MlKemKey_MakeKey(&k, 0); + (void)wc_MlKemKey_EncodePublicKey(&k, 0, 0); + (void)wc_MlKemKey_PublicKeySize(&k, &len); + (void)wc_MlKemKey_CipherTextSize(&k, &len); + (void)wc_MlKemKey_SharedSecretSize(&k, &len); + (void)wc_MlKemKey_Decapsulate(&k, 0, 0, 0); + (void)wc_MlKemKey_Free(&k); + ]])], + [have_mlkem_api=yes], + [have_mlkem_api=no]) + AC_MSG_RESULT([$have_mlkem_api]) +fi +if test "x$have_mlkem_api" = "xyes"; then + enable_mlkem=yes +elif test "x$enable_mlkem" = "xyes"; then + AC_MSG_ERROR([--enable-mlkem needs the certificate requester and a wolfSSL built with --enable-mlkem that has the wc_MlKemKey API]) +else + enable_mlkem=no + AC_DEFINE([WOLFSPDM_NO_MLKEM], [1], [Disable ML-KEM key exchange]) +fi + AC_ARG_ENABLE([chunking], [AS_HELP_STRING([--disable-chunking], [Disable CHUNK_SEND/CHUNK_GET large message chunking (default: enabled)])], [enable_chunking=$enableval], @@ -244,6 +320,8 @@ echo " Dynamic mem: $enable_dynamic_mem" echo " MCTP: $enable_mctp" echo " Standard: $enable_cert" echo " App data: $enable_app_data" +echo " ML-DSA: $enable_mldsa" +echo " ML-KEM: $enable_mlkem" echo " Chunking: $enable_chunking" echo " Meas: $enable_meas" echo " Challenge: $enable_challenge" diff --git a/src/spdm_attest.c b/src/spdm_attest.c index 130c096..a1f0271 100644 --- a/src/spdm_attest.c +++ b/src/spdm_attest.c @@ -32,7 +32,7 @@ /* OpaqueLength, room for OpaqueData, RequesterContext and the signature */ #define WOLFSPDM_ATTEST_TAIL_SZ (2 + 512 + SPDM_REQ_CONTEXT_SZ + \ - WOLFSPDM_ECC_SIG_SIZE) + WOLFSPDM_MAX_SIG_SIZE) /* 1.3+ requests end with a RequesterContext the response echoes */ static word32 wolfSPDM_ReqContextSz(const WOLFSPDM_CTX* ctx) @@ -77,7 +77,6 @@ static int wolfSPDM_RunVerify(WOLFSPDM_CTX* ctx, wc_Sha384* sha, byte* state, const byte* rsp, word32 sigOff) { byte digest[WOLFSPDM_HASH_SIZE]; - byte signHash[WOLFSPDM_HASH_SIZE]; int rc; rc = wolfSPDM_RunAdd(sha, req, reqSz, rsp, sigOff); @@ -88,13 +87,10 @@ static int wolfSPDM_RunVerify(WOLFSPDM_CTX* ctx, wc_Sha384* sha, byte* state, *state = WOLFSPDM_RUN_NONE; if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, label, labelSz, - digest, signHash); - } - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_VerifySignature(ctx, signHash, WOLFSPDM_HASH_SIZE, - rsp + sigOff, WOLFSPDM_ECC_SIG_SIZE); + rc = wolfSPDM_VerifyRspSig(ctx, label, labelSz, digest, rsp + sigOff, + wolfSPDM_SigSize(ctx)); } + wc_ForceZero(digest, sizeof(digest)); return rc; } @@ -205,7 +201,7 @@ int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* req, if (i != buf[4] || off != recordEnd || !wolfSPDM_ParseTail(ctx, req, reqSz, buf, bufSz, recordEnd + SPDM_NONCE_SZ, - signedReq ? WOLFSPDM_ECC_SIG_SIZE : 0, sigOff)) { + signedReq ? wolfSPDM_SigSize(ctx) : 0, sigOff)) { return WOLFSPDM_E_MEASUREMENT; } return WOLFSPDM_SUCCESS; @@ -392,7 +388,7 @@ int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, const byte* req, bufSz < off || XMEMCMP(&buf[4], ctx->certChainHash, WOLFSPDM_HASH_SIZE) != 0 || !wolfSPDM_ParseTail(ctx, req, reqSz, buf, bufSz, off, - WOLFSPDM_ECC_SIG_SIZE, sigOff)) { + wolfSPDM_SigSize(ctx), sigOff)) { return WOLFSPDM_E_CHALLENGE; } return WOLFSPDM_SUCCESS; diff --git a/src/spdm_chunk.c b/src/spdm_chunk.c index 11a6ef7..1a2f376 100644 --- a/src/spdm_chunk.c +++ b/src/spdm_chunk.c @@ -315,13 +315,24 @@ int wolfSPDM_ChunkExchange(WOLFSPDM_CTX* ctx, int secured, return rc; } +#endif /* !WOLFSPDM_NO_CHUNK */ + +#ifndef WOLFSPDM_NO_CERT +/* Unchunked, a request must fit the responder's DataTransferSize */ int wolfSPDM_ClearExchange(WOLFSPDM_CTX* ctx, const byte* req, word32 reqSz, byte* rsp, word32* rspSz) { +#ifndef WOLFSPDM_NO_CHUNK if (ctx != NULL && wolfSPDM_ChunkOn(ctx)) { return wolfSPDM_ChunkExchange(ctx, 0, req, reqSz, rsp, rspSz); } +#endif + if (ctx != NULL && !wolfSPDM_IsTcgMode(ctx) && + ctx->dataTransferSize != 0 && reqSz > ctx->dataTransferSize) { + wolfSPDM_DebugPrint(ctx, "Request of %u bytes exceeds the responder " + "DataTransferSize %u\n", reqSz, ctx->dataTransferSize); + return WOLFSPDM_E_BUFFER_SMALL; + } return wolfSPDM_SendReceive(ctx, req, reqSz, rsp, rspSz); } - -#endif /* !WOLFSPDM_NO_CHUNK */ +#endif /* !WOLFSPDM_NO_CERT */ diff --git a/src/spdm_context.c b/src/spdm_context.c index e21de2a..91c0a92 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -53,6 +53,11 @@ int wolfSPDM_Init(WOLFSPDM_CTX* ctx) /* Set default session ID (0x0001 is valid; 0x0000/0xFFFF are reserved) */ ctx->reqSessionId = 0x0001; +#ifdef WOLFSPDM_HAVE_MLKEM + /* Offer ECDHE and every built ML-KEM set; the responder picks one */ + ctx->kexAdvDhe = 1; + ctx->kexAdvKem = WOLFSPDM_MLKEM_SETS; +#endif ctx->flags.initialized = 1; /* isDynamic remains 0, only wolfSPDM_New sets it */ @@ -96,10 +101,7 @@ void wolfSPDM_Free(WOLFSPDM_CTX* ctx) wc_FreeRng(&ctx->rng); } - /* Free ephemeral key */ - if (ctx->flags.ephemeralKeyInit) { - wc_ecc_free(&ctx->ephemeralKey); - } + wolfSPDM_FreeEphemeralKey(ctx); #if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) wolfSPDM_AttestFree(ctx); #endif @@ -223,6 +225,22 @@ int wolfSPDM_SetMaxVersion(WOLFSPDM_CTX* ctx, byte maxVersion) return WOLFSPDM_SUCCESS; } +int wolfSPDM_SetRequesterSessionId(WOLFSPDM_CTX* ctx, word16 reqSessionId) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + /* 0x0000/0xFFFF are reserved (DSP0277); a low byte of 0x10-0x1F would + * make a secured record look like a clear SPDM message to the TCG + * binding */ + if (reqSessionId == 0x0000 || reqSessionId == 0xFFFF || + ((reqSessionId & 0xFF) >= 0x10 && (reqSessionId & 0xFF) <= 0x1F)) { + return WOLFSPDM_E_INVALID_ARG; + } + ctx->reqSessionId = reqSessionId; + return WOLFSPDM_SUCCESS; +} + byte wolfSPDM_GetLastPeerError(WOLFSPDM_CTX* ctx) { return (ctx == NULL) ? 0 : ctx->lastPeerErrorCode; @@ -313,6 +331,11 @@ byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx) return ctx->spdmVersion; } +byte wolfSPDM_GetVersion_Negotiated(WOLFSPDM_CTX* ctx) +{ + return wolfSPDM_GetNegotiatedVersion(ctx); +} + #ifdef WOLFSPDM_TCG word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx) { @@ -429,11 +452,7 @@ int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) ctx->sharedSecretSz = 0; wc_ForceZero(ctx->th1, sizeof(ctx->th1)); wc_ForceZero(ctx->th2, sizeof(ctx->th2)); - /* Free ephemeral ECC key */ - if (ctx->flags.ephemeralKeyInit) { - wc_ecc_free(&ctx->ephemeralKey); - ctx->flags.ephemeralKeyInit = 0; - } + wolfSPDM_FreeEphemeralKey(ctx); return rc; } diff --git a/src/spdm_crypto.c b/src/spdm_crypto.c index 2651098..6620785 100644 --- a/src/spdm_crypto.c +++ b/src/spdm_crypto.c @@ -57,7 +57,25 @@ int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz) return WOLFSPDM_SUCCESS; } -/* ----- ECDHE Key Generation (P-384) ----- */ +/* ----- Ephemeral Key Exchange ----- */ + +void wolfSPDM_FreeEphemeralKey(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL || !ctx->flags.ephemeralKeyInit) { + return; + } +#ifdef WOLFSPDM_HAVE_MLKEM + if (ctx->flags.ephemeralIsKem) { + wc_MlKemKey_Free(&ctx->ephemeral.mlkem); + ctx->flags.ephemeralIsKem = 0; + } + else +#endif + { + wc_ecc_free(WOLFSPDM_EPH_ECC(ctx)); + } + ctx->flags.ephemeralKeyInit = 0; +} int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx) { @@ -71,30 +89,27 @@ int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx) return WOLFSPDM_E_BAD_STATE; } - /* Free existing key if any */ - if (ctx->flags.ephemeralKeyInit) { - wc_ecc_free(&ctx->ephemeralKey); - ctx->flags.ephemeralKeyInit = 0; - } + wolfSPDM_FreeEphemeralKey(ctx); /* Initialize new key */ - rc = wc_ecc_init(&ctx->ephemeralKey); + rc = wc_ecc_init(WOLFSPDM_EPH_ECC(ctx)); if (rc != 0) { return WOLFSPDM_E_CRYPTO_FAIL; } /* Generate P-384 key pair */ - rc = wc_ecc_make_key(&ctx->rng, WOLFSPDM_ECC_KEY_SIZE, &ctx->ephemeralKey); + rc = wc_ecc_make_key(&ctx->rng, WOLFSPDM_ECC_KEY_SIZE, + WOLFSPDM_EPH_ECC(ctx)); if (rc != 0) { - wc_ecc_free(&ctx->ephemeralKey); + wc_ecc_free(WOLFSPDM_EPH_ECC(ctx)); return WOLFSPDM_E_CRYPTO_FAIL; } /* Attach RNG so timing-resistant scalar-mul inside wc_ecc_shared_secret * doesn't fail with MISSING_RNG_E in builds that enable hardening. */ - rc = wc_ecc_set_rng(&ctx->ephemeralKey, &ctx->rng); + rc = wc_ecc_set_rng(WOLFSPDM_EPH_ECC(ctx), &ctx->rng); if (rc != 0) { - wc_ecc_free(&ctx->ephemeralKey); + wc_ecc_free(WOLFSPDM_EPH_ECC(ctx)); return WOLFSPDM_E_CRYPTO_FAIL; } @@ -118,13 +133,18 @@ int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, if (!ctx->flags.ephemeralKeyInit) { return WOLFSPDM_E_BAD_STATE; } +#ifdef WOLFSPDM_HAVE_MLKEM + if (ctx->flags.ephemeralIsKem) { + return WOLFSPDM_E_BAD_STATE; + } +#endif if (*pubKeyXSz < WOLFSPDM_ECC_KEY_SIZE || *pubKeyYSz < WOLFSPDM_ECC_KEY_SIZE) { return WOLFSPDM_E_BUFFER_SMALL; } - rc = wc_ecc_export_public_raw(&ctx->ephemeralKey, + rc = wc_ecc_export_public_raw(WOLFSPDM_EPH_ECC(ctx), pubKeyX, pubKeyXSz, pubKeyY, pubKeyYSz); if (rc != 0) { return WOLFSPDM_E_CRYPTO_FAIL; @@ -155,6 +175,11 @@ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, if (!ctx->flags.ephemeralKeyInit) { return WOLFSPDM_E_BAD_STATE; } +#ifdef WOLFSPDM_HAVE_MLKEM + if (ctx->flags.ephemeralIsKem) { + return WOLFSPDM_E_BAD_STATE; + } +#endif rc = wc_ecc_init(&peerKey); if (rc == 0) { @@ -175,7 +200,7 @@ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, /* Compute ECDH shared secret */ if (rc == 0) { ctx->sharedSecretSz = sizeof(ctx->sharedSecret); - rc = wc_ecc_shared_secret(&ctx->ephemeralKey, &peerKey, + rc = wc_ecc_shared_secret(WOLFSPDM_EPH_ECC(ctx), &peerKey, ctx->sharedSecret, &ctx->sharedSecretSz); if (rc != 0) { wolfSPDM_DebugPrint(ctx, "ECDH shared_secret failed: %d\n", rc); @@ -199,6 +224,160 @@ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } +#ifdef WOLFSPDM_HAVE_MLKEM +/* ----- ML-KEM Key Exchange (DSP0274 1.4) ----- */ + +static int wolfSPDM_MlKemType(word16 kemAlgSel, int* type) +{ + switch (kemAlgSel & WOLFSPDM_MLKEM_SETS) { + case SPDM_KEM_ALGO_ML_KEM_512: *type = WC_ML_KEM_512; break; + case SPDM_KEM_ALGO_ML_KEM_768: *type = WC_ML_KEM_768; break; + case SPDM_KEM_ALGO_ML_KEM_1024: *type = WC_ML_KEM_1024; break; + default: return WOLFSPDM_E_ALGO_MISMATCH; + } + return WOLFSPDM_SUCCESS; +} + +/* Generate the ephemeral ML-KEM key pair and export the encapsulation key */ +int wolfSPDM_GenerateMlKemKey(WOLFSPDM_CTX* ctx, byte* ek, word32* ekSz) +{ + word32 pubSz = 0; + int type = 0; + int rc; + + if (ctx == NULL || ek == NULL || ekSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (!ctx->flags.rngInitialized) { + return WOLFSPDM_E_BAD_STATE; + } + rc = wolfSPDM_MlKemType(ctx->kemAlgSel, &type); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_FreeEphemeralKey(ctx); + if (wc_MlKemKey_Init(&ctx->ephemeral.mlkem, type, NULL, + INVALID_DEVID) != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + ctx->flags.ephemeralKeyInit = 1; + ctx->flags.ephemeralIsKem = 1; + + if (wc_MlKemKey_MakeKey(&ctx->ephemeral.mlkem, &ctx->rng) != 0 || + wc_MlKemKey_PublicKeySize(&ctx->ephemeral.mlkem, &pubSz) != 0) { + rc = WOLFSPDM_E_CRYPTO_FAIL; + } + else if (pubSz > *ekSz) { + rc = WOLFSPDM_E_BUFFER_SMALL; + } + else if (wc_MlKemKey_EncodePublicKey(&ctx->ephemeral.mlkem, ek, + pubSz) != 0) { + rc = WOLFSPDM_E_CRYPTO_FAIL; + } + if (rc == WOLFSPDM_SUCCESS) { + *ekSz = pubSz; + wolfSPDM_DebugPrint(ctx, "Generated ML-KEM key (ek %u bytes)\n", + pubSz); + } + else { + wolfSPDM_FreeEphemeralKey(ctx); + } + return rc; +} + +/* Recover the shared secret from the responder's ciphertext; FIPS 203 + * implicit rejection means a bad ciphertext only shows up at FINISH */ +int wolfSPDM_MlKemDecapsulate(WOLFSPDM_CTX* ctx, const byte* ct, word32 ctSz) +{ + word32 expCtSz = 0; + word32 ssSz = 0; + int rc = WOLFSPDM_SUCCESS; + + if (ctx == NULL || ct == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (!ctx->flags.ephemeralKeyInit || !ctx->flags.ephemeralIsKem) { + return WOLFSPDM_E_BAD_STATE; + } + + if (wc_MlKemKey_CipherTextSize(&ctx->ephemeral.mlkem, &expCtSz) != 0 || + wc_MlKemKey_SharedSecretSize(&ctx->ephemeral.mlkem, &ssSz) != 0 || + ssSz > sizeof(ctx->sharedSecret)) { + rc = WOLFSPDM_E_CRYPTO_FAIL; + } + else if (ctSz != expCtSz) { + rc = WOLFSPDM_E_KEY_EXCHANGE; + } + else if (wc_MlKemKey_Decapsulate(&ctx->ephemeral.mlkem, ctx->sharedSecret, + ct, ctSz) != 0) { + rc = WOLFSPDM_E_CRYPTO_FAIL; + } + if (rc == WOLFSPDM_SUCCESS) { + ctx->sharedSecretSz = ssSz; + wolfSPDM_DebugPrint(ctx, "ML-KEM shared secret (%u bytes)\n", ssSz); + } + else { + wc_ForceZero(ctx->sharedSecret, sizeof(ctx->sharedSecret)); + ctx->sharedSecretSz = 0; + } + return rc; +} +#endif /* WOLFSPDM_HAVE_MLKEM */ + +#ifdef WOLFSPDM_HAVE_MLDSA +/* ----- ML-DSA Signature Verification (DSP0274 1.4) ----- */ + +/* Pure ML-DSA verify of msg under a raw public key of the given level */ +int wolfSPDM_MlDsaVerify(byte level, const byte* pub, word32 pubSz, + const byte* context, word32 contextSz, const byte* msg, word32 msgSz, + const byte* sig, word32 sigSz) +{ +#ifdef WOLFSPDM_DYNAMIC_MEMORY + MlDsaKey* key; +#else + MlDsaKey keyBuf; + MlDsaKey* key = &keyBuf; +#endif + int keyInit = 0; + int verified = 0; + int rc; + + if (pub == NULL || msg == NULL || sig == NULL || contextSz > 255 || + (context == NULL && contextSz != 0)) { + return WOLFSPDM_E_INVALID_ARG; + } +#ifdef WOLFSPDM_DYNAMIC_MEMORY + key = (MlDsaKey*)XMALLOC(sizeof(MlDsaKey), NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (key == NULL) { + return WOLFSPDM_E_NO_MEMORY; + } +#endif + + rc = wc_MlDsaKey_Init(key, NULL, INVALID_DEVID); + if (rc == 0) { + keyInit = 1; + rc = wc_MlDsaKey_SetParams(key, level); + } + if (rc == 0) { + rc = wc_MlDsaKey_ImportPubRaw(key, pub, pubSz); + } + if (rc == 0) { + rc = wc_MlDsaKey_VerifyCtx(key, sig, sigSz, context, (byte)contextSz, + msg, msgSz, &verified); + } + if (keyInit) { + wc_MlDsaKey_Free(key); + } +#ifdef WOLFSPDM_DYNAMIC_MEMORY + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + + return (rc == 0 && verified == 1) ? WOLFSPDM_SUCCESS : + WOLFSPDM_E_BAD_SIGNATURE; +} +#endif /* WOLFSPDM_HAVE_MLDSA */ + /* ----- ECDSA Signature Verification (P-384) ----- */ int wolfSPDM_ExtractEccPoint(const byte* pubKey, word32 pubKeySz, diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 6fa9482..98f7450 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -48,6 +48,12 @@ #include #include #include +#ifdef WOLFSPDM_HAVE_MLDSA + #include +#endif +#ifdef WOLFSPDM_HAVE_MLKEM + #include +#endif #if defined(LIBWOLFSSL_VERSION_HEX) && LIBWOLFSSL_VERSION_HEX < 0x05008004 /* wc_ForceZero added in wolfSSL v5.8.4 */ @@ -130,10 +136,17 @@ struct WOLFSPDM_CTX { byte spdmVersion; /* Negotiated SPDM version */ byte lastPeerErrorCode; /* Last SPDM ERROR Param1 (0 = none) */ - /* Ephemeral ECDHE key (generated for KEY_EXCHANGE) */ + /* Ephemeral key for KEY_EXCHANGE: ECDHE P-384, or ML-KEM when negotiated */ +#ifdef WOLFSPDM_HAVE_MLKEM + union { + ecc_key ecc; + MlKemKey mlkem; + } ephemeral; +#else ecc_key ephemeralKey; +#endif - /* ECDH shared secret (P-384 X-coordinate = 48 bytes) */ + /* ECDH shared secret (P-384 X-coordinate = 48 bytes, ML-KEM 32) */ byte sharedSecret[WOLFSPDM_ECC_KEY_SIZE]; word32 sharedSecretSz; @@ -172,8 +185,8 @@ struct WOLFSPDM_CTX { word16 rspSessionId; /* Responder's session ID */ word32 sessionId; /* Combined: reqSessionId | (rspSessionId << 16) */ - /* Responder's identity public key (for cert-less mode like Nuvoton) */ - byte rspPubKey[WOLFSPDM_PUBKEY_BUF_SZ / 2]; /* pinned raw X||Y */ + /* Responder's identity public key: raw P-384 X||Y, or an ML-DSA key */ + byte rspPubKey[WOLFSPDM_RSP_PUBKEY_SZ]; word32 rspPubKeyLen; /* Mutual auth fields from KEY_EXCHANGE_RSP */ @@ -199,6 +212,14 @@ struct WOLFSPDM_CTX { byte currentSlotId; #ifndef WOLFSPDM_NO_CHUNK byte chunkHandle; /* next CHUNK_SEND handle */ +#endif +#ifdef WOLFSPDM_HAVE_MLDSA + word32 pqcAsymSel; /* negotiated ML-DSA set, 0 for ECDSA P-384 */ +#endif +#ifdef WOLFSPDM_HAVE_MLKEM + word16 kemAlgSel; /* negotiated ML-KEM set, 0 for ECDHE */ + word16 kexAdvKem; /* ML-KEM sets offered at SPDM 1.4 */ + byte kexAdvDhe; /* offer ECDHE P-384 */ #endif byte certChain[WOLFSPDM_MAX_CERT_CHAIN]; byte trustedCA[WOLFSPDM_MAX_TRUSTED_CA]; @@ -222,6 +243,9 @@ struct WOLFSPDM_CTX { unsigned int isDynamic : 1; /* Set by wolfSPDM_New(), checked by Free */ unsigned int rngInitialized : 1; unsigned int ephemeralKeyInit : 1; +#ifdef WOLFSPDM_HAVE_MLKEM + unsigned int ephemeralIsKem : 1; /* live union member is mlkem */ +#endif unsigned int hasRspPubKey : 1; #ifdef WOLFSPDM_MUTUAL_AUTH unsigned int hasReqKeyPair : 1; @@ -256,6 +280,94 @@ static WC_INLINE int wolfSPDM_ChunkOn(const WOLFSPDM_CTX* ctx) } #endif +#ifdef WOLFSPDM_HAVE_MLKEM + #define WOLFSPDM_EPH_ECC(ctx) (&(ctx)->ephemeral.ecc) +#else + #define WOLFSPDM_EPH_ECC(ctx) (&(ctx)->ephemeralKey) +#endif + +#ifdef WOLFSPDM_HAVE_MLDSA +/* Offer only the ML-DSA sets this wolfSSL was built with */ +#ifndef WOLFSSL_NO_ML_DSA_44 + #define WOLFSPDM_MLDSA_44_SET SPDM_PQC_ASYM_ALGO_ML_DSA_44 +#else + #define WOLFSPDM_MLDSA_44_SET 0 +#endif +#ifndef WOLFSSL_NO_ML_DSA_65 + #define WOLFSPDM_MLDSA_65_SET SPDM_PQC_ASYM_ALGO_ML_DSA_65 +#else + #define WOLFSPDM_MLDSA_65_SET 0 +#endif +#ifndef WOLFSSL_NO_ML_DSA_87 + #define WOLFSPDM_MLDSA_87_SET SPDM_PQC_ASYM_ALGO_ML_DSA_87 +#else + #define WOLFSPDM_MLDSA_87_SET 0 +#endif +#define WOLFSPDM_MLDSA_SETS (WOLFSPDM_MLDSA_44_SET | WOLFSPDM_MLDSA_65_SET | \ + WOLFSPDM_MLDSA_87_SET) + +/* wolfCrypt level of an ML-DSA set, 0 if not one this build supports */ +static WC_INLINE byte wolfSPDM_MlDsaLevel(word32 pqcAsymSel) +{ + if ((pqcAsymSel & WOLFSPDM_MLDSA_SETS) == 0) { + return 0; + } + switch (pqcAsymSel) { + case SPDM_PQC_ASYM_ALGO_ML_DSA_44: return WC_ML_DSA_44; + case SPDM_PQC_ASYM_ALGO_ML_DSA_65: return WC_ML_DSA_65; + case SPDM_PQC_ASYM_ALGO_ML_DSA_87: return WC_ML_DSA_87; + default: return 0; + } +} +#endif /* WOLFSPDM_HAVE_MLDSA */ + +#ifdef WOLFSPDM_HAVE_MLKEM +#ifdef WOLFSSL_WC_ML_KEM_512 + #define WOLFSPDM_MLKEM_512_SET SPDM_KEM_ALGO_ML_KEM_512 +#else + #define WOLFSPDM_MLKEM_512_SET 0 +#endif +#ifdef WOLFSSL_WC_ML_KEM_768 + #define WOLFSPDM_MLKEM_768_SET SPDM_KEM_ALGO_ML_KEM_768 +#else + #define WOLFSPDM_MLKEM_768_SET 0 +#endif +#ifdef WOLFSSL_WC_ML_KEM_1024 + #define WOLFSPDM_MLKEM_1024_SET SPDM_KEM_ALGO_ML_KEM_1024 +#else + #define WOLFSPDM_MLKEM_1024_SET 0 +#endif +#define WOLFSPDM_MLKEM_SETS ((word16)(WOLFSPDM_MLKEM_512_SET | \ + WOLFSPDM_MLKEM_768_SET | WOLFSPDM_MLKEM_1024_SET)) +#endif /* WOLFSPDM_HAVE_MLKEM */ + +/* wolfCrypt ML-DSA level of the responder key, 0 for ECDSA P-384 */ +static WC_INLINE byte wolfSPDM_RspMlDsaLevel(const WOLFSPDM_CTX* ctx) +{ +#ifdef WOLFSPDM_HAVE_MLDSA + if (!wolfSPDM_IsTcgMode(ctx)) { + return wolfSPDM_MlDsaLevel(ctx->pqcAsymSel); + } +#endif + (void)ctx; + return 0; +} + +/* SigLen of the negotiated responder signature */ +static WC_INLINE word32 wolfSPDM_SigSize(const WOLFSPDM_CTX* ctx) +{ +#ifdef WOLFSPDM_HAVE_MLDSA + switch (wolfSPDM_RspMlDsaLevel(ctx)) { + case WC_ML_DSA_44: return WOLFSPDM_MLDSA44_SIG_SIZE; + case WC_ML_DSA_65: return WOLFSPDM_MLDSA65_SIG_SIZE; + case WC_ML_DSA_87: return WOLFSPDM_MLDSA87_SIG_SIZE; + default: break; + } +#endif + (void)ctx; + return WOLFSPDM_ECC_SIG_SIZE; +} + /* ----- Byte-Order Helpers ----- */ static WC_INLINE void SPDM_Set16LE(byte* buf, word16 val) { @@ -391,6 +503,18 @@ WOLFSPDM_TEST_API int wolfSPDM_ExtractEccPoint(const byte* pubKey, WOLFSPDM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, const byte* sig, word32 sigSz); +WOLFSPDM_API void wolfSPDM_FreeEphemeralKey(WOLFSPDM_CTX* ctx); +#ifdef WOLFSPDM_HAVE_MLDSA +WOLFSPDM_API int wolfSPDM_MlDsaVerify(byte level, const byte* pub, + word32 pubSz, const byte* context, word32 contextSz, + const byte* msg, word32 msgSz, const byte* sig, word32 sigSz); +#endif +#ifdef WOLFSPDM_HAVE_MLKEM +WOLFSPDM_API int wolfSPDM_GenerateMlKemKey(WOLFSPDM_CTX* ctx, byte* ek, + word32* ekSz); +WOLFSPDM_API int wolfSPDM_MlKemDecapsulate(WOLFSPDM_CTX* ctx, const byte* ct, + word32 ctSz); +#endif /* ----- Internal Function Declarations - Key Derivation ----- */ @@ -407,6 +531,11 @@ WOLFSPDM_API int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* WOLFSPDM_LOCAL int wolfSPDM_BuildSignedHash(byte spdmVersion, const char* contextStr, word32 contextStrLen, const byte* inputDigest, byte* outputDigest); +/* Verify a responder signature over messageHash with the negotiated + * algorithm; sigSz must be wolfSPDM_SigSize(ctx) */ +WOLFSPDM_API int wolfSPDM_VerifyRspSig(WOLFSPDM_CTX* ctx, + const char* contextStr, word32 contextStrLen, const byte* messageHash, + const byte* sig, word32 sigSz); /* ----- Internal Function Declarations - Message Building ----- */ @@ -510,6 +639,8 @@ WOLFSPDM_LOCAL int wolfSPDM_SecuredXfer(WOLFSPDM_CTX* ctx, /* Request/response that chunks with CHUNK_SEND and CHUNK_GET as needed */ WOLFSPDM_TEST_API int wolfSPDM_ChunkExchange(WOLFSPDM_CTX* ctx, int secured, const byte* req, word32 reqSz, byte* rsp, word32* rspSz); +#endif +#ifndef WOLFSPDM_NO_CERT WOLFSPDM_LOCAL int wolfSPDM_ClearExchange(WOLFSPDM_CTX* ctx, const byte* req, word32 reqSz, byte* rsp, word32* rspSz); #else diff --git a/src/spdm_msg.c b/src/spdm_msg.c index d9d26ba..730d9cb 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -105,12 +105,12 @@ static void wolfSPDM_KeyExOpaque(const WOLFSPDM_CTX* ctx, int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { word32 offset = 0; - byte pubKeyX[WOLFSPDM_ECC_KEY_SIZE]; - byte pubKeyY[WOLFSPDM_ECC_KEY_SIZE]; - word32 pubKeyXSz = sizeof(pubKeyX); - word32 pubKeyYSz = sizeof(pubKeyY); + word32 exSz = WOLFSPDM_ECC_POINT_SIZE; + word32 xSz = WOLFSPDM_ECC_KEY_SIZE; + word32 ySz = WOLFSPDM_ECC_KEY_SIZE; const byte* opaque; word32 opaqueSz; + int useKem = 0; int rc; if (ctx == NULL) { @@ -120,53 +120,61 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) if (opaque == NULL) { return WOLFSPDM_E_NOT_AVAILABLE; } +#ifdef WOLFSPDM_HAVE_MLKEM + useKem = (ctx->kemAlgSel != 0 && !wolfSPDM_IsTcgMode(ctx)); +#endif - /* Require exactly the encoded request size */ - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, WOLFSPDM_KEYEX_FIXED_SZ + opaqueSz); - - rc = wolfSPDM_GenerateEphemeralKey(ctx); - if (rc == WOLFSPDM_SUCCESS) - rc = wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &pubKeyXSz, - pubKeyY, &pubKeyYSz); - - if (rc == WOLFSPDM_SUCCESS) { - XMEMSET(buf, 0, *bufSz); + /* ECDHE: exactly the encoded request size; ML-KEM bounds its own key */ + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, WOLFSPDM_KEYEX_FIXED_SZ + opaqueSz - + (useKem ? WOLFSPDM_ECC_POINT_SIZE : 0)); + XMEMSET(buf, 0, *bufSz); - /* Use negotiated SPDM version (not hardcoded 1.2) */ - buf[offset++] = ctx->spdmVersion; - buf[offset++] = SPDM_KEY_EXCHANGE; - buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ - /* SlotID: 0xFF = provisioned public key (TCG), else the cert slot */ + /* Use negotiated SPDM version (not hardcoded 1.2) */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_KEY_EXCHANGE; + buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ + /* SlotID: 0xFF = provisioned public key (TCG), else the cert slot */ #ifndef WOLFSPDM_NO_CERT - buf[offset++] = wolfSPDM_IsTcgMode(ctx) ? 0xFF : ctx->currentSlotId; + buf[offset++] = wolfSPDM_IsTcgMode(ctx) ? 0xFF : ctx->currentSlotId; #else - buf[offset++] = wolfSPDM_IsTcgMode(ctx) ? 0xFF : 0x00; + buf[offset++] = wolfSPDM_IsTcgMode(ctx) ? 0xFF : 0x00; #endif - /* ReqSessionID (2 LE) */ - buf[offset++] = (byte)(ctx->reqSessionId & 0xFF); - buf[offset++] = (byte)((ctx->reqSessionId >> 8) & 0xFF); + /* ReqSessionID (2 LE) */ + buf[offset++] = (byte)(ctx->reqSessionId & 0xFF); + buf[offset++] = (byte)((ctx->reqSessionId >> 8) & 0xFF); - buf[offset++] = 0x00; /* SessionPolicy */ - buf[offset++] = 0x00; /* Reserved */ + buf[offset++] = 0x00; /* SessionPolicy */ + buf[offset++] = 0x00; /* Reserved */ - /* RandomData (32 bytes) */ - rc = wolfSPDM_GetRandom(ctx, &buf[offset], WOLFSPDM_RANDOM_SIZE); + /* RandomData (32 bytes) */ + rc = wolfSPDM_GetRandom(ctx, &buf[offset], WOLFSPDM_RANDOM_SIZE); + offset += WOLFSPDM_RANDOM_SIZE; + + /* ExchangeData: the ML-KEM encapsulation key, or ECDHE X || Y */ +#ifdef WOLFSPDM_HAVE_MLKEM + if (rc == WOLFSPDM_SUCCESS && useKem) { + exSz = *bufSz - offset - opaqueSz; + rc = wolfSPDM_GenerateMlKemKey(ctx, &buf[offset], &exSz); + } + else +#endif + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_GenerateEphemeralKey(ctx); if (rc == WOLFSPDM_SUCCESS) { - offset += WOLFSPDM_RANDOM_SIZE; + rc = wolfSPDM_ExportEphemeralPubKey(ctx, &buf[offset], &xSz, + &buf[offset + WOLFSPDM_ECC_KEY_SIZE], &ySz); + } + } - /* ExchangeData: X || Y */ - XMEMCPY(&buf[offset], pubKeyX, WOLFSPDM_ECC_KEY_SIZE); - offset += WOLFSPDM_ECC_KEY_SIZE; - XMEMCPY(&buf[offset], pubKeyY, WOLFSPDM_ECC_KEY_SIZE); - offset += WOLFSPDM_ECC_KEY_SIZE; + if (rc == WOLFSPDM_SUCCESS) { + offset += exSz; - /* OpaqueData for secured message version negotiation */ - XMEMCPY(&buf[offset], opaque, opaqueSz); - offset += opaqueSz; + /* OpaqueData for secured message version negotiation */ + XMEMCPY(&buf[offset], opaque, opaqueSz); + offset += opaqueSz; - *bufSz = offset; - } + *bufSz = offset; } return rc; @@ -174,59 +182,119 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) /* ----- Shared Signing Helpers ----- */ -/* Build SPDM 1.2+ signed hash per DSP0274: +/* Build the SPDM 1.2+ signing input per DSP0274 (148 bytes at most): * M = combined_spdm_prefix || zero_pad || context_str || inputDigest - * outputDigest = Hash(M) * * combined_spdm_prefix = "dmtf-spdm-v1.X.*" x4 = 64 bytes * zero_pad = (36 - contextStrLen) bytes of 0x00 * context_str = signing context string (variable length, max 36) */ -int wolfSPDM_BuildSignedHash(byte spdmVersion, +static int wolfSPDM_BuildSignedMsg(byte spdmVersion, const char* contextStr, word32 contextStrLen, - const byte* inputDigest, byte* outputDigest) + const byte* inputDigest, byte* signMsg, word32* signMsgLen) { - byte signMsg[200]; /* 64 + 36 + 48 = 148 bytes max */ - word32 signMsgLen = 0; - word32 zeroPadLen; + word32 len = 0; byte majorVer, minorVer; - int i, rc; + int i; + + if (contextStrLen > 36) { + return WOLFSPDM_E_INVALID_ARG; + } majorVer = (byte)('0' + ((spdmVersion >> 4) & 0xF)); minorVer = (byte)('0' + (spdmVersion & 0xF)); /* combined_spdm_prefix: "dmtf-spdm-v1.X.*" x4 = 64 bytes */ for (i = 0; i < 4; i++) { - XMEMCPY(&signMsg[signMsgLen], "dmtf-spdm-v1.2.*", 16); - signMsg[signMsgLen + 11] = majorVer; - signMsg[signMsgLen + 13] = minorVer; - signMsg[signMsgLen + 15] = '*'; - signMsgLen += 16; + XMEMCPY(&signMsg[len], "dmtf-spdm-v1.2.*", 16); + signMsg[len + 11] = majorVer; + signMsg[len + 13] = minorVer; + signMsg[len + 15] = '*'; + len += 16; } /* Zero padding: 36 - contextStrLen bytes */ - if (contextStrLen > 36) { - return WOLFSPDM_E_INVALID_ARG; - } - zeroPadLen = 36 - contextStrLen; - XMEMSET(&signMsg[signMsgLen], 0x00, zeroPadLen); - signMsgLen += zeroPadLen; + XMEMSET(&signMsg[len], 0x00, 36 - contextStrLen); + len += 36 - contextStrLen; /* Signing context string */ - XMEMCPY(&signMsg[signMsgLen], contextStr, contextStrLen); - signMsgLen += contextStrLen; + XMEMCPY(&signMsg[len], contextStr, contextStrLen); + len += contextStrLen; /* Input digest */ - XMEMCPY(&signMsg[signMsgLen], inputDigest, WOLFSPDM_HASH_SIZE); - signMsgLen += WOLFSPDM_HASH_SIZE; - - /* Hash M */ - rc = wolfSPDM_Sha384Hash(outputDigest, signMsg, signMsgLen, - NULL, 0, NULL, 0); - if (rc != WOLFSPDM_SUCCESS) return rc; + XMEMCPY(&signMsg[len], inputDigest, WOLFSPDM_HASH_SIZE); + len += WOLFSPDM_HASH_SIZE; + *signMsgLen = len; return WOLFSPDM_SUCCESS; } +/* outputDigest = Hash(M) */ +int wolfSPDM_BuildSignedHash(byte spdmVersion, + const char* contextStr, word32 contextStrLen, + const byte* inputDigest, byte* outputDigest) +{ + byte signMsg[200]; /* 64 + 36 + 48 = 148 bytes max */ + word32 signMsgLen = 0; + int rc; + + rc = wolfSPDM_BuildSignedMsg(spdmVersion, contextStr, contextStrLen, + inputDigest, signMsg, &signMsgLen); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_Sha384Hash(outputDigest, signMsg, signMsgLen, + NULL, 0, NULL, 0); + } + wc_ForceZero(signMsg, sizeof(signMsg)); + return rc; +} + +/* ECDSA signs Hash(M); ML-DSA signs M itself with spdm_context as its + * context string (DSP0274 1.4 Sec. 15) */ +int wolfSPDM_VerifyRspSig(WOLFSPDM_CTX* ctx, + const char* contextStr, word32 contextStrLen, const byte* messageHash, + const byte* sig, word32 sigSz) +{ + byte signHash[WOLFSPDM_HASH_SIZE]; + int rc; + + if (ctx == NULL || contextStr == NULL || messageHash == NULL || + sig == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (!ctx->flags.hasRspPubKey) { + wolfSPDM_DebugPrint(ctx, "No responder public key set\n"); + return WOLFSPDM_E_BAD_STATE; + } + if (sigSz != wolfSPDM_SigSize(ctx)) { + return WOLFSPDM_E_BAD_SIGNATURE; + } + +#ifdef WOLFSPDM_HAVE_MLDSA + if (wolfSPDM_RspMlDsaLevel(ctx) != 0) { + byte signMsg[200]; + word32 signMsgLen = 0; + + rc = wolfSPDM_BuildSignedMsg(ctx->spdmVersion, contextStr, + contextStrLen, messageHash, signMsg, &signMsgLen); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_MlDsaVerify(wolfSPDM_RspMlDsaLevel(ctx), + ctx->rspPubKey, ctx->rspPubKeyLen, (const byte*)contextStr, + contextStrLen, signMsg, signMsgLen, sig, sigSz); + } + wc_ForceZero(signMsg, sizeof(signMsg)); + return rc; + } +#endif + + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, contextStr, contextStrLen, + messageHash, signHash); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_VerifySignature(ctx, signHash, WOLFSPDM_HASH_SIZE, sig, + sigSz); + } + wc_ForceZero(signHash, sizeof(signHash)); + return rc; +} + int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { byte th2Hash[WOLFSPDM_HASH_SIZE]; @@ -427,13 +495,11 @@ int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { - word16 opaqueLen; + word32 exSz = WOLFSPDM_ECC_POINT_SIZE; + word32 sigSz; + word32 opaqueOff; word32 sigOffset; - word32 keRspPartialLen; - byte peerPubKeyX[WOLFSPDM_ECC_KEY_SIZE]; - byte peerPubKeyY[WOLFSPDM_ECC_KEY_SIZE]; byte th1SigHash[WOLFSPDM_HASH_SIZE]; - byte signMsgHash[WOLFSPDM_HASH_SIZE]; byte expectedHmac[WOLFSPDM_HASH_SIZE]; const byte* signature; const byte* rspVerifyData; @@ -453,50 +519,52 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: MutAuth=0x%02x ReqSlotID=0x%02x\n", buf[6], buf[7]); - /* Extract responder's ephemeral public key (offset 40 = 4+2+1+1+32) */ - XMEMCPY(peerPubKeyX, &buf[40], WOLFSPDM_ECC_KEY_SIZE); - XMEMCPY(peerPubKeyY, &buf[88], WOLFSPDM_ECC_KEY_SIZE); - - /* OpaqueLen at offset 136 */ - opaqueLen = SPDM_Get16LE(&buf[136]); - sigOffset = 138 + opaqueLen; - keRspPartialLen = sigOffset; - - if (bufSz < sigOffset + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE) { + /* ExchangeData at offset 40: the ECDHE point, or the ML-KEM ciphertext */ +#ifdef WOLFSPDM_HAVE_MLKEM + if (ctx->flags.ephemeralKeyInit && ctx->flags.ephemeralIsKem && + wc_MlKemKey_CipherTextSize(&ctx->ephemeral.mlkem, &exSz) != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } +#endif + sigSz = wolfSPDM_SigSize(ctx); + opaqueOff = 40 + exSz; + if (bufSz < opaqueOff + 2) { + return WOLFSPDM_E_BUFFER_SMALL; + } + sigOffset = opaqueOff + 2 + SPDM_Get16LE(&buf[opaqueOff]); + if (bufSz < sigOffset + sigSz + WOLFSPDM_HASH_SIZE) { return WOLFSPDM_E_BUFFER_SMALL; } signature = buf + sigOffset; - rspVerifyData = buf + sigOffset + WOLFSPDM_ECC_SIG_SIZE; + rspVerifyData = buf + sigOffset + sigSz; /* Add KEY_EXCHANGE_RSP partial (without sig/verify) to transcript */ - rc = wolfSPDM_TranscriptAdd(ctx, buf, keRspPartialLen); + rc = wolfSPDM_TranscriptAdd(ctx, buf, sigOffset); /* Verify responder signature over TH1 (DSP0274). Responder public key * must be provisioned before KEY_EXCHANGE. */ - if (rc == WOLFSPDM_SUCCESS && !ctx->flags.hasRspPubKey) { - wolfSPDM_DebugPrint(ctx, "No responder public key set\n"); - rc = WOLFSPDM_E_BAD_STATE; - } if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_TranscriptHash(ctx, th1SigHash); } if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, - "responder-key_exchange_rsp signing", 34, - th1SigHash, signMsgHash); - } - if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_VerifySignature(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, - signature, WOLFSPDM_ECC_SIG_SIZE); + rc = wolfSPDM_VerifyRspSig(ctx, "responder-key_exchange_rsp signing", + 34, th1SigHash, signature, sigSz); if (rc != WOLFSPDM_SUCCESS) wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP signature INVALID\n"); } if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_SIG_SIZE); + rc = wolfSPDM_TranscriptAdd(ctx, signature, sigSz); } +#ifdef WOLFSPDM_HAVE_MLKEM + if (rc == WOLFSPDM_SUCCESS && ctx->flags.ephemeralIsKem) { + rc = wolfSPDM_MlKemDecapsulate(ctx, &buf[40], exSz); + } + else +#endif if (rc == WOLFSPDM_SUCCESS) { - rc = wolfSPDM_ComputeSharedSecret(ctx, peerPubKeyX, peerPubKeyY); + rc = wolfSPDM_ComputeSharedSecret(ctx, &buf[40], + &buf[40 + WOLFSPDM_ECC_KEY_SIZE]); } if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_TranscriptHash(ctx, ctx->th1); @@ -533,7 +601,6 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS wc_ForceZero(expectedHmac, sizeof(expectedHmac)); wc_ForceZero(th1SigHash, sizeof(th1SigHash)); - wc_ForceZero(signMsgHash, sizeof(signMsgHash)); return rc; } diff --git a/src/spdm_standard.c b/src/spdm_standard.c index d085f0e..ee21e02 100644 --- a/src/spdm_standard.c +++ b/src/spdm_standard.c @@ -88,41 +88,87 @@ int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 48); + word32 off = 32; + int advDhe = 1; +#ifdef WOLFSPDM_HAVE_MLKEM + int advKem; +#endif + + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, WOLFSPDM_NEG_ALGO_SZ); + +#ifdef WOLFSPDM_HAVE_MLKEM + /* KEMAlg exists from 1.4; a KEM-only preference must not fall back */ + advDhe = (ctx->kexAdvDhe != 0); + advKem = (ctx->spdmVersion >= SPDM_VERSION_14 && ctx->kexAdvKem != 0); + if (!advDhe && !advKem) { + wolfSPDM_DebugPrint(ctx, "NEGOTIATE_ALGORITHMS: ML-KEM only needs " + "SPDM 1.4\n"); + return WOLFSPDM_E_ALGO_MISMATCH; + } +#endif - XMEMSET(buf, 0, 48); + XMEMSET(buf, 0, WOLFSPDM_NEG_ALGO_SZ); buf[0] = ctx->spdmVersion; buf[1] = SPDM_NEGOTIATE_ALGORITHMS; - buf[2] = 4; /* AlgStruct count */ - SPDM_Set16LE(&buf[4], 48); /* Length */ buf[6] = 0x01; /* MeasurementSpecification = DMTF */ buf[7] = 0x02; /* OtherParams = OpaqueDataFmt1 */ SPDM_Set32LE(&buf[8], SPDM_ASYM_ALGO_ECDSA_P384); SPDM_Set32LE(&buf[12], SPDM_HASH_ALGO_SHA_384); +#ifdef WOLFSPDM_HAVE_MLDSA + /* PqcAsymAlgo, reserved before 1.4 */ + if (ctx->spdmVersion >= SPDM_VERSION_14) { + SPDM_Set32LE(&buf[16], WOLFSPDM_MLDSA_SETS); + } +#endif - buf[32] = SPDM_ALG_TYPE_DHE; - buf[33] = 0x20; - SPDM_Set16LE(&buf[34], SPDM_DHE_ALGO_SECP384R1); - buf[36] = SPDM_ALG_TYPE_AEAD; - buf[37] = 0x20; - SPDM_Set16LE(&buf[38], SPDM_AEAD_ALGO_AES_256_GCM); - buf[40] = SPDM_ALG_TYPE_REQ_BASE_ASYM; - buf[41] = 0x20; - SPDM_Set16LE(&buf[42], (word16)SPDM_ASYM_ALGO_ECDSA_P384); - buf[44] = SPDM_ALG_TYPE_KEY_SCHEDULE; - buf[45] = 0x20; - SPDM_Set16LE(&buf[46], SPDM_KEY_SCHEDULE_SPDM); - *bufSz = 48; + if (advDhe) { + buf[off] = SPDM_ALG_TYPE_DHE; + buf[off + 1] = 0x20; + SPDM_Set16LE(&buf[off + 2], SPDM_DHE_ALGO_SECP384R1); + off += 4; + } + buf[off] = SPDM_ALG_TYPE_AEAD; + buf[off + 1] = 0x20; + SPDM_Set16LE(&buf[off + 2], SPDM_AEAD_ALGO_AES_256_GCM); + off += 4; + buf[off] = SPDM_ALG_TYPE_REQ_BASE_ASYM; + buf[off + 1] = 0x20; + SPDM_Set16LE(&buf[off + 2], (word16)SPDM_ASYM_ALGO_ECDSA_P384); + off += 4; + buf[off] = SPDM_ALG_TYPE_KEY_SCHEDULE; + buf[off + 1] = 0x20; + SPDM_Set16LE(&buf[off + 2], SPDM_KEY_SCHEDULE_SPDM); + off += 4; +#ifdef WOLFSPDM_HAVE_MLKEM + if (advKem) { + buf[off] = SPDM_ALG_TYPE_KEM; + buf[off + 1] = 0x20; + SPDM_Set16LE(&buf[off + 2], ctx->kexAdvKem); + off += 4; + } +#endif + + buf[2] = (byte)((off - 32) / 4); /* AlgStruct count */ + SPDM_Set16LE(&buf[4], (word16)off); /* Length */ + *bufSz = off; return WOLFSPDM_SUCCESS; } +/* The responder picks one signature algorithm across BaseAsymSel and the + * 1.4 PqcAsymSel, and one key exchange: a DHE group or a KEM, never both */ int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { word32 off; + word32 baseAsymSel; + word32 pqcAsymSel = 0; +#ifdef WOLFSPDM_HAVE_MLKEM + word16 kemSel = 0; +#endif byte numAlgs; byte i; int dheOk = 0; + int kemOk = 0; int aeadOk = 0; int ksOk = 0; @@ -138,8 +184,30 @@ int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) if (buf[6] > 0x01 || buf[7] != 0x02) { return WOLFSPDM_E_ALGO_MISMATCH; } - if (SPDM_Get32LE(&buf[12]) != SPDM_ASYM_ALGO_ECDSA_P384 || - SPDM_Get32LE(&buf[16]) != SPDM_HASH_ALGO_SHA_384) { + if (SPDM_Get32LE(&buf[16]) != SPDM_HASH_ALGO_SHA_384) { + return WOLFSPDM_E_ALGO_MISMATCH; + } + +#ifdef WOLFSPDM_HAVE_MLDSA + ctx->pqcAsymSel = 0; +#endif +#ifdef WOLFSPDM_HAVE_MLKEM + ctx->kemAlgSel = 0; +#endif + baseAsymSel = SPDM_Get32LE(&buf[12]); + if (ctx->spdmVersion >= SPDM_VERSION_14) { + pqcAsymSel = SPDM_Get32LE(&buf[20]); + } + if (pqcAsymSel != 0) { +#ifdef WOLFSPDM_HAVE_MLDSA + if (baseAsymSel != 0 || wolfSPDM_MlDsaLevel(pqcAsymSel) == 0) { + return WOLFSPDM_E_ALGO_MISMATCH; + } +#else + return WOLFSPDM_E_ALGO_MISMATCH; +#endif + } + else if (baseAsymSel != SPDM_ASYM_ALGO_ECDSA_P384) { return WOLFSPDM_E_ALGO_MISMATCH; } @@ -158,7 +226,24 @@ int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) switch (buf[off]) { case SPDM_ALG_TYPE_DHE: dheOk = (algSel == SPDM_DHE_ALGO_SECP384R1); + #ifdef WOLFSPDM_HAVE_MLKEM + /* Zero when the responder picked a KEM instead */ + if ((algSel != 0 && !dheOk) || (dheOk && !ctx->kexAdvDhe)) { + return WOLFSPDM_E_ALGO_MISMATCH; + } + #endif + break; + #ifdef WOLFSPDM_HAVE_MLKEM + case SPDM_ALG_TYPE_KEM: + if (algSel != 0 && ((algSel & (algSel - 1)) != 0 || + (algSel & ctx->kexAdvKem & WOLFSPDM_MLKEM_SETS) == 0 || + ctx->spdmVersion < SPDM_VERSION_14)) { + return WOLFSPDM_E_ALGO_MISMATCH; + } + kemOk = (algSel != 0); + kemSel = algSel; break; + #endif case SPDM_ALG_TYPE_AEAD: aeadOk = (algSel == SPDM_AEAD_ALGO_AES_256_GCM); break; @@ -170,11 +255,19 @@ int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) } off += 4 + extLen; } - if (!dheOk || !aeadOk || !ksOk) { + if (dheOk + kemOk != 1 || !aeadOk || !ksOk) { wolfSPDM_DebugPrint(ctx, "ALGORITHMS: not Algorithm Set B " - "(dhe=%d aead=%d ks=%d)\n", dheOk, aeadOk, ksOk); + "(dhe=%d kem=%d aead=%d ks=%d)\n", dheOk, kemOk, aeadOk, ksOk); return WOLFSPDM_E_ALGO_MISMATCH; } +#ifdef WOLFSPDM_HAVE_MLDSA + ctx->pqcAsymSel = pqcAsymSel; +#endif +#ifdef WOLFSPDM_HAVE_MLKEM + ctx->kemAlgSel = kemSel; +#endif + wolfSPDM_DebugPrint(ctx, "ALGORITHMS: asym 0x%08x pqc 0x%08x kem %d\n", + baseAsymSel, pqcAsymSel, kemOk); return WOLFSPDM_SUCCESS; } @@ -193,7 +286,7 @@ int wolfSPDM_GetCapabilities(WOLFSPDM_CTX* ctx) int wolfSPDM_NegotiateAlgorithms(WOLFSPDM_CTX* ctx) { - byte txBuf[48]; + byte txBuf[WOLFSPDM_NEG_ALGO_SZ]; byte rxBuf[128]; int rc; @@ -382,24 +475,70 @@ static word32 wolfSPDM_DerSeqLen(const byte* der, word32 derSz) return hdr + len; } -/* Load the ECC public key of a certificate into an initialized key */ -static int wolfSPDM_CertPubKey(const byte* der, word32 derSz, ecc_key* key, - int* isCA) +/* A certificate's subject key: an ECC SubjectPublicKeyInfo, or a raw ML-DSA + * public key */ +#define WOLFSPDM_CERT_KEY_SZ (WOLFSPDM_RSP_PUBKEY_SZ > 256 ? \ + WOLFSPDM_RSP_PUBKEY_SZ : 256) +typedef struct { + word32 oid; + word32 len; + byte der[WOLFSPDM_CERT_KEY_SZ]; +} WOLFSPDM_CERT_KEY; + +#ifdef WOLFSPDM_HAVE_MLDSA +static byte wolfSPDM_KeyOidMlDsaLevel(word32 oid) +{ + switch (oid) { + case ML_DSA_44k: + return wolfSPDM_MlDsaLevel(SPDM_PQC_ASYM_ALGO_ML_DSA_44); + case ML_DSA_65k: + return wolfSPDM_MlDsaLevel(SPDM_PQC_ASYM_ALGO_ML_DSA_65); + case ML_DSA_87k: + return wolfSPDM_MlDsaLevel(SPDM_PQC_ASYM_ALGO_ML_DSA_87); + default: + return 0; + } +} + +static byte wolfSPDM_SigOidMlDsaLevel(word32 oid) +{ + switch (oid) { + case CTC_ML_DSA_44: + return wolfSPDM_MlDsaLevel(SPDM_PQC_ASYM_ALGO_ML_DSA_44); + case CTC_ML_DSA_65: + return wolfSPDM_MlDsaLevel(SPDM_PQC_ASYM_ALGO_ML_DSA_65); + case CTC_ML_DSA_87: + return wolfSPDM_MlDsaLevel(SPDM_PQC_ASYM_ALGO_ML_DSA_87); + default: + return 0; + } +} +#endif /* WOLFSPDM_HAVE_MLDSA */ + +/* Read the subject key and CA flag of a certificate */ +static int wolfSPDM_CertKey(const byte* der, word32 derSz, + WOLFSPDM_CERT_KEY* key, int* isCA) { DecodedCert cert; - word32 idx = 0; int rc; wc_InitDecodedCert(&cert, der, derSz, NULL); rc = wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL); - if (rc == 0 && cert.keyOID != ECDSAk) { + if (rc == 0 && cert.keyOID != ECDSAk + #ifdef WOLFSPDM_HAVE_MLDSA + && wolfSPDM_KeyOidMlDsaLevel(cert.keyOID) == 0 + #endif + ) { rc = -1; } - if (rc == 0) { - rc = wc_EccPublicKeyDecode(cert.publicKey, &idx, key, - cert.pubKeySize); + if (rc == 0 && (cert.publicKey == NULL || + cert.pubKeySize > sizeof(key->der))) { + rc = -1; } if (rc == 0) { + key->oid = cert.keyOID; + key->len = cert.pubKeySize; + XMEMCPY(key->der, cert.publicKey, cert.pubKeySize); *isCA = cert.isCA; } wc_FreeDecodedCert(&cert); @@ -407,46 +546,134 @@ static int wolfSPDM_CertPubKey(const byte* der, word32 derSz, ecc_key* key, return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CERT_PARSE; } -/* Verify that the certificate at der is signed by issuer (ECDSA-SHA384) */ +/* ECDSA verify of hash under an ECC SubjectPublicKeyInfo */ +static int wolfSPDM_EccSpkiVerify(const byte* spki, word32 spkiSz, + const byte* sig, word32 sigSz, const byte* hash, word32 hashSz) +{ + ecc_key key; + word32 idx = 0; + int verified = 0; + int rc; + + rc = wc_ecc_init(&key); + if (rc == 0) { + rc = wc_EccPublicKeyDecode(spki, &idx, &key, spkiSz); + if (rc == 0) { + rc = wc_ecc_verify_hash(sig, sigSz, hash, hashSz, &verified, &key); + } + wc_ecc_free(&key); + } + + return (rc == 0 && verified == 1) ? WOLFSPDM_SUCCESS : + WOLFSPDM_E_CERT_FAIL; +} + +/* Verify that the certificate at der was signed by issuer: ECDSA-SHA384, or + * pure ML-DSA with an empty context */ static int wolfSPDM_CertSignedBy(const byte* der, word32 derSz, - ecc_key* issuer) + const WOLFSPDM_CERT_KEY* issuer) { DecodedCert cert; byte hash[WOLFSPDM_HASH_SIZE]; - int verified = 0; + const byte* tbs; + word32 tbsSz; int rc; wc_InitDecodedCert(&cert, der, derSz, NULL); rc = wc_ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL); - if (rc == 0 && (cert.signatureOID != CTC_SHA384wECDSA || - cert.sigIndex <= cert.certBegin)) { + if (rc == 0 && cert.sigIndex <= cert.certBegin) { rc = -1; } - if (rc == 0) { - rc = wolfSPDM_Sha384Hash(hash, cert.source + cert.certBegin, - cert.sigIndex - cert.certBegin, NULL, 0, NULL, 0); + tbs = cert.source + cert.certBegin; + tbsSz = cert.sigIndex - cert.certBegin; + + if (rc == 0 && issuer->oid == ECDSAk) { + rc = (cert.signatureOID == CTC_SHA384wECDSA) ? 0 : -1; + if (rc == 0) { + rc = wolfSPDM_Sha384Hash(hash, tbs, tbsSz, NULL, 0, NULL, 0); + } + if (rc == 0) { + rc = wolfSPDM_EccSpkiVerify(issuer->der, issuer->len, + cert.signature, cert.sigLength, hash, sizeof(hash)); + } } - if (rc == 0) { - rc = wc_ecc_verify_hash(cert.signature, cert.sigLength, hash, - sizeof(hash), &verified, issuer); +#ifdef WOLFSPDM_HAVE_MLDSA + else if (rc == 0 && wolfSPDM_KeyOidMlDsaLevel(issuer->oid) != 0) { + rc = (wolfSPDM_SigOidMlDsaLevel(cert.signatureOID) == + wolfSPDM_KeyOidMlDsaLevel(issuer->oid)) ? 0 : -1; + if (rc == 0) { + rc = wolfSPDM_MlDsaVerify(wolfSPDM_KeyOidMlDsaLevel(issuer->oid), + issuer->der, issuer->len, NULL, 0, tbs, tbsSz, cert.signature, + cert.sigLength); + } + } +#endif + else { + rc = -1; } wc_FreeDecodedCert(&cert); - return (rc == 0 && verified == 1) ? WOLFSPDM_SUCCESS : - WOLFSPDM_E_CERT_FAIL; + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CERT_FAIL; } -/* Walk the retrieved chain: each certificate must be signed by the one - * before it, the first by the trusted root when one is set. The leaf's - * P-384 key becomes the responder key, or must match a pinned key. */ -int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) +/* The leaf key in the ctx->rspPubKey format: raw P-384 X||Y, or the raw + * ML-DSA key of the negotiated set */ +static int wolfSPDM_LeafKey(const WOLFSPDM_CTX* ctx, + const WOLFSPDM_CERT_KEY* key, byte* leaf, word32* leafSz) { - ecc_key key; - byte leaf[WOLFSPDM_ECC_POINT_SIZE]; + ecc_key eccKey; + word32 idx = 0; word32 xSz = WOLFSPDM_ECC_KEY_SIZE; word32 ySz = WOLFSPDM_ECC_KEY_SIZE; + int rc; + +#ifdef WOLFSPDM_HAVE_MLDSA + if (wolfSPDM_RspMlDsaLevel(ctx) != 0) { + if (wolfSPDM_KeyOidMlDsaLevel(key->oid) != + wolfSPDM_RspMlDsaLevel(ctx) || key->len > *leafSz) { + return WOLFSPDM_E_CERT_PARSE; + } + XMEMCPY(leaf, key->der, key->len); + *leafSz = key->len; + return WOLFSPDM_SUCCESS; + } +#else + (void)ctx; +#endif + + /* Algorithm Set B: the leaf carries a P-384 key */ + if (key->oid != ECDSAk || *leafSz < WOLFSPDM_ECC_POINT_SIZE || + wc_ecc_init(&eccKey) != 0) { + return WOLFSPDM_E_CERT_PARSE; + } + rc = wc_EccPublicKeyDecode(key->der, &idx, &eccKey, key->len); + if (rc == 0 && wc_ecc_get_curve_id(eccKey.idx) != ECC_SECP384R1) { + rc = -1; + } + if (rc == 0) { + rc = wc_ecc_export_public_raw(&eccKey, leaf, &xSz, + leaf + WOLFSPDM_ECC_KEY_SIZE, &ySz); + } + if (rc == 0 && (xSz != WOLFSPDM_ECC_KEY_SIZE || + ySz != WOLFSPDM_ECC_KEY_SIZE)) { + rc = -1; + } + wc_ecc_free(&eccKey); + *leafSz = WOLFSPDM_ECC_POINT_SIZE; + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CERT_PARSE; +} + +/* Walk the retrieved chain: each certificate must be signed by the one + * before it, the first by the trusted root when one is set. The leaf key + * must match the negotiated signature algorithm and becomes the responder + * key, or must match a pinned key. */ +int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) +{ + WOLFSPDM_CERT_KEY key; + word32 leafSz = WOLFSPDM_RSP_PUBKEY_SZ; word32 pos = WOLFSPDM_CERT_CHAIN_HDR_SZ; - int keyInit = 0; + int haveKey = 0; int isCA = 0; int anchored = 0; int rc = WOLFSPDM_SUCCESS; @@ -469,13 +696,9 @@ int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) rc = WOLFSPDM_E_CERT_FAIL; } if (rc == WOLFSPDM_SUCCESS) { - rc = (wc_ecc_init(&key) == 0) ? WOLFSPDM_SUCCESS : - WOLFSPDM_E_CRYPTO_FAIL; - } - if (rc == WOLFSPDM_SUCCESS) { - keyInit = 1; - rc = wolfSPDM_CertPubKey(ctx->trustedCA, ctx->trustedCASz, &key, + rc = wolfSPDM_CertKey(ctx->trustedCA, ctx->trustedCASz, &key, &isCA); + haveKey = (rc == WOLFSPDM_SUCCESS); } anchored = 1; } @@ -486,52 +709,40 @@ int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) if (certSz == 0) { rc = WOLFSPDM_E_CERT_PARSE; } - if (rc == WOLFSPDM_SUCCESS && keyInit) { + if (rc == WOLFSPDM_SUCCESS && haveKey) { rc = isCA ? wolfSPDM_CertSignedBy(ctx->certChain + pos, certSz, &key) : WOLFSPDM_E_CERT_FAIL; } - if (keyInit) { - wc_ecc_free(&key); - keyInit = 0; - } if (rc == WOLFSPDM_SUCCESS) { - rc = (wc_ecc_init(&key) == 0) ? WOLFSPDM_SUCCESS : - WOLFSPDM_E_CRYPTO_FAIL; - } - if (rc == WOLFSPDM_SUCCESS) { - keyInit = 1; - rc = wolfSPDM_CertPubKey(ctx->certChain + pos, certSz, &key, - &isCA); + rc = wolfSPDM_CertKey(ctx->certChain + pos, certSz, &key, &isCA); + haveKey = (rc == WOLFSPDM_SUCCESS); } pos += certSz; } - /* The leaf must carry a P-384 key (Algorithm Set B) */ - if (rc == WOLFSPDM_SUCCESS && - (!keyInit || wc_ecc_get_curve_id(key.idx) != ECC_SECP384R1 || - wc_ecc_export_public_raw(&key, leaf, &xSz, - leaf + WOLFSPDM_ECC_KEY_SIZE, &ySz) != 0 || - xSz != WOLFSPDM_ECC_KEY_SIZE || ySz != WOLFSPDM_ECC_KEY_SIZE)) { + if (rc == WOLFSPDM_SUCCESS && !haveKey) { rc = WOLFSPDM_E_CERT_PARSE; } - if (keyInit) { - wc_ecc_free(&key); - } - if (rc == WOLFSPDM_SUCCESS && ctx->flags.hasRspPubKey && !ctx->flags.rspKeyFromCert) { - if (ctx->rspPubKeyLen != WOLFSPDM_ECC_POINT_SIZE || - XMEMCMP(ctx->rspPubKey, leaf, WOLFSPDM_ECC_POINT_SIZE) != 0) { + byte leaf[WOLFSPDM_ECC_POINT_SIZE]; + + leafSz = sizeof(leaf); + rc = wolfSPDM_LeafKey(ctx, &key, leaf, &leafSz); + if (rc == WOLFSPDM_SUCCESS && (ctx->rspPubKeyLen != leafSz || + XMEMCMP(ctx->rspPubKey, leaf, leafSz) != 0)) { wolfSPDM_DebugPrint(ctx, "Leaf key does not match pinned key\n"); rc = WOLFSPDM_E_CERT_FAIL; } anchored = 1; } else if (rc == WOLFSPDM_SUCCESS) { - XMEMCPY(ctx->rspPubKey, leaf, WOLFSPDM_ECC_POINT_SIZE); - ctx->rspPubKeyLen = WOLFSPDM_ECC_POINT_SIZE; - ctx->flags.hasRspPubKey = 1; - ctx->flags.rspKeyFromCert = 1; + rc = wolfSPDM_LeafKey(ctx, &key, ctx->rspPubKey, &leafSz); + if (rc == WOLFSPDM_SUCCESS) { + ctx->rspPubKeyLen = leafSz; + ctx->flags.hasRspPubKey = 1; + ctx->flags.rspKeyFromCert = 1; + } } if (rc == WOLFSPDM_SUCCESS && !anchored && @@ -570,6 +781,25 @@ int wolfSPDM_AllowUntrustedCerts(WOLFSPDM_CTX* ctx, int allow) return WOLFSPDM_SUCCESS; } +int wolfSPDM_SetKeyExchangePref(WOLFSPDM_CTX* ctx, int advDhe, word16 kemMask) +{ + if (ctx == NULL || (advDhe == 0 && kemMask == 0)) { + return WOLFSPDM_E_INVALID_ARG; + } +#ifdef WOLFSPDM_HAVE_MLKEM + if ((kemMask & (word16)~WOLFSPDM_MLKEM_SETS) != 0) { + return WOLFSPDM_E_INVALID_ARG; + } + ctx->kexAdvDhe = (byte)(advDhe != 0); + ctx->kexAdvKem = kemMask; +#else + if (kemMask != 0) { + return WOLFSPDM_E_INVALID_ARG; + } +#endif + return WOLFSPDM_SUCCESS; +} + /* GET_VERSION -> CAPS -> ALGO -> DIGESTS -> CERTIFICATE -> KEY_EXCHANGE -> * FINISH */ int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx) diff --git a/test/test_certs_mldsa.h b/test/test_certs_mldsa.h new file mode 100644 index 0000000..25d950b --- /dev/null +++ b/test/test_certs_mldsa.h @@ -0,0 +1,1165 @@ +/* test_certs_mldsa.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* DMTF libspdm ML-DSA-44 sample chain (libspdm unit_test/sample_key/mldsa44): + * root CA, intermediate and responder leaf, plus the leaf's raw public key */ + +#ifndef WOLFSPDM_TEST_CERTS_MLDSA_H +#define WOLFSPDM_TEST_CERTS_MLDSA_H + +/* The root CA is the first certificate of the chain */ +#define TEST_MLDSA44_CA_SZ 4009 + +static const byte test_mldsa44_chain_der[] = { + 0x30, 0x82, 0x0f, 0xa5, 0x30, 0x82, 0x06, 0x1b, 0xa0, 0x03, 0x02, 0x01, + 0x02, 0x02, 0x14, 0x6d, 0xba, 0x2a, 0xde, 0x10, 0x65, 0x5d, 0xf8, 0x2a, + 0x71, 0xef, 0x8e, 0xcc, 0x0d, 0x59, 0x6f, 0x15, 0xa0, 0x5b, 0xb9, 0x30, + 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, + 0x30, 0x22, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x17, 0x44, 0x4d, 0x54, 0x46, 0x20, 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, + 0x6d, 0x20, 0x6d, 0x6c, 0x64, 0x73, 0x61, 0x34, 0x34, 0x20, 0x63, 0x61, + 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x34, 0x32, 0x33, 0x30, 0x38, + 0x32, 0x39, 0x35, 0x31, 0x5a, 0x17, 0x0d, 0x33, 0x35, 0x30, 0x34, 0x32, + 0x31, 0x30, 0x38, 0x32, 0x39, 0x35, 0x31, 0x5a, 0x30, 0x22, 0x31, 0x20, + 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x17, 0x44, 0x4d, 0x54, + 0x46, 0x20, 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, 0x6d, 0x20, 0x6d, 0x6c, + 0x64, 0x73, 0x61, 0x34, 0x34, 0x20, 0x63, 0x61, 0x30, 0x82, 0x05, 0x32, + 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, + 0x11, 0x03, 0x82, 0x05, 0x21, 0x00, 0x80, 0x55, 0x6f, 0x8f, 0x1d, 0x26, + 0xf5, 0x76, 0x32, 0x4d, 0x0b, 0x2f, 0xf3, 0xb0, 0x75, 0xc3, 0xd7, 0xc7, + 0x2b, 0xbd, 0xb2, 0x8b, 0x0e, 0x2b, 0xc3, 0x2e, 0x21, 0xe8, 0xf5, 0x62, + 0x91, 0x24, 0x15, 0x5e, 0xbc, 0x62, 0x31, 0x29, 0x30, 0x0b, 0x81, 0x80, + 0xaf, 0xd2, 0x68, 0x8a, 0x59, 0x7a, 0xf2, 0xb1, 0x1b, 0x3b, 0xe7, 0x44, + 0x10, 0xb0, 0xf9, 0xe6, 0x29, 0x05, 0x18, 0x34, 0xb1, 0x88, 0x16, 0x9e, + 0x49, 0x63, 0x62, 0x33, 0x99, 0x41, 0x9d, 0x88, 0xd6, 0xde, 0x3e, 0x22, + 0xc5, 0xe9, 0x2b, 0x5a, 0xa9, 0x4a, 0xca, 0x66, 0x17, 0x31, 0x92, 0x47, + 0xcf, 0xdd, 0x28, 0xcb, 0x2d, 0x48, 0x61, 0x03, 0x2d, 0x8b, 0x78, 0xa0, + 0xdb, 0x55, 0x58, 0xef, 0xf9, 0x68, 0xe9, 0x66, 0x58, 0x59, 0x34, 0xa8, + 0x94, 0x73, 0xfa, 0x06, 0x08, 0xcf, 0x24, 0x50, 0x7e, 0xfb, 0x81, 0xbf, + 0x49, 0x18, 0xf9, 0xf6, 0x65, 0x59, 0xe1, 0x0c, 0xae, 0x05, 0x76, 0x97, + 0x48, 0x59, 0xf5, 0xfb, 0xc0, 0xb1, 0x01, 0x18, 0xcd, 0xf9, 0x81, 0xe7, + 0xe9, 0xe4, 0x72, 0xb0, 0xb8, 0xe2, 0x59, 0x8d, 0x1c, 0xc7, 0x1a, 0xbb, + 0x60, 0x19, 0xb9, 0x06, 0x8d, 0x83, 0x0a, 0x7d, 0xbd, 0xbe, 0xd9, 0xd7, + 0x39, 0x73, 0xfd, 0xe5, 0x9e, 0x12, 0x15, 0x4a, 0x00, 0x1f, 0x4e, 0x29, + 0x57, 0x70, 0xca, 0xbc, 0x46, 0xc4, 0x63, 0xcd, 0x92, 0xc6, 0x1b, 0x88, + 0x43, 0x05, 0x1f, 0x69, 0x47, 0x37, 0x92, 0x0a, 0xc6, 0x3a, 0x8f, 0xa8, + 0xdf, 0xea, 0x03, 0x95, 0xd2, 0xc8, 0x26, 0xd5, 0x9c, 0x2b, 0x82, 0xd5, + 0xae, 0x06, 0x76, 0xf8, 0x58, 0x92, 0x0e, 0x59, 0xbe, 0x85, 0x9a, 0x94, + 0x6d, 0x24, 0x14, 0x0d, 0x7d, 0xdc, 0xb9, 0x23, 0x0b, 0x94, 0xe4, 0x32, + 0x2d, 0xa9, 0xf8, 0xbd, 0x61, 0x55, 0x55, 0xd2, 0x30, 0x2d, 0xc2, 0x06, + 0x73, 0xfb, 0xbb, 0x94, 0xae, 0xaf, 0x8d, 0x97, 0xc3, 0x3f, 0x88, 0xa9, + 0xd4, 0x06, 0x68, 0x0c, 0x0c, 0x24, 0x20, 0xd2, 0xe9, 0xc3, 0xdd, 0x0f, + 0x5e, 0xf2, 0x26, 0xf6, 0x8b, 0xe6, 0x4c, 0x2e, 0xf4, 0x9d, 0xd0, 0xd4, + 0x67, 0xfb, 0x2c, 0x69, 0xdd, 0xc3, 0x90, 0x6a, 0xe4, 0x00, 0x1e, 0x84, + 0x95, 0x93, 0x4b, 0xed, 0x89, 0xdb, 0x08, 0xe7, 0x5b, 0x7b, 0xd8, 0x3a, + 0x06, 0x3a, 0x7e, 0x98, 0xc9, 0x39, 0xb4, 0x63, 0x8b, 0x0e, 0xa6, 0xd7, + 0xdd, 0x85, 0x25, 0x32, 0xb5, 0x93, 0xd6, 0xcb, 0x3c, 0x15, 0x42, 0xbc, + 0x35, 0xc2, 0x63, 0x14, 0xcf, 0x65, 0x19, 0xfa, 0x2b, 0x90, 0xdb, 0xd7, + 0x0c, 0xff, 0x5f, 0x26, 0x1f, 0x94, 0xdc, 0xeb, 0x80, 0xad, 0x18, 0x5f, + 0xca, 0x44, 0x48, 0xa6, 0x6f, 0x8e, 0x8b, 0x9d, 0x70, 0x83, 0xc2, 0x37, + 0x6d, 0x41, 0xb6, 0x97, 0x33, 0x62, 0x96, 0x8d, 0x72, 0x64, 0x3a, 0xcd, + 0x29, 0x95, 0x72, 0x43, 0xdc, 0x42, 0xa2, 0x33, 0xfa, 0x90, 0xc7, 0x80, + 0x90, 0x3c, 0x30, 0x67, 0xe6, 0x7c, 0x5b, 0x61, 0x04, 0x3a, 0x63, 0x43, + 0x9f, 0xdd, 0xdb, 0x0e, 0x44, 0xae, 0xc7, 0x6f, 0xb5, 0xf0, 0x0e, 0xc6, + 0x65, 0x78, 0x8f, 0x08, 0x3f, 0xd7, 0x7f, 0xa2, 0xf3, 0x14, 0x22, 0x59, + 0x38, 0xac, 0xa4, 0xa3, 0xff, 0x38, 0xe5, 0x3e, 0x7f, 0xc2, 0x36, 0x03, + 0x1b, 0xef, 0x76, 0xee, 0xb4, 0xdb, 0x1a, 0xd2, 0x21, 0x66, 0xde, 0x19, + 0x70, 0x66, 0xc4, 0x19, 0x58, 0x3b, 0x3b, 0x99, 0x07, 0x35, 0xe3, 0x96, + 0x85, 0x30, 0x6d, 0x9c, 0x61, 0xc5, 0xcf, 0x5a, 0x10, 0x37, 0x59, 0x1c, + 0x5c, 0x0c, 0xaa, 0xca, 0xdf, 0xd0, 0xeb, 0x33, 0x6d, 0xea, 0x64, 0xe7, + 0x15, 0xa9, 0x01, 0xc7, 0x3f, 0x37, 0xc6, 0x89, 0x14, 0x98, 0x22, 0x75, + 0xfe, 0xb4, 0xa4, 0x41, 0x3a, 0x23, 0xda, 0x8b, 0x96, 0xf9, 0x82, 0x5d, + 0xf6, 0xf5, 0xf4, 0xd7, 0x64, 0x2c, 0x18, 0xcc, 0x28, 0x96, 0xfc, 0xeb, + 0xfb, 0x64, 0x5c, 0x8d, 0x9d, 0xc1, 0x29, 0x90, 0x43, 0xbc, 0x36, 0xcf, + 0x51, 0xaf, 0x8d, 0xee, 0x92, 0x05, 0x05, 0x88, 0x67, 0xad, 0xba, 0x38, + 0xf4, 0x89, 0x8a, 0xd7, 0x2d, 0xdb, 0xff, 0x0a, 0xa6, 0xb6, 0x83, 0x8d, + 0x54, 0x8e, 0xec, 0x81, 0x45, 0xdf, 0x7b, 0xa4, 0x82, 0xd8, 0x54, 0xe9, + 0x5d, 0xdb, 0x20, 0x44, 0x63, 0xe7, 0xaf, 0xfe, 0x02, 0x56, 0x61, 0x6f, + 0x19, 0x55, 0x53, 0x6e, 0xf6, 0xee, 0xc1, 0x36, 0x55, 0x78, 0x16, 0x9a, + 0x43, 0x84, 0xe0, 0x0d, 0xd6, 0x15, 0xa5, 0x26, 0x9e, 0x22, 0xdc, 0x90, + 0xc2, 0x55, 0xc3, 0xfd, 0x04, 0xcb, 0x8a, 0x43, 0x7d, 0x04, 0x11, 0x9c, + 0xd0, 0x5f, 0x8b, 0xd2, 0x00, 0x4d, 0x91, 0x1e, 0xbf, 0x8d, 0xcc, 0x11, + 0x16, 0x00, 0x59, 0x93, 0x02, 0xa6, 0x2b, 0x55, 0x0e, 0xd9, 0x79, 0x92, + 0xa5, 0xc3, 0x2a, 0xf5, 0x4c, 0xf5, 0x57, 0x02, 0x08, 0xe8, 0xd5, 0x7c, + 0x74, 0xd6, 0xfd, 0x0e, 0xa8, 0x1d, 0x98, 0x8b, 0x0c, 0x1a, 0xaa, 0xa0, + 0x78, 0x26, 0x6d, 0x85, 0x0c, 0x59, 0xe9, 0x50, 0xfd, 0x46, 0x35, 0x70, + 0xe4, 0x1a, 0x94, 0xa5, 0xde, 0x32, 0x87, 0xb9, 0xd2, 0xc5, 0xac, 0x7a, + 0xe9, 0xba, 0x36, 0x9f, 0x5d, 0x13, 0xa6, 0x51, 0x78, 0x94, 0x52, 0x50, + 0x42, 0x9c, 0x53, 0xb7, 0xd9, 0xc1, 0x9e, 0x3f, 0xb2, 0xdb, 0xc3, 0xe0, + 0x14, 0x9f, 0xc2, 0xef, 0x9b, 0x54, 0x21, 0x26, 0x98, 0x7e, 0xbd, 0xc7, + 0x20, 0xf3, 0x07, 0x13, 0xf4, 0xbe, 0xcf, 0x24, 0xf9, 0x6c, 0x09, 0x55, + 0xdf, 0x2a, 0xb5, 0xda, 0x58, 0xd4, 0xc9, 0x5b, 0x15, 0x52, 0x21, 0x25, + 0xfa, 0xfe, 0xb2, 0x0a, 0xba, 0x38, 0xad, 0x55, 0xf3, 0x05, 0x59, 0x3b, + 0x34, 0x6a, 0x3d, 0x78, 0xe1, 0x93, 0x57, 0x03, 0x03, 0x29, 0x6e, 0x52, + 0x21, 0x14, 0x0a, 0x8d, 0xf6, 0x75, 0x96, 0x9c, 0x5a, 0x9c, 0x88, 0xab, + 0x01, 0x4a, 0xbb, 0x29, 0xbb, 0xfe, 0x5d, 0x9a, 0xa6, 0x13, 0xf9, 0x4f, + 0x54, 0x20, 0x11, 0x13, 0xd2, 0x60, 0xd8, 0xc5, 0xf5, 0x49, 0x2d, 0x0d, + 0xc3, 0x7f, 0xeb, 0x1c, 0x98, 0x2f, 0x96, 0x9f, 0x05, 0x50, 0xfa, 0x75, + 0x54, 0x16, 0x7e, 0x18, 0xcc, 0x37, 0x83, 0x95, 0x87, 0x94, 0x7e, 0xcc, + 0xb4, 0x4f, 0x09, 0xb8, 0xcb, 0xa9, 0x03, 0x00, 0x99, 0x13, 0x91, 0x43, + 0x0f, 0xe2, 0x06, 0x94, 0x47, 0x68, 0xe3, 0x15, 0x36, 0x9e, 0x14, 0xa2, + 0x47, 0xc9, 0x9f, 0xa5, 0x2f, 0xc6, 0x6b, 0xf6, 0xa1, 0xd0, 0xfe, 0xf2, + 0x61, 0xad, 0xfe, 0x60, 0x5a, 0xfd, 0xaa, 0x72, 0xfe, 0x36, 0xaa, 0xd5, + 0xcf, 0x82, 0x6b, 0xaf, 0x2b, 0x4b, 0x04, 0x5a, 0xb7, 0xc6, 0xe7, 0x42, + 0x48, 0x17, 0xd6, 0x39, 0x0e, 0x81, 0x0a, 0x89, 0x84, 0x0a, 0x80, 0xc9, + 0x7f, 0x56, 0x20, 0xd6, 0x46, 0x80, 0x5a, 0x10, 0x1f, 0xa4, 0x08, 0xa3, + 0xca, 0x96, 0x1d, 0xdb, 0x82, 0xfa, 0xc7, 0x0a, 0x7a, 0xc2, 0x33, 0x43, + 0x73, 0x05, 0x1a, 0x13, 0x1c, 0x57, 0xf4, 0x82, 0x94, 0x68, 0xb3, 0x06, + 0x69, 0x82, 0x8d, 0xdc, 0x1e, 0x73, 0xf4, 0xf5, 0xbe, 0xf2, 0x00, 0xb0, + 0x0f, 0x1e, 0xb7, 0x97, 0x67, 0x9f, 0x58, 0xea, 0x74, 0xe1, 0xd2, 0x49, + 0x5a, 0x36, 0xa3, 0xec, 0xd0, 0x84, 0xcd, 0xe4, 0x7a, 0x7e, 0xbc, 0xf2, + 0x42, 0x8d, 0xd4, 0xff, 0x87, 0xb0, 0x9b, 0x93, 0xc4, 0x6f, 0xb4, 0xa2, + 0xe1, 0xfa, 0x9f, 0x6a, 0x7e, 0x3a, 0x3a, 0x02, 0xd9, 0xca, 0x09, 0x6a, + 0x8e, 0xec, 0x43, 0x4e, 0xbe, 0x39, 0x6c, 0x18, 0xcf, 0x1c, 0xd9, 0xe5, + 0x6a, 0xde, 0x28, 0xee, 0xa2, 0x7a, 0xc2, 0x0d, 0xce, 0xa3, 0x74, 0xd8, + 0x1d, 0x84, 0xba, 0x5f, 0xee, 0xd5, 0xd8, 0xbb, 0x97, 0x56, 0x14, 0x8b, + 0x6b, 0x43, 0x80, 0x02, 0x09, 0x06, 0x95, 0xd0, 0xdd, 0x14, 0x82, 0x1d, + 0xcb, 0x4a, 0x06, 0xa0, 0x4d, 0x44, 0x52, 0x91, 0x38, 0x6b, 0x60, 0x62, + 0x0e, 0xc2, 0x2e, 0x23, 0xab, 0xf4, 0xf2, 0x1f, 0xcf, 0x1a, 0x98, 0x02, + 0x02, 0x29, 0x12, 0x89, 0x6e, 0x87, 0xaa, 0xd1, 0x78, 0x66, 0xcf, 0xb4, + 0x40, 0xab, 0x93, 0x2b, 0xca, 0x15, 0xf7, 0xc9, 0xd9, 0x99, 0xc4, 0xd3, + 0x07, 0x95, 0xf1, 0x3e, 0x7c, 0x6d, 0x58, 0x86, 0xc0, 0x8d, 0x5a, 0xec, + 0x2b, 0xe4, 0x92, 0xc2, 0x02, 0x38, 0xc8, 0x98, 0x9d, 0xba, 0x4d, 0x69, + 0x24, 0x0c, 0x3d, 0x3a, 0x6b, 0xb4, 0xd2, 0x7e, 0x22, 0xae, 0xdf, 0x28, + 0x4c, 0x3d, 0xb6, 0x36, 0xb7, 0x17, 0x7b, 0x3d, 0x24, 0xb5, 0xe9, 0x1f, + 0x1a, 0xb9, 0x80, 0x57, 0x38, 0x9e, 0xe3, 0x0a, 0xca, 0xd7, 0x43, 0xa5, + 0xf8, 0x00, 0x7c, 0x1e, 0xd9, 0x1e, 0xc0, 0xfe, 0xf4, 0x8c, 0x90, 0xc9, + 0x9a, 0xb3, 0x70, 0xb2, 0x89, 0xa8, 0x39, 0x8a, 0x8f, 0x78, 0x5d, 0xa2, + 0x20, 0xd4, 0x13, 0xb1, 0x2e, 0x16, 0xa9, 0xb6, 0xe2, 0xfb, 0xfa, 0x27, + 0x48, 0xb5, 0x4c, 0x6e, 0x56, 0x1e, 0xaa, 0xcc, 0xb0, 0x73, 0xa5, 0xcb, + 0x4a, 0xa4, 0xaa, 0xc0, 0xc6, 0x8d, 0x99, 0x80, 0x02, 0x7d, 0x2e, 0x20, + 0xc7, 0xa6, 0x6b, 0xa8, 0x04, 0x8b, 0xbd, 0xfa, 0x63, 0x7c, 0x54, 0x2b, + 0x1a, 0x97, 0x3c, 0xdc, 0x75, 0x40, 0x42, 0xd7, 0x5b, 0x01, 0x4e, 0x93, + 0x40, 0x29, 0x35, 0xb8, 0xf9, 0xdc, 0x65, 0xed, 0x7a, 0x63, 0x8f, 0xd7, + 0x14, 0x87, 0xcc, 0x4f, 0xb3, 0xde, 0x7f, 0x75, 0xe2, 0xbe, 0x86, 0x0c, + 0xb6, 0xf1, 0xd3, 0x2c, 0xe6, 0x27, 0x11, 0x2a, 0xd1, 0xe8, 0xb9, 0x00, + 0xc9, 0x3d, 0x13, 0xcf, 0xf8, 0x68, 0x2c, 0xa0, 0x96, 0x3b, 0xec, 0xd0, + 0x50, 0x7a, 0x4c, 0xf5, 0xdf, 0x01, 0x25, 0x50, 0x3a, 0x09, 0xa3, 0x53, + 0x30, 0x51, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, + 0x14, 0xf5, 0xd9, 0x15, 0xbc, 0x9f, 0xc2, 0x54, 0xfe, 0xc5, 0x53, 0x1f, + 0xf7, 0xb3, 0x42, 0xc9, 0xbd, 0xa3, 0xb1, 0x87, 0x18, 0x30, 0x1f, 0x06, + 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xf5, 0xd9, + 0x15, 0xbc, 0x9f, 0xc2, 0x54, 0xfe, 0xc5, 0x53, 0x1f, 0xf7, 0xb3, 0x42, + 0xc9, 0xbd, 0xa3, 0xb1, 0x87, 0x18, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, + 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, + 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, + 0x03, 0x82, 0x09, 0x75, 0x00, 0x74, 0x4d, 0x20, 0x62, 0xcb, 0x10, 0x56, + 0xfb, 0x56, 0xc0, 0x81, 0xda, 0x87, 0x65, 0x04, 0x99, 0x05, 0xb0, 0x1d, + 0x88, 0xe6, 0xcf, 0xf3, 0xe4, 0xe1, 0x54, 0xe0, 0xca, 0xda, 0x16, 0xec, + 0xc2, 0x1d, 0x6b, 0x13, 0x98, 0x0b, 0x1f, 0x52, 0xe3, 0xb0, 0xe7, 0x15, + 0xac, 0x59, 0x75, 0x0d, 0xf5, 0x7e, 0xc8, 0x79, 0xb6, 0xc6, 0x79, 0x2f, + 0x2b, 0x37, 0x9d, 0x9b, 0x4c, 0xb6, 0xe8, 0xc3, 0x66, 0xeb, 0x86, 0x1e, + 0x76, 0xba, 0x40, 0xbb, 0x14, 0x0b, 0xd3, 0x54, 0x23, 0xab, 0xf1, 0x2d, + 0x1f, 0x5e, 0x1e, 0x47, 0xa0, 0x3f, 0x71, 0x3c, 0xd7, 0x35, 0x75, 0xa7, + 0x0a, 0x0d, 0xdf, 0x9f, 0x39, 0x57, 0xb8, 0xf1, 0x3b, 0x70, 0x12, 0xb6, + 0xff, 0xee, 0xa9, 0x81, 0xd1, 0xc1, 0x64, 0x83, 0x93, 0xfe, 0xb4, 0xcd, + 0x8b, 0xbc, 0xf1, 0xb5, 0xbb, 0xf3, 0xac, 0x45, 0xc9, 0x18, 0x7a, 0xc1, + 0xb0, 0x90, 0x85, 0xa5, 0xae, 0x5d, 0x81, 0x2b, 0xe8, 0xa3, 0x2a, 0xdf, + 0x23, 0x27, 0x55, 0x6b, 0x87, 0x9b, 0x15, 0x45, 0x93, 0xf1, 0x25, 0x88, + 0x4c, 0x98, 0x04, 0x64, 0x42, 0x7e, 0x54, 0xdf, 0x83, 0x81, 0x53, 0xdb, + 0xca, 0x9f, 0x03, 0x1b, 0x92, 0x42, 0xcf, 0xcd, 0xe2, 0xf0, 0x30, 0xa4, + 0x90, 0x9e, 0x18, 0x00, 0xc5, 0xb6, 0x03, 0xb4, 0x00, 0xc7, 0xc7, 0x02, + 0xc1, 0xbb, 0x40, 0xc3, 0x5a, 0x24, 0x47, 0xa1, 0xbf, 0x46, 0x36, 0x9d, + 0x34, 0x9a, 0x7f, 0x67, 0x9c, 0xa2, 0x7d, 0xa2, 0x1a, 0x03, 0x49, 0x5b, + 0xf3, 0x8a, 0x4d, 0xe0, 0xdb, 0x0d, 0x2b, 0xfe, 0xfe, 0x7f, 0x1c, 0x04, + 0x7a, 0xf2, 0x4d, 0x43, 0xb7, 0x1d, 0x2a, 0xc9, 0xe6, 0x8b, 0xa0, 0x46, + 0x38, 0x4f, 0xd8, 0x77, 0x4c, 0x68, 0x08, 0xb9, 0x16, 0xa8, 0x97, 0x8a, + 0xea, 0x91, 0xc5, 0x70, 0x3e, 0xad, 0xb6, 0x20, 0x3f, 0x45, 0xcc, 0xfa, + 0x77, 0x7a, 0x54, 0xff, 0x68, 0x37, 0x2a, 0x6c, 0x0b, 0xed, 0x12, 0x4a, + 0x92, 0xa5, 0x20, 0xc4, 0xa6, 0xec, 0x11, 0xb0, 0x42, 0xfa, 0xc7, 0x12, + 0x4c, 0xc1, 0xea, 0xcd, 0xd3, 0xd4, 0x39, 0x97, 0x47, 0x37, 0x5c, 0xe4, + 0xc2, 0x6a, 0x0c, 0x4c, 0xa1, 0x78, 0xbd, 0x45, 0x76, 0x27, 0x0d, 0xc3, + 0xbc, 0x31, 0x8b, 0x1e, 0x56, 0x5b, 0x24, 0xb4, 0x10, 0x92, 0x7a, 0xc8, + 0xdc, 0x7b, 0xba, 0xfb, 0xb5, 0x2a, 0x6e, 0x8c, 0x5e, 0x9a, 0x5a, 0xf4, + 0x9d, 0x50, 0xe7, 0xe2, 0x3c, 0xc6, 0xfa, 0x19, 0x32, 0xf7, 0x13, 0x05, + 0xb0, 0x3f, 0x9a, 0xf5, 0xfe, 0x12, 0x22, 0x2a, 0xfa, 0x3d, 0x0b, 0x33, + 0x01, 0x96, 0x2e, 0x33, 0x21, 0xa6, 0x27, 0x64, 0x00, 0x6b, 0x0d, 0x33, + 0xba, 0xa9, 0x02, 0xe2, 0x83, 0x87, 0xfe, 0x52, 0x09, 0xa0, 0xb1, 0x95, + 0x4d, 0xf5, 0xb2, 0x57, 0x4e, 0x27, 0xf7, 0x9e, 0xba, 0x92, 0xf1, 0xda, + 0xb3, 0x04, 0xea, 0x65, 0xda, 0x30, 0x0d, 0x55, 0x4f, 0xba, 0x55, 0xef, + 0x00, 0x4b, 0x01, 0x6c, 0xe6, 0x74, 0x9b, 0x88, 0x78, 0x4a, 0x5d, 0x84, + 0x09, 0x63, 0xe9, 0x42, 0xa4, 0xcb, 0x0c, 0x1b, 0xf0, 0x30, 0xf6, 0xf8, + 0x63, 0x12, 0x79, 0x86, 0x1e, 0x01, 0xf6, 0xf5, 0x79, 0x33, 0xf8, 0x21, + 0xf0, 0x07, 0x38, 0x25, 0x7f, 0xd1, 0x70, 0x9b, 0x2e, 0x22, 0x8a, 0x26, + 0xf7, 0x2f, 0xb4, 0xf9, 0x45, 0x9d, 0x3e, 0xcf, 0x8a, 0x31, 0x02, 0xb8, + 0x12, 0xcd, 0xdf, 0x65, 0x61, 0x1f, 0xb8, 0x15, 0xb8, 0x78, 0x88, 0x2b, + 0xba, 0x3a, 0x16, 0x77, 0x53, 0x09, 0x4a, 0xbc, 0xa0, 0xd0, 0x25, 0xc5, + 0xf8, 0x34, 0x83, 0x2d, 0x6e, 0x3c, 0x94, 0x2c, 0x34, 0xfa, 0xf5, 0xeb, + 0x55, 0xf2, 0x97, 0x88, 0xf9, 0x46, 0xde, 0xd8, 0xc6, 0xe9, 0x0d, 0xef, + 0x0b, 0xe0, 0xa4, 0x39, 0x89, 0x63, 0xcf, 0xea, 0x16, 0x23, 0xa7, 0x9e, + 0xa3, 0x69, 0xd3, 0x7a, 0x1f, 0x9c, 0xb2, 0x5f, 0x19, 0x65, 0xaf, 0xbe, + 0x3b, 0x14, 0x47, 0xe0, 0xe7, 0x1d, 0x0e, 0xe7, 0x94, 0x3a, 0xda, 0xb0, + 0x82, 0xb0, 0xaf, 0x59, 0x37, 0x86, 0x4f, 0xe5, 0xc0, 0x06, 0x90, 0x05, + 0x02, 0x5d, 0x0c, 0x27, 0x37, 0x56, 0xe6, 0x3c, 0x1a, 0x2a, 0xb3, 0xa6, + 0x13, 0xe2, 0x3e, 0x80, 0x69, 0x9f, 0x7d, 0x1b, 0xd9, 0x05, 0x2d, 0xd1, + 0x99, 0x62, 0xf2, 0x13, 0x11, 0x7a, 0xef, 0x99, 0x83, 0x67, 0xcd, 0xda, + 0x17, 0x09, 0x34, 0x95, 0xf8, 0x8a, 0x75, 0xf9, 0xd1, 0x1c, 0x84, 0x1b, + 0xd9, 0x00, 0x85, 0x6f, 0xc5, 0xcd, 0x9b, 0xe1, 0x32, 0xa3, 0x21, 0x1e, + 0xdf, 0x09, 0x56, 0x7a, 0xaa, 0x45, 0x82, 0x22, 0xda, 0x94, 0x9e, 0x5d, + 0xd6, 0x9c, 0xd9, 0x93, 0x69, 0x90, 0x3b, 0xff, 0xb8, 0x64, 0xc6, 0x85, + 0xa8, 0xfc, 0xe7, 0xf8, 0x3a, 0xbc, 0x50, 0x87, 0xa9, 0xbd, 0x93, 0xcf, + 0xfb, 0xc6, 0x12, 0xc0, 0x35, 0xbc, 0xfa, 0x85, 0xd8, 0x24, 0x3a, 0xe5, + 0xdc, 0xae, 0x2e, 0x16, 0xba, 0xd9, 0x32, 0x4b, 0x21, 0x90, 0x90, 0x75, + 0x34, 0x53, 0x28, 0x9e, 0xb8, 0x3a, 0x65, 0xad, 0x3c, 0xf8, 0xb9, 0xb7, + 0x3e, 0xad, 0x10, 0x17, 0x65, 0xe9, 0xae, 0x66, 0x3e, 0x84, 0xee, 0x02, + 0xf2, 0x66, 0x59, 0x25, 0xc1, 0xd8, 0x89, 0x63, 0xcf, 0x8c, 0xcb, 0x71, + 0x4b, 0xda, 0x0c, 0x40, 0xf2, 0x54, 0x33, 0xed, 0xc6, 0x05, 0x46, 0xff, + 0x78, 0xa4, 0x1e, 0x49, 0x57, 0xb7, 0x84, 0x78, 0x35, 0x0a, 0x9a, 0x28, + 0x4a, 0x0a, 0x29, 0x54, 0x45, 0xe3, 0xee, 0xf4, 0x01, 0x24, 0x7e, 0x46, + 0xc4, 0x4f, 0xee, 0xd1, 0x2f, 0xd6, 0x59, 0xe9, 0xfe, 0x64, 0x07, 0x19, + 0xda, 0xea, 0x91, 0x71, 0x19, 0x4f, 0x2c, 0x9b, 0x5c, 0xab, 0xcc, 0x3d, + 0x49, 0x09, 0x51, 0x8e, 0xaa, 0xff, 0x2e, 0xa6, 0x53, 0xf4, 0x26, 0x88, + 0x14, 0xe9, 0x19, 0x12, 0x17, 0x98, 0x8b, 0x53, 0xc9, 0xd5, 0x6f, 0x5d, + 0xb0, 0x03, 0x17, 0x98, 0xd2, 0x88, 0x0c, 0xc4, 0xc7, 0xd1, 0x06, 0x80, + 0x29, 0xc0, 0xe4, 0x64, 0x75, 0xb8, 0xed, 0x3e, 0x8d, 0x33, 0x85, 0xe6, + 0x40, 0x8a, 0x60, 0xf5, 0x65, 0xe3, 0x20, 0x5a, 0xaa, 0xb1, 0x15, 0x32, + 0xa2, 0x27, 0xe9, 0xe5, 0x18, 0x19, 0x77, 0xbb, 0x31, 0x17, 0x7a, 0x1c, + 0xe6, 0xe1, 0x25, 0xa2, 0xfa, 0x06, 0x3d, 0xa3, 0xb7, 0xff, 0x2a, 0xa3, + 0x5c, 0x8e, 0x31, 0x26, 0x50, 0xcc, 0x1d, 0xd6, 0xf8, 0xaf, 0x1a, 0x1c, + 0x16, 0xf2, 0x9f, 0xda, 0x07, 0x5a, 0x89, 0xe9, 0x9b, 0x4f, 0xcb, 0xc4, + 0x96, 0xf5, 0x7d, 0xc2, 0x67, 0x31, 0x74, 0x29, 0xaf, 0x93, 0x2b, 0x26, + 0x20, 0x96, 0x87, 0xa1, 0xea, 0xe2, 0x9e, 0xfe, 0x0b, 0x60, 0xcf, 0xec, + 0x88, 0x72, 0x0d, 0xa1, 0x7b, 0xb6, 0xc4, 0x27, 0xe7, 0x9c, 0x27, 0x65, + 0xd6, 0x93, 0xfb, 0xfd, 0x96, 0xe5, 0xa1, 0x2f, 0xe7, 0x9e, 0x8d, 0xb8, + 0x51, 0x74, 0x91, 0x56, 0xeb, 0xdf, 0xf4, 0x0d, 0x6f, 0x17, 0xdf, 0x97, + 0x1e, 0x65, 0x37, 0x8c, 0xd9, 0x27, 0xf1, 0x24, 0xfc, 0x7d, 0x2e, 0x3a, + 0x47, 0x52, 0xb4, 0xfb, 0xe9, 0xf6, 0xbe, 0x4a, 0xf7, 0x34, 0x24, 0x10, + 0x4b, 0xda, 0x2a, 0x91, 0xca, 0x76, 0x8c, 0x1f, 0x38, 0x83, 0x5d, 0x16, + 0x14, 0x3f, 0x1a, 0xc2, 0xa1, 0x28, 0x21, 0x4c, 0x0a, 0x41, 0x4b, 0xb6, + 0xc5, 0x50, 0x48, 0x97, 0x9e, 0x53, 0xb2, 0x34, 0x21, 0xfd, 0xde, 0x25, + 0xb5, 0xf4, 0x65, 0x72, 0x19, 0xfc, 0x16, 0x9e, 0xb9, 0x55, 0x57, 0x46, + 0x7c, 0x2f, 0x21, 0x47, 0xab, 0x5e, 0x8b, 0xd1, 0x6d, 0xb9, 0x37, 0xdd, + 0x33, 0x59, 0xea, 0xe9, 0x2c, 0x53, 0x4a, 0x10, 0xc5, 0x22, 0xf5, 0xe5, + 0x04, 0x62, 0x5b, 0x97, 0x1f, 0xbb, 0xf8, 0xd8, 0xd8, 0x77, 0x67, 0x99, + 0xb7, 0xaf, 0x59, 0xd3, 0xb1, 0xca, 0xe4, 0x9f, 0x33, 0xa6, 0xca, 0x2c, + 0x33, 0x0c, 0xf9, 0xc5, 0x78, 0x52, 0x22, 0xf7, 0xf5, 0x3e, 0xb9, 0x5f, + 0x74, 0x10, 0xaa, 0x6e, 0xd6, 0x78, 0xc2, 0xba, 0x1c, 0x05, 0x0a, 0xfc, + 0x54, 0x03, 0x67, 0xff, 0xd2, 0x5c, 0x0b, 0xad, 0x1e, 0xfd, 0x58, 0x90, + 0x11, 0xb9, 0xf8, 0x27, 0x8b, 0xb8, 0x3d, 0xa5, 0x40, 0x09, 0xe1, 0xa0, + 0x7d, 0x31, 0x7c, 0x65, 0x38, 0x60, 0x55, 0x00, 0xa7, 0xbd, 0xa6, 0x3b, + 0x69, 0x7a, 0x57, 0x30, 0x60, 0xa0, 0x14, 0xf7, 0x01, 0x6e, 0x5a, 0xf8, + 0xcc, 0x64, 0xf1, 0x24, 0x55, 0x71, 0x23, 0x42, 0x70, 0xdc, 0x06, 0x29, + 0x84, 0xfd, 0x8f, 0x73, 0xf1, 0xad, 0xde, 0x5b, 0x91, 0xd9, 0xe9, 0xa1, + 0xe5, 0x39, 0xb8, 0x52, 0xd2, 0x4c, 0x0c, 0x8e, 0xa7, 0x05, 0xad, 0xe5, + 0x17, 0x1f, 0x67, 0xed, 0x7f, 0x83, 0x7c, 0x29, 0xa0, 0x9e, 0xcb, 0xd9, + 0x36, 0x68, 0xb0, 0x4f, 0xb8, 0x08, 0xa7, 0x07, 0x15, 0xd5, 0x47, 0x3a, + 0xb7, 0x8f, 0xdc, 0xe5, 0xed, 0x89, 0x0a, 0xaa, 0x6e, 0xfe, 0x18, 0x62, + 0xc3, 0x0a, 0x0c, 0x5f, 0xd7, 0x9d, 0xaf, 0x85, 0x48, 0x77, 0xf3, 0x8d, + 0x16, 0x3d, 0x3a, 0x26, 0xee, 0x64, 0xd3, 0xc2, 0x85, 0x0b, 0x55, 0x1b, + 0x48, 0xe3, 0x51, 0x7f, 0x57, 0xf6, 0xd2, 0x21, 0x44, 0x33, 0xab, 0x50, + 0x3a, 0xb4, 0xcd, 0x70, 0x8f, 0x62, 0x5f, 0xaa, 0x68, 0xa1, 0x57, 0x7f, + 0x53, 0x21, 0x42, 0x0b, 0xaa, 0xbf, 0xd1, 0x5c, 0xf5, 0x40, 0x50, 0x09, + 0x1c, 0x1f, 0xc8, 0x88, 0xbf, 0x00, 0x40, 0x5d, 0xd3, 0x3a, 0x48, 0xc9, + 0xc7, 0x3f, 0xe4, 0x88, 0xf0, 0xd2, 0x19, 0xcc, 0xe9, 0xfc, 0xc3, 0x53, + 0x5c, 0xa2, 0x8c, 0xd0, 0x40, 0x27, 0xcf, 0x42, 0x5e, 0x0d, 0x56, 0xf2, + 0x30, 0xc6, 0xfb, 0xe4, 0xdc, 0x4a, 0x9d, 0xe2, 0x64, 0x12, 0x07, 0x3d, + 0x34, 0xfe, 0x30, 0x6a, 0xcb, 0x8a, 0x85, 0x2a, 0xf8, 0xc0, 0x3e, 0xaf, + 0x0c, 0x44, 0xf1, 0x20, 0x82, 0x4e, 0xe3, 0xfa, 0x88, 0x18, 0xd7, 0x5e, + 0x6b, 0x27, 0xc6, 0x21, 0x29, 0x9a, 0xa6, 0xb7, 0x15, 0x74, 0x45, 0x2f, + 0x22, 0xe5, 0xcf, 0x2d, 0xc7, 0x0f, 0xb5, 0x69, 0x59, 0x7a, 0x3f, 0x14, + 0x48, 0xe0, 0x21, 0x4c, 0x43, 0x3d, 0xf1, 0xd9, 0xa5, 0x08, 0xf4, 0xb3, + 0xd7, 0xf5, 0x4d, 0xd3, 0xed, 0xab, 0x58, 0x17, 0x98, 0xf6, 0xda, 0x5e, + 0x4d, 0x64, 0x74, 0x17, 0xd0, 0x97, 0x74, 0xa6, 0x68, 0x2f, 0xb5, 0xd8, + 0x79, 0x53, 0x60, 0x84, 0x9a, 0xc6, 0x54, 0x41, 0x7c, 0x86, 0xb7, 0x5a, + 0x75, 0xc5, 0x61, 0x7a, 0x02, 0x97, 0xf8, 0x30, 0x3e, 0x49, 0x02, 0x79, + 0x65, 0x01, 0xa5, 0xe6, 0x1c, 0xa5, 0xe2, 0x1b, 0x1d, 0xec, 0x57, 0x89, + 0xd3, 0x57, 0x1f, 0x54, 0x4d, 0x7f, 0xf0, 0x30, 0x57, 0xd5, 0x40, 0x9d, + 0xa4, 0xc3, 0xef, 0x04, 0x9d, 0x2f, 0xa3, 0x06, 0x33, 0x83, 0x1e, 0x29, + 0x60, 0xc5, 0x5c, 0x23, 0xbb, 0x13, 0xf6, 0x09, 0xfa, 0x79, 0x04, 0x13, + 0x42, 0x03, 0xd1, 0x4f, 0x20, 0x59, 0xad, 0xb3, 0x89, 0x4c, 0xce, 0x6e, + 0xe3, 0xe0, 0xa2, 0x76, 0x05, 0x49, 0x06, 0xdc, 0x8b, 0x07, 0x1f, 0x72, + 0x54, 0x2e, 0x5c, 0xbe, 0x46, 0xde, 0x0e, 0x94, 0xac, 0x3b, 0x72, 0x05, + 0xde, 0x8a, 0x2d, 0x05, 0xcd, 0xc0, 0x1b, 0x30, 0xb6, 0x82, 0x74, 0x15, + 0xf6, 0x1e, 0xd5, 0x32, 0x38, 0x40, 0x96, 0xf5, 0xbd, 0xb0, 0xf0, 0x82, + 0x99, 0x36, 0x26, 0x9c, 0x79, 0x0f, 0x15, 0xde, 0x2f, 0x89, 0xaa, 0xfd, + 0x1c, 0x0e, 0xec, 0x7f, 0xaf, 0x24, 0x65, 0xac, 0xf4, 0xca, 0x60, 0x12, + 0x94, 0x3b, 0x6a, 0xd8, 0x19, 0x36, 0x4c, 0x54, 0x71, 0xf3, 0x2e, 0x28, + 0x6e, 0x1c, 0xe7, 0x96, 0xa3, 0x6e, 0x42, 0x0f, 0xcc, 0x0f, 0x75, 0x75, + 0x25, 0x9b, 0xeb, 0x09, 0xdc, 0x1c, 0xde, 0x4d, 0x3a, 0x4a, 0x84, 0x4c, + 0xcf, 0x21, 0x0d, 0x65, 0x3b, 0x0d, 0x6b, 0xc2, 0x90, 0xb6, 0x32, 0x0f, + 0xce, 0x9f, 0x7f, 0xd8, 0xef, 0x08, 0x59, 0x4b, 0x11, 0x2a, 0x5c, 0x41, + 0x39, 0x3d, 0x3e, 0x15, 0x17, 0x8a, 0x00, 0x14, 0x92, 0xa5, 0x60, 0x0f, + 0x62, 0xbc, 0x44, 0xb8, 0x66, 0xdb, 0x51, 0xc6, 0x06, 0x85, 0x1e, 0xfe, + 0xab, 0x64, 0x35, 0x03, 0x2e, 0x98, 0x54, 0x6d, 0x65, 0xb4, 0x12, 0x08, + 0xaa, 0x48, 0x3e, 0x5c, 0x30, 0x7b, 0xcf, 0xd4, 0x24, 0xe1, 0xf1, 0xd6, + 0x96, 0xc3, 0x3e, 0xaf, 0xd9, 0x37, 0xc4, 0x47, 0xed, 0x37, 0x46, 0x4b, + 0xac, 0x52, 0x13, 0xc4, 0x3c, 0xb2, 0x71, 0x58, 0xf4, 0x26, 0x98, 0x64, + 0x73, 0x5a, 0x60, 0xf3, 0x27, 0xbb, 0xd3, 0x30, 0x7c, 0x79, 0x99, 0x1c, + 0x06, 0x19, 0x18, 0xfc, 0x2f, 0xf8, 0x40, 0x20, 0x24, 0x04, 0xc9, 0x91, + 0x1b, 0xb2, 0x20, 0x77, 0x3f, 0x5e, 0xd7, 0xf5, 0x81, 0x11, 0x2e, 0xb1, + 0x05, 0xd1, 0x75, 0xf9, 0xbc, 0x69, 0xbf, 0x73, 0x42, 0xf2, 0xa0, 0x57, + 0x27, 0x62, 0xa2, 0x8a, 0xad, 0x8c, 0x9c, 0x96, 0xa6, 0xa9, 0x91, 0xca, + 0x1f, 0x74, 0x66, 0x6e, 0x5a, 0x76, 0x08, 0x62, 0xcd, 0x08, 0x03, 0x24, + 0x79, 0x5e, 0x6a, 0x85, 0xfe, 0x24, 0x19, 0xd6, 0x3b, 0x6e, 0x91, 0x32, + 0x57, 0xd8, 0xc6, 0x86, 0x58, 0x03, 0xba, 0x1d, 0x8f, 0xc3, 0xb4, 0x48, + 0x3d, 0x48, 0x39, 0x9e, 0x85, 0x02, 0x09, 0xb7, 0xa7, 0x05, 0x20, 0x24, + 0x02, 0x59, 0xc3, 0x81, 0x5f, 0x58, 0x11, 0xf2, 0xb3, 0x3b, 0x43, 0x41, + 0xb2, 0x30, 0xd5, 0x08, 0x63, 0xd7, 0xb4, 0x86, 0x1a, 0x45, 0x3c, 0xf0, + 0x83, 0xc7, 0x46, 0x09, 0x0c, 0x2f, 0x33, 0xdc, 0x56, 0xba, 0x2c, 0xbd, + 0x0a, 0x43, 0x5d, 0x7b, 0x57, 0xcf, 0x64, 0x6a, 0xb4, 0x5c, 0xb4, 0x8c, + 0x28, 0x46, 0xd4, 0x18, 0x6c, 0x5b, 0xa3, 0x1b, 0x72, 0xfd, 0xb6, 0x8f, + 0x28, 0x59, 0x22, 0xa0, 0xa0, 0x05, 0x60, 0x35, 0x89, 0x39, 0x08, 0xfb, + 0x51, 0x00, 0x1d, 0x5c, 0xc8, 0x15, 0xc9, 0x4b, 0xce, 0x75, 0xa3, 0xc9, + 0x85, 0x5c, 0x51, 0x47, 0x58, 0xf9, 0xc5, 0xb5, 0x96, 0x39, 0x9e, 0x24, + 0x2f, 0x20, 0x3b, 0xb0, 0x3f, 0x02, 0xc5, 0xe2, 0x9a, 0x01, 0x77, 0x69, + 0xd1, 0x9c, 0x01, 0x8a, 0x3a, 0xec, 0xf0, 0xf1, 0x38, 0x31, 0xa7, 0x16, + 0x1d, 0xf3, 0xcb, 0xcd, 0x57, 0x1f, 0xc3, 0x34, 0xae, 0xea, 0x54, 0x94, + 0xfa, 0xf4, 0x8f, 0xe8, 0x30, 0xc8, 0x0e, 0x04, 0x42, 0x9d, 0xf7, 0xdc, + 0x52, 0x97, 0x3f, 0xff, 0xc1, 0x4e, 0x62, 0x5e, 0xd9, 0xc1, 0x16, 0xdc, + 0xec, 0x4e, 0x6d, 0xd5, 0x9a, 0xe2, 0x53, 0x2d, 0x41, 0xdd, 0x4e, 0x42, + 0xb0, 0x9a, 0x0c, 0x1b, 0x1f, 0x9e, 0x03, 0xce, 0xb3, 0x64, 0x76, 0xd1, + 0x38, 0x32, 0x3a, 0x4f, 0xe6, 0xef, 0x6c, 0x7a, 0xdf, 0x57, 0x2b, 0xe4, + 0xf3, 0x92, 0x41, 0x74, 0x21, 0x13, 0x48, 0x96, 0x0e, 0xbc, 0x85, 0x86, + 0xe0, 0xcc, 0x87, 0x43, 0xb6, 0x28, 0xff, 0x7a, 0x66, 0x33, 0x83, 0x58, + 0x51, 0xf1, 0x46, 0x7e, 0x91, 0xf1, 0x05, 0x71, 0x0f, 0x1e, 0x6a, 0x1c, + 0xac, 0x48, 0xea, 0x29, 0xbc, 0x19, 0x41, 0xfe, 0x96, 0xfe, 0x6a, 0x44, + 0x1d, 0x96, 0x91, 0x1a, 0x76, 0xce, 0xb8, 0x4c, 0x3b, 0xa0, 0x5c, 0x62, + 0xed, 0x00, 0xfb, 0xb4, 0xea, 0xf2, 0x2c, 0x1b, 0xe2, 0xe4, 0x50, 0x4c, + 0x28, 0x89, 0x06, 0xf4, 0x16, 0xd9, 0x7b, 0xec, 0x92, 0xad, 0xa5, 0x3b, + 0x02, 0xc8, 0x87, 0xb7, 0xae, 0x83, 0xf5, 0xf9, 0xb8, 0x6b, 0xd0, 0x3b, + 0x1a, 0xac, 0x8c, 0x44, 0xbf, 0x38, 0x75, 0x2d, 0x57, 0x14, 0xf9, 0x17, + 0x53, 0x4d, 0xb7, 0xfe, 0xf1, 0xbc, 0x10, 0x78, 0xb7, 0x27, 0xa4, 0xd1, + 0xd3, 0x45, 0xf5, 0x8d, 0x95, 0xe8, 0x89, 0x3c, 0xf9, 0x23, 0xbb, 0xf6, + 0xc8, 0x42, 0xc8, 0x18, 0x10, 0xd0, 0x10, 0x18, 0x96, 0x33, 0x1d, 0x71, + 0x6f, 0x0f, 0xd1, 0xb9, 0x3a, 0x40, 0x31, 0xba, 0xc8, 0xce, 0x62, 0x2c, + 0x32, 0x66, 0x8d, 0x19, 0xf1, 0x71, 0x47, 0x3f, 0x22, 0x39, 0x00, 0x3e, + 0xe8, 0x5a, 0x43, 0x4f, 0x63, 0xbc, 0xb6, 0x23, 0x57, 0x6d, 0x00, 0x40, + 0x01, 0x09, 0x27, 0xab, 0xa7, 0x26, 0x20, 0xce, 0x63, 0xf1, 0xaa, 0xb2, + 0xd2, 0x4e, 0xb8, 0x8c, 0x87, 0x84, 0x43, 0x89, 0xec, 0x0d, 0x89, 0xef, + 0x6a, 0x2f, 0x2b, 0xb9, 0x6b, 0x5c, 0x45, 0x1c, 0xdd, 0xe0, 0xbd, 0x47, + 0x5b, 0x2e, 0x18, 0x75, 0xc8, 0x17, 0x41, 0xb3, 0x2b, 0x4d, 0x0e, 0x08, + 0xbb, 0x3f, 0x44, 0x01, 0x53, 0x00, 0x33, 0x8b, 0x70, 0xeb, 0x09, 0xe9, + 0xb3, 0x06, 0xe5, 0xa1, 0x45, 0x66, 0x71, 0x8b, 0x6f, 0x50, 0x70, 0xf5, + 0x23, 0xa1, 0xb7, 0x70, 0x82, 0x2f, 0x55, 0xcd, 0x10, 0xe3, 0x1c, 0xae, + 0x80, 0x6f, 0xd7, 0x2b, 0x8f, 0x17, 0x6d, 0x40, 0xe6, 0x9a, 0x64, 0x10, + 0x55, 0x99, 0x03, 0x4b, 0xc1, 0x95, 0xeb, 0x99, 0xf1, 0x49, 0x1a, 0xe3, + 0xba, 0x5e, 0x6c, 0x82, 0xc7, 0xee, 0x2e, 0x7f, 0x3b, 0x36, 0x57, 0x9f, + 0x2c, 0xe7, 0xb5, 0x2b, 0xf0, 0xab, 0xd6, 0xa9, 0xe7, 0x2e, 0x4b, 0xea, + 0x87, 0xb5, 0x19, 0x55, 0x1b, 0xea, 0xe6, 0x76, 0xd2, 0x22, 0x7b, 0x23, + 0xdf, 0x63, 0xc7, 0xcb, 0x64, 0x41, 0xca, 0xd2, 0x4b, 0x88, 0x82, 0xbf, + 0x58, 0xbd, 0xad, 0xd4, 0x48, 0x1f, 0xcd, 0x42, 0x94, 0x66, 0xc7, 0x16, + 0x84, 0x00, 0x03, 0x08, 0x1f, 0x3d, 0x40, 0x59, 0x65, 0x77, 0x95, 0x99, + 0xa4, 0xae, 0xc0, 0xd6, 0xee, 0xfa, 0x3b, 0x49, 0x71, 0x73, 0x9f, 0xc1, + 0xee, 0xfb, 0x12, 0x36, 0x59, 0x83, 0x90, 0x93, 0xa8, 0xad, 0xc6, 0xc9, + 0xcc, 0xd5, 0xde, 0xe9, 0xee, 0xf1, 0xff, 0x24, 0x41, 0x73, 0x75, 0x79, + 0x7e, 0x84, 0xa7, 0xba, 0xc0, 0xc6, 0xd7, 0xdd, 0xeb, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x19, 0x2a, + 0x38, 0x30, 0x82, 0x0f, 0xcd, 0x30, 0x82, 0x06, 0x43, 0xa0, 0x03, 0x02, + 0x01, 0x02, 0x02, 0x01, 0x01, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, + 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x30, 0x22, 0x31, 0x20, 0x30, 0x1e, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x17, 0x44, 0x4d, 0x54, 0x46, 0x20, + 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, 0x6d, 0x20, 0x6d, 0x6c, 0x64, 0x73, + 0x61, 0x34, 0x34, 0x20, 0x63, 0x61, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x35, + 0x30, 0x34, 0x32, 0x33, 0x30, 0x38, 0x32, 0x39, 0x35, 0x31, 0x5a, 0x17, + 0x0d, 0x33, 0x35, 0x30, 0x34, 0x32, 0x31, 0x30, 0x38, 0x32, 0x39, 0x35, + 0x31, 0x5a, 0x30, 0x31, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x0c, 0x26, 0x44, 0x4d, 0x54, 0x46, 0x20, 0x6c, 0x69, 0x62, 0x73, + 0x70, 0x64, 0x6d, 0x20, 0x6d, 0x6c, 0x64, 0x73, 0x61, 0x34, 0x34, 0x20, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, + 0x20, 0x63, 0x65, 0x72, 0x74, 0x30, 0x82, 0x05, 0x32, 0x30, 0x0b, 0x06, + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x03, 0x82, + 0x05, 0x21, 0x00, 0xfb, 0x22, 0xe5, 0x4f, 0x2d, 0xba, 0x97, 0xe3, 0x2c, + 0x5b, 0xe4, 0x77, 0x72, 0xef, 0xda, 0xee, 0x47, 0x18, 0x6b, 0xea, 0x02, + 0xc9, 0x33, 0xae, 0x22, 0xe3, 0x22, 0x99, 0x47, 0xc2, 0x19, 0xe3, 0xeb, + 0x95, 0xb5, 0x8f, 0x8b, 0x3c, 0x8f, 0xac, 0xa6, 0x6a, 0xaf, 0xf6, 0x67, + 0x12, 0xd0, 0xbb, 0x50, 0x65, 0x92, 0x2b, 0xaa, 0xb7, 0xc5, 0xd8, 0xd3, + 0xb6, 0xe8, 0xf0, 0x88, 0xff, 0xf0, 0x41, 0x10, 0xa5, 0x97, 0x74, 0x23, + 0x00, 0x6a, 0xa2, 0x05, 0xaf, 0x78, 0x53, 0x19, 0x60, 0xae, 0x87, 0x9a, + 0x64, 0x0d, 0xdc, 0xf1, 0xcd, 0x01, 0x94, 0x4c, 0x92, 0xd5, 0xbd, 0x43, + 0x71, 0x25, 0x82, 0x86, 0x1c, 0xa3, 0xee, 0xc8, 0x8e, 0xc5, 0x81, 0xf4, + 0xc4, 0xbf, 0xc3, 0xc3, 0x1f, 0x57, 0x9c, 0x22, 0x4b, 0x65, 0xb8, 0x78, + 0xcd, 0xca, 0xa5, 0x5c, 0x55, 0xb0, 0x7c, 0x39, 0xeb, 0x20, 0xf3, 0x15, + 0x1b, 0x78, 0x34, 0x04, 0x46, 0x9d, 0xf6, 0xc2, 0x92, 0x74, 0xc8, 0x49, + 0x89, 0xb9, 0x61, 0x30, 0x7a, 0xb0, 0x7d, 0xdd, 0x36, 0x3b, 0x20, 0x5f, + 0xe2, 0xaa, 0x5a, 0x0f, 0x22, 0x4f, 0x40, 0x6b, 0x8c, 0x5e, 0x12, 0x34, + 0xb8, 0x5c, 0x09, 0x17, 0x18, 0xa1, 0x86, 0x14, 0xa4, 0x37, 0xc9, 0xb3, + 0x1e, 0x7a, 0xbe, 0x0e, 0xb0, 0x29, 0x63, 0x23, 0x8a, 0x7e, 0xbc, 0x4c, + 0xbd, 0x2b, 0x0e, 0x4b, 0xd2, 0xbd, 0x1d, 0x48, 0x0b, 0x48, 0xe3, 0x23, + 0xce, 0x54, 0x11, 0xc8, 0xb1, 0xcf, 0x73, 0xea, 0xb6, 0x7b, 0x8f, 0x92, + 0xdd, 0xc8, 0xf6, 0x6e, 0x65, 0xdd, 0x1e, 0x49, 0x23, 0xaa, 0x58, 0x7e, + 0xd8, 0x43, 0x55, 0xb7, 0xf1, 0x38, 0xe8, 0xa5, 0x87, 0x7f, 0x30, 0x63, + 0x20, 0x34, 0x81, 0x8b, 0x3a, 0xde, 0x8e, 0x8f, 0x2e, 0x56, 0x32, 0xfb, + 0x1c, 0xeb, 0x69, 0xb3, 0x8c, 0x7e, 0x71, 0x78, 0x99, 0x96, 0xc8, 0x11, + 0x27, 0xf5, 0xef, 0xb6, 0xf1, 0x90, 0x3e, 0x4b, 0x3d, 0x1e, 0x4f, 0x35, + 0x26, 0xd5, 0xcd, 0x5b, 0x5e, 0x38, 0xd6, 0x98, 0x6f, 0xd0, 0x88, 0x6a, + 0x46, 0x0b, 0xa3, 0xe4, 0x1a, 0x47, 0x60, 0x09, 0x68, 0x20, 0x84, 0xbf, + 0x31, 0xb0, 0xae, 0xcf, 0x26, 0xd5, 0x1d, 0xc0, 0x34, 0x6a, 0x87, 0xdf, + 0x1d, 0xd7, 0x26, 0xe1, 0xe5, 0xc2, 0x35, 0x4c, 0x4d, 0x35, 0x53, 0x23, + 0xae, 0x4b, 0xf3, 0x5a, 0x8c, 0xb1, 0x66, 0x9d, 0x5e, 0x35, 0x90, 0xd7, + 0xef, 0x68, 0x68, 0x21, 0x2b, 0xfe, 0xac, 0x34, 0xcf, 0xcc, 0xfe, 0x11, + 0xba, 0x8b, 0x3a, 0x9d, 0x56, 0xe1, 0x0f, 0xd0, 0xa4, 0x34, 0xb3, 0xbc, + 0xb1, 0x4e, 0x02, 0x62, 0xa5, 0x31, 0x6e, 0xd7, 0x7a, 0x40, 0x51, 0x96, + 0xf7, 0x88, 0x53, 0xcd, 0x04, 0xca, 0x1e, 0xd8, 0x25, 0x39, 0xe0, 0xa8, + 0x05, 0x2d, 0xa1, 0xef, 0x43, 0x5b, 0x3a, 0x20, 0xa0, 0x88, 0x7c, 0x2c, + 0x92, 0x0b, 0x34, 0x85, 0x6d, 0x98, 0xe1, 0xfe, 0x16, 0x2d, 0x6f, 0x0d, + 0x3d, 0x29, 0xbc, 0x3b, 0xe2, 0x35, 0x37, 0x33, 0x31, 0x18, 0x9e, 0x9b, + 0xe5, 0x58, 0x2c, 0x4f, 0x63, 0xfe, 0xbf, 0xc1, 0x3f, 0x82, 0xf8, 0xe6, + 0xd9, 0xa6, 0xfb, 0x67, 0xe2, 0x3a, 0x7a, 0xa4, 0xc8, 0xe9, 0x98, 0xf4, + 0xb4, 0x79, 0xc1, 0x21, 0x3e, 0xa7, 0x24, 0xd1, 0xab, 0x0f, 0x7b, 0xe5, + 0xe4, 0x31, 0x43, 0x63, 0x5b, 0x82, 0x82, 0xc6, 0xed, 0xab, 0x86, 0xc3, + 0xdc, 0xa5, 0x7a, 0x67, 0x4c, 0x71, 0xae, 0x22, 0x4e, 0xcb, 0x0c, 0xb3, + 0x83, 0x71, 0x77, 0x61, 0xe8, 0x5b, 0xd6, 0xc8, 0x73, 0x0e, 0x12, 0x59, + 0xcc, 0xf2, 0x93, 0x5a, 0xb2, 0x13, 0xae, 0x7e, 0x22, 0xb3, 0x69, 0x1b, + 0x43, 0x5d, 0xd5, 0xf6, 0xc4, 0x2f, 0xd8, 0xb6, 0x94, 0xbe, 0x3d, 0xb8, + 0x02, 0x30, 0x8e, 0x16, 0x99, 0xfb, 0xb7, 0x17, 0x57, 0xeb, 0xce, 0xc4, + 0x55, 0xb3, 0x8e, 0x6e, 0x5f, 0x61, 0x6d, 0xea, 0xd3, 0x5b, 0xa4, 0xf6, + 0x19, 0xf2, 0x8f, 0xfa, 0xc3, 0x09, 0x9e, 0xe4, 0x0d, 0x89, 0x8f, 0x79, + 0x03, 0xf6, 0xe1, 0x3f, 0xb2, 0xdd, 0xa4, 0x08, 0x67, 0x56, 0x58, 0x88, + 0xf2, 0xf0, 0x16, 0xcc, 0x24, 0x22, 0x63, 0x6c, 0x0d, 0xfc, 0xb6, 0x83, + 0x93, 0x02, 0x1b, 0x2d, 0x7b, 0xbf, 0x76, 0x2b, 0x68, 0xf8, 0x68, 0xdb, + 0x60, 0x1b, 0xb5, 0x2c, 0x04, 0xd4, 0x21, 0xfc, 0xd0, 0xcd, 0x0a, 0xbc, + 0x11, 0x94, 0xb4, 0xe0, 0x96, 0x89, 0xe1, 0x36, 0xa3, 0x49, 0xb4, 0xeb, + 0x22, 0xf2, 0x78, 0x50, 0x5d, 0x9f, 0xca, 0x1b, 0x3e, 0xec, 0x81, 0xd5, + 0x18, 0x33, 0xc8, 0x2f, 0x2e, 0xad, 0xf1, 0xc4, 0x85, 0xe9, 0x6f, 0x84, + 0xec, 0x18, 0x02, 0x19, 0xfc, 0xec, 0x00, 0xd7, 0x9b, 0x61, 0x12, 0x27, + 0xde, 0x51, 0x3c, 0x55, 0x0e, 0xb1, 0xb4, 0x51, 0x98, 0x2b, 0x7a, 0x5f, + 0xaf, 0x0f, 0xd2, 0x38, 0x83, 0x2a, 0x4a, 0x2a, 0x61, 0x39, 0x47, 0xc2, + 0xc4, 0xd5, 0x6f, 0x8e, 0x74, 0x7e, 0xe5, 0x64, 0x86, 0xe9, 0x46, 0x80, + 0x68, 0xd0, 0xb3, 0xdb, 0x2f, 0xc1, 0xd1, 0x94, 0xd6, 0x06, 0xd4, 0x1f, + 0x5e, 0x44, 0x04, 0x2a, 0xf8, 0xde, 0xfd, 0xc5, 0x97, 0xd8, 0x6a, 0xe2, + 0x35, 0x79, 0x40, 0x3a, 0x9f, 0x88, 0x8a, 0x33, 0x63, 0xb0, 0xfb, 0x4b, + 0x91, 0xa6, 0x03, 0xbe, 0x16, 0xf7, 0x09, 0x72, 0xa5, 0xf7, 0x83, 0x73, + 0x41, 0x9b, 0xc5, 0x45, 0x78, 0xc2, 0x92, 0x93, 0x87, 0xc2, 0xcd, 0x31, + 0x77, 0x7d, 0x1f, 0x4e, 0x65, 0x83, 0xc0, 0x62, 0xcf, 0x6a, 0x19, 0xfa, + 0xb9, 0x8c, 0x31, 0x62, 0x6c, 0xf9, 0xcd, 0x28, 0x92, 0x9e, 0x80, 0xdb, + 0x33, 0x4c, 0xfc, 0x7f, 0x05, 0x8a, 0x8e, 0x57, 0x5b, 0xc5, 0xae, 0x3d, + 0x5a, 0x8a, 0xed, 0xa7, 0xf4, 0xc8, 0x0e, 0xae, 0x12, 0x33, 0x83, 0xda, + 0xbc, 0x40, 0x2d, 0xd4, 0xff, 0xf9, 0xf8, 0x75, 0x34, 0xb5, 0xb5, 0x8a, + 0xd9, 0xc1, 0x1a, 0x5f, 0xe8, 0xe3, 0x34, 0xaf, 0x0b, 0xdd, 0x31, 0x74, + 0x27, 0x15, 0x6a, 0x9d, 0x8b, 0x26, 0x79, 0xc5, 0xa8, 0xae, 0xb7, 0x48, + 0x41, 0x26, 0x08, 0x75, 0xe6, 0xfc, 0x3f, 0x4a, 0x32, 0x9c, 0x76, 0x1a, + 0x71, 0xb2, 0x57, 0x74, 0x81, 0xae, 0x4c, 0x26, 0x78, 0x6b, 0xbc, 0x2a, + 0x34, 0x3d, 0x13, 0x4f, 0xe4, 0xd1, 0x9f, 0x9b, 0xb5, 0x36, 0x15, 0xf5, + 0x63, 0x7a, 0x40, 0x49, 0xc0, 0xae, 0x3a, 0xaa, 0x15, 0xdd, 0xd7, 0x8a, + 0x8e, 0xa7, 0x73, 0x2c, 0xf7, 0x31, 0x6b, 0x3a, 0xc3, 0x39, 0xcf, 0xe2, + 0xc4, 0x4a, 0x25, 0xad, 0x73, 0xb0, 0xc8, 0x18, 0xfc, 0xdb, 0xdc, 0x8a, + 0xae, 0x71, 0xcc, 0x97, 0x6f, 0xa7, 0x25, 0x46, 0xc3, 0xf3, 0x27, 0x27, + 0xcd, 0x35, 0x9d, 0xa2, 0x33, 0x93, 0xdc, 0x60, 0x07, 0x0e, 0xfe, 0x6e, + 0xac, 0x08, 0xf4, 0xb5, 0x07, 0x2a, 0xd2, 0x59, 0x3f, 0x9c, 0xc6, 0xe2, + 0x5f, 0x3b, 0xab, 0x63, 0x03, 0x4f, 0xae, 0xf6, 0x79, 0x85, 0x8f, 0xc8, + 0x0d, 0xcc, 0x04, 0x96, 0x37, 0x97, 0xaa, 0xb9, 0x20, 0x43, 0xdc, 0xa0, + 0x65, 0xef, 0x3a, 0xbb, 0xe4, 0x12, 0xd0, 0x2c, 0xbb, 0x08, 0x6f, 0x12, + 0x33, 0x59, 0xd4, 0x6d, 0x17, 0xc5, 0xe4, 0x86, 0x0e, 0x34, 0x64, 0xc7, + 0x76, 0xf1, 0x71, 0x54, 0x65, 0x00, 0xd3, 0xb8, 0x3b, 0x51, 0x0e, 0x0d, + 0x67, 0xef, 0xcb, 0x68, 0xa4, 0xca, 0x5e, 0xe4, 0x96, 0xc1, 0x53, 0xa5, + 0x5b, 0x70, 0xfc, 0x86, 0x25, 0x24, 0xbe, 0x0f, 0xbb, 0xfe, 0xae, 0x09, + 0xe8, 0x98, 0x2b, 0x21, 0xe3, 0xb5, 0x3f, 0x36, 0x85, 0x63, 0xe8, 0xa6, + 0xfd, 0x7d, 0x6c, 0xb4, 0x6c, 0x09, 0x03, 0xce, 0x20, 0xfa, 0x3a, 0xf1, + 0xb4, 0x6e, 0x90, 0x84, 0xbc, 0xa1, 0x6d, 0x53, 0x33, 0x54, 0xd6, 0x58, + 0x5f, 0xde, 0xd1, 0xc1, 0xeb, 0xc6, 0xe4, 0x65, 0x12, 0x77, 0xd1, 0xb7, + 0x01, 0xee, 0x6c, 0x37, 0x07, 0xb5, 0xa4, 0xe6, 0xe7, 0x32, 0xeb, 0x00, + 0x13, 0xd5, 0x78, 0x0a, 0x13, 0xf2, 0xc4, 0x63, 0x86, 0x42, 0xc0, 0xe1, + 0xc2, 0x93, 0x66, 0xc7, 0xae, 0xfa, 0x25, 0x23, 0x71, 0xd1, 0x39, 0x62, + 0x0c, 0x26, 0x09, 0x3a, 0xed, 0x3c, 0x49, 0xaf, 0x0f, 0xc3, 0xb8, 0xdd, + 0x6f, 0x12, 0x42, 0x9f, 0xe7, 0xda, 0xed, 0x4b, 0x2b, 0xe6, 0x61, 0x91, + 0x8f, 0x46, 0x53, 0x6e, 0x05, 0x0b, 0xf4, 0xcb, 0x18, 0x39, 0x7b, 0xd0, + 0x84, 0x44, 0xef, 0x29, 0xaa, 0x04, 0xeb, 0x52, 0x1b, 0x83, 0x8b, 0x5b, + 0x07, 0xd6, 0x2a, 0x94, 0x9c, 0xec, 0x1b, 0x67, 0x47, 0x39, 0x22, 0x9a, + 0xab, 0xce, 0xaa, 0xda, 0x2d, 0xca, 0x92, 0x7c, 0x6c, 0x5c, 0x6b, 0x69, + 0xec, 0x88, 0x1b, 0xdc, 0x46, 0x3a, 0x94, 0x0c, 0x79, 0x6b, 0xd2, 0x19, + 0xc7, 0x64, 0x3b, 0xc9, 0x93, 0x9b, 0x74, 0x49, 0x33, 0xb4, 0xe0, 0xd3, + 0x86, 0x25, 0xea, 0x8c, 0xd3, 0x51, 0x26, 0x25, 0x4a, 0xa1, 0x15, 0x8d, + 0x6e, 0x27, 0xb7, 0x64, 0x7c, 0x59, 0x71, 0xab, 0x45, 0xc4, 0x0e, 0x00, + 0xe1, 0x43, 0x03, 0xcc, 0xd3, 0x96, 0x23, 0x2e, 0xe6, 0xba, 0xb0, 0x31, + 0xfa, 0x47, 0xbb, 0xf2, 0x61, 0xc2, 0x50, 0xf6, 0x39, 0xbb, 0xbe, 0xe3, + 0x44, 0xe1, 0x63, 0xe3, 0x5f, 0xac, 0x20, 0x93, 0x19, 0x71, 0x3a, 0xb3, + 0x88, 0xc3, 0xdc, 0xc4, 0x92, 0x9d, 0xba, 0x5e, 0x1d, 0x7a, 0x2c, 0xa4, + 0x8e, 0x6a, 0x01, 0x02, 0xa0, 0x57, 0x9f, 0xe9, 0xac, 0xe6, 0x81, 0xde, + 0x7e, 0x62, 0xca, 0xd6, 0x61, 0x99, 0xa8, 0x63, 0x39, 0xbd, 0xab, 0xe7, + 0xe1, 0x88, 0xc2, 0x2d, 0x93, 0x30, 0xb1, 0x1a, 0x7b, 0x1c, 0xbb, 0xbf, + 0xd5, 0x0c, 0x3b, 0xcf, 0x36, 0x9d, 0x86, 0xa3, 0x7f, 0x30, 0x7d, 0x30, + 0x0c, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, + 0xff, 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, + 0x01, 0xfe, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, + 0x14, 0xec, 0x39, 0xea, 0x34, 0x3a, 0xcc, 0x1e, 0x04, 0x9f, 0x54, 0x8a, + 0x76, 0xcf, 0x7f, 0x29, 0x88, 0x10, 0x5d, 0x99, 0xeb, 0x30, 0x20, 0x06, + 0x03, 0x55, 0x1d, 0x25, 0x01, 0x01, 0xff, 0x04, 0x16, 0x30, 0x14, 0x06, + 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x02, 0x30, 0x1f, 0x06, 0x03, 0x55, + 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0xf5, 0xd9, 0x15, 0xbc, + 0x9f, 0xc2, 0x54, 0xfe, 0xc5, 0x53, 0x1f, 0xf7, 0xb3, 0x42, 0xc9, 0xbd, + 0xa3, 0xb1, 0x87, 0x18, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x03, 0x11, 0x03, 0x82, 0x09, 0x75, 0x00, 0x63, 0xab, + 0xc8, 0xcc, 0x21, 0xfe, 0xbc, 0x48, 0x49, 0xef, 0x5c, 0x4d, 0x08, 0x48, + 0xac, 0x8b, 0x44, 0x7e, 0x25, 0x85, 0x15, 0x06, 0x2a, 0xee, 0x7f, 0x8e, + 0xcf, 0x4c, 0x93, 0x1a, 0xb9, 0x01, 0x65, 0xc4, 0xc1, 0x07, 0xfb, 0x37, + 0x65, 0x01, 0xc3, 0xf9, 0x4f, 0x61, 0x42, 0xfa, 0xdc, 0x72, 0x46, 0xb9, + 0x5e, 0x61, 0x76, 0x0f, 0x56, 0x7a, 0xbf, 0x06, 0xa1, 0xb1, 0x14, 0x13, + 0xdd, 0x94, 0xa4, 0xf3, 0x6f, 0x6d, 0x21, 0x55, 0xc6, 0xc4, 0x58, 0x31, + 0x32, 0x55, 0x20, 0xa3, 0x8b, 0xb3, 0xb0, 0x33, 0xe1, 0xc1, 0xd0, 0x34, + 0x22, 0x37, 0x26, 0x7d, 0xdb, 0x73, 0x40, 0xcb, 0x70, 0x00, 0xa6, 0xcc, + 0xba, 0x76, 0xd7, 0x3d, 0xc8, 0x4b, 0x03, 0xe1, 0xa7, 0xcf, 0x46, 0x72, + 0x8d, 0x7e, 0xe0, 0x0e, 0x60, 0x7e, 0xdb, 0x09, 0x5a, 0x6c, 0x4e, 0x2f, + 0xc4, 0xe7, 0xe0, 0xb1, 0x17, 0x2a, 0x5c, 0x49, 0x06, 0x7d, 0x0d, 0x73, + 0xe0, 0xea, 0xd3, 0xb6, 0x20, 0x70, 0x5e, 0xf6, 0xdc, 0x0a, 0xc6, 0x90, + 0xf8, 0x63, 0x99, 0xc4, 0x5f, 0x2c, 0x87, 0xbf, 0x81, 0xf3, 0xd5, 0x08, + 0x76, 0x4f, 0xca, 0xdf, 0x4b, 0x14, 0xb6, 0xc9, 0xe8, 0x62, 0xbc, 0x15, + 0x20, 0x66, 0x13, 0x3c, 0xf5, 0x3f, 0x77, 0x18, 0xe1, 0xbd, 0x95, 0xdf, + 0xfb, 0xc0, 0x04, 0x0c, 0x81, 0x9f, 0x77, 0x51, 0x30, 0xf2, 0x02, 0x4a, + 0x08, 0xc4, 0x03, 0x74, 0x0b, 0x25, 0xd7, 0xe1, 0x64, 0x88, 0x45, 0xf2, + 0xb0, 0xe4, 0xb4, 0xf3, 0x10, 0xc5, 0x98, 0x31, 0x89, 0x4d, 0x4f, 0xe7, + 0xda, 0x1a, 0x86, 0x70, 0x2d, 0xee, 0xf6, 0xd6, 0xe9, 0xe1, 0x95, 0x74, + 0xcb, 0xb7, 0x15, 0x43, 0x82, 0x25, 0xc6, 0x5d, 0xaa, 0xa7, 0xc8, 0xcd, + 0x05, 0xa1, 0xfa, 0x85, 0x31, 0x6a, 0x6b, 0x06, 0xcd, 0x3b, 0xb1, 0x82, + 0x09, 0xf7, 0xc0, 0x8e, 0x9f, 0xfe, 0xa2, 0xe4, 0xbc, 0x67, 0x79, 0x37, + 0xdc, 0x6f, 0xc2, 0xd6, 0x40, 0xc5, 0x35, 0x6a, 0x18, 0x33, 0x5e, 0x2b, + 0x43, 0x9a, 0x21, 0x89, 0x4c, 0xe4, 0x2d, 0x4c, 0xce, 0xbd, 0x5b, 0x90, + 0xa8, 0x2b, 0x56, 0x67, 0xd0, 0x55, 0x96, 0xf6, 0xea, 0x41, 0x41, 0x47, + 0x3a, 0x13, 0xfe, 0x42, 0xea, 0xb1, 0xf4, 0x57, 0x87, 0x92, 0x82, 0x80, + 0x87, 0xce, 0x43, 0xca, 0xcf, 0x8f, 0x8b, 0x63, 0xf1, 0xf0, 0x23, 0x29, + 0x66, 0xc5, 0xc5, 0x50, 0x70, 0xfd, 0x08, 0x8d, 0xab, 0xd5, 0xe1, 0xaa, + 0x1e, 0xbb, 0x9c, 0x08, 0x2c, 0x6d, 0x29, 0xaf, 0xe6, 0x03, 0x8f, 0x77, + 0x8c, 0xd8, 0x6c, 0x78, 0xf1, 0x3f, 0x6e, 0xbd, 0x28, 0xbf, 0x98, 0x84, + 0xac, 0xac, 0xd8, 0x6f, 0x9d, 0x85, 0x11, 0xff, 0x6b, 0xde, 0x0d, 0x16, + 0x78, 0x47, 0x3e, 0xae, 0x1a, 0x8c, 0xc4, 0x0e, 0x1e, 0xac, 0xd8, 0xad, + 0x51, 0x7e, 0xf3, 0x9c, 0xdf, 0xfd, 0x94, 0xb9, 0xe6, 0x86, 0xce, 0x90, + 0xe2, 0xe8, 0x6f, 0x40, 0xb3, 0x16, 0xf7, 0x34, 0x64, 0xc3, 0xb9, 0x9f, + 0xa4, 0xaf, 0x00, 0xfb, 0x30, 0xf4, 0x70, 0x37, 0x42, 0xc8, 0x45, 0x1b, + 0xba, 0x5d, 0x04, 0xae, 0xf7, 0x53, 0x7a, 0x8c, 0x4f, 0xc1, 0xa6, 0x88, + 0xc3, 0xf0, 0x11, 0x7f, 0x1f, 0x56, 0x95, 0xe4, 0xef, 0x3e, 0x9d, 0x82, + 0x11, 0xdb, 0xdf, 0xda, 0x2a, 0xdb, 0x48, 0x4e, 0x49, 0xa9, 0x95, 0x04, + 0xfd, 0x0f, 0x6b, 0x82, 0x89, 0x42, 0x55, 0x30, 0xd7, 0xc9, 0x6e, 0xb0, + 0xf2, 0x31, 0x27, 0x1d, 0xb3, 0xb6, 0x9d, 0x50, 0x37, 0xb3, 0x53, 0x6e, + 0xfc, 0xe4, 0x45, 0x13, 0xca, 0xb4, 0xc7, 0x25, 0xa9, 0x54, 0xba, 0xf3, + 0xda, 0xf6, 0x13, 0xf5, 0x7c, 0xa0, 0x0e, 0x07, 0xa9, 0x0f, 0xd1, 0x81, + 0xdb, 0xba, 0xf4, 0x3e, 0xae, 0xc9, 0x6e, 0x15, 0x03, 0x03, 0x25, 0x89, + 0x73, 0xc7, 0x95, 0x18, 0x6c, 0xf8, 0x3b, 0x9b, 0x2b, 0x41, 0xbf, 0xfa, + 0x41, 0xcd, 0x04, 0x68, 0x80, 0xaf, 0x72, 0x1e, 0x26, 0x5b, 0x5f, 0x9d, + 0x4c, 0x71, 0xa7, 0x89, 0xbc, 0x8c, 0x20, 0xdb, 0xb6, 0xdf, 0x17, 0x8b, + 0x97, 0xca, 0xaa, 0xe9, 0xe0, 0xb5, 0xcf, 0x2b, 0xb3, 0x6a, 0x5b, 0xb8, + 0xfe, 0xc6, 0xbd, 0xb3, 0x5e, 0xa1, 0x21, 0xac, 0xe3, 0x7d, 0x53, 0xff, + 0x56, 0x5f, 0xad, 0xee, 0x86, 0x60, 0x86, 0x92, 0xa1, 0xa7, 0xd7, 0x87, + 0x71, 0x12, 0x62, 0x25, 0xa5, 0xa0, 0x4f, 0x3a, 0xe6, 0x76, 0x98, 0x2f, + 0x05, 0x10, 0x1e, 0x0b, 0x6c, 0xb6, 0x47, 0x83, 0xc4, 0x9b, 0x1d, 0xf0, + 0xee, 0x1e, 0x27, 0x12, 0xd8, 0x4a, 0xb2, 0x50, 0xeb, 0xa4, 0xdb, 0xa4, + 0xcc, 0xe0, 0x9c, 0xe1, 0x1f, 0xd8, 0x32, 0x14, 0x1d, 0x36, 0x54, 0xd9, + 0x61, 0x6b, 0xa7, 0xc6, 0x00, 0x5c, 0xb1, 0x60, 0xa3, 0x05, 0xc9, 0xfa, + 0x5f, 0x70, 0x13, 0xb9, 0x52, 0x2b, 0x3d, 0x26, 0x1e, 0xa3, 0xe8, 0x13, + 0x37, 0xf0, 0x76, 0x3e, 0x5a, 0xd3, 0x2e, 0xd0, 0xbf, 0x73, 0x46, 0xdb, + 0x75, 0x08, 0xee, 0xc9, 0x32, 0xad, 0xab, 0x01, 0x7e, 0xcc, 0x0c, 0x43, + 0x39, 0x58, 0x08, 0xa4, 0x83, 0xbb, 0x8d, 0xf0, 0xe5, 0x21, 0xa0, 0x57, + 0xea, 0xda, 0x45, 0xc3, 0xaa, 0xec, 0x1e, 0x89, 0xaa, 0x31, 0x47, 0x74, + 0x4d, 0xe8, 0xfb, 0x99, 0x57, 0x3e, 0xea, 0x71, 0xbb, 0x22, 0x94, 0xc2, + 0x3b, 0x57, 0x49, 0x05, 0xb0, 0xea, 0xf5, 0xee, 0x0b, 0xed, 0x4f, 0xef, + 0xb6, 0x9a, 0xdd, 0x65, 0x54, 0xb1, 0x22, 0x14, 0x83, 0xfc, 0xea, 0x27, + 0xe9, 0x04, 0xb5, 0x28, 0xf1, 0xd8, 0xba, 0x98, 0xcb, 0xe9, 0xd9, 0x4b, + 0xaf, 0xd8, 0x74, 0xa1, 0xc8, 0xc7, 0xc7, 0x11, 0x6c, 0x13, 0x7a, 0x2b, + 0x3e, 0x33, 0x30, 0x45, 0x62, 0xc1, 0x04, 0xa9, 0xb8, 0x22, 0x19, 0x2b, + 0xb5, 0xd6, 0x0d, 0x99, 0x9e, 0x08, 0x61, 0x15, 0xf7, 0xf2, 0x5d, 0xd7, + 0x4f, 0xe3, 0xb6, 0xbf, 0x4e, 0xd4, 0x56, 0xae, 0x13, 0xed, 0xdf, 0x7e, + 0x9f, 0x90, 0x08, 0x47, 0x90, 0xd4, 0xfd, 0x72, 0xb5, 0x44, 0xe0, 0x0c, + 0xb2, 0x79, 0x12, 0xe6, 0x5a, 0xa3, 0xde, 0x5e, 0x15, 0x39, 0xa3, 0xad, + 0x0e, 0x47, 0x2c, 0xce, 0x44, 0x82, 0xee, 0x56, 0xcb, 0x50, 0x84, 0xf0, + 0xc1, 0x3c, 0xf4, 0x0b, 0x77, 0x5d, 0x55, 0x80, 0xb2, 0x60, 0x80, 0x5e, + 0x4a, 0xbb, 0x9f, 0x3c, 0x9f, 0x5e, 0x31, 0xbd, 0x8c, 0xec, 0x09, 0xc5, + 0xf8, 0x59, 0x43, 0x0d, 0x5d, 0x73, 0xa1, 0x91, 0xfe, 0x31, 0x6e, 0xf5, + 0x78, 0xd2, 0x92, 0xe2, 0x8b, 0x1a, 0xb2, 0x3d, 0x06, 0xd9, 0x6e, 0x19, + 0x7d, 0xef, 0xde, 0x21, 0x5a, 0x85, 0xe7, 0x98, 0xa1, 0x8f, 0x00, 0xd3, + 0x48, 0x80, 0x15, 0x2b, 0x12, 0x03, 0x3d, 0x4d, 0xf5, 0xc5, 0xb3, 0x28, + 0x85, 0x06, 0x87, 0xef, 0xab, 0xef, 0x80, 0x43, 0xea, 0x1e, 0x38, 0xa2, + 0xae, 0x11, 0x05, 0x7d, 0x88, 0x03, 0xd1, 0x5c, 0xd2, 0x77, 0x0f, 0x11, + 0xf9, 0x02, 0xdc, 0xb5, 0x9f, 0x04, 0x0f, 0xb5, 0xa3, 0xc3, 0x9f, 0x9b, + 0xd6, 0x9a, 0xb3, 0xe2, 0xb8, 0x30, 0x19, 0x8e, 0xcc, 0x42, 0x14, 0x1e, + 0x6e, 0xef, 0x56, 0x4a, 0x3c, 0x82, 0x8c, 0xcb, 0x22, 0x6f, 0x4e, 0x5c, + 0x7e, 0xd5, 0x2e, 0x0c, 0x5f, 0x88, 0x45, 0x4a, 0x3d, 0x38, 0x7f, 0x47, + 0x58, 0x7c, 0x7d, 0xd3, 0x60, 0xec, 0x9e, 0xf7, 0x05, 0xd0, 0xee, 0x9c, + 0x8f, 0x27, 0xb9, 0xab, 0x2e, 0x77, 0x18, 0x22, 0xcf, 0xaa, 0x37, 0xda, + 0xbd, 0x0d, 0xcc, 0xbc, 0xac, 0xa7, 0x42, 0x53, 0x42, 0x82, 0x7c, 0x81, + 0xda, 0x31, 0x67, 0x8f, 0xcc, 0xa2, 0x12, 0x92, 0x1e, 0xee, 0x1a, 0x0f, + 0x43, 0xf3, 0x32, 0xe9, 0x13, 0xcb, 0x7e, 0x26, 0x3f, 0x4e, 0x2c, 0x4c, + 0x04, 0x3a, 0x3e, 0x33, 0xf8, 0xa4, 0x9b, 0x51, 0xbe, 0x50, 0x7b, 0x06, + 0x82, 0x78, 0xbf, 0xc1, 0xb2, 0xd9, 0x8d, 0xb1, 0x3a, 0xcd, 0x98, 0xe0, + 0xee, 0xec, 0x70, 0x8b, 0x80, 0xd3, 0x65, 0x7a, 0x5e, 0x8e, 0x6a, 0xa6, + 0x3b, 0x6b, 0x9e, 0xa8, 0x25, 0x1d, 0x77, 0x99, 0xca, 0xb1, 0x25, 0xcb, + 0xaf, 0x60, 0xbd, 0xbd, 0xfe, 0x1b, 0x56, 0x36, 0xbf, 0xb1, 0x89, 0xa1, + 0xb6, 0x91, 0x60, 0x16, 0xb9, 0x1e, 0xca, 0x36, 0x69, 0xc9, 0xc4, 0x9e, + 0x65, 0x3c, 0xaf, 0x1d, 0x46, 0x5d, 0x78, 0x2f, 0x39, 0x75, 0xa9, 0xdc, + 0x25, 0x00, 0xe7, 0xcf, 0xb0, 0xde, 0xf7, 0x76, 0x8b, 0x4a, 0xca, 0xba, + 0xfd, 0xe3, 0x95, 0xdc, 0x24, 0x1a, 0x55, 0x6d, 0x77, 0x1a, 0x46, 0x2b, + 0xea, 0x33, 0xf9, 0xe8, 0x39, 0x2f, 0xce, 0x5d, 0x4f, 0x90, 0xa0, 0xa9, + 0x3d, 0x3a, 0x72, 0x68, 0x66, 0xd8, 0x51, 0xd0, 0x63, 0x20, 0x12, 0xde, + 0x62, 0xcc, 0x24, 0x86, 0xf3, 0xc8, 0x3c, 0x16, 0xe9, 0x30, 0xf7, 0x69, + 0xae, 0xe3, 0xb6, 0x71, 0xa4, 0xcd, 0x94, 0x87, 0x93, 0x9c, 0x72, 0x38, + 0x90, 0x22, 0x58, 0x9b, 0x2d, 0x53, 0x71, 0x04, 0xea, 0x47, 0xeb, 0x60, + 0x68, 0x4a, 0x25, 0xd5, 0x78, 0x1b, 0xce, 0x8c, 0x48, 0xb1, 0x18, 0x24, + 0xe2, 0x87, 0xec, 0x59, 0x95, 0xbb, 0xf3, 0x41, 0x1a, 0x3a, 0x38, 0xd6, + 0x09, 0x0d, 0xf5, 0x44, 0xa7, 0xb5, 0x8c, 0xec, 0x5b, 0x62, 0xb7, 0x22, + 0xae, 0xf3, 0xb6, 0x88, 0x20, 0x34, 0x55, 0x33, 0x64, 0x27, 0xb5, 0xf9, + 0x06, 0xe4, 0x09, 0xa7, 0xcc, 0xc7, 0x98, 0x12, 0x7d, 0xc1, 0xb9, 0x1a, + 0xd8, 0xaf, 0xe2, 0x3b, 0x76, 0xbf, 0x83, 0x3f, 0x0e, 0x2f, 0xe2, 0x94, + 0x27, 0xb6, 0x86, 0x68, 0xa6, 0x45, 0x5a, 0x9d, 0x2e, 0xc1, 0x25, 0x25, + 0xc1, 0x09, 0x3b, 0x5a, 0x47, 0xc1, 0x85, 0x4f, 0x5d, 0xb9, 0xe8, 0xe8, + 0x01, 0x29, 0xeb, 0xcf, 0x46, 0x57, 0x61, 0xbd, 0x29, 0x06, 0xa5, 0xaf, + 0x89, 0x05, 0x0d, 0xdc, 0x3f, 0x04, 0x14, 0x6f, 0x12, 0x87, 0xf3, 0xe7, + 0x06, 0xa7, 0x55, 0x4a, 0xe9, 0x58, 0x65, 0xc1, 0x25, 0x1a, 0xa7, 0x98, + 0xbe, 0xdd, 0x8f, 0x34, 0x97, 0x44, 0x96, 0x4d, 0x29, 0x63, 0xb3, 0x2d, + 0x65, 0xee, 0xcd, 0x5d, 0x2d, 0x1d, 0x44, 0x56, 0x3b, 0xdd, 0x24, 0x5b, + 0x2f, 0x22, 0x50, 0xaf, 0xf4, 0x91, 0xac, 0x47, 0x20, 0x76, 0x00, 0x9a, + 0xa7, 0x42, 0x6d, 0x9d, 0x6b, 0xae, 0xf1, 0x00, 0x75, 0xd8, 0x95, 0x10, + 0x21, 0x71, 0x52, 0x23, 0x4a, 0x57, 0xca, 0x2d, 0xb6, 0xe8, 0xfd, 0x70, + 0x16, 0x8c, 0x90, 0xde, 0x83, 0xc6, 0x2f, 0xc0, 0x6f, 0xa4, 0xf8, 0xd7, + 0x4e, 0xc5, 0x14, 0xdc, 0x99, 0x41, 0xd7, 0x3d, 0xa6, 0xd6, 0xa0, 0x69, + 0x47, 0x3a, 0x2a, 0x08, 0xf6, 0xf4, 0x09, 0x3e, 0x4a, 0x98, 0x98, 0xbe, + 0x64, 0x29, 0x47, 0xda, 0x3c, 0x95, 0xed, 0xe3, 0xfd, 0xec, 0x74, 0x30, + 0xca, 0xa9, 0xbc, 0xc5, 0xac, 0x2d, 0x00, 0x03, 0xe8, 0x02, 0x87, 0xcc, + 0xc6, 0x16, 0x22, 0xd1, 0x9c, 0xce, 0xc2, 0xcf, 0x7d, 0xcc, 0x29, 0x5b, + 0xf6, 0xde, 0xb5, 0xaa, 0xa0, 0x3e, 0x59, 0x98, 0x3c, 0xed, 0x8b, 0xfb, + 0x39, 0xa4, 0xfb, 0x87, 0x1c, 0xd4, 0x25, 0x05, 0xf0, 0x21, 0x72, 0x4c, + 0xb3, 0x11, 0xdb, 0x87, 0x86, 0x4b, 0x08, 0x51, 0x4f, 0xc7, 0x6d, 0x39, + 0x0f, 0x79, 0x93, 0xdd, 0x6c, 0x30, 0x5e, 0xa9, 0xb1, 0x7f, 0xb7, 0x71, + 0xb0, 0xa8, 0x57, 0x1e, 0xab, 0xc3, 0x2d, 0x6e, 0x06, 0x7c, 0x23, 0x4a, + 0x6e, 0xbe, 0xe7, 0xb1, 0x94, 0x60, 0x7e, 0x77, 0x35, 0xbf, 0xb2, 0xe7, + 0xe8, 0x37, 0xbd, 0x3f, 0x36, 0x77, 0x0f, 0xc7, 0x99, 0x0d, 0x7b, 0xff, + 0x5b, 0xc9, 0x88, 0xb4, 0x05, 0x79, 0xa1, 0x50, 0xf5, 0x9a, 0x10, 0x59, + 0xac, 0x7a, 0x1d, 0x24, 0x3d, 0xc0, 0x3b, 0xef, 0x46, 0xa6, 0xc1, 0x0c, + 0x4d, 0x56, 0xdf, 0x14, 0x6f, 0x17, 0xb7, 0x0b, 0xeb, 0x26, 0x59, 0xfe, + 0xb7, 0x70, 0x58, 0xfa, 0x5b, 0xbe, 0xc3, 0x06, 0x7e, 0xf5, 0x7b, 0xa5, + 0xd0, 0xf0, 0x09, 0xcd, 0x29, 0x0b, 0xa5, 0xfb, 0xd3, 0x84, 0x03, 0x3c, + 0x66, 0x84, 0xa0, 0x2f, 0xd4, 0x60, 0x56, 0x4d, 0xbf, 0x3f, 0x58, 0xca, + 0x46, 0x1c, 0x73, 0x93, 0x1d, 0x5c, 0x0d, 0xc4, 0xbc, 0xc8, 0x75, 0x1c, + 0x88, 0x1e, 0xf7, 0x83, 0xd6, 0x7a, 0x44, 0x18, 0x2c, 0x49, 0xe8, 0x7a, + 0x42, 0x2a, 0xaa, 0xea, 0xf8, 0x12, 0x10, 0x6d, 0xa5, 0xea, 0x56, 0x7e, + 0x4f, 0x0b, 0x36, 0x1c, 0xd1, 0xd2, 0xfc, 0xb1, 0x79, 0x23, 0xa4, 0x59, + 0x54, 0x3a, 0xb0, 0xa9, 0x8a, 0x75, 0x3e, 0x01, 0x5b, 0xe5, 0x3f, 0x6d, + 0x0a, 0x62, 0x7a, 0x2e, 0x39, 0xcc, 0x80, 0x70, 0xc2, 0x80, 0x80, 0x3c, + 0x9e, 0x07, 0x62, 0xc9, 0x76, 0x8d, 0x50, 0xf2, 0xb7, 0xc3, 0x3b, 0xc8, + 0x69, 0x6d, 0x51, 0x00, 0x1e, 0xca, 0xb3, 0x39, 0xd7, 0x76, 0x1e, 0x0b, + 0x47, 0x28, 0xa0, 0xcd, 0xd5, 0xed, 0xf4, 0x93, 0x0d, 0xe8, 0x8b, 0xa8, + 0xa5, 0x33, 0x54, 0xc2, 0x6e, 0x1c, 0xc3, 0x5a, 0x27, 0x09, 0x6a, 0x12, + 0x7d, 0x1e, 0x16, 0x89, 0x45, 0x47, 0x2e, 0x5d, 0x28, 0xe0, 0xe7, 0x29, + 0xb2, 0x50, 0x49, 0x4a, 0xd8, 0xbb, 0x1e, 0x63, 0xe5, 0xd7, 0x42, 0xbc, + 0x60, 0x6d, 0x0a, 0x8d, 0x07, 0xf1, 0xe2, 0xbb, 0x06, 0x1e, 0x8b, 0xcf, + 0x13, 0x1e, 0x6f, 0xc9, 0x9e, 0xf9, 0xca, 0x65, 0xac, 0x58, 0x5a, 0xee, + 0x27, 0x5f, 0x92, 0x6b, 0x11, 0x4e, 0x5c, 0x28, 0x43, 0x28, 0xd7, 0x9d, + 0x8d, 0xfa, 0xf5, 0xf5, 0xfc, 0xd7, 0x74, 0xd8, 0xf8, 0xb7, 0xa5, 0xa9, + 0xb2, 0x31, 0x48, 0xcb, 0x07, 0xb7, 0x75, 0x69, 0x37, 0x52, 0x59, 0xad, + 0xdd, 0xdc, 0x6d, 0xb9, 0xf5, 0x8a, 0x89, 0x31, 0x52, 0x81, 0x44, 0xa7, + 0x3a, 0xe6, 0x5e, 0x86, 0x39, 0x17, 0xbc, 0x25, 0x9b, 0xb4, 0x07, 0x4b, + 0xef, 0x42, 0x16, 0x7f, 0xe4, 0x80, 0xa5, 0xad, 0x21, 0x9b, 0xe4, 0x85, + 0xe0, 0x7b, 0x52, 0x18, 0x37, 0x34, 0x82, 0x9a, 0x8a, 0xc7, 0x9f, 0xfc, + 0x35, 0x1b, 0xb5, 0x7d, 0x60, 0xcc, 0x98, 0xb2, 0x67, 0x48, 0x09, 0xd2, + 0x6e, 0x94, 0x3c, 0x56, 0x99, 0x59, 0xe4, 0xb7, 0x24, 0x6b, 0x2c, 0xf0, + 0x3d, 0xda, 0x01, 0x48, 0xc2, 0xf4, 0x9a, 0x58, 0xfa, 0x07, 0xd4, 0x41, + 0xa0, 0x61, 0x9f, 0x55, 0xd1, 0x02, 0x1a, 0x3a, 0x4e, 0x7c, 0x58, 0x06, + 0x4b, 0x66, 0xd2, 0x54, 0xf3, 0xb8, 0x96, 0xd5, 0x92, 0xe1, 0x95, 0x65, + 0xfc, 0x60, 0x74, 0x25, 0x22, 0x08, 0x64, 0x72, 0xf7, 0x1f, 0x8b, 0xc4, + 0x81, 0xe1, 0x63, 0x34, 0xe6, 0x32, 0x4e, 0x3f, 0x65, 0xc0, 0x77, 0xb8, + 0x7a, 0x7c, 0x70, 0x29, 0xb1, 0xc8, 0xd0, 0xcc, 0x3d, 0xbf, 0x99, 0x4d, + 0xb9, 0x48, 0x3a, 0x36, 0x8a, 0x64, 0xd7, 0x70, 0x39, 0xc4, 0xad, 0x1e, + 0x9b, 0x1b, 0xd5, 0xac, 0x13, 0x5a, 0xfd, 0xdd, 0x31, 0xa6, 0xa8, 0x98, + 0x09, 0x40, 0x89, 0xec, 0x82, 0x35, 0x55, 0x90, 0x92, 0x96, 0x5c, 0xf2, + 0xa9, 0xef, 0x7a, 0x69, 0x3f, 0xfd, 0x12, 0x2c, 0x80, 0x44, 0xfa, 0x62, + 0xec, 0xc6, 0xc5, 0x2d, 0xbf, 0x1b, 0xf2, 0x17, 0x88, 0xd6, 0x63, 0xbe, + 0xc1, 0x84, 0x9d, 0x11, 0x24, 0x1e, 0x21, 0xc8, 0x6a, 0xee, 0xfe, 0x75, + 0xbc, 0x14, 0x8b, 0xb1, 0x47, 0xf2, 0x07, 0x49, 0x57, 0xb2, 0x65, 0x38, + 0xc8, 0xe3, 0x8c, 0xc5, 0x9c, 0xa3, 0x2c, 0x77, 0x2a, 0xf7, 0x46, 0xf6, + 0x2b, 0x56, 0xb9, 0xcf, 0x19, 0x58, 0x30, 0xe4, 0x55, 0x81, 0x85, 0xd6, + 0xb2, 0xce, 0x10, 0xe0, 0xb8, 0xa6, 0xc1, 0xaa, 0x31, 0x37, 0xed, 0x4c, + 0x27, 0x21, 0xd4, 0xc1, 0x70, 0xee, 0x72, 0x02, 0x41, 0x7d, 0xd1, 0xa5, + 0xbc, 0x0e, 0x01, 0x6f, 0xca, 0x23, 0x24, 0xa4, 0x09, 0x2c, 0x80, 0x3b, + 0x58, 0x49, 0x8e, 0x1f, 0xc7, 0xf0, 0xb2, 0x1a, 0xd5, 0xda, 0xfe, 0x40, + 0x41, 0x2b, 0xbc, 0x6c, 0xfb, 0xb6, 0xdf, 0xd1, 0xed, 0x25, 0xc8, 0x13, + 0xcc, 0x79, 0x29, 0xd7, 0x94, 0x79, 0x5b, 0x85, 0x7d, 0xbc, 0xab, 0x6c, + 0xd6, 0xf6, 0xda, 0xe3, 0x0c, 0xe4, 0xf8, 0xb9, 0xe4, 0x32, 0x96, 0xd6, + 0x28, 0xd8, 0x9c, 0xf4, 0x3f, 0x55, 0x3c, 0x37, 0x7d, 0xda, 0x01, 0x6d, + 0x84, 0x0d, 0x6d, 0xd3, 0x91, 0xe8, 0x77, 0xba, 0x94, 0xb0, 0x47, 0xe4, + 0x9b, 0x76, 0xca, 0x5b, 0x86, 0xb5, 0x34, 0xa8, 0x84, 0x00, 0xe1, 0x6f, + 0x8a, 0x3f, 0xc6, 0x50, 0xc1, 0x6b, 0x77, 0x28, 0x6f, 0xef, 0x61, 0xb5, + 0x74, 0x43, 0x48, 0xc4, 0xb5, 0x56, 0x0c, 0x1c, 0x04, 0xed, 0x5b, 0xa9, + 0xdb, 0x66, 0x62, 0x26, 0x7b, 0x56, 0x8b, 0x28, 0x7e, 0xf0, 0x5d, 0x28, + 0x32, 0xfa, 0x12, 0xd9, 0xaf, 0x93, 0x64, 0x91, 0xec, 0x42, 0xc5, 0x2f, + 0xce, 0x6a, 0xc5, 0x1a, 0xb8, 0x95, 0x74, 0xa8, 0x6e, 0x94, 0xce, 0x71, + 0x40, 0x1d, 0x51, 0xce, 0xf5, 0xda, 0x3b, 0x1d, 0xd5, 0xa5, 0xa0, 0xea, + 0x0b, 0x73, 0x29, 0xf4, 0x95, 0xe5, 0x40, 0xa4, 0x32, 0x49, 0x7b, 0xa8, + 0x9c, 0xbf, 0xd4, 0x26, 0xb0, 0xb7, 0x6c, 0x9e, 0x57, 0x8e, 0x54, 0xce, + 0x85, 0x4b, 0x75, 0x6f, 0x2a, 0x88, 0x7e, 0x29, 0x75, 0xd3, 0x38, 0x27, + 0x7e, 0xa2, 0xb2, 0x9f, 0x70, 0xd9, 0x2b, 0x13, 0x6a, 0x44, 0x66, 0x3d, + 0xa2, 0xb0, 0x00, 0xe9, 0x24, 0xcc, 0x0d, 0x2d, 0x2e, 0x35, 0x4b, 0x50, + 0x52, 0x66, 0x73, 0x88, 0x8c, 0x8f, 0xa7, 0xae, 0xb2, 0xbf, 0xc0, 0xc1, + 0xd1, 0xd6, 0xdd, 0xec, 0x0c, 0x1a, 0x33, 0x36, 0x39, 0x48, 0x4c, 0x65, + 0x69, 0x72, 0x7c, 0x89, 0x8a, 0x94, 0x95, 0xa1, 0xa8, 0xad, 0xb0, 0xb3, + 0xc5, 0xd9, 0xe7, 0x22, 0x27, 0x39, 0x63, 0x6f, 0x70, 0x74, 0x7b, 0x8b, + 0x8f, 0xa0, 0xab, 0xbc, 0xbf, 0xc1, 0xce, 0xdf, 0xe8, 0xe9, 0xeb, 0xef, + 0xfd, 0x0d, 0x1a, 0x35, 0x36, 0x90, 0x95, 0xa3, 0xf4, 0xfa, 0x00, 0x00, + 0x00, 0x00, 0x16, 0x2d, 0x43, 0x4c, 0x30, 0x82, 0x10, 0x38, 0x30, 0x82, + 0x06, 0xae, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x01, 0x03, 0x30, 0x0b, + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x30, + 0x31, 0x31, 0x2f, 0x30, 0x2d, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x26, + 0x44, 0x4d, 0x54, 0x46, 0x20, 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, 0x6d, + 0x20, 0x6d, 0x6c, 0x64, 0x73, 0x61, 0x34, 0x34, 0x20, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x20, 0x63, 0x65, + 0x72, 0x74, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x35, 0x30, 0x34, 0x32, 0x33, + 0x30, 0x38, 0x32, 0x39, 0x35, 0x31, 0x5a, 0x17, 0x0d, 0x33, 0x35, 0x30, + 0x34, 0x32, 0x31, 0x30, 0x38, 0x32, 0x39, 0x35, 0x31, 0x5a, 0x30, 0x2e, + 0x31, 0x2c, 0x30, 0x2a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x23, 0x44, + 0x4d, 0x54, 0x46, 0x20, 0x6c, 0x69, 0x62, 0x73, 0x70, 0x64, 0x6d, 0x20, + 0x6d, 0x6c, 0x64, 0x73, 0x61, 0x34, 0x34, 0x20, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x64, 0x65, 0x72, 0x20, 0x63, 0x65, 0x72, 0x74, 0x30, 0x82, + 0x05, 0x32, 0x30, 0x0b, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, + 0x04, 0x03, 0x11, 0x03, 0x82, 0x05, 0x21, 0x00, 0x55, 0x33, 0xe2, 0xf7, + 0xb5, 0xef, 0x3b, 0x71, 0xe8, 0xf5, 0xbd, 0x71, 0x96, 0xac, 0x5e, 0x29, + 0x34, 0x2d, 0xe1, 0xe6, 0xc3, 0x5d, 0xa2, 0xc3, 0x81, 0x32, 0x57, 0x85, + 0xe1, 0xa1, 0xb8, 0x8e, 0x38, 0xcf, 0x69, 0xff, 0x1e, 0x38, 0x9f, 0xe4, + 0xe4, 0xf5, 0xab, 0x2e, 0xb6, 0xbd, 0x80, 0x63, 0x27, 0x19, 0xd0, 0xca, + 0xdb, 0xc6, 0x89, 0x9e, 0x2b, 0xa6, 0xf1, 0x5b, 0x2a, 0xd9, 0x1d, 0xe0, + 0xaa, 0xfb, 0x8d, 0x0a, 0xe2, 0x10, 0x58, 0xc6, 0xf8, 0x62, 0x00, 0x71, + 0x87, 0x42, 0x6f, 0x1e, 0x84, 0x00, 0x37, 0x62, 0x84, 0x82, 0x80, 0x57, + 0x79, 0x18, 0x9d, 0xf6, 0xe4, 0x88, 0x52, 0xa5, 0x97, 0xaa, 0x8a, 0xe2, + 0xb3, 0x3c, 0x71, 0xe2, 0x2e, 0x25, 0x82, 0xea, 0x67, 0xbc, 0x99, 0x4a, + 0xb6, 0xc9, 0xb7, 0x66, 0x4b, 0x81, 0xb8, 0x37, 0xa5, 0x66, 0xba, 0xa2, + 0x0e, 0x96, 0x77, 0xf7, 0x23, 0xa9, 0xab, 0x2c, 0xa5, 0x7f, 0x1f, 0x97, + 0x3c, 0xd8, 0x98, 0xc1, 0xcc, 0xca, 0x32, 0x78, 0x21, 0xe3, 0x4a, 0x3e, + 0xfd, 0x82, 0x97, 0x76, 0x6e, 0xb0, 0xc8, 0xef, 0xf1, 0x18, 0x3d, 0x3d, + 0x92, 0x63, 0x1b, 0x62, 0xfe, 0xab, 0x16, 0x1a, 0x75, 0xb2, 0x2f, 0x17, + 0x64, 0xd1, 0x47, 0x8a, 0x46, 0x50, 0x21, 0xba, 0xfb, 0xc5, 0x9d, 0xe9, + 0xf9, 0xcb, 0x95, 0x75, 0xa7, 0x7a, 0x46, 0xc6, 0x6d, 0x9c, 0x11, 0x07, + 0xd0, 0x02, 0x7c, 0x5d, 0x19, 0x2b, 0x2e, 0x3f, 0x39, 0x02, 0x7c, 0x09, + 0xbc, 0xd8, 0x57, 0x0c, 0x9d, 0x78, 0xc6, 0xd5, 0x64, 0xf4, 0x81, 0xdc, + 0x82, 0x65, 0x66, 0x77, 0xa2, 0x77, 0x26, 0xef, 0x47, 0x0d, 0x95, 0x48, + 0x51, 0x5a, 0x13, 0x5f, 0x2e, 0xac, 0x1f, 0x89, 0x24, 0x75, 0x0c, 0xef, + 0xf8, 0x48, 0xfd, 0x92, 0xfe, 0xc9, 0x7f, 0x50, 0xbe, 0x1d, 0x29, 0xb7, + 0x6e, 0xf1, 0x8e, 0x42, 0x34, 0xc5, 0x6a, 0xe8, 0x77, 0x6c, 0x40, 0x2c, + 0x70, 0xa0, 0x9c, 0x82, 0x0e, 0x65, 0x24, 0x79, 0x7f, 0x77, 0x3c, 0x36, + 0x88, 0x51, 0x3f, 0x18, 0xc6, 0x77, 0x3c, 0xb1, 0xbb, 0xdd, 0x2d, 0xbb, + 0x0e, 0x4e, 0x17, 0xf5, 0x1f, 0x90, 0x2d, 0x9b, 0x8b, 0x63, 0xd5, 0x24, + 0x9f, 0x95, 0xed, 0xf9, 0xb1, 0xe4, 0xb9, 0x2c, 0xf1, 0xf3, 0x3a, 0xc8, + 0xa6, 0x28, 0xf7, 0x42, 0x3d, 0xd5, 0x8e, 0x72, 0xce, 0x94, 0x4a, 0x5b, + 0x34, 0xe9, 0x51, 0x8b, 0x28, 0x74, 0x49, 0x55, 0xd2, 0x1d, 0xa9, 0xa3, + 0xa7, 0x80, 0xd4, 0x45, 0x75, 0xec, 0x06, 0xac, 0x6f, 0xa7, 0xab, 0x27, + 0x5f, 0x2d, 0xe3, 0xf8, 0x15, 0xea, 0x21, 0x16, 0xa7, 0xa9, 0x26, 0x0b, + 0x61, 0xb4, 0xc3, 0x11, 0x4c, 0xbb, 0x71, 0x14, 0x06, 0xc4, 0xb1, 0x94, + 0xd8, 0xe9, 0xfa, 0x8a, 0xef, 0x60, 0x55, 0x1b, 0x47, 0x6d, 0xff, 0x58, + 0x98, 0x68, 0x9f, 0xab, 0x48, 0xc9, 0x9d, 0xa0, 0xdb, 0xc7, 0x89, 0xf9, + 0x5a, 0x8c, 0xd9, 0x48, 0x3b, 0x0f, 0x4b, 0xe3, 0xc3, 0x6e, 0x82, 0x7c, + 0xf1, 0x98, 0xce, 0xf7, 0x8a, 0x00, 0xea, 0xb7, 0x8f, 0x37, 0x8d, 0x59, + 0x6a, 0xf1, 0xe2, 0x5e, 0xca, 0x09, 0xef, 0x1d, 0x09, 0x98, 0xa1, 0x7a, + 0x87, 0x21, 0xab, 0x13, 0xad, 0x62, 0x17, 0x17, 0x65, 0xfe, 0x4b, 0x54, + 0x29, 0x20, 0xe2, 0x45, 0xaa, 0x86, 0x20, 0xa2, 0x30, 0xed, 0x84, 0x37, + 0xd4, 0xc5, 0x3a, 0x8a, 0x25, 0x83, 0x97, 0xab, 0x5f, 0x49, 0x5e, 0xfb, + 0xbf, 0xf3, 0xe9, 0x4f, 0x1b, 0xe4, 0x9b, 0xcf, 0xa7, 0x9d, 0xf2, 0x1c, + 0xf9, 0x97, 0xc4, 0x0f, 0xbc, 0xa1, 0xbc, 0x0e, 0x23, 0x42, 0x79, 0xec, + 0x9e, 0x1d, 0x9f, 0x29, 0x85, 0xc8, 0xf8, 0xd2, 0x35, 0x01, 0xce, 0x2b, + 0x82, 0xd1, 0x7d, 0x19, 0x52, 0xeb, 0x24, 0x94, 0x9c, 0x4a, 0x8a, 0x13, + 0xc6, 0xc2, 0x59, 0x58, 0xf4, 0xfa, 0x7a, 0x8f, 0xc5, 0x77, 0x1a, 0xd6, + 0x65, 0x29, 0xb3, 0xf6, 0x92, 0x08, 0x2e, 0x35, 0xf0, 0xad, 0x0f, 0x5e, + 0x2d, 0xf1, 0x2c, 0x6a, 0x3e, 0x54, 0x3a, 0x2a, 0xb0, 0xfa, 0x08, 0x58, + 0x65, 0x73, 0xf5, 0x67, 0x14, 0xd5, 0x0a, 0x6a, 0x79, 0xf1, 0xa8, 0x2a, + 0x85, 0x3c, 0x02, 0x04, 0xf3, 0x4d, 0x1d, 0x00, 0x4e, 0x45, 0xb0, 0x06, + 0x37, 0xbe, 0xf7, 0x18, 0x45, 0x8f, 0x7c, 0xbb, 0x31, 0xb8, 0xc6, 0x55, + 0x5d, 0x92, 0xee, 0x13, 0xe5, 0x2b, 0x30, 0x01, 0x60, 0xce, 0x2d, 0x99, + 0xcf, 0x96, 0x39, 0x40, 0x6c, 0x4b, 0xf6, 0x06, 0xa9, 0x16, 0x08, 0x07, + 0x2b, 0x34, 0x64, 0xf2, 0x72, 0x99, 0x62, 0xa8, 0x1d, 0xd5, 0x3c, 0x94, + 0x83, 0x03, 0x41, 0xcc, 0xe8, 0xd8, 0x30, 0xae, 0x25, 0x8f, 0x3b, 0xf9, + 0x4b, 0x25, 0x0a, 0xfd, 0x5b, 0x6e, 0xb5, 0x63, 0x6a, 0x1c, 0x5b, 0x98, + 0x34, 0xd0, 0xaa, 0x67, 0xc9, 0x85, 0x1e, 0x22, 0xca, 0x0f, 0x47, 0x94, + 0x89, 0xda, 0x93, 0x4f, 0x9b, 0x30, 0xbd, 0x3e, 0xde, 0x49, 0xe4, 0x70, + 0x11, 0xf3, 0x21, 0xde, 0xc7, 0xb9, 0xa9, 0x3e, 0x62, 0x7b, 0x3f, 0x22, + 0x53, 0xf2, 0x6f, 0xfb, 0x14, 0x51, 0x9f, 0x72, 0xb6, 0xe9, 0x8f, 0xcc, + 0xe7, 0x83, 0xa8, 0x92, 0xba, 0x3b, 0x55, 0x17, 0xf0, 0xad, 0x34, 0x11, + 0x9d, 0xe7, 0x5e, 0x49, 0x96, 0xc2, 0x04, 0xbb, 0x36, 0x79, 0x77, 0x38, + 0x90, 0x52, 0x9d, 0x38, 0xee, 0x3b, 0xa0, 0x2b, 0x35, 0x3d, 0xbe, 0xfb, + 0x52, 0x6d, 0x68, 0xda, 0xa7, 0x00, 0x88, 0x49, 0x3e, 0x53, 0x74, 0xa3, + 0xed, 0x49, 0x20, 0xe0, 0xad, 0x28, 0xa2, 0x9d, 0xdf, 0x3f, 0x8e, 0x00, + 0xe9, 0x31, 0xe4, 0x51, 0x5e, 0x1a, 0xb0, 0x59, 0x12, 0xc9, 0xf2, 0xc8, + 0xba, 0xd7, 0xe0, 0x6f, 0x70, 0x9a, 0xb9, 0x81, 0x6c, 0x8a, 0xc2, 0xcc, + 0x9e, 0xa1, 0xe5, 0x81, 0x97, 0x67, 0x9f, 0xc0, 0xe7, 0xe2, 0x05, 0x73, + 0x10, 0x21, 0x96, 0x10, 0x8f, 0xd6, 0x2b, 0xd5, 0x07, 0x74, 0xb4, 0x2e, + 0x1c, 0x08, 0x2d, 0xc3, 0x5e, 0x13, 0x4b, 0x67, 0x1b, 0x33, 0x6d, 0xea, + 0x76, 0x38, 0xc0, 0x0e, 0x0f, 0xa5, 0xd5, 0xc6, 0xe6, 0xa0, 0x9a, 0xd0, + 0x01, 0x39, 0xca, 0x86, 0xe6, 0xbc, 0xec, 0x5a, 0xb8, 0x44, 0x79, 0x01, + 0x7a, 0x68, 0x94, 0xed, 0xa0, 0xb6, 0xeb, 0xd0, 0x5b, 0x05, 0x3f, 0x58, + 0xb2, 0xd9, 0x28, 0x27, 0xa5, 0x2c, 0xfa, 0xe6, 0xbf, 0x6c, 0x48, 0xa2, + 0xe0, 0x3e, 0x93, 0x87, 0x7d, 0x2a, 0x07, 0x4e, 0x29, 0x9b, 0xbf, 0xfd, + 0xd7, 0x8c, 0x2d, 0xf8, 0x1a, 0x2b, 0xec, 0xc9, 0x6f, 0x12, 0x5a, 0x67, + 0x27, 0x1a, 0x9e, 0x1c, 0x82, 0xee, 0x3c, 0x2c, 0xd9, 0x63, 0x86, 0xe1, + 0xa5, 0x74, 0x1f, 0x30, 0x1e, 0xa3, 0x4e, 0xb7, 0xba, 0x0c, 0xd0, 0x0c, + 0xe9, 0x8c, 0x20, 0x50, 0x68, 0x89, 0x47, 0xfe, 0xc1, 0xc6, 0xc7, 0x3a, + 0x05, 0xcc, 0xa5, 0x2c, 0x2b, 0x8b, 0x40, 0x12, 0x44, 0x69, 0xd8, 0x1f, + 0x25, 0x07, 0xac, 0x32, 0xbe, 0x47, 0xe2, 0xd9, 0xa5, 0xe4, 0xc1, 0xb5, + 0x55, 0x9f, 0x53, 0x61, 0x6d, 0x3d, 0xa0, 0x5b, 0xc1, 0xa9, 0xf5, 0xf5, + 0xc6, 0x94, 0x7f, 0x51, 0x0c, 0x0c, 0xa9, 0xa7, 0x09, 0x42, 0xd5, 0xe3, + 0x47, 0x79, 0xe4, 0xe6, 0x1f, 0x04, 0xbe, 0x25, 0xd0, 0xa0, 0xff, 0xbd, + 0xb0, 0x8d, 0x88, 0x91, 0x74, 0xba, 0x66, 0xe7, 0xfa, 0xb5, 0x3e, 0xff, + 0x21, 0x4e, 0x78, 0x29, 0x58, 0x9d, 0xf6, 0xd1, 0x84, 0x67, 0x82, 0x91, + 0xad, 0x00, 0x04, 0xce, 0x8f, 0xd2, 0xef, 0xce, 0x9f, 0x88, 0xc3, 0xca, + 0x74, 0x54, 0xf4, 0xef, 0xe8, 0xf8, 0x1f, 0x1d, 0x81, 0x8f, 0x96, 0x4d, + 0xbe, 0x6a, 0xf3, 0x9d, 0xac, 0xe0, 0x69, 0x48, 0x31, 0x8a, 0x3c, 0x4f, + 0x18, 0x48, 0xd6, 0xf5, 0x98, 0x5a, 0x74, 0x4d, 0xc1, 0xc4, 0x90, 0xa3, + 0xed, 0xbf, 0x13, 0x55, 0x6c, 0x17, 0x08, 0xda, 0xa0, 0x6f, 0x3e, 0x1b, + 0xb3, 0xd5, 0x23, 0xea, 0xcc, 0x8b, 0x68, 0x66, 0xe2, 0x59, 0xaf, 0x6a, + 0x6c, 0x6b, 0x87, 0x81, 0x91, 0xac, 0xc7, 0x6b, 0x21, 0x4b, 0x2d, 0xa0, + 0xdd, 0x9e, 0xdc, 0xd9, 0x66, 0x5b, 0x51, 0xac, 0x61, 0xd6, 0xc8, 0xf9, + 0x87, 0x11, 0xcd, 0xdd, 0xeb, 0xde, 0x1e, 0x04, 0x9b, 0xbd, 0x20, 0xaf, + 0x89, 0x85, 0x23, 0x8b, 0x0c, 0xb9, 0x74, 0x1b, 0xc2, 0xe3, 0x00, 0x0a, + 0xc3, 0x9f, 0x11, 0xd3, 0x79, 0x43, 0x67, 0xd7, 0xbb, 0x50, 0x3b, 0x07, + 0x44, 0xf0, 0xed, 0x32, 0x72, 0x23, 0xcb, 0x7c, 0x9a, 0x25, 0xc4, 0x41, + 0xe1, 0x7d, 0x5b, 0x5b, 0xa4, 0x75, 0xc0, 0xaf, 0x96, 0xea, 0x70, 0xbe, + 0xc1, 0x9c, 0x2a, 0xaf, 0xb1, 0x63, 0xed, 0xb0, 0x01, 0xf2, 0x85, 0x06, + 0x2e, 0x47, 0xf7, 0xc0, 0xa7, 0xa6, 0xae, 0x77, 0x3e, 0x29, 0x8d, 0xd0, + 0xfa, 0xc0, 0xc2, 0xb7, 0x51, 0x9a, 0xd9, 0xa7, 0xf7, 0x9d, 0x01, 0x8a, + 0x2f, 0xa7, 0x28, 0x73, 0x4b, 0xba, 0x77, 0x6d, 0xd2, 0xd3, 0x5b, 0x35, + 0x0c, 0x5f, 0xf9, 0xbf, 0x60, 0x75, 0xdf, 0x55, 0x86, 0xef, 0xee, 0x91, + 0xcf, 0x94, 0x45, 0xd7, 0xb5, 0x4d, 0x16, 0x6f, 0x19, 0x44, 0x08, 0xee, + 0x1e, 0x20, 0xbf, 0x5c, 0x4d, 0xb9, 0xce, 0x86, 0x0f, 0x56, 0xa7, 0xff, + 0x97, 0xcd, 0x23, 0xd5, 0x1e, 0xf9, 0xfe, 0x50, 0x82, 0x5e, 0x19, 0x89, + 0x4e, 0x78, 0x0b, 0xa6, 0xd7, 0xe7, 0x16, 0xd1, 0x28, 0x8a, 0xb5, 0x26, + 0xab, 0x39, 0x3a, 0xee, 0xf7, 0x74, 0x6e, 0x7e, 0xc6, 0x85, 0x63, 0xfd, + 0xbe, 0xba, 0xac, 0x8d, 0x02, 0xa1, 0xaa, 0x11, 0x3e, 0x2d, 0x17, 0x14, + 0x4e, 0xc2, 0xf8, 0x13, 0x5e, 0x28, 0x99, 0x7b, 0x9f, 0x3a, 0xa4, 0x91, + 0xa3, 0x81, 0xdd, 0x30, 0x81, 0xda, 0x30, 0x0c, 0x06, 0x03, 0x55, 0x1d, + 0x13, 0x01, 0x01, 0xff, 0x04, 0x02, 0x30, 0x00, 0x30, 0x0b, 0x06, 0x03, + 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x05, 0xe0, 0x30, 0x1d, 0x06, + 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x07, 0xcb, 0x47, 0xfb, + 0x66, 0x79, 0x64, 0x04, 0x87, 0x56, 0x3c, 0x62, 0xd3, 0x57, 0x13, 0xfa, + 0x75, 0x86, 0x7c, 0xa0, 0x30, 0x31, 0x06, 0x03, 0x55, 0x1d, 0x11, 0x04, + 0x2a, 0x30, 0x28, 0xa0, 0x26, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, + 0x83, 0x1c, 0x82, 0x12, 0x01, 0xa0, 0x18, 0x0c, 0x16, 0x41, 0x43, 0x4d, + 0x45, 0x3a, 0x57, 0x49, 0x44, 0x47, 0x45, 0x54, 0x3a, 0x31, 0x32, 0x33, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x30, 0x2a, 0x06, 0x03, 0x55, + 0x1d, 0x25, 0x01, 0x01, 0xff, 0x04, 0x20, 0x30, 0x1e, 0x06, 0x08, 0x2b, + 0x06, 0x01, 0x05, 0x05, 0x07, 0x03, 0x01, 0x06, 0x08, 0x2b, 0x06, 0x01, + 0x05, 0x05, 0x07, 0x03, 0x02, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, + 0x07, 0x03, 0x09, 0x30, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, + 0x83, 0x1c, 0x82, 0x12, 0x06, 0x04, 0x10, 0x30, 0x0e, 0x30, 0x0c, 0x06, + 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x83, 0x1c, 0x82, 0x12, 0x02, 0x30, + 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, + 0xec, 0x39, 0xea, 0x34, 0x3a, 0xcc, 0x1e, 0x04, 0x9f, 0x54, 0x8a, 0x76, + 0xcf, 0x7f, 0x29, 0x88, 0x10, 0x5d, 0x99, 0xeb, 0x30, 0x0b, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x11, 0x03, 0x82, 0x09, + 0x75, 0x00, 0xe0, 0xe0, 0x2b, 0x8a, 0x11, 0x03, 0x54, 0xa0, 0xf4, 0xe6, + 0x16, 0xf8, 0x49, 0x9b, 0x02, 0x5f, 0x0e, 0x3e, 0x86, 0x4c, 0xc4, 0x04, + 0x39, 0xab, 0xb5, 0xd4, 0x00, 0xcb, 0x9d, 0xf4, 0x5b, 0x27, 0xd5, 0xf5, + 0x6b, 0x92, 0xef, 0xe6, 0x4d, 0x14, 0xc7, 0x0d, 0x85, 0xe7, 0xc4, 0x07, + 0xf1, 0x71, 0x91, 0xc3, 0x6d, 0x08, 0x7e, 0xb1, 0xce, 0xfb, 0x10, 0x6f, + 0xe2, 0x50, 0x0c, 0x74, 0xc9, 0xaa, 0xc3, 0xab, 0xe7, 0xd3, 0x03, 0x92, + 0xed, 0x81, 0x4e, 0x45, 0xa2, 0x0e, 0x2a, 0x24, 0xd2, 0x45, 0xcf, 0x7e, + 0xe8, 0xdc, 0x41, 0x97, 0x9b, 0x8e, 0xb3, 0x90, 0x6f, 0x7f, 0x03, 0xf3, + 0xb5, 0x8e, 0x52, 0xf6, 0xbd, 0x97, 0x05, 0xda, 0xa0, 0xb2, 0xa8, 0x3c, + 0xd6, 0xd3, 0x29, 0xd1, 0x7a, 0x8a, 0xb5, 0x06, 0x4d, 0x01, 0x98, 0xbd, + 0xc4, 0x5f, 0x63, 0xe0, 0x4d, 0xd5, 0x09, 0xee, 0x21, 0x89, 0x21, 0x8f, + 0x7c, 0x32, 0x10, 0x8a, 0xe9, 0xd9, 0x4e, 0xc8, 0x50, 0x79, 0x89, 0x25, + 0xaf, 0xf4, 0xd8, 0x90, 0xf8, 0xba, 0x2c, 0x6c, 0xf5, 0x40, 0x0f, 0x3e, + 0xf0, 0x04, 0x1d, 0xa7, 0x85, 0x9f, 0xf3, 0xdc, 0x7a, 0xd5, 0x18, 0x0c, + 0x82, 0xb2, 0x99, 0x02, 0xcf, 0xe4, 0xf8, 0xbe, 0xd2, 0x88, 0xbc, 0x6d, + 0xce, 0x07, 0xac, 0x9d, 0xd8, 0xa6, 0xa8, 0xf8, 0xda, 0x45, 0xf5, 0x0f, + 0x5c, 0x69, 0xd2, 0x17, 0xff, 0x86, 0x21, 0x85, 0x69, 0x40, 0x4e, 0xba, + 0x99, 0xb3, 0x01, 0xf3, 0x50, 0x64, 0x06, 0x90, 0x70, 0x0a, 0x3c, 0xa4, + 0x79, 0xd4, 0x7f, 0x72, 0x87, 0x50, 0x28, 0x0f, 0xf8, 0xbb, 0x40, 0xab, + 0x8e, 0x5c, 0x74, 0xef, 0x22, 0xf7, 0x5a, 0x26, 0x38, 0x20, 0x27, 0x65, + 0xb5, 0xd6, 0x61, 0xd3, 0xa4, 0x15, 0x7e, 0x0d, 0xf0, 0x5c, 0xa7, 0x7c, + 0xbd, 0xe6, 0x57, 0xae, 0x40, 0xe9, 0x54, 0xf5, 0x61, 0x1d, 0x73, 0xba, + 0x69, 0x16, 0x95, 0xc7, 0x74, 0x43, 0x68, 0x11, 0xe7, 0xe4, 0xc9, 0x36, + 0xba, 0x5a, 0x30, 0xf0, 0x26, 0xc3, 0x3d, 0x61, 0x6d, 0xdc, 0x15, 0x49, + 0xda, 0x5f, 0xd0, 0xf5, 0x5e, 0x6c, 0x4a, 0x03, 0x0c, 0x43, 0xaa, 0x70, + 0xf2, 0x69, 0xce, 0xf3, 0xf6, 0x1c, 0x84, 0x11, 0xcc, 0xd9, 0x9f, 0x77, + 0x47, 0x27, 0x05, 0x53, 0xf3, 0x97, 0x4a, 0xce, 0x1f, 0x66, 0xc8, 0x0c, + 0x9c, 0x35, 0xe1, 0x3d, 0xf3, 0xcb, 0xef, 0xa9, 0xd4, 0x79, 0x16, 0xb0, + 0xb4, 0x67, 0x7d, 0xba, 0x4e, 0xfb, 0x8a, 0xe7, 0xdf, 0xce, 0xde, 0x7d, + 0x19, 0xd7, 0xce, 0x81, 0xf0, 0x6a, 0x8e, 0xdc, 0x60, 0xa1, 0x1f, 0x58, + 0x77, 0x91, 0x05, 0xa0, 0x21, 0xf1, 0x1e, 0x01, 0xbd, 0x29, 0xb9, 0x45, + 0x10, 0xe5, 0x0b, 0x85, 0xc9, 0x03, 0x64, 0x30, 0x6b, 0x9c, 0xab, 0x4d, + 0xc6, 0x8f, 0xdc, 0xbe, 0xd5, 0x90, 0xdf, 0xd1, 0x25, 0xf8, 0xd3, 0xe8, + 0x9f, 0xd8, 0x8a, 0x35, 0x2a, 0xcb, 0xcd, 0xdd, 0x62, 0x33, 0xa5, 0xde, + 0x0f, 0x75, 0x73, 0xbf, 0xe4, 0x01, 0x50, 0xee, 0x76, 0xe0, 0xcf, 0x29, + 0xc6, 0xa0, 0xc4, 0x92, 0x94, 0xbb, 0x6f, 0xef, 0x13, 0x38, 0x90, 0xf8, + 0x2a, 0x7b, 0x48, 0xed, 0xf6, 0x51, 0x68, 0x03, 0x14, 0xc7, 0x31, 0x71, + 0xd9, 0xa2, 0x09, 0xa9, 0x9c, 0xec, 0xa3, 0x4a, 0xa9, 0x1a, 0xd6, 0x08, + 0x28, 0x5c, 0x23, 0x16, 0x20, 0x00, 0x2c, 0x7c, 0x1d, 0x9e, 0x89, 0xfa, + 0x3e, 0xb6, 0xdc, 0x56, 0xdb, 0x99, 0xdc, 0x2a, 0xae, 0x8b, 0x84, 0x80, + 0xf7, 0xa8, 0x63, 0x5d, 0xa6, 0x24, 0x7b, 0x46, 0xa5, 0xef, 0xc4, 0x91, + 0x3f, 0xb3, 0x56, 0xc2, 0x70, 0x49, 0x07, 0x68, 0xae, 0x81, 0x2a, 0xa8, + 0xf7, 0x03, 0x7c, 0x8f, 0xfb, 0xf0, 0x00, 0x36, 0x0b, 0xd4, 0x75, 0x6f, + 0xe7, 0xa9, 0xc6, 0x47, 0x9d, 0x19, 0x12, 0x2d, 0xb5, 0x99, 0xa1, 0x57, + 0x97, 0x7a, 0xff, 0x7f, 0xc9, 0x3b, 0xd8, 0xea, 0x49, 0xed, 0xed, 0x03, + 0x7d, 0x85, 0x19, 0x97, 0xdc, 0x02, 0xa0, 0x5f, 0x97, 0x83, 0x68, 0x5e, + 0x14, 0x9b, 0x91, 0x3f, 0x87, 0x84, 0x48, 0xa0, 0x0c, 0xe3, 0xe3, 0x2f, + 0x7e, 0x50, 0x1c, 0x08, 0x62, 0xe4, 0xef, 0x1e, 0x76, 0x8e, 0xba, 0x75, + 0xef, 0x2b, 0x33, 0xbc, 0x7b, 0x62, 0x48, 0x3d, 0xfb, 0xe1, 0xd8, 0x02, + 0x34, 0xed, 0x3e, 0x8d, 0x25, 0x52, 0xe2, 0x06, 0xbf, 0x21, 0x67, 0xac, + 0x02, 0x3d, 0x0a, 0x05, 0x96, 0x75, 0x33, 0xe5, 0xf1, 0x7b, 0xe6, 0xe3, + 0x45, 0x25, 0x74, 0x56, 0x2e, 0x92, 0xd9, 0x6e, 0x0c, 0xe3, 0xce, 0x99, + 0x2c, 0xc6, 0xbc, 0x15, 0xce, 0x34, 0x36, 0xd4, 0xa3, 0x96, 0x34, 0x04, + 0xb8, 0x89, 0x18, 0xa0, 0x26, 0x56, 0xb0, 0xcc, 0x9b, 0x1b, 0x71, 0x54, + 0x59, 0x9b, 0xd2, 0x95, 0x75, 0x0d, 0xe6, 0xeb, 0x95, 0x3d, 0xde, 0x11, + 0x3b, 0x9a, 0x16, 0x14, 0xf8, 0x28, 0x01, 0x0c, 0xfd, 0xbe, 0x2f, 0xaa, + 0x55, 0xb6, 0xab, 0x4b, 0xcc, 0x82, 0xa3, 0x62, 0x33, 0xa9, 0x10, 0x4d, + 0x84, 0x8b, 0x5d, 0xf2, 0x15, 0xfd, 0x90, 0x2a, 0x22, 0x74, 0x18, 0x6c, + 0x8a, 0xe7, 0x7b, 0x5c, 0x17, 0x73, 0xc1, 0xb4, 0x08, 0x63, 0xbd, 0x26, + 0xbe, 0x81, 0xb3, 0x73, 0xd2, 0x65, 0xb4, 0xe8, 0xf3, 0x2c, 0xb1, 0x44, + 0xba, 0x67, 0x99, 0xca, 0x2b, 0x1b, 0xb7, 0x03, 0x75, 0x44, 0x11, 0x2a, + 0x4e, 0xe4, 0xa9, 0xdc, 0xbd, 0x61, 0xcd, 0x40, 0xde, 0x69, 0x39, 0x5e, + 0x29, 0x6b, 0xf6, 0x37, 0xd3, 0x0c, 0xd9, 0x3f, 0xa5, 0xac, 0x15, 0xda, + 0xbe, 0xae, 0x47, 0xcf, 0xf7, 0x6e, 0xbb, 0x92, 0x1c, 0x7b, 0x72, 0xf2, + 0xc8, 0xd5, 0xc3, 0x35, 0xc2, 0x1b, 0x8a, 0x19, 0x1c, 0x35, 0x14, 0x09, + 0x6e, 0x6c, 0x41, 0x6d, 0x6c, 0x2b, 0x11, 0x4f, 0xe7, 0x82, 0xd7, 0x31, + 0x1a, 0xca, 0x70, 0xd1, 0x80, 0xc4, 0x01, 0x4c, 0x00, 0xd7, 0x32, 0x2b, + 0x6c, 0x50, 0xe4, 0x94, 0x2a, 0x41, 0xd5, 0xb9, 0xa0, 0xca, 0x96, 0xf2, + 0x7a, 0x2c, 0x42, 0xed, 0xf8, 0x62, 0x79, 0xd4, 0x26, 0xc5, 0xb3, 0x91, + 0xe7, 0xe0, 0xe6, 0x94, 0xcb, 0x55, 0x5b, 0xff, 0x66, 0x2a, 0x4c, 0xbe, + 0x0d, 0xb2, 0xd2, 0xd2, 0x2d, 0x4c, 0xd9, 0x52, 0xbb, 0x7c, 0xfa, 0xa4, + 0x01, 0x64, 0xa2, 0x34, 0x27, 0xe2, 0xbc, 0x7a, 0xb5, 0x79, 0xdd, 0xb1, + 0x65, 0x26, 0x92, 0xaa, 0x32, 0xcf, 0xe7, 0x8a, 0xe0, 0x4b, 0xa7, 0x23, + 0x20, 0xe6, 0x41, 0xdc, 0x9a, 0xb4, 0xa5, 0x77, 0x71, 0x46, 0x87, 0x5c, + 0xfe, 0x65, 0xd1, 0x0a, 0xfb, 0x01, 0xe8, 0xba, 0x79, 0x2f, 0xc7, 0x32, + 0xda, 0x3f, 0xca, 0xf0, 0x99, 0x16, 0x20, 0x8f, 0x92, 0x6c, 0x89, 0xb6, + 0x3b, 0x2f, 0x1c, 0x83, 0x04, 0xe0, 0xa2, 0x25, 0xa5, 0x4b, 0x22, 0x35, + 0x82, 0x98, 0x0d, 0x09, 0x8e, 0xff, 0xb0, 0x28, 0x1f, 0x96, 0x11, 0xf4, + 0x65, 0xbf, 0xb6, 0x0c, 0x03, 0x32, 0xc6, 0xe8, 0xdf, 0x33, 0x37, 0x85, + 0xce, 0x67, 0x82, 0xe1, 0xd9, 0x6e, 0x76, 0xe7, 0xd1, 0xfb, 0x2e, 0x39, + 0x32, 0x62, 0xb2, 0x41, 0x13, 0xf5, 0x98, 0x25, 0x20, 0xef, 0x43, 0x79, + 0x72, 0x79, 0x76, 0x95, 0xae, 0x23, 0x96, 0x37, 0x8f, 0xde, 0xf8, 0xcd, + 0xc2, 0x81, 0xb4, 0x7e, 0xb1, 0xbb, 0x34, 0xc6, 0x9c, 0x0f, 0x04, 0x58, + 0xbc, 0x11, 0x03, 0x12, 0x60, 0x3c, 0xe8, 0xd0, 0xb5, 0xc4, 0x96, 0x4b, + 0xf6, 0xfe, 0xed, 0x66, 0x9f, 0x13, 0xab, 0x48, 0x7d, 0x0f, 0xc3, 0x3d, + 0x20, 0x60, 0xcf, 0x4b, 0xd7, 0xbf, 0x9c, 0x77, 0xf9, 0x01, 0x50, 0xf5, + 0x6f, 0x50, 0x1a, 0x33, 0x36, 0xf7, 0xab, 0x20, 0x89, 0xce, 0x6d, 0xbb, + 0xbd, 0x6d, 0x6d, 0xe1, 0x7e, 0x13, 0xb6, 0xf4, 0x38, 0xc8, 0x3e, 0x23, + 0x53, 0x55, 0xf7, 0x18, 0xbc, 0x00, 0x99, 0x2c, 0xb8, 0x1b, 0xa8, 0xee, + 0x1c, 0x45, 0x13, 0x1a, 0x95, 0x72, 0x8e, 0x00, 0x0c, 0x78, 0x86, 0x05, + 0x59, 0x26, 0x0b, 0x13, 0x95, 0x69, 0x44, 0x1d, 0xa0, 0xf8, 0xc2, 0x27, + 0x50, 0x67, 0x3c, 0xb4, 0x99, 0xb7, 0xec, 0x5d, 0x43, 0x3b, 0xe0, 0x88, + 0xe5, 0xe3, 0x84, 0x51, 0x85, 0x74, 0x14, 0xad, 0x42, 0x7d, 0x8d, 0x42, + 0x15, 0xb2, 0xd4, 0xc0, 0x40, 0x59, 0x1c, 0xb2, 0xa8, 0xb5, 0x60, 0x67, + 0x46, 0xa7, 0x81, 0x1f, 0xa6, 0x22, 0x7c, 0x57, 0x2b, 0x1c, 0x3d, 0x49, + 0xfd, 0x37, 0x31, 0xa0, 0xcd, 0x5a, 0x2b, 0x89, 0xe4, 0xbf, 0xea, 0xc7, + 0x08, 0x45, 0xe2, 0xed, 0xab, 0x80, 0x8d, 0xef, 0x83, 0x20, 0x4e, 0x4d, + 0x4e, 0x2a, 0x6d, 0x56, 0x3c, 0xf3, 0x2a, 0x5a, 0x7e, 0x3b, 0xef, 0x1e, + 0x04, 0xf9, 0x41, 0xde, 0xe5, 0xbf, 0x62, 0x69, 0xbb, 0x30, 0xf0, 0xc1, + 0x56, 0xb1, 0x08, 0x2f, 0x0c, 0x65, 0x84, 0x99, 0xaf, 0x76, 0x65, 0xe1, + 0xbc, 0x12, 0xc1, 0xb8, 0x35, 0xc7, 0xe9, 0x8e, 0x14, 0xcf, 0x3c, 0xf0, + 0x5d, 0xd0, 0x50, 0x42, 0x50, 0x79, 0x2c, 0xc8, 0x5f, 0x58, 0xe6, 0xe0, + 0x92, 0x74, 0x98, 0x3c, 0x45, 0x80, 0x31, 0x5b, 0xe1, 0x5a, 0x8d, 0xf1, + 0x7d, 0x2c, 0x4f, 0xb0, 0x9c, 0xa3, 0x83, 0xab, 0xd6, 0x3c, 0xa7, 0x49, + 0x46, 0x7f, 0x42, 0x12, 0x1d, 0x2c, 0xc3, 0x2b, 0xa4, 0x63, 0xe2, 0xd2, + 0xb6, 0xfe, 0x14, 0x3d, 0xb9, 0x3f, 0x04, 0x80, 0x5f, 0x11, 0x61, 0xd1, + 0x9e, 0x57, 0x45, 0x7a, 0x00, 0x59, 0xf9, 0x5a, 0x72, 0xe1, 0x0a, 0x44, + 0xa1, 0x67, 0xac, 0x0d, 0x6e, 0x7d, 0x06, 0xcc, 0x5d, 0xd1, 0xa9, 0xcc, + 0xed, 0xe3, 0x48, 0x70, 0x6e, 0x1d, 0xa9, 0x91, 0xbf, 0xc0, 0x31, 0x3b, + 0xad, 0x22, 0xfd, 0xb3, 0x8f, 0xb5, 0x25, 0x34, 0xd4, 0xbc, 0x81, 0x52, + 0xbd, 0x5b, 0x3c, 0xa3, 0x11, 0x11, 0x7a, 0x1a, 0x26, 0xca, 0xd3, 0x77, + 0x37, 0xbf, 0xd3, 0x7e, 0x07, 0x3e, 0x06, 0xdc, 0xa1, 0x40, 0xb2, 0x39, + 0x99, 0x4c, 0x5d, 0x24, 0x80, 0xee, 0x7d, 0xaf, 0xdc, 0x09, 0xfe, 0x43, + 0xb9, 0xbb, 0x96, 0xbf, 0x56, 0x21, 0x85, 0xaf, 0xc5, 0xaa, 0x05, 0x78, + 0x7c, 0x6d, 0xa9, 0xd5, 0x3c, 0x3d, 0xe3, 0x50, 0xd2, 0x60, 0x33, 0x78, + 0x3c, 0x5d, 0x1a, 0xce, 0x9f, 0xc5, 0xea, 0xae, 0x29, 0xcd, 0x11, 0x0c, + 0x81, 0xbb, 0xd2, 0x2a, 0x27, 0x4e, 0xcf, 0x98, 0x20, 0xe5, 0x62, 0x60, + 0x4b, 0x19, 0x87, 0x6b, 0x68, 0x13, 0xcb, 0x96, 0xb3, 0xc2, 0xad, 0xdb, + 0x4a, 0xec, 0x4a, 0x86, 0x2e, 0x4d, 0x52, 0x75, 0x11, 0x06, 0xca, 0x94, + 0x41, 0xb8, 0xce, 0x29, 0xe2, 0x62, 0xd3, 0xd0, 0xe9, 0x15, 0xef, 0x0e, + 0xb5, 0x44, 0x95, 0x53, 0x8b, 0xe4, 0x53, 0x74, 0xbd, 0xd2, 0x03, 0xc0, + 0x15, 0xd4, 0x8a, 0xbe, 0x60, 0x51, 0xb3, 0x16, 0x0d, 0x70, 0x3c, 0x71, + 0x17, 0x40, 0x5c, 0x83, 0x6a, 0xba, 0x84, 0xe0, 0xf4, 0xcf, 0x7c, 0x34, + 0x9c, 0x15, 0x65, 0xd0, 0xc9, 0x46, 0xa4, 0x6c, 0xc3, 0xa0, 0x35, 0x7c, + 0x3c, 0x2e, 0xc7, 0xb1, 0xd3, 0xfc, 0xce, 0xd7, 0x03, 0x49, 0x82, 0x8c, + 0x47, 0x3e, 0xf7, 0xcc, 0xeb, 0xa3, 0xfe, 0x9a, 0xd9, 0x04, 0x4a, 0x59, + 0xe5, 0x14, 0xa8, 0x91, 0x38, 0x16, 0x12, 0x51, 0x2a, 0x34, 0x4d, 0x1b, + 0x67, 0xfd, 0xe9, 0xf1, 0x3a, 0xc4, 0xa4, 0x71, 0xd7, 0xdd, 0x2d, 0xc2, + 0xf0, 0x50, 0x67, 0xe6, 0xaf, 0x7c, 0xb6, 0x56, 0x5a, 0xad, 0x99, 0x70, + 0x41, 0x4c, 0xa3, 0xaf, 0x89, 0xc4, 0x6c, 0x9b, 0x3f, 0x33, 0x5a, 0xb0, + 0xa6, 0x9d, 0xc6, 0x1a, 0xeb, 0xd2, 0x86, 0xbe, 0x32, 0x38, 0x1b, 0xb8, + 0xa4, 0xb1, 0x16, 0x0c, 0xec, 0x64, 0x1b, 0xc0, 0x52, 0x8a, 0x79, 0x51, + 0x1f, 0x59, 0x55, 0x49, 0x43, 0x02, 0xb8, 0x5a, 0x67, 0xb5, 0x7d, 0x26, + 0xfc, 0x12, 0x4a, 0x19, 0x0c, 0x02, 0xa8, 0x0d, 0x91, 0x61, 0xe7, 0x7f, + 0xf0, 0xf6, 0x8c, 0xa0, 0x0a, 0xdc, 0x3b, 0xa6, 0x28, 0x99, 0x42, 0x22, + 0x60, 0xa8, 0x3b, 0x14, 0x49, 0xe0, 0x17, 0xa2, 0x5f, 0xda, 0x93, 0xff, + 0xe6, 0xd8, 0x4f, 0x3b, 0x94, 0x5c, 0x8a, 0xcd, 0x85, 0x31, 0xcd, 0xe2, + 0xa9, 0x57, 0x81, 0xd4, 0x9f, 0x59, 0x42, 0x40, 0x5e, 0xc9, 0x2a, 0x07, + 0x46, 0x59, 0x3e, 0x6d, 0x05, 0x98, 0x65, 0x51, 0x31, 0x15, 0x08, 0x32, + 0x18, 0xd1, 0x68, 0xa5, 0x87, 0x26, 0xf3, 0x4a, 0xcc, 0x9c, 0xf1, 0x20, + 0xd8, 0xf0, 0x6e, 0x22, 0x1d, 0xa1, 0x8a, 0xaf, 0xf4, 0xc4, 0x27, 0xf8, + 0x2e, 0x77, 0x73, 0x6c, 0x16, 0xfe, 0xfc, 0x21, 0x7c, 0xc4, 0x66, 0x83, + 0xd6, 0x14, 0x8d, 0xfc, 0x72, 0x68, 0xff, 0x3d, 0x06, 0xd3, 0x00, 0xd5, + 0x5e, 0x0d, 0x0a, 0x62, 0xc5, 0x47, 0x13, 0xdc, 0x7c, 0x65, 0x93, 0xf1, + 0x17, 0x77, 0x21, 0xe8, 0xc5, 0xc3, 0xb2, 0xbf, 0x1c, 0xc5, 0xde, 0x49, + 0xcd, 0x64, 0x95, 0xbb, 0xae, 0x07, 0xf7, 0xd7, 0x6f, 0x43, 0x2c, 0x5f, + 0x10, 0x00, 0x58, 0x9a, 0x21, 0x7c, 0x09, 0x85, 0x0a, 0xf0, 0x49, 0x03, + 0x88, 0xb4, 0x32, 0xfe, 0xcb, 0xca, 0xb4, 0x1c, 0x07, 0xc9, 0x15, 0x77, + 0xc7, 0xba, 0xc7, 0x2f, 0xfd, 0x53, 0x40, 0xb0, 0x5d, 0xe0, 0x58, 0x65, + 0xa3, 0x7e, 0x71, 0x81, 0xdc, 0xb5, 0x32, 0xa2, 0x09, 0x13, 0xc4, 0x53, + 0x9c, 0xa8, 0xfc, 0xf1, 0xa3, 0x7f, 0x33, 0xd8, 0xc9, 0x54, 0xbd, 0xb9, + 0xfc, 0xe0, 0xa6, 0x27, 0x64, 0x35, 0x7a, 0xff, 0x08, 0x39, 0x1e, 0x89, + 0x17, 0x5d, 0xee, 0x97, 0xc5, 0x58, 0x4c, 0x32, 0x1c, 0x1d, 0xa9, 0x1b, + 0xee, 0x39, 0x67, 0x15, 0xfb, 0xc0, 0x46, 0x53, 0x79, 0xd2, 0x71, 0xdc, + 0x12, 0x58, 0x22, 0x0b, 0xbe, 0xfb, 0xcb, 0x31, 0xec, 0xa7, 0x8e, 0x29, + 0xab, 0xaf, 0x30, 0xd3, 0x9e, 0x4c, 0xe6, 0x98, 0x19, 0xe5, 0x40, 0xb4, + 0xa6, 0x2c, 0x61, 0x09, 0x31, 0x8e, 0xce, 0x75, 0x5d, 0x57, 0x2f, 0xc1, + 0xc8, 0xd0, 0xde, 0xe3, 0x5d, 0x97, 0x8a, 0xd6, 0x89, 0x08, 0x93, 0xdc, + 0x11, 0xc5, 0x77, 0xdd, 0x69, 0x37, 0xcb, 0x69, 0x22, 0x38, 0x6c, 0x78, + 0x02, 0x93, 0x0b, 0xdb, 0x9c, 0x13, 0xb2, 0x4b, 0x83, 0x88, 0x65, 0xec, + 0xa4, 0xc3, 0xa9, 0xb7, 0xa8, 0x64, 0x76, 0x98, 0xcf, 0xc9, 0x73, 0x36, + 0xd5, 0x13, 0x5d, 0x59, 0x86, 0xb7, 0x02, 0x1c, 0x23, 0x6b, 0x72, 0x72, + 0xb4, 0xf0, 0x18, 0x41, 0x65, 0xf4, 0x3a, 0x50, 0x5c, 0x5e, 0x7c, 0xe1, + 0x4d, 0x5e, 0xca, 0x45, 0x78, 0x50, 0x6d, 0x0c, 0x2b, 0x72, 0x77, 0xc0, + 0x6c, 0x99, 0x47, 0x78, 0x4c, 0x17, 0xda, 0x5e, 0xfb, 0x0b, 0x33, 0xaa, + 0x26, 0xf9, 0x47, 0x76, 0x59, 0xd0, 0x04, 0xc0, 0x9b, 0xc3, 0xac, 0x16, + 0xde, 0x1c, 0x8e, 0x0d, 0x7e, 0x44, 0x1a, 0x99, 0x83, 0xa7, 0xbd, 0xf9, + 0x71, 0x90, 0x68, 0x61, 0x7f, 0x19, 0x94, 0xf9, 0x6c, 0x0c, 0xff, 0xf5, + 0x30, 0x26, 0x13, 0x5f, 0x48, 0x53, 0x67, 0xfa, 0x54, 0x71, 0x7b, 0x06, + 0xc3, 0xa5, 0x16, 0x7c, 0x0a, 0x2a, 0x38, 0x96, 0xdb, 0x89, 0xb5, 0xa7, + 0xb3, 0x74, 0x5b, 0xc9, 0xfd, 0xed, 0x00, 0xf7, 0xfe, 0x19, 0x2b, 0xcc, + 0x9a, 0x8c, 0xbf, 0x77, 0x1f, 0x1d, 0x47, 0x46, 0xe6, 0xf5, 0xec, 0x23, + 0xb8, 0x22, 0xb2, 0x77, 0xd2, 0xba, 0xde, 0xc9, 0x59, 0x1d, 0x4a, 0x1b, + 0xa4, 0xdb, 0x25, 0x9d, 0x2b, 0x6e, 0xc7, 0x50, 0xb6, 0x77, 0x82, 0xb9, + 0x22, 0x49, 0x32, 0x38, 0x4d, 0xc4, 0x2c, 0xb5, 0x39, 0x85, 0x77, 0x02, + 0xe5, 0x3d, 0x21, 0xd2, 0x33, 0xe7, 0x9c, 0x4e, 0xe6, 0xa1, 0x46, 0xd1, + 0xa7, 0xa5, 0x21, 0x37, 0x94, 0xb6, 0x63, 0x2d, 0x46, 0xe2, 0xfe, 0x13, + 0xb2, 0xe4, 0x16, 0x3f, 0x8d, 0xbf, 0x83, 0xb0, 0xc5, 0xce, 0x02, 0xe2, + 0xb4, 0xc4, 0x90, 0x65, 0x6a, 0x71, 0x4c, 0xa5, 0x52, 0x70, 0x70, 0x58, + 0x1b, 0xfb, 0xfe, 0xb3, 0x05, 0x47, 0x29, 0x2b, 0xad, 0x4a, 0x14, 0x4d, + 0x9e, 0x4b, 0x7f, 0x0d, 0xcf, 0x54, 0x55, 0xdd, 0xe8, 0x98, 0xf4, 0xf1, + 0x03, 0x55, 0x1d, 0xae, 0xf7, 0xdf, 0x03, 0x89, 0x7e, 0x6f, 0xfb, 0x28, + 0x0a, 0x01, 0xc2, 0x8b, 0x47, 0xa9, 0x73, 0xe7, 0x8e, 0x9d, 0x6b, 0x05, + 0x5a, 0x1a, 0xf0, 0xf1, 0x62, 0xd4, 0xe5, 0xb2, 0xa0, 0xca, 0x6d, 0x61, + 0xbb, 0xf5, 0x26, 0x66, 0xe2, 0x5d, 0x8f, 0xd7, 0x9c, 0x75, 0x4a, 0x95, + 0x7f, 0x75, 0x2b, 0x86, 0xc3, 0xd4, 0x9b, 0x7d, 0x4e, 0xe4, 0x8a, 0x53, + 0x14, 0x23, 0x26, 0xf1, 0x4a, 0x8f, 0x3f, 0xd2, 0x14, 0x09, 0x9f, 0xd6, + 0x0b, 0x75, 0x8f, 0x30, 0x82, 0xd6, 0xc6, 0xdb, 0xc4, 0x2e, 0x5d, 0xc3, + 0xe5, 0xb4, 0x7b, 0xa0, 0xa8, 0x0c, 0xe4, 0xc0, 0x2f, 0xf7, 0x5f, 0x11, + 0x53, 0x23, 0x13, 0xa8, 0x86, 0x40, 0xc9, 0x1d, 0x4a, 0x0b, 0x4d, 0x4f, + 0xf2, 0x34, 0x6c, 0x66, 0x90, 0x8a, 0x58, 0xaf, 0x1f, 0xc0, 0x8c, 0xb5, + 0xbb, 0xc1, 0x4c, 0xf7, 0xc2, 0xb2, 0xaf, 0x5b, 0xdd, 0xa2, 0xf6, 0xd2, + 0xe0, 0x46, 0x41, 0xf0, 0x08, 0x96, 0xd1, 0x2b, 0x4f, 0xae, 0x97, 0x95, + 0xc4, 0x91, 0xf6, 0x5f, 0x8f, 0x73, 0x49, 0xf7, 0xda, 0x89, 0x67, 0xc4, + 0xc4, 0xcc, 0xf8, 0x5f, 0x26, 0xa7, 0xfc, 0x76, 0x3a, 0x35, 0x00, 0x0d, + 0x13, 0x27, 0x29, 0x3c, 0x4b, 0x61, 0x63, 0x68, 0x6c, 0x85, 0x8b, 0x9d, + 0xb9, 0xc9, 0xde, 0xe5, 0xef, 0xf2, 0xf3, 0xf5, 0x18, 0x20, 0x23, 0x3f, + 0x46, 0x5b, 0x5c, 0x60, 0x70, 0x8f, 0xaf, 0xd2, 0xda, 0xec, 0x0b, 0x14, + 0x36, 0x50, 0x72, 0x77, 0x84, 0x9e, 0xab, 0xd6, 0xec, 0x0e, 0x15, 0x2a, + 0x6c, 0x8b, 0xaf, 0xc0, 0xcc, 0xd8, 0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x24, 0x2f, 0x39 +}; + +static const byte test_mldsa44_leaf_pub[] = { + 0x55, 0x33, 0xe2, 0xf7, 0xb5, 0xef, 0x3b, 0x71, 0xe8, 0xf5, 0xbd, 0x71, + 0x96, 0xac, 0x5e, 0x29, 0x34, 0x2d, 0xe1, 0xe6, 0xc3, 0x5d, 0xa2, 0xc3, + 0x81, 0x32, 0x57, 0x85, 0xe1, 0xa1, 0xb8, 0x8e, 0x38, 0xcf, 0x69, 0xff, + 0x1e, 0x38, 0x9f, 0xe4, 0xe4, 0xf5, 0xab, 0x2e, 0xb6, 0xbd, 0x80, 0x63, + 0x27, 0x19, 0xd0, 0xca, 0xdb, 0xc6, 0x89, 0x9e, 0x2b, 0xa6, 0xf1, 0x5b, + 0x2a, 0xd9, 0x1d, 0xe0, 0xaa, 0xfb, 0x8d, 0x0a, 0xe2, 0x10, 0x58, 0xc6, + 0xf8, 0x62, 0x00, 0x71, 0x87, 0x42, 0x6f, 0x1e, 0x84, 0x00, 0x37, 0x62, + 0x84, 0x82, 0x80, 0x57, 0x79, 0x18, 0x9d, 0xf6, 0xe4, 0x88, 0x52, 0xa5, + 0x97, 0xaa, 0x8a, 0xe2, 0xb3, 0x3c, 0x71, 0xe2, 0x2e, 0x25, 0x82, 0xea, + 0x67, 0xbc, 0x99, 0x4a, 0xb6, 0xc9, 0xb7, 0x66, 0x4b, 0x81, 0xb8, 0x37, + 0xa5, 0x66, 0xba, 0xa2, 0x0e, 0x96, 0x77, 0xf7, 0x23, 0xa9, 0xab, 0x2c, + 0xa5, 0x7f, 0x1f, 0x97, 0x3c, 0xd8, 0x98, 0xc1, 0xcc, 0xca, 0x32, 0x78, + 0x21, 0xe3, 0x4a, 0x3e, 0xfd, 0x82, 0x97, 0x76, 0x6e, 0xb0, 0xc8, 0xef, + 0xf1, 0x18, 0x3d, 0x3d, 0x92, 0x63, 0x1b, 0x62, 0xfe, 0xab, 0x16, 0x1a, + 0x75, 0xb2, 0x2f, 0x17, 0x64, 0xd1, 0x47, 0x8a, 0x46, 0x50, 0x21, 0xba, + 0xfb, 0xc5, 0x9d, 0xe9, 0xf9, 0xcb, 0x95, 0x75, 0xa7, 0x7a, 0x46, 0xc6, + 0x6d, 0x9c, 0x11, 0x07, 0xd0, 0x02, 0x7c, 0x5d, 0x19, 0x2b, 0x2e, 0x3f, + 0x39, 0x02, 0x7c, 0x09, 0xbc, 0xd8, 0x57, 0x0c, 0x9d, 0x78, 0xc6, 0xd5, + 0x64, 0xf4, 0x81, 0xdc, 0x82, 0x65, 0x66, 0x77, 0xa2, 0x77, 0x26, 0xef, + 0x47, 0x0d, 0x95, 0x48, 0x51, 0x5a, 0x13, 0x5f, 0x2e, 0xac, 0x1f, 0x89, + 0x24, 0x75, 0x0c, 0xef, 0xf8, 0x48, 0xfd, 0x92, 0xfe, 0xc9, 0x7f, 0x50, + 0xbe, 0x1d, 0x29, 0xb7, 0x6e, 0xf1, 0x8e, 0x42, 0x34, 0xc5, 0x6a, 0xe8, + 0x77, 0x6c, 0x40, 0x2c, 0x70, 0xa0, 0x9c, 0x82, 0x0e, 0x65, 0x24, 0x79, + 0x7f, 0x77, 0x3c, 0x36, 0x88, 0x51, 0x3f, 0x18, 0xc6, 0x77, 0x3c, 0xb1, + 0xbb, 0xdd, 0x2d, 0xbb, 0x0e, 0x4e, 0x17, 0xf5, 0x1f, 0x90, 0x2d, 0x9b, + 0x8b, 0x63, 0xd5, 0x24, 0x9f, 0x95, 0xed, 0xf9, 0xb1, 0xe4, 0xb9, 0x2c, + 0xf1, 0xf3, 0x3a, 0xc8, 0xa6, 0x28, 0xf7, 0x42, 0x3d, 0xd5, 0x8e, 0x72, + 0xce, 0x94, 0x4a, 0x5b, 0x34, 0xe9, 0x51, 0x8b, 0x28, 0x74, 0x49, 0x55, + 0xd2, 0x1d, 0xa9, 0xa3, 0xa7, 0x80, 0xd4, 0x45, 0x75, 0xec, 0x06, 0xac, + 0x6f, 0xa7, 0xab, 0x27, 0x5f, 0x2d, 0xe3, 0xf8, 0x15, 0xea, 0x21, 0x16, + 0xa7, 0xa9, 0x26, 0x0b, 0x61, 0xb4, 0xc3, 0x11, 0x4c, 0xbb, 0x71, 0x14, + 0x06, 0xc4, 0xb1, 0x94, 0xd8, 0xe9, 0xfa, 0x8a, 0xef, 0x60, 0x55, 0x1b, + 0x47, 0x6d, 0xff, 0x58, 0x98, 0x68, 0x9f, 0xab, 0x48, 0xc9, 0x9d, 0xa0, + 0xdb, 0xc7, 0x89, 0xf9, 0x5a, 0x8c, 0xd9, 0x48, 0x3b, 0x0f, 0x4b, 0xe3, + 0xc3, 0x6e, 0x82, 0x7c, 0xf1, 0x98, 0xce, 0xf7, 0x8a, 0x00, 0xea, 0xb7, + 0x8f, 0x37, 0x8d, 0x59, 0x6a, 0xf1, 0xe2, 0x5e, 0xca, 0x09, 0xef, 0x1d, + 0x09, 0x98, 0xa1, 0x7a, 0x87, 0x21, 0xab, 0x13, 0xad, 0x62, 0x17, 0x17, + 0x65, 0xfe, 0x4b, 0x54, 0x29, 0x20, 0xe2, 0x45, 0xaa, 0x86, 0x20, 0xa2, + 0x30, 0xed, 0x84, 0x37, 0xd4, 0xc5, 0x3a, 0x8a, 0x25, 0x83, 0x97, 0xab, + 0x5f, 0x49, 0x5e, 0xfb, 0xbf, 0xf3, 0xe9, 0x4f, 0x1b, 0xe4, 0x9b, 0xcf, + 0xa7, 0x9d, 0xf2, 0x1c, 0xf9, 0x97, 0xc4, 0x0f, 0xbc, 0xa1, 0xbc, 0x0e, + 0x23, 0x42, 0x79, 0xec, 0x9e, 0x1d, 0x9f, 0x29, 0x85, 0xc8, 0xf8, 0xd2, + 0x35, 0x01, 0xce, 0x2b, 0x82, 0xd1, 0x7d, 0x19, 0x52, 0xeb, 0x24, 0x94, + 0x9c, 0x4a, 0x8a, 0x13, 0xc6, 0xc2, 0x59, 0x58, 0xf4, 0xfa, 0x7a, 0x8f, + 0xc5, 0x77, 0x1a, 0xd6, 0x65, 0x29, 0xb3, 0xf6, 0x92, 0x08, 0x2e, 0x35, + 0xf0, 0xad, 0x0f, 0x5e, 0x2d, 0xf1, 0x2c, 0x6a, 0x3e, 0x54, 0x3a, 0x2a, + 0xb0, 0xfa, 0x08, 0x58, 0x65, 0x73, 0xf5, 0x67, 0x14, 0xd5, 0x0a, 0x6a, + 0x79, 0xf1, 0xa8, 0x2a, 0x85, 0x3c, 0x02, 0x04, 0xf3, 0x4d, 0x1d, 0x00, + 0x4e, 0x45, 0xb0, 0x06, 0x37, 0xbe, 0xf7, 0x18, 0x45, 0x8f, 0x7c, 0xbb, + 0x31, 0xb8, 0xc6, 0x55, 0x5d, 0x92, 0xee, 0x13, 0xe5, 0x2b, 0x30, 0x01, + 0x60, 0xce, 0x2d, 0x99, 0xcf, 0x96, 0x39, 0x40, 0x6c, 0x4b, 0xf6, 0x06, + 0xa9, 0x16, 0x08, 0x07, 0x2b, 0x34, 0x64, 0xf2, 0x72, 0x99, 0x62, 0xa8, + 0x1d, 0xd5, 0x3c, 0x94, 0x83, 0x03, 0x41, 0xcc, 0xe8, 0xd8, 0x30, 0xae, + 0x25, 0x8f, 0x3b, 0xf9, 0x4b, 0x25, 0x0a, 0xfd, 0x5b, 0x6e, 0xb5, 0x63, + 0x6a, 0x1c, 0x5b, 0x98, 0x34, 0xd0, 0xaa, 0x67, 0xc9, 0x85, 0x1e, 0x22, + 0xca, 0x0f, 0x47, 0x94, 0x89, 0xda, 0x93, 0x4f, 0x9b, 0x30, 0xbd, 0x3e, + 0xde, 0x49, 0xe4, 0x70, 0x11, 0xf3, 0x21, 0xde, 0xc7, 0xb9, 0xa9, 0x3e, + 0x62, 0x7b, 0x3f, 0x22, 0x53, 0xf2, 0x6f, 0xfb, 0x14, 0x51, 0x9f, 0x72, + 0xb6, 0xe9, 0x8f, 0xcc, 0xe7, 0x83, 0xa8, 0x92, 0xba, 0x3b, 0x55, 0x17, + 0xf0, 0xad, 0x34, 0x11, 0x9d, 0xe7, 0x5e, 0x49, 0x96, 0xc2, 0x04, 0xbb, + 0x36, 0x79, 0x77, 0x38, 0x90, 0x52, 0x9d, 0x38, 0xee, 0x3b, 0xa0, 0x2b, + 0x35, 0x3d, 0xbe, 0xfb, 0x52, 0x6d, 0x68, 0xda, 0xa7, 0x00, 0x88, 0x49, + 0x3e, 0x53, 0x74, 0xa3, 0xed, 0x49, 0x20, 0xe0, 0xad, 0x28, 0xa2, 0x9d, + 0xdf, 0x3f, 0x8e, 0x00, 0xe9, 0x31, 0xe4, 0x51, 0x5e, 0x1a, 0xb0, 0x59, + 0x12, 0xc9, 0xf2, 0xc8, 0xba, 0xd7, 0xe0, 0x6f, 0x70, 0x9a, 0xb9, 0x81, + 0x6c, 0x8a, 0xc2, 0xcc, 0x9e, 0xa1, 0xe5, 0x81, 0x97, 0x67, 0x9f, 0xc0, + 0xe7, 0xe2, 0x05, 0x73, 0x10, 0x21, 0x96, 0x10, 0x8f, 0xd6, 0x2b, 0xd5, + 0x07, 0x74, 0xb4, 0x2e, 0x1c, 0x08, 0x2d, 0xc3, 0x5e, 0x13, 0x4b, 0x67, + 0x1b, 0x33, 0x6d, 0xea, 0x76, 0x38, 0xc0, 0x0e, 0x0f, 0xa5, 0xd5, 0xc6, + 0xe6, 0xa0, 0x9a, 0xd0, 0x01, 0x39, 0xca, 0x86, 0xe6, 0xbc, 0xec, 0x5a, + 0xb8, 0x44, 0x79, 0x01, 0x7a, 0x68, 0x94, 0xed, 0xa0, 0xb6, 0xeb, 0xd0, + 0x5b, 0x05, 0x3f, 0x58, 0xb2, 0xd9, 0x28, 0x27, 0xa5, 0x2c, 0xfa, 0xe6, + 0xbf, 0x6c, 0x48, 0xa2, 0xe0, 0x3e, 0x93, 0x87, 0x7d, 0x2a, 0x07, 0x4e, + 0x29, 0x9b, 0xbf, 0xfd, 0xd7, 0x8c, 0x2d, 0xf8, 0x1a, 0x2b, 0xec, 0xc9, + 0x6f, 0x12, 0x5a, 0x67, 0x27, 0x1a, 0x9e, 0x1c, 0x82, 0xee, 0x3c, 0x2c, + 0xd9, 0x63, 0x86, 0xe1, 0xa5, 0x74, 0x1f, 0x30, 0x1e, 0xa3, 0x4e, 0xb7, + 0xba, 0x0c, 0xd0, 0x0c, 0xe9, 0x8c, 0x20, 0x50, 0x68, 0x89, 0x47, 0xfe, + 0xc1, 0xc6, 0xc7, 0x3a, 0x05, 0xcc, 0xa5, 0x2c, 0x2b, 0x8b, 0x40, 0x12, + 0x44, 0x69, 0xd8, 0x1f, 0x25, 0x07, 0xac, 0x32, 0xbe, 0x47, 0xe2, 0xd9, + 0xa5, 0xe4, 0xc1, 0xb5, 0x55, 0x9f, 0x53, 0x61, 0x6d, 0x3d, 0xa0, 0x5b, + 0xc1, 0xa9, 0xf5, 0xf5, 0xc6, 0x94, 0x7f, 0x51, 0x0c, 0x0c, 0xa9, 0xa7, + 0x09, 0x42, 0xd5, 0xe3, 0x47, 0x79, 0xe4, 0xe6, 0x1f, 0x04, 0xbe, 0x25, + 0xd0, 0xa0, 0xff, 0xbd, 0xb0, 0x8d, 0x88, 0x91, 0x74, 0xba, 0x66, 0xe7, + 0xfa, 0xb5, 0x3e, 0xff, 0x21, 0x4e, 0x78, 0x29, 0x58, 0x9d, 0xf6, 0xd1, + 0x84, 0x67, 0x82, 0x91, 0xad, 0x00, 0x04, 0xce, 0x8f, 0xd2, 0xef, 0xce, + 0x9f, 0x88, 0xc3, 0xca, 0x74, 0x54, 0xf4, 0xef, 0xe8, 0xf8, 0x1f, 0x1d, + 0x81, 0x8f, 0x96, 0x4d, 0xbe, 0x6a, 0xf3, 0x9d, 0xac, 0xe0, 0x69, 0x48, + 0x31, 0x8a, 0x3c, 0x4f, 0x18, 0x48, 0xd6, 0xf5, 0x98, 0x5a, 0x74, 0x4d, + 0xc1, 0xc4, 0x90, 0xa3, 0xed, 0xbf, 0x13, 0x55, 0x6c, 0x17, 0x08, 0xda, + 0xa0, 0x6f, 0x3e, 0x1b, 0xb3, 0xd5, 0x23, 0xea, 0xcc, 0x8b, 0x68, 0x66, + 0xe2, 0x59, 0xaf, 0x6a, 0x6c, 0x6b, 0x87, 0x81, 0x91, 0xac, 0xc7, 0x6b, + 0x21, 0x4b, 0x2d, 0xa0, 0xdd, 0x9e, 0xdc, 0xd9, 0x66, 0x5b, 0x51, 0xac, + 0x61, 0xd6, 0xc8, 0xf9, 0x87, 0x11, 0xcd, 0xdd, 0xeb, 0xde, 0x1e, 0x04, + 0x9b, 0xbd, 0x20, 0xaf, 0x89, 0x85, 0x23, 0x8b, 0x0c, 0xb9, 0x74, 0x1b, + 0xc2, 0xe3, 0x00, 0x0a, 0xc3, 0x9f, 0x11, 0xd3, 0x79, 0x43, 0x67, 0xd7, + 0xbb, 0x50, 0x3b, 0x07, 0x44, 0xf0, 0xed, 0x32, 0x72, 0x23, 0xcb, 0x7c, + 0x9a, 0x25, 0xc4, 0x41, 0xe1, 0x7d, 0x5b, 0x5b, 0xa4, 0x75, 0xc0, 0xaf, + 0x96, 0xea, 0x70, 0xbe, 0xc1, 0x9c, 0x2a, 0xaf, 0xb1, 0x63, 0xed, 0xb0, + 0x01, 0xf2, 0x85, 0x06, 0x2e, 0x47, 0xf7, 0xc0, 0xa7, 0xa6, 0xae, 0x77, + 0x3e, 0x29, 0x8d, 0xd0, 0xfa, 0xc0, 0xc2, 0xb7, 0x51, 0x9a, 0xd9, 0xa7, + 0xf7, 0x9d, 0x01, 0x8a, 0x2f, 0xa7, 0x28, 0x73, 0x4b, 0xba, 0x77, 0x6d, + 0xd2, 0xd3, 0x5b, 0x35, 0x0c, 0x5f, 0xf9, 0xbf, 0x60, 0x75, 0xdf, 0x55, + 0x86, 0xef, 0xee, 0x91, 0xcf, 0x94, 0x45, 0xd7, 0xb5, 0x4d, 0x16, 0x6f, + 0x19, 0x44, 0x08, 0xee, 0x1e, 0x20, 0xbf, 0x5c, 0x4d, 0xb9, 0xce, 0x86, + 0x0f, 0x56, 0xa7, 0xff, 0x97, 0xcd, 0x23, 0xd5, 0x1e, 0xf9, 0xfe, 0x50, + 0x82, 0x5e, 0x19, 0x89, 0x4e, 0x78, 0x0b, 0xa6, 0xd7, 0xe7, 0x16, 0xd1, + 0x28, 0x8a, 0xb5, 0x26, 0xab, 0x39, 0x3a, 0xee, 0xf7, 0x74, 0x6e, 0x7e, + 0xc6, 0x85, 0x63, 0xfd, 0xbe, 0xba, 0xac, 0x8d, 0x02, 0xa1, 0xaa, 0x11, + 0x3e, 0x2d, 0x17, 0x14, 0x4e, 0xc2, 0xf8, 0x13, 0x5e, 0x28, 0x99, 0x7b, + 0x9f, 0x3a, 0xa4, 0x91 +}; + +#endif /* WOLFSPDM_TEST_CERTS_MLDSA_H */ diff --git a/test/unit_test.c b/test/unit_test.c index 5826a3a..8de8502 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -31,6 +31,9 @@ #ifndef WOLFSPDM_NO_CERT #include "test_certs.h" #endif +#ifdef WOLFSPDM_HAVE_MLDSA + #include "test_certs_mldsa.h" +#endif #include #include #include @@ -518,6 +521,22 @@ static int test_session_state(void) ASSERT_EQ(wolfSPDM_IsConnected(ctx), 1, "Should be connected"); ASSERT_EQ(wolfSPDM_GetSessionId(ctx), (word32)0xAABBCCDD, "SessionId wrong"); ASSERT_EQ(wolfSPDM_GetNegotiatedVersion(ctx), SPDM_VERSION_12, "Version wrong"); + ASSERT_EQ(wolfSPDM_GetVersion_Negotiated(ctx), SPDM_VERSION_12, + "Old version getter"); + + /* Reserved IDs, and low bytes that read as an SPDM version on the wire */ + ASSERT_EQ(wolfSPDM_SetRequesterSessionId(NULL, 0x0002), + WOLFSPDM_E_INVALID_ARG, "NULL ctx"); + ASSERT_EQ(wolfSPDM_SetRequesterSessionId(ctx, 0x0000), + WOLFSPDM_E_INVALID_ARG, "Reserved 0x0000"); + ASSERT_EQ(wolfSPDM_SetRequesterSessionId(ctx, 0xFFFF), + WOLFSPDM_E_INVALID_ARG, "Reserved 0xFFFF"); + ASSERT_EQ(wolfSPDM_SetRequesterSessionId(ctx, 0x0012), + WOLFSPDM_E_INVALID_ARG, "Low byte 0x12"); + ASSERT_EQ(wolfSPDM_SetRequesterSessionId(ctx, 0xAB1F), + WOLFSPDM_E_INVALID_ARG, "Low byte 0x1F"); + ASSERT_SUCCESS(wolfSPDM_SetRequesterSessionId(ctx, 0x1020)); + ASSERT_EQ(ctx->reqSessionId, 0x1020, "ReqSessionID stored"); TEST_CTX_FREE(); TEST_PASS(); @@ -654,7 +673,7 @@ static int test_key_exchange_rsp_hmac_check(void) * (e.g. ECC_TIMING_RESISTANT) require an RNG on the ECDH private * key for blinding; ensure one is attached for wc_ecc_shared_secret. */ ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); - ASSERT_SUCCESS(wc_ecc_set_rng(&ctx->ephemeralKey, &ctx->rng)); + ASSERT_SUCCESS(wc_ecc_set_rng(WOLFSPDM_EPH_ECC(ctx), &ctx->rng)); ASSERT_SUCCESS(wolfSPDM_ExportEphemeralPubKey(ctx, ourPubX, &ourXSz, ourPubY, &ourYSz)); @@ -2449,7 +2468,7 @@ static int test_encrypt_internal_null_args(void) #ifndef WOLFSPDM_NO_MCTP static int test_encrypt_decrypt_roundtrip(void) { - byte plain[16] = "Hello SPDM test!"; + byte plain[] = "Hello SPDM test!"; static byte enc[512]; static byte dec[256]; word32 encSz = sizeof(enc); @@ -2492,7 +2511,7 @@ static int test_encrypt_decrypt_roundtrip(void) #ifdef WOLFSPDM_TCG static int test_encrypt_decrypt_roundtrip_tcg(void) { - byte plain[16] = "TCG encrypt tst!"; + byte plain[] = "TCG encrypt tst!"; static byte enc[512]; static byte dec[256]; word32 encSz = sizeof(enc); @@ -3205,7 +3224,7 @@ static int test_parse_capabilities(void) static int test_negotiate_algorithms_roundtrip(void) { - byte req[48]; + byte req[WOLFSPDM_NEG_ALGO_SZ]; byte rsp[52]; word32 reqSz = sizeof(req); TEST_CTX_SETUP_V12(); @@ -3386,6 +3405,447 @@ static int test_validate_cert_chain(void) } #endif /* !WOLFSPDM_NO_CERT */ +#if defined(WOLFSPDM_HAVE_MLDSA) || defined(WOLFSPDM_HAVE_MLKEM) +/* ALGORITHMS response: fixed fields, then DHE, AEAD, ReqBaseAsym, KeySchedule + * and KEMAlg structs */ +static word32 test_algo_rsp(byte* rsp, byte ver, word32 baseAsym, word32 pqc, + word16 dhe, word16 kem) +{ + XMEMSET(rsp, 0, 56); + rsp[0] = ver; + rsp[1] = SPDM_ALGORITHMS; + rsp[2] = 5; + SPDM_Set16LE(&rsp[4], 56); + rsp[6] = 0x01; + rsp[7] = 0x02; + SPDM_Set32LE(&rsp[12], baseAsym); + SPDM_Set32LE(&rsp[16], SPDM_HASH_ALGO_SHA_384); + SPDM_Set32LE(&rsp[20], pqc); + rsp[36] = SPDM_ALG_TYPE_DHE; + rsp[37] = 0x20; + SPDM_Set16LE(&rsp[38], dhe); + rsp[40] = SPDM_ALG_TYPE_AEAD; + rsp[41] = 0x20; + SPDM_Set16LE(&rsp[42], SPDM_AEAD_ALGO_AES_256_GCM); + rsp[44] = SPDM_ALG_TYPE_REQ_BASE_ASYM; + rsp[45] = 0x20; + rsp[48] = SPDM_ALG_TYPE_KEY_SCHEDULE; + rsp[49] = 0x20; + SPDM_Set16LE(&rsp[50], SPDM_KEY_SCHEDULE_SPDM); + rsp[52] = SPDM_ALG_TYPE_KEM; + rsp[53] = 0x20; + SPDM_Set16LE(&rsp[54], kem); + return 56; +} + +static int test_negotiate_algorithms_pqc(void) +{ + byte req[WOLFSPDM_NEG_ALGO_SZ]; + byte rsp[56]; + word32 reqSz = sizeof(req); + word32 rspSz; +#ifdef WOLFSPDM_HAVE_MLKEM + /* One ML-KEM set this wolfSSL has */ + word16 kem = (word16)(WOLFSPDM_MLKEM_SETS & (~WOLFSPDM_MLKEM_SETS + 1)); +#endif +#ifdef WOLFSPDM_HAVE_MLDSA + word32 pqc = WOLFSPDM_MLDSA_SETS & (~WOLFSPDM_MLDSA_SETS + 1); +#endif + TEST_CTX_SETUP(); + + printf("test_negotiate_algorithms_pqc...\n"); + + /* Before 1.4 the request carries neither PqcAsymAlgo nor KEMAlg */ + ctx->spdmVersion = SPDM_VERSION_13; + ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, req, &reqSz)); + ASSERT_EQ(reqSz, (word32)48, "classical request below 1.4"); + ASSERT_EQ(SPDM_Get32LE(&req[16]), 0, "PqcAsymAlgo reserved below 1.4"); + + ctx->spdmVersion = SPDM_VERSION_14; + reqSz = sizeof(req); + ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, req, &reqSz)); + ASSERT_EQ(SPDM_Get32LE(&req[8]), SPDM_ASYM_ALGO_ECDSA_P384, + "ECDSA stays on offer"); +#ifdef WOLFSPDM_HAVE_MLDSA + ASSERT_EQ(SPDM_Get32LE(&req[16]), WOLFSPDM_MLDSA_SETS, + "ML-DSA sets offered at 1.4"); +#endif +#ifdef WOLFSPDM_HAVE_MLKEM + ASSERT_EQ(reqSz, (word32)52, "KEMAlg appended at 1.4"); + ASSERT_EQ(req[2], 5, "five AlgStructs"); + ASSERT_EQ(req[32], SPDM_ALG_TYPE_DHE, "DHE on offer"); + ASSERT_EQ(req[48], SPDM_ALG_TYPE_KEM, "KEMAlg type"); + ASSERT_EQ(SPDM_Get16LE(&req[50]), WOLFSPDM_MLKEM_SETS, + "every built ML-KEM set offered"); +#endif + + ASSERT_EQ(wolfSPDM_SetKeyExchangePref(NULL, 1, 0), WOLFSPDM_E_INVALID_ARG, + "NULL ctx"); + ASSERT_EQ(wolfSPDM_SetKeyExchangePref(ctx, 0, 0), WOLFSPDM_E_INVALID_ARG, + "nothing to offer"); + ASSERT_EQ(wolfSPDM_SetKeyExchangePref(ctx, 1, 0x0008), + WOLFSPDM_E_INVALID_ARG, "undefined ML-KEM bit"); + + /* ECDSA and ECDHE at 1.4 */ + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, SPDM_ASYM_ALGO_ECDSA_P384, 0, + SPDM_DHE_ALGO_SECP384R1, 0); + ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz)); + ASSERT_EQ(wolfSPDM_SigSize(ctx), WOLFSPDM_ECC_SIG_SIZE, "ECDSA SigLen"); + +#ifdef WOLFSPDM_HAVE_MLDSA + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, 0, pqc, + SPDM_DHE_ALGO_SECP384R1, 0); + ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz)); + ASSERT_EQ(ctx->pqcAsymSel, pqc, "ML-DSA selected"); + ASSERT_NE(wolfSPDM_SigSize(ctx), WOLFSPDM_ECC_SIG_SIZE, "ML-DSA SigLen"); + + /* One signature algorithm across BaseAsymSel and PqcAsymSel */ + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, SPDM_ASYM_ALGO_ECDSA_P384, + pqc, SPDM_DHE_ALGO_SECP384R1, 0); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz), + WOLFSPDM_E_ALGO_MISMATCH, "both signature fields set"); + ASSERT_EQ(ctx->pqcAsymSel, 0, "failed negotiation clears the selection"); + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, 0, + SPDM_PQC_ASYM_ALGO_ML_DSA_44 | SPDM_PQC_ASYM_ALGO_ML_DSA_65, + SPDM_DHE_ALGO_SECP384R1, 0); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz), + WOLFSPDM_E_ALGO_MISMATCH, "two ML-DSA sets"); + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, 0, 0x00000008, + SPDM_DHE_ALGO_SECP384R1, 0); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz), + WOLFSPDM_E_ALGO_MISMATCH, "unknown PQC algorithm"); + + /* PqcAsymSel is reserved before 1.4 */ + ctx->spdmVersion = SPDM_VERSION_13; + rspSz = test_algo_rsp(rsp, SPDM_VERSION_13, SPDM_ASYM_ALGO_ECDSA_P384, + pqc, SPDM_DHE_ALGO_SECP384R1, 0); + ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz)); + ASSERT_EQ(ctx->pqcAsymSel, 0, "PqcAsymSel ignored below 1.4"); + ctx->spdmVersion = SPDM_VERSION_14; +#endif + +#ifdef WOLFSPDM_HAVE_MLKEM + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, SPDM_ASYM_ALGO_ECDSA_P384, 0, + 0, kem); + ASSERT_SUCCESS(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz)); + ASSERT_EQ(ctx->kemAlgSel, kem, "ML-KEM selected"); + + /* Exactly one key exchange, from what was offered */ + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, SPDM_ASYM_ALGO_ECDSA_P384, 0, + SPDM_DHE_ALGO_SECP384R1, kem); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz), + WOLFSPDM_E_ALGO_MISMATCH, "hybrid DHE and KEM"); + ASSERT_EQ(ctx->kemAlgSel, 0, "failed negotiation clears the KEM"); + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, SPDM_ASYM_ALGO_ECDSA_P384, 0, + 0, 0); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz), + WOLFSPDM_E_ALGO_MISMATCH, "no key exchange"); + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, SPDM_ASYM_ALGO_ECDSA_P384, 0, + 0, SPDM_KEM_ALGO_ML_KEM_512 | SPDM_KEM_ALGO_ML_KEM_768); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz), + WOLFSPDM_E_ALGO_MISMATCH, "two ML-KEM sets"); + + ASSERT_SUCCESS(wolfSPDM_SetKeyExchangePref(ctx, 1, 0)); + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, SPDM_ASYM_ALGO_ECDSA_P384, 0, + 0, kem); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz), + WOLFSPDM_E_ALGO_MISMATCH, "ML-KEM not offered"); + + /* KEM only: no DHE struct, and no silent fallback below 1.4 */ + ASSERT_SUCCESS(wolfSPDM_SetKeyExchangePref(ctx, 0, kem)); + reqSz = sizeof(req); + ASSERT_SUCCESS(wolfSPDM_BuildNegotiateAlgorithms(ctx, req, &reqSz)); + ASSERT_EQ(reqSz, (word32)48, "four AlgStructs"); + ASSERT_EQ(req[32], SPDM_ALG_TYPE_AEAD, "no DHE struct"); + rspSz = test_algo_rsp(rsp, SPDM_VERSION_14, SPDM_ASYM_ALGO_ECDSA_P384, 0, + SPDM_DHE_ALGO_SECP384R1, 0); + ASSERT_EQ(wolfSPDM_ParseAlgorithms(ctx, rsp, rspSz), + WOLFSPDM_E_ALGO_MISMATCH, "DHE not offered"); + ctx->spdmVersion = SPDM_VERSION_13; + reqSz = sizeof(req); + ASSERT_EQ(wolfSPDM_BuildNegotiateAlgorithms(ctx, req, &reqSz), + WOLFSPDM_E_ALGO_MISMATCH, "KEM only below 1.4"); +#else + ASSERT_EQ(wolfSPDM_SetKeyExchangePref(ctx, 1, SPDM_KEM_ALGO_ML_KEM_768), + WOLFSPDM_E_INVALID_ARG, "ML-KEM not built in"); +#endif + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* WOLFSPDM_HAVE_MLDSA || WOLFSPDM_HAVE_MLKEM */ + +#ifdef WOLFSPDM_HAVE_MLDSA +#ifndef WOLFSSL_MLDSA_VERIFY_ONLY +/* Sign M = combined_spdm_prefix || message_hash with an ML-DSA key of the + * given set, context = spdm_context, and verify it through the dispatcher */ +static int test_mldsa_verify_one(word32 set, byte level, word32 sigSz) +{ + static const char label[] = "responder-key_exchange_rsp signing"; + static MlDsaKey key; + static byte sig[WOLFSPDM_MLDSA87_SIG_SIZE]; + byte hash[WOLFSPDM_HASH_SIZE]; + byte m[148]; + word32 labelSz = (word32)sizeof(label) - 1; + word32 pubSz = WOLFSPDM_RSP_PUBKEY_SZ; + word32 outSz = sizeof(sig); + word32 i; + WC_RNG rng; + TEST_CTX_SETUP(); + + ctx->spdmVersion = SPDM_VERSION_14; + ctx->pqcAsymSel = set; + ASSERT_SUCCESS(wc_InitRng(&rng)); + ASSERT_SUCCESS(wc_MlDsaKey_Init(&key, NULL, INVALID_DEVID)); + ASSERT_SUCCESS(wc_MlDsaKey_SetParams(&key, level)); + ASSERT_SUCCESS(wc_MlDsaKey_MakeKey(&key, &rng)); + ASSERT_SUCCESS(wc_MlDsaKey_ExportPubRaw(&key, ctx->rspPubKey, &pubSz)); + ctx->rspPubKeyLen = pubSz; + ctx->flags.hasRspPubKey = 1; + + XMEMSET(hash, 0x5A, sizeof(hash)); + for (i = 0; i < 4; i++) { + XMEMCPY(&m[i * 16], "dmtf-spdm-v1.4.*", 16); + } + XMEMSET(&m[64], 0, 36 - labelSz); + XMEMCPY(&m[100 - labelSz], label, labelSz); + XMEMCPY(&m[100], hash, sizeof(hash)); + ASSERT_SUCCESS(wc_MlDsaKey_SignCtx(&key, (const byte*)label, + (byte)labelSz, sig, &outSz, m, sizeof(m), &rng)); + ASSERT_EQ(outSz, sigSz, "SigLen of the set"); + ASSERT_EQ(wolfSPDM_SigSize(ctx), sigSz, "negotiated SigLen"); + + ASSERT_SUCCESS(wolfSPDM_VerifyRspSig(ctx, label, labelSz, hash, sig, + outSz)); + sig[10] ^= 0x01; + ASSERT_EQ(wolfSPDM_VerifyRspSig(ctx, label, labelSz, hash, sig, outSz), + WOLFSPDM_E_BAD_SIGNATURE, "tampered signature"); + sig[10] ^= 0x01; + hash[0] ^= 0x01; + ASSERT_EQ(wolfSPDM_VerifyRspSig(ctx, label, labelSz, hash, sig, outSz), + WOLFSPDM_E_BAD_SIGNATURE, "other transcript"); + hash[0] ^= 0x01; + ASSERT_EQ(wolfSPDM_VerifyRspSig(ctx, "responder-challenge_auth signing", + 32, hash, sig, outSz), WOLFSPDM_E_BAD_SIGNATURE, + "spdm_context binds the signature"); + ASSERT_EQ(wolfSPDM_VerifyRspSig(ctx, label, labelSz, hash, sig, + outSz - 1), WOLFSPDM_E_BAD_SIGNATURE, "short signature"); +#ifdef WOLFSPDM_TCG + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ASSERT_EQ(wolfSPDM_SigSize(ctx), WOLFSPDM_ECC_SIG_SIZE, + "TCG binding is ECDSA only"); + ctx->mode = WOLFSPDM_MODE_AUTO; +#endif + + wc_MlDsaKey_Free(&key); + wc_FreeRng(&rng); + TEST_CTX_FREE(); + return 0; +} + +static int test_mldsa_verify(void) +{ + printf("test_mldsa_verify...\n"); +#ifndef WOLFSSL_NO_ML_DSA_44 + if (test_mldsa_verify_one(SPDM_PQC_ASYM_ALGO_ML_DSA_44, WC_ML_DSA_44, + WOLFSPDM_MLDSA44_SIG_SIZE) != 0) { + return -1; + } +#endif +#ifndef WOLFSSL_NO_ML_DSA_65 + if (test_mldsa_verify_one(SPDM_PQC_ASYM_ALGO_ML_DSA_65, WC_ML_DSA_65, + WOLFSPDM_MLDSA65_SIG_SIZE) != 0) { + return -1; + } +#endif +#ifndef WOLFSSL_NO_ML_DSA_87 + if (test_mldsa_verify_one(SPDM_PQC_ASYM_ALGO_ML_DSA_87, WC_ML_DSA_87, + WOLFSPDM_MLDSA87_SIG_SIZE) != 0) { + return -1; + } +#endif + TEST_PASS(); +} +#endif /* !WOLFSSL_MLDSA_VERIFY_ONLY */ + +/* The KEY_EXCHANGE_RSP bounds come from the negotiated SigLen */ +static int test_key_exchange_rsp_mldsa_sigsize(void) +{ + byte buf[300]; + TEST_CTX_SETUP(); + + printf("test_key_exchange_rsp_mldsa_sigsize...\n"); + ctx->spdmVersion = SPDM_VERSION_14; + ctx->pqcAsymSel = WOLFSPDM_MLDSA_SETS & (~WOLFSPDM_MLDSA_SETS + 1); + ctx->flags.hasRspPubKey = 1; + XMEMSET(buf, 0, sizeof(buf)); + buf[0] = SPDM_VERSION_14; + buf[1] = SPDM_KEY_EXCHANGE_RSP; + /* OpaqueLength 0: room for an ECDSA signature and HMAC, not ML-DSA */ + ASSERT_EQ(wolfSPDM_ParseKeyExchangeRsp(ctx, buf, 138 + + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE), WOLFSPDM_E_BUFFER_SMALL, + "ML-DSA SigLen bounds KEY_EXCHANGE_RSP"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +#ifndef WOLFSSL_NO_ML_DSA_44 +static int test_load_mldsa_chain(WOLFSPDM_CTX* ctx) +{ + word32 total = WOLFSPDM_CERT_CHAIN_HDR_SZ + + (word32)sizeof(test_mldsa44_chain_der); + + if (total > WOLFSPDM_MAX_CERT_CHAIN) { + return -1; + } + SPDM_Set16LE(ctx->certChain, (word16)total); + ctx->certChain[2] = 0; + ctx->certChain[3] = 0; + if (wolfSPDM_Sha384Hash(ctx->certChain + 4, test_mldsa44_chain_der, + TEST_MLDSA44_CA_SZ, NULL, 0, NULL, 0) != 0) { + return -1; + } + XMEMCPY(ctx->certChain + WOLFSPDM_CERT_CHAIN_HDR_SZ, + test_mldsa44_chain_der, sizeof(test_mldsa44_chain_der)); + ctx->certChainLen = total; + return 0; +} + +/* Every link of an ML-DSA-44 chain verifies up to the root */ +static int test_validate_cert_chain_mldsa(void) +{ + TEST_CTX_SETUP(); + + printf("test_validate_cert_chain_mldsa...\n"); + ctx->spdmVersion = SPDM_VERSION_14; + ctx->pqcAsymSel = SPDM_PQC_ASYM_ALGO_ML_DSA_44; + ASSERT_SUCCESS(test_load_mldsa_chain(ctx)); + ASSERT_SUCCESS(wolfSPDM_SetTrustedCAs(ctx, test_mldsa44_chain_der, + TEST_MLDSA44_CA_SZ)); + ASSERT_SUCCESS(wolfSPDM_ValidateCertChain(ctx)); + ASSERT_EQ(ctx->rspPubKeyLen, (word32)sizeof(test_mldsa44_leaf_pub), + "ML-DSA-44 leaf key size"); + ASSERT_EQ(memcmp(ctx->rspPubKey, test_mldsa44_leaf_pub, + sizeof(test_mldsa44_leaf_pub)), 0, "leaf key installed"); + + /* A forged leaf signature breaks the chain */ + ctx->certChain[ctx->certChainLen - 2] ^= 0x01; + ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_FAIL, + "forged ML-DSA signature"); + ctx->certChain[ctx->certChainLen - 2] ^= 0x01; + + /* The leaf must carry the negotiated algorithm */ + ctx->pqcAsymSel = 0; + ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_PARSE, + "ML-DSA leaf with ECDSA negotiated"); +#ifndef WOLFSSL_NO_ML_DSA_65 + ctx->pqcAsymSel = SPDM_PQC_ASYM_ALGO_ML_DSA_65; + ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_PARSE, + "ML-DSA-44 leaf with ML-DSA-65 negotiated"); +#endif + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSSL_NO_ML_DSA_44 */ +#endif /* WOLFSPDM_HAVE_MLDSA */ + +#if defined(WOLFSPDM_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + defined(WOLFSSL_WC_ML_KEM_768) +/* ML-KEM-768: the request carries ek, the responder encapsulates to it and + * decapsulation recovers the same secret */ +static int test_mlkem_key_exchange(void) +{ + static MlKemKey peer; + byte req[WOLFSPDM_KEY_EX_TX_SZ]; + byte rsp[300]; + byte ct[1568]; + byte ss[32]; + byte x[WOLFSPDM_ECC_KEY_SIZE]; + byte y[WOLFSPDM_ECC_KEY_SIZE]; + word32 xSz = sizeof(x); + word32 ySz = sizeof(y); + word32 reqSz = sizeof(req); + word32 ctSz = 0; + WC_RNG rng; + TEST_CTX_SETUP(); + + printf("test_mlkem_key_exchange...\n"); + ctx->spdmVersion = SPDM_VERSION_14; + ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, req, &reqSz)); + ASSERT_EQ(reqSz, (word32)(40 + 1184 + 22), "ek replaces the ECDHE point"); + ASSERT_EQ(ctx->flags.ephemeralIsKem, 1, "ML-KEM key live"); + ASSERT_EQ(wolfSPDM_ExportEphemeralPubKey(ctx, x, &xSz, y, &ySz), + WOLFSPDM_E_BAD_STATE, "no ECDH point from an ML-KEM key"); + + ASSERT_SUCCESS(wc_InitRng(&rng)); + ASSERT_SUCCESS(wc_MlKemKey_Init(&peer, WC_ML_KEM_768, NULL, + INVALID_DEVID)); + ASSERT_SUCCESS(wc_MlKemKey_DecodePublicKey(&peer, &req[40], 1184)); + ASSERT_SUCCESS(wc_MlKemKey_CipherTextSize(&peer, &ctSz)); + ASSERT_SUCCESS(wc_MlKemKey_Encapsulate(&peer, ct, ss, &rng)); + ASSERT_EQ(wolfSPDM_MlKemDecapsulate(ctx, ct, ctSz - 1), + WOLFSPDM_E_KEY_EXCHANGE, "ciphertext size of the set"); + ASSERT_SUCCESS(wolfSPDM_MlKemDecapsulate(ctx, ct, ctSz)); + ASSERT_EQ(ctx->sharedSecretSz, (word32)sizeof(ss), "32-byte secret"); + ASSERT_EQ(memcmp(ctx->sharedSecret, ss, sizeof(ss)), 0, "shared secret"); + + /* KEY_EXCHANGE_RSP bounds use the ciphertext size */ + XMEMSET(rsp, 0, sizeof(rsp)); + rsp[0] = SPDM_VERSION_14; + rsp[1] = SPDM_KEY_EXCHANGE_RSP; + ASSERT_EQ(wolfSPDM_ParseKeyExchangeRsp(ctx, rsp, sizeof(rsp)), + WOLFSPDM_E_BUFFER_SMALL, "ciphertext past the response"); + + /* Renegotiating ECDHE replaces the ML-KEM key */ + ctx->kemAlgSel = 0; + reqSz = sizeof(req); + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, req, &reqSz)); + ASSERT_EQ(reqSz, (word32)(40 + WOLFSPDM_ECC_POINT_SIZE + 22), + "ECDHE request"); + ASSERT_EQ(ctx->flags.ephemeralIsKem, 0, "ECDHE key live"); + ASSERT_EQ(wolfSPDM_MlKemDecapsulate(ctx, ct, ctSz), WOLFSPDM_E_BAD_STATE, + "no decapsulation with an ECDHE key"); + + /* A request buffer too small for ek */ + ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; + reqSz = 40 + 22 + 512; + ASSERT_EQ(wolfSPDM_BuildKeyExchange(ctx, req, &reqSz), + WOLFSPDM_E_BUFFER_SMALL, "ek does not fit"); + ASSERT_EQ(ctx->flags.ephemeralKeyInit, 0, "failed key freed"); + + wc_MlKemKey_Free(&peer); + wc_FreeRng(&rng); + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* WOLFSPDM_HAVE_MLKEM */ + +#ifndef WOLFSPDM_NO_CERT +/* Unchunked, a request larger than the responder's DataTransferSize is + * refused before it reaches the transport */ +static int test_clear_exchange_dts(void) +{ + byte req[64]; + byte rsp[16]; + word32 rspSz = sizeof(rsp); + TEST_CTX_SETUP_V12(); + + printf("test_clear_exchange_dts...\n"); + XMEMSET(req, 0, sizeof(req)); + wolfSPDM_SetIO(ctx, dummy_io_cb, NULL); + ctx->dataTransferSize = 42; + ctx->rspCaps = 0; + ASSERT_EQ(wolfSPDM_ClearExchange(ctx, req, sizeof(req), rsp, &rspSz), + WOLFSPDM_E_BUFFER_SMALL, "request over the responder DTS"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif + #ifndef WOLFSPDM_NO_HEARTBEAT static int test_heartbeat_msgs(void) { @@ -4425,9 +4885,13 @@ static int test_chunk_transfers(void) ctx->maxSpdmMsgSize = 4096; #if WOLFSPDM_DATA_TRANSFER_SIZE >= 300 - /* Without CHUNK_CAP every message goes whole */ + /* Without CHUNK_CAP every message goes whole, within the responder DTS */ ctx->rspCaps &= ~(word32)SPDM_CAP_CHUNK_CAP; + ctx->dataTransferSize = 200; + ASSERT_EQ(test_chunk_echo(ctx, 0, 300), WOLFSPDM_E_BUFFER_SMALL, + "Unchunked request above the responder DTS"); g_peerDts = sizeof(g_peerReq); + ctx->dataTransferSize = g_peerDts; ASSERT_SUCCESS(test_chunk_echo(ctx, 0, 300)); ASSERT_SUCCESS(test_chunk_echo(ctx, 1, 300)); #endif @@ -4593,6 +5057,23 @@ int main(void) test_parse_certificate(); test_mutual_auth_rejected_in_standard_mode(); test_validate_cert_chain(); + test_clear_exchange_dts(); +#endif +#if defined(WOLFSPDM_HAVE_MLDSA) || defined(WOLFSPDM_HAVE_MLKEM) + test_negotiate_algorithms_pqc(); +#endif +#ifdef WOLFSPDM_HAVE_MLDSA +#ifndef WOLFSSL_MLDSA_VERIFY_ONLY + test_mldsa_verify(); +#endif + test_key_exchange_rsp_mldsa_sigsize(); +#ifndef WOLFSSL_NO_ML_DSA_44 + test_validate_cert_chain_mldsa(); +#endif +#endif +#if defined(WOLFSPDM_HAVE_MLKEM) && !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + defined(WOLFSSL_WC_ML_KEM_768) + test_mlkem_key_exchange(); #endif #ifdef WOLFSPDM_TCG test_encrypt_decrypt_roundtrip_tcg(); diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index 79cefe8..a6fe8b2 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -56,11 +56,19 @@ typedef enum { * // ... same as above ... * wolfSPDM_Free(ctx); * - * WOLFSPDM_CTX is ~22KB. Use static global on small-stack systems. - * SecuredExchange call chain uses ~20KB stack for message buffers. */ + * WOLFSPDM_CTX is ~17KB (~60KB with ML-DSA). Use static global on + * small-stack systems. SecuredExchange call chain uses ~20KB stack for + * message buffers. */ -/* Compile-time buffer size for static allocation (32KB, runtime-verified) */ -#define WOLFSPDM_CTX_STATIC_SIZE 32768 +/* Compile-time buffer size for static allocation (runtime-verified); ML-DSA + * chains and transcripts, and MlKemKey with WOLFSSL_MLKEM_CACHE_A, need more */ +#if defined(WOLFSPDM_HAVE_MLDSA) + #define WOLFSPDM_CTX_STATIC_SIZE 73728 +#elif defined(WOLFSPDM_HAVE_MLKEM) + #define WOLFSPDM_CTX_STATIC_SIZE 40960 +#else + #define WOLFSPDM_CTX_STATIC_SIZE 32768 +#endif struct WOLFSPDM_CTX; typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; @@ -114,6 +122,10 @@ WOLFSPDM_API int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, #endif /* Cap the negotiated version (0x12-0x14, 0 = build default) */ WOLFSPDM_API int wolfSPDM_SetMaxVersion(WOLFSPDM_CTX* ctx, byte maxVersion); +/* ReqSessionID for KEY_EXCHANGE and PSK_EXCHANGE (default 0x0001); rejects + * 0x0000, 0xFFFF and low bytes 0x10-0x1F */ +WOLFSPDM_API int wolfSPDM_SetRequesterSessionId(WOLFSPDM_CTX* ctx, + word16 reqSessionId); #ifndef WOLFSPDM_NO_CERT /* Standard (certificate) mode, used by Connect when no vendor mode is set. @@ -122,6 +134,12 @@ WOLFSPDM_API int wolfSPDM_SetMaxVersion(WOLFSPDM_CTX* ctx, byte maxVersion); WOLFSPDM_API int wolfSPDM_SetTrustedCAs(WOLFSPDM_CTX* ctx, const byte* derCerts, word32 derCertsSz); WOLFSPDM_API int wolfSPDM_AllowUntrustedCerts(WOLFSPDM_CTX* ctx, int allow); +/* Key exchanges offered in NEGOTIATE_ALGORITHMS: ECDHE P-384 when advDhe is + * set, plus the SPDM_KEM_ALGO_ML_KEM_* sets in kemMask at SPDM 1.4. The + * default offers ECDHE and every ML-KEM set built in; a KEM-only preference + * fails below 1.4 rather than falling back. */ +WOLFSPDM_API int wolfSPDM_SetKeyExchangePref(WOLFSPDM_CTX* ctx, int advDhe, + word16 kemMask); #endif /* Session establishment */ @@ -195,6 +213,8 @@ WOLFSPDM_API int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll); /* Session info */ WOLFSPDM_API word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx); WOLFSPDM_API byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx); +/* Older name for wolfSPDM_GetNegotiatedVersion */ +WOLFSPDM_API byte wolfSPDM_GetVersion_Negotiated(WOLFSPDM_CTX* ctx); /* Param1 of the last SPDM ERROR from the responder, 0 if none */ WOLFSPDM_API byte wolfSPDM_GetLastPeerError(WOLFSPDM_CTX* ctx); #ifdef WOLFSPDM_TCG diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index dfc401c..953e422 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -131,10 +131,6 @@ extern "C" { * WOLFSPDM_SECURED_PAD of AppDataLength, MCTP type and padding */ #define WOLFSPDM_AEAD_OVERHEAD (32 + WOLFSPDM_SECURED_PAD) -/* ----- Buffer/Message Size Limits ----- */ - -#define WOLFSPDM_MAX_MSG_SIZE 4096 /* Maximum SPDM message size */ -#define WOLFSPDM_MAX_TRANSCRIPT 4096 /* Maximum transcript buffer */ #define WOLFSPDM_RANDOM_SIZE 32 /* Random data in KEY_EXCHANGE */ /* ----- MCTP Transport Constants ----- */ @@ -158,12 +154,6 @@ extern "C" { /* ----- Buffer Size Macros (overridable) ----- */ -#ifndef WOLFSPDM_KEY_EX_TX_SZ -#define WOLFSPDM_KEY_EX_TX_SZ 192 /* KEY_EXCHANGE request (~158 bytes) */ -#endif -#ifndef WOLFSPDM_KEY_EX_RX_SZ -#define WOLFSPDM_KEY_EX_RX_SZ 384 /* KEY_EXCHANGE_RSP (~302 bytes) */ -#endif #ifndef WOLFSPDM_FINISH_BUF_SZ #define WOLFSPDM_FINISH_BUF_SZ 152 /* FINISH mutual auth (~148 bytes) */ #endif @@ -223,6 +213,61 @@ extern "C" { #define WOLFSPDM_NO_CHUNK #endif +/* ----- Post-Quantum Algorithms (DSP0274 1.4) ----- */ + +/* ML-DSA and ML-KEM follow the linked wolfSSL and ride the certificate flow */ +#if defined(WOLFSSL_HAVE_MLDSA) && !defined(WOLFSPDM_NO_MLDSA) && \ + !defined(WOLFSPDM_NO_CERT) && !defined(WOLFSPDM_HAVE_MLDSA) + #define WOLFSPDM_HAVE_MLDSA +#endif +#if defined(WOLFSSL_HAVE_MLKEM) && !defined(WOLFSPDM_NO_MLKEM) && \ + !defined(WOLFSPDM_NO_CERT) && !defined(WOLFSPDM_HAVE_MLKEM) + #define WOLFSPDM_HAVE_MLKEM +#endif + +#ifdef WOLFSPDM_HAVE_MLDSA +#define WOLFSPDM_MLDSA44_SIG_SIZE 2420 +#define WOLFSPDM_MLDSA65_SIG_SIZE 3309 +#define WOLFSPDM_MLDSA87_SIG_SIZE 4627 +#define WOLFSPDM_MAX_SIG_SIZE WOLFSPDM_MLDSA87_SIG_SIZE +#define WOLFSPDM_RSP_PUBKEY_SZ 2592 /* ML-DSA-87 public key */ +#else +#define WOLFSPDM_MAX_SIG_SIZE WOLFSPDM_ECC_SIG_SIZE +#define WOLFSPDM_RSP_PUBKEY_SZ (WOLFSPDM_PUBKEY_BUF_SZ / 2) +#endif +#ifdef WOLFSPDM_HAVE_MLKEM +/* ExchangeData: the ML-KEM-1024 encapsulation key and ciphertext are largest */ +#define WOLFSPDM_MAX_KEX_DATA 1568 +#else +#define WOLFSPDM_MAX_KEX_DATA WOLFSPDM_ECC_POINT_SIZE +#endif + +/* ----- Buffer/Message Size Limits ----- */ + +#ifndef WOLFSPDM_MAX_MSG_SIZE + #ifdef WOLFSPDM_HAVE_MLDSA + #define WOLFSPDM_MAX_MSG_SIZE 8192 /* ML-DSA-87 signed responses */ + #else + #define WOLFSPDM_MAX_MSG_SIZE 4096 + #endif +#endif +#ifndef WOLFSPDM_MAX_TRANSCRIPT + #if defined(WOLFSPDM_HAVE_MLDSA) + #define WOLFSPDM_MAX_TRANSCRIPT 16384 + #elif defined(WOLFSPDM_HAVE_MLKEM) + #define WOLFSPDM_MAX_TRANSCRIPT 8192 + #else + #define WOLFSPDM_MAX_TRANSCRIPT 4096 + #endif +#endif +#ifndef WOLFSPDM_KEY_EX_TX_SZ +#define WOLFSPDM_KEY_EX_TX_SZ (96 + WOLFSPDM_MAX_KEX_DATA) +#endif +#ifndef WOLFSPDM_KEY_EX_RX_SZ +#define WOLFSPDM_KEY_EX_RX_SZ (192 + WOLFSPDM_MAX_KEX_DATA + \ + WOLFSPDM_MAX_SIG_SIZE) +#endif + /* ----- Session Keep-Alive and Key Rotation ----- */ #define SPDM_CAP_HBEAT_CAP 0x00002000 @@ -284,15 +329,39 @@ extern "C" { #define SPDM_ALG_TYPE_AEAD 3 #define SPDM_ALG_TYPE_REQ_BASE_ASYM 4 #define SPDM_ALG_TYPE_KEY_SCHEDULE 5 +#define SPDM_ALG_TYPE_KEM 7 + +/* DSP0274 1.4 PqcAsymAlgo (Tables 19 and 20) and KEMAlg (Table 24) */ +#define SPDM_PQC_ASYM_ALGO_ML_DSA_44 0x00000001 +#define SPDM_PQC_ASYM_ALGO_ML_DSA_65 0x00000002 +#define SPDM_PQC_ASYM_ALGO_ML_DSA_87 0x00000004 +#define SPDM_KEM_ALGO_ML_KEM_512 0x0001 +#define SPDM_KEM_ALGO_ML_KEM_768 0x0002 +#define SPDM_KEM_ALGO_ML_KEM_1024 0x0004 + +/* NEGOTIATE_ALGORITHMS: 32-byte header and up to five AlgStructs */ +#ifdef WOLFSPDM_HAVE_MLKEM +#define WOLFSPDM_NEG_ALGO_SZ 52 +#else +#define WOLFSPDM_NEG_ALGO_SZ 48 +#endif /* SPDM cert chain header: Length(2) + Reserved(2) + RootHash(48) */ #define WOLFSPDM_CERT_CHAIN_HDR_SZ (4 + WOLFSPDM_HASH_SIZE) #ifndef WOLFSPDM_MAX_CERT_CHAIN -#define WOLFSPDM_MAX_CERT_CHAIN 4096 + #ifdef WOLFSPDM_HAVE_MLDSA + #define WOLFSPDM_MAX_CERT_CHAIN 24576 /* three ML-DSA-87 certificates */ + #else + #define WOLFSPDM_MAX_CERT_CHAIN 4096 + #endif #endif #ifndef WOLFSPDM_MAX_TRUSTED_CA -#define WOLFSPDM_MAX_TRUSTED_CA 2048 + #ifdef WOLFSPDM_HAVE_MLDSA + #define WOLFSPDM_MAX_TRUSTED_CA 8192 + #else + #define WOLFSPDM_MAX_TRUSTED_CA 2048 + #endif #endif #endif /* !WOLFSPDM_NO_CERT */ From 5b477e13fb302eb18aa059dac534074286eb364b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 09:16:22 -0700 Subject: [PATCH 13/27] Update the README and wiki for the consolidated SPDM stack --- README.md | 24 ++--- docs/API-Reference.md | 112 ++++++++++++++++------- docs/Attestation-Notes.md | 78 ++++++++++++---- docs/Configuration-and-Macros.md | 141 +++++++++++++++++++++-------- docs/Getting-Started.md | 57 ++++++++++-- docs/Home.md | 45 +++++++--- docs/Message-Chunking.md | 147 +++++++++++++------------------ docs/Post-Quantum-ML-DSA.md | 86 ++++++++++++------ docs/Post-Quantum-ML-KEM.md | 80 +++++++++-------- docs/Project-Structure.md | 57 +++++++++--- docs/Supported-Operations.md | 17 ++-- docs/Testing-and-CI.md | 30 ++++--- 12 files changed, 577 insertions(+), 297 deletions(-) diff --git a/README.md b/README.md index ff38d4a..bd077ca 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ wolfSPDM is a lightweight C library implementing [SPDM 1.2 / 1.3 / 1.4](https:// - **Post-quantum signatures (SPDM 1.4):** optional ML-DSA-44 / 65 / 87 (FIPS 204), dual-stacked with ECDSA P-384 — see the [Post-Quantum ML-DSA](https://github.com/aidangarske/wolfSPDM/wiki/Post-Quantum-ML-DSA) wiki page - **Post-quantum key exchange (SPDM 1.4):** optional ML-KEM-512 / 768 / 1024 (FIPS 203), advertised alongside ECDHE P-384 — see the [Post-Quantum ML-KEM](https://github.com/aidangarske/wolfSPDM/wiki/Post-Quantum-ML-KEM) wiki page - **Fully post-quantum SPDM handshake:** ML-KEM key exchange + ML-DSA authentication (no classical asymmetric crypto), proven end-to-end against spdm-emu -- **Zero-malloc by default:** static memory, ~32 KB context, ideal for constrained/embedded environments +- **Zero-malloc by default:** static memory, ~17 KB context (~59 KB with ML-DSA), ideal for constrained/embedded environments - **Optional `--enable-dynamic-mem`** for heap-allocated contexts on small-stack platforms - **Full session lifecycle:** key exchange, finish, encrypted messaging, heartbeat keep-alive, key update - **Device attestation:** signed / unsigned `GET_MEASUREMENTS`, sessionless `CHALLENGE_AUTH`, certificate-chain validation against trusted root CAs @@ -74,7 +74,7 @@ make check ### Memory Modes -**Static (default):** zero heap allocation. The caller provides a buffer (`WOLFSPDM_CTX_STATIC_SIZE` bytes, ~32 KB) and wolfSPDM operates entirely within it. Ideal for embedded and constrained environments where malloc is unavailable or undesirable. +**Static (default):** zero heap allocation. The caller provides a buffer (`WOLFSPDM_CTX_STATIC_SIZE` bytes: 32 KB, 40 KB with ML-KEM, 72 KB with ML-DSA) and wolfSPDM operates entirely within it. Ideal for embedded and constrained environments where malloc is unavailable or undesirable. ```c #include @@ -86,7 +86,7 @@ wolfSPDM_InitStatic(ctx, sizeof(spdmBuf)); wolfSPDM_Free(ctx); ``` -**Dynamic (`--enable-dynamic-mem`):** context is heap-allocated via `wolfSPDM_New()`. Useful on platforms with small stacks where a ~32 KB local variable is impractical. +**Dynamic (`--enable-dynamic-mem`):** context is heap-allocated via `wolfSPDM_New()`. Useful on platforms with small stacks where a large local variable is impractical. ```c #include @@ -116,16 +116,14 @@ The driver starts/stops `spdm_responder_emu` per test and runs seven scenarios ## Relationship to wolfTPM's SPDM -wolfTPM ships its own SPDM implementation in `src/spdm/` for hardware-backed responders (Nuvoton NPCT75x, NSING NS350) with PSK / TCG-binding extensions. **wolfSPDM is a separate implementation** focused on the standard DSP0274 / DSP0277 requester for embedded use with `spdm-emu` and any standards-compliant peer. The two share heritage and are both designed for lightweight embedded use, with different deployment targets: +wolfSPDM is the SPDM stack wolfTPM builds on. Its core is the SPDM code that wolfTPM shipped in `src/spdm/` (TCG binding, Nuvoton NPCT75x and Nations NS350 vendor commands, PSK, the responder), with the standard DMTF requester layered on top. Build switches decide which side is compiled, so a standalone build carries none of the TPM code and a wolfTPM build carries none of the standard requester: -| | wolfSPDM | wolfTPM `src/spdm/` | +| Build | Compiled in | `sizeof(WOLFSPDM_CTX)` (arm64) | |---|---|---| -| Role | Requester only | Requester + responder | -| Scope | Pure standard SPDM 1.2 / 1.3 / 1.4 | Same, plus PSK / TCG / Nuvoton / Nations vendor bindings | -| Target | Embedded / spdm-emu / generic SPDM peer | TPM hardware (Nuvoton, NS350) | -| Footprint | ~32 KB context, zero-malloc (default static mode) | Lightweight embedded footprint; size depends on TPM stack, target, and build configuration | - -Either library can be used standalone; they aren't link-time compatible. +| Standalone (default) | Standard DSP0274 / DSP0277 requester: certificates, attestation, heartbeat, key update, chunking, application data; ML-DSA / ML-KEM when wolfSSL has them | ~17 KB classical, ~59 KB with ML-DSA | +| Standalone + TPM side | Adds `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-psk` / `--enable-responder` | ~17 KB classical | +| Pure TCG (`--disable-mctp`) | TCG binding, vendors, PSK and responder only | ~9.6 KB | +| wolfTPM (`WOLFTPM_SPDM`, profile `WOLFSPDM_PROFILE_TPM`) | What wolfTPM needs: TCG binding, vendors, PSK, responder | ~9.5 KB | ## CI / Testing @@ -137,7 +135,9 @@ Runs on every push and PR: - **Static Analysis**: cppcheck and Clang Static Analyzer (`scan-build`) - **CodeQL Security**: weekly + per-PR analysis - **Memory Check**: Valgrind `--leak-check=full` (static and dynamic mem) -- **SPDM Emulator Integration**: 18-test matrix (6 scenarios x SPDM 1.2 / 1.3 / 1.4) across ubuntu-22.04 x64, ubuntu-24.04 x64, and ubuntu-24.04-arm aarch64 +- **SPDM Emulator Integration**: 21-test matrix (7 scenarios x SPDM 1.2 / 1.3 / 1.4) across ubuntu-22.04 x64, ubuntu-24.04 x64, and ubuntu-24.04-arm aarch64, plus chunking against small-buffer responders +- **SPDM Emulator PQC**: ML-DSA-44 / 65 / 87, ML-KEM-512 / 768 / 1024 and the fully post-quantum handshake against spdm-emu on OpenSSL +- **wolfTPM downstream**: wolfTPM master built with this wolfSPDM in its 14 SPDM configurations, its unit tests, and the fwTPM TCG and PSK end-to-end runs; the standard requester must stay compiled out - **Skoll review**: wolfSSL deep-review pipeline, pre-merge security and code review diff --git a/docs/API-Reference.md b/docs/API-Reference.md index 85f2c11..eee5d42 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -1,8 +1,11 @@ # API Reference -Public APIs are declared in `wolfspdm/spdm.h`. +Public APIs are declared in `wolfspdm/spdm.h`, plus `wolfspdm/spdm_tcg.h`, +`spdm_nuvoton.h`, `spdm_nations.h`, `spdm_psk.h`, and `spdm_responder.h` for +the TPM/TCG side. -All APIs return `WOLFSPDM_SUCCESS` (`0`) on success unless documented otherwise; failures are negative error codes from `wolfspdm/spdm_error.h`. +All APIs return `WOLFSPDM_SUCCESS` (`0`) on success unless documented +otherwise; failures are negative error codes from `wolfspdm/spdm_error.h`. ## Context and lifecycle @@ -15,10 +18,14 @@ All APIs return `WOLFSPDM_SUCCESS` (`0`) on success unless documented otherwise; ## Configuration - `wolfSPDM_SetIO` -- `wolfSPDM_SetMaxVersion` -- `wolfSPDM_SetRequesterSessionId` -- `wolfSPDM_AllowUntrustedCerts` -- `wolfSPDM_SetTrustedCAs` +- `wolfSPDM_SetMode` / `wolfSPDM_GetMode` *(`WOLFSPDM_MODE_AUTO` / `_NUVOTON` / `_NATIONS` / `_NATIONS_PSK`)* +- `wolfSPDM_SetResponderPubKey` — pin the responder key (96-byte P-384 X‖Y) for cert-less operation +- `wolfSPDM_SetRequesterKeyPair` *(`WOLFSPDM_MUTUAL_AUTH` builds — TCG or TPM profile)* +- `wolfSPDM_SetMaxVersion` — cap the negotiated version (0x12-0x14) +- `wolfSPDM_SetRequesterSessionId` — rejects `0x0000`, `0xFFFF`, and low bytes `0x10`-`0x1F` +- `wolfSPDM_SetTrustedCAs` *(not with `WOLFSPDM_NO_CERT`)* +- `wolfSPDM_AllowUntrustedCerts` *(not with `WOLFSPDM_NO_CERT`)* +- `wolfSPDM_SetKeyExchangePref` *(not with `WOLFSPDM_NO_CERT`)* — see [[Post-Quantum ML-KEM]] - `wolfSPDM_SetDebug` ## Session establishment and state @@ -28,38 +35,77 @@ All APIs return `WOLFSPDM_SUCCESS` (`0`) on success unless documented otherwise; - `wolfSPDM_Disconnect` - `wolfSPDM_GetSessionId` - `wolfSPDM_GetNegotiatedVersion` -- `wolfSPDM_GetVersion_Negotiated` *(legacy compatibility symbol)* -- `wolfSPDM_GetLastPeerError` +- `wolfSPDM_GetVersion_Negotiated` *(older name for `wolfSPDM_GetNegotiatedVersion`)* +- `wolfSPDM_GetLastPeerError` — Param1 of the last SPDM ERROR, 0 if none +- `wolfSPDM_GetConnectionHandle` / `wolfSPDM_GetFipsIndicator` *(`WOLFSPDM_TCG` only)* -## Fine-grained handshake +## Fine-grained handshake (standard requester) - `wolfSPDM_GetVersion` -- `wolfSPDM_GetCapabilities` -- `wolfSPDM_NegotiateAlgorithms` -- `wolfSPDM_GetDigests` -- `wolfSPDM_GetCertificate` +- `wolfSPDM_GetCapabilities` *(not with `WOLFSPDM_NO_CERT`)* +- `wolfSPDM_NegotiateAlgorithms` *(not with `WOLFSPDM_NO_CERT`)* +- `wolfSPDM_GetDigests` *(not with `WOLFSPDM_NO_CERT`)* +- `wolfSPDM_GetCertificate` *(not with `WOLFSPDM_NO_CERT`)* - `wolfSPDM_KeyExchange` - `wolfSPDM_Finish` ## Secured messaging - `wolfSPDM_SecuredExchange` -- `wolfSPDM_SendData` *(not in `WOLFSPDM_LEAN`)* -- `wolfSPDM_ReceiveData` *(not in `WOLFSPDM_LEAN`)* -- `wolfSPDM_EncryptMessage` *(not in `WOLFSPDM_LEAN`)* -- `wolfSPDM_DecryptMessage` *(not in `WOLFSPDM_LEAN`)* +- `wolfSPDM_SendData` / `wolfSPDM_ReceiveData` *(`WOLFSPDM_HAS_APP_DATA`, not over the TCG binding)* +- `wolfSPDM_EncryptMessage` / `wolfSPDM_DecryptMessage` *(`WOLFSPDM_HAS_APP_DATA`)* ## Attestation -- `wolfSPDM_GetMeasurements` *(not with `NO_WOLFSPDM_MEAS`)* -- `wolfSPDM_GetMeasurementCount` *(not with `NO_WOLFSPDM_MEAS`)* -- `wolfSPDM_GetMeasurementBlock` *(not with `NO_WOLFSPDM_MEAS`)* -- `wolfSPDM_Challenge` *(not with `NO_WOLFSPDM_CHALLENGE`)* +- `wolfSPDM_GetMeasurements` *(`WOLFSPDM_HAS_MEASUREMENTS`)* +- `wolfSPDM_GetMeasurementCount` *(`WOLFSPDM_HAS_MEASUREMENTS`)* +- `wolfSPDM_GetMeasurementBlock` *(`WOLFSPDM_HAS_MEASUREMENTS`)* +- `wolfSPDM_Challenge` *(`WOLFSPDM_HAS_CHALLENGE`)* ## Session maintenance -- `wolfSPDM_Heartbeat` -- `wolfSPDM_KeyUpdate` +- `wolfSPDM_Heartbeat` *(`WOLFSPDM_HAS_HEARTBEAT`)* +- `wolfSPDM_KeyUpdate` *(`WOLFSPDM_HAS_KEY_UPDATE`)* + +## TCG SPDM binding (`wolfspdm/spdm_tcg.h`, `WOLFSPDM_TCG`) + +- `wolfSPDM_ConnectTCG` *(alias `wolfSPDM_ConnectNuvoton`)* +- `wolfSPDM_TCG_GetPubKey` / `wolfSPDM_TCG_GivePubKey` +- `wolfSPDM_TCG_GetCapabilities` / `wolfSPDM_TCG_NegotiateAlgorithms` +- `wolfSPDM_SetRequesterKeyTPMT` +- `wolfSPDM_TCG_VendorCmdClear` / `wolfSPDM_TCG_VendorCmdSecured` +- `wolfSPDM_BuildTcgClearMessage` / `wolfSPDM_ParseTcgClearMessage` +- `wolfSPDM_BuildVendorDefined` / `wolfSPDM_ParseVendorDefined` + +## Nuvoton (`wolfspdm/spdm_nuvoton.h`, `WOLFSPDM_NUVOTON`) + +- `wolfSPDM_Nuvoton_GetStatus` +- `wolfSPDM_Nuvoton_SetOnlyMode` + +## Nations (`wolfspdm/spdm_nations.h`, `WOLFSPDM_NATIONS`) + +- `wolfSPDM_Nations_GetStatus` +- `wolfSPDM_Nations_SetOnlyMode` +- `wolfSPDM_Nations_PskSet` / `wolfSPDM_Nations_PskClear` / `wolfSPDM_Nations_PskClearWithVCA` + +## PSK (`wolfspdm/spdm_psk.h`, `WOLFSPDM_PSK`) + +- `wolfSPDM_SetPSK` +- `wolfSPDM_ConnectPsk` *(alias `wolfSPDM_ConnectNationsPsk`)* +- `wolfSPDM_BuildPskExchange` / `wolfSPDM_ParsePskExchangeRsp` +- `wolfSPDM_BuildPskFinish` / `wolfSPDM_ParsePskFinishRsp` +- `wolfSPDM_DeriveHandshakeKeysPsk` + +## Responder (`wolfspdm/spdm_responder.h`, `WOLFSPDM_RESPONDER`) + +- `wolfSPDM_RespInit` / `wolfSPDM_RespFree` / `wolfSPDM_RespGetCtxSize` +- `wolfSPDM_RespSetMode`, `wolfSPDM_RespSetPSK`, `wolfSPDM_RespSetIdentityKey` +- `wolfSPDM_RespSetTpmCallback`, `wolfSPDM_RespSetDebug` +- `wolfSPDM_RespHandleMessage` — returns `WOLFSPDM_E_FRAMING` on a non-TCG + inbound frame; callers must drop the connection rather than fall through to + the TPM parser +- `wolfSPDM_RespReset`, `wolfSPDM_RespIsLocked`, `wolfSPDM_RespIsSessionActive` +- `wolfSPDM_RespGetIdentityKey` ## Error utilities @@ -67,12 +113,14 @@ All APIs return `WOLFSPDM_SUCCESS` (`0`) on success unless documented otherwise; ## Common error codes -Examples: -- `WOLFSPDM_E_INVALID_ARG` -- `WOLFSPDM_E_BAD_STATE` -- `WOLFSPDM_E_NOT_CONNECTED` -- `WOLFSPDM_E_IO_FAIL` -- `WOLFSPDM_E_PEER_ERROR` -- `WOLFSPDM_E_MEAS_SIG_FAIL` -- `WOLFSPDM_E_CHALLENGE` -- `WOLFSPDM_E_KEY_UPDATE` +Defined in `wolfspdm/spdm_error.h`: + +- `WOLFSPDM_E_INVALID_ARG`, `WOLFSPDM_E_BUFFER_SMALL`, `WOLFSPDM_E_BAD_STATE` +- `WOLFSPDM_E_VERSION_MISMATCH`, `WOLFSPDM_E_ALGO_MISMATCH`, `WOLFSPDM_E_CAPS_MISMATCH` +- `WOLFSPDM_E_CRYPTO_FAIL`, `WOLFSPDM_E_BAD_SIGNATURE`, `WOLFSPDM_E_BAD_HMAC`, `WOLFSPDM_E_DECRYPT_FAIL` +- `WOLFSPDM_E_IO_FAIL`, `WOLFSPDM_E_TIMEOUT`, `WOLFSPDM_E_PEER_ERROR`, `WOLFSPDM_E_SEQUENCE` +- `WOLFSPDM_E_NOT_CONNECTED`, `WOLFSPDM_E_ALREADY_INIT`, `WOLFSPDM_E_NO_MEMORY` +- `WOLFSPDM_E_SESSION_INVALID`, `WOLFSPDM_E_KEY_EXCHANGE`, `WOLFSPDM_E_NOT_AVAILABLE` +- `WOLFSPDM_E_FRAMING` — frame did not parse (e.g. plaintext TPM2 while SPDM mode is active) +- `WOLFSPDM_E_NOT_IMPL`, `WOLFSPDM_E_CERT_FAIL`, `WOLFSPDM_E_CERT_PARSE` +- `WOLFSPDM_E_KEY_UPDATE`, `WOLFSPDM_E_MEASUREMENT`, `WOLFSPDM_E_CHALLENGE`, `WOLFSPDM_E_CHUNK` diff --git a/docs/Attestation-Notes.md b/docs/Attestation-Notes.md index 28b079c..b23b8a4 100644 --- a/docs/Attestation-Notes.md +++ b/docs/Attestation-Notes.md @@ -1,6 +1,9 @@ # Attestation Notes -wolfSPDM supports SPDM attestation through both measurement retrieval and challenge authentication. +wolfSPDM supports SPDM attestation through both measurement retrieval and +challenge authentication (`src/spdm_attest.c`). Both ride the certificate +flow, so they require the standard requester (not `WOLFSPDM_NO_CERT`) and are +compiled out entirely without it. ## Measurement attestation (`GET_MEASUREMENTS`) @@ -12,8 +15,21 @@ int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, byte measOperation, ``` Behavior: -- `requestSignature=1`: requests signed measurements; verifies signature when verification support is compiled in -- `requestSignature=0`: retrieves unsigned measurements (informational) +- `requestSignature=1`: requests signed measurements; the responder's + signature is verified against the running L1/L2 transcript hash before the + blocks are exposed to the caller. +- `requestSignature=0`: retrieves unsigned measurements without a signature + check. +- The call requires `SPDM_CAP_MEAS_CAP_SIG` (signed) or + `SPDM_CAP_MEAS_CAP_NO_SIG` (unsigned) from the responder's negotiated + capabilities, else `WOLFSPDM_E_CAPS_MISMATCH`. + +**L1/L2 running hash.** L1/L2 spans *consecutive* `GET_MEASUREMENTS` +exchanges: each call restarts the hash at the VCA transcript unless the +previous `GET_MEASUREMENTS` left it open (`WOLFSPDM_RUN_OPEN`, i.e. the prior +call was unsigned). A signed request closes the run and verifies the +signature over it; an unsigned request adds itself to the hash and leaves the +run open for the next call. Signature verification (measurements and `CHALLENGE_AUTH`) uses whichever asymmetric algorithm was negotiated — ECDSA P-384 or, on SPDM 1.4 with ML-DSA @@ -21,13 +37,14 @@ built in, ML-DSA-44/65/87. See [[Post-Quantum ML-DSA]]. Result access: - `wolfSPDM_GetMeasurementCount` -- `wolfSPDM_GetMeasurementBlock` +- `wolfSPDM_GetMeasurementBlock` — `valueSz` is in/out; `measType` is the DMTF + value type, 0 for raw (non-DMTF-spec) blocks Relevant return codes: - `WOLFSPDM_SUCCESS` -- `WOLFSPDM_E_MEAS_NOT_VERIFIED` -- `WOLFSPDM_E_MEAS_SIG_FAIL` -- `WOLFSPDM_E_MEASUREMENT` +- `WOLFSPDM_E_CAPS_MISMATCH` — signed/unsigned measurement capability not negotiated +- `WOLFSPDM_E_MEASUREMENT` — malformed or inconsistent `MEASUREMENTS` response +- `WOLFSPDM_E_BAD_SIGNATURE` / `WOLFSPDM_E_CRYPTO_FAIL` — signature length mismatch or verification failure ## Sessionless challenge attestation (`CHALLENGE_AUTH`) @@ -37,9 +54,34 @@ Primary API: int wolfSPDM_Challenge(WOLFSPDM_CTX* ctx, int slotId, byte measHashType); ``` -Typical prerequisite state: -- Version/capabilities/algorithms negotiated -- Digest and cert chain retrieved +Prerequisite state (checked, returns `WOLFSPDM_E_BAD_STATE` otherwise): +- Certificate chain retrieved for `slotId` (`ctx->state >= WOLFSPDM_STATE_CERT`) +- `slotId` matches the slot the chain was fetched for +- An M1 transcript run is open (started by `GET_DIGESTS`/`GET_CERTIFICATE`) +- `SPDM_CAP_CHAL_CAP` negotiated, else `WOLFSPDM_E_CAPS_MISMATCH` + +**M1 running hash.** M1 starts at the VCA transcript when the certificate +chain is fetched and accumulates through `CHALLENGE`/`CHALLENGE_AUTH`. +`wolfSPDM_KeyExchange` restarts M1 at the VCA before building its request — +`KEY_EXCHANGE` drops `GET_DIGESTS`/`GET_CERTIFICATE` from its own M1 — and a +successful `CHALLENGE` restarts M1 again afterward, so the next M1 is the VCA +plus only the messages that follow. + +`wolfSPDM_ValidateCertChain` is run before building the request; see +[[Post-Quantum ML-DSA]] for how the chain is verified link by link. + +Relevant return codes: +- `WOLFSPDM_E_BAD_STATE`, `WOLFSPDM_E_CAPS_MISMATCH` +- `WOLFSPDM_E_CERT_FAIL` — chain validation failed +- `WOLFSPDM_E_CHALLENGE` — malformed or mismatched `CHALLENGE_AUTH` +- `WOLFSPDM_E_BAD_SIGNATURE` / `WOLFSPDM_E_CRYPTO_FAIL` — signature failure + +## Signature context strings + +Each signed exchange uses its own SPDM signing context string, mixed into the +ML-DSA `ctx` parameter or the classical signed-hash construction: +`"responder-measurements signing"`, `"responder-challenge_auth signing"`, +`"responder-key_exchange_rsp signing"`. ## Trust anchor handling @@ -50,12 +92,18 @@ int wolfSPDM_SetTrustedCAs(WOLFSPDM_CTX* ctx, const byte* derCerts, word32 derCertsSz); ``` -`wolfSPDM_SetTrustedCAs` currently accepts a single DER certificate buffer for root-hash matching. +`wolfSPDM_SetTrustedCAs` accepts a single DER root certificate; its SHA-384 +hash is compared against the chain header's `RootHash`. Alternatively, pin +the responder's leaf key directly with `wolfSPDM_SetResponderPubKey`, or +explicitly opt out of anchoring with `wolfSPDM_AllowUntrustedCerts`. Without +one of the three, `wolfSPDM_ValidateCertChain` fails with +`WOLFSPDM_E_CERT_FAIL`. ## Feature toggles -- `NO_WOLFSPDM_MEAS` disables measurements -- `NO_WOLFSPDM_MEAS_VERIFY` disables measurement signature verification -- `NO_WOLFSPDM_CHALLENGE` disables challenge API +- `--disable-meas` / `WOLFSPDM_NO_MEAS` disables `GET_MEASUREMENTS` (also + implied by `WOLFSPDM_NO_CERT`) +- `--disable-challenge` / `WOLFSPDM_NO_CHALLENGE` disables `CHALLENGE` (also + implied by `WOLFSPDM_NO_CERT`) -For deeper measurement details and test examples, see `docs/ATTESTATION.md`. +See [[Configuration and Macros]] for the full implication chain. diff --git a/docs/Configuration-and-Macros.md b/docs/Configuration-and-Macros.md index b925bbb..b951626 100644 --- a/docs/Configuration-and-Macros.md +++ b/docs/Configuration-and-Macros.md @@ -4,64 +4,129 @@ From `configure.ac`: -| Option | Default | Effect | -|--------|---------|--------| -| `--with-wolfssl=PATH` | system paths | Adds wolfSSL include/library search paths | -| `--enable-debug` | off | Defines `WOLFSPDM_DEBUG`, builds with `-g -O0` | -| `--enable-dynamic-mem` | off | Defines `WOLFSPDM_DYNAMIC_MEMORY` and enables `wolfSPDM_New` | -| `--disable-mldsa` | auto | Force ML-DSA off (default follows wolfSSL — see [[Post-Quantum ML-DSA]]) | -| `--disable-mlkem` | auto | Force ML-KEM off (default follows wolfSSL — see [[Post-Quantum ML-KEM]]) | -| `--disable-chunking` | on | Defines `WOLFSPDM_NO_CHUNK` — compile out CHUNK_GET (see [[Message Chunking]]) | +| Option | Default | Defines | Effect | +|--------|---------|---------|--------| +| `--with-wolfssl=PATH` | system paths | — | Adds wolfSSL include/library search paths | +| `--enable-debug` | off | `WOLFSPDM_DEBUG` | Debug output, `-g -O0` | +| `--enable-dynamic-mem` | off | `WOLFSPDM_DYNAMIC_MEMORY` | Heap-allocated context, enables `wolfSPDM_New` | +| `--disable-cert` | enabled | `WOLFSPDM_NO_CERT` | Drops the standard certificate-based requester | +| `--disable-mctp` | enabled | `WOLFSPDM_NO_MCTP` | Pure TCG build: drops MCTP secured messages (implies `--disable-cert`); needs `--enable-tcg` or a vendor | +| `--disable-app-data` | enabled | `WOLFSPDM_NO_APP_DATA` | Drops `SendData`/`ReceiveData`/`Encrypt`/`DecryptMessage` | +| `--disable-chunking` | enabled | `WOLFSPDM_NO_CHUNK` | Drops CHUNK_SEND/CHUNK_GET (see [[Message Chunking]]) | +| `--disable-meas` | enabled | `WOLFSPDM_NO_MEAS` | Drops GET_MEASUREMENTS | +| `--disable-challenge` | enabled | `WOLFSPDM_NO_CHALLENGE` | Drops CHALLENGE | +| `--disable-heartbeat` | enabled | `WOLFSPDM_NO_HEARTBEAT` | Drops HEARTBEAT | +| `--disable-key-update` | enabled | `WOLFSPDM_NO_KEY_UPDATE` | Drops KEY_UPDATE | +| `--disable-mldsa` | auto | `WOLFSPDM_NO_MLDSA` | Force ML-DSA off (default follows wolfSSL — see [[Post-Quantum ML-DSA]]) | +| `--disable-mlkem` | auto | `WOLFSPDM_NO_MLKEM` | Force ML-KEM off (default follows wolfSSL — see [[Post-Quantum ML-KEM]]) | +| `--enable-tcg` | off | `WOLFSPDM_TCG` | TCG SPDM binding (TPM transport) | +| `--enable-nuvoton` | off | `WOLFSPDM_NUVOTON` | Nuvoton NPCT75x vendor commands (implies `--enable-tcg`) | +| `--enable-nations` | off | `WOLFSPDM_NATIONS` | Nations NS350 vendor commands (implies `--enable-tcg` and `--enable-psk`) | +| `--enable-psk` | off | `WOLFSPDM_PSK` | SPDM PSK_EXCHANGE/PSK_FINISH (requires `--enable-tcg`) | +| `--enable-responder` | off | `WOLFSPDM_RESPONDER` | SPDM responder (requires `--enable-tcg`) | + +`CFLAGS=-DWOLFSPDM_DATA_TRANSFER_SIZE=N` sets the largest single SPDM message +(42 to `WOLFSPDM_MAX_MSG_SIZE`); smaller values shrink transport buffers and +rely more on chunking. + +## wolfTPM build profile + +Built inside wolfTPM (`WOLFTPM_SPDM`), `WOLFSPDM_PROFILE_TPM` is implied, and +wolfTPM's own switches (`WOLFTPM_SPDM_TCG`, `WOLFTPM_SPDM_PSK`, +`WOLFTPM_SPDM_RESPONDER`, `DEBUG_WOLFTPM`, `WOLFTPM_SMALL_STACK`) map onto the +matching `WOLFSPDM_*` macro. `WOLFSPDM_PROFILE_TPM` implies: + +- `WOLFSPDM_NO_CERT` (the TPM only speaks the TCG binding) +- `WOLFSPDM_NO_HEARTBEAT`, `WOLFSPDM_NO_KEY_UPDATE` +- `WOLFSPDM_NO_APP_DATA` (application messages ride MCTP, which the TPM profile doesn't use) +- `WOLFSPDM_SECURED_PAD` = 16 instead of 48 (the TCG binding only pads to 16; + MCTP secured records may carry up to 32 bytes of random padding per DSP0277) + +## Implication chain + +- `WOLFSPDM_NO_CERT` implies `WOLFSPDM_NO_MEAS`, `WOLFSPDM_NO_CHALLENGE`, and + `WOLFSPDM_NO_CHUNK` (attestation and chunking need the certificate flow's + VCA transcript), and blocks ML-DSA/ML-KEM (both ride the certificate flow). + `NO_ASN` also forces `WOLFSPDM_NO_CERT`. +- `WOLFSPDM_NO_MCTP` implies `WOLFSPDM_NO_CERT` (and therefore everything + above) and `WOLFSPDM_NO_APP_DATA`. +- `WOLFSPDM_PROFILE_TPM` implies `WOLFSPDM_NO_CERT`, `WOLFSPDM_NO_HEARTBEAT`, + `WOLFSPDM_NO_KEY_UPDATE`, `WOLFSPDM_NO_APP_DATA`. +- `WOLFSPDM_NUVOTON` or `WOLFSPDM_NATIONS` implies `WOLFSPDM_TCG`. +- `WOLFSPDM_NATIONS` implies `WOLFSPDM_PSK`. +- `WOLFSPDM_TCG` or `WOLFSPDM_PROFILE_TPM` implies `WOLFSPDM_MUTUAL_AUTH` + (requester identity-key API for TCG GIVE_PUB). +- `WOLFSPDM_LEAN` is accepted as an older alias for `WOLFSPDM_NO_APP_DATA`. ## Public feature macros Defined in `wolfspdm/spdm.h` depending on build flags: -- `WOLFSPDM_HAS_MEASUREMENTS` *(not defined if `NO_WOLFSPDM_MEAS`)* -- `WOLFSPDM_HAS_CHALLENGE` *(not defined if `NO_WOLFSPDM_CHALLENGE`)* -- `WOLFSPDM_HAS_HEARTBEAT` -- `WOLFSPDM_HAS_KEY_UPDATE` -- `WOLFSPDM_HAVE_MLDSA` *(defined when ML-DSA is built in; follows wolfSSL's `WOLFSSL_HAVE_MLDSA`, suppress with `WOLFSPDM_NO_MLDSA`)* -- `WOLFSPDM_HAVE_MLKEM` *(defined when ML-KEM key exchange is built in; follows wolfSSL's `WOLFSSL_HAVE_MLKEM`, suppress with `WOLFSPDM_NO_MLKEM`)* — see [[Post-Quantum ML-KEM]]. The advertised key-exchange methods are chosen at runtime with `wolfSPDM_SetKeyExchangePref(ctx, advDhe, kemMask)` (default: ECDHE + all ML-KEM sets). -- `WOLFSPDM_HAVE_CHUNK` *(defined when CHUNK_GET chunking is built in; suppress with `WOLFSPDM_NO_CHUNK`)* — tunables `WOLFSPDM_CHUNK_BUF_SIZE` (MTU, default 4096), `WOLFSPDM_CHUNK_MAX_CHUNKS` (default 64), and `WOLFSPDM_CHUNK_NO_SECURED` (drop the encrypted path). See [[Message Chunking]]. +- `WOLFSPDM_HAS_APP_DATA` *(not defined if `WOLFSPDM_NO_APP_DATA`)* +- `WOLFSPDM_HAS_MEASUREMENTS` *(not defined if `WOLFSPDM_NO_MEAS`)* +- `WOLFSPDM_HAS_CHALLENGE` *(not defined if `WOLFSPDM_NO_CHALLENGE`)* +- `WOLFSPDM_HAS_HEARTBEAT` *(not defined if `WOLFSPDM_NO_HEARTBEAT`)* +- `WOLFSPDM_HAS_KEY_UPDATE` *(not defined if `WOLFSPDM_NO_KEY_UPDATE`)* +- `WOLFSPDM_HAVE_MLDSA` *(defined when ML-DSA is built in; follows wolfSSL's + `WOLFSSL_HAVE_MLDSA`, suppress with `WOLFSPDM_NO_MLDSA`)* — see + [[Post-Quantum ML-DSA]] +- `WOLFSPDM_HAVE_MLKEM` *(defined when ML-KEM is built in; follows wolfSSL's + `WOLFSSL_HAVE_MLKEM`, suppress with `WOLFSPDM_NO_MLKEM`)* — see + [[Post-Quantum ML-KEM]]. The advertised key-exchange methods are chosen at + runtime with `wolfSPDM_SetKeyExchangePref(ctx, advDhe, kemMask)` (default: + ECDHE + every ML-KEM set built in). + +There is no `WOLFSPDM_HAVE_CHUNK` macro; chunking compiles in unless +`WOLFSPDM_NO_CHUNK` is defined (or implied by `WOLFSPDM_NO_CERT`). ## Size and protocol constants -From `wolfspdm/spdm.h` and `wolfspdm/spdm_types.h`. The buffer/context defaults -grow when ML-DSA is built in so ML-DSA-65 payloads fit a single message -(all caps are overridable with `-D`). ML-KEM-only builds use an intermediate -context/transcript budget for the in-context ephemeral ML-KEM key: +From `wolfspdm/spdm.h` and `wolfspdm/spdm_types.h`. The buffer/context +defaults grow when ML-DSA or ML-KEM is built in (all are overridable with +`-D`): | Constant | Classical | ML-KEM only | With ML-DSA | |----------|-----------|-------------|-------------| -| `WOLFSPDM_CTX_STATIC_SIZE` | `32768` | `49152` | `73728` | +| `WOLFSPDM_CTX_STATIC_SIZE` | `32768` | `40960` | `73728` | | `WOLFSPDM_MAX_MSG_SIZE` | `4096` | `4096` | `8192` | | `WOLFSPDM_MAX_CERT_CHAIN` | `4096` | `4096` | `24576` | +| `WOLFSPDM_MAX_TRUSTED_CA` | `2048` | `2048` | `8192` | | `WOLFSPDM_MAX_TRANSCRIPT` | `4096` | `8192` | `16384` | -ML-KEM size constants (`WOLFSPDM_HAVE_MLKEM`): `WOLFSPDM_MLKEM{512,768,1024}_EK_SIZE` -(encapsulation key), `_CT_SIZE` (ciphertext), and `WOLFSPDM_KEM_SS_SIZE` (32). -KEM algorithm bits: `SPDM_KEM_ALGO_ML_KEM_512/768/1024` (`0x01/0x02/0x04`). +Measured `sizeof(WOLFSPDM_CTX)` on arm64: ~17 KB classical, ~22 KB ML-KEM +only, ~59 KB with ML-DSA, ~9.5 KB in the TPM profile (well under the +corresponding `WOLFSPDM_CTX_STATIC_SIZE`). -Version constants: -- `SPDM_VERSION_12`, `SPDM_VERSION_13`, `SPDM_VERSION_14` +Other overridable size macros (`wolfspdm/spdm_types.h`): +`WOLFSPDM_DATA_TRANSFER_SIZE` (default `WOLFSPDM_MAX_MSG_SIZE`, floor 42), +`WOLFSPDM_MAX_MEAS_RECORD` (`1024`), `WOLFSPDM_REQ_CAPS` (the CAPABILITIES +flags this requester advertises). -Measurement constants (when enabled): -- `SPDM_MEAS_OPERATION_ALL` -- `SPDM_MEAS_SUMMARY_HASH_NONE`, `_TCB`, `_ALL` +ML-KEM/ML-DSA size constants: `WOLFSPDM_MLDSA{44,65,87}_SIG_SIZE`, +`WOLFSPDM_MAX_SIG_SIZE`, `WOLFSPDM_MAX_KEX_DATA`. KEM algorithm bits: +`SPDM_KEM_ALGO_ML_KEM_512/768/1024` (`0x0001/0x0002/0x0004`). PQC asym bits: +`SPDM_PQC_ASYM_ALGO_ML_DSA_44/65/87` (`0x01/0x02/0x04`). -## Common compile-time feature toggles +Version constants: `SPDM_VERSION_10`, `SPDM_VERSION_12`, `SPDM_VERSION_13`, +`SPDM_VERSION_14`. -These are used in source-level conditional compilation: +Measurement constants (when enabled): `SPDM_MEAS_OPERATION_TOTAL_NUMBER`, +`SPDM_MEAS_OPERATION_ALL`, `SPDM_MEAS_SUMMARY_HASH_NONE`/`_TCB`/`_ALL`. -| Macro | Effect | -|-------|--------| -| `NO_WOLFSPDM_MEAS` | Removes measurement APIs and related fields/code | -| `NO_WOLFSPDM_MEAS_VERIFY` | Keeps retrieval path but disables measurement signature verification | -| `NO_WOLFSPDM_CHALLENGE` | Removes challenge-attestation API/code | -| `WOLFSPDM_LEAN` | Excludes selected convenience secured-message helpers | +## Removed / renamed macros + +These names from the old standalone design no longer exist: + +- `NO_WOLFSPDM_MEAS` -> `WOLFSPDM_NO_MEAS` +- `NO_WOLFSPDM_CHALLENGE` -> `WOLFSPDM_NO_CHALLENGE` +- `WOLFSPDM_HAVE_CHUNK` -> chunking is on by default; use `WOLFSPDM_NO_CHUNK` to disable +- `WOLFSPDM_CHUNK_BUF_SIZE` -> removed; the chunk MTU is `WOLFSPDM_DATA_TRANSFER_SIZE` +- `WOLFSPDM_CHUNK_MAX_CHUNKS` -> removed, no configurable loop guard +- `WOLFSPDM_CHUNK_NO_SECURED` -> removed; secured chunking (e.g. GET_MEASUREMENTS) is always available when chunking is built in ## Notes -- `wolfspdm/options.h` is auto-generated from `config.h` during build/install. -- API availability should be detected using feature macros rather than hard-coded assumptions. +- `wolfspdm/options.h` is auto-generated from `config.h` during the build + (`Makefile.am` greps `WOLFSPDM_` defines out of `config.h`). +- API availability should be detected using the `WOLFSPDM_HAS_*` feature + macros rather than hard-coded assumptions. diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 20e0322..000abe2 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -19,9 +19,10 @@ sudo make install sudo ldconfig ``` -For optional post-quantum ML-DSA support, add `--enable-mldsa` and use -wolfSSL master (or a release that ships the `wc_MlDsaKey` context API). See -[[Post-Quantum ML-DSA]]. +For optional post-quantum support add `--enable-mldsa` (signatures, FIPS 204) +and/or `--enable-mlkem` (key exchange, FIPS 203); wolfSPDM then auto-enables +each capability it finds. See [[Post-Quantum ML-DSA]] and +[[Post-Quantum ML-KEM]]. ## Build wolfSPDM @@ -39,7 +40,17 @@ make check | `--with-wolfssl=PATH` | Path to wolfSSL headers/libs | | `--enable-debug` | Enables debug build flags and `WOLFSPDM_DEBUG` | | `--enable-dynamic-mem` | Enables heap-allocated context APIs (`wolfSPDM_New`) | -| `--disable-mldsa` | Forces ML-DSA off (default auto-follows wolfSSL) | +| `--disable-cert` | Disables the standard certificate requester | +| `--disable-mctp` | Pure TCG build: drops MCTP secured messages and the whole standard requester (needs `--enable-tcg` or a vendor) | +| `--disable-app-data` | Disables the MCTP application data API | +| `--disable-chunking` | Disables CHUNK_SEND/CHUNK_GET | +| `--disable-meas` / `--disable-challenge` | Disables GET_MEASUREMENTS / CHALLENGE | +| `--disable-heartbeat` / `--disable-key-update` | Disables HEARTBEAT / KEY_UPDATE | +| `--disable-mldsa` / `--disable-mlkem` | Forces ML-DSA / ML-KEM off (default auto-follows wolfSSL) | +| `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-psk` / `--enable-responder` | TPM side: TCG SPDM binding, vendor commands, PSK, responder (default: all off) | + +See [[Configuration and Macros]] for the full option-to-macro mapping and +their implications. ## Memory modes @@ -53,7 +64,8 @@ WOLFSPDM_CTX* ctx = (WOLFSPDM_CTX*)spdmBuf; wolfSPDM_InitStatic(ctx, sizeof(spdmBuf)); ``` -`WOLFSPDM_CTX_STATIC_SIZE` is 32768 bytes. +`WOLFSPDM_CTX_STATIC_SIZE` is 32768 bytes by default, 40960 with ML-KEM only, +and 73728 with ML-DSA built in (see [[Configuration and Macros]]). ### Dynamic mode (optional) @@ -63,13 +75,15 @@ Enable with `--enable-dynamic-mem`, then: WOLFSPDM_CTX* ctx = wolfSPDM_New(); ``` -## Minimal connection flow +## Minimal connection flow (standard requester) 1. Initialize context (`wolfSPDM_Init` or `wolfSPDM_InitStatic`) 2. Register transport callback with `wolfSPDM_SetIO` -3. Optionally set trust root with `wolfSPDM_SetTrustedCAs` +3. Optionally set trust root with `wolfSPDM_SetTrustedCAs`, or pin the + responder key with `wolfSPDM_SetResponderPubKey` 4. Establish session with `wolfSPDM_Connect` -5. Exchange secured data using `wolfSPDM_SecuredExchange` (or send/receive helpers) +5. Exchange secured data using `wolfSPDM_SecuredExchange` (or the + `SendData`/`ReceiveData` MCTP application-message helpers) 6. End session with `wolfSPDM_Disconnect` 7. Cleanup via `wolfSPDM_Free` @@ -84,4 +98,29 @@ typedef int (*WOLFSPDM_IO_CB)(WOLFSPDM_CTX* ctx, void* userCtx); ``` -The callback sends raw SPDM/SPDM-secured records and returns the responder message. +The callback sends raw SPDM/SPDM-secured records and returns the responder +message. `wolfSPDM_SendData` calls it with `rxBuf` NULL and `*rxSz` 0 +(send only); `wolfSPDM_ReceiveData` calls it with `txBuf` NULL and `txSz` 0 +(receive only). + +## Running the demo against spdm-emu + +`examples/spdm_demo` drives each SPDM operation against `spdm-emu` over +TCP/MCTP: + +```bash +git clone --recursive https://github.com/DMTF/spdm-emu.git +cd spdm-emu && mkdir build && cd build +cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=mbedtls .. +make copy_sample_key && make + +export SPDM_EMU_PATH=../spdm-emu/build/bin +./examples/spdm_test.sh +``` + +`spdm_demo` accepts `--emu`, `--meas` (add `--no-sig` for unsigned +measurements), `--challenge`, `--heartbeat`, `--key-update`, `--app-data`, +`--ver 1.2|1.3|1.4`, `--kex ecdhe|mlkem512|mlkem768|mlkem1024`, and `--debug`. +`examples/spdm_test.sh` runs the 21-test matrix (7 scenarios x SPDM +1.2/1.3/1.4): session, signed measurements, unsigned measurements, challenge, +heartbeat, key update, and application data (PLDM GetTID). diff --git a/docs/Home.md b/docs/Home.md index bd843f4..d62459d 100644 --- a/docs/Home.md +++ b/docs/Home.md @@ -1,41 +1,61 @@ # wolfSPDM Documentation -Welcome to the wolfSPDM wiki. This documentation covers wolfSPDM, a lightweight requester-only SPDM implementation for embedded systems and constrained environments. +Welcome to the wolfSPDM wiki. wolfSPDM implements SPDM over two layers: a +**wolfTPM-derived TCG binding core** (Nuvoton / Nations Technology TPM +transport, PSK, identity-key mutual auth, and an SPDM responder) and, layered +on top of it behind compile-time switches, the **standard DMTF requester** +(certificates, measurements, challenge, chunking, PQC). ## What is wolfSPDM? wolfSPDM is a C library implementing: - **SPDM 1.2 / 1.3 / 1.4** ([DMTF DSP0274](https://www.dmtf.org/sites/default/files/standards/documents/DSP0274_1.4.0.pdf)) - **Secured Messages over MCTP** ([DMTF DSP0277](https://www.dmtf.org/sites/default/files/standards/documents/DSP0277_1.2.0.pdf)) +- **TCG SPDM Binding** (TPM transport, vendor commands) for Nuvoton NPCT75x and + Nations NS350 TPMs -It uses [wolfSSL / wolfCrypt](https://www.wolfssl.com/) as its crypto backend and is tested end-to-end against the DMTF [spdm-emu](https://github.com/DMTF/spdm-emu) responder emulator. +It uses [wolfSSL / wolfCrypt](https://www.wolfssl.com/) as its crypto backend +and is tested end-to-end against the DMTF +[spdm-emu](https://github.com/DMTF/spdm-emu) responder emulator. + +## Two build profiles + +- **wolfTPM builds** (`WOLFTPM_SPDM`, which implies `WOLFSPDM_PROFILE_TPM`): + compile only the TCG binding side. The standard certificate requester, + HEARTBEAT, KEY_UPDATE, and application data are compiled out + (`WOLFSPDM_NO_CERT`/`NO_HEARTBEAT`/`NO_KEY_UPDATE`/`NO_APP_DATA`). +- **Standalone builds** (`./configure`): compile the standard requester by + default; the TPM side (TCG binding, Nuvoton/Nations vendor commands, PSK, + responder) is compiled in only with `--enable-tcg` / `--enable-nuvoton` / + `--enable-nations` / `--enable-psk` / `--enable-responder`. ## Key Features | Feature | Description | |---------|-------------| -| Requester-only SPDM stack | Purpose-built initiator implementation | -| SPDM 1.2/1.3/1.4 | Standards-based negotiation and session setup | +| Standard SPDM 1.2/1.3/1.4 requester | Certificate-based DSP0274 flow (`--disable-cert` to drop it) | +| TCG SPDM binding | Nuvoton / Nations TPM transport, vendor commands, identity-key mutual auth (`--enable-tcg`) | +| PSK mode | `PSK_EXCHANGE`/`PSK_FINISH` over the TCG binding (`--enable-psk`) | +| SPDM responder | Answers requester-driven messages for a TPM-backed device (`--enable-responder`) | | Fixed Algorithm Set B | ECDSA P-384, ECDHE P-384, SHA-384, AES-256-GCM, HKDF-SHA384 | | Post-quantum signatures (1.4) | Optional ML-DSA-44/65/87 (FIPS 204), dual-stacked with ECDSA P-384 | | Post-quantum key exchange (1.4) | Optional ML-KEM-512/768/1024 (FIPS 203), advertised alongside ECDHE P-384 | | Fully post-quantum handshake | ML-KEM key exchange + ML-DSA authentication, no classical asymmetric crypto | -| Message chunking | SPDM 1.2 CHUNK_GET reassembly over a fixed MTU buffer (zero-alloc) | -| Zero-malloc by default | Static context (`WOLFSPDM_CTX_STATIC_SIZE`, 32 KB; ~72 KB with ML-DSA) | +| Message chunking | CHUNK_SEND and CHUNK_GET, in the clear and inside secured sessions | +| Zero-malloc by default | Static context (`WOLFSPDM_CTX_STATIC_SIZE`) | | Optional dynamic context | `--enable-dynamic-mem` enables `wolfSPDM_New()` | | Attestation operations | Signed/unsigned `GET_MEASUREMENTS`, sessionless `CHALLENGE_AUTH` | | Session operations | `HEARTBEAT`, `KEY_UPDATE`, secured app data transfer | -| CI + security coverage | Multi-compiler, static analysis, CodeQL, Valgrind, spdm-emu integration | ## Documentation | Page | Description | -|------|-------------| +|------|--------------| | [[Getting Started]] | Dependencies, build, install, and first connection flow | | [[Supported Operations]] | Supported SPDM flows and operation/API mapping | | [[Post-Quantum ML-DSA]] | SPDM 1.4 ML-DSA (FIPS 204) post-quantum signatures | | [[Post-Quantum ML-KEM]] | SPDM 1.4 ML-KEM (FIPS 203) post-quantum key exchange + fully post-quantum handshake | -| [[Message Chunking]] | SPDM 1.2 CHUNK_GET reassembly of large responses | +| [[Message Chunking]] | CHUNK_SEND / CHUNK_GET large message chunking | | [[API Reference]] | Public API grouped by lifecycle and purpose | | [[Configuration and Macros]] | Configure flags and compile-time feature controls | | [[Testing and CI]] | Unit tests, emulator tests, and CI workflow coverage | @@ -44,10 +64,15 @@ It uses [wolfSSL / wolfCrypt](https://www.wolfssl.com/) as its crypto backend an ## Protocol Session Flow -The primary session establishment sequence is: +Standard (certificate) requester: `GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> GET_DIGESTS -> GET_CERTIFICATE -> KEY_EXCHANGE -> FINISH` +TCG binding (identity-key mode, no `GET_CAPABILITIES`/`NEGOTIATE_ALGORITHMS` +since Algorithm Set B is fixed): + +`GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH` + After `FINISH`, secured messaging and maintenance operations are available. ## Quick Links diff --git a/docs/Message-Chunking.md b/docs/Message-Chunking.md index 79cfb41..07b367c 100644 --- a/docs/Message-Chunking.md +++ b/docs/Message-Chunking.md @@ -1,102 +1,79 @@ -# Message Chunking (CHUNK_GET) +# Message Chunking (CHUNK_SEND / CHUNK_GET) SPDM 1.2 added a *Large SPDM message transfer mechanism* (DSP0274 Sec. 10.27): -when a response is larger than the requester's `DataTransferSize`, the responder -returns `ERROR(LargeResponse)` and the requester fetches the message in pieces -with `CHUNK_GET` / `CHUNK_RESPONSE`, then reassembles it. wolfSPDM implements the -requester (CHUNK_GET) side. - -This is what lets ML-DSA-87 work over the wire: its KEY_EXCHANGE_RSP / -CHALLENGE_AUTH / signed MEASUREMENTS (~4.7–4.8 KB) exceed the common -DataTransferSize (spdm-emu advertises 4608 B), so the responder chunks them. +when a message is larger than a peer's `DataTransferSize`, it is split into +pieces and reassembled on the other end. wolfSPDM implements **both** +directions — `CHUNK_SEND` for large outbound requests and `CHUNK_GET` for +large inbound responses — in the clear and inside secured sessions, once both +sides negotiate `CHUNK_CAP`. + +This is what lets ML-DSA-87 and ML-KEM work over the wire: ML-DSA-87 signed +responses (KEY_EXCHANGE_RSP, CHALLENGE_AUTH, signed MEASUREMENTS, ~4.6-4.8 KB) +exceed common `DataTransferSize` values, and an ML-KEM KEY_EXCHANGE request +carries a multi-hundred-byte encapsulation key. See +[[Post-Quantum ML-DSA]] and [[Post-Quantum ML-KEM]]. ## How it works -- In GET_CAPABILITIES wolfSPDM advertises `CHUNK_CAP` and a `DataTransferSize` - equal to the **MTU** (`WOLFSPDM_CHUNK_BUF_SIZE`). The responder splits any - response larger than that. -- Reassembly is **transparent**: it hooks the two transport functions - (`wolfSPDM_SendReceive` for cleartext KEY_EXCHANGE / CHALLENGE, and - `wolfSPDM_SecuredExchange` for the encrypted GET_MEASUREMENTS), so every parser - sees a complete logical message and the transcript/hash stay correct. The chunk - transport messages themselves are not hashed. -- **Zero dynamic allocation**: a single fixed `WOLFSPDM_CHUNK_BUF_SIZE` buffer in - the context holds one CHUNK_RESPONSE; the reassembled message lands in the - caller's existing message buffer. - -The `ChunkSeqNo` field is `u16` for SPDM < 1.4 and `u32` for ≥ 1.4; wolfSPDM -emits the version-appropriate form. The first chunk (`ChunkSeqNo == 0`) carries -`LargeMessageSize`, which is bounds-checked against the output buffer. +`src/spdm_chunk.c` implements the engine: + +- `wolfSPDM_ChunkExchange` is the entry point used from the session/attest + code (in the clear for KEY_EXCHANGE, or via `wolfSPDM_SecuredExchange` for + in-session requests like GET_MEASUREMENTS). It sends the request in one + shot when it fits the negotiated transfer limit, chunks it with + `CHUNK_SEND` when it doesn't, and reassembles a chunked response with + `CHUNK_GET` when the reply is `ERROR(LargeResponse)`. +- `wolfSPDM_ChunkSend` splits an outbound message into `CHUNK_SEND` requests. + Only the last `CHUNK_SEND_ACK` carries the actual response (or an + `ERROR(LargeResponse)` handing off to `CHUNK_GET` when the response itself + needs chunking). +- `wolfSPDM_ChunkGet` reassembles a chunked response by issuing `CHUNK_GET` + requests and validating each `CHUNK_RESPONSE`'s `Handle`, `ChunkSeqNo`, and + `LargeMessageSize`/`ChunkSize` bounds before copying it into the caller's + buffer. +- `ChunkSeqNo` is `u16` for SPDM < 1.4 and `u32` for >= 1.4; wolfSPDM emits + and checks the version-appropriate width and never lets the 16-bit counter + wrap (`WOLFSPDM_E_CHUNK` if it would). +- **Zero dynamic allocation**: chunk buffers are fixed + `WOLFSPDM_DATA_TRANSFER_SIZE` stack arrays; the reassembled message lands in + the caller's existing message buffer. + +## Without chunking + +`wolfSPDM_ClearExchange` (the non-session request path) checks the request +against the responder's negotiated `DataTransferSize` even when +`WOLFSPDM_NO_CHUNK` is defined or `CHUNK_CAP` was not negotiated: a clear +request larger than that limit is refused locally with +`WOLFSPDM_E_BUFFER_SMALL` — wolfSPDM never emits an oversized, non-conformant +message. ## Compile-time configuration | Macro / option | Default | Effect | |----------------|---------|--------| -| `--disable-chunking` / `WOLFSPDM_NO_CHUNK` | enabled | Compile the engine out entirely (no `CHUNK_CAP` advertised) | -| `WOLFSPDM_CHUNK_BUF_SIZE` | `4096` | MTU = advertised DataTransferSize = transport buffer size. Lower it for constrained devices (smaller buffer, more round-trips) | -| `WOLFSPDM_CHUNK_NO_SECURED` | — | Keep cleartext chunking but compile out the encrypted (in-session MEASUREMENTS) path | -| `WOLFSPDM_CHUNK_MAX_CHUNKS` | `64` | Reassembly loop guard (max chunks per message) | +| `--disable-chunking` / `WOLFSPDM_NO_CHUNK` | enabled | Compiles the engine out entirely (no `CHUNK_CAP` advertised); implied by `WOLFSPDM_NO_CERT` | +| `WOLFSPDM_DATA_TRANSFER_SIZE` | `WOLFSPDM_MAX_MSG_SIZE` | The largest single message sent or received (the chunk MTU). Must be 42 to `WOLFSPDM_MAX_MSG_SIZE`; below `WOLFSPDM_MAX_MSG_SIZE` requires chunking to be enabled (a compile-time `#error` enforces both) | -The configure summary prints `Chunking: enabled|disabled`. - -`WOLFSPDM_CHUNK_BUF_SIZE` has a hard floor of 64 (it must hold a CHUNK_RESPONSE -header plus payload). +The configure summary prints `Chunking: yes|no`. ## Memory and interop notes -- **Advertised DataTransferSize drops to the MTU.** With chunking enabled, - GET_CAPABILITIES advertises `DataTransferSize = WOLFSPDM_CHUNK_BUF_SIZE` (4096 - by default) rather than `WOLFSPDM_MAX_MSG_SIZE`. This is the intended - constrained-device tradeoff — the responder chunks anything larger. A - responder that does **not** implement `CHUNK_CAP` must then keep every single - response within that MTU; raise `WOLFSPDM_CHUNK_BUF_SIZE` if you need a larger - single-message limit while still reassembling anything above it. Two ceilings - apply: it must stay `<= WOLFSPDM_MAX_MSG_SIZE` (the secured path decrypts each - chunk through a `WOLFSPDM_MAX_MSG_SIZE` stage buffer — a compile-time `#error` - enforces this), and raising it enlarges the in-context `chunkBuf`, so bump - `WOLFSPDM_CTX_STATIC_SIZE` to match (a `_Static_assert` in `spdm_context.c` - enforces that — in ML-DSA builds the context already sits ~1 KB under the cap). -- **Secured path stack.** In-session reassembly (GET_MEASUREMENTS) encrypts each - CHUNK_GET and decrypts each CHUNK_RESPONSE. Nested under - `wolfSPDM_SecuredExchange`'s frame plus the AEAD scratch buffers, peak stack - approaches ~30 KB in ML-DSA builds during a chunked GET_MEASUREMENTS (it - scales with `WOLFSPDM_MAX_MSG_SIZE` and `WOLFSPDM_CHUNK_BUF_SIZE`). Lower the - MTU or use `WOLFSPDM_CHUNK_NO_SECURED` on stack-constrained targets. -- **Untrusted input.** Every CHUNK_RESPONSE byte is responder-controlled; the - reassembler validates `ChunkSize` with overflow-safe (subtraction) bounds, - echoes of `Handle`/`ChunkSeqNo`, and the per-message and total length before - any copy. - -## Why CHUNK_GET only (no CHUNK_SEND) - -The mechanism has two directions, controlled by different endpoints: - -- `CHUNK_GET` pulls a large **response** the responder chose to split. The - requester has no control over a responder's reply size (an ML-DSA-87 signature - is 4627 B regardless), so it must be able to reassemble one. wolfSPDM - implements this. -- `CHUNK_SEND` pushes a large **request** in pieces. This is requester-initiated: - the requester decides to chunk its own outbound request; a responder cannot - force it. - -Almost every request wolfSPDM builds (GET_VERSION through GET_MEASUREMENTS, -FINISH) is small and fixed, well under any responder's `DataTransferSize`. The -one exception is an **ML-KEM** KEY_EXCHANGE, whose `ExchangeData` carries the -encapsulation key `ek` (800/1184/1568 B for ML-KEM-512/768/1024) — a request of -~870–1640 B. This still fits common responders (e.g. spdm-emu advertises -4608 B), but a constrained responder could advertise a smaller -`DataTransferSize`. Because `CHUNK_SEND` is unimplemented, `wolfSPDM_KeyExchange` -**fails fast** (`WOLFSPDM_E_BUFFER_SMALL`) when the built request exceeds the -responder's `DataTransferSize` rather than emit a non-conformant oversized -message — so the library never sends something it cannot chunk. - -`CHUNK_CAP` advertises support for the large-message mechanism; it does not -obligate an endpoint to chunk requests it never sends. `CHUNK_SEND` / -`CHUNK_SEND_ACK` are defined in `spdm_types.h` for completeness but intentionally -unimplemented; the fail-fast guard keeps that conformant. +- **`WOLFSPDM_DATA_TRANSFER_SIZE` is the advertised MTU.** GET_CAPABILITIES + advertises this value as `DataTransferSize`. Lowering it shrinks transport + buffers (more round-trips under chunking); it can never exceed + `WOLFSPDM_MAX_MSG_SIZE`, which bounds `MaxSPDMmsgSize` and every + single-message stage buffer. +- **The TCG binding is never chunked.** `WOLFSPDM_XFER_MSG_SIZE` is + `WOLFSPDM_MAX_MSG_SIZE` under `WOLFSPDM_TCG` (single-message buffers only) + and `WOLFSPDM_DATA_TRANSFER_SIZE` otherwise. +- **Untrusted input.** Every `CHUNK_SEND_ACK`/`CHUNK_RESPONSE` byte is + peer-controlled; the reassembler validates `ChunkSize` with overflow-safe + (subtraction) bounds, echoes of `Handle`/`ChunkSeqNo`, and the per-message + and total length before any copy. ## References -- DMTF DSP0274 1.4.0 — Sec. 10.27 (Large SPDM message transfer), Tables 68 / 101–105 -- `ERROR(LargeResponse)` = error code `0x0F`; `CHUNK_GET` = `0x86`, - `CHUNK_RESPONSE` = `0x06`; `CHUNK_CAP` = `0x00020000` +- DMTF DSP0274 1.4.0 — Sec. 10.27 (Large SPDM message transfer) +- `ERROR(LargeResponse)` = error code `0x0F`; `CHUNK_SEND` = `0x85`, + `CHUNK_GET` = `0x86`, `CHUNK_SEND_ACK` = `0x05`, `CHUNK_RESPONSE` = `0x06`; + `CHUNK_CAP` = `0x00020000` diff --git a/docs/Post-Quantum-ML-DSA.md b/docs/Post-Quantum-ML-DSA.md index add34c1..1a7aa3d 100644 --- a/docs/Post-Quantum-ML-DSA.md +++ b/docs/Post-Quantum-ML-DSA.md @@ -2,24 +2,26 @@ SPDM 1.4 (DMTF DSP0274 1.4.0) adds post-quantum cryptography: **ML-DSA** (FIPS 204) for signatures and **ML-KEM** (FIPS 203) for key exchange. wolfSPDM -implements the requester side of **ML-DSA signature verification**, dual-stacked -alongside the classical ECDSA P-384 profile. +implements the requester side of **ML-DSA signature verification** and +certificate-chain validation, dual-stacked alongside the classical ECDSA +P-384 profile. ML-DSA rides the certificate flow, so it is only available +when the standard requester is built (not `WOLFSPDM_NO_CERT`). All three parameter sets work over the wire: ML-DSA-87's larger responses are -reassembled with **SPDM 1.2 message chunking** ([[Message Chunking]]). ML-KEM -**key exchange** is also implemented ([[Post-Quantum ML-KEM]]); combining the two +reassembled with SPDM message chunking ([[Message Chunking]]). ML-KEM key +exchange is also implemented ([[Post-Quantum ML-KEM]]); combining the two gives a **fully post-quantum SPDM handshake** (ML-KEM key exchange + ML-DSA authentication, no classical asymmetric crypto). ## How negotiation works In `NEGOTIATE_ALGORITHMS`, wolfSPDM advertises ECDSA P-384 in `BaseAsymAlgo` -**and** ML-DSA-44 / ML-DSA-65 / ML-DSA-87 in the SPDM 1.4 `PqcAsymAlgo` field -(8-byte field at offset 16). Per DSP0274 1.4, the responder selects exactly one -signature algorithm across `BaseAsymSel` and `PqcAsymSel` (offset 20 in -`ALGORITHMS`). wolfSPDM records the choice, pins the certificate's parameter set -to it, and verifies `KEY_EXCHANGE_RSP`, `CHALLENGE_AUTH`, and signed -`MEASUREMENTS` with whichever family was negotiated. +**and** whichever ML-DSA parameter sets the linked wolfSSL was built with, in +the SPDM 1.4 `PqcAsymAlgo` field. Per DSP0274 1.4, the responder selects +exactly one signature algorithm across `BaseAsymSel` and `PqcAsymSel`. +wolfSPDM records the choice and verifies `KEY_EXCHANGE_RSP`, +`CHALLENGE_AUTH`, and signed `MEASUREMENTS` with whichever family was +negotiated. | PqcAsymSel bit | Algorithm | SigLen | Public key | |----------------|-----------|--------|------------| @@ -27,6 +29,36 @@ to it, and verifies `KEY_EXCHANGE_RSP`, `CHALLENGE_AUTH`, and signed | `0x02` | ML-DSA-65 | 3309 B | 1952 B | | `0x04` | ML-DSA-87 | 4627 B | 2592 B | +## Certificate chain verification + +`wolfSPDM_ValidateCertChain` (`src/spdm_standard.c`) walks the retrieved +chain link by link: + +- If a trusted root CA is configured (`wolfSPDM_SetTrustedCAs`), its SHA-384 + hash must match the chain header's `RootHash`, and the root itself signs + the next certificate in the chain. +- Each subsequent certificate must be signed by the one before it — either + ECDSA-SHA384 (`ECDSAk`), or pure ML-DSA (`wc_MlDsaKey_VerifyCtx` with an + empty context) when the issuer carries an ML-DSA key. +- The leaf key must match the negotiated signature algorithm: for ML-DSA, its + parameter set (OID) must equal the negotiated level; for ECDSA, it must + decode as a P-384 key. A pinned responder key + (`wolfSPDM_SetResponderPubKey`) is compared against the leaf instead of + being trusted from the chain. +- Without a trusted root, a pinned key, or `wolfSPDM_AllowUntrustedCerts`, + validation fails with `WOLFSPDM_E_CERT_FAIL`. + +## Responder key handling + +The negotiated-algorithm responder public key is kept as raw bytes in the +context (`ctx->rspPubKey`, the ML-DSA public key or a P-384 point). An +`MlDsaKey` is not kept live in the context: `wolfSPDM_MlDsaVerify` +(`src/spdm_crypto.c`) allocates one on the stack for each verification (a +static local when `WOLFSPDM_DYNAMIC_MEMORY` is off) and imports the raw key +with `wc_MlDsaKey_ImportPubRaw` before calling `wc_MlDsaKey_VerifyCtx`; with +`--enable-dynamic-mem`, the key is heap-allocated instead of living on the +stack for that call. + ## Signing construction (DSP0274 1.4 §15.5) ML-DSA uses **Algorithm 2 (pure `ML-DSA.Sign`)**, not the pre-hash variant: @@ -40,17 +72,16 @@ ML-DSA uses **Algorithm 2 (pure `ML-DSA.Sign`)**, not the pre-hash variant: string) wolfSPDM verifies with `wc_MlDsaKey_VerifyCtx(key, sig, sigLen, ctx, ctxLen, M, -mLen, &res)`. Public keys are imported from the leaf certificate with -`wc_MlDsaKey_PublicKeyDecode`, pinned to the negotiated parameter set. +mLen, &res)`. ## Building ML-DSA follows the linked wolfSSL automatically: it is enabled when wolfSSL -reports `WOLFSSL_HAVE_MLDSA` and provides the `wc_MlDsaKey` context API (wolfSSL -master or a release that ships it; build wolfSSL with `--enable-mldsa`). The -capability is detected at configure time — wolfSPDM does not gate on a wolfSSL -version number, since master and the matching stable can report the same -`LIBWOLFSSL_VERSION_HEX`. +reports `WOLFSSL_HAVE_MLDSA` and provides the `wc_MlDsaKey` context API +(build wolfSSL with `--enable-mldsa`), the standard requester is built +(not `WOLFSPDM_NO_CERT`), and `--disable-mldsa` was not passed. The +capability is detected at configure time — wolfSPDM does not gate on a +wolfSSL version number. ```sh # wolfSSL with ML-DSA @@ -63,21 +94,18 @@ make && make install make && make check ``` -The configure summary prints `ML-DSA: enabled|disabled`. +The configure summary prints `ML-DSA: yes|no`. ## Memory note -PQC signatures, public keys, and certificate chains are multi-kilobyte, so the -buffer and static-context sizes grow when ML-DSA is built in (see -[[Configuration and Macros]]). The signature-bearing responses also use larger -on-stack receive buffers in ML-DSA builds — `wolfSPDM_GetMeasurements` uses -`WOLFSPDM_MAX_MSG_SIZE` (8 KB) and `wolfSPDM_KeyExchange` / -`wolfSPDM_Challenge` use `WOLFSPDM_SIG_RSP_BUF` (~5.3 KB) — so size embedded -thread/task stacks accordingly. ML-DSA-44 and ML-DSA-65 responses fit a single -SPDM message at the common DataTransferSize (spdm-emu uses 4608 B). ML-DSA-87 -responses (sig 4627 B) exceed that, so the responder splits them and wolfSPDM -reassembles via CHUNK_GET — see [[Message Chunking]] for the engine and its -compile-time knobs. +PQC signatures, public keys, and certificate chains are multi-kilobyte, so +`WOLFSPDM_CTX_STATIC_SIZE` grows to 73728 bytes when ML-DSA is built in (32768 +classical, 40960 ML-KEM only — see [[Configuration and Macros]]). Measured +`sizeof(WOLFSPDM_CTX)` on arm64 is roughly 59 KB with ML-DSA, well under that +cap. ML-DSA-44 and ML-DSA-65 responses fit a single SPDM message at common +`DataTransferSize` values; ML-DSA-87 responses (sig 4627 B) typically exceed +it, so the responder chunks them and wolfSPDM reassembles via +`CHUNK_GET` — see [[Message Chunking]]. ## References diff --git a/docs/Post-Quantum-ML-KEM.md b/docs/Post-Quantum-ML-KEM.md index 3cbcb16..11133aa 100644 --- a/docs/Post-Quantum-ML-KEM.md +++ b/docs/Post-Quantum-ML-KEM.md @@ -3,6 +3,8 @@ SPDM 1.4 (DMTF DSP0274 1.4.0) adds **ML-KEM** (FIPS 203) as a post-quantum **key-exchange** method. wolfSPDM implements the requester side, advertised alongside the classical ECDHE P-384 group so the responder selects one. +ML-KEM rides the certificate flow, so it is only available when the standard +requester is built (not `WOLFSPDM_NO_CERT`). ML-KEM in SPDM 1.4 is **standalone, not hybrid** — DSP0274 §23.5 states "key encapsulation (ML-KEM) for session establishment. **No support for hybrid @@ -14,33 +16,38 @@ post-quantum SPDM handshake**. ## How it works (DSP0274 1.4 §10.17.2) 1. The requester generates an ephemeral ML-KEM key pair and sends the - **encapsulation key `ek`** as the `KEY_EXCHANGE` `ExchangeData` (replacing the - 96-byte ECDHE X‖Y point). + **encapsulation key `ek`** as the `KEY_EXCHANGE` `ExchangeData` (replacing + the 96-byte ECDHE X‖Y point). The ephemeral key lives in the context as a + union of `ecc_key` and `MlKemKey` (`ctx->ephemeral`) — only one is ever + live per session. 2. The responder encapsulates, returning the **ciphertext `c`** as the `KEY_EXCHANGE_RSP` `ExchangeData` (alongside its signature and HMAC). -3. The requester **decapsulates** `c` with its decapsulation key `dk` to recover - the 32-byte shared secret `K′`, which drives the key schedule (§12.2). TH1/TH2 - and all downstream derivation are unchanged from the ECDHE path. +3. The requester **decapsulates** `c` with its decapsulation key `dk` to + recover the 32-byte shared secret `K′`, which drives the key schedule + (§12.2). TH1/TH2 and all downstream derivation are unchanged from the + ECDHE path. Per FIPS 203 implicit rejection, a `K′ ≠ K` mismatch surfaces only as a FINISH -integrity-check failure and is handled like any other session-message failure. +integrity-check failure and is handled like any other session-message +failure. ## How negotiation works In `NEGOTIATE_ALGORITHMS`, wolfSPDM advertises a `KEMAlg` `AlgStruct` -(`AlgType = 0x07`, DSP0274 1.4 Table 24) with the supported ML-KEM sets, dual-stack -alongside the DHE group. The responder selects **exactly one** key-exchange -method — a DHE group **or** a KEM, never both (no hybrid). wolfSPDM enforces that -mutual exclusivity when parsing `ALGORITHMS`. +(`AlgType = 0x07`, DSP0274 1.4 Table 24) with the ML-KEM sets the linked +wolfSSL was built with, dual-stack alongside the DHE group. The responder +selects **exactly one** key-exchange method — a DHE group **or** a KEM, never +both (no hybrid). wolfSPDM enforces that mutual exclusivity when parsing +`ALGORITHMS`. | KEMAlg bit | Algorithm | `ek` (request) | ciphertext `c` (response) | shared secret | |-----------|-----------|----------------|---------------------------|---------------| -| `0x01` | ML-KEM-512 | 800 B | 768 B | 32 B | -| `0x02` | ML-KEM-768 | 1184 B | 1088 B | 32 B | -| `0x04` | ML-KEM-1024 | 1568 B | 1568 B | 32 B | +| `0x0001` | ML-KEM-512 | 800 B | 768 B | 32 B | +| `0x0002` | ML-KEM-768 | 1184 B | 1088 B | 32 B | +| `0x0004` | ML-KEM-1024 | 1568 B | 1568 B | 32 B | -By default wolfSPDM advertises ECDHE **and** all three ML-KEM sets. To force a -PQC-only key exchange (e.g. for testing), pin it at runtime: +By default wolfSPDM advertises ECDHE **and** every ML-KEM set built in. To +force a PQC-only key exchange (e.g. for testing), pin it at runtime: ```c /* Advertise only ML-KEM-768 (advDhe = 0). The responder must use it or fail. */ @@ -53,8 +60,10 @@ than silently downgrading to DHE. ## Building ML-KEM follows the linked wolfSSL automatically: it is enabled when wolfSSL -reports `WOLFSSL_HAVE_MLKEM` and provides the `wc_MlKemKey` API (build wolfSSL -with `--enable-mlkem`). The capability is detected at configure time. +reports `WOLFSSL_HAVE_MLKEM` and provides the `wc_MlKemKey` API (build +wolfSSL with `--enable-mlkem`), the standard requester is built (not +`WOLFSPDM_NO_CERT`), and `--disable-mlkem` was not passed. The capability is +detected at configure time. ```sh # wolfSSL with ML-KEM (and ML-DSA for a fully post-quantum handshake) @@ -67,7 +76,7 @@ make && make install make && make check ``` -The configure summary prints `ML-KEM: enabled|disabled`. wolfSPDM uses the +The configure summary prints `ML-KEM: yes|no`. wolfSPDM uses the `wc_MlKemKey_*` API only (`Init`, `MakeKey`, `EncodePublicKey`, `Decapsulate`, the size getters, `Free`); the legacy `wc_KyberKey_*` aliases are not used. @@ -83,23 +92,24 @@ The demo selects a key exchange with `--kex`: ML-KEM only changes the key-exchange `ExchangeData`; everything downstream is unchanged. Two size effects: -- **Larger KEY_EXCHANGE request.** The `ek` (up to 1568 B for ML-KEM-1024) makes - the request ~870–1640 B, vs ~158 B for ECDHE. This still fits common responders - (spdm-emu advertises 4608 B), but wolfSPDM implements only `CHUNK_GET` (response - reassembly), not `CHUNK_SEND` (request fragmentation). If the request exceeds the - responder's advertised `DataTransferSize`, `wolfSPDM_KeyExchange` **fails fast** - (`WOLFSPDM_E_BUFFER_SMALL`) rather than emit a non-conformant oversized message - — see [[Message Chunking]]. -- **Static context.** The ephemeral ML-KEM key lives in the context (a union with - the classical `ecc_key`; only one is ever live), so ML-KEM-only builds use a - larger `WOLFSPDM_CTX_STATIC_SIZE` than the classical profile (see - [[Configuration and Macros]]). A fully post-quantum (ML-KEM + ML-DSA) build uses - the ML-DSA budget, which already covers it. - -When ML-KEM and ML-DSA are combined, an ML-DSA-87 signed response plus the ML-KEM -ciphertext can exceed the DataTransferSize, so the responder chunks it and -wolfSPDM reassembles via CHUNK_GET — the full-PQ path exercises ML-KEM, ML-DSA, -and chunking together. +- **Larger KEY_EXCHANGE request.** The `ek` (up to 1568 B for ML-KEM-1024) + makes the request larger than the ~158 B classical ECDHE request. If it + exceeds the negotiated `DataTransferSize`, wolfSPDM sends it with + `CHUNK_SEND` when the responder has negotiated `CHUNK_CAP` — see + [[Message Chunking]]. Without chunking support on either side, a request + that exceeds `DataTransferSize` is refused locally with + `WOLFSPDM_E_BUFFER_SMALL` rather than sent oversized. +- **Static context.** The ephemeral ML-KEM key lives in the context (a union + with the classical `ecc_key`; only one is ever live), so ML-KEM-only builds + use a larger `WOLFSPDM_CTX_STATIC_SIZE` (40960) than the classical profile + (32768) — see [[Configuration and Macros]]. A fully post-quantum + (ML-KEM + ML-DSA) build uses the ML-DSA budget (73728), which already + covers it. + +When ML-KEM and ML-DSA are combined, an ML-DSA-87 signed response plus the +ML-KEM ciphertext can exceed the `DataTransferSize`, so the responder chunks +it and wolfSPDM reassembles via `CHUNK_GET` — the full-PQ path exercises +ML-KEM, ML-DSA, and chunking together. ## References diff --git a/docs/Project-Structure.md b/docs/Project-Structure.md index 59f1e00..e958b69 100644 --- a/docs/Project-Structure.md +++ b/docs/Project-Structure.md @@ -4,11 +4,12 @@ | Path | Purpose | |------|---------| -| `src/` | Core protocol, crypto glue, transcript, secured messaging, and session logic | -| `wolfspdm/` | Public headers (`spdm.h`, `spdm_types.h`, `spdm_error.h`) | +| `src/` | Core protocol, crypto glue, transcript, secured messaging, session logic, and TPM/TCG side | +| `src/vendor/` | Nuvoton and Nations TPM-specific vendor commands | +| `wolfspdm/` | Public headers | | `examples/` | Demo client and emulator integration script | -| `test/` | Unit tests and emulator smoke test | -| `docs/` | Project documentation (including attestation notes) | +| `test/` | Unit tests and test certificate data | +| `docs/` | Project documentation (this wiki, mirrored under `docs/`) | | `.github/workflows/` | CI workflows | ## Core source modules @@ -16,21 +17,48 @@ | File | Responsibility | |------|----------------| | `src/spdm_context.c` | Context init/free lifecycle and state setup | -| `src/spdm_msg.c` | SPDM message construction and parsing | -| `src/spdm_crypto.c` | Cryptographic helper operations | +| `src/spdm_msg.c` | SPDM message construction, parsing, and signature verification helpers | +| `src/spdm_crypto.c` | Cryptographic helper operations (ECDSA/ML-DSA verify, hashing) | | `src/spdm_kdf.c` | HKDF-based key derivation | | `src/spdm_transcript.c` | Transcript management (TH computations) | -| `src/spdm_secured.c` | Secured message protection (AES-256-GCM) | -| `src/spdm_session.c` | Handshake/session flow and higher-level operations | -| `src/spdm_internal.h` | Internal types, constants, and internal APIs | +| `src/spdm_secured.c` | Secured message protection (AES-256-GCM), app data (`SendData`/`ReceiveData`/`Encrypt`/`DecryptMessage`) | +| `src/spdm_session.c` | Handshake exchange helper, `KeyExchange`/`Finish`, `Heartbeat`, `KeyUpdate` | +| `src/spdm_internal.h` | Internal types, constants, and internal APIs shared across `src/` | + +## Standard (certificate) requester modules — built with `BUILD_CERT` + +Compiled when the standard requester is enabled (`--disable-cert` removes +these; requires `WOLFSPDM_NO_CERT` not set): + +| File | Responsibility | +|------|----------------| +| `src/spdm_standard.c` | `GET_CAPABILITIES`/`NEGOTIATE_ALGORITHMS`/`GET_DIGESTS`/`GET_CERTIFICATE`, certificate-chain validation, trust anchor configuration, `Connect` | +| `src/spdm_attest.c` | `GET_MEASUREMENTS` and `CHALLENGE`, L1/L2 and M1 running transcript hashes | +| `src/spdm_chunk.c` | `CHUNK_SEND`/`CHUNK_GET` large-message chunking engine | + +## TPM/TCG side modules + +| File | Built with | Responsibility | +|------|-----------|-----------------| +| `src/spdm_tcg.c` | `--enable-tcg` (`BUILD_TCG`) | TCG SPDM binding message framing, vendor-command helpers, identity-key exchange, `ConnectTCG` | +| `src/spdm_psk.c` | `--enable-psk` (`BUILD_PSK`) | `PSK_EXCHANGE`/`PSK_FINISH`, PSK key derivation, `ConnectPsk` | +| `src/vendor/spdm_nuvoton.c` | `--enable-nuvoton` (`BUILD_NUVOTON`) | Nuvoton NPCT75x status/lock vendor commands | +| `src/vendor/spdm_nations.c` | `--enable-nations` (`BUILD_NATIONS`) | Nations NS350 status/lock/PSK-provisioning vendor commands | +| `src/spdm_responder.c` | `--enable-responder` (`BUILD_RESPONDER`) | SPDM responder: answers requester-driven messages over the TCG binding, TPM command tunneling | ## Public API surface | Header | Content | |--------|---------| -| `wolfspdm/spdm.h` | Main public API and feature macros | -| `wolfspdm/spdm_types.h` | SPDM protocol constants and algorithm identifiers | +| `wolfspdm/spdm.h` | Main public API, `WOLFSPDM_MODE`, feature macros (`WOLFSPDM_HAS_*`) | +| `wolfspdm/spdm_types.h` | SPDM protocol constants, algorithm identifiers, build-switch implications | | `wolfspdm/spdm_error.h` | Error code enum and error-string helper | +| `wolfspdm/spdm_tcg.h` | TCG SPDM binding framing, vendor-command codes and helpers | +| `wolfspdm/spdm_nuvoton.h` | Nuvoton-specific status API | +| `wolfspdm/spdm_nations.h` | Nations-specific status/PSK-provisioning API | +| `wolfspdm/spdm_psk.h` | Shared PSK protocol API | +| `wolfspdm/spdm_responder.h` | SPDM responder API | +| `wolfspdm/options.h` | Auto-generated from `config.h` at build time | ## Build/test assets @@ -38,7 +66,8 @@ |------|---------| | `configure.ac` | Autotools configure logic and options | | `Makefile.am` | Library, test, and example build targets | -| `examples/spdm_demo.c` | CLI demo for session/measurement/challenge/heartbeat/key update | -| `examples/spdm_test.sh` | 18-case emulator integration driver | +| `examples/spdm_demo.c` | CLI demo: session, measurements, challenge, heartbeat, key update, app data, version and key-exchange selection | +| `examples/spdm_test.sh` | 21-test emulator integration driver (7 scenarios x SPDM 1.2/1.3/1.4) | | `test/unit_test.c` | Unit test coverage | -| `test/test_spdm.c` | SPDM smoke-test utility | +| `test/test_certs.h` | Classical (ECDSA P-384) test certificate chains | +| `test/test_certs_mldsa.h` | ML-DSA test certificate chains | diff --git a/docs/Supported-Operations.md b/docs/Supported-Operations.md index 1a402cc..f552c40 100644 --- a/docs/Supported-Operations.md +++ b/docs/Supported-Operations.md @@ -1,6 +1,6 @@ # Supported Operations -wolfSPDM implements requester-side SPDM operations for session establishment, secure data exchange, attestation, and session maintenance. +wolfSPDM implements requester-side SPDM operations for session establishment, secure data exchange, attestation, and session maintenance, plus the TCG binding, TPM vendor commands, PSK and a responder for TPM builds. ## Operation coverage @@ -15,12 +15,17 @@ wolfSPDM implements requester-side SPDM operations for session establishment, se | Session finalization | FINISH | `wolfSPDM_Finish` | | One-shot full connect | Full handshake | `wolfSPDM_Connect` | | Secured app exchange | Secured messages | `wolfSPDM_SecuredExchange` | -| App send/receive helpers | Secured messages | `wolfSPDM_SendData`, `wolfSPDM_ReceiveData` | +| MCTP application messages | Secured messages (DSP0275) | `wolfSPDM_SendData`, `wolfSPDM_ReceiveData` | +| Caller-driven secured records | Secured messages | `wolfSPDM_EncryptMessage`, `wolfSPDM_DecryptMessage` | +| Large messages | CHUNK_SEND / CHUNK_GET | automatic once both sides set CHUNK_CAP | | Measurements (signed/unsigned) | GET_MEASUREMENTS | `wolfSPDM_GetMeasurements` | | Measurement block access | Measurement parsing | `wolfSPDM_GetMeasurementCount`, `wolfSPDM_GetMeasurementBlock` | | Sessionless challenge auth | CHALLENGE / CHALLENGE_AUTH | `wolfSPDM_Challenge` | | Keep-alive | HEARTBEAT | `wolfSPDM_Heartbeat` | | Session key rotation | KEY_UPDATE | `wolfSPDM_KeyUpdate` | +| TCG binding session (TPM) | TCG SPDM binding, GIVE_PUB mutual auth | `wolfSPDM_Connect` in a Nuvoton or Nations mode | +| PSK session (TPM) | PSK_EXCHANGE / PSK_FINISH | `wolfSPDM_SetPSK`, `wolfSPDM_Connect` | +| Responder (fwTPM) | TCG binding and PSK responder | `wolfSPDM_Resp*` (`spdm_responder.h`) | ## Supported protocol versions @@ -55,11 +60,13 @@ profile so the responder selects one: Enabling both yields a **fully post-quantum SPDM handshake** (ML-KEM key exchange + ML-DSA authentication). Large responses (e.g. an ML-DSA-87 signature, or ML-DSA + the ML-KEM ciphertext, exceeding the negotiated `DataTransferSize`) -are reassembled with **SPDM 1.2 message chunking** (`CHUNK_GET`); see +are reassembled with **SPDM 1.2 message chunking** (`CHUNK_GET`), and a large +ML-KEM `KEY_EXCHANGE` request is split with `CHUNK_SEND`; see [[Message Chunking]]. ## Notable implementation scope -- Requester-only implementation (no responder role) -- Designed for standards-based SPDM peers and DMTF spdm-emu +- Standard requester for standards-based SPDM peers and DMTF spdm-emu; the + responder covers the TCG binding and PSK used by wolfTPM's fwTPM +- Build switches compile out either side (see [[Configuration and Macros]]) - Trust anchor support via `wolfSPDM_SetTrustedCAs` (single DER CA cert buffer) diff --git a/docs/Testing-and-CI.md b/docs/Testing-and-CI.md index 6ada247..19026ed 100644 --- a/docs/Testing-and-CI.md +++ b/docs/Testing-and-CI.md @@ -16,15 +16,16 @@ export SPDM_EMU_PATH=../spdm-emu/build/bin ./examples/spdm_test.sh ``` -`spdm_test.sh` runs 18 scenarios: +`spdm_test.sh` runs 21 tests, seven scenarios across SPDM 1.2, 1.3 and 1.4: - Session - Signed measurements - Unsigned measurements - Challenge - Heartbeat - Key update +- Application data (PLDM GetTID as an MCTP application message) -Across SPDM versions 1.2, 1.3, and 1.4. +`SPDM_EMU_ARGS` passes extra responder options, e.g. `--cap ...,CHUNK`. ## CI workflow coverage @@ -38,25 +39,28 @@ Documented workflows include: - Empty Brace Scope Scan - CodeQL Security - Codespell -- SPDM Emulator Test (integration matrix on x64 + aarch64) +- SPDM Emulator Test (integration matrix on x64 + aarch64, plus chunking + against small-buffer responders at DataTransferSize 42 and 64) +- wolfTPM downstream: wolfTPM master built with this wolfSPDM in its 14 SPDM + configurations, its SPDM unit tests, and the fwTPM TCG and PSK end-to-end + runs; the standard requester symbols must stay out of `libwolftpm` - SPDM Emulator PQC Test — wolfSSL master + spdm-emu (OpenSSL backend) on the full x64 + aarch64 matrix. Builds wolfSPDM ML-KEM-only as well as the combined config, then runs over the wire: ML-DSA-44/65/87 (signatures), ML-KEM-512/768/1024 (key exchange), and a **fully post-quantum** leg (ML-KEM-768 + ML-DSA-65/87) for - session, measurements, and challenge. + session, measurements, challenge, heartbeat, key update and application data. See `.github/workflows/README.md` for workflow inventory details. ## ML-DSA (post-quantum signatures) test coverage - **Unit (`make check`, ML-DSA build):** PqcAsymAlgo/PqcAsymSel wire offsets and - the Base/Pqc mutual-exclusion; a real wolfSSL ML-DSA sign + verify round-trip - through `wolfSPDM_VerifyMeasurementSig` for ML-DSA-44/65/87 (with a tamper - negative); and KEY_EXCHANGE_RSP / CHALLENGE_AUTH signature-size guards. -- **Real-certificate validation:** `wolfSPDM_ExtractResponderPubKey` was - validated against the actual spdm-emu ML-DSA cert chains for all three levels - (44 -> WC_ML_DSA_44, 65 -> 65, 87 -> 87), plus a negative case where a - level-65 cert is rejected when ML-DSA-87 was negotiated (level pinning). + the Base/Pqc mutual exclusion; a real wolfSSL ML-DSA sign + verify round trip + through `wolfSPDM_VerifyRspSig` for ML-DSA-44/65/87 with tamper, wrong-context + and wrong-size negatives; and the KEY_EXCHANGE_RSP signature-size guard. +- **Certificate chains:** `wolfSPDM_ValidateCertChain` verifies every link of + the libspdm ML-DSA-44 sample chain against its root, rejects a forged leaf + signature, and rejects a leaf whose set differs from the negotiated one. - **Over-the-wire (CI):** ML-DSA-44/65/87 all complete against spdm-emu; ML-DSA-87 responses exceed the 4608 B DataTransferSize and are reassembled via the SPDM 1.2 chunking engine (see [[Message Chunking]]). @@ -67,8 +71,8 @@ See `.github/workflows/README.md` for workflow inventory details. DHE-xor-KEM mutual-exclusion, a real wolfSSL ML-KEM encapsulate/decapsulate round-trip asserting `K′ == K`, the KEY_EXCHANGE `ek` placement, the KEY_EXCHANGE_RSP ciphertext-offset math, the reconnect key-type-switch (no - type-confused free), the KEM-only-below-1.4 refusal, and the oversized-request - fail-fast guard. + type-confused free), the KEM-only-below-1.4 refusal, and the refusal of an + unchunked request above the responder's DataTransferSize. - **Over-the-wire (CI):** ML-KEM-512/768/1024 against spdm-emu (`--dhe NONE --kem ML_KEM_*`), and a **fully post-quantum** leg pairing ML-KEM-768 with ML-DSA-65/87 — the ML-DSA-87 case also exercises chunking, so ML-KEM + ML-DSA + From 765d347fcc9fa4b1994156c284a765540dba835c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 09:24:05 -0700 Subject: [PATCH 14/27] Port main's remaining unit test coverage onto the core --- test/unit_test.c | 244 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) diff --git a/test/unit_test.c b/test/unit_test.c index 8de8502..ca69f9d 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -3809,6 +3809,20 @@ static int test_mlkem_key_exchange(void) ASSERT_EQ(wolfSPDM_MlKemDecapsulate(ctx, ct, ctSz), WOLFSPDM_E_BAD_STATE, "no decapsulation with an ECDHE key"); + /* And back: an ML-KEM exchange replaces the ECDHE key */ + ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; + reqSz = sizeof(req); + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, req, &reqSz)); + ASSERT_EQ(ctx->flags.ephemeralIsKem, 1, "ML-KEM key live again"); + + /* Key generation arguments */ + ASSERT_EQ(wolfSPDM_GenerateMlKemKey(NULL, req, &reqSz), + WOLFSPDM_E_INVALID_ARG, "NULL ctx"); + ctx->kemAlgSel = 0x0008; + reqSz = sizeof(req); + ASSERT_EQ(wolfSPDM_GenerateMlKemKey(ctx, req, &reqSz), + WOLFSPDM_E_ALGO_MISMATCH, "unknown ML-KEM set"); + /* A request buffer too small for ek */ ctx->kemAlgSel = SPDM_KEM_ALGO_ML_KEM_768; reqSz = 40 + 22 + 512; @@ -3846,6 +3860,226 @@ static int test_clear_exchange_dts(void) } #endif +#ifndef WOLFSPDM_NO_MCTP +/* Secured record edges: IV layout, sequence limits, session binding and an + * empty payload */ +static int test_secured_record_edges(void) +{ + byte base[WOLFSPDM_AEAD_IV_SIZE]; + byte iv[WOLFSPDM_AEAD_IV_SIZE]; + byte plain[1] = { 0 }; + byte enc[64]; + byte dec[16]; + word32 encSz = sizeof(enc); + word32 decSz = sizeof(dec); + word32 i; + TEST_CTX_SETUP_V12(); + + printf("test_secured_record_edges...\n"); + + /* DSP0277: the little-endian sequence number XORs the leading IV bytes */ + XMEMSET(base, 0, sizeof(base)); + wolfSPDM_BuildIV(iv, base, 0x0102030405060708ULL); + for (i = 0; i < 8; i++) { + ASSERT_EQ(iv[i], (byte)(8 - i), "sequence byte position"); + } + for (i = 8; i < WOLFSPDM_AEAD_IV_SIZE; i++) { + ASSERT_EQ(iv[i], 0, "trailing IV bytes untouched"); + } + + ctx->sessionId = 0x00020001; + XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->reqDataIv, 0x22, WOLFSPDM_AEAD_IV_SIZE); + XMEMSET(ctx->rspDataIv, 0x22, WOLFSPDM_AEAD_IV_SIZE); + + /* An empty message still round trips */ + ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, 0, enc, &encSz)); + ASSERT_SUCCESS(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz)); + ASSERT_EQ(decSz, 0, "empty message"); + + /* A record for another session is refused */ + encSz = sizeof(enc); + ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, 1, enc, &encSz)); + ctx->sessionId = 0x00030001; + decSz = sizeof(dec); + ASSERT_EQ(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz), + WOLFSPDM_E_SESSION_INVALID, "session ID mismatch"); + ctx->sessionId = 0x00020001; + + /* MCTP carries 16 sequence bits and must not wrap */ + ctx->reqSeqNum = 0x10000; + encSz = sizeof(enc); + ASSERT_EQ(wolfSPDM_EncryptInternal(ctx, plain, 1, enc, &encSz), + WOLFSPDM_E_BAD_STATE, "request sequence past 16 bits"); + ctx->reqSeqNum = 0; + encSz = sizeof(enc); + ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, 1, enc, &encSz)); + ctx->rspSeqNum = 0x10000; + decSz = sizeof(dec); + ASSERT_EQ(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz), + WOLFSPDM_E_SEQUENCE, "response sequence past 16 bits"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSPDM_NO_MCTP */ + +static int test_version_and_finish_14(void) +{ + byte rsp[16]; + byte fin[WOLFSPDM_FINISH_BUF_SZ]; + word32 finSz = sizeof(fin); + TEST_CTX_SETUP(); + + printf("test_version_and_finish_14...\n"); + + /* SetMaxVersion takes 1.2 to 1.4, or 0 for the build default */ + ASSERT_EQ(wolfSPDM_SetMaxVersion(NULL, SPDM_VERSION_12), + WOLFSPDM_E_INVALID_ARG, "NULL ctx"); + ASSERT_EQ(wolfSPDM_SetMaxVersion(ctx, 0x11), WOLFSPDM_E_INVALID_ARG, + "below 1.2"); + ASSERT_EQ(wolfSPDM_SetMaxVersion(ctx, 0x15), WOLFSPDM_E_INVALID_ARG, + "above 1.4"); + ASSERT_SUCCESS(wolfSPDM_SetMaxVersion(ctx, SPDM_VERSION_13)); + + /* VERSION: the highest common entry, capped by SetMaxVersion */ + XMEMSET(rsp, 0, sizeof(rsp)); + rsp[0] = SPDM_VERSION_10; + rsp[1] = SPDM_VERSION; + rsp[5] = 3; + rsp[7] = SPDM_VERSION_12; + rsp[9] = SPDM_VERSION_13; + rsp[11] = SPDM_VERSION_14; + ASSERT_SUCCESS(wolfSPDM_ParseVersion(ctx, rsp, 12)); + ASSERT_EQ(ctx->spdmVersion, SPDM_VERSION_13, "capped at 1.3"); + ASSERT_SUCCESS(wolfSPDM_SetMaxVersion(ctx, 0)); + ASSERT_SUCCESS(wolfSPDM_ParseVersion(ctx, rsp, 12)); + ASSERT_EQ(ctx->spdmVersion, SPDM_VERSION_14, "highest common version"); + ASSERT_EQ(wolfSPDM_ParseVersion(ctx, rsp, 11), WOLFSPDM_E_VERSION_MISMATCH, + "truncated entry list"); + rsp[7] = 0x10; + rsp[9] = 0x11; + rsp[11] = 0x11; + ASSERT_EQ(wolfSPDM_ParseVersion(ctx, rsp, 12), WOLFSPDM_E_VERSION_MISMATCH, + "nothing at 1.2 or above"); + rsp[5] = 0; + ASSERT_EQ(wolfSPDM_ParseVersion(ctx, rsp, 6), WOLFSPDM_E_VERSION_MISMATCH, + "no entries"); + + /* SPDM 1.4 FINISH and FINISH_RSP carry OpaqueLength */ + ctx->spdmVersion = SPDM_VERSION_14; + ASSERT_SUCCESS(wolfSPDM_BuildFinish(ctx, fin, &finSz)); + ASSERT_EQ(finSz, (word32)(4 + 2 + WOLFSPDM_HASH_SIZE), "1.4 FINISH size"); + ASSERT_EQ(fin[0], SPDM_VERSION_14, "FINISH version"); + ASSERT_EQ(SPDM_Get16LE(&fin[4]), 0, "empty OpaqueData"); + XMEMSET(rsp, 0, sizeof(rsp)); + rsp[0] = SPDM_VERSION_14; + rsp[1] = SPDM_FINISH_RSP; + ASSERT_EQ(wolfSPDM_ParseFinishRsp(ctx, rsp, 4), WOLFSPDM_E_BUFFER_SMALL, + "OpaqueLength missing"); + SPDM_Set16LE(&rsp[4], 4); + ASSERT_EQ(wolfSPDM_ParseFinishRsp(ctx, rsp, 8), WOLFSPDM_E_BUFFER_SMALL, + "OpaqueData truncated"); + ASSERT_SUCCESS(wolfSPDM_ParseFinishRsp(ctx, rsp, 10)); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +#ifndef WOLFSPDM_NO_CERT +static int test_standard_request_fields(void) +{ + byte buf[WOLFSPDM_KEY_EX_TX_SZ]; + byte rsp[WOLFSPDM_KEY_EX_RX_SZ]; + byte dig[4 + WOLFSPDM_HASH_SIZE]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + + printf("test_standard_request_fields...\n"); + + /* KEY_EXCHANGE names the slot whose chain was fetched */ + ctx->currentSlotId = 2; + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); + ASSERT_EQ(buf[3], 2, "KEY_EXCHANGE SlotID"); +#ifndef WOLFSPDM_NO_MEAS + ctx->currentSlotId = 3; + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildGetMeasurements(ctx, buf, &bufSz, + SPDM_MEAS_OPERATION_ALL, 1)); + ASSERT_EQ(buf[4 + 32], 3, "GET_MEASUREMENTS SlotIDParam"); +#endif + + /* No responder key: the signature cannot be checked */ + XMEMSET(rsp, 0, sizeof(rsp)); + rsp[0] = SPDM_VERSION_12; + rsp[1] = SPDM_KEY_EXCHANGE_RSP; + ctx->flags.hasRspPubKey = 0; + ASSERT_EQ(wolfSPDM_ParseKeyExchangeRsp(ctx, rsp, 138 + + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE), WOLFSPDM_E_BAD_STATE, + "KEY_EXCHANGE_RSP without a responder key"); + + /* DIGESTS: the slot mask, and an ERROR recorded */ + XMEMSET(dig, 0, sizeof(dig)); + dig[0] = SPDM_VERSION_12; + dig[1] = SPDM_DIGESTS; + dig[3] = 0x05; + ASSERT_SUCCESS(wolfSPDM_ParseDigests(ctx, dig, sizeof(dig))); + ASSERT_EQ(ctx->slotMask, 0x05, "slot mask"); + dig[1] = SPDM_ERROR; + dig[2] = SPDM_ERROR_UNSUPPORTED_REQUEST; + ASSERT_EQ(wolfSPDM_ParseDigests(ctx, dig, sizeof(dig)), + WOLFSPDM_E_PEER_ERROR, "DIGESTS error"); + ASSERT_EQ(wolfSPDM_GetLastPeerError(ctx), SPDM_ERROR_UNSUPPORTED_REQUEST, + "peer error recorded"); + + /* Trust anchor arguments */ + ASSERT_EQ(wolfSPDM_SetTrustedCAs(NULL, test_ca_cert_der, + sizeof(test_ca_cert_der)), WOLFSPDM_E_INVALID_ARG, "NULL ctx"); + ASSERT_EQ(wolfSPDM_SetTrustedCAs(ctx, NULL, 16), WOLFSPDM_E_INVALID_ARG, + "NULL certificate"); + ASSERT_EQ(wolfSPDM_SetTrustedCAs(ctx, test_ca_cert_der, 0), + WOLFSPDM_E_INVALID_ARG, "empty certificate"); + ASSERT_EQ(wolfSPDM_SetTrustedCAs(ctx, test_ca_cert_der, + WOLFSPDM_MAX_TRUSTED_CA + 1), WOLFSPDM_E_BUFFER_SMALL, + "certificate too large"); + + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !WOLFSPDM_NO_CERT */ + +#if defined(WOLFSPDM_HAVE_MLDSA) && !defined(WOLFSPDM_NO_CHALLENGE) +/* CHALLENGE_AUTH bounds use the negotiated SigLen */ +static int test_challenge_auth_mldsa_sigsize(void) +{ + byte req[4 + 32]; + byte auth[4 + WOLFSPDM_HASH_SIZE + 32 + 2 + WOLFSPDM_ECC_SIG_SIZE]; + word32 sigOff = 0; + TEST_CTX_SETUP_V12(); + + printf("test_challenge_auth_mldsa_sigsize...\n"); + XMEMSET(req, 0, sizeof(req)); + req[0] = SPDM_VERSION_12; + req[1] = SPDM_CHALLENGE; + XMEMSET(auth, 0, sizeof(auth)); + auth[0] = SPDM_VERSION_12; + auth[1] = SPDM_CHALLENGE_AUTH; + XMEMSET(&auth[4], 0xCC, WOLFSPDM_HASH_SIZE); + XMEMSET(ctx->certChainHash, 0xCC, WOLFSPDM_HASH_SIZE); + ASSERT_SUCCESS(wolfSPDM_ParseChallengeAuth(ctx, req, sizeof(req), auth, + sizeof(auth), &sigOff)); + ctx->pqcAsymSel = WOLFSPDM_MLDSA_SETS & (~WOLFSPDM_MLDSA_SETS + 1); + ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, req, sizeof(req), auth, + sizeof(auth), &sigOff), WOLFSPDM_E_CHALLENGE, + "ML-DSA SigLen bounds CHALLENGE_AUTH"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif + #ifndef WOLFSPDM_NO_HEARTBEAT static int test_heartbeat_msgs(void) { @@ -5075,6 +5309,16 @@ int main(void) defined(WOLFSSL_WC_ML_KEM_768) test_mlkem_key_exchange(); #endif +#ifndef WOLFSPDM_NO_CERT + test_standard_request_fields(); +#endif +#if defined(WOLFSPDM_HAVE_MLDSA) && !defined(WOLFSPDM_NO_CHALLENGE) + test_challenge_auth_mldsa_sigsize(); +#endif +#ifndef WOLFSPDM_NO_MCTP + test_secured_record_edges(); +#endif + test_version_and_finish_14(); #ifdef WOLFSPDM_TCG test_encrypt_decrypt_roundtrip_tcg(); #endif From def71cacdf4c23723da4914afef0cf6ac9abaf89 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 10:12:22 -0700 Subject: [PATCH 15/27] Fix the Skoll review findings in session handling, TCG transport and the responder --- .github/workflows/spdm-emu-pqc-test.yml | 9 +- .github/workflows/spdm-emu-test.yml | 6 +- .github/workflows/wolfssl-versions.yml | 12 +- .github/workflows/wolftpm-downstream.yml | 15 +- docs/Post-Quantum-ML-DSA.md | 4 +- examples/spdm_demo.c | 6 +- src/spdm_attest.c | 20 +- src/spdm_context.c | 241 +++++++++++++---------- src/spdm_internal.h | 13 +- src/spdm_kdf.c | 6 +- src/spdm_msg.c | 4 +- src/spdm_psk.c | 2 +- src/spdm_responder.c | 82 ++++++-- src/spdm_secured.c | 8 +- src/spdm_session.c | 13 +- src/spdm_standard.c | 20 +- src/spdm_tcg.c | 43 ++-- test/unit_test.c | 133 ++++++++++++- wolfspdm/spdm.h | 11 +- wolfspdm/spdm_error.h | 4 + wolfspdm/spdm_responder.h | 5 +- 21 files changed, 462 insertions(+), 195 deletions(-) diff --git a/.github/workflows/spdm-emu-pqc-test.yml b/.github/workflows/spdm-emu-pqc-test.yml index 5c854c4..687fc89 100644 --- a/.github/workflows/spdm-emu-pqc-test.yml +++ b/.github/workflows/spdm-emu-pqc-test.yml @@ -82,6 +82,7 @@ jobs: # the combined build below does not. Cleaned up before the full build. --- - name: Build + test wolfSPDM ML-KEM-only (--disable-mldsa --enable-mlkem) run: | + export LD_LIBRARY_PATH="${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:$HOME/wolfssl-install/lib" ./autogen.sh ./configure --with-wolfssl=$HOME/wolfssl-install \ --disable-mldsa --enable-mlkem \ @@ -89,8 +90,6 @@ jobs: make -j"$(nproc)" make check make distclean - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib # --- wolfSPDM with ML-DSA + ML-KEM asserted on, static or dynamic memory --- - name: Build and install wolfSPDM (--enable-mldsa --enable-mlkem) @@ -103,9 +102,9 @@ jobs: make install - name: Run unit tests (includes ML-DSA verify + ML-KEM decap) - run: make check - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib + run: | + export LD_LIBRARY_PATH="${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:$HOME/wolfssl-install/lib" + make check # --- spdm-emu with OpenSSL backend (ML-DSA), cached per OS/arch --- # Cache the whole build tree, not just build/bin: the OpenSSL-backed diff --git a/.github/workflows/spdm-emu-test.yml b/.github/workflows/spdm-emu-test.yml index 248011a..0a19228 100644 --- a/.github/workflows/spdm-emu-test.yml +++ b/.github/workflows/spdm-emu-test.yml @@ -79,9 +79,9 @@ jobs: make install - name: Run unit tests - run: make check - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib + run: | + export LD_LIBRARY_PATH="${{ github.workspace }}/.libs:${{ github.workspace }}/src/.libs:$HOME/wolfssl-install/lib" + make check # --- spdm-emu (cached) --- - name: Cache spdm-emu diff --git a/.github/workflows/wolfssl-versions.yml b/.github/workflows/wolfssl-versions.yml index 80b66f7..13f9db0 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -119,9 +119,9 @@ jobs: make -j"$(nproc)" - name: Run unit tests - run: make check - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib + run: | + export LD_LIBRARY_PATH="${{ github.workspace }}/src/.libs:$HOME/wolfssl-install/lib" + make check - name: Build with --enable-dynamic-mem run: | @@ -133,9 +133,9 @@ jobs: make -j"$(nproc)" - name: Run unit tests (dynamic-mem) - run: make check - env: - LD_LIBRARY_PATH: ${{ github.workspace }}/src/.libs:${{ env.HOME }}/wolfssl-install/lib + run: | + export LD_LIBRARY_PATH="${{ github.workspace }}/src/.libs:$HOME/wolfssl-install/lib" + make check - name: Upload failure logs if: failure() diff --git a/.github/workflows/wolftpm-downstream.yml b/.github/workflows/wolftpm-downstream.yml index afffe1b..20f4dfc 100644 --- a/.github/workflows/wolftpm-downstream.yml +++ b/.github/workflows/wolftpm-downstream.yml @@ -90,9 +90,9 @@ jobs: make -j"$(nproc)" - name: Run SPDM unit tests - env: - LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }} - run: ./src/spdm/unit_test + run: | + export LD_LIBRARY_PATH="$HOME/wolfssl-install/lib" + ./src/spdm/unit_test # The TPM profile compiles none of the standard requester - name: Check the standard requester is compiled out @@ -106,9 +106,8 @@ jobs: - name: Test unavailable vendor rejection if: matrix.reject != '' - env: - LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }} run: | + export LD_LIBRARY_PATH="$HOME/wolfssl-install/lib" set -eo pipefail if [ "${{ matrix.reject }}" = "nations" ]; then expected="Nations adapter is not available in this build" @@ -166,16 +165,14 @@ jobs: make -j"$(nproc)" - name: Run wolfTPM make check - env: - LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }} run: | + export LD_LIBRARY_PATH="$HOME/wolfssl-install/lib" set -eo pipefail make check 2>&1 | tee make-check-${{ matrix.mode }}.log - name: Run spdm_test.sh ${{ matrix.mode }} - env: - LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }} run: | + export LD_LIBRARY_PATH="$HOME/wolfssl-install/lib" set -eo pipefail ./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl ${{ matrix.mode }} 2>&1 \ | tee spdm-${{ matrix.mode }}.log diff --git a/docs/Post-Quantum-ML-DSA.md b/docs/Post-Quantum-ML-DSA.md index 1a7aa3d..f0ef0bb 100644 --- a/docs/Post-Quantum-ML-DSA.md +++ b/docs/Post-Quantum-ML-DSA.md @@ -53,8 +53,8 @@ chain link by link: The negotiated-algorithm responder public key is kept as raw bytes in the context (`ctx->rspPubKey`, the ML-DSA public key or a P-384 point). An `MlDsaKey` is not kept live in the context: `wolfSPDM_MlDsaVerify` -(`src/spdm_crypto.c`) allocates one on the stack for each verification (a -static local when `WOLFSPDM_DYNAMIC_MEMORY` is off) and imports the raw key +(`src/spdm_crypto.c`) creates one for each verification (on the stack, or on +the heap with `WOLFSPDM_DYNAMIC_MEMORY`) and imports the raw key with `wc_MlDsaKey_ImportPubRaw` before calling `wc_MlDsaKey_VerifyCtx`; with `--enable-dynamic-mem`, the key is heap-allocated instead of living on the stack for that call. diff --git a/examples/spdm_demo.c b/examples/spdm_demo.c index 480b013..80d6e96 100644 --- a/examples/spdm_demo.c +++ b/examples/spdm_demo.c @@ -544,6 +544,7 @@ int main(int argc, char* argv[]) word16 kexKemOnly = 0; int opt; int rc; + int notBuilt = 0; WOLFSPDM_CTX* ctx = (WOLFSPDM_CTX*)g_ctxBuf; while ((opt = getopt_long(argc, argv, "emncbkav:hd", longOpts, NULL)) != -1) { @@ -672,6 +673,7 @@ int main(int argc, char* argv[]) default: fprintf(stderr, "Scenario not built into this wolfSPDM\n"); rc = WOLFSPDM_E_NOT_AVAILABLE; + notBuilt = 1; break; } @@ -682,8 +684,8 @@ int main(int argc, char* argv[]) done: wolfSPDM_Free(ctx); tcp_disconnect(); - /* 77 = scenario skipped (automake convention) */ - if (rc == WOLFSPDM_E_NOT_AVAILABLE) { + /* 77 = scenario skipped (automake convention), never a runtime error */ + if (notBuilt) { return 77; } return (rc == WOLFSPDM_SUCCESS) ? 0 : 1; diff --git a/src/spdm_attest.c b/src/spdm_attest.c index a1f0271..7bc126d 100644 --- a/src/spdm_attest.c +++ b/src/spdm_attest.c @@ -178,7 +178,7 @@ int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* req, word32 i; int signedReq; - SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 8); + SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 8); if (req == NULL || reqSz < 4 || sigOff == NULL) { return WOLFSPDM_E_INVALID_ARG; } @@ -285,10 +285,10 @@ int wolfSPDM_GetMeasurementBlock(WOLFSPDM_CTX* ctx, int blockIdx, const byte* blk; word32 off = 0; word32 len; + byte type = 0; int i; - if (ctx == NULL || measIndex == NULL || measType == NULL || - value == NULL || valueSz == NULL || blockIdx < 0 || + if (ctx == NULL || valueSz == NULL || blockIdx < 0 || blockIdx >= (int)ctx->measBlockCount) { return WOLFSPDM_E_INVALID_ARG; } @@ -299,19 +299,25 @@ int wolfSPDM_GetMeasurementBlock(WOLFSPDM_CTX* ctx, int blockIdx, SPDM_Get16LE(&ctx->measRecord[off + 2]); } blk = &ctx->measRecord[off]; - *measIndex = blk[0]; - *measType = 0; + if (measIndex != NULL) { + *measIndex = blk[0]; + } len = SPDM_Get16LE(&blk[2]); if (blk[1] == SPDM_MEAS_SPEC_DMTF && len >= 3 && SPDM_Get16LE(&blk[WOLFSPDM_MEAS_BLOCK_HDR_SZ + 1]) <= len - 3) { /* DMTF value: Type(1) + ValueSize(2) + Value */ - *measType = blk[WOLFSPDM_MEAS_BLOCK_HDR_SZ]; + type = blk[WOLFSPDM_MEAS_BLOCK_HDR_SZ]; len = SPDM_Get16LE(&blk[WOLFSPDM_MEAS_BLOCK_HDR_SZ + 1]); blk += 3; } blk += WOLFSPDM_MEAS_BLOCK_HDR_SZ; + if (measType != NULL) { + *measType = type; + } - if (len > *valueSz) { + /* A short or NULL value buffer learns the size it needs */ + if (value == NULL || len > *valueSz) { + *valueSz = len; return WOLFSPDM_E_BUFFER_SMALL; } XMEMCPY(value, blk, len); diff --git a/src/spdm_context.c b/src/spdm_context.c index 91c0a92..43df510 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -291,6 +291,13 @@ int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode) return WOLFSPDM_SUCCESS; } #endif +#ifndef WOLFSPDM_NO_CERT + /* Back to the standard requester */ + if (mode == WOLFSPDM_MODE_AUTO) { + ctx->mode = WOLFSPDM_MODE_AUTO; + return WOLFSPDM_SUCCESS; + } +#endif return WOLFSPDM_E_INVALID_ARG; /* Unsupported mode */ } @@ -370,6 +377,9 @@ int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) return WOLFSPDM_E_IO_FAIL; } + /* A retry or reconnect starts from sequence number 0 and fresh keys */ + wolfSPDM_ResetSession(ctx); + #ifdef WOLFSPDM_TCG if (ctx->mode == WOLFSPDM_MODE_NUVOTON || ctx->mode == WOLFSPDM_MODE_NATIONS) { @@ -390,48 +400,17 @@ int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) return WOLFSPDM_E_INVALID_ARG; } -int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) +/* Drop session state and wipe session-scoped secrets; configured identity + * keys, PSK and trust anchors stay for a later connection */ +void wolfSPDM_ResetSession(WOLFSPDM_CTX* ctx) { - int rc; - byte txBuf[8]; - byte rxBuf[16]; /* END_SESSION_ACK: 4 bytes */ - word32 txSz, rxSz; - - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (ctx->state != WOLFSPDM_STATE_CONNECTED) { - return WOLFSPDM_E_NOT_CONNECTED; - } - - /* Build END_SESSION */ - txSz = sizeof(txBuf); - rc = wolfSPDM_BuildEndSession(ctx, txBuf, &txSz); - if (rc == WOLFSPDM_SUCCESS) { - rxSz = sizeof(rxBuf); - rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); - } - if (rc == WOLFSPDM_SUCCESS) { - if (rxSz < 4) { - rc = WOLFSPDM_E_BUFFER_SMALL; - } - else if (wolfSPDM_CheckError(rxBuf, rxSz, NULL)) { - rc = WOLFSPDM_E_PEER_ERROR; - } - else if (rxSz != 4 || rxBuf[0] != ctx->spdmVersion || - rxBuf[1] != SPDM_END_SESSION_ACK || - rxBuf[2] != 0 || rxBuf[3] != 0) { - rc = WOLFSPDM_E_PEER_ERROR; - } - } - - /* Reset session state and wipe session-scoped keys; configured identity - * keys remain for a later connection */ ctx->state = WOLFSPDM_STATE_INIT; ctx->sessionId = 0; + ctx->rspSessionId = 0; ctx->reqSeqNum = 0; ctx->rspSeqNum = 0; + ctx->mutAuthRequested = 0; + ctx->reqSlotIdParam = 0; /* App data keys */ wc_ForceZero(ctx->reqDataKey, sizeof(ctx->reqDataKey)); wc_ForceZero(ctx->rspDataKey, sizeof(ctx->rspDataKey)); @@ -453,86 +432,117 @@ int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) wc_ForceZero(ctx->th1, sizeof(ctx->th1)); wc_ForceZero(ctx->th2, sizeof(ctx->th2)); wolfSPDM_FreeEphemeralKey(ctx); - - return rc; +#if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) + wolfSPDM_AttestFree(ctx); +#endif } -/* ----- I/O Helper ----- */ - -int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, - const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz) +int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) { - int rc; + int rc = WOLFSPDM_E_NOT_CONNECTED; + byte txBuf[8]; + byte rxBuf[16]; /* END_SESSION_ACK: 4 bytes */ + word32 txSz, rxSz; - if (ctx == NULL || ctx->ioCb == NULL) { - return WOLFSPDM_E_IO_FAIL; + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; } -#ifdef WOLFSPDM_TCG - if (wolfSPDM_IsTcgMode(ctx)) { - /* Wrap messages with TCG SPDM - * headers; I/O sends TCG-framed messages. */ - byte tcgTx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + - WOLFSPDM_TCG_HEADER_SIZE]; - byte tcgRx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + - WOLFSPDM_TCG_HEADER_SIZE]; - word32 tcgRxSz = sizeof(tcgRx); - int tcgTxSz; - word32 msgSize; - word32 payloadSz; - word16 tag; - - /* Detect message type: SPDM version byte 0x10-0x1F = clear message. - * Secured records start with SessionID (LE, typically 0x01 0x00...), - * which is never in the SPDM version range. */ - if (txSz > 0 && txBuf[0] >= 0x10 && txBuf[0] <= 0x1F) { - /* Clear SPDM message - wrap with TCG clear header (0x8101) */ - tcgTxSz = wolfSPDM_BuildTcgClearMessage(ctx, txBuf, txSz, - tcgTx, sizeof(tcgTx)); - } else { - /* Secured record - prepend TCG secured header (0x8201) */ - word32 totalSz; - if (txSz > sizeof(tcgTx) - WOLFSPDM_TCG_HEADER_SIZE) { - return WOLFSPDM_E_BUFFER_SMALL; + /* END_SESSION only for a live session; the wipe always runs */ + if (ctx->state == WOLFSPDM_STATE_CONNECTED) { + txSz = sizeof(txBuf); + rc = wolfSPDM_BuildEndSession(ctx, txBuf, &txSz); + if (rc == WOLFSPDM_SUCCESS) { + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + if (rxSz < 4) { + rc = WOLFSPDM_E_BUFFER_SMALL; + } + else if (wolfSPDM_CheckError(rxBuf, rxSz, NULL)) { + rc = WOLFSPDM_E_PEER_ERROR; + } + else if (rxSz != 4 || rxBuf[0] != ctx->spdmVersion || + rxBuf[1] != SPDM_END_SESSION_ACK || + rxBuf[2] != 0 || rxBuf[3] != 0) { + rc = WOLFSPDM_E_PEER_ERROR; } - totalSz = WOLFSPDM_TCG_HEADER_SIZE + txSz; - wolfSPDM_WriteTcgHeader(tcgTx, WOLFSPDM_TCG_TAG_SECURED, - totalSz, ctx->connectionHandle, ctx->fipsIndicator); - XMEMCPY(tcgTx + WOLFSPDM_TCG_HEADER_SIZE, txBuf, txSz); - tcgTxSz = (int)totalSz; } + } + wolfSPDM_ResetSession(ctx); + return rc; +} + +/* ----- I/O Helper ----- */ + +#ifdef WOLFSPDM_TCG +/* One exchange in TCG binding framing: a clear (0x8101) or secured (0x8201) + * header around the message */ +static int wolfSPDM_TcgSendReceive(WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz) +{ + byte tcgTx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + byte tcgRx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + word32 tcgRxSz = sizeof(tcgRx); + int tcgTxSz = 0; + word32 msgSize; + word32 payloadSz; + word16 tag; + int rc = WOLFSPDM_SUCCESS; + + /* Detect message type: SPDM version byte 0x10-0x1F = clear message. + * Secured records start with SessionID (LE, typically 0x01 0x00...), + * which is never in the SPDM version range. */ + if (txSz > 0 && txBuf[0] >= 0x10 && txBuf[0] <= 0x1F) { + /* Clear SPDM message - wrap with TCG clear header (0x8101) */ + tcgTxSz = wolfSPDM_BuildTcgClearMessage(ctx, txBuf, txSz, + tcgTx, sizeof(tcgTx)); if (tcgTxSz < 0) { - return tcgTxSz; + rc = tcgTxSz; } + } + else if (txSz > sizeof(tcgTx) - WOLFSPDM_TCG_HEADER_SIZE) { + rc = WOLFSPDM_E_BUFFER_SMALL; + } + else { + /* Secured record - prepend TCG secured header (0x8201) */ + tcgTxSz = (int)(WOLFSPDM_TCG_HEADER_SIZE + txSz); + wolfSPDM_WriteTcgHeader(tcgTx, WOLFSPDM_TCG_TAG_SECURED, + (word32)tcgTxSz, ctx->connectionHandle, ctx->fipsIndicator); + XMEMCPY(tcgTx + WOLFSPDM_TCG_HEADER_SIZE, txBuf, txSz); + } + if (rc == WOLFSPDM_SUCCESS) { wolfSPDM_DebugHex(ctx, "TCG TX", tcgTx, (word32)tcgTxSz); /* Send/receive via I/O callback (raw transport) */ - rc = ctx->ioCb(ctx, tcgTx, (word32)tcgTxSz, tcgRx, &tcgRxSz, - ctx->ioUserCtx); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "TCG I/O failed: %d\n", rc); - return WOLFSPDM_E_IO_FAIL; + if (ctx->ioCb(ctx, tcgTx, (word32)tcgTxSz, tcgRx, &tcgRxSz, + ctx->ioUserCtx) != 0 || tcgRxSz > sizeof(tcgRx)) { + wolfSPDM_DebugPrint(ctx, "TCG I/O failed\n"); + rc = WOLFSPDM_E_IO_FAIL; } + } + /* Strip TCG binding header from response */ + if (rc == WOLFSPDM_SUCCESS && tcgRxSz < WOLFSPDM_TCG_HEADER_SIZE) { + wolfSPDM_DebugPrint(ctx, "SendReceive: response too short (%u)\n", + tcgRxSz); + rc = WOLFSPDM_E_BUFFER_SMALL; + } + if (rc == WOLFSPDM_SUCCESS) { wolfSPDM_DebugHex(ctx, "TCG RX", tcgRx, tcgRxSz); - - /* Strip TCG binding header from response */ - if (tcgRxSz < WOLFSPDM_TCG_HEADER_SIZE) { - wolfSPDM_DebugPrint(ctx, "SendReceive: response too short (%u)\n", - tcgRxSz); - return WOLFSPDM_E_BUFFER_SMALL; - } - tag = SPDM_Get16BE(tcgRx); if (tag != WOLFSPDM_TCG_TAG_CLEAR && tag != WOLFSPDM_TCG_TAG_SECURED) { wolfSPDM_DebugPrint(ctx, "SendReceive: unexpected TCG tag " "0x%04x\n", tag); - return WOLFSPDM_E_PEER_ERROR; + rc = WOLFSPDM_E_PEER_ERROR; } - + } + if (rc == WOLFSPDM_SUCCESS) { /* Capture FIPS indicator from response if non-zero */ tag = SPDM_Get16BE(tcgRx + 10); if (tag != 0) { @@ -541,28 +551,51 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, /* Extract payload (everything after 16-byte TCG header) */ msgSize = SPDM_Get32BE(tcgRx + 2); - if (msgSize < WOLFSPDM_TCG_HEADER_SIZE || msgSize > tcgRxSz) { wolfSPDM_DebugPrint(ctx, "SendReceive: TCG size %u invalid " "(min=%u, received=%u)\n", msgSize, WOLFSPDM_TCG_HEADER_SIZE, tcgRxSz); - return WOLFSPDM_E_BUFFER_SMALL; + rc = WOLFSPDM_E_BUFFER_SMALL; } - + } + if (rc == WOLFSPDM_SUCCESS) { payloadSz = msgSize - WOLFSPDM_TCG_HEADER_SIZE; if (payloadSz > *rxSz) { - return WOLFSPDM_E_BUFFER_SMALL; + rc = WOLFSPDM_E_BUFFER_SMALL; + } + else { + XMEMCPY(rxBuf, tcgRx + WOLFSPDM_TCG_HEADER_SIZE, payloadSz); + *rxSz = payloadSz; } + } - XMEMCPY(rxBuf, tcgRx + WOLFSPDM_TCG_HEADER_SIZE, payloadSz); - *rxSz = payloadSz; + /* Clear vendor messages carry PSK material */ + wc_ForceZero(tcgTx, sizeof(tcgTx)); + wc_ForceZero(tcgRx, sizeof(tcgRx)); + return rc; +} +#endif /* WOLFSPDM_TCG */ - return WOLFSPDM_SUCCESS; +int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz) +{ + word32 cap; + + if (ctx == NULL || ctx->ioCb == NULL || rxSz == NULL) { + return WOLFSPDM_E_IO_FAIL; } -#endif /* WOLFSPDM_TCG */ - rc = ctx->ioCb(ctx, txBuf, txSz, rxBuf, rxSz, ctx->ioUserCtx); - if (rc != 0) { +#ifdef WOLFSPDM_TCG + if (wolfSPDM_IsTcgMode(ctx)) { + return wolfSPDM_TcgSendReceive(ctx, txBuf, txSz, rxBuf, rxSz); + } +#endif + + /* The callback may not report more than the buffer holds */ + cap = *rxSz; + if (ctx->ioCb(ctx, txBuf, txSz, rxBuf, rxSz, ctx->ioUserCtx) != 0 || + *rxSz > cap) { return WOLFSPDM_E_IO_FAIL; } diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 98f7450..518ee71 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -454,6 +454,14 @@ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, return WOLFSPDM_E_BUFFER_SMALL; \ } while(0) +/* A 4-byte SPDM ERROR passes so the response check can report it */ +#define SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, minSz) \ + do { \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) < 4 || \ + ((bufSz) < (minSz) && (buf)[1] != SPDM_ERROR)) \ + return WOLFSPDM_E_INVALID_ARG; \ + } while(0) + #define SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, minSz) \ do { \ if ((ctx) == NULL || (buf) == NULL || (bufSz) < (minSz)) \ @@ -504,6 +512,7 @@ WOLFSPDM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, const byte* sig, word32 sigSz); WOLFSPDM_API void wolfSPDM_FreeEphemeralKey(WOLFSPDM_CTX* ctx); +WOLFSPDM_LOCAL void wolfSPDM_ResetSession(WOLFSPDM_CTX* ctx); #ifdef WOLFSPDM_HAVE_MLDSA WOLFSPDM_API int wolfSPDM_MlDsaVerify(byte level, const byte* pub, word32 pubSz, const byte* context, word32 contextSz, @@ -527,7 +536,8 @@ WOLFSPDM_API int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, WOLFSPDM_API int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, byte* verifyData); /* Cross-TU helper, not a shipped API - WOLFSPDM_LOCAL keeps it out of the - * shared-library export table. spdm_internal.h is private to wolfSPDM. */ + * shared-library export table in wolfTPM builds. spdm_internal.h is private + * to wolfSPDM. */ WOLFSPDM_LOCAL int wolfSPDM_BuildSignedHash(byte spdmVersion, const char* contextStr, word32 contextStrLen, const byte* inputDigest, byte* outputDigest); @@ -584,7 +594,6 @@ WOLFSPDM_TEST_API int wolfSPDM_ParseDigests(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); WOLFSPDM_TEST_API int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz, word16* portionLen, word16* remainderLen); -WOLFSPDM_TEST_API int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx); WOLFSPDM_LOCAL int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx); #endif diff --git a/src/spdm_kdf.c b/src/spdm_kdf.c index 4291837..2dd506a 100644 --- a/src/spdm_kdf.c +++ b/src/spdm_kdf.c @@ -60,7 +60,9 @@ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secret labelLen = (word32)XSTRLEN(label); /* Bounds check: 2 + prefix(8) + label + context must fit in info[128] */ - if (2 + SPDM_BIN_CONCAT_PREFIX_LEN + labelLen + contextSz > sizeof(info)) { + if (labelLen > sizeof(info) || contextSz > sizeof(info) || + 2 + SPDM_BIN_CONCAT_PREFIX_LEN + labelLen + contextSz > + sizeof(info)) { return WOLFSPDM_E_BUFFER_SMALL; } @@ -76,6 +78,8 @@ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secret } rc = wc_HKDF_Expand(WC_SHA384, secret, secretSz, info, infoLen, out, outSz); + /* The context is a transcript hash */ + wc_ForceZero(info, sizeof(info)); return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } diff --git a/src/spdm_msg.c b/src/spdm_msg.c index 730d9cb..54ed597 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -446,7 +446,7 @@ int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) byte highestVersion = 0; /* No version found yet */ byte maxVer; - SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 6); + SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 6); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_VERSION, WOLFSPDM_E_VERSION_MISMATCH); /* VersionNumberEntryCount is the one-byte field at offset 5 (byte 4 @@ -505,7 +505,7 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS const byte* rspVerifyData; int rc; - SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 140); + SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 140); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_KEY_EXCHANGE_RSP, WOLFSPDM_E_KEY_EXCHANGE); /* Only the TCG binding carries a requester identity (GIVE_PUB) */ diff --git a/src/spdm_psk.c b/src/spdm_psk.c index cacaf0a..fb1a15d 100644 --- a/src/spdm_psk.c +++ b/src/spdm_psk.c @@ -137,7 +137,7 @@ int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, /* Minimum: header(4) + RspSessionID(2) + Reserved(1) + RspContextLen(2) + * OpaqueLen(2) + VerifyData(48) = 59 */ - SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 59); + SPDM_CHECK_PARSE_OR_ERROR_ARGS(ctx, buf, bufSz, 59); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_PSK_EXCHANGE_RSP, WOLFSPDM_E_KEY_EXCHANGE); diff --git a/src/spdm_responder.c b/src/spdm_responder.c index d883689..e15e9dc 100644 --- a/src/spdm_responder.c +++ b/src/spdm_responder.c @@ -178,6 +178,9 @@ int wolfSPDM_RespSetPSK(WOLFSPDM_RESP_CTX* ctx, ctx->pskHintStoreSz = 0; } ctx->flags.pskProvisioned = 1; + /* A ClearAuth registered for an earlier PSK must not clear this one */ + ctx->flags.clearAuthSet = 0; + wc_ForceZero(ctx->clearAuthDigest, sizeof(ctx->clearAuthDigest)); return WOLFSPDM_SUCCESS; #endif } @@ -272,6 +275,13 @@ void wolfSPDM_RespReset(WOLFSPDM_RESP_CTX* ctx) sizeof(ctx->ctx.rspFinishedKey)); wc_ForceZero(ctx->ctx.sharedSecret, sizeof(ctx->ctx.sharedSecret)); ctx->ctx.sharedSecretSz = 0; +#ifndef WOLFSPDM_NO_KEY_UPDATE + wc_ForceZero(ctx->ctx.reqAppSecret, sizeof(ctx->ctx.reqAppSecret)); + wc_ForceZero(ctx->ctx.rspAppSecret, sizeof(ctx->ctx.rspAppSecret)); +#endif + /* The ephemeral key and transcript would rebuild every session key */ + wolfSPDM_FreeEphemeralKey(&ctx->ctx); + wolfSPDM_TranscriptReset(&ctx->ctx); ctx->ctx.reqSeqNum = 0; ctx->ctx.rspSeqNum = 0; ctx->ctx.sessionId = 0; @@ -382,9 +392,9 @@ static int RespBuildVersion(WOLFSPDM_CTX* ctx, out[off++] = SPDM_VERSION; out[off++] = 0x00; out[off++] = 0x00; - /* VersionNumberEntryCount (LE) at offset 4. */ - out[off++] = WOLFSPDM_RESP_VERSION_COUNT; + /* Reserved, then VersionNumberEntryCount (DSP0274) */ out[off++] = 0x00; + out[off++] = WOLFSPDM_RESP_VERSION_COUNT; /* Entries: 2 bytes each, byte+1 holds the version (Major<<4 | Minor). */ for (ver = WOLFSPDM_MIN_SPDM_VERSION; ver <= WOLFSPDM_RESP_MAX_VERSION; ver++) { @@ -508,6 +518,14 @@ static int RespBuildPskExchangeRsp(WOLFSPDM_RESP_CTX* rctx, if (*outSz < 12u + 32u + WOLFSPDM_HASH_SIZE) { return WOLFSPDM_E_BUFFER_SMALL; } + reqHintLen = SPDM_Get16LE(&in[6]); + reqContextLen = SPDM_Get16LE(&in[8]); + reqOpaqueLen = SPDM_Get16LE(&in[10]); + /* Every declared variable-length field must fit within the request */ + if ((word32)12 + reqHintLen + reqContextLen + reqOpaqueLen > inSz) { + return WOLFSPDM_E_FRAMING; + } + /* Reload PSK from the persistent store - the requester-side helper * zeroes ctx->psk after derivation. */ XMEMCPY(ctx->psk, rctx->pskStore, rctx->pskStoreSz); @@ -516,15 +534,7 @@ static int RespBuildPskExchangeRsp(WOLFSPDM_RESP_CTX* rctx, XMEMCPY(ctx->pskHint, rctx->pskHintStore, rctx->pskHintStoreSz); ctx->pskHintSz = rctx->pskHintStoreSz; } - ctx->reqSessionId = SPDM_Get16LE(&in[4]); - reqHintLen = SPDM_Get16LE(&in[6]); - reqContextLen = SPDM_Get16LE(&in[8]); - reqOpaqueLen = SPDM_Get16LE(&in[10]); - /* Every declared variable-length field must fit within the request */ - if ((word32)12 + reqHintLen + reqContextLen + reqOpaqueLen > inSz) { - return WOLFSPDM_E_FRAMING; - } ctx->rspSessionId = 0xFFFE; ctx->sessionId = (word32)ctx->reqSessionId | @@ -609,6 +619,20 @@ static int RespDispatchClear(WOLFSPDM_RESP_CTX* rctx, } } + /* A handshake needs its mode, and cannot restart over a live one */ + if (code == SPDM_KEY_EXCHANGE || code == SPDM_PSK_EXCHANGE) { + if ((code == SPDM_KEY_EXCHANGE && !rctx->flags.useTcg) || + (code == SPDM_PSK_EXCHANGE && !rctx->flags.usePsk)) { + return RespBuildErrorClear(ctx, + SPDM_ERROR_UNSUPPORTED_REQUEST, code, out, outSz); + } + if (ctx->state == WOLFSPDM_STATE_KEY_EX || + ctx->state == WOLFSPDM_STATE_CONNECTED) { + return RespBuildErrorClear(ctx, + SPDM_ERROR_UNEXPECTED_REQUEST, 0, out, outSz); + } + } + /* VENDOR_DEFINED bytes don't go into the SPDM transcript - the * requester's wolfSPDM_TCG_VendorCmdClear doesn't add them, so the * responder mustn't either. GET_PUBK contributes via Ct = SHA-384 @@ -979,6 +1003,12 @@ static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, XSTRCMP(vdCode, WOLFSPDM_VDCODE_SPDMONLY) == 0)) { return WOLFSPDM_E_BAD_STATE; } + /* Under handshake keys, before FINISH verifies the requester, only + * GIVE_PUB belongs */ + if (fromSecured && ctx->state != WOLFSPDM_STATE_CONNECTED && + XSTRCMP(vdCode, WOLFSPDM_VDCODE_GIVE_PUB) != 0) { + return WOLFSPDM_E_BAD_STATE; + } if (XSTRCMP(vdCode, WOLFSPDM_VDCODE_TPM2_CMD) == 0) { /* Reserve the VENDOR_DEFINED_RSP wrapper overhead @@ -996,6 +1026,9 @@ static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, if (rc != 0) { return WOLFSPDM_E_IO_FAIL; } + if (respPayloadSz > tpmRespCap) { + return WOLFSPDM_E_BUFFER_SMALL; + } } #ifdef WOLFSPDM_TCG else if (XSTRCMP(vdCode, WOLFSPDM_VDCODE_GET_PUBK) == 0) { @@ -1029,12 +1062,11 @@ static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, respPayloadSz = 4; } else if (XSTRCMP(vdCode, WOLFSPDM_VDCODE_SPDMONLY) == 0) { - if (payloadSz >= 1 && payload[0] == WOLFSPDM_SPDMONLY_LOCK) { - rctx->flags.spdmOnlyLock = 1; - } - else { - rctx->flags.spdmOnlyLock = 0; + if (payloadSz != 1 || (payload[0] != WOLFSPDM_SPDMONLY_LOCK && + payload[0] != WOLFSPDM_SPDMONLY_UNLOCK)) { + return WOLFSPDM_E_INVALID_ARG; } + rctx->flags.spdmOnlyLock = (payload[0] == WOLFSPDM_SPDMONLY_LOCK); respPayloadSz = 0; } #endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ @@ -1050,10 +1082,25 @@ static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, } /* Once a ClearAuth is registered, replacing the PSK requires * PSK_CLR_ first, or that check is trivially skipped. A PSK set by - * configuration has no ClearAuth, so it may still be provisioned. */ + * configuration may only gain a ClearAuth here: an unauthenticated + * clear frame must never swap in a PSK of its own. */ if (rctx->flags.clearAuthSet) { return WOLFSPDM_E_BAD_STATE; } + if (rctx->pskStoreSz != 0) { + volatile int diff = 0; + word32 i; + + if (rctx->pskStoreSz != pskLen) { + return WOLFSPDM_E_BAD_STATE; + } + for (i = 0; i < pskLen; i++) { + diff |= rctx->pskStore[i] ^ payload[i]; + } + if (diff != 0) { + return WOLFSPDM_E_BAD_STATE; + } + } XMEMCPY(rctx->pskStore, payload, pskLen); rctx->pskStoreSz = pskLen; XMEMCPY(rctx->clearAuthDigest, payload + pskLen, WOLFSPDM_HASH_SIZE); @@ -1121,6 +1168,9 @@ static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, off += respPayloadSz; } *outSz = off; + /* PSK_SET_ payloads and TPM traffic do not linger in scratch */ + wc_ForceZero(payload, payloadSz); + wc_ForceZero(respPayload, respPayloadSz); return WOLFSPDM_SUCCESS; } diff --git a/src/spdm_secured.c b/src/spdm_secured.c index aa68324..62877b8 100644 --- a/src/spdm_secured.c +++ b/src/spdm_secured.c @@ -48,7 +48,7 @@ static int wolfSPDM_EncryptRecord(WOLFSPDM_CTX* ctx, int appMsg, Aes aes; byte iv[WOLFSPDM_AEAD_IV_SIZE]; byte aad[16]; /* Up to 14 bytes for TCG format */ - byte plainBuf[WOLFSPDM_XFER_MSG_SIZE + 16]; + byte plainBuf[WOLFSPDM_XFER_MSG_SIZE + 2 + 16]; /* AppDataLength, pad */ byte tag[WOLFSPDM_AEAD_TAG_SIZE]; word32 plainBufSz; word16 recordLen; @@ -409,6 +409,12 @@ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, if (ctx == NULL || cmdPlain == NULL || rspPlain == NULL || rspSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } + /* Session keys exist from KEY_EXCHANGE_RSP on (GIVE_PUB runs before + * FINISH); anything earlier would seal with zero keys */ + if (ctx->state < WOLFSPDM_STATE_KEY_EX || + ctx->state == WOLFSPDM_STATE_ERROR || ctx->sessionId == 0) { + return WOLFSPDM_E_NOT_CONNECTED; + } #ifndef WOLFSPDM_NO_MEAS /* Only back-to-back GET_MEASUREMENTS extend L1/L2 */ if (ctx->l1l2State == WOLFSPDM_RUN_OPEN) { diff --git a/src/spdm_session.c b/src/spdm_session.c index 3517441..71669b7 100644 --- a/src/spdm_session.c +++ b/src/spdm_session.c @@ -32,6 +32,7 @@ int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, { word32 txSz = txBufSz; word32 rxSz = rxBufSz; + word32 mark = (ctx != NULL) ? ctx->transcriptLen : 0; int rc; rc = buildFn(ctx, txBuf, &txSz); @@ -47,6 +48,10 @@ int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, if (rc == WOLFSPDM_SUCCESS) { rc = parseFn(ctx, rxBuf, rxSz); } + /* A failed exchange leaves no half message for a retry to build on */ + if (rc != WOLFSPDM_SUCCESS && ctx != NULL && ctx->transcriptLen > mark) { + ctx->transcriptLen = mark; + } return rc; } @@ -125,12 +130,10 @@ static int wolfSPDM_FinishXfer(WOLFSPDM_CTX* ctx, const byte* finishBuf, /* Check for unencrypted SPDM error response */ if (rc == WOLFSPDM_SUCCESS && rxSz >= 2 && rxBuf[0] >= 0x10 && rxBuf[0] <= 0x1F) { - #ifdef WOLFSPDM_DEBUG - if (rxBuf[1] == 0x7F) { - byte errCode = (rxSz >= 3) ? rxBuf[2] : 0xFF; - wolfSPDM_DebugPrint(ctx, "FINISH: SPDM ERROR 0x%02x\n", errCode); + if (rxSz >= 4 && rxBuf[1] == SPDM_ERROR) { + ctx->lastPeerErrorCode = rxBuf[2]; + wolfSPDM_DebugPrint(ctx, "FINISH: SPDM ERROR 0x%02x\n", rxBuf[2]); } - #endif rc = WOLFSPDM_E_PEER_ERROR; } diff --git a/src/spdm_standard.c b/src/spdm_standard.c index ee21e02..961cd4c 100644 --- a/src/spdm_standard.c +++ b/src/spdm_standard.c @@ -684,6 +684,12 @@ int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) if (ctx->certChainLen <= WOLFSPDM_CERT_CHAIN_HDR_SZ) { return WOLFSPDM_E_CERT_FAIL; } + /* A key taken from an earlier chain is not an anchor for this one */ + if (ctx->flags.rspKeyFromCert) { + ctx->flags.hasRspPubKey = 0; + ctx->flags.rspKeyFromCert = 0; + ctx->rspPubKeyLen = 0; + } /* RootHash in the chain header must name the configured root */ if (ctx->trustedCASz > 0) { @@ -736,6 +742,13 @@ int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) } anchored = 1; } + else if (rc == WOLFSPDM_SUCCESS && !anchored && + !ctx->flags.allowUntrustedCert) { + wolfSPDM_DebugPrint(ctx, "No trust anchor: set a root CA, pin the " + "responder key, or allow untrusted certificates\n"); + rc = WOLFSPDM_E_CERT_FAIL; + } + /* Only a chain that passed every check supplies the responder key */ else if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_LeafKey(ctx, &key, ctx->rspPubKey, &leafSz); if (rc == WOLFSPDM_SUCCESS) { @@ -745,13 +758,6 @@ int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) } } - if (rc == WOLFSPDM_SUCCESS && !anchored && - !ctx->flags.allowUntrustedCert) { - wolfSPDM_DebugPrint(ctx, "No trust anchor: set a root CA, pin the " - "responder key, or allow untrusted certificates\n"); - rc = WOLFSPDM_E_CERT_FAIL; - } - return rc; } diff --git a/src/spdm_tcg.c b/src/spdm_tcg.c index a459485..d2dcb7f 100644 --- a/src/spdm_tcg.c +++ b/src/spdm_tcg.c @@ -39,46 +39,53 @@ int wolfSPDM_TCG_VendorCmdClear(WOLFSPDM_CTX* ctx, const char* vdCode, byte spdmMsg[WOLFSPDM_VENDOR_BUF_SZ]; int spdmMsgSz; byte rxBuf[WOLFSPDM_VENDOR_RX_SZ]; - word32 rxSz; - int rc; + word32 rxSz = sizeof(rxBuf); + int rc = WOLFSPDM_SUCCESS; byte ver; + if (ctx == NULL || vdCode == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + ver = ctx->spdmVersion ? ctx->spdmVersion : SPDM_VERSION_13; spdmMsgSz = wolfSPDM_BuildVendorDefined(ver, vdCode, payload, payloadSz, spdmMsg, sizeof(spdmMsg)); if (spdmMsgSz < 0) { - return spdmMsgSz; + rc = spdmMsgSz; } - - rxSz = sizeof(rxBuf); - rc = wolfSPDM_SendReceive(ctx, spdmMsg, (word32)spdmMsgSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, spdmMsg, (word32)spdmMsgSz, rxBuf, + &rxSz); } - if (rxSz >= 4 && rxBuf[1] == SPDM_ERROR) { + if (rc == WOLFSPDM_SUCCESS && rxSz >= 4 && rxBuf[1] == SPDM_ERROR) { wolfSPDM_DebugPrint(ctx, "%s: SPDM ERROR 0x%02x 0x%02x\n", vdCode, rxBuf[2], rxBuf[3]); - return WOLFSPDM_E_PEER_ERROR; + ctx->lastPeerErrorCode = rxBuf[2]; + rc = WOLFSPDM_E_PEER_ERROR; } - if (rsp != NULL) { + if (rc == WOLFSPDM_SUCCESS && rsp != NULL) { rsp->payloadSz = sizeof(rsp->payload); XMEMSET(rsp->vdCode, 0, sizeof(rsp->vdCode)); rc = wolfSPDM_ParseVendorDefined(rxBuf, rxSz, rsp->vdCode, rsp->payload, &rsp->payloadSz); - if (rc < 0) { - return rc; + if (rc >= 0) { + rc = WOLFSPDM_SUCCESS; } /* Validate response VdCode matches the request */ - if (XMEMCMP(rsp->vdCode, vdCode, WOLFSPDM_VDCODE_LEN) != 0) { + if (rc == WOLFSPDM_SUCCESS && + XMEMCMP(rsp->vdCode, vdCode, WOLFSPDM_VDCODE_LEN) != 0) { wolfSPDM_DebugPrint(ctx, "%s: unexpected VdCode '%.8s'\n", vdCode, rsp->vdCode); - return WOLFSPDM_E_PEER_ERROR; + rc = WOLFSPDM_E_PEER_ERROR; } } - return WOLFSPDM_SUCCESS; + /* PSK_SET_ and PSK_CLR_ carry secrets */ + wc_ForceZero(spdmMsg, sizeof(spdmMsg)); + wc_ForceZero(rxBuf, sizeof(rxBuf)); + return rc; } int wolfSPDM_TCG_VendorCmdSecured(WOLFSPDM_CTX* ctx, const char* vdCode, @@ -91,6 +98,10 @@ int wolfSPDM_TCG_VendorCmdSecured(WOLFSPDM_CTX* ctx, const char* vdCode, int rc; byte ver; + if (ctx == NULL || vdCode == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + ver = ctx->spdmVersion ? ctx->spdmVersion : SPDM_VERSION_13; spdmMsgSz = wolfSPDM_BuildVendorDefined(ver, vdCode, payload, payloadSz, spdmMsg, sizeof(spdmMsg)); diff --git a/test/unit_test.c b/test/unit_test.c index ca69f9d..c7fac46 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1468,11 +1468,59 @@ static int test_secured_exchange_null_args(void) static int test_disconnect_states(void) { + byte cmd[4] = { SPDM_VERSION_12, 0xE8, 0x00, 0x00 }; + byte rsp[16]; + byte err[4] = { SPDM_VERSION_12, SPDM_ERROR, SPDM_ERROR_BUSY, 0x00 }; + byte zero[WOLFSPDM_AEAD_KEY_SIZE]; + word32 rspSz = sizeof(rsp); TEST_CTX_SETUP(); printf("test_disconnect_states...\n"); - /* Not connected should still succeed (cleanup is safe) */ - wolfSPDM_Disconnect(ctx); - wolfSPDM_Disconnect(NULL); /* Should not crash */ + + ASSERT_EQ(wolfSPDM_Disconnect(ctx), WOLFSPDM_E_NOT_CONNECTED, + "no session to end"); + ASSERT_EQ(wolfSPDM_Disconnect(NULL), WOLFSPDM_E_INVALID_ARG, "NULL ctx"); + + /* Nothing is sealed before KEY_EXCHANGE sets up session keys */ + ASSERT_EQ(wolfSPDM_SecuredExchange(ctx, cmd, sizeof(cmd), rsp, &rspSz), + WOLFSPDM_E_NOT_CONNECTED, "secured message before a session"); + + /* A handshake that failed after KEY_EXCHANGE is still wiped */ + XMEMSET(zero, 0, sizeof(zero)); + ctx->state = WOLFSPDM_STATE_ERROR; + ctx->sessionId = 0x00020001; + ctx->reqSeqNum = 1; + XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->handshakeSecret, 0x22, WOLFSPDM_HASH_SIZE); + ASSERT_EQ(wolfSPDM_SecuredExchange(ctx, cmd, sizeof(cmd), rsp, &rspSz), + WOLFSPDM_E_NOT_CONNECTED, "secured message after a failure"); + ASSERT_EQ(wolfSPDM_Disconnect(ctx), WOLFSPDM_E_NOT_CONNECTED, + "failed handshake"); + ASSERT_EQ(ctx->state, WOLFSPDM_STATE_INIT, "state reset"); + ASSERT_EQ(ctx->reqSeqNum, 0, "sequence reset"); + ASSERT_EQ(ctx->sessionId, 0, "session ID reset"); + ASSERT_EQ(memcmp(ctx->reqDataKey, zero, sizeof(zero)), 0, "keys wiped"); + ASSERT_EQ(memcmp(ctx->handshakeSecret, zero, sizeof(zero)), 0, + "secrets wiped"); + + /* A retry starts from sequence number 0 even if it fails early */ + ctx->reqSeqNum = 5; + ctx->rspSeqNum = 5; + wolfSPDM_SetIO(ctx, dummy_io_cb, NULL); + TEST_ASSERT(wolfSPDM_Connect(ctx) != WOLFSPDM_SUCCESS, "no responder"); + ASSERT_EQ(ctx->reqSeqNum, 0, "request sequence reset on connect"); + ASSERT_EQ(ctx->rspSeqNum, 0, "response sequence reset on connect"); + + /* A 4-byte SPDM ERROR is reported as such, not as a short buffer */ + ASSERT_EQ(wolfSPDM_ParseKeyExchangeRsp(ctx, err, sizeof(err)), + WOLFSPDM_E_PEER_ERROR, "short ERROR to KEY_EXCHANGE"); + ASSERT_EQ(wolfSPDM_GetLastPeerError(ctx), SPDM_ERROR_BUSY, + "peer error recorded"); + ASSERT_EQ(wolfSPDM_ParseVersion(ctx, err, sizeof(err)), + WOLFSPDM_E_PEER_ERROR, "short ERROR to GET_VERSION"); + err[1] = SPDM_KEY_EXCHANGE_RSP; + ASSERT_EQ(wolfSPDM_ParseKeyExchangeRsp(ctx, err, sizeof(err)), + WOLFSPDM_E_INVALID_ARG, "short non-ERROR response"); + TEST_CTX_FREE(); TEST_PASS(); } @@ -2948,6 +2996,78 @@ static int resp_send_clear_vd(WOLFSPDM_RESP_CTX* rctx, const char* vdCode, (word32)spdmMsgSz + WOLFSPDM_TCG_HEADER_SIZE, out, outSz); } +/* A clear SPDM message in a TCG clear frame */ +static int resp_send_clear(WOLFSPDM_RESP_CTX* rctx, const byte* msg, + word32 msgSz, byte* out, word32* outSz) +{ + byte frame[320]; + + if (msgSz > sizeof(frame) - WOLFSPDM_TCG_HEADER_SIZE) { + return -1; + } + XMEMSET(frame, 0, sizeof(frame)); + frame[0] = 0x81; frame[1] = 0x01; + frame[4] = (byte)((msgSz + WOLFSPDM_TCG_HEADER_SIZE) >> 8); + frame[5] = (byte)(msgSz + WOLFSPDM_TCG_HEADER_SIZE); + XMEMCPY(frame + WOLFSPDM_TCG_HEADER_SIZE, msg, msgSz); + return wolfSPDM_RespHandleMessage(rctx, frame, + msgSz + WOLFSPDM_TCG_HEADER_SIZE, out, outSz); +} + +/* The clear channel may register a ClearAuth for a configured PSK but never + * swap the PSK, and a PSK-only responder refuses KEY_EXCHANGE */ +static int test_responder_configured_psk(void) +{ + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + byte psk[WOLFSPDM_PSK_MAX_SIZE]; + byte setPayload[WOLFSPDM_PSK_MAX_SIZE + WOLFSPDM_HASH_SIZE]; + byte clearAuth[32]; + byte getVer[4] = { SPDM_VERSION_10, SPDM_GET_VERSION, 0x00, 0x00 }; + byte keyEx[160]; + byte outBuf[256]; + word32 outSz; + + printf("test_responder_configured_psk...\n"); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 0, 1)); + XMEMSET(psk, 0x5C, sizeof(psk)); + ASSERT_SUCCESS(wolfSPDM_RespSetPSK(rctx, psk, sizeof(psk), NULL, 0)); + + XMEMSET(clearAuth, 0xC1, sizeof(clearAuth)); + XMEMSET(setPayload, 0xA5, WOLFSPDM_PSK_MAX_SIZE); + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(setPayload + WOLFSPDM_PSK_MAX_SIZE, + clearAuth, sizeof(clearAuth), NULL, 0, NULL, 0)); + outSz = sizeof(outBuf); + TEST_ASSERT(resp_send_clear_vd(rctx, WOLFSPDM_NATIONS_VDCODE_PSK_SET, + setPayload, (word32)sizeof(setPayload), outBuf, &outSz) != + WOLFSPDM_SUCCESS, "a clear frame must not replace a configured PSK"); + + /* The same PSK only registers the ClearAuth */ + XMEMCPY(setPayload, psk, sizeof(psk)); + outSz = sizeof(outBuf); + ASSERT_SUCCESS(resp_send_clear_vd(rctx, WOLFSPDM_NATIONS_VDCODE_PSK_SET, + setPayload, (word32)sizeof(setPayload), outBuf, &outSz)); + + /* PSK only: KEY_EXCHANGE gets ERROR(UnsupportedRequest) */ + outSz = sizeof(outBuf); + ASSERT_SUCCESS(resp_send_clear(rctx, getVer, sizeof(getVer), outBuf, + &outSz)); + XMEMSET(keyEx, 0, sizeof(keyEx)); + keyEx[0] = SPDM_VERSION_13; + keyEx[1] = SPDM_KEY_EXCHANGE; + outSz = sizeof(outBuf); + ASSERT_SUCCESS(resp_send_clear(rctx, keyEx, sizeof(keyEx), outBuf, + &outSz)); + ASSERT_EQ(outBuf[WOLFSPDM_TCG_HEADER_SIZE + 1], SPDM_ERROR, + "KEY_EXCHANGE refused"); + ASSERT_EQ(outBuf[WOLFSPDM_TCG_HEADER_SIZE + 2], + SPDM_ERROR_UNSUPPORTED_REQUEST, "unsupported in PSK-only mode"); + + wolfSPDM_RespFree(rctx); + TEST_PASS(); +} + /* A provisioned PSK may only be replaced after an authenticated PSK_CLR_. */ static int test_responder_psk_replace_guard(void) { @@ -3359,6 +3479,7 @@ static int test_validate_cert_chain(void) ASSERT_SUCCESS(test_load_sample_chain(ctx)); ASSERT_EQ(wolfSPDM_ValidateCertChain(ctx), WOLFSPDM_E_CERT_FAIL, "chain without a trust anchor must fail"); + ASSERT_EQ(ctx->flags.hasRspPubKey, 0, "rejected chain installs no key"); /* Root CA anchor: every signature verifies, leaf key installed */ ASSERT_SUCCESS(wolfSPDM_SetTrustedCAs(ctx, test_ca_cert_der, @@ -4873,6 +4994,11 @@ static int test_measurements_loopback(void) valSz = 2; ASSERT_EQ(wolfSPDM_GetMeasurementBlock(ctx, 0, &idx, &type, val, &valSz), WOLFSPDM_E_BUFFER_SMALL, "Small value buffer"); + ASSERT_EQ(valSz, 4, "Needed size reported"); + valSz = 0; + ASSERT_EQ(wolfSPDM_GetMeasurementBlock(ctx, 0, NULL, NULL, NULL, &valSz), + WOLFSPDM_E_BUFFER_SMALL, "Size query"); + ASSERT_EQ(valSz, 4, "Size query result"); valSz = sizeof(val); ASSERT_EQ(wolfSPDM_GetMeasurementBlock(ctx, 1, &idx, &type, val, &valSz), WOLFSPDM_E_INVALID_ARG, "Block out of range"); @@ -5356,6 +5482,7 @@ int main(void) test_responder_secured_requires_session(); #ifdef WOLFSPDM_NATIONS test_responder_psk_replace_guard(); + test_responder_configured_psk(); #endif test_responder_identity_roundtrip(); #endif diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index a6fe8b2..86079d5 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -88,7 +88,9 @@ typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; /* I/O callback: transport-agnostic send/receive. * Returns 0 on success, negative on error. - * rxSz: [in] buffer size, [out] actual received size. */ + * rxSz: [in] buffer size, [out] actual received size, never more than [in]. + * wolfSPDM_SendData calls it send only (rxBuf NULL, *rxSz 0) and + * wolfSPDM_ReceiveData receive only (txBuf NULL, txSz 0). */ typedef int (*WOLFSPDM_IO_CB)( WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, @@ -154,6 +156,9 @@ WOLFSPDM_API int wolfSPDM_GetCapabilities(WOLFSPDM_CTX* ctx); WOLFSPDM_API int wolfSPDM_NegotiateAlgorithms(WOLFSPDM_CTX* ctx); WOLFSPDM_API int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx); WOLFSPDM_API int wolfSPDM_GetCertificate(WOLFSPDM_CTX* ctx, int slotId); +/* Verify the fetched chain against the trust anchor and install its leaf as + * the responder key; call between GetCertificate and KeyExchange */ +WOLFSPDM_API int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx); #endif WOLFSPDM_API int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx); WOLFSPDM_API int wolfSPDM_Finish(WOLFSPDM_CTX* ctx); @@ -189,7 +194,9 @@ WOLFSPDM_API int wolfSPDM_ReceiveData(WOLFSPDM_CTX* ctx, byte* data, WOLFSPDM_API int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, byte measOperation, int requestSignature); WOLFSPDM_API int wolfSPDM_GetMeasurementCount(WOLFSPDM_CTX* ctx); -/* valueSz is in/out; measType is the DMTF value type, 0 for raw blocks */ +/* valueSz is in/out; measType is the DMTF value type, 0 for raw blocks. + * measIndex, measType and value may be NULL; a NULL or short value returns + * WOLFSPDM_E_BUFFER_SMALL with the needed size in *valueSz. */ WOLFSPDM_API int wolfSPDM_GetMeasurementBlock(WOLFSPDM_CTX* ctx, int blockIdx, byte* measIndex, byte* measType, byte* value, word32* valueSz); #endif diff --git a/wolfspdm/spdm_error.h b/wolfspdm/spdm_error.h index c43281c..bcd0acd 100644 --- a/wolfspdm/spdm_error.h +++ b/wolfspdm/spdm_error.h @@ -63,6 +63,10 @@ enum WOLFSPDM_ERROR { WOLFSPDM_E_CHUNK = -28, /* Chunked transfer failed */ }; +/* Older spellings, kept for source compatibility */ +#define WOLFSPDM_E_MEAS_NOT_VERIFIED WOLFSPDM_E_MEASUREMENT +#define WOLFSPDM_E_MEAS_SIG_FAIL WOLFSPDM_E_BAD_SIGNATURE + /* Get human-readable error string */ WOLFSPDM_API const char* wolfSPDM_GetErrorString(int error); diff --git a/wolfspdm/spdm_responder.h b/wolfspdm/spdm_responder.h index 5e5550b..9d88014 100644 --- a/wolfspdm/spdm_responder.h +++ b/wolfspdm/spdm_responder.h @@ -20,7 +20,10 @@ */ /* SPDM responder. Lives above fwtpm's transport HAL and reuses wolfSPDM - * crypto + framing helpers, flipped to answer requester-driven messages. */ + * crypto + framing helpers, flipped to answer requester-driven messages. + * It authenticates itself to the requester but not the reverse: a KEY_EXCHANGE + * session asks for no mutual auth and GIVE_PUB is recorded, not verified, so + * gate sensitive TPM use on the PSK or on transport access. */ #ifndef WOLFSPDM_RESPONDER_H #define WOLFSPDM_RESPONDER_H From 7a7f0f654f149965423505ff6af77c8e41996082 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 10:15:30 -0700 Subject: [PATCH 16/27] Drop a dead store scan-build flags in chain validation --- src/spdm_standard.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/spdm_standard.c b/src/spdm_standard.c index 961cd4c..d032756 100644 --- a/src/spdm_standard.c +++ b/src/spdm_standard.c @@ -740,7 +740,6 @@ int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx) wolfSPDM_DebugPrint(ctx, "Leaf key does not match pinned key\n"); rc = WOLFSPDM_E_CERT_FAIL; } - anchored = 1; } else if (rc == WOLFSPDM_SUCCESS && !anchored && !ctx->flags.allowUntrustedCert) { From 4b2f0dee536d4cd0df1a354ded6b40d60a7c1a75 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 11:09:19 -0700 Subject: [PATCH 17/27] Fix the Codex review findings in key update, FINISH, PSK replacement and headers --- configure.ac | 7 +++-- examples/spdm_demo.c | 3 ++- src/spdm_context.c | 10 ++++---- src/spdm_kdf.c | 2 +- src/spdm_msg.c | 7 ++++- src/spdm_psk.c | 4 ++- src/spdm_responder.c | 2 ++ src/spdm_session.c | 54 +++++++++++++++++++++++++++++++++++++++ src/spdm_tcg.c | 1 - src/spdm_transcript.c | 1 - src/vendor/spdm_nations.c | 1 - src/vendor/spdm_nuvoton.c | 1 - test/unit_test.c | 38 +++++++++++++++++++++++++-- wolfspdm/spdm_psk.h | 2 +- wolfspdm/spdm_tcg.h | 2 +- wolfspdm/spdm_types.h | 8 ++++++ 16 files changed, 124 insertions(+), 19 deletions(-) diff --git a/configure.ac b/configure.ac index f8fd4d5..656f261 100644 --- a/configure.ac +++ b/configure.ac @@ -145,11 +145,14 @@ if test "x$enable_mldsa" != "xno" && test "x$enable_cert" = "xyes"; then #include #include ]], [[ - MlDsaKey k; int res = 0; word32 oid = ML_DSA_44k + CTC_ML_DSA_87; + MlDsaKey k; int res = 0; int keyOid = ML_DSA_44k; + int sigOid = CTC_ML_DSA_87; + (void)wc_MlDsaKey_Init(&k, 0, INVALID_DEVID); (void)wc_MlDsaKey_SetParams(&k, WC_ML_DSA_65); (void)wc_MlDsaKey_ImportPubRaw(&k, 0, 0); (void)wc_MlDsaKey_VerifyCtx(&k, 0, 0, 0, 0, 0, 0, &res); - (void)oid; + wc_MlDsaKey_Free(&k); + (void)keyOid; (void)sigOid; ]])], [have_mldsa_api=yes], [have_mldsa_api=no]) diff --git a/examples/spdm_demo.c b/examples/spdm_demo.c index 80d6e96..16fecca 100644 --- a/examples/spdm_demo.c +++ b/examples/spdm_demo.c @@ -509,7 +509,8 @@ static int do_app_data(WOLFSPDM_CTX* ctx) return rc; } /* MCTP type, PLDM header (3), completion code, TID */ - if (rspSz != 6 || rsp[0] != getTid[0] || rsp[2] != getTid[2] || + if (rspSz != 6 || rsp[0] != getTid[0] || rsp[1] != 0x00 || + rsp[2] != getTid[2] || rsp[3] != getTid[3] || rsp[4] != 0x00) { fprintf(stderr, "App data: unexpected PLDM GetTID response\n"); return WOLFSPDM_E_FRAMING; diff --git a/src/spdm_context.c b/src/spdm_context.c index 43df510..669497f 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -440,9 +440,6 @@ void wolfSPDM_ResetSession(WOLFSPDM_CTX* ctx) int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) { int rc = WOLFSPDM_E_NOT_CONNECTED; - byte txBuf[8]; - byte rxBuf[16]; /* END_SESSION_ACK: 4 bytes */ - word32 txSz, rxSz; if (ctx == NULL) { return WOLFSPDM_E_INVALID_ARG; @@ -450,7 +447,11 @@ int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) /* END_SESSION only for a live session; the wipe always runs */ if (ctx->state == WOLFSPDM_STATE_CONNECTED) { - txSz = sizeof(txBuf); + byte txBuf[8]; + byte rxBuf[16]; /* END_SESSION_ACK: 4 bytes */ + word32 txSz = sizeof(txBuf); + word32 rxSz; + rc = wolfSPDM_BuildEndSession(ctx, txBuf, &txSz); if (rc == WOLFSPDM_SUCCESS) { rxSz = sizeof(rxBuf); @@ -676,4 +677,3 @@ const char* wolfSPDM_GetErrorString(int error) default: return "Unknown error"; } } - diff --git a/src/spdm_kdf.c b/src/spdm_kdf.c index 2dd506a..b58b310 100644 --- a/src/spdm_kdf.c +++ b/src/spdm_kdf.c @@ -25,7 +25,7 @@ #include "spdm_internal.h" -/* SPDM key derivation (DSP0277): HKDF with +/* SPDM key derivation (DSP0277): HKDF with * info = Length(2,LE) || "spdm1.2 " || Label || Context. */ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, diff --git a/src/spdm_msg.c b/src/spdm_msg.c index 54ed597..c79e945 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -316,7 +316,12 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) #ifdef WOLFSPDM_MUTUAL_AUTH /* Mutual auth is enabled when the responder requested it (MutAuthRequested * bit 0) AND we have a requester key pair to sign with */ - if ((ctx->mutAuthRequested & 0x01) && ctx->flags.hasReqKeyPair) { + if ((ctx->mutAuthRequested & 0x01) && !ctx->flags.hasReqKeyPair) { + wolfSPDM_DebugPrint(ctx, "FINISH: mutual auth requested, no " + "requester key\n"); + return WOLFSPDM_E_BAD_STATE; + } + if (ctx->mutAuthRequested & 0x01) { mutualAuth = 1; wolfSPDM_DebugPrint(ctx, "FINISH: Mutual auth ENABLED " "(MutAuth=0x%02x ReqSlot=0x%02x)\n", diff --git a/src/spdm_psk.c b/src/spdm_psk.c index fb1a15d..9c649bd 100644 --- a/src/spdm_psk.c +++ b/src/spdm_psk.c @@ -48,10 +48,13 @@ int wolfSPDM_SetPSK(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_INVALID_ARG; } + /* No tail of a longer earlier PSK survives */ + wc_ForceZero(ctx->psk, sizeof(ctx->psk)); XMEMCPY(ctx->psk, psk, pskSz); ctx->pskSz = pskSz; if (hint != NULL && hintSz > 0) { + XMEMSET(ctx->pskHint, 0, sizeof(ctx->pskHint)); XMEMCPY(ctx->pskHint, hint, hintSz); ctx->pskHintSz = hintSz; } else { @@ -427,4 +430,3 @@ int wolfSPDM_ConnectPsk(WOLFSPDM_CTX* ctx) } #endif /* WOLFSPDM_PSK */ - diff --git a/src/spdm_responder.c b/src/spdm_responder.c index e15e9dc..f9747fb 100644 --- a/src/spdm_responder.c +++ b/src/spdm_responder.c @@ -168,8 +168,10 @@ int wolfSPDM_RespSetPSK(WOLFSPDM_RESP_CTX* ctx, if (rc != WOLFSPDM_SUCCESS) { return rc; } + wc_ForceZero(ctx->pskStore, sizeof(ctx->pskStore)); XMEMCPY(ctx->pskStore, psk, pskSz); ctx->pskStoreSz = pskSz; + XMEMSET(ctx->pskHintStore, 0, sizeof(ctx->pskHintStore)); if (hint != NULL && hintSz > 0 && hintSz <= sizeof(ctx->pskHintStore)) { XMEMCPY(ctx->pskHintStore, hint, hintSz); ctx->pskHintStoreSz = hintSz; diff --git a/src/spdm_session.c b/src/spdm_session.c index 71669b7..b7febae 100644 --- a/src/spdm_session.c +++ b/src/spdm_session.c @@ -222,6 +222,46 @@ int wolfSPDM_Heartbeat(WOLFSPDM_CTX* ctx) #endif /* !WOLFSPDM_NO_HEARTBEAT */ #ifndef WOLFSPDM_NO_KEY_UPDATE +static void wolfSPDM_KeySnap(byte* snap, byte* key, word32 sz, int save) +{ + if (save) { + XMEMCPY(snap, key, sz); + } + else { + XMEMCPY(key, snap, sz); + } +} + +/* Copy the traffic keys, app secrets and sequence numbers out (save) or + * back in */ +static void wolfSPDM_SaveTrafficKeys(WOLFSPDM_CTX* ctx, byte* snap, + word64* reqSeq, word64* rspSeq, int save) +{ + word32 off = 0; + + wolfSPDM_KeySnap(snap + off, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE, + save); + off += WOLFSPDM_AEAD_KEY_SIZE; + wolfSPDM_KeySnap(snap + off, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE, + save); + off += WOLFSPDM_AEAD_KEY_SIZE; + wolfSPDM_KeySnap(snap + off, ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE, save); + off += WOLFSPDM_AEAD_IV_SIZE; + wolfSPDM_KeySnap(snap + off, ctx->rspDataIv, WOLFSPDM_AEAD_IV_SIZE, save); + off += WOLFSPDM_AEAD_IV_SIZE; + wolfSPDM_KeySnap(snap + off, ctx->reqAppSecret, WOLFSPDM_HASH_SIZE, save); + off += WOLFSPDM_HASH_SIZE; + wolfSPDM_KeySnap(snap + off, ctx->rspAppSecret, WOLFSPDM_HASH_SIZE, save); + if (save) { + *reqSeq = ctx->reqSeqNum; + *rspSeq = ctx->rspSeqNum; + } + else { + ctx->reqSeqNum = *reqSeq; + ctx->rspSeqNum = *rspSeq; + } +} + int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll) { byte txBuf[4]; @@ -232,6 +272,10 @@ int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll) word32 rxSz = sizeof(rxBuf); word32 encSz = sizeof(encBuf); word32 rawSz = sizeof(rawBuf); + byte saved[2 * WOLFSPDM_AEAD_KEY_SIZE + 2 * WOLFSPDM_AEAD_IV_SIZE + + 2 * WOLFSPDM_HASH_SIZE]; + word64 savedReqSeq = 0; + word64 savedRspSeq = 0; byte op; byte tag = 0; int rotated = 0; @@ -255,6 +299,8 @@ int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll) if (rc == WOLFSPDM_SUCCESS) { rc = wolfSPDM_DecryptInternal(ctx, rawBuf, rawSz, rxBuf, &rxSz); if (rc != WOLFSPDM_SUCCESS && updateAll) { + wolfSPDM_SaveTrafficKeys(ctx, saved, &savedReqSeq, &savedRspSeq, + 1); rotated = 1; rc = wolfSPDM_DeriveUpdatedKeys(ctx, 1); ctx->reqSeqNum = 0; @@ -264,6 +310,13 @@ int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll) rc = wolfSPDM_DecryptInternal(ctx, rawBuf, rawSz, rxBuf, &rxSz); } + /* Not sealed under the new keys either: the responder did not + * rotate, so neither do we */ + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_SaveTrafficKeys(ctx, saved, &savedReqSeq, + &savedRspSeq, 0); + rotated = 0; + } } } if (rc == WOLFSPDM_SUCCESS) { @@ -291,6 +344,7 @@ int wolfSPDM_KeyUpdate(WOLFSPDM_CTX* ctx, int updateAll) SPDM_KEY_UPDATE_OP_VERIFY_NEW_KEY, tag); } + wc_ForceZero(saved, sizeof(saved)); return rc; } #endif /* !WOLFSPDM_NO_KEY_UPDATE */ diff --git a/src/spdm_tcg.c b/src/spdm_tcg.c index d2dcb7f..70ae92b 100644 --- a/src/spdm_tcg.c +++ b/src/spdm_tcg.c @@ -734,4 +734,3 @@ int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx) } #endif /* WOLFSPDM_TCG */ - diff --git a/src/spdm_transcript.c b/src/spdm_transcript.c index 66ea411..3b1ee55 100644 --- a/src/spdm_transcript.c +++ b/src/spdm_transcript.c @@ -100,4 +100,3 @@ int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash) return wolfSPDM_Sha384Hash(hash, ctx->transcript, ctx->transcriptLen, NULL, 0, NULL, 0); } - diff --git a/src/vendor/spdm_nations.c b/src/vendor/spdm_nations.c index 4375c25..37f8e05 100644 --- a/src/vendor/spdm_nations.c +++ b/src/vendor/spdm_nations.c @@ -179,4 +179,3 @@ int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, * wolfSPDM_ConnectNationsPsk is a backward-compat alias in spdm_psk.h. */ #endif /* WOLFSPDM_NATIONS */ - diff --git a/src/vendor/spdm_nuvoton.c b/src/vendor/spdm_nuvoton.c index b9870ca..0bd80cc 100644 --- a/src/vendor/spdm_nuvoton.c +++ b/src/vendor/spdm_nuvoton.c @@ -117,4 +117,3 @@ int wolfSPDM_Nuvoton_SetOnlyMode( } #endif /* WOLFSPDM_NUVOTON */ - diff --git a/test/unit_test.c b/test/unit_test.c index c7fac46..a3df7a2 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1043,7 +1043,7 @@ static int test_tcg_underflow(void) txBuf[3] = 0x00; rc = wolfSPDM_SendReceive(ctx, txBuf, 4, rxBuf, &rxSz); - ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, + ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, "msgSize < 16 must return BUFFER_SMALL"); TEST_CTX_FREE(); @@ -1111,6 +1111,12 @@ static int test_nations_psk_set(void) ASSERT_SUCCESS(rc); ASSERT_EQ(ctx->pskHintSz, sizeof(hint) - 1, "hintSz mismatch"); + /* A shorter replacement leaves nothing of the longer PSK behind */ + rc = wolfSPDM_SetPSK(ctx, psk, 16, NULL, 0); + ASSERT_SUCCESS(rc); + ASSERT_EQ(ctx->psk[16], 0, "old PSK tail wiped"); + ASSERT_EQ(ctx->psk[sizeof(psk) - 1], 0, "old PSK tail wiped"); + TEST_CTX_FREE(); TEST_PASS(); } @@ -2486,6 +2492,15 @@ static int test_build_finish_format(void) ASSERT_EQ(buf[2], 0, "sigIncluded should be 0"); ASSERT_EQ(bufSz, 52, "expected 4 header + 48 HMAC"); +#ifdef WOLFSPDM_MUTUAL_AUTH + /* Mutual auth requested but no requester key: no unsigned FINISH */ + ctx->mutAuthRequested = 0x01; + ctx->flags.hasReqKeyPair = 0; + bufSz = sizeof(buf); + ASSERT_EQ(wolfSPDM_BuildFinish(ctx, buf, &bufSz), WOLFSPDM_E_BAD_STATE, + "mutual auth without a requester key"); +#endif + TEST_CTX_FREE(); TEST_PASS(); } @@ -4311,6 +4326,7 @@ static int test_derive_updated_keys(void) /* Loopback responder: a mirrored context that answers requests over MCTP */ static WOLFSPDM_CTX g_peer; static int g_peerRejects; +static int g_peerGarble; /* corrupt the next secured response */ #ifndef WOLFSPDM_NO_KEY_UPDATE static void test_swap(byte* a, byte* b, word32 sz) @@ -4789,7 +4805,12 @@ static int test_peer_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, *rxSz = rspSz; return 0; } - return wolfSPDM_EncryptInternal(p, rsp, rspSz, rxBuf, rxSz); + rc = wolfSPDM_EncryptInternal(p, rsp, rspSz, rxBuf, rxSz); + if (rc == 0 && g_peerGarble) { + g_peerGarble = 0; + rxBuf[*rxSz - 1] ^= 0x01; + } + return rc; } static void test_session_loopback(WOLFSPDM_CTX* ctx) @@ -4898,6 +4919,19 @@ static int test_key_update_loopback(void) g_peerRejects = 0; ASSERT_SUCCESS(wolfSPDM_KeyUpdate(ctx, 1)); + /* An ACK that authenticates under neither key set rotates nothing */ + XMEMCPY(reqKey, ctx->reqDataKey, sizeof(reqKey)); + XMEMCPY(rspKey, ctx->rspDataKey, sizeof(rspKey)); + reqSeq = ctx->reqSeqNum; + g_peerGarble = 1; + TEST_ASSERT(wolfSPDM_KeyUpdate(ctx, 1) != WOLFSPDM_SUCCESS, + "Corrupt UpdateAllKeys ACK should fail"); + ASSERT_EQ(memcmp(reqKey, ctx->reqDataKey, sizeof(reqKey)), 0, + "Corrupt ACK keeps the request key"); + ASSERT_EQ(memcmp(rspKey, ctx->rspDataKey, sizeof(rspKey)), 0, + "Corrupt ACK keeps the response key"); + ASSERT_EQ(ctx->reqSeqNum, reqSeq + 1, "Sequence kept past the request"); + #ifndef WOLFSPDM_NO_CERT ctx->rspCaps = 0; ASSERT_EQ(wolfSPDM_KeyUpdate(ctx, 1), WOLFSPDM_E_CAPS_MISMATCH, diff --git a/wolfspdm/spdm_psk.h b/wolfspdm/spdm_psk.h index 2f1dcd9..29f10a5 100644 --- a/wolfspdm/spdm_psk.h +++ b/wolfspdm/spdm_psk.h @@ -34,7 +34,7 @@ #ifndef WOLFSPDM_PSK_H #define WOLFSPDM_PSK_H -#include +#include #ifdef WOLFSPDM_PSK diff --git a/wolfspdm/spdm_tcg.h b/wolfspdm/spdm_tcg.h index 54eef41..e7698ec 100644 --- a/wolfspdm/spdm_tcg.h +++ b/wolfspdm/spdm_tcg.h @@ -33,7 +33,7 @@ #ifndef WOLFSPDM_TCG_H #define WOLFSPDM_TCG_H -#include +#include /* Transport framing constants - needed by any SPDM-over-TPM build, * not just the TCG cert handshake. */ diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 953e422..4e652e2 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -167,6 +167,14 @@ extern "C" { #define WOLFSPDM_PUBKEY_BUF_SZ 256 /* Public key buffer */ #endif +/* Spellings used by earlier wolfSPDM releases */ +#if defined(NO_WOLFSPDM_MEAS) && !defined(WOLFSPDM_NO_MEAS) + #define WOLFSPDM_NO_MEAS +#endif +#if defined(NO_WOLFSPDM_CHALLENGE) && !defined(WOLFSPDM_NO_CHALLENGE) + #define WOLFSPDM_NO_CHALLENGE +#endif + /* ----- TPM Build Profile ----- */ /* Built inside wolfTPM: the TPM only speaks the TCG binding */ From aa876cb371034caef2ff53ccddc66f21087b2f98 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 11:19:59 -0700 Subject: [PATCH 18/27] Reject a vendor-defined VdCode that is not exactly eight characters --- src/spdm_tcg.c | 3 ++- test/unit_test.c | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/spdm_tcg.c b/src/spdm_tcg.c index 70ae92b..fc4f034 100644 --- a/src/spdm_tcg.c +++ b/src/spdm_tcg.c @@ -210,7 +210,8 @@ int wolfSPDM_BuildVendorDefined( word32 totalSz; word32 offset = 0; - if (vdCode == NULL || outBuf == NULL) { + if (vdCode == NULL || outBuf == NULL || + XSTRLEN(vdCode) != WOLFSPDM_VDCODE_LEN) { return WOLFSPDM_E_INVALID_ARG; } if (payload == NULL && payloadSz != 0) { diff --git a/test/unit_test.c b/test/unit_test.c index a3df7a2..b4a4364 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1620,6 +1620,10 @@ static int test_build_vendor_defined(void) (byte*)"X", 1, outBuf, sizeof(outBuf)) < 0, "NULL vdCode"); TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", (byte*)"X", 1, NULL, sizeof(outBuf)) < 0, "NULL outBuf"); + TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "A", + (byte*)"X", 1, outBuf, sizeof(outBuf)) < 0, "short vdCode"); + TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMDX", + (byte*)"X", 1, outBuf, sizeof(outBuf)) < 0, "long vdCode"); /* Buffer too small */ TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", From ee4e319b1650643e292c6597a92f46250b32f92e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 11:29:08 -0700 Subject: [PATCH 19/27] Restore the 4096-byte trusted CA default from main --- README.md | 6 +++--- docs/Configuration-and-Macros.md | 4 ++-- wolfspdm/spdm_types.h | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index bd077ca..8f47177 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ wolfSPDM is a lightweight C library implementing [SPDM 1.2 / 1.3 / 1.4](https:// - **Post-quantum signatures (SPDM 1.4):** optional ML-DSA-44 / 65 / 87 (FIPS 204), dual-stacked with ECDSA P-384 — see the [Post-Quantum ML-DSA](https://github.com/aidangarske/wolfSPDM/wiki/Post-Quantum-ML-DSA) wiki page - **Post-quantum key exchange (SPDM 1.4):** optional ML-KEM-512 / 768 / 1024 (FIPS 203), advertised alongside ECDHE P-384 — see the [Post-Quantum ML-KEM](https://github.com/aidangarske/wolfSPDM/wiki/Post-Quantum-ML-KEM) wiki page - **Fully post-quantum SPDM handshake:** ML-KEM key exchange + ML-DSA authentication (no classical asymmetric crypto), proven end-to-end against spdm-emu -- **Zero-malloc by default:** static memory, ~17 KB context (~59 KB with ML-DSA), ideal for constrained/embedded environments +- **Zero-malloc by default:** static memory, ~19 KB context (~59 KB with ML-DSA), ideal for constrained/embedded environments - **Optional `--enable-dynamic-mem`** for heap-allocated contexts on small-stack platforms - **Full session lifecycle:** key exchange, finish, encrypted messaging, heartbeat keep-alive, key update - **Device attestation:** signed / unsigned `GET_MEASUREMENTS`, sessionless `CHALLENGE_AUTH`, certificate-chain validation against trusted root CAs @@ -120,8 +120,8 @@ wolfSPDM is the SPDM stack wolfTPM builds on. Its core is the SPDM code that wol | Build | Compiled in | `sizeof(WOLFSPDM_CTX)` (arm64) | |---|---|---| -| Standalone (default) | Standard DSP0274 / DSP0277 requester: certificates, attestation, heartbeat, key update, chunking, application data; ML-DSA / ML-KEM when wolfSSL has them | ~17 KB classical, ~59 KB with ML-DSA | -| Standalone + TPM side | Adds `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-psk` / `--enable-responder` | ~17 KB classical | +| Standalone (default) | Standard DSP0274 / DSP0277 requester: certificates, attestation, heartbeat, key update, chunking, application data; ML-DSA / ML-KEM when wolfSSL has them | ~19 KB classical, ~59 KB with ML-DSA | +| Standalone + TPM side | Adds `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-psk` / `--enable-responder` | ~19 KB classical | | Pure TCG (`--disable-mctp`) | TCG binding, vendors, PSK and responder only | ~9.6 KB | | wolfTPM (`WOLFTPM_SPDM`, profile `WOLFSPDM_PROFILE_TPM`) | What wolfTPM needs: TCG binding, vendors, PSK, responder | ~9.5 KB | diff --git a/docs/Configuration-and-Macros.md b/docs/Configuration-and-Macros.md index b951626..6e94da5 100644 --- a/docs/Configuration-and-Macros.md +++ b/docs/Configuration-and-Macros.md @@ -90,10 +90,10 @@ defaults grow when ML-DSA or ML-KEM is built in (all are overridable with | `WOLFSPDM_CTX_STATIC_SIZE` | `32768` | `40960` | `73728` | | `WOLFSPDM_MAX_MSG_SIZE` | `4096` | `4096` | `8192` | | `WOLFSPDM_MAX_CERT_CHAIN` | `4096` | `4096` | `24576` | -| `WOLFSPDM_MAX_TRUSTED_CA` | `2048` | `2048` | `8192` | +| `WOLFSPDM_MAX_TRUSTED_CA` | `4096` | `4096` | `8192` | | `WOLFSPDM_MAX_TRANSCRIPT` | `4096` | `8192` | `16384` | -Measured `sizeof(WOLFSPDM_CTX)` on arm64: ~17 KB classical, ~22 KB ML-KEM +Measured `sizeof(WOLFSPDM_CTX)` on arm64: ~19 KB classical, ~24 KB ML-KEM only, ~59 KB with ML-DSA, ~9.5 KB in the TPM profile (well under the corresponding `WOLFSPDM_CTX_STATIC_SIZE`). diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 4e652e2..ba091cd 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -368,7 +368,7 @@ extern "C" { #ifdef WOLFSPDM_HAVE_MLDSA #define WOLFSPDM_MAX_TRUSTED_CA 8192 #else - #define WOLFSPDM_MAX_TRUSTED_CA 2048 + #define WOLFSPDM_MAX_TRUSTED_CA 4096 #endif #endif #endif /* !WOLFSPDM_NO_CERT */ From 46293444171b9d1de88bf0ad5e99413384f232d2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 11:41:49 -0700 Subject: [PATCH 20/27] Accept --kex ecdhe without ML-KEM and tighten two unit tests --- examples/spdm_demo.c | 15 +++++++++------ test/unit_test.c | 3 ++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/spdm_demo.c b/examples/spdm_demo.c index 16fecca..3eeb9ab 100644 --- a/examples/spdm_demo.c +++ b/examples/spdm_demo.c @@ -567,10 +567,10 @@ int main(int argc, char* argv[]) } break; case 'K': -#ifdef WOLFSPDM_HAVE_MLKEM if (strcmp(optarg, "ecdhe") == 0) { kexEcdheOnly = 1; } +#ifdef WOLFSPDM_HAVE_MLKEM else if (strcmp(optarg, "mlkem512") == 0) { kexKemOnly = SPDM_KEM_ALGO_ML_KEM_512; } @@ -580,16 +580,19 @@ int main(int argc, char* argv[]) else if (strcmp(optarg, "mlkem1024") == 0) { kexKemOnly = SPDM_KEM_ALGO_ML_KEM_1024; } +#else + else if (strncmp(optarg, "mlkem", 5) == 0) { + fprintf(stderr, "--kex %s needs ML-KEM support in " + "wolfSPDM\n", optarg); + return 77; + } +#endif else { fprintf(stderr, "Invalid --kex %s (expected ecdhe/" "mlkem512/mlkem768/mlkem1024)\n", optarg); return 1; } break; -#else - fprintf(stderr, "--kex needs ML-KEM support in wolfSPDM\n"); - return 77; -#endif case 'h': usage(argv[0]); return 0; default: usage(argv[0]); return 1; } @@ -637,7 +640,7 @@ int main(int argc, char* argv[]) } } -#ifdef WOLFSPDM_HAVE_MLKEM +#ifndef WOLFSPDM_NO_CERT if (kexEcdheOnly || kexKemOnly != 0) { rc = wolfSPDM_SetKeyExchangePref(ctx, kexEcdheOnly ? 1 : 0, kexKemOnly); if (rc != WOLFSPDM_SUCCESS) { diff --git a/test/unit_test.c b/test/unit_test.c index b4a4364..35d1af3 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -323,6 +323,7 @@ static int test_build_get_version(void) ASSERT_SUCCESS(wolfSPDM_BuildGetVersion(buf, &bufSz)); ASSERT_EQ(bufSz, 4, "GET_VERSION should be 4 bytes"); + ASSERT_EQ(buf[0], SPDM_VERSION_10, "Version should be 0x10"); ASSERT_EQ(buf[1], SPDM_GET_VERSION, "Code should be 0x84"); bufSz = 2; @@ -2586,7 +2587,7 @@ static int test_encrypt_decrypt_roundtrip_tcg(void) TEST_CTX_SETUP_V12(); printf("test_encrypt_decrypt_roundtrip_tcg...\n"); - wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS); + ctx->mode = WOLFSPDM_MODE_NATIONS; ctx->sessionId = 0x00020001; ctx->reqSeqNum = 0; ctx->rspSeqNum = 0; From 66d9503c5e39a174f9d1d844847ccc2205deaf2b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 11:48:37 -0700 Subject: [PATCH 21/27] Check the vendor-defined VdCode without scanning past eight bytes --- src/spdm_tcg.c | 10 ++++++++-- test/unit_test.c | 2 -- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/spdm_tcg.c b/src/spdm_tcg.c index fc4f034..7079388 100644 --- a/src/spdm_tcg.c +++ b/src/spdm_tcg.c @@ -209,11 +209,17 @@ int wolfSPDM_BuildVendorDefined( { word32 totalSz; word32 offset = 0; + word32 i; - if (vdCode == NULL || outBuf == NULL || - XSTRLEN(vdCode) != WOLFSPDM_VDCODE_LEN) { + if (vdCode == NULL || outBuf == NULL) { return WOLFSPDM_E_INVALID_ARG; } + /* Fixed 8-byte code: a NUL inside it means the caller's string is short */ + for (i = 0; i < WOLFSPDM_VDCODE_LEN; i++) { + if (vdCode[i] == '\0') { + return WOLFSPDM_E_INVALID_ARG; + } + } if (payload == NULL && payloadSz != 0) { return WOLFSPDM_E_INVALID_ARG; } diff --git a/test/unit_test.c b/test/unit_test.c index 35d1af3..03caeb4 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1623,8 +1623,6 @@ static int test_build_vendor_defined(void) (byte*)"X", 1, NULL, sizeof(outBuf)) < 0, "NULL outBuf"); TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "A", (byte*)"X", 1, outBuf, sizeof(outBuf)) < 0, "short vdCode"); - TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMDX", - (byte*)"X", 1, outBuf, sizeof(outBuf)) < 0, "long vdCode"); /* Buffer too small */ TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", From 9db7ab012efaac09d0c0e8ab19c64802f5673d51 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 12:06:19 -0700 Subject: [PATCH 22/27] Drop the integration branch from the workflow push triggers --- .github/workflows/build-test.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/codespell.yml | 2 +- .github/workflows/compiler-warnings.yml | 2 +- .github/workflows/empty-brace-scan.yml | 2 +- .github/workflows/memory-check.yml | 2 +- .github/workflows/multi-compiler.yml | 2 +- .github/workflows/spdm-emu-pqc-test.yml | 2 +- .github/workflows/spdm-emu-test.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/wolfssl-versions.yml | 2 +- .github/workflows/wolftpm-downstream.yml | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 58a151e..f22b508 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -2,7 +2,7 @@ name: Build and Test on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a9fba61..aad2708 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,7 +2,7 @@ name: CodeQL Security on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] schedule: diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 257581b..5f19b64 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -5,7 +5,7 @@ name: Codespell test # START OF COMMON SECTION on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/compiler-warnings.yml b/.github/workflows/compiler-warnings.yml index 4a25266..145be26 100644 --- a/.github/workflows/compiler-warnings.yml +++ b/.github/workflows/compiler-warnings.yml @@ -2,7 +2,7 @@ name: Compiler Warnings on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/empty-brace-scan.yml b/.github/workflows/empty-brace-scan.yml index 85e78d4..a0fb6f9 100644 --- a/.github/workflows/empty-brace-scan.yml +++ b/.github/workflows/empty-brace-scan.yml @@ -2,7 +2,7 @@ name: Empty Brace Scope Scan on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/memory-check.yml b/.github/workflows/memory-check.yml index b3ea538..71e6307 100644 --- a/.github/workflows/memory-check.yml +++ b/.github/workflows/memory-check.yml @@ -2,7 +2,7 @@ name: Memory Check on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/multi-compiler.yml b/.github/workflows/multi-compiler.yml index f551b71..5755d0e 100644 --- a/.github/workflows/multi-compiler.yml +++ b/.github/workflows/multi-compiler.yml @@ -2,7 +2,7 @@ name: Multiple Compilers on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/spdm-emu-pqc-test.yml b/.github/workflows/spdm-emu-pqc-test.yml index 687fc89..b5b1a0f 100644 --- a/.github/workflows/spdm-emu-pqc-test.yml +++ b/.github/workflows/spdm-emu-pqc-test.yml @@ -10,7 +10,7 @@ name: SPDM Emulator PQC Test on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/spdm-emu-test.yml b/.github/workflows/spdm-emu-test.yml index 0a19228..8513a23 100644 --- a/.github/workflows/spdm-emu-test.yml +++ b/.github/workflows/spdm-emu-test.yml @@ -2,7 +2,7 @@ name: SPDM Emulator Integration Test on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 928ff1e..783069a 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -2,7 +2,7 @@ name: Static Analysis on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/wolfssl-versions.yml b/.github/workflows/wolfssl-versions.yml index 13f9db0..899a5ed 100644 --- a/.github/workflows/wolfssl-versions.yml +++ b/.github/workflows/wolfssl-versions.yml @@ -2,7 +2,7 @@ name: wolfSSL Version Matrix on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: diff --git a/.github/workflows/wolftpm-downstream.yml b/.github/workflows/wolftpm-downstream.yml index 20f4dfc..85085f2 100644 --- a/.github/workflows/wolftpm-downstream.yml +++ b/.github/workflows/wolftpm-downstream.yml @@ -6,7 +6,7 @@ name: wolfTPM downstream on: push: - branches: [ 'main', 'wolftpm-core', 'release/**' ] + branches: [ 'main', 'release/**' ] pull_request: branches: [ '*' ] repository_dispatch: From ecc89abf7d3817b707d3fa6a1329046580461501 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 28 Sep 2026 12:42:55 -0700 Subject: [PATCH 23/27] Support wolfTPM's lib/wolfSPDM submodule layout in the downstream job --- .github/workflows/wolftpm-downstream.yml | 22 +++++++++++++++------- scripts/wolftpm-overlay.sh | 9 +++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.github/workflows/wolftpm-downstream.yml b/.github/workflows/wolftpm-downstream.yml index 85085f2..98a3c6c 100644 --- a/.github/workflows/wolftpm-downstream.yml +++ b/.github/workflows/wolftpm-downstream.yml @@ -1,8 +1,8 @@ name: wolfTPM downstream -# Builds wolfTPM master with this wolfSPDM tree in place of its embedded SPDM -# sources and runs wolfTPM's SPDM test suite, so no change can land in -# wolfSPDM that breaks wolfTPM. +# Builds wolfTPM master (or a dispatched repo/ref) with this wolfSPDM tree in +# place of its SPDM sources and runs wolfTPM's SPDM test suite, so no change +# can land in wolfSPDM that breaks wolfTPM. on: push: @@ -11,6 +11,14 @@ on: branches: [ '*' ] repository_dispatch: types: [nightly-trigger] + workflow_dispatch: + inputs: + wolftpm_repo: + description: wolfTPM repository to test against + default: wolfSSL/wolfTPM + wolftpm_ref: + description: wolfTPM branch or commit + default: master permissions: contents: read @@ -63,8 +71,8 @@ jobs: - name: Checkout wolfTPM uses: actions/checkout@v4 with: - repository: wolfSSL/wolfTPM - ref: master + repository: ${{ inputs.wolftpm_repo || 'wolfSSL/wolfTPM' }} + ref: ${{ inputs.wolftpm_ref || 'master' }} persist-credentials: false - name: Checkout wolfSPDM @@ -134,8 +142,8 @@ jobs: - name: Checkout wolfTPM uses: actions/checkout@v4 with: - repository: wolfSSL/wolfTPM - ref: master + repository: ${{ inputs.wolftpm_repo || 'wolfSSL/wolfTPM' }} + ref: ${{ inputs.wolftpm_ref || 'master' }} persist-credentials: false - name: Checkout wolfSPDM diff --git a/scripts/wolftpm-overlay.sh b/scripts/wolftpm-overlay.sh index cac8bcc..f78bb04 100755 --- a/scripts/wolftpm-overlay.sh +++ b/scripts/wolftpm-overlay.sh @@ -13,6 +13,15 @@ usage() { TPM=$1 SPDM=$(cd "$(dirname "$0")/.." && pwd) +# wolfTPM with the lib/wolfSPDM submodule: replace the submodule contents +if grep -qs 'lib/wolfSPDM' "$TPM/.gitmodules"; then + mkdir -p "$TPM/lib/wolfSPDM" + rm -rf "$TPM/lib/wolfSPDM/src" "$TPM/lib/wolfSPDM/wolfspdm" + cp -R "$SPDM/src" "$SPDM/wolfspdm" "$SPDM/LICENSE" "$TPM/lib/wolfSPDM/" + echo "wolfSPDM copied into $TPM/lib/wolfSPDM" + exit 0 +fi + [ -d "$TPM/src/spdm" ] && [ -d "$TPM/wolftpm/spdm" ] || { echo "error: $TPM does not look like a wolfTPM tree with src/spdm" >&2 exit 1 From 299cec82bc042914a193d2902cc8213097f7bbf1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 29 Sep 2026 10:27:40 -0700 Subject: [PATCH 24/27] Fix the Opus review findings in session gates, responder wipes and FINISH sizing --- .github/workflows/wolftpm-downstream.yml | 1 + Makefile.am | 1 + examples/spdm_demo.c | 4 +- src/spdm_context.c | 9 +++ src/spdm_internal.h | 13 +++++ src/spdm_msg.c | 11 ++-- src/spdm_psk.c | 16 ++---- src/spdm_responder.c | 73 +++++++++++++----------- src/spdm_secured.c | 10 ++-- src/spdm_session.c | 18 ++++-- src/spdm_standard.c | 19 +++--- test/unit_test.c | 41 ++++++++++++- wolfspdm/spdm.h | 2 +- wolfspdm/spdm_error.h | 1 - wolfspdm/spdm_responder.h | 9 +-- wolfspdm/spdm_types.h | 13 +++++ 16 files changed, 165 insertions(+), 76 deletions(-) diff --git a/.github/workflows/wolftpm-downstream.yml b/.github/workflows/wolftpm-downstream.yml index 98a3c6c..638f24b 100644 --- a/.github/workflows/wolftpm-downstream.yml +++ b/.github/workflows/wolftpm-downstream.yml @@ -173,6 +173,7 @@ jobs: make -j"$(nproc)" - name: Run wolfTPM make check + if: matrix.mode == 'fwtpm-tcg' run: | export LD_LIBRARY_PATH="$HOME/wolfssl-install/lib" set -eo pipefail diff --git a/Makefile.am b/Makefile.am index edb2ecf..0d40aec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,7 @@ endif libwolfspdm_la_CPPFLAGS = -I$(srcdir)/wolfspdm -I$(srcdir)/src libwolfspdm_la_LIBADD = -lwolfssl +libwolfspdm_la_LDFLAGS = -version-info 1:0:0 # Generate options.h from config.h (propagate WOLFSPDM_ defines to consumers) BUILT_SOURCES = wolfspdm/options.h diff --git a/examples/spdm_demo.c b/examples/spdm_demo.c index 3eeb9ab..ce70b8d 100644 --- a/examples/spdm_demo.c +++ b/examples/spdm_demo.c @@ -437,8 +437,8 @@ static int do_challenge(WOLFSPDM_CTX* ctx) rc = load_trusted_ca(ctx); if (rc != 0) { rc = WOLFSPDM_E_INVALID_ARG; goto done; } - /* wolfSPDM_Challenge internally validates the cert chain against the - * loaded CAs when flags.hasTrustedCAs is set. */ + /* wolfSPDM_Challenge validates the chain against the loaded root CA + * before sending CHALLENGE */ rc = wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_ALL); if (rc == WOLFSPDM_SUCCESS) { printf("Challenge succeeded (signature verified)\n"); diff --git a/src/spdm_context.c b/src/spdm_context.c index 669497f..c8370dc 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -432,6 +432,15 @@ void wolfSPDM_ResetSession(WOLFSPDM_CTX* ctx) wc_ForceZero(ctx->th1, sizeof(ctx->th1)); wc_ForceZero(ctx->th2, sizeof(ctx->th2)); wolfSPDM_FreeEphemeralKey(ctx); +#ifndef WOLFSPDM_NO_CERT + ctx->rspCaps = 0; + ctx->dataTransferSize = 0; + ctx->maxSpdmMsgSize = 0; +#endif +#ifndef WOLFSPDM_NO_MEAS + ctx->measBlockCount = 0; + ctx->measRecordLen = 0; +#endif #if !defined(WOLFSPDM_NO_MEAS) || !defined(WOLFSPDM_NO_CHALLENGE) wolfSPDM_AttestFree(ctx); #endif diff --git a/src/spdm_internal.h b/src/spdm_internal.h index 518ee71..ac085b4 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -259,6 +259,19 @@ struct WOLFSPDM_CTX { }; /* The vendor modes select TCG binding framing and pinned-key identity */ +/* Constant-time compare of secrets: 0 when equal */ +static WC_INLINE int wolfSPDM_ConstCompare(const byte* a, const byte* b, + word32 sz) +{ + volatile byte diff = 0; + word32 i; + + for (i = 0; i < sz; i++) { + diff |= (byte)(a[i] ^ b[i]); + } + return (int)diff; +} + static WC_INLINE int wolfSPDM_IsTcgMode(const WOLFSPDM_CTX* ctx) { #ifdef WOLFSPDM_TCG diff --git a/src/spdm_msg.c b/src/spdm_msg.c index c79e945..ee2a933 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -581,12 +581,8 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, ctx->th1, expectedHmac); } if (rc == WOLFSPDM_SUCCESS) { - word32 i; - volatile int diff = 0; - for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { - diff |= expectedHmac[i] ^ rspVerifyData[i]; - } - if (diff != 0) { + if (wolfSPDM_ConstCompare(expectedHmac, rspVerifyData, + WOLFSPDM_HASH_SIZE) != 0) { wolfSPDM_DebugPrint(ctx, "ResponderVerifyData MISMATCH\n"); rc = WOLFSPDM_E_BAD_HMAC; } @@ -624,6 +620,9 @@ int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) return WOLFSPDM_E_BUFFER_SMALL; } opaqueLen = SPDM_Get16LE(&buf[4]); + if (opaqueLen > WOLFSPDM_FINISH_OPAQUE_MAX) { + return WOLFSPDM_E_INVALID_ARG; + } rspMsgLen = 4 + 2 + opaqueLen; if (bufSz < rspMsgLen) { return WOLFSPDM_E_BUFFER_SMALL; diff --git a/src/spdm_psk.c b/src/spdm_psk.c index 9c649bd..37eb50e 100644 --- a/src/spdm_psk.c +++ b/src/spdm_psk.c @@ -147,10 +147,6 @@ int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, /* Per SPDM 1.3 DSP0274 Table 65: * [4-5] RspSessionID, [6] MutAuthRequested, [7] ReqSlotIDParam, * [8-9] RspContextLength, [10-11] OpaqueDataLength */ - ctx->rspSessionId = SPDM_Get16LE(&buf[4]); - ctx->sessionId = (word32)ctx->reqSessionId | - ((word32)ctx->rspSessionId << 16); - rspContextLen = SPDM_Get16LE(&buf[8]); opaqueLen = SPDM_Get16LE(&buf[10]); @@ -181,16 +177,12 @@ int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, expectedHmac); } if (rc == WOLFSPDM_SUCCESS) { - word32 i; - volatile int diff = 0; wolfSPDM_DebugHex(ctx, "Expected HMAC", expectedHmac, WOLFSPDM_HASH_SIZE); wolfSPDM_DebugHex(ctx, "Received HMAC", rspVerifyData, WOLFSPDM_HASH_SIZE); - for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { - diff |= expectedHmac[i] ^ rspVerifyData[i]; - } - if (diff != 0) { + if (wolfSPDM_ConstCompare(expectedHmac, rspVerifyData, + WOLFSPDM_HASH_SIZE) != 0) { wolfSPDM_DebugPrint(ctx, "PSK ResponderVerifyData MISMATCH\n"); rc = WOLFSPDM_E_BAD_HMAC; } @@ -199,7 +191,11 @@ int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, wolfSPDM_DebugPrint(ctx, "PSK ResponderVerifyData VERIFIED OK\n"); rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); } + /* The session exists only once ResponderVerifyData checks out */ if (rc == WOLFSPDM_SUCCESS) { + ctx->rspSessionId = SPDM_Get16LE(&buf[4]); + ctx->sessionId = (word32)ctx->reqSessionId | + ((word32)ctx->rspSessionId << 16); ctx->state = WOLFSPDM_STATE_KEY_EX; } diff --git a/src/spdm_responder.c b/src/spdm_responder.c index f9747fb..41a3e14 100644 --- a/src/spdm_responder.c +++ b/src/spdm_responder.c @@ -2,14 +2,14 @@ * * Copyright (C) 2006-2026 wolfSSL Inc. * - * This file is part of wolfTPM. + * This file is part of wolfSPDM. * - * wolfTPM is free software; you can redistribute it and/or modify + * wolfSPDM is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * - * wolfTPM is distributed in the hope that it will be useful, + * wolfSPDM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. @@ -290,6 +290,10 @@ void wolfSPDM_RespReset(WOLFSPDM_RESP_CTX* ctx) ctx->ctx.state = WOLFSPDM_STATE_INIT; ctx->flags.sessionAsym = 0; ctx->flags.pendingAsym = 0; + wc_ForceZero(ctx->secureInPlain, sizeof(ctx->secureInPlain)); + wc_ForceZero(ctx->secureOutPlain, sizeof(ctx->secureOutPlain)); + wc_ForceZero(ctx->vdInPayload, sizeof(ctx->vdInPayload)); + wc_ForceZero(ctx->vdOutPayload, sizeof(ctx->vdOutPayload)); } #ifdef WOLFSPDM_TCG @@ -845,8 +849,6 @@ static int RespHandleFinish(WOLFSPDM_RESP_CTX* rctx, byte th2[WOLFSPDM_HASH_SIZE]; byte expectedHmac[WOLFSPDM_HASH_SIZE]; int rc; - word32 i; - volatile int diff = 0; if (inSz < 4u + WOLFSPDM_HASH_SIZE) { return WOLFSPDM_E_FRAMING; @@ -865,10 +867,8 @@ static int RespHandleFinish(WOLFSPDM_RESP_CTX* rctx, expectedHmac); } if (rc == WOLFSPDM_SUCCESS) { - for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { - diff |= expectedHmac[i] ^ in[4 + i]; - } - if (diff != 0) { + if (wolfSPDM_ConstCompare(expectedHmac, in + 4, + WOLFSPDM_HASH_SIZE) != 0) { rc = WOLFSPDM_E_BAD_HMAC; } } @@ -901,8 +901,6 @@ static int RespHandlePskFinish(WOLFSPDM_RESP_CTX* rctx, byte th2Hash[WOLFSPDM_HASH_SIZE]; byte expectedHmac[WOLFSPDM_HASH_SIZE]; int rc; - word32 i; - volatile int diff = 0; if (inSz < 4u + WOLFSPDM_HASH_SIZE) { return WOLFSPDM_E_FRAMING; @@ -921,10 +919,8 @@ static int RespHandlePskFinish(WOLFSPDM_RESP_CTX* rctx, expectedHmac); } if (rc == WOLFSPDM_SUCCESS) { - for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { - diff |= expectedHmac[i] ^ in[4 + i]; - } - if (diff != 0) { + if (wolfSPDM_ConstCompare(expectedHmac, in + 4, + WOLFSPDM_HASH_SIZE) != 0) { rc = WOLFSPDM_E_BAD_HMAC; } } @@ -969,7 +965,7 @@ static int RespBuildEndSessionAck(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } -static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, +static int RespHandleVendorDefinedInner(WOLFSPDM_RESP_CTX* rctx, const byte* in, word32 inSz, byte* out, word32* outSz, int fromSecured, char* vdCodeOut) { @@ -1090,16 +1086,10 @@ static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, return WOLFSPDM_E_BAD_STATE; } if (rctx->pskStoreSz != 0) { - volatile int diff = 0; - word32 i; - if (rctx->pskStoreSz != pskLen) { return WOLFSPDM_E_BAD_STATE; } - for (i = 0; i < pskLen; i++) { - diff |= rctx->pskStore[i] ^ payload[i]; - } - if (diff != 0) { + if (wolfSPDM_ConstCompare(rctx->pskStore, payload, pskLen) != 0) { return WOLFSPDM_E_BAD_STATE; } } @@ -1116,8 +1106,7 @@ static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, else if (XSTRCMP(vdCode, "PSK_CLR_") == 0) { /* Payload: ClearAuth(32 raw bytes). Verify SHA-384 matches stored. */ byte digest[WOLFSPDM_HASH_SIZE]; - volatile int diff = 0; - word32 i; + int diff; if (payloadSz != 32 || !rctx->flags.pskProvisioned) { return WOLFSPDM_E_INVALID_ARG; } @@ -1127,9 +1116,8 @@ static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, return rc; } /* Constant-time compare, matching the FINISH HMAC paths. */ - for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { - diff |= digest[i] ^ rctx->clearAuthDigest[i]; - } + diff = wolfSPDM_ConstCompare(digest, rctx->clearAuthDigest, + WOLFSPDM_HASH_SIZE); wc_ForceZero(digest, sizeof(digest)); if (diff != 0) { return WOLFSPDM_E_BAD_HMAC; @@ -1170,13 +1158,22 @@ static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, off += respPayloadSz; } *outSz = off; - /* PSK_SET_ payloads and TPM traffic do not linger in scratch */ - wc_ForceZero(payload, payloadSz); - wc_ForceZero(respPayload, respPayloadSz); return WOLFSPDM_SUCCESS; } -static int RespDispatchSecured(WOLFSPDM_RESP_CTX* rctx, +/* PSK_SET_ payloads and TPM traffic do not linger in scratch, on any path */ +static int RespHandleVendorDefined(WOLFSPDM_RESP_CTX* rctx, + const byte* in, word32 inSz, byte* out, word32* outSz, int fromSecured, + char* vdCodeOut) +{ + int rc = RespHandleVendorDefinedInner(rctx, in, inSz, out, outSz, + fromSecured, vdCodeOut); + wc_ForceZero(rctx->vdInPayload, sizeof(rctx->vdInPayload)); + wc_ForceZero(rctx->vdOutPayload, sizeof(rctx->vdOutPayload)); + return rc; +} + +static int RespDispatchSecuredInner(WOLFSPDM_RESP_CTX* rctx, const byte* securedIn, word32 securedInSz, byte* securedOut, word32* securedOutSz) { @@ -1263,6 +1260,18 @@ static int RespDispatchSecured(WOLFSPDM_RESP_CTX* rctx, return rc; } +/* Decrypted TPM commands and responses are wiped on every path */ +static int RespDispatchSecured(WOLFSPDM_RESP_CTX* rctx, + const byte* securedIn, word32 securedInSz, + byte* securedOut, word32* securedOutSz) +{ + int rc = RespDispatchSecuredInner(rctx, securedIn, securedInSz, + securedOut, securedOutSz); + wc_ForceZero(rctx->secureInPlain, sizeof(rctx->secureInPlain)); + wc_ForceZero(rctx->secureOutPlain, sizeof(rctx->secureOutPlain)); + return rc; +} + #endif /* WOLFSPDM_TCG */ int wolfSPDM_RespHandleMessage(WOLFSPDM_RESP_CTX* ctx, diff --git a/src/spdm_secured.c b/src/spdm_secured.c index 62877b8..aae7707 100644 --- a/src/spdm_secured.c +++ b/src/spdm_secured.c @@ -124,7 +124,7 @@ static int wolfSPDM_EncryptRecord(WOLFSPDM_CTX* ctx, int appMsg, /* MCTP carries a 16-bit sequence number; fail rather than let the wire * value and the 64-bit IV counter diverge past 0xFFFF */ if (ctx->reqSeqNum > 0xFFFF) { - return WOLFSPDM_E_BAD_STATE; + return WOLFSPDM_E_SEQUENCE; } plainBufSz = encDataLen; @@ -409,10 +409,12 @@ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, if (ctx == NULL || cmdPlain == NULL || rspPlain == NULL || rspSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } - /* Session keys exist from KEY_EXCHANGE_RSP on (GIVE_PUB runs before - * FINISH); anything earlier would seal with zero keys */ + /* Session keys exist from KEY_EXCHANGE_RSP on, but only the TCG + * binding sends a request (GIVE_PUB) under handshake keys */ if (ctx->state < WOLFSPDM_STATE_KEY_EX || - ctx->state == WOLFSPDM_STATE_ERROR || ctx->sessionId == 0) { + ctx->state == WOLFSPDM_STATE_ERROR || ctx->sessionId == 0 || + (ctx->state == WOLFSPDM_STATE_KEY_EX && + !wolfSPDM_IsTcgMode(ctx))) { return WOLFSPDM_E_NOT_CONNECTED; } #ifndef WOLFSPDM_NO_MEAS diff --git a/src/spdm_session.c b/src/spdm_session.c index b7febae..6dbff75 100644 --- a/src/spdm_session.c +++ b/src/spdm_session.c @@ -81,7 +81,17 @@ int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) word32 rxSz = sizeof(rxBuf); int rc; - rc = wolfSPDM_BuildKeyExchange(ctx, txBuf, &txSz); + /* The signature in KEY_EXCHANGE_RSP can only be checked against a + * trusted responder key: ValidateCertChain or SetResponderPubKey */ + if (ctx == NULL) { + rc = WOLFSPDM_E_INVALID_ARG; + } + else if (!ctx->flags.hasRspPubKey) { + rc = WOLFSPDM_E_BAD_STATE; + } + else { + rc = wolfSPDM_BuildKeyExchange(ctx, txBuf, &txSz); + } #ifndef WOLFSPDM_NO_CHALLENGE /* KEY_EXCHANGE drops DIGESTS and CERTIFICATE from M1 */ if (rc == WOLFSPDM_SUCCESS && ctx->m1State != WOLFSPDM_RUN_NONE) { @@ -110,7 +120,7 @@ static int wolfSPDM_FinishXfer(WOLFSPDM_CTX* ctx, const byte* finishBuf, word32 finishSz, byte* decBuf, word32* decSz) { byte encBuf[WOLFSPDM_VENDOR_BUF_SZ]; - byte rxBuf[128]; /* Encrypted FINISH_RSP: ~94 bytes max */ + byte rxBuf[WOLFSPDM_FINISH_RSP_MAX + 128]; /* + record and TCG framing */ word32 encSz = sizeof(encBuf); word32 rxSz = sizeof(rxBuf); int rc; @@ -147,14 +157,14 @@ static int wolfSPDM_FinishXfer(WOLFSPDM_CTX* ctx, const byte* finishBuf, int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) { byte finishBuf[WOLFSPDM_FINISH_BUF_SZ]; - byte decBuf[64]; /* Decrypted FINISH_RSP: 4 hdr + 48 verify = 52 */ + byte decBuf[WOLFSPDM_FINISH_RSP_MAX]; word32 finishSz = sizeof(finishBuf); word32 decSz = sizeof(decBuf); int rc; /* FINISH is only valid after a successful KEY_EXCHANGE; otherwise the * session keys are unestablished (zero-entropy). */ - if (ctx == NULL || ctx->state < WOLFSPDM_STATE_KEY_EX) { + if (ctx == NULL || ctx->state != WOLFSPDM_STATE_KEY_EX) { return WOLFSPDM_E_BAD_STATE; } diff --git a/src/spdm_standard.c b/src/spdm_standard.c index d032756..8e95678 100644 --- a/src/spdm_standard.c +++ b/src/spdm_standard.c @@ -59,6 +59,9 @@ int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, { const word32 required = SPDM_CAP_CERT_CAP | SPDM_CAP_ENCRYPT_CAP | SPDM_CAP_MAC_CAP | SPDM_CAP_KEY_EX_CAP; + word32 caps; + word32 dts; + word32 maxMsg; SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CAPABILITIES, @@ -68,20 +71,20 @@ int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, return WOLFSPDM_E_CAPS_MISMATCH; } - ctx->rspCaps = SPDM_Get32LE(&buf[8]); - ctx->dataTransferSize = SPDM_Get32LE(&buf[12]); - ctx->maxSpdmMsgSize = SPDM_Get32LE(&buf[16]); + caps = SPDM_Get32LE(&buf[8]); + dts = SPDM_Get32LE(&buf[12]); + maxMsg = SPDM_Get32LE(&buf[16]); /* DSP0274: MinDataTransferSize is 42 */ - if ((ctx->rspCaps & required) != required || - ctx->dataTransferSize < 42 || - ctx->maxSpdmMsgSize < ctx->dataTransferSize) { + if ((caps & required) != required || dts < 42 || maxMsg < dts) { wolfSPDM_DebugPrint(ctx, "CAPABILITIES rejected: caps=0x%08x " - "dts=%u max=%u\n", ctx->rspCaps, ctx->dataTransferSize, - ctx->maxSpdmMsgSize); + "dts=%u max=%u\n", caps, dts, maxMsg); return WOLFSPDM_E_CAPS_MISMATCH; } + ctx->rspCaps = caps; + ctx->dataTransferSize = dts; + ctx->maxSpdmMsgSize = maxMsg; return WOLFSPDM_SUCCESS; } diff --git a/test/unit_test.c b/test/unit_test.c index 03caeb4..0f4e5c9 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1365,7 +1365,6 @@ static int test_key_zeroing(void) ASSERT_EQ(memcmp(ctx->th2, zeros, sizeof(ctx->th2)), 0, "th2 not zeroed"); - wolfSPDM_Init(ctx); TEST_CTX_FREE(); TEST_PASS(); } @@ -1449,6 +1448,18 @@ static int test_finish_no_io(void) "NULL ctx should fail"); TEST_ASSERT(wolfSPDM_Finish(ctx) != WOLFSPDM_SUCCESS, "No session should fail"); + ctx->state = WOLFSPDM_STATE_CONNECTED; + ASSERT_EQ(wolfSPDM_Finish(ctx), WOLFSPDM_E_BAD_STATE, + "FINISH twice"); + ctx->state = WOLFSPDM_STATE_ERROR; + ASSERT_EQ(wolfSPDM_Finish(ctx), WOLFSPDM_E_BAD_STATE, + "FINISH after a failure"); + + /* No trusted responder key: nothing is sent */ + ctx->state = WOLFSPDM_STATE_INIT; + wolfSPDM_SetIO(ctx, dummy_io_cb, NULL); + ASSERT_EQ(wolfSPDM_KeyExchange(ctx), WOLFSPDM_E_BAD_STATE, + "KEY_EXCHANGE without a responder key"); TEST_CTX_FREE(); TEST_PASS(); } @@ -1498,8 +1509,18 @@ static int test_disconnect_states(void) ctx->reqSeqNum = 1; XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); XMEMSET(ctx->handshakeSecret, 0x22, WOLFSPDM_HASH_SIZE); +#ifndef WOLFSPDM_NO_CERT + ctx->dataTransferSize = 3; +#endif +#ifndef WOLFSPDM_NO_MEAS + ctx->measBlockCount = 2; +#endif ASSERT_EQ(wolfSPDM_SecuredExchange(ctx, cmd, sizeof(cmd), rsp, &rspSz), WOLFSPDM_E_NOT_CONNECTED, "secured message after a failure"); + ctx->state = WOLFSPDM_STATE_KEY_EX; + ASSERT_EQ(wolfSPDM_SecuredExchange(ctx, cmd, sizeof(cmd), rsp, &rspSz), + WOLFSPDM_E_NOT_CONNECTED, "application request before FINISH"); + ctx->state = WOLFSPDM_STATE_ERROR; ASSERT_EQ(wolfSPDM_Disconnect(ctx), WOLFSPDM_E_NOT_CONNECTED, "failed handshake"); ASSERT_EQ(ctx->state, WOLFSPDM_STATE_INIT, "state reset"); @@ -1508,6 +1529,13 @@ static int test_disconnect_states(void) ASSERT_EQ(memcmp(ctx->reqDataKey, zero, sizeof(zero)), 0, "keys wiped"); ASSERT_EQ(memcmp(ctx->handshakeSecret, zero, sizeof(zero)), 0, "secrets wiped"); +#ifndef WOLFSPDM_NO_CERT + ASSERT_EQ(ctx->dataTransferSize, 0, "negotiated limits dropped"); +#endif +#ifndef WOLFSPDM_NO_MEAS + ASSERT_EQ(wolfSPDM_GetMeasurementCount(ctx), 0, + "previous session's measurements dropped"); +#endif /* A retry starts from sequence number 0 even if it fails early */ ctx->reqSeqNum = 5; @@ -1946,17 +1974,21 @@ static int test_parse_psk_exchange_rsp_hmac_check(void) ASSERT_EQ(rc, WOLFSPDM_SUCCESS, "valid PSK HMAC should succeed"); ASSERT_EQ(ctx->state, WOLFSPDM_STATE_KEY_EX, "state should advance to KEY_EX on valid PSK parse"); + ASSERT_EQ(ctx->rspSessionId, 0x1234, "session ID after verify"); /* Negative: flip one byte — must return BAD_HMAC. * Parse scrubs ctx->psk after derivation, so re-set it; also reset * transcript because the successful parse appended 60 bytes. */ wolfSPDM_TranscriptReset(ctx); ctx->state = WOLFSPDM_STATE_INIT; + ctx->sessionId = 0; + ctx->rspSessionId = 0; ASSERT_SUCCESS(wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0)); pskRsp[12] ^= 0x01; rc = wolfSPDM_ParsePskExchangeRsp(ctx, pskRsp, pskRspLen); ASSERT_EQ(rc, WOLFSPDM_E_BAD_HMAC, "flipped PSK rspVerifyData byte must return BAD_HMAC"); + ASSERT_EQ(ctx->sessionId, 0, "no session ID from an unverified reply"); TEST_CTX_FREE(); TEST_PASS(); @@ -3349,6 +3381,8 @@ static int test_parse_capabilities(void) SPDM_Set32LE(&rsp[12], 41); ASSERT_EQ(wolfSPDM_ParseCapabilities(ctx, rsp, sizeof(rsp)), WOLFSPDM_E_CAPS_MISMATCH, "DataTransferSize < 42 must fail"); + ASSERT_EQ(ctx->dataTransferSize, (word32)1024, + "rejected CAPABILITIES leaves the limits alone"); /* Version must echo the negotiated version */ SPDM_Set32LE(&rsp[12], 1024); @@ -4050,7 +4084,7 @@ static int test_secured_record_edges(void) ctx->reqSeqNum = 0x10000; encSz = sizeof(enc); ASSERT_EQ(wolfSPDM_EncryptInternal(ctx, plain, 1, enc, &encSz), - WOLFSPDM_E_BAD_STATE, "request sequence past 16 bits"); + WOLFSPDM_E_SEQUENCE, "request sequence past 16 bits"); ctx->reqSeqNum = 0; encSz = sizeof(enc); ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, 1, enc, &encSz)); @@ -4121,6 +4155,9 @@ static int test_version_and_finish_14(void) ASSERT_EQ(wolfSPDM_ParseFinishRsp(ctx, rsp, 8), WOLFSPDM_E_BUFFER_SMALL, "OpaqueData truncated"); ASSERT_SUCCESS(wolfSPDM_ParseFinishRsp(ctx, rsp, 10)); + SPDM_Set16LE(&rsp[4], WOLFSPDM_FINISH_OPAQUE_MAX + 1); + ASSERT_EQ(wolfSPDM_ParseFinishRsp(ctx, rsp, 10), WOLFSPDM_E_INVALID_ARG, + "OpaqueLength over the cap"); TEST_CTX_FREE(); TEST_PASS(); diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index 86079d5..5c40dd8 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -231,7 +231,7 @@ WOLFSPDM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); /* wolfSPDM_SetPSK declared in spdm_psk.h */ -/* Debug */ +/* Debug output exists only in WOLFSPDM_DEBUG (--enable-debug) builds */ WOLFSPDM_API void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable); #ifdef __cplusplus diff --git a/wolfspdm/spdm_error.h b/wolfspdm/spdm_error.h index bcd0acd..96debd1 100644 --- a/wolfspdm/spdm_error.h +++ b/wolfspdm/spdm_error.h @@ -64,7 +64,6 @@ enum WOLFSPDM_ERROR { }; /* Older spellings, kept for source compatibility */ -#define WOLFSPDM_E_MEAS_NOT_VERIFIED WOLFSPDM_E_MEASUREMENT #define WOLFSPDM_E_MEAS_SIG_FAIL WOLFSPDM_E_BAD_SIGNATURE /* Get human-readable error string */ diff --git a/wolfspdm/spdm_responder.h b/wolfspdm/spdm_responder.h index 9d88014..6acd76f 100644 --- a/wolfspdm/spdm_responder.h +++ b/wolfspdm/spdm_responder.h @@ -2,14 +2,14 @@ * * Copyright (C) 2006-2026 wolfSSL Inc. * - * This file is part of wolfTPM. + * This file is part of wolfSPDM. * - * wolfTPM is free software; you can redistribute it and/or modify + * wolfSPDM is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * - * wolfTPM is distributed in the hope that it will be useful, + * wolfSPDM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. @@ -28,9 +28,6 @@ #ifndef WOLFSPDM_RESPONDER_H #define WOLFSPDM_RESPONDER_H -#ifdef HAVE_CONFIG_H - #include -#endif #include diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index ba091cd..3b56c14 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -157,6 +157,9 @@ extern "C" { #ifndef WOLFSPDM_FINISH_BUF_SZ #define WOLFSPDM_FINISH_BUF_SZ 152 /* FINISH mutual auth (~148 bytes) */ #endif +/* SPDM 1.4 FINISH_RSP: header + OpaqueLength + OpaqueData up to this cap */ +#define WOLFSPDM_FINISH_OPAQUE_MAX 256 +#define WOLFSPDM_FINISH_RSP_MAX (4 + 2 + WOLFSPDM_FINISH_OPAQUE_MAX) #ifndef WOLFSPDM_VENDOR_BUF_SZ #define WOLFSPDM_VENDOR_BUF_SZ 256 /* Vendor command message/payload */ #endif @@ -463,6 +466,16 @@ extern "C" { #define WOLFSPDM_PSK #endif +/* wolfTPM guards its SPDM wrappers with its own prefix */ +#ifdef WOLFTPM_SPDM + #if defined(WOLFSPDM_TCG) && !defined(WOLFTPM_SPDM_TCG) + #define WOLFTPM_SPDM_TCG + #endif + #if defined(WOLFSPDM_PSK) && !defined(WOLFTPM_SPDM_PSK) + #define WOLFTPM_SPDM_PSK + #endif +#endif + /* ----- PSK Message Codes (SPDM 1.2+ DSP0274) ----- */ #define SPDM_PSK_EXCHANGE 0xE6 From 3b92690e3bc7b2d3555670829f330aed869d05a8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 29 Sep 2026 10:45:59 -0700 Subject: [PATCH 25/27] Load options.h for every consumer and keep a safe MEAS_NOT_VERIFIED spelling --- Makefile.am | 4 ++-- wolfspdm/spdm_error.h | 3 +++ wolfspdm/spdm_types.h | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index 0d40aec..386349c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,7 +31,7 @@ if BUILD_RESPONDER libwolfspdm_la_SOURCES += src/spdm_responder.c endif -libwolfspdm_la_CPPFLAGS = -I$(srcdir)/wolfspdm -I$(srcdir)/src +libwolfspdm_la_CPPFLAGS = -I$(srcdir)/wolfspdm -I$(srcdir)/src -DBUILDING_WOLFSPDM libwolfspdm_la_LIBADD = -lwolfssl libwolfspdm_la_LDFLAGS = -version-info 1:0:0 @@ -61,7 +61,7 @@ nobase_include_HEADERS = \ check_PROGRAMS = test/unit_test test_unit_test_SOURCES = test/unit_test.c test/test_certs.h test/test_certs_mldsa.h -test_unit_test_CPPFLAGS = -I$(srcdir)/wolfspdm -I$(srcdir)/src +test_unit_test_CPPFLAGS = -I$(srcdir)/wolfspdm -I$(srcdir)/src -DBUILDING_WOLFSPDM test_unit_test_LDADD = libwolfspdm.la -lwolfssl TESTS = test/unit_test diff --git a/wolfspdm/spdm_error.h b/wolfspdm/spdm_error.h index 96debd1..ba33965 100644 --- a/wolfspdm/spdm_error.h +++ b/wolfspdm/spdm_error.h @@ -65,6 +65,9 @@ enum WOLFSPDM_ERROR { /* Older spellings, kept for source compatibility */ #define WOLFSPDM_E_MEAS_SIG_FAIL WOLFSPDM_E_BAD_SIGNATURE +/* Never returned: unverified measurements are now an error, so an old + * "rc != E_MEAS_NOT_VERIFIED" soft-success check can no longer pass one */ +#define WOLFSPDM_E_MEAS_NOT_VERIFIED (-1000) /* Get human-readable error string */ WOLFSPDM_API const char* wolfSPDM_GetErrorString(int error); diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 3b56c14..96b692b 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -28,7 +28,9 @@ #endif #include -#if !defined(HAVE_CONFIG_H) && !defined(WOLFTPM_SPDM) && \ +/* The library build takes its switches from config.h; consumers from the + * generated options.h, even when they have a config.h of their own */ +#if !defined(BUILDING_WOLFSPDM) && !defined(WOLFTPM_SPDM) && \ !defined(WOLFSPDM_USER_SETTINGS) #include #endif From bcb9e9b15d01142366258e3b583e5374cccc72cb Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 29 Sep 2026 11:00:29 -0700 Subject: [PATCH 26/27] Name the legacy MEAS_NOT_VERIFIED code in GetErrorString --- src/spdm_context.c | 2 ++ test/unit_test.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/spdm_context.c b/src/spdm_context.c index c8370dc..c4ca8b2 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -683,6 +683,8 @@ const char* wolfSPDM_GetErrorString(int error) case WOLFSPDM_E_MEASUREMENT: return "Measurement response invalid"; case WOLFSPDM_E_CHALLENGE: return "Challenge response invalid"; case WOLFSPDM_E_CHUNK: return "Chunked transfer failed"; + case WOLFSPDM_E_MEAS_NOT_VERIFIED: + return "Measurements not verified (legacy, never returned)"; default: return "Unknown error"; } } diff --git a/test/unit_test.c b/test/unit_test.c index 0f4e5c9..0f10cb9 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -378,6 +378,8 @@ static int test_error_strings(void) "Invalid argument") == 0, "INVALID_ARG string wrong"); TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_E_CRYPTO_FAIL), "Crypto operation failed") == 0, "CRYPTO_FAIL string wrong"); + TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_E_MEAS_NOT_VERIFIED), + "Unknown error") != 0, "legacy MEAS_NOT_VERIFIED string"); TEST_PASS(); } From c0f0ce27461f4d8f2cb3bff069f8f737d370de71 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 29 Sep 2026 11:08:53 -0700 Subject: [PATCH 27/27] Check the exact legacy MEAS_NOT_VERIFIED error string --- test/unit_test.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit_test.c b/test/unit_test.c index 0f10cb9..d22b9f2 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -379,7 +379,8 @@ static int test_error_strings(void) TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_E_CRYPTO_FAIL), "Crypto operation failed") == 0, "CRYPTO_FAIL string wrong"); TEST_ASSERT(strcmp(wolfSPDM_GetErrorString(WOLFSPDM_E_MEAS_NOT_VERIFIED), - "Unknown error") != 0, "legacy MEAS_NOT_VERIFIED string"); + "Measurements not verified (legacy, never returned)") == 0, + "legacy MEAS_NOT_VERIFIED string"); TEST_PASS(); }