Problem creating JWT in C program

798 views
Skip to first unread message

pmac

unread,
Mar 24, 2013, 12:29:43 PM3/24/13
to gs-dis...@googlegroups.com
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);
}




Google Cloud Storage Team

unread,
Mar 25, 2013, 2:23:53 PM3/25/13
to gs-discussion
Hi Paul,

Would you be willing to repost this question on stack overflow?  I think you'll get more eyes on the question and have a better chance at a helpful response over there.  

Best Regards,
Benson
Google Cloud Storage Team







--
You received this message because you are subscribed to the Google Groups "Google Cloud Storage" group.
To unsubscribe from this group and stop receiving emails from it, send an email to gs-discussio...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Paul McClure

unread,
Mar 25, 2013, 7:16:13 PM3/25/13
to gs-dis...@googlegroups.com
Sure, will do, thanks Benson.
Is there any thought about support for C on the Google team?
Thanks again,
Paul


--
You received this message because you are subscribed to a topic in the Google Groups "Google Cloud Storage" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/gs-discussion/_9MtAcVNdAM/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to gs-discussio...@googlegroups.com.

Google Cloud Storage Team

unread,
Mar 25, 2013, 7:27:14 PM3/25/13
to gs-discussion
Hi Paul,

We don't currently have any library support for C.  We're happy to help you interact with our APIs from whatever environment you'd like, but we do not have any libraries to facilitate that interaction.  It should all be doable, but you'll be breaking new ground.  

Best,
Benson
Google Cloud Storage Team
Reply all
Reply to author
Forward
0 new messages