Commit ae487f4f666 for php.net

commit ae487f4f666ec4d59baa4c42c577ccdf6574a6e5
Author: Jakub Zelenka <bukka@php.net>
Date:   Wed Jul 22 22:16:42 2026 +0200

    Add $salt_length param to openssl_sign and openssl_verify (#20681)

    This allows controlling the RSA-PSS salt length and enforcing an expected
    salt length when verifying. It also adds OPENSSL_RSA_PSS_SALTLEN_* constants.
    This is needed for some application. Custom TLS implementation is one example

diff --git a/NEWS b/NEWS
index 217d6b959c8..3c5101c096c 100644
--- a/NEWS
+++ b/NEWS
@@ -132,6 +132,8 @@ PHP                                                                        NEWS
     preloaded trait). (iliaal)

 - OpenSSL:
+  . Added $salt_length parameter to openssl_sign() and openssl_verify() with
+    new OPENSSL_RSA_PSS_SALTLEN_* constants. (Jakub Zelenka)
   . Fixed timeout for supplemental read at end of a blocking stream in SSL
     stream wrapper. (ilutov)
   . Fixed stream_socket_get_crypto_status() after supplemental read. (ilutov)
diff --git a/UPGRADING b/UPGRADING
index 077a7faf656..97670e09246 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -383,6 +383,10 @@ PHP 8.6 UPGRADE NOTES
 - OpenSSL:
   . Output of openssl_x509_parse() contains criticalExtensions listing all
     critical certificate extensions.
+  . openssl_sign() and openssl_verify() now have an additional optional
+    argument $salt_length that allows controlling the RSA-PSS salt length
+    when OPENSSL_PKCS1_PSS_PADDING is used. It accepts an explicit length or
+    one of the new OPENSSL_RSA_PSS_SALTLEN_* constants.

 - PDO_DBLIB:
   . When using persistent connections, there is now a liveness check in the
@@ -513,6 +517,11 @@ PHP 8.6 UPGRADE NOTES
   . CURL_SEEKFUNC_FAIL.
   . CURL_SEEKFUNC_CANTSEEK.

+- OpenSSL:
+  . OPENSSL_RSA_PSS_SALTLEN_DIGEST.
+  . OPENSSL_RSA_PSS_SALTLEN_AUTO.
+  . OPENSSL_RSA_PSS_SALTLEN_MAX.
+
 - Sockets:
   . TCP_USER_TIMEOUT (Linux only).
   . AF_UNSPEC.
diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c
index fa91f232bcd..a3d8edd4c8d 100644
--- a/ext/openssl/openssl.c
+++ b/ext/openssl/openssl.c
@@ -4525,6 +4525,27 @@ static zend_result php_openssl_setup_rsa_padding(EVP_PKEY_CTX *pctx, EVP_PKEY *p
 	return SUCCESS;
 }

+static zend_result php_openssl_setup_rsa_pss_salt_length(EVP_PKEY_CTX *pctx, EVP_PKEY *pkey, zend_long padding, zend_long salt_length)
+{
+	/* Only apply if using PSS padding */
+	if (padding != RSA_PKCS1_PSS_PADDING) {
+		return SUCCESS;
+	}
+
+	/* Only apply to RSA keys */
+	if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA && EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA_PSS) {
+		return SUCCESS;
+	}
+
+	if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, (int)salt_length) <= 0) {
+		php_openssl_store_errors();
+		php_error_docref(NULL, E_WARNING, "Could not set RSA-PSS salt length");
+		return FAILURE;
+	}
+
+	return SUCCESS;
+}
+
 /* {{{ Signs data */
 PHP_FUNCTION(openssl_sign)
 {
@@ -4538,16 +4559,18 @@ PHP_FUNCTION(openssl_sign)
 	zend_long method_long = OPENSSL_ALGO_SHA1;
 	const EVP_MD *mdtype;
 	zend_long padding = 0;
+	zend_long salt_length = RSA_PSS_SALTLEN_AUTO;
 	EVP_PKEY_CTX *pctx;
 	bool can_default_digest = ZEND_THREEWAY_COMPARE(PHP_OPENSSL_API_VERSION, 0x30000) >= 0;

-	ZEND_PARSE_PARAMETERS_START(3, 5)
+	ZEND_PARSE_PARAMETERS_START(3, 6)
 		Z_PARAM_STRING(data, data_len)
 		Z_PARAM_ZVAL(signature)
 		Z_PARAM_ZVAL(key)
 		Z_PARAM_OPTIONAL
 		Z_PARAM_STR_OR_LONG(method_str, method_long)
 		Z_PARAM_LONG(padding)
+		Z_PARAM_LONG(salt_length)
 	ZEND_PARSE_PARAMETERS_END();

 	pkey = php_openssl_pkey_from_zval(key, 0, "", 0, 3);
@@ -4568,12 +4591,14 @@ PHP_FUNCTION(openssl_sign)
 		php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
 		RETURN_FALSE;
 	}
+	PHP_OPENSSL_CHECK_LONG_TO_INT(salt_length, salt_length, 6);

 	md_ctx = EVP_MD_CTX_create();
 	size_t siglen;
 	if (md_ctx != NULL &&
 			EVP_DigestSignInit(md_ctx, &pctx, mdtype, NULL, pkey) &&
 			php_openssl_setup_rsa_padding(pctx, pkey, padding) == SUCCESS &&
+			php_openssl_setup_rsa_pss_salt_length(pctx, pkey, padding, salt_length) == SUCCESS &&
 			EVP_DigestSign(md_ctx, NULL, &siglen, (unsigned char*)data, data_len) &&
 			(sigbuf = zend_string_alloc(siglen, 0)) != NULL &&
 			EVP_DigestSign(md_ctx, (unsigned char*)ZSTR_VAL(sigbuf), &siglen, (unsigned char*)data, data_len)) {
@@ -4607,19 +4632,22 @@ PHP_FUNCTION(openssl_verify)
 	zend_string *method_str = NULL;
 	zend_long method_long = OPENSSL_ALGO_SHA1;
 	zend_long padding = 0;
+	zend_long salt_length = RSA_PSS_SALTLEN_AUTO;
 	EVP_PKEY_CTX *pctx;
 	bool can_default_digest = ZEND_THREEWAY_COMPARE(PHP_OPENSSL_API_VERSION, 0x30000) >= 0;

-	ZEND_PARSE_PARAMETERS_START(3, 5)
+	ZEND_PARSE_PARAMETERS_START(3, 6)
 		Z_PARAM_STRING(data, data_len)
 		Z_PARAM_STRING(signature, signature_len)
 		Z_PARAM_ZVAL(key)
 		Z_PARAM_OPTIONAL
 		Z_PARAM_STR_OR_LONG(method_str, method_long)
 		Z_PARAM_LONG(padding)
+		Z_PARAM_LONG(salt_length)
 	ZEND_PARSE_PARAMETERS_END();

 	PHP_OPENSSL_CHECK_SIZE_T_TO_UINT(signature_len, signature, 2);
+	PHP_OPENSSL_CHECK_LONG_TO_INT(salt_length, salt_length, 6);

 	if (method_str) {
 		mdtype = php_openssl_get_evp_md_by_name(ZSTR_VAL(method_str));
@@ -4648,7 +4676,8 @@ PHP_FUNCTION(openssl_verify)
 	}

 	if (!EVP_DigestVerifyInit(md_ctx, &pctx, mdtype, NULL, pkey) ||
-			php_openssl_setup_rsa_padding(pctx, pkey, padding) == FAILURE) {
+			php_openssl_setup_rsa_padding(pctx, pkey, padding) == FAILURE ||
+			php_openssl_setup_rsa_pss_salt_length(pctx, pkey, padding, salt_length) == FAILURE) {
 		php_openssl_store_errors();
 		err = -1;
 		goto cleanup;
diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php
index 6080ac32390..3d3fa3ea634 100644
--- a/ext/openssl/openssl.stub.php
+++ b/ext/openssl/openssl.stub.php
@@ -323,6 +323,22 @@ public function __unserialize(array $data): void {}
  */
 const OPENSSL_PKCS1_PSS_PADDING = UNKNOWN;

+/**
+ * @var int
+ * @cvalue RSA_PSS_SALTLEN_DIGEST
+ */
+const OPENSSL_RSA_PSS_SALTLEN_DIGEST = UNKNOWN;
+/**
+ * @var int
+ * @cvalue RSA_PSS_SALTLEN_AUTO
+ */
+const OPENSSL_RSA_PSS_SALTLEN_AUTO = UNKNOWN;
+/**
+ * @var int
+ * @cvalue RSA_PSS_SALTLEN_MAX
+ */
+const OPENSSL_RSA_PSS_SALTLEN_MAX = UNKNOWN;
+
 /* Informational stream wrapper constants */

 /**
@@ -679,10 +695,10 @@ function openssl_error_string(): string|false {}
  * @param string $signature
  * @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key
  */
-function openssl_sign(string $data, &$signature, #[\SensitiveParameter] $private_key, string|int $algorithm = OPENSSL_ALGO_SHA1, int $padding = 0): bool {}
+function openssl_sign(string $data, &$signature, #[\SensitiveParameter] $private_key, string|int $algorithm = OPENSSL_ALGO_SHA1, int $padding = 0, int $salt_length = OPENSSL_RSA_PSS_SALTLEN_AUTO): bool {}

 /** @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $public_key */
-function openssl_verify(string $data, string $signature, $public_key, string|int $algorithm = OPENSSL_ALGO_SHA1, int $padding = 0): int|false {}
+function openssl_verify(string $data, string $signature, $public_key, string|int $algorithm = OPENSSL_ALGO_SHA1, int $padding = 0, int $salt_length = OPENSSL_RSA_PSS_SALTLEN_AUTO): int|false {}

 /**
  * @param string $sealed_data
diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h
index caf47a256e7..b1742dcb05f 100644
Binary files a/ext/openssl/openssl_arginfo.h and b/ext/openssl/openssl_arginfo.h differ
diff --git a/ext/openssl/tests/openssl_sign_basic.phpt b/ext/openssl/tests/openssl_sign_basic.phpt
index 9d2edbec59f..d8880a6a189 100644
--- a/ext/openssl/tests/openssl_sign_basic.phpt
+++ b/ext/openssl/tests/openssl_sign_basic.phpt
@@ -7,7 +7,6 @@
 $data = "Testing openssl_sign()";
 $privkey = "file://" . __DIR__ . "/private_rsa_1024.key";
 $wrong = "wrong";
-
 var_dump(openssl_sign($data, $sign1, $privkey, OPENSSL_ALGO_SHA256));
 var_dump(bin2hex($sign1));
 var_dump(openssl_sign($data, $sign2, $privkey, OPENSSL_ALGO_SHA256));
@@ -17,6 +16,10 @@
 var_dump(openssl_sign($data, $sign2, $privkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING));
 var_dump(strlen($sign2));
 var_dump($sign1 === $sign2);
+var_dump(openssl_sign($data, $sign3, $privkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, OPENSSL_RSA_PSS_SALTLEN_DIGEST));
+var_dump(strlen($sign3));
+var_dump(openssl_sign($data, $sign4, $privkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, 32));
+var_dump(strlen($sign4));
 var_dump(openssl_sign($data, $sign, $wrong));
 ?>
 --EXPECTF--
@@ -29,6 +32,10 @@
 bool(true)
 int(128)
 bool(false)
+bool(true)
+int(128)
+bool(true)
+int(128)

 Warning: openssl_sign(): Supplied key param cannot be coerced into a private key in %s on line %d
 bool(false)
diff --git a/ext/openssl/tests/openssl_verify_basic.phpt b/ext/openssl/tests/openssl_verify_basic.phpt
index 1b28c137108..3bcf3ba0e66 100644
--- a/ext/openssl/tests/openssl_verify_basic.phpt
+++ b/ext/openssl/tests/openssl_verify_basic.phpt
@@ -13,6 +13,14 @@
 openssl_sign($data, $sign, $privkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING);
 var_dump(openssl_verify($data, $sign, $pubkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING));
 var_dump(openssl_verify($data, $sign, $pubkey, OPENSSL_ALGO_SHA256));
+openssl_sign($data, $sign_pss, $privkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, OPENSSL_RSA_PSS_SALTLEN_DIGEST);
+var_dump(openssl_verify($data, $sign_pss, $pubkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, OPENSSL_RSA_PSS_SALTLEN_DIGEST));
+var_dump(openssl_verify($data, $sign_pss, $pubkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, 32));
+var_dump(openssl_verify($data, $sign_pss, $pubkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, OPENSSL_RSA_PSS_SALTLEN_AUTO));
+var_dump(openssl_verify($data, $sign_pss, $pubkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, 20));
+openssl_sign($data, $sign_pss, $privkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, OPENSSL_RSA_PSS_SALTLEN_MAX);
+var_dump(openssl_verify($data, $sign_pss, $pubkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, OPENSSL_RSA_PSS_SALTLEN_MAX));
+var_dump(openssl_verify($data, $sign_pss, $pubkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING, OPENSSL_RSA_PSS_SALTLEN_DIGEST));
 openssl_sign($data, $sign, $privkey, OPENSSL_ALGO_SHA256);
 var_dump(openssl_verify($data, $sign, $pubkey, OPENSSL_ALGO_SHA256));
 var_dump(openssl_verify($data, $sign, $privkey, OPENSSL_ALGO_SHA256));
@@ -24,6 +32,12 @@
 int(1)
 int(0)
 int(1)
+int(1)
+int(1)
+int(0)
+int(1)
+int(0)
+int(1)

 Warning: openssl_verify(): Supplied key param cannot be coerced into a public key in %s on line %d
 bool(false)