diff --git a/examples/spdm/README.md b/examples/spdm/README.md index 61dca041..36915a34 100644 --- a/examples/spdm/README.md +++ b/examples/spdm/README.md @@ -9,8 +9,8 @@ The `spdm_ctrl` tool establishes SPDM secure sessions between the host and a TPM over SPI, enabling AES-256-GCM encrypted bus communication. Identity mode requires the responder key from a trusted provisioning source. -`spdm_ctrl` is the only example that accepts SPDM credentials. Other wolfTPM -examples use uncredentialed `wolfTPM2_Init()` and intentionally return +`spdm_ctrl` and `nv_bind` are the examples that accept SPDM credentials. Other +wolfTPM examples use uncredentialed `wolfTPM2_Init()` and intentionally return `WOLFSPDM_E_BAD_STATE` while a TPM is locked in SPDM-only mode; unlock it with `spdm_ctrl` before running those examples. @@ -64,6 +64,8 @@ make | `--responder-pubkey ` | Pin a trusted raw P-384 X\|\|Y key (192 hex characters) | | `--connect` | Establish SPDM session (ECDH P-384 handshake) | | `--caps` | Read TPM capabilities over the current transport | +| `--session-info` | Show the TPM's view of the SPDM session (`TPM_CAP_SPDM_SESSION_INFO`) | +| `--policy-nv` | Define an NV index guarded by `TPM2_PolicyTransportSPDM`, then write and read it over the session | | `--psk ` | Start a PSK session | | `--psk-set ` | Provision a 64-byte PSK and 32-byte ClearAuth | | `--psk-clear ` | Clear a provisioned PSK | @@ -71,6 +73,21 @@ make | `--unlock` | Unlock SPDM-only mode (use with `--connect`) | | `--tpm-clear` | Send `TPM2_Clear` over the current transport | +The `nv_bind` example is a focused, self-contained version of the same idea: it +provisions an NV index whose `authPolicy` is `TPM2_PolicyTransportSPDM`, stores a +secret over an SPDM-PSK session, then shows that the identical read over a plain +(non-SPDM) connection is refused with `TPM_RC_CHANNEL`. + +```sh +./src/fwtpm/fwtpm_server --spdm-psk --spdm-psk-hex --clear & +./examples/spdm/nv_bind --psk +``` + +The fwTPM generates a fresh SPDM identity key each time it starts, so on the +fwTPM a policy bound to `tpmKeyName` is only valid for that server lifetime; a +hardware TPM holds a persistent identity key, where such a binding is durable. +PSK sessions report empty key names, since no asymmetric key authenticated them. + ## Usage Examples ```bash diff --git a/examples/spdm/include.am b/examples/spdm/include.am index deb6283d..32736150 100644 --- a/examples/spdm/include.am +++ b/examples/spdm/include.am @@ -4,15 +4,23 @@ if BUILD_EXAMPLES if BUILD_SPDM noinst_PROGRAMS += examples/spdm/spdm_ctrl +noinst_PROGRAMS += examples/spdm/nv_bind examples_spdm_spdm_ctrl_SOURCES = examples/spdm/spdm_ctrl.c examples_spdm_spdm_ctrl_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_spdm_spdm_ctrl_DEPENDENCIES = src/libwolftpm.la examples_spdm_spdm_ctrl_CFLAGS = $(AM_CFLAGS) + +examples_spdm_nv_bind_SOURCES = examples/spdm/nv_bind.c +examples_spdm_nv_bind_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_spdm_nv_bind_DEPENDENCIES = src/libwolftpm.la +examples_spdm_nv_bind_CFLAGS = $(AM_CFLAGS) endif endif example_spdmdir = $(exampledir)/spdm dist_example_spdm_DATA = examples/spdm/spdm_ctrl.c +dist_example_spdm_DATA += examples/spdm/nv_bind.c DISTCLEANFILES+= examples/spdm/.libs/spdm_ctrl +DISTCLEANFILES+= examples/spdm/.libs/nv_bind diff --git a/examples/spdm/nv_bind.c b/examples/spdm/nv_bind.c new file mode 100644 index 00000000..da73ca2c --- /dev/null +++ b/examples/spdm/nv_bind.c @@ -0,0 +1,347 @@ +/* nv_bind.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 + */ + +/* Bind an NV index to an SPDM session with TPM2_PolicyTransportSPDM so it can + * only be accessed over the SPDM secure channel: a secret in NV that a normal + * (plaintext) bus request cannot reach. + * + * The demo runs against a firmware TPM started in SPDM-PSK mode: + * ./src/fwtpm/fwtpm_server --spdm-psk --spdm-psk-hex --clear & + * ./examples/spdm/nv_bind --psk + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include +#include + +#include +#include +#include + +#ifndef WOLFTPM2_NO_WRAPPER + +#include +#include + +int TPM2_SPDM_NVBind_Example(void* userCtx, int argc, char *argv[]); + +#if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_PSK) + +#define NV_BIND_INDEX TPM2_DEMO_NVRAM_STORE_INDEX + +static int nv_bind_nibble(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static int nv_bind_hex(const char* hex, byte* out, word32 outSz, word32* usedSz) +{ + word32 len = (word32)XSTRLEN(hex); + word32 i; + int hi, lo; + + if ((len & 1U) != 0U || (len / 2U) > outSz) { + return BAD_FUNC_ARG; + } + for (i = 0; i < len; i += 2) { + hi = nv_bind_nibble(hex[i]); + lo = nv_bind_nibble(hex[i + 1]); + if (hi < 0 || lo < 0) { + return BAD_FUNC_ARG; + } + out[i / 2] = (byte)((hi << 4) | lo); + } + *usedSz = len / 2U; + return TPM_RC_SUCCESS; +} + +/* Read the SPDM-bound NV index through a fresh policy session that asserts + * PolicyTransportSPDM. Returns the TPM_RC so the caller can tell an + * off-channel denial (TPM_RC_CHANNEL) from a real error. */ +static int nv_bind_policy_read(WOLFTPM2_DEV* dev, WOLFTPM2_NV* nv, + byte* buf, word32* bufSz) +{ + int rc; + WOLFTPM2_SESSION session; + + XMEMSET(&session, 0, sizeof(session)); + /* Writing flips TPMA_NV_WRITTEN and changes the Name, so refresh it. */ + rc = wolfTPM2_NVOpen(dev, nv, NV_BIND_INDEX, NULL, 0); + if (rc == 0) { + rc = wolfTPM2_StartSession(dev, &session, NULL, NULL, TPM_SE_POLICY, + TPM_ALG_NULL); + } + if (rc == 0) { + rc = wolfTPM2_SetAuthSession(dev, 0, &session, + TPMA_SESSION_continueSession); + } + if (rc == 0) { + rc = wolfTPM2_PolicyTransportSPDM(dev, session.handle.hndl, NULL, NULL); + } + if (rc == 0) { + rc = wolfTPM2_NVReadAuth(dev, nv, NV_BIND_INDEX, buf, bufSz, 0); + } + if (session.handle.hndl != 0) { + wolfTPM2_UnsetAuth(dev, 0); + wolfTPM2_UnloadHandle(dev, &session.handle); + } + return rc; +} + +/* Create the NV index and write the secret to it over SPDM. */ +static int nv_bind_provision(WOLFTPM2_DEV* dev, const byte* secret, + word32 secretSz, const byte* policyDigest, word32 policyDigestSz) +{ + int rc; + word32 nvAttributes; + WOLFTPM2_HANDLE parent; + WOLFTPM2_SESSION session; + WOLFTPM2_NV nv; + int nvAttempted = 0; + + XMEMSET(&parent, 0, sizeof(parent)); + XMEMSET(&session, 0, sizeof(session)); + XMEMSET(&nv, 0, sizeof(nv)); + parent.hndl = TPM_RH_OWNER; + + rc = wolfTPM2_GetNvAttributesTemplate(parent.hndl, &nvAttributes); + if (rc == 0) { + /* Policy-only: clear owner and auth access so the SPDM policy is the + * only way in (an owner-authorized read would otherwise bypass it). */ + nvAttributes &= ~(TPMA_NV_AUTHREAD | TPMA_NV_AUTHWRITE | + TPMA_NV_OWNERREAD | TPMA_NV_OWNERWRITE); + nvAttributes |= (TPMA_NV_POLICYREAD | TPMA_NV_POLICYWRITE); + nvAttempted = 1; + rc = wolfTPM2_NVCreateAuthPolicy(dev, &parent, &nv, NV_BIND_INDEX, + nvAttributes, secretSz, NULL, 0, policyDigest, (int)policyDigestSz); + if (rc == TPM_RC_NV_DEFINED) { + rc = wolfTPM2_NVDeleteAuth(dev, &parent, NV_BIND_INDEX); + if (rc == 0) { + rc = wolfTPM2_NVCreateAuthPolicy(dev, &parent, &nv, + NV_BIND_INDEX, nvAttributes, secretSz, NULL, 0, + policyDigest, (int)policyDigestSz); + } + } + } + if (rc == 0) { + rc = wolfTPM2_StartSession(dev, &session, NULL, NULL, TPM_SE_POLICY, + TPM_ALG_NULL); + } + if (rc == 0) { + rc = wolfTPM2_SetAuthSession(dev, 0, &session, + TPMA_SESSION_continueSession); + } + if (rc == 0) { + rc = wolfTPM2_PolicyTransportSPDM(dev, session.handle.hndl, NULL, NULL); + } + if (rc == 0) { + rc = wolfTPM2_NVWriteAuth(dev, &nv, NV_BIND_INDEX, (byte*)secret, + secretSz, 0); + } + if (session.handle.hndl != 0) { + wolfTPM2_UnsetAuth(dev, 0); + wolfTPM2_UnloadHandle(dev, &session.handle); + } + /* The wrapper defines the index before it opens it, so clean up on any + * failure after the attempt, not only after a reported success. */ + if (rc != 0 && nvAttempted) { + (void)wolfTPM2_NVDeleteAuth(dev, &parent, NV_BIND_INDEX); + } + return rc; +} + +int TPM2_SPDM_NVBind_Example(void* userCtx, int argc, char *argv[]) +{ + int rc; + int i; + const char* pskHex = NULL; + byte psk[128]; + word32 pskSz = 0; + byte secret[] = "SPDM-only NV secret"; + byte readBuf[sizeof(secret)]; + word32 readSz; + byte policyDigest[TPM_MAX_DIGEST_SIZE]; + word32 policyDigestSz; + WOLFTPM2_DEV dev; + WOLFTPM2_NV nv; + WOLFTPM2_HANDLE parent; + int nvProvisioned = 0; + + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "--psk") == 0 && i + 1 < argc) { + pskHex = argv[++i]; + } + else if (XSTRCMP(argv[i], "-h") == 0 || + XSTRCMP(argv[i], "--help") == 0) { + printf("Usage: nv_bind --psk \n"); + printf("Binds NV index 0x%x to an SPDM session.\n", NV_BIND_INDEX); + return 0; + } + } +#ifndef NO_GETENV + if (pskHex == NULL) { + pskHex = getenv("WOLFTPM_TEST_SPDM_PSK"); + } +#endif + if (pskHex == NULL || pskHex[0] == '\0') { + printf("No PSK provided (use --psk or WOLFTPM_TEST_SPDM_PSK)\n"); + return 0; + } + XMEMSET(&nv, 0, sizeof(nv)); + XMEMSET(&parent, 0, sizeof(parent)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + parent.hndl = TPM_RH_OWNER; + + /* The policy binds to any SPDM session (no key names). */ + XMEMSET(policyDigest, 0, sizeof(policyDigest)); + policyDigestSz = (word32)sizeof(policyDigest); + rc = wolfTPM2_PolicyTransportSPDMMake(WOLFTPM2_WRAP_DIGEST, NULL, NULL, + policyDigest, &policyDigestSz); + if (rc != 0) { + printf("PolicyTransportSPDMMake failed 0x%x: %s\n", + rc, TPM2_GetRCString(rc)); + return rc; + } + + printf("=== Bind an NV index to an SPDM session ===\n"); + printf("NV index 0x%x, authPolicy = PolicyTransportSPDM: ", NV_BIND_INDEX); + TPM2_PrintBin(policyDigest, policyDigestSz); + + /* Decode the PSK just before use; every path from here zeroizes it. */ + rc = nv_bind_hex(pskHex, psk, (word32)sizeof(psk), &pskSz); + if (rc != 0) { + wc_ForceZero(psk, sizeof(psk)); + printf("Invalid PSK hex string\n"); + return rc; + } + + /* Step 1: over SPDM, create the index and store the secret. */ + printf("\n[1] Over the SPDM channel: provision and store the secret\n"); + XMEMSET(&dev, 0, sizeof(dev)); + rc = wolfTPM2_InitWithSpdmPsk(&dev, TPM2_IoCb, userCtx, psk, pskSz, + NULL, 0); + wc_ForceZero(psk, sizeof(psk)); + if (rc != 0) { + printf(" SPDM init failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; + } + if (!wolfTPM2_SpdmIsConnected(&dev)) { + printf(" SPDM session not established\n"); + wolfTPM2_Cleanup(&dev); + return WOLFSPDM_E_BAD_STATE; + } + printf(" SPDM session established (0x%08x)\n", + wolfTPM2_SpdmGetSessionId(&dev)); + rc = nv_bind_provision(&dev, secret, (word32)sizeof(secret), + policyDigest, policyDigestSz); + if (rc == 0) { + nvProvisioned = 1; + readSz = (word32)sizeof(readBuf); + rc = nv_bind_policy_read(&dev, &nv, readBuf, &readSz); + } + if (rc == 0 && (readSz != (word32)sizeof(secret) || + XMEMCMP(readBuf, secret, readSz) != 0)) { + printf(" Read-back mismatch over SPDM\n"); + rc = TPM_RC_FAILURE; + } + if (rc == 0) { + printf(" Wrote and read back over SPDM: \"%s\"\n", (char*)readBuf); + } + else { + printf(" FAILED over SPDM 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + /* Remove the index before leaving so nothing persists on failure. */ + if (nvProvisioned) + (void)wolfTPM2_NVDeleteAuth(&dev, &parent, NV_BIND_INDEX); + } + wolfTPM2_Cleanup(&dev); + if (rc != 0) { + return rc; + } + + /* Step 2: off the channel (plaintext bus), the same read is refused. */ + printf("\n[2] Off the SPDM channel: the same read is refused\n"); + XMEMSET(&dev, 0, sizeof(dev)); + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + if (rc != 0) { + printf(" Plaintext init failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + if (nvProvisioned) { + printf(" NV index 0x%x remains; remove it with NV_UndefineSpace\n", + NV_BIND_INDEX); + } + return rc; + } + XMEMSET(&nv, 0, sizeof(nv)); + readSz = (word32)sizeof(readBuf); + rc = nv_bind_policy_read(&dev, &nv, readBuf, &readSz); + /* Format-one codes may carry an auth-session selector in the upper bits. */ + if ((rc & RC_MAX_FMT1) == TPM_RC_CHANNEL) { + printf(" Correctly denied with TPM_RC_CHANNEL: " + "no SPDM channel, no access\n"); + rc = TPM_RC_SUCCESS; + } + else if (rc == TPM_RC_SUCCESS) { + printf(" UNEXPECTED: plaintext read succeeded\n"); + rc = TPM_RC_FAILURE; + } + else { + printf(" UNEXPECTED 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + } + + /* Clean up the NV index (owner authorization, not the NV policy). */ + (void)wolfTPM2_NVDeleteAuth(&dev, &parent, NV_BIND_INDEX); + wolfTPM2_Cleanup(&dev); + + printf("\n%s\n", rc == TPM_RC_SUCCESS ? + "PASS: the NV index is reachable only over SPDM" : + "FAIL"); + return rc; +} + +#else /* !WOLFTPM_SPDM || !WOLFTPM_SPDM_PSK */ +int TPM2_SPDM_NVBind_Example(void* userCtx, int argc, char *argv[]) +{ + (void)userCtx; (void)argc; (void)argv; + printf("Example requires --enable-spdm --enable-psk\n"); + return 0; +} +#endif + +#endif /* !WOLFTPM2_NO_WRAPPER */ + +#ifndef NO_MAIN_DRIVER +int main(int argc, char *argv[]) +{ + int rc = -1; +#ifndef WOLFTPM2_NO_WRAPPER + rc = TPM2_SPDM_NVBind_Example(NULL, argc, argv); +#else + printf("Wrapper code not compiled in\n"); + (void)argc; (void)argv; +#endif + return rc == 0 ? 0 : 1; +} +#endif diff --git a/examples/spdm/spdm_ctrl.c b/examples/spdm/spdm_ctrl.c index e393f9c4..d29dd65c 100644 --- a/examples/spdm/spdm_ctrl.c +++ b/examples/spdm/spdm_ctrl.c @@ -79,6 +79,9 @@ static void usage(void) " --get-pubkey Get TPM's SPDM-Identity public key\n" " --connect Establish SPDM session\n" " --caps Get TPM capabilities (use with --connect)\n" + " --session-info Show the TPM's view of the SPDM session\n" + " --policy-nv Bind an NV index to the SPDM session with\n" + " TPM2_PolicyTransportSPDM and access it (use with --connect)\n" " -h, --help Show this help\n\n" #ifdef WOLFSPDM_NUVOTON "Build: ./configure --enable-spdm --enable-nuvoton\n" @@ -105,6 +108,251 @@ static int ctrl_caps(WOLFTPM2_DEV* dev) return rc; } +static void ctrl_print_name(const char* label, const TPM2B_NAME* name) +{ + word32 i; + printf(" %s (%d bytes): ", label, name->size); + if (name->size == 0) { + printf("(empty)"); + } + for (i = 0; i < name->size; i++) { + printf("%02x", name->name[i]); + } + printf("\n"); +} + +/* Ask the TPM which SPDM session (if any) this command arrived through. + * Returns TPM_RC_SUCCESS on a successful query, else the TPM/wolfTPM code. */ +static int ctrl_session_info(WOLFTPM2_DEV* dev) +{ + int rc; + word32 i; + TPML_SPDM_SESSION_INFO info; + + printf("\n=== SPDM Session Info (TPM_CAP_SPDM_SESSION_INFO) ===\n"); + XMEMSET(&info, 0, sizeof(info)); + rc = wolfTPM2_GetCapability_SPDMSessionInfo(dev, &info); + if (rc == TPM_RC_VALUE) { + printf(" Not supported by this TPM (TPM_RC_VALUE)\n"); + return rc; + } + if (rc != 0) { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; + } + printf(" Sessions: %u%s\n", info.count, + info.count == 0 ? " (command was not sent inside an SPDM session)" : ""); + for (i = 0; i < info.count; i++) { + printf(" Session %u:\n", i); + ctrl_print_name("reqKeyName", &info.spdmSessionInfo[i].reqKeyName); + ctrl_print_name("tpmKeyName", &info.spdmSessionInfo[i].tpmKeyName); + } + return TPM_RC_SUCCESS; +} + +/* Run one NV op (write when writeBuf != NULL, else read) under a fresh + * policy session whose only term is PolicyTransportSPDM. */ +static int ctrl_policy_nv_op(WOLFTPM2_DEV* dev, WOLFTPM2_NV* nv, + word32 nvIndex, const TPM2B_NAME* reqKeyName, const TPM2B_NAME* tpmKeyName, + byte* writeBuf, byte* readBuf, word32* ioSz) +{ + int rc; + WOLFTPM2_SESSION session; + + XMEMSET(&session, 0, sizeof(session)); + /* Refresh the NV Name: writing flips TPMA_NV_WRITTEN, which changes the + * index's Name and therefore the policy-session authorization. */ + rc = wolfTPM2_NVOpen(dev, nv, nvIndex, NULL, 0); + if (rc != 0) { + return rc; + } + rc = wolfTPM2_StartSession(dev, &session, NULL, NULL, TPM_SE_POLICY, + TPM_ALG_NULL); + if (rc != 0) { + return rc; + } + rc = wolfTPM2_SetAuthSession(dev, 0, &session, + TPMA_SESSION_continueSession); + if (rc == 0) { + rc = wolfTPM2_PolicyTransportSPDM(dev, session.handle.hndl, + reqKeyName, tpmKeyName); + } + if (rc == 0) { + if (writeBuf != NULL) { + rc = wolfTPM2_NVWriteAuth(dev, nv, nvIndex, writeBuf, *ioSz, 0); + } + else { + rc = wolfTPM2_NVReadAuth(dev, nv, nvIndex, readBuf, ioSz, 0); + } + } + wolfTPM2_UnsetAuth(dev, 0); + wolfTPM2_UnloadHandle(dev, &session.handle); + return rc; +} + +/* Define (or replace) the demo index with the given authPolicy. */ +static int ctrl_policy_nv_define(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* parent, + WOLFTPM2_NV* nv, word32 nvIndex, word32 nvAttributes, word32 dataSz, + const byte* policyDigest, word32 policyDigestSz) +{ + int rc; + + rc = wolfTPM2_NVCreateAuthPolicy(dev, parent, nv, nvIndex, nvAttributes, + dataSz, NULL, 0, policyDigest, (int)policyDigestSz); + if (rc == TPM_RC_NV_DEFINED) { + rc = wolfTPM2_NVDeleteAuth(dev, parent, nvIndex); + if (rc == 0) { + rc = wolfTPM2_NVCreateAuthPolicy(dev, parent, nv, nvIndex, + nvAttributes, dataSz, NULL, 0, policyDigest, + (int)policyDigestSz); + } + } + return rc; +} + +/* Define an NV index whose authPolicy is PolicyTransportSPDM bound to the + * current session's key names, then write and read it. Outside an SPDM + * session the TPM answers TPM_RC_CHANNEL. */ +static int ctrl_policy_nv(WOLFTPM2_DEV* dev) +{ + int rc; + word32 nvIndex = TPM2_DEMO_NVRAM_STORE_INDEX; + word32 nvAttributes; + word32 ioSz; + byte policyDigest[TPM_MAX_DIGEST_SIZE]; + word32 policyDigestSz; + byte writeBuf[] = "wolfTPM SPDM bound NV"; + byte readBuf[sizeof(writeBuf)]; + const TPM2B_NAME* reqKeyName = NULL; + const TPM2B_NAME* tpmKeyName = NULL; + TPML_SPDM_SESSION_INFO info; + TPM2B_NAME badName; + WOLFTPM2_HANDLE parent; + WOLFTPM2_NV nv; + int nvAttempted = 0; + + XMEMSET(&parent, 0, sizeof(parent)); + XMEMSET(&nv, 0, sizeof(nv)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + parent.hndl = TPM_RH_OWNER; + + printf("\n=== PolicyTransportSPDM NV binding ===\n"); + + /* Bind to any SPDM session (empty key names). */ + printf(" Binding NV access to any SPDM session\n"); + + XMEMSET(policyDigest, 0, sizeof(policyDigest)); + policyDigestSz = (word32)sizeof(policyDigest); + rc = wolfTPM2_PolicyTransportSPDMMake(WOLFTPM2_WRAP_DIGEST, reqKeyName, + tpmKeyName, policyDigest, &policyDigestSz); + if (rc != 0) goto exit; + printf(" authPolicy: "); + TPM2_PrintBin(policyDigest, policyDigestSz); + + rc = wolfTPM2_GetNvAttributesTemplate(parent.hndl, &nvAttributes); + if (rc != 0) goto exit; + /* Policy-only: clear owner and auth access so the SPDM policy is the only + * way in (an owner-authorized read would otherwise bypass it). */ + nvAttributes &= ~(TPMA_NV_AUTHREAD | TPMA_NV_AUTHWRITE | + TPMA_NV_OWNERREAD | TPMA_NV_OWNERWRITE); + nvAttributes |= (TPMA_NV_POLICYREAD | TPMA_NV_POLICYWRITE); + + nvAttempted = 1; + rc = ctrl_policy_nv_define(dev, &parent, &nv, nvIndex, nvAttributes, + (word32)sizeof(writeBuf), policyDigest, policyDigestSz); + if (rc != 0) goto exit; + printf(" Created NV index 0x%x with PolicyTransportSPDM authPolicy\n", + nvIndex); + + ioSz = (word32)sizeof(writeBuf); + rc = ctrl_policy_nv_op(dev, &nv, nvIndex, reqKeyName, tpmKeyName, + writeBuf, NULL, &ioSz); + if (rc != 0) goto exit; + printf(" NV write through the policy session succeeded\n"); + + ioSz = (word32)sizeof(readBuf); + rc = ctrl_policy_nv_op(dev, &nv, nvIndex, reqKeyName, tpmKeyName, + NULL, readBuf, &ioSz); + if (rc != 0) goto exit; + if (ioSz != (word32)sizeof(writeBuf) || + XMEMCMP(readBuf, writeBuf, ioSz) != 0) { + printf(" NV read back mismatch\n"); + rc = TPM_RC_FAILURE; + goto exit; + } + printf(" NV read through the policy session succeeded: \"%s\"\n", + (const char*)readBuf); + + /* Only asymmetric sessions report a TPM identity key (PSK sessions carry + * none), so the bound-name checks are skipped rather than failed. */ + XMEMSET(&info, 0, sizeof(info)); + rc = wolfTPM2_GetCapability_SPDMSessionInfo(dev, &info); + if (rc != 0) goto exit; + if (info.count != 1 || info.spdmSessionInfo[0].tpmKeyName.size == 0) { + printf(" No TPM identity key reported; bound-name check skipped\n"); + goto exit; + } + tpmKeyName = &info.spdmSessionInfo[0].tpmKeyName; + XMEMSET(policyDigest, 0, sizeof(policyDigest)); + policyDigestSz = (word32)sizeof(policyDigest); + rc = wolfTPM2_PolicyTransportSPDMMake(WOLFTPM2_WRAP_DIGEST, NULL, + tpmKeyName, policyDigest, &policyDigestSz); + if (rc != 0) goto exit; + rc = ctrl_policy_nv_define(dev, &parent, &nv, nvIndex, nvAttributes, + (word32)sizeof(writeBuf), policyDigest, policyDigestSz); + if (rc != 0) goto exit; + ioSz = (word32)sizeof(writeBuf); + rc = ctrl_policy_nv_op(dev, &nv, nvIndex, NULL, tpmKeyName, + writeBuf, NULL, &ioSz); + if (rc != 0) goto exit; + ioSz = (word32)sizeof(readBuf); + rc = ctrl_policy_nv_op(dev, &nv, nvIndex, NULL, tpmKeyName, + NULL, readBuf, &ioSz); + if (rc != 0) goto exit; + if (ioSz != (word32)sizeof(writeBuf) || + XMEMCMP(readBuf, writeBuf, ioSz) != 0) { + printf(" Bound NV read back mismatch\n"); + rc = TPM_RC_FAILURE; + goto exit; + } + printf(" Bound tpmKeyName policy: write/read OK\n"); + + /* A policy bound to a different name must be refused by the channel. */ + XMEMCPY(&badName, tpmKeyName, sizeof(badName)); + badName.name[badName.size - 1] ^= 0x01; + XMEMSET(policyDigest, 0, sizeof(policyDigest)); + policyDigestSz = (word32)sizeof(policyDigest); + rc = wolfTPM2_PolicyTransportSPDMMake(WOLFTPM2_WRAP_DIGEST, NULL, + &badName, policyDigest, &policyDigestSz); + if (rc != 0) goto exit; + rc = ctrl_policy_nv_define(dev, &parent, &nv, nvIndex, nvAttributes, + (word32)sizeof(writeBuf), policyDigest, policyDigestSz); + if (rc != 0) goto exit; + ioSz = (word32)sizeof(readBuf); + rc = ctrl_policy_nv_op(dev, &nv, nvIndex, NULL, &badName, + NULL, readBuf, &ioSz); + if ((rc & RC_MAX_FMT1) != TPM_RC_CHANNEL_KEY) { + printf(" Bound-name mismatch not rejected (0x%x)\n", rc); + rc = TPM_RC_FAILURE; + goto exit; + } + rc = TPM_RC_SUCCESS; + printf(" Bound tpmKeyName policy: mismatch rejected (TPM_RC_CHANNEL_KEY)\n"); + +exit: + if (rc != 0) { + printf(" FAILED: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + /* Format-one codes may carry an auth-session selector in upper bits. */ + if ((rc & RC_MAX_FMT1) == TPM_RC_CHANNEL) { + printf(" (the policy requires the command to arrive over SPDM)\n"); + } + } + if (nvAttempted) { + (void)wolfTPM2_NVDeleteAuth(dev, &parent, nvIndex); + } + return rc; +} + #ifdef WOLFSPDM_NUVOTON static int ctrl_enable(WOLFTPM2_DEV* dev) { @@ -504,26 +752,7 @@ static int ctrl_nations_caps184(WOLFTPM2_DEV* dev) } /* 3. TPM_CAP_SPDM_SESSION_INFO (TPM 184: SPDM session state) */ - printf(" SPDM Session Info (TPM_CAP_SPDM_SESSION_INFO):\n"); - XMEMSET(&capIn, 0, sizeof(capIn)); - capIn.capability = TPM_CAP_SPDM_SESSION_INFO; - capIn.property = 0; - capIn.propertyCount = 1; - XMEMSET(&capOut, 0, sizeof(capOut)); - rc = TPM2_GetCapability(&capIn, &capOut); - if (rc == 0) { - byte* raw = (byte*)&capOut.capabilityData; - word32 rawSz = sizeof(capOut.capabilityData); - printf(" Response (%u bytes): ", rawSz); - for (i = 0; i < rawSz && i < 64; i++) - printf("%02x", raw[i]); - if (rawSz > 64) printf("..."); - printf("\n"); - } else if (rc == TPM_RC_VALUE) { - printf(" Not supported (TPM_RC_VALUE)\n"); - } else { - printf(" Failed: 0x%x: %s\n", rc, TPM2_GetRCString(rc)); - } + (void)ctrl_session_info(dev); return 0; } @@ -963,6 +1192,14 @@ int TPM2_SPDM_Ctrl(void* userCtx, int argc, char *argv[]) rc = ctrl_caps(&dev); matched = 1; } + else if (!matched && XSTRCMP(argv[i], "--session-info") == 0) { + rc = ctrl_session_info(&dev); + matched = 1; + } + else if (!matched && XSTRCMP(argv[i], "--policy-nv") == 0) { + rc = ctrl_policy_nv(&dev); + matched = 1; + } else if (!matched && XSTRCMP(argv[i], "--tpm-clear") == 0) { printf("\n=== TPM2_Clear ===\n"); rc = wolfTPM2_Clear(&dev); diff --git a/examples/spdm/spdm_test.sh b/examples/spdm/spdm_test.sh index 159c7d1a..c1309a6c 100755 --- a/examples/spdm/spdm_test.sh +++ b/examples/spdm/spdm_test.sh @@ -447,6 +447,10 @@ elif [ "$VENDOR" = "fwtpm-tcg" ]; then fi run_test "Status in SPDM-only mode" run_identity --status run_test "TPM capabilities in SPDM-only mode" run_identity --caps + run_test_output "SPDM session info reports the TPM identity key" \ + "tpmKeyName (50 bytes): 000c" run_identity --connect --session-info + run_test_output "PolicyTransportSPDM NV binding over SPDM" \ + "mismatch rejected (TPM_RC_CHANNEL_KEY)" run_identity --connect --policy-nv run_test "Unlock SPDM-only mode" run_identity --connect --unlock if [ -x "$CAPS_DEMO" ]; then @@ -512,6 +516,11 @@ elif [ "$VENDOR" = "fwtpm-psk" ]; then else echo -e " ${YELLOW}Skipping: $UNIT_TEST not found${NC}" fi + if [ -x ./examples/spdm/nv_bind ]; then + run_test_output "NV index bound to SPDM (nv_bind demo)" \ + "reachable only over SPDM" \ + ./examples/spdm/nv_bind --psk "$NATIONS_PSK" + fi run_test "Lock PSK SPDM-only mode" "$SPDM_DEMO" --vendor=nations \ --psk "$NATIONS_PSK" --lock run_test_rejected "Uncredentialed initialization rejected while locked" \ @@ -522,6 +531,9 @@ elif [ "$VENDOR" = "fwtpm-psk" ]; then run_test_output "Status preserves the PSK session for TPM commands" \ "Session: active" "$SPDM_DEMO" --vendor=nations \ --psk "$NATIONS_PSK" --status --caps + run_test_output "SPDM session info reports no identity key for PSK" \ + "tpmKeyName (0 bytes): (empty)" "$SPDM_DEMO" --vendor=nations \ + --psk "$NATIONS_PSK" --session-info run_test "Unlock PSK SPDM-only mode" "$SPDM_DEMO" --vendor=nations \ --psk "$NATIONS_PSK" --unlock run_test "PSK clear (PSK_CLEAR)" "$SPDM_DEMO" --vendor=nations \