@@ -8172,6 +8172,112 @@ static void test_fwtpm_nv_define_write_read(void)
81728172 fwtpm_pass ("NV Define/Write/Read/Undef:" , 0 );
81738173}
81748174
8175+ /* TPM2_CreateLoaded must validate ML templates (parameter set, allowExternalMu,
8176+ * Hash-ML-DSA hashAlg, ML-KEM symmetric) before key generation
8177+ * (TCG v1.85 Part 2 Tables 204/207/208/229-231). */
8178+ static TPM_RC tmp_cl_ml (FWTPM_CTX * ctx , UINT32 parent , UINT16 algType ,
8179+ UINT16 ps , byte mu , UINT16 hashAlg , UINT16 symAlg , UINT16 symBits )
8180+ {
8181+ int pos = 0 , rspSize = 0 , sensStart , sensLen , pubStart , pubLen ;
8182+ UINT32 h ;
8183+ PutU16BE (gCmd + pos , TPM_ST_SESSIONS ); pos += 2 ;
8184+ PutU32BE (gCmd + pos , 0 ); pos += 4 ;
8185+ PutU32BE (gCmd + pos , TPM_CC_CreateLoaded ); pos += 4 ;
8186+ PutU32BE (gCmd + pos , parent ); pos += 4 ;
8187+ pos = AppendPwAuth (gCmd , pos , NULL , 0 );
8188+ /* inSensitive */
8189+ sensStart = pos ;
8190+ PutU16BE (gCmd + pos , 0 ); pos += 2 ;
8191+ PutU16BE (gCmd + pos , 0 ); pos += 2 ;
8192+ PutU16BE (gCmd + pos , 0 ); pos += 2 ;
8193+ sensLen = pos - sensStart - 2 ;
8194+ PutU16BE (gCmd + sensStart , (UINT16 )sensLen );
8195+ /* inPublic template */
8196+ pubStart = pos ;
8197+ PutU16BE (gCmd + pos , 0 ); pos += 2 ;
8198+ PutU16BE (gCmd + pos , algType ); pos += 2 ;
8199+ PutU16BE (gCmd + pos , TPM_ALG_SHA256 ); pos += 2 ;
8200+ if (algType == TPM_ALG_MLKEM ) {
8201+ PutU32BE (gCmd + pos , 0x00020072 ); pos += 4 ; /* decrypt */
8202+ }
8203+ else {
8204+ PutU32BE (gCmd + pos , 0x00040072 ); pos += 4 ; /* sign */
8205+ }
8206+ PutU16BE (gCmd + pos , 0 ); pos += 2 ; /* authPolicy */
8207+ PutU16BE (gCmd + pos , ps ); pos += 2 ; /* parameterSet (before sym? no) */
8208+ if (algType == TPM_ALG_MLKEM ) {
8209+ /* TPMS_MLKEM_PARMS: symmetric FIRST, then parameterSet. Rebuild. */
8210+ pos -= 2 ; /* undo ps */
8211+ PutU16BE (gCmd + pos , symAlg ); pos += 2 ;
8212+ if (symAlg != TPM_ALG_NULL ) {
8213+ PutU16BE (gCmd + pos , symBits ); pos += 2 ;
8214+ PutU16BE (gCmd + pos , TPM_ALG_CFB ); pos += 2 ;
8215+ }
8216+ PutU16BE (gCmd + pos , ps ); pos += 2 ;
8217+ }
8218+ else if (algType == TPM_ALG_HASH_MLDSA ) {
8219+ PutU16BE (gCmd + pos , hashAlg ); pos += 2 ;
8220+ }
8221+ else {
8222+ gCmd [pos ++ ] = mu ;
8223+ }
8224+ PutU16BE (gCmd + pos , 0 ); pos += 2 ; /* unique size 0 */
8225+ pubLen = pos - pubStart - 2 ;
8226+ PutU16BE (gCmd + pubStart , (UINT16 )pubLen );
8227+ PutU32BE (gCmd + 2 , (UINT32 )pos );
8228+ rspSize = 0 ;
8229+ FWTPM_ProcessCommand (ctx , gCmd , pos , gRsp , & rspSize , 0 );
8230+ if (GetRspRC (gRsp ) == TPM_RC_SUCCESS ) {
8231+ h = GetU32BE (gRsp + TPM2_HEADER_SIZE );
8232+ FlushHandle (ctx , h );
8233+ }
8234+ return GetRspRC (gRsp );
8235+ }
8236+
8237+ static void test_fwtpm_createloaded_ml_validation (void )
8238+ {
8239+ FWTPM_CTX ctx ;
8240+ UINT32 srk ;
8241+ TPM_RC rc ;
8242+ memset (& ctx , 0 , sizeof (ctx ));
8243+ AssertIntEQ (fwtpm_test_startup (& ctx ), 0 );
8244+ srk = make_srk_parent (& ctx );
8245+ AssertIntNE (srk , 0 );
8246+
8247+ /* Baseline: valid MLDSA-65 template creates. */
8248+ rc = tmp_cl_ml (& ctx , srk , TPM_ALG_MLDSA , TPM_MLDSA_65 , NO , 0 ,
8249+ TPM_ALG_NULL , 0 );
8250+ printf (" valid MLDSA-65 CL rc=0x%x\n" , rc );
8251+ AssertIntEQ (rc , TPM_RC_SUCCESS );
8252+
8253+ /* Unsupported parameter set. */
8254+ rc = tmp_cl_ml (& ctx , srk , TPM_ALG_MLDSA , 0x0099 , NO , 0 , TPM_ALG_NULL , 0 );
8255+ printf (" MLDSA bad ps CL rc=0x%x\n" , rc );
8256+ AssertIntEQ (rc , TPM_RC_PARMS );
8257+
8258+ /* allowExternalMu out of range (TPMI_YES_NO). */
8259+ rc = tmp_cl_ml (& ctx , srk , TPM_ALG_MLDSA , TPM_MLDSA_65 , 0x02 , 0 ,
8260+ TPM_ALG_NULL , 0 );
8261+ printf (" MLDSA bad ext-mu CL rc=0x%x\n" , rc );
8262+ AssertIntEQ (rc , TPM_RC_VALUE );
8263+
8264+ /* Hash-ML-DSA with an invalid pre-hash algorithm. */
8265+ rc = tmp_cl_ml (& ctx , srk , TPM_ALG_HASH_MLDSA , TPM_MLDSA_65 , 0 ,
8266+ TPM_ALG_NULL , TPM_ALG_NULL , 0 );
8267+ printf (" HASH_MLDSA bad hash CL rc=0x%x\n" , rc );
8268+ AssertIntEQ (rc , TPM_RC_HASH );
8269+
8270+ /* ML-KEM invalid symmetric (AES bogus keyBits). */
8271+ rc = tmp_cl_ml (& ctx , srk , TPM_ALG_MLKEM , TPM_MLKEM_512 , 0 , 0 ,
8272+ TPM_ALG_AES , 7 );
8273+ printf (" MLKEM bad symmetric CL rc=0x%x\n" , rc );
8274+ AssertIntNE (rc , TPM_RC_SUCCESS );
8275+
8276+ FlushHandle (& ctx , srk );
8277+ FWTPM_Cleanup (& ctx );
8278+ fwtpm_pass ("CreateLoaded ML validation:" , 1 );
8279+ }
8280+
81758281static void test_fwtpm_nv_read_public (void )
81768282{
81778283 FWTPM_CTX ctx ;
@@ -11992,6 +12098,7 @@ int fwtpm_unit_tests(int argc, char *argv[])
1199212098 test_fwtpm_mlkem_wolfssl_keygen_kat ();
1199312099 test_fwtpm_mldsa_loadexternal_verify ();
1199412100 test_fwtpm_loadexternal_ml_validation ();
12101+ test_fwtpm_createloaded_ml_validation ();
1199512102 test_fwtpm_mldsa_primary_determinism ();
1199612103 test_fwtpm_mlkem_primary_determinism ();
1199712104 test_fwtpm_getcap_pqc ();
0 commit comments