Skip to content

Commit 5f2a0c8

Browse files
manuelmbukka
authored andcommitted
Add support for Curve25519 + Curve448 based keys
For openssl_pkey_get_details we export the priv+pub parameters. ED25519/ED448 do not support streaming, so we need to use EVP_Digest{Sign,Verify} instead. In general the older EVP_{Sign,Verify} interface should be avoided as the key is passed very late. See BUGS section in OpenSSL manpages of EVP_{Sign,Verify}Final Additionally per requirement we need to allow sign/verify without digest. So we need to allow passing 0 as digest. In OpenSSL 3.0+ this also corresponds to the default digest (see EVP_PKEY_get_default_digest_name). For CSR creation we need to allow "null" as digest_alg option. Closes GH-14052
1 parent 6e1d20c commit 5f2a0c8

File tree

9 files changed

+516
-11
lines changed

9 files changed

+516
-11
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ PHP NEWS
132132
. Bumped minimum required OpenSSL version to 1.1.1. (Ayesh Karunaratne)
133133
. Added compile-time option --with-openssl-legacy-provider to enable legacy
134134
provider. (Adam Saponara)
135+
. Added support for Curve25519 + Curve448 based keys. (Manuel Mausz)
135136

136137
- Output:
137138
. Clear output handler status flags during handler initialization. (haszi)

UPGRADING

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ PHP 8.4 UPGRADE NOTES
222222
. NumberFormatter::ROUND_HALFODD added to complement existing
223223
NumberFormatter::ROUND_HALFEVEN functionality.
224224

225+
- OpenSSL:
226+
. Added support for Curve25519 + Curve448 based keys. Specifically x25519,
227+
ed25519, x448 and ed448 fields are supported in openssl_pkey_new and
228+
openssl_pkey_get_details as well as openssl_sign and openssl_verify were
229+
extended to support those keys.
230+
231+
225232
- Phar:
226233
. Added support for the unix timestamp extension for zip archives.
227234

ext/openssl/openssl.c

Lines changed: 179 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,13 @@ enum php_openssl_key_type {
121121
OPENSSL_KEYTYPE_DH,
122122
OPENSSL_KEYTYPE_DEFAULT = OPENSSL_KEYTYPE_RSA,
123123
#ifdef HAVE_EVP_PKEY_EC
124-
OPENSSL_KEYTYPE_EC = OPENSSL_KEYTYPE_DH +1
124+
OPENSSL_KEYTYPE_EC = OPENSSL_KEYTYPE_DH +1,
125+
#endif
126+
#if PHP_OPENSSL_API_VERSION >= 0x30000
127+
OPENSSL_KEYTYPE_X25519 = OPENSSL_KEYTYPE_DH +2,
128+
OPENSSL_KEYTYPE_ED25519 = OPENSSL_KEYTYPE_DH +3,
129+
OPENSSL_KEYTYPE_X448 = OPENSSL_KEYTYPE_DH +4,
130+
OPENSSL_KEYTYPE_ED448 = OPENSSL_KEYTYPE_DH +5,
125131
#endif
126132
};
127133

@@ -1012,7 +1018,11 @@ static int php_openssl_parse_config(struct php_x509_request * req, zval * option
10121018
req->digest_name = php_openssl_conf_get_string(req->req_config, req->section_name, "default_md");
10131019
}
10141020
if (req->digest_name != NULL) {
1015-
req->digest = req->md_alg = EVP_get_digestbyname(req->digest_name);
1021+
if (strcmp(req->digest_name, "null") == 0) {
1022+
req->digest = req->md_alg = EVP_md_null();
1023+
} else {
1024+
req->digest = req->md_alg = EVP_get_digestbyname(req->digest_name);
1025+
}
10161026
}
10171027
if (req->md_alg == NULL) {
10181028
req->md_alg = req->digest = EVP_sha1();
@@ -3737,6 +3747,16 @@ static int php_openssl_get_evp_pkey_type(int key_type) {
37373747
#ifdef HAVE_EVP_PKEY_EC
37383748
case OPENSSL_KEYTYPE_EC:
37393749
return EVP_PKEY_EC;
3750+
#endif
3751+
#if PHP_OPENSSL_API_VERSION >= 0x30000
3752+
case OPENSSL_KEYTYPE_X25519:
3753+
return EVP_PKEY_X25519;
3754+
case OPENSSL_KEYTYPE_ED25519:
3755+
return EVP_PKEY_ED25519;
3756+
case OPENSSL_KEYTYPE_X448:
3757+
return EVP_PKEY_X448;
3758+
case OPENSSL_KEYTYPE_ED448:
3759+
return EVP_PKEY_ED448;
37403760
#endif
37413761
default:
37423762
return -1;
@@ -3807,6 +3827,16 @@ static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req
38073827
goto cleanup;
38083828
}
38093829
break;
3830+
#endif
3831+
#if PHP_OPENSSL_API_VERSION >= 0x30000
3832+
case EVP_PKEY_X25519:
3833+
break;
3834+
case EVP_PKEY_ED25519:
3835+
break;
3836+
case EVP_PKEY_X448:
3837+
break;
3838+
case EVP_PKEY_ED448:
3839+
break;
38103840
#endif
38113841
EMPTY_SWITCH_DEFAULT_CASE()
38123842
}
@@ -4473,6 +4503,10 @@ static EVP_PKEY *php_openssl_pkey_init_ec(zval *data, bool *is_private) {
44734503

44744504
*is_private = false;
44754505

4506+
if (!ctx || !bld || !bctx) {
4507+
goto cleanup;
4508+
}
4509+
44764510
zval *curve_name_zv = zend_hash_str_find(Z_ARRVAL_P(data), "curve_name", sizeof("curve_name") - 1);
44774511
if (curve_name_zv && Z_TYPE_P(curve_name_zv) == IS_STRING && Z_STRLEN_P(curve_name_zv) > 0) {
44784512
nid = OBJ_sn2nid(Z_STRVAL_P(curve_name_zv));
@@ -4674,6 +4708,66 @@ static EVP_PKEY *php_openssl_pkey_init_ec(zval *data, bool *is_private) {
46744708
}
46754709
#endif
46764710

4711+
#if PHP_OPENSSL_API_VERSION >= 0x30000
4712+
static void php_openssl_pkey_object_curve_25519_448(zval *return_value, int key_type, zval *data) {
4713+
EVP_PKEY *pkey = NULL;
4714+
EVP_PKEY_CTX *ctx = NULL;
4715+
OSSL_PARAM *params = NULL;
4716+
OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
4717+
bool is_private;
4718+
4719+
RETVAL_FALSE;
4720+
4721+
if (!bld) {
4722+
goto cleanup;
4723+
}
4724+
4725+
zval *priv_key = zend_hash_str_find(Z_ARRVAL_P(data), "priv_key", sizeof("priv_key") - 1);
4726+
if (priv_key && Z_TYPE_P(priv_key) == IS_STRING && Z_STRLEN_P(priv_key) > 0) {
4727+
if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PRIV_KEY, Z_STRVAL_P(priv_key), Z_STRLEN_P(priv_key))) {
4728+
goto cleanup;
4729+
}
4730+
}
4731+
4732+
zval *pub_key = zend_hash_str_find(Z_ARRVAL_P(data), "pub_key", sizeof("pub_key") - 1);
4733+
if (pub_key && Z_TYPE_P(pub_key) == IS_STRING && Z_STRLEN_P(pub_key) > 0) {
4734+
if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, Z_STRVAL_P(pub_key), Z_STRLEN_P(pub_key))) {
4735+
goto cleanup;
4736+
}
4737+
}
4738+
4739+
params = OSSL_PARAM_BLD_to_param(bld);
4740+
ctx = EVP_PKEY_CTX_new_id(key_type, NULL);
4741+
if (!params || !ctx) {
4742+
goto cleanup;
4743+
}
4744+
4745+
if (pub_key || priv_key) {
4746+
if (EVP_PKEY_fromdata_init(ctx) <= 0 ||
4747+
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
4748+
goto cleanup;
4749+
}
4750+
is_private = priv_key != NULL;
4751+
} else {
4752+
is_private = true;
4753+
PHP_OPENSSL_RAND_ADD_TIME();
4754+
if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_keygen(ctx, &pkey) <= 0) {
4755+
goto cleanup;
4756+
}
4757+
}
4758+
4759+
if (pkey) {
4760+
php_openssl_pkey_object_init(return_value, pkey, is_private);
4761+
}
4762+
4763+
cleanup:
4764+
php_openssl_store_errors();
4765+
EVP_PKEY_CTX_free(ctx);
4766+
OSSL_PARAM_free(params);
4767+
OSSL_PARAM_BLD_free(bld);
4768+
}
4769+
#endif
4770+
46774771
/* {{{ Generates a new private key */
46784772
PHP_FUNCTION(openssl_pkey_new)
46794773
{
@@ -4725,6 +4819,24 @@ PHP_FUNCTION(openssl_pkey_new)
47254819
}
47264820
php_openssl_pkey_object_init(return_value, pkey, is_private);
47274821
return;
4822+
#endif
4823+
#if PHP_OPENSSL_API_VERSION >= 0x30000
4824+
} else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "x25519", sizeof("x25519") - 1)) != NULL &&
4825+
Z_TYPE_P(data) == IS_ARRAY) {
4826+
php_openssl_pkey_object_curve_25519_448(return_value, EVP_PKEY_X25519, data);
4827+
return;
4828+
} else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "ed25519", sizeof("ed25519") - 1)) != NULL &&
4829+
Z_TYPE_P(data) == IS_ARRAY) {
4830+
php_openssl_pkey_object_curve_25519_448(return_value, EVP_PKEY_ED25519, data);
4831+
return;
4832+
} else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "x448", sizeof("x448") - 1)) != NULL &&
4833+
Z_TYPE_P(data) == IS_ARRAY) {
4834+
php_openssl_pkey_object_curve_25519_448(return_value, EVP_PKEY_X448, data);
4835+
return;
4836+
} else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "ed448", sizeof("ed448") - 1)) != NULL &&
4837+
Z_TYPE_P(data) == IS_ARRAY) {
4838+
php_openssl_pkey_object_curve_25519_448(return_value, EVP_PKEY_ED448, data);
4839+
return;
47284840
#endif
47294841
}
47304842
}
@@ -4957,6 +5069,27 @@ static zend_string *php_openssl_get_utf8_param(
49575069
return NULL;
49585070
}
49595071
#endif
5072+
5073+
static void php_openssl_copy_octet_string_param(
5074+
zval *ary, EVP_PKEY *pkey, const char *param, const char *name) {
5075+
unsigned char buf[64];
5076+
size_t len;
5077+
if (EVP_PKEY_get_octet_string_param(pkey, param, buf, sizeof(buf), &len) > 0) {
5078+
zend_string *str = zend_string_alloc(len, 0);
5079+
memcpy(ZSTR_VAL(str), buf, len);
5080+
ZSTR_VAL(str)[len] = '\0';
5081+
add_assoc_str(ary, name, str);
5082+
}
5083+
}
5084+
5085+
static void php_openssl_copy_curve_25519_448_params(
5086+
zval *return_value, const char *assoc_name, EVP_PKEY *pkey) {
5087+
zval ary;
5088+
array_init(&ary);
5089+
add_assoc_zval(return_value, assoc_name, &ary);
5090+
php_openssl_copy_octet_string_param(&ary, pkey, OSSL_PKEY_PARAM_PRIV_KEY, "priv_key");
5091+
php_openssl_copy_octet_string_param(&ary, pkey, OSSL_PKEY_PARAM_PUB_KEY, "pub_key");
5092+
}
49605093
#endif
49615094

49625095
/* {{{ returns an array with the key details (bits, pkey, type)*/
@@ -5065,6 +5198,28 @@ PHP_FUNCTION(openssl_pkey_get_details)
50655198
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_PRIV_KEY, "d");
50665199
break;
50675200
}
5201+
#endif
5202+
#if PHP_OPENSSL_API_VERSION >= 0x30000
5203+
case EVP_PKEY_X25519: {
5204+
ktype = OPENSSL_KEYTYPE_X25519;
5205+
php_openssl_copy_curve_25519_448_params(return_value, "x25519", pkey);
5206+
break;
5207+
}
5208+
case EVP_PKEY_ED25519: {
5209+
ktype = OPENSSL_KEYTYPE_ED25519;
5210+
php_openssl_copy_curve_25519_448_params(return_value, "ed25519", pkey);
5211+
break;
5212+
}
5213+
case EVP_PKEY_X448: {
5214+
ktype = OPENSSL_KEYTYPE_X448;
5215+
php_openssl_copy_curve_25519_448_params(return_value, "x448", pkey);
5216+
break;
5217+
}
5218+
case EVP_PKEY_ED448: {
5219+
ktype = OPENSSL_KEYTYPE_ED448;
5220+
php_openssl_copy_curve_25519_448_params(return_value, "ed448", pkey);
5221+
break;
5222+
}
50685223
#endif
50695224
default:
50705225
ktype = -1;
@@ -6942,14 +7097,14 @@ PHP_FUNCTION(openssl_sign)
69427097
{
69437098
zval *key, *signature;
69447099
EVP_PKEY *pkey;
6945-
unsigned int siglen;
6946-
zend_string *sigbuf;
7100+
zend_string *sigbuf = NULL;
69477101
char * data;
69487102
size_t data_len;
69497103
EVP_MD_CTX *md_ctx;
69507104
zend_string *method_str = NULL;
69517105
zend_long method_long = OPENSSL_ALGO_SHA1;
69527106
const EVP_MD *mdtype;
7107+
bool can_default_digest = ZEND_THREEWAY_COMPARE(PHP_OPENSSL_API_VERSION, 0x30000) >= 0;
69537108

69547109
ZEND_PARSE_PARAMETERS_START(3, 4)
69557110
Z_PARAM_STRING(data, data_len)
@@ -6972,19 +7127,27 @@ PHP_FUNCTION(openssl_sign)
69727127
} else {
69737128
mdtype = php_openssl_get_evp_md_from_algo(method_long);
69747129
}
6975-
if (!mdtype) {
7130+
if (!mdtype && (!can_default_digest || method_long != 0)) {
69767131
php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
69777132
RETURN_FALSE;
69787133
}
69797134

6980-
siglen = EVP_PKEY_size(pkey);
6981-
sigbuf = zend_string_alloc(siglen, 0);
6982-
69837135
md_ctx = EVP_MD_CTX_create();
7136+
size_t siglen;
7137+
#if PHP_OPENSSL_API_VERSION >= 0x10100
7138+
if (md_ctx != NULL &&
7139+
EVP_DigestSignInit(md_ctx, NULL, mdtype, NULL, pkey) &&
7140+
EVP_DigestSign(md_ctx, NULL, &siglen, (unsigned char*)data, data_len) &&
7141+
(sigbuf = zend_string_alloc(siglen, 0)) != NULL &&
7142+
EVP_DigestSign(md_ctx, (unsigned char*)ZSTR_VAL(sigbuf), &siglen, (unsigned char*)data, data_len)) {
7143+
#else
69847144
if (md_ctx != NULL &&
69857145
EVP_SignInit(md_ctx, mdtype) &&
69867146
EVP_SignUpdate(md_ctx, data, data_len) &&
6987-
EVP_SignFinal(md_ctx, (unsigned char*)ZSTR_VAL(sigbuf), &siglen, pkey)) {
7147+
(siglen = EVP_PKEY_size(pkey)) &&
7148+
(sigbuf = zend_string_alloc(siglen, 0)) != NULL &&
7149+
EVP_SignFinal(md_ctx, (unsigned char*)ZSTR_VAL(sigbuf), (unsigned int*)&siglen, pkey)) {
7150+
#endif
69887151
ZSTR_VAL(sigbuf)[siglen] = '\0';
69897152
ZSTR_LEN(sigbuf) = siglen;
69907153
ZEND_TRY_ASSIGN_REF_NEW_STR(signature, sigbuf);
@@ -7013,6 +7176,7 @@ PHP_FUNCTION(openssl_verify)
70137176
size_t signature_len;
70147177
zend_string *method_str = NULL;
70157178
zend_long method_long = OPENSSL_ALGO_SHA1;
7179+
bool can_default_digest = ZEND_THREEWAY_COMPARE(PHP_OPENSSL_API_VERSION, 0x30000) >= 0;
70167180

70177181
ZEND_PARSE_PARAMETERS_START(3, 4)
70187182
Z_PARAM_STRING(data, data_len)
@@ -7029,7 +7193,7 @@ PHP_FUNCTION(openssl_verify)
70297193
} else {
70307194
mdtype = php_openssl_get_evp_md_from_algo(method_long);
70317195
}
7032-
if (!mdtype) {
7196+
if (!mdtype && (!can_default_digest || method_long != 0)) {
70337197
php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
70347198
RETURN_FALSE;
70357199
}
@@ -7044,9 +7208,14 @@ PHP_FUNCTION(openssl_verify)
70447208

70457209
md_ctx = EVP_MD_CTX_create();
70467210
if (md_ctx == NULL ||
7211+
#if PHP_OPENSSL_API_VERSION >= 0x10100
7212+
!EVP_DigestVerifyInit(md_ctx, NULL, mdtype, NULL, pkey) ||
7213+
(err = EVP_DigestVerify(md_ctx, (unsigned char *)signature, signature_len, (unsigned char*)data, data_len)) < 0) {
7214+
#else
70477215
!EVP_VerifyInit (md_ctx, mdtype) ||
70487216
!EVP_VerifyUpdate (md_ctx, data, data_len) ||
70497217
(err = EVP_VerifyFinal(md_ctx, (unsigned char *)signature, (unsigned int)signature_len, pkey)) < 0) {
7218+
#endif
70507219
php_openssl_store_errors();
70517220
}
70527221
EVP_MD_CTX_destroy(md_ctx);

ext/openssl/openssl.stub.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,28 @@
331331
*/
332332
const OPENSSL_KEYTYPE_EC = UNKNOWN;
333333
#endif
334+
#if PHP_OPENSSL_API_VERSION >= 0x30000
335+
/**
336+
* @var int
337+
* @cvalue OPENSSL_KEYTYPE_X25519
338+
*/
339+
const OPENSSL_KEYTYPE_X25519 = UNKNOWN;
340+
/**
341+
* @var int
342+
* @cvalue OPENSSL_KEYTYPE_ED25519
343+
*/
344+
const OPENSSL_KEYTYPE_ED25519 = UNKNOWN;
345+
/**
346+
* @var int
347+
* @cvalue OPENSSL_KEYTYPE_X448
348+
*/
349+
const OPENSSL_KEYTYPE_X448 = UNKNOWN;
350+
/**
351+
* @var int
352+
* @cvalue OPENSSL_KEYTYPE_ED448
353+
*/
354+
const OPENSSL_KEYTYPE_ED448 = UNKNOWN;
355+
#endif
334356

335357
/**
336358
* @var int

ext/openssl/openssl_arginfo.h

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

0 commit comments

Comments
 (0)