Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Design for support of HMAC precomputed keys #1574

Merged
merged 31 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fa25f37
Initial design for export of Merkle-Damgard hash state
May 6, 2024
7058ed3
Initial design for support of HMAC precomputed keys
May 6, 2024
3a3a9ad
Addressing comments from review of PR #1574
Jun 10, 2024
f4b67b1
Clarifying definition of EVP_MAX_MD_CHAINING_LENGTH following review
Jun 10, 2024
7148248
Merge branch 'main' into hmac-precompute
Jun 10, 2024
8b5dbea
Remove redundant function declaration in HMAC/SHA256 trampoline
Jun 10, 2024
6734e02
Function comments improvements from review of PR #1574
Jun 13, 2024
7c68e2f
Update function comment in crypto/fipsmodule/hmac/internal.h
fabrice102 Jun 13, 2024
1f6b510
Function comments improvements from review of PR #1574
Jun 14, 2024
a11cb33
Improve error management and check out_len - from review of PR #1574
Jun 14, 2024
41fd25d
Apply suggestions from code review
fabrice102 Jun 14, 2024
6f14823
Apply suggestions from code review
Jun 14, 2024
73debcf
Function comments improvements from review of PR #1574
fabrice102 Jun 14, 2024
580159c
Extend PR #1574 to the other hash functions
Jun 19, 2024
35b4f90
Fix warnings when assert disabled in release mode
Jun 19, 2024
31a27df
Improving comment
Jun 19, 2024
e867e8f
Fix bug in HMAC_with_precompute
Jun 20, 2024
57aec5b
Add service indicator tests for HMAC with precomputed keys
Jun 20, 2024
e16bd40
Fix SHA-512 Init_with_stae/get_state + comment improvements
Jul 5, 2024
866fd7f
Unit test for HMAC_with_precompute service indicator
Jul 5, 2024
7a99180
Unit test for hash Init_with_state/get_state after hashing > 2^32 bits
Jul 8, 2024
7227a2c
Fixing type of some constants
Jul 8, 2024
6df5d2e
Adding unit tests to increase coverage
Jul 8, 2024
43510e5
Style and comment improvements from review of PR aws/aws-lc#1574
Jul 11, 2024
220b8d3
Merge branch 'main' into hmac-precompute
Jul 11, 2024
4ec5b37
Add hmac.errordata
Jul 17, 2024
34e5089
python3 ./util/generate_build_files.py
skmcgrail Jul 17, 2024
f096cfb
Merge branch 'main' into hmac-precompute
nebeid Jul 18, 2024
cd102d6
Fix Windows ARM64 compilation + comment improvements
Jul 18, 2024
1fd75d9
Merge branch 'main' into hmac-precompute
nebeid Jul 19, 2024
d77ee73
Merge branch 'main' into hmac-precompute
nebeid Jul 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions crypto/digest_extra/digest_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -476,3 +476,46 @@ TEST(DigestTest, TransformBlocks) {

EXPECT_TRUE(0 == OPENSSL_memcmp(ctx1.h, ctx2.h, sizeof(ctx1.h)));
}

// FIXME: Need to implement this for all hash functions used by HMAC
TEST(DigestTest, InitAndGetStateSHA256) {
const size_t nb_blocks = 10;
const size_t block_size = SHA256_CBLOCK;
uint8_t data[block_size * nb_blocks];
for (size_t i = 0; i < sizeof(data); i++) {
data[i] = i*3;
}

// SHA-256

// Compute the hash of the data for the baseline
SHA256_CTX ctx1;
EXPECT_TRUE(SHA256_Init(&ctx1));
EXPECT_TRUE(SHA256_Update(&ctx1, data, sizeof(data)));
uint8_t hash1[SHA256_DIGEST_LENGTH];
EXPECT_TRUE(SHA256_Final(hash1, &ctx1));

// Compute it by stopping in the middle, getting the state, and restoring it
SHA256_CTX ctx2;
EXPECT_TRUE(SHA256_Init(&ctx2));
EXPECT_TRUE(SHA256_Update(&ctx2, data, 1));
uint8_t state_h[SHA256_DIGEST_LENGTH];
uint64_t state_n;
// we should not be able to export the state before a full block
EXPECT_FALSE(SHA256_get_state(&ctx2, state_h, &state_n));
// finish 2 blocks
EXPECT_TRUE(SHA256_Update(&ctx2, data+1, 2*block_size-1));
// now we should be able to export the state
EXPECT_TRUE(SHA256_get_state(&ctx2, state_h, &state_n));
// check that state_n corresponds to 2 blocks
EXPECT_EQ(2*block_size*8, state_n);

// and we continue on a fresh new context
SHA256_CTX ctx3;
EXPECT_TRUE(SHA256_Init_from_state(&ctx3, state_h, state_n));
EXPECT_TRUE(SHA256_Update(&ctx2, data+2*block_size, (nb_blocks-2)*block_size));
uint8_t hash2[SHA256_DIGEST_LENGTH];
EXPECT_TRUE(SHA256_Final(hash2, &ctx2));

EXPECT_EQ(Bytes(hash1), Bytes(hash2));
}
274 changes: 236 additions & 38 deletions crypto/fipsmodule/hmac/hmac.c
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In many external API calls here, we're directly accessing ctx->state or other members of ctx without checking it's not null. I think we should handle this as was done in #1398. This can be its own PR.

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions crypto/fipsmodule/hmac/internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#ifndef AWSLC_HEADER_HMAC_INTERNAL_H
#define AWSLC_HEADER_HMAC_INTERNAL_H

#include <openssl/base.h>

#if defined(__cplusplus)
extern "C" {
#endif

// HMAC_with_precompute is only used in FIPS ACVP harness, in order to test
// the computation of HMAC using precomputed keys (internally). It should not
// be used for any other purposes as it outputs the same results as HMAC and is
// slower than HMAC.
fabrice102 marked this conversation as resolved.
Show resolved Hide resolved
OPENSSL_EXPORT uint8_t *HMAC_with_precompute(const EVP_MD *evp_md,
fabrice102 marked this conversation as resolved.
Show resolved Hide resolved
const void *key, size_t key_len,
const uint8_t *data,
size_t data_len, uint8_t *out,
unsigned int *out_len);

#if defined(__cplusplus)
} // extern C
#endif

#endif // AWSLC_HEADER_HMAC_INTERNAL_H
31 changes: 31 additions & 0 deletions crypto/fipsmodule/sha/internal.h
fabrice102 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
extern "C" {
#endif

// Internal SHA2 constants

// SHA224_CHAINING_LENGTH is the chaining length in bytes of SHA-224
// It corresponds to the length in bytes of the h part of the state
#define SHA224_CHAINING_LENGTH 32

// SHA256_CHAINING_LENGTH is the chaining length in bytes of SHA-256
// It corresponds to the length in bytes of the h part of the state
#define SHA256_CHAINING_LENGTH 32


// SHA3 constants, from NIST FIPS202.
// https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
#define SHA3_ROWS 5
Expand Down Expand Up @@ -92,6 +103,26 @@ void sha512_block_data_order(uint64_t *state, const uint8_t *in,
#define KECCAK1600_ASM
#endif

// SHA256_Init_from_state is a low-level function that initializes |sha| with a
// custom state. |h| is the hash state in big endian. |n| is the number of bits
// processed at this point. It must be a multiple of |SHA256_CBLOCK*8|.
// It returns one on success and zero on programmer error.
// External users should never use directly this function.
fabrice102 marked this conversation as resolved.
Show resolved Hide resolved
OPENSSL_EXPORT int SHA256_Init_from_state(
SHA256_CTX *sha, const uint8_t h[SHA256_CHAINING_LENGTH], uint64_t n);
nebeid marked this conversation as resolved.
Show resolved Hide resolved

// SHA256_get_state is a low-level function that exports the hash state in big
// endian into |out_n| and the number of bits processed at this point in
fabrice102 marked this conversation as resolved.
Show resolved Hide resolved
// |out_n|. SHA256_Final must not have been called before (otherwise results
// are not guaranteed). Furthermore, the number of bytes processed by
// SHA256_Update must be a multiple of the block length |SHA256_CBLOCK|
// (otherwise it fails). It returns one on success and zero on programmer
// error.
// External users should never use directly this function.
fabrice102 marked this conversation as resolved.
Show resolved Hide resolved
OPENSSL_EXPORT int SHA256_get_state(SHA256_CTX *ctx,
uint8_t out_h[SHA256_CHAINING_LENGTH],
uint64_t *out_n);

// SHA3_224 writes the digest of |len| bytes from |data| to |out| and returns |out|.
// There must be at least |SHA3_224_DIGEST_LENGTH| bytes of space in |out|.
// On failure |SHA3_224| returns NULL.
Expand Down
64 changes: 63 additions & 1 deletion crypto/fipsmodule/sha/sha256.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,41 @@ int SHA256_Init(SHA256_CTX *sha) {
return 1;
}

OPENSSL_STATIC_ASSERT(SHA256_CHAINING_LENGTH==SHA224_CHAINING_LENGTH,
sha256_and_sha224_have_same_chaining_length)

// sha256_init_from_state_impl is the implementation of
// SHA256_Init_from_state and SHA224_Init_from_state
// Note that the state h is always SHA256_CHAINING_LENGTH-byte long
static int sha256_init_from_state_impl(SHA256_CTX *sha, int md_len,
const uint8_t h[SHA256_CHAINING_LENGTH],
uint64_t n) {
if(n % ((uint64_t) SHA256_CBLOCK * 8) != 0) {
// n is not a multiple of the block size in bits, so it fails
return 0;
}

OPENSSL_memset(sha, 0, sizeof(SHA256_CTX));
sha->md_len = md_len;

const size_t out_words = SHA256_CHAINING_LENGTH / 4;
for (size_t i = 0; i < out_words; i++) {
sha->h[i] = CRYPTO_load_u32_be(h);
h += 4;
}

sha->Nh = n >> 32;
sha->Nl = n & 0xffffffff;

return 1;
}

int SHA256_Init_from_state(SHA256_CTX *sha,
const uint8_t h[SHA256_CHAINING_LENGTH],
uint64_t n) {
return sha256_init_from_state_impl(sha, SHA256_DIGEST_LENGTH, h, n);
}

uint8_t *SHA224(const uint8_t *data, size_t len,
uint8_t out[SHA224_DIGEST_LENGTH]) {
// We have to verify that all the SHA services actually succeed before
Expand Down Expand Up @@ -163,14 +198,41 @@ static int sha256_final_impl(uint8_t *out, size_t md_len, SHA256_CTX *c) {
return 1;
}

int SHA256_Final(uint8_t out[SHA256_DIGEST_LENGTH], SHA256_CTX *c) {
int SHA256_Final(uint8_t out[SHA256_CHAINING_LENGTH], SHA256_CTX *c) {
fabrice102 marked this conversation as resolved.
Show resolved Hide resolved
return sha256_final_impl(out, SHA256_DIGEST_LENGTH, c);
}

int SHA224_Final(uint8_t out[SHA224_DIGEST_LENGTH], SHA256_CTX *ctx) {
return sha256_final_impl(out, SHA224_DIGEST_LENGTH, ctx);
}

// sha256_get_state_impl is the implementation of
// SHA256_get_state and SHA224_get_state
// Note that the state out_h is always SHA256_CHAINING_LENGTH-byte long
static int sha256_get_state_impl(SHA256_CTX *ctx,
uint8_t out_h[SHA256_CHAINING_LENGTH],
uint64_t *out_n) {
if (ctx->Nl % ((uint64_t)SHA256_CBLOCK * 8) != 0) {
// ctx->Nl is not a multiple of the block size in bits, so it fails
return 0;
}

const size_t out_words = SHA256_CHAINING_LENGTH / 4;
for (size_t i = 0; i < out_words; i++) {
CRYPTO_store_u32_be(out_h, ctx->h[i]);
out_h += 4;
}

*out_n = (((uint64_t)ctx->Nh) << 32) + ctx->Nl;

return 1;
}

int SHA256_get_state(SHA256_CTX *ctx, uint8_t out_h[SHA256_CHAINING_LENGTH],
uint64_t *out_n) {
return sha256_get_state_impl(ctx, out_h, out_n);
}

#ifndef SHA256_ASM
static const uint32_t K256[64] = {
0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL,
Expand Down
106 changes: 104 additions & 2 deletions crypto/hmac_extra/hmac_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
#include <openssl/evp.h>
#include <openssl/hmac.h>

#include "../../../crypto/fipsmodule/hmac/internal.h"
#include "../test/file_test.h"
#include "../test/test_util.h"
#include "../test/wycheproof_util.h"
Expand Down Expand Up @@ -210,14 +211,21 @@ TEST(HMACTest, TestVectors) {
ASSERT_EQ(EVP_MD_size(digest), output.size());

// Test using the one-shot API.
unsigned expected_mac_len = EVP_MD_size(digest);
const unsigned expected_mac_len = EVP_MD_size(digest);
std::unique_ptr<uint8_t[]> mac(new uint8_t[expected_mac_len]);
unsigned mac_len;
ASSERT_TRUE(HMAC(digest, key.data(), key.size(), input.data(), input.size(),
mac.get(), &mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Test using the one-shot API with precompute
ASSERT_TRUE(HMAC_with_precompute(digest, key.data(), key.size(),
input.data(), input.size(), mac.get(),
&mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Test using HMAC_CTX.
bssl::ScopedHMAC_CTX ctx;
ASSERT_TRUE(
Expand All @@ -227,20 +235,61 @@ TEST(HMACTest, TestVectors) {
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Test the precomputed key size is at most HMAC_MAX_PRECOMPUTED_KEY_SIZE
const size_t precomputed_key_len = HMAC_precomputed_key_size(ctx.get());
ASSERT_LE(precomputed_key_len, (size_t) HMAC_MAX_PRECOMPUTED_KEY_SIZE);
uint8_t precomputed_key[HMAC_MAX_PRECOMPUTED_KEY_SIZE];

// Test that the precomputed key cannot be exported without calling
// HMAC_set_precomputed_key_export
size_t precomputed_key_len_out;
ASSERT_TRUE(HMAC_Init_ex(ctx.get(), key.data(), key.size(), digest, nullptr));
ASSERT_FALSE(HMAC_get_precomputed_key(ctx.get(), precomputed_key, &precomputed_key_len_out));

// Test that the precomputed key cannot be exported if ctx not initialized
// and the precomputed_key_export flag cannot be set
bssl::ScopedHMAC_CTX ctx2;
ASSERT_FALSE(HMAC_set_precomputed_key_export(ctx2.get()));
ASSERT_FALSE(HMAC_get_precomputed_key(ctx2.get(), precomputed_key, &precomputed_key_len_out));

// Export the precomputed key for later use
ASSERT_TRUE(HMAC_set_precomputed_key_export(ctx.get()));
ASSERT_TRUE(HMAC_get_precomputed_key(ctx.get(), precomputed_key, &precomputed_key_len_out));
ASSERT_EQ(precomputed_key_len, precomputed_key_len_out);

// Test that at this point the context cannot be used with HMAC_Update
// nor HMAC_Final
ASSERT_FALSE(HMAC_Update(ctx.get(), input.data(), input.size()));
ASSERT_FALSE(HMAC_Final(ctx.get(), mac.get(), &mac_len));

// Test that an HMAC_CTX may be reset with the same key.
ASSERT_TRUE(HMAC_Init_ex(ctx.get(), nullptr, 0, digest, nullptr));
ASSERT_TRUE(HMAC_Update(ctx.get(), input.data(), input.size()));
ASSERT_TRUE(HMAC_Final(ctx.get(), mac.get(), &mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Test that an HMAC_CTX may be reset with the same key and an null md
// Same test but with HMAC_Init_from_precomputed_key
ASSERT_TRUE(HMAC_Init_from_precomputed_key(ctx.get(), nullptr, 0, digest));
ASSERT_TRUE(HMAC_Update(ctx.get(), input.data(), input.size()));
ASSERT_TRUE(HMAC_Final(ctx.get(), mac.get(), &mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Test that an HMAC_CTX may be reset with the same key and a null md
ASSERT_TRUE(HMAC_Init_ex(ctx.get(), nullptr, 0, nullptr, nullptr));
ASSERT_TRUE(HMAC_Update(ctx.get(), input.data(), input.size()));
ASSERT_TRUE(HMAC_Final(ctx.get(), mac.get(), &mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Same test but using the Init_with_precompute instead
fabrice102 marked this conversation as resolved.
Show resolved Hide resolved
ASSERT_TRUE(HMAC_Init_from_precomputed_key(ctx.get(), nullptr, 0, nullptr));
ASSERT_TRUE(HMAC_Update(ctx.get(), input.data(), input.size()));
ASSERT_TRUE(HMAC_Final(ctx.get(), mac.get(), &mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Some callers will call init multiple times and we need to ensure that doesn't break anything
ASSERT_TRUE(HMAC_Init_ex(ctx.get(), key.data(), key.size(), digest, nullptr));
ASSERT_TRUE(HMAC_Init_ex(ctx.get(), nullptr, 0, nullptr, nullptr));
Expand All @@ -249,6 +298,59 @@ TEST(HMACTest, TestVectors) {
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Same test but using a mix of Init_ex and Init_with_precompute
ASSERT_TRUE(HMAC_Init_ex(ctx.get(), key.data(), key.size(), digest, nullptr));
ASSERT_TRUE(HMAC_Init_from_precomputed_key(ctx.get(), nullptr, 0, nullptr));
ASSERT_TRUE(HMAC_Init_ex(ctx.get(), nullptr, 0, nullptr, nullptr));
ASSERT_TRUE(HMAC_Init_from_precomputed_key(ctx.get(), nullptr, 0, nullptr));
ASSERT_TRUE(HMAC_Update(ctx.get(), input.data(), input.size()));
ASSERT_TRUE(HMAC_Final(ctx.get(), mac.get(), &mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Test that the HMAC_CTX can be reset using the precomputed key
ASSERT_TRUE(HMAC_Init_from_precomputed_key(ctx.get(), precomputed_key, precomputed_key_len, nullptr));
ASSERT_TRUE(HMAC_Update(ctx.get(), input.data(), input.size()));
ASSERT_TRUE(HMAC_Final(ctx.get(), mac.get(), &mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Same test but starting from an empty context
ctx.Reset();
ASSERT_TRUE(HMAC_Init_from_precomputed_key(ctx.get(), precomputed_key, precomputed_key_len, digest));
ASSERT_TRUE(HMAC_Update(ctx.get(), input.data(), input.size()));
ASSERT_TRUE(HMAC_Final(ctx.get(), mac.get(), &mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Some callers will call init_from_precomputed_key multiple times and we need to ensure that doesn't break anything
ASSERT_TRUE(HMAC_Init_from_precomputed_key(ctx.get(), precomputed_key, precomputed_key_len, nullptr));
ASSERT_TRUE(HMAC_Init_from_precomputed_key(ctx.get(), nullptr, 0, nullptr));
ASSERT_TRUE(HMAC_Update(ctx.get(), input.data(), input.size()));
ASSERT_TRUE(HMAC_Final(ctx.get(), mac.get(), &mac_len));
EXPECT_EQ(Bytes(output), Bytes(mac.get(), mac_len));
OPENSSL_memset(mac.get(), 0, expected_mac_len); // Clear the prior correct answer

// Test that we get the same precompute_key the second time
ASSERT_TRUE(HMAC_Init_ex(ctx.get(), key.data(), key.size(), digest, nullptr));
uint8_t precomputed_key2[HMAC_MAX_PRECOMPUTED_KEY_SIZE];
size_t precomputed_key_len_out2;
ASSERT_TRUE(HMAC_set_precomputed_key_export(ctx.get()));
ASSERT_TRUE(HMAC_set_precomputed_key_export(ctx.get())); // testing we can set it twice
ASSERT_TRUE(HMAC_get_precomputed_key(ctx.get(), precomputed_key2, &precomputed_key_len_out2));
ASSERT_EQ(precomputed_key_len, precomputed_key_len_out2);
ASSERT_EQ(Bytes(precomputed_key, precomputed_key_len), Bytes(precomputed_key2, precomputed_key_len));
OPENSSL_memset(precomputed_key2, 0, HMAC_MAX_PRECOMPUTED_KEY_SIZE); // Clear the prior correct answer
precomputed_key_len_out2 = 0; // Clear the prior correct answer

// Same but initializing the ctx using the precompute key in the first place
ctx.Reset();
ASSERT_TRUE(HMAC_Init_from_precomputed_key(ctx.get(), precomputed_key, precomputed_key_len, digest));
ASSERT_TRUE(HMAC_set_precomputed_key_export(ctx.get()));
ASSERT_TRUE(HMAC_get_precomputed_key(ctx.get(), precomputed_key2, &precomputed_key_len_out2));
ASSERT_EQ(precomputed_key_len, precomputed_key_len_out2);
ASSERT_EQ(Bytes(precomputed_key, precomputed_key_len), Bytes(precomputed_key2, precomputed_key_len));

// Test feeding the input in byte by byte.
ASSERT_TRUE(HMAC_Init_ex(ctx.get(), nullptr, 0, nullptr, nullptr));
for (size_t i = 0; i < input.size(); i++) {
Expand Down
Loading
Loading