Search code examples
c++opensslecdsa

OpenSSL3.0 - ECDSA Signature verification


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;
    }

Solution

  • 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.