replay: replace HttpRequest with libcurl for accessing comma API (#33851)
Replay: Replace HttpRequest with libcurl for accessing comma apipull/34003/head
parent
d050e0c2ac
commit
f5dc1d08c9
5 changed files with 213 additions and 33 deletions
@ -0,0 +1,162 @@ |
|||||||
|
|
||||||
|
#include "tools/replay/api.h" |
||||||
|
|
||||||
|
#include <openssl/pem.h> |
||||||
|
#include <openssl/rsa.h> |
||||||
|
#include <openssl/evp.h> |
||||||
|
#include <openssl/sha.h> |
||||||
|
|
||||||
|
#include <cassert> |
||||||
|
#include <chrono> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
#include "common/params.h" |
||||||
|
#include "common/version.h" |
||||||
|
#include "system/hardware/hw.h" |
||||||
|
|
||||||
|
namespace CommaApi2 { |
||||||
|
|
||||||
|
// Base64 URL-safe character set (uses '-' and '_' instead of '+' and '/')
|
||||||
|
static const std::string base64url_chars = |
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
||||||
|
"abcdefghijklmnopqrstuvwxyz" |
||||||
|
"0123456789-_"; |
||||||
|
|
||||||
|
std::string base64url_encode(const std::string &in) { |
||||||
|
std::string out; |
||||||
|
int val = 0, valb = -6; |
||||||
|
for (unsigned char c : in) { |
||||||
|
val = (val << 8) + c; |
||||||
|
valb += 8; |
||||||
|
while (valb >= 0) { |
||||||
|
out.push_back(base64url_chars[(val >> valb) & 0x3F]); |
||||||
|
valb -= 6; |
||||||
|
} |
||||||
|
} |
||||||
|
if (valb > -6) { |
||||||
|
out.push_back(base64url_chars[((val << 8) >> (valb + 8)) & 0x3F]); |
||||||
|
} |
||||||
|
|
||||||
|
return out; |
||||||
|
} |
||||||
|
|
||||||
|
EVP_PKEY *get_rsa_private_key() { |
||||||
|
static std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> rsa_private(nullptr, EVP_PKEY_free); |
||||||
|
if (!rsa_private) { |
||||||
|
FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); |
||||||
|
if (!fp) { |
||||||
|
std::cerr << "No RSA private key found, please run manager.py or registration.py" << std::endl; |
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
rsa_private.reset(PEM_read_PrivateKey(fp, NULL, NULL, NULL)); |
||||||
|
fclose(fp); |
||||||
|
} |
||||||
|
return rsa_private.get(); |
||||||
|
} |
||||||
|
|
||||||
|
std::string rsa_sign(const std::string &data) { |
||||||
|
EVP_PKEY *private_key = get_rsa_private_key(); |
||||||
|
if (!private_key) return {}; |
||||||
|
|
||||||
|
EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); |
||||||
|
assert(mdctx != nullptr); |
||||||
|
|
||||||
|
std::vector<uint8_t> sig(EVP_PKEY_size(private_key)); |
||||||
|
uint32_t sig_len; |
||||||
|
|
||||||
|
EVP_SignInit(mdctx, EVP_sha256()); |
||||||
|
EVP_SignUpdate(mdctx, data.data(), data.size()); |
||||||
|
int ret = EVP_SignFinal(mdctx, sig.data(), &sig_len, private_key); |
||||||
|
|
||||||
|
EVP_MD_CTX_free(mdctx); |
||||||
|
|
||||||
|
assert(ret == 1); |
||||||
|
assert(sig.size() == sig_len); |
||||||
|
return std::string(sig.begin(), sig.begin() + sig_len); |
||||||
|
} |
||||||
|
|
||||||
|
std::string create_jwt(const json11::Json &extra, int exp_time) { |
||||||
|
int now = std::chrono::seconds(std::time(nullptr)).count(); |
||||||
|
std::string dongle_id = Params().get("DongleId"); |
||||||
|
|
||||||
|
// Create header and payload
|
||||||
|
json11::Json header = json11::Json::object{{"alg", "RS256"}}; |
||||||
|
auto payload = json11::Json::object{ |
||||||
|
{"identity", dongle_id}, |
||||||
|
{"iat", now}, |
||||||
|
{"nbf", now}, |
||||||
|
{"exp", now + exp_time}, |
||||||
|
}; |
||||||
|
// Merge extra payload
|
||||||
|
for (const auto &item : extra.object_items()) { |
||||||
|
payload[item.first] = item.second; |
||||||
|
} |
||||||
|
|
||||||
|
// JWT construction
|
||||||
|
std::string jwt = base64url_encode(header.dump()) + '.' + |
||||||
|
base64url_encode(json11::Json(payload).dump()); |
||||||
|
|
||||||
|
// Hash and sign
|
||||||
|
std::string hash(SHA256_DIGEST_LENGTH, '\0'); |
||||||
|
SHA256((uint8_t *)jwt.data(), jwt.size(), (uint8_t *)hash.data()); |
||||||
|
std::string signature = rsa_sign(hash); |
||||||
|
|
||||||
|
return jwt + "." + base64url_encode(signature); |
||||||
|
} |
||||||
|
|
||||||
|
std::string create_token(bool use_jwt, const json11::Json &payloads, int expiry) { |
||||||
|
if (use_jwt) { |
||||||
|
return create_jwt(payloads, expiry); |
||||||
|
} |
||||||
|
|
||||||
|
std::string token_json = util::read_file(util::getenv("HOME") + "/.comma/auth.json"); |
||||||
|
std::string err; |
||||||
|
auto json = json11::Json::parse(token_json, err); |
||||||
|
if (!err.empty()) { |
||||||
|
std::cerr << "Error parsing auth.json " << err << std::endl; |
||||||
|
return ""; |
||||||
|
} |
||||||
|
return json["access_token"].string_value(); |
||||||
|
} |
||||||
|
|
||||||
|
std::string httpGet(const std::string &url, long *response_code) { |
||||||
|
CURL *curl = curl_easy_init(); |
||||||
|
assert(curl); |
||||||
|
|
||||||
|
std::string readBuffer; |
||||||
|
const std::string token = CommaApi2::create_token(!Hardware::PC()); |
||||||
|
|
||||||
|
// Set up the lambda for the write callback
|
||||||
|
// The '+' makes the lambda non-capturing, allowing it to be used as a C function pointer
|
||||||
|
auto writeCallback = +[](char *contents, size_t size, size_t nmemb, std::string *userp) ->size_t{ |
||||||
|
size_t totalSize = size * nmemb; |
||||||
|
userp->append((char *)contents, totalSize); |
||||||
|
return totalSize; |
||||||
|
}; |
||||||
|
|
||||||
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); |
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); |
||||||
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); |
||||||
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); |
||||||
|
|
||||||
|
// Handle headers
|
||||||
|
struct curl_slist *headers = nullptr; |
||||||
|
headers = curl_slist_append(headers, "User-Agent: openpilot-" COMMA_VERSION); |
||||||
|
if (!token.empty()) { |
||||||
|
headers = curl_slist_append(headers, ("Authorization: JWT " + token).c_str()); |
||||||
|
} |
||||||
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); |
||||||
|
|
||||||
|
CURLcode res = curl_easy_perform(curl); |
||||||
|
|
||||||
|
if (response_code) { |
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, response_code); |
||||||
|
} |
||||||
|
|
||||||
|
curl_slist_free_all(headers); |
||||||
|
curl_easy_cleanup(curl); |
||||||
|
|
||||||
|
return res == CURLE_OK ? readBuffer : std::string{}; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace CommaApi
|
@ -0,0 +1,15 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <curl/curl.h> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "common/util.h" |
||||||
|
#include "third_party/json11/json11.hpp" |
||||||
|
|
||||||
|
namespace CommaApi2 { |
||||||
|
|
||||||
|
const std::string BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str(); |
||||||
|
std::string create_token(bool use_jwt, const json11::Json& payloads = {}, int expiry = 3600); |
||||||
|
std::string httpGet(const std::string &url, long *response_code = nullptr); |
||||||
|
|
||||||
|
} // namespace CommaApi2
|
Loading…
Reference in new issue