[php-src] master: Add $salt_length param to openssl_sign and openssl_verify (#20681)
Jakub Zelenka via GitHub <[email protected]> Wed, 22 Jul 2026 20:16:45 +0000
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Jakub Zelenka (bukka)
Committer: GitHub (web-flow)
Pusher: bukka
Date: 2026-07-22T22:16:42+02:00
Commit: https://github.com/php/php-src/commit/ae487f4f666ec4d59baa4c42c577ccdf6574a6e5
Raw diff: https://github.com/php/php-src/commit/ae487f4f666ec4d59baa4c42c577ccdf6574a6e5.diff
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
Changed paths:
M NEWS
M UPGRADING
M ext/openssl/openssl.c
M ext/openssl/openssl.stub.php
M ext/openssl/openssl_arginfo.h
M ext/openssl/tests/openssl_sign_basic.phpt
M ext/openssl/tests/openssl_verify_basic.phpt
Diff:
diff --git a/NEWS b/NEWS
index 217d6b959c81..3c5101c096cb 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 077a7faf6562..97670e09246d 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 fa91f232bcdf..a3d8edd4c8d4 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 6080ac323903..3d3fa3ea634f 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 caf47a256e78..b1742dcb05f8 100644
--- a/ext/openssl/openssl_arginfo.h
+++ b/ext/openssl/openssl_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit openssl.stub.php instead.
- * Stub hash: 4d38e81a2f73bb6dd4bbe7a3e0b8ba86600654e2 */
+ * Stub hash: 7cad995b734d69f98d489edb97a7878a4ea8f47e */
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)
@@ -285,6 +285,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_sign, 0, 3, _IS_BOOL, 0)
ZEND_ARG_INFO(0, private_key)
ZEND_ARG_TYPE_MASK(0, algorithm, MAY_BE_STRING|MAY_BE_LONG, "OPENSSL_ALGO_SHA1")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, padding, IS_LONG, 0, "0")
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, salt_length, IS_LONG, 0, "OPENSSL_RSA_PSS_SALTLEN_AUTO")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_verify, 0, 3, MAY_BE_LONG|MAY_BE_FALSE)
@@ -293,6 +294,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_verify, 0, 3, MAY_BE_LON
ZEND_ARG_INFO(0, public_key)
ZEND_ARG_TYPE_MASK(0, algorithm, MAY_BE_STRING|MAY_BE_LONG, "OPENSSL_ALGO_SHA1")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, padding, IS_LONG, 0, "0")
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, salt_length, IS_LONG, 0, "OPENSSL_RSA_PSS_SALTLEN_AUTO")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_seal, 0, 5, MAY_BE_LONG|MAY_BE_FALSE)
@@ -676,6 +678,9 @@ static void register_openssl_symbols(int module_number)
REGISTER_LONG_CONSTANT("OPENSSL_NO_PADDING", RSA_NO_PADDING, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("OPENSSL_PKCS1_OAEP_PADDING", RSA_PKCS1_OAEP_PADDING, CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("OPENSSL_PKCS1_PSS_PADDING", RSA_PKCS1_PSS_PADDING, CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("OPENSSL_RSA_PSS_SALTLEN_DIGEST", RSA_PSS_SALTLEN_DIGEST, CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("OPENSSL_RSA_PSS_SALTLEN_AUTO", RSA_PSS_SALTLEN_AUTO, CONST_PERSISTENT);
+ REGISTER_LONG_CONSTANT("OPENSSL_RSA_PSS_SALTLEN_MAX", RSA_PSS_SALTLEN_MAX, CONST_PERSISTENT);
REGISTER_STRING_CONSTANT("OPENSSL_DEFAULT_STREAM_CIPHERS", OPENSSL_DEFAULT_STREAM_CIPHERS, CONST_PERSISTENT);
#if !defined(OPENSSL_NO_RC2)
REGISTER_LONG_CONSTANT("OPENSSL_CIPHER_RC2_40", PHP_OPENSSL_CIPHER_RC2_40, CONST_PERSISTENT);
diff --git a/ext/openssl/tests/openssl_sign_basic.phpt b/ext/openssl/tests/openssl_sign_basic.phpt
index 9d2edbec59f7..d8880a6a1899 100644
--- a/ext/openssl/tests/openssl_sign_basic.phpt
+++ b/ext/openssl/tests/openssl_sign_basic.phpt
@@ -7,7 +7,6 @@ openssl
$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(strlen($sign1));
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 @@ int(128)
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 1b28c1371083..3bcf3ba0e66f 100644
--- a/ext/openssl/tests/openssl_verify_basic.phpt
+++ b/ext/openssl/tests/openssl_verify_basic.phpt
@@ -13,6 +13,14 @@ $wrong = "wrong";
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 @@ var_dump(openssl_verify($wrong, $sign, $pubkey, OPENSSL_ALGO_SHA256));
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)