4

I need to get an access token (for a service account) for the google's OAuth authentication service. I tried several things an studied a lot of on the web but don't succeed.

Basically i followed https://developers.google.com/accounts/docs/OAuth2ServiceAccount

What i have done (VS2013):

int _tmain(int argc, _TCHAR* argv[])
{
    Json::Value jwt_header;
    Json::Value jwt_claim_set;
    std::string jwt_b64;
    std::time_t t = std::time(NULL);
    Json::FastWriter jfw;
    Json::StyledWriter jsw;

    /* Create jwt header */
    jwt_header["alg"] = "RS256";
    jwt_header["typ"] = "JWT";
    std::cout << jsw.write(jwt_header);

    /* Create jwt claim set */
    jwt_claim_set["iss"] = "myid@developer.gserviceaccount.com"; /* service account email address */
    jwt_claim_set["scope"] = "https://www.googleapis.com/auth/plus.me" /* scope of requested access token */;
    jwt_claim_set["aud"] = "https://accounts.google.com/o/oauth2/token"; /* intended target of the assertion for an access token */
    jwt_claim_set["iad"] = std::to_string(t); /* issued time */
    jwt_claim_set["exp"] = std::to_string(t+3600); /* expire time*/
    std::cout << jsw.write(jwt_claim_set);

    /* create http POST request body */
    /* for header */
    std::string json_buffer;
    std::string json_buffer1;
    json_buffer = jfw.write(jwt_header);
    json_buffer = json_buffer.substr(0, json_buffer.size() - 1);
    json_buffer = base64_encode(reinterpret_cast<const unsigned char*>(json_buffer.c_str()), json_buffer.length(), true); /* urlsafeBasic64 encode*/
    json_buffer1.clear();
    std::remove_copy(json_buffer.begin(), json_buffer.end(), std::back_inserter(json_buffer1), '=');
    jwt_b64 = json_buffer1;
    jwt_b64 += ".";

    /* for claim set */
    json_buffer = jfw.write(jwt_claim_set);
    json_buffer = json_buffer.substr(0, json_buffer.size() - 1);
    json_buffer = base64_encode(reinterpret_cast<const unsigned char*>(json_buffer.c_str()), json_buffer.length(), true); /* urlsafeBasic64 encode*/
    json_buffer1.clear();
    std::remove_copy(json_buffer.begin(), json_buffer.end(), std::back_inserter(json_buffer1), '=');
    jwt_b64 += json_buffer1;


    /* for signature */
    std::string jwt_signature = jws_sign(jwt_b64, "key.p12");
    if (!jwt_signature.empty())
    {
        jwt_b64 += ".";
        json_buffer1.clear();
        std::remove_copy(jwt_signature.begin(), jwt_signature.end(), std::back_inserter(json_buffer1), '=');
        jwt_b64 += json_buffer1;
        write2file("jwt.bat", jwt_b64); /* for test purpose calling with curl */
    }
    else
        std::cout << "Error creating signature";

    return 0;
}

int write2file(std::string filename, std::string data)
{
    std::ofstream f(filename);
    f << "%curl% -d \"grant_type=urn%%3Aietf%%3Aparams%%3Aoauth%%3Agrant-type%%3Ajwt-bearer&assertion=";
    f << data;
    f << "\" https://accounts.google.com/o/oauth2/token";
    f.close();
    return 0;
}

std::string jws_sign(std::string data, std::string pkcs12_path) {

    SHA256_CTX mctx;
    unsigned char hash[SHA256_DIGEST_LENGTH];
    size_t hlen = SHA256_DIGEST_LENGTH;
    const char *buf = data.c_str();
    int n = strlen((const char*) buf);

    SHA256_Init(&mctx);
    SHA256_Update(&mctx, buf, n);
    SHA256_Final(hash, &mctx);

    std::string signature_b64;
    unsigned char *sig = NULL;
    size_t slen = 0;
    EVP_PKEY_CTX *kctx;
    EVP_PKEY *key = getPkey(pkcs12_path);
    kctx = EVP_PKEY_CTX_new(key, NULL);
    if (!kctx) goto err;

    if (EVP_PKEY_sign_init(kctx) <= 0) goto err;

    if (EVP_PKEY_CTX_set_rsa_padding(kctx, RSA_PKCS1_PADDING) <= 0) goto err;

    if (EVP_PKEY_CTX_set_signature_md(kctx, EVP_sha256()) <= 0) goto err;

    /* Determine buffer length */
    if (EVP_PKEY_sign(kctx, NULL, &slen, hash, hlen) <= 0) goto err;

    sig = (unsigned char *) OPENSSL_malloc(slen);

    if (!sig) goto err;

    if (EVP_PKEY_sign(kctx, sig, &slen, hash, hlen) <= 0) goto err;

    signature_b64 = base64_encode(sig, (unsigned int)slen, true);

    return signature_b64;

err:

    /* Clean up */
    EVP_cleanup();

    signature_b64.clear();
    return signature_b64;
}

All i receive back is

{
  "error" : "invalid_grant"
}

So if someone can point me into the right direction would be great. It would also help, if someone can point me to get the thing working by manually generating the jwt request out of openssl commands.

I'm working with VS2013

mcd
  • 101
  • 1
  • 7
  • cant help with the c++ but invalid grant is normally one of two things. the time is off time needs to be NTP. or you are using two many refresh tokens. Have you tried digging though the client lib for c++ to see what they are doing? https://github.com/google/google-api-cpp-client – Linda Lawton - DaImTo Feb 26 '15 at 09:18
  • @DalmTo Thanks to pointing me to the google-api-cpp-client, did not know about that. Unfortunately there seems no method for service account authentication to be included. I can safely say it's not an NTP or refresh token count issue ... It seems related to the signature procedure. – mcd Feb 26 '15 at 10:26

2 Answers2

3

I found my mistake - was simply a typo :(

jwt_claim_set["iad"] = std::to_string(t); /* issued time */

needs to be

jwt_claim_set["iat"] = std::to_string(t); /* issued time */

The code works and generate valid token requests.

mcd
  • 101
  • 1
  • 7
1

I've made a class for authentication on C++, will leave it here, may be someone may need it.

// YOU SHOULD GO TO Credentials SECTION FOR YOUR PROJECT AT https://console.developers.google.com/
// MAKE Service Account AND GET AUTHENTICATION JSON FROM IT, 
// PLACE IT TO BUILD FOLDER AND CALL IT google_service_account.json

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <iomanip>

// SSL INCLUDES
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/bio.h>

// https://github.com/nlohmann/json
#include <nlohmann/json.hpp>
using json = nlohmann::json;

class TGoogleAuthCpp {
    json serviceAccountJSON;
    bool serviceAccountExists;

    void readServiceAccountJson();
    RSA* createPrivateRSA(std::string key);

    bool RSASign( RSA* rsa,
                  const unsigned char* Msg,
                  size_t MsgLen,
                  unsigned char** EncMsg,
                  size_t* MsgLenEnc);

    std::string signMessage(std::string privateKey, std::string plainText);
    std::string url_encode(const std::string &value);
    std::string base64_encode(const std::string &in);

public:
    TGoogleAuthCpp();
    int createRequest();
};

TGoogleAuthCpp::TGoogleAuthCpp() {
    serviceAccountExists = false;
    readServiceAccountJson();
}

RSA* TGoogleAuthCpp::createPrivateRSA(std::string key) {
    RSA *rsa = NULL;
    const char* c_string = key.c_str();
    BIO * keybio = BIO_new_mem_buf((void*)c_string, -1);
    if (keybio==NULL) {
        return 0;
    }
    rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa,NULL, NULL);
    return rsa;
}

bool TGoogleAuthCpp::RSASign( RSA* rsa,
                                const unsigned char* Msg,
                                size_t MsgLen,
                                unsigned char** EncMsg,
                                size_t* MsgLenEnc) {
    EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create();
    EVP_PKEY* priKey  = EVP_PKEY_new();
    EVP_PKEY_assign_RSA(priKey, rsa);
    if (EVP_DigestSignInit(m_RSASignCtx,NULL, EVP_sha256(), NULL,priKey)<=0) {
        return false;
    }
    if (EVP_DigestSignUpdate(m_RSASignCtx, Msg, MsgLen) <= 0) {
        return false;
    }
    if (EVP_DigestSignFinal(m_RSASignCtx, NULL, MsgLenEnc) <=0) {
        return false;
    }
    *EncMsg = (unsigned char*)malloc(*MsgLenEnc);
    if (EVP_DigestSignFinal(m_RSASignCtx, *EncMsg, MsgLenEnc) <= 0) {
        return false;
    }
    EVP_MD_CTX_cleanup(m_RSASignCtx);
    return true;
}

std::string TGoogleAuthCpp::signMessage(std::string privateKey, std::string plainText) {
    RSA* privateRSA = createPrivateRSA(privateKey);
    unsigned char* encMessage;
    size_t encMessageLength;
    RSASign(privateRSA, (unsigned char*) plainText.c_str(), plainText.length(), &encMessage, &encMessageLength);
    std::string str1((char *)(encMessage), encMessageLength);
    free(encMessage);
    return base64_encode(str1);
}

void TGoogleAuthCpp::readServiceAccountJson() {

    std::string fname = "google_service_account.json";

    std::string line;
    std::ifstream myfile (fname);
    if (myfile.good()) {

        std::stringstream ss;

        if (myfile.is_open()) {
            while (getline(myfile, line)) {
                ss << line << '\n';
            }
            myfile.close();
            serviceAccountJSON = json::parse(ss.str());
            serviceAccountExists = true;
        }
    }
}

std::string TGoogleAuthCpp::base64_encode(const std::string &in) {
    std::string out;

    std::string base64_encode_b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//=

    int val=0, valb=-6;
    for (unsigned char c : in) {
        val = (val<<8) + c;
        valb += 8;
        while (valb>=0) {
            out.push_back(base64_encode_b[(val>>valb)&0x3F]);
            valb-=6;
        }
    }
    if (valb>-6) out.push_back(base64_encode_b[((val<<8)>>(valb+8))&0x3F]);
    while (out.size()%4) out.push_back('=');
    return out;
}

std::string TGoogleAuthCpp::url_encode(const std::string &value) {
    std::ostringstream escaped;
    escaped.fill('0');
    escaped << std::hex;

    for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
        std::string::value_type c = (*i);

        // Keep alphanumeric and other accepted characters intact
        if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
            escaped << c;
            continue;
        }

        // Any other characters are percent-encoded
        escaped << std::uppercase;
        escaped << '%' << std::setw(2) << int((unsigned char) c);
        escaped << std::nouppercase;
    }

    return escaped.str();
}

int TGoogleAuthCpp::createRequest() {
    if (!serviceAccountExists) return 0;
    json jwt_header;
    json jwt_claim_set;
    std::time_t t = std::time(NULL);

    // Create jwt header
    jwt_header["alg"] = "RS256";
    jwt_header["typ"] = "JWT";

    // Create jwt claim set
    jwt_claim_set["iss"] = serviceAccountJSON["client_email"]; /* service account email address */
    jwt_claim_set["scope"] = "https://www.googleapis.com/auth/androidpublisher" /* scope of requested access token */;
    jwt_claim_set["aud"] = serviceAccountJSON["token_uri"]; /* intended target of the assertion for an access token */
    jwt_claim_set["iat"] = t; /* issued time */
    jwt_claim_set["exp"] = t+3600; /* expire time*/

    // web token
    std::stringstream jwt_ss;

    // header
    jwt_ss << base64_encode(jwt_header.dump());
    jwt_ss << ".";

    // claim set
    jwt_ss << base64_encode(jwt_claim_set.dump());

    // signature
    std::string signed_msg = signMessage(serviceAccountJSON["private_key"], jwt_ss.str());

    jwt_ss << "." << signed_msg;

    std::stringstream post_body_ss;
    post_body_ss << "curl -d '";
    post_body_ss << "grant_type=" << url_encode("urn:ietf:params:oauth:grant-type:jwt-bearer");
    post_body_ss << "&assertion=" << url_encode(jwt_ss.str());
    post_body_ss << "' https://oauth2.googleapis.com/token";
    std::string post_body = post_body_ss.str();
    std::cout << post_body << std::endl;

    return 1;
}

int main() {
    TGoogleAuthCpp auth;
    int res = auth.createRequest();
}
nickeyzzz
  • 355
  • 4
  • 13
  • While this code may solve the question, [including an explanation](//meta.stackexchange.com/q/114762) of how and why this solves the problem would really help to improve the quality of your post, and probably result in more up-votes. Remember that you are answering the question for readers in the future, not just the person asking now. Please [edit] your answer to add explanations and give an indication of what limitations and assumptions apply. [From Review](/review/first-posts/26249142) – double-beep May 29 '20 at 09:44
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/26264591) – Gaurav Roy May 29 '20 at 12:07
  • @GauravRoy What link? – Ardent Coder May 29 '20 at 15:43