diff --git a/.github/workflows/make-test-swtpm.yml b/.github/workflows/make-test-swtpm.yml index 4f7cc926..ac610ecb 100644 --- a/.github/workflows/make-test-swtpm.yml +++ b/.github/workflows/make-test-swtpm.yml @@ -69,6 +69,17 @@ jobs: # STMicro ST33KTPM2 - name: st33ktpm2 firmware wolftpm_config: --enable-st33 --enable-firmware + + # SPDM with wolfSPDM (emulator mode, compile + unit test) + - name: spdm-wolfspdm + wolfssl_config: --enable-wolftpm --enable-all + wolftpm_config: --enable-spdm --enable-swtpm --with-wolfspdm=../wolfspdm + # SPDM + Nuvoton (compile-only, no hardware in CI) + - name: spdm-nuvoton + wolfssl_config: --enable-wolftpm --enable-all + wolftpm_config: --enable-spdm --enable-nuvoton --with-wolfspdm=../wolfspdm + needs_swtpm: false + # Microchip - name: microchip wolftpm_config: --enable-microchip @@ -187,6 +198,22 @@ jobs: sudo make install sudo ldconfig + - name: Checkout and build wolfSPDM + if: contains(matrix.wolftpm_config, '--enable-spdm') + run: | + # TODO: Change to wolfSSL/wolfSPDM when repo is moved over + git clone https://github.com/aidangarske/wolfSPDM.git ../wolfspdm + cd ../wolfspdm + ./autogen.sh + WOLFSPDM_CONFIG="" + if echo "${{ matrix.wolftpm_config }}" | grep -q -- '--enable-nuvoton'; then + WOLFSPDM_CONFIG="--enable-nuvoton" + fi + ./configure $WOLFSPDM_CONFIG + make + sudo make install + sudo ldconfig + # For old-wolfssl test: checkout and build old wolfSSL for linking - name: Checkout old wolfSSL if: matrix.name == 'old-wolfssl' diff --git a/configure.ac b/configure.ac index c9df3658..8d613852 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,7 @@ AC_CANONICAL_HOST AC_CANONICAL_TARGET AC_CONFIG_MACRO_DIR([m4]) + AM_INIT_AUTOMAKE([1.11 -Wall -Werror -Wno-portability foreign tar-ustar subdir-objects no-define color-tests]) AC_ARG_PROGRAM @@ -462,6 +463,76 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFTPM_PROVISIONING" fi +# SPDM Authenticated Controller (AC) Support +# Requires wolfSPDM library for all SPDM protocol operations +AC_ARG_ENABLE([spdm], + [AS_HELP_STRING([--enable-spdm],[Enable SPDM support using wolfSPDM library (default: disabled)])], + [ ENABLED_SPDM=$enableval ], + [ ENABLED_SPDM=no ] + ) + +# wolfSPDM path (required when --enable-spdm is used) +WOLFSPDM_PATH="" +AC_ARG_WITH([wolfspdm], + [AS_HELP_STRING([--with-wolfspdm=PATH],[Path to wolfSPDM install or source directory (required for --enable-spdm)])], + [ + if test "x$withval" != "xno" && test "x$withval" != "xyes" + then + WOLFSPDM_PATH="$withval" + fi + ] +) + +if test "x$ENABLED_SPDM" = "xyes" +then + # wolfSPDM is required for SPDM support + if test "x$WOLFSPDM_PATH" = "x" + then + AC_MSG_ERROR([--enable-spdm requires --with-wolfspdm=PATH + wolfSPDM provides all SPDM protocol implementation. + Build wolfSPDM first: + cd /path/to/wolfspdm + ./autogen.sh + ./configure --with-wolfssl=/path/to/wolfssl + make + Then use: --with-wolfspdm=/path/to/wolfspdm]) + fi + + # Check for installed layout: PATH/include + PATH/lib + if test -d "${WOLFSPDM_PATH}/include" && test -d "${WOLFSPDM_PATH}/lib" + then + WOLFSPDM_INCDIR="${WOLFSPDM_PATH}/include" + WOLFSPDM_LIBDIR="${WOLFSPDM_PATH}/lib" + # Check for source tree layout: PATH/wolfspdm + PATH/.libs + elif test -d "${WOLFSPDM_PATH}/wolfspdm" && test -d "${WOLFSPDM_PATH}/.libs" + then + WOLFSPDM_INCDIR="${WOLFSPDM_PATH}" + WOLFSPDM_LIBDIR="${WOLFSPDM_PATH}/.libs" + else + AC_MSG_ERROR([wolfSPDM not found at: ${WOLFSPDM_PATH} + Expected either installed layout (PATH/include + PATH/lib) + or source tree layout (PATH/wolfspdm + PATH/.libs). + Build wolfSPDM first: cd wolfspdm && ./autogen.sh && ./configure && make]) + fi + + CPPFLAGS="-I${WOLFSPDM_INCDIR} $CPPFLAGS" + LDFLAGS="-L${WOLFSPDM_LIBDIR} $LDFLAGS" + LIBS="$LIBS -lwolfspdm" + + # Check that wolfSPDM library is usable + AC_CHECK_HEADER([wolfspdm/spdm.h], [], + [AC_MSG_ERROR([wolfSPDM header not found at ${WOLFSPDM_INCDIR}])]) + + AC_DEFINE([WOLFTPM_SPDM], [1], [Enable SPDM support using wolfSPDM]) + AC_MSG_NOTICE([SPDM support enabled using wolfSPDM at: ${WOLFSPDM_PATH}]) + + # Enable wolfSPDM Nuvoton support if Nuvoton TPM is also enabled + if test "x$ENABLED_NUVOTON" = "xyes" + then + AM_CFLAGS="$AM_CFLAGS -DWOLFSPDM_NUVOTON" + AC_MSG_NOTICE([Nuvoton SPDM vendor commands enabled]) + fi +fi # HARDEN FLAGS AX_HARDEN_CC_COMPILER_FLAGS @@ -493,6 +564,7 @@ AM_CONDITIONAL([BUILD_CHECKWAITSTATE], [test "x$ENABLED_CHECKWAITSTATE" = "xyes" AM_CONDITIONAL([BUILD_AUTODETECT], [test "x$ENABLED_AUTODETECT" = "xyes"]) AM_CONDITIONAL([BUILD_FIRMWARE], [test "x$ENABLED_FIRMWARE" = "xyes"]) AM_CONDITIONAL([BUILD_HAL], [test "x$ENABLED_EXAMPLE_HAL" = "xyes" || test "x$ENABLED_MMIO" = "xyes"]) +AM_CONDITIONAL([BUILD_SPDM], [test "x$ENABLED_SPDM" = "xyes"]) CREATE_HEX_VERSION @@ -622,3 +694,7 @@ echo " * Nuvoton NPCT75x: $ENABLED_NUVOTON" echo " * Runtime Module Detection: $ENABLED_AUTODETECT" echo " * Firmware Upgrade Support: $ENABLED_FIRMWARE" +echo " * SPDM Support: $ENABLED_SPDM" +if test "x$ENABLED_SPDM" = "xyes"; then + echo " * wolfSPDM: ${WOLFSPDM_PATH}" +fi diff --git a/docs/SPDM.md b/docs/SPDM.md new file mode 100644 index 00000000..99ab00c5 --- /dev/null +++ b/docs/SPDM.md @@ -0,0 +1,468 @@ +# wolfTPM SPDM Support + +wolfTPM supports SPDM (Security Protocol and Data Model) for establishing authenticated and encrypted communication channels. SPDM is defined by DMTF specification DSP0274. + +## Overview + +SPDM provides: +- Device authentication using certificates +- Secure session establishment with key exchange (ECDHE P-384) +- Encrypted and authenticated messaging (AES-256-GCM) + +wolfTPM's SPDM implementation supports: +- **Emulator Mode**: Testing with libspdm responder emulator via TCP +- **Nuvoton Hardware Mode**: Nuvoton TPMs with SPDM AC (Authenticated Channel) support + +## Building wolfTPM with SPDM Support + +### Prerequisites + +1. **wolfSSL** with cryptographic algorithms needed for SPDM: + +```bash +git clone https://github.com/wolfSSL/wolfssl.git +cd wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-all +make && sudo make install && sudo ldconfig +``` + +**Note:** The `--enable-all` flag is required because SPDM Algorithm Set B uses: +- P-384 (secp384r1) for ECDSA signatures and ECDH key exchange +- SHA-384 for hashing +- HKDF for key derivation +- AES-256-GCM for authenticated encryption + +2. **wolfSPDM** library (for emulator testing): + +```bash +git clone https://github.com/wolfSSL/wolfspdm.git +cd wolfspdm +./autogen.sh +./configure +make +``` + +### Building wolfTPM + +For **emulator testing** (libspdm responder): +```bash +cd wolfTPM +./autogen.sh +./configure --enable-spdm --with-wolfspdm=/path/to/wolfspdm +make +``` + +For **Nuvoton TPM hardware**: +```bash +./configure --enable-spdm --enable-nuvoton +make +``` + +## Emulator Mode (--emu) + +For testing SPDM protocol flow without hardware, use the DMTF libspdm emulator. + +### Building spdm-emu + +```bash +git clone https://github.com/DMTF/spdm-emu.git +cd spdm-emu +mkdir build && cd build + +# For x86_64: +cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=mbedtls .. + +# For ARM64 (Raspberry Pi, etc.): +cmake -DARCH=arm64 -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=mbedtls .. + +make copy_sample_key +make -j4 +``` + +### Running the Test + +```bash +# Run all emulator tests (starts/stops emulator automatically) +cd wolfTPM +./examples/spdm/spdm_test.sh --emu +``` + +The script runs session establishment, signed measurements, and unsigned measurements. +It finds the emulator in `../spdm-emu/build/bin/` automatically. + +To run individual commands manually: + +```bash +# Terminal 1: Start the emulator (from spdm-emu/build/bin directory) +cd spdm-emu/build/bin +./spdm_responder_emu --ver 1.2 --hash SHA_384 --asym ECDSA_P384 \ + --dhe SECP_384_R1 --aead AES_256_GCM + +# Terminal 2: Run wolfTPM SPDM demo +cd wolfTPM +./examples/spdm/spdm_demo --emu +``` + +### Expected Output + +A successful run shows: +``` +=== SPDM Emulator Test (wolfSPDM -> libspdm) === +Connecting to 127.0.0.1:2323... + +Establishing SPDM session... +[wolfSPDM] Step 1: GET_VERSION +[wolfSPDM] Negotiated SPDM version: 0x12 +[wolfSPDM] Step 2: GET_CAPABILITIES +[wolfSPDM] Responder caps: 0x001afbf7 +[wolfSPDM] Step 3: NEGOTIATE_ALGORITHMS +[wolfSPDM] Step 4: GET_DIGESTS +[wolfSPDM] Step 5: GET_CERTIFICATE +[wolfSPDM] Step 6: KEY_EXCHANGE +[wolfSPDM] ResponderVerifyData VERIFIED +[wolfSPDM] Step 7: FINISH +[wolfSPDM] FINISH_RSP received - session established + +============================================= + SUCCESS: SPDM Session Established! + Session ID: 0xffffffff + SPDM Version: 0x12 +============================================= +``` + +### Measurement Retrieval and Verification + +After session establishment, you can retrieve device measurements with +cryptographic signature verification: + +```bash +# Terminal 1: Start emulator (must restart — emulator is single-shot) +cd spdm-emu/build/bin +./spdm_responder_emu --ver 1.2 --hash SHA_384 --asym ECDSA_P384 \ + --dhe SECP_384_R1 --aead AES_256_GCM + +# Terminal 2: Session + signed measurements (signature verified) +./examples/spdm/spdm_demo --meas + +# Or without signature verification +./examples/spdm/spdm_demo --meas --no-sig +``` + +Expected output with `--meas`: +``` +=== SPDM GET_MEASUREMENTS === +Measurements retrieved and signature VERIFIED +Measurement blocks: 8 + [1] type=0x00 size=48: + [2] type=0x01 size=48: + ... +``` + +When `--no-sig` is used, the output shows: +``` +Measurements retrieved (not signature-verified) +``` + +**Note:** The `--meas` flag implies `--emu` — it automatically uses emulator mode. +The emulator must be restarted between runs as it exits after each connection. + +### Automated Test Script + +The `spdm_test.sh` script automates emulator and/or Nuvoton hardware tests: + +```bash +# Run all emulator tests (starts/stops emulator automatically) +./examples/spdm/spdm_test.sh --emu + +# Run Nuvoton hardware tests (requires GPIO reset) +./examples/spdm/spdm_test.sh --nuvoton + +# Run both +./examples/spdm/spdm_test.sh --emu --nuvoton +``` + +The script expects `spdm_responder_emu` to be found via: +1. `SPDM_EMU_PATH` environment variable +2. `../spdm-emu/build/bin/` (cloned next to wolfTPM) +3. `spdm_responder_emu` in `PATH` + +Emulator tests run: +1. Session establishment (`--emu`) +2. Signed measurements with verification (`--meas`) +3. Unsigned measurements (`--meas --no-sig`) + +## Nuvoton Hardware Mode + +For Nuvoton NPCT75x TPMs with SPDM AC support (Firmware 7.2+). + +### Building for Nuvoton Hardware + +wolfTPM with Nuvoton SPDM requires both wolfSSL and wolfSPDM: + +```bash +# 1. Build and install wolfSSL (--enable-all required for P-384/HKDF/AES-GCM) +cd wolfssl +./autogen.sh +./configure --enable-wolftpm --enable-all +make && sudo make install && sudo ldconfig + +# 2. Build and install wolfSPDM +cd wolfspdm +./autogen.sh +./configure +make && sudo make install && sudo ldconfig + +# 3. Build wolfTPM with Nuvoton SPDM support +cd wolfTPM +./autogen.sh +./configure --enable-debug --enable-nuvoton --enable-spdm --with-wolfspdm=/path/to/wolfspdm +make +``` + +**Important:** After making changes to wolfSPDM, you must reinstall the library +(`sudo make install && sudo ldconfig`) so wolfTPM links against the updated version. +Alternatively, use `LD_LIBRARY_PATH` to point to the local build: + +```bash +LD_LIBRARY_PATH=/path/to/wolfspdm/.libs ./examples/spdm/spdm_demo --status +``` + +### Verifying TPM State + +Before running SPDM commands, verify the TPM is in a clean state: + +```bash +./examples/wrap/caps +``` + +A healthy TPM shows `TPM2_Startup pass`. If you see `TPM_RC_DISABLED`, the TPM is +stuck in SPDM-only mode (see [TPM Recovery](#tpm-recovery-from-spdm-only-mode) below). + +### Running Nuvoton SPDM Demo + +```bash +# Step 1: Enable SPDM on the TPM (only needed once, persists across resets) +./examples/spdm/spdm_demo --enable + +# Step 2: Reset the TPM (required after --enable for NTC2_PreConfig to take effect) +# Use GPIO reset or power cycle (see TPM Recovery section) + +# Step 3: Verify SPDM status +./examples/spdm/spdm_demo --status + +# Step 4: Get TPM's SPDM-Identity public key +./examples/spdm/spdm_demo --get-pubkey + +# Step 5: Establish SPDM session +./examples/spdm/spdm_demo --connect + +# Or run the full demo sequence (steps 1-5 combined) +./examples/spdm/spdm_demo --all +``` + +### SPDM-Only Provisioning Flow + +The full SPDM-only provisioning flow locks the TPM so all communication +is encrypted. After provisioning, all TPM commands are automatically routed +through the SPDM secure channel using AES-256-GCM encryption. + +**Step 1: Enable SPDM (one-time)** +```bash +./examples/spdm/spdm_demo --enable +# Reset TPM for NTC2_PreConfig to take effect: +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` + +**Step 2: Establish session and lock SPDM-only mode** +```bash +./examples/spdm/spdm_demo --connect --lock +``` +This performs the full SPDM handshake (GET_VERSION, GET_PUB_KEY, KEY_EXCHANGE, +GIVE_PUB_KEY, FINISH) and then sends the SPDM_ONLY(LOCK=YES) vendor command. +After locking, the TPM will only accept commands over SPDM. + +**Step 3: Reset the TPM** +```bash +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +``` +After reset with SPDM-only locked, the TPM requires a new SPDM session before +it will accept any TPM commands (except GetCapability and GetTestResult which +are excluded from the SPDM-only restriction per Nuvoton spec). + +**Step 4: Reconnect and run TPM commands over SPDM** +```bash +./examples/spdm/spdm_demo --connect --caps +``` +This establishes a new SPDM session and then runs TPM2_Startup and +wolfTPM2_GetCapabilities over the encrypted channel. All TPM commands +are automatically wrapped in SPDM VENDOR_DEFINED("TPM2_CMD") messages +and encrypted with AES-256-GCM. + +Expected output: +``` + SUCCESS: SPDM session established! + TPM2_Startup: PASS + Mfg 0x4 (NTC), Vendor NPCT75x, Fw 7.2 (0x50001) + SUCCESS: TPM commands working over SPDM encrypted channel! +``` + +**Step 5: Unlock (when needed)** +```bash +# Reset and reconnect first +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 && sleep 2 +./examples/spdm/spdm_demo --connect --unlock +``` + +### How TPM Commands Work Over SPDM + +When an SPDM session is active, wolfTPM automatically intercepts all TPM +commands and routes them through the SPDM encrypted channel. The process is: + +1. Application calls a wolfTPM API (e.g., `wolfTPM2_GetCapabilities()`) +2. wolfTPM serializes the TPM command packet +3. The SPDM transport layer wraps the TPM command in an SPDM + VENDOR_DEFINED_REQUEST message with the "TPM2_CMD" vendor code +4. wolfSPDM encrypts the message using AES-256-GCM with the session keys +5. The encrypted record is wrapped in a TCG binding header (tag 0x8201) + and sent to the TPM via SPI +6. The TPM's SPDM layer decrypts the record, extracts the TPM command, + and executes it +7. The TPM response is encrypted and sent back through the same path +8. wolfTPM receives the decrypted TPM response transparently + +This is completely transparent to the application. Any wolfTPM API call +works identically whether SPDM is active or not. + +### Nuvoton SPDM Protocol Flow + +The Nuvoton NPCT75x uses a simplified SPDM flow compared to standard SPDM: + +``` +GET_VERSION (cleartext, SPDM 1.0 request) + VERSION (cleartext, negotiates SPDM 1.3) +GET_PUB_KEY (cleartext, Nuvoton vendor-defined) + PUB_KEY_RSP (cleartext, returns TPM's P-384 public key) +KEY_EXCHANGE (cleartext, ECDHE P-384 key agreement) + KEY_EXCHANGE_RSP (cleartext, includes ResponderVerifyData HMAC) + --- Session keys derived (handshake phase) --- +GIVE_PUB_KEY (encrypted, sends host's P-384 public key) + GIVE_PUB_KEY_RSP (encrypted) +FINISH (encrypted, includes signature + RequesterVerifyData) + FINISH_RSP (encrypted) + --- App data keys derived (application phase) --- +SPDM_ONLY/TPM2_CMD (encrypted, application data) +``` + +Notable differences from standard SPDM 1.2: +- No GET_CAPABILITIES or NEGOTIATE_ALGORITHMS (Algorithm Set B is fixed) +- Vendor-defined commands for identity key exchange (GET_PUB_KEY, GIVE_PUB_KEY) +- Mutual authentication is mandatory +- VCA (Version/Capabilities/Algorithms transcript) = GET_VERSION + VERSION only + +### TPM Recovery from SPDM-Only Mode + +If an SPDM session fails mid-handshake (e.g., during GIVE_PUB_KEY or FINISH), the +Nuvoton TPM can enter SPDM-only mode with a stale session. In this state, all +standard TPM commands return `TPM_RC_DISABLED (0x120)`. + +**Symptoms:** +``` +TPM2_Startup: TPM_RC_DISABLED (SPDM-only mode active, this is expected) +TPM2_Shutdown failed 0x120: TPM_RC_DISABLED +``` + +**Recovery via GPIO4 hardware reset** (Raspberry Pi with GPIO4 wired to TPM reset): +```bash +# Toggle GPIO4 low for 100ms then high to reset the TPM +gpioset gpiochip0 4=0 && sleep 0.1 && gpioset gpiochip0 4=1 +sleep 2 # Wait for TPM to reinitialize + +# Verify clean state +./examples/wrap/caps +# Should show: TPM2_Startup pass +``` + +**Recovery via power cycle:** If GPIO reset is not wired, a full power cycle of +the board (not just reboot) is required to reset the TPM. + +**Note:** After recovery, SPDM remains enabled (NTC2_PreConfig is persistent) but +any active session is cleared. You can verify with `--status` and proceed with +`--connect` to establish a new session. + +## Demo Options + +| Option | Description | +|--------|-------------| +| `--emu` | Test SPDM with libspdm emulator (TCP) | +| `--host ` | Emulator host IP (default: 127.0.0.1) | +| `--port ` | Emulator port (default: 2323) | +| `--enable` | Enable SPDM on Nuvoton TPM | +| `--status` | Query SPDM status from TPM | +| `--get-pubkey` | Get TPM's SPDM-Identity public key | +| `--connect` | Establish SPDM session with TPM | +| `--caps` | Run TPM commands over SPDM (Startup + GetCapabilities) | +| `--lock` | Lock SPDM-only mode | +| `--unlock` | Unlock SPDM-only mode | +| `--meas` | Retrieve and verify device measurements (implies `--emu`) | +| `--no-sig` | Skip measurement signature verification (use with `--meas`) | +| `--all` | Run full Nuvoton demo sequence | +| `-h, --help` | Show help message | + +## SPDM Protocol Flow + +### Standard SPDM 1.2 (Emulator Mode) + +1. **GET_VERSION / VERSION** - Negotiate SPDM protocol version +2. **GET_CAPABILITIES / CAPABILITIES** - Exchange capability flags +3. **NEGOTIATE_ALGORITHMS / ALGORITHMS** - Negotiate crypto algorithms +4. **GET_DIGESTS / DIGESTS** - Get certificate chain digests +5. **GET_CERTIFICATE / CERTIFICATE** - Retrieve certificate chain +6. **KEY_EXCHANGE / KEY_EXCHANGE_RSP** - ECDH key exchange with signature +7. **FINISH / FINISH_RSP** - Complete handshake (encrypted) +8. **GET_MEASUREMENTS / MEASUREMENTS** - Retrieve device measurements with optional signature verification (via `--meas`) + +### Nuvoton SPDM (Hardware Mode) + +See [Nuvoton SPDM Protocol Flow](#nuvoton-spdm-protocol-flow) above for the +Nuvoton-specific flow which differs from standard SPDM. + +## Troubleshooting + +### Bind error 0x62 (EADDRINUSE) +Port still in use from previous run: +```bash +pkill -9 spdm_responder_emu +sleep 5 +./spdm_responder_emu +``` + +### Connection refused +Check if emulator is listening: +```bash +ss -tlnp | grep 2323 +``` + +### Certificate not found +Run emulator from the `spdm-emu/build/bin` directory so it can find the certificate files: +```bash +cd spdm-emu/build/bin +./spdm_responder_emu +``` + +### TPM_RC_DISABLED (0x120) + +All TPM commands returning `TPM_RC_DISABLED` means the TPM is in SPDM-only mode +without an active session. This happens after a failed SPDM handshake. See +[TPM Recovery from SPDM-Only Mode](#tpm-recovery-from-spdm-only-mode) for +recovery steps. + +### SPDM Error Codes + +| Code | Name | Description | +|------|------|-------------| +| 0x01 | InvalidRequest | Message format incorrect | +| 0x04 | UnexpectedRequest | Message out of sequence | +| 0x05 | DecryptError | Decryption or MAC verification failed | +| 0x06 | UnsupportedRequest | Request not supported or format rejected | +| 0x41 | VersionMismatch | SPDM version mismatch | diff --git a/examples/include.am b/examples/include.am index 96c034f0..d34804ac 100644 --- a/examples/include.am +++ b/examples/include.am @@ -18,6 +18,7 @@ include examples/seal/include.am include examples/attestation/include.am include examples/firmware/include.am include examples/endorsement/include.am +include examples/spdm/include.am if BUILD_EXAMPLES EXTRA_DIST += examples/run_examples.sh diff --git a/examples/spdm/README.md b/examples/spdm/README.md new file mode 100644 index 00000000..e3fe04d7 --- /dev/null +++ b/examples/spdm/README.md @@ -0,0 +1,76 @@ +# SPDM Examples + +This directory contains examples demonstrating SPDM (Security Protocol and Data Model) +functionality with wolfTPM. + +## Overview + +The SPDM demo (`spdm_demo`) shows how to establish an SPDM secure session between +the host and a TPM using the wolfSPDM library backend. It supports both the standard +spdm-emu emulator and Nuvoton hardware TPMs. + +For real SPDM support on hardware TPMs, contact **support@wolfssl.com** + +## Example + +### `spdm_demo.c` - SPDM Secure Session Demo + +**Quick test (emulator — starts/stops automatically):** + +```bash +./examples/spdm/spdm_test.sh --emu +``` + +Runs session establishment, signed measurements, unsigned measurements, +challenge authentication, heartbeat, and key update. + +**Quick test (Nuvoton hardware):** + +```bash +./examples/spdm/spdm_test.sh --nuvoton +``` + +Runs connect, lock, caps-over-SPDM, unlock, and cleartext verification. + +**Manual commands:** + +```bash +# Emulator (start spdm_responder_emu first, see docs/SPDM.md) +./spdm_demo --emu # Session only +./spdm_demo --meas # Session + signed measurements +./spdm_demo --meas --no-sig # Session + unsigned measurements +./spdm_demo --challenge # Sessionless challenge authentication +./spdm_demo --emu --heartbeat # Session + heartbeat keep-alive +./spdm_demo --emu --key-update # Session + key rotation + +# Nuvoton hardware +./spdm_demo --enable # Enable SPDM on TPM (one-time, requires reset) +./spdm_demo --connect --status # Connect + get SPDM status +./spdm_demo --connect --lock # Connect + lock SPDM-only mode +./spdm_demo --connect --caps # Connect + run TPM commands over SPDM +./spdm_demo --connect --unlock # Connect + unlock SPDM-only mode +``` + +## Building + +### Prerequisites + +Build wolfSSL with full crypto support and wolfSPDM: + +```bash +# wolfSSL (needs --enable-all for P-384/ECDH) +cd wolfssl && ./configure --enable-wolftpm --enable-all && make && sudo make install + +# wolfSPDM +cd wolfspdm && ./autogen.sh && ./configure && make && sudo make install + +# wolfTPM with SPDM +./configure --enable-spdm --with-wolfspdm=/path/to/wolfspdm +make +``` + +## Support + +For production use with hardware TPMs and full SPDM protocol support, contact: + +**support@wolfssl.com** diff --git a/examples/spdm/include.am b/examples/spdm/include.am new file mode 100644 index 00000000..d4526b56 --- /dev/null +++ b/examples/spdm/include.am @@ -0,0 +1,17 @@ +# vim:ft=automake +# All paths should be given relative to the root + +if BUILD_EXAMPLES +if BUILD_SPDM +noinst_PROGRAMS += examples/spdm/spdm_demo + +examples_spdm_spdm_demo_SOURCES = examples/spdm/spdm_demo.c +examples_spdm_spdm_demo_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_spdm_spdm_demo_DEPENDENCIES = src/libwolftpm.la +endif +endif + +example_spdmdir = $(exampledir)/spdm +dist_example_spdm_DATA = examples/spdm/spdm_demo.c + +DISTCLEANFILES+= examples/spdm/.libs/spdm_demo diff --git a/examples/spdm/spdm_demo.c b/examples/spdm/spdm_demo.c new file mode 100644 index 00000000..7514f1c4 --- /dev/null +++ b/examples/spdm/spdm_demo.c @@ -0,0 +1,1394 @@ +/* spdm_demo.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* SPDM Secure Session Demo + * + * Demonstrates establishing an SPDM secure session with a TPM + * and running TPM commands over the encrypted channel. + * + * Targets: Nuvoton NPCT75x (Fw 7.2+) connected via SPI + * + * Usage: + * ./spdm_demo --enable Enable SPDM on TPM (requires reset) + * ./spdm_demo --status Query SPDM status + * ./spdm_demo --connect Establish SPDM session and run test command + * ./spdm_demo --lock Lock SPDM-only mode + * ./spdm_demo --unlock Unlock SPDM-only mode + * ./spdm_demo --all Run full demo sequence + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifndef WOLFTPM2_NO_WOLFCRYPT +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include + +/* Socket includes for TCP transport to libspdm emulator */ +#ifdef WOLFTPM_SWTPM + #include + #include + #include /* TCP_NODELAY */ + #include + #include + #include + #define SPDM_EMU_DEFAULT_PORT 2323 /* DEFAULT_SPDM_PLATFORM_PORT (MCTP) */ + #define SPDM_EMU_DEFAULT_HOST "127.0.0.1" + /* Transport types for libspdm emulator socket protocol */ + #ifndef SOCKET_TRANSPORT_TYPE_MCTP + #define SOCKET_TRANSPORT_TYPE_MCTP 0x00000001 + #endif + #ifndef SOCKET_TRANSPORT_TYPE_TCP + #define SOCKET_TRANSPORT_TYPE_TCP 0x00000003 + #endif +#endif + +#ifndef WOLFTPM2_NO_WRAPPER + +#include +#include + +#ifdef WOLFTPM_SPDM + +#include + +#include + +#ifndef WOLFTPM2_NO_WOLFCRYPT + #include + #include +#endif + +/* -------------------------------------------------------------------------- */ +/* Unified SPDM I/O Layer + * + * Single I/O callback that handles both: + * - TCP transport to libspdm emulator (--emu mode) + * - TPM TIS transport to Nuvoton hardware (--connect mode) + * + * The callback gates internally based on the transport mode set in context. + * -------------------------------------------------------------------------- */ + +/* Transport modes for unified I/O callback */ +typedef enum { + SPDM_IO_MODE_NONE = 0, /* Not configured */ + SPDM_IO_MODE_TCP = 1, /* TCP socket to libspdm emulator */ + SPDM_IO_MODE_TPM = 2 /* TPM TIS (SPI) to Nuvoton hardware */ +} SPDM_IO_MODE; + +/* Unified I/O context - passed as userCtx to wolfSPDM */ +typedef struct { + SPDM_IO_MODE mode; + /* TCP fields (for emulator) */ + int sockFd; + int isSecured; + /* TPM fields (for Nuvoton hardware) */ + WOLFTPM2_DEV* tpmDev; +} SPDM_IO_CTX; + +/* Global unified I/O context */ +static SPDM_IO_CTX g_ioCtx; + +/******************************************************************************/ +/* --- SPDM Demo --- */ +/******************************************************************************/ + +/* Forward declarations */ +int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]); + +static void usage(void) +{ + printf("SPDM Secure Session Demo\n"); + printf("Demonstrates SPDM secure communication with Nuvoton NPCT75x\n"); + printf("\n"); + printf("Usage: spdm_demo [options]\n"); + printf("Options:\n"); + printf(" --enable Enable SPDM on TPM via NTC2_PreConfig\n"); + printf(" --status Query SPDM status from TPM\n"); + printf(" --get-pubkey Get TPM's SPDM-Identity public key\n"); + printf(" --connect Establish SPDM session and run test command\n"); + printf(" --lock Lock SPDM-only mode\n"); + printf(" --unlock Unlock SPDM-only mode\n"); + printf(" --all Run full demo sequence\n"); +#ifdef WOLFTPM_SWTPM + printf(" --emu Test SPDM with libspdm emulator (TCP)\n"); + printf(" --meas Retrieve and verify device measurements (--emu)\n"); + printf(" --no-sig Skip signature verification (use with --meas)\n"); + printf(" --challenge Challenge authentication (sessionless, --emu)\n"); + printf(" --heartbeat Session heartbeat keep-alive (--emu)\n"); + printf(" --key-update Session key rotation (--emu)\n"); + printf(" --host Emulator IP address (default: 127.0.0.1)\n"); + printf(" --port Emulator port (default: 2323)\n"); +#endif + printf(" -h, --help Show this help message\n"); + printf("\n"); + printf("Nuvoton Hardware Mode (--enable, --connect, etc.):\n"); + printf(" - Requires Nuvoton NPCT75x TPM with Fw 7.2+ via SPI\n"); + printf(" - Built with: ./configure --enable-spdm --enable-nuvoton\n"); +#ifdef WOLFTPM_SWTPM + printf("\n"); + printf("Emulator Mode (--emu):\n"); + printf(" - Tests SPDM 1.2 protocol with libspdm responder emulator\n"); + printf(" - Built with: ./configure --enable-spdm --with-wolfspdm=PATH\n"); + printf(" - Start emulator: ./spdm_responder_emu\n"); + printf(" - Run test: ./spdm_demo --emu\n"); +#endif +} + +/* -------------------------------------------------------------------------- */ +/* Unified I/O Callback Implementation + * -------------------------------------------------------------------------- */ + +#ifdef WOLFTPM_SWTPM +/* MCTP transport constants */ +#define SOCKET_SPDM_COMMAND_NORMAL 0x00000001 +#define MCTP_MESSAGE_TYPE_SPDM 0x05 +#define MCTP_MESSAGE_TYPE_SECURED 0x06 + +/* Initialize I/O context for TCP mode (emulator) */ +static int spdm_io_init_tcp(SPDM_IO_CTX* ioCtx, const char* host, int port) +{ + int sockFd; + struct sockaddr_in addr; + int optVal = 1; + + XMEMSET(ioCtx, 0, sizeof(*ioCtx)); + ioCtx->mode = SPDM_IO_MODE_NONE; + ioCtx->sockFd = -1; + + sockFd = socket(AF_INET, SOCK_STREAM, 0); + if (sockFd < 0) { + printf("TCP: Failed to create socket (%d)\n", errno); + return -1; + } + + /* Disable Nagle's algorithm for immediate send */ + setsockopt(sockFd, IPPROTO_TCP, TCP_NODELAY, &optVal, sizeof(optVal)); + + XMEMSET(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + if (inet_pton(AF_INET, host, &addr.sin_addr) != 1) { + printf("TCP: Invalid address %s\n", host); + close(sockFd); + return -1; + } + + if (connect(sockFd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + printf("TCP: Failed to connect to %s:%d (%d)\n", host, port, errno); + close(sockFd); + return -1; + } + + ioCtx->mode = SPDM_IO_MODE_TCP; + ioCtx->sockFd = sockFd; + return 0; +} + +/* Cleanup TCP I/O context */ +static void spdm_io_cleanup(SPDM_IO_CTX* ioCtx) +{ + if (ioCtx->sockFd >= 0) { + close(ioCtx->sockFd); + ioCtx->sockFd = -1; + } + ioCtx->mode = SPDM_IO_MODE_NONE; + ioCtx->tpmDev = NULL; +} + +/* Internal: TCP send/receive for emulator */ +static int spdm_io_tcp_exchange(SPDM_IO_CTX* ioCtx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz) +{ + byte sendBuf[512]; + byte recvHdr[12]; + ssize_t sent, recvd; + word32 respSize; + word32 payloadSz; + int isSecured = 0; + + if (ioCtx->sockFd < 0) { + return -1; + } + + /* Detect secured messages: SPDM messages start with version (0x10-0x1F), + * secured messages start with SessionID (typically 0xFF...). */ + if (txSz >= 8 && (txBuf[0] < 0x10 || txBuf[0] > 0x1F)) { + isSecured = 1; + } + + /* Payload = MCTP header (1 byte) + SPDM message */ + payloadSz = 1 + txSz; + if (12 + payloadSz > sizeof(sendBuf)) { + return -1; + } + + /* Build socket header: command(4,BE) + transport_type(4,BE) + size(4,BE) */ + sendBuf[0] = 0x00; sendBuf[1] = 0x00; sendBuf[2] = 0x00; sendBuf[3] = 0x01; + sendBuf[4] = 0x00; sendBuf[5] = 0x00; sendBuf[6] = 0x00; sendBuf[7] = 0x01; + sendBuf[8] = (byte)(payloadSz >> 24); + sendBuf[9] = (byte)(payloadSz >> 16); + sendBuf[10] = (byte)(payloadSz >> 8); + sendBuf[11] = (byte)(payloadSz & 0xFF); + + /* MCTP header: 0x05 for SPDM, 0x06 for secured SPDM */ + sendBuf[12] = isSecured ? MCTP_MESSAGE_TYPE_SECURED : MCTP_MESSAGE_TYPE_SPDM; + + if (txSz > 0) { + XMEMCPY(sendBuf + 13, txBuf, txSz); + } + + sent = send(ioCtx->sockFd, sendBuf, 12 + payloadSz, 0); + if (sent != (ssize_t)(12 + payloadSz)) { + return -1; + } + + recvd = recv(ioCtx->sockFd, recvHdr, 12, MSG_WAITALL); + if (recvd != 12) { + return -1; + } + + respSize = ((word32)recvHdr[8] << 24) | ((word32)recvHdr[9] << 16) | + ((word32)recvHdr[10] << 8) | (word32)recvHdr[11]; + + if (respSize < 1 || respSize - 1 > *rxSz) { + return -1; + } + + /* Skip MCTP header */ + { + byte mctpHdr; + recvd = recv(ioCtx->sockFd, &mctpHdr, 1, MSG_WAITALL); + if (recvd != 1) return -1; + } + + *rxSz = respSize - 1; + if (*rxSz > 0) { + recvd = recv(ioCtx->sockFd, rxBuf, *rxSz, MSG_WAITALL); + if (recvd != (ssize_t)*rxSz) return -1; + } + + return 0; +} +#endif /* WOLFTPM_SWTPM */ + +/* TCG SPDM Binding tags */ +#define TCG_SPDM_TAG_CLEAR 0x8101 +#define TCG_SPDM_TAG_SECURED 0x8201 + +#ifdef WOLFTPM_NUVOTON +/* Initialize I/O context for TPM mode (Nuvoton hardware) */ +static void spdm_io_init_tpm(SPDM_IO_CTX* ioCtx, WOLFTPM2_DEV* dev) +{ + XMEMSET(ioCtx, 0, sizeof(*ioCtx)); + ioCtx->mode = SPDM_IO_MODE_TPM; + ioCtx->sockFd = -1; + ioCtx->tpmDev = dev; +} + +/* Internal: TPM TIS send/receive for Nuvoton hardware + * + * wolfSPDM may send either: + * - Raw SPDM messages (standard commands like GET_VERSION) + * - Already TCG-framed messages (vendor-defined commands like GET_PUBK) + * - Encrypted SPDM records (session messages like FINISH) + * + * This function detects which format and handles accordingly: + * - If already TCG-framed (starts with 0x8101 or 0x8201): send as-is + * - If encrypted record (starts with session ID): wrap in TCG secured format + * - If raw SPDM: wrap in TCG clear message format first + */ +static int spdm_io_tpm_exchange(SPDM_IO_CTX* ioCtx, WOLFSPDM_CTX* spdmCtx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz) +{ + WOLFTPM2_DEV* dev = ioCtx->tpmDev; + byte tcgTxBuf[512]; /* TCG-framed message to send */ + byte tcgRxBuf[512]; /* TCG-framed response */ + const byte* sendBuf; + word32 sendSz; + word32 tcgRxSz; + int alreadyFramed = 0; + int isEncrypted = 0; + int rc; + + if (dev == NULL || spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Check if message is already TCG-framed (starts with tag 0x8101 or 0x8201) */ + if (txSz >= 2) { + word16 tag = (word16)((txBuf[0] << 8) | txBuf[1]); + if (tag == TCG_SPDM_TAG_CLEAR || tag == TCG_SPDM_TAG_SECURED) { + alreadyFramed = 1; + } + } + + /* Check if message is encrypted (not a clear SPDM message) + * Clear SPDM messages start with version byte (0x10-0x1F) + * Encrypted messages start with session ID (first byte is low byte of reqSessionId) */ + if (!alreadyFramed && txSz >= 8) { + /* SPDM version bytes are 0x10 (1.0), 0x11 (1.1), 0x12 (1.2), 0x13 (1.3) */ + if (txBuf[0] < 0x10 || txBuf[0] > 0x1F) { + isEncrypted = 1; + } + } + +#ifdef DEBUG_WOLFTPM + printf("SPDM I/O TX (%u bytes, %s)\n", txSz, + alreadyFramed ? "TCG-framed" : + (isEncrypted ? "encrypted" : "raw SPDM")); +#endif + + if (alreadyFramed) { + /* Already TCG-framed, send as-is */ + sendBuf = txBuf; + sendSz = txSz; + } + else if (isEncrypted) { + /* Wrap TCG-format encrypted message in TCG binding header (16 bytes) + * + * wolfSPDM (Nuvoton mode) outputs TCG format: + * SessionID(4 LE) + SeqNum(8 LE) + Length(2 LE) + encrypted + tag = 14 + data + * + * We just add the TCG binding header (16 bytes with tag 0x8201): + * Tag(2 BE) + Size(4 BE) + ConnHandle(4 BE) + FIPS(2 BE) + Reserved(4) + */ + word32 totalSz = 16 + txSz; + word32 connHandle = 0; + word16 fipsInd = 0; + + if (totalSz > sizeof(tcgTxBuf)) { + printf("SPDM I/O: Encrypted message too large\n"); + return -1; + } + + /* Get ConnectionHandle from SPDM context. + * FipsIndicator is Don't Care for requests (use 0x0000). */ + if (spdmCtx != NULL) { + connHandle = wolfSPDM_GetConnectionHandle(spdmCtx); + } + fipsInd = WOLFSPDM_FIPS_NON_FIPS; /* Don't Care for requests */ + + /* TCG binding header (16 bytes, all BE) */ + tcgTxBuf[0] = (byte)(TCG_SPDM_TAG_SECURED >> 8); + tcgTxBuf[1] = (byte)(TCG_SPDM_TAG_SECURED & 0xFF); + tcgTxBuf[2] = (byte)(totalSz >> 24); + tcgTxBuf[3] = (byte)(totalSz >> 16); + tcgTxBuf[4] = (byte)(totalSz >> 8); + tcgTxBuf[5] = (byte)(totalSz & 0xFF); + tcgTxBuf[6] = (byte)(connHandle >> 24); /* ConnectionHandle (BE) */ + tcgTxBuf[7] = (byte)(connHandle >> 16); + tcgTxBuf[8] = (byte)(connHandle >> 8); + tcgTxBuf[9] = (byte)(connHandle & 0xFF); + tcgTxBuf[10] = (byte)(fipsInd >> 8); /* FipsIndicator (BE) */ + tcgTxBuf[11] = (byte)(fipsInd & 0xFF); + tcgTxBuf[12] = 0; tcgTxBuf[13] = 0; /* Reserved */ + tcgTxBuf[14] = 0; tcgTxBuf[15] = 0; + + /* Copy TCG-format encrypted SPDM record (already has 14-byte header) */ + XMEMCPY(tcgTxBuf + 16, txBuf, txSz); + + sendBuf = tcgTxBuf; + sendSz = totalSz; +#ifdef DEBUG_WOLFTPM + printf(" -> TCG secured (%u bytes, connHandle=0x%x)\n", + sendSz, connHandle); +#endif + } + else { + /* Wrap raw SPDM message in TCG clear message format (16-byte header) */ + int tcgTxSz = wolfSPDM_BuildTcgClearMessage(spdmCtx, txBuf, txSz, + tcgTxBuf, sizeof(tcgTxBuf)); + if (tcgTxSz < 0) { + printf("SPDM I/O: BuildTcgClearMessage failed: %d\n", tcgTxSz); + return tcgTxSz; + } + sendBuf = tcgTxBuf; + sendSz = (word32)tcgTxSz; + } + + /* Send via TPM2_SendRawBytes */ + tcgRxSz = sizeof(tcgRxBuf); + rc = TPM2_SendRawBytes(&dev->ctx, sendBuf, sendSz, tcgRxBuf, &tcgRxSz); + + if (rc != TPM_RC_SUCCESS) { + printf("SPDM I/O: SendRawBytes failed: %s\n", TPM2_GetRCString(rc)); + return rc; + } + +#ifdef DEBUG_WOLFTPM + printf("SPDM I/O RX (%u bytes)\n", tcgRxSz); +#endif + + if (alreadyFramed) { + /* wolfSPDM already did TCG framing, so it will parse the response. + * Return the raw TCG response as-is. */ + if (tcgRxSz > *rxSz) { + return -1; + } + XMEMCPY(rxBuf, tcgRxBuf, tcgRxSz); + *rxSz = tcgRxSz; + } + else if (isEncrypted) { + /* For encrypted requests, response can be: + * - SECURED (0x8201): successful response, return encrypted record for decryption + * - CLEAR (0x8101): error response, return SPDM payload directly */ + word16 rspTag = 0; + if (tcgRxSz >= 2) { + rspTag = (word16)((tcgRxBuf[0] << 8) | tcgRxBuf[1]); + } + + if (rspTag == TCG_SPDM_TAG_SECURED) { + /* Secured response - strip TCG header, return encrypted record */ + if (tcgRxSz < 16) { + return -1; + } + if (tcgRxSz - 16 > *rxSz) { + return -1; + } + XMEMCPY(rxBuf, tcgRxBuf + 16, tcgRxSz - 16); + *rxSz = tcgRxSz - 16; + } + else if (rspTag == TCG_SPDM_TAG_CLEAR) { + /* Clear response - likely an error, extract SPDM payload */ + rc = wolfSPDM_ParseTcgClearMessage(tcgRxBuf, tcgRxSz, rxBuf, rxSz, NULL); + if (rc < 0) { + return rc; + } +#ifdef DEBUG_WOLFTPM + /* Check if it's an SPDM ERROR response */ + if (*rxSz >= 2 && rxBuf[1] == 0x7F) { /* SPDM_ERROR */ + printf(" SPDM ERROR: code=0x%02x data=0x%02x\n", + (*rxSz >= 3) ? rxBuf[2] : 0, + (*rxSz >= 4) ? rxBuf[3] : 0); + } +#endif + } + else { + return -1; + } + } + else { + /* For clear requests, response should be CLEAR */ + rc = wolfSPDM_ParseTcgClearMessage(tcgRxBuf, tcgRxSz, rxBuf, rxSz, NULL); + if (rc < 0) { + return rc; + } + } + + return 0; +} +#endif /* WOLFTPM_NUVOTON */ + +/* Unified I/O callback for wolfSPDM + * Handles both TCP (emulator) and TPM TIS (Nuvoton hardware) transports. + * The mode is determined by ioCtx->mode set during initialization. + * + * For TCP (emulator): adds MCTP framing and sends over socket + * For TPM (Nuvoton): adds TCG binding framing and sends via TIS */ +static int wolfspdm_io_callback( + WOLFSPDM_CTX* ctx, + const byte* txBuf, word32 txSz, + byte* rxBuf, word32* rxSz, + void* userCtx) +{ + SPDM_IO_CTX* ioCtx = (SPDM_IO_CTX*)userCtx; + + if (ioCtx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) { + return -1; + } + + (void)ctx; + + if (0) { + /* not reached */ + } +#ifdef WOLFTPM_SWTPM + else if (ioCtx->mode == SPDM_IO_MODE_TCP) { + /* TCP path for emulator - uses MCTP framing */ + return spdm_io_tcp_exchange(ioCtx, txBuf, txSz, rxBuf, rxSz); + } +#endif /* WOLFTPM_SWTPM */ +#ifdef WOLFTPM_NUVOTON + else if (ioCtx->mode == SPDM_IO_MODE_TPM) { + /* TPM TIS path for Nuvoton - uses TCG binding framing */ + return spdm_io_tpm_exchange(ioCtx, ctx, txBuf, txSz, rxBuf, rxSz); + } +#endif /* WOLFTPM_NUVOTON */ + + return -1; +} + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Demo Functions + * -------------------------------------------------------------------------- */ + +#ifdef WOLFSPDM_NUVOTON +static int demo_enable(WOLFTPM2_DEV* dev) +{ + int rc; + + printf("\n=== Enable SPDM on TPM ===\n"); + printf("Sending NTC2_PreConfig to enable SPDM (CFG_H bit 1 = 0)...\n"); + + rc = wolfTPM2_SpdmEnable(dev); + if (rc == 0) { + printf(" SUCCESS: SPDM is enabled on this TPM (was already enabled " + "or just configured).\n"); + printf(" If newly enabled, TPM must be reset to take effect.\n"); + } else if (rc == (int)TPM_RC_DISABLED) { + printf(" SPDM-only mode is active - TPM commands are blocked.\n"); + printf(" SPDM is already enabled (this is not an error).\n"); + rc = 0; /* Not an error - SPDM is already active */ + } else if (rc == TPM_RC_COMMAND_CODE) { + printf(" NOTE: NTC2_PreConfig not supported on this TPM.\n"); + printf(" SPDM may already be enabled, or use vendor tools to enable.\n"); + rc = 0; /* Not a fatal error for demo */ + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef WOLFSPDM_NUVOTON +static int demo_status(WOLFTPM2_DEV* dev) +{ + int rc; + WOLFSPDM_NUVOTON_STATUS status; + + printf("\n=== SPDM Status (GET_STS_ vendor command) ===\n"); + + /* Initialize I/O context for TPM mode */ + spdm_io_init_tpm(&g_ioCtx, dev); + + /* Set I/O callback for wolfSPDM */ + rc = wolfSPDM_SetIO(dev->spdmCtx->spdmCtx, wolfspdm_io_callback, &g_ioCtx); + if (rc != 0) { + printf(" ERROR: Failed to set I/O callback: %d\n", rc); + return rc; + } + + XMEMSET(&status, 0, sizeof(status)); + rc = wolfTPM2_SpdmGetStatus(dev, &status); + if (rc == 0) { + int isConnected = wolfTPM2_SpdmIsConnected(dev); + byte negVer = wolfSPDM_GetVersion_Negotiated(dev->spdmCtx->spdmCtx); + + printf(" SPDM Enabled: %s\n", status.spdmEnabled ? "Yes" : "No"); + printf(" SPDM-Only Locked: %s\n", + status.spdmOnlyLocked ? "YES (TPM commands blocked)" : "No"); + printf(" Session Active: %s\n", isConnected ? "Yes" : "No"); + if (isConnected) { + printf(" Negotiated Ver: SPDM %u.%u (0x%02x)\n", + (negVer >> 4) & 0xF, negVer & 0xF, negVer); + printf(" Session ID: 0x%08x\n", + wolfTPM2_SpdmGetSessionId(dev)); + } + printf(" Nuvoton Status: v%u.%u\n", + status.specVersionMajor, status.specVersionMinor); + + if (status.spdmOnlyLocked) { + printf("\n NOTE: TPM is in SPDM-only mode. Standard TPM commands will\n"); + printf(" return TPM_RC_DISABLED until SPDM session is established\n"); + printf(" and --unlock is called.\n"); + } + } else { + printf(" FAILED to get status: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + printf(" Note: GET_STS requires SPDM to be enabled on the TPM\n"); + } + return rc; +} +#endif /* WOLFSPDM_NUVOTON */ + +#ifdef WOLFSPDM_NUVOTON +static int demo_get_pubkey(WOLFTPM2_DEV* dev) +{ + int rc; + byte pubKey[128]; + word32 pubKeySz = sizeof(pubKey); + word32 i; + + printf("\n=== Get TPM SPDM-Identity Public Key ===\n"); + + /* Initialize I/O context for TPM mode */ + spdm_io_init_tpm(&g_ioCtx, dev); + + /* Set I/O callback for wolfSPDM */ + rc = wolfSPDM_SetIO(dev->spdmCtx->spdmCtx, wolfspdm_io_callback, &g_ioCtx); + if (rc != 0) { + printf(" ERROR: Failed to set I/O callback: %d\n", rc); + return rc; + } + + rc = wolfTPM2_SpdmGetPubKey(dev, pubKey, &pubKeySz); + if (rc == 0) { + printf(" SUCCESS: Got TPM public key (%d bytes)\n", (int)pubKeySz); + printf(" Key (hex): "); + for (i = 0; i < pubKeySz && i < 32; i++) { + printf("%02x", pubKey[i]); + } + if (pubKeySz > 32) { + printf("..."); + } + printf("\n"); + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + printf(" Note: GET_PUB_KEY requires SPDM to be enabled\n"); + } + return rc; +} + +static int demo_connect(WOLFTPM2_DEV* dev) +{ + int rc; +#ifndef WOLFTPM2_NO_WOLFCRYPT + /* Generate test ECDSA P-384 keypair for mutual authentication */ + ecc_key hostKey; + WC_RNG rng; + byte hostPrivKey[48]; /* Raw 48-byte scalar */ + word32 hostPrivKeySz = sizeof(hostPrivKey); + byte hostPubKeyX[48], hostPubKeyY[48]; + word32 xSz = sizeof(hostPubKeyX), ySz = sizeof(hostPubKeyY); + /* TPMT_PUBLIC structure for host's public key (120 bytes) */ + byte hostPubKeyTPMT[120]; + word32 hostPubKeyTPMTSz = 0; +#endif + + printf("\n=== SPDM Connect (Full Handshake) ===\n"); + printf("Establishing SPDM secure session...\n"); + printf(" Steps: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> " + "GIVE_PUB_KEY -> FINISH\n\n"); + +#ifndef WOLFTPM2_NO_WOLFCRYPT + /* Generate test keypair for SPDM mutual authentication */ + printf("Generating host ECDSA P-384 keypair for mutual auth...\n"); + + rc = wc_InitRng(&rng); + if (rc != 0) { + printf(" ERROR: wc_InitRng failed: %d\n", rc); + return rc; + } + + rc = wc_ecc_init(&hostKey); + if (rc != 0) { + wc_FreeRng(&rng); + printf(" ERROR: wc_ecc_init failed: %d\n", rc); + return rc; + } + + rc = wc_ecc_make_key_ex(&rng, 48, &hostKey, ECC_SECP384R1); + if (rc != 0) { + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + printf(" ERROR: wc_ecc_make_key failed: %d\n", rc); + return rc; + } + + /* Export private key (raw scalar) */ + rc = wc_ecc_export_private_only(&hostKey, hostPrivKey, &hostPrivKeySz); + if (rc != 0) { + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + printf(" ERROR: export private key failed: %d\n", rc); + return rc; + } + + /* Export public key X,Y */ + rc = wc_ecc_export_public_raw(&hostKey, hostPubKeyX, &xSz, + hostPubKeyY, &ySz); + wc_ecc_free(&hostKey); + wc_FreeRng(&rng); + if (rc != 0) { + printf(" ERROR: export public key failed: %d\n", rc); + return rc; + } + + /* Build TPMT_PUBLIC structure for ECDSA P-384 signing key: + * type(2) + nameAlg(2) + objectAttr(4) + authPolicy(2+0) + + * symmetric(2) + scheme(2+2) + curveID(2) + kdf(2) + unique(2+48+2+48) = 120 bytes */ + { + byte* p = hostPubKeyTPMT; + /* type = TPM_ALG_ECC (0x0023) */ + *p++ = 0x00; *p++ = 0x23; + /* nameAlg = TPM_ALG_SHA384 (0x000C) */ + *p++ = 0x00; *p++ = 0x0C; + /* objectAttributes = 0x00040000 (sign only) */ + *p++ = 0x00; *p++ = 0x04; *p++ = 0x00; *p++ = 0x00; + /* authPolicy size = 0 */ + *p++ = 0x00; *p++ = 0x00; + /* parameters.eccDetail.symmetric = TPM_ALG_NULL (0x0010) */ + *p++ = 0x00; *p++ = 0x10; + /* parameters.eccDetail.scheme = TPM_ALG_ECDSA (0x0018) */ + *p++ = 0x00; *p++ = 0x18; + /* parameters.eccDetail.scheme.hashAlg = SHA384 (0x000C) */ + *p++ = 0x00; *p++ = 0x0C; + /* parameters.eccDetail.curveID = TPM_ECC_NIST_P384 (0x0004) */ + *p++ = 0x00; *p++ = 0x04; + /* parameters.eccDetail.kdf = TPM_ALG_NULL (0x0010) */ + *p++ = 0x00; *p++ = 0x10; + /* unique.x size = 48 */ + *p++ = 0x00; *p++ = 0x30; + /* unique.x data */ + XMEMCPY(p, hostPubKeyX, 48); p += 48; + /* unique.y size = 48 */ + *p++ = 0x00; *p++ = 0x30; + /* unique.y data */ + XMEMCPY(p, hostPubKeyY, 48); p += 48; + hostPubKeyTPMTSz = (word32)(p - hostPubKeyTPMT); + } + + printf(" Generated host key (TPMT_PUBLIC: %u bytes, private: %u bytes)\n", + hostPubKeyTPMTSz, hostPrivKeySz); + + /* Initialize unified I/O context for TPM mode */ + spdm_io_init_tpm(&g_ioCtx, dev); + + /* Set unified I/O callback (handles both TCP emulator and TPM TIS modes) */ + rc = wolfSPDM_SetIO(dev->spdmCtx->spdmCtx, wolfspdm_io_callback, &g_ioCtx); + if (rc != 0) { + printf(" ERROR: Failed to set I/O callback: %d\n", rc); + return rc; + } + +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(dev->spdmCtx->spdmCtx, 1); +#endif + + rc = wolfTPM2_SpdmConnectNuvoton(dev, hostPubKeyTPMT, hostPubKeyTPMTSz, + hostPrivKey, hostPrivKeySz); +#else + /* No wolfCrypt - skip mutual authentication */ + /* Initialize unified I/O context for TPM mode */ + spdm_io_init_tpm(&g_ioCtx, dev); + + /* Set unified I/O callback (handles both TCP emulator and TPM TIS modes) */ + rc = wolfSPDM_SetIO(dev->spdmCtx->spdmCtx, wolfspdm_io_callback, &g_ioCtx); + if (rc != 0) { + printf(" ERROR: Failed to set I/O callback: %d\n", rc); + return rc; + } + +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(dev->spdmCtx->spdmCtx, 1); +#endif + + rc = wolfTPM2_SpdmConnectNuvoton(dev, NULL, 0, NULL, 0); +#endif + if (rc == 0) { + printf(" SUCCESS: SPDM session established!\n"); + printf(" All TPM commands now encrypted with AES-256-GCM\n"); + + /* TPM commands are automatically wrapped in SPDM VENDOR_DEFINED + * messages and encrypted with AES-256-GCM over the secure channel. */ + + /* Check connection status */ + if (wolfTPM2_SpdmIsConnected(dev)) { + printf(" SPDM session is active\n"); + } + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + printf(" Note: Ensure SPDM is enabled and backend is configured\n"); + } + return rc; +} + +static int demo_lock(WOLFTPM2_DEV* dev, int lock) +{ + int rc; + + printf("\n=== SPDM-Only Mode: %s ===\n", lock ? "LOCK" : "UNLOCK"); + + rc = wolfTPM2_SpdmSetOnlyMode(dev, lock); + if (rc == 0) { + printf(" SUCCESS: SPDM-only mode %s\n", + lock ? "LOCKED" : "UNLOCKED"); + if (lock) { + printf(" WARNING: TPM will only accept commands over SPDM!\n"); + } + } else { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + return rc; +} + +/* Test TPM commands over SPDM secure channel. + * Only works after full provisioning: connect -> lock -> reset -> reconnect. + * Uses wolfTPM2 wrapper API - all commands route through SPDM automatically. */ +static int demo_caps(WOLFTPM2_DEV* dev) +{ + int rc; + WOLFTPM2_CAPS caps; + + printf("\n=== TPM Commands over SPDM Secure Channel ===\n"); + + if (!wolfTPM2_SpdmIsConnected(dev)) { + printf(" ERROR: SPDM session not established. Run --connect first.\n"); + return -1; + } + + /* TPM2_Startup - needed after TPM reset */ + printf(" TPM2_Startup over SPDM...\n"); + { + Startup_In startupIn; + XMEMSET(&startupIn, 0, sizeof(startupIn)); + startupIn.startupType = TPM_SU_CLEAR; + rc = TPM2_Startup(&startupIn); + } + if (rc == TPM_RC_SUCCESS) { + printf(" TPM2_Startup: PASS\n"); + } else if (rc == TPM_RC_INITIALIZE) { + printf(" TPM2_Startup: Already initialized (OK)\n"); + rc = TPM_RC_SUCCESS; + } else { + printf(" TPM2_Startup: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; + } + + /* wolfTPM2_GetCapabilities - reads manufacturer, vendor, firmware info + * This calls TPM2_GetCapability internally, all encrypted over SPDM */ + printf(" wolfTPM2_GetCapabilities over SPDM...\n"); + rc = wolfTPM2_GetCapabilities(dev, &caps); + if (rc == TPM_RC_SUCCESS) { + printf(" Mfg 0x%x (%s), Vendor %s, Fw %u.%u (0x%x)\n", + caps.mfg, caps.mfgStr, caps.vendorStr, + caps.fwVerMajor, caps.fwVerMinor, caps.fwVerVendor); + printf("\n SUCCESS: TPM commands working over SPDM encrypted channel!\n"); + } else { + printf(" wolfTPM2_GetCapabilities: 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + } + + return rc; +} + +static int demo_all(WOLFTPM2_DEV* dev) +{ + int rc; + int failures = 0; + + printf("\n========================================\n"); + printf("SPDM Secure Session Full Demo\n"); + printf("========================================\n"); + printf("Target: Nuvoton NPCT75x via SPI\n"); + printf("Algorithm Set B: ECDSA P-384, SHA-384, ECDHE P-384, AES-256-GCM\n"); + printf("========================================\n"); + + /* Step 1: Check/Enable SPDM */ + rc = demo_enable(dev); + if (rc != 0) failures++; + + /* Step 2: Query Status */ + rc = demo_status(dev); + if (rc != 0) failures++; + + /* Step 3: Get TPM public key */ + rc = demo_get_pubkey(dev); + if (rc != 0) failures++; + + /* Step 4: Connect (full handshake) */ + rc = demo_connect(dev); + if (rc != 0) failures++; + + /* Step 5: Disconnect */ + if (wolfTPM2_SpdmIsConnected(dev)) { + rc = wolfTPM2_SpdmDisconnect(dev); + if (rc == 0) { + printf("\n SPDM session disconnected\n"); + } else { + printf("\n Disconnect failed: 0x%x\n", rc); + failures++; + } + } + + printf("\n========================================\n"); + printf("Demo Summary\n"); + printf("========================================\n"); + if (failures == 0) { + printf("ALL STEPS COMPLETED SUCCESSFULLY\n"); + } else { + printf("%d STEP(S) FAILED\n", failures); + } + printf("========================================\n"); + + return (failures == 0) ? 0 : 1; +} +#endif /* WOLFSPDM_NUVOTON */ + +/* -------------------------------------------------------------------------- */ +/* Standard SPDM over TCP (for libspdm emulator testing) */ +/* -------------------------------------------------------------------------- */ + +#ifdef WOLFTPM_SWTPM + +#ifndef NO_WOLFSPDM_MEAS +/* Retrieve and display device measurements from an established SPDM session. + * Calls wolfSPDM measurement APIs directly. */ +static int demo_measurements(WOLFSPDM_CTX* ctx, int requestSignature) +{ + int rc, count, i; + + printf("\n=== SPDM GET_MEASUREMENTS ===\n"); + + rc = wolfSPDM_GetMeasurements(ctx, SPDM_MEAS_OPERATION_ALL, + requestSignature); + if (rc == WOLFSPDM_SUCCESS) { + printf("Measurements retrieved and signature VERIFIED\n"); + } + else if (rc == WOLFSPDM_E_MEAS_NOT_VERIFIED) { + printf("Measurements retrieved (not signature-verified)\n"); + } + else if (rc == WOLFSPDM_E_MEAS_SIG_FAIL) { + printf("WARNING: Measurement signature INVALID\n"); + return rc; + } + else { + printf("ERROR: %s (%d)\n", wolfSPDM_GetErrorString(rc), rc); + return rc; + } + + count = wolfSPDM_GetMeasurementCount(ctx); + printf("Measurement blocks: %d\n", count); + + for (i = 0; i < count; i++) { + byte idx = 0, mtype = 0; + byte val[WOLFSPDM_MAX_MEAS_VALUE_SIZE]; + word32 valSz = sizeof(val); + int j; + + rc = wolfSPDM_GetMeasurementBlock(ctx, i, &idx, &mtype, val, &valSz); + if (rc != WOLFSPDM_SUCCESS) + continue; + + printf(" [%u] type=0x%02x size=%u: ", idx, mtype, valSz); + for (j = 0; j < (int)valSz && j < 48; j++) + printf("%02x", val[j]); + if (valSz > 48) + printf("..."); + printf("\n"); + } + + return 0; +} +#endif /* !NO_WOLFSPDM_MEAS */ + +#ifndef NO_WOLFSPDM_CHALLENGE +/* Perform CHALLENGE authentication (sessionless attestation). + * Uses individual handshake steps instead of wolfSPDM_Connect() to avoid + * establishing a full session (KEY_EXCHANGE/FINISH). */ +static int demo_challenge(WOLFSPDM_CTX* ctx) +{ + int rc; + + printf("\n=== SPDM CHALLENGE (Sessionless Attestation) ===\n"); + + /* Step 1: GET_VERSION */ + printf(" GET_VERSION...\n"); + rc = wolfSPDM_GetVersion(ctx); + if (rc != WOLFSPDM_SUCCESS) { + printf(" ERROR: GET_VERSION failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return rc; + } + + /* Step 2: GET_CAPABILITIES */ + printf(" GET_CAPABILITIES...\n"); + rc = wolfSPDM_GetCapabilities(ctx); + if (rc != WOLFSPDM_SUCCESS) { + printf(" ERROR: GET_CAPABILITIES failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return rc; + } + + /* Step 3: NEGOTIATE_ALGORITHMS */ + printf(" NEGOTIATE_ALGORITHMS...\n"); + rc = wolfSPDM_NegotiateAlgorithms(ctx); + if (rc != WOLFSPDM_SUCCESS) { + printf(" ERROR: NEGOTIATE_ALGORITHMS failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return rc; + } + + /* Step 4: GET_DIGESTS */ + printf(" GET_DIGESTS...\n"); + rc = wolfSPDM_GetDigests(ctx); + if (rc != WOLFSPDM_SUCCESS) { + printf(" ERROR: GET_DIGESTS failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return rc; + } + + /* Step 5: GET_CERTIFICATE (also extracts responder public key) */ + printf(" GET_CERTIFICATE...\n"); + rc = wolfSPDM_GetCertificate(ctx, 0); + if (rc != WOLFSPDM_SUCCESS) { + printf(" ERROR: GET_CERTIFICATE failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + return rc; + } + + /* Step 6: CHALLENGE */ + printf(" CHALLENGE (slot=0, no measurement summary)...\n"); + rc = wolfSPDM_Challenge(ctx, 0, SPDM_MEAS_SUMMARY_HASH_NONE); + if (rc == WOLFSPDM_SUCCESS) { + printf("\n CHALLENGE authentication PASSED\n"); + } + else { + printf("\n CHALLENGE authentication FAILED: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + + return rc; +} +#endif /* !NO_WOLFSPDM_CHALLENGE */ + +/* Send HEARTBEAT over an established SPDM session */ +static int demo_heartbeat(WOLFSPDM_CTX* ctx) +{ + int rc; + + printf("\n=== SPDM HEARTBEAT ===\n"); + + rc = wolfSPDM_Heartbeat(ctx); + if (rc == WOLFSPDM_SUCCESS) { + printf(" HEARTBEAT_ACK received — session alive\n"); + } + else { + printf(" HEARTBEAT failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + + return rc; +} + +/* Perform KEY_UPDATE to rotate session encryption keys */ +static int demo_key_update(WOLFSPDM_CTX* ctx) +{ + int rc; + + printf("\n=== SPDM KEY_UPDATE ===\n"); + + rc = wolfSPDM_KeyUpdate(ctx, 1); /* updateAll = 1: rotate both keys */ + if (rc == WOLFSPDM_SUCCESS) { + printf(" KEY_UPDATE completed — new keys active\n"); + } + else { + printf(" KEY_UPDATE failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + + return rc; +} + +/* SPDM emulator test using wolfSPDM library + * Connects to libspdm responder emulator via TCP and performs full SPDM 1.2 handshake + * Uses the unified I/O callback (same as Nuvoton hardware mode) */ +static int demo_emulator(const char* host, int port, int doMeas, + int requestSignature, int doChallenge, + int doHeartbeat, int doKeyUpdate) +{ + WOLFSPDM_CTX* ctx; + int rc; +#ifndef WOLFSPDM_DYNAMIC_MEMORY + byte spdmBuf[WOLFSPDM_CTX_STATIC_SIZE]; +#endif + + printf("\n=== wolfSPDM spdm-emu Test ===\n"); + printf("Connecting to %s:%d...\n", host, port); + + /* Initialize unified I/O context for TCP mode (emulator) */ + rc = spdm_io_init_tcp(&g_ioCtx, host, port); + if (rc < 0) { + printf("Failed to connect to emulator\n"); + printf("Make sure spdm_responder_emu is running:\n"); + printf(" ./spdm_responder_emu --trans TCP\n"); + return rc; + } + + /* Create wolfSPDM context */ +#ifdef WOLFSPDM_DYNAMIC_MEMORY + ctx = wolfSPDM_New(); + if (ctx == NULL) { + printf("ERROR: wolfSPDM_New() failed\n"); + spdm_io_cleanup(&g_ioCtx); + return -1; + } +#else + ctx = (WOLFSPDM_CTX*)spdmBuf; + rc = wolfSPDM_InitStatic(ctx, (int)sizeof(spdmBuf)); + if (rc != WOLFSPDM_SUCCESS) { + printf("ERROR: wolfSPDM_InitStatic() failed: %s\n", + wolfSPDM_GetErrorString(rc)); + spdm_io_cleanup(&g_ioCtx); + return rc; + } +#endif + + /* Set unified I/O callback (handles both TCP emulator and TPM TIS modes) */ + wolfSPDM_SetIO(ctx, wolfspdm_io_callback, &g_ioCtx); +#ifdef DEBUG_WOLFTPM + wolfSPDM_SetDebug(ctx, 1); +#endif + +#ifndef NO_WOLFSPDM_CHALLENGE + /* Challenge mode: sessionless attestation (no KEY_EXCHANGE/FINISH) */ + if (doChallenge) { + rc = demo_challenge(ctx); + + /* Cleanup */ + wolfSPDM_Free(ctx); + spdm_io_cleanup(&g_ioCtx); + return (rc == WOLFSPDM_SUCCESS) ? 0 : rc; + } +#else + (void)doChallenge; +#endif + + /* Full SPDM handshake - this single call replaces ~1000 lines of code! + * Performs: GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> + * GET_DIGESTS -> GET_CERTIFICATE -> KEY_EXCHANGE -> FINISH */ + printf("\nEstablishing SPDM session...\n"); + rc = wolfSPDM_Connect(ctx); + + if (rc == WOLFSPDM_SUCCESS) { + printf("\n=============================================\n"); + printf(" SUCCESS: SPDM Session Established!\n"); + printf(" Session ID: 0x%08x\n", wolfSPDM_GetSessionId(ctx)); + printf(" SPDM Version: 0x%02x\n", wolfSPDM_GetVersion_Negotiated(ctx)); + printf("=============================================\n"); + + /* Heartbeat: send keep-alive over encrypted channel */ + if (doHeartbeat) { + rc = demo_heartbeat(ctx); + if (rc != WOLFSPDM_SUCCESS) goto cleanup; + } + + /* Key update: rotate session keys */ + if (doKeyUpdate) { + rc = demo_key_update(ctx); + if (rc != WOLFSPDM_SUCCESS) goto cleanup; + } + +#ifndef NO_WOLFSPDM_MEAS + /* Retrieve measurements if requested */ + if (doMeas) { + rc = demo_measurements(ctx, requestSignature); + } +#else + (void)doMeas; + (void)requestSignature; +#endif + } else { + printf("\nERROR: wolfSPDM_Connect() failed: %s (%d)\n", + wolfSPDM_GetErrorString(rc), rc); + } + +cleanup: + /* Cleanup */ + wolfSPDM_Free(ctx); + spdm_io_cleanup(&g_ioCtx); + + return (rc == WOLFSPDM_SUCCESS) ? 0 : rc; +} + + +#endif /* WOLFTPM_SWTPM */ + +int TPM2_SPDM_Demo(void* userCtx, int argc, char *argv[]) +{ + int rc; + WOLFTPM2_DEV dev; + int i; +#ifdef WOLFTPM_SWTPM + const char* emuHost = SPDM_EMU_DEFAULT_HOST; + int emuPort = SPDM_EMU_DEFAULT_PORT; + int useEmulator = 0; + int doMeas = 0; + int requestSignature = 1; + int doChallenge = 0; + int doHeartbeat = 0; + int doKeyUpdate = 0; +#endif + + if (argc <= 1) { + usage(); + return 0; + } + + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "-h") == 0 || + XSTRCMP(argv[i], "--help") == 0) { + usage(); + return 0; + } +#ifdef WOLFTPM_SWTPM + else if (XSTRCMP(argv[i], "--emu") == 0) { + useEmulator = 1; + } + else if (XSTRCMP(argv[i], "--host") == 0 && i + 1 < argc) { + emuHost = argv[++i]; + } + else if (XSTRCMP(argv[i], "--port") == 0 && i + 1 < argc) { + emuPort = atoi(argv[++i]); + } + else if (XSTRCMP(argv[i], "--meas") == 0) { + doMeas = 1; + useEmulator = 1; + } + else if (XSTRCMP(argv[i], "--no-sig") == 0) { + requestSignature = 0; + } + else if (XSTRCMP(argv[i], "--challenge") == 0) { + doChallenge = 1; + useEmulator = 1; + } + else if (XSTRCMP(argv[i], "--heartbeat") == 0) { + doHeartbeat = 1; + useEmulator = 1; + } + else if (XSTRCMP(argv[i], "--key-update") == 0) { + doKeyUpdate = 1; + useEmulator = 1; + } +#endif + } + +#ifdef WOLFTPM_SWTPM + /* Handle --emu mode (TCP to emulator, no TPM needed) */ + if (useEmulator) { + printf("Entering emulator mode...\n"); + fflush(stdout); + return demo_emulator(emuHost, emuPort, doMeas, requestSignature, + doChallenge, doHeartbeat, doKeyUpdate); + } +#endif + + /* Init the TPM2 device. + * When SPDM is enabled on Nuvoton TPMs, TPM2_Startup may return + * TPM_RC_DISABLED because the TPM expects SPDM-only communication. + * wolfTPM2_Init tolerates this when built with WOLFTPM_SPDM - + * SPDM commands work over raw SPI regardless of TPM startup state. */ + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + if (rc != 0) { + printf("wolfTPM2_Init failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; + } + + /* Initialize SPDM support */ + rc = wolfTPM2_SpdmInit(&dev); + if (rc != 0) { + printf("wolfTPM2_SpdmInit failed: 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + printf("Ensure wolfTPM is built with --enable-spdm --with-wolfspdm=PATH\n"); + wolfTPM2_Cleanup(&dev); + return rc; + } + + /* Process command-line options */ + for (i = 1; i < argc; i++) { +#ifdef WOLFSPDM_NUVOTON + if (XSTRCMP(argv[i], "--all") == 0) { + rc = demo_all(&dev); + break; + } + else if (XSTRCMP(argv[i], "--enable") == 0) { + rc = demo_enable(&dev); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--status") == 0) { + rc = demo_status(&dev); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--get-pubkey") == 0) { + rc = demo_get_pubkey(&dev); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--connect") == 0) { + rc = demo_connect(&dev); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--lock") == 0) { + rc = demo_lock(&dev, 1); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--unlock") == 0) { + rc = demo_lock(&dev, 0); + if (rc != 0) break; + } + else if (XSTRCMP(argv[i], "--caps") == 0) { + rc = demo_caps(&dev); + if (rc != 0) break; + } + else +#endif /* WOLFSPDM_NUVOTON */ + { + printf("Unknown option: %s\n", argv[i]); + usage(); + rc = BAD_FUNC_ARG; + break; + } + } + + /* Cleanup SPDM */ + wolfTPM2_SpdmCleanup(&dev); + + wolfTPM2_Cleanup(&dev); + return rc; +} + +/******************************************************************************/ +/* --- END SPDM Demo --- */ +/******************************************************************************/ + +#ifndef NO_MAIN_DRIVER +int main(int argc, char *argv[]) +{ + int rc = -1; + +#ifndef WOLFTPM2_NO_WRAPPER + rc = TPM2_SPDM_Demo(NULL, argc, argv); +#else + printf("Wrapper code not compiled in\n"); + (void)argc; + (void)argv; +#endif + + return (rc == 0) ? 0 : 1; +} +#endif /* !NO_MAIN_DRIVER */ + +#endif /* WOLFTPM_SPDM */ +#endif /* !WOLFTPM2_NO_WRAPPER */ \ No newline at end of file diff --git a/examples/spdm/spdm_test.sh b/examples/spdm/spdm_test.sh new file mode 100755 index 00000000..e36291a5 --- /dev/null +++ b/examples/spdm/spdm_test.sh @@ -0,0 +1,305 @@ +#!/bin/bash +# +# spdm_test.sh - SPDM test script +# +# Supports two modes: +# --emu Test SPDM with libspdm emulator (session + measurements) +# --nuvoton Test Nuvoton SPDM provisioning flow (lock/unlock/caps) +# +# Usage: +# ./spdm_test.sh --emu # Emulator tests +# ./spdm_test.sh --nuvoton # Nuvoton hardware tests +# ./spdm_test.sh --emu --nuvoton # Both +# ./spdm_test.sh # Default: --nuvoton +# + +SPDM_DEMO="./examples/spdm/spdm_demo" +CAPS_DEMO="./examples/wrap/caps" +GPIO_CHIP="gpiochip0" +GPIO_PIN="4" +PASS=0 +FAIL=0 +TOTAL=0 +DO_EMU=0 +DO_NUVOTON=0 +EMU_PID="" + +# Colors (if terminal supports it) +if [ -t 1 ]; then + GREEN='\033[0;32m' + RED='\033[0;31m' + YELLOW='\033[0;33m' + NC='\033[0m' +else + GREEN='' + RED='' + YELLOW='' + NC='' +fi + +usage() { + echo "Usage: $0 [--emu] [--nuvoton] [path-to-spdm_demo]" + echo "" + echo "Options:" + echo " --emu Test SPDM with libspdm emulator (session + measurements)" + echo " --nuvoton Test Nuvoton SPDM provisioning flow (lock/unlock/caps)" + echo " -h, --help Show this help" + echo "" + echo "If neither --emu nor --nuvoton is specified, defaults to --nuvoton." + echo "" + echo "Emulator mode expects spdm_responder_emu to be found via:" + echo " 1. SPDM_EMU_PATH environment variable" + echo " 2. ../spdm-emu/build/bin/ (cloned next to wolfTPM)" + echo " 3. spdm_responder_emu in PATH" +} + +# Parse arguments +for arg in "$@"; do + case "$arg" in + --emu) + DO_EMU=1 + ;; + --nuvoton) + DO_NUVOTON=1 + ;; + -h|--help) + usage + exit 0 + ;; + *) + # Treat as path to spdm_demo + SPDM_DEMO="$arg" + ;; + esac +done + +# Default to --nuvoton if nothing specified +if [ $DO_EMU -eq 0 ] && [ $DO_NUVOTON -eq 0 ]; then + DO_NUVOTON=1 +fi + +# Find spdm_responder_emu for --emu mode +find_emu() { + # 1. Check SPDM_EMU_PATH + if [ -n "$SPDM_EMU_PATH" ]; then + if [ -x "$SPDM_EMU_PATH/spdm_responder_emu" ]; then + EMU_DIR="$SPDM_EMU_PATH" + EMU_BIN="$SPDM_EMU_PATH/spdm_responder_emu" + return 0 + elif [ -x "$SPDM_EMU_PATH" ]; then + EMU_DIR="$(dirname "$SPDM_EMU_PATH")" + EMU_BIN="$SPDM_EMU_PATH" + return 0 + fi + fi + + # 2. Check common relative paths (cloned next to wolfTPM) + for dir in \ + "../spdm-emu/build/bin" \ + "../../spdm-emu/build/bin" \ + "$HOME/spdm-emu/build/bin"; do + if [ -x "$dir/spdm_responder_emu" ]; then + EMU_DIR="$dir" + EMU_BIN="$dir/spdm_responder_emu" + return 0 + fi + done + + # 3. Check PATH + if command -v spdm_responder_emu >/dev/null 2>&1; then + EMU_BIN="$(command -v spdm_responder_emu)" + EMU_DIR="$(dirname "$EMU_BIN")" + return 0 + fi + + return 1 +} + +# Start the emulator (must run from its bin dir for cert files) +start_emu() { + echo " Starting spdm_responder_emu..." + (cd "$EMU_DIR" && ./spdm_responder_emu --ver 1.2 \ + --hash SHA_384 --asym ECDSA_P384 \ + --dhe SECP_384_R1 --aead AES_256_GCM >/dev/null 2>&1) & + EMU_PID=$! + sleep 1 + + # Verify it started + if ! kill -0 "$EMU_PID" 2>/dev/null; then + echo -e " ${RED}ERROR: Emulator failed to start${NC}" + EMU_PID="" + return 1 + fi + return 0 +} + +# Stop the emulator +stop_emu() { + if [ -n "$EMU_PID" ]; then + kill "$EMU_PID" 2>/dev/null + wait "$EMU_PID" 2>/dev/null + EMU_PID="" + fi +} + +# Cleanup on exit +cleanup() { + stop_emu +} +trap cleanup EXIT + +gpio_reset() { + echo " GPIO reset..." + gpioset "$GPIO_CHIP" "$GPIO_PIN=0" 2>/dev/null + sleep 0.1 + gpioset "$GPIO_CHIP" "$GPIO_PIN=1" 2>/dev/null + sleep 2 +} + +# Run a test with GPIO reset (Nuvoton hardware) +run_test_nuvoton() { + local name="$1" + shift + + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + gpio_reset + + if "$@"; then + echo -e " ${GREEN}PASS${NC}" + PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}" + FAIL=$((FAIL + 1)) + fi + echo "" +} + +# Run an emulator test (starts/stops emu per test since it's single-shot) +run_test_emu() { + local name="$1" + shift + + TOTAL=$((TOTAL + 1)) + echo "[$TOTAL] $name" + + if ! start_emu; then + echo -e " ${RED}FAIL (emulator start)${NC}" + FAIL=$((FAIL + 1)) + echo "" + return 1 + fi + + if "$@"; then + echo -e " ${GREEN}PASS${NC}" + PASS=$((PASS + 1)) + else + echo -e " ${RED}FAIL${NC}" + FAIL=$((FAIL + 1)) + fi + + stop_emu + sleep 1 # Let port release + echo "" +} + +# Check spdm_demo exists +if [ ! -x "$SPDM_DEMO" ]; then + echo "Error: $SPDM_DEMO not found or not executable" + usage + exit 1 +fi + +# ========================================================================== +# Emulator Tests +# ========================================================================== +if [ $DO_EMU -eq 1 ]; then + echo "=== SPDM Emulator Tests ===" + + if ! find_emu; then + echo -e "${RED}ERROR: spdm_responder_emu not found${NC}" + echo "" + echo "Set SPDM_EMU_PATH or clone spdm-emu next to wolfTPM:" + echo " git clone https://github.com/DMTF/spdm-emu.git ../spdm-emu" + echo " cd ../spdm-emu && mkdir build && cd build" + echo " cmake -DARCH=x64 -DTOOLCHAIN=GCC -DTARGET=Release -DCRYPTO=mbedtls .." + echo " make copy_sample_key && make" + exit 1 + fi + + echo "Using emulator: $EMU_BIN" + echo "Using demo: $SPDM_DEMO" + echo "" + + # Test 1: Session establishment + run_test_emu "Session establishment (--emu)" \ + "$SPDM_DEMO" --emu + + # Test 2: Session + signed measurements + run_test_emu "Signed measurements (--meas)" \ + "$SPDM_DEMO" --meas + + # Test 3: Session + unsigned measurements + run_test_emu "Unsigned measurements (--meas --no-sig)" \ + "$SPDM_DEMO" --meas --no-sig + + # Test 4: Challenge authentication (sessionless) + run_test_emu "Challenge authentication (--challenge)" \ + "$SPDM_DEMO" --challenge + + # Test 5: Session + heartbeat + run_test_emu "Heartbeat (--emu --heartbeat)" \ + "$SPDM_DEMO" --emu --heartbeat + + # Test 6: Session + key update + run_test_emu "Key update (--emu --key-update)" \ + "$SPDM_DEMO" --emu --key-update + + echo "" +fi + +# ========================================================================== +# Nuvoton Hardware Tests +# ========================================================================== +if [ $DO_NUVOTON -eq 1 ]; then + echo "=== Nuvoton SPDM Provisioning Flow Test ===" + echo "Using: $SPDM_DEMO" + echo "Caps: $CAPS_DEMO" + echo "" + + if [ ! -x "$CAPS_DEMO" ]; then + echo -e "${YELLOW}Warning: $CAPS_DEMO not found, skipping cleartext test${NC}" + fi + + # Step 1: Connect + status (baseline, no SPDM-only) + run_test_nuvoton "Connect + Status" "$SPDM_DEMO" --connect --status + + # Step 2: Lock SPDM-only mode + run_test_nuvoton "Connect + Lock SPDM-only" "$SPDM_DEMO" --connect --lock + + # Step 3: TPM commands over SPDM (requires SPDM-only to be locked) + run_test_nuvoton "Connect + Caps over SPDM" "$SPDM_DEMO" --connect --caps + + # Step 4: Unlock SPDM-only mode + run_test_nuvoton "Connect + Unlock SPDM-only" "$SPDM_DEMO" --connect --unlock + + # Step 5: Verify cleartext TPM works (no SPDM, proves unlock worked) + if [ -x "$CAPS_DEMO" ]; then + run_test_nuvoton "Cleartext caps (no SPDM)" "$CAPS_DEMO" + fi + + echo "" +fi + +# ========================================================================== +# Summary +# ========================================================================== +echo "=== Results ===" +echo "Total: $TOTAL Passed: $PASS Failed: $FAIL" +if [ $FAIL -eq 0 ]; then + echo -e "${GREEN}ALL TESTS PASSED${NC}" + exit 0 +else + echo -e "${RED}$FAIL TEST(S) FAILED${NC}" + exit 1 +fi diff --git a/src/include.am b/src/include.am index a59409bc..0ebcd82c 100644 --- a/src/include.am +++ b/src/include.am @@ -23,6 +23,10 @@ if BUILD_WINAPI src_libwolftpm_la_SOURCES += src/tpm2_winapi.c src_libwolftpm_la_LIBADD = -ltbs endif +if BUILD_SPDM +# SPDM support using wolfSPDM library +src_libwolftpm_la_SOURCES += src/tpm2_spdm.c +endif src_libwolftpm_la_CFLAGS = $(src_libwolftpm_la_EXTRAS) -DBUILDING_WOLFTPM $(AM_CFLAGS) src_libwolftpm_la_CPPFLAGS = -DBUILDING_WOLFTPM $(AM_CPPFLAGS) diff --git a/src/tpm2.c b/src/tpm2.c index bfdfee54..933fe906 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -30,6 +30,9 @@ #include #include #include +#ifdef WOLFTPM_SPDM +#include +#endif #include @@ -477,6 +480,36 @@ static TPM_RC TPM2_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) if (ctx == NULL || packet == NULL) return BAD_FUNC_ARG; +#ifdef WOLFTPM_SPDM + /* If SPDM session is active, wrap command through SPDM transport */ + if (ctx->spdmCtx != NULL) { + WOLFTPM2_SPDM_CTX* spdmCtx = (WOLFTPM2_SPDM_CTX*)ctx->spdmCtx; + if (wolfTPM2_SPDM_IsConnected(spdmCtx)) { + byte tpmResp[WOLFSPDM_MAX_MSG_SIZE]; + word32 tpmRespSz = sizeof(tpmResp); + + /* Use wolfSPDM to encrypt, send, receive, and decrypt */ + rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, + packet->buf, packet->pos, tpmResp, &tpmRespSz); + if (rc != 0) { + return rc; + } + + /* Copy TPM response back into packet buffer. + * Note: packet->buf is a pointer so sizeof gives pointer size, + * use MAX_RESPONSE_SIZE for the actual buffer capacity. */ + if (tpmRespSz > MAX_RESPONSE_SIZE) { + return TPM_RC_SIZE; + } + XMEMCPY(packet->buf, tpmResp, tpmRespSz); + packet->pos = 0; + packet->size = tpmRespSz; + + return TPM2_Packet_Parse(TPM_RC_SUCCESS, packet); + } + } +#endif /* WOLFTPM_SPDM */ + /* submit command and wait for response */ rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet); if (rc != 0) @@ -619,6 +652,61 @@ TPM_RC TPM2_SetHalIoCb(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx) return rc; } +#ifdef WOLFTPM_SPDM +TPM_RC TPM2_SendRawBytes(TPM2_CTX* ctx, + const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz) +{ + TPM_RC rc; + TPM2_Packet packet; + word32 rspSz; + UINT32 tmpSz; + + if (ctx == NULL || txBuf == NULL || rxBuf == NULL || rxSz == NULL) { + return BAD_FUNC_ARG; + } + if (txSz > sizeof(ctx->cmdBuf)) { + return TPM_RC_SIZE; + } + + /* Copy transmit data into the context command buffer */ + XMEMCPY(ctx->cmdBuf, txBuf, txSz); + + /* Set up the packet structure pointing to cmdBuf. + * pos = number of bytes to send, size = buffer capacity. */ + packet.buf = ctx->cmdBuf; + packet.pos = (int)txSz; + packet.size = (int)sizeof(ctx->cmdBuf); + + /* Send through the transport layer (TIS, Linux dev, SWTPM, etc.). + * TIS will write txSz bytes, then read the response into cmdBuf. + * The response size is parsed from the header (offset 2, 4 bytes BE) + * inside TIS and only that many bytes are read from the FIFO. */ + rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, &packet); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + + /* After TIS returns, the response is in cmdBuf. The TIS layer read + * exactly the number of bytes indicated by the header size field. + * Extract that size from the response to know how many bytes to copy. + * Both TPM2 and TCG SPDM headers have: tag(2) + size(4) at offset 0. */ + if (packet.size < 6) { + return TPM_RC_FAILURE; + } + XMEMCPY(&tmpSz, &ctx->cmdBuf[2], sizeof(UINT32)); + rspSz = TPM2_Packet_SwapU32(tmpSz); + + if (rspSz > (word32)packet.size || rspSz > *rxSz) { + return TPM_RC_SIZE; + } + + XMEMCPY(rxBuf, ctx->cmdBuf, rspSz); + *rxSz = rspSz; + + return TPM_RC_SUCCESS; +} +#endif /* WOLFTPM_SPDM */ + /* If timeoutTries <= 0 then it will not try and startup chip and will * use existing default locality */ TPM_RC TPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, @@ -1554,6 +1642,7 @@ TPM_RC TPM2_StartAuthSession(StartAuthSession_In* in, StartAuthSession_Out* out) return rc; } + TPM_RC TPM2_PolicyRestart(PolicyRestart_In* in) { TPM_RC rc; @@ -5598,6 +5687,67 @@ int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out) } #endif /* WOLFTPM_NUVOTON */ +/* NTC2 PreConfig/GetConfig for runtime vendor detection (WOLFTPM_AUTODETECT). + * Identical to the WOLFTPM_NUVOTON implementations above. */ +#if defined(WOLFTPM_AUTODETECT) && !defined(WOLFTPM_NUVOTON) && \ + !defined(WOLFTPM_ST33) + +int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in) +{ + int rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + + if (in == NULL || ctx == NULL) + return BAD_FUNC_ARG; + + rc = TPM2_AcquireLock(ctx); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet packet; + CmdInfo_t info = {0,0,0,0}; + info.inHandleCnt = 1; + + TPM2_Packet_Init(ctx, &packet); + TPM2_Packet_AppendU32(&packet, in->authHandle); + TPM2_Packet_AppendAuth(&packet, ctx, &info); + TPM2_Packet_AppendBytes(&packet, (byte*)&in->preConfig, + sizeof(in->preConfig)); + TPM2_Packet_Finalize(&packet, TPM_ST_SESSIONS, + TPM_CC_NTC2_PreConfig); + + rc = TPM2_SendCommandAuth(ctx, &packet, &info); + + TPM2_ReleaseLock(ctx); + } + return rc; +} + +int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out) +{ + int rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + + if (out == NULL || ctx == NULL) + return BAD_FUNC_ARG; + + rc = TPM2_AcquireLock(ctx); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet packet; + TPM2_Packet_Init(ctx, &packet); + TPM2_Packet_Finalize(&packet, TPM_ST_NO_SESSIONS, + TPM_CC_NTC2_GetConfig); + + rc = TPM2_SendCommand(ctx, &packet); + if (rc == TPM_RC_SUCCESS) { + TPM2_Packet_ParseBytes(&packet, (byte*)&out->preConfig, + sizeof(out->preConfig)); + } + + TPM2_ReleaseLock(ctx); + } + return rc; +} +#endif /* WOLFTPM_AUTODETECT && !WOLFTPM_NUVOTON && !WOLFTPM_ST33 */ + #ifdef WOLFTPM_FIRMWARE_UPGRADE #if defined(WOLFTPM_SLB9672) || defined(WOLFTPM_SLB9673) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index c24a1563..64b0c885 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -972,6 +972,7 @@ int TPM2_Packet_Finalize(TPM2_Packet* packet, TPM_ST tag, TPM_CC cc) return cmdSz; } + /******************************************************************************/ /* --- END TPM Packet Assembly / Parsing -- */ /******************************************************************************/ diff --git a/src/tpm2_spdm.c b/src/tpm2_spdm.c new file mode 100644 index 00000000..3755f0be --- /dev/null +++ b/src/tpm2_spdm.c @@ -0,0 +1,368 @@ +/* tpm2_spdm.c + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* SPDM Thin Wrapper Layer for wolfTPM + * + * This file provides thin wrapper functions around the wolfSPDM library. + * All SPDM protocol logic, cryptography, and message handling is implemented + * in wolfSPDM. This file only provides: + * + * 1. Context management (init/free) + * 2. Pass-through wrappers to wolfSPDM functions + * 3. TPM-specific NTC2_PreConfig for SPDM enable (Nuvoton only) + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#ifdef WOLFTPM_SPDM + +#include + +/* wolfSPDM provides all SPDM protocol implementation */ +#include + +/* -------------------------------------------------------------------------- */ +/* Context Management */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_InitCtx( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_IO_CB ioCb, + void* userCtx) +{ + int rc; + + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + + /* Zero initialize context */ + XMEMSET(ctx, 0, sizeof(WOLFTPM2_SPDM_CTX)); + +#ifdef WOLFSPDM_DYNAMIC_MEMORY + /* Dynamic path: allocate and initialize via wolfSPDM_New() */ + ctx->spdmCtx = wolfSPDM_New(); + if (ctx->spdmCtx == NULL) { + return MEMORY_E; + } +#else + /* Static path: use inline buffer, no malloc */ + ctx->spdmCtx = (WOLFSPDM_CTX*)ctx->spdmBuf; + rc = wolfSPDM_InitStatic(ctx->spdmCtx, (int)sizeof(ctx->spdmBuf)); + if (rc != WOLFSPDM_SUCCESS) { + ctx->spdmCtx = NULL; + return rc; + } +#endif + + /* Set I/O callback if provided */ + if (ioCb != NULL) { + rc = wolfSPDM_SetIO(ctx->spdmCtx, ioCb, userCtx); + if (rc != WOLFSPDM_SUCCESS) { + wolfSPDM_Free(ctx->spdmCtx); + ctx->spdmCtx = NULL; + return rc; + } + } + + return TPM_RC_SUCCESS; +} + +int wolfTPM2_SPDM_SetTPMCtx( + WOLFTPM2_SPDM_CTX* ctx, + TPM2_CTX* tpmCtx) +{ + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + ctx->tpmCtx = tpmCtx; + return TPM_RC_SUCCESS; +} + +void wolfTPM2_SPDM_FreeCtx(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL) { + return; + } + + if (ctx->spdmCtx != NULL) { + wolfSPDM_Free(ctx->spdmCtx); + ctx->spdmCtx = NULL; + } + + ctx->tpmCtx = NULL; + ctx->spdmOnlyLocked = 0; +} + +/* -------------------------------------------------------------------------- */ +/* Configuration */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_SetIoCb( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_IO_CB ioCb, + void* userCtx) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfSPDM_SetIO(ctx->spdmCtx, ioCb, userCtx); +} + +int wolfTPM2_SPDM_SetRequesterKeyPair( + WOLFTPM2_SPDM_CTX* ctx, + const byte* privKey, word32 privKeySz, + const byte* pubKey, word32 pubKeySz) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfSPDM_SetRequesterKeyPair(ctx->spdmCtx, + privKey, privKeySz, pubKey, pubKeySz); +} + +void wolfTPM2_SPDM_SetDebug(WOLFTPM2_SPDM_CTX* ctx, int enable) +{ + if (ctx != NULL && ctx->spdmCtx != NULL) { + wolfSPDM_SetDebug(ctx->spdmCtx, enable); + } +} + +/* -------------------------------------------------------------------------- */ +/* Session Establishment */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_Connect(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfSPDM_Connect(ctx->spdmCtx); +} + +int wolfTPM2_SPDM_IsConnected(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return 0; + } + return wolfSPDM_IsConnected(ctx->spdmCtx); +} + +word32 wolfTPM2_SPDM_GetSessionId(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return 0; + } + return wolfSPDM_GetSessionId(ctx->spdmCtx); +} + +int wolfTPM2_SPDM_Disconnect(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfSPDM_Disconnect(ctx->spdmCtx); +} + +/* -------------------------------------------------------------------------- */ +/* Secured Messaging */ +/* -------------------------------------------------------------------------- */ + +int wolfTPM2_SPDM_SecuredExchange( + WOLFTPM2_SPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSPDM_NUVOTON + /* In SPDM-only mode, TPM commands must be wrapped in SPDM VENDOR_DEFINED + * messages with the TPM2_CMD vendor code. The TPM's SPDM layer only + * accepts SPDM messages (starting with version byte 0x13), not raw TPM + * commands (starting with tag 0x80 0x01). */ + if (wolfSPDM_GetMode(ctx->spdmCtx) == WOLFSPDM_MODE_NUVOTON) { + byte vdMsg[WOLFSPDM_MAX_MSG_SIZE]; + byte vdRsp[WOLFSPDM_MAX_MSG_SIZE]; + word32 vdRspSz = sizeof(vdRsp); + char rspVdCode[WOLFSPDM_VDCODE_LEN + 1]; + int vdMsgSz; + int rc; + + /* Wrap TPM command in SPDM VENDOR_DEFINED_REQUEST("TPM2_CMD") */ + vdMsgSz = wolfSPDM_BuildVendorDefined(WOLFSPDM_VDCODE_TPM2_CMD, + cmdPlain, cmdSz, vdMsg, sizeof(vdMsg)); + if (vdMsgSz < 0) { + return vdMsgSz; + } + + /* Send encrypted VENDOR_DEFINED, receive encrypted response */ + rc = wolfSPDM_SecuredExchange(ctx->spdmCtx, + vdMsg, (word32)vdMsgSz, vdRsp, &vdRspSz); + if (rc != 0) { + return rc; + } + + /* Parse VENDOR_DEFINED_RESPONSE to extract TPM response */ + rc = wolfSPDM_ParseVendorDefined(vdRsp, vdRspSz, + rspVdCode, rspPlain, rspSz); + if (rc < 0) { + return rc; + } + + return TPM_RC_SUCCESS; + } +#endif /* WOLFSPDM_NUVOTON */ + + /* Standard SPDM mode: send TPM command as raw app data */ + return wolfSPDM_SecuredExchange(ctx->spdmCtx, + cmdPlain, cmdSz, rspPlain, rspSz); +} + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Functions */ +/* -------------------------------------------------------------------------- */ + +#ifdef WOLFSPDM_NUVOTON + +int wolfTPM2_SPDM_SetNuvotonMode(WOLFTPM2_SPDM_CTX* ctx) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfSPDM_SetMode(ctx->spdmCtx, WOLFSPDM_MODE_NUVOTON); +} + +int wolfTPM2_SPDM_GetStatus( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status) +{ + if (ctx == NULL || ctx->spdmCtx == NULL || status == NULL) { + return BAD_FUNC_ARG; + } + return wolfSPDM_Nuvoton_GetStatus(ctx->spdmCtx, status); +} + +int wolfTPM2_SPDM_GetPubKey( + WOLFTPM2_SPDM_CTX* ctx, + byte* pubKey, word32* pubKeySz) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfSPDM_Nuvoton_GetPubKey(ctx->spdmCtx, pubKey, pubKeySz); +} + +int wolfTPM2_SPDM_SetOnlyMode(WOLFTPM2_SPDM_CTX* ctx, int lock) +{ + int rc; + + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + + rc = wolfSPDM_Nuvoton_SetOnlyMode(ctx->spdmCtx, lock); + if (rc == WOLFSPDM_SUCCESS) { + ctx->spdmOnlyLocked = lock; + } + return rc; +} + +int wolfTPM2_SPDM_SetRequesterKeyTPMT( + WOLFTPM2_SPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz) +{ + if (ctx == NULL || ctx->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfSPDM_SetRequesterKeyTPMT(ctx->spdmCtx, tpmtPub, tpmtPubSz); +} + +/* Enable SPDM on Nuvoton TPM via NTC2_PreConfig vendor command. + * This requires platform hierarchy authorization and a TPM reset. */ +int wolfTPM2_SPDM_Enable(WOLFTPM2_SPDM_CTX* ctx) +{ + int rc; + WOLFTPM2_DEV dev; + NTC2_PreConfig_In preConfigIn; + NTC2_GetConfig_Out getConfigOut; + + if (ctx == NULL) { + return BAD_FUNC_ARG; + } + + if (ctx->tpmCtx == NULL) { + /* Need TPM context for NTC2 commands */ + return BAD_FUNC_ARG; + } + + /* Initialize wrapper device from TPM context */ + XMEMSET(&dev, 0, sizeof(dev)); + dev.ctx = *ctx->tpmCtx; + + /* Get current NTC2 configuration */ + XMEMSET(&getConfigOut, 0, sizeof(getConfigOut)); + rc = TPM2_NTC2_GetConfig(&getConfigOut); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_GetConfig failed: 0x%x\n", rc); + #endif + return rc; + } + + /* Check if SPDM is already enabled (bit 1 of Cfg_H, 0 = enabled) */ + if ((getConfigOut.preConfig.Cfg_H & NTC2_CFG_H_SPDM_DISABLE) == 0) { + #ifdef DEBUG_WOLFTPM + printf("SPDM already enabled on TPM\n"); + #endif + return TPM_RC_SUCCESS; + } + + /* Set SPDM capability bit (clear bit 1 to enable) */ + XMEMSET(&preConfigIn, 0, sizeof(preConfigIn)); + preConfigIn.preConfig = getConfigOut.preConfig; + preConfigIn.preConfig.Cfg_H &= ~NTC2_CFG_H_SPDM_DISABLE; + + /* Apply new configuration (requires platform auth) */ + rc = TPM2_NTC2_PreConfig(&preConfigIn); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("NTC2_PreConfig failed: 0x%x\n", rc); + #endif + return rc; + } + +#ifdef DEBUG_WOLFTPM + printf("SPDM enabled. TPM reset required for changes to take effect.\n"); +#endif + + return TPM_RC_SUCCESS; +} + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFTPM_SPDM */ diff --git a/src/tpm2_swtpm.c b/src/tpm2_swtpm.c index 75e5bd11..098ba883 100644 --- a/src/tpm2_swtpm.c +++ b/src/tpm2_swtpm.c @@ -52,8 +52,11 @@ #include #include #include -#ifdef HAVE_NETDB_H +#ifndef WOLFTPM_ZEPHYR +#include #include +#include +#include #endif #include diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 24880a59..bb833c06 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -25,6 +25,9 @@ #include #include +#ifdef WOLFTPM_SPDM +#include +#endif #ifndef WOLFTPM2_NO_WRAPPER @@ -48,89 +51,122 @@ static void wolfTPM2_CopyNvPublic(TPMS_NV_PUBLIC* out, const TPMS_NV_PUBLIC* in) /* --- BEGIN Wrapper Device Functions -- */ /******************************************************************************/ -static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, - int timeoutTries) -{ - int rc; - -#if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_WINAPI) - Startup_In startupIn; -#if defined(WOLFTPM_MICROCHIP) || defined(WOLFTPM_PERFORM_SELFTEST) - SelfTest_In selfTest; -#endif -#endif /* ! WOLFTPM_LINUX_DEV */ - - if (ctx == NULL) - return BAD_FUNC_ARG; - -#if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || \ - defined(WOLFTPM_WINAPI) - rc = TPM2_Init_minimal(ctx); - /* Using standard file I/O for the Linux TPM device */ - (void)ioCb; - (void)userCtx; - (void)timeoutTries; -#else - rc = TPM2_Init_ex(ctx, ioCb, userCtx, timeoutTries); -#endif - if (rc != TPM_RC_SUCCESS) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Init failed %d: %s\n", rc, wolfTPM2_GetRCString(rc)); - #endif - return rc; - } -#ifdef DEBUG_WOLFTPM - printf("TPM2: Caps 0x%08x, Did 0x%04x, Vid 0x%04x, Rid 0x%2x \n", - ctx->caps, - ctx->did_vid >> 16, - ctx->did_vid & 0xFFFF, - ctx->rid); -#endif - -#if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_WINAPI) - /* startup */ - XMEMSET(&startupIn, 0, sizeof(Startup_In)); - startupIn.startupType = TPM_SU_CLEAR; - rc = TPM2_Startup(&startupIn); - if (rc != TPM_RC_SUCCESS && - rc != TPM_RC_INITIALIZE /* TPM_RC_INITIALIZE = Already started */ && - rc != TPM_RC_UPGRADE /* TPM_RC_UPGRADE = In firmware upgrade mode */ ) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Startup failed %d: %s\n", rc, wolfTPM2_GetRCString(rc)); - #endif - return rc; - } - /* Return upgrade status so caller can handle appropriately */ - if (rc == TPM_RC_UPGRADE) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Startup: TPM is in firmware upgrade mode\n"); - #endif - return rc; - } -#ifdef DEBUG_WOLFTPM - printf("TPM2_Startup pass\n"); -#endif - rc = TPM_RC_SUCCESS; - -#if defined(WOLFTPM_MICROCHIP) || defined(WOLFTPM_PERFORM_SELFTEST) - /* Do full self-test (Chips such as ATTPM20 require this before some operations) */ - XMEMSET(&selfTest, 0, sizeof(selfTest)); - selfTest.fullTest = YES; - rc = TPM2_SelfTest(&selfTest); - if (rc != TPM_RC_SUCCESS) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_SelfTest failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - #endif - return rc; - } -#ifdef DEBUG_WOLFTPM - printf("TPM2_SelfTest pass\n"); -#endif -#endif /* WOLFTPM_MICROCHIP || WOLFTPM_PERFORM_SELFTEST */ -#endif /* !WOLFTPM_LINUX_DEV && !WOLFTPM_WINAPI */ - - return rc; -} +static int wolfTPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx, + int timeoutTries) + { + int rc; + + #if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_WINAPI) + Startup_In startupIn; + #if defined(WOLFTPM_MICROCHIP) || defined(WOLFTPM_PERFORM_SELFTEST) + SelfTest_In selfTest; + #endif + #endif /* ! WOLFTPM_LINUX_DEV */ + + if (ctx == NULL) + return BAD_FUNC_ARG; + + #if defined(WOLFTPM_LINUX_DEV) || defined(WOLFTPM_SWTPM) || \ + defined(WOLFTPM_WINAPI) + rc = TPM2_Init_minimal(ctx); + /* Using standard file I/O for the Linux TPM device */ + (void)ioCb; + (void)userCtx; + (void)timeoutTries; + #else + rc = TPM2_Init_ex(ctx, ioCb, userCtx, timeoutTries); + #endif + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Init failed %d: %s\n", rc, wolfTPM2_GetRCString(rc)); + #endif + return rc; + } + #ifdef DEBUG_WOLFTPM + printf("TPM2: Caps 0x%08x, Did 0x%04x, Vid 0x%04x, Rid 0x%2x \n", + ctx->caps, + ctx->did_vid >> 16, + ctx->did_vid & 0xFFFF, + ctx->rid); + #endif + + #if !defined(WOLFTPM_LINUX_DEV) && !defined(WOLFTPM_WINAPI) + /* startup */ + XMEMSET(&startupIn, 0, sizeof(Startup_In)); + startupIn.startupType = TPM_SU_CLEAR; + rc = TPM2_Startup(&startupIn); + if (rc != TPM_RC_SUCCESS && + rc != TPM_RC_INITIALIZE /* TPM_RC_INITIALIZE = Already started */ && + rc != TPM_RC_UPGRADE /* TPM_RC_UPGRADE = In firmware upgrade mode */ ) { + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* When SPDM-only mode is active on the TPM, TPM2_Startup returns + * TPM_RC_DISABLED. This is expected - SPDM commands bypass the + * normal TPM command path and work over raw SPI. */ + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup: TPM_RC_DISABLED (SPDM-only mode active, " + "this is expected)\n"); + #endif + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup failed %d: %s\n", rc, + wolfTPM2_GetRCString(rc)); + #endif + return rc; + } + } + /* Return upgrade status so caller can handle appropriately */ + if (rc == TPM_RC_UPGRADE) { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Startup: TPM is in firmware upgrade mode\n"); + #endif + return rc; + } + #ifdef DEBUG_WOLFTPM + if (rc == TPM_RC_SUCCESS || rc == TPM_RC_INITIALIZE) { + printf("TPM2_Startup pass\n"); + } + #endif + rc = TPM_RC_SUCCESS; + + #if defined(WOLFTPM_MICROCHIP) || defined(WOLFTPM_PERFORM_SELFTEST) + /* Do full self-test (Chips such as ATTPM20 require this before some operations) */ + XMEMSET(&selfTest, 0, sizeof(selfTest)); + selfTest.fullTest = YES; + rc = TPM2_SelfTest(&selfTest); + if (rc != TPM_RC_SUCCESS) { + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* SPDM-only mode active - SelfTest not needed */ + #ifdef DEBUG_WOLFTPM + printf("TPM2_SelfTest: TPM_RC_DISABLED (SPDM-only mode, " + "expected)\n"); + #endif + rc = TPM_RC_SUCCESS; + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_SelfTest failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + #endif + return rc; + } + } + #ifdef DEBUG_WOLFTPM + if (rc == TPM_RC_SUCCESS) { + printf("TPM2_SelfTest pass\n"); + } + #endif + #endif /* WOLFTPM_MICROCHIP || WOLFTPM_PERFORM_SELFTEST */ + #endif /* !WOLFTPM_LINUX_DEV && !WOLFTPM_WINAPI */ + + return rc; + } /* Single-shot API for testing access to hardware and optionally return capabilities */ int wolfTPM2_Test(TPM2HalIoCb ioCb, void* userCtx, WOLFTPM2_CAPS* caps) @@ -874,6 +910,199 @@ int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles) return handles->count; } +#ifdef WOLFTPM_SPDM +/* --- SPDM Secure Session Wrapper API --- + * + * These functions provide a high-level interface to wolfSPDM. + * All SPDM protocol logic is implemented in the wolfSPDM library. + */ + +int wolfTPM2_SpdmInit(WOLFTPM2_DEV* dev) +{ + int rc; + WOLFTPM2_SPDM_CTX* spdmCtx; + + if (dev == NULL) { + return BAD_FUNC_ARG; + } + + /* Allocate SPDM context */ + spdmCtx = (WOLFTPM2_SPDM_CTX*)XMALLOC(sizeof(WOLFTPM2_SPDM_CTX), + NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (spdmCtx == NULL) { + return MEMORY_E; + } + + /* Initialize SPDM context with wolfSPDM. + * I/O callback will be set later when Connect is called. */ + rc = wolfTPM2_SPDM_InitCtx(spdmCtx, NULL, NULL); + if (rc != 0) { + XFREE(spdmCtx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return rc; + } + + /* Set TPM context for NTC2 vendor commands */ + rc = wolfTPM2_SPDM_SetTPMCtx(spdmCtx, &dev->ctx); + if (rc != 0) { + wolfTPM2_SPDM_FreeCtx(spdmCtx); + XFREE(spdmCtx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return rc; + } + + /* Link SPDM context to device */ + dev->spdmCtx = spdmCtx; + dev->ctx.spdmCtx = spdmCtx; + +#ifdef DEBUG_WOLFTPM + printf("SPDM initialized with wolfSPDM backend\n"); +#endif + + return TPM_RC_SUCCESS; +} + +int wolfTPM2_SpdmConnect(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfTPM2_SPDM_Connect(dev->spdmCtx); +} + +int wolfTPM2_SpdmIsConnected(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL) { + return 0; + } + return wolfTPM2_SPDM_IsConnected(dev->spdmCtx); +} + +word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL) { + return 0; + } + return wolfTPM2_SPDM_GetSessionId(dev->spdmCtx); +} + +int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfTPM2_SPDM_Disconnect(dev->spdmCtx); +} + +int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev) +{ + if (dev == NULL) { + return BAD_FUNC_ARG; + } + if (dev->spdmCtx != NULL) { + wolfTPM2_SPDM_FreeCtx(dev->spdmCtx); + XFREE(dev->spdmCtx, NULL, DYNAMIC_TYPE_TMP_BUFFER); + dev->spdmCtx = NULL; + dev->ctx.spdmCtx = NULL; + } + return TPM_RC_SUCCESS; +} + +#ifdef WOLFSPDM_NUVOTON +/* Nuvoton-specific SPDM functions */ + +int wolfTPM2_SpdmSetNuvotonMode(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfTPM2_SPDM_SetNuvotonMode(dev->spdmCtx); +} + +int wolfTPM2_SpdmEnable(WOLFTPM2_DEV* dev) +{ + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfTPM2_SPDM_Enable(dev->spdmCtx); +} + +int wolfTPM2_SpdmGetStatus(WOLFTPM2_DEV* dev, WOLFSPDM_NUVOTON_STATUS* status) +{ + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfTPM2_SPDM_GetStatus(dev->spdmCtx, status); +} + +int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, byte* pubKey, word32* pubKeySz) +{ + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfTPM2_SPDM_GetPubKey(dev->spdmCtx, pubKey, pubKeySz); +} + +int wolfTPM2_SpdmSetOnlyMode(WOLFTPM2_DEV* dev, int lock) +{ + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + return wolfTPM2_SPDM_SetOnlyMode(dev->spdmCtx, lock); +} + +int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz) +{ + int rc; + + if (dev == NULL || dev->spdmCtx == NULL) { + return BAD_FUNC_ARG; + } + + /* Note: I/O callback must be set by caller before calling this function. + * For Nuvoton TPM, use wolfTPM2_SPDM_SetIoCb() with a callback that + * calls TPM2_SendRawBytes(). */ + + /* Set Nuvoton mode first */ + rc = wolfTPM2_SPDM_SetNuvotonMode(dev->spdmCtx); + if (rc != 0) { + return rc; + } + + /* Set requester key pair if provided (for mutual authentication) */ + if (reqPrivKey != NULL && reqPrivKeySz > 0 && + reqPubKey != NULL && reqPubKeySz > 0) { + /* Extract raw X||Y from TPMT_PUBLIC format (skip header, get unique) */ + /* TPMT_PUBLIC: type(2) + nameAlg(2) + attr(4) + authPolicy(2) + + * params(10) + unique.x.size(2) + x(48) + unique.y.size(2) + y(48) */ + if (reqPubKeySz >= 120) { + const byte* uniqueX = reqPubKey + 22; /* offset to unique.x data */ + const byte* uniqueY = reqPubKey + 72; /* offset to unique.y data */ + byte rawPubKey[96]; + XMEMCPY(rawPubKey, uniqueX, 48); + XMEMCPY(rawPubKey + 48, uniqueY, 48); + rc = wolfTPM2_SPDM_SetRequesterKeyPair(dev->spdmCtx, + reqPrivKey, reqPrivKeySz, rawPubKey, 96); + if (rc != 0) { + return rc; + } + /* Also store the full TPMT_PUBLIC for GIVE_PUB step */ + rc = wolfTPM2_SPDM_SetRequesterKeyTPMT(dev->spdmCtx, + reqPubKey, reqPubKeySz); + if (rc != 0) { + return rc; + } + } + } + + /* Perform the Nuvoton SPDM handshake */ + return wolfTPM2_SPDM_Connect(dev->spdmCtx); +} + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFTPM_SPDM */ + int wolfTPM2_UnsetAuth(WOLFTPM2_DEV* dev, int index) { TPM2_AUTH_SESSION* session; @@ -1204,10 +1433,23 @@ int wolfTPM2_Cleanup_ex(WOLFTPM2_DEV* dev, int doShutdown) shutdownIn.shutdownType = TPM_SU_CLEAR; rc = TPM2_Shutdown(&shutdownIn); if (rc != TPM_RC_SUCCESS) { - #ifdef DEBUG_WOLFTPM - printf("TPM2_Shutdown failed %d: %s\n", - rc, wolfTPM2_GetRCString(rc)); - #endif + #ifdef WOLFTPM_SPDM + if (rc == (int)TPM_RC_DISABLED) { + /* SPDM-only mode active - shutdown not needed */ + #ifdef DEBUG_WOLFTPM + printf("TPM2_Shutdown: TPM_RC_DISABLED (SPDM-only mode, " + "expected)\n"); + #endif + rc = TPM_RC_SUCCESS; /* Not an error in SPDM mode */ + } + else + #endif /* WOLFTPM_SPDM */ + { + #ifdef DEBUG_WOLFTPM + printf("TPM2_Shutdown failed %d: %s\n", + rc, wolfTPM2_GetRCString(rc)); + #endif + } /* finish cleanup and return error */ } } diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 3da2a165..4e1cbe22 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -891,6 +891,75 @@ static void test_wolfTPM2_thread_local_storage(void) #endif /* HAVE_THREAD_LS && HAVE_PTHREAD */ } +#ifdef WOLFTPM_SPDM +/* Test SPDM wrapper API functions */ +static void test_wolfTPM2_SPDM_Functions(void) +{ + int rc; + WOLFTPM2_DEV dev; + + printf("Test TPM Wrapper:\tSPDM Functions:\t"); + + /* Initialize device */ + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + if (rc != 0) { + printf("Failed (Init failed: 0x%x)\n", rc); + return; + } + + /* Test 1: Parameter validation - NULL args */ + rc = wolfTPM2_SpdmInit(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmConnect(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_SpdmIsConnected(NULL), 0); + AssertIntEQ(wolfTPM2_SpdmGetSessionId(NULL), 0); + rc = wolfTPM2_SpdmDisconnect(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmCleanup(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + + /* Test 2: Context lifecycle - init, check state, cleanup */ + rc = wolfTPM2_SpdmInit(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + /* Not yet connected */ + AssertIntEQ(wolfTPM2_SpdmIsConnected(&dev), 0); + AssertIntEQ(wolfTPM2_SpdmGetSessionId(&dev), 0); + /* Cleanup */ + rc = wolfTPM2_SpdmCleanup(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + /* Idempotent cleanup */ + rc = wolfTPM2_SpdmCleanup(&dev); + AssertIntEQ(rc, TPM_RC_SUCCESS); + +#ifdef WOLFSPDM_NUVOTON + /* Test 3: Nuvoton-specific parameter validation */ + rc = wolfTPM2_SpdmSetNuvotonMode(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmEnable(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + { + WOLFSPDM_NUVOTON_STATUS status; + byte pubKey[256]; + word32 pubKeySz = sizeof(pubKey); + + rc = wolfTPM2_SpdmGetStatus(NULL, &status); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmGetStatus(&dev, NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmGetPubKey(NULL, pubKey, &pubKeySz); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_SpdmSetOnlyMode(NULL, 0); + AssertIntEQ(rc, BAD_FUNC_ARG); + } +#endif /* WOLFSPDM_NUVOTON */ + + wolfTPM2_Cleanup(&dev); + + printf("Passed\n"); +} +#endif /* WOLFTPM_SPDM */ + /* Test creating key and exporting keyblob as buffer, * importing and loading key. */ static void test_wolfTPM2_KeyBlob(TPM_ALG_ID alg) @@ -1026,6 +1095,9 @@ int unit_tests(int argc, char *argv[]) #endif test_wolfTPM2_Cleanup(); test_wolfTPM2_thread_local_storage(); +#ifdef WOLFTPM_SPDM + test_wolfTPM2_SPDM_Functions(); +#endif #endif /* !WOLFTPM2_NO_WRAPPER */ return 0; diff --git a/wolftpm/include.am b/wolftpm/include.am index 630436cf..b7b75c93 100644 --- a/wolftpm/include.am +++ b/wolftpm/include.am @@ -15,5 +15,6 @@ nobase_include_HEADERS+= \ wolftpm/tpm2_socket.h \ wolftpm/tpm2_asn.h \ wolftpm/version.h \ + wolftpm/tpm2_spdm.h \ wolftpm/visibility.h \ wolftpm/options.h diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index dfd22115..ce4ae71c 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -262,7 +262,7 @@ typedef enum { TPM_CC_SetCommandSetLock = CC_VEND + 0x030B, TPM_CC_GPIO_Config = CC_VEND + 0x030F, #endif -#ifdef WOLFTPM_NUVOTON +#if defined(WOLFTPM_NUVOTON) || defined(WOLFTPM_AUTODETECT) TPM_CC_NTC2_PreConfig = CC_VEND + 0x0211, TPM_CC_NTC2_GetConfig = CC_VEND + 0x0213, #endif @@ -508,6 +508,128 @@ typedef enum { } TPM_CAP_T; typedef UINT32 TPM_CAP; +#ifdef WOLFTPM_SPDM +/* TCG SPDM Binding for Secure Communication v1.0 Constants */ + +/* TCG SPDM Binding Message Tags */ +#define SPDM_TAG_CLEAR 0x8101 /* Clear (unencrypted) SPDM message */ +#define SPDM_TAG_SECURED 0x8201 /* Secured (encrypted) SPDM message */ + +/* SPDM Protocol Version */ +#define SPDM_VERSION_1_3 0x13 /* SPDM v1.3 */ + +/* SPDM Message Request Codes (DMTF DSP0274) */ +#define SPDM_GET_VERSION 0x84 +#define SPDM_GET_CAPABILITIES 0xE1 +#define SPDM_NEGOTIATE_ALGORITHMS 0xE3 +#define SPDM_GET_DIGESTS 0x81 +#define SPDM_GET_CERTIFICATE 0x82 +#define SPDM_CHALLENGE 0x83 +#define SPDM_GET_MEASUREMENTS 0xE0 +#define SPDM_KEY_EXCHANGE 0xE4 +#define SPDM_FINISH 0xE5 +#define SPDM_PSK_EXCHANGE 0xE6 +#define SPDM_PSK_FINISH 0xE7 +#define SPDM_HEARTBEAT 0xE8 +#define SPDM_KEY_UPDATE 0xE9 +#define SPDM_END_SESSION 0xEC +#define SPDM_VENDOR_DEFINED_REQUEST 0xFE + +/* SPDM Message Response Codes */ +#define SPDM_VERSION_RESP 0x04 +#define SPDM_CAPABILITIES_RESP 0x61 +#define SPDM_ALGORITHMS_RESP 0x63 +#define SPDM_DIGESTS_RESP 0x01 +#define SPDM_CERTIFICATE_RESP 0x02 +#define SPDM_CHALLENGE_AUTH 0x03 +#define SPDM_MEASUREMENTS_RESP 0x60 +#define SPDM_KEY_EXCHANGE_RSP 0x64 +#define SPDM_FINISH_RSP 0x65 +#define SPDM_PSK_EXCHANGE_RSP 0x66 +#define SPDM_PSK_FINISH_RSP 0x67 +#define SPDM_HEARTBEAT_ACK 0x68 +#define SPDM_KEY_UPDATE_ACK 0x69 +#define SPDM_END_SESSION_ACK 0x6C +#define SPDM_VENDOR_DEFINED_RESP 0x7E +#define SPDM_ERROR 0x7F + +/* SPDM Error Codes (per DSP0274) */ +#define SPDM_ERR_INVALID_REQUEST 0x01 +#define SPDM_ERR_BUSY 0x03 +#define SPDM_ERR_UNEXPECTED_REQUEST 0x04 +#define SPDM_ERR_UNSPECIFIED 0x05 +#define SPDM_ERR_DECRYPT_ERROR 0x06 +#define SPDM_ERR_UNSUPPORTED_REQUEST 0x07 +#define SPDM_ERR_REQUEST_IN_FLIGHT 0x08 +#define SPDM_ERR_INVALID_RESPONSE_CODE 0x09 +#define SPDM_ERR_SESSION_LIMIT_EXCEEDED 0x0A +#define SPDM_ERR_SESSION_REQUIRED 0x0B +#define SPDM_ERR_RESET_REQUIRED 0x0C +#define SPDM_ERR_RESPONSE_TOO_LARGE 0x0D +#define SPDM_ERR_REQUEST_TOO_LARGE 0x0E +#define SPDM_ERR_LARGE_RESPONSE 0x0F +#define SPDM_ERR_MESSAGE_LOST 0x10 +#define SPDM_ERR_VENDOR_DEFINED 0xFF + +/* SPDM Vendor Defined Codes (8-byte ASCII, used as VdCode in VENDOR_DEFINED) */ +#define SPDM_VDCODE_TPM2_CMD "TPM2_CMD" /* TPM command over SPDM session */ +#define SPDM_VDCODE_GET_PUBK "GET_PUBK" /* Get TPM's SPDM-Identity pub key */ +#define SPDM_VDCODE_GIVE_PUB "GIVE_PUB" /* Give host's SPDM-Identity pub key */ +#define SPDM_VDCODE_SPDMONLY "SPDMONLY" /* Lock/unlock SPDM-only mode */ +#define SPDM_VDCODE_GET_STS "GET_STS_" /* Get SPDM status */ + +/* SPDM Vendor Defined Code Length */ +#define SPDM_VDCODE_LEN 8 + +/* SPDM Session Constants (Nuvoton NPCT7xx) */ +#define SPDM_CONNECTION_ID 0 /* Single connection */ +#define SPDM_RSP_SESSION_ID 0xAEAD /* Responder session ID */ +#define SPDM_REQ_SESSION_ID 0x0001 /* Default requester session ID */ + +/* SPDM FIPS Indicator (TCG binding) */ +#define SPDM_FIPS_NON_FIPS 0x00 +#define SPDM_FIPS_APPROVED 0x01 + +/* SPDM Algorithm Set B (192-bit security strength) */ +#define SPDM_ALG_ECDSA_P384 0x0003 /* Signing algorithm */ +#define SPDM_ALG_SHA384 0x0002 /* Hash algorithm */ +#define SPDM_ALG_ECDHE_P384 0x0003 /* Key exchange algorithm */ +#define SPDM_ALG_AES256_GCM 0x0002 /* AEAD algorithm */ + +/* SPDM-Identity NV Indices */ +#define SPDM_NV_INDEX_TPM_KEY 0x01C20110 /* TPM SPDM-Identity key */ +#define SPDM_NV_INDEX_REQ_KEY 0x01C20111 /* Requester SPDM-Identity key */ + +/* NTC2 PreConfig CFG_H Bit Definitions for SPDM */ +#define NTC2_CFG_H_SPDM_ENABLE_BIT 1 /* Bit position in CFG_H */ +#define NTC2_CFG_H_SPDM_ENABLE 0x00 /* SPDM enabled (bit 1 = 0) */ +#define NTC2_CFG_H_SPDM_DISABLE 0x02 /* SPDM disabled (bit 1 = 1) */ + +/* SPDM ONLY mode sub-commands */ +#define SPDM_ONLY_LOCK 0x01 +#define SPDM_ONLY_UNLOCK 0x00 + +/* SPDM Message Sizes */ +#define SPDM_MAX_MSG_SIZE 4096 +#define SPDM_AEAD_TAG_SIZE 16 /* AES-256-GCM tag size */ +#define SPDM_AEAD_IV_SIZE 12 /* AES-256-GCM IV size */ +#define SPDM_AEAD_KEY_SIZE 32 /* AES-256-GCM key size */ +#define SPDM_HASH_SIZE 48 /* SHA-384 digest size */ +#define SPDM_ECDSA_KEY_SIZE 48 /* P-384 coordinate size */ +#define SPDM_ECDSA_SIG_SIZE 96 /* P-384 signature (r+s) */ + +/* TCG SPDM Binding Header Size (per TCG SPDM Binding Spec): + * tag(2/BE) + size(4/BE) + connectionHandle(4/BE) + fipsIndicator(2/BE) + + * reserved(4) = 16 bytes */ +#define SPDM_TCG_BINDING_HEADER_SIZE 16 + +/* SPDM Secured Message Header Size (per DSP0277): + * sessionId(4/LE) + sequenceNumber(8/LE) + length(2/LE) = 14 bytes + * where length = size of encrypted data + MAC */ +#define SPDM_SECURED_MSG_HEADER_SIZE 14 + +#endif /* WOLFTPM_SPDM */ + /* Property Tag */ typedef enum { TPM_PT_NONE = 0x00000000, @@ -816,6 +938,9 @@ typedef TPM_HANDLE TPMI_RH_CLEAR; typedef TPM_HANDLE TPMI_RH_NV_AUTH; typedef TPM_HANDLE TPMI_RH_LOCKOUT; typedef TPM_HANDLE TPMI_RH_NV_INDEX; +#ifdef WOLFTPM_SPDM +typedef TPM_HANDLE TPMI_DH_AC; /* Authenticated Controller handle */ +#endif typedef TPM_ALG_ID TPMI_ALG_HASH; typedef TPM_ALG_ID TPMI_ALG_ASYM; @@ -1045,7 +1170,6 @@ typedef struct TPML_ACT_DATA { TPMS_ACT_DATA actData[MAX_ACT_DATA]; } TPML_ACT_DATA; - /* Capabilities Structures */ typedef union TPMU_CAPABILITIES { @@ -1896,6 +2020,9 @@ typedef struct TPM2_CTX { #ifdef WOLFTPM_LINUX_DEV int fd; #endif +#ifdef WOLFTPM_SPDM + void* spdmCtx; /* Pointer to WOLFTPM2_SPDM_CTX when session active */ +#endif } TPM2_CTX; @@ -1923,7 +2050,6 @@ typedef struct { WOLFTPM_API TPM_RC TPM2_GetCapability(GetCapability_In* in, GetCapability_Out* out); - typedef struct { TPMI_YES_NO fullTest; } SelfTest_In; @@ -2644,6 +2770,7 @@ typedef struct { } PolicyAuthValue_In; WOLFTPM_API TPM_RC TPM2_PolicyAuthValue(PolicyAuthValue_In* in); + typedef struct { TPMI_SH_POLICY policySession; } PolicyPassword_In; @@ -3136,6 +3263,44 @@ WOLFTPM_API int TPM2_ST33_FieldUpgradeCommand(TPM_CC cc, uint8_t* data, uint32_t WOLFTPM_API int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out); #endif /* Vendor GPIO Commands */ +/* NTC2 PreConfig/GetConfig for WOLFTPM_AUTODETECT (runtime vendor detection). + * When a specific vendor is not selected at compile time, the NTC2 types + * must still be available for SPDM enable via NTC2_PreConfig. */ +#if defined(WOLFTPM_AUTODETECT) && !defined(WOLFTPM_NUVOTON) && \ + !defined(WOLFTPM_ST33) + typedef struct { + BYTE Base0; + BYTE Base1; + BYTE GpioAltCfg; + BYTE GpioInitValue; + BYTE GpioPullUp; + BYTE GpioPushPull; + BYTE Cfg_A; + BYTE Cfg_B; + BYTE Cfg_C; + BYTE Cfg_D; + BYTE Cfg_E; + BYTE Cfg_F; + BYTE Cfg_G; + BYTE Cfg_H; + BYTE Cfg_I; + BYTE Cfg_J; + BYTE isValid; + BYTE isLocked; + } CFG_STRUCT; + + typedef struct { + TPMI_RH_PLATFORM authHandle; + CFG_STRUCT preConfig; + } NTC2_PreConfig_In; + WOLFTPM_API int TPM2_NTC2_PreConfig(NTC2_PreConfig_In* in); + + typedef struct { + CFG_STRUCT preConfig; + } NTC2_GetConfig_Out; + WOLFTPM_API int TPM2_NTC2_GetConfig(NTC2_GetConfig_Out* out); +#endif /* WOLFTPM_AUTODETECT && !WOLFTPM_NUVOTON && !WOLFTPM_ST33 */ + /* Non-standard API's */ @@ -3296,6 +3461,27 @@ WOLFTPM_API TPM_RC TPM2_ChipStartup(TPM2_CTX* ctx, int timeoutTries); */ WOLFTPM_API TPM_RC TPM2_SetHalIoCb(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx); +#ifdef WOLFTPM_SPDM +/*! + \ingroup TPM2_Proprietary + \brief Send raw bytes through the TIS/HAL transport and receive the response. + Used by the SPDM layer to send TCG-framed SPDM messages over the same + SPI FIFO as regular TPM commands. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: check the provided arguments + \return TPM_RC_FAILURE: communication failure + + \param ctx pointer to a TPM2 context + \param txBuf pointer to the transmit buffer (TCG SPDM framed message) + \param txSz size of the transmit buffer in bytes + \param rxBuf pointer to the receive buffer for the response + \param rxSz pointer to size; on input max buffer size, on output actual response size +*/ +WOLFTPM_API TPM_RC TPM2_SendRawBytes(TPM2_CTX* ctx, + const byte* txBuf, word32 txSz, byte* rxBuf, word32* rxSz); +#endif /* WOLFTPM_SPDM */ + /*! \ingroup TPM2_Proprietary \brief Sets the structure holding the TPM Authorizations. diff --git a/wolftpm/tpm2_packet.h b/wolftpm/tpm2_packet.h index c79894b1..cb9db922 100644 --- a/wolftpm/tpm2_packet.h +++ b/wolftpm/tpm2_packet.h @@ -180,6 +180,7 @@ WOLFTPM_LOCAL void TPM2_Packet_AppendSignature(TPM2_Packet* packet, TPMT_SIGNATU WOLFTPM_LOCAL void TPM2_Packet_ParseSignature(TPM2_Packet* packet, TPMT_SIGNATURE* sig); WOLFTPM_LOCAL void TPM2_Packet_ParseAttest(TPM2_Packet* packet, TPMS_ATTEST* out); + WOLFTPM_LOCAL TPM_RC TPM2_Packet_Parse(TPM_RC rc, TPM2_Packet* packet); WOLFTPM_LOCAL int TPM2_Packet_Finalize(TPM2_Packet* packet, TPM_ST tag, TPM_CC cc); diff --git a/wolftpm/tpm2_spdm.h b/wolftpm/tpm2_spdm.h new file mode 100644 index 00000000..cfcd228a --- /dev/null +++ b/wolftpm/tpm2_spdm.h @@ -0,0 +1,340 @@ +/* tpm2_spdm.h + * + * Copyright (C) 2006-2025 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* SPDM Secure Session Support for wolfTPM + * + * Implements SPDM (Security Protocol and Data Model) secure communication + * between host and TPM using the wolfSPDM library for all protocol operations. + * + * References: + * - DMTF DSP0274 (SPDM v1.2/1.3) + * - TCG SPDM Binding for Secure Communication v1.0 + * - TCG TPM 2.0 Library Specification v1.84 + * + * Architecture: + * Application -> wolfTPM2 Wrapper -> SPDM Transport (this module) -> SPI HAL + * | + * wolfSPDM library + * (all SPDM protocol logic) + * + * wolfTPM provides: + * - Thin wrapper APIs that call wolfSPDM functions + * - TPM-specific SPDM enable via NTC2 vendor commands + * - I/O callback adapter to route wolfSPDM through TPM transport + * + * wolfSPDM provides: + * - Full SPDM protocol implementation (handshake, key derivation, encryption) + * - Standard and Nuvoton mode support + * - TCG binding message framing (for Nuvoton TPMs) + * - All cryptographic operations + */ + +#ifndef __TPM2_SPDM_H__ +#define __TPM2_SPDM_H__ + +#include + +#ifdef WOLFTPM_SPDM + +/* wolfSPDM library provides all SPDM protocol implementation */ +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* Forward declarations */ +struct WOLFTPM2_SPDM_CTX; + +/* -------------------------------------------------------------------------- */ +/* SPDM Context + * + * This is a thin wrapper around WOLFSPDM_CTX. wolfSPDM handles all the + * SPDM protocol state, key derivation, and encryption. This context adds + * only TPM-specific fields needed for integration with wolfTPM2. + * -------------------------------------------------------------------------- */ + +typedef struct WOLFTPM2_SPDM_CTX { + /* wolfSPDM context - handles all SPDM protocol operations */ + WOLFSPDM_CTX* spdmCtx; + + /* Reference to TPM context for NTC2 vendor commands */ + TPM2_CTX* tpmCtx; + + /* SPDM-only mode tracking (for Nuvoton TPMs) */ + int spdmOnlyLocked; + +#ifndef WOLFSPDM_DYNAMIC_MEMORY + /* Inline buffer for static wolfSPDM context (zero-malloc mode) */ + byte spdmBuf[WOLFSPDM_CTX_STATIC_SIZE]; +#endif +} WOLFTPM2_SPDM_CTX; + +/* -------------------------------------------------------------------------- */ +/* SPDM Core API Functions + * + * These are thin wrappers around wolfSPDM functions. All protocol logic + * is implemented in wolfSPDM. + * -------------------------------------------------------------------------- */ + +/** + * Initialize SPDM context with wolfSPDM. + * Must be called before any other SPDM function. + * + * @param ctx wolfTPM2 SPDM context + * @param ioCb I/O callback for sending/receiving SPDM messages + * @param userCtx User context passed to the I/O callback + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_InitCtx( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_IO_CB ioCb, + void* userCtx +); + +/** + * Set the TPM context for NTC2 vendor commands. + * Only needed for Nuvoton TPMs when using wolfTPM2_SPDM_Enable(). + * + * @param ctx wolfTPM2 SPDM context + * @param tpmCtx TPM2 context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SetTPMCtx( + WOLFTPM2_SPDM_CTX* ctx, + TPM2_CTX* tpmCtx +); + +/** + * Enable SPDM on the TPM via NTC2_PreConfig. + * Requires platform hierarchy authorization. + * TPM must be reset after this for SPDM to take effect. + * NOTE: This is a Nuvoton-specific feature. + * + * @param ctx wolfTPM2 SPDM context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_Enable( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Establish an SPDM secure session using standard message flow. + * Uses: GET_VERSION -> GET_CAPABILITIES -> NEGOTIATE_ALGORITHMS -> + * GET_DIGESTS -> GET_CERTIFICATE (optional) -> KEY_EXCHANGE -> FINISH + * + * For use with libspdm emulator or standard SPDM responders. + * + * @param ctx wolfTPM2 SPDM context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_Connect( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Check if an SPDM session is currently active. + * + * @param ctx wolfTPM2 SPDM context + * @return 1 if connected, 0 if not + */ +WOLFTPM_API int wolfTPM2_SPDM_IsConnected( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Get the current session ID. + * + * @param ctx wolfTPM2 SPDM context + * @return Session ID, or 0 if not connected + */ +WOLFTPM_API word32 wolfTPM2_SPDM_GetSessionId( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Perform a secured message exchange (encrypt, send, receive, decrypt). + * Wraps wolfSPDM_SecuredExchange() for TPM command/response. + * + * @param ctx wolfTPM2 SPDM context + * @param cmdPlain Plaintext command to send + * @param cmdSz Size of command + * @param rspPlain Buffer for plaintext response + * @param rspSz [in] Size of response buffer, [out] Actual response size + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SecuredExchange( + WOLFTPM2_SPDM_CTX* ctx, + const byte* cmdPlain, word32 cmdSz, + byte* rspPlain, word32* rspSz +); + +/** + * Disconnect the SPDM session (END_SESSION). + * After this call, TPM commands will be sent in the clear. + * + * @param ctx wolfTPM2 SPDM context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_Disconnect( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Free all SPDM context resources. + * Safe to call on an already-cleaned-up or zero-initialized context. + * + * @param ctx wolfTPM2 SPDM context + */ +WOLFTPM_API void wolfTPM2_SPDM_FreeCtx( + WOLFTPM2_SPDM_CTX* ctx +); + +/* -------------------------------------------------------------------------- */ +/* Nuvoton-Specific Functions (requires wolfSPDM with --enable-nuvoton) + * -------------------------------------------------------------------------- */ + +#ifdef WOLFSPDM_NUVOTON + +/** + * Set Nuvoton mode and configure for Nuvoton TPM handshake. + * Must be called before wolfTPM2_SPDM_Connect() for Nuvoton TPMs. + * + * @param ctx wolfTPM2 SPDM context + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SetNuvotonMode( + WOLFTPM2_SPDM_CTX* ctx +); + +/** + * Get SPDM status from the TPM (GET_STS_ vendor command). + * Wraps wolfSPDM_Nuvoton_GetStatus(). + * + * @param ctx wolfTPM2 SPDM context + * @param status Receives SPDM status information + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_GetStatus( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_NUVOTON_STATUS* status +); + +/** + * Get the TPM's SPDM-Identity public key (GET_PUBK vendor command). + * Wraps wolfSPDM_Nuvoton_GetPubKey(). + * + * @param ctx wolfTPM2 SPDM context + * @param pubKey Output buffer for public key (raw X||Y, 96 bytes for P-384) + * @param pubKeySz [in] Size of buffer, [out] Actual key size + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_GetPubKey( + WOLFTPM2_SPDM_CTX* ctx, + byte* pubKey, word32* pubKeySz +); + +/** + * Lock or unlock SPDM-only mode (SPDMONLY vendor command). + * When locked, the TPM only accepts commands over SPDM. + * Wraps wolfSPDM_Nuvoton_SetOnlyMode(). + * + * @param ctx wolfTPM2 SPDM context + * @param lock WOLFSPDM_SPDMONLY_LOCK (1) or WOLFSPDM_SPDMONLY_UNLOCK (0) + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SetOnlyMode( + WOLFTPM2_SPDM_CTX* ctx, + int lock +); + +/** + * Set the requester's SPDM-Identity public key in TPMT_PUBLIC format. + * Required for GIVE_PUB step in Nuvoton handshake. + * Wraps wolfSPDM_SetRequesterKeyTPMT(). + * + * @param ctx wolfTPM2 SPDM context + * @param tpmtPub Public key in TPMT_PUBLIC format (~120 bytes for P-384) + * @param tpmtPubSz Size of TPMT_PUBLIC + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SetRequesterKeyTPMT( + WOLFTPM2_SPDM_CTX* ctx, + const byte* tpmtPub, word32 tpmtPubSz +); + +#endif /* WOLFSPDM_NUVOTON */ + +/* -------------------------------------------------------------------------- */ +/* Configuration Helpers + * -------------------------------------------------------------------------- */ + +/** + * Set the I/O callback and user context on an existing SPDM context. + * Wraps wolfSPDM_SetIO(). + * + * @param ctx wolfTPM2 SPDM context + * @param ioCb I/O callback function + * @param userCtx User context passed to callback + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SetIoCb( + WOLFTPM2_SPDM_CTX* ctx, + WOLFSPDM_IO_CB ioCb, + void* userCtx +); + +/** + * Set the requester's key pair for mutual authentication. + * Wraps wolfSPDM_SetRequesterKeyPair(). + * + * @param ctx wolfTPM2 SPDM context + * @param privKey Raw private key bytes (48 bytes for P-384) + * @param privKeySz Size of private key + * @param pubKey Raw public key bytes (96 bytes for P-384: X||Y) + * @param pubKeySz Size of public key + * @return 0 on success, negative on error + */ +WOLFTPM_API int wolfTPM2_SPDM_SetRequesterKeyPair( + WOLFTPM2_SPDM_CTX* ctx, + const byte* privKey, word32 privKeySz, + const byte* pubKey, word32 pubKeySz +); + +/** + * Enable or disable debug output. + * Wraps wolfSPDM_SetDebug(). + * + * @param ctx wolfTPM2 SPDM context + * @param enable Non-zero to enable, 0 to disable + */ +WOLFTPM_API void wolfTPM2_SPDM_SetDebug( + WOLFTPM2_SPDM_CTX* ctx, + int enable +); + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif /* WOLFTPM_SPDM */ + +#endif /* __TPM2_SPDM_H__ */ diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 21c80968..ae4f6878 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -23,6 +23,9 @@ #define __TPM2_WRAP_H__ #include +#ifdef WOLFTPM_SPDM +#include +#endif #ifdef __cplusplus extern "C" { @@ -57,6 +60,9 @@ typedef struct WOLFTPM2_SESSION { typedef struct WOLFTPM2_DEV { TPM2_CTX ctx; TPM2_AUTH_SESSION session[MAX_SESSION_NUM]; +#ifdef WOLFTPM_SPDM + struct WOLFTPM2_SPDM_CTX* spdmCtx; /* NULL = no SPDM, non-NULL = active */ +#endif } WOLFTPM2_DEV; /* Public Key with Handle. @@ -145,6 +151,10 @@ typedef struct WOLFTPM2_CAPS { word16 fips140_2 : 1; /* using FIPS mode */ word16 cc_eal4 : 1; /* Common Criteria EAL4+ */ word16 req_wait_state : 1; /* requires SPI wait state */ +#ifdef WOLFTPM_SPDM + word32 acHandleCount; /* Number of AC handles discovered */ + TPM_HANDLE acHandles[16]; /* AC handles (max 16) */ +#endif } WOLFTPM2_CAPS; @@ -394,6 +404,167 @@ WOLFTPM_API int wolfTPM2_GetCapabilities(WOLFTPM2_DEV* dev, WOLFTPM2_CAPS* caps) */ WOLFTPM_API int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles); +#ifdef WOLFTPM_SPDM +/* SPDM Secure Session Wrapper API + * + * These functions provide a high-level interface for SPDM secure sessions. + * All SPDM protocol logic is implemented in the wolfSPDM library. + */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Initialize SPDM support on a wolfTPM2 device. + Allocates and configures the SPDM context using wolfSPDM. + After init, call wolfTPM2_SpdmConnect to establish a secure session. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + \return MEMORY_E: memory allocation failed + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmInit(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish an SPDM secure session (full handshake). + Uses standard SPDM flow: GET_VERSION -> GET_CAPABILITIES -> + NEGOTIATE_ALGORITHMS -> KEY_EXCHANGE -> FINISH. + + \return TPM_RC_SUCCESS: session established + \return TPM_RC_FAILURE: handshake failed + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmConnect(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Check if an SPDM secure session is currently active. + + \return 1 if connected, 0 if not + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmIsConnected(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get the current SPDM session ID. + + \return Session ID, or 0 if not connected + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Disconnect the SPDM secure session. + After this, TPM commands are sent in the clear. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Free SPDM context and resources. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmCleanup(WOLFTPM2_DEV* dev); + +#ifdef WOLFSPDM_NUVOTON +/* Nuvoton-specific SPDM functions (requires wolfSPDM with --enable-nuvoton) */ + +/*! + \ingroup wolfTPM2_Wrappers + \brief Configure for Nuvoton TPM SPDM mode. + Must be called before wolfTPM2_SpdmConnect() for Nuvoton TPMs. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmSetNuvotonMode(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Enable SPDM on the TPM via NTC2_PreConfig vendor command. + Requires platform hierarchy auth. TPM must be reset after this call. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure +*/ +WOLFTPM_API int wolfTPM2_SpdmEnable(WOLFTPM2_DEV* dev); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Establish Nuvoton SPDM secure session with mutual authentication. + Uses Nuvoton flow: GET_VERSION -> GET_PUB_KEY -> KEY_EXCHANGE -> + GIVE_PUB_KEY -> FINISH. + + \return TPM_RC_SUCCESS: session established + \return TPM_RC_FAILURE: handshake failed + + \param dev pointer to a WOLFTPM2_DEV structure + \param reqPubKey host's ECDSA P-384 public key (TPMT_PUBLIC format) + \param reqPubKeySz size of reqPubKey in bytes + \param reqPrivKey host's ECDSA P-384 private key (raw 48 bytes) + \param reqPrivKeySz size of reqPrivKey in bytes +*/ +WOLFTPM_API int wolfTPM2_SpdmConnectNuvoton(WOLFTPM2_DEV* dev, + const byte* reqPubKey, word32 reqPubKeySz, + const byte* reqPrivKey, word32 reqPrivKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get SPDM status from the TPM (GET_STS_ vendor command). + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param status output: SPDM status information +*/ +WOLFTPM_API int wolfTPM2_SpdmGetStatus(WOLFTPM2_DEV* dev, + WOLFSPDM_NUVOTON_STATUS* status); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Get the TPM's SPDM-Identity public key. + + \return TPM_RC_SUCCESS: successful + \return BAD_FUNC_ARG: invalid parameters + + \param dev pointer to a WOLFTPM2_DEV structure + \param pubKey output buffer for the public key + \param pubKeySz in/out: buffer size / actual key size +*/ +WOLFTPM_API int wolfTPM2_SpdmGetPubKey(WOLFTPM2_DEV* dev, + byte* pubKey, word32* pubKeySz); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Lock or unlock SPDM-only mode. + When locked, TPM only accepts commands over SPDM secure channel. + + \return TPM_RC_SUCCESS: successful + + \param dev pointer to a WOLFTPM2_DEV structure + \param lock 1 to lock SPDM-only mode, 0 to unlock +*/ +WOLFTPM_API int wolfTPM2_SpdmSetOnlyMode(WOLFTPM2_DEV* dev, int lock); + +#endif /* WOLFSPDM_NUVOTON */ + +#endif /* WOLFTPM_SPDM */ /*! \ingroup wolfTPM2_Wrappers diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 41059918..0c3f79d8 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -6,6 +6,10 @@ if(CONFIG_WOLFTPM) ${ZEPHYR_CURRENT_MODULE_DIR}/src/*.c ${ZEPHYR_CURRENT_MODULE_DIR}/hal/*.c ) + # Exclude transport backends not applicable to Zephyr + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_linux\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_winapi\\.c$") + list(FILTER wolftpm_sources EXCLUDE REGEX "tpm2_spdm\\.c$") target_sources(app PRIVATE ${wolftpm_sources}) if(CONFIG_WOLFTPM_DEBUG)