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
15 changes: 15 additions & 0 deletions src/fwtpm/fwtpm_command.c
Original file line number Diff line number Diff line change
Expand Up @@ -18910,6 +18910,14 @@ static TPM_RC FwCmd_SignSequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd,
if (rc == 0 && !(keyObj->pub.objectAttributes & TPMA_OBJECT_sign)) {
rc = TPM_RC_KEY;
}
/* Reject a public-only ML-DSA object: its all-zero seed derives a
* universally reproducible key. Checked here rather than at
* SequenceStart because a public-only twin shares the key's Name. */
if (rc == 0 && (keyObj->pub.type == TPM_ALG_MLDSA ||
Comment thread
aidangarske marked this conversation as resolved.
keyObj->pub.type == TPM_ALG_HASH_MLDSA) &&
keyObj->privKeySize != MAX_MLDSA_PRIV_SEED_SIZE) {
rc = TPM_RC_KEY;
}
/* Part 3 Sec.20.6.1: the x509sign attribute of keyHandle MUST NOT be SET
* (TPM_RC_ATTRIBUTES). Keys with x509sign restrict what digests can be
* signed for X.509 cert use; SignSequenceComplete is not the right
Expand Down Expand Up @@ -19576,6 +19584,13 @@ static TPM_RC FwCmd_SignDigest(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize,
if (rc == 0 && !(obj->pub.objectAttributes & TPMA_OBJECT_sign)) {
rc = TPM_RC_KEY;
}
/* Reject a public-only ML-DSA object: its all-zero seed derives a
* universally reproducible key. */
if (rc == 0 && (obj->pub.type == TPM_ALG_MLDSA ||
obj->pub.type == TPM_ALG_HASH_MLDSA) &&
obj->privKeySize != MAX_MLDSA_PRIV_SEED_SIZE) {
rc = TPM_RC_KEY;
}

/* Part 3 Sec.20.7.1: x509sign restricts the key to X.509-cert signing
* and is rejected outright here with TPM_RC_ATTRIBUTES. Restricted
Expand Down
121 changes: 121 additions & 0 deletions tests/fwtpm_unit_tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -6072,6 +6072,126 @@ static void test_fwtpm_mldsa_quote_public_only_returns_key(void)
}
#endif /* WOLFTPM_MLDSA_SIGN && WOLFTPM_MLDSA_VERIFY && !FWTPM_NO_ATTESTATION */

/* LoadExternal a public-only ML-DSA-44 (or Hash-ML-DSA-44/SHA-256) signing
* key and return its handle. */
static UINT32 LoadPublicOnlyMldsa(FWTPM_CTX* ctx, UINT16 algType)
{
int rspSize = 0, pos = 0, pubStart;

PutU16BE(gCmd + pos, TPM_ST_NO_SESSIONS); pos += 2;
PutU32BE(gCmd + pos, 0); pos += 4;
PutU32BE(gCmd + pos, TPM_CC_LoadExternal); pos += 4;
PutU16BE(gCmd + pos, 0); pos += 2; /* inPrivate empty */
pubStart = pos;
PutU16BE(gCmd + pos, 0); pos += 2; /* size placeholder */
PutU16BE(gCmd + pos, algType); pos += 2; /* type */
PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; /* nameAlg */
PutU32BE(gCmd + pos, 0x00040040); pos += 4; /* sign|userWithAuth */
PutU16BE(gCmd + pos, 0); pos += 2; /* authPolicy */
PutU16BE(gCmd + pos, TPM_MLDSA_44); pos += 2; /* parameterSet */
if (algType == TPM_ALG_HASH_MLDSA) {
PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; /* hashAlg */
}
else {
gCmd[pos++] = NO; /* allowExternalMu */
}
PutU16BE(gCmd + pos, sizeof(gNistMldsa44Pk)); pos += 2;
memcpy(gCmd + pos, gNistMldsa44Pk, sizeof(gNistMldsa44Pk));
pos += sizeof(gNistMldsa44Pk);
PutU16BE(gCmd + pubStart, (UINT16)(pos - pubStart - 2));
PutU32BE(gCmd + pos, TPM_RH_NULL); pos += 4; /* hierarchy */
PutU32BE(gCmd + 2, (UINT32)pos);
AssertIntEQ(FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, 0),
TPM_RC_SUCCESS);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
return GetU32BE(gRsp + TPM2_HEADER_SIZE);
}

/* A public-only ML-DSA key has no private seed. SignSequenceComplete and
* SignDigest must return TPM_RC_KEY rather than sign from an all-zero seed. */
static void test_fwtpm_mldsa_sign_public_only_returns_key(void)
Comment thread
aidangarske marked this conversation as resolved.
{
FWTPM_CTX ctx;
int rspSize, pos;
UINT32 handle, seqHandle;

memset(&ctx, 0, sizeof(ctx));
AssertIntEQ(fwtpm_test_startup(&ctx), 0);

handle = LoadPublicOnlyMldsa(&ctx, TPM_ALG_MLDSA);

/* SignSequenceStart (no auth, empty context) */
pos = 0;
PutU16BE(gCmd + pos, TPM_ST_NO_SESSIONS); pos += 2;
PutU32BE(gCmd + pos, 0); pos += 4;
PutU32BE(gCmd + pos, TPM_CC_SignSequenceStart); pos += 4;
PutU32BE(gCmd + pos, handle); pos += 4;
PutU16BE(gCmd + pos, 0); pos += 2;
PutU16BE(gCmd + pos, 0); pos += 2;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0),
TPM_RC_SUCCESS);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS);
seqHandle = GetU32BE(gRsp + TPM2_HEADER_SIZE);

/* SignSequenceComplete must refuse: no private seed. */
pos = 0;
PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2;
PutU32BE(gCmd + pos, 0); pos += 4;
PutU32BE(gCmd + pos, TPM_CC_SignSequenceComplete); pos += 4;
PutU32BE(gCmd + pos, seqHandle); pos += 4;
PutU32BE(gCmd + pos, handle); pos += 4;
PutU32BE(gCmd + pos, 18); pos += 4;
PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4;
PutU16BE(gCmd + pos, 0); pos += 2;
gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); pos += 2;
PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4;
PutU16BE(gCmd + pos, 0); pos += 2;
gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); pos += 2;
PutU16BE(gCmd + pos, 4); pos += 2; /* buffer */
memset(gCmd + pos, 0xAA, 4); pos += 4;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0),
TPM_RC_SUCCESS);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_KEY);

FlushHandle(&ctx, seqHandle);
FlushHandle(&ctx, handle);

#ifdef WOLFTPM_HASH_MLDSA
handle = LoadPublicOnlyMldsa(&ctx, TPM_ALG_HASH_MLDSA);

/* SignDigest must refuse: no private seed. */
pos = 0;
PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2;
PutU32BE(gCmd + pos, 0); pos += 4;
PutU32BE(gCmd + pos, TPM_CC_SignDigest); pos += 4;
PutU32BE(gCmd + pos, handle); pos += 4;
PutU32BE(gCmd + pos, 9); pos += 4;
PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4;
PutU16BE(gCmd + pos, 0); pos += 2;
gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); pos += 2;
PutU16BE(gCmd + pos, 0); pos += 2; /* context empty */
PutU16BE(gCmd + pos, 32); pos += 2; /* digest */
memset(gCmd + pos, 0xAA, 32); pos += 32;
PutU16BE(gCmd + pos, TPM_ST_HASHCHECK); pos += 2;
PutU32BE(gCmd + pos, TPM_RH_NULL); pos += 4;
PutU16BE(gCmd + pos, 0); pos += 2;
PutU32BE(gCmd + 2, (UINT32)pos);
rspSize = 0;
AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0),
TPM_RC_SUCCESS);
AssertIntEQ(GetRspRC(gRsp), TPM_RC_KEY);

FlushHandle(&ctx, handle);
#endif /* WOLFTPM_HASH_MLDSA */

FWTPM_Cleanup(&ctx);
fwtpm_pass("MLDSA sign public-only key rejected:", 1);
}

/* TPM2_LoadExternal must reject ML public areas with an unsupported parameter
* set, an out-of-range allowExternalMu, an invalid Hash-ML-DSA hash, invalid
* ML-KEM symmetric parameters, or a public key size inconsistent with the
Expand Down Expand Up @@ -19140,6 +19260,7 @@ int fwtpm_unit_tests(int argc, char *argv[])
test_fwtpm_signseqcomplete_truncated_buffer();
test_fwtpm_verifyseqcomplete_neg();
test_fwtpm_signdigest_neg();
test_fwtpm_mldsa_sign_public_only_returns_key();
#ifdef WOLFTPM_HASH_MLDSA
test_fwtpm_signdigest_malformed_hashcheck_tag();
test_fwtpm_signdigest_restricted_null_ticket_returns_ticket();
Expand Down
Loading