diff --git a/.github/workflows/make-test-swtpm.yml b/.github/workflows/make-test-swtpm.yml index c44197c2..2595237c 100644 --- a/.github/workflows/make-test-swtpm.yml +++ b/.github/workflows/make-test-swtpm.yml @@ -75,6 +75,21 @@ 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 # Microchip - name: microchip wolftpm_config: --enable-microchip diff --git a/.gitignore b/.gitignore index d6a057fb..66039172 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ examples/firmware/ifx_fw_update examples/firmware/st33_fw_update examples/endorsement/get_ek_certs examples/endorsement/verify_ek_cert +examples/spdm/spdm_demo # Generated Cert Files certs/ca-*.pem @@ -176,10 +177,18 @@ UpgradeLog.htm /IDE/Espressif/**/sdkconfig /IDE/Espressif/**/sdkconfig.old +# SPDM build artifacts +spdm/wolfspdm/options.h +spdm/config.h +spdm/stamp-h1 +spdm/src/.libs/ +spdm/src/.deps/ +spdm/test/.libs/ +spdm/test/unit_test + # Firmware files examples/firmware/*.fi examples/firmware/*.BIN examples/firmware/*.DATA examples/firmware/*.MANIFEST examples/firmware/*.MANIFESTHASH - diff --git a/Makefile.am b/Makefile.am index afee7851..458d6cdd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -46,6 +46,7 @@ include tests/include.am include docs/include.am include wrapper/include.am include hal/include.am +include spdm/include.am include cmake/include.am include zephyr/include.am diff --git a/configure.ac b/configure.ac index 00533a2a..64e36b4e 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,7 @@ AC_CANONICAL_HOST AC_CANONICAL_TARGET AC_CONFIG_MACRO_DIR([m4]) + AM_INIT_AUTOMAKE([1.11 -Wall -Werror -Wno-portability foreign tar-ustar subdir-objects no-define color-tests]) AC_ARG_PROGRAM @@ -462,6 +463,48 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_PROVISIONING" fi +# SPDM Support +AC_ARG_ENABLE([spdm], + [AS_HELP_STRING([--enable-spdm],[Enable SPDM support (default: disabled)])], + [ ENABLED_SPDM=$enableval ], + [ ENABLED_SPDM=no ] + ) + +AC_ARG_WITH([wolfspdm], + [AS_HELP_STRING([--with-wolfspdm=PATH],[DEPRECATED: Use --enable-spdm instead.])], + [AC_MSG_ERROR([--with-wolfspdm is no longer needed. Use --enable-spdm instead.])]) + +# SPDM dynamic memory (default: static/zero-malloc) +AC_ARG_ENABLE([spdm-dynamic-mem], + [AS_HELP_STRING([--enable-spdm-dynamic-mem],[SPDM: Use heap allocation for context (default: static)])], + [ ENABLED_SPDM_DYNMEM=$enableval ], + [ ENABLED_SPDM_DYNMEM=no ] + ) + +if test "x$ENABLED_SPDM" = "xyes" +then + AC_DEFINE([WOLFTPM_SPDM], [1], [Enable SPDM support]) + + # Add spdm/ include path so all targets can find + AM_CPPFLAGS="$AM_CPPFLAGS -I\$(srcdir)/spdm" + + # Nuvoton SPDM support (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 + + if test "x$ENABLED_SPDM_DYNMEM" = "xyes" + then + AC_DEFINE([WOLFSPDM_DYNAMIC_MEMORY], [1], [SPDM: Enable dynamic memory allocation]) + fi + + if test "x$ax_enable_debug" != "xno" + then + AC_DEFINE([WOLFSPDM_DEBUG], [1], [SPDM: Enable debug output]) + fi +fi # HARDEN FLAGS AX_HARDEN_CC_COMPILER_FLAGS @@ -493,6 +536,7 @@ AM_CONDITIONAL([BUILD_CHECKWAITSTATE], [test "x$ENABLED_CHECKWAITSTATE" = "xyes" AM_CONDITIONAL([BUILD_AUTODETECT], [test "x$ENABLED_AUTODETECT" = "xyes"]) AM_CONDITIONAL([BUILD_FIRMWARE], [test "x$ENABLED_FIRMWARE" = "xyes"]) AM_CONDITIONAL([BUILD_HAL], [test "x$ENABLED_EXAMPLE_HAL" = "xyes" || test "x$ENABLED_MMIO" = "xyes"]) +AM_CONDITIONAL([BUILD_SPDM], [test "x$ENABLED_SPDM" = "xyes"]) CREATE_HEX_VERSION @@ -578,6 +622,10 @@ for option in $OPTION_FLAGS; do fi done +# Also capture SPDM defines from config.h (set via AC_DEFINE, not AM_CFLAGS) +grep '^#define WOLFSPDM_' src/config.h >> $OPTION_FILE 2>/dev/null || true +grep '^#define WOLFTPM_SPDM' src/config.h >> $OPTION_FILE 2>/dev/null || true + echo "" >> $OPTION_FILE echo "#ifdef __cplusplus" >> $OPTION_FILE echo "}" >> $OPTION_FILE @@ -622,3 +670,7 @@ echo " * Nuvoton NPCT75x: $ENABLED_NUVOTON" echo " * Runtime Module Detection: $ENABLED_AUTODETECT" echo " * Firmware Upgrade Support: $ENABLED_FIRMWARE" +echo " * SPDM Support: $ENABLED_SPDM" +if test "x$ENABLED_SPDM" = "xyes"; then + echo " * SPDM Dynamic Mem: $ENABLED_SPDM_DYNMEM" +fi diff --git a/examples/include.am b/examples/include.am index 96c034f0..d34804ac 100644 --- a/examples/include.am +++ b/examples/include.am @@ -18,6 +18,7 @@ include examples/seal/include.am include examples/attestation/include.am include examples/firmware/include.am include examples/endorsement/include.am +include examples/spdm/include.am if BUILD_EXAMPLES EXTRA_DIST += examples/run_examples.sh diff --git a/examples/spdm/README.md b/examples/spdm/README.md new file mode 100644 index 00000000..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..0d07e8a4 --- /dev/null +++ b/examples/spdm/include.am @@ -0,0 +1,18 @@ +# vim:ft=automake +# All paths should be given relative to the root + +if BUILD_EXAMPLES +if BUILD_SPDM +noinst_PROGRAMS += examples/spdm/spdm_demo + +examples_spdm_spdm_demo_SOURCES = examples/spdm/spdm_demo.c +examples_spdm_spdm_demo_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_spdm_spdm_demo_DEPENDENCIES = src/libwolftpm.la +examples_spdm_spdm_demo_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/spdm +endif +endif + +example_spdmdir = $(exampledir)/spdm +dist_example_spdm_DATA = examples/spdm/spdm_demo.c + +DISTCLEANFILES+= examples/spdm/.libs/spdm_demo diff --git a/examples/spdm/spdm_demo.c b/examples/spdm/spdm_demo.c new file mode 100644 index 00000000..be6e5ea5 --- /dev/null +++ b/examples/spdm/spdm_demo.c @@ -0,0 +1,256 @@ +/* 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 - Nuvoton NPCT75x secure session\n\n" + "Usage: spdm_demo [options]\n" + " --enable Enable SPDM via NTC2_PreConfig\n" + " --disable Disable SPDM via NTC2_PreConfig\n" + " --status Query SPDM status\n" + " --get-pubkey Get TPM's SPDM-Identity public key\n" + " --connect Establish SPDM session\n" + " --lock Lock SPDM-only mode\n" + " --unlock Unlock SPDM-only mode\n" + " -h, --help Show this help\n\n" + "Build: ./configure --enable-spdm --enable-nuvoton\n"); +} + +#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 */ + +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); +#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 + { printf("Unknown option: %s\n", argv[i]); usage(); rc = BAD_FUNC_ARG; } + if (rc != 0) break; + } + + wolfTPM2_SpdmCleanup(&dev); + wolfTPM2_Cleanup(&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..6d9e0e49 --- /dev/null +++ b/examples/spdm/spdm_test.sh @@ -0,0 +1,88 @@ +#!/bin/bash +# spdm_test.sh - Nuvoton SPDM hardware tests +# +# 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" +PASS=0 FAIL=0 TOTAL=0 + +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 "" +} + +if [ ! -x "$SPDM_DEMO" ]; then + echo "Error: $SPDM_DEMO not found. Usage: $0 [path-to-spdm_demo]" + exit 1 +fi + +echo "=== Nuvoton SPDM Hardware Tests ===" +echo "Demo: $SPDM_DEMO Caps: $CAPS_DEMO Unit: $UNIT_TEST" +echo "" + +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 "Cleartext caps (no SPDM)" "$CAPS_DEMO" +else + echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" +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/spdm/README.md b/spdm/README.md new file mode 100644 index 00000000..c8b25a51 --- /dev/null +++ b/spdm/README.md @@ -0,0 +1,246 @@ +# wolfTPM SPDM + +wolfTPM includes built-in SPDM support for Nuvoton NPCT75x TPMs using +wolfSSL/wolfCrypt. This provides encrypted bus communication between the host +and TPM, ensuring all commands and responses are protected with AES-256-GCM. + +For standard SPDM protocol testing with the DMTF spdm-emu emulator, see the +[wolfSPDM](https://github.com/aidangarske/wolfSPDM) standalone library. + +## How It Works + +SPDM (Security Protocol and Data Model) establishes an authenticated encrypted +channel over the existing SPI/I2C bus. Once active, every TPM command is +automatically encrypted — no application code changes needed. + +### Protocol Flow + +``` +Host TPM (Nuvoton NPCT75x) + | | + |--- GET_VERSION ------------------>| (negotiate SPDM 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. + +### Command Flow + +The typical usage flow for SPDM-only mode: + +``` +1. Enable SPDM (one-time, persists across resets) +2. Connect (ECDH handshake, derives session keys) +3. Lock SPDM-only (TPM rejects all cleartext commands) +4. GPIO reset (TPM enters SPDM-only enforcement) +5. Run any commands (each auto-establishes SPDM, all AES-256-GCM encrypted) +6. Unlock (connect + unlock in one session) +7. GPIO reset (TPM back to normal cleartext mode) +``` + +Step 5 is fully automatic. When wolfTPM detects SPDM-only mode (TPM2_Startup +returns `TPM_RC_DISABLED`), it transparently establishes an SPDM session. +Existing applications like `caps`, `wrap_test`, and `unit.test` work without +modification — all commands are encrypted over the bus. + +## Building + +### Prerequisites + +wolfSSL with the crypto algorithms required for SPDM Algorithm Set B: + +```bash +cd wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp +make +sudo make install +sudo ldconfig +``` + +The `--enable-sp` flag enables Single Precision math with optimized ECC P-384 +support. For a broader feature set, `--enable-all` can be used instead. + +### wolfTPM with Nuvoton SPDM + +```bash +cd wolfTPM +./autogen.sh +./configure --enable-spdm --enable-nuvoton +make +``` + +### Configure Options + +| Option | Description | +|---|---| +| `--enable-spdm` | Enable SPDM support (required) | +| `--enable-nuvoton` | Enable Nuvoton TPM hardware support | +| `--enable-debug` | Debug output with verbose SPDM tracing | +| `--enable-spdm-dynamic-mem` | Heap-allocated SPDM context (default: static ~32 KB) | + +## Nuvoton NPCT75x Usage + +### One-Time Setup + +```bash +# Enable SPDM on the TPM (persists across resets) +./examples/spdm/spdm_demo --enable + +# GPIO reset to apply +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# Verify SPDM is enabled +./examples/spdm/spdm_demo --status +``` + +### Lock SPDM-Only Mode + +```bash +# Establish session and lock +./examples/spdm/spdm_demo --connect --lock + +# GPIO reset — TPM now requires SPDM for all commands +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# All commands are now automatically encrypted: +./examples/wrap/caps # auto-SPDM session, all AES-256-GCM +./tests/unit.test # full test suite over encrypted bus +``` + +### Unlock SPDM-Only Mode + +```bash +# GPIO reset + unlock (auto-connects since TPM is in SPDM-only mode) +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect --unlock + +# GPIO reset — TPM back to normal +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# Verify cleartext works +./examples/wrap/caps +``` + +### Running the Test Suite + +```bash +# Run all Nuvoton SPDM tests (status, connect, lock, unit test, unlock, caps) +./examples/spdm/spdm_test.sh +``` + +### Demo Options + +| Option | Description | +|--------|-------------| +| `--enable` | Enable SPDM on Nuvoton TPM (one-time) | +| `--disable` | Disable SPDM on Nuvoton TPM | +| `--status` | Query SPDM status from TPM | +| `--get-pubkey` | Get TPM's SPDM-Identity public key | +| `--connect` | Establish SPDM session | +| `--lock` | Lock SPDM-only mode (use with `--connect`) | +| `--unlock` | Unlock SPDM-only mode (use with `--connect`) | + +## 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 + +### TPM returns TPM_RC_DISABLED (0x120) + +The TPM is in SPDM-only mode. Either establish an SPDM session first, +or unlock SPDM-only mode: + +```bash +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect --unlock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +### SPDM handshake fails after interrupted session + +GPIO reset clears stale SPDM state on the TPM: + +```bash +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +If GPIO is not wired, a full power cycle is required. + +### 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/spdm/include.am b/spdm/include.am new file mode 100644 index 00000000..d37a58ea --- /dev/null +++ b/spdm/include.am @@ -0,0 +1,35 @@ +# vim:ft=automake +# All paths should be given relative to the root + +if BUILD_SPDM + +src_libwolftpm_la_SOURCES += \ + spdm/src/spdm_context.c \ + spdm/src/spdm_crypto.c \ + spdm/src/spdm_kdf.c \ + spdm/src/spdm_msg.c \ + spdm/src/spdm_secured.c \ + spdm/src/spdm_session.c \ + spdm/src/spdm_transcript.c + +if BUILD_NUVOTON +src_libwolftpm_la_SOURCES += spdm/src/spdm_nuvoton.c +endif + +src_libwolftpm_la_CFLAGS += -I$(srcdir)/spdm -I$(srcdir)/spdm/src + +wolfspdmincdir = $(includedir)/wolfspdm +wolfspdminc_HEADERS = \ + spdm/wolfspdm/spdm.h \ + spdm/wolfspdm/spdm_types.h \ + spdm/wolfspdm/spdm_error.h \ + spdm/wolfspdm/spdm_nuvoton.h + +check_PROGRAMS += spdm/test/unit_test +spdm_test_unit_test_SOURCES = spdm/test/unit_test.c +spdm_test_unit_test_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +spdm_test_unit_test_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/spdm -I$(srcdir)/spdm/src + +EXTRA_DIST += spdm/src/spdm_internal.h + +endif BUILD_SPDM diff --git a/spdm/src/spdm_context.c b/spdm/src/spdm_context.c new file mode 100644 index 00000000..0b0019e5 --- /dev/null +++ b/spdm/src/spdm_context.c @@ -0,0 +1,530 @@ +/* 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; +} + +#ifdef WOLFSPDM_NUVOTON +int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz) +{ + if (ctx == NULL || tpmtPub == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (tpmtPubSz > sizeof(ctx->reqPubKeyTPMT)) { + return WOLFSPDM_E_INVALID_ARG; + } + XMEMCPY(ctx->reqPubKeyTPMT, tpmtPub, tpmtPubSz); + ctx->reqPubKeyTPMTLen = tpmtPubSz; + return WOLFSPDM_SUCCESS; +} +#endif /* WOLFSPDM_NUVOTON */ + +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; + } + + if (mode != WOLFSPDM_MODE_NUVOTON) { + return WOLFSPDM_E_INVALID_ARG; /* Only Nuvoton mode supported */ + } + +#ifdef WOLFSPDM_NUVOTON + ctx->mode = WOLFSPDM_MODE_NUVOTON; + /* Initialize Nuvoton-specific fields */ + ctx->connectionHandle = WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT; + ctx->fipsIndicator = WOLFSPDM_NUVOTON_FIPS_DEFAULT; + return WOLFSPDM_SUCCESS; +#else + return WOLFSPDM_E_INVALID_ARG; /* Nuvoton support not compiled in */ +#endif +} + +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; +} + +#ifdef WOLFSPDM_NUVOTON +word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return ctx->connectionHandle; +} + +word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return ctx->fipsIndicator; +} +#endif + +/* ----- Session Establishment - Connect (Full Handshake) ----- */ + +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; + } + +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + return wolfSPDM_ConnectNuvoton(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; + } + +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + /* Wrap messages with TCG SPDM + * headers; I/O sends TCG-framed messages. */ + byte tcgTx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + byte tcgRx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + word32 tcgRxSz = sizeof(tcgRx); + int tcgTxSz; + word16 tag; + + /* Detect message type: SPDM version byte 0x10-0x1F = clear message. + * Secured records start with SessionID (LE, typically 0x01 0x00...), + * which is never in the SPDM version range. */ + if (txSz > 0 && txBuf[0] >= 0x10 && txBuf[0] <= 0x1F) { + /* Clear SPDM message - wrap with TCG clear header (0x8101) */ + tcgTxSz = wolfSPDM_BuildTcgClearMessage(ctx, txBuf, txSz, + tcgTx, sizeof(tcgTx)); + } else { + /* Secured record - prepend TCG secured header (0x8201) */ + word32 totalSz = WOLFSPDM_TCG_HEADER_SIZE + txSz; + if (totalSz > sizeof(tcgTx)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + wolfSPDM_WriteTcgHeader(tcgTx, WOLFSPDM_TCG_TAG_SECURED, + totalSz, ctx->connectionHandle, ctx->fipsIndicator); + XMEMCPY(tcgTx + WOLFSPDM_TCG_HEADER_SIZE, txBuf, txSz); + tcgTxSz = (int)totalSz; + } + + if (tcgTxSz < 0) { + return tcgTxSz; + } + + wolfSPDM_DebugHex(ctx, "TCG TX", tcgTx, (word32)tcgTxSz); + + /* Send/receive via I/O callback (raw transport) */ + rc = ctx->ioCb(ctx, tcgTx, (word32)tcgTxSz, tcgRx, &tcgRxSz, + ctx->ioUserCtx); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "Nuvoton I/O failed: %d\n", rc); + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugHex(ctx, "TCG RX", tcgRx, tcgRxSz); + + /* Strip TCG binding header from response */ + if (tcgRxSz < WOLFSPDM_TCG_HEADER_SIZE) { + wolfSPDM_DebugPrint(ctx, "SendReceive: response too short (%u)\n", + tcgRxSz); + return WOLFSPDM_E_BUFFER_SMALL; + } + + tag = SPDM_Get16BE(tcgRx); + if (tag != WOLFSPDM_TCG_TAG_CLEAR && tag != WOLFSPDM_TCG_TAG_SECURED) { + wolfSPDM_DebugPrint(ctx, "SendReceive: unexpected TCG tag " + "0x%04x\n", tag); + return WOLFSPDM_E_PEER_ERROR; + } + + /* Capture FIPS indicator from response if non-zero */ + { + word16 rspFips = SPDM_Get16BE(tcgRx + 10); + if (rspFips != 0) { + ctx->fipsIndicator = rspFips; + } + } + + /* Extract payload (everything after 16-byte TCG header) */ + { + word32 msgSize = SPDM_Get32BE(tcgRx + 2); + word32 payloadSz; + + if (msgSize < 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 */ + + rc = ctx->ioCb(ctx, txBuf, txSz, rxBuf, rxSz, ctx->ioUserCtx); + if (rc != 0) { + return WOLFSPDM_E_IO_FAIL; + } + + return WOLFSPDM_SUCCESS; +} + +/* ----- Debug Utilities ----- */ + +void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) +{ + va_list args; + + if (ctx == NULL || !ctx->flags.debug) { + return; + } + + printf("[wolfSPDM] "); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + fflush(stdout); +} + +void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, + const byte* data, word32 len) +{ + word32 i; + + if (ctx == NULL || !ctx->flags.debug || data == NULL) { + return; + } + + printf("[wolfSPDM] %s (%u bytes): ", label, len); + for (i = 0; i < len && i < 32; i++) { + printf("%02x", data[i]); + } + if (len > 32) { + printf("..."); + } + printf("\n"); + fflush(stdout); +} + +/* ----- 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/spdm/src/spdm_crypto.c b/spdm/src/spdm_crypto.c new file mode 100644 index 00000000..b7938f47 --- /dev/null +++ b/spdm/src/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[120]; /* P-384 DER sig max */ + word32 derSigSz = sizeof(derSig); + int verified = 0; + const byte* pubKeyX; + const byte* pubKeyY; + + if (ctx == NULL || hash == NULL || sig == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.hasRspPubKey || ctx->rspPubKeyLen < WOLFSPDM_ECC_POINT_SIZE) { + wolfSPDM_DebugPrint(ctx, "No responder public key for verification\n"); + return WOLFSPDM_E_BAD_STATE; + } + + 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[104]; /* P-384 DER sig max: 2 + (2+49) + (2+49) = 104 */ + word32 derSigSz = sizeof(derSig); + word32 rLen, sLen; + + if (ctx == NULL || hash == NULL || sig == NULL || sigSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.hasReqKeyPair || ctx->reqPrivKeyLen == 0) { + wolfSPDM_DebugPrint(ctx, "No requester key pair for signing\n"); + return WOLFSPDM_E_BAD_STATE; + } + + 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/spdm/src/spdm_internal.h b/spdm/src/spdm_internal.h new file mode 100644 index 00000000..9f32bf60 --- /dev/null +++ b/spdm/src/spdm_internal.h @@ -0,0 +1,336 @@ +/* spdm_internal.h + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFSPDM_INTERNAL_H +#define WOLFSPDM_INTERNAL_H + +/* Include autoconf generated config.h for feature detection */ +#ifdef HAVE_CONFIG_H + #include +#endif + +/* wolfSSL options MUST be included first */ +#ifndef WOLFSSL_USER_SETTINGS + #include +#endif +#include + +#include +#include +#include + +/* wolfCrypt includes */ +#include +#include +#include +#include +#include +#include +#include + +#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; + +#ifdef WOLFSPDM_NUVOTON + /* Nuvoton-specific: TCG binding fields */ + word32 connectionHandle; /* Connection handle (usually 0) */ + word16 fipsIndicator; /* FIPS service indicator */ + + /* Nuvoton-specific: Host's public key in TPMT_PUBLIC format */ + byte reqPubKeyTPMT[128]; /* TPMT_PUBLIC serialized (~120 bytes) */ + word32 reqPubKeyTPMTLen; +#endif + + /* Random number generator */ + WC_RNG rng; + + /* Negotiated parameters */ + byte 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[128]; /* TPMT_PUBLIC (120 bytes for P-384) or raw X||Y (96) */ + word32 rspPubKeyLen; + + /* Requester's identity key pair (for mutual auth) */ + byte reqPrivKey[WOLFSPDM_ECC_KEY_SIZE]; + word32 reqPrivKeyLen; + byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; + +}; + +/* ----- 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) */ +#ifdef WOLFSPDM_NUVOTON +static WC_INLINE void wolfSPDM_WriteTcgHeader(byte* buf, word16 tag, + word32 totalSz, word32 connHandle, word16 fips) +{ + SPDM_Set16BE(buf, tag); + SPDM_Set32BE(buf + 2, totalSz); + SPDM_Set32BE(buf + 6, connHandle); + SPDM_Set16BE(buf + 10, fips); + XMEMSET(buf + 12, 0, 4); /* Reserved */ +} +#endif + +/* ----- Build IV ----- */ +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_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); + +/* ----- 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); + +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); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_INTERNAL_H */ diff --git a/spdm/src/spdm_kdf.c b/spdm/src/spdm_kdf.c new file mode 100644 index 00000000..16411d10 --- /dev/null +++ b/spdm/src/spdm_kdf.c @@ -0,0 +1,253 @@ +/* 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); +} + +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) { + rc = WOLFSPDM_E_CRYPTO_FAIL; + } + if (rc == WOLFSPDM_SUCCESS) { + /* reqHsSecret = HKDF-Expand(HS, "req hs data" || TH1, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_REQ_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, + ctx->reqHsSecret, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* rspHsSecret = HKDF-Expand(HS, "rsp hs data" || TH1, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_RSP_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, + ctx->rspHsSecret, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Finished keys (used for VerifyData HMAC) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->reqHsSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, + ctx->reqFinishedKey, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->rspHsSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, + ctx->rspFinishedKey, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Data encryption keys + IVs (AES-256-GCM) */ + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->reqHsSecret, + ctx->reqDataKey, ctx->reqDataIv); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->rspHsSecret, + ctx->rspDataKey, ctx->rspDataIv); + } + + return rc; +} + +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/spdm/src/spdm_msg.c b/spdm/src/spdm_msg.c new file mode 100644 index 00000000..b906820f --- /dev/null +++ b/spdm/src/spdm_msg.c @@ -0,0 +1,528 @@ +/* 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 0x10 */ + buf[0] = 0x10; + 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 */ +#ifdef WOLFSPDM_NUVOTON + buf[offset++] = 0xFF; /* SlotID = 0xFF (no cert, use provisioned public key) */ +#else + buf[offset++] = 0x00; /* SlotID = 0 (certificate slot 0) */ +#endif + + /* ReqSessionID (2 LE) */ + buf[offset++] = (byte)(ctx->reqSessionId & 0xFF); + buf[offset++] = (byte)((ctx->reqSessionId >> 8) & 0xFF); + + buf[offset++] = 0x00; /* SessionPolicy */ + buf[offset++] = 0x00; /* Reserved */ + + /* RandomData (32 bytes) */ + rc = wolfSPDM_GetRandom(ctx, &buf[offset], WOLFSPDM_RANDOM_SIZE); + if (rc == WOLFSPDM_SUCCESS) { + offset += WOLFSPDM_RANDOM_SIZE; + + /* ExchangeData: X || Y */ + XMEMCPY(&buf[offset], pubKeyX, WOLFSPDM_ECC_KEY_SIZE); + offset += WOLFSPDM_ECC_KEY_SIZE; + XMEMCPY(&buf[offset], pubKeyY, WOLFSPDM_ECC_KEY_SIZE); + offset += WOLFSPDM_ECC_KEY_SIZE; + + /* OpaqueData for secured message version negotiation */ +#ifdef WOLFSPDM_NUVOTON + /* Nuvoton format: 12 bytes per spec Rev 1.11 page 19-20 */ + buf[offset++] = 0x0c; /* OpaqueLength = 12 (LE) */ + buf[offset++] = 0x00; + buf[offset++] = 0x00; buf[offset++] = 0x00; /* SMDataID = 0 */ + buf[offset++] = 0x05; buf[offset++] = 0x00; /* DataSize = 5 (LE) */ + buf[offset++] = 0x01; /* Registry ID = 1 (DMTF) */ + buf[offset++] = 0x01; /* VendorLen = 1 */ + buf[offset++] = 0x01; buf[offset++] = 0x00; /* VersionCount = 1, Reserved = 0 */ + buf[offset++] = 0x10; buf[offset++] = 0x00; /* Version 1.0 (0x0010 LE) */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding to make OpaqueData 12 bytes */ +#else + /* Standard SPDM 1.2+ OpaqueData format: 20 bytes */ + buf[offset++] = 0x14; /* OpaqueLength = 20 */ + buf[offset++] = 0x00; + buf[offset++] = 0x01; buf[offset++] = 0x00; /* TotalElements */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Reserved */ + buf[offset++] = 0x00; buf[offset++] = 0x00; + buf[offset++] = 0x09; buf[offset++] = 0x00; /* DataSize */ + buf[offset++] = 0x01; /* Registry ID */ + buf[offset++] = 0x01; /* VendorLen */ + buf[offset++] = 0x03; buf[offset++] = 0x00; /* VersionCount */ + buf[offset++] = 0x10; buf[offset++] = 0x00; /* 1.0 */ + buf[offset++] = 0x11; buf[offset++] = 0x00; /* 1.1 */ + buf[offset++] = 0x12; buf[offset++] = 0x00; /* 1.2 */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding */ +#endif + + *bufSz = offset; + } + } + + return 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; + } + +#ifdef WOLFSPDM_NUVOTON + /* Nuvoton requires mutual authentication when we have a requester key */ + if (ctx->mode == WOLFSPDM_MODE_NUVOTON && ctx->flags.hasReqKeyPair) { + mutualAuth = 1; + wolfSPDM_DebugPrint(ctx, "Nuvoton: Mutual auth ENABLED (required after GIVE_PUB)\n"); + } +#endif + + /* Check buffer size: 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 */ + buf[3] = 0xFF; /* Param2: 0xFF = requester public key provisioned in trusted environment (GIVE_PUB_KEY) */ + } else { + buf[2] = 0x00; /* Param1: No signature */ + buf[3] = 0x00; /* Param2: SlotID = 0 when no signature */ + } + + /* 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 Nuvoton PUB_KEY_ID, Cm = TPMT_PUBLIC. */ +#ifdef WOLFSPDM_NUVOTON + 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); + + /* 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; +} diff --git a/spdm/src/spdm_nuvoton.c b/spdm/src/spdm_nuvoton.c new file mode 100644 index 00000000..449ff17f --- /dev/null +++ b/spdm/src/spdm_nuvoton.c @@ -0,0 +1,566 @@ +/* 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 + */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_NUVOTON + +#include + +/* ----- Vendor Command Helper Types ----- */ + +/* Response container for clear vendor commands */ +typedef struct { + char vdCode[WOLFSPDM_VDCODE_LEN + 1]; + byte payload[256]; + word32 payloadSz; +} WOLFSPDM_VENDOR_RSP; + +/* Clear vendor command: build → SendReceive → check error → parse response */ +static int wolfSPDM_VendorCmdClear(WOLFSPDM_CTX* ctx, const char* vdCode, + const byte* payload, word32 payloadSz, WOLFSPDM_VENDOR_RSP* rsp) +{ + byte spdmMsg[256]; + int spdmMsgSz; + byte rxBuf[512]; + word32 rxSz; + int rc; + + { + 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; +} + +/* Secured vendor command: build → SecuredExchange → check error */ +static int wolfSPDM_VendorCmdSecured(WOLFSPDM_CTX* ctx, const char* vdCode, + const byte* payload, word32 payloadSz) +{ + byte spdmMsg[256]; + int spdmMsgSz; + byte decBuf[256]; + word32 decSz; + int rc; + + { + 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; + } + + /* TCG binding header (16 bytes per Nuvoton spec): + * tag(2/BE) + size(4/BE) + connHandle(4/BE) + fips(2/BE) + reserved(4) */ + totalSz = WOLFSPDM_TCG_HEADER_SIZE + spdmPayloadSz; + + if (outBufSz < totalSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + wolfSPDM_WriteTcgHeader(outBuf, WOLFSPDM_TCG_TAG_CLEAR, totalSz, + ctx->connectionHandle, ctx->fipsIndicator); + /* SPDM Payload */ + XMEMCPY(outBuf + WOLFSPDM_TCG_HEADER_SIZE, spdmPayload, spdmPayloadSz); + + return (int)totalSz; +} + +int wolfSPDM_ParseTcgClearMessage( + const byte* inBuf, word32 inBufSz, + byte* spdmPayload, word32* spdmPayloadSz, + WOLFSPDM_TCG_CLEAR_HDR* hdr) +{ + word16 tag; + word32 msgSize; + word32 payloadSz; + + if (inBuf == NULL || spdmPayload == NULL || spdmPayloadSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (inBufSz < WOLFSPDM_TCG_HEADER_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Parse header */ + tag = SPDM_Get16BE(inBuf); + if (tag != WOLFSPDM_TCG_TAG_CLEAR) { + return WOLFSPDM_E_PEER_ERROR; + } + + msgSize = SPDM_Get32BE(inBuf + 2); + if (msgSize < 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; + } + + /* Fill header if requested */ + if (hdr != NULL) { + hdr->tag = tag; + hdr->size = msgSize; + hdr->connectionHandle = SPDM_Get32BE(inBuf + 6); + hdr->fipsIndicator = SPDM_Get16BE(inBuf + 10); + hdr->reserved = SPDM_Get32BE(inBuf + 12); + } + + /* Extract payload */ + XMEMCPY(spdmPayload, inBuf + WOLFSPDM_TCG_HEADER_SIZE, payloadSz); + *spdmPayloadSz = payloadSz; + + return (int)payloadSz; +} + +/*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 (per Nuvoton SPDM Guidance): + * SPDMVersion(1) + reqRspCode(1) + param1(1) + param2(1) + + * standardId(2/LE) + vendorIdLen(1) + reqLength(2/LE) + + * vdCode(8) + payload */ + totalSz = 1 + 1 + 1 + 1 + 2 + 1 + 2 + WOLFSPDM_VDCODE_LEN + payloadSz; + + if (outBufSz < totalSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + outBuf[offset++] = spdmVersion; + /* Request/Response Code */ + outBuf[offset++] = SPDM_VENDOR_DEFINED_REQUEST; + /* Param1, Param2 */ + outBuf[offset++] = 0x00; + outBuf[offset++] = 0x00; + /* Standard ID (0x0001 = TCG, little-endian per Nuvoton spec) */ + SPDM_Set16LE(outBuf + offset, 0x0001); + offset += 2; + /* Vendor ID Length (0 for TCG) */ + outBuf[offset++] = 0x00; + /* Request Length (vdCode + payload, little-endian per Nuvoton spec) */ + SPDM_Set16LE(outBuf + offset, (word16)(WOLFSPDM_VDCODE_LEN + payloadSz)); + offset += 2; + /* VdCode (8-byte ASCII) */ + XMEMCPY(outBuf + offset, vdCode, WOLFSPDM_VDCODE_LEN); + offset += WOLFSPDM_VDCODE_LEN; + /* Payload */ + if (payload != NULL && payloadSz > 0) { + XMEMCPY(outBuf + offset, payload, payloadSz); + offset += payloadSz; + } + + return (int)offset; +} + +int wolfSPDM_ParseVendorDefined( + const byte* inBuf, word32 inBufSz, + char* vdCode, + byte* payload, word32* payloadSz) +{ + word32 offset = 0; + word16 reqLength; + word32 dataLen; + byte vendorIdLen; + + if (inBuf == NULL || vdCode == NULL || payload == NULL || + payloadSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Minimum: version(1) + code(1) + param1(1) + param2(1) + stdId(2/LE) + + * vidLen(1) + reqLen(2/LE) + vdCode(8) = 17 */ + if (inBufSz < 17) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Skip SPDM version */ + offset += 1; + /* Skip request/response code + params */ + offset += 3; + /* Skip standard ID (2 bytes LE) */ + offset += 2; + /* Vendor ID length and vendor ID data */ + vendorIdLen = inBuf[offset]; + offset += 1 + vendorIdLen; + + if (offset + 2 > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Request/Response Length (2 bytes LE per Nuvoton spec) */ + reqLength = SPDM_Get16LE(inBuf + offset); + offset += 2; + + if (reqLength < WOLFSPDM_VDCODE_LEN) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + if (offset + reqLength > inBufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* VdCode */ + XMEMCPY(vdCode, inBuf + offset, WOLFSPDM_VDCODE_LEN); + vdCode[WOLFSPDM_VDCODE_LEN] = '\0'; /* Null-terminate */ + offset += WOLFSPDM_VDCODE_LEN; + + /* Payload */ + dataLen = reqLength - WOLFSPDM_VDCODE_LEN; + if (*payloadSz < dataLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + if (dataLen > 0) { + XMEMCPY(payload, inBuf + offset, dataLen); + } + *payloadSz = dataLen; + + return (int)dataLen; +} + +/* ----- Nuvoton-Specific SPDM Functions ----- */ + +int wolfSPDM_Nuvoton_GetPubKey( + WOLFSPDM_CTX* ctx, + byte* pubKey, word32* pubKeySz) +{ + WOLFSPDM_VENDOR_RSP rsp; + int rc; + + if (ctx == NULL || pubKey == NULL || pubKeySz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + wolfSPDM_DebugPrint(ctx, "Nuvoton: GET_PUBK\n"); + + rc = wolfSPDM_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_PUBK, + NULL, 0, &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + /* Verify VdCode */ + if (XMEMCMP(rsp.vdCode, WOLFSPDM_VDCODE_GET_PUBK, WOLFSPDM_VDCODE_LEN) != 0) { + wolfSPDM_DebugPrint(ctx, "GET_PUBK: Unexpected VdCode '%.8s'\n", rsp.vdCode); + return WOLFSPDM_E_PEER_ERROR; + } + + wolfSPDM_DebugPrint(ctx, "GET_PUBK: Got TPMT_PUBLIC (%u bytes)\n", rsp.payloadSz); + + /* Copy public key to output */ + if (*pubKeySz < rsp.payloadSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(pubKey, rsp.payload, rsp.payloadSz); + *pubKeySz = rsp.payloadSz; + + /* Store for KEY_EXCHANGE cert_chain_buffer_hash computation. + * Per Nuvoton SPDM Guidance: cert_chain_buffer_hash = SHA-384(TPMT_PUBLIC) */ + if (rsp.payloadSz <= sizeof(ctx->rspPubKey)) { + XMEMCPY(ctx->rspPubKey, rsp.payload, rsp.payloadSz); + ctx->rspPubKeyLen = rsp.payloadSz; + ctx->flags.hasRspPubKey = 1; + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nuvoton_GivePubKey( + WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz) +{ + int rc; + + if (ctx == NULL || pubKey == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state < WOLFSPDM_STATE_KEY_EX) { + return WOLFSPDM_E_BAD_STATE; + } + + wolfSPDM_DebugPrint(ctx, "Nuvoton: GIVE_PUB (%u bytes) - sending ENCRYPTED\n", pubKeySz); + + rc = wolfSPDM_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_GIVE_PUB, + pubKey, pubKeySz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GIVE_PUB: SecuredExchange failed %d\n", rc); + return rc; + } + + wolfSPDM_DebugPrint(ctx, "GIVE_PUB: Success\n"); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nuvoton_GetStatus( + WOLFSPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status) +{ + WOLFSPDM_VENDOR_RSP rsp; + byte statusType[4] = {0x00, 0x00, 0x00, 0x00}; /* All */ + int rc; + + if (ctx == NULL || status == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMSET(status, 0, sizeof(*status)); + + wolfSPDM_DebugPrint(ctx, "Nuvoton: GET_STS_\n"); + + rc = wolfSPDM_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_STS, + statusType, sizeof(statusType), &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "GET_STS_: VdCode='%.8s', %u bytes\n", + rsp.vdCode, rsp.payloadSz); + + /* Parse status fields per Nuvoton spec page 9: + * Byte 0: SpecVersionMajor (0 for SPDM 1.x) + * Byte 1: SpecVersionMinor (1 = SPDM 1.1, 3 = SPDM 1.3) + * Byte 2: Reserved + * Byte 3: SPDMOnly lock state (0 = unlocked, 1 = locked) */ + if (rsp.payloadSz >= 4) { + byte specMajor = rsp.payload[0]; + byte specMinor = rsp.payload[1]; + byte spdmOnly = rsp.payload[3]; + + status->specVersionMajor = specMajor; + status->specVersionMinor = specMinor; + status->spdmOnlyLocked = (spdmOnly != 0); + status->spdmEnabled = 1; /* If GET_STS works, SPDM is enabled */ + + /* Session active can't be determined from GET_STS alone - + * if we're getting a response, SPDM is working */ + status->sessionActive = 0; + + wolfSPDM_DebugPrint(ctx, "GET_STS_: SpecVersion=%u.%u, SPDMOnly=%s\n", + specMajor, specMinor, spdmOnly ? "LOCKED" : "unlocked"); + } else if (rsp.payloadSz >= 1) { + /* Minimal response - just SPDMOnly */ + status->spdmOnlyLocked = (rsp.payload[0] != 0); + status->spdmEnabled = 1; + wolfSPDM_DebugPrint(ctx, "GET_STS_: SPDMOnly=%s (minimal response)\n", + status->spdmOnlyLocked ? "LOCKED" : "unlocked"); + } + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nuvoton_SetOnlyMode( + WOLFSPDM_CTX* ctx, + int lock) +{ + byte param[1]; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + param[0] = lock ? WOLFSPDM_SPDMONLY_LOCK : WOLFSPDM_SPDMONLY_UNLOCK; + + wolfSPDM_DebugPrint(ctx, "Nuvoton: SPDMONLY %s\n", + lock ? "LOCK" : "UNLOCK"); + + rc = wolfSPDM_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_SPDMONLY, + param, sizeof(param)); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "SPDMONLY: Success\n"); + return WOLFSPDM_SUCCESS; +} + +/* ----- Nuvoton SPDM Connection Flow ----- */ + +/* Nuvoton connection: GET_VERSION -> GET_PUBK -> KEY_EXCHANGE -> GIVE_PUB -> FINISH */ +int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx) +{ + int rc; + byte pubKey[256]; + word32 pubKeySz; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugPrint(ctx, "Nuvoton: Starting SPDM connection\n"); + + /* Reset state for new connection */ + ctx->state = WOLFSPDM_STATE_INIT; + wolfSPDM_TranscriptReset(ctx); + + /* Step 1: GET_VERSION / VERSION */ + SPDM_CONNECT_STEP(ctx, "Nuvoton Step 1: GET_VERSION\n", + wolfSPDM_GetVersion(ctx)); + + /* Step 2: GET_PUBK (Nuvoton vendor command) + * Gets the TPM's SPDM-Identity public key (TPMT_PUBLIC format) */ + wolfSPDM_DebugPrint(ctx, "Nuvoton Step 2: GET_PUBK\n"); + pubKeySz = sizeof(pubKey); + rc = wolfSPDM_Nuvoton_GetPubKey(ctx, pubKey, &pubKeySz); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GET_PUBK failed: %d\n", rc); + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + ctx->state = WOLFSPDM_STATE_CERT; + + /* Step 2.5: Compute Ct = SHA-384(TPMT_PUBLIC) and add to transcript + * For Nuvoton, the cert_chain_buffer_hash is SHA-384(TPMT_PUBLIC) + * instead of the standard certificate chain hash */ + if (ctx->flags.hasRspPubKey && ctx->rspPubKeyLen > 0) { + wolfSPDM_DebugPrint(ctx, "Nuvoton: Computing Ct = SHA-384(TPMT_PUBLIC[%u])\n", + ctx->rspPubKeyLen); + rc = wolfSPDM_Sha384Hash(ctx->certChainHash, + ctx->rspPubKey, ctx->rspPubKeyLen, NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, WOLFSPDM_HASH_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } else { + wolfSPDM_DebugPrint(ctx, "Nuvoton: Warning - no responder public key for Ct\n"); + } + + SPDM_CONNECT_STEP(ctx, "Nuvoton Step 3: KEY_EXCHANGE\n", + wolfSPDM_KeyExchange(ctx)); + + /* Step 4: GIVE_PUB (Nuvoton vendor command) - sent as SECURED message + * Gives the host's SPDM-Identity public key to the TPM. + * Per Nuvoton spec Rev 1.11 section 4.2.4, GIVE_PUB uses tag 0x8201 (secured). */ + if (ctx->flags.hasReqKeyPair && ctx->reqPubKeyTPMTLen > 0) { + wolfSPDM_DebugPrint(ctx, "Nuvoton Step 4: GIVE_PUB\n"); + rc = wolfSPDM_Nuvoton_GivePubKey(ctx, ctx->reqPubKeyTPMT, + ctx->reqPubKeyTPMTLen); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "GIVE_PUB failed: %d\n", rc); + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + wolfSPDM_DebugPrint(ctx, "GIVE_PUB succeeded!\n"); + } else { + wolfSPDM_DebugPrint(ctx, "Nuvoton Step 4: GIVE_PUB (skipped, no host key)\n"); + } + + /* Step 5: FINISH */ + SPDM_CONNECT_STEP(ctx, "Nuvoton Step 5: FINISH\n", + wolfSPDM_Finish(ctx)); + + ctx->state = WOLFSPDM_STATE_CONNECTED; + wolfSPDM_DebugPrint(ctx, "Nuvoton: SPDM Session Established! " + "SessionID=0x%08x\n", ctx->sessionId); + + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON */ diff --git a/spdm/src/spdm_secured.c b/spdm/src/spdm_secured.c new file mode 100644 index 00000000..7fffdf51 --- /dev/null +++ b/spdm/src/spdm_secured.c @@ -0,0 +1,343 @@ +/* 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; + } + +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + /* Nuvoton TCG binding format per Rev 1.11 spec page 25: + * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes + * IV XOR: Leftmost 8 bytes (bytes 0-7) with 8-byte LE sequence number + */ + word16 appDataLen = (word16)plainSz; + + word16 unpadded = (word16)(2 + appDataLen); /* AppDataLength + SPDM msg */ + word16 padLen = (word16)((16 - (unpadded % 16)) % 16); /* Pad to 16-byte boundary */ + word16 encPayloadSz = (word16)(unpadded + padLen); + + plainBufSz = encPayloadSz; + /* Length field = ciphertext + MAC (per Nuvoton spec page 25: Length=160=144+16) */ + recordLen = (word16)(encPayloadSz + WOLFSPDM_AEAD_TAG_SIZE); + hdrSz = 14; /* 4 + 8 + 2 (TCG binding format) */ + + if (*encSz < hdrSz + plainBufSz + WOLFSPDM_AEAD_TAG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Build plaintext: AppDataLength(2 LE) || SPDM message || RandomData */ + SPDM_Set16LE(plainBuf, appDataLen); + XMEMCPY(&plainBuf[2], plain, plainSz); + /* Fill RandomData with actual random bytes per Nuvoton spec */ + if (padLen > 0) { + 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 ----- */ +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + 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); +#ifdef WOLFSPDM_NUVOTON + if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { + /* Nuvoton: AppDataLen(2) || SPDM msg || RandomData */ + if (cipherLen < (word32)(2 + appDataLen) || + *plainSz < appDataLen) { + ret = WOLFSPDM_E_BUFFER_SMALL; + } else { + XMEMCPY(plain, &decrypted[2], appDataLen); + *plainSz = appDataLen; + ret = WOLFSPDM_SUCCESS; + } + } else +#endif + { + /* MCTP: AppDataLen(2) || MCTP(1) || SPDM msg */ + if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen) || + *plainSz < (word32)(appDataLen - 1)) { + ret = WOLFSPDM_E_BUFFER_SMALL; + } else { + XMEMCPY(plain, &decrypted[3], appDataLen - 1); + *plainSz = appDataLen - 1; + ret = WOLFSPDM_SUCCESS; + } + } + } + + if (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/spdm/src/spdm_session.c b/spdm/src/spdm_session.c new file mode 100644 index 00000000..9ed1190e --- /dev/null +++ b/spdm/src/spdm_session.c @@ -0,0 +1,146 @@ +/* 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[192]; /* KEY_EXCHANGE: ~158 bytes */ + byte rxBuf[384]; /* KEY_EXCHANGE_RSP: ~302 bytes */ + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + int rc; + + rc = wolfSPDM_BuildKeyExchange(ctx, txBuf, &txSz); + if (rc == WOLFSPDM_SUCCESS) { + 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[152]; /* 148 bytes max for mutual auth FINISH */ + byte encBuf[256]; /* Encrypted: hdr(14) + padded(160) + tag(16) = 190 max */ + byte rxBuf[128]; /* Encrypted FINISH_RSP: ~94 bytes max */ + byte decBuf[64]; /* Decrypted FINISH_RSP: 4 hdr + 48 verify = 52 */ + word32 finishSz = sizeof(finishBuf); + word32 encSz = sizeof(encBuf); + word32 rxSz = sizeof(rxBuf); + word32 decSz = sizeof(decBuf); + int rc; + + rc = wolfSPDM_BuildFinish(ctx, finishBuf, &finishSz); + + /* 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) { + if (rxBuf[1] == 0x7F) { + byte errCode = (rxSz >= 3) ? rxBuf[2] : 0xFF; + wolfSPDM_DebugPrint(ctx, "FINISH: SPDM ERROR 0x%02x\n", errCode); + } + 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/spdm/src/spdm_transcript.c b/spdm/src/spdm_transcript.c new file mode 100644 index 00000000..02db21f7 --- /dev/null +++ b/spdm/src/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/spdm/test/unit_test.c b/spdm/test/unit_test.c new file mode 100644 index 00000000..ce06f065 --- /dev/null +++ b/spdm/test/unit_test.c @@ -0,0 +1,846 @@ +/* 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 "../src/spdm_internal.h" +#include +#include +#include + +static int g_testsPassed = 0; +static int g_testsFailed = 0; + +#define TEST_ASSERT(cond, msg) do { \ + if (!(cond)) { \ + printf(" FAIL: %s (line %d)\n", msg, __LINE__); \ + g_testsFailed++; \ + return -1; \ + } \ +} while(0) + +#define TEST_PASS() do { \ + g_testsPassed++; \ + return 0; \ +} while(0) + +#define ASSERT_SUCCESS(expr) do { int _r = (expr); if (_r != 0) { \ + printf(" FAIL %s:%d: %s returned %d\n", __FILE__, __LINE__, #expr, _r); \ + g_testsFailed++; return -1; } } while(0) + +#define ASSERT_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(); +} + +#ifdef WOLFSPDM_NUVOTON +/* I/O callback that returns a TCG response with msgSize < TCG_HEADER_SIZE */ +static int tcg_underflow_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, void* userCtx) +{ + (void)ctx; (void)txBuf; (void)txSz; (void)userCtx; + /* Return a 20-byte TCG response with msgSize field = 5 (< 16) */ + if (*rxSz < 20) return -1; + memset(rxBuf, 0, 20); + SPDM_Set16BE(rxBuf, 0x8101); /* tag: clear SPDM */ + SPDM_Set32BE(rxBuf + 2, 5); /* msgSize = 5 (underflow!) */ + *rxSz = 20; + return 0; +} + +static int test_tcg_underflow(void) +{ + byte txBuf[32]; + byte rxBuf[32]; + word32 rxSz = sizeof(rxBuf); + int rc; + TEST_CTX_SETUP(); + + printf("test_tcg_underflow...\n"); + + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NUVOTON); + wolfSPDM_SetIO(ctx, tcg_underflow_io_cb, NULL); + + txBuf[0] = 0x10; + txBuf[1] = SPDM_GET_VERSION; + txBuf[2] = 0x00; + txBuf[3] = 0x00; + + rc = wolfSPDM_SendReceive(ctx, txBuf, 4, rxBuf, &rxSz); + ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, "msgSize < 16 must return BUFFER_SMALL"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* WOLFSPDM_NUVOTON */ + +static int test_decrypt_overflow(void) +{ + /* Static to avoid 4KB+ on stack; cipherLen must exceed + * sizeof(decrypted) = WOLFSPDM_MAX_MSG_SIZE + 16 = 4112 */ + static byte enc[4140]; + byte plain[64]; + word32 plainSz = sizeof(plain); + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_decrypt_overflow...\n"); + + ctx->sessionId = 0x00010001; + ctx->rspSeqNum = 0; + memset(ctx->rspDataKey, 0x42, sizeof(ctx->rspDataKey)); + memset(ctx->rspDataIv, 0x42, sizeof(ctx->rspDataIv)); + + /* MCTP header: rspLen=4130 -> cipherLen=4114 > 4112 = overflow guard */ + memset(enc, 0, sizeof(enc)); + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set16LE(&enc[4], 0x0000); + SPDM_Set16LE(&enc[6], 4130); + + rc = wolfSPDM_DecryptInternal(ctx, enc, 4138, plain, &plainSz); + ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, "Overflow cipherLen must be caught"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_oob_read_error(void) +{ + byte shortErr[2] = {0x12, SPDM_ERROR}; + byte fullErr[4] = {0x12, SPDM_ERROR, 0x06, 0x00}; + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_oob_read_error...\n"); + + rc = wolfSPDM_ParseFinishRsp(ctx, fullErr, sizeof(fullErr)); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, "Should return peer error"); + + rc = wolfSPDM_ParseFinishRsp(ctx, shortErr, sizeof(shortErr)); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "Short buffer should fail"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_constant_time_hmac(void) +{ + byte finishedKey[WOLFSPDM_HASH_SIZE]; + byte thHash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + byte fakeVerify[WOLFSPDM_HASH_SIZE]; + word32 i; + int diff; + + printf("test_constant_time_hmac...\n"); + + memset(finishedKey, 0xAB, sizeof(finishedKey)); + memset(thHash, 0xCD, sizeof(thHash)); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKey, thHash, verifyData)); + + /* 1-byte difference must be detected */ + memcpy(fakeVerify, verifyData, sizeof(fakeVerify)); + fakeVerify[WOLFSPDM_HASH_SIZE - 1] ^= 0x01; + + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ fakeVerify[i]; + ASSERT_NE(diff, 0, "Should detect 1-byte diff"); + + /* Identical must pass */ + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ verifyData[i]; + ASSERT_EQ(diff, 0, "Identical data should match"); + + TEST_PASS(); +} + +static int test_setdebug_truncation(void) +{ + TEST_CTX_SETUP(); + + printf("test_setdebug_truncation...\n"); + + wolfSPDM_SetDebug(ctx, 2); + ASSERT_EQ(ctx->flags.debug, 1, "debug=2 should be 1"); + + wolfSPDM_SetDebug(ctx, 0); + ASSERT_EQ(ctx->flags.debug, 0, "debug=0 should be 0"); + + wolfSPDM_SetDebug(ctx, 255); + ASSERT_EQ(ctx->flags.debug, 1, "debug=255 should be 1"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_key_zeroing(void) +{ + byte zeros[WOLFSPDM_HASH_SIZE]; + byte zeroKey[WOLFSPDM_AEAD_KEY_SIZE]; + byte zeroIv[WOLFSPDM_AEAD_IV_SIZE]; + TEST_CTX_SETUP_V12(); + + printf("test_key_zeroing...\n"); + + memset(zeros, 0, sizeof(zeros)); + memset(zeroKey, 0, sizeof(zeroKey)); + memset(zeroIv, 0, sizeof(zeroIv)); + + /* Fill key material with non-zero data */ + memset(ctx->reqDataKey, 0xAA, sizeof(ctx->reqDataKey)); + memset(ctx->rspDataKey, 0xBB, sizeof(ctx->rspDataKey)); + memset(ctx->reqDataIv, 0xCC, sizeof(ctx->reqDataIv)); + memset(ctx->rspDataIv, 0xDD, sizeof(ctx->rspDataIv)); + memset(ctx->reqHsSecret, 0x11, sizeof(ctx->reqHsSecret)); + memset(ctx->rspHsSecret, 0x22, sizeof(ctx->rspHsSecret)); + memset(ctx->reqFinishedKey, 0x33, sizeof(ctx->reqFinishedKey)); + memset(ctx->rspFinishedKey, 0x44, sizeof(ctx->rspFinishedKey)); + memset(ctx->handshakeSecret, 0x55, sizeof(ctx->handshakeSecret)); + memset(ctx->sharedSecret, 0x66, sizeof(ctx->sharedSecret)); + memset(ctx->th1, 0x77, sizeof(ctx->th1)); + memset(ctx->th2, 0x88, sizeof(ctx->th2)); + ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; + + ctx->state = WOLFSPDM_STATE_CONNECTED; + ctx->sessionId = 0x00010001; + ctx->ioCb = dummy_io_cb; + + wolfSPDM_Disconnect(ctx); + + ASSERT_EQ(memcmp(ctx->reqDataKey, zeroKey, sizeof(ctx->reqDataKey)), 0, + "reqDataKey not zeroed"); + ASSERT_EQ(memcmp(ctx->rspDataKey, zeroKey, sizeof(ctx->rspDataKey)), 0, + "rspDataKey not zeroed"); + ASSERT_EQ(memcmp(ctx->reqDataIv, zeroIv, sizeof(ctx->reqDataIv)), 0, + "reqDataIv not zeroed"); + ASSERT_EQ(memcmp(ctx->rspDataIv, zeroIv, sizeof(ctx->rspDataIv)), 0, + "rspDataIv not zeroed"); + ASSERT_EQ(memcmp(ctx->reqHsSecret, zeros, sizeof(ctx->reqHsSecret)), 0, + "reqHsSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->rspHsSecret, zeros, sizeof(ctx->rspHsSecret)), 0, + "rspHsSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->reqFinishedKey, zeros, sizeof(ctx->reqFinishedKey)), 0, + "reqFinishedKey not zeroed"); + ASSERT_EQ(memcmp(ctx->rspFinishedKey, zeros, sizeof(ctx->rspFinishedKey)), 0, + "rspFinishedKey not zeroed"); + ASSERT_EQ(memcmp(ctx->handshakeSecret, zeros, sizeof(ctx->handshakeSecret)), 0, + "handshakeSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->sharedSecret, zeros, sizeof(ctx->sharedSecret)), 0, + "sharedSecret not zeroed"); + ASSERT_EQ(ctx->sharedSecretSz, 0, "sharedSecretSz not zeroed"); + ASSERT_EQ(memcmp(ctx->th1, zeros, sizeof(ctx->th1)), 0, + "th1 not zeroed"); + ASSERT_EQ(memcmp(ctx->th2, zeros, sizeof(ctx->th2)), 0, + "th2 not zeroed"); + + wolfSPDM_Init(ctx); + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ----- Main ----- */ + +int main(void) +{ + printf("===========================================\n"); + printf("wolfSPDM Unit Tests (Nuvoton subset)\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(); +#ifdef WOLFSPDM_NUVOTON + test_tcg_underflow(); +#endif + test_decrypt_overflow(); + test_oob_read_error(); + test_constant_time_hmac(); + test_setdebug_truncation(); + test_key_zeroing(); + + printf("\n===========================================\n"); + printf("Results: %d passed, %d failed\n", g_testsPassed, g_testsFailed); + printf("===========================================\n"); + + return (g_testsFailed == 0) ? 0 : 1; +} diff --git a/spdm/wolfspdm/spdm.h b/spdm/wolfspdm/spdm.h new file mode 100644 index 00000000..85d9c135 --- /dev/null +++ b/spdm/wolfspdm/spdm.h @@ -0,0 +1,134 @@ +/* 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: Nuvoton TCG binding + vendor commands (requires --enable-nuvoton). + * For standard SPDM (emulator, measurements, challenge), see wolfSPDM standalone. */ +typedef enum { + WOLFSPDM_MODE_NUVOTON = 1, +} 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; + +#ifdef WOLFSPDM_NUVOTON + #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); +#ifdef WOLFSPDM_NUVOTON +WOLFSPDM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); +WOLFSPDM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); +#endif + +/* Debug */ +WOLFSPDM_API void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_SPDM_H */ diff --git a/spdm/wolfspdm/spdm_error.h b/spdm/wolfspdm/spdm_error.h new file mode 100644 index 00000000..71ae5354 --- /dev/null +++ b/spdm/wolfspdm/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/spdm/wolfspdm/spdm_nuvoton.h b/spdm/wolfspdm/spdm_nuvoton.h new file mode 100644 index 00000000..b2c37af7 --- /dev/null +++ b/spdm/wolfspdm/spdm_nuvoton.h @@ -0,0 +1,265 @@ +/* spdm_nuvoton.h + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Nuvoton TPM SPDM Support + * + * This header provides Nuvoton-specific SPDM functionality: + * - TCG SPDM Binding message framing (per TCG SPDM Binding Spec v1.0) + * - Nuvoton vendor-defined commands (GET_PUBK, GIVE_PUB, GET_STS_, SPDMONLY) + * - Nuvoton SPDM handshake flow (differs from standard SPDM) + * + * The Nuvoton NPCT75x TPM uses a simplified SPDM flow: + * GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH + * + * Notable differences from standard SPDM: + * - No GET_CAPABILITIES or NEGOTIATE_ALGORITHMS (Algorithm Set B is fixed) + * - Uses vendor-defined commands for identity key exchange + * - TCG binding headers wrap all SPDM messages + * + * Reference: Nuvoton SPDM Guidance Rev 1.11 + */ + +#ifndef WOLFSPDM_NUVOTON_H +#define WOLFSPDM_NUVOTON_H + +/* Note: This header is included from spdm.h after WOLFSPDM_CTX forward declaration. + * DO NOT include spdm.h here to avoid circular dependency. + * Include spdm_types.h for basic types only. */ +#include + +#ifdef WOLFSPDM_NUVOTON + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) ----- */ + +/* Message Tags */ +#define WOLFSPDM_TCG_TAG_CLEAR 0x8101 /* Clear (unencrypted) message */ +#define WOLFSPDM_TCG_TAG_SECURED 0x8201 /* Secured (encrypted) message */ + +/* Header Sizes */ +#define WOLFSPDM_TCG_HEADER_SIZE 16 /* TCG binding header size */ + +/* FIPS Service Indicator */ +#define WOLFSPDM_FIPS_NON_FIPS 0x00 +#define WOLFSPDM_FIPS_APPROVED 0x01 + +/* ----- Nuvoton Vendor-Defined Command Codes ----- */ + +/* 8-byte ASCII vendor codes for SPDM VENDOR_DEFINED messages */ +#define WOLFSPDM_VDCODE_LEN 8 + +#define WOLFSPDM_VDCODE_TPM2_CMD "TPM2_CMD" /* TPM command over SPDM */ +#define WOLFSPDM_VDCODE_GET_PUBK "GET_PUBK" /* Get TPM's SPDM-Identity key */ +#define WOLFSPDM_VDCODE_GIVE_PUB "GIVE_PUB" /* Give host's SPDM-Identity key */ +#define WOLFSPDM_VDCODE_GET_STS "GET_STS_" /* Get SPDM status */ +#define WOLFSPDM_VDCODE_SPDMONLY "SPDMONLY" /* Lock/unlock SPDM-only mode */ + +/* SPDMONLY command parameters */ +#define WOLFSPDM_SPDMONLY_LOCK 0x01 +#define WOLFSPDM_SPDMONLY_UNLOCK 0x00 + +/* ----- TCG Binding Header Structures ----- */ + +/* Clear message header (tag 0x8101) + * Layout: tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + + * fipsIndicator(2/BE) + reserved(4) = 16 bytes */ +typedef struct WOLFSPDM_TCG_CLEAR_HDR { + word16 tag; /* WOLFSPDM_TCG_TAG_CLEAR (0x8101) */ + word32 size; /* Total message size including header */ + word32 connectionHandle; /* Connection handle (0 for single connection) */ + word16 fipsIndicator; /* FIPS service indicator */ + word32 reserved; /* Must be 0 */ +} WOLFSPDM_TCG_CLEAR_HDR; + +/* ----- Nuvoton SPDM Status ----- */ + +typedef struct WOLFSPDM_NUVOTON_STATUS { + int spdmEnabled; /* SPDM is enabled on the TPM */ + int sessionActive; /* An SPDM session is currently active */ + int spdmOnlyLocked; /* SPDM-only mode is locked */ + byte specVersionMajor; /* SPDM spec version major (0 for 1.x) */ + byte specVersionMinor; /* SPDM spec version minor (1=1.1, 3=1.3) */ +} WOLFSPDM_NUVOTON_STATUS; + +/* ----- TCG Binding Message Framing Functions ----- */ + +/** + * Build a TCG SPDM clear message (tag 0x8101). + * Wraps an SPDM payload in the TCG binding header format. + * + * @param ctx wolfSPDM context + * @param spdmPayload SPDM message payload + * @param spdmPayloadSz Size of SPDM payload + * @param outBuf Output buffer for framed message + * @param outBufSz Size of output buffer + * @return Total message size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_BuildTcgClearMessage( + WOLFSPDM_CTX* ctx, + const byte* spdmPayload, word32 spdmPayloadSz, + byte* outBuf, word32 outBufSz); + +/** + * Parse a TCG SPDM clear message (tag 0x8101). + * Extracts the SPDM payload from the TCG binding header. + * + * @param inBuf Input buffer containing framed message + * @param inBufSz Size of input buffer + * @param spdmPayload Output buffer for SPDM payload + * @param spdmPayloadSz [in] Size of output buffer, [out] Actual payload size + * @param hdr Optional: receives parsed header fields + * @return Payload size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_ParseTcgClearMessage( + const byte* inBuf, word32 inBufSz, + byte* spdmPayload, word32* spdmPayloadSz, + WOLFSPDM_TCG_CLEAR_HDR* hdr); + +/* ----- Vendor-Defined Message Helpers ----- */ + +/** + * Build an SPDM VENDOR_DEFINED_REQUEST message. + * + * @param spdmVersion Negotiated SPDM version byte (e.g., 0x13) + * @param vdCode 8-byte ASCII vendor code (e.g., "GET_PUBK") + * @param payload Vendor-specific payload (may be NULL) + * @param payloadSz Size of payload + * @param outBuf Output buffer for message + * @param outBufSz Size of output buffer + * @return Message size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_BuildVendorDefined( + byte spdmVersion, + const char* vdCode, + const byte* payload, word32 payloadSz, + byte* outBuf, word32 outBufSz); + +/** + * Parse an SPDM VENDOR_DEFINED_RESPONSE message. + * + * @param inBuf Input buffer containing message + * @param inBufSz Size of input buffer + * @param vdCode Receives 8-byte vendor code (buffer must be 9+ bytes) + * @param payload Output buffer for payload + * @param payloadSz [in] Size of output buffer, [out] Actual payload size + * @return Payload size on success, negative on error + */ +WOLFSPDM_API int wolfSPDM_ParseVendorDefined( + const byte* inBuf, word32 inBufSz, + char* vdCode, + byte* payload, word32* payloadSz); + +/* ----- Nuvoton-Specific SPDM Functions ----- */ + +/** + * Get the TPM's SPDM-Identity public key (GET_PUBK vendor command). + * This is sent as a clear (unencrypted) SPDM message before key exchange. + * + * @param ctx wolfSPDM context + * @param pubKey Output buffer for public key (raw X||Y, 96 bytes for P-384) + * @param pubKeySz [in] Size of buffer, [out] Actual key size + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_Nuvoton_GetPubKey( + WOLFSPDM_CTX* ctx, + byte* pubKey, word32* pubKeySz); + +/** + * Give the host's SPDM-Identity public key to the TPM (GIVE_PUB vendor command). + * This is sent as a secured (encrypted) message after key exchange. + * + * @param ctx wolfSPDM context + * @param pubKey Host's public key (TPMT_PUBLIC format, ~120 bytes) + * @param pubKeySz Size of public key + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_Nuvoton_GivePubKey( + WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz); + +/** + * Get SPDM status from the TPM (GET_STS_ vendor command). + * Can be called before or after session establishment. + * + * @param ctx wolfSPDM context + * @param status Receives SPDM status information + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_Nuvoton_GetStatus( + WOLFSPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status); + +/** + * Lock or unlock SPDM-only mode (SPDMONLY vendor command). + * When locked, the TPM only accepts commands over SPDM. + * + * @param ctx wolfSPDM context + * @param lock WOLFSPDM_SPDMONLY_LOCK (1) or WOLFSPDM_SPDMONLY_UNLOCK (0) + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_Nuvoton_SetOnlyMode( + WOLFSPDM_CTX* ctx, + int lock); + +/** + * Set the requester's SPDM-Identity public key in TPMT_PUBLIC format. + * Required for GIVE_PUB step in Nuvoton handshake. + * + * @param ctx wolfSPDM context + * @param tpmtPub Public key in TPMT_PUBLIC format (~120 bytes for P-384) + * @param tpmtPubSz Size of TPMT_PUBLIC + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz); + +/** + * Perform Nuvoton-specific SPDM connection. + * Uses the Nuvoton handshake flow: + * GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH + * + * This is called internally by wolfSPDM_Connect() when mode is NUVOTON. + * + * @param ctx wolfSPDM context + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_ConnectNuvoton(WOLFSPDM_CTX* ctx); + +/* ----- Nuvoton Context Fields ----- */ + +/* These fields are added to WOLFSPDM_CTX when WOLFSPDM_NUVOTON is defined */ + +/* Connection handle for TCG binding (usually 0) */ +#define WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT 0 + +/* FIPS indicator for TCG binding */ +#define WOLFSPDM_NUVOTON_FIPS_DEFAULT WOLFSPDM_FIPS_NON_FIPS + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFSPDM_NUVOTON_H */ diff --git a/spdm/wolfspdm/spdm_types.h b/spdm/wolfspdm/spdm_types.h new file mode 100644 index 00000000..c8058d40 --- /dev/null +++ b/spdm/wolfspdm/spdm_types.h @@ -0,0 +1,129 @@ +/* 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_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" +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_TYPES_H */ diff --git a/src/include.am b/src/include.am index 4714939b..a1f25039 100644 --- a/src/include.am +++ b/src/include.am @@ -20,6 +20,10 @@ if BUILD_WINAPI src_libwolftpm_la_SOURCES += src/tpm2_winapi.c src_libwolftpm_la_LIBADD = -ltbs endif +if BUILD_SPDM +# SPDM support using wolfSPDM library +src_libwolftpm_la_SOURCES += src/tpm2_spdm.c +endif src_libwolftpm_la_CFLAGS = $(src_libwolftpm_la_EXTRAS) -DBUILDING_WOLFTPM $(AM_CFLAGS) src_libwolftpm_la_CPPFLAGS = -DBUILDING_WOLFTPM $(AM_CPPFLAGS) diff --git a/src/tpm2.c b/src/tpm2.c index dbd3293c..347cbf09 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -30,6 +30,9 @@ #include #include #include +#ifdef WOLFTPM_SPDM +#include +#endif #include @@ -407,6 +410,38 @@ static int TPM2_ResponseProcess(TPM2_CTX* ctx, TPM2_Packet* packet, return rc; } +#ifdef WOLFTPM_SPDM +/* SPDM intercept: if SPDM session is active, send TPM command through + * the encrypted SPDM channel instead of raw SPI/I2C. + * Returns TPM_RC_SUCCESS on success, negative if SPDM not active + * (caller should use normal transport), or positive error code. */ +static TPM_RC TPM2_SPDM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) +{ + WOLFTPM2_SPDM_CTX* spdmCtx; + byte tpmResp[WOLFSPDM_MAX_MSG_SIZE]; + word32 tpmRespSz = sizeof(tpmResp); + TPM_RC rc; + + if (ctx->spdmCtx == NULL) + return -1; /* SPDM not configured */ + spdmCtx = (WOLFTPM2_SPDM_CTX*)ctx->spdmCtx; + if (spdmCtx->spdmCtx == NULL || !wolfSPDM_IsConnected(spdmCtx->spdmCtx)) + return -1; /* SPDM not connected */ + + rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, + packet->buf, packet->pos, tpmResp, &tpmRespSz); + if (rc != 0) + return rc; + + if (tpmRespSz > MAX_RESPONSE_SIZE) + return TPM_RC_SIZE; + XMEMCPY(packet->buf, tpmResp, tpmRespSz); + packet->pos = 0; + packet->size = tpmRespSz; + return TPM_RC_SUCCESS; +} +#endif /* WOLFTPM_SPDM */ + static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet, CmdInfo_t* info) { @@ -450,7 +485,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; @@ -480,6 +521,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) @@ -1580,6 +1628,7 @@ TPM_RC TPM2_StartAuthSession(StartAuthSession_In* in, StartAuthSession_Out* out) return rc; } + TPM_RC TPM2_PolicyRestart(PolicyRestart_In* in) { TPM_RC rc; @@ -5593,6 +5642,7 @@ int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in) TPM2_Packet packet; CmdInfo_t info = {0,0,0,0}; info.inHandleCnt = 1; + info.flags = CMD_FLAG_AUTH_USER1; TPM2_Packet_Init(ctx, &packet); /* Process the auth handle for GPIO configuration */ @@ -5636,6 +5686,68 @@ int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out) } #endif /* WOLFTPM_NUVOTON */ +/* NTC2 PreConfig/GetConfig for runtime vendor detection (WOLFTPM_AUTODETECT). + * Identical to the WOLFTPM_NUVOTON implementations above. */ +#if defined(WOLFTPM_AUTODETECT) && !defined(WOLFTPM_NUVOTON) && \ + !defined(WOLFTPM_ST33) + +int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in) +{ + int rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + + if (in == NULL || ctx == NULL) + return BAD_FUNC_ARG; + + rc = TPM2_AcquireLock(ctx); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet packet; + CmdInfo_t info = {0,0,0,0}; + info.inHandleCnt = 1; + info.flags = CMD_FLAG_AUTH_USER1; + + TPM2_Packet_Init(ctx, &packet); + TPM2_Packet_AppendU32(&packet, in->authHandle); + TPM2_Packet_AppendAuth(&packet, ctx, &info); + TPM2_Packet_AppendBytes(&packet, (byte*)&in->preConfig, + sizeof(in->preConfig)); + TPM2_Packet_Finalize(&packet, TPM_ST_SESSIONS, + TPM_CC_NTC2_PreConfig); + + rc = TPM2_SendCommandAuth(ctx, &packet, &info); + + TPM2_ReleaseLock(ctx); + } + return rc; +} + +int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out) +{ + int rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + + if (out == NULL || ctx == NULL) + return BAD_FUNC_ARG; + + rc = TPM2_AcquireLock(ctx); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet packet; + TPM2_Packet_Init(ctx, &packet); + TPM2_Packet_Finalize(&packet, TPM_ST_NO_SESSIONS, + TPM_CC_NTC2_GetConfig); + + rc = TPM2_SendCommand(ctx, &packet); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet_ParseBytes(&packet, (byte*)&out->preConfig, + sizeof(out->preConfig)); + } + + TPM2_ReleaseLock(ctx); + } + return rc; +} +#endif /* WOLFTPM_AUTODETECT && !WOLFTPM_NUVOTON && !WOLFTPM_ST33 */ + #ifdef WOLFTPM_FIRMWARE_UPGRADE #if defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index e603c005..ade4a81c 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -1045,6 +1045,7 @@ int TPM2_Packet_Finalize(TPM2_Packet* packet, TPM_ST tag, TPM_CC cc) return cmdSz; } + /******************************************************************************/ /* --- END TPM Packet Assembly / Parsing -- */ /******************************************************************************/ diff --git a/src/tpm2_spdm.c b/src/tpm2_spdm.c new file mode 100644 index 00000000..e0d5a919 --- /dev/null +++ b/src/tpm2_spdm.c @@ -0,0 +1,382 @@ +/* tpm2_spdm.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* SPDM Integration Layer for wolfTPM + * + * All SPDM protocol logic, cryptography, and message handling is implemented + * in wolfSPDM (spdm/ subdirectory). This file provides: + * + * 1. Context management (init/free) + * 2. Secured exchange with VENDOR_DEFINED wrapping (Nuvoton) + * 3. TPM-specific NTC2_PreConfig for SPDM enable/disable (Nuvoton only) + * 4. TIS I/O callback for routing wolfSPDM through TPM SPI transport + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifdef WOLFTPM_SPDM + +#include + +/* TIS functions for SPI/I2C TPM transport */ +#if defined(WOLFSPDM_NUVOTON) && \ + !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ + !defined(WOLFTPM_WINAPI) + #include + #include + #define WOLFTPM_SPDM_TIS_IO +#endif + +/* wolfSPDM provides all SPDM protocol implementation */ +#include + +/* -------------------------------------------------------------------------- */ +/* TIS I/O Callback (SPI/I2C TPM transport for SPDM) */ +/* -------------------------------------------------------------------------- */ + +#ifdef WOLFTPM_SPDM_TIS_IO +/* TIS I/O callback for routing wolfSPDM through TPM SPI/I2C FIFO. + * This matches the WOLFSPDM_IO_CB signature. TCG framing (headers) is + * handled by wolfSPDM_SendReceive() in Nuvoton mode, so this callback + * just sends/receives raw bytes through the TIS FIFO. */ +static int wolfTPM2_SPDM_TisIoCb( + WOLFSPDM_CTX* spdmCtx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + void* userCtx) +{ + TPM2_CTX* tpmCtx = (TPM2_CTX*)userCtx; + byte ioBuf[MAX_RESPONSE_SIZE]; + TPM2_Packet packet; + int rc; + UINT32 rspSz; + + (void)spdmCtx; + + if (tpmCtx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) { + return -1; + } + + if (txSz > sizeof(ioBuf)) { + return -1; + } + + /* Set up packet with TX data */ + XMEMCPY(ioBuf, txBuf, txSz); + packet.buf = ioBuf; + packet.pos = (int)txSz; + packet.size = (int)sizeof(ioBuf); + + /* Ensure we have TPM locality */ + rc = TPM2_TIS_RequestLocality(tpmCtx, TPM_TIMEOUT_TRIES); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + + /* Send through TIS FIFO and receive response */ + rc = TPM2_TIS_SendCommand(tpmCtx, &packet); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + + /* Extract response size from header bytes [2..5] (big-endian). + * Both TPM headers and TCG SPDM binding headers store the total + * message size at this offset in the same format. */ + XMEMCPY(&rspSz, &ioBuf[2], sizeof(UINT32)); + rspSz = TPM2_Packet_SwapU32(rspSz); + + if (rspSz > *rxSz || rspSz > sizeof(ioBuf)) { + return -1; + } + + XMEMCPY(rxBuf, ioBuf, rspSz); + *rxSz = rspSz; + + return 0; +} +#endif /* WOLFTPM_SPDM_TIS_IO */ + +/* -------------------------------------------------------------------------- */ +/* Context Management */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_InitCtx( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_IO_CB ioCb, + void* userCtx) +{ + int rc; + + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + + /* Zero initialize context */ + XMEMSET(ctx, 0, sizeof(WOLFTPM2_SPDM_CTX)); + +#ifdef WOLFSPDM_DYNAMIC_MEMORY + /* Dynamic path: allocate and initialize via wolfSPDM_New() */ + ctx->spdmCtx = wolfSPDM_New(); + if (ctx->spdmCtx == NULL) { + return MEMORY_E; + } +#else + /* Static path: use inline buffer, no malloc */ + ctx->spdmCtx = (WOLFSPDM_CTX*)ctx->spdmBuf; + rc = wolfSPDM_InitStatic(ctx->spdmCtx, (int)sizeof(ctx->spdmBuf)); + if (rc != WOLFSPDM_SUCCESS) { + ctx->spdmCtx = NULL; + return rc; + } +#endif + + /* Set I/O callback if provided */ + if (ioCb != NULL) { + rc = wolfSPDM_SetIO(ctx->spdmCtx, ioCb, userCtx); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_Free(ctx->spdmCtx); + ctx->spdmCtx = NULL; + return rc; + } + } + + return TPM_RC_SUCCESS; +} + +int wolfTPM2_SPDM_SetTPMCtx( + WOLFTPM2_SPDM_CTX* ctx, + TPM2_CTX* tpmCtx) +{ + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + ctx->tpmCtx = tpmCtx; + return TPM_RC_SUCCESS; +} + +void wolfTPM2_SPDM_FreeCtx(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL) { + return; + } + + if (ctx->spdmCtx != NULL) { + wolfSPDM_Free(ctx->spdmCtx); + ctx->spdmCtx = NULL; + } + + ctx->tpmCtx = NULL; + ctx->spdmOnlyLocked = 0; +} + +/* -------------------------------------------------------------------------- */ +/* Secured Messaging */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_SecuredExchange( + WOLFTPM2_SPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSPDM_NUVOTON + /* In SPDM-only mode, TPM commands must be wrapped in SPDM VENDOR_DEFINED + * messages with the TPM2_CMD vendor code. The TPM's SPDM layer only + * accepts SPDM messages (starting with version byte 0x13), not raw TPM + * commands (starting with tag 0x80 0x01). */ + if (wolfSPDM_GetMode(ctx->spdmCtx) == WOLFSPDM_MODE_NUVOTON) { + byte vdMsg[WOLFSPDM_MAX_MSG_SIZE]; + byte vdRsp[WOLFSPDM_MAX_MSG_SIZE]; + word32 vdRspSz = sizeof(vdRsp); + char rspVdCode[WOLFSPDM_VDCODE_LEN + 1]; + int vdMsgSz; + int rc; + + /* Wrap TPM command in SPDM VENDOR_DEFINED_REQUEST("TPM2_CMD") */ + { + 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 */ + + /* Standard SPDM mode: send TPM command as raw app data */ + return wolfSPDM_SecuredExchange(ctx->spdmCtx, + cmdPlain, cmdSz, rspPlain, rspSz); +} + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Functions */ +/* -------------------------------------------------------------------------- */ + +#ifdef WOLFSPDM_NUVOTON + +/* Set built-in TIS I/O callback for routing SPDM through TPM SPI/I2C. + * Must be called after wolfTPM2_SPDM_InitCtx() and SetTPMCtx(). */ +int wolfTPM2_SPDM_SetTisIO(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->spdmCtx == NULL || ctx->tpmCtx == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFTPM_SPDM_TIS_IO + return wolfSPDM_SetIO(ctx->spdmCtx, wolfTPM2_SPDM_TisIoCb, ctx->tpmCtx); +#else + (void)ctx; + return NOT_COMPILED_IN; +#endif +} + +/* Enable SPDM on Nuvoton TPM via NTC2_PreConfig vendor command. + * This requires platform hierarchy authorization and a TPM reset. */ +int wolfTPM2_SPDM_Enable(WOLFTPM2_SPDM_CTX* ctx) +{ + int rc; + NTC2_PreConfig_In preConfigIn; + NTC2_GetConfig_Out getConfigOut; + + if (ctx == NULL || ctx->tpmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Get current NTC2 configuration */ + XMEMSET(&getConfigOut, 0, sizeof(getConfigOut)); + rc = TPM2_NTC2_GetConfig(&getConfigOut); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_GetConfig failed: 0x%x\n", rc); + #endif + return rc; + } + + /* Check if SPDM is already enabled (bit 1 of Cfg_H, 0 = enabled) */ + if ((getConfigOut.preConfig.Cfg_H & NTC2_CFG_H_SPDM_DISABLE) == 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM already enabled on TPM\n"); + #endif + return TPM_RC_SUCCESS; + } + + + /* Set SPDM capability bit (clear bit 1 to enable) */ + XMEMSET(&preConfigIn, 0, sizeof(preConfigIn)); + preConfigIn.authHandle = TPM_RH_PLATFORM; + preConfigIn.preConfig = getConfigOut.preConfig; + preConfigIn.preConfig.Cfg_H &= ~NTC2_CFG_H_SPDM_DISABLE; + + rc = TPM2_NTC2_PreConfig(&preConfigIn); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_PreConfig failed: 0x%x\n", rc); + #endif + return rc; + } + +#ifdef DEBUG_WOLFTPM + printf("SPDM enabled. TPM reset required for changes to take effect.\n"); +#endif + + return TPM_RC_SUCCESS; +} + +int wolfTPM2_SPDM_Disable(WOLFTPM2_SPDM_CTX* ctx) +{ + int rc; + NTC2_PreConfig_In preConfigIn; + NTC2_GetConfig_Out getConfigOut; + + if (ctx == NULL || ctx->tpmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Get current NTC2 configuration */ + XMEMSET(&getConfigOut, 0, sizeof(getConfigOut)); + rc = TPM2_NTC2_GetConfig(&getConfigOut); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_GetConfig failed: 0x%x\n", rc); + #endif + return rc; + } + + /* Check if SPDM is already disabled (bit 1 of Cfg_H, 1 = disabled) */ + if ((getConfigOut.preConfig.Cfg_H & NTC2_CFG_H_SPDM_DISABLE) != 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM already disabled on TPM\n"); + #endif + return TPM_RC_SUCCESS; + } + + + /* Set SPDM disable bit (set bit 1 to disable) */ + XMEMSET(&preConfigIn, 0, sizeof(preConfigIn)); + preConfigIn.authHandle = TPM_RH_PLATFORM; + preConfigIn.preConfig = getConfigOut.preConfig; + preConfigIn.preConfig.Cfg_H |= NTC2_CFG_H_SPDM_DISABLE; + + rc = TPM2_NTC2_PreConfig(&preConfigIn); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_PreConfig failed: 0x%x\n", rc); + #endif + return rc; + } + +#ifdef DEBUG_WOLFTPM + printf("SPDM disabled. TPM reset required for changes to take effect.\n"); +#endif + + return TPM_RC_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/tpm2_swtpm.c b/src/tpm2_swtpm.c index 8c8ed122..cb5fcc7d 100644 --- a/src/tpm2_swtpm.c +++ b/src/tpm2_swtpm.c @@ -52,8 +52,11 @@ #include #include #include -#ifdef HAVE_NETDB_H +#ifndef WOLFTPM_ZEPHYR +#include #include +#include +#include #endif #include diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index dc6cfdb1..dc06ce0f 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -25,6 +25,9 @@ #include #include +#ifdef WOLFTPM_SPDM +#include +#endif #ifndef WOLFTPM2_NO_WRAPPER @@ -142,10 +145,26 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, if (rc != TPM_RC_SUCCESS && rc != TPM_RC_INITIALIZE /* TPM_RC_INITIALIZE = Already started */ && rc != TPM_RC_UPGRADE /* TPM_RC_UPGRADE = In firmware upgrade mode */ ) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Startup failed %d: %s\n", rc, wolfTPM2_GetRCString(rc)); - #endif - return rc; + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* When SPDM-only mode is active on the TPM, TPM2_Startup returns + * TPM_RC_DISABLED. This is expected - SPDM commands bypass the + * normal TPM command path and work over raw SPI. */ + ctx->spdmOnlyDetected = 1; + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup: TPM_RC_DISABLED (SPDM-only mode active, " + "this is expected)\n"); + #endif + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup failed %d: %s\n", rc, + wolfTPM2_GetRCString(rc)); + #endif + return rc; + } } /* Return upgrade status so caller can handle appropriately */ if (rc == TPM_RC_UPGRADE) { @@ -155,7 +174,9 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, return rc; } #ifdef DEBUG_WOLFTPM - printf("TPM2_Startup pass\n"); + if (rc == TPM_RC_SUCCESS || rc == TPM_RC_INITIALIZE) { + printf("TPM2_Startup pass\n"); + } #endif rc = TPM_RC_SUCCESS; @@ -165,13 +186,29 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, selfTest.fullTest = YES; rc = TPM2_SelfTest(&selfTest); if (rc != TPM_RC_SUCCESS) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_SelfTest failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - #endif - return rc; + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* SPDM-only mode active - SelfTest not needed */ + #ifdef DEBUG_WOLFTPM + printf("TPM2_SelfTest: TPM_RC_DISABLED (SPDM-only mode, " + "expected)\n"); + #endif + rc = TPM_RC_SUCCESS; + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_SelfTest failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + #endif + return rc; + } } #ifdef DEBUG_WOLFTPM - printf("TPM2_SelfTest pass\n"); + if (rc == TPM_RC_SUCCESS) { + printf("TPM2_SelfTest pass\n"); + } #endif #endif /* WOLFTPM_MICROCHIP || WOLFTPM_PERFORM_SELFTEST */ #endif /* !WOLFTPM_LINUX_DEV && !WOLFTPM_WINAPI */ @@ -227,6 +264,49 @@ int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx) XMEMSET(dev->session, 0, sizeof(dev->session)); wolfTPM2_SetAuthPassword(dev, 0, NULL); +#if defined(WOLFTPM_SPDM) && defined(WOLFSPDM_NUVOTON) + /* If TPM is in SPDM-only mode, transparently establish an SPDM session + * so all subsequent TPM commands are encrypted over the bus. + * This allows existing binaries (caps, wrap_test, unit.test) to work + * without any SPDM-specific code. */ + if (dev->ctx.spdmOnlyDetected) { + Startup_In startupIn; + + rc = wolfTPM2_SpdmInit(dev); + if (rc != 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM auto-init failed: %d\n", rc); + #endif + return rc; + } + + rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); + if (rc != 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM auto-connect failed: %d\n", rc); + #endif + return rc; + } + + #ifdef DEBUG_WOLFTPM + printf("SPDM session established (auto), SessionID=0x%08x\n", + wolfTPM2_SpdmGetSessionId(dev)); + #endif + + /* Retry TPM2_Startup over the SPDM encrypted channel */ + XMEMSET(&startupIn, 0, sizeof(startupIn)); + startupIn.startupType = TPM_SU_CLEAR; + rc = TPM2_Startup(&startupIn); + if (rc != TPM_RC_SUCCESS && rc != TPM_RC_INITIALIZE) { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup over SPDM failed: 0x%x\n", rc); + #endif + return rc; + } + rc = TPM_RC_SUCCESS; + } +#endif /* WOLFTPM_SPDM && WOLFSPDM_NUVOTON */ + return rc; } @@ -923,6 +1003,312 @@ int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles) return handles->count; } +#ifdef WOLFTPM_SPDM +/* --- SPDM Secure Session Wrapper API --- + * + * These functions provide a high-level interface to wolfSPDM. + * All SPDM protocol logic is implemented in the wolfSPDM library. + */ + +int wolfTPM2_SpdmInit(WOLFTPM2_DEV* dev) +{ + int rc; + + if (dev == NULL) { + return BAD_FUNC_ARG; + } + + /* Already initialized (e.g., by auto-SPDM in wolfTPM2_Init) */ + if (dev->spdmCtx != NULL) { + return TPM_RC_SUCCESS; + } + + /* Initialize inline SPDM context */ + rc = wolfTPM2_SPDM_InitCtx(&dev->spdmCtxData, NULL, NULL); + if (rc != 0) { + return rc; + } + + rc = wolfTPM2_SPDM_SetTPMCtx(&dev->spdmCtxData, &dev->ctx); + if (rc != 0) { + wolfTPM2_SPDM_FreeCtx(&dev->spdmCtxData); + return rc; + } + + dev->spdmCtx = &dev->spdmCtxData; + dev->ctx.spdmCtx = dev->spdmCtx; + + return TPM_RC_SUCCESS; +} + +/* Validate SPDM context chain is fully initialized */ +#define WOLFTPM2_SPDM_CHECK_CTX(dev) \ + if ((dev) == NULL || (dev)->spdmCtx == NULL || \ + (dev)->spdmCtx->spdmCtx == NULL) \ + return BAD_FUNC_ARG + +int wolfTPM2_SpdmConnect(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Connect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmIsConnected(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL || dev->spdmCtx->spdmCtx == NULL) { + return 0; + } + return wolfSPDM_IsConnected(dev->spdmCtx->spdmCtx); +} + +word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL || dev->spdmCtx->spdmCtx == NULL) { + return 0; + } + return wolfSPDM_GetSessionId(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Disconnect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev) +{ + if (dev == NULL) { + return BAD_FUNC_ARG; + } + if (dev->spdmCtx != NULL) { + wolfTPM2_SPDM_FreeCtx(dev->spdmCtx); + dev->spdmCtx = NULL; + dev->ctx.spdmCtx = NULL; + } + return TPM_RC_SUCCESS; +} + +#ifdef WOLFSPDM_NUVOTON +/* Nuvoton-specific SPDM functions */ + +int wolfTPM2_SpdmSetNuvotonMode(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NUVOTON); +} + +int wolfTPM2_SpdmEnable(WOLFTPM2_DEV* dev) +{ + int rc; + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + /* NTC2_PreConfig requires platform auth (empty password) */ + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != 0) return rc; + return wolfTPM2_SPDM_Enable(dev->spdmCtx); +} + +int wolfTPM2_SpdmDisable(WOLFTPM2_DEV* dev) +{ + int rc; + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + /* NTC2_PreConfig requires platform auth (empty password) */ + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != 0) return rc; + return wolfTPM2_SPDM_Disable(dev->spdmCtx); +} + +int wolfTPM2_SpdmGetStatus(WOLFTPM2_DEV* dev, WOLFSPDM_NUVOTON_STATUS* status) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nuvoton_GetStatus(dev->spdmCtx->spdmCtx, status); +} + +int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nuvoton_GetPubKey(dev->spdmCtx->spdmCtx, pubKey, pubKeySz); +} + +int wolfTPM2_SpdmSetOnlyMode(WOLFTPM2_DEV* dev, int lock) +{ + int rc; + WOLFTPM2_SPDM_CHECK_CTX(dev); + rc = wolfSPDM_Nuvoton_SetOnlyMode(dev->spdmCtx->spdmCtx, lock); + if (rc == WOLFSPDM_SUCCESS) { + dev->spdmCtx->spdmOnlyLocked = lock; + } + return rc; +} + +int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz) +{ + int rc; + + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Auto-set TIS I/O callback if not already configured by caller */ + rc = wolfTPM2_SPDM_SetTisIO(dev->spdmCtx); + if (rc != 0 && rc != NOT_COMPILED_IN) { + return rc; + } + +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(dev->spdmCtx->spdmCtx, 1); +#endif + + /* Set Nuvoton mode first */ + rc = wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NUVOTON); + if (rc != 0) { + return rc; + } + + /* Set requester key pair if provided (for mutual authentication) */ + if (reqPrivKey != NULL && reqPrivKeySz > 0 && + reqPubKey != NULL && reqPubKeySz > 0) { + /* Parse TPMT_PUBLIC to extract raw X||Y ECC point */ + TPM2_Packet pktPub; + TPMT_PUBLIC pub; + byte rawPubKey[WOLFSPDM_ECC_POINT_SIZE]; + + XMEMSET(&pub, 0, sizeof(pub)); + pktPub.buf = (byte*)reqPubKey; + pktPub.pos = 0; + pktPub.size = (int)reqPubKeySz; + + TPM2_Packet_ParseU16(&pktPub, &pub.type); + TPM2_Packet_ParseU16(&pktPub, &pub.nameAlg); + TPM2_Packet_ParseU32(&pktPub, &pub.objectAttributes); + TPM2_Packet_ParseU16(&pktPub, &pub.authPolicy.size); + TPM2_Packet_ParseBytes(&pktPub, pub.authPolicy.buffer, + pub.authPolicy.size); + TPM2_Packet_ParsePublicParms(&pktPub, pub.type, &pub.parameters); + TPM2_Packet_ParseEccPoint(&pktPub, &pub.unique.ecc); + + if (pub.type != TPM_ALG_ECC || + pub.unique.ecc.x.size != WOLFSPDM_ECC_KEY_SIZE || + pub.unique.ecc.y.size != WOLFSPDM_ECC_KEY_SIZE) { + return BAD_FUNC_ARG; + } + + XMEMCPY(rawPubKey, pub.unique.ecc.x.buffer, + WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(rawPubKey + WOLFSPDM_ECC_KEY_SIZE, + pub.unique.ecc.y.buffer, WOLFSPDM_ECC_KEY_SIZE); + rc = wolfSPDM_SetRequesterKeyPair(dev->spdmCtx->spdmCtx, + reqPrivKey, reqPrivKeySz, rawPubKey, sizeof(rawPubKey)); + if (rc != 0) { + return rc; + } + /* Also store the full TPMT_PUBLIC for GIVE_PUB step */ + rc = wolfSPDM_SetRequesterKeyTPMT(dev->spdmCtx->spdmCtx, + reqPubKey, reqPubKeySz); + if (rc != 0) { + return rc; + } + } +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) + else { + /* Auto-generate ephemeral P-384 key pair for mutual authentication */ + ecc_key hostKey; + WC_RNG rng; + byte privKey[48]; + word32 privKeySz = sizeof(privKey); + byte pubKeyX[48], pubKeyY[48]; + word32 xSz = sizeof(pubKeyX), ySz = sizeof(pubKeyY); + byte rawPubKey[96]; + /* TPMT_PUBLIC: type(2) + nameAlg(2) + attr(4) + authPolicy(2) + + * symmetric(2) + scheme(2+2) + curveID(2) + kdf(2) + + * unique.x(2+48) + unique.y(2+48) = 120 bytes */ + byte tpmtPub[120]; + byte* p; + + rc = wc_InitRng(&rng); + if (rc != 0) return rc; + + rc = wc_ecc_init(&hostKey); + if (rc != 0) { + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_make_key_ex(&rng, 48, &hostKey, ECC_SECP384R1); + if (rc != 0) { + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_export_private_only(&hostKey, privKey, &privKeySz); + if (rc != 0) { + wc_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 */ + +#endif /* WOLFTPM_SPDM */ + int wolfTPM2_UnsetAuth(WOLFTPM2_DEV* dev, int index) { TPM2_AUTH_SESSION* session; @@ -1253,14 +1639,32 @@ int wolfTPM2_Cleanup_ex(WOLFTPM2_DEV* dev, int doShutdown) shutdownIn.shutdownType = TPM_SU_CLEAR; rc = TPM2_Shutdown(&shutdownIn); if (rc != TPM_RC_SUCCESS) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Shutdown failed %d: %s\n", - rc, wolfTPM2_GetRCString(rc)); - #endif + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* SPDM-only mode active - shutdown not needed */ + #ifdef DEBUG_WOLFTPM + printf("TPM2_Shutdown: TPM_RC_DISABLED (SPDM-only mode, " + "expected)\n"); + #endif + rc = TPM_RC_SUCCESS; /* Not an error in SPDM mode */ + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Shutdown failed %d: %s\n", + rc, wolfTPM2_GetRCString(rc)); + #endif + } /* finish cleanup and return error */ } } +#ifdef WOLFTPM_SPDM + /* Clean up SPDM context if it was auto-established */ + wolfTPM2_SpdmCleanup(dev); +#endif + TPM2_Cleanup(&dev->ctx); return rc; diff --git a/tests/unit_tests.c b/tests/unit_tests.c index f11d30f1..7c43cf59 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -924,6 +924,78 @@ static void test_wolfTPM2_thread_local_storage(void) #endif /* HAVE_THREAD_LS && HAVE_PTHREAD */ } +#ifdef WOLFTPM_SPDM +/* Test SPDM wrapper API functions */ +static void test_wolfTPM2_SPDM_Functions(void) +{ + int rc; + WOLFTPM2_DEV dev; + + printf("Test TPM Wrapper:\tSPDM Functions:\t"); + + /* Initialize device */ + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + if (rc != 0) { + printf("Failed (Init failed: 0x%x)\n", rc); + return; + } + + /* Test 1: Parameter validation - NULL args */ + rc = wolfTPM2_SpdmInit(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmConnect(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_SpdmIsConnected(NULL), 0); + AssertIntEQ(wolfTPM2_SpdmGetSessionId(NULL), 0); + rc = wolfTPM2_SpdmDisconnect(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmCleanup(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + + /* Test 2: Context lifecycle - init, check state, cleanup */ + rc = wolfTPM2_SpdmInit(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + /* When SPDM-only mode is active, auto-SPDM connects during Init. + * Otherwise, just initialized but not yet connected. */ + if (!dev.ctx.spdmOnlyDetected) { + AssertIntEQ(wolfTPM2_SpdmIsConnected(&dev), 0); + AssertIntEQ(wolfTPM2_SpdmGetSessionId(&dev), 0); + } + /* Cleanup */ + rc = wolfTPM2_SpdmCleanup(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + /* Idempotent cleanup */ + rc = wolfTPM2_SpdmCleanup(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + +#ifdef WOLFSPDM_NUVOTON + /* Test 3: Nuvoton-specific parameter validation */ + rc = wolfTPM2_SpdmSetNuvotonMode(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmEnable(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + { + WOLFSPDM_NUVOTON_STATUS status; + byte pubKey[256]; + word32 pubKeySz = sizeof(pubKey); + + rc = wolfTPM2_SpdmGetStatus(NULL, &status); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmGetStatus(&dev, NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmGetPubKey(NULL, pubKey, &pubKeySz); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmSetOnlyMode(NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + } +#endif /* WOLFSPDM_NUVOTON */ + + wolfTPM2_Cleanup(&dev); + + printf("Passed\n"); +} +#endif /* WOLFTPM_SPDM */ + /* Test creating key and exporting keyblob as buffer, * importing and loading key. */ static void test_wolfTPM2_KeyBlob(TPM_ALG_ID alg) @@ -1060,6 +1132,9 @@ int unit_tests(int argc, char *argv[]) #endif test_wolfTPM2_Cleanup(); test_wolfTPM2_thread_local_storage(); +#ifdef WOLFTPM_SPDM + test_wolfTPM2_SPDM_Functions(); +#endif #endif /* !WOLFTPM2_NO_WRAPPER */ return 0; diff --git a/wolftpm/include.am b/wolftpm/include.am index 630436cf..b7b75c93 100644 --- a/wolftpm/include.am +++ b/wolftpm/include.am @@ -15,5 +15,6 @@ nobase_include_HEADERS+= \ wolftpm/tpm2_socket.h \ wolftpm/tpm2_asn.h \ wolftpm/version.h \ + wolftpm/tpm2_spdm.h \ wolftpm/visibility.h \ wolftpm/options.h diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 6f2ca10d..aba4004c 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -262,7 +262,7 @@ typedef enum { TPM_CC_SetCommandSetLock = CC_VEND + 0x030B, TPM_CC_GPIO_Config = CC_VEND + 0x030F, #endif -#ifdef WOLFTPM_NUVOTON +#if defined(WOLFTPM_NUVOTON) || defined(WOLFTPM_AUTODETECT) TPM_CC_NTC2_PreConfig = CC_VEND + 0x0211, TPM_CC_NTC2_GetConfig = CC_VEND + 0x0213, #endif @@ -508,6 +508,73 @@ typedef enum { } TPM_CAP_T; typedef UINT32 TPM_CAP; +#ifdef WOLFTPM_SPDM +/* TCG SPDM Binding for Secure Communication v1.0 Constants */ + +/* TCG SPDM Binding Message Tags */ +#define SPDM_TAG_CLEAR 0x8101 /* Clear (unencrypted) SPDM message */ +#define SPDM_TAG_SECURED 0x8201 /* Secured (encrypted) SPDM message */ + +/* SPDM Protocol Version */ +#define SPDM_VERSION_1_3 0x13 /* SPDM v1.3 */ + +/* SPDM protocol message codes, response codes, and error codes are + * defined in (the authoritative source). + * Include that header directly if you need SPDM protocol constants. */ + +/* SPDM Vendor Defined Codes (8-byte ASCII, used as VdCode in VENDOR_DEFINED) */ +#define SPDM_VDCODE_TPM2_CMD "TPM2_CMD" /* TPM command over SPDM session */ +#define SPDM_VDCODE_GET_PUBK "GET_PUBK" /* Get TPM's SPDM-Identity pub key */ +#define SPDM_VDCODE_GIVE_PUB "GIVE_PUB" /* Give host's SPDM-Identity pub key */ +#define SPDM_VDCODE_SPDMONLY "SPDMONLY" /* Lock/unlock SPDM-only mode */ +#define SPDM_VDCODE_GET_STS "GET_STS_" /* Get SPDM status */ + +/* SPDM Vendor Defined Code Length */ +#define SPDM_VDCODE_LEN 8 + +/* SPDM Session Constants (Nuvoton NPCT7xx) */ +#define SPDM_CONNECTION_ID 0 /* Single connection */ +#define SPDM_RSP_SESSION_ID 0xAEAD /* Responder session ID */ +#define SPDM_REQ_SESSION_ID 0x0001 /* Default requester session ID */ + +/* SPDM FIPS Indicator (TCG binding) */ +#define SPDM_FIPS_NON_FIPS 0x00 +#define SPDM_FIPS_APPROVED 0x01 + +/* SPDM Algorithm Set B (192-bit security strength) */ +#define SPDM_ALG_ECDSA_P384 0x0003 /* Signing algorithm */ +#define SPDM_ALG_SHA384 0x0002 /* Hash algorithm */ +#define SPDM_ALG_ECDHE_P384 0x0003 /* Key exchange algorithm */ +#define SPDM_ALG_AES256_GCM 0x0002 /* AEAD algorithm */ + +/* SPDM-Identity NV Indices */ +#define SPDM_NV_INDEX_TPM_KEY 0x01C20110 /* TPM SPDM-Identity key */ +#define SPDM_NV_INDEX_REQ_KEY 0x01C20111 /* Requester SPDM-Identity key */ + +/* NTC2 PreConfig CFG_H Bit Definitions for SPDM */ +#define NTC2_CFG_H_SPDM_ENABLE_BIT 1 /* Bit position in CFG_H */ +#define NTC2_CFG_H_SPDM_ENABLE 0x00 /* SPDM enabled (bit 1 = 0) */ +#define NTC2_CFG_H_SPDM_DISABLE 0x02 /* SPDM disabled (bit 1 = 1) */ + +/* SPDM ONLY mode sub-commands */ +#define SPDM_ONLY_LOCK 0x01 +#define SPDM_ONLY_UNLOCK 0x00 + +/* SPDM Message Sizes */ +#define SPDM_MAX_MSG_SIZE 4096 + +/* TCG SPDM Binding Header Size (per TCG SPDM Binding Spec): + * tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + fipsIndicator(2/BE) + + * reserved(4) = 16 bytes */ +#define SPDM_TCG_BINDING_HEADER_SIZE 16 + +/* SPDM Secured Message Header Size (per DSP0277): + * sessionId(4/LE) + sequenceNumber(8/LE) + length(2/LE) = 14 bytes + * where length = size of encrypted data + MAC */ +#define SPDM_SECURED_MSG_HEADER_SIZE 14 + +#endif /* WOLFTPM_SPDM */ + /* Property Tag */ typedef enum { TPM_PT_NONE = 0x00000000, @@ -816,6 +883,9 @@ typedef TPM_HANDLE TPMI_RH_CLEAR; typedef TPM_HANDLE TPMI_RH_NV_AUTH; typedef TPM_HANDLE TPMI_RH_LOCKOUT; typedef TPM_HANDLE TPMI_RH_NV_INDEX; +#ifdef WOLFTPM_SPDM +typedef TPM_HANDLE TPMI_DH_AC; /* Authenticated Controller handle */ +#endif typedef TPM_ALG_ID TPMI_ALG_HASH; typedef TPM_ALG_ID TPMI_ALG_ASYM; @@ -1045,7 +1115,6 @@ typedef struct TPML_ACT_DATA { TPMS_ACT_DATA actData[MAX_ACT_DATA]; } TPML_ACT_DATA; - /* Capabilities Structures */ typedef union TPMU_CAPABILITIES { @@ -1902,6 +1971,10 @@ typedef struct TPM2_CTX { #if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_LINUX_DEV_AUTODETECT) int fd; #endif +#ifdef WOLFTPM_SPDM + void* spdmCtx; /* Pointer to WOLFTPM2_SPDM_CTX when session active */ + unsigned int spdmOnlyDetected:1; /* TPM_RC_DISABLED from Startup */ +#endif } TPM2_CTX; @@ -1929,7 +2002,6 @@ typedef struct { WOLFTPM_API TPM_RC TPM2_GetCapability(GetCapability_In* in, GetCapability_Out* out); - typedef struct { TPMI_YES_NO fullTest; } SelfTest_In; @@ -2650,6 +2722,7 @@ typedef struct { } PolicyAuthValue_In; WOLFTPM_API TPM_RC TPM2_PolicyAuthValue(PolicyAuthValue_In* in); + typedef struct { TPMI_SH_POLICY policySession; } PolicyPassword_In; @@ -3142,6 +3215,44 @@ WOLFTPM_API int TPM2_ST33_FieldUpgradeCommand(TPM_CC cc, uint8_t* data, uint32_t WOLFTPM_API int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out); #endif /* Vendor GPIO Commands */ +/* NTC2 PreConfig/GetConfig for WOLFTPM_AUTODETECT (runtime vendor detection). + * When a specific vendor is not selected at compile time, the NTC2 types + * must still be available for SPDM enable via NTC2_PreConfig. */ +#if defined(WOLFTPM_AUTODETECT) && !defined(WOLFTPM_NUVOTON) && \ + !defined(WOLFTPM_ST33) + typedef struct { + BYTE Base0; + BYTE Base1; + BYTE GpioAltCfg; + BYTE GpioInitValue; + BYTE GpioPullUp; + BYTE GpioPushPull; + BYTE Cfg_A; + BYTE Cfg_B; + BYTE Cfg_C; + BYTE Cfg_D; + BYTE Cfg_E; + BYTE Cfg_F; + BYTE Cfg_G; + BYTE Cfg_H; + BYTE Cfg_I; + BYTE Cfg_J; + BYTE isValid; + BYTE isLocked; + } CFG_STRUCT; + + typedef struct { + TPMI_RH_PLATFORM authHandle; + CFG_STRUCT preConfig; + } NTC2_PreConfig_In; + WOLFTPM_API int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in); + + typedef struct { + CFG_STRUCT preConfig; + } NTC2_GetConfig_Out; + WOLFTPM_API int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out); +#endif /* WOLFTPM_AUTODETECT && !WOLFTPM_NUVOTON && !WOLFTPM_ST33 */ + /* Non-standard API's */ 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..567857f6 --- /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) + * -------------------------------------------------------------------------- */ + +#ifdef WOLFSPDM_NUVOTON + +/** + * Set the built-in TIS I/O callback for routing SPDM through TPM SPI/I2C. + * Uses the TPM TIS FIFO to send/receive raw SPDM messages. + * TCG framing is handled internally by wolfSPDM_SendReceive(). + * Must be called after wolfTPM2_SPDM_InitCtx() and SetTPMCtx(). + * + * Only available on hardware TPM builds (not LINUX_DEV, SWTPM, or WINAPI). + * + * @param ctx wolfTPM2 SPDM context (with tpmCtx already set) + * @return 0 on success, NOT_COMPILED_IN if TIS not available + */ +WOLFTPM_API int wolfTPM2_SPDM_SetTisIO( + WOLFTPM2_SPDM_CTX* ctx +); + +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFTPM_SPDM */ + +#endif /* __TPM2_SPDM_H__ */ diff --git a/wolftpm/tpm2_types.h b/wolftpm/tpm2_types.h index ddd08d55..101e4977 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -760,6 +760,9 @@ typedef int64_t INT64; #ifndef MAX_ACT_DATA #define MAX_ACT_DATA (MAX_CAP_DATA / sizeof(TPMS_ACT_DATA)) #endif +#ifndef MAX_AC_HANDLES +#define MAX_AC_HANDLES 16 +#endif /* ---------------------------------------------------------------------------*/ diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 40bd5132..72b88e91 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -23,6 +23,9 @@ #define __TPM2_WRAP_H__ #include +#ifdef WOLFTPM_SPDM +#include +#endif #ifdef __cplusplus extern "C" { @@ -57,6 +60,10 @@ typedef struct WOLFTPM2_SESSION { typedef struct WOLFTPM2_DEV { TPM2_CTX ctx; TPM2_AUTH_SESSION session[MAX_SESSION_NUM]; +#ifdef WOLFTPM_SPDM + WOLFTPM2_SPDM_CTX spdmCtxData; + WOLFTPM2_SPDM_CTX* spdmCtx; /* NULL = not initialized */ +#endif } WOLFTPM2_DEV; /* Public Key with Handle. @@ -163,6 +170,10 @@ typedef struct WOLFTPM2_CAPS { word16 fips140_2 : 1; /* using FIPS mode */ word16 cc_eal4 : 1; /* Common Criteria EAL4+ */ word16 req_wait_state : 1; /* requires SPI wait state */ +#ifdef WOLFTPM_SPDM + word32 acHandleCount; /* Number of AC handles discovered */ + TPM_HANDLE acHandles[MAX_AC_HANDLES]; /* AC handles */ +#endif } WOLFTPM2_CAPS; @@ -412,6 +423,176 @@ WOLFTPM_API int wolfTPM2_GetCapabilities(WOLFTPM2_DEV* dev, WOLFTPM2_CAPS* caps) */ WOLFTPM_API int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles); +#ifdef WOLFTPM_SPDM +/* SPDM Secure Session Wrapper API + * + * These functions provide a high-level interface for SPDM secure sessions. + * All SPDM protocol logic is implemented in the wolfSPDM library. + */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Initialize SPDM support on a wolfTPM2 device. + Allocates and configures the SPDM context using wolfSPDM. + After init, call wolfTPM2_SpdmConnect to establish a secure session. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + \return MEMORY_E: memory allocation failed + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmInit(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish an SPDM secure session (full handshake). + Uses standard SPDM flow: GET_VERSION -> GET_CAPABILITIES -> + NEGOTIATE_ALGORITHMS -> KEY_EXCHANGE -> FINISH. + + \return TPM_RC_SUCCESS: session established + \return TPM_RC_FAILURE: handshake failed + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmConnect(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Check if an SPDM secure session is currently active. + + \return 1 if connected, 0 if not + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmIsConnected(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get the current SPDM session ID. + + \return Session ID, or 0 if not connected + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Disconnect the SPDM secure session. + After this, TPM commands are sent in the clear. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Free SPDM context and resources. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev); + +#ifdef WOLFSPDM_NUVOTON +/* Nuvoton-specific SPDM functions (requires wolfSPDM with --enable-nuvoton) */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Configure for Nuvoton TPM SPDM mode. + Must be called before wolfTPM2_SpdmConnect() for Nuvoton TPMs. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmSetNuvotonMode(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Enable SPDM on the TPM via NTC2_PreConfig vendor command. + Requires platform hierarchy auth. TPM must be reset after this call. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmEnable(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Disable SPDM on Nuvoton TPM via NTC2_PreConfig. + Sets Cfg_H bit 1 to disable SPDM. Requires TPM reset to take effect. + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmDisable(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish Nuvoton SPDM secure session with mutual authentication. + Uses Nuvoton flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> + GIVE_PUB_KEY -> FINISH. + + \return TPM_RC_SUCCESS: session established + \return TPM_RC_FAILURE: handshake failed + + \param dev pointer to a WOLFTPM2_DEV structure + \param reqPubKey host's ECDSA P-384 public key (TPMT_PUBLIC format) + \param reqPubKeySz size of reqPubKey in bytes + \param reqPrivKey host's ECDSA P-384 private key (raw 48 bytes) + \param reqPrivKeySz size of reqPrivKey in bytes +*/ +WOLFTPM_API int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get SPDM status from the TPM (GET_STS_ vendor command). + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param status output: SPDM status information +*/ +WOLFTPM_API int wolfTPM2_SpdmGetStatus(WOLFTPM2_DEV* dev, + WOLFSPDM_NUVOTON_STATUS* status); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get the TPM's SPDM-Identity public key. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param pubKey output buffer for the public key + \param pubKeySz in/out: buffer size / actual key size +*/ +WOLFTPM_API int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, + byte* pubKey, word32* pubKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Lock or unlock SPDM-only mode. + When locked, TPM only accepts commands over SPDM secure channel. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure + \param lock 1 to lock SPDM-only mode, 0 to unlock +*/ +WOLFTPM_API int wolfTPM2_SpdmSetOnlyMode(WOLFTPM2_DEV* dev, int lock); + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFTPM_SPDM */ /*! \ingroup wolfTPM2_Wrappers diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 41059918..0c3f79d8 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -6,6 +6,10 @@ if(CONFIG_WOLFTPM) ${ZEPHYR_CURRENT_MODULE_DIR}/src/*.c ${ZEPHYR_CURRENT_MODULE_DIR}/hal/*.c ) + # Exclude transport backends not applicable to Zephyr + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_linux\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_winapi\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_spdm\\.c$") target_sources(app PRIVATE ${wolftpm_sources}) if(CONFIG_WOLFTPM_DEBUG)