Add MLKEM arm to wolfTPM2_EncryptSecret + TPMU_ENCRYPTED_SECRET

Per TCG TPM 2.0 Library v1.85 Part 1 §24 (p.316) and Part 2 Table 222,
  v1.85 adds ML-KEM as a valid key-exchange type for encryptedSalt
  (TPM2_StartAuthSession), inSymSeed (Duplicate/Import/Rewrap), and
  credentialBlob (ActivateCredential/MakeCredential). The caller
  encapsulates under the TPM's ML-KEM public key: the 32-byte shared
  secret becomes the session salt, the ciphertext goes on the wire.

  Changes:
  - wolftpm/tpm2.h: add mlkem[MAX_MLKEM_CT_SIZE] arm to
    TPMU_ENCRYPTED_SECRET union (gated on WOLFTPM_V185). Without this the
    union sized at MAX_RSA_KEY_BYTES (512) could not hold an ML-KEM-768
    ciphertext (1088 bytes) let alone ML-KEM-1024 (1568 bytes).
  - src/tpm2_wrap.c: new static wolfTPM2_EncryptSecret_MLKEM helper that
    inits an MlKemKey from tpmKey->pub.unique.mlkem, calls
    wc_MlKemKey_Encapsulate, writes shared secret to data->buffer and
    ciphertext to secret->secret. Dispatch switch in wolfTPM2_EncryptSecret
    gains a TPM_ALG_MLKEM case. ML-DSA intentionally omitted — signing
    keys have no encrypt operation (spec Table 222 has no mldsa arm).
  - tests/unit_tests.c: test_wolfTPM2_EncryptSecret now creates a real
    MLKEM-768 primary and asserts data.size == 32 and secret.size == 1088.
pull/445/head
Aidan Garske 2026-04-22 10:28:32 -07:00
parent c2b631ebf5
commit e86b8971df
3 changed files with 127 additions and 1 deletions

View File

@ -2282,6 +2282,77 @@ static int wolfTPM2_EncryptSecret_RSA(WOLFTPM2_DEV* dev, const WOLFTPM2_KEY* tpm
}
#endif /* !WOLFTPM2_NO_WOLFCRYPT && !NO_RSA && !WC_NO_RNG */
#if defined(WOLFTPM_V185) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \
(defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_KYBER512) || \
defined(WOLFSSL_KYBER768) || defined(WOLFSSL_KYBER1024))
/* ML-KEM session-salt path per TCG TPM 2.0 Library v1.85 Part 1 §24
* (p.316): caller encapsulates under the TPM's ML-KEM public key; the
* ML-KEM shared secret becomes the session salt; the ML-KEM ciphertext
* is carried on the wire as encryptedSalt.bytes. The TPM decapsulates
* internally to recover the same shared secret. Unlike RSA-OAEP /
* ECDH, ML-KEM generates the shared secret as part of encapsulation
* the caller does not supply their own salt.
*/
static int wolfTPM2_EncryptSecret_MLKEM(const WOLFTPM2_KEY* tpmKey,
TPM2B_DATA* data, TPM2B_ENCRYPTED_SECRET* secret)
{
int rc;
int wcType;
WC_RNG rng;
MlKemKey mlkemKey;
const TPMS_MLKEM_PARMS* parms;
const TPM2B_PUBLIC_KEY_MLKEM* pubIn;
word32 ctSz = 0, ssSz = 0;
parms = &tpmKey->pub.publicArea.parameters.mlkemDetail;
pubIn = &tpmKey->pub.publicArea.unique.mlkem;
switch (parms->parameterSet) {
case TPM_MLKEM_512: wcType = WC_ML_KEM_512; break;
case TPM_MLKEM_768: wcType = WC_ML_KEM_768; break;
case TPM_MLKEM_1024: wcType = WC_ML_KEM_1024; break;
default: return TPM_RC_VALUE;
}
XMEMSET(&rng, 0, sizeof(rng));
XMEMSET(&mlkemKey, 0, sizeof(mlkemKey));
rc = wc_InitRng_ex(&rng, NULL, INVALID_DEVID);
if (rc == 0) {
rc = wc_MlKemKey_Init(&mlkemKey, wcType, NULL, INVALID_DEVID);
}
if (rc == 0) {
rc = wc_MlKemKey_DecodePublicKey(&mlkemKey, pubIn->buffer,
pubIn->size);
}
if (rc == 0) {
rc = wc_MlKemKey_CipherTextSize(&mlkemKey, &ctSz);
}
if (rc == 0) {
rc = wc_MlKemKey_SharedSecretSize(&mlkemKey, &ssSz);
}
if (rc == 0 &&
(ctSz > sizeof(secret->secret) || ssSz > sizeof(data->buffer))) {
rc = BUFFER_E;
}
if (rc == 0) {
rc = wc_MlKemKey_Encapsulate(&mlkemKey, secret->secret,
data->buffer, &rng);
}
if (rc == 0) {
secret->size = (UINT16)ctSz;
data->size = (UINT16)ssSz;
}
wc_MlKemKey_Free(&mlkemKey);
TPM2_ForceZero(&mlkemKey, sizeof(mlkemKey));
wc_FreeRng(&rng);
TPM2_ForceZero(&rng, sizeof(rng));
return rc;
}
#endif /* WOLFTPM_V185 && ML-KEM enabled */
int wolfTPM2_EncryptSecret(WOLFTPM2_DEV* dev, const WOLFTPM2_KEY* tpmKey,
TPM2B_DATA *data, TPM2B_ENCRYPTED_SECRET *secret,
const char* label)
@ -2313,6 +2384,14 @@ int wolfTPM2_EncryptSecret(WOLFTPM2_DEV* dev, const WOLFTPM2_KEY* tpmKey,
case TPM_ALG_RSA:
rc = wolfTPM2_EncryptSecret_RSA(dev, tpmKey, data, secret, label);
break;
#endif
#if defined(WOLFTPM_V185) && \
(defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_KYBER512) || \
defined(WOLFSSL_KYBER768) || defined(WOLFSSL_KYBER1024))
case TPM_ALG_MLKEM:
rc = wolfTPM2_EncryptSecret_MLKEM(tpmKey, data, secret);
(void)label; /* ML-KEM.Encaps does not take a KDF label */
break;
#endif
default:
rc = NOT_COMPILED_IN;

View File

@ -816,6 +816,12 @@ static void test_wolfTPM2_EncryptSecret(void)
WOLFTPM2_KEY tpmKey;
TPM2B_DATA data;
TPM2B_ENCRYPTED_SECRET secret;
#if defined(WOLFTPM_V185) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \
(defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_KYBER512) || \
defined(WOLFSSL_KYBER768) || defined(WOLFSSL_KYBER1024))
WOLFTPM2_KEY mlkemKey;
TPMT_PUBLIC mlkemPub;
#endif
XMEMSET(&tpmKey, 0, sizeof(tpmKey));
XMEMSET(&data, 0, sizeof(data));
@ -840,10 +846,48 @@ static void test_wolfTPM2_EncryptSecret(void)
rc = wolfTPM2_EncryptSecret(&dev, &tpmKey, &data, NULL, "SECRET");
AssertIntEQ(rc, BAD_FUNC_ARG);
#if defined(WOLFTPM_V185) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \
(defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_KYBER512) || \
defined(WOLFSSL_KYBER768) || defined(WOLFSSL_KYBER1024))
/* MLKEM path (v1.85 Part 1 §24): caller encapsulates under the TPM's
* ML-KEM public key; the shared secret (32 bytes) becomes the session
* salt, the ciphertext (1088 bytes for MLKEM-768) goes on the wire. */
XMEMSET(&mlkemKey, 0, sizeof(mlkemKey));
XMEMSET(&mlkemPub, 0, sizeof(mlkemPub));
XMEMSET(&data, 0, sizeof(data));
XMEMSET(&secret, 0, sizeof(secret));
rc = wolfTPM2_GetKeyTemplate_MLKEM(&mlkemPub,
TPMA_OBJECT_decrypt | TPMA_OBJECT_fixedTPM |
TPMA_OBJECT_fixedParent | TPMA_OBJECT_sensitiveDataOrigin |
TPMA_OBJECT_userWithAuth, TPM_MLKEM_768);
AssertIntEQ(rc, TPM_RC_SUCCESS);
rc = wolfTPM2_CreatePrimaryKey(&dev, &mlkemKey, TPM_RH_OWNER,
&mlkemPub, NULL, 0);
if (rc == TPM_RC_VALUE || rc == TPM_RC_SCHEME ||
rc == TPM_RC_COMMAND_CODE || rc == (int)(RC_VER1 + 0x043)) {
printf("Test TPM Wrapper: %-40s Skipped (not supported)\n",
"EncryptSecret MLKEM:");
}
else {
AssertIntEQ(rc, 0);
rc = wolfTPM2_EncryptSecret(&dev, &mlkemKey, &data, &secret,
"SECRET");
AssertIntEQ(rc, 0);
AssertIntEQ(data.size, 32); /* MLKEM shared secret */
AssertIntEQ(secret.size, 1088); /* MLKEM-768 ciphertext */
printf("Test TPM Wrapper: %-40s Passed\n",
"EncryptSecret MLKEM:");
wolfTPM2_UnloadHandle(&dev, &mlkemKey.handle);
}
#endif
wolfTPM2_Cleanup(&dev);
printf("Test TPM Wrapper: %-40s %s\n", "EncryptSecret:",
rc == BAD_FUNC_ARG ? "Passed" : "Failed");
rc == 0 || rc == BAD_FUNC_ARG ? "Passed" : "Failed");
}
static void test_wolfTPM2_Cleanup(void)

View File

@ -1647,6 +1647,9 @@ typedef union TPMU_ENCRYPTED_SECRET {
BYTE rsa[MAX_RSA_KEY_BYTES]; /* TPM_ALG_RSA */
BYTE symmetric[sizeof(TPM2B_DIGEST)]; /* TPM_ALG_SYMCIPHER */
BYTE keyedHash[sizeof(TPM2B_DIGEST)]; /* TPM_ALG_KEYEDHASH */
#ifdef WOLFTPM_V185
BYTE mlkem[MAX_MLKEM_CT_SIZE]; /* TPM_ALG_MLKEM (v1.85 T222) */
#endif
} TPMU_ENCRYPTED_SECRET;
typedef struct TPM2B_ENCRYPTED_SECRET {