Hi,
I'm having trouble creating a JWT for a server to server application.
I'm doing this in C (no choice on that) - so need to write the code for JWT.
I have a Service Account and have my Private Key.
I've been following the documentation for
Using OAuth 2.0 for Server to Server Applications.
I have verified that my base64 encoded header and claim set are correct (they decode to the right source).
I use openssl to write the private key from my server account p12 file to a separate file (p12.out).
It has the header and footer in it;
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
I think the key is base64 encoded. I looks like this;
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKuUus61xp7GIrf+
dfBkEuv7quscDsDSJAI4MVs1p1ej4cxIRHasoV0rIYU/8cURuiVud5exLOF2A+7A
... [ 7 lines removed ]
mrYfAkBpbupxOMkEL1Tt7fkgOC4KTVNNSYKpX5CvcL/i8tfn31nN8oqt+iwEYDrY
9FGQGXrxXw1w6sSs0fot75uH2fU9AkB/8qp5QUzJfqAP5a/2kJvwpJN/yDsmRfRD
gBELtbXfjR1COYOfCFTufqJAy+JLLLyWXw7R3vlu1EBD9SGJJF/TAkEAr8kyqJd/
zM29bY+omdRWenkI1qmMM+O3SsKbz4ue/NQ73fP1NrcFzCSerNCqzq4uonR5TXbE
rFnkA9xEogM7eg==
I then read this file to get my private key.
I use openssl to sign the;
{Base64url encoded header}.{Base64url encoded claim set}
I get a signature (base64 encoded) that looks like this;
mwPFYd/5TbrgQ5z+V7QvJOvhIsVRyu3n9AeqYjxzmJ4=
My final JWT looks like this;
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzMDAyMzQ0MDg3NzNAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZGV2c3RvcmFnZS5yZWFkb25seSIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTM2NDE0NDQxOCwiaWF0IjoxMzY0MTQwODE4fQ==.mwPFYd/5TbrgQ5z+V7QvJOvhIsVRyu3n9AeqYjxzmJ4=
When I request the token using curl, I get an
invalid_grant error.
curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIzMDAyMzQ0MDg3NzNAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvZGV2c3RvcmFnZS5yZWFkb25seSIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9XXXXXXXXXMi90b2tlbiIsImV4cCI6MTM2NDA5NzUyNywiaWF0IjoxMzY0MDkzOTI3fQ==.4gQf4Tqv140c9PJygxl3O2X6SnKHSm/KpK5Aw/+h7Co='
https://accounts.google.com/o/oauth2/token{
"error" : "invalid_grant"
}
I have tried many things with no luck.
I have not tried stripping out the 'BEGIN...' and 'END...' from the private key file - and I have not tried to decode the actual key. My understanding is that this key can be copied and used as is.
I'm doing something wrong somewhere, but can't find it. Any help would really be appreciated.
The code I'm using is below. I can send an email with the actual code if needed...
Thanks,
Paul
// =============== code ================
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <curl/curl.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>
#include <openssl/sha.h>
#include <stdint.h>
static char* gcs_header = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
static char* gcs_claim_set_fmt = "{\
\"iss\":\"%s\",\
\"scope\":\"
https://www.googleapis.com/auth/%s\",\
\"aud\":\"
https://accounts.google.com/o/oauth2/token\",\
\"exp\":%ld,\"iat\":%ld\
}";
static char* gcs_email = "
300XXX...@developer.gserviceaccount.com";
//static char* example_email = "
761326798069-r5mljlln1...@developer.gserviceaccount.com"; // prediction 1328554385, 1328550785
static char* gcs_str_to_sign_fmt = "%s.%s";
static char* gcs_jwt_fmt = "%s.%s";
char* b64_encode(const char* source, size_t len)
{
BIO *bmem, *b64;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, bmem);
BIO_write(b64, (uint8_t*)source, len);
if(BIO_flush(b64))
BIO_get_mem_ptr(b64, &bptr);
// copy the encoded data into a new char buffer
char *buf = (char *)malloc(bptr->length + 1);
memset(buf, 0, bptr->length + 1);
memcpy(buf, bptr->data, bptr->length);
BIO_free_all(b64);
return buf;
}
int extractP12PrivateKey(char* keyfile)
{
FILE *fp;
EVP_PKEY *pkey;
PKCS12 *p12;
X509 *cert;
STACK_OF(X509) *ca = NULL;
char* args[4];
args[0] = "getPrivateKey";
args[1] = "auth/3efe6f130f43f81300a14db7f919b36XXXXXXXXX-privatekey.p12";
args[2] = "notasecret";
args[3] = keyfile;
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
if(!(fp = fopen(args[1], "rb"))) {
fprintf(stderr, "open failed(%s), errno(%d)\n", args[1], errno);
exit(1);
}
p12 = d2i_PKCS12_fp(fp, NULL);
fclose(fp);
if(!p12) {
fprintf(stderr, "Could not read PKCS#12 file(%s)\n", args[1]);
ERR_print_errors_fp(stderr);
exit (1);
}
if(!PKCS12_parse(p12, args[2], &pkey, &cert, &ca)) {
fprintf(stderr, "Error parsing PKCS#12 file(%s)\n", args[1]);
ERR_print_errors_fp(stderr);
exit (1);
}
PKCS12_free(p12);
if(!(fp = fopen(args[3], "w"))) {
fprintf(stderr, "open failed on(%s), errno(%d)\n", args[3], errno);
exit(1);
}
if(pkey) {
PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL);
}
fclose(fp);
return 0;
}
uint8_t* getPrivateKey(char* p12file)
{
FILE *keyfile;
size_t nbytes;
keyfile = fopen(p12file, "rb");
if(keyfile == NULL) {
printf("open failed on(%s), errno(%d)\n", p12file, errno);
exit(1);
}
struct stat fileInfo;
if(stat(p12file, &fileInfo) == (-1)) {
printf("stat() failed on(%s), errno(%d)\n", p12file, errno);
exit(1);
}
long filesize = fileInfo.st_size;
printf("source file(%s):size(%ld)\n", p12file, filesize);
uint8_t* buf = (uint8_t*)malloc(filesize + 1);
memset(buf, 0, filesize + 1);
// copy the file into the buffer:
nbytes = fread(buf, 1, filesize, keyfile);
if(nbytes != filesize) {
fclose(keyfile);
printf("fread() failed on(%s), read(%ld), filesize(%ld)\n", p12file, nbytes, filesize);
exit(1);
}
fclose(keyfile);
printf("\n[PEM private key]\n%s\n", buf);
return buf;
}
unsigned
create_jwt(uint8_t* key, size_t keylen, char* src, size_t srclen, char* digest)
{
HMAC_CTX hctx;
unsigned len;
//size_t _srclen = (srclen >= SHA256_DIGEST_LENGTH) ? SHA256_DIGEST_LENGTH : srclen;
HMAC_CTX_init(&hctx);
HMAC_Init_ex(&hctx, key, keylen, EVP_sha256(), NULL);
HMAC_Update(&hctx, (uint8_t*)src, srclen);
HMAC_Final(&hctx, (uint8_t*)digest, &len);
HMAC_CTX_cleanup(&hctx);
return len;
}
time_t issue_time() { return time(NULL); }
time_t expire_time(time_t issue_time) { return issue_time + 3600; }
int main(int argc, char * argv[])
{
// extract the P12 private key from the service account p12 file into 'p12.out'
extractP12PrivateKey("p12.out");
// read the private key in from the given file
uint8_t* pkey = getPrivateKey("p12.out");
// base64 encode the header
char* header64 = b64_encode(gcs_header, strlen(gcs_header));
printf("[header64] len(%d)\n%s\n\n", (int)strlen((char*)gcs_header), header64);
char claim_str[1024];
memset(claim_str, 0, 1024);
time_t iat = issue_time();
// create the claim set
sprintf(claim_str, gcs_claim_set_fmt, gcs_email, "devstorage.readonly", expire_time(iat), iat);
printf("claim_str=%s\n\n", claim_str);
// base64 encode the claim set
char* claim64 = b64_encode(claim_str, strlen(claim_str));
printf("[claim64]\n%s\n\n", claim64);
// make the string to sign
char str_to_sign[2048];
memset(str_to_sign, 0, 2048);
sprintf(str_to_sign, gcs_str_to_sign_fmt, header64, claim64);
printf("[strToSign]\n%s\n\n", str_to_sign);
// create the JWT
char digest[128];
unsigned len;
len = create_jwt(pkey, strlen((const char*)pkey), str_to_sign, strlen(str_to_sign), digest);
// base64 encode the private key
char* signature64 = b64_encode(digest, len);
printf("digest len(%d)\n", len);
printf("[signatureb64]\n%s\n\n", signature64);
char jwt[2048];
sprintf(jwt, gcs_jwt_fmt, str_to_sign, signature64);
printf("[jwt]\n%s\n", jwt);
exit(0);
}