mirror of https://github.com/wolfSSL/wolfTPM.git
Add fwtpm SPDM support
parent
3cfbec2a4d
commit
eb21c17a7a
|
|
@ -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
|
||||
22
README.md
22
README.md
|
|
@ -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
|
||||
|
|
|
|||
98
configure.ac
98
configure.ac
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ----- */
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
||||
/*!
|
||||
|
|
|
|||
Loading…
Reference in New Issue