From 833540418bffab282c61921980163db26c655794 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 14 Oct 2026 11:34:07 -0700 Subject: [PATCH] Add Infineon SLB9678 SPDM TCG identity mode --- .github/workflows/build-test.yml | 4 +- Makefile.am | 4 + README.md | 4 +- config.h.in | 3 + configure.ac | 13 +- docs/API-Reference.md | 15 +- docs/Configuration-and-Macros.md | 3 +- docs/Getting-Started.md | 2 +- src/spdm_context.c | 24 ++- src/spdm_internal.h | 3 +- src/spdm_msg.c | 22 ++- src/vendor/spdm_infineon.c | 112 ++++++++++++++ test/unit_test.c | 252 +++++++++++++++++++++++++++++++ wolfspdm/spdm.h | 12 +- wolfspdm/spdm_infineon.h | 70 +++++++++ wolfspdm/spdm_types.h | 9 +- 16 files changed, 534 insertions(+), 18 deletions(-) create mode 100644 src/vendor/spdm_infineon.c create mode 100644 wolfspdm/spdm_infineon.h diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f22b508..2e28fcb 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -79,7 +79,7 @@ jobs: env: # Standalone builds carry none of the TPM side, pure TCG builds none of # the standard requester - TPM_SYMS: 'wolfSPDM_(TCG_.*|Nuvoton_.*|Nations_.*|Resp[A-Z].*|.*Psk.*|SetPSK|ConnectTCG|.*TcgClear.*|.*VendorDefined|SetRequesterKey.*|SignHash|GetConnectionHandle|GetFipsIndicator)' + TPM_SYMS: 'wolfSPDM_(TCG_.*|Nuvoton_.*|Nations_.*|Infineon_.*|Resp[A-Z].*|.*Psk.*|SetPSK|ConnectTCG|.*TcgClear.*|.*VendorDefined|SetRequesterKey.*|SignHash|GetConnectionHandle|GetFipsIndicator)' STD_SYMS: 'wolfSPDM_(GetCapabilities|BuildGetCapabilities|ParseCapabilities|NegotiateAlgorithms|BuildNegotiateAlgorithms|ParseAlgorithms|GetDigests|ParseDigests|GetCertificate|ParseCertificate|ValidateCertChain|SetTrustedCAs|AllowUntrustedCerts|ConnectStandard|.*Measurement.*|.*Challenge.*|M1Start|M1Add|AttestFree|ChunkExchange|ClearExchange|SendData|ReceiveData|EncryptMessage|DecryptMessage|.*MlDsa.*|.*MlKem.*|SetKeyExchangePref)' strategy: fail-fast: false @@ -96,6 +96,8 @@ jobs: configure: '--enable-nuvoton' - variant: nations configure: '--enable-nations' + - variant: infineon + configure: '--enable-infineon' - variant: tcg-responder configure: '--enable-tcg --enable-responder' - variant: tcg-pure diff --git a/Makefile.am b/Makefile.am index 386349c..dc6b8c5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,6 +27,9 @@ endif if BUILD_NATIONS libwolfspdm_la_SOURCES += src/vendor/spdm_nations.c endif +if BUILD_INFINEON +libwolfspdm_la_SOURCES += src/vendor/spdm_infineon.c +endif if BUILD_RESPONDER libwolfspdm_la_SOURCES += src/spdm_responder.c endif @@ -54,6 +57,7 @@ nobase_include_HEADERS = \ wolfspdm/spdm_psk.h \ wolfspdm/spdm_nuvoton.h \ wolfspdm/spdm_nations.h \ + wolfspdm/spdm_infineon.h \ wolfspdm/spdm_responder.h \ wolfspdm/options.h diff --git a/README.md b/README.md index fd20cb9..3ea4f8f 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ make make ``` -Add `--disable-mctp` for a TPM-only build that drops the standard DMTF requester entirely, and `--enable-nuvoton` / `--enable-nations` for vendor TPM commands. Each piece is independent, so you can compose exactly what you want: `--enable-tcg` alone gives just the TCG binding (PSK and the responder stay off until you enable them). +Add `--disable-mctp` for a TPM-only build that drops the standard DMTF requester entirely, and `--enable-nuvoton` / `--enable-nations` / `--enable-infineon` for vendor TPM commands. Each piece is independent, so you can compose exactly what you want: `--enable-tcg` alone gives just the TCG binding (PSK and the responder stay off until you enable them). ## Main Features @@ -89,7 +89,7 @@ make check | `--disable-meas` / `--disable-challenge` | Compile out GET_MEASUREMENTS / CHALLENGE (default: enabled) | | `--disable-heartbeat` / `--disable-key-update` | Compile out HEARTBEAT / KEY_UPDATE (default: enabled) | | `--disable-app-data` | Compile out `SendData`/`ReceiveData` MCTP application messages and `Encrypt`/`DecryptMessage` (default: enabled) | -| `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-psk` / `--enable-responder` | TPM side: TCG SPDM binding, vendor commands, PSK and the responder (default: all disabled, so a standalone build carries none of it) | +| `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-infineon` / `--enable-psk` / `--enable-responder` | TPM side: TCG SPDM binding, vendor commands, PSK and the responder (default: all disabled, so a standalone build carries none of it) | | `--disable-mctp` | Pure TCG build: drops MCTP secured messages and the whole standard requester (needs `--enable-tcg` or a vendor) | | `--with-wolfssl=PATH` | wolfSSL installation path | | `CFLAGS=-DWOLFSPDM_DATA_TRANSFER_SIZE=N` | Largest single SPDM message, 42 to 4096 (default 4096). Smaller values shrink the per-message transport buffers; larger messages then travel in CHUNK_SEND/CHUNK_GET pieces when the responder supports chunking | diff --git a/config.h.in b/config.h.in index 80db462..23322f1 100644 --- a/config.h.in +++ b/config.h.in @@ -71,6 +71,9 @@ /* Enable dynamic memory allocation */ #undef WOLFSPDM_DYNAMIC_MEMORY +/* Enable Infineon SPDM vendor commands */ +#undef WOLFSPDM_INFINEON + /* Enable Nations SPDM vendor commands */ #undef WOLFSPDM_NATIONS diff --git a/configure.ac b/configure.ac index 656f261..8825cfd 100644 --- a/configure.ac +++ b/configure.ac @@ -258,12 +258,18 @@ AC_ARG_ENABLE([nations], [enable_nations=$enableval], [enable_nations=no]) +AC_ARG_ENABLE([infineon], + [AS_HELP_STRING([--enable-infineon], [Enable Infineon OPTIGA SLB9678 SPDM vendor commands (default: disabled)])], + [enable_infineon=$enableval], + [enable_infineon=no]) + AC_ARG_ENABLE([responder], [AS_HELP_STRING([--enable-responder], [Enable the SPDM responder (default: disabled)])], [enable_responder=$enableval], [enable_responder=no]) -if test "x$enable_nuvoton" = "xyes" || test "x$enable_nations" = "xyes"; then +if test "x$enable_nuvoton" = "xyes" || test "x$enable_nations" = "xyes" \ + || test "x$enable_infineon" = "xyes"; then enable_tcg=yes fi if test "x$enable_nations" = "xyes"; then @@ -294,6 +300,9 @@ fi if test "x$enable_nations" = "xyes"; then AC_DEFINE([WOLFSPDM_NATIONS], [1], [Enable Nations SPDM vendor commands]) fi +if test "x$enable_infineon" = "xyes"; then + AC_DEFINE([WOLFSPDM_INFINEON], [1], [Enable Infineon SPDM vendor commands]) +fi if test "x$enable_responder" = "xyes"; then AC_DEFINE([WOLFSPDM_RESPONDER], [1], [Enable the SPDM responder]) fi @@ -302,6 +311,7 @@ AM_CONDITIONAL([BUILD_TCG], [test "x$enable_tcg" = "xyes"]) AM_CONDITIONAL([BUILD_PSK], [test "x$enable_psk" = "xyes"]) AM_CONDITIONAL([BUILD_NUVOTON], [test "x$enable_nuvoton" = "xyes"]) AM_CONDITIONAL([BUILD_NATIONS], [test "x$enable_nations" = "xyes"]) +AM_CONDITIONAL([BUILD_INFINEON], [test "x$enable_infineon" = "xyes"]) AM_CONDITIONAL([BUILD_RESPONDER], [test "x$enable_responder" = "xyes"]) # Output files @@ -334,6 +344,7 @@ echo " TCG: $enable_tcg" echo " PSK: $enable_psk" echo " Nuvoton: $enable_nuvoton" echo " Nations: $enable_nations" +echo " Infineon: $enable_infineon" echo " Responder: $enable_responder" echo " wolfSSL: ${WOLFSSL_DIR:-system}" echo "" diff --git a/docs/API-Reference.md b/docs/API-Reference.md index e72dfcb..eff3dfe 100644 --- a/docs/API-Reference.md +++ b/docs/API-Reference.md @@ -1,8 +1,8 @@ # API Reference Public APIs are declared in `wolfspdm/spdm.h`, plus `wolfspdm/spdm_tcg.h`, -`spdm_nuvoton.h`, `spdm_nations.h`, `spdm_psk.h`, and `spdm_responder.h` for -the TPM/TCG side. +`spdm_nuvoton.h`, `spdm_nations.h`, `spdm_infineon.h`, `spdm_psk.h`, and +`spdm_responder.h` for the TPM/TCG side. All APIs return `WOLFSPDM_SUCCESS` (`0`) on success unless documented otherwise; failures are negative error codes from `wolfspdm/spdm_error.h`. @@ -17,8 +17,8 @@ otherwise; failures are negative error codes from `wolfspdm/spdm_error.h`. ## Configuration -- `wolfSPDM_SetIO` -- `wolfSPDM_SetMode` / `wolfSPDM_GetMode` *(`WOLFSPDM_MODE_AUTO` / `_NUVOTON` / `_NATIONS` / `_NATIONS_PSK`)* +- `wolfSPDM_SetIO` / `wolfSPDM_HasIO` +- `wolfSPDM_SetMode` / `wolfSPDM_GetMode` *(`WOLFSPDM_MODE_AUTO` / `_NUVOTON` / `_NATIONS` / `_NATIONS_PSK`, alias `_PSK` / `_TCG`)*. `_TCG` is the TCG binding with no vendor commands; `_AUTO` picks it when no vendor adapter is compiled in. - `wolfSPDM_SetResponderPubKey`: pin the responder key (96-byte P-384 X‖Y) for cert-less operation - `wolfSPDM_SetRequesterKeyPair` *(`WOLFSPDM_MUTUAL_AUTH` builds, TCG or TPM profile)* - `wolfSPDM_SetMaxVersion`: cap the negotiated version (0x12-0x14) @@ -88,6 +88,13 @@ otherwise; failures are negative error codes from `wolfspdm/spdm_error.h`. - `wolfSPDM_Nations_SetOnlyMode` - `wolfSPDM_Nations_PskSet` / `wolfSPDM_Nations_PskClear` / `wolfSPDM_Nations_PskClearWithVCA` +## Infineon (`wolfspdm/spdm_infineon.h`, `WOLFSPDM_INFINEON`) + +- `wolfSPDM_Infineon_GetStatus` +- `wolfSPDM_Infineon_SetOnlyMode` + +The SLB9678 identity flow itself is plain TCG, so it needs no adapter: set `WOLFSPDM_MODE_TCG` and connect. The calls above are the vendor commands on top of it. + ## PSK (`wolfspdm/spdm_psk.h`, `WOLFSPDM_PSK`) - `wolfSPDM_SetPSK` diff --git a/docs/Configuration-and-Macros.md b/docs/Configuration-and-Macros.md index adcaf16..fe547b0 100644 --- a/docs/Configuration-and-Macros.md +++ b/docs/Configuration-and-Macros.md @@ -22,6 +22,7 @@ From `configure.ac`: | `--enable-tcg` | off | `WOLFSPDM_TCG` | TCG SPDM binding (TPM transport) | | `--enable-nuvoton` | off | `WOLFSPDM_NUVOTON` | Nuvoton NPCT75x vendor commands (implies `--enable-tcg`) | | `--enable-nations` | off | `WOLFSPDM_NATIONS` | Nations NS350 vendor commands (implies `--enable-tcg` and `--enable-psk`) | +| `--enable-infineon` | off | `WOLFSPDM_INFINEON` | Infineon OPTIGA SLB9678 vendor commands (implies `--enable-tcg`; PSK stays opt-in) | | `--enable-psk` | off | `WOLFSPDM_PSK` | SPDM PSK_EXCHANGE/PSK_FINISH (requires `--enable-tcg`) | | `--enable-responder` | off | `WOLFSPDM_RESPONDER` | SPDM responder (requires `--enable-tcg`) | @@ -52,7 +53,7 @@ matching `WOLFSPDM_*` macro. `WOLFSPDM_PROFILE_TPM` implies: above) and `WOLFSPDM_NO_APP_DATA`. - `WOLFSPDM_PROFILE_TPM` implies `WOLFSPDM_NO_CERT`, `WOLFSPDM_NO_HEARTBEAT`, `WOLFSPDM_NO_KEY_UPDATE`, `WOLFSPDM_NO_APP_DATA`. -- `WOLFSPDM_NUVOTON` or `WOLFSPDM_NATIONS` implies `WOLFSPDM_TCG`. +- `WOLFSPDM_NUVOTON`, `WOLFSPDM_NATIONS` or `WOLFSPDM_INFINEON` implies `WOLFSPDM_TCG`. - `WOLFSPDM_NATIONS` implies `WOLFSPDM_PSK`. - `WOLFSPDM_TCG` or `WOLFSPDM_PROFILE_TPM` implies `WOLFSPDM_MUTUAL_AUTH` (requester identity-key API for TCG GIVE_PUB). diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index 000abe2..16c83ef 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -47,7 +47,7 @@ make check | `--disable-meas` / `--disable-challenge` | Disables GET_MEASUREMENTS / CHALLENGE | | `--disable-heartbeat` / `--disable-key-update` | Disables HEARTBEAT / KEY_UPDATE | | `--disable-mldsa` / `--disable-mlkem` | Forces ML-DSA / ML-KEM off (default auto-follows wolfSSL) | -| `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-psk` / `--enable-responder` | TPM side: TCG SPDM binding, vendor commands, PSK, responder (default: all off) | +| `--enable-tcg` / `--enable-nuvoton` / `--enable-nations` / `--enable-infineon` / `--enable-psk` / `--enable-responder` | TPM side: TCG SPDM binding, vendor commands, PSK, responder (default: all off) | See [[Configuration and Macros]] for the full option-to-macro mapping and their implications. diff --git a/src/spdm_context.c b/src/spdm_context.c index c4ca8b2..d04689c 100644 --- a/src/spdm_context.c +++ b/src/spdm_context.c @@ -150,6 +150,14 @@ int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx) return WOLFSPDM_SUCCESS; } +/* Whether a transport is already installed. A caller that wants to supply a + * default transport needs to know, so it can leave an I/O callback the + * application set alone rather than replacing it. */ +int wolfSPDM_HasIO(WOLFSPDM_CTX* ctx) +{ + return ctx != NULL && ctx->ioCb != NULL; +} + int wolfSPDM_SetResponderPubKey(WOLFSPDM_CTX* ctx, const byte* pubKey, word32 pubKeySz) { @@ -260,10 +268,21 @@ int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode) } #if !defined(WOLFSPDM_NUVOTON) && !defined(WOLFSPDM_NATIONS) && \ - !defined(WOLFSPDM_PSK) + !defined(WOLFSPDM_PSK) && !defined(WOLFSPDM_TCG) (void)mode; #endif +#ifdef WOLFSPDM_TCG + /* The TCG binding with no vendor commands, for a part that uses only the + * identity flow of "TPM Communication over SPDM". */ + if (mode == WOLFSPDM_MODE_TCG) { + ctx->mode = WOLFSPDM_MODE_TCG; + ctx->connectionHandle = 0; + ctx->fipsIndicator = WOLFSPDM_FIPS_NON_FIPS; + return WOLFSPDM_SUCCESS; + } +#endif + #ifdef WOLFSPDM_NUVOTON if (mode == WOLFSPDM_MODE_NUVOTON) { ctx->mode = WOLFSPDM_MODE_NUVOTON; @@ -381,7 +400,8 @@ int wolfSPDM_Connect(WOLFSPDM_CTX* ctx) wolfSPDM_ResetSession(ctx); #ifdef WOLFSPDM_TCG - if (ctx->mode == WOLFSPDM_MODE_NUVOTON || + if (ctx->mode == WOLFSPDM_MODE_TCG || + ctx->mode == WOLFSPDM_MODE_NUVOTON || ctx->mode == WOLFSPDM_MODE_NATIONS) { return wolfSPDM_ConnectTCG(ctx); } diff --git a/src/spdm_internal.h b/src/spdm_internal.h index ac085b4..cb1558c 100644 --- a/src/spdm_internal.h +++ b/src/spdm_internal.h @@ -275,7 +275,8 @@ static WC_INLINE int wolfSPDM_ConstCompare(const byte* a, const byte* b, static WC_INLINE int wolfSPDM_IsTcgMode(const WOLFSPDM_CTX* ctx) { #ifdef WOLFSPDM_TCG - return ctx->mode == WOLFSPDM_MODE_NUVOTON || + return ctx->mode == WOLFSPDM_MODE_TCG || + ctx->mode == WOLFSPDM_MODE_NUVOTON || ctx->mode == WOLFSPDM_MODE_NATIONS || ctx->mode == WOLFSPDM_MODE_NATIONS_PSK; #else diff --git a/src/spdm_msg.c b/src/spdm_msg.c index ee2a933..bac56e9 100644 --- a/src/spdm_msg.c +++ b/src/spdm_msg.c @@ -74,6 +74,19 @@ static const byte kexOpaqueNuvoton[] = { /* Nations only accepts OpaqueLength=0 */ static const byte kexOpaqueNations[] = { 0x00, 0x00 }; #endif +#ifdef WOLFSPDM_TCG +/* TCG binding version 1.0 in DSP0274 opaque format 1. A part that follows + * the TCG binding and offers no secured message version list of its own + * rejects the standard list above with InvalidRequest. */ +static const byte kexOpaqueTcg[] = { + 0x10, 0x00, /* OpaqueLength */ + 0x01, 0x00, 0x00, 0x00, /* one element */ + 0x00, 0x00, 0x05, 0x00, /* DMTF, no vendor ID */ + 0x01, 0x01, 0x01, /* SMDataVersion, ID, count */ + 0x00, 0x10, /* version 1.0 */ + 0x00, 0x00, 0x00 /* alignment */ +}; +#endif static void wolfSPDM_KeyExOpaque(const WOLFSPDM_CTX* ctx, const byte** opaque, word32* opaqueSz) @@ -85,6 +98,12 @@ static void wolfSPDM_KeyExOpaque(const WOLFSPDM_CTX* ctx, *opaque = NULL; *opaqueSz = 0; #endif +#ifdef WOLFSPDM_TCG + if (ctx->mode == WOLFSPDM_MODE_TCG) { + *opaque = kexOpaqueTcg; + *opaqueSz = (word32)sizeof(kexOpaqueTcg); + } +#endif #ifdef WOLFSPDM_NUVOTON if (ctx->mode == WOLFSPDM_MODE_NUVOTON) { *opaque = kexOpaqueNuvoton; @@ -97,7 +116,8 @@ static void wolfSPDM_KeyExOpaque(const WOLFSPDM_CTX* ctx, *opaqueSz = (word32)sizeof(kexOpaqueNations); } #endif -#if !defined(WOLFSPDM_NUVOTON) && !defined(WOLFSPDM_NATIONS) +#if !defined(WOLFSPDM_NUVOTON) && !defined(WOLFSPDM_NATIONS) && \ + !defined(WOLFSPDM_TCG) (void)ctx; #endif } diff --git a/src/vendor/spdm_infineon.c b/src/vendor/spdm_infineon.c new file mode 100644 index 0000000..19535da --- /dev/null +++ b/src/vendor/spdm_infineon.c @@ -0,0 +1,112 @@ +/* spdm_infineon.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +/* Infineon OPTIGA SLB9678 SPDM Functions + * + * Infineon PSK provisioning and configuration commands. Both asymmetric + * and PSK handshakes use the shared protocol code, subject to firmware + * capabilities. Framing uses the shared TCG code in spdm_tcg.c. + */ + +#include "spdm_internal.h" + +#ifdef WOLFSPDM_INFINEON + +#include + +/* ----- Infineon PSK-Mode Vendor Commands ----- */ + +int wolfSPDM_Infineon_GetStatus(WOLFSPDM_CTX* ctx, + WOLFSPDM_INFINEON_STATUS* status) +{ + WOLFSPDM_VENDOR_RSP rsp; + int rc; + + if (ctx == NULL || status == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + XMEMSET(status, 0, sizeof(*status)); + + wolfSPDM_DebugPrint(ctx, "Infineon: GET_STS_\n"); + + /* SLB9678 accepts GET_STATUS with no payload (Type field omitted) */ + rc = wolfSPDM_TCG_VendorCmdClear(ctx, WOLFSPDM_VDCODE_GET_STS, + NULL, 0, &rsp); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugHex(ctx, "GET_STS_ payload", rsp.payload, rsp.payloadSz); + + /* Per TCG spec Table 15 - GET_STATUS_RSP payload: + * [0] SpecMajorVersion, [1] SpecMinorVersion, + * [2] PSKSet (00=NO, 01=YES), + * [3] SPDMOnly (00=DISABLED, 01=ENABLED, 81=PENDING_DISABLE) */ + if (rsp.payloadSz < 4) { + return WOLFSPDM_E_FRAMING; + } + status->spdmEnabled = 1; + status->pskProvisioned = (rsp.payload[2] != 0); + status->spdmOnlyLocked = (rsp.payload[3] != 0); + status->spdmOnlyPending = (rsp.payload[3] == 0x81); + wolfSPDM_DebugPrint(ctx, "GET_STS_: v%u.%u PSK=%s SPDMOnly=0x%02x\n", + rsp.payload[0], rsp.payload[1], + status->pskProvisioned ? "YES" : "NO", + rsp.payload[3]); + + return WOLFSPDM_SUCCESS; +} + +int wolfSPDM_Infineon_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock) +{ + byte param[1]; + int rc; + + if (ctx == NULL) { + return WOLFSPDM_E_INVALID_ARG; + } + + if (ctx->state != WOLFSPDM_STATE_CONNECTED) { + return WOLFSPDM_E_NOT_CONNECTED; + } + + param[0] = lock ? WOLFSPDM_SPDMONLY_LOCK : WOLFSPDM_SPDMONLY_UNLOCK; + + wolfSPDM_DebugPrint(ctx, "Infineon: SPDMONLY %s\n", + lock ? "LOCK" : "UNLOCK"); + + rc = wolfSPDM_TCG_VendorCmdSecured(ctx, WOLFSPDM_VDCODE_SPDMONLY, + param, sizeof(param)); + if (rc != WOLFSPDM_SUCCESS) { + return rc; + } + + wolfSPDM_DebugPrint(ctx, "SPDMONLY: Success (Lock=%u)\n", param[0]); + return WOLFSPDM_SUCCESS; +} + +#endif /* WOLFSPDM_INFINEON */ + diff --git a/test/unit_test.c b/test/unit_test.c index cb05c4c..1b9b91d 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -175,11 +175,15 @@ static int test_context_set_io(void) printf("test_context_set_io...\n"); + ASSERT_EQ(wolfSPDM_HasIO(NULL), 0, "NULL ctx has no IO"); + ASSERT_EQ(wolfSPDM_HasIO(ctx), 0, "fresh ctx has no IO"); ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, dummy_io_cb, &dummy)); ASSERT_EQ(ctx->ioCb, dummy_io_cb, "IO callback not set"); ASSERT_EQ(ctx->ioUserCtx, &dummy, "User context not set"); + ASSERT_EQ(wolfSPDM_HasIO(ctx), 1, "IO must be reported after SetIO"); ASSERT_EQ(wolfSPDM_SetIO(ctx, NULL, NULL), WOLFSPDM_E_INVALID_ARG, "NULL callback should fail"); + ASSERT_EQ(wolfSPDM_HasIO(ctx), 1, "a refused SetIO must not clear IO"); TEST_CTX_FREE(); TEST_PASS(); @@ -1202,6 +1206,159 @@ static int test_nations_psk_message_format(void) } #endif /* WOLFSPDM_NATIONS */ +#ifdef WOLFSPDM_INFINEON +/* Mock responder for the Infineon vendor commands. It records the VdCode and + * payload the requester sent so a test can assert the wire form, and replies + * with a caller-supplied payload so short and unexpected GET_STS_ responses + * are reachable without a part. */ +typedef struct INFINEON_IO_CTX { + const byte* rspPayload; + word32 rspPayloadSz; + int failIo; + char reqVdCode[WOLFSPDM_VDCODE_LEN + 1]; + byte reqPayload[160]; + word32 reqPayloadSz; + int calls; +} INFINEON_IO_CTX; + +static int infineon_io_cb(WOLFSPDM_CTX* ctx, const byte* txBuf, + word32 txSz, byte* rxBuf, word32* rxSz, void* userCtx) +{ + INFINEON_IO_CTX* ioCtx = (INFINEON_IO_CTX*)userCtx; + byte spdmRsp[WOLFSPDM_VENDOR_BUF_SZ]; + word32 totalSz; + int spdmRspSz; + + if (ctx == NULL || rxBuf == NULL || rxSz == NULL || ioCtx == NULL) { + return -1; + } + ioCtx->calls++; + if (ioCtx->failIo) { + return -1; + } + + /* The requester wraps every clear message in a TCG header. */ + if (txSz <= WOLFSPDM_TCG_HEADER_SIZE) { + return -1; + } + ioCtx->reqPayloadSz = sizeof(ioCtx->reqPayload); + if (wolfSPDM_ParseVendorDefined(txBuf + WOLFSPDM_TCG_HEADER_SIZE, + txSz - WOLFSPDM_TCG_HEADER_SIZE, ioCtx->reqVdCode, + ioCtx->reqPayload, &ioCtx->reqPayloadSz) < 0) { + return -1; + } + + spdmRspSz = wolfSPDM_BuildVendorDefined(SPDM_VERSION_13, + ioCtx->reqVdCode, ioCtx->rspPayload, ioCtx->rspPayloadSz, + spdmRsp, sizeof(spdmRsp)); + if (spdmRspSz < 0) { + return -1; + } + spdmRsp[1] = SPDM_TEST_VENDOR_DEFINED_RSP; + + totalSz = WOLFSPDM_TCG_HEADER_SIZE + (word32)spdmRspSz; + if (*rxSz < totalSz) { + return -1; + } + wolfSPDM_WriteTcgHeader(rxBuf, WOLFSPDM_TCG_TAG_CLEAR, totalSz, + ctx->connectionHandle, ctx->fipsIndicator); + XMEMCPY(rxBuf + WOLFSPDM_TCG_HEADER_SIZE, spdmRsp, (word32)spdmRspSz); + *rxSz = totalSz; + + return 0; +} + +static int test_infineon_get_status(void) +{ + /* TCG Table 15: SpecMajor, SpecMinor, PSKSet, SPDMOnly */ + static const byte stsClear[4] = { 0x01, 0x00, 0x00, 0x00 }; + static const byte stsSet[4] = { 0x01, 0x00, 0x01, 0x01 }; + static const byte stsPending[4] = { 0x01, 0x00, 0x01, 0x81 }; + static const byte stsShort[3] = { 0x01, 0x00, 0x01 }; + WOLFSPDM_INFINEON_STATUS status; + INFINEON_IO_CTX ioCtx; + int rc; + TEST_CTX_SETUP(); + + printf("test_infineon_get_status...\n"); + + XMEMSET(&ioCtx, 0, sizeof(ioCtx)); + ASSERT_SUCCESS(wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_TCG)); + ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, infineon_io_cb, &ioCtx)); + + rc = wolfSPDM_Infineon_GetStatus(NULL, &status); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "NULL ctx must be refused"); + rc = wolfSPDM_Infineon_GetStatus(ctx, NULL); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "NULL status must be refused"); + + ioCtx.rspPayload = stsClear; + ioCtx.rspPayloadSz = sizeof(stsClear); + ASSERT_SUCCESS(wolfSPDM_Infineon_GetStatus(ctx, &status)); + ASSERT_EQ(XMEMCMP(ioCtx.reqVdCode, WOLFSPDM_VDCODE_GET_STS, + WOLFSPDM_VDCODE_LEN), 0, "request VdCode must be GET_STS_"); + ASSERT_EQ(ioCtx.reqPayloadSz, 0, "GET_STS_ carries no payload"); + ASSERT_EQ(status.pskProvisioned, 0, "PSKSet 0 means not provisioned"); + ASSERT_EQ(status.spdmOnlyLocked, 0, "SPDMOnly 0 means unlocked"); + ASSERT_EQ(status.spdmOnlyPending, 0, "SPDMOnly 0 is not pending"); + + ioCtx.rspPayload = stsSet; + ioCtx.rspPayloadSz = sizeof(stsSet); + ASSERT_SUCCESS(wolfSPDM_Infineon_GetStatus(ctx, &status)); + ASSERT_EQ(status.pskProvisioned, 1, "PSKSet 1 means provisioned"); + ASSERT_EQ(status.spdmOnlyLocked, 1, "SPDMOnly 1 means locked"); + ASSERT_EQ(status.spdmOnlyPending, 0, "SPDMOnly 1 is not pending"); + + /* 0x81 is locked AND pending disable, not a third state */ + ioCtx.rspPayload = stsPending; + ioCtx.rspPayloadSz = sizeof(stsPending); + ASSERT_SUCCESS(wolfSPDM_Infineon_GetStatus(ctx, &status)); + ASSERT_EQ(status.spdmOnlyLocked, 1, "SPDMOnly 0x81 is still locked"); + ASSERT_EQ(status.spdmOnlyPending, 1, "SPDMOnly 0x81 is pending"); + + /* A truncated report must not be read as a cleared status */ + XMEMSET(&status, 0xFF, sizeof(status)); + ioCtx.rspPayload = stsShort; + ioCtx.rspPayloadSz = sizeof(stsShort); + rc = wolfSPDM_Infineon_GetStatus(ctx, &status); + ASSERT_EQ(rc, WOLFSPDM_E_FRAMING, "short GET_STS_ must be refused"); + ASSERT_EQ(status.pskProvisioned, 0, "status must be cleared on failure"); + ASSERT_EQ(status.spdmOnlyLocked, 0, "status must be cleared on failure"); + + ioCtx.failIo = 1; + rc = wolfSPDM_Infineon_GetStatus(ctx, &status); + ASSERT_EQ(rc, WOLFSPDM_E_IO_FAIL, "transport failure must propagate"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +static int test_infineon_set_only_mode(void) +{ + INFINEON_IO_CTX ioCtx; + int rc; + TEST_CTX_SETUP(); + + printf("test_infineon_set_only_mode...\n"); + + XMEMSET(&ioCtx, 0, sizeof(ioCtx)); + ASSERT_SUCCESS(wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_TCG)); + ASSERT_SUCCESS(wolfSPDM_SetIO(ctx, infineon_io_cb, &ioCtx)); + + rc = wolfSPDM_Infineon_SetOnlyMode(NULL, 1); + ASSERT_EQ(rc, WOLFSPDM_E_INVALID_ARG, "NULL ctx must be refused"); + + /* SPDMONLY travels in a secured record, so it needs a session first */ + rc = wolfSPDM_Infineon_SetOnlyMode(ctx, 1); + ASSERT_EQ(rc, WOLFSPDM_E_NOT_CONNECTED, + "SPDMONLY before a session must be refused"); + ASSERT_EQ(ioCtx.calls, 0, "nothing may go on the wire unconnected"); + + TEST_CTX_FREE(); + TEST_PASS(); +} + +#endif /* WOLFSPDM_INFINEON */ + #ifndef WOLFSPDM_NO_MCTP static int test_decrypt_overflow(void) { @@ -2364,6 +2521,20 @@ static int test_build_key_exchange_format(void) } #endif +#ifdef WOLFSPDM_TCG +/* The plain TCG binding block, asserted whole rather than by length alone: a + * part that follows the binding rejects the standard version list, and on + * that path the only symptom is an InvalidRequest from the device. */ +static const byte tcgKeyExOpaque[] = { + 0x10, 0x00, /* OpaqueLength = 16 */ + 0x01, 0x00, 0x00, 0x00, /* one element */ + 0x00, 0x00, 0x05, 0x00, /* DMTF, no vendor ID */ + 0x01, 0x01, 0x01, /* SMDataVersion, ID, count */ + 0x00, 0x10, /* secured message version 1.0 */ + 0x00, 0x00, 0x00 /* alignment */ +}; +#endif + static int test_build_key_exchange_mode_opaque(void) { byte buf[256]; @@ -2398,6 +2569,15 @@ static int test_build_key_exchange_mode_opaque(void) ASSERT_EQ(bufSz, (word32)(136 + 2), "Nations KEY_EXCHANGE size"); ASSERT_EQ(buf[136] | buf[137], 0, "Nations OpaqueLength must be 0"); #endif +#ifdef WOLFSPDM_TCG + ASSERT_SUCCESS(wolfSPDM_SetMode(ctx, WOLFSPDM_MODE_TCG)); + bufSz = sizeof(buf); + ASSERT_SUCCESS(wolfSPDM_BuildKeyExchange(ctx, buf, &bufSz)); + ASSERT_EQ(buf[3], 0xFF, "TCG SlotID must be 0xFF"); + ASSERT_EQ(bufSz, (word32)(136 + 18), "TCG KEY_EXCHANGE size"); + ASSERT_EQ(XMEMCMP(buf + 136, tcgKeyExOpaque, sizeof(tcgKeyExOpaque)), 0, + "TCG OpaqueData must be the 18-byte binding block"); +#endif TEST_CTX_FREE(); TEST_PASS(); @@ -3270,6 +3450,73 @@ static int test_responder_identity_roundtrip(void) TEST_PASS(); } +/* The same handshake reached through wolfSPDM_Connect in WOLFSPDM_MODE_TCG, + * which is the mode a part with no vendor adapter uses. The roundtrip above + * names the Nuvoton mode and calls wolfSPDM_ConnectTCG directly, so neither + * the dispatch nor the adapter-less path is covered by it. */ +static int test_tcg_mode_connect_roundtrip(void) +{ + WOLFSPDM_CTX req; + byte rctxBuf[WOLFSPDM_RESP_CTX_STATIC_SIZE]; + WOLFSPDM_RESP_CTX* rctx = (WOLFSPDM_RESP_CTX*)rctxBuf; + ecc_key idKey; + byte idPriv[WOLFSPDM_ECC_KEY_SIZE]; + byte idPubX[WOLFSPDM_ECC_KEY_SIZE]; + byte idPubY[WOLFSPDM_ECC_KEY_SIZE]; + byte idPub[WOLFSPDM_ECC_POINT_SIZE]; + byte cmd[10]; + word32 idPrivSz = sizeof(idPriv); + word32 idPubXSz = sizeof(idPubX); + word32 idPubYSz = sizeof(idPubY); + int rc; + + printf("test_tcg_mode_connect_roundtrip...\n"); + + ASSERT_SUCCESS(wolfSPDM_Init(&req)); + ASSERT_SUCCESS(wolfSPDM_RespInit(rctx)); + ASSERT_SUCCESS(wc_ecc_init(&idKey)); + ASSERT_SUCCESS(wc_ecc_make_key(&req.rng, WOLFSPDM_ECC_KEY_SIZE, &idKey)); + ASSERT_SUCCESS(wc_ecc_export_private_only(&idKey, idPriv, &idPrivSz)); + ASSERT_SUCCESS(wc_ecc_export_public_raw(&idKey, idPubX, &idPubXSz, + idPubY, &idPubYSz)); + wc_ecc_free(&idKey); + XMEMCPY(idPub, idPubX, sizeof(idPubX)); + XMEMCPY(idPub + sizeof(idPubX), idPubY, sizeof(idPubY)); + + ASSERT_SUCCESS(wolfSPDM_SetMode(&req, WOLFSPDM_MODE_TCG)); + ASSERT_EQ(wolfSPDM_GetMode(&req), WOLFSPDM_MODE_TCG, "mode must be TCG"); + ASSERT_SUCCESS(wolfSPDM_SetResponderPubKey(&req, idPub, sizeof(idPub))); + ASSERT_SUCCESS(wolfSPDM_RespSetMode(rctx, 1, 0)); + ASSERT_SUCCESS(wolfSPDM_RespSetIdentityKey(rctx, idPriv, sizeof(idPriv), + idPub, sizeof(idPub))); + g_tpmCbInvocations = 0; + ASSERT_SUCCESS(wolfSPDM_RespSetTpmCallback(rctx, responder_tpm_stub, + NULL)); + ASSERT_SUCCESS(wolfSPDM_SetIO(&req, requester_to_responder_iocb, rctx)); + + /* Dispatch, not wolfSPDM_ConnectTCG: TCG mode must reach the TCG flow */ + rc = wolfSPDM_Connect(&req); + TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "TCG-mode Connect failed"); + ASSERT_EQ(wolfSPDM_IsConnected(&req), 1, "Requester not connected"); + + /* One encrypted command over the session the handshake produced */ + XMEMSET(cmd, 0, sizeof(cmd)); + cmd[0] = 0x80; cmd[1] = 0x01; + cmd[5] = 0x0A; + cmd[8] = 0x01; cmd[9] = 0x44; + rc = wolfSPDM_TCG_VendorCmdSecured(&req, WOLFSPDM_VDCODE_TPM2_CMD, + cmd, sizeof(cmd)); + TEST_ASSERT(rc == WOLFSPDM_SUCCESS, "TPM2_CMD passthrough failed"); + ASSERT_EQ(g_tpmCbInvocations, 1, "TPM stub must have run once"); + + ASSERT_SUCCESS(wolfSPDM_Disconnect(&req)); + + wc_ForceZero(idPriv, sizeof(idPriv)); + wolfSPDM_RespFree(rctx); + wolfSPDM_Free(&req); + TEST_PASS(); +} + #ifdef WOLFSPDM_PSK /* PSK handshake + tunneled TPM2_CMD round-trip + END_SESSION, end-to-end. */ static int test_responder_psk_roundtrip(void) @@ -5419,6 +5666,10 @@ int main(void) test_nations_psk_kdf(); test_nations_psk_message_format(); #endif +#ifdef WOLFSPDM_INFINEON + test_infineon_get_status(); + test_infineon_set_only_mode(); +#endif #ifndef WOLFSPDM_NO_MCTP test_decrypt_overflow(); #endif @@ -5582,6 +5833,7 @@ int main(void) test_responder_configured_psk(); #endif test_responder_identity_roundtrip(); + test_tcg_mode_connect_roundtrip(); #endif #if defined(WOLFSPDM_PSK) && defined(WOLFSPDM_TCG) test_responder_psk_roundtrip(); diff --git a/wolfspdm/spdm.h b/wolfspdm/spdm.h index 5c40dd8..58a8c01 100644 --- a/wolfspdm/spdm.h +++ b/wolfspdm/spdm.h @@ -35,7 +35,13 @@ typedef enum { WOLFSPDM_MODE_AUTO = 0, WOLFSPDM_MODE_NUVOTON = 1, WOLFSPDM_MODE_NATIONS = 2, - WOLFSPDM_MODE_NATIONS_PSK = 3 + WOLFSPDM_MODE_NATIONS_PSK = 3, + /* PSK is not specific to one vendor; the name above is kept so existing + * callers still compile. */ + WOLFSPDM_MODE_PSK = WOLFSPDM_MODE_NATIONS_PSK, + /* TCG binding with no vendor commands, which is what a part using only + * the TCG "TPM Communication over SPDM" identity flow needs. */ + WOLFSPDM_MODE_TCG = 4 } WOLFSPDM_MODE; /* wolfSPDM: Lightweight SPDM requester using wolfCrypt. @@ -82,6 +88,9 @@ typedef struct WOLFSPDM_CTX WOLFSPDM_CTX; #ifdef WOLFSPDM_NATIONS #include #endif +#ifdef WOLFSPDM_INFINEON + #include +#endif #ifdef WOLFSPDM_PSK #include #endif @@ -108,6 +117,7 @@ WOLFSPDM_API int wolfSPDM_GetCtxSize(void); WOLFSPDM_API int wolfSPDM_InitStatic(WOLFSPDM_CTX* ctx, int size); /* Configuration */ +WOLFSPDM_API int wolfSPDM_HasIO(WOLFSPDM_CTX* ctx); WOLFSPDM_API int wolfSPDM_SetIO(WOLFSPDM_CTX* ctx, WOLFSPDM_IO_CB ioCb, void* userCtx); WOLFSPDM_API int wolfSPDM_SetMode(WOLFSPDM_CTX* ctx, WOLFSPDM_MODE mode); diff --git a/wolfspdm/spdm_infineon.h b/wolfspdm/spdm_infineon.h new file mode 100644 index 0000000..4613996 --- /dev/null +++ b/wolfspdm/spdm_infineon.h @@ -0,0 +1,70 @@ +/* spdm_infineon.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfSPDM. + * + * wolfSPDM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfSPDM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + */ + +/* Infineon OPTIGA SLB9678 TPM SPDM Support + * + * Sessions use the shared TCG binding with SHA-384/AES-256-GCM and require + * firmware advertising KEY_EX_CAP and PUB_KEY_ID_CAP. Do not infer that + * support from the device name; verify capabilities before connecting. + * + * Reference: SLB9678 TPM 2.0 FW45.02 Extended Datasheet, Section 3.2.6.9. + */ + +#ifndef WOLFSPDM_INFINEON_H +#define WOLFSPDM_INFINEON_H + +#ifdef WOLFSPDM_INFINEON + +/* Include shared TCG declarations */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* GET_STATUS / SPDM_ONLY / TPM_CMD reuse the shared 8-byte vendor-defined + * command codes in spdm_tcg.h; the part adds none of its own here. */ + +/* ----- Infineon SPDM Status ----- */ + +/* GET_STATUS_RSP fields per TCG spec Table 15 */ +typedef struct WOLFSPDM_INFINEON_STATUS { + unsigned int spdmEnabled : 1; + unsigned int sessionActive : 1; + unsigned int spdmOnlyLocked : 1; + unsigned int spdmOnlyPending : 1; + unsigned int pskProvisioned : 1; +} WOLFSPDM_INFINEON_STATUS; + +/* ----- Infineon SPDM Functions ----- */ + +WOLFSPDM_API int wolfSPDM_Infineon_GetStatus(WOLFSPDM_CTX* ctx, + WOLFSPDM_INFINEON_STATUS* status); + +WOLFSPDM_API int wolfSPDM_Infineon_SetOnlyMode(WOLFSPDM_CTX* ctx, int lock); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSPDM_INFINEON */ + +#endif /* WOLFSPDM_INFINEON_H */ diff --git a/wolfspdm/spdm_types.h b/wolfspdm/spdm_types.h index 96b692b..c4b7eaf 100644 --- a/wolfspdm/spdm_types.h +++ b/wolfspdm/spdm_types.h @@ -437,9 +437,12 @@ extern "C" { /* ----- TCG Build Option ----- */ -/* Nuvoton or Nations enables TCG SPDM binding; future chips can set directly */ -#if (defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS)) && \ - !defined(WOLFSPDM_TCG) +/* A vendor adapter enables the TCG SPDM binding; future chips can set it + * directly. The same implication is in configure.ac, and it has to be here + * too for a build that defines the vendor macro without using configure: + * without it the vendor header hides the TCG types it needs. */ +#if (defined(WOLFSPDM_NUVOTON) || defined(WOLFSPDM_NATIONS) || \ + defined(WOLFSPDM_INFINEON)) && !defined(WOLFSPDM_TCG) #define WOLFSPDM_TCG #endif