Skip to content

Commit ddbda9a

Browse files
committed
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.
1 parent 3626e2d commit ddbda9a

File tree

7 files changed

+493
-10
lines changed

7 files changed

+493
-10
lines changed

ext/openssl/openssl.c

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

@@ -1011,7 +1017,11 @@ static int php_openssl_parse_config(struct php_x509_request * req, zval * option
10111017
req->digest_name = php_openssl_conf_get_string(req->req_config, req->section_name, "default_md");
10121018
}
10131019
if (req->digest_name != NULL) {
1014-
req->digest = req->md_alg = EVP_get_digestbyname(req->digest_name);
1020+
if (strcmp(req->digest_name, "null") == 0) {
1021+
req->digest = req->md_alg = EVP_md_null();
1022+
} else {
1023+
req->digest = req->md_alg = EVP_get_digestbyname(req->digest_name);
1024+
}
10151025
}
10161026
if (req->md_alg == NULL) {
10171027
req->md_alg = req->digest = EVP_sha1();
@@ -3732,6 +3742,16 @@ static int php_openssl_get_evp_pkey_type(int key_type) {
37323742
#ifdef HAVE_EVP_PKEY_EC
37333743
case OPENSSL_KEYTYPE_EC:
37343744
return EVP_PKEY_EC;
3745+
#endif
3746+
#if PHP_OPENSSL_API_VERSION >= 0x30000
3747+
case OPENSSL_KEYTYPE_X25519:
3748+
return EVP_PKEY_X25519;
3749+
case OPENSSL_KEYTYPE_ED25519:
3750+
return EVP_PKEY_ED25519;
3751+
case OPENSSL_KEYTYPE_X448:
3752+
return EVP_PKEY_X448;
3753+
case OPENSSL_KEYTYPE_ED448:
3754+
return EVP_PKEY_ED448;
37353755
#endif
37363756
default:
37373757
return -1;
@@ -3802,6 +3822,16 @@ static EVP_PKEY * php_openssl_generate_private_key(struct php_x509_request * req
38023822
goto cleanup;
38033823
}
38043824
break;
3825+
#endif
3826+
#if PHP_OPENSSL_API_VERSION >= 0x30000
3827+
case EVP_PKEY_X25519:
3828+
break;
3829+
case EVP_PKEY_ED25519:
3830+
break;
3831+
case EVP_PKEY_X448:
3832+
break;
3833+
case EVP_PKEY_ED448:
3834+
break;
38053835
#endif
38063836
EMPTY_SWITCH_DEFAULT_CASE()
38073837
}
@@ -4669,6 +4699,62 @@ static EVP_PKEY *php_openssl_pkey_init_ec(zval *data, bool *is_private) {
46694699
}
46704700
#endif
46714701

4702+
#if PHP_OPENSSL_API_VERSION >= 0x30000
4703+
static void php_openssl_pkey_object_curve_25519_448(zval *return_value, int key_type, zval *data) {
4704+
EVP_PKEY *pkey = NULL;
4705+
EVP_PKEY_CTX *ctx = NULL;
4706+
OSSL_PARAM *params = NULL;
4707+
OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
4708+
bool is_private;
4709+
4710+
RETVAL_FALSE;
4711+
4712+
zval *priv_key = zend_hash_str_find(Z_ARRVAL_P(data), "priv_key", sizeof("priv_key") - 1);
4713+
if (priv_key && Z_TYPE_P(priv_key) == IS_STRING && Z_STRLEN_P(priv_key) > 0) {
4714+
if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PRIV_KEY, Z_STRVAL_P(priv_key), Z_STRLEN_P(priv_key))) {
4715+
goto cleanup;
4716+
}
4717+
}
4718+
4719+
zval *pub_key = zend_hash_str_find(Z_ARRVAL_P(data), "pub_key", sizeof("pub_key") - 1);
4720+
if (pub_key && Z_TYPE_P(pub_key) == IS_STRING && Z_STRLEN_P(pub_key) > 0) {
4721+
if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, Z_STRVAL_P(pub_key), Z_STRLEN_P(pub_key))) {
4722+
goto cleanup;
4723+
}
4724+
}
4725+
4726+
params = OSSL_PARAM_BLD_to_param(bld);
4727+
if (!params) {
4728+
goto cleanup;
4729+
}
4730+
4731+
ctx = EVP_PKEY_CTX_new_id(key_type, NULL);
4732+
if (pub_key || priv_key) {
4733+
if (EVP_PKEY_fromdata_init(ctx) <= 0 ||
4734+
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
4735+
goto cleanup;
4736+
}
4737+
is_private = priv_key != NULL;
4738+
} else {
4739+
is_private = true;
4740+
PHP_OPENSSL_RAND_ADD_TIME();
4741+
if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_keygen(ctx, &pkey) <= 0) {
4742+
goto cleanup;
4743+
}
4744+
}
4745+
4746+
if (pkey) {
4747+
php_openssl_pkey_object_init(return_value, pkey, is_private);
4748+
}
4749+
4750+
cleanup:
4751+
php_openssl_store_errors();
4752+
EVP_PKEY_CTX_free(ctx);
4753+
OSSL_PARAM_free(params);
4754+
OSSL_PARAM_BLD_free(bld);
4755+
}
4756+
#endif
4757+
46724758
/* {{{ Generates a new private key */
46734759
PHP_FUNCTION(openssl_pkey_new)
46744760
{
@@ -4720,6 +4806,24 @@ PHP_FUNCTION(openssl_pkey_new)
47204806
}
47214807
php_openssl_pkey_object_init(return_value, pkey, is_private);
47224808
return;
4809+
#endif
4810+
#if PHP_OPENSSL_API_VERSION >= 0x30000
4811+
} else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "x25519", sizeof("x25519") - 1)) != NULL &&
4812+
Z_TYPE_P(data) == IS_ARRAY) {
4813+
php_openssl_pkey_object_curve_25519_448(return_value, EVP_PKEY_X25519, data);
4814+
return;
4815+
} else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "ed25519", sizeof("ed25519") - 1)) != NULL &&
4816+
Z_TYPE_P(data) == IS_ARRAY) {
4817+
php_openssl_pkey_object_curve_25519_448(return_value, EVP_PKEY_ED25519, data);
4818+
return;
4819+
} else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "x448", sizeof("x448") - 1)) != NULL &&
4820+
Z_TYPE_P(data) == IS_ARRAY) {
4821+
php_openssl_pkey_object_curve_25519_448(return_value, EVP_PKEY_X448, data);
4822+
return;
4823+
} else if ((data = zend_hash_str_find(Z_ARRVAL_P(args), "ed448", sizeof("ed448") - 1)) != NULL &&
4824+
Z_TYPE_P(data) == IS_ARRAY) {
4825+
php_openssl_pkey_object_curve_25519_448(return_value, EVP_PKEY_ED448, data);
4826+
return;
47234827
#endif
47244828
}
47254829
}
@@ -4952,6 +5056,27 @@ static zend_string *php_openssl_get_utf8_param(
49525056
return NULL;
49535057
}
49545058
#endif
5059+
5060+
static void php_openssl_copy_octet_string_param(
5061+
zval *ary, EVP_PKEY *pkey, const char *param, const char *name) {
5062+
unsigned char buf[64];
5063+
size_t len;
5064+
if (EVP_PKEY_get_octet_string_param(pkey, param, buf, sizeof(buf), &len) > 0) {
5065+
zend_string *str = zend_string_alloc(len, 0);
5066+
memcpy(ZSTR_VAL(str), buf, len);
5067+
ZSTR_VAL(str)[len] = '\0';
5068+
add_assoc_str(ary, name, str);
5069+
}
5070+
}
5071+
5072+
static void php_openssl_copy_curve_25519_448_params(
5073+
zval *return_value, const char *assoc_name, EVP_PKEY *pkey) {
5074+
zval ary;
5075+
array_init(&ary);
5076+
add_assoc_zval(return_value, assoc_name, &ary);
5077+
php_openssl_copy_octet_string_param(&ary, pkey, OSSL_PKEY_PARAM_PRIV_KEY, "priv_key");
5078+
php_openssl_copy_octet_string_param(&ary, pkey, OSSL_PKEY_PARAM_PUB_KEY, "pub_key");
5079+
}
49555080
#endif
49565081

49575082
/* {{{ returns an array with the key details (bits, pkey, type)*/
@@ -5060,6 +5185,28 @@ PHP_FUNCTION(openssl_pkey_get_details)
50605185
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_PRIV_KEY, "d");
50615186
break;
50625187
}
5188+
#endif
5189+
#if PHP_OPENSSL_API_VERSION >= 0x30000
5190+
case EVP_PKEY_X25519: {
5191+
ktype = OPENSSL_KEYTYPE_X25519;
5192+
php_openssl_copy_curve_25519_448_params(return_value, "x25519", pkey);
5193+
break;
5194+
}
5195+
case EVP_PKEY_ED25519: {
5196+
ktype = OPENSSL_KEYTYPE_ED25519;
5197+
php_openssl_copy_curve_25519_448_params(return_value, "ed25519", pkey);
5198+
break;
5199+
}
5200+
case EVP_PKEY_X448: {
5201+
ktype = OPENSSL_KEYTYPE_X448;
5202+
php_openssl_copy_curve_25519_448_params(return_value, "x448", pkey);
5203+
break;
5204+
}
5205+
case EVP_PKEY_ED448: {
5206+
ktype = OPENSSL_KEYTYPE_ED448;
5207+
php_openssl_copy_curve_25519_448_params(return_value, "ed448", pkey);
5208+
break;
5209+
}
50635210
#endif
50645211
default:
50655212
ktype = -1;
@@ -6937,14 +7084,14 @@ PHP_FUNCTION(openssl_sign)
69377084
{
69387085
zval *key, *signature;
69397086
EVP_PKEY *pkey;
6940-
unsigned int siglen;
6941-
zend_string *sigbuf;
7087+
zend_string *sigbuf = NULL;
69427088
char * data;
69437089
size_t data_len;
69447090
EVP_MD_CTX *md_ctx;
69457091
zend_string *method_str = NULL;
69467092
zend_long method_long = OPENSSL_ALGO_SHA1;
69477093
const EVP_MD *mdtype;
7094+
bool can_default_digest = ZEND_THREEWAY_COMPARE(PHP_OPENSSL_API_VERSION, 0x30000) >= 0;
69487095

69497096
ZEND_PARSE_PARAMETERS_START(3, 4)
69507097
Z_PARAM_STRING(data, data_len)
@@ -6967,19 +7114,27 @@ PHP_FUNCTION(openssl_sign)
69677114
} else {
69687115
mdtype = php_openssl_get_evp_md_from_algo(method_long);
69697116
}
6970-
if (!mdtype) {
7117+
if (!mdtype && (!can_default_digest || method_long != 0)) {
69717118
php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
69727119
RETURN_FALSE;
69737120
}
69747121

6975-
siglen = EVP_PKEY_size(pkey);
6976-
sigbuf = zend_string_alloc(siglen, 0);
6977-
69787122
md_ctx = EVP_MD_CTX_create();
7123+
size_t siglen;
7124+
#if PHP_OPENSSL_API_VERSION >= 0x10100
7125+
if (md_ctx != NULL &&
7126+
EVP_DigestSignInit(md_ctx, NULL, mdtype, NULL, pkey) &&
7127+
EVP_DigestSign(md_ctx, NULL, &siglen, (unsigned char*)data, data_len) &&
7128+
(sigbuf = zend_string_alloc(siglen, 0)) != NULL &&
7129+
EVP_DigestSign(md_ctx, (unsigned char*)ZSTR_VAL(sigbuf), &siglen, (unsigned char*)data, data_len)) {
7130+
#else
69797131
if (md_ctx != NULL &&
69807132
EVP_SignInit(md_ctx, mdtype) &&
69817133
EVP_SignUpdate(md_ctx, data, data_len) &&
6982-
EVP_SignFinal(md_ctx, (unsigned char*)ZSTR_VAL(sigbuf), &siglen, pkey)) {
7134+
(siglen = EVP_PKEY_size(pkey)) &&
7135+
(sigbuf = zend_string_alloc(siglen, 0)) != NULL &&
7136+
EVP_SignFinal(md_ctx, (unsigned char*)ZSTR_VAL(sigbuf), (unsigned int*)&siglen, pkey)) {
7137+
#endif
69837138
ZSTR_VAL(sigbuf)[siglen] = '\0';
69847139
ZSTR_LEN(sigbuf) = siglen;
69857140
ZEND_TRY_ASSIGN_REF_NEW_STR(signature, sigbuf);
@@ -7008,6 +7163,7 @@ PHP_FUNCTION(openssl_verify)
70087163
size_t signature_len;
70097164
zend_string *method_str = NULL;
70107165
zend_long method_long = OPENSSL_ALGO_SHA1;
7166+
bool can_default_digest = ZEND_THREEWAY_COMPARE(PHP_OPENSSL_API_VERSION, 0x30000) >= 0;
70117167

70127168
ZEND_PARSE_PARAMETERS_START(3, 4)
70137169
Z_PARAM_STRING(data, data_len)
@@ -7024,7 +7180,7 @@ PHP_FUNCTION(openssl_verify)
70247180
} else {
70257181
mdtype = php_openssl_get_evp_md_from_algo(method_long);
70267182
}
7027-
if (!mdtype) {
7183+
if (!mdtype && (!can_default_digest || method_long != 0)) {
70287184
php_error_docref(NULL, E_WARNING, "Unknown digest algorithm");
70297185
RETURN_FALSE;
70307186
}
@@ -7039,9 +7195,14 @@ PHP_FUNCTION(openssl_verify)
70397195

70407196
md_ctx = EVP_MD_CTX_create();
70417197
if (md_ctx == NULL ||
7198+
#if PHP_OPENSSL_API_VERSION >= 0x10100
7199+
!EVP_DigestVerifyInit(md_ctx, NULL, mdtype, NULL, pkey) ||
7200+
(err = EVP_DigestVerify(md_ctx, (unsigned char *)signature, signature_len, (unsigned char*)data, data_len)) < 0) {
7201+
#else
70427202
!EVP_VerifyInit (md_ctx, mdtype) ||
70437203
!EVP_VerifyUpdate (md_ctx, data, data_len) ||
70447204
(err = EVP_VerifyFinal(md_ctx, (unsigned char *)signature, (unsigned int)signature_len, pkey)) < 0) {
7205+
#endif
70457206
php_openssl_store_errors();
70467207
}
70477208
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: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)