]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
add hkdf to isc_crypto
authorAydın Mercan <aydin@isc.org>
Mon, 2 Feb 2026 17:17:11 +0000 (20:17 +0300)
committerAydın Mercan <aydin@isc.org>
Tue, 14 Jul 2026 13:40:49 +0000 (16:40 +0300)
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

lib/isc/crypto/ossl1_1.c
lib/isc/crypto/ossl3.c
lib/isc/include/isc/crypto.h
meson.build
tests/isc/crypto_test.c [new file with mode: 0644]
tests/isc/meson.build

index c0645bcaa2388d6067af9f4529876432735754b7..3e143b4868f54a5e8877be3220391a557b421cda 100644 (file)
@@ -16,6 +16,7 @@
 #include <openssl/crypto.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
+#include <openssl/kdf.h>
 #include <openssl/rand.h>
 #include <openssl/ssl.h>
 
 #include <isc/safe.h>
 #include <isc/util.h>
 
+#ifdef HAVE_OPENSSL_HKDF_H
+#include <openssl/hkdf.h>
+#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) {
index 35344dc8b1b3547a1ebe9fec37979b02d0b928ba..73db62411f04c7601962bd7e5e0516ffb9df8f9c 100644 (file)
@@ -18,6 +18,7 @@
 #include <openssl/crypto.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
+#include <openssl/kdf.h>
 #include <openssl/provider.h>
 #include <openssl/rand.h>
 #include <openssl/ssl.h>
 #include <isc/safe.h>
 #include <isc/util.h>
 
+#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;
index cd32da93918529efbd26b99bf5f990d61e995018..698b8857269893072de93663228b9d0d288aab60 100644 (file)
 
 #pragma once
 
+#include <stddef.h>
+#include <stdint.h>
+
+#include <isc/md.h>
+#include <isc/region.h>
 #include <isc/types.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);
+/**<
+ * 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);
 /*
index 7eb9ea83264e1a61daf53cb53307caadac0fb28b..c47009030adf40236e10239437cecc103b71dd9e 100644 (file)
@@ -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 (file)
index 0000000..5a6b99e
--- /dev/null
@@ -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 <inttypes.h>
+#include <sched.h>  /* IWYU pragma: keep */
+#include <setjmp.h> /* IWYU pragma: keep */
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define UNIT_TESTING
+#include <cmocka.h>
+
+#include <isc/crypto.h>
+#include <isc/lib.h>
+#include <isc/md.h>
+
+#include <tests/isc.h>
+
+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
index df9575b1aaf602abb534abbf96430b00e55ab52e..12e045a737d768fb51bdf98be9ee1d2d9598c315 100644 (file)
@@ -14,6 +14,7 @@ isc_test = [
     'async',
     'buffer',
     'counter',
+    'crypto',
     'dnsstream_utils',
     'errno',
     'file',