Add fwtpm SPDM support

pull/510/head
Aidan Garske 2026-06-03 10:28:27 -07:00
parent 3cfbec2a4d
commit eb21c17a7a
27 changed files with 2541 additions and 133 deletions

176
.github/workflows/spdm-test.yml vendored 100644
View File

@ -0,0 +1,176 @@
name: SPDM Test
# Validates wolfSPDM end-to-end against the fwtpm SPDM responder on
# ubuntu-latest. Replaces hw-spdm-test.yml's self-hosted hardware runs;
# same protocol coverage (PSK + TCG) but no silicon required.
on:
push:
branches: [master]
paths:
- 'src/spdm/**'
- 'wolftpm/spdm/**'
- 'src/fwtpm/**'
- 'wolftpm/fwtpm/**'
- 'examples/spdm/**'
- 'src/tpm2_wrap.c'
- 'src/tpm2_spdm.c'
- 'src/tpm2.c'
- 'configure.ac'
- '.github/workflows/spdm-test.yml'
- 'tests/unit_tests.c'
pull_request:
branches: [master]
permissions: read-all
jobs:
# Build-only matrix: every supported SPDM configure permutation compiles
# cleanly. Catches missing `#ifdef WOLFTPM_SPDM_*` guards in headers/sources
# and per-mode include.am gating regressions.
build-only:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
include:
- name: spdm-tcg-only
wolftpm_config: "--enable-fwtpm --enable-spdm --enable-tcg --disable-psk"
- name: spdm-both
wolftpm_config: "--enable-fwtpm --enable-spdm --enable-tcg --enable-psk"
- name: spdm-nuvoton
wolftpm_config: "--enable-fwtpm --enable-spdm --enable-nuvoton"
- name: spdm-nations
wolftpm_config: "--enable-fwtpm --enable-spdm --enable-nations"
- name: spdm-full-vendors
wolftpm_config: "--enable-fwtpm --enable-spdm --enable-tcg --enable-psk --enable-nuvoton --enable-nations"
- name: spdm-requester-only
wolftpm_config: "--enable-spdm --enable-nuvoton --enable-nations"
- name: spdm-debug-on
wolftpm_config: "--enable-fwtpm --enable-spdm --enable-tcg --enable-psk --enable-debug"
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df
with:
egress-policy: audit
- name: Checkout wolfTPM
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
- name: Checkout wolfSSL
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: wolfssl/wolfssl
path: wolfssl
persist-credentials: false
- name: Build & install wolfSSL
working-directory: ./wolfssl
run: |
./autogen.sh
./configure --prefix=$HOME/wolfssl-install \
--enable-wolftpm --enable-pkcallbacks --enable-keygen \
--enable-aescfb \
CFLAGS="-DWC_RSA_NO_PADDING"
make -j"$(nproc)"
make install
- name: Build wolfTPM (${{ matrix.name }})
run: |
./autogen.sh
./configure ${{ matrix.wolftpm_config }} \
--with-wolfcrypt=$HOME/wolfssl-install
make -j"$(nproc)"
# End-to-end matrix: same protocol coverage as the old hw-spdm-test, but
# against fwtpm_server instead of real silicon. fwtpm-tcg mirrors the
# Nuvoton 6-step sequence (status, connect, lock, unit.test, unlock,
# cleartext caps). fwtpm-psk mirrors the Nations-PSK 10-step sequence.
e2e:
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
include:
- name: fwtpm-tcg
mode: fwtpm-tcg
- name: fwtpm-psk
mode: fwtpm-psk
steps:
- name: Harden Runner
uses: step-security/harden-runner@6c3c2f2c1c457b00c10c4848d6f5491db3b629df
with:
egress-policy: audit
- name: Checkout wolfTPM
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
- name: Checkout wolfSSL
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
repository: wolfssl/wolfssl
path: wolfssl
persist-credentials: false
- name: Build & install wolfSSL
working-directory: ./wolfssl
run: |
./autogen.sh
./configure --prefix=$HOME/wolfssl-install \
--enable-wolftpm --enable-pkcallbacks --enable-keygen \
--enable-aescfb \
CFLAGS="-DWC_RSA_NO_PADDING"
make -j"$(nproc)"
make install
- name: Build wolfTPM with full SPDM + vendor support
# spdm_ctrl's --connect/--status/--lock CLI dispatch is gated on
# WOLFSPDM_NUVOTON (TCG flow) and the --psk-set/--psk-clear CLI
# on WOLFSPDM_NATIONS (PSK flow). Build with both vendor flags so
# the same binary drives both fwtpm-tcg and fwtpm-psk sequences.
run: |
./autogen.sh
./configure --enable-fwtpm --enable-spdm \
--enable-tcg --enable-psk \
--enable-nuvoton --enable-nations \
--enable-debug --enable-swtpm \
--with-wolfcrypt=$HOME/wolfssl-install
make -j"$(nproc)"
- name: Run wolfTPM make check (unit tests)
env:
LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }}
run: |
set -eo pipefail
make check 2>&1 | tee make-check-${{ matrix.name }}.log
- name: Run spdm_test.sh ${{ matrix.mode }}
env:
LD_LIBRARY_PATH: ${{ format('{0}/wolfssl-install/lib', env.HOME) }}
run: |
set -eo pipefail
./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl ${{ matrix.mode }} 2>&1 \
| tee spdm-${{ matrix.mode }}.log
- name: Upload logs on failure
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: spdm-logs-${{ matrix.name }}
path: |
*.log
config.log
test-suite.log
tests/*.log
/tmp/fwtpm_spdm_test.log
retention-days: 14

View File

@ -39,6 +39,7 @@ Portable TPM 2.0 project designed for embedded use.
* Support for reading Endorsement certificates (EK Credential Profile).
* Includes a portable firmware TPM 2.0 implementation (fwTPM, also known as fTPM / swtpm) for embedded platforms without a discrete TPM chip. See [Firmware TPM (fwTPM / fTPM / swtpm)](#firmware-tpm-fwtpm--ftpm--swtpm) below.
* **Post-quantum cryptography support** via TPM 2.0 Library Specification v1.85: ML-DSA (FIPS 204) signing and ML-KEM (FIPS 203) key encapsulation, enabled with `--enable-pqc` (alias for `--enable-v185`). Auto-detected when `--enable-fwtpm` is built against a wolfCrypt that has ML-DSA + ML-KEM. Both the client library and the fwTPM server implement the eight new v1.85 PQC commands. See [Post-Quantum Cryptography (v1.85)](#post-quantum-cryptography-v185) below.
* **SPDM attestation support** (DMTF DSP0274) over the TCG SPDM-over-TPM binding, with a TCG certificate handshake and a DSP0274 pre-shared-key (PSK) handshake, enabled with `--enable-spdm`. The fwTPM server includes an SPDM 1.3 responder so the stack can be exercised end-to-end in CI without discrete silicon. See [SPDM Attestation](#spdm-attestation) below.
Note: See [examples/README.md](examples/README.md) for details on using the examples.
@ -122,6 +123,27 @@ decisions — see
[docs/FWTPM.md](docs/FWTPM.md#tpm-20-v185-post-quantum-support).
## SPDM Attestation
wolfTPM implements SPDM (Security Protocol and Data Model, DMTF DSP0274)
for TPM 2.0 attestation over the TCG SPDM-over-TPM binding. Both the TCG
certificate handshake and the DSP0274 pre-shared-key (PSK) handshake are
supported, negotiating SPDM protocol version 1.3.
For testing without discrete silicon, the `fwtpm_server` ships an SPDM 1.3
responder that drives the same handshake the real Nuvoton and Nations
parts use, so the SPDM stack can be exercised end-to-end in CI.
Build with `--enable-spdm` plus at least one handshake mode
(`--enable-tcg` for the certificate handshake, `--enable-psk` for the PSK
handshake). Vendor wire-format adapters are optional (`--enable-nuvoton`,
`--enable-nations`).
See [src/spdm/README.md](src/spdm/README.md) and
[src/fwtpm/README.md](src/fwtpm/README.md) for build instructions,
responder modes, and the end-to-end test scripts.
## TPM 2.0 Overview
### Hierarchies

View File

@ -648,6 +648,20 @@ 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 TCG Binding mode. Auto-on with --enable-spdm + (fwtpm|nuvoton|nations).
AC_ARG_ENABLE([tcg],
[AS_HELP_STRING([--enable-tcg],[Enable SPDM TCG Binding mode (default: auto under --enable-spdm)])],
[ ENABLED_TCG=$enableval ],
[ ENABLED_TCG=detect ]
)
# SPDM PSK mode. Auto-on with --enable-spdm + --enable-nations.
AC_ARG_ENABLE([psk],
[AS_HELP_STRING([--enable-psk],[Enable SPDM PSK mode (default: auto under --enable-spdm + --enable-nations)])],
[ ENABLED_PSK=$enableval ],
[ ENABLED_PSK=detect ]
)
if test "x$ENABLED_SPDM" = "xyes"
then
if test "x$ENABLED_FWTPM_ONLY" = "xyes"; then
@ -655,6 +669,70 @@ then
fi
AC_DEFINE([WOLFTPM_SPDM], [1], [Enable SPDM support])
if test "x$ENABLED_TCG" = "xdetect"
then
if test "x$ENABLED_FWTPM" = "xyes" || \
test "x$ENABLED_NUVOTON" = "xyes" || \
test "x$ENABLED_NATIONS" = "xyes"
then
ENABLED_TCG=yes
else
ENABLED_TCG=no
fi
fi
if test "x$ENABLED_PSK" = "xdetect"
then
if test "x$ENABLED_NATIONS" = "xyes"
then
ENABLED_PSK=yes
else
ENABLED_PSK=no
fi
fi
if test "x$ENABLED_TCG" = "xyes"
then
AC_DEFINE([WOLFTPM_SPDM_TCG], [1], [Enable SPDM TCG Binding mode])
AC_MSG_NOTICE([SPDM TCG Binding mode enabled])
fi
if test "x$ENABLED_PSK" = "xyes"
then
AC_DEFINE([WOLFTPM_SPDM_PSK], [1], [Enable SPDM PSK mode])
AC_MSG_NOTICE([SPDM PSK mode enabled])
fi
if test "x$ENABLED_FWTPM" = "xyes" && \
test "x$ENABLED_TCG" != "xyes" && \
test "x$ENABLED_PSK" != "xyes"
then
AC_MSG_ERROR([--enable-fwtpm --enable-spdm requires at least one of --enable-tcg or --enable-psk])
fi
# PSK_EXCHANGE / PSK_FINISH are carried over the TCG SPDM Binding
# transport (tag 0x8101 / 0x8201), so a PSK build without TCG framing
# support cannot actually send or receive PSK handshake messages.
if test "x$ENABLED_PSK" = "xyes" && test "x$ENABLED_TCG" != "xyes"
then
AC_MSG_ERROR([--enable-psk requires --enable-tcg (PSK handshake uses TCG SPDM Binding framing)])
fi
# Vendor format adapters layer on top of the spec implementation, so
# the spec flag must be enabled for the vendor's wire-format wrappers
# to even make sense.
if test "x$ENABLED_NUVOTON" = "xyes" && test "x$ENABLED_TCG" != "xyes"
then
AC_MSG_ERROR([--enable-nuvoton requires --enable-tcg (Nuvoton uses the TCG SPDM Binding spec)])
fi
if test "x$ENABLED_NATIONS" = "xyes" && test "x$ENABLED_TCG" != "xyes"
then
AC_MSG_ERROR([--enable-nations requires --enable-tcg (Nations uses the TCG SPDM Binding spec)])
fi
if test "x$ENABLED_NATIONS" = "xyes" && test "x$ENABLED_PSK" != "xyes"
then
AC_MSG_ERROR([--enable-nations requires --enable-psk (PSK_SET / PSK_CLR provisioning uses DSP0274 PSK)])
fi
# Nuvoton SPDM support (required for SPDM in wolfTPM)
if test "x$ENABLED_NUVOTON" = "xyes"
then
@ -669,10 +747,27 @@ then
AC_MSG_NOTICE([Nations Technology SPDM vendor commands enabled])
fi
# Responder only makes sense with fwTPM (real silicon speaks SPDM in firmware).
if test "x$ENABLED_FWTPM" = "xyes"
then
AC_DEFINE([WOLFTPM_SPDM_RESPONDER], [1], [Enable SPDM responder in fwTPM])
AC_MSG_NOTICE([SPDM responder enabled in fwtpm_server])
ENABLED_FWTPM_SPDM=yes
if test "x$WOLFTPM_HW_SELECTED" = "xyes"
then
AC_MSG_WARN([SPDM responder is enabled alongside a hardware vendor flag. The responder runs only inside fwtpm_server; real silicon ignores it.])
fi
fi
if test "x$ax_enable_debug" != "xno"
then
AC_DEFINE([WOLFSPDM_DEBUG], [1], [SPDM: Enable debug output])
fi
else
if test "x$ENABLED_TCG" = "xyes" || test "x$ENABLED_PSK" = "xyes"
then
AC_MSG_ERROR([--enable-tcg / --enable-psk require --enable-spdm])
fi
fi
# PQC / v1.85 enablement.
@ -822,10 +917,13 @@ 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" || test "x$ENABLED_FWTPM_TIS" = "xyes"])
AM_CONDITIONAL([BUILD_SPDM], [test "x$ENABLED_SPDM" = "xyes"])
AM_CONDITIONAL([BUILD_SPDM_TCG], [test "x$ENABLED_TCG" = "xyes"])
AM_CONDITIONAL([BUILD_SPDM_PSK], [test "x$ENABLED_PSK" = "xyes"])
AM_CONDITIONAL([BUILD_FWTPM], [test "x$ENABLED_FWTPM" = "xyes"])
AM_CONDITIONAL([BUILD_FWTPM_ONLY], [test "x$ENABLED_FWTPM_ONLY" = "xyes"])
AM_CONDITIONAL([BUILD_FWTPM_TIS], [test "x$ENABLED_FWTPM_TIS" = "xyes"])
AM_CONDITIONAL([BUILD_FWTPM_SPDM], [test "x$ENABLED_FWTPM_SPDM" = "xyes"])
CREATE_HEX_VERSION

View File

@ -529,6 +529,7 @@ static int ctrl_nations_connect(WOLFTPM2_DEV* dev)
int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[])
{
int rc, i;
int useNations = 0;
WOLFTPM2_DEV dev;
if (argc <= 1) { usage(); return 0; }
@ -536,6 +537,18 @@ int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[])
if (XSTRCMP(argv[i], "-h") == 0 || XSTRCMP(argv[i], "--help") == 0) {
usage(); return 0;
}
if (XSTRNCMP(argv[i], "--vendor=", 9) == 0) {
if (XSTRCMP(argv[i] + 9, "nations") == 0) {
useNations = 1;
}
else if (XSTRCMP(argv[i] + 9, "nuvoton") == 0) {
useNations = 0;
}
else {
printf("Unknown --vendor= value: %s\n", argv[i] + 9);
return BAD_FUNC_ARG;
}
}
}
rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx);
@ -551,70 +564,136 @@ int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[])
return rc;
}
#ifdef WOLFSPDM_NUVOTON
/* Vendor selection: --vendor=nuvoton|nations chooses at runtime when
* both adapters are built. Single-vendor builds default to that
* vendor; dual builds default to Nuvoton unless overridden. */
#if defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS)
if (useNations) {
wolfTPM2_SpdmSetNationsMode(&dev);
}
else {
wolfTPM2_SpdmSetNuvotonMode(&dev);
}
wolfTPM2_SPDM_SetTisIO(dev.spdmCtx);
#elif defined(WOLFSPDM_NUVOTON)
(void)useNations;
wolfTPM2_SpdmSetNuvotonMode(&dev);
wolfTPM2_SPDM_SetTisIO(dev.spdmCtx);
#elif defined(WOLFSPDM_NATIONS)
(void)useNations;
wolfTPM2_SpdmSetNationsMode(&dev);
wolfTPM2_SPDM_SetTisIO(dev.spdmCtx);
#ifdef DEBUG_WOLFTPM
wolfSPDM_SetDebug(dev.spdmCtx->spdmCtx, 1);
#else
(void)useNations;
#endif
#if defined(DEBUG_WOLFTPM) && \
(defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS))
wolfSPDM_SetDebug(dev.spdmCtx->spdmCtx, 1);
#endif
for (i = 1; i < argc; i++) {
int matched = 0;
/* --vendor= was consumed during the parse pass above; skip it
* here so it isn't reported as Unknown. */
if (XSTRNCMP(argv[i], "--vendor=", 9) == 0) {
continue;
}
#ifdef WOLFSPDM_NUVOTON
if (XSTRCMP(argv[i], "--enable") == 0)
rc = ctrl_enable(&dev);
else if (XSTRCMP(argv[i], "--disable") == 0)
rc = ctrl_disable(&dev);
else if (XSTRCMP(argv[i], "--status") == 0)
rc = ctrl_status(&dev);
else if (XSTRCMP(argv[i], "--get-pubkey") == 0)
rc = ctrl_get_pubkey(&dev);
else if (XSTRCMP(argv[i], "--connect") == 0)
rc = ctrl_connect(&dev);
else if (XSTRCMP(argv[i], "--lock") == 0)
rc = ctrl_lock(&dev, 1);
else if (XSTRCMP(argv[i], "--unlock") == 0)
rc = ctrl_lock(&dev, 0);
else
#endif
/* Nuvoton wire-format adapter: TCG handshake (Nuvoton flavor -
* skips GET_CAPABILITIES / NEGOTIATE_ALGORITHMS, fixed Algo Set B),
* Nuvoton GET_STATUS / SPDMONLY payload format, and the proprietary
* SPDM_ENABLE / SPDM_DISABLE vendor commands. In dual-vendor
* builds we only enter this block when useNations == 0. */
if (!matched && !useNations && XSTRCMP(argv[i], "--connect") == 0) {
rc = ctrl_connect(&dev); matched = 1;
}
else if (!matched && !useNations
&& XSTRCMP(argv[i], "--get-pubkey") == 0) {
rc = ctrl_get_pubkey(&dev); matched = 1;
}
else if (!matched && !useNations
&& XSTRCMP(argv[i], "--enable") == 0) {
rc = ctrl_enable(&dev); matched = 1;
}
else if (!matched && !useNations
&& XSTRCMP(argv[i], "--disable") == 0) {
rc = ctrl_disable(&dev); matched = 1;
}
else if (!matched && !useNations
&& XSTRCMP(argv[i], "--status") == 0) {
rc = ctrl_status(&dev); matched = 1;
}
else if (!matched && !useNations
&& XSTRCMP(argv[i], "--lock") == 0) {
rc = ctrl_lock(&dev, 1); matched = 1;
}
else if (!matched && !useNations
&& XSTRCMP(argv[i], "--unlock") == 0) {
rc = ctrl_lock(&dev, 0); matched = 1;
}
#endif /* WOLFSPDM_NUVOTON */
#ifdef WOLFSPDM_NATIONS
if (XSTRCMP(argv[i], "--identity-key-set") == 0)
rc = ctrl_nations_identity_key_set(&dev, 1);
else if (XSTRCMP(argv[i], "--identity-key-unset") == 0)
rc = ctrl_nations_identity_key_set(&dev, 0);
else if (XSTRCMP(argv[i], "--get-pubkey") == 0)
rc = ctrl_nations_get_pubkey(&dev);
else if (XSTRCMP(argv[i], "--connect") == 0)
rc = ctrl_nations_connect(&dev);
else if (XSTRCMP(argv[i], "--status") == 0)
rc = ctrl_nations_status(&dev);
else if (XSTRCMP(argv[i], "--psk") == 0 && i + 1 < argc)
/* Nations wire-format adapter: TCG handshake (Nations flavor -
* always sends GET_CAPABILITIES), NSING GET_STS_ / SPDMONLY,
* PSK_SET_ / PSK_CLR_ provisioning, IdentityKeySet TPM2 cmd. */
if (!matched && XSTRCMP(argv[i], "--identity-key-set") == 0) {
rc = ctrl_nations_identity_key_set(&dev, 1); matched = 1;
}
else if (!matched && XSTRCMP(argv[i], "--identity-key-unset") == 0) {
rc = ctrl_nations_identity_key_set(&dev, 0); matched = 1;
}
else if (!matched && XSTRCMP(argv[i], "--get-pubkey") == 0) {
rc = ctrl_nations_get_pubkey(&dev); matched = 1;
}
else if (!matched && XSTRCMP(argv[i], "--connect") == 0) {
rc = ctrl_nations_connect(&dev); matched = 1;
}
else if (!matched && XSTRCMP(argv[i], "--status") == 0) {
rc = ctrl_nations_status(&dev); matched = 1;
}
else if (!matched && XSTRCMP(argv[i], "--psk") == 0
&& i + 1 < argc) {
rc = ctrl_nations_psk_connect(&dev, argv[++i]);
else if (XSTRCMP(argv[i], "--psk-set") == 0 && i + 2 < argc)
{
matched = 1;
}
else if (!matched && XSTRCMP(argv[i], "--psk-set") == 0
&& i + 2 < argc) {
const char* pskArg = argv[++i];
const char* authArg = argv[++i];
rc = ctrl_nations_psk_set(&dev, pskArg, authArg);
matched = 1;
}
else if (XSTRCMP(argv[i], "--psk-clear") == 0 && i + 1 < argc)
rc = ctrl_nations_psk_clear(&dev, argv[++i]);
else if (XSTRCMP(argv[i], "--lock") == 0)
rc = wolfTPM2_SpdmNationsSetOnlyMode(&dev, 1);
else if (XSTRCMP(argv[i], "--unlock") == 0)
rc = wolfTPM2_SpdmNationsSetOnlyMode(&dev, 0);
else if (XSTRCMP(argv[i], "--tpm-clear") == 0) {
else if (!matched && XSTRCMP(argv[i], "--psk-clear") == 0
&& i + 1 < argc) {
rc = ctrl_nations_psk_clear(&dev, argv[++i]); matched = 1;
}
else if (!matched && XSTRCMP(argv[i], "--lock") == 0) {
rc = wolfTPM2_SpdmNationsSetOnlyMode(&dev, 1); matched = 1;
}
else if (!matched && XSTRCMP(argv[i], "--unlock") == 0) {
rc = wolfTPM2_SpdmNationsSetOnlyMode(&dev, 0); matched = 1;
}
else if (!matched && XSTRCMP(argv[i], "--caps184") == 0) {
rc = ctrl_nations_caps184(&dev); matched = 1;
}
#endif /* WOLFSPDM_NATIONS */
/* Generic admin - available whenever the library is built. */
if (!matched && 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);
matched = 1;
}
if (!matched) {
printf("Unknown option: %s\n", argv[i]);
usage();
rc = BAD_FUNC_ARG;
}
else if (XSTRCMP(argv[i], "--caps184") == 0)
rc = ctrl_nations_caps184(&dev);
else
#endif
{ printf("Unknown option: %s\n", argv[i]); usage(); rc = BAD_FUNC_ARG; }
if (rc != 0) break;
}

View File

@ -24,7 +24,11 @@ CAPS_DEMO="./examples/wrap/caps"
UNIT_TEST="./tests/unit.test"
GPIO_CHIP="gpiochip0"
GPIO_PIN="4"
VENDOR="${2:-nuvoton}" # "nuvoton", "nations", or "nations-psk"
VENDOR="${2:-nuvoton}" # nuvoton, nations, nations-psk, fwtpm-tcg, fwtpm-psk
FWTPM="${FWTPM:-./src/fwtpm/fwtpm_server}"
FWTPM_PORT="${FWTPM_PORT:-22321}"
FWTPM_PLAT_PORT="${FWTPM_PLAT_PORT:-22322}"
FWTPM_PID=""
PASS=0 FAIL=0 TOTAL=0
# Nations PSK test data (from Vision/NSING reference PSK_DEMO_3)
@ -41,13 +45,48 @@ else
GREEN='' RED='' YELLOW='' NC=''
fi
# fwtpm modes route TPM/SPDM traffic over the swtpm socket. No GPIO line,
# no NV provisioning persistence - the responder is started fresh per run.
is_fwtpm_mode() {
[ "$VENDOR" = "fwtpm-tcg" ] || [ "$VENDOR" = "fwtpm-psk" ]
}
gpio_reset() {
if is_fwtpm_mode; then
return 0
fi
gpioset "$GPIO_CHIP" "$GPIO_PIN=0" 2>/dev/null
sleep 0.1
gpioset "$GPIO_CHIP" "$GPIO_PIN=1" 2>/dev/null
sleep 2
}
fwtpm_start() {
local mode="$1"
rm -f fwtpm_nv.bin NVChip 2>/dev/null
if [ "$mode" = "psk" ]; then
"$FWTPM" --spdm-psk --spdm-psk-hex "$NATIONS_PSK" \
--port "$FWTPM_PORT" --platform-port "$FWTPM_PLAT_PORT" \
--clear > /tmp/fwtpm_spdm_test.log 2>&1 &
else
"$FWTPM" --spdm-tcg --port "$FWTPM_PORT" \
--platform-port "$FWTPM_PLAT_PORT" --clear \
> /tmp/fwtpm_spdm_test.log 2>&1 &
fi
FWTPM_PID=$!
sleep 1
export TPM2_SWTPM_HOST=127.0.0.1
export TPM2_SWTPM_PORT="$FWTPM_PORT"
}
fwtpm_stop() {
if [ -n "$FWTPM_PID" ]; then
kill "$FWTPM_PID" 2>/dev/null || true
wait "$FWTPM_PID" 2>/dev/null || true
FWTPM_PID=""
fi
}
# normalize_nations_chip: bring NS350 to canonical clean state
# (identity-key=1, no PSK). Idempotent — safe to call multiple times.
# NS350 IdentityKeySet returns TPM_RC_VALUE when setting to current value,
@ -226,8 +265,70 @@ elif [ "$VENDOR" = "nations-psk" ]; then
echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}"
fi
elif [ "$VENDOR" = "fwtpm-tcg" ]; then
# fwtpm in TCG cert mode - mirrors the Nuvoton 6-test sequence against
# the software responder. Lock/unlock toggles the SPDMONLY runtime
# state in the responder; while locked, plaintext TPM frames are
# rejected with TPM_RC_DISABLED.
if [ ! -x "$FWTPM" ]; then
echo "Error: $FWTPM not found"
exit 1
fi
fwtpm_start tcg
trap 'fwtpm_stop' EXIT
run_test "SPDM status query" "$SPDM_DEMO" --status
run_test "SPDM session connect" "$SPDM_DEMO" --connect
run_test "Lock SPDM-only mode" "$SPDM_DEMO" --connect --lock
if [ -x "$UNIT_TEST" ]; then
run_test "Unit test over SPDM" "$UNIT_TEST"
else
echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}"
fi
run_test "Unlock SPDM-only mode" "$SPDM_DEMO" --connect --unlock
if [ -x "$CAPS_DEMO" ]; then
run_test_caps "Cleartext caps (no SPDM)" "$CAPS_DEMO"
else
echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}"
fi
elif [ "$VENDOR" = "fwtpm-psk" ]; then
# fwtpm in PSK mode - mirrors the Nations-PSK 12-test sequence, minus
# the two identity-key steps. IDENTITY_KEY_SET/UNSET are Nations
# TPM2 vendor commands (TPM_CC_Nations_IdentityKeySet) that write to
# vendor NV; they don't apply to a software TPM.
if [ ! -x "$FWTPM" ]; then
echo "Error: $FWTPM not found"
exit 1
fi
fwtpm_start psk
trap 'fwtpm_stop' EXIT
run_test "PSK provision (PSK_SET)" "$SPDM_DEMO" \
--psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH"
run_test "Status (PSK provisioned)" "$SPDM_DEMO" --status
run_test "PSK session connect" "$SPDM_DEMO" --psk "$NATIONS_PSK"
run_test "PSK session connect (repeat)" "$SPDM_DEMO" --psk "$NATIONS_PSK"
run_test "PSK clear (PSK_CLEAR)" "$SPDM_DEMO" --psk-clear "$NATIONS_CLEARAUTH"
run_test "Status (PSK cleared)" "$SPDM_DEMO" --status
run_test "PSK re-provision (PSK_SET)" "$SPDM_DEMO" \
--psk-set "$NATIONS_PSK" "$NATIONS_CLEARAUTH"
run_test "PSK session connect (after re-provision)" "$SPDM_DEMO" \
--psk "$NATIONS_PSK"
run_test "Final PSK clear" "$SPDM_DEMO" --psk-clear "$NATIONS_CLEARAUTH"
if [ -x "$CAPS_DEMO" ]; then
run_test_caps "Cleartext caps (no SPDM)" "$CAPS_DEMO"
else
echo -e " ${YELLOW}Skipping: $CAPS_DEMO not found${NC}"
fi
else
echo "Error: Unknown vendor '$VENDOR'. Use 'nuvoton', 'nations', or 'nations-psk'."
echo "Error: Unknown vendor '$VENDOR'."
echo "Valid: nuvoton, nations, nations-psk, fwtpm-tcg, fwtpm-psk"
exit 1
fi

View File

@ -142,6 +142,42 @@ Key options: `--enable-fwtpm`, `--enable-fwtpm-only`, `--enable-swtpm`,
`FWTPM_NO_ATTESTATION`, `FWTPM_NO_NV`, `FWTPM_NO_POLICY`, `FWTPM_NO_CREDENTIAL`,
`FWTPM_NO_DA`, `FWTPM_NO_PARAM_ENC`.
## SPDM Responder Mode
`fwtpm_server` ships an SPDM 1.3 responder so the SPDM stack (TCG cert
handshake + DSP0274 PSK) can be tested without real silicon. When SPDM
is on, the responder sits above the existing transport HAL and dispatches
TCG-framed messages (tags `0x8101` clear, `0x8201` secured) into the SPDM
state machine; plaintext TPM frames fall through to the regular dispatcher
until the requester issues `SPDMONLY LOCK`, after which only `GetCapability`
is allowed through in plaintext (matching Nuvoton/Nations silicon).
Build with `--enable-fwtpm --enable-spdm` plus at least one of
`--enable-tcg` / `--enable-psk`. Start in one of two modes:
```bash
./src/fwtpm/fwtpm_server --spdm-tcg # TCG cert handshake
./src/fwtpm/fwtpm_server --spdm-psk \
--spdm-psk-hex dbc2192291d807742441b963f6712841... # PSK handshake
./src/fwtpm/fwtpm_server --no-spdm # plaintext only (default)
```
The responder generates a fresh P-384 identity keypair at startup (used
for `GET_PUBK`/`KEY_EXCHANGE` signing); the private key never leaves
`fwtpm_server` memory and the stack copy is zeroed with `wc_ForceZero`
after handoff to the responder context.
End-to-end coverage runs via the same script that drives real silicon:
```bash
./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl fwtpm-tcg
./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl fwtpm-psk
```
CI exercises 7 build-only configure permutations + the two e2e modes on
`ubuntu-latest`; the original `hw-spdm-test.yml` still runs the same
script against Nuvoton + Nations on self-hosted Pi runners.
## TPM 2.0 Command Coverage
### Currently Implemented (105 commands)

View File

@ -39,6 +39,10 @@
#ifdef WOLFTPM_FWTPM_TIS
#include <wolftpm/fwtpm/fwtpm_tis.h>
#endif
#ifdef WOLFTPM_SPDM_RESPONDER
#include <wolftpm/spdm/spdm_responder.h>
#include <wolftpm/spdm/spdm_types.h>
#endif
#include <stdio.h>
#include <string.h>
@ -316,12 +320,61 @@ static int DispatchAndRespond(FWTPM_CTX* ctx, UINT32 cmdSize, int locality,
int rspSize = 0;
int procRc;
UINT32 netVal;
int dispatched = 0;
#ifdef WOLFTPM_SPDM_RESPONDER
UINT16 firstTag = (cmdSize >= 2) ? FwLoadU16BE(ctx->cmdBuf) : 0;
int isSpdmFrame = (firstTag == 0x8101 || firstTag == 0x8201);
word32 outSz;
UINT32 cc;
procRc = FWTPM_ProcessCommand(ctx, ctx->cmdBuf, (int)cmdSize,
ctx->rspBuf, &rspSize, locality);
if (procRc != TPM_RC_SUCCESS || rspSize == 0) {
rspSize = BuildErrorResponse(ctx->rspBuf, TPM_ST_NO_SESSIONS,
TPM_RC_FAILURE);
if (ctx->spdmMode != FWTPM_SPDM_MODE_OFF && isSpdmFrame) {
outSz = (word32)sizeof(ctx->rspBuf);
procRc = wolfSPDM_RespHandleMessage(ctx->spdmRespCtx,
ctx->cmdBuf, cmdSize, ctx->rspBuf, &outSz);
if (procRc == WOLFSPDM_E_FRAMING) {
/* Per wolfSPDM_RespHandleMessage's contract, an E_FRAMING
* return MUST drop the connection - it indicates a plaintext
* bypass attempt or other malformed inbound frame. */
return -1;
}
if (procRc != WOLFSPDM_SUCCESS) {
rspSize = BuildErrorResponse(ctx->rspBuf, TPM_ST_NO_SESSIONS,
TPM_RC_FAILURE);
}
else {
rspSize = (int)outSz;
}
dispatched = 1;
}
else if (ctx->spdmMode != FWTPM_SPDM_MODE_OFF && !isSpdmFrame &&
wolfSPDM_RespIsLocked(ctx->spdmRespCtx)) {
/* SPDMONLY locked: real silicon lets plaintext GetCapability
* through so wolfTPM2 can probe vendor IDs before establishing
* SPDM. Allowlist that one command; reject the rest. */
cc = (cmdSize >= 10) ? FwLoadU32BE(ctx->cmdBuf + 6) : 0;
if (cc == TPM_CC_GetCapability) {
procRc = FWTPM_ProcessCommand(ctx, ctx->cmdBuf, (int)cmdSize,
ctx->rspBuf, &rspSize, locality);
if (procRc != TPM_RC_SUCCESS || rspSize == 0) {
rspSize = BuildErrorResponse(ctx->rspBuf,
TPM_ST_NO_SESSIONS, TPM_RC_FAILURE);
}
}
else {
rspSize = BuildErrorResponse(ctx->rspBuf, TPM_ST_NO_SESSIONS,
TPM_RC_DISABLED);
}
dispatched = 1;
}
#endif
if (!dispatched) {
procRc = FWTPM_ProcessCommand(ctx, ctx->cmdBuf, (int)cmdSize,
ctx->rspBuf, &rspSize, locality);
if (procRc != TPM_RC_SUCCESS || rspSize == 0) {
rspSize = BuildErrorResponse(ctx->rspBuf, TPM_ST_NO_SESSIONS,
TPM_RC_FAILURE);
}
}
if (isSwtpm) {
@ -386,6 +439,10 @@ static int HandleCommandConnection(FWTPM_CTX* ctx, int clientFd)
return rc;
}
/* SPDM-mode payload inspection now lives in DispatchAndRespond after
* MSSIM unwrap, so the wolfTPM SWTPM client (which always MSSIM-wraps)
* works alongside the bus-snooping defence. */
/* Check if this looks like a raw TPM command (swtpm protocol).
* TPM commands start with tag 0x8001 or 0x8002 in big-endian. */
tag = FwLoadU16BE((byte*)&firstWord);

View File

@ -32,6 +32,13 @@
#include <wolftpm/fwtpm/fwtpm.h>
#include <wolftpm/fwtpm/fwtpm_nv.h>
#include <wolftpm/fwtpm/fwtpm_io.h>
#include <wolftpm/fwtpm/fwtpm_command.h>
#ifdef WOLFTPM_SPDM_RESPONDER
#include <wolftpm/spdm/spdm_responder.h>
#include <wolfssl/wolfcrypt/ecc.h>
#include <wolfssl/wolfcrypt/random.h>
#endif
#include <stdio.h>
#include <stdlib.h>
@ -61,14 +68,157 @@ static void usage(const char* progname)
printf(" --platform-port <port> Platform port (default: %d)\n",
FWTPM_PLAT_PORT);
#endif
#ifdef WOLFTPM_SPDM_RESPONDER
printf("\nSPDM responder (TCG SPDM Binding v1.0 / DSP0274 1.3):\n");
printf(" --spdm-tcg Speak SPDM in TCG Binding mode\n");
printf(" --spdm-psk Speak SPDM in PSK mode\n");
printf(" --no-spdm Disable SPDM (default)\n");
printf(" --spdm-psk-hex <hex> PSK as hex (PSK mode)\n");
printf("\nWith SPDM enabled, raw TPM2 / MSSIM frames are rejected\n");
printf("(bus-snooping defence). Every TPM command must arrive inside\n");
printf("a decrypted SPDM secured-message envelope.\n");
#endif
}
#ifdef WOLFTPM_SPDM_RESPONDER
static int fwtpmSpdmTpmDispatch(void* userCtx,
const byte* cmd, word32 cmdSz,
byte* resp, word32 respBufSz, word32* respSz)
{
FWTPM_CTX* ctx = (FWTPM_CTX*)userCtx;
int rc;
int rspSize;
if (ctx == NULL || cmd == NULL || resp == NULL || respSz == NULL) {
return BAD_FUNC_ARG;
}
rspSize = (int)respBufSz;
rc = FWTPM_ProcessCommand(ctx, cmd, (int)cmdSz, resp, &rspSize, 0);
/* A non-zero TPM_RC is a valid TPM response - the requester must see
* the actual error code. If the dispatcher didn't write one (rspSize
* left at zero or partial), synthesize a 10-byte TPM_ST_NO_SESSIONS
* error frame here so the SPDM layer encrypts and sends it back. */
if ((rc != TPM_RC_SUCCESS || rspSize < TPM2_HEADER_SIZE) &&
respBufSz >= TPM2_HEADER_SIZE) {
resp[0] = 0x80; resp[1] = 0x01; /* TPM_ST_NO_SESSIONS */
resp[2] = 0x00; resp[3] = 0x00; resp[4] = 0x00; resp[5] = 0x0A;
resp[6] = (byte)((rc >> 24) & 0xFF);
resp[7] = (byte)((rc >> 16) & 0xFF);
resp[8] = (byte)((rc >> 8) & 0xFF);
resp[9] = (byte)(rc & 0xFF);
rspSize = TPM2_HEADER_SIZE;
}
*respSz = (word32)rspSize;
return 0; /* I/O layer succeeded; TPM error code is in the response. */
}
/* Generate a fresh P-384 identity key for the SPDM responder. The
* requester verifies KEY_EXCHANGE_RSP signatures against the public half
* (delivered via GET_PUBK). Re-generated every startup - for production
* use this would be backed by a stable per-device key. */
static int fwtpmSpdmGenIdentityKey(byte* privOut, word32 privCap,
byte* pubOut, word32 pubCap)
{
WC_RNG rng;
ecc_key key;
byte tmpX[48];
byte tmpY[48];
word32 privSz;
word32 xSz;
word32 ySz;
int rc;
int keyInit = 0;
if (privCap < 48 || pubCap < 96) {
return BAD_FUNC_ARG;
}
/* wolfCrypt may trim leading zeros from exported scalars/coords -
* zero the destinations and left-pad each value into a 48-byte slot
* so the responder always sees fixed-width P-384 material. */
XMEMSET(privOut, 0, 48);
XMEMSET(pubOut, 0, 96);
XMEMSET(tmpX, 0, sizeof(tmpX));
XMEMSET(tmpY, 0, sizeof(tmpY));
rc = wc_InitRng(&rng);
if (rc != 0) {
return rc;
}
rc = wc_ecc_init(&key);
if (rc == 0) {
keyInit = 1;
rc = wc_ecc_make_key_ex(&rng, 48, &key, ECC_SECP384R1);
}
if (rc == 0) {
privSz = sizeof(tmpX);
rc = wc_ecc_export_private_only(&key, tmpX, &privSz);
if (rc == 0) {
if (privSz > 48) {
/* Fail loudly - silently leaving privOut zeroed would
* give the responder a publicly-known identity key. */
rc = BAD_FUNC_ARG;
}
else {
XMEMCPY(privOut + (48 - privSz), tmpX, privSz);
}
}
}
if (rc == 0) {
XMEMSET(tmpX, 0, sizeof(tmpX));
xSz = sizeof(tmpX);
ySz = sizeof(tmpY);
rc = wc_ecc_export_public_raw(&key, tmpX, &xSz, tmpY, &ySz);
if (rc == 0) {
if (xSz > 48 || ySz > 48) {
rc = BAD_FUNC_ARG;
}
else {
XMEMCPY(pubOut + (48 - xSz), tmpX, xSz);
XMEMCPY(pubOut + 48 + (48 - ySz), tmpY, ySz);
}
}
}
wc_ForceZero(tmpX, sizeof(tmpX));
wc_ForceZero(tmpY, sizeof(tmpY));
if (keyInit) {
wc_ecc_free(&key);
}
wc_FreeRng(&rng);
return rc;
}
static int fwtpmHexDecode(const char* hex, byte* out, word32 outCap)
{
word32 len, i;
if (hex == NULL || out == NULL) {
return -1;
}
len = (word32)XSTRLEN(hex);
if ((len & 1) != 0 || (len / 2) > outCap) {
return -1;
}
for (i = 0; i < len / 2; i++) {
unsigned int v;
if (sscanf(hex + 2 * i, "%2x", &v) != 1) {
return -1;
}
out[i] = (byte)v;
}
return (int)(len / 2);
}
#endif /* WOLFTPM_SPDM_RESPONDER */
int main(int argc, char* argv[])
{
int rc, rcCleanup;
static FWTPM_CTX ctx;
int i;
int clearNv = 0;
#ifdef WOLFTPM_SPDM_RESPONDER
int spdmMode = FWTPM_SPDM_MODE_OFF;
const char* spdmPskHex = NULL;
#endif
#ifndef _WIN32
struct sigaction sa;
#endif
@ -99,6 +249,20 @@ int main(int argc, char* argv[])
else if (XSTRCMP(argv[i], "--platform-port") == 0 && i + 1 < argc) {
i++; /* skip value for now, handled below */
}
#endif
#ifdef WOLFTPM_SPDM_RESPONDER
else if (XSTRCMP(argv[i], "--spdm-tcg") == 0) {
spdmMode |= FWTPM_SPDM_MODE_TCG;
}
else if (XSTRCMP(argv[i], "--spdm-psk") == 0) {
spdmMode |= FWTPM_SPDM_MODE_PSK;
}
else if (XSTRCMP(argv[i], "--no-spdm") == 0) {
spdmMode = FWTPM_SPDM_MODE_OFF;
}
else if (XSTRCMP(argv[i], "--spdm-psk-hex") == 0 && i + 1 < argc) {
spdmPskHex = argv[++i];
}
#endif
else {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
@ -156,6 +320,72 @@ int main(int argc, char* argv[])
printf(" Manufacturer: %s\n", FWTPM_MANUFACTURER);
printf(" Model: %s\n", FWTPM_MODEL);
#ifdef WOLFTPM_SPDM_RESPONDER
/* Failure here is fatal - never fall back to plaintext when SPDM was
* requested (bus-snooping defence). */
if (spdmMode != FWTPM_SPDM_MODE_OFF) {
int useTcg, usePsk;
useTcg = (spdmMode & FWTPM_SPDM_MODE_TCG) ? 1 : 0;
usePsk = (spdmMode & FWTPM_SPDM_MODE_PSK) ? 1 : 0;
ctx.spdmRespCtx = (struct WOLFSPDM_RESP_CTX*)
XMALLOC((size_t)wolfSPDM_RespGetCtxSize(),
NULL, DYNAMIC_TYPE_TMP_BUFFER);
if (ctx.spdmRespCtx == NULL) {
fprintf(stderr, "fwTPM: SPDM responder context alloc failed\n");
FWTPM_Cleanup(&ctx);
return 1;
}
rc = wolfSPDM_RespInit(ctx.spdmRespCtx);
if (rc == 0) {
rc = wolfSPDM_RespSetMode(ctx.spdmRespCtx, useTcg, usePsk);
}
if (rc == 0 && spdmPskHex != NULL) {
byte pskBuf[64];
int pskLen = fwtpmHexDecode(spdmPskHex, pskBuf, sizeof(pskBuf));
if (pskLen < 0) {
fprintf(stderr, "fwTPM: invalid --spdm-psk-hex value\n");
rc = -1;
}
else {
rc = wolfSPDM_RespSetPSK(ctx.spdmRespCtx,
pskBuf, (word32)pskLen, NULL, 0);
}
wc_ForceZero(pskBuf, sizeof(pskBuf));
}
if (rc == 0) {
rc = wolfSPDM_RespSetTpmCallback(ctx.spdmRespCtx,
fwtpmSpdmTpmDispatch, &ctx);
}
if (rc == 0) {
byte idPriv[48];
byte idPub[96];
rc = fwtpmSpdmGenIdentityKey(idPriv, sizeof(idPriv),
idPub, sizeof(idPub));
if (rc == 0) {
rc = wolfSPDM_RespSetIdentityKey(ctx.spdmRespCtx,
idPriv, sizeof(idPriv), idPub, sizeof(idPub));
}
wc_ForceZero(idPriv, sizeof(idPriv));
}
if (rc != 0) {
fprintf(stderr, "fwTPM: SPDM responder init failed (rc=%d)\n",
rc);
wolfSPDM_RespFree(ctx.spdmRespCtx);
XFREE(ctx.spdmRespCtx, NULL, DYNAMIC_TYPE_TMP_BUFFER);
ctx.spdmRespCtx = NULL;
FWTPM_Cleanup(&ctx);
return 1;
}
ctx.spdmMode = spdmMode;
printf(" SPDM mode: %s%s%s\n",
useTcg ? "TCG" : "",
(useTcg && usePsk) ? "+" : "",
usePsk ? "PSK" : "");
printf(" Bus-snooping defence: armed (plaintext rejected after SPDMONLY lock)\n");
}
#endif
/* Install signal handler for graceful shutdown with NV save */
#ifdef _WIN32
signal(SIGTERM, sigterm_handler);
@ -181,6 +411,13 @@ int main(int argc, char* argv[])
printf("fwTPM server shutting down (rc=%d)\n", rc);
FWTPM_IO_Cleanup(&ctx);
#ifdef WOLFTPM_SPDM_RESPONDER
if (ctx.spdmRespCtx != NULL) {
wolfSPDM_RespFree(ctx.spdmRespCtx);
XFREE(ctx.spdmRespCtx, NULL, DYNAMIC_TYPE_TMP_BUFFER);
ctx.spdmRespCtx = NULL;
}
#endif
rcCleanup = FWTPM_Cleanup(&ctx);
if (rcCleanup != TPM_RC_SUCCESS) {
fprintf(stderr, "fwTPM: NV save failed during cleanup (rc=%d)\n", rcCleanup);

View File

@ -26,6 +26,26 @@ src_fwtpm_fwtpm_server_CFLAGS += -DWOLFTPM_FWTPM_TIS
src_fwtpm_fwtpm_server_CPPFLAGS += -DWOLFTPM_FWTPM_TIS
endif
# fwtpm_server doesn't link libwolftpm.la (it compiles its own copies with
# -DWOLFTPM_FWTPM), so SPDM sources have to be listed here too.
if BUILD_FWTPM_SPDM
src_fwtpm_fwtpm_server_SOURCES += \
src/spdm/spdm_responder.c \
src/spdm/spdm_context.c \
src/spdm/spdm_crypto.c \
src/spdm/spdm_kdf.c \
src/spdm/spdm_msg.c \
src/spdm/spdm_secured.c \
src/spdm/spdm_session.c \
src/spdm/spdm_transcript.c
if BUILD_SPDM_TCG
src_fwtpm_fwtpm_server_SOURCES += src/spdm/spdm_tcg.c
endif
if BUILD_SPDM_PSK
src_fwtpm_fwtpm_server_SOURCES += src/spdm/spdm_psk.c
endif
endif BUILD_FWTPM_SPDM
# Fuzz target (libFuzzer)
if BUILD_FUZZ
noinst_PROGRAMS += tests/fuzz/fwtpm_fuzz

View File

@ -181,11 +181,50 @@ make
| Option | Description |
|-----------------------------|-------------|
| `--enable-spdm` | Enable SPDM support (required) |
| `--enable-nuvoton` | Enable Nuvoton TPM hardware support |
| `--enable-nations` | Enable Nations NS350 hardware support |
| `--enable-tcg` | TCG SPDM Binding spec handshake (auto when fwtpm/nuvoton/nations on) |
| `--enable-psk` | DSP0274 PSK handshake (auto with `--enable-nations`; requires `--enable-tcg`) |
| `--enable-fwtpm` | Build fwtpm_server with the SPDM responder (no silicon needed) |
| `--enable-nuvoton` | Enable Nuvoton TPM hardware support (auto-enables `--enable-tcg`) |
| `--enable-nations` | Enable Nations NS350 hardware support (auto-enables `--enable-tcg --enable-psk`) |
| `--enable-debug` | Debug output with verbose SPDM tracing |
| `--enable-smallstack` | Heap-allocated SPDM context (default: static ~32 KB) |
Incompatibility errors enforced by `configure`:
- `--enable-nuvoton --disable-tcg` (Nuvoton uses TCG SPDM Binding)
- `--enable-nations --disable-tcg` or `--enable-nations --disable-psk`
- `--enable-psk --disable-tcg` (PSK rides on TCG framing)
### fwtpm SPDM Responder (no silicon required)
`fwtpm_server` ships an SPDM 1.3 responder so the full TCG + PSK stack
can be exercised in CI without real hardware:
```bash
./src/fwtpm/fwtpm_server --spdm-tcg # TCG cert handshake
./src/fwtpm/fwtpm_server --spdm-psk \
--spdm-psk-hex dbc2192291d807742441b963f6712841... # PSK handshake
```
Test it end-to-end:
```bash
./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl fwtpm-tcg
./examples/spdm/spdm_test.sh ./examples/spdm/spdm_ctrl fwtpm-psk
```
### Vendor selection in dual-vendor builds
When both `--enable-nuvoton` and `--enable-nations` are compiled in, the
`spdm_ctrl` CLI selects which vendor adapter to use via an optional
runtime flag:
```bash
./examples/spdm/spdm_ctrl --vendor=nuvoton --connect # default
./examples/spdm/spdm_ctrl --vendor=nations --connect
```
Single-vendor builds ignore `--vendor=`.
## Usage
### One-Time Setup

View File

@ -4,6 +4,7 @@
if BUILD_SPDM
# Side-agnostic SPDM core: same code feeds requester and responder.
src_libwolftpm_la_SOURCES += \
src/spdm/spdm_context.c \
src/spdm/spdm_crypto.c \
@ -11,17 +12,27 @@ src_libwolftpm_la_SOURCES += \
src/spdm/spdm_msg.c \
src/spdm/spdm_secured.c \
src/spdm/spdm_session.c \
src/spdm/spdm_tcg.c \
src/spdm/spdm_transcript.c
# Vendor-specific SPDM code
if BUILD_SPDM_TCG
src_libwolftpm_la_SOURCES += src/spdm/spdm_tcg.c
endif
# Gated on the mode flag, not on a vendor flag - PSK isn't Nations-specific.
if BUILD_SPDM_PSK
src_libwolftpm_la_SOURCES += src/spdm/spdm_psk.c
endif
if BUILD_NUVOTON
src_libwolftpm_la_SOURCES += src/spdm/spdm_nuvoton.c
endif
if BUILD_NATIONS
src_libwolftpm_la_SOURCES += src/spdm/spdm_nations.c
src_libwolftpm_la_SOURCES += src/spdm/spdm_psk.c
endif
if BUILD_FWTPM_SPDM
src_libwolftpm_la_SOURCES += src/spdm/spdm_responder.c
endif
check_PROGRAMS += src/spdm/unit_test

View File

@ -214,6 +214,11 @@ int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode)
return WOLFSPDM_E_INVALID_ARG;
}
#if !defined(WOLFSPDM_NUVOTON) && !defined(WOLFSPDM_NATIONS) && \
!defined(WOLFTPM_SPDM_PSK)
(void)mode;
#endif
#ifdef WOLFSPDM_NUVOTON
if (mode == WOLFSPDM_MODE_NUVOTON) {
ctx->mode = WOLFSPDM_MODE_NUVOTON;
@ -230,6 +235,10 @@ int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode)
ctx->fipsIndicator = WOLFSPDM_FIPS_NON_FIPS;
return WOLFSPDM_SUCCESS;
}
#endif
#ifdef WOLFTPM_SPDM_PSK
/* Spec-pure PSK mode - DSP0274 handshake. Available whenever the PSK
* feature is built, independent of any vendor adapter. */
if (mode == WOLFSPDM_MODE_NATIONS_PSK) {
ctx->mode = WOLFSPDM_MODE_NATIONS_PSK;
ctx->connectionHandle = 0;
@ -551,6 +560,9 @@ const char* wolfSPDM_GetErrorString(int error)
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";
case WOLFSPDM_E_NOT_AVAILABLE: return "Feature not compiled in";
case WOLFSPDM_E_FRAMING: return "Framing violation";
case WOLFSPDM_E_NOT_IMPL: return "Not implemented";
default: return "Unknown error";
}
}

View File

@ -92,6 +92,14 @@ int wolfSPDM_GenerateEphemeralKey(WOLFSPDM_CTX* ctx)
return WOLFSPDM_E_CRYPTO_FAIL;
}
/* Attach RNG so timing-resistant scalar-mul inside wc_ecc_shared_secret
* doesn't fail with MISSING_RNG_E in builds that enable hardening. */
rc = wc_ecc_set_rng(&ctx->ephemeralKey, &ctx->rng);
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");

View File

@ -303,6 +303,11 @@ WOLFTPM_API int wolfSPDM_HkdfExpandLabel(byte spdmVersion, const byte* secret, w
byte* out, word32 outSz);
WOLFTPM_API int wolfSPDM_ComputeVerifyData(const byte* finishedKey, const byte* thHash,
byte* verifyData);
/* Cross-TU helper, not a shipped API - WOLFTPM_LOCAL keeps it out of the
* shared-library export table. spdm_internal.h is private to wolfSPDM. */
WOLFTPM_LOCAL int wolfSPDM_BuildSignedHash(byte spdmVersion,
const char* contextStr, word32 contextStrLen,
const byte* inputDigest, byte* outputDigest);
/* ----- Internal Function Declarations - Message Building ----- */

View File

@ -148,7 +148,7 @@ int wolfSPDM_BuildKeyExchange(WOLFSPDM_CTX* ctx, byte* buf, word32* bufSz)
* 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,
int wolfSPDM_BuildSignedHash(byte spdmVersion,
const char* contextStr, word32 contextStrLen,
const byte* inputDigest, byte* outputDigest)
{

File diff suppressed because it is too large Load Diff

View File

@ -489,17 +489,15 @@ int wolfSPDM_ConnectTCG(WOLFSPDM_CTX* ctx)
SPDM_CONNECT_STEP(ctx, "TCG Step 1: GET_VERSION\n",
wolfSPDM_GetVersion(ctx));
#ifdef WOLFSPDM_NATIONS
/* Steps 2-3: GET_CAPABILITIES + NEGOTIATE_ALGORITHMS
* Required by Nations (TCG spec mandates these before GET_PUB_KEY).
* Nuvoton skips these its simplified flow goes directly to GET_PUB_KEY.*/
if (ctx->mode == WOLFSPDM_MODE_NATIONS) {
/* Steps 2-3: GET_CAPABILITIES + NEGOTIATE_ALGORITHMS.
* TCG SPDM Binding mandates these before GET_PUB_KEY. Nuvoton silicon
* uses a simplified flow that skips them, so gate at runtime by mode. */
if (ctx->mode != WOLFSPDM_MODE_NUVOTON) {
SPDM_CONNECT_STEP(ctx, "TCG Step 2: GET_CAPABILITIES\n",
wolfSPDM_TCG_GetCapabilities(ctx, WOLFSPDM_TCG_CAPS_FLAGS_DEFAULT));
SPDM_CONNECT_STEP(ctx, "TCG Step 3: NEGOTIATE_ALGORITHMS\n",
wolfSPDM_TCG_NegotiateAlgorithms(ctx));
}
#endif
/* Step 4: GET_PUBK */
wolfSPDM_DebugPrint(ctx, "TCG Step 4: GET_PUBK\n");

View File

@ -24,6 +24,9 @@
#endif
#include <wolftpm/spdm/spdm.h>
#ifdef WOLFTPM_SPDM_RESPONDER
#include <wolftpm/spdm/spdm_responder.h>
#endif
#include "spdm_internal.h"
#include <stdio.h>
#include <stdlib.h>
@ -769,10 +772,14 @@ static int test_tcg_underflow(void)
printf("test_tcg_underflow...\n");
/* wolfSPDM_SetMode is vendor-gated; set field directly so the TCG path
* runs in vendor-neutral builds too. */
#ifdef WOLFSPDM_NUVOTON
wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NUVOTON);
#else
#elif defined(WOLFSPDM_NATIONS)
wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_NATIONS);
#else
ctx->mode = WOLFSPDM_MODE_NUVOTON;
#endif
wolfSPDM_SetIO(ctx, tcg_underflow_io_cb, NULL);
@ -2127,6 +2134,179 @@ static int test_encrypt_decrypt_roundtrip_tcg(void)
}
#endif /* WOLFTPM_SPDM_TCG */
#ifdef WOLFTPM_SPDM_RESPONDER
static int g_tpmCbInvocations = 0;
static byte g_tpmCbLastCmd[256];
static word32 g_tpmCbLastCmdSz;
static int responder_tpm_stub(void* userCtx,
const byte* cmd, word32 cmdSz,
byte* resp, word32 respBufSz, word32* respSz)
{
(void)userCtx;
if (respBufSz < 10) {
return -1;
}
g_tpmCbInvocations++;
if (cmdSz <= sizeof(g_tpmCbLastCmd)) {
XMEMCPY(g_tpmCbLastCmd, cmd, cmdSz);
g_tpmCbLastCmdSz = cmdSz;
}
/* Return a fixed TPM2 TPM_ST_NO_SESSIONS / size=10 / TPM_RC_SUCCESS reply. */
resp[0] = 0x80; resp[1] = 0x01;
resp[2] = 0x00; resp[3] = 0x00; resp[4] = 0x00; resp[5] = 0x0A;
resp[6] = 0x00; resp[7] = 0x00; resp[8] = 0x00; resp[9] = 0x00;
*respSz = 10;
return 0;
}
static int test_responder_init_free(void)
{
byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE];
WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf;
printf("test_responder_init_free...\n");
ASSERT_SUCCESS(wolfSPDM_RespInit(rctx));
wolfSPDM_RespFree(rctx);
/* Idempotent: re-free should be safe. */
wolfSPDM_RespFree(rctx);
wolfSPDM_RespFree(NULL);
TEST_PASS();
}
static int test_responder_setmode_rejects_both_off(void)
{
byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE];
WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf;
int rc;
printf("test_responder_setmode_rejects_both_off...\n");
ASSERT_SUCCESS(wolfSPDM_RespInit(rctx));
rc = wolfSPDM_RespSetMode(rctx, 0, 0);
TEST_ASSERT(rc != WOLFSPDM_SUCCESS, "Both modes off must error");
wolfSPDM_RespFree(rctx);
TEST_PASS();
}
/* Plaintext-bypass regression. With SPDM mode active, a raw TPM2 frame
* (tag 0x8001, the swtpm-mssim plaintext path) must be rejected with
* WOLFSPDM_E_FRAMING and never reach the dispatcher. */
static int test_responder_no_plaintext_bypass(void)
{
byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE];
WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf;
/* Use a buffer >= TCG header (16) so the test exercises the tag
* comparison, not just the length check. */
byte rawTpm[24];
byte outBuf[64];
word32 outSz = sizeof(outBuf);
int rc;
printf("test_responder_no_plaintext_bypass...\n");
ASSERT_SUCCESS(wolfSPDM_RespInit(rctx));
#ifdef WOLFTPM_SPDM_TCG
ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 1, 0));
#else
ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 0, 1));
#endif
g_tpmCbInvocations = 0;
ASSERT_SUCCESS(wolfSPDM_RespSetTpmCallback(rctx, responder_tpm_stub, NULL));
/* Raw TPM2 frame: tag 0x8001 (TPM_ST_NO_SESSIONS), size 0x18,
* remainder zero. Length passes the 16-byte gate so the responder
* proceeds to the tag check and rejects on tag != 0x8101/0x8201. */
XMEMSET(rawTpm, 0, sizeof(rawTpm));
rawTpm[0] = 0x80; rawTpm[1] = 0x01;
rawTpm[5] = 0x18;
outSz = sizeof(outBuf);
rc = wolfSPDM_RespHandleMessage(rctx, rawTpm, sizeof(rawTpm),
outBuf, &outSz);
TEST_ASSERT(rc == WOLFSPDM_E_FRAMING,
"Tagged TPM2 frame must be rejected by tag check with E_FRAMING");
ASSERT_EQ(g_tpmCbInvocations, 0,
"TPM callback must NOT have been invoked");
wolfSPDM_RespFree(rctx);
TEST_PASS();
}
#if defined(WOLFTPM_SPDM_PSK) && defined(WOLFTPM_SPDM_TCG)
/* In-process I/O glue: route the requester's outbound TCG frame to the
* responder's HandleMessage and copy the response back. */
static int requester_to_responder_iocb(WOLFSPDM_CTX* spdmCtx,
const byte* txBuf, word32 txSz,
byte* rxBuf, word32* rxSz,
void* userCtx)
{
WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)userCtx;
(void)spdmCtx;
return wolfSPDM_RespHandleMessage(rctx, txBuf, txSz, rxBuf, rxSz);
}
/* PSK handshake + tunneled TPM2_CMD round-trip + END_SESSION, end-to-end. */
static int test_responder_psk_roundtrip(void)
{
static const byte testPsk[64] = {
0xdb,0xc2,0x19,0x22,0x91,0xd8,0x07,0x74,
0x24,0x41,0xb9,0x63,0xf6,0x71,0x28,0x41,
0xf7,0x69,0x7e,0x2e,0x39,0xc4,0x59,0x31,
0xf3,0xab,0xc5,0x36,0x58,0xc8,0xb9,0x33,
0x8b,0xd3,0x56,0x1c,0xab,0x5d,0x90,0xcf,
0x9e,0x49,0x32,0x95,0xbb,0x5b,0xd6,0xb2,
0xc4,0x55,0xe0,0xfd,0x19,0x39,0x2e,0x0c,
0xe4,0xf3,0x43,0x3c,0xbc,0xfc,0x70,0x47
};
WOLFSPDM_CTX req;
byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE];
WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf;
byte cmd[10];
int rc;
printf("test_responder_psk_roundtrip...\n");
ASSERT_SUCCESS(wolfSPDM_Init(&req));
ASSERT_SUCCESS(wolfSPDM_RespInit(rctx));
/* Bypass wolfSPDM_SetMode (vendor-gated) - set field directly so the
* test runs in a vendor-neutral build. */
req.mode = WOLFSPDM_MODE_NATIONS_PSK;
ASSERT_SUCCESS(wolfSPDM_SetPSK(&req, testPsk, sizeof(testPsk), NULL, 0));
ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 0, 1));
ASSERT_SUCCESS(wolfSPDM_RespSetPSK(rctx, testPsk, sizeof(testPsk),
NULL, 0));
g_tpmCbInvocations = 0;
ASSERT_SUCCESS(wolfSPDM_RespSetTpmCallback(rctx, responder_tpm_stub,
NULL));
ASSERT_SUCCESS(wolfSPDM_SetIO(&req, requester_to_responder_iocb, rctx));
rc = wolfSPDM_Connect(&req);
TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "PSK Connect failed");
ASSERT_EQ(wolfSPDM_IsConnected(&req), 1, "Requester not connected");
/* Tunnel a fake TPM2_Startup command via the VENDOR_DEFINED "TPM2_CMD"
* vendor message - same wrapping the real Nuvoton/Nations requester uses. */
XMEMSET(cmd, 0, sizeof(cmd));
cmd[0] = 0x80; cmd[1] = 0x01;
cmd[5] = 0x0A;
cmd[8] = 0x01; cmd[9] = 0x44;
rc = wolfSPDM_TCG_VendorCmdSecured(&req, WOLFSPDM_VDCODE_TPM2_CMD,
cmd, sizeof(cmd));
TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "TPM2_CMD passthrough failed");
ASSERT_EQ(g_tpmCbInvocations, 1, "TPM stub must have run once");
ASSERT_SUCCESS(wolfSPDM_Disconnect(&req));
wolfSPDM_RespFree(rctx);
wolfSPDM_Free(&req);
TEST_PASS();
}
#endif /* WOLFTPM_SPDM_PSK && WOLFTPM_SPDM_TCG */
#endif /* WOLFTPM_SPDM_RESPONDER */
/* ----- Main ----- */
int main(void)
@ -2259,6 +2439,15 @@ int main(void)
test_encrypt_decrypt_roundtrip_tcg();
#endif
#ifdef WOLFTPM_SPDM_RESPONDER
test_responder_init_free();
test_responder_setmode_rejects_both_off();
test_responder_no_plaintext_bypass();
#if defined(WOLFTPM_SPDM_PSK) && defined(WOLFTPM_SPDM_TCG)
test_responder_psk_roundtrip();
#endif
#endif
printf("\n===========================================\n");
printf("Results: %d passed, %d failed\n", g_testsPassed, g_testsFailed);
printf("===========================================\n");

View File

@ -50,6 +50,15 @@
#define WOLFTPM_SPDM_TIS_IO
#endif
/* swtpm/fwtpm MSSIM TCP transport for SPDM. SPDM bytes ride inside an
* MSSIM TPM_SEND_COMMAND envelope; fwtpm_server peels off the envelope
* and routes the inner TCG-framed payload to its responder. */
#if defined(WOLFTPM_SPDM_TCG) && defined(WOLFTPM_SWTPM)
#include <wolftpm/tpm2_swtpm.h>
#include <wolftpm/tpm2_packet.h>
#define WOLFTPM_SPDM_SWTPM_IO
#endif
/* wolfSPDM provides all SPDM protocol implementation */
#include <wolftpm/spdm/spdm.h>
@ -119,6 +128,59 @@ static int wolfTPM2_SPDM_TisIoCb(
}
#endif /* WOLFTPM_SPDM_TIS_IO */
#ifdef WOLFTPM_SPDM_SWTPM_IO
static int wolfTPM2_SPDM_SwtpmIoCb(
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;
word32 rspSz;
int rc;
(void)spdmCtx;
if (tpmCtx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) {
return -1;
}
if (txSz > sizeof(ioBuf)) {
return -1;
}
XMEMCPY(ioBuf, txBuf, txSz);
packet.buf = ioBuf;
packet.pos = (int)txSz;
packet.size = (int)sizeof(ioBuf);
rc = TPM2_SWTPM_SendCommand(tpmCtx, &packet);
if (rc != TPM_RC_SUCCESS) {
return rc;
}
/* Lower-bound the read so we don't index uninitialized bytes if the
* peer mis-frames a too-short response. Header is 6 bytes minimum. */
if (packet.pos < TPM2_HEADER_SIZE) {
return -1;
}
/* TCG SPDM Binding header and TPM2 header both carry total size at
* bytes [2..5] big-endian. */
XMEMCPY(&rspSz, &ioBuf[2], sizeof(word32));
rspSz = TPM2_Packet_SwapU32(rspSz);
if (rspSz < TPM2_HEADER_SIZE || rspSz > *rxSz || rspSz > sizeof(ioBuf)) {
return -1;
}
XMEMCPY(rxBuf, ioBuf, rspSz);
*rxSz = rspSz;
return 0;
}
#endif /* WOLFTPM_SPDM_SWTPM_IO */
/* -------------------------------------------------------------------------- */
/* Context Management */
/* -------------------------------------------------------------------------- */
@ -288,24 +350,26 @@ int wolfTPM2_SPDM_SecuredExchange(
/* Nuvoton-Specific Functions */
/* -------------------------------------------------------------------------- */
#ifdef WOLFTPM_SPDM_TCG
/* Set built-in TIS I/O callback for routing SPDM through TPM SPI/I2C.
* Must be called after wolfTPM2_SPDM_InitCtx() and SetTPMCtx(). */
* Must be called after wolfTPM2_SPDM_InitCtx() and SetTPMCtx().
* Transport-agnostic - used by both TCG cert and PSK handshakes. */
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
#if defined(WOLFTPM_SPDM_TIS_IO)
return wolfSPDM_SetIO(ctx->spdmCtx, wolfTPM2_SPDM_TisIoCb, ctx->tpmCtx);
#elif defined(WOLFTPM_SPDM_SWTPM_IO)
return wolfSPDM_SetIO(ctx->spdmCtx, wolfTPM2_SPDM_SwtpmIoCb, ctx->tpmCtx);
#else
(void)ctx;
return NOT_COMPILED_IN;
#endif
}
#ifdef WOLFTPM_SPDM_TCG
#ifdef WOLFSPDM_NUVOTON
/* Enable SPDM on Nuvoton TPM via NTC2_PreConfig vendor command.
* This requires platform hierarchy authorization and a TPM reset. */

View File

@ -1596,42 +1596,6 @@ int wolfTPM2_SpdmNationsIdentityKeySet(WOLFTPM2_DEV* dev, int set)
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)
{
@ -1662,6 +1626,43 @@ int wolfTPM2_SpdmNationsPskClear(WOLFTPM2_DEV* dev,
#endif /* WOLFSPDM_NATIONS */
#ifdef WOLFTPM_SPDM_PSK
/* Spec-pure PSK handshake (DSP0274 PSK_EXCHANGE / PSK_FINISH). The
* underlying state machine runs the standard messages - the mode enum
* still uses the historical NATIONS_PSK name. */
int wolfTPM2_SpdmConnectPsk(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;
}
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
rc = wolfSPDM_SetMode(dev->spdmCtx->spdmCtx, WOLFSPDM_MODE_NATIONS_PSK);
if (rc != 0) {
return rc;
}
rc = wolfSPDM_SetPSK(dev->spdmCtx->spdmCtx, psk, pskSz, hint, hintSz);
if (rc != 0) {
return rc;
}
return wolfSPDM_Connect(dev->spdmCtx->spdmCtx);
}
#endif /* WOLFTPM_SPDM_PSK */
#endif /* WOLFTPM_SPDM */
int wolfTPM2_UnsetAuth(WOLFTPM2_DEV* dev, int index)

View File

@ -726,8 +726,19 @@ typedef struct FWTPM_CTX {
#ifndef WOLFTPM2_NO_WOLFCRYPT
WC_RNG rng;
#endif
#ifdef WOLFTPM_SPDM_RESPONDER
/* Bit 0 = TCG, bit 1 = PSK. Zero disables SPDM at runtime. */
int spdmMode;
struct WOLFSPDM_RESP_CTX* spdmRespCtx;
#endif
} FWTPM_CTX;
/* fwTPM SPDM mode bits - used with FWTPM_CTX.spdmMode. */
#define FWTPM_SPDM_MODE_OFF 0x00
#define FWTPM_SPDM_MODE_TCG 0x01
#define FWTPM_SPDM_MODE_PSK 0x02
/** @defgroup wolfTPM_fwTPM wolfTPM fwTPM (Firmware TPM)
*
* Public API for the wolfTPM firmware TPM (fwTPM) software TPM 2.0

View File

@ -26,6 +26,7 @@ nobase_include_HEADERS+= \
wolftpm/spdm/spdm_nuvoton.h \
wolftpm/spdm/spdm_nations.h \
wolftpm/spdm/spdm_psk.h \
wolftpm/spdm/spdm_responder.h \
wolftpm/fwtpm/fwtpm.h \
wolftpm/fwtpm/fwtpm_io.h \
wolftpm/fwtpm/fwtpm_command.h \

View File

@ -48,6 +48,11 @@ enum WOLFSPDM_ERROR {
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 */
WOLFSPDM_E_NOT_AVAILABLE = -18, /* Feature/mode not compiled in */
WOLFSPDM_E_FRAMING = -19, /* Frame did not parse (e.g. plaintext
* TPM2 sent while SPDM mode active -
* bus-snooping defence) */
WOLFSPDM_E_NOT_IMPL = -20, /* Handler not yet implemented */
};
/* Get human-readable error string */

View File

@ -0,0 +1,95 @@
/* spdm_responder.h
*
* Copyright (C) 2006-2026 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 responder. Lives above fwtpm's transport HAL and reuses wolfSPDM
* crypto + framing helpers, flipped to answer requester-driven messages. */
#ifndef WOLFSPDM_RESPONDER_H
#define WOLFSPDM_RESPONDER_H
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <wolftpm/spdm/spdm.h>
#ifdef WOLFTPM_SPDM_RESPONDER
#ifdef __cplusplus
extern "C" {
#endif
struct WOLFSPDM_RESP_CTX;
typedef struct WOLFSPDM_RESP_CTX WOLFSPDM_RESP_CTX;
/* Dispatcher for tunneled TPM2_CMD payloads. fwtpm_server wires this to
* FWTPM_ProcessCommand; unit tests wire a stub. */
typedef int (*WOLFSPDM_RESP_TPM_CB)(void* userCtx,
const byte* cmd, word32 cmdSz,
byte* resp, word32 respBufSz, word32* respSz);
WOLFTPM_API int wolfSPDM_RespInit(WOLFSPDM_RESP_CTX* ctx);
WOLFTPM_API void wolfSPDM_RespFree(WOLFSPDM_RESP_CTX* ctx);
/* RESP_CTX embeds the requester CTX + four MAX_MSG_SIZE working buffers
* (4 * 4096) + identity/PSK material + tpm callback + flags. Static buffer
* is sized for that worst case; spdm_responder.c has a compile-time assert
* so it can't silently undersize. */
#define WOLFSPDM_RESP_CTX_STATIC_SIZE \
(WOLFSPDM_CTX_STATIC_SIZE + (4 * WOLFSPDM_MAX_MSG_SIZE) + 1024)
WOLFTPM_API int wolfSPDM_RespGetCtxSize(void);
WOLFTPM_API int wolfSPDM_RespSetMode(WOLFSPDM_RESP_CTX* ctx,
int useTcg, int usePsk);
WOLFTPM_API int wolfSPDM_RespSetPSK(WOLFSPDM_RESP_CTX* ctx,
const byte* psk, word32 pskSz,
const byte* hint, word32 hintSz);
/* privKey: 48 bytes (P-384 scalar). pubKey: 96 bytes (X||Y, big-endian). */
WOLFTPM_API int wolfSPDM_RespSetIdentityKey(WOLFSPDM_RESP_CTX* ctx,
const byte* privKey, word32 privSz,
const byte* pubKey, word32 pubSz);
WOLFTPM_API int wolfSPDM_RespSetTpmCallback(WOLFSPDM_RESP_CTX* ctx,
WOLFSPDM_RESP_TPM_CB cb, void* userCtx);
WOLFTPM_API void wolfSPDM_RespSetDebug(WOLFSPDM_RESP_CTX* ctx, int enable);
/* Returns WOLFSPDM_E_FRAMING on a non-TCG inbound frame. Callers MUST drop
* the connection on E_FRAMING; never fall through to the TPM parser. */
WOLFTPM_API int wolfSPDM_RespHandleMessage(WOLFSPDM_RESP_CTX* ctx,
const byte* inBuf, word32 inSz,
byte* outBuf, word32* outSz);
WOLFTPM_API void wolfSPDM_RespReset(WOLFSPDM_RESP_CTX* ctx);
/* SPDMONLY lock: when 1, the transport must reject plaintext TPM frames.
* Toggled by the requester via SPDMONLY vendor command. */
WOLFTPM_API int wolfSPDM_RespIsLocked(const WOLFSPDM_RESP_CTX* ctx);
#ifdef __cplusplus
}
#endif
#endif /* WOLFTPM_SPDM_RESPONDER */
#endif /* WOLFSPDM_RESPONDER_H */

View File

@ -35,13 +35,8 @@
#include <wolftpm/spdm/spdm_types.h>
#ifdef WOLFTPM_SPDM_TCG
#ifdef __cplusplus
extern "C" {
#endif
/* ----- TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) ----- */
/* Transport framing constants - needed by any SPDM-over-TPM build,
* not just the TCG cert handshake. */
/* Message Tags */
#define WOLFSPDM_TCG_TAG_CLEAR 0x8101 /* Clear (unencrypted) message */
@ -50,6 +45,14 @@ extern "C" {
/* Header Sizes */
#define WOLFSPDM_TCG_HEADER_SIZE 16 /* TCG binding header size */
#ifdef WOLFTPM_SPDM_TCG
#ifdef __cplusplus
extern "C" {
#endif
/* ----- TCG SPDM Binding Constants (per TCG SPDM Binding Spec v1.0) ----- */
/* FIPS Service Indicator */
#define WOLFSPDM_FIPS_NON_FIPS 0x00
#define WOLFSPDM_FIPS_APPROVED 0x01

View File

@ -176,25 +176,11 @@ WOLFTPM_API void wolfTPM2_SPDM_FreeCtx(
/* Nuvoton-Specific Functions (requires wolfSPDM with --enable-nuvoton)
* -------------------------------------------------------------------------- */
#ifdef WOLFTPM_SPDM_TCG
/**
* 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
*/
/* Transport setter - works for any SPDM-over-TPM flow (cert or PSK). */
WOLFTPM_API int wolfTPM2_SPDM_SetTisIO(
WOLFTPM2_SPDM_CTX* ctx
);
#endif /* WOLFTPM_SPDM_TCG */
#ifdef __cplusplus
} /* extern "C" */
#endif

View File

@ -641,13 +641,9 @@ WOLFTPM_API int wolfTPM2_SpdmConnectNations(WOLFTPM2_DEV* dev,
*/
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);
/* wolfTPM2_SpdmConnectNationsPsk is now an alias for the spec-flag-gated
* wolfTPM2_SpdmConnectPsk (declared below). Kept for source compatibility. */
#define wolfTPM2_SpdmConnectNationsPsk wolfTPM2_SpdmConnectPsk
/*!
\ingroup wolfTPM2_Wrappers
@ -678,6 +674,17 @@ WOLFTPM_API int wolfTPM2_SpdmNationsPskClear(WOLFTPM2_DEV* dev,
#endif /* WOLFSPDM_NATIONS */
#ifdef WOLFTPM_SPDM_PSK
/*!
\ingroup wolfTPM2_Wrappers
\brief Establish SPDM secure session via the DSP0274 PSK handshake
(PSK_EXCHANGE / PSK_FINISH). Vendor-neutral spec implementation.
*/
WOLFTPM_API int wolfTPM2_SpdmConnectPsk(WOLFTPM2_DEV* dev,
const byte* psk, word32 pskSz,
const byte* hint, word32 hintSz);
#endif /* WOLFTPM_SPDM_PSK */
#endif /* WOLFTPM_SPDM */
/*!