Loading raw EC and RSA keys with OpenSSL 3
Tomas Mraz
tomas at openssl.org
Tue Aug 23 12:46:18 UTC 2022
On Tue, 2022-08-23 at 12:09 +0000, Jonathan Wernberg wrote:
> 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?
Basically what you have done is right. With the RSA public keys there
is not much that could be improved even on the OpenSSL side apart from
providing some wrapper functions that would do basically the same thing
internally.
The deficiency of the API that is really missing is a high level API
way to generate the public key from the private key if the public key
is missing for the EC algorithm. You did it the only way that avoids
using deprecated API calls however it should really be done inside a
provider. I've opened https://github.com/openssl/openssl/issues/19046
for this RFE.
--
Tomáš Mráz, OpenSSL
More information about the openssl-users
mailing list