JWT (JSON Web Token) in C++ using boost and openssl bug

6.1k views Asked by At

I am a fairly novice c++ programmer (I'm still in college so I suppose I'm a fairly novice programmer in general) and I am trying to generate a JWT in c++. I am able to generate and encode a header and payload, but the signature I generate using openssl's hmac library seems to be invalid when I check it on jwt.io. I have a feeling this is due to some nuanced mistake I'm just not seeing. If you can find my bug I'll greatly appreciate it. If you also have suggestions for how to improve my code in general that would also be appreciated. Note I have to use openssl and boost for json.

****UPDATE****

I discovered that extra new lines were being added and this caused errors during authentication. Currently I am calling str.erase to remove them but if someone could let me know where they are coming I would appreciate it so I can avoid having to manipulate the string


char* Auth::genJWT()
{
    ptree pt;
    std::stringstream jwt;

    //Make and encode header
    pt.put("typ","JWT");
    pt.put("alg","HS256");
    std::ostringstream buf;
    write_json(buf, pt, false);
    std::string header = buf.str();
    jwt <<  base64_encode((unsigned char*)header.c_str(), (int) header.length());

    //clear buffer
    pt.clear();
    buf.clear();
    buf.str("");

    //make pay load
    pt.put("org_guid", Config::organization_guid);
    pt.put("agent_guid", Config::agent_guid);
    write_json(buf, pt, false);
    std::string payload = buf.str();
    jwt << '.' <<  base64_encode((unsigned char*)payload.c_str(), (int) payload.length());

    //sign data
    std::string signed_data = signToken(jwt.str());
    jwt << '.' <<  base64_encode((unsigned char*) signed_data.c_str(), (int) signed_data.length());

    //jwt made
    std::cout << "JWT Token: " << jwt.str() << std::endl;

    //Note I am testing by copying the token into an online debugger 
    //returning "" is not my bug 
    return "";
}

This is the method I use sign the token

std::string Auth::signToken(std::string to_sign)
{
    //std::string key = Config::secret;
    std::string key = "foo";

    unsigned int len = 32;
    unsigned char signed_data[32];

    HMAC_CTX ctx;
    HMAC_CTX_init(&ctx);

    HMAC_Init_ex(&ctx, &key[0], (int) key.length(), EVP_sha256(), NULL);
    HMAC_Update(&ctx, (unsigned char*)&to_sign[0], to_sign.length());
    HMAC_Final(&ctx, signed_data, &len);
    HMAC_CTX_cleanup(&ctx);

    std::stringstream ss;
    ss << std::setfill('0');

    for(unsigned int i = 0; i < len; i++)
    {
        ss << signed_data[i];
    }

    return (ss.str());
}

This is how I encode the data in base64Url

std::string Auth::base64_encode(const unsigned char* input, int length)
{
    BIO *bmem, *b64;
    BUF_MEM *bptr;

    b64 = BIO_new(BIO_f_base64());
    bmem = BIO_new(BIO_s_mem());
    b64 = BIO_push(b64, bmem);
    BIO_write(b64, input, length);
    BIO_flush(b64);
    BIO_get_mem_ptr(b64, &bptr);

    std::stringstream ss;

    //make URL safe
    for(unsigned int i = 0; i < bptr->length - 1; i++)
    {
        switch(bptr->data[i])
        {
            case '+':
                ss << '_';
                break;
            case '/':
                ss << '-';
                break;
            case '=':
                //don't add in padding
                break;
            default:
                ss << bptr->data[i];
                break;
        }
    }

    BIO_free_all(b64);
    return (ss.str());
}
1

There are 1 answers

0
Halt On BEST ANSWER

OpenSSL inserts a newline character after each 64’th character, you can disable this by using:

BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);

See the documentation here.

While testing your code I also found the following 2 problems:

1) You skip the last character of the buffer when making it URL-safe. You should remove the - 1.

for(unsigned int i = 0; i < bptr->length ; i++)

2) When making the Base64 output URL-safe you need to replace '+' with '-' and '/' with '_'. your code had that the other way around. So:

case '+':
    ss << '-';
    break;
case '/':
    ss << '_';
    break;