Commit ea655177e0 for openssl.org
commit ea655177e0d0dd7885640d1d9a921ef6d2c9bf8c
Author: Billy Brumley <bbb@iki.fi>
Date: Thu Jun 25 07:20:25 2026 -0400
[test] check tag abuse for AEAD ciphers
With AEAD ciphers, a tag is an input for decryption (the value to verify)
and an output of encryption (the generated value). Therefore:
- supplying a tag value while encrypting must fail
- reading a tag while decrypting must fail
- error codes should be consistent across all AEADs
Assisted-by: Claude:claude-opus-4-8
Reviewed-by: Dmitry Belyavskiy <beldmit@gmail.com>
Reviewed-by: Daniel Kubec <kubec@openssl.foundation>
MergeDate: Tue Jun 30 07:47:03 2026
(Merged from https://github.com/openssl/openssl/pull/31734)
diff --git a/providers/implementations/ciphers/cipher_aes_gcm_siv.c b/providers/implementations/ciphers/cipher_aes_gcm_siv.c
index 1fd97fe3d1..2f4a86dc56 100644
--- a/providers/implementations/ciphers/cipher_aes_gcm_siv.c
+++ b/providers/implementations/ciphers/cipher_aes_gcm_siv.c
@@ -182,8 +182,11 @@ static int ossl_aes_gcm_siv_get_ctx_params(void *vctx, OSSL_PARAM params[])
return 0;
if (p.tag != NULL && p.tag->data_type == OSSL_PARAM_OCTET_STRING) {
- if (!ctx->enc || !ctx->generated_tag
- || p.tag->data_size != sizeof(ctx->tag)
+ if (!ctx->enc || !ctx->generated_tag) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_TAG_NOT_SET);
+ return 0;
+ }
+ if (p.tag->data_size != sizeof(ctx->tag)
|| !OSSL_PARAM_set_octet_string(p.tag, ctx->tag,
sizeof(ctx->tag))) {
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_SET_PARAMETER);
@@ -227,6 +230,9 @@ static int ossl_aes_gcm_siv_set_ctx_params(void *vctx, const OSSL_PARAM params[]
if (!ctx->enc) {
memcpy(ctx->user_tag, p.tag->data, sizeof(ctx->tag));
ctx->have_user_tag = 1;
+ } else if (p.tag->data != NULL) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_TAG_NOT_NEEDED);
+ return 0;
}
}
diff --git a/providers/implementations/ciphers/cipher_aes_ocb.c b/providers/implementations/ciphers/cipher_aes_ocb.c
index 329d4bfa3e..1bd5281cc2 100644
--- a/providers/implementations/ciphers/cipher_aes_ocb.c
+++ b/providers/implementations/ciphers/cipher_aes_ocb.c
@@ -376,7 +376,7 @@ static int aes_ocb_set_ctx_params(void *vctx, const OSSL_PARAM params[])
ctx->taglen = p.tag->data_size;
} else {
if (ctx->base.enc) {
- ERR_raise(ERR_LIB_PROV, ERR_R_PASSED_INVALID_ARGUMENT);
+ ERR_raise(ERR_LIB_PROV, PROV_R_TAG_NOT_NEEDED);
return 0;
}
if (p.tag->data_size != ctx->taglen) {
@@ -476,7 +476,11 @@ static int aes_ocb_get_ctx_params(void *vctx, OSSL_PARAM params[])
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER);
return 0;
}
- if (!ctx->base.enc || p.tag->data_size != ctx->taglen) {
+ if (!ctx->base.enc) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_TAG_NOT_SET);
+ return 0;
+ }
+ if (p.tag->data_size != ctx->taglen) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_TAG_LENGTH);
return 0;
}
diff --git a/providers/implementations/ciphers/ciphercommon_gcm.c b/providers/implementations/ciphers/ciphercommon_gcm.c
index d9be50f513..c93b0767b0 100644
--- a/providers/implementations/ciphers/ciphercommon_gcm.c
+++ b/providers/implementations/ciphers/ciphercommon_gcm.c
@@ -215,7 +215,7 @@ int ossl_gcm_get_ctx_params(void *vctx, OSSL_PARAM params[])
if (p.tag != NULL) {
sz = p.tag->data_size;
if (!ctx->enc || ctx->taglen == UNINITIALISED_SIZET) {
- ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_TAG);
+ ERR_raise(ERR_LIB_PROV, PROV_R_TAG_NOT_SET);
return 0;
}
if (p.tag->data != NULL && (sz > EVP_GCM_TLS_TAG_LEN || sz == 0)) {
@@ -263,7 +263,11 @@ int ossl_gcm_set_ctx_params(void *vctx, const OSSL_PARAM params[])
ERR_raise(ERR_LIB_PROV, PROV_R_FAILED_TO_GET_PARAMETER);
return 0;
}
- if (sz == 0 || ctx->enc) {
+ if (ctx->enc) {
+ ERR_raise(ERR_LIB_PROV, PROV_R_TAG_NOT_NEEDED);
+ return 0;
+ }
+ if (sz == 0) {
ERR_raise(ERR_LIB_PROV, PROV_R_INVALID_TAG);
return 0;
}
diff --git a/test/evp_extra_test.c b/test/evp_extra_test.c
index 236991f1f5..cb18ead4db 100644
--- a/test/evp_extra_test.c
+++ b/test/evp_extra_test.c
@@ -5929,6 +5929,123 @@ err:
return testresult;
}
+/*
+ * With AEAD ciphers, a tag is an input for decryption (the value to verify)
+ * and an output of encryption (the generated value). Therefore:
+ * - supplying a tag value while encrypting must fail
+ * - reading a tag while decrypting must fail
+ * - error codes should be consistent across all AEADs
+ */
+static int test_evp_aead_tag_direction(int idx)
+{
+ const EVP_CIPHER_TEST_INFO *info = &cipher_list[idx];
+ EVP_CIPHER_CTX *ctx_enc = NULL; /* set tag while encrypting: must fail */
+ EVP_CIPHER_CTX *ctx_dec = NULL; /* get tag while decrypting: must fail */
+
+ OSSL_PARAM tagparams[2];
+
+ unsigned char key[EVP_MAX_KEY_LENGTH] = { 0 };
+ unsigned char iv[EVP_MAX_IV_LENGTH] = { 0 };
+ unsigned char tag[EVPTEST_TAG_LEN_MAX] = { 0 };
+
+ int i = 0, testresult = 0, expected = 0;
+ char *errmsg = NULL;
+ unsigned long err_code = 0;
+
+ /* filter out various modes */
+ if (info->taglen == 0
+ /* skip TLS stitched MTE cipher */
+ || EVP_CIPHER_is_a(info->ciph, "AES-128-CBC-HMAC-SHA1")
+ /* skip TLS stitched MTE cipher */
+ || EVP_CIPHER_is_a(info->ciph, "AES-256-CBC-HMAC-SHA1")
+ /* 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"))
+ return 1;
+
+ for (i = 0; i < info->keylen && i < (int)sizeof(key); i++)
+ key[i] = (unsigned char)(0xA0 + i);
+ for (i = 0; i < info->ivlen && i < (int)sizeof(iv); i++)
+ iv[i] = (unsigned char)(0xB0 + i);
+
+ /*
+ * a tag value supplied while encrypting must be rejected. data is non-NULL
+ * so this is a value-set, not the data == NULL tag-length query.
+ */
+ if (!TEST_ptr(ctx_enc = EVP_CIPHER_CTX_new())) {
+ errmsg = "ENC_ALLOC";
+ goto err;
+ }
+ if (!TEST_true(EVP_EncryptInit_ex2(ctx_enc, info->ciph, key, iv, NULL))) {
+ errmsg = "ENC_INIT";
+ goto err;
+ }
+ tagparams[0] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+ tag, info->taglen);
+ tagparams[1] = OSSL_PARAM_construct_end();
+ ERR_set_mark();
+ if (!TEST_false(EVP_CIPHER_CTX_set_params(ctx_enc, tagparams))) {
+ ERR_clear_last_mark();
+ errmsg = "ENC_SET_TAG_NOT_REJECTED";
+ goto err;
+ }
+ err_code = ERR_peek_last_error();
+ if (!TEST_int_eq(ERR_GET_LIB(err_code), ERR_LIB_PROV)
+ || !TEST_int_eq(ERR_GET_REASON(err_code), PROV_R_TAG_NOT_NEEDED)) {
+ ERR_clear_last_mark();
+ expected = PROV_R_TAG_NOT_NEEDED;
+ errmsg = "ENC_SET_TAG_WRONG_REASON";
+ goto err;
+ }
+ ERR_pop_to_mark();
+
+ /* a tag read while decrypting must be rejected */
+ if (!TEST_ptr(ctx_dec = EVP_CIPHER_CTX_new())) {
+ errmsg = "DEC_ALLOC";
+ goto err;
+ }
+ if (!TEST_true(EVP_DecryptInit_ex2(ctx_dec, info->ciph, key, iv, NULL))) {
+ errmsg = "DEC_INIT";
+ goto err;
+ }
+ tagparams[0] = OSSL_PARAM_construct_octet_string(OSSL_CIPHER_PARAM_AEAD_TAG,
+ tag, info->taglen);
+ tagparams[1] = OSSL_PARAM_construct_end();
+ ERR_set_mark();
+ if (!TEST_false(EVP_CIPHER_CTX_get_params(ctx_dec, tagparams))) {
+ ERR_clear_last_mark();
+ errmsg = "DEC_GET_TAG_NOT_REJECTED";
+ goto err;
+ }
+ err_code = ERR_peek_last_error();
+ if (!TEST_int_eq(ERR_GET_LIB(err_code), ERR_LIB_PROV)
+ || !TEST_int_eq(ERR_GET_REASON(err_code), PROV_R_TAG_NOT_SET)) {
+ ERR_clear_last_mark();
+ expected = PROV_R_TAG_NOT_SET;
+ errmsg = "DEC_GET_TAG_WRONG_REASON";
+ goto err;
+ }
+ ERR_pop_to_mark();
+
+ testresult = 1;
+
+err:
+ if (errmsg != NULL) {
+ if (expected != 0)
+ TEST_info("test_evp_aead_tag_direction %d, %s: %s"
+ " (expected reason %d, got %d)",
+ idx, errmsg, info->name,
+ expected, ERR_GET_REASON(err_code));
+ else
+ TEST_info("test_evp_aead_tag_direction %d, %s: %s",
+ idx, errmsg, info->name);
+ }
+ EVP_CIPHER_CTX_free(ctx_enc);
+ EVP_CIPHER_CTX_free(ctx_dec);
+ return testresult;
+}
+
/*
* Verify stale key is not being used after providing a new key in multiple steps.
* This test performs a full round of encryption and then changes the
@@ -8937,6 +9054,7 @@ int setup_tests(void)
ADD_ALL_TESTS(test_evp_stale_key_reinit, cipher_list_n);
ADD_ALL_TESTS(test_evp_decrypt_roundtrip_multistep, cipher_list_n);
ADD_ALL_TESTS(test_evp_oneshot_aead_zerolen, cipher_list_n);
+ ADD_ALL_TESTS(test_evp_aead_tag_direction, cipher_list_n);
ADD_ALL_TESTS(test_evp_init_seq, OSSL_NELEM(evp_init_tests));
ADD_ALL_TESTS(test_evp_reset, OSSL_NELEM(evp_reset_tests));