-1

I Am currently writing a C++ program with OpenSSL3.0 which takes a string of data, EDCSA signature, and checks the validity with EC public Key stored in a .pem file.

Currently I have tried to use EVP_DigestVerifyFinal() but can't quite figure out how I can, with the new API, import my EC key to perform the verificaton, without the use of deprecated functions.

I Have tried the OSSL_DECODER_from_data, by giving it the public key as const unsigned char* but had no luck on making it quite work.

Here is my attempt at the the verification

 bool SignatureEcDSA::testVerif(const unsigned char* sig, const char* msg)
    {
        std::string key_ = "-----BEGIN PUBLIC "
                           "KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEy687PNFBHUW3KIYrgrdGtCY5bdDGvnbMj1v/"
                           "APR71dBv0mD3UXNULjAKSWVc4ahfIddpfX/i2N9ppMxVljk8BA==\n-----END PUBLIC KEY-----";

        EVP_PKEY*   key   = nullptr;
        EVP_MD_CTX* mdctx = EVP_MD_CTX_new();

        //make key get from key_
        BIO* keybio = BIO_new_mem_buf(key_.c_str(), -1);
        if (keybio == nullptr) {

            return false;
        }
        key = PEM_read_bio_PUBKEY(keybio, &key, nullptr, nullptr);

        if (1 != EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, key))
            return false;

        if (1 != EVP_DigestVerifyUpdate(mdctx, msg, strlen(msg))) {
            return false;
        }

        if (1 == EVP_DigestVerifyFinal(mdctx, sig, strlen((const char*) sig))) {
            return true;
        }
    }

And here is my attempt at making the OSSL_Decoder_from_data which comes from https://www.openssl.org/docs/man3.0/man3/OSSL_DECODER_from_bio.html

const char* key_ = "-----BEGIN PUBLIC "
                           "KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEy687PNFBHUW3KIYrgrdGtCY5bdDGvnbMj1v/"
                           "APR71dBv0mD3UXNULjAKSWVc4ahfIddpfX/i2N9ppMxVljk8BA==\n-----END PUBLIC KEY-----";

        OSSL_DECODER_CTX* dctx;
        EVP_PKEY*         pkey      = NULL;
        const char*       format    = "PEM"; /* NULL for any format */
        const char*       structure = NULL;  /* any structure */
        const char*       keytype   = "EC";  /* NULL for any key */
        auto*             data      = (const unsigned char*) key_;
        size_t            datalen   = sizeof(key_);

        dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, format, structure, keytype,
                                             OSSL_KEYMGMT_SELECT_KEYPAIR | OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL);
        if (dctx == NULL) {
            std::cout << "No decoder context" << std::endl;
        }
        if (OSSL_DECODER_from_data(dctx, &data, &datalen)) {
            std::cout << "OSSL_DECODER_from_data success" << std::endl;
        }
        else {
            std::cout << "OSSL_DECODER_from_data failed" << std::endl; //fails here
        }
        OSSL_DECODER_CTX_free(dctx);

It still uses a string as the initial EC key here, but I would like to open it from a file. (solved by BIO_new_file()!)

If anyone has a hint on how i could make this work it would be greatly appreciated !

Thanks

EDIT:

I have tried the solution proposed here prior to my tests, but it uses PEM_read_bio_EC_PUBKEY deprecated function : ECDSA signature verification: Go vs OpenSSL

From https://www.openssl.org/docs/man3.0/man3/PEM_read_bio_EC_PUBKEY.html : Applications should use OSSL_ENCODER_to_bio() and OSSL_DECODER_from_bio(), but didn't manage to make it work either.

EDIT 2:

Added the verification along with firstly corrected code, still fails at the same place. OSSL_DECODER_from_data returns 0.

bool SignatureEcDSA::testVerif(const unsigned char* sig, const char* msg)
    {

        const char* key_ = "-----BEGIN PUBLIC "
                           "KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEy687PNFBHUW3KIYrgrdGtCY5bdDGvnbMj1v/"
                           "APR71dBv0mD3UXNULjAKSWVc4ahfIddpfX/i2N9ppMxVljk8BA==\n-----END PUBLIC KEY-----";

        OSSL_DECODER_CTX*    dctx;
        EVP_PKEY*            pkey      = NULL;
        const char*          format    = "PEM"; // NULL for any format
        const char*          structure = NULL;  // any structure
        const char*          keytype   = "EC";  // NULL for any key
        const unsigned char* data      = (const unsigned char*) key_;
        size_t               datalen   = strlen(key_); // Use strlen instead of sizeof

        dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, format, structure, keytype,
                                             OSSL_KEYMGMT_SELECT_KEYPAIR | OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL);
        if (dctx == NULL) {
            std::cout << "No decoder context" << std::endl;
        }
        if (OSSL_DECODER_from_data(dctx, &data, &datalen)) {
            std::cout << "OSSL_DECODER_from_data success" << std::endl;

            EVP_MD_CTX* mdctx = EVP_MD_CTX_new();

            if (1 != EVP_DigestVerifyInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey))
                return false;

            if (1 != EVP_DigestVerifyUpdate(mdctx, msg, strlen(msg))) {
                return false;
            }

            if (1 == EVP_DigestVerifyFinal(mdctx, sig, strlen((const char*) sig))) {
                return true;
            }
        }
        else {
            std::cout << "OSSL_DECODER_from_data failed" << std::endl; //fails here
        }
        return false;
    }
NSmn
  • 1
  • 2
  • "BIO" is an IO abstraction used by OpenSSL. You want `BIO_new_file` to have it open a file for you. – Botje Aug 24 '23 at 09:28
  • The problem with your second snippet is that `sizeof(key_)` is the size of your pointer, not the stuff it points at. Either declare `key` as a `const char key_[] = "..."` or use `strlen(key_)` for `datalen` – Botje Aug 24 '23 at 09:30
  • What do you mean by "fails at the same place"? Does `ERR_get_error` return 0 or an actual error code? – Botje Aug 24 '23 at 10:03
  • OSSL_DECODER_from_data returns 0, which according to the doc : https://www.openssl.org/docs/man3.1/man3/OSSL_DECODER_from_data.html OSSL_DECODER_from_bio(), OSSL_DECODER_from_data() and OSSL_DECODER_from_fp() return 1 on success, or 0 on failure. – NSmn Aug 24 '23 at 10:14
  • The BIO_new_file works well thanks ! updated OSSL_DECODER_from_data to work with the value read, but problem persists. – NSmn Aug 24 '23 at 11:04
  • Again, check with `ERR_get_error` if there are any pending errors after you call `OSSL_DECODER_from_data` – Botje Aug 24 '23 at 12:07

2 Answers2

0

It seems like you're on the right track with the OSSL_DECODER_from_data approach to avoid deprecated functions. However, there are a couple of issues in your code that might be causing the problem.

Key Length Issue: In your OSSL_DECODER_from_data attempt, you are using the sizeof(key_) to determine the length of the key data. This will give you the size of the pointer, not the actual length of the data. You should use strlen(key_) instead.

Memory Ownership and Use After Free: When using OSSL_DECODER_from_data, the decoder takes ownership of the input data, so you shouldn't modify or free it after passing it to the decoder. However, in your code, you are trying to free the decoder context before using it, which can lead to undefined behavior.

Here's a corrected version of your code:

const char* key_ = "-----BEGIN PUBLIC "
               "KEY----- 
\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEy687PNFBHUW3KIYrgrdGtCY5bdDGvnbMj1v/"
               "APR71dBv0mD3UXNULjAKSWVc4ahfIddpfX/i2N9ppMxVljk8BA==\n----- 
END PUBLIC KEY-----";

OSSL_DECODER_CTX* dctx;
EVP_PKEY* pkey = NULL;
const char* format = "PEM";   // NULL for any format
const char* structure = NULL; // any structure
const char* keytype = "EC";    // NULL for any key
const unsigned char* data = (const unsigned char*)key_;
size_t datalen = strlen(key_); // Use strlen instead of sizeof

dctx = OSSL_DECODER_CTX_new_for_pkey(&pkey, format, structure, keytype,
                                 OSSL_KEYMGMT_SELECT_KEYPAIR | 
OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, NULL, NULL);
if (dctx == NULL) {
std::cout << "No decoder context" << std::endl;
}
if (OSSL_DECODER_from_data(dctx, &data, &datalen)) {
std::cout << "OSSL_DECODER_from_data success" << std::endl;
// You can use 'pkey' for verification here
} else {
std::cout << "OSSL_DECODER_from_data failed" << std::endl;
}

// Don't free dctx here since it holds the ownership of 'data' OSSL_DECODER_CTX_free(dctx);

Please make sure to adjust the rest of your verification code accordingly, using the pkey obtained from the OSSL_DECODER_from_data call.

  • Thank you for the help! I have updated my code with yours, and added the verification with the pkey, but it still fails at the same place. I have put the full snippet in the edit, I will try the solution proposed by @Botje BIO_new_file and let you updated on it ! – NSmn Aug 24 '23 at 09:49
  • Problem persists with added BIO_new_file to import the EC Key – NSmn Aug 24 '23 at 11:04
  • Is this ChatGPT? – user16217248 Aug 28 '23 at 04:50
0

The problem was in your selection parameter. Passing either 0 or OSSL_KEYMGMT_SELECT_PUBLIC_KEY allows the call of OSSL_DECODER_from_data (or OSSL_DECODER_from_bio) to pass.

Botje
  • 26,269
  • 3
  • 31
  • 41