使用 openssl 和 c++ 生成 OAuth 访问令牌

Generate an OAuth access token with openssl and c++

我需要为 google 的 OAuth 身份验证服务获取访问令牌(用于服务帐户)。我尝试了几件事,并在网上进行了很多研究,但没有成功。

基本上我都关注了https://developers.google.com/accounts/docs/OAuth2ServiceAccount

我做了什么(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;
}

我收到的都是

{
  "error" : "invalid_grant"
}

所以,如果有人能指出我正确的方向,那就太好了。 如果有人可以指出我通过从 openssl 命令手动生成 jwt 请求来使它正常工作,它也会有所帮助。

我正在使用 VS2013

我发现我的错误 - 只是一个错字:(

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

需要

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

代码有效并生成有效的令牌请求。

我已经制作了一个 class 用于在 C++ 上进行身份验证,将其保留在这里,可能有人需要它。

// 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();
}