From: Aydın Mercan Date: Mon, 9 Feb 2026 05:03:02 +0000 (+0300) Subject: add aead api to isc_crypto X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=80ac5dfda1a5504d5600611e07d9bf495d26f233;p=thirdparty%2Fbind9.git add aead api to isc_crypto The new AEAD API exists to cather to the needs for QUIC but is still usable in other future contexts. Only AES-128-GCM, AES-256-GCM and ChaCha20-Poly1305 are supported. AES-128-CCM is intentionally skipped as the algorithm is neither encountered in the wild nor has any useful advantages comapred to the more popular AES-128-GCM mode. --- diff --git a/lib/isc/crypto/ossl1_1.c b/lib/isc/crypto/ossl1_1.c index 3e143b4868f..31d344b9961 100644 --- a/lib/isc/crypto/ossl1_1.c +++ b/lib/isc/crypto/ossl1_1.c @@ -28,9 +28,15 @@ #include #include #include +#include +#include #include #include +#ifdef HAVE_OPENSSL_AEAD_H +#include +#endif /* HAVE_OPENSSL_AEAD_H */ + #ifdef HAVE_OPENSSL_HKDF_H #include #endif /* HAVE_OPENSSL_HKDF_H */ @@ -50,6 +56,14 @@ struct isc_hmac_key { uint8_t secret[]; }; +#ifdef HAVE_EVP_AEAD_CTX_NEW +STATIC_ASSERT(ISC_TYPES_COMPATIBLE(isc_crypto_aead_t, EVP_AEAD_CTX), + "isc_crypto_aead_t is not compatible with EVP_AEAD_CTX"); +#else /* HAVE_EVP_AEAD_CTX_NEW */ +STATIC_ASSERT(ISC_TYPES_COMPATIBLE(isc_crypto_aead_t, EVP_CIPHER_CTX), + "isc_crypto_aead_t is not compatible with EVP_CIPHER_CTX"); +#endif /* HAVE_EVP_AEAD_CTX_NEW */ + static isc_mem_t *isc__crypto_mctx = NULL; #define md_register_algorithm(alg, upperalg) \ @@ -316,6 +330,316 @@ isc__crypto_free_ex(void *ptr, const char *file, int line) { #endif /* !LIBRESSL_VERSION_NUMBER */ +#ifdef HAVE_EVP_AEAD_CTX_NEW + +void +isc_crypto_aead_destroy(isc_crypto_aead_t **aeadp) { + EVP_AEAD_CTX *ctx; + + REQUIRE(aeadp != NULL && *aeadp != NULL); + + ctx = MOVE_OWNERSHIP(*aeadp); + EVP_AEAD_CTX_free(ctx); +} + +isc_result_t +isc_crypto_aead_create(isc_crypto_aead_algorithm_t algorithm, + isc_constregion_t key, + isc_crypto_aead_direction_t direction ISC_ATTR_UNUSED, + isc_crypto_aead_t **aeadp) { + const EVP_AEAD *evp; + EVP_AEAD_CTX *ctx; + + REQUIRE(aeadp != NULL && *aeadp == NULL); + REQUIRE(key.base != NULL); + + switch (algorithm) { + case ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM: + evp = EVP_aead_aes_128_gcm(); + break; + case ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM: + evp = EVP_aead_aes_256_gcm(); + break; + case ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305: + evp = EVP_aead_chacha20_poly1305(); + break; + default: + UNREACHABLE(); + } + + /* + * LibreSSL's EVP_AEAD_CTX_new is *just slightly* different than the + * BoringSSL version. + */ +#ifdef LIBRESSL_VERSION_NUMBER + ctx = EVP_AEAD_CTX_new(); + if (ctx == NULL) { + return CRYPTO_ERROR("EVP_AEAD_CTX_new"); + } + + if (EVP_AEAD_CTX_init(ctx, evp, key.base, key.length, 0, NULL) != 1) { + EVP_AEAD_CTX_free(ctx); + return CRYPTO_ERROR("EVP_AEAD_CTX_init"); + } +#else /* LIBRESSL_VERSION_NUMBER */ + ctx = EVP_AEAD_CTX_new(evp, key.base, key.length, 0); + if (ctx == NULL) { + return CRYPTO_ERROR("EVP_AEAD_CTX_new"); + } +#endif /* LIBRESSL_VERSION_NUMBER */ + + *aeadp = MOVE_OWNERSHIP(ctx); + + return ISC_R_SUCCESS; +} + +isc_result_t +isc_crypto_aead_seal(isc_crypto_aead_t *aead, isc_constregion_t nonce, + isc_constregion_t plaintext, isc_region_t out, + size_t *out_sealed_len, + isc_constregion_t additional_data) { + isc_result_t result; + size_t len = out.length; + + REQUIRE(aead != NULL); + + ERR_set_mark(); + + if (EVP_AEAD_CTX_seal(aead, out.base, &len, out.length, nonce.base, + nonce.length, plaintext.base, plaintext.length, + additional_data.base, + additional_data.length) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + *out_sealed_len = len; + + result = ISC_R_SUCCESS; +cleanup: + ERR_pop_to_mark(); + return result; +} + +isc_result_t +isc_crypto_aead_open(isc_crypto_aead_t *aead, isc_constregion_t nonce, + isc_constregion_t ciphertext, isc_region_t out, + size_t *out_opened_len, + isc_constregion_t additional_data) { + isc_result_t result; + size_t len; + + REQUIRE(aead != NULL); + + ERR_set_mark(); + + if (EVP_AEAD_CTX_open(aead, out.base, &len, out.length, nonce.base, + nonce.length, ciphertext.base, ciphertext.length, + additional_data.base, + additional_data.length) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + *out_opened_len = len; + + result = ISC_R_SUCCESS; +cleanup: + ERR_pop_to_mark(); + return result; +} + +#else /* HAVE_EVP_AEAD_CTX_NEW */ + +void +isc_crypto_aead_destroy(isc_crypto_aead_t **aeadp) { + EVP_CIPHER_CTX *ctx; + + REQUIRE(aeadp != NULL && *aeadp != NULL); + + ctx = MOVE_OWNERSHIP(*aeadp); + + EVP_CIPHER_CTX_free(ctx); +} + +isc_result_t +isc_crypto_aead_create(isc_crypto_aead_algorithm_t algorithm, + isc_constregion_t key, + isc_crypto_aead_direction_t direction, + isc_crypto_aead_t **aeadp) { + EVP_CIPHER_CTX *ctx; + isc_result_t result; + const EVP_CIPHER *evp; + int dir; + + REQUIRE(aeadp != NULL && *aeadp == NULL); + REQUIRE(key.base != NULL); + + switch (algorithm) { + case ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM: + evp = EVP_aes_128_gcm(); + break; + case ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM: + evp = EVP_aes_256_gcm(); + break; + case ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305: + evp = EVP_chacha20_poly1305(); + break; + default: + UNREACHABLE(); + } + + switch (direction) { + case ISC_CRYPTO_AEAD_DIRECTION_SEAL: + dir = 1; + break; + case ISC_CRYPTO_AEAD_DIRECTION_OPEN: + dir = 0; + break; + default: + UNREACHABLE(); + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_CIPHER_CTX_new")); + } + + if (EVP_CipherInit_ex(ctx, evp, NULL, key.base, NULL, dir) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_CipherInit_ex")); + } + + *aeadp = MOVE_OWNERSHIP(ctx); + + result = ISC_R_SUCCESS; +cleanup: + EVP_CIPHER_CTX_free(ctx); + return result; +} + +isc_result_t +isc_crypto_aead_seal(isc_crypto_aead_t *aead, isc_constregion_t nonce, + isc_constregion_t plaintext, isc_region_t out, + size_t *out_sealed_len, + isc_constregion_t additional_data) { + isc_result_t result; + size_t sealed; + int len; + + REQUIRE(aead != NULL); + REQUIRE(nonce.base != NULL); + REQUIRE(out.base != NULL && plaintext.base != NULL); + REQUIRE(out.length == + ISC_CHECKED_ADD(plaintext.length, isc_crypto_aead_tag_length)); + REQUIRE(out_sealed_len != NULL); + + ERR_set_mark(); + + if (EVP_CipherInit_ex(aead, NULL, NULL, NULL, nonce.base, 1) != 1) { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + if (additional_data.base != NULL) { + INSIST(additional_data.length != 0); + if (EVP_EncryptUpdate(aead, NULL, &len, additional_data.base, + additional_data.length) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + } + + len = out.length; + if (EVP_EncryptUpdate(aead, out.base, &len, plaintext.base, + plaintext.length) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + sealed = len + isc_crypto_aead_tag_length; + + out.base += len; + len = out.length - len; + if (EVP_EncryptFinal_ex(aead, out.base, &len) != 1) { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_AEAD_GET_TAG, + isc_crypto_aead_tag_length, + out.base + len) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + *out_sealed_len = sealed; + result = ISC_R_SUCCESS; +cleanup: + ERR_pop_to_mark(); + return result; +} + +isc_result_t +isc_crypto_aead_open(isc_crypto_aead_t *aead, isc_constregion_t nonce, + isc_constregion_t ciphertext, isc_region_t out, + size_t *out_opened_len, + isc_constregion_t additional_data) { + isc_result_t result; + const uint8_t *ct; + size_t opened; + int len; + + REQUIRE(aead != NULL); + REQUIRE(nonce.base != NULL); + REQUIRE(out.base != NULL && ciphertext.base != NULL); + REQUIRE(out.length == + ISC_CHECKED_SUB(ciphertext.length, isc_crypto_aead_tag_length)); + REQUIRE(out_opened_len != NULL); + + ct = ciphertext.base; + + ERR_set_mark(); + + if (EVP_CipherInit_ex(aead, NULL, NULL, NULL, nonce.base, 0) != 1) { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + if (EVP_CIPHER_CTX_ctrl(aead, EVP_CTRL_AEAD_SET_TAG, + isc_crypto_aead_tag_length, + UNCONST(ct + out.length)) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + if (additional_data.base != NULL) { + INSIST(additional_data.length != 0); + if (EVP_DecryptUpdate(aead, NULL, &len, additional_data.base, + additional_data.length) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + } + + len = out.length; + if (EVP_DecryptUpdate(aead, out.base, &len, ciphertext.base, + ciphertext.length) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + opened = len; + out.base += len; + len = out.length - len; + if (EVP_DecryptFinal_ex(aead, out.base, &len) != 1) { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + *out_opened_len = opened + len; + result = ISC_R_SUCCESS; +cleanup: + ERR_pop_to_mark(); + return result; +} +#endif /* HAVE_EVP_AEAD_CTX_NEW */ + #ifdef HAVE_OPENSSL_HKDF_H isc_result_t diff --git a/lib/isc/crypto/ossl3.c b/lib/isc/crypto/ossl3.c index 73db62411f0..0ce87524833 100644 --- a/lib/isc/crypto/ossl3.c +++ b/lib/isc/crypto/ossl3.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,9 @@ struct isc_hmac_key { uint8_t secret[]; }; +STATIC_ASSERT(ISC_TYPES_COMPATIBLE(isc_crypto_aead_t, EVP_CIPHER_CTX), + "isc_crypto_aead_t is not compatible with EVP_CIPHER_CTX"); + constexpr uint32_t hmac_key_magic = ISC_MAGIC('H', 'M', 'A', 'C'); static OSSL_PROVIDER *base = NULL, *fips = NULL; @@ -61,6 +65,11 @@ static EVP_KDF *evp_hkdf = NULL; static EVP_MAC *evp_hmac = NULL; +/* AEAD */ +static EVP_CIPHER *evp_aes_128_gcm = NULL; +static EVP_CIPHER *evp_aes_256_gcm = NULL; +static EVP_CIPHER *evp_chacha20poly1305 = NULL; + static isc_constregion_t md_to_name[ISC_MD_MAX] = { [ISC_MD_UNKNOWN] = { NULL, 0 }, [ISC_MD_MD5] = { "MD5", sizeof("MD5") - 1 }, @@ -112,6 +121,10 @@ static isc_result_t register_algorithms(void) { if (!isc_crypto_fips_mode()) { md_register_algorithm(MD5); + + INSIST(evp_chacha20poly1305 == NULL); + evp_chacha20poly1305 = + EVP_CIPHER_fetch(NULL, "ChaCha20-Poly1305", NULL); } md_register_algorithm(SHA1); @@ -120,6 +133,12 @@ register_algorithms(void) { md_register_algorithm(SHA384); md_register_algorithm(SHA512); + INSIST(evp_aes_128_gcm == NULL); + evp_aes_128_gcm = EVP_CIPHER_fetch(NULL, "AES-128-GCM", NULL); + + INSIST(evp_aes_256_gcm == NULL); + evp_aes_256_gcm = EVP_CIPHER_fetch(NULL, "AES-256-GCM", NULL); + /* We _must_ have HMAC */ evp_hmac = EVP_MAC_fetch(NULL, "HMAC", NULL); if (evp_hmac == NULL) { @@ -150,6 +169,8 @@ register_algorithms(void) { static void unregister_algorithms(void) { + size_t i; + INSIST(evp_hkdf != NULL); EVP_KDF_free(evp_hkdf); evp_hkdf = NULL; @@ -158,16 +179,25 @@ unregister_algorithms(void) { EVP_KDF_free(evp_tls_1_3_kdf); evp_tls_1_3_kdf = NULL; - for (size_t i = 0; i < ISC_MD_MAX; i++) { + INSIST(evp_hmac != NULL); + EVP_MAC_free(evp_hmac); + evp_hmac = NULL; + + EVP_CIPHER_free(evp_chacha20poly1305); + evp_chacha20poly1305 = NULL; + + EVP_CIPHER_free(evp_aes_256_gcm); + evp_aes_256_gcm = NULL; + + EVP_CIPHER_free(evp_aes_128_gcm); + evp_aes_128_gcm = NULL; + + for (i = 0; i < ISC_MD_MAX; i++) { if (isc__crypto_md[i] != NULL) { EVP_MD_free(isc__crypto_md[i]); isc__crypto_md[i] = NULL; } } - - INSIST(evp_hmac != NULL); - EVP_MAC_free(evp_hmac); - evp_hmac = NULL; } #undef md_register_algorithm @@ -365,6 +395,207 @@ isc_hmac_final(isc_hmac_t *hmac, isc_buffer_t *out) { return ISC_R_SUCCESS; } +void +isc_crypto_aead_destroy(isc_crypto_aead_t **aeadp) { + EVP_CIPHER_CTX *ctx; + + REQUIRE(aeadp != NULL && *aeadp != NULL); + + ctx = MOVE_OWNERSHIP(*aeadp); + + EVP_CIPHER_CTX_free(ctx); +} + +isc_result_t +isc_crypto_aead_create(isc_crypto_aead_algorithm_t algorithm, + isc_constregion_t key, + isc_crypto_aead_direction_t direction, + isc_crypto_aead_t **aeadp) { + EVP_CIPHER_CTX *ctx; + isc_result_t result; + EVP_CIPHER *evp; + size_t nonce; + int dir; + + const OSSL_PARAM params[] = { + OSSL_PARAM_size_t(OSSL_CIPHER_PARAM_IVLEN, &nonce), + OSSL_PARAM_END, + }; + + REQUIRE(key.base != NULL); + REQUIRE(aeadp != NULL && *aeadp == NULL); + + switch (direction) { + case ISC_CRYPTO_AEAD_DIRECTION_SEAL: + dir = 1; + break; + case ISC_CRYPTO_AEAD_DIRECTION_OPEN: + dir = 0; + break; + default: + UNREACHABLE(); + } + + switch (algorithm) { + case ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM: + nonce = isc_crypto_aes128gcm_nonce_length; + evp = evp_aes_128_gcm; + break; + case ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM: + nonce = isc_crypto_aes256gcm_nonce_length; + evp = evp_aes_256_gcm; + break; + case ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305: + nonce = isc_crypto_chacha20poly1305_nonce_length; + evp = evp_chacha20poly1305; + break; + default: + UNREACHABLE(); + }; + + if (evp == NULL) { + return ISC_R_NOTIMPLEMENTED; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + CLEANUP(CRYPTO_ERROR("EVP_CIPHER_CTX_new")); + } + + if (EVP_CipherInit_ex2(ctx, evp, key.base, NULL, dir, params) != 1) { + CLEANUP(CRYPTO_ERROR("EVP_CipherInit_ex2")); + } + + *aeadp = MOVE_OWNERSHIP(ctx); + + result = ISC_R_SUCCESS; +cleanup: + EVP_CIPHER_CTX_free(ctx); + return result; +} + +isc_result_t +isc_crypto_aead_seal(isc_crypto_aead_t *aead, isc_constregion_t nonce, + isc_constregion_t plaintext, isc_region_t out, + size_t *out_sealed_len, + isc_constregion_t additional_data) { + isc_result_t result; + size_t sealed; + int len; + + REQUIRE(aead != NULL); + REQUIRE(nonce.base != NULL); + REQUIRE(out.base != NULL && plaintext.base != NULL); + REQUIRE(out.length == + ISC_CHECKED_ADD(plaintext.length, isc_crypto_aead_tag_length)); + REQUIRE(out_sealed_len != NULL); + + OSSL_PARAM params[] = { + OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, + out.base + plaintext.length, + isc_crypto_aead_tag_length), + OSSL_PARAM_END, + }; + + ERR_set_mark(); + + if (EVP_EncryptInit_ex(aead, NULL, NULL, NULL, nonce.base) != 1) { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + if (EVP_EncryptUpdate(aead, NULL, &len, additional_data.base, + additional_data.length) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + len = out.length; + if (EVP_EncryptUpdate(aead, out.base, &len, plaintext.base, + plaintext.length) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + sealed = len + isc_crypto_aead_tag_length; + + out.base += len; + len = out.length - len; + if (EVP_EncryptFinal_ex(aead, out.base, &len) != 1) { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + if (EVP_CIPHER_CTX_get_params(aead, params) != 1) { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + *out_sealed_len = sealed + len; + + result = ISC_R_SUCCESS; +cleanup: + ERR_pop_to_mark(); + return result; +} + +isc_result_t +isc_crypto_aead_open(isc_crypto_aead_t *aead, isc_constregion_t nonce, + isc_constregion_t ciphertext, isc_region_t out, + size_t *out_opened_len, + isc_constregion_t additional_data) { + isc_result_t result; + const uint8_t *ct; + size_t opened; + int len = 0; + + uint8_t *k = NULL; + + REQUIRE(aead != NULL); + REQUIRE(nonce.base != NULL); + REQUIRE(out.base != NULL && ciphertext.base != NULL); + REQUIRE(out.length == + ISC_CHECKED_SUB(ciphertext.length, isc_crypto_aead_tag_length)); + REQUIRE(out_opened_len != NULL); + + ct = ciphertext.base; + const OSSL_PARAM params[] = { + OSSL_PARAM_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG, + UNCONST(ct + out.length), + isc_crypto_aead_tag_length), + OSSL_PARAM_END, + }; + + ERR_set_mark(); + + if (EVP_DecryptInit_ex2(aead, NULL, k, nonce.base, params) != 1) { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + if (EVP_DecryptUpdate(aead, NULL, &len, additional_data.base, + additional_data.length) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + len = out.length; + if (EVP_DecryptUpdate(aead, out.base, &len, ciphertext.base, len) != 1) + { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + opened = len; + + out.base += len; + len = out.length - len; + if (EVP_DecryptFinal_ex(aead, out.base, &len) != 1) { + CLEANUP(ISC_R_CRYPTOFAILURE); + } + + *out_opened_len = opened; + result = ISC_R_SUCCESS; +cleanup: + ERR_pop_to_mark(); + return result; +} + isc_result_t isc_crypto_hkdf_extract(isc_region_t out, isc_md_type_t md, isc_constregion_t secret, isc_constregion_t salt) { diff --git a/lib/isc/crypto/ossl_common.c b/lib/isc/crypto/ossl_common.c index 8b5d56925b4..965bbb7faba 100644 --- a/lib/isc/crypto/ossl_common.c +++ b/lib/isc/crypto/ossl_common.c @@ -17,6 +17,7 @@ #include #include #include +#include EVP_MD *isc__crypto_md[] = { [ISC_MD_UNKNOWN] = NULL, [ISC_MD_MD5] = NULL, [ISC_MD_SHA1] = NULL, diff --git a/lib/isc/include/isc/crypto.h b/lib/isc/include/isc/crypto.h index 698b8857269..08cd0c82fa9 100644 --- a/lib/isc/include/isc/crypto.h +++ b/lib/isc/include/isc/crypto.h @@ -20,6 +20,122 @@ #include #include +/** + * \brief + * Context to an AEAD cipher. + * + * \warning Do not rely on the fact that this typedef is a `EVP_CIPHER_CTX`. + * It _can_ and **will** change without any announcement. + */ +#ifdef HAVE_EVP_AEAD_CTX_NEW +typedef struct evp_aead_ctx_st isc_crypto_aead_t; +#else /* HAVE_EVP_AEAD_CTX_NEW */ +typedef struct evp_cipher_ctx_st isc_crypto_aead_t; +#endif /* HAVE_EVP_AEAD_CTX_NEW */ + +typedef enum isc_crypto_aead_algorithm { + ISC_CRYPTO_AEAD_ALGORITHM_INVALID = 0, + ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM = 1, + ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM = 2, + ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305 = 3, + ISC_CRYPTO_AEAD_ALGORITHM_MAX = 4, +} isc_crypto_aead_algorithm_t; + +typedef enum isc_crypto_aead_direction { + ISC_CRYPTO_AEAD_DIRECTION_INVALID = 0, + ISC_CRYPTO_AEAD_DIRECTION_SEAL = 1, + ISC_CRYPTO_AEAD_DIRECTION_OPEN = 2, + ISC_CRYPTO_AEAD_DIRECTION_MAX = 3, +} isc_crypto_aead_direction_t; + +constexpr size_t isc_crypto_aes128gcm_key_length = 16; +constexpr size_t isc_crypto_aes256gcm_key_length = 32; +constexpr size_t isc_crypto_chacha20poly1305_key_length = 32; + +constexpr size_t isc_crypto_aes128gcm_nonce_length = 12; +constexpr size_t isc_crypto_aes256gcm_nonce_length = 12; +constexpr size_t isc_crypto_chacha20poly1305_nonce_length = 12; + +constexpr size_t isc_crypto_aes128gcm_tag_length = 16; +constexpr size_t isc_crypto_aes256gcm_tag_length = 16; +constexpr size_t isc_crypto_chacha20poly1305_tag_length = 16; + +constexpr size_t isc_crypto_aead_tag_length = 16; + +void +isc_crypto_aead_destroy(isc_crypto_aead_t **aeadp); +/**< + * \brief + * Destroy the AEAD context and wipe out the keys. + * + * Requires: + * - `*aeadp` must be a valid AEAD context. + */ + +isc_result_t +isc_crypto_aead_create(isc_crypto_aead_algorithm_t algorithm, + isc_constregion_t key, + isc_crypto_aead_direction_t direction, + isc_crypto_aead_t **aeadp); +/**< + * \brief + * Create an AEAD context for encryption or decryption operations. + * + * Requires: + * - `algorithm` must be a valid algorithm enum value. + * - `key.base != NULL` and key.length must be equal to the exact key length of + * the algorithm. + * - `direction` must be either #ISC_CRYPTO_AEAD_DIRECTION_SEAL or + * #ISC_CRYPTO_AEAD_DIRECTION_OPEN + * - `aeadp != NULL` and `*aeadp == NULL` + * + * \retval ISC_R_SUCCESS on success + * \retval ISC_R_NOTIMPLEMENTED if the aead algorithm is not available + * \retval ISC_R_CRYPTOFAILURE on libcrypto failure + */ + +isc_result_t +isc_crypto_aead_seal(isc_crypto_aead_t *aead, isc_constregion_t nonce, + isc_constregion_t plaintext, isc_region_t out, + size_t *out_sealed_len, isc_constregion_t additional_data); +/**< + * \brief + * Encrypt the plaintext and authenticate the ciphertext with the additional + * data. + * + * Requires: + * - `aead` is a valid AEAD context. + * - `nonce.base != NULL` and has the appropriate length for the algorithm. + * - `plaintext.base != NULL` and `out.base != NULL` + * - `out.length == plaintext.length + isc_crypto_aead_tag_length` + * - `out_sealed_len != NULL` + * - `additional_data` must either have a `NULL` base or a non-zero length. + * + * \retval ISC_R_SUCCESS on success + * \retval ISC_R_CRYPTOFAILURE on opaque cryptographical failure + */ + +isc_result_t +isc_crypto_aead_open(isc_crypto_aead_t *aead, isc_constregion_t nonce, + isc_constregion_t ciphertext, isc_region_t out, + size_t *out_opened_len, isc_constregion_t additional_data); +/**< + * \brief + * Decrypt the ciphertext and authenticate the ciphertext with the additional + * data. + * + * Requires: + * - `aead` is a valid AEAD context. + * - `nonce.base != NULL` and has the appropriate length for the algorithm. + * - `ciphertext.base != NULL` and `out.base != NULL` + * - `out.length == ciphertext.length - isc_crypto_aead_tag_length` + * - `out_opened_len != NULL` + * - `additional_data` must either have a `NULL` base or a non-zero length. + * + * \retval ISC_R_SUCCESS on success + * \retval ISC_R_CRYPTOFAILURE on opaque cryptographical failure + */ + isc_result_t isc_crypto_hkdf_extract(isc_region_t out, isc_md_type_t md, isc_constregion_t secret, isc_constregion_t salt); diff --git a/meson.build b/meson.build index c47009030ad..6e02fda5a64 100644 --- a/meson.build +++ b/meson.build @@ -643,6 +643,21 @@ else config.set('HAVE_OPENSSL_3', false) config.set('OPENSSL_API_COMPAT', 10100) + have_openssl_aead_h = cc.has_header('openssl/aead.h', dependencies: openssl_dep) + config.set('HAVE_OPENSSL_AEAD_H', have_openssl_aead_h) + if have_openssl_aead_h + config.set('HAVE_EVP_AEAD_CTX_NEW', true) + else + config.set( + 'HAVE_EVP_AEAD_CTX_NEW', + cc.has_function( + 'EVP_AEAD_CTX_new', + dependencies: openssl_dep, + prefix: '#include ', + ), + ) + endif + config.set( 'HAVE_FIPS_MODE', cc.has_function( diff --git a/tests/isc/crypto_test.c b/tests/isc/crypto_test.c index 5a6b99ed965..10ba7601614 100644 --- a/tests/isc/crypto_test.c +++ b/tests/isc/crypto_test.c @@ -11,6 +11,20 @@ * information regarding copyright ownership. */ +/* + * The AEAD test vectors are taken from Project Wycheproof. The vectors are not + * for checking various cryptographic properties (that is the job of libcrypto + * and its forks) but as a sanity check for changes in the relevant code. + * + * Test vectors do not require a license notice since they are not copyrightable + * material in the first place. This is also further reinforced by Wycheproof + * contributors. [1] + * + * Last used commit: d544ce0881731f28eaa98e946db430880d216fd5 + * + * [1]: https://github.com/C2SP/wycheproof/issues/52#issuecomment-394413920 + */ + #include #include /* IWYU pragma: keep */ #include /* IWYU pragma: keep */ @@ -25,9 +39,191 @@ #include #include #include +#include #include +typedef struct aead_testcase aead_testcase_t; + +struct aead_testcase { + isc_crypto_aead_algorithm_t algorithm; + const uint8_t key[32]; + const uint8_t nonce[12]; + const uint8_t plaintext[20]; + const uint8_t ciphertext[36]; + const uint8_t additional_data[8]; +}; + + +static void +test_aead(const aead_testcase_t *const testcase) { + uint8_t ciphertext_buf[36], plaintext_buf[20]; + isc_constregion_t nonce, key; + isc_crypto_aead_t *aead = NULL; + isc_result_t result; + size_t outlen; + + const isc_constregion_t aad = { + .base = testcase->additional_data, + .length = sizeof(testcase->additional_data), + }; + + isc_region_t actual_ciphertext = { + .base = ciphertext_buf, + .length = sizeof(ciphertext_buf), + }; + + isc_region_t actual_plaintext = { + .base = plaintext_buf, + .length = sizeof(plaintext_buf), + }; + + nonce.base = testcase->nonce; + key.base = testcase->key; + + switch (testcase->algorithm) { + case ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM: + nonce.length = isc_crypto_aes128gcm_nonce_length; + key.length = isc_crypto_aes128gcm_key_length; + break; + case ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM: + nonce.length = isc_crypto_aes256gcm_nonce_length; + key.length = isc_crypto_aes256gcm_key_length; + break; + case ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305: + nonce.length = isc_crypto_chacha20poly1305_nonce_length; + key.length = isc_crypto_chacha20poly1305_key_length; + break; + default: + UNREACHABLE(); + } + + result = isc_crypto_aead_create(testcase->algorithm, key, + ISC_CRYPTO_AEAD_DIRECTION_SEAL, &aead); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_crypto_aead_seal( + aead, nonce, + (isc_constregion_t){ testcase->plaintext, + sizeof(testcase->plaintext) }, + actual_ciphertext, &outlen, (isc_constregion_t){ NULL, 0 }); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(outlen, actual_ciphertext.length); + assert_memory_not_equal(actual_ciphertext.base, testcase->ciphertext, + actual_ciphertext.length); + + result = isc_crypto_aead_seal( + aead, nonce, + (isc_constregion_t){ testcase->plaintext, + sizeof(testcase->plaintext) }, + actual_ciphertext, &outlen, + (isc_constregion_t){ testcase->additional_data, + sizeof(testcase->additional_data) }); + assert_int_equal(result, ISC_R_SUCCESS); + assert_int_equal(outlen, actual_ciphertext.length); + assert_memory_equal(actual_ciphertext.base, testcase->ciphertext, + actual_ciphertext.length); + isc_crypto_aead_destroy(&aead); + + result = isc_crypto_aead_create(testcase->algorithm, key, + ISC_CRYPTO_AEAD_DIRECTION_OPEN, &aead); + assert_int_equal(result, ISC_R_SUCCESS); + + result = isc_crypto_aead_open( + aead, nonce, + (isc_constregion_t){ (uint8_t *)testcase->ciphertext, + sizeof(testcase->ciphertext) }, + actual_plaintext, &outlen, (isc_constregion_t){ NULL, 0 }); + assert_int_not_equal(result, ISC_R_SUCCESS); + + result = isc_crypto_aead_open( + aead, nonce, + (isc_constregion_t){ (uint8_t *)testcase->ciphertext, + sizeof(testcase->ciphertext) }, + actual_plaintext, &outlen, aad); + assert_int_equal(result, ISC_R_SUCCESS); + assert_memory_equal(actual_plaintext.base, testcase->plaintext, + actual_plaintext.length); + assert_int_equal(outlen, sizeof(testcase->plaintext)); + isc_crypto_aead_destroy(&aead); +} + +ISC_RUN_TEST_IMPL(aead) { + size_t i; + + static const aead_testcase_t fips_testcases[] = { + { + /* tcid : 12 */ + ISC_CRYPTO_AEAD_ALGORITHM_AES128GCM, + { 0xe6, 0x3a, 0x43, 0x21, 0x6c, 0x08, 0x86, 0x72, 0x10, + 0xe2, 0x48, 0x85, 0x9e, 0xb5, 0xe9, 0x9c }, + { 0x9c, 0x3a, 0x42, 0x63, 0xd9, 0x83, 0x45, 0x66, 0x58, + 0xaa, 0xd4, 0xb1 }, + { 0xb1, 0x4d, 0xa5, 0x6b, 0x04, 0x62, 0xdc, + 0x05, 0xb8, 0x71, 0xfc, 0x81, 0x52, 0x73, + 0xff, 0x48, 0x10, 0xf9, 0x2f, 0x4b }, + { 0xbf, 0x86, 0x46, 0x16, 0xc2, 0x34, 0x75, 0x09, 0xca, + 0x9b, 0x10, 0x44, 0x63, 0x79, 0xb9, 0xbd, 0xbb, 0x3b, + 0x8f, 0x64, 0xa9, 0x7d, 0x25, 0xb4, 0x90, 0x39, 0x0b, + 0x53, 0xc5, 0xdb, 0x91, 0xf6, 0xee, 0x2a, 0x15, 0xb8 }, + { 0x83, 0x4a, 0xfd, 0xc5, 0xc7, 0x37, 0x18, 0x6b }, + }, + + { + /* tcid: 101 */ + ISC_CRYPTO_AEAD_ALGORITHM_AES256GCM, + { 0xcd, 0xcc, 0xfe, 0x3f, 0x46, 0xd7, 0x82, 0xef, + 0x47, 0xdf, 0x4e, 0x72, 0xf0, 0xc0, 0x2d, 0x9c, + 0x7f, 0x77, 0x4d, 0xef, 0x97, 0x0d, 0x23, 0x48, + 0x6f, 0x11, 0xa5, 0x7f, 0x54, 0x24, 0x7f, 0x17 }, + { 0x37, 0x61, 0x87, 0x89, 0x46, 0x05, 0xa8, 0xd4, 0x5e, + 0x30, 0xde, 0x51 }, + { 0xe2, 0x8e, 0x0e, 0x9f, 0x9d, 0x22, 0x46, + 0x3a, 0xc0, 0xe4, 0x26, 0x39, 0xb5, 0x30, + 0xf4, 0x21, 0x02, 0xfd, 0xed, 0x75 }, + { 0xfe, 0xca, 0x44, 0x95, 0x24, 0x47, 0x01, 0x5b, 0x5d, + 0xf1, 0xf4, 0x56, 0xdf, 0x8c, 0xa4, 0xbb, 0x4e, 0xee, + 0x2c, 0xe2, 0x08, 0x2e, 0x91, 0x92, 0x4d, 0xee, 0xb7, + 0x78, 0x80, 0xe1, 0xb1, 0xc8, 0x4f, 0x9b, 0x8d, 0x30 }, + { 0x95, 0x68, 0x46, 0xa2, 0x09, 0xe0, 0x87, 0xed }, + + }, + }; + + static const aead_testcase_t non_fips_testcases[] = { + { + /* tcid : 43 */ + ISC_CRYPTO_AEAD_ALGORITHM_CHACHA20POLY1305, + { 0xcd, 0xcc, 0xfe, 0x3f, 0x46, 0xd7, 0x82, 0xef, + 0x47, 0xdf, 0x4e, 0x72, 0xf0, 0xc0, 0x2d, 0x9c, + 0x7f, 0x77, 0x4d, 0xef, 0x97, 0x0d, 0x23, 0x48, + 0x6f, 0x11, 0xa5, 0x7f, 0x54, 0x24, 0x7f, 0x17 }, + { 0x37, 0x61, 0x87, 0x89, 0x46, 0x05, 0xa8, 0xd4, 0x5e, + 0x30, 0xde, 0x51 }, + { 0xe2, 0x8e, 0x0e, 0x9f, 0x9d, 0x22, 0x46, + 0x3a, 0xc0, 0xe4, 0x26, 0x39, 0xb5, 0x30, + 0xf4, 0x21, 0x02, 0xfd, 0xed, 0x75 }, + { 0x14, 0xf7, 0x07, 0xc4, 0x46, 0x98, 0x8a, 0x49, 0x03, + 0x77, 0x5e, 0xc7, 0xac, 0xec, 0x6d, 0xa1, 0x14, 0xd4, + 0x31, 0x12, 0x98, 0x7d, 0x4b, 0x14, 0x7c, 0x49, 0x0d, + 0x43, 0xd3, 0x76, 0xa1, 0x98, 0xca, 0xb3, 0x83, 0xf0 }, + { 0x95, 0x68, 0x46, 0xa2, 0x09, 0xe0, 0x87, 0xed }, + }, + }; + + for (i = 0; i < ARRAY_SIZE(fips_testcases); i++) { + test_aead(&fips_testcases[i]); + } + + if (isc_crypto_fips_mode()) { + return; + } + + for (i = 0; i < ARRAY_SIZE(non_fips_testcases); i++) { + test_aead(&non_fips_testcases[i]); + } +} + ISC_RUN_TEST_IMPL(hkdf) { isc_result_t result; uint8_t actual[42]; @@ -88,6 +284,7 @@ ISC_RUN_TEST_IMPL(hkdf_expand_label) { } ISC_TEST_LIST_START +ISC_TEST_ENTRY(aead) ISC_TEST_ENTRY(hkdf) ISC_TEST_ENTRY(hkdf_expand_label) ISC_TEST_LIST_END