Skip to content

Commit

Permalink
NIST SP 800-108r1-upd1: KDF Counter Implementation (#1644)
Browse files Browse the repository at this point in the history
### Description of changes: 
This pull request implements the KDF in Counter Mode
defined in [Section 4 of
NIST.SP.800-108r1-upd1](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1-upd1.pdf#%5B%7B%22num%22%3A77%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C70%2C300%2C0%5D)

The abbreviation `KBKDF` stands for Key-based key derivation function.
`ctr` is abbreviation for counter.

### Call-outs:
* We will need to add appropriate service indicator logic ahead of our
next FIPS certification round.
* We will need to wire-up ACVP tests separately.

### Testing:
Test vectors are provided
[here](https://github.com/aws/aws-lc/pull/1644/files#diff-d0cd06e99fdc733df70e2aa730e973c5ed0ab73ff851c33b1f2e5f44beb8a82d)

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license and the ISC license.
  • Loading branch information
skmcgrail authored Jul 19, 2024
1 parent b525f0c commit 4ac1742
Show file tree
Hide file tree
Showing 9 changed files with 1,749 additions and 740 deletions.
1 change: 1 addition & 0 deletions crypto/fipsmodule/bcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
#include "evp/p_rsa.c"
#include "hkdf/hkdf.c"
#include "hmac/hmac.c"
#include "kdf/kbkdf.c"
#include "kdf/sskdf.c"
#include "md4/md4.c"
#include "md5/md5.c"
Expand Down
2 changes: 2 additions & 0 deletions crypto/fipsmodule/kdf/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#define SSKDF_MAX_INPUT_LEN (1 << 30)
#define SSKDF_COUNTER_SIZE 4

#define KBKDF_COUNTER_SIZE 4

typedef struct {
void *data;
} sskdf_variant_ctx;
Expand Down
100 changes: 100 additions & 0 deletions crypto/fipsmodule/kdf/kbkdf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/kdf.h>
#include "internal.h"

int KBKDF_ctr_hmac(uint8_t *out_key, size_t out_len, const EVP_MD *digest,
const uint8_t *secret, size_t secret_len,
const uint8_t *info, size_t info_len) {
int ret = 0;

HMAC_CTX *hmac_ctx = NULL;

if (!out_key || out_len == 0 || !secret || secret_len == 0) {
goto err;
}

hmac_ctx = HMAC_CTX_new();
if (!hmac_ctx) {
goto err;
}

if (!HMAC_Init_ex(hmac_ctx, secret, secret_len, digest, NULL)) {
goto err;
}

// Determine the length of the output in bytes of a single invocation of the
// HMAC function.
size_t h_output_bytes = HMAC_size(hmac_ctx);
if (h_output_bytes == 0 || h_output_bytes > EVP_MAX_MD_SIZE) {
goto err;
}

if (out_len > SIZE_MAX - h_output_bytes) {
goto err;
}

// NIST.SP.800-108r1-upd1: Step 1:
// Determine how many output chunks are required to produce the requested
// output length |out_len|. This determines how many times the variant compute
// function will be called to output key material.
uint64_t n = ((uint64_t)out_len + (uint64_t)h_output_bytes - 1) /
(uint64_t)h_output_bytes;

// NIST.SP.800-108r1-upd1: Step 2:
// Verify that the number of output chunks does not exceed an unsigned 32-bit
// integer.
if (n > UINT32_MAX) {
goto err;
}

size_t done = 0;

for (uint32_t i = 0; i < n; i++) {
uint8_t out_key_i[EVP_MAX_MD_SIZE];
uint8_t counter[KBKDF_COUNTER_SIZE];
size_t todo;

// Increment the counter
CRYPTO_store_u32_be(&counter[0], i + 1);

uint32_t written;

// NIST.SP.800-108r1-upd1: Step 4a:
// K(i) := PRF(K_IN, [i] || FixedInfo)
// Note |hmac_ctx| has already been configured with the secret key
if (!HMAC_Init_ex(hmac_ctx, NULL, 0, NULL, NULL) ||
!HMAC_Update(hmac_ctx, &counter[0], KBKDF_COUNTER_SIZE) ||
!HMAC_Update(hmac_ctx, info, info_len) ||
!HMAC_Final(hmac_ctx, out_key_i, &written) ||
written != h_output_bytes) {
OPENSSL_cleanse(&out_key_i[0], EVP_MAX_MD_SIZE);
goto err;
}

// NIST.SP.800-108r1-upd1: Step 4b, Step 5
// result := result || K(i)
todo = h_output_bytes;
if (todo > out_len - done) {
todo = out_len - done;
}
OPENSSL_memcpy(out_key + done, out_key_i, todo);
done += todo;

// When we are finished clear the temporary buffer to cleanse key material
// from stack.
if (done == out_len) {
OPENSSL_cleanse(&out_key_i[0], EVP_MAX_MD_SIZE);
}
}

ret = 1;

err:
if (ret <= 0 && out_key && out_len > 0) {
OPENSSL_cleanse(out_key, out_len);
}
HMAC_CTX_free(hmac_ctx);
return ret;
}
64 changes: 64 additions & 0 deletions crypto/fipsmodule/kdf/kdf_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,67 @@ TEST(SSKDFTest, HMACNegativeTests) {
ASSERT_FALSE(SSKDF_hmac(out.data(), out.size(), EVP_sha256(), &secret[0], 0,
NULL, 0, NULL, 0));
}

TEST(KBKDFCounterTest, TestVectors) {
FileTestGTest(
"crypto/fipsmodule/kdf/test/kbkdf_counter.txt", [](FileTest *t) {
const EVP_MD *md;
std::string hash;
ASSERT_TRUE(t->GetAttribute(&hash, "HASH"));
if (hash == "SHA1") {
md = EVP_sha1();
} else if (hash == "SHA-224") {
md = EVP_sha224();
} else if (hash == "SHA-256") {
md = EVP_sha256();
} else if (hash == "SHA-384") {
md = EVP_sha384();
} else if (hash == "SHA-512") {
md = EVP_sha512();
} else {
FAIL() << "Unknown HASH=" + hash;
}

std::vector<uint8_t> secret, info, expect;

ASSERT_TRUE(t->GetBytes(&secret, "SECRET"));
if (t->HasAttribute("INFO")) {
ASSERT_TRUE(t->GetBytes(&info, "INFO"));
} else {
info = std::vector<uint8_t>(0);
}
ASSERT_TRUE(t->GetBytes(&expect, "EXPECT"));

std::vector<uint8_t> out(expect.size());

ASSERT_TRUE(KBKDF_ctr_hmac(out.data(), out.size(), md, secret.data(),
secret.size(), info.data(), info.size()));
ASSERT_EQ(Bytes(expect.data(), expect.size()),
Bytes(out.data(), out.size()));
});
}

TEST(KBKDFCounterTest, NegativeTests) {
const uint8_t secret[16] = {0};
std::vector<uint8_t> out(16);

// NULL output
ASSERT_FALSE(KBKDF_ctr_hmac(NULL, out.size(), EVP_sha256(), &secret[0],
sizeof(secret), NULL, 0));

// zero-length output
ASSERT_FALSE(KBKDF_ctr_hmac(out.data(), 0, EVP_sha256(), &secret[0],
sizeof(secret), NULL, 0));

// NULL Digest
ASSERT_FALSE(KBKDF_ctr_hmac(out.data(), out.size(), NULL, &secret[0],
sizeof(secret), NULL, 0));

// NULL secret
ASSERT_FALSE(KBKDF_ctr_hmac(out.data(), out.size(), EVP_sha256(), NULL,
sizeof(secret), NULL, 0));

// zero-length secret
ASSERT_FALSE(KBKDF_ctr_hmac(out.data(), out.size(), EVP_sha256(), &secret[0],
0, NULL, 0));
}
17 changes: 11 additions & 6 deletions crypto/fipsmodule/kdf/sskdf.c
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ static int sskdf_variant_hmac_compute(sskdf_variant_ctx *ctx, uint8_t *out,

// NIST.SP.800-56Cr2: Step 6.2 HMAC-hash(salt, counter || secret || info)
// Note: |variant_ctx->hmac_ctx| is already initalized with the salt during
// it's initial construction.
// its initial construction.
if (!HMAC_Init_ex(variant_ctx->hmac_ctx, NULL, 0, NULL, NULL) ||
!HMAC_Update(variant_ctx->hmac_ctx, &counter[0], SSKDF_COUNTER_SIZE) ||
!HMAC_Update(variant_ctx->hmac_ctx, secret, secret_len) ||
Expand Down Expand Up @@ -224,10 +224,11 @@ static int SSKDF(const sskdf_variant *variant, sskdf_variant_ctx *ctx,
}

// NIST.SP.800-56Cr2 Step 1:
// Determine how many output chunks are required to produce the request output
// length |out_len|. This determines how many times the variant compute
// Determine how many output chunks are required to produce the requested
// output length |out_len|. This determines how many times the variant compute
// function will be called to output key material.
uint64_t n = (out_len + h_output_bytes - 1) / h_output_bytes;
uint64_t n = ((uint64_t)out_len + (uint64_t)h_output_bytes - 1) /
(uint64_t)h_output_bytes;

// NIST.SP.800-56Cr2 Step 2:
// Verify that the number of output chunks does not exceed an unsigned 32-bit
Expand Down Expand Up @@ -259,8 +260,9 @@ static int SSKDF(const sskdf_variant *variant, sskdf_variant_ctx *ctx,
}

// NIST.SP.800-56Cr2: Step 6.3. Step 7, Step 8
// Combine the output from |out_key_i| with the output written to |out_key| so far.
// Ensure that we only copy |out_len| bytes in total from all chunks.
// Combine the output from |out_key_i| with the output written to |out_key|
// so far. Ensure that we only copy |out_len| bytes in total from all
// chunks.
todo = h_output_bytes;
if (todo > out_len - done) {
todo = out_len - done;
Expand All @@ -278,6 +280,9 @@ static int SSKDF(const sskdf_variant *variant, sskdf_variant_ctx *ctx,
ret = 1;

err:
if (ret <= 0 && out_key && out_len > 0) {
OPENSSL_cleanse(out_key, out_len);
}
return ret;
}

Expand Down
Loading

0 comments on commit 4ac1742

Please sign in to comment.