Enforce SPDM transport policies in the firmware TPM

Implement PolicyTransportSPDM and the SPDM session-info capability in the fwTPM. Track authenticated responder key names, fail closed on malformed state, and add regression coverage for policy enforcement.
pull/594/head
Aidan Garske 2026-09-08 11:10:18 -07:00
parent b064b409ae
commit ecf89639a8
7 changed files with 718 additions and 1 deletions

View File

@ -38,6 +38,16 @@
#include <wolftpm/fwtpm/fwtpm_command.h>
#include <wolftpm/fwtpm/fwtpm_nv.h>
#include <wolftpm/fwtpm/fwtpm_crypto.h>
/* 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 <wolftpm/spdm/spdm_responder.h>
#endif
#include <limits.h>
#include <stdio.h>
@ -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, &paramSzPos);
@ -1893,6 +2082,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;
@ -10725,6 +10938,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);
}
@ -11833,6 +12052,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).
@ -18563,6 +18887,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 },
@ -19519,6 +19846,33 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx,
TPM2_ForceZero(cmdAuths, sizeof(cmdAuths));
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). */

View File

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

View File

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

View File

@ -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. */
@ -191,6 +193,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);
@ -223,6 +230,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) {
@ -248,6 +275,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
@ -537,6 +566,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));
@ -768,6 +798,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));
@ -827,6 +858,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));
@ -885,6 +917,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));
@ -1124,14 +1157,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;

View File

@ -10460,6 +10460,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)
@ -15837,6 +16125,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();

View File

@ -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. */

View File

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