使用openssl和C++来验证JWT的确切base64 url解码规则和实现是什么

What are the exact base64 url decoding rules and implementation using openssl and C++ to validate JWT

我对一般的 openssl 接口和 base64 解码规则还很陌生,并尝试使用以下解决方案验证用私钥 (RS256 alg) 签名的 JWT: 但我的示例 JWT 无法验证。

我只是没有 base64 url 解码功能,所以我按照我的理解手动执行了这些步骤 - 在调用我的 base64 解码功能之前,我替换了 '-' -> '+' 和 '_ ' -> '/'。为了能够获得正确的签名长度,我还必须手动添加填充 ('=')。是否有一些我遗漏的 base64 url 解码规则,或者首先替换符号的方法是否以任何方式错误?

我首先尝试使用RSA_verify,但错误是一样的。

使用 python 和在线 JWT 验证验证相同的令牌和密钥,因此它们应该没问题。我在 gdb 上的 b64 十六进制解码之后和 python 上的 urlsafe_b64decode 之后打印了签名,并且值是相同的(除了 python 版本末尾的一些符号)这更让人相信替换策略应该有效。

   // This is the original token
   static constexpr const char* buffer_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXBpLXJlc291cmNlIl0sInNjb3BlIjpbInJlYWQiXSwiZXhwIjoxNTYzNDUwODkzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMzVhYmVjZDEtNjcxMi00M2M3LWE4MDItZjg3MGYzMTY4MmI0IiwiY2xpZW50X2lkIjoidGVzdCJ9.ZCXtI2nN-d0Cn5dgb3K9JMI41nrEaK_AVSMRG9c5cyZqXpnMQETfGcDEs0jPzmRh-jDc-Kuq53naOtjkItMcR_vYPn72dKZ4Fpp8mvOAZXypkVCLzof3Lsxrtqq9G3V4LNTuOHiXW_q-9mEu51zWg1HDr1-rSt3YXkFFSWp5e4MWS2TNP1MB7lBbZC-kdMZ_GqZ9lrfNo2YqJR7tqcHOrfOmFTzqxVivEB8s-A0iEv_MwdlS6LpJBKU9-d94i1P9Lsqzlg7b_0ekRoYJEG4DXeNp2zxxBxZ1u3FBlIbyJoOGDmX-EU4A5eh2RlDdEvG1YF_zcMARpP1bFV86WTSOuQ";

    // This is token with replaced symbols that I am testing before writing the method for url decode
    static constexpr const char* buffer_token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXBpLXJlc291cmNlIl0sInNjb3BlIjpbInJlYWQiXSwiZXhwIjoxNTYzNDUwODkzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMzVhYmVjZDEtNjcxMi00M2M3LWE4MDItZjg3MGYzMTY4MmI0IiwiY2xpZW50X2lkIjoidGVzdCJ9.ZCXtI2nN+d0Cn5dgb3K9JMI41nrEaK/AVSMRG9c5cyZqXpnMQETfGcDEs0jPzmRh+jDc+Kuq53naOtjkItMcR/vYPn72dKZ4Fpp8mvOAZXypkVCLzof3Lsxrtqq9G3V4LNTuOHiXW/q+9mEu51zWg1HDr1+rSt3YXkFFSWp5e4MWS2TNP1MB7lBbZC+kdMZ/GqZ9lrfNo2YqJR7tqcHOrfOmFTzqxVivEB8s+A0iEv/MwdlS6LpJBKU9+d94i1P9Lsqzlg7b/0ekRoYJEG4DXeNp2zxxBxZ1u3FBlIbyJoOGDmX+EU4A5eh2RlDdEvG1YF/zcMARpP1bFV86WTSOuQ==";

    // this is how I create the RSA from a key, hopefully successfully because a key is returned with no error
    RSA* create_public_rsa(const unsigned char* p_key)
    {
       BIO *keybio = BIO_new_mem_buf(p_key, -1); // -1: assume string is null terminated
       if (!keybio)
       {
            return nullptr;
       }

       RSA* l_res = nullptr;

       l_res = PEM_read_bio_RSA_PUBKEY(keybio, NULL, NULL, NULL);

       BIO_free(keybio);

       return l_res;
    }

    bool RSAVerifySignature(RSA* rsa, std::string const& token)
    {
       auto pub_key_handle = std::shared_ptr<EVP_PKEY>(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::string decoded_header(token, 0, token.find('.'));
       std::string decoded_body;
       decoded_body.append(token.begin()+ token.find('.')+1, token.begin() + token.rfind('.')-1);
       std::string sig;
       sig.append(token.begin() + token.rfind('.') + 1, token.end());
       std::string sig_decoded;
       base64_decode(sig.c_str(), sig.size(), sig_decoded);

       EVP_MD_CTX* l_ctx = EVP_MD_CTX_create();
       EVP_MD_CTX_init(l_ctx);

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

       // ERR_print_errors_fp(stdout);

       ERR_load_crypto_strings();
       char err[130];
       while(auto e = ERR_get_error())
       {
          ERR_error_string(e, err);
          fprintf(stderr, "Error verifying message: %s\n", err);
       }

       return false;
    }  

错误是:

header: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
body: eyJhdWQiOlsiYXBpLXJlc291cmNlIl0sInNjb3BlIjpbInJlYWQiXSwiZXhwIjoxNTYzNDUwODkzLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiMzVhYmVjZDEtNjcxMi00M2M3LWE4MDItZjg3MGYzMTY4MmI0IiwiY2xpZW50X2lkIjoidGVzdCJ
sig: ZCXtI2nN+d0Cn5dgb3K9JMI41nrEaK/AVSMRG9c5cyZqXpnMQETfGcDEs0jPzmRh+jDc+Kuq53naOtjkItMcR/vYPn72dKZ4Fpp8mvOAZXypkVCLzof3Lsxrtqq9G3V4LNTuOHiXW/q+9mEu51zWg1HDr1+rSt3YXkFFSWp5e4MWS2TNP1MB7lBbZC+kdMZ/GqZ9lrfNo2YqJR7tqcHOrfOmFTzqxVivEB8s+A0iEv/MwdlS6LpJBKU9+d94i1P9Lsqzlg7b/0ekRoYJEG4DXeNp2zxxBxZ1u3FBlIbyJoOGDmX+EU4A5eh2RlDdEvG1YF/zcMARpP1bFV86WTSOuQ==
Error verifying message: error:04091068:rsa routines:INT_RSA_VERIFY:bad signature
error:04091068:rsa routines:INT_RSA_VERIFY:bad signature

这是我用过的base64_url_decode。我没有写,但我找不到从哪里得到的。

使用下面的代码,你还有问题吗?

/*
Base64 translates 24 bits into 4 ASCII characters at a time. First,
3 8-bit bytes are treated as 4 6-bit groups. Those 4 groups are
translated into ASCII characters. That is, each 6-bit number is treated
as an index into the ASCII character array.

If the final set of bits is less 8 or 16 instead of 24, traditional base64
would add a padding character. However, if the length of the data is
known, then padding can be eliminated.

One difference between the "standard" Base64 is two characters are different.
See RFC 4648 for details.
This is how we end up with the Base64 URL encoding.
*/

const char base64_url_alphabet[] = {
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
    'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
    'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
};

std::string base64_url_encode(const std::string & in) {
  std::string out;
  int val =0, valb=-6;
  size_t len = in.length();
  unsigned int i = 0;
  for (i = 0; i < len; i++) {
    unsigned char c = in[i];
    val = (val<<8) + c;
    valb += 8;
    while (valb >= 0) {
      out.push_back(base64_url_alphabet[(val>>valb)&0x3F]);
      valb -= 6;
    }
  }
  if (valb > -6) {
    out.push_back(base64_url_alphabet[((val<<8)>>(valb+8))&0x3F]);
  }
  return out;
}

std::string base64_url_decode(const std::string & in) {
  std::string out;
  std::vector<int> T(256, -1);
  unsigned int i;
  for (i =0; i < 64; i++) T[base64_url_alphabet[i]] = i;

  int val = 0, valb = -8;
  for (i = 0; i < in.length(); i++) {
    unsigned char c = in[i];
    if (T[c] == -1) break;
    val = (val<<6) + T[c];
    valb += 6;
    if (valb >= 0) {
      out.push_back(char((val>>valb)&0xFF));
      valb -= 8;
    }
  }
  return out;
}

更新:

你的问题不是 base64 解码,而是这一行:

decoded_body.append(token.begin()+ token.find('.')+1, token.begin() + token.rfind('.')-1);

你差一分。 decoded_body 缺少最后一个字符。如果您将其更改为以下内容,它将正常工作:

decoded_body.append(token.begin()+ token.find('.')+1, token.begin() + token.rfind('.'));