2

I'm looking for a native openssl solution to this and all the other suggestions did NOT help. I'm trying to make a JWT packet I can use to login to google services from within C code as I wish to write an X application.

Here's my code:

#include <openssl/evp.h>
#include <openssl/pem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void getsha(char* msg, char* hash) {
  // This function wont work, yet it returns a 256 byte value as hash.
  // 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(hash, 0, 384);  // 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* X = EVP_MD_CTX_create();
  // We want SHA256 key
  if (!EVP_SignInit(X, EVP_sha256())) {
    printf("Error getting digest\n");
    exit(-1);
  }
  // We want to add our generated JWT
  if (!EVP_SignUpdate(X, 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(X, (unsigned char*)hash, &len, SK);
  // Free allocations
  BIO_free_all(IO);
  EVP_PKEY_free(SK);
  EVP_MD_CTX_destroy(X);
}

char* b64enc(const char* in, char* res, const int bufsz) {
  // Use modified lookup table because today's base-64 standards are different
  // when dealing with JWT
  const char* lut =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
  // get output pointer and size of input
  int n = 0, insz = strlen(in);
  char* rp = res;
  // exit if input buffer is too small but we made large buffers for our test so
  // this shouldn't happen.
  if ((insz / 3) * 4 + 2 > bufsz) {
    return NULL;
  }
  // wipe out output buffer
  memset(res, 0, bufsz);
  while (n < insz) {
    unsigned long d = 0, n2 = 0, gotbytes = 0;
    // shove up to 3 characters into a 32-bit buffer (defined as unsigned long
    // d)
    while (n2 < 3 && n < insz) {
      d = (d << 8) + in[n];
      n++;
      n2++;
      gotbytes++;
    }
    // Keep shoving in binary 0's until 24 bits are shifted in.
    while (n2 < 3) {
      d = (d << 8);
      n2++;
    }
    // Output is stored backwards so we make it forwards but only giving 6 bits
    // (hence anding with 0x3F)
    *rp++ = lut[(d >> 18) & 0x3F];
    *rp++ = lut[(d >> 12) & 0x3F];
    if (gotbytes >= 2) {
      *rp++ = lut[(d >> 6) & 0x3F];
    }  // input bytes >= 2 means 3 bytes in base64
    if (gotbytes >= 3) {
      *rp++ = lut[d & 0x3F];
    }  // input bytes >= 3 means 4 bytes in base64
  }
  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;
  // feed each char array from ssl array to not make memory fragments
  char ssl[11000], *tmp2 = ssl, *tmp = ssl + bufsz,
                   *headerdotclaims = ssl + (bufsz * 2),
                   *sha256val = ssl + (bufsz * 3), *result = ssl + (bufsz * 4);
  // and erase the whole space in ONE operation
  memset(ssl, 0, 10999);
  // Base64 encode JWT values and combine them with a dot.
  sprintf(headerdotclaims, "%s%c%s", b64enc(jwtheader, tmp2, bufsz - 1), '.',
          b64enc(claims, tmp, bufsz - 1));
  // Get SHA256 value of above. (apparently never working)
  getsha(headerdotclaims, sha256val);
  // Add a dot and the base64 encoded version to the result
  sprintf(result, "%s%c%s", headerdotclaims, '.',
          b64enc(sha256val, tmp, bufsz - 1));
  // and show it
  printf("%s", result);
}

I am successful with implementing a PHP version of this with openssl_sign function but I can't figure out how to do it in C. In all cases, the openSSL version is 1.0.2.

Is there something I'm missing in my code to make it work?

1 Answer 1

1

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
Sign up to request clarification or add additional context in comments.

2 Comments

I tried figuring out your code with your public key but the PEM_read_bio_PrivateKey function isn't accepting your key. One thing I found out through your link is that my base64 encoding function wasn't working because I needed to ignore (not process) newline characters during encoding.
It's a public key, not a private key, so PEM_read_bio_PrivateKey won't work. I didn't post the private key. JWT.io can verify the signature just fine: imgur.com/a/ZafJyi3 The code should also work with your actual private key, so you can regenerate the signature.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.