Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
client wrappers, DA/noDA/lockout/self-heal/persistence unit tests, an
`examples/management/da_check` end-to-end example, and the
`tests/fwtpm_da_retry.sh` CI harness.
* Added optional transparent `TPM_RC_RETRY` handling so commands can be
automatically resubmitted when the TPM reports it is momentarily busy (for
example persisting the daUsed flag on first auth use of an externally
provisioned non-noDA AIK/SUDI key). Disabled by default to preserve the raw
TPM response code; opt in via `TPM2_SetCommandRetries` at runtime or
`-DWOLFTPM_MAX_RETRIES=N` at build time. Define `WOLFTPM_NO_RETRY` to compile
the handling out entirely. wolfTPM's own key templates set `noDA` and never
trigger it.

## wolfTPM Release 4.0.0 (Apr 22, 2026)

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,8 @@ WOLFTPM2_USE_SW_ECDHE Disables use of TPM for ECC ephemeral key generation and
WOLFTPM2_ECC_DEFAULT_CURVE Default ECC curve for wrapper key templates that request P256 (SRK/AIK/general ECC). Defaults to TPM_ECC_NIST_P256, or the smallest enabled curve meeting ECC_MIN_KEY_SZ. Override e.g. -DWOLFTPM2_ECC_DEFAULT_CURVE=TPM_ECC_NIST_P384.
TLS_BENCH_MODE Enables TLS benchmarking mode.
NO_TPM_BENCH Disables the TPM benchmarking example.
WOLFTPM_MAX_RETRIES Default number of times a command is transparently resubmitted when the TPM returns TPM_RC_RETRY (momentarily busy, e.g. persisting the daUsed flag on first auth use of an externally provisioned non-noDA AIK/SUDI key). Disabled by default (0); opt in with TPM2_SetCommandRetries() at runtime or -DWOLFTPM_MAX_RETRIES=N at build time. wolfTPM's own key templates set noDA and never trigger it.
WOLFTPM_NO_RETRY Compiles out the TPM_RC_RETRY auto-resubmit handling entirely; TPM_RC_RETRY is returned to the caller for manual handling.
```

Note: For the I2C support on Raspberry Pi you may need to enable I2C. Here are the steps:
Expand Down
162 changes: 126 additions & 36 deletions src/tpm2.c
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,13 @@ static TPM_RC TPM2_SPDM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet)
packet->buf, packet->pos, tpmResp, &tpmRespSz);
if (rc != 0) {
TPM2_ForceZero(tpmResp, sizeof(tpmResp));
return rc;
#ifdef DEBUG_WOLFTPM
printf("SPDM secured exchange failed: %d\n", rc);
#endif
/* SPDM is active: never downgrade to cleartext. Map the (negative)
* transport error to a positive TPM RC so the caller treats it as a
* hard failure instead of "SPDM not active". */
return TPM_RC_FAILURE;
}

if (tpmRespSz > MAX_RESPONSE_SIZE) {
Expand All @@ -467,6 +473,87 @@ static TPM_RC TPM2_SPDM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet)
}
#endif /* WOLFTPM_SPDM */

/* Send the finalized command in packet over the SPDM secured channel when a
* session is active, otherwise over the raw transport. Once SPDM is active an
* exchange failure is returned as-is and never falls back to a cleartext send,
* so a forced SPDM failure cannot downgrade the link. */
static TPM_RC TPM2_DispatchCommand(TPM2_CTX* ctx, TPM2_Packet* packet)
{
TPM_RC rc;

#ifdef WOLFTPM_SPDM
rc = TPM2_SPDM_SendCommand(ctx, packet);
if (rc >= 0)
return rc; /* SPDM active: success or hard failure, no cleartext */
/* rc < 0: SPDM not active, use normal transport */
#endif
rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet);

return rc;
}

#ifdef WOLFTPM_NO_RETRY
/* Submit the finalized command in packet (length cmdSz) and parse the
* response, returning the TPM response code. */
static TPM_RC TPM2_TransmitCommand(TPM2_CTX* ctx, TPM2_Packet* packet,
UINT32 cmdSz)
{
TPM_RC rc;

/* send command requires packet->pos to be the total command length */
packet->pos = cmdSz;

rc = TPM2_DispatchCommand(ctx, packet);
if (rc != 0)
return rc; /* transport or SPDM error */

/* parse response header and extract the TPM response code */
rc = TPM2_Packet_Parse(rc, packet);

return rc;
}
#else
/* Submit the finalized command in packet (length cmdSz) and parse the
Comment thread
dgarske marked this conversation as resolved.
* response, returning the TPM response code. On TPM_RC_RETRY the TPM is
* momentarily busy (e.g. persisting the daUsed flag on first auth use of a
* non-noDA key) and asks for the identical command to be resubmitted; the
* error response is header-only, so restoring the command header and resending
* up to ctx->retries times recovers transparently. */
static TPM_RC TPM2_TransmitCommand(TPM2_CTX* ctx, TPM2_Packet* packet,
UINT32 cmdSz)
{
TPM_RC rc;
byte cmdHdr[TPM2_HEADER_SIZE];
int origSize = packet->size;
int retries = ctx->retries;

XMEMCPY(cmdHdr, packet->buf, TPM2_HEADER_SIZE);

for (;;) {
/* send command requires packet->pos to be the total command length */
packet->pos = cmdSz;

rc = TPM2_DispatchCommand(ctx, packet);
if (rc != 0)
return rc; /* transport or SPDM error */

/* parse response header and extract the TPM response code */
rc = TPM2_Packet_Parse(rc, packet);

if (TPM2_Packet_RetryRestore(rc, &retries, packet, cmdHdr, origSize)) {
#ifdef DEBUG_WOLFTPM
printf("TPM_RC_RETRY: resubmitting command, %d retries left\n",
retries);
#endif
continue;
}
break;
}

return rc;
}
#endif /* WOLFTPM_NO_RETRY */

static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet,
CmdInfo_t* info)
{
Expand Down Expand Up @@ -507,26 +594,8 @@ static TPM_RC TPM2_SendCommandAuth(TPM2_CTX* ctx, TPM2_Packet* packet,
return rc;
}

/* reset packet->pos to total command length (send command requires it) */
packet->pos = cmdSz;

/* submit command and wait for response */
#ifdef WOLFTPM_SPDM
rc = TPM2_SPDM_SendCommand(ctx, packet);
if (rc >= 0) {
if (rc != TPM_RC_SUCCESS)
return rc; /* SPDM active but failed, do not retry cleartext */
}
else /* rc < 0: SPDM not active, use normal transport */
#endif
{
rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet);
}
if (rc != 0)
return rc;

/* parse response */
rc = TPM2_Packet_Parse(rc, packet);
/* submit command and parse the response */
rc = TPM2_TransmitCommand(ctx, packet, cmdSz);
respSz = packet->size;

/* restart the unmarshalling position */
Expand Down Expand Up @@ -559,22 +628,10 @@ static TPM_RC TPM2_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet)
if (ctx == NULL || packet == NULL)
return BAD_FUNC_ARG;

#ifdef WOLFTPM_SPDM
rc = TPM2_SPDM_SendCommand(ctx, packet);
if (rc >= 0) {
if (rc != TPM_RC_SUCCESS)
return rc; /* SPDM active but failed, do not retry cleartext */
return TPM2_Packet_Parse(rc, packet);
}
/* rc < 0 means SPDM not active, fall through to normal transport */
#endif
/* submit command and parse the response */
rc = TPM2_TransmitCommand(ctx, packet, (UINT32)packet->pos);

/* submit command and wait for response */
rc = (TPM_RC)INTERNAL_SEND_COMMAND(ctx, packet);
if (rc != 0)
return rc;

return TPM2_Packet_Parse(rc, packet);
return rc;
}

#ifndef WOLFTPM2_NO_WOLFCRYPT
Expand Down Expand Up @@ -711,6 +768,35 @@ TPM_RC TPM2_SetHalIoCb(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx)
return rc;
}

#ifndef WOLFTPM_NO_RETRY
TPM_RC TPM2_SetCommandRetries(TPM2_CTX* ctx, int retries)
{
TPM_RC rc;

if (ctx == NULL || retries < 0) {
return BAD_FUNC_ARG;
}

rc = TPM2_AcquireLock(ctx);
if (rc == TPM_RC_SUCCESS) {
ctx->retries = retries;

TPM2_ReleaseLock(ctx);
}

return rc;
}

int TPM2_GetCommandRetries(TPM2_CTX* ctx)
{
if (ctx == NULL) {
return BAD_FUNC_ARG;
}
/* atomic int read, no lock needed; the setter takes the lock */
return ctx->retries;
}
#endif /* !WOLFTPM_NO_RETRY */

/* If timeoutTries <= 0 then it will not try and startup chip and will
* use existing default locality */
TPM_RC TPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx,
Expand All @@ -724,6 +810,10 @@ TPM_RC TPM2_Init_ex(TPM2_CTX* ctx, TPM2HalIoCb ioCb, void* userCtx,

XMEMSET(ctx, 0, sizeof(TPM2_CTX));

#ifndef WOLFTPM_NO_RETRY
ctx->retries = WOLFTPM_MAX_RETRIES;
#endif

#ifndef WOLFTPM2_NO_WOLFCRYPT
rc = TPM2_WolfCrypt_Init();
if (rc != 0)
Expand Down
17 changes: 17 additions & 0 deletions src/tpm2_packet.c
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,23 @@ TPM_RC TPM2_Packet_Parse(TPM_RC rc, TPM2_Packet* packet)
return rc;
}

#ifndef WOLFTPM_NO_RETRY
int TPM2_Packet_RetryRestore(TPM_RC rc, int* retries, TPM2_Packet* packet,
Comment thread
dgarske marked this conversation as resolved.
const byte* cmdHdr, int origSize)
{
if (rc != TPM_RC_RETRY || retries == NULL || *retries <= 0 ||
packet == NULL || packet->buf == NULL || cmdHdr == NULL) {
return 0;
}
(*retries)--;
/* A TPM_RC_RETRY response is header-only, so the command body survives;
* restore the clobbered header and the buffer size for an identical resend */
XMEMCPY(packet->buf, cmdHdr, TPM2_HEADER_SIZE);
packet->size = origSize;
return 1;
}
#endif /* !WOLFTPM_NO_RETRY */

int TPM2_Packet_Finalize(TPM2_Packet* packet, TPM_ST tag, TPM_CC cc)
{
word32 cmdSz = packet->pos; /* get total packet size */
Expand Down
92 changes: 92 additions & 0 deletions tests/unit_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -2515,6 +2515,94 @@ static void test_wolfTPM2_VerifyHashTicket_DigestSize(void)
#endif
}

#ifndef WOLFTPM_NO_RETRY
/* Transparent TPM_RC_RETRY resubmit is configurable. Verify the default seeded
* by init, the setter/getter round-trip, and rejection of bad arguments. */
static void test_TPM2_CommandRetries(void)
{
int rc;
WOLFTPM2_DEV dev;

XMEMSET(&dev, 0, sizeof(dev));

rc = TPM2_SetCommandRetries(NULL, 1);
AssertIntEQ(rc, BAD_FUNC_ARG);
rc = TPM2_GetCommandRetries(NULL);
AssertIntEQ(rc, BAD_FUNC_ARG);
rc = TPM2_SetCommandRetries(&dev.ctx, -1);
AssertIntEQ(rc, BAD_FUNC_ARG);

/* retries is seeded before any HAL IO setup, so the default holds even on
* builds without a default IO callback where init returns an error */
(void)TPM2_Init_minimal(&dev.ctx);
AssertIntEQ(TPM2_GetCommandRetries(&dev.ctx), WOLFTPM_MAX_RETRIES);

rc = TPM2_SetCommandRetries(&dev.ctx, 0);
AssertIntEQ(rc, TPM_RC_SUCCESS);
AssertIntEQ(TPM2_GetCommandRetries(&dev.ctx), 0);

rc = TPM2_SetCommandRetries(&dev.ctx, 7);
AssertIntEQ(rc, TPM_RC_SUCCESS);
AssertIntEQ(TPM2_GetCommandRetries(&dev.ctx), 7);

TPM2_Cleanup(&dev.ctx);

printf("Test TPM Wrapper:\tCommandRetries config:\t\tPassed\n");
}

/* Exercise the TPM_RC_RETRY resubmit bookkeeping directly: header/size restore,
* command-body preservation, retry-count decrement, and surfacing TPM_RC_RETRY
* once the count is exhausted - the parts of the loop most likely to regress. */
static void test_TPM2_Packet_RetryRestore(void)
{
TPM2_Packet packet;
byte buf[TPM2_HEADER_SIZE + 4];
byte hdr[TPM2_HEADER_SIZE];
int retries;

XMEMSET(hdr, 0xAA, sizeof(hdr));
XMEMSET(buf, 0x00, sizeof(buf));
buf[TPM2_HEADER_SIZE] = 0xBB; /* command body byte, must survive a resend */
packet.buf = buf;
packet.pos = 0;
packet.size = TPM2_HEADER_SIZE; /* shrunk by a prior TPM2_Packet_Parse */

/* A non-retry response must not resubmit and must leave state untouched */
retries = 2;
AssertIntEQ(TPM2_Packet_RetryRestore(TPM_RC_SUCCESS, &retries, &packet,
hdr, (int)sizeof(buf)), 0);
AssertIntEQ(retries, 2);
AssertIntEQ(packet.size, TPM2_HEADER_SIZE);

/* RETRY with budget: resubmit, header + size restored, body preserved */
AssertIntEQ(TPM2_Packet_RetryRestore(TPM_RC_RETRY, &retries, &packet,
hdr, (int)sizeof(buf)), 1);
AssertIntEQ(retries, 1);
AssertIntEQ(packet.size, (int)sizeof(buf));
AssertIntEQ(buf[0], 0xAA);
AssertIntEQ(buf[TPM2_HEADER_SIZE], 0xBB);

/* Second RETRY exhausts the budget */
AssertIntEQ(TPM2_Packet_RetryRestore(TPM_RC_RETRY, &retries, &packet,
hdr, (int)sizeof(buf)), 1);
AssertIntEQ(retries, 0);

/* Budget exhausted: RETRY is surfaced to the caller, no resubmit */
AssertIntEQ(TPM2_Packet_RetryRestore(TPM_RC_RETRY, &retries, &packet,
hdr, (int)sizeof(buf)), 0);
AssertIntEQ(retries, 0);

/* NULL guards */
retries = 1;
AssertIntEQ(TPM2_Packet_RetryRestore(TPM_RC_RETRY, NULL, &packet,
hdr, (int)sizeof(buf)), 0);
AssertIntEQ(TPM2_Packet_RetryRestore(TPM_RC_RETRY, &retries, NULL,
hdr, (int)sizeof(buf)), 0);

printf("Test TPM Wrapper:\tRetryRestore logic:\t\tPassed\n");
}
#endif /* !WOLFTPM_NO_RETRY */

/* wolfTPM2_NVCreateAuthPolicy must derive nameAlg from authPolicySz so
* the policy digest hash matches the index's nameAlg. Bug-mode hardcoded
* SHA-256 nameAlg, which made SHA-384/SHA-512 policies unsatisfiable.
Expand Down Expand Up @@ -5699,6 +5787,10 @@ int unit_tests(int argc, char *argv[])
test_wolfTPM2_GetKeyTemplate_ex_nameAlg();
test_wolfTPM2_SignHashScheme_DigestSize();
test_wolfTPM2_VerifyHashTicket_DigestSize();
#ifndef WOLFTPM_NO_RETRY
test_TPM2_CommandRetries();
test_TPM2_Packet_RetryRestore();
#endif
test_wolfTPM2_NVCreateAuthPolicy_NameAlg();
test_wolfTPM2_GetKeyTemplate_KeyedHash_Scheme();
test_wolfTPM2_LoadEccPublicKey_Ex();
Expand Down
Loading
Loading