Skip to content

Commit

Permalink
[crypto] Implement HKDF.
Browse files Browse the repository at this point in the history
HKDF is a common HMAC-based KDF separate from KDF-CTR. This PR
implements it on top of the existing HMAC interface.

Signed-off-by: Jade Philipoom <[email protected]>
  • Loading branch information
jadephilipoom committed Dec 22, 2023
1 parent f508590 commit 597bdbe
Show file tree
Hide file tree
Showing 12 changed files with 440 additions and 49 deletions.
23 changes: 18 additions & 5 deletions doc/security/cryptolib/cryptolib_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -460,15 +460,24 @@ Key derivation functions (KDFs) generate a new key from an existing key.

### Supported Modes

The OpenTitan key derivation function is based on the counter mode and uses a pseudorandom function (PRF) as a building block.
The PRF may be either HMAC or KMAC.
OpenTitan supports two different key derivation methods:
- KDF-CTR following [NIST SP800-108][kdf-prf-spec] with HMAC or KMAC as the PRF
- HKDF following [IETF RFC 5869][hkdf-rfc], which is special case of [NIST SP800-56C][nist-kdf-key-establishment]

To learn more about PRFs, various key derivation mechanisms and security considerations, please refer to [NIST SP800-108][kdf-spec] and the links in the [reference](#reference) section.
To learn more about PRFs, various key derivation mechanisms and security considerations, please refer to the links in the [reference](#reference) section.

### API

#### KDF-CTR

{{#header-snippet sw/device/lib/crypto/include/kdf.h otcrypto_kdf_ctr }}

#### HKDF

{{#header-snippet sw/device/lib/crypto/include/kdf.h otcrypto_kdf_hkdf }}
{{#header-snippet sw/device/lib/crypto/include/kdf.h otcrypto_kdf_hkdf_extract }}
{{#header-snippet sw/device/lib/crypto/include/kdf.h otcrypto_kdf_hkdf_expand }}

## Key import and export

The following section defines the interface for importing keys to and exporting keys from the crypto library.
Expand Down Expand Up @@ -624,7 +633,9 @@ The table below is a recommendation from [NIST SP800-57 Part 1][nist-sp800-57] a
4. OpenTitan [CSRNG block][csrng] technical specification

**Key derivation**
1. [NIST SP800-108][kdf-spec]: Recommendation for Key Derivation using Pseudorandom Functions
1. [NIST SP800-108][kdf-prf-spec]: Recommendation for Key Derivation using Pseudorandom Functions
2. [NIST SP800-56C][nist-kdf-key-establishment]: Recommendation for Key-Derivation Methods in Key-Establishment Schemes
3. [RFC 5869][hkdf-rfc]: HMAC-based Extract-and-Expand Key Derivation Function (HKDF)

**Key management and security strength**
1. [NIST SP800-131][nist-sp800-131a]: Transitioning the Use of Cryptographic Algorithms and Key Lengths
Expand All @@ -643,17 +654,19 @@ The table below is a recommendation from [NIST SP800-57 Part 1][nist-sp800-57] a
[entropy-src]: ../../../hw/ip/entropy_src/README.md
[fips-186]: https://csrc.nist.gov/publications/detail/fips/186/5/final
[gcm-spec]: https://csrc.nist.gov/publications/detail/sp/800-38d/final
[hkdf-rfc]: https://datatracker.ietf.org/doc/html/rfc5869
[hmac]: ../../../hw/ip/hmac/README.md
[hmac-rfc]: https://datatracker.ietf.org/doc/html/rfc2104
[hmac-testvectors-rfc]: https://datatracker.ietf.org/doc/html/rfc4231
[hmac-usage-rfc]: https://datatracker.ietf.org/doc/html/rfc4868
[kdf-spec]: https://csrc.nist.gov/publications/detail/sp/800-108/final
[kdf-prf-spec]: https://csrc.nist.gov/publications/detail/sp/800-108/final
[keymgr]: ../../../hw/ip/keymgr/README.md
[kmac]: ../../../hw/ip/kmac/README.md
[kwp-spec]: https://csrc.nist.gov/publications/detail/sp/800-38f/final
[nist-drbg-spec]: https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final
[nist-ecc-domain-params]: https://csrc.nist.gov/publications/detail/sp/800-186/final
[nist-entropy-spec]: https://csrc.nist.gov/publications/detail/sp/800-90b/final
[nist-kdf-key-establishment]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Cr2.pdf
[nist-rng-spec]: https://csrc.nist.gov/CSRC/media/Publications/sp/800-90c/draft/documents/sp800_90c_second_draft.pdf
[nist-sp800-131a]: https://csrc.nist.gov/publications/detail/sp/800-131a/rev-2/final
[nist-sp800-57]: https://csrc.nist.gov/publications/detail/sp/800-57-part-1/rev-5/final
Expand Down
4 changes: 4 additions & 0 deletions sw/device/lib/crypto/impl/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ cc_library(
srcs = ["kdf.c"],
hdrs = ["//sw/device/lib/crypto/include:kdf.h"],
deps = [
":integrity",
":keyblob",
":mac",
":status",
"//sw/device/lib/base:math",
"//sw/device/lib/crypto/include:datatypes",
],
)
Expand Down
302 changes: 300 additions & 2 deletions sw/device/lib/crypto/impl/kdf.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,314 @@

#include "sw/device/lib/crypto/include/kdf.h"

#include "sw/device/lib/base/math.h"
#include "sw/device/lib/crypto/impl/integrity.h"
#include "sw/device/lib/crypto/impl/keyblob.h"
#include "sw/device/lib/crypto/impl/status.h"
#include "sw/device/lib/crypto/include/datatypes.h"
#include "sw/device/lib/crypto/include/mac.h"

// Module ID for status codes.
#define MODULE_ID MAKE_MODULE_ID('k', 'd', 'f')

crypto_status_t otcrypto_kdf_ctr(const crypto_blinded_key_t key_derivation_key,
crypto_status_t otcrypto_kdf_ctr(const crypto_blinded_key_t ikm,
kdf_type_t kdf_mode, key_mode_t key_mode,
size_t required_bit_len,
crypto_blinded_key_t keying_material) {
// TODO: Implement HMAC-KDF and KMAC-KDF.
// TODO: Implement HMAC-KDF-CTR and KMAC-KDF-CTR.
return OTCRYPTO_NOT_IMPLEMENTED;
}

/**
* Infer the digest length in 32-bit words for the given hash function.
*
* @param key_mode HMAC key mode.
* @param[out] digest_words Number of words in the hash digest.
* @return OK or error.
*/
static status_t digest_num_words_from_key_mode(key_mode_t key_mode,
size_t *digest_words) {
*digest_words = 0;
switch (launder32(key_mode)) {
case kKeyModeHmacSha256:
HARDENED_CHECK_EQ(key_mode, kKeyModeHmacSha256);
*digest_words = 256 / 32;
break;
case kKeyModeHmacSha384:
HARDENED_CHECK_EQ(key_mode, kKeyModeHmacSha384);
*digest_words = 384 / 32;
break;
case kKeyModeHmacSha512:
HARDENED_CHECK_EQ(key_mode, kKeyModeHmacSha512);
*digest_words = 512 / 32;
break;
default:
return OTCRYPTO_BAD_ARGS;
}
HARDENED_CHECK_NE(*digest_words, 0);
return OTCRYPTO_OK;
}

crypto_status_t otcrypto_kdf_hkdf(const crypto_blinded_key_t ikm,
crypto_const_byte_buf_t salt,
crypto_const_byte_buf_t info,
crypto_blinded_key_t *okm) {
// Infer the digest length.
size_t digest_wordlen;
HARDENED_TRY(
digest_num_words_from_key_mode(ikm.config.key_mode, &digest_wordlen));
size_t digest_bytelen = digest_wordlen * sizeof(uint32_t);

// Construct a blinded key struct for the intermediate key.
crypto_key_config_t prk_config = {
.version = kCryptoLibVersion1,
.key_mode = ikm.config.key_mode,
.key_length = digest_bytelen,
.hw_backed = kHardenedBoolFalse,
.exportable = kHardenedBoolFalse,
.security_level = kSecurityLevelLow,
};
size_t keyblob_wordlen = keyblob_num_words(prk_config);
uint32_t keyblob[keyblob_wordlen];
crypto_blinded_key_t prk = {
.config = prk_config,
.keyblob = keyblob,
.keyblob_length = sizeof(keyblob),
};

// Call extract and expand.
HARDENED_TRY(otcrypto_kdf_hkdf_extract(ikm, salt, &prk));
return otcrypto_kdf_hkdf_expand(prk, info, okm);
}

/**
* Check that an HKDF pseudo-random key is correctly configured.
*
* Ensures that the key mode, length, and allocated keyblob length are suitable
* for an HKDF pseudo-random key (i.e. the input for extract and the output for
* expand). Does not dereference the keyblob, so it is safe to call this before
* the keyblob is initialized.
*
* @param digest_words Length of the hash digest in 32-bit words.
* @param prk Pseudo-random key struct to check.
* @return OK if the PRK is acceptable, otherwise OTCRYPTO_BAD_ARGS.
*/
static status_t hkdf_check_prk(size_t digest_words,
const crypto_blinded_key_t *prk) {
if (launder32(prk->config.key_mode) >> 16 != kKeyTypeHmac) {
return OTCRYPTO_BAD_ARGS;
}
HARDENED_CHECK_EQ(prk->config.key_mode >> 16, kKeyTypeHmac);

// PRK should be the same length as the digest.
size_t digest_bytelen = digest_words * sizeof(uint32_t);
if (launder32(prk->config.key_length) != digest_bytelen) {
return OTCRYPTO_BAD_ARGS;
}
HARDENED_CHECK_EQ(prk->config.key_length, digest_bytelen);

// Check the keyblob length.
size_t keyblob_bytelen = keyblob_num_words(prk->config) * sizeof(uint32_t);
if (launder32(prk->keyblob_length) != keyblob_bytelen) {
return OTCRYPTO_BAD_ARGS;
}
HARDENED_CHECK_EQ(prk->keyblob_length, keyblob_bytelen);

// Ensure that the PRK is a symmetric key masked with XOR and is not supposed
// to be hardware-backed.
HARDENED_TRY(keyblob_ensure_xor_masked(prk->config));

return OTCRYPTO_OK;
}

crypto_status_t otcrypto_kdf_hkdf_extract(const crypto_blinded_key_t ikm,
crypto_const_byte_buf_t salt,
crypto_blinded_key_t *prk) {
// Check for null pointers.
if (ikm.keyblob == NULL || prk == NULL || prk->keyblob == NULL) {
return OTCRYPTO_BAD_ARGS;
}
if (salt.data == NULL && salt.len != 0) {
return OTCRYPTO_BAD_ARGS;
}

// Check the private key checksum.
if (integrity_blinded_key_check(&ikm) != kHardenedBoolTrue) {
return OTCRYPTO_BAD_ARGS;
}

if (launder32(ikm.config.security_level) != kSecurityLevelLow ||
launder32(prk->config.security_level) != kSecurityLevelLow) {
// The underlying HMAC implementation is not currently hardened.
return OTCRYPTO_NOT_IMPLEMENTED;
}
HARDENED_CHECK_EQ(ikm.config.security_level, kSecurityLevelLow);
HARDENED_CHECK_EQ(prk->config.security_level, kSecurityLevelLow);

// Ensure the key modes match.
if (launder32(prk->config.key_mode) != launder32(ikm.config.key_mode)) {
return OTCRYPTO_BAD_ARGS;
}
HARDENED_CHECK_EQ(prk->config.key_mode, ikm.config.key_mode);

// Infer the digest size. This step also ensures that the key mode is
// supported.
size_t digest_words = 0;
HARDENED_TRY(
digest_num_words_from_key_mode(ikm.config.key_mode, &digest_words));

// Validate the PRK configuration.
HARDENED_TRY(hkdf_check_prk(digest_words, prk));

// Copy the salt into a 32-bit aligned buffer. If the salt is empty, replace
// it with a string of `hashLen` zeroes as specified in RFC 5869.
size_t salt_bytelen =
(salt.len == 0) ? digest_words * sizeof(uint32_t) : salt.len;
size_t salt_wordlen = ceil_div(salt_bytelen, sizeof(uint32_t));
uint32_t salt_aligned_data[salt_wordlen];
memset(salt_aligned_data, 0, sizeof(salt_aligned_data));
if (salt.len > 0) {
memcpy(salt_aligned_data, salt.data, salt.len);
}

// The extract stage uses `salt` as the key and the input key as the message.
// We therefore need to unmask the key and package the salt in a blinded key
// struct.

// Unmask the input key.
uint32_t *ikm_share0;
uint32_t *ikm_share1;
HARDENED_TRY(keyblob_to_shares(&ikm, &ikm_share0, &ikm_share1));
uint32_t unmasked_ikm_data[keyblob_share_num_words(ikm.config)];
for (size_t i = 0; i < ARRAYSIZE(unmasked_ikm_data); i++) {
unmasked_ikm_data[i] = ikm_share0[i] ^ ikm_share1[i];
}
crypto_const_byte_buf_t unmasked_ikm = {
.data = (unsigned char *)unmasked_ikm_data,
.len = ikm.config.key_length,
};

// Package the salt value in a blinded key, using an all-zero mask because
// the salt is not actually secret.
uint32_t salt_mask[ARRAYSIZE(salt_aligned_data)];
memset(salt_mask, 0, sizeof(salt_mask));
crypto_key_config_t salt_key_config = {
.version = kCryptoLibVersion1,
.key_mode = ikm.config.key_mode,
.key_length = salt_bytelen,
.hw_backed = kHardenedBoolFalse,
.exportable = kHardenedBoolFalse,
.security_level = kSecurityLevelLow,
};
uint32_t salt_keyblob[keyblob_num_words(salt_key_config)];
TRY(keyblob_from_key_and_mask(salt_aligned_data, salt_mask, salt_key_config,
salt_keyblob));
crypto_blinded_key_t salt_key = {
.config = salt_key_config,
.keyblob = salt_keyblob,
.keyblob_length = sizeof(salt_keyblob),
};

// Call HMAC(salt, IKM).
uint32_t tag_data[digest_words];
crypto_word32_buf_t tag = {.data = tag_data, .len = ARRAYSIZE(tag_data)};
HARDENED_TRY(otcrypto_hmac(&salt_key, unmasked_ikm, &tag));

// Construct the blinded keyblob for PRK (with an all-zero mask for now
// because HMAC is unhardened anyway).
uint32_t prk_mask[digest_words];
memset(prk_mask, 0, sizeof(prk_mask));
HARDENED_TRY(
keyblob_from_key_and_mask(tag_data, prk_mask, prk->config, prk->keyblob));
prk->checksum = integrity_blinded_checksum(prk);
return OTCRYPTO_OK;
}

crypto_status_t otcrypto_kdf_hkdf_expand(const crypto_blinded_key_t prk,
crypto_const_byte_buf_t info,
crypto_blinded_key_t *okm) {
if (okm == NULL || okm->keyblob == NULL || prk.keyblob == NULL) {
return OTCRYPTO_BAD_ARGS;
}
if (info.data == NULL && info.len != 0) {
return OTCRYPTO_BAD_ARGS;
}

if (launder32(okm->config.security_level) != kSecurityLevelLow ||
launder32(prk.config.security_level) != kSecurityLevelLow) {
// The underlying HMAC implementation is not currently hardened.
return OTCRYPTO_NOT_IMPLEMENTED;
}

// Infer the digest size.
size_t digest_words = 0;
HARDENED_TRY(
digest_num_words_from_key_mode(prk.config.key_mode, &digest_words));

// Check the PRK configuration.
HARDENED_TRY(hkdf_check_prk(digest_words, &prk));

// Ensure that the derived key is a symmetric key masked with XOR and is not
// supposed to be hardware-backed.
HARDENED_TRY(keyblob_ensure_xor_masked(okm->config));

// Check the keyblob length.
size_t keyblob_bytelen = keyblob_num_words(okm->config) * sizeof(uint32_t);
if (launder32(okm->keyblob_length) != keyblob_bytelen) {
return OTCRYPTO_BAD_ARGS;
}
HARDENED_CHECK_EQ(okm->keyblob_length, keyblob_bytelen);

// Check that the unmasked key length is not too large for HKDF (see RFC
// 5869, section 2.3).
size_t okm_bytelen = okm->config.key_length;
size_t okm_wordlen = ceil_div(okm_bytelen, sizeof(uint32_t));
size_t num_iterations = ceil_div(okm_wordlen, digest_words);
if (launder32(num_iterations) > 255) {
return OTCRYPTO_BAD_ARGS;
}
HARDENED_CHECK_LE(num_iterations, 255);

// Create a buffer that holds `info` and a one-byte counter.
uint8_t info_and_counter_data[info.len + 1];
memcpy(info_and_counter_data, info.data, info.len);
info_and_counter_data[info.len] = 0x00;
crypto_const_byte_buf_t info_and_counter = {
.data = info_and_counter_data,
.len = sizeof(info_and_counter_data),
};

// Repeatedly call HMAC to generate the derived key (see RFC 5869, section
// 2.3):
uint32_t okm_data[okm_wordlen];
uint32_t *t_data = okm_data;
for (uint8_t i = 0; i < num_iterations; i++) {
info_and_counter_data[info.len] = i + 1;
hmac_context_t ctx;
HARDENED_TRY(otcrypto_hmac_init(&ctx, &prk));
if (launder32(i) != 0) {
crypto_const_byte_buf_t t_bytes = {
.data = (unsigned char *)t_data,
.len = digest_words * sizeof(uint32_t),
};
HARDENED_TRY(otcrypto_hmac_update(&ctx, t_bytes));
t_data += digest_words;
}
HARDENED_TRY(otcrypto_hmac_update(&ctx, info_and_counter));
crypto_word32_buf_t t_words = {
.data = t_data,
.len = digest_words,
};
HARDENED_TRY(otcrypto_hmac_final(&ctx, &t_words));
}

// Generate a mask (all-zero for now, since HMAC is unhardened anyway).
uint32_t mask[digest_words];
memset(mask, 0, sizeof(mask));

// Construct a blinded key.
HARDENED_TRY(
keyblob_from_key_and_mask(okm_data, mask, okm->config, okm->keyblob));
okm->checksum = integrity_blinded_checksum(okm);
return OTCRYPTO_OK;
}
3 changes: 3 additions & 0 deletions sw/device/lib/crypto/impl/keyblob.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ status_t keyblob_remask(crypto_blinded_key_t *key, const uint32_t *mask) {
// Check that the key is masked with XOR.
HARDENED_TRY(keyblob_ensure_xor_masked(key->config));

// Double-check the length of the keyblob.
HARDENED_TRY(check_keyblob_length(key));

size_t key_share_words = keyblob_share_num_words(key->config);
size_t keyblob_words = keyblob_num_words(key->config);

Expand Down
Loading

0 comments on commit 597bdbe

Please sign in to comment.