From 94ce570d68fc1e8bc32c52bcaacf1a26e2b1b874 Mon Sep 17 00:00:00 2001 From: Alex Lanzano Date: Tue, 10 Nov 2026 10:39:52 -0500 Subject: [PATCH] Add one-shot HMAC generate and verify API Add wc_HmacGenerate() and wc_HmacVerify(), plus _ex variants taking caller provided Hmac storage, heap and devId, modeled on the AES-CMAC one-shots. Tags are full length only. Move the async wait macro out of HAVE_HKDF so both can use it. Add docs and API tests. --- doc/dox_comments/header_files/hmac.h | 157 ++++++++++++++++ tests/api/test_hmac.c | 251 ++++++++++++++++++++++++++ tests/api/test_hmac.h | 8 +- wolfcrypt/src/hmac.c | 181 +++++++++++++++++-- wolfcrypt/src/port/maxim/maxq10xx.c | 7 +- wolfcrypt/src/port/realtek/rtl8735b.c | 12 +- wolfcrypt/test/test.c | 42 +++++ wolfssl/wolfcrypt/hmac.h | 17 ++ 8 files changed, 650 insertions(+), 25 deletions(-) diff --git a/doc/dox_comments/header_files/hmac.h b/doc/dox_comments/header_files/hmac.h index d7cc60c79ff..0a0eb22ccb5 100644 --- a/doc/dox_comments/header_files/hmac.h +++ b/doc/dox_comments/header_files/hmac.h @@ -125,6 +125,163 @@ int wc_HmacFinal(Hmac* hmac, byte* out); */ int wolfSSL_GetHmacMaxSize(void); +/*! + \ingroup HMAC + + \brief Single shot function for generating an HMAC. Initializes, keys, + hashes the message and finalizes in one call. The full digest size tag of + the hash type is always written. + + \return 0 on success + \return BAD_FUNC_ARG if out or outSz is NULL, in is NULL with a non-zero + inSz, key is NULL with a non-zero keySz, or type is invalid + \return BUFFER_E if *outSz is smaller than the digest size of type + \return MEMORY_E if allocating the HMAC context fails + \return HMAC_MIN_KEYLEN_E may be returned in FIPS builds when the key is + shorter than the FIPS minimum + + \param type hash type, such as WC_SHA256 or WC_SHA3_384 + \param out buffer to hold the tag + \param outSz on input the size of out, at least the digest size of type + (WC_MAX_DIGEST_SIZE always fits); on output the digest size + \param in message to authenticate + \param inSz length of the message + \param key HMAC key; may be NULL when keySz is 0 + \param keySz length of the key + + _Example_ + \code + byte tag[WC_SHA256_DIGEST_SIZE]; + word32 tagSz = sizeof(tag); + ret = wc_HmacGenerate(WC_SHA256, tag, &tagSz, msg, msgSz, key, keySz); + \endcode + + \sa wc_HmacGenerate_ex + \sa wc_HmacVerify +*/ +int wc_HmacGenerate(int type, byte* out, word32* outSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz); + +/*! + \ingroup HMAC + + \brief Same as wc_HmacGenerate, but uses caller provided storage for the + HMAC context and takes a heap hint and device ID. The context is + initialized with wc_HmacInit and released with wc_HmacFree inside this + call, so it must not hold a live HMAC operation on entry. + + \return see wc_HmacGenerate; also BAD_FUNC_ARG if hmac is NULL + \return WC_PENDING_E if a crypto callback device returned pending; the + context has been freed and no tag was produced. The caller may call + again, which starts a new operation. + + \param hmac storage for the HMAC context + \param type hash type, such as WC_SHA256 or WC_SHA3_384 + \param out buffer to hold the tag + \param outSz on input the size of out; on output the digest size + \param in message to authenticate + \param inSz length of the message + \param key HMAC key; may be NULL when keySz is 0 + \param keySz length of the key + \param heap heap hint for dynamic memory, or NULL + \param devId device ID for crypto callbacks or async, or INVALID_DEVID + + _Example_ + \code + Hmac hmac; + byte tag[WC_SHA256_DIGEST_SIZE]; + word32 tagSz = sizeof(tag); + ret = wc_HmacGenerate_ex(&hmac, WC_SHA256, tag, &tagSz, msg, msgSz, + key, keySz, NULL, devId); + \endcode + + \sa wc_HmacGenerate + \sa wc_HmacVerify_ex +*/ +int wc_HmacGenerate_ex(Hmac* hmac, int type, byte* out, word32* outSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz, + void* heap, int devId); + +/*! + \ingroup HMAC + + \brief Single shot function for validating an HMAC. Computes the tag over + the message and compares it against check in constant time. Only full + length tags are accepted. + + \return 0 if the tag matches + \return MAC_CMP_FAILED_E if the tag does not match + \return BAD_FUNC_ARG if check is NULL, checkSz is not the digest size of + type, or any wc_HmacGenerate argument check fails + \return MEMORY_E if allocating the HMAC context fails + \return HMAC_MIN_KEYLEN_E may be returned in FIPS builds when the key is + shorter than the FIPS minimum + + \param type hash type, such as WC_SHA256 or WC_SHA3_384 + \param check expected tag + \param checkSz length of the expected tag; must equal the digest size of + type + \param in message that was authenticated + \param inSz length of the message + \param key HMAC key; may be NULL when keySz is 0 + \param keySz length of the key + + _Example_ + \code + ret = wc_HmacVerify(WC_SHA256, tag, tagSz, msg, msgSz, key, keySz); + if (ret == MAC_CMP_FAILED_E) { + // reject the message + } + \endcode + + \sa wc_HmacVerify_ex + \sa wc_HmacGenerate +*/ +int wc_HmacVerify(int type, const byte* check, word32 checkSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz); + +/*! + \ingroup HMAC + + \brief Same as wc_HmacVerify, but uses caller provided storage for the + HMAC context and takes a heap hint and device ID. The context is + initialized and freed inside this call. + + \return see wc_HmacVerify; also BAD_FUNC_ARG if hmac is NULL + \return WC_PENDING_E if a crypto callback device returned pending; the + context has been freed and the tag was not checked. The caller may call + again, which starts a new operation. + + \param hmac storage for the HMAC context + \param type hash type, such as WC_SHA256 or WC_SHA3_384 + \param check expected tag + \param checkSz length of the expected tag; must equal the digest size of + type + \param in message that was authenticated + \param inSz length of the message + \param key HMAC key; may be NULL when keySz is 0 + \param keySz length of the key + \param heap heap hint for dynamic memory, or NULL + \param devId device ID for crypto callbacks or async, or INVALID_DEVID + + _Example_ + \code + Hmac hmac; + ret = wc_HmacVerify_ex(&hmac, WC_SHA256, tag, tagSz, msg, msgSz, + key, keySz, NULL, devId); + \endcode + + \sa wc_HmacVerify + \sa wc_HmacGenerate_ex +*/ +int wc_HmacVerify_ex(Hmac* hmac, int type, const byte* check, word32 checkSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz, + void* heap, int devId); + /*! \ingroup HMAC diff --git a/tests/api/test_hmac.c b/tests/api/test_hmac.c index 916f5c48ab4..47d8b9d7da9 100644 --- a/tests/api/test_hmac.c +++ b/tests/api/test_hmac.c @@ -1513,3 +1513,254 @@ int test_wc_Tls13_HKDF_Extract_ZeroLenIkm(void) #endif return EXPECT_RESULT(); } /* END test_wc_Tls13_HKDF_Extract_ZeroLenIkm */ + +/* + * Testing wc_HmacGenerate(), wc_HmacVerify() and their _ex variants. + */ +int test_wc_HmacGenerate(void) +{ + EXPECT_DECLS; +/* The one-shot HMAC API is newer than the frozen FIPS and selftest modules. */ +#if !defined(NO_HMAC) && !defined(NO_SHA256) && !defined(HAVE_SELFTEST) && \ + !defined(HAVE_FIPS) + /* RFC 4231 test case 2 */ + static const byte key[] = "Jefe"; + static const byte msg[] = "what do ya want for nothing?"; + static const byte expected[WC_SHA256_DIGEST_SIZE] = { + 0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e, + 0x6a, 0x04, 0x24, 0x26, 0x08, 0x95, 0x75, 0xc7, + 0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, 0x39, 0x83, + 0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec, 0x38, 0x43 + }; + const word32 keySz = (word32)XSTRLEN((const char*)key); + const word32 msgSz = (word32)XSTRLEN((const char*)msg); + Hmac hmac; + byte tag[WC_SHA256_DIGEST_SIZE]; + byte ref[WC_SHA256_DIGEST_SIZE]; + byte big[WC_MAX_DIGEST_SIZE]; + word32 tagSz; + + /* Known answer, full length. */ + tagSz = sizeof(tag); + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, tag, &tagSz, msg, msgSz, key, + keySz), 0); + ExpectIntEQ(tagSz, WC_SHA256_DIGEST_SIZE); + ExpectBufEQ(tag, expected, WC_SHA256_DIGEST_SIZE); + ExpectIntEQ(wc_HmacVerify(WC_SHA256, expected, sizeof(expected), msg, + msgSz, key, keySz), 0); + + /* A larger buffer is accepted and outSz reports the digest size. */ + tagSz = sizeof(big); + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, big, &tagSz, msg, msgSz, key, + keySz), 0); + ExpectIntEQ(tagSz, WC_SHA256_DIGEST_SIZE); + ExpectBufEQ(big, expected, WC_SHA256_DIGEST_SIZE); + + /* Any flipped bit is rejected. */ + XMEMCPY(tag, expected, sizeof(expected)); + tag[sizeof(tag) - 1] ^= 0x01; + ExpectIntEQ(wc_HmacVerify(WC_SHA256, tag, sizeof(tag), msg, msgSz, key, + keySz), WC_NO_ERR_TRACE(MAC_CMP_FAILED_E)); + XMEMCPY(tag, expected, sizeof(expected)); + tag[0] ^= 0x80; + ExpectIntEQ(wc_HmacVerify(WC_SHA256, tag, sizeof(tag), msg, msgSz, key, + keySz), WC_NO_ERR_TRACE(MAC_CMP_FAILED_E)); + + /* A valid tag is rejected under a different key or message. */ + ExpectIntEQ(wc_HmacVerify(WC_SHA256, expected, sizeof(expected), msg, + msgSz, (const byte*)"Jefx", keySz), + WC_NO_ERR_TRACE(MAC_CMP_FAILED_E)); + ExpectIntEQ(wc_HmacVerify(WC_SHA256, expected, sizeof(expected), msg, + msgSz - 1, key, keySz), WC_NO_ERR_TRACE(MAC_CMP_FAILED_E)); + + /* Empty key and empty message match the streaming API. */ + ExpectIntEQ(wc_HmacInit(&hmac, NULL, INVALID_DEVID), 0); + ExpectIntEQ(wc_HmacSetKey(&hmac, WC_SHA256, NULL, 0), 0); + ExpectIntEQ(wc_HmacFinal(&hmac, ref), 0); + wc_HmacFree(&hmac); + tagSz = sizeof(tag); + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, tag, &tagSz, NULL, 0, NULL, 0), 0); + ExpectBufEQ(tag, ref, sizeof(ref)); + ExpectIntEQ(wc_HmacVerify(WC_SHA256, ref, sizeof(ref), NULL, 0, NULL, 0), + 0); + + /* _ex variants with caller provided context storage. */ + tagSz = sizeof(tag); + ExpectIntEQ(wc_HmacGenerate_ex(&hmac, WC_SHA256, tag, &tagSz, msg, msgSz, + key, keySz, NULL, INVALID_DEVID), 0); + ExpectBufEQ(tag, expected, sizeof(expected)); + ExpectIntEQ(wc_HmacVerify_ex(&hmac, WC_SHA256, expected, sizeof(expected), + msg, msgSz, key, keySz, NULL, INVALID_DEVID), 0); + + /* Bad arguments. */ + tagSz = sizeof(tag); + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, NULL, &tagSz, msg, msgSz, key, + keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, tag, NULL, msg, msgSz, key, + keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, tag, &tagSz, NULL, msgSz, key, + keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, tag, &tagSz, msg, msgSz, NULL, + keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacGenerate(WC_HASH_TYPE_NONE, tag, &tagSz, msg, msgSz, + key, keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + tagSz = 0; + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, tag, &tagSz, msg, msgSz, key, + keySz), WC_NO_ERR_TRACE(BUFFER_E)); + tagSz = WC_SHA256_DIGEST_SIZE - 1; + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, tag, &tagSz, msg, msgSz, key, + keySz), WC_NO_ERR_TRACE(BUFFER_E)); + ExpectIntEQ(tagSz, WC_SHA256_DIGEST_SIZE - 1); + tagSz = sizeof(tag); + ExpectIntEQ(wc_HmacGenerate_ex(NULL, WC_SHA256, tag, &tagSz, msg, msgSz, + key, keySz, NULL, INVALID_DEVID), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + ExpectIntEQ(wc_HmacVerify(WC_SHA256, NULL, sizeof(expected), msg, msgSz, + key, keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacVerify(WC_SHA256, expected, 0, msg, msgSz, key, keySz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacVerify(WC_SHA256, expected, WC_SHA256_DIGEST_SIZE - 1, + msg, msgSz, key, keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacVerify(WC_HASH_TYPE_NONE, expected, sizeof(expected), + msg, msgSz, key, keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacVerify(WC_SHA256, expected, WC_SHA256_DIGEST_SIZE + 1, + msg, msgSz, key, keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacVerify(WC_SHA256, expected, sizeof(expected), NULL, + msgSz, key, keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacVerify(WC_SHA256, expected, sizeof(expected), msg, + msgSz, NULL, keySz), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wc_HmacVerify_ex(NULL, WC_SHA256, expected, sizeof(expected), + msg, msgSz, key, keySz, NULL, INVALID_DEVID), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif + return EXPECT_RESULT(); +} /* END test_wc_HmacGenerate */ + +/* + * Testing wc_HmacGenerate() and wc_HmacVerify() digest sizes beyond SHA-256. + */ +int test_wc_HmacGenerate_HashTypes(void) +{ + EXPECT_DECLS; +#if !defined(NO_HMAC) && !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) && \ + (defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512)) + /* RFC 4231 test case 2 */ + static const byte key[] = "Jefe"; + static const byte msg[] = "what do ya want for nothing?"; + const word32 keySz = (word32)XSTRLEN((const char*)key); + const word32 msgSz = (word32)XSTRLEN((const char*)msg); + byte tag[WC_MAX_DIGEST_SIZE]; + word32 tagSz; +#ifdef WOLFSSL_SHA384 + static const byte expected384[WC_SHA384_DIGEST_SIZE] = { + 0xaf, 0x45, 0xd2, 0xe3, 0x76, 0x48, 0x40, 0x31, + 0x61, 0x7f, 0x78, 0xd2, 0xb5, 0x8a, 0x6b, 0x1b, + 0x9c, 0x7e, 0xf4, 0x64, 0xf5, 0xa0, 0x1b, 0x47, + 0xe4, 0x2e, 0xc3, 0x73, 0x63, 0x22, 0x44, 0x5e, + 0x8e, 0x22, 0x40, 0xca, 0x5e, 0x69, 0xe2, 0xc7, + 0x8b, 0x32, 0x39, 0xec, 0xfa, 0xb2, 0x16, 0x49 + }; +#endif +#ifdef WOLFSSL_SHA512 + static const byte expected512[WC_SHA512_DIGEST_SIZE] = { + 0x16, 0x4b, 0x7a, 0x7b, 0xfc, 0xf8, 0x19, 0xe2, + 0xe3, 0x95, 0xfb, 0xe7, 0x3b, 0x56, 0xe0, 0xa3, + 0x87, 0xbd, 0x64, 0x22, 0x2e, 0x83, 0x1f, 0xd6, + 0x10, 0x27, 0x0c, 0xd7, 0xea, 0x25, 0x05, 0x54, + 0x97, 0x58, 0xbf, 0x75, 0xc0, 0x5a, 0x99, 0x4a, + 0x6d, 0x03, 0x4f, 0x65, 0xf8, 0xf0, 0xe6, 0xfd, + 0xca, 0xea, 0xb1, 0xa3, 0x4d, 0x4a, 0x6b, 0x4b, + 0x63, 0x6e, 0x07, 0x0a, 0x38, 0xbc, 0xe7, 0x37 + }; +#endif + +#ifdef WOLFSSL_SHA384 + tagSz = sizeof(tag); + ExpectIntEQ(wc_HmacGenerate(WC_SHA384, tag, &tagSz, msg, msgSz, key, + keySz), 0); + ExpectIntEQ(tagSz, WC_SHA384_DIGEST_SIZE); + ExpectBufEQ(tag, expected384, WC_SHA384_DIGEST_SIZE); + ExpectIntEQ(wc_HmacVerify(WC_SHA384, expected384, sizeof(expected384), + msg, msgSz, key, keySz), 0); + ExpectIntEQ(wc_HmacVerify(WC_SHA384, expected384, + WC_SHA384_DIGEST_SIZE - 1, msg, msgSz, key, keySz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + tagSz = WC_SHA384_DIGEST_SIZE - 1; + ExpectIntEQ(wc_HmacGenerate(WC_SHA384, tag, &tagSz, msg, msgSz, key, + keySz), WC_NO_ERR_TRACE(BUFFER_E)); +#endif +#ifdef WOLFSSL_SHA512 + tagSz = sizeof(tag); + ExpectIntEQ(wc_HmacGenerate(WC_SHA512, tag, &tagSz, msg, msgSz, key, + keySz), 0); + ExpectIntEQ(tagSz, WC_SHA512_DIGEST_SIZE); + ExpectBufEQ(tag, expected512, WC_SHA512_DIGEST_SIZE); + ExpectIntEQ(wc_HmacVerify(WC_SHA512, expected512, sizeof(expected512), + msg, msgSz, key, keySz), 0); + ExpectIntEQ(wc_HmacVerify(WC_SHA512, expected512, + WC_SHA512_DIGEST_SIZE - 1, msg, msgSz, key, keySz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#endif +#endif + return EXPECT_RESULT(); +} /* END test_wc_HmacGenerate_HashTypes */ + +#if !defined(NO_HMAC) && !defined(NO_SHA256) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && !defined(WOLFSSL_KCAPI_HMAC) && \ + !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) +#define TEST_HMAC_ONESHOT_DEVID 0x484d4f53 /* "HMOS" */ + +/* Counts HMAC callbacks, then declines so software computes the tag. */ +static int test_hmac_oneshot_cb(int cbDevId, wc_CryptoInfo* info, void* ctx) +{ + (void)cbDevId; + + if ((info != NULL) && (info->algo_type == WC_ALGO_TYPE_HMAC)) { + (*(int*)ctx)++; + } + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif + +/* + * Testing that wc_HmacGenerate_ex() and wc_HmacVerify_ex() pass devId to a + * crypto callback and fall back to software when it declines. + */ +int test_wc_HmacGenerate_CryptoCb(void) +{ + EXPECT_DECLS; +#if !defined(NO_HMAC) && !defined(NO_SHA256) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && !defined(WOLFSSL_KCAPI_HMAC) && \ + !defined(HAVE_SELFTEST) && !defined(HAVE_FIPS) + static const byte key[] = "Jefe"; + static const byte msg[] = "what do ya want for nothing?"; + const word32 keySz = (word32)XSTRLEN((const char*)key); + const word32 msgSz = (word32)XSTRLEN((const char*)msg); + Hmac hmac; + byte sw[WC_SHA256_DIGEST_SIZE]; + byte tag[WC_SHA256_DIGEST_SIZE]; + word32 tagSz; + int calls = 0; + + tagSz = sizeof(sw); + ExpectIntEQ(wc_HmacGenerate(WC_SHA256, sw, &tagSz, msg, msgSz, key, + keySz), 0); + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_HMAC_ONESHOT_DEVID, + test_hmac_oneshot_cb, &calls), 0); + + tagSz = sizeof(tag); + ExpectIntEQ(wc_HmacGenerate_ex(&hmac, WC_SHA256, tag, &tagSz, msg, msgSz, + key, keySz, NULL, TEST_HMAC_ONESHOT_DEVID), 0); + ExpectIntGT(calls, 0); + ExpectBufEQ(tag, sw, sizeof(sw)); + + calls = 0; + ExpectIntEQ(wc_HmacVerify_ex(&hmac, WC_SHA256, sw, sizeof(sw), msg, msgSz, + key, keySz, NULL, TEST_HMAC_ONESHOT_DEVID), 0); + ExpectIntGT(calls, 0); + + wc_CryptoCb_UnRegisterDevice(TEST_HMAC_ONESHOT_DEVID); +#endif + return EXPECT_RESULT(); +} /* END test_wc_HmacGenerate_CryptoCb */ diff --git a/tests/api/test_hmac.h b/tests/api/test_hmac.h index 639a65f7814..25071872e61 100644 --- a/tests/api/test_hmac.h +++ b/tests/api/test_hmac.h @@ -51,6 +51,9 @@ int test_wc_HmacId_NoDevice(void); int test_wc_HmacNoSwKey_CryptoCb(void); int test_wc_HKDF_NullKeyEdgeCases(void); int test_wc_Tls13_HKDF_Extract_ZeroLenIkm(void); +int test_wc_HmacGenerate(void); +int test_wc_HmacGenerate_HashTypes(void); +int test_wc_HmacGenerate_CryptoCb(void); #define TEST_HMAC_DECLS \ TEST_DECL_GROUP("hmac", test_wc_Md5HmacSetKey), \ @@ -79,6 +82,9 @@ int test_wc_Tls13_HKDF_Extract_ZeroLenIkm(void); TEST_DECL_GROUP("hmac", test_wc_HmacId_NoDevice), \ TEST_DECL_GROUP("hmac", test_wc_HmacNoSwKey_CryptoCb), \ TEST_DECL_GROUP("hmac", test_wc_HKDF_NullKeyEdgeCases), \ - TEST_DECL_GROUP("hmac", test_wc_Tls13_HKDF_Extract_ZeroLenIkm) + TEST_DECL_GROUP("hmac", test_wc_Tls13_HKDF_Extract_ZeroLenIkm), \ + TEST_DECL_GROUP("hmac", test_wc_HmacGenerate), \ + TEST_DECL_GROUP("hmac", test_wc_HmacGenerate_HashTypes), \ + TEST_DECL_GROUP("hmac", test_wc_HmacGenerate_CryptoCb) #endif /* WOLFCRYPT_TEST_HMAC_H */ diff --git a/wolfcrypt/src/hmac.c b/wolfcrypt/src/hmac.c index 52f9c56411e..2e5b84c39da 100644 --- a/wolfcrypt/src/hmac.c +++ b/wolfcrypt/src/hmac.c @@ -1854,17 +1854,16 @@ int wolfSSL_GetHmacMaxSize(void) return WC_MAX_DIGEST_SIZE; } -#ifdef HAVE_HKDF - /* Wait out an async HMAC sub-op: the HKDF loops cannot resume - * mid-chain. QAT/Cavium only (not covered by CI); a crypto callback - * pending must instead propagate so the caller can re-invoke. */ +/* Wait out an async HMAC sub-op: the one-shot and HKDF callers cannot resume + * mid-chain. QAT/Cavium only (not covered by CI); a crypto callback pending + * must instead propagate so the caller can re-invoke. */ #if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_HMAC) && \ (defined(HAVE_INTEL_QA) || defined(HAVE_CAVIUM)) /* QAT/Cavium only: WOLFSSL_ASYNC_REINVOKE is never defined for these - * backends (see internal.h), so an HKDF sub-op can only pend on the + * backends (see internal.h), so an HMAC sub-op can only pend on the * hardware device, never on a re-invokable crypto callback. Wait - * unconditionally: the HKDF loops cannot resume mid-chain. */ - #define HKDF_HMAC_WAIT(ret, hmac) \ + * unconditionally: the callers cannot resume mid-chain. */ + #define HMAC_SUBOP_WAIT(ret, hmac) \ do { \ (ret) = wc_AsyncWait((ret), &(hmac)->asyncDev, \ WC_ASYNC_FLAG_NONE); \ @@ -1872,8 +1871,158 @@ int wolfSSL_GetHmacMaxSize(void) #else /* No HMAC device to wait on, or the pending must reach the caller * (crypto callback re-invocation); hmac is unevaluated. */ - #define HKDF_HMAC_WAIT(ret, hmac) WC_DO_NOTHING + #define HMAC_SUBOP_WAIT(ret, hmac) WC_DO_NOTHING +#endif + +/* One-shot HMAC using caller provided storage for the context. + * + * hmac Storage for the HMAC context. Initialized and freed here. + * type The hash algorithm type. + * out Buffer to hold the tag. + * outSz On input, the size of out (at least the digest size). On output, + * the digest size. + * in The message. + * inSz The size of the message. + * key The HMAC key. May be NULL when keySz is 0. + * keySz The size of the key. + * heap The heap hint. + * devId The device ID. + * returns 0 on success, BUFFER_E when out is too small, otherwise failure. + */ +int wc_HmacGenerate_ex(Hmac* hmac, int type, byte* out, word32* outSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz, + void* heap, int devId) +{ + int ret; + word32 digestSz; + + if (hmac == NULL || out == NULL || outSz == NULL || + (in == NULL && inSz != 0) || (key == NULL && keySz != 0)) { + return BAD_FUNC_ARG; + } + + ret = wc_HmacSizeByType(type); + if (ret < 0) { + return ret; + } + digestSz = (word32)ret; + if (*outSz < digestSz) { + return BUFFER_E; + } + + ret = wc_HmacInit(hmac, heap, devId); + if (ret == 0) { + ret = wc_HmacSetKey(hmac, type, key, keySz); + if (ret == 0) { +#ifdef WOLF_CRYPTO_CB + ret = WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); + if (WC_HMAC_USE_CRYPTOCB(hmac)) { + ret = wc_CryptoCb_Hmac(hmac, hmac->macType, in, inSz, out); + } + if (ret == WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE)) #endif + { + ret = wc_HmacUpdate(hmac, in, inSz); + HMAC_SUBOP_WAIT(ret, hmac); + if (ret == 0) { + ret = wc_HmacFinal(hmac, out); + HMAC_SUBOP_WAIT(ret, hmac); + } + } + } + wc_HmacFree(hmac); + } + if (ret == 0) { + *outSz = digestSz; + } + + return ret; +} + +int wc_HmacGenerate(int type, byte* out, word32* outSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz) +{ + int ret; + WC_DECLARE_VAR(hmac, Hmac, 1, 0); + + WC_ALLOC_VAR_EX(hmac, Hmac, 1, NULL, DYNAMIC_TYPE_HMAC, + return MEMORY_E); + + ret = wc_HmacGenerate_ex(hmac, type, out, outSz, in, inSz, key, keySz, + NULL, INVALID_DEVID); + + WC_FREE_VAR_EX(hmac, NULL, DYNAMIC_TYPE_HMAC); + + return ret; +} + +/* One-shot HMAC verify using caller provided storage for the context. + * checkSz must be the digest size of type. + * + * returns 0 when the tag matches, MAC_CMP_FAILED_E when it does not, + * otherwise failure. + */ +int wc_HmacVerify_ex(Hmac* hmac, int type, const byte* check, word32 checkSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz, + void* heap, int devId) +{ + int ret; + byte tag[WC_MAX_DIGEST_SIZE]; + word32 tagSz = sizeof(tag); + + if (check == NULL) { + return BAD_FUNC_ARG; + } + + ret = wc_HmacSizeByType(type); + if (ret < 0) { + return ret; + } + if (checkSz != (word32)ret) { + return BAD_FUNC_ARG; + } + + XMEMSET(tag, 0, sizeof(tag)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("wc_HmacVerify_ex tag", tag, sizeof(tag)); +#endif + + ret = wc_HmacGenerate_ex(hmac, type, tag, &tagSz, in, inSz, key, keySz, + heap, devId); + if (ret == 0 && ConstantCompare(check, tag, (int)checkSz) != 0) { + ret = MAC_CMP_FAILED_E; + } + + ForceZero(tag, sizeof(tag)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(tag, sizeof(tag)); +#endif + + return ret; +} + +int wc_HmacVerify(int type, const byte* check, word32 checkSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz) +{ + int ret; + WC_DECLARE_VAR(hmac, Hmac, 1, 0); + + WC_ALLOC_VAR_EX(hmac, Hmac, 1, NULL, DYNAMIC_TYPE_HMAC, + return MEMORY_E); + + ret = wc_HmacVerify_ex(hmac, type, check, checkSz, in, inSz, key, keySz, + NULL, INVALID_DEVID); + + WC_FREE_VAR_EX(hmac, NULL, DYNAMIC_TYPE_HMAC); + + return ret; +} + +#ifdef HAVE_HKDF /* HMAC-KDF-Extract. * RFC 5869 - HMAC-based Extract-and-Expand Key Derivation Function (HKDF). @@ -1937,11 +2086,11 @@ int wolfSSL_GetHmacMaxSize(void) #endif if (ret == 0) { ret = wc_HmacUpdate(myHmac, inKey, inKeySz); - HKDF_HMAC_WAIT(ret, myHmac); + HMAC_SUBOP_WAIT(ret, myHmac); } if (ret == 0) { ret = wc_HmacFinal(myHmac, out); - HKDF_HMAC_WAIT(ret, myHmac); + HMAC_SUBOP_WAIT(ret, myHmac); } wc_HmacFree(myHmac); } @@ -2035,19 +2184,19 @@ int wolfSSL_GetHmacMaxSize(void) if (ret != 0) break; ret = wc_HmacUpdate(myHmac, tmp, tmpSz); - HKDF_HMAC_WAIT(ret, myHmac); + HMAC_SUBOP_WAIT(ret, myHmac); if (ret != 0) break; ret = wc_HmacUpdate(myHmac, info, infoSz); - HKDF_HMAC_WAIT(ret, myHmac); + HMAC_SUBOP_WAIT(ret, myHmac); if (ret != 0) break; ret = wc_HmacUpdate(myHmac, &n, 1); - HKDF_HMAC_WAIT(ret, myHmac); + HMAC_SUBOP_WAIT(ret, myHmac); if (ret != 0) break; ret = wc_HmacFinal(myHmac, tmp); - HKDF_HMAC_WAIT(ret, myHmac); + HMAC_SUBOP_WAIT(ret, myHmac); if (ret != 0) break; @@ -2146,8 +2295,8 @@ int wolfSSL_GetHmacMaxSize(void) outSz, NULL, INVALID_DEVID); } -#undef HKDF_HMAC_WAIT - #endif /* HAVE_HKDF */ +#undef HMAC_SUBOP_WAIT + #endif /* NO_HMAC */ diff --git a/wolfcrypt/src/port/maxim/maxq10xx.c b/wolfcrypt/src/port/maxim/maxq10xx.c index bdfa722c072..c90e5373aa4 100644 --- a/wolfcrypt/src/port/maxim/maxq10xx.c +++ b/wolfcrypt/src/port/maxim/maxq10xx.c @@ -1893,7 +1893,7 @@ int wolfSSL_MAXQ10XX_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) } #ifdef WOLFSSL_MAXQ108X else if (info->algo_type == WC_ALGO_TYPE_HMAC) { - if (info->hmac.in != NULL && info->hmac.digest == NULL) { + if (info->hmac.in != NULL) { rc = 0; if (mac_comp_active == 0) { rc = wc_MAXQ10XX_HmacSetKey(info->hmac.macType); @@ -1901,8 +1901,11 @@ int wolfSSL_MAXQ10XX_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) if (rc == 0) { rc = wc_MAXQ10XX_HmacUpdate(info->hmac.in, info->hmac.inSz); } + if (rc == 0 && info->hmac.digest != NULL) { + rc = wc_MAXQ10XX_HmacFinal(info->hmac.digest); + } } - else if (info->hmac.in == NULL && info->hmac.digest != NULL) { + else if (info->hmac.digest != NULL) { rc = wc_MAXQ10XX_HmacFinal(info->hmac.digest); } else { diff --git a/wolfcrypt/src/port/realtek/rtl8735b.c b/wolfcrypt/src/port/realtek/rtl8735b.c index a991e26d664..1120e4d7a86 100644 --- a/wolfcrypt/src/port/realtek/rtl8735b.c +++ b/wolfcrypt/src/port/realtek/rtl8735b.c @@ -1451,9 +1451,9 @@ static int Rtl8735bHuk_Hmac(const byte* seed, const byte* msg, word32 msgSz, /* Route an HMAC request to the HUK backend. Handles only HMAC-SHA256 keyed by a * 32-byte HUK seed (the seed is the wc_HmacSetKey key, read from hmac->keyRaw); - * anything else returns CRYPTOCB_UNAVAILABLE for software fallback. Update calls - * (digest == NULL) accumulate; the final call (digest != NULL) produces the MAC - * and frees the buffer. The HmacFree cleanup callback also arrives as a final + * anything else returns CRYPTOCB_UNAVAILABLE for software fallback. Any input is + * accumulated; when digest != NULL the MAC is then produced and the buffer + * freed. The HmacFree cleanup callback also arrives as a final * call with a throwaway digest; computing into it is harmless. */ static int Rtl8735bHuk_HmacCb(struct wc_CryptoInfo* info) { @@ -1481,9 +1481,9 @@ static int Rtl8735bHuk_HmacCb(struct wc_CryptoInfo* info) } seed = hmac->keyRaw; - if (info->hmac.digest == NULL) { - return Rtl8735bHuk_HmacAccumulate(hmac, info->hmac.in, - info->hmac.inSz); + ret = Rtl8735bHuk_HmacAccumulate(hmac, info->hmac.in, info->hmac.inSz); + if (ret != 0 || info->hmac.digest == NULL) { + return ret; } ctx = (Rtl8735bHmacCtx*)hmac->hash.sha256.devCtx; if (ctx != NULL) { diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 9fabf2da026..b6d74fed214 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -12563,6 +12563,32 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t hmac_sha256_test(void) wc_HmacFree(&hmac); +#if !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + { + word32 hashSz = sizeof(hash); + + XMEMSET(hash, 0, sizeof(hash)); + ret = wc_HmacGenerate_ex(&hmac, WC_SHA256, hash, &hashSz, + (const byte*)test_hmac[i].input, (word32)test_hmac[i].inLen, + (const byte*)keys[i], (word32)XSTRLEN(keys[i]), HEAP_HINT, + devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out); + if (hashSz != WC_SHA256_DIGEST_SIZE || + XMEMCMP(hash, test_hmac[i].output, + WC_SHA256_DIGEST_SIZE) != 0) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out); + + ret = wc_HmacVerify_ex(&hmac, WC_SHA256, + (const byte*)test_hmac[i].output, WC_SHA256_DIGEST_SIZE, + (const byte*)test_hmac[i].input, (word32)test_hmac[i].inLen, + (const byte*)keys[i], (word32)XSTRLEN(keys[i]), HEAP_HINT, + devId); + if (ret != 0) + ERROR_OUT(WC_TEST_RET_ENC_I(i), out); + } +#endif + #if !defined(HAVE_SELFTEST) && (!defined(HAVE_FIPS) || FIPS_VERSION3_GT(7,0,0)) if (test_hmac[i].input != NULL) { ret = wc_HmacUpdate(hmac_copy, (byte*)test_hmac[i].input, @@ -98487,6 +98513,9 @@ typedef struct { int chachaPolyEncCount; /* ChaCha20-Poly1305 encrypt cb invocations */ int chachaPolyDecCount; /* ChaCha20-Poly1305 decrypt cb invocations */ #endif +#if !defined(NO_HMAC) && !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + int hmacOneShotCount; /* one-shot HMAC callback invocations */ +#endif #if (defined(HAVE_PBKDF2) && !defined(NO_HMAC) && !defined(NO_PWDBASED)) int pbkdf2Count; /* PBKDF2 callback invocations */ int pbkdf2Decline; /* when set, decline so software fallback runs */ @@ -103164,6 +103193,12 @@ static int myCryptoDevCb(int devIdArg, wc_CryptoInfo* info, void* ctx) info->hmac.hmac, info->hmac.in, info->hmac.inSz); + if (ret == 0 && info->hmac.digest != NULL) { + myCtx->hmacOneShotCount++; + ret = wc_HmacFinal( + info->hmac.hmac, + info->hmac.digest); + } } else if (info->hmac.digest != NULL) { ret = wc_HmacFinal( @@ -105269,6 +105304,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) myCtx.chachaPolyEncCount = 0; myCtx.chachaPolyDecCount = 0; #endif +#if !defined(NO_HMAC) && !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + myCtx.hmacOneShotCount = 0; +#endif #if (defined(HAVE_PBKDF2) && !defined(NO_HMAC) && !defined(NO_PWDBASED)) myCtx.pbkdf2Count = 0; myCtx.pbkdf2Decline = 0; @@ -106699,6 +106737,10 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cryptocb_test(void) #ifndef NO_SHA256 if (ret == 0) ret = hmac_sha256_test(); + #if !defined(HAVE_FIPS) && !defined(HAVE_SELFTEST) + if (ret == 0 && myCtx.hmacOneShotCount == 0) + ret = WC_TEST_RET_ENC_NC; + #endif #endif #ifdef WOLFSSL_SHA3 if (ret == 0) diff --git a/wolfssl/wolfcrypt/hmac.h b/wolfssl/wolfcrypt/hmac.h index a874c31d3f3..25beb71e4fa 100644 --- a/wolfssl/wolfcrypt/hmac.h +++ b/wolfssl/wolfcrypt/hmac.h @@ -213,6 +213,23 @@ WOLFSSL_API void wc_HmacFree(Hmac* hmac); WOLFSSL_API int wolfSSL_GetHmacMaxSize(void); +WOLFSSL_API int wc_HmacGenerate(int type, byte* out, word32* outSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz); +WOLFSSL_API int wc_HmacGenerate_ex(Hmac* hmac, int type, + byte* out, word32* outSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz, + void* heap, int devId); +WOLFSSL_API int wc_HmacVerify(int type, const byte* check, word32 checkSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz); +WOLFSSL_API int wc_HmacVerify_ex(Hmac* hmac, int type, + const byte* check, word32 checkSz, + const byte* in, word32 inSz, + const byte* key, word32 keySz, + void* heap, int devId); + WOLFSSL_LOCAL int _InitHmac(Hmac* hmac, int type, void* heap); WOLFSSL_LOCAL int _HmacInitIOHashes(Hmac* hmac); #ifdef WC_HMAC_HAVE_KEY_LOCATION