Loading raw EC and RSA keys with OpenSSL 3
Jonathan Wernberg
Jonathan.Wernberg at axis.com
Wed Aug 24 14:14:09 UTC 2022
Tomas Mraz wrote:
> Jonathan Wernberg wrote:
>> 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.
Ok, thank you. Good to hear we were on the right track and did not miss anything. We will watch the ticket, as a solution here would likely simplify our code a bit.
More information about the openssl-users
mailing list