Skip to content

Commit

Permalink
One-step key derivation implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
skmcgrail committed May 24, 2024
1 parent 92bf532 commit 3687895
Show file tree
Hide file tree
Showing 7 changed files with 579 additions and 1 deletion.
1 change: 1 addition & 0 deletions crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,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
58 changes: 58 additions & 0 deletions crypto/fipsmodule/kdf/kdf_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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-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"));
ASSERT_TRUE(t->GetBytes(&info, "INFO"));
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()));
});
}
315 changes: 315 additions & 0 deletions crypto/fipsmodule/kdf/sskdf.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,315 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR ISC

#include <assert.h>
#include <openssl/base.h>
#include <openssl/digest.h>
#include <openssl/hmac.h>
#include <openssl/kdf.h>

typedef struct {
void *data;
} SSKDF_VARIANT_CTX;

typedef struct {
const EVP_MD *digest;
EVP_MD_CTX *ctx;
} SSKDF_VARIANT_DIGEST_CTX;

typedef struct {
HMAC_CTX *ctx;
} SSKDF_VARIANT_HMAC_CTX;

typedef struct {
size_t (*size)(SSKDF_VARIANT_CTX *ctx);
int (*compute)(SSKDF_VARIANT_CTX *ctx, uint8_t *out, size_t out_len,
const uint8_t counter[4], const uint8_t *secret,
size_t secret_len, const uint8_t *info, size_t info_len);
} SSKDF_VARIANT;

static int SSKDF_VARIANT_DIGEST_CTX_init(SSKDF_VARIANT_CTX *ctx,
const EVP_MD *digest) {
SSKDF_VARIANT_DIGEST_CTX *variant_ctx = NULL;
EVP_MD_CTX *md_ctx = NULL;

int ret = 0;

if (!ctx || !digest) {
goto err;
}

variant_ctx = OPENSSL_malloc(sizeof(SSKDF_VARIANT_DIGEST_CTX));
if (!variant_ctx) {
goto err;
}

md_ctx = EVP_MD_CTX_new();
if (!md_ctx) {
goto err;
}

ret = 1;
variant_ctx->digest = digest;
variant_ctx->ctx = md_ctx;
ctx->data = variant_ctx;

return ret;

err:
EVP_MD_CTX_free(md_ctx);
OPENSSL_free(variant_ctx);

return ret;
}



static void SSKDF_VARIANT_DIGEST_CTX_cleanup(SSKDF_VARIANT_CTX *ctx) {
if (!ctx) {
return;
}
SSKDF_VARIANT_DIGEST_CTX *variant_ctx = (SSKDF_VARIANT_DIGEST_CTX *)ctx->data;
if (!ctx->data) {
return;
}
EVP_MD_CTX_free(variant_ctx->ctx);
OPENSSL_free(variant_ctx);
ctx->data = NULL;
}

static int SSKDF_VARIANT_HMAC_CTX_init(SSKDF_VARIANT_CTX *ctx,
const EVP_MD *digest,
const uint8_t *salt, size_t salt_len) {
SSKDF_VARIANT_HMAC_CTX *variant_ctx = NULL;
HMAC_CTX *hmac_ctx = NULL;

int ret = 0;

if (!ctx || !digest) {
goto err;
}

variant_ctx = OPENSSL_malloc(sizeof(SSKDF_VARIANT_HMAC_CTX));
if (!variant_ctx) {
goto err;
}

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

if (!HMAC_Init_ex(hmac_ctx, salt, salt_len, digest, NULL)) {
goto err;
}

ret = 1;
variant_ctx->ctx = hmac_ctx;
ctx->data = variant_ctx;

return ret;

err:
HMAC_CTX_free(hmac_ctx);
OPENSSL_free(variant_ctx);

return ret;
}



static void SSKDF_VARIANT_HMAC_CTX_cleanup(SSKDF_VARIANT_CTX *ctx) {
if (!ctx) {
return;
}
SSKDF_VARIANT_HMAC_CTX *hmac_ctx = (SSKDF_VARIANT_HMAC_CTX *)ctx->data;
if (!hmac_ctx) {
return;
}
HMAC_CTX_free(hmac_ctx->ctx);
OPENSSL_free(hmac_ctx);
ctx->data = NULL;
}

static size_t sskdf_variant_digest_size(SSKDF_VARIANT_CTX *ctx) {
if (!ctx || !ctx->data) {
return 0;
}
SSKDF_VARIANT_DIGEST_CTX *variant_ctx = (SSKDF_VARIANT_DIGEST_CTX *)ctx->data;
return EVP_MD_size(variant_ctx->digest);
}

static int sskdf_variant_digest_compute(SSKDF_VARIANT_CTX *ctx, uint8_t *out,
size_t out_len,
const uint8_t counter[4],
const uint8_t *secret,
size_t secret_len, const uint8_t *info,
size_t info_len) {
if (!ctx || !ctx->data || !out || !counter || !secret || !info) {
return 0;
}

SSKDF_VARIANT_DIGEST_CTX *variant_ctx = (SSKDF_VARIANT_DIGEST_CTX *)ctx->data;

if (!EVP_MD_CTX_reset(variant_ctx->ctx) ||
!EVP_DigestInit_ex(variant_ctx->ctx, variant_ctx->digest, NULL) ||
!EVP_DigestUpdate(variant_ctx->ctx, &counter[0], 4) ||
!EVP_DigestUpdate(variant_ctx->ctx, secret, secret_len) ||
!EVP_DigestUpdate(variant_ctx->ctx, info, info_len) ||
!EVP_DigestFinal(variant_ctx->ctx, out, NULL)) {
// TODO: put error on queue
return 0;
}

return 1;
}

static size_t sskdf_variant_hmac_size(SSKDF_VARIANT_CTX *ctx) {
if (!ctx || !ctx->data) {
// TODO: put error on queue
return 0;
}
SSKDF_VARIANT_HMAC_CTX *variant_ctx = (SSKDF_VARIANT_HMAC_CTX *)ctx->data;
if (!variant_ctx) {
// TODO: put error on queue
return 0;
}
return HMAC_size(variant_ctx->ctx);
}

static int sskdf_variant_hmac_compute(SSKDF_VARIANT_CTX *ctx, uint8_t *out,
size_t out_len, const uint8_t counter[4],
const uint8_t *secret, size_t secret_len,
const uint8_t *info, size_t info_len) {
if (!ctx || !ctx->data || !out || !counter || !secret || !info) {
return 0;
}

SSKDF_VARIANT_HMAC_CTX *variant_ctx = (SSKDF_VARIANT_HMAC_CTX *)ctx->data;


if (!HMAC_Init_ex(variant_ctx->ctx, NULL, 0, NULL, NULL) ||
!HMAC_Update(variant_ctx->ctx, &counter[0], 4) ||
!HMAC_Update(variant_ctx->ctx, secret, secret_len) ||
!HMAC_Update(variant_ctx->ctx, info, info_len) ||
!HMAC_Final(variant_ctx->ctx, out, NULL)) {
return 0;
}

return 1;
}

static const SSKDF_VARIANT SSKDF_VARIANT_DIGEST = {
.size = sskdf_variant_digest_size,
.compute = sskdf_variant_digest_compute,
};

static const SSKDF_VARIANT SSKDF_VARIANT_HMAC = {
.size = sskdf_variant_hmac_size,
.compute = sskdf_variant_hmac_compute,
};

static int SSKDF(const SSKDF_VARIANT *variant, SSKDF_VARIANT_CTX *ctx,
uint8_t *out_key, size_t out_len, const uint8_t *secret,
size_t secret_len, const uint8_t *info, size_t info_len) {
int ret = 0;

if (!ctx) {
abort();
}

size_t h_len = variant->size(ctx);
if (!h_len) {
return 0;
}
assert(h_len <= EVP_MAX_MD_SIZE);

uint64_t n = (out_len + h_len - 1) / h_len;

size_t done = 0;

if (out_len + h_len < out_len || n > UINT32_MAX) {
goto err;
}

// UINT32_MAX is sufficient to cap the approved algorithms to not exceed the
// max_H_inputBits hash(x) or HMAC-hash(salt, x).
// See Section 4.2 Table 1 and 2
// https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf
if (4 + secret_len + info_len > UINT32_MAX) {
// TODO: put error on queue
goto err;
}

// TODO: Abstract buffer size, if we ever need to support KMAC this could be
// variable. Currently sufficient for HMAC and digest variants
uint8_t previous[EVP_MAX_MD_SIZE];

for (uint32_t i = 0; i < n; i++) {
uint8_t counter[4]; // counter == 4 bytes
size_t todo;

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

if (!variant->compute(ctx, &previous[0], h_len, counter, secret, secret_len,
info, info_len)) {
goto err;
}

todo = h_len;
if (todo > out_len - done) {
todo = out_len - done;
}
OPENSSL_memcpy(out_key + done, previous, todo);
done += todo;
}

ret = 1;

err:
return ret;
}

int SSKDF_DIGEST(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) {
SSKDF_VARIANT_CTX ctx;
int ret = 0;

if (!SSKDF_VARIANT_DIGEST_CTX_init(&ctx, digest)) {
return 0;
}

if (!SSKDF(&SSKDF_VARIANT_DIGEST, &ctx, out_key, out_len, secret, secret_len,
info, info_len)) {
goto end;
}

ret = 1;

end:
SSKDF_VARIANT_DIGEST_CTX_cleanup(&ctx);
return ret;
}

int SSKDF_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, const uint8_t *salt, size_t salt_len) {
SSKDF_VARIANT_CTX ctx;
int ret = 0;

if (!SSKDF_VARIANT_HMAC_CTX_init(&ctx, digest, salt, salt_len)) {
return 0;
}

if (!SSKDF(&SSKDF_VARIANT_HMAC, &ctx, out_key, out_len, secret, secret_len,
info, info_len)) {
goto end;
}

ret = 1;

end:
SSKDF_VARIANT_HMAC_CTX_cleanup(&ctx);
return ret;
}
Loading

0 comments on commit 3687895

Please sign in to comment.