diff --git a/.github/workflows/fwtpm-test.yml b/.github/workflows/fwtpm-test.yml index 480c8fa10..b134fde16 100644 --- a/.github/workflows/fwtpm-test.yml +++ b/.github/workflows/fwtpm-test.yml @@ -68,6 +68,16 @@ jobs: build_only: true extra_cflags: -DDEBUG_WOLFTPM + # v1.85 PQC without HKDF, so ECC DHKEM is unavailable and + # Encapsulate/Decapsulate must reject an ECC key while the ML-KEM + # path still works. TLS 1.3 turns HKDF on regardless of + # --disable-hkdf, so it is disabled too. + - name: fwtpm-v185-no-hkdf + os: ubuntu-latest + wolftpm_config: --enable-fwtpm --enable-v185 + wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen --enable-dilithium --enable-mlkem --enable-experimental --enable-harden --disable-tls13 --disable-hkdf + build_only: false + # Build-only: fwTPM with RSA disabled - name: fwtpm-no-rsa os: ubuntu-latest diff --git a/examples/wrap/wrap_test.c b/examples/wrap/wrap_test.c index 06c277113..d1cdb142c 100644 --- a/examples/wrap/wrap_test.c +++ b/examples/wrap/wrap_test.c @@ -376,11 +376,17 @@ int TPM2_Wrapper_TestArgs(void* userCtx, int argc, char *argv[]) cipher.size = sizeof(cipher.buffer); /* signature */ rc = wolfTPM2_SignHashScheme(&dev, &rsaKey, message.buffer, message.size, cipher.buffer, &cipher.size, TPM_ALG_RSAPSS, TPM_ALG_SHA256); - if (rc != 0) goto exit; - rc = wolfTPM2_VerifyHashScheme(&dev, &rsaKey, cipher.buffer, cipher.size, - message.buffer, message.size, TPM_ALG_RSAPSS, TPM_ALG_SHA256); - if (rc != 0) goto exit; - printf("RSA Sign/Verify using RSA PSS padding\n"); + if ((rc & RC_MAX_FMT1) == TPM_RC_SCHEME) { + printf("RSA PSS padding not supported by this TPM\n"); + } + else { + if (rc != 0) goto exit; + rc = wolfTPM2_VerifyHashScheme(&dev, &rsaKey, cipher.buffer, + cipher.size, message.buffer, message.size, TPM_ALG_RSAPSS, + TPM_ALG_SHA256); + if (rc != 0) goto exit; + printf("RSA Sign/Verify using RSA PSS padding\n"); + } rc = wolfTPM2_UnloadHandle(&dev, &rsaKey.handle); if (rc != 0) goto exit; diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index b3dac5268..04ad23f31 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1855,9 +1855,11 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, #ifndef NO_RSA { TPM_ALG_RSASSA, TPMA_ALGORITHM_asymmetric | TPMA_ALGORITHM_signing }, + #ifdef WC_RSA_PSS { TPM_ALG_RSAPSS, TPMA_ALGORITHM_asymmetric | TPMA_ALGORITHM_signing }, #endif + #endif #ifdef HAVE_ECC { TPM_ALG_ECDSA, TPMA_ALGORITHM_asymmetric | TPMA_ALGORITHM_signing }, @@ -2450,7 +2452,10 @@ static TPM_RC FwTestRsaScheme(const TPMT_RSA_SCHEME* scheme) if (alg == TPM_ALG_NULL || alg == TPM_ALG_RSAES) { rc = TPM_RC_SUCCESS; } - else if (alg == TPM_ALG_RSASSA || alg == TPM_ALG_RSAPSS || + else if (alg == TPM_ALG_RSASSA || + #ifdef WC_RSA_PSS + alg == TPM_ALG_RSAPSS || + #endif alg == TPM_ALG_OAEP) { rc = FwTestHashAlg(scheme->details.anySig.hashAlg); } @@ -18138,7 +18143,7 @@ static TPM_RC FwCmd_Encapsulate(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, rc = FwEncapsulateMlkem(&ctx->rng, ps, &obj->pub.unique.mlkem, &sharedSecret, ciphertext); } -#ifdef HAVE_ECC +#ifdef FWTPM_HAVE_DHKEM else if (obj->pub.type == TPM_ALG_ECC && obj->pub.parameters.eccDetail.kdf.scheme == TPM_ALG_HKDF) { rc = FwEncapsulateEcdhDhkem(&ctx->rng, &obj->pub, @@ -18258,12 +18263,16 @@ static TPM_RC FwCmd_Decapsulate(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, rc = FwDecapsulateMlkem(ps, obj->privKey, ciphertext->buffer, ciphertext->size, &sharedSecret); } -#ifdef HAVE_ECC +#ifdef FWTPM_HAVE_DHKEM else { rc = FwDecapsulateEcdhDhkem(&ctx->rng, obj, obj->pub.parameters.eccDetail.kdf.details.any.hashAlg, ciphertext->buffer, ciphertext->size, &sharedSecret); } +#else + else { + rc = TPM_RC_KEY; + } #endif } diff --git a/src/fwtpm/fwtpm_crypto.c b/src/fwtpm/fwtpm_crypto.c index f4e08adea..0f6929209 100644 --- a/src/fwtpm/fwtpm_crypto.c +++ b/src/fwtpm/fwtpm_crypto.c @@ -1201,7 +1201,7 @@ TPM_RC FwDecapsulateMlkem(TPMI_MLKEM_PARAMETER_SET parameterSet, return rc; } -#ifdef HAVE_ECC +#ifdef FWTPM_HAVE_DHKEM /* RFC 9180 Sec.7 kem_id mapping for the curve+hash pairings the TPM accepts. * Returns 0 on a supported pairing; -1 otherwise (caller maps to TPM_RC_KDF). */ static int FwDhkemParamsLookup(int wcCurve, TPMI_ALG_HASH kdfHash, @@ -1519,7 +1519,7 @@ TPM_RC FwDecapsulateEcdhDhkem(WC_RNG* rng, const FWTPM_Object* recipObj, FWTPM_FREE_VAR(ephKey); return rc; } -#endif /* HAVE_ECC */ +#endif /* FWTPM_HAVE_DHKEM */ #endif /* WOLFTPM_MLKEM */ @@ -3770,7 +3770,9 @@ int FwGetRsaPadding(UINT16 scheme) { switch (scheme) { case TPM_ALG_RSASSA: return WC_RSA_PKCSV15_PAD; + #ifdef WC_RSA_PSS case TPM_ALG_RSAPSS: return WC_RSA_PSS_PAD; + #endif default: return -1; } } @@ -3897,13 +3899,16 @@ TPM_RC FwSignDigestAndAppend(FWTPM_CTX* ctx, FWTPM_Object* obj, } if (rc == 0) { + #ifdef WC_RSA_PSS if (pad == WC_RSA_PSS_PAD) { int mgf = FwGetMgfType(sigHashAlg); wcRc = wc_RsaPSS_Sign_ex(digest, digestSz, sigBuf, sigSz, (enum wc_HashType)wcHashType, mgf, RSA_PSS_SALT_LEN_DEFAULT, rsaKey, &ctx->rng); } - else { + else + #endif + { /* RSASSA PKCS#1 v1.5: wrap in ASN.1 DigestInfo */ byte encHash[WOLFTPM_MAX_DER_DIGEST_SZ]; int encSz; @@ -4043,6 +4048,7 @@ TPM_RC FwVerifySignatureCore(FWTPM_Object* obj, } if (rc == 0) { + #ifdef WC_RSA_PSS if (pad == WC_RSA_PSS_PAD) { FWTPM_DECLARE_BUF(decSig, FWTPM_MAX_PUB_BUF); int wcHashType = FwGetRsaHashOid( @@ -4061,7 +4067,9 @@ TPM_RC FwVerifySignatureCore(FWTPM_Object* obj, rsaKey); FWTPM_FREE_BUF(decSig); } - else { + else + #endif + { FWTPM_DECLARE_BUF(decSig, FWTPM_MAX_PUB_BUF); byte expDI[WOLFTPM_MAX_DER_DIGEST_SZ]; int wcHash = FwGetRsaHashOid( diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index a21191543..eb5d151e1 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -3553,6 +3553,8 @@ static void test_fwtpm_mlkem_roundtrip(void) fwtpm_pass("MLKEM Encap/Decap Roundtrip:", 1); } +#if defined(FWTPM_HAVE_DHKEM) && defined(WOLFTPM_MLKEM_ENCAP) && \ + defined(WOLFTPM_MLKEM_DECAP) /* ECC DHKEM Encap/Decap roundtrip per Part 2 Sec.12.2.3.5 + RFC 9180 Sec.4.1. * An unrestricted-decryption ECC key with the matching HKDF kdf qualifies for * Encap/Decap. Encap and Decap MUST produce identical sharedSecrets. */ @@ -3680,6 +3682,104 @@ static void test_fwtpm_ecc_dhkem_p521_roundtrip(void) "Encap/Decap ECC DHKEM (P-521/HKDF-SHA512) Roundtrip:"); } #endif +#endif /* FWTPM_HAVE_DHKEM && WOLFTPM_MLKEM_ENCAP && WOLFTPM_MLKEM_DECAP */ + +#if !defined(FWTPM_HAVE_DHKEM) && defined(HAVE_ECC) && \ + defined(WOLFTPM_MLKEM_ENCAP) && defined(WOLFTPM_MLKEM_DECAP) +/* Without HKDF there is no ECC DHKEM, so an ECC key is not a usable KEM key + * even though it still passes the Part 2 Sec.10.3.13 template checks that + * admit it. Encap and Decap MUST report TPM_RC_KEY rather than dispatch into + * the absent DHKEM path. ML-KEM remains the one supported KEM type here and + * is covered by test_fwtpm_mlkem_roundtrip. */ +static void test_fwtpm_ecc_dhkem_unsupported(void) +{ + FWTPM_CTX ctx; + int rc, rspSize; + int pos, pubAreaStart, pubAreaLen; + UINT32 keyHandle; + byte ct[32]; + + memset(&ctx, 0, sizeof(ctx)); + XMEMSET(ct, 0, sizeof(ct)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* Same key the DHKEM roundtrip uses: ECC unrestricted decrypt with an + * HKDF kdf. FwTestKdfScheme accepts HKDF whether or not wolfCrypt has + * it, so creation still succeeds in this build. */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_CreatePrimary); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, 9); pos += 4; + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + gCmd[pos++] = 0; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 4); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + pubAreaStart = pos; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_ECC); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + /* fixedTPM|fixedParent|sensitiveDataOrigin|userWithAuth|decrypt */ + PutU32BE(gCmd + pos, 0x00020072); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* sym */ + PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* scheme */ + PutU16BE(gCmd + pos, TPM_ECC_NIST_P256); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_HKDF); pos += 2; /* kdf scheme */ + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; /* kdf hash */ + PutU16BE(gCmd + pos, 0); pos += 2; /* unique.x */ + PutU16BE(gCmd + pos, 0); pos += 2; /* unique.y */ + pubAreaLen = pos - pubAreaStart - 2; + PutU16BE(gCmd + pubAreaStart, (UINT16)pubAreaLen); + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + keyHandle = GetU32BE(gRsp + TPM2_HEADER_SIZE); + AssertIntNE(keyHandle, 0); + + /* Encapsulate falls through to the unsupported-type branch */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_NO_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_Encapsulate); pos += 4; + PutU32BE(gCmd + pos, keyHandle); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), + TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_KEY); + + /* Decapsulate passes every template check and is rejected at dispatch */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_Decapsulate); pos += 4; + PutU32BE(gCmd + pos, keyHandle); pos += 4; + PutU32BE(gCmd + pos, 9); pos += 4; + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + gCmd[pos++] = 0; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, (UINT16)sizeof(ct)); pos += 2; + memcpy(gCmd + pos, ct, sizeof(ct)); pos += (int)sizeof(ct); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), + TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_KEY); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("Encap/Decap ECC DHKEM Unsupported (no HKDF):", 1); +} +#endif /* !FWTPM_HAVE_DHKEM && HAVE_ECC && MLKEM_ENCAP && MLKEM_DECAP */ /* ML-KEM Labeled KEM seed roundtrip per Part 1 Sec.47.4 Eq.66: * seed = KDFa(nameAlg, K, label, ciphertext, publicKey, bits). @@ -19477,7 +19577,14 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_create_loaded_mldsa(); test_fwtpm_create_loaded_mlkem(); test_fwtpm_mlkem_roundtrip(); +#if defined(FWTPM_HAVE_DHKEM) && defined(WOLFTPM_MLKEM_ENCAP) && \ + defined(WOLFTPM_MLKEM_DECAP) test_fwtpm_ecc_dhkem_roundtrip(); +#endif +#if !defined(FWTPM_HAVE_DHKEM) && defined(HAVE_ECC) && \ + defined(WOLFTPM_MLKEM_ENCAP) && defined(WOLFTPM_MLKEM_DECAP) + test_fwtpm_ecc_dhkem_unsupported(); +#endif test_fwtpm_encseed_decseed_mlkem_roundtrip(); #if defined(WOLFTPM_MLKEM_ENCAP) && defined(WOLFTPM_MLKEM_DECAP) && \ !defined(FWTPM_NO_CREDENTIAL) @@ -19521,10 +19628,13 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_signsequence_hmac_roundtrip(); test_fwtpm_signsequence_handle_auth_required(); test_fwtpm_verifysequence_long_message(); +#if defined(FWTPM_HAVE_DHKEM) && defined(WOLFTPM_MLKEM_ENCAP) && \ + defined(WOLFTPM_MLKEM_DECAP) test_fwtpm_ecc_dhkem_p384_roundtrip(); #ifdef FWTPM_HAVE_ECC521 test_fwtpm_ecc_dhkem_p521_roundtrip(); #endif +#endif #ifdef WOLFTPM_HASH_MLDSA test_fwtpm_mldsa_digest_roundtrip(); #endif /* WOLFTPM_HASH_MLDSA */ diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index acc06293a..e6b68e2f2 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -53,6 +53,13 @@ #endif #endif +/* v1.85 ECC DHKEM (RFC 9180) derives its shared secret with HKDF. Reachable + * only through TPM2_Encapsulate/Decapsulate, which are gated on ML-KEM, so + * the capability has to match that gate as well. */ +#if defined(WOLFTPM_MLKEM) && defined(HAVE_ECC) && defined(HAVE_HKDF) + #define FWTPM_HAVE_DHKEM +#endif + /* Endian byte-array helpers - use shared TPM2_Packet helpers. * Note: argument order differs (Fw: buf,val; TPM2_Packet: val,buf) */ #define FwStoreU16BE(buf, val) TPM2_Packet_U16ToByteArray((val), (buf))