]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
add aead api to isc_crypto
authorAydın Mercan <aydin@isc.org>
Mon, 9 Feb 2026 05:03:02 +0000 (08:03 +0300)
committerAydın Mercan <aydin@isc.org>
Tue, 14 Jul 2026 13:40:49 +0000 (16:40 +0300)
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.

lib/isc/crypto/ossl1_1.c
lib/isc/crypto/ossl3.c
lib/isc/crypto/ossl_common.c
lib/isc/include/isc/crypto.h
meson.build
tests/isc/crypto_test.c

index 3e143b4868f54a5e8877be3220391a557b421cda..31d344b9961479f0a345d349af992f792384f103 100644 (file)
 #include <isc/md.h>
 #include <isc/mem.h>
 #include <isc/ossl_wrap.h>
+#include <isc/overflow.h>
+#include <isc/result.h>
 #include <isc/safe.h>
 #include <isc/util.h>
 
+#ifdef HAVE_OPENSSL_AEAD_H
+#include <openssl/aead.h>
+#endif /* HAVE_OPENSSL_AEAD_H */
+
 #ifdef HAVE_OPENSSL_HKDF_H
 #include <openssl/hkdf.h>
 #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
index 73db62411f04c7601962bd7e5e0516ffb9df8f9c..0ce8752483396af39092d805bfae249be66e929e 100644 (file)
@@ -31,6 +31,7 @@
 #include <isc/md.h>
 #include <isc/mem.h>
 #include <isc/ossl_wrap.h>
+#include <isc/overflow.h>
 #include <isc/region.h>
 #include <isc/safe.h>
 #include <isc/util.h>
@@ -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) {
index 8b5d56925b4c62f7df2f69cb281ce29b8238fa3f..965bbb7fabad2d166d1186719d51ac7485fb6fde 100644 (file)
@@ -17,6 +17,7 @@
 #include <isc/crypto.h>
 #include <isc/md.h>
 #include <isc/ossl_wrap.h>
+#include <isc/util.h>
 
 EVP_MD *isc__crypto_md[] = {
        [ISC_MD_UNKNOWN] = NULL, [ISC_MD_MD5] = NULL,    [ISC_MD_SHA1] = NULL,
index 698b8857269893072de93663228b9d0d288aab60..08cd0c82fa99306429bde388243addfbc6f636f7 100644 (file)
 #include <isc/region.h>
 #include <isc/types.h>
 
+/**
+ * \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);
index c47009030adf40236e10239437cecc103b71dd9e..6e02fda5a64839b9442130db36823be7e177fc7d 100644 (file)
@@ -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 <openssl/evp.h>',
+            ),
+        )
+    endif
+
     config.set(
         'HAVE_FIPS_MODE',
         cc.has_function(
index 5a6b99ed965b1869de1d743c16214f516fee9016..10ba76016146a66001a0af8fffbed1b922293989 100644 (file)
  * 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 <inttypes.h>
 #include <sched.h>  /* IWYU pragma: keep */
 #include <setjmp.h> /* IWYU pragma: keep */
 #include <isc/crypto.h>
 #include <isc/lib.h>
 #include <isc/md.h>
+#include <isc/types.h>
 
 #include <tests/isc.h>
 
+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