From 65a6f532ee31e7e0a1ad1a6e7a4cdb4ecf3ddffd Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 25 Jun 2026 11:20:37 -0700 Subject: [PATCH 1/6] Fix bound-session parameter-encryption key derivation --- src/fwtpm/fwtpm_command.c | 111 ++++++++++++++++++++++++++++---------- src/tpm2_param_enc.c | 44 +++++++++------ src/tpm2_wrap.c | 17 +++++- wolftpm/fwtpm/fwtpm.h | 1 + wolftpm/tpm2.h | 1 + 5 files changed, 126 insertions(+), 48 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 6b70c391..c8c59da2 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -199,15 +199,42 @@ static int FwGetPcrBankIndex(UINT16 hashAlg) * TPM2_ParamEnc_AESCFB). See wolftpm/tpm2_param_enc.h */ #ifndef FWTPM_NO_PARAM_ENC +/* Build the parameter-encryption key = sessionKey || authValue, where authValue + * is the bind authValue when the encrypt session authorizes its own bind entity + * (TPM 2.0 Part 1 "Symmetric Encrypt" - the encrypt key keeps the authorized + * entity's authValue even when bound). authHandle is the handle the session + * authorizes (0 for a secondary encrypt-only session). */ +static int FwBuildParamEncKey(FWTPM_Session* sess, TPM_HANDLE authHandle, + byte* keyBuf, int keyBufMax, int* keyBufSz) +{ + int sz = sess->sessionKey.size; + if (sz < 0 || sz > keyBufMax) + return TPM_RC_FAILURE; + XMEMCPY(keyBuf, sess->sessionKey.buffer, sz); + if (sess->bindHandle != 0 && sess->bindHandle == authHandle && + sess->bindAuth.size > 0 && sz + sess->bindAuth.size <= keyBufMax) { + XMEMCPY(keyBuf + sz, sess->bindAuth.buffer, sess->bindAuth.size); + sz += sess->bindAuth.size; + } + *keyBufSz = sz; + return 0; +} + /* Decrypt first TPM2B parameter of incoming command (param encryption) */ static int FwParamDecryptCmd(FWTPM_CTX* ctx, FWTPM_Session* sess, - byte* paramData, UINT32 paramSz) + TPM_HANDLE authHandle, byte* paramData, UINT32 paramSz) { - int rc = 0; + int rc; + byte keyBuf[TPM_MAX_DIGEST_SIZE * 2]; + int keyBufSz = 0; + + rc = FwBuildParamEncKey(sess, authHandle, keyBuf, (int)sizeof(keyBuf), + &keyBufSz); + if (rc != 0) + return rc; if (sess->symmetric.algorithm == TPM_ALG_XOR) { - rc = TPM2_ParamEnc_XOR(sess->authHash, - sess->sessionKey.buffer, sess->sessionKey.size, + rc = TPM2_ParamEnc_XOR(sess->authHash, keyBuf, keyBufSz, sess->nonceCaller.buffer, sess->nonceCaller.size, sess->nonceTPM.buffer, sess->nonceTPM.size, paramData, paramSz); @@ -215,42 +242,47 @@ static int FwParamDecryptCmd(FWTPM_CTX* ctx, FWTPM_Session* sess, else if (sess->symmetric.algorithm == TPM_ALG_AES && sess->symmetric.mode.aes == TPM_ALG_CFB) { rc = TPM2_ParamEnc_AESCFB(sess->authHash, - sess->symmetric.keyBits.aes, - sess->sessionKey.buffer, sess->sessionKey.size, + sess->symmetric.keyBits.aes, keyBuf, keyBufSz, sess->nonceCaller.buffer, sess->nonceCaller.size, sess->nonceTPM.buffer, sess->nonceTPM.size, paramData, paramSz, 0); /* decrypt */ } + TPM2_ForceZero(keyBuf, sizeof(keyBuf)); (void)ctx; return rc; } /* Encrypt first TPM2B parameter of outgoing response (param encryption) */ static int FwParamEncryptRsp(FWTPM_CTX* ctx, FWTPM_Session* sess, - byte* paramData, UINT32 paramSz) + TPM_HANDLE authHandle, byte* paramData, UINT32 paramSz) { - int rc = 0; + int rc; + byte keyBuf[TPM_MAX_DIGEST_SIZE * 2]; + int keyBufSz = 0; + + rc = FwBuildParamEncKey(sess, authHandle, keyBuf, (int)sizeof(keyBuf), + &keyBufSz); + if (rc != 0) + return rc; + /* Response direction: nonceTPM first, nonceCaller second */ if (sess->symmetric.algorithm == TPM_ALG_XOR) { - /* Response direction: nonceTPM first, nonceCaller second */ - rc = TPM2_ParamEnc_XOR(sess->authHash, - sess->sessionKey.buffer, sess->sessionKey.size, + rc = TPM2_ParamEnc_XOR(sess->authHash, keyBuf, keyBufSz, sess->nonceTPM.buffer, sess->nonceTPM.size, sess->nonceCaller.buffer, sess->nonceCaller.size, paramData, paramSz); } else if (sess->symmetric.algorithm == TPM_ALG_AES && sess->symmetric.mode.aes == TPM_ALG_CFB) { - /* Response direction: nonceTPM first, nonceCaller second */ rc = TPM2_ParamEnc_AESCFB(sess->authHash, - sess->symmetric.keyBits.aes, - sess->sessionKey.buffer, sess->sessionKey.size, + sess->symmetric.keyBits.aes, keyBuf, keyBufSz, sess->nonceTPM.buffer, sess->nonceTPM.size, sess->nonceCaller.buffer, sess->nonceCaller.size, paramData, paramSz, 1); /* encrypt */ } + TPM2_ForceZero(keyBuf, sizeof(keyBuf)); (void)ctx; return rc; } @@ -8330,23 +8362,17 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* Compute session key: KDFa(authHash, salt||bindAuth, "ATH", - * nonceTPM, nonceCaller, digestSize) - * Per TPM 2.0 Part 1 Section 19.6.8: if both tpmKey and bind are - * TPM_RH_NULL (no salt, no bind auth) then sessionKey = {} (empty). */ + /* sessionKey = KDFa(authHash, bindAuth || salt, "ATH", ...) per TPM 2.0 + * Part 1 Sec.19.6.8 - bind authValue FIRST, then salt. Unbound + unsalted + * yields an empty sessionKey. */ if (rc == 0) { - byte keyIn[TPM_MAX_DIGEST_SIZE * 2]; /* salt || bindAuth */ + byte keyIn[TPM_MAX_DIGEST_SIZE * 2]; /* bindAuth || salt */ int keyInSz = 0; - /* Append salt if present */ - if (saltSize > 0) { - XMEMCPY(keyIn, salt, saltSize); - keyInSz = saltSize; - } - /* Append bind entity auth if bound */ if (bind != TPM_RH_NULL) { TPM2B_AUTH bindAuth; XMEMSET(&bindAuth, 0, sizeof(bindAuth)); + sess->bindHandle = bind; /* Hierarchy handles: use hierarchy auth from ctx */ if (bind == TPM_RH_OWNER) { XMEMCPY(&bindAuth, &ctx->ownerAuth, sizeof(TPM2B_AUTH)); @@ -8382,14 +8408,30 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, keyInSz += bindAuth.size; } } + /* Retain the bind authValue: parameter encryption appends it to + * the key when the session authorizes its own bind entity. */ + if (rc == 0) { + XMEMCPY(&sess->bindAuth, &bindAuth, sizeof(TPM2B_AUTH)); + } TPM2_ForceZero(&bindAuth, sizeof(bindAuth)); } + /* Then append salt if present */ + if (rc == 0 && saltSize > 0) { + if (keyInSz + saltSize <= (int)sizeof(keyIn)) { + XMEMCPY(keyIn + keyInSz, salt, saltSize); + keyInSz += saltSize; + } + } - if (keyInSz == 0) { - /* Unsalted, unbound: sessionKey is empty per spec */ + if (saltSize == 0 && bind == TPM_RH_NULL) { + /* Unsalted AND unbound: sessionKey is empty per spec */ sess->sessionKey.size = 0; } else { + /* Salted and/or bound: compute the sessionKey. For a session + * bound to an entity with an EmptyAuth, keyInSz is 0 here and + * KDFa runs over a zero-length key input - the sessionKey is + * still computed (Part 1 Bound Session Key Generation). */ int sessKeyRc; sess->sessionKey.size = (UINT16)nonceSize; sessKeyRc = TPM2_KDFa_ex(authHash, @@ -15959,6 +16001,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, TPM_RC rc = TPM_RC_SUCCESS; #ifndef FWTPM_NO_PARAM_ENC FWTPM_Session* encSess = NULL; /* Session requesting param encryption */ + TPM_HANDLE encAuthHandle = 0; /* Handle the encrypt session authorizes */ int doEncCmd = 0; /* Decrypt incoming encrypted param */ int doEncRsp = 0; /* Encrypt outgoing response param */ #endif @@ -16152,6 +16195,16 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, if (encSess == NULL && sess->symmetric.algorithm != TPM_ALG_NULL) { encSess = sess; + /* The first authHandleCnt handles are the ones + * that require authorization, in order, so auth + * session i authorizes handle i for + * i < authHandleCnt. Beyond that the session is + * a secondary encrypt-only session and + * authorizes no handle. */ + if (cmdAuthCnt < (int)entry->authHandleCnt && + cmdAuthCnt < cmdHandleCnt) { + encAuthHandle = cmdHandles[cmdAuthCnt]; + } /* decrypt attr = client encrypted cmd param */ if ((attribs & TPMA_SESSION_decrypt) && @@ -16563,7 +16616,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, UINT16 paramSz; paramSz = FwLoadU16BE(cmdBuf + cpStart); if (paramSz > 0 && cpStart + 2 + paramSz <= cmdSize) { - rc = (TPM_RC)FwParamDecryptCmd(ctx, encSess, + rc = (TPM_RC)FwParamDecryptCmd(ctx, encSess, encAuthHandle, (byte*)cmdBuf + cpStart + 2, paramSz); if (rc != 0) { #ifdef DEBUG_WOLFTPM @@ -16649,7 +16702,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, firstParamSz = FwLoadU16BE(rspBuf + rspParamStart); if (firstParamSz > 0 && rspParamStart + 2 + firstParamSz <= rspParamEnd) { - int encRc = FwParamEncryptRsp(ctx, encSess, + int encRc = FwParamEncryptRsp(ctx, encSess, encAuthHandle, rspBuf + rspParamStart + 2, firstParamSz); if (encRc != 0) { #ifdef DEBUG_WOLFTPM diff --git a/src/tpm2_param_enc.c b/src/tpm2_param_enc.c index 4f9b1198..8be69d77 100644 --- a/src/tpm2_param_enc.c +++ b/src/tpm2_param_enc.c @@ -180,12 +180,33 @@ int TPM2_ParamEnc_AESCFB( #ifndef WOLFTPM_FWTPM -/* Build combined HMAC key from session key + bind key */ +/* The parameter-encryption key is sessionKey || authValue(authorized entity) + * (TPM 2.0 Part 1, "Symmetric Encrypt" - unlike the command HMAC, the encrypt + * key keeps the authValue even when the session is bound to that entity). The + * authorized authValue is normally already folded into session->auth; the + * exception is a session bound to the entity it authorizes, where the bind + * rule leaves session->auth as just the session key and the authValue is in + * session->bind. Fold session->bind back in only for that case (bound, + * name == bindName, session->auth still the session key). */ +static TPM2B_AUTH* TPM2_ParamEncBindKey(TPM2_AUTH_SESSION* session) +{ + int digestSz = TPM2_GetHashDigestSize(session->authHash); + if (session->bind != NULL && digestSz > 0 && + session->name.size > 0 && + session->name.size == session->bindName.size && + XMEMCMP(session->name.name, session->bindName.name, + session->name.size) == 0 && + session->auth.size <= (UINT16)digestSz) { + return session->bind; + } + return NULL; +} + +/* Build combined param-enc key from session key + optional bind authValue. */ static int TPM2_BuildParamKey(TPM2B_AUTH* sessKey, TPM2B_AUTH* bindKey, BYTE* keyBuf, UINT32* keyBufSz) { UINT16 bindKeySz = (bindKey != NULL) ? bindKey->size : 0; - if (sessKey->size > sizeof(sessKey->buffer)) { return BUFFER_E; } @@ -195,7 +216,6 @@ static int TPM2_BuildParamKey(TPM2B_AUTH* sessKey, TPM2B_AUTH* bindKey, if (sessKey->size + bindKeySz > MAX_SYM_DATA) { return BUFFER_E; } - XMEMCPY(keyBuf, sessKey->buffer, sessKey->size); *keyBufSz = sessKey->size; if (bindKey != NULL && bindKey->size > 0) { @@ -217,19 +237,14 @@ TPM_RC TPM2_ParamEnc_CmdRequest(TPM2_AUTH_SESSION *session, #ifdef WOLFTPM_DEBUG_SECRETS TPM2_PrintBin(session->auth.buffer, session->auth.size); #endif - if (session->bind != NULL) { - printf("CmdEnc Extra Key %d\n", session->bind->size); - #ifdef WOLFTPM_DEBUG_SECRETS - TPM2_PrintBin(session->bind->buffer, session->bind->size); - #endif - } printf("CmdEnc Nonce caller %d\n", session->nonceCaller.size); TPM2_PrintBin(session->nonceCaller.buffer, session->nonceCaller.size); printf("CmdEnc Nonce TPM %d\n", session->nonceTPM.size); TPM2_PrintBin(session->nonceTPM.buffer, session->nonceTPM.size); #endif - rc = TPM2_BuildParamKey(&session->auth, session->bind, keyBuf, &keyBufSz); + rc = TPM2_BuildParamKey(&session->auth, TPM2_ParamEncBindKey(session), + keyBuf, &keyBufSz); if (rc != 0) { return rc; } @@ -268,19 +283,14 @@ TPM_RC TPM2_ParamDec_CmdResponse(TPM2_AUTH_SESSION *session, #ifdef WOLFTPM_DEBUG_SECRETS TPM2_PrintBin(session->auth.buffer, session->auth.size); #endif - if (session->bind != NULL) { - printf("RspDec Extra Key %d\n", session->bind->size); - #ifdef WOLFTPM_DEBUG_SECRETS - TPM2_PrintBin(session->bind->buffer, session->bind->size); - #endif - } printf("RspDec Nonce caller %d\n", session->nonceCaller.size); TPM2_PrintBin(session->nonceCaller.buffer, session->nonceCaller.size); printf("RspDec Nonce TPM %d\n", session->nonceTPM.size); TPM2_PrintBin(session->nonceTPM.buffer, session->nonceTPM.size); #endif - rc = TPM2_BuildParamKey(&session->auth, session->bind, keyBuf, &keyBufSz); + rc = TPM2_BuildParamKey(&session->auth, TPM2_ParamEncBindKey(session), + keyBuf, &keyBufSz); if (rc != 0) { return rc; } diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index c9039de5..959bbb0d 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -1899,8 +1899,15 @@ int wolfTPM2_SetAuthSession(WOLFTPM2_DEV* dev, int index, session->policyAuth = tpmSession->handle.policyAuth; session->policyPass = tpmSession->handle.policyPass; - /* Capture pointer to bind */ + /* Capture the bind auth pointer and the bind entity Name (set by + * wolfTPM2_StartSession). Used by parameter encryption to tell whether + * the session authorizes its own bind entity. */ session->bind = tpmSession->bind; + session->bindName.size = tpmSession->handle.name.size; + if (session->bindName.size > (UINT16)sizeof(session->bindName.name)) + session->bindName.size = (UINT16)sizeof(session->bindName.name); + XMEMCPY(session->bindName.name, tpmSession->handle.name.name, + session->bindName.size); /* define the symmetric algorithm */ session->authHash = tpmSession->authHash; @@ -2629,7 +2636,13 @@ int wolfTPM2_StartSession_ex(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* session, } } - if (rc == TPM_RC_SUCCESS && keyInSz > 0) { + /* Compute the sessionKey when salted or bound. A bound EmptyAuth session + * still derives a key (KDFa over a zero-length input); only a fully + * unbound, unsalted session keeps an empty sessionKey. Gate on the bind + * handle value (not just the pointer) so a NULL-handle bind matches the + * TPM's own bound-ness test. */ + if (rc == TPM_RC_SUCCESS && + (keyInSz > 0 || (bind != NULL && bind->hndl != TPM_RH_NULL))) { session->handle.auth.size = hashDigestSz; rc = TPM2_KDFa_ex(authSesIn.authHash, keyIn, keyInSz, "ATH", diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index 12c34b7e..08335342 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -544,6 +544,7 @@ typedef struct FWTPM_Session { TPM2B_NONCE nonceCaller; /* Last caller nonce received */ TPM2B_AUTH sessionKey; /* Session HMAC key (from KDFa) */ TPM2B_AUTH bindAuth; /* Auth of bound entity */ + TPM_HANDLE bindHandle; /* Bound entity handle (0 if unbound) */ TPM2B_DIGEST policyDigest; /* Running policy digest (policy sessions) */ int isPasswordPolicy; /* 1 if PolicyPassword was called */ int isAuthValuePolicy; /* 1 if PolicyAuthValue was called */ diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 8d381423..70bebcf8 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -1984,6 +1984,7 @@ typedef struct TPM2_AUTH_SESSION { TPM2B_NAME name; TPM2B_AUTH auth; TPM2B_AUTH* bind; + TPM2B_NAME bindName; /* bind entity Name; see TPM2_ParamEncBindKey */ unsigned int policyAuth : 1; /* if policy auth should be used */ unsigned int policyPass : 1; From 74f428bdfb4dae956743c08d9a7c863a7b66885d Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 25 Jun 2026 11:20:37 -0700 Subject: [PATCH 2/6] Fix TPM2_CreateLoaded response handle for parameter encryption --- src/tpm2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tpm2.c b/src/tpm2.c index 610acaa5..72aed2d7 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -1536,6 +1536,7 @@ TPM_RC TPM2_CreateLoaded(CreateLoaded_In* in, CreateLoaded_Out* out) TPM2_Packet packet; CmdInfo_t info = {0,0,0,0}; info.inHandleCnt = 1; + info.outHandleCnt = 1; /* response returns the loaded object handle */ info.flags = (CMD_FLAG_ENC2 | CMD_FLAG_DEC2 | CMD_FLAG_AUTH_USER1); TPM2_Packet_Init(ctx, &packet); From 0f51da58d074a9e4b949d29bfe13a185fb484398 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 25 Jun 2026 11:20:37 -0700 Subject: [PATCH 3/6] Add PQC parameter encryption and ML-DSA support to examples --- examples/bench/bench.c | 12 ++- examples/keygen/create_primary.c | 49 ++++++++++ examples/keygen/keygen.c | 102 ++++++++++++++++--- examples/nvram/counter.c | 64 +++++++++++- examples/nvram/store.c | 57 ++++++++++- examples/pcr/quote.c | 57 ++++++++++- examples/tpm_test_keys.c | 162 +++++++++++++++++++++++++++++++ examples/tpm_test_keys.h | 23 +++++ examples/wrap/include.am | 3 +- examples/wrap/wrap_test.c | 61 +++++++++++- 10 files changed, 556 insertions(+), 34 deletions(-) diff --git a/examples/bench/bench.c b/examples/bench/bench.c index 64f1868d..0bd56013 100644 --- a/examples/bench/bench.c +++ b/examples/bench/bench.c @@ -215,7 +215,7 @@ static int bench_sym_aes(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* storageKey, return rc; } -#ifdef WOLFTPM_PQC +#ifdef WOLFTPM_MLDSA /* Benchmark Pure ML-DSA: key gen, sign and verify (sign/verify sequence). */ static int bench_pqc_mldsa(WOLFTPM2_DEV* dev, double maxDuration, double maxKeyGenDurSec) @@ -291,7 +291,9 @@ static int bench_pqc_mldsa(WOLFTPM2_DEV* dev, double maxDuration, wolfTPM2_UnloadHandle(dev, &mldsaKey.handle); return rc; } +#endif /* WOLFTPM_MLDSA */ +#ifdef WOLFTPM_MLKEM /* Benchmark ML-KEM: key gen, encapsulate and decapsulate. */ static int bench_pqc_mlkem(WOLFTPM2_DEV* dev, double maxDuration, double maxKeyGenDurSec) @@ -353,7 +355,7 @@ static int bench_pqc_mlkem(WOLFTPM2_DEV* dev, double maxDuration, wolfTPM2_UnloadHandle(dev, &mlkemKey.handle); return rc; } -#endif /* WOLFTPM_PQC */ +#endif /* WOLFTPM_MLKEM */ static void usage(void) { @@ -677,17 +679,21 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) rc = wolfTPM2_UnloadHandle(&dev, &eccKey.handle); if (rc != 0) goto exit; -#ifdef WOLFTPM_PQC +#if defined(WOLFTPM_MLDSA) || defined(WOLFTPM_MLKEM) /* Post-quantum (TPM 2.0 v1.85). Skipped under parameter encryption: the * large PQC public-key responses exceed the parameter-decryption buffer * (TPM_RC_... BUFFER_E). Free the storage key first so the larger PQC keys * fit within the TPM's transient object slots. */ if (paramEncAlg == TPM_ALG_NULL) { wolfTPM2_UnloadHandle(&dev, &storageKey.handle); + #ifdef WOLFTPM_MLDSA rc = bench_pqc_mldsa(&dev, maxDuration, maxKeyGenDurSec); if (rc != 0) goto exit; + #endif + #ifdef WOLFTPM_MLKEM rc = bench_pqc_mlkem(&dev, maxDuration, maxKeyGenDurSec); if (rc != 0) goto exit; + #endif } #endif diff --git a/examples/keygen/create_primary.c b/examples/keygen/create_primary.c index b6c0c4a1..190dfabd 100644 --- a/examples/keygen/create_primary.c +++ b/examples/keygen/create_primary.c @@ -47,6 +47,10 @@ static void usage(void) printf("Primary Key Type:\n"); printf("\t-rsa: Use RSA for asymmetric key generation (DEFAULT)\n"); printf("\t-ecc: Use ECC for asymmetric key generation \n"); +#ifdef WOLFTPM_MLDSA + printf("\t-mldsa[=44|65|87]: Use ML-DSA for primary key generation " + "(v1.85, default 65)\n"); +#endif printf("Hierarchy:\n"); printf("\t-oh: Create keys under the Owner Hierarchy (DEFAULT)\n"); printf("\t-eh: Create keys under the Endorsement Hierarchy\n"); @@ -77,7 +81,25 @@ static void usage(void) printf("\t* Create SRK used by wolfTPM:\n"); printf("\t\tcreate_primary -rsa -oh -auth=ThisIsMyStorageKeyAuth " "-store=0x81000200\n"); +#ifdef WOLFTPM_MLDSA + printf("\t* Create an ML-DSA-65 primary key:\n"); + printf("\t\tcreate_primary -mldsa -oh\n"); +#endif +} + +#ifdef WOLFTPM_MLDSA +static int mldsaParamSet(const char* optVal, TPMI_MLDSA_PARAMETER_SET* ps) +{ + int n = XATOI(optVal); + switch (n) { + case 0: /* missing or empty suffix, use default */ + case 65: *ps = TPM_MLDSA_65; return TPM_RC_SUCCESS; + case 44: *ps = TPM_MLDSA_44; return TPM_RC_SUCCESS; + case 87: *ps = TPM_MLDSA_87; return TPM_RC_SUCCESS; + default: return TPM_RC_FAILURE; + } } +#endif /* WOLFTPM_MLDSA */ int TPM2_CreatePrimaryKey_Example(void* userCtx, int argc, char *argv[]) { @@ -98,6 +120,9 @@ int TPM2_CreatePrimaryKey_Example(void* userCtx, int argc, char *argv[]) #ifdef WOLFTPM_PROVISIONING int useIAKTemplate = 0, useIDevIDTemplate = 0; #endif +#ifdef WOLFTPM_MLDSA + TPMI_MLDSA_PARAMETER_SET mldsaPs = TPM_MLDSA_65; /* default param set */ +#endif if (argc >= 2) { if (XSTRCMP(argv[1], "-?") == 0 || @@ -114,6 +139,19 @@ int TPM2_CreatePrimaryKey_Example(void* userCtx, int argc, char *argv[]) else if (XSTRCMP(argv[argc-1], "-ecc") == 0) { alg = TPM_ALG_ECC; } +#ifdef WOLFTPM_MLDSA + else if (XSTRCMP(argv[argc-1], "-mldsa") == 0 || + XSTRNCMP(argv[argc-1], "-mldsa=", + XSTRLEN("-mldsa=")) == 0) { + const char* optVal = (argv[argc-1][6] == '=') ? + argv[argc-1] + 7 : ""; + if (mldsaParamSet(optVal, &mldsaPs) != TPM_RC_SUCCESS) { + usage(); + return 0; + } + alg = TPM_ALG_MLDSA; + } +#endif else if (XSTRCMP(argv[argc-1], "-aes") == 0) { paramEncAlg = TPM_ALG_CFB; } @@ -221,6 +259,17 @@ int TPM2_CreatePrimaryKey_Example(void* userCtx, int argc, char *argv[]) else rc = wolfTPM2_GetKeyTemplate_ECC_SRK(&publicTemplate); } +#ifdef WOLFTPM_MLDSA + else if (alg == TPM_ALG_MLDSA) { + /* ML-DSA is sign-only and has no AIK/IAK/IDevID variant; the + * wrapper enforces TPMA_OBJECT_sign and clears decrypt. */ + rc = wolfTPM2_GetKeyTemplate_MLDSA(&publicTemplate, + TPMA_OBJECT_sign | TPMA_OBJECT_fixedTPM | + TPMA_OBJECT_fixedParent | TPMA_OBJECT_sensitiveDataOrigin | + TPMA_OBJECT_userWithAuth | TPMA_OBJECT_noDA, + mldsaPs, 0 /* allowExternalMu: 0 avoids TPM_RC_EXT_MU */); + } +#endif else { rc = BAD_FUNC_ARG; } diff --git a/examples/keygen/keygen.c b/examples/keygen/keygen.c index 2c87065c..5a2672af 100644 --- a/examples/keygen/keygen.c +++ b/examples/keygen/keygen.c @@ -75,15 +75,27 @@ static void usage(void) printf("* -sym: Use Symmetric Cipher for key generation\n"); printf("\tDefault Symmetric Cipher is AES CTR with 256 bits\n"); printf("* -keyedhash: Use Keyed Hash for key generation\n"); -#ifdef WOLFTPM_PQC +#ifdef WOLFTPM_MLDSA printf("* -mldsa[=44|65|87]: Use ML-DSA for signing (v1.85, default 65)\n"); +#endif +#ifdef WOLFTPM_HASH_MLDSA printf("* -hash_mldsa[=44|65|87]: Use Hash-ML-DSA for signing " "(v1.85, default 65 with SHA-256 pre-hash)\n"); +#endif +#ifdef WOLFTPM_MLKEM printf("* -mlkem[=512|768|1024]: Use ML-KEM for key encapsulation " "(v1.85, default 768)\n"); #endif printf("* -t: Use default template (otherwise AIK)\n"); printf("* -aes/xor: Use Parameter Encryption\n"); +#ifdef WOLFTPM_MLKEM + printf("* -paramkey=mlkem[=512|768|1024]: Use an ML-KEM key as the " + "param-enc session salt (v1.85, default 768)\n"); +#endif +#ifdef WOLFTPM_MLDSA + printf("* -paramkey=mldsa[=44|65|87]: Use an ML-DSA key as the " + "param-enc session bind (v1.85, default 65)\n"); +#endif printf("* -unique=[value]\n"); printf("\t* Used for the KDF of the create\n"); printf("* -auth=pass: Use custom password for key authentication\n"); @@ -102,17 +114,21 @@ static void usage(void) printf("\t* Symmetric key, AES, CBC mode, 128 bits, "\ "with XOR parameter encryption\n"); printf("\t\t keygen -sym=aescbc256 -xor\n"); -#ifdef WOLFTPM_PQC +#ifdef WOLFTPM_MLDSA printf("\t* ML-DSA-65 signing key\n"); printf("\t\t keygen -mldsa\n"); +#endif +#ifdef WOLFTPM_HASH_MLDSA printf("\t* Hash-ML-DSA-87 with SHA-256 pre-hash\n"); printf("\t\t keygen -hash_mldsa=87\n"); +#endif +#ifdef WOLFTPM_MLKEM printf("\t* ML-KEM-1024 key encapsulation key\n"); printf("\t\t keygen -mlkem=1024\n"); #endif } -#ifdef WOLFTPM_PQC +#ifdef WOLFTPM_MLDSA static int mldsaParamSet(const char* optVal, TPMI_MLDSA_PARAMETER_SET* ps) { int n = XATOI(optVal); @@ -124,7 +140,9 @@ static int mldsaParamSet(const char* optVal, TPMI_MLDSA_PARAMETER_SET* ps) default: return TPM_RC_FAILURE; } } +#endif /* WOLFTPM_MLDSA */ +#ifdef WOLFTPM_MLKEM static int mlkemParamSet(const char* optVal, TPMI_MLKEM_PARAMETER_SET* ps) { int n = XATOI(optVal); @@ -136,7 +154,7 @@ static int mlkemParamSet(const char* optVal, TPMI_MLKEM_PARAMETER_SET* ps) default: return TPM_RC_FAILURE; } } -#endif /* WOLFTPM_PQC */ +#endif /* WOLFTPM_MLKEM */ static int symChoice(const char* symMode, TPM_ALG_ID* algSym, int* keyBits) { @@ -176,8 +194,15 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) TPMI_ALG_PUBLIC srkAlg = TPM_ALG_RSA; /* default matches seal.c / keyload.c */ TPM_ALG_ID algSym = TPM_ALG_CTR; /* default Symmetric Cipher, see usage */ TPM_ALG_ID paramEncAlg = TPM_ALG_NULL; -#ifdef WOLFTPM_PQC +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + TPM_ALG_ID pqcParamEncAlg = TPM_ALG_NULL; + int pqcParamSet = 0; + WOLFTPM2_KEY pqcKey; +#endif +#ifdef WOLFTPM_MLDSA TPMI_MLDSA_PARAMETER_SET mldsaPs = TPM_MLDSA_65; /* default */ +#endif +#ifdef WOLFTPM_MLKEM TPMI_MLKEM_PARAMETER_SET mlkemPs = TPM_MLKEM_768; /* default */ #endif WOLFTPM2_SESSION tpmSession; @@ -228,7 +253,7 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) alg = TPM_ALG_KEYEDHASH; bAIK = 0; } -#ifdef WOLFTPM_PQC +#ifdef WOLFTPM_MLDSA else if (XSTRCMP(argv[argc-1], "-mldsa") == 0 || XSTRNCMP(argv[argc-1], "-mldsa=", XSTRLEN("-mldsa=")) == 0) { @@ -241,6 +266,8 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) alg = TPM_ALG_MLDSA; bAIK = 0; } +#endif /* WOLFTPM_MLDSA */ +#ifdef WOLFTPM_HASH_MLDSA else if (XSTRCMP(argv[argc-1], "-hash_mldsa") == 0 || XSTRNCMP(argv[argc-1], "-hash_mldsa=", XSTRLEN("-hash_mldsa=")) == 0) { @@ -253,6 +280,8 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) alg = TPM_ALG_HASH_MLDSA; bAIK = 0; } +#endif /* WOLFTPM_HASH_MLDSA */ +#ifdef WOLFTPM_MLKEM else if (XSTRCMP(argv[argc-1], "-mlkem") == 0 || XSTRNCMP(argv[argc-1], "-mlkem=", XSTRLEN("-mlkem=")) == 0) { @@ -265,7 +294,7 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) alg = TPM_ALG_MLKEM; bAIK = 0; } -#endif /* WOLFTPM_PQC */ +#endif /* WOLFTPM_MLKEM */ else if (XSTRCMP(argv[argc-1], "-t") == 0) { bAIK = 0; } @@ -281,6 +310,18 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) else if (XSTRCMP(argv[argc-1], "-xor") == 0) { paramEncAlg = TPM_ALG_XOR; } +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + else if (XSTRNCMP(argv[argc-1], "-paramkey=", + XSTRLEN("-paramkey=")) == 0) { + /* PQC key for the param-enc session, e.g. "mlkem=768"/"mldsa=65" */ + const char* pkVal = argv[argc-1] + XSTRLEN("-paramkey="); + if (parsePqcParamSet(pkVal, &pqcParamEncAlg, &pqcParamSet) != 1) { + printf("Invalid -paramkey value: %s\n", pkVal); + usage(); + return 0; + } + } +#endif else if (XSTRNCMP(argv[argc-1], "-unique=", XSTRLEN("-unique=")) == 0) { uniqueStr = argv[argc-1] + XSTRLEN("-unique="); } @@ -310,6 +351,9 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) XMEMSET(&primaryBlob, 0, sizeof(primaryBlob)); XMEMSET(&tpmSession, 0, sizeof(tpmSession)); XMEMSET(&auth, 0, sizeof(auth)); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + XMEMSET(&pqcKey, 0, sizeof(pqcKey)); +#endif /* Only use the ECC SRK for ECC child keys; RSA, SYMCIPHER, KEYEDHASH * all stay on the RSA SRK so that keyload/seal can round-trip them. */ @@ -323,6 +367,16 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) } } +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + /* A PQC param-enc key only supplies the salt/bind material; a symmetric + * session cipher is still required. Default to AES-CFB if none given. */ + if (pqcParamEncAlg != TPM_ALG_NULL && paramEncAlg == TPM_ALG_NULL) { + paramEncAlg = TPM_ALG_CFB; + printf("PQC param-enc key selected; defaulting session cipher to " + "AES-CFB.\n"); + } +#endif + printf("TPM2.0 Key generation example\n"); printf("\tKey Blob: %s\n", outputFile); printf("\tAlgorithm: %s\n", TPM2_GetAlgName(alg)); @@ -364,9 +418,20 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) if (srkAlg == TPM_ALG_RSA) bindKey = NULL; /* cannot bind to key without RSA enabled */ #endif - /* Start an authenticated session (salted / unbound) with parameter encryption */ - rc = wolfTPM2_StartSession(&dev, &tpmSession, bindKey, NULL, - TPM_SE_HMAC, paramEncAlg); + #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + if (pqcParamEncAlg != TPM_ALG_NULL) { + /* Use a PQC primary as the param-enc session key: ML-KEM salt + * or ML-DSA bind. This replaces the salted-to-SRK session. */ + rc = getPrimaryParamEncKey(&dev, &tpmSession, &pqcKey, + pqcParamEncAlg, pqcParamSet, paramEncAlg); + } + else + #endif + { + /* Start an authenticated session (salted / unbound) with parameter encryption */ + rc = wolfTPM2_StartSession(&dev, &tpmSession, bindKey, NULL, + TPM_SE_HMAC, paramEncAlg); + } if (rc != 0) goto exit; printf("HMAC Session: Handle 0x%x\n", (word32)tpmSession.handle.hndl); @@ -405,7 +470,7 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) "not symmetric or keyedhash keys.\n"); rc = BAD_FUNC_ARG; } -#ifdef WOLFTPM_PQC +#if defined(WOLFTPM_MLDSA) || defined(WOLFTPM_MLKEM) else if (alg == TPM_ALG_MLDSA || alg == TPM_ALG_HASH_MLDSA || alg == TPM_ALG_MLKEM) { printf("AIK template is RSA or ECC only; PQC keys use their " @@ -454,7 +519,7 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) TPM_ALG_SHA256, YES, NO); publicTemplate.objectAttributes |= TPMA_OBJECT_sensitiveDataOrigin; } -#ifdef WOLFTPM_PQC +#ifdef WOLFTPM_MLDSA else if (alg == TPM_ALG_MLDSA) { printf("ML-DSA template (parameter set %u)\n", (unsigned)mldsaPs); @@ -462,10 +527,12 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) TPMA_OBJECT_sign | TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | TPMA_OBJECT_noDA, - mldsaPs, 0 /* allowExternalMu — TPM_RC_EXT_MU at create + mldsaPs, 0 /* allowExternalMu - TPM_RC_EXT_MU at create * per Part 2 Sec.12.2.3.6 when SET on a TPM - * without μ-direct sign support */); + * without mu-direct sign support */); } +#endif /* WOLFTPM_MLDSA */ +#ifdef WOLFTPM_HASH_MLDSA else if (alg == TPM_ALG_HASH_MLDSA) { printf("Hash-ML-DSA template (parameter set %u, pre-hash %s)\n", (unsigned)mldsaPs, TPM2_GetAlgName(TPM_ALG_SHA256)); @@ -475,6 +542,8 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) TPMA_OBJECT_userWithAuth | TPMA_OBJECT_noDA, mldsaPs, TPM_ALG_SHA256); } +#endif /* WOLFTPM_HASH_MLDSA */ +#ifdef WOLFTPM_MLKEM else if (alg == TPM_ALG_MLKEM) { printf("ML-KEM template (parameter set %u)\n", (unsigned)mlkemPs); @@ -484,7 +553,7 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) TPMA_OBJECT_userWithAuth | TPMA_OBJECT_noDA, mlkemPs); } -#endif /* WOLFTPM_PQC */ +#endif /* WOLFTPM_MLKEM */ else { rc = BAD_FUNC_ARG; } @@ -613,6 +682,9 @@ int TPM2_Keygen_Example(void* userCtx, int argc, char *argv[]) wolfTPM2_UnloadHandle(&dev, &primary->handle); wolfTPM2_UnloadHandle(&dev, &newKeyBlob.handle); wolfTPM2_UnloadHandle(&dev, &tpmSession.handle); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + wolfTPM2_UnloadHandle(&dev, &pqcKey.handle); +#endif wolfTPM2_Cleanup(&dev); return rc; diff --git a/examples/nvram/counter.c b/examples/nvram/counter.c index 80f04563..59095f4c 100644 --- a/examples/nvram/counter.c +++ b/examples/nvram/counter.c @@ -50,6 +50,14 @@ static void usage(void) printf("./examples/nvram/counter [-nvindex=] [-aes/-xor]\n"); printf("* -nvindex=[handle] (default 0x%x)\n", TPM2_DEMO_NV_COUNTER_INDEX); printf("* -aes/xor: Use Parameter Encryption\n"); +#ifdef WOLFTPM_MLKEM + printf("* -mlkem[=512|768|1024]: Use an ML-KEM key as the param-enc " + "session salt (v1.85, default 768)\n"); +#endif +#ifdef WOLFTPM_MLDSA + printf("* -mldsa[=44|65|87]: Use an ML-DSA key as the param-enc " + "session bind (v1.85, default 65)\n"); +#endif } int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) @@ -63,12 +71,20 @@ int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) TPMS_NV_PUBLIC nvPublic; TPMI_RH_NV_AUTH authHandle = TPM_RH_OWNER; /* or TPM_RH_PLATFORM */ int paramEncAlg = TPM_ALG_NULL; +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + TPM_ALG_ID pqcParamEncAlg = TPM_ALG_NULL; + int pqcParamSet = 0; + WOLFTPM2_KEY pqcKey; +#endif word32 nvIndex = TPM2_DEMO_NV_COUNTER_INDEX; XMEMSET(&tpmSession, 0, sizeof(tpmSession)); XMEMSET(&parent, 0, sizeof(parent)); XMEMSET(&nv, 0, sizeof(nv)); XMEMSET(&storage, 0, sizeof(storage)); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + XMEMSET(&pqcKey, 0, sizeof(pqcKey)); +#endif if (argc >= 2) { if (XSTRCMP(argv[1], "-?") == 0 || @@ -104,6 +120,17 @@ int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) else if (XSTRCMP(argv[argc-1], "-xor") == 0) { paramEncAlg = TPM_ALG_XOR; } +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + else if (parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, + &pqcParamSet) != 0) { + /* PQC option; an unsupported parameter set (alg left NULL) is a + * fatal typo, not a silent drop of parameter encryption. */ + if (pqcParamEncAlg == TPM_ALG_NULL) { + usage(); + return 0; + } + } +#endif else { printf("Warning: Unrecognized option: %s\n", argv[argc-1]); } @@ -111,6 +138,16 @@ int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) argc--; } +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + /* A PQC param-enc key only supplies the salt/bind material; a symmetric + * session cipher is still required. Default to AES-CFB if none given. */ + if (pqcParamEncAlg != TPM_ALG_NULL && paramEncAlg == TPM_ALG_NULL) { + paramEncAlg = TPM_ALG_CFB; + printf("PQC param-enc key selected; defaulting session cipher to " + "AES-CFB.\n"); + } +#endif + printf("NV Counter: NV Index 0x%x\n", nvIndex); if (paramEncAlg == TPM_ALG_CFB) { printf("Parameter Encryption: Enabled. (AES CFB)\n\n"); @@ -130,9 +167,20 @@ int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) } if (paramEncAlg != TPM_ALG_NULL) { - /* Start TPM session for parameter encryption */ - rc = wolfTPM2_StartSession(&dev, &tpmSession, NULL, NULL, - TPM_SE_HMAC, paramEncAlg); + #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + if (pqcParamEncAlg != TPM_ALG_NULL) { + /* Use a PQC primary as the param-enc session key: ML-KEM salt + * or ML-DSA bind. */ + rc = getPrimaryParamEncKey(&dev, &tpmSession, &pqcKey, + pqcParamEncAlg, pqcParamSet, paramEncAlg); + } + else + #endif + { + /* Start TPM session for parameter encryption */ + rc = wolfTPM2_StartSession(&dev, &tpmSession, NULL, NULL, + TPM_SE_HMAC, paramEncAlg); + } if (rc != 0) goto exit; printf("TPM2_StartAuthSession: sessionHandle 0x%x\n", (word32)tpmSession.handle.hndl); @@ -160,8 +208,6 @@ int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) nvAttributes, 8, (byte*)gNvAuth, sizeof(gNvAuth)-1); if (rc != 0) goto exit; - wolfTPM2_SetAuthHandle(&dev, 0, &nv.handle); - rc = wolfTPM2_NVReadPublic(&dev, nvIndex, &nvPublic); } if (rc != TPM_RC_SUCCESS) { @@ -178,6 +224,11 @@ int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) goto exit; } + /* Set the NV index auth in slot 0 (slot 1 holds the param-enc session). A + * salted/PQC param-enc session leaves slot 0 pointing at the salt key, so + * reset it here for both the create and existing-index paths. */ + wolfTPM2_SetAuthHandle(&dev, 0, &nv.handle); + rc = wolfTPM2_NVIncrement(&dev, &nv); if (rc != TPM_RC_SUCCESS) { printf("NV Increment failed\n"); @@ -191,6 +242,9 @@ int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) } wolfTPM2_UnloadHandle(&dev, &tpmSession.handle); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + wolfTPM2_UnloadHandle(&dev, &pqcKey.handle); +#endif wolfTPM2_Cleanup(&dev); return rc; diff --git a/examples/nvram/store.c b/examples/nvram/store.c index ba965901..383db785 100644 --- a/examples/nvram/store.c +++ b/examples/nvram/store.c @@ -57,6 +57,14 @@ static void usage(void) printf("* -priv: Store only the private part of the key\n"); printf("* -pub: Store only the public part of the key\n"); printf("* -aes/xor: Use Parameter Encryption\n"); +#ifdef WOLFTPM_MLKEM + printf("* -mlkem[=512|768|1024]: Use an ML-KEM key as the param-enc " + "session salt (v1.85, default 768)\n"); +#endif +#ifdef WOLFTPM_MLDSA + printf("* -mldsa[=44|65|87]: Use an ML-DSA key as the param-enc " + "session bind (v1.85, default 65)\n"); +#endif } int TPM2_NVRAM_Store_Example(void* userCtx, int argc, char *argv[]) @@ -71,6 +79,11 @@ int TPM2_NVRAM_Store_Example(void* userCtx, int argc, char *argv[]) TPMI_RH_NV_AUTH authHandle = TPM_RH_OWNER; /* or TPM_RH_PLATFORM */ const char* filename = "keyblob.bin"; int paramEncAlg = TPM_ALG_NULL; +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + TPM_ALG_ID pqcParamEncAlg = TPM_ALG_NULL; + int pqcParamSet = 0; + WOLFTPM2_KEY pqcKey; +#endif int partialStore = 0; int offset = 0; /* Needed for TPM2_AppendPublic */ @@ -121,6 +134,17 @@ int TPM2_NVRAM_Store_Example(void* userCtx, int argc, char *argv[]) else if (XSTRCMP(argv[argc-1], "-pub") == 0) { partialStore = PUBLIC_PART_ONLY; } +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + else if (parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, + &pqcParamSet) != 0) { + /* PQC option; an unsupported parameter set (alg left NULL) is a + * fatal typo, not a silent drop of parameter encryption. */ + if (pqcParamEncAlg == TPM_ALG_NULL) { + usage(); + return 0; + } + } +#endif else if (argv[argc-1][0] != '-') { filename = argv[argc-1]; } @@ -130,6 +154,16 @@ int TPM2_NVRAM_Store_Example(void* userCtx, int argc, char *argv[]) argc--; }; +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + /* A PQC param-enc key only supplies the salt/bind material; a symmetric + * session cipher is still required. Default to AES-CFB if none given. */ + if (pqcParamEncAlg != TPM_ALG_NULL && paramEncAlg == TPM_ALG_NULL) { + paramEncAlg = TPM_ALG_CFB; + printf("PQC param-enc key selected; defaulting session cipher to " + "AES-CFB.\n"); + } +#endif + if (paramEncAlg == TPM_ALG_CFB) { printf("Parameter Encryption: Enabled. (AES CFB)\n\n"); } @@ -144,6 +178,9 @@ int TPM2_NVRAM_Store_Example(void* userCtx, int argc, char *argv[]) XMEMSET(&keyBlob, 0, sizeof(keyBlob)); XMEMSET(&tpmSession, 0, sizeof(tpmSession)); XMEMSET(&parent, 0, sizeof(parent)); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + XMEMSET(&pqcKey, 0, sizeof(pqcKey)); +#endif rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); if (rc != TPM_RC_SUCCESS) { @@ -152,9 +189,20 @@ int TPM2_NVRAM_Store_Example(void* userCtx, int argc, char *argv[]) } if (paramEncAlg != TPM_ALG_NULL) { - /* Start TPM session for parameter encryption */ - rc = wolfTPM2_StartSession(&dev, &tpmSession, NULL, NULL, - TPM_SE_HMAC, paramEncAlg); + #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + if (pqcParamEncAlg != TPM_ALG_NULL) { + /* Use a PQC primary as the param-enc session key: ML-KEM salt + * or ML-DSA bind. */ + rc = getPrimaryParamEncKey(&dev, &tpmSession, &pqcKey, + pqcParamEncAlg, pqcParamSet, paramEncAlg); + } + else + #endif + { + /* Start TPM session for parameter encryption */ + rc = wolfTPM2_StartSession(&dev, &tpmSession, NULL, NULL, + TPM_SE_HMAC, paramEncAlg); + } if (rc != 0) goto exit; printf("TPM2_StartAuthSession: sessionHandle 0x%x\n", (word32)tpmSession.handle.hndl); @@ -246,6 +294,9 @@ int TPM2_NVRAM_Store_Example(void* userCtx, int argc, char *argv[]) } wolfTPM2_UnloadHandle(&dev, &tpmSession.handle); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + wolfTPM2_UnloadHandle(&dev, &pqcKey.handle); +#endif wolfTPM2_Cleanup(&dev); return rc; diff --git a/examples/pcr/quote.c b/examples/pcr/quote.c index 37160cd0..634d9e14 100644 --- a/examples/pcr/quote.c +++ b/examples/pcr/quote.c @@ -49,6 +49,14 @@ static void usage(void) printf("* filename: for saving the TPMS_ATTEST structure to a file\n"); printf("* -ecc: Use RSA or ECC for SRK/AIK\n"); printf("* -aes/xor: Use Parameter Encryption\n"); +#ifdef WOLFTPM_MLKEM + printf("* -mlkem[=512|768|1024]: Use an ML-KEM key as the param-enc " + "session salt (v1.85, default 768)\n"); +#endif +#ifdef WOLFTPM_MLDSA + printf("* -mldsa[=44|65|87]: Use an ML-DSA key as the param-enc " + "session bind (v1.85, default 65)\n"); +#endif printf("Demo usage without parameters, generates quote over PCR%d and\n" "saves the output TPMS_ATTEST structure to \"quote.blob\" file.\n", TPM2_TEST_PCR); @@ -80,6 +88,11 @@ int TPM2_PCR_Quote_Test(void* userCtx, int argc, char *argv[]) } cmdOut; TPM_ALG_ID paramEncAlg = TPM_ALG_NULL; WOLFTPM2_SESSION tpmSession; +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + TPM_ALG_ID pqcParamEncAlg = TPM_ALG_NULL; + int pqcParamSet = 0; + WOLFTPM2_KEY pqcKey; +#endif #if !defined(NO_FILESYSTEM) && !defined(NO_WRITE_TEMP_FILES) XFILE f; #endif @@ -87,6 +100,9 @@ int TPM2_PCR_Quote_Test(void* userCtx, int argc, char *argv[]) XMEMSET(&storage, 0, sizeof(storage)); XMEMSET(&aik, 0, sizeof(aik)); XMEMSET(&tpmSession, 0, sizeof(tpmSession)); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + XMEMSET(&pqcKey, 0, sizeof(pqcKey)); +#endif if (argc >= 2) { if (XSTRCMP(argv[1], "-?") == 0 || @@ -123,9 +139,30 @@ int TPM2_PCR_Quote_Test(void* userCtx, int argc, char *argv[]) else if (XSTRCMP(argv[argc-1], "-xor") == 0) { paramEncAlg = TPM_ALG_XOR; } +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + else if (parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, + &pqcParamSet) != 0) { + /* PQC option; an unsupported parameter set (alg left NULL) is a + * fatal typo, not a silent drop of parameter encryption. */ + if (pqcParamEncAlg == TPM_ALG_NULL) { + usage(); + return 0; + } + } +#endif argc--; } +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + /* A PQC param-enc key only supplies the salt/bind material; a symmetric + * session cipher is still required. Default to AES-CFB if none given. */ + if (pqcParamEncAlg != TPM_ALG_NULL && paramEncAlg == TPM_ALG_NULL) { + paramEncAlg = TPM_ALG_CFB; + printf("PQC param-enc key selected; defaulting session cipher to " + "AES-CFB.\n"); + } +#endif + printf("PCR Quote example - Demo of signed PCR measurement\n"); printf("\tOutput file: %s\n", outputFile); printf("\tPCR Index: %d\n", pcrIndex); @@ -198,9 +235,20 @@ int TPM2_PCR_Quote_Test(void* userCtx, int argc, char *argv[]) if (alg == TPM_ALG_RSA) bindKey = NULL; /* cannot bind to key without RSA enabled */ #endif - /* Start an authenticated session (salted / unbound) with parameter encryption */ - rc = wolfTPM2_StartSession(&dev, &tpmSession, bindKey, NULL, - TPM_SE_HMAC, paramEncAlg); + #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + if (pqcParamEncAlg != TPM_ALG_NULL) { + /* Use a PQC primary as the param-enc session key: ML-KEM salt + * or ML-DSA bind. The RSA/ECC SRK stays the AIK parent. */ + rc = getPrimaryParamEncKey(&dev, &tpmSession, &pqcKey, + pqcParamEncAlg, pqcParamSet, paramEncAlg); + } + else + #endif + { + /* Start an authenticated session (salted / unbound) with parameter encryption */ + rc = wolfTPM2_StartSession(&dev, &tpmSession, bindKey, NULL, + TPM_SE_HMAC, paramEncAlg); + } if (rc != 0) goto exit; printf("TPM2_StartAuthSession: sessionHandle 0x%x\n", (word32)tpmSession.handle.hndl); @@ -334,6 +382,9 @@ int TPM2_PCR_Quote_Test(void* userCtx, int argc, char *argv[]) wolfTPM2_UnloadHandle(&dev, &aik.handle); wolfTPM2_UnloadHandle(&dev, &storage.handle); wolfTPM2_UnloadHandle(&dev, &tpmSession.handle); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + wolfTPM2_UnloadHandle(&dev, &pqcKey.handle); +#endif wolfTPM2_Cleanup(&dev); diff --git a/examples/tpm_test_keys.c b/examples/tpm_test_keys.c index 171252d8..3f3f000e 100644 --- a/examples/tpm_test_keys.c +++ b/examples/tpm_test_keys.c @@ -430,6 +430,168 @@ int getPrimaryStoragekey(WOLFTPM2_DEV* pDev, WOLFTPM2_KEY* pStorageKey, return rc; } +#if defined(WOLFTPM_MLDSA) || defined(WOLFTPM_MLKEM) +int getPrimaryParamEncKey(WOLFTPM2_DEV* pDev, WOLFTPM2_SESSION* session, + WOLFTPM2_KEY* pqcKey, TPM_ALG_ID pqcAlg, int paramSet, int paramEncAlg) +{ + int rc; + TPMT_PUBLIC publicTemplate; +#ifdef WOLFTPM_MLDSA + WOLFTPM2_KEY saltKey; /* transient SRK salt for the ML-DSA bound session */ +#if !defined(NO_RSA) + TPM_ALG_ID saltAlg = TPM_ALG_RSA; +#else + TPM_ALG_ID saltAlg = TPM_ALG_ECC; +#endif +#endif + const char* pqcAuth = "pqcParamEnc"; /* primary userAuth; the bind auth for + * ML-DSA, immaterial for the ML-KEM + * salt key */ + + if (pDev == NULL || session == NULL || pqcKey == NULL) + return BAD_FUNC_ARG; + + XMEMSET(pqcKey, 0, sizeof(*pqcKey)); + XMEMSET(&publicTemplate, 0, sizeof(publicTemplate)); +#ifdef WOLFTPM_MLDSA + XMEMSET(&saltKey, 0, sizeof(saltKey)); +#endif + + if (pqcAlg == TPM_ALG_MLKEM) { + #ifdef WOLFTPM_MLKEM + /* ML-KEM salt key: restricted decryption, which requires a symmetric + * definition (a TPM rejects a restricted key without one). */ + rc = wolfTPM2_GetKeyTemplate_MLKEM(&publicTemplate, + TPMA_OBJECT_decrypt | TPMA_OBJECT_restricted | + TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent | + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_noDA, (TPMI_MLKEM_PARAMETER_SET)paramSet); + if (rc == TPM_RC_SUCCESS) { + publicTemplate.parameters.mlkemDetail.symmetric.algorithm = + TPM_ALG_AES; + publicTemplate.parameters.mlkemDetail.symmetric.keyBits.aes = 128; + publicTemplate.parameters.mlkemDetail.symmetric.mode.aes = + TPM_ALG_CFB; + } + #else + (void)paramSet; + return NOT_COMPILED_IN; + #endif + } + else if (pqcAlg == TPM_ALG_MLDSA) { + #ifdef WOLFTPM_MLDSA + /* ML-DSA is sign-only; it can only be a session bind entity. */ + rc = wolfTPM2_GetKeyTemplate_MLDSA(&publicTemplate, + TPMA_OBJECT_sign | TPMA_OBJECT_fixedTPM | + TPMA_OBJECT_fixedParent | TPMA_OBJECT_sensitiveDataOrigin | + TPMA_OBJECT_userWithAuth | TPMA_OBJECT_noDA, + (TPMI_MLDSA_PARAMETER_SET)paramSet, 0); + #else + (void)paramSet; + return NOT_COMPILED_IN; + #endif + } + else { + return BAD_FUNC_ARG; + } + if (rc != TPM_RC_SUCCESS) + return rc; + + rc = wolfTPM2_CreatePrimaryKey(pDev, pqcKey, TPM_RH_OWNER, &publicTemplate, + (const byte*)pqcAuth, (int)XSTRLEN(pqcAuth)); + if (rc != TPM_RC_SUCCESS) + return rc; + printf("PQC param-enc key: %s primary 0x%x\n", TPM2_GetAlgName(pqcAlg), + (word32)pqcKey->handle.hndl); + + /* ML-KEM keys the session salt (tpmKey). ML-DSA is sign-only so it can + * only bind; a transient SRK provides the salt (confidentiality) while the + * ML-DSA key supplies the binding. */ + if (pqcAlg == TPM_ALG_MLKEM) { + rc = wolfTPM2_StartSession(pDev, session, pqcKey, NULL, + TPM_SE_HMAC, paramEncAlg); + } + else { + #ifdef WOLFTPM_MLDSA + rc = wolfTPM2_CreateSRK(pDev, &saltKey, saltAlg, NULL, 0); + if (rc == TPM_RC_SUCCESS) { + rc = wolfTPM2_StartSession(pDev, session, &saltKey, + &pqcKey->handle, TPM_SE_HMAC, paramEncAlg); + /* salt is baked into the session key; drop the salt key handle */ + wolfTPM2_UnloadHandle(pDev, &saltKey.handle); + } + #endif + } + if (rc != TPM_RC_SUCCESS) { + printf("PQC param-enc StartSession failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + wolfTPM2_UnloadHandle(pDev, &pqcKey->handle); + XMEMSET(pqcKey, 0, sizeof(*pqcKey)); + } + return rc; +} + +/* Returns 1 and sets the alg and paramSet outputs for a valid PQC value, -1 if + * the value names a PQC algorithm but with an unsupported parameter set + * (message printed), or 0 if it is not a PQC option. Outputs are committed + * only on success. An empty suffix ("mlkem=") uses the default like "mlkem". */ +int parsePqcParamSet(const char* val, TPM_ALG_ID* alg, int* paramSet) +{ + int n; + int localSet; + + if (val == NULL || alg == NULL || paramSet == NULL) + return 0; +#ifdef WOLFTPM_MLKEM + if (XSTRCMP(val, "mlkem") == 0 || + XSTRNCMP(val, "mlkem=", 6) == 0) { + n = (val[5] == '=' && val[6] != '\0') ? XATOI(&val[6]) : 768; + if (n == 512) + localSet = TPM_MLKEM_512; + else if (n == 768) + localSet = TPM_MLKEM_768; + else if (n == 1024) + localSet = TPM_MLKEM_1024; + else { + printf("Invalid ML-KEM parameter set: %d (use 512, 768, or 1024)\n", + n); + return -1; + } + *alg = TPM_ALG_MLKEM; + *paramSet = localSet; + return 1; + } +#endif +#ifdef WOLFTPM_MLDSA + if (XSTRCMP(val, "mldsa") == 0 || + XSTRNCMP(val, "mldsa=", 6) == 0) { + n = (val[5] == '=' && val[6] != '\0') ? XATOI(&val[6]) : 65; + if (n == 44) + localSet = TPM_MLDSA_44; + else if (n == 65) + localSet = TPM_MLDSA_65; + else if (n == 87) + localSet = TPM_MLDSA_87; + else { + printf("Invalid ML-DSA parameter set: %d (use 44, 65, or 87)\n", n); + return -1; + } + *alg = TPM_ALG_MLDSA; + *paramSet = localSet; + return 1; + } +#endif + return 0; +} + +int parsePqcParamEncArg(const char* arg, TPM_ALG_ID* alg, int* paramSet) +{ + if (arg == NULL || arg[0] != '-') + return 0; + return parsePqcParamSet(arg + 1, alg, paramSet); +} +#endif /* WOLFTPM_MLDSA || WOLFTPM_MLKEM */ + int getRSAkey(WOLFTPM2_DEV* pDev, WOLFTPM2_KEY* pStorageKey, WOLFTPM2_KEY* key, void* pWolfRsaKey, int tpmDevId, const byte* auth, int authSz, TPMT_PUBLIC* publicTemplate) diff --git a/examples/tpm_test_keys.h b/examples/tpm_test_keys.h index edd11f7a..375f5496 100644 --- a/examples/tpm_test_keys.h +++ b/examples/tpm_test_keys.h @@ -52,6 +52,29 @@ WOLFTPM_LOCAL int getPrimaryStoragekey(WOLFTPM2_DEV* pDev, WOLFTPM2_KEY* pStorageKey, TPM_ALG_ID alg); +#if defined(WOLFTPM_MLDSA) || defined(WOLFTPM_MLKEM) +/* Create a transient PQC primary and start an HMAC param-enc session using it: + * ML-KEM as the session salt key, ML-DSA as the bind key (with a transient SRK + * salt, since sign-only ML-DSA cannot supply one). The session is started but + * not assigned a slot; the caller calls wolfTPM2_SetAuthSession() and later + * unloads pqcKey->handle and session->handle. */ +WOLFTPM_LOCAL int getPrimaryParamEncKey(WOLFTPM2_DEV* pDev, + WOLFTPM2_SESSION* session, + WOLFTPM2_KEY* pqcKey, + TPM_ALG_ID pqcAlg, + int paramSet, + int paramEncAlg); + +/* Parse a PQC parameter-set value ("mlkem[=512|768|1024]" / + * "mldsa[=44|65|87]"). Returns 1 on a valid value (sets the alg and paramSet + * outputs), -1 if a PQC algorithm is named with an unsupported set (printed), + * or 0 if not a PQC value. parsePqcParamEncArg() takes the "-" option form. */ +WOLFTPM_LOCAL int parsePqcParamSet(const char* val, TPM_ALG_ID* alg, + int* paramSet); +WOLFTPM_LOCAL int parsePqcParamEncArg(const char* arg, TPM_ALG_ID* alg, + int* paramSet); +#endif + WOLFTPM_LOCAL int getRSAkey(WOLFTPM2_DEV* pDev, WOLFTPM2_KEY* pStorageKey, WOLFTPM2_KEY* key, diff --git a/examples/wrap/include.am b/examples/wrap/include.am index c40889ab..415e2283 100644 --- a/examples/wrap/include.am +++ b/examples/wrap/include.am @@ -10,7 +10,8 @@ noinst_PROGRAMS += examples/wrap/wrap_test \ examples/wrap/encrypt_decrypt noinst_HEADERS += examples/wrap/wrap_test.h -examples_wrap_wrap_test_SOURCES = examples/wrap/wrap_test.c +examples_wrap_wrap_test_SOURCES = examples/wrap/wrap_test.c \ + examples/tpm_test_keys.c examples_wrap_wrap_test_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_wrap_wrap_test_DEPENDENCIES = src/libwolftpm.la diff --git a/examples/wrap/wrap_test.c b/examples/wrap/wrap_test.c index f1295475..7ed67738 100644 --- a/examples/wrap/wrap_test.c +++ b/examples/wrap/wrap_test.c @@ -35,6 +35,7 @@ #include #include +#include #include /* Configuration */ @@ -50,9 +51,16 @@ static void usage(void) { printf("Expected Usage:\n"); - printf("./examples/wrap/wrap_test [-aes/xor]\n"); + printf("./examples/wrap/wrap_test [-aes/xor] [-mlkem/-mldsa]\n"); printf("* -aes/xor: Use Parameter Encryption\n"); - +#ifdef WOLFTPM_MLKEM + printf("* -mlkem[=512|768|1024]: Use an ML-KEM key as the param-enc " + "session salt (v1.85, default 768)\n"); +#endif +#ifdef WOLFTPM_MLDSA + printf("* -mldsa[=44|65|87]: Use an ML-DSA key as the param-enc " + "session bind (v1.85, default 65)\n"); +#endif } int TPM2_Wrapper_Test(void* userCtx) @@ -125,11 +133,21 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) #endif /* !WOLFTPM2_NO_WOLFCRYPT */ TPM_ALG_ID paramEncAlg = TPM_ALG_NULL; WOLFTPM2_SESSION tpmSession; +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + /* Optional PQC key used as the parameter-encryption session salt + * (ML-KEM) or bind (ML-DSA) key. */ + TPM_ALG_ID pqcParamEncAlg = TPM_ALG_NULL; + int pqcParamSet = 0; + WOLFTPM2_KEY pqcKey; +#endif XMEMSET(&rsaKey, 0, sizeof(rsaKey)); XMEMSET(&eccKey, 0, sizeof(eccKey)); XMEMSET(&aesKey, 0, sizeof(aesKey)); XMEMSET(&publicKey, 0, sizeof(publicKey)); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + XMEMSET(&pqcKey, 0, sizeof(pqcKey)); +#endif #ifndef WOLFTPM2_NO_WOLFCRYPT #ifndef NO_RSA XMEMSET(&wolfRsaPubKey, 0, sizeof(wolfRsaPubKey)); @@ -159,12 +177,33 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) else if (XSTRCMP(argv[argc-1], "-xor") == 0) { paramEncAlg = TPM_ALG_XOR; } +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + else if (parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, + &pqcParamSet) != 0) { + /* PQC option; an unsupported parameter set (alg left NULL) is a + * fatal typo, not a silent drop of parameter encryption. */ + if (pqcParamEncAlg == TPM_ALG_NULL) { + usage(); + return 0; + } + } +#endif else { printf("Warning: Unrecognized option: %s\n", argv[argc-1]); } argc--; } +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + /* A PQC param-enc key only supplies the salt/bind material; a symmetric + * session cipher is still required. Default to AES-CFB if none given. */ + if (pqcParamEncAlg != TPM_ALG_NULL && paramEncAlg == TPM_ALG_NULL) { + paramEncAlg = TPM_ALG_CFB; + printf("PQC param-enc key selected; defaulting session cipher to " + "AES-CFB.\n"); + } +#endif + printf("TPM2 Demo for Wrapper API's\n"); @@ -265,8 +304,19 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) #ifdef NO_RSA bindKey = NULL; /* cannot bind to key without RSA enabled */ #endif - rc = wolfTPM2_StartSession(&dev, &tpmSession, bindKey, NULL, - TPM_SE_HMAC, paramEncAlg); + #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + if (pqcParamEncAlg != TPM_ALG_NULL) { + /* Use a PQC primary as the param-enc session key: ML-KEM salt + * or ML-DSA bind. The RSA storageKey stays the child parent. */ + rc = getPrimaryParamEncKey(&dev, &tpmSession, &pqcKey, + pqcParamEncAlg, pqcParamSet, paramEncAlg); + } + else + #endif + { + rc = wolfTPM2_StartSession(&dev, &tpmSession, bindKey, NULL, + TPM_SE_HMAC, paramEncAlg); + } if (rc != 0) goto exit; printf("TPM2_StartAuthSession: sessionHandle 0x%x\n", (word32)tpmSession.handle.hndl); @@ -1036,6 +1086,9 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) wolfTPM2_UnloadHandle(&dev, &eccKey.handle); wolfTPM2_UnloadHandle(&dev, &ekKey.handle); wolfTPM2_UnloadHandle(&dev, &tpmSession.handle); +#if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) + wolfTPM2_UnloadHandle(&dev, &pqcKey.handle); +#endif /* Only doShutdown=1: Just shutdown the TPM */ wolfTPM2_Reset(&dev, 1, 0); From 9ddd51b06d882cbc5d462107a80602574cf2b4a9 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 25 Jun 2026 11:20:37 -0700 Subject: [PATCH 4/6] Add parameter-encryption regression tests and PQC example coverage --- examples/run_examples.sh | 53 +++++++ tests/unit_tests.c | 288 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 336 insertions(+), 5 deletions(-) diff --git a/examples/run_examples.sh b/examples/run_examples.sh index a6379dbf..bdc3e12b 100755 --- a/examples/run_examples.sh +++ b/examples/run_examples.sh @@ -376,6 +376,59 @@ if [ $ENABLE_V185 -eq 1 ]; then [ $RESULT -ne 0 ] && echo -e "mlkem_encap mlkem=$PS failed! $RESULT" && exit 1 done + echo -e "PQC primary key (create_primary -mldsa)" + for PS in 44 65 87; do + ./examples/keygen/create_primary -mldsa=$PS -oh >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "create_primary mldsa=$PS failed! $RESULT" && exit 1 + done + + echo -e "PQC parameter encryption (ML-KEM salt / ML-DSA bind)" + # ML-KEM as the param-enc session salt, ML-DSA as the param-enc session + # bind; exercise AES-CFB and XOR across child-create, attestation and NV. + # The ML-DSA primary used as the bind entity has an EmptyAuth, so the + # -mldsa cases below are the regression for the bound/EmptyAuth sessionKey + # derivation: pre-fix these failed with a param-enc HMAC mismatch. + ./examples/wrap/wrap_test -aes -mlkem=768 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "wrap_test -aes -mlkem failed! $RESULT" && exit 1 + ./examples/wrap/wrap_test -xor -mldsa=65 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "wrap_test -xor -mldsa failed! $RESULT" && exit 1 + ./examples/pcr/quote 16 quote.blob -ecc -aes -mlkem=768 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "quote -aes -mlkem failed! $RESULT" && exit 1 + ./examples/pcr/quote 16 quote.blob -ecc -xor -mldsa=65 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "quote -xor -mldsa failed! $RESULT" && exit 1 + ./examples/nvram/counter -aes -mlkem=768 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "counter -aes -mlkem failed! $RESULT" && exit 1 + ./examples/nvram/counter -xor -mldsa=65 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "counter -xor -mldsa failed! $RESULT" && exit 1 + # store/read round-trip (uses the keyblob.bin kept from earlier), both the + # ML-DSA bind and ML-KEM salt param-enc paths; read destroys the NV index. + ./examples/nvram/store -aes -mldsa=65 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "store -aes -mldsa failed! $RESULT" && exit 1 + ./examples/nvram/read -aes >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "read -aes (after store -mldsa) failed! $RESULT" && exit 1 + ./examples/nvram/store -xor -mlkem=768 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "store -xor -mlkem failed! $RESULT" && exit 1 + ./examples/nvram/read -xor >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "read -xor (after store -mlkem) failed! $RESULT" && exit 1 + ./examples/keygen/keygen pqcpe.bin -ecc -aes -paramkey=mlkem=768 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "keygen -paramkey=mlkem failed! $RESULT" && exit 1 + ./examples/keygen/keygen pqcpe.bin -ecc -xor -paramkey=mldsa=65 >> $TPMPWD/run.out 2>&1 + RESULT=$? + [ $RESULT -ne 0 ] && echo -e "keygen -paramkey=mldsa failed! $RESULT" && exit 1 + rm -f pqcpe.bin quote.blob + echo -e "PQC negative verify (mldsa_verify_neg)" for PS in 44 65 87; do ./examples/pqc/mldsa_verify_neg -mldsa=$PS >> $TPMPWD/run.out 2>&1 diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 056c081e..6121e7cb 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -733,6 +733,277 @@ static void test_wolfTPM2_StartSession_ex_authHash(void) #endif } +/* Bind an AES-CFB param-enc session to an EmptyAuth SRK and create a child + * under it. The pre-fix code left a bound EmptyAuth sessionKey empty, breaking + * the HMAC; this command fails pre-fix. */ +static void test_wolfTPM2_BoundSession_EmptyAuth_ParamEnc(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && \ + (!defined(NO_RSA) || defined(HAVE_ECC)) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY srk; + WOLFTPM2_KEY child; + WOLFTPM2_SESSION session; + TPMT_PUBLIC publicTemplate; +#if !defined(NO_RSA) + TPM_ALG_ID srkAlg = TPM_ALG_RSA; +#else + TPM_ALG_ID srkAlg = TPM_ALG_ECC; +#endif + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&srk, 0, sizeof(srk)); + XMEMSET(&child, 0, sizeof(child)); + XMEMSET(&session, 0, sizeof(session)); + XMEMSET(&publicTemplate, 0, sizeof(publicTemplate)); + + /* Skip cleanly when no TPM is reachable. */ + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + if (rc != 0) { + printf("Test TPM Wrapper:\tBound EmptyAuth param-enc:\tSkipped\n"); + return; + } + + /* Storage root key with an EmptyAuth (auth NULL, authSz 0). */ + rc = wolfTPM2_CreateSRK(&dev, &srk, srkAlg, NULL, 0); + if (rc != 0) { + /* Environmental (TPM busy / unsupported). Treat as skip. */ + wolfTPM2_Cleanup(&dev); + printf("Test TPM Wrapper:\tBound EmptyAuth param-enc:\tSkipped\n"); + return; + } + + /* Bind an HMAC session to the EmptyAuth SRK with AES-CFB param enc. */ + rc = wolfTPM2_StartSession(&dev, &session, NULL, &srk.handle, + TPM_SE_HMAC, TPM_ALG_CFB); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + /* Slot 1: the create/load wrappers own slot 0 (parent auth), so the + * param-enc session lives in slot 1 to survive into the command. */ + rc = wolfTPM2_SetAuthSession(&dev, 1, &session, + (TPMA_SESSION_decrypt | TPMA_SESSION_encrypt | + TPMA_SESSION_continueSession)); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + /* Create+load a child under the EmptyAuth SRK; fails pre-fix. */ +#if !defined(NO_RSA) + rc = wolfTPM2_GetKeyTemplate_RSA(&publicTemplate, + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_sign | TPMA_OBJECT_noDA); +#else + rc = wolfTPM2_GetKeyTemplate_ECC(&publicTemplate, + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_sign | TPMA_OBJECT_noDA, + TPM_ECC_NIST_P256, TPM_ALG_ECDSA); +#endif + AssertIntEQ(rc, TPM_RC_SUCCESS); + + rc = wolfTPM2_CreateAndLoadKey(&dev, &child, &srk.handle, + &publicTemplate, NULL, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + /* Clear the session slot, then release handles. */ + wolfTPM2_SetAuthSession(&dev, 1, NULL, 0); + wolfTPM2_UnloadHandle(&dev, &child.handle); + wolfTPM2_UnloadHandle(&dev, &session.handle); + wolfTPM2_UnloadHandle(&dev, &srk.handle); + wolfTPM2_Cleanup(&dev); + printf("Test TPM Wrapper:\tBound EmptyAuth param-enc:\tPassed\n"); +#endif +} + +/* Run TPM2_CreateLoaded under a salted AES-CFB param-enc session. Pre-fix the + * missing response outHandleCnt mis-parsed the rpHash offset and the reply was + * rejected with TPM_RC_HMAC. */ +static void test_wolfTPM2_CreateLoaded_ParamEnc(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && \ + (!defined(NO_RSA) || defined(HAVE_ECC)) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY srk; + WOLFTPM2_KEYBLOB child; + WOLFTPM2_SESSION session; + TPMT_PUBLIC publicTemplate; +#if !defined(NO_RSA) + TPM_ALG_ID srkAlg = TPM_ALG_RSA; +#else + TPM_ALG_ID srkAlg = TPM_ALG_ECC; +#endif + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&srk, 0, sizeof(srk)); + XMEMSET(&child, 0, sizeof(child)); + XMEMSET(&session, 0, sizeof(session)); + XMEMSET(&publicTemplate, 0, sizeof(publicTemplate)); + + /* Skip cleanly when no TPM is reachable. */ + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + if (rc != 0) { + printf("Test TPM Wrapper:\tCreateLoaded param-enc:\tSkipped\n"); + return; + } + + rc = wolfTPM2_CreateSRK(&dev, &srk, srkAlg, NULL, 0); + if (rc != 0) { + /* Environmental (TPM busy / unsupported). Treat as skip. */ + wolfTPM2_Cleanup(&dev); + printf("Test TPM Wrapper:\tCreateLoaded param-enc:\tSkipped\n"); + return; + } + + /* Salted AES-CFB parameter-encryption session in slot 1; slot 0 is left + * for the parent auth that wolfTPM2_CreateLoadedKey sets internally. */ + rc = wolfTPM2_StartSession(&dev, &session, &srk, NULL, + TPM_SE_HMAC, TPM_ALG_CFB); + AssertIntEQ(rc, TPM_RC_SUCCESS); + rc = wolfTPM2_SetAuthSession(&dev, 1, &session, + (TPMA_SESSION_decrypt | TPMA_SESSION_encrypt | + TPMA_SESSION_continueSession)); + AssertIntEQ(rc, TPM_RC_SUCCESS); + +#if !defined(NO_RSA) + rc = wolfTPM2_GetKeyTemplate_RSA(&publicTemplate, + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_sign | TPMA_OBJECT_noDA); +#else + rc = wolfTPM2_GetKeyTemplate_ECC(&publicTemplate, + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_sign | TPMA_OBJECT_noDA, + TPM_ECC_NIST_P256, TPM_ALG_ECDSA); +#endif + AssertIntEQ(rc, TPM_RC_SUCCESS); + + /* CreateLoaded under the param-enc session. Fails pre-fix with + * TPM_RC_HMAC; a TPM that does not implement CreateLoaded is a skip. */ + rc = wolfTPM2_CreateLoadedKey(&dev, &child, &srk.handle, + &publicTemplate, NULL, 0); + if (WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { + printf("Test TPM Wrapper:\tCreateLoaded param-enc:\tSkipped\n"); + } + else { + AssertIntEQ(rc, TPM_RC_SUCCESS); + wolfTPM2_UnloadHandle(&dev, &child.handle); + printf("Test TPM Wrapper:\tCreateLoaded param-enc:\tPassed\n"); + } + + /* Clear the session slot, then release handles. */ + wolfTPM2_SetAuthSession(&dev, 1, NULL, 0); + wolfTPM2_UnloadHandle(&dev, &session.handle); + wolfTPM2_UnloadHandle(&dev, &srk.handle); + wolfTPM2_Cleanup(&dev); +#else + printf("Test TPM Wrapper:\tCreateLoaded param-enc:\tSkipped\n"); +#endif +} + +/* Exercise the bound-own-entity branch of TPM2_ParamEncBindKey: the + * parameter-encryption key for a session that authorizes its own bind entity + * is sessionKey || authValue. An HMAC session cannot authorize in slot 0, so + * this uses a bound policy session (the examples/nvram/extend.c pattern): write + * a POLICYWRITE NV index (policy PolicyPCR(16), auth "cpusecret") under a bound + * AES-CFB policy session, then read it back. A wrong param-enc key corrupts the + * stored data even though the write command itself succeeds. */ +static void test_wolfTPM2_BoundOwnEntity_ParamEnc(void) +{ +#if !defined(WOLFTPM2_NO_WOLFCRYPT) && !defined(WOLFTPM_WINAPI) + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_SESSION session; + WOLFTPM2_SESSION trial; + WOLFTPM2_NV nv; + WOLFTPM2_HANDLE parent; + const word32 nvIndex = TPM2_DEMO_NV_TEST_AUTH_INDEX; + const byte nvAuth[] = "cpusecret"; + const int nvAuthSz = (int)sizeof(nvAuth) - 1; + word32 nvAttributes; + byte policyDigest[TPM_SHA256_DIGEST_SIZE]; + word32 policyDigestSz = (word32)sizeof(policyDigest); + byte pcrArray[1]; + byte buf[8]; + byte readBuf[8]; + word32 readSz; + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&session, 0, sizeof(session)); + XMEMSET(&trial, 0, sizeof(trial)); + XMEMSET(&nv, 0, sizeof(nv)); + XMEMSET(&parent, 0, sizeof(parent)); + XMEMSET(policyDigest, 0, sizeof(policyDigest)); + XMEMSET(buf, 0x11, sizeof(buf)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + pcrArray[0] = 16; /* resettable debug PCR */ + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + if (rc != 0) { + printf("Test TPM Wrapper:\tBound own-entity param-enc:\tSkipped\n"); + return; + } + + /* Compute the index authPolicy = PolicyPCR(16) with a trial session + * (the write helper re-runs PolicyPCR with the same selection). */ + rc = wolfTPM2_StartSession(&dev, &trial, NULL, NULL, TPM_SE_TRIAL, + TPM_ALG_NULL); + AssertIntEQ(rc, TPM_RC_SUCCESS); + rc = wolfTPM2_PolicyPCR(&dev, trial.handle.hndl, TPM_ALG_SHA256, + pcrArray, 1); + AssertIntEQ(rc, TPM_RC_SUCCESS); + rc = wolfTPM2_GetPolicyDigest(&dev, trial.handle.hndl, policyDigest, + &policyDigestSz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + wolfTPM2_UnloadHandle(&dev, &trial.handle); + + parent.hndl = TPM_RH_OWNER; + nvAttributes = TPMA_NV_POLICYWRITE | TPMA_NV_AUTHREAD | TPMA_NV_NO_DA; + rc = wolfTPM2_NVCreateAuthPolicy(&dev, &parent, &nv, nvIndex, nvAttributes, + (word32)sizeof(buf), (byte*)nvAuth, nvAuthSz, + policyDigest, (int)policyDigestSz); + if (rc != 0 && rc != TPM_RC_NV_DEFINED) { + /* Environmental (NV space / unsupported). Treat as skip. */ + wolfTPM2_Cleanup(&dev); + printf("Test TPM Wrapper:\tBound own-entity param-enc:\tSkipped\n"); + return; + } + /* Load the NV handle's auth and Name for the bind. */ + rc = wolfTPM2_NVOpen(&dev, &nv, nvIndex, (byte*)nvAuth, nvAuthSz); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + /* Bound AES-CFB policy session (slot 0) authorizing its own bind entity. */ + rc = wolfTPM2_StartSession(&dev, &session, NULL, &nv.handle, + TPM_SE_POLICY, TPM_ALG_CFB); + AssertIntEQ(rc, TPM_RC_SUCCESS); + rc = wolfTPM2_SetAuthSession(&dev, 0, &session, + (TPMA_SESSION_decrypt | TPMA_SESSION_encrypt | + TPMA_SESSION_continueSession)); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + /* Write under the bound policy session - the data is parameter-encrypted + * with the folded key. */ + rc = wolfTPM2_NVWriteAuthPolicy(&dev, &session, TPM_ALG_SHA256, pcrArray, 1, + &nv, nvIndex, buf, (word32)sizeof(buf), 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + + /* Read back with plain password auth (no param enc) and verify the data + * round-tripped. A doubled or dropped bind authValue would have stored + * garbage even though the write command itself succeeded. */ + wolfTPM2_SetAuthSession(&dev, 0, NULL, 0); + wolfTPM2_UnloadHandle(&dev, &session.handle); + wolfTPM2_SetAuthHandle(&dev, 0, &nv.handle); + readSz = (word32)sizeof(readBuf); + rc = wolfTPM2_NVReadAuth(&dev, &nv, nvIndex, readBuf, &readSz, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ((int)readSz, (int)sizeof(buf)); + AssertIntEQ(XMEMCMP(readBuf, buf, sizeof(buf)), 0); + + wolfTPM2_NVDeleteAuth(&dev, &parent, nvIndex); + wolfTPM2_Cleanup(&dev); + printf("Test TPM Wrapper:\tBound own-entity param-enc:\tPassed\n"); +#else + printf("Test TPM Wrapper:\tBound own-entity param-enc:\tSkipped\n"); +#endif +} + static void test_wolfTPM2_PolicyHash(void) { #ifndef WOLFTPM2_NO_WOLFCRYPT @@ -917,7 +1188,7 @@ static void test_wolfTPM2_EncryptSecret(void) WOLFTPM2_KEY tpmKey; TPM2B_DATA data; TPM2B_ENCRYPTED_SECRET secret; -#if defined(WOLFTPM_PQC) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \ +#if defined(WOLFTPM_MLKEM) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \ (defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_KYBER512) || \ defined(WOLFSSL_KYBER768) || defined(WOLFSSL_KYBER1024)) WOLFTPM2_KEY mlkemKey; @@ -947,7 +1218,7 @@ static void test_wolfTPM2_EncryptSecret(void) rc = wolfTPM2_EncryptSecret(&dev, &tpmKey, &data, NULL, "SECRET"); AssertIntEQ(rc, BAD_FUNC_ARG); -#if defined(WOLFTPM_PQC) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \ +#if defined(WOLFTPM_MLKEM) && !defined(WOLFTPM2_NO_WOLFCRYPT) && \ (defined(WOLFSSL_HAVE_MLKEM) || defined(WOLFSSL_KYBER512) || \ defined(WOLFSSL_KYBER768) || defined(WOLFSSL_KYBER1024)) /* MLKEM path (v1.85 Part 1 Sec.24): caller encapsulates under the TPM's @@ -4961,7 +5232,11 @@ static void test_TPM2_GetHashDigestSize_AllAlgs(void) printf("Test TPM2:\t\tGetHashDigestSize all algs:\tPassed\n"); } -#ifdef WOLFTPM_PQC +/* These PQC unit tests call both ML-DSA and ML-KEM wrappers, so they compile + * only when both families are present (a WOLFTPM_NO_MLDSA or WOLFTPM_NO_MLKEM + * build excludes the matching wrapper definitions). CI always builds full + * PQC, so coverage is unchanged there. */ +#if defined(WOLFTPM_MLDSA) && defined(WOLFTPM_MLKEM) /* Post-Quantum Cryptography (PQC) Unit Tests - TPM 2.0 v185 */ /* TODO: Remove TPM_RC_COMMAND_CODE skip logic once we have a TPM simulator @@ -5721,7 +5996,7 @@ static void test_wolfTPM2_PQC_Sizes(void) printf("Test TPM Wrapper: %-40s Passed\n", "PQC Sizes:"); } -#endif /* WOLFTPM_PQC */ +#endif /* WOLFTPM_MLDSA && WOLFTPM_MLKEM */ #endif /* !WOLFTPM2_NO_WRAPPER */ @@ -5747,6 +6022,9 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_SetAuthHandle_PolicyAuthOffset(); test_wolfTPM2_StartSession_SaltedEncryptAttrs(); test_wolfTPM2_StartSession_ex_authHash(); + test_wolfTPM2_BoundSession_EmptyAuth_ParamEnc(); + test_wolfTPM2_CreateLoaded_ParamEnc(); + test_wolfTPM2_BoundOwnEntity_ParamEnc(); test_wolfTPM2_PolicyHash(); test_wolfTPM2_SensitiveToPrivate(); test_TPM2_KDFa(); @@ -5847,7 +6125,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_ST33_FirmwareUpgrade(); #endif #endif - #ifdef WOLFTPM_PQC + #if defined(WOLFTPM_MLDSA) && defined(WOLFTPM_MLKEM) /* Run non-TPM-dependent tests first */ test_wolfTPM2_PQC_KeyTemplates(); test_wolfTPM2_PQC_Sizes(); From ae88ab8812cdc789ad68d36f112488061da92678 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 25 Jun 2026 11:20:37 -0700 Subject: [PATCH 5/6] Document PQC parameter encryption in example READMEs --- examples/README.md | 13 ++++++++++++ examples/pqc/README.md | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/examples/README.md b/examples/README.md index d8fc0fc8..a592a181 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,6 +8,8 @@ The PKCS #7 and TLS examples require generating CSR's and signing them using a t To enable parameter encryption use `-aes` for AES-CFB mode or `-xor` for XOR mode. Only some TPM commands / responses support parameter encryption. If the TPM2_ API has .flags `CMD_FLAG_ENC2` or `CMD_FLAG_DEC2` set then the command will use parameter encryption / decryption. +On a v1.85 PQC capable TPM the parameter encryption session can also be keyed with a post-quantum primary: pass `-mlkem` to salt the session with an ML-KEM key or `-mldsa` to bind it to an ML-DSA key (`keygen` uses `-paramkey=mlkem|mldsa` since `-mlkem`/`-mldsa` there select the child key). See "Parameter Encryption" below. + There are some vendor specific examples, like the TPM 2.0 extra GPIO examples for ST33 and NPCT75x. ## Native API Test @@ -90,6 +92,17 @@ This behavior depends on the `sessionAttributes`: Either one can be set separately or both can be set in one authorization session. This is up to the user (developer). +### Post-quantum session keys (v1.85) + +The parameter encryption session can be keyed with a post-quantum primary instead of an RSA/ECC storage key. ML-KEM is decrypt capable and is used as the session salt key; ML-DSA is sign only and is used as the session bind key. The RSA/ECC storage key (where one is needed, such as the parent of a created child) is unchanged. `wrap_test`, `pcr/quote`, and `nvram/store` and `nvram/counter` accept `-mlkem[=512|768|1024]` and `-mldsa[=44|65|87]`; `keygen` uses `-paramkey=mlkem[=...]` / `-paramkey=mldsa[=...]` because `-mlkem`/`-mldsa` there select the child key. This requires a v1.85 PQC capable TPM. + +```sh +./examples/wrap/wrap_test -aes -mlkem=768 +./examples/pcr/quote 16 quote.blob -ecc -xor -mldsa=65 +./examples/nvram/counter -aes -mldsa=65 +./examples/keygen/keygen keyblob.bin -ecc -aes -paramkey=mlkem=768 +``` + ## CSR Generates a Certificate Signing Request for building a certificate based on a TPM key pair. diff --git a/examples/pqc/README.md b/examples/pqc/README.md index 9d41680a..bce99cdb 100644 --- a/examples/pqc/README.md +++ b/examples/pqc/README.md @@ -126,3 +126,49 @@ by loading it back: A successful load prints `Loaded key to 0x80000000`. The full 18-way matrix (three variants x three parameter sets) is exercised by `examples/run_examples.sh` when v1.85 is detected in `config.h`. + +### PQC keys for parameter encryption + +A post-quantum primary can key a TPM 2.0 parameter-encryption session: +ML-KEM (decrypt capable) is used as the session salt key and ML-DSA +(sign only) as the session bind key. The session protects the command's +first sized parameter the same way an RSA/ECC salted session does. Any +RSA/ECC storage key the example needs (for example the parent of a +created child) is unchanged. + +Note on ML-DSA confidentiality: parameter-encryption confidentiality comes +from the session key, which a bound session derives from the bind entity's +authValue (TPM 2.0 Library Part 1, Salted Session). A sign-only ML-DSA key +cannot exchange a salt, and the example's bind authValue is a public +constant, so an ML-DSA bind alone provides session binding but no +confidentiality against a bus observer. To keep the advertised encryption +real, the helper additionally creates a transient SRK and uses it as the +asymmetric salt for the ML-DSA session: confidentiality comes from the +encrypted salt while the ML-DSA key supplies the binding. A real deployment +that relies on a bare bound session for confidentiality must use a bind +entity whose authValue is secret and was not sent in cleartext. + +`wrap_test`, `pcr/quote`, and `nvram/store` and `nvram/counter` take +`-mlkem[=512|768|1024]` and `-mldsa[=44|65|87]`. `keygen` uses +`-paramkey=mlkem[=...]` / `-paramkey=mldsa[=...]` because its `-mlkem` / +`-mldsa` already select the child key algorithm. + +``` +./examples/wrap/wrap_test -aes -mlkem=768 +./examples/pcr/quote 16 quote.blob -ecc -xor -mldsa=65 +./examples/nvram/counter -aes -mldsa=65 +./examples/keygen/keygen keyblob.bin -ecc -aes -paramkey=mlkem=768 +``` + +ML-KEM is a restricted decryption (salt) key, which requires a symmetric +definition; the example helper sets AES-128-CFB on it (a TPM rejects a +restricted key with no symmetric algorithm via `TPM_RC_SYMMETRIC`). + +### `create_primary` ML-DSA primary + +`examples/keygen/create_primary` can create an ML-DSA primary key: + +``` +./examples/keygen/create_primary -mldsa # default MLDSA-65 +./examples/keygen/create_primary -mldsa=87 -oh +``` From ec93a9b7337d4f4eef3ff054f2c18347448c3fd1 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 25 Jun 2026 15:17:07 -0700 Subject: [PATCH 6/6] peer review fixes --- .gitignore | 6 ++++++ examples/keygen/create_primary.c | 17 ++++++++++++++--- examples/nvram/counter.c | 15 +++++++++------ examples/nvram/store.c | 15 +++++++++------ examples/pcr/quote.c | 15 +++++++++------ examples/run_examples.sh | 14 ++++++++++++++ examples/wrap/wrap_test.c | 15 +++++++++------ src/fwtpm/fwtpm_command.c | 10 ++++++++-- zephyr/samples/wolftpm_wrap_test/CMakeLists.txt | 3 +++ 9 files changed, 81 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 70091d78..494a8ace 100644 --- a/.gitignore +++ b/.gitignore @@ -41,6 +41,9 @@ build/ examples/wrap/wrap_test examples/wrap/caps examples/wrap/hmac +examples/wrap/getrandom +examples/wrap/hash +examples/wrap/encrypt_decrypt examples/native/native_test examples/bench/bench examples/csr/csr @@ -56,6 +59,7 @@ examples/pcr/reset examples/timestamp/clock_set examples/management/flush examples/management/tpmclear +examples/management/da_check pkcs7tpmsigned.p7s pkcs7tpmsignedex.p7s examples/tls/tls_server @@ -70,6 +74,7 @@ examples/keygen/keyload examples/keygen/keygen examples/keygen/keyimport examples/keygen/external_import +examples/keygen/ecdh examples/pqc/mldsa_sign examples/pqc/mlkem_encap examples/pqc/pqc_mssim_e2e @@ -207,6 +212,7 @@ examples/firmware/*.MANIFESTHASH src/fwtpm/fwtpm_server fwtpm_nv.bin fwtpm_test_nv.bin +fwtpm_test_nv.bin.key # Fuzz artifacts (corpus generated at runtime by gen_corpus.py) tests/fuzz/corpus/ diff --git a/examples/keygen/create_primary.c b/examples/keygen/create_primary.c index 190dfabd..fcbfa0c9 100644 --- a/examples/keygen/create_primary.c +++ b/examples/keygen/create_primary.c @@ -90,9 +90,20 @@ static void usage(void) #ifdef WOLFTPM_MLDSA static int mldsaParamSet(const char* optVal, TPMI_MLDSA_PARAMETER_SET* ps) { - int n = XATOI(optVal); + int n; + const char* p; + + if (optVal[0] == '\0') { /* missing or empty suffix, use default */ + *ps = TPM_MLDSA_65; + return TPM_RC_SUCCESS; + } + /* reject non-digit input (e.g. -mldsa=abc) before XATOI */ + for (p = optVal; *p != '\0'; p++) { + if (*p < '0' || *p > '9') + return TPM_RC_FAILURE; + } + n = XATOI(optVal); switch (n) { - case 0: /* missing or empty suffix, use default */ case 65: *ps = TPM_MLDSA_65; return TPM_RC_SUCCESS; case 44: *ps = TPM_MLDSA_44; return TPM_RC_SUCCESS; case 87: *ps = TPM_MLDSA_87; return TPM_RC_SUCCESS; @@ -147,7 +158,7 @@ int TPM2_CreatePrimaryKey_Example(void* userCtx, int argc, char *argv[]) argv[argc-1] + 7 : ""; if (mldsaParamSet(optVal, &mldsaPs) != TPM_RC_SUCCESS) { usage(); - return 0; + return -1; } alg = TPM_ALG_MLDSA; } diff --git a/examples/nvram/counter.c b/examples/nvram/counter.c index 59095f4c..5782087d 100644 --- a/examples/nvram/counter.c +++ b/examples/nvram/counter.c @@ -74,6 +74,7 @@ int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) TPM_ALG_ID pqcParamEncAlg = TPM_ALG_NULL; int pqcParamSet = 0; + int pqcRc = 0; WOLFTPM2_KEY pqcKey; #endif word32 nvIndex = TPM2_DEMO_NV_COUNTER_INDEX; @@ -121,13 +122,15 @@ int TPM2_NVRAM_Counter_Example(void* userCtx, int argc, char *argv[]) paramEncAlg = TPM_ALG_XOR; } #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) - else if (parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, - &pqcParamSet) != 0) { - /* PQC option; an unsupported parameter set (alg left NULL) is a - * fatal typo, not a silent drop of parameter encryption. */ - if (pqcParamEncAlg == TPM_ALG_NULL) { + else if ((pqcRc = parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, + &pqcParamSet)) != 0) { + /* An unsupported parameter set (parser returns -1) is a fatal typo, + * not a silent drop of parameter encryption. Branch on the parser + * result directly: pqcParamEncAlg may already hold a valid alg from + * an earlier PQC option (argv is parsed right-to-left). */ + if (pqcRc < 0) { usage(); - return 0; + return -1; } } #endif diff --git a/examples/nvram/store.c b/examples/nvram/store.c index 383db785..97528506 100644 --- a/examples/nvram/store.c +++ b/examples/nvram/store.c @@ -82,6 +82,7 @@ int TPM2_NVRAM_Store_Example(void* userCtx, int argc, char *argv[]) #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) TPM_ALG_ID pqcParamEncAlg = TPM_ALG_NULL; int pqcParamSet = 0; + int pqcRc = 0; WOLFTPM2_KEY pqcKey; #endif int partialStore = 0; @@ -135,13 +136,15 @@ int TPM2_NVRAM_Store_Example(void* userCtx, int argc, char *argv[]) partialStore = PUBLIC_PART_ONLY; } #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) - else if (parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, - &pqcParamSet) != 0) { - /* PQC option; an unsupported parameter set (alg left NULL) is a - * fatal typo, not a silent drop of parameter encryption. */ - if (pqcParamEncAlg == TPM_ALG_NULL) { + else if ((pqcRc = parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, + &pqcParamSet)) != 0) { + /* An unsupported parameter set (parser returns -1) is a fatal typo, + * not a silent drop of parameter encryption. Branch on the parser + * result directly: pqcParamEncAlg may already hold a valid alg from + * an earlier PQC option (argv is parsed right-to-left). */ + if (pqcRc < 0) { usage(); - return 0; + return -1; } } #endif diff --git a/examples/pcr/quote.c b/examples/pcr/quote.c index 634d9e14..bdf7e3bb 100644 --- a/examples/pcr/quote.c +++ b/examples/pcr/quote.c @@ -91,6 +91,7 @@ int TPM2_PCR_Quote_Test(void* userCtx, int argc, char *argv[]) #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) TPM_ALG_ID pqcParamEncAlg = TPM_ALG_NULL; int pqcParamSet = 0; + int pqcRc = 0; WOLFTPM2_KEY pqcKey; #endif #if !defined(NO_FILESYSTEM) && !defined(NO_WRITE_TEMP_FILES) @@ -140,13 +141,15 @@ int TPM2_PCR_Quote_Test(void* userCtx, int argc, char *argv[]) paramEncAlg = TPM_ALG_XOR; } #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) - else if (parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, - &pqcParamSet) != 0) { - /* PQC option; an unsupported parameter set (alg left NULL) is a - * fatal typo, not a silent drop of parameter encryption. */ - if (pqcParamEncAlg == TPM_ALG_NULL) { + else if ((pqcRc = parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, + &pqcParamSet)) != 0) { + /* An unsupported parameter set (parser returns -1) is a fatal typo, + * not a silent drop of parameter encryption. Branch on the parser + * result directly: pqcParamEncAlg may already hold a valid alg from + * an earlier PQC option (argv is parsed right-to-left). */ + if (pqcRc < 0) { usage(); - return 0; + return -1; } } #endif diff --git a/examples/run_examples.sh b/examples/run_examples.sh index bdc3e12b..e0b549fc 100755 --- a/examples/run_examples.sh +++ b/examples/run_examples.sh @@ -383,6 +383,20 @@ if [ $ENABLE_V185 -eq 1 ]; then [ $RESULT -ne 0 ] && echo -e "create_primary mldsa=$PS failed! $RESULT" && exit 1 done + echo -e "PQC usage-error checks (invalid parameter sets must be rejected)" + # These return before touching the TPM; a zero exit means an invalid + # parameter set was silently accepted as the default. + ./examples/keygen/create_primary -mldsa=0 -oh >> $TPMPWD/run.out 2>&1 + [ $? -eq 0 ] && echo -e "create_primary -mldsa=0 should fail!" && exit 1 + ./examples/keygen/create_primary -mldsa=abc -oh >> $TPMPWD/run.out 2>&1 + [ $? -eq 0 ] && echo -e "create_primary -mldsa=abc should fail!" && exit 1 + # An invalid PQC option must stay fatal even after a valid one (argv is + # parsed right-to-left), in either argument order. + ./examples/wrap/wrap_test -aes -mldsa=999 -mlkem=768 >> $TPMPWD/run.out 2>&1 + [ $? -eq 0 ] && echo -e "wrap_test -mldsa=999 -mlkem=768 should fail!" && exit 1 + ./examples/wrap/wrap_test -aes -mlkem=768 -mldsa=999 >> $TPMPWD/run.out 2>&1 + [ $? -eq 0 ] && echo -e "wrap_test -mlkem=768 -mldsa=999 should fail!" && exit 1 + echo -e "PQC parameter encryption (ML-KEM salt / ML-DSA bind)" # ML-KEM as the param-enc session salt, ML-DSA as the param-enc session # bind; exercise AES-CFB and XOR across child-create, attestation and NV. diff --git a/examples/wrap/wrap_test.c b/examples/wrap/wrap_test.c index 7ed67738..e4b03e1f 100644 --- a/examples/wrap/wrap_test.c +++ b/examples/wrap/wrap_test.c @@ -138,6 +138,7 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) * (ML-KEM) or bind (ML-DSA) key. */ TPM_ALG_ID pqcParamEncAlg = TPM_ALG_NULL; int pqcParamSet = 0; + int pqcRc = 0; WOLFTPM2_KEY pqcKey; #endif @@ -178,13 +179,15 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) paramEncAlg = TPM_ALG_XOR; } #if defined(WOLFTPM_MLKEM) || defined(WOLFTPM_MLDSA) - else if (parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, - &pqcParamSet) != 0) { - /* PQC option; an unsupported parameter set (alg left NULL) is a - * fatal typo, not a silent drop of parameter encryption. */ - if (pqcParamEncAlg == TPM_ALG_NULL) { + else if ((pqcRc = parsePqcParamEncArg(argv[argc-1], &pqcParamEncAlg, + &pqcParamSet)) != 0) { + /* An unsupported parameter set (parser returns -1) is a fatal typo, + * not a silent drop of parameter encryption. Branch on the parser + * result directly: pqcParamEncAlg may already hold a valid alg from + * an earlier PQC option (argv is parsed right-to-left). */ + if (pqcRc < 0) { usage(); - return 0; + return -1; } } #endif diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index c8c59da2..ac06cf0e 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -224,7 +224,7 @@ static int FwBuildParamEncKey(FWTPM_Session* sess, TPM_HANDLE authHandle, static int FwParamDecryptCmd(FWTPM_CTX* ctx, FWTPM_Session* sess, TPM_HANDLE authHandle, byte* paramData, UINT32 paramSz) { - int rc; + int rc = TPM_RC_FAILURE; byte keyBuf[TPM_MAX_DIGEST_SIZE * 2]; int keyBufSz = 0; @@ -247,6 +247,9 @@ static int FwParamDecryptCmd(FWTPM_CTX* ctx, FWTPM_Session* sess, sess->nonceTPM.buffer, sess->nonceTPM.size, paramData, paramSz, 0); /* decrypt */ } + else { + rc = TPM_RC_SYMMETRIC; /* unsupported param-enc cipher */ + } TPM2_ForceZero(keyBuf, sizeof(keyBuf)); (void)ctx; @@ -257,7 +260,7 @@ static int FwParamDecryptCmd(FWTPM_CTX* ctx, FWTPM_Session* sess, static int FwParamEncryptRsp(FWTPM_CTX* ctx, FWTPM_Session* sess, TPM_HANDLE authHandle, byte* paramData, UINT32 paramSz) { - int rc; + int rc = TPM_RC_FAILURE; byte keyBuf[TPM_MAX_DIGEST_SIZE * 2]; int keyBufSz = 0; @@ -281,6 +284,9 @@ static int FwParamEncryptRsp(FWTPM_CTX* ctx, FWTPM_Session* sess, sess->nonceCaller.buffer, sess->nonceCaller.size, paramData, paramSz, 1); /* encrypt */ } + else { + rc = TPM_RC_SYMMETRIC; /* unsupported param-enc cipher */ + } TPM2_ForceZero(keyBuf, sizeof(keyBuf)); (void)ctx; diff --git a/zephyr/samples/wolftpm_wrap_test/CMakeLists.txt b/zephyr/samples/wolftpm_wrap_test/CMakeLists.txt index 27ef27f4..61066091 100644 --- a/zephyr/samples/wolftpm_wrap_test/CMakeLists.txt +++ b/zephyr/samples/wolftpm_wrap_test/CMakeLists.txt @@ -4,6 +4,9 @@ project(wolftpm_wrap_test) # Include source code for wrap test target_sources(app PRIVATE ${ZEPHYR_WOLFTPM_MODULE_DIR}/examples/wrap/wrap_test.c) +# wrap_test calls PQC param-enc helpers from examples/tpm_test_keys.c under +# WOLFTPM_MLKEM / WOLFTPM_MLDSA; compile it so those references resolve. +target_sources(app PRIVATE ${ZEPHYR_WOLFTPM_MODULE_DIR}/examples/tpm_test_keys.c) target_sources(app PRIVATE ${app_sources}) # Include header files