CCF
Loading...
Searching...
No Matches
quote_endorsements_client.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
7#include "http/curl.h"
8
9#include <curl/curl.h>
10
11namespace ccf
12{
14 std::function<void(std::vector<uint8_t>&& endorsements)>;
16
17 static inline size_t max_retries_count(const Server& server)
18 {
19 // Each server should contain at least one endpoint definition
20 if (server.empty())
21 {
22 throw std::logic_error(
23 "No endpoints defined in SNP attestation collateral server");
24 }
25
26 // If multiple endpoints are defined, the max_retries_count of the first
27 // if the maximum number of retries for the server.
28 return server.front().max_retries_count;
29 }
30
31 // Resilient client to fetch attestation report endorsement certificate.
33 : public std::enable_shared_from_this<QuoteEndorsementsClient>
34 {
35 private:
36 using EndpointInfo =
38
39 // Resend request after this interval if no response was received from
40 // remote server
41 static constexpr size_t server_connection_timeout_s = 3;
42
43 std::shared_ptr<RPCSessions> rpcsessions;
44
47
48 std::vector<uint8_t> endorsements_pem;
49
50 ccf::pal::Mutex lock;
51
52 // Uniquely identify each received request. We assume that this client sends
53 // requests in series, after receiving the response to each one or after a
54 // long timeout.
55 size_t last_submitted_request_id = 0;
56 bool has_completed = false;
57 size_t server_retries_count = 0;
58
59 struct QuoteEndorsementsClientMsg
60 {
61 QuoteEndorsementsClientMsg(
62 const std::shared_ptr<QuoteEndorsementsClient>& self_,
63 const Server& server_) :
64 self(self_),
65 server(server_)
66 {}
67
68 std::shared_ptr<QuoteEndorsementsClient> self;
69 Server server;
70 };
71
72 struct QuoteEndorsementsClientTimeoutMsg
73 {
74 QuoteEndorsementsClientTimeoutMsg(
75 const std::shared_ptr<QuoteEndorsementsClient>& self_,
76 const EndpointInfo& endpoint_,
77 size_t request_id_) :
78 self(self_),
79 endpoint(endpoint_),
80 request_id(request_id_)
81 {}
82
83 std::shared_ptr<QuoteEndorsementsClient> self;
84 EndpointInfo endpoint;
85 size_t request_id;
86 };
87
88 void handle_success_response(
89 std::vector<uint8_t>&& data, const EndpointInfo& response_endpoint)
90 {
91 // We may receive a response to an in-flight request after having
92 // fetched all endorsements
93 auto& server = config.servers.front();
94 if (server.empty())
95 {
96 return;
97 }
98 auto endpoint = server.front();
99 if (has_completed || response_endpoint != endpoint)
100 {
101 return;
102 }
103
104 if (response_endpoint.response_is_der)
105 {
106 auto raw = ccf::crypto::cert_der_to_pem(data).raw();
107 endorsements_pem.insert(endorsements_pem.end(), raw.begin(), raw.end());
108 }
109 else if (response_endpoint.response_is_thim_json)
110 {
111 auto j = nlohmann::json::parse(data);
112 auto vcekCert = j.at("vcekCert").get<std::string>();
113 auto certificateChain = j.at("certificateChain").get<std::string>();
114 endorsements_pem.insert(
115 endorsements_pem.end(), vcekCert.begin(), vcekCert.end());
116 endorsements_pem.insert(
117 endorsements_pem.end(),
118 certificateChain.begin(),
119 certificateChain.end());
120 }
121 else
122 {
123 endorsements_pem.insert(
124 endorsements_pem.end(), data.begin(), data.end());
125 }
126
127 server.pop_front();
128 if (server.empty())
129 {
130 LOG_INFO_FMT("Complete endorsement chain successfully retrieved");
132 "{}", std::string(endorsements_pem.begin(), endorsements_pem.end()));
133 has_completed = true;
134 done_cb(std::move(endorsements_pem));
135 }
136 else
137 {
138 fetch(server);
139 }
140 }
141
142 std::string get_formatted_query(
143 const std::map<std::string, std::string> params) const
144 {
145 std::string formatted_query;
146 bool first = true;
147 for (const auto& it : params)
148 {
149 formatted_query +=
150 fmt::format("{}{}={}", (first ? '?' : '&'), it.first, it.second);
151 first = false;
152 }
153 return formatted_query;
154 }
155
156 void fetch(const Server& server)
157 {
158 auto request_id = ++last_submitted_request_id;
159 auto endpoint = server.front();
160
161 curl::UniqueCURL curl_handle;
162
163 // set curl get
164 curl_handle.set_opt(CURLOPT_HTTPGET, 1L);
165
166 auto url = fmt::format(
167 "{}://{}:{}{}{}",
168 endpoint.tls ? "https" : "http",
169 endpoint.host,
170 endpoint.port,
171 endpoint.uri,
172 get_formatted_query(endpoint.params));
173
174 if (endpoint.tls)
175 {
176 // Note: server CA is not checked here as this client is not sending
177 // private data. If the server was malicious and the certificate chain
178 // was bogus, the verification of the endorsement of the quote would
179 // fail anyway.
180 curl_handle.set_opt(CURLOPT_SSL_VERIFYHOST, 0L);
181 curl_handle.set_opt(CURLOPT_SSL_VERIFYPEER, 0L);
182 curl_handle.set_opt(CURLOPT_SSL_VERIFYSTATUS, 0L);
183 }
184
185 auto headers = ccf::curl::UniqueSlist();
186 for (auto const& [k, v] : endpoint.headers)
187 {
188 headers.append(k, v);
189 }
190 headers.append(http::headers::HOST, endpoint.host);
191
192 auto response_callback = ([this, server, endpoint](
193 curl::CurlRequest& request,
194 CURLcode curl_response,
195 long status_code) {
196 std::lock_guard<ccf::pal::Mutex> guard(this->lock);
197 auto* response_body = request.get_response_body();
198 auto& response_headers = request.get_response_headers();
199
200 if (curl_response == CURLE_OK && status_code == HTTP_STATUS_OK)
201 {
203 "Successfully retrieved endorsements for attestation report: "
204 "{} bytes",
205 response_body->buffer.size());
206
207 handle_success_response(std::move(response_body->buffer), endpoint);
208 return;
209 }
210
212 "Error fetching endorsements for attestation report: {} ({}) {}",
213 curl_easy_strerror(curl_response),
214 curl_response,
215 status_code);
216 if (
217 curl_response == CURLE_OK &&
218 status_code == HTTP_STATUS_TOO_MANY_REQUESTS)
219 {
220 constexpr size_t default_retry_after_s = 3;
221 size_t retry_after_s = default_retry_after_s;
222 auto h = response_headers.data.find(http::headers::RETRY_AFTER);
223 if (h != response_headers.data.end())
224 {
225 const auto& retry_after_value = h->second;
226 // If value is invalid, retry_after_s is unchanged
227 std::from_chars(
228 retry_after_value.data(),
229 retry_after_value.data() + retry_after_value.size(),
230 retry_after_s);
231 }
232
233 auto msg =
234 std::make_unique<::threading::Tmsg<QuoteEndorsementsClientMsg>>(
235 [](std::unique_ptr<::threading::Tmsg<QuoteEndorsementsClientMsg>>
236 msg) { msg->data.self->fetch(msg->data.server); },
237 shared_from_this(),
238 server);
239
241 "{} endorsements endpoint had too many requests. Retrying "
242 "in {}s",
243 endpoint,
244 retry_after_s);
245
247 std::move(msg), std::chrono::milliseconds(retry_after_s * 1000));
248 }
249 return;
250 });
251
252 auto request = std::make_unique<curl::CurlRequest>(
253 std::move(curl_handle),
254 HTTP_GET,
255 std::move(url),
256 std::move(headers),
257 nullptr,
258 std::make_unique<ccf::curl::ResponseBody>(
259 endpoint.max_client_response_size),
260 std::move(response_callback));
261
262 // Start watchdog to send request on new server if it is unresponsive
263 auto msg = std::make_unique<
265 [](std::unique_ptr<::threading::Tmsg<QuoteEndorsementsClientTimeoutMsg>>
266 msg) {
267 std::lock_guard<ccf::pal::Mutex> guard(msg->data.self->lock);
268 if (msg->data.self->has_completed)
269 {
270 return;
271 }
272 if (msg->data.request_id >= msg->data.self->last_submitted_request_id)
273 {
274 auto& servers = msg->data.self->config.servers;
275 // Should always contain at least one server,
276 // installed by ccf::pal::make_endorsement_endpoint_configuration()
277 if (servers.empty())
278 {
279 throw std::logic_error(
280 "No server specified to fetch endorsements");
281 }
282
283 msg->data.self->server_retries_count++;
284 if (
285 msg->data.self->server_retries_count >=
286 max_retries_count(servers.front()))
287 {
288 if (servers.size() > 1)
289 {
290 // Move on to next server if we have passed max retries count
291 servers.pop_front();
292 }
293 else
294 {
295 auto& server = servers.front();
297 "Giving up retrying fetching attestation endorsements from "
298 "{} after {} attempts",
299 server.front().host,
300 server.front().max_retries_count);
302 "Timed out fetching attestation endorsements from all "
303 "configured servers");
304 return;
305 }
306 }
307
308 msg->data.self->fetch(servers.front());
309 }
310 },
311 shared_from_this(),
312 endpoint,
313 request_id);
314
316 std::move(msg),
317 std::chrono::milliseconds(server_connection_timeout_s * 1000));
318
320 "Fetching endorsements for attestation report at {}",
321 request->get_url());
322
324 std::move(request));
325 }
326
327 public:
329 const std::shared_ptr<RPCSessions>& rpcsessions_,
332 rpcsessions(rpcsessions_),
333 config(config_),
334 done_cb(cb) {};
335
337 {
338 std::lock_guard<ccf::pal::Mutex> guard(this->lock);
339 auto const& server = config.servers.front();
340 if (server.empty())
341 {
342 throw std::logic_error("No server specified to fetch endorsements");
343 }
344 fetch(server);
345 }
346 };
347}
Definition quote_endorsements_client.h:34
void fetch_endorsements()
Definition quote_endorsements_client.h:336
QuoteEndorsementsClient(const std::shared_ptr< RPCSessions > &rpcsessions_, const pal::snp::EndorsementEndpointsConfiguration &config_, QuoteEndorsementsFetchedCallback cb)
Definition quote_endorsements_client.h:328
std::vector< uint8_t > raw() const
Definition pem.h:71
Definition curl.h:360
static CurlmLibuvContext & get_instance()
Definition curl.h:966
Definition curl.h:53
void set_opt(auto option, auto value)
Definition curl.h:93
Definition curl.h:135
static ThreadMessaging & instance()
Definition thread_messaging.h:283
TaskQueue::TimerEntry add_task_after(std::unique_ptr< Tmsg< Payload > > msg, std::chrono::milliseconds ms)
Definition thread_messaging.h:326
#define LOG_INFO_FMT
Definition logger.h:362
#define LOG_DEBUG_FMT
Definition logger.h:357
#define LOG_FAIL_FMT
Definition logger.h:363
ccf::crypto::Pem cert_der_to_pem(const std::vector< uint8_t > &der)
Definition verifier.cpp:33
std::mutex Mutex
Definition locking.h:12
Definition app_interface.h:14
pal::snp::EndorsementEndpointsConfiguration::Server Server
Definition quote_endorsements_client.h:15
std::function< void(std::vector< uint8_t > &&endorsements)> QuoteEndorsementsFetchedCallback
Definition quote_endorsements_client.h:14
Definition attestation_sev_snp_endorsements.h:41
bool response_is_der
Definition attestation_sev_snp_endorsements.h:46
bool response_is_thim_json
Definition attestation_sev_snp_endorsements.h:47
Definition attestation_sev_snp_endorsements.h:39
std::list< Server > servers
Definition attestation_sev_snp_endorsements.h:59
std::list< EndpointInfo > Server
Definition attestation_sev_snp_endorsements.h:55
Definition thread_messaging.h:27