Add SPDM-bound NV policy examples

Add an NV binding example and extend the SPDM control flow to exercise PSK- and certificate-authenticated policy sessions. Document and automate the end-to-end policy checks.
pull/594/head
Aidan Garske 2026-09-08 11:10:44 -07:00
parent ecf89639a8
commit 64f2db9fcb
5 changed files with 643 additions and 22 deletions

View File

@ -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 <hex>` | 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 <hex>` | Start a PSK session |
| `--psk-set <psk> <clearauth>` | Provision a 64-byte PSK and 32-byte ClearAuth |
| `--psk-clear <clearauth>` | 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 <psk> --clear &
./examples/spdm/nv_bind --psk <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

View File

@ -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

View File

@ -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 <psk> --clear &
* ./examples/spdm/nv_bind --psk <psk>
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <wolftpm/tpm2.h>
#include <wolftpm/tpm2_wrap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef WOLFTPM2_NO_WRAPPER
#include <hal/tpm_io.h>
#include <examples/tpm_test.h>
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 <hex-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 <hex> 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

View File

@ -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);

View File

@ -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 \