在C ++中通过OpenSSL使用公钥验证JWT令牌时出现分段错误 [英] Segmentation Fault while verifying JWT token using a public key through openSSL in C++

查看:107
本文介绍了在C ++中通过OpenSSL使用公钥验证JWT令牌时出现分段错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对C ++和OpenSSL非常陌生.我必须通过C ++中的OpenSSL使用公钥来验证给定的JWT令牌(算法RS256).我正在使用以下算法来验证JWT令牌.

I am very new to C++ and OpenSSL. I have to verify a given JWT token (algorithm RS256) using a public key through OpenSSL in C++. I am using following algorithm to verify the JWT token.

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

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

我在Mac系统上,正在使用g ++编译我的代码.终端上的openssl version显示LibreSSL 2.6.5.

I am on a Mac system and using g++ to compile my code. openssl version on terminal shows 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;
}

我在EVP_DigestVerifyFinal通话中收到Segmentation fault: 11.我不知道我在哪里错.请帮忙.

I am getting Segmentation fault: 11 at EVP_DigestVerifyFinal call. I have no idea where am I wrong. Please help.

推荐答案

如果您进行了一些基本的错误检查,您会发现createPublicRSA函数返回nullptr.这是因为PEM_read_bio_RSA_PUBKEY期望看到换行符,并且您的publicKey字符串没有任何内容.

If you had done some basic error checking you will see that your createPublicRSA function returns a nullptr. This is because PEM_read_bio_RSA_PUBKEY is expecting to see newlines and your publicKey string doesn't have any.

如果将其更改为具有换行符,则应该能够创建RSA密钥.

If you change it to have newlines it should be able to create the RSA key fine.

例如

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

您的代码也将无法使用,因为您不需要编码"标题和正文,但是您需要"base64url解码"签名,因为它必须是二进制值才能生效.

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.

以下代码对我有用:

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

更新:

上面的代码假定您正在使用RSA指针(即,传递RSA的所有权,并且将在函数退出时释放).

The above code assumes that you are consuming the RSA pointer (i.e. passing ownership of the RSA and it will be freed by the exit of the function).

在Dmity的回答中,他错误地认为自己可以多次将RSA指针传递给EVP_PKEY_assign_RSA.这样做是造成他的推理的原因,因为您不能释放EVP_PKEY指针,因为第一个释放将起作用并且还将破坏RSA指针.第二次释放将导致他正在谈论的sib故障,因为RSA指针已被释放.要更改上面的示例以不使用RSA指针,我们需要增加RSA内部引用,以便释放EVP_PKEY指针不会释放RSA指针,而只是使用RSA_up_ref函数将其减小.

In Dmity's answer he is incorrectly assuming that he can pass the RSA pointer to EVP_PKEY_assign_RSA multiple times. 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. The second free will cause the sib fault he's talking about as the RSA pointer is already freed. 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.

例如

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:

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();

这篇关于在C ++中通过OpenSSL使用公钥验证JWT令牌时出现分段错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆