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
6#include "ccf/http_consts.h"
9#include "ccf/pal/locking.h"
10#include "http/curl.h"
11#include "tasks/basic_task.h"
12#include "tasks/task.h"
13#include "tasks/task_system.h"
14
15#include <curl/curl.h>
16
17namespace ccf
18{
20 std::function<void(std::vector<uint8_t>&& endorsements)>;
22
23 static inline size_t max_retries_count(const Server& server)
24 {
25 // Each server should contain at least one endpoint definition
26 if (server.empty())
27 {
28 throw std::logic_error(
29 "No endpoints defined in SNP attestation collateral server");
30 }
31
32 // If multiple endpoints are defined, the max_retries_count of the first
33 // if the maximum number of retries for the server.
34 return server.front().max_retries_count;
35 }
36
37 // Resilient client to fetch attestation report endorsement certificate.
39 : public std::enable_shared_from_this<QuoteEndorsementsClient>
40 {
41 private:
42 using EndpointInfo =
44
45 // Resend request after this interval if no response was received from
46 // remote server
47 static constexpr size_t server_connection_timeout_s = 3;
48 static constexpr size_t server_response_timeout_s = 3;
49
52
53 std::vector<uint8_t> endorsements_pem;
54
55 ccf::pal::Mutex lock;
56
57 // Iteration variables
58 std::list<Server> servers;
59 size_t server_retries_count = 0;
60 size_t total_retries_count = 0;
61
62 struct QuoteEndorsementsClientMsg
63 {
64 QuoteEndorsementsClientMsg(
65 std::shared_ptr<QuoteEndorsementsClient> self_, Server server_) :
66 self(std::move(self_)),
67 server(std::move(server_))
68 {}
69
70 std::shared_ptr<QuoteEndorsementsClient> self;
71 Server server;
72 };
73
74 void handle_success_response_unsafe(std::vector<uint8_t>&& data)
75 {
76 auto& server = servers.front();
77 if (server.empty())
78 {
79 return;
80 }
81 auto endpoint = server.front();
82
83 if (endpoint.response_is_der)
84 {
85 auto raw = ccf::crypto::cert_der_to_pem(data).raw();
86 endorsements_pem.insert(endorsements_pem.end(), raw.begin(), raw.end());
87 }
88 else if (endpoint.response_is_thim_json)
89 {
90 auto j = nlohmann::json::parse(data);
91 auto vcekCert = j.at("vcekCert").get<std::string>();
92 auto certificateChain = j.at("certificateChain").get<std::string>();
93 endorsements_pem.insert(
94 endorsements_pem.end(), vcekCert.begin(), vcekCert.end());
95 endorsements_pem.insert(
96 endorsements_pem.end(),
97 certificateChain.begin(),
98 certificateChain.end());
99 }
100 else
101 {
102 endorsements_pem.insert(
103 endorsements_pem.end(), data.begin(), data.end());
104 }
105
106 // advance to the next endpoint
107 server.pop_front();
108
109 if (server.empty())
110 {
111 LOG_INFO_FMT("Complete endorsement chain successfully retrieved");
113 "{}", std::string(endorsements_pem.begin(), endorsements_pem.end()));
114 done_cb(std::move(endorsements_pem));
115 }
116 else
117 {
118 fetch_unsafe();
119 }
120 }
121
122 std::string get_formatted_query(
123 const std::map<std::string, std::string> params) const
124 {
125 std::string formatted_query;
126 bool first = true;
127 for (const auto& it : params)
128 {
129 formatted_query +=
130 fmt::format("{}{}={}", (first ? '?' : '&'), it.first, it.second);
131 first = false;
132 }
133 return formatted_query;
134 }
135
136 void fetch()
137 {
138 std::lock_guard<ccf::pal::Mutex> guard(this->lock);
139 fetch_unsafe();
140 }
141
142 struct HandleResponseTask : public ccf::tasks::BaseTask
143 {
144 std::shared_ptr<QuoteEndorsementsClient> self;
145 std::unique_ptr<curl::CurlRequest> request;
146 CURLcode curl_response;
147 long status_code;
148
149 HandleResponseTask(
150 std::shared_ptr<QuoteEndorsementsClient> self_,
151 std::unique_ptr<curl::CurlRequest>&& request_,
152 CURLcode curl_response_,
153 long status_code_) :
154 self(std::move(self_)),
155 request(std::move(request_)),
156 curl_response(curl_response_),
157 status_code(status_code_)
158 {}
159
160 void do_task_implementation() override
161 {
162 std::lock_guard<ccf::pal::Mutex> guard(self->lock);
163
164 auto* response_body = request->get_response_body();
165 const auto& response_headers = request->get_response_headers();
166
167 if (curl_response == CURLE_OK && status_code == HTTP_STATUS_OK)
168 {
170 "Successfully retrieved endorsements for attestation report: "
171 "{} bytes",
172 response_body->buffer.size());
173
174 self->handle_success_response_unsafe(
175 std::move(response_body->buffer));
176 return;
177 }
178
180 "Error fetching endorsements for attestation report: {} ({}) {}",
181 curl_easy_strerror(curl_response),
182 curl_response,
183 status_code);
184
185 if (
186 self->server_retries_count >=
187 max_retries_count(self->servers.front()))
188 {
189 self->servers.pop_front();
190
191 if (self->servers.empty())
192 {
193 auto servers_tried = std::accumulate(
194 self->config.servers.begin(),
195 self->config.servers.end(),
196 std::string{},
197 [](const std::string& a, const Server& b) {
198 return a + (!a.empty() ? ", " : "") + b.front().host;
199 });
201 "Giving up retrying fetching attestation endorsements from [{}] "
202 "after {} attempts",
203 servers_tried,
204 self->total_retries_count);
206 "Timed out fetching attestation endorsements from all "
207 "configured servers");
208 }
209
210 self->server_retries_count = 0;
211 self->fetch_unsafe();
212 }
213 else
214 {
215 ++self->server_retries_count;
216 ++self->total_retries_count;
217
218 const auto& endpoint = self->servers.front().front();
219
220 constexpr size_t default_retry_after_s = 3;
221 size_t retry_after_s = default_retry_after_s;
222 if (
223 curl_response == CURLE_OK &&
224 status_code == HTTP_STATUS_TOO_MANY_REQUESTS)
225 {
226 auto h = response_headers.find(http::headers::RETRY_AFTER);
227 if (h != response_headers.end())
228 {
229 const auto& retry_after_value = h->second;
230 // If value is invalid, retry_after_s is unchanged
231 std::from_chars(
232 retry_after_value.data(),
233 retry_after_value.data() + retry_after_value.size(),
234 retry_after_s);
235 }
236
238 "{} endorsements endpoint had too many requests. Retrying "
239 "in {}s",
240 endpoint,
241 retry_after_s);
242 }
243 else
244 {
246 "{} endorsements endpoint failed to respond. Retrying "
247 "in {}s",
248 endpoint,
249 retry_after_s);
250 }
251
252 const std::chrono::seconds retry_after(retry_after_s);
253
256 [self = this->self]() { self->fetch(); }),
257 retry_after);
258 }
259 }
260
261 [[nodiscard]] const std::string& get_name() const override
262 {
263 static const std::string name =
264 "QuoteEndorsementsClient::HandleResponseTask";
265 return name;
266 }
267 };
268
269 void fetch_unsafe()
270 {
271 const auto& server = servers.front();
272 const auto& endpoint = server.front();
273
274 curl::UniqueCURL curl_handle;
275
276 // Set curl get
277 curl_handle.set_opt(CURLOPT_HTTPGET, 1L);
278 // If the server does not respond at all within this time timeout
279 curl_handle.set_opt(CURLOPT_CONNECTTIMEOUT, server_connection_timeout_s);
280 // If the server does not completely response within this time timeout
281 curl_handle.set_opt(CURLOPT_TIMEOUT, server_response_timeout_s);
282
283 auto url = fmt::format(
284 "{}://{}:{}{}{}",
285 endpoint.tls ? "https" : "http",
286 endpoint.host,
287 endpoint.port,
288 endpoint.uri,
289 get_formatted_query(endpoint.params));
290
291 if (endpoint.tls)
292 {
293 // Note: server CA is not checked here as this client is not sending
294 // private data. If the server was malicious and the certificate chain
295 // was bogus, the verification of the endorsement of the quote would
296 // fail anyway.
297 curl_handle.set_opt(CURLOPT_SSL_VERIFYHOST, 0L);
298 curl_handle.set_opt(CURLOPT_SSL_VERIFYPEER, 0L);
299 curl_handle.set_opt(CURLOPT_SSL_VERIFYSTATUS, 0L);
300 }
301
302 auto headers = ccf::curl::UniqueSlist();
303 for (auto const& [k, v] : endpoint.headers)
304 {
305 headers.append(k, v);
306 }
307 headers.append(http::headers::HOST, endpoint.host);
308
309 auto response_callback = ([self = shared_from_this()](
310 std::unique_ptr<curl::CurlRequest>&& request,
311 CURLcode curl_response,
312 long status_code) {
313 std::shared_ptr<HandleResponseTask> response_task =
314 std::make_shared<HandleResponseTask>(
315 self, std::move(request), curl_response, status_code);
316 ccf::tasks::add_task(response_task);
317 });
318
319 auto request = std::make_unique<curl::CurlRequest>(
320 std::move(curl_handle),
321 HTTP_GET,
322 std::move(url),
323 std::move(headers),
324 nullptr,
325 std::make_unique<ccf::curl::ResponseBody>(
326 endpoint.max_client_response_size),
327 std::move(response_callback));
328
330 "Fetching endorsements for attestation report at {}",
331 request->get_url());
333 std::move(request));
334 }
335
336 public:
340 config(std::move(config_)),
341 done_cb(std::move(cb)) {};
342
344 {
345 std::lock_guard<ccf::pal::Mutex> guard(this->lock);
346 servers = std::list<Server>(config.servers);
347 server_retries_count = 0;
348
349 fetch_unsafe();
350 }
351 };
352}
Definition quote_endorsements_client.h:40
void fetch_endorsements()
Definition quote_endorsements_client.h:343
QuoteEndorsementsClient(pal::snp::EndorsementEndpointsConfiguration config_, QuoteEndorsementsFetchedCallback cb)
Definition quote_endorsements_client.h:337
std::vector< uint8_t > raw() const
Definition pem.h:71
static CurlmLibuvContext & get_instance()
Definition curl.h:1031
Definition curl.h:53
void set_opt(auto option, auto value)
Definition curl.h:107
Definition curl.h:149
#define LOG_INFO_FMT
Definition internal_logger.h:15
#define LOG_DEBUG_FMT
Definition internal_logger.h:14
#define LOG_FAIL_FMT
Definition internal_logger.h:16
ccf::crypto::Pem cert_der_to_pem(const std::vector< uint8_t > &der)
Definition verifier.cpp:33
std::mutex Mutex
Definition locking.h:12
Task make_basic_task(Ts &&... ts)
Definition basic_task.h:33
void add_task(Task task)
Definition task_system.cpp:65
void add_delayed_task(Task task, std::chrono::milliseconds delay)
Definition task_system.cpp:70
Definition app_interface.h:14
pal::snp::EndorsementEndpointsConfiguration::Server Server
Definition quote_endorsements_client.h:21
std::function< void(std::vector< uint8_t > &&endorsements)> QuoteEndorsementsFetchedCallback
Definition quote_endorsements_client.h:20
STL namespace.
Definition attestation_sev_snp_endorsements.h:41
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 task.h:15