From e56719c991c568cd093c069f6075f99acace904a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 26 Feb 2026 18:25:07 +0000 Subject: [PATCH 1/3] Add full SPDM nuvoton and spdm-emu support as wolfTPM/spdm --- .github/workflows/make-test-swtpm.yml | 18 + .github/workflows/spdm-emu-test.yml | 76 ++ .gitignore | 11 +- Makefile.am | 1 + configure.ac | 56 ++ examples/include.am | 1 + examples/spdm/README.md | 90 ++ examples/spdm/include.am | 18 + examples/spdm/spdm_demo.c | 907 +++++++++++++++++ examples/spdm/spdm_test.sh | 332 +++++++ spdm/README.md | 324 +++++++ spdm/include.am | 35 + spdm/src/spdm_context.c | 653 +++++++++++++ spdm/src/spdm_crypto.c | 265 +++++ spdm/src/spdm_internal.h | 553 +++++++++++ spdm/src/spdm_kdf.c | 318 ++++++ spdm/src/spdm_msg.c | 1292 +++++++++++++++++++++++++ spdm/src/spdm_nuvoton.c | 737 ++++++++++++++ spdm/src/spdm_secured.c | 534 ++++++++++ spdm/src/spdm_session.c | 591 +++++++++++ spdm/src/spdm_transcript.c | 132 +++ spdm/test/unit_test.c | 1048 ++++++++++++++++++++ spdm/wolfspdm/spdm.h | 598 ++++++++++++ spdm/wolfspdm/spdm_error.h | 67 ++ spdm/wolfspdm/spdm_nuvoton.h | 319 ++++++ spdm/wolfspdm/spdm_types.h | 254 +++++ src/include.am | 4 + src/tpm2.c | 184 +++- src/tpm2_packet.c | 1 + src/tpm2_spdm.c | 377 ++++++++ src/tpm2_swtpm.c | 5 +- src/tpm2_wrap.c | 430 +++++++- tests/unit_tests.c | 75 ++ wolftpm/include.am | 1 + wolftpm/tpm2.h | 138 ++- wolftpm/tpm2_packet.h | 1 + wolftpm/tpm2_spdm.h | 204 ++++ wolftpm/tpm2_types.h | 3 + wolftpm/tpm2_wrap.h | 181 ++++ zephyr/CMakeLists.txt | 4 + 40 files changed, 10818 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/spdm-emu-test.yml create mode 100644 examples/spdm/README.md create mode 100644 examples/spdm/include.am create mode 100644 examples/spdm/spdm_demo.c create mode 100755 examples/spdm/spdm_test.sh create mode 100644 spdm/README.md create mode 100644 spdm/include.am create mode 100644 spdm/src/spdm_context.c create mode 100644 spdm/src/spdm_crypto.c create mode 100644 spdm/src/spdm_internal.h create mode 100644 spdm/src/spdm_kdf.c create mode 100644 spdm/src/spdm_msg.c create mode 100644 spdm/src/spdm_nuvoton.c create mode 100644 spdm/src/spdm_secured.c create mode 100644 spdm/src/spdm_session.c create mode 100644 spdm/src/spdm_transcript.c create mode 100644 spdm/test/unit_test.c create mode 100644 spdm/wolfspdm/spdm.h create mode 100644 spdm/wolfspdm/spdm_error.h create mode 100644 spdm/wolfspdm/spdm_nuvoton.h create mode 100644 spdm/wolfspdm/spdm_types.h create mode 100644 src/tpm2_spdm.c create mode 100644 wolftpm/tpm2_spdm.h diff --git a/.github/workflows/make-test-swtpm.yml b/.github/workflows/make-test-swtpm.yml index c44197c2..0b4c3b9f 100644 --- a/.github/workflows/make-test-swtpm.yml +++ b/.github/workflows/make-test-swtpm.yml @@ -75,6 +75,24 @@ jobs: # STMicro ST33KTPM2 - name: st33ktpm2 firmware wolftpm_config: --enable-st33 --enable-firmware + # SPDM (emulator mode, compile + unit test) + - name: spdm + wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp + wolftpm_config: --enable-spdm --enable-swtpm + # SPDM + Nuvoton (compile-only, no hardware in CI) + - name: spdm-nuvoton + wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp + wolftpm_config: --enable-spdm --enable-nuvoton + needs_swtpm: false + # SPDM dynamic memory + - name: spdm-dynamic-mem + wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp + wolftpm_config: --enable-spdm --enable-swtpm --enable-spdm-dynamic-mem + # SPDM debug + - name: spdm-debug + wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp + wolftpm_config: --enable-spdm --enable-nuvoton --enable-debug + needs_swtpm: false # Microchip - name: microchip wolftpm_config: --enable-microchip diff --git a/.github/workflows/spdm-emu-test.yml b/.github/workflows/spdm-emu-test.yml new file mode 100644 index 00000000..e45b27e4 --- /dev/null +++ b/.github/workflows/spdm-emu-test.yml @@ -0,0 +1,76 @@ +name: SPDM Emulator Integration Test + +on: + push: + branches: [ 'master', 'main', 'release/**' ] + paths: [ 'spdm/**', 'src/tpm2_spdm.c', 'examples/spdm/**' ] + pull_request: + branches: [ '*' ] + paths: [ 'spdm/**', 'src/tpm2_spdm.c', 'examples/spdm/**' ] + +jobs: + spdm-emu-test: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-22.04 + arch: x64 + - os: ubuntu-24.04 + arch: x64 + - os: ubuntu-24.04-arm + arch: aarch64 + runs-on: ${{ matrix.os }} + steps: + - name: Checkout wolfTPM + uses: actions/checkout@v4 + + - name: Cache wolfSSL + id: cache-wolfssl + uses: actions/cache@v4 + with: + path: wolfssl + key: wolfssl-spdm-${{ matrix.os }}-${{ hashFiles('.github/workflows/spdm-emu-test.yml') }} + + - name: Build wolfSSL + if: steps.cache-wolfssl.outputs.cache-hit != 'true' + run: | + 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 + make -j$(nproc) + + - name: Install wolfSSL + run: | + cd wolfssl + sudo make install + sudo ldconfig + + - name: Cache spdm-emu + id: cache-spdm-emu + uses: actions/cache@v4 + with: + path: spdm-emu/build/bin + key: spdm-emu-${{ matrix.os }}-${{ hashFiles('.github/workflows/spdm-emu-test.yml') }} + + - name: Build spdm-emu + if: steps.cache-spdm-emu.outputs.cache-hit != 'true' + run: | + 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) + + - name: Build wolfTPM with SPDM + run: | + ./autogen.sh + ./configure --enable-spdm --enable-swtpm --enable-debug + make -j$(nproc) + + - name: Run SPDM emulator tests + run: | + export SPDM_EMU_PATH=$PWD/spdm-emu/build/bin + ./examples/spdm/spdm_test.sh --emu diff --git a/.gitignore b/.gitignore index d6a057fb..66039172 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ examples/firmware/ifx_fw_update examples/firmware/st33_fw_update examples/endorsement/get_ek_certs examples/endorsement/verify_ek_cert +examples/spdm/spdm_demo # Generated Cert Files certs/ca-*.pem @@ -176,10 +177,18 @@ UpgradeLog.htm /IDE/Espressif/**/sdkconfig /IDE/Espressif/**/sdkconfig.old +# SPDM build artifacts +spdm/wolfspdm/options.h +spdm/config.h +spdm/stamp-h1 +spdm/src/.libs/ +spdm/src/.deps/ +spdm/test/.libs/ +spdm/test/unit_test + # Firmware files examples/firmware/*.fi examples/firmware/*.BIN examples/firmware/*.DATA examples/firmware/*.MANIFEST examples/firmware/*.MANIFESTHASH - diff --git a/Makefile.am b/Makefile.am index afee7851..458d6cdd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -46,6 +46,7 @@ include tests/include.am include docs/include.am include wrapper/include.am include hal/include.am +include spdm/include.am include cmake/include.am include zephyr/include.am diff --git a/configure.ac b/configure.ac index 00533a2a..665c1d05 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,7 @@ AC_CANONICAL_HOST AC_CANONICAL_TARGET AC_CONFIG_MACRO_DIR([m4]) + AM_INIT_AUTOMAKE([1.11 -Wall -Werror -Wno-portability foreign tar-ustar subdir-objects no-define color-tests]) AC_ARG_PROGRAM @@ -462,6 +463,52 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_PROVISIONING" fi +# SPDM Support +AC_ARG_ENABLE([spdm], + [AS_HELP_STRING([--enable-spdm],[Enable SPDM support (default: disabled)])], + [ ENABLED_SPDM=$enableval ], + [ ENABLED_SPDM=no ] + ) + +AC_ARG_WITH([wolfspdm], + [AS_HELP_STRING([--with-wolfspdm=PATH],[DEPRECATED: Use --enable-spdm instead.])], + [AC_MSG_ERROR([--with-wolfspdm is no longer needed. Use --enable-spdm instead.])]) + +# SPDM dynamic memory (default: static/zero-malloc) +AC_ARG_ENABLE([spdm-dynamic-mem], + [AS_HELP_STRING([--enable-spdm-dynamic-mem],[SPDM: Use heap allocation for context (default: static)])], + [ ENABLED_SPDM_DYNMEM=$enableval ], + [ ENABLED_SPDM_DYNMEM=no ] + ) + +if test "x$ENABLED_SPDM" = "xyes" +then + AC_DEFINE([WOLFTPM_SPDM], [1], [Enable SPDM support]) + + # Add spdm/ include path so all targets can find + AM_CPPFLAGS="$AM_CPPFLAGS -I\$(srcdir)/spdm" + + # Nuvoton SPDM support + if test "x$ENABLED_NUVOTON" = "xyes" + then + if test "x$ENABLED_SWTPM" = "xyes" + then + AC_MSG_ERROR([Cannot enable both swtpm and nuvoton with SPDM. Use --enable-swtpm --enable-spdm for emulator testing, or --enable-nuvoton --enable-spdm for hardware.]) + fi + AC_DEFINE([WOLFSPDM_NUVOTON], [1], [Enable SPDM Nuvoton TPM support]) + AC_MSG_NOTICE([Nuvoton SPDM vendor commands enabled]) + fi + + if test "x$ENABLED_SPDM_DYNMEM" = "xyes" + then + AC_DEFINE([WOLFSPDM_DYNAMIC_MEMORY], [1], [SPDM: Enable dynamic memory allocation]) + fi + + if test "x$ax_enable_debug" != "xno" + then + AC_DEFINE([WOLFSPDM_DEBUG], [1], [SPDM: Enable debug output]) + fi +fi # HARDEN FLAGS AX_HARDEN_CC_COMPILER_FLAGS @@ -493,6 +540,7 @@ AM_CONDITIONAL([BUILD_CHECKWAITSTATE], [test "x$ENABLED_CHECKWAITSTATE" = "xyes" AM_CONDITIONAL([BUILD_AUTODETECT], [test "x$ENABLED_AUTODETECT" = "xyes"]) AM_CONDITIONAL([BUILD_FIRMWARE], [test "x$ENABLED_FIRMWARE" = "xyes"]) AM_CONDITIONAL([BUILD_HAL], [test "x$ENABLED_EXAMPLE_HAL" = "xyes" || test "x$ENABLED_MMIO" = "xyes"]) +AM_CONDITIONAL([BUILD_SPDM], [test "x$ENABLED_SPDM" = "xyes"]) CREATE_HEX_VERSION @@ -578,6 +626,10 @@ for option in $OPTION_FLAGS; do fi done +# Also capture SPDM defines from config.h (set via AC_DEFINE, not AM_CFLAGS) +grep '^#define WOLFSPDM_' src/config.h >> $OPTION_FILE 2>/dev/null || true +grep '^#define WOLFTPM_SPDM' src/config.h >> $OPTION_FILE 2>/dev/null || true + echo "" >> $OPTION_FILE echo "#ifdef __cplusplus" >> $OPTION_FILE echo "}" >> $OPTION_FILE @@ -622,3 +674,7 @@ echo " * Nuvoton NPCT75x: $ENABLED_NUVOTON" echo " * Runtime Module Detection: $ENABLED_AUTODETECT" echo " * Firmware Upgrade Support: $ENABLED_FIRMWARE" +echo " * SPDM Support: $ENABLED_SPDM" +if test "x$ENABLED_SPDM" = "xyes"; then + echo " * SPDM Dynamic Mem: $ENABLED_SPDM_DYNMEM" +fi diff --git a/examples/include.am b/examples/include.am index 96c034f0..d34804ac 100644 --- a/examples/include.am +++ b/examples/include.am @@ -18,6 +18,7 @@ include examples/seal/include.am include examples/attestation/include.am include examples/firmware/include.am include examples/endorsement/include.am +include examples/spdm/include.am if BUILD_EXAMPLES EXTRA_DIST += examples/run_examples.sh diff --git a/examples/spdm/README.md b/examples/spdm/README.md new file mode 100644 index 00000000..af3357dd --- /dev/null +++ b/examples/spdm/README.md @@ -0,0 +1,90 @@ +# SPDM Examples + +This directory contains examples demonstrating SPDM (Security Protocol and Data Model) +functionality with wolfTPM. + +## Overview + +The SPDM demo (`spdm_demo`) shows how to establish an SPDM secure session between +the host and a TPM using the built-in wolfSPDM library. It supports both the standard +spdm-emu emulator and Nuvoton hardware TPMs. + +For real SPDM support on hardware TPMs, contact **support@wolfssl.com** + +## Example + +### `spdm_demo.c` - SPDM Secure Session Demo + +**Quick test (emulator — starts/stops automatically):** + +```bash +./examples/spdm/spdm_test.sh --emu +``` + +Runs session establishment, signed measurements, unsigned measurements, +challenge authentication, heartbeat, and key update. + +**Quick test (Nuvoton hardware):** + +```bash +./examples/spdm/spdm_test.sh --nuvoton +``` + +Runs connect, lock, caps-over-SPDM, unlock, and cleartext verification. + +**Manual commands:** + +```bash +# Emulator (start spdm_responder_emu first, see spdm/README.md) +./spdm_demo --emu # Session only +./spdm_demo --meas # Session + signed measurements +./spdm_demo --meas --no-sig # Session + unsigned measurements +./spdm_demo --challenge # Sessionless challenge authentication +./spdm_demo --emu --heartbeat # Session + heartbeat keep-alive +./spdm_demo --emu --key-update # Session + key rotation + +# Nuvoton hardware +./spdm_demo --enable # Enable SPDM on TPM (one-time, requires reset) +./spdm_demo --connect --status # Connect + get SPDM status +./spdm_demo --connect --lock # Connect + lock SPDM-only mode +./spdm_demo --connect --caps # Connect + run TPM commands over SPDM +./spdm_demo --connect --unlock # Connect + unlock SPDM-only mode +``` + +## Building + +### Prerequisites + +Build wolfSSL with the cryptographic algorithms required by SPDM: + +```bash +# wolfSSL (needs ECC P-384, SHA-384, AES-GCM, HKDF for SPDM) +cd wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp +make && sudo make install && sudo ldconfig +``` + +### wolfTPM with SPDM + +SPDM support is built into wolfTPM (no external wolfSPDM needed): + +```bash +cd wolfTPM +./autogen.sh +./configure --enable-spdm +make +``` + +For Nuvoton hardware TPMs, add `--enable-nuvoton`: + +```bash +./configure --enable-spdm --enable-nuvoton +make +``` + +## Support + +For production use with hardware TPMs and full SPDM protocol support, contact: + +**support@wolfssl.com** diff --git a/examples/spdm/include.am b/examples/spdm/include.am new file mode 100644 index 00000000..0d07e8a4 --- /dev/null +++ b/examples/spdm/include.am @@ -0,0 +1,18 @@ +# vim:ft=automake +# All paths should be given relative to the root + +if BUILD_EXAMPLES +if BUILD_SPDM +noinst_PROGRAMS += examples/spdm/spdm_demo + +examples_spdm_spdm_demo_SOURCES = examples/spdm/spdm_demo.c +examples_spdm_spdm_demo_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_spdm_spdm_demo_DEPENDENCIES = src/libwolftpm.la +examples_spdm_spdm_demo_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/spdm +endif +endif + +example_spdmdir = $(exampledir)/spdm +dist_example_spdm_DATA = examples/spdm/spdm_demo.c + +DISTCLEANFILES+= examples/spdm/.libs/spdm_demo diff --git a/examples/spdm/spdm_demo.c b/examples/spdm/spdm_demo.c new file mode 100644 index 00000000..36813806 --- /dev/null +++ b/examples/spdm/spdm_demo.c @@ -0,0 +1,907 @@ +/* spdm_demo.c + * + * Copyright (C) 2006-2025 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 Secure Session Demo + * + * Demonstrates establishing an SPDM secure session with a TPM + * and running TPM commands over the encrypted channel. + * + * Targets: Nuvoton NPCT75x (Fw 7.2+) connected via SPI + * + * Usage: + * ./spdm_demo --enable Enable SPDM on TPM (requires reset) + * ./spdm_demo --disable Disable SPDM on TPM (requires reset) + * ./spdm_demo --status Query SPDM status + * ./spdm_demo --connect Establish SPDM session and run test command + * ./spdm_demo --lock Lock SPDM-only mode + * ./spdm_demo --unlock Unlock SPDM-only mode + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#include +#include +#include + +/* Socket includes for TCP transport to libspdm emulator */ +#ifdef WOLFTPM_SWTPM + #include + #include + #include /* TCP_NODELAY */ + #include + #include + #include + #define SPDM_EMU_DEFAULT_PORT 2323 /* DEFAULT_SPDM_PLATFORM_PORT (MCTP) */ + #define SPDM_EMU_DEFAULT_HOST "127.0.0.1" +#endif + +#ifndef WOLFTPM2_NO_WRAPPER + +#include +#include + +#ifdef WOLFTPM_SPDM + +#include + +#include + +/* -------------------------------------------------------------------------- */ +/* Unified SPDM I/O Layer + * + * Single I/O callback that handles both: + * - TCP transport to libspdm emulator (--emu mode) + * - TPM TIS transport to Nuvoton hardware (--connect mode) + * + * The callback gates internally based on the transport mode set in context. + * -------------------------------------------------------------------------- */ + +#ifdef WOLFTPM_SWTPM +/* Transport modes for I/O callback */ +typedef enum { + SPDM_IO_MODE_NONE = 0, /* Not configured */ + SPDM_IO_MODE_TCP = 1 /* TCP socket to libspdm emulator */ +} SPDM_IO_MODE; + +/* I/O context for TCP emulator mode */ +typedef struct { + SPDM_IO_MODE mode; + int sockFd; + int isSecured; +} SPDM_IO_CTX; + +/* Global I/O context for emulator */ +static SPDM_IO_CTX g_ioCtx; +#endif /* WOLFTPM_SWTPM */ + +/******************************************************************************/ +/* --- SPDM Demo --- */ +/******************************************************************************/ + +/* Forward declarations */ +int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]); + +static void usage(void) +{ + printf("SPDM Secure Session Demo\n"); + printf("Demonstrates SPDM secure communication with Nuvoton NPCT75x\n"); + printf("\n"); + printf("Usage: spdm_demo [options]\n"); + printf("Options:\n"); + printf(" --enable Enable SPDM on TPM via NTC2_PreConfig\n"); + printf(" --disable Disable SPDM on TPM via NTC2_PreConfig\n"); + printf(" --status Query SPDM status from TPM\n"); + printf(" --get-pubkey Get TPM's SPDM-Identity public key\n"); + printf(" --connect Establish SPDM session and run test command\n"); + printf(" --lock Lock SPDM-only mode\n"); + printf(" --unlock Unlock SPDM-only mode\n"); +#ifdef WOLFTPM_SWTPM + printf(" --emu Test SPDM with libspdm emulator (TCP)\n"); + printf(" --meas Retrieve and verify device measurements (--emu)\n"); + printf(" --no-sig Skip signature verification (use with --meas)\n"); + printf(" --challenge Challenge authentication (sessionless, --emu)\n"); + printf(" --heartbeat Session heartbeat keep-alive (--emu)\n"); + printf(" --key-update Session key rotation (--emu)\n"); + printf(" --host Emulator IP address (default: 127.0.0.1)\n"); + printf(" --port Emulator port (default: 2323)\n"); +#endif + printf(" -h, --help Show this help message\n"); + printf("\n"); + printf("Nuvoton Hardware Mode (--enable, --connect, etc.):\n"); + printf(" - Requires Nuvoton NPCT75x TPM with Fw 7.2+ via SPI\n"); + printf(" - Built with: ./configure --enable-spdm --enable-nuvoton\n"); +#ifdef WOLFTPM_SWTPM + printf("\n"); + printf("Emulator Mode (--emu):\n"); + printf(" - Tests SPDM 1.2 protocol with libspdm responder emulator\n"); + printf(" - Built with: ./configure --enable-spdm --enable-swtpm\n"); + printf(" - Start emulator: ./spdm_responder_emu\n"); + printf(" - Run test: ./spdm_demo --emu\n"); +#endif +} + +/* -------------------------------------------------------------------------- */ +/* Unified I/O Callback Implementation + * -------------------------------------------------------------------------- */ + +#ifdef WOLFTPM_SWTPM +/* MCTP transport constants */ +#define SOCKET_SPDM_COMMAND_NORMAL 0x00000001 +#define MCTP_MESSAGE_TYPE_SPDM 0x05 +#define MCTP_MESSAGE_TYPE_SECURED 0x06 + +/* Initialize I/O context for TCP mode (emulator) */ +static int spdm_io_init_tcp(SPDM_IO_CTX* ioCtx, const char* host, int port) +{ + int sockFd; + struct sockaddr_in addr; + int optVal = 1; + + XMEMSET(ioCtx, 0, sizeof(*ioCtx)); + ioCtx->mode = SPDM_IO_MODE_NONE; + ioCtx->sockFd = -1; + + sockFd = socket(AF_INET, SOCK_STREAM, 0); + if (sockFd < 0) { + printf("TCP: Failed to create socket (%d)\n", errno); + return -1; + } + + /* Disable Nagle's algorithm for immediate send */ + setsockopt(sockFd, IPPROTO_TCP, TCP_NODELAY, &optVal, sizeof(optVal)); + + XMEMSET(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { + printf("TCP: Invalid address %s\n", host); + close(sockFd); + return -1; + } + + if (connect(sockFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + printf("TCP: Failed to connect to %s:%d (%d)\n", host, port, errno); + close(sockFd); + return -1; + } + + ioCtx->mode = SPDM_IO_MODE_TCP; + ioCtx->sockFd = sockFd; + return 0; +} + +/* Cleanup TCP I/O context */ +static void spdm_io_cleanup(SPDM_IO_CTX* ioCtx) +{ + if (ioCtx->sockFd >= 0) { + close(ioCtx->sockFd); + ioCtx->sockFd = -1; + } + ioCtx->mode = SPDM_IO_MODE_NONE; +} + +/* Internal: TCP send/receive for emulator */ +static int spdm_io_tcp_exchange(SPDM_IO_CTX* ioCtx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz) +{ + byte sendBuf[512]; + byte recvHdr[12]; + ssize_t sent, recvd; + word32 respSize; + word32 payloadSz; + int isSecured = 0; + + if (ioCtx->sockFd < 0) { + return -1; + } + + /* Detect secured messages: SPDM messages start with version (0x10-0x1F), + * secured messages start with SessionID (typically 0xFF...). */ + if (txSz >= 8 && (txBuf[0] < 0x10 || txBuf[0] > 0x1F)) { + isSecured = 1; + } + + /* Payload = MCTP header (1 byte) + SPDM message */ + payloadSz = 1 + txSz; + if (12 + payloadSz > sizeof(sendBuf)) { + return -1; + } + + /* Build 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 header: 0x05 for SPDM, 0x06 for secured SPDM */ + sendBuf[12] = isSecured ? MCTP_MESSAGE_TYPE_SECURED : MCTP_MESSAGE_TYPE_SPDM; + + if (txSz > 0) { + XMEMCPY(sendBuf + 13, txBuf, txSz); + } + + sent = send(ioCtx->sockFd, sendBuf, 12 + payloadSz, 0); + if (sent != (ssize_t)(12 + payloadSz)) { + return -1; + } + + recvd = recv(ioCtx->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 */ + { + byte mctpHdr; + recvd = recv(ioCtx->sockFd, &mctpHdr, 1, MSG_WAITALL); + if (recvd != 1) return -1; + } + + *rxSz = respSize - 1; + if (*rxSz > 0) { + recvd = recv(ioCtx->sockFd, rxBuf, *rxSz, MSG_WAITALL); + if (recvd != (ssize_t)*rxSz) return -1; + } + + return 0; +} +#endif /* WOLFTPM_SWTPM */ + +#ifdef WOLFTPM_SWTPM +/* I/O callback for TCP mode (emulator only). + * Nuvoton TPM mode uses the built-in library callback via + * wolfTPM2_SpdmSetNuvotonIo(). */ +static int wolfspdm_io_callback( + WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + void* userCtx) +{ + SPDM_IO_CTX* ioCtx = (SPDM_IO_CTX*)userCtx; + + if (ioCtx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) { + return -1; + } + + (void)ctx; + + if (ioCtx->mode == SPDM_IO_MODE_TCP) { + return spdm_io_tcp_exchange(ioCtx, txBuf, txSz, rxBuf, rxSz); + } + + return -1; +} +#endif /* WOLFTPM_SWTPM */ + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Demo Functions + * -------------------------------------------------------------------------- */ + +#ifdef WOLFSPDM_NUVOTON +static int demo_enable(WOLFTPM2_DEV* dev) +{ + int rc; + + printf("\n=== Enable SPDM on TPM ===\n"); + printf("Sending NTC2_PreConfig to enable SPDM (CFG_H bit 1 = 0)...\n"); + + rc = wolfTPM2_SpdmEnable(dev); + if (rc == 0) { + printf(" SUCCESS: SPDM is enabled on this TPM (was already enabled " + "or just configured).\n"); + printf(" If newly enabled, TPM must be reset to take effect.\n"); + } else if (rc == (int)TPM_RC_DISABLED) { + printf(" SPDM-only mode is active - TPM commands are blocked.\n"); + printf(" SPDM is already enabled (this is not an error).\n"); + rc = 0; /* Not an error - SPDM is already active */ + } else if (rc == TPM_RC_COMMAND_CODE) { + printf(" NOTE: NTC2_PreConfig not supported on this TPM.\n"); + printf(" SPDM may already be enabled, or use vendor tools to enable.\n"); + rc = 0; /* Not a fatal error for demo */ + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_disable(WOLFTPM2_DEV* dev) +{ + int rc; + + printf("\n=== Disable SPDM on TPM ===\n"); + printf("Sending NTC2_PreConfig to disable SPDM (CFG_H bit 1 = 1)...\n"); + + rc = wolfTPM2_SpdmDisable(dev); + if (rc == 0) { + printf(" SUCCESS: SPDM is disabled on this TPM.\n"); + printf(" TPM must be reset for changes to take effect.\n"); + } else if (rc == (int)TPM_RC_DISABLED) { + printf(" SPDM-only mode is active - cannot disable via cleartext.\n"); + printf(" Unlock SPDM-only mode first, then reset and disable.\n"); + } else if (rc == TPM_RC_COMMAND_CODE) { + printf(" NOTE: NTC2_PreConfig not supported on this TPM.\n"); + rc = 0; + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef WOLFSPDM_NUVOTON +static int demo_status(WOLFTPM2_DEV* dev) +{ + int rc; + WOLFSPDM_NUVOTON_STATUS status; + + printf("\n=== SPDM Status (GET_STS_ vendor command) ===\n"); + + XMEMSET(&status, 0, sizeof(status)); + rc = wolfTPM2_SpdmGetStatus(dev, &status); + if (rc == 0) { + int isConnected = wolfTPM2_SpdmIsConnected(dev); + byte negVer = wolfSPDM_GetNegotiatedVersion(dev->spdmCtx->spdmCtx); + + printf(" SPDM Enabled: %s\n", status.spdmEnabled ? "Yes" : "No"); + printf(" SPDM-Only Locked: %s\n", + status.spdmOnlyLocked ? "YES (TPM commands blocked)" : "No"); + printf(" Session Active: %s\n", isConnected ? "Yes" : "No"); + if (isConnected) { + printf(" Negotiated Ver: SPDM %u.%u (0x%02x)\n", + (negVer >> 4) & 0xF, negVer & 0xF, negVer); + printf(" Session ID: 0x%08x\n", + wolfTPM2_SpdmGetSessionId(dev)); + } + printf(" Nuvoton Status: v%u.%u\n", + status.specVersionMajor, status.specVersionMinor); + + if (status.spdmOnlyLocked) { + printf("\n NOTE: TPM is in SPDM-only mode. Standard TPM commands will\n"); + printf(" return TPM_RC_DISABLED until SPDM session is established\n"); + printf(" and --unlock is called.\n"); + } + } else { + printf(" FAILED to get status: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + printf(" Note: GET_STS requires SPDM to be enabled on the TPM\n"); + } + return rc; +} +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef WOLFSPDM_NUVOTON +static int demo_get_pubkey(WOLFTPM2_DEV* dev) +{ + int rc; + byte pubKey[128]; + word32 pubKeySz = sizeof(pubKey); + word32 i; + + printf("\n=== Get TPM SPDM-Identity Public Key ===\n"); + + rc = wolfTPM2_SpdmGetPubKey(dev, pubKey, &pubKeySz); + if (rc == 0) { + printf(" SUCCESS: Got TPM public key (%d bytes)\n", (int)pubKeySz); + printf(" Key (hex): "); + for (i = 0; i < pubKeySz && i < 32; i++) { + printf("%02x", pubKey[i]); + } + if (pubKeySz > 32) { + printf("..."); + } + printf("\n"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + printf(" Note: GET_PUB_KEY requires SPDM to be enabled\n"); + } + return rc; +} + +static int demo_connect(WOLFTPM2_DEV* dev) +{ + int rc; + + printf("\n=== SPDM Connect (Full Handshake) ===\n"); + + /* If auto-SPDM already established a session (SPDM-only mode), skip */ + if (wolfTPM2_SpdmIsConnected(dev)) { + printf(" SPDM session already active (auto-established)\n"); + printf(" Session ID: 0x%08x\n", wolfTPM2_SpdmGetSessionId(dev)); + return 0; + } + + printf("Establishing SPDM secure session...\n"); + printf(" Steps: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> " + "GIVE_PUB_KEY -> FINISH\n\n"); + + /* wolfTPM2_SpdmConnectNuvoton handles everything: + * - Auto-sets TIS I/O callback for SPI/I2C transport + * - Auto-generates ephemeral P-384 key pair (when NULL keys passed) + * - Sets Nuvoton mode and performs full SPDM handshake */ + rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); + if (rc == 0) { + printf(" SUCCESS: SPDM session established!\n"); + printf(" All TPM commands now encrypted with AES-256-GCM\n"); + + if (wolfTPM2_SpdmIsConnected(dev)) { + printf(" Session ID: 0x%08x\n", wolfTPM2_SpdmGetSessionId(dev)); + } + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + printf(" Note: Ensure SPDM is enabled and backend is configured\n"); + } + return rc; +} + +static int demo_lock(WOLFTPM2_DEV* dev, int lock) +{ + int rc; + + printf("\n=== SPDM-Only Mode: %s ===\n", lock ? "LOCK" : "UNLOCK"); + + rc = wolfTPM2_SpdmSetOnlyMode(dev, lock); + if (rc == 0) { + printf(" SUCCESS: SPDM-only mode %s\n", + lock ? "LOCKED" : "UNLOCKED"); + if (lock) { + printf(" WARNING: TPM will only accept commands over SPDM!\n"); + } + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +#endif /* WOLFSPDM_NUVOTON */ + +/* -------------------------------------------------------------------------- */ +/* Standard SPDM over TCP (for libspdm emulator testing) */ +/* -------------------------------------------------------------------------- */ + +#ifdef WOLFTPM_SWTPM + +#ifndef NO_WOLFSPDM_MEAS +/* Retrieve and display device measurements from an established SPDM session. + * Calls wolfSPDM measurement APIs directly. */ +static int demo_measurements(WOLFSPDM_CTX* ctx, int requestSignature) +{ + int rc, count, i; + + printf("\n=== SPDM GET_MEASUREMENTS ===\n"); + + rc = wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, + requestSignature); + if (rc == WOLFSPDM_SUCCESS) { + printf("Measurements retrieved and signature VERIFIED\n"); + } + else if (rc == WOLFSPDM_E_MEAS_NOT_VERIFIED) { + printf("Measurements retrieved (not signature-verified)\n"); + } + else if (rc == WOLFSPDM_E_MEAS_SIG_FAIL) { + printf("WARNING: Measurement signature INVALID\n"); + return rc; + } + else { + printf("ERROR: %s (%d)\n", wolfSPDM_GetErrorString(rc), rc); + return rc; + } + + count = wolfSPDM_GetMeasurementCount(ctx); + printf("Measurement blocks: %d\n", count); + + for (i = 0; i < count; i++) { + byte idx = 0, mtype = 0; + byte val[WOLFSPDM_MAX_MEAS_VALUE_SIZE]; + word32 valSz = sizeof(val); + int j; + + rc = wolfSPDM_GetMeasurementBlock(ctx, i, &idx, &mtype, val, &valSz); + if (rc != WOLFSPDM_SUCCESS) + continue; + + printf(" [%u] type=0x%02x size=%u: ", idx, mtype, valSz); + for (j = 0; j < (int)valSz && j < 48; j++) + printf("%02x", val[j]); + if (valSz > 48) + printf("..."); + printf("\n"); + } + + return 0; +} +#endif /* !NO_WOLFSPDM_MEAS */ + +#ifndef NO_WOLFSPDM_CHALLENGE + +/* Execute an SPDM step with error reporting */ +#define DEMO_STEP(name, call) do { \ + printf(" " name "...\n"); \ + rc = (call); \ + if (rc != WOLFSPDM_SUCCESS) { \ + printf(" ERROR: " name " failed: %s (%d)\n", \ + wolfSPDM_GetErrorString(rc), rc); \ + return rc; \ + } \ +} while(0) + +/* Perform CHALLENGE authentication (sessionless attestation). + * Uses individual handshake steps instead of wolfSPDM_Connect() to avoid + * establishing a full session (KEY_EXCHANGE/FINISH). */ +static int demo_challenge(WOLFSPDM_CTX* ctx) +{ + int rc; + + printf("\n=== SPDM CHALLENGE (Sessionless Attestation) ===\n"); + + DEMO_STEP("GET_VERSION", wolfSPDM_GetVersion(ctx)); + DEMO_STEP("GET_CAPABILITIES", wolfSPDM_GetCapabilities(ctx)); + DEMO_STEP("NEGOTIATE_ALGORITHMS", wolfSPDM_NegotiateAlgorithms(ctx)); + DEMO_STEP("GET_DIGESTS", wolfSPDM_GetDigests(ctx)); + DEMO_STEP("GET_CERTIFICATE", wolfSPDM_GetCertificate(ctx, 0)); + + /* Step 6: CHALLENGE */ + printf(" CHALLENGE (slot=0, no measurement summary)...\n"); + rc = wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_NONE); + if (rc == WOLFSPDM_SUCCESS) { + printf("\n CHALLENGE authentication PASSED\n"); + } + else { + printf("\n CHALLENGE authentication FAILED: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + + return rc; +} +#endif /* !NO_WOLFSPDM_CHALLENGE */ + +/* Send HEARTBEAT over an established SPDM session */ +static int demo_heartbeat(WOLFSPDM_CTX* ctx) +{ + int rc; + + printf("\n=== SPDM HEARTBEAT ===\n"); + + rc = wolfSPDM_Heartbeat(ctx); + if (rc == WOLFSPDM_SUCCESS) { + printf(" HEARTBEAT_ACK received — session alive\n"); + } + else { + printf(" HEARTBEAT failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + + return rc; +} + +/* Perform KEY_UPDATE to rotate session encryption keys */ +static int demo_key_update(WOLFSPDM_CTX* ctx) +{ + int rc; + + printf("\n=== SPDM KEY_UPDATE ===\n"); + + rc = wolfSPDM_KeyUpdate(ctx, 1); /* updateAll = 1: rotate both keys */ + if (rc == WOLFSPDM_SUCCESS) { + printf(" KEY_UPDATE completed — new keys active\n"); + } + else { + printf(" KEY_UPDATE failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + + return rc; +} + +/* SPDM emulator test using wolfSPDM library + * Connects to libspdm responder emulator via TCP and performs full SPDM 1.2 handshake + * Uses the unified I/O callback (same as Nuvoton hardware mode) */ +static int demo_emulator(const char* host, int port, int doMeas, + int requestSignature, int doChallenge, + int doHeartbeat, int doKeyUpdate) +{ + WOLFSPDM_CTX* ctx; + int rc; +#ifndef WOLFSPDM_DYNAMIC_MEMORY + byte spdmBuf[WOLFSPDM_CTX_STATIC_SIZE]; +#endif + + printf("\n=== wolfSPDM spdm-emu Test ===\n"); + printf("Connecting to %s:%d...\n", host, port); + + /* Initialize unified I/O context for TCP mode (emulator) */ + rc = spdm_io_init_tcp(&g_ioCtx, host, port); + if (rc < 0) { + printf("Failed to connect to emulator\n"); + printf("Make sure spdm_responder_emu is running:\n"); + printf(" ./spdm_responder_emu --trans TCP\n"); + return rc; + } + + /* Create wolfSPDM context */ +#ifdef WOLFSPDM_DYNAMIC_MEMORY + ctx = wolfSPDM_New(); + if (ctx == NULL) { + printf("ERROR: wolfSPDM_New() failed\n"); + spdm_io_cleanup(&g_ioCtx); + return -1; + } +#else + ctx = (WOLFSPDM_CTX*)spdmBuf; + rc = wolfSPDM_InitStatic(ctx, (int)sizeof(spdmBuf)); + if (rc != WOLFSPDM_SUCCESS) { + printf("ERROR: wolfSPDM_InitStatic() failed: %s\n", + wolfSPDM_GetErrorString(rc)); + spdm_io_cleanup(&g_ioCtx); + return rc; + } +#endif + + /* Set unified I/O callback (handles both TCP emulator and TPM TIS modes) */ + wolfSPDM_SetIO(ctx, wolfspdm_io_callback, &g_ioCtx); +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(ctx, 1); +#endif + +#ifndef NO_WOLFSPDM_CHALLENGE + /* Challenge mode: sessionless attestation (no KEY_EXCHANGE/FINISH) */ + if (doChallenge) { + rc = demo_challenge(ctx); + + /* Cleanup */ + wolfSPDM_Free(ctx); + spdm_io_cleanup(&g_ioCtx); + return (rc == WOLFSPDM_SUCCESS) ? 0 : rc; + } +#else + (void)doChallenge; +#endif + + /* Full SPDM handshake - this single call replaces ~1000 lines of code! + * Performs: GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> + * GET_DIGESTS -> GET_CERTIFICATE -> KEY_EXCHANGE -> FINISH */ + printf("\nEstablishing SPDM session...\n"); + rc = wolfSPDM_Connect(ctx); + + if (rc == WOLFSPDM_SUCCESS) { + printf("\n=============================================\n"); + printf(" SUCCESS: SPDM Session Established!\n"); + printf(" Session ID: 0x%08x\n", wolfSPDM_GetSessionId(ctx)); + printf(" SPDM Version: 0x%02x\n", wolfSPDM_GetNegotiatedVersion(ctx)); + printf("=============================================\n"); + + /* Heartbeat: send keep-alive over encrypted channel */ + if (doHeartbeat) { + rc = demo_heartbeat(ctx); + if (rc != WOLFSPDM_SUCCESS) goto cleanup; + } + + /* Key update: rotate session keys */ + if (doKeyUpdate) { + rc = demo_key_update(ctx); + if (rc != WOLFSPDM_SUCCESS) goto cleanup; + } + +#ifndef NO_WOLFSPDM_MEAS + /* Retrieve measurements if requested */ + if (doMeas) { + rc = demo_measurements(ctx, requestSignature); + } +#else + (void)doMeas; + (void)requestSignature; +#endif + } else { + printf("\nERROR: wolfSPDM_Connect() failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + +cleanup: + /* Cleanup */ + wolfSPDM_Free(ctx); + spdm_io_cleanup(&g_ioCtx); + + return (rc == WOLFSPDM_SUCCESS) ? 0 : rc; +} + + +#endif /* WOLFTPM_SWTPM */ + +int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) +{ + int rc; + WOLFTPM2_DEV dev; + int i; +#ifdef WOLFTPM_SWTPM + const char* emuHost = SPDM_EMU_DEFAULT_HOST; + int emuPort = SPDM_EMU_DEFAULT_PORT; + int useEmulator = 0; + int doMeas = 0; + int requestSignature = 1; + int doChallenge = 0; + int doHeartbeat = 0; + int doKeyUpdate = 0; +#endif + + if (argc <= 1) { + usage(); + return 0; + } + + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "-h") == 0 || + XSTRCMP(argv[i], "--help") == 0) { + usage(); + return 0; + } +#ifdef WOLFTPM_SWTPM + else if (XSTRCMP(argv[i], "--emu") == 0) { + useEmulator = 1; + } + else if (XSTRCMP(argv[i], "--host") == 0 && i + 1 < argc) { + emuHost = argv[++i]; + } + else if (XSTRCMP(argv[i], "--port") == 0 && i + 1 < argc) { + emuPort = atoi(argv[++i]); + } + else if (XSTRCMP(argv[i], "--meas") == 0) { + doMeas = 1; + useEmulator = 1; + } + else if (XSTRCMP(argv[i], "--no-sig") == 0) { + requestSignature = 0; + } + else if (XSTRCMP(argv[i], "--challenge") == 0) { + doChallenge = 1; + useEmulator = 1; + } + else if (XSTRCMP(argv[i], "--heartbeat") == 0) { + doHeartbeat = 1; + useEmulator = 1; + } + else if (XSTRCMP(argv[i], "--key-update") == 0) { + doKeyUpdate = 1; + useEmulator = 1; + } +#endif + } + +#ifdef WOLFTPM_SWTPM + /* Handle --emu mode (TCP to emulator, no TPM needed) */ + if (useEmulator) { + printf("Entering emulator mode...\n"); + fflush(stdout); + return demo_emulator(emuHost, emuPort, doMeas, requestSignature, + doChallenge, doHeartbeat, doKeyUpdate); + } +#endif + + /* Init the TPM2 device. + * When SPDM is enabled on Nuvoton TPMs, TPM2_Startup may return + * TPM_RC_DISABLED because the TPM expects SPDM-only communication. + * wolfTPM2_Init tolerates this when built with WOLFTPM_SPDM - + * SPDM commands work over raw SPI regardless of TPM startup state. */ + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + if (rc != 0) { + printf("wolfTPM2_Init failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; + } + + /* Initialize SPDM support */ + rc = wolfTPM2_SpdmInit(&dev); + if (rc != 0) { + printf("wolfTPM2_SpdmInit failed: 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + printf("Ensure wolfTPM is built with --enable-spdm\n"); + wolfTPM2_Cleanup(&dev); + return rc; + } + +#ifdef WOLFSPDM_NUVOTON + /* Set Nuvoton mode + TIS I/O for all Nuvoton commands */ + wolfTPM2_SpdmSetNuvotonMode(&dev); + wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); +#endif + + /* Process command-line options */ + for (i = 1; i < argc; i++) { +#ifdef WOLFSPDM_NUVOTON + if (XSTRCMP(argv[i], "--enable") == 0) { + rc = demo_enable(&dev); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--disable") == 0) { + rc = demo_disable(&dev); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--status") == 0) { + rc = demo_status(&dev); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--get-pubkey") == 0) { + rc = demo_get_pubkey(&dev); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--connect") == 0) { + rc = demo_connect(&dev); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--lock") == 0) { + rc = demo_lock(&dev, 1); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--unlock") == 0) { + rc = demo_lock(&dev, 0); + if (rc != 0) break; + } + else +#endif /* WOLFSPDM_NUVOTON */ + { + printf("Unknown option: %s\n", argv[i]); + usage(); + rc = BAD_FUNC_ARG; + break; + } + } + + /* Cleanup SPDM */ + wolfTPM2_SpdmCleanup(&dev); + + wolfTPM2_Cleanup(&dev); + return rc; +} + +/******************************************************************************/ +/* --- END SPDM Demo --- */ +/******************************************************************************/ + +#ifndef NO_MAIN_DRIVER +int main(int argc, char *argv[]) +{ + int rc = -1; + +#ifndef WOLFTPM2_NO_WRAPPER + rc = TPM2_SPDM_Demo(NULL, argc, argv); +#else + printf("Wrapper code not compiled in\n"); + (void)argc; + (void)argv; +#endif + + return (rc == 0) ? 0 : 1; +} +#endif /* !NO_MAIN_DRIVER */ + +#endif /* WOLFTPM_SPDM */ +#endif /* !WOLFTPM2_NO_WRAPPER */ \ No newline at end of file diff --git a/examples/spdm/spdm_test.sh b/examples/spdm/spdm_test.sh new file mode 100755 index 00000000..10c3a631 --- /dev/null +++ b/examples/spdm/spdm_test.sh @@ -0,0 +1,332 @@ +#!/bin/bash +# +# spdm_test.sh - SPDM test script +# +# Supports two modes: +# --emu Test SPDM with libspdm emulator (session + measurements) +# --nuvoton Test Nuvoton SPDM hardware (lock, unit test over SPDM, unlock) +# +# Usage: +# ./spdm_test.sh --emu # Emulator tests +# ./spdm_test.sh --nuvoton # Nuvoton hardware tests +# ./spdm_test.sh --emu --nuvoton # Both +# ./spdm_test.sh # Default: --nuvoton +# + +SPDM_DEMO="./examples/spdm/spdm_demo" +CAPS_DEMO="./examples/wrap/caps" +UNIT_TEST="./tests/unit.test" +GPIO_CHIP="gpiochip0" +GPIO_PIN="4" +PASS=0 +FAIL=0 +TOTAL=0 +DO_EMU=0 +DO_NUVOTON=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 [--emu] [--nuvoton] [path-to-spdm_demo]" + echo "" + echo "Options:" + echo " --emu Test SPDM with libspdm emulator (session + measurements)" + echo " --nuvoton Test Nuvoton SPDM hardware (lock, unit test over SPDM, unlock)" + echo " -h, --help Show this help" + echo "" + echo "If neither --emu nor --nuvoton is specified, defaults to --nuvoton." + echo "" + echo "Emulator mode expects spdm_responder_emu to be found via:" + echo " 1. SPDM_EMU_PATH environment variable" + echo " 2. ../spdm-emu/build/bin/ (cloned next to wolfTPM)" + echo " 3. spdm_responder_emu in PATH" +} + +# Parse arguments +for arg in "$@"; do + case "$arg" in + --emu) + DO_EMU=1 + ;; + --nuvoton) + DO_NUVOTON=1 + ;; + -h|--help) + usage + exit 0 + ;; + *) + # Treat as path to spdm_demo + SPDM_DEMO="$arg" + ;; + esac +done + +# Default to --nuvoton if nothing specified +if [ $DO_EMU -eq 0 ] && [ $DO_NUVOTON -eq 0 ]; then + DO_NUVOTON=1 +fi + +# Find spdm_responder_emu for --emu mode +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 (cloned next to wolfTPM) + 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) +start_emu() { + echo " Starting spdm_responder_emu..." + + # Kill any stale emulator processes + if pgrep -x spdm_responder_emu >/dev/null 2>&1; then + echo " Killing stale emulator process..." + pkill -9 -x spdm_responder_emu 2>/dev/null + sleep 2 + fi + + # Check port availability + if ss -tlnp 2>/dev/null | grep -q ":2323 "; then + echo -e " ${RED}ERROR: Port 2323 already in use${NC}" + ss -tlnp 2>/dev/null | grep ":2323 " + return 1 + fi + + # Verify cert/key files exist in EMU_DIR + if [ ! -f "$EMU_DIR/EcP384/end_responder.cert" ] && \ + [ ! -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 1.2 \ + --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 + +gpio_reset() { + echo " GPIO reset..." + gpioset "$GPIO_CHIP" "$GPIO_PIN=0" 2>/dev/null + sleep 0.1 + gpioset "$GPIO_CHIP" "$GPIO_PIN=1" 2>/dev/null + sleep 2 +} + +# Run a test with optional setup/teardown +# Usage: run_test +# mode: "nuvoton" (GPIO reset before) or "emu" (start/stop emulator around) +run_test() { + local mode="$1" + local name="$2" + shift 2 + + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + + # Pre-test setup + if [ "$mode" = "nuvoton" ]; then + gpio_reset + elif [ "$mode" = "emu" ]; then + if ! start_emu; then + echo -e " ${RED}FAIL (emulator start)${NC}" + FAIL=$((FAIL + 1)) + echo "" + return 1 + fi + fi + + if "$@"; then + echo -e " ${GREEN}PASS${NC}" + PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}" + FAIL=$((FAIL + 1)) + fi + + # Post-test teardown + if [ "$mode" = "emu" ]; then + stop_emu + sleep 1 # Let port release + fi + 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 +# ========================================================================== +if [ $DO_EMU -eq 1 ]; then + 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 wolfTPM:" + 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 1: Session establishment + run_test emu "Session establishment (--emu)" \ + "$SPDM_DEMO" --emu + + # Test 2: Session + signed measurements + run_test emu "Signed measurements (--meas)" \ + "$SPDM_DEMO" --meas + + # Test 3: Session + unsigned measurements + run_test emu "Unsigned measurements (--meas --no-sig)" \ + "$SPDM_DEMO" --meas --no-sig + + # Test 4: Challenge authentication (sessionless) + run_test emu "Challenge authentication (--challenge)" \ + "$SPDM_DEMO" --challenge + + # Test 5: Session + heartbeat + run_test emu "Heartbeat (--emu --heartbeat)" \ + "$SPDM_DEMO" --emu --heartbeat + + # Test 6: Session + key update + run_test emu "Key update (--emu --key-update)" \ + "$SPDM_DEMO" --emu --key-update + + echo "" +fi + +# ========================================================================== +# Nuvoton Hardware Tests +# ========================================================================== +if [ $DO_NUVOTON -eq 1 ]; then + echo "=== Nuvoton SPDM Hardware Tests ===" + echo "Demo: $SPDM_DEMO" + echo "Caps: $CAPS_DEMO" + echo "Unit test: $UNIT_TEST" + echo "" + + # Step 1: SPDM status query (vendor command over TIS) + run_test nuvoton "SPDM status query" "$SPDM_DEMO" --status + + # Step 2: SPDM session establishment (version + keygen + handshake) + run_test nuvoton "SPDM session connect" "$SPDM_DEMO" --connect + + # Step 3: Lock SPDM-only mode (connect + lock in one session) + run_test nuvoton "Lock SPDM-only mode" "$SPDM_DEMO" --connect --lock + + # Step 4: Unit test over SPDM (auto-detects SPDM-only, all commands encrypted) + if [ -x "$UNIT_TEST" ]; then + run_test nuvoton "Unit test over SPDM" "$UNIT_TEST" + else + echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" + fi + + # Step 5: Unlock SPDM-only mode + run_test nuvoton "Unlock SPDM-only mode" "$SPDM_DEMO" --connect --unlock + + # Step 6: Verify cleartext TPM works (proves unlock succeeded) + if [ -x "$CAPS_DEMO" ]; then + run_test nuvoton "Cleartext caps (no SPDM)" "$CAPS_DEMO" + else + echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" + fi + + echo "" +fi + +# ========================================================================== +# 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/spdm/README.md b/spdm/README.md new file mode 100644 index 00000000..660fbae3 --- /dev/null +++ b/spdm/README.md @@ -0,0 +1,324 @@ +# wolfTPM SPDM + +wolfTPM includes a built-in SPDM 1.2+ requester stack using wolfSSL/wolfCrypt. +This provides encrypted bus communication between the host and TPM, ensuring +all commands and responses are protected with AES-256-GCM. + +## How It Works + +SPDM (Security Protocol and Data Model) establishes an authenticated encrypted +channel over the existing SPI/I2C bus. Once active, every TPM command is +automatically encrypted — no application code changes needed. + +### Protocol Flow + +``` +Host TPM (Nuvoton NPCT75x) + | | + |--- GET_VERSION ------------------>| (negotiate SPDM 1.3) + |<-- VERSION -----------------------| + | | + |--- GET_PUB_KEY ------------------>| (get TPM's P-384 identity key) + |<-- PUB_KEY_RSP -------------------| + | | + |--- KEY_EXCHANGE ----------------->| (ECDHE P-384 key agreement) + |<-- KEY_EXCHANGE_RSP --------------| (+ HMAC proof of shared secret) + | | + | --- Handshake keys derived --- | + | | + |=== GIVE_PUB_KEY ================>| (encrypted: host's P-384 key) + |<== GIVE_PUB_KEY_RSP =============| + | | + |=== FINISH ========================>| (encrypted: signature + HMAC) + |<== FINISH_RSP ====================| + | | + | --- App data keys derived --- | + | | + |=== TPM2_CMD (AES-256-GCM) ======>| (every command encrypted) + |<== TPM2_RSP (AES-256-GCM) =======| +``` + +The handshake uses ECDH P-384 for key agreement and HMAC-SHA384 for +authentication. After the handshake, all TPM commands are wrapped in SPDM +`VENDOR_DEFINED_REQUEST("TPM2_CMD")` messages and encrypted with AES-256-GCM. +A sequence number increments with each message to prevent replay attacks. + +### Command Flow + +The typical usage flow for SPDM-only mode: + +``` +1. Enable SPDM (one-time, persists across resets) +2. Connect (ECDH handshake, derives session keys) +3. Lock SPDM-only (TPM rejects all cleartext commands) +4. GPIO reset (TPM enters SPDM-only enforcement) +5. Run any commands (each auto-establishes SPDM, all AES-256-GCM encrypted) +6. Unlock (connect + unlock in one session) +7. GPIO reset (TPM back to normal cleartext mode) +``` + +Step 5 is fully automatic. When wolfTPM detects SPDM-only mode (TPM2_Startup +returns `TPM_RC_DISABLED`), it transparently establishes an SPDM session. +Existing applications like `caps`, `wrap_test`, and `unit.test` work without +modification — all commands are encrypted over the bus. + +## Building + +### Prerequisites + +wolfSSL with the crypto algorithms required for SPDM Algorithm Set B: + +```bash +cd wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp +make +sudo make install +sudo ldconfig +``` + +The `--enable-sp` flag enables Single Precision math with optimized ECC P-384 +support, which is required for SPDM Algorithm Set B on platforms like ARM64. +For a broader feature set, `--enable-all` can be used instead. + +### wolfTPM with Nuvoton SPDM + +```bash +cd wolfTPM +./autogen.sh +./configure --enable-spdm --enable-nuvoton +make +``` + +### Configure Options + +| Option | Description | +|---|---| +| `--enable-spdm` | Enable SPDM support (required) | +| `--enable-nuvoton` | Enable Nuvoton TPM hardware support | +| `--enable-debug` | Debug output with verbose SPDM tracing | +| `--enable-spdm-dynamic-mem` | Heap-allocated SPDM context (default: static ~32 KB) | + +## Nuvoton NPCT75x Usage + +### One-Time Setup + +```bash +# Enable SPDM on the TPM (persists across resets) +./examples/spdm/spdm_demo --enable + +# GPIO reset to apply +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# Verify SPDM is enabled +./examples/spdm/spdm_demo --status +``` + +### Lock SPDM-Only Mode + +```bash +# Establish session and lock +./examples/spdm/spdm_demo --connect --lock + +# GPIO reset — TPM now requires SPDM for all commands +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# All commands are now automatically encrypted: +./examples/wrap/caps # auto-SPDM session, all AES-256-GCM +./tests/unit.test # full test suite over encrypted bus +``` + +### Unlock SPDM-Only Mode + +```bash +# GPIO reset + unlock (auto-connects since TPM is in SPDM-only mode) +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect --unlock + +# GPIO reset — TPM back to normal +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# Verify cleartext works +./examples/wrap/caps +``` + +### Running the Test Suite + +```bash +# Run all Nuvoton SPDM tests (status, connect, lock, unit test, unlock, caps) +./examples/spdm/spdm_test.sh --nuvoton +``` + +### Demo Options + +| Option | Description | +|--------|-------------| +| `--enable` | Enable SPDM on Nuvoton TPM (one-time) | +| `--disable` | Disable SPDM on Nuvoton TPM | +| `--status` | Query SPDM status from TPM | +| `--get-pubkey` | Get TPM's SPDM-Identity public key | +| `--connect` | Establish SPDM session | +| `--lock` | Lock SPDM-only mode (use with `--connect`) | +| `--unlock` | Unlock SPDM-only mode (use with `--connect`) | + +## Emulator Testing (spdm-emu) + +For development without Nuvoton hardware, use the DMTF spdm-emu emulator: + +```bash +# Build emulator +git clone 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 + +# Build wolfSSL +cd wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-all +make && sudo make install && sudo ldconfig + +# Build wolfTPM with SPDM + SWTPM +cd wolfTPM +./autogen.sh +./configure --enable-spdm --enable-swtpm +make + +# Run emulator tests (starts/stops emulator automatically) +./examples/spdm/spdm_test.sh --emu +``` + +The test script automatically finds `spdm_responder_emu` in `../spdm-emu/build/bin/`, +starts it for each test, and runs session establishment, signed measurements, +unsigned measurements, challenge authentication, heartbeat, and key update. + +To run individual commands manually: + +```bash +# Terminal 1: Start the emulator +cd spdm-emu/build/bin +./spdm_responder_emu --ver 1.2 --hash SHA_384 --asym ECDSA_P384 \ + --dhe SECP_384_R1 --aead AES_256_GCM + +# Terminal 2: Run wolfTPM SPDM demo +cd wolfTPM +./examples/spdm/spdm_demo --emu +``` + +### Emulator Demo Options + +| Option | Description | +|--------|-------------| +| `--emu` | Session establishment with emulator | +| `--meas` | Retrieve signed device measurements | +| `--meas --no-sig` | Measurements without signature verification | +| `--challenge` | Sessionless challenge authentication | +| `--emu --heartbeat` | Session keep-alive | +| `--emu --key-update` | Session key rotation | +| `--host ` | Emulator host (default: 127.0.0.1) | +| `--port ` | Emulator port (default: 2323) | + +## How Auto-SPDM Works + +When the TPM is in SPDM-only mode, `wolfTPM2_Init()` handles everything: + +1. `TPM2_Startup` is sent in cleartext — TPM returns `TPM_RC_DISABLED` +2. wolfTPM detects this and sets `spdmOnlyDetected` +3. An SPDM session is automatically established (P-384 keygen + handshake) +4. `TPM2_Startup` is retried over the encrypted channel — succeeds +5. All subsequent commands go through the SPDM encrypted channel + +Both `TPM2_SendCommand` (non-auth commands) and `TPM2_SendCommandAuth` +(auth-session commands like PCR operations, key creation, signing) are +intercepted and routed through SPDM when a session is active. + +## Memory Modes + +**Static (default):** Zero heap allocation. SPDM context uses ~32 KB of +static memory, ideal for embedded environments. + +**Dynamic (`--enable-spdm-dynamic-mem`):** Context is heap-allocated. +Useful on platforms with small stacks. + +## wolfSPDM API + +| Function | Description | +|---|---| +| `wolfSPDM_InitStatic()` | Initialize context in caller-provided buffer (static mode) | +| `wolfSPDM_New()` | Allocate and initialize context on heap (dynamic mode) | +| `wolfSPDM_Init()` | Initialize a pre-allocated context | +| `wolfSPDM_Free()` | Free context (releases resources; frees heap only if dynamic) | +| `wolfSPDM_GetCtxSize()` | Return `sizeof(WOLFSPDM_CTX)` at runtime | +| `wolfSPDM_SetIO()` | Set transport I/O callback | +| `wolfSPDM_SetDebug()` | Enable/disable debug output | +| `wolfSPDM_Connect()` | Full SPDM handshake | +| `wolfSPDM_IsConnected()` | Check session status | +| `wolfSPDM_Disconnect()` | End session | +| `wolfSPDM_EncryptMessage()` | Encrypt outgoing message | +| `wolfSPDM_DecryptMessage()` | Decrypt incoming message | +| `wolfSPDM_SecuredExchange()` | Encrypt/send/receive/decrypt in one call | +| `wolfSPDM_SetTrustedCAs()` | Load trusted root CA certificates for chain validation | +| `wolfSPDM_GetMeasurements()` | Retrieve device measurements with optional signature verification | +| `wolfSPDM_GetMeasurementCount()` | Get number of measurement blocks retrieved | +| `wolfSPDM_GetMeasurementBlock()` | Access individual measurement block data | +| `wolfSPDM_Challenge()` | Sessionless device attestation via CHALLENGE/CHALLENGE_AUTH | +| `wolfSPDM_Heartbeat()` | Session keep-alive (HEARTBEAT/HEARTBEAT_ACK) | +| `wolfSPDM_KeyUpdate()` | Rotate session encryption keys (KEY_UPDATE/KEY_UPDATE_ACK) | +| `wolfSPDM_SendData()` | Send application data over established session | +| `wolfSPDM_ReceiveData()` | Receive application data over established session | + +## Troubleshooting + +### TPM returns TPM_RC_DISABLED (0x120) + +The TPM is in SPDM-only mode. Either establish an SPDM session first, +or unlock SPDM-only mode: + +```bash +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect --unlock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +### SPDM handshake fails after interrupted session + +GPIO reset clears stale SPDM state on the TPM: + +```bash +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +If GPIO is not wired, a full power cycle is required. + +### Emulator: bind error / connection refused + +```bash +pkill -9 spdm_responder_emu +sleep 5 +ss -tlnp | grep 2323 # verify port is free +``` + +### SPDM Error Codes + +| Code | Name | Description | +|------|------|-------------| +| 0x01 | InvalidRequest | Message format incorrect | +| 0x04 | UnexpectedRequest | Message out of sequence | +| 0x05 | DecryptError | Decryption or MAC verification failed | +| 0x06 | UnsupportedRequest | Request not supported or format rejected | +| 0x41 | VersionMismatch | SPDM version mismatch | + +## SPDM Support Beyond wolfTPM + +The wolfSPDM requester stack was developed as a standalone implementation and +is integrated into wolfTPM for convenience. It is designed to be portable and +can support SPDM on other hardware platforms, additional algorithm sets beyond +Algorithm Set B, and responder-side implementations. + +For inquiries about full standalone SPDM support, custom hardware integration, +additional algorithm sets, or commercial licensing, please contact +support@wolfssl.com. + +## License + +GPLv3 — see LICENSE file. Copyright (C) 2006-2025 wolfSSL Inc. diff --git a/spdm/include.am b/spdm/include.am new file mode 100644 index 00000000..d37a58ea --- /dev/null +++ b/spdm/include.am @@ -0,0 +1,35 @@ +# vim:ft=automake +# All paths should be given relative to the root + +if BUILD_SPDM + +src_libwolftpm_la_SOURCES += \ + spdm/src/spdm_context.c \ + spdm/src/spdm_crypto.c \ + spdm/src/spdm_kdf.c \ + spdm/src/spdm_msg.c \ + spdm/src/spdm_secured.c \ + spdm/src/spdm_session.c \ + spdm/src/spdm_transcript.c + +if BUILD_NUVOTON +src_libwolftpm_la_SOURCES += spdm/src/spdm_nuvoton.c +endif + +src_libwolftpm_la_CFLAGS += -I$(srcdir)/spdm -I$(srcdir)/spdm/src + +wolfspdmincdir = $(includedir)/wolfspdm +wolfspdminc_HEADERS = \ + spdm/wolfspdm/spdm.h \ + spdm/wolfspdm/spdm_types.h \ + spdm/wolfspdm/spdm_error.h \ + spdm/wolfspdm/spdm_nuvoton.h + +check_PROGRAMS += spdm/test/unit_test +spdm_test_unit_test_SOURCES = spdm/test/unit_test.c +spdm_test_unit_test_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +spdm_test_unit_test_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/spdm -I$(srcdir)/spdm/src + +EXTRA_DIST += spdm/src/spdm_internal.h + +endif BUILD_SPDM diff --git a/spdm/src/spdm_context.c b/spdm/src/spdm_context.c new file mode 100644 index 00000000..22c1dc9a --- /dev/null +++ b/spdm/src/spdm_context.c @@ -0,0 +1,653 @@ +/* spdm_context.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" +#include +#include +#include +#include + +/* --- Context Management --- */ + +int wolfSPDM_Init(WOLFSPDM_CTX* ctx) +{ + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Clean slate — do NOT read any fields before this (could be garbage) */ + XMEMSET(ctx, 0, sizeof(WOLFSPDM_CTX)); + ctx->state = WOLFSPDM_STATE_INIT; + + /* Initialize RNG */ + rc = wc_InitRng(&ctx->rng); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + ctx->flags.rngInitialized = 1; + + /* Set default requester capabilities */ + ctx->reqCaps = WOLFSPDM_DEFAULT_REQ_CAPS; + + /* 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 */ + + return WOLFSPDM_SUCCESS; +} + +#ifdef WOLFSPDM_DYNAMIC_MEMORY +WOLFSPDM_CTX* wolfSPDM_New(void) +{ + WOLFSPDM_CTX* ctx; + + ctx = (WOLFSPDM_CTX*)XMALLOC(sizeof(WOLFSPDM_CTX), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (ctx == NULL) { + return NULL; + } + + if (wolfSPDM_Init(ctx) != WOLFSPDM_SUCCESS) { + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return NULL; + } + ctx->flags.isDynamic = 1; /* Tag AFTER Init so it isn't wiped */ + + return ctx; +} +#endif /* WOLFSPDM_DYNAMIC_MEMORY */ + +void wolfSPDM_Free(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return; + } + +#ifdef WOLFSPDM_DYNAMIC_MEMORY + { + int wasDynamic = ctx->flags.isDynamic; +#endif + + /* Free RNG */ + if (ctx->flags.rngInitialized) { + wc_FreeRng(&ctx->rng); + } + + /* Free ephemeral key */ + if (ctx->flags.ephemeralKeyInit) { + wc_ecc_free(&ctx->ephemeralKey); + } + + /* Free responder public key (used for measurement/challenge verification) */ + if (ctx->flags.hasResponderPubKey) { + wc_ecc_free(&ctx->responderPubKey); + } + +#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)); + +#ifdef WOLFSPDM_DYNAMIC_MEMORY + if (wasDynamic) { + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + } +#endif +} + +int wolfSPDM_GetCtxSize(void) +{ + return (int)sizeof(WOLFSPDM_CTX); +} + +int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (size < (int)sizeof(WOLFSPDM_CTX)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + return wolfSPDM_Init(ctx); +} + +/* --- Configuration --- */ + +int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx) +{ + if (ctx == NULL || ioCb == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + ctx->ioCb = ioCb; + ctx->ioUserCtx = userCtx; + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz) +{ + if (ctx == NULL || pubKey == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (pubKeySz != WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMCPY(ctx->rspPubKey, pubKey, pubKeySz); + ctx->rspPubKeyLen = pubKeySz; + ctx->flags.hasRspPubKey = 1; + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, + const byte* privKey, word32 privKeySz, + const byte* pubKey, word32 pubKeySz) +{ + if (ctx == NULL || privKey == NULL || pubKey == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (privKeySz != WOLFSPDM_ECC_KEY_SIZE || + pubKeySz != WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMCPY(ctx->reqPrivKey, privKey, privKeySz); + ctx->reqPrivKeyLen = privKeySz; + XMEMCPY(ctx->reqPubKey, pubKey, pubKeySz); + ctx->reqPubKeyLen = pubKeySz; + ctx->flags.hasReqKeyPair = 1; + + return WOLFSPDM_SUCCESS; +} + +#ifdef WOLFSPDM_NUVOTON +int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz) +{ + if (ctx == NULL || tpmtPub == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (tpmtPubSz > sizeof(ctx->reqPubKeyTPMT)) { + return WOLFSPDM_E_INVALID_ARG; + } + XMEMCPY(ctx->reqPubKeyTPMT, tpmtPub, tpmtPubSz); + ctx->reqPubKeyTPMTLen = tpmtPubSz; + return WOLFSPDM_SUCCESS; +} +#endif /* WOLFSPDM_NUVOTON */ + +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 > WOLFSPDM_MAX_CERT_CHAIN) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(ctx->trustedCAs, derCerts, derCertsSz); + ctx->trustedCAsSz = derCertsSz; + ctx->flags.hasTrustedCAs = 1; + + return WOLFSPDM_SUCCESS; +} + +void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable) +{ + if (ctx != NULL) { + ctx->flags.debug = enable; + } +} + +int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (mode == WOLFSPDM_MODE_NUVOTON) { +#ifdef WOLFSPDM_NUVOTON + ctx->mode = WOLFSPDM_MODE_NUVOTON; + /* Initialize Nuvoton-specific fields */ + ctx->connectionHandle = WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT; + ctx->fipsIndicator = WOLFSPDM_NUVOTON_FIPS_DEFAULT; + return WOLFSPDM_SUCCESS; +#else + return WOLFSPDM_E_INVALID_ARG; /* Nuvoton support not compiled in */ +#endif + } + + ctx->mode = WOLFSPDM_MODE_STANDARD; + return WOLFSPDM_SUCCESS; +} + +WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return WOLFSPDM_MODE_STANDARD; + } + return ctx->mode; +} + +/* --- Session Status --- */ + +int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return (ctx->state == WOLFSPDM_STATE_CONNECTED) ? 1 : 0; +} + +word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->state != WOLFSPDM_STATE_CONNECTED) { + return 0; + } + return ctx->sessionId; +} + +byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->state < WOLFSPDM_STATE_VERSION) { + return 0; + } + return ctx->spdmVersion; +} + +#ifdef WOLFSPDM_NUVOTON +word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return ctx->connectionHandle; +} + +word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return ctx->fipsIndicator; +} +#endif + +/* --- Session Establishment - Connect (Full Handshake) --- */ + +/* Standard SPDM 1.2 connection flow (for libspdm emulator, etc.) */ +static int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx) +{ + int rc; + + /* Reset state for new connection */ + ctx->state = WOLFSPDM_STATE_INIT; + 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)); + SPDM_CONNECT_STEP(ctx, "Step 5: GET_CERTIFICATE\n", + wolfSPDM_GetCertificate(ctx, 0)); + + /* Validate certificate chain if trusted CAs are loaded */ + if (ctx->flags.hasTrustedCAs) { + SPDM_CONNECT_STEP(ctx, "Validating certificate chain\n", + wolfSPDM_ValidateCertChain(ctx)); + } + else if (!ctx->flags.hasResponderPubKey) { + wolfSPDM_DebugPrint(ctx, + "Warning: No trusted CAs loaded — chain not validated\n"); + } + + 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; +} + +int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + /* Dispatch based on mode */ +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + return wolfSPDM_ConnectNuvoton(ctx); + } +#endif + + return wolfSPDM_ConnectStandard(ctx); +} + +int wolfSPDM_Disconnect(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) { + return rc; + } + + /* Send as secured message */ + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + + /* Reset state regardless of result */ + ctx->state = WOLFSPDM_STATE_INIT; + ctx->sessionId = 0; + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + + return (rc == WOLFSPDM_SUCCESS) ? WOLFSPDM_SUCCESS : rc; +} + +/* --- I/O Helper --- */ + +int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz) +{ + int rc; + + if (ctx == NULL || ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + /* In Nuvoton mode, all messages need TCG SPDM Binding headers. + * Clear SPDM messages (version 0x10-0x1F): tag 0x8101 + * Secured messages (encrypted records): tag 0x8201 + * The I/O callback receives fully-framed TCG messages and + * just needs to transport them over SPI/I2C to the TPM. */ + byte tcgTx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_TCG_HEADER_SIZE]; + byte tcgRx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_TCG_HEADER_SIZE]; + word32 tcgRxSz = sizeof(tcgRx); + int tcgTxSz; + 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 = WOLFSPDM_TCG_HEADER_SIZE + txSz; + if (totalSz > sizeof(tcgTx)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + 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, "Nuvoton 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 */ + { + word16 rspFips = SPDM_Get16BE(tcgRx + 10); + if (rspFips != 0) { + ctx->fipsIndicator = rspFips; + } + } + + /* Extract payload (everything after 16-byte TCG header) */ + { + word32 msgSize = SPDM_Get32BE(tcgRx + 2); + word32 payloadSz; + + if (msgSize > tcgRxSz) { + wolfSPDM_DebugPrint(ctx, "SendReceive: TCG size %u > " + "received %u\n", msgSize, 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_NUVOTON */ + + rc = ctx->ioCb(ctx, txBuf, txSz, rxBuf, rxSz, ctx->ioUserCtx); + if (rc != 0) { + return WOLFSPDM_E_IO_FAIL; + } + + return WOLFSPDM_SUCCESS; +} + +/* --- Debug Utilities --- */ + +void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) +{ + va_list args; + + if (ctx == NULL || !ctx->flags.debug) { + return; + } + + printf("[wolfSPDM] "); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + fflush(stdout); +} + +void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, + const byte* data, word32 len) +{ + word32 i; + + if (ctx == NULL || !ctx->flags.debug || data == NULL) { + return; + } + + printf("[wolfSPDM] %s (%u bytes): ", label, len); + for (i = 0; i < len && i < 32; i++) { + printf("%02x", data[i]); + } + if (len > 32) { + printf("..."); + } + printf("\n"); + fflush(stdout); +} + +/* --- 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 --- */ + +const char* wolfSPDM_GetErrorString(int error) +{ + switch (error) { + case WOLFSPDM_SUCCESS: return "Success"; + case WOLFSPDM_E_INVALID_ARG: return "Invalid argument"; + case WOLFSPDM_E_BUFFER_SMALL: return "Buffer too small"; + case WOLFSPDM_E_BAD_STATE: return "Invalid state"; + case WOLFSPDM_E_VERSION_MISMATCH: return "Version mismatch"; + case WOLFSPDM_E_CRYPTO_FAIL: return "Crypto operation failed"; + case WOLFSPDM_E_BAD_SIGNATURE: return "Bad signature"; + case WOLFSPDM_E_BAD_HMAC: return "HMAC verification failed"; + case WOLFSPDM_E_IO_FAIL: return "I/O failure"; + case WOLFSPDM_E_TIMEOUT: return "Timeout"; + case WOLFSPDM_E_PEER_ERROR: return "Peer error response"; + case WOLFSPDM_E_DECRYPT_FAIL: return "Decryption failed"; + case WOLFSPDM_E_SEQUENCE: return "Sequence number 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"; + default: return "Unknown error"; + } +} diff --git a/spdm/src/spdm_crypto.c b/spdm/src/spdm_crypto.c new file mode 100644 index 00000000..e3cbb816 --- /dev/null +++ b/spdm/src/spdm_crypto.c @@ -0,0 +1,265 @@ +/* spdm_crypto.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" +#include + +/* Left-pad a buffer in-place to targetSz with leading zeros */ +static void wolfSPDM_LeftPadToSize(byte* buf, word32 currentSz, word32 targetSz) +{ + if (currentSz < targetSz) { + word32 padLen = targetSz - currentSz; + XMEMMOVE(buf + padLen, buf, currentSz); + XMEMSET(buf, 0, padLen); + } +} + +/* --- Random Number Generation --- */ + +int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz) +{ + int rc; + + if (ctx == NULL || out == NULL || outSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.rngInitialized) { + return WOLFSPDM_E_BAD_STATE; + } + + rc = wc_RNG_GenerateBlock(&ctx->rng, out, outSz); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + return WOLFSPDM_SUCCESS; +} + +/* --- ECDHE Key Generation (P-384) --- */ + +int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx) +{ + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.rngInitialized) { + return WOLFSPDM_E_BAD_STATE; + } + + /* Free existing key if any */ + if (ctx->flags.ephemeralKeyInit) { + wc_ecc_free(&ctx->ephemeralKey); + ctx->flags.ephemeralKeyInit = 0; + } + + /* Initialize new key */ + 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); + if (rc != 0) { + wc_ecc_free(&ctx->ephemeralKey); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + ctx->flags.ephemeralKeyInit = 1; + wolfSPDM_DebugPrint(ctx, "Generated P-384 ephemeral key\n"); + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, + byte* pubKeyX, word32* pubKeyXSz, + byte* pubKeyY, word32* pubKeyYSz) +{ + int rc; + + if (ctx == NULL || pubKeyX == NULL || pubKeyXSz == NULL || + pubKeyY == NULL || pubKeyYSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.ephemeralKeyInit) { + return WOLFSPDM_E_BAD_STATE; + } + + if (*pubKeyXSz < WOLFSPDM_ECC_KEY_SIZE || + *pubKeyYSz < WOLFSPDM_ECC_KEY_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + rc = wc_ecc_export_public_raw(&ctx->ephemeralKey, + pubKeyX, pubKeyXSz, pubKeyY, pubKeyYSz); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + return WOLFSPDM_SUCCESS; +} + +/* --- ECDH Shared Secret Computation --- */ + +int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, + const byte* peerPubKeyX, const byte* peerPubKeyY) +{ + ecc_key peerKey; + int rc; + int peerKeyInit = 0; + + if (ctx == NULL || peerPubKeyX == NULL || peerPubKeyY == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.ephemeralKeyInit) { + 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; + } + + /* Compute ECDH shared secret */ + 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); + goto cleanup; + } + + /* Zero-pad if needed (P-384 should always return 48 bytes, but just in case) */ + 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); + + rc = 0; + +cleanup: + if (peerKeyInit) { + wc_ecc_free(&peerKey); + } + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +/* --- ECDSA Signing (P-384) --- */ + +int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, + byte* sig, word32* sigSz) +{ + ecc_key sigKey; + int rc; + int keyInit = 0; + byte derSig[104]; /* P-384 DER sig max: 2 + (2+49) + (2+49) = 104 */ + word32 derSigSz = sizeof(derSig); + word32 rLen, sLen; + + if (ctx == NULL || hash == NULL || sig == NULL || sigSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.hasReqKeyPair || ctx->reqPrivKeyLen == 0) { + wolfSPDM_DebugPrint(ctx, "No requester key pair for signing\n"); + return WOLFSPDM_E_BAD_STATE; + } + + /* Need at least 96 bytes for P-384 signature (R||S) */ + if (*sigSz < WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Initialize key structure */ + rc = wc_ecc_init(&sigKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_init failed: %d\n", rc); + return WOLFSPDM_E_CRYPTO_FAIL; + } + keyInit = 1; + + /* Import private key and public key */ + rc = wc_ecc_import_unsigned(&sigKey, + ctx->reqPubKey, /* X coordinate */ + ctx->reqPubKey + WOLFSPDM_ECC_KEY_SIZE, /* Y coordinate */ + ctx->reqPrivKey, /* Private key (d) */ + ECC_SECP384R1); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_import_unsigned failed: %d\n", rc); + goto cleanup; + } + + /* Sign the hash - wolfSSL returns DER encoded signature */ + 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); + goto cleanup; + } + + /* Convert DER signature to raw R||S format (96 bytes for P-384) */ + 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); + goto cleanup; + } + + /* Pad R and S to 48 bytes if needed */ + 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; /* 96 bytes */ + + wolfSPDM_DebugPrint(ctx, "Signed hash with P-384 key (sig=%u bytes)\n", *sigSz); + + rc = 0; + +cleanup: + if (keyInit) { + wc_ecc_free(&sigKey); + } + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} diff --git a/spdm/src/spdm_internal.h b/spdm/src/spdm_internal.h new file mode 100644 index 00000000..f3c8c551 --- /dev/null +++ b/spdm/src/spdm_internal.h @@ -0,0 +1,553 @@ +/* spdm_internal.h + * + * 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 + */ + +#ifndef WOLFSPDM_INTERNAL_H +#define WOLFSPDM_INTERNAL_H + +/* Include autoconf generated config.h for feature detection */ +#ifdef HAVE_CONFIG_H + #include +#endif + +/* wolfSSL options MUST be included first */ +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include + +#include +#include +#include + +/* wolfCrypt includes */ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* --- 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 + +/* --- 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 --- */ + +struct WOLFSPDM_CTX { + /* State machine */ + int state; + + /* Boolean flags — packed into a bit-field struct to save ~28 bytes */ + struct { + byte debug : 1; + byte initialized : 1; + byte isDynamic : 1; /* Set by wolfSPDM_New(), checked by Free */ + byte rngInitialized : 1; + byte ephemeralKeyInit : 1; + byte hasRspPubKey : 1; + byte hasReqKeyPair : 1; + byte hasMeasurements : 1; + byte hasResponderPubKey : 1; + byte hasTrustedCAs : 1; + byte m1m2HashInit : 1; + } flags; + + /* Protocol mode (standard SPDM or Nuvoton) */ + WOLFSPDM_MODE mode; + + /* I/O callback */ + WOLFSPDM_IO_CB ioCb; + void* ioUserCtx; + +#ifdef WOLFSPDM_NUVOTON + /* Nuvoton-specific: TCG binding fields */ + word32 connectionHandle; /* Connection handle (usually 0) */ + word16 fipsIndicator; /* FIPS service indicator */ + + /* Nuvoton-specific: Host's public key in TPMT_PUBLIC format */ + byte reqPubKeyTPMT[128]; /* TPMT_PUBLIC serialized (~120 bytes) */ + word32 reqPubKeyTPMTLen; +#endif + + /* Random number generator */ + WC_RNG rng; + + /* Negotiated parameters */ + byte spdmVersion; /* Negotiated SPDM version */ + word32 rspCaps; /* Responder capabilities */ + word32 reqCaps; /* Our (requester) capabilities */ + byte mutAuthRequested; /* MutAuthRequested from KEY_EXCHANGE_RSP (offset 6) */ + byte reqSlotId; /* ReqSlotIDParam from KEY_EXCHANGE_RSP (offset 7) */ + + /* 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]; + byte reqHsSecret[WOLFSPDM_HASH_SIZE]; + byte rspHsSecret[WOLFSPDM_HASH_SIZE]; + byte reqFinishedKey[WOLFSPDM_HASH_SIZE]; + byte rspFinishedKey[WOLFSPDM_HASH_SIZE]; + + /* Session encryption keys (AES-256-GCM) */ + byte reqDataKey[WOLFSPDM_AEAD_KEY_SIZE]; /* Outgoing encryption key */ + 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 */ + + /* Sequence numbers for IV generation */ + word64 reqSeqNum; /* Outgoing message sequence */ + word64 rspSeqNum; /* Incoming message sequence (expected) */ + + /* Session IDs */ + word16 reqSessionId; /* Our session ID (chosen by us) */ + 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[128]; /* TPMT_PUBLIC (120 bytes for P-384) or raw X||Y (96) */ + word32 rspPubKeyLen; + + /* Requester's identity key pair (for mutual auth) */ + byte reqPrivKey[WOLFSPDM_ECC_KEY_SIZE]; + word32 reqPrivKeyLen; + byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; + word32 reqPubKeyLen; + + /* Message buffers */ + byte sendBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_TAG_SIZE]; + byte recvBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_TAG_SIZE]; + +#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_ECC_SIG_SIZE]; /* Captured signature (96 bytes P-384) */ + word32 measSignatureSize; /* 0 if unsigned, 96 if signed */ + +#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) */ + ecc_key responderPubKey; /* Extracted from cert chain leaf */ + + /* Certificate chain validation */ + byte trustedCAs[WOLFSPDM_MAX_CERT_CHAIN]; /* DER-encoded root CAs */ + word32 trustedCAsSz; + +#ifndef NO_WOLFSPDM_CHALLENGE + /* Challenge authentication */ + byte challengeNonce[32]; /* Saved nonce from CHALLENGE request */ + byte challengeMeasHashType; /* MeasurementSummaryHashType from req */ + + /* 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 + + /* Key update state — app secrets for re-derivation */ + byte reqAppSecret[WOLFSPDM_HASH_SIZE]; /* 48 bytes */ + byte rspAppSecret[WOLFSPDM_HASH_SIZE]; /* 48 bytes */ +}; + +/* --- Byte-Order Helpers --- */ + +static WC_INLINE void SPDM_Set16LE(byte* buf, word16 val) { + buf[0] = (byte)(val & 0xFF); buf[1] = (byte)(val >> 8); +} +static WC_INLINE word16 SPDM_Get16LE(const byte* buf) { + return (word16)(buf[0] | (buf[1] << 8)); +} +static WC_INLINE void SPDM_Set16BE(byte* buf, word16 val) { + buf[0] = (byte)(val >> 8); buf[1] = (byte)(val & 0xFF); +} +static WC_INLINE word16 SPDM_Get16BE(const byte* buf) { + return (word16)((buf[0] << 8) | buf[1]); +} +static WC_INLINE void SPDM_Set32LE(byte* buf, word32 val) { + buf[0] = (byte)(val & 0xFF); buf[1] = (byte)((val >> 8) & 0xFF); + buf[2] = (byte)((val >> 16) & 0xFF); buf[3] = (byte)((val >> 24) & 0xFF); +} +static WC_INLINE word32 SPDM_Get32LE(const byte* buf) { + return (word32)buf[0] | ((word32)buf[1] << 8) | + ((word32)buf[2] << 16) | ((word32)buf[3] << 24); +} +static WC_INLINE void SPDM_Set32BE(byte* buf, word32 val) { + buf[0] = (byte)(val >> 24); buf[1] = (byte)((val >> 16) & 0xFF); + buf[2] = (byte)((val >> 8) & 0xFF); buf[3] = (byte)(val & 0xFF); +} +static WC_INLINE word32 SPDM_Get32BE(const byte* buf) { + return ((word32)buf[0] << 24) | ((word32)buf[1] << 16) | + ((word32)buf[2] << 8) | (word32)buf[3]; +} +static WC_INLINE void SPDM_Set64LE(byte* buf, word64 val) { + buf[0] = (byte)(val & 0xFF); buf[1] = (byte)((val >> 8) & 0xFF); + buf[2] = (byte)((val >> 16) & 0xFF); buf[3] = (byte)((val >> 24) & 0xFF); + buf[4] = (byte)((val >> 32) & 0xFF); buf[5] = (byte)((val >> 40) & 0xFF); + buf[6] = (byte)((val >> 48) & 0xFF); buf[7] = (byte)((val >> 56) & 0xFF); +} +static WC_INLINE word64 SPDM_Get64LE(const byte* buf) { + return (word64)buf[0] | ((word64)buf[1] << 8) | + ((word64)buf[2] << 16) | ((word64)buf[3] << 24) | + ((word64)buf[4] << 32) | ((word64)buf[5] << 40) | + ((word64)buf[6] << 48) | ((word64)buf[7] << 56); +} + +/* Write TCG SPDM Binding header (16 bytes): tag(2/BE) + size(4/BE) + + * connHandle(4/BE) + fips(2/BE) + reserved(4) */ +#ifdef WOLFSPDM_NUVOTON +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: BaseIV XOR zero-extended sequence number (DSP0277) */ +static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, + word64 seqNum, int nuvotonMode) +{ + XMEMCPY(iv, baseIv, WOLFSPDM_AEAD_IV_SIZE); +#ifdef WOLFSPDM_NUVOTON + if (nuvotonMode) { + byte seq[8]; int i; + SPDM_Set64LE(seq, seqNum); + for (i = 0; i < 8; i++) iv[i] ^= seq[i]; + } + else +#endif + { + (void)nuvotonMode; + iv[0] ^= (byte)(seqNum & 0xFF); + iv[1] ^= (byte)((seqNum >> 8) & 0xFF); + } +} + +/* --- Connect Step Macro --- */ + +#define SPDM_CONNECT_STEP(ctx, msg, func) do { \ + wolfSPDM_DebugPrint(ctx, msg); \ + rc = func; \ + if (rc != WOLFSPDM_SUCCESS) { ctx->state = WOLFSPDM_STATE_ERROR; return rc; } \ +} while (0) + +/* --- Argument Validation Macros --- */ + +#define SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, minSz) \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) == NULL || *(bufSz) < (minSz)) \ + return WOLFSPDM_E_BUFFER_SMALL + +#define SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, minSz) \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) < (minSz)) \ + return WOLFSPDM_E_INVALID_ARG + +/* --- 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)) { \ + wolfSPDM_DebugPrint((ctx), "SPDM error: 0x%02x\n", _ec); \ + return WOLFSPDM_E_PEER_ERROR; \ + } \ + return (fallbackErr); \ + } \ + } while (0) + +/* --- Internal Function Declarations - Transcript --- */ + +/* Reset transcript buffer */ +WOLFSPDM_API void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); + +/* Add data to transcript */ +WOLFSPDM_API int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); + +/* Add data to certificate chain buffer */ +WOLFSPDM_API int wolfSPDM_CertChainAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); + +/* Compute hash of current transcript */ +WOLFSPDM_API int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); + +/* Compute Ct = Hash(certificate_chain) */ +WOLFSPDM_API int wolfSPDM_ComputeCertChainHash(WOLFSPDM_CTX* ctx); + +/* SHA-384 hash helper: Hash(d1 || d2 || d3), pass NULL/0 for unused buffers */ +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 --- */ + +/* Generate ephemeral P-384 key for ECDHE */ +WOLFSPDM_API int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); + +/* Export ephemeral public key (X||Y) */ +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 */ +WOLFSPDM_API int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, + const byte* peerPubKeyX, const byte* peerPubKeyY); + +/* Generate random bytes */ +WOLFSPDM_API int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); + +/* Sign hash with requester's private key (for mutual auth FINISH) */ +WOLFSPDM_API int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, + byte* sig, word32* sigSz); + +/* --- Internal Function Declarations - Key Derivation --- */ + +/* Derive all keys from shared secret and TH1 */ +WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); + +/* Derive application data keys from MasterSecret and TH2_final */ +WOLFSPDM_API int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx); + +/* HKDF-Expand with SPDM BinConcat format (uses version-specific prefix) */ +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 */ +WOLFSPDM_API int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, + byte* verifyData); + +/* --- Internal Function Declarations - Message Building --- */ + +/* Build GET_VERSION request */ +WOLFSPDM_API int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); + +/* Build GET_CAPABILITIES request */ +WOLFSPDM_API int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); + +/* Build NEGOTIATE_ALGORITHMS request */ +WOLFSPDM_API int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); + +/* Build GET_DIGESTS request */ +WOLFSPDM_API int wolfSPDM_BuildGetDigests(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); + +/* Build GET_CERTIFICATE request */ +WOLFSPDM_API int wolfSPDM_BuildGetCertificate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + int slotId, word16 offset, word16 length); + +/* Build KEY_EXCHANGE request */ +WOLFSPDM_API int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); + +/* Build FINISH request */ +WOLFSPDM_API int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); + +/* Build END_SESSION request */ +WOLFSPDM_API int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); + +/* --- Internal Function Declarations - Message Parsing --- */ + +/* Parse VERSION response */ +WOLFSPDM_API int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); + +/* Parse CAPABILITIES response */ +WOLFSPDM_API int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); + +/* Parse ALGORITHMS response */ +WOLFSPDM_API int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); + +/* Parse DIGESTS response */ +WOLFSPDM_API int wolfSPDM_ParseDigests(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); + +/* Parse CERTIFICATE response */ +WOLFSPDM_API int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz, + word16* portionLen, word16* remainderLen); + +/* Parse KEY_EXCHANGE_RSP */ +WOLFSPDM_API int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); + +/* Parse FINISH_RSP (after decryption) */ +WOLFSPDM_API int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); + +/* Check for ERROR response */ +WOLFSPDM_API int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); + +/* --- Internal Function Declarations - Secured Messaging --- */ + +/* Encrypt plaintext using session keys */ +WOLFSPDM_API int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, + const byte* plain, word32 plainSz, + byte* enc, word32* encSz); + +/* Decrypt ciphertext using session keys */ +WOLFSPDM_API int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, + const byte* enc, word32 encSz, + byte* plain, word32* plainSz); + +/* --- Internal Utility Functions --- */ + +/* Send message via I/O callback and receive response */ +WOLFSPDM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz); + +/* Debug print (if enabled) */ +WOLFSPDM_API void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) +#ifdef __GNUC__ + __attribute__((format(printf, 2, 3))) +#endif + ; + +/* Hex dump for debugging */ +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 */ +WOLFSPDM_API int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + byte operation, byte requestSig); + +/* Parse MEASUREMENTS response */ +WOLFSPDM_API int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); + +#ifndef NO_WOLFSPDM_MEAS_VERIFY +/* Verify measurement signature (L1/L2 transcript) */ +WOLFSPDM_API 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 */ +WOLFSPDM_API int wolfSPDM_ExtractResponderPubKey(WOLFSPDM_CTX* ctx); + +/* Validate certificate chain using trusted CAs and extract public key */ +WOLFSPDM_API int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx); + +/* --- Internal Function Declarations - Challenge --- */ + +#ifndef NO_WOLFSPDM_CHALLENGE +/* Build CHALLENGE request */ +WOLFSPDM_API int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + int slotId, byte measHashType); + +/* Parse CHALLENGE_AUTH response */ +WOLFSPDM_API int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz, word32* sigOffset); + +/* Verify CHALLENGE_AUTH signature */ +WOLFSPDM_API 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 */ +WOLFSPDM_API int wolfSPDM_BuildHeartbeat(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); + +/* Parse HEARTBEAT_ACK response */ +WOLFSPDM_API int wolfSPDM_ParseHeartbeatAck(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz); + +/* --- Internal Function Declarations - Key Update --- */ + +/* Build KEY_UPDATE request */ +WOLFSPDM_API int wolfSPDM_BuildKeyUpdate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + byte operation, byte* tag); + +/* Parse KEY_UPDATE_ACK response */ +WOLFSPDM_API int wolfSPDM_ParseKeyUpdateAck(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz, byte operation, byte tag); + +/* Derive updated keys from saved app secrets */ +WOLFSPDM_API int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, int updateAll); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_INTERNAL_H */ diff --git a/spdm/src/spdm_kdf.c b/spdm/src/spdm_kdf.c new file mode 100644 index 00000000..1eae369a --- /dev/null +++ b/spdm/src/spdm_kdf.c @@ -0,0 +1,318 @@ +/* spdm_kdf.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" +#include + +/* + * 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) + */ + +int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, + const char* label, const byte* context, word32 contextSz, + byte* out, word32 outSz) +{ + byte info[128]; + word32 infoLen = 0; + const char* prefix; + int rc; + + if (secret == NULL || label == NULL || out == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Select version-specific prefix */ + if (spdmVersion >= 0x14) { + prefix = SPDM_BIN_CONCAT_PREFIX_14; /* "spdm1.4 " */ + } else if (spdmVersion >= 0x13) { + prefix = SPDM_BIN_CONCAT_PREFIX_13; /* "spdm1.3 " */ + } else { + prefix = SPDM_BIN_CONCAT_PREFIX_12; /* "spdm1.2 " */ + } + + /* 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); + + XMEMCPY(info + infoLen, prefix, SPDM_BIN_CONCAT_PREFIX_LEN); + infoLen += SPDM_BIN_CONCAT_PREFIX_LEN; + + XMEMCPY(info + infoLen, label, XSTRLEN(label)); + infoLen += (word32)XSTRLEN(label); + + if (context != NULL && contextSz > 0) { + XMEMCPY(info + infoLen, context, contextSz); + infoLen += contextSz; + } + + rc = wc_HKDF_Expand(WC_SHA384, secret, secretSz, info, infoLen, out, outSz); + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, + byte* verifyData) +{ + Hmac hmac; + int rc; + + if (finishedKey == NULL || thHash == NULL || verifyData == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wc_HmacSetKey(&hmac, WC_SHA384, finishedKey, WOLFSPDM_HASH_SIZE); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wc_HmacUpdate(&hmac, thHash, WOLFSPDM_HASH_SIZE); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wc_HmacFinal(&hmac, verifyData); + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +/* Derive both data key (AES-256) and IV from a secret using HKDF-Expand */ +static int wolfSPDM_DeriveKeyIvPair(byte spdmVersion, const byte* secret, + byte* key, byte* iv) +{ + int rc; + rc = wolfSPDM_HkdfExpandLabel(spdmVersion, secret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_KEY, NULL, 0, + key, WOLFSPDM_AEAD_KEY_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + return wolfSPDM_HkdfExpandLabel(spdmVersion, secret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_IV, NULL, 0, + iv, WOLFSPDM_AEAD_IV_SIZE); +} + +int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash) +{ + byte salt[WOLFSPDM_HASH_SIZE]; + int rc; + + if (ctx == NULL || th1Hash == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* SPDM uses zero salt (unlike TLS 1.3 which uses Hash("")) */ + XMEMSET(salt, 0, sizeof(salt)); + + /* HandshakeSecret = HKDF-Extract(zeros, sharedSecret) */ + rc = wc_HKDF_Extract(WC_SHA384, salt, sizeof(salt), + ctx->sharedSecret, ctx->sharedSecretSz, + ctx->handshakeSecret); + if (rc != 0) { + 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); +} + +int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) +{ + byte th2Hash[WOLFSPDM_HASH_SIZE]; + byte salt[WOLFSPDM_HASH_SIZE]; + 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) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Compute TH2_final = Hash(full transcript including FINISH + FINISH_RSP) */ + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + /* 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) { + return rc; + } + + /* 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) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + /* 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) { + return rc; + } + + /* 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) { + return rc; + } + + /* 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) { + return rc; + } + + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, rspAppSecret, + ctx->rspDataKey, ctx->rspDataIv); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* 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"); + + return WOLFSPDM_SUCCESS; +} + +/* --- Key Update Re-derivation (DSP0277) --- */ + +int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, int updateAll) +{ + byte newReqAppSecret[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) { + return rc; + } + + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, newReqAppSecret, + ctx->reqDataKey, ctx->reqDataIv); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Save new requester secret for future updates */ + XMEMCPY(ctx->reqAppSecret, newReqAppSecret, WOLFSPDM_HASH_SIZE); + + /* Optionally update responder key */ + if (updateAll) { + byte newRspAppSecret[WOLFSPDM_HASH_SIZE]; + + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->rspAppSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_UPDATE, NULL, 0, + newRspAppSecret, WOLFSPDM_HASH_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, newRspAppSecret, + ctx->rspDataKey, ctx->rspDataIv); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Save new responder secret for future updates */ + XMEMCPY(ctx->rspAppSecret, newRspAppSecret, WOLFSPDM_HASH_SIZE); + } + + return WOLFSPDM_SUCCESS; +} diff --git a/spdm/src/spdm_msg.c b/spdm/src/spdm_msg.c new file mode 100644 index 00000000..08582f69 --- /dev/null +++ b/spdm/src/spdm_msg.c @@ -0,0 +1,1292 @@ +/* spdm_msg.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" +#include +#include + +int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) +{ + /* Note: ctx is not used for GET_VERSION, check buf/bufSz directly */ + if (buf == NULL || bufSz == NULL || *bufSz < 4) + return WOLFSPDM_E_BUFFER_SMALL; + + /* Per SPDM spec, GET_VERSION always uses version 0x10 */ + buf[0] = SPDM_VERSION_10; + buf[1] = SPDM_GET_VERSION; + buf[2] = 0x00; + buf[3] = 0x00; + *bufSz = 4; + + 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) */ + buf[12] = 0x00; buf[13] = 0x10; buf[14] = 0x00; buf[15] = 0x00; + /* MaxSPDMmsgSize (4 LE) */ + buf[16] = 0x00; buf[17] = 0x10; buf[18] = 0x00; buf[19] = 0x00; + + *bufSz = 20; + 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; /* Use negotiated version */ + buf[1] = SPDM_NEGOTIATE_ALGORITHMS; + buf[2] = 0x04; /* NumAlgoStructTables = 4 */ + buf[3] = 0x00; + buf[4] = 48; buf[5] = 0x00; /* Length = 48 */ + 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; + + /* Struct tables start at offset 32 */ + /* DHE: SECP_384_R1 */ + buf[32] = 0x02; buf[33] = 0x20; buf[34] = 0x10; buf[35] = 0x00; + /* AEAD: AES_256_GCM */ + buf[36] = 0x03; buf[37] = 0x20; buf[38] = 0x02; buf[39] = 0x00; + /* ReqBaseAsymAlg */ + buf[40] = 0x04; buf[41] = 0x20; buf[42] = 0x0F; buf[43] = 0x00; + /* KeySchedule */ + buf[44] = 0x05; buf[45] = 0x20; buf[46] = 0x01; buf[47] = 0x00; + + *bufSz = 48; + return WOLFSPDM_SUCCESS; +} + +static int wolfSPDM_BuildSimpleMsg(WOLFSPDM_CTX* ctx, byte msgCode, + byte* buf, word32* bufSz) +{ + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 4); + buf[0] = ctx->spdmVersion; + buf[1] = msgCode; + buf[2] = 0x00; + buf[3] = 0x00; + *bufSz = 4; + 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; +} + +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); + int rc; + + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 180); + + rc = wolfSPDM_GenerateEphemeralKey(ctx); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rc = wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &pubKeyXSz, + pubKeyY, &pubKeyYSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + 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_NUVOTON + 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); + + 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: 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; + + /* OpaqueData for secured message version negotiation */ +#ifdef WOLFSPDM_NUVOTON + /* Nuvoton format: 12 bytes per spec Rev 1.11 page 19-20 + * OpaqueLength(2 LE) + OpaqueData(12 bytes) = 14 bytes total */ + buf[offset++] = 0x0c; /* OpaqueLength = 12 (LE) */ + buf[offset++] = 0x00; + buf[offset++] = 0x00; buf[offset++] = 0x00; /* SMDataID = 0 */ + buf[offset++] = 0x05; buf[offset++] = 0x00; /* DataSize = 5 (LE) */ + buf[offset++] = 0x01; /* Registry ID = 1 (DMTF) */ + buf[offset++] = 0x01; /* VendorLen = 1 */ + buf[offset++] = 0x01; buf[offset++] = 0x00; /* VersionCount = 1, Reserved = 0 */ + buf[offset++] = 0x10; buf[offset++] = 0x00; /* Version 1.0 (0x0010 LE) */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding to make OpaqueData 12 bytes */ +#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 + + *bufSz = offset; + return WOLFSPDM_SUCCESS; +} + +/* --- 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) */ +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; + word32 zeroPadLen; + byte majorVer, minorVer; + int i, rc; + + 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; + } + + /* Zero padding: 36 - contextStrLen bytes */ + zeroPadLen = 36 - contextStrLen; + XMEMSET(&signMsg[signMsgLen], 0x00, zeroPadLen); + signMsgLen += zeroPadLen; + + /* Signing context string */ + XMEMCPY(&signMsg[signMsgLen], contextStr, contextStrLen); + signMsgLen += 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; + + return WOLFSPDM_SUCCESS; +} + +/* 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); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "ECC verify_hash failed: %d\n", rc); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + return verified == 1 ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +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 */ + int mutualAuth = 0; + int rc; + +#ifdef WOLFSPDM_NUVOTON + /* Nuvoton requires mutual authentication when we have a requester key */ + if (ctx->mode == WOLFSPDM_MODE_NUVOTON && ctx->flags.hasReqKeyPair) { + mutualAuth = 1; + wolfSPDM_DebugPrint(ctx, "Nuvoton: Mutual auth ENABLED (required after GIVE_PUB)\n"); + } +#endif + + /* Check buffer size: 148 bytes for mutual auth, 52 bytes otherwise */ + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (mutualAuth && *bufSz < 148) { + return WOLFSPDM_E_BUFFER_SMALL; + } + if (!mutualAuth && *bufSz < 52) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Build FINISH header */ + buf[0] = ctx->spdmVersion; + buf[1] = SPDM_FINISH; + if (mutualAuth) { + buf[2] = 0x01; /* Param1: Signature field is included */ + buf[3] = 0xFF; /* Param2: 0xFF = requester public key provisioned in trusted environment (GIVE_PUB_KEY) */ + } + else { + buf[2] = 0x00; /* Param1: No signature */ + buf[3] = 0x00; /* Param2: SlotID = 0 when no signature */ + } + + /* Per DSP0274 / libspdm: When mutual auth is requested, the transcript + * for TH2 must include Hash(Cm_requester) - the hash of the requester's + * public key/cert chain - BETWEEN message_k and message_f (FINISH header). + * + * TH2 = Hash(VCA || Ct || message_k || Hash(Cm_req) || FINISH_header) + * + * For Nuvoton with PUB_KEY_ID (SlotID=0xFF), Cm is the TPMT_PUBLIC + * structure that was sent via GIVE_PUB_KEY. */ +#ifdef WOLFSPDM_NUVOTON + if (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) return rc; + rc = wolfSPDM_TranscriptAdd(ctx, cmHash, WOLFSPDM_HASH_SIZE); + if (rc != WOLFSPDM_SUCCESS) return rc; + } +#endif + + /* Add FINISH header to transcript for TH2 */ + rc = wolfSPDM_TranscriptAdd(ctx, buf, 4); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* TH2 = Hash(transcript with FINISH header) */ + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); + + /* For mutual auth, use SPDM 1.2+ signing context format per DSP0274 */ + if (mutualAuth) { + byte signMsgHash[WOLFSPDM_HASH_SIZE]; + + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "requester-finish signing", 24, th2Hash, signMsgHash); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Sign Hash(M) */ + rc = wolfSPDM_SignHash(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, signature, &sigSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "Failed to sign FINISH: %d\n", rc); + return rc; + } + + /* Copy signature to buffer (96 bytes) */ + XMEMCPY(&buf[offset], signature, WOLFSPDM_ECC_POINT_SIZE); + offset += WOLFSPDM_ECC_POINT_SIZE; + + /* Per DSP0274: TH2 for RequesterVerifyData MUST include the signature. + * TH2_sign = Hash(transcript || FINISH_header[4]) - used above for signature + * TH2_hmac = Hash(transcript || FINISH_header[4] || Signature[96]) - used for HMAC + * Add signature to transcript and recompute TH2 for HMAC. */ + rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_POINT_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + } + + /* RequesterVerifyData = HMAC(reqFinishedKey, TH2_hmac) + * For mutual auth: th2Hash now includes the signature (TH2_hmac) + * For no mutual auth: th2Hash is just Hash(transcript || FINISH_header) */ + rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, verifyData); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + 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) { + return rc; + } + + *bufSz = offset; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + return wolfSPDM_BuildSimpleMsg(ctx, SPDM_END_SESSION, buf, bufSz); +} + +int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode) +{ + if (buf == NULL || bufSz < 4) { + return 0; + } + + if (buf[1] == SPDM_ERROR) { + if (errorCode != NULL) { + *errorCode = buf[2]; + } + return 1; + } + + return 0; +} + +int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + word16 entryCount; + word32 i; + byte highestVersion = SPDM_VERSION_12; /* Start at 1.2, find highest supported (capped at 1.3) */ + + 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) + * Offset 6+: VersionNumberEntry array (2 bytes each, LE) */ + entryCount = SPDM_Get16LE(&buf[4]); + + /* Find highest supported version from entries (capped at 1.3 for now) + * + * TODO: SPDM 1.4 fails at FINISH step with libspdm emulator returning + * InvalidRequest (0x01). KEY_EXCHANGE and key derivation work correctly + * with "spdm1.4 " prefix, but FINISH message format may differ in 1.4. + * Investigate OpaqueData format or FINISH requirements for 1.4 support. + */ + for (i = 0; i < entryCount && (6 + i * 2 + 1) < bufSz; i++) { + byte ver = buf[6 + i * 2 + 1]; /* Major.Minor in high byte */ + /* Cap at 1.3 (0x13) - SPDM 1.4 FINISH handling needs work */ + if (ver > highestVersion && ver <= SPDM_VERSION_13) { + highestVersion = ver; + } + } + + ctx->spdmVersion = highestVersion; + ctx->state = WOLFSPDM_STATE_VERSION; + + wolfSPDM_DebugPrint(ctx, "Negotiated SPDM version: 0x%02x\n", ctx->spdmVersion); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 12); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CAPABILITIES, WOLFSPDM_E_CAPS_MISMATCH); + + ctx->rspCaps = SPDM_Get32LE(&buf[8]); + 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) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_ALGORITHMS, WOLFSPDM_E_ALGO_MISMATCH); + + ctx->state = WOLFSPDM_STATE_ALGO; + return WOLFSPDM_SUCCESS; +} + +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); + + ctx->state = WOLFSPDM_STATE_DIGESTS; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz, + word16* portionLen, word16* remainderLen) +{ + if (ctx == NULL || buf == NULL || bufSz < 8 || + portionLen == NULL || remainderLen == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CERTIFICATE, WOLFSPDM_E_CERT_FAIL); + + *portionLen = SPDM_Get16LE(&buf[4]); + *remainderLen = SPDM_Get16LE(&buf[6]); + + /* Add certificate chain data (starting at offset 8) */ + if (*portionLen > 0 && bufSz >= (word32)(8 + *portionLen)) { + wolfSPDM_CertChainAdd(ctx, buf + 8, *portionLen); + } + + if (*remainderLen == 0) { + ctx->state = WOLFSPDM_STATE_CERT; + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + word16 opaqueLen; + word32 sigOffset; + word32 keRspPartialLen; + byte peerPubKeyX[WOLFSPDM_ECC_KEY_SIZE]; + byte peerPubKeyY[WOLFSPDM_ECC_KEY_SIZE]; + const byte* signature; + const byte* rspVerifyData; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + int rc; + + 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 (offset 6) and ReqSlotIDParam (offset 7) per DSP0274 */ + ctx->mutAuthRequested = buf[6]; + ctx->reqSlotId = 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; + + (void)opaqueLen; + + if (bufSz < sigOffset + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + signature = buf + sigOffset; + 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) { + return rc; + } + + /* Add signature to transcript (TH1 includes signature) */ + rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_SIG_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Compute ECDH shared secret */ + rc = wolfSPDM_ComputeSharedSecret(ctx, peerPubKeyX, peerPubKeyY); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Compute TH1 = Hash(transcript including signature) */ + rc = wolfSPDM_TranscriptHash(ctx, ctx->th1); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + /* Derive all session keys */ + rc = wolfSPDM_DeriveHandshakeKeys(ctx, ctx->th1); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Verify ResponderVerifyData = HMAC(rspFinishedKey, TH1) */ + rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, ctx->th1, expectedHmac); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + if (XMEMCMP(expectedHmac, rspVerifyData, WOLFSPDM_HASH_SIZE) != 0) { + wolfSPDM_DebugPrint(ctx, "ResponderVerifyData MISMATCH\n"); + } else { + 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) { + return rc; + } + + ctx->state = WOLFSPDM_STATE_KEY_EX; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + + if (buf[1] == SPDM_FINISH_RSP) { + int addRc; + /* Add FINISH_RSP to transcript for TH2_final (app data key derivation) */ + addRc = wolfSPDM_TranscriptAdd(ctx, buf, 4); + if (addRc != WOLFSPDM_SUCCESS) { + return addRc; + } + ctx->state = WOLFSPDM_STATE_FINISH; + wolfSPDM_DebugPrint(ctx, "FINISH_RSP received - session established\n"); + return WOLFSPDM_SUCCESS; + } + + if (buf[1] == SPDM_ERROR) { + wolfSPDM_DebugPrint(ctx, "FINISH error: 0x%02x\n", buf[2]); + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_E_BAD_STATE; +} + +/* --- Measurement Message Building and Parsing --- */ + +#ifndef NO_WOLFSPDM_MEAS + +int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, + byte operation, byte requestSig) +{ + word32 offset = 0; + + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Size: 4 header + (requestSig ? 32 nonce + 1 slotId : 0) */ + if (requestSig && *bufSz < 37) { + return WOLFSPDM_E_BUFFER_SMALL; + } + if (!requestSig && *bufSz < 4) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_GET_MEASUREMENTS; + /* Param1: bits [7:1] = MeasurementSummaryHashType, 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 (1 byte) — slot 0 */ + buf[offset++] = 0x00; + } + + *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; + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 8); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_MEASUREMENTS, 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 + Signature */ + /* Nonce is present only if signature was requested */ + ctx->measSignatureSize = 0; + + if (offset + 32 + 2 <= bufSz) { + /* 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; + + /* Signature (if present) */ + if (offset + WOLFSPDM_ECC_SIG_SIZE <= bufSz) { + XMEMCPY(ctx->measSignature, &buf[offset], WOLFSPDM_ECC_SIG_SIZE); + ctx->measSignatureSize = WOLFSPDM_ECC_SIG_SIZE; + } + } + + 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: hash, overwritten by BuildSignedHash */ + const byte* sig, word32 sigSz, + const char* passMsg, const char* failMsg, int failErr) +{ + int rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + contextStr, contextStrLen, digest, digest); + if (rc != WOLFSPDM_SUCCESS) return rc; + + rc = wolfSPDM_VerifyEccSig(ctx, sig, sigSz, digest, WOLFSPDM_HASH_SIZE); + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "%s\n", passMsg); + return WOLFSPDM_SUCCESS; + } + wolfSPDM_DebugPrint(ctx, "%s\n", failMsg); + return failErr; +} + +int wolfSPDM_VerifyMeasurementSig(WOLFSPDM_CTX* ctx, + const byte* rspBuf, word32 rspBufSz, + const byte* reqMsg, word32 reqMsgSz) +{ + byte digest[WOLFSPDM_HASH_SIZE]; + word32 sigOffset; + 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 WOLFSPDM_ECC_SIG_SIZE bytes of the response */ + if (rspBufSz < WOLFSPDM_ECC_SIG_SIZE) { + return WOLFSPDM_E_MEASUREMENT; + } + sigOffset = rspBufSz - WOLFSPDM_ECC_SIG_SIZE; + + /* 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) return rc; + + return wolfSPDM_VerifySignedDigest(ctx, + "responder-measurements signing", 30, digest, + rspBuf + sigOffset, WOLFSPDM_ECC_SIG_SIZE, + "Measurement signature VERIFIED", + "Measurement signature INVALID", + WOLFSPDM_E_MEAS_SIG_FAIL); +} + +#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; +} + +int wolfSPDM_ExtractResponderPubKey(WOLFSPDM_CTX* ctx) +{ + DecodedCert cert; + const byte* leafCert; + word32 leafCertSz; + word32 idx; + 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; + } + + /* Extract public key from cert and import into ecc_key */ + rc = wc_ecc_init(&ctx->responderPubKey); + if (rc != 0) { + wc_FreeDecodedCert(&cert); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + idx = 0; + rc = wc_EccPublicKeyDecode(cert.publicKey, &idx, &ctx->responderPubKey, + cert.pubKeySize); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "ECC public key decode failed: %d\n", rc); + wc_ecc_free(&ctx->responderPubKey); + wc_FreeDecodedCert(&cert); + return WOLFSPDM_E_CERT_PARSE; + } + + wc_FreeDecodedCert(&cert); + ctx->flags.hasResponderPubKey = 1; + wolfSPDM_DebugPrint(ctx, "Extracted responder ECC P-384 public key\n"); + + 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 (reuses FindLeafCert internally) */ + 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; + int rc; + + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 36); + + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_CHALLENGE; + buf[offset++] = (byte)(slotId & 0x0F); + buf[offset++] = measHashType; + + /* Save measHashType for ParseChallengeAuth */ + ctx->challengeMeasHashType = measHashType; + + /* 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; + + *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); + + 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; + + /* Signature starts here */ + if (offset + WOLFSPDM_ECC_SIG_SIZE > 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) */ + rc = wc_Sha384Update(&ctx->m1m2Hash, reqMsg, reqMsgSz); + if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; + rc = wc_Sha384Update(&ctx->m1m2Hash, rspBuf, sigOffset); + if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; + + /* Finalize M1/M2 hash */ + rc = wc_Sha384Final(&ctx->m1m2Hash, digest); + ctx->flags.m1m2HashInit = 0; /* Hash consumed */ + if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; + + return wolfSPDM_VerifySignedDigest(ctx, + "responder-challenge_auth signing", 32, digest, + rspBuf + sigOffset, WOLFSPDM_ECC_SIG_SIZE, + "CHALLENGE_AUTH signature VERIFIED", + "CHALLENGE_AUTH signature INVALID", + WOLFSPDM_E_CHALLENGE); +} + +#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_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_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/spdm/src/spdm_nuvoton.c b/spdm/src/spdm_nuvoton.c new file mode 100644 index 00000000..af388b43 --- /dev/null +++ b/spdm/src/spdm_nuvoton.c @@ -0,0 +1,737 @@ +/* spdm_nuvoton.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 + */ + +/* Nuvoton TPM SPDM Support + * + * This file implements Nuvoton-specific SPDM functionality: + * - TCG SPDM Binding message framing (per TCG SPDM Binding Spec v1.0) + * - Nuvoton vendor-defined commands (GET_PUBK, GIVE_PUB, GET_STS_, SPDMONLY) + * - Nuvoton SPDM handshake flow + * + * Reference: Nuvoton SPDM Guidance Rev 1.11 + */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_NUVOTON + +#include +#include + +/* Check for SPDM ERROR in response payload */ +#define SPDM_CHECK_ERROR_RSP(ctx, buf, sz, label) \ + if ((sz) >= 4 && (buf)[1] == SPDM_ERROR) { \ + wolfSPDM_DebugPrint(ctx, label ": SPDM ERROR 0x%02x 0x%02x\n", \ + (buf)[2], (buf)[3]); \ + return WOLFSPDM_E_PEER_ERROR; \ + } + +/* --- Vendor Command Helper Types --- */ + +/* Response container for clear vendor commands */ +typedef struct { + char vdCode[WOLFSPDM_VDCODE_LEN + 1]; + byte payload[256]; + word32 payloadSz; +} WOLFSPDM_VENDOR_RSP; + +/* Clear vendor command: build → SendReceive → check error → parse response */ +static int wolfSPDM_VendorCmdClear(WOLFSPDM_CTX* ctx, const char* vdCode, + const byte* payload, word32 payloadSz, WOLFSPDM_VENDOR_RSP* rsp) +{ + byte spdmMsg[256]; + int spdmMsgSz; + byte rxBuf[512]; + word32 rxSz; + int rc; + + spdmMsgSz = wolfSPDM_BuildVendorDefined(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; + } + } + + return WOLFSPDM_SUCCESS; +} + +/* Secured vendor command: build → SecuredExchange → check error */ +static int wolfSPDM_VendorCmdSecured(WOLFSPDM_CTX* ctx, const char* vdCode, + const byte* payload, word32 payloadSz) +{ + byte spdmMsg[256]; + int spdmMsgSz; + byte decBuf[256]; + word32 decSz; + int rc; + + spdmMsgSz = wolfSPDM_BuildVendorDefined(vdCode, payload, payloadSz, + spdmMsg, sizeof(spdmMsg)); + if (spdmMsgSz < 0) { + return spdmMsgSz; + } + + decSz = sizeof(decBuf); + rc = wolfSPDM_SecuredExchange(ctx, spdmMsg, (word32)spdmMsgSz, + decBuf, &decSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + if (decSz >= 4 && decBuf[1] == SPDM_ERROR) { + wolfSPDM_DebugPrint(ctx, "%s: SPDM ERROR 0x%02x 0x%02x\n", + vdCode, decBuf[2], decBuf[3]); + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_SUCCESS; +} + +/* --- 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; + } + + /* TCG binding header (16 bytes per Nuvoton spec): + * tag(2/BE) + size(4/BE) + connHandle(4/BE) + fips(2/BE) + reserved(4) */ + 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); + /* SPDM Payload */ + 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; + } + + /* Parse header */ + tag = SPDM_Get16BE(inBuf); + if (tag != WOLFSPDM_TCG_TAG_CLEAR) { + return WOLFSPDM_E_PEER_ERROR; + } + + msgSize = SPDM_Get32BE(inBuf + 2); + if (msgSize > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + payloadSz = msgSize - WOLFSPDM_TCG_HEADER_SIZE; + if (*spdmPayloadSz < payloadSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Fill header if requested */ + 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); + } + + /* Extract payload */ + XMEMCPY(spdmPayload, inBuf + WOLFSPDM_TCG_HEADER_SIZE, payloadSz); + *spdmPayloadSz = payloadSz; + + return (int)payloadSz; +} + +int wolfSPDM_BuildTcgSecuredMessage( + WOLFSPDM_CTX* ctx, + const byte* encPayload, word32 encPayloadSz, + const byte* mac, word32 macSz, + byte* outBuf, word32 outBufSz) +{ + word32 totalSz; + word32 offset; + word16 recordLen; + + if (ctx == NULL || encPayload == NULL || mac == NULL || outBuf == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Total: TCG header(16) + sessionId(4/LE) + seqNum(8/LE) + + * length(2/LE) + encPayload + MAC */ + totalSz = WOLFSPDM_TCG_HEADER_SIZE + WOLFSPDM_TCG_SECURED_HDR_SIZE + + encPayloadSz + macSz; + + if (outBufSz < totalSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* TCG binding header (16 bytes, all BE) */ + wolfSPDM_WriteTcgHeader(outBuf, WOLFSPDM_TCG_TAG_SECURED, totalSz, + ctx->connectionHandle, ctx->fipsIndicator); + + offset = WOLFSPDM_TCG_HEADER_SIZE; + + /* Session ID (4 bytes LE per DSP0277): + * ReqSessionId(2/LE) || RspSessionId(2/LE) */ + SPDM_Set16LE(outBuf + offset, ctx->reqSessionId); + offset += 2; + SPDM_Set16LE(outBuf + offset, ctx->rspSessionId); + offset += 2; + + /* Sequence Number (8 bytes LE per DSP0277) */ + SPDM_Set64LE(outBuf + offset, ctx->reqSeqNum); + offset += 8; + + /* Length (2 bytes LE per DSP0277) = encrypted data + MAC */ + recordLen = (word16)(encPayloadSz + macSz); + SPDM_Set16LE(outBuf + offset, recordLen); + offset += 2; + + /* Encrypted payload */ + XMEMCPY(outBuf + offset, encPayload, encPayloadSz); + offset += encPayloadSz; + + /* MAC (AES-256-GCM tag) */ + XMEMCPY(outBuf + offset, mac, macSz); + + /* Note: Sequence number increment is handled by caller */ + + return (int)totalSz; +} + +int wolfSPDM_ParseTcgSecuredMessage( + const byte* inBuf, word32 inBufSz, + word32* sessionId, word64* seqNum, + byte* encPayload, word32* encPayloadSz, + byte* mac, word32* macSz, + WOLFSPDM_TCG_SECURED_HDR* hdr) +{ + word16 tag; + word32 msgSize; + word32 offset; + word16 recordLen; + word32 payloadSz; + + if (inBuf == NULL || sessionId == NULL || seqNum == NULL || + encPayload == NULL || encPayloadSz == NULL || + mac == NULL || macSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (inBufSz < WOLFSPDM_TCG_HEADER_SIZE + WOLFSPDM_TCG_SECURED_HDR_SIZE + + WOLFSPDM_AEAD_TAG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Parse TCG binding header (16 bytes, all BE) */ + tag = SPDM_Get16BE(inBuf); + if (tag != WOLFSPDM_TCG_TAG_SECURED) { + return WOLFSPDM_E_PEER_ERROR; + } + + msgSize = SPDM_Get32BE(inBuf + 2); + if (msgSize > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Fill header if requested */ + 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); + } + + offset = WOLFSPDM_TCG_HEADER_SIZE; + + /* Session ID (4 bytes LE per DSP0277): + * ReqSessionId(2/LE) || RspSessionId(2/LE) */ + { + word16 reqSid = SPDM_Get16LE(inBuf + offset); + word16 rspSid = SPDM_Get16LE(inBuf + offset + 2); + *sessionId = ((word32)reqSid << 16) | rspSid; + } + offset += 4; + + /* Sequence Number (8 bytes LE per DSP0277) */ + *seqNum = SPDM_Get64LE(inBuf + offset); + offset += 8; + + /* Length (2 bytes LE per DSP0277) = encrypted data + MAC */ + recordLen = SPDM_Get16LE(inBuf + offset); + offset += 2; + + /* Validate record length */ + if (recordLen < WOLFSPDM_AEAD_TAG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + if (offset + recordLen > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Encrypted payload size = recordLen - MAC */ + payloadSz = recordLen - WOLFSPDM_AEAD_TAG_SIZE; + if (*encPayloadSz < payloadSz || *macSz < WOLFSPDM_AEAD_TAG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Encrypted payload */ + XMEMCPY(encPayload, inBuf + offset, payloadSz); + *encPayloadSz = payloadSz; + offset += payloadSz; + + /* MAC */ + XMEMCPY(mac, inBuf + offset, WOLFSPDM_AEAD_TAG_SIZE); + *macSz = WOLFSPDM_AEAD_TAG_SIZE; + + return (int)payloadSz; +} + +/* --- SPDM Vendor Defined Message Helpers --- */ + +int wolfSPDM_BuildVendorDefined( + 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; + } + + /* SPDM VENDOR_DEFINED_REQUEST format (per Nuvoton SPDM Guidance): + * 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; + } + + /* SPDM Version (v1.3 = 0x13) */ + outBuf[offset++] = SPDM_VERSION_13; + /* Request/Response Code */ + outBuf[offset++] = SPDM_VENDOR_DEFINED_REQUEST; + /* Param1, Param2 */ + outBuf[offset++] = 0x00; + outBuf[offset++] = 0x00; + /* Standard ID (0x0001 = TCG, little-endian per Nuvoton spec) */ + SPDM_Set16LE(outBuf + offset, 0x0001); + offset += 2; + /* Vendor ID Length (0 for TCG) */ + outBuf[offset++] = 0x00; + /* Request Length (vdCode + payload, little-endian per Nuvoton spec) */ + 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; + } + + /* Skip SPDM version */ + offset += 1; + /* Skip request/response code + params */ + offset += 3; + /* Skip standard ID (2 bytes LE) */ + offset += 2; + /* Vendor ID length and vendor ID data */ + vendorIdLen = inBuf[offset]; + offset += 1 + vendorIdLen; + + if (offset + 2 > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Request/Response Length (2 bytes LE per Nuvoton spec) */ + 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; + } + + /* VdCode */ + XMEMCPY(vdCode, inBuf + offset, WOLFSPDM_VDCODE_LEN); + vdCode[WOLFSPDM_VDCODE_LEN] = '\0'; /* Null-terminate */ + offset += WOLFSPDM_VDCODE_LEN; + + /* Payload */ + 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; +} + +/* --- Nuvoton-Specific SPDM Functions --- */ + +int wolfSPDM_Nuvoton_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, "Nuvoton: GET_PUBK\n"); + + rc = wolfSPDM_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_PUBK, + NULL, 0, &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Verify VdCode */ + 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); + + /* Copy public key to output */ + if (*pubKeySz < rsp.payloadSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(pubKey, rsp.payload, rsp.payloadSz); + *pubKeySz = rsp.payloadSz; + + /* Store for KEY_EXCHANGE cert_chain_buffer_hash computation. + * Per Nuvoton SPDM Guidance: cert_chain_buffer_hash = SHA-384(TPMT_PUBLIC) */ + if (rsp.payloadSz <= sizeof(ctx->rspPubKey)) { + XMEMCPY(ctx->rspPubKey, rsp.payload, rsp.payloadSz); + ctx->rspPubKeyLen = rsp.payloadSz; + ctx->flags.hasRspPubKey = 1; + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nuvoton_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, "Nuvoton: GIVE_PUB (%u bytes) - sending ENCRYPTED\n", pubKeySz); + + rc = wolfSPDM_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; +} + +int wolfSPDM_Nuvoton_GetStatus( + WOLFSPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status) +{ + WOLFSPDM_VENDOR_RSP rsp; + byte statusType[4] = {0x00, 0x00, 0x00, 0x00}; /* All */ + 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_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; /* If GET_STS works, SPDM is enabled */ + + /* Session active can't be determined from GET_STS alone - + * if we're getting a response, SPDM is working */ + status->sessionActive = 0; + + wolfSPDM_DebugPrint(ctx, "GET_STS_: SpecVersion=%u.%u, SPDMOnly=%s\n", + specMajor, specMinor, spdmOnly ? "LOCKED" : "unlocked"); + } + else if (rsp.payloadSz >= 1) { + /* Minimal response - just SPDMOnly */ + status->spdmOnlyLocked = (rsp.payload[0] != 0); + status->spdmEnabled = 1; + wolfSPDM_DebugPrint(ctx, "GET_STS_: SPDMOnly=%s (minimal response)\n", + status->spdmOnlyLocked ? "LOCKED" : "unlocked"); + } + 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_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_SPDMONLY, + param, sizeof(param)); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "SPDMONLY: Success\n"); + return WOLFSPDM_SUCCESS; +} + +/* --- Nuvoton SPDM Connection Flow --- */ + +/* Nuvoton-specific connection flow: + * GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH + * + * Key differences from standard SPDM: + * - No GET_CAPABILITIES or NEGOTIATE_ALGORITHMS (Algorithm Set B is fixed) + * - Uses GET_PUBK vendor command instead of GET_CERTIFICATE + * - Uses GIVE_PUB vendor command for mutual authentication + * - All messages wrapped in TCG binding headers + */ +int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) +{ + int rc; + byte pubKey[256]; + word32 pubKeySz; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugPrint(ctx, "Nuvoton: Starting SPDM connection\n"); + + /* Reset state for new connection */ + ctx->state = WOLFSPDM_STATE_INIT; + wolfSPDM_TranscriptReset(ctx); + + /* Step 1: GET_VERSION / VERSION */ + SPDM_CONNECT_STEP(ctx, "Nuvoton Step 1: GET_VERSION\n", + wolfSPDM_GetVersion(ctx)); + + /* Step 2: GET_PUBK (Nuvoton vendor command) + * Gets the TPM's SPDM-Identity public key (TPMT_PUBLIC format) */ + wolfSPDM_DebugPrint(ctx, "Nuvoton Step 2: GET_PUBK\n"); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_Nuvoton_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; + + /* Step 2.5: Compute Ct = SHA-384(TPMT_PUBLIC) and add to transcript + * For Nuvoton, the cert_chain_buffer_hash is SHA-384(TPMT_PUBLIC) + * instead of the standard certificate chain hash */ + if (ctx->flags.hasRspPubKey && ctx->rspPubKeyLen > 0) { + wolfSPDM_DebugPrint(ctx, "Nuvoton: Computing Ct = SHA-384(TPMT_PUBLIC[%u])\n", + ctx->rspPubKeyLen); + rc = wolfSPDM_Sha384Hash(ctx->certChainHash, + ctx->rspPubKey, ctx->rspPubKeyLen, 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; + } + } + else { + wolfSPDM_DebugPrint(ctx, "Nuvoton: Warning - no responder public key for Ct\n"); + } + + SPDM_CONNECT_STEP(ctx, "Nuvoton Step 3: KEY_EXCHANGE\n", + wolfSPDM_KeyExchange(ctx)); + + /* Step 4: GIVE_PUB (Nuvoton vendor command) - sent as SECURED message + * Gives the host's SPDM-Identity public key to the TPM. + * Per Nuvoton spec Rev 1.11 section 4.2.4, GIVE_PUB uses tag 0x8201 (secured). */ + if (ctx->flags.hasReqKeyPair && ctx->reqPubKeyTPMTLen > 0) { + wolfSPDM_DebugPrint(ctx, "Nuvoton Step 4: GIVE_PUB\n"); + rc = wolfSPDM_Nuvoton_GivePubKey(ctx, ctx->reqPubKeyTPMT, + ctx->reqPubKeyTPMTLen); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GIVE_PUB failed: %d\n", rc); + /* Don't fail - continue to FINISH for debug */ + } + else { + wolfSPDM_DebugPrint(ctx, "GIVE_PUB succeeded!\n"); + } + } + else { + wolfSPDM_DebugPrint(ctx, "Nuvoton Step 4: GIVE_PUB (skipped, no host key)\n"); + } + + /* Step 5: FINISH */ + SPDM_CONNECT_STEP(ctx, "Nuvoton Step 5: FINISH\n", + wolfSPDM_Finish(ctx)); + + ctx->state = WOLFSPDM_STATE_CONNECTED; + wolfSPDM_DebugPrint(ctx, "Nuvoton: SPDM Session Established! " + "SessionID=0x%08x\n", ctx->sessionId); + + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON */ diff --git a/spdm/src/spdm_secured.c b/spdm/src/spdm_secured.c new file mode 100644 index 00000000..e36034cd --- /dev/null +++ b/spdm/src/spdm_secured.c @@ -0,0 +1,534 @@ +/* spdm_secured.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" +#include + +/* + * SPDM Secured Message Format (DSP0277): + * + * 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 (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) + */ + +#ifdef WOLFSPDM_NUVOTON +/* Self-test: verify AES-GCM encrypt/decrypt round-trip with current keys. + * Called before first encrypted message to confirm crypto parameters. */ +static int wolfSPDM_AesGcmSelfTest(WOLFSPDM_CTX* ctx) +{ + Aes aesEnc, aesDec; + byte testPlain[] = "wolfSPDM AES-GCM self-test 1234"; /* 31 bytes */ + byte testCipher[32]; + byte testDecrypted[32]; + byte testTag[WOLFSPDM_AEAD_TAG_SIZE]; + byte testAad[14]; + word32 testPlainSz = sizeof(testPlain); + int rc; + + /* Build AAD matching what we'd use for SeqNum=0 */ + SPDM_Set32LE(&testAad[0], ctx->sessionId); + XMEMSET(&testAad[4], 0, 8); /* SeqNum = 0 */ + SPDM_Set16LE(&testAad[12], (word16)(testPlainSz + 16)); + + /* Encrypt */ + rc = wc_AesGcmSetKey(&aesEnc, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Self-test: AesGcmSetKey (enc) failed: %d\n", rc); + return rc; + } + rc = wc_AesGcmEncrypt(&aesEnc, testCipher, testPlain, testPlainSz, + ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE, + testTag, WOLFSPDM_AEAD_TAG_SIZE, testAad, 14); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Self-test: AesGcmEncrypt failed: %d\n", rc); + return rc; + } + + /* Decrypt with same key */ + rc = wc_AesGcmSetKey(&aesDec, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Self-test: AesGcmSetKey (dec) failed: %d\n", rc); + return rc; + } + rc = wc_AesGcmDecrypt(&aesDec, testDecrypted, testCipher, testPlainSz, + ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE, + testTag, WOLFSPDM_AEAD_TAG_SIZE, testAad, 14); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Self-test: AesGcmDecrypt FAILED: %d\n", rc); + return rc; + } + + /* Verify plaintext matches */ + if (XMEMCMP(testPlain, testDecrypted, testPlainSz) != 0) { + wolfSPDM_DebugPrint(ctx, "Self-test: Plaintext mismatch!\n"); + return -1; + } + + wolfSPDM_DebugPrint(ctx, "Self-test: AES-GCM round-trip PASSED\n"); + return 0; +} +#endif /* WOLFSPDM_NUVOTON */ + +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[16]; /* Up to 14 bytes for TCG format */ + byte plainBuf[WOLFSPDM_MAX_MSG_SIZE + 16]; + byte tag[WOLFSPDM_AEAD_TAG_SIZE]; + word32 plainBufSz; + word16 recordLen; + word32 hdrSz; + word32 aadSz; + int rc; + + if (ctx == NULL || plain == NULL || enc == NULL || encSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + /* 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: Rightmost 8 bytes (bytes 4-11) with 8-byte sequence number + */ + word16 appDataLen = (word16)plainSz; + + /* Run self-test before first encrypted message */ + if (ctx->reqSeqNum == 0) { + rc = wolfSPDM_AesGcmSelfTest(ctx); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "AES-GCM self-test FAILED: %d\n", rc); + return WOLFSPDM_E_CRYPTO_FAIL; + } + } + word16 unpadded = (word16)(2 + appDataLen); /* AppDataLength + SPDM msg */ + word16 padLen = (word16)((16 - (unpadded % 16)) % 16); /* Pad to 16-byte boundary */ + 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) { + WC_RNG rng; + if (wc_InitRng(&rng) == 0) { + wc_RNG_GenerateBlock(&rng, &plainBuf[unpadded], padLen); + wc_FreeRng(&rng); + } else { + /* Fallback to zeros if RNG fails */ + XMEMSET(&plainBuf[unpadded], 0, padLen); + } + } + + /* 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); + + 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 IV: BaseIV XOR sequence number (DSP0277) */ + wolfSPDM_BuildIV(iv, ctx->reqDataIv, ctx->reqSeqNum, + ctx->mode == WOLFSPDM_MODE_NUVOTON); + + rc = wc_AesGcmSetKey(&aes, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + /* 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) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + 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)); + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, + const byte* enc, word32 encSz, + byte* plain, word32* plainSz) +{ + Aes aes; + byte iv[WOLFSPDM_AEAD_IV_SIZE]; + byte aad[16]; + byte decrypted[WOLFSPDM_MAX_MSG_SIZE + 16]; + const byte* ciphertext; + const byte* tag; + word32 rspSessionId; + word16 rspSeqNum; + word16 rspLen; + word16 cipherLen; + word16 appDataLen; + word32 hdrSz; + word32 aadSz; + int rc; + + if (ctx == NULL || enc == NULL || plain == NULL || plainSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + /* Nuvoton TCG binding format per Rev 1.11 spec page 25: + * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes + * Encrypted: AppDataLength(2 LE) + SPDM message + RandomData padding + * MAC: 16 bytes + */ + word64 rspSeqNum64; + hdrSz = 14; + aadSz = 14; + + if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Parse header: SessionID(4) + SeqNum(8) + Length(2) */ + rspSessionId = SPDM_Get32LE(&enc[0]); + rspSeqNum64 = SPDM_Get64LE(&enc[4]); + rspLen = SPDM_Get16LE(&enc[12]); + rspSeqNum = (word16)(rspSeqNum64 & 0xFFFF); /* For debug output */ + + if (rspSessionId != ctx->sessionId) { + wolfSPDM_DebugPrint(ctx, "Session ID mismatch: 0x%08x != 0x%08x\n", + rspSessionId, ctx->sessionId); + return WOLFSPDM_E_SESSION_INVALID; + } + + /* Length field = ciphertext + MAC (per Nuvoton spec) */ + if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < hdrSz + rspLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + cipherLen = (word16)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + ciphertext = enc + hdrSz; + tag = enc + hdrSz + cipherLen; + + XMEMCPY(aad, enc, aadSz); + + /* Build IV: BaseIV XOR sequence number (DSP0277 1.2) */ + wolfSPDM_BuildIV(iv, ctx->rspDataIv, rspSeqNum64, 1); + + rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + 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); + return WOLFSPDM_E_DECRYPT_FAIL; + } + + /* Parse decrypted: AppDataLen (2 LE) || SPDM message || RandomData */ + appDataLen = SPDM_Get16LE(decrypted); + + if (cipherLen < (word32)(2 + appDataLen)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + if (*plainSz < appDataLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Copy SPDM message (no MCTP header to skip) */ + XMEMCPY(plain, &decrypted[2], appDataLen); + *plainSz = appDataLen; + } + else +#endif + { + /* MCTP format */ + hdrSz = 8; + aadSz = 8; + + 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; + } + + if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < (word32)(hdrSz + rspLen)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + cipherLen = (word16)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + 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, 0); + + rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + 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); + return WOLFSPDM_E_DECRYPT_FAIL; + } + + /* Parse decrypted: AppDataLen (2) || MCTP (1) || SPDM msg */ + appDataLen = SPDM_Get16LE(decrypted); + + if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Skip MCTP header, copy SPDM message */ + if (*plainSz < (word32)(appDataLen - 1)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + 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); + + return WOLFSPDM_SUCCESS; +} + +#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; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED && + ctx->state != WOLFSPDM_STATE_KEY_EX && + ctx->state != WOLFSPDM_STATE_FINISH) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + 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 (ctx->state != WOLFSPDM_STATE_CONNECTED && + ctx->state != WOLFSPDM_STATE_KEY_EX && + ctx->state != WOLFSPDM_STATE_FINISH) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + return wolfSPDM_DecryptInternal(ctx, enc, encSz, plain, plainSz); +} +#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]; + word32 encSz = sizeof(encBuf); + word32 rxSz = sizeof(rxBuf); + int rc; + + if (ctx == NULL || cmdPlain == NULL || rspPlain == NULL || rspSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wolfSPDM_EncryptInternal(ctx, cmdPlain, cmdSz, encBuf, &encSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + return wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); +} + +/* --- Application Data Transfer --- */ + +#ifndef WOLFSPDM_LEAN +int wolfSPDM_SendData(WOLFSPDM_CTX* ctx, const byte* data, word32 dataSz) +{ + byte encBuf[WOLFSPDM_MAX_MSG_SIZE + 48]; + word32 encSz = sizeof(encBuf); + 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; + } + + { + byte rxBuf[16]; + word32 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/spdm/src/spdm_session.c b/spdm/src/spdm_session.c new file mode 100644 index 00000000..32ab905d --- /dev/null +++ b/spdm/src/spdm_session.c @@ -0,0 +1,591 @@ +/* spdm_session.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" +#include + +/* 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, + wolfSPDM_BuildFn buildFn, wolfSPDM_ParseFn parseFn, + byte* txBuf, word32 txBufSz, byte* rxBuf, word32 rxBufSz) +{ + word32 txSz = txBufSz; + word32 rxSz = rxBufSz; + int rc; + + rc = buildFn(ctx, txBuf, &txSz); + if (rc != WOLFSPDM_SUCCESS) return rc; + + wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) return rc; + + wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); + + return parseFn(ctx, rxBuf, rxSz); +} + +/* Adapter: BuildGetVersion doesn't take ctx */ +static int wolfSPDM_BuildGetVersionAdapter(WOLFSPDM_CTX* ctx, byte* buf, + word32* bufSz) +{ + (void)ctx; + return wolfSPDM_BuildGetVersion(buf, bufSz); +} + +int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx) +{ + byte txBuf[8]; + 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 bytes */ + byte rxBuf[80]; /* ALGORITHMS: ~56 bytes with struct tables */ + int rc; + + 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). */ + { + int 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); + } + } + /* Non-fatal: challenge just won't work if this fails */ + } +#endif + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx) +{ + byte txBuf[8]; + byte rxBuf[256]; + 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; + int rc; + + while (remainderLen > 0) { + txSz = sizeof(txBuf); + rc = wolfSPDM_BuildGetCertificate(ctx, txBuf, &txSz, slotId, offset, 1024); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + rxSz = sizeof(rxBuf); + 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; + } + + 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. + * Needed by both measurement signature verification and challenge auth. + * Non-fatal: caller can still proceed, but signature ops will fail. */ + if (!ctx->flags.hasResponderPubKey) { + int keyRc = wolfSPDM_ExtractResponderPubKey(ctx); + if (keyRc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, + "Warning: Could not extract responder public key (%d)\n", keyRc); + } + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) +{ + byte txBuf[192]; /* KEY_EXCHANGE: ~158 bytes */ + byte rxBuf[384]; /* KEY_EXCHANGE_RSP: ~302 bytes */ + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + int rc; + + rc = wolfSPDM_BuildKeyExchange(ctx, txBuf, &txSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + + 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); +} + +int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) +{ + byte finishBuf[152]; /* 148 bytes max for mutual auth FINISH */ + byte encBuf[256]; /* Encrypted: hdr(14) + padded(160) + tag(16) = 190 max */ + 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) { + return rc; + } + + /* FINISH must be sent encrypted (HANDSHAKE_IN_THE_CLEAR not negotiated) */ + rc = wolfSPDM_EncryptInternal(ctx, finishBuf, finishSz, encBuf, &encSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "FINISH encrypt failed: %d\n", rc); + return rc; + } + + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "FINISH SendReceive failed: %d\n", rc); + return rc; + } + + /* Check if response is unencrypted SPDM message + * SPDM messages start with version byte (0x10-0x1F). + * Encrypted records start with session ID. */ + if (rxSz >= 2 && rxBuf[0] >= 0x10 && rxBuf[0] <= 0x1F) { + /* Unencrypted SPDM message - check for ERROR */ + if (rxBuf[1] == 0x7F) { /* SPDM_ERROR */ + wolfSPDM_DebugPrint(ctx, "FINISH: peer returned SPDM ERROR 0x%02x\n", + rxBuf[2]); + return WOLFSPDM_E_PEER_ERROR; + } + wolfSPDM_DebugPrint(ctx, "FINISH: unexpected response code 0x%02x\n", + rxBuf[1]); + return WOLFSPDM_E_PEER_ERROR; + } + + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "FINISH decrypt failed: %d\n", rc); + return rc; + } + + rc = wolfSPDM_ParseFinishRsp(ctx, decBuf, decSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* 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); + return rc; + } + return WOLFSPDM_SUCCESS; +} + +/* --- 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 rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Must be at least past algorithm negotiation */ + if (ctx->state < WOLFSPDM_STATE_ALGO) { + return WOLFSPDM_E_BAD_STATE; + } + + /* 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/receive: use secured exchange if session established, else cleartext */ + if (ctx->state == WOLFSPDM_STATE_CONNECTED) { + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + } + else { + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GET_MEASUREMENTS exchange failed: %d\n", rc); + return rc; + } + + /* 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) { + return WOLFSPDM_E_MEAS_SIG_FAIL; + } + + ctx->state = WOLFSPDM_STATE_MEASURED; + return WOLFSPDM_SUCCESS; /* Verified! */ + } +#else + (void)requestSignature; +#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ + + /* No signature requested or verification not compiled in */ + ctx->state = WOLFSPDM_STATE_MEASURED; + return WOLFSPDM_E_MEAS_NOT_VERIFIED; +} + +#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 */ + byte rxBuf[512]; /* CHALLENGE_AUTH: variable, up to ~300+ bytes */ + 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; + } + + /* 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]; + word32 txSz, rxSz; + 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"); + + { + byte encBuf[64]; + byte rawRxBuf[64]; + word32 encSz = sizeof(encBuf); + word32 rawRxSz = sizeof(rawRxBuf); + + /* Encrypt with current req key */ + rc = wolfSPDM_EncryptInternal(ctx, txBuf, txSz, encBuf, &encSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Send and receive raw (don't decrypt yet) */ + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rawRxBuf, &rawRxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: SendReceive failed: %d\n", rc); + return rc; + } + + /* 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); + return rc; + } + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + + /* Decrypt ACK with new rsp key */ + rxSz = sizeof(rxBuf); + rc = wolfSPDM_DecryptInternal(ctx, rawRxBuf, rawRxSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: ACK decrypt failed: %d\n", rc); + 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/spdm/src/spdm_transcript.c b/spdm/src/spdm_transcript.c new file mode 100644 index 00000000..0bf422cf --- /dev/null +++ b/spdm/src/spdm_transcript.c @@ -0,0 +1,132 @@ +/* spdm_transcript.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" +#include + +/* --- 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) + * TH2 = Hash(VCA || Ct || message_k || FINISH_header) */ + +void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return; + } + + 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) +{ + if (ctx == NULL || data == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->transcriptLen + len > WOLFSPDM_MAX_TRANSCRIPT) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(ctx->transcript + ctx->transcriptLen, data, len); + ctx->transcriptLen += len; + + wolfSPDM_DebugPrint(ctx, "Transcript: added %u bytes, total=%u\n", + len, ctx->transcriptLen); + + 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, + const byte* d3, word32 d3Sz) +{ + wc_Sha384 sha; + int rc; + + rc = wc_InitSha384(&sha); + if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; + if (d1 != NULL && d1Sz > 0) { + rc = wc_Sha384Update(&sha, d1, d1Sz); + if (rc != 0) { wc_Sha384Free(&sha); return WOLFSPDM_E_CRYPTO_FAIL; } + } + if (d2 != NULL && d2Sz > 0) { + rc = wc_Sha384Update(&sha, d2, d2Sz); + if (rc != 0) { wc_Sha384Free(&sha); return WOLFSPDM_E_CRYPTO_FAIL; } + } + if (d3 != NULL && d3Sz > 0) { + rc = wc_Sha384Update(&sha, d3, d3Sz); + if (rc != 0) { wc_Sha384Free(&sha); return WOLFSPDM_E_CRYPTO_FAIL; } + } + rc = wc_Sha384Final(&sha, out); + wc_Sha384Free(&sha); + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash) +{ + if (ctx == NULL || hash == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + return wolfSPDM_Sha384Hash(hash, ctx->transcript, ctx->transcriptLen, + 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/spdm/test/unit_test.c b/spdm/test/unit_test.c new file mode 100644 index 00000000..941cb041 --- /dev/null +++ b/spdm/test/unit_test.c @@ -0,0 +1,1048 @@ +/* unit_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * Unit tests for wolfSPDM library functions. + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include "../src/spdm_internal.h" +#include +#include +#include + +static int g_testsPassed = 0; +static int g_testsFailed = 0; + +#define TEST_ASSERT(cond, msg) do { \ + if (!(cond)) { \ + printf(" FAIL: %s (line %d)\n", msg, __LINE__); \ + g_testsFailed++; \ + return -1; \ + } \ +} while(0) + +#define TEST_PASS() do { \ + g_testsPassed++; \ + return 0; \ +} while(0) + +#define ASSERT_SUCCESS(expr) do { int _r = (expr); if (_r != 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) + +/* Test context setup/cleanup macros */ +#define TEST_CTX_SETUP() \ + WOLFSPDM_CTX ctxBuf; \ + WOLFSPDM_CTX* ctx = &ctxBuf; \ + wolfSPDM_Init(ctx) + +#define TEST_CTX_SETUP_V12() \ + TEST_CTX_SETUP(); \ + ctx->spdmVersion = SPDM_VERSION_12 + +#define TEST_CTX_FREE() \ + wolfSPDM_Free(ctx) + +/* Dummy I/O callback for testing */ +static int dummy_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, void* userCtx) +{ + (void)ctx; (void)txBuf; (void)txSz; + (void)rxBuf; (void)rxSz; (void)userCtx; + return -1; +} + +/* ========================================================================== */ +/* Context Tests */ +/* ========================================================================== */ + +#ifdef WOLFSPDM_DYNAMIC_MEMORY +static int test_context_new_free(void) +{ + WOLFSPDM_CTX* ctx; + + printf("test_context_new_free...\n"); + + ctx = wolfSPDM_New(); + TEST_ASSERT(ctx != NULL, "wolfSPDM_New returned NULL"); + ASSERT_EQ(ctx->state, WOLFSPDM_STATE_INIT, "Initial state wrong"); + ASSERT_EQ(ctx->flags.initialized, 1, "Should be initialized by New()"); + + wolfSPDM_Free(ctx); + wolfSPDM_Free(NULL); /* Should not crash */ + + TEST_PASS(); +} +#endif /* WOLFSPDM_DYNAMIC_MEMORY */ + +static int test_context_init(void) +{ + TEST_CTX_SETUP(); + + 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(); +} + +static int test_context_static_alloc(void) +{ + byte buffer[sizeof(WOLFSPDM_CTX) + 64]; + WOLFSPDM_CTX* ctx = (WOLFSPDM_CTX*)buffer; + + 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_SUCCESS(wolfSPDM_InitStatic(ctx, sizeof(buffer))); + ASSERT_EQ(ctx->flags.initialized, 1, "Static ctx not initialized"); + + wolfSPDM_Free(ctx); + TEST_PASS(); +} + +static int test_context_set_io(void) +{ + int dummy = 42; + TEST_CTX_SETUP(); + + printf("test_context_set_io...\n"); + + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ========================================================================== */ +/* Transcript Tests */ +/* ========================================================================== */ + +static int test_transcript_add_reset(void) +{ + byte data1[] = {0x01, 0x02, 0x03, 0x04}; + byte data2[] = {0x05, 0x06, 0x07, 0x08}; + TEST_CTX_SETUP(); + + printf("test_transcript_add_reset...\n"); + ASSERT_EQ(ctx->transcriptLen, 0, "Transcript should start empty"); + + ASSERT_SUCCESS(wolfSPDM_TranscriptAdd(ctx, data1, sizeof(data1))); + ASSERT_EQ(ctx->transcriptLen, 4, "Length should be 4"); + ASSERT_EQ(memcmp(ctx->transcript, data1, 4), 0, "Data mismatch"); + + ASSERT_SUCCESS(wolfSPDM_TranscriptAdd(ctx, data2, sizeof(data2))); + ASSERT_EQ(ctx->transcriptLen, 8, "Length should be 8"); + ASSERT_EQ(memcmp(ctx->transcript + 4, data2, 4), 0, "Data2 mismatch"); + + wolfSPDM_TranscriptReset(ctx); + ASSERT_EQ(ctx->transcriptLen, 0, "Reset should clear length"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_transcript_hash(void) +{ + byte data[] = "test data for hashing"; + byte hash[WOLFSPDM_HASH_SIZE]; + byte zeros[WOLFSPDM_HASH_SIZE]; + TEST_CTX_SETUP(); + + printf("test_transcript_hash...\n"); + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ========================================================================== */ +/* Crypto Tests */ +/* ========================================================================== */ + +static int test_random_generation(void) +{ + byte buf1[32], buf2[32]; + TEST_CTX_SETUP(); + + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_ephemeral_key_generation(void) +{ + byte pubKeyX[WOLFSPDM_ECC_KEY_SIZE]; + byte pubKeyY[WOLFSPDM_ECC_KEY_SIZE]; + byte zeros[WOLFSPDM_ECC_KEY_SIZE]; + word32 xSz = sizeof(pubKeyX); + word32 ySz = sizeof(pubKeyY); + TEST_CTX_SETUP(); + + printf("test_ephemeral_key_generation...\n"); + ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); + ASSERT_EQ(ctx->flags.ephemeralKeyInit, 1, "Key not marked initialized"); + ASSERT_SUCCESS(wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &xSz, pubKeyY, &ySz)); + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ========================================================================== */ +/* KDF Tests */ +/* ========================================================================== */ + +static int test_hkdf_expand_label(void) +{ + byte secret[48]; + byte output[32]; + byte context[48]; + byte zeros[32]; + + printf("test_hkdf_expand_label...\n"); + + memset(secret, 0x5A, sizeof(secret)); + memset(context, 0x00, sizeof(context)); + + 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"); + + TEST_PASS(); +} + +static int test_compute_verify_data(void) +{ + byte finishedKey[WOLFSPDM_HASH_SIZE]; + byte thHash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + byte zeros[WOLFSPDM_HASH_SIZE]; + + printf("test_compute_verify_data...\n"); + + memset(finishedKey, 0xAB, sizeof(finishedKey)); + memset(thHash, 0xCD, sizeof(thHash)); + + 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"); + + TEST_PASS(); +} + +/* ========================================================================== */ +/* Message Builder Tests */ +/* ========================================================================== */ + +static int test_build_get_version(void) +{ + byte buf[16]; + word32 bufSz = sizeof(buf); + + printf("test_build_get_version...\n"); + + 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"); + + TEST_PASS(); +} + +static int test_build_get_capabilities(void) +{ + byte buf[32]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_negotiate_algorithms(void) +{ + byte buf[64]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + + 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_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_get_digests(void) +{ + byte buf[16]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_get_certificate(void) +{ + byte buf[16]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_end_session(void) +{ + byte buf[16]; + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ========================================================================== */ +/* Error Check Tests */ +/* ========================================================================== */ + +static int test_check_error(void) +{ + byte errorMsg[] = {0x12, SPDM_ERROR, 0x06, 0x00}; + byte okMsg[] = {0x12, SPDM_VERSION, 0x00, 0x00}; + int errorCode = 0; + + 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_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"); + + TEST_PASS(); +} + +/* ========================================================================== */ +/* Measurement Tests */ +/* ========================================================================== */ + +#ifndef NO_WOLFSPDM_MEAS + +static int test_build_get_measurements(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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_measurement_accessors(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)); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_parse_measurements(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)); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +#ifndef NO_WOLFSPDM_MEAS_VERIFY + +static int test_measurement_sig_verification(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); + int rc; + 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); + TEST_ASSERT(rc == 0, "wc_ecc_init responderPubKey failed"); + + /* Export/import just the public key */ + { + byte pubDer[256]; + word32 pubDerSz = sizeof(pubDer); + word32 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, + 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) */ + { + static const char 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) + byte signMsg[200]; + word32 signMsgLen = 0; + int i; + + /* 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], 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) */ + { + byte sigRaw[WOLFSPDM_ECC_SIG_SIZE]; + 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_FREE(); + TEST_PASS(); +} + +#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ +#endif /* !NO_WOLFSPDM_MEAS */ + +/* ========================================================================== */ +/* Certificate Chain Validation Tests */ +/* ========================================================================== */ + +static int test_set_trusted_cas(void) +{ + byte fakeCa[] = {0x30, 0x82, 0x01, 0x00, 0xAA, 0xBB, 0xCC, 0xDD}; + TEST_CTX_SETUP(); + + 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_CTX_FREE(); + TEST_PASS(); +} + +static int test_validate_cert_chain_no_cas(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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ========================================================================== */ +/* Challenge Tests */ +/* ========================================================================== */ + +#ifndef NO_WOLFSPDM_CHALLENGE + +static int test_build_challenge(void) +{ + byte buf[64]; + byte zeros[32]; + word32 bufSz; + TEST_CTX_SETUP_V12(); + + printf("test_build_challenge...\n"); + + 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"); + + /* Buffer too small */ + 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_CTX_FREE(); + TEST_PASS(); +} + +static int test_parse_challenge_auth(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; + + 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); + + ASSERT_SUCCESS(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset)); + ASSERT_EQ(sigOffset, 86, "Signature offset should be 86"); + + /* 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; + + /* CertChainHash mismatch */ + ctx->certChainHash[0] = 0x00; + ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset), + WOLFSPDM_E_CHALLENGE, "Hash mismatch should fail"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +#endif /* !NO_WOLFSPDM_CHALLENGE */ + +/* ========================================================================== */ +/* Heartbeat Tests */ +/* ========================================================================== */ + +static int test_build_heartbeat(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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_parse_heartbeat_ack(void) +{ + byte ack[] = {0x12, SPDM_HEARTBEAT_ACK, 0x00, 0x00}; + byte err[] = {0x12, SPDM_ERROR, 0x01, 0x00}; + TEST_CTX_SETUP(); + + 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_CTX_FREE(); + TEST_PASS(); +} + +static int test_heartbeat_state_check(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"); + + 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(); + + 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"); + + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_parse_key_update_ack(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"); + + 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); + + /* 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(); +} + +static int test_key_update_state_check(void) +{ + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ========================================================================== */ +/* Session State Tests */ +/* ========================================================================== */ + +static int test_session_state(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"); + + /* 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(); + TEST_PASS(); +} + +/* ========================================================================== */ +/* Main */ +/* ========================================================================== */ + +int main(void) +{ + printf("===========================================\n"); + printf("wolfSPDM Unit Tests\n"); + printf("===========================================\n\n"); + + /* Context tests */ +#ifdef WOLFSPDM_DYNAMIC_MEMORY + test_context_new_free(); +#endif + test_context_init(); + test_context_static_alloc(); + test_context_set_io(); + + /* Transcript tests */ + test_transcript_add_reset(); + test_transcript_hash(); + test_certchain_hash(); + + /* Crypto tests */ + test_random_generation(); + test_ephemeral_key_generation(); + + /* KDF tests */ + test_hkdf_expand_label(); + test_compute_verify_data(); + + /* Message builder tests */ + test_build_get_version(); + test_build_get_capabilities(); + test_build_negotiate_algorithms(); + test_build_get_digests(); + test_build_get_certificate(); + test_build_end_session(); + + /* Error tests */ + test_check_error(); + test_error_strings(); + + /* Measurement tests */ +#ifndef NO_WOLFSPDM_MEAS + test_build_get_measurements(); + 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(); +#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(); + + /* Session state tests */ + test_session_state(); + + printf("\n===========================================\n"); + printf("Results: %d passed, %d failed\n", g_testsPassed, g_testsFailed); + printf("===========================================\n"); + + return (g_testsFailed == 0) ? 0 : 1; +} diff --git a/spdm/wolfspdm/spdm.h b/spdm/wolfspdm/spdm.h new file mode 100644 index 00000000..7a419374 --- /dev/null +++ b/spdm/wolfspdm/spdm.h @@ -0,0 +1,598 @@ +/* spdm.h + * + * 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 + */ + +#ifndef WOLFSPDM_SPDM_H +#define WOLFSPDM_SPDM_H + +/* Include build options (WOLFSPDM_DYNAMIC_MEMORY, WOLFSPDM_NUVOTON, 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 + +/* --- Protocol Mode Selection --- + * + * wolfSPDM supports two protocol modes: + * + * WOLFSPDM_MODE_STANDARD (default): + * 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 + * + * WOLFSPDM_MODE_NUVOTON (requires --enable-nuvoton): + * Nuvoton TPM-specific protocol with TCG binding headers. + * Flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH + * Use with: Nuvoton NPCT75x TPMs (FW 7.2+) */ + +typedef enum { + WOLFSPDM_MODE_STANDARD = 0, /* Standard SPDM 1.2 (default) */ + WOLFSPDM_MODE_NUVOTON = 1, /* Nuvoton TCG binding + vendor commands */ +} WOLFSPDM_MODE; + +/* --- 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 + * - 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). + * Actual struct size: ~31.3 KB (with measurements) / ~29.9 KB (NO_WOLFSPDM_MEAS). + * Rounded up to 32 KB for platform alignment. + * wolfSPDM_InitStatic() verifies at runtime that the provided buffer + * is large enough; returns WOLFSPDM_E_BUFFER_SMALL if not. */ +#define WOLFSPDM_CTX_STATIC_SIZE 32768 /* 32KB - fits CTX with cert validation + challenge + key update fields */ + +/* Forward declaration */ +struct WOLFSPDM_CTX; +typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; + +/* Include Nuvoton support if enabled (must be after WOLFSPDM_CTX forward declaration) */ +#ifdef WOLFSPDM_NUVOTON + #include +#endif + +/* --- 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 */ +typedef int (*WOLFSPDM_IO_CB)( + WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + 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. + */ +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 protocol mode (standard SPDM or Nuvoton-specific). + * Must be called before wolfSPDM_Connect(). + * + * @param ctx The wolfSPDM context. + * @param mode WOLFSPDM_MODE_STANDARD or WOLFSPDM_MODE_NUVOTON. + * @return WOLFSPDM_SUCCESS or negative error code. + * Returns WOLFSPDM_E_INVALID_ARG if NUVOTON mode requested + * but wolfSPDM was not built with --enable-nuvoton. + */ +WOLFSPDM_API int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode); + +/** + * Get the current protocol mode. + * + * @param ctx The wolfSPDM context. + * @return Current mode (WOLFSPDM_MODE_STANDARD or WOLFSPDM_MODE_NUVOTON). + */ +WOLFSPDM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); + +/** + * Set the responder's public key for certificate-less operation. + * Used when the responder doesn't send a certificate chain (e.g., Nuvoton TPM). + * + * @param ctx The wolfSPDM context. + * @param pubKey Raw public key bytes (96 bytes for P-384: X||Y). + * @param pubKeySz Size of public key (must be 96 for P-384). + * @return WOLFSPDM_SUCCESS or negative error code. + */ +WOLFSPDM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz); + +/** + * Set the requester's key pair for mutual authentication. + * Optional - only needed if responder requires mutual auth. + * + * @param ctx The wolfSPDM context. + * @param privKey Raw private key bytes (48 bytes for P-384). + * @param privKeySz Size of private key. + * @param pubKey Raw public key bytes (96 bytes for P-384: X||Y). + * @param pubKeySz Size of public key. + * @return WOLFSPDM_SUCCESS or negative error code. + */ +WOLFSPDM_API int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, + const byte* privKey, word32 privKeySz, + const byte* pubKey, word32 pubKeySz); + +/* --- 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. + */ +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. + */ +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. + */ +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_E_MEAS_NOT_VERIFIED. Measurements are informational + * only and should not be used for security-critical decisions. + * + * If compiled with NO_WOLFSPDM_MEAS_VERIFY, signature verification is + * disabled and returns WOLFSPDM_E_MEAS_NOT_VERIFIED regardless of + * requestSignature (signature bytes are still captured in the context). + * + * 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 (verified), WOLFSPDM_E_MEAS_NOT_VERIFIED (unsigned), + * WOLFSPDM_E_MEAS_SIG_FAIL (sig invalid), or negative error code. + */ +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. + * + * @param ctx The wolfSPDM context. + * @return Session ID (combined reqSessionId | rspSessionId << 16), or 0 if not connected. + */ +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_NUVOTON +/** + * Get the connection handle (Nuvoton TCG binding). + * + * @param ctx The wolfSPDM context. + * @return Connection handle value. + */ +WOLFSPDM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); + +/** + * Get the FIPS indicator (Nuvoton TCG binding). + * + * @param ctx The wolfSPDM context. + * @return FIPS indicator value. + */ +WOLFSPDM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); +#endif + +/* --- Certificate Chain Validation --- */ + +/** + * Load trusted root CA certificates for certificate chain validation. + * When set, wolfSPDM_Connect() will validate the responder's certificate + * chain against these CAs. Without this, only the public key is extracted. + * + * @param ctx The wolfSPDM context. + * @param derCerts DER-encoded CA certificate(s) (concatenated if multiple). + * @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 --- */ + +/** + * Enable or disable debug output. + * + * @param ctx The wolfSPDM context. + * @param enable Non-zero to enable, 0 to disable. + */ +WOLFSPDM_API void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_SPDM_H */ diff --git a/spdm/wolfspdm/spdm_error.h b/spdm/wolfspdm/spdm_error.h new file mode 100644 index 00000000..ec6317eb --- /dev/null +++ b/spdm/wolfspdm/spdm_error.h @@ -0,0 +1,67 @@ +/* spdm_error.h + * + * 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 + */ + +#ifndef WOLFSPDM_ERROR_H +#define WOLFSPDM_ERROR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* wolfSPDM Error Codes */ +enum WOLFSPDM_ERROR { + WOLFSPDM_SUCCESS = 0, /* Operation successful */ + WOLFSPDM_E_INVALID_ARG = -1, /* Invalid argument provided */ + WOLFSPDM_E_BUFFER_SMALL = -2, /* Buffer too small for operation */ + WOLFSPDM_E_BAD_STATE = -3, /* Invalid state for operation */ + WOLFSPDM_E_VERSION_MISMATCH = -4, /* SPDM version negotiation failed */ + WOLFSPDM_E_CRYPTO_FAIL = -5, /* Cryptographic operation failed */ + WOLFSPDM_E_BAD_SIGNATURE = -6, /* Signature verification failed */ + WOLFSPDM_E_BAD_HMAC = -7, /* HMAC verification failed */ + WOLFSPDM_E_IO_FAIL = -8, /* I/O callback failed */ + WOLFSPDM_E_TIMEOUT = -9, /* Operation timed out */ + WOLFSPDM_E_PEER_ERROR = -10, /* Responder sent ERROR message */ + WOLFSPDM_E_DECRYPT_FAIL = -11, /* AEAD decryption/tag verification failed */ + WOLFSPDM_E_SEQUENCE = -12, /* Sequence number 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 */ +}; + +/* Get human-readable error string */ +WOLFSPDM_API const char* wolfSPDM_GetErrorString(int error); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_ERROR_H */ diff --git a/spdm/wolfspdm/spdm_nuvoton.h b/spdm/wolfspdm/spdm_nuvoton.h new file mode 100644 index 00000000..d0efb87b --- /dev/null +++ b/spdm/wolfspdm/spdm_nuvoton.h @@ -0,0 +1,319 @@ +/* spdm_nuvoton.h + * + * 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 + */ + +/* Nuvoton TPM SPDM Support + * + * This header provides Nuvoton-specific SPDM functionality: + * - TCG SPDM Binding message framing (per TCG SPDM Binding Spec v1.0) + * - Nuvoton vendor-defined commands (GET_PUBK, GIVE_PUB, GET_STS_, SPDMONLY) + * - Nuvoton SPDM handshake flow (differs from standard SPDM) + * + * 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 + +/* Note: This header is included from spdm.h after WOLFSPDM_CTX forward declaration. + * DO NOT include spdm.h here to avoid circular dependency. + * Include spdm_types.h for basic types only. */ +#include + +#ifdef WOLFSPDM_NUVOTON + +#ifdef __cplusplus +extern "C" { +#endif + +/* --- TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) --- */ + +/* 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 */ +#define WOLFSPDM_TCG_SECURED_HDR_SIZE 14 /* Session record header (4+8+2) */ + +/* FIPS Service Indicator */ +#define WOLFSPDM_FIPS_NON_FIPS 0x00 +#define WOLFSPDM_FIPS_APPROVED 0x01 + +/* --- Nuvoton Vendor-Defined Command Codes --- */ + +/* 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 */ +#define WOLFSPDM_VDCODE_GET_PUBK "GET_PUBK" /* Get TPM's SPDM-Identity key */ +#define WOLFSPDM_VDCODE_GIVE_PUB "GIVE_PUB" /* Give host's SPDM-Identity key */ +#define WOLFSPDM_VDCODE_GET_STS "GET_STS_" /* Get SPDM status */ +#define WOLFSPDM_VDCODE_SPDMONLY "SPDMONLY" /* Lock/unlock SPDM-only mode */ + +/* 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; /* WOLFSPDM_TCG_TAG_CLEAR (0x8101) */ + word32 size; /* Total message size including header */ + word32 connectionHandle; /* Connection handle (0 for single connection) */ + word16 fipsIndicator; /* FIPS service indicator */ + word32 reserved; /* Must be 0 */ +} WOLFSPDM_TCG_CLEAR_HDR; + +/* Secured message header (tag 0x8201) + * Layout: tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + + * fipsIndicator(2/BE) + reserved(4) = 16 bytes + * Followed by SPDM secured record (per DSP0277, all LE): + * sessionId(4/LE) + seqNum(8/LE) + length(2/LE) + encData + MAC(16) */ +typedef struct WOLFSPDM_TCG_SECURED_HDR { + word16 tag; /* WOLFSPDM_TCG_TAG_SECURED (0x8201) */ + word32 size; /* Total message size including header */ + word32 connectionHandle; /* Connection handle */ + word16 fipsIndicator; /* FIPS service indicator */ + word32 reserved; /* Must be 0 */ +} WOLFSPDM_TCG_SECURED_HDR; + +/* --- Nuvoton SPDM Status --- */ + +typedef struct WOLFSPDM_NUVOTON_STATUS { + int spdmEnabled; /* SPDM is enabled on the TPM */ + int sessionActive; /* An SPDM session is currently active */ + int spdmOnlyLocked; /* SPDM-only mode is locked */ + word32 fwVersion; /* TPM firmware version */ + byte specVersionMajor; /* SPDM spec version major (0 for 1.x) */ + byte specVersionMinor; /* SPDM spec version minor (1=1.1, 3=1.3) */ +} WOLFSPDM_NUVOTON_STATUS; + +/* --- TCG Binding Message Framing Functions --- */ + +/** + * Build a TCG SPDM clear message (tag 0x8101). + * Wraps an SPDM payload in the TCG binding header format. + * + * @param ctx wolfSPDM context + * @param spdmPayload SPDM message payload + * @param spdmPayloadSz Size of SPDM payload + * @param outBuf Output buffer for framed message + * @param outBufSz Size of output buffer + * @return Total message size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_BuildTcgClearMessage( + WOLFSPDM_CTX* ctx, + const byte* spdmPayload, word32 spdmPayloadSz, + byte* outBuf, word32 outBufSz); + +/** + * Parse a TCG SPDM clear message (tag 0x8101). + * Extracts the SPDM payload from the TCG binding header. + * + * @param inBuf Input buffer containing framed message + * @param inBufSz Size of input buffer + * @param spdmPayload Output buffer for SPDM payload + * @param spdmPayloadSz [in] Size of output buffer, [out] Actual payload size + * @param hdr Optional: receives parsed header fields + * @return Payload size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_ParseTcgClearMessage( + const byte* inBuf, word32 inBufSz, + byte* spdmPayload, word32* spdmPayloadSz, + WOLFSPDM_TCG_CLEAR_HDR* hdr); + +/** + * Build a TCG SPDM secured message (tag 0x8201). + * Wraps encrypted payload with session ID, sequence number, and MAC. + * + * @param ctx wolfSPDM context + * @param encPayload Encrypted payload (ciphertext) + * @param encPayloadSz Size of encrypted payload + * @param mac Authentication tag (AES-GCM tag) + * @param macSz Size of MAC (must be 16) + * @param outBuf Output buffer for framed message + * @param outBufSz Size of output buffer + * @return Total message size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_BuildTcgSecuredMessage( + WOLFSPDM_CTX* ctx, + const byte* encPayload, word32 encPayloadSz, + const byte* mac, word32 macSz, + byte* outBuf, word32 outBufSz); + +/** + * Parse a TCG SPDM secured message (tag 0x8201). + * Extracts session ID, sequence number, encrypted payload, and MAC. + * + * @param inBuf Input buffer containing framed message + * @param inBufSz Size of input buffer + * @param sessionId Receives session ID + * @param seqNum Receives sequence number + * @param encPayload Output buffer for encrypted payload + * @param encPayloadSz [in] Size of output buffer, [out] Actual payload size + * @param mac Output buffer for MAC + * @param macSz [in] Size of MAC buffer, [out] Actual MAC size + * @param hdr Optional: receives parsed header fields + * @return Payload size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_ParseTcgSecuredMessage( + const byte* inBuf, word32 inBufSz, + word32* sessionId, word64* seqNum, + byte* encPayload, word32* encPayloadSz, + byte* mac, word32* macSz, + WOLFSPDM_TCG_SECURED_HDR* hdr); + +/* --- Vendor-Defined Message Helpers --- */ + +/** + * Build an SPDM VENDOR_DEFINED_REQUEST message. + * + * @param vdCode 8-byte ASCII vendor code (e.g., "GET_PUBK") + * @param payload Vendor-specific payload (may be NULL) + * @param payloadSz Size of payload + * @param outBuf Output buffer for message + * @param outBufSz Size of output buffer + * @return Message size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_BuildVendorDefined( + const char* vdCode, + const byte* payload, word32 payloadSz, + byte* outBuf, word32 outBufSz); + +/** + * Parse an SPDM VENDOR_DEFINED_RESPONSE message. + * + * @param inBuf Input buffer containing message + * @param inBufSz Size of input buffer + * @param vdCode Receives 8-byte vendor code (buffer must be 9+ bytes) + * @param payload Output buffer for payload + * @param payloadSz [in] Size of output buffer, [out] Actual payload size + * @return Payload size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_ParseVendorDefined( + const byte* inBuf, word32 inBufSz, + char* vdCode, + byte* payload, word32* payloadSz); + +/* --- Nuvoton-Specific SPDM Functions --- */ + +/** + * Get the TPM's SPDM-Identity public key (GET_PUBK vendor command). + * This is sent as a clear (unencrypted) SPDM message before key exchange. + * + * @param ctx wolfSPDM context + * @param pubKey Output buffer for public key (raw X||Y, 96 bytes for P-384) + * @param pubKeySz [in] Size of buffer, [out] Actual key size + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_Nuvoton_GetPubKey( + WOLFSPDM_CTX* ctx, + byte* pubKey, word32* pubKeySz); + +/** + * Give the host's SPDM-Identity public key to the TPM (GIVE_PUB vendor command). + * This is sent as a secured (encrypted) message after key exchange. + * + * @param ctx wolfSPDM context + * @param pubKey Host's public key (TPMT_PUBLIC format, ~120 bytes) + * @param pubKeySz Size of public key + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_Nuvoton_GivePubKey( + WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz); + +/** + * Get SPDM status from the TPM (GET_STS_ vendor command). + * Can be called before or after session establishment. + * + * @param ctx wolfSPDM context + * @param status Receives SPDM status information + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_Nuvoton_GetStatus( + WOLFSPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status); + +/** + * Lock or unlock SPDM-only mode (SPDMONLY vendor command). + * When locked, the TPM only accepts commands over SPDM. + * + * @param ctx wolfSPDM context + * @param lock WOLFSPDM_SPDMONLY_LOCK (1) or WOLFSPDM_SPDMONLY_UNLOCK (0) + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_Nuvoton_SetOnlyMode( + WOLFSPDM_CTX* ctx, + int lock); + +/** + * Set the requester's SPDM-Identity public key in TPMT_PUBLIC format. + * Required for GIVE_PUB step in Nuvoton handshake. + * + * @param ctx wolfSPDM context + * @param tpmtPub Public key in TPMT_PUBLIC format (~120 bytes for P-384) + * @param tpmtPubSz Size of TPMT_PUBLIC + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz); + +/** + * Perform Nuvoton-specific SPDM connection. + * Uses the Nuvoton handshake flow: + * GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH + * + * This is called internally by wolfSPDM_Connect() when mode is NUVOTON. + * + * @param ctx wolfSPDM context + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx); + +/* --- Nuvoton Context Fields --- */ + +/* These fields are added to WOLFSPDM_CTX when WOLFSPDM_NUVOTON is defined */ + +/* Connection handle for TCG binding (usually 0) */ +#define WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT 0 + +/* FIPS indicator for TCG binding */ +#define WOLFSPDM_NUVOTON_FIPS_DEFAULT WOLFSPDM_FIPS_NON_FIPS + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFSPDM_NUVOTON_H */ diff --git a/spdm/wolfspdm/spdm_types.h b/spdm/wolfspdm/spdm_types.h new file mode 100644 index 00000000..59e632c4 --- /dev/null +++ b/spdm/wolfspdm/spdm_types.h @@ -0,0 +1,254 @@ +/* spdm_types.h + * + * 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 + */ + +#ifndef WOLFSPDM_TYPES_H +#define WOLFSPDM_TYPES_H + +/* wolfSSL options MUST be included first */ +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include + +/* Visibility: when built as part of wolfTPM, use WOLFTPM_API for export */ +#ifdef BUILDING_WOLFTPM + #include + #define WOLFSPDM_API WOLFTPM_API +#else + #ifndef WOLFSPDM_API + #define WOLFSPDM_API + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Include wolfSSL types */ +#ifndef WOLFSSL_TYPES + #include +#endif + +/* --- 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 */ +#define SPDM_VERSION_12 0x12 /* SPDM 1.2 */ +#define SPDM_VERSION_13 0x13 /* SPDM 1.3 */ + +/* SPDM Message Header Size */ +#define SPDM_HEADER_SIZE 4 /* Version + Code + Param1 + Param2 */ + +/* SPDM Request Codes (sent by requester) */ +#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 +#define SPDM_END_SESSION 0xEA +#define SPDM_VENDOR_DEFINED_REQUEST 0xFE +#define SPDM_VENDOR_DEFINED 0xFF + +/* SPDM Response Codes (sent by responder) */ +#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 +#define SPDM_END_SESSION_ACK 0x6A +#define SPDM_VENDOR_DEFINED_RSP 0x7E +#define SPDM_ERROR 0x7F + +/* SPDM Error Codes (in Param1 of ERROR response) */ +#define SPDM_ERROR_INVALID_REQUEST 0x01 +#define SPDM_ERROR_BUSY 0x03 +#define SPDM_ERROR_UNEXPECTED_REQUEST 0x04 +#define SPDM_ERROR_UNSPECIFIED 0x05 +#define SPDM_ERROR_DECRYPT_ERROR 0x06 +#define SPDM_ERROR_UNSUPPORTED_REQUEST 0x07 +#define SPDM_ERROR_REQUEST_IN_FLIGHT 0x08 +#define SPDM_ERROR_INVALID_RESPONSE 0x09 +#define SPDM_ERROR_SESSION_LIMIT 0x0A +#define SPDM_ERROR_SESSION_REQUIRED 0x0B +#define SPDM_ERROR_RESET_REQUIRED 0x0C +#define SPDM_ERROR_RESPONSE_TOO_LARGE 0x0D +#define SPDM_ERROR_REQUEST_TOO_LARGE 0x0E +#define SPDM_ERROR_LARGE_RESPONSE 0x0F +#define SPDM_ERROR_MSG_LOST 0x10 +#define SPDM_ERROR_MAJOR_VERSION_MISMATCH 0x41 +#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 */ + +/* DHE (Diffie-Hellman Ephemeral) Algorithms */ +#define SPDM_DHE_ALGO_SECP384R1 0x0010 /* secp384r1 */ + +/* 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 */ + +/* Algorithm Set B Fixed Parameters */ +#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 */ +#define WOLFSPDM_ECC_SIG_SIZE (2 * WOLFSPDM_ECC_KEY_SIZE) /* ECDSA r||s */ +#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 */ + +/* --- Capability Flags (per DSP0274) --- */ + +/* 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 */ + +/* Default requester capabilities for Algorithm Set B session */ +#define WOLFSPDM_DEFAULT_REQ_CAPS (SPDM_CAP_CERT_CAP | SPDM_CAP_CHAL_CAP | \ + SPDM_CAP_ENCRYPT_CAP | SPDM_CAP_MAC_CAP | \ + SPDM_CAP_KEY_EX_CAP | SPDM_CAP_HBEAT_CAP | \ + SPDM_CAP_KEY_UPD_CAP) + +/* --- Buffer/Message Size Limits --- */ + +#define WOLFSPDM_MAX_MSG_SIZE 4096 /* Maximum SPDM message size */ +#define WOLFSPDM_MAX_CERT_CHAIN 4096 /* Maximum certificate chain size */ +#define WOLFSPDM_MAX_TRANSCRIPT 4096 /* Maximum transcript buffer */ +#define WOLFSPDM_RANDOM_SIZE 32 /* Random data in KEY_EXCHANGE */ + +/* --- MCTP Transport Constants (for TCP/socket transport) --- */ + +#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 +#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 + +/* 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 */ +#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 + +#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" + +/* 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 + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_TYPES_H */ diff --git a/src/include.am b/src/include.am index 4714939b..a1f25039 100644 --- a/src/include.am +++ b/src/include.am @@ -20,6 +20,10 @@ if BUILD_WINAPI src_libwolftpm_la_SOURCES += src/tpm2_winapi.c src_libwolftpm_la_LIBADD = -ltbs endif +if BUILD_SPDM +# SPDM support using wolfSPDM library +src_libwolftpm_la_SOURCES += src/tpm2_spdm.c +endif src_libwolftpm_la_CFLAGS = $(src_libwolftpm_la_EXTRAS) -DBUILDING_WOLFTPM $(AM_CFLAGS) src_libwolftpm_la_CPPFLAGS = -DBUILDING_WOLFTPM $(AM_CPPFLAGS) diff --git a/src/tpm2.c b/src/tpm2.c index dbd3293c..cc4645b6 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -30,6 +30,9 @@ #include #include #include +#ifdef WOLFTPM_SPDM +#include +#endif #include @@ -450,7 +453,36 @@ static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet, packet->pos = cmdSz; /* submit command and wait for response */ - rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); +#ifdef WOLFTPM_SPDM + /* If SPDM session is active, wrap command through SPDM transport */ + if (ctx->spdmCtx != NULL) { + WOLFTPM2_SPDM_CTX* spdmCtx = (WOLFTPM2_SPDM_CTX*)ctx->spdmCtx; + if (spdmCtx->spdmCtx != NULL && + wolfSPDM_IsConnected(spdmCtx->spdmCtx)) { + byte tpmResp[WOLFSPDM_MAX_MSG_SIZE]; + word32 tpmRespSz = sizeof(tpmResp); + + rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, + packet->buf, packet->pos, tpmResp, &tpmRespSz); + if (rc != 0) + return rc; + + if (tpmRespSz > MAX_RESPONSE_SIZE) + return TPM_RC_SIZE; + XMEMCPY(packet->buf, tpmResp, tpmRespSz); + packet->pos = 0; + packet->size = tpmRespSz; + rc = TPM_RC_SUCCESS; + } + else { + rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); + } + } + else +#endif /* WOLFTPM_SPDM */ + { + rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); + } if (rc != 0) return rc; @@ -480,6 +512,37 @@ static TPM_RC TPM2_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) if (ctx == NULL || packet == NULL) return BAD_FUNC_ARG; +#ifdef WOLFTPM_SPDM + /* If SPDM session is active, wrap command through SPDM transport */ + if (ctx->spdmCtx != NULL) { + WOLFTPM2_SPDM_CTX* spdmCtx = (WOLFTPM2_SPDM_CTX*)ctx->spdmCtx; + if (spdmCtx->spdmCtx != NULL && + wolfSPDM_IsConnected(spdmCtx->spdmCtx)) { + byte tpmResp[WOLFSPDM_MAX_MSG_SIZE]; + word32 tpmRespSz = sizeof(tpmResp); + + /* Use wolfSPDM to encrypt, send, receive, and decrypt */ + rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, + packet->buf, packet->pos, tpmResp, &tpmRespSz); + if (rc != 0) { + return rc; + } + + /* Copy TPM response back into packet buffer. + * Note: packet->buf is a pointer so sizeof gives pointer size, + * use MAX_RESPONSE_SIZE for the actual buffer capacity. */ + if (tpmRespSz > MAX_RESPONSE_SIZE) { + return TPM_RC_SIZE; + } + XMEMCPY(packet->buf, tpmResp, tpmRespSz); + packet->pos = 0; + packet->size = tpmRespSz; + + return TPM2_Packet_Parse(TPM_RC_SUCCESS, packet); + } + } +#endif /* WOLFTPM_SPDM */ + /* submit command and wait for response */ rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); if (rc != 0) @@ -622,6 +685,61 @@ TPM_RC TPM2_SetHalIoCb(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx) return rc; } +#ifdef WOLFTPM_SPDM +TPM_RC TPM2_SendRawBytes(TPM2_CTX* ctx, + const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz) +{ + TPM_RC rc; + TPM2_Packet packet; + word32 rspSz; + UINT32 tmpSz; + + if (ctx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) { + return BAD_FUNC_ARG; + } + if (txSz > sizeof(ctx->cmdBuf)) { + return TPM_RC_SIZE; + } + + /* Copy transmit data into the context command buffer */ + XMEMCPY(ctx->cmdBuf, txBuf, txSz); + + /* Set up the packet structure pointing to cmdBuf. + * pos = number of bytes to send, size = buffer capacity. */ + packet.buf = ctx->cmdBuf; + packet.pos = (int)txSz; + packet.size = (int)sizeof(ctx->cmdBuf); + + /* Send through the transport layer (TIS, Linux dev, SWTPM, etc.). + * TIS will write txSz bytes, then read the response into cmdBuf. + * The response size is parsed from the header (offset 2, 4 bytes BE) + * inside TIS and only that many bytes are read from the FIFO. */ + rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, &packet); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + + /* After TIS returns, the response is in cmdBuf. The TIS layer read + * exactly the number of bytes indicated by the header size field. + * Extract that size from the response to know how many bytes to copy. + * Both TPM2 and TCG SPDM headers have: tag(2) + size(4) at offset 0. */ + if (packet.size < 6) { + return TPM_RC_FAILURE; + } + XMEMCPY(&tmpSz, &ctx->cmdBuf[2], sizeof(UINT32)); + rspSz = TPM2_Packet_SwapU32(tmpSz); + + if (rspSz > (word32)packet.size || rspSz > *rxSz) { + return TPM_RC_SIZE; + } + + XMEMCPY(rxBuf, ctx->cmdBuf, rspSz); + *rxSz = rspSz; + + return TPM_RC_SUCCESS; +} +#endif /* WOLFTPM_SPDM */ + /* If timeoutTries <= 0 then it will not try and startup chip and will * use existing default locality */ TPM_RC TPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, @@ -1580,6 +1698,7 @@ TPM_RC TPM2_StartAuthSession(StartAuthSession_In* in, StartAuthSession_Out* out) return rc; } + TPM_RC TPM2_PolicyRestart(PolicyRestart_In* in) { TPM_RC rc; @@ -5593,6 +5712,7 @@ int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in) TPM2_Packet packet; CmdInfo_t info = {0,0,0,0}; info.inHandleCnt = 1; + info.flags = CMD_FLAG_AUTH_USER1; TPM2_Packet_Init(ctx, &packet); /* Process the auth handle for GPIO configuration */ @@ -5636,6 +5756,68 @@ int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out) } #endif /* WOLFTPM_NUVOTON */ +/* NTC2 PreConfig/GetConfig for runtime vendor detection (WOLFTPM_AUTODETECT). + * Identical to the WOLFTPM_NUVOTON implementations above. */ +#if defined(WOLFTPM_AUTODETECT) && !defined(WOLFTPM_NUVOTON) && \ + !defined(WOLFTPM_ST33) + +int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in) +{ + int rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + + if (in == NULL || ctx == NULL) + return BAD_FUNC_ARG; + + rc = TPM2_AcquireLock(ctx); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet packet; + CmdInfo_t info = {0,0,0,0}; + info.inHandleCnt = 1; + info.flags = CMD_FLAG_AUTH_USER1; + + TPM2_Packet_Init(ctx, &packet); + TPM2_Packet_AppendU32(&packet, in->authHandle); + TPM2_Packet_AppendAuth(&packet, ctx, &info); + TPM2_Packet_AppendBytes(&packet, (byte*)&in->preConfig, + sizeof(in->preConfig)); + TPM2_Packet_Finalize(&packet, TPM_ST_SESSIONS, + TPM_CC_NTC2_PreConfig); + + rc = TPM2_SendCommandAuth(ctx, &packet, &info); + + TPM2_ReleaseLock(ctx); + } + return rc; +} + +int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out) +{ + int rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + + if (out == NULL || ctx == NULL) + return BAD_FUNC_ARG; + + rc = TPM2_AcquireLock(ctx); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet packet; + TPM2_Packet_Init(ctx, &packet); + TPM2_Packet_Finalize(&packet, TPM_ST_NO_SESSIONS, + TPM_CC_NTC2_GetConfig); + + rc = TPM2_SendCommand(ctx, &packet); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet_ParseBytes(&packet, (byte*)&out->preConfig, + sizeof(out->preConfig)); + } + + TPM2_ReleaseLock(ctx); + } + return rc; +} +#endif /* WOLFTPM_AUTODETECT && !WOLFTPM_NUVOTON && !WOLFTPM_ST33 */ + #ifdef WOLFTPM_FIRMWARE_UPGRADE #if defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index e603c005..ade4a81c 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -1045,6 +1045,7 @@ int TPM2_Packet_Finalize(TPM2_Packet* packet, TPM_ST tag, TPM_CC cc) return cmdSz; } + /******************************************************************************/ /* --- END TPM Packet Assembly / Parsing -- */ /******************************************************************************/ diff --git a/src/tpm2_spdm.c b/src/tpm2_spdm.c new file mode 100644 index 00000000..a58b99a6 --- /dev/null +++ b/src/tpm2_spdm.c @@ -0,0 +1,377 @@ +/* tpm2_spdm.c + * + * Copyright (C) 2006-2025 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 Integration Layer for wolfTPM + * + * All SPDM protocol logic, cryptography, and message handling is implemented + * in wolfSPDM (spdm/ subdirectory). This file provides: + * + * 1. Context management (init/free) + * 2. Secured exchange with VENDOR_DEFINED wrapping (Nuvoton) + * 3. TPM-specific NTC2_PreConfig for SPDM enable/disable (Nuvoton only) + * 4. TIS I/O callback for routing wolfSPDM through TPM SPI transport + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifdef WOLFTPM_SPDM + +#include + +/* TIS functions for SPI/I2C TPM transport */ +#if defined(WOLFSPDM_NUVOTON) && \ + !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ + !defined(WOLFTPM_WINAPI) + #include + #include + #define WOLFTPM_SPDM_TIS_IO +#endif + +/* wolfSPDM provides all SPDM protocol implementation */ +#include + +/* -------------------------------------------------------------------------- */ +/* TIS I/O Callback (SPI/I2C TPM transport for SPDM) */ +/* -------------------------------------------------------------------------- */ + +#ifdef WOLFTPM_SPDM_TIS_IO +/* TIS I/O callback for routing wolfSPDM through TPM SPI/I2C FIFO. + * This matches the WOLFSPDM_IO_CB signature. TCG framing (headers) is + * handled by wolfSPDM_SendReceive() in Nuvoton mode, so this callback + * just sends/receives raw bytes through the TIS FIFO. */ +static int wolfTPM2_SPDM_TisIoCb( + WOLFSPDM_CTX* spdmCtx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + void* userCtx) +{ + TPM2_CTX* tpmCtx = (TPM2_CTX*)userCtx; + byte ioBuf[MAX_RESPONSE_SIZE]; + TPM2_Packet packet; + int rc; + UINT32 rspSz; + + (void)spdmCtx; + + if (tpmCtx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) { + return -1; + } + + if (txSz > sizeof(ioBuf)) { + return -1; + } + + /* Set up packet with TX data */ + XMEMCPY(ioBuf, txBuf, txSz); + packet.buf = ioBuf; + packet.pos = (int)txSz; + packet.size = (int)sizeof(ioBuf); + + /* Ensure we have TPM locality */ + rc = TPM2_TIS_RequestLocality(tpmCtx, TPM_TIMEOUT_TRIES); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + + /* Send through TIS FIFO and receive response */ + rc = TPM2_TIS_SendCommand(tpmCtx, &packet); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + + /* Extract response size from header bytes [2..5] (big-endian). + * Both TPM headers and TCG SPDM binding headers store the total + * message size at this offset in the same format. */ + XMEMCPY(&rspSz, &ioBuf[2], sizeof(UINT32)); + rspSz = TPM2_Packet_SwapU32(rspSz); + + if (rspSz > *rxSz || rspSz > sizeof(ioBuf)) { + return -1; + } + + XMEMCPY(rxBuf, ioBuf, rspSz); + *rxSz = rspSz; + + return 0; +} +#endif /* WOLFTPM_SPDM_TIS_IO */ + +/* -------------------------------------------------------------------------- */ +/* Context Management */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_InitCtx( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_IO_CB ioCb, + void* userCtx) +{ + int rc; + + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + + /* Zero initialize context */ + XMEMSET(ctx, 0, sizeof(WOLFTPM2_SPDM_CTX)); + +#ifdef WOLFSPDM_DYNAMIC_MEMORY + /* Dynamic path: allocate and initialize via wolfSPDM_New() */ + ctx->spdmCtx = wolfSPDM_New(); + if (ctx->spdmCtx == NULL) { + return MEMORY_E; + } +#else + /* Static path: use inline buffer, no malloc */ + ctx->spdmCtx = (WOLFSPDM_CTX*)ctx->spdmBuf; + rc = wolfSPDM_InitStatic(ctx->spdmCtx, (int)sizeof(ctx->spdmBuf)); + if (rc != WOLFSPDM_SUCCESS) { + ctx->spdmCtx = NULL; + return rc; + } +#endif + + /* Set I/O callback if provided */ + if (ioCb != NULL) { + rc = wolfSPDM_SetIO(ctx->spdmCtx, ioCb, userCtx); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_Free(ctx->spdmCtx); + ctx->spdmCtx = NULL; + return rc; + } + } + + return TPM_RC_SUCCESS; +} + +int wolfTPM2_SPDM_SetTPMCtx( + WOLFTPM2_SPDM_CTX* ctx, + TPM2_CTX* tpmCtx) +{ + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + ctx->tpmCtx = tpmCtx; + return TPM_RC_SUCCESS; +} + +void wolfTPM2_SPDM_FreeCtx(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL) { + return; + } + + if (ctx->spdmCtx != NULL) { + wolfSPDM_Free(ctx->spdmCtx); + ctx->spdmCtx = NULL; + } + + ctx->tpmCtx = NULL; + ctx->spdmOnlyLocked = 0; +} + +/* -------------------------------------------------------------------------- */ +/* Secured Messaging */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_SecuredExchange( + WOLFTPM2_SPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSPDM_NUVOTON + /* In SPDM-only mode, TPM commands must be wrapped in SPDM VENDOR_DEFINED + * messages with the TPM2_CMD vendor code. The TPM's SPDM layer only + * accepts SPDM messages (starting with version byte 0x13), not raw TPM + * commands (starting with tag 0x80 0x01). */ + if (wolfSPDM_GetMode(ctx->spdmCtx) == WOLFSPDM_MODE_NUVOTON) { + byte vdMsg[WOLFSPDM_MAX_MSG_SIZE]; + byte vdRsp[WOLFSPDM_MAX_MSG_SIZE]; + word32 vdRspSz = sizeof(vdRsp); + char rspVdCode[WOLFSPDM_VDCODE_LEN + 1]; + int vdMsgSz; + int rc; + + /* Wrap TPM command in SPDM VENDOR_DEFINED_REQUEST("TPM2_CMD") */ + vdMsgSz = wolfSPDM_BuildVendorDefined(WOLFSPDM_VDCODE_TPM2_CMD, + cmdPlain, cmdSz, vdMsg, sizeof(vdMsg)); + if (vdMsgSz < 0) { + return vdMsgSz; + } + + /* Send encrypted VENDOR_DEFINED, receive encrypted response */ + rc = wolfSPDM_SecuredExchange(ctx->spdmCtx, + vdMsg, (word32)vdMsgSz, vdRsp, &vdRspSz); + if (rc != 0) { + return rc; + } + + /* Parse VENDOR_DEFINED_RESPONSE to extract TPM response */ + rc = wolfSPDM_ParseVendorDefined(vdRsp, vdRspSz, + rspVdCode, rspPlain, rspSz); + if (rc < 0) { + return rc; + } + + return TPM_RC_SUCCESS; + } +#endif /* WOLFSPDM_NUVOTON */ + + /* Standard SPDM mode: send TPM command as raw app data */ + return wolfSPDM_SecuredExchange(ctx->spdmCtx, + cmdPlain, cmdSz, rspPlain, rspSz); +} + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Functions */ +/* -------------------------------------------------------------------------- */ + +#ifdef WOLFSPDM_NUVOTON + +/* Set built-in TIS I/O callback for routing SPDM through TPM SPI/I2C. + * Must be called after wolfTPM2_SPDM_InitCtx() and SetTPMCtx(). */ +int wolfTPM2_SPDM_SetTisIO(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->spdmCtx == NULL || ctx->tpmCtx == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFTPM_SPDM_TIS_IO + return wolfSPDM_SetIO(ctx->spdmCtx, wolfTPM2_SPDM_TisIoCb, ctx->tpmCtx); +#else + (void)ctx; + return NOT_COMPILED_IN; +#endif +} + +/* Enable SPDM on Nuvoton TPM via NTC2_PreConfig vendor command. + * This requires platform hierarchy authorization and a TPM reset. */ +int wolfTPM2_SPDM_Enable(WOLFTPM2_SPDM_CTX* ctx) +{ + int rc; + NTC2_PreConfig_In preConfigIn; + NTC2_GetConfig_Out getConfigOut; + + if (ctx == NULL || ctx->tpmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Get current NTC2 configuration */ + XMEMSET(&getConfigOut, 0, sizeof(getConfigOut)); + rc = TPM2_NTC2_GetConfig(&getConfigOut); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_GetConfig failed: 0x%x\n", rc); + #endif + return rc; + } + + /* Check if SPDM is already enabled (bit 1 of Cfg_H, 0 = enabled) */ + if ((getConfigOut.preConfig.Cfg_H & NTC2_CFG_H_SPDM_DISABLE) == 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM already enabled on TPM\n"); + #endif + return TPM_RC_SUCCESS; + } + + + /* Set SPDM capability bit (clear bit 1 to enable) */ + XMEMSET(&preConfigIn, 0, sizeof(preConfigIn)); + preConfigIn.authHandle = TPM_RH_PLATFORM; + preConfigIn.preConfig = getConfigOut.preConfig; + preConfigIn.preConfig.Cfg_H &= ~NTC2_CFG_H_SPDM_DISABLE; + + rc = TPM2_NTC2_PreConfig(&preConfigIn); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_PreConfig failed: 0x%x\n", rc); + #endif + return rc; + } + +#ifdef DEBUG_WOLFTPM + printf("SPDM enabled. TPM reset required for changes to take effect.\n"); +#endif + + return TPM_RC_SUCCESS; +} + +int wolfTPM2_SPDM_Disable(WOLFTPM2_SPDM_CTX* ctx) +{ + int rc; + NTC2_PreConfig_In preConfigIn; + NTC2_GetConfig_Out getConfigOut; + + if (ctx == NULL || ctx->tpmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Get current NTC2 configuration */ + XMEMSET(&getConfigOut, 0, sizeof(getConfigOut)); + rc = TPM2_NTC2_GetConfig(&getConfigOut); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_GetConfig failed: 0x%x\n", rc); + #endif + return rc; + } + + /* Check if SPDM is already disabled (bit 1 of Cfg_H, 1 = disabled) */ + if ((getConfigOut.preConfig.Cfg_H & NTC2_CFG_H_SPDM_DISABLE) != 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM already disabled on TPM\n"); + #endif + return TPM_RC_SUCCESS; + } + + + /* Set SPDM disable bit (set bit 1 to disable) */ + XMEMSET(&preConfigIn, 0, sizeof(preConfigIn)); + preConfigIn.authHandle = TPM_RH_PLATFORM; + preConfigIn.preConfig = getConfigOut.preConfig; + preConfigIn.preConfig.Cfg_H |= NTC2_CFG_H_SPDM_DISABLE; + + rc = TPM2_NTC2_PreConfig(&preConfigIn); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_PreConfig failed: 0x%x\n", rc); + #endif + return rc; + } + +#ifdef DEBUG_WOLFTPM + printf("SPDM disabled. TPM reset required for changes to take effect.\n"); +#endif + + return TPM_RC_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/tpm2_swtpm.c b/src/tpm2_swtpm.c index 8c8ed122..cb5fcc7d 100644 --- a/src/tpm2_swtpm.c +++ b/src/tpm2_swtpm.c @@ -52,8 +52,11 @@ #include #include #include -#ifdef HAVE_NETDB_H +#ifndef WOLFTPM_ZEPHYR +#include #include +#include +#include #endif #include diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index dc6cfdb1..d5d12e11 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -25,6 +25,9 @@ #include #include +#ifdef WOLFTPM_SPDM +#include +#endif #ifndef WOLFTPM2_NO_WRAPPER @@ -142,10 +145,26 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, if (rc != TPM_RC_SUCCESS && rc != TPM_RC_INITIALIZE /* TPM_RC_INITIALIZE = Already started */ && rc != TPM_RC_UPGRADE /* TPM_RC_UPGRADE = In firmware upgrade mode */ ) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Startup failed %d: %s\n", rc, wolfTPM2_GetRCString(rc)); - #endif - return rc; + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* When SPDM-only mode is active on the TPM, TPM2_Startup returns + * TPM_RC_DISABLED. This is expected - SPDM commands bypass the + * normal TPM command path and work over raw SPI. */ + ctx->spdmOnlyDetected = 1; + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup: TPM_RC_DISABLED (SPDM-only mode active, " + "this is expected)\n"); + #endif + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup failed %d: %s\n", rc, + wolfTPM2_GetRCString(rc)); + #endif + return rc; + } } /* Return upgrade status so caller can handle appropriately */ if (rc == TPM_RC_UPGRADE) { @@ -155,7 +174,9 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, return rc; } #ifdef DEBUG_WOLFTPM - printf("TPM2_Startup pass\n"); + if (rc == TPM_RC_SUCCESS || rc == TPM_RC_INITIALIZE) { + printf("TPM2_Startup pass\n"); + } #endif rc = TPM_RC_SUCCESS; @@ -165,13 +186,29 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, selfTest.fullTest = YES; rc = TPM2_SelfTest(&selfTest); if (rc != TPM_RC_SUCCESS) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_SelfTest failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - #endif - return rc; + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* SPDM-only mode active - SelfTest not needed */ + #ifdef DEBUG_WOLFTPM + printf("TPM2_SelfTest: TPM_RC_DISABLED (SPDM-only mode, " + "expected)\n"); + #endif + rc = TPM_RC_SUCCESS; + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_SelfTest failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + #endif + return rc; + } } #ifdef DEBUG_WOLFTPM - printf("TPM2_SelfTest pass\n"); + if (rc == TPM_RC_SUCCESS) { + printf("TPM2_SelfTest pass\n"); + } #endif #endif /* WOLFTPM_MICROCHIP || WOLFTPM_PERFORM_SELFTEST */ #endif /* !WOLFTPM_LINUX_DEV && !WOLFTPM_WINAPI */ @@ -227,6 +264,49 @@ int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx) XMEMSET(dev->session, 0, sizeof(dev->session)); wolfTPM2_SetAuthPassword(dev, 0, NULL); +#if defined(WOLFTPM_SPDM) && defined(WOLFSPDM_NUVOTON) + /* If TPM is in SPDM-only mode, transparently establish an SPDM session + * so all subsequent TPM commands are encrypted over the bus. + * This allows existing binaries (caps, wrap_test, unit.test) to work + * without any SPDM-specific code. */ + if (dev->ctx.spdmOnlyDetected) { + Startup_In startupIn; + + rc = wolfTPM2_SpdmInit(dev); + if (rc != 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM auto-init failed: %d\n", rc); + #endif + return rc; + } + + rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); + if (rc != 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM auto-connect failed: %d\n", rc); + #endif + return rc; + } + + #ifdef DEBUG_WOLFTPM + printf("SPDM session established (auto), SessionID=0x%08x\n", + wolfTPM2_SpdmGetSessionId(dev)); + #endif + + /* Retry TPM2_Startup over the SPDM encrypted channel */ + XMEMSET(&startupIn, 0, sizeof(startupIn)); + startupIn.startupType = TPM_SU_CLEAR; + rc = TPM2_Startup(&startupIn); + if (rc != TPM_RC_SUCCESS && rc != TPM_RC_INITIALIZE) { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup over SPDM failed: 0x%x\n", rc); + #endif + return rc; + } + rc = TPM_RC_SUCCESS; + } +#endif /* WOLFTPM_SPDM && WOLFSPDM_NUVOTON */ + return rc; } @@ -923,6 +1003,310 @@ int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles) return handles->count; } +#ifdef WOLFTPM_SPDM +/* --- SPDM Secure Session Wrapper API --- + * + * These functions provide a high-level interface to wolfSPDM. + * All SPDM protocol logic is implemented in the wolfSPDM library. + */ + +int wolfTPM2_SpdmInit(WOLFTPM2_DEV* dev) +{ + int rc; + + if (dev == NULL) { + return BAD_FUNC_ARG; + } + + /* Already initialized (e.g., by auto-SPDM in wolfTPM2_Init) */ + if (dev->spdmCtx != NULL) { + return TPM_RC_SUCCESS; + } + + /* Initialize inline SPDM context */ + rc = wolfTPM2_SPDM_InitCtx(&dev->spdmCtxData, NULL, NULL); + if (rc != 0) { + return rc; + } + + rc = wolfTPM2_SPDM_SetTPMCtx(&dev->spdmCtxData, &dev->ctx); + if (rc != 0) { + wolfTPM2_SPDM_FreeCtx(&dev->spdmCtxData); + return rc; + } + + dev->spdmCtx = &dev->spdmCtxData; + dev->ctx.spdmCtx = dev->spdmCtx; + + return TPM_RC_SUCCESS; +} + +/* Validate SPDM context chain is fully initialized */ +#define WOLFTPM2_SPDM_CHECK_CTX(dev) \ + if ((dev) == NULL || (dev)->spdmCtx == NULL || \ + (dev)->spdmCtx->spdmCtx == NULL) \ + return BAD_FUNC_ARG + +int wolfTPM2_SpdmConnect(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Connect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmIsConnected(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL || dev->spdmCtx->spdmCtx == NULL) { + return 0; + } + return wolfSPDM_IsConnected(dev->spdmCtx->spdmCtx); +} + +word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL || dev->spdmCtx->spdmCtx == NULL) { + return 0; + } + return wolfSPDM_GetSessionId(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Disconnect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev) +{ + if (dev == NULL) { + return BAD_FUNC_ARG; + } + if (dev->spdmCtx != NULL) { + wolfTPM2_SPDM_FreeCtx(dev->spdmCtx); + dev->spdmCtx = NULL; + dev->ctx.spdmCtx = NULL; + } + return TPM_RC_SUCCESS; +} + +#ifdef WOLFSPDM_NUVOTON +/* Nuvoton-specific SPDM functions */ + +int wolfTPM2_SpdmSetNuvotonMode(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NUVOTON); +} + +int wolfTPM2_SpdmEnable(WOLFTPM2_DEV* dev) +{ + int rc; + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + /* NTC2_PreConfig requires platform auth (empty password) */ + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != 0) return rc; + return wolfTPM2_SPDM_Enable(dev->spdmCtx); +} + +int wolfTPM2_SpdmDisable(WOLFTPM2_DEV* dev) +{ + int rc; + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + /* NTC2_PreConfig requires platform auth (empty password) */ + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != 0) return rc; + return wolfTPM2_SPDM_Disable(dev->spdmCtx); +} + +int wolfTPM2_SpdmGetStatus(WOLFTPM2_DEV* dev, WOLFSPDM_NUVOTON_STATUS* status) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nuvoton_GetStatus(dev->spdmCtx->spdmCtx, status); +} + +int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nuvoton_GetPubKey(dev->spdmCtx->spdmCtx, pubKey, pubKeySz); +} + +int wolfTPM2_SpdmSetOnlyMode(WOLFTPM2_DEV* dev, int lock) +{ + int rc; + WOLFTPM2_SPDM_CHECK_CTX(dev); + rc = wolfSPDM_Nuvoton_SetOnlyMode(dev->spdmCtx->spdmCtx, lock); + if (rc == WOLFSPDM_SUCCESS) { + dev->spdmCtx->spdmOnlyLocked = lock; + } + return rc; +} + +int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz) +{ + int rc; + + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Auto-set TIS I/O callback if not already configured by caller */ + rc = wolfTPM2_SPDM_SetTisIO(dev->spdmCtx); + if (rc != 0 && rc != NOT_COMPILED_IN) { + return rc; + } + +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(dev->spdmCtx->spdmCtx, 1); +#endif + + /* Set Nuvoton mode first */ + rc = wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NUVOTON); + if (rc != 0) { + return rc; + } + + /* Set requester key pair if provided (for mutual authentication) */ + if (reqPrivKey != NULL && reqPrivKeySz > 0 && + reqPubKey != NULL && reqPubKeySz > 0) { + /* Parse TPMT_PUBLIC to extract raw X||Y ECC point */ + TPM2_Packet pktPub; + TPMT_PUBLIC pub; + byte rawPubKey[WOLFSPDM_ECC_POINT_SIZE]; + + XMEMSET(&pub, 0, sizeof(pub)); + pktPub.buf = (byte*)reqPubKey; + pktPub.pos = 0; + pktPub.size = (int)reqPubKeySz; + + TPM2_Packet_ParseU16(&pktPub, &pub.type); + TPM2_Packet_ParseU16(&pktPub, &pub.nameAlg); + TPM2_Packet_ParseU32(&pktPub, &pub.objectAttributes); + TPM2_Packet_ParseU16(&pktPub, &pub.authPolicy.size); + TPM2_Packet_ParseBytes(&pktPub, pub.authPolicy.buffer, + pub.authPolicy.size); + TPM2_Packet_ParsePublicParms(&pktPub, pub.type, &pub.parameters); + TPM2_Packet_ParseEccPoint(&pktPub, &pub.unique.ecc); + + if (pub.type != TPM_ALG_ECC || + pub.unique.ecc.x.size != WOLFSPDM_ECC_KEY_SIZE || + pub.unique.ecc.y.size != WOLFSPDM_ECC_KEY_SIZE) { + return BAD_FUNC_ARG; + } + + XMEMCPY(rawPubKey, pub.unique.ecc.x.buffer, + WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(rawPubKey + WOLFSPDM_ECC_KEY_SIZE, + pub.unique.ecc.y.buffer, WOLFSPDM_ECC_KEY_SIZE); + rc = wolfSPDM_SetRequesterKeyPair(dev->spdmCtx->spdmCtx, + reqPrivKey, reqPrivKeySz, rawPubKey, sizeof(rawPubKey)); + if (rc != 0) { + return rc; + } + /* Also store the full TPMT_PUBLIC for GIVE_PUB step */ + rc = wolfSPDM_SetRequesterKeyTPMT(dev->spdmCtx->spdmCtx, + reqPubKey, reqPubKeySz); + if (rc != 0) { + return rc; + } + } +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) + else { + /* Auto-generate ephemeral P-384 key pair for mutual authentication */ + ecc_key hostKey; + WC_RNG rng; + byte privKey[48]; + word32 privKeySz = sizeof(privKey); + byte pubKeyX[48], pubKeyY[48]; + word32 xSz = sizeof(pubKeyX), ySz = sizeof(pubKeyY); + byte rawPubKey[96]; + /* TPMT_PUBLIC: type(2) + nameAlg(2) + attr(4) + authPolicy(2) + + * symmetric(2) + scheme(2+2) + curveID(2) + kdf(2) + + * unique.x(2+48) + unique.y(2+48) = 120 bytes */ + byte tpmtPub[120]; + byte* p; + + rc = wc_InitRng(&rng); + if (rc != 0) return rc; + + rc = wc_ecc_init(&hostKey); + if (rc != 0) { + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_make_key_ex(&rng, 48, &hostKey, ECC_SECP384R1); + if (rc != 0) { + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_export_private_only(&hostKey, privKey, &privKeySz); + if (rc != 0) { + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_export_public_raw(&hostKey, pubKeyX, &xSz, + pubKeyY, &ySz); + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + if (rc != 0) return rc; + + /* Set raw key pair (X||Y format) */ + XMEMCPY(rawPubKey, pubKeyX, 48); + XMEMCPY(rawPubKey + 48, pubKeyY, 48); + rc = wolfSPDM_SetRequesterKeyPair(dev->spdmCtx->spdmCtx, + privKey, privKeySz, rawPubKey, 96); + if (rc != 0) return rc; + + /* Build TPMT_PUBLIC for GIVE_PUB step */ + p = tpmtPub; + /* type = TPM_ALG_ECC (0x0023) */ + *p++ = 0x00; *p++ = 0x23; + /* nameAlg = TPM_ALG_SHA384 (0x000C) */ + *p++ = 0x00; *p++ = 0x0C; + /* objectAttributes = 0x00040000 (sign only) */ + *p++ = 0x00; *p++ = 0x04; *p++ = 0x00; *p++ = 0x00; + /* authPolicy size = 0 */ + *p++ = 0x00; *p++ = 0x00; + /* symmetric = TPM_ALG_NULL (0x0010) */ + *p++ = 0x00; *p++ = 0x10; + /* scheme = TPM_ALG_ECDSA (0x0018) */ + *p++ = 0x00; *p++ = 0x18; + /* scheme.hashAlg = SHA384 (0x000C) */ + *p++ = 0x00; *p++ = 0x0C; + /* curveID = TPM_ECC_NIST_P384 (0x0004) */ + *p++ = 0x00; *p++ = 0x04; + /* kdf = TPM_ALG_NULL (0x0010) */ + *p++ = 0x00; *p++ = 0x10; + /* unique.x size = 48 */ + *p++ = 0x00; *p++ = 0x30; + XMEMCPY(p, pubKeyX, 48); p += 48; + /* unique.y size = 48 */ + *p++ = 0x00; *p++ = 0x30; + XMEMCPY(p, pubKeyY, 48); p += 48; + + rc = wolfSPDM_SetRequesterKeyTPMT(dev->spdmCtx->spdmCtx, + tpmtPub, (word32)(p - tpmtPub)); + if (rc != 0) return rc; + } +#endif /* !WOLFTPM2_NO_WOLFCRYPT && HAVE_ECC */ + + /* Perform the Nuvoton SPDM handshake */ + return wolfSPDM_Connect(dev->spdmCtx->spdmCtx); +} + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFTPM_SPDM */ + int wolfTPM2_UnsetAuth(WOLFTPM2_DEV* dev, int index) { TPM2_AUTH_SESSION* session; @@ -1253,14 +1637,32 @@ int wolfTPM2_Cleanup_ex(WOLFTPM2_DEV* dev, int doShutdown) shutdownIn.shutdownType = TPM_SU_CLEAR; rc = TPM2_Shutdown(&shutdownIn); if (rc != TPM_RC_SUCCESS) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Shutdown failed %d: %s\n", - rc, wolfTPM2_GetRCString(rc)); - #endif + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* SPDM-only mode active - shutdown not needed */ + #ifdef DEBUG_WOLFTPM + printf("TPM2_Shutdown: TPM_RC_DISABLED (SPDM-only mode, " + "expected)\n"); + #endif + rc = TPM_RC_SUCCESS; /* Not an error in SPDM mode */ + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Shutdown failed %d: %s\n", + rc, wolfTPM2_GetRCString(rc)); + #endif + } /* finish cleanup and return error */ } } +#ifdef WOLFTPM_SPDM + /* Clean up SPDM context if it was auto-established */ + wolfTPM2_SpdmCleanup(dev); +#endif + TPM2_Cleanup(&dev->ctx); return rc; diff --git a/tests/unit_tests.c b/tests/unit_tests.c index f11d30f1..7c43cf59 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -924,6 +924,78 @@ static void test_wolfTPM2_thread_local_storage(void) #endif /* HAVE_THREAD_LS && HAVE_PTHREAD */ } +#ifdef WOLFTPM_SPDM +/* Test SPDM wrapper API functions */ +static void test_wolfTPM2_SPDM_Functions(void) +{ + int rc; + WOLFTPM2_DEV dev; + + printf("Test TPM Wrapper:\tSPDM Functions:\t"); + + /* Initialize device */ + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + if (rc != 0) { + printf("Failed (Init failed: 0x%x)\n", rc); + return; + } + + /* Test 1: Parameter validation - NULL args */ + rc = wolfTPM2_SpdmInit(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmConnect(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_SpdmIsConnected(NULL), 0); + AssertIntEQ(wolfTPM2_SpdmGetSessionId(NULL), 0); + rc = wolfTPM2_SpdmDisconnect(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmCleanup(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + + /* Test 2: Context lifecycle - init, check state, cleanup */ + rc = wolfTPM2_SpdmInit(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + /* When SPDM-only mode is active, auto-SPDM connects during Init. + * Otherwise, just initialized but not yet connected. */ + if (!dev.ctx.spdmOnlyDetected) { + AssertIntEQ(wolfTPM2_SpdmIsConnected(&dev), 0); + AssertIntEQ(wolfTPM2_SpdmGetSessionId(&dev), 0); + } + /* Cleanup */ + rc = wolfTPM2_SpdmCleanup(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + /* Idempotent cleanup */ + rc = wolfTPM2_SpdmCleanup(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + +#ifdef WOLFSPDM_NUVOTON + /* Test 3: Nuvoton-specific parameter validation */ + rc = wolfTPM2_SpdmSetNuvotonMode(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmEnable(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + { + WOLFSPDM_NUVOTON_STATUS status; + byte pubKey[256]; + word32 pubKeySz = sizeof(pubKey); + + rc = wolfTPM2_SpdmGetStatus(NULL, &status); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmGetStatus(&dev, NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmGetPubKey(NULL, pubKey, &pubKeySz); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmSetOnlyMode(NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + } +#endif /* WOLFSPDM_NUVOTON */ + + wolfTPM2_Cleanup(&dev); + + printf("Passed\n"); +} +#endif /* WOLFTPM_SPDM */ + /* Test creating key and exporting keyblob as buffer, * importing and loading key. */ static void test_wolfTPM2_KeyBlob(TPM_ALG_ID alg) @@ -1060,6 +1132,9 @@ int unit_tests(int argc, char *argv[]) #endif test_wolfTPM2_Cleanup(); test_wolfTPM2_thread_local_storage(); +#ifdef WOLFTPM_SPDM + test_wolfTPM2_SPDM_Functions(); +#endif #endif /* !WOLFTPM2_NO_WRAPPER */ return 0; diff --git a/wolftpm/include.am b/wolftpm/include.am index 630436cf..b7b75c93 100644 --- a/wolftpm/include.am +++ b/wolftpm/include.am @@ -15,5 +15,6 @@ nobase_include_HEADERS+= \ wolftpm/tpm2_socket.h \ wolftpm/tpm2_asn.h \ wolftpm/version.h \ + wolftpm/tpm2_spdm.h \ wolftpm/visibility.h \ wolftpm/options.h diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 6f2ca10d..331f0858 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -262,7 +262,7 @@ typedef enum { TPM_CC_SetCommandSetLock = CC_VEND + 0x030B, TPM_CC_GPIO_Config = CC_VEND + 0x030F, #endif -#ifdef WOLFTPM_NUVOTON +#if defined(WOLFTPM_NUVOTON) || defined(WOLFTPM_AUTODETECT) TPM_CC_NTC2_PreConfig = CC_VEND + 0x0211, TPM_CC_NTC2_GetConfig = CC_VEND + 0x0213, #endif @@ -508,6 +508,73 @@ typedef enum { } TPM_CAP_T; typedef UINT32 TPM_CAP; +#ifdef WOLFTPM_SPDM +/* TCG SPDM Binding for Secure Communication v1.0 Constants */ + +/* TCG SPDM Binding Message Tags */ +#define SPDM_TAG_CLEAR 0x8101 /* Clear (unencrypted) SPDM message */ +#define SPDM_TAG_SECURED 0x8201 /* Secured (encrypted) SPDM message */ + +/* SPDM Protocol Version */ +#define SPDM_VERSION_1_3 0x13 /* SPDM v1.3 */ + +/* SPDM protocol message codes, response codes, and error codes are + * defined in (the authoritative source). + * Include that header directly if you need SPDM protocol constants. */ + +/* SPDM Vendor Defined Codes (8-byte ASCII, used as VdCode in VENDOR_DEFINED) */ +#define SPDM_VDCODE_TPM2_CMD "TPM2_CMD" /* TPM command over SPDM session */ +#define SPDM_VDCODE_GET_PUBK "GET_PUBK" /* Get TPM's SPDM-Identity pub key */ +#define SPDM_VDCODE_GIVE_PUB "GIVE_PUB" /* Give host's SPDM-Identity pub key */ +#define SPDM_VDCODE_SPDMONLY "SPDMONLY" /* Lock/unlock SPDM-only mode */ +#define SPDM_VDCODE_GET_STS "GET_STS_" /* Get SPDM status */ + +/* SPDM Vendor Defined Code Length */ +#define SPDM_VDCODE_LEN 8 + +/* SPDM Session Constants (Nuvoton NPCT7xx) */ +#define SPDM_CONNECTION_ID 0 /* Single connection */ +#define SPDM_RSP_SESSION_ID 0xAEAD /* Responder session ID */ +#define SPDM_REQ_SESSION_ID 0x0001 /* Default requester session ID */ + +/* SPDM FIPS Indicator (TCG binding) */ +#define SPDM_FIPS_NON_FIPS 0x00 +#define SPDM_FIPS_APPROVED 0x01 + +/* SPDM Algorithm Set B (192-bit security strength) */ +#define SPDM_ALG_ECDSA_P384 0x0003 /* Signing algorithm */ +#define SPDM_ALG_SHA384 0x0002 /* Hash algorithm */ +#define SPDM_ALG_ECDHE_P384 0x0003 /* Key exchange algorithm */ +#define SPDM_ALG_AES256_GCM 0x0002 /* AEAD algorithm */ + +/* SPDM-Identity NV Indices */ +#define SPDM_NV_INDEX_TPM_KEY 0x01C20110 /* TPM SPDM-Identity key */ +#define SPDM_NV_INDEX_REQ_KEY 0x01C20111 /* Requester SPDM-Identity key */ + +/* NTC2 PreConfig CFG_H Bit Definitions for SPDM */ +#define NTC2_CFG_H_SPDM_ENABLE_BIT 1 /* Bit position in CFG_H */ +#define NTC2_CFG_H_SPDM_ENABLE 0x00 /* SPDM enabled (bit 1 = 0) */ +#define NTC2_CFG_H_SPDM_DISABLE 0x02 /* SPDM disabled (bit 1 = 1) */ + +/* SPDM ONLY mode sub-commands */ +#define SPDM_ONLY_LOCK 0x01 +#define SPDM_ONLY_UNLOCK 0x00 + +/* SPDM Message Sizes */ +#define SPDM_MAX_MSG_SIZE 4096 + +/* TCG SPDM Binding Header Size (per TCG SPDM Binding Spec): + * tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + fipsIndicator(2/BE) + + * reserved(4) = 16 bytes */ +#define SPDM_TCG_BINDING_HEADER_SIZE 16 + +/* SPDM Secured Message Header Size (per DSP0277): + * sessionId(4/LE) + sequenceNumber(8/LE) + length(2/LE) = 14 bytes + * where length = size of encrypted data + MAC */ +#define SPDM_SECURED_MSG_HEADER_SIZE 14 + +#endif /* WOLFTPM_SPDM */ + /* Property Tag */ typedef enum { TPM_PT_NONE = 0x00000000, @@ -816,6 +883,9 @@ typedef TPM_HANDLE TPMI_RH_CLEAR; typedef TPM_HANDLE TPMI_RH_NV_AUTH; typedef TPM_HANDLE TPMI_RH_LOCKOUT; typedef TPM_HANDLE TPMI_RH_NV_INDEX; +#ifdef WOLFTPM_SPDM +typedef TPM_HANDLE TPMI_DH_AC; /* Authenticated Controller handle */ +#endif typedef TPM_ALG_ID TPMI_ALG_HASH; typedef TPM_ALG_ID TPMI_ALG_ASYM; @@ -1045,7 +1115,6 @@ typedef struct TPML_ACT_DATA { TPMS_ACT_DATA actData[MAX_ACT_DATA]; } TPML_ACT_DATA; - /* Capabilities Structures */ typedef union TPMU_CAPABILITIES { @@ -1902,6 +1971,10 @@ typedef struct TPM2_CTX { #if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_LINUX_DEV_AUTODETECT) int fd; #endif +#ifdef WOLFTPM_SPDM + void* spdmCtx; /* Pointer to WOLFTPM2_SPDM_CTX when session active */ + unsigned int spdmOnlyDetected:1; /* TPM_RC_DISABLED from Startup */ +#endif } TPM2_CTX; @@ -1929,7 +2002,6 @@ typedef struct { WOLFTPM_API TPM_RC TPM2_GetCapability(GetCapability_In* in, GetCapability_Out* out); - typedef struct { TPMI_YES_NO fullTest; } SelfTest_In; @@ -2650,6 +2722,7 @@ typedef struct { } PolicyAuthValue_In; WOLFTPM_API TPM_RC TPM2_PolicyAuthValue(PolicyAuthValue_In* in); + typedef struct { TPMI_SH_POLICY policySession; } PolicyPassword_In; @@ -3142,6 +3215,44 @@ WOLFTPM_API int TPM2_ST33_FieldUpgradeCommand(TPM_CC cc, uint8_t* data, uint32_t WOLFTPM_API int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out); #endif /* Vendor GPIO Commands */ +/* NTC2 PreConfig/GetConfig for WOLFTPM_AUTODETECT (runtime vendor detection). + * When a specific vendor is not selected at compile time, the NTC2 types + * must still be available for SPDM enable via NTC2_PreConfig. */ +#if defined(WOLFTPM_AUTODETECT) && !defined(WOLFTPM_NUVOTON) && \ + !defined(WOLFTPM_ST33) + typedef struct { + BYTE Base0; + BYTE Base1; + BYTE GpioAltCfg; + BYTE GpioInitValue; + BYTE GpioPullUp; + BYTE GpioPushPull; + BYTE Cfg_A; + BYTE Cfg_B; + BYTE Cfg_C; + BYTE Cfg_D; + BYTE Cfg_E; + BYTE Cfg_F; + BYTE Cfg_G; + BYTE Cfg_H; + BYTE Cfg_I; + BYTE Cfg_J; + BYTE isValid; + BYTE isLocked; + } CFG_STRUCT; + + typedef struct { + TPMI_RH_PLATFORM authHandle; + CFG_STRUCT preConfig; + } NTC2_PreConfig_In; + WOLFTPM_API int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in); + + typedef struct { + CFG_STRUCT preConfig; + } NTC2_GetConfig_Out; + WOLFTPM_API int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out); +#endif /* WOLFTPM_AUTODETECT && !WOLFTPM_NUVOTON && !WOLFTPM_ST33 */ + /* Non-standard API's */ @@ -3302,6 +3413,27 @@ WOLFTPM_API TPM_RC TPM2_ChipStartup(TPM2_CTX* ctx, int timeoutTries); */ WOLFTPM_API TPM_RC TPM2_SetHalIoCb(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx); +#ifdef WOLFTPM_SPDM +/*! + \ingroup TPM2_Proprietary + \brief Send raw bytes through the TIS/HAL transport and receive the response. + Used by the SPDM layer to send TCG-framed SPDM messages over the same + SPI FIFO as regular TPM commands. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: check the provided arguments + \return TPM_RC_FAILURE: communication failure + + \param ctx pointer to a TPM2 context + \param txBuf pointer to the transmit buffer (TCG SPDM framed message) + \param txSz size of the transmit buffer in bytes + \param rxBuf pointer to the receive buffer for the response + \param rxSz pointer to size; on input max buffer size, on output actual response size +*/ +WOLFTPM_API TPM_RC TPM2_SendRawBytes(TPM2_CTX* ctx, + const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); +#endif /* WOLFTPM_SPDM */ + /*! \ingroup TPM2_Proprietary \brief Sets the structure holding the TPM Authorizations. diff --git a/wolftpm/tpm2_packet.h b/wolftpm/tpm2_packet.h index 563fc81a..14f514be 100644 --- a/wolftpm/tpm2_packet.h +++ b/wolftpm/tpm2_packet.h @@ -180,6 +180,7 @@ WOLFTPM_LOCAL void TPM2_Packet_AppendSignature(TPM2_Packet* packet, TPMT_SIGNATU WOLFTPM_LOCAL void TPM2_Packet_ParseSignature(TPM2_Packet* packet, TPMT_SIGNATURE* sig); WOLFTPM_LOCAL void TPM2_Packet_ParseAttest(TPM2_Packet* packet, TPMS_ATTEST* out); + WOLFTPM_LOCAL TPM_RC TPM2_Packet_Parse(TPM_RC rc, TPM2_Packet* packet); WOLFTPM_LOCAL int TPM2_Packet_Finalize(TPM2_Packet* packet, TPM_ST tag, TPM_CC cc); diff --git a/wolftpm/tpm2_spdm.h b/wolftpm/tpm2_spdm.h new file mode 100644 index 00000000..704927d0 --- /dev/null +++ b/wolftpm/tpm2_spdm.h @@ -0,0 +1,204 @@ +/* tpm2_spdm.h + * + * Copyright (C) 2006-2025 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 Secure Session Support for wolfTPM + * + * Implements SPDM (Security Protocol and Data Model) secure communication + * between host and TPM using the wolfSPDM library for all protocol operations. + * + * References: + * - DMTF DSP0274 (SPDM v1.2/1.3) + * - TCG SPDM Binding for Secure Communication v1.0 + * - TCG TPM 2.0 Library Specification v1.84 + * + * Architecture: + * Application -> wolfTPM2 Wrapper -> SPDM Transport (this module) -> SPI HAL + * | + * wolfSPDM library (spdm/ subdirectory) + * (all SPDM protocol logic) + * + * This module provides: + * - SPDM context management (init/free) + * - Secured exchange with VENDOR_DEFINED wrapping (Nuvoton) + * - TPM-specific SPDM enable/disable via NTC2 vendor commands + * - I/O callback adapter to route wolfSPDM through TPM transport + * + * wolfSPDM (spdm/) provides: + * - Full SPDM protocol implementation (handshake, key derivation, encryption) + * - Standard and Nuvoton mode support + * - TCG binding message framing (for Nuvoton TPMs) + * - All cryptographic operations + */ + +#ifndef __TPM2_SPDM_H__ +#define __TPM2_SPDM_H__ + +#include + +#ifdef WOLFTPM_SPDM + +/* wolfSPDM library provides all SPDM protocol implementation */ +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* Forward declarations */ +struct WOLFTPM2_SPDM_CTX; + +/* -------------------------------------------------------------------------- */ +/* SPDM Context + * + * This is a thin wrapper around WOLFSPDM_CTX. wolfSPDM handles all the + * SPDM protocol state, key derivation, and encryption. This context adds + * only TPM-specific fields needed for integration with wolfTPM2. + * -------------------------------------------------------------------------- */ + +typedef struct WOLFTPM2_SPDM_CTX { + /* wolfSPDM context - handles all SPDM protocol operations */ + WOLFSPDM_CTX* spdmCtx; + + /* Reference to TPM context for NTC2 vendor commands */ + TPM2_CTX* tpmCtx; + + /* SPDM-only mode tracking (for Nuvoton TPMs) */ + int spdmOnlyLocked; + +#ifndef WOLFSPDM_DYNAMIC_MEMORY + /* Inline buffer for static wolfSPDM context (zero-malloc mode) */ + byte spdmBuf[WOLFSPDM_CTX_STATIC_SIZE]; +#endif +} WOLFTPM2_SPDM_CTX; + +/* -------------------------------------------------------------------------- */ +/* SPDM Core API Functions + * -------------------------------------------------------------------------- */ + +/** + * Initialize SPDM context with wolfSPDM. + * Must be called before any other SPDM function. + * + * @param ctx wolfTPM2 SPDM context + * @param ioCb I/O callback for sending/receiving SPDM messages + * @param userCtx User context passed to the I/O callback + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_InitCtx( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_IO_CB ioCb, + void* userCtx +); + +/** + * Set the TPM context for NTC2 vendor commands. + * Only needed for Nuvoton TPMs when using wolfTPM2_SPDM_Enable(). + * + * @param ctx wolfTPM2 SPDM context + * @param tpmCtx TPM2 context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SetTPMCtx( + WOLFTPM2_SPDM_CTX* ctx, + TPM2_CTX* tpmCtx +); + +/** + * Enable SPDM on the TPM via NTC2_PreConfig. + * Requires platform hierarchy authorization. + * TPM must be reset after this for SPDM to take effect. + * NOTE: This is a Nuvoton-specific feature. + * + * @param ctx wolfTPM2 SPDM context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_Enable( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Disable SPDM on a Nuvoton TPM via NTC2_PreConfig. + * Sets Cfg_H bit 1 to disable SPDM. Requires TPM reset to take effect. + * + * @param ctx wolfTPM2 SPDM context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_Disable( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Perform a secured message exchange (encrypt, send, receive, decrypt). + * Wraps wolfSPDM_SecuredExchange() for TPM command/response. + * + * @param ctx wolfTPM2 SPDM 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 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SecuredExchange( + WOLFTPM2_SPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz +); + +/** + * Free all SPDM context resources. + * Safe to call on an already-cleaned-up or zero-initialized context. + * + * @param ctx wolfTPM2 SPDM context + */ +WOLFTPM_API void wolfTPM2_SPDM_FreeCtx( + WOLFTPM2_SPDM_CTX* ctx +); + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Functions (requires wolfSPDM with --enable-nuvoton) + * -------------------------------------------------------------------------- */ + +#ifdef WOLFSPDM_NUVOTON + +/** + * Set the built-in TIS I/O callback for routing SPDM through TPM SPI/I2C. + * Uses the TPM TIS FIFO to send/receive raw SPDM messages. + * TCG framing is handled internally by wolfSPDM_SendReceive(). + * Must be called after wolfTPM2_SPDM_InitCtx() and SetTPMCtx(). + * + * Only available on hardware TPM builds (not LINUX_DEV, SWTPM, or WINAPI). + * + * @param ctx wolfTPM2 SPDM context (with tpmCtx already set) + * @return 0 on success, NOT_COMPILED_IN if TIS not available + */ +WOLFTPM_API int wolfTPM2_SPDM_SetTisIO( + WOLFTPM2_SPDM_CTX* ctx +); + +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFTPM_SPDM */ + +#endif /* __TPM2_SPDM_H__ */ diff --git a/wolftpm/tpm2_types.h b/wolftpm/tpm2_types.h index ddd08d55..101e4977 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -760,6 +760,9 @@ typedef int64_t INT64; #ifndef MAX_ACT_DATA #define MAX_ACT_DATA (MAX_CAP_DATA / sizeof(TPMS_ACT_DATA)) #endif +#ifndef MAX_AC_HANDLES +#define MAX_AC_HANDLES 16 +#endif /* ---------------------------------------------------------------------------*/ diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 40bd5132..72b88e91 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -23,6 +23,9 @@ #define __TPM2_WRAP_H__ #include +#ifdef WOLFTPM_SPDM +#include +#endif #ifdef __cplusplus extern "C" { @@ -57,6 +60,10 @@ typedef struct WOLFTPM2_SESSION { typedef struct WOLFTPM2_DEV { TPM2_CTX ctx; TPM2_AUTH_SESSION session[MAX_SESSION_NUM]; +#ifdef WOLFTPM_SPDM + WOLFTPM2_SPDM_CTX spdmCtxData; + WOLFTPM2_SPDM_CTX* spdmCtx; /* NULL = not initialized */ +#endif } WOLFTPM2_DEV; /* Public Key with Handle. @@ -163,6 +170,10 @@ typedef struct WOLFTPM2_CAPS { word16 fips140_2 : 1; /* using FIPS mode */ word16 cc_eal4 : 1; /* Common Criteria EAL4+ */ word16 req_wait_state : 1; /* requires SPI wait state */ +#ifdef WOLFTPM_SPDM + word32 acHandleCount; /* Number of AC handles discovered */ + TPM_HANDLE acHandles[MAX_AC_HANDLES]; /* AC handles */ +#endif } WOLFTPM2_CAPS; @@ -412,6 +423,176 @@ WOLFTPM_API int wolfTPM2_GetCapabilities(WOLFTPM2_DEV* dev, WOLFTPM2_CAPS* caps) */ WOLFTPM_API int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles); +#ifdef WOLFTPM_SPDM +/* SPDM Secure Session Wrapper API + * + * These functions provide a high-level interface for SPDM secure sessions. + * All SPDM protocol logic is implemented in the wolfSPDM library. + */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Initialize SPDM support on a wolfTPM2 device. + Allocates and configures the SPDM context using wolfSPDM. + After init, call wolfTPM2_SpdmConnect to establish a secure session. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + \return MEMORY_E: memory allocation failed + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmInit(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish an SPDM secure session (full handshake). + Uses standard SPDM flow: GET_VERSION -> GET_CAPABILITIES -> + NEGOTIATE_ALGORITHMS -> KEY_EXCHANGE -> FINISH. + + \return TPM_RC_SUCCESS: session established + \return TPM_RC_FAILURE: handshake failed + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmConnect(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Check if an SPDM secure session is currently active. + + \return 1 if connected, 0 if not + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmIsConnected(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get the current SPDM session ID. + + \return Session ID, or 0 if not connected + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Disconnect the SPDM secure session. + After this, TPM commands are sent in the clear. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Free SPDM context and resources. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev); + +#ifdef WOLFSPDM_NUVOTON +/* Nuvoton-specific SPDM functions (requires wolfSPDM with --enable-nuvoton) */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Configure for Nuvoton TPM SPDM mode. + Must be called before wolfTPM2_SpdmConnect() for Nuvoton TPMs. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmSetNuvotonMode(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Enable SPDM on the TPM via NTC2_PreConfig vendor command. + Requires platform hierarchy auth. TPM must be reset after this call. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmEnable(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Disable SPDM on Nuvoton TPM via NTC2_PreConfig. + Sets Cfg_H bit 1 to disable SPDM. Requires TPM reset to take effect. + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmDisable(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish Nuvoton SPDM secure session with mutual authentication. + Uses Nuvoton flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> + GIVE_PUB_KEY -> FINISH. + + \return TPM_RC_SUCCESS: session established + \return TPM_RC_FAILURE: handshake failed + + \param dev pointer to a WOLFTPM2_DEV structure + \param reqPubKey host's ECDSA P-384 public key (TPMT_PUBLIC format) + \param reqPubKeySz size of reqPubKey in bytes + \param reqPrivKey host's ECDSA P-384 private key (raw 48 bytes) + \param reqPrivKeySz size of reqPrivKey in bytes +*/ +WOLFTPM_API int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get SPDM status from the TPM (GET_STS_ vendor command). + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param status output: SPDM status information +*/ +WOLFTPM_API int wolfTPM2_SpdmGetStatus(WOLFTPM2_DEV* dev, + WOLFSPDM_NUVOTON_STATUS* status); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get the TPM's SPDM-Identity public key. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param pubKey output buffer for the public key + \param pubKeySz in/out: buffer size / actual key size +*/ +WOLFTPM_API int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, + byte* pubKey, word32* pubKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Lock or unlock SPDM-only mode. + When locked, TPM only accepts commands over SPDM secure channel. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure + \param lock 1 to lock SPDM-only mode, 0 to unlock +*/ +WOLFTPM_API int wolfTPM2_SpdmSetOnlyMode(WOLFTPM2_DEV* dev, int lock); + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFTPM_SPDM */ /*! \ingroup wolfTPM2_Wrappers diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 41059918..0c3f79d8 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -6,6 +6,10 @@ if(CONFIG_WOLFTPM) ${ZEPHYR_CURRENT_MODULE_DIR}/src/*.c ${ZEPHYR_CURRENT_MODULE_DIR}/hal/*.c ) + # Exclude transport backends not applicable to Zephyr + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_linux\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_winapi\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_spdm\\.c$") target_sources(app PRIVATE ${wolftpm_sources}) if(CONFIG_WOLFTPM_DEBUG) From 0f0c1484a25b402c3877e065525468e440997f21 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 3 Mar 2026 19:28:41 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=20-=2040/40=20unit=20tests=20PASS=20=20=20?= =?UTF-8?q?-=2018/18=20emulator=20tests=20PASS=20(6=20tests=20x=203=20vers?= =?UTF-8?q?ions:=201.2,=201.3,=201.4)=20=20=201.=20Removed=20OpaqueDataLen?= =?UTF-8?q?gth(2)=20from=20CHALLENGE=20request=20for=20SPDM=201.3+=20?= =?UTF-8?q?=E2=80=94=20spec=20says=20only=20RequesterContext(8),=20no=20Op?= =?UTF-8?q?aqueDataLength=20in=20request=20=20=202.=20Removed=20OpaqueData?= =?UTF-8?q?Length(2)=20from=20signed=20GET=5FMEASUREMENTS=20request=20for?= =?UTF-8?q?=20SPDM=201.3+=20=E2=80=94=20same=20issue=20=20=20-=20spdm/READ?= =?UTF-8?q?ME.md:=20Added=20supported=20versions=20table=20(spdm-emu:=201.?= =?UTF-8?q?2/1.3/1.4,=20Nuvoton:=201.3),=20updated=20protocol=20flow=20dia?= =?UTF-8?q?gram,=20added=20--ver=20flag=20to=20demo=20options,=20=20=20add?= =?UTF-8?q?ed=20wolfSPDM=5FSetMaxVersion()=20to=20API=20table,=20updated?= =?UTF-8?q?=20emulator=20section=20to=20mention=2018-test=20multi-version?= =?UTF-8?q?=20coverage=20=20=20-=20Addressed=20PR=20review=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/spdm/spdm_demo.c | 33 +++++- examples/spdm/spdm_test.sh | 88 +++++++++------ spdm/README.md | 22 +++- spdm/src/spdm_context.c | 23 +++- spdm/src/spdm_crypto.c | 7 +- spdm/src/spdm_internal.h | 9 +- spdm/src/spdm_kdf.c | 9 +- spdm/src/spdm_msg.c | 218 ++++++++++++++++++++++++++++++------- spdm/src/spdm_nuvoton.c | 8 +- spdm/src/spdm_secured.c | 109 ++++++------------- spdm/src/spdm_session.c | 19 +++- spdm/src/spdm_transcript.c | 1 - spdm/test/unit_test.c | 182 +++++++++++++++++++++++++++++++ spdm/wolfspdm/spdm.h | 12 ++ spdm/wolfspdm/spdm_types.h | 1 + 15 files changed, 567 insertions(+), 174 deletions(-) diff --git a/examples/spdm/spdm_demo.c b/examples/spdm/spdm_demo.c index 36813806..198eb54b 100644 --- a/examples/spdm/spdm_demo.c +++ b/examples/spdm/spdm_demo.c @@ -125,6 +125,7 @@ static void usage(void) printf(" --challenge Challenge authentication (sessionless, --emu)\n"); printf(" --heartbeat Session heartbeat keep-alive (--emu)\n"); printf(" --key-update Session key rotation (--emu)\n"); + printf(" --ver SPDM version cap: 1.2, 1.3, or 1.4 (default: highest)\n"); printf(" --host Emulator IP address (default: 127.0.0.1)\n"); printf(" --port Emulator port (default: 2323)\n"); #endif @@ -629,7 +630,8 @@ static int demo_key_update(WOLFSPDM_CTX* ctx) * Uses the unified I/O callback (same as Nuvoton hardware mode) */ static int demo_emulator(const char* host, int port, int doMeas, int requestSignature, int doChallenge, - int doHeartbeat, int doKeyUpdate) + int doHeartbeat, int doKeyUpdate, + byte maxVersion) { WOLFSPDM_CTX* ctx; int rc; @@ -674,6 +676,18 @@ static int demo_emulator(const char* host, int port, int doMeas, wolfSPDM_SetDebug(ctx, 1); #endif + /* Set max version if specified via --ver */ + if (maxVersion != 0) { + rc = wolfSPDM_SetMaxVersion(ctx, maxVersion); + if (rc != WOLFSPDM_SUCCESS) { + printf("ERROR: wolfSPDM_SetMaxVersion(0x%02x) failed: %s\n", + maxVersion, wolfSPDM_GetErrorString(rc)); + wolfSPDM_Free(ctx); + spdm_io_cleanup(&g_ioCtx); + return rc; + } + } + #ifndef NO_WOLFSPDM_CHALLENGE /* Challenge mode: sessionless attestation (no KEY_EXCHANGE/FINISH) */ if (doChallenge) { @@ -752,6 +766,7 @@ int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) int doChallenge = 0; int doHeartbeat = 0; int doKeyUpdate = 0; + byte maxVersion = 0; /* 0 = use default (highest supported) */ #endif if (argc <= 1) { @@ -794,6 +809,19 @@ int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) doKeyUpdate = 1; useEmulator = 1; } + else if (XSTRCMP(argv[i], "--ver") == 0 && i + 1 < argc) { + const char* verStr = argv[++i]; + if (XSTRCMP(verStr, "1.2") == 0) + maxVersion = SPDM_VERSION_12; + else if (XSTRCMP(verStr, "1.3") == 0) + maxVersion = SPDM_VERSION_13; + else if (XSTRCMP(verStr, "1.4") == 0) + maxVersion = SPDM_VERSION_14; + else { + printf("Invalid version: %s (use 1.2, 1.3, or 1.4)\n", verStr); + return BAD_FUNC_ARG; + } + } #endif } @@ -803,7 +831,8 @@ int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) printf("Entering emulator mode...\n"); fflush(stdout); return demo_emulator(emuHost, emuPort, doMeas, requestSignature, - doChallenge, doHeartbeat, doKeyUpdate); + doChallenge, doHeartbeat, doKeyUpdate, + maxVersion); } #endif diff --git a/examples/spdm/spdm_test.sh b/examples/spdm/spdm_test.sh index 10c3a631..8fa60a64 100755 --- a/examples/spdm/spdm_test.sh +++ b/examples/spdm/spdm_test.sh @@ -118,19 +118,30 @@ find_emu() { } # Start the emulator (must run from its bin dir for cert files) +# Usage: start_emu [version] +# version: "1.2", "1.3", or "1.4" (default: "1.2") start_emu() { - echo " Starting spdm_responder_emu..." + local ver="${1:-1.2}" + echo " Starting spdm_responder_emu (SPDM $ver)..." - # Kill any stale emulator processes - if pgrep -x spdm_responder_emu >/dev/null 2>&1; then + # Kill any stale emulator processes (use -f for partial name match — + # process name can be truncated to "spdm_responder_" on some systems) + if pgrep -f spdm_responder_emu >/dev/null 2>&1; then echo " Killing stale emulator process..." - pkill -9 -x spdm_responder_emu 2>/dev/null + pkill -9 -f spdm_responder_emu 2>/dev/null sleep 2 fi - # Check port availability + # Free port 2323 if still held (e.g. by orphaned process) if ss -tlnp 2>/dev/null | grep -q ":2323 "; then - echo -e " ${RED}ERROR: Port 2323 already in use${NC}" + echo " Port 2323 in use, killing holder..." + fuser -k 2323/tcp 2>/dev/null + sleep 2 + fi + + # Final port check + if ss -tlnp 2>/dev/null | grep -q ":2323 "; then + echo -e " ${RED}ERROR: Port 2323 still in use after cleanup${NC}" ss -tlnp 2>/dev/null | grep ":2323 " return 1 fi @@ -142,7 +153,7 @@ start_emu() { echo " Run 'make copy_sample_key' in the spdm-emu build directory" fi - (cd "$EMU_DIR" && ./spdm_responder_emu --ver 1.2 \ + (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=$! @@ -186,12 +197,14 @@ gpio_reset() { } # Run a test with optional setup/teardown -# Usage: run_test +# Usage: run_test # mode: "nuvoton" (GPIO reset before) or "emu" (start/stop emulator around) +# emu_ver: emulator SPDM version (e.g., "1.2"), ignored for nuvoton mode run_test() { local mode="$1" local name="$2" - shift 2 + local emu_ver="$3" + shift 3 TOTAL=$((TOTAL + 1)) echo "[$TOTAL] $name" @@ -200,7 +213,7 @@ run_test() { if [ "$mode" = "nuvoton" ]; then gpio_reset elif [ "$mode" = "emu" ]; then - if ! start_emu; then + if ! start_emu "$emu_ver"; then echo -e " ${RED}FAIL (emulator start)${NC}" FAIL=$((FAIL + 1)) echo "" @@ -252,31 +265,36 @@ if [ $DO_EMU -eq 1 ]; then echo "Using demo: $SPDM_DEMO" echo "" - # Test 1: Session establishment - run_test emu "Session establishment (--emu)" \ - "$SPDM_DEMO" --emu + # 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 ---" - # Test 2: Session + signed measurements - run_test emu "Signed measurements (--meas)" \ - "$SPDM_DEMO" --meas + # Session establishment + run_test emu "Session (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --emu --ver "$VER" - # Test 3: Session + unsigned measurements - run_test emu "Unsigned measurements (--meas --no-sig)" \ - "$SPDM_DEMO" --meas --no-sig + # Session + signed measurements + run_test emu "Signed measurements (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --meas --ver "$VER" - # Test 4: Challenge authentication (sessionless) - run_test emu "Challenge authentication (--challenge)" \ - "$SPDM_DEMO" --challenge + # Session + unsigned measurements + run_test emu "Unsigned measurements (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --meas --no-sig --ver "$VER" - # Test 5: Session + heartbeat - run_test emu "Heartbeat (--emu --heartbeat)" \ - "$SPDM_DEMO" --emu --heartbeat + # Challenge authentication (sessionless) + run_test emu "Challenge (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --challenge --ver "$VER" - # Test 6: Session + key update - run_test emu "Key update (--emu --key-update)" \ - "$SPDM_DEMO" --emu --key-update + # Session + heartbeat + run_test emu "Heartbeat (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --emu --heartbeat --ver "$VER" - echo "" + # Session + key update + run_test emu "Key update (SPDM $VER)" "$VER" \ + "$SPDM_DEMO" --emu --key-update --ver "$VER" + + echo "" + done fi # ========================================================================== @@ -290,27 +308,27 @@ if [ $DO_NUVOTON -eq 1 ]; then echo "" # Step 1: SPDM status query (vendor command over TIS) - run_test nuvoton "SPDM status query" "$SPDM_DEMO" --status + run_test nuvoton "SPDM status query" "" "$SPDM_DEMO" --status # Step 2: SPDM session establishment (version + keygen + handshake) - run_test nuvoton "SPDM session connect" "$SPDM_DEMO" --connect + run_test nuvoton "SPDM session connect" "" "$SPDM_DEMO" --connect # Step 3: Lock SPDM-only mode (connect + lock in one session) - run_test nuvoton "Lock SPDM-only mode" "$SPDM_DEMO" --connect --lock + run_test nuvoton "Lock SPDM-only mode" "" "$SPDM_DEMO" --connect --lock # Step 4: Unit test over SPDM (auto-detects SPDM-only, all commands encrypted) if [ -x "$UNIT_TEST" ]; then - run_test nuvoton "Unit test over SPDM" "$UNIT_TEST" + run_test nuvoton "Unit test over SPDM" "" "$UNIT_TEST" else echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" fi # Step 5: Unlock SPDM-only mode - run_test nuvoton "Unlock SPDM-only mode" "$SPDM_DEMO" --connect --unlock + run_test nuvoton "Unlock SPDM-only mode" "" "$SPDM_DEMO" --connect --unlock # Step 6: Verify cleartext TPM works (proves unlock succeeded) if [ -x "$CAPS_DEMO" ]; then - run_test nuvoton "Cleartext caps (no SPDM)" "$CAPS_DEMO" + run_test nuvoton "Cleartext caps (no SPDM)" "" "$CAPS_DEMO" else echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" fi diff --git a/spdm/README.md b/spdm/README.md index 660fbae3..e24ee6af 100644 --- a/spdm/README.md +++ b/spdm/README.md @@ -4,6 +4,13 @@ wolfTPM includes a built-in SPDM 1.2+ requester stack using wolfSSL/wolfCrypt. This provides encrypted bus communication between the host and TPM, ensuring all commands and responses are protected with AES-256-GCM. +### Supported Versions + +| Target | SPDM Versions | Notes | +|--------|---------------|-------| +| DMTF spdm-emu | 1.2, 1.3, 1.4 | Full protocol support including session, measurements, challenge, heartbeat, key update | +| Nuvoton NPCT75x | 1.3 | Hardware SPDM with SPDM-only mode and vendor-defined TPM command wrapping | + ## How It Works SPDM (Security Protocol and Data Model) establishes an authenticated encrypted @@ -15,7 +22,7 @@ automatically encrypted — no application code changes needed. ``` Host TPM (Nuvoton NPCT75x) | | - |--- GET_VERSION ------------------>| (negotiate SPDM 1.3) + |--- GET_VERSION ------------------>| (negotiate SPDM 1.2-1.4) |<-- VERSION -----------------------| | | |--- GET_PUB_KEY ------------------>| (get TPM's P-384 identity key) @@ -190,19 +197,20 @@ make The test script automatically finds `spdm_responder_emu` in `../spdm-emu/build/bin/`, starts it for each test, and runs session establishment, signed measurements, -unsigned measurements, challenge authentication, heartbeat, and key update. +unsigned measurements, challenge authentication, heartbeat, and key update +across SPDM versions 1.2, 1.3, and 1.4 (18 tests total). To run individual commands manually: ```bash -# Terminal 1: Start the emulator +# Terminal 1: Start the emulator (specify version with --ver) cd spdm-emu/build/bin -./spdm_responder_emu --ver 1.2 --hash SHA_384 --asym ECDSA_P384 \ +./spdm_responder_emu --ver 1.3 --hash SHA_384 --asym ECDSA_P384 \ --dhe SECP_384_R1 --aead AES_256_GCM -# Terminal 2: Run wolfTPM SPDM demo +# Terminal 2: Run wolfTPM SPDM demo (--ver must match emulator) cd wolfTPM -./examples/spdm/spdm_demo --emu +./examples/spdm/spdm_demo --emu --ver 1.3 ``` ### Emulator Demo Options @@ -215,6 +223,7 @@ cd wolfTPM | `--challenge` | Sessionless challenge authentication | | `--emu --heartbeat` | Session keep-alive | | `--emu --key-update` | Session key rotation | +| `--ver 1.2\|1.3\|1.4` | Maximum SPDM version to negotiate (default: 1.4) | | `--host ` | Emulator host (default: 127.0.0.1) | | `--port ` | Emulator port (default: 2323) | @@ -251,6 +260,7 @@ Useful on platforms with small stacks. | `wolfSPDM_GetCtxSize()` | Return `sizeof(WOLFSPDM_CTX)` at runtime | | `wolfSPDM_SetIO()` | Set transport I/O callback | | `wolfSPDM_SetDebug()` | Enable/disable debug output | +| `wolfSPDM_SetMaxVersion()` | Set maximum SPDM version to negotiate (0x12=1.2, 0x13=1.3, 0x14=1.4) | | `wolfSPDM_Connect()` | Full SPDM handshake | | `wolfSPDM_IsConnected()` | Check session status | | `wolfSPDM_Disconnect()` | End session | diff --git a/spdm/src/spdm_context.c b/spdm/src/spdm_context.c index 22c1dc9a..96b494a7 100644 --- a/spdm/src/spdm_context.c +++ b/spdm/src/spdm_context.c @@ -20,8 +20,6 @@ */ #include "spdm_internal.h" -#include -#include #include #include @@ -237,6 +235,27 @@ void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable) } } +int wolfSPDM_SetMaxVersion(WOLFSPDM_CTX* ctx, byte maxVersion) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* 0 means reset to compile-time default */ + if (maxVersion == 0) { + ctx->maxVersion = 0; + return WOLFSPDM_SUCCESS; + } + + /* Validate range: we only support 1.2 through 1.4 */ + if (maxVersion < SPDM_VERSION_12 || maxVersion > SPDM_VERSION_14) { + return WOLFSPDM_E_INVALID_ARG; + } + + ctx->maxVersion = maxVersion; + return WOLFSPDM_SUCCESS; +} + int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode) { if (ctx == NULL) { diff --git a/spdm/src/spdm_crypto.c b/spdm/src/spdm_crypto.c index e3cbb816..ebf13c5a 100644 --- a/spdm/src/spdm_crypto.c +++ b/spdm/src/spdm_crypto.c @@ -20,7 +20,6 @@ */ #include "spdm_internal.h" -#include /* Left-pad a buffer in-place to targetSz with leading zeros */ static void wolfSPDM_LeftPadToSize(byte* buf, word32 currentSz, word32 targetSz) @@ -119,6 +118,12 @@ int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_CRYPTO_FAIL; } + /* Left-pad coordinates to full size (wolfSSL may strip leading zeros) */ + wolfSPDM_LeftPadToSize(pubKeyX, *pubKeyXSz, WOLFSPDM_ECC_KEY_SIZE); + *pubKeyXSz = WOLFSPDM_ECC_KEY_SIZE; + wolfSPDM_LeftPadToSize(pubKeyY, *pubKeyYSz, WOLFSPDM_ECC_KEY_SIZE); + *pubKeyYSz = WOLFSPDM_ECC_KEY_SIZE; + return WOLFSPDM_SUCCESS; } diff --git a/spdm/src/spdm_internal.h b/spdm/src/spdm_internal.h index f3c8c551..f1377918 100644 --- a/spdm/src/spdm_internal.h +++ b/spdm/src/spdm_internal.h @@ -121,6 +121,7 @@ struct WOLFSPDM_CTX { WC_RNG rng; /* Negotiated parameters */ + byte maxVersion; /* Runtime max version cap (0 = use compile-time default) */ byte spdmVersion; /* Negotiated SPDM version */ word32 rspCaps; /* Responder capabilities */ word32 reqCaps; /* Our (requester) capabilities */ @@ -180,10 +181,6 @@ struct WOLFSPDM_CTX { byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; word32 reqPubKeyLen; - /* Message buffers */ - byte sendBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_TAG_SIZE]; - byte recvBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_TAG_SIZE]; - #ifndef NO_WOLFSPDM_MEAS /* Measurement data */ WOLFSPDM_MEAS_BLOCK measBlocks[WOLFSPDM_MAX_MEAS_BLOCKS]; @@ -315,7 +312,9 @@ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, /* --- Argument Validation Macros --- */ #define SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, minSz) \ - if ((ctx) == NULL || (buf) == NULL || (bufSz) == NULL || *(bufSz) < (minSz)) \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) == NULL) \ + return WOLFSPDM_E_INVALID_ARG; \ + if (*(bufSz) < (minSz)) \ return WOLFSPDM_E_BUFFER_SMALL #define SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, minSz) \ diff --git a/spdm/src/spdm_kdf.c b/spdm/src/spdm_kdf.c index 1eae369a..424b2998 100644 --- a/spdm/src/spdm_kdf.c +++ b/spdm/src/spdm_kdf.c @@ -20,7 +20,6 @@ */ #include "spdm_internal.h" -#include /* * SPDM Key Derivation (DSP0277) @@ -93,17 +92,25 @@ int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, return WOLFSPDM_E_INVALID_ARG; } + rc = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + rc = wc_HmacSetKey(&hmac, WC_SHA384, finishedKey, WOLFSPDM_HASH_SIZE); if (rc != 0) { + wc_HmacFree(&hmac); return WOLFSPDM_E_CRYPTO_FAIL; } rc = wc_HmacUpdate(&hmac, thHash, WOLFSPDM_HASH_SIZE); if (rc != 0) { + wc_HmacFree(&hmac); return WOLFSPDM_E_CRYPTO_FAIL; } rc = wc_HmacFinal(&hmac, verifyData); + wc_HmacFree(&hmac); return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } diff --git a/spdm/src/spdm_msg.c b/spdm/src/spdm_msg.c index 08582f69..df12d0c2 100644 --- a/spdm/src/spdm_msg.c +++ b/spdm/src/spdm_msg.c @@ -20,7 +20,6 @@ */ #include "spdm_internal.h" -#include #include int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) @@ -54,9 +53,9 @@ int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) SPDM_Set32LE(&buf[8], ctx->reqCaps); /* DataTransferSize (4 LE) */ - buf[12] = 0x00; buf[13] = 0x10; buf[14] = 0x00; buf[15] = 0x00; + SPDM_Set32LE(&buf[12], WOLFSPDM_MAX_MSG_SIZE); /* MaxSPDMmsgSize (4 LE) */ - buf[16] = 0x00; buf[17] = 0x10; buf[18] = 0x00; buf[19] = 0x00; + SPDM_Set32LE(&buf[16], WOLFSPDM_MAX_MSG_SIZE); *bufSz = 20; return WOLFSPDM_SUCCESS; @@ -195,7 +194,8 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) buf[offset++] = 0x10; buf[offset++] = 0x00; /* Version 1.0 (0x0010 LE) */ buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding to make OpaqueData 12 bytes */ #else - /* Standard SPDM 1.2+ OpaqueData format: 20 bytes */ + /* Standard SPDM 1.2+ OpaqueData format: 20 bytes + * OpaqueLength must be a multiple of 4 per DSP0274. */ buf[offset++] = 0x14; /* OpaqueLength = 20 */ buf[offset++] = 0x00; buf[offset++] = 0x01; buf[offset++] = 0x00; /* TotalElements */ @@ -307,6 +307,11 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) int mutualAuth = 0; int rc; + /* Check arguments first before any ctx dereference */ + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + #ifdef WOLFSPDM_NUVOTON /* Nuvoton requires mutual authentication when we have a requester key */ if (ctx->mode == WOLFSPDM_MODE_NUVOTON && ctx->flags.hasReqKeyPair) { @@ -315,15 +320,16 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) } #endif - /* Check buffer size: 148 bytes for mutual auth, 52 bytes otherwise */ - if (ctx == NULL || buf == NULL || bufSz == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - if (mutualAuth && *bufSz < 148) { - return WOLFSPDM_E_BUFFER_SMALL; - } - if (!mutualAuth && *bufSz < 52) { - return WOLFSPDM_E_BUFFER_SMALL; + /* Check buffer size: header(4) + [OpaqueLength(2) for 1.4+] + + * [signature(96) for mutual auth] + HMAC(48) */ + { + word32 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 */ @@ -338,6 +344,12 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) 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) { + buf[offset++] = 0x00; /* OpaqueLength = 0 (LE) */ + buf[offset++] = 0x00; + } + /* Per DSP0274 / libspdm: When mutual auth is requested, the transcript * for TH2 must include Hash(Cm_requester) - the hash of the requester's * public key/cert chain - BETWEEN message_k and message_f (FINISH header). @@ -357,8 +369,8 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) } #endif - /* Add FINISH header to transcript for TH2 */ - rc = wolfSPDM_TranscriptAdd(ctx, buf, 4); + /* Add FINISH header (+ OpaqueLength for 1.4) to transcript for TH2 */ + rc = wolfSPDM_TranscriptAdd(ctx, buf, offset); if (rc != WOLFSPDM_SUCCESS) { return rc; } @@ -450,11 +462,27 @@ int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode) return 0; } +/* 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. Runtime override via wolfSPDM_SetMaxVersion(). */ +#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 + int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { word16 entryCount; + word16 maxEntries; word32 i; - byte highestVersion = SPDM_VERSION_12; /* Start at 1.2, find highest supported (capped at 1.3) */ + byte highestVersion = 0; /* No version found yet */ SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 6); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_VERSION, WOLFSPDM_E_VERSION_MISMATCH); @@ -464,21 +492,38 @@ int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) * Offset 6+: VersionNumberEntry array (2 bytes each, LE) */ entryCount = SPDM_Get16LE(&buf[4]); - /* Find highest supported version from entries (capped at 1.3 for now) - * - * TODO: SPDM 1.4 fails at FINISH step with libspdm emulator returning - * InvalidRequest (0x01). KEY_EXCHANGE and key derivation work correctly - * with "spdm1.4 " prefix, but FINISH message format may differ in 1.4. - * Investigate OpaqueData format or FINISH requirements for 1.4 support. - */ - for (i = 0; i < entryCount && (6 + i * 2 + 1) < bufSz; i++) { - byte ver = buf[6 + i * 2 + 1]; /* Major.Minor in high byte */ - /* Cap at 1.3 (0x13) - SPDM 1.4 FINISH handling needs work */ - if (ver > highestVersion && ver <= SPDM_VERSION_13) { - highestVersion = ver; + /* 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; + } + + /* Find highest mutually supported version. + * Per DSP0274, negotiated version must be the highest version + * that both sides support. We support WOLFSPDM_MIN_SPDM_VERSION + * through WOLFSPDM_MAX_SPDM_VERSION (or ctx->maxVersion if set). */ + { + byte maxVer = (ctx->maxVersion != 0) ? ctx->maxVersion + : 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]; + if (ver >= WOLFSPDM_MIN_SPDM_VERSION && + ver <= maxVer && + ver > highestVersion) { + highestVersion = ver; + } } } + /* If no mutually supported version found, fail */ + if (highestVersion == 0) { + wolfSPDM_DebugPrint(ctx, "No mutually supported SPDM version found " + "(require >= 0x%02x)\n", WOLFSPDM_MIN_SPDM_VERSION); + return WOLFSPDM_E_VERSION_MISMATCH; + } + ctx->spdmVersion = highestVersion; ctx->state = WOLFSPDM_STATE_VERSION; @@ -500,9 +545,38 @@ int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { - SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + word32 baseAsymAlgo; + word32 baseHashAlgo; + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 36); SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_ALGORITHMS, 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]); + + if (!(baseAsymAlgo & SPDM_ASYM_ALGO_ECDSA_P384)) { + wolfSPDM_DebugPrint(ctx, + "ALGORITHMS: responder does not support ECDSA P-384 (0x%08x)\n", + baseAsymAlgo); + return WOLFSPDM_E_ALGO_MISMATCH; + } + if (!(baseHashAlgo & SPDM_HASH_ALGO_SHA_384)) { + wolfSPDM_DebugPrint(ctx, + "ALGORITHMS: responder does not support SHA-384 (0x%08x)\n", + baseHashAlgo); + return WOLFSPDM_E_ALGO_MISMATCH; + } + + wolfSPDM_DebugPrint(ctx, "ALGORITHMS: BaseAsym=0x%08x BaseHash=0x%08x\n", + baseAsymAlgo, baseHashAlgo); + ctx->state = WOLFSPDM_STATE_ALGO; return WOLFSPDM_SUCCESS; } @@ -587,6 +661,10 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS return rc; } + /* TODO: Verify responder signature (DSP0274) - prevents MITM key + * substitution. Requires correct TH1 transcript construction which + * differs between standard and Nuvoton modes. */ + /* Add signature to transcript (TH1 includes signature) */ rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_SIG_SIZE); if (rc != WOLFSPDM_SUCCESS) { @@ -638,8 +716,23 @@ int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) if (buf[1] == SPDM_FINISH_RSP) { int addRc; - /* Add FINISH_RSP to transcript for TH2_final (app data key derivation) */ - addRc = wolfSPDM_TranscriptAdd(ctx, buf, 4); + word32 rspMsgLen = 4; + + /* 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]); + rspMsgLen = 4 + 2 + opaqueLen; + if (bufSz < rspMsgLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + } + + /* Add FINISH_RSP (header + OpaqueData for 1.4) to transcript */ + addRc = wolfSPDM_TranscriptAdd(ctx, buf, rspMsgLen); if (addRc != WOLFSPDM_SUCCESS) { return addRc; } @@ -669,17 +762,26 @@ int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, return WOLFSPDM_E_INVALID_ARG; } - /* Size: 4 header + (requestSig ? 32 nonce + 1 slotId : 0) */ - if (requestSig && *bufSz < 37) { - return WOLFSPDM_E_BUFFER_SMALL; - } - if (!requestSig && *bufSz < 4) { - return WOLFSPDM_E_BUFFER_SMALL; + /* Size: 4 header + (requestSig ? 32 nonce + 1 slotId : 0) + * SPDM 1.3+ adds RequesterContext(8) always, plus + * OpaqueDataLength(2) when signature is requested */ + { + word32 minSz = 4; + if (requestSig) { + minSz += 32 + 1; /* Nonce + SlotIDParam */ + } + if (ctx->spdmVersion >= SPDM_VERSION_13) { + minSz += 8; /* RequesterContext (always for 1.3+) */ + if (requestSig) + minSz += 2; /* OpaqueDataLength */ + } + if (*bufSz < minSz) + return WOLFSPDM_E_BUFFER_SMALL; } buf[offset++] = ctx->spdmVersion; buf[offset++] = SPDM_GET_MEASUREMENTS; - /* Param1: bits [7:1] = MeasurementSummaryHashType, bit 0 = signature requested */ + /* Param1: bit 0 = signature requested */ buf[offset++] = requestSig ? SPDM_MEAS_REQUEST_SIG_BIT : 0x00; /* Param2: MeasurementOperation */ buf[offset++] = operation; @@ -697,6 +799,18 @@ int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, buf[offset++] = 0x00; } + /* SPDM 1.3+ adds RequesterContext (8 bytes) for both signed and unsigned. + * Per DSP0274 Table 51, this field is always present for version >= 1.3. */ + if (ctx->spdmVersion >= SPDM_VERSION_13) { + int rc = wolfSPDM_GetRandom(ctx, &buf[offset], 8); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + offset += 8; + /* Note: OpaqueDataLength is NOT part of GET_MEASUREMENTS request + * per DSP0274 Table 51 / libspdm. Only RequesterContext is added. */ + } + *bufSz = offset; return WOLFSPDM_SUCCESS; } @@ -1084,9 +1198,14 @@ int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, int slotId, byte measHashType) { word32 offset = 0; + word32 minSz; int rc; - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 36); + /* SPDM 1.3+ adds RequesterContext(8) per DSP0274 Table 46 */ + minSz = 36; + if (ctx->spdmVersion >= SPDM_VERSION_13) + minSz += 8; /* RequesterContext */ + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, minSz); buf[offset++] = ctx->spdmVersion; buf[offset++] = SPDM_CHALLENGE; @@ -1104,6 +1223,16 @@ int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, XMEMCPY(ctx->challengeNonce, &buf[offset], 32); offset += 32; + /* SPDM 1.3+ adds RequesterContext(8) per DSP0274 Table 46. + * 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; + } + offset += 8; + } + *bufSz = offset; return WOLFSPDM_SUCCESS; } @@ -1170,6 +1299,17 @@ int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, const byte* buf, } offset += opaqueLen; + /* SPDM 1.3+ adds RequesterContext (8 bytes echoed from request) + * Per DSP0274, this comes AFTER OpaqueData and BEFORE Signature */ + if (ctx->spdmVersion >= SPDM_VERSION_13) { + if (offset + 8 > bufSz) { + wolfSPDM_DebugPrint(ctx, + "CHALLENGE_AUTH: too short for RequesterContext\n"); + return WOLFSPDM_E_CHALLENGE; + } + offset += 8; + } + /* Signature starts here */ if (offset + WOLFSPDM_ECC_SIG_SIZE > bufSz) { wolfSPDM_DebugPrint(ctx, "CHALLENGE_AUTH: no room for signature\n"); diff --git a/spdm/src/spdm_nuvoton.c b/spdm/src/spdm_nuvoton.c index af388b43..f469a10e 100644 --- a/spdm/src/spdm_nuvoton.c +++ b/spdm/src/spdm_nuvoton.c @@ -34,7 +34,6 @@ #ifdef WOLFSPDM_NUVOTON #include -#include /* Check for SPDM ERROR in response payload */ #define SPDM_CHECK_ERROR_RSP(ctx, buf, sz, label) \ @@ -713,11 +712,10 @@ int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) ctx->reqPubKeyTPMTLen); if (rc != WOLFSPDM_SUCCESS) { wolfSPDM_DebugPrint(ctx, "GIVE_PUB failed: %d\n", rc); - /* Don't fail - continue to FINISH for debug */ - } - else { - wolfSPDM_DebugPrint(ctx, "GIVE_PUB succeeded!\n"); + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; } + wolfSPDM_DebugPrint(ctx, "GIVE_PUB succeeded!\n"); } else { wolfSPDM_DebugPrint(ctx, "Nuvoton Step 4: GIVE_PUB (skipped, no host key)\n"); diff --git a/spdm/src/spdm_secured.c b/spdm/src/spdm_secured.c index e36034cd..3b190ef3 100644 --- a/spdm/src/spdm_secured.c +++ b/spdm/src/spdm_secured.c @@ -20,7 +20,6 @@ */ #include "spdm_internal.h" -#include /* * SPDM Secured Message Format (DSP0277): @@ -37,63 +36,6 @@ * Full message: Header || Ciphertext || Tag (16) */ -#ifdef WOLFSPDM_NUVOTON -/* Self-test: verify AES-GCM encrypt/decrypt round-trip with current keys. - * Called before first encrypted message to confirm crypto parameters. */ -static int wolfSPDM_AesGcmSelfTest(WOLFSPDM_CTX* ctx) -{ - Aes aesEnc, aesDec; - byte testPlain[] = "wolfSPDM AES-GCM self-test 1234"; /* 31 bytes */ - byte testCipher[32]; - byte testDecrypted[32]; - byte testTag[WOLFSPDM_AEAD_TAG_SIZE]; - byte testAad[14]; - word32 testPlainSz = sizeof(testPlain); - int rc; - - /* Build AAD matching what we'd use for SeqNum=0 */ - SPDM_Set32LE(&testAad[0], ctx->sessionId); - XMEMSET(&testAad[4], 0, 8); /* SeqNum = 0 */ - SPDM_Set16LE(&testAad[12], (word16)(testPlainSz + 16)); - - /* Encrypt */ - rc = wc_AesGcmSetKey(&aesEnc, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "Self-test: AesGcmSetKey (enc) failed: %d\n", rc); - return rc; - } - rc = wc_AesGcmEncrypt(&aesEnc, testCipher, testPlain, testPlainSz, - ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE, - testTag, WOLFSPDM_AEAD_TAG_SIZE, testAad, 14); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "Self-test: AesGcmEncrypt failed: %d\n", rc); - return rc; - } - - /* Decrypt with same key */ - rc = wc_AesGcmSetKey(&aesDec, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "Self-test: AesGcmSetKey (dec) failed: %d\n", rc); - return rc; - } - rc = wc_AesGcmDecrypt(&aesDec, testDecrypted, testCipher, testPlainSz, - ctx->reqDataIv, WOLFSPDM_AEAD_IV_SIZE, - testTag, WOLFSPDM_AEAD_TAG_SIZE, testAad, 14); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "Self-test: AesGcmDecrypt FAILED: %d\n", rc); - return rc; - } - - /* Verify plaintext matches */ - if (XMEMCMP(testPlain, testDecrypted, testPlainSz) != 0) { - wolfSPDM_DebugPrint(ctx, "Self-test: Plaintext mismatch!\n"); - return -1; - } - - wolfSPDM_DebugPrint(ctx, "Self-test: AES-GCM round-trip PASSED\n"); - return 0; -} -#endif /* WOLFSPDM_NUVOTON */ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, const byte* plain, word32 plainSz, @@ -122,14 +64,6 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, */ word16 appDataLen = (word16)plainSz; - /* Run self-test before first encrypted message */ - if (ctx->reqSeqNum == 0) { - rc = wolfSPDM_AesGcmSelfTest(ctx); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "AES-GCM self-test FAILED: %d\n", rc); - return WOLFSPDM_E_CRYPTO_FAIL; - } - } word16 unpadded = (word16)(2 + appDataLen); /* AppDataLength + SPDM msg */ word16 padLen = (word16)((16 - (unpadded % 16)) % 16); /* Pad to 16-byte boundary */ word16 encPayloadSz = (word16)(unpadded + padLen); @@ -148,13 +82,9 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, XMEMCPY(&plainBuf[2], plain, plainSz); /* Fill RandomData with actual random bytes per Nuvoton spec */ if (padLen > 0) { - WC_RNG rng; - if (wc_InitRng(&rng) == 0) { - wc_RNG_GenerateBlock(&rng, &plainBuf[unpadded], padLen); - wc_FreeRng(&rng); - } else { - /* Fallback to zeros if RNG fails */ - XMEMSET(&plainBuf[unpadded], 0, padLen); + rc = wolfSPDM_GetRandom(ctx, &plainBuf[unpadded], padLen); + if (rc != WOLFSPDM_SUCCESS) { + return rc; } } @@ -203,14 +133,20 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, wolfSPDM_BuildIV(iv, ctx->reqDataIv, ctx->reqSeqNum, ctx->mode == WOLFSPDM_MODE_NUVOTON); + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } rc = wc_AesGcmSetKey(&aes, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); if (rc != 0) { + wc_AesFree(&aes); return WOLFSPDM_E_CRYPTO_FAIL; } /* 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); + wc_AesFree(&aes); if (rc != 0) { return WOLFSPDM_E_CRYPTO_FAIL; } @@ -276,6 +212,14 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_SESSION_INVALID; } + /* Validate sequence number matches expected (DSP0277 replay protection) */ + if (rspSeqNum64 != ctx->rspSeqNum) { + wolfSPDM_DebugPrint(ctx, "Sequence number mismatch: %llu != %llu\n", + (unsigned long long)rspSeqNum64, + (unsigned long long)ctx->rspSeqNum); + return WOLFSPDM_E_SEQUENCE; + } + /* Length field = ciphertext + MAC (per Nuvoton spec) */ if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < hdrSz + rspLen) { return WOLFSPDM_E_BUFFER_SMALL; @@ -290,13 +234,19 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, /* Build IV: BaseIV XOR sequence number (DSP0277 1.2) */ wolfSPDM_BuildIV(iv, ctx->rspDataIv, rspSeqNum64, 1); + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); if (rc != 0) { + wc_AesFree(&aes); return WOLFSPDM_E_CRYPTO_FAIL; } rc = wc_AesGcmDecrypt(&aes, decrypted, ciphertext, cipherLen, iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); + wc_AesFree(&aes); if (rc != 0) { wolfSPDM_DebugPrint(ctx, "AES-GCM decrypt failed: %d\n", rc); return WOLFSPDM_E_DECRYPT_FAIL; @@ -339,6 +289,13 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_SESSION_INVALID; } + /* Validate sequence number matches expected (DSP0277 replay protection) */ + if ((word64)rspSeqNum != ctx->rspSeqNum) { + wolfSPDM_DebugPrint(ctx, "Sequence number 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; } @@ -352,13 +309,19 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, /* Build IV: BaseIV XOR sequence number (DSP0277) */ wolfSPDM_BuildIV(iv, ctx->rspDataIv, (word64)rspSeqNum, 0); + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); if (rc != 0) { + wc_AesFree(&aes); return WOLFSPDM_E_CRYPTO_FAIL; } rc = wc_AesGcmDecrypt(&aes, decrypted, ciphertext, cipherLen, iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); + wc_AesFree(&aes); if (rc != 0) { wolfSPDM_DebugPrint(ctx, "AES-GCM decrypt failed: %d\n", rc); return WOLFSPDM_E_DECRYPT_FAIL; diff --git a/spdm/src/spdm_session.c b/spdm/src/spdm_session.c index 32ab905d..223526be 100644 --- a/spdm/src/spdm_session.c +++ b/spdm/src/spdm_session.c @@ -20,7 +20,6 @@ */ #include "spdm_internal.h" -#include /* Callback types for build/parse functions */ typedef int (*wolfSPDM_BuildFn)(WOLFSPDM_CTX*, byte*, word32*); @@ -38,12 +37,14 @@ static int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, rc = buildFn(ctx, txBuf, &txSz); if (rc != WOLFSPDM_SUCCESS) return rc; - wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + if (rc != WOLFSPDM_SUCCESS) return rc; rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); if (rc != WOLFSPDM_SUCCESS) return rc; - wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); + rc = wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); + if (rc != WOLFSPDM_SUCCESS) return rc; return parseFn(ctx, rxBuf, rxSz); } @@ -351,6 +352,16 @@ int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, byte measOperation, return rc; } + /* Check for SPDM error before parsing measurements */ + { + int errCode = 0; + if (wolfSPDM_CheckError(rxBuf, rxSz, &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) { @@ -392,7 +403,7 @@ int wolfSPDM_GetMeasurements(WOLFSPDM_CTX* ctx, byte measOperation, int wolfSPDM_Challenge(WOLFSPDM_CTX* ctx, int slotId, byte measHashType) { - byte txBuf[48]; /* CHALLENGE: 36 bytes */ + byte txBuf[48]; /* CHALLENGE: 36 bytes (1.2) or 46 bytes (1.3+) */ byte rxBuf[512]; /* CHALLENGE_AUTH: variable, up to ~300+ bytes */ word32 txSz = sizeof(txBuf); word32 rxSz = sizeof(rxBuf); diff --git a/spdm/src/spdm_transcript.c b/spdm/src/spdm_transcript.c index 0bf422cf..893e5d9c 100644 --- a/spdm/src/spdm_transcript.c +++ b/spdm/src/spdm_transcript.c @@ -20,7 +20,6 @@ */ #include "spdm_internal.h" -#include /* --- Transcript Management --- * VCA = GET_VERSION || VERSION || GET_CAPS || CAPS || NEG_ALGO || ALGO diff --git a/spdm/test/unit_test.c b/spdm/test/unit_test.c index 941cb041..bdddbd68 100644 --- a/spdm/test/unit_test.c +++ b/spdm/test/unit_test.c @@ -939,6 +939,178 @@ static int test_key_update_state_check(void) TEST_PASS(); } +/* ========================================================================== */ +/* Multi-Version Tests */ +/* ========================================================================== */ + +static int test_kdf_version_prefix(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_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"); + + TEST_PASS(); +} + +static int test_transcript_overflow(void) +{ + byte chunk[256]; + word32 i, needed; + TEST_CTX_SETUP(); + + printf("test_transcript_overflow...\n"); + + memset(chunk, 0x42, sizeof(chunk)); + + /* 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(); +} + +#ifndef NO_WOLFSPDM_MEAS +static int test_parse_measurements_negative(void) +{ + byte truncated[] = {0x12, 0x60, 0x00, 0x00, 0x01}; + byte wrongCode[] = {0x12, 0xFF, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00}; + TEST_CTX_SETUP(); + + printf("test_parse_measurements_negative...\n"); + + /* Truncated buffer */ + ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, truncated, sizeof(truncated))); + + /* Wrong response code */ + ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, wrongCode, sizeof(wrongCode))); + + /* 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)); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* !NO_WOLFSPDM_MEAS */ + +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(); + + printf("test_version_fallback...\n"); + + /* 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"); + + /* 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(); +} + +static int test_set_max_version(void) +{ + TEST_CTX_SETUP(); + + printf("test_set_max_version...\n"); + + /* 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"); + + /* Reset to default */ + ASSERT_SUCCESS(wolfSPDM_SetMaxVersion(ctx, 0)); + ASSERT_EQ(ctx->maxVersion, 0, "maxVersion should be 0 (default)"); + + /* 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)); + + TEST_CTX_FREE(); + TEST_PASS(); +} + /* ========================================================================== */ /* Session State Tests */ /* ========================================================================== */ @@ -1037,6 +1209,16 @@ int main(void) 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(); +#endif + test_version_fallback(); + test_set_max_version(); + /* Session state tests */ test_session_state(); diff --git a/spdm/wolfspdm/spdm.h b/spdm/wolfspdm/spdm.h index 7a419374..19f5012e 100644 --- a/spdm/wolfspdm/spdm.h +++ b/spdm/wolfspdm/spdm.h @@ -230,6 +230,18 @@ WOLFSPDM_API int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode); */ WOLFSPDM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); +/** + * 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); + /** * Set the responder's public key for certificate-less operation. * Used when the responder doesn't send a certificate chain (e.g., Nuvoton TPM). diff --git a/spdm/wolfspdm/spdm_types.h b/spdm/wolfspdm/spdm_types.h index 59e632c4..458448e3 100644 --- a/spdm/wolfspdm/spdm_types.h +++ b/spdm/wolfspdm/spdm_types.h @@ -54,6 +54,7 @@ extern "C" { #define SPDM_VERSION_11 0x11 /* SPDM 1.1 */ #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 */ From d58702eb8b2f325981e375622391551fde3167ba Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 13 Mar 2026 01:00:28 +0000 Subject: [PATCH 3/3] Security hardening, code review fixes, and codebase reduction (-4,301 lines) Security fixes: - Mandatory responder signature verification in KEY_EXCHANGE_RSP (was conditional skip) - Sensitive stack buffer zeroing (wc_ForceZero) in BuildFinish, ParseKeyExchangeRsp, wolfSPDM_Finish for keys, HMAC, and signature data - TCG integer underflow guard in ParseTcgClearMessage (msgSize < header check) - BuildIV: removed dead code duplicate branches, collapsed to single 8-byte XOR path Code quality: - Cascade error handling (rc == WOLFSPDM_SUCCESS pattern) across spdm_msg.c, spdm_session.c, spdm_secured.c for single-cleanup-path safety - wolfSPDM_BuildVendorDefined: added spdmVersion parameter (was hardcoded 0x13) - spdm_error.h: added spdm_types.h include for standalone WOLFSPDM_API definition - spdm.h: added stack usage documentation (~22KB context, ~20KB call chain) Codebase reduction (-4,301 net lines): - Condensed spdm_demo.c (382->256 lines), spdm_test.sh (143->70 lines) - Removed verbose banner comments and redundant section headers across all files - Consolidated unit tests with shared helpers, removed duplicate test patterns - Removed spdm-emu-test.yml CI workflow (moved to standalone wolfSPDM repo) - Streamlined README documentation Test results: - 26/26 unit tests PASS - 6/6 Nuvoton hardware tests PASS (spdm_test.sh) --- .github/workflows/make-test-swtpm.yml | 7 +- .github/workflows/spdm-emu-test.yml | 76 -- configure.ac | 6 +- examples/spdm/README.md | 112 ++- examples/spdm/spdm_demo.c | 818 ++--------------- examples/spdm/spdm_test.sh | 348 +------ spdm/README.md | 114 +-- spdm/src/spdm_context.c | 258 ++---- spdm/src/spdm_crypto.c | 230 +++-- spdm/src/spdm_internal.h | 310 +------ spdm/src/spdm_kdf.c | 262 ++---- spdm/src/spdm_msg.c | 1214 ++++--------------------- spdm/src/spdm_nuvoton.c | 213 +---- spdm/src/spdm_secured.c | 354 ++----- spdm/src/spdm_session.c | 552 +---------- spdm/src/spdm_transcript.c | 36 +- spdm/test/unit_test.c | 1104 ++++++++-------------- spdm/wolfspdm/spdm.h | 546 +---------- spdm/wolfspdm/spdm_error.h | 17 +- spdm/wolfspdm/spdm_nuvoton.h | 74 +- spdm/wolfspdm/spdm_types.h | 148 +-- src/tpm2.c | 150 +-- src/tpm2_spdm.c | 9 +- src/tpm2_wrap.c | 2 + wolftpm/tpm2.h | 21 - wolftpm/tpm2_spdm.h | 4 +- 26 files changed, 1335 insertions(+), 5650 deletions(-) delete mode 100644 .github/workflows/spdm-emu-test.yml diff --git a/.github/workflows/make-test-swtpm.yml b/.github/workflows/make-test-swtpm.yml index 0b4c3b9f..2595237c 100644 --- a/.github/workflows/make-test-swtpm.yml +++ b/.github/workflows/make-test-swtpm.yml @@ -75,10 +75,6 @@ jobs: # STMicro ST33KTPM2 - name: st33ktpm2 firmware wolftpm_config: --enable-st33 --enable-firmware - # SPDM (emulator mode, compile + unit test) - - name: spdm - wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp - wolftpm_config: --enable-spdm --enable-swtpm # SPDM + Nuvoton (compile-only, no hardware in CI) - name: spdm-nuvoton wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp @@ -87,7 +83,8 @@ jobs: # SPDM dynamic memory - name: spdm-dynamic-mem wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp - wolftpm_config: --enable-spdm --enable-swtpm --enable-spdm-dynamic-mem + wolftpm_config: --enable-spdm --enable-nuvoton --enable-spdm-dynamic-mem + needs_swtpm: false # SPDM debug - name: spdm-debug wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp diff --git a/.github/workflows/spdm-emu-test.yml b/.github/workflows/spdm-emu-test.yml deleted file mode 100644 index e45b27e4..00000000 --- a/.github/workflows/spdm-emu-test.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: SPDM Emulator Integration Test - -on: - push: - branches: [ 'master', 'main', 'release/**' ] - paths: [ 'spdm/**', 'src/tpm2_spdm.c', 'examples/spdm/**' ] - pull_request: - branches: [ '*' ] - paths: [ 'spdm/**', 'src/tpm2_spdm.c', 'examples/spdm/**' ] - -jobs: - spdm-emu-test: - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-22.04 - arch: x64 - - os: ubuntu-24.04 - arch: x64 - - os: ubuntu-24.04-arm - arch: aarch64 - runs-on: ${{ matrix.os }} - steps: - - name: Checkout wolfTPM - uses: actions/checkout@v4 - - - name: Cache wolfSSL - id: cache-wolfssl - uses: actions/cache@v4 - with: - path: wolfssl - key: wolfssl-spdm-${{ matrix.os }}-${{ hashFiles('.github/workflows/spdm-emu-test.yml') }} - - - name: Build wolfSSL - if: steps.cache-wolfssl.outputs.cache-hit != 'true' - run: | - 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 - make -j$(nproc) - - - name: Install wolfSSL - run: | - cd wolfssl - sudo make install - sudo ldconfig - - - name: Cache spdm-emu - id: cache-spdm-emu - uses: actions/cache@v4 - with: - path: spdm-emu/build/bin - key: spdm-emu-${{ matrix.os }}-${{ hashFiles('.github/workflows/spdm-emu-test.yml') }} - - - name: Build spdm-emu - if: steps.cache-spdm-emu.outputs.cache-hit != 'true' - run: | - 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) - - - name: Build wolfTPM with SPDM - run: | - ./autogen.sh - ./configure --enable-spdm --enable-swtpm --enable-debug - make -j$(nproc) - - - name: Run SPDM emulator tests - run: | - export SPDM_EMU_PATH=$PWD/spdm-emu/build/bin - ./examples/spdm/spdm_test.sh --emu diff --git a/configure.ac b/configure.ac index 665c1d05..64e36b4e 100644 --- a/configure.ac +++ b/configure.ac @@ -488,13 +488,9 @@ then # Add spdm/ include path so all targets can find AM_CPPFLAGS="$AM_CPPFLAGS -I\$(srcdir)/spdm" - # Nuvoton SPDM support + # Nuvoton SPDM support (required for SPDM in wolfTPM) if test "x$ENABLED_NUVOTON" = "xyes" then - if test "x$ENABLED_SWTPM" = "xyes" - then - AC_MSG_ERROR([Cannot enable both swtpm and nuvoton with SPDM. Use --enable-swtpm --enable-spdm for emulator testing, or --enable-nuvoton --enable-spdm for hardware.]) - fi AC_DEFINE([WOLFSPDM_NUVOTON], [1], [Enable SPDM Nuvoton TPM support]) AC_MSG_NOTICE([Nuvoton SPDM vendor commands enabled]) fi diff --git a/examples/spdm/README.md b/examples/spdm/README.md index af3357dd..377cd485 100644 --- a/examples/spdm/README.md +++ b/examples/spdm/README.md @@ -1,90 +1,88 @@ -# SPDM Examples +# TPM SPDM Examples -This directory contains examples demonstrating SPDM (Security Protocol and Data Model) -functionality with wolfTPM. +This directory contains the SPDM demo for Nuvoton NPCT75x TPMs with wolfTPM. ## Overview -The SPDM demo (`spdm_demo`) shows how to establish an SPDM secure session between -the host and a TPM using the built-in wolfSPDM library. It supports both the standard -spdm-emu emulator and Nuvoton hardware TPMs. +The `spdm_demo` establishes an SPDM secure session between the host and a +Nuvoton TPM over SPI, enabling AES-256-GCM encrypted bus communication. Once +active, all TPM commands are automatically encrypted with no application changes. -For real SPDM support on hardware TPMs, contact **support@wolfssl.com** +For standard SPDM protocol support (spdm-emu, measurements, challenge, etc.), +see the [wolfSPDM](https://github.com/aidangarske/wolfSPDM) standalone library. -## Example +## Building -### `spdm_demo.c` - SPDM Secure Session Demo +### Prerequisites -**Quick test (emulator — starts/stops automatically):** +wolfSSL with crypto algorithms required for SPDM Algorithm Set B: ```bash -./examples/spdm/spdm_test.sh --emu +cd wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp +make && sudo make install && sudo ldconfig ``` -Runs session establishment, signed measurements, unsigned measurements, -challenge authentication, heartbeat, and key update. - -**Quick test (Nuvoton hardware):** +### wolfTPM with Nuvoton SPDM ```bash -./examples/spdm/spdm_test.sh --nuvoton +cd wolfTPM +./autogen.sh +./configure --enable-spdm --enable-nuvoton +make ``` -Runs connect, lock, caps-over-SPDM, unlock, and cleartext verification. +## Demo Commands -**Manual commands:** +| Option | Description | +|--------|-------------| +| `--enable` | Enable SPDM on TPM via NTC2_PreConfig (one-time, requires reset) | +| `--disable` | Disable SPDM on TPM via NTC2_PreConfig (requires reset) | +| `--status` | Query SPDM status from TPM | +| `--get-pubkey` | Get TPM's SPDM-Identity P-384 public key | +| `--connect` | Establish SPDM session (ECDH P-384 handshake) | +| `--lock` | Lock SPDM-only mode (use with `--connect`) | +| `--unlock` | Unlock SPDM-only mode (use with `--connect`) | -```bash -# Emulator (start spdm_responder_emu first, see spdm/README.md) -./spdm_demo --emu # Session only -./spdm_demo --meas # Session + signed measurements -./spdm_demo --meas --no-sig # Session + unsigned measurements -./spdm_demo --challenge # Sessionless challenge authentication -./spdm_demo --emu --heartbeat # Session + heartbeat keep-alive -./spdm_demo --emu --key-update # Session + key rotation - -# Nuvoton hardware -./spdm_demo --enable # Enable SPDM on TPM (one-time, requires reset) -./spdm_demo --connect --status # Connect + get SPDM status -./spdm_demo --connect --lock # Connect + lock SPDM-only mode -./spdm_demo --connect --caps # Connect + run TPM commands over SPDM -./spdm_demo --connect --unlock # Connect + unlock SPDM-only mode -``` +## Usage Examples -## Building +```bash +# One-time setup: enable SPDM + GPIO reset +./examples/spdm/spdm_demo --enable +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 -### Prerequisites +# Query SPDM status +./examples/spdm/spdm_demo --status -Build wolfSSL with the cryptographic algorithms required by SPDM: +# Get TPM identity key +./examples/spdm/spdm_demo --get-pubkey -```bash -# wolfSSL (needs ECC P-384, SHA-384, AES-GCM, HKDF for SPDM) -cd wolfssl -./autogen.sh -./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp -make && sudo make install && sudo ldconfig -``` +# Establish SPDM session +./examples/spdm/spdm_demo --connect -### wolfTPM with SPDM +# Lock SPDM-only mode (connect + lock in one session) +./examples/spdm/spdm_demo --connect --lock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 -SPDM support is built into wolfTPM (no external wolfSPDM needed): +# All commands now auto-encrypt: +./examples/wrap/caps # auto-SPDM, AES-256-GCM encrypted +./tests/unit.test # full test suite over encrypted bus -```bash -cd wolfTPM -./autogen.sh -./configure --enable-spdm -make +# Unlock SPDM-only mode +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect --unlock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 ``` -For Nuvoton hardware TPMs, add `--enable-nuvoton`: +## Automated Test Suite + +Runs 6 tests: status, connect, lock, unit test over SPDM, unlock, cleartext caps setup lifecycle on hardware. ```bash -./configure --enable-spdm --enable-nuvoton -make +./examples/spdm/spdm_test.sh ``` ## Support -For production use with hardware TPMs and full SPDM protocol support, contact: - -**support@wolfssl.com** +For production use with hardware TPMs and SPDM support, contact **support@wolfssl.com**. diff --git a/examples/spdm/spdm_demo.c b/examples/spdm/spdm_demo.c index 198eb54b..be6e5ea5 100644 --- a/examples/spdm/spdm_demo.c +++ b/examples/spdm/spdm_demo.c @@ -19,22 +19,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -/* SPDM Secure Session Demo - * - * Demonstrates establishing an SPDM secure session with a TPM - * and running TPM commands over the encrypted channel. - * - * Targets: Nuvoton NPCT75x (Fw 7.2+) connected via SPI - * - * Usage: - * ./spdm_demo --enable Enable SPDM on TPM (requires reset) - * ./spdm_demo --disable Disable SPDM on TPM (requires reset) - * ./spdm_demo --status Query SPDM status - * ./spdm_demo --connect Establish SPDM session and run test command - * ./spdm_demo --lock Lock SPDM-only mode - * ./spdm_demo --unlock Unlock SPDM-only mode - */ - #ifdef HAVE_CONFIG_H #include #endif @@ -46,18 +30,6 @@ #include #include -/* Socket includes for TCP transport to libspdm emulator */ -#ifdef WOLFTPM_SWTPM - #include - #include - #include /* TCP_NODELAY */ - #include - #include - #include - #define SPDM_EMU_DEFAULT_PORT 2323 /* DEFAULT_SPDM_PLATFORM_PORT (MCTP) */ - #define SPDM_EMU_DEFAULT_HOST "127.0.0.1" -#endif - #ifndef WOLFTPM2_NO_WRAPPER #include @@ -66,272 +38,39 @@ #ifdef WOLFTPM_SPDM #include - #include -/* -------------------------------------------------------------------------- */ -/* Unified SPDM I/O Layer - * - * Single I/O callback that handles both: - * - TCP transport to libspdm emulator (--emu mode) - * - TPM TIS transport to Nuvoton hardware (--connect mode) - * - * The callback gates internally based on the transport mode set in context. - * -------------------------------------------------------------------------- */ - -#ifdef WOLFTPM_SWTPM -/* Transport modes for I/O callback */ -typedef enum { - SPDM_IO_MODE_NONE = 0, /* Not configured */ - SPDM_IO_MODE_TCP = 1 /* TCP socket to libspdm emulator */ -} SPDM_IO_MODE; - -/* I/O context for TCP emulator mode */ -typedef struct { - SPDM_IO_MODE mode; - int sockFd; - int isSecured; -} SPDM_IO_CTX; - -/* Global I/O context for emulator */ -static SPDM_IO_CTX g_ioCtx; -#endif /* WOLFTPM_SWTPM */ - -/******************************************************************************/ -/* --- SPDM Demo --- */ -/******************************************************************************/ - -/* Forward declarations */ int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]); static void usage(void) { - printf("SPDM Secure Session Demo\n"); - printf("Demonstrates SPDM secure communication with Nuvoton NPCT75x\n"); - printf("\n"); - printf("Usage: spdm_demo [options]\n"); - printf("Options:\n"); - printf(" --enable Enable SPDM on TPM via NTC2_PreConfig\n"); - printf(" --disable Disable SPDM on TPM via NTC2_PreConfig\n"); - printf(" --status Query SPDM status from TPM\n"); - printf(" --get-pubkey Get TPM's SPDM-Identity public key\n"); - printf(" --connect Establish SPDM session and run test command\n"); - printf(" --lock Lock SPDM-only mode\n"); - printf(" --unlock Unlock SPDM-only mode\n"); -#ifdef WOLFTPM_SWTPM - printf(" --emu Test SPDM with libspdm emulator (TCP)\n"); - printf(" --meas Retrieve and verify device measurements (--emu)\n"); - printf(" --no-sig Skip signature verification (use with --meas)\n"); - printf(" --challenge Challenge authentication (sessionless, --emu)\n"); - printf(" --heartbeat Session heartbeat keep-alive (--emu)\n"); - printf(" --key-update Session key rotation (--emu)\n"); - printf(" --ver SPDM version cap: 1.2, 1.3, or 1.4 (default: highest)\n"); - printf(" --host Emulator IP address (default: 127.0.0.1)\n"); - printf(" --port Emulator port (default: 2323)\n"); -#endif - printf(" -h, --help Show this help message\n"); - printf("\n"); - printf("Nuvoton Hardware Mode (--enable, --connect, etc.):\n"); - printf(" - Requires Nuvoton NPCT75x TPM with Fw 7.2+ via SPI\n"); - printf(" - Built with: ./configure --enable-spdm --enable-nuvoton\n"); -#ifdef WOLFTPM_SWTPM - printf("\n"); - printf("Emulator Mode (--emu):\n"); - printf(" - Tests SPDM 1.2 protocol with libspdm responder emulator\n"); - printf(" - Built with: ./configure --enable-spdm --enable-swtpm\n"); - printf(" - Start emulator: ./spdm_responder_emu\n"); - printf(" - Run test: ./spdm_demo --emu\n"); -#endif + printf("SPDM Demo - Nuvoton NPCT75x secure session\n\n" + "Usage: spdm_demo [options]\n" + " --enable Enable SPDM via NTC2_PreConfig\n" + " --disable Disable SPDM via NTC2_PreConfig\n" + " --status Query SPDM status\n" + " --get-pubkey Get TPM's SPDM-Identity public key\n" + " --connect Establish SPDM session\n" + " --lock Lock SPDM-only mode\n" + " --unlock Unlock SPDM-only mode\n" + " -h, --help Show this help\n\n" + "Build: ./configure --enable-spdm --enable-nuvoton\n"); } -/* -------------------------------------------------------------------------- */ -/* Unified I/O Callback Implementation - * -------------------------------------------------------------------------- */ - -#ifdef WOLFTPM_SWTPM -/* MCTP transport constants */ -#define SOCKET_SPDM_COMMAND_NORMAL 0x00000001 -#define MCTP_MESSAGE_TYPE_SPDM 0x05 -#define MCTP_MESSAGE_TYPE_SECURED 0x06 - -/* Initialize I/O context for TCP mode (emulator) */ -static int spdm_io_init_tcp(SPDM_IO_CTX* ioCtx, const char* host, int port) -{ - int sockFd; - struct sockaddr_in addr; - int optVal = 1; - - XMEMSET(ioCtx, 0, sizeof(*ioCtx)); - ioCtx->mode = SPDM_IO_MODE_NONE; - ioCtx->sockFd = -1; - - sockFd = socket(AF_INET, SOCK_STREAM, 0); - if (sockFd < 0) { - printf("TCP: Failed to create socket (%d)\n", errno); - return -1; - } - - /* Disable Nagle's algorithm for immediate send */ - setsockopt(sockFd, IPPROTO_TCP, TCP_NODELAY, &optVal, sizeof(optVal)); - - XMEMSET(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { - printf("TCP: Invalid address %s\n", host); - close(sockFd); - return -1; - } - - if (connect(sockFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { - printf("TCP: Failed to connect to %s:%d (%d)\n", host, port, errno); - close(sockFd); - return -1; - } - - ioCtx->mode = SPDM_IO_MODE_TCP; - ioCtx->sockFd = sockFd; - return 0; -} - -/* Cleanup TCP I/O context */ -static void spdm_io_cleanup(SPDM_IO_CTX* ioCtx) -{ - if (ioCtx->sockFd >= 0) { - close(ioCtx->sockFd); - ioCtx->sockFd = -1; - } - ioCtx->mode = SPDM_IO_MODE_NONE; -} - -/* Internal: TCP send/receive for emulator */ -static int spdm_io_tcp_exchange(SPDM_IO_CTX* ioCtx, - const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz) -{ - byte sendBuf[512]; - byte recvHdr[12]; - ssize_t sent, recvd; - word32 respSize; - word32 payloadSz; - int isSecured = 0; - - if (ioCtx->sockFd < 0) { - return -1; - } - - /* Detect secured messages: SPDM messages start with version (0x10-0x1F), - * secured messages start with SessionID (typically 0xFF...). */ - if (txSz >= 8 && (txBuf[0] < 0x10 || txBuf[0] > 0x1F)) { - isSecured = 1; - } - - /* Payload = MCTP header (1 byte) + SPDM message */ - payloadSz = 1 + txSz; - if (12 + payloadSz > sizeof(sendBuf)) { - return -1; - } - - /* Build 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 header: 0x05 for SPDM, 0x06 for secured SPDM */ - sendBuf[12] = isSecured ? MCTP_MESSAGE_TYPE_SECURED : MCTP_MESSAGE_TYPE_SPDM; - - if (txSz > 0) { - XMEMCPY(sendBuf + 13, txBuf, txSz); - } - - sent = send(ioCtx->sockFd, sendBuf, 12 + payloadSz, 0); - if (sent != (ssize_t)(12 + payloadSz)) { - return -1; - } - - recvd = recv(ioCtx->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 */ - { - byte mctpHdr; - recvd = recv(ioCtx->sockFd, &mctpHdr, 1, MSG_WAITALL); - if (recvd != 1) return -1; - } - - *rxSz = respSize - 1; - if (*rxSz > 0) { - recvd = recv(ioCtx->sockFd, rxBuf, *rxSz, MSG_WAITALL); - if (recvd != (ssize_t)*rxSz) return -1; - } - - return 0; -} -#endif /* WOLFTPM_SWTPM */ - -#ifdef WOLFTPM_SWTPM -/* I/O callback for TCP mode (emulator only). - * Nuvoton TPM mode uses the built-in library callback via - * wolfTPM2_SpdmSetNuvotonIo(). */ -static int wolfspdm_io_callback( - WOLFSPDM_CTX* ctx, - const byte* txBuf, word32 txSz, - byte* rxBuf, word32* rxSz, - void* userCtx) -{ - SPDM_IO_CTX* ioCtx = (SPDM_IO_CTX*)userCtx; - - if (ioCtx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) { - return -1; - } - - (void)ctx; - - if (ioCtx->mode == SPDM_IO_MODE_TCP) { - return spdm_io_tcp_exchange(ioCtx, txBuf, txSz, rxBuf, rxSz); - } - - return -1; -} -#endif /* WOLFTPM_SWTPM */ - -/* -------------------------------------------------------------------------- */ -/* Nuvoton-Specific Demo Functions - * -------------------------------------------------------------------------- */ - #ifdef WOLFSPDM_NUVOTON static int demo_enable(WOLFTPM2_DEV* dev) { int rc; - - printf("\n=== Enable SPDM on TPM ===\n"); - printf("Sending NTC2_PreConfig to enable SPDM (CFG_H bit 1 = 0)...\n"); - + printf("\n=== Enable SPDM ===\n"); rc = wolfTPM2_SpdmEnable(dev); if (rc == 0) { - printf(" SUCCESS: SPDM is enabled on this TPM (was already enabled " - "or just configured).\n"); - printf(" If newly enabled, TPM must be reset to take effect.\n"); + printf(" SPDM enabled (reset TPM if newly configured)\n"); } else if (rc == (int)TPM_RC_DISABLED) { - printf(" SPDM-only mode is active - TPM commands are blocked.\n"); - printf(" SPDM is already enabled (this is not an error).\n"); - rc = 0; /* Not an error - SPDM is already active */ + printf(" SPDM-only active (already enabled)\n"); + rc = 0; } else if (rc == TPM_RC_COMMAND_CODE) { - printf(" NOTE: NTC2_PreConfig not supported on this TPM.\n"); - printf(" SPDM may already be enabled, or use vendor tools to enable.\n"); - rc = 0; /* Not a fatal error for demo */ + printf(" NTC2_PreConfig not supported (may already be enabled)\n"); + rc = 0; } else { printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); } @@ -341,68 +80,51 @@ static int demo_enable(WOLFTPM2_DEV* dev) static int demo_disable(WOLFTPM2_DEV* dev) { int rc; - - printf("\n=== Disable SPDM on TPM ===\n"); - printf("Sending NTC2_PreConfig to disable SPDM (CFG_H bit 1 = 1)...\n"); - + printf("\n=== Disable SPDM ===\n"); rc = wolfTPM2_SpdmDisable(dev); if (rc == 0) { - printf(" SUCCESS: SPDM is disabled on this TPM.\n"); - printf(" TPM must be reset for changes to take effect.\n"); + printf(" SPDM disabled (reset TPM for effect)\n"); } else if (rc == (int)TPM_RC_DISABLED) { - printf(" SPDM-only mode is active - cannot disable via cleartext.\n"); - printf(" Unlock SPDM-only mode first, then reset and disable.\n"); + printf(" SPDM-only active - unlock first, then reset and disable\n"); } else if (rc == TPM_RC_COMMAND_CODE) { - printf(" NOTE: NTC2_PreConfig not supported on this TPM.\n"); + printf(" NTC2_PreConfig not supported\n"); rc = 0; } else { printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); } return rc; } -#endif /* WOLFSPDM_NUVOTON */ -#ifdef WOLFSPDM_NUVOTON static int demo_status(WOLFTPM2_DEV* dev) { int rc; WOLFSPDM_NUVOTON_STATUS status; - printf("\n=== SPDM Status (GET_STS_ vendor command) ===\n"); - + printf("\n=== SPDM Status ===\n"); XMEMSET(&status, 0, sizeof(status)); rc = wolfTPM2_SpdmGetStatus(dev, &status); if (rc == 0) { - int isConnected = wolfTPM2_SpdmIsConnected(dev); - byte negVer = wolfSPDM_GetNegotiatedVersion(dev->spdmCtx->spdmCtx); - - printf(" SPDM Enabled: %s\n", status.spdmEnabled ? "Yes" : "No"); - printf(" SPDM-Only Locked: %s\n", - status.spdmOnlyLocked ? "YES (TPM commands blocked)" : "No"); - printf(" Session Active: %s\n", isConnected ? "Yes" : "No"); - if (isConnected) { - printf(" Negotiated Ver: SPDM %u.%u (0x%02x)\n", - (negVer >> 4) & 0xF, negVer & 0xF, negVer); - printf(" Session ID: 0x%08x\n", + int isConn = wolfTPM2_SpdmIsConnected(dev); + printf(" Enabled: %s Locked: %s Session: %s\n", + status.spdmEnabled ? "Yes" : "No", + status.spdmOnlyLocked ? "YES" : "No", + isConn ? "Yes" : "No"); + if (isConn) { + byte negVer = wolfSPDM_GetNegotiatedVersion(dev->spdmCtx->spdmCtx); + printf(" Version: SPDM %u.%u SessionID: 0x%08x\n", + (negVer >> 4) & 0xF, negVer & 0xF, wolfTPM2_SpdmGetSessionId(dev)); } - printf(" Nuvoton Status: v%u.%u\n", - status.specVersionMajor, status.specVersionMinor); - - if (status.spdmOnlyLocked) { - printf("\n NOTE: TPM is in SPDM-only mode. Standard TPM commands will\n"); - printf(" return TPM_RC_DISABLED until SPDM session is established\n"); - printf(" and --unlock is called.\n"); - } + printf(" Nuvoton: v%u.%u\n", status.specVersionMajor, + status.specVersionMinor); + if (status.spdmOnlyLocked) + printf(" NOTE: SPDM-only mode, use --unlock to restore\n"); } else { - printf(" FAILED to get status: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - printf(" Note: GET_STS requires SPDM to be enabled on the TPM\n"); + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); } return rc; } -#endif /* WOLFSPDM_NUVOTON */ -#ifdef WOLFSPDM_NUVOTON static int demo_get_pubkey(WOLFTPM2_DEV* dev) { int rc; @@ -410,22 +132,15 @@ static int demo_get_pubkey(WOLFTPM2_DEV* dev) word32 pubKeySz = sizeof(pubKey); word32 i; - printf("\n=== Get TPM SPDM-Identity Public Key ===\n"); - + printf("\n=== Get SPDM-Identity Public Key ===\n"); rc = wolfTPM2_SpdmGetPubKey(dev, pubKey, &pubKeySz); if (rc == 0) { - printf(" SUCCESS: Got TPM public key (%d bytes)\n", (int)pubKeySz); - printf(" Key (hex): "); - for (i = 0; i < pubKeySz && i < 32; i++) { - printf("%02x", pubKey[i]); - } - if (pubKeySz > 32) { - printf("..."); - } + printf(" Got %d bytes: ", (int)pubKeySz); + for (i = 0; i < pubKeySz && i < 32; i++) printf("%02x", pubKey[i]); + if (pubKeySz > 32) printf("..."); printf("\n"); } else { printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - printf(" Note: GET_PUB_KEY requires SPDM to be enabled\n"); } return rc; } @@ -434,34 +149,21 @@ static int demo_connect(WOLFTPM2_DEV* dev) { int rc; - printf("\n=== SPDM Connect (Full Handshake) ===\n"); - - /* If auto-SPDM already established a session (SPDM-only mode), skip */ + printf("\n=== SPDM Connect ===\n"); if (wolfTPM2_SpdmIsConnected(dev)) { - printf(" SPDM session already active (auto-established)\n"); - printf(" Session ID: 0x%08x\n", wolfTPM2_SpdmGetSessionId(dev)); + printf(" Already connected (SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); return 0; } - printf("Establishing SPDM secure session...\n"); - printf(" Steps: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> " - "GIVE_PUB_KEY -> FINISH\n\n"); - - /* wolfTPM2_SpdmConnectNuvoton handles everything: - * - Auto-sets TIS I/O callback for SPI/I2C transport - * - Auto-generates ephemeral P-384 key pair (when NULL keys passed) - * - Sets Nuvoton mode and performs full SPDM handshake */ + printf(" Handshake: VERSION -> GET_PUBK -> KEY_EXCHANGE -> " + "GIVE_PUB -> FINISH\n"); rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); if (rc == 0) { - printf(" SUCCESS: SPDM session established!\n"); - printf(" All TPM commands now encrypted with AES-256-GCM\n"); - - if (wolfTPM2_SpdmIsConnected(dev)) { - printf(" Session ID: 0x%08x\n", wolfTPM2_SpdmGetSessionId(dev)); - } + printf(" Session established (AES-256-GCM, SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); } else { printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - printf(" Note: Ensure SPDM is enabled and backend is configured\n"); } return rc; } @@ -469,468 +171,86 @@ static int demo_connect(WOLFTPM2_DEV* dev) static int demo_lock(WOLFTPM2_DEV* dev, int lock) { int rc; - - printf("\n=== SPDM-Only Mode: %s ===\n", lock ? "LOCK" : "UNLOCK"); - + printf("\n=== SPDM-Only: %s ===\n", lock ? "LOCK" : "UNLOCK"); rc = wolfTPM2_SpdmSetOnlyMode(dev, lock); - if (rc == 0) { - printf(" SUCCESS: SPDM-only mode %s\n", - lock ? "LOCKED" : "UNLOCKED"); - if (lock) { - printf(" WARNING: TPM will only accept commands over SPDM!\n"); - } - } else { + if (rc == 0) + printf(" %s\n", lock ? "LOCKED (TPM requires SPDM)" : "UNLOCKED"); + else printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - } return rc; } - #endif /* WOLFSPDM_NUVOTON */ -/* -------------------------------------------------------------------------- */ -/* Standard SPDM over TCP (for libspdm emulator testing) */ -/* -------------------------------------------------------------------------- */ - -#ifdef WOLFTPM_SWTPM - -#ifndef NO_WOLFSPDM_MEAS -/* Retrieve and display device measurements from an established SPDM session. - * Calls wolfSPDM measurement APIs directly. */ -static int demo_measurements(WOLFSPDM_CTX* ctx, int requestSignature) -{ - int rc, count, i; - - printf("\n=== SPDM GET_MEASUREMENTS ===\n"); - - rc = wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, - requestSignature); - if (rc == WOLFSPDM_SUCCESS) { - printf("Measurements retrieved and signature VERIFIED\n"); - } - else if (rc == WOLFSPDM_E_MEAS_NOT_VERIFIED) { - printf("Measurements retrieved (not signature-verified)\n"); - } - else if (rc == WOLFSPDM_E_MEAS_SIG_FAIL) { - printf("WARNING: Measurement signature INVALID\n"); - return rc; - } - else { - printf("ERROR: %s (%d)\n", wolfSPDM_GetErrorString(rc), rc); - return rc; - } - - count = wolfSPDM_GetMeasurementCount(ctx); - printf("Measurement blocks: %d\n", count); - - for (i = 0; i < count; i++) { - byte idx = 0, mtype = 0; - byte val[WOLFSPDM_MAX_MEAS_VALUE_SIZE]; - word32 valSz = sizeof(val); - int j; - - rc = wolfSPDM_GetMeasurementBlock(ctx, i, &idx, &mtype, val, &valSz); - if (rc != WOLFSPDM_SUCCESS) - continue; - - printf(" [%u] type=0x%02x size=%u: ", idx, mtype, valSz); - for (j = 0; j < (int)valSz && j < 48; j++) - printf("%02x", val[j]); - if (valSz > 48) - printf("..."); - printf("\n"); - } - - return 0; -} -#endif /* !NO_WOLFSPDM_MEAS */ - -#ifndef NO_WOLFSPDM_CHALLENGE - -/* Execute an SPDM step with error reporting */ -#define DEMO_STEP(name, call) do { \ - printf(" " name "...\n"); \ - rc = (call); \ - if (rc != WOLFSPDM_SUCCESS) { \ - printf(" ERROR: " name " failed: %s (%d)\n", \ - wolfSPDM_GetErrorString(rc), rc); \ - return rc; \ - } \ -} while(0) - -/* Perform CHALLENGE authentication (sessionless attestation). - * Uses individual handshake steps instead of wolfSPDM_Connect() to avoid - * establishing a full session (KEY_EXCHANGE/FINISH). */ -static int demo_challenge(WOLFSPDM_CTX* ctx) -{ - int rc; - - printf("\n=== SPDM CHALLENGE (Sessionless Attestation) ===\n"); - - DEMO_STEP("GET_VERSION", wolfSPDM_GetVersion(ctx)); - DEMO_STEP("GET_CAPABILITIES", wolfSPDM_GetCapabilities(ctx)); - DEMO_STEP("NEGOTIATE_ALGORITHMS", wolfSPDM_NegotiateAlgorithms(ctx)); - DEMO_STEP("GET_DIGESTS", wolfSPDM_GetDigests(ctx)); - DEMO_STEP("GET_CERTIFICATE", wolfSPDM_GetCertificate(ctx, 0)); - - /* Step 6: CHALLENGE */ - printf(" CHALLENGE (slot=0, no measurement summary)...\n"); - rc = wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_NONE); - if (rc == WOLFSPDM_SUCCESS) { - printf("\n CHALLENGE authentication PASSED\n"); - } - else { - printf("\n CHALLENGE authentication FAILED: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - } - - return rc; -} -#endif /* !NO_WOLFSPDM_CHALLENGE */ - -/* Send HEARTBEAT over an established SPDM session */ -static int demo_heartbeat(WOLFSPDM_CTX* ctx) -{ - int rc; - - printf("\n=== SPDM HEARTBEAT ===\n"); - - rc = wolfSPDM_Heartbeat(ctx); - if (rc == WOLFSPDM_SUCCESS) { - printf(" HEARTBEAT_ACK received — session alive\n"); - } - else { - printf(" HEARTBEAT failed: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - } - - return rc; -} - -/* Perform KEY_UPDATE to rotate session encryption keys */ -static int demo_key_update(WOLFSPDM_CTX* ctx) -{ - int rc; - - printf("\n=== SPDM KEY_UPDATE ===\n"); - - rc = wolfSPDM_KeyUpdate(ctx, 1); /* updateAll = 1: rotate both keys */ - if (rc == WOLFSPDM_SUCCESS) { - printf(" KEY_UPDATE completed — new keys active\n"); - } - else { - printf(" KEY_UPDATE failed: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - } - - return rc; -} - -/* SPDM emulator test using wolfSPDM library - * Connects to libspdm responder emulator via TCP and performs full SPDM 1.2 handshake - * Uses the unified I/O callback (same as Nuvoton hardware mode) */ -static int demo_emulator(const char* host, int port, int doMeas, - int requestSignature, int doChallenge, - int doHeartbeat, int doKeyUpdate, - byte maxVersion) -{ - WOLFSPDM_CTX* ctx; - int rc; -#ifndef WOLFSPDM_DYNAMIC_MEMORY - byte spdmBuf[WOLFSPDM_CTX_STATIC_SIZE]; -#endif - - printf("\n=== wolfSPDM spdm-emu Test ===\n"); - printf("Connecting to %s:%d...\n", host, port); - - /* Initialize unified I/O context for TCP mode (emulator) */ - rc = spdm_io_init_tcp(&g_ioCtx, host, port); - if (rc < 0) { - printf("Failed to connect to emulator\n"); - printf("Make sure spdm_responder_emu is running:\n"); - printf(" ./spdm_responder_emu --trans TCP\n"); - return rc; - } - - /* Create wolfSPDM context */ -#ifdef WOLFSPDM_DYNAMIC_MEMORY - ctx = wolfSPDM_New(); - if (ctx == NULL) { - printf("ERROR: wolfSPDM_New() failed\n"); - spdm_io_cleanup(&g_ioCtx); - return -1; - } -#else - ctx = (WOLFSPDM_CTX*)spdmBuf; - rc = wolfSPDM_InitStatic(ctx, (int)sizeof(spdmBuf)); - if (rc != WOLFSPDM_SUCCESS) { - printf("ERROR: wolfSPDM_InitStatic() failed: %s\n", - wolfSPDM_GetErrorString(rc)); - spdm_io_cleanup(&g_ioCtx); - return rc; - } -#endif - - /* Set unified I/O callback (handles both TCP emulator and TPM TIS modes) */ - wolfSPDM_SetIO(ctx, wolfspdm_io_callback, &g_ioCtx); -#ifdef DEBUG_WOLFTPM - wolfSPDM_SetDebug(ctx, 1); -#endif - - /* Set max version if specified via --ver */ - if (maxVersion != 0) { - rc = wolfSPDM_SetMaxVersion(ctx, maxVersion); - if (rc != WOLFSPDM_SUCCESS) { - printf("ERROR: wolfSPDM_SetMaxVersion(0x%02x) failed: %s\n", - maxVersion, wolfSPDM_GetErrorString(rc)); - wolfSPDM_Free(ctx); - spdm_io_cleanup(&g_ioCtx); - return rc; - } - } - -#ifndef NO_WOLFSPDM_CHALLENGE - /* Challenge mode: sessionless attestation (no KEY_EXCHANGE/FINISH) */ - if (doChallenge) { - rc = demo_challenge(ctx); - - /* Cleanup */ - wolfSPDM_Free(ctx); - spdm_io_cleanup(&g_ioCtx); - return (rc == WOLFSPDM_SUCCESS) ? 0 : rc; - } -#else - (void)doChallenge; -#endif - - /* Full SPDM handshake - this single call replaces ~1000 lines of code! - * Performs: GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> - * GET_DIGESTS -> GET_CERTIFICATE -> KEY_EXCHANGE -> FINISH */ - printf("\nEstablishing SPDM session...\n"); - rc = wolfSPDM_Connect(ctx); - - if (rc == WOLFSPDM_SUCCESS) { - printf("\n=============================================\n"); - printf(" SUCCESS: SPDM Session Established!\n"); - printf(" Session ID: 0x%08x\n", wolfSPDM_GetSessionId(ctx)); - printf(" SPDM Version: 0x%02x\n", wolfSPDM_GetNegotiatedVersion(ctx)); - printf("=============================================\n"); - - /* Heartbeat: send keep-alive over encrypted channel */ - if (doHeartbeat) { - rc = demo_heartbeat(ctx); - if (rc != WOLFSPDM_SUCCESS) goto cleanup; - } - - /* Key update: rotate session keys */ - if (doKeyUpdate) { - rc = demo_key_update(ctx); - if (rc != WOLFSPDM_SUCCESS) goto cleanup; - } - -#ifndef NO_WOLFSPDM_MEAS - /* Retrieve measurements if requested */ - if (doMeas) { - rc = demo_measurements(ctx, requestSignature); - } -#else - (void)doMeas; - (void)requestSignature; -#endif - } else { - printf("\nERROR: wolfSPDM_Connect() failed: %s (%d)\n", - wolfSPDM_GetErrorString(rc), rc); - } - -cleanup: - /* Cleanup */ - wolfSPDM_Free(ctx); - spdm_io_cleanup(&g_ioCtx); - - return (rc == WOLFSPDM_SUCCESS) ? 0 : rc; -} - - -#endif /* WOLFTPM_SWTPM */ - int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) { - int rc; + int rc, i; WOLFTPM2_DEV dev; - int i; -#ifdef WOLFTPM_SWTPM - const char* emuHost = SPDM_EMU_DEFAULT_HOST; - int emuPort = SPDM_EMU_DEFAULT_PORT; - int useEmulator = 0; - int doMeas = 0; - int requestSignature = 1; - int doChallenge = 0; - int doHeartbeat = 0; - int doKeyUpdate = 0; - byte maxVersion = 0; /* 0 = use default (highest supported) */ -#endif - - if (argc <= 1) { - usage(); - return 0; - } + if (argc <= 1) { usage(); return 0; } for (i = 1; i < argc; i++) { - if (XSTRCMP(argv[i], "-h") == 0 || - XSTRCMP(argv[i], "--help") == 0) { - usage(); - return 0; + if (XSTRCMP(argv[i], "-h") == 0 || XSTRCMP(argv[i], "--help") == 0) { + usage(); return 0; } -#ifdef WOLFTPM_SWTPM - else if (XSTRCMP(argv[i], "--emu") == 0) { - useEmulator = 1; - } - else if (XSTRCMP(argv[i], "--host") == 0 && i + 1 < argc) { - emuHost = argv[++i]; - } - else if (XSTRCMP(argv[i], "--port") == 0 && i + 1 < argc) { - emuPort = atoi(argv[++i]); - } - else if (XSTRCMP(argv[i], "--meas") == 0) { - doMeas = 1; - useEmulator = 1; - } - else if (XSTRCMP(argv[i], "--no-sig") == 0) { - requestSignature = 0; - } - else if (XSTRCMP(argv[i], "--challenge") == 0) { - doChallenge = 1; - useEmulator = 1; - } - else if (XSTRCMP(argv[i], "--heartbeat") == 0) { - doHeartbeat = 1; - useEmulator = 1; - } - else if (XSTRCMP(argv[i], "--key-update") == 0) { - doKeyUpdate = 1; - useEmulator = 1; - } - else if (XSTRCMP(argv[i], "--ver") == 0 && i + 1 < argc) { - const char* verStr = argv[++i]; - if (XSTRCMP(verStr, "1.2") == 0) - maxVersion = SPDM_VERSION_12; - else if (XSTRCMP(verStr, "1.3") == 0) - maxVersion = SPDM_VERSION_13; - else if (XSTRCMP(verStr, "1.4") == 0) - maxVersion = SPDM_VERSION_14; - else { - printf("Invalid version: %s (use 1.2, 1.3, or 1.4)\n", verStr); - return BAD_FUNC_ARG; - } - } -#endif } -#ifdef WOLFTPM_SWTPM - /* Handle --emu mode (TCP to emulator, no TPM needed) */ - if (useEmulator) { - printf("Entering emulator mode...\n"); - fflush(stdout); - return demo_emulator(emuHost, emuPort, doMeas, requestSignature, - doChallenge, doHeartbeat, doKeyUpdate, - maxVersion); - } -#endif - - /* Init the TPM2 device. - * When SPDM is enabled on Nuvoton TPMs, TPM2_Startup may return - * TPM_RC_DISABLED because the TPM expects SPDM-only communication. - * wolfTPM2_Init tolerates this when built with WOLFTPM_SPDM - - * SPDM commands work over raw SPI regardless of TPM startup state. */ rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); if (rc != 0) { printf("wolfTPM2_Init failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); return rc; } - /* Initialize SPDM support */ rc = wolfTPM2_SpdmInit(&dev); if (rc != 0) { - printf("wolfTPM2_SpdmInit failed: 0x%x: %s\n", rc, - TPM2_GetRCString(rc)); - printf("Ensure wolfTPM is built with --enable-spdm\n"); + printf("wolfTPM2_SpdmInit failed: %s\n", TPM2_GetRCString(rc)); wolfTPM2_Cleanup(&dev); return rc; } #ifdef WOLFSPDM_NUVOTON - /* Set Nuvoton mode + TIS I/O for all Nuvoton commands */ wolfTPM2_SpdmSetNuvotonMode(&dev); wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); #endif - /* Process command-line options */ for (i = 1; i < argc; i++) { #ifdef WOLFSPDM_NUVOTON - if (XSTRCMP(argv[i], "--enable") == 0) { + if (XSTRCMP(argv[i], "--enable") == 0) rc = demo_enable(&dev); - if (rc != 0) break; - } - else if (XSTRCMP(argv[i], "--disable") == 0) { + else if (XSTRCMP(argv[i], "--disable") == 0) rc = demo_disable(&dev); - if (rc != 0) break; - } - else if (XSTRCMP(argv[i], "--status") == 0) { + else if (XSTRCMP(argv[i], "--status") == 0) rc = demo_status(&dev); - if (rc != 0) break; - } - else if (XSTRCMP(argv[i], "--get-pubkey") == 0) { + else if (XSTRCMP(argv[i], "--get-pubkey") == 0) rc = demo_get_pubkey(&dev); - if (rc != 0) break; - } - else if (XSTRCMP(argv[i], "--connect") == 0) { + else if (XSTRCMP(argv[i], "--connect") == 0) rc = demo_connect(&dev); - if (rc != 0) break; - } - else if (XSTRCMP(argv[i], "--lock") == 0) { + else if (XSTRCMP(argv[i], "--lock") == 0) rc = demo_lock(&dev, 1); - if (rc != 0) break; - } - else if (XSTRCMP(argv[i], "--unlock") == 0) { + else if (XSTRCMP(argv[i], "--unlock") == 0) rc = demo_lock(&dev, 0); - if (rc != 0) break; - } else -#endif /* WOLFSPDM_NUVOTON */ - { - printf("Unknown option: %s\n", argv[i]); - usage(); - rc = BAD_FUNC_ARG; - break; - } +#endif + { printf("Unknown option: %s\n", argv[i]); usage(); rc = BAD_FUNC_ARG; } + if (rc != 0) break; } - /* Cleanup SPDM */ wolfTPM2_SpdmCleanup(&dev); - wolfTPM2_Cleanup(&dev); return rc; } -/******************************************************************************/ -/* --- END SPDM Demo --- */ -/******************************************************************************/ - #ifndef NO_MAIN_DRIVER int main(int argc, char *argv[]) { int rc = -1; - #ifndef WOLFTPM2_NO_WRAPPER rc = TPM2_SPDM_Demo(NULL, argc, argv); #else printf("Wrapper code not compiled in\n"); - (void)argc; - (void)argv; + (void)argc; (void)argv; #endif - return (rc == 0) ? 0 : 1; } -#endif /* !NO_MAIN_DRIVER */ +#endif #endif /* WOLFTPM_SPDM */ -#endif /* !WOLFTPM2_NO_WRAPPER */ \ No newline at end of file +#endif /* !WOLFTPM2_NO_WRAPPER */ diff --git a/examples/spdm/spdm_test.sh b/examples/spdm/spdm_test.sh index 8fa60a64..6d9e0e49 100755 --- a/examples/spdm/spdm_test.sh +++ b/examples/spdm/spdm_test.sh @@ -1,350 +1,88 @@ #!/bin/bash +# spdm_test.sh - Nuvoton SPDM hardware tests # -# spdm_test.sh - SPDM test script +# Copyright (C) 2006-2025 wolfSSL Inc. # -# Supports two modes: -# --emu Test SPDM with libspdm emulator (session + measurements) -# --nuvoton Test Nuvoton SPDM hardware (lock, unit test over SPDM, unlock) +# This file is part of wolfTPM. # -# Usage: -# ./spdm_test.sh --emu # Emulator tests -# ./spdm_test.sh --nuvoton # Nuvoton hardware tests -# ./spdm_test.sh --emu --nuvoton # Both -# ./spdm_test.sh # Default: --nuvoton +# 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_DEMO="./examples/spdm/spdm_demo" +SPDM_DEMO="${1:-./examples/spdm/spdm_demo}" CAPS_DEMO="./examples/wrap/caps" UNIT_TEST="./tests/unit.test" GPIO_CHIP="gpiochip0" GPIO_PIN="4" -PASS=0 -FAIL=0 -TOTAL=0 -DO_EMU=0 -DO_NUVOTON=0 -EMU_PID="" -EMU_LOG="/tmp/spdm_emu_$$.log" +PASS=0 FAIL=0 TOTAL=0 -# Colors (if terminal supports it) if [ -t 1 ]; then - GREEN='\033[0;32m' - RED='\033[0;31m' - YELLOW='\033[0;33m' - NC='\033[0m' + 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 [--emu] [--nuvoton] [path-to-spdm_demo]" - echo "" - echo "Options:" - echo " --emu Test SPDM with libspdm emulator (session + measurements)" - echo " --nuvoton Test Nuvoton SPDM hardware (lock, unit test over SPDM, unlock)" - echo " -h, --help Show this help" - echo "" - echo "If neither --emu nor --nuvoton is specified, defaults to --nuvoton." - echo "" - echo "Emulator mode expects spdm_responder_emu to be found via:" - echo " 1. SPDM_EMU_PATH environment variable" - echo " 2. ../spdm-emu/build/bin/ (cloned next to wolfTPM)" - echo " 3. spdm_responder_emu in PATH" -} - -# Parse arguments -for arg in "$@"; do - case "$arg" in - --emu) - DO_EMU=1 - ;; - --nuvoton) - DO_NUVOTON=1 - ;; - -h|--help) - usage - exit 0 - ;; - *) - # Treat as path to spdm_demo - SPDM_DEMO="$arg" - ;; - esac -done - -# Default to --nuvoton if nothing specified -if [ $DO_EMU -eq 0 ] && [ $DO_NUVOTON -eq 0 ]; then - DO_NUVOTON=1 + GREEN='' RED='' YELLOW='' NC='' fi -# Find spdm_responder_emu for --emu mode -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 (cloned next to wolfTPM) - 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] -# version: "1.2", "1.3", or "1.4" (default: "1.2") -start_emu() { - local ver="${1:-1.2}" - echo " Starting spdm_responder_emu (SPDM $ver)..." - - # Kill any stale emulator processes (use -f for partial name match — - # process name can be truncated to "spdm_responder_" on some systems) - if pgrep -f spdm_responder_emu >/dev/null 2>&1; then - echo " Killing stale emulator process..." - pkill -9 -f spdm_responder_emu 2>/dev/null - sleep 2 - fi - - # Free port 2323 if still held (e.g. by orphaned process) - if ss -tlnp 2>/dev/null | grep -q ":2323 "; then - echo " Port 2323 in use, killing holder..." - fuser -k 2323/tcp 2>/dev/null - sleep 2 - fi - - # Final port check - if ss -tlnp 2>/dev/null | grep -q ":2323 "; then - echo -e " ${RED}ERROR: Port 2323 still in use after cleanup${NC}" - ss -tlnp 2>/dev/null | grep ":2323 " - return 1 - fi - - # Verify cert/key files exist in EMU_DIR - if [ ! -f "$EMU_DIR/EcP384/end_responder.cert" ] && \ - [ ! -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 - gpio_reset() { - echo " GPIO reset..." gpioset "$GPIO_CHIP" "$GPIO_PIN=0" 2>/dev/null sleep 0.1 gpioset "$GPIO_CHIP" "$GPIO_PIN=1" 2>/dev/null sleep 2 } -# Run a test with optional setup/teardown -# Usage: run_test -# mode: "nuvoton" (GPIO reset before) or "emu" (start/stop emulator around) -# emu_ver: emulator SPDM version (e.g., "1.2"), ignored for nuvoton mode run_test() { - local mode="$1" - local name="$2" - local emu_ver="$3" - shift 3 - + local name="$1"; shift TOTAL=$((TOTAL + 1)) echo "[$TOTAL] $name" - - # Pre-test setup - if [ "$mode" = "nuvoton" ]; then - gpio_reset - elif [ "$mode" = "emu" ]; then - if ! start_emu "$emu_ver"; then - echo -e " ${RED}FAIL (emulator start)${NC}" - FAIL=$((FAIL + 1)) - echo "" - return 1 - fi - fi - + gpio_reset if "$@"; then - echo -e " ${GREEN}PASS${NC}" - PASS=$((PASS + 1)) + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) else - echo -e " ${RED}FAIL${NC}" - FAIL=$((FAIL + 1)) - fi - - # Post-test teardown - if [ "$mode" = "emu" ]; then - stop_emu - sleep 1 # Let port release + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) fi echo "" } -# Check spdm_demo exists if [ ! -x "$SPDM_DEMO" ]; then - echo "Error: $SPDM_DEMO not found or not executable" - usage + echo "Error: $SPDM_DEMO not found. Usage: $0 [path-to-spdm_demo]" exit 1 fi -# ========================================================================== -# Emulator Tests -# ========================================================================== -if [ $DO_EMU -eq 1 ]; then - 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 wolfTPM:" - 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 emu "Session (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --emu --ver "$VER" - - # Session + signed measurements - run_test emu "Signed measurements (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --meas --ver "$VER" - - # Session + unsigned measurements - run_test emu "Unsigned measurements (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --meas --no-sig --ver "$VER" - - # Challenge authentication (sessionless) - run_test emu "Challenge (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --challenge --ver "$VER" +echo "=== Nuvoton SPDM Hardware Tests ===" +echo "Demo: $SPDM_DEMO Caps: $CAPS_DEMO Unit: $UNIT_TEST" +echo "" - # Session + heartbeat - run_test emu "Heartbeat (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --emu --heartbeat --ver "$VER" +run_test "SPDM status query" "$SPDM_DEMO" --status +run_test "SPDM session connect" "$SPDM_DEMO" --connect +run_test "Lock SPDM-only mode" "$SPDM_DEMO" --connect --lock - # Session + key update - run_test emu "Key update (SPDM $VER)" "$VER" \ - "$SPDM_DEMO" --emu --key-update --ver "$VER" - - echo "" - done +if [ -x "$UNIT_TEST" ]; then + run_test "Unit test over SPDM" "$UNIT_TEST" +else + echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" fi -# ========================================================================== -# Nuvoton Hardware Tests -# ========================================================================== -if [ $DO_NUVOTON -eq 1 ]; then - echo "=== Nuvoton SPDM Hardware Tests ===" - echo "Demo: $SPDM_DEMO" - echo "Caps: $CAPS_DEMO" - echo "Unit test: $UNIT_TEST" - echo "" - - # Step 1: SPDM status query (vendor command over TIS) - run_test nuvoton "SPDM status query" "" "$SPDM_DEMO" --status - - # Step 2: SPDM session establishment (version + keygen + handshake) - run_test nuvoton "SPDM session connect" "" "$SPDM_DEMO" --connect - - # Step 3: Lock SPDM-only mode (connect + lock in one session) - run_test nuvoton "Lock SPDM-only mode" "" "$SPDM_DEMO" --connect --lock - - # Step 4: Unit test over SPDM (auto-detects SPDM-only, all commands encrypted) - if [ -x "$UNIT_TEST" ]; then - run_test nuvoton "Unit test over SPDM" "" "$UNIT_TEST" - else - echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" - fi - - # Step 5: Unlock SPDM-only mode - run_test nuvoton "Unlock SPDM-only mode" "" "$SPDM_DEMO" --connect --unlock - - # Step 6: Verify cleartext TPM works (proves unlock succeeded) - if [ -x "$CAPS_DEMO" ]; then - run_test nuvoton "Cleartext caps (no SPDM)" "" "$CAPS_DEMO" - else - echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" - fi +run_test "Unlock SPDM-only mode" "$SPDM_DEMO" --connect --unlock - echo "" +if [ -x "$CAPS_DEMO" ]; then + run_test "Cleartext caps (no SPDM)" "$CAPS_DEMO" +else + echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" fi -# ========================================================================== -# Summary -# ========================================================================== -echo "=== Results ===" -echo "Total: $TOTAL Passed: $PASS Failed: $FAIL" +echo "" +echo "=== Results: $TOTAL total, $PASS passed, $FAIL failed ===" if [ $FAIL -eq 0 ]; then - echo -e "${GREEN}ALL TESTS PASSED${NC}" - exit 0 + echo -e "${GREEN}ALL TESTS PASSED${NC}"; exit 0 else - echo -e "${RED}$FAIL TEST(S) FAILED${NC}" - exit 1 + echo -e "${RED}$FAIL TEST(S) FAILED${NC}"; exit 1 fi diff --git a/spdm/README.md b/spdm/README.md index e24ee6af..c8b25a51 100644 --- a/spdm/README.md +++ b/spdm/README.md @@ -1,15 +1,11 @@ # wolfTPM SPDM -wolfTPM includes a built-in SPDM 1.2+ requester stack using wolfSSL/wolfCrypt. -This provides encrypted bus communication between the host and TPM, ensuring -all commands and responses are protected with AES-256-GCM. +wolfTPM includes built-in SPDM support for Nuvoton NPCT75x TPMs using +wolfSSL/wolfCrypt. This provides encrypted bus communication between the host +and TPM, ensuring all commands and responses are protected with AES-256-GCM. -### Supported Versions - -| Target | SPDM Versions | Notes | -|--------|---------------|-------| -| DMTF spdm-emu | 1.2, 1.3, 1.4 | Full protocol support including session, measurements, challenge, heartbeat, key update | -| Nuvoton NPCT75x | 1.3 | Hardware SPDM with SPDM-only mode and vendor-defined TPM command wrapping | +For standard SPDM protocol testing with the DMTF spdm-emu emulator, see the +[wolfSPDM](https://github.com/aidangarske/wolfSPDM) standalone library. ## How It Works @@ -22,7 +18,7 @@ automatically encrypted — no application code changes needed. ``` Host TPM (Nuvoton NPCT75x) | | - |--- GET_VERSION ------------------>| (negotiate SPDM 1.2-1.4) + |--- GET_VERSION ------------------>| (negotiate SPDM version) |<-- VERSION -----------------------| | | |--- GET_PUB_KEY ------------------>| (get TPM's P-384 identity key) @@ -85,8 +81,7 @@ sudo ldconfig ``` The `--enable-sp` flag enables Single Precision math with optimized ECC P-384 -support, which is required for SPDM Algorithm Set B on platforms like ARM64. -For a broader feature set, `--enable-all` can be used instead. +support. For a broader feature set, `--enable-all` can be used instead. ### wolfTPM with Nuvoton SPDM @@ -153,7 +148,7 @@ gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 ```bash # Run all Nuvoton SPDM tests (status, connect, lock, unit test, unlock, caps) -./examples/spdm/spdm_test.sh --nuvoton +./examples/spdm/spdm_test.sh ``` ### Demo Options @@ -168,65 +163,6 @@ gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 | `--lock` | Lock SPDM-only mode (use with `--connect`) | | `--unlock` | Unlock SPDM-only mode (use with `--connect`) | -## Emulator Testing (spdm-emu) - -For development without Nuvoton hardware, use the DMTF spdm-emu emulator: - -```bash -# Build emulator -git clone 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 - -# Build wolfSSL -cd wolfssl -./autogen.sh -./configure --enable-wolftpm --enable-all -make && sudo make install && sudo ldconfig - -# Build wolfTPM with SPDM + SWTPM -cd wolfTPM -./autogen.sh -./configure --enable-spdm --enable-swtpm -make - -# Run emulator tests (starts/stops emulator automatically) -./examples/spdm/spdm_test.sh --emu -``` - -The test script automatically finds `spdm_responder_emu` in `../spdm-emu/build/bin/`, -starts it for each test, and runs session establishment, signed measurements, -unsigned measurements, challenge authentication, heartbeat, and key update -across SPDM versions 1.2, 1.3, and 1.4 (18 tests total). - -To run individual commands manually: - -```bash -# Terminal 1: Start the emulator (specify version with --ver) -cd spdm-emu/build/bin -./spdm_responder_emu --ver 1.3 --hash SHA_384 --asym ECDSA_P384 \ - --dhe SECP_384_R1 --aead AES_256_GCM - -# Terminal 2: Run wolfTPM SPDM demo (--ver must match emulator) -cd wolfTPM -./examples/spdm/spdm_demo --emu --ver 1.3 -``` - -### Emulator Demo Options - -| Option | Description | -|--------|-------------| -| `--emu` | Session establishment with emulator | -| `--meas` | Retrieve signed device measurements | -| `--meas --no-sig` | Measurements without signature verification | -| `--challenge` | Sessionless challenge authentication | -| `--emu --heartbeat` | Session keep-alive | -| `--emu --key-update` | Session key rotation | -| `--ver 1.2\|1.3\|1.4` | Maximum SPDM version to negotiate (default: 1.4) | -| `--host ` | Emulator host (default: 127.0.0.1) | -| `--port ` | Emulator port (default: 2323) | - ## How Auto-SPDM Works When the TPM is in SPDM-only mode, `wolfTPM2_Init()` handles everything: @@ -260,22 +196,10 @@ Useful on platforms with small stacks. | `wolfSPDM_GetCtxSize()` | Return `sizeof(WOLFSPDM_CTX)` at runtime | | `wolfSPDM_SetIO()` | Set transport I/O callback | | `wolfSPDM_SetDebug()` | Enable/disable debug output | -| `wolfSPDM_SetMaxVersion()` | Set maximum SPDM version to negotiate (0x12=1.2, 0x13=1.3, 0x14=1.4) | | `wolfSPDM_Connect()` | Full SPDM handshake | | `wolfSPDM_IsConnected()` | Check session status | | `wolfSPDM_Disconnect()` | End session | -| `wolfSPDM_EncryptMessage()` | Encrypt outgoing message | -| `wolfSPDM_DecryptMessage()` | Decrypt incoming message | | `wolfSPDM_SecuredExchange()` | Encrypt/send/receive/decrypt in one call | -| `wolfSPDM_SetTrustedCAs()` | Load trusted root CA certificates for chain validation | -| `wolfSPDM_GetMeasurements()` | Retrieve device measurements with optional signature verification | -| `wolfSPDM_GetMeasurementCount()` | Get number of measurement blocks retrieved | -| `wolfSPDM_GetMeasurementBlock()` | Access individual measurement block data | -| `wolfSPDM_Challenge()` | Sessionless device attestation via CHALLENGE/CHALLENGE_AUTH | -| `wolfSPDM_Heartbeat()` | Session keep-alive (HEARTBEAT/HEARTBEAT_ACK) | -| `wolfSPDM_KeyUpdate()` | Rotate session encryption keys (KEY_UPDATE/KEY_UPDATE_ACK) | -| `wolfSPDM_SendData()` | Send application data over established session | -| `wolfSPDM_ReceiveData()` | Receive application data over established session | ## Troubleshooting @@ -300,14 +224,6 @@ gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 If GPIO is not wired, a full power cycle is required. -### Emulator: bind error / connection refused - -```bash -pkill -9 spdm_responder_emu -sleep 5 -ss -tlnp | grep 2323 # verify port is free -``` - ### SPDM Error Codes | Code | Name | Description | @@ -318,16 +234,12 @@ ss -tlnp | grep 2323 # verify port is free | 0x06 | UnsupportedRequest | Request not supported or format rejected | | 0x41 | VersionMismatch | SPDM version mismatch | -## SPDM Support Beyond wolfTPM - -The wolfSPDM requester stack was developed as a standalone implementation and -is integrated into wolfTPM for convenience. It is designed to be portable and -can support SPDM on other hardware platforms, additional algorithm sets beyond -Algorithm Set B, and responder-side implementations. +## Standard SPDM Support -For inquiries about full standalone SPDM support, custom hardware integration, -additional algorithm sets, or commercial licensing, please contact -support@wolfssl.com. +For standard SPDM protocol support including session establishment with the +DMTF spdm-emu emulator, measurements, challenge authentication, heartbeat, +and key update, see the [wolfSPDM](https://github.com/aidangarske/wolfSPDM) +standalone library. ## License diff --git a/spdm/src/spdm_context.c b/spdm/src/spdm_context.c index 96b494a7..0b0019e5 100644 --- a/spdm/src/spdm_context.c +++ b/spdm/src/spdm_context.c @@ -23,7 +23,7 @@ #include #include -/* --- Context Management --- */ +/* ----- Context Management ----- */ int wolfSPDM_Init(WOLFSPDM_CTX* ctx) { @@ -33,7 +33,7 @@ int wolfSPDM_Init(WOLFSPDM_CTX* ctx) return WOLFSPDM_E_INVALID_ARG; } - /* Clean slate — do NOT read any fields before this (could be garbage) */ + /* Clean slate, dont read fields before this */ XMEMSET(ctx, 0, sizeof(WOLFSPDM_CTX)); ctx->state = WOLFSPDM_STATE_INIT; @@ -44,14 +44,11 @@ int wolfSPDM_Init(WOLFSPDM_CTX* ctx) } ctx->flags.rngInitialized = 1; - /* Set default requester capabilities */ - ctx->reqCaps = WOLFSPDM_DEFAULT_REQ_CAPS; - /* 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; } @@ -98,19 +95,6 @@ void wolfSPDM_Free(WOLFSPDM_CTX* ctx) wc_ecc_free(&ctx->ephemeralKey); } - /* Free responder public key (used for measurement/challenge verification) */ - if (ctx->flags.hasResponderPubKey) { - wc_ecc_free(&ctx->responderPubKey); - } - -#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)); @@ -140,7 +124,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) { @@ -188,7 +172,6 @@ int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, XMEMCPY(ctx->reqPrivKey, privKey, privKeySz); ctx->reqPrivKeyLen = privKeySz; XMEMCPY(ctx->reqPubKey, pubKey, pubKeySz); - ctx->reqPubKeyLen = pubKeySz; ctx->flags.hasReqKeyPair = 1; return WOLFSPDM_SUCCESS; @@ -210,83 +193,43 @@ int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, } #endif /* WOLFSPDM_NUVOTON */ -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 > WOLFSPDM_MAX_CERT_CHAIN) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - XMEMCPY(ctx->trustedCAs, derCerts, derCertsSz); - ctx->trustedCAsSz = derCertsSz; - ctx->flags.hasTrustedCAs = 1; - - return WOLFSPDM_SUCCESS; -} - void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable) { if (ctx != NULL) { - ctx->flags.debug = enable; + ctx->flags.debug = (enable != 0); } } -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; - return WOLFSPDM_SUCCESS; - } - - /* Validate range: we only support 1.2 through 1.4 */ - if (maxVersion < SPDM_VERSION_12 || maxVersion > SPDM_VERSION_14) { - return WOLFSPDM_E_INVALID_ARG; - } - - ctx->maxVersion = maxVersion; - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode) -{ - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; + if (mode != WOLFSPDM_MODE_NUVOTON) { + return WOLFSPDM_E_INVALID_ARG; /* Only Nuvoton mode supported */ } - if (mode == WOLFSPDM_MODE_NUVOTON) { #ifdef WOLFSPDM_NUVOTON - ctx->mode = WOLFSPDM_MODE_NUVOTON; - /* Initialize Nuvoton-specific fields */ - ctx->connectionHandle = WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT; - ctx->fipsIndicator = WOLFSPDM_NUVOTON_FIPS_DEFAULT; - return WOLFSPDM_SUCCESS; + ctx->mode = WOLFSPDM_MODE_NUVOTON; + /* Initialize Nuvoton-specific fields */ + ctx->connectionHandle = WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT; + ctx->fipsIndicator = WOLFSPDM_NUVOTON_FIPS_DEFAULT; + return WOLFSPDM_SUCCESS; #else - return WOLFSPDM_E_INVALID_ARG; /* Nuvoton support not compiled in */ + return WOLFSPDM_E_INVALID_ARG; /* Nuvoton support not compiled in */ #endif - } - - ctx->mode = WOLFSPDM_MODE_STANDARD; - return WOLFSPDM_SUCCESS; } WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx) { if (ctx == NULL) { - return WOLFSPDM_MODE_STANDARD; + return (WOLFSPDM_MODE)0; } return ctx->mode; } -/* --- Session Status --- */ +/* ----- Session Status ----- */ int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx) { @@ -330,49 +273,7 @@ word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx) } #endif -/* --- Session Establishment - Connect (Full Handshake) --- */ - -/* Standard SPDM 1.2 connection flow (for libspdm emulator, etc.) */ -static int wolfSPDM_ConnectStandard(WOLFSPDM_CTX* ctx) -{ - int rc; - - /* Reset state for new connection */ - ctx->state = WOLFSPDM_STATE_INIT; - 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)); - SPDM_CONNECT_STEP(ctx, "Step 5: GET_CERTIFICATE\n", - wolfSPDM_GetCertificate(ctx, 0)); - - /* Validate certificate chain if trusted CAs are loaded */ - if (ctx->flags.hasTrustedCAs) { - SPDM_CONNECT_STEP(ctx, "Validating certificate chain\n", - wolfSPDM_ValidateCertChain(ctx)); - } - else if (!ctx->flags.hasResponderPubKey) { - wolfSPDM_DebugPrint(ctx, - "Warning: No trusted CAs loaded — chain not validated\n"); - } - - 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; -} +/* ----- Session Establishment - Connect (Full Handshake) ----- */ int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) { @@ -388,14 +289,13 @@ int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) return WOLFSPDM_E_IO_FAIL; } - /* Dispatch based on mode */ #ifdef WOLFSPDM_NUVOTON if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { return wolfSPDM_ConnectNuvoton(ctx); } #endif - return wolfSPDM_ConnectStandard(ctx); + return WOLFSPDM_E_INVALID_ARG; /* Standard mode not available */ } int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) @@ -416,24 +316,42 @@ int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) /* Build END_SESSION */ txSz = sizeof(txBuf); rc = wolfSPDM_BuildEndSession(ctx, txBuf, &txSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); } - /* Send as secured message */ - rxSz = sizeof(rxBuf); - rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); - - /* Reset state regardless of result */ + /* Reset state and zero ALL key material */ ctx->state = WOLFSPDM_STATE_INIT; ctx->sessionId = 0; ctx->reqSeqNum = 0; ctx->rspSeqNum = 0; + /* 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 == WOLFSPDM_SUCCESS) ? WOLFSPDM_SUCCESS : rc; + return rc; } -/* --- I/O Helper --- */ +/* ----- I/O Helper ----- */ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, @@ -447,13 +365,12 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, #ifdef WOLFSPDM_NUVOTON if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { - /* In Nuvoton mode, all messages need TCG SPDM Binding headers. - * Clear SPDM messages (version 0x10-0x1F): tag 0x8101 - * Secured messages (encrypted records): tag 0x8201 - * The I/O callback receives fully-framed TCG messages and - * just needs to transport them over SPI/I2C to the TPM. */ - byte tcgTx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_TCG_HEADER_SIZE]; - byte tcgRx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_TCG_HEADER_SIZE]; + /* 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; word16 tag; @@ -465,8 +382,7 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, /* Clear SPDM message - wrap with TCG clear header (0x8101) */ tcgTxSz = wolfSPDM_BuildTcgClearMessage(ctx, txBuf, txSz, tcgTx, sizeof(tcgTx)); - } - else { + } else { /* Secured record - prepend TCG secured header (0x8201) */ word32 totalSz = WOLFSPDM_TCG_HEADER_SIZE + txSz; if (totalSz > sizeof(tcgTx)) { @@ -521,9 +437,10 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, word32 msgSize = SPDM_Get32BE(tcgRx + 2); word32 payloadSz; - if (msgSize > tcgRxSz) { - wolfSPDM_DebugPrint(ctx, "SendReceive: TCG size %u > " - "received %u\n", msgSize, tcgRxSz); + 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; } @@ -548,7 +465,7 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } -/* --- Debug Utilities --- */ +/* ----- Debug Utilities ----- */ void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) { @@ -585,57 +502,7 @@ void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, fflush(stdout); } -/* --- 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) { @@ -656,17 +523,8 @@ 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"; default: return "Unknown error"; } } diff --git a/spdm/src/spdm_crypto.c b/spdm/src/spdm_crypto.c index ebf13c5a..b7938f47 100644 --- a/spdm/src/spdm_crypto.c +++ b/spdm/src/spdm_crypto.c @@ -31,7 +31,7 @@ static void wolfSPDM_LeftPadToSize(byte* buf, word32 currentSz, word32 targetSz) } } -/* --- Random Number Generation --- */ +/* ----- Random Number Generation ----- */ int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz) { @@ -53,7 +53,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) { @@ -127,7 +127,7 @@ int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } -/* --- ECDH Shared Secret Computation --- */ +/* ----- ECDH Shared Secret Computation ----- */ int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, const byte* peerPubKeyX, const byte* peerPubKeyY) @@ -144,51 +144,138 @@ 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; + 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 */ + 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; } - 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 (peerKeyInit) { + wc_ecc_free(&peerKey); } - /* Compute ECDH shared secret */ - 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); - goto cleanup; + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +/* ----- ECDSA Signature Verification (P-384) ----- */ + +int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, + const byte* sig, word32 sigSz) +{ + ecc_key verifyKey; + int rc; + int keyInit = 0; + byte derSig[120]; /* P-384 DER sig max */ + 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.hasRspPubKey || ctx->rspPubKeyLen < WOLFSPDM_ECC_POINT_SIZE) { + wolfSPDM_DebugPrint(ctx, "No responder public key for verification\n"); + return WOLFSPDM_E_BAD_STATE; } - /* Zero-pad if needed (P-384 should always return 48 bytes, but just in case) */ - wolfSPDM_LeftPadToSize(ctx->sharedSecret, ctx->sharedSecretSz, - WOLFSPDM_ECC_KEY_SIZE); - ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; + if (sigSz != WOLFSPDM_ECC_SIG_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } - wolfSPDM_DebugPrint(ctx, "ECDH shared secret computed (%u bytes)\n", - ctx->sharedSecretSz); + /* Extract X/Y coordinates from rspPubKey. + * If len == 96: raw X||Y format. + * If len > 96: TPMT_PUBLIC format — X/Y are at the tail: + * [len-100]: X size(2 BE) + X(48) + Y size(2 BE) + Y(48) */ + if (ctx->rspPubKeyLen == WOLFSPDM_ECC_POINT_SIZE) { + pubKeyX = ctx->rspPubKey; + pubKeyY = ctx->rspPubKey + WOLFSPDM_ECC_KEY_SIZE; + } else if (ctx->rspPubKeyLen >= WOLFSPDM_ECC_POINT_SIZE + 4) { + /* TPMT_PUBLIC: skip 2-byte size prefixes on each coordinate */ + pubKeyX = ctx->rspPubKey + (ctx->rspPubKeyLen - 100 + 2); + pubKeyY = ctx->rspPubKey + (ctx->rspPubKeyLen - 48); + } else { + return WOLFSPDM_E_INVALID_ARG; + } - rc = 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); + } + } + 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); + } + } + /* 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); + } + } + 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 (rc == 0 && !verified) { + wolfSPDM_DebugPrint(ctx, "Responder signature verification FAILED\n"); + rc = -1; + } + if (rc == 0) { + wolfSPDM_DebugPrint(ctx, "Responder signature VERIFIED OK\n"); + } -cleanup: - if (peerKeyInit) { - wc_ecc_free(&peerKey); + if (keyInit) { + wc_ecc_free(&verifyKey); } - return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_BAD_SIGNATURE; } -/* --- ECDSA Signing (P-384) --- */ +/* ----- ECDSA Signing (P-384) ----- */ int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, byte* sig, word32* sigSz) @@ -209,59 +296,50 @@ int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, return WOLFSPDM_E_BAD_STATE; } - /* Need at least 96 bytes for P-384 signature (R||S) */ if (*sigSz < WOLFSPDM_ECC_POINT_SIZE) { return WOLFSPDM_E_BUFFER_SMALL; } - /* Initialize key structure */ rc = wc_ecc_init(&sigKey); - if (rc != 0) { + 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); - return WOLFSPDM_E_CRYPTO_FAIL; } - keyInit = 1; - - /* Import private key and public key */ - rc = wc_ecc_import_unsigned(&sigKey, - ctx->reqPubKey, /* X coordinate */ - ctx->reqPubKey + WOLFSPDM_ECC_KEY_SIZE, /* Y coordinate */ - ctx->reqPrivKey, /* Private key (d) */ - ECC_SECP384R1); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "wc_ecc_import_unsigned failed: %d\n", rc); - goto cleanup; + 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); + } } - - /* Sign the hash - wolfSSL returns DER encoded signature */ - 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); - goto cleanup; - } - /* Convert DER signature to raw R||S format (96 bytes for P-384) */ - 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); - goto cleanup; + 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); } - /* Pad R and S to 48 bytes if needed */ - 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; /* 96 bytes */ - - wolfSPDM_DebugPrint(ctx, "Signed hash with P-384 key (sig=%u bytes)\n", *sigSz); - - rc = 0; - -cleanup: if (keyInit) { wc_ecc_free(&sigKey); } diff --git a/spdm/src/spdm_internal.h b/spdm/src/spdm_internal.h index f1377918..9f32bf60 100644 --- a/spdm/src/spdm_internal.h +++ b/spdm/src/spdm_internal.h @@ -45,62 +45,39 @@ #include #include #include -#include #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 - -/* --- Measurement Block Structure --- */ +#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 */ -#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 bit-field struct to save ~28 bytes */ + /* Boolean flag bit field */ struct { - byte debug : 1; - byte initialized : 1; - byte isDynamic : 1; /* Set by wolfSPDM_New(), checked by Free */ - byte rngInitialized : 1; - byte ephemeralKeyInit : 1; - byte hasRspPubKey : 1; - byte hasReqKeyPair : 1; - byte hasMeasurements : 1; - byte hasResponderPubKey : 1; - byte hasTrustedCAs : 1; - byte m1m2HashInit : 1; + 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; - /* Protocol mode (standard SPDM or Nuvoton) */ + /* Protocol mode */ WOLFSPDM_MODE mode; /* I/O callback */ @@ -123,10 +100,6 @@ struct WOLFSPDM_CTX { /* Negotiated parameters */ byte maxVersion; /* Runtime max version cap (0 = use compile-time default) */ byte spdmVersion; /* Negotiated SPDM version */ - word32 rspCaps; /* Responder capabilities */ - word32 reqCaps; /* Our (requester) capabilities */ - byte mutAuthRequested; /* MutAuthRequested from KEY_EXCHANGE_RSP (offset 6) */ - byte reqSlotId; /* ReqSlotIDParam from KEY_EXCHANGE_RSP (offset 7) */ /* Ephemeral ECDHE key (generated for KEY_EXCHANGE) */ ecc_key ephemeralKey; @@ -138,11 +111,6 @@ struct WOLFSPDM_CTX { /* 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) */ @@ -179,52 +147,10 @@ struct WOLFSPDM_CTX { byte reqPrivKey[WOLFSPDM_ECC_KEY_SIZE]; word32 reqPrivKeyLen; byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; - word32 reqPubKeyLen; - -#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_ECC_SIG_SIZE]; /* Captured signature (96 bytes P-384) */ - word32 measSignatureSize; /* 0 if unsigned, 96 if signed */ - -#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) */ - ecc_key responderPubKey; /* Extracted from cert chain leaf */ - - /* Certificate chain validation */ - byte trustedCAs[WOLFSPDM_MAX_CERT_CHAIN]; /* DER-encoded root CAs */ - word32 trustedCAsSz; - -#ifndef NO_WOLFSPDM_CHALLENGE - /* Challenge authentication */ - byte challengeNonce[32]; /* Saved nonce from CHALLENGE request */ - byte challengeMeasHashType; /* MeasurementSummaryHashType from req */ - - /* 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 - /* Key update state — app secrets for re-derivation */ - byte reqAppSecret[WOLFSPDM_HASH_SIZE]; /* 48 bytes */ - byte rspAppSecret[WOLFSPDM_HASH_SIZE]; /* 48 bytes */ }; -/* --- 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); @@ -267,7 +193,8 @@ static WC_INLINE word64 SPDM_Get64LE(const byte* buf) { ((word64)buf[6] << 48) | ((word64)buf[7] << 56); } -/* Write TCG SPDM Binding header (16 bytes): tag(2/BE) + size(4/BE) + +/* ----- Write TCG SPDM Binding header ----- */ +/* tag(2/BE) + size(4/BE) + * connHandle(4/BE) + fips(2/BE) + reserved(4) */ #ifdef WOLFSPDM_NUVOTON static WC_INLINE void wolfSPDM_WriteTcgHeader(byte* buf, word16 tag, @@ -281,27 +208,17 @@ static WC_INLINE void wolfSPDM_WriteTcgHeader(byte* buf, word16 tag, } #endif -/* Build IV: BaseIV XOR zero-extended sequence number (DSP0277) */ +/* ----- Build IV ----- */ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, - word64 seqNum, int nuvotonMode) + word64 seqNum) { + byte seq[8]; int i; XMEMCPY(iv, baseIv, WOLFSPDM_AEAD_IV_SIZE); -#ifdef WOLFSPDM_NUVOTON - if (nuvotonMode) { - byte seq[8]; int i; - SPDM_Set64LE(seq, seqNum); - for (i = 0; i < 8; i++) iv[i] ^= seq[i]; - } - else -#endif - { - (void)nuvotonMode; - 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); \ @@ -309,19 +226,23 @@ 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) \ - if ((ctx) == NULL || (buf) == NULL || (bufSz) == NULL) \ - return WOLFSPDM_E_INVALID_ARG; \ - if (*(bufSz) < (minSz)) \ - return WOLFSPDM_E_BUFFER_SMALL + do { \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) == NULL) \ + return WOLFSPDM_E_INVALID_ARG; \ + if (*(bufSz) < (minSz)) \ + return WOLFSPDM_E_BUFFER_SMALL; \ + } while(0) #define SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, minSz) \ - if ((ctx) == NULL || (buf) == NULL || (bufSz) < (minSz)) \ - return WOLFSPDM_E_INVALID_ARG + do { \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) < (minSz)) \ + return WOLFSPDM_E_INVALID_ARG; \ + } while(0) -/* --- Response Code Check Macro --- */ +/* ----- Response Code Check Macro ----- */ #define SPDM_CHECK_RESPONSE(ctx, buf, bufSz, expected, fallbackErr) \ do { \ @@ -335,216 +256,79 @@ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, } \ } while (0) -/* --- Internal Function Declarations - Transcript --- */ +/* ----- Internal Function Declarations - Transcript ----- */ -/* Reset transcript buffer */ WOLFSPDM_API void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); - -/* Add data to transcript */ WOLFSPDM_API int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); - -/* Add data to certificate chain buffer */ -WOLFSPDM_API int wolfSPDM_CertChainAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); - -/* Compute hash of current transcript */ WOLFSPDM_API int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); - -/* Compute Ct = Hash(certificate_chain) */ -WOLFSPDM_API int wolfSPDM_ComputeCertChainHash(WOLFSPDM_CTX* ctx); - -/* SHA-384 hash helper: Hash(d1 || d2 || d3), pass NULL/0 for unused buffers */ 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 */ WOLFSPDM_API int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); - -/* Export ephemeral public key (X||Y) */ 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 */ WOLFSPDM_API int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, const byte* peerPubKeyX, const byte* peerPubKeyY); - -/* Generate random bytes */ WOLFSPDM_API int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); - -/* Sign hash with requester's private key (for mutual auth FINISH) */ WOLFSPDM_API int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, byte* sig, word32* sigSz); +WOLFSPDM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, + const byte* hash, word32 hashSz, + const byte* sig, word32 sigSz); -/* --- Internal Function Declarations - Key Derivation --- */ +/* ----- Internal Function Declarations - Key Derivation ----- */ -/* Derive all keys from shared secret and TH1 */ WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); - -/* Derive application data keys from MasterSecret and TH2_final */ WOLFSPDM_API int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx); - -/* HKDF-Expand with SPDM BinConcat format (uses version-specific prefix) */ 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 */ WOLFSPDM_API int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, byte* verifyData); -/* --- Internal Function Declarations - Message Building --- */ +/* ----- Internal Function Declarations - Message Building ----- */ -/* Build GET_VERSION request */ WOLFSPDM_API int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); - -/* Build GET_CAPABILITIES request */ -WOLFSPDM_API int wolfSPDM_BuildGetCapabilities(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build NEGOTIATE_ALGORITHMS request */ -WOLFSPDM_API int wolfSPDM_BuildNegotiateAlgorithms(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build GET_DIGESTS request */ -WOLFSPDM_API int wolfSPDM_BuildGetDigests(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build GET_CERTIFICATE request */ -WOLFSPDM_API int wolfSPDM_BuildGetCertificate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - int slotId, word16 offset, word16 length); - -/* Build KEY_EXCHANGE request */ WOLFSPDM_API int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build FINISH request */ WOLFSPDM_API int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Build END_SESSION request */ WOLFSPDM_API int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); -/* --- Internal Function Declarations - Message Parsing --- */ +/* ----- Internal Function Declarations - Message Parsing ----- */ -/* Parse VERSION response */ WOLFSPDM_API int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -/* Parse CAPABILITIES response */ -WOLFSPDM_API int wolfSPDM_ParseCapabilities(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -/* Parse ALGORITHMS response */ -WOLFSPDM_API int wolfSPDM_ParseAlgorithms(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -/* Parse DIGESTS response */ -WOLFSPDM_API int wolfSPDM_ParseDigests(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -/* Parse CERTIFICATE response */ -WOLFSPDM_API int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz, - word16* portionLen, word16* remainderLen); - -/* Parse KEY_EXCHANGE_RSP */ WOLFSPDM_API int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -/* Parse FINISH_RSP (after decryption) */ WOLFSPDM_API int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -/* Check for ERROR response */ WOLFSPDM_API 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 */ WOLFSPDM_API int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, const byte* plain, word32 plainSz, byte* enc, word32* encSz); - -/* Decrypt ciphertext using session keys */ 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 */ WOLFSPDM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); -/* Debug print (if enabled) */ WOLFSPDM_API void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) #ifdef __GNUC__ __attribute__((format(printf, 2, 3))) #endif ; -/* Hex dump for debugging */ 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 */ -WOLFSPDM_API int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - byte operation, byte requestSig); - -/* Parse MEASUREMENTS response */ -WOLFSPDM_API int wolfSPDM_ParseMeasurements(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); - -#ifndef NO_WOLFSPDM_MEAS_VERIFY -/* Verify measurement signature (L1/L2 transcript) */ -WOLFSPDM_API 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 */ -WOLFSPDM_API int wolfSPDM_ExtractResponderPubKey(WOLFSPDM_CTX* ctx); - -/* Validate certificate chain using trusted CAs and extract public key */ -WOLFSPDM_API int wolfSPDM_ValidateCertChain(WOLFSPDM_CTX* ctx); - -/* --- Internal Function Declarations - Challenge --- */ - -#ifndef NO_WOLFSPDM_CHALLENGE -/* Build CHALLENGE request */ -WOLFSPDM_API int wolfSPDM_BuildChallenge(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - int slotId, byte measHashType); - -/* Parse CHALLENGE_AUTH response */ -WOLFSPDM_API int wolfSPDM_ParseChallengeAuth(WOLFSPDM_CTX* ctx, const byte* buf, - word32 bufSz, word32* sigOffset); - -/* Verify CHALLENGE_AUTH signature */ -WOLFSPDM_API 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 */ -WOLFSPDM_API int wolfSPDM_BuildHeartbeat(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); - -/* Parse HEARTBEAT_ACK response */ -WOLFSPDM_API int wolfSPDM_ParseHeartbeatAck(WOLFSPDM_CTX* ctx, const byte* buf, - word32 bufSz); - -/* --- Internal Function Declarations - Key Update --- */ - -/* Build KEY_UPDATE request */ -WOLFSPDM_API int wolfSPDM_BuildKeyUpdate(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - byte operation, byte* tag); - -/* Parse KEY_UPDATE_ACK response */ -WOLFSPDM_API int wolfSPDM_ParseKeyUpdateAck(WOLFSPDM_CTX* ctx, const byte* buf, - word32 bufSz, byte operation, byte tag); - -/* Derive updated keys from saved app secrets */ -WOLFSPDM_API int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, int updateAll); - #ifdef __cplusplus } #endif diff --git a/spdm/src/spdm_kdf.c b/spdm/src/spdm_kdf.c index 424b2998..16411d10 100644 --- a/spdm/src/spdm_kdf.c +++ b/spdm/src/spdm_kdf.c @@ -21,22 +21,8 @@ #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,6 +30,7 @@ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secret { byte info[128]; word32 infoLen = 0; + word32 labelLen; const char* prefix; int rc; @@ -66,11 +53,18 @@ int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secret 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; - XMEMCPY(info + infoLen, label, XSTRLEN(label)); - infoLen += (word32)XSTRLEN(label); + XMEMCPY(info + infoLen, label, labelLen); + infoLen += labelLen; if (context != NULL && contextSz > 0) { XMEMCPY(info + infoLen, context, contextSz); @@ -148,47 +142,42 @@ int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash) ctx->sharedSecret, ctx->sharedSecretSz, ctx->handshakeSecret); if (rc != 0) { - 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; + rc = WOLFSPDM_E_CRYPTO_FAIL; + } + if (rc == WOLFSPDM_SUCCESS) { + /* 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); } - return wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->rspHsSecret, - ctx->rspDataKey, ctx->rspDataIv); + return rc; } int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) @@ -207,119 +196,58 @@ 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) { - return rc; - } - /* 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) { - return rc; - } - - /* 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) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - /* 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) { - return rc; - } - - /* 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) { - return rc; - } - - /* 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) { - return rc; - } - - rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, rspAppSecret, - ctx->rspDataKey, ctx->rspDataIv); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* 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"); - - return WOLFSPDM_SUCCESS; -} - -/* --- Key Update Re-derivation (DSP0277) --- */ - -int wolfSPDM_DeriveUpdatedKeys(WOLFSPDM_CTX* ctx, int updateAll) -{ - byte newReqAppSecret[WOLFSPDM_HASH_SIZE]; - int rc; - - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; + 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) { + /* 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; + } } - - /* 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) { - return rc; + 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); } - - rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, newReqAppSecret, - ctx->reqDataKey, ctx->reqDataIv); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + 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"); } - /* Save new requester secret for future updates */ - XMEMCPY(ctx->reqAppSecret, newReqAppSecret, WOLFSPDM_HASH_SIZE); - - /* Optionally update responder key */ - if (updateAll) { - byte newRspAppSecret[WOLFSPDM_HASH_SIZE]; - - rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->rspAppSecret, - WOLFSPDM_HASH_SIZE, SPDM_LABEL_UPDATE, NULL, 0, - newRspAppSecret, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, newRspAppSecret, - ctx->rspDataKey, ctx->rspDataIv); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Save new responder secret for future updates */ - XMEMCPY(ctx->rspAppSecret, newRspAppSecret, WOLFSPDM_HASH_SIZE); - } + /* 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)); - return WOLFSPDM_SUCCESS; + return rc; } diff --git a/spdm/src/spdm_msg.c b/spdm/src/spdm_msg.c index df12d0c2..b906820f 100644 --- a/spdm/src/spdm_msg.c +++ b/spdm/src/spdm_msg.c @@ -20,7 +20,6 @@ */ #include "spdm_internal.h" -#include int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) { @@ -29,7 +28,7 @@ int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) return WOLFSPDM_E_BUFFER_SMALL; /* Per SPDM spec, GET_VERSION always uses version 0x10 */ - buf[0] = SPDM_VERSION_10; + buf[0] = 0x10; buf[1] = SPDM_GET_VERSION; buf[2] = 0x00; buf[3] = 0x00; @@ -38,61 +37,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) */ - SPDM_Set32LE(&buf[12], WOLFSPDM_MAX_MSG_SIZE); - /* MaxSPDMmsgSize (4 LE) */ - SPDM_Set32LE(&buf[16], WOLFSPDM_MAX_MSG_SIZE); - - *bufSz = 20; - 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; /* Use negotiated version */ - buf[1] = SPDM_NEGOTIATE_ALGORITHMS; - buf[2] = 0x04; /* NumAlgoStructTables = 4 */ - buf[3] = 0x00; - buf[4] = 48; buf[5] = 0x00; /* Length = 48 */ - 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; - - /* Struct tables start at offset 32 */ - /* DHE: SECP_384_R1 */ - buf[32] = 0x02; buf[33] = 0x20; buf[34] = 0x10; buf[35] = 0x00; - /* AEAD: AES_256_GCM */ - buf[36] = 0x03; buf[37] = 0x20; buf[38] = 0x02; buf[39] = 0x00; - /* ReqBaseAsymAlg */ - buf[40] = 0x04; buf[41] = 0x20; buf[42] = 0x0F; buf[43] = 0x00; - /* KeySchedule */ - buf[44] = 0x05; buf[45] = 0x20; buf[46] = 0x01; buf[47] = 0x00; - - *bufSz = 48; - return WOLFSPDM_SUCCESS; -} - static int wolfSPDM_BuildSimpleMsg(WOLFSPDM_CTX* ctx, byte msgCode, byte* buf, word32* bufSz) { @@ -105,27 +49,6 @@ 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; -} - int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { word32 offset = 0; @@ -138,84 +61,78 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 180); rc = wolfSPDM_GenerateEphemeralKey(ctx); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &pubKeyXSz, + pubKeyY, &pubKeyYSz); - rc = wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &pubKeyXSz, - pubKeyY, &pubKeyYSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - XMEMSET(buf, 0, *bufSz); + 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 */ + /* Use negotiated SPDM version (not hardcoded 1.2) */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_KEY_EXCHANGE; + buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ #ifdef WOLFSPDM_NUVOTON - buf[offset++] = 0xFF; /* SlotID = 0xFF (no cert, use provisioned public key) */ + buf[offset++] = 0xFF; /* SlotID = 0xFF (no cert, use provisioned public key) */ #else - buf[offset++] = 0x00; /* SlotID = 0 (certificate slot 0) */ + 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; + /* 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; + /* 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; - /* OpaqueData for secured message version negotiation */ + /* OpaqueData for secured message version negotiation */ #ifdef WOLFSPDM_NUVOTON - /* Nuvoton format: 12 bytes per spec Rev 1.11 page 19-20 - * OpaqueLength(2 LE) + OpaqueData(12 bytes) = 14 bytes total */ - buf[offset++] = 0x0c; /* OpaqueLength = 12 (LE) */ - buf[offset++] = 0x00; - buf[offset++] = 0x00; buf[offset++] = 0x00; /* SMDataID = 0 */ - buf[offset++] = 0x05; buf[offset++] = 0x00; /* DataSize = 5 (LE) */ - buf[offset++] = 0x01; /* Registry ID = 1 (DMTF) */ - buf[offset++] = 0x01; /* VendorLen = 1 */ - buf[offset++] = 0x01; buf[offset++] = 0x00; /* VersionCount = 1, Reserved = 0 */ - buf[offset++] = 0x10; buf[offset++] = 0x00; /* Version 1.0 (0x0010 LE) */ - buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding to make OpaqueData 12 bytes */ + /* Nuvoton format: 12 bytes per spec Rev 1.11 page 19-20 */ + buf[offset++] = 0x0c; /* OpaqueLength = 12 (LE) */ + buf[offset++] = 0x00; + buf[offset++] = 0x00; buf[offset++] = 0x00; /* SMDataID = 0 */ + buf[offset++] = 0x05; buf[offset++] = 0x00; /* DataSize = 5 (LE) */ + buf[offset++] = 0x01; /* Registry ID = 1 (DMTF) */ + buf[offset++] = 0x01; /* VendorLen = 1 */ + buf[offset++] = 0x01; buf[offset++] = 0x00; /* VersionCount = 1, Reserved = 0 */ + buf[offset++] = 0x10; buf[offset++] = 0x00; /* Version 1.0 (0x0010 LE) */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding to make OpaqueData 12 bytes */ #else - /* Standard SPDM 1.2+ OpaqueData format: 20 bytes - * OpaqueLength must be a multiple of 4 per DSP0274. */ - 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 */ + /* 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 - *bufSz = offset; - return WOLFSPDM_SUCCESS; + *bufSz = offset; + } + } + + return rc; } -/* --- Shared Signing Helpers --- */ +/* ----- Shared Signing Helpers ----- */ /* Build SPDM 1.2+ signed hash per DSP0274: * M = combined_spdm_prefix || zero_pad || context_str || inputDigest @@ -247,6 +164,9 @@ static int wolfSPDM_BuildSignedHash(byte spdmVersion, } /* Zero padding: 36 - contextStrLen bytes */ + if (contextStrLen > 36) { + return WOLFSPDM_E_INVALID_ARG; + } zeroPadLen = 36 - contextStrLen; XMEMSET(&signMsg[signMsgLen], 0x00, zeroPadLen); signMsgLen += zeroPadLen; @@ -267,36 +187,6 @@ static int wolfSPDM_BuildSignedHash(byte spdmVersion, return WOLFSPDM_SUCCESS; } -/* 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); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "ECC verify_hash failed: %d\n", rc); - return WOLFSPDM_E_CRYPTO_FAIL; - } - - return verified == 1 ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; -} - int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) { byte th2Hash[WOLFSPDM_HASH_SIZE]; @@ -338,8 +228,7 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) if (mutualAuth) { buf[2] = 0x01; /* Param1: Signature field is included */ buf[3] = 0xFF; /* Param2: 0xFF = requester public key provisioned in trusted environment (GIVE_PUB_KEY) */ - } - else { + } else { buf[2] = 0x00; /* Param1: No signature */ buf[3] = 0x00; /* Param2: SlotID = 0 when no signature */ } @@ -350,95 +239,64 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) buf[offset++] = 0x00; } - /* Per DSP0274 / libspdm: When mutual auth is requested, the transcript - * for TH2 must include Hash(Cm_requester) - the hash of the requester's - * public key/cert chain - BETWEEN message_k and message_f (FINISH header). - * - * TH2 = Hash(VCA || Ct || message_k || Hash(Cm_req) || FINISH_header) - * - * For Nuvoton with PUB_KEY_ID (SlotID=0xFF), Cm is the TPMT_PUBLIC - * structure that was sent via GIVE_PUB_KEY. */ + rc = WOLFSPDM_SUCCESS; + + /* Mutual auth: add Hash(Cm_requester) to transcript between message_k + * and FINISH header. For Nuvoton PUB_KEY_ID, Cm = TPMT_PUBLIC. */ #ifdef WOLFSPDM_NUVOTON - if (mutualAuth && ctx->reqPubKeyTPMTLen > 0) { + 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) return rc; - rc = wolfSPDM_TranscriptAdd(ctx, cmHash, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) return rc; + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, cmHash, WOLFSPDM_HASH_SIZE); } #endif - /* Add FINISH header (+ OpaqueLength for 1.4) to transcript for TH2 */ - rc = wolfSPDM_TranscriptAdd(ctx, buf, offset); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* TH2 = Hash(transcript with FINISH header) */ - rc = wolfSPDM_TranscriptHash(ctx, th2Hash); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); + /* 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); - /* For mutual auth, use SPDM 1.2+ signing context format per DSP0274 */ - if (mutualAuth) { + /* 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) { - return rc; - } - - /* Sign Hash(M) */ - rc = wolfSPDM_SignHash(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, signature, &sigSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "Failed to sign FINISH: %d\n", rc); - return rc; - } - - /* Copy signature to buffer (96 bytes) */ - XMEMCPY(&buf[offset], signature, WOLFSPDM_ECC_POINT_SIZE); - offset += WOLFSPDM_ECC_POINT_SIZE; - - /* Per DSP0274: TH2 for RequesterVerifyData MUST include the signature. - * TH2_sign = Hash(transcript || FINISH_header[4]) - used above for signature - * TH2_hmac = Hash(transcript || FINISH_header[4] || Signature[96]) - used for HMAC - * Add signature to transcript and recompute TH2 for HMAC. */ - rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_POINT_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + 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); } - - rc = wolfSPDM_TranscriptHash(ctx, th2Hash); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - } - - /* RequesterVerifyData = HMAC(reqFinishedKey, TH2_hmac) - * For mutual auth: th2Hash now includes the signature (TH2_hmac) - * For no mutual auth: th2Hash is just Hash(transcript || FINISH_header) */ - rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, verifyData); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + 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) { - return rc; + /* 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; - return WOLFSPDM_SUCCESS; + /* Always zero sensitive stack buffers */ + wc_ForceZero(th2Hash, sizeof(th2Hash)); + wc_ForceZero(verifyData, sizeof(verifyData)); + wc_ForceZero(signature, sizeof(signature)); + return rc; } int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) @@ -464,7 +322,7 @@ int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode) /* 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. Runtime override via wolfSPDM_SetMaxVersion(). */ + * at a lower version. */ #ifndef WOLFSPDM_MAX_SPDM_VERSION #define WOLFSPDM_MAX_SPDM_VERSION SPDM_VERSION_14 #endif @@ -531,90 +389,6 @@ 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_ARGS(ctx, buf, bufSz, 12); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CAPABILITIES, WOLFSPDM_E_CAPS_MISMATCH); - - ctx->rspCaps = SPDM_Get32LE(&buf[8]); - 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; - - SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 36); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_ALGORITHMS, 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]); - - if (!(baseAsymAlgo & SPDM_ASYM_ALGO_ECDSA_P384)) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: responder does not support ECDSA P-384 (0x%08x)\n", - baseAsymAlgo); - return WOLFSPDM_E_ALGO_MISMATCH; - } - if (!(baseHashAlgo & SPDM_HASH_ALGO_SHA_384)) { - wolfSPDM_DebugPrint(ctx, - "ALGORITHMS: responder does not support SHA-384 (0x%08x)\n", - baseHashAlgo); - return WOLFSPDM_E_ALGO_MISMATCH; - } - - 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_ARGS(ctx, buf, bufSz, 4); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_DIGESTS, WOLFSPDM_E_CERT_FAIL); - - ctx->state = WOLFSPDM_STATE_DIGESTS; - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_ParseCertificate(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz, - word16* portionLen, word16* remainderLen) -{ - if (ctx == NULL || buf == NULL || bufSz < 8 || - portionLen == NULL || remainderLen == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_CERTIFICATE, WOLFSPDM_E_CERT_FAIL); - - *portionLen = SPDM_Get16LE(&buf[4]); - *remainderLen = SPDM_Get16LE(&buf[6]); - - /* Add certificate chain data (starting at offset 8) */ - if (*portionLen > 0 && bufSz >= (word32)(8 + *portionLen)) { - wolfSPDM_CertChainAdd(ctx, buf + 8, *portionLen); - } - - if (*remainderLen == 0) { - ctx->state = WOLFSPDM_STATE_CERT; - } - - return WOLFSPDM_SUCCESS; -} - int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) { word16 opaqueLen; @@ -622,9 +396,11 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS 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]; int rc; SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 140); @@ -633,10 +409,6 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS ctx->rspSessionId = SPDM_Get16LE(&buf[4]); ctx->sessionId = (word32)ctx->reqSessionId | ((word32)ctx->rspSessionId << 16); - /* Parse MutAuthRequested (offset 6) and ReqSlotIDParam (offset 7) per DSP0274 */ - ctx->mutAuthRequested = buf[6]; - ctx->reqSlotId = 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); @@ -646,8 +418,6 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS sigOffset = 138 + opaqueLen; keRspPartialLen = sigOffset; - (void)opaqueLen; - if (bufSz < sigOffset + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE) { return WOLFSPDM_E_BUFFER_SMALL; } @@ -657,57 +427,65 @@ int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufS /* Add KEY_EXCHANGE_RSP partial (without sig/verify) to transcript */ rc = wolfSPDM_TranscriptAdd(ctx, buf, keRspPartialLen); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* TODO: Verify responder signature (DSP0274) - prevents MITM key - * substitution. Requires correct TH1 transcript construction which - * differs between standard and Nuvoton modes. */ - /* Add signature to transcript (TH1 includes signature) */ - rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_SIG_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + /* 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; } - - /* Compute ECDH shared secret */ - rc = wolfSPDM_ComputeSharedSecret(ctx, peerPubKeyX, peerPubKeyY); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th1SigHash); } - - /* Compute TH1 = Hash(transcript including signature) */ - rc = wolfSPDM_TranscriptHash(ctx, ctx->th1); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "responder-key_exchange_rsp signing", 34, + th1SigHash, signMsgHash); } - /* Derive all session keys */ - rc = wolfSPDM_DeriveHandshakeKeys(ctx, ctx->th1); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + 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"); } - - /* Verify ResponderVerifyData = HMAC(rspFinishedKey, TH1) */ - rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, ctx->th1, expectedHmac); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_SIG_SIZE); } - - if (XMEMCMP(expectedHmac, rspVerifyData, WOLFSPDM_HASH_SIZE) != 0) { - wolfSPDM_DebugPrint(ctx, "ResponderVerifyData MISMATCH\n"); - } else { + 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, expectedHmac); + } + if (rc == WOLFSPDM_SUCCESS) { + word32 i; + 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; + } + } + if (rc == WOLFSPDM_SUCCESS) { wolfSPDM_DebugPrint(ctx, "ResponderVerifyData VERIFIED OK\n"); + rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); } - - /* Add ResponderVerifyData to transcript (per SPDM spec, always included) */ - rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_KEY_EX; } - ctx->state = WOLFSPDM_STATE_KEY_EX; - return WOLFSPDM_SUCCESS; + wc_ForceZero(expectedHmac, sizeof(expectedHmac)); + wc_ForceZero(th1SigHash, sizeof(th1SigHash)); + wc_ForceZero(signMsgHash, sizeof(signMsgHash)); + return rc; } int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) @@ -748,685 +526,3 @@ int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) return WOLFSPDM_E_BAD_STATE; } - -/* --- Measurement Message Building and Parsing --- */ - -#ifndef NO_WOLFSPDM_MEAS - -int wolfSPDM_BuildGetMeasurements(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz, - byte operation, byte requestSig) -{ - word32 offset = 0; - - 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, plus - * OpaqueDataLength(2) when signature is requested */ - { - word32 minSz = 4; - if (requestSig) { - minSz += 32 + 1; /* Nonce + SlotIDParam */ - } - if (ctx->spdmVersion >= SPDM_VERSION_13) { - minSz += 8; /* RequesterContext (always for 1.3+) */ - if (requestSig) - minSz += 2; /* OpaqueDataLength */ - } - 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 (1 byte) — slot 0 */ - buf[offset++] = 0x00; - } - - /* SPDM 1.3+ adds RequesterContext (8 bytes) for both signed and unsigned. - * Per DSP0274 Table 51, this field is always present for version >= 1.3. */ - if (ctx->spdmVersion >= SPDM_VERSION_13) { - int rc = wolfSPDM_GetRandom(ctx, &buf[offset], 8); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - offset += 8; - /* Note: OpaqueDataLength is NOT part of GET_MEASUREMENTS request - * per DSP0274 Table 51 / libspdm. Only RequesterContext is added. */ - } - - *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; - - SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 8); - SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_MEASUREMENTS, 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 + Signature */ - /* Nonce is present only if signature was requested */ - ctx->measSignatureSize = 0; - - if (offset + 32 + 2 <= bufSz) { - /* 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; - - /* Signature (if present) */ - if (offset + WOLFSPDM_ECC_SIG_SIZE <= bufSz) { - XMEMCPY(ctx->measSignature, &buf[offset], WOLFSPDM_ECC_SIG_SIZE); - ctx->measSignatureSize = WOLFSPDM_ECC_SIG_SIZE; - } - } - - 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: hash, overwritten by BuildSignedHash */ - const byte* sig, word32 sigSz, - const char* passMsg, const char* failMsg, int failErr) -{ - int rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, - contextStr, contextStrLen, digest, digest); - if (rc != WOLFSPDM_SUCCESS) return rc; - - rc = wolfSPDM_VerifyEccSig(ctx, sig, sigSz, digest, WOLFSPDM_HASH_SIZE); - if (rc == WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "%s\n", passMsg); - return WOLFSPDM_SUCCESS; - } - wolfSPDM_DebugPrint(ctx, "%s\n", failMsg); - return failErr; -} - -int wolfSPDM_VerifyMeasurementSig(WOLFSPDM_CTX* ctx, - const byte* rspBuf, word32 rspBufSz, - const byte* reqMsg, word32 reqMsgSz) -{ - byte digest[WOLFSPDM_HASH_SIZE]; - word32 sigOffset; - 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 WOLFSPDM_ECC_SIG_SIZE bytes of the response */ - if (rspBufSz < WOLFSPDM_ECC_SIG_SIZE) { - return WOLFSPDM_E_MEASUREMENT; - } - sigOffset = rspBufSz - WOLFSPDM_ECC_SIG_SIZE; - - /* 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) return rc; - - return wolfSPDM_VerifySignedDigest(ctx, - "responder-measurements signing", 30, digest, - rspBuf + sigOffset, WOLFSPDM_ECC_SIG_SIZE, - "Measurement signature VERIFIED", - "Measurement signature INVALID", - WOLFSPDM_E_MEAS_SIG_FAIL); -} - -#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; -} - -int wolfSPDM_ExtractResponderPubKey(WOLFSPDM_CTX* ctx) -{ - DecodedCert cert; - const byte* leafCert; - word32 leafCertSz; - word32 idx; - 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; - } - - /* Extract public key from cert and import into ecc_key */ - rc = wc_ecc_init(&ctx->responderPubKey); - if (rc != 0) { - wc_FreeDecodedCert(&cert); - return WOLFSPDM_E_CRYPTO_FAIL; - } - - idx = 0; - rc = wc_EccPublicKeyDecode(cert.publicKey, &idx, &ctx->responderPubKey, - cert.pubKeySize); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "ECC public key decode failed: %d\n", rc); - wc_ecc_free(&ctx->responderPubKey); - wc_FreeDecodedCert(&cert); - return WOLFSPDM_E_CERT_PARSE; - } - - wc_FreeDecodedCert(&cert); - ctx->flags.hasResponderPubKey = 1; - wolfSPDM_DebugPrint(ctx, "Extracted responder ECC P-384 public key\n"); - - 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 (reuses FindLeafCert internally) */ - 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; - - /* SPDM 1.3+ adds RequesterContext(8) per DSP0274 Table 46 */ - minSz = 36; - if (ctx->spdmVersion >= SPDM_VERSION_13) - minSz += 8; /* RequesterContext */ - SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, minSz); - - buf[offset++] = ctx->spdmVersion; - buf[offset++] = SPDM_CHALLENGE; - buf[offset++] = (byte)(slotId & 0x0F); - buf[offset++] = measHashType; - - /* Save measHashType for ParseChallengeAuth */ - ctx->challengeMeasHashType = measHashType; - - /* 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. - * 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; - } - 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); - - 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 */ - if (ctx->spdmVersion >= SPDM_VERSION_13) { - if (offset + 8 > bufSz) { - wolfSPDM_DebugPrint(ctx, - "CHALLENGE_AUTH: too short for RequesterContext\n"); - return WOLFSPDM_E_CHALLENGE; - } - offset += 8; - } - - /* Signature starts here */ - if (offset + WOLFSPDM_ECC_SIG_SIZE > 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) */ - rc = wc_Sha384Update(&ctx->m1m2Hash, reqMsg, reqMsgSz); - if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; - rc = wc_Sha384Update(&ctx->m1m2Hash, rspBuf, sigOffset); - if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; - - /* Finalize M1/M2 hash */ - rc = wc_Sha384Final(&ctx->m1m2Hash, digest); - ctx->flags.m1m2HashInit = 0; /* Hash consumed */ - if (rc != 0) return WOLFSPDM_E_CRYPTO_FAIL; - - return wolfSPDM_VerifySignedDigest(ctx, - "responder-challenge_auth signing", 32, digest, - rspBuf + sigOffset, WOLFSPDM_ECC_SIG_SIZE, - "CHALLENGE_AUTH signature VERIFIED", - "CHALLENGE_AUTH signature INVALID", - WOLFSPDM_E_CHALLENGE); -} - -#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_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_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/spdm/src/spdm_nuvoton.c b/spdm/src/spdm_nuvoton.c index f469a10e..449ff17f 100644 --- a/spdm/src/spdm_nuvoton.c +++ b/spdm/src/spdm_nuvoton.c @@ -19,31 +19,13 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ -/* Nuvoton TPM SPDM Support - * - * This file implements Nuvoton-specific SPDM functionality: - * - TCG SPDM Binding message framing (per TCG SPDM Binding Spec v1.0) - * - Nuvoton vendor-defined commands (GET_PUBK, GIVE_PUB, GET_STS_, SPDMONLY) - * - Nuvoton SPDM handshake flow - * - * Reference: Nuvoton SPDM Guidance Rev 1.11 - */ - #include "spdm_internal.h" #ifdef WOLFSPDM_NUVOTON #include -/* Check for SPDM ERROR in response payload */ -#define SPDM_CHECK_ERROR_RSP(ctx, buf, sz, label) \ - if ((sz) >= 4 && (buf)[1] == SPDM_ERROR) { \ - wolfSPDM_DebugPrint(ctx, label ": SPDM ERROR 0x%02x 0x%02x\n", \ - (buf)[2], (buf)[3]); \ - return WOLFSPDM_E_PEER_ERROR; \ - } - -/* --- Vendor Command Helper Types --- */ +/* ----- Vendor Command Helper Types ----- */ /* Response container for clear vendor commands */ typedef struct { @@ -62,8 +44,11 @@ static int wolfSPDM_VendorCmdClear(WOLFSPDM_CTX* ctx, const char* vdCode, word32 rxSz; int rc; - spdmMsgSz = wolfSPDM_BuildVendorDefined(vdCode, payload, payloadSz, - spdmMsg, sizeof(spdmMsg)); + { + byte ver = ctx->spdmVersion ? ctx->spdmVersion : SPDM_VERSION_13; + spdmMsgSz = wolfSPDM_BuildVendorDefined(ver, vdCode, payload, + payloadSz, spdmMsg, sizeof(spdmMsg)); + } if (spdmMsgSz < 0) { return spdmMsgSz; } @@ -103,8 +88,11 @@ static int wolfSPDM_VendorCmdSecured(WOLFSPDM_CTX* ctx, const char* vdCode, word32 decSz; int rc; - spdmMsgSz = wolfSPDM_BuildVendorDefined(vdCode, payload, payloadSz, - spdmMsg, sizeof(spdmMsg)); + { + byte ver = ctx->spdmVersion ? ctx->spdmVersion : SPDM_VERSION_13; + spdmMsgSz = wolfSPDM_BuildVendorDefined(ver, vdCode, payload, + payloadSz, spdmMsg, sizeof(spdmMsg)); + } if (spdmMsgSz < 0) { return spdmMsgSz; } @@ -125,7 +113,7 @@ static int wolfSPDM_VendorCmdSecured(WOLFSPDM_CTX* ctx, const char* vdCode, return WOLFSPDM_SUCCESS; } -/* --- TCG SPDM Binding Message Framing --- */ +/* ----- TCG SPDM Binding Message Framing ----- */ int wolfSPDM_BuildTcgClearMessage( WOLFSPDM_CTX* ctx, @@ -178,7 +166,7 @@ int wolfSPDM_ParseTcgClearMessage( } msgSize = SPDM_Get32BE(inBuf + 2); - if (msgSize > inBufSz) { + if (msgSize < WOLFSPDM_TCG_HEADER_SIZE || msgSize > inBufSz) { return WOLFSPDM_E_BUFFER_SMALL; } @@ -203,155 +191,10 @@ int wolfSPDM_ParseTcgClearMessage( return (int)payloadSz; } -int wolfSPDM_BuildTcgSecuredMessage( - WOLFSPDM_CTX* ctx, - const byte* encPayload, word32 encPayloadSz, - const byte* mac, word32 macSz, - byte* outBuf, word32 outBufSz) -{ - word32 totalSz; - word32 offset; - word16 recordLen; - - if (ctx == NULL || encPayload == NULL || mac == NULL || outBuf == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - /* Total: TCG header(16) + sessionId(4/LE) + seqNum(8/LE) + - * length(2/LE) + encPayload + MAC */ - totalSz = WOLFSPDM_TCG_HEADER_SIZE + WOLFSPDM_TCG_SECURED_HDR_SIZE + - encPayloadSz + macSz; - - if (outBufSz < totalSz) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - /* TCG binding header (16 bytes, all BE) */ - wolfSPDM_WriteTcgHeader(outBuf, WOLFSPDM_TCG_TAG_SECURED, totalSz, - ctx->connectionHandle, ctx->fipsIndicator); - - offset = WOLFSPDM_TCG_HEADER_SIZE; - - /* Session ID (4 bytes LE per DSP0277): - * ReqSessionId(2/LE) || RspSessionId(2/LE) */ - SPDM_Set16LE(outBuf + offset, ctx->reqSessionId); - offset += 2; - SPDM_Set16LE(outBuf + offset, ctx->rspSessionId); - offset += 2; - - /* Sequence Number (8 bytes LE per DSP0277) */ - SPDM_Set64LE(outBuf + offset, ctx->reqSeqNum); - offset += 8; - - /* Length (2 bytes LE per DSP0277) = encrypted data + MAC */ - recordLen = (word16)(encPayloadSz + macSz); - SPDM_Set16LE(outBuf + offset, recordLen); - offset += 2; - - /* Encrypted payload */ - XMEMCPY(outBuf + offset, encPayload, encPayloadSz); - offset += encPayloadSz; - - /* MAC (AES-256-GCM tag) */ - XMEMCPY(outBuf + offset, mac, macSz); - - /* Note: Sequence number increment is handled by caller */ - - return (int)totalSz; -} - -int wolfSPDM_ParseTcgSecuredMessage( - const byte* inBuf, word32 inBufSz, - word32* sessionId, word64* seqNum, - byte* encPayload, word32* encPayloadSz, - byte* mac, word32* macSz, - WOLFSPDM_TCG_SECURED_HDR* hdr) -{ - word16 tag; - word32 msgSize; - word32 offset; - word16 recordLen; - word32 payloadSz; - - if (inBuf == NULL || sessionId == NULL || seqNum == NULL || - encPayload == NULL || encPayloadSz == NULL || - mac == NULL || macSz == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - if (inBufSz < WOLFSPDM_TCG_HEADER_SIZE + WOLFSPDM_TCG_SECURED_HDR_SIZE + - WOLFSPDM_AEAD_TAG_SIZE) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - /* Parse TCG binding header (16 bytes, all BE) */ - tag = SPDM_Get16BE(inBuf); - if (tag != WOLFSPDM_TCG_TAG_SECURED) { - return WOLFSPDM_E_PEER_ERROR; - } - - msgSize = SPDM_Get32BE(inBuf + 2); - if (msgSize > inBufSz) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - /* Fill header if requested */ - 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); - } - - offset = WOLFSPDM_TCG_HEADER_SIZE; - - /* Session ID (4 bytes LE per DSP0277): - * ReqSessionId(2/LE) || RspSessionId(2/LE) */ - { - word16 reqSid = SPDM_Get16LE(inBuf + offset); - word16 rspSid = SPDM_Get16LE(inBuf + offset + 2); - *sessionId = ((word32)reqSid << 16) | rspSid; - } - offset += 4; - - /* Sequence Number (8 bytes LE per DSP0277) */ - *seqNum = SPDM_Get64LE(inBuf + offset); - offset += 8; - - /* Length (2 bytes LE per DSP0277) = encrypted data + MAC */ - recordLen = SPDM_Get16LE(inBuf + offset); - offset += 2; - - /* Validate record length */ - if (recordLen < WOLFSPDM_AEAD_TAG_SIZE) { - return WOLFSPDM_E_BUFFER_SMALL; - } - if (offset + recordLen > inBufSz) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - /* Encrypted payload size = recordLen - MAC */ - payloadSz = recordLen - WOLFSPDM_AEAD_TAG_SIZE; - if (*encPayloadSz < payloadSz || *macSz < WOLFSPDM_AEAD_TAG_SIZE) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - /* Encrypted payload */ - XMEMCPY(encPayload, inBuf + offset, payloadSz); - *encPayloadSz = payloadSz; - offset += payloadSz; - - /* MAC */ - XMEMCPY(mac, inBuf + offset, WOLFSPDM_AEAD_TAG_SIZE); - *macSz = WOLFSPDM_AEAD_TAG_SIZE; - - return (int)payloadSz; -} - -/* --- SPDM Vendor Defined Message Helpers --- */ +/*SPDM Vendor Defined Message Helpers */ int wolfSPDM_BuildVendorDefined( + byte spdmVersion, const char* vdCode, const byte* payload, word32 payloadSz, byte* outBuf, word32 outBufSz) @@ -373,8 +216,7 @@ int wolfSPDM_BuildVendorDefined( return WOLFSPDM_E_BUFFER_SMALL; } - /* SPDM Version (v1.3 = 0x13) */ - outBuf[offset++] = SPDM_VERSION_13; + outBuf[offset++] = spdmVersion; /* Request/Response Code */ outBuf[offset++] = SPDM_VENDOR_DEFINED_REQUEST; /* Param1, Param2 */ @@ -466,7 +308,7 @@ int wolfSPDM_ParseVendorDefined( return (int)dataLen; } -/* --- Nuvoton-Specific SPDM Functions --- */ +/* ----- Nuvoton-Specific SPDM Functions ----- */ int wolfSPDM_Nuvoton_GetPubKey( WOLFSPDM_CTX* ctx, @@ -586,8 +428,7 @@ int wolfSPDM_Nuvoton_GetStatus( wolfSPDM_DebugPrint(ctx, "GET_STS_: SpecVersion=%u.%u, SPDMOnly=%s\n", specMajor, specMinor, spdmOnly ? "LOCKED" : "unlocked"); - } - else if (rsp.payloadSz >= 1) { + } else if (rsp.payloadSz >= 1) { /* Minimal response - just SPDMOnly */ status->spdmOnlyLocked = (rsp.payload[0] != 0); status->spdmEnabled = 1; @@ -627,17 +468,9 @@ int wolfSPDM_Nuvoton_SetOnlyMode( return WOLFSPDM_SUCCESS; } -/* --- Nuvoton SPDM Connection Flow --- */ +/* ----- Nuvoton SPDM Connection Flow ----- */ -/* Nuvoton-specific connection flow: - * GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH - * - * Key differences from standard SPDM: - * - No GET_CAPABILITIES or NEGOTIATE_ALGORITHMS (Algorithm Set B is fixed) - * - Uses GET_PUBK vendor command instead of GET_CERTIFICATE - * - Uses GIVE_PUB vendor command for mutual authentication - * - All messages wrapped in TCG binding headers - */ +/* Nuvoton connection: GET_VERSION -> GET_PUBK -> KEY_EXCHANGE -> GIVE_PUB -> FINISH */ int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) { int rc; @@ -695,8 +528,7 @@ int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) ctx->state = WOLFSPDM_STATE_ERROR; return rc; } - } - else { + } else { wolfSPDM_DebugPrint(ctx, "Nuvoton: Warning - no responder public key for Ct\n"); } @@ -716,8 +548,7 @@ int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) return rc; } wolfSPDM_DebugPrint(ctx, "GIVE_PUB succeeded!\n"); - } - else { + } else { wolfSPDM_DebugPrint(ctx, "Nuvoton Step 4: GIVE_PUB (skipped, no host key)\n"); } diff --git a/spdm/src/spdm_secured.c b/spdm/src/spdm_secured.c index 3b190ef3..7fffdf51 100644 --- a/spdm/src/spdm_secured.c +++ b/spdm/src/spdm_secured.c @@ -36,7 +36,6 @@ * Full message: Header || Ciphertext || Tag (16) */ - int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, const byte* plain, word32 plainSz, byte* enc, word32* encSz) @@ -50,17 +49,21 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, 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; } + if (plainSz > WOLFSPDM_MAX_MSG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } #ifdef WOLFSPDM_NUVOTON if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { /* 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: Rightmost 8 bytes (bytes 4-11) with 8-byte sequence number + * IV XOR: Leftmost 8 bytes (bytes 0-7) with 8-byte LE sequence number */ word16 appDataLen = (word16)plainSz; @@ -95,8 +98,7 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, aadSz = 14; XMEMCPY(aad, enc, aadSz); - } - else + } else #endif { /* MCTP format (per DSP0277): @@ -130,36 +132,32 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, } /* Build IV: BaseIV XOR sequence number (DSP0277) */ - wolfSPDM_BuildIV(iv, ctx->reqDataIv, ctx->reqSeqNum, - ctx->mode == WOLFSPDM_MODE_NUVOTON); + wolfSPDM_BuildIV(iv, ctx->reqDataIv, ctx->reqSeqNum); + /* AES-GCM encrypt — cascade with single cleanup */ rc = wc_AesInit(&aes, NULL, INVALID_DEVID); - if (rc != 0) { - 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) { + if (rc == 0) { + rc = wc_AesGcmEncrypt(&aes, &enc[hdrSz], plainBuf, plainBufSz, + iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); + } + if (aesInit) { wc_AesFree(&aes); - return WOLFSPDM_E_CRYPTO_FAIL; } - /* 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); - wc_AesFree(&aes); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; + 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)); } - 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)); - - return WOLFSPDM_SUCCESS; + wc_ForceZero(plainBuf, sizeof(plainBuf)); + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, @@ -172,113 +170,67 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, 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; word32 aadSz; + int aesInit = 0; + int ret; int rc; if (ctx == NULL || enc == NULL || plain == NULL || plainSz == NULL) { return WOLFSPDM_E_INVALID_ARG; } + /* ----- Transport-specific header parsing ----- */ #ifdef WOLFSPDM_NUVOTON if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { - /* Nuvoton TCG binding format per Rev 1.11 spec page 25: - * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes - * Encrypted: AppDataLength(2 LE) + SPDM message + RandomData padding - * MAC: 16 bytes - */ word64 rspSeqNum64; + word32 rspSessionId; + word16 rspLen; hdrSz = 14; aadSz = 14; - if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) { + if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) return WOLFSPDM_E_BUFFER_SMALL; - } - /* Parse header: SessionID(4) + SeqNum(8) + Length(2) */ rspSessionId = SPDM_Get32LE(&enc[0]); rspSeqNum64 = SPDM_Get64LE(&enc[4]); rspLen = SPDM_Get16LE(&enc[12]); - rspSeqNum = (word16)(rspSeqNum64 & 0xFFFF); /* For debug output */ 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) */ if (rspSeqNum64 != ctx->rspSeqNum) { - wolfSPDM_DebugPrint(ctx, "Sequence number mismatch: %llu != %llu\n", + 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; - /* Length field = ciphertext + MAC (per Nuvoton spec) */ - if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < hdrSz + rspLen) { + cipherLen = (word32)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + if (cipherLen > sizeof(decrypted)) return WOLFSPDM_E_BUFFER_SMALL; - } - cipherLen = (word16)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); ciphertext = enc + hdrSz; tag = enc + hdrSz + cipherLen; - XMEMCPY(aad, enc, aadSz); - - /* Build IV: BaseIV XOR sequence number (DSP0277 1.2) */ - wolfSPDM_BuildIV(iv, ctx->rspDataIv, rspSeqNum64, 1); - - rc = wc_AesInit(&aes, NULL, INVALID_DEVID); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; - } - rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); - if (rc != 0) { - wc_AesFree(&aes); - return WOLFSPDM_E_CRYPTO_FAIL; - } - - rc = wc_AesGcmDecrypt(&aes, decrypted, ciphertext, cipherLen, - iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); - wc_AesFree(&aes); - if (rc != 0) { - wolfSPDM_DebugPrint(ctx, "AES-GCM decrypt failed: %d\n", rc); - return WOLFSPDM_E_DECRYPT_FAIL; - } - - /* Parse decrypted: AppDataLen (2 LE) || SPDM message || RandomData */ - appDataLen = SPDM_Get16LE(decrypted); - - if (cipherLen < (word32)(2 + appDataLen)) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - if (*plainSz < appDataLen) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - /* Copy SPDM message (no MCTP header to skip) */ - XMEMCPY(plain, &decrypted[2], appDataLen); - *plainSz = appDataLen; - } - else + wolfSPDM_BuildIV(iv, ctx->rspDataIv, rspSeqNum64); + } else #endif { - /* MCTP format */ + word32 rspSessionId; + word16 rspSeqNum, rspLen; hdrSz = 8; aadSz = 8; - if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) { + 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]); @@ -288,111 +240,89 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, rspSessionId, ctx->sessionId); return WOLFSPDM_E_SESSION_INVALID; } - - /* Validate sequence number matches expected (DSP0277 replay protection) */ if ((word64)rspSeqNum != ctx->rspSeqNum) { - wolfSPDM_DebugPrint(ctx, "Sequence number mismatch: %u != %llu\n", + 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; - if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < (word32)(hdrSz + rspLen)) { + cipherLen = (word32)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + if (cipherLen > sizeof(decrypted)) return WOLFSPDM_E_BUFFER_SMALL; - } - cipherLen = (word16)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); ciphertext = enc + hdrSz; tag = enc + hdrSz + cipherLen; - XMEMCPY(aad, enc, aadSz); + wolfSPDM_BuildIV(iv, ctx->rspDataIv, (word64)rspSeqNum); + } - /* Build IV: BaseIV XOR sequence number (DSP0277) */ - wolfSPDM_BuildIV(iv, ctx->rspDataIv, (word64)rspSeqNum, 0); - - rc = wc_AesInit(&aes, NULL, INVALID_DEVID); - if (rc != 0) { - return WOLFSPDM_E_CRYPTO_FAIL; - } + /* ----- AES-GCM decrypt (shared for both transports) ----- */ + ret = WOLFSPDM_E_CRYPTO_FAIL; + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc == 0) { + aesInit = 1; rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); - if (rc != 0) { - wc_AesFree(&aes); - return WOLFSPDM_E_CRYPTO_FAIL; - } - + } + if (rc == 0) { rc = wc_AesGcmDecrypt(&aes, decrypted, ciphertext, cipherLen, - iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); - wc_AesFree(&aes); + 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); - return WOLFSPDM_E_DECRYPT_FAIL; - } - - /* Parse decrypted: AppDataLen (2) || MCTP (1) || SPDM msg */ - appDataLen = SPDM_Get16LE(decrypted); - - if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen)) { - return WOLFSPDM_E_BUFFER_SMALL; + ret = WOLFSPDM_E_DECRYPT_FAIL; } - - /* Skip MCTP header, copy SPDM message */ - if (*plainSz < (word32)(appDataLen - 1)) { - return WOLFSPDM_E_BUFFER_SMALL; - } - - 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); - - return WOLFSPDM_SUCCESS; -} - -#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; - } - - if (ctx->state != WOLFSPDM_STATE_CONNECTED && - ctx->state != WOLFSPDM_STATE_KEY_EX && - ctx->state != WOLFSPDM_STATE_FINISH) { - return WOLFSPDM_E_NOT_CONNECTED; + if (aesInit) { + wc_AesFree(&aes); } - 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; + /* ----- Parse decrypted payload ----- */ + if (rc == 0) { + appDataLen = SPDM_Get16LE(decrypted); +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + /* Nuvoton: 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 + { + /* 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 (ctx->state != WOLFSPDM_STATE_CONNECTED && - ctx->state != WOLFSPDM_STATE_KEY_EX && - ctx->state != WOLFSPDM_STATE_FINISH) { - return WOLFSPDM_E_NOT_CONNECTED; + if (ret == WOLFSPDM_SUCCESS) { + ctx->rspSeqNum++; + wolfSPDM_DebugPrint(ctx, "Decrypted %u bytes -> %u bytes\n", + encSz, *plainSz); } - 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); int rc; @@ -402,96 +332,12 @@ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, } rc = wolfSPDM_EncryptInternal(ctx, cmdPlain, cmdSz, encBuf, &encSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - return wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); -} - -/* --- Application Data Transfer --- */ - -#ifndef WOLFSPDM_LEAN -int wolfSPDM_SendData(WOLFSPDM_CTX* ctx, const byte* data, word32 dataSz) -{ - byte encBuf[WOLFSPDM_MAX_MSG_SIZE + 48]; - word32 encSz = sizeof(encBuf); - 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; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); } - - /* 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; - } - - { - byte rxBuf[16]; - word32 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; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); } - /* Decrypt the received data */ - return wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, data, dataSz); + return rc; } -#endif /* !WOLFSPDM_LEAN */ diff --git a/spdm/src/spdm_session.c b/spdm/src/spdm_session.c index 223526be..9ed1190e 100644 --- a/spdm/src/spdm_session.c +++ b/spdm/src/spdm_session.c @@ -25,7 +25,7 @@ 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 */ +/* 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) @@ -35,18 +35,20 @@ static int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, int rc; rc = buildFn(ctx, txBuf, &txSz); - if (rc != WOLFSPDM_SUCCESS) return rc; - - rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); - if (rc != WOLFSPDM_SUCCESS) return rc; - - rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) return rc; - - rc = wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); - if (rc != WOLFSPDM_SUCCESS) return rc; + 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); + } - return parseFn(ctx, rxBuf, rxSz); + return rc; } /* Adapter: BuildGetVersion doesn't take ctx */ @@ -66,151 +68,6 @@ int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx) 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 bytes */ - byte rxBuf[80]; /* ALGORITHMS: ~56 bytes with struct tables */ - int rc; - - 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). */ - { - int 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); - } - } - /* Non-fatal: challenge just won't work if this fails */ - } -#endif - - return WOLFSPDM_SUCCESS; -} - -int wolfSPDM_GetDigests(WOLFSPDM_CTX* ctx) -{ - byte txBuf[8]; - byte rxBuf[256]; - 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; - int rc; - - while (remainderLen > 0) { - txSz = sizeof(txBuf); - rc = wolfSPDM_BuildGetCertificate(ctx, txBuf, &txSz, slotId, offset, 1024); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - rxSz = sizeof(rxBuf); - 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; - } - - 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. - * Needed by both measurement signature verification and challenge auth. - * Non-fatal: caller can still proceed, but signature ops will fail. */ - if (!ctx->flags.hasResponderPubKey) { - int keyRc = wolfSPDM_ExtractResponderPubKey(ctx); - if (keyRc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, - "Warning: Could not extract responder public key (%d)\n", keyRc); - } - } - - return WOLFSPDM_SUCCESS; -} - int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) { byte txBuf[192]; /* KEY_EXCHANGE: ~158 bytes */ @@ -220,22 +77,21 @@ int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) int rc; rc = wolfSPDM_BuildKeyExchange(ctx, txBuf, &txSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); } - - wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); - - rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE: SendReceive failed: %d\n", rc); - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE: SendReceive failed: %d\n", rc); + } + } + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: received %u bytes\n", rxSz); + rc = wolfSPDM_ParseKeyExchangeRsp(ctx, rxBuf, rxSz); } - 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) @@ -251,352 +107,40 @@ int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) int rc; rc = wolfSPDM_BuildFinish(ctx, finishBuf, &finishSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } /* FINISH must be sent encrypted (HANDSHAKE_IN_THE_CLEAR not negotiated) */ - rc = wolfSPDM_EncryptInternal(ctx, finishBuf, finishSz, encBuf, &encSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "FINISH encrypt failed: %d\n", rc); - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_EncryptInternal(ctx, finishBuf, finishSz, encBuf, + &encSz); } - - rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "FINISH SendReceive failed: %d\n", rc); - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); } - /* Check if response is unencrypted SPDM message - * SPDM messages start with version byte (0x10-0x1F). - * Encrypted records start with session ID. */ - if (rxSz >= 2 && rxBuf[0] >= 0x10 && rxBuf[0] <= 0x1F) { - /* Unencrypted SPDM message - check for ERROR */ - if (rxBuf[1] == 0x7F) { /* SPDM_ERROR */ - wolfSPDM_DebugPrint(ctx, "FINISH: peer returned SPDM ERROR 0x%02x\n", - rxBuf[2]); - return WOLFSPDM_E_PEER_ERROR; + /* Check for unencrypted SPDM error response */ + if (rc == WOLFSPDM_SUCCESS && + rxSz >= 2 && rxBuf[0] >= 0x10 && rxBuf[0] <= 0x1F) { + 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]); - return WOLFSPDM_E_PEER_ERROR; + rc = WOLFSPDM_E_PEER_ERROR; } - rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "FINISH decrypt failed: %d\n", rc); - return rc; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); } - - rc = wolfSPDM_ParseFinishRsp(ctx, decBuf, decSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; + 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); - return rc; - } - return WOLFSPDM_SUCCESS; -} - -/* --- 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 rc; - - if (ctx == NULL) { - return WOLFSPDM_E_INVALID_ARG; - } - - /* Must be at least past algorithm negotiation */ - if (ctx->state < WOLFSPDM_STATE_ALGO) { - return WOLFSPDM_E_BAD_STATE; - } - - /* 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/receive: use secured exchange if session established, else cleartext */ - if (ctx->state == WOLFSPDM_STATE_CONNECTED) { - rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); - } - else { - rc = wolfSPDM_SendReceive(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 measurements */ - { - int errCode = 0; - if (wolfSPDM_CheckError(rxBuf, rxSz, &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) { - return WOLFSPDM_E_MEAS_SIG_FAIL; - } - - ctx->state = WOLFSPDM_STATE_MEASURED; - return WOLFSPDM_SUCCESS; /* Verified! */ - } -#else - (void)requestSignature; -#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ - - /* No signature requested or verification not compiled in */ - ctx->state = WOLFSPDM_STATE_MEASURED; - return WOLFSPDM_E_MEAS_NOT_VERIFIED; -} - -#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 46 bytes (1.3+) */ - byte rxBuf[512]; /* CHALLENGE_AUTH: variable, up to ~300+ bytes */ - 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; - } - - /* 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]; - word32 txSz, rxSz; - 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"); - - { - byte encBuf[64]; - byte rawRxBuf[64]; - word32 encSz = sizeof(encBuf); - word32 rawRxSz = sizeof(rawRxBuf); - - /* Encrypt with current req key */ - rc = wolfSPDM_EncryptInternal(ctx, txBuf, txSz, encBuf, &encSz); - if (rc != WOLFSPDM_SUCCESS) { - return rc; - } - - /* Send and receive raw (don't decrypt yet) */ - rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rawRxBuf, &rawRxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: SendReceive failed: %d\n", rc); - return rc; - } - - /* 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); - return rc; - } - ctx->reqSeqNum = 0; - ctx->rspSeqNum = 0; - - /* Decrypt ACK with new rsp key */ - rxSz = sizeof(rxBuf); - rc = wolfSPDM_DecryptInternal(ctx, rawRxBuf, rawRxSz, rxBuf, &rxSz); - if (rc != WOLFSPDM_SUCCESS) { - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE: ACK decrypt failed: %d\n", rc); - 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; + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveAppDataKeys(ctx); } - wolfSPDM_DebugPrint(ctx, "KEY_UPDATE completed, new keys active\n"); - return WOLFSPDM_SUCCESS; + /* Always zero sensitive stack buffers */ + wc_ForceZero(finishBuf, sizeof(finishBuf)); + wc_ForceZero(decBuf, sizeof(decBuf)); + return rc; } diff --git a/spdm/src/spdm_transcript.c b/spdm/src/spdm_transcript.c index 893e5d9c..02db21f7 100644 --- a/spdm/src/spdm_transcript.c +++ b/spdm/src/spdm_transcript.c @@ -21,7 +21,7 @@ #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,9 +36,6 @@ 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)); @@ -63,22 +60,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,18 +95,3 @@ int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash) return wolfSPDM_Sha384Hash(hash, ctx->transcript, ctx->transcriptLen, 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/spdm/test/unit_test.c b/spdm/test/unit_test.c index bdddbd68..ce06f065 100644 --- a/spdm/test/unit_test.c +++ b/spdm/test/unit_test.c @@ -2,7 +2,21 @@ * * Copyright (C) 2006-2025 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 @@ -35,10 +49,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 +74,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 +102,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 +114,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 +135,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 +179,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"); + 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"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -/* ========================================================================== */ -/* Crypto Tests */ -/* ========================================================================== */ +/* ----- Crypto Tests ----- */ static int test_random_generation(void) { @@ -206,7 +196,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 +219,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 +243,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 +263,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,75 +280,12 @@ 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"); - - TEST_PASS(); -} - -static int test_build_get_capabilities(void) -{ - byte buf[32]; - word32 bufSz = sizeof(buf); - TEST_CTX_SETUP_V12(); - - 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"); + ASSERT_EQ(wolfSPDM_BuildGetVersion(buf, &bufSz), WOLFSPDM_E_BUFFER_SMALL, + "Should fail on small buffer"); - TEST_CTX_FREE(); - TEST_PASS(); -} - -static int test_build_negotiate_algorithms(void) -{ - byte buf[64]; - word32 bufSz = sizeof(buf); - TEST_CTX_SETUP_V12(); - - 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_CTX_FREE(); - TEST_PASS(); -} - -static int test_build_get_digests(void) -{ - byte buf[16]; - word32 bufSz = sizeof(buf); - TEST_CTX_SETUP_V12(); - - 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"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -static int test_build_get_certificate(void) -{ - byte buf[16]; - word32 bufSz = sizeof(buf); - TEST_CTX_SETUP_V12(); - - 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"); - - TEST_CTX_FREE(); TEST_PASS(); } @@ -377,9 +304,7 @@ static int test_build_end_session(void) TEST_PASS(); } -/* ========================================================================== */ -/* Error Check Tests */ -/* ========================================================================== */ +/* ----- Error Check Tests ----- */ static int test_check_error(void) { @@ -413,535 +338,7 @@ static int test_error_strings(void) TEST_PASS(); } -/* ========================================================================== */ -/* Measurement Tests */ -/* ========================================================================== */ - -#ifndef NO_WOLFSPDM_MEAS - -static int test_build_get_measurements(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"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -static int test_measurement_accessors(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)); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -static int test_parse_measurements(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)); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -#ifndef NO_WOLFSPDM_MEAS_VERIFY - -static int test_measurement_sig_verification(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); - int rc; - 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); - TEST_ASSERT(rc == 0, "wc_ecc_init responderPubKey failed"); - - /* Export/import just the public key */ - { - byte pubDer[256]; - word32 pubDerSz = sizeof(pubDer); - word32 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, - 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) */ - { - static const char 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) - byte signMsg[200]; - word32 signMsgLen = 0; - int i; - - /* 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], 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) */ - { - byte sigRaw[WOLFSPDM_ECC_SIG_SIZE]; - 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_FREE(); - TEST_PASS(); -} - -#endif /* !NO_WOLFSPDM_MEAS_VERIFY */ -#endif /* !NO_WOLFSPDM_MEAS */ - -/* ========================================================================== */ -/* Certificate Chain Validation Tests */ -/* ========================================================================== */ - -static int test_set_trusted_cas(void) -{ - byte fakeCa[] = {0x30, 0x82, 0x01, 0x00, 0xAA, 0xBB, 0xCC, 0xDD}; - TEST_CTX_SETUP(); - - 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_CTX_FREE(); - TEST_PASS(); -} - -static int test_validate_cert_chain_no_cas(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"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -/* ========================================================================== */ -/* Challenge Tests */ -/* ========================================================================== */ - -#ifndef NO_WOLFSPDM_CHALLENGE - -static int test_build_challenge(void) -{ - byte buf[64]; - byte zeros[32]; - word32 bufSz; - TEST_CTX_SETUP_V12(); - - printf("test_build_challenge...\n"); - - 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"); - - /* Buffer too small */ - 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_CTX_FREE(); - TEST_PASS(); -} - -static int test_parse_challenge_auth(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; - - 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); - - ASSERT_SUCCESS(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset)); - ASSERT_EQ(sigOffset, 86, "Signature offset should be 86"); - - /* 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; - - /* CertChainHash mismatch */ - ctx->certChainHash[0] = 0x00; - ASSERT_EQ(wolfSPDM_ParseChallengeAuth(ctx, rsp, sizeof(rsp), &sigOffset), - WOLFSPDM_E_CHALLENGE, "Hash mismatch should fail"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -#endif /* !NO_WOLFSPDM_CHALLENGE */ - -/* ========================================================================== */ -/* Heartbeat Tests */ -/* ========================================================================== */ - -static int test_build_heartbeat(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"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -static int test_parse_heartbeat_ack(void) -{ - byte ack[] = {0x12, SPDM_HEARTBEAT_ACK, 0x00, 0x00}; - byte err[] = {0x12, SPDM_ERROR, 0x01, 0x00}; - TEST_CTX_SETUP(); - - 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_CTX_FREE(); - TEST_PASS(); -} - -static int test_heartbeat_state_check(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"); - - 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(); - - 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"); - - 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"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -static int test_parse_key_update_ack(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"); - - 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); - - /* 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(); -} - -static int test_key_update_state_check(void) -{ - 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"); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -/* ========================================================================== */ -/* Multi-Version Tests */ -/* ========================================================================== */ +/* ----- Multi-Version Tests ----- */ static int test_kdf_version_prefix(void) { @@ -1025,33 +422,6 @@ static int test_transcript_overflow(void) TEST_PASS(); } -#ifndef NO_WOLFSPDM_MEAS -static int test_parse_measurements_negative(void) -{ - byte truncated[] = {0x12, 0x60, 0x00, 0x00, 0x01}; - byte wrongCode[] = {0x12, 0xFF, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00}; - TEST_CTX_SETUP(); - - printf("test_parse_measurements_negative...\n"); - - /* Truncated buffer */ - ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, truncated, sizeof(truncated))); - - /* Wrong response code */ - ASSERT_FAIL(wolfSPDM_ParseMeasurements(ctx, wrongCode, sizeof(wrongCode))); - - /* 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)); - - TEST_CTX_FREE(); - TEST_PASS(); -} -#endif /* !NO_WOLFSPDM_MEAS */ - static int test_version_fallback(void) { /* Fake VERSION response with versions 1.0, 1.1, 1.2, 1.3 */ @@ -1084,36 +454,7 @@ static int test_version_fallback(void) TEST_PASS(); } -static int test_set_max_version(void) -{ - TEST_CTX_SETUP(); - - printf("test_set_max_version...\n"); - - /* 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"); - - /* Reset to default */ - ASSERT_SUCCESS(wolfSPDM_SetMaxVersion(ctx, 0)); - ASSERT_EQ(ctx->maxVersion, 0, "maxVersion should be 0 (default)"); - - /* 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)); - - TEST_CTX_FREE(); - TEST_PASS(); -} - -/* ========================================================================== */ -/* Session State Tests */ -/* ========================================================================== */ +/* ----- Session State Tests ----- */ static int test_session_state(void) { @@ -1135,14 +476,317 @@ static int test_session_state(void) TEST_PASS(); } -/* ========================================================================== */ -/* Main */ -/* ========================================================================== */ +/* ----- 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) +{ + 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(); +} + +/* Test Fix 4: Invalid curve point must be rejected by ComputeSharedSecret */ +static int test_invalid_curve_point(void) +{ + byte badX[WOLFSPDM_ECC_KEY_SIZE]; + byte badY[WOLFSPDM_ECC_KEY_SIZE]; + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_invalid_curve_point...\n"); + + 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 */ + { + byte zeros[WOLFSPDM_ECC_KEY_SIZE]; + 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(); +} + +#ifdef WOLFSPDM_NUVOTON +/* 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 txBuf[32]; + byte rxBuf[32]; + word32 rxSz = sizeof(rxBuf); + int rc; + TEST_CTX_SETUP(); + + printf("test_tcg_underflow...\n"); + + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NUVOTON); + 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"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* WOLFSPDM_NUVOTON */ + +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]; + byte plain[64]; + word32 plainSz = sizeof(plain); + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_decrypt_overflow...\n"); + + ctx->sessionId = 0x00010001; + ctx->rspSeqNum = 0; + memset(ctx->rspDataKey, 0x42, sizeof(ctx->rspDataKey)); + memset(ctx->rspDataIv, 0x42, sizeof(ctx->rspDataIv)); + + /* 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); + + 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_oob_read_error(void) +{ + byte shortErr[2] = {0x12, SPDM_ERROR}; + byte fullErr[4] = {0x12, SPDM_ERROR, 0x06, 0x00}; + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_oob_read_error...\n"); + + 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_constant_time_hmac(void) +{ + byte finishedKey[WOLFSPDM_HASH_SIZE]; + byte thHash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + byte fakeVerify[WOLFSPDM_HASH_SIZE]; + word32 i; + int diff; + + printf("test_constant_time_hmac...\n"); + + memset(finishedKey, 0xAB, sizeof(finishedKey)); + memset(thHash, 0xCD, sizeof(thHash)); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKey, thHash, verifyData)); + + /* 1-byte difference must be detected */ + memcpy(fakeVerify, verifyData, sizeof(fakeVerify)); + fakeVerify[WOLFSPDM_HASH_SIZE - 1] ^= 0x01; + + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ fakeVerify[i]; + ASSERT_NE(diff, 0, "Should detect 1-byte diff"); + + /* Identical must pass */ + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ verifyData[i]; + ASSERT_EQ(diff, 0, "Identical data should match"); + + TEST_PASS(); +} + +static int test_setdebug_truncation(void) +{ + TEST_CTX_SETUP(); + + printf("test_setdebug_truncation...\n"); + + wolfSPDM_SetDebug(ctx, 2); + ASSERT_EQ(ctx->flags.debug, 1, "debug=2 should be 1"); + + wolfSPDM_SetDebug(ctx, 0); + ASSERT_EQ(ctx->flags.debug, 0, "debug=0 should be 0"); + + wolfSPDM_SetDebug(ctx, 255); + ASSERT_EQ(ctx->flags.debug, 1, "debug=255 should be 1"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_key_zeroing(void) +{ + byte zeros[WOLFSPDM_HASH_SIZE]; + byte zeroKey[WOLFSPDM_AEAD_KEY_SIZE]; + byte zeroIv[WOLFSPDM_AEAD_IV_SIZE]; + TEST_CTX_SETUP_V12(); + + 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; + + ctx->state = WOLFSPDM_STATE_CONNECTED; + ctx->sessionId = 0x00010001; + ctx->ioCb = dummy_io_cb; + + wolfSPDM_Disconnect(ctx); + + 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(); +} + +/* ----- Main ----- */ int main(void) { printf("===========================================\n"); - printf("wolfSPDM Unit Tests\n"); + printf("wolfSPDM Unit Tests (Nuvoton subset)\n"); printf("===========================================\n\n"); /* Context tests */ @@ -1156,7 +800,6 @@ int main(void) /* Transcript tests */ test_transcript_add_reset(); test_transcript_hash(); - test_certchain_hash(); /* Crypto tests */ test_random_generation(); @@ -1168,60 +811,33 @@ int main(void) /* Message builder tests */ test_build_get_version(); - test_build_get_capabilities(); - test_build_negotiate_algorithms(); - test_build_get_digests(); - test_build_get_certificate(); test_build_end_session(); /* Error tests */ test_check_error(); test_error_strings(); - /* Measurement tests */ -#ifndef NO_WOLFSPDM_MEAS - test_build_get_measurements(); - 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(); -#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(); -#endif test_version_fallback(); - test_set_max_version(); /* Session state tests */ test_session_state(); + /* Security tests */ + test_mitm_signature_rejected(); + test_invalid_curve_point(); +#ifdef WOLFSPDM_NUVOTON + test_tcg_underflow(); +#endif + test_decrypt_overflow(); + test_oob_read_error(); + test_constant_time_hmac(); + test_setdebug_truncation(); + test_key_zeroing(); + printf("\n===========================================\n"); printf("Results: %d passed, %d failed\n", g_testsPassed, g_testsFailed); printf("===========================================\n"); diff --git a/spdm/wolfspdm/spdm.h b/spdm/wolfspdm/spdm.h index 19f5012e..85d9c135 100644 --- a/spdm/wolfspdm/spdm.h +++ b/spdm/wolfspdm/spdm.h @@ -22,8 +22,6 @@ #ifndef WOLFSPDM_SPDM_H #define WOLFSPDM_SPDM_H -/* Include build options (WOLFSPDM_DYNAMIC_MEMORY, WOLFSPDM_NUVOTON, etc.) - * Generated from config.h during build; installed alongside this header. */ #ifndef HAVE_CONFIG_H #include #endif @@ -31,113 +29,49 @@ #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 -/* --- Protocol Mode Selection --- - * - * wolfSPDM supports two protocol modes: - * - * WOLFSPDM_MODE_STANDARD (default): - * 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 - * - * WOLFSPDM_MODE_NUVOTON (requires --enable-nuvoton): - * Nuvoton TPM-specific protocol with TCG binding headers. - * Flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH - * Use with: Nuvoton NPCT75x TPMs (FW 7.2+) */ - +/* Protocol mode: Nuvoton TCG binding + vendor commands (requires --enable-nuvoton). + * For standard SPDM (emulator, measurements, challenge), see wolfSPDM standalone. */ typedef enum { - WOLFSPDM_MODE_STANDARD = 0, /* Standard SPDM 1.2 (default) */ - WOLFSPDM_MODE_NUVOTON = 1, /* Nuvoton TCG binding + vendor commands */ + WOLFSPDM_MODE_NUVOTON = 1, } WOLFSPDM_MODE; -/* --- 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 - * - Compatible with libspdm emulator for testing - * - No external dependencies beyond wolfCrypt +/* wolfSPDM: Lightweight SPDM requester using wolfCrypt. + * Algorithm Set B fixed: P-384/SHA-384/AES-256-GCM. * - * Typical Usage: + * Usage (static, 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); * - * 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 (requires --enable-dynamic-mem): + * ctx = wolfSPDM_New(); + * // ... same as above ... + * 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. */ + * WOLFSPDM_CTX is ~22KB. Use static global on small-stack systems. + * SecuredExchange call chain uses ~20KB stack for message buffers. */ -/* 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). - * Actual struct size: ~31.3 KB (with measurements) / ~29.9 KB (NO_WOLFSPDM_MEAS). - * Rounded up to 32 KB for platform alignment. - * wolfSPDM_InitStatic() verifies at runtime that the provided buffer - * is large enough; returns WOLFSPDM_E_BUFFER_SMALL if not. */ -#define WOLFSPDM_CTX_STATIC_SIZE 32768 /* 32KB - fits CTX with cert validation + challenge + key update fields */ +/* Compile-time buffer size for static allocation (32KB, runtime-verified) */ +#define WOLFSPDM_CTX_STATIC_SIZE 32768 -/* Forward declaration */ struct WOLFSPDM_CTX; typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; -/* Include Nuvoton support if enabled (must be after WOLFSPDM_CTX forward declaration) */ #ifdef WOLFSPDM_NUVOTON #include #endif -/* --- 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 */ +/* 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, @@ -145,462 +79,52 @@ 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 protocol mode (standard SPDM or Nuvoton-specific). - * Must be called before wolfSPDM_Connect(). - * - * @param ctx The wolfSPDM context. - * @param mode WOLFSPDM_MODE_STANDARD or WOLFSPDM_MODE_NUVOTON. - * @return WOLFSPDM_SUCCESS or negative error code. - * Returns WOLFSPDM_E_INVALID_ARG if NUVOTON mode requested - * but wolfSPDM was not built with --enable-nuvoton. - */ +/* 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); - -/** - * Get the current protocol mode. - * - * @param ctx The wolfSPDM context. - * @return Current mode (WOLFSPDM_MODE_STANDARD or WOLFSPDM_MODE_NUVOTON). - */ WOLFSPDM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); - -/** - * 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); - -/** - * Set the responder's public key for certificate-less operation. - * Used when the responder doesn't send a certificate chain (e.g., Nuvoton TPM). - * - * @param ctx The wolfSPDM context. - * @param pubKey Raw public key bytes (96 bytes for P-384: X||Y). - * @param pubKeySz Size of public key (must be 96 for P-384). - * @return WOLFSPDM_SUCCESS or negative error code. - */ +/* Set responder pub key for cert-less operation (96 bytes P-384 X||Y) */ WOLFSPDM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, const byte* pubKey, word32 pubKeySz); - -/** - * Set the requester's key pair for mutual authentication. - * Optional - only needed if responder requires mutual auth. - * - * @param ctx The wolfSPDM context. - * @param privKey Raw private key bytes (48 bytes for P-384). - * @param privKeySz Size of private key. - * @param pubKey Raw public key bytes (96 bytes for P-384: X||Y). - * @param pubKeySz Size of public key. - * @return WOLFSPDM_SUCCESS or negative error code. - */ +/* 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 --- */ - -/** - * 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. - */ +/* 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_E_MEAS_NOT_VERIFIED. Measurements are informational - * only and should not be used for security-critical decisions. - * - * If compiled with NO_WOLFSPDM_MEAS_VERIFY, signature verification is - * disabled and returns WOLFSPDM_E_MEAS_NOT_VERIFIED regardless of - * requestSignature (signature bytes are still captured in the context). - * - * 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 (verified), WOLFSPDM_E_MEAS_NOT_VERIFIED (unsigned), - * WOLFSPDM_E_MEAS_SIG_FAIL (sig invalid), or negative error code. - */ -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. - * - * @param ctx The wolfSPDM context. - * @return Session ID (combined reqSessionId | rspSessionId << 16), or 0 if not connected. - */ +/* 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_NUVOTON -/** - * Get the connection handle (Nuvoton TCG binding). - * - * @param ctx The wolfSPDM context. - * @return Connection handle value. - */ WOLFSPDM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); - -/** - * Get the FIPS indicator (Nuvoton TCG binding). - * - * @param ctx The wolfSPDM context. - * @return FIPS indicator value. - */ WOLFSPDM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); #endif -/* --- Certificate Chain Validation --- */ - -/** - * Load trusted root CA certificates for certificate chain validation. - * When set, wolfSPDM_Connect() will validate the responder's certificate - * chain against these CAs. Without this, only the public key is extracted. - * - * @param ctx The wolfSPDM context. - * @param derCerts DER-encoded CA certificate(s) (concatenated if multiple). - * @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 --- */ - -/** - * 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/spdm/wolfspdm/spdm_error.h b/spdm/wolfspdm/spdm_error.h index ec6317eb..71ae5354 100644 --- a/spdm/wolfspdm/spdm_error.h +++ b/spdm/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,6 +22,8 @@ #ifndef WOLFSPDM_ERROR_H #define WOLFSPDM_ERROR_H +#include + #ifdef __cplusplus extern "C" { #endif @@ -44,17 +46,8 @@ 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_SESSION_INVALID = -16, /* Session ID invalid or mismatch */ + WOLFSPDM_E_KEY_EXCHANGE = -17, /* Key exchange failed */ }; /* Get human-readable error string */ diff --git a/spdm/wolfspdm/spdm_nuvoton.h b/spdm/wolfspdm/spdm_nuvoton.h index d0efb87b..b2c37af7 100644 --- a/spdm/wolfspdm/spdm_nuvoton.h +++ b/spdm/wolfspdm/spdm_nuvoton.h @@ -51,7 +51,7 @@ extern "C" { #endif -/* --- TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) --- */ +/* ----- TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) ----- */ /* Message Tags */ #define WOLFSPDM_TCG_TAG_CLEAR 0x8101 /* Clear (unencrypted) message */ @@ -59,13 +59,12 @@ extern "C" { /* Header Sizes */ #define WOLFSPDM_TCG_HEADER_SIZE 16 /* TCG binding header size */ -#define WOLFSPDM_TCG_SECURED_HDR_SIZE 14 /* Session record header (4+8+2) */ /* FIPS Service Indicator */ #define WOLFSPDM_FIPS_NON_FIPS 0x00 #define WOLFSPDM_FIPS_APPROVED 0x01 -/* --- Nuvoton Vendor-Defined Command Codes --- */ +/* ----- Nuvoton Vendor-Defined Command Codes ----- */ /* 8-byte ASCII vendor codes for SPDM VENDOR_DEFINED messages */ #define WOLFSPDM_VDCODE_LEN 8 @@ -80,7 +79,7 @@ extern "C" { #define WOLFSPDM_SPDMONLY_LOCK 0x01 #define WOLFSPDM_SPDMONLY_UNLOCK 0x00 -/* --- TCG Binding Header Structures --- */ +/* ----- TCG Binding Header Structures ----- */ /* Clear message header (tag 0x8101) * Layout: tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + @@ -93,31 +92,17 @@ typedef struct WOLFSPDM_TCG_CLEAR_HDR { word32 reserved; /* Must be 0 */ } WOLFSPDM_TCG_CLEAR_HDR; -/* Secured message header (tag 0x8201) - * Layout: tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + - * fipsIndicator(2/BE) + reserved(4) = 16 bytes - * Followed by SPDM secured record (per DSP0277, all LE): - * sessionId(4/LE) + seqNum(8/LE) + length(2/LE) + encData + MAC(16) */ -typedef struct WOLFSPDM_TCG_SECURED_HDR { - word16 tag; /* WOLFSPDM_TCG_TAG_SECURED (0x8201) */ - word32 size; /* Total message size including header */ - word32 connectionHandle; /* Connection handle */ - word16 fipsIndicator; /* FIPS service indicator */ - word32 reserved; /* Must be 0 */ -} WOLFSPDM_TCG_SECURED_HDR; - -/* --- Nuvoton SPDM Status --- */ +/* ----- Nuvoton SPDM Status ----- */ typedef struct WOLFSPDM_NUVOTON_STATUS { int spdmEnabled; /* SPDM is enabled on the TPM */ int sessionActive; /* An SPDM session is currently active */ int spdmOnlyLocked; /* SPDM-only mode is locked */ - word32 fwVersion; /* TPM firmware version */ byte specVersionMajor; /* SPDM spec version major (0 for 1.x) */ byte specVersionMinor; /* SPDM spec version minor (1=1.1, 3=1.3) */ } WOLFSPDM_NUVOTON_STATUS; -/* --- TCG Binding Message Framing Functions --- */ +/* ----- TCG Binding Message Framing Functions ----- */ /** * Build a TCG SPDM clear message (tag 0x8101). @@ -151,52 +136,12 @@ WOLFSPDM_API int wolfSPDM_ParseTcgClearMessage( byte* spdmPayload, word32* spdmPayloadSz, WOLFSPDM_TCG_CLEAR_HDR* hdr); -/** - * Build a TCG SPDM secured message (tag 0x8201). - * Wraps encrypted payload with session ID, sequence number, and MAC. - * - * @param ctx wolfSPDM context - * @param encPayload Encrypted payload (ciphertext) - * @param encPayloadSz Size of encrypted payload - * @param mac Authentication tag (AES-GCM tag) - * @param macSz Size of MAC (must be 16) - * @param outBuf Output buffer for framed message - * @param outBufSz Size of output buffer - * @return Total message size on success, negative on error - */ -WOLFSPDM_API int wolfSPDM_BuildTcgSecuredMessage( - WOLFSPDM_CTX* ctx, - const byte* encPayload, word32 encPayloadSz, - const byte* mac, word32 macSz, - byte* outBuf, word32 outBufSz); - -/** - * Parse a TCG SPDM secured message (tag 0x8201). - * Extracts session ID, sequence number, encrypted payload, and MAC. - * - * @param inBuf Input buffer containing framed message - * @param inBufSz Size of input buffer - * @param sessionId Receives session ID - * @param seqNum Receives sequence number - * @param encPayload Output buffer for encrypted payload - * @param encPayloadSz [in] Size of output buffer, [out] Actual payload size - * @param mac Output buffer for MAC - * @param macSz [in] Size of MAC buffer, [out] Actual MAC size - * @param hdr Optional: receives parsed header fields - * @return Payload size on success, negative on error - */ -WOLFSPDM_API int wolfSPDM_ParseTcgSecuredMessage( - const byte* inBuf, word32 inBufSz, - word32* sessionId, word64* seqNum, - byte* encPayload, word32* encPayloadSz, - byte* mac, word32* macSz, - WOLFSPDM_TCG_SECURED_HDR* hdr); - -/* --- Vendor-Defined Message Helpers --- */ +/* ----- Vendor-Defined Message Helpers ----- */ /** * Build an SPDM VENDOR_DEFINED_REQUEST message. * + * @param spdmVersion Negotiated SPDM version byte (e.g., 0x13) * @param vdCode 8-byte ASCII vendor code (e.g., "GET_PUBK") * @param payload Vendor-specific payload (may be NULL) * @param payloadSz Size of payload @@ -205,6 +150,7 @@ WOLFSPDM_API int wolfSPDM_ParseTcgSecuredMessage( * @return Message size on success, negative on error */ WOLFSPDM_API int wolfSPDM_BuildVendorDefined( + byte spdmVersion, const char* vdCode, const byte* payload, word32 payloadSz, byte* outBuf, word32 outBufSz); @@ -224,7 +170,7 @@ WOLFSPDM_API int wolfSPDM_ParseVendorDefined( char* vdCode, byte* payload, word32* payloadSz); -/* --- Nuvoton-Specific SPDM Functions --- */ +/* ----- Nuvoton-Specific SPDM Functions ----- */ /** * Get the TPM's SPDM-Identity public key (GET_PUBK vendor command). @@ -300,7 +246,7 @@ WOLFSPDM_API int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, */ WOLFSPDM_API int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx); -/* --- Nuvoton Context Fields --- */ +/* ----- Nuvoton Context Fields ----- */ /* These fields are added to WOLFSPDM_CTX when WOLFSPDM_NUVOTON is defined */ diff --git a/spdm/wolfspdm/spdm_types.h b/spdm/wolfspdm/spdm_types.h index 458448e3..c8058d40 100644 --- a/spdm/wolfspdm/spdm_types.h +++ b/spdm/wolfspdm/spdm_types.h @@ -47,52 +47,24 @@ extern "C" { #include #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_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 #define SPDM_END_SESSION 0xEA #define SPDM_VENDOR_DEFINED_REQUEST 0xFE -#define SPDM_VENDOR_DEFINED 0xFF -/* 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 -#define SPDM_END_SESSION_ACK 0x6A -#define SPDM_VENDOR_DEFINED_RSP 0x7E #define SPDM_ERROR 0x7F /* SPDM Error Codes (in Param1 of ERROR response) */ @@ -115,25 +87,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 */ - -/* DHE (Diffie-Hellman Ephemeral) Algorithms */ -#define SPDM_DHE_ALGO_SECP384R1 0x0010 /* secp384r1 */ - -/* 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 */ - -/* 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 */ @@ -141,93 +96,19 @@ 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 */ - -/* --- Capability Flags (per DSP0274) --- */ - -/* 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 */ - -/* Default requester capabilities for Algorithm Set B session */ -#define WOLFSPDM_DEFAULT_REQ_CAPS (SPDM_CAP_CERT_CAP | SPDM_CAP_CHAL_CAP | \ - SPDM_CAP_ENCRYPT_CAP | SPDM_CAP_MAC_CAP | \ - SPDM_CAP_KEY_EX_CAP | SPDM_CAP_HBEAT_CAP | \ - SPDM_CAP_KEY_UPD_CAP) - -/* --- Buffer/Message Size Limits --- */ +#define WOLFSPDM_AEAD_OVERHEAD 48 /* Max AEAD record overhead (hdr+pad+tag) */ + +/* ----- Buffer/Message Size Limits ----- */ #define WOLFSPDM_MAX_MSG_SIZE 4096 /* Maximum SPDM message size */ -#define WOLFSPDM_MAX_CERT_CHAIN 4096 /* Maximum certificate chain size */ #define WOLFSPDM_MAX_TRANSCRIPT 4096 /* Maximum transcript buffer */ #define WOLFSPDM_RANDOM_SIZE 32 /* Random data in KEY_EXCHANGE */ -/* --- MCTP Transport Constants (for TCP/socket transport) --- */ +/* ----- MCTP Transport Constants ----- */ #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 -#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 - -/* 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 */ -#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) --- */ +/* ----- Key Derivation Labels (SPDM 1.2 per DSP0277) ----- */ #define SPDM_BIN_CONCAT_PREFIX_12 "spdm1.2 " #define SPDM_BIN_CONCAT_PREFIX_13 "spdm1.3 " @@ -241,13 +122,6 @@ extern "C" { #define SPDM_LABEL_FINISHED "finished" #define SPDM_LABEL_KEY "key" #define SPDM_LABEL_IV "iv" -#define SPDM_LABEL_UPDATE "traffic upd" - -/* 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 - #ifdef __cplusplus } #endif diff --git a/src/tpm2.c b/src/tpm2.c index cc4645b6..347cbf09 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -410,6 +410,38 @@ static int TPM2_ResponseProcess(TPM2_CTX* ctx, TPM2_Packet* packet, return rc; } +#ifdef WOLFTPM_SPDM +/* SPDM intercept: if SPDM session is active, send TPM command through + * the encrypted SPDM channel instead of raw SPI/I2C. + * Returns TPM_RC_SUCCESS on success, negative if SPDM not active + * (caller should use normal transport), or positive error code. */ +static TPM_RC TPM2_SPDM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) +{ + WOLFTPM2_SPDM_CTX* spdmCtx; + byte tpmResp[WOLFSPDM_MAX_MSG_SIZE]; + word32 tpmRespSz = sizeof(tpmResp); + TPM_RC rc; + + if (ctx->spdmCtx == NULL) + return -1; /* SPDM not configured */ + spdmCtx = (WOLFTPM2_SPDM_CTX*)ctx->spdmCtx; + if (spdmCtx->spdmCtx == NULL || !wolfSPDM_IsConnected(spdmCtx->spdmCtx)) + return -1; /* SPDM not connected */ + + rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, + packet->buf, packet->pos, tpmResp, &tpmRespSz); + if (rc != 0) + return rc; + + if (tpmRespSz > MAX_RESPONSE_SIZE) + return TPM_RC_SIZE; + XMEMCPY(packet->buf, tpmResp, tpmRespSz); + packet->pos = 0; + packet->size = tpmRespSz; + return TPM_RC_SUCCESS; +} +#endif /* WOLFTPM_SPDM */ + static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet, CmdInfo_t* info) { @@ -454,32 +486,9 @@ static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet, /* submit command and wait for response */ #ifdef WOLFTPM_SPDM - /* If SPDM session is active, wrap command through SPDM transport */ - if (ctx->spdmCtx != NULL) { - WOLFTPM2_SPDM_CTX* spdmCtx = (WOLFTPM2_SPDM_CTX*)ctx->spdmCtx; - if (spdmCtx->spdmCtx != NULL && - wolfSPDM_IsConnected(spdmCtx->spdmCtx)) { - byte tpmResp[WOLFSPDM_MAX_MSG_SIZE]; - word32 tpmRespSz = sizeof(tpmResp); - - rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, - packet->buf, packet->pos, tpmResp, &tpmRespSz); - if (rc != 0) - return rc; - - if (tpmRespSz > MAX_RESPONSE_SIZE) - return TPM_RC_SIZE; - XMEMCPY(packet->buf, tpmResp, tpmRespSz); - packet->pos = 0; - packet->size = tpmRespSz; - rc = TPM_RC_SUCCESS; - } - else { - rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); - } - } - else -#endif /* WOLFTPM_SPDM */ + rc = TPM2_SPDM_SendCommand(ctx, packet); + if (rc < 0) /* SPDM not active, use normal transport */ +#endif { rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); } @@ -513,35 +522,11 @@ static TPM_RC TPM2_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) return BAD_FUNC_ARG; #ifdef WOLFTPM_SPDM - /* If SPDM session is active, wrap command through SPDM transport */ - if (ctx->spdmCtx != NULL) { - WOLFTPM2_SPDM_CTX* spdmCtx = (WOLFTPM2_SPDM_CTX*)ctx->spdmCtx; - if (spdmCtx->spdmCtx != NULL && - wolfSPDM_IsConnected(spdmCtx->spdmCtx)) { - byte tpmResp[WOLFSPDM_MAX_MSG_SIZE]; - word32 tpmRespSz = sizeof(tpmResp); - - /* Use wolfSPDM to encrypt, send, receive, and decrypt */ - rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, - packet->buf, packet->pos, tpmResp, &tpmRespSz); - if (rc != 0) { - return rc; - } - - /* Copy TPM response back into packet buffer. - * Note: packet->buf is a pointer so sizeof gives pointer size, - * use MAX_RESPONSE_SIZE for the actual buffer capacity. */ - if (tpmRespSz > MAX_RESPONSE_SIZE) { - return TPM_RC_SIZE; - } - XMEMCPY(packet->buf, tpmResp, tpmRespSz); - packet->pos = 0; - packet->size = tpmRespSz; - - return TPM2_Packet_Parse(TPM_RC_SUCCESS, packet); - } - } -#endif /* WOLFTPM_SPDM */ + rc = TPM2_SPDM_SendCommand(ctx, packet); + if (rc == TPM_RC_SUCCESS) + return TPM2_Packet_Parse(rc, packet); + /* rc < 0 means SPDM not active, fall through to normal transport */ +#endif /* submit command and wait for response */ rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); @@ -685,61 +670,6 @@ TPM_RC TPM2_SetHalIoCb(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx) return rc; } -#ifdef WOLFTPM_SPDM -TPM_RC TPM2_SendRawBytes(TPM2_CTX* ctx, - const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz) -{ - TPM_RC rc; - TPM2_Packet packet; - word32 rspSz; - UINT32 tmpSz; - - if (ctx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) { - return BAD_FUNC_ARG; - } - if (txSz > sizeof(ctx->cmdBuf)) { - return TPM_RC_SIZE; - } - - /* Copy transmit data into the context command buffer */ - XMEMCPY(ctx->cmdBuf, txBuf, txSz); - - /* Set up the packet structure pointing to cmdBuf. - * pos = number of bytes to send, size = buffer capacity. */ - packet.buf = ctx->cmdBuf; - packet.pos = (int)txSz; - packet.size = (int)sizeof(ctx->cmdBuf); - - /* Send through the transport layer (TIS, Linux dev, SWTPM, etc.). - * TIS will write txSz bytes, then read the response into cmdBuf. - * The response size is parsed from the header (offset 2, 4 bytes BE) - * inside TIS and only that many bytes are read from the FIFO. */ - rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, &packet); - if (rc != TPM_RC_SUCCESS) { - return rc; - } - - /* After TIS returns, the response is in cmdBuf. The TIS layer read - * exactly the number of bytes indicated by the header size field. - * Extract that size from the response to know how many bytes to copy. - * Both TPM2 and TCG SPDM headers have: tag(2) + size(4) at offset 0. */ - if (packet.size < 6) { - return TPM_RC_FAILURE; - } - XMEMCPY(&tmpSz, &ctx->cmdBuf[2], sizeof(UINT32)); - rspSz = TPM2_Packet_SwapU32(tmpSz); - - if (rspSz > (word32)packet.size || rspSz > *rxSz) { - return TPM_RC_SIZE; - } - - XMEMCPY(rxBuf, ctx->cmdBuf, rspSz); - *rxSz = rspSz; - - return TPM_RC_SUCCESS; -} -#endif /* WOLFTPM_SPDM */ - /* If timeoutTries <= 0 then it will not try and startup chip and will * use existing default locality */ TPM_RC TPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, diff --git a/src/tpm2_spdm.c b/src/tpm2_spdm.c index a58b99a6..e0d5a919 100644 --- a/src/tpm2_spdm.c +++ b/src/tpm2_spdm.c @@ -219,8 +219,13 @@ int wolfTPM2_SPDM_SecuredExchange( int rc; /* Wrap TPM command in SPDM VENDOR_DEFINED_REQUEST("TPM2_CMD") */ - vdMsgSz = wolfSPDM_BuildVendorDefined(WOLFSPDM_VDCODE_TPM2_CMD, - cmdPlain, cmdSz, vdMsg, sizeof(vdMsg)); + { + byte ver = wolfSPDM_GetNegotiatedVersion(ctx->spdmCtx); + if (ver == 0) ver = SPDM_VERSION_13; + vdMsgSz = wolfSPDM_BuildVendorDefined(ver, + WOLFSPDM_VDCODE_TPM2_CMD, + cmdPlain, cmdSz, vdMsg, sizeof(vdMsg)); + } if (vdMsgSz < 0) { return vdMsgSz; } diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index d5d12e11..dc06ce0f 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -1248,6 +1248,7 @@ int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, rc = wc_ecc_export_private_only(&hostKey, privKey, &privKeySz); if (rc != 0) { + wc_ForceZero(privKey, sizeof(privKey)); wc_ecc_free(&hostKey); wc_FreeRng(&rng); return rc; @@ -1264,6 +1265,7 @@ int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, XMEMCPY(rawPubKey + 48, pubKeyY, 48); rc = wolfSPDM_SetRequesterKeyPair(dev->spdmCtx->spdmCtx, privKey, privKeySz, rawPubKey, 96); + wc_ForceZero(privKey, sizeof(privKey)); if (rc != 0) return rc; /* Build TPMT_PUBLIC for GIVE_PUB step */ diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 331f0858..aba4004c 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -3413,27 +3413,6 @@ WOLFTPM_API TPM_RC TPM2_ChipStartup(TPM2_CTX* ctx, int timeoutTries); */ WOLFTPM_API TPM_RC TPM2_SetHalIoCb(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx); -#ifdef WOLFTPM_SPDM -/*! - \ingroup TPM2_Proprietary - \brief Send raw bytes through the TIS/HAL transport and receive the response. - Used by the SPDM layer to send TCG-framed SPDM messages over the same - SPI FIFO as regular TPM commands. - - \return TPM_RC_SUCCESS: successful - \return BAD_FUNC_ARG: check the provided arguments - \return TPM_RC_FAILURE: communication failure - - \param ctx pointer to a TPM2 context - \param txBuf pointer to the transmit buffer (TCG SPDM framed message) - \param txSz size of the transmit buffer in bytes - \param rxBuf pointer to the receive buffer for the response - \param rxSz pointer to size; on input max buffer size, on output actual response size -*/ -WOLFTPM_API TPM_RC TPM2_SendRawBytes(TPM2_CTX* ctx, - const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); -#endif /* WOLFTPM_SPDM */ - /*! \ingroup TPM2_Proprietary \brief Sets the structure holding the TPM Authorizations. diff --git a/wolftpm/tpm2_spdm.h b/wolftpm/tpm2_spdm.h index 704927d0..567857f6 100644 --- a/wolftpm/tpm2_spdm.h +++ b/wolftpm/tpm2_spdm.h @@ -84,8 +84,8 @@ typedef struct WOLFTPM2_SPDM_CTX { int spdmOnlyLocked; #ifndef WOLFSPDM_DYNAMIC_MEMORY - /* Inline buffer for static wolfSPDM context (zero-malloc mode) */ - byte spdmBuf[WOLFSPDM_CTX_STATIC_SIZE]; + /* Static wolfSPDM context buffer, aligned for WOLFSPDM_CTX cast */ + XGEN_ALIGN byte spdmBuf[WOLFSPDM_CTX_STATIC_SIZE]; #endif } WOLFTPM2_SPDM_CTX;