From ad2143f3b06f629c8e6a5d3a4aa409935182de59 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:28:14 +0200 Subject: [PATCH 1/4] Fix arginfo/zpp violation if zend_hrtime is not available Part of GH-19210. Closes GH-19218. --- ext/standard/hrtime.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/hrtime.c b/ext/standard/hrtime.c index 6af8bfc965069..831e903e1cc38 100644 --- a/ext/standard/hrtime.c +++ b/ext/standard/hrtime.c @@ -46,7 +46,6 @@ delivered timestamp is monotonic and cannot be adjusted. */ PHP_FUNCTION(hrtime) { -#if ZEND_HRTIME_AVAILABLE bool get_as_num = 0; zend_hrtime_t t = zend_hrtime(); @@ -55,6 +54,7 @@ PHP_FUNCTION(hrtime) Z_PARAM_BOOL(get_as_num) ZEND_PARSE_PARAMETERS_END(); +#if ZEND_HRTIME_AVAILABLE if (UNEXPECTED(get_as_num)) { PHP_RETURN_HRTIME(t); } else { From beeeee29783604b80be465d738dc4eee635161c1 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:32:16 +0200 Subject: [PATCH 2/4] Handle broken hrtime in ftp Part of GH-19210. Closes GH-19219. --- ext/ftp/ftp.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index acdc1522e58fc..3f9a78a81f0f4 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -1471,7 +1471,8 @@ static int my_poll(php_socket_t fd, int events, int timeout) { if (n == -1 && php_socket_errno() == EINTR) { zend_hrtime_t delta_ns = zend_hrtime() - start_ns; - if (delta_ns > timeout_hr) { + /* delta_ns == 0 is only possible with a platform that does not support a high-res timer. */ + if (delta_ns > timeout_hr || UNEXPECTED(delta_ns == 0)) { #ifndef PHP_WIN32 errno = ETIMEDOUT; #endif From f94c11fff8475acc7b8a39eb4433bd594f73ac3b Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 25 Jul 2025 12:04:40 +0200 Subject: [PATCH 3/4] NEWS for hrtime in FTP and standard --- NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS b/NEWS index 89f24c4bfc450..2b21969dffbce 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,9 @@ PHP NEWS . Fixed bug GH-18581 (Coerce numeric string keys from iterators when argument unpacking). (ilutov) +- FTP: + . Fix theoretical issues with hrtime() not being available. (nielsdos) + - Hash: . Fix crash on clone failure. (nielsdos) @@ -34,6 +37,7 @@ PHP NEWS - Standard: . Fixed OSS Fuzz #433303828 (Leak in failed unserialize() with opcache). (ilutov) + . Fix theoretical issues with hrtime() not being available. (nielsdos) 31 Jul 2025, PHP 8.3.24 From b1fce8a98cecc4425c99aa1bf3315914556ccad6 Mon Sep 17 00:00:00 2001 From: Jakub Zelenka Date: Wed, 23 Jul 2025 16:56:37 +0200 Subject: [PATCH 4/4] Add digest algo param to public encrypt and private decrypt Specifically, it is added to openssl_public_encrypt() and openssl_private_decrypt() functions. The purpose is to specify digest algorithm for OEAP padding. It currently defaults to SHA1 for some OpenSSL versions which is not preferred for modern setup and causes problems in compatibility with web crypto. Closes GH-19223 --- NEWS | 4 ++ UPGRADING | 4 ++ ext/openssl/openssl.c | 45 ++++++++++++--- ext/openssl/openssl.stub.php | 4 +- ext/openssl/openssl_arginfo.h | 4 +- .../tests/openssl_private_decrypt_digest.phpt | 57 +++++++++++++++++++ 6 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 ext/openssl/tests/openssl_private_decrypt_digest.phpt diff --git a/NEWS b/NEWS index 2b055eec918e7..961870b6f3053 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,10 @@ PHP NEWS . Add support for CURLINFO_CONN_ID in curl_getinfo() (thecaliskan) . Add support for CURLINFO_QUEUE_TIME_T in curl_getinfo() (thecaliskan) +- OpenSSL: + . Add $digest_algo parameter to openssl_public_encrypt() and + openssl_private_decrypt() functions. (Jakub Zelenka) + - Reflection: . Fixed bug GH-19187 (ReflectionNamedType::getName() prints nullable type when retrieved from ReflectionProperty::getSettableType()). (ilutov) diff --git a/UPGRADING b/UPGRADING index 40d9069b91c2e..5a206fee6d115 100644 --- a/UPGRADING +++ b/UPGRADING @@ -327,6 +327,10 @@ PHP 8.5 UPGRADE NOTES - libxml: . libxml_set_external_entity_loader() now has a formal return type of true. +- OpenSSL: + . openssl_public_encrypt() and openssl_private_decrypt() have new parameter + $digest_algo that allows specifying hash digest algorith for OEAP padding. + - PCNTL: . pcntl_exec() now has a formal return type of false. . pcntl_waitid() takes an additional resource_usage argument to diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 6b2fcd7898b38..04879d1a51635 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -3725,6 +3725,29 @@ PHP_FUNCTION(openssl_cms_decrypt) /* }}} */ +/* Helper to set RSA padding and digest for OAEP */ +static int php_openssl_set_rsa_padding_and_digest(EVP_PKEY_CTX *ctx, zend_long padding, const char *digest_algo, const EVP_MD **pmd) +{ + if (EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0) { + return 0; + } + + if (digest_algo != NULL) { + const EVP_MD *md = php_openssl_get_evp_md_by_name(digest_algo); + if (md == NULL) { + php_error_docref(NULL, E_WARNING, "Unknown digest algorithm: %s", digest_algo); + return 0; + } + *pmd = md; + if (padding == RSA_PKCS1_OAEP_PADDING) { + if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0) { + return 0; + } + } + } + + return 1; +} /* {{{ Encrypts data with private key */ PHP_FUNCTION(openssl_private_encrypt) @@ -3780,10 +3803,12 @@ PHP_FUNCTION(openssl_private_decrypt) { zval *key, *crypted; zend_long padding = RSA_PKCS1_PADDING; - char * data; - size_t data_len; + char *data; + char *digest_algo = NULL; + size_t data_len, digest_algo_len = 0; + const EVP_MD *md = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|lp!", &data, &data_len, &crypted, &key, &padding, &digest_algo, &digest_algo_len) == FAILURE) { RETURN_THROWS(); } @@ -3798,7 +3823,7 @@ PHP_FUNCTION(openssl_private_decrypt) size_t out_len = 0; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); if (!ctx || EVP_PKEY_decrypt_init(ctx) <= 0 || - EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0 || + !php_openssl_set_rsa_padding_and_digest(ctx, padding, digest_algo, &md) || EVP_PKEY_decrypt(ctx, NULL, &out_len, (unsigned char *) data, data_len) <= 0) { php_openssl_store_errors(); RETVAL_FALSE; @@ -3820,6 +3845,7 @@ PHP_FUNCTION(openssl_private_decrypt) RETVAL_TRUE; cleanup: + php_openssl_release_evp_md(md); EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(pkey); } @@ -3831,9 +3857,11 @@ PHP_FUNCTION(openssl_public_encrypt) zval *key, *crypted; zend_long padding = RSA_PKCS1_PADDING; char * data; - size_t data_len; + char *digest_algo = NULL; + size_t data_len, digest_algo_len = 0; + const EVP_MD *md = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|lp!", &data, &data_len, &crypted, &key, &padding, &digest_algo, &digest_algo_len) == FAILURE) { RETURN_THROWS(); } @@ -3848,7 +3876,7 @@ PHP_FUNCTION(openssl_public_encrypt) size_t out_len = 0; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL); if (!ctx || EVP_PKEY_encrypt_init(ctx) <= 0 || - EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0 || + !php_openssl_set_rsa_padding_and_digest(ctx, padding, digest_algo, &md) || EVP_PKEY_encrypt(ctx, NULL, &out_len, (unsigned char *) data, data_len) <= 0) { php_openssl_store_errors(); RETVAL_FALSE; @@ -3869,6 +3897,7 @@ PHP_FUNCTION(openssl_public_encrypt) RETVAL_TRUE; cleanup: + php_openssl_release_evp_md(md); EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(pkey); } @@ -3879,7 +3908,7 @@ PHP_FUNCTION(openssl_public_decrypt) { zval *key, *crypted; zend_long padding = RSA_PKCS1_PADDING; - char * data; + char *data; size_t data_len; if (zend_parse_parameters(ZEND_NUM_ARGS(), "szz|l", &data, &data_len, &crypted, &key, &padding) == FAILURE) { diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 1fe3a9fc168eb..b35c15f2b524c 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -575,13 +575,13 @@ function openssl_private_encrypt(#[\SensitiveParameter] string $data, &$encrypte * @param string $decrypted_data * @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key */ -function openssl_private_decrypt(string $data, #[\SensitiveParameter] &$decrypted_data, #[\SensitiveParameter] $private_key, int $padding = OPENSSL_PKCS1_PADDING): bool {} +function openssl_private_decrypt(string $data, #[\SensitiveParameter] &$decrypted_data, #[\SensitiveParameter] $private_key, int $padding = OPENSSL_PKCS1_PADDING, ?string $digest_algo = null): bool {} /** * @param string $encrypted_data * @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $public_key */ -function openssl_public_encrypt(#[\SensitiveParameter] string $data, &$encrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING): bool {} +function openssl_public_encrypt(#[\SensitiveParameter] string $data, &$encrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING, ?string $digest_algo = null): bool {} /** * @param string $decrypted_data diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index 9ac53cf47aa88..ac513dd945587 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: a42bd7dec0a5e011983ce08b5e31cd8718247501 */ + * Stub hash: 4186e01a05ec179f1f2196a2afd1860671f793d5 */ 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) @@ -258,6 +258,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_private_decrypt, 0, 3, _ ZEND_ARG_INFO(1, decrypted_data) ZEND_ARG_INFO(0, private_key) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, padding, IS_LONG, 0, "OPENSSL_PKCS1_PADDING") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, digest_algo, IS_STRING, 1, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_public_encrypt, 0, 3, _IS_BOOL, 0) @@ -265,6 +266,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_public_encrypt, 0, 3, _I ZEND_ARG_INFO(1, encrypted_data) ZEND_ARG_INFO(0, public_key) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, padding, IS_LONG, 0, "OPENSSL_PKCS1_PADDING") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, digest_algo, IS_STRING, 1, "null") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_public_decrypt, 0, 3, _IS_BOOL, 0) diff --git a/ext/openssl/tests/openssl_private_decrypt_digest.phpt b/ext/openssl/tests/openssl_private_decrypt_digest.phpt new file mode 100644 index 0000000000000..9bb07ba71eac1 --- /dev/null +++ b/ext/openssl/tests/openssl_private_decrypt_digest.phpt @@ -0,0 +1,57 @@ +--TEST-- +openssl_private_decrypt() with digest algorithm tests +--EXTENSIONS-- +openssl +--FILE-- +getMessage()); +} + +openssl_public_encrypt($data, $encrypted_pkcs1, $pubkey, OPENSSL_PKCS1_PADDING); +var_dump(openssl_private_decrypt($encrypted_pkcs1, $output_pkcs1, $privkey, OPENSSL_PKCS1_PADDING, "sha256")); +var_dump($output_pkcs1); +?> +--EXPECTF-- +bool(true) +string(56) "Testing openssl_private_decrypt() with digest algorithms" +bool(true) +string(56) "Testing openssl_private_decrypt() with digest algorithms" +bool(false) +NULL + +Warning: openssl_private_decrypt(): Unknown digest algorithm: invalid_hash in %s on line %d +bool(false) +NULL +string(85) "openssl_private_decrypt(): Argument #5 ($digest_algo) must not contain any null bytes" +bool(true) +string(56) "Testing openssl_private_decrypt() with digest algorithms"