Commit 122db7eb07 for openssl.org

commit 122db7eb076bf0625df87b91bcc2dccfaccc4900
Author: Hamzah Yamani <hamzah.yamani125@gmail.com>
Date:   Mon Oct 5 12:00:04 2026 -0400

    Migrate test_evp_final_no_tag to evp_aead_test.c

    Move test_evp_final_no_tag out of evp_extra_test.c into evp_aead_test.c
    and run it over every AEAD in aead_list instead of a hardcoded pair of
    ciphers. The check is that EVP_DecryptFinal_ex() refuses to finalize a
    decrypt when no tag has been set.

    CCM, OCB, SIV and GCM-SIV are skipped for now. Those modes either
    require the tag and payload length to be declared before the payload
    update, or verify inside the payload update rather than in final, so the
    check as written does not apply to them. How the interface should behave
    for these modes is still under discussion.

    Key and IV buffers are sized to EVP_MAX_KEY_LENGTH and EVP_MAX_IV_LENGTH
    so ciphers with longer keys, such as AES-256-SIV, stay in bounds, and the
    keylen, ivlen and taglen of each cipher are checked against those buffers.
    Encryption and decryption use separate contexts, initialized with
    EVP_*Init_ex2(), and the decrypt final writes to the plaintext buffer.

    Assisted-by: Claude:claude-sonnet-5
    Reviewed-by: Neil Horman <nhorman@openssl.org>
    Reviewed-by: Saša NedvÄ›dický <sashan@openssl.org>
    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Merge-date: Sun Oct 11 22:27:06 2026
    Merged-from: https://github.com/openssl/openssl/pull/32907

diff --git a/test/evp_aead_test.c b/test/evp_aead_test.c
index 4b2685c20c..a032cb0024 100644
--- a/test/evp_aead_test.c
+++ b/test/evp_aead_test.c
@@ -688,6 +688,88 @@ err:
     return testresult;
 }

+/*-
+ * A tag must be set before decrypting; EVP_DecryptFinal_ex() must reject
+ * finalization when no tag has been supplied. Applies to every AEAD in
+ * aead_list except the modes skipped below.
+ */
+static int test_evp_final_no_tag(int idx)
+{
+    const AEAD_DATA *info = &aead_list[idx];
+    EVP_CIPHER_CTX *ctx_enc = NULL;
+    EVP_CIPHER_CTX *ctx_dec = NULL;
+    OSSL_PARAM params[2];
+    unsigned char tag[16];
+    unsigned char data[5] = { 1, 1, 1, 1, 1 };
+    unsigned char ctext[EVP_MAX_BLOCK_LENGTH] = { 0 };
+    unsigned char plaintext[sizeof(data)] = { 0 };
+    int ctext_len = 0, len = 0, testresult = 0;
+    unsigned char key[EVP_MAX_KEY_LENGTH] = {
+        0xc9, 0xee, 0xa3, 0x0c, 0x1c, 0x59, 0x0c, 0x8b, 0xd8, 0xbb, 0xa1, 0x1c,
+        0xbc, 0x3a, 0x56, 0xe7, 0xb7, 0xe1, 0x9f, 0xfd, 0x3b, 0x4a, 0xa3, 0xd5,
+        0xc4, 0xdc, 0x2e, 0x62, 0xe6, 0x75, 0x15, 0x5c
+    };
+    unsigned char iv[EVP_MAX_IV_LENGTH] = {
+        0x03, 0x2d, 0x79, 0xef, 0xed, 0x2e, 0xad, 0x3e, 0x0b, 0xdc, 0x8f, 0x57,
+        0x0d, 0x0e, 0x0f, 0x10
+    };
+
+    /*
+     * CCM requires the tag and payload length to be declared up front. OCB
+     * emits plaintext before the tag check. SIV and GCM-SIV verify in the
+     * payload update rather than in final. Skipped until the interface
+     * behavior for these modes is settled.
+     */
+    if (info->mode == EVP_CIPH_CCM_MODE
+        || info->mode == EVP_CIPH_OCB_MODE
+        || info->mode == EVP_CIPH_SIV_MODE
+        || info->mode == EVP_CIPH_GCM_SIV_MODE)
+        return 1;
+
+    if (!TEST_int_le(info->keylen, (int)sizeof(key))
+        || !TEST_int_le(info->ivlen, (int)sizeof(iv))
+        || !TEST_int_le(info->taglen, (int)sizeof(tag))) {
+        TEST_info("%s: keylen %d, ivlen %d or taglen %d exceeds the buffers",
+            EVP_CIPHER_get0_name(info->ciph), info->keylen, info->ivlen,
+            info->taglen);
+        goto err;
+    }
+
+    params[0] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+        tag, sizeof(tag));
+    params[1] = OSSL_PARAM_construct_end();
+
+    if (!TEST_ptr(ctx_enc = EVP_CIPHER_CTX_new())
+        || !TEST_true(EVP_EncryptInit_ex2(ctx_enc, info->ciph, key, iv, NULL))
+        || !TEST_true(EVP_EncryptUpdate(ctx_enc, ctext, &ctext_len, data,
+            sizeof(data)))
+        || !TEST_true(EVP_EncryptFinal_ex(ctx_enc, ctext + ctext_len, &len))
+        || !TEST_true(EVP_CIPHER_CTX_get_params(ctx_enc, params))) {
+        TEST_info("%s: encrypt or tag retrieval failed",
+            EVP_CIPHER_get0_name(info->ciph));
+        goto err;
+    }
+    ctext_len += len;
+
+    /* No tag is set on the decrypt side, so finalization must fail. */
+    if (!TEST_ptr(ctx_dec = EVP_CIPHER_CTX_new())
+        || !TEST_true(EVP_DecryptInit_ex2(ctx_dec, info->ciph, key, iv, NULL))
+        || !TEST_true(EVP_DecryptUpdate(ctx_dec, plaintext, &len, ctext,
+            ctext_len))
+        || !TEST_mem_eq(plaintext, sizeof(data), data, sizeof(data))
+        || !TEST_false(EVP_DecryptFinal_ex(ctx_dec, plaintext + len, &len))) {
+        TEST_info("%s: decrypt without tag did not behave as expected",
+            EVP_CIPHER_get0_name(info->ciph));
+        goto err;
+    }
+
+    testresult = 1;
+err:
+    EVP_CIPHER_CTX_free(ctx_enc);
+    EVP_CIPHER_CTX_free(ctx_dec);
+    return testresult;
+}
+
 int setup_tests(void)
 {
     int i = 0;
@@ -706,6 +788,7 @@ int setup_tests(void)
     ADD_ALL_TESTS(test_evp_aead_late_aad, aead_list_n);
     ADD_ALL_TESTS(test_evp_aead_finished_ctx, aead_list_n);
     ADD_ALL_TESTS(test_evp_aead_get_tag_pairwise, aead_list_n);
+    ADD_ALL_TESTS(test_evp_final_no_tag, aead_list_n);
     return 1;
 }

diff --git a/test/evp_extra_test.c b/test/evp_extra_test.c
index e321619b46..81467dd9a0 100644
--- a/test/evp_extra_test.c
+++ b/test/evp_extra_test.c
@@ -7137,80 +7137,6 @@ err:
     return testresult;
 }

-typedef struct {
-    const char *cipher;
-} EVP_FINAL_NO_TAG_TEST_st;
-
-static const EVP_FINAL_NO_TAG_TEST_st evp_final_no_tag[] = {
-    { "chacha20-poly1305" },
-    { "aes-256-gcm" }
-};
-
-static int test_evp_final_no_tag(int idx)
-{
-    const EVP_FINAL_NO_TAG_TEST_st *t = &evp_final_no_tag[idx];
-    EVP_CIPHER_CTX *ctx = NULL;
-    EVP_CIPHER *cipher = NULL;
-    unsigned char tag[16];
-    unsigned char data[5] = { 1, 1, 1, 1, 1 };
-    uint32_t data_len = 5;
-    unsigned char ctext[1024], plaintext[1024];
-    int ctext_len = 0, len = 0, testresult = 0;
-    unsigned char key[] = {
-        0xc9, 0xee, 0xa3, 0x0c, 0x1c, 0x59, 0x0c, 0x8b, 0xd8, 0xbb, 0xa1, 0x1c,
-        0xbc, 0x3a, 0x56, 0xe7, 0xb7, 0xe1, 0x9f, 0xfd, 0x3b, 0x4a, 0xa3, 0xd5,
-        0xc4, 0xdc, 0x2e, 0x62, 0xe6, 0x75, 0x15, 0x5c
-    };
-    unsigned char iv[16] = {
-        0x03, 0x2d, 0x79, 0xef, 0xed, 0x2e, 0xad, 0x3e, 0x0b, 0xdc, 0x8f, 0x57,
-        0x0d, 0x0e, 0x0f, 0x10
-    };
-
-    if ((cipher = EVP_CIPHER_fetch(testctx, t->cipher, testpropq)) == NULL) {
-        TEST_info("cipher %s not supported, skipping", t->cipher);
-        goto ok;
-    }
-
-    if (!TEST_ptr(ctx = EVP_CIPHER_CTX_new()))
-        goto err;
-    if (!TEST_true(EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)))
-        goto err;
-    if (!TEST_true(EVP_EncryptUpdate(ctx, ctext, &len, data, data_len)))
-        goto err;
-    ctext_len = len;
-    if (!TEST_true(EVP_EncryptFinal_ex(ctx, ctext + len, &len)))
-        goto err;
-
-    ctext_len += len;
-    if (!TEST_int_gt(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, tag),
-            0))
-        goto err;
-    EVP_CIPHER_CTX_free(ctx);
-
-    if (!TEST_ptr(ctx = EVP_CIPHER_CTX_new()))
-        goto err;
-    if (!TEST_true(EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)))
-        goto err;
-    if (!TEST_true(EVP_DecryptUpdate(ctx, plaintext, &len, ctext, ctext_len)))
-        goto err;
-    if (!TEST_mem_eq(plaintext, 5, data, 5))
-        goto err;
-
-    /*
-     * The tag must be set before decrypting the data; here we expect failure
-     * for each of the defined ciphers.
-     */
-    if (!TEST_false(EVP_DecryptFinal_ex(ctx, ctext + len, &len)))
-        goto err;
-
-ok:
-    testresult = 1;
-err:
-    EVP_CIPHER_CTX_free(ctx);
-    EVP_CIPHER_free(cipher);
-    return testresult;
-}
-
 typedef struct {
     const unsigned char *iv1;
     const unsigned char *iv2;
@@ -9956,7 +9882,6 @@ int setup_tests(void)
     ADD_ALL_TESTS(test_evp_reinit_seq, OSSL_NELEM(evp_reinit_tests));
     ADD_ALL_TESTS(test_gcm_reinit, OSSL_NELEM(gcm_reinit_tests));
     ADD_ALL_TESTS(test_evp_updated_iv, OSSL_NELEM(evp_updated_iv_tests));
-    ADD_ALL_TESTS(test_evp_final_no_tag, OSSL_NELEM(evp_final_no_tag));

     ADD_ALL_TESTS(test_ivlen_change, OSSL_NELEM(ivlen_change_ciphers));
     ADD_ALL_TESTS(test_iv_reuse, OSSL_NELEM(iv_state_ciphers));