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 \ diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index bcbcbdd3..8270a5c2 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -38,6 +38,16 @@ #include #include #include +/* Responder objects are linked into fwtpm_server (FWTPM_SPDM_HAVE_RESPONDER) + * and into libwolftpm. The fwTPM unit test and fuzz harness compile this file + * standalone against wolfSSL only, so they drive the SPDM flag directly. */ +#if defined(WOLFTPM_SPDM_RESPONDER) && \ + (defined(FWTPM_SPDM_HAVE_RESPONDER) || defined(BUILDING_WOLFTPM)) + #define FWTPM_SPDM_USE_RESPONDER +#endif +#ifdef FWTPM_SPDM_USE_RESPONDER +#include +#endif #include #include @@ -200,6 +210,180 @@ static TPM_RC FwSkipAuthArea(TPM2_Packet* cmd, int cmdSize) return TPM_RC_SUCCESS; } +#ifdef WOLFTPM_SPDM +#ifdef FWTPM_SPDM_USE_RESPONDER +#define FWTPM_SPDM_P384_SZ 48 +/* Name of a marshaled TPMT_PUBLIC: nameAlg || H_nameAlg(publicArea). The + * publicArea is marshaled type(2) || nameAlg(2) || ..., so nameAlg is at + * offset 2, not 0. */ +static TPM_RC FwNameFromPublicArea(const byte* pub, word32 pubSz, + TPM2B_NAME* name) +{ + TPM_RC rc = TPM_RC_SUCCESS; + UINT16 nameAlg; + int digestSz; + enum wc_HashType wcHash; + + name->size = 0; + if (pub == NULL || pubSz < 4) { + return TPM_RC_SUCCESS; + } + nameAlg = (UINT16)(((UINT16)pub[2] << 8) | pub[3]); + digestSz = TPM2_GetHashDigestSize(nameAlg); + wcHash = FwGetWcHashType(nameAlg); + if (digestSz <= 0 || wcHash == WC_HASH_TYPE_NONE || + digestSz + 2 > (int)sizeof(name->name)) { + return TPM_RC_HASH; + } + name->name[0] = pub[2]; + name->name[1] = pub[3]; + if (wc_Hash(wcHash, pub, pubSz, name->name + 2, digestSz) != 0) { + rc = TPM_RC_FAILURE; + } + if (rc == 0) { + name->size = (UINT16)(2 + digestSz); + } + return rc; +} + +/* Compute the Name of the responder's own SPDM identity key (raw P-384 + * X||Y) using the TCG SPDM key template. */ +static TPM_RC FwSpdmTpmKeyName(const byte* rawPub, word32 rawPubSz, + TPM2B_NAME* name) +{ + TPM_RC rc = TPM_RC_SUCCESS; + TPM2_Packet pkt; + FWTPM_DECLARE_VAR(pub, TPMT_PUBLIC); + FWTPM_DECLARE_BUF(buf, FWTPM_MAX_PUB_BUF); + + name->size = 0; + if (rawPub == NULL || rawPubSz != 2 * FWTPM_SPDM_P384_SZ) { + return TPM_RC_SUCCESS; + } + FWTPM_ALLOC_VAR(pub, TPMT_PUBLIC); + FWTPM_ALLOC_BUF(buf, FWTPM_MAX_PUB_BUF); + if (rc == 0) { + XMEMSET(pub, 0, sizeof(TPMT_PUBLIC)); + pub->type = TPM_ALG_ECC; + pub->nameAlg = TPM_ALG_SHA384; + pub->objectAttributes = TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_restricted | + TPMA_OBJECT_sign; + pub->parameters.eccDetail.symmetric.algorithm = TPM_ALG_NULL; + pub->parameters.eccDetail.scheme.scheme = TPM_ALG_ECDSA; + pub->parameters.eccDetail.scheme.details.ecdsa.hashAlg = TPM_ALG_SHA384; + pub->parameters.eccDetail.curveID = TPM_ECC_NIST_P384; + pub->parameters.eccDetail.kdf.scheme = TPM_ALG_NULL; + pub->unique.ecc.x.size = FWTPM_SPDM_P384_SZ; + XMEMCPY(pub->unique.ecc.x.buffer, rawPub, FWTPM_SPDM_P384_SZ); + pub->unique.ecc.y.size = FWTPM_SPDM_P384_SZ; + XMEMCPY(pub->unique.ecc.y.buffer, rawPub + FWTPM_SPDM_P384_SZ, + FWTPM_SPDM_P384_SZ); + + XMEMSET(&pkt, 0, sizeof(pkt)); + pkt.buf = buf; + pkt.size = (int)FWTPM_MAX_PUB_BUF; + TPM2_Packet_AppendPublicArea(&pkt, pub); + rc = FwNameFromPublicArea(buf, (word32)pkt.pos, name); + } + FWTPM_FREE_BUF(buf); + FWTPM_FREE_VAR(pub); + return rc; +} +#endif /* FWTPM_SPDM_USE_RESPONDER */ + +/* Returns 1 when the command being processed arrived inside an established + * SPDM secure session. Any SPDM secure session qualifies, asymmetric (TCG + * binding) or vendor PSK, since wolfTPM supports both SPDM session types. + * reqKeyName is always empty: the responder performs no requester + * mutual-authentication, so a requester key is never trusted. tpmKeyName is + * the responder's own identity-key Name when one exists. */ +static int FwSpdmSessionNames(FWTPM_CTX* ctx, TPM2B_NAME* reqKeyName, + TPM2B_NAME* tpmKeyName) +{ +#ifdef FWTPM_SPDM_USE_RESPONDER + const byte* idPub = NULL; + word32 idPubSz = 0; +#endif + + reqKeyName->size = 0; + tpmKeyName->size = 0; + if (!ctx->activeCmdOverSpdm) { + return 0; + } +#ifdef FWTPM_SPDM_USE_RESPONDER + if (ctx->spdmRespCtx != NULL) { + if (!wolfSPDM_RespIsSessionActive(ctx->spdmRespCtx)) { + return 0; + } + idPubSz = wolfSPDM_RespGetIdentityKey(ctx->spdmRespCtx, &idPub); + if (FwSpdmTpmKeyName(idPub, idPubSz, tpmKeyName) != 0) { + tpmKeyName->size = 0; + } + } +#endif + return 1; +} + +#ifndef FWTPM_NO_POLICY +/* scKeyNameHash = H(req.size || req.name || tpm.size || tpm.name). A name + * that is not checked contributes a zero size (Part 3 CompareScKeyNameHash). */ +static TPM_RC FwScKeyNameHash(TPMI_ALG_HASH hashAlg, + const TPM2B_NAME* reqKeyName, int inclReq, + const TPM2B_NAME* tpmKeyName, int inclTpm, + byte* out, int* outSz) +{ + TPM_RC rc = TPM_RC_SUCCESS; + FWTPM_DECLARE_VAR(hashCtx, wc_HashAlg); + enum wc_HashType wcHash = FwGetWcHashType(hashAlg); + int digestSz = TPM2_GetHashDigestSize(hashAlg); + byte szBuf[2]; + UINT16 sz; + + FWTPM_ALLOC_VAR(hashCtx, wc_HashAlg); + if (rc == 0 && (digestSz <= 0 || wcHash == WC_HASH_TYPE_NONE)) { + rc = TPM_RC_HASH; + } + if (rc == 0 && wc_HashInit_ex(hashCtx, wcHash, NULL, INVALID_DEVID) != 0) { + rc = TPM_RC_FAILURE; + } + if (rc == 0) { + sz = inclReq ? reqKeyName->size : 0; + szBuf[0] = (byte)(sz >> 8); + szBuf[1] = (byte)sz; + if (wc_HashUpdate(hashCtx, wcHash, szBuf, 2) != 0) { + rc = TPM_RC_FAILURE; + } + if (rc == 0 && sz > 0 && + wc_HashUpdate(hashCtx, wcHash, reqKeyName->name, sz) != 0) { + rc = TPM_RC_FAILURE; + } + if (rc == 0) { + sz = inclTpm ? tpmKeyName->size : 0; + szBuf[0] = (byte)(sz >> 8); + szBuf[1] = (byte)sz; + if (wc_HashUpdate(hashCtx, wcHash, szBuf, 2) != 0) { + rc = TPM_RC_FAILURE; + } + } + if (rc == 0 && sz > 0 && + wc_HashUpdate(hashCtx, wcHash, tpmKeyName->name, sz) != 0) { + rc = TPM_RC_FAILURE; + } + if (rc == 0 && wc_HashFinal(hashCtx, wcHash, out) != 0) { + rc = TPM_RC_FAILURE; + } + wc_HashFree(hashCtx, wcHash); + if (rc == 0) { + *outSz = digestSz; + } + } + FWTPM_FREE_VAR(hashCtx); + return rc; +} +#endif /* !FWTPM_NO_POLICY */ +#endif /* WOLFTPM_SPDM */ + /* Map hash alg to fwTPM PCR bank index */ static int FwGetPcrBankIndex(UINT16 hashAlg) { @@ -1355,6 +1539,11 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, printf("fwTPM: GetCapability(cap=0x%x, prop=0x%x, count=%d)\n", capability, property, propertyCount); #endif +#ifdef WOLFTPM_SPDM + if (capability == TPM_CAP_SPDM_SESSION_INFO && property != 0) { + return TPM_RC_VALUE; + } +#endif paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); @@ -1871,6 +2060,30 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_AppendU32(rsp, 0); break; + #ifdef WOLFTPM_SPDM + case TPM_CAP_SPDM_SESSION_INFO: { + TPM2B_NAME reqKeyName; + TPM2B_NAME tpmKeyName; + /* A session entry exists only when this command came over SPDM. */ + int haveSession = FwSpdmSessionNames(ctx, &reqKeyName, &tpmKeyName); + if (haveSession && propertyCount > 0) { + TPM2_Packet_AppendU32(rsp, 1); + TPM2_Packet_AppendU16(rsp, reqKeyName.size); + TPM2_Packet_AppendBytes(rsp, reqKeyName.name, reqKeyName.size); + TPM2_Packet_AppendU16(rsp, tpmKeyName.size); + TPM2_Packet_AppendBytes(rsp, tpmKeyName.name, tpmKeyName.size); + } + else { + TPM2_Packet_AppendU32(rsp, 0); + /* Entry exists but was not returned (propertyCount 0): the + * TPM must report moreData=YES per Part 3 GetCapability. */ + if (haveSession) + FwPatchMoreData(rsp, moreDataPos, 1); + } + break; + } + #endif /* WOLFTPM_SPDM */ + default: TPM2_Packet_AppendU32(rsp, 0); break; @@ -10663,6 +10876,12 @@ static TPM_RC FwCmd_PolicyRestart(FWTPM_CTX* ctx, TPM2_Packet* cmd, sess->nvWrittenState = 0; sess->pcrUpdateCounter = 0; sess->hasPcrUpdateCounter = 0; + #ifdef WOLFTPM_SPDM + sess->checkSecureChannel = 0; + sess->checkReqKey = 0; + sess->checkTpmKey = 0; + sess->scKeyNameHash.size = 0; + #endif FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); } @@ -11771,6 +11990,111 @@ static TPM_RC FwCmd_PolicyLocality(FWTPM_CTX* ctx, TPM2_Packet* cmd, return rc; } +#ifdef WOLFTPM_SPDM +/* A non-empty name is nameAlg || digest and must be exactly that long. */ +static TPM_RC FwSpdmCheckName(const TPM2B_NAME* name) +{ + UINT16 alg; + int digestSz; + + if (name->size == 0) { + return TPM_RC_SUCCESS; + } + if (name->size < 2) { + return TPM_RC_SIZE; + } + alg = (UINT16)(((UINT16)name->name[0] << 8) | name->name[1]); + digestSz = TPM2_GetHashDigestSize(alg); + if (digestSz <= 0 || FwGetWcHashType(alg) == WC_HASH_TYPE_NONE) { + return TPM_RC_HASH; + } + if ((int)name->size - 2 != digestSz) { + return TPM_RC_SIZE; + } + return TPM_RC_SUCCESS; +} + +static TPM_RC FwSpdmParseName(TPM2_Packet* cmd, int cmdSize, TPM2B_NAME* name) +{ + /* A missing size prefix must be rejected, not read as an empty name. */ + if (cmd->pos + (int)sizeof(UINT16) > cmdSize) { + return TPM_RC_COMMAND_SIZE; + } + TPM2_Packet_ParseU16(cmd, &name->size); + if (name->size > sizeof(name->name)) { + return TPM_RC_SIZE; + } + if (cmd->pos + (int)name->size > cmdSize) { + return TPM_RC_COMMAND_SIZE; + } + TPM2_Packet_ParseBytes(cmd, name->name, name->size); + return TPM_RC_SUCCESS; +} + +/* --- TPM2_PolicyTransportSPDM (CC 0x01A1) --- */ +/* policyDigest = H(policyDigest || TPM_CC_PolicyTransportSPDM || scKeyNameHash) + * Wire: policySession (U32) -> reqKeyName (TPM2B_NAME) -> tpmKeyName. */ +static TPM_RC FwCmd_PolicyTransportSPDM(FWTPM_CTX* ctx, TPM2_Packet* cmd, + int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) +{ + TPM_RC rc = TPM_RC_SUCCESS; + UINT32 sessHandle; + FWTPM_Session* sess; + TPM2B_NAME reqKeyName; + TPM2B_NAME tpmKeyName; + byte scHash[TPM_MAX_DIGEST_SIZE]; + int scHashSz = 0; + + XMEMSET(&reqKeyName, 0, sizeof(reqKeyName)); + XMEMSET(&tpmKeyName, 0, sizeof(tpmKeyName)); + + TPM2_Packet_ParseU32(cmd, &sessHandle); + if (cmdTag == TPM_ST_SESSIONS) rc = FwSkipAuthArea(cmd, cmdSize); + if (rc == 0) rc = FwSpdmParseName(cmd, cmdSize, &reqKeyName); + if (rc == 0) rc = FwSpdmParseName(cmd, cmdSize, &tpmKeyName); + + sess = FwFindSession(ctx, sessHandle); + if (rc == 0 && sess == NULL) { + rc = TPM_RC_VALUE; + } + if (rc == 0 && sess->sessionType != TPM_SE_POLICY && + sess->sessionType != TPM_SE_TRIAL) { + rc = TPM_RC_AUTH_TYPE; + } + /* Part 3: may only be applied once per session */ + if (rc == 0 && sess->checkSecureChannel) { + rc = TPM_RC_VALUE; + } + if (rc == 0) rc = FwSpdmCheckName(&reqKeyName); + if (rc == 0) rc = FwSpdmCheckName(&tpmKeyName); + + if (rc == 0 && (reqKeyName.size > 0 || tpmKeyName.size > 0)) { + rc = FwScKeyNameHash(sess->authHash, &reqKeyName, 1, &tpmKeyName, 1, + scHash, &scHashSz); + } + if (rc == 0) { + #ifdef DEBUG_WOLFTPM + printf("fwTPM: PolicyTransportSPDM(session=0x%x, req=%d, tpm=%d)\n", + sessHandle, reqKeyName.size, tpmKeyName.size); + #endif + if (FwPolicyExtend(sess, TPM_CC_PolicyTransportSPDM, + (scHashSz > 0) ? scHash : NULL, scHashSz, NULL, 0, 0) != 0) { + rc = TPM_RC_FAILURE; + } + } + if (rc == 0) { + sess->checkSecureChannel = 1; + sess->checkReqKey = (reqKeyName.size > 0); + sess->checkTpmKey = (tpmKeyName.size > 0); + sess->scKeyNameHash.size = (UINT16)scHashSz; + XMEMCPY(sess->scKeyNameHash.buffer, scHash, (size_t)scHashSz); + FwRspNoParams(rsp, cmdTag); + } + + return rc; +} +#endif /* WOLFTPM_SPDM */ + /* --- TPM2_PolicySigned (CC 0x0160) --- */ /* Simplified: verify signature and extend policyDigest with * H(policyDigest || TPM_CC_PolicySigned || authObject.name). @@ -18479,6 +18803,9 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = { { TPM_CC_PolicySecret, FwCmd_PolicySecret, 2, 1, 0, 0 }, { TPM_CC_PolicyAuthorize, FwCmd_PolicyAuthorize, 1, 0, 0, 0 }, { TPM_CC_PolicyLocality, FwCmd_PolicyLocality, 1, 0, 0, 0 }, +#ifdef WOLFTPM_SPDM + { TPM_CC_PolicyTransportSPDM, FwCmd_PolicyTransportSPDM, 1, 0, 0, FW_CMD_FLAG_ENC }, +#endif { TPM_CC_PolicySigned, FwCmd_PolicySigned, 2, 0, 0, 0 }, #ifndef FWTPM_NO_NV { TPM_CC_PolicyNV, FwCmd_PolicyNV, 3, 1, 0, 0 }, @@ -19404,6 +19731,33 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, TPM_ST_NO_SESSIONS, TPM_RC_LOCALITY); return TPM_RC_SUCCESS; } +#if defined(WOLFTPM_SPDM) && !defined(FWTPM_NO_POLICY) + /* Enforce PolicyTransportSPDM: the command must have arrived + * over an SPDM session, optionally under specific keys. */ + if (pSess->checkSecureChannel) { + TPM2B_NAME scReq; + TPM2B_NAME scTpm; + byte scHash[TPM_MAX_DIGEST_SIZE]; + int scHashSz = 0; + if (!FwSpdmSessionNames(ctx, &scReq, &scTpm)) { + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, + TPM_ST_NO_SESSIONS, TPM_RC_CHANNEL); + return TPM_RC_SUCCESS; + } + if ((pSess->checkReqKey || pSess->checkTpmKey) && + (FwScKeyNameHash(pSess->authHash, + &scReq, pSess->checkReqKey, + &scTpm, pSess->checkTpmKey, + scHash, &scHashSz) != 0 || + scHashSz != (int)pSess->scKeyNameHash.size || + TPM2_ConstantCompare(pSess->scKeyNameHash.buffer, + scHash, (word32)scHashSz) != 0)) { + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, + TPM_ST_NO_SESSIONS, TPM_RC_CHANNEL_KEY); + return TPM_RC_SUCCESS; + } + } +#endif /* WOLFTPM_SPDM && !FWTPM_NO_POLICY */ #ifndef FWTPM_NO_PP /* Enforce PolicyPhysicalPresence: the platform PP signal must * be asserted now (Part 1 Sec.23.2). */ diff --git a/src/fwtpm/fwtpm_main.c b/src/fwtpm/fwtpm_main.c index 7a85cd77..80804d65 100644 --- a/src/fwtpm/fwtpm_main.c +++ b/src/fwtpm/fwtpm_main.c @@ -111,7 +111,9 @@ static int fwtpmSpdmTpmDispatch(void* userCtx, return BAD_FUNC_ARG; } rspSize = (int)sizeof(stageBuf); + ctx->activeCmdOverSpdm = 1; rc = FWTPM_ProcessCommand(ctx, cmd, (int)cmdSz, stageBuf, &rspSize, 0); + ctx->activeCmdOverSpdm = 0; if (rc == TPM_RC_SUCCESS && rspSize >= TPM2_HEADER_SIZE) { if ((word32)rspSize <= respBufSz) { XMEMCPY(resp, stageBuf, (size_t)rspSize); diff --git a/src/fwtpm/include.am b/src/fwtpm/include.am index 866351ae..f98d9b0a 100644 --- a/src/fwtpm/include.am +++ b/src/fwtpm/include.am @@ -39,6 +39,8 @@ src_fwtpm_fwtpm_server_SOURCES += \ src/spdm/spdm_secured.c \ src/spdm/spdm_session.c \ src/spdm/spdm_transcript.c +src_fwtpm_fwtpm_server_CFLAGS += -DFWTPM_SPDM_HAVE_RESPONDER +src_fwtpm_fwtpm_server_CPPFLAGS += -DFWTPM_SPDM_HAVE_RESPONDER if BUILD_SPDM_TCG src_fwtpm_fwtpm_server_SOURCES += src/spdm/spdm_tcg.c endif diff --git a/src/spdm/spdm_responder.c b/src/spdm/spdm_responder.c index 37112288..8b800e92 100644 --- a/src/spdm/spdm_responder.c +++ b/src/spdm/spdm_responder.c @@ -44,6 +44,8 @@ struct WOLFSPDM_RESP_CTX { * rejected with TPM_RC_DISABLED */ unsigned int pskProvisioned : 1; /* PSK_SET / PSK_CLR vendor state */ unsigned int clearAuthSet : 1; /* a ClearAuth digest is stored */ + unsigned int sessionAsym : 1; /* session came from KEY_EXCHANGE */ + unsigned int pendingAsym : 1; /* KEY_EX reached via KEY_EXCHANGE */ } flags; /* SHA-384(ClearAuth) stored on PSK_SET, verified on PSK_CLR. */ @@ -184,6 +186,11 @@ int wolfSPDM_RespSetIdentityKey(WOLFSPDM_RESP_CTX* ctx, pubSz != WOLFSPDM_ECC_POINT_SIZE) { return WOLFSPDM_E_INVALID_ARG; } + /* Rotating the key mid-session would attribute that session to a key it + * never negotiated with. */ + if (ctx->ctx.state != WOLFSPDM_STATE_INIT) { + return WOLFSPDM_E_BAD_STATE; + } XMEMCPY(ctx->idPrivKey, privKey, privSz); ctx->idPrivKeyLen = privSz; XMEMCPY(ctx->idPubKey, pubKey, pubSz); @@ -216,6 +223,26 @@ int wolfSPDM_RespIsLocked(const WOLFSPDM_RESP_CTX* ctx) return (ctx != NULL && ctx->flags.spdmOnlyLock) ? 1 : 0; } +int wolfSPDM_RespIsSessionActive(const WOLFSPDM_RESP_CTX* ctx) +{ + if (ctx == NULL) { + return 0; + } + return (ctx->ctx.state == WOLFSPDM_STATE_CONNECTED && + ctx->ctx.sessionId != 0) ? 1 : 0; +} + +word32 wolfSPDM_RespGetIdentityKey(const WOLFSPDM_RESP_CTX* ctx, + const byte** idPub) +{ + if (ctx == NULL || idPub == NULL || !ctx->flags.hasIdKey || + !ctx->flags.sessionAsym) { + return 0; + } + *idPub = ctx->idPubKey; + return ctx->idPubKeyLen; +} + void wolfSPDM_RespReset(WOLFSPDM_RESP_CTX* ctx) { if (ctx == NULL) { @@ -241,6 +268,8 @@ void wolfSPDM_RespReset(WOLFSPDM_RESP_CTX* ctx) ctx->ctx.rspSeqNum = 0; ctx->ctx.sessionId = 0; ctx->ctx.state = WOLFSPDM_STATE_INIT; + ctx->flags.sessionAsym = 0; + ctx->flags.pendingAsym = 0; } #ifdef WOLFTPM_SPDM_TCG @@ -529,6 +558,7 @@ static int RespBuildPskExchangeRsp(WOLFSPDM_RESP_CTX* rctx, if (rc == WOLFSPDM_SUCCESS) { *outSz = off; ctx->state = WOLFSPDM_STATE_KEY_EX; + rctx->flags.pendingAsym = 0; } wc_ForceZero(verifyData, sizeof(verifyData)); @@ -760,6 +790,7 @@ static int RespBuildKeyExchangeRsp(WOLFSPDM_RESP_CTX* rctx, if (rc == WOLFSPDM_SUCCESS) { *outSz = off; ctx->state = WOLFSPDM_STATE_KEY_EX; + rctx->flags.pendingAsym = 1; } wc_ForceZero(savedReqPriv, sizeof(savedReqPriv)); @@ -819,6 +850,7 @@ static int RespHandleFinish(WOLFSPDM_RESP_CTX* rctx, } if (rc == WOLFSPDM_SUCCESS) { ctx->state = WOLFSPDM_STATE_CONNECTED; + rctx->flags.sessionAsym = 1; } wc_ForceZero(expectedHmac, sizeof(expectedHmac)); @@ -877,6 +909,7 @@ static int RespHandlePskFinish(WOLFSPDM_RESP_CTX* rctx, * requester decrypts with handshake keys but we wrote with app keys). */ if (rc == WOLFSPDM_SUCCESS) { ctx->state = WOLFSPDM_STATE_CONNECTED; + rctx->flags.sessionAsym = 0; } wc_ForceZero(expectedHmac, sizeof(expectedHmac)); @@ -1116,14 +1149,25 @@ static int RespDispatchSecured(WOLFSPDM_RESP_CTX* rctx, respPlainSz = WOLFSPDM_MAX_MSG_SIZE; switch (code) { + /* A finish must match the exchange that opened KEY_EX and cannot run + * again once connected, or a PSK peer could relabel its session as + * identity-key authenticated with a plain FINISH. */ #ifdef WOLFTPM_SPDM_PSK case SPDM_PSK_FINISH: + if (ctx->state != WOLFSPDM_STATE_KEY_EX || + rctx->flags.pendingAsym) { + return WOLFSPDM_E_BAD_STATE; + } rc = RespHandlePskFinish(rctx, plain, plainSz, respPlain, &respPlainSz); derivedAppKeys = (rc == WOLFSPDM_SUCCESS) ? 1 : 0; break; #endif case SPDM_FINISH: + if (ctx->state != WOLFSPDM_STATE_KEY_EX || + !rctx->flags.pendingAsym) { + return WOLFSPDM_E_BAD_STATE; + } rc = RespHandleFinish(rctx, plain, plainSz, respPlain, &respPlainSz); derivedAppKeys = (rc == WOLFSPDM_SUCCESS) ? 1 : 0; diff --git a/src/tpm2.c b/src/tpm2.c index e3d1ec67..16ce6580 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -1263,6 +1263,51 @@ TPM_RC TPM2_GetTestResult(GetTestResult_Out* out) return rc; } +#ifdef WOLFTPM_SPDM +/* Parse a TPM2B_NAME from a capability response, rejecting truncated names + * and lengths that exceed the local buffer. */ +static int TPM2_ParseSpdmName(TPM2_Packet* packet, TPM2B_NAME* name) +{ + UINT16 wireSz = 0; + int avail; + + TPM2_Packet_ParseU16(packet, &wireSz); + avail = packet->size - packet->pos; + /* A short size field, a declared length past the packet, or a length + * larger than the Name buffer is a malformed capability response. */ + if (packet->overflow || avail < 0 || (int)wireSz > avail || + (int)wireSz > (int)sizeof(name->name)) { + return TPM_RC_SIZE; + } + name->size = wireSz; + TPM2_Packet_ParseBytes(packet, name->name, (int)wireSz); + return TPM_RC_SUCCESS; +} + +int TPM2_ParseSpdmSessionInfo(TPM2_Packet* packet, + TPML_SPDM_SESSION_INFO* sessInfo) +{ + int rc = TPM_RC_SUCCESS; + int i; + + if (packet == NULL || sessInfo == NULL) + return BAD_FUNC_ARG; + + TPM2_Packet_ParseU32(packet, &sessInfo->count); + if (packet->overflow) + return TPM_RC_SIZE; + if (sessInfo->count > MAX_SPDM_SESS_INFO) + sessInfo->count = MAX_SPDM_SESS_INFO; + for (i = 0; i < (int)sessInfo->count && rc == TPM_RC_SUCCESS; i++) { + TPMS_SPDM_SESSION_INFO* si = &sessInfo->spdmSessionInfo[i]; + rc = TPM2_ParseSpdmName(packet, &si->reqKeyName); + if (rc == TPM_RC_SUCCESS) + rc = TPM2_ParseSpdmName(packet, &si->tpmKeyName); + } + return rc; +} +#endif /* WOLFTPM_SPDM */ + TPM_RC TPM2_GetCapability(GetCapability_In* in, GetCapability_Out* out) { TPM_RC rc; @@ -1452,6 +1497,15 @@ TPM_RC TPM2_GetCapability(GetCapability_In* in, GetCapability_Out* out) } break; } + #ifdef WOLFTPM_SPDM + case TPM_CAP_SPDM_SESSION_INFO: + { + TPML_SPDM_SESSION_INFO* sessInfo = + &out->capabilityData.data.spdmSessionInfo; + rc = TPM2_ParseSpdmSessionInfo(&packet, sessInfo); + break; + } + #endif /* WOLFTPM_SPDM */ case TPM_CAP_VENDOR_PROPERTY: { out->capabilityData.data.vendor.size = @@ -4600,6 +4654,51 @@ TPM_RC TPM2_PolicyPCR(PolicyPCR_In* in) return rc; } +#ifdef WOLFTPM_SPDM +TPM_RC TPM2_PolicyTransportSPDM(PolicyTransportSPDM_In* in) +{ + TPM_RC rc; + TPM2_CTX* ctx = TPM2_GetActiveCtx(); + TPM_ST st; + + if (ctx == NULL || in == NULL) + return BAD_FUNC_ARG; + if (in->reqKeyName.size > sizeof(in->reqKeyName.name) || + in->tpmKeyName.size > sizeof(in->tpmKeyName.name)) + 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; + info.flags = (CMD_FLAG_ENC2); + + TPM2_Packet_Init(ctx, &packet); + + TPM2_Packet_AppendU32(&packet, in->policySession); + + st = TPM2_Packet_AppendAuth(&packet, ctx, &info); + + TPM2_Packet_AppendU16(&packet, in->reqKeyName.size); + TPM2_Packet_AppendBytes(&packet, in->reqKeyName.name, + in->reqKeyName.size); + + TPM2_Packet_AppendU16(&packet, in->tpmKeyName.size); + TPM2_Packet_AppendBytes(&packet, in->tpmKeyName.name, + in->tpmKeyName.size); + + TPM2_Packet_Finalize(&packet, st, TPM_CC_PolicyTransportSPDM); + + /* send command */ + rc = TPM2_SendCommandAuth(ctx, &packet, &info); + + TPM2_ReleaseLock(ctx); + } + return rc; +} +#endif /* WOLFTPM_SPDM */ + TPM_RC TPM2_PolicyLocality(PolicyLocality_In* in) { TPM_RC rc; @@ -7130,6 +7229,8 @@ const char* TPM2_GetRCString(int rc) TPM_RC_STR(TPM_RC_BINDING, "Public and sensitive portions of an object are not cryptographically bound"); TPM_RC_STR(TPM_RC_CURVE, "Curve not supported"); TPM_RC_STR(TPM_RC_ECC_POINT, "Point is not on the required curve"); + TPM_RC_STR(TPM_RC_CHANNEL, "Command is not protected by a secure channel required by the policy"); + TPM_RC_STR(TPM_RC_CHANNEL_KEY, "Secure channel key does not match the key required by the policy"); default: break; } diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 96999072..51ebc75c 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -1392,6 +1392,42 @@ word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev) return wolfSPDM_GetSessionId(dev->spdmCtx->spdmCtx); } +int wolfTPM2_GetCapability_SPDMSessionInfo(WOLFTPM2_DEV* dev, + TPML_SPDM_SESSION_INFO* spdmSessionInfo) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + + if (dev == NULL || spdmSessionInfo == NULL) { + return BAD_FUNC_ARG; + } + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_SPDM_SESSION_INFO; + in.property = 0; /* must be 0 per Part 3 */ + in.propertyCount = MAX_SPDM_SESS_INFO; + + rc = TPM2_GetCapability(&in, &out); + if (rc == TPM_RC_SUCCESS) { + if (out.capabilityData.capability == TPM_CAP_SPDM_SESSION_INFO) { + XMEMCPY(spdmSessionInfo, &out.capabilityData.data.spdmSessionInfo, + sizeof(*spdmSessionInfo)); + } + else { + rc = TPM_RC_VALUE; + } + } +#ifdef DEBUG_WOLFTPM + if (rc != TPM_RC_SUCCESS) { + printf("wolfTPM2_GetCapability_SPDMSessionInfo failed 0x%x: %s\n", + rc, TPM2_GetRCString(rc)); + } +#endif + return rc; +} + int wolfTPM2_SpdmDisconnect(WOLFTPM2_DEV* dev) { WOLFTPM2_SPDM_CHECK_CTX(dev); @@ -11062,6 +11098,42 @@ int wolfTPM2_PolicyPCR(WOLFTPM2_DEV* dev, TPM_HANDLE sessionHandle, return rc; } +#ifdef WOLFTPM_SPDM +int wolfTPM2_PolicyTransportSPDM(WOLFTPM2_DEV* dev, TPM_HANDLE sessionHandle, + const TPM2B_NAME* reqKeyName, const TPM2B_NAME* tpmKeyName) +{ + int rc; + PolicyTransportSPDM_In in; + + if (dev == NULL) + return BAD_FUNC_ARG; + + XMEMSET(&in, 0, sizeof(in)); + in.policySession = sessionHandle; + if (reqKeyName != NULL) { + if (reqKeyName->size > sizeof(in.reqKeyName.name)) + return BUFFER_E; + in.reqKeyName.size = reqKeyName->size; + XMEMCPY(in.reqKeyName.name, reqKeyName->name, reqKeyName->size); + } + if (tpmKeyName != NULL) { + if (tpmKeyName->size > sizeof(in.tpmKeyName.name)) + return BUFFER_E; + in.tpmKeyName.size = tpmKeyName->size; + XMEMCPY(in.tpmKeyName.name, tpmKeyName->name, tpmKeyName->size); + } + + rc = TPM2_PolicyTransportSPDM(&in); +#ifdef DEBUG_WOLFTPM + if (rc != TPM_RC_SUCCESS) { + printf("wolfTPM2_PolicyTransportSPDM failed 0x%x: %s\n", + rc, TPM2_GetRCString(rc)); + } +#endif + return rc; +} +#endif /* WOLFTPM_SPDM */ + /* Use this password (in clear) for the policy session instead of the HMAC */ int wolfTPM2_PolicyPassword(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* tpmSession, const byte* auth, int authSz) @@ -11475,6 +11547,93 @@ int wolfTPM2_PolicyPCRMake(TPM_ALG_ID pcrAlg, byte* pcrArray, word32 pcrArraySz, return rc; } +#ifdef WOLFTPM_SPDM +/* A non-empty SPDM key name is nameAlg(2) || digest and must match the hash + * the TPM would require; empty names are allowed. Mirrors the TPM-side check + * so an offline authPolicy cannot be built from a name the TPM rejects. */ +static int wolfTPM2_CheckSpdmName(const TPM2B_NAME* name) +{ + UINT16 alg; + int digestSz; + + if (name == NULL || name->size == 0) { + return TPM_RC_SUCCESS; + } + if (name->size < 2 || name->size > sizeof(name->name)) { + return BAD_FUNC_ARG; + } + alg = (UINT16)(((UINT16)name->name[0] << 8) | name->name[1]); + digestSz = TPM2_GetHashDigestSize(alg); + if (digestSz <= 0 || (int)name->size - 2 != digestSz) { + return BAD_FUNC_ARG; + } + return TPM_RC_SUCCESS; +} + +/* Part 3 PolicyTransportSPDM: when either name is present the policy binds + * scKeyNameHash = H(req.size || req.name || tpm.size || tpm.name), else an + * empty buffer. digest is policyDigestOld in and policyDigestNew out. */ +int wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_ID hashAlg, + const TPM2B_NAME* reqKeyName, const TPM2B_NAME* tpmKeyName, + byte* digest, word32* digestSz) +{ + int rc; + int hashSz; + word32 pos = 0; + word32 scSz = 0; + byte sc[TPM_MAX_DIGEST_SIZE]; + byte buf[2 * (sizeof(UINT16) + sizeof(TPM2B_NAME))]; + UINT16 reqSz = (reqKeyName != NULL) ? reqKeyName->size : 0; + UINT16 tpmSz = (tpmKeyName != NULL) ? tpmKeyName->size : 0; + + if (digest == NULL || digestSz == NULL || + reqSz > sizeof(reqKeyName->name) || tpmSz > sizeof(tpmKeyName->name)) { + return BAD_FUNC_ARG; + } + if (wolfTPM2_CheckSpdmName(reqKeyName) != TPM_RC_SUCCESS || + wolfTPM2_CheckSpdmName(tpmKeyName) != TPM_RC_SUCCESS) { + return BAD_FUNC_ARG; + } + hashSz = TPM2_GetHashDigestSize(hashAlg); + if (hashSz <= 0) { + return BAD_FUNC_ARG; + } + if (*digestSz < (word32)hashSz) { + return BUFFER_E; + } + + rc = TPM_RC_SUCCESS; + if (reqSz > 0 || tpmSz > 0) { + buf[pos++] = (byte)(reqSz >> 8); + buf[pos++] = (byte)reqSz; + if (reqSz > 0) { + XMEMCPY(buf + pos, reqKeyName->name, reqSz); + pos += reqSz; + } + buf[pos++] = (byte)(tpmSz >> 8); + buf[pos++] = (byte)tpmSz; + if (tpmSz > 0) { + XMEMCPY(buf + pos, tpmKeyName->name, tpmSz); + pos += tpmSz; + } + scSz = 0; /* no policyDigestOld for the key name hash */ + rc = wolfTPM2_PolicyHash(hashAlg, sc, &scSz, 0, buf, pos); + } + if (rc == TPM_RC_SUCCESS) { + rc = wolfTPM2_PolicyHash(hashAlg, digest, digestSz, + TPM_CC_PolicyTransportSPDM, sc, scSz); + } + +#ifdef DEBUG_WOLFTPM + if (rc != 0) { + printf("wolfTPM2_PolicyTransportSPDMMake failed %d: %s\n", + rc, wolfTPM2_GetRCString(rc)); + } +#endif + return rc; +} +#endif /* WOLFTPM_SPDM */ + /* Assemble a PCR policy ref - optional */ /* aHash = hash(approvedPolicy || policyRef) */ int wolfTPM2_PolicyRefMake(TPM_ALG_ID pcrAlg, byte* digest, word32* digestSz, diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 5f440a72..aae65ebe 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -10455,6 +10455,294 @@ static void test_fwtpm_policy_locality_enforced(void) printf("Test fwTPM:\tPolicyLocality enforced:\tPassed\n"); } +#ifdef WOLFTPM_SPDM +/* PolicyTransportSPDM(sessHandle, reqKeyName, tpmKeyName) */ +static TPM_RC SendPolicyTransportSPDM(FWTPM_CTX* ctx, UINT32 sessH, + const byte* req, UINT16 reqSz, const byte* tpm, UINT16 tpmSz) +{ + int pos = 0, rspSize = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PolicyTransportSPDM); pos += 4; + PutU32BE(gCmd + pos, sessH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, reqSz); pos += 2; + if (reqSz > 0) { memcpy(gCmd + pos, req, reqSz); pos += reqSz; } + PutU16BE(gCmd + pos, tpmSz); pos += 2; + if (tpmSz > 0) { memcpy(gCmd + pos, tpm, tpmSz); pos += tpmSz; } + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, 0); + return GetRspRC(gRsp); +} + +static void ReadPolicyDigest(FWTPM_CTX* ctx, UINT32 sessH, byte* digest, + UINT16* dSz) +{ + AssertIntEQ(SendPolicyCmd(ctx, TPM_CC_PolicyGetDigest, sessH), + TPM_RC_SUCCESS); + *dSz = GetU16BE(gRsp + TPM2_HEADER_SIZE + 4); + AssertIntEQ(*dSz, 32); + memcpy(digest, gRsp + TPM2_HEADER_SIZE + 6, *dSz); +} + +/* Vectors: SHA-256 of (zeros[32] || 0x000001A1 || scKeyNameHash) where + * scKeyNameHash = SHA-256(reqSz || req || tpmSz || tpm) or absent. */ +static const byte kSpdmDigestNoNames[32] = { + 0xf9,0x63,0xdc,0x07,0x41,0x29,0x97,0x27,0x0c,0xb4,0x3f,0xf9,0x3f,0x56,0xd3,0x58, + 0x61,0xe1,0xc9,0x5c,0x3c,0x5d,0x07,0xc7,0x33,0x9b,0x5c,0xf5,0xbb,0xa1,0x58,0x2d +}; +static const byte kSpdmDigestBothNames[32] = { + 0x95,0x2f,0x41,0x84,0xb8,0x29,0x2a,0x66,0xa4,0x5e,0xb6,0x61,0xb9,0xfd,0xad,0x4c, + 0x6d,0x7e,0x49,0x0a,0xe7,0x4b,0x0b,0x7c,0x0b,0x7f,0x12,0x54,0x1c,0x9d,0x4d,0x95 +}; +static const byte kSpdmDigestReqOnly[32] = { + 0x1b,0x94,0xc1,0xb4,0x82,0x5a,0x35,0xd5,0x08,0x7e,0x75,0xba,0x0e,0xee,0x72,0xf8, + 0xef,0xff,0x32,0xf1,0xc9,0x86,0x0c,0xbf,0xec,0x51,0x84,0x28,0xbd,0xc0,0x56,0x3c +}; + +static void FillSpdmTestName(byte* name, byte fill) +{ + PutU16BE(name, TPM_ALG_SHA256); + memset(name + 2, fill, 32); +} + +static void test_fwtpm_policy_transport_spdm(void) +{ + FWTPM_CTX ctx; + UINT32 sessH; + UINT16 dSz; + int tpos, trspSize; + byte digest[64]; + byte reqName[34]; + byte tpmName[34]; + byte badName[34]; + + FillSpdmTestName(reqName, 0x11); + FillSpdmTestName(tpmName, 0x22); + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* No names: policyDigest = H(0 || CC) */ + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, NULL, 0, NULL, 0), + TPM_RC_SUCCESS); + ReadPolicyDigest(&ctx, sessH, digest, &dSz); + AssertIntEQ(memcmp(digest, kSpdmDigestNoNames, 32), 0); + /* Only once per session */ + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, NULL, 0, NULL, 0), + TPM_RC_VALUE); + /* PolicyRestart clears the binding */ + AssertIntEQ(SendPolicyCmd(&ctx, TPM_CC_PolicyRestart, sessH), + TPM_RC_SUCCESS); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, reqName, 34, tpmName, 34), + TPM_RC_SUCCESS); + ReadPolicyDigest(&ctx, sessH, digest, &dSz); + AssertIntEQ(memcmp(digest, kSpdmDigestBothNames, 32), 0); + FlushHandle(&ctx, sessH); + + /* Requester name only */ + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, reqName, 34, NULL, 0), + TPM_RC_SUCCESS); + ReadPolicyDigest(&ctx, sessH, digest, &dSz); + AssertIntEQ(memcmp(digest, kSpdmDigestReqOnly, 32), 0); + FlushHandle(&ctx, sessH); + + /* Malformed names are rejected and leave the session untouched */ + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, reqName, 1, NULL, 0), + TPM_RC_SIZE); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, reqName, 33, NULL, 0), + TPM_RC_SIZE); + memcpy(badName, reqName, sizeof(badName)); + PutU16BE(badName, 0x1234); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, NULL, 0, badName, 34), + TPM_RC_HASH); + /* Truncated command with no name fields is rejected, not read as empty */ + tpos = 0; + trspSize = 0; + PutU16BE(gCmd + tpos, TPM_ST_SESSIONS); tpos += 2; + PutU32BE(gCmd + tpos, 0); tpos += 4; + PutU32BE(gCmd + tpos, TPM_CC_PolicyTransportSPDM); tpos += 4; + PutU32BE(gCmd + tpos, sessH); tpos += 4; + tpos = AppendPwAuth(gCmd, tpos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)tpos); + FWTPM_ProcessCommand(&ctx, gCmd, tpos, gRsp, &trspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_COMMAND_SIZE); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, NULL, 0, NULL, 0), + TPM_RC_SUCCESS); + FlushHandle(&ctx, sessH); + + /* Trial sessions accept it too */ + sessH = StartSessionHelper(&ctx, TPM_SE_TRIAL); + AssertIntNE(sessH, 0); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, NULL, 0, NULL, 0), + TPM_RC_SUCCESS); + FlushHandle(&ctx, sessH); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("PolicyTransportSPDM:", 0); +} + +/* A policy carrying PolicyTransportSPDM must only authorize commands that + * arrived over an SPDM session; a bound key name must match the session. */ +static void test_fwtpm_policy_transport_spdm_enforced(void) +{ + FWTPM_CTX ctx; + int pos, cmdSz, rspSize = 0; + UINT32 sessH; + UINT16 dSz; + byte digest[64]; + byte reqName[34]; + UINT32 nvIdx = 0x01500081; + UINT32 nvAttrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA; + + FillSpdmTestName(reqName, 0x11); + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, NULL, 0, NULL, 0), + TPM_RC_SUCCESS); + ReadPolicyDigest(&ctx, sessH, digest, &dSz); + + /* Bind that policy to the owner hierarchy */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_SetPrimaryPolicy); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, dSz); pos += 2; + memcpy(gCmd + pos, digest, dSz); pos += dSz; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* Plaintext command: digest matches but no SPDM session */ + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, nvAttrs); + PutU32BE(gCmd + 18, sessH); /* replace TPM_RS_PW with the policy session */ + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_CHANNEL); + + /* Same command marked as arriving inside an SPDM session */ + ctx.activeCmdOverSpdm = 1; + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, nvAttrs); + PutU32BE(gCmd + 18, sessH); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + ctx.activeCmdOverSpdm = 0; + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + FlushHandle(&ctx, sessH); + + /* A requester-key binding cannot be satisfied without a responder + * reporting that key, even inside an SPDM session */ + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + AssertIntEQ(SendPolicyTransportSPDM(&ctx, sessH, reqName, 34, NULL, 0), + TPM_RC_SUCCESS); + ReadPolicyDigest(&ctx, sessH, digest, &dSz); + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_SetPrimaryPolicy); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, dSz); pos += 2; + memcpy(gCmd + pos, digest, dSz); pos += dSz; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + ctx.activeCmdOverSpdm = 1; + cmdSz = BuildNvDefineCmd(gCmd, nvIdx + 1, 8, nvAttrs); + PutU32BE(gCmd + 18, sessH); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + ctx.activeCmdOverSpdm = 0; + AssertIntEQ(GetRspRC(gRsp), TPM_RC_CHANNEL_KEY); + FlushHandle(&ctx, sessH); + + /* Undefine the NV index created over the SPDM session */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_NV_UndefineSpace); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, nvIdx); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tPolicyTransportSPDM enforced:\tPassed\n"); +} + +/* TPM_CAP_SPDM_SESSION_INFO is empty outside an SPDM session and lists one + * entry inside; property must be zero. */ +static void test_fwtpm_spdm_session_info_cap(void) +{ + FWTPM_CTX ctx; + int pos, rspSize = 0; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 22, TPM_CC_GetCapability); + PutU32BE(gCmd + pos, TPM_CAP_SPDM_SESSION_INFO); pos += 4; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, 1); pos += 4; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 1), + TPM_CAP_SPDM_SESSION_INFO); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 5), 0); + + ctx.activeCmdOverSpdm = 1; + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + ctx.activeCmdOverSpdm = 0; + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 5), 1); + /* No responder context: names are reported empty */ + AssertIntEQ(GetU16BE(gRsp + TPM2_HEADER_SIZE + 9), 0); + AssertIntEQ(GetU16BE(gRsp + TPM2_HEADER_SIZE + 11), 0); + + /* propertyCount 0 yields an empty list but moreData=YES when a session + * entry exists and could not be returned */ + PutU32BE(gCmd + TPM2_HEADER_SIZE + 8, 0); + ctx.activeCmdOverSpdm = 1; + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + ctx.activeCmdOverSpdm = 0; + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE], 1); /* moreData = YES */ + AssertIntEQ(GetU32BE(gRsp + TPM2_HEADER_SIZE + 5), 0); + PutU32BE(gCmd + TPM2_HEADER_SIZE + 8, 1); /* restore propertyCount */ + + PutU32BE(gCmd + TPM2_HEADER_SIZE + 4, 1); /* property must be 0 */ + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_VALUE); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("SPDM session info capability:", 0); +} +#endif /* WOLFTPM_SPDM */ + /* PolicyCpHash binds a session to a specific command; a command whose real * cpHash differs must be rejected even when the policyDigest matches. */ static void test_fwtpm_policy_cphash_enforced(void) @@ -15826,6 +16114,11 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_policyauthorizenv_owner_read_denied(); test_fwtpm_policy_locality_enforced(); test_fwtpm_policy_cphash_enforced(); +#ifdef WOLFTPM_SPDM + test_fwtpm_policy_transport_spdm(); + test_fwtpm_policy_transport_spdm_enforced(); + test_fwtpm_spdm_session_info_cap(); +#endif #endif #if !defined(NO_RSA) && defined(WOLFSSL_KEY_GEN) test_fwtpm_admin_authorization_requires_policy(); diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 2eee812a..f725c7b6 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -216,6 +216,10 @@ static void test_wolfTPM2_SpdmModeFromDidVid(void) } #endif +#ifdef WOLFTPM_SPDM +static void test_wolfTPM2_PolicyTransportSPDM_live(WOLFTPM2_DEV* dev); +#endif + #if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_PSK) && \ !defined(NO_GETENV) static void test_wolfTPM2_InitWithSpdmPsk_success(void) @@ -250,11 +254,169 @@ static void test_wolfTPM2_InitWithSpdmPsk_success(void) AssertIntEQ(rc, TPM_RC_SUCCESS); AssertIntEQ(wolfTPM2_SpdmIsConnected(&dev), 1); AssertIntNE(wolfTPM2_SpdmGetSessionId(&dev), 0); + test_wolfTPM2_PolicyTransportSPDM_live(&dev); AssertIntEQ(wolfTPM2_Cleanup(&dev), TPM_RC_SUCCESS); AssertNull(dev.spdmCtx); } #endif +#ifdef WOLFTPM_SPDM +static void test_wolfTPM2_PolicyTransportSPDMMake(void) +{ + /* SHA-256 of (zeros[32] || 0x000001A1 || scKeyNameHash), scKeyNameHash = + * SHA-256(reqSz || req || tpmSz || tpm) or absent when both are empty */ + static const byte noNames[TPM_SHA256_DIGEST_SIZE] = { + 0xf9,0x63,0xdc,0x07,0x41,0x29,0x97,0x27,0x0c,0xb4,0x3f,0xf9,0x3f,0x56,0xd3,0x58, + 0x61,0xe1,0xc9,0x5c,0x3c,0x5d,0x07,0xc7,0x33,0x9b,0x5c,0xf5,0xbb,0xa1,0x58,0x2d + }; + static const byte bothNames[TPM_SHA256_DIGEST_SIZE] = { + 0x95,0x2f,0x41,0x84,0xb8,0x29,0x2a,0x66,0xa4,0x5e,0xb6,0x61,0xb9,0xfd,0xad,0x4c, + 0x6d,0x7e,0x49,0x0a,0xe7,0x4b,0x0b,0x7c,0x0b,0x7f,0x12,0x54,0x1c,0x9d,0x4d,0x95 + }; + static const byte reqOnly[TPM_SHA256_DIGEST_SIZE] = { + 0x1b,0x94,0xc1,0xb4,0x82,0x5a,0x35,0xd5,0x08,0x7e,0x75,0xba,0x0e,0xee,0x72,0xf8, + 0xef,0xff,0x32,0xf1,0xc9,0x86,0x0c,0xbf,0xec,0x51,0x84,0x28,0xbd,0xc0,0x56,0x3c + }; + int rc; + TPM2B_NAME reqName; + TPM2B_NAME tpmName; + byte digest[TPM_SHA256_DIGEST_SIZE]; + word32 digestSz; + + XMEMSET(&reqName, 0, sizeof(reqName)); + XMEMSET(&tpmName, 0, sizeof(tpmName)); + reqName.size = 2 + TPM_SHA256_DIGEST_SIZE; + reqName.name[0] = 0x00; reqName.name[1] = TPM_ALG_SHA256; + XMEMSET(reqName.name + 2, 0x11, TPM_SHA256_DIGEST_SIZE); + tpmName.size = 2 + TPM_SHA256_DIGEST_SIZE; + tpmName.name[0] = 0x00; tpmName.name[1] = TPM_ALG_SHA256; + XMEMSET(tpmName.name + 2, 0x22, TPM_SHA256_DIGEST_SIZE); + + XMEMSET(digest, 0, sizeof(digest)); + digestSz = 0; + rc = wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_SHA256, NULL, NULL, + digest, &digestSz); + AssertIntEQ(rc, BUFFER_E); + + digestSz = (word32)sizeof(digest); + rc = wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_SHA256, NULL, NULL, + NULL, &digestSz); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_SHA256, NULL, NULL, + digest, NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_NULL, NULL, NULL, + digest, &digestSz); + AssertIntEQ(rc, BAD_FUNC_ARG); + + XMEMSET(digest, 0, sizeof(digest)); + digestSz = (word32)sizeof(digest); + rc = wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_SHA256, NULL, NULL, + digest, &digestSz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(digestSz, TPM_SHA256_DIGEST_SIZE); + AssertIntEQ(XMEMCMP(digest, noNames, sizeof(noNames)), 0); + + XMEMSET(digest, 0, sizeof(digest)); + digestSz = (word32)sizeof(digest); + rc = wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_SHA256, &reqName, &tpmName, + digest, &digestSz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(XMEMCMP(digest, bothNames, sizeof(bothNames)), 0); + + XMEMSET(digest, 0, sizeof(digest)); + digestSz = (word32)sizeof(digest); + rc = wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_SHA256, &reqName, NULL, + digest, &digestSz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(XMEMCMP(digest, reqOnly, sizeof(reqOnly)), 0); + + /* Malformed names the TPM would reject must also be rejected offline */ + digestSz = (word32)sizeof(digest); + reqName.size = 1; /* too short for a nameAlg */ + AssertIntEQ(wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_SHA256, &reqName, + NULL, digest, &digestSz), BAD_FUNC_ARG); + reqName.size = 2 + TPM_SHA256_DIGEST_SIZE; + reqName.name[0] = 0x00; reqName.name[1] = 0x99; /* unsupported nameAlg */ + AssertIntEQ(wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_SHA256, &reqName, + NULL, digest, &digestSz), BAD_FUNC_ARG); + reqName.name[1] = TPM_ALG_SHA256; + reqName.size = 2 + TPM_SHA256_DIGEST_SIZE - 1; /* digest length mismatch */ + AssertIntEQ(wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_SHA256, &reqName, + NULL, digest, &digestSz), BAD_FUNC_ARG); + + /* Wrapper NULL handling */ + rc = wolfTPM2_PolicyTransportSPDM(NULL, 0, NULL, NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = wolfTPM2_GetCapability_SPDMSessionInfo(NULL, NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + rc = TPM2_PolicyTransportSPDM(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + + printf("Test TPM Wrapper: %-40s Passed\n", "PolicyTransportSPDM make:"); +} + +/* Inside a live SPDM session the TPM reports the session's key names and + * extends a policy exactly as the offline helper predicts. */ +static void test_wolfTPM2_PolicyTransportSPDM_live(WOLFTPM2_DEV* dev) +{ + int rc; + TPML_SPDM_SESSION_INFO info; + TPMS_SPDM_SESSION_INFO* si; + WOLFTPM2_SESSION session; + byte expected[TPM_MAX_DIGEST_SIZE]; + word32 expectedSz; + byte actual[TPM_MAX_DIGEST_SIZE]; + word32 actualSz; + + AssertIntEQ(wolfTPM2_SpdmIsConnected(dev), 1); + + XMEMSET(&info, 0, sizeof(info)); + rc = wolfTPM2_GetCapability_SPDMSessionInfo(dev, &info); + /* PolicyTransportSPDM and its capability are optional (TPM 2.0 v1.84). + * A connected SPDM TPM that does not implement them is not a failure. */ + if (rc == TPM_RC_VALUE || rc == TPM_RC_COMMAND_CODE) { + printf("Test TPM Wrapper: %-40s Skipped\n", + "PolicyTransportSPDM live:"); + return; + } + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(info.count, 1); + si = &info.spdmSessionInfo[0]; + + XMEMSET(&session, 0, sizeof(session)); + rc = wolfTPM2_StartSession(dev, &session, NULL, NULL, TPM_SE_TRIAL, + TPM_ALG_NULL); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + rc = wolfTPM2_PolicyTransportSPDM(dev, session.handle.hndl, + &si->reqKeyName, &si->tpmKeyName); + if (rc == TPM_RC_COMMAND_CODE) { + printf("Test TPM Wrapper: %-40s Skipped\n", + "PolicyTransportSPDM live:"); + wolfTPM2_UnloadHandle(dev, &session.handle); + return; + } + AssertIntEQ(rc, TPM_RC_SUCCESS); + + actualSz = (word32)sizeof(actual); + rc = wolfTPM2_GetPolicyDigest(dev, session.handle.hndl, actual, + &actualSz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + XMEMSET(expected, 0, sizeof(expected)); + expectedSz = (word32)sizeof(expected); + rc = wolfTPM2_PolicyTransportSPDMMake(session.authHash, &si->reqKeyName, + &si->tpmKeyName, expected, &expectedSz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(actualSz, expectedSz); + AssertIntEQ(XMEMCMP(actual, expected, actualSz), 0); + + wolfTPM2_UnloadHandle(dev, &session.handle); + printf("Test TPM Wrapper: %-40s Passed\n", "PolicyTransportSPDM live:"); +} +#endif /* WOLFTPM_SPDM */ + static void test_wolfTPM2_Init(void) { int rc; @@ -344,6 +506,11 @@ static void test_wolfTPM2_Init(void) /* Test success */ rc = TestWolfTPM2_InitConfigured(&dev, TPM2_IoCb, NULL); AssertIntEQ(rc, 0); +#ifdef WOLFTPM_SPDM + if (wolfTPM2_SpdmIsConnected(&dev)) { + test_wolfTPM2_PolicyTransportSPDM_live(&dev); + } +#endif wolfTPM2_Cleanup(&dev); @@ -3864,6 +4031,53 @@ static void test_TPM2_PolicyAuthorize_DigestVerifiedMetaAlg(void) } #endif /* WOLFTPM_MLDSA_VERIFY */ +#ifdef WOLFTPM_SPDM +/* PR #594 must reject SPDM session-info capability responses truncated in the + * count or either TPM2B_NAME size field. */ +static void test_TPM2_ParseSpdmSessionInfo_Truncated(void) +{ + byte emptySession[] = { + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, + 0x00, 0x00 + }; + TPM2_Packet packet; + TPML_SPDM_SESSION_INFO info; + int rc; + + XMEMSET(&packet, 0, sizeof(packet)); + XMEMSET(&info, 0, sizeof(info)); + packet.buf = emptySession; + packet.size = (int)sizeof(emptySession); + rc = TPM2_ParseSpdmSessionInfo(&packet, &info); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(info.count, 1); + + XMEMSET(&packet, 0, sizeof(packet)); + XMEMSET(&info, 0, sizeof(info)); + packet.buf = emptySession; + packet.size = 5; + rc = TPM2_ParseSpdmSessionInfo(&packet, &info); + AssertIntEQ(rc, TPM_RC_SIZE); + + XMEMSET(&packet, 0, sizeof(packet)); + XMEMSET(&info, 0, sizeof(info)); + packet.buf = emptySession; + packet.size = 3; + rc = TPM2_ParseSpdmSessionInfo(&packet, &info); + AssertIntEQ(rc, TPM_RC_SIZE); + + XMEMSET(&packet, 0, sizeof(packet)); + XMEMSET(&info, 0, sizeof(info)); + packet.buf = emptySession; + packet.size = 7; + rc = TPM2_ParseSpdmSessionInfo(&packet, &info); + AssertIntEQ(rc, TPM_RC_SIZE); + + printf("Test TPM Wrapper:\tSPDM session info truncation:\tPassed\n"); +} +#endif /* WOLFTPM_SPDM */ + /* TPM2_Packet_ParsePoint must resync to outerStart + point->size so a * malformed wire blob with inner x.size / y.size disagreement can't * desynchronize subsequent fields. */ @@ -9136,6 +9350,10 @@ int unit_tests(int argc, char *argv[]) #if defined(WOLFTPM_SPDM) && defined(WOLFTPM_SPDM_TCG) && \ defined(WOLFSPDM_NUVOTON) && defined(WOLFSPDM_NATIONS) test_wolfTPM2_SpdmModeFromDidVid(); +#endif +#ifdef WOLFTPM_SPDM + test_wolfTPM2_PolicyTransportSPDMMake(); + test_TPM2_ParseSpdmSessionInfo_Truncated(); #endif test_wolfTPM2_Init(); test_wolfTPM2_OpenExisting(); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index d25cb1af..c613a7c2 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -620,6 +620,12 @@ typedef struct FWTPM_Session { int nvWrittenState; /* PolicyNvWritten writtenSet */ UINT32 pcrUpdateCounter; /* PCR update counter seen by PolicyPCR */ int hasPcrUpdateCounter; /* 1 once PolicyPCR has been evaluated */ +#ifdef WOLFTPM_SPDM + int checkSecureChannel; /* 1 once PolicyTransportSPDM has been called */ + int checkReqKey; /* PolicyTransportSPDM bound reqKeyName */ + int checkTpmKey; /* PolicyTransportSPDM bound tpmKeyName */ + TPM2B_DIGEST scKeyNameHash; /* PolicyTransportSPDM key name hash */ +#endif } FWTPM_Session; /* NV index slot (user NV RAM) */ @@ -766,6 +772,10 @@ typedef struct FWTPM_CTX { * only when clockless or lockoutRecovery==0) */ #endif int activeLocality; /* locality of the command being processed */ +#ifdef WOLFTPM_SPDM + int activeCmdOverSpdm; /* command being processed arrived inside an + * SPDM secured session */ +#endif #ifndef FWTPM_NO_PP int physicalPresence; /* Platform-channel PP latch (volatile). Only * consulted when no PP HAL is registered. */ diff --git a/wolftpm/spdm/spdm_responder.h b/wolftpm/spdm/spdm_responder.h index e85ff074..ca9223e4 100644 --- a/wolftpm/spdm/spdm_responder.h +++ b/wolftpm/spdm/spdm_responder.h @@ -69,7 +69,9 @@ WOLFTPM_API int wolfSPDM_RespSetPSK(WOLFSPDM_RESP_CTX* ctx, const byte* psk, word32 pskSz, const byte* hint, word32 hintSz); -/* privKey: 48 bytes (P-384 scalar). pubKey: 96 bytes (X||Y, big-endian). */ +/* privKey: 48 bytes (P-384 scalar). pubKey: 96 bytes (X||Y, big-endian). + * Rejected with WOLFSPDM_E_BAD_STATE while a session is negotiating or + * connected; reset the responder first. */ WOLFTPM_API int wolfSPDM_RespSetIdentityKey(WOLFSPDM_RESP_CTX* ctx, const byte* privKey, word32 privSz, const byte* pubKey, word32 pubSz); @@ -91,6 +93,16 @@ WOLFTPM_API void wolfSPDM_RespReset(WOLFSPDM_RESP_CTX* ctx); * Toggled by the requester via SPDMONLY vendor command. */ WOLFTPM_API int wolfSPDM_RespIsLocked(const WOLFSPDM_RESP_CTX* ctx); +/* Returns 1 when a secured SPDM session is established. */ +WOLFTPM_API int wolfSPDM_RespIsSessionActive(const WOLFSPDM_RESP_CTX* ctx); + +/* On success, points idPub at the responder's own SPDM identity key (raw + * P-384 X||Y) and returns its length. Returns 0 when there is no identity + * key or the active session did not authenticate with it (PSK sessions). + * The requester's key is never exposed: no requester mutual-auth is done. */ +WOLFTPM_API word32 wolfSPDM_RespGetIdentityKey(const WOLFSPDM_RESP_CTX* ctx, + const byte** idPub); + #ifdef __cplusplus } #endif diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 84a11b88..f749f3d0 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -275,6 +275,9 @@ typedef enum { TPM_CC_CreateLoaded = 0x00000191, TPM_CC_PolicyAuthorizeNV = 0x00000192, TPM_CC_EncryptDecrypt2 = 0x00000193, +#ifdef WOLFTPM_SPDM + TPM_CC_PolicyTransportSPDM = 0x000001A1, +#endif #ifdef WOLFTPM_PQC /* Post-Quantum Cryptography Commands - TPM 2.0 Library v185 */ TPM_CC_VerifySequenceComplete = 0x000001A3, @@ -286,6 +289,8 @@ typedef enum { TPM_CC_VerifySequenceStart = 0x000001A9, TPM_CC_SignSequenceStart = 0x000001AA, TPM_CC_LAST = TPM_CC_SignSequenceStart, +#elif defined(WOLFTPM_SPDM) + TPM_CC_LAST = TPM_CC_PolicyTransportSPDM, #else TPM_CC_LAST = TPM_CC_EncryptDecrypt2, #endif @@ -414,6 +419,8 @@ typedef enum { TPM_RC_ECC_POINT = RC_FMT1 + 0x027, /* TCG Part 2 Sec.6.6.3 Table 17 -- present since v1.16, not v1.85 */ TPM_RC_PARMS = RC_FMT1 + 0x02A, + TPM_RC_CHANNEL = RC_FMT1 + 0x030, + TPM_RC_CHANNEL_KEY = RC_FMT1 + 0x031, #ifdef WOLFTPM_PQC /* v185 rc4 Part 2 Sec.6.6.3 Table 17 additions */ TPM_RC_EXT_MU = RC_FMT1 + 0x02B, @@ -565,7 +572,8 @@ typedef enum { TPM_CAP_ECC_CURVES = 0x00000008, TPM_CAP_AUTH_POLICIES = 0x00000009, TPM_CAP_ACT = 0x0000000A, -#if defined(WOLFTPM_NATIONS) || defined(WOLFTPM_AUTODETECT) +#if defined(WOLFTPM_NATIONS) || defined(WOLFTPM_AUTODETECT) || \ + defined(WOLFTPM_SPDM) TPM_CAP_PUB_KEYS = 0x0000000B, /* TPM 184: SPDM identity keys */ TPM_CAP_SPDM_SESSION_INFO = 0x0000000C, /* TPM 184: SPDM session info */ TPM_CAP_LAST = TPM_CAP_SPDM_SESSION_INFO, @@ -1301,6 +1309,29 @@ typedef struct TPML_ACT_DATA { TPMS_ACT_DATA actData[MAX_ACT_DATA]; } TPML_ACT_DATA; +#ifdef WOLFTPM_SPDM +/* SPDM session information (TPM 2.0 Library v1.84) */ +typedef struct TPMS_SPDM_SESSION_INFO { + TPM2B_NAME reqKeyName; + TPM2B_NAME tpmKeyName; +} TPMS_SPDM_SESSION_INFO; + +/* Spec-derived list capacity (TPM 2.0 Library v1.84): as many entries as fit + * in the capability buffer. MAX_SPDM_SESS_INFO is kept as a compatibility + * alias for the spec name MAX_SPDM_SESSION_INFO. */ +#ifndef MAX_SPDM_SESSION_INFO +#define MAX_SPDM_SESSION_INFO \ + (MAX_CAP_DATA / (UINT32)sizeof(TPMS_SPDM_SESSION_INFO)) +#endif +#ifndef MAX_SPDM_SESS_INFO +#define MAX_SPDM_SESS_INFO MAX_SPDM_SESSION_INFO +#endif +typedef struct TPML_SPDM_SESSION_INFO { + UINT32 count; + TPMS_SPDM_SESSION_INFO spdmSessionInfo[MAX_SPDM_SESS_INFO]; +} TPML_SPDM_SESSION_INFO; +#endif + /* Capabilities Structures */ typedef union TPMU_CAPABILITIES { @@ -1315,6 +1346,9 @@ typedef union TPMU_CAPABILITIES { TPML_ECC_CURVE eccCurves; /* TPM_CAP_ECC_CURVES */ TPML_TAGGED_POLICY authPolicies; /* TPM_CAP_AUTH_POLICIES */ TPML_ACT_DATA actData; /* TPM_CAP_ACT - added v1.57 */ +#ifdef WOLFTPM_SPDM + TPML_SPDM_SESSION_INFO spdmSessionInfo; /* TPM_CAP_SPDM_SESSION_INFO - v1.84 */ +#endif TPM2B_MAX_BUFFER vendor; } TPMU_CAPABILITIES; @@ -3027,6 +3061,15 @@ typedef struct { } PolicyPCR_In; WOLFTPM_API TPM_RC TPM2_PolicyPCR(PolicyPCR_In* in); +#ifdef WOLFTPM_SPDM +typedef struct { + TPMI_SH_POLICY policySession; + TPM2B_NAME reqKeyName; + TPM2B_NAME tpmKeyName; +} PolicyTransportSPDM_In; +WOLFTPM_API TPM_RC TPM2_PolicyTransportSPDM(PolicyTransportSPDM_In* in); +#endif + typedef struct { TPMI_SH_POLICY policySession; TPMA_LOCALITY locality; diff --git a/wolftpm/tpm2_packet.h b/wolftpm/tpm2_packet.h index e78caaf0..0fb1dd2f 100644 --- a/wolftpm/tpm2_packet.h +++ b/wolftpm/tpm2_packet.h @@ -162,6 +162,10 @@ WOLFTPM_LOCAL void TPM2_Packet_ParseU64(TPM2_Packet* packet, UINT64* data); WOLFTPM_LOCAL void TPM2_Packet_AppendS32(TPM2_Packet* packet, INT32 data); WOLFTPM_LOCAL void TPM2_Packet_AppendBytes(TPM2_Packet* packet, byte* buf, int size); WOLFTPM_LOCAL void TPM2_Packet_ParseBytes(TPM2_Packet* packet, byte* buf, int size); +#ifdef WOLFTPM_SPDM +WOLFTPM_TEST_API int TPM2_ParseSpdmSessionInfo(TPM2_Packet* packet, + TPML_SPDM_SESSION_INFO* sessInfo); +#endif /* WOLFTPM_SPDM */ /*! \brief Parse a UINT16-prefixed buffer from a TPM2 packet. Reads a 16-bit size followed by that many bytes into buf, clamped to maxBufSz. diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 81bfd711..a3166cf3 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -650,6 +650,27 @@ WOLFTPM_API int wolfTPM2_SpdmIsConnected(WOLFTPM2_DEV* dev); */ WOLFTPM_API word32 wolfTPM2_SpdmGetSessionId(WOLFTPM2_DEV* dev); +/*! + \ingroup wolfTPM2_Wrappers + \brief Read the TPM's view of the active SPDM session + (GetCapability TPM_CAP_SPDM_SESSION_INFO, TPM 2.0 Library v1.84). + \note The TPM returns an empty list unless the command itself arrived + inside an SPDM session. The names returned are the ones a + PolicyTransportSPDM policy can bind to. + + \return TPM_RC_SUCCESS: successful + \return TPM_RC_VALUE: the TPM does not implement the capability + \return BAD_FUNC_ARG: check the provided arguments + + \param dev pointer to a WOLFTPM2_DEV structure + \param spdmSessionInfo output list of requester / TPM key names + + \sa wolfTPM2_PolicyTransportSPDM + \sa wolfTPM2_PolicyTransportSPDMMake +*/ +WOLFTPM_API int wolfTPM2_GetCapability_SPDMSessionInfo(WOLFTPM2_DEV* dev, + TPML_SPDM_SESSION_INFO* spdmSessionInfo); + /*! \ingroup wolfTPM2_Wrappers \brief Disconnect the SPDM secure session. @@ -4999,6 +5020,36 @@ WOLFTPM_API int wolfTPM2_GetPolicyDigest(WOLFTPM2_DEV* dev, TPM_HANDLE sessionHa WOLFTPM_API int wolfTPM2_PolicyPCR(WOLFTPM2_DEV* dev, TPM_HANDLE sessionHandle, TPM_ALG_ID pcrAlg, byte* pcrArray, word32 pcrArraySz); +#ifdef WOLFTPM_SPDM +/*! + \ingroup wolfTPM2_Wrappers + + \brief Add TPM2_PolicyTransportSPDM (TPM 2.0 Library v1.84) to a policy + session. An entity whose authPolicy contains this term can only be + authorized by a command that arrives inside an SPDM session. Optional + key names further restrict which SPDM session qualifies. + + \return TPM_RC_SUCCESS: successful + \return TPM_RC_VALUE: PolicyTransportSPDM already applied to this session + \return TPM_RC_HASH: a name uses an unsupported hash algorithm + \return TPM_RC_SIZE: a name is not the size its hash algorithm requires + \return BUFFER_E: a name is larger than TPM2B_NAME can hold + \return BAD_FUNC_ARG: check the provided arguments + + \param dev pointer to a TPM2_DEV struct + \param sessionHandle the handle of the current policy session + \param reqKeyName requester SPDM key name to bind, or NULL for any + \param tpmKeyName TPM SPDM key name to bind, or NULL for any + + \sa wolfTPM2_PolicyTransportSPDMMake + \sa wolfTPM2_GetCapability_SPDMSessionInfo + \sa wolfTPM2_GetPolicyDigest +*/ +WOLFTPM_API int wolfTPM2_PolicyTransportSPDM(WOLFTPM2_DEV* dev, + TPM_HANDLE sessionHandle, const TPM2B_NAME* reqKeyName, + const TPM2B_NAME* tpmKeyName); +#endif + /*! \ingroup wolfTPM2_Wrappers @@ -5104,6 +5155,35 @@ WOLFTPM_API int wolfTPM2_PolicyPCRMake(TPM_ALG_ID pcrAlg, byte* pcrArray, word32 pcrArraySz, const byte* pcrDigest, word32 pcrDigestSz, byte* digest, word32* digestSz); +#ifdef WOLFTPM_SPDM +/*! + \ingroup wolfTPM2_Wrappers + + \brief Compute the policy digest that TPM2_PolicyTransportSPDM produces, + without a TPM. Use it to build the authPolicy of an NV index or key that + must only be reachable over an SPDM session. + + \return TPM_RC_SUCCESS: successful + \return BUFFER_E: digestSz is too small for the selected hash + \return BAD_FUNC_ARG: check the provided arguments + + \param hashAlg the policy session hash algorithm + \param reqKeyName requester SPDM key name to bind, or NULL for any + \param tpmKeyName TPM SPDM key name to bind, or NULL for any + \param digest input/output: policyDigestOld in (zeros for a fresh + session), policyDigestNew out + \param digestSz input/output: current digest size and buffer capacity on + input, selected hash size on output + + \sa wolfTPM2_PolicyTransportSPDM + \sa wolfTPM2_PolicyHash + \sa wolfTPM2_NVCreateAuthPolicy +*/ +WOLFTPM_API int wolfTPM2_PolicyTransportSPDMMake(TPM_ALG_ID hashAlg, + const TPM2B_NAME* reqKeyName, const TPM2B_NAME* tpmKeyName, + byte* digest, word32* digestSz); +#endif + /*! \ingroup wolfTPM2_Wrappers