Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 102 additions & 47 deletions ext/openssl/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -4171,42 +4171,57 @@ PHP_FUNCTION(openssl_verify)
/* {{{ Seals data */
PHP_FUNCTION(openssl_seal)
{
zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL;
zval *pubkeys, *pubkey, *sealdata, *ekeys, *iv = NULL, *tag = NULL;
HashTable *pubkeysht;
EVP_PKEY **pkeys;
int i, len1, len2, *eksl, nkeys, iv_len;
unsigned char iv_buf[EVP_MAX_IV_LENGTH + 1], *buf = NULL, **eks;
EVP_PKEY **pkeys = NULL;
int i, len1, len2, *eksl = NULL, nkeys = 0, iv_len;
unsigned char iv_buf[EVP_MAX_IV_LENGTH + 1], *buf = NULL, **eks = NULL;
char * data;
size_t data_len;
char *method;
size_t method_len;
const EVP_CIPHER *cipher;
EVP_CIPHER_CTX *ctx;
EVP_CIPHER_CTX *ctx = NULL;
size_t tag_len;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "szzas|z", &data, &data_len,
&sealdata, &ekeys, &pubkeys, &method, &method_len, &iv) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szzas|z!z!", &data, &data_len,
&sealdata, &ekeys, &pubkeys, &method, &method_len, &iv, &tag) == FAILURE) {
RETURN_THROWS();
}
RETVAL_FALSE;

PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data, 1);

pubkeysht = Z_ARRVAL_P(pubkeys);
nkeys = pubkeysht ? zend_hash_num_elements(pubkeysht) : 0;
if (!nkeys) {
zend_argument_must_not_be_empty_error(4);
RETURN_THROWS();
goto clean_exit;
}

cipher = php_openssl_get_evp_cipher_by_name(method);
if (!cipher) {
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
RETURN_FALSE;
goto clean_exit;
}

iv_len = EVP_CIPHER_iv_length(cipher);
if (!iv && iv_len > 0) {
zend_argument_value_error(6, "cannot be null for the chosen cipher algorithm");
RETURN_THROWS();
goto clean_exit;
}

ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL || !EVP_EncryptInit(ctx,cipher,NULL,NULL)) {
php_openssl_store_errors();
goto clean_exit;
}

tag_len = EVP_CIPHER_CTX_get_tag_length(ctx);
if ((tag != NULL) != (tag_len > 0)) {
const char *imp = tag ? "cannot" : "must";
zend_argument_value_error(7, "%s be specified for the chosen cipher algorithm", imp);
goto clean_exit;
}

pkeys = safe_emalloc(nkeys, sizeof(*pkeys), 0);
Expand All @@ -4223,21 +4238,12 @@ PHP_FUNCTION(openssl_seal)
if (!EG(exception)) {
php_error_docref(NULL, E_WARNING, "Not a public key (%dth member of pubkeys)", i+1);
}
RETVAL_FALSE;
goto clean_exit;
}
eks[i] = emalloc(EVP_PKEY_size(pkeys[i]) + 1);
i++;
} ZEND_HASH_FOREACH_END();

ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL || !EVP_EncryptInit(ctx,cipher,NULL,NULL)) {
EVP_CIPHER_CTX_free(ctx);
php_openssl_store_errors();
RETVAL_FALSE;
goto clean_exit;
}

/* allocate one byte extra to make room for \0 */
buf = emalloc(data_len + EVP_CIPHER_CTX_block_size(ctx));
EVP_CIPHER_CTX_reset(ctx);
Expand All @@ -4246,19 +4252,23 @@ PHP_FUNCTION(openssl_seal)
!EVP_SealUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) ||
!EVP_SealFinal(ctx, buf + len1, &len2)) {
efree(buf);
EVP_CIPHER_CTX_free(ctx);
php_openssl_store_errors();
RETVAL_FALSE;
goto clean_exit;
}

if (tag) {
zend_string *tag_str = zend_string_alloc(tag_len, 0);
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, ZSTR_LEN(tag_str), ZSTR_VAL(tag_str));
ZSTR_VAL(tag_str)[ZSTR_LEN(tag_str)] = 0;
ZEND_TRY_ASSIGN_REF_NEW_STR(tag, tag_str);
}

if (len1 + len2 > 0) {
ZEND_TRY_ASSIGN_REF_NEW_STR(sealdata, zend_string_init((char*)buf, len1 + len2, 0));
efree(buf);

ekeys = zend_try_array_init(ekeys);
if (!ekeys) {
EVP_CIPHER_CTX_free(ctx);
goto clean_exit;
}

Expand All @@ -4276,44 +4286,61 @@ PHP_FUNCTION(openssl_seal)
} else {
efree(buf);
}

RETVAL_LONG(len1 + len2);
EVP_CIPHER_CTX_free(ctx);

clean_exit:
for (i=0; i<nkeys; i++) {
if (pkeys[i] != NULL) {
EVP_PKEY_free(pkeys[i]);
if (ctx) {
EVP_CIPHER_CTX_free(ctx);
}

if (pkeys) {
for (i=0; i<nkeys; i++) {
if (pkeys[i] != NULL) {
EVP_PKEY_free(pkeys[i]);
}
}
if (eks[i]) {
efree(eks[i]);
efree(pkeys);
}

if (eks) {
for (i=0; i<nkeys; i++) {
if (eks[i]) {
efree(eks[i]);
}
}
efree(eks);
}

if (eksl) {
efree(eksl);
}
efree(eks);
efree(eksl);
efree(pkeys);
}
/* }}} */

/* {{{ Opens data */
PHP_FUNCTION(openssl_open)
{
zval *privkey, *opendata;
EVP_PKEY *pkey;
EVP_PKEY *pkey = NULL;
int len1, len2, cipher_iv_len;
unsigned char *buf, *iv_buf;
EVP_CIPHER_CTX *ctx;
unsigned char *buf = NULL, *iv_buf;
EVP_CIPHER_CTX *ctx = NULL;
char * data;
size_t data_len;
char * ekey;
size_t ekey_len;
char *method, *iv = NULL;
size_t method_len, iv_len = 0;
zend_string *tag = NULL;
const EVP_CIPHER *cipher;
int tag_len;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "szszs|s!", &data, &data_len, &opendata,
&ekey, &ekey_len, &privkey, &method, &method_len, &iv, &iv_len) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "szszs|s!S!", &data, &data_len, &opendata,
&ekey, &ekey_len, &privkey, &method, &method_len, &iv, &iv_len, &tag) == FAILURE) {
RETURN_THROWS();
}
RETVAL_FALSE;

PHP_OPENSSL_CHECK_SIZE_T_TO_INT(data_len, data, 1);
PHP_OPENSSL_CHECK_SIZE_T_TO_INT(ekey_len, ekey, 3);
Expand All @@ -4323,24 +4350,24 @@ PHP_FUNCTION(openssl_open)
if (!EG(exception)) {
php_error_docref(NULL, E_WARNING, "Unable to coerce parameter 4 into a private key");
}
RETURN_FALSE;
goto clean_exit;
}

cipher = php_openssl_get_evp_cipher_by_name(method);
if (!cipher) {
php_error_docref(NULL, E_WARNING, "Unknown cipher algorithm");
RETURN_FALSE;
goto clean_exit;
}

cipher_iv_len = EVP_CIPHER_iv_length(cipher);
if (cipher_iv_len > 0) {
if (!iv) {
zend_argument_value_error(6, "cannot be null for the chosen cipher algorithm");
RETURN_THROWS();
goto clean_exit;
}
if ((size_t)cipher_iv_len != iv_len) {
php_error_docref(NULL, E_WARNING, "IV length is invalid");
RETURN_FALSE;
goto clean_exit;
}
iv_buf = (unsigned char *)iv;
} else {
Expand All @@ -4350,20 +4377,48 @@ PHP_FUNCTION(openssl_open)
buf = emalloc(data_len + 1);

ctx = EVP_CIPHER_CTX_new();
if (ctx != NULL && EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, (int)ekey_len, iv_buf, pkey) &&
EVP_OpenUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) &&
EVP_OpenFinal(ctx, buf + len1, &len2) && (len1 + len2 > 0)) {
if (ctx == NULL || !EVP_OpenInit(ctx, cipher, (unsigned char *)ekey, (int)ekey_len, iv_buf, pkey)) {
php_openssl_store_errors();
goto clean_exit;
}

tag_len = EVP_CIPHER_CTX_get_tag_length(ctx);
if ((tag != NULL) != (tag_len > 0)) {
const char *imp = tag ? "cannot" : "must";
zend_argument_value_error(7, "%s be specified for the chosen cipher algorithm", imp);
goto clean_exit;
}
if (tag) {
if (ZSTR_LEN(tag) != tag_len) {
zend_argument_value_error(7, "must be %d bytes long", tag_len);
goto clean_exit;
}

if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, ZSTR_LEN(tag), ZSTR_VAL(tag))) {
php_openssl_store_errors();
goto clean_exit;
}
}

if (EVP_OpenUpdate(ctx, buf, &len1, (unsigned char *)data, (int)data_len) &&
EVP_OpenFinal(ctx, buf + len1, &len2) && (len1 + len2 > 0)) {
buf[len1 + len2] = '\0';
ZEND_TRY_ASSIGN_REF_NEW_STR(opendata, zend_string_init((char*)buf, len1 + len2, 0));
RETVAL_TRUE;
} else {
php_openssl_store_errors();
RETVAL_FALSE;
}

efree(buf);
EVP_PKEY_free(pkey);
EVP_CIPHER_CTX_free(ctx);
clean_exit:
if (buf) {
efree(buf);
}
if (pkey) {
EVP_PKEY_free(pkey);
}
if (ctx) {
EVP_CIPHER_CTX_free(ctx);
}
}
/* }}} */

Expand Down
5 changes: 3 additions & 2 deletions ext/openssl/openssl.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -628,14 +628,15 @@ function openssl_verify(string $data, string $signature, $public_key, string|int
* @param string $sealed_data
* @param array $encrypted_keys
* @param string $iv
* @param string $tag
*/
function openssl_seal(#[\SensitiveParameter] string $data, &$sealed_data, &$encrypted_keys, array $public_key, string $cipher_algo, &$iv = null): int|false {}
function openssl_seal(#[\SensitiveParameter] string $data, &$sealed_data, &$encrypted_keys, array $public_key, string $cipher_algo, &$iv = null, &$tag = null): int|false {}

/**
* @param string $output
* @param OpenSSLAsymmetricKey|OpenSSLCertificate|array|string $private_key
*/
function openssl_open(string $data, #[\SensitiveParameter] &$output, string $encrypted_key, #[\SensitiveParameter] $private_key, string $cipher_algo, ?string $iv = null): bool {}
function openssl_open(string $data, #[\SensitiveParameter] &$output, string $encrypted_key, #[\SensitiveParameter] $private_key, string $cipher_algo, ?string $iv = null, ?string $tag = null): bool {}

/**
* @return array<int, string>
Expand Down
4 changes: 3 additions & 1 deletion ext/openssl/openssl_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions ext/openssl/tests/gh7737.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
--TEST--
GitHub Bug#7737 - openssl_seal/open() does not handle ciphers with Tags (e.g. AES-256-CGM)
--EXTENSIONS--
openssl
--SKIPIF--
<?php
// Skip if aes-256-cgm is not available in this build.
in_array('aes-256-gcm', openssl_get_cipher_methods()) or print 'skip';
?>
--FILE--
<?php

const CIPHER_ALGO = 'aes-256-gcm';
const KEY_TYPE = OPENSSL_KEYTYPE_RSA;
const PLAINTEXT = 'Test Data String';

(function() {
$key = openssl_pkey_new(['type' => KEY_TYPE]);
define('KEY_PUBLIC', openssl_pkey_get_details($key)['key']);
define('KEY_PRIVATE', openssl_pkey_get_private($key));
})();

echo 'Plaintext: '; var_dump(PLAINTEXT);

$sealResult = openssl_seal(PLAINTEXT,
$sealedData, /* out */
$sealedKeys, /* out */
[KEY_PUBLIC],
CIPHER_ALGO,
$iv, /* out */
$tag); /* out */

echo 'Seal Result: '; var_dump($sealResult);
echo 'Sealed Data: '; var_dump(strlen($sealedData));
echo 'IV Length: '; var_dump(strlen($iv));
echo 'Tag Length: '; var_dump(strlen($tag));

$unsealResult = openssl_open($sealedData,
$unsealedData, /* out */
$sealedKeys[0],
KEY_PRIVATE,
CIPHER_ALGO,
$iv,
$tag);

echo 'Unseal Result: '; var_dump($unsealResult);
echo 'Unsealed Data: '; var_dump($unsealedData);

?>
--EXPECT--
Plaintext: string(16) "Test Data String"
Seal Result: int(16)
Sealed Data: int(16)
IV Length: int(12)
Tag Length: int(16)
Unseal Result: bool(true)
Unsealed Data: string(16) "Test Data String"