fwTPM PQC: finish v1.85 protocol wiring for Sign/Verify over mssim

Server-side handler fixes:
   - FwCmd_SignSequenceStart / VerifySequenceStart: call FwSkipAuthArea when
     cmdTag == TPM_ST_SESSIONS. Without it, the 4-byte authAreaSize prefix
     was mis-parsed as the auth / context TPM2B size fields, producing
     mis-aligned context bytes (ctxSz=9 on sign, 0 on verify) so the μ fed
     into FIPS 204 differed between the two handlers — verify always failed.
   - FwCmd_SignSequenceStart / VerifySequenceStart: emit the output
     sequenceHandle before FwRspParamsBegin, matching TPM 2.0 response
     framing (handles precede the SESSIONS paramSize).
   - FwCmd_CreatePrimary: add MLDSA / HASH_MLDSA / MLKEM arms to the
     hashUnique switch so the unique template actually binds into KDFa
     derivation.
   - FwCmd_TestParms: accept PQC algs (MLKEM / MLDSA / HASH_MLDSA).

   Client-side wrapper fixes:
   - wolfTPM2_CopyPubT: add MLDSA / HASH_MLDSA / MLKEM cases. Previous
     switch fell through, leaving unique.mlkem / .mldsa as zero-filled
     buffers after a successful CreatePrimary (Jay's reported bug).
   - GetKeyTemplateSize: add PQC parameter-set-aware sizes.
   - wolfTPM2_SetKeyTemplate_Unique: add PQC arms.
   - TPM2_SignSequenceComplete: add CMD_FLAG_AUTH_USER2 (Table 124 requires
     USER auth on both @seq and @key handles).
   - TPM2_VerifySequenceComplete: remove extra buffer field (Table 118 has
     no buffer parameter); add CMD_FLAG_AUTH_USER1.

   examples/pqc/pqc_mssim_e2e.c: tighten validation — check_pub_populated
   catches CopyPubT-class regressions, MLKEM-768 Encap/Decap secrets must
   match, HashMLDSA-65 SignDigest emits a DIGEST_VERIFIED ticket.
pull/445/head
Aidan Garske 2026-04-22 09:06:05 -07:00
parent 552032d357
commit 4ead816713
4 changed files with 219 additions and 7 deletions

View File

@ -36,6 +36,25 @@
#if !defined(WOLFTPM2_NO_WRAPPER) && defined(WOLFTPM_V185)
/* Guard against the CopyPubT-class bug where the server-side key exists
* and the handle works, but the client-side TPM2B buffer is zero-filled
* (Part 2 Table 225 unique arm never copied). */
static int check_pub_populated(const char* label, const byte* buf,
UINT16 gotSize, UINT16 wantSize)
{
int i;
if (gotSize != wantSize) {
printf("%s.size = %u (expected %u)\n", label, gotSize, wantSize);
return -1;
}
for (i = 0; i < wantSize; i++) {
if (buf[i] != 0) return 0;
}
printf("%s.buffer is all zero (client-side unique-arm copy dropped)\n",
label);
return -1;
}
static int test_mlkem_roundtrip(WOLFTPM2_DEV* dev)
{
WOLFTPM2_KEY mlkem;
@ -64,6 +83,11 @@ static int test_mlkem_roundtrip(WOLFTPM2_DEV* dev)
return rc;
}
rc = check_pub_populated("mlkem.unique",
mlkem.pub.publicArea.unique.mlkem.buffer,
mlkem.pub.publicArea.unique.mlkem.size, 1184);
if (rc != 0) goto cleanup;
rc = wolfTPM2_Encapsulate(dev, &mlkem, ct, &ctSz, ss1, &ss1Sz);
if (rc != 0) {
printf("Encapsulate rc=%d\n", rc);
@ -126,6 +150,12 @@ static int test_hash_mldsa_digest_roundtrip(WOLFTPM2_DEV* dev)
return rc;
}
/* HashMLDSA shares the mldsa arm of TPMU_PUBLIC_ID. */
rc = check_pub_populated("mldsa.unique",
mldsa.pub.publicArea.unique.mldsa.buffer,
mldsa.pub.publicArea.unique.mldsa.size, 1952);
if (rc != 0) goto cleanup;
rc = wolfTPM2_SignDigest(dev, &mldsa,
digest, sizeof(digest),
NULL, 0,

View File

@ -1312,6 +1312,11 @@ static TPM_RC FwCmd_TestParms(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize,
#endif
case TPM_ALG_HMAC:
case TPM_ALG_NULL:
#ifdef WOLFTPM_V185
case TPM_ALG_MLDSA:
case TPM_ALG_HASH_MLDSA:
case TPM_ALG_MLKEM:
#endif
/* Supported - skip remaining type-specific params */
break;
default:
@ -2289,6 +2294,24 @@ static TPM_RC FwCmd_CreatePrimary(FWTPM_CTX* ctx, TPM2_Packet* cmd,
uBuf = inPublic->publicArea.unique.sym.buffer;
uSz = (int)inPublic->publicArea.unique.sym.size;
break;
#ifdef WOLFTPM_V185
/* MLDSA / HASH_MLDSA / MLKEM: only feed user-supplied unique
* bytes into hashUnique, not the raw buffer. A size==0 arm
* must not read the uninitialized buffer pointer. */
case TPM_ALG_MLDSA:
case TPM_ALG_HASH_MLDSA:
if (inPublic->publicArea.unique.mldsa.size > 0) {
uBuf = inPublic->publicArea.unique.mldsa.buffer;
uSz = (int)inPublic->publicArea.unique.mldsa.size;
}
break;
case TPM_ALG_MLKEM:
if (inPublic->publicArea.unique.mlkem.size > 0) {
uBuf = inPublic->publicArea.unique.mlkem.buffer;
uSz = (int)inPublic->publicArea.unique.mlkem.size;
}
break;
#endif /* WOLFTPM_V185 */
default:
break;
}
@ -13022,6 +13045,15 @@ static TPM_RC FwCmd_SignSequenceStart(FWTPM_CTX* ctx, TPM2_Packet* cmd,
}
}
/* Skip auth area when the client used ST_SESSIONS. SignSequenceStart
* has no mandatory auth (Table 89 Auth Index: None) but clients may
* still emit a password session. Without skipping, the 4-byte
* authAreaSize prefix gets mis-parsed as the TPM2B_AUTH and
* TPM2B_SIGNATURE_CTX size fields. */
if (rc == 0 && cmdTag == TPM_ST_SESSIONS) {
rc = FwSkipAuthArea(cmd, cmdSize);
}
/* Parse auth (TPM2B_AUTH) */
if (rc == 0) {
TPM2_Packet_ParseU16(cmd, &authSz);
@ -13067,8 +13099,11 @@ static TPM_RC FwCmd_SignSequenceStart(FWTPM_CTX* ctx, TPM2_Packet* cmd,
}
if (rc == 0) {
paramStart = FwRspParamsBegin(rsp, cmdTag, &paramSzPos);
/* sequenceHandle is an output handle per Table 89 — must be
* emitted BEFORE the parameterSize field, not inside the
* parameter area. */
TPM2_Packet_AppendU32(rsp, seqHandle);
paramStart = FwRspParamsBegin(rsp, cmdTag, &paramSzPos);
FwRspParamsEnd(rsp, cmdTag, paramSzPos, paramStart);
}
else if (seq != NULL) {
@ -13110,6 +13145,13 @@ static TPM_RC FwCmd_VerifySequenceStart(FWTPM_CTX* ctx, TPM2_Packet* cmd,
}
}
/* Skip auth area when tag is ST_SESSIONS — Table 87 Auth Index: None,
* but clients may still emit a password session that otherwise
* desynchronises the auth / hint / context TPM2B parse. */
if (rc == 0 && cmdTag == TPM_ST_SESSIONS) {
rc = FwSkipAuthArea(cmd, cmdSize);
}
if (rc == 0) {
TPM2_Packet_ParseU16(cmd, &authSz);
if (authSz > sizeof(((TPM2B_AUTH*)0)->buffer)) {
@ -13158,8 +13200,10 @@ static TPM_RC FwCmd_VerifySequenceStart(FWTPM_CTX* ctx, TPM2_Packet* cmd,
}
if (rc == 0) {
paramStart = FwRspParamsBegin(rsp, cmdTag, &paramSzPos);
/* sequenceHandle is an output handle per Table 87 — emitted
* before parameterSize. */
TPM2_Packet_AppendU32(rsp, seqHandle);
paramStart = FwRspParamsBegin(rsp, cmdTag, &paramSzPos);
FwRspParamsEnd(rsp, cmdTag, paramSzPos, paramStart);
}
else if (seq != NULL) {

View File

@ -3414,7 +3414,10 @@ TPM_RC TPM2_SignSequenceComplete(SignSequenceComplete_In* in,
TPM2_Packet packet;
CmdInfo_t info = {0,0,0,0};
info.inHandleCnt = 2;
info.flags = (CMD_FLAG_ENC2 | CMD_FLAG_AUTH_USER1);
/* Part 3 §20.6 Table 124: both @sequenceHandle and @keyHandle
* require USER authorization. */
info.flags = (CMD_FLAG_ENC2 | CMD_FLAG_AUTH_USER1 |
CMD_FLAG_AUTH_USER2);
TPM2_Packet_Init(ctx, &packet);
@ -3457,7 +3460,10 @@ TPM_RC TPM2_VerifySequenceComplete(VerifySequenceComplete_In* in,
TPM2_Packet packet;
CmdInfo_t info = {0,0,0,0};
info.inHandleCnt = 2;
info.flags = (CMD_FLAG_ENC2);
/* Part 3 §20.3 Table 118: @sequenceHandle requires USER auth;
* keyHandle has no auth. The framework needs the USER1 flag so
* the auth area matches what the server parses under ST_SESSIONS. */
info.flags = (CMD_FLAG_ENC2 | CMD_FLAG_AUTH_USER1);
TPM2_Packet_Init(ctx, &packet);
@ -3466,9 +3472,8 @@ TPM_RC TPM2_VerifySequenceComplete(VerifySequenceComplete_In* in,
st = TPM2_Packet_AppendAuth(&packet, ctx, &info);
TPM2_Packet_AppendU16(&packet, in->buffer.size);
TPM2_Packet_AppendBytes(&packet, in->buffer.buffer, in->buffer.size);
/* Part 3 §20.3 Table 118: parameters are {signature} only — no
* buffer field. Message was accumulated via SequenceUpdate. */
TPM2_Packet_AppendSignature(&packet, &in->signature);
TPM2_Packet_Finalize(&packet, st, TPM_CC_VerifySequenceComplete);
@ -7326,6 +7331,31 @@ void TPM2_PrintPublicArea(const TPM2B_PUBLIC* pub)
TPM2_PrintBin(pub->publicArea.unique.ecc.y.buffer, pub->publicArea.unique.ecc.y.size);
#endif
break;
#ifdef WOLFTPM_V185
case TPM_ALG_MLDSA:
case TPM_ALG_HASH_MLDSA:
printf(" %s: parameterSet 0x%X, unique size %d\n",
(pub->publicArea.type == TPM_ALG_MLDSA)
? "ML-DSA" : "Hash-ML-DSA",
(pub->publicArea.type == TPM_ALG_MLDSA)
? pub->publicArea.parameters.mldsaDetail.parameterSet
: pub->publicArea.parameters.hash_mldsaDetail.parameterSet,
pub->publicArea.unique.mldsa.size);
#ifdef WOLFTPM_DEBUG_VERBOSE
TPM2_PrintBin(pub->publicArea.unique.mldsa.buffer,
pub->publicArea.unique.mldsa.size);
#endif
break;
case TPM_ALG_MLKEM:
printf(" ML-KEM: parameterSet 0x%X, unique size %d\n",
pub->publicArea.parameters.mlkemDetail.parameterSet,
pub->publicArea.unique.mlkem.size);
#ifdef WOLFTPM_DEBUG_VERBOSE
TPM2_PrintBin(pub->publicArea.unique.mlkem.buffer,
pub->publicArea.unique.mlkem.size);
#endif
break;
#endif /* WOLFTPM_V185 */
default:
/* derive does not seem to have specific fields in the parameters struct */
printf("Derive Type: unique label size %d, context size %d\n",

View File

@ -8482,6 +8482,31 @@ static int GetKeyTemplateSize(TPMT_PUBLIC* publicTemplate)
case TPM_ALG_SYMCIPHER:
ret = publicTemplate->parameters.symDetail.sym.keyBits.sym / 8;
break;
#ifdef WOLFTPM_V185
case TPM_ALG_MLDSA:
case TPM_ALG_HASH_MLDSA: {
TPMI_MLDSA_PARAMETER_SET ps =
(publicTemplate->type == TPM_ALG_MLDSA)
? publicTemplate->parameters.mldsaDetail.parameterSet
: publicTemplate->parameters.hash_mldsaDetail.parameterSet;
/* Per Part 2 Table 207 MLDSA public-key sizes. */
if (ps == TPM_MLDSA_44) ret = 1312;
else if (ps == TPM_MLDSA_65) ret = 1952;
else if (ps == TPM_MLDSA_87) ret = 2592;
else ret = BAD_FUNC_ARG;
break;
}
case TPM_ALG_MLKEM: {
TPMI_MLKEM_PARAMETER_SET ps =
publicTemplate->parameters.mlkemDetail.parameterSet;
/* Per Part 2 Table 204 MLKEM public-key sizes. */
if (ps == TPM_MLKEM_512) ret = 800;
else if (ps == TPM_MLKEM_768) ret = 1184;
else if (ps == TPM_MLKEM_1024) ret = 1568;
else ret = BAD_FUNC_ARG;
break;
}
#endif /* WOLFTPM_V185 */
case TPM_ALG_KEYEDHASH:
default:
ret = BAD_FUNC_ARG;
@ -8565,6 +8590,49 @@ int wolfTPM2_SetKeyTemplate_Unique(TPMT_PUBLIC* publicTemplate,
}
publicTemplate->unique.sym.size = uniqueSz;
break;
#ifdef WOLFTPM_V185
/* TPMU_PUBLIC_ID shares the mldsa arm between MLDSA and HASH_MLDSA
* (Part 2 Table 225 note one union field, two selectors). */
case TPM_ALG_MLDSA:
case TPM_ALG_HASH_MLDSA:
if (uniqueSz == 0) {
uniqueSz = keySz;
}
else if (uniqueSz > keySz) {
uniqueSz = keySz;
}
if (uniqueSz > (int)sizeof(publicTemplate->unique.mldsa.buffer)) {
uniqueSz =
(int)sizeof(publicTemplate->unique.mldsa.buffer); /* truncate */
}
if (unique == NULL) {
XMEMSET(publicTemplate->unique.mldsa.buffer, 0, uniqueSz);
}
else {
XMEMCPY(publicTemplate->unique.mldsa.buffer, unique, uniqueSz);
}
publicTemplate->unique.mldsa.size = uniqueSz;
break;
case TPM_ALG_MLKEM:
if (uniqueSz == 0) {
uniqueSz = keySz;
}
else if (uniqueSz > keySz) {
uniqueSz = keySz;
}
if (uniqueSz > (int)sizeof(publicTemplate->unique.mlkem.buffer)) {
uniqueSz =
(int)sizeof(publicTemplate->unique.mlkem.buffer); /* truncate */
}
if (unique == NULL) {
XMEMSET(publicTemplate->unique.mlkem.buffer, 0, uniqueSz);
}
else {
XMEMCPY(publicTemplate->unique.mlkem.buffer, unique, uniqueSz);
}
publicTemplate->unique.mlkem.size = uniqueSz;
break;
#endif /* WOLFTPM_V185 */
case TPM_ALG_KEYEDHASH:
/* not supported */
ret = BAD_FUNC_ARG;
@ -8946,6 +9014,46 @@ static void wolfTPM2_CopyPubT(TPMT_PUBLIC* out, const TPMT_PUBLIC* in)
wolfTPM2_CopyEccParam(&out->unique.ecc.y,
&in->unique.ecc.y);
break;
#ifdef WOLFTPM_V185
case TPM_ALG_MLDSA:
out->parameters.mldsaDetail.parameterSet =
in->parameters.mldsaDetail.parameterSet;
out->parameters.mldsaDetail.allowExternalMu =
in->parameters.mldsaDetail.allowExternalMu;
out->unique.mldsa.size = in->unique.mldsa.size;
if (out->unique.mldsa.size > (UINT16)sizeof(out->unique.mldsa.buffer)) {
out->unique.mldsa.size = (UINT16)sizeof(out->unique.mldsa.buffer);
}
XMEMCPY(out->unique.mldsa.buffer, in->unique.mldsa.buffer,
out->unique.mldsa.size);
break;
case TPM_ALG_HASH_MLDSA:
out->parameters.hash_mldsaDetail.parameterSet =
in->parameters.hash_mldsaDetail.parameterSet;
out->parameters.hash_mldsaDetail.hashAlg =
in->parameters.hash_mldsaDetail.hashAlg;
/* TPMU_PUBLIC_ID shares the mldsa arm between MLDSA and HASH_MLDSA
* (Part 2 Table 225 note one union field, two selectors). */
out->unique.mldsa.size = in->unique.mldsa.size;
if (out->unique.mldsa.size > (UINT16)sizeof(out->unique.mldsa.buffer)) {
out->unique.mldsa.size = (UINT16)sizeof(out->unique.mldsa.buffer);
}
XMEMCPY(out->unique.mldsa.buffer, in->unique.mldsa.buffer,
out->unique.mldsa.size);
break;
case TPM_ALG_MLKEM:
wolfTPM2_CopySymmetric(&out->parameters.mlkemDetail.symmetric,
&in->parameters.mlkemDetail.symmetric);
out->parameters.mlkemDetail.parameterSet =
in->parameters.mlkemDetail.parameterSet;
out->unique.mlkem.size = in->unique.mlkem.size;
if (out->unique.mlkem.size > (UINT16)sizeof(out->unique.mlkem.buffer)) {
out->unique.mlkem.size = (UINT16)sizeof(out->unique.mlkem.buffer);
}
XMEMCPY(out->unique.mlkem.buffer, in->unique.mlkem.buffer,
out->unique.mlkem.size);
break;
#endif /* WOLFTPM_V185 */
default:
wolfTPM2_CopySymmetric(&out->parameters.asymDetail.symmetric,
&in->parameters.asymDetail.symmetric);