Commit 040ea4ab5f9 for php.net

commit 040ea4ab5f9e2390ead553104a3a569768c678a5
Author: Jakub Zelenka <bukka@php.net>
Date:   Sat Dec 13 11:42:10 2025 +0100

    Revert "Fix GH-7737: openssl_seal/openssl_open do not handle tagged algorithm…" (#20698)

    This reverts commit 2ee5e6b432c7ed51ff7296522c123551975be95b.

diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index f770dc6deac..2c09b89e312 100644
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -4171,24 +4171,22 @@ PHP_FUNCTION(openssl_verify)
 /* {{{ Seals data */
 PHP_FUNCTION(openssl_seal)
 {
-	zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL, *tag = NULL;
+	zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL;
 	HashTable *pubkeysht;
-	EVP_PKEY **pkeys = NULL;
-	int i, len1, len2, *eksl = NULL, nkeys = 0, iv_len;
-	unsigned char iv_buf[EVP_MAX_IV_LENGTH + 1], *buf = NULL, **eks = NULL;
+	EVP_PKEY **pkeys;
+	int i, len1, len2, *eksl, nkeys, iv_len;
+	unsigned char iv_buf[EVP_MAX_IV_LENGTH + 1], *buf = NULL, **eks;
 	char * data;
 	size_t data_len;
 	char *method;
 	size_t method_len;
 	const EVP_CIPHER *cipher;
-	EVP_CIPHER_CTX *ctx = NULL;
-	size_t tag_len;
+	EVP_CIPHER_CTX *ctx;

-	if (zend_parse_parameters(ZEND_NUM_ARGS(), "szzas|z!z!", &data, &data_len,
-				&sealdata, &ekeys, &pubkeys, &method, &method_len, &iv, &tag) == FAILURE) {
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "szzas|z", &data, &data_len,
+				&sealdata, &ekeys, &pubkeys, &method, &method_len, &iv) == FAILURE) {
 		RETURN_THROWS();
 	}
-	RETVAL_FALSE;

 	PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data, 1);

@@ -4196,32 +4194,19 @@ PHP_FUNCTION(openssl_seal)
 	nkeys = pubkeysht ? zend_hash_num_elements(pubkeysht) : 0;
 	if (!nkeys) {
 		zend_argument_must_not_be_empty_error(4);
-		goto clean_exit;
+		RETURN_THROWS();
 	}

 	cipher = php_openssl_get_evp_cipher_by_name(method);
 	if (!cipher) {
 		php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
-		goto clean_exit;
+		RETURN_FALSE;
 	}

 	iv_len = EVP_CIPHER_iv_length(cipher);
 	if (!iv && iv_len > 0) {
 		zend_argument_value_error(6, "cannot be null for the chosen cipher algorithm");
-		goto clean_exit;
-	}
-
-	ctx = EVP_CIPHER_CTX_new();
-	if (ctx == NULL || !EVP_EncryptInit(ctx,cipher,NULL,NULL)) {
-		php_openssl_store_errors();
-		goto clean_exit;
-	}
-
-	tag_len = EVP_CIPHER_CTX_get_tag_length(ctx);
-	if ((tag != NULL) != (tag_len > 0)) {
-		const char *imp = tag ? "cannot" : "must";
-		zend_argument_value_error(7, "%s be specified for the chosen cipher algorithm", imp);
-		goto clean_exit;
+		RETURN_THROWS();
 	}

 	pkeys = safe_emalloc(nkeys, sizeof(*pkeys), 0);
@@ -4238,12 +4223,21 @@ PHP_FUNCTION(openssl_seal)
 			if (!EG(exception)) {
 				php_error_docref(NULL, E_WARNING, "Not a public key (%dth member of pubkeys)", i+1);
 			}
+			RETVAL_FALSE;
 			goto clean_exit;
 		}
 		eks[i] = emalloc(EVP_PKEY_size(pkeys[i]) + 1);
 		i++;
 	} ZEND_HASH_FOREACH_END();

+	ctx = EVP_CIPHER_CTX_new();
+	if (ctx == NULL || !EVP_EncryptInit(ctx,cipher,NULL,NULL)) {
+		EVP_CIPHER_CTX_free(ctx);
+		php_openssl_store_errors();
+		RETVAL_FALSE;
+		goto clean_exit;
+	}
+
 	/* allocate one byte extra to make room for \0 */
 	buf = emalloc(data_len + EVP_CIPHER_CTX_block_size(ctx));
 	EVP_CIPHER_CTX_reset(ctx);
@@ -4252,23 +4246,19 @@ PHP_FUNCTION(openssl_seal)
 			!EVP_SealUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) ||
 			!EVP_SealFinal(ctx, buf + len1, &len2)) {
 		efree(buf);
+		EVP_CIPHER_CTX_free(ctx);
 		php_openssl_store_errors();
+		RETVAL_FALSE;
 		goto clean_exit;
 	}

-	if (tag) {
-		zend_string *tag_str = zend_string_alloc(tag_len, 0);
-		EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, ZSTR_LEN(tag_str), ZSTR_VAL(tag_str));
-		ZSTR_VAL(tag_str)[ZSTR_LEN(tag_str)] = 0;
-		ZEND_TRY_ASSIGN_REF_NEW_STR(tag, tag_str);
-	}
-
 	if (len1 + len2 > 0) {
 		ZEND_TRY_ASSIGN_REF_NEW_STR(sealdata, zend_string_init((char*)buf, len1 + len2, 0));
 		efree(buf);

 		ekeys = zend_try_array_init(ekeys);
 		if (!ekeys) {
+			EVP_CIPHER_CTX_free(ctx);
 			goto clean_exit;
 		}

@@ -4286,35 +4276,21 @@ PHP_FUNCTION(openssl_seal)
 	} else {
 		efree(buf);
 	}
-
 	RETVAL_LONG(len1 + len2);
+	EVP_CIPHER_CTX_free(ctx);

 clean_exit:
-	if (ctx) {
-		EVP_CIPHER_CTX_free(ctx);
-	}
-
-	if (pkeys) {
-		for (i=0; i<nkeys; i++) {
-			if (pkeys[i] != NULL) {
-				EVP_PKEY_free(pkeys[i]);
-			}
+	for (i=0; i<nkeys; i++) {
+		if (pkeys[i] != NULL) {
+			EVP_PKEY_free(pkeys[i]);
 		}
-		efree(pkeys);
-	}
-
-	if (eks) {
-		for (i=0; i<nkeys; i++) {
-			if (eks[i]) {
-				efree(eks[i]);
-			}
+		if (eks[i]) {
+			efree(eks[i]);
 		}
-		efree(eks);
-	}
-
-	if (eksl) {
-		efree(eksl);
 	}
+	efree(eks);
+	efree(eksl);
+	efree(pkeys);
 }
 /* }}} */

@@ -4322,25 +4298,22 @@ PHP_FUNCTION(openssl_seal)
 PHP_FUNCTION(openssl_open)
 {
 	zval *privkey, *opendata;
-	EVP_PKEY *pkey = NULL;
+	EVP_PKEY *pkey;
 	int len1, len2, cipher_iv_len;
-	unsigned char *buf = NULL, *iv_buf;
-	EVP_CIPHER_CTX *ctx = NULL;
+	unsigned char *buf, *iv_buf;
+	EVP_CIPHER_CTX *ctx;
 	char * data;
 	size_t data_len;
 	char * ekey;
 	size_t ekey_len;
 	char *method, *iv = NULL;
 	size_t method_len, iv_len = 0;
-	zend_string *tag = NULL;
 	const EVP_CIPHER *cipher;
-	int tag_len;

-	if (zend_parse_parameters(ZEND_NUM_ARGS(), "szszs|s!S!", &data, &data_len, &opendata,
-				&ekey, &ekey_len, &privkey, &method, &method_len, &iv, &iv_len, &tag) == FAILURE) {
+	if (zend_parse_parameters(ZEND_NUM_ARGS(), "szszs|s!", &data, &data_len, &opendata,
+				&ekey, &ekey_len, &privkey, &method, &method_len, &iv, &iv_len) == FAILURE) {
 		RETURN_THROWS();
 	}
-	RETVAL_FALSE;

 	PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data, 1);
 	PHP_OPENSSL_CHECK_SIZE_T_TO_INT(ekey_len, ekey, 3);
@@ -4350,24 +4323,24 @@ PHP_FUNCTION(openssl_open)
 		if (!EG(exception)) {
 			php_error_docref(NULL, E_WARNING, "Unable to coerce parameter 4 into a private key");
 		}
-		goto clean_exit;
+		RETURN_FALSE;
 	}

 	cipher = php_openssl_get_evp_cipher_by_name(method);
 	if (!cipher) {
 		php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
-		goto clean_exit;
+		RETURN_FALSE;
 	}

 	cipher_iv_len = EVP_CIPHER_iv_length(cipher);
 	if (cipher_iv_len > 0) {
 		if (!iv) {
 			zend_argument_value_error(6, "cannot be null for the chosen cipher algorithm");
-			goto clean_exit;
+			RETURN_THROWS();
 		}
 		if ((size_t)cipher_iv_len != iv_len) {
 			php_error_docref(NULL, E_WARNING, "IV length is invalid");
-			goto clean_exit;
+			RETURN_FALSE;
 		}
 		iv_buf = (unsigned char *)iv;
 	} else {
@@ -4377,48 +4350,20 @@ PHP_FUNCTION(openssl_open)
 	buf = emalloc(data_len + 1);

 	ctx = EVP_CIPHER_CTX_new();
-	if (ctx == NULL || !EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, (int)ekey_len, iv_buf, pkey)) {
-		php_openssl_store_errors();
-		goto clean_exit;
-	}
-
-	tag_len = EVP_CIPHER_CTX_get_tag_length(ctx);
-	if ((tag != NULL) != (tag_len > 0)) {
-		const char *imp = tag ? "cannot" : "must";
-		zend_argument_value_error(7, "%s be specified for the chosen cipher algorithm", imp);
-		goto clean_exit;
-	}
-	if (tag) {
-		if (ZSTR_LEN(tag) != tag_len) {
-			zend_argument_value_error(7, "must be %d bytes long", tag_len);
-			goto clean_exit;
-		}
-
-		if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, ZSTR_LEN(tag), ZSTR_VAL(tag))) {
-			php_openssl_store_errors();
-			goto clean_exit;
-		}
-	}
-
-	if (EVP_OpenUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) &&
-		EVP_OpenFinal(ctx, buf + len1, &len2) && (len1 + len2 > 0)) {
+	if (ctx != NULL && EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, (int)ekey_len, iv_buf, pkey) &&
+			EVP_OpenUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) &&
+			EVP_OpenFinal(ctx, buf + len1, &len2) && (len1 + len2 > 0)) {
 		buf[len1 + len2] = '\0';
 		ZEND_TRY_ASSIGN_REF_NEW_STR(opendata, zend_string_init((char*)buf, len1 + len2, 0));
 		RETVAL_TRUE;
 	} else {
 		php_openssl_store_errors();
+		RETVAL_FALSE;
 	}

-clean_exit:
-	if (buf) {
-		efree(buf);
-	}
-	if (pkey) {
-		EVP_PKEY_free(pkey);
-	}
-	if (ctx) {
-		EVP_CIPHER_CTX_free(ctx);
-	}
+	efree(buf);
+	EVP_PKEY_free(pkey);
+	EVP_CIPHER_CTX_free(ctx);
 }
 /* }}} */

diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php
index 56e96a27a6a..94902a4acf0 100644
--- a/ext/openssl/openssl.stub.php
+++ b/ext/openssl/openssl.stub.php
@@ -628,15 +628,14 @@ function openssl_verify(string $data, string $signature, $public_key, string|int
  * @param string $sealed_data
  * @param array $encrypted_keys
  * @param string $iv
- * @param string $tag
  */
-function openssl_seal(#[\SensitiveParameter] string $data, &$sealed_data, &$encrypted_keys, array $public_key, string $cipher_algo, &$iv = null, &$tag = null): int|false {}
+function openssl_seal(#[\SensitiveParameter] string $data, &$sealed_data, &$encrypted_keys, array $public_key, string $cipher_algo, &$iv = null): int|false {}

 /**
  * @param string $output
  * @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key
  */
-function openssl_open(string $data, #[\SensitiveParameter] &$output, string $encrypted_key, #[\SensitiveParameter] $private_key, string $cipher_algo, ?string $iv = null, ?string $tag = null): bool {}
+function openssl_open(string $data, #[\SensitiveParameter] &$output, string $encrypted_key, #[\SensitiveParameter] $private_key, string $cipher_algo, ?string $iv = null): bool {}

 /**
  * @return array<int, string>
diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h
index 5ab604828e5..796582c185b 100644
Binary files a/ext/openssl/openssl_arginfo.h and b/ext/openssl/openssl_arginfo.h differ
diff --git a/ext/openssl/tests/gh7737.phpt b/ext/openssl/tests/gh7737.phpt
deleted file mode 100644
index 5136e3e295c..00000000000
--- a/ext/openssl/tests/gh7737.phpt
+++ /dev/null
@@ -1,57 +0,0 @@
---TEST--
-GitHub Bug#7737 - openssl_seal/open() does not handle ciphers with Tags (e.g. AES-256-CGM)
---EXTENSIONS--
-openssl
---SKIPIF--
-<?php
-// Skip if aes-256-cgm is not available in this build.
-in_array('aes-256-gcm', openssl_get_cipher_methods()) or print 'skip';
-?>
---FILE--
-<?php
-
-const CIPHER_ALGO = 'aes-256-gcm';
-const KEY_TYPE = OPENSSL_KEYTYPE_RSA;
-const PLAINTEXT = 'Test Data String';
-
-(function() {
-    $key = openssl_pkey_new(['type' => KEY_TYPE]);
-    define('KEY_PUBLIC', openssl_pkey_get_details($key)['key']);
-    define('KEY_PRIVATE', openssl_pkey_get_private($key));
-})();
-
-echo 'Plaintext: '; var_dump(PLAINTEXT);
-
-$sealResult = openssl_seal(PLAINTEXT,
-                           $sealedData, /* out */
-                           $sealedKeys, /* out */
-                           [KEY_PUBLIC],
-                           CIPHER_ALGO,
-                           $iv, /* out */
-                           $tag); /* out */
-
-echo 'Seal Result: '; var_dump($sealResult);
-echo 'Sealed Data: '; var_dump(strlen($sealedData));
-echo 'IV Length: '; var_dump(strlen($iv));
-echo 'Tag Length: '; var_dump(strlen($tag));
-
-$unsealResult = openssl_open($sealedData,
-                             $unsealedData, /* out */
-                             $sealedKeys[0],
-                             KEY_PRIVATE,
-                             CIPHER_ALGO,
-                             $iv,
-                             $tag);
-
-echo 'Unseal Result: '; var_dump($unsealResult);
-echo 'Unsealed Data: '; var_dump($unsealedData);
-
-?>
---EXPECT--
-Plaintext: string(16) "Test Data String"
-Seal Result: int(16)
-Sealed Data: int(16)
-IV Length: int(12)
-Tag Length: int(16)
-Unseal Result: bool(true)
-Unsealed Data: string(16) "Test Data String"