Fix #81724: openssl_cms_encrypt only allows specific ciphers

The allows cipher_algo to be specified as a string. It means the not
only predefined ID ciphers are available which means that also auth
enveloped data can be created using AES GCM.

Closes GH-19459
This commit is contained in:
Jakub Zelenka 2025-08-11 21:44:44 +02:00
parent 38beb44176
commit 20c8c12d9e
No known key found for this signature in database
GPG key ID: 1C0779DC5C0A9DE4
7 changed files with 68 additions and 20 deletions

2
NEWS
View file

@ -53,6 +53,8 @@ PHP NEWS
(Jakub Zelenka)
. Implement #47728 (openssl_pkcs7_sign ignores new openssl flags).
(Jakub Zelenka)
. Implement #81724 (openssl_cms_encrypt only allows specific ciphers).
(Jakub Zelenka)
- PDO:
. The "uri:" DSN scheme has been deprecated due to security concerns with

View file

@ -456,6 +456,9 @@ PHP 8.5 UPGRADE NOTES
$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.
. openssl_cms_encrypt() $cipher_algo parameter can be a string with the
cipher name. That allows to use more algorithms including AES GCM cipher
algorithms for auth enveloped data.
- PCNTL:
. pcntl_exec() now has a formal return type of false.

View file

@ -3201,6 +3201,7 @@ PHP_FUNCTION(openssl_cms_encrypt)
X509 * cert;
const EVP_CIPHER *cipher = NULL;
zend_long cipherid = PHP_OPENSSL_CIPHER_DEFAULT;
zend_string *cipher_str = NULL;
zend_string * strindex;
char * infilename = NULL;
size_t infilename_len;
@ -3210,11 +3211,16 @@ PHP_FUNCTION(openssl_cms_encrypt)
RETVAL_FALSE;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "ppza!|lll", &infilename, &infilename_len,
&outfilename, &outfilename_len, &zrecipcerts, &zheaders, &flags, &encoding, &cipherid) == FAILURE) {
RETURN_THROWS();
}
ZEND_PARSE_PARAMETERS_START(4, 7)
Z_PARAM_PATH(infilename, infilename_len)
Z_PARAM_PATH(outfilename, outfilename_len)
Z_PARAM_ZVAL(zrecipcerts)
Z_PARAM_ARRAY_OR_NULL(zheaders)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(flags)
Z_PARAM_LONG(encoding)
Z_PARAM_STR_OR_LONG(cipher_str, cipherid)
ZEND_PARSE_PARAMETERS_END();
infile = php_openssl_bio_new_file(
infilename, infilename_len, 1, PHP_OPENSSL_BIO_MODE_R(flags));
@ -3273,7 +3279,11 @@ PHP_FUNCTION(openssl_cms_encrypt)
}
/* sanity check the cipher */
cipher = php_openssl_get_evp_cipher_from_algo(cipherid);
if (cipher_str) {
cipher = php_openssl_get_evp_cipher_by_name(ZSTR_VAL(cipher_str));
} else {
cipher = php_openssl_get_evp_cipher_from_algo(cipherid);
}
if (cipher == NULL) {
/* shouldn't happen */
php_error_docref(NULL, E_WARNING, "Failed to get cipher");

View file

@ -574,7 +574,7 @@ function openssl_pkcs7_read(string $data, &$certificates): bool {}
function openssl_cms_verify(string $input_filename, int $flags = 0, ?string $certificates = null, array $ca_info = [], ?string $untrusted_certificates_filename = null, ?string $content = null, ?string $pk7 = null, ?string $sigfile = null, int $encoding = OPENSSL_ENCODING_SMIME): bool {}
/** @param OpenSSLCertificate|array|string $certificate */
function openssl_cms_encrypt(string $input_filename, string $output_filename, $certificate, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, int $cipher_algo = OPENSSL_CIPHER_AES_128_CBC): bool {}
function openssl_cms_encrypt(string $input_filename, string $output_filename, $certificate, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, string|int $cipher_algo = OPENSSL_CIPHER_AES_128_CBC): bool {}
/**
* @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 03e511abb97901a4a8268276b809b3cf7afbfa29 */
* Stub hash: 0e6a5f1a5f23602bafd5b7fdb10525c19a9476fc */
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)
@ -219,7 +219,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_cms_encrypt, 0, 4, _IS_B
ZEND_ARG_TYPE_INFO(0, headers, IS_ARRAY, 1)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, encoding, IS_LONG, 0, "OPENSSL_ENCODING_SMIME")
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, cipher_algo, IS_LONG, 0, "OPENSSL_CIPHER_AES_128_CBC")
ZEND_ARG_TYPE_MASK(0, cipher_algo, MAY_BE_STRING|MAY_BE_LONG, "OPENSSL_CIPHER_AES_128_CBC")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_cms_sign, 0, 5, _IS_BOOL, 0)

View file

@ -0,0 +1,34 @@
--TEST--
openssl_cms_encrypt() auth enveloped data tests
--EXTENSIONS--
openssl
--SKIPIF--
<?php
if (OPENSSL_VERSION_NUMBER < 0x30000000) die('skip For OpenSSL >= 3.0');
?>
--FILE--
<?php
$infile = __DIR__ . "/plain.txt";
$outfile = __DIR__ . "/openssl_cms_encrypt_auth_env.out1";
$outfile2 = __DIR__ . "/openssl_cms_encrypt_auth_env.out2";
$single_cert = "file://" . __DIR__ . "/cert.crt";
$privkey = "file://" . __DIR__ . "/private_rsa_1024.key";
$headers = array("test@test", "testing openssl_cms_encrypt()");
$cipher = "AES-128-GCM";
var_dump(openssl_cms_encrypt($infile, $outfile, $single_cert, $headers, cipher_algo: $cipher));
var_dump(openssl_cms_encrypt($infile, $outfile, openssl_x509_read($single_cert), $headers, cipher_algo: $cipher));
var_dump(openssl_cms_decrypt($outfile, $outfile2, $single_cert, $privkey));
readfile($outfile2);
?>
--CLEAN--
<?php
@unlink(__DIR__ . "/openssl_cms_encrypt_auth_env.out1");
@unlink(__DIR__ . "/openssl_cms_encrypt_auth_env.out2");
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
Now is the winter of our discontent.

View file

@ -5,13 +5,9 @@ openssl
--FILE--
<?php
$infile = __DIR__ . "/plain.txt";
$outfile = tempnam(sys_get_temp_dir(), "cms_enc_basic");
if ($outfile === false)
die("failed to get a temporary filename!");
$outfile2 = $outfile . ".out";
$outfile3 = tempnam(sys_get_temp_dir(), "cms_enc_basic");
if ($outfile3 === false)
die("failed to get a temporary filename!");
$outfile = __DIR__ . "/openssl_cms_encrypt_basic.out1";
$outfile2 = __DIR__ . "/openssl_cms_encrypt_basic.out2";
$outfile3 = __DIR__ . "/openssl_cms_encrypt_basic.out3";
$single_cert = "file://" . __DIR__ . "/cert.crt";
$privkey = "file://" . __DIR__ . "/private_rsa_1024.key";
$wrongkey = "file://" . __DIR__ . "/private_rsa_2048.key";
@ -27,7 +23,7 @@ var_dump(openssl_cms_encrypt($infile, $outfile, $single_cert, $headers, cipher_a
var_dump(openssl_cms_encrypt($infile, $outfile, openssl_x509_read($single_cert), $headers, cipher_algo: $cipher));
var_dump(openssl_cms_decrypt($outfile, $outfile2, $single_cert, $privkey));
readfile($outfile2);
var_dump(openssl_cms_encrypt($infile, $outfile, $single_cert, $assoc_headers, cipher_algo: $cipher));
var_dump(openssl_cms_encrypt($infile, $outfile, $single_cert, $assoc_headers, cipher_algo: "AES-128-CBC"));
var_dump(openssl_cms_encrypt($infile, $outfile, $single_cert, $empty_headers, cipher_algo: $cipher));
var_dump(openssl_cms_encrypt($wrong, $outfile, $single_cert, $headers, cipher_algo: $cipher));
var_dump(openssl_cms_encrypt($empty, $outfile, $single_cert, $headers, cipher_algo: $cipher));
@ -40,11 +36,9 @@ var_dump(openssl_cms_encrypt($infile, $outfile3, $single_cert, $headers, flags:
if (file_exists($outfile)) {
echo "true\n";
unlink($outfile);
}
if (file_exists($outfile2)) {
echo "true\n";
unlink($outfile2);
}
if (file_exists($outfile3)) {
@ -53,9 +47,14 @@ if (file_exists($outfile3)) {
echo "true\n";
}
unset($content);
unlink($outfile3);
}
?>
--CLEAN--
<?php
@unlink(__DIR__ . "/openssl_cms_encrypt_basic.out1");
@unlink(__DIR__ . "/openssl_cms_encrypt_basic.out2");
@unlink(__DIR__ . "/openssl_cms_encrypt_basic.out3");
?>
--EXPECT--
bool(true)
bool(true)