TL;DR: With OpenSSL 3.x API, what is the recommended and safe way to read in an 
EC private key from raw format into an EVP_PKEY object ready to be used? What 
is the easiest way to convert an RSA public key from raw modulus and exponent 
components to proper DER encoded SubjectPublicKeyInfo data?

Hi openssl-users mailing list.

We are having some troubles converting some code from OpenSSL 1.x to OpenSSL 
3.x APIs, to get rid of deprecation warnings, and hope someone may be able to 
give us some hints in the right direction.

One thing we want to do is to convert an EC private key from raw format into a 
EVP_PKEY. Today we do as below (error checking, freeing and secure memory 
context things removed for brevity, private key is in "privkey" and curve in 
"nid"):

BIGNUM *privkey_bn = BN_bin2bn(privkey, privkey_len, NULL);
EC_KEY *eckey = EC_KEY_new_by_curve_name(nid);
const EC_GROUP *group = EC_KEY_get0_group(eckey);
EC_POINT *pubkey_point = EC_POINT_new(group);
EC_POINT_mul(group, pubkey_point, privkey_bn, NULL, NULL, NULL);
EC_KEY_set_private_key(eckey, privkey_bn);
EC_KEY_set_public_key(eckey, pubkey_point);
EVP_PKEY *pkey = EVP_PKEY_new();
EVP_PKEY_assign_EC_KEY(pkey, eckey);

Basically we chained a lot of operations because we could not find any single 
function that did it for us. Some of these operations are now deprecated, such 
as the EC_KEY ones. We tried experimenting with the OSSL fromdata() function 
instead (omitted the mapping from "nid" to "sn" for brevity):

BIGNUM *privkey_bn = BN_bin2bn(privkey, privkey_len, NULL);
EC_GROUP *group = EC_GROUP_new_by_curve_name(nid);
EC_POINT *pubkey_point = EC_POINT_new(group);
EC_POINT_mul(group, pubkey_point, privkey_bn, NULL, NULL, NULL);
unsigned char pubkey_buf[65]; // size just an example
EC_POINT_point2oct(grp, pubkey_point, POINT_CONVERSION_UNCOMPRESSED, 
pubkey_buf, sizeof(pubkey_buf), NULL);
OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new();
OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, sn, 0);
OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, privkey_bn);
OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, 
pubkey_buf, sizeof(pubkey_buf));
OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(param_bld);
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
EVP_PKEY_fromdata_init(ctx);
EVP_PKEY *pkey = NULL;
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params);
EVP_PKEY_CTX_free(ctx);
ctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_PKEY_check(ctx);

Although it works, it does not feel right. We ended up chaining many more 
operations than before. Our understanding was that the new OpenSSL 3.x API was 
redesigned partially to remove low-level manipulations like these. We have 
looked though both the migration document and the reference API without finding 
anything that does our job better. OSSL_DECODERs as frequently suggested in the 
migration documentation do not seem to support raw EC key formats at all. The 
EVP_PKEY_new_raw_private_key() functions mentioned in the reference API does 
not appear to support NIST P curves, according to the documentation. The OSSL 
fromdata() way above does not calculate the public key from the private one 
itself, nor does it verify that the points are on the curve, and we are 
uncertain if there are anything else it does not do that we need to do to not 
compromise security. We could use d2i_PrivateKey() or d2i_AutoPrivateKey(), 
which both seem to read in the key data in a secure way and derive the public 
part automatically. But that way would require us to implement custom logic in 
our code to manually put together DER data from the raw key data, for multiple 
curve types.

What is the recommended and safe way to read in an EC private key from raw 
format into an EVP_PKEY object ready to be used?

Another thing we want to do is to convert an RSA public key from raw modulus 
and exponent components into proper DER encoded SubjectPublicKeyInfo data. 
Today we piggyback on OpenSSL to accomplish this like this:

BIGNUM *n = BN_bin2bn(modulus, (int)modulus_len, NULL);
BIGNUM *e = BN_bin2bn(exponent, (int)exponent_len, NULL);
RSA *rsa = RSA_new();
RSA_set0_key(rsa, n, e, NULL);
int data_len = i2d_RSA_PUBKEY(rsa, NULL);
uint8_t *data_buf = malloc((size_t)data_len);
uint8_t *pdata = data_buf;
data_len = i2d_RSA_PUBKEY(rsa, &pdata);

However, some of those functions are now deprecated. Unfortunately our best 
attempt with OpenSSL 3.x compatible APIs ended up being this comparably long 
sequence of operations:

BIGNUM *n = BN_bin2bn(modulus, (int)modulus_len, NULL);
BIGNUM *e = BN_bin2bn(exponent, (int)exponent_len, NULL);
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);
OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new();
OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, n);
OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, e);
OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(param_bld);
EVP_PKEY_fromdata_init(ctx);
EVP_PKEY *pkey = NULL;
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params);
EVP_PKEY_CTX_free(ctx);
ctx = EVP_PKEY_CTX_new(pkey, NULL);
EVP_PKEY_public_check(ctx);
int data_len = i2d_PUBKEY(pkey, NULL);
uint8_t *data_buf = malloc((size_t)data_len);
uint8_t *pdata = data_buf;
data_len = i2d_PUBKEY(pkey, &pdata);

This also does not feel quite right. Especially the conversion from raw modulus 
and exponent ended up being much longer, and we failed to find an easier way to 
do it.

What is the easiest or most recommended way to convert an RSA public key from 
raw modulus and exponent components to proper DER encoded SubjectPublicKeyInfo 
data using non-deprecated OpenSSL 3.x APIs?

Reply via email to