diff --git a/include/cose/crypto.h b/include/cose/crypto.h index 158d024..0f28684 100644 --- a/include/cose/crypto.h +++ b/include/cose/crypto.h @@ -303,6 +303,63 @@ void cose_crypto_keypair_ecdsa(cose_key_t *key, cose_curve_t curve); * @return Signature size */ size_t cose_crypto_sig_size_ed25519(void); + +/** @} */ + +/** + * @name HKDF related functions + * + * @{ + */ + +/** @brief Decide whether a given algorithm is known and an HKDF algorithm + * + * @param[in] alg The algorithm to be checked + * @return true iff @p alg can be used with @ref cose_crypto_hkdf_derive + */ +bool cose_crypto_is_hkdf(cose_algo_t alg); + +/** @brief Derive a key using HKDF (HMAC based key derivation function) + * + * @param[in] salt Salt for key generation. Can be empty + * @param[in] salt_len Length of @p salt + * @param[in] ikm key material + * @param[in] ikm_length Length of @p ikm + * @param[in] info Info for for derived key + * @param[in] info_length Length of @p info + * @param[out] out Output buffer where the key is written to + * @param[in] out_length Length of @p out + * @param[in] alg HKDF algorithm to use + */ +int cose_crypto_hkdf_derive(const uint8_t *salt, + size_t salt_len, + const uint8_t *ikm, + size_t ikm_length, + const uint8_t *info, + size_t info_length, + uint8_t *out, + size_t out_length, + cose_algo_t alg); + +/** @brief Derive a key using HMAC256 + * + * @param[in] salt Salt for key generation. Can be empty + * @param[in] salt_len Length of @p salt + * @param[in] ikm key material + * @param[in] ikm_length Length of @p ikm + * @param[in] info Info for for derived key + * @param[in] info_length Length of @p info + * @param[out] out Output buffer where the key is written to + * @param[in] out_length Length of @p out + */ +int cose_crypto_hkdf_derive_sha256(const uint8_t *salt, + size_t salt_len, + const uint8_t *ikm, + size_t ikm_length, + const uint8_t *info, + size_t info_length, + uint8_t *out, + size_t out_length); /** @} */ #ifdef __cplusplus diff --git a/include/cose/crypto/selectors.h b/include/cose/crypto/selectors.h index 960cc42..237fe22 100644 --- a/include/cose/crypto/selectors.h +++ b/include/cose/crypto/selectors.h @@ -48,6 +48,15 @@ #define CRYPTO_HACL_INCLUDE_CHACHAPOLY #endif /** @} */ + +/** + * + * @name HKDF SHA256 selector + */ +#ifdef CRYPTO_SODIUM +#define CRYPTO_SODIUM_INCLUDE_HKDFSHA256 +#endif + #endif /* COSE_CRYPTO_SELECTORS_H */ /** @} */ diff --git a/include/cose/crypto/sodium.h b/include/cose/crypto/sodium.h index 31014b7..1739e1c 100644 --- a/include/cose/crypto/sodium.h +++ b/include/cose/crypto/sodium.h @@ -34,6 +34,7 @@ extern "C" { */ #define HAVE_ALGO_CHACHA20POLY1305 #define HAVE_ALGO_EDDSA +#define HAVE_ALGO_HMAC256 /** @} */ #ifdef __cplusplus diff --git a/include/cose_defines.h b/include/cose_defines.h index 84ff534..a3bf2f5 100644 --- a/include/cose_defines.h +++ b/include/cose_defines.h @@ -160,6 +160,7 @@ typedef enum { COSE_ALGO_A128GCM = 1, /**< AES-GCM mode w/ 128-bit key, 128-bit tag */ COSE_ALGO_A192GCM = 2, /**< AES-GCM mode w/ 192-bit key, 128-bit tag */ COSE_ALGO_A256GCM = 3, /**< AES-GCM mode w/ 256-bit key, 128-bit tag */ + COSE_ALGO_HMAC256 = 5, /**< HMAC w/ SHA-256 */ COSE_ALGO_CHACHA20POLY1305 = 24, /**< IETF ChaCha20/Poly1305 w/ 256-bit key, 128-bit tag */ } cose_algo_t; diff --git a/src/cose_hkdf.c b/src/cose_hkdf.c new file mode 100644 index 0000000..cc5cb56 --- /dev/null +++ b/src/cose_hkdf.c @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 Freie Universitat Berlin + * Copyright (C) 2018 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include "cose/crypto.h" + +bool cose_crypto_is_hkdf(cose_algo_t alg) +{ + /* NOLINTNEXTLINE(hicpp-multiway-paths-covered) */ + switch(alg) { +#ifdef HAVE_ALGO_HMAC256 + case COSE_ALGO_HMAC256: + return true; +#endif + default: + (void)alg; + return false; + } +} + +int cose_crypto_hkdf_derive(const uint8_t *salt, + size_t salt_len, + const uint8_t *ikm, + size_t ikm_length, + const uint8_t *info, + size_t info_length, + uint8_t *out, + size_t out_length, cose_algo_t alg) { + /* NOLINTNEXTLINE(hicpp-multiway-paths-covered) */ + switch(alg) { +#ifdef HAVE_ALGO_HMAC256 + case COSE_ALGO_HMAC256: + return cose_crypto_hkdf_derive_sha256(salt, salt_len, ikm, + ikm_length, info, info_length, out, out_length); +#endif + default: + (void)salt; + (void)salt_len; + (void)ikm; + (void)ikm_length; + (void)info; + (void)info_length; + (void)out; + (void)out_length; + (void)alg; + return COSE_ERR_NOTIMPLEMENTED; + } +} diff --git a/src/crypt/sodium.c b/src/crypt/sodium.c index 92303d1..e31e66c 100644 --- a/src/crypt/sodium.c +++ b/src/crypt/sodium.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -111,3 +112,54 @@ size_t cose_crypto_sig_size_ed25519(void) return crypto_sign_BYTES; } #endif /* CRYPTO_SODIUM_INCLUDE_ED25519 */ + +#ifdef CRYPTO_SODIUM_INCLUDE_HKDFSHA256 +int cose_crypto_hkdf_derive_sha256(const uint8_t *salt, + size_t salt_len, + const uint8_t *ikm, + size_t ikm_length, + const uint8_t *info, + size_t info_length, + uint8_t *out, + size_t out_length) +{ + uint8_t prk[crypto_auth_hmacsha256_KEYBYTES]; + + if (salt_len == crypto_auth_hmacsha256_KEYBYTES) { + crypto_auth_hmacsha256(prk, ikm, ikm_length, salt); + } else if (salt_len < crypto_auth_hmacsha256_KEYBYTES) { + uint8_t padding[crypto_auth_hmacsha256_KEYBYTES]; + memset(padding, 0, crypto_auth_hmacsha256_KEYBYTES); + memcpy(padding, salt, salt_len); + crypto_auth_hmacsha256(prk, ikm, ikm_length, padding); + } else { + return COSE_ERR_INVALID_PARAM; + } + + uint8_t slice[crypto_auth_hmacsha256_BYTES]; + size_t slice_len = crypto_auth_hmacsha256_BYTES; + uint8_t counter[1] = {0x01}; + crypto_auth_hmacsha256_state state; + size_t rounds = out_length / crypto_auth_hmacsha256_BYTES; + if (out_length % crypto_auth_hmacsha256_BYTES > 0) { + rounds++; + } + for (size_t i = 0; i < rounds; ++i) { + size_t offset = i * crypto_auth_hmacsha256_BYTES; + *counter = i + 1; + crypto_auth_hmacsha256_init(&state, prk, crypto_auth_hmacsha256_KEYBYTES); + if (i > 0) { + crypto_auth_hmacsha256_update(&state, slice, slice_len); + } + crypto_auth_hmacsha256_update(&state, info, info_length); + crypto_auth_hmacsha256_update(&state, counter, 1); + crypto_auth_hmacsha256_final(&state, slice); + if (i + 1 == rounds) { + slice_len = out_length - offset; + } + memcpy(out + offset, slice, slice_len); + } + + return COSE_OK; +} +#endif /* CRYPTO_SODIUM_INCLUDE_HKDFSHA256 */ diff --git a/tests/hkdf.c b/tests/hkdf.c new file mode 100644 index 0000000..8708bd7 --- /dev/null +++ b/tests/hkdf.c @@ -0,0 +1,80 @@ +#include + +#include +#include +#include +#include + +#include "CUnit/CUnit.h" + +void test_hkdf_vectors_hmac256(void) { + uint8_t out[100]; + uint8_t salt[] = "1234567890abcdefghijklmnopqrstuv"; + uint8_t ikm[] = "abcdefghijklmnopqrst"; + uint8_t info[] = "HKDF TEST"; + size_t salt_len = 32; + size_t ikm_len = 20; + size_t info_len = 9; + + uint8_t expected[] = "\x20\xb5\x23\xe5\x51\x29\xf1\xdb\x54\xfe\xa4\xbd\x60\x84\xf2\x74\xb5\xc9\xcd\x91\xf1\xde\xc7\x3b\x37\xa8\xd4\x8d\x5d\xeb\xc5\xf3\x06\xa1\x10\x90\x05\x88\x5b\x38\x40\x2f\x6d\x86\x49\xd1\x0e\x44\x55\x76\xfb\xb9\x3d\x1d\x42\xa9\x06\x96\xe9\x40\x7f\xd8\x79\xe4\xad\x6e\xae\xc8\x81\x93\x41\xa7\x06\x35\xf4\xd0\x53\x62\xb5\xce\x18\x3b\x98\xc5\xf6\x92\x02\xb2\xe8\x7e\xc1\xfd\x45\xf9\x48\x1c\x39\x0a\xa7\x4f"; + uint8_t expected_nosalt[] = "\x4e\xc9\x5f\x9d\xa4\x02\x4d\x42\xe9\xd8\xce\x26\x1e\x9d\xc1\xaa\xd9\x35\x04\xbb\x02\xe3\xf5\x84\x31\x45\x78\x60\x47\xf3\xc4\x22\x23\x6e\x60\x5c\x92\xf6\xaa\x44\x2f\xf3\xa2\x4e\x26\x3b\xf4\xa9\x57\x89\xa7\xe9\x51\x75\xb1\x5d\xa3\x6f\x31\xc5\xc2\xf8\x93\xfd\x80\x65\x54\x7b\xde\xa5\x04\xe9\x33\x57\x0f\x33\x13\x96\x70\x46\x2d\xdf\x57\xb4\xe7\x3e\x6c\x06\x36\x79\xa3\x54\xc6\x89\x0d\x51\x2c\x5e\x3b\x3b"; + + size_t out_len = 10; + cose_crypto_hkdf_derive(salt, salt_len, ikm, ikm_len, info, info_len, out, out_len, COSE_ALGO_HMAC256); + CU_ASSERT_EQUAL(memcmp(out, expected, out_len), 0); + cose_crypto_hkdf_derive(salt, 0, ikm, ikm_len, info, info_len, out, out_len, COSE_ALGO_HMAC256); + CU_ASSERT_EQUAL(memcmp(out, expected_nosalt, out_len), 0); + + out_len = 64; + cose_crypto_hkdf_derive(salt, salt_len, ikm, ikm_len, info, info_len, out, out_len, COSE_ALGO_HMAC256); + CU_ASSERT_EQUAL(memcmp(out, expected, out_len), 0); + cose_crypto_hkdf_derive(salt, 0, ikm, ikm_len, info, info_len, out, out_len, COSE_ALGO_HMAC256); + CU_ASSERT_EQUAL(memcmp(out, expected_nosalt, out_len), 0); + + out_len = 100; + cose_crypto_hkdf_derive(salt, salt_len, ikm, ikm_len, info, info_len, out, out_len, COSE_ALGO_HMAC256); + CU_ASSERT_EQUAL(memcmp(out, expected, out_len), 0); + cose_crypto_hkdf_derive(salt, 0, ikm, ikm_len, info, info_len, out, out_len, COSE_ALGO_HMAC256); + CU_ASSERT_EQUAL(memcmp(out, expected_nosalt, out_len), 0); + + + uint8_t master_secret[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10}; + uint8_t master_salt[8] = {0x9e, 0x7c, 0xa9, 0x22, 0x23, 0x78, 0x63, 0x40}; + /* An example key info map for RFC8613 */ + uint8_t skey_info[9] = {0x85, 0x40, 0xf6, 0x0a, 0x63, 0x4b, 0x65, 0x79, 0x10}; + uint8_t expected_key_S[16] = {0xf0, 0x91, 0x0e, 0xd7, 0x29, 0x5e, 0x6a, 0xd4, + 0xb5, 0x4f, 0xc7, 0x93, 0x15, 0x43, 0x02, 0xff}; + cose_crypto_hkdf_derive(master_salt, 8, master_secret, 16, skey_info, 9, out, 16, COSE_ALGO_HMAC256); + CU_ASSERT_EQUAL(memcmp(out, expected_key_S, 16), 0); +} + +void test_is_hkdf(void) { + CU_ASSERT_EQUAL(cose_crypto_is_hkdf(COSE_ALGO_NONE), false); + CU_ASSERT_EQUAL(cose_crypto_is_hkdf(COSE_ALGO_CHACHA20POLY1305), false); + bool have_algo_hmac256 = +#ifdef HAVE_ALGO_HMAC256 + true +#else + false +#endif + ; + CU_ASSERT_EQUAL(cose_crypto_is_hkdf(COSE_ALGO_HMAC256), have_algo_hmac256); +} + +const test_t tests_hkdf[] = { +#ifdef HAVE_ALGO_HMAC256 + { + .f = test_hkdf_vectors_hmac256, + .n = "HKDF test vectors for HMAC 256/256", + }, +#endif + { + .f = test_is_hkdf, + .n = "cose_crypto_is_hkdf", + }, + { + .f = NULL, + .n = NULL, + }, +}; diff --git a/tests/test.c b/tests/test.c index 407b18e..2429e4e 100644 --- a/tests/test.c +++ b/tests/test.c @@ -23,6 +23,7 @@ extern test_t tests_crypto[]; extern test_t tests_sign[]; extern test_t tests_suit[]; extern test_t tests_encrypt[]; +extern test_t tests_hkdf[]; int getrandom(void *arg, unsigned char *buf, size_t bytes) { @@ -86,6 +87,13 @@ int main() } add_tests(pSuite, tests_encrypt); + pSuite = CU_add_suite("Suite_hkdf", NULL, NULL); + if (NULL == pSuite) { + CU_cleanup_registry(); + return CU_get_error(); + } + add_tests(pSuite, tests_hkdf); + srand(time(NULL)); cose_crypt_set_rng(getrandom, NULL);