Skip to content

Commit 2ee5e6b

Browse files
authored
Fix phpGH-7737: openssl_seal/openssl_open do not handle tagged algorithms (php#20687)
This commit adds a seventh parameter to both two OpenSSL functions: * openssl_seal(): The new parameter is by-ref and is populated with the computed tag. * openssl_open(): The new parameter is by-value to provide the computed tag. Closes phpGH-7737
1 parent 1f3fe93 commit 2ee5e6b

File tree

4 files changed

+165
-50
lines changed

4 files changed

+165
-50
lines changed

ext/openssl/openssl.c

Lines changed: 102 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4171,42 +4171,57 @@ PHP_FUNCTION(openssl_verify)
41714171
/* {{{ Seals data */
41724172
PHP_FUNCTION(openssl_seal)
41734173
{
4174-
zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL;
4174+
zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL, *tag = NULL;
41754175
HashTable *pubkeysht;
4176-
EVP_PKEY **pkeys;
4177-
int i, len1, len2, *eksl, nkeys, iv_len;
4178-
unsigned char iv_buf[EVP_MAX_IV_LENGTH + 1], *buf = NULL, **eks;
4176+
EVP_PKEY **pkeys = NULL;
4177+
int i, len1, len2, *eksl = NULL, nkeys = 0, iv_len;
4178+
unsigned char iv_buf[EVP_MAX_IV_LENGTH + 1], *buf = NULL, **eks = NULL;
41794179
char * data;
41804180
size_t data_len;
41814181
char *method;
41824182
size_t method_len;
41834183
const EVP_CIPHER *cipher;
4184-
EVP_CIPHER_CTX *ctx;
4184+
EVP_CIPHER_CTX *ctx = NULL;
4185+
size_t tag_len;
41854186

4186-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szzas|z", &data, &data_len,
4187-
&sealdata, &ekeys, &pubkeys, &method, &method_len, &iv) == FAILURE) {
4187+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szzas|z!z!", &data, &data_len,
4188+
&sealdata, &ekeys, &pubkeys, &method, &method_len, &iv, &tag) == FAILURE) {
41884189
RETURN_THROWS();
41894190
}
4191+
RETVAL_FALSE;
41904192

41914193
PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data, 1);
41924194

41934195
pubkeysht = Z_ARRVAL_P(pubkeys);
41944196
nkeys = pubkeysht ? zend_hash_num_elements(pubkeysht) : 0;
41954197
if (!nkeys) {
41964198
zend_argument_must_not_be_empty_error(4);
4197-
RETURN_THROWS();
4199+
goto clean_exit;
41984200
}
41994201

42004202
cipher = php_openssl_get_evp_cipher_by_name(method);
42014203
if (!cipher) {
42024204
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
4203-
RETURN_FALSE;
4205+
goto clean_exit;
42044206
}
42054207

42064208
iv_len = EVP_CIPHER_iv_length(cipher);
42074209
if (!iv && iv_len > 0) {
42084210
zend_argument_value_error(6, "cannot be null for the chosen cipher algorithm");
4209-
RETURN_THROWS();
4211+
goto clean_exit;
4212+
}
4213+
4214+
ctx = EVP_CIPHER_CTX_new();
4215+
if (ctx == NULL || !EVP_EncryptInit(ctx,cipher,NULL,NULL)) {
4216+
php_openssl_store_errors();
4217+
goto clean_exit;
4218+
}
4219+
4220+
tag_len = EVP_CIPHER_CTX_get_tag_length(ctx);
4221+
if ((tag != NULL) != (tag_len > 0)) {
4222+
const char *imp = tag ? "cannot" : "must";
4223+
zend_argument_value_error(7, "%s be specified for the chosen cipher algorithm", imp);
4224+
goto clean_exit;
42104225
}
42114226

42124227
pkeys = safe_emalloc(nkeys, sizeof(*pkeys), 0);
@@ -4223,21 +4238,12 @@ PHP_FUNCTION(openssl_seal)
42234238
if (!EG(exception)) {
42244239
php_error_docref(NULL, E_WARNING, "Not a public key (%dth member of pubkeys)", i+1);
42254240
}
4226-
RETVAL_FALSE;
42274241
goto clean_exit;
42284242
}
42294243
eks[i] = emalloc(EVP_PKEY_size(pkeys[i]) + 1);
42304244
i++;
42314245
} ZEND_HASH_FOREACH_END();
42324246

4233-
ctx = EVP_CIPHER_CTX_new();
4234-
if (ctx == NULL || !EVP_EncryptInit(ctx,cipher,NULL,NULL)) {
4235-
EVP_CIPHER_CTX_free(ctx);
4236-
php_openssl_store_errors();
4237-
RETVAL_FALSE;
4238-
goto clean_exit;
4239-
}
4240-
42414247
/* allocate one byte extra to make room for \0 */
42424248
buf = emalloc(data_len + EVP_CIPHER_CTX_block_size(ctx));
42434249
EVP_CIPHER_CTX_reset(ctx);
@@ -4246,19 +4252,23 @@ PHP_FUNCTION(openssl_seal)
42464252
!EVP_SealUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) ||
42474253
!EVP_SealFinal(ctx, buf + len1, &len2)) {
42484254
efree(buf);
4249-
EVP_CIPHER_CTX_free(ctx);
42504255
php_openssl_store_errors();
4251-
RETVAL_FALSE;
42524256
goto clean_exit;
42534257
}
42544258

4259+
if (tag) {
4260+
zend_string *tag_str = zend_string_alloc(tag_len, 0);
4261+
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, ZSTR_LEN(tag_str), ZSTR_VAL(tag_str));
4262+
ZSTR_VAL(tag_str)[ZSTR_LEN(tag_str)] = 0;
4263+
ZEND_TRY_ASSIGN_REF_NEW_STR(tag, tag_str);
4264+
}
4265+
42554266
if (len1 + len2 > 0) {
42564267
ZEND_TRY_ASSIGN_REF_NEW_STR(sealdata, zend_string_init((char*)buf, len1 + len2, 0));
42574268
efree(buf);
42584269

42594270
ekeys = zend_try_array_init(ekeys);
42604271
if (!ekeys) {
4261-
EVP_CIPHER_CTX_free(ctx);
42624272
goto clean_exit;
42634273
}
42644274

@@ -4276,44 +4286,61 @@ PHP_FUNCTION(openssl_seal)
42764286
} else {
42774287
efree(buf);
42784288
}
4289+
42794290
RETVAL_LONG(len1 + len2);
4280-
EVP_CIPHER_CTX_free(ctx);
42814291

42824292
clean_exit:
4283-
for (i=0; i<nkeys; i++) {
4284-
if (pkeys[i] != NULL) {
4285-
EVP_PKEY_free(pkeys[i]);
4293+
if (ctx) {
4294+
EVP_CIPHER_CTX_free(ctx);
4295+
}
4296+
4297+
if (pkeys) {
4298+
for (i=0; i<nkeys; i++) {
4299+
if (pkeys[i] != NULL) {
4300+
EVP_PKEY_free(pkeys[i]);
4301+
}
42864302
}
4287-
if (eks[i]) {
4288-
efree(eks[i]);
4303+
efree(pkeys);
4304+
}
4305+
4306+
if (eks) {
4307+
for (i=0; i<nkeys; i++) {
4308+
if (eks[i]) {
4309+
efree(eks[i]);
4310+
}
42894311
}
4312+
efree(eks);
4313+
}
4314+
4315+
if (eksl) {
4316+
efree(eksl);
42904317
}
4291-
efree(eks);
4292-
efree(eksl);
4293-
efree(pkeys);
42944318
}
42954319
/* }}} */
42964320

42974321
/* {{{ Opens data */
42984322
PHP_FUNCTION(openssl_open)
42994323
{
43004324
zval *privkey, *opendata;
4301-
EVP_PKEY *pkey;
4325+
EVP_PKEY *pkey = NULL;
43024326
int len1, len2, cipher_iv_len;
4303-
unsigned char *buf, *iv_buf;
4304-
EVP_CIPHER_CTX *ctx;
4327+
unsigned char *buf = NULL, *iv_buf;
4328+
EVP_CIPHER_CTX *ctx = NULL;
43054329
char * data;
43064330
size_t data_len;
43074331
char * ekey;
43084332
size_t ekey_len;
43094333
char *method, *iv = NULL;
43104334
size_t method_len, iv_len = 0;
4335+
zend_string *tag = NULL;
43114336
const EVP_CIPHER *cipher;
4337+
int tag_len;
43124338

4313-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szszs|s!", &data, &data_len, &opendata,
4314-
&ekey, &ekey_len, &privkey, &method, &method_len, &iv, &iv_len) == FAILURE) {
4339+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szszs|s!S!", &data, &data_len, &opendata,
4340+
&ekey, &ekey_len, &privkey, &method, &method_len, &iv, &iv_len, &tag) == FAILURE) {
43154341
RETURN_THROWS();
43164342
}
4343+
RETVAL_FALSE;
43174344

43184345
PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data, 1);
43194346
PHP_OPENSSL_CHECK_SIZE_T_TO_INT(ekey_len, ekey, 3);
@@ -4323,24 +4350,24 @@ PHP_FUNCTION(openssl_open)
43234350
if (!EG(exception)) {
43244351
php_error_docref(NULL, E_WARNING, "Unable to coerce parameter 4 into a private key");
43254352
}
4326-
RETURN_FALSE;
4353+
goto clean_exit;
43274354
}
43284355

43294356
cipher = php_openssl_get_evp_cipher_by_name(method);
43304357
if (!cipher) {
43314358
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
4332-
RETURN_FALSE;
4359+
goto clean_exit;
43334360
}
43344361

43354362
cipher_iv_len = EVP_CIPHER_iv_length(cipher);
43364363
if (cipher_iv_len > 0) {
43374364
if (!iv) {
43384365
zend_argument_value_error(6, "cannot be null for the chosen cipher algorithm");
4339-
RETURN_THROWS();
4366+
goto clean_exit;
43404367
}
43414368
if ((size_t)cipher_iv_len != iv_len) {
43424369
php_error_docref(NULL, E_WARNING, "IV length is invalid");
4343-
RETURN_FALSE;
4370+
goto clean_exit;
43444371
}
43454372
iv_buf = (unsigned char *)iv;
43464373
} else {
@@ -4350,20 +4377,48 @@ PHP_FUNCTION(openssl_open)
43504377
buf = emalloc(data_len + 1);
43514378

43524379
ctx = EVP_CIPHER_CTX_new();
4353-
if (ctx != NULL && EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, (int)ekey_len, iv_buf, pkey) &&
4354-
EVP_OpenUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) &&
4355-
EVP_OpenFinal(ctx, buf + len1, &len2) && (len1 + len2 > 0)) {
4380+
if (ctx == NULL || !EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, (int)ekey_len, iv_buf, pkey)) {
4381+
php_openssl_store_errors();
4382+
goto clean_exit;
4383+
}
4384+
4385+
tag_len = EVP_CIPHER_CTX_get_tag_length(ctx);
4386+
if ((tag != NULL) != (tag_len > 0)) {
4387+
const char *imp = tag ? "cannot" : "must";
4388+
zend_argument_value_error(7, "%s be specified for the chosen cipher algorithm", imp);
4389+
goto clean_exit;
4390+
}
4391+
if (tag) {
4392+
if (ZSTR_LEN(tag) != tag_len) {
4393+
zend_argument_value_error(7, "must be %d bytes long", tag_len);
4394+
goto clean_exit;
4395+
}
4396+
4397+
if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, ZSTR_LEN(tag), ZSTR_VAL(tag))) {
4398+
php_openssl_store_errors();
4399+
goto clean_exit;
4400+
}
4401+
}
4402+
4403+
if (EVP_OpenUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) &&
4404+
EVP_OpenFinal(ctx, buf + len1, &len2) && (len1 + len2 > 0)) {
43564405
buf[len1 + len2] = '\0';
43574406
ZEND_TRY_ASSIGN_REF_NEW_STR(opendata, zend_string_init((char*)buf, len1 + len2, 0));
43584407
RETVAL_TRUE;
43594408
} else {
43604409
php_openssl_store_errors();
4361-
RETVAL_FALSE;
43624410
}
43634411

4364-
efree(buf);
4365-
EVP_PKEY_free(pkey);
4366-
EVP_CIPHER_CTX_free(ctx);
4412+
clean_exit:
4413+
if (buf) {
4414+
efree(buf);
4415+
}
4416+
if (pkey) {
4417+
EVP_PKEY_free(pkey);
4418+
}
4419+
if (ctx) {
4420+
EVP_CIPHER_CTX_free(ctx);
4421+
}
43674422
}
43684423
/* }}} */
43694424

ext/openssl/openssl.stub.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,14 +628,15 @@ function openssl_verify(string $data, string $signature, $public_key, string|int
628628
* @param string $sealed_data
629629
* @param array $encrypted_keys
630630
* @param string $iv
631+
* @param string $tag
631632
*/
632-
function openssl_seal(#[\SensitiveParameter] string $data, &$sealed_data, &$encrypted_keys, array $public_key, string $cipher_algo, &$iv = null): int|false {}
633+
function openssl_seal(#[\SensitiveParameter] string $data, &$sealed_data, &$encrypted_keys, array $public_key, string $cipher_algo, &$iv = null, &$tag = null): int|false {}
633634

634635
/**
635636
* @param string $output
636637
* @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key
637638
*/
638-
function openssl_open(string $data, #[\SensitiveParameter] &$output, string $encrypted_key, #[\SensitiveParameter] $private_key, string $cipher_algo, ?string $iv = null): bool {}
639+
function openssl_open(string $data, #[\SensitiveParameter] &$output, string $encrypted_key, #[\SensitiveParameter] $private_key, string $cipher_algo, ?string $iv = null, ?string $tag = null): bool {}
639640

640641
/**
641642
* @return array<int, string>

ext/openssl/openssl_arginfo.h

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ext/openssl/tests/gh7737.phpt

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
--TEST--
2+
GitHub Bug#7737 - openssl_seal/open() does not handle ciphers with Tags (e.g. AES-256-CGM)
3+
--EXTENSIONS--
4+
openssl
5+
--SKIPIF--
6+
<?php
7+
// Skip if aes-256-cgm is not available in this build.
8+
in_array('aes-256-gcm', openssl_get_cipher_methods()) or print 'skip';
9+
?>
10+
--FILE--
11+
<?php
12+
13+
const CIPHER_ALGO = 'aes-256-gcm';
14+
const KEY_TYPE = OPENSSL_KEYTYPE_RSA;
15+
const PLAINTEXT = 'Test Data String';
16+
17+
(function() {
18+
$key = openssl_pkey_new(['type' => KEY_TYPE]);
19+
define('KEY_PUBLIC', openssl_pkey_get_details($key)['key']);
20+
define('KEY_PRIVATE', openssl_pkey_get_private($key));
21+
})();
22+
23+
echo 'Plaintext: '; var_dump(PLAINTEXT);
24+
25+
$sealResult = openssl_seal(PLAINTEXT,
26+
$sealedData, /* out */
27+
$sealedKeys, /* out */
28+
[KEY_PUBLIC],
29+
CIPHER_ALGO,
30+
$iv, /* out */
31+
$tag); /* out */
32+
33+
echo 'Seal Result: '; var_dump($sealResult);
34+
echo 'Sealed Data: '; var_dump(strlen($sealedData));
35+
echo 'IV Length: '; var_dump(strlen($iv));
36+
echo 'Tag Length: '; var_dump(strlen($tag));
37+
38+
$unsealResult = openssl_open($sealedData,
39+
$unsealedData, /* out */
40+
$sealedKeys[0],
41+
KEY_PRIVATE,
42+
CIPHER_ALGO,
43+
$iv,
44+
$tag);
45+
46+
echo 'Unseal Result: '; var_dump($unsealResult);
47+
echo 'Unsealed Data: '; var_dump($unsealedData);
48+
49+
?>
50+
--EXPECT--
51+
Plaintext: string(16) "Test Data String"
52+
Seal Result: int(16)
53+
Sealed Data: int(16)
54+
IV Length: int(12)
55+
Tag Length: int(16)
56+
Unseal Result: bool(true)
57+
Unsealed Data: string(16) "Test Data String"

0 commit comments

Comments
 (0)