Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extracting an EC Key out of X and Y with OpenSSL 3.0 #19219

Closed
HagaiVinik opened this issue Sep 15, 2022 · 3 comments
Closed

Extracting an EC Key out of X and Y with OpenSSL 3.0 #19219

HagaiVinik opened this issue Sep 15, 2022 · 3 comments
Labels
resolved: answered The issue contained a question which has been answered triaged: question The issue contains a question

Comments

@HagaiVinik
Copy link

HagaiVinik commented Sep 15, 2022

Hello Everyone,
I would really appreciate your help with this:
I have an X and Y input and what I would like to do is just convert them into a public EC key in PEM format.
I'll attach my code here bellow:

// OpenSSL3.0 extracting EC key - doesn't work
std::string extractEcKey(const std::string& x, const std::string& y)
{
    auto xBin = cppcodec::base64_url_unpadded::decode(x);
    auto yBin = cppcodec::base64_url_unpadded::decode(y);

    BIGNUM* xBig = BN_bin2bn(xBin.data(),xBin.size(),NULL);
    BIGNUM* yBig = BN_bin2bn(yBin.data(),yBin.size(),NULL);

    EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);

    OSSL_PARAM_BLD *params_build = OSSL_PARAM_BLD_new();
    if ( params_build == NULL )
    {
        throw ;
    }
    if ( !OSSL_PARAM_BLD_push_BN(params_build, "x", xBig) )
    {
        throw std::logic_error("Error: failed to push modulus into param build.");
    }
    if ( !OSSL_PARAM_BLD_push_BN(params_build, "y", yBig) )
    {
        throw std::logic_error("Error: failed to push exponent into param build.");
    }

    OSSL_PARAM_BLD_push_utf8_string(params_build, OSSL_PKEY_PARAM_GROUP_NAME, SN_X9_62_prime256v1, 0);
    OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(params_build);
    
    if ( params == NULL )
    {
        throw std::logic_error("Error: failed to construct params from build.");
    }

    int status = EVP_PKEY_fromdata_init(ctx);
    if ( status <= 0 )
    {
        throw std::logic_error("Error: failed to initialize key creation.");
    }

    EVP_PKEY* evpPkey = EVP_PKEY_new();
    status = EVP_PKEY_fromdata(ctx, &evpPkey, EVP_PKEY_PUBLIC_KEY, params);
    if ( status <= 0 || evpPkey == NULL )
    {
        throw std::runtime_error("Error: failed to create key.\n");
    }

    std::vector<unsigned char> publicKeyBuff(33);

    BIO* bio = BIO_new(BIO_s_mem());
    PEM_write_bio_PUBKEY(bio, evpPkey);
    BIO_read(bio, publicKeyBuff.data(), 33);

    // Free allocated memory:
    BIO_free(bio);

    std::string publicKey(publicKeyBuff.begin(), publicKeyBuff.end());
    return publicKey;
}

after PEM_write_bio_PUBKEY(bio, evpPkey) and BIO_read(bio, publicKeyBuff.data(), 33);
I'm getting an empty buffer/string. :(
This code, was meant to replace the code that was working for me on version 1.1 (!)
Which was:

// OpenSSL1.1   extracting EC key - works.
std::string ExtractEcKey(const std::string& x, const std::string& y, int crv)
{
    auto xBin = cppcodec::base64_url_unpadded::decode(x);
    auto yBin = cppcodec::base64_url_unpadded::decode(y);

    BIGNUM* xBigNum = BN_bin2bn(xBin.data(),xBin.size(),NULL);
    BIGNUM* yBigNum = BN_bin2bn(yBin.data(),yBin.size(),NULL);

    EC_KEY* ecKey = EC_KEY_new_by_curve_name(crv);
    EC_KEY_set_public_key_affine_coordinates(ecKey, xBigNum, yBigNum);

    EVP_PKEY* pKey = EVP_PKEY_new();
    EVP_PKEY_assign_EC_KEY(pKey, ecKey);

    std::vector<unsigned char> publicKeyBuff(KEY_SIZE);

    BIO* bio = BIO_new(BIO_s_mem());
    PEM_write_bio_EC_PUBKEY(bio, ecKey);
    BIO_read(bio, publicKeyBuff.data(), KEY_SIZE);

    //Free allocated memory:
    BIO_free(bio);
    EC_KEY_free(ecKey);
    BN_free(xBigNum);
    BN_free(yBigNum);

    std::string publicKey(publicKeyBuff.begin(), publicKeyBuff.end());

    return publicKey;
}

If helps, RSA was working for me well on OpenSSL3.0:

// OpenSSL3.0  extracting RSA key - works.
std::string ExtractRsaKey(const std::string& n, const std::string& e)
{
    auto nBin = cppcodec::base64_url_unpadded::decode(n);
    auto eBin = cppcodec::base64_url_unpadded::decode(e);

    BIGNUM* modul = BN_bin2bn(nBin.data(),nBin.size(),NULL);
    BIGNUM* expon = BN_bin2bn(eBin.data(),eBin.size(),NULL);

    EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL);

    OSSL_PARAM_BLD *params_build = OSSL_PARAM_BLD_new();
    if ( params_build == NULL )
    {
        throw std::runtime_error("failed creating param object");
    }
    if ( !OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_N, modul) )
    {
        throw std::logic_error("Error: failed to push modulus into param build.");
    }
    if ( !OSSL_PARAM_BLD_push_BN(params_build, OSSL_PKEY_PARAM_RSA_E, expon) )
    {
        throw std::logic_error("Error: failed to push exponent into param build.");
    }

    OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(params_build);
    if ( params == NULL )
    {
        throw std::logic_error("Error: failed to construct params from build.");
    }

    int status = EVP_PKEY_fromdata_init(ctx);
    if ( status <= 0 )
    {
        throw std::logic_error("Error: failed to initialize key creation.");
    }

    EVP_PKEY* evpPkey = EVP_PKEY_new();
    status = EVP_PKEY_fromdata(ctx, &evpPkey, EVP_PKEY_PUBLIC_KEY, params);
    if ( status <= 0 || evpPkey == NULL )
    {
        throw std::runtime_error("Error: failed to create key.\n");
    }

    std::vector<unsigned char> publicKeyBuff(KEY_SIZE);

    BIO* bio = BIO_new(BIO_s_mem());
    PEM_write_bio_PUBKEY(bio, evpPkey);
    BIO_read(bio, publicKeyBuff.data(), KEY_SIZE);

    // Free allocated memory:
    BIO_free(bio);

    std::string publicKey(publicKeyBuff.begin(), publicKeyBuff.end());

    return publicKey;
}

I would appreciate any help with this, thanks in advance!
Hagai.

@HagaiVinik HagaiVinik added the issue: question The issue was opened to ask a question label Sep 15, 2022
@t8m t8m added triaged: question The issue contains a question and removed issue: question The issue was opened to ask a question labels Sep 15, 2022
@mattcaswell
Copy link
Member

You are attempting to create an EC public key using the "x" and "y" parameters - but no such parameters exist. The list of available EC parameters is on this page:

https://www.openssl.org/docs/man3.0/man7/EVP_PKEY-EC.html

For your purposes you need to use the OSSL_PKEY_PARAM_PUB_KEY parameter ("pub") to supply the public key. It needs to be an octet string with the value POINT_CONVERSION_UNCOMPRESSED at the start followed by the x and y co-ords concatenated together. For that curve, x and y need to be zero padded to be 32 bytes long each. There is an example of doing this on the EVP_PKEY_fromdata man page. Actually the example is is for EVP_PKEY_KEYPAIR rather than EVP_PKEY_PUBLIC_KEY, but the principle is exactly the same:

https://www.openssl.org/docs/man3.0/man3/EVP_PKEY_fromdata.html

We should add a feature to make it possible to supply the x and y co-ords separately. But that is not currently possible.

@HagaiVinik
Copy link
Author

@mattcaswell Thank you very much!

If anyone's interested, here is the solution:

std::string ExtractEcKey(const std::string& x, const std::string& y, const std::string& crv)
{
    auto xBin = cppcodec::base64_url_unpadded::decode(x);
    auto yBin = cppcodec::base64_url_unpadded::decode(y);

    // Build uncompressed raw public key.
    std::vector<unsigned char> publicKeyXY;
    publicKeyXY.reserve(1 + xBin.size() + yBin.size());
    publicKeyXY.push_back(POINT_CONVERSION_UNCOMPRESSED);
    publicKeyXY.insert( publicKeyXY.end(), xBin.begin(), xBin.end() );
    publicKeyXY.insert( publicKeyXY.end(), yBin.begin(), yBin.end() );

    EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
    EVP_PKEY_fromdata_init(ctx);

    EVP_PKEY* evpPkey = EVP_PKEY_new();

    //This section builds the param block for creating an EVP key for writing a PEM using BIO:
    OSSL_PARAM_BLD *params_build = OSSL_PARAM_BLD_new();
    OSSL_PARAM_BLD_push_utf8_string(params_build, OSSL_PKEY_PARAM_GROUP_NAME, crv.c_str(), 0);
    OSSL_PARAM_BLD_push_octet_string(params_build, OSSL_PKEY_PARAM_PUB_KEY,publicKeyXY.data(), publicKeyXY.size());
    OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(params_build);

    EVP_PKEY_fromdata_init(ctx);
    int status = EVP_PKEY_fromdata(ctx, &evpPkey, EVP_PKEY_KEYPAIR, params);
    if ( status <= 0 || evpPkey == NULL )
    {
        throw std::logic_error("Error: failed to create EC key.");
    }


    std::vector<unsigned char> publicKeyBuff(KEY_SIZE);

    BIO* bio = BIO_new(BIO_s_mem());
    PEM_write_bio_PUBKEY(bio, evpPkey);
    BIO_read(bio, publicKeyBuff.data(), KEY_SIZE);

    // Free allocated memory:
    BIO_free(bio);
    EVP_PKEY_free(evpPkey);
    OSSL_PARAM_free(params);
    OSSL_PARAM_BLD_free(params_build);
    EVP_PKEY_CTX_free(ctx);

    std::string publicKey(publicKeyBuff.begin(), publicKeyBuff.end());
    spdlog::debug("EC PUBLIC KEY: {}", publicKey.data());

    return publicKey;
}

@davidben
Copy link
Contributor

Note that code will misinterpret inputs if xBin and yBin are not the same length. If you're expecting both inputs to be zero-padded already, you should check xBin.size() == yBin.size() to correctly reject those cases.

@t8m t8m added the resolved: answered The issue contained a question which has been answered label Sep 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
resolved: answered The issue contained a question which has been answered triaged: question The issue contains a question
Projects
None yet
Development

No branches or pull requests

4 participants