From e56719c991c568cd093c069f6075f99acace904a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 26 Feb 2026 18:25:07 +0000 Subject: [PATCH] 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)