From 0a7bbfcbc7813a526ac93d4084fd1957e0d59355 Mon Sep 17 00:00:00 2001 From: Justin Smith Date: Fri, 18 Oct 2024 15:01:31 -0400 Subject: [PATCH] Expand support for EVP_PKEY_HMAC --- crypto/fipsmodule/evp/internal.h | 19 ++- crypto/fipsmodule/evp/p_hmac.c | 105 +++++++++++++++- crypto/hmac_extra/hmac_test.cc | 206 ++++++++++++++++--------------- 3 files changed, 224 insertions(+), 106 deletions(-) diff --git a/crypto/fipsmodule/evp/internal.h b/crypto/fipsmodule/evp/internal.h index f90c6ff6c7..50b323f083 100644 --- a/crypto/fipsmodule/evp/internal.h +++ b/crypto/fipsmodule/evp/internal.h @@ -243,6 +243,7 @@ int EVP_RSA_PKEY_CTX_ctrl(EVP_PKEY_CTX *ctx, int optype, int cmd, int p1, void * #define EVP_PKEY_CTRL_DH_PAD (EVP_PKEY_ALG_CTRL + 19) #define EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN (EVP_PKEY_ALG_CTRL + 20) #define EVP_PKEY_CTRL_DH_PARAMGEN_GENERATOR (EVP_PKEY_ALG_CTRL + 21) +#define EVP_PKEY_CTRL_SET_MAC_KEY (EVP_PKEY_ALG_CTRL + 22) // EVP_PKEY_CTX_KEYGEN_INFO_COUNT is the maximum array length for // |EVP_PKEY_CTX->keygen_info|. The array length corresponds to the number of @@ -343,18 +344,26 @@ struct evp_pkey_method_st { // operation. int used_for_hmac(EVP_MD_CTX *ctx); -typedef struct { - const EVP_MD *md; // MD for HMAC use. - HMAC_CTX ctx; -} HMAC_PKEY_CTX; - typedef struct { uint8_t *key; size_t key_len; } HMAC_KEY; +typedef struct { + const EVP_MD *md; // MD for HMAC use. + HMAC_CTX ctx; + HMAC_KEY ktmp; +} HMAC_PKEY_CTX; + +// HMAC_KEY_set copies provided key into hmac_key. It frees any existing key +// on hmac_key. It returns 1 on success, and 0 otherwise. +int HMAC_KEY_set(HMAC_KEY* hmac_key, const uint8_t* key, const size_t key_len); +// HMAC_KEY_copy allocates and a new |HMAC_KEY| with identical contents (internal use). +int HMAC_KEY_copy(HMAC_KEY* dest, HMAC_KEY* src); // HMAC_KEY_new allocates and zeroizes a |HMAC_KEY| for internal use. HMAC_KEY *HMAC_KEY_new(void); +// HMAC_KEY_free frees resources of a |HMAC_KEY| (internal use). +void HMAC_KEY_free(HMAC_KEY* key); typedef struct { // key is the concatenation of the private seed and public key. It is stored diff --git a/crypto/fipsmodule/evp/p_hmac.c b/crypto/fipsmodule/evp/p_hmac.c index 6a6d34e222..fc24a2ad5b 100644 --- a/crypto/fipsmodule/evp/p_hmac.c +++ b/crypto/fipsmodule/evp/p_hmac.c @@ -81,6 +81,10 @@ static int hmac_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) { sctx = src->data; dctx = dst->data; dctx->md = sctx->md; + if(sctx->ktmp.key != NULL && !HMAC_KEY_copy(&sctx->ktmp, &dctx->ktmp)) { + OPENSSL_free(dctx); + return 0; + } if (!HMAC_CTX_copy_ex(&dctx->ctx, &sctx->ctx)) { OPENSSL_free(dctx); return 0; @@ -90,19 +94,78 @@ static int hmac_copy(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src) { static void hmac_cleanup(EVP_PKEY_CTX *ctx) { HMAC_PKEY_CTX *hctx = ctx->data; + OPENSSL_free(hctx->ktmp.key); OPENSSL_free(hctx); } -static int hmac_ctrl(EVP_PKEY_CTX *ctx, int type, int p1, void *p2) { +static int hmac_ctrl(EVP_PKEY_CTX *ctx, int cmd, int p1, void *p2) { + int result = -2; + HMAC_PKEY_CTX *hctx = ctx->data; - switch (type) { + switch (cmd) { + case EVP_PKEY_CTRL_SET_MAC_KEY: + if (p1 < INT16_MAX && p1 > 0 && p2 != NULL) { + // p1 is the key length + // p2 is the key + if (HMAC_KEY_set(&hctx->ktmp, p2, p1)) { + result = 1; + } else { + result = 0; + } + } + break; case EVP_PKEY_CTRL_MD: hctx->md = p2; + result = 1; break; default: OPENSSL_PUT_ERROR(EVP, EVP_R_COMMAND_NOT_SUPPORTED); + } + return result; +} + +static int hmac_ctrl_str(EVP_PKEY_CTX *ctx, const char *type, + const char *value) { + if (!value) { + return 0; + } + if (strcmp(type, "key") == 0) { + // What if the key contains a 0-byte? + const size_t keylen = OPENSSL_strnlen(value, INT16_MAX); + return EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_HMAC, EVP_PKEY_OP_KEYGEN, + EVP_PKEY_CTRL_SET_MAC_KEY, keylen, (void*)value); + } + if (strcmp(type, "hexkey") == 0) { + size_t hex_keylen = 0; + uint8_t *key = OPENSSL_hexstr2buf(value, &hex_keylen); + if (key == NULL) { return 0; + } + int result = + EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_HMAC, EVP_PKEY_OP_KEYGEN, + EVP_PKEY_CTRL_SET_MAC_KEY, hex_keylen, key); + OPENSSL_free(key); + return result; } + return -2; +} + +static int hmac_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey) { + GUARD_PTR(pkey); + HMAC_KEY *hmac = NULL; + HMAC_PKEY_CTX *hctx = ctx->data; + if(hctx == NULL) { + OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PARAMETERS); + return 0; + } + + if (!(hmac = HMAC_KEY_new()) || + !HMAC_KEY_copy(hmac, &hctx->ktmp) || + !EVP_PKEY_assign(pkey, EVP_PKEY_HMAC, hmac)) { + HMAC_KEY_free(hmac); + return 0; + } + return 1; } @@ -111,7 +174,7 @@ DEFINE_METHOD_FUNCTION(EVP_PKEY_METHOD, EVP_PKEY_hmac_pkey_meth) { out->init = hmac_init; out->copy = hmac_copy; out->cleanup = hmac_cleanup; - out->keygen = NULL; + out->keygen = hmac_keygen; out->sign_init = NULL; out->sign = NULL; out->sign_message = NULL; @@ -124,7 +187,7 @@ DEFINE_METHOD_FUNCTION(EVP_PKEY_METHOD, EVP_PKEY_hmac_pkey_meth) { out->derive = NULL; out->paramgen = NULL; out->ctrl = hmac_ctrl; - out->ctrl_str = NULL; + out->ctrl_str = hmac_ctrl_str; } int used_for_hmac(EVP_MD_CTX *ctx) { @@ -138,3 +201,37 @@ HMAC_KEY *HMAC_KEY_new(void) { } return key; } + +int HMAC_KEY_set(HMAC_KEY* hmac_key, const uint8_t* key, const size_t key_len) { + if(hmac_key == NULL ) { + return 0; + } + if (key == NULL || key_len == 0) { + hmac_key->key = NULL; + hmac_key->key_len = 0; + return 1; + } + + uint8_t* new_key = OPENSSL_memdup(key, key_len); + if(new_key == NULL) { + return 0; + } + OPENSSL_free(hmac_key->key); + hmac_key->key = new_key; + hmac_key->key_len = key_len; + return 1; +} + +void HMAC_KEY_free(HMAC_KEY* key) { + if (key != NULL) { + OPENSSL_free(key->key); + } + OPENSSL_free(key); +} + +int HMAC_KEY_copy(HMAC_KEY* dest, HMAC_KEY* src) { + GUARD_PTR(dest); + GUARD_PTR(src); + + return HMAC_KEY_set(dest, src->key, src->key_len); +} diff --git a/crypto/hmac_extra/hmac_test.cc b/crypto/hmac_extra/hmac_test.cc index 074710bf03..f3a85ab7d2 100644 --- a/crypto/hmac_extra/hmac_test.cc +++ b/crypto/hmac_extra/hmac_test.cc @@ -115,105 +115,117 @@ static size_t GetPrecomputedKeySize(const std::string &name) { static void RunHMACTestEVP(const std::vector &key, const std::vector &msg, const std::vector &tag, const EVP_MD *md) { - bssl::UniquePtr pkey( + + bssl::UniquePtr pkey_mac( EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, nullptr, key.data(), key.size())); - ASSERT_TRUE(pkey); + ASSERT_TRUE(pkey_mac); + + bssl::UniquePtr ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_HMAC, NULL)); + ASSERT_TRUE(EVP_PKEY_keygen_init(ctx.get())); + auto hexkey = EncodeHex(key); + ASSERT_TRUE(EVP_PKEY_CTX_ctrl_str(ctx.get(), "hexkey", hexkey.data())); + EVP_PKEY* my_pkey = NULL; + ASSERT_TRUE(EVP_PKEY_keygen(ctx.get(), &my_pkey)); + bssl::UniquePtr pkey_gen(my_pkey); + ASSERT_TRUE(pkey_gen); + + for (const auto pkey : {pkey_mac.get(), pkey_gen.get()}) { + bssl::ScopedEVP_MD_CTX copy, mctx; + size_t len; + std::vector actual; + ASSERT_TRUE(EVP_DigestSignInit(mctx.get(), nullptr, md, nullptr, pkey)); + // Make a copy we can test against later. + ASSERT_TRUE(EVP_MD_CTX_copy_ex(copy.get(), mctx.get())); + ASSERT_TRUE(EVP_DigestSignUpdate(mctx.get(), msg.data(), msg.size())); + ASSERT_TRUE(EVP_DigestSignFinal(mctx.get(), nullptr, &len)); + actual.resize(len); + ASSERT_TRUE(EVP_DigestSignFinal(mctx.get(), actual.data(), &len)); + actual.resize(len); + // Wycheproof tests truncate the tags down to |tagSize|. Expected outputs in + // hmac_tests.txt have the length of the entire tag. + EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); + + // Repeat the test with |copy|, to check |EVP_MD_CTX_copy_ex| duplicated + // everything. + len = 0; + actual.clear(); + ASSERT_TRUE(EVP_DigestSignUpdate(copy.get(), msg.data(), msg.size())); + ASSERT_TRUE(EVP_DigestSignFinal(copy.get(), nullptr, &len)); + actual.resize(len); + ASSERT_TRUE(EVP_DigestSignFinal(copy.get(), actual.data(), &len)); + actual.resize(len); + EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); - bssl::ScopedEVP_MD_CTX copy, mctx; - size_t len; - std::vector actual; - ASSERT_TRUE(EVP_DigestSignInit(mctx.get(), nullptr, md, nullptr, pkey.get())); - // Make a copy we can test against later. - ASSERT_TRUE(EVP_MD_CTX_copy_ex(copy.get(), mctx.get())); - ASSERT_TRUE(EVP_DigestSignUpdate(mctx.get(), msg.data(), msg.size())); - ASSERT_TRUE(EVP_DigestSignFinal(mctx.get(), nullptr, &len)); - actual.resize(len); - ASSERT_TRUE(EVP_DigestSignFinal(mctx.get(), actual.data(), &len)); - actual.resize(len); - // Wycheproof tests truncate the tags down to |tagSize|. Expected outputs in - // hmac_tests.txt have the length of the entire tag. - EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); - - // Repeat the test with |copy|, to check |EVP_MD_CTX_copy_ex| duplicated - // everything. - len = 0; - actual.clear(); - ASSERT_TRUE(EVP_DigestSignUpdate(copy.get(), msg.data(), msg.size())); - ASSERT_TRUE(EVP_DigestSignFinal(copy.get(), nullptr, &len)); - actual.resize(len); - ASSERT_TRUE(EVP_DigestSignFinal(copy.get(), actual.data(), &len)); - actual.resize(len); - EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); - - // Test using the one-shot API. - mctx.Reset(); - copy.Reset(); - len = 0; - actual.clear(); - ASSERT_TRUE(EVP_DigestSignInit(mctx.get(), nullptr, md, nullptr, pkey.get())); - ASSERT_TRUE(EVP_MD_CTX_copy_ex(copy.get(), mctx.get())); - ASSERT_TRUE( - EVP_DigestSign(mctx.get(), nullptr, &len, msg.data(), msg.size())); - actual.resize(len); - ASSERT_TRUE( - EVP_DigestSign(mctx.get(), actual.data(), &len, msg.data(), msg.size())); - actual.resize(len); - EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); - - // Repeat the test with |copy|, to check |EVP_MD_CTX_copy_ex| duplicated - // everything. - len = 0; - actual.clear(); - ASSERT_TRUE(EVP_DigestSignUpdate(copy.get(), msg.data(), msg.size())); - ASSERT_TRUE(EVP_DigestSignFinal(copy.get(), nullptr, &len)); - actual.resize(len); - ASSERT_TRUE(EVP_DigestSignFinal(copy.get(), actual.data(), &len)); - actual.resize(len); - EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); - - // Test feeding the input in byte by byte. - mctx.Reset(); - ASSERT_TRUE(EVP_DigestSignInit(mctx.get(), nullptr, md, nullptr, pkey.get())); - for (const unsigned char &i : msg) { - ASSERT_TRUE(EVP_DigestSignUpdate(mctx.get(), &i, 1)); - } - ASSERT_TRUE(EVP_DigestSignFinal(mctx.get(), actual.data(), &len)); - EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); - - - // Test |EVP_PKEY| key creation with |EVP_PKEY_new_raw_private_key|. - bssl::UniquePtr raw_pkey(EVP_PKEY_new_raw_private_key( - EVP_PKEY_HMAC, nullptr, key.data(), key.size())); - mctx.Reset(); - len = 0; - actual.clear(); - EXPECT_TRUE( - EVP_DigestSignInit(mctx.get(), nullptr, md, nullptr, raw_pkey.get())); - EXPECT_TRUE(EVP_DigestSignUpdate(mctx.get(), msg.data(), msg.size())); - EXPECT_TRUE(EVP_DigestSignFinal(mctx.get(), nullptr, &len)); - actual.resize(len); - EXPECT_TRUE(EVP_DigestSignFinal(mctx.get(), actual.data(), &len)); - actual.resize(len); - EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); - - // Test retrieving key passed into |raw_pkey| with - // |EVP_PKEY_get_raw_private_key|. - std::vector retrieved_key; - size_t retrieved_key_len; - EXPECT_TRUE(EVP_PKEY_get_raw_private_key(raw_pkey.get(), nullptr, - &retrieved_key_len)); - EXPECT_EQ(key.size(), retrieved_key_len); - retrieved_key.resize(retrieved_key_len); - EXPECT_TRUE(EVP_PKEY_get_raw_private_key(raw_pkey.get(), retrieved_key.data(), - &retrieved_key_len)); - retrieved_key.resize(retrieved_key_len); - EXPECT_EQ(Bytes(retrieved_key), Bytes(key)); - - // Test retrieving key with a buffer length that's too small. This should fail - if (!key.empty()) { - size_t short_key_len = retrieved_key_len - 1; - EXPECT_FALSE(EVP_PKEY_get_raw_private_key( - raw_pkey.get(), retrieved_key.data(), &short_key_len)); + // Test using the one-shot API. + mctx.Reset(); + copy.Reset(); + len = 0; + actual.clear(); + ASSERT_TRUE(EVP_DigestSignInit(mctx.get(), nullptr, md, nullptr, pkey)); + ASSERT_TRUE(EVP_MD_CTX_copy_ex(copy.get(), mctx.get())); + ASSERT_TRUE( + EVP_DigestSign(mctx.get(), nullptr, &len, msg.data(), msg.size())); + actual.resize(len); + ASSERT_TRUE( + EVP_DigestSign(mctx.get(), actual.data(), &len, msg.data(), msg.size())); + actual.resize(len); + EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); + + // Repeat the test with |copy|, to check |EVP_MD_CTX_copy_ex| duplicated + // everything. + len = 0; + actual.clear(); + ASSERT_TRUE(EVP_DigestSignUpdate(copy.get(), msg.data(), msg.size())); + ASSERT_TRUE(EVP_DigestSignFinal(copy.get(), nullptr, &len)); + actual.resize(len); + ASSERT_TRUE(EVP_DigestSignFinal(copy.get(), actual.data(), &len)); + actual.resize(len); + EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); + + // Test feeding the input in byte by byte. + mctx.Reset(); + ASSERT_TRUE(EVP_DigestSignInit(mctx.get(), nullptr, md, nullptr, pkey)); + for (const unsigned char &i : msg) { + ASSERT_TRUE(EVP_DigestSignUpdate(mctx.get(), &i, 1)); + } + ASSERT_TRUE(EVP_DigestSignFinal(mctx.get(), actual.data(), &len)); + EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); + + + // Test |EVP_PKEY| key creation with |EVP_PKEY_new_raw_private_key|. + bssl::UniquePtr raw_pkey(EVP_PKEY_new_raw_private_key( + EVP_PKEY_HMAC, nullptr, key.data(), key.size())); + mctx.Reset(); + len = 0; + actual.clear(); + EXPECT_TRUE( + EVP_DigestSignInit(mctx.get(), nullptr, md, nullptr, raw_pkey.get())); + EXPECT_TRUE(EVP_DigestSignUpdate(mctx.get(), msg.data(), msg.size())); + EXPECT_TRUE(EVP_DigestSignFinal(mctx.get(), nullptr, &len)); + actual.resize(len); + EXPECT_TRUE(EVP_DigestSignFinal(mctx.get(), actual.data(), &len)); + actual.resize(len); + EXPECT_EQ(Bytes(tag), Bytes(actual.data(), tag.size())); + + // Test retrieving key passed into |raw_pkey| with + // |EVP_PKEY_get_raw_private_key|. + std::vector retrieved_key; + size_t retrieved_key_len; + EXPECT_TRUE(EVP_PKEY_get_raw_private_key(raw_pkey.get(), nullptr, + &retrieved_key_len)); + EXPECT_EQ(key.size(), retrieved_key_len); + retrieved_key.resize(retrieved_key_len); + EXPECT_TRUE(EVP_PKEY_get_raw_private_key(raw_pkey.get(), retrieved_key.data(), + &retrieved_key_len)); + retrieved_key.resize(retrieved_key_len); + EXPECT_EQ(Bytes(retrieved_key), Bytes(key)); + + // Test retrieving key with a buffer length that's too small. This should fail + if (!key.empty()) { + size_t short_key_len = retrieved_key_len - 1; + EXPECT_FALSE(EVP_PKEY_get_raw_private_key( + raw_pkey.get(), retrieved_key.data(), &short_key_len)); + } } }