-1

Got "Invalid JWT Signature". It's seems like only signing is wrong. But I just don't know what to do. Please help!

Curl works 100% Base64 seems like works fine. Header and Claim decodes is OK.

It has some info here (REST) if it will help.

bool RSASign( RSA* rsa, const unsigned char* Msg, size_t MsgLen, unsigned char** EncMsg, size_t* MsgLenEnc) 
{
  EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create();
  EVP_PKEY* priKey  = EVP_PKEY_new();
  EVP_PKEY_assign_RSA(priKey, rsa);
  if (EVP_DigestSignInit(m_RSASignCtx,NULL, EVP_sha256(), NULL,priKey)<=0) { std::cout << "BAD! " << std::endl; return false; }
  if (EVP_DigestSignUpdate(m_RSASignCtx, Msg, MsgLen) <= 0) { std::cout << "BAD! " << std::endl; return false; }
  if (EVP_DigestSignFinal(m_RSASignCtx, NULL, MsgLenEnc) <=0) { std::cout << "BAD! " << std::endl; return false; }
  *EncMsg = (unsigned char*)malloc(*MsgLenEnc);
  if (EVP_DigestSignFinal(m_RSASignCtx, *EncMsg, MsgLenEnc) <= 0) { std::cout << "BAD! " << std::endl; return false; }

  EVP_MD_CTX_free(m_RSASignCtx);
  return true;
}

    // JWT HEADER
    std::string jwtHeader = base64_encode( "{\"alg\":\"RS256\",\"typ\":\"JWT\"}" );

    // JWT CLAIM
    std::string jwtClaim = base64_encode( ... );

    // JWT SIGNATURE
    std::string JWS  = jwtHeader + "." + jwtClaim;
    const char* privateKey = "-----BEGIN PRIVATE KEY-----\n...";

    // HASH        
        SHA256_CTX sha_ctx = { 0 };
        unsigned char digest[SHA256_DIGEST_LENGTH];
        SHA256_Init(&sha_ctx);
        SHA256_Update(&sha_ctx, JWS.c_str(), JWS.size());
        SHA256_Final(digest, &sha_ctx);

    // SIGN
    FILE *file = nullptr;
    file = fopen("file.key", "r");
    if (!file) { std::cout << "BAD FILE" << std::endl; }
    RSA* rsa = PEM_read_RSAPrivateKey(file, NULL, NULL, NULL);
    fclose(file);

    if(!rsa)
       std::cout << "BAD KEY" << std::endl;

    // there is no luck with or without sha256 before signing 
    if (RSASign( rsa, (const unsigned char*) JWS.c_str(), JWS.length(), &sig, (size_t*)&slen))
            std::cout << "SIGNED size: " << slen << std::endl;

        // TOKEN REQUEST
        std::string sign = base64_encode(sig, slen);
        std::string requestStr = "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=" + JWS + "." + sign;

        headers = curl_slist_append(headers, "Content-Type: application/x-www-form-urlencoded");
        curl_easy_setopt(curl_handle, CURLOPT_URL, "https://oauth2.googleapis.com/token");
        curl_easy_setopt( ... CURLOPT_POST, CURLOPT_HTTPHEADER, CURLOPT_POSTFIELDS ... );

UPDATE:

Ok. Now I know that I need SHA256withRSA. But problem is still there.

pr0f1t
  • 9
  • 3
  • Does this answer your question? [SHA256withRSA what does it do and in what order?](https://stackoverflow.com/questions/21018355/sha256withrsa-what-does-it-do-and-in-what-order) – Botje Apr 14 '20 at 08:21
  • Openssl is a pain... it fails here: EVP_PKEY_get_raw_private_key() Google doesn't helps much.. – pr0f1t Apr 14 '20 at 10:17
  • I don't know, should I be worried about sha256 paddins here or not. Seems like this function should take care about it. – pr0f1t Apr 14 '20 at 10:28
  • The bit you are missing is PKCS#1 padding. [The documentation](https://www.openssl.org/docs/manmaster/man3/EVP_PKEY_CTX_set_rsa_padding.html) seems to suggest you need to call `EVP_PKEY_CTX_set_signature_md` on *ppCtx as well. – Botje Apr 14 '20 at 11:29

3 Answers3

1

Thank You Botje! Thank You very much for trying to help me. But I have no time anymore for learning openssl. So I found a great one: https://github.com/Thalhammer/jwt-cpp from here jwt.io

It doesn't support the 'scope' field for the jwt-claim, so I just added a method into a class into a header: 'set_scope(...);'.

Ten, 10... Nooo, You just can't feel my pain beacuse of openssl_from_devs_with_crooked_hands_with_no_human_docs. 10 strings of a code and it's done!

It has simple sources, so You can find out how to work with ssl from there. If You want to use OpenSSL lib and don't know how it works - think twice, may be it's the worst choice of Your life. Good luck!

pr0f1t
  • 9
  • 3
1

Just for future people looking this up and struggle how to compute a RS256 (SHA256WithRSA)-Hash in OpenSSL, since I spent about 5 hours researching the error. The code from Botje is quite close, but is missing the Finalize method (and the destructors).

#include <OpenSSL/obj_mac.h>
#include <OpenSSL/evp.h>
#include <OpenSSL/pem.h>
#include <OpenSSL/bio.h> 

bool SHA256WithRSA(const std::string RSAKey, const std::string Data)
{
    struct bio_st* pem_BIO = BIO_new(BIO_s_mem());
    if (BIO_write(pem_BIO, RSAKey.data(), RSAKey.size()) != RSAKey.size())
    {
        BIO_free_all(pem_BIO);
        return false;
    }
    struct evp_pkey_st* signing_key = PEM_read_bio_PrivateKey(pem_BIO, nullptr, nullptr, nullptr);
    if(signing_key == nullptr)
    {
        BIO_free_all(pem_BIO);
        EVP_PKEY_free(signing_key);
        return false;
    }
    
    std::string ResultBuffer(EVP_PKEY_size(signing_key), '\0');
    unsigned int len = 0;
    struct evp_md_ctx_st * MD_ctx = EVP_MD_CTX_new();
    if (!EVP_SignInit(MD_ctx, EVP_get_digestbyname(SN_sha256WithRSAEncryption)) ||
        !EVP_SignUpdate(MD_ctx, Data.data(), Data.size()) ||
        EVP_SignFinal(MD_ctx, (unsigned char*)ResultBuffer.data(), &len, signing_key) == 0)
    {
        BIO_free_all(pem_BIO);
        EVP_PKEY_free(signing_key);
        EVP_MD_CTX_free(MD_ctx);
        return false;
    }
    ResultBuffer.resize(len);
    
    /* Do something with the ResultBuffer */
    
    BIO_free_all(pem_BIO);
    EVP_PKEY_free(signing_key);
    EVP_MD_CTX_free(MD_ctx);
    return true;
}
Sepel
  • 11
  • 2
0

No need to mark this answer as accepted, yours is by far the easier solution. I'm posting this for future readers to find.

For what it's worth, I was able to reproduce the raw hash in the RS256 test case with the following:

void die() {
    for (int err = ERR_get_error(); err != 0; err = ERR_get_error()) {
        fprintf(stderr, "%s\n", ERR_error_string(err, nullptr));
    }
    exit(1);
}

int main() {
    /* md is a SHA-256 digest in this example. */
    BIO *pem_BIO = BIO_new_mem_buf(RSA_pem, sizeof(RSA_pem));
    EVP_PKEY *signing_key = PEM_read_bio_PrivateKey(pem_BIO, nullptr, nullptr, nullptr);

    const unsigned char header_payload[] = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9";

    EVP_MD_CTX * MD_ctx = EVP_MD_CTX_new();
    bool success = true;
    success = EVP_DigestSignInit(MD_ctx, nullptr, EVP_get_digestbyname(SN_sha256WithRSAEncryption), nullptr, signing_key);
    if (!success) die();

    unsigned char sigbuf[512];
    size_t siglen = sizeof(sigbuf);
    success = EVP_DigestSign(MD_ctx, sigbuf, &siglen, header_payload, sizeof(header_payload)-1);
    if (!success) die();
    fwrite(sigbuf, 1, siglen, stdout);
}

The end result still has to be base64-encoded (with the url-safe alphabet), though.

Botje
  • 26,269
  • 3
  • 31
  • 41