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());
}
OpenSSL inserts a newline character after each 64’th character, you can disable this by using:
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.
2) When making the Base64 output URL-safe you need to replace '+' with '-' and '/' with '_'. your code had that the other way around. So: