简体   繁体   English

分段在C ++中通过openSSL使用公钥验证JWT令牌时出错

[英]Segmentation Fault while verifying JWT token using a public key through openSSL in C++

I am very new to C++ and OpenSSL. 我是C ++和OpenSSL的新手。 I have to verify a given JWT token (algorithm RS256) using a public key through OpenSSL in C++. 我必须通过C ++中的OpenSSL使用公钥来验证给定的JWT令牌(算法RS256)。 I am using following algorithm to verify the JWT token. 我使用以下算法来验证JWT令牌。

// signature algorithm data = base64urlEncode( header ) + “.” + base64urlEncode( payload ) hashedData = hash( data, secret ) signature = base64urlEncode( hashedData )

I am on a Mac system and using g++ to compile my code. 我在Mac系统上并使用g ++编译我的代码。 openssl version on terminal shows LibreSSL 2.6.5 . 终端上的openssl version显示了LibreSSL 2.6.5

// Assume that base64 encode and decode functions are available
bool RSAVerifySignature( RSA* rsa, std::string token, std::string pub_key) {

  std::vector<std::string> tokenParts;
  split(token, tokenParts, '.');

  std::string decoded_header = tokenParts[0];
  std::string header = base64_encode(reinterpret_cast<const unsigned char*>(decoded_header.c_str()),
    decoded_header.length());

  std::string decoded_body = tokenParts[1];
  std::string body = base64_encode(reinterpret_cast<const unsigned char*>(decoded_body.c_str()),
    decoded_body.length());


  std::string sig = tokenParts[2];

  EVP_PKEY* pubKey  = EVP_PKEY_new();
  EVP_PKEY_assign_RSA(pubKey, rsa);
  EVP_MD_CTX* m_RSAVerifyCtx = EVP_MD_CTX_create();

  if (1 != EVP_DigestVerifyInit(m_RSAVerifyCtx, NULL, EVP_sha256(), NULL, pubKey)) {
      printf("verify init failed....\n");
  } else {
      printf("verify init passed....\n");
  }
  if (1 != EVP_DigestVerifyUpdate(m_RSAVerifyCtx, (unsigned char *)header.data(), header.length())) {
      printf("DigestVerifyUpdate for header failed....\n");
  } else {
          printf("DigestVerifyUpdate for header passed....\n");
  }
  if (1 != EVP_DigestVerifyUpdate(m_RSAVerifyCtx, ".", 1)) {
      printf("DigestVerifyUpdate for dot failed\n");
  } else {
      printf("DigestVerifyUpdate for dot passed\n");
  }
  if (1 != EVP_DigestVerifyUpdate(m_RSAVerifyCtx, (unsigned char *)body.data(), body.length())) {
      printf("DigestVerifyUpdate for body failed\n");
  } else {
      printf("DigestVerifyUpdate for body passed\n");
  }

  int result = EVP_DigestVerifyFinal(m_RSAVerifyCtx, (unsigned char *)sig.data(), sig.length());
  return result;
}

RSA* createPublicRSA(std::string key) {
  RSA *rsa = NULL;
  BIO *keybio;
  const char* c_string = key.c_str();
  keybio = BIO_new_mem_buf((void*)c_string, -1);
  if (keybio==NULL) {
      return 0;
  }
  rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa,NULL, NULL);
  return rsa;
}

int main()
{
    std::string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.POstGetfAytaZS82wHcjoTyoqhMyxXiWdR7Nn7A29DNSl0EiXLdwJ6xC6AfgZWF1bOsS_TuYI3OG85AmiExREkrS6tDfTQ2B3WXlrr-wp5AokiRbz3_oB4OxG-W9KcEEbDRcZc0nH3L7LzYptiy1PtAylQGxHTWZXtGz4ht0bAecBgmpdgXMguEIcoqPJ1n3pIWk_dUZegpqx0Lka21H6XxUTxiy8OcaarA8zdnPUnV6AmNP3ecFawIFYdvJB_cm-GvpCSbr8G8y_Mllj8f4x9nBH8pQux89_6gUY618iYv7tuPWBFfEbLxtF2pZS6YC1aSfLQxeNe8djT9YjpvRZA";

    std::string publicKey = "-----BEGIN PUBLIC KEY-----"\
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv"\
"vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc"\
"aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy"\
"tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0"\
"e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb"\
"V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9"\
"MwIDAQAB"\
"-----END PUBLIC KEY-----";

    RSA* publicRSA = createPublicRSA(publicKey);
    bool result = RSAVerifySignature(publicRSA, token, publicKey);
    return 0;
}

I am getting Segmentation fault: 11 at EVP_DigestVerifyFinal call. 我在EVP_DigestVerifyFinal调用时遇到Segmentation fault: 11 I have no idea where am I wrong. 我不知道我哪里错了。 Please help. 请帮忙。

If you had done some basic error checking you will see that your createPublicRSA function returns a nullptr. 如果您已完成一些基本错误检查,您将看到createPublicRSA函数返回nullptr。 This is because PEM_read_bio_RSA_PUBKEY is expecting to see newlines and your publicKey string doesn't have any. 这是因为PEM_read_bio_RSA_PUBKEY期望看到换行符,而您的publicKey字符串没有。

If you change it to have newlines it should be able to create the RSA key fine. 如果您将其更改为具有换行符,则应该能够创建RSA密钥。

eg 例如

    std::string publicKey = "-----BEGIN PUBLIC KEY-----\n"\
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnzyis1ZjfNB0bBgKFMSv\n"\
"vkTtwlvBsaJq7S5wA+kzeVOVpVWwkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHc\n"\
"aT92whREFpLv9cj5lTeJSibyr/Mrm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIy\n"\
"tvHWTxZYEcXLgAXFuUuaS3uF9gEiNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0\n"\
"e+lf4s4OxQawWD79J9/5d3Ry0vbV3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWb\n"\
"V6L11BWkpzGXSW4Hv43qa+GSYOD2QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9\n"\
"MwIDAQAB\n"\
"-----END PUBLIC KEY-----\n";

Also your code will not work as you don't need to "encode" the header and body text, but you do need to "base64url decode" the signature as it needs to be a binary value for the verification to work. 此外,您的代码将无法工作,因为您不需要“编码”标题和正文文本,但您需要“base64url decode”签名,因为它需要是一个二进制值才能使验证生效。

The following code works for me: 以下代码适用于我:

bool RSAVerifySignature(RSA* rsa, std::string const& token)
{
    auto const pub_key_handle = make_handle(EVP_PKEY_new(), EVP_PKEY_free);
    if (!pub_key_handle)
    {
        RSA_free(rsa);
        return false;
    }
    EVP_PKEY_assign_RSA(pub_key_handle.get(), rsa);

    std::vector<std::string> token_parts;
    split(token, token_parts, '.');
    if (token_parts.size() != 3) return false;

    auto& decoded_header = token_parts[0];
    auto& decoded_body = token_parts[1];
    auto sig_decoded = base64_url_decode(token_parts[2]);

    auto const rsa_verify_ctx = make_handle(EVP_MD_CTX_new(), EVP_MD_CTX_free);
    if (!rsa_verify_ctx) return false;

    if (1 != EVP_DigestVerifyInit(rsa_verify_ctx.get(), nullptr, EVP_sha256(), nullptr, pub_key_handle.get())) return false;
    if (1 != EVP_DigestVerifyUpdate(rsa_verify_ctx.get(), reinterpret_cast<unsigned char*>(decoded_header.data()), decoded_header.length())) return false;
    if (1 != EVP_DigestVerifyUpdate(rsa_verify_ctx.get(), ".", 1)) return false;
    if (1 != EVP_DigestVerifyUpdate(rsa_verify_ctx.get(), reinterpret_cast<unsigned char*>(decoded_body.data()), decoded_body.length())) return false;
    return 1 == EVP_DigestVerifyFinal(rsa_verify_ctx.get(), reinterpret_cast<unsigned char*>(sig_decoded.data()), sig_decoded.length());
}

UPDATE: 更新:

The above code assumes that you are consuming the RSA pointer (ie passing ownership of the RSA and it will be freed by the exit of the function). 上面的代码假设你正在使用RSA指针(即传递RSA的所有权,它将被函数的退出释放)。

In Dmity's answer he is incorrectly assuming that he can pass the RSA pointer to EVP_PKEY_assign_RSA multiple times. 在Dmity的回答中,他错误地认为他可以多次将RSA指针传递给EVP_PKEY_assign_RSA。 Doing this is what is causing his reasoning that your can't free the EVP_PKEY pointer, as the first free will work and will also destroy the RSA pointer. 这样做是导致他无法释放EVP_PKEY指针的原因,因为第一个free将起作用并且还会破坏RSA指针。 The second free will cause the sib fault he's talking about as the RSA pointer is already freed. 第二个空闲将导致他正在谈论的同胞错误,因为RSA指针已经被释放。 To change my example above to not consume the RSA pointer we need to increase the RSA internal reference so that the free of the EVP_PKEY pointer will not free the RSA pointer but just decrease it using the RSA_up_ref function. 要更改上面的示例以不使用RSA指针,我们需要增加RSA内部引用,以便释放EVP_PKEY指针不会释放RSA指针,而只是使用RSA_up_ref函数来减少它。

eg 例如

bool RSAVerifySignature(RSA* rsa, std::string const& token)
{
    auto pub_key_handle = make_handle(EVP_PKEY_new(), EVP_PKEY_free);
    if (!pub_key_handle)
    {
        return false;
    }

    RSA_up_ref(rsa);
    EVP_PKEY_assign_RSA(pub_key_handle.get(), rsa);

    std::vector<std::string> token_parts;
    split(token, token_parts, '.');
    if (token_parts.size() != 3) return false;

    auto& decoded_header = token_parts[0];
    auto& decoded_body = token_parts[1];
    auto sig_decoded = base64_url_decode(token_parts[2]);

    ///auto const rsa_verify_ctx = make_handle(EVP_MD_CTX_new(), EVP_MD_CTX_free);
    auto const rsa_verify_ctx = make_handle(EVP_MD_CTX_new(), EVP_MD_CTX_free);
    if (!rsa_verify_ctx) return false;

    EVP_PKEY_CTX *pctx;
    if (1 != EVP_DigestVerifyInit(rsa_verify_ctx.get(), &pctx, EVP_sha256(), nullptr, pub_key_handle.get())) return false;
    pub_key_handle.reset();
    if (1 != EVP_DigestVerifyUpdate(rsa_verify_ctx.get(), reinterpret_cast<unsigned char*>(decoded_header.data()), decoded_header.length())) return false;
    if (1 != EVP_DigestVerifyUpdate(rsa_verify_ctx.get(), ".", 1)) return false;
    if (1 != EVP_DigestVerifyUpdate(rsa_verify_ctx.get(), reinterpret_cast<unsigned char*>(decoded_body.data()), decoded_body.length())) return false;
    return 1 == EVP_DigestVerifyFinal(rsa_verify_ctx.get(), reinterpret_cast<unsigned char*>(sig_decoded.data()), sig_decoded.length());
}

And the test calling code can now do this and not cause a sig fault: 测试调用代码现在可以执行此操作而不会导致sig错误:

auto public_rsa = make_handle(createPublicRSA(publicKey), RSA_free);
if(!public_rsa) return false;

for(auto i = 0; i < 5; ++i)
{
    if(!RSAVerifySignature(public_rsa.get(), token)) return false;
}
public_rsa.reset();

Following peace of code with CTX cleanup 遵循CTX清理代码的和平

bool sha_validate( const EVP_MD* type, const std::string& input, const std::vector<unsigned char>& digest )
{
    if( !rsa )
        return false;

    EVP_PKEY* pub_key  = EVP_PKEY_new(); 
    EVP_PKEY_assign_RSA(pub_key, rsa);
    EVP_MD_CTX* rsa_verify_ctx = EVP_MD_CTX_create();

    auto ctx_free = scope_remove( [rsa_verify_ctx]() {
        EVP_MD_CTX_cleanup( rsa_verify_ctx );
        EVP_MD_CTX_destroy( rsa_verify_ctx );
    });

    if (EVP_DigestVerifyInit( rsa_verify_ctx,NULL, type, NULL, pub_key  ) <=0 )
        return false;

    if (EVP_DigestVerifyUpdate( rsa_verify_ctx, input.c_str(), input.size() ) <= 0)
        return false;

    return EVP_DigestVerifyFinal( rsa_verify_ctx, &digest[0], digest.size() ) == 1;
}

bool sha_validate( int type, const std::string& input, const std::vector<unsigned char>& digest )
{
     bool result = false;

     if( type & RsaOaep::SHA1 )
         result = sha_validate( EVP_sha1(), input, digest );

     if( !result && ( type & RsaOaep::SHA256 ) == static_cast<int>(RsaOaep::SHA256) )
         result = sha_validate( EVP_sha256(), input, digest );

     return result;
}

RSA* rsa = nullptr;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM