302 lines
11 KiB
C++
302 lines
11 KiB
C++
#include <cstring>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "tee/common/error.h"
|
|
#include "tee/common/log.h"
|
|
#include "tee/common/type.h"
|
|
|
|
#include "tee/ra_ias.h"
|
|
#include "tee/ra_json.h"
|
|
|
|
// use cppcodec/base64
|
|
#include "cppcodec/base64_rfc4648.hpp"
|
|
using base64 = cppcodec::base64_rfc4648;
|
|
|
|
namespace ra {
|
|
namespace occlum {
|
|
|
|
constexpr char kStrEpidPseudonym[] = "epidPseudonym";
|
|
constexpr char kStrQuoteStatus[] = "isvEnclaveQuoteStatus";
|
|
constexpr char kStrPlatform[] = "platformInfoBlob";
|
|
constexpr char kStrQuoteBody[] = "isvEnclaveQuoteBody";
|
|
constexpr char kStrHeaderSig[] = "x-iasreport-signature:";
|
|
constexpr char kStrHeaderSigAk[] = "X-IASReport-Signature:";
|
|
constexpr char kStrHeaderCa[] = "x-iasreport-signing-certificate:";
|
|
constexpr char kStrHeaderCaAk[] = "X-IASReport-Signing-Certificate:";
|
|
constexpr char kStrHeaderAdvisoryUrl[] = "advisory-url:";
|
|
constexpr char kStrHeaderAdvisoryIDs[] = "advisory-ids:";
|
|
|
|
static std::string GetHeaderValue(const char* header, const char* name) {
|
|
std::string header_str = header;
|
|
std::string ending("\n\r");
|
|
|
|
// Name: value\r\n
|
|
std::size_t pos_start = header_str.find_first_of(" ");
|
|
std::size_t pos_end = header_str.find_first_of("\r\n");
|
|
if ((pos_start != std::string::npos) && (pos_end != std::string::npos)) {
|
|
return header_str.substr(pos_start + 1, pos_end - pos_start - 1);
|
|
} else {
|
|
return std::string("");
|
|
}
|
|
}
|
|
|
|
static size_t ParseSigrlResponseBody(const void* contents, size_t size,
|
|
size_t nmemb, void* response) {
|
|
size_t content_length = size * nmemb;
|
|
RaIasSigrl* sigrl = RCAST(RaIasSigrl*, response);
|
|
|
|
if (content_length == 0) {
|
|
sigrl->b64_sigrl.clear();
|
|
TEE_LOG_DEBUG("GetSigRL: Empty");
|
|
} else {
|
|
sigrl->b64_sigrl.assign(RCAST(const char*, contents), content_length);
|
|
TEE_LOG_DEBUG("GetSigRL: %s", sigrl->b64_sigrl.c_str());
|
|
}
|
|
return content_length;
|
|
}
|
|
|
|
static size_t ParseSigrlResponseHeader(const void* contents, size_t size,
|
|
size_t nmemb, void* response) {
|
|
size_t len = size * nmemb;
|
|
const char* header = RCAST(const char*, contents);
|
|
|
|
TEE_LOG_DEBUG("IAS Get SigRL %s", header);
|
|
return len;
|
|
}
|
|
|
|
static size_t ParseReportResponseBody(const void* contents, size_t size,
|
|
size_t nmemb, void* response) {
|
|
const char* body = RCAST(const char*, contents);
|
|
size_t content_length = size * nmemb;
|
|
RaIasReport* report = RCAST(RaIasReport*, response);
|
|
|
|
// The json response body maybe will be splited into two times
|
|
report->mutable_response_body()->append(body, content_length);
|
|
|
|
rapidjson::Document doc;
|
|
if (!doc.Parse(report->response_body().data()).HasParseError()) {
|
|
report->set_epid_pseudonym(JsonConfig::GetStr(doc, kStrEpidPseudonym));
|
|
report->set_quote_status(JsonConfig::GetStr(doc, kStrQuoteStatus));
|
|
report->set_b16_platform_info_blob(JsonConfig::GetStr(doc, kStrPlatform));
|
|
report->set_b64_quote_body(JsonConfig::GetStr(doc, kStrQuoteBody));
|
|
} else if (body[content_length - 1] == '}') {
|
|
TEE_LOG_ERROR("Fail to parse report response body");
|
|
}
|
|
|
|
return content_length;
|
|
}
|
|
|
|
static size_t ParseReportResponseHeader(const void* contents, size_t size,
|
|
size_t nmemb, void* response) {
|
|
size_t len = size * nmemb;
|
|
const char* header = RCAST(const char*, contents);
|
|
RaIasReport* report = RCAST(RaIasReport*, response);
|
|
|
|
if (strncmp(header, kStrHeaderSig, strlen(kStrHeaderSig)) == 0) {
|
|
report->set_b64_signature(GetHeaderValue(header, kStrHeaderSig));
|
|
} else if (strncmp(header, kStrHeaderSigAk, strlen(kStrHeaderSigAk)) == 0) {
|
|
report->set_b64_signature(GetHeaderValue(header, kStrHeaderSigAk));
|
|
} else if (strncmp(header, kStrHeaderCa, strlen(kStrHeaderCa)) == 0) {
|
|
report->set_signing_cert(GetHeaderValue(header, kStrHeaderCa));
|
|
} else if (strncmp(header, kStrHeaderCaAk, strlen(kStrHeaderCaAk)) == 0) {
|
|
report->set_signing_cert(GetHeaderValue(header, kStrHeaderCaAk));
|
|
} else if (strncmp(header, kStrHeaderAdvisoryUrl,
|
|
strlen(kStrHeaderAdvisoryUrl)) == 0) {
|
|
report->set_advisory_url(GetHeaderValue(header, kStrHeaderAdvisoryUrl));
|
|
} else if (strncmp(header, kStrHeaderAdvisoryIDs,
|
|
strlen(kStrHeaderAdvisoryIDs)) == 0) {
|
|
report->set_advisory_ids(GetHeaderValue(header, kStrHeaderAdvisoryIDs));
|
|
}
|
|
return len;
|
|
}
|
|
|
|
std::mutex RaIasClient::init_mutex_;
|
|
|
|
void RaIasClient::InitIasConnection(const std::string& endpoint) {
|
|
if (endpoint.empty()) {
|
|
curl_ = NULL;
|
|
return;
|
|
}
|
|
|
|
// curl_global_init is not multithreads safe function. It's suggested to
|
|
// call it in main thread. Here we just add lock to make sure safety, but
|
|
// don't consider the performance, as multithreads is not common usecase.
|
|
{
|
|
std::lock_guard<std::mutex> lock(init_mutex_);
|
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
}
|
|
|
|
curl_ = curl_easy_init();
|
|
if (!curl_) {
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
/* set libcurl verbose */
|
|
curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1L);
|
|
#endif
|
|
|
|
/* set the common header */
|
|
headers_ = curl_slist_append(NULL, "Accept: application/json");
|
|
headers_ = curl_slist_append(headers_, "Content-Type: application/json");
|
|
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers_);
|
|
curl_easy_setopt(curl_, CURLOPT_USERAGENT, "sgx-sp/1.0");
|
|
|
|
/* set commom option */
|
|
curl_easy_setopt(curl_, CURLOPT_FORBID_REUSE, 1L);
|
|
curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L);
|
|
curl_easy_setopt(curl_, CURLOPT_TIMEOUT, 60L);
|
|
curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, 10L);
|
|
curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYPEER, 0L);
|
|
curl_easy_setopt(curl_, CURLOPT_SSL_VERIFYHOST, 0L);
|
|
|
|
server_endpoint_ = endpoint;
|
|
}
|
|
|
|
RaIasClient::RaIasClient(const RaIasServerCfg& ias_server) {
|
|
// Configure the other normal settings firstly.
|
|
InitIasConnection(ias_server.endpoint);
|
|
|
|
// Check the HTTPS server addr and set the cert/key settings
|
|
// Or use the Access key authentication
|
|
std::string header_access_key = "Ocp-Apim-Subscription-Key: ";
|
|
if (!ias_server.accesskey.empty()) {
|
|
header_access_key += ias_server.accesskey;
|
|
headers_ = curl_slist_append(headers_, header_access_key.c_str());
|
|
}
|
|
|
|
if (curl_ && (ias_server.endpoint.find("https://") != std::string::npos) &&
|
|
(ias_server.accesskey.empty())) {
|
|
const char* ias_cert_key_type = "PEM";
|
|
TEE_LOG_DEBUG("IAS cert: %s", ias_server.cert.c_str());
|
|
TEE_LOG_DEBUG("IAS key: %s", ias_server.key.c_str());
|
|
|
|
curl_easy_setopt(curl_, CURLOPT_SSLCERT, ias_server.cert.c_str());
|
|
curl_easy_setopt(curl_, CURLOPT_SSLKEY, ias_server.key.c_str());
|
|
curl_easy_setopt(curl_, CURLOPT_SSLCERTTYPE, ias_cert_key_type);
|
|
curl_easy_setopt(curl_, CURLOPT_SSLKEYTYPE, ias_cert_key_type);
|
|
}
|
|
}
|
|
|
|
RaIasClient::~RaIasClient() {
|
|
if (headers_) {
|
|
curl_slist_free_all(headers_);
|
|
}
|
|
if (curl_) {
|
|
curl_easy_cleanup(curl_);
|
|
}
|
|
|
|
// add lock for multi-threads safety
|
|
{
|
|
std::lock_guard<std::mutex> lock(init_mutex_);
|
|
curl_global_cleanup();
|
|
}
|
|
}
|
|
|
|
TeeErrorCode RaIasClient::GetSigRL(const sgx_epid_group_id_t& gid,
|
|
std::string* sigrl) {
|
|
if (!curl_) {
|
|
TEE_LOG_ERROR("IAS client is not initialized");
|
|
return TEE_ERROR_IAS_CLIENT_INIT;
|
|
}
|
|
|
|
/* Set the URL */
|
|
std::string url = server_endpoint_ + "/sigrl/";
|
|
std::vector<char> tmp_gid_vec(sizeof(sgx_epid_group_id_t) * 2 + 1, 0);
|
|
snprintf(tmp_gid_vec.data(), tmp_gid_vec.size(), "%02X%02X%02X%02X", gid[3],
|
|
gid[2], gid[1], gid[0]);
|
|
url += std::string(tmp_gid_vec.data());
|
|
TEE_LOG_DEBUG("URL: %s", url.c_str());
|
|
curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
|
|
|
|
/* Set the sigrl request header and body handler function and data */
|
|
RaIasSigrl ias_sigrl;
|
|
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, ParseSigrlResponseBody);
|
|
curl_easy_setopt(curl_, CURLOPT_HEADERFUNCTION, ParseSigrlResponseHeader);
|
|
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, RCAST(void*, &ias_sigrl));
|
|
curl_easy_setopt(curl_, CURLOPT_WRITEHEADER, RCAST(void*, &ias_sigrl));
|
|
|
|
CURLcode rc = curl_easy_perform(curl_);
|
|
if (rc != CURLE_OK) {
|
|
TEE_LOG_ERROR("Fail to connect server: %s\n", curl_easy_strerror(rc));
|
|
return TEE_ERROR_IAS_CLIENT_CONNECT;
|
|
}
|
|
|
|
if (!ias_sigrl.b64_sigrl.empty()) {
|
|
std::vector<uint8_t> sigrl_vec;
|
|
try {
|
|
sigrl_vec = base64::decode(ias_sigrl.b64_sigrl);
|
|
} catch (std::exception& e) {
|
|
TEE_LOG_ERROR("Cannot decode base64 sigrl: %s", e.what());
|
|
return TEE_ERROR_IAS_CLIENT_GETSIGRL;
|
|
}
|
|
sigrl->assign(RCAST(const char*, sigrl_vec.data()), sigrl_vec.size());
|
|
}
|
|
return TEE_SUCCESS;
|
|
}
|
|
|
|
TeeErrorCode RaIasClient::FetchReport(const std::string& quote,
|
|
RaIasReport* ias_report) {
|
|
/* should not be empty is not to use cache */
|
|
if (quote.empty()) {
|
|
TEE_LOG_ERROR("Invalid base64 quote value");
|
|
return TEE_ERROR_PARAMETERS;
|
|
}
|
|
|
|
if (!curl_) {
|
|
TEE_LOG_ERROR("IAS client is not initialized!");
|
|
return TEE_ERROR_IAS_CLIENT_INIT;
|
|
}
|
|
|
|
/* Set the report url */
|
|
std::string url = server_endpoint_ + "/report";
|
|
TEE_LOG_DEBUG("URL: %s", url.c_str());
|
|
curl_easy_setopt(curl_, CURLOPT_URL, url.c_str());
|
|
|
|
/* Set the post data */
|
|
TEE_LOG_DEBUG("Quote length: %ld", quote.length());
|
|
std::string b64_quote = base64::encode(RCAST(const char*, quote.c_str()),
|
|
SCAST(size_t, quote.length()));
|
|
TEE_LOG_DEBUG("QUTEO[%lu]: %s", b64_quote.length(), b64_quote.c_str());
|
|
std::string post_data = "{\"isvEnclaveQuote\": \"";
|
|
post_data += b64_quote;
|
|
post_data += "\"}";
|
|
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, post_data.c_str());
|
|
|
|
/* Set the report request header and body handler function and data */
|
|
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, ParseReportResponseBody);
|
|
curl_easy_setopt(curl_, CURLOPT_HEADERFUNCTION, ParseReportResponseHeader);
|
|
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, RCAST(void*, ias_report));
|
|
curl_easy_setopt(curl_, CURLOPT_WRITEHEADER, RCAST(void*, ias_report));
|
|
|
|
CURLcode rc = curl_easy_perform(curl_);
|
|
if (rc != CURLE_OK) {
|
|
TEE_LOG_ERROR("Fail to connect server: %s\n", curl_easy_strerror(rc));
|
|
return TEE_ERROR_IAS_CLIENT_CONNECT;
|
|
}
|
|
|
|
/* deal with the escaped certificates */
|
|
std::string signing_cert = ias_report->signing_cert();
|
|
if (!signing_cert.empty()) {
|
|
int unescape_len = 0;
|
|
char* p_unescape = curl_easy_unescape(curl_, signing_cert.data(),
|
|
signing_cert.length(), &unescape_len);
|
|
if (p_unescape && unescape_len) {
|
|
ias_report->set_signing_cert(p_unescape, unescape_len);
|
|
curl_free(p_unescape);
|
|
} else {
|
|
TEE_LOG_ERROR("Fail to convert the escaped certificate in response.");
|
|
return TEE_ERROR_IAS_CLIENT_UNESCAPE;
|
|
}
|
|
} else {
|
|
TEE_LOG_ERROR("Fail to get quote report from IAS");
|
|
return TEE_ERROR_IAS_CLIENT_GETREPORT;
|
|
}
|
|
|
|
return TEE_SUCCESS;
|
|
}
|
|
|
|
} // namespace occlum
|
|
} // namespace ra
|