From 49c767432fbb2c10a4b58bdf8ca971659a70a660 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 24 Mar 2026 18:15:38 +0000 Subject: [PATCH 1/5] Add Nuvoton NPCT75x and NSING NS350 SPDM support for wolfTPM --- .github/workflows/make-test-swtpm.yml | 30 + .gitignore | 11 +- Makefile.am | 1 + configure.ac | 72 +- examples/include.am | 1 + examples/spdm/README.md | 88 +++ examples/spdm/include.am | 18 + examples/spdm/spdm_demo.c | 644 +++++++++++++++++ examples/spdm/spdm_test.sh | 214 ++++++ hal/tpm_io_linux.c | 3 + src/include.am | 4 + src/spdm/README.md | 474 ++++++++++++ src/spdm/include.am | 37 + src/spdm/spdm_context.c | 549 ++++++++++++++ src/spdm/spdm_crypto.c | 348 +++++++++ src/spdm/spdm_internal.h | 361 ++++++++++ src/spdm/spdm_kdf.c | 264 +++++++ src/spdm/spdm_msg.c | 541 ++++++++++++++ src/spdm/spdm_nations.c | 174 +++++ src/spdm/spdm_nuvoton.c | 112 +++ src/spdm/spdm_psk.c | 437 +++++++++++ src/spdm/spdm_secured.c | 354 +++++++++ src/spdm/spdm_session.c | 148 ++++ src/spdm/spdm_tcg.c | 572 +++++++++++++++ src/spdm/spdm_transcript.c | 97 +++ src/spdm/unit_test.c | 998 ++++++++++++++++++++++++++ src/tpm2.c | 146 +++- src/tpm2_packet.c | 1 + src/tpm2_spdm.c | 386 ++++++++++ src/tpm2_swtpm.c | 5 +- src/tpm2_wrap.c | 693 +++++++++++++++++- tests/unit_tests.c | 104 +++ wolftpm/include.am | 10 +- wolftpm/spdm/spdm.h | 147 ++++ wolftpm/spdm/spdm_error.h | 60 ++ wolftpm/spdm/spdm_nations.h | 99 +++ wolftpm/spdm/spdm_nuvoton.h | 76 ++ wolftpm/spdm/spdm_psk.h | 91 +++ wolftpm/spdm/spdm_tcg.h | 164 +++++ wolftpm/spdm/spdm_types.h | 170 +++++ wolftpm/tpm2.h | 135 +++- wolftpm/tpm2_packet.h | 1 + wolftpm/tpm2_spdm.h | 204 ++++++ wolftpm/tpm2_types.h | 3 + wolftpm/tpm2_wrap.h | 266 +++++++ zephyr/CMakeLists.txt | 4 + 46 files changed, 9295 insertions(+), 22 deletions(-) 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 src/spdm/README.md create mode 100644 src/spdm/include.am create mode 100644 src/spdm/spdm_context.c create mode 100644 src/spdm/spdm_crypto.c create mode 100644 src/spdm/spdm_internal.h create mode 100644 src/spdm/spdm_kdf.c create mode 100644 src/spdm/spdm_msg.c create mode 100644 src/spdm/spdm_nations.c create mode 100644 src/spdm/spdm_nuvoton.c create mode 100644 src/spdm/spdm_psk.c create mode 100644 src/spdm/spdm_secured.c create mode 100644 src/spdm/spdm_session.c create mode 100644 src/spdm/spdm_tcg.c create mode 100644 src/spdm/spdm_transcript.c create mode 100644 src/spdm/unit_test.c create mode 100644 src/tpm2_spdm.c create mode 100644 wolftpm/spdm/spdm.h create mode 100644 wolftpm/spdm/spdm_error.h create mode 100644 wolftpm/spdm/spdm_nations.h create mode 100644 wolftpm/spdm/spdm_nuvoton.h create mode 100644 wolftpm/spdm/spdm_psk.h create mode 100644 wolftpm/spdm/spdm_tcg.h create mode 100644 wolftpm/spdm/spdm_types.h 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..3568172e 100644 --- a/.github/workflows/make-test-swtpm.yml +++ b/.github/workflows/make-test-swtpm.yml @@ -75,6 +75,36 @@ jobs: # STMicro ST33KTPM2 - name: st33ktpm2 firmware wolftpm_config: --enable-st33 --enable-firmware + # 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-nuvoton --enable-spdm-dynamic-mem + needs_swtpm: false + # SPDM debug + - name: spdm-debug + wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp + wolftpm_config: --enable-spdm --enable-nuvoton --enable-debug + needs_swtpm: false + # SPDM + Nations (compile-only, no hardware in CI) + - name: spdm-nations + wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp + wolftpm_config: --enable-spdm --enable-nations + needs_swtpm: false + # SPDM + Nations debug + - name: spdm-nations-debug + wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp + wolftpm_config: --enable-spdm --enable-nations --enable-debug + needs_swtpm: false + # SPDM + Nations dynamic memory + - name: spdm-nations-dynamic-mem + wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp + wolftpm_config: --enable-spdm --enable-nations --enable-spdm-dynamic-mem + needs_swtpm: false # Microchip - name: microchip wolftpm_config: --enable-microchip diff --git a/.gitignore b/.gitignore index 2dd27e1f..222f771e 100644 --- a/.gitignore +++ b/.gitignore @@ -92,6 +92,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 @@ -181,10 +182,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..fbb77791 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 src/spdm/include.am include cmake/include.am include zephyr/include.am diff --git a/configure.ac b/configure.ac index 00533a2a..60346e35 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 @@ -332,6 +333,17 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_NUVOTON" fi +# Nations Technology NS350 +AC_ARG_ENABLE([nations], + [AS_HELP_STRING([--enable-nations],[Enable Nations Technology NS350 TPM Support (default: disabled)])], + [ ENABLED_NATIONS=$enableval ], + [ ENABLED_NATIONS=no ] + ) +if test "x$ENABLED_NATIONS" = "xyes" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_NATIONS" +fi + # Infineon SLB9670/SLB9672/SLB9673 AC_ARG_ENABLE([infineon], [AS_HELP_STRING([--enable-infineon],[Enable Infineon SLB9670/SLB9672 TPM Support (default: disabled)])], @@ -407,7 +419,8 @@ if test "x$ENABLED_AUTODETECT" = "xtest" then # If a module hasn't been selected then enable auto-detection if test "x$ENABLED_INFINEON" = "xno" && test "x$ENABLED_MCHP" = "xno" && test "x$ENABLED_MICROCHIP" = "xno" && \ - test "x$ENABLED_ST" = "xno" && test "x$ENABLED_ST33" = "xno" && test "x$ENABLED_NUVOTON" = "xno" + test "x$ENABLED_ST" = "xno" && test "x$ENABLED_ST33" = "xno" && test "x$ENABLED_NUVOTON" = "xno" && \ + test "x$ENABLED_NATIONS" = "xno" then ENABLED_AUTODETECT=yes fi @@ -462,6 +475,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]) + + # Nuvoton SPDM support (required for SPDM in wolfTPM) + if test "x$ENABLED_NUVOTON" = "xyes" + then + AC_DEFINE([WOLFSPDM_NUVOTON], [1], [Enable SPDM Nuvoton TPM support]) + AC_MSG_NOTICE([Nuvoton SPDM vendor commands enabled]) + fi + + # Nations Technology SPDM support + if test "x$ENABLED_NATIONS" = "xyes" + then + AC_DEFINE([WOLFSPDM_NATIONS], [1], [Enable SPDM Nations Technology support]) + AC_MSG_NOTICE([Nations Technology 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 @@ -489,10 +548,12 @@ AM_CONDITIONAL([BUILD_DEVTPM], [test "x$ENABLED_DEVTPM" = "xyes"]) AM_CONDITIONAL([BUILD_SWTPM], [test "x$ENABLED_SWTPM" = "xyes"]) AM_CONDITIONAL([BUILD_WINAPI], [test "x$ENABLED_WINAPI" = "xyes"]) AM_CONDITIONAL([BUILD_NUVOTON], [test "x$ENABLED_NUVOTON" = "xyes"]) +AM_CONDITIONAL([BUILD_NATIONS], [test "x$ENABLED_NATIONS" = "xyes"]) 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 +639,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 @@ -619,6 +684,11 @@ echo " * Infineon SLB967X $ENABLED_INFINEON" echo " * STM ST33: $ENABLED_ST" echo " * Microchip ATTPM20: $ENABLED_MICROCHIP" echo " * Nuvoton NPCT75x: $ENABLED_NUVOTON" +echo " * Nations Tech NS350: $ENABLED_NATIONS" 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..377cd485 --- /dev/null +++ b/examples/spdm/README.md @@ -0,0 +1,88 @@ +# TPM SPDM Examples + +This directory contains the SPDM demo for Nuvoton NPCT75x TPMs with wolfTPM. + +## Overview + +The `spdm_demo` establishes an SPDM secure session between the host and a +Nuvoton TPM over SPI, enabling AES-256-GCM encrypted bus communication. Once +active, all TPM commands are automatically encrypted with no application changes. + +For standard SPDM protocol support (spdm-emu, measurements, challenge, etc.), +see the [wolfSPDM](https://github.com/aidangarske/wolfSPDM) standalone library. + +## Building + +### Prerequisites + +wolfSSL with 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 +``` + +### wolfTPM with Nuvoton SPDM + +```bash +cd wolfTPM +./autogen.sh +./configure --enable-spdm --enable-nuvoton +make +``` + +## Demo Commands + +| Option | Description | +|--------|-------------| +| `--enable` | Enable SPDM on TPM via NTC2_PreConfig (one-time, requires reset) | +| `--disable` | Disable SPDM on TPM via NTC2_PreConfig (requires reset) | +| `--status` | Query SPDM status from TPM | +| `--get-pubkey` | Get TPM's SPDM-Identity P-384 public key | +| `--connect` | Establish SPDM session (ECDH P-384 handshake) | +| `--lock` | Lock SPDM-only mode (use with `--connect`) | +| `--unlock` | Unlock SPDM-only mode (use with `--connect`) | + +## Usage Examples + +```bash +# One-time setup: enable SPDM + GPIO reset +./examples/spdm/spdm_demo --enable +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# Query SPDM status +./examples/spdm/spdm_demo --status + +# Get TPM identity key +./examples/spdm/spdm_demo --get-pubkey + +# Establish SPDM session +./examples/spdm/spdm_demo --connect + +# Lock SPDM-only mode (connect + lock in one session) +./examples/spdm/spdm_demo --connect --lock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# All commands now auto-encrypt: +./examples/wrap/caps # auto-SPDM, AES-256-GCM encrypted +./tests/unit.test # full test suite over encrypted bus + +# Unlock SPDM-only mode +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect --unlock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +## Automated Test Suite + +Runs 6 tests: status, connect, lock, unit test over SPDM, unlock, cleartext caps setup lifecycle on hardware. + +```bash +./examples/spdm/spdm_test.sh +``` + +## Support + +For production use with hardware TPMs and SPDM support, contact **support@wolfssl.com**. diff --git a/examples/spdm/include.am b/examples/spdm/include.am new file mode 100644 index 00000000..54980203 --- /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) +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..e543b9d5 --- /dev/null +++ b/examples/spdm/spdm_demo.c @@ -0,0 +1,644 @@ +/* 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 + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#include +#include +#include + +#ifndef WOLFTPM2_NO_WRAPPER + +#include +#include + +#ifdef WOLFTPM_SPDM + +#include +#include + +int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]); + +static void usage(void) +{ + printf("SPDM Demo - TPM secure session\n\n" + "Usage: spdm_demo [options]\n" +#ifdef WOLFSPDM_NUVOTON + " --enable Enable SPDM via NTC2_PreConfig\n" + " --disable Disable SPDM via NTC2_PreConfig\n" + " --status Query SPDM status\n" + " --lock Lock SPDM-only mode\n" + " --unlock Unlock SPDM-only mode\n" +#endif +#ifdef WOLFSPDM_NATIONS + " --identity-key-set Provision SPDM identity key\n" + " --identity-key-unset Un-provision SPDM identity key\n" + " --psk PSK mode connect (64-byte PSK)\n" + " --psk-set Provision PSK (64-byte PSK, 32-byte ClearAuth)\n" + " --psk-clear Clear PSK (32-byte ClearAuth from psk-set)\n" + " --lock Lock SPDM-only mode (PSK mode, use with --psk)\n" + " --unlock Unlock SPDM-only mode (PSK mode, use with --psk)\n" + " --status Query SPDM status (PSK mode)\n" +#endif + " --get-pubkey Get TPM's SPDM-Identity public key\n" + " --connect Establish SPDM session\n" + " --caps Get TPM capabilities (use with --connect)\n" + " -h, --help Show this help\n\n" +#ifdef WOLFSPDM_NUVOTON + "Build: ./configure --enable-spdm --enable-nuvoton\n" +#elif defined(WOLFSPDM_NATIONS) + "Build: ./configure --enable-spdm --enable-nations\n" +#endif + ); +} + +#ifdef WOLFSPDM_NUVOTON +static int demo_enable(WOLFTPM2_DEV* dev) +{ + int rc; + printf("\n=== Enable SPDM ===\n"); + rc = wolfTPM2_SpdmEnable(dev); + if (rc == 0) { + printf(" SPDM enabled (reset TPM if newly configured)\n"); + } else if (rc == (int)TPM_RC_DISABLED) { + printf(" SPDM-only active (already enabled)\n"); + rc = 0; + } else if (rc == TPM_RC_COMMAND_CODE) { + printf(" NTC2_PreConfig not supported (may already be enabled)\n"); + rc = 0; + } 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 ===\n"); + rc = wolfTPM2_SpdmDisable(dev); + if (rc == 0) { + printf(" SPDM disabled (reset TPM for effect)\n"); + } else if (rc == (int)TPM_RC_DISABLED) { + printf(" SPDM-only active - unlock first, then reset and disable\n"); + } else if (rc == TPM_RC_COMMAND_CODE) { + printf(" NTC2_PreConfig not supported\n"); + rc = 0; + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_status(WOLFTPM2_DEV* dev) +{ + int rc; + WOLFSPDM_NUVOTON_STATUS status; + + printf("\n=== SPDM Status ===\n"); + XMEMSET(&status, 0, sizeof(status)); + rc = wolfTPM2_SpdmGetStatus(dev, &status); + if (rc == 0) { + int isConn = wolfTPM2_SpdmIsConnected(dev); + printf(" Enabled: %s Locked: %s Session: %s\n", + status.spdmEnabled ? "Yes" : "No", + status.spdmOnlyLocked ? "YES" : "No", + isConn ? "Yes" : "No"); + if (isConn) { + byte negVer = wolfSPDM_GetNegotiatedVersion(dev->spdmCtx->spdmCtx); + printf(" Version: SPDM %u.%u SessionID: 0x%08x\n", + (negVer >> 4) & 0xF, negVer & 0xF, + wolfTPM2_SpdmGetSessionId(dev)); + } + printf(" Nuvoton: v%u.%u\n", status.specVersionMajor, + status.specVersionMinor); + if (status.spdmOnlyLocked) + printf(" NOTE: SPDM-only mode, use --unlock to restore\n"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_get_pubkey(WOLFTPM2_DEV* dev) +{ + int rc; + byte pubKey[128]; + word32 pubKeySz = sizeof(pubKey); + word32 i; + + printf("\n=== Get SPDM-Identity Public Key ===\n"); + rc = wolfTPM2_SpdmGetPubKey(dev, pubKey, &pubKeySz); + if (rc == 0) { + printf(" Got %d bytes: ", (int)pubKeySz); + for (i = 0; i < pubKeySz && i < 32; i++) printf("%02x", pubKey[i]); + if (pubKeySz > 32) printf("..."); + printf("\n"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_connect(WOLFTPM2_DEV* dev) +{ + int rc; + + printf("\n=== SPDM Connect ===\n"); + if (wolfTPM2_SpdmIsConnected(dev)) { + printf(" Already connected (SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + return 0; + } + + printf(" Handshake: VERSION -> GET_PUBK -> KEY_EXCHANGE -> " + "GIVE_PUB -> FINISH\n"); + rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); + if (rc == 0) { + printf(" Session established (AES-256-GCM, SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_lock(WOLFTPM2_DEV* dev, int lock) +{ + int rc; + printf("\n=== SPDM-Only: %s ===\n", lock ? "LOCK" : "UNLOCK"); + rc = wolfTPM2_SpdmSetOnlyMode(dev, lock); + if (rc == 0) + printf(" %s\n", lock ? "LOCKED (TPM requires SPDM)" : "UNLOCKED"); + else + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; +} +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef WOLFSPDM_NATIONS +static int hex2bin(const char* hex, byte* bin, word32* binSz) +{ + word32 hexLen = (word32)XSTRLEN(hex); + word32 i; + if (hexLen % 2 != 0 || hexLen / 2 > *binSz) return -1; + for (i = 0; i < hexLen; i += 2) { + byte hi, lo; + hi = (byte)((hex[i] >= '0' && hex[i] <= '9') ? hex[i] - '0' : + (hex[i] >= 'a' && hex[i] <= 'f') ? hex[i] - 'a' + 10 : + (hex[i] >= 'A' && hex[i] <= 'F') ? hex[i] - 'A' + 10 : 0xFF); + lo = (byte)((hex[i+1] >= '0' && hex[i+1] <= '9') ? hex[i+1] - '0' : + (hex[i+1] >= 'a' && hex[i+1] <= 'f') ? hex[i+1] - 'a' + 10 : + (hex[i+1] >= 'A' && hex[i+1] <= 'F') ? hex[i+1] - 'A' + 10 : 0xFF); + if (hi == 0xFF || lo == 0xFF) return -1; + bin[i / 2] = (byte)((hi << 4) | lo); + } + *binSz = hexLen / 2; + return 0; +} + +static int demo_nations_status(WOLFTPM2_DEV* dev) +{ + int rc; + int isConn; + GetCapability_In capIn; + GetCapability_Out capOut; + + printf("\n=== Nations SPDM Status ===\n"); + + /* 1. Check identity key provisioning via GetCapability (always works) */ + XMEMSET(&capIn, 0, sizeof(capIn)); + capIn.capability = TPM_CAP_VENDOR_PROPERTY; + capIn.property = 12; /* TPM_PT_VENDOR + 12: identity key status */ + capIn.propertyCount = 1; + XMEMSET(&capOut, 0, sizeof(capOut)); + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc == 0) { + byte* raw = (byte*)&capOut.capabilityData.data.tpmProperties; + int identityKey = (raw[3] != 0); /* value at prop 12 */ + printf(" Identity Key: %s\n", + identityKey ? "provisioned" : "not provisioned"); + } else { + printf(" Identity Key: unknown (GetCap failed: 0x%x)\n", rc); + } + + /* 2. Try GET_STS_ vendor command (PSK mode only — may fail) */ + { + WOLFSPDM_NATIONS_STATUS status; + int stsRc; + + stsRc = wolfSPDM_GetVersion(dev->spdmCtx->spdmCtx); + if (stsRc == 0) { + XMEMSET(&status, 0, sizeof(status)); + stsRc = wolfTPM2_SpdmNationsGetStatus(dev, &status); + if (stsRc == 0) { + printf(" PSK: %s SPDM-Only: %s\n", + status.pskProvisioned ? "provisioned" : "not provisioned", + !status.spdmOnlyLocked ? "disabled" : + status.spdmOnlyPending ? "PENDING_DISABLE" : "ENABLED"); + } else { + printf(" PSK Status: unknown (GET_STS failed)\n"); + } + } else { + printf(" PSK Status: GET_VERSION failed\n"); + } + } + + /* 3. Local session state */ + isConn = wolfTPM2_SpdmIsConnected(dev); + printf(" Session: %s\n", isConn ? "active" : "none"); + if (isConn) { + printf(" SessionID: 0x%08x\n", wolfTPM2_SpdmGetSessionId(dev)); + } + + return 0; /* status is informational, don't fail */ +} + +static int demo_nations_psk_connect(WOLFTPM2_DEV* dev, const char* pskHex) +{ + int rc; + byte psk[128]; + word32 pskSz = sizeof(psk); + + printf("\n=== Nations PSK Connect ===\n"); + rc = hex2bin(pskHex, psk, &pskSz); + if (rc != 0) { + printf(" Invalid PSK hex string\n"); + return BAD_FUNC_ARG; + } + + rc = wolfTPM2_SpdmConnectNationsPsk(dev, psk, pskSz, NULL, 0); + XMEMSET(psk, 0, sizeof(psk)); + if (rc == 0) { + printf(" PSK session established (SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_nations_psk_set(WOLFTPM2_DEV* dev, + const char* pskHex, const char* clearAuthHex) +{ + int rc; + byte psk[64]; + word32 pskSz = sizeof(psk); + byte clearAuth[256]; + word32 clearAuthSz = sizeof(clearAuth); + byte payload[112]; /* PSK(64) + SHA-384(ClearAuth)(48) */ + wc_Sha384 sha; + + printf("\n=== Nations PSK Set ===\n"); + rc = hex2bin(pskHex, psk, &pskSz); + if (rc != 0 || pskSz != 64) { + printf(" Error: PSK must be exactly 64 bytes, got %u\n", pskSz); + return BAD_FUNC_ARG; + } + rc = hex2bin(clearAuthHex, clearAuth, &clearAuthSz); + if (rc != 0 || clearAuthSz != 32) { + printf(" Error: ClearAuth must be exactly 32 bytes, got %u\n", + clearAuthSz); + return BAD_FUNC_ARG; + } + + /* Build payload: PSK(64) + SHA-384(ClearAuth)(48) */ + XMEMCPY(payload, psk, 64); + rc = wc_InitSha384(&sha); + if (rc == 0) rc = wc_Sha384Update(&sha, clearAuth, clearAuthSz); + if (rc == 0) rc = wc_Sha384Final(&sha, payload + 64); + wc_Sha384Free(&sha); + XMEMSET(psk, 0, sizeof(psk)); + if (rc != 0) { + printf(" SHA-384 failed: %d\n", rc); + XMEMSET(payload, 0, sizeof(payload)); + return rc; + } + + printf(" ClearAuthDigest = SHA-384(%u bytes ClearAuth)\n", clearAuthSz); + + /* PSK_SET is a vendor-defined SPDM command — needs GET_VERSION first */ + rc = wolfSPDM_GetVersion(dev->spdmCtx->spdmCtx); + if (rc != 0) { + printf(" GET_VERSION failed: %d\n", rc); + XMEMSET(payload, 0, sizeof(payload)); + return rc; + } + + rc = wolfTPM2_SpdmNationsPskSet(dev, payload, sizeof(payload)); + XMEMSET(payload, 0, sizeof(payload)); + if (rc == 0) + printf(" PSK provisioned (64-byte PSK + 48-byte digest)\n"); + else + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; +} + +static int demo_nations_psk_clear(WOLFTPM2_DEV* dev, const char* authHex) +{ + int rc; + byte clearAuth[256]; + word32 clearAuthSz = sizeof(clearAuth); + + printf("\n=== Nations PSK Clear ===\n"); + + if (authHex == NULL) { + printf(" Error: --psk-clear requires ClearAuth hex argument\n"); + return BAD_FUNC_ARG; + } + rc = hex2bin(authHex, clearAuth, &clearAuthSz); + if (rc != 0 || clearAuthSz != 32) { + printf(" Error: ClearAuth must be exactly 32 bytes, got %u\n", + clearAuthSz); + return BAD_FUNC_ARG; + } + /* PSK_CLEAR: sends raw 32-byte ClearAuth. TPM computes SHA-384 + * internally and compares against stored ClearAuthDigest. */ + rc = wolfSPDM_Nations_PskClearWithVCA(dev->spdmCtx->spdmCtx, + clearAuth, clearAuthSz); + XMEMSET(clearAuth, 0, sizeof(clearAuth)); + if (rc == 0) + printf(" PSK cleared\n"); + else + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; +} + +static int demo_nations_identity_key_set(WOLFTPM2_DEV* dev, int set) +{ + int rc; + printf("\n=== Nations Identity Key %s ===\n", set ? "Set" : "Unset"); + rc = wolfTPM2_SpdmNationsIdentityKeySet(dev, set); + if (rc == 0) { + printf(" Identity key %s\n", set ? "provisioned" : "un-provisioned"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_nations_get_pubkey(WOLFTPM2_DEV* dev) +{ + int rc; + byte pubKey[128]; + word32 pubKeySz = sizeof(pubKey); + word32 i; + + printf("\n=== Get SPDM-Identity Public Key ===\n"); + + /* GET_PUBK is a vendor SPDM command — needs GET_VERSION first */ + rc = wolfSPDM_GetVersion(dev->spdmCtx->spdmCtx); + if (rc != 0) { + printf(" GET_VERSION failed: %d\n", rc); + return rc; + } + + rc = wolfTPM2_SpdmGetPubKey(dev, pubKey, &pubKeySz); + if (rc == 0) { + printf(" Got %d bytes: ", (int)pubKeySz); + for (i = 0; i < pubKeySz && i < 32; i++) printf("%02x", pubKey[i]); + if (pubKeySz > 32) printf("..."); + printf("\n"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_nations_caps184(WOLFTPM2_DEV* dev) +{ + int rc; + GetCapability_In capIn; + GetCapability_Out capOut; + word32 i; + (void)dev; + + printf("\n=== Nations TPM 184 Capabilities ===\n"); + + /* 1. Vendor properties (identity key status, FIPS mode, etc.) */ + printf(" Vendor Properties (TPM_CAP_VENDOR_PROPERTY):\n"); + XMEMSET(&capIn, 0, sizeof(capIn)); + capIn.capability = TPM_CAP_VENDOR_PROPERTY; + capIn.property = 11; /* FIPS_SL2_MODE */ + capIn.propertyCount = 2; /* Read props 11 and 12 */ + XMEMSET(&capOut, 0, sizeof(capOut)); + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc == 0) { + /* Vendor props are raw UINT32 values, not tagged pairs. + * With count=2 starting at prop 11, we get props 11 and 12 */ + byte* raw = (byte*)&capOut.capabilityData.data.tpmProperties; + printf(" Raw response: "); + for (i = 0; i < 16 && i < sizeof(capOut.capabilityData); i++) + printf("%02x", raw[i]); + printf("\n"); + } else { + printf(" Failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + + /* 2. TPM_CAP_PUB_KEYS (TPM 184: SPDM identity keys) */ + printf(" SPDM Public Keys (TPM_CAP_PUB_KEYS):\n"); + XMEMSET(&capIn, 0, sizeof(capIn)); + capIn.capability = TPM_CAP_PUB_KEYS; + capIn.property = 0; + capIn.propertyCount = 1; + XMEMSET(&capOut, 0, sizeof(capOut)); + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc == 0) { + byte* raw = (byte*)&capOut.capabilityData; + word32 rawSz = sizeof(capOut.capabilityData); + printf(" Response (%u bytes): ", rawSz); + for (i = 0; i < rawSz && i < 64; i++) + printf("%02x", raw[i]); + if (rawSz > 64) printf("..."); + printf("\n"); + } else if (rc == TPM_RC_VALUE) { + printf(" Not supported (TPM_RC_VALUE)\n"); + } else { + printf(" Failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + + /* 3. TPM_CAP_SPDM_SESSION_INFO (TPM 184: SPDM session state) */ + printf(" SPDM Session Info (TPM_CAP_SPDM_SESSION_INFO):\n"); + XMEMSET(&capIn, 0, sizeof(capIn)); + capIn.capability = TPM_CAP_SPDM_SESSION_INFO; + capIn.property = 0; + capIn.propertyCount = 1; + XMEMSET(&capOut, 0, sizeof(capOut)); + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc == 0) { + byte* raw = (byte*)&capOut.capabilityData; + word32 rawSz = sizeof(capOut.capabilityData); + printf(" Response (%u bytes): ", rawSz); + for (i = 0; i < rawSz && i < 64; i++) + printf("%02x", raw[i]); + if (rawSz > 64) printf("..."); + printf("\n"); + } else if (rc == TPM_RC_VALUE) { + printf(" Not supported (TPM_RC_VALUE)\n"); + } else { + printf(" Failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + + return 0; +} + +static int demo_nations_connect(WOLFTPM2_DEV* dev) +{ + int rc; + + printf("\n=== SPDM Connect (Nations) ===\n"); + if (wolfTPM2_SpdmIsConnected(dev)) { + printf(" Already connected (SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + return 0; + } + + printf(" Handshake: VERSION -> GET_PUBK -> KEY_EXCHANGE -> " + "GIVE_PUB -> FINISH\n"); + rc = wolfTPM2_SpdmConnectNations(dev, NULL, 0, NULL, 0); + if (rc == 0) { + printf(" Session established (AES-256-GCM, SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} +#endif /* WOLFSPDM_NATIONS */ + +int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) +{ + int rc, i; + WOLFTPM2_DEV dev; + + 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; + } + } + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + if (rc != 0) { + printf("wolfTPM2_Init failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; + } + + rc = wolfTPM2_SpdmInit(&dev); + if (rc != 0) { + printf("wolfTPM2_SpdmInit failed: %s\n", TPM2_GetRCString(rc)); + wolfTPM2_Cleanup(&dev); + return rc; + } + +#ifdef WOLFSPDM_NUVOTON + wolfTPM2_SpdmSetNuvotonMode(&dev); + wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); +#elif defined(WOLFSPDM_NATIONS) + wolfTPM2_SpdmSetNationsMode(&dev); + wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(dev.spdmCtx->spdmCtx, 1); +#endif +#endif + + for (i = 1; i < argc; i++) { +#ifdef WOLFSPDM_NUVOTON + if (XSTRCMP(argv[i], "--enable") == 0) + rc = demo_enable(&dev); + else if (XSTRCMP(argv[i], "--disable") == 0) + rc = demo_disable(&dev); + else if (XSTRCMP(argv[i], "--status") == 0) + rc = demo_status(&dev); + else if (XSTRCMP(argv[i], "--get-pubkey") == 0) + rc = demo_get_pubkey(&dev); + else if (XSTRCMP(argv[i], "--connect") == 0) + rc = demo_connect(&dev); + else if (XSTRCMP(argv[i], "--lock") == 0) + rc = demo_lock(&dev, 1); + else if (XSTRCMP(argv[i], "--unlock") == 0) + rc = demo_lock(&dev, 0); + else +#endif +#ifdef WOLFSPDM_NATIONS + if (XSTRCMP(argv[i], "--identity-key-set") == 0) + rc = demo_nations_identity_key_set(&dev, 1); + else if (XSTRCMP(argv[i], "--identity-key-unset") == 0) + rc = demo_nations_identity_key_set(&dev, 0); + else if (XSTRCMP(argv[i], "--get-pubkey") == 0) + rc = demo_nations_get_pubkey(&dev); + else if (XSTRCMP(argv[i], "--connect") == 0) + rc = demo_nations_connect(&dev); + else if (XSTRCMP(argv[i], "--status") == 0) + rc = demo_nations_status(&dev); + else if (XSTRCMP(argv[i], "--psk") == 0 && i + 1 < argc) + rc = demo_nations_psk_connect(&dev, argv[++i]); + else if (XSTRCMP(argv[i], "--psk-set") == 0 && i + 2 < argc) + { + const char* pskArg = argv[++i]; + const char* authArg = argv[++i]; + rc = demo_nations_psk_set(&dev, pskArg, authArg); + } + else if (XSTRCMP(argv[i], "--psk-clear") == 0 && i + 1 < argc) + rc = demo_nations_psk_clear(&dev, argv[++i]); + else if (XSTRCMP(argv[i], "--lock") == 0) + rc = wolfTPM2_SpdmNationsSetOnlyMode(&dev, 1); + else if (XSTRCMP(argv[i], "--unlock") == 0) + rc = wolfTPM2_SpdmNationsSetOnlyMode(&dev, 0); + else if (XSTRCMP(argv[i], "--tpm-clear") == 0) { + printf("\n=== TPM2_Clear ===\n"); + rc = wolfTPM2_Clear(&dev); + printf(" %s (rc=0x%x)\n", rc == 0 ? "Success" : "FAILED", rc); + } + else if (XSTRCMP(argv[i], "--caps184") == 0) + rc = demo_nations_caps184(&dev); + else +#endif + { printf("Unknown option: %s\n", argv[i]); usage(); rc = BAD_FUNC_ARG; } + if (rc != 0) break; + } + + wolfTPM2_Cleanup(&dev); /* Shutdown goes through SPDM if session active */ + wolfTPM2_SpdmCleanup(&dev); + return rc; +} + +#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 + +#endif /* WOLFTPM_SPDM */ +#endif /* !WOLFTPM2_NO_WRAPPER */ diff --git a/examples/spdm/spdm_test.sh b/examples/spdm/spdm_test.sh new file mode 100755 index 00000000..b72825bc --- /dev/null +++ b/examples/spdm/spdm_test.sh @@ -0,0 +1,214 @@ +#!/bin/bash +# spdm_test.sh - SPDM hardware tests (Nuvoton / Nations Technology) +# +# 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_DEMO="${1:-./examples/spdm/spdm_demo}" +CAPS_DEMO="./examples/wrap/caps" +UNIT_TEST="./tests/unit.test" +GPIO_CHIP="gpiochip0" +GPIO_PIN="4" +VENDOR="${2:-nuvoton}" # "nuvoton", "nations", or "nations-psk" +PASS=0 FAIL=0 TOTAL=0 + +# Nations PSK test data (from Vision/NSING reference PSK_DEMO_3) +# PSK: 64 bytes (used as IKM in HKDF-Extract during PSK_EXCHANGE) +NATIONS_PSK="dbc2192291d807742441b963f6712841f7697e2e39c45931f3abc53658c8b9338bd3561cab5d90cf9e493295bb5bd6b2c455e0fd19392e0ce4f3433cbcfc7047" +# ClearAuth: exactly 32 bytes (first 32 bytes of PSK per NSING convention) +# PSK_SET sends SHA-384(ClearAuth) as the 48-byte ClearAuthDigest +# PSK_CLEAR sends raw ClearAuth; TPM verifies SHA-384 match internally +NATIONS_CLEARAUTH="dbc2192291d807742441b963f6712841f7697e2e39c45931f3abc53658c8b933" + +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 + +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_test() { + local name="$1"; shift + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + gpio_reset + if "$@"; then + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) + fi + echo "" +} + +# run_test_caps: caps returns number of persistent handles as exit code, not 0 +run_test_caps() { + local name="$1"; shift + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + gpio_reset + if "$@" 2>&1 | grep -q "caps read successfully"; then + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) + fi + echo "" +} + +run_test_caps_no_reset() { + local name="$1"; shift + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + if "$@" 2>&1 | grep -q "caps read successfully"; then + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) + fi + echo "" +} + +# run_test_no_reset: Same as run_test but skip GPIO reset (for back-to-back commands) +run_test_no_reset() { + local name="$1"; shift + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + if "$@"; then + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) + fi + echo "" +} + +if [ ! -x "$SPDM_DEMO" ]; then + echo "Error: $SPDM_DEMO not found." + echo "Usage: $0 [path-to-spdm_demo] [nuvoton|nations|nations-psk]" + exit 1 +fi + +echo "=== SPDM Hardware Tests ($VENDOR) ===" +echo "Demo: $SPDM_DEMO Caps: $CAPS_DEMO Unit: $UNIT_TEST" +echo "" + +if [ "$VENDOR" = "nuvoton" ]; then + # Nuvoton test flow (identity key mode) + run_test "SPDM status query" "$SPDM_DEMO" --status + run_test "SPDM session connect" "$SPDM_DEMO" --connect + run_test "Lock SPDM-only mode" "$SPDM_DEMO" --connect --lock + + if [ -x "$UNIT_TEST" ]; then + run_test "Unit test over SPDM" "$UNIT_TEST" + else + echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" + fi + + run_test "Unlock SPDM-only mode" "$SPDM_DEMO" --connect --unlock + + if [ -x "$CAPS_DEMO" ]; then + run_test_caps "Cleartext caps (no SPDM)" "$CAPS_DEMO" + else + echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" + fi + +elif [ "$VENDOR" = "nations" ]; then + # Nations NS350 identity key mode — full lifecycle test + # Note: GPIO 4 is NOT wired to TPM_RST on NS350 daughter boards. + + run_test_no_reset "Unset identity key" "$SPDM_DEMO" --identity-key-unset + run_test_no_reset "Set identity key" "$SPDM_DEMO" --identity-key-set + run_test_no_reset "SPDM session connect" "$SPDM_DEMO" --connect + run_test_no_reset "Status query" "$SPDM_DEMO" --status + + if [ -x "$CAPS_DEMO" ]; then + run_test_caps_no_reset "Cleartext caps (no SPDM)" "$CAPS_DEMO" + else + echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" + fi + +elif [ "$VENDOR" = "nations-psk" ]; then + # Nations NS350 PSK mode — full lifecycle test + # + # PSK and identity key are mutually exclusive on NS350. + # Flow: unset identity key → PSK_SET → PSK connect → status → + # PSK_CLEAR → re-provision → re-connect → final clear → + # restore identity key → cleartext caps + # + # Uses NSING reference test data (PSK_DEMO_3 from Vision's traces). + # ClearAuth is always exactly 32 bytes per TCG spec. + + # Note: GPIO 4 is NOT wired to TPM_RST on NS350 daughter boards. + # Use run_test_no_reset instead of run_test. + + # Step 1: Ensure identity key is unset (required for PSK mode) + run_test_no_reset "Unset identity key" "$SPDM_DEMO" --identity-key-unset + + # Step 2: Provision PSK (PSK_SET_ vendor command) + # Sends PSK(64) + SHA-384(ClearAuth)(48) = 112 bytes + run_test_no_reset "PSK provision (PSK_SET)" "$SPDM_DEMO" --psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH" + + # Step 3: Status check (should show PSK provisioned) + run_test_no_reset "Status (PSK provisioned)" "$SPDM_DEMO" --status + + # Step 4: PSK connect (VCA → PSK_EXCHANGE → PSK_FINISH) + run_test_no_reset "PSK session connect" "$SPDM_DEMO" --psk "$NATIONS_PSK" + + # Step 5: PSK connect again (verify repeatable sessions) + run_test_no_reset "PSK session connect (repeat)" "$SPDM_DEMO" --psk "$NATIONS_PSK" + + # Step 6: PSK_CLEAR (sends raw 32-byte ClearAuth, TPM verifies SHA-384) + run_test_no_reset "PSK clear (PSK_CLEAR)" "$SPDM_DEMO" --psk-clear "$NATIONS_CLEARAUTH" + + # Step 7: Status check (should show PSK not provisioned) + run_test_no_reset "Status (PSK cleared)" "$SPDM_DEMO" --status + + # Step 8: Re-provision PSK (verify PSK_SET works after clear) + run_test_no_reset "PSK re-provision (PSK_SET)" "$SPDM_DEMO" --psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH" + + # Step 9: PSK connect after re-provision + run_test_no_reset "PSK session connect (after re-provision)" "$SPDM_DEMO" --psk "$NATIONS_PSK" + + # Step 10: Final PSK_CLEAR (leave module in clean state) + run_test_no_reset "Final PSK clear" "$SPDM_DEMO" --psk-clear "$NATIONS_CLEARAUTH" + + # Step 11: Restore identity key (factory default) + run_test_no_reset "Restore identity key" "$SPDM_DEMO" --identity-key-set + + # Step 12: Cleartext TPM commands (verify module works normally) + if [ -x "$CAPS_DEMO" ]; then + run_test_caps_no_reset "Cleartext caps (no SPDM)" "$CAPS_DEMO" + else + echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" + fi + +else + echo "Error: Unknown vendor '$VENDOR'. Use 'nuvoton', 'nations', or 'nations-psk'." + exit 1 +fi + +echo "" +echo "=== Results: $TOTAL total, $PASS passed, $FAIL failed ===" +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/hal/tpm_io_linux.c b/hal/tpm_io_linux.c index bb0e466f..cffefb78 100644 --- a/hal/tpm_io_linux.c +++ b/hal/tpm_io_linux.c @@ -83,6 +83,9 @@ #elif defined(WOLFTPM_NUVOTON) /* Nuvoton NPCT75x uses CE0 */ #define TPM2_SPI_DEV_CS "0" + #elif defined(WOLFTPM_NATIONS) + /* Nations Technology NS350 uses CE0 */ + #define TPM2_SPI_DEV_CS "0" #else /* OPTIGA SLB9670/SLB9762 and LetsTrust TPM use CE1 */ #define TPM2_SPI_DEV_CS "1" 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/spdm/README.md b/src/spdm/README.md new file mode 100644 index 00000000..b0a9d8ef --- /dev/null +++ b/src/spdm/README.md @@ -0,0 +1,474 @@ +# wolfTPM SPDM + +wolfTPM includes built-in SPDM support for Nuvoton NPCT75x and Nations NS350 +TPMs using wolfSSL/wolfCrypt. Both vendors support identity key mode (ECDHE +P-384) for session establishment. The Nations NS350 additionally supports PSK +(pre-shared key) mode. Once a session is established, all TPM commands and +responses are encrypted with AES-256-GCM over the existing SPI/I2C bus — no +application code changes needed. + +For standard SPDM protocol testing with the DMTF spdm-emu emulator, see the +[wolfSPDM](https://github.com/aidangarske/wolfSPDM) standalone library. + +## Quick Start + +### Nuvoton NPCT75x + +```bash +# Build wolfSSL +pushd ../wolfssl && ./autogen.sh && \ +./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp && \ +make && sudo make install && sudo ldconfig && popd + +# Build wolfTPM +./autogen.sh && ./configure --enable-spdm --enable-nuvoton && make + +# Enable SPDM (one-time), reset, connect +./examples/spdm/spdm_demo --enable +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect +``` + +See [Building](#building) and [Nuvoton NPCT75x Details](#nuvoton-npct75x) for +full instructions. + +### Nations NS350 + +```bash +# Build wolfSSL +pushd ../wolfssl && ./autogen.sh && \ +./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp && \ +make && sudo make install && sudo ldconfig && popd + +# Build wolfTPM +./autogen.sh && ./configure --enable-spdm --enable-nations && make + +# Connect (identity key is factory default) +./examples/spdm/spdm_demo --connect +``` + +See [Building](#building) and [Nations NS350 Details](#nations-ns350) for full +instructions. + +## How It Works + +SPDM (Security Protocol and Data Model) establishes an authenticated encrypted +channel over the existing SPI/I2C bus. The implementation uses Algorithm Set B: +ECDH P-384 / SHA-384 / AES-256-GCM. Two session establishment modes are +supported. + +### Protocol Flow: Identity Key Mode (Nuvoton + Nations) + +``` +Host TPM (Nuvoton NPCT75x / Nations NS350) + | | + |--- GET_VERSION ------------------>| (negotiate SPDM version) + |<-- 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. + +### Protocol Flow: PSK Mode (Nations Only) + +PSK mode replaces the ECDHE key exchange with a symmetric pre-shared key. +The same AES-256-GCM encryption is used for data transport. + +``` +Host TPM (Nations NS350) + | | + |--- GET_VERSION ------------------>| (negotiate SPDM version) + |<-- VERSION -----------------------| + | | + |--- GET_CAPABILITIES ------------->| (capability exchange) + |<-- CAPABILITIES ------------------| + | | + |--- NEGOTIATE_ALGORITHMS --------->| (Algorithm Set B: P-384/SHA-384) + |<-- ALGORITHMS --------------------| + | | + |--- PSK_EXCHANGE ----------------->| (session key from PSK) + |<-- PSK_EXCHANGE_RSP --------------| (+ HMAC proof) + | | + | --- Handshake keys derived --- | (Salt_0 = 0xFF * H for PSK mode) + | | + |=== PSK_FINISH ===================>| (encrypted: requester HMAC) + |<== PSK_FINISH_RSP ================| + | | + | --- App data keys derived --- | + | | + |=== TPM2_CMD (AES-256-GCM) =======>| (every command encrypted) + |<== TPM2_RSP (AES-256-GCM) ========| +``` + +PSK and identity key modes are mutually exclusive on the NS350. The identity +key is provisioned by factory default; it must be unset before PSK can be used. +See [PSK Lifecycle (Nations)](#psk-lifecycle-nations). + +### SPDM-Only Mode (Encrypted Bus Enforcement) + +SPDM-only mode forces all TPM commands through the encrypted SPDM channel. +Both vendors support this. The typical lifecycle: + +``` +1. Enable SPDM (one-time, persists across resets) +2. Connect (handshake, derives session keys) +3. Lock SPDM-only (TPM rejects all cleartext commands) +4. 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. 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. See +[How Auto-SPDM Works](#how-auto-spdm-works) for details. + +**Reset method differs by vendor:** +- **Nuvoton:** GPIO reset — `gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2` +- **Nations:** Full power cycle required (GPIO 4 is not wired to TPM_RST on NS350 daughter boards) + +## Building + +### wolfSSL + +```bash +pushd ../wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-ecc --enable-sha384 \ + --enable-aesgcm --enable-hkdf --enable-sp +make +sudo make install && sudo ldconfig +popd +``` + +Both Nuvoton and Nations use the same wolfSSL flags above. + +### wolfTPM + +```bash +./autogen.sh +./configure --enable-spdm --enable-nuvoton # Nuvoton +# or +./configure --enable-spdm --enable-nations # Nations +make +``` + +### Configure Options + +| Option | Description | +|-----------------------------|-------------| +| `--enable-spdm` | Enable SPDM support (required) | +| `--enable-nuvoton` | Enable Nuvoton TPM hardware support | +| `--enable-nations` | Enable Nations NS350 hardware support | +| `--enable-debug` | Debug output with verbose SPDM tracing | +| `--enable-spdm-dynamic-mem` | Heap-allocated SPDM context (default: static ~32 KB) | + +## Usage + +### One-Time Setup + +#### Nuvoton + +```bash +# Enable SPDM on the TPM (persists across resets) +./examples/spdm/spdm_demo --enable + +# GPIO reset +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# Verify SPDM is enabled +./examples/spdm/spdm_demo --status +``` + +#### Nations + +Identity key mode is the factory default — no setup required. If previously +unset, restore with: + +```bash +./examples/spdm/spdm_demo --identity-key-set +``` + +### Establishing a Session + +#### Identity Key Mode (Both Vendors) + +```bash +# Establish SPDM session (VERSION → GET_PUBK → KEY_EXCHANGE → GIVE_PUB → FINISH) +./examples/spdm/spdm_demo --connect + +# Query SPDM status +./examples/spdm/spdm_demo --status +``` + +**Note:** `--get-pubkey` retrieves the TPM's identity key as part of the full +handshake within `--connect`. It is not intended as a standalone command. + +#### PSK Mode (Nations) + +Requires PSK to be provisioned first. See +[PSK Lifecycle (Nations)](#psk-lifecycle-nations). + +```bash +# Establish PSK session (VERSION → CAPS → ALGO → PSK_EXCHANGE → PSK_FINISH) +./examples/spdm/spdm_demo --psk +``` + +### Lock/Unlock SPDM-Only Mode + +Lock requires an active SPDM session. After locking, a reset is required for +enforcement to take effect. + +**Nuvoton (identity key):** + +```bash +./examples/spdm/spdm_demo --connect --lock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# TPM now requires SPDM — all commands auto-encrypted: +./examples/wrap/caps # auto-SPDM session, all AES-256-GCM +./tests/unit.test # full test suite over encrypted bus + +# Unlock +./examples/spdm/spdm_demo --connect --unlock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +**Nations (identity key):** + +```bash +./examples/spdm/spdm_demo --connect --lock +# Power cycle required (unplug and re-plug Raspberry Pi) + +./examples/spdm/spdm_demo --connect --unlock +# Power cycle again +``` + +**Nations (PSK mode):** + +```bash +./examples/spdm/spdm_demo --psk --lock +# Power cycle required + +./examples/spdm/spdm_demo --psk --unlock +# Power cycle again +``` + +### PSK Lifecycle (Nations) + +PSK and identity key modes are mutually exclusive on the NS350. The identity key +is provisioned by default; it must be unset before PSK can be used. + +```bash +# 1. Unset identity key (enables PSK mode) +./examples/spdm/spdm_demo --identity-key-unset + +# 2. Provision PSK (64-byte PSK + 32-byte ClearAuth) +# The demo computes SHA-384(ClearAuth) and sends PSK(64)+Digest(48) = 112 bytes +./examples/spdm/spdm_demo --psk-set + +# 3. Establish PSK session +./examples/spdm/spdm_demo --psk + +# 4. Clear PSK (sends raw 32-byte ClearAuth; TPM verifies SHA-384 internally) +./examples/spdm/spdm_demo --psk-clear + +# 5. Restore identity key (factory default) +./examples/spdm/spdm_demo --identity-key-set +``` + +**Important:** The ClearAuth must be exactly 32 bytes. PSK_SET stores its SHA-384 +digest (48 bytes). PSK_CLEAR sends the raw 32 bytes and the TPM computes SHA-384 +to verify. Using the wrong size makes PSK_CLEAR impossible. + +### Running the Test Suite + +```bash +# Nuvoton (identity key — includes GPIO resets between tests) +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_demo nuvoton + +# Nations (identity key — no GPIO resets) +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_demo nations + +# Nations (PSK — full lifecycle: provision → connect → clear → restore) +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_demo nations-psk +``` + +## TCG SPDM Vendor Commands + +Both Nuvoton and Nations TPMs implement the TCG "TPM Communication over SPDM +Secure Session" specification. These commands use 8-byte ASCII vendor codes in +SPDM `VENDOR_DEFINED_REQUEST` messages with `StandardID=0x0001` (TCG). + +| VdCode | Command | Vendor | Description | +|-------------|-----------------|---------|-------------| +| `GET_PUBK` | Get Public Key | Both | Get TPM's SPDM-Identity P-384 public key | +| `GIVE_PUB` | Give Public Key | Both | Send host's P-384 public key to TPM | +| `TPM2_CMD` | TPM Command | Both | Wrap TPM command in SPDM secured message | +| `GET_STS_` | Get Status | Both | Query SPDM status | +| `SPDMONLY` | SPDM-Only Mode | Both | Lock/unlock SPDM-only enforcement | +| `PSK_SET_` | PSK Set | Nations | Provision pre-shared key (64-byte PSK + SHA-384 digest) | +| `PSK_CLR_` | PSK Clear | Nations | Clear provisioned PSK (requires ClearAuth) | + +## Command Reference + +All `spdm_demo` options in one table: + +| Option | Vendor | Description | +|-------------------------------|---------|-------------| +| `--enable` | Nuvoton | Enable SPDM via NTC2_PreConfig (one-time, persists) | +| `--disable` | Nuvoton | Disable SPDM via NTC2_PreConfig | +| `--identity-key-set` | Nations | Provision SPDM identity key (factory default) | +| `--identity-key-unset` | Nations | Un-provision identity key (required before PSK) | +| `--get-pubkey` | Both | Get TPM's SPDM-Identity P-384 public key (used within `--connect`) | +| `--connect` | Both | Establish identity key SPDM session | +| `--status` | Both | Query SPDM status | +| `--lock` | Both | Lock SPDM-only mode (requires active session) | +| `--unlock` | Both | Unlock SPDM-only mode (requires active session) | +| `--psk ` | Nations | Establish PSK session (64-byte PSK) | +| `--psk-set ` | Nations | Provision PSK (64-byte PSK, 32-byte ClearAuth) | +| `--psk-clear ` | Nations | Clear PSK (32-byte ClearAuth) | +| `--caps184` | Nations | Query TPM 184 vendor properties and SPDM session info | +| `--tpm-clear` | Nations | Send TPM2_Clear (platform auth) | + +## Vendor-Specific Details + +### Nuvoton NPCT75x + +**Enable/Disable:** SPDM is enabled via the `NTC2_PreConfig` vendor command +(`--enable` / `--disable`). This persists across resets. + +**GPIO Reset:** GPIO 4 is wired to TPM_RST on the Nuvoton daughter board. +A GPIO reset clears stale SPDM state: + +```bash +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +### Nations NS350 + +**Mode Switching:** Identity key and PSK modes are mutually exclusive. The +identity key is provisioned by factory default. Use `--identity-key-unset` +before provisioning PSK, and `--identity-key-set` to restore. + +**No GPIO Reset:** GPIO 4 is NOT wired to TPM_RST on the NS350 daughter board. +A full power cycle (unplug and re-plug the Raspberry Pi) is required to reset +the TPM. `sudo reboot` is not sufficient as the 3.3V rail stays powered. + +**Capabilities Query:** Use `--caps184` to query TPM 184 vendor properties +including SPDM session info. + +**Status Caveat:** On some NS350 firmware versions, `--status` may report +"Identity Key: not provisioned" even when the key is present. The `--connect` +command is the definitive test — if the ECDHE handshake succeeds, the identity +key is provisioned. + +**ClearAuth:** Must be exactly 32 bytes. `PSK_SET` stores its SHA-384 digest +(48 bytes). `PSK_CLEAR` sends the raw 32 bytes and the TPM computes SHA-384 +to verify. + +**PSK Vendor Error Codes:** + +| Code | Name | Description | +|------|------|-------------| +| 0xA1 | Vd_PSKAlreadySet | PSK already provisioned (must PSK_CLEAR first) | +| 0xA2 | Vd_InternalFailure | SPDM session layer internal error | +| 0xA3 | Vd_PSKNotSet | No PSK provisioned | +| 0xA5 | Vd_AuthFail | ClearAuth SHA-384 doesn't match stored digest | + +## 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_SecuredExchange()` | Encrypt/send/receive/decrypt in one call | + +## Troubleshooting + +### SPDM handshake fails after interrupted session + +**Nuvoton:** GPIO 4 is wired to TPM_RST on the Nuvoton daughter board. +A GPIO reset clears stale SPDM state: + +```bash +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +**Nations NS350:** GPIO 4 is NOT wired to TPM_RST on the NS350 daughter board. +A full power cycle (unplug and re-plug the Raspberry Pi) is required to reset +the TPM. `sudo reboot` is not sufficient as the 3.3V rail stays powered. + +### 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 | + +## Standard SPDM Support + +For standard SPDM protocol support including session establishment with the +DMTF spdm-emu emulator, measurements, challenge authentication, heartbeat, +and key update, see the [wolfSPDM](https://github.com/aidangarske/wolfSPDM) +standalone library. + +## License + +GPLv3 — see LICENSE file. Copyright (C) 2006-2025 wolfSSL Inc. diff --git a/src/spdm/include.am b/src/spdm/include.am new file mode 100644 index 00000000..196691c6 --- /dev/null +++ b/src/spdm/include.am @@ -0,0 +1,37 @@ +# vim:ft=automake +# included from Top Level Makefile.am +# All paths should be given relative to the root + +if BUILD_SPDM + +src_libwolftpm_la_SOURCES += \ + src/spdm/spdm_context.c \ + src/spdm/spdm_crypto.c \ + src/spdm/spdm_kdf.c \ + src/spdm/spdm_msg.c \ + src/spdm/spdm_secured.c \ + src/spdm/spdm_session.c \ + src/spdm/spdm_transcript.c + +# spdm_tcg.c: shared TCG SPDM code (Nuvoton + Nations) +if BUILD_NUVOTON +src_libwolftpm_la_SOURCES += src/spdm/spdm_tcg.c src/spdm/spdm_nuvoton.c +endif + +if BUILD_NATIONS +if !BUILD_NUVOTON +src_libwolftpm_la_SOURCES += src/spdm/spdm_tcg.c +endif +src_libwolftpm_la_SOURCES += src/spdm/spdm_nations.c +src_libwolftpm_la_SOURCES += src/spdm/spdm_psk.c +endif + +check_PROGRAMS += src/spdm/unit_test +src_spdm_unit_test_SOURCES = src/spdm/unit_test.c +src_spdm_unit_test_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +src_spdm_unit_test_CFLAGS = $(AM_CFLAGS) + +EXTRA_DIST += src/spdm/spdm_internal.h +EXTRA_DIST += src/spdm/README.md + +endif BUILD_SPDM diff --git a/src/spdm/spdm_context.c b/src/spdm/spdm_context.c new file mode 100644 index 00000000..a493c45b --- /dev/null +++ b/src/spdm/spdm_context.c @@ -0,0 +1,549 @@ +/* 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 + +/* ----- Context Management ----- */ + +int wolfSPDM_Init(WOLFSPDM_CTX* ctx) +{ + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Clean slate, dont read fields before this */ + 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 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); + } + + /* 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->flags.hasReqKeyPair = 1; + + return WOLFSPDM_SUCCESS; +} + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +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 || WOLFSPDM_NATIONS */ + +/* wolfSPDM_SetPSK moved to spdm_psk.c */ + +void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable) +{ + if (ctx != NULL) { + ctx->flags.debug = (enable != 0); + } +} + +int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + +#ifdef WOLFSPDM_NUVOTON + if (mode == WOLFSPDM_MODE_NUVOTON) { + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ctx->connectionHandle = WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT; + ctx->fipsIndicator = WOLFSPDM_NUVOTON_FIPS_DEFAULT; + return WOLFSPDM_SUCCESS; + } +#endif +#ifdef WOLFSPDM_NATIONS + if (mode == WOLFSPDM_MODE_NATIONS) { + ctx->mode = WOLFSPDM_MODE_NATIONS; + ctx->connectionHandle = 0; + /* Default to NON_FIPS; overridden by auto-detect if FIPS configured */ + ctx->fipsIndicator = WOLFSPDM_FIPS_NON_FIPS; + return WOLFSPDM_SUCCESS; + } + if (mode == WOLFSPDM_MODE_NATIONS_PSK) { + ctx->mode = WOLFSPDM_MODE_NATIONS_PSK; + ctx->connectionHandle = 0; + ctx->fipsIndicator = WOLFSPDM_FIPS_NON_FIPS; + return WOLFSPDM_SUCCESS; + } +#endif + + return WOLFSPDM_E_INVALID_ARG; /* Unsupported mode */ +} + +WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return (WOLFSPDM_MODE)0; + } + 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; +} + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +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) ----- */ + +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; + } + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS) { + return wolfSPDM_ConnectTCG(ctx); + } +#endif +#ifdef WOLFTPM_SPDM_PSK + if (ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + return wolfSPDM_ConnectPsk(ctx); + } +#endif + + return WOLFSPDM_E_INVALID_ARG; /* Standard mode not available */ +} + +int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) +{ + int rc; + byte txBuf[8]; + byte rxBuf[16]; /* END_SESSION_ACK: 4 bytes */ + word32 txSz, rxSz; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + /* Build END_SESSION */ + txSz = sizeof(txBuf); + rc = wolfSPDM_BuildEndSession(ctx, txBuf, &txSz); + if (rc == WOLFSPDM_SUCCESS) { + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + } + + /* Reset state and zero ALL key material */ + ctx->state = WOLFSPDM_STATE_INIT; + ctx->sessionId = 0; + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + /* App data keys */ + wc_ForceZero(ctx->reqDataKey, sizeof(ctx->reqDataKey)); + wc_ForceZero(ctx->rspDataKey, sizeof(ctx->rspDataKey)); + wc_ForceZero(ctx->reqDataIv, sizeof(ctx->reqDataIv)); + wc_ForceZero(ctx->rspDataIv, sizeof(ctx->rspDataIv)); + /* Handshake keys */ + wc_ForceZero(ctx->reqHsSecret, sizeof(ctx->reqHsSecret)); + wc_ForceZero(ctx->rspHsSecret, sizeof(ctx->rspHsSecret)); + wc_ForceZero(ctx->reqFinishedKey, sizeof(ctx->reqFinishedKey)); + wc_ForceZero(ctx->rspFinishedKey, sizeof(ctx->rspFinishedKey)); + /* Secrets and hashes */ + wc_ForceZero(ctx->handshakeSecret, sizeof(ctx->handshakeSecret)); + wc_ForceZero(ctx->sharedSecret, sizeof(ctx->sharedSecret)); + ctx->sharedSecretSz = 0; + wc_ForceZero(ctx->th1, sizeof(ctx->th1)); + wc_ForceZero(ctx->th2, sizeof(ctx->th2)); + /* Free ephemeral ECC key */ + if (ctx->flags.ephemeralKeyInit) { + wc_ecc_free(&ctx->ephemeralKey); + ctx->flags.ephemeralKeyInit = 0; + } + + return rc; +} + +/* ----- 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; + } + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + /* Wrap messages with TCG SPDM + * headers; I/O sends TCG-framed messages. */ + byte tcgTx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + byte tcgRx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + word32 tcgRxSz = sizeof(tcgRx); + int tcgTxSz; + word32 msgSize; + word32 payloadSz; + word16 tag; + + /* Detect message type: SPDM version byte 0x10-0x1F = clear message. + * Secured records start with SessionID (LE, typically 0x01 0x00...), + * which is never in the SPDM version range. */ + if (txSz > 0 && txBuf[0] >= 0x10 && txBuf[0] <= 0x1F) { + /* Clear SPDM message - wrap with TCG clear header (0x8101) */ + tcgTxSz = wolfSPDM_BuildTcgClearMessage(ctx, txBuf, txSz, + tcgTx, sizeof(tcgTx)); + } else { + /* Secured record - prepend TCG secured header (0x8201) */ + word32 totalSz = 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, "TCG I/O failed: %d\n", rc); + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugHex(ctx, "TCG RX", tcgRx, tcgRxSz); + + /* Strip TCG binding header from response */ + if (tcgRxSz < WOLFSPDM_TCG_HEADER_SIZE) { + wolfSPDM_DebugPrint(ctx, "SendReceive: response too short (%u)\n", + tcgRxSz); + return WOLFSPDM_E_BUFFER_SMALL; + } + + tag = SPDM_Get16BE(tcgRx); + if (tag != WOLFSPDM_TCG_TAG_CLEAR && tag != WOLFSPDM_TCG_TAG_SECURED) { + wolfSPDM_DebugPrint(ctx, "SendReceive: unexpected TCG tag " + "0x%04x\n", tag); + return WOLFSPDM_E_PEER_ERROR; + } + + /* Capture FIPS indicator from response if non-zero */ + tag = SPDM_Get16BE(tcgRx + 10); + if (tag != 0) { + ctx->fipsIndicator = tag; + } + + /* Extract payload (everything after 16-byte TCG header) */ + msgSize = SPDM_Get32BE(tcgRx + 2); + + if (msgSize < WOLFSPDM_TCG_HEADER_SIZE || msgSize > tcgRxSz) { + wolfSPDM_DebugPrint(ctx, "SendReceive: TCG size %u invalid " + "(min=%u, received=%u)\n", msgSize, + WOLFSPDM_TCG_HEADER_SIZE, tcgRxSz); + return WOLFSPDM_E_BUFFER_SMALL; + } + + payloadSz = msgSize - WOLFSPDM_TCG_HEADER_SIZE; + if (payloadSz > *rxSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(rxBuf, tcgRx + WOLFSPDM_TCG_HEADER_SIZE, payloadSz); + *rxSz = payloadSz; + + return WOLFSPDM_SUCCESS; + } +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + + rc = ctx->ioCb(ctx, txBuf, txSz, rxBuf, rxSz, ctx->ioUserCtx); + if (rc != 0) { + return WOLFSPDM_E_IO_FAIL; + } + + return WOLFSPDM_SUCCESS; +} + +/* ----- Debug Utilities ----- */ +#ifdef DEBUG_WOLFTPM +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); +} +#endif + +/* ----- 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_SESSION_INVALID: return "Invalid session"; + case WOLFSPDM_E_KEY_EXCHANGE: return "Key exchange failed"; + default: return "Unknown error"; + } +} diff --git a/src/spdm/spdm_crypto.c b/src/spdm/spdm_crypto.c new file mode 100644 index 00000000..8ccc596c --- /dev/null +++ b/src/spdm/spdm_crypto.c @@ -0,0 +1,348 @@ +/* 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" + +/* 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; + } + + /* Left-pad coordinates to full size (wolfSSL may strip leading zeros) */ + wolfSPDM_LeftPadToSize(pubKeyX, *pubKeyXSz, WOLFSPDM_ECC_KEY_SIZE); + *pubKeyXSz = WOLFSPDM_ECC_KEY_SIZE; + wolfSPDM_LeftPadToSize(pubKeyY, *pubKeyYSz, WOLFSPDM_ECC_KEY_SIZE); + *pubKeyYSz = WOLFSPDM_ECC_KEY_SIZE; + + return WOLFSPDM_SUCCESS; +} + +/* ----- 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; + } + + rc = wc_ecc_init(&peerKey); + if (rc == 0) { + peerKeyInit = 1; + rc = wc_ecc_import_unsigned(&peerKey, peerPubKeyX, peerPubKeyY, + NULL, ECC_SECP384R1); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Failed to import peer public key: %d\n", rc); + } + } + /* Validate peer's public key is on the curve (prevents invalid-curve attacks) */ + if (rc == 0) { + rc = wc_ecc_check_key(&peerKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Peer public key invalid (not on curve): %d\n", rc); + } + } + /* Compute ECDH shared secret */ + if (rc == 0) { + ctx->sharedSecretSz = sizeof(ctx->sharedSecret); + rc = wc_ecc_shared_secret(&ctx->ephemeralKey, &peerKey, + ctx->sharedSecret, &ctx->sharedSecretSz); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "ECDH shared_secret failed: %d\n", rc); + } + } + if (rc == 0) { + wolfSPDM_LeftPadToSize(ctx->sharedSecret, ctx->sharedSecretSz, + WOLFSPDM_ECC_KEY_SIZE); + ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; + wolfSPDM_DebugPrint(ctx, "ECDH shared secret computed (%u bytes)\n", + ctx->sharedSecretSz); + } else { + wc_ForceZero(ctx->sharedSecret, sizeof(ctx->sharedSecret)); + ctx->sharedSecretSz = 0; + } + + if (peerKeyInit) { + wc_ecc_free(&peerKey); + } + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +/* ----- ECDSA Signature Verification (P-384) ----- */ + +int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, + const byte* sig, word32 sigSz) +{ + ecc_key verifyKey; + int rc; + int keyInit = 0; + byte derSig[ECC_MAX_SIG_SIZE]; + word32 derSigSz = sizeof(derSig); + int verified = 0; + const byte* pubKeyX; + const byte* pubKeyY; + + if (ctx == NULL || hash == NULL || sig == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.hasRspPubKey || ctx->rspPubKeyLen < WOLFSPDM_ECC_POINT_SIZE) { + wolfSPDM_DebugPrint(ctx, "No responder public key for verification\n"); + return WOLFSPDM_E_BAD_STATE; + } + + if (sigSz != WOLFSPDM_ECC_SIG_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Extract X/Y coordinates from rspPubKey. + * If len == 96: raw X||Y format. + * If len > 96: TPMT_PUBLIC format — X/Y are at the tail: + * [len-100]: X size(2 BE) + X(48) + Y size(2 BE) + Y(48) */ + if (ctx->rspPubKeyLen == WOLFSPDM_ECC_POINT_SIZE) { + pubKeyX = ctx->rspPubKey; + pubKeyY = ctx->rspPubKey + WOLFSPDM_ECC_KEY_SIZE; + } else if (ctx->rspPubKeyLen >= WOLFSPDM_ECC_POINT_SIZE + 4) { + /* TPMT_PUBLIC: skip 2-byte size prefixes on each coordinate */ + pubKeyX = ctx->rspPubKey + (ctx->rspPubKeyLen - 100 + 2); + pubKeyY = ctx->rspPubKey + (ctx->rspPubKeyLen - 48); + } else { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wc_ecc_init(&verifyKey); + if (rc == 0) { + keyInit = 1; + rc = wc_ecc_import_unsigned(&verifyKey, pubKeyX, pubKeyY, + NULL, ECC_SECP384R1); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Failed to import rsp pub key for verify: %d\n", rc); + } + } + if (rc == 0) { + rc = wc_ecc_check_key(&verifyKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Responder pub key invalid (not on curve): %d\n", rc); + } + } + /* Convert raw R||S signature to DER format for wolfCrypt */ + if (rc == 0) { + rc = wc_ecc_rs_raw_to_sig(sig, WOLFSPDM_ECC_KEY_SIZE, + sig + WOLFSPDM_ECC_KEY_SIZE, WOLFSPDM_ECC_KEY_SIZE, + derSig, &derSigSz); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_rs_raw_to_sig failed: %d\n", rc); + } + } + if (rc == 0) { + rc = wc_ecc_verify_hash(derSig, derSigSz, hash, hashSz, + &verified, &verifyKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_verify_hash failed: %d\n", rc); + } + } + if (rc == 0 && !verified) { + wolfSPDM_DebugPrint(ctx, "Responder signature verification FAILED\n"); + rc = -1; + } + if (rc == 0) { + wolfSPDM_DebugPrint(ctx, "Responder signature VERIFIED OK\n"); + } + + if (keyInit) { + wc_ecc_free(&verifyKey); + } + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_BAD_SIGNATURE; +} + +/* ----- 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[ECC_MAX_SIG_SIZE]; + 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; + } + + if (*sigSz < WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + rc = wc_ecc_init(&sigKey); + if (rc == 0) { + keyInit = 1; + rc = wc_ecc_import_unsigned(&sigKey, + ctx->reqPubKey, + ctx->reqPubKey + WOLFSPDM_ECC_KEY_SIZE, + ctx->reqPrivKey, + ECC_SECP384R1); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_import_unsigned failed: %d\n", rc); + } + } else { + wolfSPDM_DebugPrint(ctx, "wc_ecc_init failed: %d\n", rc); + } + if (rc == 0) { + rc = wc_ecc_sign_hash(hash, hashSz, derSig, &derSigSz, + &ctx->rng, &sigKey); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_sign_hash failed: %d\n", rc); + } + } + /* Convert DER signature to raw R||S format (96 bytes for P-384) */ + if (rc == 0) { + rLen = WOLFSPDM_ECC_KEY_SIZE; + sLen = WOLFSPDM_ECC_KEY_SIZE; + rc = wc_ecc_sig_to_rs(derSig, derSigSz, sig, &rLen, + sig + WOLFSPDM_ECC_KEY_SIZE, &sLen); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "wc_ecc_sig_to_rs failed: %d\n", rc); + } + } + if (rc == 0) { + wolfSPDM_LeftPadToSize(sig, rLen, WOLFSPDM_ECC_KEY_SIZE); + wolfSPDM_LeftPadToSize(sig + WOLFSPDM_ECC_KEY_SIZE, sLen, + WOLFSPDM_ECC_KEY_SIZE); + *sigSz = WOLFSPDM_ECC_POINT_SIZE; + wolfSPDM_DebugPrint(ctx, "Signed hash with P-384 key (sig=%u bytes)\n", + *sigSz); + } + + if (keyInit) { + wc_ecc_free(&sigKey); + } + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} diff --git a/src/spdm/spdm_internal.h b/src/spdm/spdm_internal.h new file mode 100644 index 00000000..a8533fd3 --- /dev/null +++ b/src/spdm/spdm_internal.h @@ -0,0 +1,361 @@ +/* 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 - verify required algorithms */ +#ifndef HAVE_ECC + #error "wolfSPDM requires ECC (--enable-ecc in wolfSSL)" +#endif +#ifndef WOLFSSL_SHA384 + #error "wolfSPDM requires SHA-384 (--enable-sha384 in wolfSSL)" +#endif +#include +#include +#include +#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_CERT 2 /* GET_CERTIFICATE / GET_PUB_KEY complete */ +#define WOLFSPDM_STATE_KEY_EX 3 /* KEY_EXCHANGE complete */ +#define WOLFSPDM_STATE_FINISH 4 /* FINISH complete */ +#define WOLFSPDM_STATE_CONNECTED 5 /* Session established */ +#define WOLFSPDM_STATE_ERROR 6 /* Error state */ + +/* ----- Internal Context Structure ----- */ + +struct WOLFSPDM_CTX { + /* State machine */ + int state; + + /* Boolean flag bit field */ + struct { + unsigned int debug : 1; + unsigned int initialized : 1; + unsigned int isDynamic : 1; /* Set by wolfSPDM_New(), checked by Free */ + unsigned int rngInitialized : 1; + unsigned int ephemeralKeyInit : 1; + unsigned int hasRspPubKey : 1; + unsigned int hasReqKeyPair : 1; + } flags; + + /* Protocol mode */ + WOLFSPDM_MODE mode; + + /* I/O callback */ + WOLFSPDM_IO_CB ioCb; + void* ioUserCtx; + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + /* TCG binding fields (shared by Nuvoton + Nations) */ + word32 connectionHandle; /* Connection handle (usually 0) */ + word16 fipsIndicator; /* FIPS service indicator */ + + /* Host's public key in TPMT_PUBLIC format */ + byte reqPubKeyTPMT[WOLFSPDM_PUBKEY_BUF_SZ / 2]; /* TPMT_PUBLIC (~120 bytes) */ + word32 reqPubKeyTPMTLen; +#endif + +#ifdef WOLFTPM_SPDM_PSK + /* PSK fields */ + byte psk[WOLFSPDM_PSK_MAX_SIZE]; + word32 pskSz; /* pskSz > 0 means PSK is set */ + byte pskHint[WOLFSPDM_PSK_HINT_MAX]; + word32 pskHintSz; +#endif + + /* Random number generator */ + WC_RNG rng; + + /* Negotiated parameters */ + byte maxVersion; /* Runtime max version cap (0 = use compile-time default) */ + byte spdmVersion; /* Negotiated SPDM version */ + + /* 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; + + /* 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[WOLFSPDM_PUBKEY_BUF_SZ / 2]; /* TPMT_PUBLIC or raw X||Y */ + word32 rspPubKeyLen; + + /* Mutual auth fields from KEY_EXCHANGE_RSP */ + byte mutAuthRequested; /* MutAuthRequested from KEY_EXCHANGE_RSP */ + byte reqSlotIdParam; /* ReqSlotIDParam from KEY_EXCHANGE_RSP */ + + /* Requester's identity key pair (for mutual auth) */ + byte reqPrivKey[WOLFSPDM_ECC_KEY_SIZE]; + word32 reqPrivKeyLen; + byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; + +}; + +/* ----- 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 ----- */ +/* tag(2/BE) + size(4/BE) + + * connHandle(4/BE) + fips(2/BE) + reserved(4) */ +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +static WC_INLINE void wolfSPDM_WriteTcgHeader(byte* buf, word16 tag, + word32 totalSz, word32 connHandle, word16 fips) +{ + SPDM_Set16BE(buf, tag); + SPDM_Set32BE(buf + 2, totalSz); + SPDM_Set32BE(buf + 6, connHandle); + SPDM_Set16BE(buf + 10, fips); + XMEMSET(buf + 12, 0, 4); /* Reserved */ +} +#endif + +/* ----- Build IV ----- */ +static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, + word64 seqNum) +{ + byte seq[8]; int i; + XMEMCPY(iv, baseIv, WOLFSPDM_AEAD_IV_SIZE); + SPDM_Set64LE(seq, seqNum); + for (i = 0; i < 8; i++) iv[i] ^= seq[i]; +} + +/* ----- 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) \ + do { \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) == NULL) \ + return WOLFSPDM_E_INVALID_ARG; \ + if (*(bufSz) < (minSz)) \ + return WOLFSPDM_E_BUFFER_SMALL; \ + } while(0) + +#define SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, minSz) \ + do { \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) < (minSz)) \ + return WOLFSPDM_E_INVALID_ARG; \ + } while(0) + +/* ----- 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 ----- */ + +WOLFSPDM_API void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); +WOLFSPDM_API int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); +WOLFSPDM_API int wolfSPDM_Sha384Hash(byte* out, + const byte* d1, word32 d1Sz, + const byte* d2, word32 d2Sz, + const byte* d3, word32 d3Sz); + +/* ----- Internal Function Declarations - Crypto ----- */ + +WOLFSPDM_API int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, + byte* pubKeyX, word32* pubKeyXSz, + byte* pubKeyY, word32* pubKeyYSz); +WOLFSPDM_API int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, + const byte* peerPubKeyX, const byte* peerPubKeyY); +WOLFSPDM_API int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); +WOLFSPDM_API int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, + byte* sig, word32* sigSz); +WOLFSPDM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, + const byte* hash, word32 hashSz, + const byte* sig, word32 sigSz); + +/* ----- Internal Function Declarations - Key Derivation ----- */ + +WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); +WOLFSPDM_API int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, const byte* th1Hash); +WOLFSPDM_API int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, + const char* label, const byte* context, word32 contextSz, + byte* out, word32 outSz); +WOLFSPDM_API int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, + byte* verifyData); + +/* ----- Internal Function Declarations - Message Building ----- */ + +WOLFSPDM_API int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); +WOLFSPDM_API int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFSPDM_API int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFSPDM_API int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +/* PSK message builders/parsers declared in spdm_psk.h */ + +/* ----- Internal Function Declarations - Message Parsing ----- */ + +WOLFSPDM_API int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); + +/* ----- Internal Function Declarations - Secured Messaging ----- */ + +WOLFSPDM_API int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, + const byte* plain, word32 plainSz, + byte* enc, word32* encSz); +WOLFSPDM_API int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, + const byte* enc, word32 encSz, + byte* plain, word32* plainSz); + +/* ----- Internal Utility Functions ----- */ + +WOLFSPDM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz); + +#ifdef DEBUG_WOLFTPM +WOLFSPDM_API void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) +#ifdef __GNUC__ + __attribute__((format(printf, 2, 3))) +#endif + ; + +WOLFSPDM_API void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, + const byte* data, word32 len); +#else +#define wolfSPDM_DebugPrint(ctx, fmt, ...) do { (void)(ctx); (void)fmt; } while(0) +#define wolfSPDM_DebugHex(ctx, label, data, len) do { (void)(ctx); (void)(label); (void)(data); (void)(len); } while(0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_INTERNAL_H */ diff --git a/src/spdm/spdm_kdf.c b/src/spdm/spdm_kdf.c new file mode 100644 index 00000000..0d3bc79c --- /dev/null +++ b/src/spdm/spdm_kdf.c @@ -0,0 +1,264 @@ +/* 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" + +/* SPDM key derivation (DSP0277): HKDF with + * info = Length(2,LE) || "spdm1.2 " || Label || Context. */ + +int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, + const char* label, const byte* context, word32 contextSz, + byte* out, word32 outSz) +{ + byte info[128]; + word32 infoLen = 0; + word32 labelLen; + 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); + + labelLen = (word32)XSTRLEN(label); + + /* Bounds check: 2 + prefix(8) + label + context must fit in info[128] */ + if (2 + SPDM_BIN_CONCAT_PREFIX_LEN + labelLen + contextSz > sizeof(info)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(info + infoLen, prefix, SPDM_BIN_CONCAT_PREFIX_LEN); + infoLen += SPDM_BIN_CONCAT_PREFIX_LEN; + + XMEMCPY(info + infoLen, label, labelLen); + infoLen += labelLen; + + 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_HmacInit(&hmac, NULL, INVALID_DEVID); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wc_HmacSetKey(&hmac, WC_SHA384, finishedKey, WOLFSPDM_HASH_SIZE); + if (rc != 0) { + wc_HmacFree(&hmac); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wc_HmacUpdate(&hmac, thHash, WOLFSPDM_HASH_SIZE); + if (rc != 0) { + wc_HmacFree(&hmac); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wc_HmacFinal(&hmac, verifyData); + wc_HmacFree(&hmac); + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +/* 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); +} + +/* Shared post-Extract: derive HS secrets, finished keys, and data keys from + * ctx->handshakeSecret. Called by both ECDHE and PSK key derivation. */ +int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, + const byte* th1Hash) +{ + int rc; + + /* reqHsSecret = HKDF-Expand(HS, "req hs data" || TH1, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_REQ_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, + ctx->reqHsSecret, WOLFSPDM_HASH_SIZE); + if (rc == WOLFSPDM_SUCCESS) { + /* rspHsSecret = HKDF-Expand(HS, "rsp hs data" || TH1, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_RSP_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, + ctx->rspHsSecret, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Finished keys (used for VerifyData HMAC) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->reqHsSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, + ctx->reqFinishedKey, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->rspHsSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, + ctx->rspFinishedKey, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Data encryption keys + IVs (AES-256-GCM) */ + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->reqHsSecret, + ctx->reqDataKey, ctx->reqDataIv); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->rspHsSecret, + ctx->rspDataKey, ctx->rspDataIv); + } + + return rc; +} + +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; + } + + return wolfSPDM_DeriveFromHandshakeSecret(ctx, th1Hash); +} + +/* PSK key derivation moved to spdm_psk.c */ + +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) { + /* salt = HKDF-Expand(HandshakeSecret, BinConcat("derived"), 48) + * Per DSP0277: "derived" label has NO context (unlike TLS 1.3 which + * uses Hash("")). libspdm confirms: bin_concat("derived", context=NULL) + */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, "derived", NULL, 0, + salt, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* MasterSecret = HKDF-Extract(salt, 0^hashSize) */ + XMEMSET(zeroIkm, 0, sizeof(zeroIkm)); + rc = wc_HKDF_Extract(WC_SHA384, salt, WOLFSPDM_HASH_SIZE, + zeroIkm, WOLFSPDM_HASH_SIZE, masterSecret); + if (rc != 0) { + rc = WOLFSPDM_E_CRYPTO_FAIL; + } + } + if (rc == WOLFSPDM_SUCCESS) { + /* reqAppSecret = HKDF-Expand(MasterSecret, "req app data" || TH2, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, masterSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_REQ_DATA, th2Hash, + WOLFSPDM_HASH_SIZE, reqAppSecret, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* rspAppSecret = HKDF-Expand(MasterSecret, "rsp app data" || TH2, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, masterSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_RSP_DATA, th2Hash, + WOLFSPDM_HASH_SIZE, rspAppSecret, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Derive new encryption keys + IVs from app data secrets */ + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, reqAppSecret, + ctx->reqDataKey, ctx->reqDataIv); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, rspAppSecret, + ctx->rspDataKey, ctx->rspDataIv); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Reset sequence numbers for application phase */ + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + wolfSPDM_DebugPrint(ctx, "App data keys derived, seq nums reset to 0\n"); + } + + /* Always zero sensitive intermediate key material */ + wc_ForceZero(masterSecret, sizeof(masterSecret)); + wc_ForceZero(reqAppSecret, sizeof(reqAppSecret)); + wc_ForceZero(rspAppSecret, sizeof(rspAppSecret)); + wc_ForceZero(salt, sizeof(salt)); + wc_ForceZero(th2Hash, sizeof(th2Hash)); + + return rc; +} diff --git a/src/spdm/spdm_msg.c b/src/spdm/spdm_msg.c new file mode 100644 index 00000000..101b4567 --- /dev/null +++ b/src/spdm/spdm_msg.c @@ -0,0 +1,541 @@ +/* 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" + +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 1.0 */ + buf[0] = SPDM_VERSION_10; + buf[1] = SPDM_GET_VERSION; + buf[2] = 0x00; + buf[3] = 0x00; + *bufSz = 4; + + 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_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) + rc = wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &pubKeyXSz, + pubKeyY, &pubKeyYSz); + + if (rc == WOLFSPDM_SUCCESS) { + XMEMSET(buf, 0, *bufSz); + + /* Use negotiated SPDM version (not hardcoded 1.2) */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_KEY_EXCHANGE; + buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + 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) { + 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 vendor format: 12 bytes */ + buf[offset++] = 0x0c; buf[offset++] = 0x00; + buf[offset++] = 0x00; buf[offset++] = 0x00; + buf[offset++] = 0x05; buf[offset++] = 0x00; + buf[offset++] = 0x01; buf[offset++] = 0x01; + buf[offset++] = 0x01; buf[offset++] = 0x00; + buf[offset++] = 0x10; buf[offset++] = 0x00; + buf[offset++] = 0x00; buf[offset++] = 0x00; +#elif defined(WOLFSPDM_NATIONS) + /* Empty OpaqueData — Nations only accepts OpaqueLength=0 */ + buf[offset++] = 0x00; buf[offset++] = 0x00; +#else + /* Standard SPDM 1.2+ OpaqueData format: 20 bytes */ + buf[offset++] = 0x14; /* OpaqueLength = 20 */ + buf[offset++] = 0x00; + buf[offset++] = 0x01; buf[offset++] = 0x00; /* TotalElements */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Reserved */ + buf[offset++] = 0x00; buf[offset++] = 0x00; + buf[offset++] = 0x09; buf[offset++] = 0x00; /* DataSize */ + buf[offset++] = 0x01; /* Registry ID */ + buf[offset++] = 0x01; /* VendorLen */ + buf[offset++] = 0x03; buf[offset++] = 0x00; /* VersionCount */ + buf[offset++] = 0x10; buf[offset++] = 0x00; /* 1.0 */ + buf[offset++] = 0x11; buf[offset++] = 0x00; /* 1.1 */ + buf[offset++] = 0x12; buf[offset++] = 0x00; /* 1.2 */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding */ +#endif + + *bufSz = offset; + } + } + + return rc; +} + +/* ----- 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 */ + if (contextStrLen > 36) { + return WOLFSPDM_E_INVALID_ARG; + } + 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; +} + +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; + + /* Check arguments first before any ctx dereference */ + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Mutual auth is enabled when the responder requested it (MutAuthRequested + * bit 0) AND we have a requester key pair to sign with */ + if ((ctx->mutAuthRequested & 0x01) && ctx->flags.hasReqKeyPair) { + mutualAuth = 1; + wolfSPDM_DebugPrint(ctx, "FINISH: Mutual auth ENABLED " + "(MutAuth=0x%02x ReqSlot=0x%02x)\n", + ctx->mutAuthRequested, ctx->reqSlotIdParam); + } + + /* Check buffer size: header(4) + [OpaqueLength(2) for 1.4+] + + * [signature(96) for mutual auth] + HMAC(48) */ + { + word32 minSz = 4 + WOLFSPDM_HASH_SIZE; /* header + HMAC */ + if (ctx->spdmVersion >= SPDM_VERSION_14) + minSz += 2; /* OpaqueLength */ + if (mutualAuth) + minSz += WOLFSPDM_ECC_POINT_SIZE; /* Signature */ + if (*bufSz < minSz) + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Build FINISH header */ + buf[0] = ctx->spdmVersion; + buf[1] = SPDM_FINISH; + if (mutualAuth) { + buf[2] = 0x01; /* Param1: Signature field is included */ + /* Param2: For PUB_KEY_ID mode, shall be 0xFF per DSP0274 */ + buf[3] = 0xFF; + } else { + buf[2] = 0x00; /* Param1: No signature */ + buf[3] = 0x00; /* Param2: SlotID = 0 when no signature */ + } + + /* SPDM 1.4 adds OpaqueLength(2) + OpaqueData(var) after header */ + if (ctx->spdmVersion >= SPDM_VERSION_14) { + buf[offset++] = 0x00; /* OpaqueLength = 0 (LE) */ + buf[offset++] = 0x00; + } + + rc = WOLFSPDM_SUCCESS; + + /* Mutual auth: add Hash(Cm_requester) to transcript between message_k + * and FINISH header. For PUB_KEY_ID mode, Cm = SHA-384(TPMT_PUBLIC) + * of the requester's public key (matching how Ct is computed for + * responder per TCG SPDM binding). */ +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (rc == WOLFSPDM_SUCCESS && mutualAuth && ctx->reqPubKeyTPMTLen > 0) { + byte cmHash[WOLFSPDM_HASH_SIZE]; + rc = wolfSPDM_Sha384Hash(cmHash, ctx->reqPubKeyTPMT, + ctx->reqPubKeyTPMTLen, NULL, 0, NULL, 0); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, cmHash, WOLFSPDM_HASH_SIZE); + } +#endif + + /* Add FINISH header to transcript, compute TH2 */ + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, buf, offset); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + if (rc == WOLFSPDM_SUCCESS) + XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); + + /* Mutual auth: sign TH2, add signature to transcript, recompute TH2 */ + if (rc == WOLFSPDM_SUCCESS && mutualAuth) { + byte signMsgHash[WOLFSPDM_HASH_SIZE]; + + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "requester-finish signing", 24, th2Hash, signMsgHash); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SignHash(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, + signature, &sigSz); + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&buf[offset], signature, WOLFSPDM_ECC_POINT_SIZE); + offset += WOLFSPDM_ECC_POINT_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, signature, + WOLFSPDM_ECC_POINT_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + } + + /* RequesterVerifyData = HMAC(reqFinishedKey, TH2) */ + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, + verifyData); + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&buf[offset], verifyData, WOLFSPDM_HASH_SIZE); + offset += WOLFSPDM_HASH_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, verifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) + *bufSz = offset; + + /* Always zero sensitive stack buffers */ + wc_ForceZero(th2Hash, sizeof(th2Hash)); + wc_ForceZero(verifyData, sizeof(verifyData)); + wc_ForceZero(signature, sizeof(signature)); + return rc; +} + +int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + 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; +} + +/* Maximum SPDM version we support. Supports SPDM 1.2 through 1.4. + * Override with -DWOLFSPDM_MAX_SPDM_VERSION at compile time to cap + * at a lower version. */ +#ifndef WOLFSPDM_MAX_SPDM_VERSION +#define WOLFSPDM_MAX_SPDM_VERSION SPDM_VERSION_14 +#endif + +/* Minimum SPDM version we require. Our key derivation uses BinConcat + * format ("spdm1.2 " prefix) which is a 1.2+ feature. SPDM 1.1 uses + * a different HKDF label format and would require separate key + * derivation code. Override at compile time if 1.1 support is added. */ +#ifndef WOLFSPDM_MIN_SPDM_VERSION +#define WOLFSPDM_MIN_SPDM_VERSION SPDM_VERSION_12 +#endif + +int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + word16 entryCount; + word16 maxEntries; + word32 i; + byte highestVersion = 0; /* No version found yet */ + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 6); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_VERSION, WOLFSPDM_E_VERSION_MISMATCH); + + /* Parse VERSION response: + * Offset 4-5: VersionNumberEntryCount (LE) + * Offset 6+: VersionNumberEntry array (2 bytes each, LE) */ + entryCount = SPDM_Get16LE(&buf[4]); + + /* Cap entryCount to what actually fits in the buffer to prevent + * overflow on exotic compilers where i*2 could wrap */ + maxEntries = (word16)((bufSz - 6) / 2); + if (entryCount > maxEntries) { + entryCount = maxEntries; + } + + /* Find highest mutually supported version. + * Per DSP0274, negotiated version must be the highest version + * that both sides support. We support WOLFSPDM_MIN_SPDM_VERSION + * through WOLFSPDM_MAX_SPDM_VERSION (or ctx->maxVersion if set). */ + { + byte maxVer = (ctx->maxVersion != 0) ? ctx->maxVersion + : WOLFSPDM_MAX_SPDM_VERSION; + for (i = 0; i < entryCount; i++) { + /* Each entry is 2 bytes; high byte (offset +1) is Major.Minor */ + byte ver = buf[6 + i * 2 + 1]; + if (ver >= WOLFSPDM_MIN_SPDM_VERSION && + ver <= maxVer && + ver > highestVersion) { + highestVersion = ver; + } + } + } + + /* If no mutually supported version found, fail */ + if (highestVersion == 0) { + wolfSPDM_DebugPrint(ctx, "No mutually supported SPDM version found " + "(require >= 0x%02x)\n", WOLFSPDM_MIN_SPDM_VERSION); + return WOLFSPDM_E_VERSION_MISMATCH; + } + + ctx->spdmVersion = highestVersion; + ctx->state = WOLFSPDM_STATE_VERSION; + + wolfSPDM_DebugPrint(ctx, "Negotiated SPDM version: 0x%02x\n", ctx->spdmVersion); + 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]; + byte th1SigHash[WOLFSPDM_HASH_SIZE]; + byte signMsgHash[WOLFSPDM_HASH_SIZE]; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + const byte* signature; + const byte* rspVerifyData; + 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 and ReqSlotIDParam (offsets 6-7) */ + ctx->mutAuthRequested = buf[6]; + ctx->reqSlotIdParam = buf[7]; + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: MutAuth=0x%02x ReqSlotID=0x%02x\n", + ctx->mutAuthRequested, ctx->reqSlotIdParam); + + /* Extract responder's ephemeral public key (offset 40 = 4+2+1+1+32) */ + XMEMCPY(peerPubKeyX, &buf[40], WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(peerPubKeyY, &buf[88], WOLFSPDM_ECC_KEY_SIZE); + + /* OpaqueLen at offset 136 */ + opaqueLen = SPDM_Get16LE(&buf[136]); + sigOffset = 138 + opaqueLen; + keRspPartialLen = sigOffset; + + if (bufSz < sigOffset + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE) { + 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); + + /* Verify responder signature over TH1 (DSP0274). Responder public key + * must be provisioned before KEY_EXCHANGE. */ + if (rc == WOLFSPDM_SUCCESS && !ctx->flags.hasRspPubKey) { + wolfSPDM_DebugPrint(ctx, "No responder public key set\n"); + rc = WOLFSPDM_E_BAD_STATE; + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th1SigHash); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "responder-key_exchange_rsp signing", 34, + th1SigHash, signMsgHash); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_VerifySignature(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, + signature, WOLFSPDM_ECC_SIG_SIZE); + if (rc != WOLFSPDM_SUCCESS) + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP signature INVALID\n"); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_SIG_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeSharedSecret(ctx, peerPubKeyX, peerPubKeyY); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, ctx->th1); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveHandshakeKeys(ctx, ctx->th1); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, ctx->th1, expectedHmac); + } + if (rc == WOLFSPDM_SUCCESS) { + word32 i; + int diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { + diff |= expectedHmac[i] ^ rspVerifyData[i]; + } + if (diff != 0) { + wolfSPDM_DebugPrint(ctx, "ResponderVerifyData MISMATCH\n"); + rc = WOLFSPDM_E_BAD_HMAC; + } + } + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "ResponderVerifyData VERIFIED OK\n"); + rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_KEY_EX; + } + + wc_ForceZero(expectedHmac, sizeof(expectedHmac)); + wc_ForceZero(th1SigHash, sizeof(th1SigHash)); + wc_ForceZero(signMsgHash, sizeof(signMsgHash)); + return rc; +} + +int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + + if (buf[1] == SPDM_FINISH_RSP) { + int addRc; + word32 rspMsgLen = 4; + + /* SPDM 1.4 adds OpaqueLength(2) + OpaqueData(var) to FINISH_RSP */ + if (ctx->spdmVersion >= SPDM_VERSION_14) { + word16 opaqueLen; + if (bufSz < 6) { + return WOLFSPDM_E_BUFFER_SMALL; + } + opaqueLen = SPDM_Get16LE(&buf[4]); + rspMsgLen = 4 + 2 + opaqueLen; + if (bufSz < rspMsgLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + } + + /* Add FINISH_RSP (header + OpaqueData for 1.4) to transcript */ + addRc = wolfSPDM_TranscriptAdd(ctx, buf, rspMsgLen); + if (addRc != WOLFSPDM_SUCCESS) { + return addRc; + } + 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; +} + +/* PSK message builders/parsers moved to spdm_psk.c */ diff --git a/src/spdm/spdm_nations.c b/src/spdm/spdm_nations.c new file mode 100644 index 00000000..72d314c2 --- /dev/null +++ b/src/spdm/spdm_nations.c @@ -0,0 +1,174 @@ +/* spdm_nations.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 + */ + +/* Nations Technology NS350 SPDM Functions + * + * PSK-mode vendor commands and PSK connection flow. + * Identity key mode uses shared TCG code in spdm_tcg.c. + */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_NATIONS + +#include + +/* ----- Nations PSK-Mode Vendor Commands ----- */ + +int wolfSPDM_Nations_GetStatus(WOLFSPDM_CTX* ctx, + WOLFSPDM_NATIONS_STATUS* status) +{ + WOLFSPDM_VENDOR_RSP rsp; + int rc; + + if (ctx == NULL || status == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMSET(status, 0, sizeof(*status)); + + wolfSPDM_DebugPrint(ctx, "Nations: GET_STS_\n"); + + /* NS350 accepts GET_STATUS with no payload (Type field omitted) */ + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_STS, + NULL, 0, &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugHex(ctx, "GET_STS_ payload", rsp.payload, rsp.payloadSz); + + /* Per TCG spec Table 15 — GET_STATUS_RSP payload: + * [0] SpecMajorVersion, [1] SpecMinorVersion, + * [2] PSKSet (00=NO, 01=YES), + * [3] SPDMOnly (00=DISABLED, 01=ENABLED, 81=PENDING_DISABLE) */ + if (rsp.payloadSz >= 4) { + status->spdmEnabled = 1; + status->pskProvisioned = (rsp.payload[2] != 0); + status->spdmOnlyLocked = (rsp.payload[3] != 0); + wolfSPDM_DebugPrint(ctx, "GET_STS_: v%u.%u PSK=%s SPDMOnly=0x%02x\n", + rsp.payload[0], rsp.payload[1], + status->pskProvisioned ? "YES" : "NO", + rsp.payload[3]); + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock) +{ + byte param[1]; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + param[0] = lock ? WOLFSPDM_SPDMONLY_LOCK : WOLFSPDM_SPDMONLY_UNLOCK; + + wolfSPDM_DebugPrint(ctx, "Nations: SPDMONLY %s\n", + lock ? "LOCK" : "UNLOCK"); + + rc = wolfSPDM_TCG_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_SPDMONLY, + param, sizeof(param)); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "SPDMONLY: Success (Lock=%u)\n", param[0]); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_PskSet(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz) +{ + int rc; + + if (ctx == NULL || psk == NULL || pskSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + wolfSPDM_DebugPrint(ctx, "Nations: PSK_SET_ (%u bytes)\n", pskSz); + + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_NATIONS_VDCODE_PSK_SET, + psk, pskSz, NULL); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "PSK_SET_ failed: %d\n", rc); + return rc; + } + + wolfSPDM_DebugPrint(ctx, "PSK_SET_: Success\n"); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_PskClear(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz) +{ + int rc; + + if (ctx == NULL || clearAuth == NULL || clearAuthSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + wolfSPDM_DebugPrint(ctx, "Nations: PSK_CLR_ (auth=%u bytes)\n", + clearAuthSz); + + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_NATIONS_VDCODE_PSK_CLEAR, + clearAuth, clearAuthSz, NULL); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "PSK_CLR_ failed: %d\n", rc); + return rc; + } + + wolfSPDM_DebugPrint(ctx, "PSK_CLR_: Success\n"); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz) +{ + int rc; + + if (ctx == NULL || clearAuth == NULL || clearAuthSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Full VCA: GET_VERSION + GET_CAPABILITIES + NEGOTIATE_ALGORITHMS */ + rc = wolfSPDM_GetVersion(ctx); + if (rc != WOLFSPDM_SUCCESS) return rc; + + rc = wolfSPDM_TCG_GetCapabilities(ctx, WOLFSPDM_TCG_CAPS_FLAGS_PSK); + if (rc != WOLFSPDM_SUCCESS) return rc; + + rc = wolfSPDM_TCG_NegotiateAlgorithms(ctx); + if (rc != WOLFSPDM_SUCCESS) return rc; + + return wolfSPDM_Nations_PskClear(ctx, clearAuth, clearAuthSz); +} + +/* PSK connection flow moved to spdm_psk.c (wolfSPDM_ConnectPsk). + * wolfSPDM_ConnectNationsPsk is a backward-compat alias in spdm_psk.h. */ + +#endif /* WOLFSPDM_NATIONS */ diff --git a/src/spdm/spdm_nuvoton.c b/src/spdm/spdm_nuvoton.c new file mode 100644 index 00000000..f59e6d13 --- /dev/null +++ b/src/spdm/spdm_nuvoton.c @@ -0,0 +1,112 @@ +/* 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-specific SPDM functions (GetStatus, SetOnlyMode). */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_NUVOTON + +#include + +int wolfSPDM_Nuvoton_GetStatus( + WOLFSPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status) +{ + WOLFSPDM_VENDOR_RSP rsp; + byte statusType[4] = {0x00, 0x00, 0x00, 0x00}; + int rc; + + if (ctx == NULL || status == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMSET(status, 0, sizeof(*status)); + + wolfSPDM_DebugPrint(ctx, "Nuvoton: GET_STS_\n"); + + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_STS, + statusType, sizeof(statusType), &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "GET_STS_: VdCode='%.8s', %u bytes\n", + rsp.vdCode, rsp.payloadSz); + + /* Parse status fields per Nuvoton spec page 9: + * Byte 0: SpecVersionMajor (0 for SPDM 1.x) + * Byte 1: SpecVersionMinor (1 = SPDM 1.1, 3 = SPDM 1.3) + * Byte 2: Reserved + * Byte 3: SPDMOnly lock state (0 = unlocked, 1 = locked) */ + if (rsp.payloadSz >= 4) { + byte specMajor = rsp.payload[0]; + byte specMinor = rsp.payload[1]; + byte spdmOnly = rsp.payload[3]; + + status->specVersionMajor = specMajor; + status->specVersionMinor = specMinor; + status->spdmOnlyLocked = (spdmOnly != 0); + status->spdmEnabled = 1; + status->sessionActive = 0; + + wolfSPDM_DebugPrint(ctx, "GET_STS_: SpecVersion=%u.%u, SPDMOnly=%s\n", + specMajor, specMinor, spdmOnly ? "LOCKED" : "unlocked"); + } else if (rsp.payloadSz >= 1) { + status->spdmOnlyLocked = (rsp.payload[0] != 0); + status->spdmEnabled = 1; + wolfSPDM_DebugPrint(ctx, "GET_STS_: SPDMOnly=%s (minimal response)\n", + status->spdmOnlyLocked ? "LOCKED" : "unlocked"); + } + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nuvoton_SetOnlyMode( + WOLFSPDM_CTX* ctx, + int lock) +{ + byte param[1]; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + param[0] = lock ? WOLFSPDM_SPDMONLY_LOCK : WOLFSPDM_SPDMONLY_UNLOCK; + + wolfSPDM_DebugPrint(ctx, "Nuvoton: SPDMONLY %s\n", + lock ? "LOCK" : "UNLOCK"); + + rc = wolfSPDM_TCG_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_SPDMONLY, + param, sizeof(param)); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "SPDMONLY: Success\n"); + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON */ diff --git a/src/spdm/spdm_psk.c b/src/spdm/spdm_psk.c new file mode 100644 index 00000000..ce1919ff --- /dev/null +++ b/src/spdm/spdm_psk.c @@ -0,0 +1,437 @@ +/* spdm_psk.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 + */ + +/* Shared SPDM PSK protocol code used by Nations (and future Infineon). */ + +#include "spdm_internal.h" + +#ifdef WOLFTPM_SPDM_PSK + +#include +#include + +/* ----- PSK Context Setup ----- */ + +int wolfSPDM_SetPSK(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz) +{ + if (ctx == NULL || psk == NULL || pskSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + if (pskSz > WOLFSPDM_PSK_MAX_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + if (hint != NULL && hintSz > WOLFSPDM_PSK_HINT_MAX) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMCPY(ctx->psk, psk, pskSz); + ctx->pskSz = pskSz; + + if (hint != NULL && hintSz > 0) { + XMEMCPY(ctx->pskHint, hint, hintSz); + ctx->pskHintSz = hintSz; + } else { + XMEMSET(ctx->pskHint, 0, sizeof(ctx->pskHint)); + ctx->pskHintSz = 0; + } + + return WOLFSPDM_SUCCESS; +} + +/* ----- PSK Message Builders/Parsers ----- */ + +int wolfSPDM_BuildPskExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + word32 offset = 0; + int rc; + + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 48); + + if (ctx->pskSz == 0) { + return WOLFSPDM_E_BAD_STATE; + } + + XMEMSET(buf, 0, *bufSz); + + /* Header */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_PSK_EXCHANGE; + buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ + buf[offset++] = 0x00; /* Param2 = Reserved */ + + /* ReqSessionID (2 LE) */ + SPDM_Set16LE(&buf[offset], ctx->reqSessionId); + offset += 2; + + /* PSKHintLength (2 LE) */ + SPDM_Set16LE(&buf[offset], (word16)ctx->pskHintSz); + offset += 2; + + /* RequesterContextLength (2 LE) = 32 */ + SPDM_Set16LE(&buf[offset], WOLFSPDM_RANDOM_SIZE); + offset += 2; + + /* OpaqueDataLength (2 LE) = 0 */ + SPDM_Set16LE(&buf[offset], 0); + offset += 2; + + /* PSKHint */ + if (ctx->pskHintSz > 0) { + if (offset + ctx->pskHintSz > *bufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(&buf[offset], ctx->pskHint, ctx->pskHintSz); + offset += ctx->pskHintSz; + } + + /* RequesterContext (32 random bytes) */ + if (offset + WOLFSPDM_RANDOM_SIZE > *bufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + rc = wolfSPDM_GetRandom(ctx, &buf[offset], WOLFSPDM_RANDOM_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + offset += WOLFSPDM_RANDOM_SIZE; + + /* OpaqueData - none */ + + *bufSz = offset; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz) +{ + word16 rspContextLen, opaqueLen; + word32 verifyOffset; + word32 rspPartialLen; + byte th1Hash[WOLFSPDM_HASH_SIZE]; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + const byte* rspVerifyData; + int rc; + + /* Minimum: header(4) + RspSessionID(2) + Reserved(1) + RspContextLen(2) + + * OpaqueLen(2) + VerifyData(48) = 59 */ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 59); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_PSK_EXCHANGE_RSP, + WOLFSPDM_E_KEY_EXCHANGE); + + /* Per SPDM 1.3 DSP0274 Table 65: + * [4-5] RspSessionID, [6] MutAuthRequested, [7] ReqSlotIDParam, + * [8-9] RspContextLength, [10-11] OpaqueDataLength */ + ctx->rspSessionId = SPDM_Get16LE(&buf[4]); + ctx->sessionId = (word32)ctx->reqSessionId | + ((word32)ctx->rspSessionId << 16); + + rspContextLen = SPDM_Get16LE(&buf[8]); + opaqueLen = SPDM_Get16LE(&buf[10]); + + verifyOffset = 12 + rspContextLen + opaqueLen; + rspPartialLen = verifyOffset; + + if (bufSz < verifyOffset + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + rspVerifyData = buf + verifyOffset; + + /* Add PSK_EXCHANGE_RSP (without VerifyData) to transcript */ + rc = wolfSPDM_TranscriptAdd(ctx, buf, rspPartialLen); + + /* Compute TH1 and derive handshake keys from PSK BEFORE verifying */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th1Hash); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->th1, th1Hash, WOLFSPDM_HASH_SIZE); + rc = wolfSPDM_DeriveHandshakeKeysPsk(ctx, th1Hash); + } + + /* Verify ResponderVerifyData = HMAC(rspFinishedKey, TH1) */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, th1Hash, + expectedHmac); + } + if (rc == WOLFSPDM_SUCCESS) { + word32 i; + int diff = 0; + wolfSPDM_DebugHex(ctx, "Expected HMAC", expectedHmac, + WOLFSPDM_HASH_SIZE); + wolfSPDM_DebugHex(ctx, "Received HMAC", rspVerifyData, + WOLFSPDM_HASH_SIZE); + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { + diff |= expectedHmac[i] ^ rspVerifyData[i]; + } + if (diff != 0) { + wolfSPDM_DebugPrint(ctx, "PSK ResponderVerifyData MISMATCH\n"); + rc = WOLFSPDM_E_BAD_HMAC; + } + } + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "PSK ResponderVerifyData VERIFIED OK\n"); + rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_KEY_EX; + } + + wc_ForceZero(expectedHmac, sizeof(expectedHmac)); + wc_ForceZero(th1Hash, sizeof(th1Hash)); + return rc; +} + +int wolfSPDM_BuildPskFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + byte th2Hash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + word32 offset = 0; + int rc; + + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* PSK_FINISH = header(4) + VerifyData(48) = 52 bytes */ + if (*bufSz < 4 + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Header */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_PSK_FINISH; + buf[offset++] = 0x00; /* Param1 */ + buf[offset++] = 0x00; /* Param2 */ + + /* Add PSK_FINISH header to transcript, compute TH2 */ + rc = wolfSPDM_TranscriptAdd(ctx, buf, offset); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); + } + + /* RequesterVerifyData = HMAC(reqFinishedKey, TH2) */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, + verifyData); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&buf[offset], verifyData, WOLFSPDM_HASH_SIZE); + offset += WOLFSPDM_HASH_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, verifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + *bufSz = offset; + } + + wc_ForceZero(th2Hash, sizeof(th2Hash)); + wc_ForceZero(verifyData, sizeof(verifyData)); + return rc; +} + +int wolfSPDM_ParsePskFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + + if (buf[1] == SPDM_PSK_FINISH_RSP) { + int addRc = wolfSPDM_TranscriptAdd(ctx, buf, 4); + if (addRc != WOLFSPDM_SUCCESS) { + return addRc; + } + ctx->state = WOLFSPDM_STATE_FINISH; + wolfSPDM_DebugPrint(ctx, "PSK_FINISH_RSP received\n"); + return WOLFSPDM_SUCCESS; + } + + if (buf[1] == SPDM_ERROR) { + wolfSPDM_DebugPrint(ctx, "PSK_FINISH error: 0x%02x\n", buf[2]); + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_E_BAD_STATE; +} + +/* ----- PSK Key Derivation ----- */ + +int wolfSPDM_DeriveHandshakeKeysPsk(WOLFSPDM_CTX* ctx, const byte* th1Hash) +{ + byte salt[WOLFSPDM_HASH_SIZE]; + int rc; + + if (ctx == NULL || th1Hash == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (ctx->pskSz == 0) { + return WOLFSPDM_E_BAD_STATE; + } + + /* PSK mode: Salt_0 = 0xFF-filled (per TCG PSK specification). */ + XMEMSET(salt, 0xFF, sizeof(salt)); + + /* HandshakeSecret = HKDF-Extract(0xFF-salt, PSK) */ + rc = wc_HKDF_Extract(WC_SHA384, salt, sizeof(salt), + ctx->psk, ctx->pskSz, ctx->handshakeSecret); + if (rc != 0) { + wc_ForceZero(ctx->psk, sizeof(ctx->psk)); + ctx->pskSz = 0; + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wolfSPDM_DeriveFromHandshakeSecret(ctx, th1Hash); + + /* Zero PSK immediately after key derivation */ + wc_ForceZero(ctx->psk, sizeof(ctx->psk)); + ctx->pskSz = 0; + + return rc; +} + +/* ----- Shared PSK Connection Flow ----- */ + +/* GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> + * PSK_EXCHANGE -> PSK_FINISH -> app key derivation */ +int wolfSPDM_ConnectPsk(WOLFSPDM_CTX* ctx) +{ + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->pskSz == 0) { + wolfSPDM_DebugPrint(ctx, "PSK: No PSK set\n"); + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugPrint(ctx, "PSK: Starting SPDM connection\n"); + + ctx->state = WOLFSPDM_STATE_INIT; + wolfSPDM_TranscriptReset(ctx); + + /* Step 1: GET_VERSION */ + SPDM_CONNECT_STEP(ctx, "PSK Step 1: GET_VERSION\n", + wolfSPDM_GetVersion(ctx)); + + /* Step 2: GET_CAPABILITIES (with PSK_CAP flag) */ + SPDM_CONNECT_STEP(ctx, "PSK Step 2: GET_CAPABILITIES\n", + wolfSPDM_TCG_GetCapabilities(ctx, WOLFSPDM_TCG_CAPS_FLAGS_PSK)); + + /* Step 3: NEGOTIATE_ALGORITHMS */ + SPDM_CONNECT_STEP(ctx, "PSK Step 3: NEGOTIATE_ALGORITHMS\n", + wolfSPDM_TCG_NegotiateAlgorithms(ctx)); + + /* Step 4: PSK_EXCHANGE / PSK_EXCHANGE_RSP */ + { + byte txBuf[128]; + byte rxBuf[WOLFSPDM_VENDOR_RX_SZ]; + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + + wolfSPDM_DebugPrint(ctx, "PSK Step 4: PSK_EXCHANGE\n"); + rc = wolfSPDM_BuildPskExchange(ctx, txBuf, &txSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_ParsePskExchangeRsp(ctx, rxBuf, rxSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } + + /* Step 5: PSK_FINISH / PSK_FINISH_RSP (encrypted) */ + { + byte finBuf[64]; + byte encBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte decBuf[64]; + word32 finSz = sizeof(finBuf); + word32 encSz = sizeof(encBuf); + word32 rxSz = sizeof(rxBuf); + word32 decSz = sizeof(decBuf); + + wolfSPDM_DebugPrint(ctx, "PSK Step 5: PSK_FINISH\n"); + rc = wolfSPDM_BuildPskFinish(ctx, finBuf, &finSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_EncryptInternal(ctx, finBuf, finSz, encBuf, &encSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_ParsePskFinishRsp(ctx, decBuf, decSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } + + /* Derive application data keys */ + rc = wolfSPDM_DeriveAppDataKeys(ctx); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + + ctx->state = WOLFSPDM_STATE_CONNECTED; + wolfSPDM_DebugPrint(ctx, "PSK: SPDM Session Established! " + "SessionID=0x%08x\n", ctx->sessionId); + + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFTPM_SPDM_PSK */ diff --git a/src/spdm/spdm_secured.c b/src/spdm/spdm_secured.c new file mode 100644 index 00000000..55c82ce0 --- /dev/null +++ b/src/spdm/spdm_secured.c @@ -0,0 +1,354 @@ +/* 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" + +/* + * 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) + */ + +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 aesInit = 0; + int rc; + + if (ctx == NULL || plain == NULL || enc == NULL || encSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (plainSz > WOLFSPDM_MAX_MSG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + /* Nuvoton TCG binding format per Rev 1.11 spec page 25: + * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes + * IV XOR: Leftmost 8 bytes (bytes 0-7) with 8-byte LE sequence number + */ + word16 appDataLen = (word16)plainSz; + + word16 unpadded = (word16)(2 + appDataLen); + word16 padLen = (word16)((16 - (unpadded % 16)) % 16); + word16 encPayloadSz = (word16)(unpadded + padLen); + + plainBufSz = encPayloadSz; + /* Length field = ciphertext + MAC + * (per Nuvoton spec page 25: Length=160=144+16) */ + recordLen = (word16)(encPayloadSz + WOLFSPDM_AEAD_TAG_SIZE); + hdrSz = 14; /* 4 + 8 + 2 (TCG binding format) */ + + if (*encSz < hdrSz + plainBufSz + WOLFSPDM_AEAD_TAG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Build plaintext: AppDataLength(2 LE) || SPDM message || RandomData */ + SPDM_Set16LE(plainBuf, appDataLen); + XMEMCPY(&plainBuf[2], plain, plainSz); + /* Fill RandomData with actual random bytes per Nuvoton spec */ + if (padLen > 0) { + rc = wolfSPDM_GetRandom(ctx, &plainBuf[unpadded], padLen); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + } + + /* Build header/AAD: SessionID(4 LE) + SeqNum(8 LE) + + * Length(2 LE) = 14 bytes */ + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set64LE(&enc[4], ctx->reqSeqNum); + SPDM_Set16LE(&enc[12], recordLen); + + aadSz = 14; + XMEMCPY(aad, enc, aadSz); + } else +#endif + { + /* MCTP format (per DSP0277): + * Plaintext: AppDataLen(2 LE) + MCTP header(0x05) + SPDM message + * Header: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) = 8 bytes + * AAD = Header + */ + word16 appDataLen = (word16)(1 + plainSz); + word16 encDataLen = (word16)(2 + appDataLen); + + 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); + + /* AES-GCM encrypt — cascade with single cleanup */ + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc == 0) { + aesInit = 1; + rc = wc_AesGcmSetKey(&aes, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + } + if (rc == 0) { + rc = wc_AesGcmEncrypt(&aes, &enc[hdrSz], plainBuf, plainBufSz, + iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); + } + if (aesInit) { + wc_AesFree(&aes); + } + + if (rc == 0) { + XMEMCPY(&enc[hdrSz + plainBufSz], tag, WOLFSPDM_AEAD_TAG_SIZE); + *encSz = hdrSz + plainBufSz + WOLFSPDM_AEAD_TAG_SIZE; + ctx->reqSeqNum++; + wolfSPDM_DebugPrint(ctx, "Encrypted %u bytes -> %u bytes (seq=%llu)\n", + plainSz, *encSz, (unsigned long long)(ctx->reqSeqNum - 1)); + } + + wc_ForceZero(plainBuf, sizeof(plainBuf)); + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +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 cipherLen; + word16 appDataLen; + word32 hdrSz; + word32 aadSz; + int aesInit = 0; + int ret; + int rc; + + if (ctx == NULL || enc == NULL || plain == NULL || plainSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* ----- Transport-specific header parsing ----- */ + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + word64 rspSeqNum64; + word32 rspSessionId; + word16 rspLen; + hdrSz = 14; + aadSz = 14; + + if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) + return WOLFSPDM_E_BUFFER_SMALL; + + rspSessionId = SPDM_Get32LE(&enc[0]); + rspSeqNum64 = SPDM_Get64LE(&enc[4]); + rspLen = SPDM_Get16LE(&enc[12]); + + if (rspSessionId != ctx->sessionId) { + wolfSPDM_DebugPrint(ctx, "Session ID mismatch: 0x%08x != 0x%08x\n", + rspSessionId, ctx->sessionId); + return WOLFSPDM_E_SESSION_INVALID; + } + if (rspSeqNum64 != ctx->rspSeqNum) { + wolfSPDM_DebugPrint(ctx, "Seq mismatch: %llu != %llu\n", + (unsigned long long)rspSeqNum64, + (unsigned long long)ctx->rspSeqNum); + return WOLFSPDM_E_SEQUENCE; + } + if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < hdrSz + rspLen) + return WOLFSPDM_E_BUFFER_SMALL; + + cipherLen = (word32)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + if (cipherLen > sizeof(decrypted)) + return WOLFSPDM_E_BUFFER_SMALL; + + ciphertext = enc + hdrSz; + tag = enc + hdrSz + cipherLen; + XMEMCPY(aad, enc, aadSz); + wolfSPDM_BuildIV(iv, ctx->rspDataIv, rspSeqNum64); + } else +#endif + { + word32 rspSessionId; + word16 rspSeqNum, rspLen; + hdrSz = 8; + aadSz = 8; + + if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) + return WOLFSPDM_E_BUFFER_SMALL; + + rspSessionId = SPDM_Get32LE(&enc[0]); + rspSeqNum = SPDM_Get16LE(&enc[4]); + rspLen = SPDM_Get16LE(&enc[6]); + + if (rspSessionId != ctx->sessionId) { + wolfSPDM_DebugPrint(ctx, "Session ID mismatch: 0x%08x != 0x%08x\n", + rspSessionId, ctx->sessionId); + return WOLFSPDM_E_SESSION_INVALID; + } + if ((word64)rspSeqNum != ctx->rspSeqNum) { + wolfSPDM_DebugPrint(ctx, "Seq mismatch: %u != %llu\n", + rspSeqNum, (unsigned long long)ctx->rspSeqNum); + return WOLFSPDM_E_SEQUENCE; + } + if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < (word32)(hdrSz + rspLen)) + return WOLFSPDM_E_BUFFER_SMALL; + + cipherLen = (word32)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + if (cipherLen > sizeof(decrypted)) + return WOLFSPDM_E_BUFFER_SMALL; + + ciphertext = enc + hdrSz; + tag = enc + hdrSz + cipherLen; + XMEMCPY(aad, enc, aadSz); + wolfSPDM_BuildIV(iv, ctx->rspDataIv, (word64)rspSeqNum); + } + + /* ----- AES-GCM decrypt (shared for both transports) ----- */ + + ret = WOLFSPDM_E_CRYPTO_FAIL; + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc == 0) { + aesInit = 1; + rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + } + if (rc == 0) { + rc = wc_AesGcmDecrypt(&aes, decrypted, ciphertext, cipherLen, + iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, + aad, aadSz); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "AES-GCM decrypt failed: %d\n", rc); + ret = WOLFSPDM_E_DECRYPT_FAIL; + } + } + if (aesInit) { + wc_AesFree(&aes); + } + + /* ----- Parse decrypted payload ----- */ + + if (rc == 0) { + appDataLen = SPDM_Get16LE(decrypted); +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + /* TCG binding: AppDataLen(2) || SPDM msg || RandomData */ + if (cipherLen < (word32)(2 + appDataLen) || + *plainSz < appDataLen) { + ret = WOLFSPDM_E_BUFFER_SMALL; + } else { + XMEMCPY(plain, &decrypted[2], appDataLen); + *plainSz = appDataLen; + ret = WOLFSPDM_SUCCESS; + } + } else +#endif + { + /* MCTP: AppDataLen(2) || MCTP(1) || SPDM msg */ + if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen) || + *plainSz < (word32)(appDataLen - 1)) { + ret = WOLFSPDM_E_BUFFER_SMALL; + } else { + XMEMCPY(plain, &decrypted[3], appDataLen - 1); + *plainSz = appDataLen - 1; + ret = WOLFSPDM_SUCCESS; + } + } + } + + if (ret == WOLFSPDM_SUCCESS) { + ctx->rspSeqNum++; + wolfSPDM_DebugPrint(ctx, "Decrypted %u bytes -> %u bytes\n", + encSz, *plainSz); + } + + wc_ForceZero(decrypted, sizeof(decrypted)); + return ret; +} + +int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz) +{ + byte encBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + 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) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); + } + + return rc; +} diff --git a/src/spdm/spdm_session.c b/src/spdm/spdm_session.c new file mode 100644 index 00000000..82fc0c88 --- /dev/null +++ b/src/spdm/spdm_session.c @@ -0,0 +1,148 @@ +/* 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" + +/* 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) { + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = parseFn(ctx, rxBuf, rxSz); + } + + return rc; +} + +/* 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_KeyExchange(WOLFSPDM_CTX* ctx) +{ + byte txBuf[WOLFSPDM_KEY_EX_TX_SZ]; + byte rxBuf[WOLFSPDM_KEY_EX_RX_SZ]; + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + int rc; + + rc = wolfSPDM_BuildKeyExchange(ctx, txBuf, &txSz); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE: SendReceive failed: %d\n", rc); + } + } + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: received %u bytes\n", rxSz); + rc = wolfSPDM_ParseKeyExchangeRsp(ctx, rxBuf, rxSz); + } + + return rc; +} + +int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) +{ + byte finishBuf[WOLFSPDM_FINISH_BUF_SZ]; + byte encBuf[WOLFSPDM_VENDOR_BUF_SZ]; + byte rxBuf[128]; /* Encrypted FINISH_RSP: ~94 bytes max */ + byte decBuf[64]; /* Decrypted FINISH_RSP: 4 hdr + 48 verify = 52 */ + word32 finishSz = sizeof(finishBuf); + word32 encSz = sizeof(encBuf); + word32 rxSz = sizeof(rxBuf); + word32 decSz = sizeof(decBuf); + int rc; + + rc = wolfSPDM_BuildFinish(ctx, finishBuf, &finishSz); + + /* FINISH must be sent encrypted (HANDSHAKE_IN_THE_CLEAR not negotiated) */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_EncryptInternal(ctx, finishBuf, finishSz, encBuf, + &encSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); + } + + /* Check for unencrypted SPDM error response */ + if (rc == WOLFSPDM_SUCCESS && + rxSz >= 2 && rxBuf[0] >= 0x10 && rxBuf[0] <= 0x1F) { + #ifdef DEBUG_WOLFTPM + if (rxBuf[1] == 0x7F) { + byte errCode = (rxSz >= 3) ? rxBuf[2] : 0xFF; + wolfSPDM_DebugPrint(ctx, "FINISH: SPDM ERROR 0x%02x\n", errCode); + } + #endif + rc = WOLFSPDM_E_PEER_ERROR; + } + + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseFinishRsp(ctx, decBuf, decSz); + } + + /* Derive application data keys (transition from handshake to app phase) */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveAppDataKeys(ctx); + } + + /* Always zero sensitive stack buffers */ + wc_ForceZero(finishBuf, sizeof(finishBuf)); + wc_ForceZero(decBuf, sizeof(decBuf)); + return rc; +} diff --git a/src/spdm/spdm_tcg.c b/src/spdm/spdm_tcg.c new file mode 100644 index 00000000..57db8a5f --- /dev/null +++ b/src/spdm/spdm_tcg.c @@ -0,0 +1,572 @@ +/* spdm_tcg.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 + */ + +/* Shared TCG SPDM code used by both Nuvoton and Nations Technology TPMs. */ + +#include "spdm_internal.h" + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + +#include + +/* ----- Vendor Command Helpers ----- */ + +int wolfSPDM_TCG_VendorCmdClear(WOLFSPDM_CTX* ctx, const char* vdCode, + const byte* payload, word32 payloadSz, WOLFSPDM_VENDOR_RSP* rsp) +{ + byte spdmMsg[WOLFSPDM_VENDOR_BUF_SZ]; + int spdmMsgSz; + byte rxBuf[WOLFSPDM_VENDOR_RX_SZ]; + word32 rxSz; + int rc; + + { + byte ver = ctx->spdmVersion ? ctx->spdmVersion : SPDM_VERSION_13; + spdmMsgSz = wolfSPDM_BuildVendorDefined(ver, vdCode, payload, + payloadSz, spdmMsg, sizeof(spdmMsg)); + } + if (spdmMsgSz < 0) { + return spdmMsgSz; + } + + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SendReceive(ctx, spdmMsg, (word32)spdmMsgSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + if (rxSz >= 4 && rxBuf[1] == SPDM_ERROR) { + wolfSPDM_DebugPrint(ctx, "%s: SPDM ERROR 0x%02x 0x%02x\n", + vdCode, rxBuf[2], rxBuf[3]); + return WOLFSPDM_E_PEER_ERROR; + } + + if (rsp != NULL) { + rsp->payloadSz = sizeof(rsp->payload); + XMEMSET(rsp->vdCode, 0, sizeof(rsp->vdCode)); + rc = wolfSPDM_ParseVendorDefined(rxBuf, rxSz, + rsp->vdCode, rsp->payload, &rsp->payloadSz); + if (rc < 0) { + return rc; + } + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_TCG_VendorCmdSecured(WOLFSPDM_CTX* ctx, const char* vdCode, + const byte* payload, word32 payloadSz) +{ + byte spdmMsg[WOLFSPDM_VENDOR_BUF_SZ]; + int spdmMsgSz; + byte decBuf[WOLFSPDM_VENDOR_BUF_SZ]; + word32 decSz; + int rc; + + { + byte ver = ctx->spdmVersion ? ctx->spdmVersion : SPDM_VERSION_13; + spdmMsgSz = wolfSPDM_BuildVendorDefined(ver, 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; + } + + totalSz = WOLFSPDM_TCG_HEADER_SIZE + spdmPayloadSz; + + if (outBufSz < totalSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + wolfSPDM_WriteTcgHeader(outBuf, WOLFSPDM_TCG_TAG_CLEAR, totalSz, + ctx->connectionHandle, ctx->fipsIndicator); + XMEMCPY(outBuf + WOLFSPDM_TCG_HEADER_SIZE, spdmPayload, spdmPayloadSz); + + return (int)totalSz; +} + +int wolfSPDM_ParseTcgClearMessage( + const byte* inBuf, word32 inBufSz, + byte* spdmPayload, word32* spdmPayloadSz, + WOLFSPDM_TCG_CLEAR_HDR* hdr) +{ + word16 tag; + word32 msgSize; + word32 payloadSz; + + if (inBuf == NULL || spdmPayload == NULL || spdmPayloadSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (inBufSz < WOLFSPDM_TCG_HEADER_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + tag = SPDM_Get16BE(inBuf); + if (tag != WOLFSPDM_TCG_TAG_CLEAR) { + return WOLFSPDM_E_PEER_ERROR; + } + + msgSize = SPDM_Get32BE(inBuf + 2); + if (msgSize < WOLFSPDM_TCG_HEADER_SIZE || msgSize > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + payloadSz = msgSize - WOLFSPDM_TCG_HEADER_SIZE; + if (*spdmPayloadSz < payloadSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + if (hdr != NULL) { + hdr->tag = tag; + hdr->size = msgSize; + hdr->connectionHandle = SPDM_Get32BE(inBuf + 6); + hdr->fipsIndicator = SPDM_Get16BE(inBuf + 10); + hdr->reserved = SPDM_Get32BE(inBuf + 12); + } + + XMEMCPY(spdmPayload, inBuf + WOLFSPDM_TCG_HEADER_SIZE, payloadSz); + *spdmPayloadSz = payloadSz; + + return (int)payloadSz; +} + +/* ----- SPDM Vendor Defined Message Helpers ----- */ + +int wolfSPDM_BuildVendorDefined( + byte spdmVersion, + const char* vdCode, + const byte* payload, word32 payloadSz, + byte* outBuf, word32 outBufSz) +{ + word32 totalSz; + word32 offset = 0; + + if (vdCode == NULL || outBuf == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* SPDM VENDOR_DEFINED_REQUEST format: + * SPDMVersion(1) + reqRspCode(1) + param1(1) + param2(1) + + * standardId(2/LE) + vendorIdLen(1) + reqLength(2/LE) + + * vdCode(8) + payload */ + totalSz = 1 + 1 + 1 + 1 + 2 + 1 + 2 + WOLFSPDM_VDCODE_LEN + payloadSz; + + if (outBufSz < totalSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + outBuf[offset++] = spdmVersion; + outBuf[offset++] = SPDM_VENDOR_DEFINED_REQUEST; + outBuf[offset++] = 0x00; + outBuf[offset++] = 0x00; + /* Standard ID (0x0001 = TCG, little-endian) */ + SPDM_Set16LE(outBuf + offset, 0x0001); + offset += 2; + /* Vendor ID Length (0 for TCG) */ + outBuf[offset++] = 0x00; + /* Request Length (vdCode + payload, little-endian) */ + SPDM_Set16LE(outBuf + offset, (word16)(WOLFSPDM_VDCODE_LEN + payloadSz)); + offset += 2; + /* VdCode (8-byte ASCII) */ + XMEMCPY(outBuf + offset, vdCode, WOLFSPDM_VDCODE_LEN); + offset += WOLFSPDM_VDCODE_LEN; + /* Payload */ + if (payload != NULL && payloadSz > 0) { + XMEMCPY(outBuf + offset, payload, payloadSz); + offset += payloadSz; + } + + return (int)offset; +} + +int wolfSPDM_ParseVendorDefined( + const byte* inBuf, word32 inBufSz, + char* vdCode, + byte* payload, word32* payloadSz) +{ + word32 offset = 0; + word16 reqLength; + word32 dataLen; + byte vendorIdLen; + + if (inBuf == NULL || vdCode == NULL || payload == NULL || + payloadSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Minimum: version(1) + code(1) + param1(1) + param2(1) + stdId(2/LE) + + * vidLen(1) + reqLen(2/LE) + vdCode(8) = 17 */ + if (inBufSz < 17) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + offset += 1; /* SPDM version */ + offset += 3; /* request/response code + params */ + offset += 2; /* standard ID */ + vendorIdLen = inBuf[offset]; + offset += 1 + vendorIdLen; + + if (offset + 2 > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + reqLength = SPDM_Get16LE(inBuf + offset); + offset += 2; + + if (reqLength < WOLFSPDM_VDCODE_LEN) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + if (offset + reqLength > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(vdCode, inBuf + offset, WOLFSPDM_VDCODE_LEN); + vdCode[WOLFSPDM_VDCODE_LEN] = '\0'; + offset += WOLFSPDM_VDCODE_LEN; + + dataLen = reqLength - WOLFSPDM_VDCODE_LEN; + if (*payloadSz < dataLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + if (dataLen > 0) { + XMEMCPY(payload, inBuf + offset, dataLen); + } + *payloadSz = dataLen; + + return (int)dataLen; +} + +/* ----- Shared TCG SPDM Functions ----- */ + +int wolfSPDM_TCG_GetPubKey( + WOLFSPDM_CTX* ctx, + byte* pubKey, word32* pubKeySz) +{ + WOLFSPDM_VENDOR_RSP rsp; + int rc; + + if (ctx == NULL || pubKey == NULL || pubKeySz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + wolfSPDM_DebugPrint(ctx, "TCG: GET_PUBK\n"); + + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_PUBK, + NULL, 0, &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + if (XMEMCMP(rsp.vdCode, WOLFSPDM_VDCODE_GET_PUBK, + WOLFSPDM_VDCODE_LEN) != 0) { + wolfSPDM_DebugPrint(ctx, "GET_PUBK: Unexpected VdCode '%.8s'\n", + rsp.vdCode); + return WOLFSPDM_E_PEER_ERROR; + } + + wolfSPDM_DebugPrint(ctx, "GET_PUBK: Got TPMT_PUBLIC (%u bytes)\n", + rsp.payloadSz); + + if (*pubKeySz < rsp.payloadSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(pubKey, rsp.payload, rsp.payloadSz); + *pubKeySz = rsp.payloadSz; + + /* Store for cert_chain_buffer_hash computation */ + 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_TCG_GivePubKey( + WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz) +{ + int rc; + + if (ctx == NULL || pubKey == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state < WOLFSPDM_STATE_KEY_EX) { + return WOLFSPDM_E_BAD_STATE; + } + + wolfSPDM_DebugPrint(ctx, "TCG: GIVE_PUB (%u bytes)\n", pubKeySz); + + rc = wolfSPDM_TCG_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_GIVE_PUB, + pubKey, pubKeySz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GIVE_PUB: SecuredExchange failed %d\n", rc); + return rc; + } + + wolfSPDM_DebugPrint(ctx, "GIVE_PUB: Success\n"); + return WOLFSPDM_SUCCESS; +} + +/* ----- Shared GET_CAPABILITIES + NEGOTIATE_ALGORITHMS ----- */ + +int wolfSPDM_TCG_GetCapabilities(WOLFSPDM_CTX* ctx, word32 capsFlags) +{ + byte capsReq[20]; + byte capsRsp[64]; + word32 capsRspSz = sizeof(capsRsp); + word32 off = 0; + int rc; + + capsReq[off++] = ctx->spdmVersion; + capsReq[off++] = 0xE1; /* GET_CAPABILITIES */ + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + /* Reserved(1) + CTExponent(1) + Reserved(2) */ + capsReq[off++] = 0x00; capsReq[off++] = 0x1F; + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + /* Flags (4 bytes LE) */ + SPDM_Set32LE(capsReq + off, capsFlags); + off += 4; + /* DataTransferSize */ + capsReq[off++] = 0xC0; capsReq[off++] = 0x07; + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + /* MaxSPDMmsgSize */ + capsReq[off++] = 0xC0; capsReq[off++] = 0x07; + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + + wolfSPDM_DebugPrint(ctx, "TCG: GET_CAPABILITIES\n"); + rc = wolfSPDM_TranscriptAdd(ctx, capsReq, off); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SendReceive(ctx, capsReq, off, capsRsp, &capsRspSz); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, capsRsp, capsRspSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + } + return rc; +} + +int wolfSPDM_TCG_NegotiateAlgorithms(WOLFSPDM_CTX* ctx) +{ + /* Algorithm Set B: P-384/SHA-384/AES-256-GCM */ + byte algReq[48]; + byte algRsp[128]; + word32 algRspSz = sizeof(algRsp); + word32 off = 0; + int rc; + + algReq[off++] = ctx->spdmVersion; + algReq[off++] = 0xE3; /* NEGOTIATE_ALGORITHMS */ + algReq[off++] = 0x04; /* Param1: NumAlgStructs = 4 */ + algReq[off++] = 0x00; + algReq[off++] = 0x30; algReq[off++] = 0x00; /* Length = 48 */ + algReq[off++] = 0x00; algReq[off++] = 0x02; /* MeasurementSpec + Reserved */ + /* BaseAsymAlgo: ECDSA_ECC_NIST_P384 */ + algReq[off++] = 0x80; algReq[off++] = 0x00; + algReq[off++] = 0x00; algReq[off++] = 0x00; + /* BaseHashAlgo: SHA_384 */ + algReq[off++] = 0x02; algReq[off++] = 0x00; + algReq[off++] = 0x00; algReq[off++] = 0x00; + /* Reserved (16 bytes) */ + XMEMSET(&algReq[off], 0, 16); off += 16; + /* AlgStruct[0]: DHE = SECP_384_R1 */ + algReq[off++] = 0x02; algReq[off++] = 0x20; + algReq[off++] = 0x10; algReq[off++] = 0x00; + /* AlgStruct[1]: AEAD = AES_256_GCM */ + algReq[off++] = 0x03; algReq[off++] = 0x20; + algReq[off++] = 0x02; algReq[off++] = 0x00; + /* AlgStruct[2]: ReqBaseAsymAlg = ECDSA_P384 */ + algReq[off++] = 0x04; algReq[off++] = 0x20; + algReq[off++] = 0x80; algReq[off++] = 0x00; + /* AlgStruct[3]: KeySchedule = SPDM */ + algReq[off++] = 0x05; algReq[off++] = 0x20; + algReq[off++] = 0x01; algReq[off++] = 0x00; + + wolfSPDM_DebugPrint(ctx, "TCG: NEGOTIATE_ALGORITHMS\n"); + rc = wolfSPDM_TranscriptAdd(ctx, algReq, off); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SendReceive(ctx, algReq, off, algRsp, &algRspSz); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, algRsp, algRspSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + } + return rc; +} + +/* ----- TCG SPDM Connection Flow ----- */ + +/* GET_VERSION -> [GET_CAPS -> NEG_ALGO] -> GET_PUBK -> KEY_EXCHANGE -> + * GIVE_PUB -> FINISH */ +int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx) +{ + int rc; + byte pubKey[WOLFSPDM_PUBKEY_BUF_SZ]; + word32 pubKeySz; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugPrint(ctx, "TCG: Starting SPDM connection\n"); + + ctx->state = WOLFSPDM_STATE_INIT; + wolfSPDM_TranscriptReset(ctx); + + /* Step 1: GET_VERSION */ + SPDM_CONNECT_STEP(ctx, "TCG Step 1: GET_VERSION\n", + wolfSPDM_GetVersion(ctx)); + +#ifdef WOLFSPDM_NATIONS + /* Steps 2-3: GET_CAPABILITIES + NEGOTIATE_ALGORITHMS + * Required by Nations (TCG spec mandates these before GET_PUB_KEY). + * Nuvoton skips these — its simplified flow goes directly to GET_PUB_KEY.*/ + if (ctx->mode == WOLFSPDM_MODE_NATIONS) { + SPDM_CONNECT_STEP(ctx, "TCG Step 2: GET_CAPABILITIES\n", + wolfSPDM_TCG_GetCapabilities(ctx, WOLFSPDM_TCG_CAPS_FLAGS_DEFAULT)); + SPDM_CONNECT_STEP(ctx, "TCG Step 3: NEGOTIATE_ALGORITHMS\n", + wolfSPDM_TCG_NegotiateAlgorithms(ctx)); + } +#endif + + /* SPDM 1.3+: Replace VCA with Hash(VCA) in transcript. + * DSP0274 1.3 section 10.17.1: th = Hash(Hash(A) || Ct || K) + * TODO: verify with both Nuvoton and Nations hardware */ + if (0 && ctx->spdmVersion >= SPDM_VERSION_13 && ctx->transcriptLen > 12) { + byte vcaHash[WOLFSPDM_HASH_SIZE]; + rc = wolfSPDM_TranscriptHash(ctx, vcaHash); + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_TranscriptReset(ctx); + rc = wolfSPDM_TranscriptAdd(ctx, vcaHash, WOLFSPDM_HASH_SIZE); + wolfSPDM_DebugPrint(ctx, "TCG: VCA hashed (%u -> %u bytes)\n", + ctx->transcriptLen, WOLFSPDM_HASH_SIZE); + } + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } + + /* Step 4: GET_PUBK */ + wolfSPDM_DebugPrint(ctx, "TCG Step 4: GET_PUBK\n"); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_TCG_GetPubKey(ctx, pubKey, &pubKeySz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GET_PUBK failed: %d\n", rc); + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + ctx->state = WOLFSPDM_STATE_CERT; + + /* Compute Ct = SHA-384(TPMT_PUBLIC) and add to transcript */ + if (ctx->flags.hasRspPubKey && ctx->rspPubKeyLen > 0) { + wolfSPDM_DebugPrint(ctx, "TCG: 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, + "TCG: Warning - no responder public key for Ct\n"); + } + + /* Step 5: KEY_EXCHANGE */ + SPDM_CONNECT_STEP(ctx, "TCG Step 5: KEY_EXCHANGE\n", + wolfSPDM_KeyExchange(ctx)); + + /* Step 6: GIVE_PUB (secured) */ + if (ctx->flags.hasReqKeyPair && ctx->reqPubKeyTPMTLen > 0) { + wolfSPDM_DebugPrint(ctx, "TCG Step 6: GIVE_PUB\n"); + rc = wolfSPDM_TCG_GivePubKey(ctx, ctx->reqPubKeyTPMT, + ctx->reqPubKeyTPMTLen); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GIVE_PUB failed: %d\n", rc); + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } else { + wolfSPDM_DebugPrint(ctx, + "TCG Step 6: GIVE_PUB (skipped, no host key)\n"); + } + + /* Step 7: FINISH */ + SPDM_CONNECT_STEP(ctx, "TCG Step 7: FINISH\n", + wolfSPDM_Finish(ctx)); + + ctx->state = WOLFSPDM_STATE_CONNECTED; + wolfSPDM_DebugPrint(ctx, "TCG: SPDM Session Established! " + "SessionID=0x%08x\n", ctx->sessionId); + + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ diff --git a/src/spdm/spdm_transcript.c b/src/spdm/spdm_transcript.c new file mode 100644 index 00000000..02db21f7 --- /dev/null +++ b/src/spdm/spdm_transcript.c @@ -0,0 +1,97 @@ +/* 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" + +/* ----- 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->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_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); +} diff --git a/src/spdm/unit_test.c b/src/spdm/unit_test.c new file mode 100644 index 00000000..e5742cc3 --- /dev/null +++ b/src/spdm/unit_test.c @@ -0,0 +1,998 @@ +/* unit_test.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 + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include "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_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"); + + 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(); +} + +/* ----- 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[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_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(); +} + +/* ----- Multi-Version Tests ----- */ + +static int test_kdf_version_prefix(void) +{ + byte secret[48]; + byte context[48]; + byte out12[32], out13[32], out14[32]; + + printf("test_kdf_version_prefix...\n"); + + memset(secret, 0x5A, sizeof(secret)); + memset(context, 0x00, sizeof(context)); + + ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(SPDM_VERSION_12, secret, + sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), + out12, sizeof(out12))); + ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(SPDM_VERSION_13, secret, + sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), + out13, sizeof(out13))); + ASSERT_SUCCESS(wolfSPDM_HkdfExpandLabel(SPDM_VERSION_14, secret, + sizeof(secret), SPDM_LABEL_KEY, context, sizeof(context), + out14, sizeof(out14))); + + /* All three outputs should differ due to different BinConcat prefixes */ + ASSERT_NE(memcmp(out12, out13, sizeof(out12)), 0, + "1.2 and 1.3 outputs should differ"); + ASSERT_NE(memcmp(out13, out14, sizeof(out13)), 0, + "1.3 and 1.4 outputs should differ"); + ASSERT_NE(memcmp(out12, out14, sizeof(out12)), 0, + "1.2 and 1.4 outputs should differ"); + + TEST_PASS(); +} + +static int test_hmac_mismatch_negative(void) +{ + byte finishedKeyA[WOLFSPDM_HASH_SIZE]; + byte finishedKeyB[WOLFSPDM_HASH_SIZE]; + byte thHash[WOLFSPDM_HASH_SIZE]; + byte verifyA[WOLFSPDM_HASH_SIZE]; + byte verifyB[WOLFSPDM_HASH_SIZE]; + + printf("test_hmac_mismatch_negative...\n"); + + memset(finishedKeyA, 0xAB, sizeof(finishedKeyA)); + memset(finishedKeyB, 0xAC, sizeof(finishedKeyB)); /* Differs by 1 bit */ + memset(thHash, 0xCD, sizeof(thHash)); + + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKeyA, thHash, verifyA)); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKeyB, thHash, verifyB)); + + /* Single-bit change in key must produce different verify data */ + ASSERT_NE(memcmp(verifyA, verifyB, WOLFSPDM_HASH_SIZE), 0, + "Different keys should produce different verify data"); + + TEST_PASS(); +} + +static int test_transcript_overflow(void) +{ + byte chunk[256]; + word32 i, needed; + TEST_CTX_SETUP(); + + printf("test_transcript_overflow...\n"); + + memset(chunk, 0x42, sizeof(chunk)); + + /* Fill transcript to capacity */ + needed = WOLFSPDM_MAX_TRANSCRIPT / sizeof(chunk); + for (i = 0; i < needed; i++) { + ASSERT_SUCCESS(wolfSPDM_TranscriptAdd(ctx, chunk, sizeof(chunk))); + } + ASSERT_EQ(ctx->transcriptLen, (word32)(needed * sizeof(chunk)), + "Transcript should be full"); + + /* Next add should fail with BUFFER_SMALL */ + ASSERT_EQ(wolfSPDM_TranscriptAdd(ctx, chunk, sizeof(chunk)), + WOLFSPDM_E_BUFFER_SMALL, "Overflow should return BUFFER_SMALL"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_version_fallback(void) +{ + /* Fake VERSION response with versions 1.0, 1.1, 1.2, 1.3 */ + byte rsp[] = { + 0x10, SPDM_VERSION, 0x00, 0x00, /* header */ + 0x04, 0x00, /* entryCount = 4 */ + 0x00, 0x10, /* 1.0 */ + 0x00, 0x11, /* 1.1 */ + 0x00, 0x12, /* 1.2 */ + 0x00, 0x13 /* 1.3 */ + }; + TEST_CTX_SETUP(); + + printf("test_version_fallback...\n"); + + /* With no maxVersion set, should select 1.3 (highest mutual) */ + ASSERT_SUCCESS(wolfSPDM_ParseVersion(ctx, rsp, sizeof(rsp))); + ASSERT_EQ(ctx->spdmVersion, SPDM_VERSION_13, + "Should select 1.3 as highest mutual"); + + /* Reset state and set maxVersion to 1.2 */ + ctx->state = WOLFSPDM_STATE_INIT; + ctx->spdmVersion = 0; + ctx->maxVersion = SPDM_VERSION_12; + ASSERT_SUCCESS(wolfSPDM_ParseVersion(ctx, rsp, sizeof(rsp))); + ASSERT_EQ(ctx->spdmVersion, SPDM_VERSION_12, + "Should fall back to 1.2 with maxVersion cap"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ----- 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(); +} + +/* ----- Security Tests ----- */ + +/* Test Fix 1: MITM rejection — a KEY_EXCHANGE_RSP with a forged signature + * (signed by an attacker's key, not the real responder) must be rejected. */ +static int test_mitm_signature_rejected(void) +{ + ecc_key realKey, attackerKey; + byte realPubX[WOLFSPDM_ECC_KEY_SIZE], realPubY[WOLFSPDM_ECC_KEY_SIZE]; + byte atkPubX[WOLFSPDM_ECC_KEY_SIZE], atkPubY[WOLFSPDM_ECC_KEY_SIZE]; + word32 xSz, ySz; + byte rspPubKey[WOLFSPDM_ECC_POINT_SIZE]; + byte keRsp[300]; /* KEY_EXCHANGE_RSP: 282 bytes with opaqueLen=0 */ + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_mitm_signature_rejected...\n"); + + /* Generate "real responder" key and "attacker" key */ + wc_ecc_init(&realKey); + wc_ecc_init(&attackerKey); + wc_ecc_make_key(&ctx->rng, WOLFSPDM_ECC_KEY_SIZE, &realKey); + wc_ecc_make_key(&ctx->rng, WOLFSPDM_ECC_KEY_SIZE, &attackerKey); + + /* Export real responder public key and set it on ctx */ + xSz = ySz = WOLFSPDM_ECC_KEY_SIZE; + wc_ecc_export_public_raw(&realKey, realPubX, &xSz, realPubY, &ySz); + memcpy(rspPubKey, realPubX, WOLFSPDM_ECC_KEY_SIZE); + memcpy(rspPubKey + WOLFSPDM_ECC_KEY_SIZE, realPubY, WOLFSPDM_ECC_KEY_SIZE); + wolfSPDM_SetResponderPubKey(ctx, rspPubKey, WOLFSPDM_ECC_POINT_SIZE); + + /* Export attacker's ephemeral public key */ + xSz = ySz = WOLFSPDM_ECC_KEY_SIZE; + wc_ecc_export_public_raw(&attackerKey, atkPubX, &xSz, atkPubY, &ySz); + + /* Generate our ephemeral key (needed for ECDH later) */ + ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); + + /* Build a fake KEY_EXCHANGE_RSP: + * [0]=ver, [1]=0x64, [2-3]=params, [4-5]=rspSessionId, + * [6-7]=mutAuth, [8-39]=random, [40-87]=pubX, [88-135]=pubY, + * [136-137]=opaqueLen=0, [138-233]=signature, [234-281]=verifyData */ + memset(keRsp, 0, sizeof(keRsp)); + keRsp[0] = SPDM_VERSION_12; + keRsp[1] = SPDM_KEY_EXCHANGE_RSP; + SPDM_Set16LE(&keRsp[4], 0x0002); /* rspSessionId */ + wolfSPDM_GetRandom(ctx, &keRsp[8], 32); /* random */ + memcpy(&keRsp[40], atkPubX, WOLFSPDM_ECC_KEY_SIZE); + memcpy(&keRsp[88], atkPubY, WOLFSPDM_ECC_KEY_SIZE); + SPDM_Set16LE(&keRsp[136], 0); /* opaqueLen = 0 */ + /* Signature at [138]: fill with garbage (attacker can't sign with real key) */ + wolfSPDM_GetRandom(ctx, &keRsp[138], WOLFSPDM_ECC_SIG_SIZE); + /* VerifyData at [234]: garbage */ + memset(&keRsp[234], 0xAA, WOLFSPDM_HASH_SIZE); + + /* Parse should reject: signature doesn't match real responder's key */ + rc = wolfSPDM_ParseKeyExchangeRsp(ctx, keRsp, 282); + ASSERT_EQ(rc, WOLFSPDM_E_BAD_SIGNATURE, "MITM forged sig must be rejected"); + + wc_ecc_free(&realKey); + wc_ecc_free(&attackerKey); + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* Test Fix 4: Invalid curve point must be rejected by ComputeSharedSecret */ +static int test_invalid_curve_point(void) +{ + byte badX[WOLFSPDM_ECC_KEY_SIZE]; + byte badY[WOLFSPDM_ECC_KEY_SIZE]; + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_invalid_curve_point...\n"); + + ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); + + /* Point (1, 1) is not on P-384 */ + memset(badX, 0, sizeof(badX)); + memset(badY, 0, sizeof(badY)); + badX[WOLFSPDM_ECC_KEY_SIZE - 1] = 0x01; + badY[WOLFSPDM_ECC_KEY_SIZE - 1] = 0x01; + + rc = wolfSPDM_ComputeSharedSecret(ctx, badX, badY); + ASSERT_EQ(rc, WOLFSPDM_E_CRYPTO_FAIL, "Off-curve point must be rejected"); + + /* Verify shared secret was zeroed on failure */ + { + byte zeros[WOLFSPDM_ECC_KEY_SIZE]; + memset(zeros, 0, sizeof(zeros)); + ASSERT_EQ(memcmp(ctx->sharedSecret, zeros, sizeof(ctx->sharedSecret)), 0, + "sharedSecret must be zeroed on failure"); + ASSERT_EQ(ctx->sharedSecretSz, 0, "sharedSecretSz must be 0 on failure"); + } + + TEST_CTX_FREE(); + TEST_PASS(); +} + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +/* I/O callback that returns a TCG response with msgSize < TCG_HEADER_SIZE */ +static int tcg_underflow_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, void* userCtx) +{ + (void)ctx; (void)txBuf; (void)txSz; (void)userCtx; + /* Return a 20-byte TCG response with msgSize field = 5 (< 16) */ + if (*rxSz < 20) return -1; + memset(rxBuf, 0, 20); + SPDM_Set16BE(rxBuf, 0x8101); /* tag: clear SPDM */ + SPDM_Set32BE(rxBuf + 2, 5); /* msgSize = 5 (underflow!) */ + *rxSz = 20; + return 0; +} + +static int test_tcg_underflow(void) +{ + byte txBuf[32]; + byte rxBuf[32]; + word32 rxSz = sizeof(rxBuf); + int rc; + TEST_CTX_SETUP(); + + printf("test_tcg_underflow...\n"); + +#ifdef WOLFSPDM_NUVOTON + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NUVOTON); +#else + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS); +#endif + wolfSPDM_SetIO(ctx, tcg_underflow_io_cb, NULL); + + txBuf[0] = 0x10; + txBuf[1] = SPDM_GET_VERSION; + txBuf[2] = 0x00; + txBuf[3] = 0x00; + + rc = wolfSPDM_SendReceive(ctx, txBuf, 4, rxBuf, &rxSz); + ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, + "msgSize < 16 must return BUFFER_SMALL"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#ifdef WOLFSPDM_NATIONS +static int test_nations_mode(void) +{ + int rc; + TEST_CTX_SETUP(); + + printf("test_nations_mode...\n"); + + /* Test Nations mode can be set */ + rc = wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS); + ASSERT_SUCCESS(rc); + ASSERT_EQ(wolfSPDM_GetMode(ctx), WOLFSPDM_MODE_NATIONS, + "Mode should be NATIONS"); + + /* Verify TCG fields initialized */ + ASSERT_EQ(wolfSPDM_GetConnectionHandle(ctx), 0, + "connectionHandle should be 0"); + ASSERT_EQ(wolfSPDM_GetFipsIndicator(ctx), WOLFSPDM_FIPS_NON_FIPS, + "fipsIndicator should be NON_FIPS"); + + /* Test Nations PSK mode can be set */ + rc = wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS_PSK); + ASSERT_SUCCESS(rc); + ASSERT_EQ(wolfSPDM_GetMode(ctx), WOLFSPDM_MODE_NATIONS_PSK, + "Mode should be NATIONS_PSK"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_psk_set(void) +{ + int rc; + byte psk[48]; + byte hint[] = "test_hint"; + TEST_CTX_SETUP(); + + printf("test_nations_psk_set...\n"); + + memset(psk, 0xAB, sizeof(psk)); + + /* NULL args */ + rc = wolfSPDM_SetPSK(NULL, psk, sizeof(psk), NULL, 0); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "NULL ctx should fail"); + rc = wolfSPDM_SetPSK(ctx, NULL, sizeof(psk), NULL, 0); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "NULL psk should fail"); + rc = wolfSPDM_SetPSK(ctx, psk, 0, NULL, 0); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "Zero pskSz should fail"); + + /* Valid PSK without hint */ + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0); + ASSERT_SUCCESS(rc); + ASSERT_EQ(ctx->pskSz, sizeof(psk), "pskSz should be 48"); + ASSERT_EQ(ctx->pskHintSz, 0, "hintSz should be 0"); + + /* Valid PSK with hint */ + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), hint, sizeof(hint) - 1); + ASSERT_SUCCESS(rc); + ASSERT_EQ(ctx->pskHintSz, sizeof(hint) - 1, "hintSz mismatch"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_psk_kdf(void) +{ + int rc; + byte psk[48]; + byte th1[WOLFSPDM_HASH_SIZE]; + byte zeros[WOLFSPDM_PSK_MAX_SIZE]; + TEST_CTX_SETUP_V12(); + + printf("test_nations_psk_kdf...\n"); + + memset(psk, 0xCD, sizeof(psk)); + memset(th1, 0xEF, sizeof(th1)); + memset(zeros, 0, sizeof(zeros)); + + /* Set PSK */ + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0); + ASSERT_SUCCESS(rc); + + /* Derive handshake keys from PSK */ + rc = wolfSPDM_DeriveHandshakeKeysPsk(ctx, th1); + ASSERT_SUCCESS(rc); + + /* Verify PSK was scrubbed */ + ASSERT_EQ(ctx->pskSz, 0, "pskSz should be 0 after derivation"); + ASSERT_EQ(memcmp(ctx->psk, zeros, WOLFSPDM_PSK_MAX_SIZE), 0, + "PSK not zeroed after derivation"); + + /* Verify handshake secret was derived (non-zero) */ + ASSERT_NE(memcmp(ctx->handshakeSecret, zeros, sizeof(ctx->handshakeSecret)), 0, + "handshakeSecret should be non-zero"); + + /* Verify finished keys were derived (non-zero) */ + ASSERT_NE(memcmp(ctx->reqFinishedKey, zeros, sizeof(ctx->reqFinishedKey)), 0, + "reqFinishedKey should be non-zero"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_psk_message_format(void) +{ + int rc; + byte psk[48]; + byte buf[128]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + + printf("test_nations_psk_message_format...\n"); + + memset(psk, 0xAA, sizeof(psk)); + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0); + ASSERT_SUCCESS(rc); + + /* Build PSK_EXCHANGE */ + rc = wolfSPDM_BuildPskExchange(ctx, buf, &bufSz); + ASSERT_SUCCESS(rc); + + /* Verify header */ + ASSERT_EQ(buf[0], SPDM_VERSION_12, "Version should be 0x12"); + ASSERT_EQ(buf[1], SPDM_PSK_EXCHANGE, "Code should be PSK_EXCHANGE"); + + /* ReqSessionID at offset 4-5 */ + ASSERT_EQ(SPDM_Get16LE(&buf[4]), ctx->reqSessionId, + "ReqSessionID mismatch"); + + /* PSKHintLength at offset 6-7 should be 0 (no hint) */ + ASSERT_EQ(SPDM_Get16LE(&buf[6]), 0, "PSKHintLen should be 0"); + + /* RequesterContextLength at offset 8-9 should be 32 */ + ASSERT_EQ(SPDM_Get16LE(&buf[8]), WOLFSPDM_RANDOM_SIZE, + "ReqCtxLen should be 32"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* WOLFSPDM_NATIONS */ + +static int test_decrypt_overflow(void) +{ + /* Static to avoid 4KB+ on stack; cipherLen must exceed + * sizeof(decrypted) = WOLFSPDM_MAX_MSG_SIZE + 16 = 4112 */ + static byte enc[4140]; + byte plain[64]; + word32 plainSz = sizeof(plain); + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_decrypt_overflow...\n"); + + ctx->sessionId = 0x00010001; + ctx->rspSeqNum = 0; + memset(ctx->rspDataKey, 0x42, sizeof(ctx->rspDataKey)); + memset(ctx->rspDataIv, 0x42, sizeof(ctx->rspDataIv)); + + /* MCTP header: rspLen=4130 -> cipherLen=4114 > 4112 = overflow guard */ + memset(enc, 0, sizeof(enc)); + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set16LE(&enc[4], 0x0000); + SPDM_Set16LE(&enc[6], 4130); + + rc = wolfSPDM_DecryptInternal(ctx, enc, 4138, plain, &plainSz); + ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, "Overflow cipherLen must be caught"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_oob_read_error(void) +{ + byte shortErr[2] = {0x12, SPDM_ERROR}; + byte fullErr[4] = {0x12, SPDM_ERROR, 0x06, 0x00}; + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_oob_read_error...\n"); + + rc = wolfSPDM_ParseFinishRsp(ctx, fullErr, sizeof(fullErr)); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, "Should return peer error"); + + rc = wolfSPDM_ParseFinishRsp(ctx, shortErr, sizeof(shortErr)); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "Short buffer should fail"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_constant_time_hmac(void) +{ + byte finishedKey[WOLFSPDM_HASH_SIZE]; + byte thHash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + byte fakeVerify[WOLFSPDM_HASH_SIZE]; + word32 i; + int diff; + + printf("test_constant_time_hmac...\n"); + + memset(finishedKey, 0xAB, sizeof(finishedKey)); + memset(thHash, 0xCD, sizeof(thHash)); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKey, thHash, verifyData)); + + /* 1-byte difference must be detected */ + memcpy(fakeVerify, verifyData, sizeof(fakeVerify)); + fakeVerify[WOLFSPDM_HASH_SIZE - 1] ^= 0x01; + + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ fakeVerify[i]; + ASSERT_NE(diff, 0, "Should detect 1-byte diff"); + + /* Identical must pass */ + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ verifyData[i]; + ASSERT_EQ(diff, 0, "Identical data should match"); + + TEST_PASS(); +} + +static int test_setdebug_truncation(void) +{ + TEST_CTX_SETUP(); + + printf("test_setdebug_truncation...\n"); + + wolfSPDM_SetDebug(ctx, 2); + ASSERT_EQ(ctx->flags.debug, 1, "debug=2 should be 1"); + + wolfSPDM_SetDebug(ctx, 0); + ASSERT_EQ(ctx->flags.debug, 0, "debug=0 should be 0"); + + wolfSPDM_SetDebug(ctx, 255); + ASSERT_EQ(ctx->flags.debug, 1, "debug=255 should be 1"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_key_zeroing(void) +{ + byte zeros[WOLFSPDM_HASH_SIZE]; + byte zeroKey[WOLFSPDM_AEAD_KEY_SIZE]; + byte zeroIv[WOLFSPDM_AEAD_IV_SIZE]; + TEST_CTX_SETUP_V12(); + + printf("test_key_zeroing...\n"); + + memset(zeros, 0, sizeof(zeros)); + memset(zeroKey, 0, sizeof(zeroKey)); + memset(zeroIv, 0, sizeof(zeroIv)); + + /* Fill key material with non-zero data */ + memset(ctx->reqDataKey, 0xAA, sizeof(ctx->reqDataKey)); + memset(ctx->rspDataKey, 0xBB, sizeof(ctx->rspDataKey)); + memset(ctx->reqDataIv, 0xCC, sizeof(ctx->reqDataIv)); + memset(ctx->rspDataIv, 0xDD, sizeof(ctx->rspDataIv)); + memset(ctx->reqHsSecret, 0x11, sizeof(ctx->reqHsSecret)); + memset(ctx->rspHsSecret, 0x22, sizeof(ctx->rspHsSecret)); + memset(ctx->reqFinishedKey, 0x33, sizeof(ctx->reqFinishedKey)); + memset(ctx->rspFinishedKey, 0x44, sizeof(ctx->rspFinishedKey)); + memset(ctx->handshakeSecret, 0x55, sizeof(ctx->handshakeSecret)); + memset(ctx->sharedSecret, 0x66, sizeof(ctx->sharedSecret)); + memset(ctx->th1, 0x77, sizeof(ctx->th1)); + memset(ctx->th2, 0x88, sizeof(ctx->th2)); + ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; + + ctx->state = WOLFSPDM_STATE_CONNECTED; + ctx->sessionId = 0x00010001; + ctx->ioCb = dummy_io_cb; + + wolfSPDM_Disconnect(ctx); + + ASSERT_EQ(memcmp(ctx->reqDataKey, zeroKey, sizeof(ctx->reqDataKey)), 0, + "reqDataKey not zeroed"); + ASSERT_EQ(memcmp(ctx->rspDataKey, zeroKey, sizeof(ctx->rspDataKey)), 0, + "rspDataKey not zeroed"); + ASSERT_EQ(memcmp(ctx->reqDataIv, zeroIv, sizeof(ctx->reqDataIv)), 0, + "reqDataIv not zeroed"); + ASSERT_EQ(memcmp(ctx->rspDataIv, zeroIv, sizeof(ctx->rspDataIv)), 0, + "rspDataIv not zeroed"); + ASSERT_EQ(memcmp(ctx->reqHsSecret, zeros, sizeof(ctx->reqHsSecret)), 0, + "reqHsSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->rspHsSecret, zeros, sizeof(ctx->rspHsSecret)), 0, + "rspHsSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->reqFinishedKey, zeros, sizeof(ctx->reqFinishedKey)), 0, + "reqFinishedKey not zeroed"); + ASSERT_EQ(memcmp(ctx->rspFinishedKey, zeros, sizeof(ctx->rspFinishedKey)), 0, + "rspFinishedKey not zeroed"); + ASSERT_EQ(memcmp(ctx->handshakeSecret, zeros, sizeof(ctx->handshakeSecret)), 0, + "handshakeSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->sharedSecret, zeros, sizeof(ctx->sharedSecret)), 0, + "sharedSecret not zeroed"); + ASSERT_EQ(ctx->sharedSecretSz, 0, "sharedSecretSz not zeroed"); + ASSERT_EQ(memcmp(ctx->th1, zeros, sizeof(ctx->th1)), 0, + "th1 not zeroed"); + ASSERT_EQ(memcmp(ctx->th2, zeros, sizeof(ctx->th2)), 0, + "th2 not zeroed"); + + wolfSPDM_Init(ctx); + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ----- Main ----- */ + +int main(void) +{ + printf("===========================================\n"); + printf("wolfSPDM Unit Tests\n"); + printf("===========================================\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(); + + /* 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_end_session(); + + /* Error tests */ + test_check_error(); + test_error_strings(); + + /* Multi-version tests */ + test_kdf_version_prefix(); + test_hmac_mismatch_negative(); + test_transcript_overflow(); + test_version_fallback(); + + /* Session state tests */ + test_session_state(); + + /* Security tests */ + test_mitm_signature_rejected(); + test_invalid_curve_point(); +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + test_tcg_underflow(); +#endif +#ifdef WOLFSPDM_NATIONS + test_nations_mode(); + test_nations_psk_set(); + test_nations_psk_kdf(); + test_nations_psk_message_format(); +#endif + test_decrypt_overflow(); + test_oob_read_error(); + test_constant_time_hmac(); + test_setdebug_truncation(); + test_key_zeroing(); + + 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/src/tpm2.c b/src/tpm2.c index 88cb229d..62f5a8b3 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -39,6 +39,9 @@ #include #endif #include +#ifdef WOLFTPM_SPDM +#include +#endif #include @@ -417,6 +420,38 @@ static int TPM2_ResponseProcess(TPM2_CTX* ctx, TPM2_Packet* packet, return rc; } +#ifdef WOLFTPM_SPDM +/* SPDM intercept: if SPDM session is active, send TPM command through + * the encrypted SPDM channel instead of raw SPI/I2C. + * Returns TPM_RC_SUCCESS on success, negative if SPDM not active + * (caller should use normal transport), or positive error code. */ +static TPM_RC TPM2_SPDM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) +{ + WOLFTPM2_SPDM_CTX* spdmCtx; + byte tpmResp[WOLFSPDM_MAX_MSG_SIZE]; + word32 tpmRespSz = sizeof(tpmResp); + TPM_RC rc; + + if (ctx->spdmCtx == NULL) + return -1; /* SPDM not configured */ + spdmCtx = (WOLFTPM2_SPDM_CTX*)ctx->spdmCtx; + if (spdmCtx->spdmCtx == NULL || !wolfSPDM_IsConnected(spdmCtx->spdmCtx)) + return -1; /* SPDM not connected */ + + rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, + packet->buf, packet->pos, tpmResp, &tpmRespSz); + if (rc != 0) + return rc; + + if (tpmRespSz > MAX_RESPONSE_SIZE) + return TPM_RC_SIZE; + XMEMCPY(packet->buf, tpmResp, tpmRespSz); + packet->pos = 0; + packet->size = tpmRespSz; + return TPM_RC_SUCCESS; +} +#endif /* WOLFTPM_SPDM */ + static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet, CmdInfo_t* info) { @@ -460,7 +495,13 @@ 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 + rc = TPM2_SPDM_SendCommand(ctx, packet); + if (rc < 0) /* SPDM not active, use normal transport */ +#endif + { + rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); + } if (rc != 0) return rc; @@ -490,6 +531,13 @@ static TPM_RC TPM2_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) if (ctx == NULL || packet == NULL) return BAD_FUNC_ARG; +#ifdef WOLFTPM_SPDM + rc = TPM2_SPDM_SendCommand(ctx, packet); + if (rc == TPM_RC_SUCCESS) + return TPM2_Packet_Parse(rc, packet); + /* rc < 0 means SPDM not active, fall through to normal transport */ +#endif + /* submit command and wait for response */ rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); if (rc != 0) @@ -1590,6 +1638,7 @@ TPM_RC TPM2_StartAuthSession(StartAuthSession_In* in, StartAuthSession_Out* out) return rc; } + TPM_RC TPM2_PolicyRestart(PolicyRestart_In* in) { TPM_RC rc; @@ -5604,6 +5653,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 */ @@ -5647,6 +5697,100 @@ 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_NATIONS +/* Nations Technology NS350 Vendor Commands */ +int TPM2_Nations_IdentityKeySet(Nations_IdentityKeySet_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_AppendU32(&packet, in->configuration); + TPM2_Packet_Finalize(&packet, TPM_ST_SESSIONS, + TPM_CC_Nations_IdentityKeySet); + + rc = TPM2_SendCommandAuth(ctx, &packet, &info); + + TPM2_ReleaseLock(ctx); + } + return rc; +} +#endif /* WOLFTPM_NATIONS */ #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..9fba7200 --- /dev/null +++ b/src/tpm2_spdm.c @@ -0,0 +1,386 @@ +/* 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(WOLFSPDM_NATIONS)) && \ + !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; + } + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + /* 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 || + wolfSPDM_GetMode(ctx->spdmCtx) == WOLFSPDM_MODE_NATIONS) { + 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") */ + { + byte ver = wolfSPDM_GetNegotiatedVersion(ctx->spdmCtx); + if (ver == 0) ver = SPDM_VERSION_13; + vdMsgSz = wolfSPDM_BuildVendorDefined(ver, + WOLFSPDM_VDCODE_TPM2_CMD, + cmdPlain, cmdSz, vdMsg, sizeof(vdMsg)); + } + if (vdMsgSz < 0) { + return vdMsgSz; + } + + /* 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 || WOLFSPDM_NATIONS */ + + /* Standard SPDM mode: send TPM command as raw app data */ + return wolfSPDM_SecuredExchange(ctx->spdmCtx, + cmdPlain, cmdSz, rspPlain, rspSz); +} + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Functions */ +/* -------------------------------------------------------------------------- */ + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + +/* 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 +} + +#ifdef WOLFSPDM_NUVOTON +/* 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 /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/tpm2_swtpm.c b/src/tpm2_swtpm.c index 758bc354..6c8b956c 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 b06476d4..2b550b73 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -25,6 +25,9 @@ #include #include +#ifdef WOLFTPM_SPDM +#include +#endif /* Convert big-endian byte array to native word32 */ word32 wolfTPM2_RsaKey_Exponent(const byte* e, word32 eSz) @@ -154,10 +157,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) { @@ -167,7 +186,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; @@ -177,13 +198,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 */ @@ -239,6 +276,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; } @@ -951,6 +1031,573 @@ 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; +} + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +/* Shared TCG SPDM functions */ + +int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nuvoton_GetPubKey(dev->spdmCtx->spdmCtx, pubKey, pubKeySz); +} +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#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_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_ForceZero(privKey, sizeof(privKey)); + 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); + wc_ForceZero(privKey, sizeof(privKey)); + 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 */ + +#ifdef WOLFSPDM_NATIONS +/* Nations Technology NS350 SPDM functions */ + +int wolfTPM2_SpdmSetNationsMode(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NATIONS); +} + +int wolfTPM2_SpdmConnectNations(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 Nations mode */ + rc = wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NATIONS); + 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. + * Nations: GIVE_PUB is not supported, but MUT_AUTH is still required. + * The TPM may verify the requester signature using a pre-provisioned + * key or accept the key from the FINISH signature context. */ + 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]; + 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_ForceZero(privKey, sizeof(privKey)); + 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; + + XMEMCPY(rawPubKey, pubKeyX, 48); + XMEMCPY(rawPubKey + 48, pubKeyY, 48); + rc = wolfSPDM_SetRequesterKeyPair(dev->spdmCtx->spdmCtx, + privKey, privKeySz, rawPubKey, 96); + wc_ForceZero(privKey, sizeof(privKey)); + if (rc != 0) return rc; + + /* Build TPMT_PUBLIC for GIVE_PUB / Cm hash */ + p = tpmtPub; + *p++ = 0x00; *p++ = 0x23; /* type = TPM_ALG_ECC */ + *p++ = 0x00; *p++ = 0x0C; /* nameAlg = SHA384 */ + *p++ = 0x00; *p++ = 0x04; *p++ = 0x00; *p++ = 0x00; /* attr = sign */ + *p++ = 0x00; *p++ = 0x00; /* authPolicy size = 0 */ + *p++ = 0x00; *p++ = 0x10; /* symmetric = NULL */ + *p++ = 0x00; *p++ = 0x18; /* scheme = ECDSA */ + *p++ = 0x00; *p++ = 0x0C; /* hashAlg = SHA384 */ + *p++ = 0x00; *p++ = 0x04; /* curveID = P384 */ + *p++ = 0x00; *p++ = 0x10; /* kdf = NULL */ + *p++ = 0x00; *p++ = 0x30; /* x size = 48 */ + XMEMCPY(p, pubKeyX, 48); p += 48; + *p++ = 0x00; *p++ = 0x30; /* y size = 48 */ + 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 TCG SPDM handshake */ + return wolfSPDM_Connect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmNationsIdentityKeySet(WOLFTPM2_DEV* dev, int set) +{ + int rc; + Nations_IdentityKeySet_In in; + + if (dev == NULL) { + return BAD_FUNC_ARG; + } + + /* Mutual exclusion: TPM enforces this by returning TPM_RC_VALUE if PSK + * is provisioned. The error message helps users understand the failure. */ + + /* Set platform auth (empty password) with no continueSession. + * NS350 requires sessionAttributes=0x00, not 0x01 (continueSession). */ + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != 0) return rc; + dev->session[0].sessionAttributes = 0; + + XMEMSET(&in, 0, sizeof(in)); + in.authHandle = TPM_RH_PLATFORM; + in.configuration = set ? 1 : 0; + + rc = TPM2_Nations_IdentityKeySet(&in); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("Nations IdentityKeySet(%d) failed: 0x%x: %s\n", + set, rc, TPM2_GetRCString(rc)); + #endif + } + + return rc; +} + +int wolfTPM2_SpdmConnectNationsPsk(WOLFTPM2_DEV* dev, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz) +{ + int rc; + + if (dev == NULL || dev->spdmCtx == NULL || psk == NULL || pskSz == 0) { + return BAD_FUNC_ARG; + } + + /* Auto-set TIS I/O callback */ + 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 Nations PSK mode */ + rc = wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NATIONS_PSK); + if (rc != 0) { + return rc; + } + + /* Set PSK for KDF */ + rc = wolfSPDM_SetPSK(dev->spdmCtx->spdmCtx, psk, pskSz, hint, hintSz); + if (rc != 0) { + return rc; + } + + /* Perform PSK handshake (may include inline PSK_SET if set) */ + return wolfSPDM_Connect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmNationsGetStatus(WOLFTPM2_DEV* dev, + WOLFSPDM_NATIONS_STATUS* status) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nations_GetStatus(dev->spdmCtx->spdmCtx, status); +} + +int wolfTPM2_SpdmNationsSetOnlyMode(WOLFTPM2_DEV* dev, int lock) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nations_SetOnlyMode(dev->spdmCtx->spdmCtx, lock); +} + +int wolfTPM2_SpdmNationsPskSet(WOLFTPM2_DEV* dev, + const byte* psk, word32 pskSz) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nations_PskSet(dev->spdmCtx->spdmCtx, psk, pskSz); +} + +int wolfTPM2_SpdmNationsPskClear(WOLFTPM2_DEV* dev, + const byte* clearAuth, word32 clearAuthSz) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nations_PskClear(dev->spdmCtx->spdmCtx, + clearAuth, clearAuthSz); +} + +#endif /* WOLFSPDM_NATIONS */ + +#endif /* WOLFTPM_SPDM */ + int wolfTPM2_UnsetAuth(WOLFTPM2_DEV* dev, int index) { TPM2_AUTH_SESSION* session; @@ -1284,14 +1931,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 07a86d12..ad4bcf4f 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -961,6 +961,107 @@ 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 */ + +#ifdef WOLFSPDM_NATIONS + /* Test 4: Nations-specific parameter validation */ + rc = wolfTPM2_SpdmSetNationsMode(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmNationsIdentityKeySet(NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + { + byte pubKey[256]; + word32 pubKeySz = sizeof(pubKey); + + rc = wolfTPM2_SpdmGetPubKey(NULL, pubKey, &pubKeySz); + AssertIntEQ(rc, BAD_FUNC_ARG); + } + /* Nations PSK wrapper parameter validation */ + rc = wolfTPM2_SpdmConnectNationsPsk(NULL, NULL, 0, NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + { + WOLFSPDM_NATIONS_STATUS nStatus; + rc = wolfTPM2_SpdmNationsGetStatus(NULL, &nStatus); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmNationsSetOnlyMode(NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmNationsPskSet(NULL, NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmNationsPskClear(NULL, NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + } +#endif /* WOLFSPDM_NATIONS */ + + 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) @@ -1098,6 +1199,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..c13891f4 100644 --- a/wolftpm/include.am +++ b/wolftpm/include.am @@ -15,5 +15,13 @@ nobase_include_HEADERS+= \ wolftpm/tpm2_socket.h \ wolftpm/tpm2_asn.h \ wolftpm/version.h \ + wolftpm/tpm2_spdm.h \ wolftpm/visibility.h \ - wolftpm/options.h + wolftpm/options.h \ + wolftpm/spdm/spdm.h \ + wolftpm/spdm/spdm_types.h \ + wolftpm/spdm/spdm_error.h \ + wolftpm/spdm/spdm_tcg.h \ + wolftpm/spdm/spdm_nuvoton.h \ + wolftpm/spdm/spdm_nations.h \ + wolftpm/spdm/spdm_psk.h diff --git a/wolftpm/spdm/spdm.h b/wolftpm/spdm/spdm.h new file mode 100644 index 00000000..0f92d1b8 --- /dev/null +++ b/wolftpm/spdm/spdm.h @@ -0,0 +1,147 @@ +/* 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 + +#ifndef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Protocol mode: TCG binding + vendor commands. + * For standard SPDM (emulator, measurements, challenge), see wolfSPDM standalone. */ +typedef enum { + WOLFSPDM_MODE_NUVOTON = 1, + WOLFSPDM_MODE_NATIONS = 2, + WOLFSPDM_MODE_NATIONS_PSK = 3 +} WOLFSPDM_MODE; + +/* wolfSPDM: Lightweight SPDM requester using wolfCrypt. + * Algorithm Set B fixed: P-384/SHA-384/AES-256-GCM. + * + * Usage (static, zero-malloc): + * WOLFSPDM_CTX ctx; + * wolfSPDM_Init(&ctx); + * wolfSPDM_SetIO(&ctx, callback, userPtr); + * wolfSPDM_Connect(&ctx); + * wolfSPDM_SecuredExchange(&ctx, ...); + * wolfSPDM_Disconnect(&ctx); + * wolfSPDM_Free(&ctx); + * + * Dynamic (requires --enable-dynamic-mem): + * ctx = wolfSPDM_New(); + * // ... same as above ... + * wolfSPDM_Free(ctx); + * + * WOLFSPDM_CTX is ~22KB. Use static global on small-stack systems. + * SecuredExchange call chain uses ~20KB stack for message buffers. */ + +/* Compile-time buffer size for static allocation (32KB, runtime-verified) */ +#define WOLFSPDM_CTX_STATIC_SIZE 32768 + +struct WOLFSPDM_CTX; +typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + #include +#endif +#ifdef WOLFSPDM_NUVOTON + #include +#endif +#ifdef WOLFSPDM_NATIONS + #include +#endif +#ifdef WOLFTPM_SPDM_PSK + #include +#endif + +/* I/O callback: transport-agnostic send/receive. + * Returns 0 on success, negative on error. + * rxSz: [in] buffer size, [out] actual received size. */ +typedef int (*WOLFSPDM_IO_CB)( + WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + void* userCtx +); + +/* Context management */ +WOLFSPDM_API int wolfSPDM_Init(WOLFSPDM_CTX* ctx); +#ifdef WOLFSPDM_DYNAMIC_MEMORY +WOLFSPDM_API WOLFSPDM_CTX* wolfSPDM_New(void); +#endif +WOLFSPDM_API void wolfSPDM_Free(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_GetCtxSize(void); +WOLFSPDM_API int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size); + +/* Configuration */ +WOLFSPDM_API int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, + void* userCtx); +WOLFSPDM_API int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode); +WOLFSPDM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); +/* Set responder pub key for cert-less operation (96 bytes P-384 X||Y) */ +WOLFSPDM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz); +/* Set requester key pair for mutual auth (privKey=48, pubKey=96 bytes) */ +WOLFSPDM_API int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, + const byte* privKey, word32 privKeySz, + const byte* pubKey, word32 pubKeySz); + +/* Session establishment */ +WOLFSPDM_API int wolfSPDM_Connect(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx); + +/* Individual handshake steps (for fine-grained control) */ +WOLFSPDM_API int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_Finish(WOLFSPDM_CTX* ctx); + +/* Secured messaging: encrypt, send, receive, decrypt in one call */ +WOLFSPDM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz); + +/* Session info */ +WOLFSPDM_API word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx); +WOLFSPDM_API byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx); +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +WOLFSPDM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); +WOLFSPDM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); +#endif + +/* wolfSPDM_SetPSK declared in spdm_psk.h */ + +/* Debug */ +WOLFSPDM_API void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_SPDM_H */ diff --git a/wolftpm/spdm/spdm_error.h b/wolftpm/spdm/spdm_error.h new file mode 100644 index 00000000..3550b406 --- /dev/null +++ b/wolftpm/spdm/spdm_error.h @@ -0,0 +1,60 @@ +/* spdm_error.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFSPDM_ERROR_H +#define WOLFSPDM_ERROR_H + +#include + +#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_SESSION_INVALID = -16, /* Session ID invalid or mismatch */ + WOLFSPDM_E_KEY_EXCHANGE = -17, /* Key exchange failed */ +}; + +/* Get human-readable error string */ +WOLFSPDM_API const char* wolfSPDM_GetErrorString(int error); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_ERROR_H */ diff --git a/wolftpm/spdm/spdm_nations.h b/wolftpm/spdm/spdm_nations.h new file mode 100644 index 00000000..e7842425 --- /dev/null +++ b/wolftpm/spdm/spdm_nations.h @@ -0,0 +1,99 @@ +/* spdm_nations.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 + */ + +/* Nations Technology NS350 TPM SPDM Support + * + * Two SPDM modes (mutually exclusive): + * + * 1. Identity key mode — TCG "TPM Communication over SPDM" + * - Uses shared TCG binding code (spdm_tcg.c) + * - GET_PUB_KEY, GIVE_PUB_KEY, TPM_CMD vendor commands + * - Algorithm Set B (P-384/SHA-384/AES-256-GCM) + * + * 2. PSK mode — PSK_EXCHANGE/PSK_FINISH + * - GET_STATUS, SPDM_ONLY, PSK_SET, PSK_CLEAR vendor commands + * - Same Algorithm Set B + * + * Reference: NS350 Datasheet Rev 2.06 Section 4.5.8 + */ + +#ifndef WOLFSPDM_NATIONS_H +#define WOLFSPDM_NATIONS_H + +#ifdef WOLFSPDM_NATIONS + +/* Include shared TCG declarations */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- Nations Vendor TPM Command ----- */ + +#define TPM_CC_Nations_SpdmIdentityKeySet (0x20000708) + +/* Nations vendor capability properties */ +#define TPM_PT_VENDOR_NATIONS_FIPS_SL2 (TPM_PT_VENDOR + 11) +#define TPM_PT_VENDOR_NATIONS_IDENTITY_KEY (TPM_PT_VENDOR + 12) + +/* ----- Nations-Only Vendor-Defined Commands ----- */ + +#define WOLFSPDM_NATIONS_VDCODE_PSK_SET "PSK_SET_" +#define WOLFSPDM_NATIONS_VDCODE_PSK_CLEAR "PSK_CLR_" + +/* ----- Nations SPDM Status ----- */ + +/* GET_STATUS_RSP fields per TCG spec Table 15 */ +typedef struct WOLFSPDM_NATIONS_STATUS { + unsigned int spdmEnabled : 1; + unsigned int sessionActive : 1; + unsigned int spdmOnlyLocked : 1; + unsigned int spdmOnlyPending : 1; + unsigned int pskProvisioned : 1; + unsigned int identityKeyProvisioned : 1; +} WOLFSPDM_NATIONS_STATUS; + +/* ----- Nations PSK-Mode SPDM Functions ----- */ + +WOLFSPDM_API int wolfSPDM_Nations_GetStatus(WOLFSPDM_CTX* ctx, + WOLFSPDM_NATIONS_STATUS* status); + +WOLFSPDM_API int wolfSPDM_Nations_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock); + +WOLFSPDM_API int wolfSPDM_Nations_PskSet(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz); + +WOLFSPDM_API int wolfSPDM_Nations_PskClear(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz); + +WOLFSPDM_API int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz); + +/* wolfSPDM_ConnectNationsPsk is an alias for wolfSPDM_ConnectPsk (spdm_psk.h) */ + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_NATIONS */ + +#endif /* WOLFSPDM_NATIONS_H */ diff --git a/wolftpm/spdm/spdm_nuvoton.h b/wolftpm/spdm/spdm_nuvoton.h new file mode 100644 index 00000000..7aef79e2 --- /dev/null +++ b/wolftpm/spdm/spdm_nuvoton.h @@ -0,0 +1,76 @@ +/* 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 + * + * Nuvoton-specific SPDM functions (GetStatus, SetOnlyMode). + * Shared TCG code is in spdm_tcg.h / spdm_tcg.c. + * + * The Nuvoton NPCT75x TPM uses a simplified SPDM flow: + * GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH + * + * Notable differences from standard SPDM: + * - No GET_CAPABILITIES or NEGOTIATE_ALGORITHMS (Algorithm Set B is fixed) + * - Uses vendor-defined commands for identity key exchange + * - TCG binding headers wrap all SPDM messages + * + * Reference: Nuvoton SPDM Guidance Rev 1.11 + */ + +#ifndef WOLFSPDM_NUVOTON_H +#define WOLFSPDM_NUVOTON_H + +/* Include shared TCG declarations */ +#include + +#ifdef WOLFSPDM_NUVOTON + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- Nuvoton SPDM Status ----- */ + +typedef struct WOLFSPDM_NUVOTON_STATUS { + int spdmEnabled; + int sessionActive; + int spdmOnlyLocked; + byte specVersionMajor; + byte specVersionMinor; +} WOLFSPDM_NUVOTON_STATUS; + +/* ----- Nuvoton-Only Functions ----- */ + +WOLFSPDM_API int wolfSPDM_Nuvoton_GetStatus( + WOLFSPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status); + +WOLFSPDM_API int wolfSPDM_Nuvoton_SetOnlyMode( + WOLFSPDM_CTX* ctx, + int lock); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFSPDM_NUVOTON_H */ diff --git a/wolftpm/spdm/spdm_psk.h b/wolftpm/spdm/spdm_psk.h new file mode 100644 index 00000000..cd8f146b --- /dev/null +++ b/wolftpm/spdm/spdm_psk.h @@ -0,0 +1,91 @@ +/* spdm_psk.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 + */ + +/* Shared SPDM PSK Support (DSP0274 1.2+) + * + * Standard SPDM PSK protocol: + * - PSK_EXCHANGE / PSK_EXCHANGE_RSP + * - PSK_FINISH / PSK_FINISH_RSP + * - PSK key derivation (HKDF-Extract with PSK) + * - Shared PSK connection flow + * + * Vendor-specific PSK provisioning commands (PSK_SET, PSK_CLEAR, etc.) + * remain in the vendor files (spdm_nations.c, etc.). + */ + +#ifndef WOLFSPDM_PSK_H +#define WOLFSPDM_PSK_H + +#include + +#ifdef WOLFTPM_SPDM_PSK + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- PSK Context Setup ----- */ + +WOLFSPDM_API int wolfSPDM_SetPSK(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz); + +/* ----- PSK Message Builders/Parsers ----- */ + +WOLFSPDM_API int wolfSPDM_BuildPskExchange(WOLFSPDM_CTX* ctx, + byte* buf, word32* bufSz); + +WOLFSPDM_API int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz); + +WOLFSPDM_API int wolfSPDM_BuildPskFinish(WOLFSPDM_CTX* ctx, + byte* buf, word32* bufSz); + +WOLFSPDM_API int wolfSPDM_ParsePskFinishRsp(WOLFSPDM_CTX* ctx, + const byte* buf, word32 bufSz); + +/* ----- PSK Key Derivation ----- */ + +WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeysPsk(WOLFSPDM_CTX* ctx, + const byte* th1Hash); + +/* ----- Shared PSK Connection Flow ----- */ + +/** + * Perform PSK SPDM connection. + * GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> + * PSK_EXCHANGE -> PSK_FINISH -> app key derivation. + * + * @param ctx wolfSPDM context (must have PSK set via wolfSPDM_SetPSK) + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_ConnectPsk(WOLFSPDM_CTX* ctx); + +/* Backward compatibility */ +#define wolfSPDM_ConnectNationsPsk wolfSPDM_ConnectPsk + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFTPM_SPDM_PSK */ + +#endif /* WOLFSPDM_PSK_H */ diff --git a/wolftpm/spdm/spdm_tcg.h b/wolftpm/spdm/spdm_tcg.h new file mode 100644 index 00000000..dedcd95b --- /dev/null +++ b/wolftpm/spdm/spdm_tcg.h @@ -0,0 +1,164 @@ +/* spdm_tcg.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 + */ + +/* Shared TCG SPDM Binding Support + * + * This header provides shared TCG SPDM functionality used by both + * Nuvoton and Nations Technology TPMs: + * - TCG SPDM Binding message framing (per TCG SPDM Binding Spec v1.0) + * - Vendor-defined command helpers + * - Identity key exchange (GET_PUBK, GIVE_PUB) + * - GET_CAPABILITIES + NEGOTIATE_ALGORITHMS + * - TCG SPDM connection flow + */ + +#ifndef WOLFSPDM_TCG_H +#define WOLFSPDM_TCG_H + +#include + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + +#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 */ + +/* FIPS Service Indicator */ +#define WOLFSPDM_FIPS_NON_FIPS 0x00 +#define WOLFSPDM_FIPS_APPROVED 0x01 + +/* ----- TCG Vendor-Defined Command Codes (shared) ----- */ + +/* 8-byte ASCII vendor codes for SPDM VENDOR_DEFINED messages */ +#define WOLFSPDM_VDCODE_LEN 8 + +#define WOLFSPDM_VDCODE_TPM2_CMD "TPM2_CMD" /* TPM command over SPDM */ +#define WOLFSPDM_VDCODE_GET_PUBK "GET_PUBK" /* Get TPM's identity key */ +#define WOLFSPDM_VDCODE_GIVE_PUB "GIVE_PUB" /* Give host's identity key */ +#define WOLFSPDM_VDCODE_GET_STS "GET_STS_" /* Get SPDM status */ +#define WOLFSPDM_VDCODE_SPDMONLY "SPDMONLY" /* Lock/unlock SPDM-only */ + +/* SPDMONLY command parameters */ +#define WOLFSPDM_SPDMONLY_LOCK 0x01 +#define WOLFSPDM_SPDMONLY_UNLOCK 0x00 + +/* ----- TCG Binding Header Structures ----- */ + +/* Clear message header (tag 0x8101) + * Layout: tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + + * fipsIndicator(2/BE) + reserved(4) = 16 bytes */ +typedef struct WOLFSPDM_TCG_CLEAR_HDR { + word16 tag; + word32 size; + word32 connectionHandle; + word16 fipsIndicator; + word32 reserved; +} WOLFSPDM_TCG_CLEAR_HDR; + +/* ----- Vendor Command Response Container ----- */ + +typedef struct { + char vdCode[WOLFSPDM_VDCODE_LEN + 1]; + byte payload[WOLFSPDM_VENDOR_BUF_SZ]; + word32 payloadSz; +} WOLFSPDM_VENDOR_RSP; + +/* ----- Vendor Command Helpers ----- */ + +WOLFSPDM_API int wolfSPDM_TCG_VendorCmdClear(WOLFSPDM_CTX* ctx, + const char* vdCode, const byte* payload, word32 payloadSz, + WOLFSPDM_VENDOR_RSP* rsp); + +WOLFSPDM_API int wolfSPDM_TCG_VendorCmdSecured(WOLFSPDM_CTX* ctx, + const char* vdCode, const byte* payload, word32 payloadSz); + +/* ----- TCG Binding Message Framing ----- */ + +WOLFSPDM_API int wolfSPDM_BuildTcgClearMessage( + WOLFSPDM_CTX* ctx, + const byte* spdmPayload, word32 spdmPayloadSz, + byte* outBuf, word32 outBufSz); + +WOLFSPDM_API int wolfSPDM_ParseTcgClearMessage( + const byte* inBuf, word32 inBufSz, + byte* spdmPayload, word32* spdmPayloadSz, + WOLFSPDM_TCG_CLEAR_HDR* hdr); + +/* ----- Vendor-Defined Message Helpers ----- */ + +WOLFSPDM_API int wolfSPDM_BuildVendorDefined( + byte spdmVersion, const char* vdCode, + const byte* payload, word32 payloadSz, + byte* outBuf, word32 outBufSz); + +WOLFSPDM_API int wolfSPDM_ParseVendorDefined( + const byte* inBuf, word32 inBufSz, char* vdCode, + byte* payload, word32* payloadSz); + +/* ----- Shared TCG SPDM Functions ----- */ + +WOLFSPDM_API int wolfSPDM_TCG_GetPubKey(WOLFSPDM_CTX* ctx, + byte* pubKey, word32* pubKeySz); + +WOLFSPDM_API int wolfSPDM_TCG_GivePubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz); + +WOLFSPDM_API int wolfSPDM_TCG_GetCapabilities(WOLFSPDM_CTX* ctx, + word32 capsFlags); + +WOLFSPDM_API int wolfSPDM_TCG_NegotiateAlgorithms(WOLFSPDM_CTX* ctx); + +WOLFSPDM_API int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz); + +WOLFSPDM_API int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx); + +/* Backward compatibility aliases */ +#define wolfSPDM_ConnectNuvoton wolfSPDM_ConnectTCG +#define wolfSPDM_Nuvoton_GetPubKey wolfSPDM_TCG_GetPubKey +#define wolfSPDM_Nuvoton_GivePubKey wolfSPDM_TCG_GivePubKey + +/* ----- TCG Context Defaults ----- */ + +#define WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT 0 +#define WOLFSPDM_NUVOTON_FIPS_DEFAULT WOLFSPDM_FIPS_NON_FIPS + +/* Default capabilities flags (identity key mode, no PSK_CAP) */ +#define WOLFSPDM_TCG_CAPS_FLAGS_DEFAULT 0x000193C0UL +/* Capabilities flags with PSK_CAP (bit 10) set */ +#define WOLFSPDM_TCG_CAPS_FLAGS_PSK 0x000197C0UL + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#endif /* WOLFSPDM_TCG_H */ diff --git a/wolftpm/spdm/spdm_types.h b/wolftpm/spdm/spdm_types.h new file mode 100644 index 00000000..67e32a0e --- /dev/null +++ b/wolftpm/spdm/spdm_types.h @@ -0,0 +1,170 @@ +/* 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 (used in version negotiation and key derivation) */ +#define SPDM_VERSION_10 0x10 /* GET_VERSION always uses 1.0 */ +#define SPDM_VERSION_12 0x12 /* SPDM 1.2 */ +#define SPDM_VERSION_13 0x13 /* SPDM 1.3 */ +#define SPDM_VERSION_14 0x14 /* SPDM 1.4 */ + +/* SPDM Request Codes (used by this implementation) */ +#define SPDM_GET_VERSION 0x84 +#define SPDM_KEY_EXCHANGE 0xE4 +#define SPDM_FINISH 0xE5 +#define SPDM_END_SESSION 0xEA +#define SPDM_VENDOR_DEFINED_REQUEST 0xFE + +/* SPDM Response Codes (used by this implementation) */ +#define SPDM_VERSION 0x04 +#define SPDM_KEY_EXCHANGE_RSP 0x64 +#define SPDM_FINISH_RSP 0x65 +#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 Fixed Parameters (FIPS 140-3 Level 3 compliant) + * P-384 ECDSA/ECDH, SHA-384, AES-256-GCM, HKDF */ +#define WOLFSPDM_HASH_SIZE 48 /* SHA-384 output size */ +#define WOLFSPDM_ECC_KEY_SIZE 48 /* P-384 coordinate size */ +#define WOLFSPDM_ECC_POINT_SIZE (2 * WOLFSPDM_ECC_KEY_SIZE) /* P-384 X||Y */ +#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_AEAD_OVERHEAD 48 /* Max AEAD record overhead (hdr+pad+tag) */ + +/* ----- Buffer/Message Size Limits ----- */ + +#define WOLFSPDM_MAX_MSG_SIZE 4096 /* Maximum SPDM message size */ +#define WOLFSPDM_MAX_TRANSCRIPT 4096 /* Maximum transcript buffer */ +#define WOLFSPDM_RANDOM_SIZE 32 /* Random data in KEY_EXCHANGE */ + +/* ----- MCTP Transport Constants ----- */ + +#define MCTP_MESSAGE_TYPE_SPDM 0x05 /* SPDM over MCTP */ + +/* ----- 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" + +/* ----- Buffer Size Macros (overridable) ----- */ + +#ifndef WOLFSPDM_KEY_EX_TX_SZ +#define WOLFSPDM_KEY_EX_TX_SZ 192 /* KEY_EXCHANGE request (~158 bytes) */ +#endif +#ifndef WOLFSPDM_KEY_EX_RX_SZ +#define WOLFSPDM_KEY_EX_RX_SZ 384 /* KEY_EXCHANGE_RSP (~302 bytes) */ +#endif +#ifndef WOLFSPDM_FINISH_BUF_SZ +#define WOLFSPDM_FINISH_BUF_SZ 152 /* FINISH mutual auth (~148 bytes) */ +#endif +#ifndef WOLFSPDM_VENDOR_BUF_SZ +#define WOLFSPDM_VENDOR_BUF_SZ 256 /* Vendor command message/payload */ +#endif +#ifndef WOLFSPDM_VENDOR_RX_SZ +#define WOLFSPDM_VENDOR_RX_SZ 512 /* Vendor response buffer */ +#endif +#ifndef WOLFSPDM_PUBKEY_BUF_SZ +#define WOLFSPDM_PUBKEY_BUF_SZ 256 /* Public key buffer */ +#endif + +/* ----- PSK Build Option ----- */ + +/* Nations build enables PSK by default; can also be set independently */ +#if defined(WOLFSPDM_NATIONS) && !defined(WOLFTPM_SPDM_PSK) + #define WOLFTPM_SPDM_PSK +#endif + +/* ----- PSK Message Codes (SPDM 1.2+ DSP0274) ----- */ + +#define SPDM_PSK_EXCHANGE 0xE6 +#define SPDM_PSK_EXCHANGE_RSP 0x66 +#define SPDM_PSK_FINISH 0xE7 +#define SPDM_PSK_FINISH_RSP 0x67 + +/* ----- PSK Size Limits ----- */ + +#define WOLFSPDM_PSK_MAX_SIZE 64 /* Max PSK size (Nations NS350) */ +#define WOLFSPDM_PSK_HINT_MAX 32 /* Max PSK hint size */ +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_TYPES_H */ diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index a01eacf2..3acbaf40 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -262,10 +262,13 @@ 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 +#if defined(WOLFTPM_NATIONS) || defined(WOLFTPM_AUTODETECT) + TPM_CC_Nations_IdentityKeySet = 0x20000708, +#endif #if defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673) TPM_CC_FieldUpgradeStartVendor = CC_VEND + 0x12F, TPM_CC_FieldUpgradeAbandonVendor = CC_VEND + 0x130, @@ -502,12 +505,85 @@ typedef enum { TPM_CAP_ECC_CURVES = 0x00000008, TPM_CAP_AUTH_POLICIES = 0x00000009, TPM_CAP_ACT = 0x0000000A, +#if defined(WOLFTPM_NATIONS) || defined(WOLFTPM_AUTODETECT) + TPM_CAP_PUB_KEYS = 0x0000000B, /* TPM 184: SPDM identity keys */ + TPM_CAP_SPDM_SESSION_INFO = 0x0000000C, /* TPM 184: SPDM session info */ + TPM_CAP_LAST = TPM_CAP_SPDM_SESSION_INFO, +#else TPM_CAP_LAST = TPM_CAP_ACT, +#endif TPM_CAP_VENDOR_PROPERTY = 0x00000100, } 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 +892,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 +1124,6 @@ typedef struct TPML_ACT_DATA { TPMS_ACT_DATA actData[MAX_ACT_DATA]; } TPML_ACT_DATA; - /* Capabilities Structures */ typedef union TPMU_CAPABILITIES { @@ -1902,6 +1980,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 +2011,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 +2731,7 @@ typedef struct { } PolicyAuthValue_In; WOLFTPM_API TPM_RC TPM2_PolicyAuthValue(PolicyAuthValue_In* in); + typedef struct { TPMI_SH_POLICY policySession; } PolicyPassword_In; @@ -3142,6 +3224,53 @@ 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 */ + +#ifdef WOLFTPM_NATIONS + /* Nations Technology NS350 Vendor Commands */ + typedef struct { + TPMI_RH_PLATFORM authHandle; + UINT32 configuration; /* 1 = set (provision), 0 = unset */ + } Nations_IdentityKeySet_In; + WOLFTPM_API int TPM2_Nations_IdentityKeySet(Nations_IdentityKeySet_In* in); +#endif /* WOLFTPM_NATIONS */ + /* Non-standard API's */ 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..0a1935b3 --- /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 + /* Static wolfSPDM context buffer, aligned for WOLFSPDM_CTX cast */ + XGEN_ALIGN 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) + * -------------------------------------------------------------------------- */ + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + +/** + * 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 || WOLFSPDM_NATIONS */ + +#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 d37dee13..48ab1466 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -767,6 +767,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 a7114037..678a6578 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,261 @@ 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); + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +/*! + \ingroup wolfTPM2_Wrappers + \brief Get the TPM's SPDM-Identity public key (shared TCG function). + + \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); +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#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 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 */ + +#ifdef WOLFSPDM_NATIONS +/* Nations Technology NS350 SPDM functions (requires --enable-nations --enable-spdm) */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Configure for Nations Technology NS350 SPDM mode. + Must be called before wolfTPM2_SpdmConnectNations(). + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmSetNationsMode(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish Nations SPDM secure session (identity key mode). + Uses TCG 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, or NULL for auto-gen) + \param reqPubKeySz size of reqPubKey in bytes + \param reqPrivKey host's ECDSA P-384 private key (raw 48 bytes, or NULL for auto-gen) + \param reqPrivKeySz size of reqPrivKey in bytes +*/ +WOLFTPM_API int wolfTPM2_SpdmConnectNations(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Set/unset SPDM identity key provisioning on Nations NS350. + Uses vendor command TPM2_VendorSpdmIdentityKeySet (0x20000708). + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param set 1 to provision identity key, 0 to un-provision +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsIdentityKeySet(WOLFTPM2_DEV* dev, int set); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish Nations SPDM secure session (PSK mode). +*/ +WOLFTPM_API int wolfTPM2_SpdmConnectNationsPsk(WOLFTPM2_DEV* dev, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get SPDM status from Nations TPM (PSK mode). +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsGetStatus(WOLFTPM2_DEV* dev, + WOLFSPDM_NATIONS_STATUS* status); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Lock or unlock SPDM-only mode on Nations TPM (PSK mode). +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsSetOnlyMode(WOLFTPM2_DEV* dev, int lock); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Provision PSK on Nations TPM. +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsPskSet(WOLFTPM2_DEV* dev, + const byte* psk, word32 pskSz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Clear PSK from Nations TPM. Requires ClearAuth from PSK_SET. +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsPskClear(WOLFTPM2_DEV* dev, + const byte* clearAuth, word32 clearAuthSz); + +#endif /* WOLFSPDM_NATIONS */ + +#endif /* WOLFTPM_SPDM */ /*! \ingroup wolfTPM2_Wrappers diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 41059918..0c3f79d8 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -6,6 +6,10 @@ if(CONFIG_WOLFTPM) ${ZEPHYR_CURRENT_MODULE_DIR}/src/*.c ${ZEPHYR_CURRENT_MODULE_DIR}/hal/*.c ) + # Exclude transport backends not applicable to Zephyr + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_linux\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_winapi\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_spdm\\.c$") target_sources(app PRIVATE ${wolftpm_sources}) if(CONFIG_WOLFTPM_DEBUG) From 5a2fee018a08445633de7ab677ba263566c0ae65 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 24 Mar 2026 23:32:08 +0000 Subject: [PATCH 2/5] File Reorginization and review feedback - Moved spdm/src/*.c src/spdm/*.c (11 source files) - Moved spdm/wolfspdm/*.h wolftpm/spdm/*.h (7 public headers) - Moved spdm/src/spdm_internal.h src/spdm/spdm_internal.h - Moved spdm/test/unit_test.c src/spdm/unit_test.c - Moved spdm/README.md src/spdm/README.md - Deleted spdm/include.am, created src/spdm/include.am - Renamed all #include #include across all files - Added SPDM headers to wolftpm/include.am (as nobase_include_HEADERS) - Updated Makefile.am: include spdm/include.am include src/spdm/include.am - Removed -I/spdm from configure.ac and examples/spdm/include.am (no longer needed --- .github/workflows/make-test-swtpm.yml | 12 ++--- configure.ac | 15 ------ src/spdm/README.md | 4 +- src/spdm/spdm_context.c | 28 ++++++---- src/spdm/spdm_crypto.c | 8 +++ src/spdm/spdm_internal.h | 77 +++++++++++++-------------- src/spdm/spdm_kdf.c | 8 +++ src/spdm/spdm_msg.c | 8 +++ src/spdm/spdm_nations.c | 8 +++ src/spdm/spdm_nuvoton.c | 8 +++ src/spdm/spdm_psk.c | 8 +++ src/spdm/spdm_secured.c | 8 +++ src/spdm/spdm_session.c | 8 +++ src/spdm/spdm_tcg.c | 8 +++ src/spdm/spdm_transcript.c | 8 +++ src/spdm/unit_test.c | 6 +-- src/tpm2_spdm.c | 4 +- wolftpm/spdm/spdm.h | 50 ++++++++--------- wolftpm/spdm/spdm_error.h | 2 +- wolftpm/spdm/spdm_nations.h | 10 ++-- wolftpm/spdm/spdm_nuvoton.h | 4 +- wolftpm/spdm/spdm_psk.h | 14 ++--- wolftpm/spdm/spdm_tcg.h | 24 ++++----- wolftpm/spdm/spdm_types.h | 10 +--- wolftpm/tpm2_spdm.h | 2 +- 25 files changed, 203 insertions(+), 139 deletions(-) diff --git a/.github/workflows/make-test-swtpm.yml b/.github/workflows/make-test-swtpm.yml index 3568172e..11dc197f 100644 --- a/.github/workflows/make-test-swtpm.yml +++ b/.github/workflows/make-test-swtpm.yml @@ -80,10 +80,10 @@ jobs: 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 + # SPDM small stack (heap-allocated SPDM context) + - name: spdm-smallstack wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp - wolftpm_config: --enable-spdm --enable-nuvoton --enable-spdm-dynamic-mem + wolftpm_config: --enable-spdm --enable-nuvoton --enable-smallstack needs_swtpm: false # SPDM debug - name: spdm-debug @@ -100,10 +100,10 @@ jobs: wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp wolftpm_config: --enable-spdm --enable-nations --enable-debug needs_swtpm: false - # SPDM + Nations dynamic memory - - name: spdm-nations-dynamic-mem + # SPDM + Nations small stack (heap-allocated SPDM context) + - name: spdm-nations-smallstack wolfssl_config: --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp - wolftpm_config: --enable-spdm --enable-nations --enable-spdm-dynamic-mem + wolftpm_config: --enable-spdm --enable-nations --enable-smallstack needs_swtpm: false # Microchip - name: microchip diff --git a/configure.ac b/configure.ac index 60346e35..f424aa1d 100644 --- a/configure.ac +++ b/configure.ac @@ -486,13 +486,6 @@ 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]) @@ -511,11 +504,6 @@ then AC_MSG_NOTICE([Nations Technology 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]) @@ -689,6 +677,3 @@ echo " * Nations Tech NS350: $ENABLED_NATIONS" 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/src/spdm/README.md b/src/spdm/README.md index b0a9d8ef..0f6e01a0 100644 --- a/src/spdm/README.md +++ b/src/spdm/README.md @@ -184,7 +184,7 @@ make | `--enable-nuvoton` | Enable Nuvoton TPM hardware support | | `--enable-nations` | Enable Nations NS350 hardware support | | `--enable-debug` | Debug output with verbose SPDM tracing | -| `--enable-spdm-dynamic-mem` | Heap-allocated SPDM context (default: static ~32 KB) | +| `--enable-smallstack` | Heap-allocated SPDM context (default: static ~32 KB) | ## Usage @@ -418,7 +418,7 @@ intercepted and routed through SPDM when a session is active. **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. +**Small stack (`--enable-smallstack`):** Context is heap-allocated. Useful on platforms with small stacks. ## wolfSPDM API diff --git a/src/spdm/spdm_context.c b/src/spdm/spdm_context.c index a493c45b..d0ae493b 100644 --- a/src/spdm/spdm_context.c +++ b/src/spdm/spdm_context.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + #include "spdm_internal.h" #include #include @@ -53,7 +59,7 @@ int wolfSPDM_Init(WOLFSPDM_CTX* ctx) return WOLFSPDM_SUCCESS; } -#ifdef WOLFSPDM_DYNAMIC_MEMORY +#ifdef WOLFTPM_SMALL_STACK WOLFSPDM_CTX* wolfSPDM_New(void) { WOLFSPDM_CTX* ctx; @@ -72,18 +78,17 @@ WOLFSPDM_CTX* wolfSPDM_New(void) return ctx; } -#endif /* WOLFSPDM_DYNAMIC_MEMORY */ +#endif /* WOLFTPM_SMALL_STACK */ void wolfSPDM_Free(WOLFSPDM_CTX* ctx) { + int wasDynamic; + if (ctx == NULL) { return; } -#ifdef WOLFSPDM_DYNAMIC_MEMORY - { - int wasDynamic = ctx->flags.isDynamic; -#endif + wasDynamic = ctx->flags.isDynamic; /* Free RNG */ if (ctx->flags.rngInitialized) { @@ -98,11 +103,12 @@ void wolfSPDM_Free(WOLFSPDM_CTX* ctx) /* 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); - } +#ifdef WOLFTPM_SMALL_STACK + if (wasDynamic) { + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); } +#else + (void)wasDynamic; #endif } @@ -547,3 +553,5 @@ const char* wolfSPDM_GetErrorString(int error) default: return "Unknown error"; } } + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_crypto.c b/src/spdm/spdm_crypto.c index 8ccc596c..6274beb7 100644 --- a/src/spdm/spdm_crypto.c +++ b/src/spdm/spdm_crypto.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + #include "spdm_internal.h" /* Left-pad a buffer in-place to targetSz with leading zeros */ @@ -346,3 +352,5 @@ int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; } + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_internal.h b/src/spdm/spdm_internal.h index a8533fd3..90aec887 100644 --- a/src/spdm/spdm_internal.h +++ b/src/spdm/spdm_internal.h @@ -72,17 +72,6 @@ struct WOLFSPDM_CTX { /* State machine */ int state; - /* Boolean flag bit field */ - struct { - unsigned int debug : 1; - unsigned int initialized : 1; - unsigned int isDynamic : 1; /* Set by wolfSPDM_New(), checked by Free */ - unsigned int rngInitialized : 1; - unsigned int ephemeralKeyInit : 1; - unsigned int hasRspPubKey : 1; - unsigned int hasReqKeyPair : 1; - } flags; - /* Protocol mode */ WOLFSPDM_MODE mode; @@ -166,6 +155,16 @@ struct WOLFSPDM_CTX { word32 reqPrivKeyLen; byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; + /* Boolean flag bit field (at end for better struct packing) */ + struct { + unsigned int debug : 1; + unsigned int initialized : 1; + unsigned int isDynamic : 1; /* Set by wolfSPDM_New(), checked by Free */ + unsigned int rngInitialized : 1; + unsigned int ephemeralKeyInit : 1; + unsigned int hasRspPubKey : 1; + unsigned int hasReqKeyPair : 1; + } flags; }; /* ----- Byte-Order Helpers ----- */ @@ -276,78 +275,78 @@ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, /* ----- Internal Function Declarations - Transcript ----- */ -WOLFSPDM_API void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); -WOLFSPDM_API int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); -WOLFSPDM_API int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); -WOLFSPDM_API int wolfSPDM_Sha384Hash(byte* out, +WOLFTPM_LOCAL void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); +WOLFTPM_LOCAL int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); +WOLFTPM_LOCAL int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); +WOLFTPM_LOCAL int wolfSPDM_Sha384Hash(byte* out, const byte* d1, word32 d1Sz, const byte* d2, word32 d2Sz, const byte* d3, word32 d3Sz); /* ----- Internal Function Declarations - Crypto ----- */ -WOLFSPDM_API int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); -WOLFSPDM_API int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, +WOLFTPM_LOCAL int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); +WOLFTPM_LOCAL int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, byte* pubKeyX, word32* pubKeyXSz, byte* pubKeyY, word32* pubKeyYSz); -WOLFSPDM_API int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, +WOLFTPM_LOCAL int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, const byte* peerPubKeyX, const byte* peerPubKeyY); -WOLFSPDM_API int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); -WOLFSPDM_API int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, +WOLFTPM_LOCAL int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); +WOLFTPM_LOCAL int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, byte* sig, word32* sigSz); -WOLFSPDM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, +WOLFTPM_LOCAL int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, const byte* sig, word32 sigSz); /* ----- Internal Function Declarations - Key Derivation ----- */ -WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); -WOLFSPDM_API int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, const byte* th1Hash); -WOLFSPDM_API int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx); -WOLFSPDM_API int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, +WOLFTPM_LOCAL int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); +WOLFTPM_LOCAL int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, const byte* th1Hash); +WOLFTPM_LOCAL int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx); +WOLFTPM_LOCAL int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, const char* label, const byte* context, word32 contextSz, byte* out, word32 outSz); -WOLFSPDM_API int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, +WOLFTPM_LOCAL int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, byte* verifyData); /* ----- Internal Function Declarations - Message Building ----- */ -WOLFSPDM_API int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); -WOLFSPDM_API int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); -WOLFSPDM_API int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); -WOLFSPDM_API int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFTPM_LOCAL int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); +WOLFTPM_LOCAL int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFTPM_LOCAL int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFTPM_LOCAL int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); /* PSK message builders/parsers declared in spdm_psk.h */ /* ----- Internal Function Declarations - Message Parsing ----- */ -WOLFSPDM_API int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); -WOLFSPDM_API int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); -WOLFSPDM_API int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); -WOLFSPDM_API int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); +WOLFTPM_LOCAL int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFTPM_LOCAL int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFTPM_LOCAL int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFTPM_LOCAL int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); /* ----- Internal Function Declarations - Secured Messaging ----- */ -WOLFSPDM_API int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, +WOLFTPM_LOCAL int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, const byte* plain, word32 plainSz, byte* enc, word32* encSz); -WOLFSPDM_API int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, +WOLFTPM_LOCAL int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, const byte* enc, word32 encSz, byte* plain, word32* plainSz); /* ----- Internal Utility Functions ----- */ -WOLFSPDM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, +WOLFTPM_LOCAL int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); #ifdef DEBUG_WOLFTPM -WOLFSPDM_API void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) +WOLFTPM_LOCAL void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) #ifdef __GNUC__ __attribute__((format(printf, 2, 3))) #endif ; -WOLFSPDM_API void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, +WOLFTPM_LOCAL void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, const byte* data, word32 len); #else #define wolfSPDM_DebugPrint(ctx, fmt, ...) do { (void)(ctx); (void)fmt; } while(0) diff --git a/src/spdm/spdm_kdf.c b/src/spdm/spdm_kdf.c index 0d3bc79c..8e72fd62 100644 --- a/src/spdm/spdm_kdf.c +++ b/src/spdm/spdm_kdf.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + #include "spdm_internal.h" /* SPDM key derivation (DSP0277): HKDF with @@ -262,3 +268,5 @@ int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx) return rc; } + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_msg.c b/src/spdm/spdm_msg.c index 101b4567..ee02fccb 100644 --- a/src/spdm/spdm_msg.c +++ b/src/spdm/spdm_msg.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + #include "spdm_internal.h" int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz) @@ -539,3 +545,5 @@ int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) } /* PSK message builders/parsers moved to spdm_psk.c */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_nations.c b/src/spdm/spdm_nations.c index 72d314c2..f24a9294 100644 --- a/src/spdm/spdm_nations.c +++ b/src/spdm/spdm_nations.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + /* Nations Technology NS350 SPDM Functions * * PSK-mode vendor commands and PSK connection flow. @@ -172,3 +178,5 @@ int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, * wolfSPDM_ConnectNationsPsk is a backward-compat alias in spdm_psk.h. */ #endif /* WOLFSPDM_NATIONS */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_nuvoton.c b/src/spdm/spdm_nuvoton.c index f59e6d13..275dae1c 100644 --- a/src/spdm/spdm_nuvoton.c +++ b/src/spdm/spdm_nuvoton.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + /* Nuvoton-specific SPDM functions (GetStatus, SetOnlyMode). */ #include "spdm_internal.h" @@ -110,3 +116,5 @@ int wolfSPDM_Nuvoton_SetOnlyMode( } #endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_psk.c b/src/spdm/spdm_psk.c index ce1919ff..7e2cf280 100644 --- a/src/spdm/spdm_psk.c +++ b/src/spdm/spdm_psk.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + /* Shared SPDM PSK protocol code used by Nations (and future Infineon). */ #include "spdm_internal.h" @@ -435,3 +441,5 @@ int wolfSPDM_ConnectPsk(WOLFSPDM_CTX* ctx) } #endif /* WOLFTPM_SPDM_PSK */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_secured.c b/src/spdm/spdm_secured.c index 55c82ce0..9de0866a 100644 --- a/src/spdm/spdm_secured.c +++ b/src/spdm/spdm_secured.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + #include "spdm_internal.h" /* @@ -352,3 +358,5 @@ int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, return rc; } + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_session.c b/src/spdm/spdm_session.c index 82fc0c88..91a455a7 100644 --- a/src/spdm/spdm_session.c +++ b/src/spdm/spdm_session.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + #include "spdm_internal.h" /* Callback types for build/parse functions */ @@ -146,3 +152,5 @@ int wolfSPDM_Finish(WOLFSPDM_CTX* ctx) wc_ForceZero(decBuf, sizeof(decBuf)); return rc; } + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_tcg.c b/src/spdm/spdm_tcg.c index 57db8a5f..34acffdd 100644 --- a/src/spdm/spdm_tcg.c +++ b/src/spdm/spdm_tcg.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + /* Shared TCG SPDM code used by both Nuvoton and Nations Technology TPMs. */ #include "spdm_internal.h" @@ -570,3 +576,5 @@ int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx) } #endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/spdm_transcript.c b/src/spdm/spdm_transcript.c index 02db21f7..8ae87f6e 100644 --- a/src/spdm/spdm_transcript.c +++ b/src/spdm/spdm_transcript.c @@ -19,6 +19,12 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#ifdef HAVE_CONFIG_H + #include +#endif + +#ifdef WOLFTPM_SPDM + #include "spdm_internal.h" /* ----- Transcript Management ----- @@ -95,3 +101,5 @@ int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash) return wolfSPDM_Sha384Hash(hash, ctx->transcript, ctx->transcriptLen, NULL, 0, NULL, 0); } + +#endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/unit_test.c b/src/spdm/unit_test.c index e5742cc3..1e21e0fb 100644 --- a/src/spdm/unit_test.c +++ b/src/spdm/unit_test.c @@ -76,7 +76,7 @@ static int dummy_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, /* ----- Context Tests ----- */ -#ifdef WOLFSPDM_DYNAMIC_MEMORY +#ifdef WOLFTPM_SMALL_STACK static int test_context_new_free(void) { WOLFSPDM_CTX* ctx; @@ -93,7 +93,7 @@ static int test_context_new_free(void) TEST_PASS(); } -#endif /* WOLFSPDM_DYNAMIC_MEMORY */ +#endif /* WOLFTPM_SMALL_STACK */ static int test_context_init(void) { @@ -936,7 +936,7 @@ int main(void) printf("===========================================\n\n"); /* Context tests */ -#ifdef WOLFSPDM_DYNAMIC_MEMORY +#ifdef WOLFTPM_SMALL_STACK test_context_new_free(); #endif test_context_init(); diff --git a/src/tpm2_spdm.c b/src/tpm2_spdm.c index 9fba7200..a79d1751 100644 --- a/src/tpm2_spdm.c +++ b/src/tpm2_spdm.c @@ -137,8 +137,8 @@ int wolfTPM2_SPDM_InitCtx( /* Zero initialize context */ XMEMSET(ctx, 0, sizeof(WOLFTPM2_SPDM_CTX)); -#ifdef WOLFSPDM_DYNAMIC_MEMORY - /* Dynamic path: allocate and initialize via wolfSPDM_New() */ +#ifdef WOLFTPM_SMALL_STACK + /* Heap path: allocate and initialize via wolfSPDM_New() */ ctx->spdmCtx = wolfSPDM_New(); if (ctx->spdmCtx == NULL) { return MEMORY_E; diff --git a/wolftpm/spdm/spdm.h b/wolftpm/spdm/spdm.h index 0f92d1b8..177b6744 100644 --- a/wolftpm/spdm/spdm.h +++ b/wolftpm/spdm/spdm.h @@ -22,8 +22,8 @@ #ifndef WOLFSPDM_SPDM_H #define WOLFSPDM_SPDM_H -#ifndef HAVE_CONFIG_H - #include +#ifdef HAVE_CONFIG_H + #include #endif #include @@ -91,54 +91,54 @@ typedef int (*WOLFSPDM_IO_CB)( ); /* Context management */ -WOLFSPDM_API int wolfSPDM_Init(WOLFSPDM_CTX* ctx); -#ifdef WOLFSPDM_DYNAMIC_MEMORY -WOLFSPDM_API WOLFSPDM_CTX* wolfSPDM_New(void); +WOLFTPM_API int wolfSPDM_Init(WOLFSPDM_CTX* ctx); +#ifdef WOLFTPM_SMALL_STACK +WOLFTPM_API WOLFSPDM_CTX* wolfSPDM_New(void); #endif -WOLFSPDM_API void wolfSPDM_Free(WOLFSPDM_CTX* ctx); -WOLFSPDM_API int wolfSPDM_GetCtxSize(void); -WOLFSPDM_API int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size); +WOLFTPM_API void wolfSPDM_Free(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_GetCtxSize(void); +WOLFTPM_API int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size); /* Configuration */ -WOLFSPDM_API int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, +WOLFTPM_API int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx); -WOLFSPDM_API int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode); -WOLFSPDM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode); +WOLFTPM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); /* Set responder pub key for cert-less operation (96 bytes P-384 X||Y) */ -WOLFSPDM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, const byte* pubKey, word32 pubKeySz); /* Set requester key pair for mutual auth (privKey=48, pubKey=96 bytes) */ -WOLFSPDM_API int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, const byte* privKey, word32 privKeySz, const byte* pubKey, word32 pubKeySz); /* Session establishment */ -WOLFSPDM_API int wolfSPDM_Connect(WOLFSPDM_CTX* ctx); -WOLFSPDM_API int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx); -WOLFSPDM_API int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_Connect(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx); /* Individual handshake steps (for fine-grained control) */ -WOLFSPDM_API int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx); -WOLFSPDM_API int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx); -WOLFSPDM_API int wolfSPDM_Finish(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_Finish(WOLFSPDM_CTX* ctx); /* Secured messaging: encrypt, send, receive, decrypt in one call */ -WOLFSPDM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, const byte* cmdPlain, word32 cmdSz, byte* rspPlain, word32* rspSz); /* Session info */ -WOLFSPDM_API word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx); -WOLFSPDM_API byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx); +WOLFTPM_API word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx); +WOLFTPM_API byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx); #if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) -WOLFSPDM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); -WOLFSPDM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); +WOLFTPM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); +WOLFTPM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); #endif /* wolfSPDM_SetPSK declared in spdm_psk.h */ /* Debug */ -WOLFSPDM_API void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable); +WOLFTPM_API void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable); #ifdef __cplusplus } diff --git a/wolftpm/spdm/spdm_error.h b/wolftpm/spdm/spdm_error.h index 3550b406..4fff17c9 100644 --- a/wolftpm/spdm/spdm_error.h +++ b/wolftpm/spdm/spdm_error.h @@ -51,7 +51,7 @@ enum WOLFSPDM_ERROR { }; /* Get human-readable error string */ -WOLFSPDM_API const char* wolfSPDM_GetErrorString(int error); +WOLFTPM_API const char* wolfSPDM_GetErrorString(int error); #ifdef __cplusplus } diff --git a/wolftpm/spdm/spdm_nations.h b/wolftpm/spdm/spdm_nations.h index e7842425..01d93021 100644 --- a/wolftpm/spdm/spdm_nations.h +++ b/wolftpm/spdm/spdm_nations.h @@ -74,18 +74,18 @@ typedef struct WOLFSPDM_NATIONS_STATUS { /* ----- Nations PSK-Mode SPDM Functions ----- */ -WOLFSPDM_API int wolfSPDM_Nations_GetStatus(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_Nations_GetStatus(WOLFSPDM_CTX* ctx, WOLFSPDM_NATIONS_STATUS* status); -WOLFSPDM_API int wolfSPDM_Nations_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock); +WOLFTPM_API int wolfSPDM_Nations_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock); -WOLFSPDM_API int wolfSPDM_Nations_PskSet(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_Nations_PskSet(WOLFSPDM_CTX* ctx, const byte* psk, word32 pskSz); -WOLFSPDM_API int wolfSPDM_Nations_PskClear(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_Nations_PskClear(WOLFSPDM_CTX* ctx, const byte* clearAuth, word32 clearAuthSz); -WOLFSPDM_API int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, const byte* clearAuth, word32 clearAuthSz); /* wolfSPDM_ConnectNationsPsk is an alias for wolfSPDM_ConnectPsk (spdm_psk.h) */ diff --git a/wolftpm/spdm/spdm_nuvoton.h b/wolftpm/spdm/spdm_nuvoton.h index 7aef79e2..f1d2b5e3 100644 --- a/wolftpm/spdm/spdm_nuvoton.h +++ b/wolftpm/spdm/spdm_nuvoton.h @@ -59,11 +59,11 @@ typedef struct WOLFSPDM_NUVOTON_STATUS { /* ----- Nuvoton-Only Functions ----- */ -WOLFSPDM_API int wolfSPDM_Nuvoton_GetStatus( +WOLFTPM_API int wolfSPDM_Nuvoton_GetStatus( WOLFSPDM_CTX* ctx, WOLFSPDM_NUVOTON_STATUS* status); -WOLFSPDM_API int wolfSPDM_Nuvoton_SetOnlyMode( +WOLFTPM_API int wolfSPDM_Nuvoton_SetOnlyMode( WOLFSPDM_CTX* ctx, int lock); diff --git a/wolftpm/spdm/spdm_psk.h b/wolftpm/spdm/spdm_psk.h index cd8f146b..6ae7049a 100644 --- a/wolftpm/spdm/spdm_psk.h +++ b/wolftpm/spdm/spdm_psk.h @@ -44,27 +44,27 @@ extern "C" { /* ----- PSK Context Setup ----- */ -WOLFSPDM_API int wolfSPDM_SetPSK(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_SetPSK(WOLFSPDM_CTX* ctx, const byte* psk, word32 pskSz, const byte* hint, word32 hintSz); /* ----- PSK Message Builders/Parsers ----- */ -WOLFSPDM_API int wolfSPDM_BuildPskExchange(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_BuildPskExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); -WOLFSPDM_API int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); -WOLFSPDM_API int wolfSPDM_BuildPskFinish(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_BuildPskFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); -WOLFSPDM_API int wolfSPDM_ParsePskFinishRsp(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_ParsePskFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); /* ----- PSK Key Derivation ----- */ -WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeysPsk(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_DeriveHandshakeKeysPsk(WOLFSPDM_CTX* ctx, const byte* th1Hash); /* ----- Shared PSK Connection Flow ----- */ @@ -77,7 +77,7 @@ WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeysPsk(WOLFSPDM_CTX* ctx, * @param ctx wolfSPDM context (must have PSK set via wolfSPDM_SetPSK) * @return WOLFSPDM_SUCCESS or negative error code */ -WOLFSPDM_API int wolfSPDM_ConnectPsk(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_ConnectPsk(WOLFSPDM_CTX* ctx); /* Backward compatibility */ #define wolfSPDM_ConnectNationsPsk wolfSPDM_ConnectPsk diff --git a/wolftpm/spdm/spdm_tcg.h b/wolftpm/spdm/spdm_tcg.h index dedcd95b..5a5d9e4d 100644 --- a/wolftpm/spdm/spdm_tcg.h +++ b/wolftpm/spdm/spdm_tcg.h @@ -92,53 +92,53 @@ typedef struct { /* ----- Vendor Command Helpers ----- */ -WOLFSPDM_API int wolfSPDM_TCG_VendorCmdClear(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_TCG_VendorCmdClear(WOLFSPDM_CTX* ctx, const char* vdCode, const byte* payload, word32 payloadSz, WOLFSPDM_VENDOR_RSP* rsp); -WOLFSPDM_API int wolfSPDM_TCG_VendorCmdSecured(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_TCG_VendorCmdSecured(WOLFSPDM_CTX* ctx, const char* vdCode, const byte* payload, word32 payloadSz); /* ----- TCG Binding Message Framing ----- */ -WOLFSPDM_API int wolfSPDM_BuildTcgClearMessage( +WOLFTPM_API int wolfSPDM_BuildTcgClearMessage( WOLFSPDM_CTX* ctx, const byte* spdmPayload, word32 spdmPayloadSz, byte* outBuf, word32 outBufSz); -WOLFSPDM_API int wolfSPDM_ParseTcgClearMessage( +WOLFTPM_API int wolfSPDM_ParseTcgClearMessage( const byte* inBuf, word32 inBufSz, byte* spdmPayload, word32* spdmPayloadSz, WOLFSPDM_TCG_CLEAR_HDR* hdr); /* ----- Vendor-Defined Message Helpers ----- */ -WOLFSPDM_API int wolfSPDM_BuildVendorDefined( +WOLFTPM_API int wolfSPDM_BuildVendorDefined( byte spdmVersion, const char* vdCode, const byte* payload, word32 payloadSz, byte* outBuf, word32 outBufSz); -WOLFSPDM_API int wolfSPDM_ParseVendorDefined( +WOLFTPM_API int wolfSPDM_ParseVendorDefined( const byte* inBuf, word32 inBufSz, char* vdCode, byte* payload, word32* payloadSz); /* ----- Shared TCG SPDM Functions ----- */ -WOLFSPDM_API int wolfSPDM_TCG_GetPubKey(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_TCG_GetPubKey(WOLFSPDM_CTX* ctx, byte* pubKey, word32* pubKeySz); -WOLFSPDM_API int wolfSPDM_TCG_GivePubKey(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_TCG_GivePubKey(WOLFSPDM_CTX* ctx, const byte* pubKey, word32 pubKeySz); -WOLFSPDM_API int wolfSPDM_TCG_GetCapabilities(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_TCG_GetCapabilities(WOLFSPDM_CTX* ctx, word32 capsFlags); -WOLFSPDM_API int wolfSPDM_TCG_NegotiateAlgorithms(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_TCG_NegotiateAlgorithms(WOLFSPDM_CTX* ctx); -WOLFSPDM_API int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, const byte* tpmtPub, word32 tpmtPubSz); -WOLFSPDM_API int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx); /* Backward compatibility aliases */ #define wolfSPDM_ConnectNuvoton wolfSPDM_ConnectTCG diff --git a/wolftpm/spdm/spdm_types.h b/wolftpm/spdm/spdm_types.h index 67e32a0e..0fb29cf7 100644 --- a/wolftpm/spdm/spdm_types.h +++ b/wolftpm/spdm/spdm_types.h @@ -28,15 +28,7 @@ #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 +#include #ifdef __cplusplus extern "C" { diff --git a/wolftpm/tpm2_spdm.h b/wolftpm/tpm2_spdm.h index 0a1935b3..ba2c28a0 100644 --- a/wolftpm/tpm2_spdm.h +++ b/wolftpm/tpm2_spdm.h @@ -83,7 +83,7 @@ typedef struct WOLFTPM2_SPDM_CTX { /* SPDM-only mode tracking (for Nuvoton TPMs) */ int spdmOnlyLocked; -#ifndef WOLFSPDM_DYNAMIC_MEMORY +#ifndef WOLFTPM_SMALL_STACK /* Static wolfSPDM context buffer, aligned for WOLFSPDM_CTX cast */ XGEN_ALIGN byte spdmBuf[WOLFSPDM_CTX_STATIC_SIZE]; #endif From ddf990a4c9ed9cde88d307641f4d32a141ea2dd4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 25 Mar 2026 18:59:32 +0000 Subject: [PATCH 3/5] Address review feedback - Renamed spdm_demo to spdm_ctrl: file, binary, internal demo_* functions to ctrl_*, all references in include.am, spdm_test.sh, .gitignore, CLAUDE.md, both READMEs - README mentions Nations: title updated to Nuvoton NPCT75x and Nations NS350 TPMs, added Nations build section - README section header renamed from Demo Commands to Setup/Control Commands - README added reset pin control section: documents GPIO reset requirement, Pi-specific example, custom hardware design guidance - Moved spdm_tcg.c to common build section, no longer conditional on Nuvoton/Nations in src/spdm/include.am - Removed redundant wolfSSL options include from spdm_internal.h since tpm2_types.h handles this - Added WOLFTPM_SPDM_TCG generic guard as auto-define in spdm_types.h, replaced ~30 occurrences of #if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) across all files --- .gitignore | 2 +- examples/spdm/README.md | 71 ++++++++++++++++------ examples/spdm/include.am | 14 ++--- examples/spdm/{spdm_demo.c => spdm_ctrl.c} | 70 ++++++++++----------- examples/spdm/spdm_test.sh | 4 +- src/spdm/README.md | 48 +++++++-------- src/spdm/include.am | 8 +-- src/spdm/spdm_context.c | 12 ++-- src/spdm/spdm_internal.h | 11 +--- src/spdm/spdm_msg.c | 4 +- src/spdm/spdm_secured.c | 6 +- src/spdm/spdm_tcg.c | 4 +- src/spdm/unit_test.c | 6 +- src/tpm2_spdm.c | 10 +-- src/tpm2_wrap.c | 4 +- wolftpm/spdm/spdm.h | 4 +- wolftpm/spdm/spdm_tcg.h | 4 +- wolftpm/spdm/spdm_types.h | 8 +++ wolftpm/tpm2_spdm.h | 4 +- wolftpm/tpm2_wrap.h | 4 +- 20 files changed, 167 insertions(+), 131 deletions(-) rename examples/spdm/{spdm_demo.c => spdm_ctrl.c} (92%) diff --git a/.gitignore b/.gitignore index 222f771e..83f99344 100644 --- a/.gitignore +++ b/.gitignore @@ -92,7 +92,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 +examples/spdm/spdm_ctrl # Generated Cert Files certs/ca-*.pem diff --git a/examples/spdm/README.md b/examples/spdm/README.md index 377cd485..9c939c3e 100644 --- a/examples/spdm/README.md +++ b/examples/spdm/README.md @@ -1,12 +1,17 @@ -# TPM SPDM Examples +# TPM SPDM Setup/Control -This directory contains the SPDM demo for Nuvoton NPCT75x TPMs with wolfTPM. +This directory contains the SPDM setup and control tool for Nuvoton NPCT75x +and Nations NS350 TPMs with wolfTPM. ## Overview -The `spdm_demo` establishes an SPDM secure session between the host and a -Nuvoton TPM over SPI, enabling AES-256-GCM encrypted bus communication. Once -active, all TPM commands are automatically encrypted with no application changes. +The `spdm_ctrl` tool establishes SPDM secure sessions between the host and a +TPM over SPI, enabling AES-256-GCM encrypted bus communication. Once active, +all TPM commands are automatically encrypted with no application changes. + +Supported hardware: +- **Nuvoton NPCT75x** — Identity key mode (ECDHE P-384) +- **Nations NS350** — Identity key mode + PSK mode For standard SPDM protocol support (spdm-emu, measurements, challenge, etc.), see the [wolfSPDM](https://github.com/aidangarske/wolfSPDM) standalone library. @@ -33,7 +38,16 @@ cd wolfTPM make ``` -## Demo Commands +### wolfTPM with Nations SPDM + +```bash +cd wolfTPM +./autogen.sh +./configure --enable-spdm --enable-nations +make +``` + +## Setup/Control Commands | Option | Description | |--------|-------------| @@ -48,39 +62,60 @@ make ## Usage Examples ```bash -# One-time setup: enable SPDM + GPIO reset -./examples/spdm/spdm_demo --enable -gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +# One-time setup: enable SPDM + reset TPM +./examples/spdm/spdm_ctrl --enable +# Reset the TPM (see "TPM Reset Pin Control" below) # Query SPDM status -./examples/spdm/spdm_demo --status +./examples/spdm/spdm_ctrl --status # Get TPM identity key -./examples/spdm/spdm_demo --get-pubkey +./examples/spdm/spdm_ctrl --get-pubkey # Establish SPDM session -./examples/spdm/spdm_demo --connect +./examples/spdm/spdm_ctrl --connect # Lock SPDM-only mode (connect + lock in one session) -./examples/spdm/spdm_demo --connect --lock -gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_ctrl --connect --lock +# Reset the TPM # All commands now auto-encrypt: ./examples/wrap/caps # auto-SPDM, AES-256-GCM encrypted ./tests/unit.test # full test suite over encrypted bus # Unlock SPDM-only mode -gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 -./examples/spdm/spdm_demo --connect --unlock +# Reset the TPM +./examples/spdm/spdm_ctrl --connect --unlock +# Reset the TPM +``` + +## TPM Reset Pin Control + +SPDM enable/disable and SPDM-only mode changes require a TPM reset to take +effect. The reset pin must be connected and controllable by the host. + +**Important for custom hardware designs:** Ensure the TPM reset pin is routed +to a host-controllable GPIO. Without reset pin control, SPDM mode changes +cannot be applied and recovery from SPDM-only mode is not possible. + +### Raspberry Pi Example (GPIO 4) + +```bash +# Assert reset low, wait, release high, wait for TPM startup gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 ``` +Other platforms will use their own GPIO control mechanism. The key requirement +is toggling the TPM reset line (active low) with sufficient hold time. + ## Automated Test Suite -Runs 6 tests: status, connect, lock, unit test over SPDM, unlock, cleartext caps setup lifecycle on hardware. +Runs the full SPDM setup lifecycle on hardware: ```bash -./examples/spdm/spdm_test.sh +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nuvoton +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nations +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nations-psk ``` ## Support diff --git a/examples/spdm/include.am b/examples/spdm/include.am index 54980203..deb6283d 100644 --- a/examples/spdm/include.am +++ b/examples/spdm/include.am @@ -3,16 +3,16 @@ if BUILD_EXAMPLES if BUILD_SPDM -noinst_PROGRAMS += examples/spdm/spdm_demo +noinst_PROGRAMS += examples/spdm/spdm_ctrl -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) +examples_spdm_spdm_ctrl_SOURCES = examples/spdm/spdm_ctrl.c +examples_spdm_spdm_ctrl_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_spdm_spdm_ctrl_DEPENDENCIES = src/libwolftpm.la +examples_spdm_spdm_ctrl_CFLAGS = $(AM_CFLAGS) endif endif example_spdmdir = $(exampledir)/spdm -dist_example_spdm_DATA = examples/spdm/spdm_demo.c +dist_example_spdm_DATA = examples/spdm/spdm_ctrl.c -DISTCLEANFILES+= examples/spdm/.libs/spdm_demo +DISTCLEANFILES+= examples/spdm/.libs/spdm_ctrl diff --git a/examples/spdm/spdm_demo.c b/examples/spdm/spdm_ctrl.c similarity index 92% rename from examples/spdm/spdm_demo.c rename to examples/spdm/spdm_ctrl.c index e543b9d5..453921d3 100644 --- a/examples/spdm/spdm_demo.c +++ b/examples/spdm/spdm_ctrl.c @@ -1,4 +1,4 @@ -/* spdm_demo.c +/* spdm_ctrl.c * * Copyright (C) 2006-2025 wolfSSL Inc. * @@ -40,12 +40,12 @@ #include #include -int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]); +int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[]); static void usage(void) { printf("SPDM Demo - TPM secure session\n\n" - "Usage: spdm_demo [options]\n" + "Usage: spdm_ctrl [options]\n" #ifdef WOLFSPDM_NUVOTON " --enable Enable SPDM via NTC2_PreConfig\n" " --disable Disable SPDM via NTC2_PreConfig\n" @@ -76,7 +76,7 @@ static void usage(void) } #ifdef WOLFSPDM_NUVOTON -static int demo_enable(WOLFTPM2_DEV* dev) +static int ctrl_enable(WOLFTPM2_DEV* dev) { int rc; printf("\n=== Enable SPDM ===\n"); @@ -95,7 +95,7 @@ static int demo_enable(WOLFTPM2_DEV* dev) return rc; } -static int demo_disable(WOLFTPM2_DEV* dev) +static int ctrl_disable(WOLFTPM2_DEV* dev) { int rc; printf("\n=== Disable SPDM ===\n"); @@ -113,7 +113,7 @@ static int demo_disable(WOLFTPM2_DEV* dev) return rc; } -static int demo_status(WOLFTPM2_DEV* dev) +static int ctrl_status(WOLFTPM2_DEV* dev) { int rc; WOLFSPDM_NUVOTON_STATUS status; @@ -143,7 +143,7 @@ static int demo_status(WOLFTPM2_DEV* dev) return rc; } -static int demo_get_pubkey(WOLFTPM2_DEV* dev) +static int ctrl_get_pubkey(WOLFTPM2_DEV* dev) { int rc; byte pubKey[128]; @@ -163,7 +163,7 @@ static int demo_get_pubkey(WOLFTPM2_DEV* dev) return rc; } -static int demo_connect(WOLFTPM2_DEV* dev) +static int ctrl_connect(WOLFTPM2_DEV* dev) { int rc; @@ -186,7 +186,7 @@ static int demo_connect(WOLFTPM2_DEV* dev) return rc; } -static int demo_lock(WOLFTPM2_DEV* dev, int lock) +static int ctrl_lock(WOLFTPM2_DEV* dev, int lock) { int rc; printf("\n=== SPDM-Only: %s ===\n", lock ? "LOCK" : "UNLOCK"); @@ -220,7 +220,7 @@ static int hex2bin(const char* hex, byte* bin, word32* binSz) return 0; } -static int demo_nations_status(WOLFTPM2_DEV* dev) +static int ctrl_nations_status(WOLFTPM2_DEV* dev) { int rc; int isConn; @@ -277,7 +277,7 @@ static int demo_nations_status(WOLFTPM2_DEV* dev) return 0; /* status is informational, don't fail */ } -static int demo_nations_psk_connect(WOLFTPM2_DEV* dev, const char* pskHex) +static int ctrl_nations_psk_connect(WOLFTPM2_DEV* dev, const char* pskHex) { int rc; byte psk[128]; @@ -301,7 +301,7 @@ static int demo_nations_psk_connect(WOLFTPM2_DEV* dev, const char* pskHex) return rc; } -static int demo_nations_psk_set(WOLFTPM2_DEV* dev, +static int ctrl_nations_psk_set(WOLFTPM2_DEV* dev, const char* pskHex, const char* clearAuthHex) { int rc; @@ -357,7 +357,7 @@ static int demo_nations_psk_set(WOLFTPM2_DEV* dev, return rc; } -static int demo_nations_psk_clear(WOLFTPM2_DEV* dev, const char* authHex) +static int ctrl_nations_psk_clear(WOLFTPM2_DEV* dev, const char* authHex) { int rc; byte clearAuth[256]; @@ -387,7 +387,7 @@ static int demo_nations_psk_clear(WOLFTPM2_DEV* dev, const char* authHex) return rc; } -static int demo_nations_identity_key_set(WOLFTPM2_DEV* dev, int set) +static int ctrl_nations_identity_key_set(WOLFTPM2_DEV* dev, int set) { int rc; printf("\n=== Nations Identity Key %s ===\n", set ? "Set" : "Unset"); @@ -400,7 +400,7 @@ static int demo_nations_identity_key_set(WOLFTPM2_DEV* dev, int set) return rc; } -static int demo_nations_get_pubkey(WOLFTPM2_DEV* dev) +static int ctrl_nations_get_pubkey(WOLFTPM2_DEV* dev) { int rc; byte pubKey[128]; @@ -428,7 +428,7 @@ static int demo_nations_get_pubkey(WOLFTPM2_DEV* dev) return rc; } -static int demo_nations_caps184(WOLFTPM2_DEV* dev) +static int ctrl_nations_caps184(WOLFTPM2_DEV* dev) { int rc; GetCapability_In capIn; @@ -505,7 +505,7 @@ static int demo_nations_caps184(WOLFTPM2_DEV* dev) return 0; } -static int demo_nations_connect(WOLFTPM2_DEV* dev) +static int ctrl_nations_connect(WOLFTPM2_DEV* dev) { int rc; @@ -529,7 +529,7 @@ static int demo_nations_connect(WOLFTPM2_DEV* dev) } #endif /* WOLFSPDM_NATIONS */ -int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) +int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[]) { int rc, i; WOLFTPM2_DEV dev; @@ -568,42 +568,42 @@ int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) for (i = 1; i < argc; i++) { #ifdef WOLFSPDM_NUVOTON if (XSTRCMP(argv[i], "--enable") == 0) - rc = demo_enable(&dev); + rc = ctrl_enable(&dev); else if (XSTRCMP(argv[i], "--disable") == 0) - rc = demo_disable(&dev); + rc = ctrl_disable(&dev); else if (XSTRCMP(argv[i], "--status") == 0) - rc = demo_status(&dev); + rc = ctrl_status(&dev); else if (XSTRCMP(argv[i], "--get-pubkey") == 0) - rc = demo_get_pubkey(&dev); + rc = ctrl_get_pubkey(&dev); else if (XSTRCMP(argv[i], "--connect") == 0) - rc = demo_connect(&dev); + rc = ctrl_connect(&dev); else if (XSTRCMP(argv[i], "--lock") == 0) - rc = demo_lock(&dev, 1); + rc = ctrl_lock(&dev, 1); else if (XSTRCMP(argv[i], "--unlock") == 0) - rc = demo_lock(&dev, 0); + rc = ctrl_lock(&dev, 0); else #endif #ifdef WOLFSPDM_NATIONS if (XSTRCMP(argv[i], "--identity-key-set") == 0) - rc = demo_nations_identity_key_set(&dev, 1); + rc = ctrl_nations_identity_key_set(&dev, 1); else if (XSTRCMP(argv[i], "--identity-key-unset") == 0) - rc = demo_nations_identity_key_set(&dev, 0); + rc = ctrl_nations_identity_key_set(&dev, 0); else if (XSTRCMP(argv[i], "--get-pubkey") == 0) - rc = demo_nations_get_pubkey(&dev); + rc = ctrl_nations_get_pubkey(&dev); else if (XSTRCMP(argv[i], "--connect") == 0) - rc = demo_nations_connect(&dev); + rc = ctrl_nations_connect(&dev); else if (XSTRCMP(argv[i], "--status") == 0) - rc = demo_nations_status(&dev); + rc = ctrl_nations_status(&dev); else if (XSTRCMP(argv[i], "--psk") == 0 && i + 1 < argc) - rc = demo_nations_psk_connect(&dev, argv[++i]); + rc = ctrl_nations_psk_connect(&dev, argv[++i]); else if (XSTRCMP(argv[i], "--psk-set") == 0 && i + 2 < argc) { const char* pskArg = argv[++i]; const char* authArg = argv[++i]; - rc = demo_nations_psk_set(&dev, pskArg, authArg); + rc = ctrl_nations_psk_set(&dev, pskArg, authArg); } else if (XSTRCMP(argv[i], "--psk-clear") == 0 && i + 1 < argc) - rc = demo_nations_psk_clear(&dev, argv[++i]); + rc = ctrl_nations_psk_clear(&dev, argv[++i]); else if (XSTRCMP(argv[i], "--lock") == 0) rc = wolfTPM2_SpdmNationsSetOnlyMode(&dev, 1); else if (XSTRCMP(argv[i], "--unlock") == 0) @@ -614,7 +614,7 @@ int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) printf(" %s (rc=0x%x)\n", rc == 0 ? "Success" : "FAILED", rc); } else if (XSTRCMP(argv[i], "--caps184") == 0) - rc = demo_nations_caps184(&dev); + rc = ctrl_nations_caps184(&dev); else #endif { printf("Unknown option: %s\n", argv[i]); usage(); rc = BAD_FUNC_ARG; } @@ -631,7 +631,7 @@ int main(int argc, char *argv[]) { int rc = -1; #ifndef WOLFTPM2_NO_WRAPPER - rc = TPM2_SPDM_Demo(NULL, argc, argv); + rc = TPM2_SPDM_Ctrl(NULL, argc, argv); #else printf("Wrapper code not compiled in\n"); (void)argc; (void)argv; diff --git a/examples/spdm/spdm_test.sh b/examples/spdm/spdm_test.sh index b72825bc..1237c311 100755 --- a/examples/spdm/spdm_test.sh +++ b/examples/spdm/spdm_test.sh @@ -19,7 +19,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA -SPDM_DEMO="${1:-./examples/spdm/spdm_demo}" +SPDM_DEMO="${1:-./examples/spdm/spdm_ctrl}" CAPS_DEMO="./examples/wrap/caps" UNIT_TEST="./tests/unit.test" GPIO_CHIP="gpiochip0" @@ -102,7 +102,7 @@ run_test_no_reset() { if [ ! -x "$SPDM_DEMO" ]; then echo "Error: $SPDM_DEMO not found." - echo "Usage: $0 [path-to-spdm_demo] [nuvoton|nations|nations-psk]" + echo "Usage: $0 [path-to-spdm_ctrl] [nuvoton|nations|nations-psk]" exit 1 fi diff --git a/src/spdm/README.md b/src/spdm/README.md index 0f6e01a0..da546b66 100644 --- a/src/spdm/README.md +++ b/src/spdm/README.md @@ -24,9 +24,9 @@ make && sudo make install && sudo ldconfig && popd ./autogen.sh && ./configure --enable-spdm --enable-nuvoton && make # Enable SPDM (one-time), reset, connect -./examples/spdm/spdm_demo --enable +./examples/spdm/spdm_ctrl --enable gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 -./examples/spdm/spdm_demo --connect +./examples/spdm/spdm_ctrl --connect ``` See [Building](#building) and [Nuvoton NPCT75x Details](#nuvoton-npct75x) for @@ -44,7 +44,7 @@ make && sudo make install && sudo ldconfig && popd ./autogen.sh && ./configure --enable-spdm --enable-nations && make # Connect (identity key is factory default) -./examples/spdm/spdm_demo --connect +./examples/spdm/spdm_ctrl --connect ``` See [Building](#building) and [Nations NS350 Details](#nations-ns350) for full @@ -194,13 +194,13 @@ make ```bash # Enable SPDM on the TPM (persists across resets) -./examples/spdm/spdm_demo --enable +./examples/spdm/spdm_ctrl --enable # GPIO reset gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 # Verify SPDM is enabled -./examples/spdm/spdm_demo --status +./examples/spdm/spdm_ctrl --status ``` #### Nations @@ -209,7 +209,7 @@ Identity key mode is the factory default — no setup required. If previously unset, restore with: ```bash -./examples/spdm/spdm_demo --identity-key-set +./examples/spdm/spdm_ctrl --identity-key-set ``` ### Establishing a Session @@ -218,10 +218,10 @@ unset, restore with: ```bash # Establish SPDM session (VERSION → GET_PUBK → KEY_EXCHANGE → GIVE_PUB → FINISH) -./examples/spdm/spdm_demo --connect +./examples/spdm/spdm_ctrl --connect # Query SPDM status -./examples/spdm/spdm_demo --status +./examples/spdm/spdm_ctrl --status ``` **Note:** `--get-pubkey` retrieves the TPM's identity key as part of the full @@ -234,7 +234,7 @@ Requires PSK to be provisioned first. See ```bash # Establish PSK session (VERSION → CAPS → ALGO → PSK_EXCHANGE → PSK_FINISH) -./examples/spdm/spdm_demo --psk +./examples/spdm/spdm_ctrl --psk ``` ### Lock/Unlock SPDM-Only Mode @@ -245,7 +245,7 @@ enforcement to take effect. **Nuvoton (identity key):** ```bash -./examples/spdm/spdm_demo --connect --lock +./examples/spdm/spdm_ctrl --connect --lock gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 # TPM now requires SPDM — all commands auto-encrypted: @@ -253,27 +253,27 @@ gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 ./tests/unit.test # full test suite over encrypted bus # Unlock -./examples/spdm/spdm_demo --connect --unlock +./examples/spdm/spdm_ctrl --connect --unlock gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 ``` **Nations (identity key):** ```bash -./examples/spdm/spdm_demo --connect --lock +./examples/spdm/spdm_ctrl --connect --lock # Power cycle required (unplug and re-plug Raspberry Pi) -./examples/spdm/spdm_demo --connect --unlock +./examples/spdm/spdm_ctrl --connect --unlock # Power cycle again ``` **Nations (PSK mode):** ```bash -./examples/spdm/spdm_demo --psk --lock +./examples/spdm/spdm_ctrl --psk --lock # Power cycle required -./examples/spdm/spdm_demo --psk --unlock +./examples/spdm/spdm_ctrl --psk --unlock # Power cycle again ``` @@ -284,20 +284,20 @@ is provisioned by default; it must be unset before PSK can be used. ```bash # 1. Unset identity key (enables PSK mode) -./examples/spdm/spdm_demo --identity-key-unset +./examples/spdm/spdm_ctrl --identity-key-unset # 2. Provision PSK (64-byte PSK + 32-byte ClearAuth) # The demo computes SHA-384(ClearAuth) and sends PSK(64)+Digest(48) = 112 bytes -./examples/spdm/spdm_demo --psk-set +./examples/spdm/spdm_ctrl --psk-set # 3. Establish PSK session -./examples/spdm/spdm_demo --psk +./examples/spdm/spdm_ctrl --psk # 4. Clear PSK (sends raw 32-byte ClearAuth; TPM verifies SHA-384 internally) -./examples/spdm/spdm_demo --psk-clear +./examples/spdm/spdm_ctrl --psk-clear # 5. Restore identity key (factory default) -./examples/spdm/spdm_demo --identity-key-set +./examples/spdm/spdm_ctrl --identity-key-set ``` **Important:** The ClearAuth must be exactly 32 bytes. PSK_SET stores its SHA-384 @@ -308,13 +308,13 @@ to verify. Using the wrong size makes PSK_CLEAR impossible. ```bash # Nuvoton (identity key — includes GPIO resets between tests) -./examples/spdm/spdm_test.sh ./examples/spdm/spdm_demo nuvoton +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nuvoton # Nations (identity key — no GPIO resets) -./examples/spdm/spdm_test.sh ./examples/spdm/spdm_demo nations +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nations # Nations (PSK — full lifecycle: provision → connect → clear → restore) -./examples/spdm/spdm_test.sh ./examples/spdm/spdm_demo nations-psk +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl nations-psk ``` ## TCG SPDM Vendor Commands @@ -335,7 +335,7 @@ SPDM `VENDOR_DEFINED_REQUEST` messages with `StandardID=0x0001` (TCG). ## Command Reference -All `spdm_demo` options in one table: +All `spdm_ctrl` options in one table: | Option | Vendor | Description | |-------------------------------|---------|-------------| diff --git a/src/spdm/include.am b/src/spdm/include.am index 196691c6..31e3e45a 100644 --- a/src/spdm/include.am +++ b/src/spdm/include.am @@ -11,17 +11,15 @@ src_libwolftpm_la_SOURCES += \ src/spdm/spdm_msg.c \ src/spdm/spdm_secured.c \ src/spdm/spdm_session.c \ + src/spdm/spdm_tcg.c \ src/spdm/spdm_transcript.c -# spdm_tcg.c: shared TCG SPDM code (Nuvoton + Nations) +# Vendor-specific SPDM code if BUILD_NUVOTON -src_libwolftpm_la_SOURCES += src/spdm/spdm_tcg.c src/spdm/spdm_nuvoton.c +src_libwolftpm_la_SOURCES += src/spdm/spdm_nuvoton.c endif if BUILD_NATIONS -if !BUILD_NUVOTON -src_libwolftpm_la_SOURCES += src/spdm/spdm_tcg.c -endif src_libwolftpm_la_SOURCES += src/spdm/spdm_nations.c src_libwolftpm_la_SOURCES += src/spdm/spdm_psk.c endif diff --git a/src/spdm/spdm_context.c b/src/spdm/spdm_context.c index d0ae493b..16ea6044 100644 --- a/src/spdm/spdm_context.c +++ b/src/spdm/spdm_context.c @@ -183,7 +183,7 @@ int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, const byte* tpmtPub, word32 tpmtPubSz) { @@ -197,7 +197,7 @@ int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, ctx->reqPubKeyTPMTLen = tpmtPubSz; return WOLFSPDM_SUCCESS; } -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ /* wolfSPDM_SetPSK moved to spdm_psk.c */ @@ -275,7 +275,7 @@ byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx) return ctx->spdmVersion; } -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx) { if (ctx == NULL) { @@ -309,7 +309,7 @@ int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) return WOLFSPDM_E_IO_FAIL; } -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG if (ctx->mode == WOLFSPDM_MODE_NUVOTON || ctx->mode == WOLFSPDM_MODE_NATIONS) { return wolfSPDM_ConnectTCG(ctx); @@ -389,7 +389,7 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_IO_FAIL; } -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG if (ctx->mode == WOLFSPDM_MODE_NUVOTON || ctx->mode == WOLFSPDM_MODE_NATIONS || ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { @@ -480,7 +480,7 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, return WOLFSPDM_SUCCESS; } -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ rc = ctx->ioCb(ctx, txBuf, txSz, rxBuf, rxSz, ctx->ioUserCtx); if (rc != 0) { diff --git a/src/spdm/spdm_internal.h b/src/spdm/spdm_internal.h index 90aec887..364d6841 100644 --- a/src/spdm/spdm_internal.h +++ b/src/spdm/spdm_internal.h @@ -27,12 +27,7 @@ #include #endif -/* wolfSSL options MUST be included first */ -#ifndef WOLFSSL_USER_SETTINGS - #include -#endif -#include - +/* spdm_types.h pulls in wolfSSL options via tpm2_types.h */ #include #include #include @@ -79,7 +74,7 @@ struct WOLFSPDM_CTX { WOLFSPDM_IO_CB ioCb; void* ioUserCtx; -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG /* TCG binding fields (shared by Nuvoton + Nations) */ word32 connectionHandle; /* Connection handle (usually 0) */ word16 fipsIndicator; /* FIPS service indicator */ @@ -213,7 +208,7 @@ static WC_INLINE word64 SPDM_Get64LE(const byte* buf) { /* ----- Write TCG SPDM Binding header ----- */ /* tag(2/BE) + size(4/BE) + * connHandle(4/BE) + fips(2/BE) + reserved(4) */ -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG static WC_INLINE void wolfSPDM_WriteTcgHeader(byte* buf, word16 tag, word32 totalSz, word32 connHandle, word16 fips) { diff --git a/src/spdm/spdm_msg.c b/src/spdm/spdm_msg.c index ee02fccb..39fc08a4 100644 --- a/src/spdm/spdm_msg.c +++ b/src/spdm/spdm_msg.c @@ -78,7 +78,7 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) buf[offset++] = ctx->spdmVersion; buf[offset++] = SPDM_KEY_EXCHANGE; buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG buf[offset++] = 0xFF; /* SlotID = 0xFF (no cert, use provisioned public key) */ #else buf[offset++] = 0x00; /* SlotID = 0 (certificate slot 0) */ @@ -254,7 +254,7 @@ int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) * and FINISH header. For PUB_KEY_ID mode, Cm = SHA-384(TPMT_PUBLIC) * of the requester's public key (matching how Ct is computed for * responder per TCG SPDM binding). */ -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG if (rc == WOLFSPDM_SUCCESS && mutualAuth && ctx->reqPubKeyTPMTLen > 0) { byte cmHash[WOLFSPDM_HASH_SIZE]; rc = wolfSPDM_Sha384Hash(cmHash, ctx->reqPubKeyTPMT, diff --git a/src/spdm/spdm_secured.c b/src/spdm/spdm_secured.c index 9de0866a..08209d96 100644 --- a/src/spdm/spdm_secured.c +++ b/src/spdm/spdm_secured.c @@ -65,7 +65,7 @@ int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, return WOLFSPDM_E_BUFFER_SMALL; } -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG if (ctx->mode == WOLFSPDM_MODE_NUVOTON || ctx->mode == WOLFSPDM_MODE_NATIONS || ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { @@ -194,7 +194,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, /* ----- Transport-specific header parsing ----- */ -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG if (ctx->mode == WOLFSPDM_MODE_NUVOTON || ctx->mode == WOLFSPDM_MODE_NATIONS || ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { @@ -296,7 +296,7 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, if (rc == 0) { appDataLen = SPDM_Get16LE(decrypted); -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG if (ctx->mode == WOLFSPDM_MODE_NUVOTON || ctx->mode == WOLFSPDM_MODE_NATIONS || ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { diff --git a/src/spdm/spdm_tcg.c b/src/spdm/spdm_tcg.c index 34acffdd..03e21955 100644 --- a/src/spdm/spdm_tcg.c +++ b/src/spdm/spdm_tcg.c @@ -29,7 +29,7 @@ #include "spdm_internal.h" -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG #include @@ -575,6 +575,6 @@ int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx) return WOLFSPDM_SUCCESS; } -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ #endif /* WOLFTPM_SPDM */ diff --git a/src/spdm/unit_test.c b/src/spdm/unit_test.c index 1e21e0fb..d344a5f5 100644 --- a/src/spdm/unit_test.c +++ b/src/spdm/unit_test.c @@ -574,7 +574,7 @@ static int test_invalid_curve_point(void) TEST_PASS(); } -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG /* I/O callback that returns a TCG response with msgSize < TCG_HEADER_SIZE */ static int tcg_underflow_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz, void* userCtx) @@ -618,7 +618,7 @@ static int test_tcg_underflow(void) TEST_CTX_FREE(); TEST_PASS(); } -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ #ifdef WOLFSPDM_NATIONS static int test_nations_mode(void) @@ -975,7 +975,7 @@ int main(void) /* Security tests */ test_mitm_signature_rejected(); test_invalid_curve_point(); -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG test_tcg_underflow(); #endif #ifdef WOLFSPDM_NATIONS diff --git a/src/tpm2_spdm.c b/src/tpm2_spdm.c index a79d1751..56dcfc22 100644 --- a/src/tpm2_spdm.c +++ b/src/tpm2_spdm.c @@ -42,7 +42,7 @@ #include /* TIS functions for SPI/I2C TPM transport */ -#if (defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS)) && \ +#if defined(WOLFTPM_SPDM_TCG) && \ !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ !defined(WOLFTPM_WINAPI) #include @@ -205,7 +205,7 @@ int wolfTPM2_SPDM_SecuredExchange( return BAD_FUNC_ARG; } -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG /* 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 @@ -247,7 +247,7 @@ int wolfTPM2_SPDM_SecuredExchange( return TPM_RC_SUCCESS; } -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ /* Standard SPDM mode: send TPM command as raw app data */ return wolfSPDM_SecuredExchange(ctx->spdmCtx, @@ -258,7 +258,7 @@ int wolfTPM2_SPDM_SecuredExchange( /* Nuvoton-Specific Functions */ /* -------------------------------------------------------------------------- */ -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG /* Set built-in TIS I/O callback for routing SPDM through TPM SPI/I2C. * Must be called after wolfTPM2_SPDM_InitCtx() and SetTPMCtx(). */ @@ -381,6 +381,6 @@ int wolfTPM2_SPDM_Disable(WOLFTPM2_SPDM_CTX* ctx) #endif /* WOLFSPDM_NUVOTON */ -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ #endif /* WOLFTPM_SPDM */ diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 2b550b73..2a455568 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -1116,7 +1116,7 @@ int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev) return TPM_RC_SUCCESS; } -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG /* Shared TCG SPDM functions */ int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz) @@ -1124,7 +1124,7 @@ int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz) WOLFTPM2_SPDM_CHECK_CTX(dev); return wolfSPDM_Nuvoton_GetPubKey(dev->spdmCtx->spdmCtx, pubKey, pubKeySz); } -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ #ifdef WOLFSPDM_NUVOTON /* Nuvoton-specific SPDM functions */ diff --git a/wolftpm/spdm/spdm.h b/wolftpm/spdm/spdm.h index 177b6744..dc6d9416 100644 --- a/wolftpm/spdm/spdm.h +++ b/wolftpm/spdm/spdm.h @@ -67,7 +67,7 @@ typedef enum { struct WOLFSPDM_CTX; typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG #include #endif #ifdef WOLFSPDM_NUVOTON @@ -130,7 +130,7 @@ WOLFTPM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, /* Session info */ WOLFTPM_API word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx); WOLFTPM_API byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx); -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG WOLFTPM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); WOLFTPM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); #endif diff --git a/wolftpm/spdm/spdm_tcg.h b/wolftpm/spdm/spdm_tcg.h index 5a5d9e4d..c982fc32 100644 --- a/wolftpm/spdm/spdm_tcg.h +++ b/wolftpm/spdm/spdm_tcg.h @@ -35,7 +35,7 @@ #include -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG #ifdef __cplusplus extern "C" { @@ -159,6 +159,6 @@ WOLFTPM_API int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx); } #endif -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ #endif /* WOLFSPDM_TCG_H */ diff --git a/wolftpm/spdm/spdm_types.h b/wolftpm/spdm/spdm_types.h index 0fb29cf7..ddb83730 100644 --- a/wolftpm/spdm/spdm_types.h +++ b/wolftpm/spdm/spdm_types.h @@ -137,6 +137,14 @@ extern "C" { #define WOLFSPDM_PUBKEY_BUF_SZ 256 /* Public key buffer */ #endif +/* ----- TCG Build Option ----- */ + +/* Nuvoton or Nations enables TCG SPDM binding; future chips can set directly */ +#if (defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS)) && \ + !defined(WOLFTPM_SPDM_TCG) + #define WOLFTPM_SPDM_TCG +#endif + /* ----- PSK Build Option ----- */ /* Nations build enables PSK by default; can also be set independently */ diff --git a/wolftpm/tpm2_spdm.h b/wolftpm/tpm2_spdm.h index ba2c28a0..1aa961ec 100644 --- a/wolftpm/tpm2_spdm.h +++ b/wolftpm/tpm2_spdm.h @@ -176,7 +176,7 @@ WOLFTPM_API void wolfTPM2_SPDM_FreeCtx( /* Nuvoton-Specific Functions (requires wolfSPDM with --enable-nuvoton) * -------------------------------------------------------------------------- */ -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG /** * Set the built-in TIS I/O callback for routing SPDM through TPM SPI/I2C. @@ -193,7 +193,7 @@ WOLFTPM_API int wolfTPM2_SPDM_SetTisIO( WOLFTPM2_SPDM_CTX* ctx ); -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ #ifdef __cplusplus } /* extern "C" */ diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 678a6578..8ffa7e42 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -498,7 +498,7 @@ WOLFTPM_API int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev); */ WOLFTPM_API int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev); -#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +#ifdef WOLFTPM_SPDM_TCG /*! \ingroup wolfTPM2_Wrappers \brief Get the TPM's SPDM-Identity public key (shared TCG function). @@ -512,7 +512,7 @@ WOLFTPM_API int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev); */ WOLFTPM_API int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz); -#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ +#endif /* WOLFTPM_SPDM_TCG */ #ifdef WOLFSPDM_NUVOTON /* Nuvoton-specific SPDM functions (requires wolfSPDM with --enable-nuvoton) */ From 5178d792c20279bd2a4eeb65d1ec8c785919ce82 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 25 Mar 2026 19:13:03 +0000 Subject: [PATCH 4/5] Fix Copilot x Fenrir review --- src/spdm/spdm_context.c | 5 +++-- src/spdm/spdm_tcg.c | 18 ------------------ 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/src/spdm/spdm_context.c b/src/spdm/spdm_context.c index 16ea6044..41f70447 100644 --- a/src/spdm/spdm_context.c +++ b/src/spdm/spdm_context.c @@ -414,10 +414,11 @@ int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, tcgTx, sizeof(tcgTx)); } else { /* Secured record - prepend TCG secured header (0x8201) */ - word32 totalSz = WOLFSPDM_TCG_HEADER_SIZE + txSz; - if (totalSz > sizeof(tcgTx)) { + word32 totalSz; + if (txSz > sizeof(tcgTx) - WOLFSPDM_TCG_HEADER_SIZE) { return WOLFSPDM_E_BUFFER_SMALL; } + totalSz = WOLFSPDM_TCG_HEADER_SIZE + txSz; wolfSPDM_WriteTcgHeader(tcgTx, WOLFSPDM_TCG_TAG_SECURED, totalSz, ctx->connectionHandle, ctx->fipsIndicator); XMEMCPY(tcgTx + WOLFSPDM_TCG_HEADER_SIZE, txBuf, txSz); diff --git a/src/spdm/spdm_tcg.c b/src/spdm/spdm_tcg.c index 03e21955..f8eff63b 100644 --- a/src/spdm/spdm_tcg.c +++ b/src/spdm/spdm_tcg.c @@ -495,24 +495,6 @@ int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx) } #endif - /* SPDM 1.3+: Replace VCA with Hash(VCA) in transcript. - * DSP0274 1.3 section 10.17.1: th = Hash(Hash(A) || Ct || K) - * TODO: verify with both Nuvoton and Nations hardware */ - if (0 && ctx->spdmVersion >= SPDM_VERSION_13 && ctx->transcriptLen > 12) { - byte vcaHash[WOLFSPDM_HASH_SIZE]; - rc = wolfSPDM_TranscriptHash(ctx, vcaHash); - if (rc == WOLFSPDM_SUCCESS) { - wolfSPDM_TranscriptReset(ctx); - rc = wolfSPDM_TranscriptAdd(ctx, vcaHash, WOLFSPDM_HASH_SIZE); - wolfSPDM_DebugPrint(ctx, "TCG: VCA hashed (%u -> %u bytes)\n", - ctx->transcriptLen, WOLFSPDM_HASH_SIZE); - } - if (rc != WOLFSPDM_SUCCESS) { - ctx->state = WOLFSPDM_STATE_ERROR; - return rc; - } - } - /* Step 4: GET_PUBK */ wolfSPDM_DebugPrint(ctx, "TCG Step 4: GET_PUBK\n"); pubKeySz = sizeof(pubKey); From 965a810c1fb7a4cbd1ecb99efb61ab141464eda9 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 25 Mar 2026 20:14:12 +0000 Subject: [PATCH 5/5] Add extra unit test coverage for spdm --- src/spdm/spdm_internal.h | 56 +-- src/spdm/unit_test.c | 1036 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 1064 insertions(+), 28 deletions(-) diff --git a/src/spdm/spdm_internal.h b/src/spdm/spdm_internal.h index 364d6841..e0da862b 100644 --- a/src/spdm/spdm_internal.h +++ b/src/spdm/spdm_internal.h @@ -270,78 +270,78 @@ static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, /* ----- Internal Function Declarations - Transcript ----- */ -WOLFTPM_LOCAL void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); -WOLFTPM_LOCAL int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); -WOLFTPM_LOCAL int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); -WOLFTPM_LOCAL int wolfSPDM_Sha384Hash(byte* out, +WOLFTPM_API void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); +WOLFTPM_API int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); +WOLFTPM_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 ----- */ -WOLFTPM_LOCAL int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); -WOLFTPM_LOCAL int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, byte* pubKeyX, word32* pubKeyXSz, byte* pubKeyY, word32* pubKeyYSz); -WOLFTPM_LOCAL int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, const byte* peerPubKeyX, const byte* peerPubKeyY); -WOLFTPM_LOCAL int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); -WOLFTPM_LOCAL int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, +WOLFTPM_API int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); +WOLFTPM_API int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, byte* sig, word32* sigSz); -WOLFTPM_LOCAL int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, const byte* sig, word32 sigSz); /* ----- Internal Function Declarations - Key Derivation ----- */ -WOLFTPM_LOCAL int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); -WOLFTPM_LOCAL int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, const byte* th1Hash); -WOLFTPM_LOCAL int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx); -WOLFTPM_LOCAL int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, +WOLFTPM_API int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); +WOLFTPM_API int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, const byte* th1Hash); +WOLFTPM_API int wolfSPDM_DeriveAppDataKeys(WOLFSPDM_CTX* ctx); +WOLFTPM_API int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, const char* label, const byte* context, word32 contextSz, byte* out, word32 outSz); -WOLFTPM_LOCAL int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, +WOLFTPM_API int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, byte* verifyData); /* ----- Internal Function Declarations - Message Building ----- */ -WOLFTPM_LOCAL int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); -WOLFTPM_LOCAL int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); -WOLFTPM_LOCAL int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); -WOLFTPM_LOCAL int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFTPM_API int wolfSPDM_BuildGetVersion(byte* buf, word32* bufSz); +WOLFTPM_API int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFTPM_API int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFTPM_API int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); /* PSK message builders/parsers declared in spdm_psk.h */ /* ----- Internal Function Declarations - Message Parsing ----- */ -WOLFTPM_LOCAL int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); -WOLFTPM_LOCAL int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); -WOLFTPM_LOCAL int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); -WOLFTPM_LOCAL int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); +WOLFTPM_API int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFTPM_API int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFTPM_API int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFTPM_API int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); /* ----- Internal Function Declarations - Secured Messaging ----- */ -WOLFTPM_LOCAL int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, const byte* plain, word32 plainSz, byte* enc, word32* encSz); -WOLFTPM_LOCAL int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, const byte* enc, word32 encSz, byte* plain, word32* plainSz); /* ----- Internal Utility Functions ----- */ -WOLFTPM_LOCAL int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, +WOLFTPM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); #ifdef DEBUG_WOLFTPM -WOLFTPM_LOCAL void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) +WOLFTPM_API void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) #ifdef __GNUC__ __attribute__((format(printf, 2, 3))) #endif ; -WOLFTPM_LOCAL void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, +WOLFTPM_API void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, const byte* data, word32 len); #else #define wolfSPDM_DebugPrint(ctx, fmt, ...) do { (void)(ctx); (void)fmt; } while(0) diff --git a/src/spdm/unit_test.c b/src/spdm/unit_test.c index d344a5f5..3c11006b 100644 --- a/src/spdm/unit_test.c +++ b/src/spdm/unit_test.c @@ -927,6 +927,975 @@ static int test_key_zeroing(void) TEST_PASS(); } +/* ===== NEW COVERAGE TESTS ===== */ + +/* ----- Group A: Public API Coverage ----- */ + +static int test_set_requester_key_pair(void) +{ + byte privKey[48], pubKey[96]; + TEST_CTX_SETUP(); + printf("test_set_requester_key_pair...\n"); + XMEMSET(privKey, 0xAA, sizeof(privKey)); + XMEMSET(pubKey, 0xBB, sizeof(pubKey)); + + /* NULL args */ + TEST_ASSERT(wolfSPDM_SetRequesterKeyPair(NULL, privKey, 48, pubKey, 96) + != WOLFSPDM_SUCCESS, "NULL ctx should fail"); + TEST_ASSERT(wolfSPDM_SetRequesterKeyPair(ctx, NULL, 48, pubKey, 96) + != WOLFSPDM_SUCCESS, "NULL privKey should fail"); + TEST_ASSERT(wolfSPDM_SetRequesterKeyPair(ctx, privKey, 48, NULL, 96) + != WOLFSPDM_SUCCESS, "NULL pubKey should fail"); + + /* Valid call */ + ASSERT_SUCCESS(wolfSPDM_SetRequesterKeyPair(ctx, privKey, 48, pubKey, 96)); + ASSERT_EQ(ctx->flags.hasReqKeyPair, 1, "hasReqKeyPair not set"); + ASSERT_EQ(ctx->reqPrivKeyLen, 48, "privKey len wrong"); + TEST_ASSERT(memcmp(ctx->reqPrivKey, privKey, 48) == 0, "privKey mismatch"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_connect_null_args(void) +{ + TEST_CTX_SETUP(); + printf("test_connect_null_args...\n"); + + TEST_ASSERT(wolfSPDM_Connect(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + /* No ioCb set */ + TEST_ASSERT(wolfSPDM_Connect(ctx) != WOLFSPDM_SUCCESS, + "No IO should fail"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_get_version_no_io(void) +{ + TEST_CTX_SETUP(); + printf("test_get_version_no_io...\n"); + TEST_ASSERT(wolfSPDM_GetVersion(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + TEST_ASSERT(wolfSPDM_GetVersion(ctx) != WOLFSPDM_SUCCESS, + "No IO should fail"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_key_exchange_no_io(void) +{ + TEST_CTX_SETUP(); + printf("test_key_exchange_no_io...\n"); + TEST_ASSERT(wolfSPDM_KeyExchange(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + TEST_ASSERT(wolfSPDM_KeyExchange(ctx) != WOLFSPDM_SUCCESS, + "No IO should fail"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_finish_no_io(void) +{ + TEST_CTX_SETUP(); + printf("test_finish_no_io...\n"); + TEST_ASSERT(wolfSPDM_Finish(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + TEST_ASSERT(wolfSPDM_Finish(ctx) != WOLFSPDM_SUCCESS, + "No session should fail"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_secured_exchange_null_args(void) +{ + byte cmd[4] = {0}, rsp[64]; + word32 rspSz = sizeof(rsp); + TEST_CTX_SETUP(); + printf("test_secured_exchange_null_args...\n"); + + TEST_ASSERT(wolfSPDM_SecuredExchange(NULL, cmd, 4, rsp, &rspSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_SecuredExchange(ctx, NULL, 4, rsp, &rspSz) + != WOLFSPDM_SUCCESS, "NULL cmd"); + TEST_ASSERT(wolfSPDM_SecuredExchange(ctx, cmd, 4, NULL, &rspSz) + != WOLFSPDM_SUCCESS, "NULL rsp"); + TEST_ASSERT(wolfSPDM_SecuredExchange(ctx, cmd, 4, rsp, NULL) + != WOLFSPDM_SUCCESS, "NULL rspSz"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_disconnect_states(void) +{ + TEST_CTX_SETUP(); + printf("test_disconnect_states...\n"); + /* Not connected should still succeed (cleanup is safe) */ + wolfSPDM_Disconnect(ctx); + wolfSPDM_Disconnect(NULL); /* Should not crash */ + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ----- Group B: TCG Message Framing ----- */ + +#ifdef WOLFTPM_SPDM_TCG + +static int test_build_tcg_clear_message(void) +{ + byte outBuf[64]; + int rc; + TEST_CTX_SETUP(); + printf("test_build_tcg_clear_message...\n"); + + ctx->connectionHandle = 0; + ctx->fipsIndicator = 0; + + /* NULL args */ + TEST_ASSERT(wolfSPDM_BuildTcgClearMessage(NULL, (byte*)"AB", 2, outBuf, + sizeof(outBuf)) < 0, "NULL ctx"); + TEST_ASSERT(wolfSPDM_BuildTcgClearMessage(ctx, NULL, 2, outBuf, + sizeof(outBuf)) < 0, "NULL payload"); + TEST_ASSERT(wolfSPDM_BuildTcgClearMessage(ctx, (byte*)"AB", 2, NULL, + sizeof(outBuf)) < 0, "NULL outBuf"); + + /* Buffer too small */ + TEST_ASSERT(wolfSPDM_BuildTcgClearMessage(ctx, (byte*)"AB", 2, outBuf, + 4) < 0, "small buffer"); + + /* Valid build: 16 header + 4 payload = 20 bytes */ + rc = wolfSPDM_BuildTcgClearMessage(ctx, (byte*)"TEST", 4, outBuf, + sizeof(outBuf)); + TEST_ASSERT(rc == 20, "expected 20 bytes"); + /* Tag at [0-1] should be 0x8101 big-endian */ + TEST_ASSERT(outBuf[0] == 0x81 && outBuf[1] == 0x01, "wrong tag"); + /* Payload at offset 16 */ + TEST_ASSERT(memcmp(outBuf + 16, "TEST", 4) == 0, "payload mismatch"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_parse_tcg_clear_message(void) +{ + byte buf[32], payload[16]; + word32 payloadSz = sizeof(payload); + WOLFSPDM_TCG_CLEAR_HDR hdr; + TEST_CTX_SETUP(); + printf("test_parse_tcg_clear_message...\n"); + + /* Build a valid message first */ + ctx->connectionHandle = 0; + ctx->fipsIndicator = 0; + { + int built = wolfSPDM_BuildTcgClearMessage(ctx, (byte*)"ABCD", 4, buf, + sizeof(buf)); + TEST_ASSERT(built == 20, "build failed"); + } + + /* NULL args */ + TEST_ASSERT(wolfSPDM_ParseTcgClearMessage(NULL, 20, payload, &payloadSz, + &hdr) != WOLFSPDM_SUCCESS, "NULL inBuf"); + TEST_ASSERT(wolfSPDM_ParseTcgClearMessage(buf, 20, NULL, &payloadSz, + &hdr) != WOLFSPDM_SUCCESS, "NULL payload"); + + /* Short buffer */ + TEST_ASSERT(wolfSPDM_ParseTcgClearMessage(buf, 8, payload, &payloadSz, + &hdr) != WOLFSPDM_SUCCESS, "short buffer"); + + /* Valid parse (returns payload size on success) */ + payloadSz = sizeof(payload); + { + int parsed = wolfSPDM_ParseTcgClearMessage(buf, 20, payload, + &payloadSz, &hdr); + TEST_ASSERT(parsed >= 0, "parse failed"); + ASSERT_EQ(payloadSz, 4, "payload size wrong"); + TEST_ASSERT(memcmp(payload, "ABCD", 4) == 0, "payload mismatch"); + } + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_vendor_defined(void) +{ + byte outBuf[64]; + int rc; + printf("test_build_vendor_defined...\n"); + + /* NULL args */ + TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, NULL, + (byte*)"X", 1, outBuf, sizeof(outBuf)) < 0, "NULL vdCode"); + TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", + (byte*)"X", 1, NULL, sizeof(outBuf)) < 0, "NULL outBuf"); + + /* Buffer too small */ + TEST_ASSERT(wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", + (byte*)"X", 1, outBuf, 4) < 0, "small buffer"); + + /* Valid build with payload */ + rc = wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", + (byte*)"ABCD", 4, outBuf, sizeof(outBuf)); + TEST_ASSERT(rc > 0, "build failed"); + ASSERT_EQ(outBuf[0], SPDM_VERSION_12, "wrong version"); + TEST_ASSERT(outBuf[1] == 0xFE || outBuf[1] == 0x7E, + "wrong opcode"); + + /* Build with no payload */ + rc = wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "GET_PUBK", + NULL, 0, outBuf, sizeof(outBuf)); + TEST_ASSERT(rc > 0, "no-payload build failed"); + + TEST_PASS(); +} + +static int test_parse_vendor_defined(void) +{ + byte outBuf[64], payload[32]; + char vdCode[9]; + word32 payloadSz; + int built; + printf("test_parse_vendor_defined...\n"); + + /* Build, then parse back */ + built = wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, "TPM2_CMD", + (byte*)"HELLO", 5, outBuf, sizeof(outBuf)); + TEST_ASSERT(built > 0, "build failed"); + + payloadSz = sizeof(payload); + { + int parsed = wolfSPDM_ParseVendorDefined(outBuf, (word32)built, vdCode, + payload, &payloadSz); + TEST_ASSERT(parsed >= 0, "parse failed"); + TEST_ASSERT(memcmp(vdCode, "TPM2_CMD", 8) == 0, "vdCode mismatch"); + ASSERT_EQ(payloadSz, 5, "payload size wrong"); + TEST_ASSERT(memcmp(payload, "HELLO", 5) == 0, "payload mismatch"); + } + + /* NULL args */ + TEST_ASSERT(wolfSPDM_ParseVendorDefined(NULL, (word32)built, vdCode, + payload, &payloadSz) != WOLFSPDM_SUCCESS, "NULL inBuf"); + + /* Short buffer */ + payloadSz = sizeof(payload); + TEST_ASSERT(wolfSPDM_ParseVendorDefined(outBuf, 4, vdCode, + payload, &payloadSz) != WOLFSPDM_SUCCESS, "short buffer"); + + TEST_PASS(); +} + +static int test_vendor_defined_roundtrip(void) +{ + static const char* codes[] = {"GET_PUBK", "GIVE_PUB", "TPM2_CMD", + "GET_STS_", "SPDMONLY"}; + byte outBuf[64], payload[32]; + char vdCode[9]; + word32 payloadSz; + int i, built; + printf("test_vendor_defined_roundtrip...\n"); + + for (i = 0; i < 5; i++) { + byte testData[4] = {(byte)i, 0x11, 0x22, 0x33}; + built = wolfSPDM_BuildVendorDefined(SPDM_VERSION_12, codes[i], + testData, 4, outBuf, sizeof(outBuf)); + TEST_ASSERT(built > 0, "build failed"); + payloadSz = sizeof(payload); + { + int parsed = wolfSPDM_ParseVendorDefined(outBuf, (word32)built, + vdCode, payload, &payloadSz); + TEST_ASSERT(parsed >= 0, "parse failed"); + TEST_ASSERT(memcmp(vdCode, codes[i], 8) == 0, "vdCode mismatch"); + ASSERT_EQ(payloadSz, 4, "payload size"); + TEST_ASSERT(memcmp(payload, testData, 4) == 0, "payload mismatch"); + } + } + + TEST_PASS(); +} + +static int test_tcg_get_pub_key_null_args(void) +{ + byte pubKey[256]; + word32 pubKeySz = sizeof(pubKey); + TEST_CTX_SETUP(); + printf("test_tcg_get_pub_key_null_args...\n"); + TEST_ASSERT(wolfSPDM_TCG_GetPubKey(NULL, pubKey, &pubKeySz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_TCG_GetPubKey(ctx, NULL, &pubKeySz) + != WOLFSPDM_SUCCESS, "NULL pubKey"); + TEST_ASSERT(wolfSPDM_TCG_GetPubKey(ctx, pubKey, NULL) + != WOLFSPDM_SUCCESS, "NULL pubKeySz"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_tcg_give_pub_key_null_args(void) +{ + byte pubKey[128]; + TEST_CTX_SETUP(); + printf("test_tcg_give_pub_key_null_args...\n"); + XMEMSET(pubKey, 0xAA, sizeof(pubKey)); + TEST_ASSERT(wolfSPDM_TCG_GivePubKey(NULL, pubKey, 120) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_TCG_GivePubKey(ctx, NULL, 120) + != WOLFSPDM_SUCCESS, "NULL pubKey"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_set_requester_key_tpmt(void) +{ + byte tpmt[128]; + TEST_CTX_SETUP(); + printf("test_set_requester_key_tpmt...\n"); + XMEMSET(tpmt, 0x55, sizeof(tpmt)); + + TEST_ASSERT(wolfSPDM_SetRequesterKeyTPMT(NULL, tpmt, 120) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_SetRequesterKeyTPMT(ctx, NULL, 120) + != WOLFSPDM_SUCCESS, "NULL tpmtPub"); + + /* Valid 120-byte TPMT */ + ASSERT_SUCCESS(wolfSPDM_SetRequesterKeyTPMT(ctx, tpmt, 120)); + ASSERT_EQ(ctx->reqPubKeyTPMTLen, 120, "tpmt len wrong"); + TEST_ASSERT(memcmp(ctx->reqPubKeyTPMT, tpmt, 120) == 0, "tpmt mismatch"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_connect_tcg_null_args(void) +{ + TEST_CTX_SETUP(); + printf("test_connect_tcg_null_args...\n"); + TEST_ASSERT(wolfSPDM_ConnectTCG(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + /* No IO set */ + TEST_ASSERT(wolfSPDM_ConnectTCG(ctx) != WOLFSPDM_SUCCESS, + "No IO should fail"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +#endif /* WOLFTPM_SPDM_TCG */ + +/* ----- Group C: Nuvoton ----- */ + +#ifdef WOLFSPDM_NUVOTON + +static int test_nuvoton_get_status_null_args(void) +{ + WOLFSPDM_NUVOTON_STATUS status; + TEST_CTX_SETUP(); + printf("test_nuvoton_get_status_null_args...\n"); + TEST_ASSERT(wolfSPDM_Nuvoton_GetStatus(NULL, &status) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nuvoton_GetStatus(ctx, NULL) + != WOLFSPDM_SUCCESS, "NULL status"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nuvoton_set_only_mode_null_args(void) +{ + TEST_CTX_SETUP(); + printf("test_nuvoton_set_only_mode_null_args...\n"); + TEST_ASSERT(wolfSPDM_Nuvoton_SetOnlyMode(NULL, 1) + != WOLFSPDM_SUCCESS, "NULL ctx"); + /* Not connected */ + TEST_ASSERT(wolfSPDM_Nuvoton_SetOnlyMode(ctx, 1) + != WOLFSPDM_SUCCESS, "not connected"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +#endif /* WOLFSPDM_NUVOTON */ + +/* ----- Group D: Nations ----- */ + +#ifdef WOLFSPDM_NATIONS + +static int test_nations_get_status_null_args(void) +{ + WOLFSPDM_NATIONS_STATUS status; + TEST_CTX_SETUP(); + printf("test_nations_get_status_null_args...\n"); + TEST_ASSERT(wolfSPDM_Nations_GetStatus(NULL, &status) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_GetStatus(ctx, NULL) + != WOLFSPDM_SUCCESS, "NULL status"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_set_only_mode_null_args(void) +{ + TEST_CTX_SETUP(); + printf("test_nations_set_only_mode_null_args...\n"); + TEST_ASSERT(wolfSPDM_Nations_SetOnlyMode(NULL, 1) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_SetOnlyMode(ctx, 1) + != WOLFSPDM_SUCCESS, "not connected"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_psk_set_null_args(void) +{ + byte psk[64]; + TEST_CTX_SETUP(); + printf("test_nations_psk_set_null_args...\n"); + XMEMSET(psk, 0xAA, sizeof(psk)); + TEST_ASSERT(wolfSPDM_Nations_PskSet(NULL, psk, 64) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_PskSet(ctx, NULL, 64) + != WOLFSPDM_SUCCESS, "NULL psk"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_psk_clear_null_args(void) +{ + byte auth[32]; + TEST_CTX_SETUP(); + printf("test_nations_psk_clear_null_args...\n"); + XMEMSET(auth, 0xBB, sizeof(auth)); + TEST_ASSERT(wolfSPDM_Nations_PskClear(NULL, auth, 32) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_PskClear(ctx, NULL, 32) + != WOLFSPDM_SUCCESS, "NULL auth"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_psk_clear_vca_null_args(void) +{ + byte auth[32]; + TEST_CTX_SETUP(); + printf("test_nations_psk_clear_vca_null_args...\n"); + XMEMSET(auth, 0xCC, sizeof(auth)); + TEST_ASSERT(wolfSPDM_Nations_PskClearWithVCA(NULL, auth, 32) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_Nations_PskClearWithVCA(ctx, NULL, 32) + != WOLFSPDM_SUCCESS, "NULL auth"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +#endif /* WOLFSPDM_NATIONS */ + +/* ----- Group E: PSK Messages ----- */ + +#ifdef WOLFTPM_SPDM_PSK + +static int test_parse_psk_exchange_rsp_null_args(void) +{ + byte buf[64]; + TEST_CTX_SETUP_V12(); + printf("test_parse_psk_exchange_rsp_null_args...\n"); + XMEMSET(buf, 0, sizeof(buf)); + TEST_ASSERT(wolfSPDM_ParsePskExchangeRsp(NULL, buf, sizeof(buf)) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_ParsePskExchangeRsp(ctx, NULL, sizeof(buf)) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_ParsePskExchangeRsp(ctx, buf, 4) + != WOLFSPDM_SUCCESS, "short buf"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_psk_finish_null_args(void) +{ + byte buf[128]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_psk_finish_null_args...\n"); + TEST_ASSERT(wolfSPDM_BuildPskFinish(NULL, buf, &bufSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_BuildPskFinish(ctx, NULL, &bufSz) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_BuildPskFinish(ctx, buf, NULL) + != WOLFSPDM_SUCCESS, "NULL bufSz"); + bufSz = 4; + TEST_ASSERT(wolfSPDM_BuildPskFinish(ctx, buf, &bufSz) + != WOLFSPDM_SUCCESS, "small buffer"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_psk_finish_format(void) +{ + byte buf[128]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_psk_finish_format...\n"); + + /* Fill reqFinishedKey with test data */ + XMEMSET(ctx->reqFinishedKey, 0x5A, WOLFSPDM_HASH_SIZE); + /* Need some transcript data for HMAC */ + wolfSPDM_TranscriptAdd(ctx, (byte*)"test transcript data", 20); + + ASSERT_SUCCESS(wolfSPDM_BuildPskFinish(ctx, buf, &bufSz)); + ASSERT_EQ(buf[0], SPDM_VERSION_12, "wrong version"); + ASSERT_EQ(buf[1], 0xE7, "wrong opcode (PSK_FINISH)"); + ASSERT_EQ(bufSz, 52, "expected 4 header + 48 HMAC"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_parse_psk_finish_rsp(void) +{ + byte buf[8]; + TEST_CTX_SETUP_V12(); + printf("test_parse_psk_finish_rsp...\n"); + + /* NULL args */ + TEST_ASSERT(wolfSPDM_ParsePskFinishRsp(NULL, buf, 4) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_ParsePskFinishRsp(ctx, NULL, 4) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_ParsePskFinishRsp(ctx, buf, 2) + != WOLFSPDM_SUCCESS, "short buf"); + + /* Valid PSK_FINISH_RSP */ + buf[0] = SPDM_VERSION_12; + buf[1] = 0x67; /* PSK_FINISH_RSP */ + buf[2] = 0x00; + buf[3] = 0x00; + ASSERT_SUCCESS(wolfSPDM_ParsePskFinishRsp(ctx, buf, 4)); + + /* Error response */ + buf[1] = 0x7F; /* SPDM_ERROR */ + buf[2] = 0x01; + TEST_ASSERT(wolfSPDM_ParsePskFinishRsp(ctx, buf, 4) + != WOLFSPDM_SUCCESS, "error not detected"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_connect_psk_null_args(void) +{ + TEST_CTX_SETUP(); + printf("test_connect_psk_null_args...\n"); + TEST_ASSERT(wolfSPDM_ConnectPsk(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx should fail"); + /* No PSK set, no IO */ + TEST_ASSERT(wolfSPDM_ConnectPsk(ctx) != WOLFSPDM_SUCCESS, + "No PSK/IO should fail"); + TEST_CTX_FREE(); + TEST_PASS(); +} + +#endif /* WOLFTPM_SPDM_PSK */ + +/* ----- Group F: Internal Crypto ----- */ + +static int test_sha384_hash(void) +{ + byte hash[48], hash2[48]; + printf("test_sha384_hash...\n"); + + /* Single block */ + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(hash, (byte*)"abc", 3, + NULL, 0, NULL, 0)); + /* Result should be non-zero */ + TEST_ASSERT(hash[0] != 0 || hash[1] != 0, "hash is zero"); + + /* Multi-block should produce same result as single */ + ASSERT_SUCCESS(wolfSPDM_Sha384Hash(hash2, (byte*)"a", 1, + (byte*)"b", 1, (byte*)"c", 1)); + TEST_ASSERT(memcmp(hash, hash2, 48) == 0, + "split hash should match single"); + + TEST_PASS(); +} + +static int test_export_ephemeral_pub_key(void) +{ + byte pubKeyX[48], pubKeyY[48]; + word32 xSz = sizeof(pubKeyX), ySz = sizeof(pubKeyY); + TEST_CTX_SETUP(); + printf("test_export_ephemeral_pub_key...\n"); + + /* NULL args */ + TEST_ASSERT(wolfSPDM_ExportEphemeralPubKey(NULL, pubKeyX, &xSz, + pubKeyY, &ySz) != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_ExportEphemeralPubKey(ctx, NULL, &xSz, + pubKeyY, &ySz) != WOLFSPDM_SUCCESS, "NULL pubKeyX"); + + /* No key generated yet */ + TEST_ASSERT(wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &xSz, + pubKeyY, &ySz) != WOLFSPDM_SUCCESS, "no key should fail"); + + /* Generate key, then export */ + ASSERT_SUCCESS(wolfSPDM_GenerateEphemeralKey(ctx)); + xSz = sizeof(pubKeyX); + ySz = sizeof(pubKeyY); + ASSERT_SUCCESS(wolfSPDM_ExportEphemeralPubKey(ctx, pubKeyX, &xSz, + pubKeyY, &ySz)); + ASSERT_EQ(xSz, 48, "X size"); + ASSERT_EQ(ySz, 48, "Y size"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_sign_hash_null_args(void) +{ + byte hash[48], sig[128]; + word32 sigSz = sizeof(sig); + TEST_CTX_SETUP(); + printf("test_sign_hash_null_args...\n"); + XMEMSET(hash, 0xAA, sizeof(hash)); + + TEST_ASSERT(wolfSPDM_SignHash(NULL, hash, 48, sig, &sigSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_SignHash(ctx, NULL, 48, sig, &sigSz) + != WOLFSPDM_SUCCESS, "NULL hash"); + TEST_ASSERT(wolfSPDM_SignHash(ctx, hash, 48, NULL, &sigSz) + != WOLFSPDM_SUCCESS, "NULL sig"); + TEST_ASSERT(wolfSPDM_SignHash(ctx, hash, 48, sig, NULL) + != WOLFSPDM_SUCCESS, "NULL sigSz"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_verify_signature_null_args(void) +{ + byte hash[48], sig[96]; + TEST_CTX_SETUP(); + printf("test_verify_signature_null_args...\n"); + XMEMSET(hash, 0xAA, sizeof(hash)); + XMEMSET(sig, 0xBB, sizeof(sig)); + + TEST_ASSERT(wolfSPDM_VerifySignature(NULL, hash, 48, sig, 96) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_VerifySignature(ctx, NULL, 48, sig, 96) + != WOLFSPDM_SUCCESS, "NULL hash"); + TEST_ASSERT(wolfSPDM_VerifySignature(ctx, hash, 48, NULL, 96) + != WOLFSPDM_SUCCESS, "NULL sig"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_sign_verify_roundtrip(void) +{ + byte hash[48], sig[128]; + word32 sigSz = sizeof(sig); + byte privKey[48], pubKeyX[48], pubKeyY[48], pubKey[96]; + word32 privSz = 48, xSz = 48, ySz = 48; + ecc_key ecKey; + TEST_CTX_SETUP(); + printf("test_sign_verify_roundtrip...\n"); + + /* Generate a P-384 key pair */ + ASSERT_SUCCESS(wc_ecc_init(&ecKey)); + ASSERT_SUCCESS(wc_ecc_make_key(&ctx->rng, 48, &ecKey)); + ASSERT_SUCCESS(wc_ecc_export_private_only(&ecKey, privKey, &privSz)); + ASSERT_SUCCESS(wc_ecc_export_public_raw(&ecKey, pubKeyX, &xSz, + pubKeyY, &ySz)); + wc_ecc_free(&ecKey); + + XMEMCPY(pubKey, pubKeyX, 48); + XMEMCPY(pubKey + 48, pubKeyY, 48); + + /* Set requester key pair for signing */ + ASSERT_SUCCESS(wolfSPDM_SetRequesterKeyPair(ctx, privKey, 48, pubKey, 96)); + /* Set responder pub key for verification */ + ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(ctx, pubKey, 96)); + + /* Sign */ + XMEMSET(hash, 0x42, sizeof(hash)); + ASSERT_SUCCESS(wolfSPDM_SignHash(ctx, hash, 48, sig, &sigSz)); + ASSERT_EQ(sigSz, 96, "sig should be 96 bytes"); + + /* Verify */ + ASSERT_SUCCESS(wolfSPDM_VerifySignature(ctx, hash, 48, sig, sigSz)); + + /* Flip a bit - should fail */ + sig[10] ^= 0x01; + TEST_ASSERT(wolfSPDM_VerifySignature(ctx, hash, 48, sig, sigSz) + != WOLFSPDM_SUCCESS, "flipped sig should fail"); + + wc_ForceZero(privKey, sizeof(privKey)); + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ----- Group G: Internal KDF ----- */ + +static int test_derive_handshake_keys(void) +{ + byte th1[48]; + byte zeros[48]; + TEST_CTX_SETUP_V12(); + printf("test_derive_handshake_keys...\n"); + + XMEMSET(th1, 0xAB, sizeof(th1)); + XMEMSET(zeros, 0, sizeof(zeros)); + + /* NULL args */ + TEST_ASSERT(wolfSPDM_DeriveHandshakeKeys(NULL, th1) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_DeriveHandshakeKeys(ctx, NULL) + != WOLFSPDM_SUCCESS, "NULL th1"); + + /* Set up shared secret */ + XMEMSET(ctx->sharedSecret, 0x5A, WOLFSPDM_ECC_KEY_SIZE); + ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; + + ASSERT_SUCCESS(wolfSPDM_DeriveHandshakeKeys(ctx, th1)); + + /* Verify derived keys are non-zero */ + TEST_ASSERT(memcmp(ctx->handshakeSecret, zeros, 48) != 0, + "handshakeSecret is zero"); + TEST_ASSERT(memcmp(ctx->reqHsSecret, zeros, 48) != 0, + "reqHsSecret is zero"); + TEST_ASSERT(memcmp(ctx->rspHsSecret, zeros, 48) != 0, + "rspHsSecret is zero"); + TEST_ASSERT(memcmp(ctx->reqDataKey, zeros, 32) != 0, + "reqDataKey is zero"); + TEST_ASSERT(memcmp(ctx->rspDataKey, zeros, 32) != 0, + "rspDataKey is zero"); + /* req and rsp keys should differ */ + TEST_ASSERT(memcmp(ctx->reqDataKey, ctx->rspDataKey, 32) != 0, + "req/rsp keys should differ"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_derive_from_handshake_secret(void) +{ + byte th1[48]; + byte zeros[48]; + TEST_CTX_SETUP_V12(); + printf("test_derive_from_handshake_secret...\n"); + + XMEMSET(th1, 0xCD, sizeof(th1)); + XMEMSET(zeros, 0, sizeof(zeros)); + XMEMSET(ctx->handshakeSecret, 0x5A, WOLFSPDM_HASH_SIZE); + + ASSERT_SUCCESS(wolfSPDM_DeriveFromHandshakeSecret(ctx, th1)); + TEST_ASSERT(memcmp(ctx->reqHsSecret, zeros, 48) != 0, + "reqHsSecret is zero"); + TEST_ASSERT(memcmp(ctx->reqFinishedKey, zeros, 48) != 0, + "reqFinishedKey is zero"); + TEST_ASSERT(memcmp(ctx->rspFinishedKey, zeros, 48) != 0, + "rspFinishedKey is zero"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_derive_app_data_keys(void) +{ + byte zeros[48]; + TEST_CTX_SETUP_V12(); + printf("test_derive_app_data_keys...\n"); + + XMEMSET(zeros, 0, sizeof(zeros)); + TEST_ASSERT(wolfSPDM_DeriveAppDataKeys(NULL) != WOLFSPDM_SUCCESS, + "NULL ctx"); + + /* Set up handshake secret and transcript */ + XMEMSET(ctx->handshakeSecret, 0x5A, WOLFSPDM_HASH_SIZE); + wolfSPDM_TranscriptAdd(ctx, (byte*)"test data for th2", 17); + ctx->reqSeqNum = 99; + ctx->rspSeqNum = 99; + + ASSERT_SUCCESS(wolfSPDM_DeriveAppDataKeys(ctx)); + TEST_ASSERT(memcmp(ctx->reqDataKey, zeros, 32) != 0, + "reqDataKey is zero"); + TEST_ASSERT(memcmp(ctx->rspDataKey, zeros, 32) != 0, + "rspDataKey is zero"); + ASSERT_EQ(ctx->reqSeqNum, 0, "reqSeqNum not reset"); + ASSERT_EQ(ctx->rspSeqNum, 0, "rspSeqNum not reset"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ----- Group H: Internal Message Building ----- */ + +static int test_build_key_exchange_null_args(void) +{ + byte buf[256]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_key_exchange_null_args...\n"); + + TEST_ASSERT(wolfSPDM_BuildKeyExchange(NULL, buf, &bufSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_BuildKeyExchange(ctx, NULL, &bufSz) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_BuildKeyExchange(ctx, buf, NULL) + != WOLFSPDM_SUCCESS, "NULL bufSz"); + bufSz = 4; + TEST_ASSERT(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz) + != WOLFSPDM_SUCCESS, "small buffer"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_key_exchange_format(void) +{ + byte buf[256]; + word32 bufSz = sizeof(buf); + byte zeros[48]; + TEST_CTX_SETUP_V12(); + printf("test_build_key_exchange_format...\n"); + + XMEMSET(zeros, 0, sizeof(zeros)); + ctx->reqSessionId = 0x0001; + + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); + ASSERT_EQ(buf[0], SPDM_VERSION_12, "wrong version"); + ASSERT_EQ(buf[1], 0xE4, "wrong opcode (KEY_EXCHANGE)"); + TEST_ASSERT(bufSz > 100, "message too small"); + /* Ephemeral key should be generated */ + ASSERT_EQ(ctx->flags.ephemeralKeyInit, 1, "ephemeral key not init"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_finish_null_args(void) +{ + byte buf[256]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_finish_null_args...\n"); + + TEST_ASSERT(wolfSPDM_BuildFinish(NULL, buf, &bufSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_BuildFinish(ctx, NULL, &bufSz) + != WOLFSPDM_SUCCESS, "NULL buf"); + TEST_ASSERT(wolfSPDM_BuildFinish(ctx, buf, NULL) + != WOLFSPDM_SUCCESS, "NULL bufSz"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_build_finish_format(void) +{ + byte buf[256]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + printf("test_build_finish_format...\n"); + + ctx->mutAuthRequested = 0; /* No mutual auth */ + XMEMSET(ctx->reqFinishedKey, 0x5A, WOLFSPDM_HASH_SIZE); + wolfSPDM_TranscriptAdd(ctx, (byte*)"test transcript", 15); + + ASSERT_SUCCESS(wolfSPDM_BuildFinish(ctx, buf, &bufSz)); + ASSERT_EQ(buf[0], SPDM_VERSION_12, "wrong version"); + ASSERT_EQ(buf[1], 0xE5, "wrong opcode (FINISH)"); + ASSERT_EQ(buf[2], 0, "sigIncluded should be 0"); + ASSERT_EQ(bufSz, 52, "expected 4 header + 48 HMAC"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ----- Group I: Internal Encrypt/Decrypt ----- */ + +static int test_encrypt_internal_null_args(void) +{ + byte plain[16], enc[256]; + word32 encSz = sizeof(enc); + TEST_CTX_SETUP_V12(); + printf("test_encrypt_internal_null_args...\n"); + + TEST_ASSERT(wolfSPDM_EncryptInternal(NULL, plain, 16, enc, &encSz) + != WOLFSPDM_SUCCESS, "NULL ctx"); + TEST_ASSERT(wolfSPDM_EncryptInternal(ctx, NULL, 16, enc, &encSz) + != WOLFSPDM_SUCCESS, "NULL plain"); + TEST_ASSERT(wolfSPDM_EncryptInternal(ctx, plain, 16, NULL, &encSz) + != WOLFSPDM_SUCCESS, "NULL enc"); + TEST_ASSERT(wolfSPDM_EncryptInternal(ctx, plain, 16, enc, NULL) + != WOLFSPDM_SUCCESS, "NULL encSz"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_encrypt_decrypt_roundtrip(void) +{ + byte plain[16] = "Hello SPDM test!"; + static byte enc[512]; + static byte dec[256]; + word32 encSz = sizeof(enc); + word32 decSz = sizeof(dec); + TEST_CTX_SETUP_V12(); + printf("test_encrypt_decrypt_roundtrip...\n"); + + /* Set up session keys (same for req/rsp for self-roundtrip) */ + ctx->sessionId = 0x00020001; + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + XMEMSET(ctx->reqDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataKey, 0x11, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->reqDataIv, 0x22, WOLFSPDM_AEAD_IV_SIZE); + XMEMSET(ctx->rspDataIv, 0x22, WOLFSPDM_AEAD_IV_SIZE); + + /* Encrypt */ + ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, 16, enc, &encSz)); + TEST_ASSERT(encSz > 16, "encrypted should be larger"); + + /* Reset rsp seq to match what was encrypted (req incremented to 1) */ + ctx->rspSeqNum = 0; + + /* Decrypt */ + ASSERT_SUCCESS(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz)); + ASSERT_EQ(decSz, 16, "decrypted size mismatch"); + TEST_ASSERT(memcmp(dec, plain, 16) == 0, "plaintext mismatch"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +#ifdef WOLFTPM_SPDM_TCG +static int test_encrypt_decrypt_roundtrip_tcg(void) +{ + byte plain[16] = "TCG encrypt tst!"; + static byte enc[512]; + static byte dec[256]; + word32 encSz = sizeof(enc); + word32 decSz = sizeof(dec); + TEST_CTX_SETUP_V12(); + printf("test_encrypt_decrypt_roundtrip_tcg...\n"); + + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS); + ctx->sessionId = 0x00020001; + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + XMEMSET(ctx->reqDataKey, 0x33, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->rspDataKey, 0x33, WOLFSPDM_AEAD_KEY_SIZE); + XMEMSET(ctx->reqDataIv, 0x44, WOLFSPDM_AEAD_IV_SIZE); + XMEMSET(ctx->rspDataIv, 0x44, WOLFSPDM_AEAD_IV_SIZE); + + ASSERT_SUCCESS(wolfSPDM_EncryptInternal(ctx, plain, 16, enc, &encSz)); + TEST_ASSERT(encSz > 16, "encrypted should be larger"); + + ctx->rspSeqNum = 0; + ASSERT_SUCCESS(wolfSPDM_DecryptInternal(ctx, enc, encSz, dec, &decSz)); + ASSERT_EQ(decSz, 16, "decrypted size mismatch"); + TEST_ASSERT(memcmp(dec, plain, 16) == 0, "plaintext mismatch"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* WOLFTPM_SPDM_TCG */ + /* ----- Main ----- */ int main(void) @@ -990,6 +1959,73 @@ int main(void) test_setdebug_truncation(); test_key_zeroing(); + /* ----- NEW COVERAGE TESTS ----- */ + + /* Public API coverage */ + test_set_requester_key_pair(); + test_connect_null_args(); + test_get_version_no_io(); + test_key_exchange_no_io(); + test_finish_no_io(); + test_secured_exchange_null_args(); + test_disconnect_states(); + +#ifdef WOLFTPM_SPDM_TCG + /* TCG message framing */ + test_build_tcg_clear_message(); + test_parse_tcg_clear_message(); + test_build_vendor_defined(); + test_parse_vendor_defined(); + test_vendor_defined_roundtrip(); + test_tcg_get_pub_key_null_args(); + test_tcg_give_pub_key_null_args(); + test_set_requester_key_tpmt(); + test_connect_tcg_null_args(); +#endif +#ifdef WOLFSPDM_NUVOTON + test_nuvoton_get_status_null_args(); + test_nuvoton_set_only_mode_null_args(); +#endif +#ifdef WOLFSPDM_NATIONS + test_nations_get_status_null_args(); + test_nations_set_only_mode_null_args(); + test_nations_psk_set_null_args(); + test_nations_psk_clear_null_args(); + test_nations_psk_clear_vca_null_args(); +#endif +#ifdef WOLFTPM_SPDM_PSK + test_parse_psk_exchange_rsp_null_args(); + test_build_psk_finish_null_args(); + test_build_psk_finish_format(); + test_parse_psk_finish_rsp(); + test_connect_psk_null_args(); +#endif + + /* Internal crypto */ + test_sha384_hash(); + test_export_ephemeral_pub_key(); + test_sign_hash_null_args(); + test_verify_signature_null_args(); + test_sign_verify_roundtrip(); + + /* Internal KDF */ + test_derive_handshake_keys(); + test_derive_from_handshake_secret(); + test_derive_app_data_keys(); + + /* Internal message building */ + test_build_key_exchange_null_args(); + test_build_key_exchange_format(); + test_build_finish_null_args(); + test_build_finish_format(); + + /* Internal encrypt/decrypt */ + test_encrypt_internal_null_args(); + test_encrypt_decrypt_roundtrip(); +#ifdef WOLFTPM_SPDM_TCG + test_encrypt_decrypt_roundtrip_tcg(); +#endif + printf("\n===========================================\n"); printf("Results: %d passed, %d failed\n", g_testsPassed, g_testsFailed); printf("===========================================\n");