Commit fc972e592e for openssl.org

commit fc972e592ede98d7a51e1a25e4509bb9086f1f4e
Author: Daniel Kubec <kubec@openssl.foundation>
Date:   Tue Jun 23 14:10:45 2026 +0200

    AEAD: reject late AAD in ChaCha20-Poly1305 after plaintext update

    Align behavior with AES GCM, which already rejects this misuse with a hard
    error, by tracking whether plaintext processing has started and returning an
    error if AAD is supplied afterwards.

    Fixes #31188

    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Bob Beck <beck@openssl.org>
    MergeDate: Thu Jun 25 07:01:44 2026
    (Merged from https://github.com/openssl/openssl/pull/31673)

diff --git a/providers/implementations/ciphers/cipher_chacha20_poly1305_hw.c b/providers/implementations/ciphers/cipher_chacha20_poly1305_hw.c
index 47f4c3cb68..ffd2ee744c 100644
--- a/providers/implementations/ciphers/cipher_chacha20_poly1305_hw.c
+++ b/providers/implementations/ciphers/cipher_chacha20_poly1305_hw.c
@@ -301,6 +301,8 @@ static int chacha20_poly1305_aead_cipher(PROV_CIPHER_CTX *bctx,

     if (in != NULL) { /* aad or text */
         if (out == NULL) { /* aad */
+            if (ctx->len.text != 0)
+                goto err;
             Poly1305_Update(poly, in, inl);
             ctx->len.aad += inl;
             ctx->aad = 1;
diff --git a/test/evp_extra_test.c b/test/evp_extra_test.c
index b0c37e476e..236991f1f5 100644
--- a/test/evp_extra_test.c
+++ b/test/evp_extra_test.c
@@ -7636,6 +7636,30 @@ err:
     return ret;
 }

+#if !defined(OPENSSL_NO_CHACHA) && !defined(OPENSSL_NO_POLY1305)
+static int test_chacha20_poly1305_late_aad(void)
+{
+    EVP_CIPHER_CTX *ctx = NULL;
+    EVP_CIPHER *c = NULL;
+    unsigned char key[32] = { 0 };
+    unsigned char iv[12] = { 0 };
+    unsigned char aad[4] = "aad";
+    unsigned char msg[8] = "message";
+    unsigned char out[32];
+    int len, test;
+
+    test = TEST_ptr(ctx = EVP_CIPHER_CTX_new())
+        && TEST_ptr(c = EVP_CIPHER_fetch(testctx, "ChaCha20-Poly1305", testpropq))
+        && TEST_true(EVP_EncryptInit_ex2(ctx, c, key, iv, NULL))
+        && TEST_true(EVP_EncryptUpdate(ctx, NULL, &len, aad, sizeof(aad)))
+        && TEST_true(EVP_EncryptUpdate(ctx, out, &len, msg, sizeof(msg)))
+        && TEST_false(EVP_EncryptUpdate(ctx, NULL, &len, aad, sizeof(aad)));
+
+    EVP_CIPHER_free(c);
+    EVP_CIPHER_CTX_free(ctx);
+    return test;
+}
+#endif
 /*
  * AES-SIV reuse-without-rekey:
  *   msg1: legit non-empty CT, tag verifies, final_ret=0
@@ -8870,6 +8894,7 @@ int setup_tests(void)
 #endif
 #if !defined(OPENSSL_NO_CHACHA) && !defined(OPENSSL_NO_POLY1305)
     ADD_TEST(test_decrypt_null_chunks);
+    ADD_TEST(test_chacha20_poly1305_late_aad);
 #endif
 #ifndef OPENSSL_NO_DH
     ADD_TEST(test_DH_priv_pub);