Commit 5741d29a5f for openssl.org

commit 5741d29a5f356e05262cd0936a472a9961398d53
Author: Billy Brumley <bbb@iki.fi>
Date:   Tue Aug 4 07:35:48 2026 -0400

    Check the tag on EVP_Cipher() finalize: Poly1305 and OCB AEADs

    At the EVP level, for AEADs EVP_Cipher(ctx, out, NULL, 0) performs
    finalization. For consistency across AEADs (GCM, etc.), on decrypt it
    must check the tag. OCB and ChaCha20-Poly1305 took an early exit on the
    empty message (with or without AAD) and returned success without
    checking, so a forged tag (with or without AAD) was accepted before this
    change.

    Follow-up to #31555

    Assisted-by: Claude:claude-opus-4-8

    Reviewed-by: Frederik Wedel-Heinen <fwh.openssl@gmail.com>
    Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
    MergeDate: Fri Aug  7 13:34:35 2026
    (Merged from https://github.com/openssl/openssl/pull/32173)

diff --git a/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c b/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c
index 9622c2dcca..f215b9291f 100644
--- a/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c
+++ b/providers/implementations/ciphers/cipher_aes_gcm_siv_hw.c
@@ -264,11 +264,20 @@ static int aes_gcm_siv_finish(PROV_AES_GCM_SIV_CTX *ctx)
 {
     int ret = 0;

-    if (ctx->enc)
+    if (ctx->enc) {
+        /*
+         * generate the tag on FINAL so an init/final with no update
+         * (an empty message) still produces it, matching the other AEADs
+         */
+        if (ctx->generated_tag == 0
+            && aes_gcm_siv_encrypt(ctx, NULL, NULL, 0) == 0)
+            return 0;
         return ctx->generated_tag;
-    if (!ctx->generated_tag)
-        aes_gcm_siv_decrypt(ctx, NULL, NULL, 0);
-    ret = !CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag));
+    }
+    if (ctx->generated_tag == 0
+        && aes_gcm_siv_decrypt(ctx, NULL, NULL, 0) == 0)
+        return 0;
+    ret = CRYPTO_memcmp(ctx->tag, ctx->user_tag, sizeof(ctx->tag)) == 0;
     ret &= ctx->have_user_tag;
     return ret;
 }
diff --git a/providers/implementations/ciphers/cipher_aes_ocb.c b/providers/implementations/ciphers/cipher_aes_ocb.c
index 1bd5281cc2..2e1b945e2d 100644
--- a/providers/implementations/ciphers/cipher_aes_ocb.c
+++ b/providers/implementations/ciphers/cipher_aes_ocb.c
@@ -497,6 +497,14 @@ static int aes_ocb_cipher(void *vctx, unsigned char *out, size_t *outl,
     if (!ossl_prov_is_running())
         return 0;

+    /*
+     * EVP_Cipher() MUST CHECK THE TAG
+     * in == NULL indicates finalize, so hand it to the finalize path
+     * (which checks the tag on decrypt / produces it on encrypt)
+     */
+    if (in == NULL)
+        return aes_ocb_block_final(vctx, out, outl, outsize);
+
     if (outsize < inl) {
         ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL);
         return 0;
diff --git a/providers/implementations/ciphers/cipher_chacha20_poly1305.c b/providers/implementations/ciphers/cipher_chacha20_poly1305.c
index a48b9c3725..63b263c139 100644
--- a/providers/implementations/ciphers/cipher_chacha20_poly1305.c
+++ b/providers/implementations/ciphers/cipher_chacha20_poly1305.c
@@ -291,11 +291,6 @@ static int chacha20_poly1305_cipher(void *vctx, unsigned char *out,
     if (!ossl_prov_is_running())
         return 0;

-    if (inl == 0) {
-        *outl = 0;
-        return 1;
-    }
-
     if (outsize < inl) {
         ERR_raise(ERR_LIB_PROV, PROV_R_OUTPUT_BUFFER_TOO_SMALL);
         return 0;
@@ -316,6 +311,15 @@ static int chacha20_poly1305_update(void *vctx, unsigned char *out,
     if (ctx->iv_state == IV_STATE_FINISHED)
         return 0;

+    /*
+     * a zero-length update is a nop, ALWAYS SUCCEED via early exit
+     * NB: ONLY EVP_Cipher() / final produce or check the tag
+     */
+    if (inl == 0) {
+        *outl = 0;
+        return 1;
+    }
+
     return chacha20_poly1305_cipher(vctx, out, outl, outsize, in, inl);
 }

diff --git a/test/evp_extra_test.c b/test/evp_extra_test.c
index af729d13f2..14eaa2a37f 100644
--- a/test/evp_extra_test.c
+++ b/test/evp_extra_test.c
@@ -5882,8 +5882,6 @@ static int test_evp_oneshot_aead_zerolen(int idx)
     /* filter out various modes */
     if (info->taglen == 0
         || info->mode == EVP_CIPH_CCM_MODE
-        || info->mode == EVP_CIPH_OCB_MODE
-        || info->mode == EVP_CIPH_GCM_SIV_MODE
         /* skip TLS stitched MTE cipher */
         || EVP_CIPHER_is_a(info->ciph, "AES-128-CBC-HMAC-SHA1")
         /* skip TLS stitched MTE cipher */
@@ -5891,8 +5889,7 @@ static int test_evp_oneshot_aead_zerolen(int idx)
         /* skip TLS stitched MTE cipher */
         || EVP_CIPHER_is_a(info->ciph, "AES-128-CBC-HMAC-SHA256")
         /* skip TLS stitched MTE cipher */
-        || EVP_CIPHER_is_a(info->ciph, "AES-256-CBC-HMAC-SHA256")
-        || EVP_CIPHER_is_a(info->ciph, "ChaCha20-Poly1305"))
+        || EVP_CIPHER_is_a(info->ciph, "AES-256-CBC-HMAC-SHA256"))
         return 1;

     for (i = 0; i < info->keylen && i < (int)sizeof(key); i++)