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++)