Skip to content

Commit

Permalink
NIST.SP.800-56Cr2 One-Step Key Derivation (#1607)
Browse files Browse the repository at this point in the history
### Description of changes: 
This pull request implements the One-Step Key Derivation function
defined in [Section 4 of
NIST.SP.800-56Cr2](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf#%5B%7B%22num%22%3A52%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C70%2C720%2C0%5D)

Here we implement two of the three variants: option 1 (hash based) and
option 2 (hmac based).

The abbreviation `SSKDF` is used in the public functions due to the
extensive usage of the term within OpenSSL when referring to this
algorithm. `SSKDF == "Single-Step Key Derivation Function"`.

### Call-outs:
* We will need to add appropriate service indicator logic ahead of our
next FIPS certification round.
* There is some non-trivial amount of work required to wire-up the
KDA-OneStep ACVP test vectors into our current Go ACVP setup and
modulewrapper. I started going down this path, but there is some
refactoring that requires to decouple some of the JSON structure from
the current KDA-HKDF expectations. I'd rather keep this PR to the
implementation and some confidence test vectors I ported from various
other implementation sources.

### Testing:
See the test vectors provided in
[sskdf.txt](https://github.com/skmcgrail/aws-lc/blob/sskdf/crypto/fipsmodule/kdf/test/sskdf.txt)
which I sourced from a number of locations as documented in that file.
This currently provides coverage of the KDF functions for SHA-1,
SHA-224, SHA-256, SHA-384, and SHA-512. SHA-3 algorithms will also get
added coverage once we hook into the ACVP system.

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 Jun 18, 2024
1 parent daa4251 commit e3d34d7
Show file tree
Hide file tree
Showing 9 changed files with 2,482 additions and 730 deletions.
1 change: 1 addition & 0 deletions crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ if(BUILD_TESTING)
fipsmodule/ec/ec_test.cc
fipsmodule/ec/p256-nistz_test.cc
fipsmodule/ecdsa/ecdsa_test.cc
fipsmodule/kdf/kdf_test.cc
fipsmodule/md5/md5_test.cc
fipsmodule/modes/gcm_test.cc
fipsmodule/modes/xts_test.cc
Expand Down
2 changes: 1 addition & 1 deletion 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/sskdf.c"
#include "md4/md4.c"
#include "md5/md5.c"
#include "modes/cbc.c"
Expand Down Expand Up @@ -145,7 +146,6 @@
#include "sshkdf/sshkdf.c"
#include "tls/kdf.c"


#if defined(BORINGSSL_FIPS)

#if !defined(OPENSSL_ASAN)
Expand Down
38 changes: 38 additions & 0 deletions crypto/fipsmodule/kdf/internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#ifndef OPENSSL_HEADER_KDF_INTERNAL_H
#define OPENSSL_HEADER_KDF_INTERNAL_H

#include <openssl/digest.h>
#include <openssl/hmac.h>

#define SSKDF_MAX_INPUT_LEN (1 << 30)
#define SSKDF_COUNTER_SIZE 4

typedef struct {
void *data;
} sskdf_variant_ctx;

typedef struct {
const EVP_MD *digest;
EVP_MD_CTX *md_ctx;
} sskdf_variant_digest_ctx;

typedef struct {
HMAC_CTX *hmac_ctx;
} sskdf_variant_hmac_ctx;

typedef struct {
size_t (*h_output_bytes)(sskdf_variant_ctx *ctx);
int (*compute)(sskdf_variant_ctx *ctx, uint8_t *out, size_t out_len,
const uint8_t counter[SSKDF_COUNTER_SIZE],
const uint8_t *secret, size_t secret_len, const uint8_t *info,
size_t info_len);
} sskdf_variant;

const sskdf_variant *sskdf_variant_digest(void);

const sskdf_variant *sskdf_variant_hmac(void);

#endif // OPENSSL_HEADER_KDF_INTERNAL_H
114 changes: 114 additions & 0 deletions crypto/fipsmodule/kdf/kdf_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <openssl/digest.h>
#include <openssl/kdf.h>
#include <vector>
#include "../../test/file_test.h"
#include "../../test/test_util.h"

#include <gtest/gtest.h>

TEST(SSKDFTest, TestVectors) {
FileTestGTest("crypto/fipsmodule/kdf/test/sskdf.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());

std::string variant;
ASSERT_TRUE(t->GetAttribute(&variant, "VARIANT"));
if (variant == "DIGEST") {
ASSERT_TRUE(SSKDF_digest(out.data(), out.size(), md, secret.data(),
secret.size(), info.data(), info.size()));
} else if (variant == "HMAC") {
if (t->HasAttribute("SALT")) {
std::vector<uint8_t> salt;
ASSERT_TRUE(t->GetBytes(&salt, "SALT"));
ASSERT_TRUE(SSKDF_hmac(out.data(), out.size(), md, secret.data(),
secret.size(), info.data(), info.size(),
salt.data(), salt.size()));
} else {
ASSERT_TRUE(SSKDF_hmac(out.data(), out.size(), md, secret.data(),
secret.size(), info.data(), info.size(), NULL,
0));
}
}
ASSERT_EQ(Bytes(expect.data(), expect.size()),
Bytes(out.data(), out.size()));
});
}

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

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

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

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

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

// zero-length secret
ASSERT_FALSE(SSKDF_digest(out.data(), out.size(), EVP_sha256(), &secret[0], 0,
NULL, 0));
}

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

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

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

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

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

// zero-length secret
ASSERT_FALSE(SSKDF_hmac(out.data(), out.size(), EVP_sha256(), &secret[0], 0,
NULL, 0, NULL, 0));
}
Loading

0 comments on commit e3d34d7

Please sign in to comment.