CCF
Loading...
Searching...
No Matches
attestation.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
5#include "ccf/crypto/ecdsa.h"
6#include "ccf/crypto/pem.h"
8#include "ccf/ds/hex.h"
9#include "ccf/ds/logger.h"
10#include "ccf/ds/quote_info.h"
12#include "ccf/pal/measurement.h"
13#include "ccf/pal/snp_ioctl.h"
14
15#include <fcntl.h>
16#include <functional>
17
18#if !defined(INSIDE_ENCLAVE) || defined(VIRTUAL_ENCLAVE)
19# include <sys/ioctl.h>
20# ifdef SGX_ATTESTATION_VERIFICATION
22# endif
23#else
25#endif
26
27namespace ccf::pal
28{
29 // Caller-supplied callback used to retrieve endorsements as specified by
30 // the config argument. When called back, the quote_info argument will have
31 // already been populated with the raw quote.
32 using RetrieveEndorsementCallback = std::function<void(
33 const QuoteInfo& quote_info,
35
36 // Verifying SNP attestation report is available on all platforms as unlike
37 // SGX, this does not require external dependencies (Open Enclave for SGX).
38 static void verify_snp_attestation_report(
39 const QuoteInfo& quote_info,
42 {
43 if (quote_info.format != QuoteFormat::amd_sev_snp_v1)
44 {
45 throw std::logic_error(fmt::format(
46 "Unexpected attestation report to verify for SEV-SNP: {}",
47 quote_info.format));
48 }
49
50 if (quote_info.quote.size() != sizeof(snp::Attestation))
51 {
52 throw std::logic_error(fmt::format(
53 "Input SEV-SNP attestation report is not of expected size {}: {}",
54 sizeof(snp::Attestation),
55 quote_info.quote.size()));
56 }
57
58 auto quote =
59 *reinterpret_cast<const snp::Attestation*>(quote_info.quote.data());
60
61 if (quote.version < snp::minimum_attestation_version)
62 {
63 throw std::logic_error(fmt::format(
64 "SEV-SNP: Attestation version is {} not >= expected minimum {}",
65 quote.version,
66 snp::minimum_attestation_version));
67 }
68
69 if (quote.flags.signing_key != snp::attestation_flags_signing_key_vcek)
70 {
71 throw std::logic_error(fmt::format(
72 "SEV-SNP: Attestation report must be signed by VCEK: {}",
73 static_cast<uint8_t>(quote.flags.signing_key)));
74 }
75
76 if (quote.flags.mask_chip_key != 0)
77 {
78 throw std::logic_error(
79 fmt::format("SEV-SNP: Mask chip key must not be set"));
80 }
81
82 // Introduced in
83 // https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/programmer-references/56860.pdf
84 // The guest sets the VMPL field to a value from 0 thru 3 which indicates a
85 // request from the guest. For a Guest requested attestation report this
86 // field will contain the value (0-3). A Host requested attestation report
87 // will have a value of 0xffffffff. CCF current always sets VMPL to 0, and
88 // rejects non-guest values.
89 if (quote.vmpl > 3)
90 {
91 throw std::logic_error(fmt::format(
92 "SEV-SNP: VMPL for guest attestations must be in 0-3 range, not {}",
93 quote.vmpl));
94 }
95
96 report_data = SnpAttestationReportData(quote.report_data);
97 measurement = SnpAttestationMeasurement(quote.measurement);
98
99 auto certificates = ccf::crypto::split_x509_cert_bundle(std::string_view(
100 reinterpret_cast<const char*>(quote_info.endorsements.data()),
101 quote_info.endorsements.size()));
102 if (certificates.size() != 3)
103 {
104 throw std::logic_error(fmt::format(
105 "Expected 3 endorsement certificates but got {}", certificates.size()));
106 }
107
108 // chip_cert (VCEK) <-signs- sev_version (ASK)
109 // ASK <-signs- root_certificate (ARK)
110 auto chip_certificate = certificates[0];
111 auto sev_version_certificate = certificates[1];
112 auto root_certificate = certificates[2];
113
114 auto root_cert_verifier = ccf::crypto::make_verifier(root_certificate);
115
116 std::string expected_root_public_key;
117 if (quote.version < 3)
118 {
119 // before version 3 there are no cpuid fields so we must assume that it is
120 // milan
121 expected_root_public_key = snp::amd_milan_root_signing_public_key;
122 }
123 else
124 {
125 auto key = snp::amd_root_signing_keys.find(
126 snp::get_sev_snp_product(quote.cpuid_fam_id, quote.cpuid_mod_id));
127 if (key == snp::amd_root_signing_keys.end())
128 {
129 throw std::logic_error(fmt::format(
130 "SEV-SNP: Unsupported CPUID family {} model {}",
131 quote.cpuid_fam_id,
132 quote.cpuid_mod_id));
133 }
134 expected_root_public_key = key->second;
135 }
136 if (root_cert_verifier->public_key_pem().str() != expected_root_public_key)
137 {
138 throw std::logic_error(fmt::format(
139 "SEV-SNP: The root of trust public key for this attestation was not "
140 "the expected one for v{} {} {}: {} != {}",
141 quote.version,
142 quote.cpuid_fam_id,
143 quote.cpuid_mod_id,
144 root_cert_verifier->public_key_pem().str(),
145 expected_root_public_key));
146 }
147
148 if (!root_cert_verifier->verify_certificate({&root_certificate}))
149 {
150 throw std::logic_error(
151 "SEV-SNP: The root of trust public key for this attestation was not "
152 "self signed as expected");
153 }
154
155 auto chip_cert_verifier = ccf::crypto::make_verifier(chip_certificate);
156 if (!chip_cert_verifier->verify_certificate(
157 {&root_certificate}, {&sev_version_certificate}))
158 {
159 throw std::logic_error(
160 "SEV-SNP: The chain of signatures from the root of trust to this "
161 "attestation is broken");
162 }
163
164 // According to Table 134 (2025-06-12) only ecdsa_p384_sha384 is supported
165 if (quote.signature_algo != snp::SignatureAlgorithm::ecdsa_p384_sha384)
166 {
167 throw std::logic_error(fmt::format(
168 "SEV-SNP: Unsupported signature algorithm: {} (supported: {})",
169 quote.signature_algo,
171 }
172
173 // Make ASN1 DER signature
174 auto quote_signature = ccf::crypto::ecdsa_sig_from_r_s(
175 quote.signature.r,
176 sizeof(quote.signature.r),
177 quote.signature.s,
178 sizeof(quote.signature.s),
179 false /* little endian */
180 );
181
182 std::span quote_without_signature{
183 quote_info.quote.data(),
184 quote_info.quote.size() - sizeof(quote.signature)};
185 if (!chip_cert_verifier->verify(quote_without_signature, quote_signature))
186 {
187 throw std::logic_error(
188 "SEV-SNP: Chip certificate (VCEK) did not sign this attestation");
189 }
190
191 // We should check this (although not security critical) but the guest
192 // policy ABI is currently set to 0.31, although we are targeting 1.54
193 // if (quote.policy.abi_major < snp::attestation_policy_abi_major)
194 // {
195 // throw std::logic_error(fmt::format(
196 // "SEV-SNP: Attestation guest policy ABI major {} must be greater than
197 // " "or equal to {}", quote.policy.abi_major,
198 // snp::attestation_policy_abi_major));
199 // }
200
201 if (quote.policy.debug != 0)
202 {
203 throw std::logic_error(
204 "SEV-SNP: SNP attestation report guest policy debugging must not be "
205 "enabled");
206 }
207
208 if (quote.policy.migrate_ma != 0)
209 {
210 throw std::logic_error("SEV-SNP: Migration agents must not be enabled");
211 }
212
213 // Only has value when endorsements are retrieved from environment
214 if (quote_info.endorsed_tcb.has_value())
215 {
216 const auto& endorsed_tcb = quote_info.endorsed_tcb.value();
217 auto raw_tcb = ds::from_hex(quote_info.endorsed_tcb.value());
218
219 if (raw_tcb.size() != sizeof(snp::TcbVersion))
220 {
221 throw std::logic_error(fmt::format(
222 "SEV-SNP: TCB of size {}, expected {}",
223 raw_tcb.size(),
224 sizeof(snp::TcbVersion)));
225 }
226
227 snp::TcbVersion tcb = *reinterpret_cast<snp::TcbVersion*>(raw_tcb.data());
228 if (tcb != quote.reported_tcb)
229 {
230 auto* reported_tcb = reinterpret_cast<uint8_t*>(&quote.reported_tcb);
231 throw std::logic_error(fmt::format(
232 "SEV-SNP: endorsed TCB {} does not match reported TCB {}",
233 endorsed_tcb,
234 ds::to_hex(
235 {reported_tcb, reported_tcb + sizeof(quote.reported_tcb)})));
236 }
237 }
238 }
239
240#if (defined(INSIDE_ENCLAVE) && !defined(VIRTUAL_ENCLAVE)) || \
241 defined(SGX_ATTESTATION_VERIFICATION)
242 static void verify_sgx_quote(
243 const QuoteInfo& quote_info,
244 PlatformAttestationMeasurement& measurement,
245 PlatformAttestationReportData& report_data)
246 {
247 if (quote_info.format != QuoteFormat::oe_sgx_v1)
248 {
249 throw std::logic_error(fmt::format(
250 "Unexpected attestation quote to verify for SGX: {}",
251 quote_info.format));
252 }
253
254 sgx::Claims claims;
255
256 auto rc = oe_verify_evidence(
257 &sgx::oe_quote_format,
258 quote_info.quote.data(),
259 quote_info.quote.size(),
260 quote_info.endorsements.data(),
261 quote_info.endorsements.size(),
262 nullptr,
263 0,
264 &claims.data,
265 &claims.length);
266 if (rc != OE_OK)
267 {
268 throw std::logic_error(fmt::format(
269 "Failed to verify evidence in SGX attestation report: {}",
270 oe_result_str(rc)));
271 }
272
273 std::optional<SgxAttestationMeasurement> claim_measurement = std::nullopt;
274 std::optional<SgxAttestationReportData> custom_claim_report_data =
275 std::nullopt;
276 for (size_t i = 0; i < claims.length; i++)
277 {
278 auto& claim = claims.data[i];
279 auto claim_name = std::string(claim.name);
280 if (claim_name == OE_CLAIM_UNIQUE_ID)
281 {
282 if (claim.value_size != SgxAttestationMeasurement::size())
283 {
284 throw std::logic_error(
285 fmt::format("SGX measurement claim is not of expected size"));
286 }
287
288 claim_measurement =
289 SgxAttestationMeasurement({claim.value, claim.value_size});
290 }
291 else if (claim_name == OE_CLAIM_CUSTOM_CLAIMS_BUFFER)
292 {
293 // Find sgx report data in custom claims
294 sgx::CustomClaims custom_claims;
295 rc = oe_deserialize_custom_claims(
296 claim.value,
297 claim.value_size,
298 &custom_claims.data,
299 &custom_claims.length);
300 if (rc != OE_OK)
301 {
302 throw std::logic_error(fmt::format(
303 "Failed to deserialise custom claims in SGX attestation report",
304 oe_result_str(rc)));
305 }
306
307 for (size_t j = 0; j < custom_claims.length; j++)
308 {
309 const auto& custom_claim = custom_claims.data[j];
310 if (std::string(custom_claim.name) == sgx::report_data_claim_name)
311 {
312 if (custom_claim.value_size != SgxAttestationReportData::size())
313 {
314 throw std::logic_error(fmt::format(
315 "Expected claim {} of size {}, had size {}",
316 sgx::report_data_claim_name,
317 SgxAttestationReportData::size(),
318 custom_claim.value_size));
319 }
320
321 custom_claim_report_data = SgxAttestationReportData(
322 {custom_claim.value, custom_claim.value_size});
323
324 break;
325 }
326 }
327 }
328 }
329
330 if (!claim_measurement.has_value())
331 {
332 throw std::logic_error(
333 "Could not find measurement in SGX attestation report");
334 }
335
336 if (!custom_claim_report_data.has_value())
337 {
338 throw std::logic_error(
339 "Could not find report data in SGX attestation report");
340 }
341
342 measurement = claim_measurement.value();
343 report_data = custom_claim_report_data.value();
344 }
345#endif
346
347#if defined(PLATFORM_VIRTUAL)
348
349 static void generate_quote(
350 PlatformAttestationReportData& report_data,
351 RetrieveEndorsementCallback endorsement_cb,
352 const snp::EndorsementsServers& endorsements_servers = {})
353 {
354 endorsement_cb(
355 {
357 },
358 {});
359 }
360
361#elif defined(PLATFORM_SNP)
362
363 static void generate_quote(
364 PlatformAttestationReportData& report_data,
365 RetrieveEndorsementCallback endorsement_cb,
366 const snp::EndorsementsServers& endorsements_servers = {})
367 {
368 QuoteInfo node_quote_info = {};
369 node_quote_info.format = QuoteFormat::amd_sev_snp_v1;
370 auto attestation = snp::get_attestation(report_data);
371
372 node_quote_info.quote = attestation->get_raw();
373
374 if (endorsement_cb != nullptr)
375 {
376 endorsement_cb(
377 node_quote_info,
378 snp::make_endorsement_endpoint_configuration(
379 attestation->get(), endorsements_servers));
380 }
381 }
382#endif
383
384#if !defined(INSIDE_ENCLAVE) || defined(VIRTUAL_ENCLAVE)
385
386 static void verify_quote(
387 const QuoteInfo& quote_info,
388 PlatformAttestationMeasurement& measurement,
389 PlatformAttestationReportData& report_data)
390 {
391 auto is_sev_snp = snp::is_sev_snp();
392
393 if (quote_info.format == QuoteFormat::insecure_virtual)
394 {
395 if (is_sev_snp)
396 {
397 throw std::logic_error(
398 "Cannot verify virtual attestation report if node is SEV-SNP");
399 }
400 // For now, virtual resembles SGX (mostly for historical reasons)
401 measurement = SgxAttestationMeasurement();
402 report_data = SgxAttestationReportData();
403 }
404 else if (quote_info.format == QuoteFormat::amd_sev_snp_v1)
405 {
406 if (!is_sev_snp)
407 {
408 throw std::logic_error(
409 "Cannot verify SEV-SNP attestation report if node is virtual");
410 }
411
412 verify_snp_attestation_report(quote_info, measurement, report_data);
413 }
414 else
415 {
416# if defined(SGX_ATTESTATION_VERIFICATION)
417 verify_sgx_quote(quote_info, measurement, report_data);
418# else
419 if (is_sev_snp)
420 {
421 throw std::logic_error(
422 "Cannot verify SGX attestation report if node is SEV-SNP");
423 }
424 else
425 {
426 throw std::logic_error(
427 "Cannot verify SGX attestation report if node is virtual");
428 }
429# endif
430 }
431 }
432
433#else // SGX
434
435 static void generate_quote(
436 PlatformAttestationReportData& report_data,
437 RetrieveEndorsementCallback endorsement_cb,
438 const snp::EndorsementsServers& endorsements_servers = {})
439 {
440 QuoteInfo node_quote_info = {};
441 node_quote_info.format = QuoteFormat::oe_sgx_v1;
442
443 sgx::Evidence evidence;
444 sgx::Endorsements endorsements;
445 sgx::SerialisedClaims serialised_custom_claims;
446
447 const size_t custom_claim_length = 1;
448 oe_claim_t custom_claim;
449 custom_claim.name = const_cast<char*>(sgx::report_data_claim_name);
450 custom_claim.value = report_data.data.data();
451 custom_claim.value_size = report_data.data.size();
452
453 auto rc = oe_serialize_custom_claims(
454 &custom_claim,
455 custom_claim_length,
456 &serialised_custom_claims.buffer,
457 &serialised_custom_claims.size);
458 if (rc != OE_OK)
459 {
460 throw std::logic_error(fmt::format(
461 "Could not serialise node's public key as quote custom claim: {}",
462 oe_result_str(rc)));
463 }
464
465 rc = oe_get_evidence(
466 &sgx::oe_quote_format,
467 0,
468 serialised_custom_claims.buffer,
469 serialised_custom_claims.size,
470 nullptr,
471 0,
472 &evidence.buffer,
473 &evidence.size,
474 &endorsements.buffer,
475 &endorsements.size);
476 if (rc != OE_OK)
477 {
478 throw std::logic_error(
479 fmt::format("Failed to get evidence: {}", oe_result_str(rc)));
480 }
481
482 node_quote_info.quote.assign(
483 evidence.buffer, evidence.buffer + evidence.size);
484 node_quote_info.endorsements.assign(
485 endorsements.buffer, endorsements.buffer + endorsements.size);
486
487 if (endorsement_cb != nullptr)
488 {
489 endorsement_cb(node_quote_info, {});
490 }
491 }
492
493 static void verify_quote(
494 const QuoteInfo& quote_info,
495 PlatformAttestationMeasurement& measurement,
496 PlatformAttestationReportData& report_data)
497 {
498 if (quote_info.format == QuoteFormat::insecure_virtual)
499 {
500 throw std::logic_error(fmt::format(
501 "Cannot verify virtual insecure attestation report on SGX platform"));
502 }
503 else if (quote_info.format == QuoteFormat::amd_sev_snp_v1)
504 {
505 verify_snp_attestation_report(quote_info, measurement, report_data);
506 return;
507 }
508
509 verify_sgx_quote(quote_info, measurement, report_data);
510 }
511
512#endif
513}
std::vector< ccf::crypto::Pem > split_x509_cert_bundle(const std::string_view &pem)
Definition pem.cpp:36
VerifierPtr make_verifier(const std::vector< uint8_t > &cert)
Definition verifier.cpp:18
std::vector< uint8_t > ecdsa_sig_from_r_s(const uint8_t *r, size_t r_size, const uint8_t *s, size_t s_size, bool big_endian=true)
Definition ecdsa.cpp:14
constexpr auto amd_milan_root_signing_public_key
Definition attestation_sev_snp.h:23
ProductName get_sev_snp_product(AMDFamily family, AMDModel model)
Definition sev_snp_cpuid.h:42
std::vector< EndorsementsServer > EndorsementsServers
Definition attestation_sev_snp_endorsements.h:81
const std::map< ProductName, const char * > amd_root_signing_keys
Definition attestation_sev_snp.h:72
Definition attestation.h:28
std::function< void(const QuoteInfo &quote_info, const snp::EndorsementEndpointsConfiguration &config)> RetrieveEndorsementCallback
Definition attestation.h:34
AttestationMeasurement< snp_attestation_measurement_size > SnpAttestationMeasurement
Definition measurement.h:107
AttestationMeasurement< sgx_attestation_measurement_size > SgxAttestationMeasurement
Definition measurement.h:97
AttestationReportData< sgx_attestation_report_data_size > SgxAttestationReportData
Definition report_data.h:42
AttestationReportData< snp_attestation_report_data_size > SnpAttestationReportData
Definition report_data.h:47
Describes a quote (attestation) from trusted hardware.
Definition quote_info.h:26
QuoteFormat format
Quote format.
Definition quote_info.h:28
std::optional< std::string > endorsed_tcb
Endorsed TCB (hex-encoded)
Definition quote_info.h:36
std::vector< uint8_t > quote
Enclave quote.
Definition quote_info.h:30
std::vector< uint8_t > endorsements
Quote endorsements.
Definition quote_info.h:32
Definition measurement.h:116
Definition report_data.h:51
Definition attestation_sev_snp.h:164
Definition attestation_sev_snp_endorsements.h:38
Definition attestation_sev_snp.h:82
#define oe_result_str(x)
Definition virtual_enclave.h:52
constexpr oe_result_t OE_OK
Definition virtual_enclave.h:41