php-src/ext/openssl/openssl_backend_v3.c
Jakub Zelenka c5f79b8cf9
Use OpenSSL libctx for various BIO readers (#19448)
This converts mostly PEM readers but also DER for CMS
2025-08-11 18:21:38 +02:00

968 lines
26 KiB
C

/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Jakub Zelenka <bukka@php.net> |
+----------------------------------------------------------------------+
*/
#include "php_openssl_backend.h"
#if PHP_OPENSSL_API_VERSION >= 0x30000
#include <openssl/core_names.h>
#include <openssl/param_build.h>
#include <openssl/provider.h>
ZEND_EXTERN_MODULE_GLOBALS(openssl)
void php_openssl_backend_shutdown(void)
{
(void) 0;
}
#define PHP_OPENSSL_DEFAULT_CONF_MFLAGS \
(CONF_MFLAGS_DEFAULT_SECTION | CONF_MFLAGS_IGNORE_MISSING_FILE | CONF_MFLAGS_IGNORE_RETURN_CODES)
void php_openssl_backend_init_libctx(struct php_openssl_libctx *ctx)
{
ctx->default_libctx = OSSL_LIB_CTX_get0_global_default();
ctx->custom_libctx = OSSL_LIB_CTX_new();
if (ctx->custom_libctx != NULL) {
/* This is not being checked because there is not much that can be done. */
CONF_modules_load_file_ex(ctx->custom_libctx, NULL, NULL,
PHP_OPENSSL_DEFAULT_CONF_MFLAGS);
#ifdef LOAD_OPENSSL_LEGACY_PROVIDER
OSSL_PROVIDER_load(ctx->custom_libctx, "legacy");
OSSL_PROVIDER_load(ctx->custom_libctx, "default");
#endif
ctx->libctx = ctx->custom_libctx;
} else {
/* If creation fails, just fallback to default */
ctx->libctx = ctx->default_libctx;
}
ctx->propq = NULL;
}
void php_openssl_backend_destroy_libctx(struct php_openssl_libctx *ctx)
{
if (ctx->custom_libctx != NULL) {
OSSL_LIB_CTX_free(ctx->custom_libctx);
}
if (ctx->propq != NULL) {
free(ctx->propq);
}
}
EVP_PKEY_CTX *php_openssl_pkey_new_from_name(const char *name, int id)
{
return EVP_PKEY_CTX_new_from_name(PHP_OPENSSL_LIBCTX, name, PHP_OPENSSL_PROPQ);
}
EVP_PKEY_CTX *php_openssl_pkey_new_from_pkey(EVP_PKEY *pkey)
{
return EVP_PKEY_CTX_new_from_pkey(PHP_OPENSSL_LIBCTX, pkey, PHP_OPENSSL_PROPQ);
}
EVP_PKEY *php_openssl_pkey_init_rsa(zval *data)
{
BIGNUM *n = NULL, *e = NULL, *d = NULL, *p = NULL, *q = NULL;
BIGNUM *dmp1 = NULL, *dmq1 = NULL, *iqmp = NULL;
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *ctx = php_openssl_pkey_new_from_name("RSA", EVP_PKEY_RSA);
OSSL_PARAM *params = NULL;
OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
OPENSSL_PKEY_SET_BN(data, n);
OPENSSL_PKEY_SET_BN(data, e);
OPENSSL_PKEY_SET_BN(data, d);
OPENSSL_PKEY_SET_BN(data, p);
OPENSSL_PKEY_SET_BN(data, q);
OPENSSL_PKEY_SET_BN(data, dmp1);
OPENSSL_PKEY_SET_BN(data, dmq1);
OPENSSL_PKEY_SET_BN(data, iqmp);
if (!ctx || !bld || !n || !d) {
goto cleanup;
}
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, n);
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_D, d);
if (e) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, e);
}
if (p) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR1, p);
}
if (q) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_FACTOR2, q);
}
if (dmp1) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1);
}
if (dmq1) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1);
}
if (iqmp) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, iqmp);
}
params = OSSL_PARAM_BLD_to_param(bld);
if (!params) {
goto cleanup;
}
if (EVP_PKEY_fromdata_init(ctx) <= 0 ||
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
goto cleanup;
}
cleanup:
php_openssl_store_errors();
EVP_PKEY_CTX_free(ctx);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(bld);
BN_free(n);
BN_free(e);
BN_free(d);
BN_free(p);
BN_free(q);
BN_free(dmp1);
BN_free(dmq1);
BN_free(iqmp);
return pkey;
}
EVP_PKEY *php_openssl_pkey_init_dsa(zval *data, bool *is_private)
{
BIGNUM *p = NULL, *q = NULL, *g = NULL, *priv_key = NULL, *pub_key = NULL;
EVP_PKEY *param_key = NULL, *pkey = NULL;
EVP_PKEY_CTX *ctx = php_openssl_pkey_new_from_name("DSA", EVP_PKEY_DSA);
OSSL_PARAM *params = NULL;
OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
OPENSSL_PKEY_SET_BN(data, p);
OPENSSL_PKEY_SET_BN(data, q);
OPENSSL_PKEY_SET_BN(data, g);
OPENSSL_PKEY_SET_BN(data, priv_key);
OPENSSL_PKEY_SET_BN(data, pub_key);
*is_private = false;
if (!ctx || !bld || !p || !q || !g) {
goto cleanup;
}
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, p);
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_Q, q);
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, g);
// TODO: We silently ignore priv_key if pub_key is not given, unlike in the DH case.
if (pub_key) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PUB_KEY, pub_key);
if (priv_key) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, priv_key);
}
}
params = OSSL_PARAM_BLD_to_param(bld);
if (!params) {
goto cleanup;
}
if (EVP_PKEY_fromdata_init(ctx) <= 0 ||
EVP_PKEY_fromdata(ctx, &param_key, EVP_PKEY_KEYPAIR, params) <= 0) {
goto cleanup;
}
if (pub_key) {
*is_private = priv_key != NULL;
EVP_PKEY_up_ref(param_key);
pkey = param_key;
} else {
*is_private = true;
EVP_PKEY_CTX_free(ctx);
ctx = php_openssl_pkey_new_from_pkey(param_key);
if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_keygen(ctx, &pkey) <= 0) {
goto cleanup;
}
}
cleanup:
php_openssl_store_errors();
EVP_PKEY_free(param_key);
EVP_PKEY_CTX_free(ctx);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(bld);
BN_free(p);
BN_free(q);
BN_free(g);
BN_free(priv_key);
BN_free(pub_key);
return pkey;
}
EVP_PKEY *php_openssl_pkey_init_dh(zval *data, bool *is_private)
{
BIGNUM *p = NULL, *q = NULL, *g = NULL, *priv_key = NULL, *pub_key = NULL;
EVP_PKEY *param_key = NULL, *pkey = NULL;
EVP_PKEY_CTX *ctx = php_openssl_pkey_new_from_name("DH", EVP_PKEY_DH);
OSSL_PARAM *params = NULL;
OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
OPENSSL_PKEY_SET_BN(data, p);
OPENSSL_PKEY_SET_BN(data, q);
OPENSSL_PKEY_SET_BN(data, g);
OPENSSL_PKEY_SET_BN(data, priv_key);
OPENSSL_PKEY_SET_BN(data, pub_key);
*is_private = false;
if (!ctx || !bld || !p || !g) {
goto cleanup;
}
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_P, p);
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_G, g);
if (q) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_FFC_Q, q);
}
if (priv_key) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, priv_key);
if (!pub_key) {
pub_key = php_openssl_dh_pub_from_priv(priv_key, g, p);
if (!pub_key) {
goto cleanup;
}
}
}
if (pub_key) {
OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PUB_KEY, pub_key);
}
params = OSSL_PARAM_BLD_to_param(bld);
if (!params) {
goto cleanup;
}
if (EVP_PKEY_fromdata_init(ctx) <= 0 ||
EVP_PKEY_fromdata(ctx, &param_key, EVP_PKEY_KEYPAIR, params) <= 0) {
goto cleanup;
}
if (pub_key || priv_key) {
*is_private = priv_key != NULL;
EVP_PKEY_up_ref(param_key);
pkey = param_key;
} else {
*is_private = true;
EVP_PKEY_CTX_free(ctx);
ctx = php_openssl_pkey_new_from_pkey(param_key);
if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_keygen(ctx, &pkey) <= 0) {
goto cleanup;
}
}
cleanup:
php_openssl_store_errors();
EVP_PKEY_free(param_key);
EVP_PKEY_CTX_free(ctx);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(bld);
BN_free(p);
BN_free(q);
BN_free(g);
BN_free(priv_key);
BN_free(pub_key);
return pkey;
}
#ifdef HAVE_EVP_PKEY_EC
EVP_PKEY *php_openssl_pkey_init_ec(zval *data, bool *is_private) {
int nid = NID_undef;
BIGNUM *p = NULL, *a = NULL, *b = NULL, *order = NULL, *g_x = NULL, *g_y = NULL, *cofactor = NULL;
BIGNUM *x = NULL, *y = NULL, *d = NULL;
EC_POINT *point_g = NULL;
EC_POINT *point_q = NULL;
unsigned char *point_g_buf = NULL;
unsigned char *point_q_buf = NULL;
EC_GROUP *group = NULL;
EVP_PKEY *param_key = NULL, *pkey = NULL;
EVP_PKEY_CTX *ctx = php_openssl_pkey_new_from_name("EC", EVP_PKEY_EC);
BN_CTX *bctx = BN_CTX_new();
OSSL_PARAM *params = NULL;
OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
*is_private = false;
if (!ctx || !bld || !bctx) {
goto cleanup;
}
zval *curve_name_zv = zend_hash_str_find(Z_ARRVAL_P(data), "curve_name", sizeof("curve_name") - 1);
if (curve_name_zv && Z_TYPE_P(curve_name_zv) == IS_STRING && Z_STRLEN_P(curve_name_zv) > 0) {
nid = OBJ_sn2nid(Z_STRVAL_P(curve_name_zv));
if (nid == NID_undef) {
php_error_docref(NULL, E_WARNING, "Unknown elliptic curve (short) name %s", Z_STRVAL_P(curve_name_zv));
goto cleanup;
}
if (!(group = EC_GROUP_new_by_curve_name_ex(PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ, nid))) {
goto cleanup;
}
if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, Z_STRVAL_P(curve_name_zv), Z_STRLEN_P(curve_name_zv))) {
goto cleanup;
}
} else {
OPENSSL_PKEY_SET_BN(data, p);
OPENSSL_PKEY_SET_BN(data, a);
OPENSSL_PKEY_SET_BN(data, b);
OPENSSL_PKEY_SET_BN(data, order);
if (!(p && a && b && order)) {
if (!p && !a && !b && !order) {
php_error_docref(NULL, E_WARNING, "Missing params: curve_name");
} else {
php_error_docref(NULL, E_WARNING, "Missing params: curve_name or p, a, b, order");
}
goto cleanup;
}
if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_P, p) ||
!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_A, a) ||
!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_B, b) ||
!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_ORDER, order) ||
!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_EC_FIELD_TYPE, SN_X9_62_prime_field, 0)) {
goto cleanup;
}
if (!(group = EC_GROUP_new_curve_GFp(p, a, b, bctx))) {
goto cleanup;
}
if (!(point_g = EC_POINT_new(group))) {
goto cleanup;
}
zval *generator_zv = zend_hash_str_find(Z_ARRVAL_P(data), "generator", sizeof("generator") - 1);
if (generator_zv && Z_TYPE_P(generator_zv) == IS_STRING && Z_STRLEN_P(generator_zv) > 0) {
if (!EC_POINT_oct2point(group, point_g, (unsigned char *)Z_STRVAL_P(generator_zv), Z_STRLEN_P(generator_zv), bctx) ||
!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_GENERATOR, Z_STRVAL_P(generator_zv), Z_STRLEN_P(generator_zv))) {
goto cleanup;
}
} else {
OPENSSL_PKEY_SET_BN(data, g_x);
OPENSSL_PKEY_SET_BN(data, g_y);
if (!g_x || !g_y) {
php_error_docref(
NULL, E_WARNING, "Missing params: generator or g_x and g_y");
goto cleanup;
}
if (!EC_POINT_set_affine_coordinates(group, point_g, g_x, g_y, bctx)) {
goto cleanup;
}
size_t point_g_buf_len =
EC_POINT_point2buf(group, point_g, POINT_CONVERSION_COMPRESSED, &point_g_buf, bctx);
if (!point_g_buf_len) {
goto cleanup;
}
if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_GENERATOR, point_g_buf, point_g_buf_len)) {
goto cleanup;
}
}
zval *seed_zv = zend_hash_str_find(Z_ARRVAL_P(data), "seed", sizeof("seed") - 1);
if (seed_zv && Z_TYPE_P(seed_zv) == IS_STRING && Z_STRLEN_P(seed_zv) > 0) {
if (!EC_GROUP_set_seed(group, (unsigned char *)Z_STRVAL_P(seed_zv), Z_STRLEN_P(seed_zv)) ||
!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_EC_SEED, Z_STRVAL_P(seed_zv), Z_STRLEN_P(seed_zv))) {
goto cleanup;
}
}
OPENSSL_PKEY_SET_BN(data, cofactor);
if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_EC_COFACTOR, cofactor) ||
!EC_GROUP_set_generator(group, point_g, order, cofactor)) {
goto cleanup;
}
nid = EC_GROUP_check_named_curve(group, 0, bctx);
}
/* custom params not supported with SM2, SKIP */
if (nid != NID_sm2) {
OPENSSL_PKEY_SET_BN(data, d);
OPENSSL_PKEY_SET_BN(data, x);
OPENSSL_PKEY_SET_BN(data, y);
if (d) {
point_q = EC_POINT_new(group);
if (!point_q || !EC_POINT_mul(group, point_q, d, NULL, NULL, bctx) ||
!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_PRIV_KEY, d)) {
goto cleanup;
}
} else if (x && y) {
/* OpenSSL does not allow setting EC_PUB_X/EC_PUB_Y, so convert to encoded format. */
point_q = EC_POINT_new(group);
if (!point_q || !EC_POINT_set_affine_coordinates(group, point_q, x, y, bctx)) {
goto cleanup;
}
}
if (point_q) {
size_t point_q_buf_len =
EC_POINT_point2buf(group, point_q, POINT_CONVERSION_COMPRESSED, &point_q_buf, bctx);
if (!point_q_buf_len ||
!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, point_q_buf, point_q_buf_len)) {
goto cleanup;
}
}
}
params = OSSL_PARAM_BLD_to_param(bld);
if (!params) {
goto cleanup;
}
if (d || (x && y)) {
if (EVP_PKEY_fromdata_init(ctx) <= 0 ||
EVP_PKEY_fromdata(ctx, &param_key, EVP_PKEY_KEYPAIR, params) <= 0) {
goto cleanup;
}
EVP_PKEY_CTX_free(ctx);
ctx = EVP_PKEY_CTX_new(param_key, NULL);
}
if (EVP_PKEY_check(ctx) || EVP_PKEY_public_check_quick(ctx)) {
*is_private = d != NULL;
EVP_PKEY_up_ref(param_key);
pkey = param_key;
} else {
*is_private = true;
if (EVP_PKEY_keygen_init(ctx) != 1 ||
EVP_PKEY_CTX_set_params(ctx, params) != 1 ||
EVP_PKEY_generate(ctx, &pkey) != 1) {
goto cleanup;
}
}
cleanup:
php_openssl_store_errors();
EVP_PKEY_free(param_key);
EVP_PKEY_CTX_free(ctx);
BN_CTX_free(bctx);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(bld);
EC_GROUP_free(group);
EC_POINT_free(point_g);
EC_POINT_free(point_q);
OPENSSL_free(point_g_buf);
OPENSSL_free(point_q_buf);
BN_free(p);
BN_free(a);
BN_free(b);
BN_free(order);
BN_free(g_x);
BN_free(g_y);
BN_free(cofactor);
BN_free(d);
BN_free(x);
BN_free(y);
return pkey;
}
#endif
void php_openssl_pkey_object_curve_25519_448(zval *return_value, const char *name, zval *data) {
EVP_PKEY *pkey = NULL;
EVP_PKEY_CTX *ctx = NULL;
OSSL_PARAM *params = NULL;
OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new();
bool is_private;
RETVAL_FALSE;
if (!bld) {
goto cleanup;
}
zval *priv_key = zend_hash_str_find(Z_ARRVAL_P(data), "priv_key", sizeof("priv_key") - 1);
if (priv_key && Z_TYPE_P(priv_key) == IS_STRING && Z_STRLEN_P(priv_key) > 0) {
if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PRIV_KEY, Z_STRVAL_P(priv_key), Z_STRLEN_P(priv_key))) {
goto cleanup;
}
}
zval *pub_key = zend_hash_str_find(Z_ARRVAL_P(data), "pub_key", sizeof("pub_key") - 1);
if (pub_key && Z_TYPE_P(pub_key) == IS_STRING && Z_STRLEN_P(pub_key) > 0) {
if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, Z_STRVAL_P(pub_key), Z_STRLEN_P(pub_key))) {
goto cleanup;
}
}
params = OSSL_PARAM_BLD_to_param(bld);
ctx = php_openssl_pkey_new_from_name(name, 0);
if (!params || !ctx) {
goto cleanup;
}
if (pub_key || priv_key) {
if (EVP_PKEY_fromdata_init(ctx) <= 0 ||
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
goto cleanup;
}
is_private = priv_key != NULL;
} else {
is_private = true;
if (EVP_PKEY_keygen_init(ctx) <= 0 || EVP_PKEY_keygen(ctx, &pkey) <= 0) {
goto cleanup;
}
}
if (pkey) {
php_openssl_pkey_object_init(return_value, pkey, is_private);
}
cleanup:
php_openssl_store_errors();
EVP_PKEY_CTX_free(ctx);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(bld);
}
static void php_openssl_copy_bn_param(
zval *ary, EVP_PKEY *pkey, const char *param, const char *name) {
BIGNUM *bn = NULL;
if (EVP_PKEY_get_bn_param(pkey, param, &bn) > 0) {
php_openssl_add_bn_to_array(ary, bn, name);
BN_free(bn);
}
}
#ifdef HAVE_EVP_PKEY_EC
static zend_string *php_openssl_get_utf8_param(
EVP_PKEY *pkey, const char *param, const char *name) {
char buf[64];
size_t len;
if (EVP_PKEY_get_utf8_string_param(pkey, param, buf, sizeof(buf), &len) > 0) {
zend_string *str = zend_string_alloc(len, 0);
memcpy(ZSTR_VAL(str), buf, len);
ZSTR_VAL(str)[len] = '\0';
return str;
}
return NULL;
}
#endif
static void php_openssl_copy_octet_string_param(
zval *ary, EVP_PKEY *pkey, const char *param, const char *name)
{
unsigned char buf[64];
size_t len;
if (EVP_PKEY_get_octet_string_param(pkey, param, buf, sizeof(buf), &len) > 0) {
zend_string *str = zend_string_alloc(len, 0);
memcpy(ZSTR_VAL(str), buf, len);
ZSTR_VAL(str)[len] = '\0';
add_assoc_str(ary, name, str);
}
}
static void php_openssl_copy_curve_25519_448_params(
zval *return_value, const char *assoc_name, EVP_PKEY *pkey)
{
zval ary;
array_init(&ary);
add_assoc_zval(return_value, assoc_name, &ary);
php_openssl_copy_octet_string_param(&ary, pkey, OSSL_PKEY_PARAM_PRIV_KEY, "priv_key");
php_openssl_copy_octet_string_param(&ary, pkey, OSSL_PKEY_PARAM_PUB_KEY, "pub_key");
}
zend_long php_openssl_pkey_get_details(zval *return_value, EVP_PKEY *pkey)
{
zval ary;
int base_id = 0;
zend_long ktype;
if (EVP_PKEY_id(pkey) != EVP_PKEY_KEYMGMT) {
base_id = EVP_PKEY_base_id(pkey);
} else {
const char *type_name = EVP_PKEY_get0_type_name(pkey);
if (type_name) {
int nid = OBJ_txt2nid(type_name);
if (nid != NID_undef) {
base_id = EVP_PKEY_type(nid);
}
}
}
switch (base_id) {
case EVP_PKEY_RSA:
ktype = OPENSSL_KEYTYPE_RSA;
array_init(&ary);
add_assoc_zval(return_value, "rsa", &ary);
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_RSA_N, "n");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_RSA_E, "e");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_RSA_D, "d");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, "p");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, "q");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, "dmp1");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, "dmq1");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, "iqmp");
break;
case EVP_PKEY_DSA:
ktype = OPENSSL_KEYTYPE_DSA;
array_init(&ary);
add_assoc_zval(return_value, "dsa", &ary);
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_FFC_P, "p");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_FFC_Q, "q");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_FFC_G, "g");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_PRIV_KEY, "priv_key");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_PUB_KEY, "pub_key");
break;
case EVP_PKEY_DH:
ktype = OPENSSL_KEYTYPE_DH;
array_init(&ary);
add_assoc_zval(return_value, "dh", &ary);
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_FFC_P, "p");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_FFC_G, "g");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_PRIV_KEY, "priv_key");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_PUB_KEY, "pub_key");
break;
#ifdef HAVE_EVP_PKEY_EC
case EVP_PKEY_EC: {
ktype = OPENSSL_KEYTYPE_EC;
array_init(&ary);
add_assoc_zval(return_value, "ec", &ary);
zend_string *curve_name = php_openssl_get_utf8_param(
pkey, OSSL_PKEY_PARAM_GROUP_NAME, "curve_name");
if (curve_name) {
add_assoc_str(&ary, "curve_name", curve_name);
int nid = OBJ_sn2nid(ZSTR_VAL(curve_name));
if (nid != NID_undef) {
ASN1_OBJECT *obj = OBJ_nid2obj(nid);
if (obj) {
// OpenSSL recommends a buffer length of 80.
char oir_buf[80];
int oir_len = OBJ_obj2txt(oir_buf, sizeof(oir_buf), obj, 1);
add_assoc_stringl(&ary, "curve_oid", oir_buf, oir_len);
ASN1_OBJECT_free(obj);
}
}
}
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_EC_PUB_X, "x");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_EC_PUB_Y, "y");
php_openssl_copy_bn_param(&ary, pkey, OSSL_PKEY_PARAM_PRIV_KEY, "d");
break;
}
#endif
case EVP_PKEY_X25519: {
ktype = OPENSSL_KEYTYPE_X25519;
php_openssl_copy_curve_25519_448_params(return_value, "x25519", pkey);
break;
}
case EVP_PKEY_ED25519: {
ktype = OPENSSL_KEYTYPE_ED25519;
php_openssl_copy_curve_25519_448_params(return_value, "ed25519", pkey);
break;
}
case EVP_PKEY_X448: {
ktype = OPENSSL_KEYTYPE_X448;
php_openssl_copy_curve_25519_448_params(return_value, "x448", pkey);
break;
}
case EVP_PKEY_ED448: {
ktype = OPENSSL_KEYTYPE_ED448;
php_openssl_copy_curve_25519_448_params(return_value, "ed448", pkey);
break;
}
default:
ktype = -1;
break;
}
return ktype;
}
zend_string *php_openssl_dh_compute_key(EVP_PKEY *pkey, char *pub_str, size_t pub_len)
{
EVP_PKEY *peer_key = EVP_PKEY_new();
if (!peer_key || EVP_PKEY_copy_parameters(peer_key, pkey) <= 0 ||
EVP_PKEY_set1_encoded_public_key(peer_key, (unsigned char *) pub_str, pub_len) <= 0) {
php_openssl_store_errors();
EVP_PKEY_free(peer_key);
return NULL;
}
zend_string *result = php_openssl_pkey_derive(pkey, peer_key, 0);
EVP_PKEY_free(peer_key);
return result;
}
const EVP_MD *php_openssl_get_evp_md_by_name(const char *name)
{
const EVP_MD *dp = (const EVP_MD *) OBJ_NAME_get(name, OBJ_NAME_TYPE_MD_METH);
if (dp != NULL) {
return dp;
}
return EVP_MD_fetch(PHP_OPENSSL_LIBCTX, name, PHP_OPENSSL_PROPQ);
}
static const char *php_openssl_digest_names[] = {
[OPENSSL_ALGO_SHA1] = "SHA1",
[OPENSSL_ALGO_MD5] = "MD5",
#ifndef OPENSSL_NO_MD4
[OPENSSL_ALGO_MD4] = "MD4",
#endif
#ifndef OPENSSL_NO_MD2
[OPENSSL_ALGO_MD2] = "MD2",
#endif
[OPENSSL_ALGO_SHA224] = "SHA224",
[OPENSSL_ALGO_SHA256] = "SHA256",
[OPENSSL_ALGO_SHA384] = "SHA384",
[OPENSSL_ALGO_SHA512] = "SHA512",
#ifndef OPENSSL_NO_RMD160
[OPENSSL_ALGO_RMD160] = "RIPEMD160",
#endif
};
const EVP_MD *php_openssl_get_evp_md_from_algo(zend_long algo)
{
if (algo < 0 || algo >= (zend_long)(sizeof(php_openssl_digest_names) / sizeof(*php_openssl_digest_names))) {
return NULL;
}
const char *name = php_openssl_digest_names[algo];
if (!name) {
return NULL;
}
return php_openssl_get_evp_md_by_name(name);
}
void php_openssl_release_evp_md(const EVP_MD *md)
{
if (md != NULL) {
// It is fine to remove const as the md is from EVP_MD_fetch
EVP_MD_free((EVP_MD *) md);
}
}
static const char *php_openssl_cipher_names[] = {
[PHP_OPENSSL_CIPHER_RC2_40] = "RC2-40-CBC",
[PHP_OPENSSL_CIPHER_RC2_128] = "RC2-CBC",
[PHP_OPENSSL_CIPHER_RC2_64] = "RC2-64-CBC",
[PHP_OPENSSL_CIPHER_DES] = "DES-CBC",
[PHP_OPENSSL_CIPHER_3DES] = "DES-EDE3-CBC",
[PHP_OPENSSL_CIPHER_AES_128_CBC]= "AES-128-CBC",
[PHP_OPENSSL_CIPHER_AES_192_CBC]= "AES-192-CBC",
[PHP_OPENSSL_CIPHER_AES_256_CBC]= "AES-256-CBC",
};
const EVP_CIPHER *php_openssl_get_evp_cipher_by_name(const char *name)
{
const EVP_CIPHER *cp = (const EVP_CIPHER *) OBJ_NAME_get(name, OBJ_NAME_TYPE_CIPHER_METH);
if (cp != NULL) {
return cp;
}
return EVP_CIPHER_fetch(PHP_OPENSSL_LIBCTX, name, PHP_OPENSSL_PROPQ);
}
const EVP_CIPHER *php_openssl_get_evp_cipher_from_algo(zend_long algo)
{
if (algo < 0 || algo >= (zend_long)(sizeof(php_openssl_cipher_names) / sizeof(*php_openssl_cipher_names))) {
return NULL;
}
const char *name = php_openssl_cipher_names[algo];
if (!name) {
return NULL;
}
return php_openssl_get_evp_cipher_by_name(name);
}
void php_openssl_release_evp_cipher(const EVP_CIPHER *cipher)
{
if (cipher != NULL) {
// It is fine to remove const as the cipher is from EVP_CIPHER_fetch
EVP_CIPHER_free((EVP_CIPHER *) cipher);
}
}
static void php_openssl_add_cipher_name(const char *name, void *arg)
{
size_t len = strlen(name);
zend_string *str = zend_string_alloc(len, 0);
zend_str_tolower_copy(ZSTR_VAL(str), name, len);
add_next_index_str((zval*)arg, str);
}
static void php_openssl_add_cipher_or_alias(EVP_CIPHER *cipher, void *arg)
{
EVP_CIPHER_names_do_all(cipher, php_openssl_add_cipher_name, arg);
}
static void php_openssl_add_cipher(EVP_CIPHER *cipher, void *arg)
{
php_openssl_add_cipher_name(EVP_CIPHER_get0_name(cipher), arg);
}
static int php_openssl_compare_func(Bucket *a, Bucket *b)
{
return string_compare_function(&a->val, &b->val);
}
void php_openssl_get_cipher_methods(zval *return_value, bool aliases)
{
array_init(return_value);
EVP_CIPHER_do_all_provided(PHP_OPENSSL_LIBCTX,
aliases ? php_openssl_add_cipher_or_alias : php_openssl_add_cipher,
return_value);
zend_hash_sort(Z_ARRVAL_P(return_value), php_openssl_compare_func, 1);
}
CONF *php_openssl_nconf_new(void)
{
return NCONF_new_ex(PHP_OPENSSL_LIBCTX, NULL);
}
X509 *php_openssl_pem_read_asn1_bio_x509(BIO *in)
{
X509 *x = X509_new_ex(PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ);
if (x == NULL) {
return NULL;
}
if (PEM_ASN1_read_bio((d2i_of_void *)d2i_X509, PEM_STRING_X509, in, (void **) &x, NULL, NULL) == NULL) {
X509_free(x);
return NULL;
}
return x;
}
X509 *php_openssl_pem_read_bio_x509(BIO *in)
{
X509 *x = X509_new_ex(PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ);
if (x == NULL) {
return NULL;
}
if (PEM_read_bio_X509(in, &x, NULL, NULL) == NULL) {
X509_free(x);
return NULL;
}
return x;
}
X509_REQ *php_openssl_pem_read_bio_x509_req(BIO *in)
{
X509_REQ *xr = X509_REQ_new_ex(PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ);
if (xr == NULL) {
return NULL;
}
if (PEM_read_bio_X509_REQ(in, &xr, NULL, NULL) == NULL) {
X509_REQ_free(xr);
return NULL;
}
return xr;
}
EVP_PKEY *php_openssl_pem_read_bio_public_key(BIO *in)
{
return PEM_read_bio_PUBKEY_ex(in, NULL, NULL, NULL, PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ);
}
EVP_PKEY *php_openssl_pem_read_bio_private_key(BIO *in, pem_password_cb *cb, void *u)
{
return PEM_read_bio_PrivateKey_ex(in, NULL, cb, u, PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ);
}
PKCS7 *php_openssl_pem_read_bio_pkcs7(BIO *in)
{
PKCS7 *p = PKCS7_new_ex(PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ);
if (p == NULL) {
return NULL;
}
if (PEM_read_bio_PKCS7(in, &p, NULL, NULL) == NULL) {
PKCS7_free(p);
return NULL;
}
return p;
}
CMS_ContentInfo *php_openssl_pem_read_bio_cms(BIO *in)
{
CMS_ContentInfo *ci = CMS_ContentInfo_new_ex(PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ);
if (ci == NULL) {
return NULL;
}
if (PEM_read_bio_CMS(in, &ci, NULL, NULL) == NULL) {
CMS_ContentInfo_free(ci);
return NULL;
}
return ci;
}
CMS_ContentInfo *php_openssl_d2i_bio_cms(BIO *in)
{
CMS_ContentInfo *ci = CMS_ContentInfo_new_ex(PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ);
if (ci == NULL) {
return NULL;
}
if (d2i_CMS_bio(in, &ci) == NULL) {
CMS_ContentInfo_free(ci);
return NULL;
}
return ci;
}
CMS_ContentInfo *php_openssl_smime_read_cms(BIO *bio, BIO **bcont)
{
CMS_ContentInfo *ci = CMS_ContentInfo_new_ex(PHP_OPENSSL_LIBCTX, PHP_OPENSSL_PROPQ);
if (ci == NULL) {
return NULL;
}
if (SMIME_read_CMS_ex(bio, 0, bcont, &ci) == NULL) {
CMS_ContentInfo_free(ci);
return NULL;
}
return ci;
}
#endif