It was very helpful to me to follow this exact example: https://techdocs.akamai.com/iot-token-access-control/docs/generate-jwt-rsa-keys
It showcases the usage of OpenSSL on the commandline to generate a key and sign a JWT token.
You can also create the RSA-SHA256 signature using https://pkitools.net/pages/primitive/signature.html and the "RSA", "PKCS1", "SHA256withRSA" settings, putting the eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6Imh0dHBzOi8vd3d3LmV4YW1wbGUuY29tL2F1dGgifQ as input string and your private key in it. The output signature in hex should then match a hexdump of the [hash,len] memory segment w.r.t. getsha().
After comparing outputs and intermediate inputs for a bit, it became clear that while the CLI commands worked, the C program made a mistake in the Base64URL conversion. This screwed up the content to be signed as well as the encoding of the output signature. In particular, your old b64enc function didn't even take the input size in bytes. It always assumed the input was an null terminated ASCII string, which is not true in case of a e.g. binary signature output. So, I threw it out and replaced with the regular OpenSSL Base64 decoding plus a post-processing pass to make it become Base64URL, see https://base64.guru/standards/base64url. Alongside that, I cleaned up the variable names (X is not very descriptive) and simplified some other pieces.
The program could then successfully output the JWT token
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6Imh0dHBzOi8vd3d3LmV4YW1wbGUuY29tL2F1dGgifQ.Igo7J8vJL6RXP7SmuGJMm340cexcUc7Zk9EwMMG4IndVy8iw14-JcujO7wVdO7FObyTqwzg9FgwV11Wero4TPKmkGYdZzz7SDDy_8xqUich9rNy8uTq9n6hHMp40yP16i5AM5Z5JFpcEzw8qG1uK5JqAbjJZnXyj3hek450EAlyQJs3_v2oN_Wmhvzqwjb10r7IxBdvrybu7n4FcrgUJI3m0dfqSZ72Cd8PoWqD1kjqxIeew2PWFEOQm4a77o5oShSTivBEtREkUE3cDTP4_EIqZo25wYeID3z4Qo8ETo5wPfHbROu94fAj65xLxZz6R0VzznCy52Bkjy1GAVonmoQ
Which could be verified by JWT.io with the public key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoCiqat+PasvxXzHpZTF9
269i4jn+10RdJ8kP5471/hQtlgGGL5wU37UVHEW8MZxm9PVPLvwrKR2GhReOWux1
xEHu3xpQXHHD+b80FsvOnquxFzsRUTQlUKTg78EgWx8D2No3fRU95V0W3UG2p98z
bV32hN3GiLWYLI49Hk+KFl3LMswaMyxqHzEIBhJXq6yyzlinTdwWgsRtE/uF/RKJ
lcLVChDRU9DYJyCcWf1KL+vBjgvF3xRe/Z1Hj7XSz/FVIilrMWNh9lx3fHX1f39T
d2EMVVlMMKfYZjz72EZV6AFYtPzEW4latl4LaoGD4UGVP/PBnIT9lKmrfqCmOwAY
awIDAQAB
-----END PUBLIC KEY-----
The total program is as
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define CEILING(x,y) (((x) + (y) - 1) / (y))
void get_jwt_signature(const char* msg, char* jwt_signature_out, size_t* jwt_signature_outlen) {
// This function wont work, yet it returns a 256 byte value as jwt_signature_out.
// I'm given a key. let's assume its this (but more extravagant).
char* pky =
"-----BEGIN PRIVATE KEY-----\nAAAAAAAAAAA==\n-----END PRIVATE KEY-----\n";
unsigned int psz = strlen(pky), msz = strlen(msg), len = 0;
memset(jwt_signature_out, 0, *jwt_signature_outlen); // wipe out output hash
OpenSSL_add_all_digests();
BIO* IO = BIO_new_mem_buf(pky, psz); // make a buffer?
struct evp_pkey_st* SK = PEM_read_bio_PrivateKey(IO, NULL, NULL, NULL);
// error out if function fails
if (!SK) {
// we reach here if we use the example private key above, but if you replace
// it with a real private key, this test will pass.
BIO_free_all(IO);
EVP_PKEY_free(SK);
printf("Error getting key\n");
exit(-1);
}
EVP_MD_CTX* md_ctx = EVP_MD_CTX_create();
// We want SHA256 key
if (!EVP_SignInit(md_ctx, EVP_sha256())) {
printf("Error getting digest\n");
exit(-1);
}
// We want to add our generated JWT
if (!EVP_SignUpdate(md_ctx, msg, msz)) {
printf("Error updating key/digest\n");
exit(-1);
}
// Then we sign the key
// This function gives warnings if the string isn't an unsigned char
// but does that matter if I'm only feeding in base64 values?
EVP_SignFinal(md_ctx, (unsigned char*)jwt_signature_out, &len, SK);
*jwt_signature_outlen = len;
// Free allocations
BIO_free_all(IO);
EVP_PKEY_free(SK);
EVP_MD_CTX_destroy(md_ctx);
}
char* b64enc(const char* in, const int insz, char* res, const int bufsz) {
BIO *bio, *b64;
FILE* stream;
int encodedSize = 4 * CEILING(insz, 3);
if(bufsz <= encodedSize + 1) {
return NULL; // buffer not big enough
}
stream = fmemopen(res, encodedSize+1, "w");
b64 = BIO_new(BIO_f_base64());
bio = BIO_new_fp(stream, BIO_NOCLOSE);
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); //Ignore newlines - write everything in one line
BIO_write(bio, in, insz);
BIO_flush(bio);
BIO_free_all(bio);
fclose(stream);
// postprocessing to make it URL safe
for(unsigned i = 0; i < strlen(res); i++) {
if(res[i] == '+') res[i] = '-';
if(res[i] == '/') res[i] = '_';
}
// remove trailing padding ('=' sign)
while(res[strlen(res) - 1] == '=') res[strlen(res) - 1] = '\0';
return res;
}
int main() {
// Setup our header and claims for JWT
char* jwtheader = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
char* claims = "{\"scope\":\"https://www.example.com/auth\"}";
// set char array size of each element
int bufsz = 2048;
char headerdotclaims[bufsz];
char tmp2[bufsz];
char tmp[bufsz];
char jwt_signature[bufsz];
char result[bufsz];
memset(headerdotclaims, 0, sizeof(headerdotclaims));
memset(tmp2, 0, sizeof(tmp2));
memset(tmp, 0, sizeof(tmp));
memset(jwt_signature, 0, sizeof(jwt_signature));
memset(result, 0, sizeof(result));
// Base64 encode JWT values and combine them with a dot.
sprintf(headerdotclaims, "%s.%s",
b64enc(jwtheader, strlen(jwtheader), tmp2, bufsz - 1),
b64enc(claims, strlen(claims), tmp, bufsz - 1));
// Get signature
size_t jwt_signature_len = sizeof(jwt_signature);
get_jwt_signature(headerdotclaims, jwt_signature, &jwt_signature_len);
// Add a dot and the base64 encoded version to the result
sprintf(result, "%s.%s",
headerdotclaims,
b64enc(jwt_signature, jwt_signature_len, tmp, sizeof(tmp) - 1));
// and show it
printf("%s\n", result);
}
Edit: Specifically, your base64 function says
Base64 input:
22 0A 3B 27 CB C9 2F A4 57 3F B4 A6 B8 62 4C 9B | ".;'../.W?...bL.
7E 34 71 EC 5C 51 CE D9 93 D1 30 30 C1 B8 22 77 | ~4q.\Q....00.."w
55 CB C8 B0 D7 8F 89 72 E8 CE EF 05 5D 3B B1 4E | U......r....];.N
6F 24 EA C3 38 3D 16 0C 15 D7 55 9E AE 8E 13 3C | o$..8=....U....<
A9 A4 19 87 59 CF 3E D2 0C 3C BF F3 1A 94 89 C8 | ....Y.>..<......
7D AC DC BC B9 3A BD 9F A8 47 32 9E 34 C8 FD 7A | }....:...G2.4..z
8B 90 0C E5 9E 49 16 97 04 CF 0F 2A 1B 5B 8A E4 | .....I.....*.[..
9A 80 6E 32 59 9D 7C A3 DE 17 A4 E3 9D 04 02 5C | ..n2Y.|........\
90 26 CD FF BF 6A 0D FD 69 A1 BF 3A B0 8D BD 74 | .&...j..i..:...t
AF B2 31 05 DB EB C9 BB BB 9F 81 5C AE 05 09 23 | ..1........\...#
79 B4 75 FA 92 67 BD 82 77 C3 E8 5A A0 F5 92 3A | y.u..g..w..Z...:
B1 21 E7 B0 D8 F5 85 10 E4 26 E1 AE FB A3 9A 12 | .!.......&......
85 24 E2 BC 11 2D 44 49 14 13 77 03 4C FE 3F 10 | .$...-DI..w.L.?.
8A 99 A3 6E 70 61 E2 03 DF 3E 10 A3 C1 13 A3 9C | ...npa...>......
0F 7C 76 D1 3A EF 78 7C 08 FA E7 12 F1 67 3E 91 | .|v.:.x|.....g>.
D1 5C F3 9C 2C B9 D8 19 23 CB 51 80 56 89 E6 A1 | .\..,...#.Q.V...
Base64 output: Igo7JsrJLqRXPrOmuGJMm340cOxcUM3ZktEwL8C4IndVysew1o6JcefO7wVdOrFObyPqwzg9FgwV11SerY4TO6ikGIdZzz3SDDu_8xmUiMh9q9u8uTm9nqhHMZ40x_16ipAM5J5JFZcEzw8qG1qK45mAbjJZnXuj3hak4p0EAluQJcz_v2oN_WihvzmwjL10rrIxBNrryLq7noFcrgUJI3i0dPmSZryCdsLoWZ_1kjmxIOaw1_SFD-Qm4K37opoShSPivBEtREkUE3cDS_4_D4mZo25wYOID3z4QosETopwPfHXROe94fAf65xHxZz2R0VvznCu52Bkjy1CAVYjmoQ
While the correct output should be with openssl enc -base64 | tr -d '\n=' | tr -- '+/' '-_'
00000000 22 0a 3b 27 cb c9 2f a4 57 3f b4 a6 b8 62 4c 9b |".;'../.W?...bL.|
00000010 7e 34 71 ec 5c 51 ce d9 93 d1 30 30 c1 b8 22 77 |~4q.\Q....00.."w|
00000020 55 cb c8 b0 d7 8f 89 72 e8 ce ef 05 5d 3b b1 4e |U......r....];.N|
00000030 6f 24 ea c3 38 3d 16 0c 15 d7 55 9e ae 8e 13 3c |o$..8=....U....<|
00000040 a9 a4 19 87 59 cf 3e d2 0c 3c bf f3 1a 94 89 c8 |....Y.>..<......|
00000050 7d ac dc bc b9 3a bd 9f a8 47 32 9e 34 c8 fd 7a |}....:...G2.4..z|
00000060 8b 90 0c e5 9e 49 16 97 04 cf 0f 2a 1b 5b 8a e4 |.....I.....*.[..|
00000070 9a 80 6e 32 59 9d 7c a3 de 17 a4 e3 9d 04 02 5c |..n2Y.|........\|
00000080 90 26 cd ff bf 6a 0d fd 69 a1 bf 3a b0 8d bd 74 |.&...j..i..:...t|
00000090 af b2 31 05 db eb c9 bb bb 9f 81 5c ae 05 09 23 |..1........\...#|
000000a0 79 b4 75 fa 92 67 bd 82 77 c3 e8 5a a0 f5 92 3a |y.u..g..w..Z...:|
000000b0 b1 21 e7 b0 d8 f5 85 10 e4 26 e1 ae fb a3 9a 12 |.!.......&......|
000000c0 85 24 e2 bc 11 2d 44 49 14 13 77 03 4c fe 3f 10 |.$...-DI..w.L.?.|
000000d0 8a 99 a3 6e 70 61 e2 03 df 3e 10 a3 c1 13 a3 9c |...npa...>......|
000000e0 0f 7c 76 d1 3a ef 78 7c 08 fa e7 12 f1 67 3e 91 |.|v.:.x|.....g>.|
000000f0 d1 5c f3 9c 2c b9 d8 19 23 cb 51 80 56 89 e6 a1 |.\..,...#.Q.V...|
00000100
Igo7J8vJL6RXP7SmuGJMm340cexcUc7Zk9EwMMG4IndVy8iw14-JcujO7wVdO7FObyTqwzg9FgwV11Wero4TPKmkGYdZzz7SDDy_8xqUich9rNy8uTq9n6hHMp40yP16i5AM5Z5JFpcEzw8qG1uK5JqAbjJZnXyj3hek450EAlyQJs3_v2oN_Wmhvzqwjb10r7IxBdvrybu7n4FcrgUJI3m0dfqSZ72Cd8PoWqD1kjqxIeew2PWFEOQm4a77o5oShSTivBEtREkUE3cDTP4_EIqZo25wYeID3z4Qo8ETo5wPfHbROu94fAj65xLxZz6R0VzznCy52Bkjy1GAVonmoQ