diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 2c09b89e31200..f770dc6deac8b 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -4171,22 +4171,24 @@ PHP_FUNCTION(openssl_verify) /* {{{ Seals data */ PHP_FUNCTION(openssl_seal) { - zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL; + zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL, *tag = NULL; HashTable *pubkeysht; - EVP_PKEY **pkeys; - int i, len1, len2, *eksl, nkeys, iv_len; - unsigned char iv_buf[EVP_MAX_IV_LENGTH + 1], *buf = NULL, **eks; + 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; char * data; size_t data_len; char *method; size_t method_len; const EVP_CIPHER *cipher; - EVP_CIPHER_CTX *ctx; + EVP_CIPHER_CTX *ctx = NULL; + size_t tag_len; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "szzas|z", &data, &data_len, - &sealdata, &ekeys, &pubkeys, &method, &method_len, &iv) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szzas|z!z!", &data, &data_len, + &sealdata, &ekeys, &pubkeys, &method, &method_len, &iv, &tag) == FAILURE) { RETURN_THROWS(); } + RETVAL_FALSE; PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data, 1); @@ -4194,19 +4196,32 @@ PHP_FUNCTION(openssl_seal) nkeys = pubkeysht ? zend_hash_num_elements(pubkeysht) : 0; if (!nkeys) { zend_argument_must_not_be_empty_error(4); - RETURN_THROWS(); + goto clean_exit; } cipher = php_openssl_get_evp_cipher_by_name(method); if (!cipher) { php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm"); - RETURN_FALSE; + goto clean_exit; } 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"); - RETURN_THROWS(); + 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; } pkeys = safe_emalloc(nkeys, sizeof(*pkeys), 0); @@ -4223,21 +4238,12 @@ 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); @@ -4246,19 +4252,23 @@ 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; } @@ -4276,21 +4286,35 @@ PHP_FUNCTION(openssl_seal) } else { efree(buf); } + RETVAL_LONG(len1 + len2); - EVP_CIPHER_CTX_free(ctx); clean_exit: - for (i=0; i 0) { if (!iv) { zend_argument_value_error(6, "cannot be null for the chosen cipher algorithm"); - RETURN_THROWS(); + goto clean_exit; } if ((size_t)cipher_iv_len != iv_len) { php_error_docref(NULL, E_WARNING, "IV length is invalid"); - RETURN_FALSE; + goto clean_exit; } iv_buf = (unsigned char *)iv; } else { @@ -4350,20 +4377,48 @@ 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) && - 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)) { + 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)) { 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; } - efree(buf); - EVP_PKEY_free(pkey); - EVP_CIPHER_CTX_free(ctx); +clean_exit: + if (buf) { + efree(buf); + } + if (pkey) { + EVP_PKEY_free(pkey); + } + if (ctx) { + EVP_CIPHER_CTX_free(ctx); + } } /* }}} */ diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 94902a4acf0da..56e96a27a6ab2 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -628,14 +628,15 @@ 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): int|false {} +function openssl_seal(#[\SensitiveParameter] string $data, &$sealed_data, &$encrypted_keys, array $public_key, string $cipher_algo, &$iv = null, &$tag = 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): bool {} +function openssl_open(string $data, #[\SensitiveParameter] &$output, string $encrypted_key, #[\SensitiveParameter] $private_key, string $cipher_algo, ?string $iv = null, ?string $tag = null): bool {} /** * @return array diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index 796582c185bb6..5ab604828e5cb 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 8233a8abc8ab7145d905d0fa51478edfe1e55a06 */ + * Stub hash: 91d576073b79bf4b441e44eccb0deaa2b79a6949 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_x509_export_to_file, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_TYPE_MASK(0, certificate, OpenSSLCertificate, MAY_BE_STRING, NULL) @@ -302,6 +302,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_seal, 0, 5, MAY_BE_LONG| ZEND_ARG_TYPE_INFO(0, public_key, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, cipher_algo, IS_STRING, 0) ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, iv, "null") + ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, tag, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_open, 0, 5, _IS_BOOL, 0) @@ -311,6 +312,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_open, 0, 5, _IS_BOOL, 0) ZEND_ARG_INFO(0, private_key) ZEND_ARG_TYPE_INFO(0, cipher_algo, IS_STRING, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, iv, IS_STRING, 1, "null") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, tag, IS_STRING, 1, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_get_md_methods, 0, 0, IS_ARRAY, 0) diff --git a/ext/openssl/tests/gh7737.phpt b/ext/openssl/tests/gh7737.phpt new file mode 100644 index 0000000000000..5136e3e295c1d --- /dev/null +++ b/ext/openssl/tests/gh7737.phpt @@ -0,0 +1,57 @@ +--TEST-- +GitHub Bug#7737 - openssl_seal/open() does not handle ciphers with Tags (e.g. AES-256-CGM) +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + 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"