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 2dd27e1f..222f771e 100644 --- a/.gitignore +++ b/.gitignore @@ -92,6 +92,7 @@ examples/firmware/ifx_fw_update examples/firmware/st33_fw_update examples/endorsement/get_ek_certs examples/endorsement/verify_ek_cert +examples/spdm/spdm_demo # Generated Cert Files certs/ca-*.pem @@ -181,10 +182,18 @@ UpgradeLog.htm /IDE/Espressif/**/sdkconfig /IDE/Espressif/**/sdkconfig.old +# SPDM build artifacts +spdm/wolfspdm/options.h +spdm/config.h +spdm/stamp-h1 +spdm/src/.libs/ +spdm/src/.deps/ +spdm/test/.libs/ +spdm/test/unit_test + # Firmware files examples/firmware/*.fi examples/firmware/*.BIN examples/firmware/*.DATA examples/firmware/*.MANIFEST examples/firmware/*.MANIFESTHASH - diff --git a/Makefile.am b/Makefile.am index afee7851..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..334c040e 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,7 @@ AC_CANONICAL_HOST AC_CANONICAL_TARGET AC_CONFIG_MACRO_DIR([m4]) + AM_INIT_AUTOMAKE([1.11 -Wall -Werror -Wno-portability foreign tar-ustar subdir-objects no-define color-tests]) AC_ARG_PROGRAM @@ -332,6 +333,17 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_NUVOTON" fi +# Nations Technology NS350 +AC_ARG_ENABLE([nations], + [AS_HELP_STRING([--enable-nations],[Enable Nations Technology NS350 TPM Support (default: disabled)])], + [ ENABLED_NATIONS=$enableval ], + [ ENABLED_NATIONS=no ] + ) +if test "x$ENABLED_NATIONS" = "xyes" +then + AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_NATIONS" +fi + # Infineon SLB9670/SLB9672/SLB9673 AC_ARG_ENABLE([infineon], [AS_HELP_STRING([--enable-infineon],[Enable Infineon SLB9670/SLB9672 TPM Support (default: disabled)])], @@ -407,7 +419,8 @@ if test "x$ENABLED_AUTODETECT" = "xtest" then # If a module hasn't been selected then enable auto-detection if test "x$ENABLED_INFINEON" = "xno" && test "x$ENABLED_MCHP" = "xno" && test "x$ENABLED_MICROCHIP" = "xno" && \ - test "x$ENABLED_ST" = "xno" && test "x$ENABLED_ST33" = "xno" && test "x$ENABLED_NUVOTON" = "xno" + test "x$ENABLED_ST" = "xno" && test "x$ENABLED_ST33" = "xno" && test "x$ENABLED_NUVOTON" = "xno" && \ + test "x$ENABLED_NATIONS" = "xno" then ENABLED_AUTODETECT=yes fi @@ -462,6 +475,55 @@ 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 + + # Nations Technology SPDM support + if test "x$ENABLED_NATIONS" = "xyes" + then + AC_DEFINE([WOLFSPDM_NATIONS], [1], [Enable SPDM Nations Technology support]) + AC_MSG_NOTICE([Nations Technology SPDM vendor commands enabled]) + fi + + if test "x$ENABLED_SPDM_DYNMEM" = "xyes" + then + AC_DEFINE([WOLFSPDM_DYNAMIC_MEMORY], [1], [SPDM: Enable dynamic memory allocation]) + fi + + if test "x$ax_enable_debug" != "xno" + then + AC_DEFINE([WOLFSPDM_DEBUG], [1], [SPDM: Enable debug output]) + fi +fi # HARDEN FLAGS AX_HARDEN_CC_COMPILER_FLAGS @@ -489,10 +551,12 @@ AM_CONDITIONAL([BUILD_DEVTPM], [test "x$ENABLED_DEVTPM" = "xyes"]) AM_CONDITIONAL([BUILD_SWTPM], [test "x$ENABLED_SWTPM" = "xyes"]) AM_CONDITIONAL([BUILD_WINAPI], [test "x$ENABLED_WINAPI" = "xyes"]) AM_CONDITIONAL([BUILD_NUVOTON], [test "x$ENABLED_NUVOTON" = "xyes"]) +AM_CONDITIONAL([BUILD_NATIONS], [test "x$ENABLED_NATIONS" = "xyes"]) AM_CONDITIONAL([BUILD_CHECKWAITSTATE], [test "x$ENABLED_CHECKWAITSTATE" = "xyes"]) AM_CONDITIONAL([BUILD_AUTODETECT], [test "x$ENABLED_AUTODETECT" = "xyes"]) AM_CONDITIONAL([BUILD_FIRMWARE], [test "x$ENABLED_FIRMWARE" = "xyes"]) AM_CONDITIONAL([BUILD_HAL], [test "x$ENABLED_EXAMPLE_HAL" = "xyes" || test "x$ENABLED_MMIO" = "xyes"]) +AM_CONDITIONAL([BUILD_SPDM], [test "x$ENABLED_SPDM" = "xyes"]) CREATE_HEX_VERSION @@ -578,6 +642,10 @@ for option in $OPTION_FLAGS; do fi done +# Also capture SPDM defines from config.h (set via AC_DEFINE, not AM_CFLAGS) +grep '^#define WOLFSPDM_' src/config.h >> $OPTION_FILE 2>/dev/null || true +grep '^#define WOLFTPM_SPDM' src/config.h >> $OPTION_FILE 2>/dev/null || true + echo "" >> $OPTION_FILE echo "#ifdef __cplusplus" >> $OPTION_FILE echo "}" >> $OPTION_FILE @@ -619,6 +687,11 @@ echo " * Infineon SLB967X $ENABLED_INFINEON" echo " * STM ST33: $ENABLED_ST" echo " * Microchip ATTPM20: $ENABLED_MICROCHIP" echo " * Nuvoton NPCT75x: $ENABLED_NUVOTON" +echo " * Nations Tech NS350: $ENABLED_NATIONS" echo " * Runtime Module Detection: $ENABLED_AUTODETECT" echo " * Firmware Upgrade Support: $ENABLED_FIRMWARE" +echo " * SPDM Support: $ENABLED_SPDM" +if test "x$ENABLED_SPDM" = "xyes"; then + echo " * SPDM Dynamic Mem: $ENABLED_SPDM_DYNMEM" +fi diff --git a/examples/include.am b/examples/include.am index 96c034f0..d34804ac 100644 --- a/examples/include.am +++ b/examples/include.am @@ -18,6 +18,7 @@ include examples/seal/include.am include examples/attestation/include.am include examples/firmware/include.am include examples/endorsement/include.am +include examples/spdm/include.am if BUILD_EXAMPLES EXTRA_DIST += examples/run_examples.sh diff --git a/examples/spdm/README.md b/examples/spdm/README.md new file mode 100644 index 00000000..377cd485 --- /dev/null +++ b/examples/spdm/README.md @@ -0,0 +1,88 @@ +# TPM SPDM Examples + +This directory contains the SPDM demo for Nuvoton NPCT75x TPMs with wolfTPM. + +## Overview + +The `spdm_demo` establishes an SPDM secure session between the host and a +Nuvoton TPM over SPI, enabling AES-256-GCM encrypted bus communication. Once +active, all TPM commands are automatically encrypted with no application changes. + +For standard SPDM protocol support (spdm-emu, measurements, challenge, etc.), +see the [wolfSPDM](https://github.com/aidangarske/wolfSPDM) standalone library. + +## Building + +### Prerequisites + +wolfSSL with crypto algorithms required for SPDM Algorithm Set B: + +```bash +cd wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp +make && sudo make install && sudo ldconfig +``` + +### wolfTPM with Nuvoton SPDM + +```bash +cd wolfTPM +./autogen.sh +./configure --enable-spdm --enable-nuvoton +make +``` + +## Demo Commands + +| Option | Description | +|--------|-------------| +| `--enable` | Enable SPDM on TPM via NTC2_PreConfig (one-time, requires reset) | +| `--disable` | Disable SPDM on TPM via NTC2_PreConfig (requires reset) | +| `--status` | Query SPDM status from TPM | +| `--get-pubkey` | Get TPM's SPDM-Identity P-384 public key | +| `--connect` | Establish SPDM session (ECDH P-384 handshake) | +| `--lock` | Lock SPDM-only mode (use with `--connect`) | +| `--unlock` | Unlock SPDM-only mode (use with `--connect`) | + +## Usage Examples + +```bash +# One-time setup: enable SPDM + GPIO reset +./examples/spdm/spdm_demo --enable +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# Query SPDM status +./examples/spdm/spdm_demo --status + +# Get TPM identity key +./examples/spdm/spdm_demo --get-pubkey + +# Establish SPDM session +./examples/spdm/spdm_demo --connect + +# Lock SPDM-only mode (connect + lock in one session) +./examples/spdm/spdm_demo --connect --lock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# All commands now auto-encrypt: +./examples/wrap/caps # auto-SPDM, AES-256-GCM encrypted +./tests/unit.test # full test suite over encrypted bus + +# Unlock SPDM-only mode +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect --unlock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +## Automated Test Suite + +Runs 6 tests: status, connect, lock, unit test over SPDM, unlock, cleartext caps setup lifecycle on hardware. + +```bash +./examples/spdm/spdm_test.sh +``` + +## Support + +For production use with hardware TPMs and SPDM support, contact **support@wolfssl.com**. diff --git a/examples/spdm/include.am b/examples/spdm/include.am new file mode 100644 index 00000000..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..9fe63b0d --- /dev/null +++ b/examples/spdm/spdm_demo.c @@ -0,0 +1,644 @@ +/* spdm_demo.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#include +#include +#include + +#ifndef WOLFTPM2_NO_WRAPPER + +#include +#include + +#ifdef WOLFTPM_SPDM + +#include +#include + +int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]); + +static void usage(void) +{ + printf("SPDM Demo - TPM secure session\n\n" + "Usage: spdm_demo [options]\n" +#ifdef WOLFSPDM_NUVOTON + " --enable Enable SPDM via NTC2_PreConfig\n" + " --disable Disable SPDM via NTC2_PreConfig\n" + " --status Query SPDM status\n" + " --lock Lock SPDM-only mode\n" + " --unlock Unlock SPDM-only mode\n" +#endif +#ifdef WOLFSPDM_NATIONS + " --identity-key-set Provision SPDM identity key\n" + " --identity-key-unset Un-provision SPDM identity key\n" + " --psk PSK mode connect (64-byte PSK)\n" + " --psk-set Provision PSK (64-byte PSK, 32-byte ClearAuth)\n" + " --psk-clear Clear PSK (32-byte ClearAuth from psk-set)\n" + " --lock Lock SPDM-only mode (PSK mode, use with --psk)\n" + " --unlock Unlock SPDM-only mode (PSK mode, use with --psk)\n" + " --status Query SPDM status (PSK mode)\n" +#endif + " --get-pubkey Get TPM's SPDM-Identity public key\n" + " --connect Establish SPDM session\n" + " --caps Get TPM capabilities (use with --connect)\n" + " -h, --help Show this help\n\n" +#ifdef WOLFSPDM_NUVOTON + "Build: ./configure --enable-spdm --enable-nuvoton\n" +#elif defined(WOLFSPDM_NATIONS) + "Build: ./configure --enable-spdm --enable-nations\n" +#endif + ); +} + +#ifdef WOLFSPDM_NUVOTON +static int demo_enable(WOLFTPM2_DEV* dev) +{ + int rc; + printf("\n=== Enable SPDM ===\n"); + rc = wolfTPM2_SpdmEnable(dev); + if (rc == 0) { + printf(" SPDM enabled (reset TPM if newly configured)\n"); + } else if (rc == (int)TPM_RC_DISABLED) { + printf(" SPDM-only active (already enabled)\n"); + rc = 0; + } else if (rc == TPM_RC_COMMAND_CODE) { + printf(" NTC2_PreConfig not supported (may already be enabled)\n"); + rc = 0; + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_disable(WOLFTPM2_DEV* dev) +{ + int rc; + printf("\n=== Disable SPDM ===\n"); + rc = wolfTPM2_SpdmDisable(dev); + if (rc == 0) { + printf(" SPDM disabled (reset TPM for effect)\n"); + } else if (rc == (int)TPM_RC_DISABLED) { + printf(" SPDM-only active - unlock first, then reset and disable\n"); + } else if (rc == TPM_RC_COMMAND_CODE) { + printf(" NTC2_PreConfig not supported\n"); + rc = 0; + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_status(WOLFTPM2_DEV* dev) +{ + int rc; + WOLFSPDM_NUVOTON_STATUS status; + + printf("\n=== SPDM Status ===\n"); + XMEMSET(&status, 0, sizeof(status)); + rc = wolfTPM2_SpdmGetStatus(dev, &status); + if (rc == 0) { + int isConn = wolfTPM2_SpdmIsConnected(dev); + printf(" Enabled: %s Locked: %s Session: %s\n", + status.spdmEnabled ? "Yes" : "No", + status.spdmOnlyLocked ? "YES" : "No", + isConn ? "Yes" : "No"); + if (isConn) { + byte negVer = wolfSPDM_GetNegotiatedVersion(dev->spdmCtx->spdmCtx); + printf(" Version: SPDM %u.%u SessionID: 0x%08x\n", + (negVer >> 4) & 0xF, negVer & 0xF, + wolfTPM2_SpdmGetSessionId(dev)); + } + printf(" Nuvoton: v%u.%u\n", status.specVersionMajor, + status.specVersionMinor); + if (status.spdmOnlyLocked) + printf(" NOTE: SPDM-only mode, use --unlock to restore\n"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_get_pubkey(WOLFTPM2_DEV* dev) +{ + int rc; + byte pubKey[128]; + word32 pubKeySz = sizeof(pubKey); + word32 i; + + printf("\n=== Get SPDM-Identity Public Key ===\n"); + rc = wolfTPM2_SpdmGetPubKey(dev, pubKey, &pubKeySz); + if (rc == 0) { + printf(" Got %d bytes: ", (int)pubKeySz); + for (i = 0; i < pubKeySz && i < 32; i++) printf("%02x", pubKey[i]); + if (pubKeySz > 32) printf("..."); + printf("\n"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_connect(WOLFTPM2_DEV* dev) +{ + int rc; + + printf("\n=== SPDM Connect ===\n"); + if (wolfTPM2_SpdmIsConnected(dev)) { + printf(" Already connected (SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + return 0; + } + + printf(" Handshake: VERSION -> GET_PUBK -> KEY_EXCHANGE -> " + "GIVE_PUB -> FINISH\n"); + rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); + if (rc == 0) { + printf(" Session established (AES-256-GCM, SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_lock(WOLFTPM2_DEV* dev, int lock) +{ + int rc; + printf("\n=== SPDM-Only: %s ===\n", lock ? "LOCK" : "UNLOCK"); + rc = wolfTPM2_SpdmSetOnlyMode(dev, lock); + if (rc == 0) + printf(" %s\n", lock ? "LOCKED (TPM requires SPDM)" : "UNLOCKED"); + else + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; +} +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef WOLFSPDM_NATIONS +static int hex2bin(const char* hex, byte* bin, word32* binSz) +{ + word32 hexLen = (word32)XSTRLEN(hex); + word32 i; + if (hexLen % 2 != 0 || hexLen / 2 > *binSz) return -1; + for (i = 0; i < hexLen; i += 2) { + byte hi, lo; + hi = (byte)((hex[i] >= '0' && hex[i] <= '9') ? hex[i] - '0' : + (hex[i] >= 'a' && hex[i] <= 'f') ? hex[i] - 'a' + 10 : + (hex[i] >= 'A' && hex[i] <= 'F') ? hex[i] - 'A' + 10 : 0xFF); + lo = (byte)((hex[i+1] >= '0' && hex[i+1] <= '9') ? hex[i+1] - '0' : + (hex[i+1] >= 'a' && hex[i+1] <= 'f') ? hex[i+1] - 'a' + 10 : + (hex[i+1] >= 'A' && hex[i+1] <= 'F') ? hex[i+1] - 'A' + 10 : 0xFF); + if (hi == 0xFF || lo == 0xFF) return -1; + bin[i / 2] = (byte)((hi << 4) | lo); + } + *binSz = hexLen / 2; + return 0; +} + +static int demo_nations_status(WOLFTPM2_DEV* dev) +{ + int rc; + int isConn; + GetCapability_In capIn; + GetCapability_Out capOut; + + printf("\n=== Nations SPDM Status ===\n"); + + /* 1. Check identity key provisioning via GetCapability (always works) */ + XMEMSET(&capIn, 0, sizeof(capIn)); + capIn.capability = TPM_CAP_VENDOR_PROPERTY; + capIn.property = 12; /* TPM_PT_VENDOR + 12: identity key status */ + capIn.propertyCount = 1; + XMEMSET(&capOut, 0, sizeof(capOut)); + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc == 0) { + byte* raw = (byte*)&capOut.capabilityData.data.tpmProperties; + int identityKey = (raw[3] != 0); /* value at prop 12 */ + printf(" Identity Key: %s\n", + identityKey ? "provisioned" : "not provisioned"); + } else { + printf(" Identity Key: unknown (GetCap failed: 0x%x)\n", rc); + } + + /* 2. Try GET_STS_ vendor command (PSK mode only — may fail) */ + { + WOLFSPDM_NATIONS_STATUS status; + int stsRc; + + stsRc = wolfSPDM_GetVersion(dev->spdmCtx->spdmCtx); + if (stsRc == 0) { + XMEMSET(&status, 0, sizeof(status)); + stsRc = wolfTPM2_SpdmNationsGetStatus(dev, &status); + if (stsRc == 0) { + printf(" PSK: %s SPDM-Only: %s\n", + status.pskProvisioned ? "provisioned" : "not provisioned", + !status.spdmOnlyLocked ? "disabled" : + status.spdmOnlyPending ? "PENDING_DISABLE" : "ENABLED"); + } else { + printf(" PSK Status: unknown (GET_STS failed)\n"); + } + } else { + printf(" PSK Status: GET_VERSION failed\n"); + } + } + + /* 3. Local session state */ + isConn = wolfTPM2_SpdmIsConnected(dev); + printf(" Session: %s\n", isConn ? "active" : "none"); + if (isConn) { + printf(" SessionID: 0x%08x\n", wolfTPM2_SpdmGetSessionId(dev)); + } + + return 0; /* status is informational, don't fail */ +} + +static int demo_nations_psk_connect(WOLFTPM2_DEV* dev, const char* pskHex) +{ + int rc; + byte psk[128]; + word32 pskSz = sizeof(psk); + + printf("\n=== Nations PSK Connect ===\n"); + rc = hex2bin(pskHex, psk, &pskSz); + if (rc != 0) { + printf(" Invalid PSK hex string\n"); + return BAD_FUNC_ARG; + } + + rc = wolfTPM2_SpdmConnectNationsPsk(dev, psk, pskSz, NULL, 0); + XMEMSET(psk, 0, sizeof(psk)); + if (rc == 0) { + printf(" PSK session established (SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_nations_psk_set(WOLFTPM2_DEV* dev, + const char* pskHex, const char* clearAuthHex) +{ + int rc; + byte psk[64]; + word32 pskSz = sizeof(psk); + byte clearAuth[256]; + word32 clearAuthSz = sizeof(clearAuth); + byte payload[112]; /* PSK(64) + SHA-384(ClearAuth)(48) */ + wc_Sha384 sha; + + printf("\n=== Nations PSK Set ===\n"); + rc = hex2bin(pskHex, psk, &pskSz); + if (rc != 0 || pskSz != 64) { + printf(" Error: PSK must be exactly 64 bytes, got %u\n", pskSz); + return BAD_FUNC_ARG; + } + rc = hex2bin(clearAuthHex, clearAuth, &clearAuthSz); + if (rc != 0 || clearAuthSz != 32) { + printf(" Error: ClearAuth must be exactly 32 bytes, got %u\n", + clearAuthSz); + return BAD_FUNC_ARG; + } + + /* Build payload: PSK(64) + SHA-384(ClearAuth)(48) */ + XMEMCPY(payload, psk, 64); + rc = wc_InitSha384(&sha); + if (rc == 0) rc = wc_Sha384Update(&sha, clearAuth, clearAuthSz); + if (rc == 0) rc = wc_Sha384Final(&sha, payload + 64); + wc_Sha384Free(&sha); + XMEMSET(psk, 0, sizeof(psk)); + if (rc != 0) { + printf(" SHA-384 failed: %d\n", rc); + XMEMSET(payload, 0, sizeof(payload)); + return rc; + } + + printf(" ClearAuthDigest = SHA-384(%u bytes ClearAuth)\n", clearAuthSz); + + /* PSK_SET is a vendor-defined SPDM command — needs GET_VERSION first */ + rc = wolfSPDM_GetVersion(dev->spdmCtx->spdmCtx); + if (rc != 0) { + printf(" GET_VERSION failed: %d\n", rc); + XMEMSET(payload, 0, sizeof(payload)); + return rc; + } + + rc = wolfTPM2_SpdmNationsPskSet(dev, payload, sizeof(payload)); + XMEMSET(payload, 0, sizeof(payload)); + if (rc == 0) + printf(" PSK provisioned (64-byte PSK + 48-byte digest)\n"); + else + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; +} + +static int demo_nations_psk_clear(WOLFTPM2_DEV* dev, const char* authHex) +{ + int rc; + byte clearAuth[256]; + word32 clearAuthSz = sizeof(clearAuth); + + printf("\n=== Nations PSK Clear ===\n"); + + if (authHex == NULL) { + printf(" Error: --psk-clear requires ClearAuth hex argument\n"); + return BAD_FUNC_ARG; + } + rc = hex2bin(authHex, clearAuth, &clearAuthSz); + if (rc != 0 || clearAuthSz != 32) { + printf(" Error: ClearAuth must be exactly 32 bytes, got %u\n", + clearAuthSz); + return BAD_FUNC_ARG; + } + /* PSK_CLEAR: sends raw 32-byte ClearAuth. TPM computes SHA-384 + * internally and compares against stored ClearAuthDigest. */ + rc = wolfSPDM_Nations_PskClearWithVCA(dev->spdmCtx->spdmCtx, + clearAuth, clearAuthSz); + XMEMSET(clearAuth, 0, sizeof(clearAuth)); + if (rc == 0) + printf(" PSK cleared\n"); + else + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; +} + +static int demo_nations_identity_key_set(WOLFTPM2_DEV* dev, int set) +{ + int rc; + printf("\n=== Nations Identity Key %s ===\n", set ? "Set" : "Unset"); + rc = wolfTPM2_SpdmNationsIdentityKeySet(dev, set); + if (rc == 0) { + printf(" Identity key %s\n", set ? "provisioned" : "un-provisioned"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_nations_get_pubkey(WOLFTPM2_DEV* dev) +{ + int rc; + byte pubKey[128]; + word32 pubKeySz = sizeof(pubKey); + word32 i; + + printf("\n=== Get SPDM-Identity Public Key ===\n"); + + /* GET_PUBK is a vendor SPDM command — needs GET_VERSION first */ + rc = wolfSPDM_GetVersion(dev->spdmCtx->spdmCtx); + if (rc != 0) { + printf(" GET_VERSION failed: %d\n", rc); + return rc; + } + + rc = wolfTPM2_SpdmGetPubKey(dev, pubKey, &pubKeySz); + if (rc == 0) { + printf(" Got %d bytes: ", (int)pubKeySz); + for (i = 0; i < pubKeySz && i < 32; i++) printf("%02x", pubKey[i]); + if (pubKeySz > 32) printf("..."); + printf("\n"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +static int demo_nations_caps184(WOLFTPM2_DEV* dev) +{ + int rc; + GetCapability_In capIn; + GetCapability_Out capOut; + word32 i; + (void)dev; + + printf("\n=== Nations TPM 184 Capabilities ===\n"); + + /* 1. Vendor properties (identity key status, FIPS mode, etc.) */ + printf(" Vendor Properties (TPM_CAP_VENDOR_PROPERTY):\n"); + XMEMSET(&capIn, 0, sizeof(capIn)); + capIn.capability = TPM_CAP_VENDOR_PROPERTY; + capIn.property = 11; /* FIPS_SL2_MODE */ + capIn.propertyCount = 2; /* Read props 11 and 12 */ + XMEMSET(&capOut, 0, sizeof(capOut)); + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc == 0) { + /* Vendor props are raw UINT32 values, not tagged pairs. + * With count=2 starting at prop 11, we get props 11 and 12 */ + byte* raw = (byte*)&capOut.capabilityData.data.tpmProperties; + printf(" Raw response: "); + for (i = 0; i < 16 && i < sizeof(capOut.capabilityData); i++) + printf("%02x", raw[i]); + printf("\n"); + } else { + printf(" Failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + + /* 2. TPM_CAP_PUB_KEYS (TPM 184: SPDM identity keys) */ + printf(" SPDM Public Keys (TPM_CAP_PUB_KEYS):\n"); + XMEMSET(&capIn, 0, sizeof(capIn)); + capIn.capability = TPM_CAP_PUB_KEYS; + capIn.property = 0; + capIn.propertyCount = 1; + XMEMSET(&capOut, 0, sizeof(capOut)); + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc == 0) { + byte* raw = (byte*)&capOut.capabilityData; + word32 rawSz = sizeof(capOut.capabilityData); + printf(" Response (%u bytes): ", rawSz); + for (i = 0; i < rawSz && i < 64; i++) + printf("%02x", raw[i]); + if (rawSz > 64) printf("..."); + printf("\n"); + } else if (rc == TPM_RC_VALUE) { + printf(" Not supported (TPM_RC_VALUE)\n"); + } else { + printf(" Failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + + /* 3. TPM_CAP_SPDM_SESSION_INFO (TPM 184: SPDM session state) */ + printf(" SPDM Session Info (TPM_CAP_SPDM_SESSION_INFO):\n"); + XMEMSET(&capIn, 0, sizeof(capIn)); + capIn.capability = TPM_CAP_SPDM_SESSION_INFO; + capIn.property = 0; + capIn.propertyCount = 1; + XMEMSET(&capOut, 0, sizeof(capOut)); + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc == 0) { + byte* raw = (byte*)&capOut.capabilityData; + word32 rawSz = sizeof(capOut.capabilityData); + printf(" Response (%u bytes): ", rawSz); + for (i = 0; i < rawSz && i < 64; i++) + printf("%02x", raw[i]); + if (rawSz > 64) printf("..."); + printf("\n"); + } else if (rc == TPM_RC_VALUE) { + printf(" Not supported (TPM_RC_VALUE)\n"); + } else { + printf(" Failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + + return 0; +} + +static int demo_nations_connect(WOLFTPM2_DEV* dev) +{ + int rc; + + printf("\n=== SPDM Connect (Nations) ===\n"); + if (wolfTPM2_SpdmIsConnected(dev)) { + printf(" Already connected (SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + return 0; + } + + printf(" Handshake: VERSION -> GET_PUBK -> KEY_EXCHANGE -> " + "GIVE_PUB -> FINISH\n"); + rc = wolfTPM2_SpdmConnectNations(dev, NULL, 0, NULL, 0); + if (rc == 0) { + printf(" Session established (AES-256-GCM, SessionID: 0x%08x)\n", + wolfTPM2_SpdmGetSessionId(dev)); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} +#endif /* WOLFSPDM_NATIONS */ + +int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) +{ + int rc, i; + WOLFTPM2_DEV dev; + + if (argc <= 1) { usage(); return 0; } + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "-h") == 0 || XSTRCMP(argv[i], "--help") == 0) { + usage(); return 0; + } + } + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + if (rc != 0) { + printf("wolfTPM2_Init failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; + } + + rc = wolfTPM2_SpdmInit(&dev); + if (rc != 0) { + printf("wolfTPM2_SpdmInit failed: %s\n", TPM2_GetRCString(rc)); + wolfTPM2_Cleanup(&dev); + return rc; + } + +#ifdef WOLFSPDM_NUVOTON + wolfTPM2_SpdmSetNuvotonMode(&dev); + wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); +#elif defined(WOLFSPDM_NATIONS) + wolfTPM2_SpdmSetNationsMode(&dev); + wolfTPM2_SPDM_SetTisIO(dev.spdmCtx); +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(dev.spdmCtx->spdmCtx, 1); +#endif +#endif + + for (i = 1; i < argc; i++) { +#ifdef WOLFSPDM_NUVOTON + if (XSTRCMP(argv[i], "--enable") == 0) + rc = demo_enable(&dev); + else if (XSTRCMP(argv[i], "--disable") == 0) + rc = demo_disable(&dev); + else if (XSTRCMP(argv[i], "--status") == 0) + rc = demo_status(&dev); + else if (XSTRCMP(argv[i], "--get-pubkey") == 0) + rc = demo_get_pubkey(&dev); + else if (XSTRCMP(argv[i], "--connect") == 0) + rc = demo_connect(&dev); + else if (XSTRCMP(argv[i], "--lock") == 0) + rc = demo_lock(&dev, 1); + else if (XSTRCMP(argv[i], "--unlock") == 0) + rc = demo_lock(&dev, 0); + else +#endif +#ifdef WOLFSPDM_NATIONS + if (XSTRCMP(argv[i], "--identity-key-set") == 0) + rc = demo_nations_identity_key_set(&dev, 1); + else if (XSTRCMP(argv[i], "--identity-key-unset") == 0) + rc = demo_nations_identity_key_set(&dev, 0); + else if (XSTRCMP(argv[i], "--get-pubkey") == 0) + rc = demo_nations_get_pubkey(&dev); + else if (XSTRCMP(argv[i], "--connect") == 0) + rc = demo_nations_connect(&dev); + else if (XSTRCMP(argv[i], "--status") == 0) + rc = demo_nations_status(&dev); + else if (XSTRCMP(argv[i], "--psk") == 0 && i + 1 < argc) + rc = demo_nations_psk_connect(&dev, argv[++i]); + else if (XSTRCMP(argv[i], "--psk-set") == 0 && i + 2 < argc) + { + const char* pskArg = argv[++i]; + const char* authArg = argv[++i]; + rc = demo_nations_psk_set(&dev, pskArg, authArg); + } + else if (XSTRCMP(argv[i], "--psk-clear") == 0 && i + 1 < argc) + rc = demo_nations_psk_clear(&dev, argv[++i]); + else if (XSTRCMP(argv[i], "--lock") == 0) + rc = wolfTPM2_SpdmNationsSetOnlyMode(&dev, 1); + else if (XSTRCMP(argv[i], "--unlock") == 0) + rc = wolfTPM2_SpdmNationsSetOnlyMode(&dev, 0); + else if (XSTRCMP(argv[i], "--tpm-clear") == 0) { + printf("\n=== TPM2_Clear ===\n"); + rc = wolfTPM2_Clear(&dev); + printf(" %s (rc=0x%x)\n", rc == 0 ? "Success" : "FAILED", rc); + } + else if (XSTRCMP(argv[i], "--caps184") == 0) + rc = demo_nations_caps184(&dev); + else +#endif + { printf("Unknown option: %s\n", argv[i]); usage(); rc = BAD_FUNC_ARG; } + if (rc != 0) break; + } + + wolfTPM2_Cleanup(&dev); /* Shutdown goes through SPDM if session active */ + wolfTPM2_SpdmCleanup(&dev); + return rc; +} + +#ifndef NO_MAIN_DRIVER +int main(int argc, char *argv[]) +{ + int rc = -1; +#ifndef WOLFTPM2_NO_WRAPPER + rc = TPM2_SPDM_Demo(NULL, argc, argv); +#else + printf("Wrapper code not compiled in\n"); + (void)argc; (void)argv; +#endif + return (rc == 0) ? 0 : 1; +} +#endif + +#endif /* WOLFTPM_SPDM */ +#endif /* !WOLFTPM2_NO_WRAPPER */ diff --git a/examples/spdm/spdm_test.sh b/examples/spdm/spdm_test.sh new file mode 100755 index 00000000..889c3092 --- /dev/null +++ b/examples/spdm/spdm_test.sh @@ -0,0 +1,188 @@ +#!/bin/bash +# spdm_test.sh - SPDM hardware tests (Nuvoton / Nations Technology) +# +# Copyright (C) 2006-2025 wolfSSL Inc. +# +# This file is part of wolfTPM. +# +# wolfTPM is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# wolfTPM is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + +SPDM_DEMO="${1:-./examples/spdm/spdm_demo}" +CAPS_DEMO="./examples/wrap/caps" +UNIT_TEST="./tests/unit.test" +GPIO_CHIP="gpiochip0" +GPIO_PIN="4" +VENDOR="${2:-nuvoton}" # "nuvoton", "nations", or "nations-psk" +PASS=0 FAIL=0 TOTAL=0 + +# Nations PSK test data (from Vision/NSING reference PSK_DEMO_3) +# PSK: 64 bytes (used as IKM in HKDF-Extract during PSK_EXCHANGE) +NATIONS_PSK="dbc2192291d807742441b963f6712841f7697e2e39c45931f3abc53658c8b9338bd3561cab5d90cf9e493295bb5bd6b2c455e0fd19392e0ce4f3433cbcfc7047" +# ClearAuth: exactly 32 bytes (first 32 bytes of PSK per NSING convention) +# PSK_SET sends SHA-384(ClearAuth) as the 48-byte ClearAuthDigest +# PSK_CLEAR sends raw ClearAuth; TPM verifies SHA-384 match internally +NATIONS_CLEARAUTH="dbc2192291d807742441b963f6712841f7697e2e39c45931f3abc53658c8b933" + +if [ -t 1 ]; then + GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[0;33m' NC='\033[0m' +else + GREEN='' RED='' YELLOW='' NC='' +fi + +gpio_reset() { + gpioset "$GPIO_CHIP" "$GPIO_PIN=0" 2>/dev/null + sleep 0.1 + gpioset "$GPIO_CHIP" "$GPIO_PIN=1" 2>/dev/null + sleep 2 +} + +run_test() { + local name="$1"; shift + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + gpio_reset + if "$@"; then + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) + fi + echo "" +} + +# run_test_no_reset: Same as run_test but skip GPIO reset (for back-to-back commands) +run_test_no_reset() { + local name="$1"; shift + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + if "$@"; then + echo -e " ${GREEN}PASS${NC}"; PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}"; FAIL=$((FAIL + 1)) + fi + echo "" +} + +if [ ! -x "$SPDM_DEMO" ]; then + echo "Error: $SPDM_DEMO not found." + echo "Usage: $0 [path-to-spdm_demo] [nuvoton|nations|nations-psk]" + exit 1 +fi + +echo "=== SPDM Hardware Tests ($VENDOR) ===" +echo "Demo: $SPDM_DEMO Caps: $CAPS_DEMO Unit: $UNIT_TEST" +echo "" + +if [ "$VENDOR" = "nuvoton" ]; then + # Nuvoton test flow (identity key mode) + run_test "SPDM status query" "$SPDM_DEMO" --status + run_test "SPDM session connect" "$SPDM_DEMO" --connect + run_test "Lock SPDM-only mode" "$SPDM_DEMO" --connect --lock + + if [ -x "$UNIT_TEST" ]; then + run_test "Unit test over SPDM" "$UNIT_TEST" + else + echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" + fi + + run_test "Unlock SPDM-only mode" "$SPDM_DEMO" --connect --unlock + + if [ -x "$CAPS_DEMO" ]; then + run_test "Cleartext caps (no SPDM)" "$CAPS_DEMO" + else + echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" + fi + +elif [ "$VENDOR" = "nations" ]; then + # Nations NS350 identity key mode — full lifecycle test + # Note: GPIO 4 is NOT wired to TPM_RST on NS350 daughter boards. + + run_test_no_reset "Unset identity key" "$SPDM_DEMO" --identity-key-unset + run_test_no_reset "Set identity key" "$SPDM_DEMO" --identity-key-set + run_test_no_reset "SPDM session connect" "$SPDM_DEMO" --connect + run_test_no_reset "Status query" "$SPDM_DEMO" --status + + if [ -x "$CAPS_DEMO" ]; then + run_test_no_reset "Cleartext caps (no SPDM)" "$CAPS_DEMO" + else + echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" + fi + +elif [ "$VENDOR" = "nations-psk" ]; then + # Nations NS350 PSK mode — full lifecycle test + # + # PSK and identity key are mutually exclusive on NS350. + # Flow: unset identity key → PSK_SET → PSK connect → status → + # PSK_CLEAR → re-provision → re-connect → final clear → + # restore identity key → cleartext caps + # + # Uses NSING reference test data (PSK_DEMO_3 from Vision's traces). + # ClearAuth is always exactly 32 bytes per TCG spec. + + # Note: GPIO 4 is NOT wired to TPM_RST on NS350 daughter boards. + # Use run_test_no_reset instead of run_test. + + # Step 1: Ensure identity key is unset (required for PSK mode) + run_test_no_reset "Unset identity key" "$SPDM_DEMO" --identity-key-unset + + # Step 2: Provision PSK (PSK_SET_ vendor command) + # Sends PSK(64) + SHA-384(ClearAuth)(48) = 112 bytes + run_test_no_reset "PSK provision (PSK_SET)" "$SPDM_DEMO" --psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH" + + # Step 3: Status check (should show PSK provisioned) + run_test_no_reset "Status (PSK provisioned)" "$SPDM_DEMO" --status + + # Step 4: PSK connect (VCA → PSK_EXCHANGE → PSK_FINISH) + run_test_no_reset "PSK session connect" "$SPDM_DEMO" --psk "$NATIONS_PSK" + + # Step 5: PSK connect again (verify repeatable sessions) + run_test_no_reset "PSK session connect (repeat)" "$SPDM_DEMO" --psk "$NATIONS_PSK" + + # Step 6: PSK_CLEAR (sends raw 32-byte ClearAuth, TPM verifies SHA-384) + run_test_no_reset "PSK clear (PSK_CLEAR)" "$SPDM_DEMO" --psk-clear "$NATIONS_CLEARAUTH" + + # Step 7: Status check (should show PSK not provisioned) + run_test_no_reset "Status (PSK cleared)" "$SPDM_DEMO" --status + + # Step 8: Re-provision PSK (verify PSK_SET works after clear) + run_test_no_reset "PSK re-provision (PSK_SET)" "$SPDM_DEMO" --psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH" + + # Step 9: PSK connect after re-provision + run_test_no_reset "PSK session connect (after re-provision)" "$SPDM_DEMO" --psk "$NATIONS_PSK" + + # Step 10: Final PSK_CLEAR (leave module in clean state) + run_test_no_reset "Final PSK clear" "$SPDM_DEMO" --psk-clear "$NATIONS_CLEARAUTH" + + # Step 11: Restore identity key (factory default) + run_test_no_reset "Restore identity key" "$SPDM_DEMO" --identity-key-set + + # Step 12: Cleartext TPM commands (verify module works normally) + if [ -x "$CAPS_DEMO" ]; then + run_test_no_reset "Cleartext caps (no SPDM)" "$CAPS_DEMO" + else + echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}" + fi + +else + echo "Error: Unknown vendor '$VENDOR'. Use 'nuvoton', 'nations', or 'nations-psk'." + exit 1 +fi + +echo "" +echo "=== Results: $TOTAL total, $PASS passed, $FAIL failed ===" +if [ $FAIL -eq 0 ]; then + echo -e "${GREEN}ALL TESTS PASSED${NC}"; exit 0 +else + echo -e "${RED}$FAIL TEST(S) FAILED${NC}"; exit 1 +fi diff --git a/hal/tpm_io_linux.c b/hal/tpm_io_linux.c index bb0e466f..cffefb78 100644 --- a/hal/tpm_io_linux.c +++ b/hal/tpm_io_linux.c @@ -83,6 +83,9 @@ #elif defined(WOLFTPM_NUVOTON) /* Nuvoton NPCT75x uses CE0 */ #define TPM2_SPI_DEV_CS "0" + #elif defined(WOLFTPM_NATIONS) + /* Nations Technology NS350 uses CE0 */ + #define TPM2_SPI_DEV_CS "0" #else /* OPTIGA SLB9670/SLB9762 and LetsTrust TPM use CE1 */ #define TPM2_SPI_DEV_CS "1" diff --git a/spdm/README.md b/spdm/README.md new file mode 100644 index 00000000..4ccd7ca5 --- /dev/null +++ b/spdm/README.md @@ -0,0 +1,473 @@ +# wolfTPM SPDM + +wolfTPM includes built-in SPDM support for Nuvoton NPCT75x and Nations NS350 +TPMs using wolfSSL/wolfCrypt. Both vendors support identity key mode (ECDHE +P-384) for session establishment. The Nations NS350 additionally supports PSK +(pre-shared key) mode. Once a session is established, all TPM commands and +responses are encrypted with AES-256-GCM over the existing SPI/I2C bus — no +application code changes needed. + +For standard SPDM protocol testing with the DMTF spdm-emu emulator, see the +[wolfSPDM](https://github.com/aidangarske/wolfSPDM) standalone library. + +## Quick Start + +### Nuvoton NPCT75x + +```bash +# Build wolfSSL +pushd ../wolfssl && ./autogen.sh && \ +./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp && \ +make && sudo make install && sudo ldconfig && popd + +# Build wolfTPM +./autogen.sh && ./configure --enable-spdm --enable-nuvoton && make + +# Enable SPDM (one-time), reset, connect +./examples/spdm/spdm_demo --enable +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect +``` + +See [Building](#building) and [Nuvoton NPCT75x Details](#nuvoton-npct75x) for +full instructions. + +### Nations NS350 + +```bash +# Build wolfSSL (note: --enable-ecccustcurves=all required for Nations) +pushd ../wolfssl && ./autogen.sh && \ +./configure --enable-wolftpm --enable-ecc --enable-sha384 --enable-aesgcm --enable-hkdf --enable-sp --enable-ecccustcurves=all && \ +make && sudo make install && sudo ldconfig && popd + +# Build wolfTPM +./autogen.sh && ./configure --enable-spdm --enable-nations && make + +# Connect (identity key is factory default) +./examples/spdm/spdm_demo --connect +``` + +See [Building](#building) and [Nations NS350 Details](#nations-ns350) for full +instructions. + +## How It Works + +SPDM (Security Protocol and Data Model) establishes an authenticated encrypted +channel over the existing SPI/I2C bus. The implementation uses Algorithm Set B: +ECDH P-384 / SHA-384 / AES-256-GCM. Two session establishment modes are +supported. + +### Protocol Flow: Identity Key Mode (Nuvoton + Nations) + +``` +Host TPM (Nuvoton NPCT75x / Nations NS350) + | | + |--- GET_VERSION ------------------>| (negotiate SPDM version) + |<-- VERSION -----------------------| + | | + |--- GET_PUB_KEY ------------------>| (get TPM's P-384 identity key) + |<-- PUB_KEY_RSP -------------------| + | | + |--- KEY_EXCHANGE ----------------->| (ECDHE P-384 key agreement) + |<-- KEY_EXCHANGE_RSP --------------| (+ HMAC proof of shared secret) + | | + | --- Handshake keys derived --- | + | | + |=== GIVE_PUB_KEY =================>| (encrypted: host's P-384 key) + |<== GIVE_PUB_KEY_RSP ==============| + | | + |=== FINISH =======================>| (encrypted: signature + HMAC) + |<== FINISH_RSP ====================| + | | + | --- App data keys derived --- | + | | + |=== TPM2_CMD (AES-256-GCM) =======>| (every command encrypted) + |<== TPM2_RSP (AES-256-GCM) ========| +``` + +The handshake uses ECDH P-384 for key agreement and HMAC-SHA384 for +authentication. After the handshake, all TPM commands are wrapped in SPDM +`VENDOR_DEFINED_REQUEST("TPM2_CMD")` messages and encrypted with AES-256-GCM. +A sequence number increments with each message to prevent replay attacks. + +### Protocol Flow: PSK Mode (Nations Only) + +PSK mode replaces the ECDHE key exchange with a symmetric pre-shared key. +The same AES-256-GCM encryption is used for data transport. + +``` +Host TPM (Nations NS350) + | | + |--- GET_VERSION ------------------>| (negotiate SPDM version) + |<-- VERSION -----------------------| + | | + |--- GET_CAPABILITIES ------------->| (capability exchange) + |<-- CAPABILITIES ------------------| + | | + |--- NEGOTIATE_ALGORITHMS --------->| (Algorithm Set B: P-384/SHA-384) + |<-- ALGORITHMS --------------------| + | | + |--- PSK_EXCHANGE ----------------->| (session key from PSK) + |<-- PSK_EXCHANGE_RSP --------------| (+ HMAC proof) + | | + | --- Handshake keys derived --- | (Salt_0 = 0xFF * H for PSK mode) + | | + |=== PSK_FINISH ===================>| (encrypted: requester HMAC) + |<== PSK_FINISH_RSP ================| + | | + | --- App data keys derived --- | + | | + |=== TPM2_CMD (AES-256-GCM) =======>| (every command encrypted) + |<== TPM2_RSP (AES-256-GCM) ========| +``` + +PSK and identity key modes are mutually exclusive on the NS350. The identity +key is provisioned by factory default; it must be unset before PSK can be used. +See [PSK Lifecycle (Nations)](#psk-lifecycle-nations). + +### SPDM-Only Mode (Encrypted Bus Enforcement) + +SPDM-only mode forces all TPM commands through the encrypted SPDM channel. +Both vendors support this. The typical lifecycle: + +``` +1. Enable SPDM (one-time, persists across resets) +2. Connect (handshake, derives session keys) +3. Lock SPDM-only (TPM rejects all cleartext commands) +4. Reset (TPM enters SPDM-only enforcement) +5. Run any commands (each auto-establishes SPDM, all AES-256-GCM encrypted) +6. Unlock (connect + unlock in one session) +7. Reset (TPM back to normal cleartext mode) +``` + +Step 5 is fully automatic. When wolfTPM detects SPDM-only mode (TPM2_Startup +returns `TPM_RC_DISABLED`), it transparently establishes an SPDM session. +Existing applications like `caps`, `wrap_test`, and `unit.test` work without +modification — all commands are encrypted over the bus. See +[How Auto-SPDM Works](#how-auto-spdm-works) for details. + +**Reset method differs by vendor:** +- **Nuvoton:** GPIO reset — `gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2` +- **Nations:** Full power cycle required (GPIO 4 is not wired to TPM_RST on NS350 daughter boards) + +## Building + +### wolfSSL + +```bash +pushd ../wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-ecc --enable-sha384 \ + --enable-aesgcm --enable-hkdf --enable-sp \ + --enable-ecccustcurves=all # <-- required for Nations NS350 only +make +sudo make install && sudo ldconfig +popd +``` + +| Vendor | Extra wolfSSL flags | +|--------|---------------------| +| Nuvoton NPCT75x | (none) | +| Nations NS350 | `--enable-ecccustcurves=all` | + +### wolfTPM + +```bash +./autogen.sh +./configure --enable-spdm --enable-nuvoton # Nuvoton +# or +./configure --enable-spdm --enable-nations # Nations +make +``` + +### Configure Options + +| Option | Description | +|-----------------------------|-------------| +| `--enable-spdm` | Enable SPDM support (required) | +| `--enable-nuvoton` | Enable Nuvoton TPM hardware support | +| `--enable-nations` | Enable Nations NS350 hardware support | +| `--enable-debug` | Debug output with verbose SPDM tracing | +| `--enable-spdm-dynamic-mem` | Heap-allocated SPDM context (default: static ~32 KB) | + +## Usage + +### One-Time Setup + +#### Nuvoton + +```bash +# Enable SPDM on the TPM (persists across resets) +./examples/spdm/spdm_demo --enable + +# GPIO reset +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# Verify SPDM is enabled +./examples/spdm/spdm_demo --status +``` + +#### Nations + +Identity key mode is the factory default — no setup required. If previously +unset, restore with: + +```bash +./examples/spdm/spdm_demo --identity-key-set +``` + +### Establishing a Session + +#### Identity Key Mode (Both Vendors) + +```bash +# Get TPM's SPDM-Identity public key +./examples/spdm/spdm_demo --get-pubkey + +# Establish SPDM session (VERSION → GET_PUBK → KEY_EXCHANGE → GIVE_PUB → FINISH) +./examples/spdm/spdm_demo --connect + +# Query SPDM status +./examples/spdm/spdm_demo --status +``` + +#### PSK Mode (Nations) + +Requires PSK to be provisioned first. See +[PSK Lifecycle (Nations)](#psk-lifecycle-nations). + +```bash +# Establish PSK session (VERSION → CAPS → ALGO → PSK_EXCHANGE → PSK_FINISH) +./examples/spdm/spdm_demo --psk +``` + +### Lock/Unlock SPDM-Only Mode + +Lock requires an active SPDM session. After locking, a reset is required for +enforcement to take effect. + +**Nuvoton (identity key):** + +```bash +./examples/spdm/spdm_demo --connect --lock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 + +# TPM now requires SPDM — all commands auto-encrypted: +./examples/wrap/caps # auto-SPDM session, all AES-256-GCM +./tests/unit.test # full test suite over encrypted bus + +# Unlock +./examples/spdm/spdm_demo --connect --unlock +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +**Nations (identity key):** + +```bash +./examples/spdm/spdm_demo --connect --lock +# Power cycle required (unplug and re-plug Raspberry Pi) + +./examples/spdm/spdm_demo --connect --unlock +# Power cycle again +``` + +**Nations (PSK mode):** + +```bash +./examples/spdm/spdm_demo --psk --lock +# Power cycle required + +./examples/spdm/spdm_demo --psk --unlock +# Power cycle again +``` + +### PSK Lifecycle (Nations) + +PSK and identity key modes are mutually exclusive on the NS350. The identity key +is provisioned by default; it must be unset before PSK can be used. + +```bash +# 1. Unset identity key (enables PSK mode) +./examples/spdm/spdm_demo --identity-key-unset + +# 2. Provision PSK (64-byte PSK + 32-byte ClearAuth) +# The demo computes SHA-384(ClearAuth) and sends PSK(64)+Digest(48) = 112 bytes +./examples/spdm/spdm_demo --psk-set + +# 3. Establish PSK session +./examples/spdm/spdm_demo --psk + +# 4. Clear PSK (sends raw 32-byte ClearAuth; TPM verifies SHA-384 internally) +./examples/spdm/spdm_demo --psk-clear + +# 5. Restore identity key (factory default) +./examples/spdm/spdm_demo --identity-key-set +``` + +**Important:** The ClearAuth must be exactly 32 bytes. PSK_SET stores its SHA-384 +digest (48 bytes). PSK_CLEAR sends the raw 32 bytes and the TPM computes SHA-384 +to verify. Using the wrong size makes PSK_CLEAR impossible. + +### Running the Test Suite + +```bash +# Nuvoton (identity key — includes GPIO resets between tests) +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_demo nuvoton + +# Nations (identity key — no GPIO resets) +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_demo nations + +# Nations (PSK — full lifecycle: provision → connect → clear → restore) +./examples/spdm/spdm_test.sh ./examples/spdm/spdm_demo nations-psk +``` + +## TCG SPDM Vendor Commands + +Both Nuvoton and Nations TPMs implement the TCG "TPM Communication over SPDM +Secure Session" specification. These commands use 8-byte ASCII vendor codes in +SPDM `VENDOR_DEFINED_REQUEST` messages with `StandardID=0x0001` (TCG). + +| VdCode | Command | Vendor | Description | +|-------------|-----------------|---------|-------------| +| `GET_PUBK` | Get Public Key | Both | Get TPM's SPDM-Identity P-384 public key | +| `GIVE_PUB` | Give Public Key | Both | Send host's P-384 public key to TPM | +| `TPM2_CMD` | TPM Command | Both | Wrap TPM command in SPDM secured message | +| `GET_STS_` | Get Status | Both | Query SPDM status | +| `SPDMONLY` | SPDM-Only Mode | Both | Lock/unlock SPDM-only enforcement | +| `PSK_SET_` | PSK Set | Nations | Provision pre-shared key (64-byte PSK + SHA-384 digest) | +| `PSK_CLR_` | PSK Clear | Nations | Clear provisioned PSK (requires ClearAuth) | + +## Command Reference + +All `spdm_demo` options in one table: + +| Option | Vendor | Description | +|-------------------------------|---------|-------------| +| `--enable` | Nuvoton | Enable SPDM via NTC2_PreConfig (one-time, persists) | +| `--disable` | Nuvoton | Disable SPDM via NTC2_PreConfig | +| `--identity-key-set` | Nations | Provision SPDM identity key (factory default) | +| `--identity-key-unset` | Nations | Un-provision identity key (required before PSK) | +| `--get-pubkey` | Both | Get TPM's SPDM-Identity P-384 public key | +| `--connect` | Both | Establish identity key SPDM session | +| `--status` | Both | Query SPDM status | +| `--lock` | Both | Lock SPDM-only mode (requires active session) | +| `--unlock` | Both | Unlock SPDM-only mode (requires active session) | +| `--psk ` | Nations | Establish PSK session (64-byte PSK) | +| `--psk-set ` | Nations | Provision PSK (64-byte PSK, 32-byte ClearAuth) | +| `--psk-clear ` | Nations | Clear PSK (32-byte ClearAuth) | +| `--caps184` | Nations | Query TPM 184 vendor properties and SPDM session info | +| `--tpm-clear` | Nations | Send TPM2_Clear (platform auth) | + +## Vendor-Specific Details + +### Nuvoton NPCT75x + +**Enable/Disable:** SPDM is enabled via the `NTC2_PreConfig` vendor command +(`--enable` / `--disable`). This persists across resets. + +**GPIO Reset:** GPIO 4 is wired to TPM_RST on the Nuvoton daughter board. +A GPIO reset clears stale SPDM state: + +```bash +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +### Nations NS350 + +**Mode Switching:** Identity key and PSK modes are mutually exclusive. The +identity key is provisioned by factory default. Use `--identity-key-unset` +before provisioning PSK, and `--identity-key-set` to restore. + +**No GPIO Reset:** GPIO 4 is NOT wired to TPM_RST on the NS350 daughter board. +A full power cycle (unplug and re-plug the Raspberry Pi) is required to reset +the TPM. `sudo reboot` is not sufficient as the 3.3V rail stays powered. + +**Capabilities Query:** Use `--caps184` to query TPM 184 vendor properties +including SPDM session info. + +**ClearAuth:** Must be exactly 32 bytes. `PSK_SET` stores its SHA-384 digest +(48 bytes). `PSK_CLEAR` sends the raw 32 bytes and the TPM computes SHA-384 +to verify. + +**PSK Vendor Error Codes:** + +| Code | Name | Description | +|------|------|-------------| +| 0xA1 | Vd_PSKAlreadySet | PSK already provisioned (must PSK_CLEAR first) | +| 0xA2 | Vd_InternalFailure | SPDM session layer internal error | +| 0xA3 | Vd_PSKNotSet | No PSK provisioned | +| 0xA5 | Vd_AuthFail | ClearAuth SHA-384 doesn't match stored digest | + +## How Auto-SPDM Works + +When the TPM is in SPDM-only mode, `wolfTPM2_Init()` handles everything: + +1. `TPM2_Startup` is sent in cleartext — TPM returns `TPM_RC_DISABLED` +2. wolfTPM detects this and sets `spdmOnlyDetected` +3. An SPDM session is automatically established (P-384 keygen + handshake) +4. `TPM2_Startup` is retried over the encrypted channel — succeeds +5. All subsequent commands go through the SPDM encrypted channel + +Both `TPM2_SendCommand` (non-auth commands) and `TPM2_SendCommandAuth` +(auth-session commands like PCR operations, key creation, signing) are +intercepted and routed through SPDM when a session is active. + +## Memory Modes + +**Static (default):** Zero heap allocation. SPDM context uses ~32 KB of +static memory, ideal for embedded environments. + +**Dynamic (`--enable-spdm-dynamic-mem`):** Context is heap-allocated. +Useful on platforms with small stacks. + +## wolfSPDM API + +| Function | Description | +|------------------------------|-------------| +| `wolfSPDM_InitStatic()` | Initialize context in caller-provided buffer (static mode) | +| `wolfSPDM_New()` | Allocate and initialize context on heap (dynamic mode) | +| `wolfSPDM_Init()` | Initialize a pre-allocated context | +| `wolfSPDM_Free()` | Free context (releases resources; frees heap only if dynamic) | +| `wolfSPDM_GetCtxSize()` | Return `sizeof(WOLFSPDM_CTX)` at runtime | +| `wolfSPDM_SetIO()` | Set transport I/O callback | +| `wolfSPDM_SetDebug()` | Enable/disable debug output | +| `wolfSPDM_Connect()` | Full SPDM handshake | +| `wolfSPDM_IsConnected()` | Check session status | +| `wolfSPDM_Disconnect()` | End session | +| `wolfSPDM_SecuredExchange()` | Encrypt/send/receive/decrypt in one call | + +## Troubleshooting + +### SPDM handshake fails after interrupted session + +**Nuvoton:** GPIO 4 is wired to TPM_RST on the Nuvoton daughter board. +A GPIO reset clears stale SPDM state: + +```bash +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +**Nations NS350:** GPIO 4 is NOT wired to TPM_RST on the NS350 daughter board. +A full power cycle (unplug and re-plug the Raspberry Pi) is required to reset +the TPM. `sudo reboot` is not sufficient as the 3.3V rail stays powered. + +### SPDM Error Codes + +| Code | Name | Description | +|------|------|-------------| +| 0x01 | InvalidRequest | Message format incorrect | +| 0x04 | UnexpectedRequest | Message out of sequence | +| 0x05 | DecryptError | Decryption or MAC verification failed | +| 0x06 | UnsupportedRequest | Request not supported or format rejected | +| 0x41 | VersionMismatch | SPDM version mismatch | + +## Standard SPDM Support + +For standard SPDM protocol support including session establishment with the +DMTF spdm-emu emulator, measurements, challenge authentication, heartbeat, +and key update, see the [wolfSPDM](https://github.com/aidangarske/wolfSPDM) +standalone library. + +## License + +GPLv3 — see LICENSE file. Copyright (C) 2006-2025 wolfSSL Inc. diff --git a/spdm/include.am b/spdm/include.am new file mode 100644 index 00000000..0c971b28 --- /dev/null +++ b/spdm/include.am @@ -0,0 +1,44 @@ +# 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 + +# spdm_nuvoton.c contains shared TCG code used by both Nuvoton and Nations +if BUILD_NUVOTON +if !BUILD_NATIONS +src_libwolftpm_la_SOURCES += spdm/src/spdm_nuvoton.c +endif +endif + +if BUILD_NATIONS +src_libwolftpm_la_SOURCES += spdm/src/spdm_nuvoton.c +src_libwolftpm_la_SOURCES += spdm/src/spdm_nations.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 \ + spdm/wolfspdm/spdm_nations.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..f96423b2 --- /dev/null +++ b/spdm/src/spdm_context.c @@ -0,0 +1,578 @@ +/* spdm_context.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include "spdm_internal.h" +#include +#include + +/* ----- Context Management ----- */ + +int wolfSPDM_Init(WOLFSPDM_CTX* ctx) +{ + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Clean slate, dont read fields before this */ + XMEMSET(ctx, 0, sizeof(WOLFSPDM_CTX)); + ctx->state = WOLFSPDM_STATE_INIT; + + /* Initialize RNG */ + rc = wc_InitRng(&ctx->rng); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + ctx->flags.rngInitialized = 1; + + /* Set default session ID (0x0001 is valid; 0x0000/0xFFFF are reserved) */ + ctx->reqSessionId = 0x0001; + + ctx->flags.initialized = 1; + /* isDynamic remains 0, only wolfSPDM_New sets it */ + + return WOLFSPDM_SUCCESS; +} + +#ifdef WOLFSPDM_DYNAMIC_MEMORY +WOLFSPDM_CTX* wolfSPDM_New(void) +{ + WOLFSPDM_CTX* ctx; + + ctx = (WOLFSPDM_CTX*)XMALLOC(sizeof(WOLFSPDM_CTX), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (ctx == NULL) { + return NULL; + } + + if (wolfSPDM_Init(ctx) != WOLFSPDM_SUCCESS) { + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return NULL; + } + ctx->flags.isDynamic = 1; /* Tag AFTER Init so it isn't wiped */ + + return ctx; +} +#endif /* WOLFSPDM_DYNAMIC_MEMORY */ + +void wolfSPDM_Free(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return; + } + +#ifdef WOLFSPDM_DYNAMIC_MEMORY + { + int wasDynamic = ctx->flags.isDynamic; +#endif + + /* Free RNG */ + if (ctx->flags.rngInitialized) { + wc_FreeRng(&ctx->rng); + } + + /* Free ephemeral key */ + if (ctx->flags.ephemeralKeyInit) { + wc_ecc_free(&ctx->ephemeralKey); + } + + /* Zero entire struct (covers all sensitive key material) */ + wc_ForceZero(ctx, sizeof(WOLFSPDM_CTX)); + +#ifdef WOLFSPDM_DYNAMIC_MEMORY + if (wasDynamic) { + XFREE(ctx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } + } +#endif +} + +int wolfSPDM_GetCtxSize(void) +{ + return (int)sizeof(WOLFSPDM_CTX); +} + +int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (size < (int)sizeof(WOLFSPDM_CTX)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + return wolfSPDM_Init(ctx); +} + +/* ----- Configuration ----- */ + +int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx) +{ + if (ctx == NULL || ioCb == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + ctx->ioCb = ioCb; + ctx->ioUserCtx = userCtx; + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz) +{ + if (ctx == NULL || pubKey == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (pubKeySz != WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMCPY(ctx->rspPubKey, pubKey, pubKeySz); + ctx->rspPubKeyLen = pubKeySz; + ctx->flags.hasRspPubKey = 1; + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, + const byte* privKey, word32 privKeySz, + const byte* pubKey, word32 pubKeySz) +{ + if (ctx == NULL || privKey == NULL || pubKey == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (privKeySz != WOLFSPDM_ECC_KEY_SIZE || + pubKeySz != WOLFSPDM_ECC_POINT_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMCPY(ctx->reqPrivKey, privKey, privKeySz); + ctx->reqPrivKeyLen = privKeySz; + XMEMCPY(ctx->reqPubKey, pubKey, pubKeySz); + ctx->flags.hasReqKeyPair = 1; + + return WOLFSPDM_SUCCESS; +} + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +int wolfSPDM_SetRequesterKeyTPMT(WOLFSPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz) +{ + if (ctx == NULL || tpmtPub == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (tpmtPubSz > sizeof(ctx->reqPubKeyTPMT)) { + return WOLFSPDM_E_INVALID_ARG; + } + XMEMCPY(ctx->reqPubKeyTPMT, tpmtPub, tpmtPubSz); + ctx->reqPubKeyTPMTLen = tpmtPubSz; + return WOLFSPDM_SUCCESS; +} +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#ifdef WOLFSPDM_NATIONS +int wolfSPDM_SetPSK(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz) +{ + if (ctx == NULL || psk == NULL || pskSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + if (pskSz > WOLFSPDM_PSK_MAX_SIZE) { + return WOLFSPDM_E_INVALID_ARG; + } + if (hint != NULL && hintSz > WOLFSPDM_PSK_HINT_MAX) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMCPY(ctx->psk, psk, pskSz); + ctx->pskSz = pskSz; + + if (hint != NULL && hintSz > 0) { + XMEMCPY(ctx->pskHint, hint, hintSz); + ctx->pskHintSz = hintSz; + } else { + XMEMSET(ctx->pskHint, 0, sizeof(ctx->pskHint)); + ctx->pskHintSz = 0; + } + + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_NATIONS */ + +void wolfSPDM_SetDebug(WOLFSPDM_CTX* ctx, int enable) +{ + if (ctx != NULL) { + ctx->flags.debug = (enable != 0); + } +} + +int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + +#ifdef WOLFSPDM_NUVOTON + if (mode == WOLFSPDM_MODE_NUVOTON) { + ctx->mode = WOLFSPDM_MODE_NUVOTON; + ctx->connectionHandle = WOLFSPDM_NUVOTON_CONN_HANDLE_DEFAULT; + ctx->fipsIndicator = WOLFSPDM_NUVOTON_FIPS_DEFAULT; + return WOLFSPDM_SUCCESS; + } +#endif +#ifdef WOLFSPDM_NATIONS + if (mode == WOLFSPDM_MODE_NATIONS) { + ctx->mode = WOLFSPDM_MODE_NATIONS; + ctx->connectionHandle = 0; + /* Default to NON_FIPS; overridden by auto-detect if FIPS configured */ + ctx->fipsIndicator = WOLFSPDM_FIPS_NON_FIPS; + return WOLFSPDM_SUCCESS; + } + if (mode == WOLFSPDM_MODE_NATIONS_PSK) { + ctx->mode = WOLFSPDM_MODE_NATIONS_PSK; + ctx->connectionHandle = 0; + ctx->fipsIndicator = WOLFSPDM_FIPS_NON_FIPS; + return WOLFSPDM_SUCCESS; + } +#endif + + return WOLFSPDM_E_INVALID_ARG; /* Unsupported mode */ +} + +WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return (WOLFSPDM_MODE)0; + } + return ctx->mode; +} + +/* ----- Session Status ----- */ + +int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return (ctx->state == WOLFSPDM_STATE_CONNECTED) ? 1 : 0; +} + +word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->state != WOLFSPDM_STATE_CONNECTED) { + return 0; + } + return ctx->sessionId; +} + +byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->state < WOLFSPDM_STATE_VERSION) { + return 0; + } + return ctx->spdmVersion; +} + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return ctx->connectionHandle; +} + +word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return ctx->fipsIndicator; +} +#endif + +/* ----- Session Establishment - Connect (Full Handshake) ----- */ + +int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) +{ + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS) { + return wolfSPDM_ConnectTCG(ctx); + } +#endif +#ifdef WOLFSPDM_NATIONS + if (ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + return wolfSPDM_ConnectNationsPsk(ctx); + } +#endif + + return WOLFSPDM_E_INVALID_ARG; /* Standard mode not available */ +} + +int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx) +{ + int rc; + byte txBuf[8]; + byte rxBuf[16]; /* END_SESSION_ACK: 4 bytes */ + word32 txSz, rxSz; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + /* Build END_SESSION */ + txSz = sizeof(txBuf); + rc = wolfSPDM_BuildEndSession(ctx, txBuf, &txSz); + if (rc == WOLFSPDM_SUCCESS) { + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SecuredExchange(ctx, txBuf, txSz, rxBuf, &rxSz); + } + + /* Reset state and zero ALL key material */ + ctx->state = WOLFSPDM_STATE_INIT; + ctx->sessionId = 0; + ctx->reqSeqNum = 0; + ctx->rspSeqNum = 0; + /* App data keys */ + wc_ForceZero(ctx->reqDataKey, sizeof(ctx->reqDataKey)); + wc_ForceZero(ctx->rspDataKey, sizeof(ctx->rspDataKey)); + wc_ForceZero(ctx->reqDataIv, sizeof(ctx->reqDataIv)); + wc_ForceZero(ctx->rspDataIv, sizeof(ctx->rspDataIv)); + /* Handshake keys */ + wc_ForceZero(ctx->reqHsSecret, sizeof(ctx->reqHsSecret)); + wc_ForceZero(ctx->rspHsSecret, sizeof(ctx->rspHsSecret)); + wc_ForceZero(ctx->reqFinishedKey, sizeof(ctx->reqFinishedKey)); + wc_ForceZero(ctx->rspFinishedKey, sizeof(ctx->rspFinishedKey)); + /* Secrets and hashes */ + wc_ForceZero(ctx->handshakeSecret, sizeof(ctx->handshakeSecret)); + wc_ForceZero(ctx->sharedSecret, sizeof(ctx->sharedSecret)); + ctx->sharedSecretSz = 0; + wc_ForceZero(ctx->th1, sizeof(ctx->th1)); + wc_ForceZero(ctx->th2, sizeof(ctx->th2)); + /* Free ephemeral ECC key */ + if (ctx->flags.ephemeralKeyInit) { + wc_ecc_free(&ctx->ephemeralKey); + ctx->flags.ephemeralKeyInit = 0; + } + + return rc; +} + +/* ----- I/O Helper ----- */ + +int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz) +{ + int rc; + + if (ctx == NULL || ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + /* Wrap messages with TCG SPDM + * headers; I/O sends TCG-framed messages. */ + byte tcgTx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + byte tcgRx[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD + + WOLFSPDM_TCG_HEADER_SIZE]; + word32 tcgRxSz = sizeof(tcgRx); + int tcgTxSz; + word32 msgSize; + word32 payloadSz; + word16 tag; + + /* Detect message type: SPDM version byte 0x10-0x1F = clear message. + * Secured records start with SessionID (LE, typically 0x01 0x00...), + * which is never in the SPDM version range. */ + if (txSz > 0 && txBuf[0] >= 0x10 && txBuf[0] <= 0x1F) { + /* Clear SPDM message - wrap with TCG clear header (0x8101) */ + tcgTxSz = wolfSPDM_BuildTcgClearMessage(ctx, txBuf, txSz, + tcgTx, sizeof(tcgTx)); + } else { + /* Secured record - prepend TCG secured header (0x8201) */ + word32 totalSz = WOLFSPDM_TCG_HEADER_SIZE + txSz; + if (totalSz > sizeof(tcgTx)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + wolfSPDM_WriteTcgHeader(tcgTx, WOLFSPDM_TCG_TAG_SECURED, + totalSz, ctx->connectionHandle, ctx->fipsIndicator); + XMEMCPY(tcgTx + WOLFSPDM_TCG_HEADER_SIZE, txBuf, txSz); + tcgTxSz = (int)totalSz; + } + + if (tcgTxSz < 0) { + return tcgTxSz; + } + + wolfSPDM_DebugHex(ctx, "TCG TX", tcgTx, (word32)tcgTxSz); + + /* Send/receive via I/O callback (raw transport) */ + rc = ctx->ioCb(ctx, tcgTx, (word32)tcgTxSz, tcgRx, &tcgRxSz, + ctx->ioUserCtx); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "TCG I/O failed: %d\n", rc); + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugHex(ctx, "TCG RX", tcgRx, tcgRxSz); + + /* Strip TCG binding header from response */ + if (tcgRxSz < WOLFSPDM_TCG_HEADER_SIZE) { + wolfSPDM_DebugPrint(ctx, "SendReceive: response too short (%u)\n", + tcgRxSz); + return WOLFSPDM_E_BUFFER_SMALL; + } + + tag = SPDM_Get16BE(tcgRx); + if (tag != WOLFSPDM_TCG_TAG_CLEAR && tag != WOLFSPDM_TCG_TAG_SECURED) { + wolfSPDM_DebugPrint(ctx, "SendReceive: unexpected TCG tag " + "0x%04x\n", tag); + return WOLFSPDM_E_PEER_ERROR; + } + + /* Capture FIPS indicator from response if non-zero */ + tag = SPDM_Get16BE(tcgRx + 10); + if (tag != 0) { + ctx->fipsIndicator = tag; + } + + /* Extract payload (everything after 16-byte TCG header) */ + msgSize = SPDM_Get32BE(tcgRx + 2); + + if (msgSize < WOLFSPDM_TCG_HEADER_SIZE || msgSize > tcgRxSz) { + wolfSPDM_DebugPrint(ctx, "SendReceive: TCG size %u invalid " + "(min=%u, received=%u)\n", msgSize, + WOLFSPDM_TCG_HEADER_SIZE, tcgRxSz); + return WOLFSPDM_E_BUFFER_SMALL; + } + + payloadSz = msgSize - WOLFSPDM_TCG_HEADER_SIZE; + if (payloadSz > *rxSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(rxBuf, tcgRx + WOLFSPDM_TCG_HEADER_SIZE, payloadSz); + *rxSz = payloadSz; + + return WOLFSPDM_SUCCESS; + } +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + + rc = ctx->ioCb(ctx, txBuf, txSz, rxBuf, rxSz, ctx->ioUserCtx); + if (rc != 0) { + return WOLFSPDM_E_IO_FAIL; + } + + return WOLFSPDM_SUCCESS; +} + +/* ----- Debug Utilities ----- */ +#ifdef DEBUG_WOLFTPM +void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) +{ + va_list args; + + if (ctx == NULL || !ctx->flags.debug) { + return; + } + + printf("[wolfSPDM] "); + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + fflush(stdout); +} + +void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, + const byte* data, word32 len) +{ + word32 i; + + if (ctx == NULL || !ctx->flags.debug || data == NULL) { + return; + } + + printf("[wolfSPDM] %s (%u bytes): ", label, len); + for (i = 0; i < len && i < 32; i++) { + printf("%02x", data[i]); + } + if (len > 32) { + printf("..."); + } + printf("\n"); + fflush(stdout); +} +#endif + +/* ----- Error String ----- */ +const char* wolfSPDM_GetErrorString(int error) +{ + switch (error) { + case WOLFSPDM_SUCCESS: return "Success"; + case WOLFSPDM_E_INVALID_ARG: return "Invalid argument"; + case WOLFSPDM_E_BUFFER_SMALL: return "Buffer too small"; + case WOLFSPDM_E_BAD_STATE: return "Invalid state"; + case WOLFSPDM_E_VERSION_MISMATCH: return "Version mismatch"; + case WOLFSPDM_E_CRYPTO_FAIL: return "Crypto operation failed"; + case WOLFSPDM_E_BAD_SIGNATURE: return "Bad signature"; + case WOLFSPDM_E_BAD_HMAC: return "HMAC verification failed"; + case WOLFSPDM_E_IO_FAIL: return "I/O failure"; + case WOLFSPDM_E_TIMEOUT: return "Timeout"; + case WOLFSPDM_E_PEER_ERROR: return "Peer error response"; + case WOLFSPDM_E_DECRYPT_FAIL: return "Decryption failed"; + case WOLFSPDM_E_SEQUENCE: return "Sequence number error"; + case WOLFSPDM_E_NOT_CONNECTED: return "Not connected"; + case WOLFSPDM_E_ALREADY_INIT: return "Already initialized"; + case WOLFSPDM_E_NO_MEMORY: return "Memory allocation failed"; + case WOLFSPDM_E_SESSION_INVALID: return "Invalid session"; + case WOLFSPDM_E_KEY_EXCHANGE: return "Key exchange failed"; + default: return "Unknown error"; + } +} diff --git a/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..5c2fa3e6 --- /dev/null +++ b/spdm/src/spdm_internal.h @@ -0,0 +1,362 @@ +/* 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; + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + /* TCG binding fields (shared by Nuvoton + Nations) */ + word32 connectionHandle; /* Connection handle (usually 0) */ + word16 fipsIndicator; /* FIPS service indicator */ + + /* Host's public key in TPMT_PUBLIC format */ + byte reqPubKeyTPMT[128]; /* TPMT_PUBLIC serialized (~120 bytes) */ + word32 reqPubKeyTPMTLen; +#endif + +#ifdef WOLFSPDM_NATIONS + /* PSK fields (Nations PSK mode) */ + byte psk[WOLFSPDM_PSK_MAX_SIZE]; + word32 pskSz; /* pskSz > 0 means PSK is set */ + byte pskHint[WOLFSPDM_PSK_HINT_MAX]; + word32 pskHintSz; +#endif + + /* Random number generator */ + WC_RNG rng; + + /* Negotiated parameters */ + byte maxVersion; /* Runtime max version cap (0 = use compile-time default) */ + byte spdmVersion; /* Negotiated SPDM version */ + + /* Ephemeral ECDHE key (generated for KEY_EXCHANGE) */ + ecc_key ephemeralKey; + + /* ECDH shared secret (P-384 X-coordinate = 48 bytes) */ + byte sharedSecret[WOLFSPDM_ECC_KEY_SIZE]; + word32 sharedSecretSz; + + /* Transcript hash for TH1/TH2 computation */ + byte transcript[WOLFSPDM_MAX_TRANSCRIPT]; + word32 transcriptLen; + + /* Computed hashes */ + byte certChainHash[WOLFSPDM_HASH_SIZE]; /* Ct = Hash(cert_chain) */ + byte th1[WOLFSPDM_HASH_SIZE]; /* TH1 after KEY_EXCHANGE_RSP */ + byte th2[WOLFSPDM_HASH_SIZE]; /* TH2 after FINISH */ + + /* Derived keys */ + byte handshakeSecret[WOLFSPDM_HASH_SIZE]; + byte reqHsSecret[WOLFSPDM_HASH_SIZE]; + byte rspHsSecret[WOLFSPDM_HASH_SIZE]; + byte reqFinishedKey[WOLFSPDM_HASH_SIZE]; + byte rspFinishedKey[WOLFSPDM_HASH_SIZE]; + + /* Session encryption keys (AES-256-GCM) */ + byte reqDataKey[WOLFSPDM_AEAD_KEY_SIZE]; /* Outgoing encryption key */ + byte rspDataKey[WOLFSPDM_AEAD_KEY_SIZE]; /* Incoming decryption key */ + byte reqDataIv[WOLFSPDM_AEAD_IV_SIZE]; /* Base IV for outgoing */ + byte rspDataIv[WOLFSPDM_AEAD_IV_SIZE]; /* Base IV for incoming */ + + /* Sequence numbers for IV generation */ + word64 reqSeqNum; /* Outgoing message sequence */ + word64 rspSeqNum; /* Incoming message sequence (expected) */ + + /* Session IDs */ + word16 reqSessionId; /* Our session ID (chosen by us) */ + word16 rspSessionId; /* Responder's session ID */ + word32 sessionId; /* Combined: reqSessionId | (rspSessionId << 16) */ + + /* Responder's identity public key (for cert-less mode like Nuvoton) */ + byte rspPubKey[128]; /* TPMT_PUBLIC (120 bytes for P-384) or raw X||Y (96) */ + word32 rspPubKeyLen; + + /* Mutual auth fields from KEY_EXCHANGE_RSP */ + byte mutAuthRequested; /* MutAuthRequested from KEY_EXCHANGE_RSP */ + byte reqSlotIdParam; /* ReqSlotIDParam from KEY_EXCHANGE_RSP */ + + /* Requester's identity key pair (for mutual auth) */ + byte reqPrivKey[WOLFSPDM_ECC_KEY_SIZE]; + word32 reqPrivKeyLen; + byte reqPubKey[WOLFSPDM_ECC_POINT_SIZE]; + +}; + +/* ----- Byte-Order Helpers ----- */ + +static WC_INLINE void SPDM_Set16LE(byte* buf, word16 val) { + buf[0] = (byte)(val & 0xFF); buf[1] = (byte)(val >> 8); +} +static WC_INLINE word16 SPDM_Get16LE(const byte* buf) { + return (word16)(buf[0] | (buf[1] << 8)); +} +static WC_INLINE void SPDM_Set16BE(byte* buf, word16 val) { + buf[0] = (byte)(val >> 8); buf[1] = (byte)(val & 0xFF); +} +static WC_INLINE word16 SPDM_Get16BE(const byte* buf) { + return (word16)((buf[0] << 8) | buf[1]); +} +static WC_INLINE void SPDM_Set32LE(byte* buf, word32 val) { + buf[0] = (byte)(val & 0xFF); buf[1] = (byte)((val >> 8) & 0xFF); + buf[2] = (byte)((val >> 16) & 0xFF); buf[3] = (byte)((val >> 24) & 0xFF); +} +static WC_INLINE word32 SPDM_Get32LE(const byte* buf) { + return (word32)buf[0] | ((word32)buf[1] << 8) | + ((word32)buf[2] << 16) | ((word32)buf[3] << 24); +} +static WC_INLINE void SPDM_Set32BE(byte* buf, word32 val) { + buf[0] = (byte)(val >> 24); buf[1] = (byte)((val >> 16) & 0xFF); + buf[2] = (byte)((val >> 8) & 0xFF); buf[3] = (byte)(val & 0xFF); +} +static WC_INLINE word32 SPDM_Get32BE(const byte* buf) { + return ((word32)buf[0] << 24) | ((word32)buf[1] << 16) | + ((word32)buf[2] << 8) | (word32)buf[3]; +} +static WC_INLINE void SPDM_Set64LE(byte* buf, word64 val) { + buf[0] = (byte)(val & 0xFF); buf[1] = (byte)((val >> 8) & 0xFF); + buf[2] = (byte)((val >> 16) & 0xFF); buf[3] = (byte)((val >> 24) & 0xFF); + buf[4] = (byte)((val >> 32) & 0xFF); buf[5] = (byte)((val >> 40) & 0xFF); + buf[6] = (byte)((val >> 48) & 0xFF); buf[7] = (byte)((val >> 56) & 0xFF); +} +static WC_INLINE word64 SPDM_Get64LE(const byte* buf) { + return (word64)buf[0] | ((word64)buf[1] << 8) | + ((word64)buf[2] << 16) | ((word64)buf[3] << 24) | + ((word64)buf[4] << 32) | ((word64)buf[5] << 40) | + ((word64)buf[6] << 48) | ((word64)buf[7] << 56); +} + +/* ----- Write TCG SPDM Binding header ----- */ +/* tag(2/BE) + size(4/BE) + + * connHandle(4/BE) + fips(2/BE) + reserved(4) */ +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +static WC_INLINE void wolfSPDM_WriteTcgHeader(byte* buf, word16 tag, + word32 totalSz, word32 connHandle, word16 fips) +{ + SPDM_Set16BE(buf, tag); + SPDM_Set32BE(buf + 2, totalSz); + SPDM_Set32BE(buf + 6, connHandle); + SPDM_Set16BE(buf + 10, fips); + XMEMSET(buf + 12, 0, 4); /* Reserved */ +} +#endif + +/* ----- Build IV ----- */ +static WC_INLINE void wolfSPDM_BuildIV(byte* iv, const byte* baseIv, + word64 seqNum) +{ + byte seq[8]; int i; + XMEMCPY(iv, baseIv, WOLFSPDM_AEAD_IV_SIZE); + SPDM_Set64LE(seq, seqNum); + for (i = 0; i < 8; i++) iv[i] ^= seq[i]; +} + +/* ----- Connect Step Macro ----- */ + +#define SPDM_CONNECT_STEP(ctx, msg, func) do { \ + wolfSPDM_DebugPrint(ctx, msg); \ + rc = func; \ + if (rc != WOLFSPDM_SUCCESS) { ctx->state = WOLFSPDM_STATE_ERROR; return rc; } \ +} while (0) + +/* ----- Argument Validation Macros ----- */ + +#define SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, minSz) \ + do { \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) == NULL) \ + return WOLFSPDM_E_INVALID_ARG; \ + if (*(bufSz) < (minSz)) \ + return WOLFSPDM_E_BUFFER_SMALL; \ + } while(0) + +#define SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, minSz) \ + do { \ + if ((ctx) == NULL || (buf) == NULL || (bufSz) < (minSz)) \ + return WOLFSPDM_E_INVALID_ARG; \ + } while(0) + +/* ----- Response Code Check Macro ----- */ + +#define SPDM_CHECK_RESPONSE(ctx, buf, bufSz, expected, fallbackErr) \ + do { \ + if ((buf)[1] != (expected)) { \ + int _ec; \ + if (wolfSPDM_CheckError((buf), (bufSz), &_ec)) { \ + wolfSPDM_DebugPrint((ctx), "SPDM error: 0x%02x\n", _ec); \ + return WOLFSPDM_E_PEER_ERROR; \ + } \ + return (fallbackErr); \ + } \ + } while (0) + +/* ----- Internal Function Declarations - Transcript ----- */ + +WOLFSPDM_API void wolfSPDM_TranscriptReset(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len); +WOLFSPDM_API int wolfSPDM_TranscriptHash(WOLFSPDM_CTX* ctx, byte* hash); +WOLFSPDM_API int wolfSPDM_Sha384Hash(byte* out, + const byte* d1, word32 d1Sz, + const byte* d2, word32 d2Sz, + const byte* d3, word32 d3Sz); + +/* ----- Internal Function Declarations - Crypto ----- */ + +WOLFSPDM_API int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_ExportEphemeralPubKey(WOLFSPDM_CTX* ctx, + byte* pubKeyX, word32* pubKeyXSz, + byte* pubKeyY, word32* pubKeyYSz); +WOLFSPDM_API int wolfSPDM_ComputeSharedSecret(WOLFSPDM_CTX* ctx, + const byte* peerPubKeyX, const byte* peerPubKeyY); +WOLFSPDM_API int wolfSPDM_GetRandom(WOLFSPDM_CTX* ctx, byte* out, word32 outSz); +WOLFSPDM_API int wolfSPDM_SignHash(WOLFSPDM_CTX* ctx, const byte* hash, word32 hashSz, + byte* sig, word32* sigSz); +WOLFSPDM_API int wolfSPDM_VerifySignature(WOLFSPDM_CTX* ctx, + const byte* hash, word32 hashSz, + const byte* sig, word32 sigSz); + +/* ----- Internal Function Declarations - Key Derivation ----- */ + +WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash); +#ifdef WOLFSPDM_NATIONS +WOLFSPDM_API int wolfSPDM_DeriveHandshakeKeysPsk(WOLFSPDM_CTX* ctx, const byte* th1Hash); +#endif +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); +#ifdef WOLFSPDM_NATIONS +WOLFSPDM_API int wolfSPDM_BuildPskExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFSPDM_API int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_BuildPskFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz); +WOLFSPDM_API int wolfSPDM_ParsePskFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +#endif + +/* ----- Internal Function Declarations - Message Parsing ----- */ + +WOLFSPDM_API int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz); +WOLFSPDM_API int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode); + +/* ----- Internal Function Declarations - Secured Messaging ----- */ + +WOLFSPDM_API int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, + const byte* plain, word32 plainSz, + byte* enc, word32* encSz); +WOLFSPDM_API int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, + const byte* enc, word32 encSz, + byte* plain, word32* plainSz); + +/* ----- Internal Utility Functions ----- */ + +WOLFSPDM_API int wolfSPDM_SendReceive(WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz); + +#ifdef DEBUG_WOLFTPM +WOLFSPDM_API void wolfSPDM_DebugPrint(WOLFSPDM_CTX* ctx, const char* fmt, ...) +#ifdef __GNUC__ + __attribute__((format(printf, 2, 3))) +#endif + ; + +WOLFSPDM_API void wolfSPDM_DebugHex(WOLFSPDM_CTX* ctx, const char* label, + const byte* data, word32 len); +#else +#define wolfSPDM_DebugPrint(ctx, fmt, ...) do { (void)(ctx); (void)fmt; } while(0) +#define wolfSPDM_DebugHex(ctx, label, data, len) do { (void)(ctx); (void)(label); (void)(data); (void)(len); } while(0) +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_INTERNAL_H */ diff --git a/spdm/src/spdm_kdf.c b/spdm/src/spdm_kdf.c new file mode 100644 index 00000000..406ac2a3 --- /dev/null +++ b/spdm/src/spdm_kdf.c @@ -0,0 +1,300 @@ +/* spdm_kdf.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include "spdm_internal.h" + +/* SPDM key derivation (DSP0277): HKDF with + * info = Length(2,LE) || "spdm1.2 " || Label || Context. */ + +int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, word32 secretSz, + const char* label, const byte* context, word32 contextSz, + byte* out, word32 outSz) +{ + byte info[128]; + word32 infoLen = 0; + word32 labelLen; + const char* prefix; + int rc; + + if (secret == NULL || label == NULL || out == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Select version-specific prefix */ + if (spdmVersion >= 0x14) { + prefix = SPDM_BIN_CONCAT_PREFIX_14; /* "spdm1.4 " */ + } else if (spdmVersion >= 0x13) { + prefix = SPDM_BIN_CONCAT_PREFIX_13; /* "spdm1.3 " */ + } else { + prefix = SPDM_BIN_CONCAT_PREFIX_12; /* "spdm1.2 " */ + } + + /* BinConcat format: Length (2 LE) || "spdmX.Y " || Label || Context + * Note: SPDM spec references TLS 1.3 (BE), but Nuvoton uses LE. + * The ResponderVerifyData match proves LE is correct for this TPM. */ + info[infoLen++] = (byte)(outSz & 0xFF); + info[infoLen++] = (byte)((outSz >> 8) & 0xFF); + + labelLen = (word32)XSTRLEN(label); + + /* Bounds check: 2 + prefix(8) + label + context must fit in info[128] */ + if (2 + SPDM_BIN_CONCAT_PREFIX_LEN + labelLen + contextSz > sizeof(info)) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + XMEMCPY(info + infoLen, prefix, SPDM_BIN_CONCAT_PREFIX_LEN); + infoLen += SPDM_BIN_CONCAT_PREFIX_LEN; + + XMEMCPY(info + infoLen, label, labelLen); + infoLen += labelLen; + + if (context != NULL && contextSz > 0) { + XMEMCPY(info + infoLen, context, contextSz); + infoLen += contextSz; + } + + rc = wc_HKDF_Expand(WC_SHA384, secret, secretSz, info, infoLen, out, outSz); + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash, + byte* verifyData) +{ + Hmac hmac; + int rc; + + if (finishedKey == NULL || thHash == NULL || verifyData == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wc_HmacSetKey(&hmac, WC_SHA384, finishedKey, WOLFSPDM_HASH_SIZE); + if (rc != 0) { + wc_HmacFree(&hmac); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wc_HmacUpdate(&hmac, thHash, WOLFSPDM_HASH_SIZE); + if (rc != 0) { + wc_HmacFree(&hmac); + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wc_HmacFinal(&hmac, verifyData); + wc_HmacFree(&hmac); + + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +/* Derive both data key (AES-256) and IV from a secret using HKDF-Expand */ +static int wolfSPDM_DeriveKeyIvPair(byte spdmVersion, const byte* secret, + byte* key, byte* iv) +{ + int rc; + rc = wolfSPDM_HkdfExpandLabel(spdmVersion, secret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_KEY, NULL, 0, + key, WOLFSPDM_AEAD_KEY_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + return wolfSPDM_HkdfExpandLabel(spdmVersion, secret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_IV, NULL, 0, + iv, WOLFSPDM_AEAD_IV_SIZE); +} + +/* Shared post-Extract: derive HS secrets, finished keys, and data keys from + * ctx->handshakeSecret. Called by both ECDHE and PSK key derivation. */ +static int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, + const byte* th1Hash) +{ + int rc; + + /* reqHsSecret = HKDF-Expand(HS, "req hs data" || TH1, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_REQ_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, + ctx->reqHsSecret, WOLFSPDM_HASH_SIZE); + if (rc == WOLFSPDM_SUCCESS) { + /* rspHsSecret = HKDF-Expand(HS, "rsp hs data" || TH1, 48) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->handshakeSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_RSP_HS_DATA, th1Hash, WOLFSPDM_HASH_SIZE, + ctx->rspHsSecret, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Finished keys (used for VerifyData HMAC) */ + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->reqHsSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, + ctx->reqFinishedKey, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_HkdfExpandLabel(ctx->spdmVersion, ctx->rspHsSecret, + WOLFSPDM_HASH_SIZE, SPDM_LABEL_FINISHED, NULL, 0, + ctx->rspFinishedKey, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + /* Data encryption keys + IVs (AES-256-GCM) */ + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->reqHsSecret, + ctx->reqDataKey, ctx->reqDataIv); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveKeyIvPair(ctx->spdmVersion, ctx->rspHsSecret, + ctx->rspDataKey, ctx->rspDataIv); + } + + return rc; +} + +int wolfSPDM_DeriveHandshakeKeys(WOLFSPDM_CTX* ctx, const byte* th1Hash) +{ + byte salt[WOLFSPDM_HASH_SIZE]; + int rc; + + if (ctx == NULL || th1Hash == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* SPDM uses zero salt (unlike TLS 1.3 which uses Hash("")) */ + XMEMSET(salt, 0, sizeof(salt)); + + /* HandshakeSecret = HKDF-Extract(zeros, sharedSecret) */ + rc = wc_HKDF_Extract(WC_SHA384, salt, sizeof(salt), + ctx->sharedSecret, ctx->sharedSecretSz, + ctx->handshakeSecret); + if (rc != 0) { + return WOLFSPDM_E_CRYPTO_FAIL; + } + + return wolfSPDM_DeriveFromHandshakeSecret(ctx, th1Hash); +} + +#ifdef WOLFSPDM_NATIONS +int wolfSPDM_DeriveHandshakeKeysPsk(WOLFSPDM_CTX* ctx, const byte* th1Hash) +{ + byte salt[WOLFSPDM_HASH_SIZE]; + int rc; + + if (ctx == NULL || th1Hash == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (ctx->pskSz == 0) { + return WOLFSPDM_E_BAD_STATE; + } + + /* PSK mode: Salt_0 = 0xFF-filled (per Nations NS350 specification). + * This differs from identity key mode which uses zero salt. */ + XMEMSET(salt, 0xFF, sizeof(salt)); + + /* HandshakeSecret = HKDF-Extract(0xFF-salt, PSK) */ + rc = wc_HKDF_Extract(WC_SHA384, salt, sizeof(salt), + ctx->psk, ctx->pskSz, ctx->handshakeSecret); + if (rc != 0) { + /* SECURITY: Zero PSK on failure too */ + wc_ForceZero(ctx->psk, sizeof(ctx->psk)); + ctx->pskSz = 0; + return WOLFSPDM_E_CRYPTO_FAIL; + } + + rc = wolfSPDM_DeriveFromHandshakeSecret(ctx, th1Hash); + + /* SECURITY: Zero PSK immediately — no longer needed after key derivation. + * All session keys are derived from handshakeSecret. */ + wc_ForceZero(ctx->psk, sizeof(ctx->psk)); + ctx->pskSz = 0; + + return rc; +} +#endif /* WOLFSPDM_NATIONS */ + +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..606e9f7b --- /dev/null +++ b/spdm/src/spdm_msg.c @@ -0,0 +1,755 @@ +/* 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 */ +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + buf[offset++] = 0xFF; /* SlotID = 0xFF (no cert, use provisioned public key) */ +#else + buf[offset++] = 0x00; /* SlotID = 0 (certificate slot 0) */ +#endif + + /* ReqSessionID (2 LE) */ + buf[offset++] = (byte)(ctx->reqSessionId & 0xFF); + buf[offset++] = (byte)((ctx->reqSessionId >> 8) & 0xFF); + + buf[offset++] = 0x00; /* SessionPolicy */ + buf[offset++] = 0x00; /* Reserved */ + + /* RandomData (32 bytes) */ + rc = wolfSPDM_GetRandom(ctx, &buf[offset], WOLFSPDM_RANDOM_SIZE); + if (rc == WOLFSPDM_SUCCESS) { + offset += WOLFSPDM_RANDOM_SIZE; + + /* ExchangeData: X || Y */ + XMEMCPY(&buf[offset], pubKeyX, WOLFSPDM_ECC_KEY_SIZE); + offset += WOLFSPDM_ECC_KEY_SIZE; + XMEMCPY(&buf[offset], pubKeyY, WOLFSPDM_ECC_KEY_SIZE); + offset += WOLFSPDM_ECC_KEY_SIZE; + + /* OpaqueData for secured message version negotiation */ +#ifdef WOLFSPDM_NUVOTON + /* Nuvoton vendor format: 12 bytes */ + buf[offset++] = 0x0c; buf[offset++] = 0x00; + buf[offset++] = 0x00; buf[offset++] = 0x00; + buf[offset++] = 0x05; buf[offset++] = 0x00; + buf[offset++] = 0x01; buf[offset++] = 0x01; + buf[offset++] = 0x01; buf[offset++] = 0x00; + buf[offset++] = 0x10; buf[offset++] = 0x00; + buf[offset++] = 0x00; buf[offset++] = 0x00; +#elif defined(WOLFSPDM_NATIONS) + /* Empty OpaqueData — Nations only accepts OpaqueLength=0 */ + buf[offset++] = 0x00; buf[offset++] = 0x00; +#else + /* Standard SPDM 1.2+ OpaqueData format: 20 bytes */ + buf[offset++] = 0x14; /* OpaqueLength = 20 */ + buf[offset++] = 0x00; + buf[offset++] = 0x01; buf[offset++] = 0x00; /* TotalElements */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Reserved */ + buf[offset++] = 0x00; buf[offset++] = 0x00; + buf[offset++] = 0x09; buf[offset++] = 0x00; /* DataSize */ + buf[offset++] = 0x01; /* Registry ID */ + buf[offset++] = 0x01; /* VendorLen */ + buf[offset++] = 0x03; buf[offset++] = 0x00; /* VersionCount */ + buf[offset++] = 0x10; buf[offset++] = 0x00; /* 1.0 */ + buf[offset++] = 0x11; buf[offset++] = 0x00; /* 1.1 */ + buf[offset++] = 0x12; buf[offset++] = 0x00; /* 1.2 */ + buf[offset++] = 0x00; buf[offset++] = 0x00; /* Padding */ +#endif + + *bufSz = offset; + } + } + + return rc; +} + +/* ----- Shared Signing Helpers ----- */ + +/* Build SPDM 1.2+ signed hash per DSP0274: + * M = combined_spdm_prefix || zero_pad || context_str || inputDigest + * outputDigest = Hash(M) + * + * combined_spdm_prefix = "dmtf-spdm-v1.X.*" x4 = 64 bytes + * zero_pad = (36 - contextStrLen) bytes of 0x00 + * context_str = signing context string (variable length, max 36) */ +static int wolfSPDM_BuildSignedHash(byte spdmVersion, + const char* contextStr, word32 contextStrLen, + const byte* inputDigest, byte* outputDigest) +{ + byte signMsg[200]; /* 64 + 36 + 48 = 148 bytes max */ + word32 signMsgLen = 0; + word32 zeroPadLen; + byte majorVer, minorVer; + int i, rc; + + majorVer = (byte)('0' + ((spdmVersion >> 4) & 0xF)); + minorVer = (byte)('0' + (spdmVersion & 0xF)); + + /* combined_spdm_prefix: "dmtf-spdm-v1.X.*" x4 = 64 bytes */ + for (i = 0; i < 4; i++) { + XMEMCPY(&signMsg[signMsgLen], "dmtf-spdm-v1.2.*", 16); + signMsg[signMsgLen + 11] = majorVer; + signMsg[signMsgLen + 13] = minorVer; + signMsg[signMsgLen + 15] = '*'; + signMsgLen += 16; + } + + /* Zero padding: 36 - contextStrLen bytes */ + if (contextStrLen > 36) { + return WOLFSPDM_E_INVALID_ARG; + } + zeroPadLen = 36 - contextStrLen; + XMEMSET(&signMsg[signMsgLen], 0x00, zeroPadLen); + signMsgLen += zeroPadLen; + + /* Signing context string */ + XMEMCPY(&signMsg[signMsgLen], contextStr, contextStrLen); + signMsgLen += contextStrLen; + + /* Input digest */ + XMEMCPY(&signMsg[signMsgLen], inputDigest, WOLFSPDM_HASH_SIZE); + signMsgLen += WOLFSPDM_HASH_SIZE; + + /* Hash M */ + rc = wolfSPDM_Sha384Hash(outputDigest, signMsg, signMsgLen, + NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) return rc; + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_BuildFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + byte th2Hash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + byte signature[WOLFSPDM_ECC_POINT_SIZE]; /* 96 bytes for P-384 */ + word32 sigSz = sizeof(signature); + word32 offset = 4; /* Start after header */ + int mutualAuth = 0; + int rc; + + /* Check arguments first before any ctx dereference */ + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Mutual auth is enabled when the responder requested it (MutAuthRequested + * bit 0) AND we have a requester key pair to sign with */ + if ((ctx->mutAuthRequested & 0x01) && ctx->flags.hasReqKeyPair) { + mutualAuth = 1; + wolfSPDM_DebugPrint(ctx, "FINISH: Mutual auth ENABLED " + "(MutAuth=0x%02x ReqSlot=0x%02x)\n", + ctx->mutAuthRequested, ctx->reqSlotIdParam); + } + + /* Check buffer size: header(4) + [OpaqueLength(2) for 1.4+] + + * [signature(96) for mutual auth] + HMAC(48) */ + { + word32 minSz = 4 + WOLFSPDM_HASH_SIZE; /* header + HMAC */ + if (ctx->spdmVersion >= SPDM_VERSION_14) + minSz += 2; /* OpaqueLength */ + if (mutualAuth) + minSz += WOLFSPDM_ECC_POINT_SIZE; /* Signature */ + if (*bufSz < minSz) + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Build FINISH header */ + buf[0] = ctx->spdmVersion; + buf[1] = SPDM_FINISH; + if (mutualAuth) { + buf[2] = 0x01; /* Param1: Signature field is included */ + /* Param2: For PUB_KEY_ID mode, shall be 0xFF per DSP0274 */ + buf[3] = 0xFF; + } else { + buf[2] = 0x00; /* Param1: No signature */ + buf[3] = 0x00; /* Param2: SlotID = 0 when no signature */ + } + + /* SPDM 1.4 adds OpaqueLength(2) + OpaqueData(var) after header */ + if (ctx->spdmVersion >= SPDM_VERSION_14) { + buf[offset++] = 0x00; /* OpaqueLength = 0 (LE) */ + buf[offset++] = 0x00; + } + + rc = WOLFSPDM_SUCCESS; + + /* Mutual auth: add Hash(Cm_requester) to transcript between message_k + * and FINISH header. For PUB_KEY_ID mode, Cm = SHA-384(TPMT_PUBLIC) + * of the requester's public key (matching how Ct is computed for + * responder per TCG SPDM binding). */ +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (rc == WOLFSPDM_SUCCESS && mutualAuth && ctx->reqPubKeyTPMTLen > 0) { + byte cmHash[WOLFSPDM_HASH_SIZE]; + rc = wolfSPDM_Sha384Hash(cmHash, ctx->reqPubKeyTPMT, + ctx->reqPubKeyTPMTLen, NULL, 0, NULL, 0); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, cmHash, WOLFSPDM_HASH_SIZE); + } +#endif + + /* Add FINISH header to transcript, compute TH2 */ + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, buf, offset); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + if (rc == WOLFSPDM_SUCCESS) + XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); + + /* Mutual auth: sign TH2, add signature to transcript, recompute TH2 */ + if (rc == WOLFSPDM_SUCCESS && mutualAuth) { + byte signMsgHash[WOLFSPDM_HASH_SIZE]; + + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "requester-finish signing", 24, th2Hash, signMsgHash); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SignHash(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, + signature, &sigSz); + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&buf[offset], signature, WOLFSPDM_ECC_POINT_SIZE); + offset += WOLFSPDM_ECC_POINT_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, signature, + WOLFSPDM_ECC_POINT_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + } + + /* RequesterVerifyData = HMAC(reqFinishedKey, TH2) */ + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, + verifyData); + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&buf[offset], verifyData, WOLFSPDM_HASH_SIZE); + offset += WOLFSPDM_HASH_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, verifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) + *bufSz = offset; + + /* Always zero sensitive stack buffers */ + wc_ForceZero(th2Hash, sizeof(th2Hash)); + wc_ForceZero(verifyData, sizeof(verifyData)); + wc_ForceZero(signature, sizeof(signature)); + return rc; +} + +int wolfSPDM_BuildEndSession(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + return wolfSPDM_BuildSimpleMsg(ctx, SPDM_END_SESSION, buf, bufSz); +} + +int wolfSPDM_CheckError(const byte* buf, word32 bufSz, int* errorCode) +{ + if (buf == NULL || bufSz < 4) { + return 0; + } + + if (buf[1] == SPDM_ERROR) { + if (errorCode != NULL) { + *errorCode = buf[2]; + } + return 1; + } + + return 0; +} + +/* Maximum SPDM version we support. Supports SPDM 1.2 through 1.4. + * Override with -DWOLFSPDM_MAX_SPDM_VERSION at compile time to cap + * at a lower version. */ +#ifndef WOLFSPDM_MAX_SPDM_VERSION +#define WOLFSPDM_MAX_SPDM_VERSION SPDM_VERSION_14 +#endif + +/* Minimum SPDM version we require. Our key derivation uses BinConcat + * format ("spdm1.2 " prefix) which is a 1.2+ feature. SPDM 1.1 uses + * a different HKDF label format and would require separate key + * derivation code. Override at compile time if 1.1 support is added. */ +#ifndef WOLFSPDM_MIN_SPDM_VERSION +#define WOLFSPDM_MIN_SPDM_VERSION SPDM_VERSION_12 +#endif + +int wolfSPDM_ParseVersion(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + word16 entryCount; + word16 maxEntries; + word32 i; + byte highestVersion = 0; /* No version found yet */ + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 6); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_VERSION, WOLFSPDM_E_VERSION_MISMATCH); + + /* Parse VERSION response: + * Offset 4-5: VersionNumberEntryCount (LE) + * Offset 6+: VersionNumberEntry array (2 bytes each, LE) */ + entryCount = SPDM_Get16LE(&buf[4]); + + /* Cap entryCount to what actually fits in the buffer to prevent + * overflow on exotic compilers where i*2 could wrap */ + maxEntries = (word16)((bufSz - 6) / 2); + if (entryCount > maxEntries) { + entryCount = maxEntries; + } + + /* Find highest mutually supported version. + * Per DSP0274, negotiated version must be the highest version + * that both sides support. We support WOLFSPDM_MIN_SPDM_VERSION + * through WOLFSPDM_MAX_SPDM_VERSION (or ctx->maxVersion if set). */ + { + byte maxVer = (ctx->maxVersion != 0) ? ctx->maxVersion + : WOLFSPDM_MAX_SPDM_VERSION; + for (i = 0; i < entryCount; i++) { + /* Each entry is 2 bytes; high byte (offset +1) is Major.Minor */ + byte ver = buf[6 + i * 2 + 1]; + if (ver >= WOLFSPDM_MIN_SPDM_VERSION && + ver <= maxVer && + ver > highestVersion) { + highestVersion = ver; + } + } + } + + /* If no mutually supported version found, fail */ + if (highestVersion == 0) { + wolfSPDM_DebugPrint(ctx, "No mutually supported SPDM version found " + "(require >= 0x%02x)\n", WOLFSPDM_MIN_SPDM_VERSION); + return WOLFSPDM_E_VERSION_MISMATCH; + } + + ctx->spdmVersion = highestVersion; + ctx->state = WOLFSPDM_STATE_VERSION; + + wolfSPDM_DebugPrint(ctx, "Negotiated SPDM version: 0x%02x\n", ctx->spdmVersion); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParseKeyExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + word16 opaqueLen; + word32 sigOffset; + word32 keRspPartialLen; + byte peerPubKeyX[WOLFSPDM_ECC_KEY_SIZE]; + byte peerPubKeyY[WOLFSPDM_ECC_KEY_SIZE]; + byte th1SigHash[WOLFSPDM_HASH_SIZE]; + byte signMsgHash[WOLFSPDM_HASH_SIZE]; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + const byte* signature; + const byte* rspVerifyData; + int rc; + + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 140); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_KEY_EXCHANGE_RSP, WOLFSPDM_E_KEY_EXCHANGE); + + ctx->rspSessionId = SPDM_Get16LE(&buf[4]); + ctx->sessionId = (word32)ctx->reqSessionId | ((word32)ctx->rspSessionId << 16); + + /* Parse MutAuthRequested and ReqSlotIDParam (offsets 6-7) */ + ctx->mutAuthRequested = buf[6]; + ctx->reqSlotIdParam = buf[7]; + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP: MutAuth=0x%02x ReqSlotID=0x%02x\n", + ctx->mutAuthRequested, ctx->reqSlotIdParam); + + /* Extract responder's ephemeral public key (offset 40 = 4+2+1+1+32) */ + XMEMCPY(peerPubKeyX, &buf[40], WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(peerPubKeyY, &buf[88], WOLFSPDM_ECC_KEY_SIZE); + + /* OpaqueLen at offset 136 */ + opaqueLen = SPDM_Get16LE(&buf[136]); + sigOffset = 138 + opaqueLen; + keRspPartialLen = sigOffset; + + if (bufSz < sigOffset + WOLFSPDM_ECC_SIG_SIZE + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + signature = buf + sigOffset; + rspVerifyData = buf + sigOffset + WOLFSPDM_ECC_SIG_SIZE; + + /* Add KEY_EXCHANGE_RSP partial (without sig/verify) to transcript */ + rc = wolfSPDM_TranscriptAdd(ctx, buf, keRspPartialLen); + + /* Verify responder signature over TH1 (DSP0274). Responder public key + * must be provisioned before KEY_EXCHANGE. */ + if (rc == WOLFSPDM_SUCCESS && !ctx->flags.hasRspPubKey) { + wolfSPDM_DebugPrint(ctx, "No responder public key set\n"); + rc = WOLFSPDM_E_BAD_STATE; + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th1SigHash); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_BuildSignedHash(ctx->spdmVersion, + "responder-key_exchange_rsp signing", 34, + th1SigHash, signMsgHash); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_VerifySignature(ctx, signMsgHash, WOLFSPDM_HASH_SIZE, + signature, WOLFSPDM_ECC_SIG_SIZE); + if (rc != WOLFSPDM_SUCCESS) + wolfSPDM_DebugPrint(ctx, "KEY_EXCHANGE_RSP signature INVALID\n"); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, signature, WOLFSPDM_ECC_SIG_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeSharedSecret(ctx, peerPubKeyX, peerPubKeyY); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, ctx->th1); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveHandshakeKeys(ctx, ctx->th1); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, ctx->th1, expectedHmac); + } + if (rc == WOLFSPDM_SUCCESS) { + word32 i; + int diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { + diff |= expectedHmac[i] ^ rspVerifyData[i]; + } + if (diff != 0) { + wolfSPDM_DebugPrint(ctx, "ResponderVerifyData MISMATCH\n"); + rc = WOLFSPDM_E_BAD_HMAC; + } + } + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "ResponderVerifyData VERIFIED OK\n"); + rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_KEY_EX; + } + + wc_ForceZero(expectedHmac, sizeof(expectedHmac)); + wc_ForceZero(th1SigHash, sizeof(th1SigHash)); + wc_ForceZero(signMsgHash, sizeof(signMsgHash)); + return rc; +} + +int wolfSPDM_ParseFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + + if (buf[1] == SPDM_FINISH_RSP) { + int addRc; + word32 rspMsgLen = 4; + + /* SPDM 1.4 adds OpaqueLength(2) + OpaqueData(var) to FINISH_RSP */ + if (ctx->spdmVersion >= SPDM_VERSION_14) { + word16 opaqueLen; + if (bufSz < 6) { + return WOLFSPDM_E_BUFFER_SMALL; + } + opaqueLen = SPDM_Get16LE(&buf[4]); + rspMsgLen = 4 + 2 + opaqueLen; + if (bufSz < rspMsgLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + } + + /* Add FINISH_RSP (header + OpaqueData for 1.4) to transcript */ + addRc = wolfSPDM_TranscriptAdd(ctx, buf, rspMsgLen); + if (addRc != WOLFSPDM_SUCCESS) { + return addRc; + } + ctx->state = WOLFSPDM_STATE_FINISH; + wolfSPDM_DebugPrint(ctx, "FINISH_RSP received - session established\n"); + return WOLFSPDM_SUCCESS; + } + + if (buf[1] == SPDM_ERROR) { + wolfSPDM_DebugPrint(ctx, "FINISH error: 0x%02x\n", buf[2]); + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_E_BAD_STATE; +} + +#ifdef WOLFSPDM_NATIONS +/* ----- PSK Message Builders/Parsers (Nations PSK mode) ----- */ + +int wolfSPDM_BuildPskExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + word32 offset = 0; + int rc; + + SPDM_CHECK_BUILD_ARGS(ctx, buf, bufSz, 48); + + if (ctx->pskSz == 0) { + return WOLFSPDM_E_BAD_STATE; + } + + XMEMSET(buf, 0, *bufSz); + + /* Header */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_PSK_EXCHANGE; + buf[offset++] = 0x00; /* MeasurementSummaryHashType = None */ + buf[offset++] = 0x00; /* Param2 = Reserved */ + + /* ReqSessionID (2 LE) */ + SPDM_Set16LE(&buf[offset], ctx->reqSessionId); + offset += 2; + + /* PSKHintLength (2 LE) */ + SPDM_Set16LE(&buf[offset], (word16)ctx->pskHintSz); + offset += 2; + + /* RequesterContextLength (2 LE) = 32 */ + SPDM_Set16LE(&buf[offset], WOLFSPDM_RANDOM_SIZE); + offset += 2; + + /* OpaqueDataLength (2 LE) = 0 */ + SPDM_Set16LE(&buf[offset], 0); + offset += 2; + + /* PSKHint */ + if (ctx->pskHintSz > 0) { + if (offset + ctx->pskHintSz > *bufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + XMEMCPY(&buf[offset], ctx->pskHint, ctx->pskHintSz); + offset += ctx->pskHintSz; + } + + /* RequesterContext (32 random bytes) */ + if (offset + WOLFSPDM_RANDOM_SIZE > *bufSz) { + return WOLFSPDM_E_BUFFER_SMALL; + } + rc = wolfSPDM_GetRandom(ctx, &buf[offset], WOLFSPDM_RANDOM_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + offset += WOLFSPDM_RANDOM_SIZE; + + /* OpaqueData - none */ + + *bufSz = offset; + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_ParsePskExchangeRsp(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz) +{ + word16 rspContextLen, opaqueLen; + word32 verifyOffset; + word32 rspPartialLen; + byte th1Hash[WOLFSPDM_HASH_SIZE]; + byte expectedHmac[WOLFSPDM_HASH_SIZE]; + const byte* rspVerifyData; + int rc; + + /* Minimum: header(4) + RspSessionID(2) + Reserved(1) + RspContextLen(2) + + * OpaqueLen(2) + VerifyData(48) = 59 */ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 59); + SPDM_CHECK_RESPONSE(ctx, buf, bufSz, SPDM_PSK_EXCHANGE_RSP, + WOLFSPDM_E_KEY_EXCHANGE); + + /* Per SPDM 1.3 DSP0274 Table 65: + * [4-5] RspSessionID, [6] MutAuthRequested, [7] ReqSlotIDParam, + * [8-9] RspContextLength, [10-11] OpaqueDataLength */ + ctx->rspSessionId = SPDM_Get16LE(&buf[4]); + ctx->sessionId = (word32)ctx->reqSessionId | + ((word32)ctx->rspSessionId << 16); + + rspContextLen = SPDM_Get16LE(&buf[8]); + opaqueLen = SPDM_Get16LE(&buf[10]); + + verifyOffset = 12 + rspContextLen + opaqueLen; + rspPartialLen = verifyOffset; + + if (bufSz < verifyOffset + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + rspVerifyData = buf + verifyOffset; + + /* Add PSK_EXCHANGE_RSP (without VerifyData) to transcript */ + rc = wolfSPDM_TranscriptAdd(ctx, buf, rspPartialLen); + + /* Compute TH1 and derive handshake keys from PSK BEFORE verifying */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th1Hash); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->th1, th1Hash, WOLFSPDM_HASH_SIZE); + rc = wolfSPDM_DeriveHandshakeKeysPsk(ctx, th1Hash); + } + + /* Verify ResponderVerifyData = HMAC(rspFinishedKey, TH1) */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->rspFinishedKey, th1Hash, + expectedHmac); + } + if (rc == WOLFSPDM_SUCCESS) { + word32 i; + int diff = 0; + wolfSPDM_DebugHex(ctx, "Expected HMAC", expectedHmac, WOLFSPDM_HASH_SIZE); + wolfSPDM_DebugHex(ctx, "Received HMAC", rspVerifyData, WOLFSPDM_HASH_SIZE); + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) { + diff |= expectedHmac[i] ^ rspVerifyData[i]; + } + if (diff != 0) { + wolfSPDM_DebugPrint(ctx, "PSK ResponderVerifyData MISMATCH\n"); + rc = WOLFSPDM_E_BAD_HMAC; + } + } + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "PSK ResponderVerifyData VERIFIED OK\n"); + rc = wolfSPDM_TranscriptAdd(ctx, rspVerifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_KEY_EX; + } + + wc_ForceZero(expectedHmac, sizeof(expectedHmac)); + wc_ForceZero(th1Hash, sizeof(th1Hash)); + return rc; +} + +int wolfSPDM_BuildPskFinish(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz) +{ + byte th2Hash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + word32 offset = 0; + int rc; + + if (ctx == NULL || buf == NULL || bufSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* PSK_FINISH = header(4) + VerifyData(48) = 52 bytes */ + if (*bufSz < 4 + WOLFSPDM_HASH_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Header */ + buf[offset++] = ctx->spdmVersion; + buf[offset++] = SPDM_PSK_FINISH; + buf[offset++] = 0x00; /* Param1 */ + buf[offset++] = 0x00; /* Param2 */ + + /* Add PSK_FINISH header to transcript, compute TH2 */ + rc = wolfSPDM_TranscriptAdd(ctx, buf, offset); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptHash(ctx, th2Hash); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(ctx->th2, th2Hash, WOLFSPDM_HASH_SIZE); + } + + /* RequesterVerifyData = HMAC(reqFinishedKey, TH2) — no signature for PSK */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ComputeVerifyData(ctx->reqFinishedKey, th2Hash, + verifyData); + } + if (rc == WOLFSPDM_SUCCESS) { + XMEMCPY(&buf[offset], verifyData, WOLFSPDM_HASH_SIZE); + offset += WOLFSPDM_HASH_SIZE; + rc = wolfSPDM_TranscriptAdd(ctx, verifyData, WOLFSPDM_HASH_SIZE); + } + if (rc == WOLFSPDM_SUCCESS) { + *bufSz = offset; + } + + wc_ForceZero(th2Hash, sizeof(th2Hash)); + wc_ForceZero(verifyData, sizeof(verifyData)); + return rc; +} + +int wolfSPDM_ParsePskFinishRsp(WOLFSPDM_CTX* ctx, const byte* buf, + word32 bufSz) +{ + SPDM_CHECK_PARSE_ARGS(ctx, buf, bufSz, 4); + + if (buf[1] == SPDM_PSK_FINISH_RSP) { + int addRc = wolfSPDM_TranscriptAdd(ctx, buf, 4); + if (addRc != WOLFSPDM_SUCCESS) { + return addRc; + } + ctx->state = WOLFSPDM_STATE_FINISH; + wolfSPDM_DebugPrint(ctx, "PSK_FINISH_RSP received\n"); + return WOLFSPDM_SUCCESS; + } + + if (buf[1] == SPDM_ERROR) { + wolfSPDM_DebugPrint(ctx, "PSK_FINISH error: 0x%02x\n", buf[2]); + return WOLFSPDM_E_PEER_ERROR; + } + + return WOLFSPDM_E_BAD_STATE; +} +#endif /* WOLFSPDM_NATIONS */ diff --git a/spdm/src/spdm_nations.c b/spdm/src/spdm_nations.c new file mode 100644 index 00000000..a87695ec --- /dev/null +++ b/spdm/src/spdm_nations.c @@ -0,0 +1,459 @@ +/* spdm_nations.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Nations Technology NS350 SPDM Functions + * + * PSK-mode vendor commands and PSK connection flow. + * Identity key mode uses shared TCG code in spdm_nuvoton.c. + */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_NATIONS + +#include +#include /* Shared TCG: BuildVendorDefined, etc. */ + +/* ----- Vendor Command Helpers ----- */ + +/* Response container for clear vendor commands */ +typedef struct { + char vdCode[WOLFSPDM_VDCODE_LEN + 1]; + byte payload[256]; + word32 payloadSz; +} WOLFSPDM_NATIONS_VENDOR_RSP; + +/* Clear vendor command: build -> SendReceive -> check error -> parse response */ +static int wolfSPDM_NationsVendorCmdClear(WOLFSPDM_CTX* ctx, const char* vdCode, + const byte* payload, word32 payloadSz, WOLFSPDM_NATIONS_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_NationsVendorCmdSecured(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; +} + +/* ----- Nations PSK-Mode Vendor Commands ----- */ + +int wolfSPDM_Nations_GetStatus(WOLFSPDM_CTX* ctx, + WOLFSPDM_NATIONS_STATUS* status) +{ + WOLFSPDM_NATIONS_VENDOR_RSP rsp; + int rc; + + if (ctx == NULL || status == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMSET(status, 0, sizeof(*status)); + + wolfSPDM_DebugPrint(ctx, "Nations: GET_STS_\n"); + + /* NS350 accepts GET_STATUS with no payload (Type field omitted) */ + rc = wolfSPDM_NationsVendorCmdClear(ctx, + WOLFSPDM_NATIONS_VDCODE_GET_STATUS, + NULL, 0, &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugHex(ctx, "GET_STS_ payload", rsp.payload, rsp.payloadSz); + + /* Per TCG spec Table 15 — GET_STATUS_RSP payload: + * [0] SpecMajorVersion, [1] SpecMinorVersion, + * [2] PSKSet (00=NO, 01=YES), + * [3] SPDMOnly (00=DISABLED, 01=ENABLED, 81=PENDING_DISABLE) */ + if (rsp.payloadSz >= 4) { + status->spdmEnabled = 1; + status->pskProvisioned = (rsp.payload[2] != 0); + status->spdmOnlyLocked = (rsp.payload[3] != 0); + wolfSPDM_DebugPrint(ctx, "GET_STS_: v%u.%u PSK=%s SPDMOnly=0x%02x\n", + rsp.payload[0], rsp.payload[1], + status->pskProvisioned ? "YES" : "NO", + rsp.payload[3]); + } + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock) +{ + byte param[1]; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + param[0] = lock ? WOLFSPDM_NATIONS_SPDMONLY_LOCK + : WOLFSPDM_NATIONS_SPDMONLY_UNLOCK; + + wolfSPDM_DebugPrint(ctx, "Nations: SPDMONLY %s\n", + lock ? "LOCK" : "UNLOCK"); + + rc = wolfSPDM_NationsVendorCmdSecured(ctx, + WOLFSPDM_NATIONS_VDCODE_SPDM_ONLY, param, sizeof(param)); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "SPDMONLY: Success (Lock=%u)\n", param[0]); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_PskSet(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz) +{ + int rc; + + if (ctx == NULL || psk == NULL || pskSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + wolfSPDM_DebugPrint(ctx, "Nations: PSK_SET_ (%u bytes)\n", pskSz); + + rc = wolfSPDM_NationsVendorCmdClear(ctx, + WOLFSPDM_NATIONS_VDCODE_PSK_SET, psk, pskSz, NULL); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "PSK_SET_ failed: %d\n", rc); + return rc; + } + + wolfSPDM_DebugPrint(ctx, "PSK_SET_: Success\n"); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_PskClear(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz) +{ + int rc; + + if (ctx == NULL || clearAuth == NULL || clearAuthSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + wolfSPDM_DebugPrint(ctx, "Nations: PSK_CLR_ (auth=%u bytes)\n", clearAuthSz); + + rc = wolfSPDM_NationsVendorCmdClear(ctx, + WOLFSPDM_NATIONS_VDCODE_PSK_CLEAR, clearAuth, clearAuthSz, NULL); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_DebugPrint(ctx, "PSK_CLR_ failed: %d\n", rc); + return rc; + } + + wolfSPDM_DebugPrint(ctx, "PSK_CLR_: Success\n"); + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz) +{ + int rc; + byte getCapsReq[] = { + 0x13, 0xE1, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, + 0xC0, 0x97, 0x01, 0x00, 0xC0, 0x07, 0x00, 0x00, + 0xC0, 0x07, 0x00, 0x00 + }; + byte negAlgReq[] = { + 0x13, 0xE3, 0x04, 0x00, 0x30, 0x00, 0x00, 0x02, + 0x80, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x20, 0x10, 0x00, 0x03, 0x20, 0x02, 0x00, + 0x04, 0x20, 0x80, 0x00, 0x05, 0x20, 0x01, 0x00 + }; + byte rxBuf[128]; + word32 rxSz; + + if (ctx == NULL || clearAuth == NULL || clearAuthSz == 0) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* Full VCA: GET_VERSION + GET_CAPABILITIES + NEGOTIATE_ALGORITHMS */ + rc = wolfSPDM_GetVersion(ctx); + if (rc != WOLFSPDM_SUCCESS) return rc; + + getCapsReq[0] = ctx->spdmVersion; + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SendReceive(ctx, getCapsReq, sizeof(getCapsReq), rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) return rc; + + negAlgReq[0] = ctx->spdmVersion; + rxSz = sizeof(rxBuf); + rc = wolfSPDM_SendReceive(ctx, negAlgReq, sizeof(negAlgReq), rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) return rc; + + /* Now send PSK_CLEAR */ + return wolfSPDM_Nations_PskClear(ctx, clearAuth, clearAuthSz); +} + +/* ----- Nations PSK Connection Flow ----- */ + +int wolfSPDM_ConnectNationsPsk(WOLFSPDM_CTX* ctx) +{ + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (!ctx->flags.initialized) { + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->pskSz == 0) { + wolfSPDM_DebugPrint(ctx, "Nations PSK: No PSK set\n"); + return WOLFSPDM_E_BAD_STATE; + } + + if (ctx->ioCb == NULL) { + return WOLFSPDM_E_IO_FAIL; + } + + wolfSPDM_DebugPrint(ctx, "Nations: Starting PSK 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, "Nations PSK Step 1: GET_VERSION\n", + wolfSPDM_GetVersion(ctx)); + + /* Step 2: GET_CAPABILITIES (required for VCA transcript) */ + { + /* Algorithm Set B fixed capabilities per NS350 reference */ + byte getCapsReq[] = { + 0x13, 0xE1, 0x00, 0x00, /* header: ver 1.3, GET_CAPABILITIES */ + 0x00, 0x1F, 0x00, 0x00, /* Reserved, CTExponent=0 */ + 0xC0, 0x97, 0x01, 0x00, /* Flags (requester caps) */ + 0xC0, 0x07, 0x00, 0x00, /* DataTransferSize */ + 0xC0, 0x07, 0x00, 0x00 /* MaxSPDMmsgSize */ + }; + byte rxBuf[64]; + word32 rxSz = sizeof(rxBuf); + + getCapsReq[0] = ctx->spdmVersion; + wolfSPDM_DebugPrint(ctx, "Nations PSK Step 2: GET_CAPABILITIES\n"); + rc = wolfSPDM_TranscriptAdd(ctx, getCapsReq, sizeof(getCapsReq)); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SendReceive(ctx, getCapsReq, sizeof(getCapsReq), + rxBuf, &rxSz); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } + + /* Step 3: NEGOTIATE_ALGORITHMS (required for VCA transcript) */ + { + /* Algorithm Set B: P-384/SHA-384/AES-256-GCM per NS350 reference */ + byte negAlgReq[] = { + 0x13, 0xE3, 0x04, 0x00, /* header: ver 1.3, NEGOTIATE_ALGORITHMS */ + 0x30, 0x00, /* Length = 48 */ + 0x00, 0x02, /* MeasurementSpecification + Reserved */ + 0x80, 0x00, 0x00, 0x00, /* BaseAsymAlgo (ECDSA_ECC_NIST_P384) */ + 0x02, 0x00, 0x00, 0x00, /* BaseHashAlgo (SHA_384) */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 4 AlgStruct entries */ + 0x02, 0x20, 0x10, 0x00, /* DHE: SECP_384_R1 */ + 0x03, 0x20, 0x02, 0x00, /* AEAD: AES_256_GCM */ + 0x04, 0x20, 0x80, 0x00, /* ReqBaseAsymAlg: ECDSA_P384 */ + 0x05, 0x20, 0x01, 0x00 /* KeySchedule: SPDM */ + }; + byte rxBuf[128]; + word32 rxSz = sizeof(rxBuf); + + negAlgReq[0] = ctx->spdmVersion; + wolfSPDM_DebugPrint(ctx, "Nations PSK Step 3: NEGOTIATE_ALGORITHMS\n"); + rc = wolfSPDM_TranscriptAdd(ctx, negAlgReq, sizeof(negAlgReq)); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SendReceive(ctx, negAlgReq, sizeof(negAlgReq), + rxBuf, &rxSz); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } + + /* Step 4: PSK_EXCHANGE / PSK_EXCHANGE_RSP + * Derives handshake keys from PSK, verifies ResponderVerifyData */ + { + byte txBuf[128]; + byte rxBuf[512]; + word32 txSz = sizeof(txBuf); + word32 rxSz = sizeof(rxBuf); + + wolfSPDM_DebugPrint(ctx, "Nations PSK Step 4: PSK_EXCHANGE\n"); + rc = wolfSPDM_BuildPskExchange(ctx, txBuf, &txSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_ParsePskExchangeRsp(ctx, rxBuf, rxSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } + + /* Step 3: PSK_FINISH / PSK_FINISH_RSP (encrypted) */ + { + byte finBuf[64]; + byte encBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte decBuf[64]; + word32 finSz = sizeof(finBuf); + word32 encSz = sizeof(encBuf); + word32 rxSz = sizeof(rxBuf); + word32 decSz = sizeof(decBuf); + + wolfSPDM_DebugPrint(ctx, "Nations PSK Step 5: PSK_FINISH\n"); + rc = wolfSPDM_BuildPskFinish(ctx, finBuf, &finSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_EncryptInternal(ctx, finBuf, finSz, encBuf, &encSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_ParsePskFinishRsp(ctx, decBuf, decSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } + + /* Derive application data keys */ + rc = wolfSPDM_DeriveAppDataKeys(ctx); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + + ctx->state = WOLFSPDM_STATE_CONNECTED; + wolfSPDM_DebugPrint(ctx, "Nations PSK: SPDM Session Established! " + "SessionID=0x%08x\n", ctx->sessionId); + + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_NATIONS */ diff --git a/spdm/src/spdm_nuvoton.c b/spdm/src/spdm_nuvoton.c new file mode 100644 index 00000000..09c5b93c --- /dev/null +++ b/spdm/src/spdm_nuvoton.c @@ -0,0 +1,673 @@ +/* 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" + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + +#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; +} + +/* ----- Shared TCG 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, "TCG: 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; +} + +#ifdef WOLFSPDM_NUVOTON +/* ----- Nuvoton-Only SPDM Functions ----- */ + +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; +} +#endif /* WOLFSPDM_NUVOTON */ + +/* ----- TCG SPDM Connection Flow (shared) ----- */ + +/* TCG connection: GET_VERSION -> GET_PUBK -> KEY_EXCHANGE -> GIVE_PUB -> FINISH */ +int wolfSPDM_ConnectTCG(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, "TCG: 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, "TCG Step 1: GET_VERSION\n", + wolfSPDM_GetVersion(ctx)); + +#ifdef WOLFSPDM_NATIONS + /* Steps 2-3: GET_CAPABILITIES + NEGOTIATE_ALGORITHMS + * Required by Nations NS350 (TCG spec mandates these before GET_PUB_KEY). + * Nuvoton skips these — its simplified flow goes directly to GET_PUB_KEY. */ + if (ctx->mode == WOLFSPDM_MODE_NATIONS) { + byte capsReq[20]; + byte capsRsp[64]; + word32 capsRspSz = sizeof(capsRsp); + word32 off = 0; + + capsReq[off++] = ctx->spdmVersion; + capsReq[off++] = 0xE1; /* GET_CAPABILITIES */ + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + /* Reserved(1) + CTExponent(1) + Reserved(2) */ + capsReq[off++] = 0x00; capsReq[off++] = 0x1F; + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + /* Flags: CERT|CHAL|ENCRYPT|MAC|MUT_AUTH|KEY_EX|ENCAP|HBEAT|KEY_UPD|HANDSHAKE_ITC|PUB_KEY_ID + * Note: PSK_CAP (bit 10) is NOT set for identity key mode */ + capsReq[off++] = 0xC0; capsReq[off++] = 0x93; + capsReq[off++] = 0x01; capsReq[off++] = 0x00; + /* DataTransferSize */ + capsReq[off++] = 0xC0; capsReq[off++] = 0x07; + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + /* MaxSPDMmsgSize */ + capsReq[off++] = 0xC0; capsReq[off++] = 0x07; + capsReq[off++] = 0x00; capsReq[off++] = 0x00; + + wolfSPDM_DebugPrint(ctx, "TCG Step 2: GET_CAPABILITIES\n"); + rc = wolfSPDM_TranscriptAdd(ctx, capsReq, off); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SendReceive(ctx, capsReq, off, capsRsp, &capsRspSz); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, capsRsp, capsRspSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + + /* Step 3: NEGOTIATE_ALGORITHMS / ALGORITHMS */ + { + byte algReq[48]; + byte algRsp[128]; + word32 algRspSz = sizeof(algRsp); + word32 aoff = 0; + + algReq[aoff++] = ctx->spdmVersion; + algReq[aoff++] = 0xE3; /* NEGOTIATE_ALGORITHMS */ + algReq[aoff++] = 0x04; /* Param1: NumAlgStructs = 4 */ + algReq[aoff++] = 0x00; + algReq[aoff++] = 0x30; algReq[aoff++] = 0x00; /* Length = 48 */ + algReq[aoff++] = 0x00; algReq[aoff++] = 0x02; /* MeasurementSpec + Reserved */ + /* BaseAsymAlgo: ECDSA_ECC_NIST_P384 */ + algReq[aoff++] = 0x80; algReq[aoff++] = 0x00; + algReq[aoff++] = 0x00; algReq[aoff++] = 0x00; + /* BaseHashAlgo: SHA_384 */ + algReq[aoff++] = 0x02; algReq[aoff++] = 0x00; + algReq[aoff++] = 0x00; algReq[aoff++] = 0x00; + /* Reserved (16 bytes) */ + XMEMSET(&algReq[aoff], 0, 16); aoff += 16; + /* AlgStruct[0]: DHE = SECP_384_R1 */ + algReq[aoff++] = 0x02; algReq[aoff++] = 0x20; + algReq[aoff++] = 0x10; algReq[aoff++] = 0x00; + /* AlgStruct[1]: AEAD = AES_256_GCM */ + algReq[aoff++] = 0x03; algReq[aoff++] = 0x20; + algReq[aoff++] = 0x02; algReq[aoff++] = 0x00; + /* AlgStruct[2]: ReqBaseAsymAlg = ECDSA_P384 */ + algReq[aoff++] = 0x04; algReq[aoff++] = 0x20; + algReq[aoff++] = 0x80; algReq[aoff++] = 0x00; + /* AlgStruct[3]: KeySchedule = SPDM */ + algReq[aoff++] = 0x05; algReq[aoff++] = 0x20; + algReq[aoff++] = 0x01; algReq[aoff++] = 0x00; + + wolfSPDM_DebugPrint(ctx, "TCG Step 3: NEGOTIATE_ALGORITHMS\n"); + rc = wolfSPDM_TranscriptAdd(ctx, algReq, aoff); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_SendReceive(ctx, algReq, aoff, algRsp, &algRspSz); + if (rc == WOLFSPDM_SUCCESS) + rc = wolfSPDM_TranscriptAdd(ctx, algRsp, algRspSz); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } + } /* end Nations mode check */ +#endif /* WOLFSPDM_NATIONS */ + + /* SPDM 1.3+: Replace VCA with Hash(VCA) in transcript. + * DSP0274 1.3 section 10.17.1: th = Hash(Hash(A) || Ct || K) + * TODO: verify with both Nuvoton and Nations hardware */ + if (0 && ctx->spdmVersion >= SPDM_VERSION_13 && ctx->transcriptLen > 12) { + byte vcaHash[WOLFSPDM_HASH_SIZE]; + rc = wolfSPDM_TranscriptHash(ctx, vcaHash); + if (rc == WOLFSPDM_SUCCESS) { + wolfSPDM_TranscriptReset(ctx); + rc = wolfSPDM_TranscriptAdd(ctx, vcaHash, WOLFSPDM_HASH_SIZE); + wolfSPDM_DebugPrint(ctx, "TCG: VCA hashed (%u -> %u bytes)\n", + ctx->transcriptLen, WOLFSPDM_HASH_SIZE); + } + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } + + /* Step 4: GET_PUBK (TCG vendor command) + * Gets the TPM's SPDM-Identity public key (TPMT_PUBLIC format) */ + wolfSPDM_DebugPrint(ctx, "TCG Step 4: 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. + * Per TCG SPDM binding and Nuvoton/Nations: cert_chain_buffer_hash is + * the SHA-384 digest of the full TPMT_PUBLIC structure from GET_PUBK. */ + if (ctx->flags.hasRspPubKey && ctx->rspPubKeyLen > 0) { + wolfSPDM_DebugPrint(ctx, "TCG: Computing Ct = SHA-384(TPMT_PUBLIC[%u])\n", + ctx->rspPubKeyLen); + rc = wolfSPDM_Sha384Hash(ctx->certChainHash, + ctx->rspPubKey, ctx->rspPubKeyLen, NULL, 0, NULL, 0); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + rc = wolfSPDM_TranscriptAdd(ctx, ctx->certChainHash, WOLFSPDM_HASH_SIZE); + if (rc != WOLFSPDM_SUCCESS) { + ctx->state = WOLFSPDM_STATE_ERROR; + return rc; + } + } else { + wolfSPDM_DebugPrint(ctx, "TCG: Warning - no responder public key for Ct\n"); + } + + SPDM_CONNECT_STEP(ctx, "TCG Step 5: KEY_EXCHANGE\n", + wolfSPDM_KeyExchange(ctx)); + + /* GIVE_PUB: Provision requester's public key to TPM (secured). + * Sent encrypted after KEY_EXCHANGE during handshake. */ + if (ctx->flags.hasReqKeyPair && ctx->reqPubKeyTPMTLen > 0) { + wolfSPDM_DebugPrint(ctx, "TCG Step 6: 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, "TCG Step 6: GIVE_PUB (skipped, no host key)\n"); + } + + /* Step 5: FINISH */ + SPDM_CONNECT_STEP(ctx, "TCG Step 7: FINISH\n", + wolfSPDM_Finish(ctx)); + + ctx->state = WOLFSPDM_STATE_CONNECTED; + wolfSPDM_DebugPrint(ctx, "TCG: SPDM Session Established! " + "SessionID=0x%08x\n", ctx->sessionId); + + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ diff --git a/spdm/src/spdm_secured.c b/spdm/src/spdm_secured.c new file mode 100644 index 00000000..55c82ce0 --- /dev/null +++ b/spdm/src/spdm_secured.c @@ -0,0 +1,354 @@ +/* spdm_secured.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include "spdm_internal.h" + +/* + * SPDM Secured Message Format (DSP0277): + * + * MCTP transport: + * Header/AAD: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) = 8 bytes + * IV XOR: Leftmost 2 bytes (bytes 0-1) with 2-byte LE sequence number (DSP0277) + * + * Nuvoton TCG binding (Rev 1.11): + * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes + * IV XOR: Leftmost 8 bytes (bytes 0-7) with 8-byte LE sequence number (DSP0277 1.2) + * Plaintext: AppDataLength(2 LE) + SPDM msg + RandomData (pad to 16) + * + * Full message: Header || Ciphertext || Tag (16) + */ + +int wolfSPDM_EncryptInternal(WOLFSPDM_CTX* ctx, + const byte* plain, word32 plainSz, + byte* enc, word32* encSz) +{ + Aes aes; + byte iv[WOLFSPDM_AEAD_IV_SIZE]; + byte aad[16]; /* Up to 14 bytes for TCG format */ + byte plainBuf[WOLFSPDM_MAX_MSG_SIZE + 16]; + byte tag[WOLFSPDM_AEAD_TAG_SIZE]; + word32 plainBufSz; + word16 recordLen; + word32 hdrSz; + word32 aadSz; + int aesInit = 0; + int rc; + + if (ctx == NULL || plain == NULL || enc == NULL || encSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + if (plainSz > WOLFSPDM_MAX_MSG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + /* Nuvoton TCG binding format per Rev 1.11 spec page 25: + * Header/AAD: SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) = 14 bytes + * IV XOR: Leftmost 8 bytes (bytes 0-7) with 8-byte LE sequence number + */ + word16 appDataLen = (word16)plainSz; + + word16 unpadded = (word16)(2 + appDataLen); + word16 padLen = (word16)((16 - (unpadded % 16)) % 16); + word16 encPayloadSz = (word16)(unpadded + padLen); + + plainBufSz = encPayloadSz; + /* Length field = ciphertext + MAC + * (per Nuvoton spec page 25: Length=160=144+16) */ + recordLen = (word16)(encPayloadSz + WOLFSPDM_AEAD_TAG_SIZE); + hdrSz = 14; /* 4 + 8 + 2 (TCG binding format) */ + + if (*encSz < hdrSz + plainBufSz + WOLFSPDM_AEAD_TAG_SIZE) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Build plaintext: AppDataLength(2 LE) || SPDM message || RandomData */ + SPDM_Set16LE(plainBuf, appDataLen); + XMEMCPY(&plainBuf[2], plain, plainSz); + /* Fill RandomData with actual random bytes per Nuvoton spec */ + if (padLen > 0) { + rc = wolfSPDM_GetRandom(ctx, &plainBuf[unpadded], padLen); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + } + + /* Build header/AAD: SessionID(4 LE) + SeqNum(8 LE) + + * Length(2 LE) = 14 bytes */ + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set64LE(&enc[4], ctx->reqSeqNum); + SPDM_Set16LE(&enc[12], recordLen); + + aadSz = 14; + XMEMCPY(aad, enc, aadSz); + } else +#endif + { + /* MCTP format (per DSP0277): + * Plaintext: AppDataLen(2 LE) + MCTP header(0x05) + SPDM message + * Header: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) = 8 bytes + * AAD = Header + */ + word16 appDataLen = (word16)(1 + plainSz); + word16 encDataLen = (word16)(2 + appDataLen); + + plainBufSz = encDataLen; + recordLen = (word16)(encDataLen + WOLFSPDM_AEAD_TAG_SIZE); + hdrSz = 8; /* 4 + 2 + 2 */ + + if (*encSz < hdrSz + recordLen) { + return WOLFSPDM_E_BUFFER_SMALL; + } + + /* Build plaintext: AppDataLen(2 LE) || MCTP header(0x05) || SPDM msg */ + SPDM_Set16LE(plainBuf, appDataLen); + plainBuf[2] = MCTP_MESSAGE_TYPE_SPDM; + XMEMCPY(&plainBuf[3], plain, plainSz); + + /* Build header/AAD: SessionID(4 LE) + SeqNum(2 LE) + Length(2 LE) */ + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set16LE(&enc[4], (word16)ctx->reqSeqNum); + SPDM_Set16LE(&enc[6], recordLen); + + aadSz = 8; + XMEMCPY(aad, enc, aadSz); + } + + /* Build IV: BaseIV XOR sequence number (DSP0277) */ + wolfSPDM_BuildIV(iv, ctx->reqDataIv, ctx->reqSeqNum); + + /* AES-GCM encrypt — cascade with single cleanup */ + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc == 0) { + aesInit = 1; + rc = wc_AesGcmSetKey(&aes, ctx->reqDataKey, WOLFSPDM_AEAD_KEY_SIZE); + } + if (rc == 0) { + rc = wc_AesGcmEncrypt(&aes, &enc[hdrSz], plainBuf, plainBufSz, + iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, aad, aadSz); + } + if (aesInit) { + wc_AesFree(&aes); + } + + if (rc == 0) { + XMEMCPY(&enc[hdrSz + plainBufSz], tag, WOLFSPDM_AEAD_TAG_SIZE); + *encSz = hdrSz + plainBufSz + WOLFSPDM_AEAD_TAG_SIZE; + ctx->reqSeqNum++; + wolfSPDM_DebugPrint(ctx, "Encrypted %u bytes -> %u bytes (seq=%llu)\n", + plainSz, *encSz, (unsigned long long)(ctx->reqSeqNum - 1)); + } + + wc_ForceZero(plainBuf, sizeof(plainBuf)); + return (rc == 0) ? WOLFSPDM_SUCCESS : WOLFSPDM_E_CRYPTO_FAIL; +} + +int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, + const byte* enc, word32 encSz, + byte* plain, word32* plainSz) +{ + Aes aes; + byte iv[WOLFSPDM_AEAD_IV_SIZE]; + byte aad[16]; + byte decrypted[WOLFSPDM_MAX_MSG_SIZE + 16]; + const byte* ciphertext; + const byte* tag; + word32 cipherLen; + word16 appDataLen; + word32 hdrSz; + word32 aadSz; + int aesInit = 0; + int ret; + int rc; + + if (ctx == NULL || enc == NULL || plain == NULL || plainSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + /* ----- Transport-specific header parsing ----- */ + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + word64 rspSeqNum64; + word32 rspSessionId; + word16 rspLen; + hdrSz = 14; + aadSz = 14; + + if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) + return WOLFSPDM_E_BUFFER_SMALL; + + rspSessionId = SPDM_Get32LE(&enc[0]); + rspSeqNum64 = SPDM_Get64LE(&enc[4]); + rspLen = SPDM_Get16LE(&enc[12]); + + if (rspSessionId != ctx->sessionId) { + wolfSPDM_DebugPrint(ctx, "Session ID mismatch: 0x%08x != 0x%08x\n", + rspSessionId, ctx->sessionId); + return WOLFSPDM_E_SESSION_INVALID; + } + if (rspSeqNum64 != ctx->rspSeqNum) { + wolfSPDM_DebugPrint(ctx, "Seq mismatch: %llu != %llu\n", + (unsigned long long)rspSeqNum64, + (unsigned long long)ctx->rspSeqNum); + return WOLFSPDM_E_SEQUENCE; + } + if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < hdrSz + rspLen) + return WOLFSPDM_E_BUFFER_SMALL; + + cipherLen = (word32)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + if (cipherLen > sizeof(decrypted)) + return WOLFSPDM_E_BUFFER_SMALL; + + ciphertext = enc + hdrSz; + tag = enc + hdrSz + cipherLen; + XMEMCPY(aad, enc, aadSz); + wolfSPDM_BuildIV(iv, ctx->rspDataIv, rspSeqNum64); + } else +#endif + { + word32 rspSessionId; + word16 rspSeqNum, rspLen; + hdrSz = 8; + aadSz = 8; + + if (encSz < hdrSz + WOLFSPDM_AEAD_TAG_SIZE) + return WOLFSPDM_E_BUFFER_SMALL; + + rspSessionId = SPDM_Get32LE(&enc[0]); + rspSeqNum = SPDM_Get16LE(&enc[4]); + rspLen = SPDM_Get16LE(&enc[6]); + + if (rspSessionId != ctx->sessionId) { + wolfSPDM_DebugPrint(ctx, "Session ID mismatch: 0x%08x != 0x%08x\n", + rspSessionId, ctx->sessionId); + return WOLFSPDM_E_SESSION_INVALID; + } + if ((word64)rspSeqNum != ctx->rspSeqNum) { + wolfSPDM_DebugPrint(ctx, "Seq mismatch: %u != %llu\n", + rspSeqNum, (unsigned long long)ctx->rspSeqNum); + return WOLFSPDM_E_SEQUENCE; + } + if (rspLen < WOLFSPDM_AEAD_TAG_SIZE || encSz < (word32)(hdrSz + rspLen)) + return WOLFSPDM_E_BUFFER_SMALL; + + cipherLen = (word32)(rspLen - WOLFSPDM_AEAD_TAG_SIZE); + if (cipherLen > sizeof(decrypted)) + return WOLFSPDM_E_BUFFER_SMALL; + + ciphertext = enc + hdrSz; + tag = enc + hdrSz + cipherLen; + XMEMCPY(aad, enc, aadSz); + wolfSPDM_BuildIV(iv, ctx->rspDataIv, (word64)rspSeqNum); + } + + /* ----- AES-GCM decrypt (shared for both transports) ----- */ + + ret = WOLFSPDM_E_CRYPTO_FAIL; + rc = wc_AesInit(&aes, NULL, INVALID_DEVID); + if (rc == 0) { + aesInit = 1; + rc = wc_AesGcmSetKey(&aes, ctx->rspDataKey, WOLFSPDM_AEAD_KEY_SIZE); + } + if (rc == 0) { + rc = wc_AesGcmDecrypt(&aes, decrypted, ciphertext, cipherLen, + iv, WOLFSPDM_AEAD_IV_SIZE, tag, WOLFSPDM_AEAD_TAG_SIZE, + aad, aadSz); + if (rc != 0) { + wolfSPDM_DebugPrint(ctx, "AES-GCM decrypt failed: %d\n", rc); + ret = WOLFSPDM_E_DECRYPT_FAIL; + } + } + if (aesInit) { + wc_AesFree(&aes); + } + + /* ----- Parse decrypted payload ----- */ + + if (rc == 0) { + appDataLen = SPDM_Get16LE(decrypted); +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + ctx->mode == WOLFSPDM_MODE_NATIONS || + ctx->mode == WOLFSPDM_MODE_NATIONS_PSK) { + /* TCG binding: AppDataLen(2) || SPDM msg || RandomData */ + if (cipherLen < (word32)(2 + appDataLen) || + *plainSz < appDataLen) { + ret = WOLFSPDM_E_BUFFER_SMALL; + } else { + XMEMCPY(plain, &decrypted[2], appDataLen); + *plainSz = appDataLen; + ret = WOLFSPDM_SUCCESS; + } + } else +#endif + { + /* MCTP: AppDataLen(2) || MCTP(1) || SPDM msg */ + if (appDataLen < 1 || cipherLen < (word32)(2 + appDataLen) || + *plainSz < (word32)(appDataLen - 1)) { + ret = WOLFSPDM_E_BUFFER_SMALL; + } else { + XMEMCPY(plain, &decrypted[3], appDataLen - 1); + *plainSz = appDataLen - 1; + ret = WOLFSPDM_SUCCESS; + } + } + } + + if (ret == WOLFSPDM_SUCCESS) { + ctx->rspSeqNum++; + wolfSPDM_DebugPrint(ctx, "Decrypted %u bytes -> %u bytes\n", + encSz, *plainSz); + } + + wc_ForceZero(decrypted, sizeof(decrypted)); + return ret; +} + +int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz) +{ + byte encBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + byte rxBuf[WOLFSPDM_MAX_MSG_SIZE + WOLFSPDM_AEAD_OVERHEAD]; + word32 encSz = sizeof(encBuf); + word32 rxSz = sizeof(rxBuf); + int rc; + + if (ctx == NULL || cmdPlain == NULL || rspPlain == NULL || rspSz == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + rc = wolfSPDM_EncryptInternal(ctx, cmdPlain, cmdSz, encBuf, &encSz); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, encBuf, encSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, rspPlain, rspSz); + } + + return rc; +} diff --git a/spdm/src/spdm_session.c b/spdm/src/spdm_session.c new file mode 100644 index 00000000..6f3264f2 --- /dev/null +++ b/spdm/src/spdm_session.c @@ -0,0 +1,148 @@ +/* spdm_session.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#include "spdm_internal.h" + +/* Callback types for build/parse functions */ +typedef int (*wolfSPDM_BuildFn)(WOLFSPDM_CTX*, byte*, word32*); +typedef int (*wolfSPDM_ParseFn)(WOLFSPDM_CTX*, const byte*, word32); + +/* Exchange helper: build -> transcript(tx) -> sendrecv -> transcript(rx) -> parse */ +static int wolfSPDM_ExchangeMsg(WOLFSPDM_CTX* ctx, + wolfSPDM_BuildFn buildFn, wolfSPDM_ParseFn parseFn, + byte* txBuf, word32 txBufSz, byte* rxBuf, word32 rxBufSz) +{ + word32 txSz = txBufSz; + word32 rxSz = rxBufSz; + int rc; + + rc = buildFn(ctx, txBuf, &txSz); + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, txBuf, txSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_SendReceive(ctx, txBuf, txSz, rxBuf, &rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_TranscriptAdd(ctx, rxBuf, rxSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = parseFn(ctx, rxBuf, rxSz); + } + + return rc; +} + +/* Adapter: BuildGetVersion doesn't take ctx */ +static int wolfSPDM_BuildGetVersionAdapter(WOLFSPDM_CTX* ctx, byte* buf, + word32* bufSz) +{ + (void)ctx; + return wolfSPDM_BuildGetVersion(buf, bufSz); +} + +int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx) +{ + byte txBuf[8]; + byte rxBuf[32]; /* VERSION: 4 hdr + 2 count + up to 8 entries * 2 = 22 */ + + return wolfSPDM_ExchangeMsg(ctx, wolfSPDM_BuildGetVersionAdapter, + wolfSPDM_ParseVersion, txBuf, sizeof(txBuf), rxBuf, sizeof(rxBuf)); +} + +int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx) +{ + byte txBuf[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) { + #ifdef DEBUG_WOLFTPM + if (rxBuf[1] == 0x7F) { + byte errCode = (rxSz >= 3) ? rxBuf[2] : 0xFF; + wolfSPDM_DebugPrint(ctx, "FINISH: SPDM ERROR 0x%02x\n", errCode); + } + #endif + rc = WOLFSPDM_E_PEER_ERROR; + } + + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DecryptInternal(ctx, rxBuf, rxSz, decBuf, &decSz); + } + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_ParseFinishRsp(ctx, decBuf, decSz); + } + + /* Derive application data keys (transition from handshake to app phase) */ + if (rc == WOLFSPDM_SUCCESS) { + rc = wolfSPDM_DeriveAppDataKeys(ctx); + } + + /* Always zero sensitive stack buffers */ + wc_ForceZero(finishBuf, sizeof(finishBuf)); + wc_ForceZero(decBuf, sizeof(decBuf)); + return rc; +} diff --git a/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..28b75a5f --- /dev/null +++ b/spdm/test/unit_test.c @@ -0,0 +1,998 @@ +/* unit_test.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include "../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(); +} + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +/* I/O callback that returns a TCG response with msgSize < TCG_HEADER_SIZE */ +static int tcg_underflow_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, void* userCtx) +{ + (void)ctx; (void)txBuf; (void)txSz; (void)userCtx; + /* Return a 20-byte TCG response with msgSize field = 5 (< 16) */ + if (*rxSz < 20) return -1; + memset(rxBuf, 0, 20); + SPDM_Set16BE(rxBuf, 0x8101); /* tag: clear SPDM */ + SPDM_Set32BE(rxBuf + 2, 5); /* msgSize = 5 (underflow!) */ + *rxSz = 20; + return 0; +} + +static int test_tcg_underflow(void) +{ + byte txBuf[32]; + byte rxBuf[32]; + word32 rxSz = sizeof(rxBuf); + int rc; + TEST_CTX_SETUP(); + + printf("test_tcg_underflow...\n"); + +#ifdef WOLFSPDM_NUVOTON + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NUVOTON); +#else + wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS); +#endif + wolfSPDM_SetIO(ctx, tcg_underflow_io_cb, NULL); + + txBuf[0] = 0x10; + txBuf[1] = SPDM_GET_VERSION; + txBuf[2] = 0x00; + txBuf[3] = 0x00; + + rc = wolfSPDM_SendReceive(ctx, txBuf, 4, rxBuf, &rxSz); + ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, + "msgSize < 16 must return BUFFER_SMALL"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#ifdef WOLFSPDM_NATIONS +static int test_nations_mode(void) +{ + int rc; + TEST_CTX_SETUP(); + + printf("test_nations_mode...\n"); + + /* Test Nations mode can be set */ + rc = wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS); + ASSERT_SUCCESS(rc); + ASSERT_EQ(wolfSPDM_GetMode(ctx), WOLFSPDM_MODE_NATIONS, + "Mode should be NATIONS"); + + /* Verify TCG fields initialized */ + ASSERT_EQ(wolfSPDM_GetConnectionHandle(ctx), 0, + "connectionHandle should be 0"); + ASSERT_EQ(wolfSPDM_GetFipsIndicator(ctx), WOLFSPDM_FIPS_NON_FIPS, + "fipsIndicator should be NON_FIPS"); + + /* Test Nations PSK mode can be set */ + rc = wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS_PSK); + ASSERT_SUCCESS(rc); + ASSERT_EQ(wolfSPDM_GetMode(ctx), WOLFSPDM_MODE_NATIONS_PSK, + "Mode should be NATIONS_PSK"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_psk_set(void) +{ + int rc; + byte psk[48]; + byte hint[] = "test_hint"; + TEST_CTX_SETUP(); + + printf("test_nations_psk_set...\n"); + + memset(psk, 0xAB, sizeof(psk)); + + /* NULL args */ + rc = wolfSPDM_SetPSK(NULL, psk, sizeof(psk), NULL, 0); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "NULL ctx should fail"); + rc = wolfSPDM_SetPSK(ctx, NULL, sizeof(psk), NULL, 0); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "NULL psk should fail"); + rc = wolfSPDM_SetPSK(ctx, psk, 0, NULL, 0); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "Zero pskSz should fail"); + + /* Valid PSK without hint */ + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0); + ASSERT_SUCCESS(rc); + ASSERT_EQ(ctx->pskSz, sizeof(psk), "pskSz should be 48"); + ASSERT_EQ(ctx->pskHintSz, 0, "hintSz should be 0"); + + /* Valid PSK with hint */ + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), hint, sizeof(hint) - 1); + ASSERT_SUCCESS(rc); + ASSERT_EQ(ctx->pskHintSz, sizeof(hint) - 1, "hintSz mismatch"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_psk_kdf(void) +{ + int rc; + byte psk[48]; + byte th1[WOLFSPDM_HASH_SIZE]; + byte zeros[WOLFSPDM_PSK_MAX_SIZE]; + TEST_CTX_SETUP_V12(); + + printf("test_nations_psk_kdf...\n"); + + memset(psk, 0xCD, sizeof(psk)); + memset(th1, 0xEF, sizeof(th1)); + memset(zeros, 0, sizeof(zeros)); + + /* Set PSK */ + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0); + ASSERT_SUCCESS(rc); + + /* Derive handshake keys from PSK */ + rc = wolfSPDM_DeriveHandshakeKeysPsk(ctx, th1); + ASSERT_SUCCESS(rc); + + /* Verify PSK was scrubbed */ + ASSERT_EQ(ctx->pskSz, 0, "pskSz should be 0 after derivation"); + ASSERT_EQ(memcmp(ctx->psk, zeros, WOLFSPDM_PSK_MAX_SIZE), 0, + "PSK not zeroed after derivation"); + + /* Verify handshake secret was derived (non-zero) */ + ASSERT_NE(memcmp(ctx->handshakeSecret, zeros, sizeof(ctx->handshakeSecret)), 0, + "handshakeSecret should be non-zero"); + + /* Verify finished keys were derived (non-zero) */ + ASSERT_NE(memcmp(ctx->reqFinishedKey, zeros, sizeof(ctx->reqFinishedKey)), 0, + "reqFinishedKey should be non-zero"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_nations_psk_message_format(void) +{ + int rc; + byte psk[48]; + byte buf[128]; + word32 bufSz = sizeof(buf); + TEST_CTX_SETUP_V12(); + + printf("test_nations_psk_message_format...\n"); + + memset(psk, 0xAA, sizeof(psk)); + rc = wolfSPDM_SetPSK(ctx, psk, sizeof(psk), NULL, 0); + ASSERT_SUCCESS(rc); + + /* Build PSK_EXCHANGE */ + rc = wolfSPDM_BuildPskExchange(ctx, buf, &bufSz); + ASSERT_SUCCESS(rc); + + /* Verify header */ + ASSERT_EQ(buf[0], SPDM_VERSION_12, "Version should be 0x12"); + ASSERT_EQ(buf[1], SPDM_PSK_EXCHANGE, "Code should be PSK_EXCHANGE"); + + /* ReqSessionID at offset 4-5 */ + ASSERT_EQ(SPDM_Get16LE(&buf[4]), ctx->reqSessionId, + "ReqSessionID mismatch"); + + /* PSKHintLength at offset 6-7 should be 0 (no hint) */ + ASSERT_EQ(SPDM_Get16LE(&buf[6]), 0, "PSKHintLen should be 0"); + + /* RequesterContextLength at offset 8-9 should be 32 */ + ASSERT_EQ(SPDM_Get16LE(&buf[8]), WOLFSPDM_RANDOM_SIZE, + "ReqCtxLen should be 32"); + + TEST_CTX_FREE(); + TEST_PASS(); +} +#endif /* WOLFSPDM_NATIONS */ + +static int test_decrypt_overflow(void) +{ + /* Static to avoid 4KB+ on stack; cipherLen must exceed + * sizeof(decrypted) = WOLFSPDM_MAX_MSG_SIZE + 16 = 4112 */ + static byte enc[4140]; + byte plain[64]; + word32 plainSz = sizeof(plain); + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_decrypt_overflow...\n"); + + ctx->sessionId = 0x00010001; + ctx->rspSeqNum = 0; + memset(ctx->rspDataKey, 0x42, sizeof(ctx->rspDataKey)); + memset(ctx->rspDataIv, 0x42, sizeof(ctx->rspDataIv)); + + /* MCTP header: rspLen=4130 -> cipherLen=4114 > 4112 = overflow guard */ + memset(enc, 0, sizeof(enc)); + SPDM_Set32LE(&enc[0], ctx->sessionId); + SPDM_Set16LE(&enc[4], 0x0000); + SPDM_Set16LE(&enc[6], 4130); + + rc = wolfSPDM_DecryptInternal(ctx, enc, 4138, plain, &plainSz); + ASSERT_EQ(rc, WOLFSPDM_E_BUFFER_SMALL, "Overflow cipherLen must be caught"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_oob_read_error(void) +{ + byte shortErr[2] = {0x12, SPDM_ERROR}; + byte fullErr[4] = {0x12, SPDM_ERROR, 0x06, 0x00}; + int rc; + TEST_CTX_SETUP_V12(); + + printf("test_oob_read_error...\n"); + + rc = wolfSPDM_ParseFinishRsp(ctx, fullErr, sizeof(fullErr)); + ASSERT_EQ(rc, WOLFSPDM_E_PEER_ERROR, "Should return peer error"); + + rc = wolfSPDM_ParseFinishRsp(ctx, shortErr, sizeof(shortErr)); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "Short buffer should fail"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_constant_time_hmac(void) +{ + byte finishedKey[WOLFSPDM_HASH_SIZE]; + byte thHash[WOLFSPDM_HASH_SIZE]; + byte verifyData[WOLFSPDM_HASH_SIZE]; + byte fakeVerify[WOLFSPDM_HASH_SIZE]; + word32 i; + int diff; + + printf("test_constant_time_hmac...\n"); + + memset(finishedKey, 0xAB, sizeof(finishedKey)); + memset(thHash, 0xCD, sizeof(thHash)); + ASSERT_SUCCESS(wolfSPDM_ComputeVerifyData(finishedKey, thHash, verifyData)); + + /* 1-byte difference must be detected */ + memcpy(fakeVerify, verifyData, sizeof(fakeVerify)); + fakeVerify[WOLFSPDM_HASH_SIZE - 1] ^= 0x01; + + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ fakeVerify[i]; + ASSERT_NE(diff, 0, "Should detect 1-byte diff"); + + /* Identical must pass */ + diff = 0; + for (i = 0; i < WOLFSPDM_HASH_SIZE; i++) + diff |= verifyData[i] ^ verifyData[i]; + ASSERT_EQ(diff, 0, "Identical data should match"); + + TEST_PASS(); +} + +static int test_setdebug_truncation(void) +{ + TEST_CTX_SETUP(); + + printf("test_setdebug_truncation...\n"); + + wolfSPDM_SetDebug(ctx, 2); + ASSERT_EQ(ctx->flags.debug, 1, "debug=2 should be 1"); + + wolfSPDM_SetDebug(ctx, 0); + ASSERT_EQ(ctx->flags.debug, 0, "debug=0 should be 0"); + + wolfSPDM_SetDebug(ctx, 255); + ASSERT_EQ(ctx->flags.debug, 1, "debug=255 should be 1"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_key_zeroing(void) +{ + byte zeros[WOLFSPDM_HASH_SIZE]; + byte zeroKey[WOLFSPDM_AEAD_KEY_SIZE]; + byte zeroIv[WOLFSPDM_AEAD_IV_SIZE]; + TEST_CTX_SETUP_V12(); + + printf("test_key_zeroing...\n"); + + memset(zeros, 0, sizeof(zeros)); + memset(zeroKey, 0, sizeof(zeroKey)); + memset(zeroIv, 0, sizeof(zeroIv)); + + /* Fill key material with non-zero data */ + memset(ctx->reqDataKey, 0xAA, sizeof(ctx->reqDataKey)); + memset(ctx->rspDataKey, 0xBB, sizeof(ctx->rspDataKey)); + memset(ctx->reqDataIv, 0xCC, sizeof(ctx->reqDataIv)); + memset(ctx->rspDataIv, 0xDD, sizeof(ctx->rspDataIv)); + memset(ctx->reqHsSecret, 0x11, sizeof(ctx->reqHsSecret)); + memset(ctx->rspHsSecret, 0x22, sizeof(ctx->rspHsSecret)); + memset(ctx->reqFinishedKey, 0x33, sizeof(ctx->reqFinishedKey)); + memset(ctx->rspFinishedKey, 0x44, sizeof(ctx->rspFinishedKey)); + memset(ctx->handshakeSecret, 0x55, sizeof(ctx->handshakeSecret)); + memset(ctx->sharedSecret, 0x66, sizeof(ctx->sharedSecret)); + memset(ctx->th1, 0x77, sizeof(ctx->th1)); + memset(ctx->th2, 0x88, sizeof(ctx->th2)); + ctx->sharedSecretSz = WOLFSPDM_ECC_KEY_SIZE; + + ctx->state = WOLFSPDM_STATE_CONNECTED; + ctx->sessionId = 0x00010001; + ctx->ioCb = dummy_io_cb; + + wolfSPDM_Disconnect(ctx); + + ASSERT_EQ(memcmp(ctx->reqDataKey, zeroKey, sizeof(ctx->reqDataKey)), 0, + "reqDataKey not zeroed"); + ASSERT_EQ(memcmp(ctx->rspDataKey, zeroKey, sizeof(ctx->rspDataKey)), 0, + "rspDataKey not zeroed"); + ASSERT_EQ(memcmp(ctx->reqDataIv, zeroIv, sizeof(ctx->reqDataIv)), 0, + "reqDataIv not zeroed"); + ASSERT_EQ(memcmp(ctx->rspDataIv, zeroIv, sizeof(ctx->rspDataIv)), 0, + "rspDataIv not zeroed"); + ASSERT_EQ(memcmp(ctx->reqHsSecret, zeros, sizeof(ctx->reqHsSecret)), 0, + "reqHsSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->rspHsSecret, zeros, sizeof(ctx->rspHsSecret)), 0, + "rspHsSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->reqFinishedKey, zeros, sizeof(ctx->reqFinishedKey)), 0, + "reqFinishedKey not zeroed"); + ASSERT_EQ(memcmp(ctx->rspFinishedKey, zeros, sizeof(ctx->rspFinishedKey)), 0, + "rspFinishedKey not zeroed"); + ASSERT_EQ(memcmp(ctx->handshakeSecret, zeros, sizeof(ctx->handshakeSecret)), 0, + "handshakeSecret not zeroed"); + ASSERT_EQ(memcmp(ctx->sharedSecret, zeros, sizeof(ctx->sharedSecret)), 0, + "sharedSecret not zeroed"); + ASSERT_EQ(ctx->sharedSecretSz, 0, "sharedSecretSz not zeroed"); + ASSERT_EQ(memcmp(ctx->th1, zeros, sizeof(ctx->th1)), 0, + "th1 not zeroed"); + ASSERT_EQ(memcmp(ctx->th2, zeros, sizeof(ctx->th2)), 0, + "th2 not zeroed"); + + wolfSPDM_Init(ctx); + TEST_CTX_FREE(); + TEST_PASS(); +} + +/* ----- Main ----- */ + +int main(void) +{ + printf("===========================================\n"); + printf("wolfSPDM Unit Tests\n"); + printf("===========================================\n\n"); + + /* Context tests */ +#ifdef WOLFSPDM_DYNAMIC_MEMORY + test_context_new_free(); +#endif + test_context_init(); + test_context_static_alloc(); + test_context_set_io(); + + /* Transcript tests */ + test_transcript_add_reset(); + test_transcript_hash(); + + /* Crypto tests */ + test_random_generation(); + test_ephemeral_key_generation(); + + /* KDF tests */ + test_hkdf_expand_label(); + test_compute_verify_data(); + + /* Message builder tests */ + test_build_get_version(); + test_build_end_session(); + + /* Error tests */ + test_check_error(); + test_error_strings(); + + /* Multi-version tests */ + test_kdf_version_prefix(); + test_hmac_mismatch_negative(); + test_transcript_overflow(); + test_version_fallback(); + + /* Session state tests */ + test_session_state(); + + /* Security tests */ + test_mitm_signature_rejected(); + test_invalid_curve_point(); +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + test_tcg_underflow(); +#endif +#ifdef WOLFSPDM_NATIONS + test_nations_mode(); + test_nations_psk_set(); + test_nations_psk_kdf(); + test_nations_psk_message_format(); +#endif + test_decrypt_overflow(); + test_oob_read_error(); + test_constant_time_hmac(); + test_setdebug_truncation(); + test_key_zeroing(); + + printf("\n===========================================\n"); + printf("Results: %d passed, %d failed\n", g_testsPassed, g_testsFailed); + printf("===========================================\n"); + + return (g_testsFailed == 0) ? 0 : 1; +} diff --git a/spdm/wolfspdm/spdm.h b/spdm/wolfspdm/spdm.h new file mode 100644 index 00000000..bf25d54e --- /dev/null +++ b/spdm/wolfspdm/spdm.h @@ -0,0 +1,146 @@ +/* spdm.h + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifndef WOLFSPDM_SPDM_H +#define WOLFSPDM_SPDM_H + +#ifndef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Protocol mode: TCG binding + vendor commands. + * For standard SPDM (emulator, measurements, challenge), see wolfSPDM standalone. */ +typedef enum { + WOLFSPDM_MODE_NUVOTON = 1, + WOLFSPDM_MODE_NATIONS = 2, + WOLFSPDM_MODE_NATIONS_PSK = 3 +} WOLFSPDM_MODE; + +/* wolfSPDM: Lightweight SPDM requester using wolfCrypt. + * Algorithm Set B fixed: P-384/SHA-384/AES-256-GCM. + * + * Usage (static, zero-malloc): + * WOLFSPDM_CTX ctx; + * wolfSPDM_Init(&ctx); + * wolfSPDM_SetIO(&ctx, callback, userPtr); + * wolfSPDM_Connect(&ctx); + * wolfSPDM_SecuredExchange(&ctx, ...); + * wolfSPDM_Disconnect(&ctx); + * wolfSPDM_Free(&ctx); + * + * Dynamic (requires --enable-dynamic-mem): + * ctx = wolfSPDM_New(); + * // ... same as above ... + * wolfSPDM_Free(ctx); + * + * WOLFSPDM_CTX is ~22KB. Use static global on small-stack systems. + * SecuredExchange call chain uses ~20KB stack for message buffers. */ + +/* Compile-time buffer size for static allocation (32KB, runtime-verified) */ +#define WOLFSPDM_CTX_STATIC_SIZE 32768 + +struct WOLFSPDM_CTX; +typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + #include +#endif +#ifdef WOLFSPDM_NATIONS + #include +#endif + +/* I/O callback: transport-agnostic send/receive. + * Returns 0 on success, negative on error. + * rxSz: [in] buffer size, [out] actual received size. */ +typedef int (*WOLFSPDM_IO_CB)( + WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + void* userCtx +); + +/* Context management */ +WOLFSPDM_API int wolfSPDM_Init(WOLFSPDM_CTX* ctx); +#ifdef WOLFSPDM_DYNAMIC_MEMORY +WOLFSPDM_API WOLFSPDM_CTX* wolfSPDM_New(void); +#endif +WOLFSPDM_API void wolfSPDM_Free(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_GetCtxSize(void); +WOLFSPDM_API int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size); + +/* Configuration */ +WOLFSPDM_API int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, + void* userCtx); +WOLFSPDM_API int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode); +WOLFSPDM_API WOLFSPDM_MODE wolfSPDM_GetMode(WOLFSPDM_CTX* ctx); +/* Set responder pub key for cert-less operation (96 bytes P-384 X||Y) */ +WOLFSPDM_API int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, + const byte* pubKey, word32 pubKeySz); +/* Set requester key pair for mutual auth (privKey=48, pubKey=96 bytes) */ +WOLFSPDM_API int wolfSPDM_SetRequesterKeyPair(WOLFSPDM_CTX* ctx, + const byte* privKey, word32 privKeySz, + const byte* pubKey, word32 pubKeySz); + +/* Session establishment */ +WOLFSPDM_API int wolfSPDM_Connect(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_IsConnected(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_Disconnect(WOLFSPDM_CTX* ctx); + +/* Individual handshake steps (for fine-grained control) */ +WOLFSPDM_API int wolfSPDM_GetVersion(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_KeyExchange(WOLFSPDM_CTX* ctx); +WOLFSPDM_API int wolfSPDM_Finish(WOLFSPDM_CTX* ctx); + +/* Secured messaging: encrypt, send, receive, decrypt in one call */ +WOLFSPDM_API int wolfSPDM_SecuredExchange(WOLFSPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz); + +/* Session info */ +WOLFSPDM_API word32 wolfSPDM_GetSessionId(WOLFSPDM_CTX* ctx); +WOLFSPDM_API byte wolfSPDM_GetNegotiatedVersion(WOLFSPDM_CTX* ctx); +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +WOLFSPDM_API word32 wolfSPDM_GetConnectionHandle(WOLFSPDM_CTX* ctx); +WOLFSPDM_API word16 wolfSPDM_GetFipsIndicator(WOLFSPDM_CTX* ctx); +#endif + +#ifdef WOLFSPDM_NATIONS +/* Set pre-shared key for PSK mode (Nations NS350) */ +WOLFSPDM_API int wolfSPDM_SetPSK(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz); +#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_nations.h b/spdm/wolfspdm/spdm_nations.h new file mode 100644 index 00000000..28bc781c --- /dev/null +++ b/spdm/wolfspdm/spdm_nations.h @@ -0,0 +1,133 @@ +/* spdm_nations.h + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Nations Technology NS350 TPM SPDM Support + * + * Two SPDM modes (mutually exclusive): + * + * 1. Identity key mode (Phase 1) — TCG "TPM Communication over SPDM" + * - Reuses shared TCG binding code (spdm_nuvoton.c) + * - GET_PUB_KEY, GIVE_PUB_KEY, TPM_CMD vendor commands + * - Algorithm Set B (P-384/SHA-384/AES-256-GCM) + * + * 2. PSK mode (Phase 2) — PSK_EXCHANGE/PSK_FINISH + * - GET_STATUS, SPDM_ONLY, PSK_SET, PSK_CLEAR vendor commands + * - Same Algorithm Set B + * + * Reference: NS350 Datasheet Rev 2.06 Section 4.5.8 + */ + +#ifndef WOLFSPDM_NATIONS_H +#define WOLFSPDM_NATIONS_H + +#ifdef WOLFSPDM_NATIONS + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- Nations Vendor TPM Command ----- */ + +/* Nations vendor command: TPM2_VendorSpdmIdentityKeySet (0x20000708) + * Auth role: USER (Table 17 in NS350 datasheet) + * Set/unset TPM SPDM-identity key provisioning. + * Status: TPM2_GetCapability(TPM_PT_VENDOR + 12) + * + * Mutual exclusion: If PSK is provisioned, returns TPM_RC_VALUE. + * Wrapper pre-checks via GetCapability and returns clear error. */ +#define TPM_CC_Nations_SpdmIdentityKeySet (0x20000708) + +/* Nations vendor capability properties */ +#define TPM_PT_VENDOR_NATIONS_FIPS_SL2 (TPM_PT_VENDOR + 11) +#define TPM_PT_VENDOR_NATIONS_IDENTITY_KEY (TPM_PT_VENDOR + 12) + +/* ----- Nations PSK-Mode Vendor-Defined Commands ----- */ + +/* Available only when PSK is provisioned, per datasheet 4.5.8 */ +#define WOLFSPDM_NATIONS_VDCODE_GET_STATUS "GET_STS_" +#define WOLFSPDM_NATIONS_VDCODE_SPDM_ONLY "SPDMONLY" +#define WOLFSPDM_NATIONS_VDCODE_PSK_SET "PSK_SET_" +#define WOLFSPDM_NATIONS_VDCODE_PSK_CLEAR "PSK_CLR_" + +/* SPDM_ONLY command parameters */ +#define WOLFSPDM_NATIONS_SPDMONLY_LOCK 0x01 +#define WOLFSPDM_NATIONS_SPDMONLY_UNLOCK 0x00 + +/* ----- Nations SPDM Status ----- */ + +/* GET_STATUS_RSP fields per TCG spec Table 15 */ +typedef struct WOLFSPDM_NATIONS_STATUS { + unsigned int spdmEnabled : 1; + unsigned int sessionActive : 1; + unsigned int spdmOnlyLocked : 1; /* SPDM-only active (0x01 or 0x81) */ + unsigned int spdmOnlyPending : 1; /* PENDING_DISABLE (0x81) */ + unsigned int pskProvisioned : 1; + unsigned int identityKeyProvisioned : 1; +} WOLFSPDM_NATIONS_STATUS; + +/* ----- Nations PSK-Mode SPDM Functions ----- */ + +/** + * Get SPDM status from Nations TPM (GET_STS_ vendor command, PSK mode only). + */ +WOLFSPDM_API int wolfSPDM_Nations_GetStatus(WOLFSPDM_CTX* ctx, + WOLFSPDM_NATIONS_STATUS* status); + +/** + * Lock or unlock SPDM-only mode on Nations TPM (SPDMONLY, PSK mode only). + */ +WOLFSPDM_API int wolfSPDM_Nations_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock); + +/** + * Provision PSK on Nations TPM (PSK_SET_ vendor command). + * Mutual exclusion: fails if identity key is provisioned. + */ +WOLFSPDM_API int wolfSPDM_Nations_PskSet(WOLFSPDM_CTX* ctx, + const byte* psk, word32 pskSz); + +/** + * Clear PSK from Nations TPM (PSK_CLR_ vendor command). + * Requires the ClearAuth that was used during PSK_SET. + */ +WOLFSPDM_API int wolfSPDM_Nations_PskClear(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz); + +/** + * PSK_CLEAR with full VCA context (GET_VERSION + CAPS + ALGO first). + */ +WOLFSPDM_API int wolfSPDM_Nations_PskClearWithVCA(WOLFSPDM_CTX* ctx, + const byte* clearAuth, word32 clearAuthSz); + +/** + * Perform Nations PSK SPDM connection. + * Uses: GET_VERSION -> PSK_EXCHANGE -> PSK_FINISH + */ +WOLFSPDM_API int wolfSPDM_ConnectNationsPsk(WOLFSPDM_CTX* ctx); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_NATIONS */ + +#endif /* WOLFSPDM_NATIONS_H */ diff --git a/spdm/wolfspdm/spdm_nuvoton.h b/spdm/wolfspdm/spdm_nuvoton.h new file mode 100644 index 00000000..f58f8230 --- /dev/null +++ b/spdm/wolfspdm/spdm_nuvoton.h @@ -0,0 +1,276 @@ +/* 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 + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----- TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) ----- */ + +/* Message Tags */ +#define WOLFSPDM_TCG_TAG_CLEAR 0x8101 /* Clear (unencrypted) message */ +#define WOLFSPDM_TCG_TAG_SECURED 0x8201 /* Secured (encrypted) message */ + +/* Header Sizes */ +#define WOLFSPDM_TCG_HEADER_SIZE 16 /* TCG binding header size */ + +/* FIPS Service Indicator */ +#define WOLFSPDM_FIPS_NON_FIPS 0x00 +#define WOLFSPDM_FIPS_APPROVED 0x01 + +/* ----- TCG Vendor-Defined Command Codes (shared by Nuvoton + Nations) ----- */ + +/* 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 */ + +#ifdef WOLFSPDM_NUVOTON +/* Nuvoton-only vendor commands */ +#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 +#endif /* WOLFSPDM_NUVOTON */ + +/* ----- 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 (Nuvoton-only) ----- */ + +#ifdef WOLFSPDM_NUVOTON +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; +#endif /* WOLFSPDM_NUVOTON */ + +/* ----- TCG Binding Message Framing Functions (shared) ----- */ + +/** + * 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); + +/* ----- Shared TCG SPDM Functions (Nuvoton + Nations) ----- */ + +/** + * 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); + +/** + * Set the requester's SPDM-Identity public key in TPMT_PUBLIC format. + * Required for GIVE_PUB step in TCG 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 TCG SPDM connection (shared by Nuvoton and Nations). + * Uses the TCG handshake flow: + * GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> GIVE_PUB_KEY -> FINISH + * + * This is called internally by wolfSPDM_Connect() for TCG modes. + * + * @param ctx wolfSPDM context + * @return WOLFSPDM_SUCCESS or negative error code + */ +WOLFSPDM_API int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx); + +/* Backward compatibility alias */ +#define wolfSPDM_ConnectNuvoton wolfSPDM_ConnectTCG + +/* ----- Nuvoton-Only SPDM Functions ----- */ + +#ifdef WOLFSPDM_NUVOTON +/** + * 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); +#endif /* WOLFSPDM_NUVOTON */ + +/* ----- TCG Context Fields ----- */ + +/* 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 || WOLFSPDM_NATIONS */ + +#endif /* WOLFSPDM_NUVOTON_H */ diff --git a/spdm/wolfspdm/spdm_types.h b/spdm/wolfspdm/spdm_types.h new file mode 100644 index 00000000..dbfafc35 --- /dev/null +++ b/spdm/wolfspdm/spdm_types.h @@ -0,0 +1,141 @@ +/* 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" + +/* ----- PSK Message Codes (SPDM 1.2+ DSP0274) ----- */ + +#define SPDM_PSK_EXCHANGE 0xE6 +#define SPDM_PSK_EXCHANGE_RSP 0x66 +#define SPDM_PSK_FINISH 0xE7 +#define SPDM_PSK_FINISH_RSP 0x67 + +/* ----- PSK Size Limits ----- */ + +#define WOLFSPDM_PSK_MAX_SIZE 64 /* Max PSK size (Nations NS350) */ +#define WOLFSPDM_PSK_HINT_MAX 32 /* Max PSK hint size */ +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_TYPES_H */ diff --git a/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 88cb229d..62f5a8b3 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -39,6 +39,9 @@ #include #endif #include +#ifdef WOLFTPM_SPDM +#include +#endif #include @@ -417,6 +420,38 @@ static int TPM2_ResponseProcess(TPM2_CTX* ctx, TPM2_Packet* packet, return rc; } +#ifdef WOLFTPM_SPDM +/* SPDM intercept: if SPDM session is active, send TPM command through + * the encrypted SPDM channel instead of raw SPI/I2C. + * Returns TPM_RC_SUCCESS on success, negative if SPDM not active + * (caller should use normal transport), or positive error code. */ +static TPM_RC TPM2_SPDM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) +{ + WOLFTPM2_SPDM_CTX* spdmCtx; + byte tpmResp[WOLFSPDM_MAX_MSG_SIZE]; + word32 tpmRespSz = sizeof(tpmResp); + TPM_RC rc; + + if (ctx->spdmCtx == NULL) + return -1; /* SPDM not configured */ + spdmCtx = (WOLFTPM2_SPDM_CTX*)ctx->spdmCtx; + if (spdmCtx->spdmCtx == NULL || !wolfSPDM_IsConnected(spdmCtx->spdmCtx)) + return -1; /* SPDM not connected */ + + rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, + packet->buf, packet->pos, tpmResp, &tpmRespSz); + if (rc != 0) + return rc; + + if (tpmRespSz > MAX_RESPONSE_SIZE) + return TPM_RC_SIZE; + XMEMCPY(packet->buf, tpmResp, tpmRespSz); + packet->pos = 0; + packet->size = tpmRespSz; + return TPM_RC_SUCCESS; +} +#endif /* WOLFTPM_SPDM */ + static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet, CmdInfo_t* info) { @@ -460,7 +495,13 @@ static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet, packet->pos = cmdSz; /* submit command and wait for response */ - rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); +#ifdef WOLFTPM_SPDM + rc = TPM2_SPDM_SendCommand(ctx, packet); + if (rc < 0) /* SPDM not active, use normal transport */ +#endif + { + rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); + } if (rc != 0) return rc; @@ -490,6 +531,13 @@ static TPM_RC TPM2_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) if (ctx == NULL || packet == NULL) return BAD_FUNC_ARG; +#ifdef WOLFTPM_SPDM + rc = TPM2_SPDM_SendCommand(ctx, packet); + if (rc == TPM_RC_SUCCESS) + return TPM2_Packet_Parse(rc, packet); + /* rc < 0 means SPDM not active, fall through to normal transport */ +#endif + /* submit command and wait for response */ rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); if (rc != 0) @@ -1590,6 +1638,7 @@ TPM_RC TPM2_StartAuthSession(StartAuthSession_In* in, StartAuthSession_Out* out) return rc; } + TPM_RC TPM2_PolicyRestart(PolicyRestart_In* in) { TPM_RC rc; @@ -5604,6 +5653,7 @@ int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in) TPM2_Packet packet; CmdInfo_t info = {0,0,0,0}; info.inHandleCnt = 1; + info.flags = CMD_FLAG_AUTH_USER1; TPM2_Packet_Init(ctx, &packet); /* Process the auth handle for GPIO configuration */ @@ -5647,6 +5697,100 @@ int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out) } #endif /* WOLFTPM_NUVOTON */ +/* NTC2 PreConfig/GetConfig for runtime vendor detection (WOLFTPM_AUTODETECT). + * Identical to the WOLFTPM_NUVOTON implementations above. */ +#if defined(WOLFTPM_AUTODETECT) && !defined(WOLFTPM_NUVOTON) && \ + !defined(WOLFTPM_ST33) + +int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in) +{ + int rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + + if (in == NULL || ctx == NULL) + return BAD_FUNC_ARG; + + rc = TPM2_AcquireLock(ctx); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet packet; + CmdInfo_t info = {0,0,0,0}; + info.inHandleCnt = 1; + info.flags = CMD_FLAG_AUTH_USER1; + + TPM2_Packet_Init(ctx, &packet); + TPM2_Packet_AppendU32(&packet, in->authHandle); + TPM2_Packet_AppendAuth(&packet, ctx, &info); + TPM2_Packet_AppendBytes(&packet, (byte*)&in->preConfig, + sizeof(in->preConfig)); + TPM2_Packet_Finalize(&packet, TPM_ST_SESSIONS, + TPM_CC_NTC2_PreConfig); + + rc = TPM2_SendCommandAuth(ctx, &packet, &info); + + TPM2_ReleaseLock(ctx); + } + return rc; +} + +int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out) +{ + int rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + + if (out == NULL || ctx == NULL) + return BAD_FUNC_ARG; + + rc = TPM2_AcquireLock(ctx); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet packet; + TPM2_Packet_Init(ctx, &packet); + TPM2_Packet_Finalize(&packet, TPM_ST_NO_SESSIONS, + TPM_CC_NTC2_GetConfig); + + rc = TPM2_SendCommand(ctx, &packet); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet_ParseBytes(&packet, (byte*)&out->preConfig, + sizeof(out->preConfig)); + } + + TPM2_ReleaseLock(ctx); + } + return rc; +} +#endif /* WOLFTPM_AUTODETECT && !WOLFTPM_NUVOTON && !WOLFTPM_ST33 */ + + +#ifdef WOLFTPM_NATIONS +/* Nations Technology NS350 Vendor Commands */ +int TPM2_Nations_IdentityKeySet(Nations_IdentityKeySet_In* in) +{ + int rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + + if (in == NULL || ctx == NULL) + return BAD_FUNC_ARG; + + rc = TPM2_AcquireLock(ctx); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet packet; + CmdInfo_t info = {0,0,0,0}; + info.inHandleCnt = 1; + info.flags = CMD_FLAG_AUTH_USER1; + + TPM2_Packet_Init(ctx, &packet); + TPM2_Packet_AppendU32(&packet, in->authHandle); + TPM2_Packet_AppendAuth(&packet, ctx, &info); + TPM2_Packet_AppendU32(&packet, in->configuration); + TPM2_Packet_Finalize(&packet, TPM_ST_SESSIONS, + TPM_CC_Nations_IdentityKeySet); + + rc = TPM2_SendCommandAuth(ctx, &packet, &info); + + TPM2_ReleaseLock(ctx); + } + return rc; +} +#endif /* WOLFTPM_NATIONS */ #ifdef WOLFTPM_FIRMWARE_UPGRADE #if defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index e603c005..ade4a81c 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -1045,6 +1045,7 @@ int TPM2_Packet_Finalize(TPM2_Packet* packet, TPM_ST tag, TPM_CC cc) return cmdSz; } + /******************************************************************************/ /* --- END TPM Packet Assembly / Parsing -- */ /******************************************************************************/ diff --git a/src/tpm2_spdm.c b/src/tpm2_spdm.c new file mode 100644 index 00000000..bb545b1b --- /dev/null +++ b/src/tpm2_spdm.c @@ -0,0 +1,386 @@ +/* tpm2_spdm.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* SPDM Integration Layer for wolfTPM + * + * All SPDM protocol logic, cryptography, and message handling is implemented + * in wolfSPDM (spdm/ subdirectory). This file provides: + * + * 1. Context management (init/free) + * 2. Secured exchange with VENDOR_DEFINED wrapping (Nuvoton) + * 3. TPM-specific NTC2_PreConfig for SPDM enable/disable (Nuvoton only) + * 4. TIS I/O callback for routing wolfSPDM through TPM SPI transport + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifdef WOLFTPM_SPDM + +#include + +/* TIS functions for SPI/I2C TPM transport */ +#if (defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS)) && \ + !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_SWTPM) && \ + !defined(WOLFTPM_WINAPI) + #include + #include + #define WOLFTPM_SPDM_TIS_IO +#endif + +/* wolfSPDM provides all SPDM protocol implementation */ +#include + +/* -------------------------------------------------------------------------- */ +/* TIS I/O Callback (SPI/I2C TPM transport for SPDM) */ +/* -------------------------------------------------------------------------- */ + +#ifdef WOLFTPM_SPDM_TIS_IO +/* TIS I/O callback for routing wolfSPDM through TPM SPI/I2C FIFO. + * This matches the WOLFSPDM_IO_CB signature. TCG framing (headers) is + * handled by wolfSPDM_SendReceive() in Nuvoton mode, so this callback + * just sends/receives raw bytes through the TIS FIFO. */ +static int wolfTPM2_SPDM_TisIoCb( + WOLFSPDM_CTX* spdmCtx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + void* userCtx) +{ + TPM2_CTX* tpmCtx = (TPM2_CTX*)userCtx; + byte ioBuf[MAX_RESPONSE_SIZE]; + TPM2_Packet packet; + int rc; + UINT32 rspSz; + + (void)spdmCtx; + + if (tpmCtx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) { + return -1; + } + + if (txSz > sizeof(ioBuf)) { + return -1; + } + + /* Set up packet with TX data */ + XMEMCPY(ioBuf, txBuf, txSz); + packet.buf = ioBuf; + packet.pos = (int)txSz; + packet.size = (int)sizeof(ioBuf); + + /* Ensure we have TPM locality */ + rc = TPM2_TIS_RequestLocality(tpmCtx, TPM_TIMEOUT_TRIES); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + + /* Send through TIS FIFO and receive response */ + rc = TPM2_TIS_SendCommand(tpmCtx, &packet); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + + /* Extract response size from header bytes [2..5] (big-endian). + * Both TPM headers and TCG SPDM binding headers store the total + * message size at this offset in the same format. */ + XMEMCPY(&rspSz, &ioBuf[2], sizeof(UINT32)); + rspSz = TPM2_Packet_SwapU32(rspSz); + + if (rspSz > *rxSz || rspSz > sizeof(ioBuf)) { + return -1; + } + + XMEMCPY(rxBuf, ioBuf, rspSz); + *rxSz = rspSz; + + return 0; +} +#endif /* WOLFTPM_SPDM_TIS_IO */ + +/* -------------------------------------------------------------------------- */ +/* Context Management */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_InitCtx( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_IO_CB ioCb, + void* userCtx) +{ + int rc; + + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + + /* Zero initialize context */ + XMEMSET(ctx, 0, sizeof(WOLFTPM2_SPDM_CTX)); + +#ifdef WOLFSPDM_DYNAMIC_MEMORY + /* Dynamic path: allocate and initialize via wolfSPDM_New() */ + ctx->spdmCtx = wolfSPDM_New(); + if (ctx->spdmCtx == NULL) { + return MEMORY_E; + } +#else + /* Static path: use inline buffer, no malloc */ + ctx->spdmCtx = (WOLFSPDM_CTX*)ctx->spdmBuf; + rc = wolfSPDM_InitStatic(ctx->spdmCtx, (int)sizeof(ctx->spdmBuf)); + if (rc != WOLFSPDM_SUCCESS) { + ctx->spdmCtx = NULL; + return rc; + } +#endif + + /* Set I/O callback if provided */ + if (ioCb != NULL) { + rc = wolfSPDM_SetIO(ctx->spdmCtx, ioCb, userCtx); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_Free(ctx->spdmCtx); + ctx->spdmCtx = NULL; + return rc; + } + } + + return TPM_RC_SUCCESS; +} + +int wolfTPM2_SPDM_SetTPMCtx( + WOLFTPM2_SPDM_CTX* ctx, + TPM2_CTX* tpmCtx) +{ + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + ctx->tpmCtx = tpmCtx; + return TPM_RC_SUCCESS; +} + +void wolfTPM2_SPDM_FreeCtx(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL) { + return; + } + + if (ctx->spdmCtx != NULL) { + wolfSPDM_Free(ctx->spdmCtx); + ctx->spdmCtx = NULL; + } + + ctx->tpmCtx = NULL; + ctx->spdmOnlyLocked = 0; +} + +/* -------------------------------------------------------------------------- */ +/* Secured Messaging */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_SecuredExchange( + WOLFTPM2_SPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + /* In SPDM-only mode, TPM commands must be wrapped in SPDM VENDOR_DEFINED + * messages with the TPM2_CMD vendor code. The TPM's SPDM layer only + * accepts SPDM messages (starting with version byte 0x13), not raw TPM + * commands (starting with tag 0x80 0x01). */ + if (wolfSPDM_GetMode(ctx->spdmCtx) == WOLFSPDM_MODE_NUVOTON || + wolfSPDM_GetMode(ctx->spdmCtx) == WOLFSPDM_MODE_NATIONS) { + byte vdMsg[WOLFSPDM_MAX_MSG_SIZE]; + byte vdRsp[WOLFSPDM_MAX_MSG_SIZE]; + word32 vdRspSz = sizeof(vdRsp); + char rspVdCode[WOLFSPDM_VDCODE_LEN + 1]; + int vdMsgSz; + int rc; + + /* Wrap TPM command in SPDM VENDOR_DEFINED_REQUEST("TPM2_CMD") */ + { + byte ver = wolfSPDM_GetNegotiatedVersion(ctx->spdmCtx); + if (ver == 0) ver = SPDM_VERSION_13; + vdMsgSz = wolfSPDM_BuildVendorDefined(ver, + WOLFSPDM_VDCODE_TPM2_CMD, + cmdPlain, cmdSz, vdMsg, sizeof(vdMsg)); + } + if (vdMsgSz < 0) { + return vdMsgSz; + } + + /* Send encrypted VENDOR_DEFINED, receive encrypted response */ + rc = wolfSPDM_SecuredExchange(ctx->spdmCtx, + vdMsg, (word32)vdMsgSz, vdRsp, &vdRspSz); + if (rc != 0) { + return rc; + } + + /* Parse VENDOR_DEFINED_RESPONSE to extract TPM response */ + rc = wolfSPDM_ParseVendorDefined(vdRsp, vdRspSz, + rspVdCode, rspPlain, rspSz); + if (rc < 0) { + return rc; + } + + return TPM_RC_SUCCESS; + } +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + + /* Standard SPDM mode: send TPM command as raw app data */ + return wolfSPDM_SecuredExchange(ctx->spdmCtx, + cmdPlain, cmdSz, rspPlain, rspSz); +} + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Functions */ +/* -------------------------------------------------------------------------- */ + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + +/* Set built-in TIS I/O callback for routing SPDM through TPM SPI/I2C. + * Must be called after wolfTPM2_SPDM_InitCtx() and SetTPMCtx(). */ +int wolfTPM2_SPDM_SetTisIO(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->spdmCtx == NULL || ctx->tpmCtx == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFTPM_SPDM_TIS_IO + return wolfSPDM_SetIO(ctx->spdmCtx, wolfTPM2_SPDM_TisIoCb, ctx->tpmCtx); +#else + (void)ctx; + return NOT_COMPILED_IN; +#endif +} + +#ifdef WOLFSPDM_NUVOTON +/* Enable SPDM on Nuvoton TPM via NTC2_PreConfig vendor command. + * This requires platform hierarchy authorization and a TPM reset. */ +int wolfTPM2_SPDM_Enable(WOLFTPM2_SPDM_CTX* ctx) +{ + int rc; + NTC2_PreConfig_In preConfigIn; + NTC2_GetConfig_Out getConfigOut; + + if (ctx == NULL || ctx->tpmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Get current NTC2 configuration */ + XMEMSET(&getConfigOut, 0, sizeof(getConfigOut)); + rc = TPM2_NTC2_GetConfig(&getConfigOut); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_GetConfig failed: 0x%x\n", rc); + #endif + return rc; + } + + /* Check if SPDM is already enabled (bit 1 of Cfg_H, 0 = enabled) */ + if ((getConfigOut.preConfig.Cfg_H & NTC2_CFG_H_SPDM_DISABLE) == 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM already enabled on TPM\n"); + #endif + return TPM_RC_SUCCESS; + } + + + /* Set SPDM capability bit (clear bit 1 to enable) */ + XMEMSET(&preConfigIn, 0, sizeof(preConfigIn)); + preConfigIn.authHandle = TPM_RH_PLATFORM; + preConfigIn.preConfig = getConfigOut.preConfig; + preConfigIn.preConfig.Cfg_H &= ~NTC2_CFG_H_SPDM_DISABLE; + + rc = TPM2_NTC2_PreConfig(&preConfigIn); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_PreConfig failed: 0x%x\n", rc); + #endif + return rc; + } + +#ifdef DEBUG_WOLFTPM + printf("SPDM enabled. TPM reset required for changes to take effect.\n"); +#endif + + return TPM_RC_SUCCESS; +} + +int wolfTPM2_SPDM_Disable(WOLFTPM2_SPDM_CTX* ctx) +{ + int rc; + NTC2_PreConfig_In preConfigIn; + NTC2_GetConfig_Out getConfigOut; + + if (ctx == NULL || ctx->tpmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Get current NTC2 configuration */ + XMEMSET(&getConfigOut, 0, sizeof(getConfigOut)); + rc = TPM2_NTC2_GetConfig(&getConfigOut); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_GetConfig failed: 0x%x\n", rc); + #endif + return rc; + } + + /* Check if SPDM is already disabled (bit 1 of Cfg_H, 1 = disabled) */ + if ((getConfigOut.preConfig.Cfg_H & NTC2_CFG_H_SPDM_DISABLE) != 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM already disabled on TPM\n"); + #endif + return TPM_RC_SUCCESS; + } + + + /* Set SPDM disable bit (set bit 1 to disable) */ + XMEMSET(&preConfigIn, 0, sizeof(preConfigIn)); + preConfigIn.authHandle = TPM_RH_PLATFORM; + preConfigIn.preConfig = getConfigOut.preConfig; + preConfigIn.preConfig.Cfg_H |= NTC2_CFG_H_SPDM_DISABLE; + + rc = TPM2_NTC2_PreConfig(&preConfigIn); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_PreConfig failed: 0x%x\n", rc); + #endif + return rc; + } + +#ifdef DEBUG_WOLFTPM + printf("SPDM disabled. TPM reset required for changes to take effect.\n"); +#endif + + return TPM_RC_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/tpm2_swtpm.c b/src/tpm2_swtpm.c index 758bc354..6c8b956c 100644 --- a/src/tpm2_swtpm.c +++ b/src/tpm2_swtpm.c @@ -52,8 +52,11 @@ #include #include #include -#ifdef HAVE_NETDB_H +#ifndef WOLFTPM_ZEPHYR +#include #include +#include +#include #endif #include diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index b06476d4..2b550b73 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -25,6 +25,9 @@ #include #include +#ifdef WOLFTPM_SPDM +#include +#endif /* Convert big-endian byte array to native word32 */ word32 wolfTPM2_RsaKey_Exponent(const byte* e, word32 eSz) @@ -154,10 +157,26 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, if (rc != TPM_RC_SUCCESS && rc != TPM_RC_INITIALIZE /* TPM_RC_INITIALIZE = Already started */ && rc != TPM_RC_UPGRADE /* TPM_RC_UPGRADE = In firmware upgrade mode */ ) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Startup failed %d: %s\n", rc, wolfTPM2_GetRCString(rc)); - #endif - return rc; + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* When SPDM-only mode is active on the TPM, TPM2_Startup returns + * TPM_RC_DISABLED. This is expected - SPDM commands bypass the + * normal TPM command path and work over raw SPI. */ + ctx->spdmOnlyDetected = 1; + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup: TPM_RC_DISABLED (SPDM-only mode active, " + "this is expected)\n"); + #endif + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup failed %d: %s\n", rc, + wolfTPM2_GetRCString(rc)); + #endif + return rc; + } } /* Return upgrade status so caller can handle appropriately */ if (rc == TPM_RC_UPGRADE) { @@ -167,7 +186,9 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, return rc; } #ifdef DEBUG_WOLFTPM - printf("TPM2_Startup pass\n"); + if (rc == TPM_RC_SUCCESS || rc == TPM_RC_INITIALIZE) { + printf("TPM2_Startup pass\n"); + } #endif rc = TPM_RC_SUCCESS; @@ -177,13 +198,29 @@ static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, selfTest.fullTest = YES; rc = TPM2_SelfTest(&selfTest); if (rc != TPM_RC_SUCCESS) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_SelfTest failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - #endif - return rc; + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* SPDM-only mode active - SelfTest not needed */ + #ifdef DEBUG_WOLFTPM + printf("TPM2_SelfTest: TPM_RC_DISABLED (SPDM-only mode, " + "expected)\n"); + #endif + rc = TPM_RC_SUCCESS; + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_SelfTest failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + #endif + return rc; + } } #ifdef DEBUG_WOLFTPM - printf("TPM2_SelfTest pass\n"); + if (rc == TPM_RC_SUCCESS) { + printf("TPM2_SelfTest pass\n"); + } #endif #endif /* WOLFTPM_MICROCHIP || WOLFTPM_PERFORM_SELFTEST */ #endif /* !WOLFTPM_LINUX_DEV && !WOLFTPM_WINAPI */ @@ -239,6 +276,49 @@ int wolfTPM2_Init(WOLFTPM2_DEV* dev, TPM2HalIoCb ioCb, void* userCtx) XMEMSET(dev->session, 0, sizeof(dev->session)); wolfTPM2_SetAuthPassword(dev, 0, NULL); +#if defined(WOLFTPM_SPDM) && defined(WOLFSPDM_NUVOTON) + /* If TPM is in SPDM-only mode, transparently establish an SPDM session + * so all subsequent TPM commands are encrypted over the bus. + * This allows existing binaries (caps, wrap_test, unit.test) to work + * without any SPDM-specific code. */ + if (dev->ctx.spdmOnlyDetected) { + Startup_In startupIn; + + rc = wolfTPM2_SpdmInit(dev); + if (rc != 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM auto-init failed: %d\n", rc); + #endif + return rc; + } + + rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); + if (rc != 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM auto-connect failed: %d\n", rc); + #endif + return rc; + } + + #ifdef DEBUG_WOLFTPM + printf("SPDM session established (auto), SessionID=0x%08x\n", + wolfTPM2_SpdmGetSessionId(dev)); + #endif + + /* Retry TPM2_Startup over the SPDM encrypted channel */ + XMEMSET(&startupIn, 0, sizeof(startupIn)); + startupIn.startupType = TPM_SU_CLEAR; + rc = TPM2_Startup(&startupIn); + if (rc != TPM_RC_SUCCESS && rc != TPM_RC_INITIALIZE) { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup over SPDM failed: 0x%x\n", rc); + #endif + return rc; + } + rc = TPM_RC_SUCCESS; + } +#endif /* WOLFTPM_SPDM && WOLFSPDM_NUVOTON */ + return rc; } @@ -951,6 +1031,573 @@ int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles) return handles->count; } +#ifdef WOLFTPM_SPDM +/* --- SPDM Secure Session Wrapper API --- + * + * These functions provide a high-level interface to wolfSPDM. + * All SPDM protocol logic is implemented in the wolfSPDM library. + */ + +int wolfTPM2_SpdmInit(WOLFTPM2_DEV* dev) +{ + int rc; + + if (dev == NULL) { + return BAD_FUNC_ARG; + } + + /* Already initialized (e.g., by auto-SPDM in wolfTPM2_Init) */ + if (dev->spdmCtx != NULL) { + return TPM_RC_SUCCESS; + } + + /* Initialize inline SPDM context */ + rc = wolfTPM2_SPDM_InitCtx(&dev->spdmCtxData, NULL, NULL); + if (rc != 0) { + return rc; + } + + rc = wolfTPM2_SPDM_SetTPMCtx(&dev->spdmCtxData, &dev->ctx); + if (rc != 0) { + wolfTPM2_SPDM_FreeCtx(&dev->spdmCtxData); + return rc; + } + + dev->spdmCtx = &dev->spdmCtxData; + dev->ctx.spdmCtx = dev->spdmCtx; + + return TPM_RC_SUCCESS; +} + +/* Validate SPDM context chain is fully initialized */ +#define WOLFTPM2_SPDM_CHECK_CTX(dev) \ + if ((dev) == NULL || (dev)->spdmCtx == NULL || \ + (dev)->spdmCtx->spdmCtx == NULL) \ + return BAD_FUNC_ARG + +int wolfTPM2_SpdmConnect(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Connect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmIsConnected(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL || dev->spdmCtx->spdmCtx == NULL) { + return 0; + } + return wolfSPDM_IsConnected(dev->spdmCtx->spdmCtx); +} + +word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL || dev->spdmCtx->spdmCtx == NULL) { + return 0; + } + return wolfSPDM_GetSessionId(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Disconnect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev) +{ + if (dev == NULL) { + return BAD_FUNC_ARG; + } + if (dev->spdmCtx != NULL) { + wolfTPM2_SPDM_FreeCtx(dev->spdmCtx); + dev->spdmCtx = NULL; + dev->ctx.spdmCtx = NULL; + } + return TPM_RC_SUCCESS; +} + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +/* Shared TCG SPDM functions */ + +int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nuvoton_GetPubKey(dev->spdmCtx->spdmCtx, pubKey, pubKeySz); +} +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#ifdef WOLFSPDM_NUVOTON +/* Nuvoton-specific SPDM functions */ + +int wolfTPM2_SpdmSetNuvotonMode(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NUVOTON); +} + +int wolfTPM2_SpdmEnable(WOLFTPM2_DEV* dev) +{ + int rc; + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + /* NTC2_PreConfig requires platform auth (empty password) */ + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != 0) return rc; + return wolfTPM2_SPDM_Enable(dev->spdmCtx); +} + +int wolfTPM2_SpdmDisable(WOLFTPM2_DEV* dev) +{ + int rc; + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + /* NTC2_PreConfig requires platform auth (empty password) */ + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != 0) return rc; + return wolfTPM2_SPDM_Disable(dev->spdmCtx); +} + +int wolfTPM2_SpdmGetStatus(WOLFTPM2_DEV* dev, WOLFSPDM_NUVOTON_STATUS* status) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nuvoton_GetStatus(dev->spdmCtx->spdmCtx, status); +} + +int wolfTPM2_SpdmSetOnlyMode(WOLFTPM2_DEV* dev, int lock) +{ + int rc; + WOLFTPM2_SPDM_CHECK_CTX(dev); + rc = wolfSPDM_Nuvoton_SetOnlyMode(dev->spdmCtx->spdmCtx, lock); + if (rc == WOLFSPDM_SUCCESS) { + dev->spdmCtx->spdmOnlyLocked = lock; + } + return rc; +} + +int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz) +{ + int rc; + + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Auto-set TIS I/O callback if not already configured by caller */ + rc = wolfTPM2_SPDM_SetTisIO(dev->spdmCtx); + if (rc != 0 && rc != NOT_COMPILED_IN) { + return rc; + } + +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(dev->spdmCtx->spdmCtx, 1); +#endif + + /* Set Nuvoton mode first */ + rc = wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NUVOTON); + if (rc != 0) { + return rc; + } + + /* Set requester key pair if provided (for mutual authentication) */ + if (reqPrivKey != NULL && reqPrivKeySz > 0 && + reqPubKey != NULL && reqPubKeySz > 0) { + /* Parse TPMT_PUBLIC to extract raw X||Y ECC point */ + TPM2_Packet pktPub; + TPMT_PUBLIC pub; + byte rawPubKey[WOLFSPDM_ECC_POINT_SIZE]; + + XMEMSET(&pub, 0, sizeof(pub)); + pktPub.buf = (byte*)reqPubKey; + pktPub.pos = 0; + pktPub.size = (int)reqPubKeySz; + + TPM2_Packet_ParseU16(&pktPub, &pub.type); + TPM2_Packet_ParseU16(&pktPub, &pub.nameAlg); + TPM2_Packet_ParseU32(&pktPub, &pub.objectAttributes); + TPM2_Packet_ParseU16(&pktPub, &pub.authPolicy.size); + TPM2_Packet_ParseBytes(&pktPub, pub.authPolicy.buffer, + pub.authPolicy.size); + TPM2_Packet_ParsePublicParms(&pktPub, pub.type, &pub.parameters); + TPM2_Packet_ParseEccPoint(&pktPub, &pub.unique.ecc); + + if (pub.type != TPM_ALG_ECC || + pub.unique.ecc.x.size != WOLFSPDM_ECC_KEY_SIZE || + pub.unique.ecc.y.size != WOLFSPDM_ECC_KEY_SIZE) { + return BAD_FUNC_ARG; + } + + XMEMCPY(rawPubKey, pub.unique.ecc.x.buffer, + WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(rawPubKey + WOLFSPDM_ECC_KEY_SIZE, + pub.unique.ecc.y.buffer, WOLFSPDM_ECC_KEY_SIZE); + rc = wolfSPDM_SetRequesterKeyPair(dev->spdmCtx->spdmCtx, + reqPrivKey, reqPrivKeySz, rawPubKey, sizeof(rawPubKey)); + if (rc != 0) { + return rc; + } + /* Also store the full TPMT_PUBLIC for GIVE_PUB step */ + rc = wolfSPDM_SetRequesterKeyTPMT(dev->spdmCtx->spdmCtx, + reqPubKey, reqPubKeySz); + if (rc != 0) { + return rc; + } + } +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) + else { + /* Auto-generate ephemeral P-384 key pair for mutual authentication */ + ecc_key hostKey; + WC_RNG rng; + byte privKey[48]; + word32 privKeySz = sizeof(privKey); + byte pubKeyX[48], pubKeyY[48]; + word32 xSz = sizeof(pubKeyX), ySz = sizeof(pubKeyY); + byte rawPubKey[96]; + /* TPMT_PUBLIC: type(2) + nameAlg(2) + attr(4) + authPolicy(2) + + * symmetric(2) + scheme(2+2) + curveID(2) + kdf(2) + + * unique.x(2+48) + unique.y(2+48) = 120 bytes */ + byte tpmtPub[120]; + byte* p; + + rc = wc_InitRng(&rng); + if (rc != 0) return rc; + + rc = wc_ecc_init(&hostKey); + if (rc != 0) { + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_make_key_ex(&rng, 48, &hostKey, ECC_SECP384R1); + if (rc != 0) { + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_export_private_only(&hostKey, privKey, &privKeySz); + if (rc != 0) { + wc_ForceZero(privKey, sizeof(privKey)); + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_export_public_raw(&hostKey, pubKeyX, &xSz, + pubKeyY, &ySz); + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + if (rc != 0) return rc; + + /* Set raw key pair (X||Y format) */ + XMEMCPY(rawPubKey, pubKeyX, 48); + XMEMCPY(rawPubKey + 48, pubKeyY, 48); + rc = wolfSPDM_SetRequesterKeyPair(dev->spdmCtx->spdmCtx, + privKey, privKeySz, rawPubKey, 96); + wc_ForceZero(privKey, sizeof(privKey)); + if (rc != 0) return rc; + + /* Build TPMT_PUBLIC for GIVE_PUB step */ + p = tpmtPub; + /* type = TPM_ALG_ECC (0x0023) */ + *p++ = 0x00; *p++ = 0x23; + /* nameAlg = TPM_ALG_SHA384 (0x000C) */ + *p++ = 0x00; *p++ = 0x0C; + /* objectAttributes = 0x00040000 (sign only) */ + *p++ = 0x00; *p++ = 0x04; *p++ = 0x00; *p++ = 0x00; + /* authPolicy size = 0 */ + *p++ = 0x00; *p++ = 0x00; + /* symmetric = TPM_ALG_NULL (0x0010) */ + *p++ = 0x00; *p++ = 0x10; + /* scheme = TPM_ALG_ECDSA (0x0018) */ + *p++ = 0x00; *p++ = 0x18; + /* scheme.hashAlg = SHA384 (0x000C) */ + *p++ = 0x00; *p++ = 0x0C; + /* curveID = TPM_ECC_NIST_P384 (0x0004) */ + *p++ = 0x00; *p++ = 0x04; + /* kdf = TPM_ALG_NULL (0x0010) */ + *p++ = 0x00; *p++ = 0x10; + /* unique.x size = 48 */ + *p++ = 0x00; *p++ = 0x30; + XMEMCPY(p, pubKeyX, 48); p += 48; + /* unique.y size = 48 */ + *p++ = 0x00; *p++ = 0x30; + XMEMCPY(p, pubKeyY, 48); p += 48; + + rc = wolfSPDM_SetRequesterKeyTPMT(dev->spdmCtx->spdmCtx, + tpmtPub, (word32)(p - tpmtPub)); + if (rc != 0) return rc; + } +#endif /* !WOLFTPM2_NO_WOLFCRYPT && HAVE_ECC */ + + /* Perform the Nuvoton SPDM handshake */ + return wolfSPDM_Connect(dev->spdmCtx->spdmCtx); +} + +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef WOLFSPDM_NATIONS +/* Nations Technology NS350 SPDM functions */ + +int wolfTPM2_SpdmSetNationsMode(WOLFTPM2_DEV* dev) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NATIONS); +} + +int wolfTPM2_SpdmConnectNations(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz) +{ + int rc; + + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Auto-set TIS I/O callback if not already configured by caller */ + rc = wolfTPM2_SPDM_SetTisIO(dev->spdmCtx); + if (rc != 0 && rc != NOT_COMPILED_IN) { + return rc; + } + +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(dev->spdmCtx->spdmCtx, 1); +#endif + + /* Set Nations mode */ + rc = wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NATIONS); + if (rc != 0) { + return rc; + } + + /* Set requester key pair if provided (for mutual authentication) */ + if (reqPrivKey != NULL && reqPrivKeySz > 0 && + reqPubKey != NULL && reqPubKeySz > 0) { + /* Parse TPMT_PUBLIC to extract raw X||Y ECC point */ + TPM2_Packet pktPub; + TPMT_PUBLIC pub; + byte rawPubKey[WOLFSPDM_ECC_POINT_SIZE]; + + XMEMSET(&pub, 0, sizeof(pub)); + pktPub.buf = (byte*)reqPubKey; + pktPub.pos = 0; + pktPub.size = (int)reqPubKeySz; + + TPM2_Packet_ParseU16(&pktPub, &pub.type); + TPM2_Packet_ParseU16(&pktPub, &pub.nameAlg); + TPM2_Packet_ParseU32(&pktPub, &pub.objectAttributes); + TPM2_Packet_ParseU16(&pktPub, &pub.authPolicy.size); + TPM2_Packet_ParseBytes(&pktPub, pub.authPolicy.buffer, + pub.authPolicy.size); + TPM2_Packet_ParsePublicParms(&pktPub, pub.type, &pub.parameters); + TPM2_Packet_ParseEccPoint(&pktPub, &pub.unique.ecc); + + if (pub.type != TPM_ALG_ECC || + pub.unique.ecc.x.size != WOLFSPDM_ECC_KEY_SIZE || + pub.unique.ecc.y.size != WOLFSPDM_ECC_KEY_SIZE) { + return BAD_FUNC_ARG; + } + + XMEMCPY(rawPubKey, pub.unique.ecc.x.buffer, + WOLFSPDM_ECC_KEY_SIZE); + XMEMCPY(rawPubKey + WOLFSPDM_ECC_KEY_SIZE, + pub.unique.ecc.y.buffer, WOLFSPDM_ECC_KEY_SIZE); + rc = wolfSPDM_SetRequesterKeyPair(dev->spdmCtx->spdmCtx, + reqPrivKey, reqPrivKeySz, rawPubKey, sizeof(rawPubKey)); + if (rc != 0) { + return rc; + } + /* Also store the full TPMT_PUBLIC for GIVE_PUB step */ + rc = wolfSPDM_SetRequesterKeyTPMT(dev->spdmCtx->spdmCtx, + reqPubKey, reqPubKeySz); + if (rc != 0) { + return rc; + } + } +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) + else { + /* Auto-generate ephemeral P-384 key pair for mutual authentication. + * Nations: GIVE_PUB is not supported, but MUT_AUTH is still required. + * The TPM may verify the requester signature using a pre-provisioned + * key or accept the key from the FINISH signature context. */ + ecc_key hostKey; + WC_RNG rng; + byte privKey[48]; + word32 privKeySz = sizeof(privKey); + byte pubKeyX[48], pubKeyY[48]; + word32 xSz = sizeof(pubKeyX), ySz = sizeof(pubKeyY); + byte rawPubKey[96]; + byte tpmtPub[120]; + byte* p; + + rc = wc_InitRng(&rng); + if (rc != 0) return rc; + + rc = wc_ecc_init(&hostKey); + if (rc != 0) { + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_make_key_ex(&rng, 48, &hostKey, ECC_SECP384R1); + if (rc != 0) { + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_export_private_only(&hostKey, privKey, &privKeySz); + if (rc != 0) { + wc_ForceZero(privKey, sizeof(privKey)); + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + return rc; + } + + rc = wc_ecc_export_public_raw(&hostKey, pubKeyX, &xSz, + pubKeyY, &ySz); + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + if (rc != 0) return rc; + + XMEMCPY(rawPubKey, pubKeyX, 48); + XMEMCPY(rawPubKey + 48, pubKeyY, 48); + rc = wolfSPDM_SetRequesterKeyPair(dev->spdmCtx->spdmCtx, + privKey, privKeySz, rawPubKey, 96); + wc_ForceZero(privKey, sizeof(privKey)); + if (rc != 0) return rc; + + /* Build TPMT_PUBLIC for GIVE_PUB / Cm hash */ + p = tpmtPub; + *p++ = 0x00; *p++ = 0x23; /* type = TPM_ALG_ECC */ + *p++ = 0x00; *p++ = 0x0C; /* nameAlg = SHA384 */ + *p++ = 0x00; *p++ = 0x04; *p++ = 0x00; *p++ = 0x00; /* attr = sign */ + *p++ = 0x00; *p++ = 0x00; /* authPolicy size = 0 */ + *p++ = 0x00; *p++ = 0x10; /* symmetric = NULL */ + *p++ = 0x00; *p++ = 0x18; /* scheme = ECDSA */ + *p++ = 0x00; *p++ = 0x0C; /* hashAlg = SHA384 */ + *p++ = 0x00; *p++ = 0x04; /* curveID = P384 */ + *p++ = 0x00; *p++ = 0x10; /* kdf = NULL */ + *p++ = 0x00; *p++ = 0x30; /* x size = 48 */ + XMEMCPY(p, pubKeyX, 48); p += 48; + *p++ = 0x00; *p++ = 0x30; /* y size = 48 */ + XMEMCPY(p, pubKeyY, 48); p += 48; + + rc = wolfSPDM_SetRequesterKeyTPMT(dev->spdmCtx->spdmCtx, + tpmtPub, (word32)(p - tpmtPub)); + if (rc != 0) return rc; + } +#endif /* !WOLFTPM2_NO_WOLFCRYPT && HAVE_ECC */ + + /* Perform the TCG SPDM handshake */ + return wolfSPDM_Connect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmNationsIdentityKeySet(WOLFTPM2_DEV* dev, int set) +{ + int rc; + Nations_IdentityKeySet_In in; + + if (dev == NULL) { + return BAD_FUNC_ARG; + } + + /* Mutual exclusion: TPM enforces this by returning TPM_RC_VALUE if PSK + * is provisioned. The error message helps users understand the failure. */ + + /* Set platform auth (empty password) with no continueSession. + * NS350 requires sessionAttributes=0x00, not 0x01 (continueSession). */ + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != 0) return rc; + dev->session[0].sessionAttributes = 0; + + XMEMSET(&in, 0, sizeof(in)); + in.authHandle = TPM_RH_PLATFORM; + in.configuration = set ? 1 : 0; + + rc = TPM2_Nations_IdentityKeySet(&in); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("Nations IdentityKeySet(%d) failed: 0x%x: %s\n", + set, rc, TPM2_GetRCString(rc)); + #endif + } + + return rc; +} + +int wolfTPM2_SpdmConnectNationsPsk(WOLFTPM2_DEV* dev, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz) +{ + int rc; + + if (dev == NULL || dev->spdmCtx == NULL || psk == NULL || pskSz == 0) { + return BAD_FUNC_ARG; + } + + /* Auto-set TIS I/O callback */ + rc = wolfTPM2_SPDM_SetTisIO(dev->spdmCtx); + if (rc != 0 && rc != NOT_COMPILED_IN) { + return rc; + } + +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(dev->spdmCtx->spdmCtx, 1); +#endif + + /* Set Nations PSK mode */ + rc = wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NATIONS_PSK); + if (rc != 0) { + return rc; + } + + /* Set PSK for KDF */ + rc = wolfSPDM_SetPSK(dev->spdmCtx->spdmCtx, psk, pskSz, hint, hintSz); + if (rc != 0) { + return rc; + } + + /* Perform PSK handshake (may include inline PSK_SET if set) */ + return wolfSPDM_Connect(dev->spdmCtx->spdmCtx); +} + +int wolfTPM2_SpdmNationsGetStatus(WOLFTPM2_DEV* dev, + WOLFSPDM_NATIONS_STATUS* status) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nations_GetStatus(dev->spdmCtx->spdmCtx, status); +} + +int wolfTPM2_SpdmNationsSetOnlyMode(WOLFTPM2_DEV* dev, int lock) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nations_SetOnlyMode(dev->spdmCtx->spdmCtx, lock); +} + +int wolfTPM2_SpdmNationsPskSet(WOLFTPM2_DEV* dev, + const byte* psk, word32 pskSz) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nations_PskSet(dev->spdmCtx->spdmCtx, psk, pskSz); +} + +int wolfTPM2_SpdmNationsPskClear(WOLFTPM2_DEV* dev, + const byte* clearAuth, word32 clearAuthSz) +{ + WOLFTPM2_SPDM_CHECK_CTX(dev); + return wolfSPDM_Nations_PskClear(dev->spdmCtx->spdmCtx, + clearAuth, clearAuthSz); +} + +#endif /* WOLFSPDM_NATIONS */ + +#endif /* WOLFTPM_SPDM */ + int wolfTPM2_UnsetAuth(WOLFTPM2_DEV* dev, int index) { TPM2_AUTH_SESSION* session; @@ -1284,14 +1931,32 @@ int wolfTPM2_Cleanup_ex(WOLFTPM2_DEV* dev, int doShutdown) shutdownIn.shutdownType = TPM_SU_CLEAR; rc = TPM2_Shutdown(&shutdownIn); if (rc != TPM_RC_SUCCESS) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Shutdown failed %d: %s\n", - rc, wolfTPM2_GetRCString(rc)); - #endif + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* SPDM-only mode active - shutdown not needed */ + #ifdef DEBUG_WOLFTPM + printf("TPM2_Shutdown: TPM_RC_DISABLED (SPDM-only mode, " + "expected)\n"); + #endif + rc = TPM_RC_SUCCESS; /* Not an error in SPDM mode */ + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Shutdown failed %d: %s\n", + rc, wolfTPM2_GetRCString(rc)); + #endif + } /* finish cleanup and return error */ } } +#ifdef WOLFTPM_SPDM + /* Clean up SPDM context if it was auto-established */ + wolfTPM2_SpdmCleanup(dev); +#endif + TPM2_Cleanup(&dev->ctx); return rc; diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 07a86d12..ad4bcf4f 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -961,6 +961,107 @@ static void test_wolfTPM2_thread_local_storage(void) #endif /* HAVE_THREAD_LS && HAVE_PTHREAD */ } +#ifdef WOLFTPM_SPDM +/* Test SPDM wrapper API functions */ +static void test_wolfTPM2_SPDM_Functions(void) +{ + int rc; + WOLFTPM2_DEV dev; + + printf("Test TPM Wrapper:\tSPDM Functions:\t"); + + /* Initialize device */ + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + if (rc != 0) { + printf("Failed (Init failed: 0x%x)\n", rc); + return; + } + + /* Test 1: Parameter validation - NULL args */ + rc = wolfTPM2_SpdmInit(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmConnect(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_SpdmIsConnected(NULL), 0); + AssertIntEQ(wolfTPM2_SpdmGetSessionId(NULL), 0); + rc = wolfTPM2_SpdmDisconnect(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmCleanup(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + + /* Test 2: Context lifecycle - init, check state, cleanup */ + rc = wolfTPM2_SpdmInit(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + /* When SPDM-only mode is active, auto-SPDM connects during Init. + * Otherwise, just initialized but not yet connected. */ + if (!dev.ctx.spdmOnlyDetected) { + AssertIntEQ(wolfTPM2_SpdmIsConnected(&dev), 0); + AssertIntEQ(wolfTPM2_SpdmGetSessionId(&dev), 0); + } + /* Cleanup */ + rc = wolfTPM2_SpdmCleanup(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + /* Idempotent cleanup */ + rc = wolfTPM2_SpdmCleanup(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + +#ifdef WOLFSPDM_NUVOTON + /* Test 3: Nuvoton-specific parameter validation */ + rc = wolfTPM2_SpdmSetNuvotonMode(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmEnable(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + { + WOLFSPDM_NUVOTON_STATUS status; + byte pubKey[256]; + word32 pubKeySz = sizeof(pubKey); + + rc = wolfTPM2_SpdmGetStatus(NULL, &status); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmGetStatus(&dev, NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmGetPubKey(NULL, pubKey, &pubKeySz); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmSetOnlyMode(NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + } +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef WOLFSPDM_NATIONS + /* Test 4: Nations-specific parameter validation */ + rc = wolfTPM2_SpdmSetNationsMode(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmNationsIdentityKeySet(NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + { + byte pubKey[256]; + word32 pubKeySz = sizeof(pubKey); + + rc = wolfTPM2_SpdmGetPubKey(NULL, pubKey, &pubKeySz); + AssertIntEQ(rc, BAD_FUNC_ARG); + } + /* Nations PSK wrapper parameter validation */ + rc = wolfTPM2_SpdmConnectNationsPsk(NULL, NULL, 0, NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + { + WOLFSPDM_NATIONS_STATUS nStatus; + rc = wolfTPM2_SpdmNationsGetStatus(NULL, &nStatus); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmNationsSetOnlyMode(NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmNationsPskSet(NULL, NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmNationsPskClear(NULL, NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + } +#endif /* WOLFSPDM_NATIONS */ + + wolfTPM2_Cleanup(&dev); + + printf("Passed\n"); +} +#endif /* WOLFTPM_SPDM */ + /* Test creating key and exporting keyblob as buffer, * importing and loading key. */ static void test_wolfTPM2_KeyBlob(TPM_ALG_ID alg) @@ -1098,6 +1199,9 @@ int unit_tests(int argc, char *argv[]) #endif test_wolfTPM2_Cleanup(); test_wolfTPM2_thread_local_storage(); +#ifdef WOLFTPM_SPDM + test_wolfTPM2_SPDM_Functions(); +#endif #endif /* !WOLFTPM2_NO_WRAPPER */ return 0; diff --git a/wolftpm/include.am b/wolftpm/include.am index 630436cf..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 a01eacf2..6611ad18 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -262,10 +262,13 @@ typedef enum { TPM_CC_SetCommandSetLock = CC_VEND + 0x030B, TPM_CC_GPIO_Config = CC_VEND + 0x030F, #endif -#ifdef WOLFTPM_NUVOTON +#if defined(WOLFTPM_NUVOTON) || defined(WOLFTPM_AUTODETECT) TPM_CC_NTC2_PreConfig = CC_VEND + 0x0211, TPM_CC_NTC2_GetConfig = CC_VEND + 0x0213, #endif +#if defined(WOLFTPM_NATIONS) || defined(WOLFTPM_AUTODETECT) + TPM_CC_Nations_IdentityKeySet = 0x20000708, +#endif #if defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673) TPM_CC_FieldUpgradeStartVendor = CC_VEND + 0x12F, TPM_CC_FieldUpgradeAbandonVendor = CC_VEND + 0x130, @@ -502,12 +505,85 @@ typedef enum { TPM_CAP_ECC_CURVES = 0x00000008, TPM_CAP_AUTH_POLICIES = 0x00000009, TPM_CAP_ACT = 0x0000000A, +#if defined(WOLFTPM_NATIONS) || defined(WOLFTPM_AUTODETECT) + TPM_CAP_PUB_KEYS = 0x0000000B, /* TPM 184: SPDM identity keys */ + TPM_CAP_SPDM_SESSION_INFO = 0x0000000C, /* TPM 184: SPDM session info */ + TPM_CAP_LAST = TPM_CAP_SPDM_SESSION_INFO, +#else TPM_CAP_LAST = TPM_CAP_ACT, +#endif TPM_CAP_VENDOR_PROPERTY = 0x00000100, } TPM_CAP_T; typedef UINT32 TPM_CAP; +#ifdef WOLFTPM_SPDM +/* TCG SPDM Binding for Secure Communication v1.0 Constants */ + +/* TCG SPDM Binding Message Tags */ +#define SPDM_TAG_CLEAR 0x8101 /* Clear (unencrypted) SPDM message */ +#define SPDM_TAG_SECURED 0x8201 /* Secured (encrypted) SPDM message */ + +/* SPDM Protocol Version */ +#define SPDM_VERSION_1_3 0x13 /* SPDM v1.3 */ + +/* SPDM protocol message codes, response codes, and error codes are + * defined in (the authoritative source). + * Include that header directly if you need SPDM protocol constants. */ + +/* SPDM Vendor Defined Codes (8-byte ASCII, used as VdCode in VENDOR_DEFINED) */ +#define SPDM_VDCODE_TPM2_CMD "TPM2_CMD" /* TPM command over SPDM session */ +#define SPDM_VDCODE_GET_PUBK "GET_PUBK" /* Get TPM's SPDM-Identity pub key */ +#define SPDM_VDCODE_GIVE_PUB "GIVE_PUB" /* Give host's SPDM-Identity pub key */ +#define SPDM_VDCODE_SPDMONLY "SPDMONLY" /* Lock/unlock SPDM-only mode */ +#define SPDM_VDCODE_GET_STS "GET_STS_" /* Get SPDM status */ + +/* SPDM Vendor Defined Code Length */ +#define SPDM_VDCODE_LEN 8 + +/* SPDM Session Constants (Nuvoton NPCT7xx) */ +#define SPDM_CONNECTION_ID 0 /* Single connection */ +#define SPDM_RSP_SESSION_ID 0xAEAD /* Responder session ID */ +#define SPDM_REQ_SESSION_ID 0x0001 /* Default requester session ID */ + +/* SPDM FIPS Indicator (TCG binding) */ +#define SPDM_FIPS_NON_FIPS 0x00 +#define SPDM_FIPS_APPROVED 0x01 + +/* SPDM Algorithm Set B (192-bit security strength) */ +#define SPDM_ALG_ECDSA_P384 0x0003 /* Signing algorithm */ +#define SPDM_ALG_SHA384 0x0002 /* Hash algorithm */ +#define SPDM_ALG_ECDHE_P384 0x0003 /* Key exchange algorithm */ +#define SPDM_ALG_AES256_GCM 0x0002 /* AEAD algorithm */ + +/* SPDM-Identity NV Indices */ +#define SPDM_NV_INDEX_TPM_KEY 0x01C20110 /* TPM SPDM-Identity key */ +#define SPDM_NV_INDEX_REQ_KEY 0x01C20111 /* Requester SPDM-Identity key */ + +/* NTC2 PreConfig CFG_H Bit Definitions for SPDM */ +#define NTC2_CFG_H_SPDM_ENABLE_BIT 1 /* Bit position in CFG_H */ +#define NTC2_CFG_H_SPDM_ENABLE 0x00 /* SPDM enabled (bit 1 = 0) */ +#define NTC2_CFG_H_SPDM_DISABLE 0x02 /* SPDM disabled (bit 1 = 1) */ + +/* SPDM ONLY mode sub-commands */ +#define SPDM_ONLY_LOCK 0x01 +#define SPDM_ONLY_UNLOCK 0x00 + +/* SPDM Message Sizes */ +#define SPDM_MAX_MSG_SIZE 4096 + +/* TCG SPDM Binding Header Size (per TCG SPDM Binding Spec): + * tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + fipsIndicator(2/BE) + + * reserved(4) = 16 bytes */ +#define SPDM_TCG_BINDING_HEADER_SIZE 16 + +/* SPDM Secured Message Header Size (per DSP0277): + * sessionId(4/LE) + sequenceNumber(8/LE) + length(2/LE) = 14 bytes + * where length = size of encrypted data + MAC */ +#define SPDM_SECURED_MSG_HEADER_SIZE 14 + +#endif /* WOLFTPM_SPDM */ + /* Property Tag */ typedef enum { TPM_PT_NONE = 0x00000000, @@ -816,6 +892,9 @@ typedef TPM_HANDLE TPMI_RH_CLEAR; typedef TPM_HANDLE TPMI_RH_NV_AUTH; typedef TPM_HANDLE TPMI_RH_LOCKOUT; typedef TPM_HANDLE TPMI_RH_NV_INDEX; +#ifdef WOLFTPM_SPDM +typedef TPM_HANDLE TPMI_DH_AC; /* Authenticated Controller handle */ +#endif typedef TPM_ALG_ID TPMI_ALG_HASH; typedef TPM_ALG_ID TPMI_ALG_ASYM; @@ -1045,7 +1124,6 @@ typedef struct TPML_ACT_DATA { TPMS_ACT_DATA actData[MAX_ACT_DATA]; } TPML_ACT_DATA; - /* Capabilities Structures */ typedef union TPMU_CAPABILITIES { @@ -1902,6 +1980,10 @@ typedef struct TPM2_CTX { #if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_LINUX_DEV_AUTODETECT) int fd; #endif +#ifdef WOLFTPM_SPDM + void* spdmCtx; /* Pointer to WOLFTPM2_SPDM_CTX when session active */ + unsigned int spdmOnlyDetected:1; /* TPM_RC_DISABLED from Startup */ +#endif } TPM2_CTX; @@ -1929,7 +2011,6 @@ typedef struct { WOLFTPM_API TPM_RC TPM2_GetCapability(GetCapability_In* in, GetCapability_Out* out); - typedef struct { TPMI_YES_NO fullTest; } SelfTest_In; @@ -2650,6 +2731,7 @@ typedef struct { } PolicyAuthValue_In; WOLFTPM_API TPM_RC TPM2_PolicyAuthValue(PolicyAuthValue_In* in); + typedef struct { TPMI_SH_POLICY policySession; } PolicyPassword_In; @@ -3142,6 +3224,53 @@ WOLFTPM_API int TPM2_ST33_FieldUpgradeCommand(TPM_CC cc, uint8_t* data, uint32_t WOLFTPM_API int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out); #endif /* Vendor GPIO Commands */ +/* NTC2 PreConfig/GetConfig for WOLFTPM_AUTODETECT (runtime vendor detection). + * When a specific vendor is not selected at compile time, the NTC2 types + * must still be available for SPDM enable via NTC2_PreConfig. */ +#if defined(WOLFTPM_AUTODETECT) && !defined(WOLFTPM_NUVOTON) && \ + !defined(WOLFTPM_ST33) + typedef struct { + BYTE Base0; + BYTE Base1; + BYTE GpioAltCfg; + BYTE GpioInitValue; + BYTE GpioPullUp; + BYTE GpioPushPull; + BYTE Cfg_A; + BYTE Cfg_B; + BYTE Cfg_C; + BYTE Cfg_D; + BYTE Cfg_E; + BYTE Cfg_F; + BYTE Cfg_G; + BYTE Cfg_H; + BYTE Cfg_I; + BYTE Cfg_J; + BYTE isValid; + BYTE isLocked; + } CFG_STRUCT; + + typedef struct { + TPMI_RH_PLATFORM authHandle; + CFG_STRUCT preConfig; + } NTC2_PreConfig_In; + WOLFTPM_API int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in); + + typedef struct { + CFG_STRUCT preConfig; + } NTC2_GetConfig_Out; + WOLFTPM_API int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out); +#endif /* WOLFTPM_AUTODETECT && !WOLFTPM_NUVOTON && !WOLFTPM_ST33 */ + +#ifdef WOLFTPM_NATIONS + /* Nations Technology NS350 Vendor Commands */ + typedef struct { + TPMI_RH_PLATFORM authHandle; + UINT32 configuration; /* 1 = set (provision), 0 = unset */ + } Nations_IdentityKeySet_In; + WOLFTPM_API int TPM2_Nations_IdentityKeySet(Nations_IdentityKeySet_In* in); +#endif /* WOLFTPM_NATIONS */ + /* Non-standard API's */ diff --git a/wolftpm/tpm2_packet.h b/wolftpm/tpm2_packet.h index 563fc81a..14f514be 100644 --- a/wolftpm/tpm2_packet.h +++ b/wolftpm/tpm2_packet.h @@ -180,6 +180,7 @@ WOLFTPM_LOCAL void TPM2_Packet_AppendSignature(TPM2_Packet* packet, TPMT_SIGNATU WOLFTPM_LOCAL void TPM2_Packet_ParseSignature(TPM2_Packet* packet, TPMT_SIGNATURE* sig); WOLFTPM_LOCAL void TPM2_Packet_ParseAttest(TPM2_Packet* packet, TPMS_ATTEST* out); + WOLFTPM_LOCAL TPM_RC TPM2_Packet_Parse(TPM_RC rc, TPM2_Packet* packet); WOLFTPM_LOCAL int TPM2_Packet_Finalize(TPM2_Packet* packet, TPM_ST tag, TPM_CC cc); diff --git a/wolftpm/tpm2_spdm.h b/wolftpm/tpm2_spdm.h new file mode 100644 index 00000000..7cc1a295 --- /dev/null +++ b/wolftpm/tpm2_spdm.h @@ -0,0 +1,204 @@ +/* tpm2_spdm.h + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* SPDM Secure Session Support for wolfTPM + * + * Implements SPDM (Security Protocol and Data Model) secure communication + * between host and TPM using the wolfSPDM library for all protocol operations. + * + * References: + * - DMTF DSP0274 (SPDM v1.2/1.3) + * - TCG SPDM Binding for Secure Communication v1.0 + * - TCG TPM 2.0 Library Specification v1.84 + * + * Architecture: + * Application -> wolfTPM2 Wrapper -> SPDM Transport (this module) -> SPI HAL + * | + * wolfSPDM library (spdm/ subdirectory) + * (all SPDM protocol logic) + * + * This module provides: + * - SPDM context management (init/free) + * - Secured exchange with VENDOR_DEFINED wrapping (Nuvoton) + * - TPM-specific SPDM enable/disable via NTC2 vendor commands + * - I/O callback adapter to route wolfSPDM through TPM transport + * + * wolfSPDM (spdm/) provides: + * - Full SPDM protocol implementation (handshake, key derivation, encryption) + * - Standard and Nuvoton mode support + * - TCG binding message framing (for Nuvoton TPMs) + * - All cryptographic operations + */ + +#ifndef __TPM2_SPDM_H__ +#define __TPM2_SPDM_H__ + +#include + +#ifdef WOLFTPM_SPDM + +/* wolfSPDM library provides all SPDM protocol implementation */ +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* Forward declarations */ +struct WOLFTPM2_SPDM_CTX; + +/* -------------------------------------------------------------------------- */ +/* SPDM Context + * + * This is a thin wrapper around WOLFSPDM_CTX. wolfSPDM handles all the + * SPDM protocol state, key derivation, and encryption. This context adds + * only TPM-specific fields needed for integration with wolfTPM2. + * -------------------------------------------------------------------------- */ + +typedef struct WOLFTPM2_SPDM_CTX { + /* wolfSPDM context - handles all SPDM protocol operations */ + WOLFSPDM_CTX* spdmCtx; + + /* Reference to TPM context for NTC2 vendor commands */ + TPM2_CTX* tpmCtx; + + /* SPDM-only mode tracking (for Nuvoton TPMs) */ + int spdmOnlyLocked; + +#ifndef WOLFSPDM_DYNAMIC_MEMORY + /* Static wolfSPDM context buffer, aligned for WOLFSPDM_CTX cast */ + XGEN_ALIGN byte spdmBuf[WOLFSPDM_CTX_STATIC_SIZE]; +#endif +} WOLFTPM2_SPDM_CTX; + +/* -------------------------------------------------------------------------- */ +/* SPDM Core API Functions + * -------------------------------------------------------------------------- */ + +/** + * Initialize SPDM context with wolfSPDM. + * Must be called before any other SPDM function. + * + * @param ctx wolfTPM2 SPDM context + * @param ioCb I/O callback for sending/receiving SPDM messages + * @param userCtx User context passed to the I/O callback + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_InitCtx( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_IO_CB ioCb, + void* userCtx +); + +/** + * Set the TPM context for NTC2 vendor commands. + * Only needed for Nuvoton TPMs when using wolfTPM2_SPDM_Enable(). + * + * @param ctx wolfTPM2 SPDM context + * @param tpmCtx TPM2 context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SetTPMCtx( + WOLFTPM2_SPDM_CTX* ctx, + TPM2_CTX* tpmCtx +); + +/** + * Enable SPDM on the TPM via NTC2_PreConfig. + * Requires platform hierarchy authorization. + * TPM must be reset after this for SPDM to take effect. + * NOTE: This is a Nuvoton-specific feature. + * + * @param ctx wolfTPM2 SPDM context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_Enable( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Disable SPDM on a Nuvoton TPM via NTC2_PreConfig. + * Sets Cfg_H bit 1 to disable SPDM. Requires TPM reset to take effect. + * + * @param ctx wolfTPM2 SPDM context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_Disable( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Perform a secured message exchange (encrypt, send, receive, decrypt). + * Wraps wolfSPDM_SecuredExchange() for TPM command/response. + * + * @param ctx wolfTPM2 SPDM context + * @param cmdPlain Plaintext command to send + * @param cmdSz Size of command + * @param rspPlain Buffer for plaintext response + * @param rspSz [in] Size of response buffer, [out] Actual response size + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SecuredExchange( + WOLFTPM2_SPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz +); + +/** + * Free all SPDM context resources. + * Safe to call on an already-cleaned-up or zero-initialized context. + * + * @param ctx wolfTPM2 SPDM context + */ +WOLFTPM_API void wolfTPM2_SPDM_FreeCtx( + WOLFTPM2_SPDM_CTX* ctx +); + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Functions (requires wolfSPDM with --enable-nuvoton) + * -------------------------------------------------------------------------- */ + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) + +/** + * Set the built-in TIS I/O callback for routing SPDM through TPM SPI/I2C. + * Uses the TPM TIS FIFO to send/receive raw SPDM messages. + * TCG framing is handled internally by wolfSPDM_SendReceive(). + * Must be called after wolfTPM2_SPDM_InitCtx() and SetTPMCtx(). + * + * Only available on hardware TPM builds (not LINUX_DEV, SWTPM, or WINAPI). + * + * @param ctx wolfTPM2 SPDM context (with tpmCtx already set) + * @return 0 on success, NOT_COMPILED_IN if TIS not available + */ +WOLFTPM_API int wolfTPM2_SPDM_SetTisIO( + WOLFTPM2_SPDM_CTX* ctx +); + +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFTPM_SPDM */ + +#endif /* __TPM2_SPDM_H__ */ diff --git a/wolftpm/tpm2_types.h b/wolftpm/tpm2_types.h index d37dee13..48ab1466 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -767,6 +767,9 @@ typedef int64_t INT64; #ifndef MAX_ACT_DATA #define MAX_ACT_DATA (MAX_CAP_DATA / sizeof(TPMS_ACT_DATA)) #endif +#ifndef MAX_AC_HANDLES +#define MAX_AC_HANDLES 16 +#endif /* ---------------------------------------------------------------------------*/ diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index a7114037..678a6578 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -23,6 +23,9 @@ #define __TPM2_WRAP_H__ #include +#ifdef WOLFTPM_SPDM +#include +#endif #ifdef __cplusplus extern "C" { @@ -57,6 +60,10 @@ typedef struct WOLFTPM2_SESSION { typedef struct WOLFTPM2_DEV { TPM2_CTX ctx; TPM2_AUTH_SESSION session[MAX_SESSION_NUM]; +#ifdef WOLFTPM_SPDM + WOLFTPM2_SPDM_CTX spdmCtxData; + WOLFTPM2_SPDM_CTX* spdmCtx; /* NULL = not initialized */ +#endif } WOLFTPM2_DEV; /* Public Key with Handle. @@ -163,6 +170,10 @@ typedef struct WOLFTPM2_CAPS { word16 fips140_2 : 1; /* using FIPS mode */ word16 cc_eal4 : 1; /* Common Criteria EAL4+ */ word16 req_wait_state : 1; /* requires SPI wait state */ +#ifdef WOLFTPM_SPDM + word32 acHandleCount; /* Number of AC handles discovered */ + TPM_HANDLE acHandles[MAX_AC_HANDLES]; /* AC handles */ +#endif } WOLFTPM2_CAPS; @@ -412,6 +423,261 @@ WOLFTPM_API int wolfTPM2_GetCapabilities(WOLFTPM2_DEV* dev, WOLFTPM2_CAPS* caps) */ WOLFTPM_API int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles); +#ifdef WOLFTPM_SPDM +/* SPDM Secure Session Wrapper API + * + * These functions provide a high-level interface for SPDM secure sessions. + * All SPDM protocol logic is implemented in the wolfSPDM library. + */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Initialize SPDM support on a wolfTPM2 device. + Allocates and configures the SPDM context using wolfSPDM. + After init, call wolfTPM2_SpdmConnect to establish a secure session. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + \return MEMORY_E: memory allocation failed + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmInit(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish an SPDM secure session (full handshake). + Uses standard SPDM flow: GET_VERSION -> GET_CAPABILITIES -> + NEGOTIATE_ALGORITHMS -> KEY_EXCHANGE -> FINISH. + + \return TPM_RC_SUCCESS: session established + \return TPM_RC_FAILURE: handshake failed + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmConnect(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Check if an SPDM secure session is currently active. + + \return 1 if connected, 0 if not + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmIsConnected(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get the current SPDM session ID. + + \return Session ID, or 0 if not connected + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Disconnect the SPDM secure session. + After this, TPM commands are sent in the clear. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Free SPDM context and resources. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev); + +#if defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) +/*! + \ingroup wolfTPM2_Wrappers + \brief Get the TPM's SPDM-Identity public key (shared TCG function). + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param pubKey output buffer for the public key + \param pubKeySz in/out: buffer size / actual key size +*/ +WOLFTPM_API int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, + byte* pubKey, word32* pubKeySz); +#endif /* WOLFSPDM_NUVOTON || WOLFSPDM_NATIONS */ + +#ifdef WOLFSPDM_NUVOTON +/* Nuvoton-specific SPDM functions (requires wolfSPDM with --enable-nuvoton) */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Configure for Nuvoton TPM SPDM mode. + Must be called before wolfTPM2_SpdmConnect() for Nuvoton TPMs. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmSetNuvotonMode(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Enable SPDM on the TPM via NTC2_PreConfig vendor command. + Requires platform hierarchy auth. TPM must be reset after this call. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmEnable(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Disable SPDM on Nuvoton TPM via NTC2_PreConfig. + Sets Cfg_H bit 1 to disable SPDM. Requires TPM reset to take effect. + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmDisable(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish Nuvoton SPDM secure session with mutual authentication. + Uses Nuvoton flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> + GIVE_PUB_KEY -> FINISH. + + \return TPM_RC_SUCCESS: session established + \return TPM_RC_FAILURE: handshake failed + + \param dev pointer to a WOLFTPM2_DEV structure + \param reqPubKey host's ECDSA P-384 public key (TPMT_PUBLIC format) + \param reqPubKeySz size of reqPubKey in bytes + \param reqPrivKey host's ECDSA P-384 private key (raw 48 bytes) + \param reqPrivKeySz size of reqPrivKey in bytes +*/ +WOLFTPM_API int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get SPDM status from the TPM (GET_STS_ vendor command). + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param status output: SPDM status information +*/ +WOLFTPM_API int wolfTPM2_SpdmGetStatus(WOLFTPM2_DEV* dev, + WOLFSPDM_NUVOTON_STATUS* status); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Lock or unlock SPDM-only mode. + When locked, TPM only accepts commands over SPDM secure channel. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure + \param lock 1 to lock SPDM-only mode, 0 to unlock +*/ +WOLFTPM_API int wolfTPM2_SpdmSetOnlyMode(WOLFTPM2_DEV* dev, int lock); + +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef WOLFSPDM_NATIONS +/* Nations Technology NS350 SPDM functions (requires --enable-nations --enable-spdm) */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Configure for Nations Technology NS350 SPDM mode. + Must be called before wolfTPM2_SpdmConnectNations(). + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmSetNationsMode(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish Nations SPDM secure session (identity key mode). + Uses TCG flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> + GIVE_PUB_KEY -> FINISH. + + \return TPM_RC_SUCCESS: session established + \return TPM_RC_FAILURE: handshake failed + + \param dev pointer to a WOLFTPM2_DEV structure + \param reqPubKey host's ECDSA P-384 public key (TPMT_PUBLIC format, or NULL for auto-gen) + \param reqPubKeySz size of reqPubKey in bytes + \param reqPrivKey host's ECDSA P-384 private key (raw 48 bytes, or NULL for auto-gen) + \param reqPrivKeySz size of reqPrivKey in bytes +*/ +WOLFTPM_API int wolfTPM2_SpdmConnectNations(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Set/unset SPDM identity key provisioning on Nations NS350. + Uses vendor command TPM2_VendorSpdmIdentityKeySet (0x20000708). + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param set 1 to provision identity key, 0 to un-provision +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsIdentityKeySet(WOLFTPM2_DEV* dev, int set); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish Nations SPDM secure session (PSK mode). +*/ +WOLFTPM_API int wolfTPM2_SpdmConnectNationsPsk(WOLFTPM2_DEV* dev, + const byte* psk, word32 pskSz, + const byte* hint, word32 hintSz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get SPDM status from Nations TPM (PSK mode). +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsGetStatus(WOLFTPM2_DEV* dev, + WOLFSPDM_NATIONS_STATUS* status); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Lock or unlock SPDM-only mode on Nations TPM (PSK mode). +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsSetOnlyMode(WOLFTPM2_DEV* dev, int lock); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Provision PSK on Nations TPM. +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsPskSet(WOLFTPM2_DEV* dev, + const byte* psk, word32 pskSz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Clear PSK from Nations TPM. Requires ClearAuth from PSK_SET. +*/ +WOLFTPM_API int wolfTPM2_SpdmNationsPskClear(WOLFTPM2_DEV* dev, + const byte* clearAuth, word32 clearAuthSz); + +#endif /* WOLFSPDM_NATIONS */ + +#endif /* WOLFTPM_SPDM */ /*! \ingroup wolfTPM2_Wrappers diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 41059918..0c3f79d8 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -6,6 +6,10 @@ if(CONFIG_WOLFTPM) ${ZEPHYR_CURRENT_MODULE_DIR}/src/*.c ${ZEPHYR_CURRENT_MODULE_DIR}/hal/*.c ) + # Exclude transport backends not applicable to Zephyr + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_linux\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_winapi\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_spdm\\.c$") target_sources(app PRIVATE ${wolftpm_sources}) if(CONFIG_WOLFTPM_DEBUG)