mirror of https://github.com/wolfSSL/wolfTPM.git
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
parent
ecf89639a8
commit
64f2db9fcb
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
Loading…
Reference in New Issue