diff --git a/tests/api/test_evp_pkey.c b/tests/api/test_evp_pkey.c index 6a1912ced9..adf381dcad 100644 --- a/tests/api/test_evp_pkey.c +++ b/tests/api/test_evp_pkey.c @@ -382,6 +382,76 @@ int test_wolfSSL_EVP_MD_hmac_signing(void) return EXPECT_RESULT(); } +/* Verify that EVP_DigestVerifyFinal rejects zero-length HMAC tags. */ +int test_wolfSSL_EVP_DigestVerify_HMAC_zero_len_forgery(void) +{ + EXPECT_DECLS; +#if defined(OPENSSL_EXTRA) && !defined(NO_HMAC) && !defined(NO_SHA256) + static const unsigned char key[] = { + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b + }; + static const char message[] = "wolfSSL DigestVerifyFinal forgery probe"; + static const unsigned char zeros[WC_MAX_DIGEST_SIZE] = { 0 }; + + WOLFSSL_EVP_PKEY* pkey = NULL; + WOLFSSL_EVP_MD_CTX mdCtx; + unsigned char tag[WC_MAX_DIGEST_SIZE]; + size_t tagLen = sizeof(tag); + + wolfSSL_EVP_MD_CTX_init(&mdCtx); + + ExpectNotNull(pkey = wolfSSL_EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, + key, (int)sizeof(key))); + + /* Compute the genuine HMAC-SHA256 tag for the message. */ + ExpectIntEQ(wolfSSL_EVP_DigestSignInit(&mdCtx, NULL, wolfSSL_EVP_sha256(), + NULL, pkey), 1); + ExpectIntEQ(wolfSSL_EVP_DigestSignUpdate(&mdCtx, message, + (unsigned int)XSTRLEN(message)), + 1); + ExpectIntEQ(wolfSSL_EVP_DigestSignFinal(&mdCtx, tag, &tagLen), 1); + ExpectIntEQ((int)tagLen, WC_SHA256_DIGEST_SIZE); + ExpectIntEQ(wolfSSL_EVP_MD_CTX_cleanup(&mdCtx), 1); + + /* Full-length genuine tag verifies. */ + wolfSSL_EVP_MD_CTX_init(&mdCtx); + ExpectIntEQ(wolfSSL_EVP_DigestVerifyInit(&mdCtx, NULL, wolfSSL_EVP_sha256(), + NULL, pkey), 1); + ExpectIntEQ(wolfSSL_EVP_DigestVerifyUpdate(&mdCtx, message, + (unsigned int)XSTRLEN(message)), + 1); + ExpectIntEQ(wolfSSL_EVP_DigestVerifyFinal(&mdCtx, tag, tagLen), 1); + ExpectIntEQ(wolfSSL_EVP_MD_CTX_cleanup(&mdCtx), 1); + + /* Wrong full-length tag is rejected. */ + wolfSSL_EVP_MD_CTX_init(&mdCtx); + ExpectIntEQ(wolfSSL_EVP_DigestVerifyInit(&mdCtx, NULL, wolfSSL_EVP_sha256(), + NULL, pkey), 1); + ExpectIntEQ(wolfSSL_EVP_DigestVerifyUpdate(&mdCtx, message, + (unsigned int)XSTRLEN(message)), + 1); + ExpectIntNE(wolfSSL_EVP_DigestVerifyFinal(&mdCtx, zeros, + WC_SHA256_DIGEST_SIZE), 1); + ExpectIntEQ(wolfSSL_EVP_MD_CTX_cleanup(&mdCtx), 1); + + /* Zero-length tag must be rejected. */ + wolfSSL_EVP_MD_CTX_init(&mdCtx); + ExpectIntEQ(wolfSSL_EVP_DigestVerifyInit(&mdCtx, NULL, wolfSSL_EVP_sha256(), + NULL, pkey), 1); + ExpectIntEQ(wolfSSL_EVP_DigestVerifyUpdate(&mdCtx, message, + (unsigned int)XSTRLEN(message)), + 1); + ExpectIntNE(wolfSSL_EVP_DigestVerifyFinal(&mdCtx, zeros, 0), 1); + ExpectIntEQ(wolfSSL_EVP_MD_CTX_cleanup(&mdCtx), 1); + + wolfSSL_EVP_PKEY_free(pkey); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_EVP_PKEY_new_mac_key(void) { EXPECT_DECLS; diff --git a/tests/api/test_evp_pkey.h b/tests/api/test_evp_pkey.h index e11d5b9c8c..7062e922a2 100644 --- a/tests/api/test_evp_pkey.h +++ b/tests/api/test_evp_pkey.h @@ -32,6 +32,7 @@ int test_wolfSSL_EVP_PKEY_base_id(void); int test_wolfSSL_EVP_PKEY_id(void); int test_wolfSSL_EVP_MD_pkey_type(void); int test_wolfSSL_EVP_MD_hmac_signing(void); +int test_wolfSSL_EVP_DigestVerify_HMAC_zero_len_forgery(void); int test_wolfSSL_EVP_PKEY_new_mac_key(void); int test_wolfSSL_EVP_PKEY_hkdf(void); int test_wolfSSL_EVP_PBE_scrypt(void); @@ -70,6 +71,8 @@ int test_wolfSSL_EVP_PKEY_print_public(void); TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_id), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_MD_pkey_type), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_MD_hmac_signing), \ + TEST_DECL_GROUP("evp_pkey", \ + test_wolfSSL_EVP_DigestVerify_HMAC_zero_len_forgery), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_new_mac_key), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_hkdf), \ TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PBE_scrypt), \ diff --git a/tests/api/test_mlkem.c b/tests/api/test_mlkem.c index e9e42661a2..ce605a25b6 100644 --- a/tests/api/test_mlkem.c +++ b/tests/api/test_mlkem.c @@ -3950,3 +3950,67 @@ int test_wc_mlkem_decapsulate_pubonly_fails(void) return EXPECT_RESULT(); } /* END test_wc_mlkem_decapsulate_pubonly_fails */ +/* Verify that the FO re-encryption check catches ciphertext tampering + * at various byte offsets and falls back to implicit rejection. */ +int test_wc_mlkem_decap_fo_reject(void) +{ + EXPECT_DECLS; +#if !defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0) +#if defined(WOLFSSL_HAVE_MLKEM) && defined(WOLFSSL_WC_MLKEM) && \ + !defined(WOLFSSL_NO_ML_KEM) && !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) + MlKemKey* key = NULL; + WC_RNG rng; + byte ct[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; + byte ctTampered[WC_ML_KEM_MAX_CIPHER_TEXT_SIZE]; + byte ss[WC_ML_KEM_SS_SZ]; + byte ssDec[WC_ML_KEM_SS_SZ]; + byte ssTampered[WC_ML_KEM_SS_SZ]; + word32 ctLen = 0; + + key = (MlKemKey*)XMALLOC(sizeof(*key), NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(key); + + XMEMSET(&rng, 0, sizeof(rng)); + ExpectIntEQ(wc_InitRng(&rng), 0); + +#ifndef WOLFSSL_NO_ML_KEM_768 + ExpectIntEQ(wc_MlKemKey_Init(key, WC_ML_KEM_768, NULL, INVALID_DEVID), 0); +#elif !defined(WOLFSSL_NO_ML_KEM_512) + ExpectIntEQ(wc_MlKemKey_Init(key, WC_ML_KEM_512, NULL, INVALID_DEVID), 0); +#else + ExpectIntEQ(wc_MlKemKey_Init(key, WC_ML_KEM_1024, NULL, INVALID_DEVID), 0); +#endif + + ExpectIntEQ(wc_MlKemKey_CipherTextSize(key, &ctLen), 0); + ExpectIntEQ(wc_MlKemKey_MakeKey(key, &rng), 0); + ExpectIntEQ(wc_MlKemKey_Encapsulate(key, ct, ss, &rng), 0); + + /* Untampered ciphertext recovers the original ss. */ + XMEMSET(ssDec, 0, sizeof(ssDec)); + ExpectIntEQ(wc_MlKemKey_Decapsulate(key, ssDec, ct, ctLen), 0); + ExpectIntEQ(XMEMCMP(ssDec, ss, WC_ML_KEM_SS_SZ), 0); + + /* Tamper at byte 32: implicit rejection must fire. */ + XMEMCPY(ctTampered, ct, ctLen); + ctTampered[32] ^= 0x01; + XMEMSET(ssTampered, 0, sizeof(ssTampered)); + ExpectIntEQ(wc_MlKemKey_Decapsulate(key, ssTampered, ctTampered, ctLen), 0); + ExpectIntNE(XMEMCMP(ssTampered, ss, WC_ML_KEM_SS_SZ), 0); + + /* Tamper at byte 0: also must be rejected. */ + XMEMCPY(ctTampered, ct, ctLen); + ctTampered[0] ^= 0x01; + XMEMSET(ssTampered, 0, sizeof(ssTampered)); + ExpectIntEQ(wc_MlKemKey_Decapsulate(key, ssTampered, ctTampered, ctLen), 0); + ExpectIntNE(XMEMCMP(ssTampered, ss, WC_ML_KEM_SS_SZ), 0); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); + wc_MlKemKey_Free(key); + XFREE(key, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif +#endif + return EXPECT_RESULT(); +} /* END test_wc_mlkem_decap_fo_reject */ + diff --git a/tests/api/test_mlkem.h b/tests/api/test_mlkem.h index 24c05d6a0c..2c166409b6 100644 --- a/tests/api/test_mlkem.h +++ b/tests/api/test_mlkem.h @@ -28,11 +28,13 @@ int test_wc_mlkem_make_key_kats(void); int test_wc_mlkem_encapsulate_kats(void); int test_wc_mlkem_decapsulate_kats(void); int test_wc_mlkem_decapsulate_pubonly_fails(void); +int test_wc_mlkem_decap_fo_reject(void); -#define TEST_MLKEM_DECLS \ - TEST_DECL_GROUP("mlkem", test_wc_mlkem_make_key_kats), \ - TEST_DECL_GROUP("mlkem", test_wc_mlkem_encapsulate_kats), \ - TEST_DECL_GROUP("mlkem", test_wc_mlkem_decapsulate_kats), \ - TEST_DECL_GROUP("mlkem", test_wc_mlkem_decapsulate_pubonly_fails) +#define TEST_MLKEM_DECLS \ + TEST_DECL_GROUP("mlkem", test_wc_mlkem_make_key_kats), \ + TEST_DECL_GROUP("mlkem", test_wc_mlkem_encapsulate_kats), \ + TEST_DECL_GROUP("mlkem", test_wc_mlkem_decapsulate_kats), \ + TEST_DECL_GROUP("mlkem", test_wc_mlkem_decapsulate_pubonly_fails), \ + TEST_DECL_GROUP("mlkem", test_wc_mlkem_decap_fo_reject) #endif /* WOLFCRYPT_TEST_MLKEM_H */ diff --git a/tests/api/test_pkcs12.c b/tests/api/test_pkcs12.c index 62730b0f03..33feed7bd5 100644 --- a/tests/api/test_pkcs12.c +++ b/tests/api/test_pkcs12.c @@ -28,6 +28,7 @@ #include #endif +#include #include #include #include @@ -272,6 +273,137 @@ int test_wc_d2i_PKCS12_oid_underflow(void) return EXPECT_RESULT(); } +/* Test that a crafted PKCS12 with a MAC OCTET STRING shorter than the + * algorithm's native digest size is rejected, rather than allowing the + * integrity check to be truncated to a brute-forceable length. */ +int test_wc_PKCS12_truncated_mac_bypass(void) +{ + EXPECT_DECLS; +#if !defined(NO_ASN) && !defined(NO_PWDBASED) && defined(HAVE_PKCS12) \ + && !defined(NO_HMAC) && !defined(NO_SHA256) + static const byte authSafe[] = { 0x30, 0x00 }; /* empty SEQUENCE OF CI */ + static const char password[] = "wolfSSL test"; + static const byte salt[8] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 + }; + const int iter = 1; + const word32 pwLen = (word32)(sizeof(password) - 1); + + byte unicodePw[2 * sizeof(password) + 2]; + int unicodePwLen = 0; + byte macKey[WC_SHA256_DIGEST_SIZE]; + byte fullMac[WC_SHA256_DIGEST_SIZE]; + Hmac hmac; + int hmacInited = 0; + word32 i; + + WC_PKCS12* pkcs12 = NULL; + byte pfx[64]; + word32 pfxLen = 0; + + /* BMPString-style password (UTF-16BE) with trailing 0x00 0x00, matching + * the unicode conversion done internally by wc_PKCS12_create_mac. */ + for (i = 0; i < pwLen; i++) { + unicodePw[unicodePwLen++] = 0x00; + unicodePw[unicodePwLen++] = (byte)password[i]; + } + unicodePw[unicodePwLen++] = 0x00; + unicodePw[unicodePwLen++] = 0x00; + + /* Derive the MAC key the same way wc_PKCS12_create_mac does: + * PKCS12-PBKDF SHA-256, id=3 (MAC key), kLen=32. */ + ExpectIntEQ(wc_PKCS12_PBKDF_ex(macKey, unicodePw, unicodePwLen, + salt, (int)sizeof(salt), + iter, WC_SHA256_DIGEST_SIZE, + WC_SHA256, 3 /* id = MAC */, NULL), + 0); + + /* Compute the genuine HMAC-SHA256 over the authSafe content. */ + ExpectIntEQ(wc_HmacInit(&hmac, NULL, INVALID_DEVID), 0); + if (EXPECT_SUCCESS()) + hmacInited = 1; + ExpectIntEQ(wc_HmacSetKey(&hmac, WC_SHA256, macKey, sizeof(macKey)), 0); + ExpectIntEQ(wc_HmacUpdate(&hmac, authSafe, (word32)sizeof(authSafe)), 0); + ExpectIntEQ(wc_HmacFinal(&hmac, fullMac), 0); + if (hmacInited) + wc_HmacFree(&hmac); + + /* + * Build a 59-byte PFX with a 1-byte truncated digest equal to fullMac[0]: + * + * 30 39 PFX SEQUENCE (57) + * 02 01 03 version = 3 + * 30 11 AuthSafe ContentInfo (17) + * 06 09 2A 86 48 86 F7 0D 01 07 01 OID 1.2.840.113549.1.7.1 (data) + * A0 04 [0] EXPLICIT (4) + * 04 02 OCTET STRING (2) + * 30 00 authSafe = empty SEQUENCE + * 30 21 MacData (33) + * 30 12 DigestInfo (18) + * 30 0d AlgorithmIdentifier (13) + * 06 09 60 86 48 01 65 03 04 02 01 OID SHA-256 + * 05 00 NULL + * 04 01 XX OCTET STRING (1) + * 04 08 01 02 03 04 05 06 07 08 salt + * 02 01 01 iterations = 1 + */ + pfx[pfxLen++] = 0x30; pfx[pfxLen++] = 0x39; + pfx[pfxLen++] = 0x02; pfx[pfxLen++] = 0x01; pfx[pfxLen++] = 0x03; + pfx[pfxLen++] = 0x30; pfx[pfxLen++] = 0x11; + pfx[pfxLen++] = 0x06; pfx[pfxLen++] = 0x09; + pfx[pfxLen++] = 0x2A; pfx[pfxLen++] = 0x86; pfx[pfxLen++] = 0x48; + pfx[pfxLen++] = 0x86; pfx[pfxLen++] = 0xF7; pfx[pfxLen++] = 0x0D; + pfx[pfxLen++] = 0x01; pfx[pfxLen++] = 0x07; pfx[pfxLen++] = 0x01; + pfx[pfxLen++] = 0xA0; pfx[pfxLen++] = 0x04; + pfx[pfxLen++] = 0x04; pfx[pfxLen++] = 0x02; + pfx[pfxLen++] = 0x30; pfx[pfxLen++] = 0x00; + pfx[pfxLen++] = 0x30; pfx[pfxLen++] = 0x21; + pfx[pfxLen++] = 0x30; pfx[pfxLen++] = 0x12; + pfx[pfxLen++] = 0x30; pfx[pfxLen++] = 0x0D; + pfx[pfxLen++] = 0x06; pfx[pfxLen++] = 0x09; + pfx[pfxLen++] = 0x60; pfx[pfxLen++] = 0x86; pfx[pfxLen++] = 0x48; + pfx[pfxLen++] = 0x01; pfx[pfxLen++] = 0x65; pfx[pfxLen++] = 0x03; + pfx[pfxLen++] = 0x04; pfx[pfxLen++] = 0x02; pfx[pfxLen++] = 0x01; + pfx[pfxLen++] = 0x05; pfx[pfxLen++] = 0x00; + pfx[pfxLen++] = 0x04; pfx[pfxLen++] = 0x01; + pfx[pfxLen++] = fullMac[0]; + pfx[pfxLen++] = 0x04; pfx[pfxLen++] = 0x08; + pfx[pfxLen++] = 0x01; pfx[pfxLen++] = 0x02; pfx[pfxLen++] = 0x03; + pfx[pfxLen++] = 0x04; pfx[pfxLen++] = 0x05; pfx[pfxLen++] = 0x06; + pfx[pfxLen++] = 0x07; pfx[pfxLen++] = 0x08; + pfx[pfxLen++] = 0x02; pfx[pfxLen++] = 0x01; pfx[pfxLen++] = 0x01; + + { + byte* parsedPkey = NULL; + word32 parsedPkeySz = 0; + byte* parsedCert = NULL; + word32 parsedCertSz = 0; + int d2iRet; + + ExpectNotNull(pkcs12 = wc_PKCS12_new()); + + /* Accept rejection at either parse time (wc_d2i_PKCS12) or + * verify time (wc_PKCS12_parse); the test fails only if both + * succeed. */ + d2iRet = wc_d2i_PKCS12(pfx, pfxLen, pkcs12); + if (d2iRet == 0) { + ExpectIntNE(wc_PKCS12_parse(pkcs12, password, + &parsedPkey, &parsedPkeySz, + &parsedCert, &parsedCertSz, NULL), + 0); + } + else { + ExpectIntNE(d2iRet, 0); + } + + XFREE(parsedPkey, NULL, DYNAMIC_TYPE_PUBLIC_KEY); + XFREE(parsedCert, NULL, DYNAMIC_TYPE_PKCS); + wc_PKCS12_free(pkcs12); + } +#endif + return EXPECT_RESULT(); +} + int test_wc_PKCS12_PBKDF(void) { EXPECT_DECLS; diff --git a/tests/api/test_pkcs12.h b/tests/api/test_pkcs12.h index 23acd2d3c7..22c470a4e2 100644 --- a/tests/api/test_pkcs12.h +++ b/tests/api/test_pkcs12.h @@ -28,6 +28,7 @@ int test_wc_i2d_PKCS12(void); int test_wc_PKCS12_create(void); int test_wc_d2i_PKCS12_bad_mac_salt(void); int test_wc_d2i_PKCS12_oid_underflow(void); +int test_wc_PKCS12_truncated_mac_bypass(void); int test_wc_PKCS12_PBKDF(void); int test_wc_PKCS12_PBKDF_ex(void); int test_wc_PKCS12_PBKDF_ex_sha1(void); @@ -42,6 +43,7 @@ int test_wc_PKCS12_PBKDF_ex_sha512_256(void); TEST_DECL_GROUP("pkcs12", test_wc_PKCS12_create), \ TEST_DECL_GROUP("pkcs12", test_wc_d2i_PKCS12_bad_mac_salt), \ TEST_DECL_GROUP("pkcs12", test_wc_d2i_PKCS12_oid_underflow), \ + TEST_DECL_GROUP("pkcs12", test_wc_PKCS12_truncated_mac_bypass), \ TEST_DECL_GROUP("pkcs12", test_wc_PKCS12_PBKDF), \ TEST_DECL_GROUP("pkcs12", test_wc_PKCS12_PBKDF_ex), \ TEST_DECL_GROUP("pkcs12", test_wc_PKCS12_PBKDF_ex_sha1), \ diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index 997abc308d..a5ed4eda9d 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -4992,9 +4992,8 @@ int wolfSSL_EVP_DigestVerifyFinal(WOLFSSL_EVP_MD_CTX *ctx, hashLen = wolfssl_mac_len(ctx->hash.hmac.macType); - if (siglen > hashLen || siglen > INT_MAX) + if (hashLen == 0 || siglen != hashLen) return WOLFSSL_FAILURE; - /* May be a truncated signature. */ } if (wolfssl_evp_digest_pk_final(ctx, digest, &hashLen) <= 0) diff --git a/wolfcrypt/src/pkcs12.c b/wolfcrypt/src/pkcs12.c index f3f5197833..329c820772 100644 --- a/wolfcrypt/src/pkcs12.c +++ b/wolfcrypt/src/pkcs12.c @@ -633,6 +633,12 @@ static int wc_PKCS12_verify(WC_PKCS12* pkcs12, byte* data, word32 dataSz, return ret; } + if ((word32)ret != mac->digestSz) { + WOLFSSL_MSG("PKCS12 MAC digest size mismatch"); + ForceZero(digest, sizeof(digest)); + return MAC_CMP_FAILED_E; + } + #ifdef WOLFSSL_DEBUG_PKCS12 { byte* p; diff --git a/wolfcrypt/src/port/arm/armv8-mlkem-asm.S b/wolfcrypt/src/port/arm/armv8-mlkem-asm.S index 94d429c3dc..5b7df72843 100644 --- a/wolfcrypt/src/port/arm/armv8-mlkem-asm.S +++ b/wolfcrypt/src/port/arm/armv8-mlkem-asm.S @@ -8927,7 +8927,7 @@ L_mlkem_aarch64_cmp_neon_done: orr v8.16b, v8.16b, v9.16b orr v10.16b, v10.16b, v11.16b orr v8.16b, v8.16b, v10.16b - ins v9.b[0], v8.b[1] + ext v9.16b, v8.16b, v8.16b, #8 orr v8.16b, v8.16b, v9.16b mov x0, v8.d[0] subs x0, x0, xzr diff --git a/wolfcrypt/src/port/arm/armv8-mlkem-asm_c.c b/wolfcrypt/src/port/arm/armv8-mlkem-asm_c.c index 601ceaa688..9e5780815f 100644 --- a/wolfcrypt/src/port/arm/armv8-mlkem-asm_c.c +++ b/wolfcrypt/src/port/arm/armv8-mlkem-asm_c.c @@ -8404,7 +8404,7 @@ int mlkem_cmp_neon(const byte* a, const byte* b, int sz) "orr v8.16b, v8.16b, v9.16b\n\t" "orr v10.16b, v10.16b, v11.16b\n\t" "orr v8.16b, v8.16b, v10.16b\n\t" - "ins v9.b[0], v8.b[1]\n\t" + "ext v9.16b, v8.16b, v8.16b, #8\n\t" "orr v8.16b, v8.16b, v9.16b\n\t" "mov x0, v8.d[0]\n\t" "subs x0, x0, xzr\n\t"