Implement #80495: Enable to set padding in openssl_(sign|verify)

This adds support for RSA PSS padding.

Closes GH-19432
This commit is contained in:
Jakub Zelenka 2025-08-09 16:17:55 +02:00
parent 8f1a627e79
commit 702d18de99
No known key found for this signature in database
GPG key ID: 1C0779DC5C0A9DE4
7 changed files with 92 additions and 10 deletions

2
NEWS
View file

@ -32,6 +32,8 @@ PHP NEWS
algorithms appears to be broken). (Jakub Zelenka)
. The $key_length parameter for openssl_pkey_derive() has been deprecated.
(Girgias)
. Implement #80495 (Enable to set padding in openssl_(sign|verify).
(Jakub Zelenka)
- PDO:
. The "uri:" DSN scheme has been deprecated due to security concerns with

View file

@ -412,6 +412,8 @@ PHP 8.5 UPGRADE NOTES
- OpenSSL:
. openssl_public_encrypt() and openssl_private_decrypt() have new parameter
$digest_algo that allows specifying hash digest algorithm for OEAP padding.
. openssl_sign() and openssl_verify() have new parameter $padding to allow
using more secure RSA PSS padding.
- PCNTL:
. pcntl_exec() now has a formal return type of false.
@ -664,6 +666,9 @@ PHP 8.5 UPGRADE NOTES
. DECIMAL_COMPACT_SHORT.
. DECIMAL_COMPACT_LONG.
- OpenSSL:
. OPENSSL_PKCS1_PSS_PADDING
- POSIX:
. POSIX_SC_OPEN_MAX.

View file

@ -3997,6 +3997,30 @@ PHP_FUNCTION(openssl_error_string)
}
/* }}} */
static zend_result php_openssl_setup_rsa_padding(EVP_PKEY_CTX *pctx, EVP_PKEY *pkey, zend_long padding)
{
int key_type = EVP_PKEY_type(EVP_PKEY_id(pkey));
if (padding != 0) { // 0 = default/unspecified
if (key_type != EVP_PKEY_RSA) {
php_error_docref(NULL, E_WARNING, "Padding parameter is only supported for RSA keys");
return FAILURE;
}
if (padding == RSA_PKCS1_PSS_PADDING) {
if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) <= 0) {
php_openssl_store_errors();
return FAILURE;
}
} else if (padding != RSA_PKCS1_PADDING) {
php_error_docref(NULL, E_WARNING, "Unknown padding type");
return FAILURE;
}
}
return SUCCESS;
}
/* {{{ Signs data */
PHP_FUNCTION(openssl_sign)
{
@ -4009,14 +4033,17 @@ PHP_FUNCTION(openssl_sign)
zend_string *method_str = NULL;
zend_long method_long = OPENSSL_ALGO_SHA1;
const EVP_MD *mdtype;
zend_long padding = 0;
EVP_PKEY_CTX *pctx;
bool can_default_digest = ZEND_THREEWAY_COMPARE(PHP_OPENSSL_API_VERSION, 0x30000) >= 0;
ZEND_PARSE_PARAMETERS_START(3, 4)
ZEND_PARSE_PARAMETERS_START(3, 5)
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)
ZEND_PARSE_PARAMETERS_END();
pkey = php_openssl_pkey_from_zval(key, 0, "", 0, 3);
@ -4041,7 +4068,8 @@ PHP_FUNCTION(openssl_sign)
md_ctx = EVP_MD_CTX_create();
size_t siglen;
if (md_ctx != NULL &&
EVP_DigestSignInit(md_ctx, NULL, mdtype, NULL, pkey) &&
EVP_DigestSignInit(md_ctx, &pctx, mdtype, NULL, pkey) &&
php_openssl_setup_rsa_padding(pctx, pkey, padding) == 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)) {
@ -4074,14 +4102,17 @@ PHP_FUNCTION(openssl_verify)
size_t signature_len;
zend_string *method_str = NULL;
zend_long method_long = OPENSSL_ALGO_SHA1;
zend_long padding = 0;
EVP_PKEY_CTX *pctx;
bool can_default_digest = ZEND_THREEWAY_COMPARE(PHP_OPENSSL_API_VERSION, 0x30000) >= 0;
ZEND_PARSE_PARAMETERS_START(3, 4)
ZEND_PARSE_PARAMETERS_START(3, 5)
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)
ZEND_PARSE_PARAMETERS_END();
PHP_OPENSSL_CHECK_SIZE_T_TO_UINT(signature_len, signature, 2);
@ -4106,11 +4137,25 @@ PHP_FUNCTION(openssl_verify)
}
md_ctx = EVP_MD_CTX_create();
if (md_ctx == NULL ||
!EVP_DigestVerifyInit(md_ctx, NULL, mdtype, NULL, pkey) ||
(err = EVP_DigestVerify(md_ctx, (unsigned char *)signature, signature_len, (unsigned char*)data, data_len)) < 0) {
if (md_ctx == NULL) {
php_openssl_store_errors();
err = -1;
goto cleanup;
}
if (!EVP_DigestVerifyInit(md_ctx, &pctx, mdtype, NULL, pkey) ||
php_openssl_setup_rsa_padding(pctx, pkey, padding) == FAILURE) {
php_openssl_store_errors();
err = -1;
goto cleanup;
}
err = EVP_DigestVerify(md_ctx, (unsigned char *)signature, signature_len, (unsigned char*)data, data_len);
if (err < 0) {
php_openssl_store_errors();
}
cleanup:
EVP_MD_CTX_destroy(md_ctx);
php_openssl_release_evp_md(mdtype);
EVP_PKEY_free(pkey);

View file

@ -236,6 +236,11 @@ const OPENSSL_NO_PADDING = UNKNOWN;
* @cvalue RSA_PKCS1_OAEP_PADDING
*/
const OPENSSL_PKCS1_OAEP_PADDING = UNKNOWN;
/**
* @var int
* @cvalue RSA_PKCS1_PSS_PADDING
*/
const OPENSSL_PKCS1_PSS_PADDING = UNKNOWN;
/* Informational stream wrapper constants */
@ -595,10 +600,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): bool {}
function openssl_sign(string $data, &$signature, #[\SensitiveParameter] $private_key, string|int $algorithm = OPENSSL_ALGO_SHA1, int $padding = 0): bool {}
/** @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $public_key */
function openssl_verify(string $data, string $signature, $public_key, string|int $algorithm = OPENSSL_ALGO_SHA1): int|false {}
function openssl_verify(string $data, string $signature, $public_key, string|int $algorithm = OPENSSL_ALGO_SHA1, int $padding = 0): int|false {}
/**
* @param string $sealed_data

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 4186e01a05ec179f1f2196a2afd1860671f793d5 */
* Stub hash: ff620528ee3035e295099637cc9f94b78a53416b */
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)
@ -284,6 +284,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_sign, 0, 3, _IS_BOOL, 0)
ZEND_ARG_INFO(1, signature)
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_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_verify, 0, 3, MAY_BE_LONG|MAY_BE_FALSE)
@ -291,6 +292,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_verify, 0, 3, MAY_BE_LON
ZEND_ARG_TYPE_INFO(0, signature, IS_STRING, 0)
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_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_seal, 0, 5, MAY_BE_LONG|MAY_BE_FALSE)
@ -599,6 +601,7 @@ static void register_openssl_symbols(int module_number)
#endif
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_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);

View file

@ -8,11 +8,27 @@ $data = "Testing openssl_sign()";
$privkey = "file://" . __DIR__ . "/private_rsa_1024.key";
$wrong = "wrong";
var_dump(openssl_sign($data, $sign, $privkey, OPENSSL_ALGO_SHA256)); // no output
var_dump(openssl_sign($data, $sign1, $privkey, OPENSSL_ALGO_SHA256));
var_dump(bin2hex($sign1));
var_dump(openssl_sign($data, $sign2, $privkey, OPENSSL_ALGO_SHA256));
var_dump($sign1 === $sign2);
var_dump(openssl_sign($data, $sign1, $privkey, OPENSSL_ALGO_SHA256, OPENSSL_PKCS1_PSS_PADDING));
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, $sign, $wrong));
?>
--EXPECTF--
bool(true)
string(256) "5eff033d92208fcbf52edc9cbf6c9d4bd7c06b7b5a6a22c7d641d1494a09d6b0898d321c0a8fdb55c10b9bf25c2bb777c2b4660f867001f79879d089de7321a28df5f037cc02b68c47d1eb28d98a9199876961adb02524a489872a12fd3675db6a957d623ff04b9f715b565f516806cea247264c82a7569871dbd0b86cfe4689"
bool(true)
bool(true)
bool(true)
int(128)
bool(true)
int(128)
bool(false)
Warning: openssl_sign(): Supplied key param cannot be coerced into a private key in %s on line %d
bool(false)

View file

@ -9,6 +9,10 @@ $privkey = "file://" . __DIR__ . "/private_rsa_1024.key";
$pubkey = "file://" . __DIR__ . "/public.key";
$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, $privkey, OPENSSL_ALGO_SHA256);
var_dump(openssl_verify($data, $sign, $pubkey, OPENSSL_ALGO_SHA256));
var_dump(openssl_verify($data, $sign, $privkey, OPENSSL_ALGO_SHA256));
@ -18,6 +22,8 @@ var_dump(openssl_verify($wrong, $sign, $pubkey, OPENSSL_ALGO_SHA256));
?>
--EXPECTF--
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)