Skip to content

Commit d928a18

Browse files
committed
Regression test for the EVP_DigestVerifyFinal HMAC zero-length tag
forgery. wolfSSL_EVP_DigestVerifyFinal in wolfcrypt/src/evp.c uses the caller-supplied siglen as the comparison length for the HMAC branch with only an upper bound check: if (siglen > hashLen || siglen > INT_MAX) return WOLFSSL_FAILURE; ... if (ConstantCompare(sig, digest, (int)siglen) == 0) return WOLFSSL_SUCCESS; There is no lower bound, so passing siglen=0 with any non-NULL sig pointer makes ConstantCompare return 0 (zero-length comparison is trivially equal) and the function reports SUCCESS. An attacker who controls a serialized MAC length field forwarded to this API obtains a universal HMAC forgery without knowledge of the key. The test: 1. Builds an HMAC-SHA256 EVP_PKEY with a fixed key. 2. Positive control: signs a message, verifies the genuine 32-byte tag round-trips and a wrong full-length tag is rejected. 3. Forgery probe: calls EVP_DigestVerifyFinal with siglen=0 and a pointer to all-zero buffer. Asserts the call does NOT return WOLFSSL_SUCCESS. On the unfixed tree this returns WOLFSSL_SUCCESS (forgery accepted); on a fixed tree the implementation must require siglen >= the algorithm's native digest size or otherwise reject zero-length tags.
1 parent 7da1916 commit d928a18

2 files changed

Lines changed: 76 additions & 0 deletions

File tree

tests/api/test_evp_pkey.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,79 @@ int test_wolfSSL_EVP_MD_hmac_signing(void)
382382
return EXPECT_RESULT();
383383
}
384384

385+
int test_wolfSSL_EVP_DigestVerify_HMAC_zero_len_forgery(void)
386+
{
387+
EXPECT_DECLS;
388+
#if defined(OPENSSL_EXTRA) && !defined(NO_HMAC) && !defined(NO_SHA256)
389+
static const unsigned char key[] = {
390+
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
391+
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
392+
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
393+
0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b
394+
};
395+
static const char message[] = "wolfSSL DigestVerifyFinal forgery probe";
396+
static const unsigned char zeros[WC_MAX_DIGEST_SIZE] = { 0 };
397+
398+
WOLFSSL_EVP_PKEY* pkey = NULL;
399+
WOLFSSL_EVP_MD_CTX mdCtx;
400+
unsigned char tag[WC_MAX_DIGEST_SIZE];
401+
size_t tagLen = sizeof(tag);
402+
403+
wolfSSL_EVP_MD_CTX_init(&mdCtx);
404+
405+
ExpectNotNull(pkey = wolfSSL_EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL,
406+
key, (int)sizeof(key)));
407+
408+
/* Compute the genuine HMAC-SHA256 tag for the message. */
409+
ExpectIntEQ(wolfSSL_EVP_DigestSignInit(&mdCtx, NULL, wolfSSL_EVP_sha256(),
410+
NULL, pkey), 1);
411+
ExpectIntEQ(wolfSSL_EVP_DigestSignUpdate(&mdCtx, message,
412+
(unsigned int)XSTRLEN(message)),
413+
1);
414+
ExpectIntEQ(wolfSSL_EVP_DigestSignFinal(&mdCtx, tag, &tagLen), 1);
415+
ExpectIntEQ((int)tagLen, WC_SHA256_DIGEST_SIZE);
416+
ExpectIntEQ(wolfSSL_EVP_MD_CTX_cleanup(&mdCtx), 1);
417+
418+
/* Positive control: full-length genuine tag verifies. */
419+
wolfSSL_EVP_MD_CTX_init(&mdCtx);
420+
ExpectIntEQ(wolfSSL_EVP_DigestVerifyInit(&mdCtx, NULL, wolfSSL_EVP_sha256(),
421+
NULL, pkey), 1);
422+
ExpectIntEQ(wolfSSL_EVP_DigestVerifyUpdate(&mdCtx, message,
423+
(unsigned int)XSTRLEN(message)),
424+
1);
425+
ExpectIntEQ(wolfSSL_EVP_DigestVerifyFinal(&mdCtx, tag, tagLen), 1);
426+
ExpectIntEQ(wolfSSL_EVP_MD_CTX_cleanup(&mdCtx), 1);
427+
428+
/* Negative control: full-length all-zeros tag is rejected. Guards
429+
* against the forgery test passing for the wrong reason (e.g. if
430+
* verification became a no-op that always returns failure). */
431+
wolfSSL_EVP_MD_CTX_init(&mdCtx);
432+
ExpectIntEQ(wolfSSL_EVP_DigestVerifyInit(&mdCtx, NULL, wolfSSL_EVP_sha256(),
433+
NULL, pkey), 1);
434+
ExpectIntEQ(wolfSSL_EVP_DigestVerifyUpdate(&mdCtx, message,
435+
(unsigned int)XSTRLEN(message)),
436+
1);
437+
ExpectIntNE(wolfSSL_EVP_DigestVerifyFinal(&mdCtx, zeros,
438+
WC_SHA256_DIGEST_SIZE), 1);
439+
ExpectIntEQ(wolfSSL_EVP_MD_CTX_cleanup(&mdCtx), 1);
440+
441+
/* Forgery probe: zero-length tag must be rejected. On the unfixed
442+
* tree this assertion fails because ConstantCompare(sig, digest, 0)
443+
* trivially returns 0 and the function reports WOLFSSL_SUCCESS. */
444+
wolfSSL_EVP_MD_CTX_init(&mdCtx);
445+
ExpectIntEQ(wolfSSL_EVP_DigestVerifyInit(&mdCtx, NULL, wolfSSL_EVP_sha256(),
446+
NULL, pkey), 1);
447+
ExpectIntEQ(wolfSSL_EVP_DigestVerifyUpdate(&mdCtx, message,
448+
(unsigned int)XSTRLEN(message)),
449+
1);
450+
ExpectIntNE(wolfSSL_EVP_DigestVerifyFinal(&mdCtx, zeros, 0), 1);
451+
ExpectIntEQ(wolfSSL_EVP_MD_CTX_cleanup(&mdCtx), 1);
452+
453+
wolfSSL_EVP_PKEY_free(pkey);
454+
#endif
455+
return EXPECT_RESULT();
456+
}
457+
385458
int test_wolfSSL_EVP_PKEY_new_mac_key(void)
386459
{
387460
EXPECT_DECLS;

tests/api/test_evp_pkey.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ int test_wolfSSL_EVP_PKEY_base_id(void);
3232
int test_wolfSSL_EVP_PKEY_id(void);
3333
int test_wolfSSL_EVP_MD_pkey_type(void);
3434
int test_wolfSSL_EVP_MD_hmac_signing(void);
35+
int test_wolfSSL_EVP_DigestVerify_HMAC_zero_len_forgery(void);
3536
int test_wolfSSL_EVP_PKEY_new_mac_key(void);
3637
int test_wolfSSL_EVP_PKEY_hkdf(void);
3738
int test_wolfSSL_EVP_PBE_scrypt(void);
@@ -70,6 +71,8 @@ int test_wolfSSL_EVP_PKEY_print_public(void);
7071
TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_id), \
7172
TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_MD_pkey_type), \
7273
TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_MD_hmac_signing), \
74+
TEST_DECL_GROUP("evp_pkey", \
75+
test_wolfSSL_EVP_DigestVerify_HMAC_zero_len_forgery), \
7376
TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_new_mac_key), \
7477
TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PKEY_hkdf), \
7578
TEST_DECL_GROUP("evp_pkey", test_wolfSSL_EVP_PBE_scrypt), \

0 commit comments

Comments
 (0)