From: Aydın Mercan Date: Mon, 2 Feb 2026 17:17:11 +0000 (+0300) Subject: add hkdf to isc_crypto X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=4092a02f81362e29d8c0bc73e38e562afee827fb;p=thirdparty%2Fbind9.git add hkdf to isc_crypto The HKDF API exposes the extract, expand and extract-expand operations separately to support the use-case of QUIC per RFC 9001 Section 5.2. [1] [1]: https://www.rfc-editor.org/rfc/rfc9001.html#initial-secrets --- diff --git a/lib/isc/crypto/ossl1_1.c b/lib/isc/crypto/ossl1_1.c index c0645bcaa23..3e143b4868f 100644 --- a/lib/isc/crypto/ossl1_1.c +++ b/lib/isc/crypto/ossl1_1.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -30,6 +31,15 @@ #include #include +#ifdef HAVE_OPENSSL_HKDF_H +#include +#endif /* HAVE_OPENSSL_HKDF_H */ + +#define CRYPTO_ERROR(fn) \ + isc__ossl_wrap_logged_toresult( \ + ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO, fn, \ + ISC_R_CRYPTOFAILURE, __FILE__, __LINE__) + #define HMAC_KEY_MAGIC ISC_MAGIC('H', 'M', 'A', 'C') struct isc_hmac_key { @@ -306,6 +316,380 @@ isc__crypto_free_ex(void *ptr, const char *file, int line) { #endif /* !LIBRESSL_VERSION_NUMBER */ +#ifdef HAVE_OPENSSL_HKDF_H + +isc_result_t +isc_crypto_hkdf_extract(isc_region_t out, isc_md_type_t md, + isc_constregion_t secret, isc_constregion_t salt) { + EVP_MD *evp; + size_t len; + + REQUIRE(out.base != NULL && out.length != 0 && + out.length <= EVP_MAX_MD_SIZE); + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(secret.base != NULL && secret.length != 0); + REQUIRE(salt.base != NULL && salt.length != 0); + + evp = isc__crypto_md[md]; + if (evp == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + len = out.length; + if (HKDF_extract(out.base, &len, evp, secret.base, secret.length, + salt.base, salt.length) != 1) + { + return CRYPTO_ERROR("HKDF_extract"); + } + + return ISC_R_SUCCESS; +} + +isc_result_t +isc_crypto_hkdf_expand(isc_region_t out, isc_md_type_t md, + isc_constregion_t prk, isc_constregion_t info) { + EVP_MD *evp; + + REQUIRE(out.base != NULL && out.length != 0); + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(prk.base != NULL && prk.length != 0); + REQUIRE(info.base != NULL && info.length != 0); + + evp = isc__crypto_md[md]; + if (evp == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + if (HKDF_expand(out.base, out.length, evp, prk.base, prk.length, + info.base, info.length) != 1) + { + return CRYPTO_ERROR("HKDF_expand"); + } + + return ISC_R_SUCCESS; +} + +isc_result_t +isc_crypto_hkdf_expand_label(isc_region_t out, isc_md_type_t md, + isc_constregion_t secret, + isc_constregion_t label) { + const uint8_t label_prefix[] = { 't', 'l', 's', '1', '3', ' ' }; + uint8_t hkdf_label[256]; + isc_buffer_t buffer; + EVP_MD *evp; + + REQUIRE(out.base != NULL && out.length != 0 && + out.length <= UINT16_MAX); + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(secret.base != NULL && secret.length != 0); + REQUIRE(label.base != NULL && label.length != 0 && label.length <= 12); + + evp = isc__crypto_md[md]; + if (evp == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + /* + * struct { + * uint16 length = Length; + * opaque label<7..255> = "tls13 " + Label; + * opaque context<0..255> = Context; + * } HkdfLabel; + */ + isc_buffer_init(&buffer, hkdf_label, sizeof(hkdf_label)); + isc_buffer_putuint16(&buffer, out.length); + isc_buffer_putuint8(&buffer, sizeof(label_prefix) + label.length); + isc_buffer_putmem(&buffer, label_prefix, sizeof(label_prefix)); + isc_buffer_putmem(&buffer, label.base, label.length); + isc_buffer_putuint8(&buffer, 0); + + if (HKDF_expand(out.base, out.length, evp, secret.base, secret.length, + isc_buffer_base(&buffer), + isc_buffer_usedlength(&buffer)) != 1) + { + return CRYPTO_ERROR("HKDF_expand"); + } + + return ISC_R_SUCCESS; +} + +isc_result_t +isc_crypto_hkdf(isc_region_t out, isc_md_type_t md, isc_constregion_t secret, + isc_constregion_t salt, isc_constregion_t info) { + EVP_MD *evp; + + REQUIRE(out.base != NULL && out.length != 0); + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(secret.base != NULL && secret.length != 0); + REQUIRE(salt.base != NULL && salt.length != 0); + REQUIRE(info.base != NULL && info.length != 0); + + evp = isc__crypto_md[md]; + if (evp == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + if (HKDF(out.base, out.length, evp, secret.base, secret.length, + salt.base, salt.length, info.base, info.length) != 1) + { + return CRYPTO_ERROR("HKDF"); + } + + return ISC_R_SUCCESS; +} + +#else /* HAVE_OPENSSL_HKDF_H */ + +isc_result_t +isc_crypto_hkdf_extract(isc_region_t out, isc_md_type_t md, + isc_constregion_t secret, isc_constregion_t salt) { + isc_result_t result; + EVP_PKEY_CTX *pctx; + EVP_MD *evp; + size_t len; + + REQUIRE(out.base != NULL && out.length != 0 && + out.length <= EVP_MAX_MD_SIZE); + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(secret.base != NULL && secret.length != 0); + REQUIRE(salt.base != NULL && salt.length != 0); + + evp = isc__crypto_md[md]; + if (evp == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_new_id")); + } + + if (EVP_PKEY_derive_init(pctx) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_derive_init")); + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) != 1) + { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_hkdf_mode")); + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, evp) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set_hkdf_md")); + } + + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt.base, salt.length) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set1_hkdf_salt")); + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret.base, secret.length) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set1_hkdf_key")); + } + + len = out.length; + if (EVP_PKEY_derive(pctx, out.base, &len) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_derive")); + } + + result = ISC_R_SUCCESS; +cleanup: + EVP_PKEY_CTX_free(pctx); + return result; +} + +isc_result_t +isc_crypto_hkdf_expand(isc_region_t out, isc_md_type_t md, + isc_constregion_t prk, isc_constregion_t info) { + isc_result_t result; + EVP_PKEY_CTX *pctx; + EVP_MD *evp; + size_t len; + + REQUIRE(out.base != NULL && out.length != 0 && + out.length <= UINT16_MAX); + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(prk.base != NULL && prk.length != 0); + REQUIRE(info.base != NULL && info.length != 0); + + evp = isc__crypto_md[md]; + if (evp == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_new_id")); + } + + if (EVP_PKEY_derive_init(pctx) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_derive_init")); + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1) + { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_hkdf_mode")); + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, evp) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set_hkdf_md")); + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk.base, prk.length) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set1_hkdf_key")); + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info.base, info.length) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_add1_hkdf_info")); + } + + len = out.length; + if (EVP_PKEY_derive(pctx, out.base, &len) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_derive")); + } + + INSIST(len == out.length); + + result = ISC_R_SUCCESS; +cleanup: + EVP_PKEY_CTX_free(pctx); + return result; +} + +isc_result_t +isc_crypto_hkdf_expand_label(isc_region_t out, isc_md_type_t md, + isc_constregion_t secret, + isc_constregion_t label) { + const uint8_t label_prefix[] = { 't', 'l', 's', '1', '3', ' ' }; + uint8_t hkdf_label[256]; + isc_buffer_t buffer; + isc_result_t result; + EVP_PKEY_CTX *pctx; + EVP_MD *evp; + size_t len; + + REQUIRE(out.base != NULL && out.length != 0 && + out.length <= UINT16_MAX); + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(secret.base != NULL && secret.length != 0); + REQUIRE(secret.base != NULL && secret.length != 0); + REQUIRE(label.base != NULL && label.length != 0 && label.length <= 12); + + evp = isc__crypto_md[md]; + if (evp == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + /* + * struct { + * uint16 length = Length; + * opaque label<7..255> = "tls13 " + Label; + * opaque context<0..255> = Context; + * } HkdfLabel; + */ + isc_buffer_init(&buffer, hkdf_label, sizeof(hkdf_label)); + isc_buffer_putuint16(&buffer, out.length); + isc_buffer_putuint8(&buffer, sizeof(label_prefix) + label.length); + isc_buffer_putmem(&buffer, label_prefix, sizeof(label_prefix)); + isc_buffer_putmem(&buffer, label.base, label.length); + isc_buffer_putuint8(&buffer, 0); + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_new_id")); + } + + if (EVP_PKEY_derive_init(pctx) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_derive_init")); + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) != 1) + { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_hkdf_mode")); + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, evp) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set_hkdf_md")); + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret.base, secret.length) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set1_hkdf_key")); + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, isc_buffer_base(&buffer), + isc_buffer_usedlength(&buffer)) != 1) + { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_add1_hkdf_info")); + } + + len = out.length; + if (EVP_PKEY_derive(pctx, out.base, &len) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_derive")); + } + + INSIST(len == out.length); + + result = ISC_R_SUCCESS; +cleanup: + EVP_PKEY_CTX_free(pctx); + return result; +} + +isc_result_t +isc_crypto_hkdf(isc_region_t out, isc_md_type_t md, isc_constregion_t secret, + isc_constregion_t salt, isc_constregion_t info) { + isc_result_t result; + EVP_PKEY_CTX *pctx; + EVP_MD *evp; + size_t len; + + REQUIRE(out.base != NULL && out.length != 0); + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(secret.base != NULL && secret.length != 0); + REQUIRE(salt.base != NULL && salt.length != 0); + REQUIRE(info.base != NULL && info.length != 0); + + evp = isc__crypto_md[md]; + if (evp == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_new_id")); + } + + if (EVP_PKEY_derive_init(pctx) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_derive_init")); + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, evp) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set_hkdf_md")); + } + + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt.base, salt.length) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set1_hkdf_salt")); + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret.base, secret.length) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_set1_hkdf_key")); + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info.base, info.length) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_CTX_add1_hkdf_info")); + } + + len = out.length; + if (EVP_PKEY_derive(pctx, out.base, &len) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_PKEY_derive")); + } + + result = ISC_R_SUCCESS; +cleanup: + EVP_PKEY_CTX_free(pctx); + return result; +} + +#endif /* HAVE_OPENSSL_HKDF_H */ + #ifdef HAVE_FIPS_MODE bool isc_crypto_fips_mode(void) { diff --git a/lib/isc/crypto/ossl3.c b/lib/isc/crypto/ossl3.c index 35344dc8b1b..73db62411f0 100644 --- a/lib/isc/crypto/ossl3.c +++ b/lib/isc/crypto/ossl3.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,11 @@ #include #include +#define CRYPTO_ERROR(fn) \ + isc__ossl_wrap_logged_toresult( \ + ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_CRYPTO, fn, \ + ISC_R_CRYPTOFAILURE, __FILE__, __LINE__) + struct isc_hmac_key { uint32_t magic; uint32_t len; @@ -46,8 +52,25 @@ constexpr uint32_t hmac_key_magic = ISC_MAGIC('H', 'M', 'A', 'C'); static OSSL_PROVIDER *base = NULL, *fips = NULL; +/* + * Because HKDF-Expand-Label is defined in the RFC of TLS 1.3, OpenSSL + * has named the algorithm as TLS1.3 KDF internally. + */ +static EVP_KDF *evp_tls_1_3_kdf = NULL; +static EVP_KDF *evp_hkdf = NULL; + static EVP_MAC *evp_hmac = NULL; +static isc_constregion_t md_to_name[ISC_MD_MAX] = { + [ISC_MD_UNKNOWN] = { NULL, 0 }, + [ISC_MD_MD5] = { "MD5", sizeof("MD5") - 1 }, + [ISC_MD_SHA1] = { "SHA1", sizeof("SHA1") - 1 }, + [ISC_MD_SHA224] = { "SHA2-224", sizeof("SHA2-224") - 1 }, + [ISC_MD_SHA256] = { "SHA2-256", sizeof("SHA2-256") - 1 }, + [ISC_MD_SHA384] = { "SHA2-384", sizeof("SHA2-384") - 1 }, + [ISC_MD_SHA512] = { "SHA2-512", sizeof("SHA2-512") - 1 }, +}; + static OSSL_PARAM md_to_hmac_params[ISC_MD_MAX][2] = { [ISC_MD_UNKNOWN] = { OSSL_PARAM_END }, [ISC_MD_MD5] = { @@ -106,11 +129,35 @@ register_algorithms(void) { "EVP_MAC-HMAC implementation"); } + evp_tls_1_3_kdf = EVP_KDF_fetch(NULL, OSSL_KDF_NAME_TLS1_3_KDF, NULL); + if (evp_tls_1_3_kdf == NULL) { + FATAL_ERROR( + "OpenSSL failed to find an TLS 1.3 KDF implementation." + "Please make sure the default provider has an " + "EVP_KDF-TLS13_KDF implementation"); + } + + evp_hkdf = EVP_KDF_fetch(NULL, OSSL_KDF_NAME_HKDF, NULL); + if (evp_hkdf == NULL) { + ERR_clear_error(); + FATAL_ERROR("OpenSSL failed to find an HKDF implementation. " + "Please make sure the default provider has an " + "EVP_KDF-HKDF implementation"); + } + return ISC_R_SUCCESS; } static void unregister_algorithms(void) { + INSIST(evp_hkdf != NULL); + EVP_KDF_free(evp_hkdf); + evp_hkdf = NULL; + + INSIST(evp_tls_1_3_kdf != NULL); + EVP_KDF_free(evp_tls_1_3_kdf); + evp_tls_1_3_kdf = NULL; + for (size_t i = 0; i < ISC_MD_MAX; i++) { if (isc__crypto_md[i] != NULL) { EVP_MD_free(isc__crypto_md[i]); @@ -318,6 +365,186 @@ isc_hmac_final(isc_hmac_t *hmac, isc_buffer_t *out) { return ISC_R_SUCCESS; } +isc_result_t +isc_crypto_hkdf_extract(isc_region_t out, isc_md_type_t md, + isc_constregion_t secret, isc_constregion_t salt) { + isc_result_t result; + EVP_KDF_CTX *ctx; + int mode = EVP_KDF_HKDF_MODE_EXTRACT_ONLY; + + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(out.base != NULL && out.length != 0); + REQUIRE(secret.base != NULL && secret.length != 0); + REQUIRE(salt.base != NULL && salt.length != 0); + + if (isc__crypto_md[md] == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + const OSSL_PARAM params[] = { + OSSL_PARAM_int(OSSL_KDF_PARAM_MODE, &mode), + OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, + UNCONST(md_to_name[md].base), + md_to_name[md].length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_KEY, + UNCONST(secret.base), secret.length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, UNCONST(salt.base), + salt.length), + OSSL_PARAM_END, + }; + + ctx = EVP_KDF_CTX_new(evp_hkdf); + if (ctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_KDF_CTX_new")); + } + + if (EVP_KDF_derive(ctx, out.base, out.length, params) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_KDF_derive")); + } + + result = ISC_R_SUCCESS; +cleanup: + EVP_KDF_CTX_free(ctx); + return result; +} + +isc_result_t +isc_crypto_hkdf_expand(isc_region_t out, isc_md_type_t md, + isc_constregion_t prk, isc_constregion_t info) { + isc_result_t result; + EVP_KDF_CTX *ctx; + int mode = EVP_KDF_HKDF_MODE_EXPAND_ONLY; + + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(out.base != NULL && out.length != 0); + REQUIRE(prk.base != NULL && prk.length != 0); + REQUIRE(info.base != NULL && info.length != 0); + + if (isc__crypto_md[md] == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + const OSSL_PARAM params[] = { + OSSL_PARAM_int(OSSL_KDF_PARAM_MODE, &mode), + OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, + UNCONST(md_to_name[md].base), + md_to_name[md].length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_KEY, UNCONST(prk.base), + prk.length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_INFO, UNCONST(info.base), + info.length), + OSSL_PARAM_END, + }; + + ctx = EVP_KDF_CTX_new(evp_hkdf); + if (ctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_KDF_CTX_new")); + } + + if (EVP_KDF_derive(ctx, out.base, out.length, params) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_KDF_derive")); + } + + result = ISC_R_SUCCESS; +cleanup: + EVP_KDF_CTX_free(ctx); + return result; +} + +isc_result_t +isc_crypto_hkdf_expand_label(isc_region_t out, isc_md_type_t md, + isc_constregion_t secret, + isc_constregion_t label) { + isc_result_t result; + EVP_KDF_CTX *ctx; + int mode = EVP_PKEY_HKDEF_MODE_EXPAND_ONLY; + + REQUIRE(out.base != NULL && out.length != 0); + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(secret.base != NULL && secret.length != 0); + REQUIRE(label.base != NULL && label.length != 0); + + if (isc__crypto_md[md] == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + const OSSL_PARAM params[] = { + OSSL_PARAM_int(OSSL_KDF_PARAM_MODE, &mode), + OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, + UNCONST(md_to_name[md].base), + md_to_name[md].length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_PREFIX, + UNCONST("tls13 "), + sizeof("tls13 ") - 1), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_KEY, + UNCONST(secret.base), secret.length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_LABEL, + UNCONST(label.base), label.length), + OSSL_PARAM_END, + }; + + /* Please see the comment in `evp_tls_1_3_kdf` */ + ctx = EVP_KDF_CTX_new(evp_tls_1_3_kdf); + if (ctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_KDF_CTX_new")); + } + + if (EVP_KDF_derive(ctx, out.base, out.length, params) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_KDF_derive")); + } + + result = ISC_R_SUCCESS; +cleanup: + EVP_KDF_CTX_free(ctx); + return result; +} + +isc_result_t +isc_crypto_hkdf(isc_region_t out, isc_md_type_t md, isc_constregion_t ikm, + isc_constregion_t salt, isc_constregion_t info) { + isc_result_t result; + EVP_KDF_CTX *ctx; + int mode = EVP_KDF_HKDF_MODE_EXTRACT_AND_EXPAND; + + REQUIRE(md != ISC_MD_UNKNOWN && md < ISC_MD_MAX); + REQUIRE(out.base != NULL && out.length != 0); + REQUIRE(ikm.base != NULL && ikm.length != 0); + REQUIRE(salt.base != NULL && salt.length != 0); + REQUIRE(info.base != NULL && info.length != 0); + + if (isc__crypto_md[md] == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + const OSSL_PARAM params[] = { + OSSL_PARAM_int(OSSL_KDF_PARAM_MODE, &mode), + OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, + UNCONST(md_to_name[md].base), + md_to_name[md].length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_KEY, UNCONST(ikm.base), + ikm.length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_INFO, UNCONST(info.base), + info.length), + OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, UNCONST(salt.base), + salt.length), + OSSL_PARAM_END, + }; + + ctx = EVP_KDF_CTX_new(evp_hkdf); + if (ctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_KDF_CTX_new")); + } + + if (EVP_KDF_derive(ctx, out.base, out.length, params) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_KDF_derive")); + } + + result = ISC_R_SUCCESS; +cleanup: + EVP_KDF_CTX_free(ctx); + return result; +} + bool isc_crypto_fips_mode(void) { return EVP_default_properties_is_fips_enabled(NULL) != 0; diff --git a/lib/isc/include/isc/crypto.h b/lib/isc/include/isc/crypto.h index cd32da93918..698b8857269 100644 --- a/lib/isc/include/isc/crypto.h +++ b/lib/isc/include/isc/crypto.h @@ -13,8 +13,89 @@ #pragma once +#include +#include + +#include +#include #include +isc_result_t +isc_crypto_hkdf_extract(isc_region_t out, isc_md_type_t md, + isc_constregion_t secret, isc_constregion_t salt); +/**< + * HKDF-Extract as specified by RFC5869. + * + * Requires: + * - `out.base != NULL` and `out.length != 0` + * - `md` must be a valid hash type. + * - `secret.base != NULL` and `secret.length != 0` + * - `salt.base != NULL` and `sakt.length != 0` + * + * \retval ISC_R_SUCCESS on success + * \retval ISC_R_NOTIMPLEMENTED if the hash function is not supported + * \retval ISC_R_CRYPTOFAILURE on libcrypto failure + */ + +isc_result_t +isc_crypto_hkdf_expand(isc_region_t out, isc_md_type_t md, + isc_constregion_t prk, isc_constregion_t info); +/**< + * HKDF-Expand as specified by RFC5869. + * + * Please note that `prk` can't be just substituted with the output of any + * secret function. Please refer to RFC5869 Section 3.3 for more information on + * what key values are appropriate for calling HKDF-Expand. + * + * Requires: + * - `out.base != NULL` and `out.length != 0` + * - `md` must be a valid hash type. + * - `prk.base != NULL` and `prk.length != 0` + * - `info.base != NULL` and `info.length != 0` + * + * \retval ISC_R_SUCCESS on success + * \retval ISC_R_NOTIMPLEMENTED if the hash function is not supported + * \retval ISC_R_CRYPTOFAILURE on libcrypto failure + */ + +isc_result_t +isc_crypto_hkdf_expand_label(isc_region_t out, isc_md_type_t md, + isc_constregion_t secret, isc_constregion_t label); +/**< + * \brief + * HKDF-Expand-Label as specified by RFC 8446 Section 7.1. + * The context parameter is not supported and is passed as empty. + * + * Requires: + * - `out.base != NULL` and `out.length != 0` + * - `md` must be a valid hash type. + * - `secret.base != NULL` and `secret.length != 0` + * - `label.base != NULL` and `label.length != 0` + * + * \retval ISC_R_SUCCESS on success + * \retval ISC_R_NOTIMPLEMENTED if the hash function is not supported + * \retval ISC_R_CRYPTOFAILURE on libcrypto failure + */ + +isc_result_t +isc_crypto_hkdf(isc_region_t out, isc_md_type_t md, isc_constregion_t secret, + isc_constregion_t salt, isc_constregion_t info); +/**< + * \brief + * HKDF-Extract-Expand as specified by RFC5869. + * + * Requires: + * - `out.base != NULL` and `out.length != 0` + * - `md` must be a valid hash type. + * - `ikm.base != NULL` and `ikm.length != 0` + * - `salt.base != NULL` and `ikm.length != 0` + * - `info.base != NULL` and `info.length != 0` + * + * \retval ISC_R_SUCCESS on success + * \retval ISC_R_NOTIMPLEMENTED if the hash function is not supported + * \retval ISC_R_CRYPTOFAILURE on libcrypto failure + */ + bool isc_crypto_fips_mode(void); /* diff --git a/meson.build b/meson.build index 7eb9ea83264..c47009030ad 100644 --- a/meson.build +++ b/meson.build @@ -676,6 +676,11 @@ foreach fn, header : { ) endforeach +config.set( + 'HAVE_OPENSSL_HKDF_H', + cc.has_header('openssl/hkdf.h', dependencies: openssl_dep), +) + ## libuv uv_dep = dependency('libuv', version: '>=1.34.0') if uv_dep.version().version_compare('<1.40.0') diff --git a/tests/isc/crypto_test.c b/tests/isc/crypto_test.c new file mode 100644 index 00000000000..5a6b99ed965 --- /dev/null +++ b/tests/isc/crypto_test.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#include +#include /* IWYU pragma: keep */ +#include /* IWYU pragma: keep */ +#include +#include +#include +#include + +#define UNIT_TESTING +#include + +#include +#include +#include + +#include + +ISC_RUN_TEST_IMPL(hkdf) { + isc_result_t result; + uint8_t actual[42]; + + const uint8_t ikm[22] = { [0 ... 21] = 0x0B }; + const uint8_t salt[13] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + }; + const uint8_t info[10] = { + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, + }; + const uint8_t expected[42] = { + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, + 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, 0x2d, 0x2d, + 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, + 0x56, 0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, + 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65, + }; + + result = isc_crypto_hkdf((isc_region_t){ actual, sizeof(actual) }, + ISC_MD_SHA256, + (isc_constregion_t){ ikm, sizeof(ikm) }, + (isc_constregion_t){ salt, sizeof(salt) }, + (isc_constregion_t){ info, sizeof(info) }); + assert_int_equal(result, ISC_R_SUCCESS); + + assert_memory_equal(expected, actual, 42); +} + +ISC_RUN_TEST_IMPL(hkdf_expand_label) { + isc_result_t result; + uint8_t actual[32]; + + /* + * Values are taken from RFC 9001 Appendix A.1. + */ + const uint8_t secret[32] = { + 0x7d, 0xb5, 0xdf, 0x06, 0xe7, 0xa6, 0x9e, 0x43, + 0x24, 0x96, 0xad, 0xed, 0xb0, 0x08, 0x51, 0x92, + 0x35, 0x95, 0x22, 0x15, 0x96, 0xae, 0x2a, 0xe9, + 0xfb, 0x81, 0x15, 0xc1, 0xe9, 0xed, 0x0a, 0x44, + }; + + const uint8_t expected[32] = { + 0xc0, 0x0c, 0xf1, 0x51, 0xca, 0x5b, 0xe0, 0x75, + 0xed, 0x0e, 0xbf, 0xb5, 0xc8, 0x03, 0x23, 0xc4, + 0x2d, 0x6b, 0x7d, 0xb6, 0x78, 0x81, 0x28, 0x9a, + 0xf4, 0x00, 0x8f, 0x1f, 0x6c, 0x35, 0x7a, 0xea, + }; + + result = isc_crypto_hkdf_expand_label( + (isc_region_t){ actual, sizeof(actual) }, ISC_MD_SHA256, + (isc_constregion_t){ secret, sizeof(secret) }, + (isc_constregion_t){ "client in", sizeof("client in") - 1 }); + assert_int_equal(result, ISC_R_SUCCESS); + assert_memory_equal(expected, actual, 32); +} + +ISC_TEST_LIST_START +ISC_TEST_ENTRY(hkdf) +ISC_TEST_ENTRY(hkdf_expand_label) +ISC_TEST_LIST_END + +ISC_TEST_MAIN diff --git a/tests/isc/meson.build b/tests/isc/meson.build index df9575b1aaf..12e045a737d 100644 --- a/tests/isc/meson.build +++ b/tests/isc/meson.build @@ -14,6 +14,7 @@ isc_test = [ 'async', 'buffer', 'counter', + 'crypto', 'dnsstream_utils', 'errno', 'file',